Repository Public
1// Video and audio with the browser's own player; the API answers Range requests, so seeking works on large files.2import { useState } from 'react';3import { Glyph } from '../glyphs.tsx';4import { useT } from '../i18n.ts';5import type { PreviewSource } from '../types.ts';6import { DownloadLink, explain, Failure, Panel } from './shared.tsx';78export function MediaView({ source }: { source: PreviewSource }) {9 const t = useT();10 const [failed, setFailed] = useState<{ error: unknown } | null>(null);11 const [attempt, setAttempt] = useState(0);12 const src = source.contentUrl();13 const onError = () => { void explain(src).then((error) => setFailed({ error })); };14 if (failed) {15 return failed.error16 ? <Failure error={failed.error} source={source} onRetry={() => { setFailed(null); setAttempt((n) => n + 1); }} />17 : <Panel icon={source.kind} tone="warn" title={t('preview.mediaUnsupported')} text={t('preview.mediaUnsupportedText')} testId="preview-error"><DownloadLink source={source} primary /></Panel>;18 }19 if (source.kind === 'video') {20 return (21 <div className="flex min-h-0 flex-1 items-center justify-center p-0 sm:p-6">22 <video key={attempt} src={src} controls playsInline preload="metadata" onError={onError} data-testid="video" className="max-h-full max-w-full rounded-none bg-black sm:rounded-2xl" />23 </div>24 );25 }26 return (27 <div className="flex min-h-0 flex-1 items-center justify-center p-4">28 <div className="flex w-full max-w-lg flex-col items-center gap-5 rounded-2xl border border-line bg-surface p-6">29 <div className="flex h-20 w-20 items-center justify-center rounded-2xl bg-accent-soft text-accent"><Glyph name="audio" size={40} /></div>30 <p className="max-w-full truncate text-sm font-medium text-ink">{source.name}</p>31 <audio key={attempt} src={src} controls preload="metadata" onError={onError} data-testid="audio" className="w-full" />32 </div>33 </div>34 );35}36