diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 06659768a6..0f2420a648 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -61,6 +61,7 @@ - [Rob Farraher](https://github.com/farraherbg) - [Pier-Luc Ducharme](https://github.com/pl-ducharme) - [Anantharaju S](https://github.com/Anantharajus) + - [Merlin Sievers](https://github.com/dann-merlin) # Emby Contributors diff --git a/src/components/notifications/notifications.js b/src/components/notifications/notifications.js index 16c8312867..2d11691e95 100644 --- a/src/components/notifications/notifications.js +++ b/src/components/notifications/notifications.js @@ -2,6 +2,7 @@ import serverNotifications from '../../scripts/serverNotifications'; import { playbackManager } from '../playback/playbackmanager'; import Events from '../../utils/events.ts'; import globalize from '../../scripts/globalize'; +import { getItems } from '../../utils/jellyfin-apiclient/getItems.ts'; import NotificationIcon from './notificationicon.png'; @@ -130,7 +131,7 @@ function onLibraryChanged(data, apiClient) { newItems.length = 12; } - apiClient.getItems(apiClient.getCurrentUserId(), { + getItems(apiClient, apiClient.getCurrentUserId(), { Recursive: true, Limit: 3, diff --git a/src/components/playback/playbackmanager.js b/src/components/playback/playbackmanager.js index 00030e3218..392c6ceafb 100644 --- a/src/components/playback/playbackmanager.js +++ b/src/components/playback/playbackmanager.js @@ -13,6 +13,7 @@ import ServerConnections from '../ServerConnections'; import alert from '../alert'; import { PluginType } from '../../types/plugin.ts'; import { includesAny } from '../../utils/container.ts'; +import { getItems } from '../../utils/jellyfin-apiclient/getItems.ts'; const UNLIMITED_ITEMS = -1; @@ -127,7 +128,7 @@ function getItemsForPlayback(serverId, query) { query.EnableTotalRecordCount = false; query.CollapseBoxSetItems = false; - return apiClient.getItems(apiClient.getCurrentUserId(), query); + return getItems(apiClient, apiClient.getCurrentUserId(), query); } } diff --git a/src/plugins/chromecastPlayer/plugin.js b/src/plugins/chromecastPlayer/plugin.js index 95bee3838b..a656337d63 100644 --- a/src/plugins/chromecastPlayer/plugin.js +++ b/src/plugins/chromecastPlayer/plugin.js @@ -7,6 +7,7 @@ import ServerConnections from '../../components/ServerConnections'; import alert from '../../components/alert'; import { PluginType } from '../../types/plugin.ts'; import Events from '../../utils/events.ts'; +import { getItems } from '../../utils/jellyfin-apiclient/getItems.ts'; // Based on https://github.com/googlecast/CastVideos-chrome/blob/master/CastVideos.js @@ -482,7 +483,7 @@ function getItemsForPlayback(apiClient, query) { query.ExcludeLocationTypes = 'Virtual'; query.EnableTotalRecordCount = false; - return apiClient.getItems(userId, query); + return getItems(apiClient, userId, query); } } diff --git a/src/plugins/syncPlay/core/Helper.js b/src/plugins/syncPlay/core/Helper.js index 38cdf179e8..47f25578a3 100644 --- a/src/plugins/syncPlay/core/Helper.js +++ b/src/plugins/syncPlay/core/Helper.js @@ -4,6 +4,7 @@ */ import Events from '../../../utils/events.ts'; +import { getItems } from '../../../utils/jellyfin-apiclient/getItems.ts'; /** * Constants @@ -88,7 +89,7 @@ export function getItemsForPlayback(apiClient, query) { query.EnableTotalRecordCount = false; query.CollapseBoxSetItems = false; - return apiClient.getItems(apiClient.getCurrentUserId(), query); + return getItems(apiClient, apiClient.getCurrentUserId(), query); } } diff --git a/src/utils/jellyfin-apiclient/getItems.ts b/src/utils/jellyfin-apiclient/getItems.ts new file mode 100644 index 0000000000..dee681a5d5 --- /dev/null +++ b/src/utils/jellyfin-apiclient/getItems.ts @@ -0,0 +1,74 @@ +import type { BaseItemDtoQueryResult } from '@jellyfin/sdk/lib/generated-client'; +import { ApiClient } from 'jellyfin-apiclient'; + +interface GetItemsRequest { + Ids?: string; + Limit?: number; +} + +const ITEMS_PER_REQUEST_LIMIT = 40; + +function getItemsSplit(apiClient: ApiClient, userId: string, options: GetItemsRequest) { + const optionsTemplate = { ...options }; + const ids = options.Ids?.split(',') || []; + const results = []; + const limit = options.Limit ?? Infinity; + + let end; + for (let start = 0; start < ids.length && start < limit; start = end) { + end = start + ITEMS_PER_REQUEST_LIMIT; + if (end > limit) { + end = limit; + } + const idsSlice = ids.slice(start, end); + optionsTemplate.Ids = idsSlice.join(','); + results.push(apiClient.getItems(userId, optionsTemplate)); + } + + return results; +} + +function mergeResults(results: BaseItemDtoQueryResult[]) { + const merged: BaseItemDtoQueryResult = { + Items: [], + TotalRecordCount: 0, + StartIndex: 0 + }; + + for (const result of results) { + if (result.Items == null) { + console.log('[getItems] Retrieved Items array is invalid', result.Items); + continue; + } + if (result.TotalRecordCount == null) { + console.log('[getItems] Retrieved TotalRecordCount is invalid', result.TotalRecordCount); + continue; + } + if (result.StartIndex == null) { + console.log('[getItems] Retrieved StartIndex is invalid', result.StartIndex); + continue; + } + merged.Items = merged.Items?.concat(result.Items); + merged.TotalRecordCount! += result.TotalRecordCount; + merged.StartIndex = Math.min(merged.StartIndex || 0, result.StartIndex); + } + return merged; +} + +/** + * Transparently handles the call to apiClient.getItems splitting the + * call into multiple ones if the URL might get too long. + * @param apiClient The ApiClient to use + * @param userId User id to pass to actual getItems call + * @param options Options object to specify getItems option. This includes a possibly long Items list that will be split up. + * @returns A promise that resolves to the merged result of all getItems calls + */ +export function getItems(apiClient: ApiClient, userId: string, options?: GetItemsRequest) { + const ids = options?.Ids?.split(','); + if (!options || !ids || ids.length <= ITEMS_PER_REQUEST_LIMIT) { + return apiClient.getItems(apiClient.getCurrentUserId(), options); + } + const results = getItemsSplit(apiClient, userId, options); + + return Promise.all(results).then(mergeResults); +}