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 's props and the props, with one addition that may be familiar to
+ * MUI users: a `component` prop that allows you to use something other than the element under the hood.
+ * @returns A media react component (e.g. ), wired up as the media element.
+ */
+function ConnectedVideo({
+ component,
+ children,
+ ...props
+}: PropsWithChildren<{
+ component: ComponentType;
+ className?: string | undefined;
+ style?: CSSProperties | undefined;
+}>) {
+ const Component = component;
+ const mediaRefCallback = useMediaRef();
+ // NOTE: While this may feel like magic to folks, in the "default" use case, you can think of it as:
+ // return ();
+ return (
+
+ {children}
+
+ );
+}
+
+export type VideoProps = PropsWithChildren<{
+ component?: ComponentType;
+ className?: string | undefined;
+ style?: CSSProperties | undefined;
+}>;
+
+// HlsVideo component with default component
+export function HlsVideo({ component = DefaultVideoComponent, children, ...props }: VideoProps): JSX.Element {
+ return (
+
+ {children}
+
+ );
+}
+
+export default HlsVideo;
diff --git a/packages/react/src/components/SimpleVideo.tsx b/packages/react/src/components/SimpleVideo.tsx
deleted file mode 100644
index dd552284..00000000
--- a/packages/react/src/components/SimpleVideo.tsx
+++ /dev/null
@@ -1,52 +0,0 @@
-import type {
- CSSProperties,
- DetailedHTMLProps,
- PropsWithChildren,
- VideoHTMLAttributes,
-} from 'react';
-
-import React, { forwardRef } from 'react';
-import { useMediaRef } from '@/store';
-
-export type SimpleVideoProps = PropsWithChildren<
- DetailedHTMLProps, HTMLVideoElement> & {
- className?: string | undefined;
- style?: CSSProperties | undefined;
- }
->;
-
-/**
- * SimpleVideo - A basic video component that works with native HTML5 video formats (MP4, WebM, etc.)
- * without using a playback engine. Use this for simple MP4 files. For HLS/DASH streaming, use the
- * regular Video component instead.
- *
- * This component connects to VideoProvider for play/pause state but sets the src directly on the
- * video element without going through HLS.js or other playback engines.
- *
- * @example
- * ```tsx
- *
- *
- *
- *
- *
- * ```
- */
-export const SimpleVideo: React.ForwardRefExoticComponent<
- SimpleVideoProps & React.RefAttributes
-> = forwardRef(
- ({ children, ...props }, _ref) => {
- const mediaRefCallback = useMediaRef();
-
- return (
- // eslint-disable-next-line jsx-a11y/media-has-caption
-
- );
- },
-);
-
-SimpleVideo.displayName = 'SimpleVideo';
-
-export default SimpleVideo;
diff --git a/packages/react/src/components/Video.tsx b/packages/react/src/components/Video.tsx
index 66d27447..aa13b29a 100644
--- a/packages/react/src/components/Video.tsx
+++ b/packages/react/src/components/Video.tsx
@@ -1,115 +1,52 @@
import type {
CSSProperties,
DetailedHTMLProps,
- ElementType,
- FC,
PropsWithChildren,
- Ref,
VideoHTMLAttributes,
} from 'react';
-import { createMediaPlaybackController } from '@videojs/core/media';
-
-import { forwardRef, useImperativeHandle, useRef } from 'react';
+import React, { forwardRef } 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;
+export type VideoProps = PropsWithChildren<
+ DetailedHTMLProps, HTMLVideoElement> & {
+ className?: string | undefined;
+ style?: CSSProperties | undefined;
}
>;
-// 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 's props and the props, with one addition that may be familiar to
- * MUI users: a `component` prop that allows you to use something other than the element under the hood.
- * @returns A media react component (e.g. ), wired up as the media element.
+ * Video - A basic video component that works with native HTML5 video formats (MP4, WebM, etc.)
+ * without using a playback engine. Use this for simple MP4 files. For HLS/DASH streaming, use the
+ * regular Video component instead.
+ *
+ * This component connects to VideoProvider for play/pause state but sets the src directly on the
+ * video element without going through HLS.js or other playback engines.
+ *
+ * @example
+ * ```tsx
+ *
+ *
+ *
+ *
+ *
+ * ```
*/
-function ConnectedVideo({
- component,
- children,
- ...props
-}: PropsWithChildren<{
- component: ComponentType;
- className?: string | undefined;
- style?: CSSProperties | undefined;
-}>) {
- const Component = component;
- const mediaRefCallback = useMediaRef();
- // NOTE: While this may feel like magic to folks, in the "default" use case, you can think of it as:
- // return ();
- return (
-
- {children}
-
- );
-}
+export const Video: React.ForwardRefExoticComponent<
+ VideoProps & React.RefAttributes
+> = forwardRef(
+ ({ children, ...props }, _ref) => {
+ const mediaRefCallback = useMediaRef();
-export type VideoProps = PropsWithChildren<{
- component?: ComponentType;
- className?: string | undefined;
- style?: CSSProperties | undefined;
-}>;
+ return (
+ // eslint-disable-next-line jsx-a11y/media-has-caption
+
+ );
+ },
+);
-// Main Video component with default component
-export function Video({ component = DefaultVideoComponent, children, ...props }: VideoProps): JSX.Element {
- return (
-
- {children}
-
- );
-}
-
-// MediaElementVideo export (same as Video but for backwards compatibility)
-export const MediaElementVideo: FC = Video;
+Video.displayName = 'Video';
export default Video;
diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts
index 9782babe..217ea4d2 100644
--- a/packages/react/src/index.ts
+++ b/packages/react/src/index.ts
@@ -3,15 +3,15 @@
export { CurrentTimeDisplay } from './components/CurrentTimeDisplay';
export { DurationDisplay } from './components/DurationDisplay';
export { FullscreenButton } from './components/FullscreenButton';
+export { HlsVideo } from './components/HlsVideo';
export { MediaContainer, useMediaContainerRef } from './components/MediaContainer';
export { MuteButton } from './components/MuteButton';
export { PlayButton } from './components/PlayButton';
export { Popover } from './components/Popover';
export { PreviewTimeDisplay } from './components/PreviewTimeDisplay';
-export { SimpleVideo } from './components/SimpleVideo';
export { TimeSlider } from './components/TimeSlider';
export { Tooltip } from './components/Tooltip';
-export { MediaElementVideo, Video } from './components/Video';
+export { Video } from './components/Video';
export { VolumeSlider } from './components/VolumeSlider';
export * from './skins';
export * from '@/store';
diff --git a/site/src/components/home/HeroVideo.tsx b/site/src/components/home/HeroVideo.tsx
index adf56413..9e701c99 100644
--- a/site/src/components/home/HeroVideo.tsx
+++ b/site/src/components/home/HeroVideo.tsx
@@ -1,5 +1,5 @@
import { useStore } from '@nanostores/react';
-import { FrostedSkin, MinimalSkin, Video, VideoProvider } from '@videojs/react';
+import { FrostedSkin, HlsVideo, MinimalSkin, VideoProvider } from '@videojs/react';
import { VJS8_DEMO_VIDEO } from '@/consts';
import { skin } from '@/stores/homePageDemos';
import '@videojs/react/skins/frosted.css';
@@ -13,7 +13,7 @@ export default function HeroVideo({ className, poster }: { className?: string; p
return (
-