feat: add Mux video component (#1036)

This commit is contained in:
Wesley Luyten
2026-03-24 18:19:50 -05:00
committed by GitHub
parent 833210a270
commit 271a8c8502
45 changed files with 941 additions and 182 deletions
@@ -1,4 +1,5 @@
import { DashMedia } from '@videojs/core/dom/media/dash';
import type { InferDelegateProps } from '@videojs/core';
import { DashMedia, DashMediaDelegate } from '@videojs/core/dom/media/dash';
import type { PropsWithChildren, VideoHTMLAttributes } from 'react';
import { forwardRef } from 'react';
import { attachMediaElement } from '../../utils/attach-media-element';
@@ -6,7 +7,8 @@ import { mediaProps } from '../../utils/media-props';
import { useComposedRefs } from '../../utils/use-composed-refs';
import { useMediaInstance } from '../../utils/use-media-instance';
export type DashVideoProps = PropsWithChildren<VideoHTMLAttributes<HTMLVideoElement>>;
export type DashVideoProps = PropsWithChildren<VideoHTMLAttributes<HTMLVideoElement>> &
InferDelegateProps<typeof DashMediaDelegate>;
export const DashVideo = forwardRef<HTMLVideoElement, DashVideoProps>(({ children, ...props }, ref) => {
const mediaApi = useMediaInstance(DashMedia);
@@ -14,7 +16,7 @@ export const DashVideo = forwardRef<HTMLVideoElement, DashVideoProps>(({ childre
const composedRef = useComposedRefs(attachMediaElement(mediaApi), ref);
return (
<video ref={composedRef} {...mediaProps(mediaApi, props)}>
<video ref={composedRef} {...mediaProps(mediaApi, DashMediaDelegate, props)}>
{children}
</video>
);
+5 -3
View File
@@ -1,4 +1,5 @@
import { HlsMedia } from '@videojs/core/dom/media/hls';
import type { InferDelegateProps } from '@videojs/core';
import { HlsMedia, HlsMediaDelegate } from '@videojs/core/dom/media/hls';
import type { PropsWithChildren, VideoHTMLAttributes } from 'react';
import { forwardRef } from 'react';
import { attachMediaElement } from '../../utils/attach-media-element';
@@ -6,7 +7,8 @@ import { mediaProps } from '../../utils/media-props';
import { useComposedRefs } from '../../utils/use-composed-refs';
import { useMediaInstance } from '../../utils/use-media-instance';
export type HlsVideoProps = PropsWithChildren<VideoHTMLAttributes<HTMLVideoElement>>;
export type HlsVideoProps = PropsWithChildren<VideoHTMLAttributes<HTMLVideoElement>> &
InferDelegateProps<typeof HlsMediaDelegate>;
export const HlsVideo = forwardRef<HTMLVideoElement, HlsVideoProps>(({ children, ...props }, ref) => {
const mediaApi = useMediaInstance(HlsMedia);
@@ -14,7 +16,7 @@ export const HlsVideo = forwardRef<HTMLVideoElement, HlsVideoProps>(({ children,
const composedRef = useComposedRefs(attachMediaElement(mediaApi), ref);
return (
<video ref={composedRef} {...mediaProps(mediaApi, props)}>
<video ref={composedRef} {...mediaProps(mediaApi, HlsMediaDelegate, props)}>
{children}
</video>
);
@@ -0,0 +1,25 @@
import type { InferDelegateProps } from '@videojs/core';
import { MuxMediaDelegate, MuxVideo as MuxVideoApi } from '@videojs/core/dom/media/mux';
import type { PropsWithChildren, VideoHTMLAttributes } from 'react';
import { forwardRef } from 'react';
import { attachMediaElement } from '../../utils/attach-media-element';
import { mediaProps } from '../../utils/media-props';
import { useComposedRefs } from '../../utils/use-composed-refs';
import { useMediaInstance } from '../../utils/use-media-instance';
export type MuxVideoProps = PropsWithChildren<VideoHTMLAttributes<HTMLVideoElement>> &
InferDelegateProps<typeof MuxMediaDelegate>;
export const MuxVideo = forwardRef<HTMLVideoElement, MuxVideoProps>(({ children, ...props }, ref) => {
const mediaApi = useMediaInstance(MuxVideoApi);
const composedRef = useComposedRefs(attachMediaElement(mediaApi), ref);
return (
<video ref={composedRef} {...mediaProps(mediaApi, MuxMediaDelegate, props)}>
{children}
</video>
);
});
export default MuxVideo;
@@ -1,4 +1,5 @@
import { SimpleHlsMedia } from '@videojs/core/dom/media/simple-hls';
import { SpfMedia } from '@videojs/spf/dom';
import type { PropsWithChildren, VideoHTMLAttributes } from 'react';
import { forwardRef } from 'react';
import { attachMediaElement } from '../../utils/attach-media-element';
@@ -14,7 +15,7 @@ export const SimpleHlsVideo = forwardRef<HTMLVideoElement, SimpleHlsVideoProps>(
const composedRef = useComposedRefs(attachMediaElement(mediaApi), ref);
return (
<video ref={composedRef} {...mediaProps(mediaApi, props)}>
<video ref={composedRef} {...mediaProps(mediaApi, SpfMedia, props)}>
{children}
</video>
);
+24 -12
View File
@@ -1,20 +1,32 @@
import type { MediaApi } from '@videojs/core/dom';
import type { VideoHTMLAttributes } from 'react';
interface VideoProps extends VideoHTMLAttributes<HTMLVideoElement> {}
type AnyClass = abstract new (...args: any[]) => any;
export function mediaProps(media: MediaApi, props: VideoProps) {
const { src, ...remainingProps } = props;
// Preload can still be passed as a prop to the native media element
if (props.preload && media.preload !== props.preload) {
media.preload = props.preload as '' | 'none' | 'metadata' | 'auto';
function getSettableProps(DelegateClass: AnyClass): Set<string> {
const props = new Set<string>();
for (let proto = DelegateClass.prototype; proto && proto !== Object.prototype; proto = Object.getPrototypeOf(proto)) {
for (const key of Object.getOwnPropertyNames(proto)) {
const desc = Object.getOwnPropertyDescriptor(proto, key);
if (desc?.set) props.add(key);
}
}
return props;
}
if (media.src !== src) {
media.src = src ?? '';
export function mediaProps(media: MediaApi, DelegateClass: AnyClass, props: Record<string, any>) {
const delegateKeys = getSettableProps(DelegateClass);
const rest: Record<string, any> = {};
for (const key of Object.keys(props)) {
if (delegateKeys.has(key)) {
const value = props[key];
if ((media as any)[key] !== value) {
(media as any)[key] = value;
}
} else {
rest[key] = props[key];
}
}
// The remaining props are passed to the native media element
return remainingProps;
return rest;
}