Files
jellytau/src-tauri/src/player/autoplay.rs
T
dtourolle ada3ed64ab 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.
2026-06-20 18:20:25 +02:00

110 lines
3.4 KiB
Rust

// Autoplay decision logic
// TRACES: UR-023, UR-026 | DR-047, DR-048, DR-029
use serde::{Deserialize, Serialize};
use crate::repository::types::MediaItem;
/// Autoplay decision result - determines what happens after playback ends
#[derive(specta::Type, Debug, Clone, Serialize)]
#[serde(tag = "action", rename_all = "camelCase")]
pub enum AutoplayDecision {
/// Stop playback (no next item or timer expired)
Stop,
/// Advance to next track in queue (for audio/movies)
AdvanceToNext,
/// Show next episode popup with countdown
ShowNextEpisodePopup {
current_episode: MediaItem,
next_episode: MediaItem,
countdown_seconds: u32,
auto_advance: bool,
},
}
/// Autoplay settings (controls next episode behavior)
#[derive(specta::Type, Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AutoplaySettings {
/// Whether autoplay is enabled for next episodes
pub enabled: bool,
/// Countdown duration in seconds before auto-playing next episode
pub countdown_seconds: u32,
/// Maximum number of episodes to auto-play consecutively (0 = unlimited)
#[serde(default)]
pub max_episodes: u32,
}
impl Default for AutoplaySettings {
fn default() -> Self {
Self {
enabled: true,
countdown_seconds: 10,
max_episodes: 0,
}
}
}
impl AutoplaySettings {
/// Validate and clamp countdown seconds to reasonable range (5-30 seconds)
pub fn with_validated_countdown(mut self) -> Self {
self.countdown_seconds = self.countdown_seconds.clamp(5, 30);
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_autoplay_settings_defaults() {
let settings = AutoplaySettings::default();
assert!(settings.enabled);
assert_eq!(settings.countdown_seconds, 10);
assert_eq!(settings.max_episodes, 0);
}
#[test]
fn test_autoplay_settings_backward_compat() {
// Deserialize old JSON without max_episodes field
let json = r#"{"enabled":true,"countdownSeconds":15}"#;
let settings: AutoplaySettings = serde_json::from_str(json).unwrap();
assert!(settings.enabled);
assert_eq!(settings.countdown_seconds, 15);
assert_eq!(settings.max_episodes, 0); // defaults to 0 (unlimited)
}
#[test]
fn test_autoplay_settings_with_max_episodes() {
let json = r#"{"enabled":true,"countdownSeconds":10,"maxEpisodes":5}"#;
let settings: AutoplaySettings = serde_json::from_str(json).unwrap();
assert_eq!(settings.max_episodes, 5);
}
#[test]
fn test_countdown_validation() {
let settings = AutoplaySettings {
enabled: true,
countdown_seconds: 2, // Too short
max_episodes: 0,
}
.with_validated_countdown();
assert_eq!(settings.countdown_seconds, 5); // Clamped to min
let settings = AutoplaySettings {
enabled: true,
countdown_seconds: 60, // Too long
max_episodes: 0,
}
.with_validated_countdown();
assert_eq!(settings.countdown_seconds, 30); // Clamped to max
let settings = AutoplaySettings {
enabled: true,
countdown_seconds: 15, // Valid
max_episodes: 0,
}
.with_validated_countdown();
assert_eq!(settings.countdown_seconds, 15); // Unchanged
}
}