1
0
Fork 0
mirror of https://github.com/jellyfin/jellyfin-web synced 2025-03-30 19:56:21 +00:00

Fix+enhance playback sequence of episodes in a series/season

- Adds an option to a season's menu to `Play All From Here` (default
  behavior is to play only the specific season)
- Playback will start on the first unplayed episode, or at the beginning
  of the season
- After starting playback, navigation to prior episodes is immediately possible
  using the previous episode navigation button
- Fix previous button not navigating to previous episode when starting
  on a non-first episode (ex. episode 1 when starting playback on episode 2)
This commit is contained in:
Andrew Rabert 2024-05-01 11:46:14 -04:00
parent b12d92dffd
commit 46c749c547
2 changed files with 35 additions and 35 deletions

View file

@ -1858,7 +1858,7 @@ class PlaybackManager {
}, queryOptions)); }, queryOptions));
case 'Series': case 'Series':
case 'Season': case 'Season':
return getSeriesOrSeasonPlaybackPromise(firstItem, options); return getSeriesOrSeasonPlaybackPromise(firstItem, options, items);
case 'Episode': case 'Episode':
return getEpisodePlaybackPromise(firstItem, options, items); return getEpisodePlaybackPromise(firstItem, options, items);
} }
@ -1922,41 +1922,44 @@ class PlaybackManager {
return null; return null;
} }
async function getSeriesOrSeasonPlaybackPromise(firstItem, options) { async function getSeriesOrSeasonPlaybackPromise(firstItem, options, items) {
const apiClient = ServerConnections.getApiClient(firstItem.ServerId); const apiClient = ServerConnections.getApiClient(firstItem.ServerId);
const isSeason = firstItem.Type === 'Season'; const startSeasonId = firstItem.Type === 'Season' ? items[options.startIndex || 0].Id : undefined;
const episodesResult = await apiClient.getEpisodes(firstItem.SeriesId || firstItem.Id, { const episodesResult = await apiClient.getEpisodes(firstItem.SeriesId || firstItem.Id, {
IsVirtualUnaired: false, IsVirtualUnaired: false,
IsMissing: false, IsMissing: false,
SeasonId: isSeason ? firstItem.Id : undefined, SeasonId: (startSeasonId && items.length === 1) ? startSeasonId : undefined,
SortBy: options.shuffle ? 'Random' : undefined, SortBy: options.shuffle ? 'Random' : undefined,
UserId: apiClient.getCurrentUserId(), UserId: apiClient.getCurrentUserId(),
Fields: ['Chapters', 'Trickplay'] Fields: ['Chapters', 'Trickplay']
}); });
const originalResults = episodesResult.Items; if (options.shuffle) {
episodesResult.StartIndex = 0;
let foundItem = false; } else {
episodesResult.StartIndex = undefined;
if (!options.shuffle) { let seasonStartIndex;
episodesResult.Items = episodesResult.Items.filter(function (e) { for (const [index, e] of episodesResult.Items.entries()) {
if (foundItem) { if (startSeasonId) {
return true; if (e.SeasonId == startSeasonId) {
if (seasonStartIndex === undefined) {
seasonStartIndex = index;
}
} else {
continue;
}
} }
if (!e.UserData.Played) { if (!e.UserData.Played) {
foundItem = true; episodesResult.StartIndex = index;
return true; break;
} }
}
return false; episodesResult.StartIndex = episodesResult.StartIndex || seasonStartIndex || 0;
});
} }
if (episodesResult.Items.length === 0) { // TODO: fix calling code to read episodesResult.StartIndex instead when set.
episodesResult.Items = originalResults; options.startIndex = episodesResult.StartIndex;
}
episodesResult.TotalRecordCount = episodesResult.Items.length; episodesResult.TotalRecordCount = episodesResult.Items.length;
@ -1965,13 +1968,13 @@ class PlaybackManager {
function getEpisodePlaybackPromise(firstItem, options, items) { function getEpisodePlaybackPromise(firstItem, options, items) {
if (items.length === 1 && getPlayer(firstItem, options).supportsProgress !== false) { if (items.length === 1 && getPlayer(firstItem, options).supportsProgress !== false) {
return getEpisodes(firstItem); return getEpisodes(firstItem, options);
} else { } else {
return null; return null;
} }
} }
function getEpisodes(firstItem) { function getEpisodes(firstItem, options) {
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
const apiClient = ServerConnections.getApiClient(firstItem.ServerId); const apiClient = ServerConnections.getApiClient(firstItem.ServerId);
@ -1986,24 +1989,21 @@ class PlaybackManager {
UserId: apiClient.getCurrentUserId(), UserId: apiClient.getCurrentUserId(),
Fields: ['Chapters', 'Trickplay'] Fields: ['Chapters', 'Trickplay']
}).then(function (episodesResult) { }).then(function (episodesResult) {
resolve(filterEpisodes(episodesResult, firstItem)); resolve(filterEpisodes(episodesResult, firstItem, options));
}, reject); }, reject);
}); });
} }
function filterEpisodes(episodesResult, firstItem) { function filterEpisodes(episodesResult, firstItem, options) {
let foundItem = false; for (const [index, e] of episodesResult.Items.entries()) {
episodesResult.Items = episodesResult.Items.filter(function (e) {
if (foundItem) {
return true;
}
if (e.Id === firstItem.Id) { if (e.Id === firstItem.Id) {
foundItem = true; episodesResult.StartIndex = index;
return true; break;
} }
}
return false; // TODO: fix calling code to read episodesResult.StartIndex instead when set.
}); options.startIndex = episodesResult.StartIndex;
episodesResult.TotalRecordCount = episodesResult.Items.length; episodesResult.TotalRecordCount = episodesResult.Items.length;
return episodesResult; return episodesResult;
} }

View file

@ -116,7 +116,7 @@ function showContextMenu(card, options) {
item: item, item: item,
play: true, play: true,
queue: true, queue: true,
playAllFromHere: !item.IsFolder, playAllFromHere: item.Type === 'Season' || !item.IsFolder,
queueAllFromHere: !item.IsFolder, queueAllFromHere: !item.IsFolder,
playlistId: playlistId, playlistId: playlistId,
collectionId: collectionId, collectionId: collectionId,