refactor: 重构作品播放页性能、媒体同步与内存管理
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
import { useRouter } from "next/navigation";
|
||||
import type { RefObject } from "react";
|
||||
import { useEffect } from "react";
|
||||
import { useEffect, useRef } from "react";
|
||||
import type { LoopMode, Neighbors } from "../types.ts";
|
||||
import { cleanupExpiredProgressStorage, releaseMediaElement } from "../utils";
|
||||
|
||||
interface UseVideoPlayerProps {
|
||||
enabled?: boolean;
|
||||
@@ -29,18 +30,27 @@ export function useVideoPlayer({
|
||||
setIsPlaying,
|
||||
setProgress,
|
||||
setProgressRestored,
|
||||
}: UseVideoPlayerProps) {
|
||||
}: UseVideoPlayerProps): void {
|
||||
const router = useRouter();
|
||||
const restoredAwemeIdRef = useRef<string | null>(null);
|
||||
const lastProgressRef = useRef(0);
|
||||
|
||||
// 挂载时清理过期的播放进度缓存
|
||||
useEffect(() => {
|
||||
if (!enabled) return;
|
||||
cleanupExpiredProgressStorage();
|
||||
}, [enabled]);
|
||||
|
||||
// 恢复播放进度
|
||||
useEffect(() => {
|
||||
if (!enabled) return;
|
||||
if (progressRestored) return;
|
||||
if (progressRestored && restoredAwemeIdRef.current === awemeId) return;
|
||||
const v = videoRef.current;
|
||||
if (!v) return;
|
||||
|
||||
const onLoadedMetadata = () => {
|
||||
if (progressRestored) return;
|
||||
const onLoadedMetadata = (): void => {
|
||||
if (restoredAwemeIdRef.current === awemeId) return;
|
||||
restoredAwemeIdRef.current = awemeId;
|
||||
|
||||
try {
|
||||
const key = `aweme_progress_${awemeId}`;
|
||||
@@ -50,7 +60,13 @@ export function useVideoPlayer({
|
||||
return;
|
||||
}
|
||||
|
||||
const { time, timestamp } = JSON.parse(saved);
|
||||
const parsed = JSON.parse(saved) as {
|
||||
time?: unknown;
|
||||
timestamp?: unknown;
|
||||
};
|
||||
const time = typeof parsed.time === "number" ? parsed.time : 0;
|
||||
const timestamp =
|
||||
typeof parsed.timestamp === "number" ? parsed.timestamp : 0;
|
||||
const now = Date.now();
|
||||
const fiveMinutes = 5 * 60 * 1000;
|
||||
|
||||
@@ -60,7 +76,6 @@ export function useVideoPlayer({
|
||||
time < v.duration - 1
|
||||
) {
|
||||
v.currentTime = time;
|
||||
console.log(`恢复播放进度: ${Math.round(time)}s`);
|
||||
} else if (now - timestamp >= fiveMinutes) {
|
||||
localStorage.removeItem(key);
|
||||
}
|
||||
@@ -73,19 +88,20 @@ export function useVideoPlayer({
|
||||
|
||||
if (v.readyState >= 1) {
|
||||
onLoadedMetadata();
|
||||
} else {
|
||||
v.addEventListener("loadedmetadata", onLoadedMetadata, { once: true });
|
||||
return () => v.removeEventListener("loadedmetadata", onLoadedMetadata);
|
||||
return;
|
||||
}
|
||||
|
||||
v.addEventListener("loadedmetadata", onLoadedMetadata, { once: true });
|
||||
return () => v.removeEventListener("loadedmetadata", onLoadedMetadata);
|
||||
}, [awemeId, enabled, progressRestored, videoRef, setProgressRestored]);
|
||||
|
||||
// 保存播放进度
|
||||
// 保存播放进度并在卸载/切页时主动释放媒体解码缓冲区
|
||||
useEffect(() => {
|
||||
if (!enabled) return;
|
||||
const v = videoRef.current;
|
||||
if (!v) return;
|
||||
|
||||
const saveProgress = () => {
|
||||
const saveProgress = (): void => {
|
||||
if (!v.duration || Number.isNaN(v.duration) || v.currentTime < 1) return;
|
||||
|
||||
try {
|
||||
@@ -100,14 +116,15 @@ export function useVideoPlayer({
|
||||
}
|
||||
};
|
||||
|
||||
const interval = setInterval(saveProgress, 2000);
|
||||
const onBeforeUnload = () => saveProgress();
|
||||
const interval = window.setInterval(saveProgress, 2000);
|
||||
const onBeforeUnload = (): void => saveProgress();
|
||||
window.addEventListener("beforeunload", onBeforeUnload);
|
||||
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
window.clearInterval(interval);
|
||||
window.removeEventListener("beforeunload", onBeforeUnload);
|
||||
saveProgress();
|
||||
releaseMediaElement(v);
|
||||
};
|
||||
}, [awemeId, enabled, videoRef]);
|
||||
|
||||
@@ -117,13 +134,17 @@ export function useVideoPlayer({
|
||||
const v = videoRef.current;
|
||||
if (!v) return;
|
||||
|
||||
const onTime = () => {
|
||||
const onTime = (): void => {
|
||||
if (!v.duration || Number.isNaN(v.duration)) return;
|
||||
setProgress(v.currentTime / v.duration);
|
||||
const nextProgress = Math.max(0, Math.min(1, v.currentTime / v.duration));
|
||||
if (Math.abs(nextProgress - lastProgressRef.current) >= 0.001) {
|
||||
lastProgressRef.current = nextProgress;
|
||||
setProgress(nextProgress);
|
||||
}
|
||||
};
|
||||
const onPlay = () => setIsPlaying(true);
|
||||
const onPause = () => setIsPlaying(false);
|
||||
const onEnded = () => {
|
||||
const onPlay = (): void => setIsPlaying(true);
|
||||
const onPause = (): void => setIsPlaying(false);
|
||||
const onEnded = (): void => {
|
||||
if (loopMode === "sequential" && neighbors?.next) {
|
||||
router.push(`/aweme/${neighbors.next.aweme_id}`);
|
||||
}
|
||||
@@ -158,23 +179,26 @@ export function useVideoPlayer({
|
||||
const v = videoRef.current;
|
||||
if (!v) return;
|
||||
|
||||
const checkAutoplay = async () => {
|
||||
const checkAutoplay = async (): Promise<void> => {
|
||||
try {
|
||||
await v.play();
|
||||
setIsPlaying(true);
|
||||
} catch {
|
||||
console.log("自动播放被阻止,需要用户交互");
|
||||
setIsPlaying(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (v.readyState >= 1) {
|
||||
checkAutoplay();
|
||||
} else {
|
||||
v.addEventListener("loadedmetadata", checkAutoplay, { once: true });
|
||||
return () => v.removeEventListener("loadedmetadata", checkAutoplay);
|
||||
void checkAutoplay();
|
||||
return;
|
||||
}
|
||||
}, [enabled, videoRef, setIsPlaying]);
|
||||
|
||||
const onLoaded = (): void => {
|
||||
void checkAutoplay();
|
||||
};
|
||||
v.addEventListener("loadedmetadata", onLoaded, { once: true });
|
||||
return () => v.removeEventListener("loadedmetadata", onLoaded);
|
||||
}, [awemeId, enabled, videoRef, setIsPlaying]);
|
||||
|
||||
// 更新音量和倍速
|
||||
useEffect(() => {
|
||||
|
||||
Reference in New Issue
Block a user