openvibers/openvibe.sdk Commits

live: slots.history, slots.quality and slots.titleIdeas (#111)

OP OpenVibers committed 11 days ago · 5ed71e46a6 · 4 files, +29 −1

Changes

ddiff --git a/CHANGELOG.md b/CHANGELOG.md
iindex b2cdb05..68d6ee4 100644
---- a/CHANGELOG.md
++++ b/CHANGELOG.md
@@@ -1,5 +1,12 @@
# Changelog
++## Unreleased
++
++- **`openvibe-sdk/live`: the rest of the Go live screen.** `live.slots.history(id)` (the slot's past streams),
++ `live.slots.quality(id, { resolution, fps, bitrate })` and `live.slots.titleIdeas(id, { weekday, hour, title })` (the
++ card's ✨ Suggest: three titles for the next stream). With these, an app does everything Live's Go live page does
++ (https://openvibe.live/docs/streams-api).
++
## 0.80.0 (2026-11-01)
- **`openvibe-sdk/explicit`: nudity is art.** The owner, 2026-10-31: "naked and nude is fine… stop being against art on
ddiff --git a/src/live.js b/src/live.js
iindex e903ed6..68c87ce 100644
---- a/src/live.js
++++ b/src/live.js
@@@ -75,6 +75,15 @@ function createLiveClient(client, { baseUrl } = {}) {
* restream_destinations, … }. A slot OpenRestream ingests answers OpenRestream's own whip_url and rtmp_url and no key.
*/
profile: (slotId, { signal } = {}) => call({ path: `/api/streams/managed/${id(slotId, 'a slot id')}/profile`, signal }),
++ /** The slot's past streams, newest first → { sessions: [{ id, title, started_at, duration_seconds, peak_viewers, vod_id, … }] }. */
++ history: (slotId, { signal } = {}) => call({ path: `/api/streams/managed/${id(slotId, 'a slot id')}/history`, signal }),
++ /** The slot's quality: { resolution: '1080', fps: '30', bitrate: 4500 } (kbps), and the browser settings beside them. */
++ quality: (slotId, settings, { signal } = {}) => call({ method: 'PUT', path: '/api/streams/broadcast-settings', json: { managed_stream_id: Number(id(slotId, 'a slot id')), settings }, signal }),
++ /**
++ * Three title ideas for the slot's next stream (the Go live card's Suggest) → { titles }. weekday and hour are the
++ * streamer's own (Live does not know their time zone); title is what is typed now. A few a minute (429 says wait).
++ */
++ titleIdeas: (slotId, { weekday, hour, title } = {}, { signal } = {}) => call({ method: 'POST', path: `/api/streams/managed/${id(slotId, 'a slot id')}/title-ideas`, json: { weekday, hour, title }, signal }),
};
/** Go live from a slot → { stream, endpoint }. opts: title, category, tags, is_nsfw. */
ddiff --git a/test/live.test.js b/test/live.test.js
iindex 546ce75..fe9e0f2 100644
---- a/test/live.test.js
++++ b/test/live.test.js
@@@ -58,6 +58,9 @@ const { createLiveClient } = require('../src/live');
await live.slots.create({ title: 'Robot cam', streaming_method: 'whip' });
await live.slots.update(5, { browser_mode: 'screen' });
await live.slots.regenerateKey(5);
++ await live.slots.history(5);
++ await live.slots.quality(5, { resolution: '1080', fps: '30', bitrate: 4500 });
++ await live.slots.titleIdeas(5, { weekday: 'Sunday', hour: 21, title: 'Night walk' });
const went = await live.goLive(5, { title: 'Live now' });
await live.streams.endpoint(77);
await live.streams.update(77, { title: 'Still live' });
@@@ -67,10 +70,13 @@ const { createLiveClient } = require('../src/live');
assert.deepStrictEqual(went, { stream: { id: 77 }, endpoint: { whipUrlBase: 'x' } });
assert.deepStrictEqual(seen.map((s) => `${s.method} ${s.url}`), [
'GET /api/streams/managed', 'POST /api/streams/managed', 'PUT /api/streams/managed/5', 'POST /api/streams/managed/5/regenerate-key',
++ 'GET /api/streams/managed/5/history', 'PUT /api/streams/broadcast-settings', 'POST /api/streams/managed/5/title-ideas',
'POST /api/streams', 'GET /api/streams/77/endpoint', 'PUT /api/streams/77', 'POST /api/streams/77/heartbeat', 'DELETE /api/streams/77',
'DELETE /api/streams/managed/5',
]);
-- assert.deepStrictEqual(JSON.parse(seen[4].body), { title: 'Live now', managed_stream_id: 5 });
++ assert.deepStrictEqual(JSON.parse(seen[5].body), { managed_stream_id: 5, settings: { resolution: '1080', fps: '30', bitrate: 4500 } });
++ assert.deepStrictEqual(JSON.parse(seen[6].body), { weekday: 'Sunday', hour: 21, title: 'Night walk' });
++ assert.deepStrictEqual(JSON.parse(seen[7].body), { title: 'Live now', managed_stream_id: 5 });
assert.ok(seen.filter((s) => s.method !== 'GET').every((s) => !s.idem), 'writes carry no idempotency key and are never retried');
// WHIP: the offer, the answer, the Location; the key as bearer only when given; never the client's token.
ddiff --git a/types/live.d.ts b/types/live.d.ts
iindex 6259613..cc16b7b 100644
---- a/types/live.d.ts
++++ b/types/live.d.ts
@@@ -29,6 +29,12 @@ export interface LiveClient {
regenerateKey(slotId: number | string, opts?: Opts): Promise<LiveJson>;
/** A slot's settings and where to publish to it (owner): stream_key, whip_url_base, rtmp_url, or OpenRestream's whip_url. */
profile(slotId: number | string, opts?: Opts): Promise<LiveJson>;
++ /** The slot's past streams, newest first. */
++ history(slotId: number | string, opts?: Opts): Promise<{ sessions: LiveJson[] }>;
++ /** The slot's quality: resolution, fps and bitrate (kbps), and the browser settings beside them. */
++ quality(slotId: number | string, settings: { resolution?: string; fps?: string; bitrate?: number; [k: string]: unknown }, opts?: Opts): Promise<LiveJson>;
++ /** Three title ideas for the next stream (the Go live card's Suggest). */
++ titleIdeas(slotId: number | string, input?: { weekday?: string; hour?: number; title?: string }, opts?: Opts): Promise<{ titles: string[] }>;
};
/** Go live from a slot → { stream, endpoint }. */
goLive(slotId: number | string, opts?: GoLiveOptions, extra?: Opts): Promise<{ stream: LiveJson; endpoint: LiveJson }>;