8 changed files with 191 additions and 10 deletions
@ -0,0 +1,86 @@ |
|||||||
|
import { cn } from '@/lib/utils' |
||||||
|
import mediaManager from '@/services/media-manager.service' |
||||||
|
import { YouTubePlayer } from '@/types/youtube' |
||||||
|
import { useEffect, useMemo, useRef } from 'react' |
||||||
|
|
||||||
|
export default function YoutubeEmbeddedPlayer({ |
||||||
|
url, |
||||||
|
className |
||||||
|
}: { |
||||||
|
url: string |
||||||
|
className?: string |
||||||
|
}) { |
||||||
|
const videoId = useMemo(() => extractVideoId(url), [url]) |
||||||
|
const playerRef = useRef<YouTubePlayer | null>(null) |
||||||
|
const containerRef = useRef<HTMLDivElement>(null) |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
if (!videoId || !containerRef.current) return |
||||||
|
|
||||||
|
if (!window.YT) { |
||||||
|
const script = document.createElement('script') |
||||||
|
script.src = 'https://www.youtube.com/iframe_api' |
||||||
|
document.body.appendChild(script) |
||||||
|
|
||||||
|
window.onYouTubeIframeAPIReady = () => { |
||||||
|
initPlayer() |
||||||
|
} |
||||||
|
} else { |
||||||
|
initPlayer() |
||||||
|
} |
||||||
|
|
||||||
|
function initPlayer() { |
||||||
|
if (!videoId || !containerRef.current) return |
||||||
|
playerRef.current = new window.YT.Player(containerRef.current, { |
||||||
|
videoId: videoId, |
||||||
|
events: { |
||||||
|
onStateChange: (event: any) => { |
||||||
|
if (event.data === window.YT.PlayerState.PLAYING) { |
||||||
|
mediaManager.play(playerRef.current) |
||||||
|
} else if (event.data === window.YT.PlayerState.PAUSED) { |
||||||
|
mediaManager.pause(playerRef.current) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
return () => { |
||||||
|
if (playerRef.current) { |
||||||
|
playerRef.current.destroy() |
||||||
|
} |
||||||
|
} |
||||||
|
}, [videoId]) |
||||||
|
|
||||||
|
if (!videoId) { |
||||||
|
return ( |
||||||
|
<a |
||||||
|
href={url} |
||||||
|
className="text-primary hover:underline" |
||||||
|
target="_blank" |
||||||
|
rel="noopener noreferrer" |
||||||
|
> |
||||||
|
{url} |
||||||
|
</a> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className={cn('rounded-lg border overflow-hidden w-full aspect-video', className)}> |
||||||
|
<div ref={containerRef} className="w-full h-full" /> |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
function extractVideoId(url: string) { |
||||||
|
const patterns = [ |
||||||
|
/(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)([^&\n?#]+)/, |
||||||
|
/youtube\.com\/watch\?.*v=([^&\n?#]+)/ |
||||||
|
] |
||||||
|
|
||||||
|
for (const pattern of patterns) { |
||||||
|
const match = url.match(pattern) |
||||||
|
if (match) return match[1] |
||||||
|
} |
||||||
|
return null |
||||||
|
} |
||||||
@ -0,0 +1,45 @@ |
|||||||
|
declare global { |
||||||
|
interface Window { |
||||||
|
YT: { |
||||||
|
Player: new (element: HTMLElement, config: YouTubePlayerConfig) => YouTubePlayer |
||||||
|
PlayerState: { |
||||||
|
UNSTARTED: number |
||||||
|
ENDED: number |
||||||
|
PLAYING: number |
||||||
|
PAUSED: number |
||||||
|
BUFFERING: number |
||||||
|
CUED: number |
||||||
|
} |
||||||
|
} |
||||||
|
onYouTubeIframeAPIReady: () => void |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
interface YouTubePlayerConfig { |
||||||
|
videoId: string |
||||||
|
width?: number |
||||||
|
height?: number |
||||||
|
playerVars?: { |
||||||
|
autoplay?: 0 | 1 |
||||||
|
controls?: 0 | 1 |
||||||
|
start?: number |
||||||
|
end?: number |
||||||
|
} |
||||||
|
events?: { |
||||||
|
onReady?: (event: { target: YouTubePlayer }) => void |
||||||
|
onStateChange?: (event: { data: number; target: YouTubePlayer }) => void |
||||||
|
onError?: (event: { data: number }) => void |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export interface YouTubePlayer { |
||||||
|
destroy(): void |
||||||
|
playVideo(): void |
||||||
|
pauseVideo(): void |
||||||
|
stopVideo(): void |
||||||
|
getCurrentTime(): number |
||||||
|
getDuration(): number |
||||||
|
getPlayerState(): number |
||||||
|
} |
||||||
|
|
||||||
|
export {} |
||||||
Loading…
Reference in new issue