defVideo 작업분 반영
This commit is contained in:
@@ -12,6 +12,10 @@ const HLS_CONFIG = {
|
||||
enableWorker: true,
|
||||
};
|
||||
|
||||
// PC 브라우저(Chrome/Firefox/Edge)가 HTML5 video로 직접 디코딩 못 하는 코덱.
|
||||
// 이런 영상은 원본 대신 (트랜스코딩된) HLS로 재생해야 한다.
|
||||
const UNSUPPORTED_CODECS = new Set(['hevc', 'h265', 'hvc1', 'hev1']);
|
||||
|
||||
export function useVideoPlayer(containerRef: React.RefObject<HTMLDivElement | null>) {
|
||||
const playerRef = useRef<Player | null>(null);
|
||||
const hlsRef = useRef<Hls | null>(null);
|
||||
@@ -25,7 +29,8 @@ export function useVideoPlayer(containerRef: React.RefObject<HTMLDivElement | nu
|
||||
containerRef.current.appendChild(videoEl);
|
||||
|
||||
const player = videojs(videoEl, {
|
||||
controls: true,
|
||||
// 하단 시간 스크러버는 측점 기반 StationBar 로 대체하므로 Video.js 기본 컨트롤바 숨김
|
||||
controls: false,
|
||||
fluid: true,
|
||||
responsive: true,
|
||||
playbackRates: [0.25, 0.5, 0.75, 1, 1.25, 1.5, 2, 4],
|
||||
@@ -75,28 +80,58 @@ export function useVideoPlayer(containerRef: React.RefObject<HTMLDivElement | nu
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const loadServerStream = useCallback((videoId: string, filename: string) => {
|
||||
const loadServerStream = useCallback(async (videoId: string, filename: string) => {
|
||||
const player = playerRef.current;
|
||||
if (!player) return;
|
||||
|
||||
hlsRef.current?.destroy();
|
||||
hlsRef.current = null;
|
||||
|
||||
// Immediate playback via Range Request
|
||||
const streamUrl = `/api/stream/${videoId}`;
|
||||
player.src({ src: streamUrl, type: 'video/mp4' });
|
||||
store.setSource({ kind: 'server', videoId, filename });
|
||||
store.setHlsReady(false);
|
||||
const playRaw = () => player.src({ src: `/api/stream/${videoId}`, type: 'video/mp4' });
|
||||
|
||||
// 코덱 확인 — 브라우저가 직접 못 푸는 코덱(HEVC 등)이면 HLS로 자동 재생
|
||||
let needsHls = false;
|
||||
try {
|
||||
const meta = await fetch(`/api/meta/${videoId}`).then((r) => r.json());
|
||||
needsHls = UNSUPPORTED_CODECS.has(String(meta?.codec ?? '').toLowerCase());
|
||||
} catch {
|
||||
// meta 조회 실패 시 일단 원본으로 시도
|
||||
}
|
||||
|
||||
// await 사이에 사용자가 다른 영상을 선택했으면 중단
|
||||
const stillCurrent = () => {
|
||||
const s = usePlayerStore.getState().source;
|
||||
return s?.kind === 'server' && s.videoId === videoId;
|
||||
};
|
||||
|
||||
if (needsHls) {
|
||||
if (!stillCurrent()) return;
|
||||
const hlsId = videoId.replace(/\.[^.]+$/, '');
|
||||
const ready = await fetch(`/api/hls/${hlsId}/index.m3u8`, { method: 'HEAD' })
|
||||
.then((r) => r.ok)
|
||||
.catch(() => false);
|
||||
if (!stillCurrent()) return;
|
||||
if (ready) {
|
||||
switchToHls(videoId, 0);
|
||||
return;
|
||||
}
|
||||
// HLS 미생성 상태: 원본을 시도(에러 표시)하고, 사용자가 'HLS 변환' 버튼으로 생성하도록 유도
|
||||
}
|
||||
|
||||
// 지원 코덱이거나 HLS가 아직 없으면 원본 즉시 재생 (Range Request)
|
||||
playRaw();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const switchToHls = useCallback((videoId: string) => {
|
||||
const switchToHls = useCallback((videoId: string, seekTo?: number) => {
|
||||
const player = playerRef.current;
|
||||
if (!player) return;
|
||||
|
||||
const hlsId = videoId.replace(/\.[^.]+$/, '');
|
||||
const hlsUrl = `/api/hls/${hlsId}/index.m3u8`;
|
||||
const savedTime = player.currentTime() ?? 0;
|
||||
const savedTime = seekTo ?? player.currentTime() ?? 0;
|
||||
|
||||
if (Hls.isSupported()) {
|
||||
const hls = new Hls(HLS_CONFIG);
|
||||
|
||||
Reference in New Issue
Block a user