@stream-io/video-client 0.0.1-alpha.82 → 0.0.1-alpha.83
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/dist/index.browser.es.js +425 -247
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +423 -245
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +425 -247
- package/dist/index.es.js.map +1 -1
- package/dist/src/StreamSfuClient.d.ts +26 -4
- package/dist/src/rtc/Call.d.ts +4 -4
- package/dist/src/rtc/publisher.d.ts +7 -2
- package/dist/src/rtc/signal.d.ts +1 -1
- package/dist/src/rtc/subscriber.d.ts +3 -1
- package/dist/src/store/CallState.d.ts +43 -0
- package/package.json +1 -1
- package/src/StreamSfuClient.ts +70 -27
- package/src/coordinator/connection/connection.ts +2 -1
- package/src/coordinator/connection/utils.ts +3 -3
- package/src/rtc/Call.ts +167 -47
- package/src/rtc/publisher.ts +39 -25
- package/src/rtc/signal.ts +23 -23
- package/src/rtc/subscriber.ts +39 -23
- package/src/store/CallState.ts +54 -0
package/dist/index.browser.es.js
CHANGED
|
@@ -3,7 +3,7 @@ import { MessageType, isJsonObject, typeofJsonValue, reflectionMergePartial, Unk
|
|
|
3
3
|
import { TwirpFetchTransport } from '@protobuf-ts/twirp-transport';
|
|
4
4
|
import { ServiceType, stackIntercept } from '@protobuf-ts/runtime-rpc';
|
|
5
5
|
import { v4 } from 'uuid';
|
|
6
|
-
import { ReplaySubject, BehaviorSubject, Subject, takeWhile,
|
|
6
|
+
import { ReplaySubject, BehaviorSubject, Subject, takeWhile, debounceTime, startWith, pairwise, Observable, concatMap, from, shareReplay, merge, map as map$2, combineLatest, filter, firstValueFrom } from 'rxjs';
|
|
7
7
|
import { take, combineLatestWith, map as map$1, distinctUntilChanged } from 'rxjs/operators';
|
|
8
8
|
import axios, { AxiosHeaders } from 'axios';
|
|
9
9
|
import WebSocket$1 from 'isomorphic-ws';
|
|
@@ -3821,28 +3821,27 @@ const createSignalClient = (options) => {
|
|
|
3821
3821
|
};
|
|
3822
3822
|
|
|
3823
3823
|
const createWebSocketSignalChannel = (opts) => {
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
console.error('Error', e);
|
|
3833
|
-
});
|
|
3834
|
-
ws.addEventListener('close', (e) => {
|
|
3835
|
-
console.warn('Signalling channel is closed', e);
|
|
3836
|
-
});
|
|
3837
|
-
if (onMessage) {
|
|
3838
|
-
ws.addEventListener('message', (e) => {
|
|
3839
|
-
const message = e.data instanceof ArrayBuffer
|
|
3840
|
-
? SfuEvent.fromBinary(new Uint8Array(e.data))
|
|
3841
|
-
: SfuEvent.fromJsonString(e.data);
|
|
3842
|
-
onMessage(message);
|
|
3843
|
-
});
|
|
3844
|
-
}
|
|
3824
|
+
const { endpoint, onMessage } = opts;
|
|
3825
|
+
const ws = new WebSocket(endpoint);
|
|
3826
|
+
ws.binaryType = 'arraybuffer'; // do we need this?
|
|
3827
|
+
ws.addEventListener('error', (e) => {
|
|
3828
|
+
console.error('Error', e);
|
|
3829
|
+
});
|
|
3830
|
+
ws.addEventListener('close', (e) => {
|
|
3831
|
+
console.warn('Signalling channel is closed', e);
|
|
3845
3832
|
});
|
|
3833
|
+
ws.addEventListener('open', (e) => {
|
|
3834
|
+
console.log('Signalling channel is open', e);
|
|
3835
|
+
});
|
|
3836
|
+
if (onMessage) {
|
|
3837
|
+
ws.addEventListener('message', (e) => {
|
|
3838
|
+
const message = e.data instanceof ArrayBuffer
|
|
3839
|
+
? SfuEvent.fromBinary(new Uint8Array(e.data))
|
|
3840
|
+
: SfuEvent.fromJsonString(e.data);
|
|
3841
|
+
onMessage(message);
|
|
3842
|
+
});
|
|
3843
|
+
}
|
|
3844
|
+
return ws;
|
|
3846
3845
|
};
|
|
3847
3846
|
|
|
3848
3847
|
/**
|
|
@@ -3891,17 +3890,23 @@ const toURL = (url) => {
|
|
|
3891
3890
|
return null;
|
|
3892
3891
|
}
|
|
3893
3892
|
};
|
|
3893
|
+
/**
|
|
3894
|
+
* The client used for exchanging information with the SFU.
|
|
3895
|
+
*/
|
|
3894
3896
|
class StreamSfuClient {
|
|
3895
3897
|
constructor(dispatcher, url, token) {
|
|
3898
|
+
/**
|
|
3899
|
+
* A buffer for ICE Candidates that are received before
|
|
3900
|
+
* the PeerConnections are ready to handle them.
|
|
3901
|
+
*/
|
|
3896
3902
|
this.iceTrickleBuffer = new IceTrickleBuffer();
|
|
3897
|
-
this.
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
});
|
|
3903
|
+
this.pingIntervalInMs = 25 * 1000;
|
|
3904
|
+
this.unhealthyTimeoutInMs = this.pingIntervalInMs + 5 * 1000;
|
|
3905
|
+
this.close = (code = 1000) => {
|
|
3906
|
+
this.signalWs.close(code, 'Requested signal connection close');
|
|
3907
|
+
this.unsubscribeIceTrickle();
|
|
3908
|
+
clearInterval(this.keepAliveInterval);
|
|
3909
|
+
clearTimeout(this.connectionCheckTimeout);
|
|
3905
3910
|
};
|
|
3906
3911
|
this.updateSubscriptions = (subscriptions) => __awaiter(this, void 0, void 0, function* () {
|
|
3907
3912
|
return this.rpc.updateSubscriptions({
|
|
@@ -3945,7 +3950,35 @@ class StreamSfuClient {
|
|
|
3945
3950
|
signal.send(SfuRequest.toBinary(message));
|
|
3946
3951
|
});
|
|
3947
3952
|
};
|
|
3948
|
-
this.
|
|
3953
|
+
this.keepAlive = () => {
|
|
3954
|
+
if (this.keepAliveInterval) {
|
|
3955
|
+
clearInterval(this.keepAliveInterval);
|
|
3956
|
+
}
|
|
3957
|
+
this.keepAliveInterval = setInterval(() => {
|
|
3958
|
+
console.log('Sending healthCheckRequest to SFU');
|
|
3959
|
+
const message = SfuRequest.create({
|
|
3960
|
+
requestPayload: {
|
|
3961
|
+
oneofKind: 'healthCheckRequest',
|
|
3962
|
+
healthCheckRequest: {},
|
|
3963
|
+
},
|
|
3964
|
+
});
|
|
3965
|
+
void this.send(message);
|
|
3966
|
+
}, this.pingIntervalInMs);
|
|
3967
|
+
};
|
|
3968
|
+
this.scheduleConnectionCheck = () => {
|
|
3969
|
+
if (this.connectionCheckTimeout) {
|
|
3970
|
+
clearTimeout(this.connectionCheckTimeout);
|
|
3971
|
+
}
|
|
3972
|
+
this.connectionCheckTimeout = setTimeout(() => {
|
|
3973
|
+
if (this.lastMessageTimestamp) {
|
|
3974
|
+
const timeSinceLastMessage = new Date().getTime() - this.lastMessageTimestamp.getTime();
|
|
3975
|
+
if (timeSinceLastMessage > this.unhealthyTimeoutInMs) {
|
|
3976
|
+
console.log('SFU connection unhealthy, closing');
|
|
3977
|
+
this.close(4001);
|
|
3978
|
+
}
|
|
3979
|
+
}
|
|
3980
|
+
}, this.unhealthyTimeoutInMs);
|
|
3981
|
+
};
|
|
3949
3982
|
this.sessionId = v4();
|
|
3950
3983
|
this.token = token;
|
|
3951
3984
|
this.rpc = createSignalClient({
|
|
@@ -3972,36 +4005,25 @@ class StreamSfuClient {
|
|
|
3972
4005
|
// connection is established. In that case, those events (ICE candidates)
|
|
3973
4006
|
// need to be buffered and later added to the appropriate PeerConnection
|
|
3974
4007
|
// once the remoteDescription is known and set.
|
|
3975
|
-
this.dispatcher.on('iceTrickle', (e) => {
|
|
4008
|
+
this.unsubscribeIceTrickle = dispatcher.on('iceTrickle', (e) => {
|
|
3976
4009
|
if (e.eventPayload.oneofKind !== 'iceTrickle')
|
|
3977
4010
|
return;
|
|
3978
4011
|
const { iceTrickle } = e.eventPayload;
|
|
3979
4012
|
this.iceTrickleBuffer.push(iceTrickle);
|
|
3980
4013
|
});
|
|
3981
|
-
this.
|
|
4014
|
+
this.signalWs = createWebSocketSignalChannel({
|
|
3982
4015
|
endpoint: wsEndpoint,
|
|
3983
4016
|
onMessage: (message) => {
|
|
3984
|
-
this.
|
|
4017
|
+
this.lastMessageTimestamp = new Date();
|
|
4018
|
+
this.scheduleConnectionCheck();
|
|
4019
|
+
dispatcher.dispatch(message);
|
|
3985
4020
|
},
|
|
3986
4021
|
});
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
console.log('Registering healthcheck for SFU');
|
|
3993
|
-
this.keepAliveInterval = setInterval(() => {
|
|
3994
|
-
const message = SfuRequest.create({
|
|
3995
|
-
requestPayload: {
|
|
3996
|
-
oneofKind: 'healthCheckRequest',
|
|
3997
|
-
healthCheckRequest: {
|
|
3998
|
-
sessionId: this.sessionId,
|
|
3999
|
-
},
|
|
4000
|
-
},
|
|
4001
|
-
});
|
|
4002
|
-
console.log('Sending healthCheckRequest to SFU', message);
|
|
4003
|
-
this.send(message);
|
|
4004
|
-
}, 27000);
|
|
4022
|
+
this.signalReady = new Promise((resolve) => {
|
|
4023
|
+
this.signalWs.addEventListener('open', () => {
|
|
4024
|
+
this.keepAlive();
|
|
4025
|
+
resolve(this.signalWs);
|
|
4026
|
+
});
|
|
4005
4027
|
});
|
|
4006
4028
|
}
|
|
4007
4029
|
}
|
|
@@ -4019,8 +4041,9 @@ function getIceCandidate(candidate) {
|
|
|
4019
4041
|
}
|
|
4020
4042
|
}
|
|
4021
4043
|
|
|
4022
|
-
const createSubscriber = ({ rpcClient, connectionConfig, onTrack, }) => {
|
|
4044
|
+
const createSubscriber = ({ rpcClient, dispatcher, connectionConfig, onTrack, }) => {
|
|
4023
4045
|
const subscriber = new RTCPeerConnection(connectionConfig);
|
|
4046
|
+
attachDebugEventListeners(subscriber);
|
|
4024
4047
|
subscriber.addEventListener('icecandidate', (e) => __awaiter(void 0, void 0, void 0, function* () {
|
|
4025
4048
|
const { candidate } = e;
|
|
4026
4049
|
if (!candidate) {
|
|
@@ -4032,22 +4055,11 @@ const createSubscriber = ({ rpcClient, connectionConfig, onTrack, }) => {
|
|
|
4032
4055
|
peerType: PeerType.SUBSCRIBER,
|
|
4033
4056
|
});
|
|
4034
4057
|
}));
|
|
4035
|
-
subscriber.addEventListener('icecandidateerror', (e) => {
|
|
4036
|
-
const errorMessage = e instanceof RTCPeerConnectionIceErrorEvent &&
|
|
4037
|
-
`${e.errorCode}: ${e.errorText}`;
|
|
4038
|
-
console.error(`Subscriber: ICE Candidate error`, errorMessage, e);
|
|
4039
|
-
});
|
|
4040
|
-
subscriber.addEventListener('iceconnectionstatechange', (e) => {
|
|
4041
|
-
console.log(`Subscriber: ICE Connection state changed`, subscriber.iceConnectionState, e);
|
|
4042
|
-
});
|
|
4043
|
-
subscriber.addEventListener('icegatheringstatechange', (e) => {
|
|
4044
|
-
console.log(`Subscriber: ICE Gathering State`, subscriber.iceGatheringState, e);
|
|
4045
|
-
});
|
|
4046
4058
|
if (onTrack) {
|
|
4047
4059
|
subscriber.addEventListener('track', onTrack);
|
|
4048
4060
|
}
|
|
4049
|
-
const {
|
|
4050
|
-
dispatcher.on('subscriberOffer', (message) => __awaiter(void 0, void 0, void 0, function* () {
|
|
4061
|
+
const { iceTrickleBuffer } = rpcClient;
|
|
4062
|
+
const unsubscribe = dispatcher.on('subscriberOffer', (message) => __awaiter(void 0, void 0, void 0, function* () {
|
|
4051
4063
|
if (message.eventPayload.oneofKind !== 'subscriberOffer')
|
|
4052
4064
|
return;
|
|
4053
4065
|
const { subscriberOffer } = message.eventPayload;
|
|
@@ -4062,7 +4074,7 @@ const createSubscriber = ({ rpcClient, connectionConfig, onTrack, }) => {
|
|
|
4062
4074
|
yield subscriber.addIceCandidate(iceCandidate);
|
|
4063
4075
|
}
|
|
4064
4076
|
catch (e) {
|
|
4065
|
-
console.error(`
|
|
4077
|
+
console.error(`Subscriber: ICE candidate error`, e, candidate);
|
|
4066
4078
|
}
|
|
4067
4079
|
}));
|
|
4068
4080
|
// apply ice candidates
|
|
@@ -4073,8 +4085,30 @@ const createSubscriber = ({ rpcClient, connectionConfig, onTrack, }) => {
|
|
|
4073
4085
|
sdp: answer.sdp || '',
|
|
4074
4086
|
});
|
|
4075
4087
|
}));
|
|
4088
|
+
// we replace the close method of the subscriber PeerConnection
|
|
4089
|
+
// so that we can preform some cleanups before closing the connection.
|
|
4090
|
+
// We are doing this as currently there is no event that is fired
|
|
4091
|
+
// when the subscriber PeerConnection is closed.
|
|
4092
|
+
const originalClose = subscriber.close;
|
|
4093
|
+
subscriber.close = () => {
|
|
4094
|
+
unsubscribe();
|
|
4095
|
+
originalClose.call(subscriber);
|
|
4096
|
+
};
|
|
4076
4097
|
return subscriber;
|
|
4077
4098
|
};
|
|
4099
|
+
const attachDebugEventListeners = (subscriber) => {
|
|
4100
|
+
subscriber.addEventListener('icecandidateerror', (e) => {
|
|
4101
|
+
const errorMessage = e instanceof RTCPeerConnectionIceErrorEvent &&
|
|
4102
|
+
`${e.errorCode}: ${e.errorText}`;
|
|
4103
|
+
console.error(`Subscriber: ICE Candidate error`, errorMessage);
|
|
4104
|
+
});
|
|
4105
|
+
subscriber.addEventListener('iceconnectionstatechange', () => {
|
|
4106
|
+
console.log(`Subscriber: ICE Connection state changed`, subscriber.iceConnectionState);
|
|
4107
|
+
});
|
|
4108
|
+
subscriber.addEventListener('icegatheringstatechange', () => {
|
|
4109
|
+
console.log(`Subscriber: ICE Gathering State`, subscriber.iceGatheringState);
|
|
4110
|
+
});
|
|
4111
|
+
};
|
|
4078
4112
|
|
|
4079
4113
|
const findOptimalVideoLayers = (videoTrack) => {
|
|
4080
4114
|
const steps = [
|
|
@@ -4221,8 +4255,8 @@ const getGenericSdp = (direction, preferredCodec) => __awaiter(void 0, void 0, v
|
|
|
4221
4255
|
});
|
|
4222
4256
|
|
|
4223
4257
|
/**
|
|
4224
|
-
* @internal
|
|
4225
4258
|
* The `Publisher` is responsible for publishing/unpublishing media streams to/from the SFU
|
|
4259
|
+
* @internal
|
|
4226
4260
|
*/
|
|
4227
4261
|
class Publisher {
|
|
4228
4262
|
constructor({ connectionConfig, rpcClient, isDtxEnabled }) {
|
|
@@ -4276,7 +4310,7 @@ class Publisher {
|
|
|
4276
4310
|
if (trackType === TrackType.VIDEO) {
|
|
4277
4311
|
const codecPreferences = getPreferredCodecs('video', opts.preferredCodec || 'vp8');
|
|
4278
4312
|
if ('setCodecPreferences' in transceiver && codecPreferences) {
|
|
4279
|
-
console.log(`set codec preferences`, codecPreferences);
|
|
4313
|
+
console.log(`set video codec preferences`, codecPreferences);
|
|
4280
4314
|
// @ts-ignore
|
|
4281
4315
|
transceiver.setCodecPreferences(codecPreferences);
|
|
4282
4316
|
}
|
|
@@ -4286,14 +4320,17 @@ class Publisher {
|
|
|
4286
4320
|
const codecPreferences = getPreferredCodecs('audio', opts.preferredCodec, returnOnlyMatched);
|
|
4287
4321
|
console.log('Preferred codec', opts.preferredCodec);
|
|
4288
4322
|
if ('setCodecPreferences' in transceiver && codecPreferences) {
|
|
4289
|
-
console.log(`set codec preferences`, codecPreferences);
|
|
4323
|
+
console.log(`set audio codec preferences`, codecPreferences);
|
|
4290
4324
|
// @ts-ignore
|
|
4291
4325
|
transceiver.setCodecPreferences(codecPreferences);
|
|
4292
4326
|
}
|
|
4293
4327
|
}
|
|
4294
4328
|
}
|
|
4295
4329
|
else {
|
|
4296
|
-
|
|
4330
|
+
// don't stop the track if we are re-publishing the same track
|
|
4331
|
+
if (transceiver.sender.track !== track) {
|
|
4332
|
+
(_a = transceiver.sender.track) === null || _a === void 0 ? void 0 : _a.stop();
|
|
4333
|
+
}
|
|
4297
4334
|
yield transceiver.sender.replaceTrack(track);
|
|
4298
4335
|
}
|
|
4299
4336
|
});
|
|
@@ -4317,16 +4354,21 @@ class Publisher {
|
|
|
4317
4354
|
};
|
|
4318
4355
|
/**
|
|
4319
4356
|
* Stops publishing all tracks and stop all tracks.
|
|
4357
|
+
*
|
|
4358
|
+
* @param options - Options
|
|
4359
|
+
* @param options.stopTracks - If `true` (default), all tracks will be stopped.
|
|
4320
4360
|
*/
|
|
4321
|
-
this.stopPublishing = () => {
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
this.publisher.
|
|
4328
|
-
|
|
4329
|
-
|
|
4361
|
+
this.stopPublishing = (options = {}) => {
|
|
4362
|
+
const { stopTracks = true } = options;
|
|
4363
|
+
if (stopTracks) {
|
|
4364
|
+
this.publisher.getSenders().forEach((s) => {
|
|
4365
|
+
var _a;
|
|
4366
|
+
(_a = s.track) === null || _a === void 0 ? void 0 : _a.stop();
|
|
4367
|
+
if (this.publisher.signalingState !== 'closed') {
|
|
4368
|
+
this.publisher.removeTrack(s);
|
|
4369
|
+
}
|
|
4370
|
+
});
|
|
4371
|
+
}
|
|
4330
4372
|
this.publisher.close();
|
|
4331
4373
|
};
|
|
4332
4374
|
this.updateVideoPublishQuality = (enabledRids) => __awaiter(this, void 0, void 0, function* () {
|
|
@@ -4400,34 +4442,39 @@ class Publisher {
|
|
|
4400
4442
|
};
|
|
4401
4443
|
});
|
|
4402
4444
|
// TODO debounce for 250ms
|
|
4403
|
-
const response = yield this.rpcClient.setPublisher({
|
|
4445
|
+
const { response } = yield this.rpcClient.setPublisher({
|
|
4404
4446
|
sdp: offer.sdp || '',
|
|
4405
4447
|
tracks: trackInfos,
|
|
4406
4448
|
});
|
|
4407
|
-
|
|
4408
|
-
|
|
4409
|
-
|
|
4410
|
-
|
|
4449
|
+
try {
|
|
4450
|
+
yield this.publisher.setRemoteDescription({
|
|
4451
|
+
type: 'answer',
|
|
4452
|
+
sdp: response.sdp,
|
|
4453
|
+
});
|
|
4454
|
+
}
|
|
4455
|
+
catch (e) {
|
|
4456
|
+
console.error(`Publisher: setRemoteDescription error`, response.sdp, e);
|
|
4457
|
+
}
|
|
4411
4458
|
this.rpcClient.iceTrickleBuffer.publisherCandidates.subscribe((candidate) => __awaiter(this, void 0, void 0, function* () {
|
|
4412
4459
|
try {
|
|
4413
4460
|
const iceCandidate = JSON.parse(candidate.iceCandidate);
|
|
4414
4461
|
yield this.publisher.addIceCandidate(iceCandidate);
|
|
4415
4462
|
}
|
|
4416
4463
|
catch (e) {
|
|
4417
|
-
console.error(`
|
|
4464
|
+
console.error(`Publisher: ICE candidate error`, e, candidate);
|
|
4418
4465
|
}
|
|
4419
4466
|
}));
|
|
4420
4467
|
});
|
|
4421
4468
|
this.onIceCandidateError = (e) => {
|
|
4422
4469
|
const errorMessage = e instanceof RTCPeerConnectionIceErrorEvent &&
|
|
4423
4470
|
`${e.errorCode}: ${e.errorText}`;
|
|
4424
|
-
console.error(`Publisher: ICE Candidate error`, errorMessage
|
|
4471
|
+
console.error(`Publisher: ICE Candidate error`, errorMessage);
|
|
4425
4472
|
};
|
|
4426
|
-
this.onIceConnectionStateChange = (
|
|
4427
|
-
console.log(`Publisher: ICE Connection state changed`, this.publisher.iceConnectionState
|
|
4473
|
+
this.onIceConnectionStateChange = () => {
|
|
4474
|
+
console.log(`Publisher: ICE Connection state changed`, this.publisher.iceConnectionState);
|
|
4428
4475
|
};
|
|
4429
|
-
this.onIceGatheringStateChange = (
|
|
4430
|
-
console.log(`Publisher: ICE Gathering State`, this.publisher.iceGatheringState
|
|
4476
|
+
this.onIceGatheringStateChange = () => {
|
|
4477
|
+
console.log(`Publisher: ICE Gathering State`, this.publisher.iceGatheringState);
|
|
4431
4478
|
};
|
|
4432
4479
|
this.ridToVideoQuality = (rid) => {
|
|
4433
4480
|
return rid === 'q'
|
|
@@ -4978,6 +5025,40 @@ const speakerLayoutSortPreset = combineComparators(pinned, screenSharing, domina
|
|
|
4978
5025
|
*/
|
|
4979
5026
|
const livestreamOrAudioRoomSortPreset = combineComparators(ifInvisibleBy(dominantSpeaker), ifInvisibleBy(speaking), ifInvisibleBy(reactionType('raised-hand')), ifInvisibleBy(publishingVideo), ifInvisibleBy(publishingAudio), role('admin', 'host', 'speaker'));
|
|
4980
5027
|
|
|
5028
|
+
/**
|
|
5029
|
+
* Represents the state of the current call.
|
|
5030
|
+
*/
|
|
5031
|
+
var CallingState;
|
|
5032
|
+
(function (CallingState) {
|
|
5033
|
+
/**
|
|
5034
|
+
* The call is in an idle state.
|
|
5035
|
+
*/
|
|
5036
|
+
CallingState["IDLE"] = "idle";
|
|
5037
|
+
/**
|
|
5038
|
+
* The call is in the process of joining.
|
|
5039
|
+
*/
|
|
5040
|
+
CallingState["JOINING"] = "joining";
|
|
5041
|
+
/**
|
|
5042
|
+
* The call is currently active.
|
|
5043
|
+
*/
|
|
5044
|
+
CallingState["JOINED"] = "joined";
|
|
5045
|
+
/**
|
|
5046
|
+
* The call has been left.
|
|
5047
|
+
*/
|
|
5048
|
+
CallingState["LEFT"] = "left";
|
|
5049
|
+
/**
|
|
5050
|
+
* The call is in the process of reconnecting.
|
|
5051
|
+
*/
|
|
5052
|
+
CallingState["RECONNECTING"] = "reconnecting";
|
|
5053
|
+
/**
|
|
5054
|
+
* The call has failed to reconnect.
|
|
5055
|
+
*/
|
|
5056
|
+
CallingState["RECONNECTING_FAILED"] = "reconnecting-failed";
|
|
5057
|
+
/**
|
|
5058
|
+
* The call is in offline mode.
|
|
5059
|
+
*/
|
|
5060
|
+
CallingState["OFFLINE"] = "offline";
|
|
5061
|
+
})(CallingState || (CallingState = {}));
|
|
4981
5062
|
/**
|
|
4982
5063
|
* Holds the state of the current call.
|
|
4983
5064
|
* @react You don't have to use this class directly, as we are exposing the state through Hooks.
|
|
@@ -5001,6 +5082,12 @@ class CallState {
|
|
|
5001
5082
|
* @internal
|
|
5002
5083
|
*/
|
|
5003
5084
|
this.membersSubject = new BehaviorSubject([]);
|
|
5085
|
+
/**
|
|
5086
|
+
* The calling state.
|
|
5087
|
+
*
|
|
5088
|
+
* @internal
|
|
5089
|
+
*/
|
|
5090
|
+
this.callingStateSubject = new BehaviorSubject(CallingState.IDLE);
|
|
5004
5091
|
/**
|
|
5005
5092
|
* All participants of the current call (including the logged-in user).
|
|
5006
5093
|
*
|
|
@@ -5142,6 +5229,7 @@ class CallState {
|
|
|
5142
5229
|
return acc;
|
|
5143
5230
|
}, {});
|
|
5144
5231
|
}));
|
|
5232
|
+
this.callingState$ = this.callingStateSubject.asObservable();
|
|
5145
5233
|
}
|
|
5146
5234
|
}
|
|
5147
5235
|
|
|
@@ -5639,6 +5727,129 @@ const CallTypes = new CallTypesRegistry([
|
|
|
5639
5727
|
}),
|
|
5640
5728
|
]);
|
|
5641
5729
|
|
|
5730
|
+
const sleep = (m) => new Promise((r) => setTimeout(r, m));
|
|
5731
|
+
function isFunction(value) {
|
|
5732
|
+
return (value &&
|
|
5733
|
+
(Object.prototype.toString.call(value) === '[object Function]' ||
|
|
5734
|
+
'function' === typeof value ||
|
|
5735
|
+
value instanceof Function));
|
|
5736
|
+
}
|
|
5737
|
+
// todo: rename so that it does not contain word "chat"
|
|
5738
|
+
const chatCodes = {
|
|
5739
|
+
TOKEN_EXPIRED: 40,
|
|
5740
|
+
WS_CLOSED_SUCCESS: 1000,
|
|
5741
|
+
};
|
|
5742
|
+
/**
|
|
5743
|
+
* retryInterval - A retry interval which increases acc to number of failures
|
|
5744
|
+
*
|
|
5745
|
+
* @return {number} Duration to wait in milliseconds
|
|
5746
|
+
*/
|
|
5747
|
+
function retryInterval(numberOfFailures) {
|
|
5748
|
+
// try to reconnect in 0.25-5 seconds (random to spread out the load from failures)
|
|
5749
|
+
const max = Math.min(500 + numberOfFailures * 2000, 5000);
|
|
5750
|
+
const min = Math.min(Math.max(250, (numberOfFailures - 1) * 2000), 5000);
|
|
5751
|
+
return Math.floor(Math.random() * (max - min) + min);
|
|
5752
|
+
}
|
|
5753
|
+
function randomId() {
|
|
5754
|
+
return generateUUIDv4();
|
|
5755
|
+
}
|
|
5756
|
+
function hex(bytes) {
|
|
5757
|
+
let s = '';
|
|
5758
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
5759
|
+
s += bytes[i].toString(16).padStart(2, '0');
|
|
5760
|
+
}
|
|
5761
|
+
return s;
|
|
5762
|
+
}
|
|
5763
|
+
// https://tools.ietf.org/html/rfc4122
|
|
5764
|
+
function generateUUIDv4() {
|
|
5765
|
+
const bytes = getRandomBytes(16);
|
|
5766
|
+
bytes[6] = (bytes[6] & 0x0f) | 0x40; // version
|
|
5767
|
+
bytes[8] = (bytes[8] & 0xbf) | 0x80; // variant
|
|
5768
|
+
return (hex(bytes.subarray(0, 4)) +
|
|
5769
|
+
'-' +
|
|
5770
|
+
hex(bytes.subarray(4, 6)) +
|
|
5771
|
+
'-' +
|
|
5772
|
+
hex(bytes.subarray(6, 8)) +
|
|
5773
|
+
'-' +
|
|
5774
|
+
hex(bytes.subarray(8, 10)) +
|
|
5775
|
+
'-' +
|
|
5776
|
+
hex(bytes.subarray(10, 16)));
|
|
5777
|
+
}
|
|
5778
|
+
function getRandomValuesWithMathRandom(bytes) {
|
|
5779
|
+
const max = Math.pow(2, (8 * bytes.byteLength) / bytes.length);
|
|
5780
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
5781
|
+
bytes[i] = Math.random() * max;
|
|
5782
|
+
}
|
|
5783
|
+
}
|
|
5784
|
+
const getRandomValues = (() => {
|
|
5785
|
+
if (typeof crypto !== 'undefined' &&
|
|
5786
|
+
typeof (crypto === null || crypto === void 0 ? void 0 : crypto.getRandomValues) !== 'undefined') {
|
|
5787
|
+
return crypto.getRandomValues.bind(crypto);
|
|
5788
|
+
}
|
|
5789
|
+
else if (typeof msCrypto !== 'undefined') {
|
|
5790
|
+
return msCrypto.getRandomValues.bind(msCrypto);
|
|
5791
|
+
}
|
|
5792
|
+
else {
|
|
5793
|
+
return getRandomValuesWithMathRandom;
|
|
5794
|
+
}
|
|
5795
|
+
})();
|
|
5796
|
+
function getRandomBytes(length) {
|
|
5797
|
+
const bytes = new Uint8Array(length);
|
|
5798
|
+
getRandomValues(bytes);
|
|
5799
|
+
return bytes;
|
|
5800
|
+
}
|
|
5801
|
+
function convertErrorToJson(err) {
|
|
5802
|
+
const jsonObj = {};
|
|
5803
|
+
if (!err)
|
|
5804
|
+
return jsonObj;
|
|
5805
|
+
try {
|
|
5806
|
+
Object.getOwnPropertyNames(err).forEach((key) => {
|
|
5807
|
+
jsonObj[key] = Object.getOwnPropertyDescriptor(err, key);
|
|
5808
|
+
});
|
|
5809
|
+
}
|
|
5810
|
+
catch (_) {
|
|
5811
|
+
return {
|
|
5812
|
+
error: 'failed to serialize the error',
|
|
5813
|
+
};
|
|
5814
|
+
}
|
|
5815
|
+
return jsonObj;
|
|
5816
|
+
}
|
|
5817
|
+
/**
|
|
5818
|
+
* isOnline safely return the navigator.online value for browser env
|
|
5819
|
+
* if navigator is not in global object, it always return true
|
|
5820
|
+
*/
|
|
5821
|
+
function isOnline() {
|
|
5822
|
+
const nav = typeof navigator !== 'undefined'
|
|
5823
|
+
? navigator
|
|
5824
|
+
: typeof window !== 'undefined' && window.navigator
|
|
5825
|
+
? window.navigator
|
|
5826
|
+
: undefined;
|
|
5827
|
+
if (!nav) {
|
|
5828
|
+
console.warn('isOnline failed to access window.navigator and assume browser is online');
|
|
5829
|
+
return true;
|
|
5830
|
+
}
|
|
5831
|
+
// RN navigator has undefined for onLine
|
|
5832
|
+
if (typeof nav.onLine !== 'boolean') {
|
|
5833
|
+
return true;
|
|
5834
|
+
}
|
|
5835
|
+
return nav.onLine;
|
|
5836
|
+
}
|
|
5837
|
+
/**
|
|
5838
|
+
* listenForConnectionChanges - Adds an event listener fired on browser going online or offline
|
|
5839
|
+
*/
|
|
5840
|
+
function addConnectionEventListeners(cb) {
|
|
5841
|
+
if (typeof window !== 'undefined' && window.addEventListener) {
|
|
5842
|
+
window.addEventListener('offline', cb);
|
|
5843
|
+
window.addEventListener('online', cb);
|
|
5844
|
+
}
|
|
5845
|
+
}
|
|
5846
|
+
function removeConnectionEventListeners(cb) {
|
|
5847
|
+
if (typeof window !== 'undefined' && window.removeEventListener) {
|
|
5848
|
+
window.removeEventListener('offline', cb);
|
|
5849
|
+
window.removeEventListener('online', cb);
|
|
5850
|
+
}
|
|
5851
|
+
}
|
|
5852
|
+
|
|
5642
5853
|
const UPDATE_SUBSCRIPTIONS_DEBOUNCE_DURATION = 600;
|
|
5643
5854
|
/**
|
|
5644
5855
|
* A `Call` object represents the active call the user is part of.
|
|
@@ -5647,7 +5858,7 @@ const UPDATE_SUBSCRIPTIONS_DEBOUNCE_DURATION = 600;
|
|
|
5647
5858
|
class Call {
|
|
5648
5859
|
get preferredAudioCodec() {
|
|
5649
5860
|
var _a;
|
|
5650
|
-
const audioSettings = (_a = this.
|
|
5861
|
+
const audioSettings = (_a = this.data) === null || _a === void 0 ? void 0 : _a.settings.audio;
|
|
5651
5862
|
let preferredCodec = (audioSettings === null || audioSettings === void 0 ? void 0 : audioSettings.redundant_coding_enabled) === undefined
|
|
5652
5863
|
? 'opus'
|
|
5653
5864
|
: audioSettings.redundant_coding_enabled
|
|
@@ -5666,7 +5877,7 @@ class Call {
|
|
|
5666
5877
|
*/
|
|
5667
5878
|
constructor({ type, id, streamClient, metadata, members, sortParticipantsBy, clientStore, }) {
|
|
5668
5879
|
/**
|
|
5669
|
-
*
|
|
5880
|
+
* ViewportTracker instance
|
|
5670
5881
|
*/
|
|
5671
5882
|
this.viewportTracker = new ViewportTracker();
|
|
5672
5883
|
/**
|
|
@@ -5675,7 +5886,7 @@ class Call {
|
|
|
5675
5886
|
*/
|
|
5676
5887
|
this.dispatcher = new Dispatcher();
|
|
5677
5888
|
this.trackSubscriptionsSubject = new Subject();
|
|
5678
|
-
this.
|
|
5889
|
+
this.reconnectAttempts = 0;
|
|
5679
5890
|
/**
|
|
5680
5891
|
* Remove subscription for WebSocket events that were created by the `on` method.
|
|
5681
5892
|
* @param eventName
|
|
@@ -5690,10 +5901,9 @@ class Call {
|
|
|
5690
5901
|
*/
|
|
5691
5902
|
this.leave = () => {
|
|
5692
5903
|
var _a, _b, _c, _d;
|
|
5693
|
-
if (
|
|
5904
|
+
if (this.state.getCurrentValue(this.state.callingState$) === CallingState.LEFT) {
|
|
5694
5905
|
throw new Error('Cannot leave call that has already been left.');
|
|
5695
5906
|
}
|
|
5696
|
-
this.joined$.next(false);
|
|
5697
5907
|
(_a = this.statsReporter) === null || _a === void 0 ? void 0 : _a.stop();
|
|
5698
5908
|
this.statsReporter = undefined;
|
|
5699
5909
|
(_b = this.subscriber) === null || _b === void 0 ? void 0 : _b.close();
|
|
@@ -5702,13 +5912,19 @@ class Call {
|
|
|
5702
5912
|
this.publisher = undefined;
|
|
5703
5913
|
(_d = this.sfuClient) === null || _d === void 0 ? void 0 : _d.close();
|
|
5704
5914
|
this.sfuClient = undefined;
|
|
5915
|
+
this.dispatcher.offAll();
|
|
5705
5916
|
this.clientStore.setCurrentValue(this.clientStore.activeCallSubject, undefined);
|
|
5917
|
+
this.state.setCurrentValue(this.state.callingStateSubject, CallingState.LEFT);
|
|
5706
5918
|
};
|
|
5707
|
-
this.waitForJoinResponse = (timeout =
|
|
5919
|
+
this.waitForJoinResponse = (timeout = 5000) => new Promise((resolve, reject) => {
|
|
5708
5920
|
const unsubscribe = this.on('joinResponse', (event) => {
|
|
5709
|
-
|
|
5921
|
+
if (event.eventPayload.oneofKind !== 'joinResponse')
|
|
5922
|
+
return;
|
|
5923
|
+
clearTimeout(timeoutId);
|
|
5924
|
+
unsubscribe();
|
|
5925
|
+
resolve(event.eventPayload.joinResponse);
|
|
5710
5926
|
});
|
|
5711
|
-
setTimeout(() => {
|
|
5927
|
+
const timeoutId = setTimeout(() => {
|
|
5712
5928
|
unsubscribe();
|
|
5713
5929
|
reject(new Error('Waiting for "joinResponse" has timed out'));
|
|
5714
5930
|
}, timeout);
|
|
@@ -5731,9 +5947,10 @@ class Call {
|
|
|
5731
5947
|
*/
|
|
5732
5948
|
this.join = (data) => __awaiter(this, void 0, void 0, function* () {
|
|
5733
5949
|
var _a;
|
|
5734
|
-
if (this.
|
|
5950
|
+
if ([CallingState.JOINED, CallingState.JOINING].includes(this.state.getCurrentValue(this.state.callingState$))) {
|
|
5735
5951
|
throw new Error(`Illegal State: Already joined.`);
|
|
5736
5952
|
}
|
|
5953
|
+
this.state.setCurrentValue(this.state.callingStateSubject, CallingState.JOINING);
|
|
5737
5954
|
const call = yield join(this.streamClient, this.type, this.id, data);
|
|
5738
5955
|
this.state.setCurrentValue(this.state.metadataSubject, call.metadata);
|
|
5739
5956
|
this.state.setCurrentValue(this.state.membersSubject, call.members);
|
|
@@ -5749,12 +5966,79 @@ class Call {
|
|
|
5749
5966
|
sfuUrl = sfuUrlParam || call.sfuServer.url;
|
|
5750
5967
|
}
|
|
5751
5968
|
const sfuClient = (this.sfuClient = new StreamSfuClient(this.dispatcher, sfuUrl, call.token));
|
|
5969
|
+
/**
|
|
5970
|
+
* A closure which hides away the re-connection logic.
|
|
5971
|
+
*/
|
|
5972
|
+
const rejoin = () => __awaiter(this, void 0, void 0, function* () {
|
|
5973
|
+
var _b, _c, _d;
|
|
5974
|
+
console.log(`Rejoining call ${this.cid} (${this.reconnectAttempts})...`);
|
|
5975
|
+
this.reconnectAttempts++;
|
|
5976
|
+
this.state.setCurrentValue(this.state.callingStateSubject, CallingState.RECONNECTING);
|
|
5977
|
+
// take a snapshot of the current "local participant" state
|
|
5978
|
+
// we'll need it for restoring the previous publishing state later
|
|
5979
|
+
const localParticipant = this.state.getCurrentValue(this.state.localParticipant$);
|
|
5980
|
+
(_b = this.subscriber) === null || _b === void 0 ? void 0 : _b.close();
|
|
5981
|
+
(_c = this.publisher) === null || _c === void 0 ? void 0 : _c.stopPublishing({ stopTracks: false });
|
|
5982
|
+
(_d = this.statsReporter) === null || _d === void 0 ? void 0 : _d.stop();
|
|
5983
|
+
sfuClient.close(); // clean up previous connection
|
|
5984
|
+
yield sleep(retryInterval(this.reconnectAttempts));
|
|
5985
|
+
yield this.join(data);
|
|
5986
|
+
console.log(`Rejoin: ${this.reconnectAttempts} successful!`);
|
|
5987
|
+
if (localParticipant) {
|
|
5988
|
+
const { audioStream, videoStream, screenShareStream: screenShare, } = localParticipant;
|
|
5989
|
+
// restore previous publishing state
|
|
5990
|
+
if (audioStream)
|
|
5991
|
+
yield this.publishAudioStream(audioStream);
|
|
5992
|
+
if (videoStream)
|
|
5993
|
+
yield this.publishVideoStream(videoStream);
|
|
5994
|
+
if (screenShare)
|
|
5995
|
+
yield this.publishScreenShareStream(screenShare);
|
|
5996
|
+
}
|
|
5997
|
+
console.log(`Rejoin: state restored ${this.reconnectAttempts}`);
|
|
5998
|
+
});
|
|
5999
|
+
// reconnect if the connection was closed unexpectedly. example:
|
|
6000
|
+
// - SFU crash or restart
|
|
6001
|
+
// - network change
|
|
6002
|
+
sfuClient.signalReady.then(() => {
|
|
6003
|
+
sfuClient.signalWs.addEventListener('close', (e) => {
|
|
6004
|
+
// do nothing if the connection was closed on purpose
|
|
6005
|
+
if (e.code === 1000)
|
|
6006
|
+
return;
|
|
6007
|
+
if (this.reconnectAttempts >= 10) {
|
|
6008
|
+
console.log('Reconnect attempts exceeded. Giving up...');
|
|
6009
|
+
this.state.setCurrentValue(this.state.callingStateSubject, CallingState.RECONNECTING_FAILED);
|
|
6010
|
+
}
|
|
6011
|
+
else {
|
|
6012
|
+
void rejoin();
|
|
6013
|
+
}
|
|
6014
|
+
});
|
|
6015
|
+
});
|
|
6016
|
+
// handlers for connection online/offline events
|
|
6017
|
+
// Note: window.addEventListener is not available in React Native, hence the check
|
|
6018
|
+
if (typeof window !== 'undefined' && window.addEventListener) {
|
|
6019
|
+
const handleOnOffline = () => {
|
|
6020
|
+
window.removeEventListener('offline', handleOnOffline);
|
|
6021
|
+
console.log('Join: Going offline...');
|
|
6022
|
+
this.state.setCurrentValue(this.state.callingStateSubject, CallingState.OFFLINE);
|
|
6023
|
+
};
|
|
6024
|
+
const handleOnOnline = () => {
|
|
6025
|
+
window.removeEventListener('online', handleOnOnline);
|
|
6026
|
+
if (this.state.getCurrentValue(this.state.callingState$) ===
|
|
6027
|
+
CallingState.OFFLINE) {
|
|
6028
|
+
console.log('Join: Going online...');
|
|
6029
|
+
rejoin();
|
|
6030
|
+
}
|
|
6031
|
+
};
|
|
6032
|
+
window.addEventListener('offline', handleOnOffline);
|
|
6033
|
+
window.addEventListener('online', handleOnOnline);
|
|
6034
|
+
}
|
|
5752
6035
|
this.subscriber = createSubscriber({
|
|
5753
6036
|
rpcClient: sfuClient,
|
|
6037
|
+
dispatcher: this.dispatcher,
|
|
5754
6038
|
connectionConfig: call.connectionConfig,
|
|
5755
6039
|
onTrack: this.handleOnTrack,
|
|
5756
6040
|
});
|
|
5757
|
-
const audioSettings = (_a = this.
|
|
6041
|
+
const audioSettings = (_a = this.data) === null || _a === void 0 ? void 0 : _a.settings.audio;
|
|
5758
6042
|
let isDtxEnabled = (audioSettings === null || audioSettings === void 0 ? void 0 : audioSettings.opus_dtx_enabled) === undefined
|
|
5759
6043
|
? false
|
|
5760
6044
|
: audioSettings === null || audioSettings === void 0 ? void 0 : audioSettings.opus_dtx_enabled;
|
|
@@ -5777,8 +6061,19 @@ class Call {
|
|
|
5777
6061
|
store: this.state,
|
|
5778
6062
|
edgeName: call.sfuServer.edge_name,
|
|
5779
6063
|
});
|
|
5780
|
-
|
|
5781
|
-
|
|
6064
|
+
try {
|
|
6065
|
+
// 1. wait for the signal server to be ready before sending "joinRequest"
|
|
6066
|
+
sfuClient.signalReady
|
|
6067
|
+
.catch((err) => console.warn('Signal ready failed', err))
|
|
6068
|
+
// prepare a generic SDP and send it to the SFU.
|
|
6069
|
+
// this is a throw-away SDP that the SFU will use to determine
|
|
6070
|
+
// the capabilities of the client (codec support, etc.)
|
|
6071
|
+
.then(() => getGenericSdp('recvonly', this.preferredAudioCodec))
|
|
6072
|
+
.then((sdp) => sfuClient.join({ subscriberSdp: sdp || '' }));
|
|
6073
|
+
// 2. in parallel, wait for the SFU to send us the "joinResponse"
|
|
6074
|
+
// this will throw an error if the SFU rejects the join request or
|
|
6075
|
+
// fails to respond in time
|
|
6076
|
+
const { callState } = yield this.waitForJoinResponse();
|
|
5782
6077
|
const currentParticipants = (callState === null || callState === void 0 ? void 0 : callState.participants) || [];
|
|
5783
6078
|
const ownCapabilities = {
|
|
5784
6079
|
ownCapabilities: call.metadata.own_capabilities,
|
|
@@ -5787,14 +6082,22 @@ class Call {
|
|
|
5787
6082
|
? ownCapabilities
|
|
5788
6083
|
: {})))));
|
|
5789
6084
|
this.clientStore.setCurrentValue(this.clientStore.activeCallSubject, this);
|
|
5790
|
-
|
|
5791
|
-
this.
|
|
5792
|
-
|
|
5793
|
-
|
|
5794
|
-
|
|
5795
|
-
|
|
5796
|
-
|
|
5797
|
-
|
|
6085
|
+
this.reconnectAttempts = 0; // reset the reconnect attempts counter
|
|
6086
|
+
this.state.setCurrentValue(this.state.callingStateSubject, CallingState.JOINED);
|
|
6087
|
+
console.log(`Joined call ${this.cid}`);
|
|
6088
|
+
}
|
|
6089
|
+
catch (err) {
|
|
6090
|
+
// join failed, try to rejoin
|
|
6091
|
+
if (this.reconnectAttempts < 10) {
|
|
6092
|
+
yield rejoin();
|
|
6093
|
+
console.log(`Rejoin ${this.reconnectAttempts} successful!`);
|
|
6094
|
+
}
|
|
6095
|
+
else {
|
|
6096
|
+
console.log(`Rejoin failed for ${this.reconnectAttempts} times. Giving up.`);
|
|
6097
|
+
this.state.setCurrentValue(this.state.callingStateSubject, CallingState.RECONNECTING_FAILED);
|
|
6098
|
+
throw new Error('Join failed');
|
|
6099
|
+
}
|
|
6100
|
+
}
|
|
5798
6101
|
});
|
|
5799
6102
|
this.updateCallMembers = (data) => __awaiter(this, void 0, void 0, function* () {
|
|
5800
6103
|
return yield this.streamClient.post(`${this.streamClientBasePath}/members`, data);
|
|
@@ -5814,7 +6117,7 @@ class Call {
|
|
|
5814
6117
|
* @param opts the options to use when publishing the stream.
|
|
5815
6118
|
*/
|
|
5816
6119
|
this.publishVideoStream = (videoStream, opts = {}) => __awaiter(this, void 0, void 0, function* () {
|
|
5817
|
-
var
|
|
6120
|
+
var _e;
|
|
5818
6121
|
// we should wait until we get a JoinResponse from the SFU,
|
|
5819
6122
|
// otherwise we risk breaking the ICETrickle flow.
|
|
5820
6123
|
yield this.assertCallJoined();
|
|
@@ -5828,7 +6131,7 @@ class Call {
|
|
|
5828
6131
|
const trackType = TrackType.VIDEO;
|
|
5829
6132
|
try {
|
|
5830
6133
|
yield this.publisher.publishStream(videoStream, videoTrack, trackType, opts);
|
|
5831
|
-
yield ((
|
|
6134
|
+
yield ((_e = this.sfuClient) === null || _e === void 0 ? void 0 : _e.updateMuteState(trackType, false));
|
|
5832
6135
|
}
|
|
5833
6136
|
catch (e) {
|
|
5834
6137
|
throw e;
|
|
@@ -6013,8 +6316,8 @@ class Call {
|
|
|
6013
6316
|
* @returns
|
|
6014
6317
|
*/
|
|
6015
6318
|
this.getStats = (kind, selector) => __awaiter(this, void 0, void 0, function* () {
|
|
6016
|
-
var
|
|
6017
|
-
return (
|
|
6319
|
+
var _f;
|
|
6320
|
+
return (_f = this.statsReporter) === null || _f === void 0 ? void 0 : _f.getRawStatsForTrack(kind, selector);
|
|
6018
6321
|
});
|
|
6019
6322
|
/**
|
|
6020
6323
|
* Will enhance the reported stats with additional participant-specific information (`callStatsReport$` state [store variable](./StreamVideoClient.md/#readonlystatestore)).
|
|
@@ -6102,8 +6405,8 @@ class Call {
|
|
|
6102
6405
|
* @returns
|
|
6103
6406
|
*/
|
|
6104
6407
|
this.updatePublishQuality = (enabledRids) => __awaiter(this, void 0, void 0, function* () {
|
|
6105
|
-
var
|
|
6106
|
-
return (
|
|
6408
|
+
var _g;
|
|
6409
|
+
return (_g = this.publisher) === null || _g === void 0 ? void 0 : _g.updateVideoPublishQuality(enabledRids);
|
|
6107
6410
|
});
|
|
6108
6411
|
this.handleOnTrack = (e) => {
|
|
6109
6412
|
const [primaryStream] = e.streams;
|
|
@@ -6149,11 +6452,9 @@ class Call {
|
|
|
6149
6452
|
};
|
|
6150
6453
|
this.assertCallJoined = () => {
|
|
6151
6454
|
return new Promise((resolve) => {
|
|
6152
|
-
this.
|
|
6153
|
-
.pipe(takeWhile((
|
|
6154
|
-
.subscribe(() =>
|
|
6155
|
-
resolve();
|
|
6156
|
-
});
|
|
6455
|
+
this.state.callingState$
|
|
6456
|
+
.pipe(takeWhile((state) => state !== CallingState.JOINED, true))
|
|
6457
|
+
.subscribe(() => resolve());
|
|
6157
6458
|
});
|
|
6158
6459
|
};
|
|
6159
6460
|
this.sendReaction = (reaction) => __awaiter(this, void 0, void 0, function* () {
|
|
@@ -6817,129 +7118,6 @@ CallDropScheduler.getLatestCall = (from, compareTo) => {
|
|
|
6817
7118
|
|
|
6818
7119
|
var https = null;
|
|
6819
7120
|
|
|
6820
|
-
const sleep = (m) => new Promise((r) => setTimeout(r, m));
|
|
6821
|
-
function isFunction(value) {
|
|
6822
|
-
return (value &&
|
|
6823
|
-
(Object.prototype.toString.call(value) === '[object Function]' ||
|
|
6824
|
-
'function' === typeof value ||
|
|
6825
|
-
value instanceof Function));
|
|
6826
|
-
}
|
|
6827
|
-
// todo: rename so that it does not contain word "chat"
|
|
6828
|
-
const chatCodes = {
|
|
6829
|
-
TOKEN_EXPIRED: 40,
|
|
6830
|
-
WS_CLOSED_SUCCESS: 1000,
|
|
6831
|
-
};
|
|
6832
|
-
/**
|
|
6833
|
-
* retryInterval - A retry interval which increases acc to number of failures
|
|
6834
|
-
*
|
|
6835
|
-
* @return {number} Duration to wait in milliseconds
|
|
6836
|
-
*/
|
|
6837
|
-
function retryInterval(numberOfFailures) {
|
|
6838
|
-
// try to reconnect in 0.25-25 seconds (random to spread out the load from failures)
|
|
6839
|
-
const max = Math.min(500 + numberOfFailures * 2000, 25000);
|
|
6840
|
-
const min = Math.min(Math.max(250, (numberOfFailures - 1) * 2000), 25000);
|
|
6841
|
-
return Math.floor(Math.random() * (max - min) + min);
|
|
6842
|
-
}
|
|
6843
|
-
function randomId() {
|
|
6844
|
-
return generateUUIDv4();
|
|
6845
|
-
}
|
|
6846
|
-
function hex(bytes) {
|
|
6847
|
-
let s = '';
|
|
6848
|
-
for (let i = 0; i < bytes.length; i++) {
|
|
6849
|
-
s += bytes[i].toString(16).padStart(2, '0');
|
|
6850
|
-
}
|
|
6851
|
-
return s;
|
|
6852
|
-
}
|
|
6853
|
-
// https://tools.ietf.org/html/rfc4122
|
|
6854
|
-
function generateUUIDv4() {
|
|
6855
|
-
const bytes = getRandomBytes(16);
|
|
6856
|
-
bytes[6] = (bytes[6] & 0x0f) | 0x40; // version
|
|
6857
|
-
bytes[8] = (bytes[8] & 0xbf) | 0x80; // variant
|
|
6858
|
-
return (hex(bytes.subarray(0, 4)) +
|
|
6859
|
-
'-' +
|
|
6860
|
-
hex(bytes.subarray(4, 6)) +
|
|
6861
|
-
'-' +
|
|
6862
|
-
hex(bytes.subarray(6, 8)) +
|
|
6863
|
-
'-' +
|
|
6864
|
-
hex(bytes.subarray(8, 10)) +
|
|
6865
|
-
'-' +
|
|
6866
|
-
hex(bytes.subarray(10, 16)));
|
|
6867
|
-
}
|
|
6868
|
-
function getRandomValuesWithMathRandom(bytes) {
|
|
6869
|
-
const max = Math.pow(2, (8 * bytes.byteLength) / bytes.length);
|
|
6870
|
-
for (let i = 0; i < bytes.length; i++) {
|
|
6871
|
-
bytes[i] = Math.random() * max;
|
|
6872
|
-
}
|
|
6873
|
-
}
|
|
6874
|
-
const getRandomValues = (() => {
|
|
6875
|
-
if (typeof crypto !== 'undefined' &&
|
|
6876
|
-
typeof (crypto === null || crypto === void 0 ? void 0 : crypto.getRandomValues) !== 'undefined') {
|
|
6877
|
-
return crypto.getRandomValues.bind(crypto);
|
|
6878
|
-
}
|
|
6879
|
-
else if (typeof msCrypto !== 'undefined') {
|
|
6880
|
-
return msCrypto.getRandomValues.bind(msCrypto);
|
|
6881
|
-
}
|
|
6882
|
-
else {
|
|
6883
|
-
return getRandomValuesWithMathRandom;
|
|
6884
|
-
}
|
|
6885
|
-
})();
|
|
6886
|
-
function getRandomBytes(length) {
|
|
6887
|
-
const bytes = new Uint8Array(length);
|
|
6888
|
-
getRandomValues(bytes);
|
|
6889
|
-
return bytes;
|
|
6890
|
-
}
|
|
6891
|
-
function convertErrorToJson(err) {
|
|
6892
|
-
const jsonObj = {};
|
|
6893
|
-
if (!err)
|
|
6894
|
-
return jsonObj;
|
|
6895
|
-
try {
|
|
6896
|
-
Object.getOwnPropertyNames(err).forEach((key) => {
|
|
6897
|
-
jsonObj[key] = Object.getOwnPropertyDescriptor(err, key);
|
|
6898
|
-
});
|
|
6899
|
-
}
|
|
6900
|
-
catch (_) {
|
|
6901
|
-
return {
|
|
6902
|
-
error: 'failed to serialize the error',
|
|
6903
|
-
};
|
|
6904
|
-
}
|
|
6905
|
-
return jsonObj;
|
|
6906
|
-
}
|
|
6907
|
-
/**
|
|
6908
|
-
* isOnline safely return the navigator.online value for browser env
|
|
6909
|
-
* if navigator is not in global object, it always return true
|
|
6910
|
-
*/
|
|
6911
|
-
function isOnline() {
|
|
6912
|
-
const nav = typeof navigator !== 'undefined'
|
|
6913
|
-
? navigator
|
|
6914
|
-
: typeof window !== 'undefined' && window.navigator
|
|
6915
|
-
? window.navigator
|
|
6916
|
-
: undefined;
|
|
6917
|
-
if (!nav) {
|
|
6918
|
-
console.warn('isOnline failed to access window.navigator and assume browser is online');
|
|
6919
|
-
return true;
|
|
6920
|
-
}
|
|
6921
|
-
// RN navigator has undefined for onLine
|
|
6922
|
-
if (typeof nav.onLine !== 'boolean') {
|
|
6923
|
-
return true;
|
|
6924
|
-
}
|
|
6925
|
-
return nav.onLine;
|
|
6926
|
-
}
|
|
6927
|
-
/**
|
|
6928
|
-
* listenForConnectionChanges - Adds an event listener fired on browser going online or offline
|
|
6929
|
-
*/
|
|
6930
|
-
function addConnectionEventListeners(cb) {
|
|
6931
|
-
if (typeof window !== 'undefined' && window.addEventListener) {
|
|
6932
|
-
window.addEventListener('offline', cb);
|
|
6933
|
-
window.addEventListener('online', cb);
|
|
6934
|
-
}
|
|
6935
|
-
}
|
|
6936
|
-
function removeConnectionEventListeners(cb) {
|
|
6937
|
-
if (typeof window !== 'undefined' && window.removeEventListener) {
|
|
6938
|
-
window.removeEventListener('offline', cb);
|
|
6939
|
-
window.removeEventListener('online', cb);
|
|
6940
|
-
}
|
|
6941
|
-
}
|
|
6942
|
-
|
|
6943
7121
|
class InsightMetrics {
|
|
6944
7122
|
constructor() {
|
|
6945
7123
|
this.connectionStartTimestamp = null;
|
|
@@ -8550,7 +8728,7 @@ class StreamClient {
|
|
|
8550
8728
|
}
|
|
8551
8729
|
getUserAgent() {
|
|
8552
8730
|
return (this.userAgent ||
|
|
8553
|
-
`stream-video-javascript-client-${this.node ? 'node' : 'browser'}-${"0.0.1-alpha.
|
|
8731
|
+
`stream-video-javascript-client-${this.node ? 'node' : 'browser'}-${"0.0.1-alpha.82"}`);
|
|
8554
8732
|
}
|
|
8555
8733
|
setUserAgent(userAgent) {
|
|
8556
8734
|
this.userAgent = userAgent;
|
|
@@ -9078,5 +9256,5 @@ const createSoundDetector = (audioStream, onSoundDetectedStateChanged, options =
|
|
|
9078
9256
|
};
|
|
9079
9257
|
};
|
|
9080
9258
|
|
|
9081
|
-
export { browsers as Browsers, CALL_CONFIG, Call, CallState, CallType, CallTypes, DeviceFieldsRequestPushProviderEnum, ErrorFromResponse, OwnCapability, RecordSettingsModeEnum, RecordSettingsQualityEnum, RecordSettingsRequestModeEnum, RecordSettingsRequestQualityEnum, rxUtils as RxUtils, events as SfuEvents, models as SfuModels, StreamSfuClient, StreamVideoClient, StreamVideoReadOnlyStateStore, StreamVideoWriteableStateStore, TranscriptionSettingsModeEnum, TranscriptionSettingsRequestModeEnum, ViewportTracker, VisibilityState, checkIfAudioOutputChangeSupported, combineComparators, conditional, createSoundDetector, defaultSortPreset, descending, disposeOfMediaStream, dominantSpeaker, getAudioDevices, getAudioOutputDevices, getAudioStream, getScreenShareStream, getVideoDevices, getVideoStream, isStreamVideoLocalParticipant, livestreamOrAudioRoomSortPreset, name, noopComparator, pinned, publishingAudio, publishingVideo, reactionType, role, screenSharing, speakerLayoutSortPreset, speaking, watchForAddedDefaultAudioDevice, watchForAddedDefaultAudioOutputDevice, watchForAddedDefaultVideoDevice, watchForDisconnectedAudioDevice, watchForDisconnectedAudioOutputDevice, watchForDisconnectedVideoDevice };
|
|
9259
|
+
export { browsers as Browsers, CALL_CONFIG, Call, CallState, CallType, CallTypes, CallingState, DeviceFieldsRequestPushProviderEnum, ErrorFromResponse, OwnCapability, RecordSettingsModeEnum, RecordSettingsQualityEnum, RecordSettingsRequestModeEnum, RecordSettingsRequestQualityEnum, rxUtils as RxUtils, events as SfuEvents, models as SfuModels, StreamSfuClient, StreamVideoClient, StreamVideoReadOnlyStateStore, StreamVideoWriteableStateStore, TranscriptionSettingsModeEnum, TranscriptionSettingsRequestModeEnum, ViewportTracker, VisibilityState, checkIfAudioOutputChangeSupported, combineComparators, conditional, createSoundDetector, defaultSortPreset, descending, disposeOfMediaStream, dominantSpeaker, getAudioDevices, getAudioOutputDevices, getAudioStream, getScreenShareStream, getVideoDevices, getVideoStream, isStreamVideoLocalParticipant, livestreamOrAudioRoomSortPreset, name, noopComparator, pinned, publishingAudio, publishingVideo, reactionType, role, screenSharing, speakerLayoutSortPreset, speaking, watchForAddedDefaultAudioDevice, watchForAddedDefaultAudioOutputDevice, watchForAddedDefaultVideoDevice, watchForDisconnectedAudioDevice, watchForDisconnectedAudioOutputDevice, watchForDisconnectedVideoDevice };
|
|
9082
9260
|
//# sourceMappingURL=index.browser.es.js.map
|