diff --git a/src/renderer/components/watch-video-playlist/watch-video-playlist.js b/src/renderer/components/watch-video-playlist/watch-video-playlist.js index 6a854541b9e28..4195cd7075e5d 100644 --- a/src/renderer/components/watch-video-playlist/watch-video-playlist.js +++ b/src/renderer/components/watch-video-playlist/watch-video-playlist.js @@ -4,7 +4,11 @@ import FtLoader from '../ft-loader/ft-loader.vue' import FtCard from '../ft-card/ft-card.vue' import FtListVideoLazy from '../ft-list-video-lazy/ft-list-video-lazy.vue' import { copyToClipboard, showToast } from '../../helpers/utils' -import { getLocalPlaylist, parseLocalPlaylistVideo } from '../../helpers/api/local' +import { + getLocalPlaylist, + parseLocalPlaylistVideo, + untilEndOfLocalPlayList, +} from '../../helpers/api/local' import { invidiousGetPlaylistInfo } from '../../helpers/api/invidious' export default defineComponent({ @@ -327,21 +331,12 @@ export default defineComponent({ if (!process.env.IS_ELECTRON || this.backendPreference === 'invidious' || cachedPlaylist.continuationData === null) { this.playlistItems = cachedPlaylist.items } else { - const items = cachedPlaylist.items - let playlist = cachedPlaylist.continuationData + const videos = cachedPlaylist.items + await untilEndOfLocalPlayList(cachedPlaylist.continuationData, (p) => { + videos.push(...p.items.map(parseLocalPlaylistVideo)) + }, { runCallbackOnceFirst: false }) - do { - playlist = await playlist.getContinuation() - - const parsedVideos = playlist.items.map(parseLocalPlaylistVideo) - items.push(...parsedVideos) - - if (!playlist.has_continuation) { - playlist = null - } - } while (playlist !== null) - - this.playlistItems = items + this.playlistItems = videos } this.isLoading = false @@ -351,7 +346,7 @@ export default defineComponent({ this.isLoading = true try { - let playlist = await getLocalPlaylist(this.playlistId) + const playlist = await getLocalPlaylist(this.playlistId) let channelName @@ -368,14 +363,10 @@ export default defineComponent({ this.channelName = channelName this.channelId = playlist.info.author?.id - const videos = playlist.items.map(parseLocalPlaylistVideo) - - while (playlist.has_continuation) { - playlist = await playlist.getContinuation() - - const parsedVideos = playlist.items.map(parseLocalPlaylistVideo) - videos.push(...parsedVideos) - } + const videos = [] + await untilEndOfLocalPlayList(playlist, (p) => { + videos.push(...p.items.map(parseLocalPlaylistVideo)) + }) this.playlistItems = videos diff --git a/src/renderer/helpers/api/local.js b/src/renderer/helpers/api/local.js index 98115c2357a07..f95bca91ef4c7 100644 --- a/src/renderer/helpers/api/local.js +++ b/src/renderer/helpers/api/local.js @@ -75,6 +75,47 @@ export async function getLocalPlaylist(id) { return await innertube.getPlaylist(id) } +/** + * @param {Playlist} playlist + * @returns {Playlist|null} null when no valid playlist can be found (e.g. `empty continuation response`) + */ +export async function getLocalPlaylistContinuation(playlist) { + try { + return await playlist.getContinuation() + } catch (error) { + // Youtube can provide useless continuation data + if (!error.message.includes('Got empty continuation response.')) { + // Re-throw unhandled error + throw error + } + + return null + } +} + +/** + * Callback for adding two numbers. + * + * @callback untilEndOfLocalPlayListCallback + * @param {Playlist} playlist + */ + +/** + * @param {Playlist} playlist + * @param {untilEndOfLocalPlayListCallback} callback + * @param {object} options + * @param {boolean} options.runCallbackOnceFirst + */ +export async function untilEndOfLocalPlayList(playlist, callback, options = { runCallbackOnceFirst: true }) { + if (options.runCallbackOnceFirst) { callback(playlist) } + + while (playlist != null && playlist.has_continuation) { + playlist = await getLocalPlaylistContinuation(playlist) + + if (playlist != null) { callback(playlist) } + } +} + /** * @param {string} location * @param {'default'|'music'|'gaming'|'movies'} tab diff --git a/src/renderer/views/Playlist/Playlist.js b/src/renderer/views/Playlist/Playlist.js index 05bfb29290196..0c668d2b683c6 100644 --- a/src/renderer/views/Playlist/Playlist.js +++ b/src/renderer/views/Playlist/Playlist.js @@ -6,7 +6,11 @@ import PlaylistInfo from '../../components/playlist-info/playlist-info.vue' import FtListVideoLazy from '../../components/ft-list-video-lazy/ft-list-video-lazy.vue' import FtFlexBox from '../../components/ft-flex-box/ft-flex-box.vue' import FtButton from '../../components/ft-button/ft-button.vue' -import { getLocalPlaylist, parseLocalPlaylistVideo } from '../../helpers/api/local' +import { + getLocalPlaylist, + getLocalPlaylistContinuation, + parseLocalPlaylistVideo, +} from '../../helpers/api/local' import { extractNumberFromString } from '../../helpers/utils' import { invidiousGetPlaylistInfo, youtubeImageUrlToInvidious } from '../../helpers/api/invidious' @@ -188,12 +192,16 @@ export default defineComponent({ getNextPageLocal: function () { this.isLoadingMore = true - this.continuationData.getContinuation().then((result) => { - const parsedVideos = result.items.map(parseLocalPlaylistVideo) - this.playlistItems = this.playlistItems.concat(parsedVideos) + getLocalPlaylistContinuation(this.continuationData).then((result) => { + if (result) { + const parsedVideos = result.items.map(parseLocalPlaylistVideo) + this.playlistItems = this.playlistItems.concat(parsedVideos) - if (result.has_continuation) { - this.continuationData = result + if (result.has_continuation) { + this.continuationData = result + } else { + this.continuationData = null + } } else { this.continuationData = null }