Files
qbz/src/lib/stores/playbackContextStore.ts
T

182 lines
4.2 KiB
TypeScript

/**
* Playback Context Store
*
* Manages the semantic origin of playback (album, playlist, artist top, etc.)
* Determines what "next" means and displays the stack icon.
*/
import { invoke } from '@tauri-apps/api/core';
// ============ Types ============
export type ContextType = 'album' | 'playlist' | 'artist_top' | 'home_list' | 'favorites' | 'local_library' | 'radio';
export type ContentSource = 'qobuz' | 'local' | 'plex';
export interface PlaybackContext {
type: ContextType;
id: string;
label: string;
source: ContentSource;
track_ids: number[];
current_position: number;
}
// ============ State ============
let currentContext: PlaybackContext | null = null;
let pendingFocus: { contextType: ContextType; contextId: string; trackId: number } | null = null;
const listeners = new Set<() => void>();
function notifyListeners(): void {
for (const listener of listeners) {
listener();
}
}
// ============ Public API ============
/**
* Set a playback context (e.g., when playing from album/playlist)
*/
export async function setPlaybackContext(
contextType: ContextType,
id: string,
label: string,
source: ContentSource,
trackIds: number[],
startPosition: number
): Promise<void> {
console.log('[PlaybackContext] Setting context:', { contextType, id, label, trackIds: trackIds.length, startPosition });
await invoke('set_playback_context', {
contextType,
id,
label,
source,
trackIds,
startPosition
});
// Update local state
currentContext = {
type: contextType,
id,
label,
source,
track_ids: trackIds,
current_position: startPosition
};
console.log('[PlaybackContext] Context set, notifying listeners. Current context:', currentContext);
notifyListeners();
}
/**
* Clear the playback context (e.g., when playing single track)
*/
export async function clearPlaybackContext(): Promise<void> {
console.log('[PlaybackContext] Clearing context');
await invoke('clear_playback_context');
currentContext = null;
console.log('[PlaybackContext] Context cleared, notifying listeners');
notifyListeners();
}
/**
* Get the current playback context
*/
export async function getPlaybackContext(): Promise<PlaybackContext | null> {
const context = await invoke<PlaybackContext | null>('get_playback_context');
currentContext = context;
notifyListeners();
return context;
}
/**
* Check if a context is active
*/
export async function hasPlaybackContext(): Promise<boolean> {
return await invoke<boolean>('has_playback_context');
}
/**
* Get the current context (cached, no backend call)
*/
export function getCurrentContext(): PlaybackContext | null {
return currentContext;
}
export function requestContextTrackFocus(
contextType: ContextType,
contextId: string,
trackId: number
): void {
pendingFocus = { contextType, contextId, trackId };
}
export function consumeContextTrackFocus(
contextType: ContextType,
contextId: string
): number | null {
if (!pendingFocus) return null;
if (pendingFocus.contextType !== contextType || pendingFocus.contextId !== contextId) {
return null;
}
const { trackId } = pendingFocus;
pendingFocus = null;
return trackId;
}
/**
* Get display info for the current context
*/
export function getContextDisplayInfo(): string | null {
if (!currentContext) return null;
const typeStr = {
album: 'Album',
playlist: 'Playlist',
artist_top: 'Artist Top Songs',
home_list: 'Home List',
favorites: 'Favorites',
local_library: 'Local Library',
radio: 'Radio'
}[currentContext.type];
return `${typeStr} · ${currentContext.label}`;
}
/**
* Subscribe to context changes
*/
export function subscribe(listener: () => void): () => void {
listeners.add(listener);
return () => {
listeners.delete(listener);
};
}
/**
* Initialize context store (call on app startup)
*/
export async function initPlaybackContextStore(): Promise<void> {
// Sync initial state from backend
await getPlaybackContext();
}
// Export a reactive getter for Svelte components
export function usePlaybackContext() {
let context = currentContext;
const unsubscribe = subscribe(() => {
context = currentContext;
});
return {
get context() {
return context;
},
unsubscribe
};
}