restream-sdk 0.3.0 → 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 +1 -1
- package/src/core.js +51 -1
- package/src/react.js +1 -0
- package/types/index.d.ts +29 -0
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",
|
package/src/core.js
CHANGED
|
@@ -44,6 +44,14 @@ export class RestreamStudio extends EventTarget {
|
|
|
44
44
|
// connect helpers fall back to the static defaults above while it's null.
|
|
45
45
|
this._routing = null;
|
|
46
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
|
+
|
|
47
55
|
this._token = null;
|
|
48
56
|
this._tokenExp = 0;
|
|
49
57
|
this._es = null;
|
|
@@ -385,7 +393,7 @@ export class RestreamStudio extends EventTarget {
|
|
|
385
393
|
* Restream channels. Any field omitted falls back to the
|
|
386
394
|
* default pre-set for this creator on the platform.
|
|
387
395
|
*/
|
|
388
|
-
async goLive({ publisherSessionId, destinations, overlay, recording, trackNames, restreamChannels, streamMeta, destinationMeta, streamingProvider } = {}) {
|
|
396
|
+
async goLive({ publisherSessionId, destinations, overlay, recording, trackNames, restreamChannels, streamMeta, destinationMeta, streamingProvider, layout } = {}) {
|
|
389
397
|
if (!publisherSessionId) throw new Error("publisherSessionId is required (from your existing stream session)");
|
|
390
398
|
const body = {
|
|
391
399
|
publisherSessionId,
|
|
@@ -418,6 +426,13 @@ export class RestreamStudio extends EventTarget {
|
|
|
418
426
|
this.job = job;
|
|
419
427
|
this._emit("job", job);
|
|
420
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
|
+
}
|
|
421
436
|
return job;
|
|
422
437
|
}
|
|
423
438
|
|
|
@@ -425,6 +440,7 @@ export class RestreamStudio extends EventTarget {
|
|
|
425
440
|
if (!this.job?.id) return null;
|
|
426
441
|
const r = await this._req("DELETE", `/api/v1/jobs/${this.job.id}`);
|
|
427
442
|
this.stopRealtime();
|
|
443
|
+
if (this._stateTimer) { clearTimeout(this._stateTimer); this._stateTimer = null; } // drop any pending state POST
|
|
428
444
|
this.job = { ...this.job, status: "stopping" };
|
|
429
445
|
this._emit("job", this.job);
|
|
430
446
|
return r;
|
|
@@ -447,6 +463,40 @@ export class RestreamStudio extends EventTarget {
|
|
|
447
463
|
return this._req("PATCH", `/api/v1/jobs/${this.job.id}/overlay`, opts);
|
|
448
464
|
}
|
|
449
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
|
+
|
|
450
500
|
// ---- realtime (chat SSE + job-status/viewer polling) ----
|
|
451
501
|
async startRealtime(jobId) {
|
|
452
502
|
this.stopRealtime();
|
package/src/react.js
CHANGED
|
@@ -57,6 +57,7 @@ export function useRestream(opts) {
|
|
|
57
57
|
refreshJob: useCallback(() => studio.refreshJob(), [studio]),
|
|
58
58
|
sendChat: useCallback((m, p) => studio.sendChat(m, p), [studio]),
|
|
59
59
|
updateOverlay: useCallback((o) => studio.updateOverlay(o), [studio]),
|
|
60
|
+
setStreamState: useCallback((s) => studio.setStreamState(s), [studio]),
|
|
60
61
|
// collaborative split-screen
|
|
61
62
|
requestCollab: useCallback((o) => studio.requestCollab(o), [studio]),
|
|
62
63
|
acceptCollab: useCallback((id, o) => studio.acceptCollab(id, o), [studio]),
|
package/types/index.d.ts
CHANGED
|
@@ -102,6 +102,8 @@ export interface GoLiveParams {
|
|
|
102
102
|
* default → the creator's pre-set global default → the platform's existing value.
|
|
103
103
|
*/
|
|
104
104
|
destinationMeta?: DestinationMeta;
|
|
105
|
+
/** Initial presence/layout state for the stream (same shape as setStreamState). */
|
|
106
|
+
layout?: StreamState;
|
|
105
107
|
}
|
|
106
108
|
|
|
107
109
|
export interface StreamMeta {
|
|
@@ -113,6 +115,26 @@ export interface StreamMeta {
|
|
|
113
115
|
/** Per-platform stream metadata overrides, keyed by platform name. */
|
|
114
116
|
export type DestinationMeta = Partial<Record<Platform, StreamMeta>> & Record<string, StreamMeta>;
|
|
115
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
|
+
|
|
116
138
|
export interface OverlayUpdate {
|
|
117
139
|
topBanner?: string;
|
|
118
140
|
bottomBanner?: string;
|
|
@@ -190,6 +212,7 @@ export class RestreamStudio extends EventTarget {
|
|
|
190
212
|
on(type: "comment", cb: (c: Comment) => void): () => void;
|
|
191
213
|
on(type: "comments", cb: (c: Comment[]) => void): () => void;
|
|
192
214
|
on(type: "error", cb: (e: Error) => void): () => void;
|
|
215
|
+
on(type: "state", cb: (s: StreamState) => void): () => void;
|
|
193
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;
|
|
194
217
|
listConnections(): Promise<Connection[]>;
|
|
195
218
|
listOAuthPlatforms(): Promise<OAuthPlatformsResponse>;
|
|
@@ -225,6 +248,11 @@ export class RestreamStudio extends EventTarget {
|
|
|
225
248
|
refreshJob(): Promise<Job | null>;
|
|
226
249
|
sendChat(message: string, platforms?: Platform[]): Promise<unknown>;
|
|
227
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;
|
|
228
256
|
startRealtime(jobId: string): Promise<void>;
|
|
229
257
|
stopRealtime(): void;
|
|
230
258
|
// collaborative split-screen
|
|
@@ -273,6 +301,7 @@ export interface UseRestream {
|
|
|
273
301
|
refreshJob(): Promise<Job | null>;
|
|
274
302
|
sendChat(message: string, platforms?: Platform[]): Promise<unknown>;
|
|
275
303
|
updateOverlay(opts: OverlayUpdate): Promise<unknown>;
|
|
304
|
+
setStreamState(patch: StreamState): StreamState;
|
|
276
305
|
requestCollab(opts: RequestCollabParams): Promise<CollabInvite>;
|
|
277
306
|
acceptCollab(inviteId: string, opts: AcceptCollabParams): Promise<{ invite: CollabInvite; job: Job }>;
|
|
278
307
|
declineCollab(inviteId: string): Promise<CollabInvite>;
|