@stream-io/video-client 0.0.1-alpha.7
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/LICENSE +219 -0
- package/README.md +14 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +14663 -0
- package/dist/index.js.map +1 -0
- package/dist/src/Batcher.d.ts +12 -0
- package/dist/src/CallDropScheduler.d.ts +44 -0
- package/dist/src/StreamSfuClient.d.ts +25 -0
- package/dist/src/StreamVideoClient.d.ts +145 -0
- package/dist/src/__tests__/StreamVideoClient.test.d.ts +1 -0
- package/dist/src/config/defaultConfigs.d.ts +2 -0
- package/dist/src/config/types.d.ts +29 -0
- package/dist/src/coordinator/StreamCoordinatorClient.d.ts +19 -0
- package/dist/src/coordinator/connection/base64.d.ts +2 -0
- package/dist/src/coordinator/connection/client.d.ts +174 -0
- package/dist/src/coordinator/connection/connection.d.ts +139 -0
- package/dist/src/coordinator/connection/connection_fallback.d.ts +38 -0
- package/dist/src/coordinator/connection/errors.d.ts +16 -0
- package/dist/src/coordinator/connection/events.d.ts +7 -0
- package/dist/src/coordinator/connection/insights.d.ts +58 -0
- package/dist/src/coordinator/connection/signing.d.ts +30 -0
- package/dist/src/coordinator/connection/token_manager.d.ts +39 -0
- package/dist/src/coordinator/connection/types.d.ts +96 -0
- package/dist/src/coordinator/connection/utils.d.ts +25 -0
- package/dist/src/devices.d.ts +79 -0
- package/dist/src/events/call.d.ts +26 -0
- package/dist/src/events/internal.d.ts +8 -0
- package/dist/src/events/participant.d.ts +21 -0
- package/dist/src/events/speaker.d.ts +10 -0
- package/dist/src/gen/coordinator/index.d.ts +1664 -0
- package/dist/src/gen/google/protobuf/descriptor.d.ts +1650 -0
- package/dist/src/gen/google/protobuf/duration.d.ts +113 -0
- package/dist/src/gen/google/protobuf/struct.d.ts +184 -0
- package/dist/src/gen/google/protobuf/timestamp.d.ts +158 -0
- package/dist/src/gen/video/coordinator/broadcast_v1/broadcast.d.ts +66 -0
- package/dist/src/gen/video/coordinator/call_v1/call.d.ts +254 -0
- package/dist/src/gen/video/coordinator/client_v1_rpc/client_rpc.client.d.ts +351 -0
- package/dist/src/gen/video/coordinator/client_v1_rpc/client_rpc.d.ts +1488 -0
- package/dist/src/gen/video/coordinator/client_v1_rpc/envelopes.d.ts +143 -0
- package/dist/src/gen/video/coordinator/client_v1_rpc/websocket.d.ts +292 -0
- package/dist/src/gen/video/coordinator/edge_v1/edge.d.ts +183 -0
- package/dist/src/gen/video/coordinator/event_v1/event.d.ts +411 -0
- package/dist/src/gen/video/coordinator/geofence_v1/geofence.d.ts +63 -0
- package/dist/src/gen/video/coordinator/member_v1/member.d.ts +59 -0
- package/dist/src/gen/video/coordinator/participant_v1/participant.d.ts +103 -0
- package/dist/src/gen/video/coordinator/push_v1/push.d.ts +240 -0
- package/dist/src/gen/video/coordinator/stat_v1/stat.d.ts +308 -0
- package/dist/src/gen/video/coordinator/user_v1/user.d.ts +112 -0
- package/dist/src/gen/video/coordinator/utils_v1/utils.d.ts +47 -0
- package/dist/src/gen/video/sfu/event/events.d.ts +736 -0
- package/dist/src/gen/video/sfu/models/models.d.ts +460 -0
- package/dist/src/gen/video/sfu/signal_rpc/signal.client.d.ts +89 -0
- package/dist/src/gen/video/sfu/signal_rpc/signal.d.ts +320 -0
- package/dist/src/helpers/browsers.d.ts +8 -0
- package/dist/src/helpers/sound-detector.d.ts +34 -0
- package/dist/src/rpc/createClient.d.ts +10 -0
- package/dist/src/rpc/index.d.ts +2 -0
- package/dist/src/rpc/latency.d.ts +9 -0
- package/dist/src/rtc/Call.d.ts +180 -0
- package/dist/src/rtc/CallMetadata.d.ts +9 -0
- package/dist/src/rtc/Dispatcher.d.ts +9 -0
- package/dist/src/rtc/IceTrickleBuffer.d.ts +11 -0
- package/dist/src/rtc/callEventHandlers.d.ts +5 -0
- package/dist/src/rtc/codecs.d.ts +2 -0
- package/dist/src/rtc/helpers/iceCandidate.d.ts +2 -0
- package/dist/src/rtc/helpers/tracks.d.ts +3 -0
- package/dist/src/rtc/publisher.d.ts +53 -0
- package/dist/src/rtc/signal.d.ts +5 -0
- package/dist/src/rtc/subscriber.d.ts +7 -0
- package/dist/src/rtc/types.d.ts +84 -0
- package/dist/src/rtc/videoLayers.d.ts +17 -0
- package/dist/src/stats/coordinator-stats-reporter.d.ts +10 -0
- package/dist/src/stats/state-store-stats-reporter.d.ts +57 -0
- package/dist/src/stats/types.d.ts +42 -0
- package/dist/src/store/index.d.ts +2 -0
- package/dist/src/store/rxUtils.d.ts +18 -0
- package/dist/src/store/stateStore.d.ts +182 -0
- package/generate-openapi.sh +32 -0
- package/index.ts +30 -0
- package/openapitools.json +7 -0
- package/package.json +54 -0
- package/rollup.config.mjs +48 -0
- package/src/Batcher.ts +43 -0
- package/src/CallDropScheduler.ts +192 -0
- package/src/StreamSfuClient.ts +185 -0
- package/src/StreamVideoClient.ts +487 -0
- package/src/__tests__/StreamVideoClient.test.ts +83 -0
- package/src/config/defaultConfigs.ts +15 -0
- package/src/config/types.ts +30 -0
- package/src/coordinator/StreamCoordinatorClient.ts +111 -0
- package/src/coordinator/connection/base64.ts +80 -0
- package/src/coordinator/connection/client.ts +815 -0
- package/src/coordinator/connection/connection.ts +750 -0
- package/src/coordinator/connection/connection_fallback.ts +239 -0
- package/src/coordinator/connection/errors.ts +70 -0
- package/src/coordinator/connection/events.ts +10 -0
- package/src/coordinator/connection/insights.ts +88 -0
- package/src/coordinator/connection/signing.ts +104 -0
- package/src/coordinator/connection/token_manager.ts +160 -0
- package/src/coordinator/connection/types.ts +120 -0
- package/src/coordinator/connection/utils.ts +148 -0
- package/src/devices.ts +266 -0
- package/src/events/call.ts +166 -0
- package/src/events/internal.ts +47 -0
- package/src/events/participant.ts +97 -0
- package/src/events/speaker.ts +62 -0
- package/src/gen/coordinator/index.ts +1653 -0
- package/src/gen/google/protobuf/descriptor.ts +3466 -0
- package/src/gen/google/protobuf/duration.ts +232 -0
- package/src/gen/google/protobuf/struct.ts +481 -0
- package/src/gen/google/protobuf/timestamp.ts +291 -0
- package/src/gen/video/coordinator/broadcast_v1/broadcast.ts +154 -0
- package/src/gen/video/coordinator/call_v1/call.ts +651 -0
- package/src/gen/video/coordinator/client_v1_rpc/client_rpc.client.ts +463 -0
- package/src/gen/video/coordinator/client_v1_rpc/client_rpc.ts +3819 -0
- package/src/gen/video/coordinator/client_v1_rpc/envelopes.ts +424 -0
- package/src/gen/video/coordinator/client_v1_rpc/websocket.ts +719 -0
- package/src/gen/video/coordinator/edge_v1/edge.ts +532 -0
- package/src/gen/video/coordinator/event_v1/event.ts +1171 -0
- package/src/gen/video/coordinator/geofence_v1/geofence.ts +128 -0
- package/src/gen/video/coordinator/member_v1/member.ts +138 -0
- package/src/gen/video/coordinator/participant_v1/participant.ts +261 -0
- package/src/gen/video/coordinator/push_v1/push.ts +651 -0
- package/src/gen/video/coordinator/stat_v1/stat.ts +656 -0
- package/src/gen/video/coordinator/user_v1/user.ts +277 -0
- package/src/gen/video/coordinator/utils_v1/utils.ts +98 -0
- package/src/gen/video/sfu/event/events.ts +1962 -0
- package/src/gen/video/sfu/models/models.ts +1062 -0
- package/src/gen/video/sfu/signal_rpc/signal.client.ts +108 -0
- package/src/gen/video/sfu/signal_rpc/signal.ts +906 -0
- package/src/helpers/browsers.ts +13 -0
- package/src/helpers/sound-detector.ts +85 -0
- package/src/rpc/createClient.ts +50 -0
- package/src/rpc/index.ts +2 -0
- package/src/rpc/latency.ts +43 -0
- package/src/rtc/Call.ts +585 -0
- package/src/rtc/CallMetadata.ts +24 -0
- package/src/rtc/Dispatcher.ts +46 -0
- package/src/rtc/IceTrickleBuffer.ts +21 -0
- package/src/rtc/callEventHandlers.ts +37 -0
- package/src/rtc/codecs.ts +61 -0
- package/src/rtc/helpers/iceCandidate.ts +16 -0
- package/src/rtc/helpers/tracks.ts +18 -0
- package/src/rtc/publisher.ts +305 -0
- package/src/rtc/signal.ts +34 -0
- package/src/rtc/subscriber.ts +85 -0
- package/src/rtc/types.ts +105 -0
- package/src/rtc/videoLayers.ts +103 -0
- package/src/stats/coordinator-stats-reporter.ts +167 -0
- package/src/stats/state-store-stats-reporter.ts +364 -0
- package/src/stats/types.ts +46 -0
- package/src/store/index.ts +2 -0
- package/src/store/rxUtils.ts +42 -0
- package/src/store/stateStore.ts +341 -0
- package/tsconfig.json +25 -0
- package/typedoc.json +11 -0
- package/vite.config.ts +11 -0
package/src/Batcher.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export class Batcher<BatchItemType> {
|
|
2
|
+
private batch: Array<BatchItemType>;
|
|
3
|
+
private timeoutId: NodeJS.Timeout | undefined;
|
|
4
|
+
|
|
5
|
+
constructor(
|
|
6
|
+
private readonly timeoutInterval: number,
|
|
7
|
+
private readonly requestFunction: (data: Array<BatchItemType>) => void,
|
|
8
|
+
) {
|
|
9
|
+
this.batch = [];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
private scheduleTimeout = () => {
|
|
13
|
+
this.timeoutId = setTimeout(() => {
|
|
14
|
+
try {
|
|
15
|
+
if (!this.batch.length) return;
|
|
16
|
+
this.requestFunction(this.batch);
|
|
17
|
+
this.clearBatch();
|
|
18
|
+
} catch (error) {
|
|
19
|
+
console.error(error);
|
|
20
|
+
}
|
|
21
|
+
}, this.timeoutInterval);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
private clearTimeout = () => {
|
|
25
|
+
clearTimeout(this.timeoutId);
|
|
26
|
+
this.timeoutId = undefined;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
public clearBatch = () => {
|
|
30
|
+
this.batch = [];
|
|
31
|
+
this.clearTimeout();
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
public addToBatch = (item: BatchItemType) => {
|
|
35
|
+
this.batch.push(item);
|
|
36
|
+
if (!this.timeoutId) this.scheduleTimeout();
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
public removeFromBatch = (item: BatchItemType) => {
|
|
40
|
+
const itemIndex = this.batch.indexOf(item);
|
|
41
|
+
if (itemIndex > -1) this.batch.splice(itemIndex, 1);
|
|
42
|
+
};
|
|
43
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { StreamVideoWriteableStateStore } from './store';
|
|
2
|
+
import { CallConfig } from './config/types';
|
|
3
|
+
import { Observable, pairwise, startWith, Subscription } from 'rxjs';
|
|
4
|
+
import { CallMetadata } from './rtc/CallMetadata';
|
|
5
|
+
|
|
6
|
+
type CallCID = string;
|
|
7
|
+
type DropFunction = (callCid: CallCID) => Promise<void>;
|
|
8
|
+
|
|
9
|
+
export class CallDropScheduler {
|
|
10
|
+
private autoCallDropSchedule: Record<CallCID, ReturnType<typeof setTimeout>>;
|
|
11
|
+
private storeSubscriptions: { [key in keyof CallConfig]?: Subscription } & {
|
|
12
|
+
cancelDropOnPendingCallRemoval?: Subscription;
|
|
13
|
+
cancelDropOnCallAccepted?: Subscription;
|
|
14
|
+
};
|
|
15
|
+
private pairwisePendingCalls$: Observable<CallMetadata[][]>;
|
|
16
|
+
private pairwiseIncomingCalls$: Observable<CallMetadata[][]>;
|
|
17
|
+
private pairwiseOutgoingCalls$: Observable<CallMetadata[][]>;
|
|
18
|
+
constructor(
|
|
19
|
+
private store: StreamVideoWriteableStateStore,
|
|
20
|
+
private callConfig: CallConfig,
|
|
21
|
+
private reject: DropFunction,
|
|
22
|
+
private cancel: DropFunction,
|
|
23
|
+
) {
|
|
24
|
+
this.pairwisePendingCalls$ = store.pendingCallsSubject.pipe(
|
|
25
|
+
startWith([]),
|
|
26
|
+
pairwise(),
|
|
27
|
+
);
|
|
28
|
+
this.pairwiseIncomingCalls$ = store.incomingCalls$.pipe(
|
|
29
|
+
startWith([]),
|
|
30
|
+
pairwise(),
|
|
31
|
+
);
|
|
32
|
+
this.pairwiseOutgoingCalls$ = store.outgoingCalls$.pipe(
|
|
33
|
+
startWith([]),
|
|
34
|
+
pairwise(),
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
this.autoCallDropSchedule = {};
|
|
38
|
+
this.storeSubscriptions = {};
|
|
39
|
+
|
|
40
|
+
this.startAutoRejectWhenInCall();
|
|
41
|
+
this.scheduleRejectAfterTimeout();
|
|
42
|
+
this.scheduleCancelAfterTimeout();
|
|
43
|
+
this.startCancellingDrops();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
private static getLatestCall = (
|
|
47
|
+
from: CallMetadata[],
|
|
48
|
+
compareTo: CallMetadata[],
|
|
49
|
+
) => {
|
|
50
|
+
return from > compareTo ? from.slice(-1)[0] : undefined;
|
|
51
|
+
};
|
|
52
|
+
private startAutoRejectWhenInCall = () => {
|
|
53
|
+
if (!this.callConfig.autoRejectWhenInCall) return;
|
|
54
|
+
|
|
55
|
+
this.storeSubscriptions.autoRejectWhenInCall =
|
|
56
|
+
this.pairwiseIncomingCalls$.subscribe(
|
|
57
|
+
async ([prevCalls, currentCalls]) => {
|
|
58
|
+
const newIncomingCall = CallDropScheduler.getLatestCall(
|
|
59
|
+
currentCalls,
|
|
60
|
+
prevCalls,
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
if (!newIncomingCall) return;
|
|
64
|
+
|
|
65
|
+
const activeCall = this.store.getCurrentValue(
|
|
66
|
+
this.store.activeCallSubject,
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
if (activeCall && newIncomingCall?.call.cid) {
|
|
70
|
+
await this.reject(newIncomingCall.call.cid);
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
private scheduleRejectAfterTimeout = () => {
|
|
77
|
+
const { autoRejectTimeoutInMs } = this.callConfig;
|
|
78
|
+
if (typeof autoRejectTimeoutInMs !== 'number' || autoRejectTimeoutInMs < 0)
|
|
79
|
+
return;
|
|
80
|
+
|
|
81
|
+
this.storeSubscriptions.autoRejectTimeoutInMs =
|
|
82
|
+
this.pairwiseIncomingCalls$.subscribe(([prevCalls, currentCalls]) => {
|
|
83
|
+
const newIncomingCall = CallDropScheduler.getLatestCall(
|
|
84
|
+
currentCalls,
|
|
85
|
+
prevCalls,
|
|
86
|
+
);
|
|
87
|
+
if (!newIncomingCall?.call.cid) return;
|
|
88
|
+
|
|
89
|
+
const activeCall = this.store.getCurrentValue(
|
|
90
|
+
this.store.activeCallSubject,
|
|
91
|
+
);
|
|
92
|
+
const incomingCallRejectedImmediately =
|
|
93
|
+
activeCall && this.callConfig.autoRejectWhenInCall;
|
|
94
|
+
if (incomingCallRejectedImmediately) return;
|
|
95
|
+
|
|
96
|
+
this.scheduleReject(newIncomingCall.call.cid);
|
|
97
|
+
});
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
private scheduleCancelAfterTimeout = () => {
|
|
101
|
+
const { autoCancelTimeoutInMs } = this.callConfig;
|
|
102
|
+
if (typeof autoCancelTimeoutInMs !== 'number' || autoCancelTimeoutInMs < 0)
|
|
103
|
+
return;
|
|
104
|
+
|
|
105
|
+
this.storeSubscriptions.autoCancelTimeoutInMs =
|
|
106
|
+
this.pairwiseOutgoingCalls$.subscribe(([prevCalls, currentCalls]) => {
|
|
107
|
+
const newOutgoingCall = CallDropScheduler.getLatestCall(
|
|
108
|
+
currentCalls,
|
|
109
|
+
prevCalls,
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
if (!newOutgoingCall?.call.cid) return;
|
|
113
|
+
this.scheduleCancel(newOutgoingCall.call.cid);
|
|
114
|
+
});
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
private startCancellingDrops = () => {
|
|
118
|
+
this.storeSubscriptions.cancelDropOnPendingCallRemoval =
|
|
119
|
+
this.pairwisePendingCalls$.subscribe(([prevCalls, currentCalls]) => {
|
|
120
|
+
const removedCall = CallDropScheduler.getLatestCall(
|
|
121
|
+
prevCalls,
|
|
122
|
+
currentCalls,
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
if (removedCall?.call.cid) {
|
|
126
|
+
this.cancelDrop(removedCall?.call.cid);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
this.storeSubscriptions.cancelDropOnCallAccepted =
|
|
131
|
+
this.store.acceptedCallSubject.subscribe((acceptedCall) => {
|
|
132
|
+
if (acceptedCall?.call_cid) {
|
|
133
|
+
this.cancelDrop(acceptedCall.call_cid);
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Schedules automatic call cancellation.
|
|
140
|
+
* The cancellation is intended for the scenarios, when the call has been rejected
|
|
141
|
+
* or not accepted by all the call members.
|
|
142
|
+
* @param {string} callCid
|
|
143
|
+
*/
|
|
144
|
+
private scheduleCancel = (callCid: string) => {
|
|
145
|
+
const timeout = this.callConfig.autoCancelTimeoutInMs;
|
|
146
|
+
if (!timeout) return;
|
|
147
|
+
this.autoCallDropSchedule[callCid] = setTimeout(() => {
|
|
148
|
+
console.log('Automatic call cancellation after timeout', timeout);
|
|
149
|
+
this.cancel(callCid);
|
|
150
|
+
}, timeout);
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Schedules automatic call rejection.
|
|
155
|
+
* @param {string} callCid
|
|
156
|
+
*/
|
|
157
|
+
private scheduleReject = (callCid: string) => {
|
|
158
|
+
const timeout = this.callConfig.autoRejectTimeoutInMs;
|
|
159
|
+
if (!timeout) return;
|
|
160
|
+
this.autoCallDropSchedule[callCid] = setTimeout(() => {
|
|
161
|
+
console.log('Automatic call rejection after timeout', timeout);
|
|
162
|
+
this.reject(callCid);
|
|
163
|
+
}, timeout);
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Cancels the automatic call drop (rejection of an incoming call / cancellation of an outgoing call).
|
|
168
|
+
* Indented for the scenario, when:
|
|
169
|
+
* - an incoming call has been cancelled
|
|
170
|
+
* - an outgoing call has been accepted by at least one member
|
|
171
|
+
* - an outgoing call has been rejected / left by all the members resp. participants
|
|
172
|
+
* - a call has been cancelled / rejected / accepted manually before the cancellation timeout expires.
|
|
173
|
+
*/
|
|
174
|
+
private cancelDrop = (callCid: string) => {
|
|
175
|
+
const { [callCid]: timeoutRef, ...rest } = this.autoCallDropSchedule;
|
|
176
|
+
if (timeoutRef !== undefined) {
|
|
177
|
+
console.log(`Cancelling automatic call drop, [callCID: ${callCid}]`);
|
|
178
|
+
clearTimeout(timeoutRef);
|
|
179
|
+
this.autoCallDropSchedule = rest;
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
cleanUp = () => {
|
|
184
|
+
Object.values(this.autoCallDropSchedule).forEach((timeoutRef) =>
|
|
185
|
+
clearTimeout(timeoutRef),
|
|
186
|
+
);
|
|
187
|
+
this.autoCallDropSchedule = {};
|
|
188
|
+
Object.values(this.storeSubscriptions).forEach((subscription) =>
|
|
189
|
+
subscription.unsubscribe(),
|
|
190
|
+
);
|
|
191
|
+
};
|
|
192
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { SignalServerClient } from './gen/video/sfu/signal_rpc/signal.client';
|
|
2
|
+
import { createSignalClient, withHeaders } from './rpc';
|
|
3
|
+
import { createWebSocketSignalChannel } from './rtc/signal';
|
|
4
|
+
import { JoinRequest, SfuRequest } from './gen/video/sfu/event/events';
|
|
5
|
+
import { Dispatcher } from './rtc/Dispatcher';
|
|
6
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
7
|
+
import { IceTrickleBuffer } from './rtc/IceTrickleBuffer';
|
|
8
|
+
import {
|
|
9
|
+
SendAnswerRequest,
|
|
10
|
+
SetPublisherRequest,
|
|
11
|
+
TrackSubscriptionDetails,
|
|
12
|
+
UpdateMuteStatesRequest,
|
|
13
|
+
} from './gen/video/sfu/signal_rpc/signal';
|
|
14
|
+
import { ICETrickle, TrackType } from './gen/video/sfu/models/models';
|
|
15
|
+
|
|
16
|
+
const hostnameFromUrl = (url: string) => {
|
|
17
|
+
try {
|
|
18
|
+
return new URL(url).hostname;
|
|
19
|
+
} catch (e) {
|
|
20
|
+
console.warn(`Invalid URL. Can't extract hostname from it.`, e);
|
|
21
|
+
return url;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const toURL = (url: string) => {
|
|
26
|
+
try {
|
|
27
|
+
return new URL(url);
|
|
28
|
+
} catch (e) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export class StreamSfuClient {
|
|
34
|
+
readonly dispatcher = new Dispatcher();
|
|
35
|
+
readonly iceTrickleBuffer = new IceTrickleBuffer();
|
|
36
|
+
// we generate uuid session id client side
|
|
37
|
+
readonly sessionId: string;
|
|
38
|
+
private readonly rpc: SignalServerClient;
|
|
39
|
+
// Current JWT token
|
|
40
|
+
private readonly token: string;
|
|
41
|
+
signalReady: Promise<WebSocket>;
|
|
42
|
+
private keepAliveInterval?: NodeJS.Timeout;
|
|
43
|
+
|
|
44
|
+
constructor(url: string, token: string) {
|
|
45
|
+
this.sessionId = uuidv4();
|
|
46
|
+
this.token = token;
|
|
47
|
+
this.rpc = createSignalClient({
|
|
48
|
+
baseUrl: url,
|
|
49
|
+
interceptors: [
|
|
50
|
+
withHeaders({
|
|
51
|
+
Authorization: `Bearer ${token}`,
|
|
52
|
+
}),
|
|
53
|
+
],
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// FIXME: OL: this should come from the coordinator API
|
|
57
|
+
const sfuHost = hostnameFromUrl(url);
|
|
58
|
+
let wsEndpoint = `ws://${sfuHost}:3031/ws`;
|
|
59
|
+
if (!['localhost', '127.0.0.1'].includes(sfuHost)) {
|
|
60
|
+
const sfuUrl = toURL(url);
|
|
61
|
+
if (sfuUrl) {
|
|
62
|
+
sfuUrl.protocol = 'wss:';
|
|
63
|
+
sfuUrl.pathname = '/ws';
|
|
64
|
+
wsEndpoint = sfuUrl.toString();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Special handling for the ICETrickle kind of events.
|
|
69
|
+
// These events might be triggered by the SFU before the initial RTC
|
|
70
|
+
// connection is established. In that case, those events (ICE candidates)
|
|
71
|
+
// need to be buffered and later added to the appropriate PeerConnection
|
|
72
|
+
// once the remoteDescription is known and set.
|
|
73
|
+
this.dispatcher.on('iceTrickle', (e) => {
|
|
74
|
+
if (e.eventPayload.oneofKind !== 'iceTrickle') return;
|
|
75
|
+
const { iceTrickle } = e.eventPayload;
|
|
76
|
+
this.iceTrickleBuffer.push(iceTrickle);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
this.signalReady = createWebSocketSignalChannel({
|
|
80
|
+
endpoint: wsEndpoint,
|
|
81
|
+
onMessage: (message) => {
|
|
82
|
+
this.dispatcher.dispatch(message);
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
close = () => {
|
|
88
|
+
this.signalReady.then((ws) => {
|
|
89
|
+
this.signalReady = Promise.reject('Connection closed');
|
|
90
|
+
// TODO OL: re-connect flow
|
|
91
|
+
ws.close(1000, 'Requested signal connection close');
|
|
92
|
+
|
|
93
|
+
this.dispatcher.offAll();
|
|
94
|
+
clearInterval(this.keepAliveInterval);
|
|
95
|
+
});
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
updateSubscriptions = async (subscriptions: TrackSubscriptionDetails[]) => {
|
|
99
|
+
return this.rpc.updateSubscriptions({
|
|
100
|
+
sessionId: this.sessionId,
|
|
101
|
+
tracks: subscriptions,
|
|
102
|
+
});
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
setPublisher = async (data: Omit<SetPublisherRequest, 'sessionId'>) => {
|
|
106
|
+
return this.rpc.setPublisher({
|
|
107
|
+
...data,
|
|
108
|
+
sessionId: this.sessionId,
|
|
109
|
+
});
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
sendAnswer = async (data: Omit<SendAnswerRequest, 'sessionId'>) => {
|
|
113
|
+
return this.rpc.sendAnswer({
|
|
114
|
+
...data,
|
|
115
|
+
sessionId: this.sessionId,
|
|
116
|
+
});
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
iceTrickle = async (data: Omit<ICETrickle, 'sessionId'>) => {
|
|
120
|
+
return this.rpc.iceTrickle({
|
|
121
|
+
...data,
|
|
122
|
+
sessionId: this.sessionId,
|
|
123
|
+
});
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
updateMuteState = async (trackType: TrackType, muted: boolean) => {
|
|
127
|
+
return this.updateMuteStates({
|
|
128
|
+
muteStates: [
|
|
129
|
+
{
|
|
130
|
+
trackType,
|
|
131
|
+
muted,
|
|
132
|
+
},
|
|
133
|
+
],
|
|
134
|
+
});
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
updateMuteStates = async (
|
|
138
|
+
data: Omit<UpdateMuteStatesRequest, 'sessionId'>,
|
|
139
|
+
) => {
|
|
140
|
+
return this.rpc.updateMuteStates({
|
|
141
|
+
...data,
|
|
142
|
+
sessionId: this.sessionId,
|
|
143
|
+
});
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
join = async (data: Omit<JoinRequest, 'sessionId' | 'token'>) => {
|
|
147
|
+
const joinRequest = JoinRequest.create({
|
|
148
|
+
...data,
|
|
149
|
+
sessionId: this.sessionId,
|
|
150
|
+
token: this.token,
|
|
151
|
+
});
|
|
152
|
+
return this.send(
|
|
153
|
+
SfuRequest.create({
|
|
154
|
+
requestPayload: {
|
|
155
|
+
oneofKind: 'joinRequest',
|
|
156
|
+
joinRequest,
|
|
157
|
+
},
|
|
158
|
+
}),
|
|
159
|
+
);
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
send = (message: SfuRequest) => {
|
|
163
|
+
return this.signalReady.then((signal) => {
|
|
164
|
+
signal.send(SfuRequest.toBinary(message));
|
|
165
|
+
});
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
// FIXME: make this private
|
|
169
|
+
async keepAlive() {
|
|
170
|
+
await this.signalReady;
|
|
171
|
+
console.log('Registering healthcheck for SFU');
|
|
172
|
+
this.keepAliveInterval = setInterval(() => {
|
|
173
|
+
const message = SfuRequest.create({
|
|
174
|
+
requestPayload: {
|
|
175
|
+
oneofKind: 'healthCheckRequest',
|
|
176
|
+
healthCheckRequest: {
|
|
177
|
+
sessionId: this.sessionId,
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
console.log('Sending healthCheckRequest to SFU', message);
|
|
182
|
+
this.send(message);
|
|
183
|
+
}, 27000);
|
|
184
|
+
}
|
|
185
|
+
}
|