openvibers/openvibe.sdk Commits
live: slots.history, slots.quality and slots.titleIdeas (#111)
Changes
@@@ -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@@@ -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. */@@@ -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.@@@ -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 }>;
@@ -1,5 +1,12 @@
@@ -1,5 +1,12 @@
# Changelog
# 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)
## 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
- **`openvibe-sdk/explicit`: nudity is art.** The owner, 2026-10-31: "naked and nude is fine… stop being against art on
@@ -75,6 +75,15 @@ function createLiveClient(client, { baseUrl } = {}) {
@@ -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.
* 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 }),
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. */
/** Go live from a slot → { stream, endpoint }. opts: title, category, tags, is_nsfw. */
@@ -58,6 +58,9 @@ const { createLiveClient } = require('../src/live');
@@ -58,6 +58,9 @@ const { createLiveClient } = require('../src/live');
await live.slots.create({ title: 'Robot cam', streaming_method: 'whip' });
await live.slots.create({ title: 'Robot cam', streaming_method: 'whip' });
await live.slots.update(5, { browser_mode: 'screen' });
await live.slots.update(5, { browser_mode: 'screen' });
await live.slots.regenerateKey(5);
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' });
const went = await live.goLive(5, { title: 'Live now' });
await live.streams.endpoint(77);
await live.streams.endpoint(77);
await live.streams.update(77, { title: 'Still live' });
await live.streams.update(77, { title: 'Still live' });
@@ -67,10 +70,13 @@ const { createLiveClient } = require('../src/live');
@@ -67,10 +70,13 @@ const { createLiveClient } = require('../src/live');
assert.deepStrictEqual(went, { stream: { id: 77 }, endpoint: { whipUrlBase: 'x' } });
assert.deepStrictEqual(went, { stream: { id: 77 }, endpoint: { whipUrlBase: 'x' } });
assert.deepStrictEqual(seen.map((s) => `${s.method} ${s.url}`), [
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', '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',
'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',
'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');
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.
// WHIP: the offer, the answer, the Location; the key as bearer only when given; never the client's token.
@@ -29,6 +29,12 @@ export interface LiveClient {
@@ -29,6 +29,12 @@ export interface LiveClient {
regenerateKey(slotId: number | string, opts?: Opts): Promise<LiveJson>;
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. */
/** 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>;
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 }. */
/** Go live from a slot → { stream, endpoint }. */
goLive(slotId: number | string, opts?: GoLiveOptions, extra?: Opts): Promise<{ stream: LiveJson; endpoint: LiveJson }>;
goLive(slotId: number | string, opts?: GoLiveOptions, extra?: Opts): Promise<{ stream: LiveJson; endpoint: LiveJson }>;