First working POC

This commit is contained in:
2026-01-26 22:21:54 +01:00
commit cfddc1edea
255 changed files with 77606 additions and 0 deletions
+155
View File
@@ -0,0 +1,155 @@
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
/// Context for the current queue - where did the queue items come from?
/// This is used for remote playback transfer to send album/playlist context.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
#[serde(tag = "type", rename_all = "lowercase")]
pub enum QueueContext {
/// Playing from a specific album
Album {
album_id: String,
album_name: String,
},
/// Playing from a specific playlist
Playlist {
playlist_id: String,
playlist_name: String,
},
/// Custom queue (search results, manual queue, etc.)
/// Will create a temporary playlist on remote transfer
#[default]
Custom,
}
/// Represents a subtitle track
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct SubtitleTrack {
/// Stream index in the media source
pub index: i32,
/// Subtitle URL
pub url: String,
/// Language code (e.g., "eng", "spa")
pub language: Option<String>,
/// Display title
pub label: Option<String>,
/// MIME type (e.g., "text/vtt", "application/x-subrip")
pub mime_type: String,
}
/// Represents a media item that can be played
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct MediaItem {
/// Unique identifier
pub id: String,
/// Display title
pub title: String,
/// Name (alias for title - for frontend compatibility)
#[serde(default, skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
/// Artist name(s) for audio
pub artist: Option<String>,
/// Album name for audio
pub album: Option<String>,
/// Album name (alias - for frontend compatibility)
#[serde(default, skip_serializing_if = "Option::is_none")]
pub album_name: Option<String>,
/// Album ID (Jellyfin ID) for remote transfer context
#[serde(default)]
pub album_id: Option<String>,
/// Artist items with IDs for clickable links
#[serde(default, skip_serializing_if = "Option::is_none")]
pub artist_items: Option<Vec<crate::repository::types::ArtistItem>>,
/// Artists as array of strings (fallback when artist_items not available)
#[serde(default, skip_serializing_if = "Option::is_none")]
pub artists: Option<Vec<String>>,
/// Primary image tag for artwork
#[serde(default, skip_serializing_if = "Option::is_none")]
pub primary_image_tag: Option<String>,
/// Item type (Audio, Movie, Episode, etc.)
#[serde(rename = "type", default, skip_serializing_if = "Option::is_none")]
pub item_type: Option<String>,
/// Playlist ID (Jellyfin ID) for remote transfer context
#[serde(default)]
pub playlist_id: Option<String>,
/// Duration in seconds
pub duration: Option<f64>,
/// URL or path to artwork image
pub artwork_url: Option<String>,
/// Type of media
pub media_type: MediaType,
/// Source of the media
pub source: MediaSource,
/// Video codec (e.g., "h264", "hevc") for video media
#[serde(default)]
pub video_codec: Option<String>,
/// Whether the video requires server-side transcoding
#[serde(default)]
pub needs_transcoding: bool,
/// Video width in pixels
#[serde(default)]
pub video_width: Option<u32>,
/// Video height in pixels
#[serde(default)]
pub video_height: Option<u32>,
/// Available subtitle tracks
#[serde(default)]
pub subtitles: Vec<SubtitleTrack>,
/// Series ID (for TV show episodes) - used for series audio preferences
#[serde(default)]
pub series_id: Option<String>,
/// Server ID - used for series audio preferences
#[serde(default)]
pub server_id: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum MediaType {
Audio,
Video,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(tag = "type", rename_all = "lowercase")]
pub enum MediaSource {
/// Streaming from Jellyfin server
Remote {
stream_url: String,
jellyfin_item_id: String,
},
/// Downloaded/cached locally
Local {
file_path: PathBuf,
/// Original Jellyfin ID for sync-back
jellyfin_item_id: Option<String>,
},
/// Direct URL (e.g., channel plugins)
DirectUrl { url: String },
}
impl MediaItem {
/// Get the Jellyfin item ID if available
pub fn jellyfin_id(&self) -> Option<&str> {
match &self.source {
MediaSource::Remote { jellyfin_item_id, .. } => Some(jellyfin_item_id),
MediaSource::Local { jellyfin_item_id, .. } => jellyfin_item_id.as_deref(),
MediaSource::DirectUrl { .. } => None,
}
}
/// Get the playback URL or file path
///
/// Only available on Android where ExoPlayer needs direct URL access
#[cfg(target_os = "android")]
pub fn playback_url(&self) -> String {
match &self.source {
MediaSource::Remote { stream_url, .. } => stream_url.clone(),
MediaSource::Local { file_path, .. } => {
file_path.to_string_lossy().to_string()
}
MediaSource::DirectUrl { url } => url.clone(),
}
}
}