diff --git a/components/home/Home.tsx b/components/home/Home.tsx index 060dafedb..eed057b0b 100644 --- a/components/home/Home.tsx +++ b/components/home/Home.tsx @@ -221,22 +221,33 @@ const HomeMobile = () => { queryKey, queryFn: async ({ pageParam = 0 }) => { if (!api) return []; - // getLatestMedia doesn't support startIndex, so we fetch all and slice client-side - const allData = - ( - await getUserLibraryApi(api).getLatestMedia({ - userId: user?.Id, - limit: 10, - fields: ["PrimaryImageAspectRatio"], - imageTypeLimit: 1, - enableImageTypes: ["Primary", "Backdrop", "Thumb"], - includeItemTypes, - parentId, - }) - ).data || []; - - // Simulate pagination by slicing - return allData.slice(pageParam, pageParam + pageSize); + // Use getItems (not getLatestMedia) so we get item-level results + // filtered by type from a specific library. getLatestMedia is + // episode-oriented and groups results, which drops Series when + // combined with a parentId + includeItemTypes filter. + // + // The specific reason for this is jellyfin 12.0 returns episodes, seasons, or shows, + // but we only handle shows in our recently added in [shows] section. So we need to filter by type at the item level. + // + // For Series we sort by DateLastContentAdded so shows bubble up when + // a new episode is added (series cards for new episodes, matching how + // Jellyfin's "Latest" row worked pre-12.0). Movies use DateCreated. + const response = await getItemsApi(api).getItems({ + userId: user?.Id, + parentId, + includeItemTypes, + recursive: true, + sortBy: includeItemTypes.includes("Series") + ? ["DateLastContentAdded"] + : ["DateCreated"], + sortOrder: ["Descending"], + startIndex: pageParam, + limit: pageSize, + fields: ["PrimaryImageAspectRatio"], + imageTypeLimit: 1, + enableImageTypes: ["Primary", "Backdrop", "Thumb"], + }); + return response.data.Items || []; }, type: "InfiniteScrollingCollectionList", pageSize, @@ -250,9 +261,7 @@ const HomeMobile = () => { const latestMediaViews = collections.map((c) => { const includeItemTypes: BaseItemKind[] = - c.CollectionType === "tvshows" || c.CollectionType === "movies" - ? [] - : ["Movie"]; + c.CollectionType === "tvshows" ? ["Series"] : ["Movie"]; const title = t("home.recently_added_in", { libraryName: c.Name }); const queryKey: string[] = [ "home", diff --git a/providers/WebSocketProvider.tsx b/providers/WebSocketProvider.tsx index c704d3736..516f1c388 100644 --- a/providers/WebSocketProvider.tsx +++ b/providers/WebSocketProvider.tsx @@ -173,7 +173,7 @@ export const WebSocketProvider = ({ children }: WebSocketProviderProps) => { const protocol = api.basePath.includes("https") ? "wss" : "ws"; const url = `${protocol}://${api.basePath .replace("https://", "") - .replace("http://", "")}/socket?api_key=${ + .replace("http://", "")}/socket?ApiKey=${ api.accessToken }&deviceId=${deviceId}`; diff --git a/utils/jellyfin/audio/getAudioStreamUrl.ts b/utils/jellyfin/audio/getAudioStreamUrl.ts index f8eb26292..b35b56134 100644 --- a/utils/jellyfin/audio/getAudioStreamUrl.ts +++ b/utils/jellyfin/audio/getAudioStreamUrl.ts @@ -52,7 +52,7 @@ export const getAudioStreamUrl = async ( container: mediaSource?.Container || "mp3", mediaSourceId: mediaSource?.Id || "", deviceId: api.deviceInfo.id, - api_key: api.accessToken, + ApiKey: api.accessToken, userId, }); diff --git a/utils/jellyfin/media/getDownloadUrl.ts b/utils/jellyfin/media/getDownloadUrl.ts index 989dc3656..849d236c6 100644 --- a/utils/jellyfin/media/getDownloadUrl.ts +++ b/utils/jellyfin/media/getDownloadUrl.ts @@ -50,7 +50,7 @@ export const getDownloadUrl = async ({ if (maxBitrate.key === "Max" && !streamDetails?.mediaSource?.TranscodingUrl) { console.log("Downloading item directly"); return { - url: `${api.basePath}/Items/${mediaSource.Id}/Download?api_key=${api.accessToken}`, + url: `${api.basePath}/Items/${mediaSource.Id}/Download?ApiKey=${api.accessToken}`, mediaSource: streamDetails?.mediaSource ?? null, }; } diff --git a/utils/jellyfin/media/getPlaybackUrl.ts b/utils/jellyfin/media/getPlaybackUrl.ts index 0fd23d0c6..60ebafb78 100644 --- a/utils/jellyfin/media/getPlaybackUrl.ts +++ b/utils/jellyfin/media/getPlaybackUrl.ts @@ -40,7 +40,7 @@ export const getPlaybackUrl = async ( const queryParams = new URLSearchParams({ deviceId: api.deviceInfo?.id || "", - api_key: api.accessToken || "", + ApiKey: api.accessToken || "", Tag: ETag || "", MediaSourceId: Id || "", }); diff --git a/utils/jellyfin/media/getStreamUrl.ts b/utils/jellyfin/media/getStreamUrl.ts index 8fe02df0c..a1ce25379 100644 --- a/utils/jellyfin/media/getStreamUrl.ts +++ b/utils/jellyfin/media/getStreamUrl.ts @@ -73,7 +73,7 @@ const getPlaybackUrl = ( subtitleStreamIndex: params.subtitleStreamIndex?.toString() || "", audioStreamIndex: params.audioStreamIndex?.toString() || "", deviceId: params.deviceId || api.deviceInfo.id, - api_key: api.accessToken, + ApiKey: api.accessToken, startTimeTicks: params.startTimeTicks?.toString() || "0", maxStreamingBitrate: params.maxStreamingBitrate?.toString() || "", userId: params.userId, diff --git a/utils/trickplay.ts b/utils/trickplay.ts index aeb19bc98..3d12acd60 100644 --- a/utils/trickplay.ts +++ b/utils/trickplay.ts @@ -61,5 +61,5 @@ export const generateTrickplayUrl = (item: BaseItemDto, sheetIndex: number) => { const api = store.get(apiAtom); const resolution = getTrickplayInfo(item)?.resolution; if (!resolution || !api) return null; - return `${api.basePath}/Videos/${item.Id}/Trickplay/${resolution}/${sheetIndex}.jpg?api_key=${api.accessToken}`; + return `${api.basePath}/Videos/${item.Id}/Trickplay/${resolution}/${sheetIndex}.jpg?ApiKey=${api.accessToken}`; };