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>
2230 lines
78 KiB
TypeScript
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);
|
|
}
|
|
},
|
|
});
|
|
},
|
|
},
|
|
);
|
|
}
|