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)
Changes
@@@ -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' });nnew file mode 100644@@@ -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); });
@@ -95,7 +95,12 @@ async function generateFor(kind, id, req, res) {
@@ -95,7 +95,12 @@ async function generateFor(kind, id, req, res) {
if (url) return res.json({ thumbnail_url: url });
if (url) return res.json({ thumbnail_url: url });
// Media is still working on it (202 with its job): no picture yet, nothing failed.
// 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 });
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) {
} catch (err) {
if (err && err.name === 'MediaApiError' && err.status) return res.status(err.status).json(err.body || { error: err.message });
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' });
res.status(502).json({ error: 'Media service unavailable' });
new file mode 100644
new file mode 100644
@@ -0,0 +1,42 @@
@@ -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); });