restream-sdk 0.1.9 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/src/core.js +154 -4
- package/src/react.js +5 -2
- package/types/index.d.ts +69 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "restream-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Plug-and-play headless React hooks to add multi-platform restreaming (connect socials, go live, live chat + viewers, collaborative split-screen) with zero client backend.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -55,4 +55,4 @@
|
|
|
55
55
|
"react",
|
|
56
56
|
"sdk"
|
|
57
57
|
]
|
|
58
|
-
}
|
|
58
|
+
}
|
package/src/core.js
CHANGED
|
@@ -5,6 +5,12 @@
|
|
|
5
5
|
// into goLive() — this module never captures or publishes media itself.
|
|
6
6
|
|
|
7
7
|
export const PLATFORMS = ["twitch", "kick", "youtube", "facebook", "instagram", "tiktok", "restream"];
|
|
8
|
+
// FALLBACK only. The real routing (oauth | restream | off) is fetched per-client from
|
|
9
|
+
// GET /api/v1/connect/platforms → `routing`, so the platform team controls native↔Restream
|
|
10
|
+
// entirely from the admin with no SDK/app change. These statics are used solely if that
|
|
11
|
+
// fetch hasn't happened yet (e.g. a sync call before loadRouting()).
|
|
12
|
+
const NATIVE_CONNECT_PLATFORMS = new Set(["youtube", "facebook"]);
|
|
13
|
+
const RESTREAM_BRIDGE_PLATFORMS = new Set(["tiktok"]);
|
|
8
14
|
|
|
9
15
|
const TERMINAL_STATUS = new Set(["stopped", "failed", "ended", "error", "completed"]);
|
|
10
16
|
|
|
@@ -33,6 +39,19 @@ export class RestreamStudio extends EventTarget {
|
|
|
33
39
|
this.viewers = null;
|
|
34
40
|
this.comments = [];
|
|
35
41
|
|
|
42
|
+
// Per-client routing map { <platform>: "oauth" | "restream" | "off" }, fetched from
|
|
43
|
+
// GET /connect/platforms. Null until loadRouting()/listOAuthPlatforms() runs; the
|
|
44
|
+
// connect helpers fall back to the static defaults above while it's null.
|
|
45
|
+
this._routing = null;
|
|
46
|
+
|
|
47
|
+
// Real-time presence/layout state ({ screenShare, camera, collab, cameraPosition,
|
|
48
|
+
// cameraSize }) pushed to the server via setStreamState(). Coalesced (trailing debounce)
|
|
49
|
+
// so rapid toggles send once. _pendingState holds state set before goLive() (flushed once
|
|
50
|
+
// the job exists).
|
|
51
|
+
this._streamState = {};
|
|
52
|
+
this._pendingState = null;
|
|
53
|
+
this._stateTimer = null;
|
|
54
|
+
|
|
36
55
|
this._token = null;
|
|
37
56
|
this._tokenExp = 0;
|
|
38
57
|
this._es = null;
|
|
@@ -131,9 +150,57 @@ export class RestreamStudio extends EventTarget {
|
|
|
131
150
|
return this.connections;
|
|
132
151
|
}
|
|
133
152
|
|
|
134
|
-
/** Ask the backend which OAuth platforms are configured for this client.
|
|
153
|
+
/** Ask the backend which OAuth platforms are configured for this client.
|
|
154
|
+
* Also caches the per-client `routing` map ({platform: oauth|restream|off}) that
|
|
155
|
+
* drives connectPlatform()/restreamAddChannelUrl() — so native↔Restream is decided
|
|
156
|
+
* by the admin, not hardcoded here. */
|
|
135
157
|
async listOAuthPlatforms() {
|
|
136
|
-
|
|
158
|
+
const r = await this._req("GET", `/api/v1/connect/platforms${this._userQuery()}`);
|
|
159
|
+
if (r && r.routing && typeof r.routing === "object") this._routing = r.routing;
|
|
160
|
+
return r;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** Load (and cache) the per-client routing map. Call once on init so the synchronous
|
|
164
|
+
* connect helpers route correctly. Returns { platform: "oauth"|"restream"|"off" }. */
|
|
165
|
+
async loadRouting() {
|
|
166
|
+
await this.listOAuthPlatforms();
|
|
167
|
+
return this._routing || {};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** Effective route for a platform: "oauth" | "restream" | "off" | null (unknown). Uses
|
|
171
|
+
* the cached admin routing if loaded; otherwise the static fallbacks. */
|
|
172
|
+
routeFor(platform) {
|
|
173
|
+
const p = String(platform || "").toLowerCase();
|
|
174
|
+
if (this._routing && this._routing[p]) return this._routing[p];
|
|
175
|
+
if (NATIVE_CONNECT_PLATFORMS.has(p)) return "oauth";
|
|
176
|
+
if (RESTREAM_BRIDGE_PLATFORMS.has(p)) return "restream";
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
_isNativeConnect(platform) {
|
|
181
|
+
return this.routeFor(platform) === "oauth";
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
_isBridge(platform) {
|
|
185
|
+
return this.routeFor(platform) === "restream";
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Single dynamic connect entry point — the app calls this for ANY platform and the
|
|
190
|
+
* SDK routes it per the admin's configuration (no app change when routing flips):
|
|
191
|
+
* "oauth" → native OAuth popup (connect)
|
|
192
|
+
* "restream" → Restream bridge add-channel flow (connectRestreamChannel)
|
|
193
|
+
* "off" → throws (platform disabled for this client)
|
|
194
|
+
* Loads routing first if it hasn't been cached yet. Must be called from a click.
|
|
195
|
+
* @param {string} platform
|
|
196
|
+
* @param {object} [opts] forwarded to connectRestreamChannel for bridge platforms
|
|
197
|
+
*/
|
|
198
|
+
async connectPlatform(platform, opts = {}) {
|
|
199
|
+
if (!this._routing) { try { await this.loadRouting(); } catch { /* fall back to statics */ } }
|
|
200
|
+
const route = this.routeFor(platform);
|
|
201
|
+
if (route === "oauth") return this.connect(platform);
|
|
202
|
+
if (route === "restream") return this.connectRestreamChannel(platform, opts);
|
|
203
|
+
throw new Error(`"${platform}" is not available to connect for this client (routed "off").`);
|
|
137
204
|
}
|
|
138
205
|
|
|
139
206
|
/**
|
|
@@ -155,6 +222,15 @@ export class RestreamStudio extends EventTarget {
|
|
|
155
222
|
* unknown params), so it safely falls back to the channel page.
|
|
156
223
|
*/
|
|
157
224
|
restreamAddChannelUrl(platform) {
|
|
225
|
+
const normalized = String(platform || "").toLowerCase();
|
|
226
|
+
if (this._isNativeConnect(normalized) && this.publishableKey) {
|
|
227
|
+
const q = new URLSearchParams({ pk: this.publishableKey });
|
|
228
|
+
if (this.userId) q.set("userId", this.userId);
|
|
229
|
+
return `${this.apiBase}/api/v1/connect/${platform}/start?${q}`;
|
|
230
|
+
}
|
|
231
|
+
if (normalized && !this._isBridge(normalized)) {
|
|
232
|
+
throw new Error(`"${platform}" is not routed through the Restream bridge for this client. Use native OAuth (connect) instead.`);
|
|
233
|
+
}
|
|
158
234
|
const base = "https://restream.io/channel";
|
|
159
235
|
return platform ? `${base}?add=${encodeURIComponent(platform)}` : base;
|
|
160
236
|
}
|
|
@@ -174,6 +250,23 @@ export class RestreamStudio extends EventTarget {
|
|
|
174
250
|
* closed the popup without adding one.
|
|
175
251
|
*/
|
|
176
252
|
async connectRestreamChannel(platform, { pollMs = 3000, timeoutMs = 180_000 } = {}) {
|
|
253
|
+
const normalized = String(platform || "").toLowerCase();
|
|
254
|
+
// Load routing so native↔bridge is decided by the admin, not the statics.
|
|
255
|
+
if (!this._routing) { try { await this.loadRouting(); } catch { /* fall back to statics */ } }
|
|
256
|
+
if (this._isNativeConnect(normalized)) {
|
|
257
|
+
const result = await this.connect(platform);
|
|
258
|
+
return {
|
|
259
|
+
id: `native:${platform}`,
|
|
260
|
+
platform,
|
|
261
|
+
displayName: null,
|
|
262
|
+
url: null,
|
|
263
|
+
active: !result?.closed,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
if (!this._isBridge(normalized)) {
|
|
267
|
+
throw new Error(`"${platform}" is not routed through the Restream bridge for this client. Use native OAuth (connect) instead.`);
|
|
268
|
+
}
|
|
269
|
+
|
|
177
270
|
// Open synchronously to preserve the click's user-activation (else it's blocked).
|
|
178
271
|
let popup = window.open("", "restream_add_channel", "width=760,height=900");
|
|
179
272
|
if (!popup) throw new Error("popup blocked — allow popups to connect accounts");
|
|
@@ -184,6 +277,7 @@ export class RestreamStudio extends EventTarget {
|
|
|
184
277
|
await this.listConnections();
|
|
185
278
|
if (!this.connections.some((c) => c.platform === "restream")) {
|
|
186
279
|
const q = await this._authQuery();
|
|
280
|
+
q.platform = normalized;
|
|
187
281
|
popup.location = `${this.apiBase}/api/v1/connect/restream/start?${new URLSearchParams(q)}`;
|
|
188
282
|
await this._awaitRestreamConnected(timeoutMs);
|
|
189
283
|
// Our OAuth callback page auto-closes the popup; reopen for the add step.
|
|
@@ -299,7 +393,7 @@ export class RestreamStudio extends EventTarget {
|
|
|
299
393
|
* Restream channels. Any field omitted falls back to the
|
|
300
394
|
* default pre-set for this creator on the platform.
|
|
301
395
|
*/
|
|
302
|
-
async goLive({ publisherSessionId, destinations, overlay, recording, trackNames, restreamChannels, streamMeta, destinationMeta } = {}) {
|
|
396
|
+
async goLive({ publisherSessionId, destinations, overlay, recording, trackNames, restreamChannels, streamMeta, destinationMeta, streamingProvider, layout } = {}) {
|
|
303
397
|
if (!publisherSessionId) throw new Error("publisherSessionId is required (from your existing stream session)");
|
|
304
398
|
const body = {
|
|
305
399
|
publisherSessionId,
|
|
@@ -321,11 +415,24 @@ export class RestreamStudio extends EventTarget {
|
|
|
321
415
|
if (destinationMeta && typeof destinationMeta === "object" && Object.keys(destinationMeta).length) {
|
|
322
416
|
body.destinationMeta = destinationMeta;
|
|
323
417
|
}
|
|
418
|
+
// Streaming provider selection per destination.
|
|
419
|
+
// Example: { youtube: "youtube_native" } to use native YouTube instead of Restream bridge.
|
|
420
|
+
// Default is "restream" for all destinations.
|
|
421
|
+
if (streamingProvider && typeof streamingProvider === "object" && Object.keys(streamingProvider).length) {
|
|
422
|
+
body.streamingProvider = streamingProvider;
|
|
423
|
+
}
|
|
324
424
|
if (this.publishableKey && this.userId) body.clientUserId = this.userId; // token mode binds server-side
|
|
325
425
|
const job = await this._req("POST", "/api/v1/jobs", body);
|
|
326
426
|
this.job = job;
|
|
327
427
|
this._emit("job", job);
|
|
328
428
|
if (job?.id) this.startRealtime(job.id);
|
|
429
|
+
// Push the initial presence/layout state (explicit `layout`, or whatever was set via
|
|
430
|
+
// setStreamState() before we had a job id). Best-effort — never block go-live on it.
|
|
431
|
+
const initialState = { ...(this._pendingState || {}), ...(layout || {}) };
|
|
432
|
+
if (job?.id && Object.keys(initialState).length) {
|
|
433
|
+
this._pendingState = null;
|
|
434
|
+
this.setStreamState(initialState).catch(() => {});
|
|
435
|
+
}
|
|
329
436
|
return job;
|
|
330
437
|
}
|
|
331
438
|
|
|
@@ -333,6 +440,7 @@ export class RestreamStudio extends EventTarget {
|
|
|
333
440
|
if (!this.job?.id) return null;
|
|
334
441
|
const r = await this._req("DELETE", `/api/v1/jobs/${this.job.id}`);
|
|
335
442
|
this.stopRealtime();
|
|
443
|
+
if (this._stateTimer) { clearTimeout(this._stateTimer); this._stateTimer = null; } // drop any pending state POST
|
|
336
444
|
this.job = { ...this.job, status: "stopping" };
|
|
337
445
|
this._emit("job", this.job);
|
|
338
446
|
return r;
|
|
@@ -355,6 +463,40 @@ export class RestreamStudio extends EventTarget {
|
|
|
355
463
|
return this._req("PATCH", `/api/v1/jobs/${this.job.id}/overlay`, opts);
|
|
356
464
|
}
|
|
357
465
|
|
|
466
|
+
/**
|
|
467
|
+
* Push the creator's real-time presence/layout state to the server. Call on EVERY change
|
|
468
|
+
* (screen-share toggled, camera on/off, collab on/off, camera moved/resized) so the server
|
|
469
|
+
* can swap banner templates per state and keep assets clear of the camera. Partial patches
|
|
470
|
+
* merge; the full merged state is sent (trailing-debounced ~150ms so rapid toggles send once).
|
|
471
|
+
*
|
|
472
|
+
* @param {object} patch
|
|
473
|
+
* @param {boolean} [patch.screenShare]
|
|
474
|
+
* @param {boolean} [patch.camera]
|
|
475
|
+
* @param {boolean} [patch.collab]
|
|
476
|
+
* @param {"top-left"|"top-right"|"bottom-left"|"bottom-right"|null} [patch.cameraPosition]
|
|
477
|
+
* @param {"small"|"medium"|"large"|number|null} [patch.cameraSize] enum or % (1-100)
|
|
478
|
+
* @returns {object} the merged local state (the network send is debounced/best-effort)
|
|
479
|
+
*/
|
|
480
|
+
setStreamState(patch = {}) {
|
|
481
|
+
this._streamState = { ...this._streamState, ...patch };
|
|
482
|
+
this._emit("state", this._streamState);
|
|
483
|
+
if (!this.job?.id) {
|
|
484
|
+
// Not live yet — remember it and flush when goLive() creates the job.
|
|
485
|
+
this._pendingState = { ...this._streamState };
|
|
486
|
+
return this._streamState;
|
|
487
|
+
}
|
|
488
|
+
const jobId = this.job.id;
|
|
489
|
+
if (this._stateTimer) clearTimeout(this._stateTimer);
|
|
490
|
+
this._stateTimer = setTimeout(() => {
|
|
491
|
+
this._stateTimer = null;
|
|
492
|
+
this._req("POST", `/api/v1/jobs/${jobId}/state`, this._streamState).catch((e) => this._emit("error", e));
|
|
493
|
+
}, 150);
|
|
494
|
+
return this._streamState;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/** Last presence/layout state set via setStreamState() (local view). */
|
|
498
|
+
get streamState() { return this._streamState; }
|
|
499
|
+
|
|
358
500
|
// ---- realtime (chat SSE + job-status/viewer polling) ----
|
|
359
501
|
async startRealtime(jobId) {
|
|
360
502
|
this.stopRealtime();
|
|
@@ -457,9 +599,17 @@ export class RestreamStudio extends EventTarget {
|
|
|
457
599
|
* Invite another creator (same client) to a split-screen collab. The composite
|
|
458
600
|
* starts only when they accept. Pass your own publisherSessionId + destinations so
|
|
459
601
|
* the server can begin the merged broadcast on accept (or rely on your active job).
|
|
602
|
+
*
|
|
603
|
+
* collaboratorRestreamOn defaults to TRUE: the invited creator's connected social
|
|
604
|
+
* accounts MUST receive the side-by-side composite unless they explicitly opt out
|
|
605
|
+
* (pass `collaboratorRestreamOn: false`). This matches the server product intent in
|
|
606
|
+
* routes/collab.js and CollabService.createInvite. The bug: defaulting the SDK to
|
|
607
|
+
* `false` here sent `false` in the request body, which the server honored, so the
|
|
608
|
+
* collaborator's broadcasts/fan-out were silently skipped and the collab frame never
|
|
609
|
+
* appeared on the collaborator's own social accounts.
|
|
460
610
|
* @param {object} opts { collaboratorUserId, collaboratorRestreamOn?, publisherSessionId?, destinations?, metadata? }
|
|
461
611
|
*/
|
|
462
|
-
async requestCollab({ collaboratorUserId, collaboratorRestreamOn =
|
|
612
|
+
async requestCollab({ collaboratorUserId, collaboratorRestreamOn = true, publisherSessionId, destinations, metadata } = {}) {
|
|
463
613
|
if (!collaboratorUserId) throw new Error("collaboratorUserId is required");
|
|
464
614
|
const invite = await this._req("POST", "/api/v1/collab/request", this._withUser({
|
|
465
615
|
collaboratorUserId, collaboratorRestreamOn, publisherSessionId, destinations, metadata,
|
package/src/react.js
CHANGED
|
@@ -49,12 +49,15 @@ export function useRestream(opts) {
|
|
|
49
49
|
listRestreamChannels: useCallback(() => studio.listRestreamChannels(), [studio]),
|
|
50
50
|
connectRestreamChannel: useCallback((p, o) => studio.connectRestreamChannel(p, o), [studio]),
|
|
51
51
|
restreamAddChannelUrl: useCallback((p) => studio.restreamAddChannelUrl(p), [studio]),
|
|
52
|
+
connectPlatform: useCallback((p, o) => studio.connectPlatform(p, o), [studio]),
|
|
53
|
+
loadRouting: useCallback(() => studio.loadRouting(), [studio]),
|
|
52
54
|
arm: useCallback((o) => studio.arm(o), [studio]),
|
|
53
55
|
goLive: useCallback((o) => studio.goLive(o), [studio]),
|
|
54
56
|
stopLive: useCallback(() => studio.stopLive(), [studio]),
|
|
55
57
|
refreshJob: useCallback(() => studio.refreshJob(), [studio]),
|
|
56
58
|
sendChat: useCallback((m, p) => studio.sendChat(m, p), [studio]),
|
|
57
59
|
updateOverlay: useCallback((o) => studio.updateOverlay(o), [studio]),
|
|
60
|
+
setStreamState: useCallback((s) => studio.setStreamState(s), [studio]),
|
|
58
61
|
// collaborative split-screen
|
|
59
62
|
requestCollab: useCallback((o) => studio.requestCollab(o), [studio]),
|
|
60
63
|
acceptCollab: useCallback((id, o) => studio.acceptCollab(id, o), [studio]),
|
|
@@ -70,8 +73,8 @@ export function useRestream(opts) {
|
|
|
70
73
|
|
|
71
74
|
/** Convenience: just the social-connection state + actions. */
|
|
72
75
|
export function useConnections(opts) {
|
|
73
|
-
const { connections, connect, disconnect, listConnections, listOAuthPlatforms, listRestreamChannels, connectRestreamChannel, restreamAddChannelUrl } = useRestream(opts);
|
|
74
|
-
return { connections, connect, disconnect, refresh: listConnections, listOAuthPlatforms, listRestreamChannels, connectRestreamChannel, restreamAddChannelUrl };
|
|
76
|
+
const { connections, connect, disconnect, listConnections, listOAuthPlatforms, listRestreamChannels, connectRestreamChannel, restreamAddChannelUrl, connectPlatform, loadRouting } = useRestream(opts);
|
|
77
|
+
return { connections, connect, disconnect, refresh: listConnections, listOAuthPlatforms, listRestreamChannels, connectRestreamChannel, restreamAddChannelUrl, connectPlatform, loadRouting };
|
|
75
78
|
}
|
|
76
79
|
|
|
77
80
|
/** Convenience: live chat (incoming comments + outbound send). Pass an existing studio. */
|
package/types/index.d.ts
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
export type Platform = "twitch" | "kick" | "youtube" | "facebook" | "instagram" | "tiktok" | "restream";
|
|
2
2
|
|
|
3
|
+
/** How a platform is reached for this client (admin-controlled): native OAuth, the
|
|
4
|
+
* Restream bridge, or disabled. */
|
|
5
|
+
export type RoutingValue = "oauth" | "restream" | "off";
|
|
6
|
+
/** Per-client routing map returned by GET /connect/platforms (`routing`). */
|
|
7
|
+
export type Routing = Partial<Record<Platform, RoutingValue>>;
|
|
8
|
+
/** Response of listOAuthPlatforms(). `routing` is the single source of truth for
|
|
9
|
+
* native↔Restream; `platforms` is the legacy natively-connectable set. */
|
|
10
|
+
export interface OAuthPlatformsResponse {
|
|
11
|
+
platforms: Platform[];
|
|
12
|
+
routing?: Routing;
|
|
13
|
+
frontendOrigin: string;
|
|
14
|
+
redirectOrigin: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
3
17
|
/** A channel the user connected inside their Restream account (a fan-out target). */
|
|
4
18
|
export interface RestreamChannel {
|
|
5
19
|
id: string;
|
|
@@ -88,6 +102,8 @@ export interface GoLiveParams {
|
|
|
88
102
|
* default → the creator's pre-set global default → the platform's existing value.
|
|
89
103
|
*/
|
|
90
104
|
destinationMeta?: DestinationMeta;
|
|
105
|
+
/** Initial presence/layout state for the stream (same shape as setStreamState). */
|
|
106
|
+
layout?: StreamState;
|
|
91
107
|
}
|
|
92
108
|
|
|
93
109
|
export interface StreamMeta {
|
|
@@ -99,6 +115,26 @@ export interface StreamMeta {
|
|
|
99
115
|
/** Per-platform stream metadata overrides, keyed by platform name. */
|
|
100
116
|
export type DestinationMeta = Partial<Record<Platform, StreamMeta>> & Record<string, StreamMeta>;
|
|
101
117
|
|
|
118
|
+
/** Where the creator's frontend placed the camera while screen-sharing (template hint). */
|
|
119
|
+
export type CameraPosition = "top-left" | "top-right" | "bottom-left" | "bottom-right";
|
|
120
|
+
/** Camera size as an enum or a percentage of the frame (1-100). */
|
|
121
|
+
export type CameraSize = "small" | "medium" | "large" | number;
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Real-time presence/layout state pushed via setStreamState(). All fields optional —
|
|
125
|
+
* partial patches merge server-side. Camera position/size only matter while screen-sharing
|
|
126
|
+
* and are template metadata (they don't move media).
|
|
127
|
+
*/
|
|
128
|
+
export interface StreamState {
|
|
129
|
+
screenShare?: boolean;
|
|
130
|
+
camera?: boolean;
|
|
131
|
+
collab?: boolean;
|
|
132
|
+
cameraPosition?: CameraPosition | null;
|
|
133
|
+
cameraSize?: CameraSize | null;
|
|
134
|
+
/** Server-stamped on each update (read-only on the way back). */
|
|
135
|
+
updatedAt?: string;
|
|
136
|
+
}
|
|
137
|
+
|
|
102
138
|
export interface OverlayUpdate {
|
|
103
139
|
topBanner?: string;
|
|
104
140
|
bottomBanner?: string;
|
|
@@ -176,22 +212,34 @@ export class RestreamStudio extends EventTarget {
|
|
|
176
212
|
on(type: "comment", cb: (c: Comment) => void): () => void;
|
|
177
213
|
on(type: "comments", cb: (c: Comment[]) => void): () => void;
|
|
178
214
|
on(type: "error", cb: (e: Error) => void): () => void;
|
|
215
|
+
on(type: "state", cb: (s: StreamState) => void): () => void;
|
|
179
216
|
on(type: "collab" | "collab-invite" | "collab-accepted" | "collab-declined" | "collab-revoked" | "collab-ended" | "collab-expired" | "collab-composite" | "collab-snapshot", cb: (e: CollabEvent) => void): () => void;
|
|
180
217
|
listConnections(): Promise<Connection[]>;
|
|
181
|
-
listOAuthPlatforms(): Promise<
|
|
218
|
+
listOAuthPlatforms(): Promise<OAuthPlatformsResponse>;
|
|
182
219
|
listRestreamChannels(): Promise<RestreamChannel[]>;
|
|
183
|
-
/** URL of Restream's add-channel page
|
|
220
|
+
/** URL of Restream's add-channel page; YouTube/Facebook return native OAuth URLs in publishable-key mode. */
|
|
184
221
|
restreamAddChannelUrl(platform?: string): string;
|
|
185
222
|
/**
|
|
186
223
|
* One-call "Connect <platform> via Restream" — ensures the Restream account is
|
|
187
224
|
* connected, opens Restream's add-channel page, and resolves once the new channel
|
|
188
|
-
* appears.
|
|
225
|
+
* appears. YouTube and Facebook are routed to native OAuth instead. Returns the
|
|
226
|
+
* new channel, a native placeholder, or null if the user closed the popup without
|
|
189
227
|
* adding one. Must be called from a user gesture (opens a popup).
|
|
190
228
|
*/
|
|
191
229
|
connectRestreamChannel(
|
|
192
230
|
platform: string,
|
|
193
231
|
opts?: { pollMs?: number; timeoutMs?: number }
|
|
194
232
|
): Promise<RestreamChannel | null>;
|
|
233
|
+
/** Load + cache the per-client routing map; call once on init. */
|
|
234
|
+
loadRouting(): Promise<Routing>;
|
|
235
|
+
/** Effective route for a platform (uses cached admin routing, else static fallback). */
|
|
236
|
+
routeFor(platform: string): RoutingValue | null;
|
|
237
|
+
/** Single dynamic connect entry point — routes to native OAuth or the Restream bridge
|
|
238
|
+
* per the admin's configuration. Must be called from a user gesture. */
|
|
239
|
+
connectPlatform(
|
|
240
|
+
platform: string,
|
|
241
|
+
opts?: { pollMs?: number; timeoutMs?: number }
|
|
242
|
+
): Promise<{ platform: Platform; closed?: boolean } | RestreamChannel | null>;
|
|
195
243
|
connect(platform: Platform): Promise<{ platform: Platform; closed?: boolean }>;
|
|
196
244
|
disconnect(platform: Platform): Promise<Connection[]>;
|
|
197
245
|
arm(opts: { destinations: Platform[] }): unknown;
|
|
@@ -200,6 +248,11 @@ export class RestreamStudio extends EventTarget {
|
|
|
200
248
|
refreshJob(): Promise<Job | null>;
|
|
201
249
|
sendChat(message: string, platforms?: Platform[]): Promise<unknown>;
|
|
202
250
|
updateOverlay(opts: OverlayUpdate): Promise<unknown>;
|
|
251
|
+
/** Push real-time presence/layout state (screen-share/camera/collab + camera corner/size).
|
|
252
|
+
* Call on every change; sends are coalesced (trailing-debounced). Returns the merged state. */
|
|
253
|
+
setStreamState(patch: StreamState): StreamState;
|
|
254
|
+
/** Last presence/layout state set locally. */
|
|
255
|
+
readonly streamState: StreamState;
|
|
203
256
|
startRealtime(jobId: string): Promise<void>;
|
|
204
257
|
stopRealtime(): void;
|
|
205
258
|
// collaborative split-screen
|
|
@@ -230,19 +283,25 @@ export interface UseRestream {
|
|
|
230
283
|
connect(platform: Platform): Promise<{ platform: Platform; closed?: boolean }>;
|
|
231
284
|
disconnect(platform: Platform): Promise<Connection[]>;
|
|
232
285
|
listConnections(): Promise<Connection[]>;
|
|
233
|
-
listOAuthPlatforms(): Promise<
|
|
286
|
+
listOAuthPlatforms(): Promise<OAuthPlatformsResponse>;
|
|
234
287
|
listRestreamChannels(): Promise<RestreamChannel[]>;
|
|
235
288
|
restreamAddChannelUrl(platform?: string): string;
|
|
236
289
|
connectRestreamChannel(
|
|
237
290
|
platform: string,
|
|
238
291
|
opts?: { pollMs?: number; timeoutMs?: number }
|
|
239
292
|
): Promise<RestreamChannel | null>;
|
|
293
|
+
connectPlatform(
|
|
294
|
+
platform: string,
|
|
295
|
+
opts?: { pollMs?: number; timeoutMs?: number }
|
|
296
|
+
): Promise<{ platform: Platform; closed?: boolean } | RestreamChannel | null>;
|
|
297
|
+
loadRouting(): Promise<Routing>;
|
|
240
298
|
arm(opts: { destinations: Platform[] }): unknown;
|
|
241
299
|
goLive(opts: GoLiveParams): Promise<Job>;
|
|
242
300
|
stopLive(): Promise<unknown>;
|
|
243
301
|
refreshJob(): Promise<Job | null>;
|
|
244
302
|
sendChat(message: string, platforms?: Platform[]): Promise<unknown>;
|
|
245
303
|
updateOverlay(opts: OverlayUpdate): Promise<unknown>;
|
|
304
|
+
setStreamState(patch: StreamState): StreamState;
|
|
246
305
|
requestCollab(opts: RequestCollabParams): Promise<CollabInvite>;
|
|
247
306
|
acceptCollab(inviteId: string, opts: AcceptCollabParams): Promise<{ invite: CollabInvite; job: Job }>;
|
|
248
307
|
declineCollab(inviteId: string): Promise<CollabInvite>;
|
|
@@ -260,13 +319,18 @@ export function useConnections(opts: RestreamOptions): {
|
|
|
260
319
|
connect(platform: Platform): Promise<{ platform: Platform; closed?: boolean }>;
|
|
261
320
|
disconnect(platform: Platform): Promise<Connection[]>;
|
|
262
321
|
refresh(): Promise<Connection[]>;
|
|
263
|
-
listOAuthPlatforms(): Promise<
|
|
322
|
+
listOAuthPlatforms(): Promise<OAuthPlatformsResponse>;
|
|
264
323
|
listRestreamChannels(): Promise<RestreamChannel[]>;
|
|
265
324
|
restreamAddChannelUrl(platform?: string): string;
|
|
266
325
|
connectRestreamChannel(
|
|
267
326
|
platform: string,
|
|
268
327
|
opts?: { pollMs?: number; timeoutMs?: number }
|
|
269
328
|
): Promise<RestreamChannel | null>;
|
|
329
|
+
connectPlatform(
|
|
330
|
+
platform: string,
|
|
331
|
+
opts?: { pollMs?: number; timeoutMs?: number }
|
|
332
|
+
): Promise<{ platform: Platform; closed?: boolean } | RestreamChannel | null>;
|
|
333
|
+
loadRouting(): Promise<Routing>;
|
|
270
334
|
};
|
|
271
335
|
export function useLiveChat(studio: RestreamStudio): { comments: Comment[]; send(message: string, platforms?: Platform[]): Promise<unknown> };
|
|
272
336
|
export function useViewers(studio: RestreamStudio): ViewerCounts | null;
|