Qortora · Search · Indexed page

videojs.orgFetched 2026-08-15T11:11:03Z

The Open Source Player for the Web

Skip to contentUsing Video.js v8 or earlier? Visit legacy.videojs.org Video.js video playerv10 betaSearch DocsBlog Menuv10 betaThe Open Source Player for the Web For over 15 years, Video.js has been the world's web video player. Now rebuilt in v10 for modern development and perfo…

Open original source · Full cached text

Skip to contentUsing Video.js v8 or earlier? Visit legacy.videojs.org Video.js video playerv10 betaSearch DocsBlog Menuv10 betaThe Open Source Player for the Web For over 15 years, Video.js has been the world's web video player. Now rebuilt in v10 for modern development and performance. 0:00 0:00 0:00 0:00 Video hosting sponsored by DefaultMinimal ReactHTML Assemble your player Feel at home with your framework, skin, and media source ReactHTML ReactReact 'use client'; import { createPlayer } from '@videojs/react'; import { VideoSkin, Video, videoFeatures } from '@videojs/react/video'; import '@videojs/react/video/skin.css'; const Player = createPlayer({ features: videoFeatures }); export function VideoPlayer() { return ( <Player.Provider> <VideoSkin poster="https://image.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/thumbnail.webp"> <Video src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4" playsInline /> </VideoSkin> </Player.Provider> ); } Take full control Make your player truly your own with fully-editable components ReactHTML ReactReact JSJS CSSCSS 'use client'; import { type CSSProperties, type ComponentProps, forwardRef, type ReactNode, isValidElement } from 'react'; import { audioText, captionsText, playbackRateText, qualityText, settingsText, speedText } from '@videojs/core/i18n/text/menu'; import { AirPlayEnterIcon, AirPlayExitIcon, CaptionsOffIcon, CaptionsOnIcon, CastEnterIcon, CastExitIcon, CheckIcon, ChevronIcon, FullscreenEnterIcon, FullscreenExitIcon, GearIcon, PauseIcon, PipEnterIcon, PipExitIcon, PlayIcon, QualityIcon, RestartIcon, SpeechIcon, SpeedIcon, SpinnerIcon, VolumeHighIcon, VolumeLowIcon, VolumeOffIcon } from '@videojs/react/icons'; import { Poster, useTranslator, Container, usePlayer, AirPlayButton, useAudioTrackOptions, BufferingIndicator, CaptionsButton, useCaptionsOptions, CastButton, Controls, ErrorDialog, FullscreenButton, Gesture, Hotkey, Menu, MuteButton, PiPButton, PlayButton, usePlaybackRateOptions, Popover, useQualityOptions, SeekIndicator, Slider, StatusAnnouncer, StatusIndicator, Time, TimeSlider, Tooltip, VolumeIndicator, VolumeSlider, type RenderProp } from '@videojs/react'; import { Video } from '@videojs/react/video'; import './player.css'; import { Player } from './player'; const TOP_STATUS_ACTIONS = ['toggleSubtitles', 'toggleFullscreen', 'togglePictureInPicture'] as const; const CENTER_STATUS_ACTIONS = ['togglePaused'] as const; function MenuChevron({ flipped = false }: { flipped?: boolean }): ReactNode { return <ChevronIcon className={`media-icon media-menu__chevron ${flipped ? 'media-icon--flipped' : undefined}`} />; } function SettingsMenu(): ReactNode { const t = useTranslator(); const playbackRate = usePlaybackRateOptions(); const quality = useQualityOptions(); const audioTrack = useAudioTrackOptions(); const captions = useCaptionsOptions(); const hasPlaybackRate = playbackRate?.state.availability === 'available'; const hasQuality = quality?.state.availability === 'available'; const hasAudioTrack = audioTrack?.state.availability === 'available'; const hasCaptions = captions?.state.availability === 'available'; if (!hasPlaybackRate && !hasQuality && !hasAudioTrack && !hasCaptions) return null; return ( <Menu.Root side="top" align="center"> <Menu.Trigger aria-label={t(settingsText)} className="media-button--settings" render={<Button />}> <GearIcon className="media-icon media-icon--settings" /> </Menu.Trigger> <Menu.Content className="media-surface media-popover media-menu media-menu--settings"> <Menu.View className="media-menu__panel"> <div className="media-menu__group"> {hasQuality ? ( <Menu.Root> <Menu.Trigger type="quality" className="media-menu__item media-menu__item--submenu" render={(props) => ( <div {...props}> <QualityIcon className="media-icon" /> <span>{t(qualityText)}</span> <span className="media-menu__hint"> <Menu.ItemValue className="media-menu__hint-label" /> <MenuChevron /> </span> </div> )} /> <Menu.Content className="media-menu__panel"> <Menu.Back className="media-menu__back"> <MenuChevron flipped /> {t(qualityText)} </Menu.Back> <Menu.Separator className="media-menu__separator" /> <Menu.RadioGroup className="media-menu__group" value={quality.value} onValueChange={quality.setValue} aria-label={t(qualityText)} > {quality.options.map((option) => ( <Menu.RadioItem key={option.value} className="media-menu__item" value={option.value} disabled={option.disabled} > <span> {option.label} {option.tier ? <sup className="media-menu__tier">{option.tier}</sup> : null} </span> {option.badge ? <span className="media-badge">{option.badge}</span> : null} <Menu.ItemIndicator checked={option.value === quality.value} forceMount className="media-menu__indicator" > <CheckIcon className="media-icon" /> </Menu.ItemIndicator> </Menu.RadioItem> ))} </Menu.RadioGroup> </Menu.Content> </Menu.Root> ) : null} {hasAudioTrack ? ( <Menu.Root> <Menu.Trigger type="audio-track" className="media-menu__item media-menu__item--submenu" render={(props) => ( <div {...props}> <SpeechIcon className="media-icon" /> <span>{t(audioText)}</span> <span className="media-menu__hint"> <Menu.ItemValue className="media-menu__hint-label" /> <MenuChevron /> </span> </div> )} /> <Menu.Content className="media-menu__panel"> <Menu.Back className="media-menu__back"> <MenuChevron flipped /> {t(audioText)} </Menu.Back> <Menu.Separator className="media-menu__separator" /> <Menu.RadioGroup className="media-menu__group" value={audioTrack.value} onValueChange={audioTrack.setValue} aria-label={t(audioText)} > {audioTrack.options.map((option) => ( <Menu.RadioItem key={option.value} className="media-menu__item" value={option.value} disabled={option.disabled} > <span>{option.label}</span> <Menu.ItemIndicator checked={option.value === audioTrack.value} forceMount className="media-menu__indicator" > <CheckIcon className="media-icon" /> </Menu.ItemIndicator> </Menu.RadioItem> ))} </Menu.RadioGroup> </Menu.Content> </Menu.Root> ) : null} {hasPlaybackRate ? ( <Menu.Root> <Menu.Trigger type="playback-rate" className="media-menu__item media-menu__item--submenu" render={(props) => ( <div {...props}> <SpeedIcon className="media-icon" /> <span>{t(speedText)}</span> <span className="media-menu__hint"> <Menu.ItemValue className="media-menu__hint-label" /> <MenuChevron /> </span> </div> )} /> <Menu.Content className="media-menu__panel"> <Menu.Back className="media-menu__back"> <MenuChevron flipped /> {t(speedText)} </Menu.Back> <Menu.Separator className="media-menu__separator" /> <Menu.RadioGroup className="media-menu__group" value={playbackRate.value} onValueChange={playbackRate.setValue} aria-label={t(playbackRateText)} > {playbackRate.options.map((option) => ( <Menu.RadioItem key={option.value} className="media-menu__item" value={option.value} disabled={option.disabled} > <span>{option.label}</span> <Menu.ItemIndicator checked={option.value === playbackRate.value} forceMount className="media-menu__indicator" > <CheckIcon className="media-icon" /> </Menu.ItemIndicator> </Menu.RadioItem> ))} </Menu.RadioGroup> </Menu.Content> </Menu.Root> ) : null} {hasCaptions ? ( <Menu.Root> <Menu.Trigger type="captions" className="media-menu__item media-menu__item--submenu" render={(props) => ( <div {...props}> <CaptionsOffIcon className="media-icon" /> <span>{t(captionsText)}</span> <span className="media-menu__hint"> <Menu.ItemValue className="media-menu__hint-label" /> <MenuChevron /> </span> </div> )} /> <Menu.Content className="media-menu__panel"> <Menu.Back className="media-menu__back"> <MenuChevron flipped /> {t(captionsText)} </Menu.Back> <Menu.Separator className="media-menu__separator" /> <Menu.RadioGroup className="media-menu__group" value={captions.value} onValueChange={captions.setValue} aria-label={t(captionsText)} > {captions.options.map((option) => ( <Menu.RadioItem key={option.value} className="media-menu__item" value={option.value} disabled={option.disabled} > <span>{option.label}</span> <Menu.ItemIndicator checked={option.value === captions.value} forceMount className="media-menu__indicator" > <CheckIcon className="media-icon" /> </Menu.ItemIndicator> </Menu.RadioItem> ))} </Menu.RadioGroup> </Menu.Content> </Menu.Root> ) : null} </div> </Menu.View> </Menu.Content> </Menu.Root> ); } function CastControl() { return ( <Tooltip.Root side="top"> <Tooltip.Trigger render={ <CastButton className="media-button--cast" render={<Button />}> <CastEnterIcon className="media-icon media-icon--cast-enter" /> <CastExitIcon className="media-icon media-icon--cast-exit" /> </CastButton> } /> <Tooltip.Popup className="media-surface media-tooltip"> <Tooltip.Label /> <Tooltip.Shortcut className="media-tooltip__kbd" /> </Tooltip.Popup> </Tooltip.Root> ); } function AirPlayControl() { return ( <Tooltip.Root side="top"> <Tooltip.Trigger render={ <AirPlayButton className="media-button--airplay" render={<Button />}> <AirPlayEnterIcon className="media-icon media-icon--airplay-enter" /> <AirPlayExitIcon className="media-icon media-icon--airplay-exit" /> </AirPlayButton> } /> <Tooltip.Popup className="media-surface media-tooltip"> <Tooltip.Label /> <Tooltip.Shortcut className="media-tooltip__kbd" /> </Tooltip.Popup> </Tooltip.Root> ); } function PiPControl() { return ( <Tooltip.Root side="top"> <Tooltip.Trigger render={ <PiPButton className="media-button--pip" render={<Button />}> <PipEnterIcon className="media-icon media-icon--pip-enter" /> <PipExitIcon className="media-icon media-icon--pip-exit" /> </PiPButton> } /> <Tooltip.Popup className="media-surface media-tooltip"> <Tooltip.Label /> <Tooltip.Shortcut className="media-tooltip__kbd" /> </Tooltip.Popup> </Tooltip.Root> ); } function FullscreenControl() { return ( <Tooltip.Root side="top"> <Tooltip.Trigger render={ <FullscreenButton className="media-button--fullscreen" render={<Button />}> <FullscreenEnterIcon className="media-icon media-icon--fullscreen-enter" /> <FullscreenExitIcon className="media-icon media-icon--fullscreen-exit" /> </FullscreenButton> } /> <Tooltip.Popup className="media-surface media-tooltip"> <Tooltip.Label /> <Tooltip.Shortcut className="media-tooltip__kbd" /> </Tooltip.Popup> </Tooltip.Root> ); } // ================================================================ // Player // ================================================================ const SEEK_TIME = 10; export interface VideoPlayerProps { src: string; style?: CSSProperties; className?: string; poster?: string | RenderProp<Poster.State> | undefined; } /** * @example * ```tsx * <VideoPlayer * src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4" * poster="https://image.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/thumbnail.webp" * /> * ``` */ export function VideoPlayer({ src, className, poster, placeholder, style, ...rest }: VideoPlayerProps): ReactNode { const containerStyle = placeholder ? ({ '--media-poster-placeholder': `url(${placeholder})`, ...style } as CSSProperties) : style; return ( <Player.Provider> <Container className={`media-default-skin media-default-skin--video ${className ?? ''}`} style={containerStyle} {...rest} > <Video src={src} playsInline /> {poster && ( <Poster src={isString(poster) ? poster : undefined} render={isRenderProp(poster) ? poster : undefined} /> )} <BufferingIndicator render={(props) => ( <div {...props} className="media-buffering-indicator"> <SpinnerIcon className="media-icon" /> </div> )} /> <ErrorDialog.Root> <ErrorDialog.Popup className="media-error"> <div className="media-error__dialog media-surface"> <div className="media-error__content"> <ErrorDialog.Title className="media-error__title"></ErrorDialog.Title> <ErrorDialog.Description className="media-error__description" /> </div> <div className="media-error__actions"> <ErrorDialog.Close className="media-button media-button--primary"></ErrorDialog.Close> </div> </div> </ErrorDialog.Popup> </ErrorDialog.Root> <Controls.Root className="media-surface media-controls media-controls--root"> <Tooltip.Provider> <d…