feat: add error handling for audio, video, and YouTube players

This commit is contained in:
codytseng
2025-10-09 22:22:16 +08:00
parent 3395bad78b
commit 6eb3bccd38
4 changed files with 38 additions and 3 deletions

View File

@@ -4,6 +4,7 @@ import { cn } from '@/lib/utils'
import mediaManager from '@/services/media-manager.service' import mediaManager from '@/services/media-manager.service'
import { Pause, Play } from 'lucide-react' import { Pause, Play } from 'lucide-react'
import { useEffect, useRef, useState } from 'react' import { useEffect, useRef, useState } from 'react'
import ExternalLink from '../ExternalLink'
interface AudioPlayerProps { interface AudioPlayerProps {
src: string src: string
@@ -15,6 +16,7 @@ export default function AudioPlayer({ src, className }: AudioPlayerProps) {
const [isPlaying, setIsPlaying] = useState(false) const [isPlaying, setIsPlaying] = useState(false)
const [currentTime, setCurrentTime] = useState(0) const [currentTime, setCurrentTime] = useState(0)
const [duration, setDuration] = useState(0) const [duration, setDuration] = useState(0)
const [error, setError] = useState(false)
const seekTimeoutRef = useRef<NodeJS.Timeout>() const seekTimeoutRef = useRef<NodeJS.Timeout>()
const isSeeking = useRef(false) const isSeeking = useRef(false)
@@ -78,6 +80,10 @@ export default function AudioPlayer({ src, className }: AudioPlayerProps) {
}, 300) }, 300)
} }
if (error) {
return <ExternalLink url={src} />
}
return ( return (
<div <div
className={cn( className={cn(
@@ -86,7 +92,7 @@ export default function AudioPlayer({ src, className }: AudioPlayerProps) {
)} )}
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
> >
<audio ref={audioRef} src={src} preload="metadata" /> <audio ref={audioRef} src={src} preload="metadata" onError={() => setError(false)} />
{/* Play/Pause Button */} {/* Play/Pause Button */}
<Button size="icon" className="rounded-full shrink-0" onClick={togglePlay}> <Button size="icon" className="rounded-full shrink-0" onClick={togglePlay}>

View File

@@ -0,0 +1,15 @@
import { cn } from '@/lib/utils'
export default function ExternalLink({ url, className }: { url: string; className?: string }) {
return (
<a
className={cn('text-primary hover:underline', className)}
href={url}
target="_blank"
onClick={(e) => e.stopPropagation()}
rel="noreferrer"
>
{url}
</a>
)
}

View File

@@ -1,10 +1,12 @@
import { cn, isInViewport } from '@/lib/utils' import { cn, isInViewport } from '@/lib/utils'
import { useContentPolicy } from '@/providers/ContentPolicyProvider' import { useContentPolicy } from '@/providers/ContentPolicyProvider'
import mediaManager from '@/services/media-manager.service' import mediaManager from '@/services/media-manager.service'
import { useEffect, useRef } from 'react' import { useEffect, useRef, useState } from 'react'
import ExternalLink from '../ExternalLink'
export default function VideoPlayer({ src, className }: { src: string; className?: string }) { export default function VideoPlayer({ src, className }: { src: string; className?: string }) {
const { autoplay } = useContentPolicy() const { autoplay } = useContentPolicy()
const [error, setError] = useState(false)
const videoRef = useRef<HTMLVideoElement>(null) const videoRef = useRef<HTMLVideoElement>(null)
const containerRef = useRef<HTMLDivElement>(null) const containerRef = useRef<HTMLDivElement>(null)
@@ -38,6 +40,10 @@ export default function VideoPlayer({ src, className }: { src: string; className
} }
}, [autoplay]) }, [autoplay])
if (error) {
return <ExternalLink url={src} />
}
return ( return (
<div ref={containerRef}> <div ref={containerRef}>
<video <video
@@ -51,6 +57,7 @@ export default function VideoPlayer({ src, className }: { src: string; className
mediaManager.play(event.currentTarget) mediaManager.play(event.currentTarget)
}} }}
muted muted
onError={() => setError(true)}
/> />
</div> </div>
) )

View File

@@ -4,6 +4,7 @@ import mediaManager from '@/services/media-manager.service'
import { YouTubePlayer } from '@/types/youtube' import { YouTubePlayer } from '@/types/youtube'
import { useEffect, useMemo, useRef, useState } from 'react' import { useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import ExternalLink from '../ExternalLink'
export default function YoutubeEmbeddedPlayer({ export default function YoutubeEmbeddedPlayer({
url, url,
@@ -19,6 +20,7 @@ export default function YoutubeEmbeddedPlayer({
const [display, setDisplay] = useState(autoLoadMedia) const [display, setDisplay] = useState(autoLoadMedia)
const { videoId, isShort } = useMemo(() => parseYoutubeUrl(url), [url]) const { videoId, isShort } = useMemo(() => parseYoutubeUrl(url), [url])
const [initSuccess, setInitSuccess] = useState(false) const [initSuccess, setInitSuccess] = useState(false)
const [error, setError] = useState(false)
const playerRef = useRef<YouTubePlayer | null>(null) const playerRef = useRef<YouTubePlayer | null>(null)
const containerRef = useRef<HTMLDivElement>(null) const containerRef = useRef<HTMLDivElement>(null)
@@ -63,7 +65,8 @@ export default function YoutubeEmbeddedPlayer({
}, },
onReady: () => { onReady: () => {
setInitSuccess(true) setInitSuccess(true)
} },
onError: () => setError(true)
} }
}) })
} catch (error) { } catch (error) {
@@ -79,6 +82,10 @@ export default function YoutubeEmbeddedPlayer({
} }
}, [videoId, display, mustLoad]) }, [videoId, display, mustLoad])
if (error) {
return <ExternalLink url={url} />
}
if (!mustLoad && !display) { if (!mustLoad && !display) {
return ( return (
<div <div