From 1878a271ba300919d1eff19dd2db79340e8714fa Mon Sep 17 00:00:00 2001 From: Christian Pillsbury Date: Thu, 6 Nov 2025 10:34:04 -0800 Subject: [PATCH] feat(react): use SimpleVideo as default Video and rename HLS version to HlsVideo (#171) --- examples/next-demo/app/page.tsx | 6 +- examples/react-demo/src/App.tsx | 4 +- packages/react/src/components/HlsVideo.tsx | 111 +++++++++++++++ packages/react/src/components/SimpleVideo.tsx | 52 ------- packages/react/src/components/Video.tsx | 131 +++++------------- packages/react/src/index.ts | 4 +- site/src/components/home/HeroVideo.tsx | 4 +- site/src/consts.ts | 8 +- .../content/docs/concepts/architecture.mdx | 2 +- site/src/content/docs/how-to/installation.mdx | 10 +- .../html/fullscreen-button/basic.html | 4 +- site/src/examples/html/mute-button/basic.html | 4 +- site/src/examples/html/play-button/basic.html | 4 +- .../react/FrostedSkin/FrostedSkinDemo.tsx | 2 +- .../FullscreenButton/FullscreenButtonDemo.tsx | 2 +- .../react/MinimalSkin/MinimalSkinDemo.tsx | 2 +- .../react/MuteButton/MuteButtonDemo.tsx | 2 +- .../react/PlayButton/PlayButtonDemo.tsx | 2 +- .../react/TimeSlider/TimeSliderDemo.tsx | 2 +- .../react/VolumeSlider/VolumeSliderDemo.tsx | 2 +- 20 files changed, 177 insertions(+), 181 deletions(-) create mode 100644 packages/react/src/components/HlsVideo.tsx delete mode 100644 packages/react/src/components/SimpleVideo.tsx diff --git a/examples/next-demo/app/page.tsx b/examples/next-demo/app/page.tsx index 89eaadba..99305c0c 100644 --- a/examples/next-demo/app/page.tsx +++ b/examples/next-demo/app/page.tsx @@ -1,4 +1,4 @@ -import { FrostedSkin, MinimalSkin, Video, VideoProvider } from '@videojs/react'; +import { FrostedSkin, HlsVideo, MinimalSkin, VideoProvider } from '@videojs/react'; import '@videojs/react/skins/frosted.css'; import '@videojs/react/skins/minimal.css'; @@ -9,14 +9,14 @@ export default function Home() { {/* @ts-expect-error -- types are incorrect */} -

Minimal Skin

{/* @ts-expect-error -- types are incorrect */} - diff --git a/examples/react-demo/src/App.tsx b/examples/react-demo/src/App.tsx index 077280c7..68d8f7d4 100644 --- a/examples/react-demo/src/App.tsx +++ b/examples/react-demo/src/App.tsx @@ -1,6 +1,6 @@ import type { ChangeEventHandler } from 'react'; -import { FrostedSkin, MinimalSkin, Video, VideoProvider } from '@videojs/react'; +import { FrostedSkin, HlsVideo, MinimalSkin, VideoProvider } from '@videojs/react'; import { FullscreenEnterAltIcon, FullscreenExitAltIcon } from '@videojs/react/icons'; import clsx from 'clsx'; import { useCallback, useMemo, useRef, useState } from 'react'; @@ -199,7 +199,7 @@ export default function App(): JSX.Element { {/* @ts-expect-error -- types are incorrect */} - diff --git a/packages/react/src/components/HlsVideo.tsx b/packages/react/src/components/HlsVideo.tsx new file mode 100644 index 00000000..ae0bb35f --- /dev/null +++ b/packages/react/src/components/HlsVideo.tsx @@ -0,0 +1,111 @@ +import type { + CSSProperties, + DetailedHTMLProps, + ElementType, + PropsWithChildren, + Ref, + VideoHTMLAttributes, +} from 'react'; + +import { createMediaPlaybackController } from '@videojs/core/media'; + +import { forwardRef, useImperativeHandle, useRef } from 'react'; +import { useMediaRef } from '@/store'; + +export interface MuxVideoProps { + 'playback-id'?: string; +} + +type MediaStateOwner = NonNullable>[0]>; + +/** @TODO Improve type inference and narrowing/widening for different use cases (CJP) */ +type ComponentType = ElementType< + Omit, HTMLVideoElement>, 'ref'> & { + ref: Ref; + } +>; + +// These are the first steps/WIP POC of decoupling the Media State Owner from the DOM. +// Note that everything will still work if you use: +// 1. an audio/video element directly +// 2. a custom element a la media-elements +type CreateMediaStateOwner = typeof createMediaPlaybackController; + +function useMediaStateOwner(ref: Ref, createMediaPlaybackController: CreateMediaStateOwner) { + const mediaStateOwnerRef = useRef(createMediaPlaybackController(/* props? */)); + useImperativeHandle(ref, () => mediaStateOwnerRef.current, []); + /** @TODO Parameterize this (CJP) */ + type ComponentProps = DetailedHTMLProps, HTMLVideoElement>; + return { + updateMediaElement(mediaEl: HTMLMediaElement | null, props: ComponentProps) { + // NOTE: The details here will almost definitely change for a less "bare bones"/"POC" implementation of Media State Owner impl. (CJP) + mediaStateOwnerRef.current.mediaElement = mediaEl ?? undefined; + mediaStateOwnerRef.current.src = props.src as string; + if (props.muted) { + mediaStateOwnerRef.current.muted = props.muted; + } + }, + }; +} + +const DefaultVideoComponent: ElementType< + Omit, HTMLVideoElement>, 'ref'> & { ref: Ref } +> = forwardRef(({ children, ...props }, ref) => { + const { updateMediaElement } = useMediaStateOwner(ref, createMediaPlaybackController); + return ( + // eslint-disable-next-line jsx-a11y/media-has-caption + + ); +}); + +/** + * @description This is a "thin wrapper" around the media component whose primary responsibility is to wire up the element + * to the 's MediaStore for the media state. + * @param props - Identical to both a