Fix for responsive video source selection failing in Chrome and Firefox when using
<source media="..."> inside <video>. Use when: (1) building a video player that needs
different sources for mobile vs desktop (portrait vs landscape), (2) <source media>
attribute is ignored and wrong video loads, (3) need cross-browser responsive video
without HLS/DASH streaming. Covers the matchMedia JS workaround pattern for React
components with poster frame selection.
name: responsive-video-source-selection
description: |
Fix for responsive video source selection failing in Chrome and Firefox when using
inside
Responsive Video Source Selection
Problem
The HTML <source media="..."> attribute inside <video> elements does NOT work
reliably across browsers for responsive video selection. Chrome/Blink and Firefox
ignore the media attribute entirely, always selecting the first <source> that
matches by type. Only Safari/WebKit respects it.
key={videoSrc} on the <video> element forces React to remount it when the
source changes. Without this, swapping <source> children alone won't trigger a
reload in all browsers.
Evaluate at open/mount time, not continuously. Video source selection is a
one-time decision per playback session (unlike images, you can't seamlessly swap
video mid-stream without losing playback position).
Select poster frame too — if you have responsive poster frames, select them
with the same logic.
Single <source> — since JS already selected the right file, you only need
one <source> element (not two with media queries).
Vanilla JS Pattern
const mql = window.matchMedia('(max-width: 639px)');
const video = document.querySelector('video');
video.src = mql.matches ? 'portrait.mp4' : 'landscape.mp4';
video.load(); // Required to apply new source
Verification
Open Chrome DevTools → Network tab
Open the video player on desktop → verify landscape.mp4 loads (not portrait)
Use DevTools responsive mode at 390px width → reload → verify portrait.mp4 loads
Check the <source> element's src attribute in Elements panel
Example
From this project's VideoLightbox.tsx:
// Select video source based on viewport width at open time.
// <source media="..."> is unreliable in <video> across browsers,
// so we use JS-based selection instead.
const [isPortrait, setIsPortrait] = useState(false);
useEffect(() => {
if (isOpen) {
setIsPortrait(window.matchMedia('(max-width: 639px)').matches);
}
}, [isOpen]);
const videoSrc = isPortrait ? portraitSrc : landscapeSrc;
const posterSrc = isPortrait ? portraitPoster : landscapePoster;
Notes
The media attribute on <source> works reliably inside <picture> — this issue
is specific to <video> and <audio>.
The attribute was removed from the WHATWG HTML5 spec in 2014, re-added in 2023,
but Chrome (Blink) and Firefox have not re-implemented support.
Safari/WebKit never removed support, so it works there. Don't be fooled by Safari-only
testing.
For production video delivery at scale, consider HLS/DASH streaming which handles
adaptive bitrate natively. The JS workaround is best for simple use cases (1-2 video
variants).
Remember to test with window.matchMedia mock in unit tests: