Implemented batch loading for API Extended

This commit is contained in:
ward
2025-06-23 12:25:33 +12:00
parent 9ff9bfbaf1
commit 0e1a2f30f3
7 changed files with 153 additions and 107 deletions
+59
View File
@@ -0,0 +1,59 @@
// src/lib/requestQueue.ts
import { writable, type Writable } from 'svelte/store';
import type { SynciousProgressModel } from './model';
import { getVideoProgress } from '.';
const synciousCacheStore: Writable<SynciousProgressModel[] | null> = writable(null);
const videoIds: string[] = [];
const pendingResolves = new Map<string, (result: SynciousProgressModel | undefined) => void>();
let timeout: ReturnType<typeof setTimeout> | null = null;
const DEBOUNCE_MS = 1000;
const BATCH_SIZE = 100;
async function processBatches(): Promise<void> {
const batches: string[][] = [];
while (videoIds.length > 0) {
batches.push(videoIds.splice(0, BATCH_SIZE));
}
const results: SynciousProgressModel[] = [];
for (const batch of batches) {
const res: SynciousProgressModel[] = await getVideoProgress(batch.join(','));
results.push(...res);
// Resolve pending promises for this batch
for (const videoId of batch) {
const match = res.find((item) => item.video_id === videoId);
const resolve = pendingResolves.get(videoId);
if (resolve) {
resolve(match);
pendingResolves.delete(videoId);
}
}
}
synciousCacheStore.set(results);
}
export function queueSyncious(videoId: string): Promise<SynciousProgressModel | undefined> {
videoIds.push(videoId);
const promise = new Promise<SynciousProgressModel | undefined>((resolve) => {
pendingResolves.set(videoId, resolve);
});
if (timeout) clearTimeout(timeout);
timeout = setTimeout(() => {
processBatches().catch((e) => {
console.error('Failed to process batches:', e);
});
}, DEBOUNCE_MS);
return promise;
}
export { synciousCacheStore };
+1 -1
View File
@@ -64,7 +64,7 @@ export function buildAuthHeaders(): { headers: Record<string, string> } {
if (authToken.startsWith('SID=')) {
return { headers: { __sid_auth: authToken } };
} else {
return { headers: { Authorization: `Bearer ${get(authStore)?.token}` } };
return { headers: { Authorization: `Bearer ${authToken}` } };
}
}
+14 -16
View File
@@ -90,7 +90,18 @@
played: (await getDynamicTheme())['--primary']
}
});
setChapterMarkers();
const overflowMenuButton = document.querySelector('.shaka-overflow-menu-button');
if (overflowMenuButton) {
overflowMenuButton.innerHTML = 'settings';
}
const backToOverflowButton = document.querySelector('.shaka-back-to-overflow-button');
if (backToOverflowButton) {
backToOverflowButton.innerHTML = 'arrow_back_ios_new';
}
}
themeColorStore.subscribe(updateSeekBarTheme);
@@ -477,12 +488,11 @@
'statistics'
],
playbackRates: playbackRates,
enableTooltips: false,
seekBarColors: {
played: (await getDynamicTheme())['--primary']
}
enableTooltips: false
});
updateSeekBarTheme();
player.addEventListener('error', async (event) => {
const error = (event as CustomEvent).detail as shaka.util.Error;
console.error('Player error:', error);
@@ -504,16 +514,6 @@
await androidHandleRotate();
const overflowMenuButton = document.querySelector('.shaka-overflow-menu-button');
if (overflowMenuButton) {
overflowMenuButton.innerHTML = 'settings';
}
const backToOverflowButton = document.querySelector('.shaka-back-to-overflow-button');
if (backToOverflowButton) {
backToOverflowButton.innerHTML = 'arrow_back_ios_new';
}
Mousetrap.bind('space', () => {
if (!playerElement) return;
@@ -580,8 +580,6 @@
return false;
});
setChapterMarkers();
if ($isAndroidTvStore) {
Mousetrap.bind('enter', () => {
if (playerElement?.paused) {
@@ -5,7 +5,7 @@
import { onDestroy, onMount } from 'svelte';
import { _ } from '$lib/i18n';
import { get } from 'svelte/store';
import { getDeArrow, getThumbnail, getVideoProgress } from '../api';
import { getDeArrow, getThumbnail } from '../api';
import type { Notification, PlaylistPageVideo, Video, VideoBase } from '../api/model';
import { insecureRequestImageHandler, truncate } from '../misc';
import type { PlayerEvents } from '../player';
@@ -21,6 +21,7 @@
synciousStore
} from '../store';
import { goto } from '$app/navigation';
import { queueSyncious } from '$lib/api/apiExtended';
interface Props {
video: VideoBase | Video | Notification | PlaylistPageVideo;
@@ -109,7 +110,7 @@
if (get(synciousStore) && get(synciousInstanceStore) && get(authStore)) {
try {
progress = (await getVideoProgress(video.videoId, { priority: 'low' }))[0].time.toString();
progress = (await queueSyncious(video.videoId))?.time?.toString() ?? undefined;
} catch {}
}
});
+71
View File
@@ -0,0 +1,71 @@
import {
amSubscribed,
getComments,
getDislikes,
getPersonalPlaylists,
getVideo,
postHistory
} from '$lib/api/index';
import { loadEntirePlaylist } from '$lib/playlist';
import {
authStore,
playerProxyVideosStore,
returnYTDislikesInstanceStore,
returnYtDislikesStore
} from '$lib/store';
import { phaseDescription } from '$lib/timestamps';
import { error } from '@sveltejs/kit';
import { get } from 'svelte/store';
export async function getWatchDetails(videoId: string, url: URL) {
let video;
try {
video = await getVideo(videoId, get(playerProxyVideosStore), { priority: 'high' });
} catch (errorMessage: any) {
error(500, errorMessage);
}
let personalPlaylists;
if (get(authStore)) {
postHistory(video.videoId);
personalPlaylists = getPersonalPlaylists({ priority: 'low' });
} else {
personalPlaylists = null;
}
let comments;
try {
comments = video.liveNow
? null
: getComments(videoId, { sort_by: 'top', source: 'youtube' }, { priority: 'low' });
} catch {
comments = null;
}
let returnYTDislikes;
const returnYTDislikesInstance = get(returnYTDislikesInstanceStore);
if (returnYTDislikesInstance && returnYTDislikesInstance !== '') {
try {
returnYTDislikes = get(returnYtDislikesStore)
? getDislikes(videoId, { priority: 'low' })
: null;
} catch {}
}
const playlistId = url.searchParams.get('playlist');
if (playlistId) {
await loadEntirePlaylist(playlistId);
}
return {
video: video,
content: phaseDescription(video.videoId, video.descriptionHtml, video.fallbackPatch),
playlistId: playlistId,
streamed: {
personalPlaylists: personalPlaylists,
returnYTDislikes: returnYTDislikes,
comments: comments,
subscribed: amSubscribed(video.authorId)
}
};
}
@@ -1,71 +1,5 @@
import {
amSubscribed,
getComments,
getDislikes,
getPersonalPlaylists,
getVideo,
postHistory
} from '$lib/api/index';
import { loadEntirePlaylist } from '$lib/playlist';
import {
authStore,
playerProxyVideosStore,
returnYTDislikesInstanceStore,
returnYtDislikesStore
} from '$lib/store';
import { phaseDescription } from '$lib/timestamps';
import { error } from '@sveltejs/kit';
import { get } from 'svelte/store';
import { getWatchDetails } from '$lib/watch.js';
export async function load({ params, url }) {
let video;
try {
video = await getVideo(params.slug, get(playerProxyVideosStore), { priority: 'high' });
} catch (errorMessage: any) {
error(500, errorMessage);
}
let personalPlaylists;
if (get(authStore)) {
postHistory(video.videoId);
personalPlaylists = getPersonalPlaylists({ priority: 'low' });
} else {
personalPlaylists = null;
}
let comments;
try {
comments = video.liveNow
? null
: getComments(params.slug, { sort_by: 'top', source: 'youtube' }, { priority: 'low' });
} catch {
comments = null;
}
let returnYTDislikes;
const returnYTDislikesInstance = get(returnYTDislikesInstanceStore);
if (returnYTDislikesInstance && returnYTDislikesInstance !== '') {
try {
returnYTDislikes = get(returnYtDislikesStore)
? getDislikes(params.slug, { priority: 'low' })
: null;
} catch {}
}
const playlistId = url.searchParams.get('playlist');
if (playlistId) {
await loadEntirePlaylist(playlistId);
}
return {
video: video,
content: phaseDescription(video.videoId, video.descriptionHtml, video.fallbackPatch),
playlistId: playlistId,
streamed: {
personalPlaylists: personalPlaylists,
returnYTDislikes: returnYTDislikes,
comments: comments,
subscribed: amSubscribed(video.authorId)
}
};
return getWatchDetails(params.slug, url);
}
@@ -1,22 +1,5 @@
import { getVideo } from '$lib/api/index';
import { playerProxyVideosStore } from '$lib/store';
import { error } from '@sveltejs/kit';
import { get } from 'svelte/store';
import { getWatchDetails } from '$lib/watch.js';
export async function load({ params }) {
let video;
try {
video = await getVideo(params.slug, get(playerProxyVideosStore));
} catch (errorMessage: any) {
error(500, errorMessage);
}
return {
video: video,
content: {
description: '',
timestamps: []
},
playlistId: null
};
export async function load({ params, url }) {
return getWatchDetails(params.slug, url);
}