mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
feat(react): use SimpleVideo as default Video and rename HLS version to HlsVideo (#171)
This commit is contained in:
@@ -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() {
|
||||
<VideoProvider>
|
||||
<FrostedSkin>
|
||||
{/* @ts-expect-error -- types are incorrect */}
|
||||
<Video src="https://stream.mux.com/fXNzVtmtWuyz00xnSrJg4OJH6PyNo6D02UzmgeKGkP5YQ.m3u8" playsInline />
|
||||
<HlsVideo src="https://stream.mux.com/fXNzVtmtWuyz00xnSrJg4OJH6PyNo6D02UzmgeKGkP5YQ.m3u8" playsInline />
|
||||
</FrostedSkin>
|
||||
</VideoProvider>
|
||||
<h1 className="text-4xl font-extrabold py-2">Minimal Skin</h1>
|
||||
<VideoProvider>
|
||||
<MinimalSkin>
|
||||
{/* @ts-expect-error -- types are incorrect */}
|
||||
<Video src="https://stream.mux.com/fXNzVtmtWuyz00xnSrJg4OJH6PyNo6D02UzmgeKGkP5YQ.m3u8" playsInline />
|
||||
<HlsVideo src="https://stream.mux.com/fXNzVtmtWuyz00xnSrJg4OJH6PyNo6D02UzmgeKGkP5YQ.m3u8" playsInline />
|
||||
</MinimalSkin>
|
||||
</VideoProvider>
|
||||
</main>
|
||||
|
||||
@@ -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 {
|
||||
<VideoProvider key={key}>
|
||||
<Skin className={skinClassName}>
|
||||
{/* @ts-expect-error -- types are incorrect */}
|
||||
<Video src={mediaSource} poster={poster} playsInline />
|
||||
<HlsVideo src={mediaSource} poster={poster} playsInline />
|
||||
</Skin>
|
||||
</VideoProvider>
|
||||
</div>
|
||||
|
||||
@@ -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<Parameters<ReturnType<typeof useMediaRef>>[0]>;
|
||||
|
||||
/** @TODO Improve type inference and narrowing/widening for different use cases (CJP) */
|
||||
type ComponentType = ElementType<
|
||||
Omit<DetailedHTMLProps<VideoHTMLAttributes<HTMLVideoElement>, HTMLVideoElement>, 'ref'> & {
|
||||
ref: Ref<MediaStateOwner>;
|
||||
}
|
||||
>;
|
||||
|
||||
// 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<any>, createMediaPlaybackController: CreateMediaStateOwner) {
|
||||
const mediaStateOwnerRef = useRef(createMediaPlaybackController(/* props? */));
|
||||
useImperativeHandle(ref, () => mediaStateOwnerRef.current, []);
|
||||
/** @TODO Parameterize this (CJP) */
|
||||
type ComponentProps = DetailedHTMLProps<VideoHTMLAttributes<HTMLVideoElement>, 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<DetailedHTMLProps<VideoHTMLAttributes<HTMLVideoElement>, HTMLVideoElement>, 'ref'> & { ref: Ref<any> }
|
||||
> = forwardRef<any, any>(({ children, ...props }, ref) => {
|
||||
const { updateMediaElement } = useMediaStateOwner(ref, createMediaPlaybackController);
|
||||
return (
|
||||
// eslint-disable-next-line jsx-a11y/media-has-caption
|
||||
<video
|
||||
{...props}
|
||||
ref={(mediaEl) => {
|
||||
/** @TODO In later iterations/non-POC, we should be able to have a function that can be used directly for the `ref` prop (CJP) */
|
||||
updateMediaElement(mediaEl, props);
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</video>
|
||||
);
|
||||
});
|
||||
|
||||
/**
|
||||
* @description This is a "thin wrapper" around the media component whose primary responsibility is to wire up the element
|
||||
* to the <VideoProvider/>'s MediaStore for the media state.
|
||||
* @param props - Identical to both a <video/>'s props and the <Player/> props, with one addition that may be familiar to
|
||||
* MUI users: a `component` prop that allows you to use something other than the <video/> element under the hood.
|
||||
* @returns A media react component (e.g. <video/>), 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 (<video ref={mediaRefCallback} {...restProps} >{children}</video>);
|
||||
return (
|
||||
<Component {...props} ref={mediaRefCallback}>
|
||||
{children}
|
||||
</Component>
|
||||
);
|
||||
}
|
||||
|
||||
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 (
|
||||
<ConnectedVideo {...props} component={component}>
|
||||
{children}
|
||||
</ConnectedVideo>
|
||||
);
|
||||
}
|
||||
|
||||
export default HlsVideo;
|
||||
@@ -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<VideoHTMLAttributes<HTMLVideoElement>, 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
|
||||
* <VideoProvider>
|
||||
* <MediaSkin>
|
||||
* <SimpleVideo src="video.mp4" />
|
||||
* </MediaSkin>
|
||||
* </VideoProvider>
|
||||
* ```
|
||||
*/
|
||||
export const SimpleVideo: React.ForwardRefExoticComponent<
|
||||
SimpleVideoProps & React.RefAttributes<HTMLVideoElement>
|
||||
> = forwardRef<HTMLVideoElement, SimpleVideoProps>(
|
||||
({ children, ...props }, _ref) => {
|
||||
const mediaRefCallback = useMediaRef();
|
||||
|
||||
return (
|
||||
// eslint-disable-next-line jsx-a11y/media-has-caption
|
||||
<video {...props} ref={mediaRefCallback}>
|
||||
{children}
|
||||
</video>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
SimpleVideo.displayName = 'SimpleVideo';
|
||||
|
||||
export default SimpleVideo;
|
||||
@@ -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<Parameters<ReturnType<typeof useMediaRef>>[0]>;
|
||||
|
||||
/** @TODO Improve type inference and narrowing/widening for different use cases (CJP) */
|
||||
type ComponentType = ElementType<
|
||||
Omit<DetailedHTMLProps<VideoHTMLAttributes<HTMLVideoElement>, HTMLVideoElement>, 'ref'> & {
|
||||
ref: Ref<MediaStateOwner>;
|
||||
export type VideoProps = PropsWithChildren<
|
||||
DetailedHTMLProps<VideoHTMLAttributes<HTMLVideoElement>, 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<any>, createMediaPlaybackController: CreateMediaStateOwner) {
|
||||
const mediaStateOwnerRef = useRef(createMediaPlaybackController(/* props? */));
|
||||
useImperativeHandle(ref, () => mediaStateOwnerRef.current, []);
|
||||
/** @TODO Parameterize this (CJP) */
|
||||
type ComponentProps = DetailedHTMLProps<VideoHTMLAttributes<HTMLVideoElement>, 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<DetailedHTMLProps<VideoHTMLAttributes<HTMLVideoElement>, HTMLVideoElement>, 'ref'> & { ref: Ref<any> }
|
||||
> = forwardRef<any, any>(({ children, ...props }, ref) => {
|
||||
const { updateMediaElement } = useMediaStateOwner(ref, createMediaPlaybackController);
|
||||
return (
|
||||
// eslint-disable-next-line jsx-a11y/media-has-caption
|
||||
<video
|
||||
{...props}
|
||||
ref={(mediaEl) => {
|
||||
/** @TODO In later iterations/non-POC, we should be able to have a function that can be used directly for the `ref` prop (CJP) */
|
||||
updateMediaElement(mediaEl, props);
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</video>
|
||||
);
|
||||
});
|
||||
|
||||
/**
|
||||
* @description This is a "thin wrapper" around the media component whose primary responsibility is to wire up the element
|
||||
* to the <VideoProvider/>'s MediaStore for the media state.
|
||||
* @param props - Identical to both a <video/>'s props and the <Player/> props, with one addition that may be familiar to
|
||||
* MUI users: a `component` prop that allows you to use something other than the <video/> element under the hood.
|
||||
* @returns A media react component (e.g. <video/>), 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
|
||||
* <VideoProvider>
|
||||
* <MediaSkin>
|
||||
* <Video src="video.mp4" />
|
||||
* </MediaSkin>
|
||||
* </VideoProvider>
|
||||
* ```
|
||||
*/
|
||||
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 (<video ref={mediaRefCallback} {...restProps} >{children}</video>);
|
||||
return (
|
||||
<Component {...props} ref={mediaRefCallback}>
|
||||
{children}
|
||||
</Component>
|
||||
);
|
||||
}
|
||||
export const Video: React.ForwardRefExoticComponent<
|
||||
VideoProps & React.RefAttributes<HTMLVideoElement>
|
||||
> = forwardRef<HTMLVideoElement, VideoProps>(
|
||||
({ 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
|
||||
<video {...props} ref={mediaRefCallback}>
|
||||
{children}
|
||||
</video>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
// Main Video component with default component
|
||||
export function Video({ component = DefaultVideoComponent, children, ...props }: VideoProps): JSX.Element {
|
||||
return (
|
||||
<ConnectedVideo {...props} component={component}>
|
||||
{children}
|
||||
</ConnectedVideo>
|
||||
);
|
||||
}
|
||||
|
||||
// MediaElementVideo export (same as Video but for backwards compatibility)
|
||||
export const MediaElementVideo: FC<VideoProps> = Video;
|
||||
Video.displayName = 'Video';
|
||||
|
||||
export default Video;
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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 (
|
||||
<VideoProvider>
|
||||
<SkinComponent className={className}>
|
||||
<Video
|
||||
<HlsVideo
|
||||
// @ts-expect-error -- types are incorrect
|
||||
src={VJS8_DEMO_VIDEO.hls}
|
||||
poster={poster}
|
||||
|
||||
+4
-4
@@ -16,8 +16,8 @@ interface VideoSource {
|
||||
}
|
||||
|
||||
export const VJS8_DEMO_VIDEO: VideoSource = {
|
||||
id: 'UZMwOY6MgmhFNXLbSFXAuPKlRPss5XNA',
|
||||
hls: 'https://stream.mux.com/UZMwOY6MgmhFNXLbSFXAuPKlRPss5XNA.m3u8',
|
||||
mp4: 'https://stream.mux.com/UZMwOY6MgmhFNXLbSFXAuPKlRPss5XNA/high.mp4',
|
||||
poster: 'https://image.mux.com/UZMwOY6MgmhFNXLbSFXAuPKlRPss5XNA/thumbnail.webp',
|
||||
id: 'lhnU49l1VGi3zrTAZhDm9LUUxSjpaPW9BL4jY25Kwo4',
|
||||
hls: 'https://stream.mux.com/lhnU49l1VGi3zrTAZhDm9LUUxSjpaPW9BL4jY25Kwo4.m3u8',
|
||||
mp4: 'https://stream.mux.com/lhnU49l1VGi3zrTAZhDm9LUUxSjpaPW9BL4jY25Kwo4/highest.mp4',
|
||||
poster: 'https://image.mux.com/lhnU49l1VGi3zrTAZhDm9LUUxSjpaPW9BL4jY25Kwo4/thumbnail.webp',
|
||||
};
|
||||
|
||||
@@ -62,7 +62,7 @@ Want to dive deeper? <DocsLink slug="concepts/ui-components">Explore UI componen
|
||||
Media renderers are the components that actually display your media. They're essentially "players with no UI". They handle the video/audio rendering and expose a consistent API.
|
||||
|
||||
<FrameworkCase frameworks={["react"]}>
|
||||
For now, we're starting with just a `<Video />` component, but we're looking forward to adding more in the future. From engines like HLS and DASH, to services like YouTube and Vimeo.
|
||||
For now, we'll have a default `<Video />` component, which supports MP4 and other natively-supported streams and `<HlsVideo />`, which supports HLS. In the future, we'll be adding additional support for streaming standards like DASH and services like YouTube and Vimeo.
|
||||
</FrameworkCase>
|
||||
<FrameworkCase frameworks={["html"]}>
|
||||
For now, we're starting with just the native HTML5 `<video />` component, but we're looking forward to adding more in the future. From engines like HLS and DASH, to services like YouTube and Vimeo.
|
||||
|
||||
@@ -66,7 +66,7 @@ import '@videojs/html/skins/minimal';
|
||||
## 3. Pick Your Renderer
|
||||
|
||||
<FrameworkCase frameworks={["react"]}>
|
||||
Media renderers display your video and audio content. For now, Video.js v10 includes `<Video />`, which supports HLS streams.
|
||||
Media renderers display your video and audio content. For now, Video.js v10 includes `<Video />`, which supports MP4 and other natively-supported streams and `<HlsVideo />`, which supports HLS.
|
||||
</FrameworkCase>
|
||||
<FrameworkCase frameworks={["html"]}>
|
||||
Media renderers display your video and audio content. For now, you should use the native HTML5 `<video>` element.
|
||||
@@ -86,8 +86,8 @@ export default function App() {
|
||||
<VideoProvider>
|
||||
<FrostedSkin>
|
||||
<Video
|
||||
src="https://stream.mux.com/UZMwOY6MgmhFNXLbSFXAuPKlRPss5XNA.m3u8"
|
||||
poster="https://image.mux.com/UZMwOY6MgmhFNXLbSFXAuPKlRPss5XNA/thumbnail.webp"
|
||||
src="https://stream.mux.com/lhnU49l1VGi3zrTAZhDm9LUUxSjpaPW9BL4jY25Kwo4/highest.mp4"
|
||||
poster="https://image.mux.com/lhnU49l1VGi3zrTAZhDm9LUUxSjpaPW9BL4jY25Kwo4/thumbnail.webp"
|
||||
playsInline
|
||||
/>
|
||||
</FrostedSkin>
|
||||
@@ -105,8 +105,8 @@ document.body.innerHTML = `
|
||||
<media-skin-frosted>
|
||||
<video
|
||||
slot="media"
|
||||
src="https://stream.mux.com/UZMwOY6MgmhFNXLbSFXAuPKlRPss5XNA.m3u8"
|
||||
poster="https://image.mux.com/UZMwOY6MgmhFNXLbSFXAuPKlRPss5XNA/thumbnail.webp"
|
||||
src="https://stream.mux.com/lhnU49l1VGi3zrTAZhDm9LUUxSjpaPW9BL4jY25Kwo4/highest.mp4"
|
||||
poster="https://image.mux.com/lhnU49l1VGi3zrTAZhDm9LUUxSjpaPW9BL4jY25Kwo4/thumbnail.webp"
|
||||
playsinline
|
||||
></video>
|
||||
</media-skin-frosted>
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
<div class="demo-container">
|
||||
<media-container class="media-container">
|
||||
<video
|
||||
src="https://stream.mux.com/UZMwOY6MgmhFNXLbSFXAuPKlRPss5XNA.m3u8"
|
||||
poster="https://image.mux.com/UZMwOY6MgmhFNXLbSFXAuPKlRPss5XNA/thumbnail.webp"
|
||||
src="https://stream.mux.com/lhnU49l1VGi3zrTAZhDm9LUUxSjpaPW9BL4jY25Kwo4/highest.mp4"
|
||||
poster="https://image.mux.com/lhnU49l1VGi3zrTAZhDm9LUUxSjpaPW9BL4jY25Kwo4/thumbnail.webp"
|
||||
></video>
|
||||
|
||||
<div class="controls">
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
<div class="demo-container">
|
||||
<media-container class="media-container">
|
||||
<video
|
||||
src="https://stream.mux.com/UZMwOY6MgmhFNXLbSFXAuPKlRPss5XNA.m3u8"
|
||||
poster="https://image.mux.com/UZMwOY6MgmhFNXLbSFXAuPKlRPss5XNA/thumbnail.webp"
|
||||
src="https://stream.mux.com/lhnU49l1VGi3zrTAZhDm9LUUxSjpaPW9BL4jY25Kwo4/highest.mp4"
|
||||
poster="https://image.mux.com/lhnU49l1VGi3zrTAZhDm9LUUxSjpaPW9BL4jY25Kwo4/thumbnail.webp"
|
||||
></video>
|
||||
|
||||
<div class="controls">
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
<div class="demo-container">
|
||||
<media-container class="media-container">
|
||||
<video
|
||||
src="https://stream.mux.com/UZMwOY6MgmhFNXLbSFXAuPKlRPss5XNA.m3u8"
|
||||
poster="https://image.mux.com/UZMwOY6MgmhFNXLbSFXAuPKlRPss5XNA/thumbnail.webp"
|
||||
src="https://stream.mux.com/lhnU49l1VGi3zrTAZhDm9LUUxSjpaPW9BL4jY25Kwo4/highest.mp4"
|
||||
poster="https://image.mux.com/lhnU49l1VGi3zrTAZhDm9LUUxSjpaPW9BL4jY25Kwo4/thumbnail.webp"
|
||||
></video>
|
||||
|
||||
<div class="controls">
|
||||
|
||||
@@ -17,7 +17,7 @@ export function FrostedSkinDemo() {
|
||||
<VideoProvider>
|
||||
<FrostedSkin className="w-full aspect-video rounded-3xl">
|
||||
<Video
|
||||
src={VJS8_DEMO_VIDEO.hls}
|
||||
src={VJS8_DEMO_VIDEO.mp4}
|
||||
poster={VJS8_DEMO_VIDEO.poster}
|
||||
playsInline
|
||||
/>
|
||||
|
||||
@@ -12,7 +12,7 @@ export function FullscreenButtonDemo() {
|
||||
<VideoProvider>
|
||||
<MediaContainer style={{ position: 'relative', zIndex: 10 }}>
|
||||
<Video
|
||||
src={VJS8_DEMO_VIDEO.hls}
|
||||
src={VJS8_DEMO_VIDEO.mp4}
|
||||
poster={VJS8_DEMO_VIDEO.poster}
|
||||
muted
|
||||
/>
|
||||
|
||||
@@ -16,7 +16,7 @@ export function MinimalSkinDemo() {
|
||||
<VideoProvider>
|
||||
<MinimalSkin className="w-full aspect-video">
|
||||
<Video
|
||||
src={VJS8_DEMO_VIDEO.hls}
|
||||
src={VJS8_DEMO_VIDEO.mp4}
|
||||
poster={VJS8_DEMO_VIDEO.poster}
|
||||
playsInline
|
||||
/>
|
||||
|
||||
@@ -12,7 +12,7 @@ export function MuteButtonDemo() {
|
||||
<VideoProvider>
|
||||
<MediaContainer style={{ position: 'relative', zIndex: 10 }}>
|
||||
<Video
|
||||
src={VJS8_DEMO_VIDEO.hls}
|
||||
src={VJS8_DEMO_VIDEO.mp4}
|
||||
poster={VJS8_DEMO_VIDEO.poster}
|
||||
muted
|
||||
/>
|
||||
|
||||
@@ -12,7 +12,7 @@ export function PlayButtonDemo() {
|
||||
<VideoProvider>
|
||||
<MediaContainer style={{ position: 'relative', zIndex: 10 }}>
|
||||
<Video
|
||||
src={VJS8_DEMO_VIDEO.hls}
|
||||
src={VJS8_DEMO_VIDEO.mp4}
|
||||
poster={VJS8_DEMO_VIDEO.poster}
|
||||
muted
|
||||
/>
|
||||
|
||||
@@ -12,7 +12,7 @@ export function TimeSliderDemo() {
|
||||
<VideoProvider>
|
||||
<MediaContainer style={{ position: 'relative', zIndex: 10 }}>
|
||||
<Video
|
||||
src={VJS8_DEMO_VIDEO.hls}
|
||||
src={VJS8_DEMO_VIDEO.mp4}
|
||||
poster={VJS8_DEMO_VIDEO.poster}
|
||||
muted
|
||||
/>
|
||||
|
||||
@@ -12,7 +12,7 @@ export function VolumeSliderDemo() {
|
||||
<VideoProvider>
|
||||
<MediaContainer style={{ position: 'relative', zIndex: 10 }}>
|
||||
<Video
|
||||
src={VJS8_DEMO_VIDEO.hls}
|
||||
src={VJS8_DEMO_VIDEO.mp4}
|
||||
poster={VJS8_DEMO_VIDEO.poster}
|
||||
muted
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user