openvibers/openvibe.media Commits
Live's recordings and clips on Media's pages point at OpenVibe.Video: the watch page's canonical and the browse cards named openvibe.live/vod/<id>, a 301 since Live#143 (#145)
Changes
@@@ -19,7 +19,7 @@ const THUMB_DIR = path.resolve(config.thumbnails.path);// Public base URL per app for "source" links — shared with the watch pages.const pageFrame = require('./page-frame');--const { appUrl, isoDate } = pageFrame;++const { appUrl, watchPageUrl, isoDate } = pageFrame;const cache = require('openvibe-shared/cache-policy');const frame = require('openvibe-shared/frame');const showcase = require('openvibe-shared/showcase');@@@ -36,7 +36,7 @@ function vodCard(v) {thumb: abs(v.thumbnail_url), duration: v.duration_seconds,text: snip(v.description || v.ai_overview),view: `${config.publicUrl}/v/${v.id}`,-- source: `${appUrl(v.app_id)}/vod/${v.id}`, sourceLabel: 'VOD page',++ source: watchPageUrl('vod', v.app_id, v.id), sourceLabel: 'VOD page',};}function clipCard(c) {@@@ -45,7 +45,7 @@ function clipCard(c) {thumb: abs(c.thumbnail_url), duration: c.duration_seconds,text: snip(c.description || c.ai_overview),view: `${config.publicUrl}/c/${c.id}`,-- source: `${appUrl(c.app_id)}/clip/${c.id}`, sourceLabel: 'Clip page',++ source: watchPageUrl('clip', c.app_id, c.id), sourceLabel: 'Clip page',};}function fileCard(f) {@@@ -76,8 +76,8 @@ function thumbCard(name, mtimeMs) {let source = null, sourceLabel = null;if (m) {const base = appUrl('live');-- if (m[1] === 'vod') { source = `${base}/vod/${m[2]}`; sourceLabel = 'VOD page'; }-- else if (m[1] === 'clip') { source = `${base}/clip/${m[2]}`; sourceLabel = 'Clip page'; }++ if (m[1] === 'vod') { source = watchPageUrl('vod', 'live', m[2]); sourceLabel = 'VOD page'; }++ else if (m[1] === 'clip') { source = watchPageUrl('clip', 'live', m[2]); sourceLabel = 'Clip page'; }else { source = `${base}/stream/${m[2]}`; sourceLabel = 'Stream'; }}return {@@@ -36,6 +36,14 @@ const APP_PUBLIC_URLS = (() => {return defaults;})();const appUrl = (appId) => APP_PUBLIC_URLS[appId] || APP_PUBLIC_URLS.live;++// Live's recordings and clips are watched on OpenVibe.Video since 2026-10-19: Live's /vod/<id> and /clip/<id> 301 there.++const VIDEO_PUBLIC_URL = String(process.env.VIDEO_PUBLIC_URL || 'https://openvibe.video').replace(/\/+$/, '');++/** The canonical watch page of an app's VOD or clip: OpenVibe.Video's for Live's, the app's own /vod or /clip otherwise. */++function watchPageUrl(kind, appId, id) {++ const app = appId || 'live';++ if (app === 'live') return `${VIDEO_PUBLIC_URL}/${kind === 'clip' ? 'c' : 'v'}/${id}`;++ return `${appUrl(app)}/${kind === 'clip' ? 'clip' : 'vod'}/${id}`;++}const esc = (s) => String(s ?? '').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''');const abs = (u) => (!u ? null : (/^https?:\/\//i.test(u) ? u : `${config.publicUrl}${u.startsWith('/') ? '' : '/'}${u}`));@@@ -215,7 +223,7 @@ function page({ seo, css = '', body, history, footer, summary }) {}module.exports = {-- NETWORK_URL, SITE_NAME, DEFAULT_OG_IMAGE, APP_PUBLIC_URLS, appUrl,++ NETWORK_URL, SITE_NAME, DEFAULT_OG_IMAGE, APP_PUBLIC_URLS, appUrl, VIDEO_PUBLIC_URL, watchPageUrl,esc, abs, snip, isoDate, isoDuration, fmtDuration, fmtDate, jsonForScript,extraHead, baseCss, navOptions, footerOptions, defaultFooterLinks, page, DEFAULT_THEME_CSS,};@@@ -17,7 +17,7 @@ const config = require('../config');const pageFrame = require('./page-frame');const showcase = require('openvibe-shared/showcase');--const { esc, abs, snip, appUrl, isoDate, isoDuration, fmtDuration, fmtDate, SITE_NAME, NETWORK_URL } = pageFrame;++const { esc, abs, snip, appUrl, watchPageUrl, isoDate, isoDuration, fmtDuration, fmtDate, SITE_NAME, NETWORK_URL } = pageFrame;const VIDEO_MIME = { '.mp4': 'video/mp4', '.webm': 'video/webm', '.mkv': 'video/x-matroska' };/** Link-preview and search crawlers: they want the page, never the bytes. */@@@ -45,10 +45,9 @@ function creatorLine(record) {return record.username || record.display_name || null;}--/** Where the owning app shows this item — Live's /vod and /clip pages. */++/** Where this item is watched: OpenVibe.Video for Live's, the owning app's own page otherwise. */function sourcePage(kind, record) {-- const base = appUrl(record.app_id || 'live');-- return `${base}/${kind === 'vod' ? 'vod' : 'clip'}/${record.id}`;++ return watchPageUrl(kind, record.app_id, record.id);}/** A clip the owning app's automation cut (AI moments), not a person. */@@@ -58,7 +57,7 @@ function isAiClip(kind, record) {/*** May search engines index this watch page? Only when it is public, Media is its canonical home (the-- * owning app is not Live, which serves its own /vod and /clip pages), and a person made it: AI clips++ * owning app is not Live, whose items are canonical on OpenVibe.Video), and a person made it: AI clips* are noindex, as on Live. The sitemap lists exactly these pages (server/public/crawl.js).*/function watchIndexable(kind, record) {@@@ -34,7 +34,11 @@ const http = require('http');};const html = pages.renderWatchPage('vod', vod);assert.ok(html.includes('<title>Late night <build> — OpenVibe.Media</title>'));-- assert.ok(html.includes('<link rel="canonical" href="https://openvibe.live/vod/42">'), 'Live-owned VOD is canonical on Live');++ assert.ok(html.includes('<link rel="canonical" href="https://openvibe.video/v/42">'), 'Live-owned VOD is canonical on OpenVibe.Video');++ // The browse cards' "VOD page" and "Clip page" links use the same rule: Live's on OpenVibe.Video, another app's own.++ assert.strictEqual(pageFrame.watchPageUrl('vod', 'live', 42), 'https://openvibe.video/v/42');++ assert.strictEqual(pageFrame.watchPageUrl('clip', null, 9), 'https://openvibe.video/c/9');++ assert.strictEqual(pageFrame.watchPageUrl('vod', 'games', 3), 'https://openvibe.games/vod/3');assert.ok(html.includes('<meta name="robots" content="noindex, follow">'));assert.ok(/\/shared\/theme-loader\.js\?v=[0-9a-f]{12}/.test(html), 'this site\'s own pinned Frame files');// The shell loads the theme-loader eagerly (no defer) inside <head>, so it applies the user's@@@ -59,7 +63,7 @@ const http = require('http');assert.strictEqual(ld.duration, 'PT1H2M5S');assert.strictEqual(ld.uploadDate, '2026-09-10T20:15:00.000Z');assert.deepStrictEqual(ld.thumbnailUrl, ['https://media.test/t/vod-42-1.jpg']);-- assert.strictEqual(ld.url, 'https://openvibe.live/vod/42');++ assert.strictEqual(ld.url, 'https://openvibe.video/v/42');assert.ok(!html.includes('</script>"'), 'no script-closing sequence smuggled through JSON');const other = pages.renderWatchPage('clip', { ...vod, id: 7, app_id: 'games', title: 'Frag' });@@@ -70,7 +74,7 @@ const http = require('http');// The AI summary (openvibe-shared/seo pageSummary) on public watch pages: description, canonical, facts.assert.ok(html.includes('<meta name="ai-summary" content="We built a thing & it worked.">'), 'the watch page carries the AI summary');assert.ok(html.includes('<li>Duration: 1:02:05</li>') && html.includes('<li>Views: 1,234</li>'), 'with its facts');-- assert.ok(html.includes('"@type":"WebPage"') && html.includes('"url":"https://openvibe.live/vod/42"'), 'its WebPage names the canonical');++ assert.ok(html.includes('"@type":"WebPage"') && html.includes('"url":"https://openvibe.video/v/42"'), 'its WebPage names the canonical');assert.ok(!unlisted.includes('name="ai-summary"'), 'unlisted pages carry none');// ── Next-step cards (Shared ADR 0002, D94): real, signed-out targets, ov_from=media, at most four ──@@@ -88,16 +92,16 @@ const http = require('http');};// A playable public Live-owned VOD: the Live card (the canonical) and the index; no clip-cutting card on a VOD.assert.ok(html.includes('class="sc-sec sc-next"') && html.includes('<ol class="sc-next-list">'), 'a playable public VOD page carries the next-step cards');-- assert.deepStrictEqual(checkCards(html, 'Live VOD'), ['https://openvibe.live/vod/42?ov_from=media', 'https://media.test/?ov_from=media'],++ assert.deepStrictEqual(checkCards(html, 'Live VOD'), ['https://openvibe.video/v/42?ov_from=media', 'https://media.test/?ov_from=media'],'a Live-owned item\'s card is its canonical; the index card is always there');// A VOD another app owns: only the index.assert.deepStrictEqual(checkCards(pages.renderWatchPage('vod', { ...vod, id: 43, app_id: 'games' }), 'games VOD'), ['https://media.test/?ov_from=media']);// A playable person-cut clip on Live: Live, cut your own, the index.assert.deepStrictEqual(checkCards(pages.renderWatchPage('clip', { ...vod, id: 9 }), 'Live clip'),-- ['https://openvibe.live/clip/9?ov_from=media', 'https://openvibe.live/?ov_from=media', 'https://media.test/?ov_from=media']);++ ['https://openvibe.video/c/9?ov_from=media', 'https://openvibe.live/?ov_from=media', 'https://media.test/?ov_from=media']);// An AI clip with a source VOD: every card applies, and four is the most that render.const aiClip = pages.renderWatchPage('clip', { ...vod, id: 10, auto_generated: 1, vod_id: 42 });-- assert.deepStrictEqual(checkCards(aiClip, 'AI clip'), ['https://openvibe.live/clip/10?ov_from=media', 'https://media.test/v/42?ov_from=media',++ assert.deepStrictEqual(checkCards(aiClip, 'AI clip'), ['https://openvibe.video/c/10?ov_from=media', 'https://media.test/v/42?ov_from=media','https://openvibe.live/?ov_from=media', 'https://media.test/?ov_from=media'], 'a clip with a source VOD shows the VOD card');assert.ok(aiClip.includes('See the VOD this clip came from'));assert.strictEqual((aiClip.match(/class="sc-next-item"/g) || []).length, 4, 'at most four cards');
@@ -19,7 +19,7 @@ const THUMB_DIR = path.resolve(config.thumbnails.path);
@@ -19,7 +19,7 @@ const THUMB_DIR = path.resolve(config.thumbnails.path);
// Public base URL per app for "source" links — shared with the watch pages.
// Public base URL per app for "source" links — shared with the watch pages.
const pageFrame = require('./page-frame');
const pageFrame = require('./page-frame');
-const { appUrl, isoDate } = pageFrame;
+const { appUrl, watchPageUrl, isoDate } = pageFrame;
const cache = require('openvibe-shared/cache-policy');
const cache = require('openvibe-shared/cache-policy');
const frame = require('openvibe-shared/frame');
const frame = require('openvibe-shared/frame');
const showcase = require('openvibe-shared/showcase');
const showcase = require('openvibe-shared/showcase');
@@ -36,7 +36,7 @@ function vodCard(v) {
@@ -36,7 +36,7 @@ function vodCard(v) {
thumb: abs(v.thumbnail_url), duration: v.duration_seconds,
thumb: abs(v.thumbnail_url), duration: v.duration_seconds,
text: snip(v.description || v.ai_overview),
text: snip(v.description || v.ai_overview),
view: `${config.publicUrl}/v/${v.id}`,
view: `${config.publicUrl}/v/${v.id}`,
- source: `${appUrl(v.app_id)}/vod/${v.id}`, sourceLabel: 'VOD page',
+ source: watchPageUrl('vod', v.app_id, v.id), sourceLabel: 'VOD page',
};
};
}
}
function clipCard(c) {
function clipCard(c) {
@@ -45,7 +45,7 @@ function clipCard(c) {
@@ -45,7 +45,7 @@ function clipCard(c) {
thumb: abs(c.thumbnail_url), duration: c.duration_seconds,
thumb: abs(c.thumbnail_url), duration: c.duration_seconds,
text: snip(c.description || c.ai_overview),
text: snip(c.description || c.ai_overview),
view: `${config.publicUrl}/c/${c.id}`,
view: `${config.publicUrl}/c/${c.id}`,
- source: `${appUrl(c.app_id)}/clip/${c.id}`, sourceLabel: 'Clip page',
+ source: watchPageUrl('clip', c.app_id, c.id), sourceLabel: 'Clip page',
};
};
}
}
function fileCard(f) {
function fileCard(f) {
@@ -76,8 +76,8 @@ function thumbCard(name, mtimeMs) {
@@ -76,8 +76,8 @@ function thumbCard(name, mtimeMs) {
let source = null, sourceLabel = null;
let source = null, sourceLabel = null;
if (m) {
if (m) {
const base = appUrl('live');
const base = appUrl('live');
- if (m[1] === 'vod') { source = `${base}/vod/${m[2]}`; sourceLabel = 'VOD page'; }
- else if (m[1] === 'clip') { source = `${base}/clip/${m[2]}`; sourceLabel = 'Clip page'; }
+ if (m[1] === 'vod') { source = watchPageUrl('vod', 'live', m[2]); sourceLabel = 'VOD page'; }
+ else if (m[1] === 'clip') { source = watchPageUrl('clip', 'live', m[2]); sourceLabel = 'Clip page'; }
else { source = `${base}/stream/${m[2]}`; sourceLabel = 'Stream'; }
else { source = `${base}/stream/${m[2]}`; sourceLabel = 'Stream'; }
}
}
return {
return {
@@ -36,6 +36,14 @@ const APP_PUBLIC_URLS = (() => {
@@ -36,6 +36,14 @@ const APP_PUBLIC_URLS = (() => {
return defaults;
return defaults;
})();
})();
const appUrl = (appId) => APP_PUBLIC_URLS[appId] || APP_PUBLIC_URLS.live;
const appUrl = (appId) => APP_PUBLIC_URLS[appId] || APP_PUBLIC_URLS.live;
+// Live's recordings and clips are watched on OpenVibe.Video since 2026-10-19: Live's /vod/<id> and /clip/<id> 301 there.
+const VIDEO_PUBLIC_URL = String(process.env.VIDEO_PUBLIC_URL || 'https://openvibe.video').replace(/\/+$/, '');
+/** The canonical watch page of an app's VOD or clip: OpenVibe.Video's for Live's, the app's own /vod or /clip otherwise. */
+function watchPageUrl(kind, appId, id) {
+ const app = appId || 'live';
+ if (app === 'live') return `${VIDEO_PUBLIC_URL}/${kind === 'clip' ? 'c' : 'v'}/${id}`;
+ return `${appUrl(app)}/${kind === 'clip' ? 'clip' : 'vod'}/${id}`;
+}
const esc = (s) => String(s ?? '').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''');
const esc = (s) => String(s ?? '').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''');
const abs = (u) => (!u ? null : (/^https?:\/\//i.test(u) ? u : `${config.publicUrl}${u.startsWith('/') ? '' : '/'}${u}`));
const abs = (u) => (!u ? null : (/^https?:\/\//i.test(u) ? u : `${config.publicUrl}${u.startsWith('/') ? '' : '/'}${u}`));
@@ -215,7 +223,7 @@ function page({ seo, css = '', body, history, footer, summary }) {
@@ -215,7 +223,7 @@ function page({ seo, css = '', body, history, footer, summary }) {
}
}
module.exports = {
module.exports = {
- NETWORK_URL, SITE_NAME, DEFAULT_OG_IMAGE, APP_PUBLIC_URLS, appUrl,
+ NETWORK_URL, SITE_NAME, DEFAULT_OG_IMAGE, APP_PUBLIC_URLS, appUrl, VIDEO_PUBLIC_URL, watchPageUrl,
esc, abs, snip, isoDate, isoDuration, fmtDuration, fmtDate, jsonForScript,
esc, abs, snip, isoDate, isoDuration, fmtDuration, fmtDate, jsonForScript,
extraHead, baseCss, navOptions, footerOptions, defaultFooterLinks, page, DEFAULT_THEME_CSS,
extraHead, baseCss, navOptions, footerOptions, defaultFooterLinks, page, DEFAULT_THEME_CSS,
};
};
@@ -17,7 +17,7 @@ const config = require('../config');
@@ -17,7 +17,7 @@ const config = require('../config');
const pageFrame = require('./page-frame');
const pageFrame = require('./page-frame');
const showcase = require('openvibe-shared/showcase');
const showcase = require('openvibe-shared/showcase');
-const { esc, abs, snip, appUrl, isoDate, isoDuration, fmtDuration, fmtDate, SITE_NAME, NETWORK_URL } = pageFrame;
+const { esc, abs, snip, appUrl, watchPageUrl, isoDate, isoDuration, fmtDuration, fmtDate, SITE_NAME, NETWORK_URL } = pageFrame;
const VIDEO_MIME = { '.mp4': 'video/mp4', '.webm': 'video/webm', '.mkv': 'video/x-matroska' };
const VIDEO_MIME = { '.mp4': 'video/mp4', '.webm': 'video/webm', '.mkv': 'video/x-matroska' };
/** Link-preview and search crawlers: they want the page, never the bytes. */
/** Link-preview and search crawlers: they want the page, never the bytes. */
@@ -45,10 +45,9 @@ function creatorLine(record) {
@@ -45,10 +45,9 @@ function creatorLine(record) {
return record.username || record.display_name || null;
return record.username || record.display_name || null;
}
}
-/** Where the owning app shows this item — Live's /vod and /clip pages. */
+/** Where this item is watched: OpenVibe.Video for Live's, the owning app's own page otherwise. */
function sourcePage(kind, record) {
function sourcePage(kind, record) {
- const base = appUrl(record.app_id || 'live');
- return `${base}/${kind === 'vod' ? 'vod' : 'clip'}/${record.id}`;
+ return watchPageUrl(kind, record.app_id, record.id);
}
}
/** A clip the owning app's automation cut (AI moments), not a person. */
/** A clip the owning app's automation cut (AI moments), not a person. */
@@ -58,7 +57,7 @@ function isAiClip(kind, record) {
@@ -58,7 +57,7 @@ function isAiClip(kind, record) {
/**
/**
* May search engines index this watch page? Only when it is public, Media is its canonical home (the
* May search engines index this watch page? Only when it is public, Media is its canonical home (the
- * owning app is not Live, which serves its own /vod and /clip pages), and a person made it: AI clips
+ * owning app is not Live, whose items are canonical on OpenVibe.Video), and a person made it: AI clips
* are noindex, as on Live. The sitemap lists exactly these pages (server/public/crawl.js).
* are noindex, as on Live. The sitemap lists exactly these pages (server/public/crawl.js).
*/
*/
function watchIndexable(kind, record) {
function watchIndexable(kind, record) {
@@ -34,7 +34,11 @@ const http = require('http');
@@ -34,7 +34,11 @@ const http = require('http');
};
};
const html = pages.renderWatchPage('vod', vod);
const html = pages.renderWatchPage('vod', vod);
assert.ok(html.includes('<title>Late night <build> — OpenVibe.Media</title>'));
assert.ok(html.includes('<title>Late night <build> — OpenVibe.Media</title>'));
- assert.ok(html.includes('<link rel="canonical" href="https://openvibe.live/vod/42">'), 'Live-owned VOD is canonical on Live');
+ assert.ok(html.includes('<link rel="canonical" href="https://openvibe.video/v/42">'), 'Live-owned VOD is canonical on OpenVibe.Video');
+ // The browse cards' "VOD page" and "Clip page" links use the same rule: Live's on OpenVibe.Video, another app's own.
+ assert.strictEqual(pageFrame.watchPageUrl('vod', 'live', 42), 'https://openvibe.video/v/42');
+ assert.strictEqual(pageFrame.watchPageUrl('clip', null, 9), 'https://openvibe.video/c/9');
+ assert.strictEqual(pageFrame.watchPageUrl('vod', 'games', 3), 'https://openvibe.games/vod/3');
assert.ok(html.includes('<meta name="robots" content="noindex, follow">'));
assert.ok(html.includes('<meta name="robots" content="noindex, follow">'));
assert.ok(/\/shared\/theme-loader\.js\?v=[0-9a-f]{12}/.test(html), 'this site\'s own pinned Frame files');
assert.ok(/\/shared\/theme-loader\.js\?v=[0-9a-f]{12}/.test(html), 'this site\'s own pinned Frame files');
// The shell loads the theme-loader eagerly (no defer) inside <head>, so it applies the user's
// The shell loads the theme-loader eagerly (no defer) inside <head>, so it applies the user's
@@ -59,7 +63,7 @@ const http = require('http');
@@ -59,7 +63,7 @@ const http = require('http');
assert.strictEqual(ld.duration, 'PT1H2M5S');
assert.strictEqual(ld.duration, 'PT1H2M5S');
assert.strictEqual(ld.uploadDate, '2026-09-10T20:15:00.000Z');
assert.strictEqual(ld.uploadDate, '2026-09-10T20:15:00.000Z');
assert.deepStrictEqual(ld.thumbnailUrl, ['https://media.test/t/vod-42-1.jpg']);
assert.deepStrictEqual(ld.thumbnailUrl, ['https://media.test/t/vod-42-1.jpg']);
- assert.strictEqual(ld.url, 'https://openvibe.live/vod/42');
+ assert.strictEqual(ld.url, 'https://openvibe.video/v/42');
assert.ok(!html.includes('</script>"'), 'no script-closing sequence smuggled through JSON');
assert.ok(!html.includes('</script>"'), 'no script-closing sequence smuggled through JSON');
const other = pages.renderWatchPage('clip', { ...vod, id: 7, app_id: 'games', title: 'Frag' });
const other = pages.renderWatchPage('clip', { ...vod, id: 7, app_id: 'games', title: 'Frag' });
@@ -70,7 +74,7 @@ const http = require('http');
@@ -70,7 +74,7 @@ const http = require('http');
// The AI summary (openvibe-shared/seo pageSummary) on public watch pages: description, canonical, facts.
// The AI summary (openvibe-shared/seo pageSummary) on public watch pages: description, canonical, facts.
assert.ok(html.includes('<meta name="ai-summary" content="We built a thing & it worked.">'), 'the watch page carries the AI summary');
assert.ok(html.includes('<meta name="ai-summary" content="We built a thing & it worked.">'), 'the watch page carries the AI summary');
assert.ok(html.includes('<li>Duration: 1:02:05</li>') && html.includes('<li>Views: 1,234</li>'), 'with its facts');
assert.ok(html.includes('<li>Duration: 1:02:05</li>') && html.includes('<li>Views: 1,234</li>'), 'with its facts');
- assert.ok(html.includes('"@type":"WebPage"') && html.includes('"url":"https://openvibe.live/vod/42"'), 'its WebPage names the canonical');
+ assert.ok(html.includes('"@type":"WebPage"') && html.includes('"url":"https://openvibe.video/v/42"'), 'its WebPage names the canonical');
assert.ok(!unlisted.includes('name="ai-summary"'), 'unlisted pages carry none');
assert.ok(!unlisted.includes('name="ai-summary"'), 'unlisted pages carry none');
// ── Next-step cards (Shared ADR 0002, D94): real, signed-out targets, ov_from=media, at most four ──
// ── Next-step cards (Shared ADR 0002, D94): real, signed-out targets, ov_from=media, at most four ──
@@ -88,16 +92,16 @@ const http = require('http');
@@ -88,16 +92,16 @@ const http = require('http');
};
};
// A playable public Live-owned VOD: the Live card (the canonical) and the index; no clip-cutting card on a VOD.
// A playable public Live-owned VOD: the Live card (the canonical) and the index; no clip-cutting card on a VOD.
assert.ok(html.includes('class="sc-sec sc-next"') && html.includes('<ol class="sc-next-list">'), 'a playable public VOD page carries the next-step cards');
assert.ok(html.includes('class="sc-sec sc-next"') && html.includes('<ol class="sc-next-list">'), 'a playable public VOD page carries the next-step cards');
- assert.deepStrictEqual(checkCards(html, 'Live VOD'), ['https://openvibe.live/vod/42?ov_from=media', 'https://media.test/?ov_from=media'],
+ assert.deepStrictEqual(checkCards(html, 'Live VOD'), ['https://openvibe.video/v/42?ov_from=media', 'https://media.test/?ov_from=media'],
'a Live-owned item\'s card is its canonical; the index card is always there');
'a Live-owned item\'s card is its canonical; the index card is always there');
// A VOD another app owns: only the index.
// A VOD another app owns: only the index.
assert.deepStrictEqual(checkCards(pages.renderWatchPage('vod', { ...vod, id: 43, app_id: 'games' }), 'games VOD'), ['https://media.test/?ov_from=media']);
assert.deepStrictEqual(checkCards(pages.renderWatchPage('vod', { ...vod, id: 43, app_id: 'games' }), 'games VOD'), ['https://media.test/?ov_from=media']);
// A playable person-cut clip on Live: Live, cut your own, the index.
// A playable person-cut clip on Live: Live, cut your own, the index.
assert.deepStrictEqual(checkCards(pages.renderWatchPage('clip', { ...vod, id: 9 }), 'Live clip'),
assert.deepStrictEqual(checkCards(pages.renderWatchPage('clip', { ...vod, id: 9 }), 'Live clip'),
- ['https://openvibe.live/clip/9?ov_from=media', 'https://openvibe.live/?ov_from=media', 'https://media.test/?ov_from=media']);
+ ['https://openvibe.video/c/9?ov_from=media', 'https://openvibe.live/?ov_from=media', 'https://media.test/?ov_from=media']);
// An AI clip with a source VOD: every card applies, and four is the most that render.
// An AI clip with a source VOD: every card applies, and four is the most that render.
const aiClip = pages.renderWatchPage('clip', { ...vod, id: 10, auto_generated: 1, vod_id: 42 });
const aiClip = pages.renderWatchPage('clip', { ...vod, id: 10, auto_generated: 1, vod_id: 42 });
- assert.deepStrictEqual(checkCards(aiClip, 'AI clip'), ['https://openvibe.live/clip/10?ov_from=media', 'https://media.test/v/42?ov_from=media',
+ assert.deepStrictEqual(checkCards(aiClip, 'AI clip'), ['https://openvibe.video/c/10?ov_from=media', 'https://media.test/v/42?ov_from=media',
'https://openvibe.live/?ov_from=media', 'https://media.test/?ov_from=media'], 'a clip with a source VOD shows the VOD card');
'https://openvibe.live/?ov_from=media', 'https://media.test/?ov_from=media'], 'a clip with a source VOD shows the VOD card');
assert.ok(aiClip.includes('See the VOD this clip came from'));
assert.ok(aiClip.includes('See the VOD this clip came from'));
assert.strictEqual((aiClip.match(/class="sc-next-item"/g) || []).length, 4, 'at most four cards');
assert.strictEqual((aiClip.match(/class="sc-next-item"/g) || []).length, 4, 'at most four cards');