mirror of
https://github.com/KuekHaoYang/KVideo.git
synced 2026-08-16 09:13:42 +08:00
feat: Implement HLS video segment preloading and caching using a service worker.
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect } from 'react';
|
||||
|
||||
export function ServiceWorkerRegister() {
|
||||
useEffect(() => {
|
||||
if ('serviceWorker' in navigator) {
|
||||
window.addEventListener('load', () => {
|
||||
navigator.serviceWorker.register('/sw.js').then(
|
||||
(registration) => {
|
||||
console.log('ServiceWorker registration successful with scope: ', registration.scope);
|
||||
},
|
||||
(err) => {
|
||||
console.log('ServiceWorker registration failed: ', err);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { useDesktopPlayerState } from './hooks/useDesktopPlayerState';
|
||||
import { useDesktopPlayerLogic } from './hooks/useDesktopPlayerLogic';
|
||||
import { useHLSPreloader } from './hooks/useHLSPreloader';
|
||||
import { DesktopControlsWrapper } from './desktop/DesktopControlsWrapper';
|
||||
import { DesktopOverlayWrapper } from './desktop/DesktopOverlayWrapper';
|
||||
|
||||
@@ -23,6 +24,11 @@ export function DesktopVideoPlayer({
|
||||
shouldAutoPlay = false
|
||||
}: DesktopVideoPlayerProps) {
|
||||
const { refs, state } = useDesktopPlayerState();
|
||||
const { currentTime } = state;
|
||||
|
||||
// Preload HLS segments
|
||||
useHLSPreloader({ src, currentTime });
|
||||
|
||||
const {
|
||||
videoRef,
|
||||
containerRef,
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useEffect } from 'react';
|
||||
import { useScreenOrientation } from '@/lib/hooks/useMobilePlayer';
|
||||
import { useMobilePlayerState } from './hooks/useMobilePlayerState';
|
||||
import { useMobilePlayerLogic } from './hooks/useMobilePlayerLogic';
|
||||
import { useHLSPreloader } from './hooks/useHLSPreloader';
|
||||
import { useMobileGestures } from './hooks/useMobileGestures';
|
||||
import { MobileControlsWrapper } from './mobile/MobileControlsWrapper';
|
||||
import { MobileOverlay } from './mobile/MobileOverlay';
|
||||
@@ -27,6 +28,11 @@ export function MobileVideoPlayer({
|
||||
shouldAutoPlay = false
|
||||
}: MobileVideoPlayerProps) {
|
||||
const { refs, state } = useMobilePlayerState();
|
||||
const { currentTime } = state;
|
||||
|
||||
// Preload HLS segments
|
||||
useHLSPreloader({ src, currentTime });
|
||||
|
||||
const {
|
||||
videoRef,
|
||||
containerRef,
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { parseHLSManifest, type Segment } from '@/lib/utils/hlsManifestParser';
|
||||
import { downloadSegmentQueue } from '@/lib/utils/segmentDownloader';
|
||||
|
||||
interface UseHLSPreloaderProps {
|
||||
src: string;
|
||||
currentTime: number;
|
||||
}
|
||||
|
||||
export function useHLSPreloader({ src, currentTime }: UseHLSPreloaderProps) {
|
||||
const abortControllerRef = useRef<AbortController | null>(null);
|
||||
const segmentsRef = useRef<Segment[]>([]);
|
||||
const [isManifestLoaded, setIsManifestLoaded] = useState(false);
|
||||
const lastStartIndexRef = useRef<number>(-1);
|
||||
|
||||
// Fetch and parse manifest when src changes
|
||||
useEffect(() => {
|
||||
if (!src || !src.endsWith('.m3u8')) return;
|
||||
|
||||
const fetchManifest = async () => {
|
||||
try {
|
||||
console.log('[Preloader] Fetching manifest:', src);
|
||||
|
||||
// Clear cache for this video on mount (fresh start each time)
|
||||
if ('caches' in window) {
|
||||
const cache = await caches.open('video-cache-v1');
|
||||
const requests = await cache.keys();
|
||||
const baseUrl = src.substring(0, src.lastIndexOf('/') + 1);
|
||||
|
||||
for (const request of requests) {
|
||||
if (request.url.startsWith(baseUrl)) {
|
||||
await cache.delete(request);
|
||||
}
|
||||
}
|
||||
console.log('[Preloader] Cleared previous cache for this video');
|
||||
}
|
||||
|
||||
const segments = await parseHLSManifest(src);
|
||||
segmentsRef.current = segments;
|
||||
setIsManifestLoaded(true);
|
||||
const totalDuration = segments[segments.length - 1]?.startTime + segments[segments.length - 1]?.duration || 0;
|
||||
console.log(`[Preloader] Parsed ${segments.length} segments. Total duration: ${totalDuration.toFixed(2)}s`);
|
||||
} catch (error) {
|
||||
console.error('[Preloader] Error fetching manifest:', error);
|
||||
}
|
||||
};
|
||||
|
||||
fetchManifest();
|
||||
}, [src]);
|
||||
|
||||
// Manage downloads based on currentTime
|
||||
useEffect(() => {
|
||||
if (!isManifestLoaded || segmentsRef.current.length === 0) return;
|
||||
|
||||
// Find segment index for currentTime
|
||||
let startIndex = 0;
|
||||
for (let i = 0; i < segmentsRef.current.length; i++) {
|
||||
if (currentTime < segmentsRef.current[i].startTime + segmentsRef.current[i].duration) {
|
||||
startIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (startIndex >= segmentsRef.current.length) return;
|
||||
|
||||
// Check if this is sequential playback or a seek
|
||||
const diff = startIndex - lastStartIndexRef.current;
|
||||
const isSequential = diff >= 0 && diff < 3;
|
||||
|
||||
if (isSequential && abortControllerRef.current) {
|
||||
return; // Continue current download
|
||||
}
|
||||
|
||||
console.log(`[Preloader] Seek detected. Current Time: ${currentTime.toFixed(2)}s. Starting from segment ${startIndex}.`);
|
||||
lastStartIndexRef.current = startIndex;
|
||||
|
||||
// Abort previous queue and start new one
|
||||
if (abortControllerRef.current) {
|
||||
abortControllerRef.current.abort();
|
||||
}
|
||||
abortControllerRef.current = new AbortController();
|
||||
|
||||
downloadSegmentQueue({
|
||||
segments: segmentsRef.current,
|
||||
startIndex,
|
||||
signal: abortControllerRef.current.signal
|
||||
});
|
||||
}, [isManifestLoaded, currentTime]);
|
||||
|
||||
// Cleanup on unmount
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (abortControllerRef.current) {
|
||||
abortControllerRef.current.abort();
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
}
|
||||
Reference in New Issue
Block a user