From 558fb41833952d232a3eefb2201550d2fdd7be66 Mon Sep 17 00:00:00 2001 From: Gauvain Date: Wed, 27 May 2026 16:52:08 +0200 Subject: [PATCH] fix(downloads): refresh BaseItemDto for Chapters before queuing offline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some screens fetch items with a fields filter (e.g. series detail uses ["MediaSources", "MediaStreams", "Overview", "Trickplay"]) and pass the resulting DTO straight into the download flow. Jellyfin only returns Chapters in that case when explicitly listed, so the snapshot we store under DownloadedItem.item carries Chapters: undefined and the offline player renders no ticks / list / current-chapter label. initiateDownload now re-fetches the item via getUserLibraryApi.getItem (no fields filter -> full DTO incl. Chapters) when Chapters is missing, and uses the enriched item for both getDownloadUrl and the snapshot. If the refresh call fails we log and fall back to the original item — the download itself still proceeds. Trickplay offline already worked (useTrickplay reads trickPlayData.path from the downloaded sheets). --- components/DownloadItem.tsx | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/components/DownloadItem.tsx b/components/DownloadItem.tsx index e50b4efc..4adfabc0 100644 --- a/components/DownloadItem.tsx +++ b/components/DownloadItem.tsx @@ -9,6 +9,7 @@ import type { BaseItemDto, MediaSourceInfo, } from "@jellyfin/sdk/lib/generated-client/models"; +import { getUserLibraryApi } from "@jellyfin/sdk/lib/utils/api/user-library-api"; import { type Href } from "expo-router"; import { t } from "i18next"; import { useAtom } from "jotai"; @@ -195,9 +196,30 @@ export const DownloadItems: React.FC = ({ ); } const downloadDetailsPromises = items.map(async (item) => { + // Ensure the snapshot we store offline carries the Chapters array. + // Page-level fetches sometimes use a fields filter that omits it; the + // offline player would then render no chapter ticks / list. + let itemForDownload = item; + if (!itemForDownload.Chapters && itemForDownload.Id) { + try { + const enriched = await getUserLibraryApi(api).getItem({ + itemId: itemForDownload.Id, + userId: user.Id!, + }); + if (enriched.data) { + itemForDownload = enriched.data; + } + } catch (e) { + console.warn( + "[DownloadItem] failed to refresh item for Chapters, falling back to original", + e, + ); + } + } + const { mediaSource, audioIndex, subtitleIndex } = itemsNotDownloaded.length > 1 - ? getDefaultPlaySettings(item, settings!) + ? getDefaultPlaySettings(itemForDownload, settings!) : { mediaSource: selectedOptions?.mediaSource, audioIndex: selectedOptions?.audioIndex, @@ -206,7 +228,7 @@ export const DownloadItems: React.FC = ({ const downloadDetails = await getDownloadUrl({ api, - item, + item: itemForDownload, userId: user.Id!, mediaSource: mediaSource!, audioStreamIndex: audioIndex ?? -1, @@ -218,7 +240,7 @@ export const DownloadItems: React.FC = ({ return { url: downloadDetails?.url, - item, + item: itemForDownload, mediaSource: downloadDetails?.mediaSource, }; });