Player.tsx raw
1 import { cn } from '@/lib/utils'
2 import { ExternalLink, Play } from 'lucide-react'
3 import { memo, useState } from 'react'
4
5 interface PlayerProps {
6 videoId: string
7 isShort: boolean
8 className?: string
9 }
10
11 /**
12 * Privacy-preserving YouTube thumbnail display.
13 *
14 * Does NOT load any YouTube scripts or iframes. Shows a static thumbnail
15 * from YouTube's image CDN and opens the video in a new tab when clicked.
16 *
17 * This eliminates all tracking that YouTube's embedded player performs:
18 * - No /youtubei/v1/log_event telemetry
19 * - No cookies set
20 * - No browser fingerprinting
21 * - No behavioral tracking on scroll/visibility
22 */
23 const Player = memo(({ videoId, isShort, className }: PlayerProps) => {
24 const [imgError, setImgError] = useState(false)
25
26 // YouTube thumbnail URLs - try maxres first, fallback to hqdefault
27 // maxresdefault.jpg may not exist for all videos
28 const thumbnailUrl = imgError
29 ? `https://i.ytimg.com/vi/${videoId}/hqdefault.jpg`
30 : `https://i.ytimg.com/vi/${videoId}/maxresdefault.jpg`
31
32 // Construct the direct YouTube URL
33 const youtubeUrl = isShort
34 ? `https://www.youtube.com/shorts/${videoId}`
35 : `https://www.youtube.com/watch?v=${videoId}`
36
37 return (
38 <a
39 href={youtubeUrl}
40 target="_blank"
41 rel="noopener noreferrer"
42 onClick={(e) => e.stopPropagation()}
43 className={cn(
44 'block rounded-xl border overflow-hidden cursor-pointer relative group',
45 isShort ? 'aspect-[9/16] max-h-[80vh] sm:max-h-[60vh]' : 'aspect-video max-h-[60vh]',
46 className
47 )}
48 >
49 {/* Thumbnail image */}
50 <img
51 src={thumbnailUrl}
52 alt="YouTube video thumbnail"
53 className="w-full h-full object-cover"
54 loading="lazy"
55 onError={() => !imgError && setImgError(true)}
56 />
57
58 {/* Play button overlay */}
59 <div className="absolute inset-0 flex items-center justify-center bg-black/20 group-hover:bg-black/40 transition-colors">
60 <div className="bg-red-600 rounded-full p-4 group-hover:scale-110 transition-transform shadow-lg">
61 <Play className="size-8 text-white fill-white" />
62 </div>
63 </div>
64
65 {/* External link indicator */}
66 <div className="absolute top-2 right-2 bg-black/60 rounded-md px-2 py-1 flex items-center gap-1 text-white text-xs opacity-0 group-hover:opacity-100 transition-opacity">
67 <ExternalLink className="size-3" />
68 <span>YouTube</span>
69 </div>
70 </a>
71 )
72 })
73
74 Player.displayName = 'YoutubePlayer'
75
76 export default Player
77