import { PreferencesUtil } from '@pura/harmony-utils' import { VideoItem } from '../viewmodel/VideoItem' import { PlaybackSnapshot, PlaybackSnapshotSong } from './model/PlaybackSnapshot' export const PLAYBACK_SNAPSHOT_PREFERENCES_KEY = 'playback.host.snapshot' export interface RecoveredPlaybackSnapshotState { queue: VideoItem[] currentIndex: number currentSong: VideoItem | undefined } export class PlaybackSnapshotStore { public write(snapshot: PlaybackSnapshot): void { PreferencesUtil.putSync(PLAYBACK_SNAPSHOT_PREFERENCES_KEY, JSON.stringify(snapshot)) } public read(): PlaybackSnapshot | undefined { const text = PreferencesUtil.getStringSync(PLAYBACK_SNAPSHOT_PREFERENCES_KEY, '') if (text.length === 0) { return undefined } try { const parsedSnapshot = JSON.parse(text) as Object | null if (!this.isValidSnapshot(parsedSnapshot)) { this.clear() return undefined } return parsedSnapshot } catch (_error) { this.clear() return undefined } } public clear(): void { PreferencesUtil.putSync(PLAYBACK_SNAPSHOT_PREFERENCES_KEY, '') } public restoreQueue(snapshot: PlaybackSnapshot): VideoItem[] { return this.recoverQueueState(snapshot).queue } public restoreCurrentSong(snapshot: PlaybackSnapshot): VideoItem | undefined { return this.recoverQueueState(snapshot).currentSong } public recoverQueueState(snapshot: PlaybackSnapshot): RecoveredPlaybackSnapshotState { const snapshotQueue = Array.isArray(snapshot.queue) ? snapshot.queue : [] const queue: VideoItem[] = [] for (let index = 0; index < snapshotQueue.length; index++) { const snapshotSong = snapshotQueue[index] as Object | null if (!this.isValidSnapshotSong(snapshotSong)) { continue } queue.push(this.buildVideoItem(snapshotSong)) } const currentIndex = this.resolveCurrentIndex(snapshot, queue) const currentSong = currentIndex >= 0 ? queue[currentIndex] : undefined return { queue, currentIndex, currentSong } } private resolveCurrentIndex(snapshot: PlaybackSnapshot, queue: VideoItem[]): number { if (queue.length <= 0) { return -1 } if (snapshot.currentSongKey && snapshot.currentSongKey !== '') { const matchedIndex = queue.findIndex((item: VideoItem): boolean => item.filePath === snapshot.currentSongKey) if (matchedIndex >= 0) { return matchedIndex } } const requestedIndex = typeof snapshot.currentIndex === 'number' ? snapshot.currentIndex : 0 return Math.max(0, Math.min(requestedIndex, queue.length - 1)) } private isValidSnapshot(snapshot: Object | null): snapshot is PlaybackSnapshot { if (!snapshot || Array.isArray(snapshot)) { return false } const candidate = snapshot as PlaybackSnapshot return Array.isArray(candidate.queue) && typeof candidate.currentIndex === 'number' && typeof candidate.currentSongKey === 'string' && typeof candidate.currentSongName === 'string' && typeof candidate.isPlaying === 'boolean' && typeof candidate.positionMs === 'number' && typeof candidate.durationMs === 'number' && typeof candidate.playType === 'number' && typeof candidate.playlistContext === 'string' && typeof candidate.updatedAt === 'number' && typeof candidate.shouldResumeWhenActivated === 'boolean' } private isValidSnapshotSong(snapshotSong: Object | null): snapshotSong is PlaybackSnapshotSong { if (!snapshotSong || Array.isArray(snapshotSong)) { return false } const candidate = snapshotSong as PlaybackSnapshotSong return typeof candidate.filePath === 'string' && typeof candidate.name === 'string' && typeof candidate.type === 'number' } private buildVideoItem(song: PlaybackSnapshotSong): VideoItem { const item = new VideoItem(song.name, song.id ?? '', song.filePath, song.type, 0, '') item.artist = song.artist item.album = song.album item.duration = song.duration item.pixelMapPath = song.pixelMapPath item.lyricContent = song.lyricContent item.remote_rel_path = song.remote_rel_path item.webdav_account_id = song.webdav_account_id return item } }