We use cookies to enhance your experience on the site
CodeWorlds

Video Platform - Embedding and Playback

Welcome aboard @name! Time to build Captain's Cinema - a pirate video platform like YouTube. 🎬

Pirates record their adventures, sea battles, and treasure discoveries. You'll learn how to build a platform for sharing videos.

YouTube vs Other Platforms

Twitter/Instagram:

  • Short texts and photos
  • Scroll feed
  • Simple media

YouTube:

  • Long videos (minutes/hours)
  • Playlists and channels
  • Video player with controls
  • Recommendations and related videos
  • Subscriptions

Video Structure

1interface Video {
2  id: string;
3  title: string;
4  description: string;
5  thumbnail: string;
6  duration: number; // in seconds
7  views: number;
8  likes: number;
9  dislikes: number;
10  uploadDate: Date;
11  channel: {
12    id: string;
13    name: string;
14    avatar: string;
15    subscribers: number;
16  };
17  tags: string[];
18  category: string;
19}

Video Embedding - Displaying Video

In a real application you use

<video>
or embeds (YouTube, Vimeo). We'll simulate it:

1interface VideoPlayerProps {
2  video: Video;
3}
4
5export default function VideoPlayer({ video }: VideoPlayerProps) {
6  const [isPlaying, setIsPlaying] = useState(false);
7  const [currentTime, setCurrentTime] = useState(0);
8  const [volume, setVolume] = useState(100);
9
10  return (
11    <div className="bg-black rounded-lg overflow-hidden">
12      {/* Video Area - simulated */}
13      <div className="relative aspect-video bg-gradient-to-br from-slate-800 to-slate-900 flex items-center justify-center">
14        {!isPlaying ? (
15          <button
16            onClick={() => setIsPlaying(true)}
17            className="w-20 h-20 bg-red-600 rounded-full flex items-center justify-center text-white text-4xl hover:bg-red-700 transition"
18          >
1920          </button>
21        ) : (
22          <div className="text-white text-center">
23            <div className="text-6xl mb-4">🎬</div>
24            <p className="text-2xl font-bold">{video.title}</p>
25            <p className="text-gray-400">Playing...</p>
26          </div>
27        )}
28
29        {/* Overlay Controls */}
30        <div className="absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/80 to-transparent p-4">
31          {/* Progress Bar */}
32          <div className="mb-3">
33            <input
34              type="range"
35              min="0"
36              max={video.duration}
37              value={currentTime}
38              onChange={(e) => setCurrentTime(parseInt(e.target.value))}
39              className="w-full h-1 bg-gray-600 rounded-lg appearance-none cursor-pointer"
40            />
41            <div className="flex justify-between text-xs text-white mt-1">
42              <span>{formatTime(currentTime)}</span>
43              <span>{formatTime(video.duration)}</span>
44            </div>
45          </div>
46
47          {/* Controls */}
48          <div className="flex items-center gap-4">
49            <button
50              onClick={() => setIsPlaying(!isPlaying)}
51              className="text-white text-2xl hover:scale-110 transition"
52            >
53              {isPlaying ? '⏸' : '▶'}
54            </button>
55
56            <div className="flex items-center gap-2">
57              <button className="text-white text-xl">🔊</button>
58              <input
59                type="range"
60                min="0"
61                max="100"
62                value={volume}
63                onChange={(e) => setVolume(parseInt(e.target.value))}
64                className="w-20 h-1 bg-gray-600 rounded-lg"
65              />
66            </div>
67
68            <div className="flex-1" />
69
70            <button className="text-white text-xl hover:scale-110 transition">
71              ⚙️
72            </button>
73            <button className="text-white text-xl hover:scale-110 transition">
7475            </button>
76          </div>
77        </div>
78      </div>
79    </div>
80  );
81}
82
83function formatTime(seconds: number): string {
84  const mins = Math.floor(seconds / 60);
85  const secs = seconds % 60;
86  return `${mins}:${secs.toString().padStart(2, '0')}`;
87}

Video Grid - Video List

