feat: Integrate video playback progress with history store and ensure seeking on late initialization.

This commit is contained in:
kuekhaoyang
2025-11-22 15:53:05 +08:00
parent fd7d4c8083
commit 1b5f591a7a
3 changed files with 42 additions and 8 deletions
+19 -7
View File
@@ -17,18 +17,30 @@ interface VideoPlayerProps {
export function VideoPlayer({ playUrl, videoId, currentEpisode, onBack }: VideoPlayerProps) {
const [videoError, setVideoError] = useState<string>('');
// Use reactive hook to subscribe to history updates
// This ensures the component re-renders when history is hydrated from localStorage
const viewingHistory = useHistoryStore(state => state.viewingHistory);
const searchParams = useSearchParams();
const { addToHistory } = useHistoryStore();
// Get video metadata from URL params
const source = searchParams.get('source') || '';
const title = searchParams.get('title') || '未知视频';
// Get saved progress for this video
const getSavedProgress = () => {
if (!videoId) return 0;
const savedTime = localStorage.getItem(`video_progress_${videoId}_${currentEpisode}`);
return savedTime ? parseFloat(savedTime) : 0;
// Directly check HistoryStore for progress
// This is the single source of truth for playback resumption
const historyItem = viewingHistory.find(item =>
// Loose match for videoId (string vs number)
item.videoId.toString() === videoId?.toString() &&
item.source === source &&
item.episodeIndex === currentEpisode
);
return historyItem ? historyItem.playbackPosition : 0;
};
// Handle time updates and save progress
@@ -74,7 +86,7 @@ export function VideoPlayer({ playUrl, videoId, currentEpisode, onBack }: VideoP
<Card hover={false} className="p-0 overflow-hidden">
{videoError ? (
<div className="aspect-video bg-black rounded-[var(--radius-2xl)] flex items-center justify-center">
<div
<div
className="text-center text-white max-w-md px-4"
role="alert"
aria-live="assertive"
@@ -84,7 +96,7 @@ export function VideoPlayer({ playUrl, videoId, currentEpisode, onBack }: VideoP
<p className="text-lg font-semibold mb-2"></p>
<p className="text-sm text-gray-300 mb-4">{videoError}</p>
<div className="flex gap-2 justify-center flex-wrap">
<Button
<Button
variant="primary"
onClick={() => setVideoError('')}
className="flex items-center gap-2"
@@ -92,7 +104,7 @@ export function VideoPlayer({ playUrl, videoId, currentEpisode, onBack }: VideoP
<Icons.RefreshCw size={16} />
<span></span>
</Button>
<Button
<Button
variant="secondary"
onClick={onBack}
className="flex items-center gap-2"
@@ -1,4 +1,4 @@
import { useCallback } from 'react';
import { useCallback, useEffect } from 'react';
interface UsePlaybackControlsProps {
videoRef: React.RefObject<HTMLVideoElement | null>;
@@ -66,6 +66,17 @@ export function usePlaybackControls({
});
}, [videoRef, setDuration, setIsLoading, initialTime]);
// Handle late initialization of initialTime (e.g. from async storage hydration)
useEffect(() => {
if (initialTime > 0 && videoRef.current) {
// Only seek if we haven't progressed far (e.g. still near start)
// This prevents jumping if the user has already started watching
if (videoRef.current.currentTime < 2) {
videoRef.current.currentTime = initialTime;
}
}
}, [initialTime, videoRef]);
const handleVideoError = useCallback(() => {
setIsLoading(false);
if (onError) {
@@ -81,6 +81,17 @@ export function useMobilePlaybackControls({
});
}, [videoRef, setDuration, setIsLoading, initialTime]);
// Handle late initialization of initialTime (e.g. from async storage hydration)
useEffect(() => {
if (initialTime > 0 && videoRef.current) {
// Only seek if we haven't progressed far (e.g. still near start)
// This prevents jumping if the user has already started watching
if (videoRef.current.currentTime < 2) {
videoRef.current.currentTime = initialTime;
}
}
}, [initialTime, videoRef]);
const handleVideoError = useCallback(() => {
setIsLoading(false);
if (onError) {