fix(kiosk): auto-hide OSD, prioritize hardware-decoded formats, clean YouTube share URLs

This commit is contained in:
bart
2026-11-05 22:20:20 +01:00
parent 289f4a4ff5
commit 423bc75c94
10 changed files with 437 additions and 34 deletions
+102 -5
View File
@@ -26,7 +26,7 @@ pub enum PlayableTarget {
OpenFolder { folder_id: String, name: String },
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct MediaItem {
pub id: String,
pub name: String,
@@ -48,6 +48,11 @@ pub struct MediaItem {
pub has_backdrop_image: bool,
pub parent_primary_image_item_id: Option<String>,
pub parent_backdrop_item_id: Option<String>,
pub recursive_item_count: Option<u32>,
pub child_count: Option<u32>,
pub series_id: Option<String>,
pub album_id: Option<String>,
pub album: Option<String>,
}
impl MediaItem {
@@ -604,22 +609,95 @@ impl JellyfinClient {
if let Ok(headers) = self.get_authed_headers().await {
let user_id = self.user_id.read().await.clone().unwrap_or_default();
let url = format!(
"{}/Shows/{}/Episodes?userId={}&fields=Overview,ProductionYear,CommunityRating,ParentBackdropItemId,ParentPrimaryImageItemId,UserData,IndexNumber,ParentIndexNumber,SortName,Genres",
"{}/Shows/{}/Episodes?userId={}&fields=Overview,ProductionYear,CommunityRating,ParentBackdropItemId,ParentPrimaryImageItemId,UserData,IndexNumber,ParentIndexNumber,SortName,Genres,SeriesId",
base_url, show_id, user_id
);
return fetch_endpoint(&self.client, &url, &headers).await;
}
Vec::new()
}
pub async fn fetch_episodes_paged(
&self,
show_id: Option<&str>,
season: Option<u32>,
start: usize,
limit: usize,
) -> (usize, Vec<MediaItem>) {
let base_url = self.config.server_url.trim_end_matches('/');
if let Ok(headers) = self.get_authed_headers().await {
let user_id = self.user_id.read().await.clone().unwrap_or_default();
let url = if let Some(sid) = show_id {
if let Some(s_num) = season {
format!(
"{}/Shows/{}/Episodes?userId={}&season={}&startIndex={}&limit={}&fields=Overview,ProductionYear,CommunityRating,ParentBackdropItemId,ParentPrimaryImageItemId,UserData,IndexNumber,ParentIndexNumber,SortName,Genres,SeriesId",
base_url, sid, user_id, s_num, start, limit
)
} else {
format!(
"{}/Shows/{}/Episodes?userId={}&startIndex={}&limit={}&fields=Overview,ProductionYear,CommunityRating,ParentBackdropItemId,ParentPrimaryImageItemId,UserData,IndexNumber,ParentIndexNumber,SortName,Genres,SeriesId",
base_url, sid, user_id, start, limit
)
}
} else {
format!(
"{}/Users/{}/Items?includeItemTypes=Episode&recursive=true&startIndex={}&limit={}&fields=Overview,ProductionYear,CommunityRating,ParentBackdropItemId,ParentPrimaryImageItemId,UserData,IndexNumber,ParentIndexNumber,SortName,Genres,SeriesId",
base_url, user_id, start, limit
)
};
return fetch_endpoint_paged(&self.client, &url, &headers).await;
}
(0, Vec::new())
}
pub async fn fetch_songs_paged(
&self,
album_id: Option<&str>,
artist_id: Option<&str>,
start: usize,
limit: usize,
) -> (usize, Vec<MediaItem>) {
let base_url = self.config.server_url.trim_end_matches('/');
if let Ok(headers) = self.get_authed_headers().await {
let user_id = self.user_id.read().await.clone().unwrap_or_default();
let url = if let Some(aid) = album_id {
format!(
"{}/Users/{}/Items?parentId={}&includeItemTypes=Audio&startIndex={}&limit={}&fields=Overview,ProductionYear,ParentBackdropItemId,ParentPrimaryImageItemId,UserData,IndexNumber,ParentIndexNumber,SortName,Genres,Album,AlbumId,ArtistItems,Artists,RunTimeTicks",
base_url, user_id, aid, start, limit
)
} else if let Some(arid) = artist_id {
format!(
"{}/Users/{}/Items?artistIds={}&includeItemTypes=Audio&recursive=true&startIndex={}&limit={}&fields=Overview,ProductionYear,ParentBackdropItemId,ParentPrimaryImageItemId,UserData,IndexNumber,ParentIndexNumber,SortName,Genres,Album,AlbumId,ArtistItems,Artists,RunTimeTicks",
base_url, user_id, arid, start, limit
)
} else {
format!(
"{}/Users/{}/Items?includeItemTypes=Audio&recursive=true&startIndex={}&limit={}&fields=Overview,ProductionYear,ParentBackdropItemId,ParentPrimaryImageItemId,UserData,IndexNumber,ParentIndexNumber,SortName,Genres,Album,AlbumId,ArtistItems,Artists,RunTimeTicks",
base_url, user_id, start, limit
)
};
return fetch_endpoint_paged(&self.client, &url, &headers).await;
}
(0, Vec::new())
}
}
async fn fetch_endpoint(client: &reqwest::Client, url: &str, headers: &HeaderMap) -> Vec<MediaItem> {
fetch_endpoint_paged(client, url, headers).await.1
}
async fn fetch_endpoint_paged(client: &reqwest::Client, url: &str, headers: &HeaderMap) -> (usize, Vec<MediaItem>) {
if let Ok(resp) = client.get(url).headers(headers.clone()).send().await {
if let Ok(json) = resp.json::<serde_json::Value>().await {
return parse_items(&json);
let items = parse_items(&json);
let total = json["TotalRecordCount"]
.as_u64()
.map(|n| n as usize)
.unwrap_or(items.len());
return (total, items);
}
}
Vec::new()
(0, Vec::new())
}
fn parse_items(json: &serde_json::Value) -> Vec<MediaItem> {
@@ -630,7 +708,16 @@ fn parse_items(json: &serde_json::Value) -> Vec<MediaItem> {
let name = it["Name"].as_str().unwrap_or("Onbekend").to_string();
let item_type = it["Type"].as_str().unwrap_or("Video").to_string();
let series_name = it["SeriesName"].as_str().map(|s| s.to_string());
let artist_name = it["AlbumArtist"].as_str().or_else(|| it["Artist"].as_str()).map(|s| s.to_string());
let artist_name = it["AlbumArtist"]
.as_str()
.or_else(|| it["Artist"].as_str())
.map(|s| s.to_string())
.or_else(|| {
it["Artists"].as_array().and_then(|arr| arr.first().and_then(|a| a.as_str())).map(|s| s.to_string())
})
.or_else(|| {
it["ArtistItems"].as_array().and_then(|arr| arr.first().and_then(|a| a["Name"].as_str())).map(|s| s.to_string())
});
let genre = it["Genres"].as_array().and_then(|arr| {
let list: Vec<&str> = arr.iter().filter_map(|g| g.as_str()).collect();
if list.is_empty() {
@@ -661,6 +748,11 @@ fn parse_items(json: &serde_json::Value) -> Vec<MediaItem> {
let index_number = it["IndexNumber"].as_u64().map(|n| n as u32);
let parent_index_number = it["ParentIndexNumber"].as_u64().map(|n| n as u32);
let season_name = it["SeasonName"].as_str().map(|s| s.to_string());
let recursive_item_count = it["RecursiveItemCount"].as_u64().map(|n| n as u32);
let child_count = it["ChildCount"].as_u64().map(|n| n as u32);
let series_id = it["SeriesId"].as_str().map(|s| s.to_string());
let album_id = it["AlbumId"].as_str().map(|s| s.to_string());
let album = it["Album"].as_str().map(|s| s.to_string());
if !id.is_empty() {
out.push(MediaItem {
@@ -684,6 +776,11 @@ fn parse_items(json: &serde_json::Value) -> Vec<MediaItem> {
has_backdrop_image,
parent_primary_image_item_id,
parent_backdrop_item_id,
recursive_item_count,
child_count,
series_id,
album_id,
album,
});
}
}