jellytau/src/lib/api/bindings.ts
Duncan Tourolle 385d2270c9 fix(android): keep lockscreen/media controls in sync with playback
The lockscreen controls drifted out of sync, especially while casting, and
couldn't control remote playback. Two media sessions were competing (a Media3
MediaSession driving transport vs a MediaSessionCompat driving the notification),
position was only pushed on play/pause so the scrubber froze mid-track, and
remote mode showed stale local metadata with dead buttons.

- Make MediaSessionCompat the single source of truth; route all transport
  commands (both the Compat callback and the Media3 wrappedPlayer) through Rust
  via nativeOnMediaCommand instead of touching ExoPlayer directly.
- Push position on every 250ms tick via a lightweight updatePlaybackPosition,
  and report 0.0 playback speed when paused so Android stops extrapolating.
- Mirror the remote session's now-playing onto the lockscreen from the native
  session poller (works while the screen is locked, unlike WebView timers) via
  a new player::update_lockscreen_metadata JNI bridge.
- Make MediaSessionHandler mode-aware: in remote mode forward play/pause/next/
  prev/seek to the remote Jellyfin session; Stop while casting emits
  RemoteDisconnectRequested, which the frontend handles by transferring to local.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 23:55:26 +02:00

2230 lines
78 KiB
TypeScript

