Building a Custom Video Player: Claude Code 활용 가이드
building a custom video player: Claude Code 활용. 실용적인 코드 예시를 포함합니다.
カスタム동영상プレーヤーをClaude Code로作る
デフォルトの브라우저동영상プレーヤーではデザインの統一が難しく、機能も限定されます。Claude Code를 활용하면 브랜드に合ったデザインで、再生速度변경・字幕・ピクチャーインピクチャーなどの機能を備えたカスタムプレーヤーを구축할 수 있습니다。
プレーヤーの要件
> HTML5 Videoベースのカスタム동영상プレーヤーを作って。
> 再生/一時停止、シークバー、音量、再生速度변경、
> フルスクリーン、ピクチャーインピクチャー、字幕표시に대응して。
メインプレーヤー컴포넌트
// 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' },
]}
/>
関連글
メディア関連ではオーディオプレーヤーの구현、アクセシビリティ대응はアクセシビリティ구현가이드도 확인하세요.
Web동영상のフォーマットやエンコーディング에 대해서는MDN Web Docs(developer.mozilla.org)が詳しいです。
#Claude Code
#動画プレーヤー
#HTML5
#React
#メディア
Related Posts
Use Cases
Use Cases
Claude Code로 리팩토링을 자동화하는 방법
Claude Code를 활용해 코드 리팩토링을 효율적으로 자동화하는 방법을 알아봅니다. 실전 프롬프트와 구체적인 리팩토링 패턴을 소개합니다.
Use Cases
Use Cases
Claude Code로 사이드 프로젝트 개발 속도를 극대화하는 방법 [예제 포함]
Claude Code를 활용해 개인 프로젝트 개발 속도를 획기적으로 높이는 방법을 알아봅니다. 실전 예제와 아이디어부터 배포까지의 워크플로를 포함합니다.
Use Cases
Use Cases
Complete CORS Configuration Guide: Claude Code 활용 가이드
complete cors configuration guide: Claude Code 활용. 실용적인 팁과 코드 예시를 포함합니다.