1function VideoThumbnail({ video }: { video: Video }) {
2  return (
3    <div className="group cursor-pointer">
4      {/* Thumbnail */}
5      <div className="relative aspect-video bg-gray-200 rounded-lg overflow-hidden mb-2">
6        <img
7          src={video.thumbnail}
8          alt={video.title}
9          className="w-full h-full object-cover group-hover:scale-105 transition-transform"
10        />
11
12        {/* Duration Badge */}
13        <div className="absolute bottom-2 right-2 bg-black bg-opacity-80 text-white text-xs px-2 py-1 rounded">
14          {formatTime(video.duration)}
15        </div>
16
17        {/* Play Overlay on Hover */}
18        <div className="absolute inset-0 bg-black bg-opacity-0 group-hover:bg-opacity-30 transition-opacity flex items-center justify-center">
19          <div className="opacity-0 group-hover:opacity-100 transition-opacity text-white text-4xl">
2021          </div>
22        </div>
23      </div>
24
25      {/* Video Info */}
26      <div className="flex gap-3">
27        {/* Channel Avatar */}
28        <img
29          src={video.channel.avatar}
30          alt={video.channel.name}
31          className="w-9 h-9 rounded-full"
32        />
33
34        <div className="flex-1">
35          {/* Title */}
36          <h3 className="font-semibold text-sm line-clamp-2 mb-1">
37            {video.title}
38          </h3>
39
40          {/* Channel Name */}
41          <p className="text-xs text-gray-600 mb-1">
42            {video.channel.name}
43          </p>
44
45          {/* Views & Date */}
46          <p className="text-xs text-gray-600">
47            {formatViews(video.views)} views • {formatDate(video.uploadDate)}
48          </p>
49        </div>
50      </div>
51    </div>
52  );
53}
54
55function formatViews(count: number): string {
56  if (count >= 1000000) return `${(count / 1000000).toFixed(1)}M`;
57  if (count >= 1000) return `${(count / 1000).toFixed(1)}K`;
58  return count.toString();
59}
60
61function formatDate(date: Date): string {
62  const days = Math.floor((new Date().getTime() - date.getTime()) / (1000 * 60 * 60 * 24));
63  if (days === 0) return 'today';
64  if (days === 1) return 'yesterday';
65  if (days < 7) return `${days} days ago`;
66  if (days < 30) return `${Math.floor(days / 7)} weeks ago`;
67  if (days < 365) return `${Math.floor(days / 30)} months ago`;
68  return `${Math.floor(days / 365)} years ago`;
69}

Video Grid Layout

1export default function VideoGrid({ videos }: { videos: Video[] }) {
2  return (
3    <div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
4      {videos.map(video => (
5        <VideoThumbnail key={video.id} video={video} />
6      ))}
7    </div>
8  );
9}

Watch Page Layout

1export default function WatchPage({ video }: { video: Video }) {
2  const [isSubscribed, setIsSubscribed] = useState(false);
3
4  return (
5    <div className="max-w-7xl mx-auto p-4">
6      <div className="grid grid-cols-1 lg:grid-cols-3 gap-4">
7        {/* Main Column - Video Player */}
8        <div className="lg:col-span-2">
9          <VideoPlayer video={video} />
10
11          {/* Video Info */}
12          <div className="mt-4">
13            <h1 className="text-xl font-bold mb-2">{video.title}</h1>
14
15            <div className="flex items-center justify-between">
16              <div className="flex items-center gap-3">
17                <img
18                  src={video.channel.avatar}
19                  alt={video.channel.name}
20                  className="w-12 h-12 rounded-full"
21                />
22                <div>
23                  <h3 className="font-semibold">{video.channel.name}</h3>
24                  <p className="text-sm text-gray-600">
25                    {formatViews(video.channel.subscribers)} subscribers
26                  </p>
27                </div>
28                <button
29                  onClick={() => setIsSubscribed(!isSubscribed)}
30                  className={`px-4 py-2 rounded-full font-semibold transition ${
31                    isSubscribed
32                      ? 'bg-gray-200 text-gray-700'
33                      : 'bg-red-600 text-white hover:bg-red-700'
34                  }`}
35                >
36                  {isSubscribed ? 'Subscribed' : 'Subscribe'}
37                </button>
38              </div>
39
40              {/* Like/Dislike */}
41              <div className="flex items-center gap-2 bg-gray-100 rounded-full px-3 py-2">
42                <button className="flex items-center gap-1 hover:text-blue-600">
43                  👍 {formatViews(video.likes)}
44                </button>
45                <div className="w-px h-6 bg-gray-300" />
46                <button className="flex items-center gap-1 hover:text-red-600">
47                  👎
48                </button>
49              </div>
50            </div>
51
52            {/* Description */}
53            <div className="mt-4 bg-gray-100 rounded-lg p-4">
54              <p className="text-sm font-semibold mb-2">
55                {formatViews(video.views)} views • {formatDate(video.uploadDate)}
56              </p>
57              <p className="text-sm whitespace-pre-wrap">{video.description}</p>
58            </div>
59          </div>
60        </div>
61
62        {/* Sidebar - Related Videos */}
63        <div className="space-y-2">
64          <h2 className="font-bold mb-3">Related Videos</h2>
65          {/* Related videos list */}
66        </div>
67      </div>
68    </div>
69  );
70}

Real YouTube Embed (for real projects)

1function YouTubeEmbed({ videoId }: { videoId: string }) {
2  return (
3    <div className="relative aspect-video">
4      <iframe
5        src={`https://www.youtube.com/embed/${videoId}`}
6        title="YouTube video player"
7        allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
8        allowFullScreen
9        className="absolute inset-0 w-full h-full"
10      />
11    </div>
12  );
13}
14
15// Usage:
16<YouTubeEmbed videoId="dQw4w9WgXcQ" />

Summary 🎓

Video structure - title, thumbnail, duration, views ✅ Video player - controls, progress bar, volume ✅ formatTime - seconds → MM:SS ✅ Video thumbnail - hover effects, duration badge ✅ Video grid - responsive layout ✅ Watch page - player + info + related videos ✅ Subscribe button - toggle subscription ✅ Like/Dislike - engagement buttons ✅ YouTube embed - real video integration

In the next exercise we'll add playlists and channel pages!

See you there! 🚀

Go to CodeWorlds