Guide

Quick Start

Working YouTube player in your app in under 5 minutes.

Basic usage

The simplest way to add a YouTube player to your app. Just pass a video ID and ytcn handles everything — thumbnail loading, iframe creation, controls, keyboard shortcuts, and fullscreen.

app/page.tsx
import { YtcnPlayer } from "@/components/ytcn/ytcn-player"

export default function Page() {
  return (
    <div className="max-w-3xl mx-auto p-8">
      <YtcnPlayer videoId="dQw4w9WgXcQ" />
    </div>
  )
}

With Next.js App Router

Server Components are safe

YtcnPlayer is already marked with "use client". You can import it from Server Components — Next.js handles the client boundary automatically.
app/videos/[id]/page.tsx
// This is a Server Component — no "use client" directive needed
import { YtcnPlayer } from "@/components/ytcn/ytcn-player"

interface Props {
  params: Promise<{ id: string }>
}

export default async function VideoPage({ params }: Props) {
  const { id } = await params

  return (
    <main className="max-w-4xl mx-auto p-8">
      <h1 className="text-2xl font-bold mb-4">Video</h1>
      <YtcnPlayer videoId={id} />
    </main>
  )
}

Resume from saved position

Use startAt combined with onTimeUpdate to implement resume-from-where-you-left-off. This pattern is perfect for LMS platforms and course builders.

components/resumable-player.tsx
"use client"

import { YtcnPlayer } from "@/components/ytcn/ytcn-player"
import { useRef, useCallback } from "react"

export function ResumablePlayer({ videoId }: { videoId: string }) {
  const key = `ytcn-progress-${videoId}`
  const saved = typeof window !== "undefined"
    ? Number(localStorage.getItem(key) ?? 0)
    : 0

  const lastSave = useRef(0)

  const handleTimeUpdate = useCallback(
    (current: number) => {
      // Throttle saves to every 5 seconds
      if (current - lastSave.current > 5) {
        localStorage.setItem(key, String(Math.floor(current)))
        lastSave.current = current
      }
    },
    [key]
  )

  const handleEnd = useCallback(() => {
    localStorage.removeItem(key)
    console.log("Video complete!")
  }, [key])

  return (
    <YtcnPlayer
      videoId={videoId}
      startAt={saved}
      onTimeUpdate={handleTimeUpdate}
      onEnd={handleEnd}
    />
  )
}

Playlist auto-advance

Use the onEnd callback to advance to the next video in a playlist.

components/playlist-player.tsx
"use client"

import { YtcnPlayer } from "@/components/ytcn/ytcn-player"
import { useState } from "react"

const VIDEOS = [
  { id: "dQw4w9WgXcQ", title: "Never Gonna Give You Up" },
  { id: "jNQXAC9IVRw", title: "Me at the zoo" },
  { id: "9bZkp7q19f0", title: "Gangnam Style" },
]

export function PlaylistPlayer() {
  const [index, setIndex] = useState(0)
  const video = VIDEOS[index]!

  return (
    <div>
      <YtcnPlayer
        videoId={video.id}
        onEnd={() => {
          if (index < VIDEOS.length - 1) {
            setIndex((i) => i + 1)
          }
        }}
      />
      <div className="mt-4 space-y-2">
        {VIDEOS.map((v, i) => (
          <button
            key={v.id}
            onClick={() => setIndex(i)}
            className={`block w-full text-left px-3 py-2 rounded-md text-sm ${
              i === index
                ? "bg-primary/10 text-primary font-medium"
                : "text-muted-foreground hover:bg-accent"
            }`}
          >
            {v.title}
          </button>
        ))}
      </div>
    </div>
  )
}

Headless — custom UI

If the default controls don't match your design, use the useYtcnPlayer hook to build a completely custom UI. See the Headless Usage guide for full examples.