douyin-archive/app/aweme/[awemeId]/hooks/useImageCarousel.ts

133 lines
3.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useEffect, useRef, useState } from "react";
import type { RefObject } from "react";
import { useRouter } from "next/navigation";
import type { ImageData, LoopMode, Neighbors } from "../types.ts";
interface UseImageCarouselProps {
images: ImageData["images"];
isPlaying: boolean;
loopMode: LoopMode;
neighbors: Neighbors;
volume: number;
audioRef: RefObject<HTMLAudioElement | null>;
scrollerRef: RefObject<HTMLDivElement | null>;
setProgress: (progress: number) => void;
/** 单张图片显示时长(毫秒),默认 5000ms */
segmentMs?: number;
}
export function useImageCarousel({
images,
isPlaying,
loopMode,
neighbors,
volume,
audioRef,
scrollerRef,
setProgress,
segmentMs = 5000,
}: UseImageCarouselProps) {
const router = useRouter();
const [idx, setIdx] = useState(0);
const [segProgress, setSegProgress] = useState(0);
const segStartRef = useRef<number | null>(null);
const idxRef = useRef<number>(0);
const rafRef = useRef<number | null>(null);
useEffect(() => {
idxRef.current = idx;
}, [idx]);
// BGM 控制
useEffect(() => {
const el = audioRef.current;
if (!el) return;
el.volume = volume;
if (isPlaying) {
el.play().catch(() => {});
} else {
el.pause();
}
}, [audioRef, isPlaying, volume]);
// 自动切页
useEffect(() => {
if (!images?.length) return;
if (segStartRef.current == null) segStartRef.current = performance.now();
let lastTs = performance.now();
const tick = (ts: number) => {
if (!images?.length) return;
if (!isPlaying) segStartRef.current! += ts - lastTs;
lastTs = ts;
let start = segStartRef.current!;
let localIdx = idxRef.current;
let elapsed = ts - start;
// 获取当前图片的显示时长(动图使用其 duration静态图片使用 segmentMs
const getCurrentSegmentDuration = (index: number) => {
const img = images[index];
return img?.duration ?? segmentMs;
};
let currentSegmentDuration = getCurrentSegmentDuration(localIdx);
while (elapsed >= currentSegmentDuration) {
elapsed -= currentSegmentDuration;
if (localIdx >= images.length - 1) {
if (loopMode === "sequential" && neighbors?.next) {
router.push(`/aweme/${neighbors.next.aweme_id}`);
return;
}
localIdx = 0;
} else {
localIdx = localIdx + 1;
}
// 更新下一张图片的时长
currentSegmentDuration = getCurrentSegmentDuration(localIdx);
}
segStartRef.current = ts - elapsed;
if (localIdx !== idxRef.current) {
idxRef.current = localIdx;
setIdx(localIdx);
// 虚拟滚动不需要实际滚动 DOM
}
const localSeg = Math.max(0, Math.min(1, elapsed / currentSegmentDuration));
setSegProgress(localSeg);
// 计算总进度:已完成的图片 + 当前图片的进度
let totalProgress = 0;
for (let i = 0; i < localIdx; i++) {
totalProgress += 1;
}
totalProgress += localSeg;
setProgress(totalProgress / images.length);
rafRef.current = requestAnimationFrame(tick);
};
rafRef.current = requestAnimationFrame(tick);
return () => {
if (rafRef.current) cancelAnimationFrame(rafRef.current);
rafRef.current = null;
};
}, [images, isPlaying, loopMode, neighbors?.next, router, scrollerRef, setProgress, segmentMs]);
return {
idx,
setIdx,
segProgress,
segStartRef,
idxRef,
};
}