openvibers/openvibe.community Commits
Keep current Live item when older VOD is nearby (#373)
Changes
@@@ -288,7 +288,7 @@ function createPulseSources({ db, config = {}, fetchImpl = globalThis.fetch, nowconst id = Number(s && s.id), channel = clean(s && s.username, 64);const started = sqlFrom(s && s.started_at, null);if (!Number.isSafeInteger(id) || id < 1 || !/^[A-Za-z0-9_.-]{1,64}$/.test(channel) || !started) continue;-- onAir.set(channel.toLowerCase(), Date.parse(`${started.replace(' ', 'T')}Z`));++ onAir.set(channel.toLowerCase(), { id: String(id), started: Date.parse(`${started.replace(' ', 'T')}Z`) });const name = clean(s.display_name || channel, 80);const r = await store.upsertItem(db, { source_service: 'live', source_type: 'stream', source_id: String(id),title: `${name} went live: ${clean(s.title, 160) || 'a stream'}`, url: `${liveApi}/@${encodeURIComponent(channel)}`,@@@ -311,15 +311,15 @@ function createPulseSources({ db, config = {}, fetchImpl = globalThis.fetch, nowconst at = sqlFrom(v.created_at, null);if (!at) continue;const channel = clean(v.channel && v.channel.username, 64);-- const currentStart = onAir.get(channel.toLowerCase());-- if (type === 'recording' && Number.isFinite(currentStart)-- && Math.abs(Date.parse(`${at.replace(' ', 'T')}Z`) - currentStart) <= 10 * MINUTE) continue;++ const current = onAir.get(channel.toLowerCase());++ if (type === 'recording' && current && Number.isFinite(current.started)++ && Math.abs(Date.parse(`${at.replace(' ', 'T')}Z`) - current.started) <= 10 * MINUTE) continue;seen.add(`${type}:${id}`);let prior = null;if (type === 'recording' && channel) {prior = await db.prepare(`SELECT actor_subject, source_id FROM pulse_items WHERE source_service = 'live' AND source_type = 'stream'-- AND lower(url) = lower(?) AND occurred_at BETWEEN ? AND ? ORDER BY occurred_at DESC LIMIT 1`)-- .get(`${liveApi}/@${encodeURIComponent(channel)}`, sqlTime(Date.parse(`${at.replace(' ', 'T')}Z`) - 60 * MINUTE), sqlTime(Date.parse(`${at.replace(' ', 'T')}Z`) + 60 * MINUTE));++ AND lower(url) = lower(?) AND source_id <> ? AND occurred_at BETWEEN ? AND ? ORDER BY occurred_at DESC LIMIT 1`)++ .get(`${liveApi}/@${encodeURIComponent(channel)}`, current && current.id || '', sqlTime(Date.parse(`${at.replace(' ', 'T')}Z`) - 60 * MINUTE), sqlTime(Date.parse(`${at.replace(' ', 'T')}Z`) + 60 * MINUTE));}const r = await store.upsertItem(db, { source_service: 'live', source_type: type, source_id: id,title: `${type === 'recording' ? 'Recording: ' : ''}${clean(v.title, 150) || (type === 'recording' ? 'A stream' : 'A clip')}`,@@@ -15,6 +15,7 @@ const day = (ms) => new Date(ms).toISOString().slice(0, 10);const badgeAt = iso(Date.now() - 3_600_000);const calls = [];let liveStreams = [];++ let extraContent = [];let galleryRows = [{ id: 'pic_01JAB2C3D4E5F6G7H8J9K0MNPQ', title: 'First photo', author: 'Alex', listed: true, published_at: badgeAt,links: { page: 'https://openvibe.pics/i/firstpic', thumb: 'https://openvibe.pics/i/firstpic/thumb.webp' }, width: 800, height: 600 },@@@ -42,6 +43,7 @@ const day = (ms) => new Date(ms).toISOString().slice(0, 10);if (u.includes('/api/content/feed')) return { ok: true, json: async () => ({ items: [{ id: 123, kind: 'vod', title: 'Night stream', href: 'https://openvibe.video/watch?v=night', thumbnail_url: 'https://openvibe.media/night.webp', created_at: badgeAt, channel: { username: 'alex', display_name: 'Alex' }, duration_seconds: 500 },{ id: 124, kind: 'clip', title: 'Night clip', href: 'https://openvibe.video/clip/night', thumbnail_url: 'https://openvibe.media/clip.webp', created_at: badgeAt, channel: { username: 'alex', display_name: 'Alex' }, duration_seconds: 25 },++ ...extraContent,], next: null, sources: { vods: 'ok', clips: 'ok' }, partial: false }) };if (u.includes('/api/v1/apps/gallery')) return { ok: true, json: async () => ({ spaces: [{id: 'spc_01JAB2C3D4E5F6G7H8J9K0MNPQ', name: 'A shared map', description: 'A map of places',@@@ -155,6 +157,8 @@ const day = (ms) => new Date(ms).toISOString().slice(0, 10);const db = t.app.locals.db;const item = (type, id) => db.prepare('SELECT title FROM pulse_items WHERE source_service = ? AND source_type = ? AND source_id = ?').get('live', type, String(id));liveStreams = [{ id: 999, username: 'alex', display_name: 'Alex', title: 'Night stream', started_at: badgeAt }];++ extraContent = [{ id: 125, kind: 'vod', title: 'Earlier broadcast', href: 'https://openvibe.video/watch?v=earlier',++ created_at: iso(Date.parse(badgeAt) - 20 * 60_000), channel: { username: 'alex', display_name: 'Alex' } }];await t.app.locals.pulseSources.runOnce({ force: true });assert.ok(await item('stream', 999));assert.ok(!(await item('recording', 123)), 'a recording for the broadcast is suppressed while it is live');
@@ -288,7 +288,7 @@ function createPulseSources({ db, config = {}, fetchImpl = globalThis.fetch, now
@@ -288,7 +288,7 @@ function createPulseSources({ db, config = {}, fetchImpl = globalThis.fetch, now
const id = Number(s && s.id), channel = clean(s && s.username, 64);
const id = Number(s && s.id), channel = clean(s && s.username, 64);
const started = sqlFrom(s && s.started_at, null);
const started = sqlFrom(s && s.started_at, null);
if (!Number.isSafeInteger(id) || id < 1 || !/^[A-Za-z0-9_.-]{1,64}$/.test(channel) || !started) continue;
if (!Number.isSafeInteger(id) || id < 1 || !/^[A-Za-z0-9_.-]{1,64}$/.test(channel) || !started) continue;
- onAir.set(channel.toLowerCase(), Date.parse(`${started.replace(' ', 'T')}Z`));
+ onAir.set(channel.toLowerCase(), { id: String(id), started: Date.parse(`${started.replace(' ', 'T')}Z`) });
const name = clean(s.display_name || channel, 80);
const name = clean(s.display_name || channel, 80);
const r = await store.upsertItem(db, { source_service: 'live', source_type: 'stream', source_id: String(id),
const r = await store.upsertItem(db, { source_service: 'live', source_type: 'stream', source_id: String(id),
title: `${name} went live: ${clean(s.title, 160) || 'a stream'}`, url: `${liveApi}/@${encodeURIComponent(channel)}`,
title: `${name} went live: ${clean(s.title, 160) || 'a stream'}`, url: `${liveApi}/@${encodeURIComponent(channel)}`,
@@ -311,15 +311,15 @@ function createPulseSources({ db, config = {}, fetchImpl = globalThis.fetch, now
@@ -311,15 +311,15 @@ function createPulseSources({ db, config = {}, fetchImpl = globalThis.fetch, now
const at = sqlFrom(v.created_at, null);
const at = sqlFrom(v.created_at, null);
if (!at) continue;
if (!at) continue;
const channel = clean(v.channel && v.channel.username, 64);
const channel = clean(v.channel && v.channel.username, 64);
- const currentStart = onAir.get(channel.toLowerCase());
- if (type === 'recording' && Number.isFinite(currentStart)
- && Math.abs(Date.parse(`${at.replace(' ', 'T')}Z`) - currentStart) <= 10 * MINUTE) continue;
+ const current = onAir.get(channel.toLowerCase());
+ if (type === 'recording' && current && Number.isFinite(current.started)
+ && Math.abs(Date.parse(`${at.replace(' ', 'T')}Z`) - current.started) <= 10 * MINUTE) continue;
seen.add(`${type}:${id}`);
seen.add(`${type}:${id}`);
let prior = null;
let prior = null;
if (type === 'recording' && channel) {
if (type === 'recording' && channel) {
prior = await db.prepare(`SELECT actor_subject, source_id FROM pulse_items WHERE source_service = 'live' AND source_type = 'stream'
prior = await db.prepare(`SELECT actor_subject, source_id FROM pulse_items WHERE source_service = 'live' AND source_type = 'stream'
- AND lower(url) = lower(?) AND occurred_at BETWEEN ? AND ? ORDER BY occurred_at DESC LIMIT 1`)
- .get(`${liveApi}/@${encodeURIComponent(channel)}`, sqlTime(Date.parse(`${at.replace(' ', 'T')}Z`) - 60 * MINUTE), sqlTime(Date.parse(`${at.replace(' ', 'T')}Z`) + 60 * MINUTE));
+ AND lower(url) = lower(?) AND source_id <> ? AND occurred_at BETWEEN ? AND ? ORDER BY occurred_at DESC LIMIT 1`)
+ .get(`${liveApi}/@${encodeURIComponent(channel)}`, current && current.id || '', sqlTime(Date.parse(`${at.replace(' ', 'T')}Z`) - 60 * MINUTE), sqlTime(Date.parse(`${at.replace(' ', 'T')}Z`) + 60 * MINUTE));
}
}
const r = await store.upsertItem(db, { source_service: 'live', source_type: type, source_id: id,
const r = await store.upsertItem(db, { source_service: 'live', source_type: type, source_id: id,
title: `${type === 'recording' ? 'Recording: ' : ''}${clean(v.title, 150) || (type === 'recording' ? 'A stream' : 'A clip')}`,
title: `${type === 'recording' ? 'Recording: ' : ''}${clean(v.title, 150) || (type === 'recording' ? 'A stream' : 'A clip')}`,
@@ -15,6 +15,7 @@ const day = (ms) => new Date(ms).toISOString().slice(0, 10);
@@ -15,6 +15,7 @@ const day = (ms) => new Date(ms).toISOString().slice(0, 10);
const badgeAt = iso(Date.now() - 3_600_000);
const badgeAt = iso(Date.now() - 3_600_000);
const calls = [];
const calls = [];
let liveStreams = [];
let liveStreams = [];
+ let extraContent = [];
let galleryRows = [
let galleryRows = [
{ id: 'pic_01JAB2C3D4E5F6G7H8J9K0MNPQ', title: 'First photo', author: 'Alex', listed: true, published_at: badgeAt,
{ id: 'pic_01JAB2C3D4E5F6G7H8J9K0MNPQ', title: 'First photo', author: 'Alex', listed: true, published_at: badgeAt,
links: { page: 'https://openvibe.pics/i/firstpic', thumb: 'https://openvibe.pics/i/firstpic/thumb.webp' }, width: 800, height: 600 },
links: { page: 'https://openvibe.pics/i/firstpic', thumb: 'https://openvibe.pics/i/firstpic/thumb.webp' }, width: 800, height: 600 },
@@ -42,6 +43,7 @@ const day = (ms) => new Date(ms).toISOString().slice(0, 10);
@@ -42,6 +43,7 @@ const day = (ms) => new Date(ms).toISOString().slice(0, 10);
if (u.includes('/api/content/feed')) return { ok: true, json: async () => ({ items: [
if (u.includes('/api/content/feed')) return { ok: true, json: async () => ({ items: [
{ id: 123, kind: 'vod', title: 'Night stream', href: 'https://openvibe.video/watch?v=night', thumbnail_url: 'https://openvibe.media/night.webp', created_at: badgeAt, channel: { username: 'alex', display_name: 'Alex' }, duration_seconds: 500 },
{ id: 123, kind: 'vod', title: 'Night stream', href: 'https://openvibe.video/watch?v=night', thumbnail_url: 'https://openvibe.media/night.webp', created_at: badgeAt, channel: { username: 'alex', display_name: 'Alex' }, duration_seconds: 500 },
{ id: 124, kind: 'clip', title: 'Night clip', href: 'https://openvibe.video/clip/night', thumbnail_url: 'https://openvibe.media/clip.webp', created_at: badgeAt, channel: { username: 'alex', display_name: 'Alex' }, duration_seconds: 25 },
{ id: 124, kind: 'clip', title: 'Night clip', href: 'https://openvibe.video/clip/night', thumbnail_url: 'https://openvibe.media/clip.webp', created_at: badgeAt, channel: { username: 'alex', display_name: 'Alex' }, duration_seconds: 25 },
+ ...extraContent,
], next: null, sources: { vods: 'ok', clips: 'ok' }, partial: false }) };
], next: null, sources: { vods: 'ok', clips: 'ok' }, partial: false }) };
if (u.includes('/api/v1/apps/gallery')) return { ok: true, json: async () => ({ spaces: [{
if (u.includes('/api/v1/apps/gallery')) return { ok: true, json: async () => ({ spaces: [{
id: 'spc_01JAB2C3D4E5F6G7H8J9K0MNPQ', name: 'A shared map', description: 'A map of places',
id: 'spc_01JAB2C3D4E5F6G7H8J9K0MNPQ', name: 'A shared map', description: 'A map of places',
@@ -155,6 +157,8 @@ const day = (ms) => new Date(ms).toISOString().slice(0, 10);
@@ -155,6 +157,8 @@ const day = (ms) => new Date(ms).toISOString().slice(0, 10);
const db = t.app.locals.db;
const db = t.app.locals.db;
const item = (type, id) => db.prepare('SELECT title FROM pulse_items WHERE source_service = ? AND source_type = ? AND source_id = ?').get('live', type, String(id));
const item = (type, id) => db.prepare('SELECT title FROM pulse_items WHERE source_service = ? AND source_type = ? AND source_id = ?').get('live', type, String(id));
liveStreams = [{ id: 999, username: 'alex', display_name: 'Alex', title: 'Night stream', started_at: badgeAt }];
liveStreams = [{ id: 999, username: 'alex', display_name: 'Alex', title: 'Night stream', started_at: badgeAt }];
+ extraContent = [{ id: 125, kind: 'vod', title: 'Earlier broadcast', href: 'https://openvibe.video/watch?v=earlier',
+ created_at: iso(Date.parse(badgeAt) - 20 * 60_000), channel: { username: 'alex', display_name: 'Alex' } }];
await t.app.locals.pulseSources.runOnce({ force: true });
await t.app.locals.pulseSources.runOnce({ force: true });
assert.ok(await item('stream', 999));
assert.ok(await item('stream', 999));
assert.ok(!(await item('recording', 123)), 'a recording for the broadcast is suppressed while it is live');
assert.ok(!(await item('recording', 123)), 'a recording for the broadcast is suppressed while it is live');