mirror of
https://github.com/KuekHaoYang/KVideo.git
synced 2026-08-12 23:33:43 +08:00
174 lines
5.0 KiB
TypeScript
174 lines
5.0 KiB
TypeScript
/**
|
|
* History State Store using Zustand
|
|
* Manages viewing history with localStorage persistence
|
|
*/
|
|
|
|
import { create } from 'zustand';
|
|
import { persist } from 'zustand/middleware';
|
|
import type { VideoHistoryItem, Episode } from '@/lib/types';
|
|
import { clearSegmentsForUrl, clearAllCache } from '@/lib/utils/cacheManager';
|
|
import { profiledKey } from '@/lib/utils/profile-storage';
|
|
|
|
const MAX_HISTORY_ITEMS = 50;
|
|
|
|
interface HistoryState {
|
|
viewingHistory: VideoHistoryItem[];
|
|
}
|
|
|
|
interface HistoryActions {
|
|
addToHistory: (
|
|
videoId: string | number,
|
|
title: string,
|
|
url: string,
|
|
episodeIndex: number,
|
|
source: string,
|
|
playbackPosition: number,
|
|
duration: number,
|
|
poster?: string,
|
|
episodes?: Episode[],
|
|
metadata?: { vod_actor?: string; type_name?: string; vod_area?: string }
|
|
) => void;
|
|
|
|
removeFromHistory: (videoId: string | number, source: string) => void;
|
|
clearHistory: () => void;
|
|
importHistory: (history: VideoHistoryItem[]) => void;
|
|
}
|
|
|
|
interface HistoryStore extends HistoryState, HistoryActions { }
|
|
|
|
/**
|
|
* Generate unique identifier for deduplication
|
|
*/
|
|
function generateShowIdentifier(
|
|
title: string,
|
|
source: string,
|
|
videoId: string | number
|
|
): string {
|
|
return `${source}:${videoId}:${title.toLowerCase().trim()}`;
|
|
}
|
|
|
|
const createHistoryStore = (name: string) =>
|
|
create<HistoryStore>()(
|
|
persist(
|
|
(set, get) => ({
|
|
viewingHistory: [],
|
|
|
|
addToHistory: (
|
|
videoId,
|
|
title,
|
|
url,
|
|
episodeIndex,
|
|
source,
|
|
playbackPosition,
|
|
duration,
|
|
poster,
|
|
episodes = [],
|
|
metadata
|
|
) => {
|
|
const showIdentifier = generateShowIdentifier(title, source, videoId);
|
|
const timestamp = Date.now();
|
|
|
|
set((state) => {
|
|
// Check if item already exists
|
|
const existingIndex = state.viewingHistory.findIndex(
|
|
(item) => item.showIdentifier === showIdentifier
|
|
);
|
|
|
|
let newHistory: VideoHistoryItem[];
|
|
|
|
if (existingIndex !== -1) {
|
|
// Update existing item and move to top
|
|
const updatedItem: VideoHistoryItem = {
|
|
...state.viewingHistory[existingIndex],
|
|
url,
|
|
episodeIndex,
|
|
playbackPosition,
|
|
duration,
|
|
timestamp,
|
|
episodes: episodes.length > 0 ? episodes : state.viewingHistory[existingIndex].episodes,
|
|
vod_actor: metadata?.vod_actor ?? state.viewingHistory[existingIndex].vod_actor,
|
|
type_name: metadata?.type_name ?? state.viewingHistory[existingIndex].type_name,
|
|
vod_area: metadata?.vod_area ?? state.viewingHistory[existingIndex].vod_area,
|
|
};
|
|
|
|
newHistory = [
|
|
updatedItem,
|
|
...state.viewingHistory.filter((_, index) => index !== existingIndex),
|
|
];
|
|
} else {
|
|
// Add new item at the top
|
|
const newItem: VideoHistoryItem = {
|
|
videoId,
|
|
title,
|
|
url,
|
|
episodeIndex,
|
|
source,
|
|
timestamp,
|
|
playbackPosition,
|
|
duration,
|
|
poster,
|
|
episodes,
|
|
showIdentifier,
|
|
vod_actor: metadata?.vod_actor,
|
|
type_name: metadata?.type_name,
|
|
vod_area: metadata?.vod_area,
|
|
};
|
|
|
|
newHistory = [newItem, ...state.viewingHistory];
|
|
}
|
|
|
|
// Limit history size
|
|
if (newHistory.length > MAX_HISTORY_ITEMS) {
|
|
newHistory = newHistory.slice(0, MAX_HISTORY_ITEMS);
|
|
}
|
|
|
|
return { viewingHistory: newHistory };
|
|
});
|
|
},
|
|
|
|
removeFromHistory: (videoId, source) => {
|
|
const state = get();
|
|
const itemToRemove = state.viewingHistory.find(
|
|
(item) => item.videoId === videoId && item.source === source
|
|
);
|
|
|
|
if (itemToRemove) {
|
|
// Clear cache for this video
|
|
clearSegmentsForUrl(itemToRemove.url);
|
|
}
|
|
|
|
set((state) => ({
|
|
viewingHistory: state.viewingHistory.filter(
|
|
(item) => !(item.videoId === videoId && item.source === source)
|
|
),
|
|
}));
|
|
},
|
|
|
|
clearHistory: () => {
|
|
// Clear all cached segments
|
|
clearAllCache();
|
|
set({ viewingHistory: [] });
|
|
},
|
|
|
|
importHistory: (history) => {
|
|
set({ viewingHistory: history });
|
|
},
|
|
}),
|
|
{
|
|
name,
|
|
}
|
|
)
|
|
);
|
|
|
|
export const useHistoryStore = createHistoryStore(profiledKey('kvideo-history-store'));
|
|
export const usePremiumHistoryStore = createHistoryStore(profiledKey('kvideo-premium-history-store'));
|
|
|
|
/**
|
|
* Helper hook to get the appropriate history store
|
|
*/
|
|
export function useHistory(isPremium = false) {
|
|
const normalStore = useHistoryStore();
|
|
const premiumStore = usePremiumHistoryStore();
|
|
return isPremium ? premiumStore : normalStore;
|
|
}
|