mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
feat(packages): add mux-audio element and react component (#1259)
This commit is contained in:
@@ -6,6 +6,7 @@ export const PRESETS = [
|
||||
'hls-video',
|
||||
'native-hls-video',
|
||||
'mux-video',
|
||||
'mux-audio',
|
||||
'simple-hls-video',
|
||||
'dash-video',
|
||||
'audio',
|
||||
|
||||
@@ -78,9 +78,9 @@ export function App() {
|
||||
}
|
||||
}, [preset, source, setSource]);
|
||||
|
||||
// Constrain source to Mux playbackId sources when switching to mux-video
|
||||
// Constrain source to Mux playbackId sources when switching to mux-video / mux-audio
|
||||
useEffect(() => {
|
||||
if (preset === 'mux-video' && !SOURCES[source].playbackId) {
|
||||
if ((preset === 'mux-video' || preset === 'mux-audio') && !SOURCES[source].playbackId) {
|
||||
setSource(DEFAULT_SOURCE);
|
||||
}
|
||||
}, [preset, source, setSource]);
|
||||
@@ -97,7 +97,7 @@ export function App() {
|
||||
? MP4_SOURCE_IDS
|
||||
: preset === 'dash-video'
|
||||
? DASH_SOURCE_IDS
|
||||
: preset === 'mux-video'
|
||||
: preset === 'mux-video' || preset === 'mux-audio'
|
||||
? MUX_SOURCE_IDS
|
||||
: NON_DASH_SOURCE_IDS;
|
||||
|
||||
@@ -120,6 +120,7 @@ export function App() {
|
||||
isBackgroundVideo={preset === 'background-video'}
|
||||
isSimpleHlsVideo={preset === 'simple-hls-video'}
|
||||
isMuxVideo={preset === 'mux-video'}
|
||||
isMuxAudio={preset === 'mux-audio'}
|
||||
platforms={PLATFORMS}
|
||||
stylings={STYLINGS}
|
||||
presets={PRESETS}
|
||||
|
||||
@@ -17,6 +17,7 @@ type NavbarProps = {
|
||||
isBackgroundVideo: boolean;
|
||||
isSimpleHlsVideo: boolean;
|
||||
isMuxVideo: boolean;
|
||||
isMuxAudio: boolean;
|
||||
platforms: readonly Platform[];
|
||||
stylings: readonly Styling[];
|
||||
presets: readonly Preset[];
|
||||
@@ -36,6 +37,7 @@ const PRESET_LABELS: Record<Preset, string> = {
|
||||
'hls-video': 'HLS Video',
|
||||
'native-hls-video': 'Native HLS Video',
|
||||
'mux-video': 'Mux Video',
|
||||
'mux-audio': 'Mux Audio',
|
||||
'simple-hls-video': 'Simple HLS Video',
|
||||
'dash-video': 'DASH Video',
|
||||
audio: 'Audio',
|
||||
@@ -57,6 +59,7 @@ export function Navbar({
|
||||
isBackgroundVideo,
|
||||
isSimpleHlsVideo,
|
||||
isMuxVideo,
|
||||
isMuxAudio,
|
||||
platforms,
|
||||
stylings,
|
||||
presets,
|
||||
@@ -111,7 +114,7 @@ export function Navbar({
|
||||
options={availableSources
|
||||
.filter((id) => {
|
||||
if (isSimpleHlsVideo) return sources[id].subType === 'mp4';
|
||||
if (isMuxVideo) return sources[id].type !== 'dash';
|
||||
if (isMuxVideo || isMuxAudio) return sources[id].type !== 'dash';
|
||||
return true;
|
||||
})
|
||||
.map((id) => ({ value: id, label: sources[id].label }))}
|
||||
|
||||
@@ -31,6 +31,7 @@ async function loadCdnPreset(preset: Preset, skin: Skin) {
|
||||
else await import('@videojs/html/cdn/video');
|
||||
break;
|
||||
case 'audio':
|
||||
case 'mux-audio':
|
||||
if (skin === 'minimal') await import('@videojs/html/cdn/audio-minimal');
|
||||
else await import('@videojs/html/cdn/audio');
|
||||
break;
|
||||
@@ -48,6 +49,9 @@ async function loadCdnMedia(preset: Preset) {
|
||||
case 'mux-video':
|
||||
await import('@videojs/html/cdn/media/mux-video');
|
||||
break;
|
||||
case 'mux-audio':
|
||||
await import('@videojs/html/cdn/media/mux-audio');
|
||||
break;
|
||||
case 'native-hls-video':
|
||||
await import('@videojs/html/cdn/media/native-hls-video');
|
||||
break;
|
||||
@@ -66,13 +70,13 @@ async function loadCdnMedia(preset: Preset) {
|
||||
|
||||
function getPlayerTag(preset: Preset): string {
|
||||
if (preset === 'background-video') return 'background-video-player';
|
||||
if (preset === 'audio') return 'audio-player';
|
||||
if (preset === 'audio' || preset === 'mux-audio') return 'audio-player';
|
||||
return 'video-player';
|
||||
}
|
||||
|
||||
function getSkinTag(preset: Preset, skin: Skin): string {
|
||||
if (preset === 'background-video') return 'background-video-skin';
|
||||
if (preset === 'audio') return CSS_SKIN_TAGS[skin].audio;
|
||||
if (preset === 'audio' || preset === 'mux-audio') return CSS_SKIN_TAGS[skin].audio;
|
||||
return CSS_SKIN_TAGS[skin].video;
|
||||
}
|
||||
|
||||
@@ -80,6 +84,7 @@ function getMediaTag(preset: Preset): string {
|
||||
const tags: Partial<Record<Preset, string>> = {
|
||||
'hls-video': 'hls-video',
|
||||
'mux-video': 'mux-video',
|
||||
'mux-audio': 'mux-audio',
|
||||
'native-hls-video': 'native-hls-video',
|
||||
'simple-hls-video': 'simple-hls-video',
|
||||
'dash-video': 'dash-video',
|
||||
@@ -91,7 +96,7 @@ function getMediaTag(preset: Preset): string {
|
||||
}
|
||||
|
||||
function loadStylesheets(preset: Preset, skin: Skin) {
|
||||
if (preset === 'audio') loadAudioStylesheets(skin);
|
||||
if (preset === 'audio' || preset === 'mux-audio') loadAudioStylesheets(skin);
|
||||
else if (preset !== 'background-video') loadVideoStylesheets(skin);
|
||||
// Background CSS is loaded via dynamic import in loadCdnPreset.
|
||||
}
|
||||
@@ -126,7 +131,7 @@ async function render() {
|
||||
const sourceAttr =
|
||||
preset === 'background-video'
|
||||
? `src="${BACKGROUND_VIDEO_SRC}"`
|
||||
: preset === 'mux-video' && source.type !== 'mp4'
|
||||
: (preset === 'mux-video' || preset === 'mux-audio') && source.type !== 'mp4'
|
||||
? `playback-id="${source.playbackId}"`
|
||||
: `src="${source.url}"`;
|
||||
|
||||
@@ -147,7 +152,7 @@ async function render() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (preset === 'audio') {
|
||||
if (preset === 'audio' || preset === 'mux-audio') {
|
||||
root.innerHTML = html`
|
||||
<div class="w-full max-w-xl mx-auto">
|
||||
<${playerTag}>
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Sandbox — HTML Mux Audio</title>
|
||||
<link rel="preconnect" href="https://rsms.me/" />
|
||||
<link rel="stylesheet" href="https://rsms.me/inter/inter.css" />
|
||||
</head>
|
||||
<body class="font-sans p-2">
|
||||
<div id="root" class="flex justify-center items-center min-h-screen"></div>
|
||||
<script type="module" src="./main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,44 @@
|
||||
import '@app/styles.css';
|
||||
import '@videojs/html/audio/player';
|
||||
import '@videojs/html/media/mux-audio';
|
||||
import { createHtmlSandboxState, createLatestLoader } from '@app/shared/html/sandbox-state';
|
||||
import { loadAudioSkinTag } from '@app/shared/html/skins';
|
||||
import { onSkinChange, onSourceChange } from '@app/shared/sandbox-listener';
|
||||
import { SOURCES } from '@app/shared/sources';
|
||||
|
||||
const html = String.raw;
|
||||
|
||||
const state = createHtmlSandboxState();
|
||||
const loadLatest = createLatestLoader();
|
||||
|
||||
async function render() {
|
||||
const tag = await loadLatest(() => loadAudioSkinTag(state.skin, state.styling));
|
||||
if (!tag) return;
|
||||
|
||||
const sourceAttr =
|
||||
SOURCES[state.source].type === 'mp4'
|
||||
? `src="${SOURCES[state.source].url}"`
|
||||
: `playback-id="${SOURCES[state.source].playbackId}"`;
|
||||
|
||||
document.getElementById('root')!.innerHTML = html`
|
||||
<div class="w-full max-w-xl mx-auto">
|
||||
<audio-player>
|
||||
<${tag}>
|
||||
<mux-audio ${sourceAttr} debug crossorigin="anonymous"></mux-audio>
|
||||
</${tag}>
|
||||
</audio-player>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
render();
|
||||
|
||||
onSkinChange((skin) => {
|
||||
state.skin = skin;
|
||||
render();
|
||||
});
|
||||
|
||||
onSourceChange((source) => {
|
||||
state.source = source;
|
||||
render();
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Sandbox — React Mux Audio</title>
|
||||
<link rel="preconnect" href="https://rsms.me/" />
|
||||
<link rel="stylesheet" href="https://rsms.me/inter/inter.css" />
|
||||
</head>
|
||||
<body class="font-sans p-2">
|
||||
<div id="root" class="flex justify-center items-center min-h-screen"></div>
|
||||
<script type="module" src="./main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,33 @@
|
||||
import '@app/styles.css';
|
||||
import { AudioProvider } from '@app/shared/react/providers';
|
||||
import { AudioSkinComponent } from '@app/shared/react/skins';
|
||||
import { useSkin } from '@app/shared/react/use-skin';
|
||||
import { useSource } from '@app/shared/react/use-source';
|
||||
import { SOURCES } from '@app/shared/sources';
|
||||
import type { Styling } from '@app/types';
|
||||
import { MuxAudio } from '@videojs/react/media/mux-audio';
|
||||
import { useMemo } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
|
||||
function readStyling(): Styling {
|
||||
return new URLSearchParams(location.search).get('styling') === 'tailwind' ? 'tailwind' : 'css';
|
||||
}
|
||||
|
||||
function App() {
|
||||
const skin = useSkin();
|
||||
const source = useSource();
|
||||
const styling = useMemo(readStyling, []);
|
||||
|
||||
const sourceAttr =
|
||||
SOURCES[source].type === 'mp4' ? { src: SOURCES[source].url } : { playbackId: SOURCES[source].playbackId };
|
||||
|
||||
return (
|
||||
<AudioProvider>
|
||||
<AudioSkinComponent skin={skin} styling={styling} className="w-full max-w-xl mx-auto">
|
||||
<MuxAudio {...sourceAttr} debug crossOrigin="anonymous" />
|
||||
</AudioSkinComponent>
|
||||
</AudioProvider>
|
||||
);
|
||||
}
|
||||
|
||||
createRoot(document.getElementById('root')!).render(<App />);
|
||||
@@ -1,9 +1,9 @@
|
||||
import Mux from 'mux-embed';
|
||||
|
||||
import { DelegateMixin } from '../../../core/media/delegate';
|
||||
import { CustomVideoElement } from '../custom-media-element';
|
||||
import { CustomAudioElement, CustomVideoElement } from '../custom-media-element';
|
||||
import { Hls, HlsMediaDelegate } from '../hls';
|
||||
import { VideoProxy } from '../proxy';
|
||||
import { AudioProxy, VideoProxy } from '../proxy';
|
||||
import { getPlayerVersion } from './env';
|
||||
import type { MuxDataSdk } from './types';
|
||||
|
||||
@@ -235,3 +235,7 @@ export class MuxMedia extends DelegateMixin(VideoProxy, MuxMediaDelegate) {}
|
||||
export class MuxCustomVideo extends DelegateMixin(CustomVideoElement, MuxVideoDelegate) {}
|
||||
|
||||
export class MuxVideo extends DelegateMixin(VideoProxy, MuxVideoDelegate) {}
|
||||
|
||||
export class MuxCustomAudio extends DelegateMixin(CustomAudioElement, MuxAudioDelegate) {}
|
||||
|
||||
export class MuxAudio extends DelegateMixin(AudioProxy, MuxAudioDelegate) {}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { ProxyMixin } from '../../core/media/proxy';
|
||||
|
||||
export const VideoProxy = ProxyMixin(globalThis.HTMLVideoElement ?? class {});
|
||||
|
||||
export const AudioProxy = ProxyMixin(globalThis.HTMLAudioElement ?? class {});
|
||||
|
||||
@@ -20,7 +20,7 @@ export type MediaBaseApi = {
|
||||
paused: boolean;
|
||||
};
|
||||
|
||||
export type MediaApi = WithOptional<MediaBaseApi, HTMLVideoElement>;
|
||||
export type MediaApi = WithOptional<MediaBaseApi, HTMLMediaElement>;
|
||||
|
||||
export type Media = HTMLMediaElement | HTMLAudioElement | HTMLVideoElement;
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
import '../../define/media/mux-audio';
|
||||
@@ -0,0 +1,14 @@
|
||||
import { MuxAudio } from '../../media/mux-audio';
|
||||
import { safeDefine } from '../safe-define';
|
||||
|
||||
export class MuxAudioElement extends MuxAudio {
|
||||
static readonly tagName = 'mux-audio';
|
||||
}
|
||||
|
||||
safeDefine(MuxAudioElement);
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
[MuxAudioElement.tagName]: MuxAudioElement;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { MuxCustomAudio, MuxMediaDelegate } from '@videojs/core/dom/media/mux';
|
||||
import { MediaAttachMixin } from '../../store/media-attach-mixin';
|
||||
import { MediaPropsMixin } from '../../utils/media-props-mixin';
|
||||
|
||||
export class MuxAudio extends MediaPropsMixin(MediaAttachMixin(MuxCustomAudio), MuxMediaDelegate) {
|
||||
constructor() {
|
||||
super();
|
||||
this.attach(this.target);
|
||||
}
|
||||
|
||||
disconnectedCallback(): void {
|
||||
super.disconnectedCallback?.();
|
||||
|
||||
if (!this.hasAttribute('keep-alive')) {
|
||||
this.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,7 @@ const presets = [
|
||||
'audio-minimal-ui',
|
||||
'background',
|
||||
];
|
||||
const media = ['hls-video', 'mux-video', 'native-hls-video', 'simple-hls-video', 'dash-video'];
|
||||
const media = ['hls-video', 'mux-audio', 'mux-video', 'native-hls-video', 'simple-hls-video', 'dash-video'];
|
||||
|
||||
const entries = [
|
||||
...presets.map((name) => ({ src: `src/cdn/${name}.ts`, name })),
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import type { InferDelegateProps } from '@videojs/core';
|
||||
import { MuxAudio as MuxAudioApi, MuxMediaDelegate } from '@videojs/core/dom/media/mux';
|
||||
import type { AudioHTMLAttributes, PropsWithChildren } 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 MuxAudioProps = PropsWithChildren<AudioHTMLAttributes<HTMLAudioElement>> &
|
||||
InferDelegateProps<typeof MuxMediaDelegate>;
|
||||
|
||||
export const MuxAudio = forwardRef<HTMLAudioElement, MuxAudioProps>(({ children, ...props }, ref) => {
|
||||
const mediaApi = useMediaInstance(MuxAudioApi);
|
||||
|
||||
const composedRef = useComposedRefs(attachMediaElement(mediaApi), ref);
|
||||
|
||||
return (
|
||||
<audio ref={composedRef} {...mediaProps(mediaApi, MuxMediaDelegate, props)}>
|
||||
{children}
|
||||
</audio>
|
||||
);
|
||||
});
|
||||
|
||||
export default MuxAudio;
|
||||
Reference in New Issue
Block a user