openvibers/openvibe.live Commits

Thumbnails: Media answering with neither a picture nor a job is 'no picture yet' (200, remembered), not a 500 in every visitor's console (browser check, clip 455 on /@goosely) (#185)

OP OpenVibers committed 27 days ago · 7a90b0df3b · 2 files, +48 −1

Changes

ddiff --git a/server/media-proxy/thumbnails.js b/server/media-proxy/thumbnails.js
iindex 8af8d658..0baf70ed 100644
---- a/server/media-proxy/thumbnails.js
++++ b/server/media-proxy/thumbnails.js
@@@ -95,7 +95,12 @@ async function generateFor(kind, id, req, res) {
if (url) return res.json({ thumbnail_url: url });
// Media is still working on it (202 with its job): no picture yet, nothing failed.
if (out && out.job) return res.status(202).json({ thumbnail_url: null });
-- res.status(500).json({ error: 'Failed to generate thumbnail' });
++ // Media answered with neither a picture nor a job (a recording it cannot draw a frame from yet): no picture,
++ // which is not this visitor's error. Asked again after a while, never a 500 in every visitor's console
++ // (the browser check of release 35d02c6b, 2026-10-17, clip 455 on /@goosely).
++ console.warn(`[Thumbnails] Media gave no thumbnail and no job for ${key}`);
++ rememberNoPicture(key, MEDIA_BUSY_MS);
++ res.json({ thumbnail_url: null });
} catch (err) {
if (err && err.name === 'MediaApiError' && err.status) return res.status(err.status).json(err.body || { error: err.message });
res.status(502).json({ error: 'Media service unavailable' });
ddiff --git a/test/thumbnail-no-picture.test.js b/test/thumbnail-no-picture.test.js
nnew file mode 100644
iindex 00000000..38269615
---- /dev/null
++++ b/test/thumbnail-no-picture.test.js
@@@ -0,0 +1,42 @@
++'use strict';
++/**
++ * POST /api/thumbnails/generate/clip/:id when OpenVibe.Media answers with neither a picture nor a job: no picture yet,
++ * answered 200 with thumbnail_url null and remembered for a while (Media is not asked again at once), never a 500 in
++ * every visitor's console (the browser check of release 35d02c6b found one on /@goosely, clip 455).
++ *
++ * node --import ./test/helpers/pg-preload.mjs test/thumbnail-no-picture.test.js
++ */
++const assert = require('assert');
++const http = require('http');
++
++process.env.NODE_ENV = 'test';
++console.warn = () => {};
++
++(async () => {
++ const media = require('../server/media-client');
++ const access = require('../server/media-proxy/access');
++ let asked = 0;
++ media.getClip = async (id) => ({ id: Number(id), user_id: 1, visibility: 'public', thumbnail_url: null });
++ media.generateThumbnail = async () => { asked++; return {}; };
++ media.publicUrl = (u) => u || null;
++ access.canView = async () => true;
++ const auth = require('../server/auth/auth');
++ auth.optionalAuth = (req, res, next) => next();
++
++ const express = require('express');
++ const app = express();
++ app.use('/api/thumbnails', require('../server/media-proxy/thumbnails'));
++ const server = http.createServer(app);
++ await new Promise((r) => server.listen(0, '127.0.0.1', r));
++ const post = async (p) => { const r = await fetch(`http://127.0.0.1:${server.address().port}${p}`, { method: 'POST' }); return { status: r.status, json: await r.json() }; };
++
++ const first = await post('/api/thumbnails/generate/clip/455');
++ assert.deepStrictEqual([first.status, first.json], [200, { thumbnail_url: null }]);
++ const again = await post('/api/thumbnails/generate/clip/455');
++ assert.strictEqual(again.status, 200);
++ assert.strictEqual(asked, 1, 'remembered: Media is not asked again at once');
++
++ server.close();
++ console.log('thumbnail no-picture: all checks passed');
++ process.exit(0);
++})().catch((e) => { console.error(e); process.exit(1); });