Version 4 is in alpha. It can still change between releases, and it isn't on npm yet. Version 3 docs

Migrating from version 3

What changed in version 4, and how to move a player over.

Version 4 is a rewrite, because almost everything in version 3 lived in one context that re-rendered every part as time changed. The ideas you know, such as Video.Root and asChild, carry over. Most of the work is listing plugins and renaming some parts.

At a glance

AreaVersion 3Version 4
Importimport { Video } from "react-video-kit"import * as Video from "react-video-kit"
CSSIncluded automaticallyimport "react-video-kit/styles.css"
ModulesESM and CommonJSESM only
Dependenciesmotion as a peer, plus Radix and othersNone, except the optional peers hls.js and dashjs
FeaturesProps on RootPlugins
Quality levelssrc arraysrc={{ renditions: [...] }}
A src array meansQuality levelsFallbacks, tried in order
autoPlayDefault onDefault off
Video fitcovercontain
RefMethods on the handleThe Player

A full example

Here's a typical player in version 3:

tsx
import { Video } from "react-video-kit";<Video.Root  src="/video.mp4"  title="The long ridge"  tracks={[{ src: "/en.vtt", kind: "captions", srclang: "en", label: "English" }]}  hotkeys  autoPlay={false}>  <Video.Media />  <Video.Backdrop />  <Video.Center>    <Video.PlayPause />    <Video.Loading />  </Video.Center>  <Video.Footer>    <Video.Title />    <Video.Timeline />    <Video.Time.Current />    <Video.CaptionsMenu />    <Video.FullscreenToggle />  </Video.Footer></Video.Root>;

If you don't need that exact layout, this is all you need in version 4:

tsx
"use client";import { captions } from "react-video-kit/captions";import { fullscreen } from "react-video-kit/fullscreen";import { hotkeys } from "react-video-kit/hotkeys";import { idle } from "react-video-kit/idle";import { VideoPlayer } from "react-video-kit/player";import "react-video-kit/styles.css";const plugins = [  idle(),  hotkeys(),  captions({ tracks: [{ src: "/en.vtt", language: "en", label: "English" }] }),  fullscreen(),];export function Player() {  return <VideoPlayer src="/video.mp4" plugins={plugins} />;}

To keep a custom layout, use the styled parts. The tables below show what each part became.

Update your imports and add the CSS

bash
npm install react-video-kit@alphanpm uninstall motion
tsx
import * as Video from "react-video-kit";import "react-video-kit/styles.css";

The package is ESM only. CommonJS apps need an ESM-capable bundler. If you use Tailwind, declare the layer order.

Replace Root props with plugins

Create plugins in a client file, outside your component.

Version 3 propVersion 4
tracks, defaultTrackIndexcaptions({ tracks })
hotkeyshotkeys()
autoPlayautoplay(), which also handles browsers refusing to start
showControlsLeave out Video.Controls
title, subtitleYour own element, as a child of the player
posterOn Video.Media or VideoPlayer
loopOn Video.Media, or media={{ loop: true }} on VideoPlayer
defaultQualityIndexsrc={{ initial, renditions }}
onProgressuseClock, or onTimeUpdate on Video.Media
onDuration, onEnded, onPlay, onPause, onLoad, onVolumeChangeThe matching event on Video.Media, such as onEnded or onDurationChange
onTrackChangeuseVideoState with the captions plugin, reading active
onQualityChangeuseVideoState on media.quality

Controls that used to be always available need a plugin now:

FeaturePlugin
Fullscreenfullscreen()
Picture in picturepip()
Controls that hide when idleidle()
Captions menucaptions()
Quality menuquality()

Change quality lists

A src array used to mean quality levels. Now it means fallbacks, so a list of qualities would silently only ever play the first.

tsx
// Version 3<Video.Root src={[  { src: "/1080.mp4", type: "video/mp4", label: "HD" },  { src: "/720.mp4", type: "video/mp4", label: "SD" },]} />// Version 4<Video.Root  plugins={[quality()]}  src={{    renditions: [      { src: "/1080.mp4", type: "video/mp4", label: "HD" },      { src: "/720.mp4", type: "video/mp4", label: "SD" },    ],  }}/>

Rename parts

Version 3Version 4
Video.Root, Video.Media, Video.PlayPauseSame names
Video.TimelineSame name. trackClassName, rangeClassName, and thumbClassName become subparts
Video.Volume.ButtonVideo.Mute
Video.Volume.SliderVideo.VolumeSlider
Video.Time.CurrentVideo.CurrentTime
Video.Time.RemainingVideo.RemainingTime
Video.LoadingVideo.BufferingIndicator
Video.FullscreenToggleFullscreen from react-video-kit/fullscreen
Video.PipTogglePip from react-video-kit/pip
Video.CaptionsMenuCaptionsMenu from react-video-kit/captions
Video.QualityMenuQualityMenu from react-video-kit/quality
Video.SeekBack, Video.SeekForwardYour own button
Video.Header, Video.Center, Video.Footer, Video.BackdropVideo.Controls, Video.ControlRow, Video.ControlGroup, or your own elements
Video.Title, Video.SubtitleYour own elements

Named exports such as VideoPlayPause and VideoRoot are gone, because import * as Video removes unused parts anyway.

Update refs

ref gives you the Player now.

Version 3 handleVersion 4
play(), pause(), seek(time)commands.play(), commands.pause(), commands.seek(time)
setVolume(value)commands.setVolume(value)
mute(), unmute()commands.setMuted(true), commands.setMuted(false)
enterFullscreen(), exitFullscreen()plugin(fullscreen).commands.toggle()
enterPip(), exitPip()plugin(pip).commands.toggle()

Custom controls

useVideoContext still exists, in react-video-kit/compat, with the same shape. Change the import and your control keeps working:

tsx
import { useVideoContext } from "react-video-kit/compat";

It re-renders four times a second, so it's only a bridge, and it goes away in version 5. Move each control to the hooks when you can:

tsx
// Beforeconst { isPlaying, handlePlay, handlePause } = useVideoContext();// Afterconst paused = useVideoState((media) => media.paused);const { play, pause } = useCommands();
useVideoContextVersion 4
isPlaying, isMuted, volume, duration, playbackRateuseVideoState on media.paused, muted, volume, duration, rate
isLoadinguseVideoState on media.status === "loading" || media.waiting
currentTime, remainingTimeuseCurrentTime, or useClock
isFullscreen, isPipuseVideoState(fullscreen, (state) => state.active), and the same for pip
handleSeek, backBy, forwardByuseCommands().seek
handleVolumeChange, handlePlaybackRateChangeuseCommands().setVolume, setRate
videoRef, wrapperRefusePlayer().video(), root()

Styling

Classes with the rv- prefix, such as rv-flex, were internal Tailwind utilities, and they're gone. Use your own CSS or Tailwind classes, and read Theming for how to change the skin.

Parts no longer take *ClassName props. Use data-part or subparts.

Behaviour you'll notice

These changes are improvements, but you may have depended on the old behaviour.

  • Sources load when they change. Changing src used to do nothing. It now loads the new source.
  • Failed sources show an error. They used to spin forever. There's now an error message with a stable code, and a retry button when trying again could help. onError on the player hears about every failure.
  • Controls stay visible while paused, with the idle plugin.
  • crossOrigin is opt-in. Version 3 always set it. Pass it through media or Video.Media when you need it, such as for captions on another origin.
  • Menus stay inside the player. They used to portal to document.body, and now they render inside the root, so they work in fullscreen with no special handling.