Conversation
Replace the *-video-element and @mux/mux-player-react dependencies with the @videojs/react media components (@videojs/react/media/*). Each player is wrapped by createMediaPlayer, which maps src and the player's config onto the v10 source prop and passes ReactPlayer's ref as mediaRef, so ref.current is the playable media for every player (the embed iframe is ref.current.target). Breaking changes: - React 17 is no longer supported; the react peer range is now ^18 || ^19. - config keys are now the v10 engine options (config.hls -> hls.js config, config.dash -> dash.js settings, config.mux -> MuxSource options, etc.). - The ref for embed players (YouTube, Vimeo, Wistia, Spotify, Twitch, TikTok) is the playback adapter rather than a custom element. The media API is unchanged; use ref.current.target for the DOM node. BREAKING CHANGE: requires React 18+, config keys are now the Video.js v10 engine options, and the ref for embed players is the playback adapter (DOM node at ref.current.target).
luwes
force-pushed
the
feat/videojs-v10-media
branch
from
October 2, 2026 00:09
1ab7c58 to
4ab26b9
Compare
luwes
marked this pull request as ready for review
October 2, 2026 00:09
luwes
added this pull request to stack #2051
October 2, 2026 00:09
1 task done
luwes
force-pushed
the
feat/videojs-v10-media
branch
from
October 2, 2026 00:33
c61bb46 to
4ab26b9
Compare
config now mirrors v10's source.engine: engine options are keyed by engine name (hlsJs, dashJs, nativeHls, youtube, vimeo, spotify, twitch, tiktok) and every player gets the same object as source.engine, reading only its own key. This keeps one prop for configuring every player while dropping the per-player config lookup and engineSource(key). mux and wistia keep their own keys for their non-engine source options; Mux also reads hlsJs and nativeHls. Also simplifies the surrounding code: - MediaPlayer no longer memoizes source, since v10 compares sources structurally. - ReactPlayer finds the active player with Array#find/#some. - PlayerEntry drops the unused name field. - VideoElementProps extends React's VideoHTMLAttributes instead of redeclaring it. - HtmlPlayer no longer passes config to the native element. Breaking changes: - config.hls is now config.hlsJs and config.dash is now config.dashJs. - config.mux no longer takes engine; use config.hlsJs / config.nativeHls, which Mux reads too. - Custom players receive the whole config object instead of their own key. - PlayerEntry no longer has a name field. BREAKING CHANGE: config is keyed by Video.js v10 engine name (config.hls -> config.hlsJs, config.dash -> config.dashJs), custom players receive the whole config, and PlayerEntry drops name.
Replace npm, the custom scripts/builder and scripts/tester esbuild wrappers, Biome, c8 and zora/sinon with pnpm and Vite+ (vp): - build: vp pack (unbundled ESM, target es2019) with tsgo declarations - demo: Vite app in examples/react, resolving react-player to src - test: vp test (Vitest); tests converted from zora/sinon and named *.test.* - lint/format: Oxlint and Oxfmt via vp check, with a vp staged pre-commit hook - typecheck: tsgo --noEmit - CI/CD: pnpm, Node from .node-version, plus a typecheck step Also formats the codebase with Oxfmt, and indexes Player's props by `keyof typeof props` so it typechecks under tsgo.
Follow the Video.js v10 media contract: ReactPlayer's `ref` receives the rendered element (the `<video>`/`<audio>`, the embed's `<iframe>`, or `<wistia-player>`), and the new `mediaRef` prop receives the HTMLMediaElement-compatible object that plays the media (the element itself for files and streams, the playback adapter for embeds). - Player drives playing/volume/playbackRate/pip through the media, composing its own ref with `mediaRef` via v10's useComposedRefs. This also stops calling useCallback after an early return. - MediaPlayer passes `ref` and `mediaRef` straight through to the v10 component. - HtmlPlayer hands the native element to both refs. - The demo and README use `mediaRef` for instance methods. Breaking changes: - `ref` is the DOM element. Use `mediaRef` for the media API (`play()`, `currentTime`, ...), which for embeds was previously on `ref`. - Custom players must forward `ref` to their element and hand the media to the `mediaRef` prop. BREAKING CHANGE: ReactPlayer's `ref` points to the DOM element; the media API moves to the new `mediaRef` prop, and custom players must accept `mediaRef`.
Replace the URL regexes in patterns.ts with resolveAdapterType and resolveMimeType from @videojs/react, so ReactPlayer recognizes the same sources as the Video.js v10 media components it renders. - YouTube, Vimeo, Wistia, Mux, Spotify, Twitch, TikTok and DASH sources are matched by adapter type. - Mux stream URLs ending in .m3u8 still play with hls.js. - File playback keeps AUDIO_EXTENSIONS and VIDEO_EXTENSIONS as a fallback for extensions Video.js does not recognize (m4v, m4b, weba, oga, spx, ...). Newly recognized sources: - localized Spotify URLs (open.spotify.com/intl-de/track/...) and spotify: URIs - youtube/<id> and vimeo/<id> shorthands - .flac files Breaking changes: - react-player/patterns no longer exports HLS_EXTENSIONS, DASH_EXTENSIONS, MATCH_URL_MUX, MATCH_URL_YOUTUBE, MATCH_URL_VIMEO, MATCH_URL_WISTIA, MATCH_URL_SPOTIFY, MATCH_URL_TWITCH or MATCH_URL_TIKTOK. Use resolveAdapterType from @videojs/react instead. - These URLs are no longer matched to a service player and fall back to the HTML player: youtube.com/user/..., music.youtube.com, vimeo.com/channels/..., vimeo.com/showcase/... and player.twitch.tv/?video=... BREAKING CHANGE: URL pattern exports were removed from react-player/patterns in favor of resolveAdapterType from @videojs/react, and some URLs now resolve to a different player.
Every built-in player key is a Video.js adapter type (plus html), so patterns.ts resolves a URL to its player key once and canPlay(key) compares against it, instead of a table of per-player predicates. The Mux .m3u8 -> hls.js rule and the extension fallback for the HTML player are unchanged; player selection is identical for every URL compared. Also removes canPlayFile, which handled arrays of sources that src no longer accepts.
Breaking changes:
- react-player/patterns exports canPlay as a function of the player key: canPlay.youtube(url) becomes canPlay('youtube')(url).
BREAKING CHANGE: canPlay in react-player/patterns is now canPlay(key)(url) instead of canPlay[key](url).
Covers React 18, ref vs mediaRef (and api -> engine for embed SDKs), the engine-keyed config and its changed option shapes, Mux moving from Mux Player to MuxVideo, Media Chrome with embeds, URL matching via resolveAdapterType, the react-player/patterns changes, the custom player contract and the dropped *-video-element dependencies. The README links to it and drops the Mux Player-only --controls variable from the Media Chrome example.
1 task done
TikTok options are 0 | 1 in Video.js v10 instead of booleans.
Of the URLs v3 matched and resolveAdapterType does not, only music.youtube.com/watch?v= and player.twitch.tv/?video= actually played in v3; the YouTube user, Vimeo channel/showcase and player.twitch.tv URLs with Twitch's own params were matched but failed to parse in the v3 elements too. The two that did play are tracked upstream in videojs/v10#3139, to be picked up with the @videojs bump before release.
The v10 media components register with a surrounding v10 Player on their own; HtmlPlayer's plain <video>/<audio> did not, so v10 extensions such as Mux Data and Google Cast never saw file sources. Register it with useMediaAttach, like v10's own Video component.
Mux Player sent Mux Data automatically. In v4, analytics and casting come from v10 extensions: wrap ReactPlayer in VideoPlayer and add MuxData, GoogleCast and CastButton. Covers the envKey rules, casting embeds, controlling playback while casting, and sharing one @videojs/react copy.
A v10 PlayButton inside VideoPlayer plays ReactPlayer's native media. Fails without HtmlPlayer attaching to the surrounding player.
Replace the Media Chrome example with VideoPlayer + VideoSkin and VideoPlayer + UI components examples, which work with every source, embeds included. The migration guide points Mux and Media Chrome users to them and keeps a note that Media Chrome can't control embeds in v4.
The migration guide said the hls.js and dash.js instances were no longer exposed. They are, as `.engine` on the Video.js adapter, but for HLS, DASH and Mux `mediaRef` is the <video> element, so the adapter is reached with useMedia() inside a v10 player. Document that, and add a test that useMedia() returns the hls.js adapter (with `engine`) for a stream ReactPlayer renders.
Closed
2 tasks done
A jscodeshift transform in codemods/v4.ts that renames ref to mediaRef, migrates config keys and values, rewrites react-player/patterns usage and removes name from custom player entries. Changes that need a decision are left as TODO(react-player v4) comments. Run it with `pnpm codemod:v4 <dir>`, or from its URL as described in MIGRATING.md.
decepulis
approved these changes
Oct 2, 2026
decepulis
left a comment
There was a problem hiding this comment.
Huge. Excited for how this sets up react-player to take advantage of everything Video.js has to offer!
10.0.1 brings the fixes ReactPlayer was waiting on: - youtube-video: unmounting YouTube no longer crashes React with removeChild, e.g. when src changes from YouTube to another player (videojs/v10#3153, #3154) - react: autoPlay reaches the embed players, and the React 18 callback-ref warning is gone (videojs/v10#3136) - react: mediaRef is the playback adapter for HLS, DASH and Mux too, so the engine is mediaRef.current.engine for every player (videojs/v10#3141) - mux-video: extension-less stream.mux.com URLs parse to a playback ID (videojs/v10#3143) Updates the hls.js adapter test, the migration guide, README and codemod for the mediaRef change: the codemod no longer flags .engine, and the guide drops the useMedia() workaround for streams.
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Moves ReactPlayer's media from the standalone
*-video-elementpackages and@mux/mux-player-reactonto the Video.js v10 React media components (@videojs/react/media/*). The public API stays the same apart from the breaking changes below: the same props, callbacks, static methods and lazy loading per player. Players are picked with v10'sresolveAdapterType, so ReactPlayer recognizes the same sources as the media it renders. It also migrates the repo's tooling to pnpm and Vite+. (Includes #2050.)How it works
Every v10 media component behaves like a
<video>tag: the same attributes andon*callbacks, arefthat receives the rendered element, and amediaRefthat receives the playable media. That's the<video>itself for a file, or the playback adapter for a stream or an embed. ReactPlayer now exposes exactly the samerefandmediaRef, so the glue here is small:src/MediaPlayer.tsx:createMediaPlayer(Component, toMediaProps)mapssrc+configonto the v10sourceprop. Everything else,refandmediaRefincluded, passes through unchanged. v10 compares sources structurally, so nothing is memoized.src/sources.ts: thesrc/config→sourcemappings:engineSource(the default):configis keyed by engine name exactly like v10'ssource.engine, so every player gets the same{ src, engine: config }and reads only its own key. One prop still configures every player.muxSource: ReactPlayer matches extension-less Mux URLs (stream.mux.com/<id>?…), which become{ playbackId, playback }, withconfig.muxlayered on top. Mux plays through hls.js, so it also readsconfig.hlsJs/config.nativeHls.wistiaSource:srcplusconfig.wistiaas Wistia's own options.src/patterns.ts: resolves a URL to its player key once withresolveAdapterType(andresolveMimeType) from@videojs/react. Every built-in player key is a v10 adapter type, plushtml, so each player'scanPlayiscanPlay('<key>'). Two rules stay on top: Mux URLs ending in.m3u8still play with hls.js, and the HTML player keepsAUDIO_EXTENSIONS/VIDEO_EXTENSIONSas a fallback for extensions v10 doesn't recognize (m4v,m4b,weba,oga,spx, …).src/players.ts: each entry lazy-loads its@videojs/react/media/*component, so code splitting per player is unchanged.src/Player.tsx: drivesplaying,volume,playbackRateandpipthrough the media, composing its own ref withmediaRefvia v10'suseComposedRefs.src/HtmlPlayer.tsx: a native<video>/<audio>both renders and plays the media, so it is bothrefandmediaRef.src/types.ts:Configis composed from the v10*EngineConfigtypes, plusmuxandwistia.mediaRef.currentis an HTMLMediaElement-like object for every player, soplaying,volume,playbackRate,pipandmediaRef.current.currentTime = …work the same everywhere.ref.currentis the DOM element: the<video>/<audio>, the embed's<iframe>, or<wistia-player>.mediaRef.current.engineis the engine for every adapter: hls.js, dash.js, or the embed's SDK.Breaking changes
React 17 is no longer supported. The peer range is now
^18 || ^19, because@videojs/reactrequires React 18 or later.configis keyed by v10 engine name and holds that engine's own options:config.hlsis nowconfig.hlsJs(hls.js config),config.dashis nowconfig.dashJs(dash.js settings), plusnativeHls,youtube,vimeo,spotify,twitchandtiktok.config.muxtakesMuxSourceoptions (playback,poster,storyboard,drm); Mux readsconfig.hlsJsfor its engine. The README table is updated.Custom players receive the whole
configrather than only their own key, andPlayerEntryno longer has anamefield.refpoints to the DOM element; the media API moves to the newmediaRefprop. For files both are the<video>/<audio>element. For HLS, DASH and Mux,refis the<video>andmediaRefis the playback adapter that drives it. For embeds,refis the<iframe>(<wistia-player>for Wistia) andmediaRefis the playback adapter, whererefused to be a media custom element.Custom players must forward
refto the element they render and hand the media to themediaRefprop, like the v10 media components.Engines moved from
ref.current.apitomediaRef.current.engine, for every player except Wistia: hls.js for HLS and Mux, dash.js for DASH, and the embed's SDK for YouTube, Vimeo, Spotify, Twitch and TikTok.Mux plays with
MuxVideoinstead of Mux Player: no built-in UI (usecontrols, or a v10 skin or UI components), no automatic poster, and no built-in Mux Data. Mux Data and Google Cast now come from v10 extensions: wrap ReactPlayer in v10'sVideoPlayerand add<MuxData />,<GoogleCast />and<CastButton />next to it (see New).Media Chrome, which v3's README suggested for custom controls, can no longer control embeds, because
slot="media"now lands on the embed's<iframe>. File, HLS, DASH and Mux sources still render a<video>. v4 recommends Video.js v10 skins or UI components instead (see New), which work with every source.Some embed options changed shape to match the providers' own parameters: Spotify
startAt→tandtheme: 'dark'→theme: 0, TikTok booleans →0 | 1, Twitchtimenumber →'1h30m10s'. The unusedconfig.htmlis removed.react-player/patternsno longer exports the URL regexes (HLS_EXTENSIONS,DASH_EXTENSIONS,MATCH_URL_MUX,MATCH_URL_YOUTUBE,MATCH_URL_VIMEO,MATCH_URL_WISTIA,MATCH_URL_SPOTIFY,MATCH_URL_TWITCH,MATCH_URL_TIKTOK), andcanPlayis a function of the player key:canPlay.youtube(url)becomescanPlay('youtube')(url). UseresolveAdapterTypefrom@videojs/reactto classify URLs:New
URL matching loses nothing that played in v3. v3's regexes also matched
youtube.com/user/…,vimeo.com/channels/…,vimeo.com/showcase/…and Twitch's ownplayer.twitch.tv/?video=v…&parent=…embed URLs, but the v3 player elements couldn't parse those either. The two that did play,music.youtube.com/watch?v=…andplayer.twitch.tv/?video=<id>, are tracked upstream in Bug: resolveAdapterType Rejects music.youtube.com and player.twitch.tv URLs That the Embeds Can Play videojs/v10#3139 and need that fix before release (see the checklist).Custom controls with Video.js v10 skins or UI components. Wrap ReactPlayer in v10's
VideoPlayerand add a skin or individual controls; they drive files, streams and embeds alike. The README's Media Chrome example is replaced with these:Mux Data and Google Cast via v10 extensions. ReactPlayer's media attaches to a surrounding v10 player, so v10's extensions follow it whatever the source:
The v10 media components did this already;
HtmlPlayernow registers its native<video>/<audio>too (useMediaAttach, like v10'sVideo). The README and migration guide document it.Newly recognized sources: localized Spotify URLs (
open.spotify.com/intl-de/track/...),spotify:URIs,youtube/<id>andvimeo/<id>shorthands, and.flacfiles.Other changes
MIGRATING.mdhas a v3 → v4 section covering every breaking change, plus how to add Mux Data and Google Cast, and the README links to it.ReactPlayerfinds the active player withArray#find/#some, andVideoElementPropsextends React's ownVideoHTMLAttributesinstead of redeclaring it.es2020, because@wistia/wistia-playercontains BigInt literals.cloudflare-video-elementdependency.scripts/builder/scripts/testeresbuild wrappers, Biome, c8 and zora/sinon:vp pack(unbundled ESM,es2019) withtsgodeclarationsexamples/reactthat resolvesreact-playertosrcvp test(Vitest); tests are named*.test.*and run on Node 22 without flagsvp check, with avp stagedpre-commit hook; the codebase is formatted with Oxfmttsgo --noEmit.node-version, plus a typecheck stepTesting
pnpm lint,pnpm typecheck,pnpm test,pnpm buildandpnpm build:demopass, on each commit.test/MediaPlayer.test.tsx(source mapping, prop/callback pass-through,refas the element andmediaRefas the media via a fake adapter, playback props driven through the media) andtest/sources.test.tsx(the mappings), plus tests thatconfignever reaches a native<video>, that a native player hands the same element torefandmediaRef, that ReactPlayer's media attaches to a surrounding v10 player, and that a v10PlayButtonplays it (both fail without theHtmlPlayerchange), and thatmediaRefis the same hls.js adapter asuseMedia(), withengine, for a stream.@videojs/*@10.0.1and live sources:mediaRefis the adapter,engineis hls.js or dash.js, andplayingplays and pauses through it. An extension-less Mux URL gets its playback ID and poster.autoPlay mutedstarts YouTube and Vimeo.playing.YouTubeVideoinside ReactPlayer'sPlayer: the media is theYouTubeAdapterwithtargetset to the<iframe>,volume/playbackRateare applied,config.youtubereaches the embed URL, andonReady/onStart/onPlayfire. (Checked beforeconfigwas re-keyed and before theref/mediaRefsplit, when that adapter was onref.)resolveAdapterTypeacross ~50 URLs; the differences are the URLs described under New, each checked against the v3 player elements' own parsers. ThecanPlay(key)refactor picks the same player as the previous commit for every URL.Before merging
@videojs/*to that version (10.0.1)music.youtube.comandplayer.twitch.tvURLs), so URL matching has no regressions from v3. Still open; 10.0.1 still resolves both tonull.pnpm start)<MuxData />and<GoogleCast />around ReactPlayer in a browserVideoSkinaround ReactPlayer for a file, an HLS stream and an embed (sizing, controls, fullscreen)