You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
107 lines
2.6 KiB
107 lines
2.6 KiB
import { isImage } from '@/lib/url' |
|
import { useContentPolicy } from '@/providers/ContentPolicyProvider' |
|
import { useEffect, useMemo, useState } from 'react' |
|
import AudioPlayer from '../AudioPlayer' |
|
import VideoPlayer from '../VideoPlayer' |
|
import ExternalLink from '../ExternalLink' |
|
import LazyMediaTapPlaceholder from './LazyMediaTapPlaceholder' |
|
|
|
export default function MediaPlayer({ |
|
src, |
|
className, |
|
mustLoad = false, |
|
poster, |
|
blurHash |
|
}: { |
|
src: string |
|
className?: string |
|
mustLoad?: boolean |
|
poster?: string |
|
/** NIP-94 / imeta blurhash for lazy placeholder when poster is missing */ |
|
blurHash?: string |
|
}) { |
|
const { autoLoadMedia } = useContentPolicy() |
|
const [display, setDisplay] = useState(autoLoadMedia) |
|
const [mediaType, setMediaType] = useState<'video' | 'audio' | null>(null) |
|
|
|
// imeta `thumb` / `image` are sometimes the same .mp4 as `url` — <img> cannot use that, and it |
|
// would hide the blurhash placeholder in LazyMediaTapPlaceholder. |
|
const imagePoster = useMemo(() => { |
|
const p = poster?.trim() |
|
if (!p) return undefined |
|
return isImage(p) ? p : undefined |
|
}, [poster]) |
|
|
|
useEffect(() => { |
|
if (autoLoadMedia) { |
|
setDisplay(true) |
|
} else { |
|
setDisplay(false) |
|
} |
|
}, [autoLoadMedia]) |
|
|
|
useEffect(() => { |
|
if (!mustLoad && !display) { |
|
setMediaType(null) |
|
return |
|
} |
|
if (!src) { |
|
setMediaType(null) |
|
return |
|
} |
|
|
|
const url = new URL(src) |
|
const extension = url.pathname.split('.').pop()?.toLowerCase() |
|
|
|
if ( |
|
extension && |
|
['mp3', 'wav', 'flac', 'aac', 'm4a', 'opus', 'wma', 'mka'].includes(extension) |
|
) { |
|
setMediaType('audio') |
|
return |
|
} |
|
|
|
if (extension === 'mkv' || extension === 'ogv') { |
|
setMediaType('video') |
|
return |
|
} |
|
|
|
const video = document.createElement('video') |
|
video.src = src |
|
video.preload = 'metadata' |
|
|
|
video.onloadedmetadata = () => { |
|
setMediaType(video.videoWidth > 0 || video.videoHeight > 0 ? 'video' : 'audio') |
|
} |
|
|
|
video.onerror = () => { |
|
setMediaType(null) |
|
} |
|
|
|
return () => { |
|
video.src = '' |
|
} |
|
}, [src, display, mustLoad]) |
|
|
|
if (!mustLoad && !display) { |
|
return ( |
|
<LazyMediaTapPlaceholder |
|
src={src} |
|
posterUrl={imagePoster} |
|
blurHash={blurHash} |
|
onActivate={() => setDisplay(true)} |
|
className={className} |
|
/> |
|
) |
|
} |
|
|
|
if (!mediaType) { |
|
return <ExternalLink url={src} /> |
|
} |
|
|
|
if (mediaType === 'video') { |
|
return <VideoPlayer src={src} className={className} poster={imagePoster} /> |
|
} |
|
|
|
return <AudioPlayer src={src} className={className} /> |
|
}
|
|
|