openvibers/openvibe.live Commits

Thumbnails: an audio-only recording (Media's media.no_video) is asked about once a day, not hourly (three VODs cost Media a job an hour each) (#198)

OP OpenVibers committed 27 days ago · 09426db920 · 2 files, +27 −1

Changes

ddiff --git a/server/media-proxy/thumbnails.js b/server/media-proxy/thumbnails.js
iindex 16aab6f2..8aaa0d1f 100644
---- a/server/media-proxy/thumbnails.js
++++ b/server/media-proxy/thumbnails.js
@@@ -61,6 +61,9 @@ router.post('/live/:streamId', requireAuth, upload.single('thumbnail'), async (r
// 429 used to reach every visitor's console (the browser check of release 977747d7, 2026-10-12).
const NO_PICTURE_MS = 60 * 60 * 1000;
const MEDIA_BUSY_MS = 5 * 60 * 1000;
++// An audio-only recording (Media's 404 media.no_video) will never have a picture: asked again once a day, not hourly
++// (VODs 5703, 5725 and 5770 cost Media a job an hour each on 2026-10-17).
++const NO_VIDEO_MS = 24 * 60 * 60 * 1000;
const noPicture = new Map(); // `${kind}:${id}` → until (ms), oldest first
function noPictureKnown(key) {
const until = noPicture.get(key);
@@@ -92,7 +95,8 @@ async function generateFor(kind, id, req, res) {
const failed = err && err.name === 'MediaApiError' && err.status >= 500;
if (!(err && err.name === 'MediaApiError' && (err.status === 404 || err.status === 429 || failed))) throw err;
if (failed) console.warn(`[Thumbnails] Media's thumbnail job failed for ${key}: ${err.status}${err.body && err.body.job_id ? ` (${err.body.job_id})` : ''}`);
-- rememberNoPicture(key, err.status === 404 ? NO_PICTURE_MS : MEDIA_BUSY_MS);
++ const noVideo = err.status === 404 && err.body && err.body.code === 'media.no_video';
++ rememberNoPicture(key, noVideo ? NO_VIDEO_MS : err.status === 404 ? NO_PICTURE_MS : MEDIA_BUSY_MS);
return res.json({ thumbnail_url: null });
}
const url = media.publicUrl(out?.url || meta.thumbnail_url);
ddiff --git a/test/thumbnail-no-picture.test.js b/test/thumbnail-no-picture.test.js
iindex 8f2d4292..d0bd47bf 100644
---- a/test/thumbnail-no-picture.test.js
++++ b/test/thumbnail-no-picture.test.js
@@@ -49,6 +49,28 @@ console.warn = () => {};
await post('/api/thumbnails/generate/clip/456');
assert.strictEqual(asked, 2, 'a failed job is remembered too');
++ // An audio-only recording (404 media.no_video) is remembered for a day; another 404 for an hour.
++ const realNow = Date.now;
++ const notFound = (code) => async () => {
++ asked++;
++ const err = new Error('no picture');
++ Object.assign(err, { name: 'MediaApiError', status: 404, body: { error: 'no picture', code } });
++ throw err;
++ };
++ media.generateThumbnail = notFound('media.no_video');
++ await post('/api/thumbnails/generate/clip/457');
++ media.generateThumbnail = notFound('media.not_found');
++ await post('/api/thumbnails/generate/clip/458');
++ assert.strictEqual(asked, 4);
++ Date.now = () => realNow() + 2 * 60 * 60 * 1000; // two hours later
++ media.generateThumbnail = notFound('media.no_video');
++ await post('/api/thumbnails/generate/clip/457');
++ assert.strictEqual(asked, 4, 'audio only: still remembered after two hours');
++ media.generateThumbnail = notFound('media.not_found');
++ await post('/api/thumbnails/generate/clip/458');
++ assert.strictEqual(asked, 5, 'another 404: asked again after an hour');
++ Date.now = realNow;
++
server.close();
console.log('thumbnail no-picture: all checks passed');
process.exit(0);