Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ import {
parseLocalPlaylistVideo,
untilEndOfLocalPlayList,
} from '../../helpers/api/local'
import { invidiousGetPlaylistInfo } from '../../helpers/api/invidious'
import { invidiousGetPlaylistInfo, fetchAllInvidiousPlaylistVideos } from '../../helpers/api/invidious'
import { getSortedPlaylistItems, SORT_BY_VALUES } from '../../helpers/playlists'

const props = defineProps({
Expand Down Expand Up @@ -581,20 +581,22 @@ async function loadCachedPlaylistInformation(cachedPlaylist) {
channelName.value = cachedPlaylist.channelName
channelId.value = cachedPlaylist.channelId

if (!process.env.SUPPORTS_LOCAL_API || backendPreference.value === 'invidious' || cachedPlaylist.continuationData === null) {
playlistItems.value = cachedPlaylist.items
} else {
const videos = cachedPlaylist.items

const videos = cachedPlaylist.items
if (!process.env.SUPPORTS_LOCAL_API || backendPreference.value === 'invidious') {
const videoCount = cachedPlaylist.videoCount
if (videos.length < videoCount) {
const remainingVideos = await fetchAllInvidiousPlaylistVideos(cachedPlaylist.id, videos.length)
videos.push(...remainingVideos)
}
} else if (cachedPlaylist.continuationData !== null) {
const continuationData = await getLocalCachedFeedContinuation('playlist', cachedPlaylist.continuationData)
videos.push(...continuationData.items.map(parseLocalPlaylistVideo))

await untilEndOfLocalPlayList(continuationData, (p) => {
videos.push(...p.items.map(parseLocalPlaylistVideo))
}, { runCallbackOnceFirst: false })

playlistItems.value = videos
}
playlistItems.value = videos

isLoading.value = false
}
Expand Down Expand Up @@ -653,7 +655,11 @@ async function getPlaylistInformationInvidious() {
channelName.value = result.author
channelId.value = result.authorId

playlistItems.value = result.videos
const videos = result.videos
const remainingVideos = await fetchAllInvidiousPlaylistVideos(result.playlistId, videos.length)
videos.push(...remainingVideos)

playlistItems.value = videos

isLoading.value = false
} catch (err) {
Expand Down
40 changes: 39 additions & 1 deletion src/renderer/helpers/api/invidious.js
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,7 @@ export async function searchInvidiousChannel(channelId, query, page) {

/**
* @param {string} playlistId
* @param {number} index
* @returns {Promise<{
* title: string,
* playlistId: string,
Expand All @@ -307,10 +308,17 @@ export async function searchInvidiousChannel(channelId, query, page) {
* }[]
* }>}
*/
export async function invidiousGetPlaylistInfo(playlistId) {
export async function invidiousGetPlaylistInfo(playlistId, index = 0) {
// The Invidious API offsets the index by 50 to apply a lookback window
// By increasing our offset by 50, we skip those overlapping videos and avoid de-duplicating them from our response
// See: https://github.com/iv-org/invidious/blob/66fb829dbc0f96cbf4319798f3b9090bc88a7012/src/invidious/routes/api/v1/misc.cr#L67
index += 50
const playlist = await invidiousAPICall({
resource: 'playlists',
id: playlistId,
params: {
index
}
})

normalizeManyInvidiousVideosAttributes(playlist.videos)
Expand All @@ -319,6 +327,36 @@ export async function invidiousGetPlaylistInfo(playlistId) {
return playlist
}

/**
* @param {string} playlistId
* @param {number} initialOffset
* @returns {{
* title: string,
* videoId: string,
* author: string,
* authorId: string,
* authorUrl: string,
* videoThumbnails: InvidiousThumbnailObject[],
* index: number,
* lengthSeconds: number
* }[]}
*/
export async function fetchAllInvidiousPlaylistVideos(playlistId, initialOffset = 0) {
let offset = initialOffset
const videos = []

while (true) {
const playlist = await invidiousGetPlaylistInfo(playlistId, offset)
if (playlist.videos.length === 0) {
break
}
videos.push(...playlist.videos)
offset += playlist.videos.length
}

return videos
}

/**
* @param {string} videoId
* @returns {Promise<{
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/helpers/api/local.js
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ export async function getLocalPlaylistContinuation(playlist) {
}

/**
* Callback for adding two numbers.
* Callback for processing a Local playlist.
*
* @callback untilEndOfLocalPlayListCallback
* @param {import('youtubei.js').YT.Playlist} playlist
Expand Down
25 changes: 21 additions & 4 deletions src/renderer/views/Playlist/Playlist.vue
Original file line number Diff line number Diff line change
Expand Up @@ -295,9 +295,15 @@ const selectedUserPlaylistVideos = computed(() => selectedUserPlaylist.value?.vi
const selectedUserPlaylistVideoCount = computed(() => selectedUserPlaylistVideos.value.length)

const moreVideoDataAvailable = computed(() => {
return isUserPlaylistRequested.value
? userPlaylistVisibleLimit.value < sometimesFilteredUserPlaylistItems.value.length
: continuationData.value !== null
if (isUserPlaylistRequested.value) {
return userPlaylistVisibleLimit.value < sometimesFilteredUserPlaylistItems.value.length
}

if (infoSource.value === 'invidious') {
return playlistItems.value.length < videoCount.value
}

return continuationData.value !== null
})

const processedVideoSearchQuery = computed(() => videoSearchQuery.value.trim().toLowerCase())
Expand Down Expand Up @@ -668,7 +674,7 @@ function getNextPage() {
isLoadingMore.value = false
})
} else if (infoSource.value === 'invidious') {
console.error('Playlist pagination is not currently supported when the Invidious backend is selected.')
getNextPageInvidious()
}
}

Expand Down Expand Up @@ -703,6 +709,16 @@ async function getNextPageLocal() {
}
}

async function getNextPageInvidious() {
isLoadingMore.value = true

const index = playlistItems.value.length
const result = await invidiousGetPlaylistInfo(playlistId.value, index)
playlistItems.value.push(...result.videos)

isLoadingMore.value = false
}

const canMoveVideos = computed(() => {
return !playlistInVideoSearchMode.value && isSortOrderCustom.value && noPlaylistItemsPendingDeletion.value
})
Expand Down Expand Up @@ -1075,6 +1091,7 @@ onBeforeRouteLeave((to) => {
continuationData: continuationData.value
? extractLocalCacheablePlaylistContinuation(continuationData.value)
: null,
videoCount: videoCount.value,
})
}

Expand Down