# Responsive Video Source Selection

> 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.

- Skill: `hubeiqiao/responsive-video-source-selection` (Agent Skill)
- Install (CLI): `npx skillmds@latest add hubeiqiao/responsive-video-source-selection`
- Raw SKILL.md: https://api.skillmd.com/api/skills/hubeiqiao/responsive-video-source-selection/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Design & Media
- Author: hubeiqiao (https://skillmd.com/u/hubeiqiao)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/hubeiqiao/responsive-video-source-selection

---


# 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.

This means code like this **only works in Safari**:

```html
<!-- BROKEN in Chrome & Firefox -->
<video controls>
  <source src="portrait.mp4" type="video/mp4" media="(max-width: 639px)" />
  <source src="landscape.mp4" type="video/mp4" />
</video>
```

Chrome and Firefox will always load `portrait.mp4` (the first matching source)
regardless of viewport width.

## Context / Trigger Conditions

- Building a video lightbox or player with different video files for mobile vs desktop
- Using `<source media="(max-width: ...)">` inside `<video>` and the wrong video loads
- Portrait video loads on desktop, or landscape video loads on mobile
- Works in Safari but fails in Chrome/Edge/Firefox
- The `media` attribute was removed from the HTML spec in 2014, re-added in 2023, but
  Chrome and Firefox still don't implement it

## Solution

Use JavaScript `window.matchMedia()` to select the correct source at render time:

### React Pattern (Recommended)

```tsx
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;

return (
  <video
    key={videoSrc}  // Force remount when source changes
    controls
    playsInline
    poster={posterSrc}
    preload="metadata"
  >
    <source src={videoSrc} type="video/mp4" />
  </video>
);
```

### Key Details

1. **`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.

2. **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).

3. **Select poster frame too** — if you have responsive poster frames, select them
   with the same logic.

4. **Single `<source>`** — since JS already selected the right file, you only need
   one `<source>` element (not two with media queries).

### Vanilla JS Pattern

```js
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

1. Open Chrome DevTools → Network tab
2. Open the video player on desktop → verify landscape.mp4 loads (not portrait)
3. Use DevTools responsive mode at 390px width → reload → verify portrait.mp4 loads
4. Check the `<source>` element's `src` attribute in Elements panel

## Example

From this project's `VideoLightbox.tsx`:

```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:

```ts
function mockMatchMedia(matches: boolean) {
  Object.defineProperty(window, 'matchMedia', {
    writable: true,
    value: vi.fn().mockImplementation((query: string) => ({
      matches,
      media: query,
      onchange: null,
      addEventListener: vi.fn(),
      removeEventListener: vi.fn(),
      dispatchEvent: vi.fn(),
    })),
  });
}
```

## References

- [MDN: `<source>` element](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/source) — documents that `media` is allowed for `<picture>` parent, not `<audio>`/`<video>`
- [Can I Use: source media attribute](https://caniuse.com/mdn-html_elements_source_media) — shows fragmented browser support
- [Firefox Bug 1513109](https://bugzilla.mozilla.org/show_bug.cgi?id=1513109) — `media` attribute does not work on video source element
- [Chrome does not support media queries on video source tags](http://blog.greggant.com/posts/2019/08/02/responsive-videos-javascript-solution-video-source-tag.html) — JS workaround approach
- [Scott Jehl: Responsive Video](https://scottjehl.com/posts/responsive-video/) — history and current state of responsive video standards
- [Filament Group: HTML Video Sources Should Be Responsive](https://www.filamentgroup.com/lab/video-with-sizes/) — advocacy for spec support

