import type { ChangeEventHandler } from 'react'; import { FrostedSkin, HlsVideo, MinimalSkin, VideoProvider } from '@videojs/react'; import { useCallback, useMemo, useState } from 'react'; // NOTE: Commented out imports are for testing locally/externally defined skins. // import { VideoProvider, Video } from '@videojs/react'; // import FrostedSkin from './skins/frosted/FrostedSkin'; // import MinimalSkin from './skins/toasted/MinimalSkin'; import FrostedEjectedSkin from './skins/frosted-eject/FrostedSkin'; import MinimalEjectedSkin from './skins/minimal-eject/MinimalSkin'; import '@videojs/react/skins/frosted.css'; import '@videojs/react/skins/minimal.css'; import './globals.css'; const skins = [ { key: 'frosted', name: 'Frosted (imported)', component: FrostedSkin, }, { key: 'frosted-eject', name: 'Frosted (ejected)', component: FrostedEjectedSkin, }, { key: 'minimal', name: 'Minimal (imported)', component: MinimalSkin, }, { key: 'minimal-eject', name: 'Minimal (ejected)', component: MinimalEjectedSkin, }, ] as const; type SkinKey = (typeof skins)[number]['key']; const mediaSources = [ { key: '1', name: 'Mux 1 (HLS)', value: 'https://stream.mux.com/fXNzVtmtWuyz00xnSrJg4OJH6PyNo6D02UzmgeKGkP5YQ.m3u8', }, { key: '2', name: 'Mux 2 (HLS)', value: 'https://stream.mux.com/a4nOgmxGWg6gULfcBbAa00gXyfcwPnAFldF8RdsNyk8M.m3u8', }, { key: '3', name: 'Mux 3 (MP4)', value: 'https://stream.mux.com/A3VXy02VoUinw01pwyomEO3bHnG4P32xzV7u1j1FSzjNg/high.mp4', }, { key: '4', name: 'Mux 4 (HLS)', value: 'https://stream.mux.com/lyrKpPcGfqyzeI00jZAfW6MvP6GNPrkML.m3u8', }, ] as const; type MediaSourceKey = (typeof mediaSources)[number]['key']; function getParam(key: string, defaultValue: T): T { const params = new URLSearchParams(window.location.search); return (params.get(key) as T) || defaultValue; } function setParam(key: string, value: string) { const params = new URLSearchParams(window.location.search); params.set(key, value); const search = params.toString(); const url = window.location.pathname + (search ? `?${search}` : ''); window.history.replaceState(null, '', url); } const DEFAULT_SKIN: SkinKey = 'frosted'; const DEFAULT_MEDIA_SOURCE: MediaSourceKey = '1'; export default function App(): JSX.Element { const [skinKey, setSkinKey] = useState(() => getParam('skin', DEFAULT_SKIN)); const [mediaSourceKey, setMediaSourceKey] = useState(() => getParam('source', DEFAULT_MEDIA_SOURCE)); const mediaSource = useMemo(() => { let match = mediaSources.find(m => m.key === mediaSourceKey); if (!match) { match = mediaSources.find(m => m.key === DEFAULT_MEDIA_SOURCE)!; setMediaSourceKey(match.key); } return match.value; }, [mediaSourceKey]); const Skin = useMemo(() => { let match = skins.find(s => s.key === skinKey); if (!match) { match = skins.find(s => s.key === DEFAULT_SKIN)!; setSkinKey(match.key); } return match.component; }, [skinKey]); const onChangeSkin: ChangeEventHandler = useCallback((event) => { const value = event.target.value as SkinKey; setSkinKey(value); setParam('skin', value); }, []); const onChangeMediaSource: ChangeEventHandler = useCallback((event) => { const value = event.target.value as MediaSourceKey; setMediaSourceKey(value); setParam('source', value); }, []); // Force a re-render on changes. const key = `${skinKey}-${mediaSourceKey}`; const playbackId = mediaSource.match(/stream\.mux\.com\/([^./]+)/)?.[1]; const poster = playbackId ? `https://image.mux.com/${playbackId}/thumbnail.webp` : undefined; return (

Playground

Test out the various skins for Video.js.
{/* @ts-expect-error -- types are incorrect */}
); }