feat: add error handling for audio, video, and YouTube players
This commit is contained in:
@@ -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}>
|
||||||
|
|||||||
15
src/components/ExternalLink/index.tsx
Normal file
15
src/components/ExternalLink/index.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user