133 lines
3.5 KiB
TypeScript
133 lines
3.5 KiB
TypeScript
import { useEffect, useRef, useState } from "react";
|
||
import type { RefObject } from "react";
|
||
import { useRouter } from "next/navigation";
|
||
import type { ImageData, LoopMode, Neighbors } from "../types";
|
||
|
||
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,
|
||
};
|
||
}
|