Library screens:
- Add dedicated music, TV, and movie landing pages (hero banner +
horizontal carousels) backed by new music/tv/movies stores.
- Route tvshows libraries to /library/tv; surface rediscover ("haven't
listened to in a while") albums via a new repository method across
online/offline/hybrid repos plus the repository_get_rediscover_albums
command.
- Add an A-Z jump bar for long alphabetically-sorted lists, with grid
index anchors in LibraryGrid/LibraryListView/TrackList.
- Filter the "Podcasts" folder out of music library queries.
Downloads:
- Add a backend queue pump: enqueue_download / enqueue_video_downloads
persist the resolved stream URL + target dir on each row (migration
017), and the pump starts up to max_concurrent and drains the rest
automatically as slots free, instead of the frontend silently dropping
items past the concurrency limit. Album/series/season buttons now
enqueue rather than calling start_download directly.
Other fixes:
- Hybrid search now returns instant cache results and pushes the merged
cache+server union via a request-id-tagged search-event, so superseded
queries can't clobber fresher results.
- URL-encode SearchTerm / genres / item types in online repo requests.
- Android: pause on audio-becoming-noisy (headphone/BT disconnect).
268 lines
8.6 KiB
Rust
268 lines
8.6 KiB
Rust
pub mod types;
|
|
pub mod online;
|
|
pub mod offline;
|
|
pub mod hybrid;
|
|
|
|
pub use types::*;
|
|
pub use online::OnlineRepository;
|
|
pub use offline::OfflineRepository;
|
|
pub use hybrid::HybridRepository;
|
|
|
|
use async_trait::async_trait;
|
|
|
|
/// Repository trait for media access (online, offline, or hybrid)
|
|
///
|
|
/// @req: UR-002 - Access media when online or offline
|
|
/// @req: UR-007 - Navigate media in library
|
|
/// @req: UR-008 - Search media across libraries
|
|
/// @req: IR-010 - Jellyfin API client for library browsing
|
|
/// @req: DR-012 - Local database for media metadata cache
|
|
/// @req: DR-013 - Repository pattern for online/offline data access
|
|
#[async_trait]
|
|
pub trait MediaRepository: Send + Sync {
|
|
/// Get all libraries
|
|
///
|
|
/// @req: UR-007 - Navigate media in library
|
|
/// @req: JA-003 - Get user library views
|
|
async fn get_libraries(&self) -> Result<Vec<Library>, RepoError>;
|
|
|
|
/// Get items in a library or parent
|
|
///
|
|
/// @req: UR-007 - Navigate media in library
|
|
/// @req: JA-004 - Get library items (paginated)
|
|
async fn get_items(
|
|
&self,
|
|
parent_id: &str,
|
|
options: Option<GetItemsOptions>,
|
|
) -> Result<SearchResult, RepoError>;
|
|
|
|
/// Get a single item by ID
|
|
///
|
|
/// @req: UR-007 - Navigate media in library
|
|
/// @req: JA-005 - Get item details and metadata
|
|
async fn get_item(&self, item_id: &str) -> Result<MediaItem, RepoError>;
|
|
|
|
/// Get latest items in a library
|
|
///
|
|
/// @req: UR-024 - View recently added content on server
|
|
/// @req: JA-016 - Get recently added items
|
|
async fn get_latest_items(
|
|
&self,
|
|
parent_id: &str,
|
|
limit: Option<usize>,
|
|
) -> Result<Vec<MediaItem>, RepoError>;
|
|
|
|
/// Get resume items (continue watching/listening)
|
|
///
|
|
/// @req: UR-019 - Resume playback from where you left off
|
|
/// @req: UR-023 - View "Next Up" / Continue Watching on home screen
|
|
/// @req: JA-015 - Get "Continue Watching" items
|
|
async fn get_resume_items(
|
|
&self,
|
|
parent_id: Option<&str>,
|
|
limit: Option<usize>,
|
|
) -> Result<Vec<MediaItem>, RepoError>;
|
|
|
|
/// Get next up episodes
|
|
///
|
|
/// @req: UR-023 - View "Next Up" / Continue Watching; auto-play next episode
|
|
/// @req: JA-014 - Get "Next Up" items
|
|
async fn get_next_up_episodes(
|
|
&self,
|
|
series_id: Option<&str>,
|
|
limit: Option<usize>,
|
|
) -> Result<Vec<MediaItem>, RepoError>;
|
|
|
|
/// Get recently played audio
|
|
async fn get_recently_played_audio(
|
|
&self,
|
|
limit: Option<usize>,
|
|
) -> Result<Vec<MediaItem>, RepoError>;
|
|
|
|
/// Get albums the user has played, but not recently ("rediscover" / haven't
|
|
/// listened to in a while). Returns albums sorted by least-recently played
|
|
/// first, optionally restricted to a parent library.
|
|
async fn get_rediscover_albums(
|
|
&self,
|
|
parent_id: Option<&str>,
|
|
limit: Option<usize>,
|
|
) -> Result<Vec<MediaItem>, RepoError>;
|
|
|
|
/// Get resume movies
|
|
async fn get_resume_movies(&self, limit: Option<usize>) -> Result<Vec<MediaItem>, RepoError>;
|
|
|
|
/// Get genres
|
|
async fn get_genres(&self, parent_id: Option<&str>) -> Result<Vec<Genre>, RepoError>;
|
|
|
|
/// Search for items
|
|
///
|
|
/// @req: UR-008 - Search media across libraries
|
|
/// @req: JA-006 - Search across libraries
|
|
async fn search(
|
|
&self,
|
|
query: &str,
|
|
options: Option<SearchOptions>,
|
|
) -> Result<SearchResult, RepoError>;
|
|
|
|
/// Get playback info for streaming
|
|
///
|
|
/// @req: UR-003 - Play videos
|
|
/// @req: UR-004 - Play audio uninterrupted
|
|
/// @req: JA-007 - Get playback info and stream URL
|
|
async fn get_playback_info(&self, item_id: &str) -> Result<PlaybackInfo, RepoError>;
|
|
|
|
/// Get audio stream URL for a track
|
|
///
|
|
/// @req: UR-004 - Play audio uninterrupted
|
|
/// @req: JA-007 - Get playback info and stream URL
|
|
async fn get_audio_stream_url(&self, item_id: &str) -> Result<String, RepoError>;
|
|
|
|
/// Report playback start
|
|
///
|
|
/// @req: UR-025 - Sync watch history and progress back to Jellyfin
|
|
/// @req: JA-010 - Report playback start
|
|
async fn report_playback_start(
|
|
&self,
|
|
item_id: &str,
|
|
position_ticks: i64,
|
|
) -> Result<(), RepoError>;
|
|
|
|
/// Report playback progress
|
|
///
|
|
/// @req: UR-025 - Sync watch history and progress back to Jellyfin
|
|
/// @req: JA-011 - Report playback progress (periodic)
|
|
async fn report_playback_progress(
|
|
&self,
|
|
item_id: &str,
|
|
position_ticks: i64,
|
|
) -> Result<(), RepoError>;
|
|
|
|
/// Report playback stopped
|
|
///
|
|
/// @req: UR-025 - Sync watch history and progress back to Jellyfin
|
|
/// @req: JA-012 - Report playback stopped
|
|
async fn report_playback_stopped(
|
|
&self,
|
|
item_id: &str,
|
|
position_ticks: i64,
|
|
) -> Result<(), RepoError>;
|
|
|
|
/// Get image URL (synchronous - just constructs URL)
|
|
fn get_image_url(
|
|
&self,
|
|
item_id: &str,
|
|
image_type: ImageType,
|
|
options: Option<ImageOptions>,
|
|
) -> String;
|
|
|
|
/// Get subtitle URL (synchronous - just constructs URL)
|
|
/// Called by frontend via Tauri invoke (getSubtitleUrl in VideoPlayer.svelte)
|
|
#[allow(dead_code)]
|
|
fn get_subtitle_url(
|
|
&self,
|
|
item_id: &str,
|
|
media_source_id: &str,
|
|
stream_index: i32,
|
|
format: &str,
|
|
) -> String;
|
|
|
|
/// Get video download URL (synchronous - just constructs URL)
|
|
/// Called by frontend via Tauri invoke (getVideoDownloadUrl in VideoDownloadButton.svelte)
|
|
#[allow(dead_code)]
|
|
fn get_video_download_url(
|
|
&self,
|
|
item_id: &str,
|
|
quality: &str,
|
|
media_source_id: Option<&str>,
|
|
) -> String;
|
|
|
|
/// Mark item as favorite
|
|
async fn mark_favorite(&self, item_id: &str) -> Result<(), RepoError>;
|
|
|
|
/// Unmark item as favorite
|
|
async fn unmark_favorite(&self, item_id: &str) -> Result<(), RepoError>;
|
|
|
|
/// Get person details
|
|
async fn get_person(&self, person_id: &str) -> Result<MediaItem, RepoError>;
|
|
|
|
/// Get items by person (filmography)
|
|
async fn get_items_by_person(
|
|
&self,
|
|
person_id: &str,
|
|
options: Option<GetItemsOptions>,
|
|
) -> Result<SearchResult, RepoError>;
|
|
|
|
/// Get similar/related items for a movie or show
|
|
///
|
|
/// @req: UR-009 - Discover similar content based on current item
|
|
async fn get_similar_items(
|
|
&self,
|
|
item_id: &str,
|
|
limit: Option<usize>,
|
|
) -> Result<SearchResult, RepoError>;
|
|
|
|
// ===== Playlist Methods =====
|
|
|
|
/// Create a new playlist on the server
|
|
///
|
|
/// @req: UR-014 - Make and edit playlists of music that sync back to Jellyfin
|
|
/// @req: JA-019 - Get/create/update playlists
|
|
async fn create_playlist(
|
|
&self,
|
|
name: &str,
|
|
item_ids: &[String],
|
|
) -> Result<PlaylistCreatedResult, RepoError>;
|
|
|
|
/// Delete a playlist
|
|
///
|
|
/// @req: UR-014 - Make and edit playlists of music that sync back to Jellyfin
|
|
/// @req: JA-019 - Get/create/update playlists
|
|
async fn delete_playlist(&self, playlist_id: &str) -> Result<(), RepoError>;
|
|
|
|
/// Rename a playlist
|
|
///
|
|
/// @req: UR-014 - Make and edit playlists of music that sync back to Jellyfin
|
|
/// @req: JA-019 - Get/create/update playlists
|
|
async fn rename_playlist(&self, playlist_id: &str, name: &str) -> Result<(), RepoError>;
|
|
|
|
/// Get playlist items with PlaylistItemId (needed for remove/reorder)
|
|
///
|
|
/// @req: UR-014 - Make and edit playlists of music that sync back to Jellyfin
|
|
/// @req: JA-019 - Get/create/update playlists
|
|
async fn get_playlist_items(
|
|
&self,
|
|
playlist_id: &str,
|
|
) -> Result<Vec<PlaylistEntry>, RepoError>;
|
|
|
|
/// Add items to a playlist
|
|
///
|
|
/// @req: UR-014 - Make and edit playlists of music that sync back to Jellyfin
|
|
/// @req: JA-020 - Add/remove items from playlist
|
|
async fn add_to_playlist(
|
|
&self,
|
|
playlist_id: &str,
|
|
item_ids: &[String],
|
|
) -> Result<(), RepoError>;
|
|
|
|
/// Remove items from a playlist using entry IDs (PlaylistItemId, NOT media item IDs)
|
|
///
|
|
/// @req: UR-014 - Make and edit playlists of music that sync back to Jellyfin
|
|
/// @req: JA-020 - Add/remove items from playlist
|
|
async fn remove_from_playlist(
|
|
&self,
|
|
playlist_id: &str,
|
|
entry_ids: &[String],
|
|
) -> Result<(), RepoError>;
|
|
|
|
/// Move a playlist item to a new position
|
|
///
|
|
/// @req: UR-014 - Make and edit playlists of music that sync back to Jellyfin
|
|
/// @req: JA-020 - Add/remove items from playlist
|
|
async fn move_playlist_item(
|
|
&self,
|
|
playlist_id: &str,
|
|
item_id: &str,
|
|
new_index: u32,
|
|
) -> Result<(), RepoError>;
|
|
}
|