// 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<PlayerStatus> {
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<PlayerStatus> {
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<PlayerStatus> {
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<PlayerStatus> {
return await TAURI_INVOKE("player_play_tracks", { repositoryHandle, request });
},
async playerPlay() : Promise<PlayerStatus> {
return await TAURI_INVOKE("player_play");
},
async playerPause() : Promise<PlayerStatus> {
return await TAURI_INVOKE("player_pause");
},
async playerToggle() : Promise<PlayerStatus> {
return await TAURI_INVOKE("player_toggle");
},
async playerStop() : Promise<PlayerStatus> {
return await TAURI_INVOKE("player_stop");
},
async playerNext() : Promise<PlayerStatus> {
return await TAURI_INVOKE("player_next");
},
async playerPrevious() : Promise<PlayerStatus> {
return await TAURI_INVOKE("player_previous");
},
async playerSeek(position: number) : Promise<PlayerStatus> {
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<VideoSeekResponse> {
return await TAURI_INVOKE("player_seek_video", { repositoryHandle, position, mediaSourceId, audioStreamIndex, useHtml5 });
},
async playerSetVolume(volume: number) : Promise<PlayerStatus> {
return await TAURI_INVOKE("player_set_volume", { volume });
},
async playerToggleMute() : Promise<PlayerStatus> {
return await TAURI_INVOKE("player_toggle_mute");
},
async playerSetAudioTrack(streamIndex: number) : Promise<PlayerStatus> {
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<AudioTrackSwitchResponse> {
return await TAURI_INVOKE("player_switch_audio_track", { repositoryHandle, streamIndex, arrayIndex, useHtml5, currentPosition, mediaSourceId });
},
async playerSetSubtitleTrack(streamIndex: number | null) : Promise<PlayerStatus> {
return await TAURI_INVOKE("player_set_subtitle_track", { streamIndex });
},
async playerToggleShuffle() : Promise<QueueStatus> {
return await TAURI_INVOKE("player_toggle_shuffle");
},
async playerCycleRepeat() : Promise<QueueStatus> {
return await TAURI_INVOKE("player_cycle_repeat");
},
async playerGetStatus() : Promise<PlayerStatus> {
return await TAURI_INVOKE("player_get_status");
},
async playerGetQueue() : Promise<QueueStatus> {
return await TAURI_INVOKE("player_get_queue");
},
async playerAddToQueue(request: AddToQueueRequest) : Promise<QueueStatus> {
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<QueueStatus> {
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<QueueStatus> {
return await TAURI_INVOKE("player_add_tracks_by_ids", { repositoryHandle, request });
},
async playerRemoveFromQueue(index: number) : Promise<QueueStatus> {
return await TAURI_INVOKE("player_remove_from_queue", { index });
},
async playerMoveInQueue(fromIndex: number, toIndex: number) : Promise<QueueStatus> {
return await TAURI_INVOKE("player_move_in_queue", { fromIndex, toIndex });
},
async playerSkipTo(index: number) : Promise<PlayerStatus> {
return await TAURI_INVOKE("player_skip_to", { index });
},
async playerSetAudioSettings(settings: AudioSettings) : Promise<AudioSettings> {
return await TAURI_INVOKE("player_set_audio_settings", { settings });
},
async playerGetAudioSettings() : Promise<AudioSettings> {
return await TAURI_INVOKE("player_get_audio_settings");
},
async playerSetVideoSettings(settings: VideoSettings) : Promise<VideoSettings> {
return await TAURI_INVOKE("player_set_video_settings", { settings });
},
async playerGetVideoSettings() : Promise<VideoSettings> {
return await TAURI_INVOKE("player_get_video_settings");
},
/**
* Set sleep timer mode
*/
async playerSetSleepTimer(mode: SleepTimerMode) : Promise<SleepTimerState> {
return await TAURI_INVOKE("player_set_sleep_timer", { mode });
},
/**
* Cancel sleep timer
*/
async playerCancelSleepTimer() : Promise<SleepTimerState> {
return await TAURI_INVOKE("player_cancel_sleep_timer");
},
/**
* Get current sleep timer state
*/
async playerGetSleepTimer() : Promise<SleepTimerState> {
return await TAURI_INVOKE("player_get_sleep_timer");
},
/**
* Get autoplay settings
*/
async playerGetAutoplaySettings() : Promise<AutoplaySettings> {
return await TAURI_INVOKE("player_get_autoplay_settings");
},
/**
* Set autoplay settings and persist to database
*/
async playerSetAutoplaySettings(userId: string, settings: AutoplaySettings) : Promise<AutoplaySettings> {
return await TAURI_INVOKE("player_set_autoplay_settings", { userId, settings });
},
/**
* Cancel active autoplay countdown
*/
async playerCancelAutoplayCountdown() : Promise<null> {
return await TAURI_INVOKE("player_cancel_autoplay_countdown");
},
/**
* Play next episode (user confirmed from popup)
*/
async playerPlayNextEpisode(item: PlayItemRequest) : Promise<PlayerStatus> {
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<null> {
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<PreloadResult> {
return await TAURI_INVOKE("player_preload_upcoming", { userId, downloadBasePath });
},
/**
* Update SmartCache configuration
*/
async playerSetCacheConfig(config: CacheConfig) : Promise<null> {
return await TAURI_INVOKE("player_set_cache_config", { config });
},
/**
* Get current SmartCache configuration
*/
async playerGetCacheConfig() : Promise<CacheConfig> {
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<null> {
return await TAURI_INVOKE("player_configure_jellyfin", { serverUrl, accessToken, userId, deviceId });
},
/**
* Disable Jellyfin automatic playback reporting
*/
async playerDisableJellyfin() : Promise<null> {
return await TAURI_INVOKE("player_disable_jellyfin");
},
/**
* Get the current media session state
*/
async playerGetSession() : Promise<MediaSessionType> {
return await TAURI_INVOKE("player_get_session");
},
/**
* Dismiss the current media session (returns to Idle)
*/
async playerDismissSession() : Promise<null> {
return await TAURI_INVOKE("player_dismiss_session");
},
/**
* Play items on a remote Jellyfin session (casting)
*/
async remotePlayOnSession(sessionId: string, itemIds: string[], startIndex: number) : Promise<null> {
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<null> {
return await TAURI_INVOKE("remote_send_command", { sessionId, command });
},
/**
* Seek on a remote session
*/
async remoteSessionSeek(sessionId: string, positionTicks: number) : Promise<null> {
return await TAURI_INVOKE("remote_session_seek", { sessionId, positionTicks });
},
/**
* Set volume on a remote session
*/
async remoteSessionSetVolume(sessionId: string, volume: number) : Promise<null> {
return await TAURI_INVOKE("remote_session_set_volume", { sessionId, volume });
},
/**
* Toggle mute on a remote session
*/
async remoteSessionToggleMute(sessionId: string) : Promise<null> {
return await TAURI_INVOKE("remote_session_toggle_mute", { sessionId });
},
/**
* List current LMS sync groups.
*/
async lmsGetSyncGroups() : Promise<LmsSyncGroup[]> {
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<null> {
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<null> {
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<null> {
return await TAURI_INVOKE("lms_dissolve_sync_group", { masterMac });
},
/**
* Set polling frequency hint based on UI state
*/
async sessionsSetPollingHint(hint: string) : Promise<null> {
return await TAURI_INVOKE("sessions_set_polling_hint", { hint });
},
/**
* Manually trigger a session poll (for refresh button)
*/
async sessionsPollNow() : Promise<SessionInfo[]> {
return await TAURI_INVOKE("sessions_poll_now");
},
/**
* Get the current playback mode
*/
async playbackModeGetCurrent() : Promise<PlaybackMode> {
return await TAURI_INVOKE("playback_mode_get_current");
},
/**
* Set the playback mode (internal/testing use)
*/
async playbackModeSet(mode: PlaybackMode) : Promise<null> {
return await TAURI_INVOKE("playback_mode_set", { mode });
},
/**
* Check if currently transferring between playback modes
*/
async playbackModeIsTransferring() : Promise<boolean> {
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<null> {
return await TAURI_INVOKE("playback_mode_transfer_to_remote", { sessionId, position });
},
/**
* Get remote session status (for polling position/duration)
*/
async playbackModeGetRemoteStatus() : Promise<RemoteSessionStatus> {
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<null> {
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<null> {
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<null> {
return await TAURI_INVOKE("playback_reporter_init", { serverUrl, userId, accessToken, deviceId });
},
/**
* Destroy playback reporter (called on logout)
*/
async playbackReporterDestroy() : Promise<null> {
return await TAURI_INVOKE("playback_reporter_destroy");
},
/**
* Report playback start
*/
async playbackReportStart(itemId: string, positionSeconds: number, contextType: string | null, contextId: string | null) : Promise<null> {
return await TAURI_INVOKE("playback_report_start", { itemId, positionSeconds, contextType, contextId });
},
/**
* Report playback progress
*/
async playbackReportProgress(itemId: string, positionSeconds: number, isPaused: boolean) : Promise<null> {
return await TAURI_INVOKE("playback_report_progress", { itemId, positionSeconds, isPaused });
},
/**
* Report playback stopped
*/
async playbackReportStopped(itemId: string, positionSeconds: number) : Promise<null> {
return await TAURI_INVOKE("playback_report_stopped", { itemId, positionSeconds });
},
/**
* Mark item as played
*/
async playbackMarkPlayed(itemId: string) : Promise<null> {
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<Session | null> {
return await TAURI_INVOKE("auth_initialize");
},
/**
* Connect to a Jellyfin server and get server info
*/
async authConnectToServer(serverUrl: string) : Promise<AuthServerInfo> {
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<AuthResult> {
return await TAURI_INVOKE("auth_login", { serverUrl, username, password, deviceId });
},
/**
* Verify current session
*/
async authVerifySession(serverUrl: string, userId: string, accessToken: string, deviceId: string) : Promise<boolean> {
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<null> {
return await TAURI_INVOKE("auth_logout", { serverUrl, accessToken, deviceId });
},
/**
* Get current session
*/
async authGetSession() : Promise<Session | null> {
return await TAURI_INVOKE("auth_get_session");
},
/**
* Set current session (for restoration from storage)
*/
async authSetSession(session: Session | null) : Promise<null> {
return await TAURI_INVOKE("auth_set_session", { session });
},
/**
* Start background session verification
*/
async authStartVerification(deviceId: string) : Promise<null> {
return await TAURI_INVOKE("auth_start_verification", { deviceId });
},
/**
* Stop background session verification
*/
async authStopVerification() : Promise<null> {
return await TAURI_INVOKE("auth_stop_verification");
},
/**
* Re-authenticate with password (when session expired)
*/
async authReauthenticate(password: string, deviceId: string) : Promise<AuthResult> {
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<string> {
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<null> {
return await TAURI_INVOKE("device_set_id", { deviceId });
},
/**
* Check if the server is currently reachable
*/
async connectivityCheckServer() : Promise<boolean> {
return await TAURI_INVOKE("connectivity_check_server");
},
/**
* Set the server URL and trigger an immediate check
*/
async connectivitySetServerUrl(url: string) : Promise<null> {
return await TAURI_INVOKE("connectivity_set_server_url", { url });
},
/**
* Get the current connectivity status
*/
async connectivityGetStatus() : Promise<ConnectivityStatus> {
return await TAURI_INVOKE("connectivity_get_status");
},
/**
* Start monitoring connectivity with adaptive polling
*/
async connectivityStartMonitoring() : Promise<null> {
return await TAURI_INVOKE("connectivity_start_monitoring");
},
/**
* Stop monitoring connectivity
*/
async connectivityStopMonitoring() : Promise<null> {
return await TAURI_INVOKE("connectivity_stop_monitoring");
},
/**
* Mark the server as reachable (called after successful API calls)
*/
async connectivityMarkReachable() : Promise<null> {
return await TAURI_INVOKE("connectivity_mark_reachable");
},
/**
* Mark the server as unreachable (called after failed API calls)
*/
async connectivityMarkUnreachable(error: string | null) : Promise<null> {
return await TAURI_INVOKE("connectivity_mark_unreachable", { error });
},
/**
* Initialize the database and run migrations
*/
async storageInit() : Promise<string> {
return await TAURI_INVOKE("storage_init");
},
/**
* Get storage directory path (parent directory of the database file)
*/
async storageGetPath() : Promise<string> {
return await TAURI_INVOKE("storage_get_path");
},
/**
* Get database file size in bytes
*/
async storageGetSize() : Promise<number | null> {
return await TAURI_INVOKE("storage_get_size");
},
/**
* Get security status (keyring vs encrypted file fallback)
*/
async storageGetSecurityStatus() : Promise<SecurityStatus> {
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<null> {
return await TAURI_INVOKE("storage_save_server", { id, name, url, version });
},
/**
* Get all saved servers
*/
async storageGetServers() : Promise<ServerInfo[]> {
return await TAURI_INVOKE("storage_get_servers");
},
/**
* Delete a server and all associated data
*/
async storageDeleteServer(serverId: string) : Promise<null> {
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<boolean> {
return await TAURI_INVOKE("storage_save_user", { id, serverId, username, accessToken });
},
/**
* Get users for a server
*/
async storageGetUsers(serverId: string) : Promise<UserInfo[]> {
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<null> {
return await TAURI_INVOKE("storage_set_active_user", { userId, serverId });
},
/**
* Get the active user for a server
*/
async storageGetActiveUser(serverId: string) : Promise<UserInfo | null> {
return await TAURI_INVOKE("storage_get_active_user", { serverId });
},
/**
* Get the active session (user + server + token) for session restoration
*/
async storageGetActiveSession() : Promise<ActiveSession | null> {
return await TAURI_INVOKE("storage_get_active_session");
},
/**
* Get user's access token from secure storage
*/
async storageGetAccessToken(userId: string) : Promise<string | null> {
return await TAURI_INVOKE("storage_get_access_token", { userId });
},
/**
* Delete a user account and their token from secure storage
*/
async storageDeleteUser(userId: string) : Promise<null> {
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<null> {
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<null> {
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<null> {
return await TAURI_INVOKE("storage_mark_played", { userId, itemId });
},
/**
* Get playback progress for an item
*/
async storageGetPlaybackProgress(userId: string, itemId: string) : Promise<PlaybackProgress | null> {
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<null> {
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<boolean> {
return await TAURI_INVOKE("storage_toggle_favorite", { userId, itemId, isFavorite });
},
/**
* Queue a media item for download
*/
async downloadItem(request: DownloadItemRequest) : Promise<number> {
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<number> {
return await TAURI_INVOKE("download_item_and_start", { request });
},
/**
* Queue an entire album for download
*/
async downloadAlbum(albumId: string, userId: string, basePath: string) : Promise<number[]> {
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<number> {
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<number[]> {
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<number[]> {
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<DownloadsResponse> {
return await TAURI_INVOKE("get_downloads", { userId, statusFilter });
},
/**
* Pause a download
*/
async pauseDownload(downloadId: number) : Promise<null> {
return await TAURI_INVOKE("pause_download", { downloadId });
},
/**
* Resume a paused download
*/
async resumeDownload(downloadId: number) : Promise<null> {
return await TAURI_INVOKE("resume_download", { downloadId });
},
/**
* Cancel a download
*/
async cancelDownload(downloadId: number) : Promise<null> {
return await TAURI_INVOKE("cancel_download", { downloadId });
},
/**
* Delete a completed download
*/
async deleteDownload(downloadId: number) : Promise<null> {
return await TAURI_INVOKE("delete_download", { downloadId });
},
/**
* Delete all downloads for a user
*/
async deleteAllDownloads(userId: string) : Promise<number> {
return await TAURI_INVOKE("delete_all_downloads", { userId });
},
/**
* Delete all downloads for a specific album
*/
async deleteAlbumDownloads(albumId: string, userId: string) : Promise<number> {
return await TAURI_INVOKE("delete_album_downloads", { albumId, userId });
},
/**
* Clear all stale pending/failed/paused downloads
*/
async clearStaleDownloads(userId: string) : Promise<number> {
return await TAURI_INVOKE("clear_stale_downloads", { userId });
},
/**
* Get storage statistics for downloads
*/
async getDownloadStorageStats(userId: string) : Promise<StorageStats> {
return await TAURI_INVOKE("get_download_storage_stats", { userId });
},
/**
* Mark a download as completed
*/
async markDownloadCompleted(downloadId: number, bytesDownloaded: number, filePath: string) : Promise<null> {
return await TAURI_INVOKE("mark_download_completed", { downloadId, bytesDownloaded, filePath });
},
/**
* Mark a download as failed
*/
async markDownloadFailed(downloadId: number, errorMessage: string) : Promise<null> {
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<null> {
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<null> {
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<null> {
return await TAURI_INVOKE("enqueue_video_downloads", { handle, downloadIds, targetDir });
},
/**
* Get download manager statistics
*/
async getDownloadManagerStats() : Promise<DownloadManagerStats> {
return await TAURI_INVOKE("get_download_manager_stats");
},
/**
* Set the maximum concurrent downloads
*/
async setMaxConcurrentDownloads(max: number) : Promise<null> {
return await TAURI_INVOKE("set_max_concurrent_downloads", { max });
},
/**
* Get SmartCache statistics
*/
async getSmartCacheStats(userId: string) : Promise<SmartCacheStats> {
return await TAURI_INVOKE("get_smart_cache_stats", { userId });
},
/**
* Update SmartCache configuration
*/
async updateSmartCacheConfig(config: CacheConfig) : Promise<null> {
return await TAURI_INVOKE("update_smart_cache_config", { config });
},
/**
* Get SmartCache configuration
*/
async getSmartCacheConfig() : Promise<CacheConfig> {
return await TAURI_INVOKE("get_smart_cache_config");
},
/**
* Get album recommendations based on play history
*/
async getAlbumRecommendations(userId: string) : Promise<AlbumRecommendation[]> {
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<AlbumAffinityStatus[]> {
return await TAURI_INVOKE("get_album_affinity_status");
},
/**
* Pin an item's metadata (protects from cache clear)
*/
async pinItem(itemId: string) : Promise<null> {
return await TAURI_INVOKE("pin_item", { itemId });
},
/**
* Unpin an item's metadata
*/
async unpinItem(itemId: string) : Promise<null> {
return await TAURI_INVOKE("unpin_item", { itemId });
},
/**
* Check if an item is pinned
*/
async isItemPinned(itemId: string) : Promise<boolean> {
return await TAURI_INVOKE("is_item_pinned", { itemId });
},
/**
* Check if an item is available offline
*/
async offlineIsAvailable(itemId: string) : Promise<boolean> {
return await TAURI_INVOKE("offline_is_available", { itemId });
},
/**
* Get all offline items for a user
*/
async offlineGetItems(userId: string) : Promise<OfflineItem[]> {
return await TAURI_INVOKE("offline_get_items", { userId });
},
/**
* Search offline items
*/
async offlineSearch(userId: string, query: string) : Promise<OfflineItem[]> {
return await TAURI_INVOKE("offline_search", { userId, query });
},
/**
* Get cached libraries for a server
*/
async storageGetLibraries(serverId: string) : Promise<CachedLibrary[]> {
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<CachedItem[]> {
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<CachedItem | null> {
return await TAURI_INVOKE("storage_get_item", { itemId });
},
/**
* Search cached items using FTS
*/
async storageSearchItems(serverId: string, query: string, limit: number | null) : Promise<CachedItem[]> {
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<null> {
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<null> {
return await TAURI_INVOKE("storage_save_item", { item, serverId });
},
/**
* Get count of pending sync operations for a user
*/
async storageGetPendingSyncCount(userId: string) : Promise<number> {
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<number> {
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<SyncQueueItem[]> {
return await TAURI_INVOKE("sync_get_pending", { userId, limit });
},
/**
* Mark a sync operation as in progress
*/
async syncMarkProcessing(id: number) : Promise<null> {
return await TAURI_INVOKE("sync_mark_processing", { id });
},
/**
* Mark a sync operation as completed
*/
async syncMarkCompleted(id: number) : Promise<null> {
return await TAURI_INVOKE("sync_mark_completed", { id });
},
/**
* Mark a sync operation as failed with error message
*/
async syncMarkFailed(id: number, error: string) : Promise<null> {
return await TAURI_INVOKE("sync_mark_failed", { id, error });
},
/**
* Get count of pending sync operations for a user
*/
async syncGetPendingCount(userId: string) : Promise<number> {
return await TAURI_INVOKE("sync_get_pending_count", { userId });
},
/**
* Delete completed sync operations older than specified days
*/
async syncCleanupCompleted(daysOld: number) : Promise<number> {
return await TAURI_INVOKE("sync_cleanup_completed", { daysOld });
},
/**
* Delete all sync operations for a user (used during logout)
*/
async syncClearUser(userId: string) : Promise<null> {
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<string | null> {
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<string> {
return await TAURI_INVOKE("thumbnail_save", { itemId, imageType, tag, url });
},
/**
* Get thumbnail cache statistics
*/
async thumbnailGetStats() : Promise<ThumbnailCacheStats> {
return await TAURI_INVOKE("thumbnail_get_stats");
},
/**
* Set thumbnail cache storage limit in bytes
*/
async thumbnailSetLimit(limitBytes: number) : Promise<null> {
return await TAURI_INVOKE("thumbnail_set_limit", { limitBytes });
},
/**
* Clear all cached thumbnails
*/
async thumbnailClearCache() : Promise<null> {
return await TAURI_INVOKE("thumbnail_clear_cache");
},
/**
* Delete cached thumbnails for a specific item
*/
async thumbnailDeleteItem(itemId: string) : Promise<null> {
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<string> {
return await TAURI_INVOKE("image_get_url", { repositoryHandle, request });
},
/**
* Save a person to the cache
*/
async storageSavePerson(person: CachedPerson) : Promise<null> {
return await TAURI_INVOKE("storage_save_person", { person });
},
/**
* Get a cached person by ID
*/
async storageGetPerson(personId: string) : Promise<CachedPerson | null> {
return await TAURI_INVOKE("storage_get_person", { personId });
},
/**
* Save item-person associations (batch)
*/
async storageSaveItemPeople(associations: CachedItemPerson[]) : Promise<null> {
return await TAURI_INVOKE("storage_save_item_people", { associations });
},
/**
* Get people for an item (with person details joined)
*/
async storageGetItemPeople(itemId: string) : Promise<CachedItemPerson[]> {
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<null> {
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<SeriesAudioPreference | null> {
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<string> {
return await TAURI_INVOKE("repository_create", { serverUrl, userId, accessToken, serverId });
},
/**
* Destroy a repository instance
*/
async repositoryDestroy(handle: string) : Promise<null> {
return await TAURI_INVOKE("repository_destroy", { handle });
},
/**
* Get libraries
*/
async repositoryGetLibraries(handle: string) : Promise<Library[]> {
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<SearchResult> {
return await TAURI_INVOKE("repository_get_items", { handle, parentId, options });
},
/**
* Get a single item by ID
*/
async repositoryGetItem(handle: string, itemId: string) : Promise<MediaItem> {
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<MediaItem[]> {
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<MediaItem[]> {
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<MediaItem[]> {
return await TAURI_INVOKE("repository_get_next_up_episodes", { handle, seriesId, limit });
},
/**
* Get recently played audio
*/
async repositoryGetRecentlyPlayedAudio(handle: string, limit: number | null) : Promise<MediaItem[]> {
return await TAURI_INVOKE("repository_get_recently_played_audio", { handle, limit });
},
/**
* Get resume movies
*/
async repositoryGetResumeMovies(handle: string, limit: number | null) : Promise<MediaItem[]> {
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<MediaItem[]> {
return await TAURI_INVOKE("repository_get_rediscover_albums", { handle, parentId, limit });
},
/**
* Get genres for a library
*/
async repositoryGetGenres(handle: string, parentId: string | null) : Promise<Genre[]> {
return await TAURI_INVOKE("repository_get_genres", { handle, parentId });
},
/**
* Search for items
*/
async repositorySearch(handle: string, query: string, options: SearchOptions | null, requestId: number) : Promise<SearchResult> {
return await TAURI_INVOKE("repository_search", { handle, query, options, requestId });
},
/**
* Get playback info for an item
*/
async repositoryGetPlaybackInfo(handle: string, itemId: string) : Promise<PlaybackInfo> {
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<string> {
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<string> {
return await TAURI_INVOKE("repository_get_audio_stream_url", { handle, itemId });
},
/**
* Get Live TV channels (broadcast / IPTV) for browsing
*/
async repositoryGetLiveTvChannels(handle: string) : Promise<MediaItem[]> {
return await TAURI_INVOKE("repository_get_live_tv_channels", { handle });
},
/**
* Get the root list of plugin "Channels"
*/
async repositoryGetChannels(handle: string) : Promise<SearchResult> {
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<LiveStreamInfo> {
return await TAURI_INVOKE("repository_open_live_stream", { handle, itemId });
},
/**
* Report playback start
*/
async repositoryReportPlaybackStart(handle: string, itemId: string, positionTicks: number) : Promise<null> {
return await TAURI_INVOKE("repository_report_playback_start", { handle, itemId, positionTicks });
},
/**
* Report playback progress
*/
async repositoryReportPlaybackProgress(handle: string, itemId: string, positionTicks: number) : Promise<null> {
return await TAURI_INVOKE("repository_report_playback_progress", { handle, itemId, positionTicks });
},
/**
* Report playback stopped
*/
async repositoryReportPlaybackStopped(handle: string, itemId: string, positionTicks: number) : Promise<null> {
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<string> {
return await TAURI_INVOKE("repository_get_image_url", { handle, itemId, imageType, options });
},
/**
* Mark an item as favorite
*/
async repositoryMarkFavorite(handle: string, itemId: string) : Promise<null> {
return await TAURI_INVOKE("repository_mark_favorite", { handle, itemId });
},
/**
* Unmark an item as favorite
*/
async repositoryUnmarkFavorite(handle: string, itemId: string) : Promise<null> {
return await TAURI_INVOKE("repository_unmark_favorite", { handle, itemId });
},
/**
* Get person details
*/
async repositoryGetPerson(handle: string, personId: string) : Promise<MediaItem> {
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<SearchResult> {
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<SearchResult> {
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<string> {
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<string> {
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<PlaylistCreatedResult> {
return await TAURI_INVOKE("playlist_create", { handle, name, itemIds });
},
/**
* Delete a playlist
*/
async playlistDelete(handle: string, playlistId: string) : Promise<null> {
return await TAURI_INVOKE("playlist_delete", { handle, playlistId });
},
/**
* Rename a playlist
*/
async playlistRename(handle: string, playlistId: string, name: string) : Promise<null> {
return await TAURI_INVOKE("playlist_rename", { handle, playlistId, name });
},
/**
* Get playlist items with PlaylistItemId
*/
async playlistGetItems(handle: string, playlistId: string) : Promise<PlaylistEntry[]> {
return await TAURI_INVOKE("playlist_get_items", { handle, playlistId });
},
/**
* Add items to a playlist
*/
async playlistAddItems(handle: string, playlistId: string, itemIds: string[]) : Promise<null> {
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<null> {
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<null> {
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<string> {
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<string> {
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<number> {
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<number> {
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<number> {
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__<T> = {
listen: (
cb: TAURI_API_EVENT.EventCallback<T>,
) => ReturnType<typeof TAURI_API_EVENT.listen<T>>;
once: (
cb: TAURI_API_EVENT.EventCallback<T>,
) => ReturnType<typeof TAURI_API_EVENT.once<T>>;
emit: null extends T
? (payload?: T) => ReturnType<typeof TAURI_API_EVENT.emit>
: (payload: T) => ReturnType<typeof TAURI_API_EVENT.emit>;
};
export type Result<T, E> =
| { status: "ok"; data: T }
| { status: "error"; error: E };
function __makeEvents__<T extends Record<string, any>>(
mappings: Record<keyof T, string>,
) {
return new Proxy(
{} as unknown as {
[K in keyof T]: __EventObj__<T[K]> & {
(handle: __WebviewWindow__): __EventObj__<T[K]>;
};
},
{
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__<any>) => {
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);
}
},
});
},
},
);
}