mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
feat(spf): basic audio only use case + use-case-composition doc-type + implementation skills (#1584)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
338020e1d5
commit
1a3cb45b29
@@ -8,6 +8,7 @@ export const PRESETS = [
|
||||
'mux-video',
|
||||
'mux-audio',
|
||||
'simple-hls-video',
|
||||
'simple-hls-audio-only',
|
||||
'dash-video',
|
||||
'audio',
|
||||
'background-video',
|
||||
|
||||
@@ -148,7 +148,7 @@ export function App() {
|
||||
onPreloadChange={setPreload}
|
||||
availableSources={availableSources}
|
||||
isBackgroundVideo={preset === 'background-video'}
|
||||
isSimpleHlsVideo={preset === 'simple-hls-video'}
|
||||
isSimpleHls={preset.startsWith('simple-hls-')}
|
||||
isMuxVideo={preset === 'mux-video'}
|
||||
isMuxAudio={preset === 'mux-audio'}
|
||||
platforms={PLATFORMS}
|
||||
|
||||
@@ -25,7 +25,7 @@ type NavbarProps = {
|
||||
onPreloadChange: (value: PreloadValue) => void;
|
||||
availableSources: readonly SourceId[];
|
||||
isBackgroundVideo: boolean;
|
||||
isSimpleHlsVideo: boolean;
|
||||
isSimpleHls: boolean;
|
||||
isMuxVideo: boolean;
|
||||
isMuxAudio: boolean;
|
||||
platforms: readonly Platform[];
|
||||
@@ -49,6 +49,7 @@ const PRESET_LABELS: Record<Preset, string> = {
|
||||
'mux-video': 'Mux Video',
|
||||
'mux-audio': 'Mux Audio',
|
||||
'simple-hls-video': 'Simple HLS Video',
|
||||
'simple-hls-audio-only': 'Simple HLS Audio-Only',
|
||||
'dash-video': 'DASH Video',
|
||||
audio: 'Audio',
|
||||
'background-video': 'Background Video',
|
||||
@@ -75,7 +76,7 @@ export function Navbar({
|
||||
onPreloadChange,
|
||||
availableSources,
|
||||
isBackgroundVideo,
|
||||
isSimpleHlsVideo,
|
||||
isSimpleHls,
|
||||
isMuxVideo,
|
||||
isMuxAudio,
|
||||
platforms,
|
||||
@@ -131,7 +132,7 @@ export function Navbar({
|
||||
onChange={onSourceChange}
|
||||
options={availableSources
|
||||
.filter((id) => {
|
||||
if (isSimpleHlsVideo) return sources[id].subType === 'mp4';
|
||||
if (isSimpleHls) return sources[id].subType === 'mp4';
|
||||
if (isMuxVideo || isMuxAudio) return sources[id].type !== 'dash';
|
||||
return true;
|
||||
})
|
||||
|
||||
@@ -44,6 +44,7 @@ async function loadCdnPreset(preset: Preset, skin: Skin, live: boolean) {
|
||||
break;
|
||||
case 'audio':
|
||||
case 'mux-audio':
|
||||
case 'simple-hls-audio-only':
|
||||
if (skin === 'minimal') await import('@videojs/html/cdn/audio-minimal');
|
||||
else await import('@videojs/html/cdn/audio');
|
||||
break;
|
||||
@@ -70,6 +71,9 @@ async function loadCdnMedia(preset: Preset) {
|
||||
case 'simple-hls-video':
|
||||
await import('@videojs/html/cdn/media/simple-hls-video');
|
||||
break;
|
||||
case 'simple-hls-audio-only':
|
||||
await import('@videojs/html/cdn/media/simple-hls-audio-only');
|
||||
break;
|
||||
case 'dash-video':
|
||||
await import('@videojs/html/cdn/media/dash-video');
|
||||
break;
|
||||
@@ -80,15 +84,19 @@ async function loadCdnMedia(preset: Preset) {
|
||||
// Rendering — produces the exact HTML markup the installation builder generates.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function isAudioPreset(preset: Preset): boolean {
|
||||
return preset === 'audio' || preset === 'mux-audio' || preset === 'simple-hls-audio-only';
|
||||
}
|
||||
|
||||
function getPlayerTag(preset: Preset, live: boolean): string {
|
||||
if (preset === 'background-video') return 'background-video-player';
|
||||
if (preset === 'audio' || preset === 'mux-audio') return live ? 'live-audio-player' : 'audio-player';
|
||||
if (isAudioPreset(preset)) return live ? 'live-audio-player' : 'audio-player';
|
||||
return live ? 'live-video-player' : 'video-player';
|
||||
}
|
||||
|
||||
function getSkinTag(preset: Preset, skin: Skin, live: boolean): string {
|
||||
if (preset === 'background-video') return 'background-video-skin';
|
||||
if (preset === 'audio' || preset === 'mux-audio') return CSS_SKIN_TAGS[skin].audio;
|
||||
if (isAudioPreset(preset)) return CSS_SKIN_TAGS[skin].audio;
|
||||
if (live) return LIVE_VIDEO_CSS_SKIN_TAGS[skin];
|
||||
return CSS_SKIN_TAGS[skin].video;
|
||||
}
|
||||
@@ -100,6 +108,7 @@ function getMediaTag(preset: Preset): string {
|
||||
'mux-audio': 'mux-audio',
|
||||
'native-hls-video': 'native-hls-video',
|
||||
'simple-hls-video': 'simple-hls-video',
|
||||
'simple-hls-audio-only': 'simple-hls-audio-only',
|
||||
'dash-video': 'dash-video',
|
||||
audio: 'audio',
|
||||
'background-video': 'background-video',
|
||||
@@ -109,7 +118,7 @@ function getMediaTag(preset: Preset): string {
|
||||
}
|
||||
|
||||
function loadStylesheets(preset: Preset, skin: Skin) {
|
||||
if (preset === 'audio' || preset === 'mux-audio') loadAudioStylesheets(skin);
|
||||
if (isAudioPreset(preset)) loadAudioStylesheets(skin);
|
||||
else if (preset !== 'background-video') loadVideoStylesheets(skin);
|
||||
// Background CSS is loaded via dynamic import in loadCdnPreset.
|
||||
}
|
||||
@@ -169,7 +178,7 @@ async function render() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (preset === 'audio' || preset === 'mux-audio') {
|
||||
if (isAudioPreset(preset)) {
|
||||
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 Simple HLS Audio-Only</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,68 @@
|
||||
import '@app/styles.css';
|
||||
import '@videojs/html/audio/player';
|
||||
import '@videojs/html/media/simple-hls-audio-only';
|
||||
import { createHtmlSandboxState, createLatestLoader, renderMediaAttrs } from '@app/shared/html/sandbox-state';
|
||||
import { loadAudioSkinTag } from '@app/shared/html/skins';
|
||||
import {
|
||||
onAutoplayChange,
|
||||
onLoopChange,
|
||||
onMutedChange,
|
||||
onPreloadChange,
|
||||
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 mediaAttrs = renderMediaAttrs(state);
|
||||
|
||||
document.getElementById('root')!.innerHTML = html`
|
||||
<div class="w-full max-w-xl mx-auto">
|
||||
<audio-player>
|
||||
<${tag}>
|
||||
<simple-hls-audio-only src="${SOURCES[state.source].url}" ${mediaAttrs} crossorigin="anonymous"></simple-hls-audio-only>
|
||||
</${tag}>
|
||||
</audio-player>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
render();
|
||||
|
||||
onSkinChange((skin) => {
|
||||
state.skin = skin;
|
||||
render();
|
||||
});
|
||||
|
||||
onSourceChange((source) => {
|
||||
state.source = source;
|
||||
render();
|
||||
});
|
||||
|
||||
onAutoplayChange((autoplay) => {
|
||||
state.autoplay = autoplay;
|
||||
render();
|
||||
});
|
||||
|
||||
onMutedChange((muted) => {
|
||||
state.muted = muted;
|
||||
render();
|
||||
});
|
||||
|
||||
onLoopChange((loop) => {
|
||||
state.loop = loop;
|
||||
render();
|
||||
});
|
||||
|
||||
onPreloadChange((preload) => {
|
||||
state.preload = preload;
|
||||
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 Simple HLS Audio-Only</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,45 @@
|
||||
import '@app/styles.css';
|
||||
import { AudioProvider } from '@app/shared/react/providers';
|
||||
import { AudioSkinComponent } from '@app/shared/react/skins';
|
||||
import { useAutoplay } from '@app/shared/react/use-autoplay';
|
||||
import { useLoop } from '@app/shared/react/use-loop';
|
||||
import { useMuted } from '@app/shared/react/use-muted';
|
||||
import { usePreload } from '@app/shared/react/use-preload';
|
||||
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 { SimpleHlsAudioOnly } from '@videojs/react/media/simple-hls-audio-only';
|
||||
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 autoplay = useAutoplay();
|
||||
const muted = useMuted();
|
||||
const loop = useLoop();
|
||||
const preload = usePreload();
|
||||
|
||||
return (
|
||||
<AudioProvider>
|
||||
<AudioSkinComponent skin={skin} styling={styling} className="w-full max-w-xl mx-auto">
|
||||
<SimpleHlsAudioOnly
|
||||
src={SOURCES[source].url}
|
||||
autoPlay={autoplay}
|
||||
muted={muted}
|
||||
loop={loop}
|
||||
preload={preload}
|
||||
crossOrigin="anonymous"
|
||||
/>
|
||||
</AudioSkinComponent>
|
||||
</AudioProvider>
|
||||
);
|
||||
}
|
||||
|
||||
createRoot(document.getElementById('root')!).render(<App />);
|
||||
Reference in New Issue
Block a user