Skip to content

Commit f334e7b

Browse files
author
Anish Sarangi
committed
Fix skip/next to continuously fetch random albums and display correct total count
1 parent aa106da commit f334e7b

2 files changed

Lines changed: 241 additions & 6 deletions

File tree

functions/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "audio-interface-of-internet-archive",
33
"description": "Audio Interface of Internet Archive",
4-
"version": "2.5.19",
4+
"version": "2.5.21",
55
"private": true,
66
"author": "Internet Archive",
77
"license": "AGPL-3.0",

functions/src/extensions/feeders/albums.js

Lines changed: 240 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ class SyncAlbum extends DefaultFeeder {
4545
debug(`get ${albums.items.length} albums`);
4646
debug(albums.items);
4747
let albumId = null;
48-
total = albums.items.length;
48+
total = albums.total || albums.items.length;
4949
switch (albums.items.length) {
5050
case 0:
5151
// TODO: there is no such album
@@ -78,13 +78,17 @@ class SyncAlbum extends DefaultFeeder {
7878
albumId = albums.items[0].identifier;
7979
break;
8080
}
81-
return albumId;
81+
return { albumId, total, slots };
8282
})
83-
.then((albumId) => {
83+
.then(({ albumId, total, slots }) => {
84+
// Store total and slots in playlist extra for fetching random albums later
85+
playlist.setExtra(app, { total, slots });
86+
8487
if (albumId) {
8588
debug('id of album:', albumId);
8689
return albumsProvider.fetchAlbumDetails(app, albumId);
8790
}
91+
return null;
8892
})
8993
.then(album => {
9094
if (!album) {
@@ -101,9 +105,10 @@ class SyncAlbum extends DefaultFeeder {
101105
// the only place where we modify state
102106
// so maybe we can put it out of this function?
103107
debug('let\'s create playlist for songs');
104-
playlist.create(app, songs);
108+
const extra = playlist.getExtra(app);
109+
playlist.create(app, songs, extra);
105110

106-
return { total };
111+
return { total: extra.total };
107112
});
108113
// TODO:
109114
// .catch(err => {
@@ -112,6 +117,236 @@ class SyncAlbum extends DefaultFeeder {
112117
// });
113118
// });
114119
}
120+
121+
/**
122+
* Move to the next song
123+
* Called by both "next" and "skip" commands
124+
* If playlist runs out, fetch a random album from the collection
125+
*
126+
* @param ctx
127+
* @param move
128+
* @returns {Promise.<T>}
129+
*/
130+
next (ctx, move = true) {
131+
const { app, playlist } = ctx;
132+
133+
// Check if there are more songs in the current playlist
134+
if (playlist.hasNextSong(app)) {
135+
// Just move to the next song
136+
if (move) {
137+
debug('move to the next song');
138+
playlist.next(app);
139+
}
140+
return Promise.resolve(ctx);
141+
}
142+
143+
// No more songs, fetch a random album
144+
debug('playlist ran out, fetching random album');
145+
let extra = playlist.getExtra(app);
146+
let slots = extra && extra.slots;
147+
let total = extra && extra.total;
148+
149+
// Fallback: try to get slots from query if extra data is missing
150+
if (!slots && ctx.query) {
151+
try {
152+
slots = objToLowerCase(ctx.query.getSlots(app));
153+
debug('got slots from query as fallback:', slots);
154+
} catch (e) {
155+
warning('error getting slots from query:', e);
156+
}
157+
}
158+
159+
if (!slots) {
160+
warning('no slots found, cannot fetch random album');
161+
return Promise.resolve(ctx);
162+
}
163+
164+
// If we don't have total, try to fetch it first
165+
if (!total) {
166+
debug('total not found in extra, fetching to get total count');
167+
return albumsProvider
168+
.fetchAlbumsByQuery(app, slots)
169+
.then(albums => {
170+
if (albums && albums.total) {
171+
total = albums.total;
172+
// Update extra with total for future use
173+
playlist.setExtra(app, { ...extra, total, slots });
174+
debug(`got total from API: ${total}`);
175+
}
176+
// Continue with random fetch even if we couldn't get total
177+
return this.fetchRandomAlbum(ctx, slots, total, move);
178+
})
179+
.catch(error => {
180+
warning('error fetching total:', error);
181+
// Still try to fetch random album with estimated total
182+
return this.fetchRandomAlbum(ctx, slots, 1000, move); // Use a reasonable default
183+
});
184+
}
185+
186+
return this.fetchRandomAlbum(ctx, slots, total, move);
187+
}
188+
189+
/**
190+
* Fetch a random album and add it to the playlist
191+
*
192+
* @private
193+
* @param ctx
194+
* @param slots
195+
* @param total
196+
* @param move
197+
* @returns {Promise}
198+
*/
199+
fetchRandomAlbum (ctx, slots, total, move = true) {
200+
const { app, playlist } = ctx;
201+
202+
// Calculate random page number
203+
// Each page has limit items, so max page = Math.floor(total / limit)
204+
const limit = 3; // Fetch multiple albums to pick randomly from
205+
const maxPage = total ? Math.max(0, Math.floor((total - 1) / limit)) : 100; // Default to page 100 if total unknown
206+
const randomPage = Math.floor(Math.random() * (maxPage + 1));
207+
208+
debug(`fetching random album from page ${randomPage} (total: ${total || 'unknown'}, maxPage: ${maxPage})`);
209+
210+
return albumsProvider
211+
.fetchAlbumsByQuery(app, {
212+
...slots,
213+
limit: limit, // Fetch multiple albums for better randomness
214+
page: randomPage,
215+
order: 'best', // Use best order to get random page
216+
})
217+
.then(albums => {
218+
if (!albums || !albums.items || albums.items.length === 0) {
219+
warning('no albums found for random fetch');
220+
return ctx;
221+
}
222+
223+
// Pick a random album from the results for better randomness
224+
const randomAlbum = albums.items[Math.floor(Math.random() * albums.items.length)];
225+
debug(`selected random album: ${randomAlbum.identifier} from ${albums.items.length} albums`);
226+
227+
return albumsProvider.fetchAlbumDetails(app, randomAlbum.identifier);
228+
})
229+
.then(album => {
230+
if (!album) {
231+
warning('failed to fetch random album details');
232+
return ctx;
233+
}
234+
235+
debug('fetched random album', album);
236+
const songs = this.processAlbumSongs(app, album);
237+
debug(`adding ${songs.length} songs from random album to playlist`);
238+
239+
// IMPORTANT: Ensure extra data is set BEFORE updating items
240+
// This ensures it's preserved even if updateItems has issues
241+
const currentExtra = playlist.getExtra(app);
242+
const extraToPreserve = currentExtra || (slots && total ? { total, slots } : null);
243+
if (extraToPreserve) {
244+
playlist.setExtra(app, extraToPreserve);
245+
debug('ensuring extra data is set before updating items:', extraToPreserve);
246+
}
247+
248+
// Append new songs to existing playlist
249+
const currentItems = playlist.getItems(app);
250+
const newItems = currentItems.concat(songs);
251+
playlist.updateItems(app, newItems);
252+
253+
// Double-check extra data is still there after updateItems
254+
const extraAfterUpdate = playlist.getExtra(app);
255+
if (!extraAfterUpdate && extraToPreserve) {
256+
warning('extra data was lost after updateItems, restoring it');
257+
playlist.setExtra(app, extraToPreserve);
258+
} else {
259+
debug('extra data preserved after updateItems:', extraAfterUpdate);
260+
}
261+
262+
// Move to the first new song
263+
if (move && songs.length > 0) {
264+
playlist.moveTo(app, songs[0]);
265+
}
266+
267+
// Final check: ensure extra data is still there after moveTo
268+
const extraAfterMove = playlist.getExtra(app);
269+
if (!extraAfterMove && extraToPreserve) {
270+
warning('extra data was lost after moveTo, restoring it');
271+
playlist.setExtra(app, extraToPreserve);
272+
}
273+
274+
return ctx;
275+
})
276+
.catch(error => {
277+
warning('error fetching random album:', error);
278+
return ctx;
279+
});
280+
}
281+
282+
/**
283+
* Do we have next item?
284+
* Always return true if we can fetch more albums from the collection
285+
*
286+
* @param ctx
287+
* @returns {boolean}
288+
*/
289+
hasNext (ctx) {
290+
const { app, query, playlist } = ctx;
291+
292+
// If playlist is looped, always have next
293+
if (playlist.isLoop(app)) {
294+
debug('hasNext: playlist is looped');
295+
return true;
296+
}
297+
298+
// If there are more songs in current playlist, we have next
299+
if (playlist.hasNextSong(app)) {
300+
debug('hasNext: has more songs in current playlist');
301+
return true;
302+
}
303+
304+
// Always have something when songs in shuffle/random order
305+
try {
306+
if (query && typeof query.getSlot === 'function') {
307+
const order = query.getSlot(app, 'order');
308+
if (order === 'random') {
309+
debug('hasNext: order is random');
310+
return true;
311+
}
312+
}
313+
} catch (e) {
314+
debug('hasNext: error checking order slot:', e);
315+
}
316+
317+
// Check if we can fetch more albums from the collection
318+
let extra = playlist.getExtra(app);
319+
debug('hasNext check - extra from playlist:', extra);
320+
321+
// Fallback: try to get slots from query if extra data is missing
322+
if ((!extra || !extra.slots) && query && typeof query.getSlots === 'function') {
323+
try {
324+
const querySlots = query.getSlots(app);
325+
if (querySlots && Object.keys(querySlots).length > 0) {
326+
debug('hasNext: got slots from query as fallback:', querySlots);
327+
// If we have query slots, we can always fetch more albums
328+
return true;
329+
}
330+
} catch (e) {
331+
debug('hasNext: error getting slots from query:', e);
332+
}
333+
}
334+
335+
if (extra && extra.total && typeof extra.total === 'number' && extra.total > 0) {
336+
// We can always fetch more random albums from the collection
337+
debug(`hasNext: can fetch more albums (total: ${extra.total})`);
338+
return true;
339+
}
340+
341+
// If we have slots stored, we can try to fetch more (even if total is unknown)
342+
if (extra && extra.slots && Object.keys(extra.slots).length > 0) {
343+
debug('hasNext: have slots stored, can try to fetch more albums');
344+
return true;
345+
}
346+
347+
debug('hasNext: no more songs available and cannot fetch more');
348+
return false;
349+
}
115350
}
116351

117352
module.exports = new SyncAlbum();

0 commit comments

Comments
 (0)