Workstream E (backend): wire tauri-specta — annotate commands, derive Type, generate-ready Builder
- Add #[specta::specta] to all 201 #[tauri::command] functions. - Derive specta::Type on all IPC DTOs (repository/types, settings, player/storage/ download command DTOs, player enums, jellyfin SessionInfo/NowPlayingItem/PlayState, ThumbnailCacheStats, DownloadInfo, CacheConfig, etc.). - Replace tauri::generate_handler! with a tauri_specta::Builder + collect_commands! in lib.rs (exports bindings.ts in debug builds). Two contract changes required by specta constraints (frontend migration follows): - specta caps command arity at 10 args: download_item_and_start / download_item / download_video now take a single request struct (params bundled, body unchanged via destructuring). - specta can't parse split serde rename_all: SessionInfo/NowPlayingItem/PlayState switched to rename_all = "PascalCase" (Jellyfin deserialization preserved; these now serialize PascalCase to the frontend). cargo check --lib is clean (0 errors). Frontend migration to bindings.ts is the next step.
This commit is contained in:
@@ -56,7 +56,7 @@ pub struct MediaSessionManagerWrapper(pub Mutex<MediaSessionManager>);
|
||||
pub struct VideoSettingsWrapper(pub Mutex<VideoSettings>);
|
||||
|
||||
/// Response for player state queries
|
||||
#[derive(Debug, Serialize)]
|
||||
#[derive(specta::Type, Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PlayerStatus {
|
||||
pub state: PlayerState,
|
||||
@@ -82,7 +82,7 @@ pub struct PlayerStatus {
|
||||
|
||||
/// Lightweight media item for merged playback state
|
||||
/// Converts from both local MediaItem and remote NowPlayingItem
|
||||
#[derive(Debug, Serialize, Clone)]
|
||||
#[derive(specta::Type, Debug, Serialize, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MergedMediaItem {
|
||||
pub id: String,
|
||||
@@ -134,7 +134,7 @@ impl From<&crate::jellyfin::client::NowPlayingItem> for MergedMediaItem {
|
||||
}
|
||||
|
||||
/// Response for queue queries
|
||||
#[derive(Debug, Serialize)]
|
||||
#[derive(specta::Type, Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct QueueStatus {
|
||||
pub items: Vec<MediaItem>,
|
||||
@@ -146,7 +146,7 @@ pub struct QueueStatus {
|
||||
}
|
||||
|
||||
/// Backend type for video playback
|
||||
#[derive(Debug, Serialize)]
|
||||
#[derive(specta::Type, Debug, Serialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum VideoBackend {
|
||||
/// Native backend (ExoPlayer on Android, libmpv on Linux)
|
||||
@@ -159,7 +159,7 @@ pub enum VideoBackend {
|
||||
///
|
||||
/// Simplified to video playback only. Audio playback uses player_play_tracks
|
||||
/// to avoid Tauri Android serialization issues with complex objects.
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[derive(specta::Type, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PlayItemRequest {
|
||||
pub id: String,
|
||||
@@ -172,7 +172,7 @@ pub struct PlayItemRequest {
|
||||
}
|
||||
|
||||
/// Queue context for remote transfer - what type of queue is this?
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[derive(specta::Type, Debug, Deserialize)]
|
||||
#[serde(tag = "type", rename_all = "lowercase")]
|
||||
pub enum PlayQueueContext {
|
||||
/// Playing from a specific album
|
||||
@@ -194,7 +194,7 @@ pub enum PlayQueueContext {
|
||||
}
|
||||
|
||||
/// Request to play a queue of items
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[derive(specta::Type, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PlayQueueRequest {
|
||||
pub items: Vec<PlayItemRequest>,
|
||||
@@ -207,7 +207,7 @@ pub struct PlayQueueRequest {
|
||||
}
|
||||
|
||||
/// Request to play a track from an album (backend fetches all tracks)
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[derive(specta::Type, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PlayAlbumTrackRequest {
|
||||
pub album_id: String,
|
||||
@@ -217,7 +217,7 @@ pub struct PlayAlbumTrackRequest {
|
||||
}
|
||||
|
||||
/// Request to play tracks by ID (backend fetches metadata)
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[derive(specta::Type, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PlayTracksRequest {
|
||||
pub track_ids: Vec<String>,
|
||||
@@ -227,7 +227,7 @@ pub struct PlayTracksRequest {
|
||||
}
|
||||
|
||||
/// Context information for track playback
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[derive(specta::Type, Debug, Deserialize)]
|
||||
#[serde(tag = "type", rename_all = "lowercase")]
|
||||
pub enum PlayTracksContext {
|
||||
Playlist {
|
||||
@@ -249,7 +249,7 @@ pub enum PlayTracksContext {
|
||||
}
|
||||
|
||||
/// Response for video seek operations
|
||||
#[derive(Debug, Serialize)]
|
||||
#[derive(specta::Type, Debug, Serialize)]
|
||||
#[serde(tag = "strategy", rename_all = "camelCase")]
|
||||
pub enum VideoSeekResponse {
|
||||
/// Use native seeking (HLS or direct stream)
|
||||
@@ -267,7 +267,7 @@ pub enum VideoSeekResponse {
|
||||
}
|
||||
|
||||
/// Response for audio track switching operations
|
||||
#[derive(Debug, Serialize)]
|
||||
#[derive(specta::Type, Debug, Serialize)]
|
||||
#[serde(tag = "strategy", rename_all = "camelCase")]
|
||||
pub enum AudioTrackSwitchResponse {
|
||||
/// Native backend handled it (Android ExoPlayer)
|
||||
@@ -384,6 +384,7 @@ pub(super) async fn check_for_local_download(
|
||||
/// @req: UR-005 - Control media playback (play operation)
|
||||
/// @req: DR-009 - Audio player UI
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn player_play_item(
|
||||
player: State<'_, PlayerStateWrapper>,
|
||||
session: State<'_, MediaSessionManagerWrapper>,
|
||||
@@ -435,6 +436,7 @@ pub async fn player_play_item(
|
||||
/// @req: UR-015 - View and manage current audio queue
|
||||
/// @req: DR-005 - Queue manager with shuffle, repeat, history
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn player_play_queue(
|
||||
player: State<'_, PlayerStateWrapper>,
|
||||
session: State<'_, MediaSessionManagerWrapper>,
|
||||
@@ -511,6 +513,7 @@ pub async fn player_play_queue(
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn player_play(
|
||||
player: State<'_, PlayerStateWrapper>,
|
||||
playback_mode: State<'_, super::playback_mode::PlaybackModeManagerWrapper>,
|
||||
@@ -538,6 +541,7 @@ pub async fn player_play(
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn player_pause(
|
||||
player: State<'_, PlayerStateWrapper>,
|
||||
playback_mode: State<'_, super::playback_mode::PlaybackModeManagerWrapper>,
|
||||
@@ -565,6 +569,7 @@ pub async fn player_pause(
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn player_toggle(
|
||||
player: State<'_, PlayerStateWrapper>,
|
||||
playback_mode: State<'_, super::playback_mode::PlaybackModeManagerWrapper>,
|
||||
@@ -592,6 +597,7 @@ pub async fn player_toggle(
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn player_stop(
|
||||
player: State<'_, PlayerStateWrapper>,
|
||||
session: State<'_, MediaSessionManagerWrapper>,
|
||||
@@ -651,6 +657,7 @@ pub async fn player_stop(
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn player_next(
|
||||
player: State<'_, PlayerStateWrapper>,
|
||||
session: State<'_, MediaSessionManagerWrapper>,
|
||||
@@ -703,6 +710,7 @@ pub async fn player_next(
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn player_previous(
|
||||
player: State<'_, PlayerStateWrapper>,
|
||||
session: State<'_, MediaSessionManagerWrapper>,
|
||||
@@ -753,6 +761,7 @@ pub async fn player_previous(
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn player_seek(
|
||||
player: State<'_, PlayerStateWrapper>,
|
||||
playback_mode: State<'_, super::playback_mode::PlaybackModeManagerWrapper>,
|
||||
@@ -792,6 +801,7 @@ pub async fn player_seek(
|
||||
/// 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.
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn player_seek_video(
|
||||
player: State<'_, PlayerStateWrapper>,
|
||||
repository_manager: State<'_, super::repository::RepositoryManagerWrapper>,
|
||||
@@ -929,6 +939,7 @@ pub async fn player_seek_video(
|
||||
/// Switch audio track - handles both HTML5 (stream reload) and native (direct switch)
|
||||
/// Note: Frontend should handle saving series preferences after this command succeeds
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn player_switch_audio_track(
|
||||
player: State<'_, PlayerStateWrapper>,
|
||||
repository_manager: State<'_, super::repository::RepositoryManagerWrapper>,
|
||||
@@ -984,6 +995,7 @@ pub async fn player_switch_audio_track(
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn player_set_audio_track(
|
||||
player: State<'_, PlayerStateWrapper>,
|
||||
stream_index: i32,
|
||||
@@ -994,6 +1006,7 @@ pub async fn player_set_audio_track(
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn player_set_subtitle_track(
|
||||
player: State<'_, PlayerStateWrapper>,
|
||||
stream_index: Option<i32>,
|
||||
@@ -1004,6 +1017,7 @@ pub async fn player_set_subtitle_track(
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn player_set_volume(
|
||||
player: State<'_, PlayerStateWrapper>,
|
||||
playback_mode: State<'_, super::playback_mode::PlaybackModeManagerWrapper>,
|
||||
@@ -1034,6 +1048,7 @@ pub async fn player_set_volume(
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn player_toggle_mute(
|
||||
player: State<'_, PlayerStateWrapper>,
|
||||
playback_mode: State<'_, super::playback_mode::PlaybackModeManagerWrapper>,
|
||||
@@ -1062,6 +1077,7 @@ pub async fn player_toggle_mute(
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn player_toggle_shuffle(
|
||||
player: State<'_, PlayerStateWrapper>,
|
||||
) -> Result<QueueStatus, String> {
|
||||
@@ -1072,6 +1088,7 @@ pub async fn player_toggle_shuffle(
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn player_cycle_repeat(player: State<'_, PlayerStateWrapper>) -> Result<QueueStatus, String> {
|
||||
let controller = player.0.lock().await;
|
||||
controller.cycle_repeat();
|
||||
@@ -1080,6 +1097,7 @@ pub async fn player_cycle_repeat(player: State<'_, PlayerStateWrapper>) -> Resul
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn player_get_status(
|
||||
player: State<'_, PlayerStateWrapper>,
|
||||
playback_mode: State<'_, super::playback_mode::PlaybackModeManagerWrapper>,
|
||||
@@ -1165,6 +1183,7 @@ pub async fn player_get_status(
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn player_get_queue(player: State<'_, PlayerStateWrapper>) -> Result<QueueStatus, String> {
|
||||
let controller = player.0.lock().await;
|
||||
Ok(get_queue_status(&controller))
|
||||
@@ -1216,6 +1235,7 @@ pub(super) fn get_queue_status(controller: &PlayerController) -> QueueStatus {
|
||||
|
||||
/// Play a track from an album - backend fetches all album tracks and builds queue
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn player_play_album_track(
|
||||
player: State<'_, PlayerStateWrapper>,
|
||||
session: State<'_, MediaSessionManagerWrapper>,
|
||||
@@ -1383,6 +1403,7 @@ pub async fn player_play_album_track(
|
||||
|
||||
/// Play tracks by ID - backend fetches all metadata
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn player_play_tracks(
|
||||
player: State<'_, PlayerStateWrapper>,
|
||||
session: State<'_, MediaSessionManagerWrapper>,
|
||||
@@ -1521,7 +1542,7 @@ pub async fn player_play_tracks(
|
||||
}
|
||||
|
||||
/// Response for preload operation
|
||||
#[derive(Debug, Serialize)]
|
||||
#[derive(specta::Type, Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PreloadResult {
|
||||
/// Number of tracks queued for preload
|
||||
@@ -1535,6 +1556,7 @@ pub struct PreloadResult {
|
||||
/// Preload upcoming tracks from the queue
|
||||
/// This queues background downloads for the next N tracks that aren't already downloaded
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn player_preload_upcoming(
|
||||
player: State<'_, PlayerStateWrapper>,
|
||||
db: State<'_, DatabaseWrapper>,
|
||||
@@ -1671,6 +1693,7 @@ fn sanitize_filename(name: &str) -> String {
|
||||
|
||||
/// Update SmartCache configuration
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn player_set_cache_config(
|
||||
smart_cache: State<'_, SmartCacheWrapper>,
|
||||
config: CacheConfig,
|
||||
@@ -1682,6 +1705,7 @@ pub async fn player_set_cache_config(
|
||||
|
||||
/// Get current SmartCache configuration
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn player_get_cache_config(
|
||||
smart_cache: State<'_, SmartCacheWrapper>,
|
||||
) -> Result<CacheConfig, String> {
|
||||
@@ -1691,6 +1715,7 @@ pub async fn player_get_cache_config(
|
||||
|
||||
/// Configure Jellyfin API client for automatic playback reporting
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn player_configure_jellyfin(
|
||||
player: State<'_, PlayerStateWrapper>,
|
||||
server_url: String,
|
||||
@@ -1717,6 +1742,7 @@ pub async fn player_configure_jellyfin(
|
||||
|
||||
/// Disable Jellyfin automatic playback reporting
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn player_disable_jellyfin(
|
||||
player: State<'_, PlayerStateWrapper>,
|
||||
) -> Result<(), String> {
|
||||
|
||||
Reference in New Issue
Block a user