// This file was generated by [tauri-specta](https://github.com/oscartbeaumont/tauri-specta). Do not edit this file manually. /** user-defined commands **/ export const commands = { /** * Play a single media item (audio or video) * * Accepts a PlayItemRequest with all optional fields properly defaulted. * This avoids Tauri's Android serialization issues with complex objects. * * @req: UR-003 - Play videos * @req: UR-004 - Play audio uninterrupted * @req: UR-005 - Control media playback (play operation) * @req: DR-009 - Audio player UI */ async playerPlayItem(item: PlayItemRequest) : Promise { return await TAURI_INVOKE("player_play_item", { item }); }, /** * Play a queue of media items * * @req: UR-004 - Play audio uninterrupted * @req: UR-005 - Control media playback (queue playback) * @req: UR-015 - View and manage current audio queue * @req: DR-005 - Queue manager with shuffle, repeat, history */ async playerPlayQueue(request: PlayQueueRequest) : Promise { return await TAURI_INVOKE("player_play_queue", { request }); }, /** * Play a track from an album - backend fetches all album tracks and builds queue */ async playerPlayAlbumTrack(repositoryHandle: string, request: PlayAlbumTrackRequest) : Promise { return await TAURI_INVOKE("player_play_album_track", { repositoryHandle, request }); }, /** * Play tracks by ID - backend fetches all metadata */ async playerPlayTracks(repositoryHandle: string, request: PlayTracksRequest) : Promise { return await TAURI_INVOKE("player_play_tracks", { repositoryHandle, request }); }, async playerPlay() : Promise { return await TAURI_INVOKE("player_play"); }, async playerPause() : Promise { return await TAURI_INVOKE("player_pause"); }, async playerToggle() : Promise { return await TAURI_INVOKE("player_toggle"); }, async playerStop() : Promise { return await TAURI_INVOKE("player_stop"); }, async playerNext() : Promise { return await TAURI_INVOKE("player_next"); }, async playerPrevious() : Promise { return await TAURI_INVOKE("player_previous"); }, async playerSeek(position: number) : Promise { return await TAURI_INVOKE("player_seek", { position }); }, /** * Smart video seeking that decides between native and server-side seeking * * This command analyzes the current video stream and automatically chooses * the best seeking strategy: * - HLS streams (.m3u8): Use native seeking * - Direct play streams: Use native seeking * - Transcoded non-HLS: Request new stream URL from server starting at seek position * * For native (non-HTML5) backends, this command handles the entire stream reload * internally. For HTML5 backends, it returns the new URL for the frontend to handle. */ async playerSeekVideo(repositoryHandle: string, position: number, mediaSourceId: string | null, audioStreamIndex: number | null, useHtml5: boolean) : Promise { return await TAURI_INVOKE("player_seek_video", { repositoryHandle, position, mediaSourceId, audioStreamIndex, useHtml5 }); }, async playerSetVolume(volume: number) : Promise { return await TAURI_INVOKE("player_set_volume", { volume }); }, async playerToggleMute() : Promise { return await TAURI_INVOKE("player_toggle_mute"); }, async playerSetAudioTrack(streamIndex: number) : Promise { return await TAURI_INVOKE("player_set_audio_track", { streamIndex }); }, /** * Switch audio track - handles both HTML5 (stream reload) and native (direct switch) * Note: Frontend should handle saving series preferences after this command succeeds */ async playerSwitchAudioTrack(repositoryHandle: string, streamIndex: number, arrayIndex: number, useHtml5: boolean, currentPosition: number | null, mediaSourceId: string | null) : Promise { return await TAURI_INVOKE("player_switch_audio_track", { repositoryHandle, streamIndex, arrayIndex, useHtml5, currentPosition, mediaSourceId }); }, async playerSetSubtitleTrack(streamIndex: number | null) : Promise { return await TAURI_INVOKE("player_set_subtitle_track", { streamIndex }); }, async playerToggleShuffle() : Promise { return await TAURI_INVOKE("player_toggle_shuffle"); }, async playerCycleRepeat() : Promise { return await TAURI_INVOKE("player_cycle_repeat"); }, async playerGetStatus() : Promise { return await TAURI_INVOKE("player_get_status"); }, async playerGetQueue() : Promise { return await TAURI_INVOKE("player_get_queue"); }, async playerAddToQueue(request: AddToQueueRequest) : Promise { return await TAURI_INVOKE("player_add_to_queue", { request }); }, /** * Add a track to queue by ID - backend fetches metadata and constructs URLs */ async playerAddTrackById(repositoryHandle: string, request: AddTrackByIdRequest) : Promise { return await TAURI_INVOKE("player_add_track_by_id", { repositoryHandle, request }); }, /** * Add multiple tracks to queue by IDs - backend fetches metadata and constructs URLs */ async playerAddTracksByIds(repositoryHandle: string, request: AddTracksByIdsRequest) : Promise { return await TAURI_INVOKE("player_add_tracks_by_ids", { repositoryHandle, request }); }, async playerRemoveFromQueue(index: number) : Promise { return await TAURI_INVOKE("player_remove_from_queue", { index }); }, async playerMoveInQueue(fromIndex: number, toIndex: number) : Promise { return await TAURI_INVOKE("player_move_in_queue", { fromIndex, toIndex }); }, async playerSkipTo(index: number) : Promise { return await TAURI_INVOKE("player_skip_to", { index }); }, async playerSetAudioSettings(settings: AudioSettings) : Promise { return await TAURI_INVOKE("player_set_audio_settings", { settings }); }, async playerGetAudioSettings() : Promise { return await TAURI_INVOKE("player_get_audio_settings"); }, async playerSetVideoSettings(settings: VideoSettings) : Promise { return await TAURI_INVOKE("player_set_video_settings", { settings }); }, async playerGetVideoSettings() : Promise { return await TAURI_INVOKE("player_get_video_settings"); }, /** * Set sleep timer mode */ async playerSetSleepTimer(mode: SleepTimerMode) : Promise { return await TAURI_INVOKE("player_set_sleep_timer", { mode }); }, /** * Cancel sleep timer */ async playerCancelSleepTimer() : Promise { return await TAURI_INVOKE("player_cancel_sleep_timer"); }, /** * Get current sleep timer state */ async playerGetSleepTimer() : Promise { return await TAURI_INVOKE("player_get_sleep_timer"); }, /** * Get autoplay settings */ async playerGetAutoplaySettings() : Promise { return await TAURI_INVOKE("player_get_autoplay_settings"); }, /** * Set autoplay settings and persist to database */ async playerSetAutoplaySettings(userId: string, settings: AutoplaySettings) : Promise { return await TAURI_INVOKE("player_set_autoplay_settings", { userId, settings }); }, /** * Cancel active autoplay countdown */ async playerCancelAutoplayCountdown() : Promise { return await TAURI_INVOKE("player_cancel_autoplay_countdown"); }, /** * Play next episode (user confirmed from popup) */ async playerPlayNextEpisode(item: PlayItemRequest) : Promise { return await TAURI_INVOKE("player_play_next_episode", { item }); }, /** * Handle playback ended event - triggers autoplay decision logic * This is called from: * - Frontend when HTML5 video ends (Linux/desktop) - passes itemId + repositoryHandle for the video * - Frontend when audio track ends via backend event - no itemId/repositoryHandle needed * - Android JNI callback also triggers this logic directly */ async playerOnPlaybackEnded(itemId: string | null, repositoryHandle: string | null) : Promise { return await TAURI_INVOKE("player_on_playback_ended", { itemId, repositoryHandle }); }, /** * Preload upcoming tracks from the queue * This queues background downloads for the next N tracks that aren't already downloaded */ async playerPreloadUpcoming(userId: string, downloadBasePath: string) : Promise { return await TAURI_INVOKE("player_preload_upcoming", { userId, downloadBasePath }); }, /** * Update SmartCache configuration */ async playerSetCacheConfig(config: CacheConfig) : Promise { return await TAURI_INVOKE("player_set_cache_config", { config }); }, /** * Get current SmartCache configuration */ async playerGetCacheConfig() : Promise { return await TAURI_INVOKE("player_get_cache_config"); }, /** * Configure Jellyfin API client for automatic playback reporting */ async playerConfigureJellyfin(serverUrl: string, accessToken: string, userId: string, deviceId: string) : Promise { return await TAURI_INVOKE("player_configure_jellyfin", { serverUrl, accessToken, userId, deviceId }); }, /** * Disable Jellyfin automatic playback reporting */ async playerDisableJellyfin() : Promise { return await TAURI_INVOKE("player_disable_jellyfin"); }, /** * Get the current media session state */ async playerGetSession() : Promise { return await TAURI_INVOKE("player_get_session"); }, /** * Dismiss the current media session (returns to Idle) */ async playerDismissSession() : Promise { return await TAURI_INVOKE("player_dismiss_session"); }, /** * Play items on a remote Jellyfin session (casting) */ async remotePlayOnSession(sessionId: string, itemIds: string[], startIndex: number) : Promise { return await TAURI_INVOKE("remote_play_on_session", { sessionId, itemIds, startIndex }); }, /** * Send a playback command to a remote session */ async remoteSendCommand(sessionId: string, command: string) : Promise { return await TAURI_INVOKE("remote_send_command", { sessionId, command }); }, /** * Seek on a remote session */ async remoteSessionSeek(sessionId: string, positionTicks: number) : Promise { return await TAURI_INVOKE("remote_session_seek", { sessionId, positionTicks }); }, /** * Set volume on a remote session */ async remoteSessionSetVolume(sessionId: string, volume: number) : Promise { return await TAURI_INVOKE("remote_session_set_volume", { sessionId, volume }); }, /** * Toggle mute on a remote session */ async remoteSessionToggleMute(sessionId: string) : Promise { return await TAURI_INVOKE("remote_session_toggle_mute", { sessionId }); }, /** * List current LMS sync groups. */ async lmsGetSyncGroups() : Promise { return await TAURI_INVOKE("lms_get_sync_groups"); }, /** * Fuse LMS zones into a sync group. `master_mac` keeps playing and the * `slave_macs` zones join it in sync. */ async lmsCreateSyncGroup(masterMac: string, slaveMacs: string[]) : Promise { return await TAURI_INVOKE("lms_create_sync_group", { masterMac, slaveMacs }); }, /** * Remove a single LMS zone from its sync group (decouple one player). */ async lmsUnsyncPlayer(mac: string) : Promise { return await TAURI_INVOKE("lms_unsync_player", { mac }); }, /** * Dissolve an entire LMS sync group, identified by its master's MAC. */ async lmsDissolveSyncGroup(masterMac: string) : Promise { return await TAURI_INVOKE("lms_dissolve_sync_group", { masterMac }); }, /** * Set polling frequency hint based on UI state */ async sessionsSetPollingHint(hint: string) : Promise { return await TAURI_INVOKE("sessions_set_polling_hint", { hint }); }, /** * Manually trigger a session poll (for refresh button) */ async sessionsPollNow() : Promise { return await TAURI_INVOKE("sessions_poll_now"); }, /** * Get the current playback mode */ async playbackModeGetCurrent() : Promise { return await TAURI_INVOKE("playback_mode_get_current"); }, /** * Set the playback mode (internal/testing use) */ async playbackModeSet(mode: PlaybackMode) : Promise { return await TAURI_INVOKE("playback_mode_set", { mode }); }, /** * Check if currently transferring between playback modes */ async playbackModeIsTransferring() : Promise { return await TAURI_INVOKE("playback_mode_is_transferring"); }, /** * Transfer playback from local device to a remote Jellyfin session */ async playbackModeTransferToRemote(sessionId: string, position: number | null) : Promise { return await TAURI_INVOKE("playback_mode_transfer_to_remote", { sessionId, position }); }, /** * Get remote session status (for polling position/duration) */ async playbackModeGetRemoteStatus() : Promise { return await TAURI_INVOKE("playback_mode_get_remote_status"); }, /** * Transfer playback from remote session back to local device * * Parameters: * - current_item_id: The Jellyfin item ID currently playing on remote * - position_ticks: Current playback position in ticks (10,000 ticks = 1ms) */ async playbackModeTransferToLocal(currentItemId: string, positionTicks: number) : Promise { return await TAURI_INVOKE("playback_mode_transfer_to_local", { currentItemId, positionTicks }); }, /** * Set the transferring flag on the playback mode manager. * * Used by the frontend remote->local flow to mark the whole two-step sequence * as a transfer, so `player_play_tracks` starts LOCAL playback instead of * casting back to the remote session it's leaving. Always pair `true` with a * later `false` (including on error) so the flag can't stick. */ async playbackModeSetTransferring(transferring: boolean) : Promise { return await TAURI_INVOKE("playback_mode_set_transferring", { transferring }); }, /** * Initialize playback reporter (called after login) */ async playbackReporterInit(serverUrl: string, userId: string, accessToken: string, deviceId: string) : Promise { return await TAURI_INVOKE("playback_reporter_init", { serverUrl, userId, accessToken, deviceId }); }, /** * Destroy playback reporter (called on logout) */ async playbackReporterDestroy() : Promise { return await TAURI_INVOKE("playback_reporter_destroy"); }, /** * Report playback start */ async playbackReportStart(itemId: string, positionSeconds: number, contextType: string | null, contextId: string | null) : Promise { return await TAURI_INVOKE("playback_report_start", { itemId, positionSeconds, contextType, contextId }); }, /** * Report playback progress */ async playbackReportProgress(itemId: string, positionSeconds: number, isPaused: boolean) : Promise { return await TAURI_INVOKE("playback_report_progress", { itemId, positionSeconds, isPaused }); }, /** * Report playback stopped */ async playbackReportStopped(itemId: string, positionSeconds: number) : Promise { return await TAURI_INVOKE("playback_report_stopped", { itemId, positionSeconds }); }, /** * Mark item as played */ async playbackMarkPlayed(itemId: string) : Promise { return await TAURI_INVOKE("playback_mark_played", { itemId }); }, /** * Initialize the auth manager (call on app startup) * Restores session from storage if available */ async authInitialize() : Promise { return await TAURI_INVOKE("auth_initialize"); }, /** * Connect to a Jellyfin server and get server info */ async authConnectToServer(serverUrl: string) : Promise { return await TAURI_INVOKE("auth_connect_to_server", { serverUrl }); }, /** * Login with username and password */ async authLogin(serverUrl: string, username: string, password: string, deviceId: string) : Promise { return await TAURI_INVOKE("auth_login", { serverUrl, username, password, deviceId }); }, /** * Verify current session */ async authVerifySession(serverUrl: string, userId: string, accessToken: string, deviceId: string) : Promise { return await TAURI_INVOKE("auth_verify_session", { serverUrl, userId, accessToken, deviceId }); }, /** * Logout (clear session and call Jellyfin logout endpoint) */ async authLogout(serverUrl: string, accessToken: string, deviceId: string) : Promise { return await TAURI_INVOKE("auth_logout", { serverUrl, accessToken, deviceId }); }, /** * Get current session */ async authGetSession() : Promise { return await TAURI_INVOKE("auth_get_session"); }, /** * Set current session (for restoration from storage) */ async authSetSession(session: Session | null) : Promise { return await TAURI_INVOKE("auth_set_session", { session }); }, /** * Start background session verification */ async authStartVerification(deviceId: string) : Promise { return await TAURI_INVOKE("auth_start_verification", { deviceId }); }, /** * Stop background session verification */ async authStopVerification() : Promise { return await TAURI_INVOKE("auth_stop_verification"); }, /** * Re-authenticate with password (when session expired) */ async authReauthenticate(password: string, deviceId: string) : Promise { return await TAURI_INVOKE("auth_reauthenticate", { password, deviceId }); }, /** * Get or create the device ID. * Device ID is a UUID v4 that persists across app restarts. * On first call, generates and stores a new UUID. * On subsequent calls, retrieves the stored UUID. * * # Returns * - `Ok(String)` - The device ID (UUID v4) * - `Err(String)` - If database operation fails * * TRACES: UR-009 | DR-011 */ async deviceGetId() : Promise { return await TAURI_INVOKE("device_get_id"); }, /** * Set the device ID (primarily for testing or recovery). * Overwrites any existing device ID. * * # Arguments * * `device_id` - The device ID to store (should be UUID v4 format) * * # Returns * - `Ok(())` - If device ID was stored successfully * - `Err(String)` - If database operation fails * * TRACES: UR-009 | DR-011 */ async deviceSetId(deviceId: string) : Promise { return await TAURI_INVOKE("device_set_id", { deviceId }); }, /** * Check if the server is currently reachable */ async connectivityCheckServer() : Promise { return await TAURI_INVOKE("connectivity_check_server"); }, /** * Set the server URL and trigger an immediate check */ async connectivitySetServerUrl(url: string) : Promise { return await TAURI_INVOKE("connectivity_set_server_url", { url }); }, /** * Get the current connectivity status */ async connectivityGetStatus() : Promise { return await TAURI_INVOKE("connectivity_get_status"); }, /** * Start monitoring connectivity with adaptive polling */ async connectivityStartMonitoring() : Promise { return await TAURI_INVOKE("connectivity_start_monitoring"); }, /** * Stop monitoring connectivity */ async connectivityStopMonitoring() : Promise { return await TAURI_INVOKE("connectivity_stop_monitoring"); }, /** * Mark the server as reachable (called after successful API calls) */ async connectivityMarkReachable() : Promise { return await TAURI_INVOKE("connectivity_mark_reachable"); }, /** * Mark the server as unreachable (called after failed API calls) */ async connectivityMarkUnreachable(error: string | null) : Promise { return await TAURI_INVOKE("connectivity_mark_unreachable", { error }); }, /** * Initialize the database and run migrations */ async storageInit() : Promise { return await TAURI_INVOKE("storage_init"); }, /** * Get storage directory path (parent directory of the database file) */ async storageGetPath() : Promise { return await TAURI_INVOKE("storage_get_path"); }, /** * Get database file size in bytes */ async storageGetSize() : Promise { return await TAURI_INVOKE("storage_get_size"); }, /** * Get security status (keyring vs encrypted file fallback) */ async storageGetSecurityStatus() : Promise { return await TAURI_INVOKE("storage_get_security_status"); }, /** * Save a server connection * Uses INSERT ... ON CONFLICT to avoid triggering CASCADE DELETE on users */ async storageSaveServer(id: string, name: string, url: string, version: string | null) : Promise { return await TAURI_INVOKE("storage_save_server", { id, name, url, version }); }, /** * Get all saved servers */ async storageGetServers() : Promise { return await TAURI_INVOKE("storage_get_servers"); }, /** * Delete a server and all associated data */ async storageDeleteServer(serverId: string) : Promise { return await TAURI_INVOKE("storage_delete_server", { serverId }); }, /** * Save a user account (token stored in secure storage, not database) */ async storageSaveUser(id: string, serverId: string, username: string, accessToken: string | null) : Promise { return await TAURI_INVOKE("storage_save_user", { id, serverId, username, accessToken }); }, /** * Get users for a server */ async storageGetUsers(serverId: string) : Promise { return await TAURI_INVOKE("storage_get_users", { serverId }); }, /** * Set a user as active (and deactivate all other users globally) */ async storageSetActiveUser(userId: string, serverId: string) : Promise { return await TAURI_INVOKE("storage_set_active_user", { userId, serverId }); }, /** * Get the active user for a server */ async storageGetActiveUser(serverId: string) : Promise { return await TAURI_INVOKE("storage_get_active_user", { serverId }); }, /** * Get the active session (user + server + token) for session restoration */ async storageGetActiveSession() : Promise { return await TAURI_INVOKE("storage_get_active_session"); }, /** * Get user's access token from secure storage */ async storageGetAccessToken(userId: string) : Promise { return await TAURI_INVOKE("storage_get_access_token", { userId }); }, /** * Delete a user account and their token from secure storage */ async storageDeleteUser(userId: string) : Promise { return await TAURI_INVOKE("storage_delete_user", { userId }); }, /** * Update playback progress in local database * This stores the progress locally for offline access and "continue watching" */ async storageUpdatePlaybackProgress(userId: string, itemId: string, positionTicks: number) : Promise { return await TAURI_INVOKE("storage_update_playback_progress", { userId, itemId, positionTicks }); }, /** * Update playback progress with context in local database * This stores the progress along with playback context (container vs single) */ async storageUpdatePlaybackContext(userId: string, itemId: string, positionTicks: number, contextType: string | null, contextId: string | null) : Promise { return await TAURI_INVOKE("storage_update_playback_context", { userId, itemId, positionTicks, contextType, contextId }); }, /** * Mark item as played in local database */ async storageMarkPlayed(userId: string, itemId: string) : Promise { return await TAURI_INVOKE("storage_mark_played", { userId, itemId }); }, /** * Get playback progress for an item */ async storageGetPlaybackProgress(userId: string, itemId: string) : Promise { return await TAURI_INVOKE("storage_get_playback_progress", { userId, itemId }); }, /** * Mark pending sync as completed for an item */ async storageMarkSynced(userId: string, itemId: string) : Promise { return await TAURI_INVOKE("storage_mark_synced", { userId, itemId }); }, /** * Toggle favorite status for an item in local database * This updates the is_favorite field and marks it for sync to Jellyfin */ async storageToggleFavorite(userId: string, itemId: string, isFavorite: boolean) : Promise { return await TAURI_INVOKE("storage_toggle_favorite", { userId, itemId, isFavorite }); }, /** * Queue a media item for download */ async downloadItem(request: DownloadItemRequest) : Promise { return await TAURI_INVOKE("download_item", { request }); }, /** * Queue and start a download in a single atomic operation * This simplifies the frontend flow by combining multiple steps */ async downloadItemAndStart(request: DownloadItemAndStartRequest) : Promise { return await TAURI_INVOKE("download_item_and_start", { request }); }, /** * Queue an entire album for download */ async downloadAlbum(albumId: string, userId: string, basePath: string) : Promise { return await TAURI_INVOKE("download_album", { albumId, userId, basePath }); }, /** * Queue a video item (movie or episode) for download with quality preset */ async downloadVideo(request: DownloadVideoRequest) : Promise { return await TAURI_INVOKE("download_video", { request }); }, /** * Queue all episodes of a series for download */ async downloadSeries(seriesId: string, seriesName: string, userId: string, basePath: string, qualityPreset: string | null) : Promise { return await TAURI_INVOKE("download_series", { seriesId, seriesName, userId, basePath, qualityPreset }); }, /** * Queue all episodes of a specific season for download */ async downloadSeason(seasonId: string, seriesName: string, seasonName: string, seasonNumber: number, userId: string, basePath: string, qualityPreset: string | null) : Promise { return await TAURI_INVOKE("download_season", { seasonId, seriesName, seasonName, seasonNumber, userId, basePath, qualityPreset }); }, /** * Get all downloads for a user, optionally filtered by status */ async getDownloads(userId: string, statusFilter: string[] | null) : Promise { return await TAURI_INVOKE("get_downloads", { userId, statusFilter }); }, /** * Pause a download */ async pauseDownload(downloadId: number) : Promise { return await TAURI_INVOKE("pause_download", { downloadId }); }, /** * Resume a paused download */ async resumeDownload(downloadId: number) : Promise { return await TAURI_INVOKE("resume_download", { downloadId }); }, /** * Cancel a download */ async cancelDownload(downloadId: number) : Promise { return await TAURI_INVOKE("cancel_download", { downloadId }); }, /** * Delete a completed download */ async deleteDownload(downloadId: number) : Promise { return await TAURI_INVOKE("delete_download", { downloadId }); }, /** * Delete all downloads for a user */ async deleteAllDownloads(userId: string) : Promise { return await TAURI_INVOKE("delete_all_downloads", { userId }); }, /** * Delete all downloads for a specific album */ async deleteAlbumDownloads(albumId: string, userId: string) : Promise { return await TAURI_INVOKE("delete_album_downloads", { albumId, userId }); }, /** * Clear all stale pending/failed/paused downloads */ async clearStaleDownloads(userId: string) : Promise { return await TAURI_INVOKE("clear_stale_downloads", { userId }); }, /** * Get storage statistics for downloads */ async getDownloadStorageStats(userId: string) : Promise { return await TAURI_INVOKE("get_download_storage_stats", { userId }); }, /** * Mark a download as completed */ async markDownloadCompleted(downloadId: number, bytesDownloaded: number, filePath: string) : Promise { return await TAURI_INVOKE("mark_download_completed", { downloadId, bytesDownloaded, filePath }); }, /** * Mark a download as failed */ async markDownloadFailed(downloadId: number, errorMessage: string) : Promise { return await TAURI_INVOKE("mark_download_failed", { downloadId, errorMessage }); }, /** * Start downloading a file immediately * This command actually downloads the file using the worker */ async startDownload(downloadId: number, streamUrl: string, targetDir: string) : Promise { return await TAURI_INVOKE("start_download", { downloadId, streamUrl, targetDir }); }, /** * Enqueue a download with its resolved stream URL, then let the queue pump * start it (or a higher-priority pending item) when a slot is free. * * Unlike [`start_download`], this never errors when the concurrency limit is * reached: the URL is persisted on the row and the pump will pick it up once a * slot frees. This is the path bulk operations (album/series/season) use so * every queued item eventually downloads without the frontend re-issuing it. */ async enqueueDownload(downloadId: number, streamUrl: string, targetDir: string) : Promise { return await TAURI_INVOKE("enqueue_download", { downloadId, streamUrl, targetDir }); }, /** * Enqueue a batch of already-queued video downloads, resolving each one's * transcode URL from the repository using the `quality_preset` stored on the * row. Then let the pump start them subject to the concurrency limit. * * This is the bulk video path (series/season): `download_series`/ * `download_season` insert the rows, then this resolves URLs and enqueues them * so they actually start. Resolving server-side avoids round-tripping every * episode URL through the frontend. */ async enqueueVideoDownloads(handle: string, downloadIds: number[], targetDir: string) : Promise { return await TAURI_INVOKE("enqueue_video_downloads", { handle, downloadIds, targetDir }); }, /** * Get download manager statistics */ async getDownloadManagerStats() : Promise { return await TAURI_INVOKE("get_download_manager_stats"); }, /** * Set the maximum concurrent downloads */ async setMaxConcurrentDownloads(max: number) : Promise { return await TAURI_INVOKE("set_max_concurrent_downloads", { max }); }, /** * Get SmartCache statistics */ async getSmartCacheStats(userId: string) : Promise { return await TAURI_INVOKE("get_smart_cache_stats", { userId }); }, /** * Update SmartCache configuration */ async updateSmartCacheConfig(config: CacheConfig) : Promise { return await TAURI_INVOKE("update_smart_cache_config", { config }); }, /** * Get SmartCache configuration */ async getSmartCacheConfig() : Promise { return await TAURI_INVOKE("get_smart_cache_config"); }, /** * Get album recommendations based on play history */ async getAlbumRecommendations(userId: string) : Promise { return await TAURI_INVOKE("get_album_recommendations", { userId }); }, /** * Get album affinity status for all tracked albums * This shows the SmartCache's internal play history and threshold status */ async getAlbumAffinityStatus() : Promise { return await TAURI_INVOKE("get_album_affinity_status"); }, /** * Pin an item's metadata (protects from cache clear) */ async pinItem(itemId: string) : Promise { return await TAURI_INVOKE("pin_item", { itemId }); }, /** * Unpin an item's metadata */ async unpinItem(itemId: string) : Promise { return await TAURI_INVOKE("unpin_item", { itemId }); }, /** * Check if an item is pinned */ async isItemPinned(itemId: string) : Promise { return await TAURI_INVOKE("is_item_pinned", { itemId }); }, /** * Check if an item is available offline */ async offlineIsAvailable(itemId: string) : Promise { return await TAURI_INVOKE("offline_is_available", { itemId }); }, /** * Get all offline items for a user */ async offlineGetItems(userId: string) : Promise { return await TAURI_INVOKE("offline_get_items", { userId }); }, /** * Search offline items */ async offlineSearch(userId: string, query: string) : Promise { return await TAURI_INVOKE("offline_search", { userId, query }); }, /** * Get cached libraries for a server */ async storageGetLibraries(serverId: string) : Promise { return await TAURI_INVOKE("storage_get_libraries", { serverId }); }, /** * Get cached items with optional filtering */ async storageGetItems(serverId: string, parentId: string | null, libraryId: string | null, itemType: string | null, limit: number | null, offset: number | null) : Promise { return await TAURI_INVOKE("storage_get_items", { serverId, parentId, libraryId, itemType, limit, offset }); }, /** * Get a single cached item by ID */ async storageGetItem(itemId: string) : Promise { return await TAURI_INVOKE("storage_get_item", { itemId }); }, /** * Search cached items using FTS */ async storageSearchItems(serverId: string, query: string, limit: number | null) : Promise { return await TAURI_INVOKE("storage_search_items", { serverId, query, limit }); }, /** * Save a library to the cache */ async storageSaveLibrary(id: string, serverId: string, name: string, collectionType: string | null, imageTag: string | null, sortOrder: number | null) : Promise { return await TAURI_INVOKE("storage_save_library", { id, serverId, name, collectionType, imageTag, sortOrder }); }, /** * Save an item to the cache */ async storageSaveItem(item: CachedItem, serverId: string) : Promise { return await TAURI_INVOKE("storage_save_item", { item, serverId }); }, /** * Get count of pending sync operations for a user */ async storageGetPendingSyncCount(userId: string) : Promise { return await TAURI_INVOKE("storage_get_pending_sync_count", { userId }); }, /** * Queue a mutation for sync to server */ async syncQueueMutation(userId: string, operation: string, itemId: string | null, payload: string | null) : Promise { return await TAURI_INVOKE("sync_queue_mutation", { userId, operation, itemId, payload }); }, /** * Get all pending sync operations for a user */ async syncGetPending(userId: string, limit: number | null) : Promise { return await TAURI_INVOKE("sync_get_pending", { userId, limit }); }, /** * Mark a sync operation as in progress */ async syncMarkProcessing(id: number) : Promise { return await TAURI_INVOKE("sync_mark_processing", { id }); }, /** * Mark a sync operation as completed */ async syncMarkCompleted(id: number) : Promise { return await TAURI_INVOKE("sync_mark_completed", { id }); }, /** * Mark a sync operation as failed with error message */ async syncMarkFailed(id: number, error: string) : Promise { return await TAURI_INVOKE("sync_mark_failed", { id, error }); }, /** * Get count of pending sync operations for a user */ async syncGetPendingCount(userId: string) : Promise { return await TAURI_INVOKE("sync_get_pending_count", { userId }); }, /** * Delete completed sync operations older than specified days */ async syncCleanupCompleted(daysOld: number) : Promise { return await TAURI_INVOKE("sync_cleanup_completed", { daysOld }); }, /** * Delete all sync operations for a user (used during logout) */ async syncClearUser(userId: string) : Promise { return await TAURI_INVOKE("sync_clear_user", { userId }); }, /** * Get cached thumbnail path, returns None if not cached * Also updates last_accessed timestamp for LRU tracking */ async thumbnailGetCached(itemId: string, imageType: string, tag: string) : Promise { return await TAURI_INVOKE("thumbnail_get_cached", { itemId, imageType, tag }); }, /** * Download and save a thumbnail to cache * Returns the local file path on success */ async thumbnailSave(itemId: string, imageType: string, tag: string, url: string) : Promise { return await TAURI_INVOKE("thumbnail_save", { itemId, imageType, tag, url }); }, /** * Get thumbnail cache statistics */ async thumbnailGetStats() : Promise { return await TAURI_INVOKE("thumbnail_get_stats"); }, /** * Set thumbnail cache storage limit in bytes */ async thumbnailSetLimit(limitBytes: number) : Promise { return await TAURI_INVOKE("thumbnail_set_limit", { limitBytes }); }, /** * Clear all cached thumbnails */ async thumbnailClearCache() : Promise { return await TAURI_INVOKE("thumbnail_clear_cache"); }, /** * Delete cached thumbnails for a specific item */ async thumbnailDeleteItem(itemId: string) : Promise { return await TAURI_INVOKE("thumbnail_delete_item", { itemId }); }, /** * Get image as base64 data URL, caching if not already cached * This extends the thumbnail system to serve all images through Rust with automatic caching */ async imageGetUrl(repositoryHandle: string, request: GetImageRequest) : Promise { return await TAURI_INVOKE("image_get_url", { repositoryHandle, request }); }, /** * Save a person to the cache */ async storageSavePerson(person: CachedPerson) : Promise { return await TAURI_INVOKE("storage_save_person", { person }); }, /** * Get a cached person by ID */ async storageGetPerson(personId: string) : Promise { return await TAURI_INVOKE("storage_get_person", { personId }); }, /** * Save item-person associations (batch) */ async storageSaveItemPeople(associations: CachedItemPerson[]) : Promise { return await TAURI_INVOKE("storage_save_item_people", { associations }); }, /** * Get people for an item (with person details joined) */ async storageGetItemPeople(itemId: string) : Promise { return await TAURI_INVOKE("storage_get_item_people", { itemId }); }, /** * Save user's preferred audio track for a series */ async storageSaveSeriesAudioPreference(userId: string, seriesId: string, serverId: string, audioTrackDisplayTitle: string | null, audioTrackLanguage: string | null, audioTrackIndex: number | null) : Promise { return await TAURI_INVOKE("storage_save_series_audio_preference", { userId, seriesId, serverId, audioTrackDisplayTitle, audioTrackLanguage, audioTrackIndex }); }, /** * Get user's preferred audio track for a series */ async storageGetSeriesAudioPreference(userId: string, seriesId: string) : Promise { return await TAURI_INVOKE("storage_get_series_audio_preference", { userId, seriesId }); }, /** * Create a new repository instance * Returns a handle (UUID) for accessing the repository */ async repositoryCreate(serverUrl: string, userId: string, accessToken: string, serverId: string) : Promise { return await TAURI_INVOKE("repository_create", { serverUrl, userId, accessToken, serverId }); }, /** * Destroy a repository instance */ async repositoryDestroy(handle: string) : Promise { return await TAURI_INVOKE("repository_destroy", { handle }); }, /** * Get libraries */ async repositoryGetLibraries(handle: string) : Promise { return await TAURI_INVOKE("repository_get_libraries", { handle }); }, /** * Get items in a container (library, folder, album, etc.) */ async repositoryGetItems(handle: string, parentId: string, options: GetItemsOptions | null) : Promise { return await TAURI_INVOKE("repository_get_items", { handle, parentId, options }); }, /** * Get a single item by ID */ async repositoryGetItem(handle: string, itemId: string) : Promise { return await TAURI_INVOKE("repository_get_item", { handle, itemId }); }, /** * Get latest items in a library */ async repositoryGetLatestItems(handle: string, parentId: string, limit: number | null) : Promise { return await TAURI_INVOKE("repository_get_latest_items", { handle, parentId, limit }); }, /** * Get resume items (continue watching/listening) */ async repositoryGetResumeItems(handle: string, parentId: string | null, limit: number | null) : Promise { return await TAURI_INVOKE("repository_get_resume_items", { handle, parentId, limit }); }, /** * Get next up episodes */ async repositoryGetNextUpEpisodes(handle: string, seriesId: string | null, limit: number | null) : Promise { return await TAURI_INVOKE("repository_get_next_up_episodes", { handle, seriesId, limit }); }, /** * Get recently played audio */ async repositoryGetRecentlyPlayedAudio(handle: string, limit: number | null) : Promise { return await TAURI_INVOKE("repository_get_recently_played_audio", { handle, limit }); }, /** * Get resume movies */ async repositoryGetResumeMovies(handle: string, limit: number | null) : Promise { return await TAURI_INVOKE("repository_get_resume_movies", { handle, limit }); }, /** * Get albums the user hasn't listened to recently ("rediscover") */ async repositoryGetRediscoverAlbums(handle: string, parentId: string | null, limit: number | null) : Promise { return await TAURI_INVOKE("repository_get_rediscover_albums", { handle, parentId, limit }); }, /** * Get genres for a library */ async repositoryGetGenres(handle: string, parentId: string | null) : Promise { return await TAURI_INVOKE("repository_get_genres", { handle, parentId }); }, /** * Search for items */ async repositorySearch(handle: string, query: string, options: SearchOptions | null, requestId: number) : Promise { return await TAURI_INVOKE("repository_search", { handle, query, options, requestId }); }, /** * Get playback info for an item */ async repositoryGetPlaybackInfo(handle: string, itemId: string) : Promise { return await TAURI_INVOKE("repository_get_playback_info", { handle, itemId }); }, /** * Get video stream URL with optional seeking support */ async repositoryGetVideoStreamUrl(handle: string, itemId: string, mediaSourceId: string | null, startTimeSeconds: number | null, audioStreamIndex: number | null) : Promise { return await TAURI_INVOKE("repository_get_video_stream_url", { handle, itemId, mediaSourceId, startTimeSeconds, audioStreamIndex }); }, /** * Get audio stream URL for a track */ async repositoryGetAudioStreamUrl(handle: string, itemId: string) : Promise { return await TAURI_INVOKE("repository_get_audio_stream_url", { handle, itemId }); }, /** * Get Live TV channels (broadcast / IPTV) for browsing */ async repositoryGetLiveTvChannels(handle: string) : Promise { return await TAURI_INVOKE("repository_get_live_tv_channels", { handle }); }, /** * Get the root list of plugin "Channels" */ async repositoryGetChannels(handle: string) : Promise { return await TAURI_INVOKE("repository_get_channels", { handle }); }, /** * Open a live stream for a Live TV channel / live item */ async repositoryOpenLiveStream(handle: string, itemId: string) : Promise { return await TAURI_INVOKE("repository_open_live_stream", { handle, itemId }); }, /** * Report playback start */ async repositoryReportPlaybackStart(handle: string, itemId: string, positionTicks: number) : Promise { return await TAURI_INVOKE("repository_report_playback_start", { handle, itemId, positionTicks }); }, /** * Report playback progress */ async repositoryReportPlaybackProgress(handle: string, itemId: string, positionTicks: number) : Promise { return await TAURI_INVOKE("repository_report_playback_progress", { handle, itemId, positionTicks }); }, /** * Report playback stopped */ async repositoryReportPlaybackStopped(handle: string, itemId: string, positionTicks: number) : Promise { return await TAURI_INVOKE("repository_report_playback_stopped", { handle, itemId, positionTicks }); }, /** * Get image URL for an item */ async repositoryGetImageUrl(handle: string, itemId: string, imageType: ImageType, options: ImageOptions | null) : Promise { return await TAURI_INVOKE("repository_get_image_url", { handle, itemId, imageType, options }); }, /** * Mark an item as favorite */ async repositoryMarkFavorite(handle: string, itemId: string) : Promise { return await TAURI_INVOKE("repository_mark_favorite", { handle, itemId }); }, /** * Unmark an item as favorite */ async repositoryUnmarkFavorite(handle: string, itemId: string) : Promise { return await TAURI_INVOKE("repository_unmark_favorite", { handle, itemId }); }, /** * Get person details */ async repositoryGetPerson(handle: string, personId: string) : Promise { return await TAURI_INVOKE("repository_get_person", { handle, personId }); }, /** * Get items by person (actor, director, etc.) */ async repositoryGetItemsByPerson(handle: string, personId: string, options: GetItemsOptions | null) : Promise { return await TAURI_INVOKE("repository_get_items_by_person", { handle, personId, options }); }, /** * Get similar/related items for a media item */ async repositoryGetSimilarItems(handle: string, itemId: string, limit: number | null) : Promise { return await TAURI_INVOKE("repository_get_similar_items", { handle, itemId, limit }); }, /** * Get subtitle URL for a media item */ async repositoryGetSubtitleUrl(handle: string, itemId: string, mediaSourceId: string, streamIndex: number, format: string) : Promise { return await TAURI_INVOKE("repository_get_subtitle_url", { handle, itemId, mediaSourceId, streamIndex, format }); }, /** * Get video download URL with quality preset */ async repositoryGetVideoDownloadUrl(handle: string, itemId: string, quality: string, mediaSourceId: string | null) : Promise { return await TAURI_INVOKE("repository_get_video_download_url", { handle, itemId, quality, mediaSourceId }); }, /** * Create a new playlist */ async playlistCreate(handle: string, name: string, itemIds: string[] | null) : Promise { return await TAURI_INVOKE("playlist_create", { handle, name, itemIds }); }, /** * Delete a playlist */ async playlistDelete(handle: string, playlistId: string) : Promise { return await TAURI_INVOKE("playlist_delete", { handle, playlistId }); }, /** * Rename a playlist */ async playlistRename(handle: string, playlistId: string, name: string) : Promise { return await TAURI_INVOKE("playlist_rename", { handle, playlistId, name }); }, /** * Get playlist items with PlaylistItemId */ async playlistGetItems(handle: string, playlistId: string) : Promise { return await TAURI_INVOKE("playlist_get_items", { handle, playlistId }); }, /** * Add items to a playlist */ async playlistAddItems(handle: string, playlistId: string, itemIds: string[]) : Promise { return await TAURI_INVOKE("playlist_add_items", { handle, playlistId, itemIds }); }, /** * Remove items from a playlist (uses PlaylistItemId entry IDs, NOT media item IDs) */ async playlistRemoveItems(handle: string, playlistId: string, entryIds: string[]) : Promise { return await TAURI_INVOKE("playlist_remove_items", { handle, playlistId, entryIds }); }, /** * Move a playlist item to a new position */ async playlistMoveItem(handle: string, playlistId: string, itemId: string, newIndex: number) : Promise { return await TAURI_INVOKE("playlist_move_item", { handle, playlistId, itemId, newIndex }); }, /** * Format time in seconds to MM:SS display string * * # Arguments * * `seconds` - Time in seconds * * # Returns * Formatted string like "3:45" or "12:09" */ async formatTimeSeconds(seconds: number) : Promise { return await TAURI_INVOKE("format_time_seconds", { seconds }); }, /** * Format time in seconds to HH:MM:SS or MM:SS display string * * Automatically chooses format based on duration: * - Less than 1 hour: Returns MM:SS format * - 1 hour or more: Returns HH:MM:SS format * * # Arguments * * `seconds` - Time in seconds * * # Returns * Formatted string like "1:23:45" or "3:45" */ async formatTimeSecondsLong(seconds: number) : Promise { return await TAURI_INVOKE("format_time_seconds_long", { seconds }); }, /** * Convert Jellyfin ticks to seconds * * # Arguments * * `ticks` - Time in Jellyfin ticks (10,000,000 ticks = 1 second) * * # Returns * Time in seconds */ async convertTicksToSeconds(ticks: number) : Promise { return await TAURI_INVOKE("convert_ticks_to_seconds", { ticks }); }, /** * Calculate progress percentage from position and duration * * # Arguments * * `position` - Current position in seconds * * `duration` - Total duration in seconds * * # Returns * Progress as percentage (0.0 to 100.0) */ async calcProgress(position: number, duration: number) : Promise { return await TAURI_INVOKE("calc_progress", { position, duration }); }, /** * Convert percentage volume (0-100) to normalized (0.0-1.0) * * # Arguments * * `percent` - Volume as percentage (0 to 100) * * # Returns * Normalized volume (0.0 to 1.0) */ async convertPercentToVolume(percent: number) : Promise { return await TAURI_INVOKE("convert_percent_to_volume", { percent }); } } /** user-defined events **/ export const events = __makeEvents__<{ playerStatusEvent: PlayerStatusEvent }>({ playerStatusEvent: "player-status-event" }) /** user-defined constants **/ /** user-defined types **/ /** * Active session info (for session restoration) */ export type ActiveSession = { userId: string; username: string; serverId: string; serverUrl: string; serverName: string; accessToken: string } /** * Request to add items to queue */ export type AddToQueueRequest = { items: PlayItemRequest[]; position: string } /** * Request to add a track by ID - backend fetches metadata */ export type AddTrackByIdRequest = { trackId: string; position: string } /** * Request to add multiple tracks by IDs - backend fetches metadata */ export type AddTracksByIdsRequest = { trackIds: string[]; position: string } /** * Album affinity status info */ export type AlbumAffinityStatus = { albumId: string; uniqueTracksPlayed: number; threshold: number; thresholdReached: boolean } /** * Album recommendation info */ export type AlbumRecommendation = { album_id: string; album_name: string; tracks_played: number; total_tracks: number; should_download: boolean } /** * Storage info for a single album */ export type AlbumStorageInfo = { album_id: string; album_name: string; artist_name: string | null; bytes_used: number; track_count: number } /** * Artist item with ID and name (for clickable artist links) */ export type ArtistItem = { id: string; name: string } /** * Audio playback settings */ export type AudioSettings = { /** * Crossfade duration in seconds (0 = disabled, max 12) */ crossfadeDuration: number; /** * Enable gapless playback between tracks */ gaplessPlayback: boolean; /** * Enable volume normalization */ normalizeVolume: boolean; /** * Target volume level for normalization */ volumeLevel: VolumeLevel } /** * Response for audio track switching operations */ export type AudioTrackSwitchResponse = /** * Native backend handled it (Android ExoPlayer) */ { strategy: "native"; success: boolean } | /** * HTML5 needs to reload stream with new audio track */ { strategy: "reloadStream"; new_url: string; position: number } /** * Authentication result */ export type AuthResult = { user: User; accessToken: string; serverId: string } /** * Server information returned from Jellyfin */ export type AuthServerInfo = { name: string; version: string; id: string; /** * Normalized server URL with protocol and no trailing slash */ normalizedUrl: string } /** * Autoplay settings (controls next episode behavior) */ export type AutoplaySettings = { /** * Whether autoplay is enabled for next episodes */ enabled: boolean; /** * Countdown duration in seconds before auto-playing next episode */ countdownSeconds: number; /** * Maximum number of episodes to auto-play consecutively (0 = unlimited) */ maxEpisodes?: number } /** * Smart caching configuration */ export type CacheConfig = { /** * Enable queue pre-caching */ queuePrecacheEnabled: boolean; /** * Number of tracks to pre-cache from queue */ queuePrecacheCount: number; /** * Enable album affinity detection */ albumAffinityEnabled: boolean; /** * Threshold for album affinity (tracks played before caching) */ albumAffinityThreshold: number; /** * Storage limit in bytes (0 = unlimited) */ storageLimit: number; /** * Only cache on WiFi */ wifiOnly: boolean } /** * Cached media item returned to frontend */ export type CachedItem = { id: string; name: string; itemType: string; parentId: string | null; libraryId: string | null; overview: string | null; genres: string | null; runtimeTicks: number | null; productionYear: number | null; communityRating: number | null; officialRating: string | null; primaryImageTag: string | null; albumId: string | null; albumName: string | null; albumArtist: string | null; artists: string | null; indexNumber: number | null; seriesId: string | null; seriesName: string | null; seasonId: string | null; seasonName: string | null; parentIndexNumber: number | null } /** * Item-person association for caching */ export type CachedItemPerson = { itemId: string; personId: string; serverId: string; personType: string; role: string | null; sortOrder: number } /** * Cached library info returned to frontend */ export type CachedLibrary = { id: string; serverId: string; name: string; collectionType: string | null; imageTag: string | null } /** * Cached person info returned to frontend */ export type CachedPerson = { id: string; serverId: string; name: string; overview: string | null; primaryImageTag: string | null; premiereDate: string | null; endDate: string | null } /** * Connectivity status */ export type ConnectivityStatus = { /** * Whether the Jellyfin server is reachable */ isServerReachable: boolean; /** * Last time we checked server reachability (ISO 8601 string) */ lastChecked: string | null; /** * Error message from last connectivity check */ connectionError: string | null; /** * Whether we're currently checking connectivity */ isChecking: boolean } /** * Information about a download */ export type DownloadInfo = { id: number; itemId: string; userId: string; filePath: string; fileSize: number | null; mimeType: string | null; status: string; progress: number; bytesDownloaded: number; queuedAt: string; startedAt: string | null; completedAt: string | null; errorMessage: string | null; retryCount: number; priority: number; itemName: string | null; artistName: string | null; albumName: string | null; seriesName: string | null; seasonName: string | null; episodeNumber: number | null; seasonNumber: number | null; qualityPreset: string | null; mediaType: string; downloadSource: string } /** * Request payload for download_item_and_start (bundled to stay within specta's * 10-argument command limit). */ export type DownloadItemAndStartRequest = { itemId: string; userId: string; streamUrl: string; targetDir: string; itemName: string | null; artistName: string | null; albumName: string | null } /** * Request payload for download_item. */ export type DownloadItemRequest = { itemId: string; userId: string; filePath: string; mimeType: string | null; priority: number | null; itemName: string | null; artistName: string | null; albumName: string | null; expectedSize: number | null } /** * Download manager statistics */ export type DownloadManagerStats = { max_concurrent: number; active_count: number; available_slots: number } /** * Download statistics computed server-side */ export type DownloadStats = { total: number; activeCount: number; queuedCount: number; completedCount: number; failedCount: number; pausedCount: number } /** * Request payload for download_video. */ export type DownloadVideoRequest = { itemId: string; userId: string; filePath: string; mimeType: string | null; priority: number | null; itemName: string | null; qualityPreset: string | null; seriesName: string | null; seasonName: string | null; episodeNumber: number | null; seasonNumber: number | null } /** * Enhanced response with pre-computed stats */ export type DownloadsResponse = { downloads: DownloadInfo[]; stats: DownloadStats } /** * Genre */ export type Genre = { id: string; name: string; /** * Number of albums tagged with this genre, when the backend can supply it * (online only). Lets the frontend rank/pick genres without probing each * one. `None` when unknown (e.g. offline). */ albumCount: number | null } /** * Request to get an image URL (with caching) */ export type GetImageRequest = { itemId: string; imageType: string; maxWidth?: number | null; maxHeight?: number | null; tag?: string | null } /** * Options for querying items */ export type GetItemsOptions = { startIndex?: number | null; limit?: number | null; sortBy?: string | null; sortOrder?: string | null; includeItemTypes?: string[] | null; recursive?: boolean | null; fields?: string[] | null; genres?: string[] | null } /** * Image options */ export type ImageOptions = { maxWidth?: number | null; maxHeight?: number | null; quality?: number | null; tag?: string | null } /** * Image type */ export type ImageType = "Primary" | "Backdrop" | "Banner" | "Thumb" | "Logo" /** * Library (media collection) */ export type Library = { id: string; name: string; collectionType: string; imageTag?: string | null } /** * Live stream information returned from opening a Live TV / channel stream. * * Unlike on-demand video, a live channel must be "opened" before it can be * streamed; the server returns a transcoding URL (already absolute) plus a * `live_stream_id` that can later be used to close the stream. */ export type LiveStreamInfo = { streamUrl: string; playSessionId: string | null; liveStreamId: string | null; mediaSourceId: string | null } /** * An LMS multi-room sync group, as returned by JellyLMS `/JellyLms/SyncGroups`. * * Mirrors the plugin's `SyncGroup` model. The master is the sync source; slaves * follow it in lockstep. */ export type LmsSyncGroup = { masterMac: string; masterName?: string; slaveMacs?: string[]; slaveNames?: string[] } /** * Media item */ export type MediaItem = { id: string; name: string; type: string; /** * Whether this item is a folder/container (vs a playable leaf). Used to * decide whether a channel item drills into a list or plays directly. */ isFolder?: boolean; serverId: string; parentId?: string | null; libraryId?: string | null; overview?: string | null; genres?: string[] | null; productionYear?: number | null; communityRating?: number | null; officialRating?: string | null; runTimeTicks?: number | null; primaryImageTag?: string | null; backdropImageTags?: string[] | null; parentBackdropImageTags?: string[] | null; albumId?: string | null; albumName?: string | null; albumArtist?: string | null; artists?: string[] | null; artistItems?: ArtistItem[] | null; indexNumber?: number | null; parentIndexNumber?: number | null; seriesId?: string | null; seriesName?: string | null; seasonId?: string | null; seasonName?: string | null; userData?: UserData | null; mediaStreams?: MediaStream[] | null; mediaSources?: MediaSource[] | null; people?: Person[] | null } /** * Media session type tracking the high-level playback context */ export type MediaSessionType = /** * No active session - browsing library */ { type: "idle" } | /** * Audio playback session (music, audiobooks, podcasts) * Persists until explicitly dismissed */ { type: "audio"; last_item: PlayerMediaItem | null; is_active: boolean } | /** * Movie playback (single video, auto-dismiss on end) */ { type: "movie"; item: PlayerMediaItem; is_active: boolean } | /** * TV show playback (supports next episode auto-advance) */ { type: "tv_show"; item: PlayerMediaItem; series_id: string; is_active: boolean } /** * Media source information */ export type MediaSource = { id: string; name: string; container?: string | null; size?: number | null; bitrate?: number | null; supportsDirectPlay: boolean; supportsDirectStream: boolean; supportsTranscoding: boolean; directStreamUrl?: string | null } /** * Media stream information (audio, video, subtitle tracks) */ export type MediaStream = { type: string; codec?: string | null; language?: string | null; displayTitle?: string | null; index: number; isDefault: boolean; isForced: boolean } export type MediaType = "audio" | "video" /** * Lightweight media item for merged playback state * Converts from both local MediaItem and remote NowPlayingItem */ export type MergedMediaItem = { id: string; title: string; artist: string | null; album: string | null; albumId: string | null; duration: number | null; primaryImageTag: string | null; mediaType: string } export type NowPlayingItem = { id: string | null; name: string | null; runTimeTicks: number | null; album: string | null; albumId: string | null; albumArtist: string | null; artists: string[] | null; imageTags: Partial<{ [key in string]: string }> | null; primaryImageTag: string | null; albumPrimaryImageTag: string | null; Type: string | null } export type OfflineItem = { id: string; name: string; itemType: string; albumId: string | null; albumName: string | null; artists: string | null; runtimeTicks: number | null; primaryImageTag: string | null } /** * Person (cast/crew member) - for movies, series, and episodes */ export type Person = { /** * Deserializes from API's "Id" field (PascalCase), serializes as "id" (camelCase to frontend) */ id?: string; /** * Deserializes from API's "Name" field (PascalCase), serializes as "name" (camelCase to frontend) */ name?: string; /** * Person type from Jellyfin API (Actor, Director, Writer, etc.) * Deserializes from API's "Type" field (PascalCase), serializes as "type" (camelCase to frontend) */ type?: string; /** * Deserializes from API's "Role" field (PascalCase), serializes as "role" (camelCase to frontend) */ role?: string | null; /** * Deserializes from API's "PrimaryImageTag" field (PascalCase), serializes as "primaryImageTag" (camelCase to frontend) */ primaryImageTag?: string | null } /** * Request to play a track from an album (backend fetches all tracks) */ export type PlayAlbumTrackRequest = { albumId: string; albumName: string; trackId: string; shuffle: boolean } /** * Request to play a single video item * * Simplified to video playback only. Audio playback uses player_play_tracks * to avoid Tauri Android serialization issues with complex objects. */ export type PlayItemRequest = { id: string; title: string; streamUrl: string; /** * Video codec (e.g., "h264", "hevc") for video media */ videoCodec: string; /** * Whether the video requires server-side transcoding */ needsTranscoding: boolean } /** * Queue context for remote transfer - what type of queue is this? */ export type PlayQueueContext = /** * Playing from a specific album */ { type: "album"; albumId: string; albumName: string } | /** * Playing from a specific playlist */ { type: "playlist"; playlistId: string; playlistName: string } | /** * Custom queue (search results, manual queue, etc.) */ { type: "custom" } /** * Request to play a queue of items */ export type PlayQueueRequest = { items: PlayItemRequest[]; startIndex: number; shuffle: boolean; /** * Optional context for the queue (album, playlist, or custom) * Used for remote playback transfer */ context?: PlayQueueContext | null } export type PlayState = { positionTicks?: number | null; canSeek?: boolean | null; isPaused?: boolean | null; isMuted?: boolean | null; volumeLevel?: number | null; repeatMode?: string | null; shuffleMode?: string | null } /** * Context information for track playback */ export type PlayTracksContext = { type: "playlist"; playlistId: string; playlistName: string } | { type: "search"; searchQuery: string } | { type: "custom"; label: string | null } /** * Request to play tracks by ID (backend fetches metadata) */ export type PlayTracksRequest = { trackIds: string[]; startIndex: number; shuffle: boolean; context: PlayTracksContext; /** * Position (seconds) to resume the starting track from. Used when taking * over playback from a remote session so we don't restart from 0. */ startPosition?: number | null } /** * Playback information */ export type PlaybackInfo = { mediaSourceId: string; playSessionId: string; streamUrl: string; directPlay: boolean; needsTranscoding: boolean } /** * Playback mode - local device, remote session, or idle */ export type PlaybackMode = { type: "local" } | { type: "remote"; session_id: string } | { type: "idle" } /** * Playback progress info */ export type PlaybackProgress = { itemId: string; positionTicks: number; isPlayed: boolean; isFavorite: boolean; playCount: number } /** * Represents a media item that can be played * * TRACES: UR-003, UR-004 | DR-002 */ export type PlayerMediaItem = { /** * Unique identifier */ id: string; /** * Display title */ title: string; /** * Name (alias for title - for frontend compatibility) */ name?: string | null; /** * Artist name(s) for audio */ artist: string | null; /** * Album name for audio */ album: string | null; /** * Album name (alias - for frontend compatibility) */ albumName?: string | null; /** * Album ID (Jellyfin ID) for remote transfer context */ albumId?: string | null; /** * Artist items with IDs for clickable links */ artistItems?: ArtistItem[] | null; /** * Artists as array of strings (fallback when artist_items not available) */ artists?: string[] | null; /** * Primary image tag for artwork */ primaryImageTag?: string | null; /** * Item type (Audio, Movie, Episode, etc.) */ type?: string | null; /** * Playlist ID (Jellyfin ID) for remote transfer context */ playlistId?: string | null; /** * Duration in seconds */ duration: number | null; /** * URL or path to artwork image */ artworkUrl: string | null; /** * Type of media */ mediaType: MediaType; /** * Source of the media */ source: PlayerMediaSource; /** * Video codec (e.g., "h264", "hevc") for video media */ videoCodec?: string | null; /** * Whether the video requires server-side transcoding */ needsTranscoding?: boolean; /** * Video width in pixels */ videoWidth?: number | null; /** * Video height in pixels */ videoHeight?: number | null; /** * Available subtitle tracks */ subtitles?: SubtitleTrack[]; /** * Series ID (for TV show episodes) - used for series audio preferences */ seriesId?: string | null; /** * Server ID - used for series audio preferences */ serverId?: string | null } /** * TRACES: UR-002, UR-003, UR-004, UR-011 | DR-003 */ export type PlayerMediaSource = /** * Streaming from Jellyfin server */ { type: "remote"; stream_url: string; jellyfin_item_id: string } | /** * Downloaded/cached locally */ { type: "local"; file_path: string; jellyfin_item_id: string | null } | /** * Direct URL (e.g., channel plugins) */ { type: "directurl"; url: string } /** * Player state machine (6 states: Idle, Loading, Playing, Paused, Seeking, Error) * * TRACES: UR-005 | DR-001 */ export type PlayerState = /** * No media loaded */ { kind: "idle" } | /** * Media is being loaded/buffered */ { kind: "loading"; media: PlayerMediaItem } | /** * Media is playing */ { kind: "playing"; media: PlayerMediaItem; position: number; duration: number } | /** * Media is paused */ { kind: "paused"; media: PlayerMediaItem; position: number; duration: number } | /** * Seeking to a new position */ { kind: "seeking"; media: PlayerMediaItem; target: number } | /** * An error occurred */ { kind: "error"; media: PlayerMediaItem | null; error: string } /** * Response for player state queries */ export type PlayerStatus = { state: PlayerState; position: number; duration: number | null; volume: number; muted: boolean; shuffle: boolean; repeat: RepeatMode; /** * Backend being used (native = ExoPlayer/libmpv, html5 = fallback) */ backend: VideoBackend; /** * Whether frontend should render HTML5 video element */ useHtml5Element: boolean; /** * Media item from either local queue or remote session */ mergedMedia: MergedMediaItem | null; /** * Playing state from either local player or remote session */ mergedIsPlaying: boolean; /** * Volume from either local player or remote session (0-1 normalized) */ mergedVolume: number } /** * Events emitted by the player backend to the frontend via Tauri events. * * These are distinct from `PlayerEvent` in state.rs, which handles internal * state machine transitions. * * TRACES: UR-005, UR-019, UR-023, UR-026 | DR-001, DR-028, DR-047 */ export type PlayerStatusEvent = /** * Playback position updated (emitted periodically during playback) */ { type: "position_update"; position: number; duration: number } | /** * Player state changed */ { type: "state_changed"; state: string; media_id: string | null } | /** * Media has finished loading and is ready to play */ { type: "media_loaded"; duration: number } | /** * Playback has ended naturally (reached end of media) */ { type: "playback_ended" } | /** * Buffering state changed */ { type: "buffering"; percent: number } | /** * An error occurred during playback */ { type: "error"; message: string; recoverable: boolean } | /** * Volume changed */ { type: "volume_changed"; volume: number; muted: boolean } | /** * Sleep timer state changed */ { type: "sleep_timer_changed"; mode: SleepTimerMode; remaining_seconds: number } | /** * Show next episode popup with countdown */ { type: "show_next_episode_popup"; current_episode: MediaItem; next_episode: MediaItem; countdown_seconds: number; auto_advance: boolean } | /** * Countdown tick (emitted every second during autoplay countdown) */ { type: "countdown_tick"; remaining_seconds: number } | /** * Queue changed (items added, removed, reordered, or playback mode changed) */ { type: "queue_changed"; items: PlayerMediaItem[]; current_index: number | null; shuffle: boolean; repeat: RepeatMode; has_next: boolean; has_previous: boolean } | /** * Media session changed (activity context changed: Audio/Movie/TvShow/Idle) */ { type: "session_changed"; session: MediaSessionType } | /** * Remote sessions updated (for cast/remote control UI) */ { type: "sessions_updated"; sessions: SessionInfo[] } | /** * The user asked to disconnect from the remote session and resume locally. * * Emitted when the lockscreen Stop button is pressed while casting. The * frontend owns the two-step remote->local transfer (it must reload the * media item locally), so the native side only signals intent here. */ { type: "remote_disconnect_requested" } /** * Result of creating a playlist * * @req: JA-019 - Get/create/update playlists */ export type PlaylistCreatedResult = { id: string } /** * Playlist entry — wraps a MediaItem with the Jellyfin PlaylistItemId * needed for remove/reorder operations (distinct from the media item's ID) * * @req: UR-014 - Make and edit playlists of music that sync back to Jellyfin */ export type PlaylistEntry = /** * The underlying media item */ ({ id: string; name: string; type: string; /** * Whether this item is a folder/container (vs a playable leaf). Used to * decide whether a channel item drills into a list or plays directly. */ isFolder?: boolean; serverId: string; parentId?: string | null; libraryId?: string | null; overview?: string | null; genres?: string[] | null; productionYear?: number | null; communityRating?: number | null; officialRating?: string | null; runTimeTicks?: number | null; primaryImageTag?: string | null; backdropImageTags?: string[] | null; parentBackdropImageTags?: string[] | null; albumId?: string | null; albumName?: string | null; albumArtist?: string | null; artists?: string[] | null; artistItems?: ArtistItem[] | null; indexNumber?: number | null; parentIndexNumber?: number | null; seriesId?: string | null; seriesName?: string | null; seasonId?: string | null; seasonName?: string | null; userData?: UserData | null; mediaStreams?: MediaStream[] | null; mediaSources?: MediaSource[] | null; people?: Person[] | null }) & { /** * The playlist-scoped entry ID (Jellyfin's PlaylistItemId) */ playlistItemId: string } /** * Response for preload operation */ export type PreloadResult = { /** * Number of tracks queued for preload */ queuedCount: number; /** * Number of tracks already downloaded */ alreadyDownloaded: number; /** * Number of tracks skipped (no jellyfin ID or other reasons) */ skipped: number } /** * Response for queue queries */ export type QueueStatus = { items: PlayerMediaItem[]; currentIndex: number | null; shuffle: boolean; repeat: RepeatMode; hasNext: boolean; hasPrevious: boolean } /** * Remote session status for UI updates */ export type RemoteSessionStatus = { position: number; duration: number | null; isPlaying: boolean; nowPlayingItem: NowPlayingItem | null } /** * Repeat mode for the queue * * TRACES: UR-005 | DR-005 */ export type RepeatMode = "off" | "all" | "one" /** * Options for search queries */ export type SearchOptions = { limit?: number | null; includeItemTypes?: string[] | null; searchTerm?: string | null } /** * Search result with pagination */ export type SearchResult = { items: MediaItem[]; totalRecordCount: number } /** * Security status info */ export type SecurityStatus = { usingKeyring: boolean; storageType: string } /** * Audio track preference for a series */ export type SeriesAudioPreference = { seriesId: string; audioTrackDisplayTitle: string | null; audioTrackLanguage: string | null; audioTrackIndex: number | null } /** * Server info returned to frontend */ export type ServerInfo = { id: string; name: string; url: string; version: string | null } /** * Active session for restoration */ export type Session = { userId: string; username: string; serverId: string; serverUrl: string; serverName: string; accessToken: string; verified: boolean; needsReauth: boolean } /** * Session information from Jellyfin */ export type SessionInfo = { id?: string | null; userId?: string | null; userName?: string | null; client?: string | null; deviceName?: string | null; deviceId?: string | null; applicationVersion?: string | null; isActive?: boolean | null; supportsMediaControl?: boolean | null; supportsRemoteControl?: boolean; nowPlayingItem?: NowPlayingItem | null; playState?: PlayState | null; playableMediaTypes?: string[] | null; supportedCommands?: string[] | null } /** * Sleep timer mode - determines when playback should stop * TRACES: UR-026 | DR-029 */ export type SleepTimerMode = /** * Timer is off */ { kind: "off" } | /** * Stop after a specific time duration */ { kind: "time"; endTime: number } | /** * Stop at the end of current track */ { kind: "endOfTrack" } | /** * Stop after N more episodes complete (TV episodes only, not audio tracks) */ { kind: "episodes"; remaining: number } /** * Sleep timer state */ export type SleepTimerState = { mode: SleepTimerMode; remainingSeconds: number } /** * SmartCache statistics */ export type SmartCacheStats = { total_size: number; storage_limit: number; available_space: number; items_count: number; config: CacheConfig } /** * Storage statistics for downloads */ export type StorageStats = { total_bytes: number; total_items: number; albums: AlbumStorageInfo[] } /** * Represents a subtitle track */ export type SubtitleTrack = { /** * Stream index in the media source */ index: number; /** * Subtitle URL */ url: string; /** * Language code (e.g., "eng", "spa") */ language: string | null; /** * Display title */ label: string | null; /** * MIME type (e.g., "text/vtt", "application/x-subrip") */ mime_type: string } /** * Sync queue item returned to frontend */ export type SyncQueueItem = { id: number; userId: string; operation: string; itemId: string | null; payload: string | null; status: string; retryCount: number; createdAt: string | null; errorMessage: string | null } /** * Statistics about the thumbnail cache */ export type ThumbnailCacheStats = { totalSizeBytes: number; itemCount: number; limitBytes: number } /** * User information */ export type User = { id: string; name: string; serverId: string; primaryImageTag: string | null } /** * User-specific data for an item (playback state, favorites, etc.) */ export type UserData = { playbackPositionTicks?: number | null; isPlayed?: boolean | null; isFavorite?: boolean | null; playCount?: number | null; lastPlayedDate?: string | null; playbackContextType?: string | null; playbackContextId?: string | null } /** * User info returned to frontend */ export type UserInfo = { id: string; serverId: string; username: string; isActive: boolean } /** * Backend type for video playback */ export type VideoBackend = /** * Native backend (ExoPlayer on Android, libmpv on Linux) */ "native" | /** * HTML5 video element fallback */ "html5" /** * Response for video seek operations */ export type VideoSeekResponse = /** * Use native seeking (HLS or direct stream) */ { strategy: "native"; position: number } | /** * Reload stream from new position (transcoded non-HLS) */ { strategy: "reloadStream"; new_url: string; seek_offset: number } /** * Video playback settings */ export type VideoSettings = { /** * Enable auto-play of next episode (with countdown) */ autoPlayNextEpisode: boolean; /** * Countdown duration in seconds before auto-play (5-30 seconds) */ autoPlayCountdownSeconds: number; /** * Maximum number of episodes to auto-play consecutively (0 = unlimited) */ autoPlayMaxEpisodes?: number } /** * Volume normalization levels matching Spotify's presets */ export type VolumeLevel = /** * Louder output (-11 LUFS) */ "loud" | /** * Default level (-14 LUFS) */ "normal" | /** * Quieter output (-23 LUFS) */ "quiet" /** tauri-specta globals **/ import { invoke as TAURI_INVOKE, Channel as TAURI_CHANNEL, } from "@tauri-apps/api/core"; import * as TAURI_API_EVENT from "@tauri-apps/api/event"; import { type WebviewWindow as __WebviewWindow__ } from "@tauri-apps/api/webviewWindow"; type __EventObj__ = { listen: ( cb: TAURI_API_EVENT.EventCallback, ) => ReturnType>; once: ( cb: TAURI_API_EVENT.EventCallback, ) => ReturnType>; emit: null extends T ? (payload?: T) => ReturnType : (payload: T) => ReturnType; }; export type Result = | { status: "ok"; data: T } | { status: "error"; error: E }; function __makeEvents__>( mappings: Record, ) { return new Proxy( {} as unknown as { [K in keyof T]: __EventObj__ & { (handle: __WebviewWindow__): __EventObj__; }; }, { get: (_, event) => { const name = mappings[event as keyof T]; return new Proxy((() => {}) as any, { apply: (_, __, [window]: [__WebviewWindow__]) => ({ listen: (arg: any) => window.listen(name, arg), once: (arg: any) => window.once(name, arg), emit: (arg: any) => window.emit(name, arg), }), get: (_, command: keyof __EventObj__) => { switch (command) { case "listen": return (arg: any) => TAURI_API_EVENT.listen(name, arg); case "once": return (arg: any) => TAURI_API_EVENT.once(name, arg); case "emit": return (arg: any) => TAURI_API_EVENT.emit(name, arg); } }, }); }, }, ); }