Use Cases

Membangun a Custom Video Player dengan Claude Code

Learn about building a custom video player using Claude Code. Includes practical code examples.

カスタムvideoプレーヤー dengan Claude Code: 作る

デフォルト browservideoプレーヤー デザイン 統一 難しく、機能 juga 限定され.Claude Code 使えば、ブランド 合ったデザイン 、pemutaran速度変更・字幕・ピクチャーインピクチャー dll. 機能 備えたカスタムプレーヤー pembangunan bisa dilakukan.

要件 プレーヤー

> HTML5 Videoベース カスタムvideoプレーヤー buatkan.
> pemutaran/一時停止、シークバー、音量、pemutaran速度変更、
> フルスクリーン、ピクチャーインピクチャー、字幕tampilan support.

メインプレーヤーkomponen

// src/components/VideoPlayer.tsx
'use client';
import { useRef, useState, useEffect } from 'react';

interface VideoPlayerProps {
  src: string;
  poster?: string;
  subtitles?: { src: string; label: string; lang: string }[];
}

export function VideoPlayer({ src, poster, subtitles }: VideoPlayerProps) {
  const videoRef = useRef<HTMLVideoElement>(null);
  const [isPlaying, setIsPlaying] = useState(false);
  const [currentTime, setCurrentTime] = useState(0);
  const [duration, setDuration] = useState(0);
  const [volume, setVolume] = useState(1);
  const [playbackRate, setPlaybackRate] = useState(1);
  const [showControls, setShowControls] = useState(true);

  const video = videoRef.current;

  useEffect(() => {
    if (!video) return;
    const handleTimeUpdate = () => setCurrentTime(video.currentTime);
    const handleLoadedMetadata = () => setDuration(video.duration);
    video.addEventListener('timeupdate', handleTimeUpdate);
    video.addEventListener('loadedmetadata', handleLoadedMetadata);
    return () => {
      video.removeEventListener('timeupdate', handleTimeUpdate);
      video.removeEventListener('loadedmetadata', handleLoadedMetadata);
    };
  }, [video]);

  const togglePlay = () => {
    if (!video) return;
    if (video.paused) {
      video.play();
      setIsPlaying(true);
    } else {
      video.pause();
      setIsPlaying(false);
    }
  };

  const handleSeek = (e: React.ChangeEvent<HTMLInputElement>) => {
    if (!video) return;
    const time = Number(e.target.value);
    video.currentTime = time;
    setCurrentTime(time);
  };

  const handleVolumeChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    if (!video) return;
    const vol = Number(e.target.value);
    video.volume = vol;
    setVolume(vol);
  };

  const changePlaybackRate = (rate: number) => {
    if (!video) return;
    video.playbackRate = rate;
    setPlaybackRate(rate);
  };

  const toggleFullscreen = () => {
    const container = videoRef.current?.parentElement;
    if (!container) return;
    if (document.fullscreenElement) {
      document.exitFullscreen();
    } else {
      container.requestFullscreen();
    }
  };

  const togglePiP = async () => {
    if (!video) return;
    if (document.pictureInPictureElement) {
      await document.exitPictureInPicture();
    } else {
      await video.requestPictureInPicture();
    }
  };

  const formatTime = (seconds: number) => {
    const m = Math.floor(seconds / 60);
    const s = Math.floor(seconds % 60);
    return `${m}:${s.toString().padStart(2, '0')}`;
  };

  return (
    <div
      className="relative group bg-black rounded-xl overflow-hidden"
      onMouseEnter={() => setShowControls(true)}
      onMouseLeave={() => isPlaying && setShowControls(false)}
    >
      <video
        ref={videoRef}
        src={src}
        poster={poster}
        onClick={togglePlay}
        className="w-full cursor-pointer"
      >
        {subtitles?.map((sub) => (
          <track key={sub.lang} kind="subtitles" src={sub.src} label={sub.label} srcLang={sub.lang} />
        ))}
      </video>

      {/* コントロールバー */}
      <div className={`absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/80 to-transparent p-4 transition-opacity ${showControls ? 'opacity-100' : 'opacity-0'}`}>
        {/* シークバー */}
        <input
          type="range"
          min={0}
          max={duration}
          value={currentTime}
          onChange={handleSeek}
          className="w-full h-1 mb-3 accent-blue-500"
        />
        <div className="flex items-center justify-between text-white text-sm">
          <div className="flex items-center gap-3">
            <button onClick={togglePlay} className="text-xl">
              {isPlaying ? '⏸' : '▶'}
            </button>
            <span>{formatTime(currentTime)} / {formatTime(duration)}</span>
            <input
              type="range"
              min={0}
              max={1}
              step={0.1}
              value={volume}
              onChange={handleVolumeChange}
              className="w-20 h-1 accent-white"
            />
          </div>
          <div className="flex items-center gap-3">
            <select
              value={playbackRate}
              onChange={(e) => changePlaybackRate(Number(e.target.value))}
              className="bg-transparent text-white text-sm"
            >
              {[0.5, 0.75, 1, 1.25, 1.5, 2].map((rate) => (
                <option key={rate} value={rate} className="text-black">
                  {rate}x
                </option>
              ))}
            </select>
            <button onClick={togglePiP} title="ピクチャーインピクチャー">🖼</button>
            <button onClick={toggleFullscreen} title="フルスクリーン">⛶</button>
          </div>
        </div>
      </div>
    </div>
  );
}

キーボードショートカット

// src/hooks/useVideoShortcuts.ts
import { useEffect } from 'react';

export function useVideoShortcuts(videoRef: React.RefObject<HTMLVideoElement>) {
  useEffect(() => {
    const handleKeyDown = (e: KeyboardEvent) => {
      const video = videoRef.current;
      if (!video) return;

      switch (e.key) {
        case ' ':
          e.preventDefault();
          video.paused ? video.play() : video.pause();
          break;
        case 'ArrowRight':
          video.currentTime = Math.min(video.currentTime + 10, video.duration);
          break;
        case 'ArrowLeft':
          video.currentTime = Math.max(video.currentTime - 10, 0);
          break;
        case 'f':
          document.fullscreenElement
            ? document.exitFullscreen()
            : video.parentElement?.requestFullscreen();
          break;
        case 'm':
          video.muted = !video.muted;
          break;
      }
    };

    window.addEventListener('keydown', handleKeyDown);
    return () => window.removeEventListener('keydown', handleKeyDown);
  }, [videoRef]);
}

例 使い方

<VideoPlayer
  src="/videos/demo.mp4"
  poster="/images/poster.jpg"
  subtitles={[
    { src: '/subtitles/ja.vtt', label: '日本語', lang: 'ja' },
    { src: '/subtitles/en.vtt', label: 'English', lang: 'en' },
  ]}
/>

Artikel Terkait

メディア関連 オーディオプレーヤー implementasi、アクセシビリティdukungan アクセシビリティimplementasipanduan juga ご覧.

Webvideo formatやエンコーディング mengenai MDN Web Docs(developer.mozilla.org) 詳しい.

#Claude Code #videoプレーヤー #HTML5 #React #メディア
Gratis

PDF Gratis: Cheatsheet Claude Code dalam 5 Menit

Cukup masukkan emailmu dan kami akan langsung mengirim cheatsheet PDF A4 satu halaman.

Kami menjaga data pribadimu dengan aman dan tidak pernah mengirim spam.

Masa

Tentang Penulis

Masa

Engineer yang aktif menggunakan Claude Code. Mengelola claudecode-lab.com, media teknologi 10 bahasa dengan lebih dari 2.000 halaman.