openvibers/openvibe.live Commits
Live: the product at openre.stream is OpenRestream (owner, 2026-10-09) (#63)
Changes
@@@ -67,7 +67,7 @@ OV_OAUTH_CLIENT_SECRET=# Unset = both paths, as before.#GOLIVE_NOTIFY=events--# OpenRe.Stream (roadmap Wave 7): the ingest/restream authority. Unset = off, and every stream slot++# OpenRestream (roadmap Wave 7): the ingest/restream authority. Unset = off, and every stream slot# is ingested by Live exactly as before. When set, an admin can switch one slot at a time# (PUT /api/admin/openre/managed/:id/ingest-authority); RTMP, WHIP and JSMPEG move with it. Live's service principal# (the OAuth client above) needs openre.stream.read, openre.stream.write, openre.key.rotate and@@@ -68,7 +68,7 @@ The media subsystem lives in **OpenVibe.Media**:- Streaming ingest and viewer playback: RTMP (`node-media-server`), WHIP and WebRTC (`mediasoup`), JSMPEG, and the broadcast page.- Channels and streams: go-live, stream keys, managed streams, heartbeats, channel pages (`/@username`), offline screens, panels and goals.- The watch experience: the SPA in `public/`, the chat surface and its effects (TTS, sounds, emotes, overlays), anonymous chat identities.--- Streamer tools: the dashboard, restreaming (via OpenRe for managed ingest), song requests (watch party), AI viewers (the context Live builds; the runs are OpenVibe.AI's).++- Streamer tools: the dashboard, restreaming (via OpenRestream for managed ingest), song requests (watch party), AI viewers (the context Live builds; the runs are OpenVibe.AI's).- Channel points, Vibes tipping flows (the ledger moves to OpenVibe.Billing when `BILLING_AUTHORITY=billing`), moderation of Live's own surfaces, the Arena (Battle Cam) and after-show recaps.- Live-local state: PostgreSQL `ov_live` (users' Live profiles, streams, channel state, AI state for Media-hosted recordings in `vod_ai_state` / `clip_ai_state`, request analytics).@@@ -85,7 +85,7 @@ The media subsystem lives in **OpenVibe.Media**:- **OpenVibe.Network**: sign-in (OAuth2 client `live`), the signing key, service tokens (`server/net/network-principal.js`), follows, notifications, the wallet, the OpenVibe Frame (`openvibe-shared`, served at `/shared/*`).- **OpenVibe.Media**: VOD recording, storage (local, B2, R2), playback, clips, thumbnails, signed playback URLs. Outcomes arrive as `media.*` events at `POST /internal/media-events`.--- **OpenVibe.Chat**, **OpenVibe.Community**, **OpenVibe.AI**, **OpenVibe.Events**, **OpenRe.Stream**, **OpenVibe.Billing**, **OpenVibe.Tips**, **OpenVibe.VIP**, **OpenVibe.Tools**, **OpenVibe.Search**: through their APIs with Live's service token.++- **OpenVibe.Chat**, **OpenVibe.Community**, **OpenVibe.AI**, **OpenVibe.Events**, **OpenRestream**, **OpenVibe.Billing**, **OpenVibe.Tips**, **OpenVibe.VIP**, **OpenVibe.Tools**, **OpenVibe.Search**: through their APIs with Live's service token.- **Libraries**: `openvibe-contracts` (pinned tag), `openvibe-sdk`, `openvibe-shared`; FFmpeg and whisper.cpp on the host.## Capabilities@@@ -99,7 +99,7 @@ Live's own principal (`live`) holds grants to call:- **Chat**: `chat.message.send`, `chat.event.publish`, `chat.moderation.write`, `chat.cache.invalidate`, `chat.presence.read`; `chat.live_bridge.write` for the call channels (`server/streaming/calls-authority.js`) and the one-off outbox delivery (`scripts/chat-bridge-outbox-drop.js`).- **Community**: `community.paste.*`, `community.comment.*`, `community.pulse.write`.- **Events**: `events.event.publish|read`, `events.subscription.manage`.--- **OpenRe**: `openre.stream.*`, `openre.key.rotate`, `openre.session.read`.++- **OpenRestream**: `openre.stream.*`, `openre.key.rotate`, `openre.session.read`.- **Billing**: `billing.*` (intents, transfers, subscriptions, cash-outs, balances, entitlements).- **Elsewhere**: `tips.interaction.record`, `vip.entitlement.check`, `tools.tool.run`, `tools.job.read`.@@@ -22,7 +22,7 @@"deployment": "Production release layout /opt/openvibe.live (current -> releases/<time>-<sha>), unit openvibe-live.service on the systemd socket 127.0.0.1:3000, deployed with `sudo ovhost deploy live --wait-idle`.","features": ["RTMP, WHIP, WebRTC and JSMPEG ingest; viewer playback; the broadcast page",-- "channels, go-live, stream keys, managed streams, restreaming (managed ingest on OpenRe.Stream)",++ "channels, go-live, stream keys, managed streams, restreaming (managed ingest on OpenRestream)","the watch page and its chat surface (messages stored by OpenVibe.Chat), TTS, sounds, emotes, overlays","VODs and clips recorded and stored by OpenVibe.Media; pastes and comments on OpenVibe.Community","AI viewers, stream memories, overviews, moments, recaps, translation: every prompt an OpenVibe.AI template; VOD and clip transcripts by window on OpenVibe.AI",@@@ -226,7 +226,7 @@ through the SDK inbox (`idempotency_receipts`):| topics | endpoint | secret | subscribe with ||---|---|---|---|--| `openre.session.*` | `/internal/openre-events` | `OPENRE_EVENTS_SECRET` | OpenRe's `scripts/subscribe-live-events.js` |++| `openre.session.*` | `/internal/openre-events` | `OPENRE_EVENTS_SECRET` | OpenRestream's `scripts/subscribe-live-events.js` || `media.vod.*`, `media.clip.*`, `media.storage.*` | `/internal/media-events` | `MEDIA_EVENTS_SECRET` | `scripts/subscribe-media-events.js` |#### Media outcomes over Events@@@ -261,7 +261,7 @@ async function _wsLoadProfile(managedStreamId) {_wsState.selectedMs = { ..._wsState.selectedMs, ...data.managed_stream };}_wsState.streamKey = data.stream_key || null;-- // OpenRe-ingested slot: no key in the profile, only a hint (Regenerate shows a new key once).++ // OpenRestream-ingested slot: no key in the profile, only a hint (Regenerate shows a new key once)._wsState.streamKeyHint = data.stream_key_hint || null;_wsState.profile = data.broadcast_settings || {};_wsState.profile.vibeCoding = _wsNormalizeVibeSettings(_wsReadStoredVibeSettings(_wsState.profile));@@@ -269,7 +269,7 @@ async function _wsLoadProfile(managedStreamId) {_wsState.whipUrlSource = data.whip_url_source || null;_wsState.whipUrlWarning = data.whip_url_warning || null;_wsState.rtmpUrl = data.rtmp_url || null;-- // OpenRe-ingested slot: OpenRe's WHIP and JSMPEG servers (the key goes after each).++ // OpenRestream-ingested slot: OpenRestream's WHIP and JSMPEG servers (the key goes after each)._wsState.openreWhipUrl = data.whip_url || null;_wsState.openreJsmpegUrl = data.jsmpeg_url || null;} catch {@@@ -1384,7 +1384,7 @@ function _wsRenderBrowserModeCards(selected) {/* ── Method-specific endpoint info ───────────────────────────── */--// Live's WHIP takes the slot id in the path (key as Bearer token); OpenRe's takes the key itself.++// Live's WHIP takes the slot id in the path (key as Bearer token); OpenRestream's takes the key itself.function _wsWhipUrl(managedStreamId, streamKey) {if (_wsState.openreWhipUrl) return `${_wsState.openreWhipUrl.replace(/\/$/, '')}/${streamKey}`;const whipBaseUrl = (_wsState.whipUrlBase || window.location.origin).replace(/\/$/, '');@@@ -1876,7 +1876,7 @@ async function _wsRegenerateKey(managedStreamId) {try {const data = await api(`/streams/managed/${managedStreamId}/regenerate-key`, { method: 'POST' });_wsState.streamKey = data.stream_key;-- // OpenRe's rotation answers with its ingest servers too.++ // OpenRestream's rotation answers with its ingest servers too.if (data.stream_key_managed_by === 'openre') {_wsState.rtmpUrl = data.rtmp_url || _wsState.rtmpUrl;_wsState.openreWhipUrl = data.whip_url || _wsState.openreWhipUrl;@@@ -1796,7 +1796,7 @@ async function showRTMPInstructions(stream) {const data = await api(`/streams/${stream.id}/endpoint`);const ep = data.endpoint || {};const rtmpUrl = ep.rtmpUrl || `rtmp://${location.hostname}:1935/live`;-- // An OpenRe-ingested slot never returns its key here, only a hint (Regenerate shows a new key once).++ // An OpenRestream-ingested slot never returns its key here, only a hint (Regenerate shows a new key once).const streamKey = ep.streamKey || data.stream_key || ep.streamKeyHint || 'N/A';document.getElementById('bc-rtmp-url').textContent = rtmpUrl;document.getElementById('bc-rtmp-key').textContent = streamKey;@@@ -1945,7 +1945,7 @@ async function showJSMPEGInstructions(stream) {const data = await api(`/streams/${stream.id}/endpoint`);const ep = data.endpoint || {};const host = location.hostname;-- // An OpenRe-ingested slot: OpenRe's JSMPEG server (video and audio share it) and only a key hint.++ // An OpenRestream-ingested slot: OpenRestream's JSMPEG server (video and audio share it) and only a key hint.const videoServer = ep.jsmpegUrl ? ep.jsmpegUrl.replace(/\/$/, '') : `http://${host}:${ep.videoPort || 9710}`;const audioServer = ep.jsmpegUrl ? videoServer : `http://${host}:${ep.audioPort || 9711}`;const key = data.stream_key || ep.streamKeyHint || 'N/A';@@@ -2003,7 +2003,7 @@ async function showWHIPInstructions(stream) {let openreWhip = null;try {const data = await api(`/streams/${stream.id}/endpoint`);-- // An OpenRe-ingested slot: OpenRe's WHIP URL takes the key (only its hint is known here).++ // An OpenRestream-ingested slot: OpenRestream's WHIP URL takes the key (only its hint is known here).if (data.endpoint?.whipUrl) openreWhip = `${data.endpoint.whipUrl.replace(/\/$/, '')}/${data.endpoint.streamKeyHint || 'N/A'}`;whipBaseUrl = data.endpoint?.whipUrlBase || null;if (data.endpoint?.whipUrlSource === 'request_host') {@@@ -2019,7 +2019,7 @@ async function showWHIPInstructions(stream) {}}document.getElementById('bc-whip-url').textContent = openreWhip || `${whipBaseUrl}/whip/${stream.id}`;-- // Live's sign-in token is never offered as the Bearer for OpenRe's WHIP.++ // Live's sign-in token is never offered as the Bearer for OpenRestream's WHIP.const token = (!openreWhip && getStoredAuthToken()) || 'N/A';const tokenEl = document.getElementById('bc-whip-token');if (tokenEl) {@@@ -323,7 +323,7 @@ function webrtcStreamHasActiveProducer(streamId) {async function hasActiveLiveFeed(stream) {if (!stream) return false;-- // OpenRe.Stream holds the ingest (a mirrored session it confirmed recently): not stale.++ // OpenRestream holds the ingest (a mirrored session it confirmed recently): not stale.if (await require('./openre/mirror').hasLiveSession(stream.id)) return true;const streamKey = await getStreamKey(stream);if (stream.protocol === 'rtmp') {@@@ -668,7 +668,7 @@ app.post('/internal/media-webhook', require('./media-proxy/webhook'));app.post('/internal/media-events', require('./media-proxy/media-events').handler);// Network identity events (signed out everywhere, password changed, banned): Live refuses older tokens.app.post('/internal/network-events', require('./auth/network-events').handler);--// OpenVibe.Events → Live: OpenRe session lifecycle mirrored into `streams` (signed delivery,++// OpenVibe.Events → Live: OpenRestream session lifecycle mirrored into `streams` (signed delivery,// OPENRE_EVENTS_SECRET; server/openre/mirror.js). Also before /internal (no X-Internal-Key).app.post('/internal/openre-events', require('./openre/mirror').webhookHandler);@@@ -1257,7 +1257,7 @@ async function start() {// 5b. Resume enabled restreams for streams that survived the restart.// WHIP/RTMP broadcasters have no browser session to re-start them manually.for (const stream of await db.getLiveStreams()) {-- // OpenRe restreams its own sessions; Live must not start a second push for them.++ // OpenRestream restreams its own sessions; Live must not start a second push for them.if (await require('./openre/mirror').ownsStream(stream.id)) continue;restreamManager.resumeForStream(stream.id, stream.user_id, {protocol: stream.protocol,@@@ -1326,8 +1326,8 @@ async function start() {// 6e. Start periodic viewer count polling for restream destinationsrestreamManager.startViewerCountPolling();-- // 6g. OpenRe mirror reconcile (only runs when OPENRE_URL is set; idle with nothing mirrored).-- try { require('./openre/mirror').start(); } catch (e) { console.warn('[OpenRe] mirror reconcile not started:', e.message); }++ // 6g. OpenRestream mirror reconcile (only runs when OPENRE_URL is set; idle with nothing mirrored).++ try { require('./openre/mirror').start(); } catch (e) { console.warn('[OpenRestream] mirror reconcile not started:', e.message); }// 6f. VOD storage/offload is owned by OpenVibe.Media now — nothing to start here.@@@ -1,7 +1,7 @@'use strict';/*** GET|POST /internal/lineage/resolve: the canonical channel/owner resolver (./resolver.js) for other-- * services (Pulse, OpenRe, Media, creator UI). Service token only (aud openvibe.live, capability++ * services (Pulse, OpenRestream, Media, creator UI). Service token only (aud openvibe.live, capability* live.lineage.resolve), loopback only.** GET ?slug=&parent_slug=&channel_id=&stream_id=&slot_id=&vod_id=&clip_id=&media_object_id=@@@ -8,7 +8,7 @@* Auth: `Authorization: Bearer <service token>`. Live authenticates to Media with its own* Network service principal (client credentials for audience openvibe.media; grants* media.object.read/list/upload/delete on namespace live), the same construction-- * server/openre/openre-client.js uses for OpenRe. For a request made on behalf of a browser++ * server/openre/openre-client.js uses for OpenRestream. For a request made on behalf of a browser* user, add `actingUser` (their LIVE-LOCAL user id) — Media then applies that user's ACLs and* stores their id. See _authHeader for why the id is sent explicitly rather than left for* Media to read out of the caller's Network JWT.@@@ -2,14 +2,14 @@/*** The per-slot ingest switch: managed_streams.ingest_authority = 'live' (default) | 'openre'.*-- * 'openre' means OpenRe.Stream ingests this slot: Live's RTMP server, WHIP handler, browser++ * 'openre' means OpenRestream ingests this slot: Live's RTMP server, WHIP handler, browser* broadcaster socket and JSMPEG relay refuse the slot's key (so a stream can never be ingested-- * twice), the Go Live UI shows OpenRe's ingest URLs and rotates OpenRe's key, and the slot's++ * twice), the Go Live UI shows OpenRestream's ingest URLs and rotates OpenRestream's key, and the slot's* sessions reach `streams` through the mirror (mirror.js). RTMP, WebRTC (WHIP) and JSMPEG move-- * (OPENRE_PROTOCOLS): OpenRe serves WHIP on ingest.openre.stream and runs the JSMPEG worker.++ * (OPENRE_PROTOCOLS): OpenRestream serves WHIP on ingest.openre.stream and runs the JSMPEG worker.** Switch off = zero behaviour change: every helper here returns what Live did before unless the-- * slot says 'openre' AND OpenRe is configured (openre-client enabled()).++ * slot says 'openre' AND OpenRestream is configured (openre-client enabled()).*/const crypto = require('crypto');const db = require('../db/database');@@@ -17,7 +17,7 @@ const client = require('./openre-client');const OPENRE_PROTOCOLS = new Set(['rtmp', 'webrtc', 'jsmpeg']);--/** The OpenRe definition protocol a slot publishes with (OpenRe's own migrate-live mapping). */++/** The OpenRestream definition protocol a slot publishes with (OpenRestream's own migrate-live mapping). */function openreProtocolOf(slot) {const p = String((slot && slot.protocol) || '').toLowerCase();if (p === 'webrtc' || p === 'whip') return 'webrtc';@@@ -34,7 +34,7 @@ async function slotById(id) {return id ? await db.getManagedStreamById(id) : null;}--/** authorityOf(slotById(id)) === 'openre', without reading the slot when OpenRe is not configured. */++/** authorityOf(slotById(id)) === 'openre', without reading the slot when OpenRestream is not configured. */async function slotIsOpenre(id) {if (!id || !client.enabled()) return false;return authorityOf(await slotById(id)) === 'openre';@@@ -42,7 +42,7 @@ async function slotIsOpenre(id) {/*** Live's publish handlers (RTMP, WHIP, browser broadcaster, JSMPEG) ask this before accepting a key.-- * A slot key is refused when its slot is on OpenRe; a personal key (users.stream_key, no slot) is++ * A slot key is refused when its slot is on OpenRestream; a personal key (users.stream_key, no slot) is* refused when any of the user's slots is, because Live would attach it to the user's live rows and* push the user's destinations twice.*/@@@ -68,7 +68,7 @@ function serializeSlot(slot) {return { ...slot, stream_key: null, ingest_authority: 'openre', stream_key_managed_by: 'openre', openre_manage_url: client.manageUrl(slot.openre_stream_id) };}--/** OpenRe's ingest URLs from a stream or rotation answer (`ingest.rtmp.url`, `ingest.webrtc.whip_url`,++/** OpenRestream's ingest URLs from a stream or rotation answer (`ingest.rtmp.url`, `ingest.webrtc.whip_url`,* `ingest.jsmpeg.url`; each only for the protocols the definition allows). The key goes after each:* rtmp://…/live + key, <whip_url>/<key>, <jsmpeg_url>/<key>/<width>/<height>/. */function ingestUrls(ingest) {@@@ -81,7 +81,7 @@ function ingestUrls(ingest) {}/**-- * What the Go Live UI shows for an OpenRe slot: OpenRe's ingest URLs and the hint of its current key++ * What the Go Live UI shows for an OpenRestream slot: OpenRestream's ingest URLs and the hint of its current key* (one key for every protocol; the key itself is only ever shown once, by a rotation).*/async function ingestFor(slot, subject) {@@@ -95,15 +95,15 @@ async function ingestFor(slot, subject) {...ingestUrls(ingest),stream_key_hint: keyHint? `Shown once: press Regenerate for a new key (current key ends in …${keyHint})`-- : 'Press Regenerate to get your OpenRe stream key',++ : 'Press Regenerate to get your OpenRestream stream key',openre_manage_url: client.manageUrl(stream ? stream.id : slot.openre_stream_id),};}--/** Regenerate on an OpenRe slot: OpenRe rotates, the new key is returned once. */++/** Regenerate on an OpenRestream slot: OpenRestream rotates, the new key is returned once. */async function rotateFor(slot, subject) {const streamId = slot.openre_stream_id || (await client.streamForSlot(slot.id) || {}).id;-- if (!streamId) throw new client.OpenReError('this slot has no OpenRe stream yet', 409);++ if (!streamId) throw new client.OpenReError('this slot has no OpenRestream stream yet', 409);const r = await client.rotateKey(streamId, { subject });return { stream_key: r.key.key, stream_key_managed_by: 'openre', ...ingestUrls(r.ingest) };}@@@ -116,8 +116,8 @@ async function recordingModeFor(slot) {/*** The admin switch (PUT /api/admin/openre/managed/:id/ingest-authority).-- * 'openre': needs OpenRe configured, the slot offline on Live, the owner's canonical subject;-- * finds or creates the OpenRe definition (allowing the slot's protocol on it), then++ * 'openre': needs OpenRestream configured, the slot offline on Live, the owner's canonical subject;++ * finds or creates the OpenRestream definition (allowing the slot's protocol on it), then* (one transaction) flips the slot and* rotates Live's own key for it, so no key that existed before works anywhere.* 'live': flips back (rollback). Live's key was rotated at the switch: the broadcaster@@@ -129,18 +129,18 @@ async function setAuthority(slotId, authority, { force = false } = {}) {if (!['live', 'openre'].includes(authority)) return { status: 400, error: "authority must be 'live' or 'openre'" };if (authority === 'live') {await db.run("UPDATE managed_streams SET ingest_authority = 'live', updated_at = ov_now() WHERE id = ?", [slot.id]);-- return { status: 200, body: { managed_stream_id: slot.id, ingest_authority: 'live', next: 'The broadcaster regenerates the stream key on the Go Live page (the Live key was rotated when the slot moved to OpenRe).' } };++ return { status: 200, body: { managed_stream_id: slot.id, ingest_authority: 'live', next: 'The broadcaster regenerates the stream key on the Go Live page (the Live key was rotated when the slot moved to OpenRestream).' } };}-- if (!client.enabled()) return { status: 409, error: 'OpenRe is not configured on this Live (OPENRE_URL, OV_OAUTH_CLIENT_SECRET)' };++ if (!client.enabled()) return { status: 409, error: 'OpenRestream is not configured on this Live (OPENRE_URL, OV_OAUTH_CLIENT_SECRET)' };if (slot.ingest_authority === 'openre') return { status: 200, body: { managed_stream_id: slot.id, ingest_authority: 'openre', openre_stream_id: slot.openre_stream_id, unchanged: true } };const live = await db.get('SELECT id FROM streams WHERE managed_stream_id = ? AND is_live = 1 LIMIT 1', [slot.id]);if (live) return { status: 409, error: `Slot ${slot.id} is live on Live (stream ${live.id}); switch it in a maintenance window, while it is offline` };const protocol = openreProtocolOf(slot);const method = String(slot.streaming_method || '').toLowerCase();-- // WHIP encoders take OpenRe's WHIP URL and key; the Go Live page's in-browser broadcaster only++ // WHIP encoders take OpenRestream's WHIP URL and key; the Go Live page's in-browser broadcaster only// publishes to Live's own SFU, so a browser slot moved now could not go live from the page.if (!force && protocol === 'webrtc' && !['whip', 'obs'].includes(method)) {-- return { status: 409, error: `Slot ${slot.id} is a ${slot.protocol}${method ? `/${method}` : ''} slot; the Go Live page cannot publish to OpenRe yet, only WHIP encoders can (pass force to switch anyway)` };++ return { status: 409, error: `Slot ${slot.id} is a ${slot.protocol}${method ? `/${method}` : ''} slot; the Go Live page cannot publish to OpenRestream yet, only WHIP encoders can (pass force to switch anyway)` };}const subject = await require('../auth/identity-sync').subjectOf(slot.user_id);if (!subject) return { status: 409, error: 'The owner has no canonical subject yet (they need to sign in to Live once)' };@@@ -150,12 +150,12 @@ async function setAuthority(slotId, authority, { force = false } = {}) {try { visibility = await db.resolveStreamVodVisibility({ user_id: slot.user_id, managed_stream_id: slot.id }); } catch { /* public */ }stream = await client.createStreamForSlot(slot, { subject, protocols: [protocol], recordingMode: await recordingModeFor(slot), recordingVisibility: visibility });} else if (!(stream.protocols || ['rtmp']).includes(protocol)) {-- // An existing definition (OpenRe's own default is RTMP only) would refuse the slot's encoder.++ // An existing definition (OpenRestream's own default is RTMP only) would refuse the slot's encoder.stream = await client.updateStream(stream.id, { protocols: [...(stream.protocols || ['rtmp']), protocol] }, { subject });}const newLiveKey = crypto.randomBytes(20).toString('hex');const flipped = await db.getDb().tx(async () => {-- // Checked again here: a Live publish may have started while OpenRe was being asked.++ // Checked again here: a Live publish may have started while OpenRestream was being asked.if (await db.get('SELECT id FROM streams WHERE managed_stream_id = ? AND is_live = 1 LIMIT 1', [slot.id])) return false;await db.run("UPDATE managed_streams SET ingest_authority = 'openre', openre_stream_id = ?, stream_key = ?, updated_at = ov_now() WHERE id = ?", [stream.id, newLiveKey, slot.id]);return true;@@@ -169,7 +169,7 @@ async function setAuthority(slotId, authority, { force = false } = {}) {openre_stream_id: stream.id,...ingestUrls(stream.ingest),live_key_rotated: true,-- next: 'The broadcaster presses Regenerate on the Go Live page (OpenRe issues the key) and pastes the new server and key into OBS.',++ next: 'The broadcaster presses Regenerate on the Go Live page (OpenRestream issues the key) and pastes the new server and key into OBS.',},};}@@@ -1,18 +1,18 @@'use strict';/**-- * OpenRe sessions → Live's `streams` table (roadmap Wave 7, ADR-009: Live observes sessions through-- * OpenRe events and APIs). Watch pages, discovery, go-live notifications and analytics keep working-- * because an OpenRe-ingested session becomes an ordinary live `streams` row.++ * OpenRestream sessions → Live's `streams` table (roadmap Wave 7, ADR-009: Live observes sessions through++ * OpenRestream events and APIs). Watch pages, discovery, go-live notifications and analytics keep working++ * because an OpenRestream-ingested session becomes an ordinary live `streams` row.** POST /internal/openre-events the endpoint of Live's OpenVibe.Events subscription (topic* openre.session.*), signed with OPENRE_EVENTS_SECRET. Each event* is applied exactly once (openvibe-sdk inbox, consumer* 'live-openre-mirror'), in revision order per session.-- * reconcile (every 30 s) for mirrored live sessions only: asks OpenRe for the session,-- * keeps the row's heartbeat fresh, ends it if OpenRe says it ended++ * reconcile (every 30 s) for mirrored live sessions only: asks OpenRestream for the session,++ * keeps the row's heartbeat fresh, ends it if OpenRestream says it ended* (covers a missed event). Does nothing when no row is mirrored.*-- * Consent: a session is mirrored only when its slot is switched to OpenRe on Live AND the OpenRe++ * Consent: a session is mirrored only when its slot is switched to OpenRestream on Live AND the OpenRestream* definition says mirror_to_live (plan §15.10 "explicit visibility and consent").*/const db = require('../db/database');@@@ -21,7 +21,7 @@ const { authorityOf } = require('./authority');const CONSUMER = 'live-openre-mirror';const RECONCILE_MS = 30000;--// A mirrored session OpenRe has not confirmed for this long is left to Live's stale cleanup.++// A mirrored session OpenRestream has not confirmed for this long is left to Live's stale cleanup.const CONFIRM_WINDOW_MIN = 30;let inbox = null;let timer = null;@@@ -100,11 +100,11 @@ async function apply(event) {const channel = await db.getChannelByUserId(user.id);const configId = slot.control_config_id || (channel && channel.active_control_config_id);if (configId) await db.applyConfigToStream(configId, streamId);-- } catch (e) { console.warn('[OpenRe] control config for mirrored stream failed:', e.message); }++ } catch (e) { console.warn('[OpenRestream] control config for mirrored stream failed:', e.message); }const stream = await db.getStreamById(streamId) || { id: streamId };-- try { await require('../streaming/golive-notify').notifyFollowersGoLive(user, stream); } catch (e) { console.warn('[OpenRe] go-live notify failed:', e.message); }++ try { await require('../streaming/golive-notify').notifyFollowersGoLive(user, stream); } catch (e) { console.warn('[OpenRestream] go-live notify failed:', e.message); }try { require('../streaming/live-events').announceGoLive(stream, user); } catch { /* */ }-- console.log(`[OpenRe] session ${sessionId} mirrored into stream ${streamId} (${user.username}, slot ${slot.id})`);++ console.log(`[OpenRestream] session ${sessionId} mirrored into stream ${streamId} (${user.username}, slot ${slot.id})`);},};}@@@ -128,7 +128,7 @@ async function apply(event) {try { require('../integrations/robotstreamer-service').stopForStream(streamId); } catch { /* */ }try { require('../integrations/chat-relay-service').stopForStream(streamId); } catch { /* */ }try { require('../integrations/ai-chatbot-service').stopForStream(streamId); } catch { /* */ }-- console.log(`[OpenRe] session ${sessionId} ${state}: stream ${streamId} ended`);++ console.log(`[OpenRestream] session ${sessionId} ${state}: stream ${streamId} ended`);},};}@@@ -151,20 +151,20 @@ async function webhookHandler(req, res) {after = r.after || null;return r.outcome;});-- if (!result.duplicate && after) { try { await after(); } catch (e) { console.warn('[OpenRe] post-commit step failed:', e.message); } }++ if (!result.duplicate && after) { try { await after(); } catch (e) { console.warn('[OpenRestream] post-commit step failed:', e.message); } }} catch (err) {-- console.error('[OpenRe] event apply failed:', err.message);++ console.error('[OpenRestream] event apply failed:', err.message);return res.status(500).json({ error: 'apply failed' }); // Events retries}return res.status(204).end();}--/** Stream id → the mirrored OpenRe session (or null). */++/** Stream id → the mirrored OpenRestream session (or null). */async function sessionForStream(streamId) {try { return await db.get('SELECT * FROM openre_sessions WHERE stream_id = ? ORDER BY updated_at DESC LIMIT 1', [streamId]) || null; } catch { return null; }}--/** Does OpenRe hold a live ingest for this stream (confirmed recently)? Used by Live's stale cleanup. */++/** Does OpenRestream hold a live ingest for this stream (confirmed recently)? Used by Live's stale cleanup. */async function hasLiveSession(streamId) {try {return Boolean(await db.get(`SELECT 1 FROM openre_sessions WHERE stream_id = ? AND state = 'live'@@@ -172,7 +172,7 @@ async function hasLiveSession(streamId) {} catch { return false; }}--/** Does OpenRe own this stream at all (so Live must not start its own restream/recording for it)? */++/** Does OpenRestream own this stream at all (so Live must not start its own restream/recording for it)? */async function ownsStream(streamId) {return Boolean(await sessionForStream(streamId));}@@@ -183,16 +183,16 @@ async function reconcileOnce() {if (!rows.length || !client.enabled()) return 0;let n = 0;for (const r of rows) {-- // Live ended the row itself (End Stream, or stale cleanup while OpenRe was unreachable):++ // Live ended the row itself (End Stream, or stale cleanup while OpenRestream was unreachable):// stop tracking it rather than keeping a live mirror of an offline stream.const row = r.stream_id ? await db.getStreamById(r.stream_id) : null;if (row && !row.is_live) { await db.run("UPDATE openre_sessions SET state = 'detached', updated_at = ov_now() WHERE session_id = ?", [r.session_id]); continue; }let s;try { s = await client.getSession(r.session_id); } catch (err) {if (err.status === 404) s = null;-- else continue; // OpenRe unreachable: keep the last known state (bounded by CONFIRM_WINDOW_MIN)++ else continue; // OpenRestream unreachable: keep the last known state (bounded by CONFIRM_WINDOW_MIN)}-- // A session OpenRe no longer knows (the SDK answers null for a 404; older clients threw) has failed: without this++ // A session OpenRestream no longer knows (the SDK answers null for a 404; older clients threw) has failed: without this// the row stayed 'live', ownsStream() stayed true and Live never took its own restream/recording back.if (!s) s = { state: 'failed', revision: r.revision + 1, ended_at: null };if (s.state === 'live' || s.state === 'starting') {@@@ -210,7 +210,7 @@ async function reconcileOnce() {function start() {if (timer || !client.enabled()) return;-- timer = setInterval(() => { reconcileOnce().catch(err => console.warn('[OpenRe] reconcile failed:', err.message)); }, RECONCILE_MS);++ timer = setInterval(() => { reconcileOnce().catch(err => console.warn('[OpenRestream] reconcile failed:', err.message)); }, RECONCILE_MS);if (timer.unref) timer.unref();}@@@ -1,7 +1,7 @@'use strict';/**-- * OpenRe.Stream client (roadmap Wave 7). Live asks OpenRe for stream definitions, ingest URLs,-- * key rotation, sessions and playback descriptors; it never touches an OpenRe worker.++ * OpenRestream client (roadmap Wave 7). Live asks OpenRestream for stream definitions, ingest URLs,++ * key rotation, sessions and playback descriptors; it never touches an OpenRestream worker.** Auth: Live's Network service principal (OV_OAUTH_CLIENT_ID / OV_OAUTH_CLIENT_SECRET),* client-credentials tokens for audience openvibe.openre. Grants Live needs on openre:@@@ -9,7 +9,7 @@* Calls made for a streamer carry X-OV-Subject: <their usr_ subject>.** Env:-- * OPENRE_URL OpenRe API, host-local (http://127.0.0.1:4500). Unset = integration off:++ * OPENRE_URL OpenRestream API, host-local (http://127.0.0.1:4500). Unset = integration off:* every slot behaves exactly as before, whatever its ingest_authority.* OPENRE_PUBLIC_URL for links to the standalone UI (default https://openre.stream)* OPENRE_EVENTS_SECRET the signing secret of Live's OpenVibe.Events subscription to openre.*@@@ -18,7 +18,7 @@ const { createServiceTokenClient } = require('openvibe-sdk/auth');const { createClient, OpenVibeError } = require('openvibe-sdk/core');const { createOpenReClient } = require('openvibe-sdk/openre');--// The calls themselves are openvibe-sdk/openre (SDK 0.16.0), shared with every product that uses OpenRe; this++// The calls themselves are openvibe-sdk/openre (SDK 0.16.0), shared with every product that uses OpenRestream; this// module keeps Live's configuration (env), its on/off switch and the names the rest of Live calls.const TIMEOUT_MS = 5000;let sdk = null;@@@ -33,7 +33,7 @@ function settings() {};}--/** True when Live is configured to talk to OpenRe at all. */++/** True when Live is configured to talk to OpenRestream at all. */function enabled() {const s = settings();return Boolean(s.url && s.clientSecret);@@@ -45,7 +45,7 @@ class OpenReError extends Error {/** The SDK client for the current settings (rebuilt when they change: tests and rollbacks flip OPENRE_URL). */function openre() {-- if (!enabled()) throw new OpenReError('OpenRe integration is not configured (OPENRE_URL, OV_OAUTH_CLIENT_SECRET)', 0);++ if (!enabled()) throw new OpenReError('OpenRestream integration is not configured (OPENRE_URL, OV_OAUTH_CLIENT_SECRET)', 0);const s = settings();const key = `${s.url}|${s.publicUrl}|${s.clientId}@${s.networkInternalUrl}`;if (!sdk || sdk.key !== key) {@@@ -62,9 +62,9 @@ async function wrap(what, fn) {if (err instanceof OpenReError) throw err;if (err instanceof OpenVibeError || (err && err.name === 'OpenVibeError')) {const body = err.problem || (err.code ? { code: err.code, detail: err.detail || err.message } : null);-- throw new OpenReError(err.status ? (err.detail || err.message) : `OpenRe unreachable (${what}): ${err.message}`, err.status || 0, body);++ throw new OpenReError(err.status ? (err.detail || err.message) : `OpenRestream unreachable (${what}): ${err.message}`, err.status || 0, body);}-- throw new OpenReError(`OpenRe unreachable (${what}): ${err.message}`, 0);++ throw new OpenReError(`OpenRestream unreachable (${what}): ${err.message}`, 0);}}@@@ -75,7 +75,7 @@ async function request(method, path, { body, subject, timeoutMs = TIMEOUT_MS } =headers: subject ? { 'X-OV-Subject': subject } : {} }));}--/** The OpenRe stream definition serving a Live slot, or null. */++/** The OpenRestream stream definition serving a Live slot, or null. */const streamForSlot = async (managedStreamId) => await wrap('stream lookup', () => openre().streams.byExternalRef(`live:managed_stream:${managedStreamId}`));/** Create the definition for a slot, owned by the streamer's canonical subject. The key it@@@ -1,7 +1,7 @@'use strict';/**-- * Admin routes for the OpenRe ingest switch (mounted at /api/admin/openre, admin only). The lead-- * flips a slot during a maintenance window agreed with the broadcaster (OpenRe.Stream README,++ * Admin routes for the OpenRestream ingest switch (mounted at /api/admin/openre, admin only). The lead++ * flips a slot during a maintenance window agreed with the broadcaster (OpenRestream README,* "RTMP cutover runbook").** GET /api/admin/openre/status configuration + switched slots@@@ -38,11 +38,11 @@ router.put('/managed/:id/ingest-authority', requireAdmin, async (req, res) => {try {const r = await authority.setAuthority(id, String((req.body && req.body.authority) || ''), { force: Boolean(req.body && req.body.force) });if (r.error) return res.status(r.status).json({ error: r.error });-- console.log(`[OpenRe] admin ${req.user.username} set slot ${id} ingest authority to ${r.body.ingest_authority}`);++ console.log(`[OpenRestream] admin ${req.user.username} set slot ${id} ingest authority to ${r.body.ingest_authority}`);return res.status(r.status).json(r.body);} catch (err) {-- console.error('[OpenRe] switch failed:', err.message);-- return res.status(err.status && err.status < 500 ? 409 : 502).json({ error: `OpenRe: ${err.message}` });++ console.error('[OpenRestream] switch failed:', err.message);++ return res.status(err.status && err.status < 500 ? 409 : 502).json({ error: `OpenRestream: ${err.message}` });}});@@@ -1,13 +1,13 @@'use strict';/**-- * OpenRe ingest switch — Live-side schema (roadmap Wave 7, ADR-009).++ * OpenRestream ingest switch — Live-side schema (roadmap Wave 7, ADR-009).** The schema this module used to create at boot now lives in migrations/0002_live.sql, which owns every table* and column (the production runtime role cannot run DDL):** managed_streams.ingest_authority 'live' (default) | 'openre' who ingests RTMP for this slot-- * managed_streams.openre_stream_id the OpenRe stream definition serving the slot (std_…)-- * openre_sessions Live's projection of OpenRe ingest sessions it mirrors into++ * managed_streams.openre_stream_id the OpenRestream stream definition serving the slot (std_…)++ * openre_sessions Live's projection of OpenRestream ingest sessions it mirrors into* `streams` (rebuilt from openre.session.* events; the* revision guard makes out-of-order delivery harmless)*@@@ -234,12 +234,12 @@ class BroadcastServer extends EventEmitter {ws.close(4003, 'Not your stream');return;}-- // OpenRe.Stream ingests this slot (or, for a slot-less stream, one of the owner's slots):++ // OpenRestream ingests this slot (or, for a slot-less stream, one of the owner's slots):// Live's SFU never takes a second publisher for it. See server/openre/authority.js.const openre = require('../openre/authority');const managedStream = await openre.slotById(stream.managed_stream_id);if (await openre.refusesLiveIngest({ managedStream, user: managedStream ? null : user, protocol: 'webrtc' })) {-- console.warn(`[Broadcast] Broadcaster refused for stream ${streamId}: ${managedStream ? `slot ${managedStream.id}` : `personal stream of ${user.username}`} is ingested by OpenRe`);++ console.warn(`[Broadcast] Broadcaster refused for stream ${streamId}: ${managedStream ? `slot ${managedStream.id}` : `personal stream of ${user.username}`} is ingested by OpenRestream`);ws.close(4003, 'Stream key not recognized');return;}@@@ -24,7 +24,7 @@ function matchesStreamPath(url, streamKey) {/*** Consult the per-protocol ingest authority for each publish: a slot (or personal key)-- * ingested by OpenRe publishes JSMPEG to OpenRe, never to Live's relay.++ * ingested by OpenRestream publishes JSMPEG to OpenRestream, never to Live's relay.*/async function refusedByOpenre(streamKey) {try {@@@ -33,7 +33,7 @@ async function refusedByOpenre(streamKey) {const managedStream = user ? null : await db.getManagedStreamByStreamKey(streamKey);if (!user && !managedStream) return false;if (!await require('../openre/authority').refusesLiveIngest({ managedStream, user, protocol: 'jsmpeg' })) return false;-- console.log(`[JSMPEG] Rejected: ${managedStream ? `slot ${managedStream.id}` : `personal key of ${user.username}`} is ingested by OpenRe`);++ console.log(`[JSMPEG] Rejected: ${managedStream ? `slot ${managedStream.id}` : `personal key of ${user.username}`} is ingested by OpenRestream`);return true;} catch {return false;@@@ -16,7 +16,7 @@ const chatRelayService = require('../integrations/chat-relay-service');const router = express.Router();/**-- * A slot ingested by OpenRe.Stream (ingest_authority = 'openre') restreams from OpenRe: its++ * A slot ingested by OpenRestream (ingest_authority = 'openre') restreams from OpenRestream: its* destinations are managed there (openre.stream), so Live refuses to edit or run them here rather* than letting two systems push the same stream. Slots on Live's own ingest are unaffected.*/@@@ -25,7 +25,7 @@ async function refuseIfOpenre(res, managedStreamId) {if (!await openre.slotIsOpenre(managedStreamId)) return false;const slot = await openre.slotById(managedStreamId);res.status(409).json({-- error: 'Restreaming for this stream slot is managed on OpenRe.Stream',++ error: 'Restreaming for this stream slot is managed on OpenRestream',managed_by: 'openre',manage_url: require('../openre/openre-client').manageUrl(slot && slot.openre_stream_id),});@@@ -1609,13 +1609,13 @@ router.get('/managed/:managedStreamId/profile', requireAuth, async (req, res) =>rtmp_url: rtmpUrl,restream_destinations: restreamDestinations,};-- // OpenRe ingests this slot: its RTMP/WHIP/JSMPEG servers and key come from OpenRe (the key++ // OpenRestream ingests this slot: its RTMP/WHIP/JSMPEG servers and key come from OpenRestream (the key// is only ever shown by Regenerate), and Live's WHIP base is withheld: Live refuses that// publish. Slots on Live's own ingest get exactly the response above.if (openreAuthority.authorityOf(ms) === 'openre') {const subject = await require('../auth/identity-sync').subjectOf(ms.user_id);Object.assign(body, { stream_key: null, whip_url_base: null, whip_url_source: null, whip_url_warning: null }, await openreAuthority.ingestFor(ms, subject).catch((err) => ({-- ingest_authority: 'openre', stream_key_managed_by: 'openre', rtmp_url: null, whip_url: null, jsmpeg_url: null, stream_key_hint: `OpenRe is unreachable (${err.message})`,++ ingest_authority: 'openre', stream_key_managed_by: 'openre', rtmp_url: null, whip_url: null, jsmpeg_url: null, stream_key_hint: `OpenRestream is unreachable (${err.message})`,})));}res.json(body);@@@ -1954,14 +1954,14 @@ router.post('/managed/:id/regenerate-key', requireAuth, async (req, res) => {return res.status(403).json({ error: 'Not your managed stream' });}-- // OpenRe ingests this slot: OpenRe rotates its key (the old one stops working at once) and++ // OpenRestream ingests this slot: OpenRestream rotates its key (the old one stops working at once) and// the new one is shown here, once.if (openreAuthority.authorityOf(ms) === 'openre') {try {const subject = await require('../auth/identity-sync').subjectOf(ms.user_id);return res.json(await openreAuthority.rotateFor(ms, subject));} catch (err) {-- return res.status(502).json({ error: `Could not rotate the key on OpenRe: ${err.message}` });++ return res.status(502).json({ error: `Could not rotate the key on OpenRestream: ${err.message}` });}}@@@ -2283,7 +2283,7 @@ router.get('/:id/endpoint', requireAuth, async (req, res) => {const hostname = config.host === '0.0.0.0' ? req.hostname : config.host;if (openreAuthority.OPENRE_PROTOCOLS.has(stream.protocol) && await openreAuthority.slotIsOpenre(stream.managed_stream_id)) {-- // OpenRe ingests this slot: its server URL for the slot's protocol, the hint of its key++ // OpenRestream ingests this slot: its server URL for the slot's protocol, the hint of its key// (never the key). No Live relay channel, room or recorder is set up for it.const slot = await openreAuthority.slotById(stream.managed_stream_id);const ingest = await openreAuthority.ingestFor(slot, await require('../auth/identity-sync').subjectOf(slot.user_id)).catch(() => ({}));@@@ -2293,7 +2293,7 @@ router.get('/:id/endpoint', requireAuth, async (req, res) => {endpoint = {...serverUrl,streamKey: null,-- streamKeyHint: ingest.stream_key_hint || 'Press Regenerate to get your OpenRe stream key',++ streamKeyHint: ingest.stream_key_hint || 'Press Regenerate to get your OpenRestream stream key',keyManagedBy: 'openre',...(stream.protocol === 'rtmp' ? { flvUrl: `/api/streams/rtmp-proxy/${stream.id}.flv` } : {}),};@@@ -2537,8 +2537,8 @@ router.get('/rtmp-proxy/:streamId.flv', async (req, res) => {let url;const mirrored = await openreMirror.sessionForStream(stream.id);if (mirrored) {-- // An OpenRe session: pull HTTP-FLV from the OpenRe worker that holds it, as named by-- // OpenRe's playback descriptor (a loopback URL on this host; no key in it).++ // An OpenRestream session: pull HTTP-FLV from the OpenRestream worker that holds it, as named by++ // OpenRestream's playback descriptor (a loopback URL on this host; no key in it).if (mirrored.state !== 'live') return res.status(404).end();const pb = await require('../openre/openre-client').playback(mirrored.session_id).catch(() => null);if (!pb || !pb.flv || !/^http:\/\/127\.0\.0\.1:\d+\/live\/ses_[0-9A-Z]+\.flv$/.test(pb.flv.internal_url)) return res.status(502).end();@@@ -2550,7 +2550,7 @@ router.get('/rtmp-proxy/:streamId.flv', async (req, res) => {url = `http://127.0.0.1:${nmsPort}/live/${flvKey}.flv`;}-- if (req.destroyed) return undefined; // the viewer left while OpenRe was asked++ if (req.destroyed) return undefined; // the viewer left while OpenRestream was askedconst http = require('http');const upstream = http.get(url, (nmsRes) => {if (nmsRes.statusCode !== 200) {@@@ -121,11 +121,11 @@ class RTMPServer extends EventEmitter {return;}-- // OpenRe.Stream ingests this slot (managed_streams.ingest_authority = 'openre'): refuse++ // OpenRestream ingests this slot (managed_streams.ingest_authority = 'openre'): refuse// here so one stream can never be ingested twice. Slots on 'live' (the default) are// untouched. See server/openre/authority.js.if (await require('../openre/authority').refusesLiveIngest({ managedStream, user: managedStream ? null : resolvedUser, protocol: 'rtmp' })) {-- console.log(`[RTMP] Rejected: ${managedStream ? `slot ${managedStream.id}` : `personal key of ${resolvedUser.username}`} is ingested by OpenRe`);++ console.log(`[RTMP] Rejected: ${managedStream ? `slot ${managedStream.id}` : `personal key of ${resolvedUser.username}`} is ingested by OpenRestream`);const session = this.nms.getSession(id);if (session) session.reject();return;@@@ -121,13 +121,13 @@ function sendWhipError(res, status, code, message) {/*** Consult the per-protocol ingest authority before accepting a publisher: a slot (or personal-- * key) ingested by OpenRe publishes WHIP to OpenRe, never to Live.++ * key) ingested by OpenRestream publishes WHIP to OpenRestream, never to Live.*/async function refusedByOpenre(slotId, userId) {const managedStream = slotId ? await db.getManagedStreamById(slotId) : null;const user = managedStream || !userId ? null : await db.getUserById(userId);if (!await require('../openre/authority').refusesLiveIngest({ managedStream, user, protocol: 'webrtc' })) return false;-- console.log(`[WHIP] Rejected: ${managedStream ? `slot ${managedStream.id}` : `personal key of user ${userId}`} is ingested by OpenRe`);++ console.log(`[WHIP] Rejected: ${managedStream ? `slot ${managedStream.id}` : `personal key of user ${userId}`} is ingested by OpenRestream`);return true;}@@@ -1,9 +1,9 @@'use strict';--// OpenRe ingest switch (roadmap Wave 7, ADR-009). With OPENRE_URL unset, or a slot on 'live' (the++// OpenRestream ingest switch (roadmap Wave 7, ADR-009). With OPENRE_URL unset, or a slot on 'live' (the// default), nothing changes. With a slot switched to 'openre': Live's RTMP, WHIP, browser--// broadcaster and JSMPEG ingest refuse its key, the Go Live helpers show OpenRe's URLs and rotate--// OpenRe's key, and OpenRe session events (signed Events deliveries, applied once, in revision++// broadcaster and JSMPEG ingest refuse its key, the Go Live helpers show OpenRestream's URLs and rotate++// OpenRestream's key, and OpenRestream session events (signed Events deliveries, applied once, in revision// order) become ordinary `streams` rows.const assert = require('assert');@@@ -17,7 +17,7 @@ const SUBJECT = 'usr_01J0000000000000000000000Q';const SES = (n) => `ses_01J00000000000000000000${String(n).padStart(3, '0')}`.slice(0, 30);const openreCalls = [];let openreSessions = {};--/** OpenRe definitions by id: { id, ref, protocols }. */++/** OpenRestream definitions by id: { id, ref, protocols }. */const openreDefs = {};const ingestOf = (protocols) => ({...(protocols.includes('rtmp') ? { rtmp: { url: 'rtmp://ingest.openre.stream:1936/live', key_hint: 'WXYZ' } } : {}),@@@ -122,7 +122,7 @@ function sessionEvent(type, sessionId, revision, payload = {}) {assert.strictEqual(authority.serializeSlot(slot701), slot701, 'the same object: responses unchanged');assert.strictEqual(await authority.refusesLiveIngest({ managedStream: slot701 }), false);assert.strictEqual(await authority.refusesLiveIngest({ managedStream: null, user: { id: 601 } }), false);-- // Even a slot marked 'openre' behaves as before while OpenRe is not configured.++ // Even a slot marked 'openre' behaves as before while OpenRestream is not configured.await d.prepare("UPDATE managed_streams SET ingest_authority = 'openre' WHERE id = 701").run();const marked = await db.getManagedStreamById(701);assert.strictEqual(authority.authorityOf(marked), 'live');@@@ -155,7 +155,7 @@ function sessionEvent(type, sessionId, revision, payload = {}) {assert.strictEqual((await d.prepare('SELECT COUNT(*) AS n FROM streams').get()).n, 0, 'a slot on live is never mirrored');// ── The switch ───────────────────────────────────────────-- assert.match((await authority.setAuthority(702, 'openre')).error || '', /Go Live page cannot publish to OpenRe/, 'a browser slot needs force');++ assert.match((await authority.setAuthority(702, 'openre')).error || '', /Go Live page cannot publish to OpenRestream/, 'a browser slot needs force');assert.strictEqual((await authority.setAuthority(703, 'openre')).status, 409, 'no canonical subject');const liveRow = await db.createStream({ user_id: 601, managed_stream_id: 701, title: 'on Live', protocol: 'rtmp' });assert.strictEqual((await authority.setAuthority(701, 'openre')).status, 409, 'refused while live on Live');@@@ -173,15 +173,15 @@ function sessionEvent(type, sessionId, revision, payload = {}) {assert.strictEqual(switched.ingest_authority, 'openre');assert.strictEqual(await db.getManagedStreamByStreamKey('livekey701aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'), undefined, "Live's old key is rotated away");assert.strictEqual(await authority.refusesLiveIngest({ managedStream: switched }), true, "Live's RTMP refuses the slot");-- assert.strictEqual(await authority.refusesLiveIngest({ managedStream: switched, protocol: 'srt' }), false, 'only the protocols OpenRe carries are refused');++ assert.strictEqual(await authority.refusesLiveIngest({ managedStream: switched, protocol: 'srt' }), false, 'only the protocols OpenRestream carries are refused');assert.deepStrictEqual([...authority.OPENRE_PROTOCOLS], ['rtmp', 'webrtc', 'jsmpeg']);for (const protocol of ['rtmp', 'webrtc', 'jsmpeg']) {assert.strictEqual(await authority.refusesLiveIngest({ managedStream: switched, protocol }), true, `${protocol}: the slot key is refused`);assert.strictEqual(await authority.refusesLiveIngest({ managedStream: null, user: { id: 601 }, protocol }), true, `${protocol}: the owner's personal key is refused`);assert.strictEqual(await authority.refusesLiveIngest({ managedStream: await db.getManagedStreamById(703), protocol }), false, `${protocol}: a live slot is admitted`);-- assert.strictEqual(await authority.refusesLiveIngest({ managedStream: null, user: { id: 602 }, protocol }), false, `${protocol}: a personal key without an OpenRe slot is admitted`);++ assert.strictEqual(await authority.refusesLiveIngest({ managedStream: null, user: { id: 602 }, protocol }), false, `${protocol}: a personal key without an OpenRestream slot is admitted`);}-- assert.strictEqual(await authority.refusesLiveIngest({ managedStream: null, user: { id: 602 } }), false, 'personal keys of users without an OpenRe slot stay on Live');++ assert.strictEqual(await authority.refusesLiveIngest({ managedStream: null, user: { id: 602 } }), false, 'personal keys of users without an OpenRestream slot stay on Live');assert.strictEqual(await authority.refusesLiveIngest({ managedStream: null, user: { id: 601 } }), true, 'a personal key cannot double-ingest a switched channel');const shown = authority.serializeSlot(switched);assert.strictEqual(shown.stream_key, null);@@@ -196,7 +196,7 @@ function sessionEvent(type, sessionId, revision, payload = {}) {assert.strictEqual(rotated.rtmp_url, 'rtmp://ingest.openre.stream:1936/live');assert.strictEqual(openreCalls.filter(c => c.url.endsWith('/keys/rotate')).pop().subject, SUBJECT);-- // A WHIP encoder slot: its definition is created for WebRTC, the Go Live helpers give OpenRe's WHIP URL.++ // A WHIP encoder slot: its definition is created for WebRTC, the Go Live helpers give OpenRestream's WHIP URL.const whipFlip = await authority.setAuthority(708, 'openre');assert.strictEqual(whipFlip.status, 200, JSON.stringify(whipFlip));assert.deepStrictEqual(openreCalls.filter(c => c.method === 'POST' && c.url === '/api/v1/streams').pop().body.protocols, ['webrtc']);@@@ -205,7 +205,7 @@ function sessionEvent(type, sessionId, revision, payload = {}) {assert.deepStrictEqual([whipIngest.rtmp_url, whipIngest.whip_url, whipIngest.jsmpeg_url], [null, 'https://ingest.openre.stream/whip', null]);assert.match(whipIngest.stream_key_hint, /…WXYZ/);assert.strictEqual((await authority.rotateFor(await db.getManagedStreamById(708), SUBJECT)).whip_url, 'https://ingest.openre.stream/whip');-- // A JSMPEG slot whose OpenRe definition already exists (RTMP only, OpenRe's default): JSMPEG is added to it.++ // A JSMPEG slot whose OpenRestream definition already exists (RTMP only, OpenRestream's default): JSMPEG is added to it.openreDefs.std_slot709 = { id: 'std_slot709', ref: 'live:managed_stream:709', protocols: ['rtmp'] };const jsmpegFlip = await authority.setAuthority(709, 'openre');assert.strictEqual(jsmpegFlip.status, 200, JSON.stringify(jsmpegFlip));@@@ -236,7 +236,7 @@ function sessionEvent(type, sessionId, revision, payload = {}) {assert.strictEqual(await mirror.hasLiveSession(streamId), true, 'stale cleanup leaves it alone');assert.strictEqual(await mirror.ownsStream(streamId), true, 'Live does not resume its own restreams for it');-- // Reconcile: OpenRe says live → heartbeat refreshed; says ended → row ended.++ // Reconcile: OpenRestream says live → heartbeat refreshed; says ended → row ended.openreSessions[SES(4)] = { id: SES(4), state: 'live', revision: 2 };await d.prepare("UPDATE streams SET last_heartbeat = datetime('now', '-10 minutes') WHERE id = ?").run(streamId);await mirror.reconcileOnce();@@@ -280,9 +280,9 @@ function sessionEvent(type, sessionId, revision, payload = {}) {};const openReWhipKey = whipKey();const liveWhipKey = whipKey();-- await d.prepare("INSERT INTO managed_streams (id, user_id, title, protocol, stream_key, ingest_authority) OVERRIDING SYSTEM VALUE VALUES (704, 603, 'WHIP on OpenRe', 'webrtc', ?, 'openre')").run(openReWhipKey);++ await d.prepare("INSERT INTO managed_streams (id, user_id, title, protocol, stream_key, ingest_authority) OVERRIDING SYSTEM VALUE VALUES (704, 603, 'WHIP on OpenRestream', 'webrtc', ?, 'openre')").run(openReWhipKey);await d.prepare("INSERT INTO managed_streams (id, user_id, title, protocol, stream_key) OVERRIDING SYSTEM VALUE VALUES (705, 602, 'WHIP on Live', 'webrtc', ?)").run(liveWhipKey);-- await d.prepare("INSERT INTO managed_streams (id, user_id, title, protocol, stream_key, ingest_authority) OVERRIDING SYSTEM VALUE VALUES (706, 603, 'JSMPEG on OpenRe', 'jsmpeg', 'jsmpegopenre706', 'openre')").run();++ await d.prepare("INSERT INTO managed_streams (id, user_id, title, protocol, stream_key, ingest_authority) OVERRIDING SYSTEM VALUE VALUES (706, 603, 'JSMPEG on OpenRestream', 'jsmpeg', 'jsmpegopenre706', 'openre')").run();await d.prepare("INSERT INTO managed_streams (id, user_id, title, protocol, stream_key) OVERRIDING SYSTEM VALUE VALUES (707, 602, 'JSMPEG on Live', 'jsmpeg', 'jsmpeglive707')").run();process.env.OPENRE_URL = stubUrl;require('../server/openre/openre-client')._reset();@@@ -301,8 +301,8 @@ function sessionEvent(type, sessionId, revision, payload = {}) {['slot id + key', () => publish(704, { key: openReWhipKey })],]) {const r = await call();-- assert.strictEqual(r.status, 401, `WHIP ${label} on an OpenRe slot is refused`);-- assert.ok(!/openre/i.test(JSON.stringify(r.body)), 'the refusal does not say OpenRe exists');++ assert.strictEqual(r.status, 401, `WHIP ${label} on an OpenRestream slot is refused`);++ assert.ok(!/openre/i.test(JSON.stringify(r.body)), 'the refusal does not say OpenRestream exists');}assert.strictEqual(await streamCount(), before, 'a refused WHIP publish creates no stream row');const admitted = await publish(liveWhipKey);@@@ -310,7 +310,7 @@ function sessionEvent(type, sessionId, revision, payload = {}) {for (const r of [admitted, await publish(705, { key: liveWhipKey })]) assert.ok([200, 201, 503].includes(r.status), `live slot: ${r.status}`);// The switch landing between publishes: the next check refuses.await d.prepare("UPDATE managed_streams SET ingest_authority = 'openre' WHERE id = 705").run();-- assert.strictEqual((await publish(liveWhipKey)).status, 401, 'refused as soon as the slot is on OpenRe');++ assert.strictEqual((await publish(liveWhipKey)).status, 401, 'refused as soon as the slot is on OpenRestream');await d.prepare("UPDATE managed_streams SET ingest_authority = 'live' WHERE id = 705").run();// The Go Live page's in-browser broadcaster (/ws/broadcast, role=broadcaster) is WebRTC ingest too.@@@ -328,8 +328,8 @@ function sessionEvent(type, sessionId, revision, payload = {}) {const browserOnOpenre = Number((await db.createStream({ user_id: 603, managed_stream_id: 704, title: 'browser', protocol: 'webrtc' })).lastInsertRowid);const browserOnLive = Number((await db.createStream({ user_id: 602, managed_stream_id: 705, title: 'browser', protocol: 'webrtc' })).lastInsertRowid);const browserPersonal = Number((await db.createStream({ user_id: 603, title: 'browser', protocol: 'webrtc' })).lastInsertRowid);-- assert.deepStrictEqual(await connectBroadcaster(browserOnOpenre, 'tok-603'), [[4003, 'Stream key not recognized']], 'browser broadcaster on an OpenRe slot is refused');-- assert.deepStrictEqual(await connectBroadcaster(browserPersonal, 'tok-603'), [[4003, 'Stream key not recognized']], 'a slot-less stream of an owner with an OpenRe slot is refused');++ assert.deepStrictEqual(await connectBroadcaster(browserOnOpenre, 'tok-603'), [[4003, 'Stream key not recognized']], 'browser broadcaster on an OpenRestream slot is refused');++ assert.deepStrictEqual(await connectBroadcaster(browserPersonal, 'tok-603'), [[4003, 'Stream key not recognized']], 'a slot-less stream of an owner with an OpenRestream slot is refused');assert.deepStrictEqual(await connectBroadcaster(browserOnLive, 'tok-602'), [], 'browser broadcaster on a Live slot is admitted');const relay = require('../server/streaming/jsmpeg-relay');@@@ -344,9 +344,9 @@ function sessionEvent(type, sessionId, revision, payload = {}) {const liveCh = relay.createChannel('jsmpeglive707');const personalCh = relay.createChannel('personalkey601');await new Promise(r => setTimeout(r, 200));-- assert.strictEqual(await post(openreCh.videoPort, 'jsmpegopenre706'), 404, 'JSMPEG video: an OpenRe slot key is refused');-- assert.strictEqual(await post(openreCh.audioPort, 'jsmpegopenre706'), 404, 'JSMPEG audio: an OpenRe slot key is refused');-- assert.strictEqual(await post(personalCh.videoPort, 'personalkey601'), 404, "JSMPEG: the owner's personal key is refused while a slot is on OpenRe");++ assert.strictEqual(await post(openreCh.videoPort, 'jsmpegopenre706'), 404, 'JSMPEG video: an OpenRestream slot key is refused');++ assert.strictEqual(await post(openreCh.audioPort, 'jsmpegopenre706'), 404, 'JSMPEG audio: an OpenRestream slot key is refused');++ assert.strictEqual(await post(personalCh.videoPort, 'personalkey601'), 404, "JSMPEG: the owner's personal key is refused while a slot is on OpenRestream");assert.strictEqual(await post(liveCh.videoPort, 'jsmpeglive707'), 200, 'JSMPEG: a Live slot is admitted');assert.strictEqual(await post(liveCh.audioPort, 'jsmpeglive707'), 200);@@@ -364,7 +364,7 @@ function sessionEvent(type, sessionId, revision, payload = {}) {process.env.OPENRE_URL = stubUrl;require('../server/openre/openre-client')._reset();-- // The admin status lists the protocols OpenRe carries.++ // The admin status lists the protocols OpenRestream carries.const statusApp = express();// routes.js takes requireAdmin at load: admit the test caller before the router is first required.require('../server/auth/auth').requireAdmin = (req, res, next) => { req.user = { id: 1, username: 'admin', role: 'admin' }; next(); };@@@ -375,7 +375,7 @@ function sessionEvent(type, sessionId, revision, payload = {}) {else assert.fail(`status route answered ${statusRes.status}`);statusServer.close();-- // The Go Live profile and endpoint of an OpenRe WHIP/JSMPEG slot point at OpenRe, never at Live's++ // The Go Live profile and endpoint of an OpenRestream WHIP/JSMPEG slot point at OpenRestream, never at Live's// WHIP base or relay (Live refuses those publishes).require('../server/auth/auth').requireAuth = async (req, res, next) => { req.user = await db.getUserById(601); next(); };const streamsApp = express();@@@ -407,5 +407,5 @@ function sessionEvent(type, sessionId, revision, payload = {}) {mirror._reset();server.close();stub.close();-- console.log('✅ openre switch: off = unchanged, switch/rotation, RTMP/WHIP/browser/JSMPEG refusal, OpenRe ingest URLs, signed mirror (once, ordered), reconcile, rollback');++ console.log('✅ openre switch: off = unchanged, switch/rotation, RTMP/WHIP/browser/JSMPEG refusal, OpenRestream ingest URLs, signed mirror (once, ordered), reconcile, rollback');})().catch((err) => { console.error(err); process.exit(1); });@@@ -4,7 +4,7 @@** Boots the real server as a restore-drill instance (test/security-crawl.js) with a sentinel value in* every secret environment variable Live reads (the Network OAuth client secret, the internal API-- * keys, Media's webhook/event secrets, the Events and OpenRe subscription secrets, TURN,++ * keys, Media's webhook/event secrets, the Events and OpenRestream subscription secrets, TURN,* PayPal, the admin password, GitHub tokens, the ops webhook) and in every secret site setting an* owner can store (GIF, TTS, AI, Twitch/Kick/YouTube, soundboard keys, the Discord and ops* webhooks), then requests every GET route Express knows, the pages, the probes (/api/ready,@@@ -361,7 +361,7 @@ async function check(name, fn) {'server/monetization/wallet-client.js': 'OpenVibe.Network wallet (configured)','server/monetization/payments.js': 'PayPal (fixed host)','server/monetization/cosmetics.js': 'legacy quest API (env)',-- 'server/openre/openre-client.js': 'OpenRe.Stream (configured)',++ 'server/openre/openre-client.js': 'OpenRestream (configured)','server/ai/ai-service.js': 'OpenVibe.AI (configured)','server/ai/llm.js': 'images are Live\'s own frames; a streamer\'s typed provider address (testProvider) goes through egress.postJson','server/chat/soundboard-service.js': '101soundboards (host allowlist; the audio download through safeLookup)',@@@ -6,7 +6,7 @@* managed_streams[].stream_key, then the stream detail's managed_stream_key; see* public-serializers.test.js and security-redaction.test.js), enough to publish to someone's slot* over RTMP or WHIP. Those tests pin the endpoints that leaked. This one covers the class: it seeds-- * a streamer whose every credential is a sentinel (account key, a Live slot key, an OpenRe slot's++ * a streamer whose every credential is a sentinel (account key, a Live slot key, an OpenRestream slot's* rotated Live key, a restream key and SRT passphrase, RobotStreamer / platform / PowerChat tokens,* AI keys, control key and API token hashes, a camera password hash), boots the real server as a* restore-drill instance (test/security-crawl.js), and requests EVERY GET route Express knows,@@@ -70,7 +70,7 @@ const SEED = `(async () => {const admin = await mk('keyadmin', 'admin', K.admin);const ch = await db.getChannelByUserId(owner);const slot = Number((await db.createManagedStream({ user_id: owner, channel_id: ch.id, slug: 'main', title: 'Main slot', protocol: 'rtmp', stream_key: K.slotMain })).lastInsertRowid);-- const slot2 = Number((await db.createManagedStream({ user_id: owner, channel_id: ch.id, slug: 'relay', title: 'OpenRe slot', protocol: 'rtmp', stream_key: K.slotOpenre })).lastInsertRowid);++ const slot2 = Number((await db.createManagedStream({ user_id: owner, channel_id: ch.id, slug: 'relay', title: 'OpenRestream slot', protocol: 'rtmp', stream_key: K.slotOpenre })).lastInsertRowid);await db.run("UPDATE managed_streams SET ingest_authority = 'openre', openre_stream_id = 'std_sentinel' WHERE id = ?", [slot2]);const live = Number((await db.createStream({ user_id: owner, channel_id: ch.id, managed_stream_id: slot, title: 'Live now', protocol: 'rtmp' })).lastInsertRowid);const ended = Number((await db.createStream({ user_id: owner, channel_id: ch.id, managed_stream_id: slot2, title: 'Earlier', protocol: 'rtmp' })).lastInsertRowid);@@@ -195,7 +195,7 @@ const buttons = async (streamId) => (await db.getStreamControls(streamId)).map((assert.match(routes, /control_config_id: await ownControlConfigId\(req, req\.body\.control_config_id\) \|\| null,/, 'a new slot takes only the owner\'s profile');assert.match(routes, /const cfgId = await ownControlConfigId\(req, req\.body\.control_config_id\);\s*if \(cfgId === undefined\) return res\.status\(403\)/, 'and so does a slot edit');const mirror = read('server/openre/mirror.js');-- assert.match(mirror, /const configId = slot\.control_config_id \|\| \(channel && channel\.active_control_config_id\);/, 'OpenRe mirror');++ assert.match(mirror, /const configId = slot\.control_config_id \|\| \(channel && channel\.active_control_config_id\);/, 'OpenRestream mirror');});server.close();@@@ -1,8 +1,8 @@#!/usr/bin/env node'use strict';/**-- * The Go Live workspace's ingest URLs for a slot ingested by OpenRe: WHIP and JSMPEG point at-- * OpenRe's servers with the key in the path, never at Live's /whip/<slot> or JSMPEG relay (Live++ * The Go Live workspace's ingest URLs for a slot ingested by OpenRestream: WHIP and JSMPEG point at++ * OpenRestream's servers with the key in the path, never at Live's /whip/<slot> or JSMPEG relay (Live* refuses those publishes). Slots on Live's own ingest keep Live's URLs.*/const assert = require('assert');@@@ -27,15 +27,15 @@ _wsState.whipUrlBase = 'https://whip.openvibe.live/';assert.strictEqual(_wsWhipUrl(708, 'livekey'), 'https://whip.openvibe.live/whip/708');assert.ok(_wsRenderMethodEndpoint('cli', 'livekey', 708).includes('http://openvibe.live:PORT/livekey/640/480/'));--// OpenRe: its WHIP URL takes the key, its JSMPEG server takes key/width/height.++// OpenRestream: its WHIP URL takes the key, its JSMPEG server takes key/width/height.Object.assign(_wsState, { whipUrlBase: null, openreWhipUrl: 'https://ingest.openre.stream/whip', openreJsmpegUrl: 'http://ingest.openre.stream:8081' });assert.strictEqual(_wsWhipUrl(708, 'ork_key'), 'https://ingest.openre.stream/whip/ork_key');const whipInfo = _wsRenderMethodEndpoint('whip', 'ork_key', 708);assert.ok(whipInfo.includes('https://ingest.openre.stream/whip/ork_key'), whipInfo);assert.ok(!whipInfo.includes('/whip/708'));const cli = _wsRenderMethodEndpoint('cli', 'ork_key', 709);--assert.ok(cli.includes('http://ingest.openre.stream:8081/ork_key/640/480/'), 'JSMPEG commands use OpenRe');++assert.ok(cli.includes('http://ingest.openre.stream:8081/ork_key/640/480/'), 'JSMPEG commands use OpenRestream');assert.ok(!cli.includes('openvibe.live:PORT'));--assert.ok(cli.includes('https://ingest.openre.stream/whip/ork_key'), 'the CLI WHIP tab uses OpenRe');++assert.ok(cli.includes('https://ingest.openre.stream/whip/ork_key'), 'the CLI WHIP tab uses OpenRestream');--console.log('✅ workspace OpenRe ingest: WHIP and JSMPEG URLs point at OpenRe for switched slots, at Live otherwise');++console.log('✅ workspace OpenRestream ingest: WHIP and JSMPEG URLs point at OpenRestream for switched slots, at Live otherwise');
@@ -67,7 +67,7 @@ OV_OAUTH_CLIENT_SECRET=
@@ -67,7 +67,7 @@ OV_OAUTH_CLIENT_SECRET=
# Unset = both paths, as before.
# Unset = both paths, as before.
#GOLIVE_NOTIFY=events
#GOLIVE_NOTIFY=events
-# OpenRe.Stream (roadmap Wave 7): the ingest/restream authority. Unset = off, and every stream slot
+# OpenRestream (roadmap Wave 7): the ingest/restream authority. Unset = off, and every stream slot
# is ingested by Live exactly as before. When set, an admin can switch one slot at a time
# is ingested by Live exactly as before. When set, an admin can switch one slot at a time
# (PUT /api/admin/openre/managed/:id/ingest-authority); RTMP, WHIP and JSMPEG move with it. Live's service principal
# (PUT /api/admin/openre/managed/:id/ingest-authority); RTMP, WHIP and JSMPEG move with it. Live's service principal
# (the OAuth client above) needs openre.stream.read, openre.stream.write, openre.key.rotate and
# (the OAuth client above) needs openre.stream.read, openre.stream.write, openre.key.rotate and
@@ -68,7 +68,7 @@ The media subsystem lives in **OpenVibe.Media**:
@@ -68,7 +68,7 @@ The media subsystem lives in **OpenVibe.Media**:
- Streaming ingest and viewer playback: RTMP (`node-media-server`), WHIP and WebRTC (`mediasoup`), JSMPEG, and the broadcast page.
- Streaming ingest and viewer playback: RTMP (`node-media-server`), WHIP and WebRTC (`mediasoup`), JSMPEG, and the broadcast page.
- Channels and streams: go-live, stream keys, managed streams, heartbeats, channel pages (`/@username`), offline screens, panels and goals.
- Channels and streams: go-live, stream keys, managed streams, heartbeats, channel pages (`/@username`), offline screens, panels and goals.
- The watch experience: the SPA in `public/`, the chat surface and its effects (TTS, sounds, emotes, overlays), anonymous chat identities.
- The watch experience: the SPA in `public/`, the chat surface and its effects (TTS, sounds, emotes, overlays), anonymous chat identities.
-- Streamer tools: the dashboard, restreaming (via OpenRe for managed ingest), song requests (watch party), AI viewers (the context Live builds; the runs are OpenVibe.AI's).
+- Streamer tools: the dashboard, restreaming (via OpenRestream for managed ingest), song requests (watch party), AI viewers (the context Live builds; the runs are OpenVibe.AI's).
- Channel points, Vibes tipping flows (the ledger moves to OpenVibe.Billing when `BILLING_AUTHORITY=billing`), moderation of Live's own surfaces, the Arena (Battle Cam) and after-show recaps.
- Channel points, Vibes tipping flows (the ledger moves to OpenVibe.Billing when `BILLING_AUTHORITY=billing`), moderation of Live's own surfaces, the Arena (Battle Cam) and after-show recaps.
- Live-local state: PostgreSQL `ov_live` (users' Live profiles, streams, channel state, AI state for Media-hosted recordings in `vod_ai_state` / `clip_ai_state`, request analytics).
- Live-local state: PostgreSQL `ov_live` (users' Live profiles, streams, channel state, AI state for Media-hosted recordings in `vod_ai_state` / `clip_ai_state`, request analytics).
@@ -85,7 +85,7 @@ The media subsystem lives in **OpenVibe.Media**:
@@ -85,7 +85,7 @@ The media subsystem lives in **OpenVibe.Media**:
- **OpenVibe.Network**: sign-in (OAuth2 client `live`), the signing key, service tokens (`server/net/network-principal.js`), follows, notifications, the wallet, the OpenVibe Frame (`openvibe-shared`, served at `/shared/*`).
- **OpenVibe.Network**: sign-in (OAuth2 client `live`), the signing key, service tokens (`server/net/network-principal.js`), follows, notifications, the wallet, the OpenVibe Frame (`openvibe-shared`, served at `/shared/*`).
- **OpenVibe.Media**: VOD recording, storage (local, B2, R2), playback, clips, thumbnails, signed playback URLs. Outcomes arrive as `media.*` events at `POST /internal/media-events`.
- **OpenVibe.Media**: VOD recording, storage (local, B2, R2), playback, clips, thumbnails, signed playback URLs. Outcomes arrive as `media.*` events at `POST /internal/media-events`.
-- **OpenVibe.Chat**, **OpenVibe.Community**, **OpenVibe.AI**, **OpenVibe.Events**, **OpenRe.Stream**, **OpenVibe.Billing**, **OpenVibe.Tips**, **OpenVibe.VIP**, **OpenVibe.Tools**, **OpenVibe.Search**: through their APIs with Live's service token.
+- **OpenVibe.Chat**, **OpenVibe.Community**, **OpenVibe.AI**, **OpenVibe.Events**, **OpenRestream**, **OpenVibe.Billing**, **OpenVibe.Tips**, **OpenVibe.VIP**, **OpenVibe.Tools**, **OpenVibe.Search**: through their APIs with Live's service token.
- **Libraries**: `openvibe-contracts` (pinned tag), `openvibe-sdk`, `openvibe-shared`; FFmpeg and whisper.cpp on the host.
- **Libraries**: `openvibe-contracts` (pinned tag), `openvibe-sdk`, `openvibe-shared`; FFmpeg and whisper.cpp on the host.
## Capabilities
## Capabilities
@@ -99,7 +99,7 @@ Live's own principal (`live`) holds grants to call:
@@ -99,7 +99,7 @@ Live's own principal (`live`) holds grants to call:
- **Chat**: `chat.message.send`, `chat.event.publish`, `chat.moderation.write`, `chat.cache.invalidate`, `chat.presence.read`; `chat.live_bridge.write` for the call channels (`server/streaming/calls-authority.js`) and the one-off outbox delivery (`scripts/chat-bridge-outbox-drop.js`).
- **Chat**: `chat.message.send`, `chat.event.publish`, `chat.moderation.write`, `chat.cache.invalidate`, `chat.presence.read`; `chat.live_bridge.write` for the call channels (`server/streaming/calls-authority.js`) and the one-off outbox delivery (`scripts/chat-bridge-outbox-drop.js`).
- **Community**: `community.paste.*`, `community.comment.*`, `community.pulse.write`.
- **Community**: `community.paste.*`, `community.comment.*`, `community.pulse.write`.
- **Events**: `events.event.publish|read`, `events.subscription.manage`.
- **Events**: `events.event.publish|read`, `events.subscription.manage`.
-- **OpenRe**: `openre.stream.*`, `openre.key.rotate`, `openre.session.read`.
+- **OpenRestream**: `openre.stream.*`, `openre.key.rotate`, `openre.session.read`.
- **Billing**: `billing.*` (intents, transfers, subscriptions, cash-outs, balances, entitlements).
- **Billing**: `billing.*` (intents, transfers, subscriptions, cash-outs, balances, entitlements).
- **Elsewhere**: `tips.interaction.record`, `vip.entitlement.check`, `tools.tool.run`, `tools.job.read`.
- **Elsewhere**: `tips.interaction.record`, `vip.entitlement.check`, `tools.tool.run`, `tools.job.read`.
@@ -22,7 +22,7 @@
@@ -22,7 +22,7 @@
"deployment": "Production release layout /opt/openvibe.live (current -> releases/<time>-<sha>), unit openvibe-live.service on the systemd socket 127.0.0.1:3000, deployed with `sudo ovhost deploy live --wait-idle`.",
"deployment": "Production release layout /opt/openvibe.live (current -> releases/<time>-<sha>), unit openvibe-live.service on the systemd socket 127.0.0.1:3000, deployed with `sudo ovhost deploy live --wait-idle`.",
"features": [
"features": [
"RTMP, WHIP, WebRTC and JSMPEG ingest; viewer playback; the broadcast page",
"RTMP, WHIP, WebRTC and JSMPEG ingest; viewer playback; the broadcast page",
- "channels, go-live, stream keys, managed streams, restreaming (managed ingest on OpenRe.Stream)",
+ "channels, go-live, stream keys, managed streams, restreaming (managed ingest on OpenRestream)",
"the watch page and its chat surface (messages stored by OpenVibe.Chat), TTS, sounds, emotes, overlays",
"the watch page and its chat surface (messages stored by OpenVibe.Chat), TTS, sounds, emotes, overlays",
"VODs and clips recorded and stored by OpenVibe.Media; pastes and comments on OpenVibe.Community",
"VODs and clips recorded and stored by OpenVibe.Media; pastes and comments on OpenVibe.Community",
"AI viewers, stream memories, overviews, moments, recaps, translation: every prompt an OpenVibe.AI template; VOD and clip transcripts by window on OpenVibe.AI",
"AI viewers, stream memories, overviews, moments, recaps, translation: every prompt an OpenVibe.AI template; VOD and clip transcripts by window on OpenVibe.AI",
@@ -226,7 +226,7 @@ through the SDK inbox (`idempotency_receipts`):
@@ -226,7 +226,7 @@ through the SDK inbox (`idempotency_receipts`):
| topics | endpoint | secret | subscribe with |
| topics | endpoint | secret | subscribe with |
|---|---|---|---|
|---|---|---|---|
-| `openre.session.*` | `/internal/openre-events` | `OPENRE_EVENTS_SECRET` | OpenRe's `scripts/subscribe-live-events.js` |
+| `openre.session.*` | `/internal/openre-events` | `OPENRE_EVENTS_SECRET` | OpenRestream's `scripts/subscribe-live-events.js` |
| `media.vod.*`, `media.clip.*`, `media.storage.*` | `/internal/media-events` | `MEDIA_EVENTS_SECRET` | `scripts/subscribe-media-events.js` |
| `media.vod.*`, `media.clip.*`, `media.storage.*` | `/internal/media-events` | `MEDIA_EVENTS_SECRET` | `scripts/subscribe-media-events.js` |
#### Media outcomes over Events
#### Media outcomes over Events
@@ -261,7 +261,7 @@ async function _wsLoadProfile(managedStreamId) {
@@ -261,7 +261,7 @@ async function _wsLoadProfile(managedStreamId) {
_wsState.selectedMs = { ..._wsState.selectedMs, ...data.managed_stream };
_wsState.selectedMs = { ..._wsState.selectedMs, ...data.managed_stream };
}
}
_wsState.streamKey = data.stream_key || null;
_wsState.streamKey = data.stream_key || null;
- // OpenRe-ingested slot: no key in the profile, only a hint (Regenerate shows a new key once).
+ // OpenRestream-ingested slot: no key in the profile, only a hint (Regenerate shows a new key once).
_wsState.streamKeyHint = data.stream_key_hint || null;
_wsState.streamKeyHint = data.stream_key_hint || null;
_wsState.profile = data.broadcast_settings || {};
_wsState.profile = data.broadcast_settings || {};
_wsState.profile.vibeCoding = _wsNormalizeVibeSettings(_wsReadStoredVibeSettings(_wsState.profile));
_wsState.profile.vibeCoding = _wsNormalizeVibeSettings(_wsReadStoredVibeSettings(_wsState.profile));
@@ -269,7 +269,7 @@ async function _wsLoadProfile(managedStreamId) {
@@ -269,7 +269,7 @@ async function _wsLoadProfile(managedStreamId) {
_wsState.whipUrlSource = data.whip_url_source || null;
_wsState.whipUrlSource = data.whip_url_source || null;
_wsState.whipUrlWarning = data.whip_url_warning || null;
_wsState.whipUrlWarning = data.whip_url_warning || null;
_wsState.rtmpUrl = data.rtmp_url || null;
_wsState.rtmpUrl = data.rtmp_url || null;
- // OpenRe-ingested slot: OpenRe's WHIP and JSMPEG servers (the key goes after each).
+ // OpenRestream-ingested slot: OpenRestream's WHIP and JSMPEG servers (the key goes after each).
_wsState.openreWhipUrl = data.whip_url || null;
_wsState.openreWhipUrl = data.whip_url || null;
_wsState.openreJsmpegUrl = data.jsmpeg_url || null;
_wsState.openreJsmpegUrl = data.jsmpeg_url || null;
} catch {
} catch {
@@ -1384,7 +1384,7 @@ function _wsRenderBrowserModeCards(selected) {
@@ -1384,7 +1384,7 @@ function _wsRenderBrowserModeCards(selected) {
/* ── Method-specific endpoint info ───────────────────────────── */
/* ── Method-specific endpoint info ───────────────────────────── */
-// Live's WHIP takes the slot id in the path (key as Bearer token); OpenRe's takes the key itself.
+// Live's WHIP takes the slot id in the path (key as Bearer token); OpenRestream's takes the key itself.
function _wsWhipUrl(managedStreamId, streamKey) {
function _wsWhipUrl(managedStreamId, streamKey) {
if (_wsState.openreWhipUrl) return `${_wsState.openreWhipUrl.replace(/\/$/, '')}/${streamKey}`;
if (_wsState.openreWhipUrl) return `${_wsState.openreWhipUrl.replace(/\/$/, '')}/${streamKey}`;
const whipBaseUrl = (_wsState.whipUrlBase || window.location.origin).replace(/\/$/, '');
const whipBaseUrl = (_wsState.whipUrlBase || window.location.origin).replace(/\/$/, '');
@@ -1876,7 +1876,7 @@ async function _wsRegenerateKey(managedStreamId) {
@@ -1876,7 +1876,7 @@ async function _wsRegenerateKey(managedStreamId) {
try {
try {
const data = await api(`/streams/managed/${managedStreamId}/regenerate-key`, { method: 'POST' });
const data = await api(`/streams/managed/${managedStreamId}/regenerate-key`, { method: 'POST' });
_wsState.streamKey = data.stream_key;
_wsState.streamKey = data.stream_key;
- // OpenRe's rotation answers with its ingest servers too.
+ // OpenRestream's rotation answers with its ingest servers too.
if (data.stream_key_managed_by === 'openre') {
if (data.stream_key_managed_by === 'openre') {
_wsState.rtmpUrl = data.rtmp_url || _wsState.rtmpUrl;
_wsState.rtmpUrl = data.rtmp_url || _wsState.rtmpUrl;
_wsState.openreWhipUrl = data.whip_url || _wsState.openreWhipUrl;
_wsState.openreWhipUrl = data.whip_url || _wsState.openreWhipUrl;
@@ -1796,7 +1796,7 @@ async function showRTMPInstructions(stream) {
@@ -1796,7 +1796,7 @@ async function showRTMPInstructions(stream) {
const data = await api(`/streams/${stream.id}/endpoint`);
const data = await api(`/streams/${stream.id}/endpoint`);
const ep = data.endpoint || {};
const ep = data.endpoint || {};
const rtmpUrl = ep.rtmpUrl || `rtmp://${location.hostname}:1935/live`;
const rtmpUrl = ep.rtmpUrl || `rtmp://${location.hostname}:1935/live`;
- // An OpenRe-ingested slot never returns its key here, only a hint (Regenerate shows a new key once).
+ // An OpenRestream-ingested slot never returns its key here, only a hint (Regenerate shows a new key once).
const streamKey = ep.streamKey || data.stream_key || ep.streamKeyHint || 'N/A';
const streamKey = ep.streamKey || data.stream_key || ep.streamKeyHint || 'N/A';
document.getElementById('bc-rtmp-url').textContent = rtmpUrl;
document.getElementById('bc-rtmp-url').textContent = rtmpUrl;
document.getElementById('bc-rtmp-key').textContent = streamKey;
document.getElementById('bc-rtmp-key').textContent = streamKey;
@@ -1945,7 +1945,7 @@ async function showJSMPEGInstructions(stream) {
@@ -1945,7 +1945,7 @@ async function showJSMPEGInstructions(stream) {
const data = await api(`/streams/${stream.id}/endpoint`);
const data = await api(`/streams/${stream.id}/endpoint`);
const ep = data.endpoint || {};
const ep = data.endpoint || {};
const host = location.hostname;
const host = location.hostname;
- // An OpenRe-ingested slot: OpenRe's JSMPEG server (video and audio share it) and only a key hint.
+ // An OpenRestream-ingested slot: OpenRestream's JSMPEG server (video and audio share it) and only a key hint.
const videoServer = ep.jsmpegUrl ? ep.jsmpegUrl.replace(/\/$/, '') : `http://${host}:${ep.videoPort || 9710}`;
const videoServer = ep.jsmpegUrl ? ep.jsmpegUrl.replace(/\/$/, '') : `http://${host}:${ep.videoPort || 9710}`;
const audioServer = ep.jsmpegUrl ? videoServer : `http://${host}:${ep.audioPort || 9711}`;
const audioServer = ep.jsmpegUrl ? videoServer : `http://${host}:${ep.audioPort || 9711}`;
const key = data.stream_key || ep.streamKeyHint || 'N/A';
const key = data.stream_key || ep.streamKeyHint || 'N/A';
@@ -2003,7 +2003,7 @@ async function showWHIPInstructions(stream) {
@@ -2003,7 +2003,7 @@ async function showWHIPInstructions(stream) {
let openreWhip = null;
let openreWhip = null;
try {
try {
const data = await api(`/streams/${stream.id}/endpoint`);
const data = await api(`/streams/${stream.id}/endpoint`);
- // An OpenRe-ingested slot: OpenRe's WHIP URL takes the key (only its hint is known here).
+ // An OpenRestream-ingested slot: OpenRestream's WHIP URL takes the key (only its hint is known here).
if (data.endpoint?.whipUrl) openreWhip = `${data.endpoint.whipUrl.replace(/\/$/, '')}/${data.endpoint.streamKeyHint || 'N/A'}`;
if (data.endpoint?.whipUrl) openreWhip = `${data.endpoint.whipUrl.replace(/\/$/, '')}/${data.endpoint.streamKeyHint || 'N/A'}`;
whipBaseUrl = data.endpoint?.whipUrlBase || null;
whipBaseUrl = data.endpoint?.whipUrlBase || null;
if (data.endpoint?.whipUrlSource === 'request_host') {
if (data.endpoint?.whipUrlSource === 'request_host') {
@@ -2019,7 +2019,7 @@ async function showWHIPInstructions(stream) {
@@ -2019,7 +2019,7 @@ async function showWHIPInstructions(stream) {
}
}
}
}
document.getElementById('bc-whip-url').textContent = openreWhip || `${whipBaseUrl}/whip/${stream.id}`;
document.getElementById('bc-whip-url').textContent = openreWhip || `${whipBaseUrl}/whip/${stream.id}`;
- // Live's sign-in token is never offered as the Bearer for OpenRe's WHIP.
+ // Live's sign-in token is never offered as the Bearer for OpenRestream's WHIP.
const token = (!openreWhip && getStoredAuthToken()) || 'N/A';
const token = (!openreWhip && getStoredAuthToken()) || 'N/A';
const tokenEl = document.getElementById('bc-whip-token');
const tokenEl = document.getElementById('bc-whip-token');
if (tokenEl) {
if (tokenEl) {
@@ -323,7 +323,7 @@ function webrtcStreamHasActiveProducer(streamId) {
@@ -323,7 +323,7 @@ function webrtcStreamHasActiveProducer(streamId) {
async function hasActiveLiveFeed(stream) {
async function hasActiveLiveFeed(stream) {
if (!stream) return false;
if (!stream) return false;
- // OpenRe.Stream holds the ingest (a mirrored session it confirmed recently): not stale.
+ // OpenRestream holds the ingest (a mirrored session it confirmed recently): not stale.
if (await require('./openre/mirror').hasLiveSession(stream.id)) return true;
if (await require('./openre/mirror').hasLiveSession(stream.id)) return true;
const streamKey = await getStreamKey(stream);
const streamKey = await getStreamKey(stream);
if (stream.protocol === 'rtmp') {
if (stream.protocol === 'rtmp') {
@@ -668,7 +668,7 @@ app.post('/internal/media-webhook', require('./media-proxy/webhook'));
@@ -668,7 +668,7 @@ app.post('/internal/media-webhook', require('./media-proxy/webhook'));
app.post('/internal/media-events', require('./media-proxy/media-events').handler);
app.post('/internal/media-events', require('./media-proxy/media-events').handler);
// Network identity events (signed out everywhere, password changed, banned): Live refuses older tokens.
// Network identity events (signed out everywhere, password changed, banned): Live refuses older tokens.
app.post('/internal/network-events', require('./auth/network-events').handler);
app.post('/internal/network-events', require('./auth/network-events').handler);
-// OpenVibe.Events → Live: OpenRe session lifecycle mirrored into `streams` (signed delivery,
+// OpenVibe.Events → Live: OpenRestream session lifecycle mirrored into `streams` (signed delivery,
// OPENRE_EVENTS_SECRET; server/openre/mirror.js). Also before /internal (no X-Internal-Key).
// OPENRE_EVENTS_SECRET; server/openre/mirror.js). Also before /internal (no X-Internal-Key).
app.post('/internal/openre-events', require('./openre/mirror').webhookHandler);
app.post('/internal/openre-events', require('./openre/mirror').webhookHandler);
@@ -1257,7 +1257,7 @@ async function start() {
@@ -1257,7 +1257,7 @@ async function start() {
// 5b. Resume enabled restreams for streams that survived the restart.
// 5b. Resume enabled restreams for streams that survived the restart.
// WHIP/RTMP broadcasters have no browser session to re-start them manually.
// WHIP/RTMP broadcasters have no browser session to re-start them manually.
for (const stream of await db.getLiveStreams()) {
for (const stream of await db.getLiveStreams()) {
- // OpenRe restreams its own sessions; Live must not start a second push for them.
+ // OpenRestream restreams its own sessions; Live must not start a second push for them.
if (await require('./openre/mirror').ownsStream(stream.id)) continue;
if (await require('./openre/mirror').ownsStream(stream.id)) continue;
restreamManager.resumeForStream(stream.id, stream.user_id, {
restreamManager.resumeForStream(stream.id, stream.user_id, {
protocol: stream.protocol,
protocol: stream.protocol,
@@ -1326,8 +1326,8 @@ async function start() {
@@ -1326,8 +1326,8 @@ async function start() {
// 6e. Start periodic viewer count polling for restream destinations
// 6e. Start periodic viewer count polling for restream destinations
restreamManager.startViewerCountPolling();
restreamManager.startViewerCountPolling();
- // 6g. OpenRe mirror reconcile (only runs when OPENRE_URL is set; idle with nothing mirrored).
- try { require('./openre/mirror').start(); } catch (e) { console.warn('[OpenRe] mirror reconcile not started:', e.message); }
+ // 6g. OpenRestream mirror reconcile (only runs when OPENRE_URL is set; idle with nothing mirrored).
+ try { require('./openre/mirror').start(); } catch (e) { console.warn('[OpenRestream] mirror reconcile not started:', e.message); }
// 6f. VOD storage/offload is owned by OpenVibe.Media now — nothing to start here.
// 6f. VOD storage/offload is owned by OpenVibe.Media now — nothing to start here.
@@ -1,7 +1,7 @@
@@ -1,7 +1,7 @@
'use strict';
'use strict';
/**
/**
* GET|POST /internal/lineage/resolve: the canonical channel/owner resolver (./resolver.js) for other
* GET|POST /internal/lineage/resolve: the canonical channel/owner resolver (./resolver.js) for other
- * services (Pulse, OpenRe, Media, creator UI). Service token only (aud openvibe.live, capability
+ * services (Pulse, OpenRestream, Media, creator UI). Service token only (aud openvibe.live, capability
* live.lineage.resolve), loopback only.
* live.lineage.resolve), loopback only.
*
*
* GET ?slug=&parent_slug=&channel_id=&stream_id=&slot_id=&vod_id=&clip_id=&media_object_id=
* GET ?slug=&parent_slug=&channel_id=&stream_id=&slot_id=&vod_id=&clip_id=&media_object_id=
@@ -8,7 +8,7 @@
@@ -8,7 +8,7 @@
* Auth: `Authorization: Bearer <service token>`. Live authenticates to Media with its own
* Auth: `Authorization: Bearer <service token>`. Live authenticates to Media with its own
* Network service principal (client credentials for audience openvibe.media; grants
* Network service principal (client credentials for audience openvibe.media; grants
* media.object.read/list/upload/delete on namespace live), the same construction
* media.object.read/list/upload/delete on namespace live), the same construction
- * server/openre/openre-client.js uses for OpenRe. For a request made on behalf of a browser
+ * server/openre/openre-client.js uses for OpenRestream. For a request made on behalf of a browser
* user, add `actingUser` (their LIVE-LOCAL user id) — Media then applies that user's ACLs and
* user, add `actingUser` (their LIVE-LOCAL user id) — Media then applies that user's ACLs and
* stores their id. See _authHeader for why the id is sent explicitly rather than left for
* stores their id. See _authHeader for why the id is sent explicitly rather than left for
* Media to read out of the caller's Network JWT.
* Media to read out of the caller's Network JWT.
@@ -2,14 +2,14 @@
@@ -2,14 +2,14 @@
/**
/**
* The per-slot ingest switch: managed_streams.ingest_authority = 'live' (default) | 'openre'.
* The per-slot ingest switch: managed_streams.ingest_authority = 'live' (default) | 'openre'.
*
*
- * 'openre' means OpenRe.Stream ingests this slot: Live's RTMP server, WHIP handler, browser
+ * 'openre' means OpenRestream ingests this slot: Live's RTMP server, WHIP handler, browser
* broadcaster socket and JSMPEG relay refuse the slot's key (so a stream can never be ingested
* broadcaster socket and JSMPEG relay refuse the slot's key (so a stream can never be ingested
- * twice), the Go Live UI shows OpenRe's ingest URLs and rotates OpenRe's key, and the slot's
+ * twice), the Go Live UI shows OpenRestream's ingest URLs and rotates OpenRestream's key, and the slot's
* sessions reach `streams` through the mirror (mirror.js). RTMP, WebRTC (WHIP) and JSMPEG move
* sessions reach `streams` through the mirror (mirror.js). RTMP, WebRTC (WHIP) and JSMPEG move
- * (OPENRE_PROTOCOLS): OpenRe serves WHIP on ingest.openre.stream and runs the JSMPEG worker.
+ * (OPENRE_PROTOCOLS): OpenRestream serves WHIP on ingest.openre.stream and runs the JSMPEG worker.
*
*
* Switch off = zero behaviour change: every helper here returns what Live did before unless the
* Switch off = zero behaviour change: every helper here returns what Live did before unless the
- * slot says 'openre' AND OpenRe is configured (openre-client enabled()).
+ * slot says 'openre' AND OpenRestream is configured (openre-client enabled()).
*/
*/
const crypto = require('crypto');
const crypto = require('crypto');
const db = require('../db/database');
const db = require('../db/database');
@@ -17,7 +17,7 @@ const client = require('./openre-client');
@@ -17,7 +17,7 @@ const client = require('./openre-client');
const OPENRE_PROTOCOLS = new Set(['rtmp', 'webrtc', 'jsmpeg']);
const OPENRE_PROTOCOLS = new Set(['rtmp', 'webrtc', 'jsmpeg']);
-/** The OpenRe definition protocol a slot publishes with (OpenRe's own migrate-live mapping). */
+/** The OpenRestream definition protocol a slot publishes with (OpenRestream's own migrate-live mapping). */
function openreProtocolOf(slot) {
function openreProtocolOf(slot) {
const p = String((slot && slot.protocol) || '').toLowerCase();
const p = String((slot && slot.protocol) || '').toLowerCase();
if (p === 'webrtc' || p === 'whip') return 'webrtc';
if (p === 'webrtc' || p === 'whip') return 'webrtc';
@@ -34,7 +34,7 @@ async function slotById(id) {
@@ -34,7 +34,7 @@ async function slotById(id) {
return id ? await db.getManagedStreamById(id) : null;
return id ? await db.getManagedStreamById(id) : null;
}
}
-/** authorityOf(slotById(id)) === 'openre', without reading the slot when OpenRe is not configured. */
+/** authorityOf(slotById(id)) === 'openre', without reading the slot when OpenRestream is not configured. */
async function slotIsOpenre(id) {
async function slotIsOpenre(id) {
if (!id || !client.enabled()) return false;
if (!id || !client.enabled()) return false;
return authorityOf(await slotById(id)) === 'openre';
return authorityOf(await slotById(id)) === 'openre';
@@ -42,7 +42,7 @@ async function slotIsOpenre(id) {
@@ -42,7 +42,7 @@ async function slotIsOpenre(id) {
/**
/**
* Live's publish handlers (RTMP, WHIP, browser broadcaster, JSMPEG) ask this before accepting a key.
* Live's publish handlers (RTMP, WHIP, browser broadcaster, JSMPEG) ask this before accepting a key.
- * A slot key is refused when its slot is on OpenRe; a personal key (users.stream_key, no slot) is
+ * A slot key is refused when its slot is on OpenRestream; a personal key (users.stream_key, no slot) is
* refused when any of the user's slots is, because Live would attach it to the user's live rows and
* refused when any of the user's slots is, because Live would attach it to the user's live rows and
* push the user's destinations twice.
* push the user's destinations twice.
*/
*/
@@ -68,7 +68,7 @@ function serializeSlot(slot) {
@@ -68,7 +68,7 @@ function serializeSlot(slot) {
return { ...slot, stream_key: null, ingest_authority: 'openre', stream_key_managed_by: 'openre', openre_manage_url: client.manageUrl(slot.openre_stream_id) };
return { ...slot, stream_key: null, ingest_authority: 'openre', stream_key_managed_by: 'openre', openre_manage_url: client.manageUrl(slot.openre_stream_id) };
}
}
-/** OpenRe's ingest URLs from a stream or rotation answer (`ingest.rtmp.url`, `ingest.webrtc.whip_url`,
+/** OpenRestream's ingest URLs from a stream or rotation answer (`ingest.rtmp.url`, `ingest.webrtc.whip_url`,
* `ingest.jsmpeg.url`; each only for the protocols the definition allows). The key goes after each:
* `ingest.jsmpeg.url`; each only for the protocols the definition allows). The key goes after each:
* rtmp://…/live + key, <whip_url>/<key>, <jsmpeg_url>/<key>/<width>/<height>/. */
* rtmp://…/live + key, <whip_url>/<key>, <jsmpeg_url>/<key>/<width>/<height>/. */
function ingestUrls(ingest) {
function ingestUrls(ingest) {
@@ -81,7 +81,7 @@ function ingestUrls(ingest) {
@@ -81,7 +81,7 @@ function ingestUrls(ingest) {
}
}
/**
/**
- * What the Go Live UI shows for an OpenRe slot: OpenRe's ingest URLs and the hint of its current key
+ * What the Go Live UI shows for an OpenRestream slot: OpenRestream's ingest URLs and the hint of its current key
* (one key for every protocol; the key itself is only ever shown once, by a rotation).
* (one key for every protocol; the key itself is only ever shown once, by a rotation).
*/
*/
async function ingestFor(slot, subject) {
async function ingestFor(slot, subject) {
@@ -95,15 +95,15 @@ async function ingestFor(slot, subject) {
@@ -95,15 +95,15 @@ async function ingestFor(slot, subject) {
...ingestUrls(ingest),
...ingestUrls(ingest),
stream_key_hint: keyHint
stream_key_hint: keyHint
? `Shown once: press Regenerate for a new key (current key ends in …${keyHint})`
? `Shown once: press Regenerate for a new key (current key ends in …${keyHint})`
- : 'Press Regenerate to get your OpenRe stream key',
+ : 'Press Regenerate to get your OpenRestream stream key',
openre_manage_url: client.manageUrl(stream ? stream.id : slot.openre_stream_id),
openre_manage_url: client.manageUrl(stream ? stream.id : slot.openre_stream_id),
};
};
}
}
-/** Regenerate on an OpenRe slot: OpenRe rotates, the new key is returned once. */
+/** Regenerate on an OpenRestream slot: OpenRestream rotates, the new key is returned once. */
async function rotateFor(slot, subject) {
async function rotateFor(slot, subject) {
const streamId = slot.openre_stream_id || (await client.streamForSlot(slot.id) || {}).id;
const streamId = slot.openre_stream_id || (await client.streamForSlot(slot.id) || {}).id;
- if (!streamId) throw new client.OpenReError('this slot has no OpenRe stream yet', 409);
+ if (!streamId) throw new client.OpenReError('this slot has no OpenRestream stream yet', 409);
const r = await client.rotateKey(streamId, { subject });
const r = await client.rotateKey(streamId, { subject });
return { stream_key: r.key.key, stream_key_managed_by: 'openre', ...ingestUrls(r.ingest) };
return { stream_key: r.key.key, stream_key_managed_by: 'openre', ...ingestUrls(r.ingest) };
}
}
@@ -116,8 +116,8 @@ async function recordingModeFor(slot) {
@@ -116,8 +116,8 @@ async function recordingModeFor(slot) {
/**
/**
* The admin switch (PUT /api/admin/openre/managed/:id/ingest-authority).
* The admin switch (PUT /api/admin/openre/managed/:id/ingest-authority).
- * 'openre': needs OpenRe configured, the slot offline on Live, the owner's canonical subject;
- * finds or creates the OpenRe definition (allowing the slot's protocol on it), then
+ * 'openre': needs OpenRestream configured, the slot offline on Live, the owner's canonical subject;
+ * finds or creates the OpenRestream definition (allowing the slot's protocol on it), then
* (one transaction) flips the slot and
* (one transaction) flips the slot and
* rotates Live's own key for it, so no key that existed before works anywhere.
* rotates Live's own key for it, so no key that existed before works anywhere.
* 'live': flips back (rollback). Live's key was rotated at the switch: the broadcaster
* 'live': flips back (rollback). Live's key was rotated at the switch: the broadcaster
@@ -129,18 +129,18 @@ async function setAuthority(slotId, authority, { force = false } = {}) {
@@ -129,18 +129,18 @@ async function setAuthority(slotId, authority, { force = false } = {}) {
if (!['live', 'openre'].includes(authority)) return { status: 400, error: "authority must be 'live' or 'openre'" };
if (!['live', 'openre'].includes(authority)) return { status: 400, error: "authority must be 'live' or 'openre'" };
if (authority === 'live') {
if (authority === 'live') {
await db.run("UPDATE managed_streams SET ingest_authority = 'live', updated_at = ov_now() WHERE id = ?", [slot.id]);
await db.run("UPDATE managed_streams SET ingest_authority = 'live', updated_at = ov_now() WHERE id = ?", [slot.id]);
- return { status: 200, body: { managed_stream_id: slot.id, ingest_authority: 'live', next: 'The broadcaster regenerates the stream key on the Go Live page (the Live key was rotated when the slot moved to OpenRe).' } };
+ return { status: 200, body: { managed_stream_id: slot.id, ingest_authority: 'live', next: 'The broadcaster regenerates the stream key on the Go Live page (the Live key was rotated when the slot moved to OpenRestream).' } };
}
}
- if (!client.enabled()) return { status: 409, error: 'OpenRe is not configured on this Live (OPENRE_URL, OV_OAUTH_CLIENT_SECRET)' };
+ if (!client.enabled()) return { status: 409, error: 'OpenRestream is not configured on this Live (OPENRE_URL, OV_OAUTH_CLIENT_SECRET)' };
if (slot.ingest_authority === 'openre') return { status: 200, body: { managed_stream_id: slot.id, ingest_authority: 'openre', openre_stream_id: slot.openre_stream_id, unchanged: true } };
if (slot.ingest_authority === 'openre') return { status: 200, body: { managed_stream_id: slot.id, ingest_authority: 'openre', openre_stream_id: slot.openre_stream_id, unchanged: true } };
const live = await db.get('SELECT id FROM streams WHERE managed_stream_id = ? AND is_live = 1 LIMIT 1', [slot.id]);
const live = await db.get('SELECT id FROM streams WHERE managed_stream_id = ? AND is_live = 1 LIMIT 1', [slot.id]);
if (live) return { status: 409, error: `Slot ${slot.id} is live on Live (stream ${live.id}); switch it in a maintenance window, while it is offline` };
if (live) return { status: 409, error: `Slot ${slot.id} is live on Live (stream ${live.id}); switch it in a maintenance window, while it is offline` };
const protocol = openreProtocolOf(slot);
const protocol = openreProtocolOf(slot);
const method = String(slot.streaming_method || '').toLowerCase();
const method = String(slot.streaming_method || '').toLowerCase();
- // WHIP encoders take OpenRe's WHIP URL and key; the Go Live page's in-browser broadcaster only
+ // WHIP encoders take OpenRestream's WHIP URL and key; the Go Live page's in-browser broadcaster only
// publishes to Live's own SFU, so a browser slot moved now could not go live from the page.
// publishes to Live's own SFU, so a browser slot moved now could not go live from the page.
if (!force && protocol === 'webrtc' && !['whip', 'obs'].includes(method)) {
if (!force && protocol === 'webrtc' && !['whip', 'obs'].includes(method)) {
- return { status: 409, error: `Slot ${slot.id} is a ${slot.protocol}${method ? `/${method}` : ''} slot; the Go Live page cannot publish to OpenRe yet, only WHIP encoders can (pass force to switch anyway)` };
+ return { status: 409, error: `Slot ${slot.id} is a ${slot.protocol}${method ? `/${method}` : ''} slot; the Go Live page cannot publish to OpenRestream yet, only WHIP encoders can (pass force to switch anyway)` };
}
}
const subject = await require('../auth/identity-sync').subjectOf(slot.user_id);
const subject = await require('../auth/identity-sync').subjectOf(slot.user_id);
if (!subject) return { status: 409, error: 'The owner has no canonical subject yet (they need to sign in to Live once)' };
if (!subject) return { status: 409, error: 'The owner has no canonical subject yet (they need to sign in to Live once)' };
@@ -150,12 +150,12 @@ async function setAuthority(slotId, authority, { force = false } = {}) {
@@ -150,12 +150,12 @@ async function setAuthority(slotId, authority, { force = false } = {}) {
try { visibility = await db.resolveStreamVodVisibility({ user_id: slot.user_id, managed_stream_id: slot.id }); } catch { /* public */ }
try { visibility = await db.resolveStreamVodVisibility({ user_id: slot.user_id, managed_stream_id: slot.id }); } catch { /* public */ }
stream = await client.createStreamForSlot(slot, { subject, protocols: [protocol], recordingMode: await recordingModeFor(slot), recordingVisibility: visibility });
stream = await client.createStreamForSlot(slot, { subject, protocols: [protocol], recordingMode: await recordingModeFor(slot), recordingVisibility: visibility });
} else if (!(stream.protocols || ['rtmp']).includes(protocol)) {
} else if (!(stream.protocols || ['rtmp']).includes(protocol)) {
- // An existing definition (OpenRe's own default is RTMP only) would refuse the slot's encoder.
+ // An existing definition (OpenRestream's own default is RTMP only) would refuse the slot's encoder.
stream = await client.updateStream(stream.id, { protocols: [...(stream.protocols || ['rtmp']), protocol] }, { subject });
stream = await client.updateStream(stream.id, { protocols: [...(stream.protocols || ['rtmp']), protocol] }, { subject });
}
}
const newLiveKey = crypto.randomBytes(20).toString('hex');
const newLiveKey = crypto.randomBytes(20).toString('hex');
const flipped = await db.getDb().tx(async () => {
const flipped = await db.getDb().tx(async () => {
- // Checked again here: a Live publish may have started while OpenRe was being asked.
+ // Checked again here: a Live publish may have started while OpenRestream was being asked.
if (await db.get('SELECT id FROM streams WHERE managed_stream_id = ? AND is_live = 1 LIMIT 1', [slot.id])) return false;
if (await db.get('SELECT id FROM streams WHERE managed_stream_id = ? AND is_live = 1 LIMIT 1', [slot.id])) return false;
await db.run("UPDATE managed_streams SET ingest_authority = 'openre', openre_stream_id = ?, stream_key = ?, updated_at = ov_now() WHERE id = ?", [stream.id, newLiveKey, slot.id]);
await db.run("UPDATE managed_streams SET ingest_authority = 'openre', openre_stream_id = ?, stream_key = ?, updated_at = ov_now() WHERE id = ?", [stream.id, newLiveKey, slot.id]);
return true;
return true;
@@ -169,7 +169,7 @@ async function setAuthority(slotId, authority, { force = false } = {}) {
@@ -169,7 +169,7 @@ async function setAuthority(slotId, authority, { force = false } = {}) {
openre_stream_id: stream.id,
openre_stream_id: stream.id,
...ingestUrls(stream.ingest),
...ingestUrls(stream.ingest),
live_key_rotated: true,
live_key_rotated: true,
- next: 'The broadcaster presses Regenerate on the Go Live page (OpenRe issues the key) and pastes the new server and key into OBS.',
+ next: 'The broadcaster presses Regenerate on the Go Live page (OpenRestream issues the key) and pastes the new server and key into OBS.',
},
},
};
};
}
}
@@ -1,18 +1,18 @@
@@ -1,18 +1,18 @@
'use strict';
'use strict';
/**
/**
- * OpenRe sessions → Live's `streams` table (roadmap Wave 7, ADR-009: Live observes sessions through
- * OpenRe events and APIs). Watch pages, discovery, go-live notifications and analytics keep working
- * because an OpenRe-ingested session becomes an ordinary live `streams` row.
+ * OpenRestream sessions → Live's `streams` table (roadmap Wave 7, ADR-009: Live observes sessions through
+ * OpenRestream events and APIs). Watch pages, discovery, go-live notifications and analytics keep working
+ * because an OpenRestream-ingested session becomes an ordinary live `streams` row.
*
*
* POST /internal/openre-events the endpoint of Live's OpenVibe.Events subscription (topic
* POST /internal/openre-events the endpoint of Live's OpenVibe.Events subscription (topic
* openre.session.*), signed with OPENRE_EVENTS_SECRET. Each event
* openre.session.*), signed with OPENRE_EVENTS_SECRET. Each event
* is applied exactly once (openvibe-sdk inbox, consumer
* is applied exactly once (openvibe-sdk inbox, consumer
* 'live-openre-mirror'), in revision order per session.
* 'live-openre-mirror'), in revision order per session.
- * reconcile (every 30 s) for mirrored live sessions only: asks OpenRe for the session,
- * keeps the row's heartbeat fresh, ends it if OpenRe says it ended
+ * reconcile (every 30 s) for mirrored live sessions only: asks OpenRestream for the session,
+ * keeps the row's heartbeat fresh, ends it if OpenRestream says it ended
* (covers a missed event). Does nothing when no row is mirrored.
* (covers a missed event). Does nothing when no row is mirrored.
*
*
- * Consent: a session is mirrored only when its slot is switched to OpenRe on Live AND the OpenRe
+ * Consent: a session is mirrored only when its slot is switched to OpenRestream on Live AND the OpenRestream
* definition says mirror_to_live (plan §15.10 "explicit visibility and consent").
* definition says mirror_to_live (plan §15.10 "explicit visibility and consent").
*/
*/
const db = require('../db/database');
const db = require('../db/database');
@@ -21,7 +21,7 @@ const { authorityOf } = require('./authority');
@@ -21,7 +21,7 @@ const { authorityOf } = require('./authority');
const CONSUMER = 'live-openre-mirror';
const CONSUMER = 'live-openre-mirror';
const RECONCILE_MS = 30000;
const RECONCILE_MS = 30000;
-// A mirrored session OpenRe has not confirmed for this long is left to Live's stale cleanup.
+// A mirrored session OpenRestream has not confirmed for this long is left to Live's stale cleanup.
const CONFIRM_WINDOW_MIN = 30;
const CONFIRM_WINDOW_MIN = 30;
let inbox = null;
let inbox = null;
let timer = null;
let timer = null;
@@ -100,11 +100,11 @@ async function apply(event) {
@@ -100,11 +100,11 @@ async function apply(event) {
const channel = await db.getChannelByUserId(user.id);
const channel = await db.getChannelByUserId(user.id);
const configId = slot.control_config_id || (channel && channel.active_control_config_id);
const configId = slot.control_config_id || (channel && channel.active_control_config_id);
if (configId) await db.applyConfigToStream(configId, streamId);
if (configId) await db.applyConfigToStream(configId, streamId);
- } catch (e) { console.warn('[OpenRe] control config for mirrored stream failed:', e.message); }
+ } catch (e) { console.warn('[OpenRestream] control config for mirrored stream failed:', e.message); }
const stream = await db.getStreamById(streamId) || { id: streamId };
const stream = await db.getStreamById(streamId) || { id: streamId };
- try { await require('../streaming/golive-notify').notifyFollowersGoLive(user, stream); } catch (e) { console.warn('[OpenRe] go-live notify failed:', e.message); }
+ try { await require('../streaming/golive-notify').notifyFollowersGoLive(user, stream); } catch (e) { console.warn('[OpenRestream] go-live notify failed:', e.message); }
try { require('../streaming/live-events').announceGoLive(stream, user); } catch { /* */ }
try { require('../streaming/live-events').announceGoLive(stream, user); } catch { /* */ }
- console.log(`[OpenRe] session ${sessionId} mirrored into stream ${streamId} (${user.username}, slot ${slot.id})`);
+ console.log(`[OpenRestream] session ${sessionId} mirrored into stream ${streamId} (${user.username}, slot ${slot.id})`);
},
},
};
};
}
}
@@ -128,7 +128,7 @@ async function apply(event) {
@@ -128,7 +128,7 @@ async function apply(event) {
try { require('../integrations/robotstreamer-service').stopForStream(streamId); } catch { /* */ }
try { require('../integrations/robotstreamer-service').stopForStream(streamId); } catch { /* */ }
try { require('../integrations/chat-relay-service').stopForStream(streamId); } catch { /* */ }
try { require('../integrations/chat-relay-service').stopForStream(streamId); } catch { /* */ }
try { require('../integrations/ai-chatbot-service').stopForStream(streamId); } catch { /* */ }
try { require('../integrations/ai-chatbot-service').stopForStream(streamId); } catch { /* */ }
- console.log(`[OpenRe] session ${sessionId} ${state}: stream ${streamId} ended`);
+ console.log(`[OpenRestream] session ${sessionId} ${state}: stream ${streamId} ended`);
},
},
};
};
}
}
@@ -151,20 +151,20 @@ async function webhookHandler(req, res) {
@@ -151,20 +151,20 @@ async function webhookHandler(req, res) {
after = r.after || null;
after = r.after || null;
return r.outcome;
return r.outcome;
});
});
- if (!result.duplicate && after) { try { await after(); } catch (e) { console.warn('[OpenRe] post-commit step failed:', e.message); } }
+ if (!result.duplicate && after) { try { await after(); } catch (e) { console.warn('[OpenRestream] post-commit step failed:', e.message); } }
} catch (err) {
} catch (err) {
- console.error('[OpenRe] event apply failed:', err.message);
+ console.error('[OpenRestream] event apply failed:', err.message);
return res.status(500).json({ error: 'apply failed' }); // Events retries
return res.status(500).json({ error: 'apply failed' }); // Events retries
}
}
return res.status(204).end();
return res.status(204).end();
}
}
-/** Stream id → the mirrored OpenRe session (or null). */
+/** Stream id → the mirrored OpenRestream session (or null). */
async function sessionForStream(streamId) {
async function sessionForStream(streamId) {
try { return await db.get('SELECT * FROM openre_sessions WHERE stream_id = ? ORDER BY updated_at DESC LIMIT 1', [streamId]) || null; } catch { return null; }
try { return await db.get('SELECT * FROM openre_sessions WHERE stream_id = ? ORDER BY updated_at DESC LIMIT 1', [streamId]) || null; } catch { return null; }
}
}
-/** Does OpenRe hold a live ingest for this stream (confirmed recently)? Used by Live's stale cleanup. */
+/** Does OpenRestream hold a live ingest for this stream (confirmed recently)? Used by Live's stale cleanup. */
async function hasLiveSession(streamId) {
async function hasLiveSession(streamId) {
try {
try {
return Boolean(await db.get(`SELECT 1 FROM openre_sessions WHERE stream_id = ? AND state = 'live'
return Boolean(await db.get(`SELECT 1 FROM openre_sessions WHERE stream_id = ? AND state = 'live'
@@ -172,7 +172,7 @@ async function hasLiveSession(streamId) {
@@ -172,7 +172,7 @@ async function hasLiveSession(streamId) {
} catch { return false; }
} catch { return false; }
}
}
-/** Does OpenRe own this stream at all (so Live must not start its own restream/recording for it)? */
+/** Does OpenRestream own this stream at all (so Live must not start its own restream/recording for it)? */
async function ownsStream(streamId) {
async function ownsStream(streamId) {
return Boolean(await sessionForStream(streamId));
return Boolean(await sessionForStream(streamId));
}
}
@@ -183,16 +183,16 @@ async function reconcileOnce() {
@@ -183,16 +183,16 @@ async function reconcileOnce() {
if (!rows.length || !client.enabled()) return 0;
if (!rows.length || !client.enabled()) return 0;
let n = 0;
let n = 0;
for (const r of rows) {
for (const r of rows) {
- // Live ended the row itself (End Stream, or stale cleanup while OpenRe was unreachable):
+ // Live ended the row itself (End Stream, or stale cleanup while OpenRestream was unreachable):
// stop tracking it rather than keeping a live mirror of an offline stream.
// stop tracking it rather than keeping a live mirror of an offline stream.
const row = r.stream_id ? await db.getStreamById(r.stream_id) : null;
const row = r.stream_id ? await db.getStreamById(r.stream_id) : null;
if (row && !row.is_live) { await db.run("UPDATE openre_sessions SET state = 'detached', updated_at = ov_now() WHERE session_id = ?", [r.session_id]); continue; }
if (row && !row.is_live) { await db.run("UPDATE openre_sessions SET state = 'detached', updated_at = ov_now() WHERE session_id = ?", [r.session_id]); continue; }
let s;
let s;
try { s = await client.getSession(r.session_id); } catch (err) {
try { s = await client.getSession(r.session_id); } catch (err) {
if (err.status === 404) s = null;
if (err.status === 404) s = null;
- else continue; // OpenRe unreachable: keep the last known state (bounded by CONFIRM_WINDOW_MIN)
+ else continue; // OpenRestream unreachable: keep the last known state (bounded by CONFIRM_WINDOW_MIN)
}
}
- // A session OpenRe no longer knows (the SDK answers null for a 404; older clients threw) has failed: without this
+ // A session OpenRestream no longer knows (the SDK answers null for a 404; older clients threw) has failed: without this
// the row stayed 'live', ownsStream() stayed true and Live never took its own restream/recording back.
// the row stayed 'live', ownsStream() stayed true and Live never took its own restream/recording back.
if (!s) s = { state: 'failed', revision: r.revision + 1, ended_at: null };
if (!s) s = { state: 'failed', revision: r.revision + 1, ended_at: null };
if (s.state === 'live' || s.state === 'starting') {
if (s.state === 'live' || s.state === 'starting') {
@@ -210,7 +210,7 @@ async function reconcileOnce() {
@@ -210,7 +210,7 @@ async function reconcileOnce() {
function start() {
function start() {
if (timer || !client.enabled()) return;
if (timer || !client.enabled()) return;
- timer = setInterval(() => { reconcileOnce().catch(err => console.warn('[OpenRe] reconcile failed:', err.message)); }, RECONCILE_MS);
+ timer = setInterval(() => { reconcileOnce().catch(err => console.warn('[OpenRestream] reconcile failed:', err.message)); }, RECONCILE_MS);
if (timer.unref) timer.unref();
if (timer.unref) timer.unref();
}
}
@@ -1,7 +1,7 @@
@@ -1,7 +1,7 @@
'use strict';
'use strict';
/**
/**
- * OpenRe.Stream client (roadmap Wave 7). Live asks OpenRe for stream definitions, ingest URLs,
- * key rotation, sessions and playback descriptors; it never touches an OpenRe worker.
+ * OpenRestream client (roadmap Wave 7). Live asks OpenRestream for stream definitions, ingest URLs,
+ * key rotation, sessions and playback descriptors; it never touches an OpenRestream worker.
*
*
* Auth: Live's Network service principal (OV_OAUTH_CLIENT_ID / OV_OAUTH_CLIENT_SECRET),
* Auth: Live's Network service principal (OV_OAUTH_CLIENT_ID / OV_OAUTH_CLIENT_SECRET),
* client-credentials tokens for audience openvibe.openre. Grants Live needs on openre:
* client-credentials tokens for audience openvibe.openre. Grants Live needs on openre:
@@ -9,7 +9,7 @@
@@ -9,7 +9,7 @@
* Calls made for a streamer carry X-OV-Subject: <their usr_ subject>.
* Calls made for a streamer carry X-OV-Subject: <their usr_ subject>.
*
*
* Env:
* Env:
- * OPENRE_URL OpenRe API, host-local (http://127.0.0.1:4500). Unset = integration off:
+ * OPENRE_URL OpenRestream API, host-local (http://127.0.0.1:4500). Unset = integration off:
* every slot behaves exactly as before, whatever its ingest_authority.
* every slot behaves exactly as before, whatever its ingest_authority.
* OPENRE_PUBLIC_URL for links to the standalone UI (default https://openre.stream)
* OPENRE_PUBLIC_URL for links to the standalone UI (default https://openre.stream)
* OPENRE_EVENTS_SECRET the signing secret of Live's OpenVibe.Events subscription to openre.*
* OPENRE_EVENTS_SECRET the signing secret of Live's OpenVibe.Events subscription to openre.*
@@ -18,7 +18,7 @@ const { createServiceTokenClient } = require('openvibe-sdk/auth');
@@ -18,7 +18,7 @@ const { createServiceTokenClient } = require('openvibe-sdk/auth');
const { createClient, OpenVibeError } = require('openvibe-sdk/core');
const { createClient, OpenVibeError } = require('openvibe-sdk/core');
const { createOpenReClient } = require('openvibe-sdk/openre');
const { createOpenReClient } = require('openvibe-sdk/openre');
-// The calls themselves are openvibe-sdk/openre (SDK 0.16.0), shared with every product that uses OpenRe; this
+// The calls themselves are openvibe-sdk/openre (SDK 0.16.0), shared with every product that uses OpenRestream; this
// module keeps Live's configuration (env), its on/off switch and the names the rest of Live calls.
// module keeps Live's configuration (env), its on/off switch and the names the rest of Live calls.
const TIMEOUT_MS = 5000;
const TIMEOUT_MS = 5000;
let sdk = null;
let sdk = null;
@@ -33,7 +33,7 @@ function settings() {
@@ -33,7 +33,7 @@ function settings() {
};
};
}
}
-/** True when Live is configured to talk to OpenRe at all. */
+/** True when Live is configured to talk to OpenRestream at all. */
function enabled() {
function enabled() {
const s = settings();
const s = settings();
return Boolean(s.url && s.clientSecret);
return Boolean(s.url && s.clientSecret);
@@ -45,7 +45,7 @@ class OpenReError extends Error {
@@ -45,7 +45,7 @@ class OpenReError extends Error {
/** The SDK client for the current settings (rebuilt when they change: tests and rollbacks flip OPENRE_URL). */
/** The SDK client for the current settings (rebuilt when they change: tests and rollbacks flip OPENRE_URL). */
function openre() {
function openre() {
- if (!enabled()) throw new OpenReError('OpenRe integration is not configured (OPENRE_URL, OV_OAUTH_CLIENT_SECRET)', 0);
+ if (!enabled()) throw new OpenReError('OpenRestream integration is not configured (OPENRE_URL, OV_OAUTH_CLIENT_SECRET)', 0);
const s = settings();
const s = settings();
const key = `${s.url}|${s.publicUrl}|${s.clientId}@${s.networkInternalUrl}`;
const key = `${s.url}|${s.publicUrl}|${s.clientId}@${s.networkInternalUrl}`;
if (!sdk || sdk.key !== key) {
if (!sdk || sdk.key !== key) {
@@ -62,9 +62,9 @@ async function wrap(what, fn) {
@@ -62,9 +62,9 @@ async function wrap(what, fn) {
if (err instanceof OpenReError) throw err;
if (err instanceof OpenReError) throw err;
if (err instanceof OpenVibeError || (err && err.name === 'OpenVibeError')) {
if (err instanceof OpenVibeError || (err && err.name === 'OpenVibeError')) {
const body = err.problem || (err.code ? { code: err.code, detail: err.detail || err.message } : null);
const body = err.problem || (err.code ? { code: err.code, detail: err.detail || err.message } : null);
- throw new OpenReError(err.status ? (err.detail || err.message) : `OpenRe unreachable (${what}): ${err.message}`, err.status || 0, body);
+ throw new OpenReError(err.status ? (err.detail || err.message) : `OpenRestream unreachable (${what}): ${err.message}`, err.status || 0, body);
}
}
- throw new OpenReError(`OpenRe unreachable (${what}): ${err.message}`, 0);
+ throw new OpenReError(`OpenRestream unreachable (${what}): ${err.message}`, 0);
}
}
}
}
@@ -75,7 +75,7 @@ async function request(method, path, { body, subject, timeoutMs = TIMEOUT_MS } =
@@ -75,7 +75,7 @@ async function request(method, path, { body, subject, timeoutMs = TIMEOUT_MS } =
headers: subject ? { 'X-OV-Subject': subject } : {} }));
headers: subject ? { 'X-OV-Subject': subject } : {} }));
}
}
-/** The OpenRe stream definition serving a Live slot, or null. */
+/** The OpenRestream stream definition serving a Live slot, or null. */
const streamForSlot = async (managedStreamId) => await wrap('stream lookup', () => openre().streams.byExternalRef(`live:managed_stream:${managedStreamId}`));
const streamForSlot = async (managedStreamId) => await wrap('stream lookup', () => openre().streams.byExternalRef(`live:managed_stream:${managedStreamId}`));
/** Create the definition for a slot, owned by the streamer's canonical subject. The key it
/** Create the definition for a slot, owned by the streamer's canonical subject. The key it
@@ -1,7 +1,7 @@
@@ -1,7 +1,7 @@
'use strict';
'use strict';
/**
/**
- * Admin routes for the OpenRe ingest switch (mounted at /api/admin/openre, admin only). The lead
- * flips a slot during a maintenance window agreed with the broadcaster (OpenRe.Stream README,
+ * Admin routes for the OpenRestream ingest switch (mounted at /api/admin/openre, admin only). The lead
+ * flips a slot during a maintenance window agreed with the broadcaster (OpenRestream README,
* "RTMP cutover runbook").
* "RTMP cutover runbook").
*
*
* GET /api/admin/openre/status configuration + switched slots
* GET /api/admin/openre/status configuration + switched slots
@@ -38,11 +38,11 @@ router.put('/managed/:id/ingest-authority', requireAdmin, async (req, res) => {
@@ -38,11 +38,11 @@ router.put('/managed/:id/ingest-authority', requireAdmin, async (req, res) => {
try {
try {
const r = await authority.setAuthority(id, String((req.body && req.body.authority) || ''), { force: Boolean(req.body && req.body.force) });
const r = await authority.setAuthority(id, String((req.body && req.body.authority) || ''), { force: Boolean(req.body && req.body.force) });
if (r.error) return res.status(r.status).json({ error: r.error });
if (r.error) return res.status(r.status).json({ error: r.error });
- console.log(`[OpenRe] admin ${req.user.username} set slot ${id} ingest authority to ${r.body.ingest_authority}`);
+ console.log(`[OpenRestream] admin ${req.user.username} set slot ${id} ingest authority to ${r.body.ingest_authority}`);
return res.status(r.status).json(r.body);
return res.status(r.status).json(r.body);
} catch (err) {
} catch (err) {
- console.error('[OpenRe] switch failed:', err.message);
- return res.status(err.status && err.status < 500 ? 409 : 502).json({ error: `OpenRe: ${err.message}` });
+ console.error('[OpenRestream] switch failed:', err.message);
+ return res.status(err.status && err.status < 500 ? 409 : 502).json({ error: `OpenRestream: ${err.message}` });
}
}
});
});
@@ -1,13 +1,13 @@
@@ -1,13 +1,13 @@
'use strict';
'use strict';
/**
/**
- * OpenRe ingest switch — Live-side schema (roadmap Wave 7, ADR-009).
+ * OpenRestream ingest switch — Live-side schema (roadmap Wave 7, ADR-009).
*
*
* The schema this module used to create at boot now lives in migrations/0002_live.sql, which owns every table
* The schema this module used to create at boot now lives in migrations/0002_live.sql, which owns every table
* and column (the production runtime role cannot run DDL):
* and column (the production runtime role cannot run DDL):
*
*
* managed_streams.ingest_authority 'live' (default) | 'openre' who ingests RTMP for this slot
* managed_streams.ingest_authority 'live' (default) | 'openre' who ingests RTMP for this slot
- * managed_streams.openre_stream_id the OpenRe stream definition serving the slot (std_…)
- * openre_sessions Live's projection of OpenRe ingest sessions it mirrors into
+ * managed_streams.openre_stream_id the OpenRestream stream definition serving the slot (std_…)
+ * openre_sessions Live's projection of OpenRestream ingest sessions it mirrors into
* `streams` (rebuilt from openre.session.* events; the
* `streams` (rebuilt from openre.session.* events; the
* revision guard makes out-of-order delivery harmless)
* revision guard makes out-of-order delivery harmless)
*
*
@@ -234,12 +234,12 @@ class BroadcastServer extends EventEmitter {
@@ -234,12 +234,12 @@ class BroadcastServer extends EventEmitter {
ws.close(4003, 'Not your stream');
ws.close(4003, 'Not your stream');
return;
return;
}
}
- // OpenRe.Stream ingests this slot (or, for a slot-less stream, one of the owner's slots):
+ // OpenRestream ingests this slot (or, for a slot-less stream, one of the owner's slots):
// Live's SFU never takes a second publisher for it. See server/openre/authority.js.
// Live's SFU never takes a second publisher for it. See server/openre/authority.js.
const openre = require('../openre/authority');
const openre = require('../openre/authority');
const managedStream = await openre.slotById(stream.managed_stream_id);
const managedStream = await openre.slotById(stream.managed_stream_id);
if (await openre.refusesLiveIngest({ managedStream, user: managedStream ? null : user, protocol: 'webrtc' })) {
if (await openre.refusesLiveIngest({ managedStream, user: managedStream ? null : user, protocol: 'webrtc' })) {
- console.warn(`[Broadcast] Broadcaster refused for stream ${streamId}: ${managedStream ? `slot ${managedStream.id}` : `personal stream of ${user.username}`} is ingested by OpenRe`);
+ console.warn(`[Broadcast] Broadcaster refused for stream ${streamId}: ${managedStream ? `slot ${managedStream.id}` : `personal stream of ${user.username}`} is ingested by OpenRestream`);
ws.close(4003, 'Stream key not recognized');
ws.close(4003, 'Stream key not recognized');
return;
return;
}
}
@@ -24,7 +24,7 @@ function matchesStreamPath(url, streamKey) {
@@ -24,7 +24,7 @@ function matchesStreamPath(url, streamKey) {
/**
/**
* Consult the per-protocol ingest authority for each publish: a slot (or personal key)
* Consult the per-protocol ingest authority for each publish: a slot (or personal key)
- * ingested by OpenRe publishes JSMPEG to OpenRe, never to Live's relay.
+ * ingested by OpenRestream publishes JSMPEG to OpenRestream, never to Live's relay.
*/
*/
async function refusedByOpenre(streamKey) {
async function refusedByOpenre(streamKey) {
try {
try {
@@ -33,7 +33,7 @@ async function refusedByOpenre(streamKey) {
@@ -33,7 +33,7 @@ async function refusedByOpenre(streamKey) {
const managedStream = user ? null : await db.getManagedStreamByStreamKey(streamKey);
const managedStream = user ? null : await db.getManagedStreamByStreamKey(streamKey);
if (!user && !managedStream) return false;
if (!user && !managedStream) return false;
if (!await require('../openre/authority').refusesLiveIngest({ managedStream, user, protocol: 'jsmpeg' })) return false;
if (!await require('../openre/authority').refusesLiveIngest({ managedStream, user, protocol: 'jsmpeg' })) return false;
- console.log(`[JSMPEG] Rejected: ${managedStream ? `slot ${managedStream.id}` : `personal key of ${user.username}`} is ingested by OpenRe`);
+ console.log(`[JSMPEG] Rejected: ${managedStream ? `slot ${managedStream.id}` : `personal key of ${user.username}`} is ingested by OpenRestream`);
return true;
return true;
} catch {
} catch {
return false;
return false;
@@ -16,7 +16,7 @@ const chatRelayService = require('../integrations/chat-relay-service');
@@ -16,7 +16,7 @@ const chatRelayService = require('../integrations/chat-relay-service');
const router = express.Router();
const router = express.Router();
/**
/**
- * A slot ingested by OpenRe.Stream (ingest_authority = 'openre') restreams from OpenRe: its
+ * A slot ingested by OpenRestream (ingest_authority = 'openre') restreams from OpenRestream: its
* destinations are managed there (openre.stream), so Live refuses to edit or run them here rather
* destinations are managed there (openre.stream), so Live refuses to edit or run them here rather
* than letting two systems push the same stream. Slots on Live's own ingest are unaffected.
* than letting two systems push the same stream. Slots on Live's own ingest are unaffected.
*/
*/
@@ -25,7 +25,7 @@ async function refuseIfOpenre(res, managedStreamId) {
@@ -25,7 +25,7 @@ async function refuseIfOpenre(res, managedStreamId) {
if (!await openre.slotIsOpenre(managedStreamId)) return false;
if (!await openre.slotIsOpenre(managedStreamId)) return false;
const slot = await openre.slotById(managedStreamId);
const slot = await openre.slotById(managedStreamId);
res.status(409).json({
res.status(409).json({
- error: 'Restreaming for this stream slot is managed on OpenRe.Stream',
+ error: 'Restreaming for this stream slot is managed on OpenRestream',
managed_by: 'openre',
managed_by: 'openre',
manage_url: require('../openre/openre-client').manageUrl(slot && slot.openre_stream_id),
manage_url: require('../openre/openre-client').manageUrl(slot && slot.openre_stream_id),
});
});
@@ -1609,13 +1609,13 @@ router.get('/managed/:managedStreamId/profile', requireAuth, async (req, res) =>
@@ -1609,13 +1609,13 @@ router.get('/managed/:managedStreamId/profile', requireAuth, async (req, res) =>
rtmp_url: rtmpUrl,
rtmp_url: rtmpUrl,
restream_destinations: restreamDestinations,
restream_destinations: restreamDestinations,
};
};
- // OpenRe ingests this slot: its RTMP/WHIP/JSMPEG servers and key come from OpenRe (the key
+ // OpenRestream ingests this slot: its RTMP/WHIP/JSMPEG servers and key come from OpenRestream (the key
// is only ever shown by Regenerate), and Live's WHIP base is withheld: Live refuses that
// is only ever shown by Regenerate), and Live's WHIP base is withheld: Live refuses that
// publish. Slots on Live's own ingest get exactly the response above.
// publish. Slots on Live's own ingest get exactly the response above.
if (openreAuthority.authorityOf(ms) === 'openre') {
if (openreAuthority.authorityOf(ms) === 'openre') {
const subject = await require('../auth/identity-sync').subjectOf(ms.user_id);
const subject = await require('../auth/identity-sync').subjectOf(ms.user_id);
Object.assign(body, { stream_key: null, whip_url_base: null, whip_url_source: null, whip_url_warning: null }, await openreAuthority.ingestFor(ms, subject).catch((err) => ({
Object.assign(body, { stream_key: null, whip_url_base: null, whip_url_source: null, whip_url_warning: null }, await openreAuthority.ingestFor(ms, subject).catch((err) => ({
- ingest_authority: 'openre', stream_key_managed_by: 'openre', rtmp_url: null, whip_url: null, jsmpeg_url: null, stream_key_hint: `OpenRe is unreachable (${err.message})`,
+ ingest_authority: 'openre', stream_key_managed_by: 'openre', rtmp_url: null, whip_url: null, jsmpeg_url: null, stream_key_hint: `OpenRestream is unreachable (${err.message})`,
})));
})));
}
}
res.json(body);
res.json(body);
@@ -1954,14 +1954,14 @@ router.post('/managed/:id/regenerate-key', requireAuth, async (req, res) => {
@@ -1954,14 +1954,14 @@ router.post('/managed/:id/regenerate-key', requireAuth, async (req, res) => {
return res.status(403).json({ error: 'Not your managed stream' });
return res.status(403).json({ error: 'Not your managed stream' });
}
}
- // OpenRe ingests this slot: OpenRe rotates its key (the old one stops working at once) and
+ // OpenRestream ingests this slot: OpenRestream rotates its key (the old one stops working at once) and
// the new one is shown here, once.
// the new one is shown here, once.
if (openreAuthority.authorityOf(ms) === 'openre') {
if (openreAuthority.authorityOf(ms) === 'openre') {
try {
try {
const subject = await require('../auth/identity-sync').subjectOf(ms.user_id);
const subject = await require('../auth/identity-sync').subjectOf(ms.user_id);
return res.json(await openreAuthority.rotateFor(ms, subject));
return res.json(await openreAuthority.rotateFor(ms, subject));
} catch (err) {
} catch (err) {
- return res.status(502).json({ error: `Could not rotate the key on OpenRe: ${err.message}` });
+ return res.status(502).json({ error: `Could not rotate the key on OpenRestream: ${err.message}` });
}
}
}
}
@@ -2283,7 +2283,7 @@ router.get('/:id/endpoint', requireAuth, async (req, res) => {
@@ -2283,7 +2283,7 @@ router.get('/:id/endpoint', requireAuth, async (req, res) => {
const hostname = config.host === '0.0.0.0' ? req.hostname : config.host;
const hostname = config.host === '0.0.0.0' ? req.hostname : config.host;
if (openreAuthority.OPENRE_PROTOCOLS.has(stream.protocol) && await openreAuthority.slotIsOpenre(stream.managed_stream_id)) {
if (openreAuthority.OPENRE_PROTOCOLS.has(stream.protocol) && await openreAuthority.slotIsOpenre(stream.managed_stream_id)) {
- // OpenRe ingests this slot: its server URL for the slot's protocol, the hint of its key
+ // OpenRestream ingests this slot: its server URL for the slot's protocol, the hint of its key
// (never the key). No Live relay channel, room or recorder is set up for it.
// (never the key). No Live relay channel, room or recorder is set up for it.
const slot = await openreAuthority.slotById(stream.managed_stream_id);
const slot = await openreAuthority.slotById(stream.managed_stream_id);
const ingest = await openreAuthority.ingestFor(slot, await require('../auth/identity-sync').subjectOf(slot.user_id)).catch(() => ({}));
const ingest = await openreAuthority.ingestFor(slot, await require('../auth/identity-sync').subjectOf(slot.user_id)).catch(() => ({}));
@@ -2293,7 +2293,7 @@ router.get('/:id/endpoint', requireAuth, async (req, res) => {
@@ -2293,7 +2293,7 @@ router.get('/:id/endpoint', requireAuth, async (req, res) => {
endpoint = {
endpoint = {
...serverUrl,
...serverUrl,
streamKey: null,
streamKey: null,
- streamKeyHint: ingest.stream_key_hint || 'Press Regenerate to get your OpenRe stream key',
+ streamKeyHint: ingest.stream_key_hint || 'Press Regenerate to get your OpenRestream stream key',
keyManagedBy: 'openre',
keyManagedBy: 'openre',
...(stream.protocol === 'rtmp' ? { flvUrl: `/api/streams/rtmp-proxy/${stream.id}.flv` } : {}),
...(stream.protocol === 'rtmp' ? { flvUrl: `/api/streams/rtmp-proxy/${stream.id}.flv` } : {}),
};
};
@@ -2537,8 +2537,8 @@ router.get('/rtmp-proxy/:streamId.flv', async (req, res) => {
@@ -2537,8 +2537,8 @@ router.get('/rtmp-proxy/:streamId.flv', async (req, res) => {
let url;
let url;
const mirrored = await openreMirror.sessionForStream(stream.id);
const mirrored = await openreMirror.sessionForStream(stream.id);
if (mirrored) {
if (mirrored) {
- // An OpenRe session: pull HTTP-FLV from the OpenRe worker that holds it, as named by
- // OpenRe's playback descriptor (a loopback URL on this host; no key in it).
+ // An OpenRestream session: pull HTTP-FLV from the OpenRestream worker that holds it, as named by
+ // OpenRestream's playback descriptor (a loopback URL on this host; no key in it).
if (mirrored.state !== 'live') return res.status(404).end();
if (mirrored.state !== 'live') return res.status(404).end();
const pb = await require('../openre/openre-client').playback(mirrored.session_id).catch(() => null);
const pb = await require('../openre/openre-client').playback(mirrored.session_id).catch(() => null);
if (!pb || !pb.flv || !/^http:\/\/127\.0\.0\.1:\d+\/live\/ses_[0-9A-Z]+\.flv$/.test(pb.flv.internal_url)) return res.status(502).end();
if (!pb || !pb.flv || !/^http:\/\/127\.0\.0\.1:\d+\/live\/ses_[0-9A-Z]+\.flv$/.test(pb.flv.internal_url)) return res.status(502).end();
@@ -2550,7 +2550,7 @@ router.get('/rtmp-proxy/:streamId.flv', async (req, res) => {
@@ -2550,7 +2550,7 @@ router.get('/rtmp-proxy/:streamId.flv', async (req, res) => {
url = `http://127.0.0.1:${nmsPort}/live/${flvKey}.flv`;
url = `http://127.0.0.1:${nmsPort}/live/${flvKey}.flv`;
}
}
- if (req.destroyed) return undefined; // the viewer left while OpenRe was asked
+ if (req.destroyed) return undefined; // the viewer left while OpenRestream was asked
const http = require('http');
const http = require('http');
const upstream = http.get(url, (nmsRes) => {
const upstream = http.get(url, (nmsRes) => {
if (nmsRes.statusCode !== 200) {
if (nmsRes.statusCode !== 200) {
@@ -121,11 +121,11 @@ class RTMPServer extends EventEmitter {
@@ -121,11 +121,11 @@ class RTMPServer extends EventEmitter {
return;
return;
}
}
- // OpenRe.Stream ingests this slot (managed_streams.ingest_authority = 'openre'): refuse
+ // OpenRestream ingests this slot (managed_streams.ingest_authority = 'openre'): refuse
// here so one stream can never be ingested twice. Slots on 'live' (the default) are
// here so one stream can never be ingested twice. Slots on 'live' (the default) are
// untouched. See server/openre/authority.js.
// untouched. See server/openre/authority.js.
if (await require('../openre/authority').refusesLiveIngest({ managedStream, user: managedStream ? null : resolvedUser, protocol: 'rtmp' })) {
if (await require('../openre/authority').refusesLiveIngest({ managedStream, user: managedStream ? null : resolvedUser, protocol: 'rtmp' })) {
- console.log(`[RTMP] Rejected: ${managedStream ? `slot ${managedStream.id}` : `personal key of ${resolvedUser.username}`} is ingested by OpenRe`);
+ console.log(`[RTMP] Rejected: ${managedStream ? `slot ${managedStream.id}` : `personal key of ${resolvedUser.username}`} is ingested by OpenRestream`);
const session = this.nms.getSession(id);
const session = this.nms.getSession(id);
if (session) session.reject();
if (session) session.reject();
return;
return;
@@ -121,13 +121,13 @@ function sendWhipError(res, status, code, message) {
@@ -121,13 +121,13 @@ function sendWhipError(res, status, code, message) {
/**
/**
* Consult the per-protocol ingest authority before accepting a publisher: a slot (or personal
* Consult the per-protocol ingest authority before accepting a publisher: a slot (or personal
- * key) ingested by OpenRe publishes WHIP to OpenRe, never to Live.
+ * key) ingested by OpenRestream publishes WHIP to OpenRestream, never to Live.
*/
*/
async function refusedByOpenre(slotId, userId) {
async function refusedByOpenre(slotId, userId) {
const managedStream = slotId ? await db.getManagedStreamById(slotId) : null;
const managedStream = slotId ? await db.getManagedStreamById(slotId) : null;
const user = managedStream || !userId ? null : await db.getUserById(userId);
const user = managedStream || !userId ? null : await db.getUserById(userId);
if (!await require('../openre/authority').refusesLiveIngest({ managedStream, user, protocol: 'webrtc' })) return false;
if (!await require('../openre/authority').refusesLiveIngest({ managedStream, user, protocol: 'webrtc' })) return false;
- console.log(`[WHIP] Rejected: ${managedStream ? `slot ${managedStream.id}` : `personal key of user ${userId}`} is ingested by OpenRe`);
+ console.log(`[WHIP] Rejected: ${managedStream ? `slot ${managedStream.id}` : `personal key of user ${userId}`} is ingested by OpenRestream`);
return true;
return true;
}
}
@@ -1,9 +1,9 @@
@@ -1,9 +1,9 @@
'use strict';
'use strict';
-// OpenRe ingest switch (roadmap Wave 7, ADR-009). With OPENRE_URL unset, or a slot on 'live' (the
+// OpenRestream ingest switch (roadmap Wave 7, ADR-009). With OPENRE_URL unset, or a slot on 'live' (the
// default), nothing changes. With a slot switched to 'openre': Live's RTMP, WHIP, browser
// default), nothing changes. With a slot switched to 'openre': Live's RTMP, WHIP, browser
-// broadcaster and JSMPEG ingest refuse its key, the Go Live helpers show OpenRe's URLs and rotate
-// OpenRe's key, and OpenRe session events (signed Events deliveries, applied once, in revision
+// broadcaster and JSMPEG ingest refuse its key, the Go Live helpers show OpenRestream's URLs and rotate
+// OpenRestream's key, and OpenRestream session events (signed Events deliveries, applied once, in revision
// order) become ordinary `streams` rows.
// order) become ordinary `streams` rows.
const assert = require('assert');
const assert = require('assert');
@@ -17,7 +17,7 @@ const SUBJECT = 'usr_01J0000000000000000000000Q';
@@ -17,7 +17,7 @@ const SUBJECT = 'usr_01J0000000000000000000000Q';
const SES = (n) => `ses_01J00000000000000000000${String(n).padStart(3, '0')}`.slice(0, 30);
const SES = (n) => `ses_01J00000000000000000000${String(n).padStart(3, '0')}`.slice(0, 30);
const openreCalls = [];
const openreCalls = [];
let openreSessions = {};
let openreSessions = {};
-/** OpenRe definitions by id: { id, ref, protocols }. */
+/** OpenRestream definitions by id: { id, ref, protocols }. */
const openreDefs = {};
const openreDefs = {};
const ingestOf = (protocols) => ({
const ingestOf = (protocols) => ({
...(protocols.includes('rtmp') ? { rtmp: { url: 'rtmp://ingest.openre.stream:1936/live', key_hint: 'WXYZ' } } : {}),
...(protocols.includes('rtmp') ? { rtmp: { url: 'rtmp://ingest.openre.stream:1936/live', key_hint: 'WXYZ' } } : {}),
@@ -122,7 +122,7 @@ function sessionEvent(type, sessionId, revision, payload = {}) {
@@ -122,7 +122,7 @@ function sessionEvent(type, sessionId, revision, payload = {}) {
assert.strictEqual(authority.serializeSlot(slot701), slot701, 'the same object: responses unchanged');
assert.strictEqual(authority.serializeSlot(slot701), slot701, 'the same object: responses unchanged');
assert.strictEqual(await authority.refusesLiveIngest({ managedStream: slot701 }), false);
assert.strictEqual(await authority.refusesLiveIngest({ managedStream: slot701 }), false);
assert.strictEqual(await authority.refusesLiveIngest({ managedStream: null, user: { id: 601 } }), false);
assert.strictEqual(await authority.refusesLiveIngest({ managedStream: null, user: { id: 601 } }), false);
- // Even a slot marked 'openre' behaves as before while OpenRe is not configured.
+ // Even a slot marked 'openre' behaves as before while OpenRestream is not configured.
await d.prepare("UPDATE managed_streams SET ingest_authority = 'openre' WHERE id = 701").run();
await d.prepare("UPDATE managed_streams SET ingest_authority = 'openre' WHERE id = 701").run();
const marked = await db.getManagedStreamById(701);
const marked = await db.getManagedStreamById(701);
assert.strictEqual(authority.authorityOf(marked), 'live');
assert.strictEqual(authority.authorityOf(marked), 'live');
@@ -155,7 +155,7 @@ function sessionEvent(type, sessionId, revision, payload = {}) {
@@ -155,7 +155,7 @@ function sessionEvent(type, sessionId, revision, payload = {}) {
assert.strictEqual((await d.prepare('SELECT COUNT(*) AS n FROM streams').get()).n, 0, 'a slot on live is never mirrored');
assert.strictEqual((await d.prepare('SELECT COUNT(*) AS n FROM streams').get()).n, 0, 'a slot on live is never mirrored');
// ── The switch ───────────────────────────────────────────
// ── The switch ───────────────────────────────────────────
- assert.match((await authority.setAuthority(702, 'openre')).error || '', /Go Live page cannot publish to OpenRe/, 'a browser slot needs force');
+ assert.match((await authority.setAuthority(702, 'openre')).error || '', /Go Live page cannot publish to OpenRestream/, 'a browser slot needs force');
assert.strictEqual((await authority.setAuthority(703, 'openre')).status, 409, 'no canonical subject');
assert.strictEqual((await authority.setAuthority(703, 'openre')).status, 409, 'no canonical subject');
const liveRow = await db.createStream({ user_id: 601, managed_stream_id: 701, title: 'on Live', protocol: 'rtmp' });
const liveRow = await db.createStream({ user_id: 601, managed_stream_id: 701, title: 'on Live', protocol: 'rtmp' });
assert.strictEqual((await authority.setAuthority(701, 'openre')).status, 409, 'refused while live on Live');
assert.strictEqual((await authority.setAuthority(701, 'openre')).status, 409, 'refused while live on Live');
@@ -173,15 +173,15 @@ function sessionEvent(type, sessionId, revision, payload = {}) {
@@ -173,15 +173,15 @@ function sessionEvent(type, sessionId, revision, payload = {}) {
assert.strictEqual(switched.ingest_authority, 'openre');
assert.strictEqual(switched.ingest_authority, 'openre');
assert.strictEqual(await db.getManagedStreamByStreamKey('livekey701aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'), undefined, "Live's old key is rotated away");
assert.strictEqual(await db.getManagedStreamByStreamKey('livekey701aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'), undefined, "Live's old key is rotated away");
assert.strictEqual(await authority.refusesLiveIngest({ managedStream: switched }), true, "Live's RTMP refuses the slot");
assert.strictEqual(await authority.refusesLiveIngest({ managedStream: switched }), true, "Live's RTMP refuses the slot");
- assert.strictEqual(await authority.refusesLiveIngest({ managedStream: switched, protocol: 'srt' }), false, 'only the protocols OpenRe carries are refused');
+ assert.strictEqual(await authority.refusesLiveIngest({ managedStream: switched, protocol: 'srt' }), false, 'only the protocols OpenRestream carries are refused');
assert.deepStrictEqual([...authority.OPENRE_PROTOCOLS], ['rtmp', 'webrtc', 'jsmpeg']);
assert.deepStrictEqual([...authority.OPENRE_PROTOCOLS], ['rtmp', 'webrtc', 'jsmpeg']);
for (const protocol of ['rtmp', 'webrtc', 'jsmpeg']) {
for (const protocol of ['rtmp', 'webrtc', 'jsmpeg']) {
assert.strictEqual(await authority.refusesLiveIngest({ managedStream: switched, protocol }), true, `${protocol}: the slot key is refused`);
assert.strictEqual(await authority.refusesLiveIngest({ managedStream: switched, protocol }), true, `${protocol}: the slot key is refused`);
assert.strictEqual(await authority.refusesLiveIngest({ managedStream: null, user: { id: 601 }, protocol }), true, `${protocol}: the owner's personal key is refused`);
assert.strictEqual(await authority.refusesLiveIngest({ managedStream: null, user: { id: 601 }, protocol }), true, `${protocol}: the owner's personal key is refused`);
assert.strictEqual(await authority.refusesLiveIngest({ managedStream: await db.getManagedStreamById(703), protocol }), false, `${protocol}: a live slot is admitted`);
assert.strictEqual(await authority.refusesLiveIngest({ managedStream: await db.getManagedStreamById(703), protocol }), false, `${protocol}: a live slot is admitted`);
- assert.strictEqual(await authority.refusesLiveIngest({ managedStream: null, user: { id: 602 }, protocol }), false, `${protocol}: a personal key without an OpenRe slot is admitted`);
+ assert.strictEqual(await authority.refusesLiveIngest({ managedStream: null, user: { id: 602 }, protocol }), false, `${protocol}: a personal key without an OpenRestream slot is admitted`);
}
}
- assert.strictEqual(await authority.refusesLiveIngest({ managedStream: null, user: { id: 602 } }), false, 'personal keys of users without an OpenRe slot stay on Live');
+ assert.strictEqual(await authority.refusesLiveIngest({ managedStream: null, user: { id: 602 } }), false, 'personal keys of users without an OpenRestream slot stay on Live');
assert.strictEqual(await authority.refusesLiveIngest({ managedStream: null, user: { id: 601 } }), true, 'a personal key cannot double-ingest a switched channel');
assert.strictEqual(await authority.refusesLiveIngest({ managedStream: null, user: { id: 601 } }), true, 'a personal key cannot double-ingest a switched channel');
const shown = authority.serializeSlot(switched);
const shown = authority.serializeSlot(switched);
assert.strictEqual(shown.stream_key, null);
assert.strictEqual(shown.stream_key, null);
@@ -196,7 +196,7 @@ function sessionEvent(type, sessionId, revision, payload = {}) {
@@ -196,7 +196,7 @@ function sessionEvent(type, sessionId, revision, payload = {}) {
assert.strictEqual(rotated.rtmp_url, 'rtmp://ingest.openre.stream:1936/live');
assert.strictEqual(rotated.rtmp_url, 'rtmp://ingest.openre.stream:1936/live');
assert.strictEqual(openreCalls.filter(c => c.url.endsWith('/keys/rotate')).pop().subject, SUBJECT);
assert.strictEqual(openreCalls.filter(c => c.url.endsWith('/keys/rotate')).pop().subject, SUBJECT);
- // A WHIP encoder slot: its definition is created for WebRTC, the Go Live helpers give OpenRe's WHIP URL.
+ // A WHIP encoder slot: its definition is created for WebRTC, the Go Live helpers give OpenRestream's WHIP URL.
const whipFlip = await authority.setAuthority(708, 'openre');
const whipFlip = await authority.setAuthority(708, 'openre');
assert.strictEqual(whipFlip.status, 200, JSON.stringify(whipFlip));
assert.strictEqual(whipFlip.status, 200, JSON.stringify(whipFlip));
assert.deepStrictEqual(openreCalls.filter(c => c.method === 'POST' && c.url === '/api/v1/streams').pop().body.protocols, ['webrtc']);
assert.deepStrictEqual(openreCalls.filter(c => c.method === 'POST' && c.url === '/api/v1/streams').pop().body.protocols, ['webrtc']);
@@ -205,7 +205,7 @@ function sessionEvent(type, sessionId, revision, payload = {}) {
@@ -205,7 +205,7 @@ function sessionEvent(type, sessionId, revision, payload = {}) {
assert.deepStrictEqual([whipIngest.rtmp_url, whipIngest.whip_url, whipIngest.jsmpeg_url], [null, 'https://ingest.openre.stream/whip', null]);
assert.deepStrictEqual([whipIngest.rtmp_url, whipIngest.whip_url, whipIngest.jsmpeg_url], [null, 'https://ingest.openre.stream/whip', null]);
assert.match(whipIngest.stream_key_hint, /…WXYZ/);
assert.match(whipIngest.stream_key_hint, /…WXYZ/);
assert.strictEqual((await authority.rotateFor(await db.getManagedStreamById(708), SUBJECT)).whip_url, 'https://ingest.openre.stream/whip');
assert.strictEqual((await authority.rotateFor(await db.getManagedStreamById(708), SUBJECT)).whip_url, 'https://ingest.openre.stream/whip');
- // A JSMPEG slot whose OpenRe definition already exists (RTMP only, OpenRe's default): JSMPEG is added to it.
+ // A JSMPEG slot whose OpenRestream definition already exists (RTMP only, OpenRestream's default): JSMPEG is added to it.
openreDefs.std_slot709 = { id: 'std_slot709', ref: 'live:managed_stream:709', protocols: ['rtmp'] };
openreDefs.std_slot709 = { id: 'std_slot709', ref: 'live:managed_stream:709', protocols: ['rtmp'] };
const jsmpegFlip = await authority.setAuthority(709, 'openre');
const jsmpegFlip = await authority.setAuthority(709, 'openre');
assert.strictEqual(jsmpegFlip.status, 200, JSON.stringify(jsmpegFlip));
assert.strictEqual(jsmpegFlip.status, 200, JSON.stringify(jsmpegFlip));
@@ -236,7 +236,7 @@ function sessionEvent(type, sessionId, revision, payload = {}) {
@@ -236,7 +236,7 @@ function sessionEvent(type, sessionId, revision, payload = {}) {
assert.strictEqual(await mirror.hasLiveSession(streamId), true, 'stale cleanup leaves it alone');
assert.strictEqual(await mirror.hasLiveSession(streamId), true, 'stale cleanup leaves it alone');
assert.strictEqual(await mirror.ownsStream(streamId), true, 'Live does not resume its own restreams for it');
assert.strictEqual(await mirror.ownsStream(streamId), true, 'Live does not resume its own restreams for it');
- // Reconcile: OpenRe says live → heartbeat refreshed; says ended → row ended.
+ // Reconcile: OpenRestream says live → heartbeat refreshed; says ended → row ended.
openreSessions[SES(4)] = { id: SES(4), state: 'live', revision: 2 };
openreSessions[SES(4)] = { id: SES(4), state: 'live', revision: 2 };
await d.prepare("UPDATE streams SET last_heartbeat = datetime('now', '-10 minutes') WHERE id = ?").run(streamId);
await d.prepare("UPDATE streams SET last_heartbeat = datetime('now', '-10 minutes') WHERE id = ?").run(streamId);
await mirror.reconcileOnce();
await mirror.reconcileOnce();
@@ -280,9 +280,9 @@ function sessionEvent(type, sessionId, revision, payload = {}) {
@@ -280,9 +280,9 @@ function sessionEvent(type, sessionId, revision, payload = {}) {
};
};
const openReWhipKey = whipKey();
const openReWhipKey = whipKey();
const liveWhipKey = whipKey();
const liveWhipKey = whipKey();
- await d.prepare("INSERT INTO managed_streams (id, user_id, title, protocol, stream_key, ingest_authority) OVERRIDING SYSTEM VALUE VALUES (704, 603, 'WHIP on OpenRe', 'webrtc', ?, 'openre')").run(openReWhipKey);
+ await d.prepare("INSERT INTO managed_streams (id, user_id, title, protocol, stream_key, ingest_authority) OVERRIDING SYSTEM VALUE VALUES (704, 603, 'WHIP on OpenRestream', 'webrtc', ?, 'openre')").run(openReWhipKey);
await d.prepare("INSERT INTO managed_streams (id, user_id, title, protocol, stream_key) OVERRIDING SYSTEM VALUE VALUES (705, 602, 'WHIP on Live', 'webrtc', ?)").run(liveWhipKey);
await d.prepare("INSERT INTO managed_streams (id, user_id, title, protocol, stream_key) OVERRIDING SYSTEM VALUE VALUES (705, 602, 'WHIP on Live', 'webrtc', ?)").run(liveWhipKey);
- await d.prepare("INSERT INTO managed_streams (id, user_id, title, protocol, stream_key, ingest_authority) OVERRIDING SYSTEM VALUE VALUES (706, 603, 'JSMPEG on OpenRe', 'jsmpeg', 'jsmpegopenre706', 'openre')").run();
+ await d.prepare("INSERT INTO managed_streams (id, user_id, title, protocol, stream_key, ingest_authority) OVERRIDING SYSTEM VALUE VALUES (706, 603, 'JSMPEG on OpenRestream', 'jsmpeg', 'jsmpegopenre706', 'openre')").run();
await d.prepare("INSERT INTO managed_streams (id, user_id, title, protocol, stream_key) OVERRIDING SYSTEM VALUE VALUES (707, 602, 'JSMPEG on Live', 'jsmpeg', 'jsmpeglive707')").run();
await d.prepare("INSERT INTO managed_streams (id, user_id, title, protocol, stream_key) OVERRIDING SYSTEM VALUE VALUES (707, 602, 'JSMPEG on Live', 'jsmpeg', 'jsmpeglive707')").run();
process.env.OPENRE_URL = stubUrl;
process.env.OPENRE_URL = stubUrl;
require('../server/openre/openre-client')._reset();
require('../server/openre/openre-client')._reset();
@@ -301,8 +301,8 @@ function sessionEvent(type, sessionId, revision, payload = {}) {
@@ -301,8 +301,8 @@ function sessionEvent(type, sessionId, revision, payload = {}) {
['slot id + key', () => publish(704, { key: openReWhipKey })],
['slot id + key', () => publish(704, { key: openReWhipKey })],
]) {
]) {
const r = await call();
const r = await call();
- assert.strictEqual(r.status, 401, `WHIP ${label} on an OpenRe slot is refused`);
- assert.ok(!/openre/i.test(JSON.stringify(r.body)), 'the refusal does not say OpenRe exists');
+ assert.strictEqual(r.status, 401, `WHIP ${label} on an OpenRestream slot is refused`);
+ assert.ok(!/openre/i.test(JSON.stringify(r.body)), 'the refusal does not say OpenRestream exists');
}
}
assert.strictEqual(await streamCount(), before, 'a refused WHIP publish creates no stream row');
assert.strictEqual(await streamCount(), before, 'a refused WHIP publish creates no stream row');
const admitted = await publish(liveWhipKey);
const admitted = await publish(liveWhipKey);
@@ -310,7 +310,7 @@ function sessionEvent(type, sessionId, revision, payload = {}) {
@@ -310,7 +310,7 @@ function sessionEvent(type, sessionId, revision, payload = {}) {
for (const r of [admitted, await publish(705, { key: liveWhipKey })]) assert.ok([200, 201, 503].includes(r.status), `live slot: ${r.status}`);
for (const r of [admitted, await publish(705, { key: liveWhipKey })]) assert.ok([200, 201, 503].includes(r.status), `live slot: ${r.status}`);
// The switch landing between publishes: the next check refuses.
// The switch landing between publishes: the next check refuses.
await d.prepare("UPDATE managed_streams SET ingest_authority = 'openre' WHERE id = 705").run();
await d.prepare("UPDATE managed_streams SET ingest_authority = 'openre' WHERE id = 705").run();
- assert.strictEqual((await publish(liveWhipKey)).status, 401, 'refused as soon as the slot is on OpenRe');
+ assert.strictEqual((await publish(liveWhipKey)).status, 401, 'refused as soon as the slot is on OpenRestream');
await d.prepare("UPDATE managed_streams SET ingest_authority = 'live' WHERE id = 705").run();
await d.prepare("UPDATE managed_streams SET ingest_authority = 'live' WHERE id = 705").run();
// The Go Live page's in-browser broadcaster (/ws/broadcast, role=broadcaster) is WebRTC ingest too.
// The Go Live page's in-browser broadcaster (/ws/broadcast, role=broadcaster) is WebRTC ingest too.
@@ -328,8 +328,8 @@ function sessionEvent(type, sessionId, revision, payload = {}) {
@@ -328,8 +328,8 @@ function sessionEvent(type, sessionId, revision, payload = {}) {
const browserOnOpenre = Number((await db.createStream({ user_id: 603, managed_stream_id: 704, title: 'browser', protocol: 'webrtc' })).lastInsertRowid);
const browserOnOpenre = Number((await db.createStream({ user_id: 603, managed_stream_id: 704, title: 'browser', protocol: 'webrtc' })).lastInsertRowid);
const browserOnLive = Number((await db.createStream({ user_id: 602, managed_stream_id: 705, title: 'browser', protocol: 'webrtc' })).lastInsertRowid);
const browserOnLive = Number((await db.createStream({ user_id: 602, managed_stream_id: 705, title: 'browser', protocol: 'webrtc' })).lastInsertRowid);
const browserPersonal = Number((await db.createStream({ user_id: 603, title: 'browser', protocol: 'webrtc' })).lastInsertRowid);
const browserPersonal = Number((await db.createStream({ user_id: 603, title: 'browser', protocol: 'webrtc' })).lastInsertRowid);
- assert.deepStrictEqual(await connectBroadcaster(browserOnOpenre, 'tok-603'), [[4003, 'Stream key not recognized']], 'browser broadcaster on an OpenRe slot is refused');
- assert.deepStrictEqual(await connectBroadcaster(browserPersonal, 'tok-603'), [[4003, 'Stream key not recognized']], 'a slot-less stream of an owner with an OpenRe slot is refused');
+ assert.deepStrictEqual(await connectBroadcaster(browserOnOpenre, 'tok-603'), [[4003, 'Stream key not recognized']], 'browser broadcaster on an OpenRestream slot is refused');
+ assert.deepStrictEqual(await connectBroadcaster(browserPersonal, 'tok-603'), [[4003, 'Stream key not recognized']], 'a slot-less stream of an owner with an OpenRestream slot is refused');
assert.deepStrictEqual(await connectBroadcaster(browserOnLive, 'tok-602'), [], 'browser broadcaster on a Live slot is admitted');
assert.deepStrictEqual(await connectBroadcaster(browserOnLive, 'tok-602'), [], 'browser broadcaster on a Live slot is admitted');
const relay = require('../server/streaming/jsmpeg-relay');
const relay = require('../server/streaming/jsmpeg-relay');
@@ -344,9 +344,9 @@ function sessionEvent(type, sessionId, revision, payload = {}) {
@@ -344,9 +344,9 @@ function sessionEvent(type, sessionId, revision, payload = {}) {
const liveCh = relay.createChannel('jsmpeglive707');
const liveCh = relay.createChannel('jsmpeglive707');
const personalCh = relay.createChannel('personalkey601');
const personalCh = relay.createChannel('personalkey601');
await new Promise(r => setTimeout(r, 200));
await new Promise(r => setTimeout(r, 200));
- assert.strictEqual(await post(openreCh.videoPort, 'jsmpegopenre706'), 404, 'JSMPEG video: an OpenRe slot key is refused');
- assert.strictEqual(await post(openreCh.audioPort, 'jsmpegopenre706'), 404, 'JSMPEG audio: an OpenRe slot key is refused');
- assert.strictEqual(await post(personalCh.videoPort, 'personalkey601'), 404, "JSMPEG: the owner's personal key is refused while a slot is on OpenRe");
+ assert.strictEqual(await post(openreCh.videoPort, 'jsmpegopenre706'), 404, 'JSMPEG video: an OpenRestream slot key is refused');
+ assert.strictEqual(await post(openreCh.audioPort, 'jsmpegopenre706'), 404, 'JSMPEG audio: an OpenRestream slot key is refused');
+ assert.strictEqual(await post(personalCh.videoPort, 'personalkey601'), 404, "JSMPEG: the owner's personal key is refused while a slot is on OpenRestream");
assert.strictEqual(await post(liveCh.videoPort, 'jsmpeglive707'), 200, 'JSMPEG: a Live slot is admitted');
assert.strictEqual(await post(liveCh.videoPort, 'jsmpeglive707'), 200, 'JSMPEG: a Live slot is admitted');
assert.strictEqual(await post(liveCh.audioPort, 'jsmpeglive707'), 200);
assert.strictEqual(await post(liveCh.audioPort, 'jsmpeglive707'), 200);
@@ -364,7 +364,7 @@ function sessionEvent(type, sessionId, revision, payload = {}) {
@@ -364,7 +364,7 @@ function sessionEvent(type, sessionId, revision, payload = {}) {
process.env.OPENRE_URL = stubUrl;
process.env.OPENRE_URL = stubUrl;
require('../server/openre/openre-client')._reset();
require('../server/openre/openre-client')._reset();
- // The admin status lists the protocols OpenRe carries.
+ // The admin status lists the protocols OpenRestream carries.
const statusApp = express();
const statusApp = express();
// routes.js takes requireAdmin at load: admit the test caller before the router is first required.
// routes.js takes requireAdmin at load: admit the test caller before the router is first required.
require('../server/auth/auth').requireAdmin = (req, res, next) => { req.user = { id: 1, username: 'admin', role: 'admin' }; next(); };
require('../server/auth/auth').requireAdmin = (req, res, next) => { req.user = { id: 1, username: 'admin', role: 'admin' }; next(); };
@@ -375,7 +375,7 @@ function sessionEvent(type, sessionId, revision, payload = {}) {
@@ -375,7 +375,7 @@ function sessionEvent(type, sessionId, revision, payload = {}) {
else assert.fail(`status route answered ${statusRes.status}`);
else assert.fail(`status route answered ${statusRes.status}`);
statusServer.close();
statusServer.close();
- // The Go Live profile and endpoint of an OpenRe WHIP/JSMPEG slot point at OpenRe, never at Live's
+ // The Go Live profile and endpoint of an OpenRestream WHIP/JSMPEG slot point at OpenRestream, never at Live's
// WHIP base or relay (Live refuses those publishes).
// WHIP base or relay (Live refuses those publishes).
require('../server/auth/auth').requireAuth = async (req, res, next) => { req.user = await db.getUserById(601); next(); };
require('../server/auth/auth').requireAuth = async (req, res, next) => { req.user = await db.getUserById(601); next(); };
const streamsApp = express();
const streamsApp = express();
@@ -407,5 +407,5 @@ function sessionEvent(type, sessionId, revision, payload = {}) {
@@ -407,5 +407,5 @@ function sessionEvent(type, sessionId, revision, payload = {}) {
mirror._reset();
mirror._reset();
server.close();
server.close();
stub.close();
stub.close();
- console.log('✅ openre switch: off = unchanged, switch/rotation, RTMP/WHIP/browser/JSMPEG refusal, OpenRe ingest URLs, signed mirror (once, ordered), reconcile, rollback');
+ console.log('✅ openre switch: off = unchanged, switch/rotation, RTMP/WHIP/browser/JSMPEG refusal, OpenRestream ingest URLs, signed mirror (once, ordered), reconcile, rollback');
})().catch((err) => { console.error(err); process.exit(1); });
})().catch((err) => { console.error(err); process.exit(1); });
@@ -4,7 +4,7 @@
@@ -4,7 +4,7 @@
*
*
* Boots the real server as a restore-drill instance (test/security-crawl.js) with a sentinel value in
* Boots the real server as a restore-drill instance (test/security-crawl.js) with a sentinel value in
* every secret environment variable Live reads (the Network OAuth client secret, the internal API
* every secret environment variable Live reads (the Network OAuth client secret, the internal API
- * keys, Media's webhook/event secrets, the Events and OpenRe subscription secrets, TURN,
+ * keys, Media's webhook/event secrets, the Events and OpenRestream subscription secrets, TURN,
* PayPal, the admin password, GitHub tokens, the ops webhook) and in every secret site setting an
* PayPal, the admin password, GitHub tokens, the ops webhook) and in every secret site setting an
* owner can store (GIF, TTS, AI, Twitch/Kick/YouTube, soundboard keys, the Discord and ops
* owner can store (GIF, TTS, AI, Twitch/Kick/YouTube, soundboard keys, the Discord and ops
* webhooks), then requests every GET route Express knows, the pages, the probes (/api/ready,
* webhooks), then requests every GET route Express knows, the pages, the probes (/api/ready,
@@ -361,7 +361,7 @@ async function check(name, fn) {
@@ -361,7 +361,7 @@ async function check(name, fn) {
'server/monetization/wallet-client.js': 'OpenVibe.Network wallet (configured)',
'server/monetization/wallet-client.js': 'OpenVibe.Network wallet (configured)',
'server/monetization/payments.js': 'PayPal (fixed host)',
'server/monetization/payments.js': 'PayPal (fixed host)',
'server/monetization/cosmetics.js': 'legacy quest API (env)',
'server/monetization/cosmetics.js': 'legacy quest API (env)',
- 'server/openre/openre-client.js': 'OpenRe.Stream (configured)',
+ 'server/openre/openre-client.js': 'OpenRestream (configured)',
'server/ai/ai-service.js': 'OpenVibe.AI (configured)',
'server/ai/ai-service.js': 'OpenVibe.AI (configured)',
'server/ai/llm.js': 'images are Live\'s own frames; a streamer\'s typed provider address (testProvider) goes through egress.postJson',
'server/ai/llm.js': 'images are Live\'s own frames; a streamer\'s typed provider address (testProvider) goes through egress.postJson',
'server/chat/soundboard-service.js': '101soundboards (host allowlist; the audio download through safeLookup)',
'server/chat/soundboard-service.js': '101soundboards (host allowlist; the audio download through safeLookup)',
@@ -6,7 +6,7 @@
@@ -6,7 +6,7 @@
* managed_streams[].stream_key, then the stream detail's managed_stream_key; see
* managed_streams[].stream_key, then the stream detail's managed_stream_key; see
* public-serializers.test.js and security-redaction.test.js), enough to publish to someone's slot
* public-serializers.test.js and security-redaction.test.js), enough to publish to someone's slot
* over RTMP or WHIP. Those tests pin the endpoints that leaked. This one covers the class: it seeds
* over RTMP or WHIP. Those tests pin the endpoints that leaked. This one covers the class: it seeds
- * a streamer whose every credential is a sentinel (account key, a Live slot key, an OpenRe slot's
+ * a streamer whose every credential is a sentinel (account key, a Live slot key, an OpenRestream slot's
* rotated Live key, a restream key and SRT passphrase, RobotStreamer / platform / PowerChat tokens,
* rotated Live key, a restream key and SRT passphrase, RobotStreamer / platform / PowerChat tokens,
* AI keys, control key and API token hashes, a camera password hash), boots the real server as a
* AI keys, control key and API token hashes, a camera password hash), boots the real server as a
* restore-drill instance (test/security-crawl.js), and requests EVERY GET route Express knows,
* restore-drill instance (test/security-crawl.js), and requests EVERY GET route Express knows,
@@ -70,7 +70,7 @@ const SEED = `(async () => {
@@ -70,7 +70,7 @@ const SEED = `(async () => {
const admin = await mk('keyadmin', 'admin', K.admin);
const admin = await mk('keyadmin', 'admin', K.admin);
const ch = await db.getChannelByUserId(owner);
const ch = await db.getChannelByUserId(owner);
const slot = Number((await db.createManagedStream({ user_id: owner, channel_id: ch.id, slug: 'main', title: 'Main slot', protocol: 'rtmp', stream_key: K.slotMain })).lastInsertRowid);
const slot = Number((await db.createManagedStream({ user_id: owner, channel_id: ch.id, slug: 'main', title: 'Main slot', protocol: 'rtmp', stream_key: K.slotMain })).lastInsertRowid);
- const slot2 = Number((await db.createManagedStream({ user_id: owner, channel_id: ch.id, slug: 'relay', title: 'OpenRe slot', protocol: 'rtmp', stream_key: K.slotOpenre })).lastInsertRowid);
+ const slot2 = Number((await db.createManagedStream({ user_id: owner, channel_id: ch.id, slug: 'relay', title: 'OpenRestream slot', protocol: 'rtmp', stream_key: K.slotOpenre })).lastInsertRowid);
await db.run("UPDATE managed_streams SET ingest_authority = 'openre', openre_stream_id = 'std_sentinel' WHERE id = ?", [slot2]);
await db.run("UPDATE managed_streams SET ingest_authority = 'openre', openre_stream_id = 'std_sentinel' WHERE id = ?", [slot2]);
const live = Number((await db.createStream({ user_id: owner, channel_id: ch.id, managed_stream_id: slot, title: 'Live now', protocol: 'rtmp' })).lastInsertRowid);
const live = Number((await db.createStream({ user_id: owner, channel_id: ch.id, managed_stream_id: slot, title: 'Live now', protocol: 'rtmp' })).lastInsertRowid);
const ended = Number((await db.createStream({ user_id: owner, channel_id: ch.id, managed_stream_id: slot2, title: 'Earlier', protocol: 'rtmp' })).lastInsertRowid);
const ended = Number((await db.createStream({ user_id: owner, channel_id: ch.id, managed_stream_id: slot2, title: 'Earlier', protocol: 'rtmp' })).lastInsertRowid);
@@ -195,7 +195,7 @@ const buttons = async (streamId) => (await db.getStreamControls(streamId)).map((
@@ -195,7 +195,7 @@ const buttons = async (streamId) => (await db.getStreamControls(streamId)).map((
assert.match(routes, /control_config_id: await ownControlConfigId\(req, req\.body\.control_config_id\) \|\| null,/, 'a new slot takes only the owner\'s profile');
assert.match(routes, /control_config_id: await ownControlConfigId\(req, req\.body\.control_config_id\) \|\| null,/, 'a new slot takes only the owner\'s profile');
assert.match(routes, /const cfgId = await ownControlConfigId\(req, req\.body\.control_config_id\);\s*if \(cfgId === undefined\) return res\.status\(403\)/, 'and so does a slot edit');
assert.match(routes, /const cfgId = await ownControlConfigId\(req, req\.body\.control_config_id\);\s*if \(cfgId === undefined\) return res\.status\(403\)/, 'and so does a slot edit');
const mirror = read('server/openre/mirror.js');
const mirror = read('server/openre/mirror.js');
- assert.match(mirror, /const configId = slot\.control_config_id \|\| \(channel && channel\.active_control_config_id\);/, 'OpenRe mirror');
+ assert.match(mirror, /const configId = slot\.control_config_id \|\| \(channel && channel\.active_control_config_id\);/, 'OpenRestream mirror');
});
});
server.close();
server.close();
@@ -1,8 +1,8 @@
@@ -1,8 +1,8 @@
#!/usr/bin/env node
#!/usr/bin/env node
'use strict';
'use strict';
/**
/**
- * The Go Live workspace's ingest URLs for a slot ingested by OpenRe: WHIP and JSMPEG point at
- * OpenRe's servers with the key in the path, never at Live's /whip/<slot> or JSMPEG relay (Live
+ * The Go Live workspace's ingest URLs for a slot ingested by OpenRestream: WHIP and JSMPEG point at
+ * OpenRestream's servers with the key in the path, never at Live's /whip/<slot> or JSMPEG relay (Live
* refuses those publishes). Slots on Live's own ingest keep Live's URLs.
* refuses those publishes). Slots on Live's own ingest keep Live's URLs.
*/
*/
const assert = require('assert');
const assert = require('assert');
@@ -27,15 +27,15 @@ _wsState.whipUrlBase = 'https://whip.openvibe.live/';
@@ -27,15 +27,15 @@ _wsState.whipUrlBase = 'https://whip.openvibe.live/';
assert.strictEqual(_wsWhipUrl(708, 'livekey'), 'https://whip.openvibe.live/whip/708');
assert.strictEqual(_wsWhipUrl(708, 'livekey'), 'https://whip.openvibe.live/whip/708');
assert.ok(_wsRenderMethodEndpoint('cli', 'livekey', 708).includes('http://openvibe.live:PORT/livekey/640/480/'));
assert.ok(_wsRenderMethodEndpoint('cli', 'livekey', 708).includes('http://openvibe.live:PORT/livekey/640/480/'));
-// OpenRe: its WHIP URL takes the key, its JSMPEG server takes key/width/height.
+// OpenRestream: its WHIP URL takes the key, its JSMPEG server takes key/width/height.
Object.assign(_wsState, { whipUrlBase: null, openreWhipUrl: 'https://ingest.openre.stream/whip', openreJsmpegUrl: 'http://ingest.openre.stream:8081' });
Object.assign(_wsState, { whipUrlBase: null, openreWhipUrl: 'https://ingest.openre.stream/whip', openreJsmpegUrl: 'http://ingest.openre.stream:8081' });
assert.strictEqual(_wsWhipUrl(708, 'ork_key'), 'https://ingest.openre.stream/whip/ork_key');
assert.strictEqual(_wsWhipUrl(708, 'ork_key'), 'https://ingest.openre.stream/whip/ork_key');
const whipInfo = _wsRenderMethodEndpoint('whip', 'ork_key', 708);
const whipInfo = _wsRenderMethodEndpoint('whip', 'ork_key', 708);
assert.ok(whipInfo.includes('https://ingest.openre.stream/whip/ork_key'), whipInfo);
assert.ok(whipInfo.includes('https://ingest.openre.stream/whip/ork_key'), whipInfo);
assert.ok(!whipInfo.includes('/whip/708'));
assert.ok(!whipInfo.includes('/whip/708'));
const cli = _wsRenderMethodEndpoint('cli', 'ork_key', 709);
const cli = _wsRenderMethodEndpoint('cli', 'ork_key', 709);
-assert.ok(cli.includes('http://ingest.openre.stream:8081/ork_key/640/480/'), 'JSMPEG commands use OpenRe');
+assert.ok(cli.includes('http://ingest.openre.stream:8081/ork_key/640/480/'), 'JSMPEG commands use OpenRestream');
assert.ok(!cli.includes('openvibe.live:PORT'));
assert.ok(!cli.includes('openvibe.live:PORT'));
-assert.ok(cli.includes('https://ingest.openre.stream/whip/ork_key'), 'the CLI WHIP tab uses OpenRe');
+assert.ok(cli.includes('https://ingest.openre.stream/whip/ork_key'), 'the CLI WHIP tab uses OpenRestream');
-console.log('✅ workspace OpenRe ingest: WHIP and JSMPEG URLs point at OpenRe for switched slots, at Live otherwise');
+console.log('✅ workspace OpenRestream ingest: WHIP and JSMPEG URLs point at OpenRestream for switched slots, at Live otherwise');