8 changed files with 191 additions and 10 deletions
@ -0,0 +1,86 @@
@@ -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 @@
@@ -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