@stream-io/video-client 1.14.0 → 1.15.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/CHANGELOG.md +7 -0
- package/dist/index.browser.es.js +1532 -1784
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +1512 -1783
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +1532 -1784
- package/dist/index.es.js.map +1 -1
- package/dist/src/Call.d.ts +43 -28
- package/dist/src/StreamSfuClient.d.ts +4 -5
- package/dist/src/devices/CameraManager.d.ts +5 -8
- package/dist/src/devices/InputMediaDeviceManager.d.ts +5 -5
- package/dist/src/devices/MicrophoneManager.d.ts +7 -2
- package/dist/src/devices/ScreenShareManager.d.ts +1 -2
- package/dist/src/gen/video/sfu/event/events.d.ts +38 -19
- package/dist/src/gen/video/sfu/models/models.d.ts +76 -9
- package/dist/src/helpers/array.d.ts +7 -0
- package/dist/src/permissions/PermissionsContext.d.ts +6 -0
- package/dist/src/rtc/BasePeerConnection.d.ts +90 -0
- package/dist/src/rtc/Dispatcher.d.ts +0 -1
- package/dist/src/rtc/IceTrickleBuffer.d.ts +3 -2
- package/dist/src/rtc/Publisher.d.ts +32 -86
- package/dist/src/rtc/Subscriber.d.ts +4 -56
- package/dist/src/rtc/TransceiverCache.d.ts +55 -0
- package/dist/src/rtc/codecs.d.ts +1 -15
- package/dist/src/rtc/helpers/sdp.d.ts +8 -0
- package/dist/src/rtc/helpers/tracks.d.ts +1 -0
- package/dist/src/rtc/index.d.ts +3 -0
- package/dist/src/rtc/videoLayers.d.ts +11 -25
- package/dist/src/stats/{stateStoreStatsReporter.d.ts → CallStateStatsReporter.d.ts} +5 -1
- package/dist/src/stats/SfuStatsReporter.d.ts +4 -2
- package/dist/src/stats/index.d.ts +1 -1
- package/dist/src/stats/types.d.ts +8 -0
- package/dist/src/types.d.ts +12 -22
- package/package.json +1 -1
- package/src/Call.ts +254 -268
- package/src/StreamSfuClient.ts +9 -14
- package/src/StreamVideoClient.ts +1 -1
- package/src/__tests__/Call.publishing.test.ts +306 -0
- package/src/devices/CameraManager.ts +33 -16
- package/src/devices/InputMediaDeviceManager.ts +36 -27
- package/src/devices/MicrophoneManager.ts +29 -8
- package/src/devices/ScreenShareManager.ts +6 -8
- package/src/devices/__tests__/CameraManager.test.ts +111 -14
- package/src/devices/__tests__/InputMediaDeviceManager.test.ts +4 -4
- package/src/devices/__tests__/MicrophoneManager.test.ts +59 -21
- package/src/devices/__tests__/ScreenShareManager.test.ts +5 -5
- package/src/devices/__tests__/mocks.ts +1 -0
- package/src/events/__tests__/internal.test.ts +132 -0
- package/src/events/__tests__/mutes.test.ts +0 -3
- package/src/events/__tests__/speaker.test.ts +92 -0
- package/src/events/participant.ts +3 -4
- package/src/gen/video/sfu/event/events.ts +91 -30
- package/src/gen/video/sfu/models/models.ts +105 -13
- package/src/helpers/array.ts +14 -0
- package/src/permissions/PermissionsContext.ts +22 -0
- package/src/permissions/__tests__/PermissionsContext.test.ts +40 -0
- package/src/rpc/__tests__/createClient.test.ts +38 -0
- package/src/rpc/createClient.ts +11 -5
- package/src/rtc/BasePeerConnection.ts +240 -0
- package/src/rtc/Dispatcher.ts +0 -9
- package/src/rtc/IceTrickleBuffer.ts +24 -4
- package/src/rtc/Publisher.ts +210 -528
- package/src/rtc/Subscriber.ts +26 -200
- package/src/rtc/TransceiverCache.ts +120 -0
- package/src/rtc/__tests__/Publisher.test.ts +407 -210
- package/src/rtc/__tests__/Subscriber.test.ts +88 -36
- package/src/rtc/__tests__/mocks/webrtc.mocks.ts +22 -2
- package/src/rtc/__tests__/videoLayers.test.ts +161 -54
- package/src/rtc/codecs.ts +1 -131
- package/src/rtc/helpers/__tests__/rtcConfiguration.test.ts +34 -0
- package/src/rtc/helpers/__tests__/sdp.test.ts +59 -0
- package/src/rtc/helpers/sdp.ts +30 -0
- package/src/rtc/helpers/tracks.ts +3 -0
- package/src/rtc/index.ts +4 -0
- package/src/rtc/videoLayers.ts +68 -76
- package/src/stats/{stateStoreStatsReporter.ts → CallStateStatsReporter.ts} +58 -27
- package/src/stats/SfuStatsReporter.ts +31 -3
- package/src/stats/index.ts +1 -1
- package/src/stats/types.ts +12 -0
- package/src/types.ts +12 -22
- package/dist/src/helpers/sdp-munging.d.ts +0 -24
- package/dist/src/rtc/bitrateLookup.d.ts +0 -2
- package/dist/src/rtc/helpers/iceCandidate.d.ts +0 -2
- package/src/helpers/__tests__/hq-audio-sdp.ts +0 -332
- package/src/helpers/__tests__/sdp-munging.test.ts +0 -283
- package/src/helpers/sdp-munging.ts +0 -265
- package/src/rtc/__tests__/bitrateLookup.test.ts +0 -12
- package/src/rtc/__tests__/codecs.test.ts +0 -145
- package/src/rtc/bitrateLookup.ts +0 -61
- package/src/rtc/helpers/iceCandidate.ts +0 -16
- /package/dist/src/{compatibility.d.ts → helpers/compatibility.d.ts} +0 -0
- /package/src/{compatibility.ts → helpers/compatibility.ts} +0 -0
package/src/Call.ts
CHANGED
|
@@ -2,12 +2,14 @@ import { StreamSfuClient } from './StreamSfuClient';
|
|
|
2
2
|
import {
|
|
3
3
|
Dispatcher,
|
|
4
4
|
getGenericSdp,
|
|
5
|
+
isAudioTrackType,
|
|
5
6
|
isSfuEvent,
|
|
7
|
+
muteTypeToTrackType,
|
|
6
8
|
Publisher,
|
|
7
9
|
Subscriber,
|
|
10
|
+
toRtcConfiguration,
|
|
11
|
+
trackTypeToParticipantStreamKey,
|
|
8
12
|
} from './rtc';
|
|
9
|
-
import { muteTypeToTrackType } from './rtc/helpers/tracks';
|
|
10
|
-
import { toRtcConfiguration } from './rtc/helpers/rtcConfiguration';
|
|
11
13
|
import {
|
|
12
14
|
registerEventHandlers,
|
|
13
15
|
registerRingingCallEventHandlers,
|
|
@@ -27,6 +29,7 @@ import type {
|
|
|
27
29
|
BlockUserRequest,
|
|
28
30
|
BlockUserResponse,
|
|
29
31
|
CallRingEvent,
|
|
32
|
+
CallSettingsResponse,
|
|
30
33
|
CollectUserFeedbackRequest,
|
|
31
34
|
CollectUserFeedbackResponse,
|
|
32
35
|
Credentials,
|
|
@@ -86,9 +89,9 @@ import {
|
|
|
86
89
|
AudioTrackType,
|
|
87
90
|
CallConstructor,
|
|
88
91
|
CallLeaveOptions,
|
|
92
|
+
ClientPublishOptions,
|
|
89
93
|
ClosedCaptionsSettings,
|
|
90
94
|
JoinCallData,
|
|
91
|
-
PublishOptions,
|
|
92
95
|
TrackMuteType,
|
|
93
96
|
VideoTrackType,
|
|
94
97
|
} from './types';
|
|
@@ -96,6 +99,9 @@ import { BehaviorSubject, Subject, takeWhile } from 'rxjs';
|
|
|
96
99
|
import { ReconnectDetails } from './gen/video/sfu/event/events';
|
|
97
100
|
import {
|
|
98
101
|
ClientDetails,
|
|
102
|
+
Codec,
|
|
103
|
+
PublishOption,
|
|
104
|
+
SubscribeOption,
|
|
99
105
|
TrackType,
|
|
100
106
|
WebsocketReconnectStrategy,
|
|
101
107
|
} from './gen/video/sfu/models/models';
|
|
@@ -116,7 +122,6 @@ import {
|
|
|
116
122
|
import { getClientDetails } from './client-details';
|
|
117
123
|
import { getLogger } from './logger';
|
|
118
124
|
import {
|
|
119
|
-
CameraDirection,
|
|
120
125
|
CameraManager,
|
|
121
126
|
MicrophoneManager,
|
|
122
127
|
ScreenShareManager,
|
|
@@ -125,6 +130,7 @@ import {
|
|
|
125
130
|
import { getSdkSignature } from './stats/utils';
|
|
126
131
|
import { withoutConcurrency } from './helpers/concurrency';
|
|
127
132
|
import { ensureExhausted } from './helpers/ensureExhausted';
|
|
133
|
+
import { pushToIfMissing } from './helpers/array';
|
|
128
134
|
import {
|
|
129
135
|
makeSafePromise,
|
|
130
136
|
PromiseWithResolvers,
|
|
@@ -206,7 +212,8 @@ export class Call {
|
|
|
206
212
|
*/
|
|
207
213
|
private readonly dispatcher = new Dispatcher();
|
|
208
214
|
|
|
209
|
-
private
|
|
215
|
+
private clientPublishOptions?: ClientPublishOptions;
|
|
216
|
+
private currentPublishOptions?: PublishOption[];
|
|
210
217
|
private statsReporter?: StatsReporter;
|
|
211
218
|
private sfuStatsReporter?: SfuStatsReporter;
|
|
212
219
|
private dropTimeout: ReturnType<typeof setTimeout> | undefined;
|
|
@@ -292,7 +299,7 @@ export class Call {
|
|
|
292
299
|
this.dynascaleManager = new DynascaleManager(this.state, this.speaker);
|
|
293
300
|
}
|
|
294
301
|
|
|
295
|
-
private async
|
|
302
|
+
private setup = async () => {
|
|
296
303
|
await withoutConcurrency(this.joinLeaveConcurrencyTag, async () => {
|
|
297
304
|
if (this.initialized) return;
|
|
298
305
|
|
|
@@ -303,6 +310,12 @@ export class Call {
|
|
|
303
310
|
}),
|
|
304
311
|
);
|
|
305
312
|
|
|
313
|
+
this.leaveCallHooks.add(
|
|
314
|
+
this.on('changePublishOptions', (event) => {
|
|
315
|
+
this.currentPublishOptions = event.publishOptions;
|
|
316
|
+
}),
|
|
317
|
+
);
|
|
318
|
+
|
|
306
319
|
this.leaveCallHooks.add(registerEventHandlers(this, this.dispatcher));
|
|
307
320
|
this.registerEffects();
|
|
308
321
|
this.registerReconnectHandlers();
|
|
@@ -313,9 +326,9 @@ export class Call {
|
|
|
313
326
|
|
|
314
327
|
this.initialized = true;
|
|
315
328
|
});
|
|
316
|
-
}
|
|
329
|
+
};
|
|
317
330
|
|
|
318
|
-
private registerEffects() {
|
|
331
|
+
private registerEffects = () => {
|
|
319
332
|
this.leaveCallHooks.add(
|
|
320
333
|
// handles updating the permissions context when the settings change.
|
|
321
334
|
createSubscription(this.state.settings$, (settings) => {
|
|
@@ -406,7 +419,7 @@ export class Call {
|
|
|
406
419
|
}
|
|
407
420
|
}),
|
|
408
421
|
);
|
|
409
|
-
}
|
|
422
|
+
};
|
|
410
423
|
|
|
411
424
|
private handleOwnCapabilitiesUpdated = async (
|
|
412
425
|
ownCapabilities: OwnCapability[],
|
|
@@ -545,10 +558,10 @@ export class Call {
|
|
|
545
558
|
this.sfuStatsReporter?.stop();
|
|
546
559
|
this.sfuStatsReporter = undefined;
|
|
547
560
|
|
|
548
|
-
this.subscriber?.
|
|
561
|
+
this.subscriber?.dispose();
|
|
549
562
|
this.subscriber = undefined;
|
|
550
563
|
|
|
551
|
-
this.publisher?.
|
|
564
|
+
this.publisher?.dispose();
|
|
552
565
|
this.publisher = undefined;
|
|
553
566
|
|
|
554
567
|
await this.sfuClient?.leaveAndClose(reason);
|
|
@@ -613,9 +626,8 @@ export class Call {
|
|
|
613
626
|
// call.ring event excludes the call creator in the members list
|
|
614
627
|
// as the creator does not get the ring event
|
|
615
628
|
// so update the member list accordingly
|
|
616
|
-
const
|
|
617
|
-
|
|
618
|
-
);
|
|
629
|
+
const { created_by, settings } = event.call;
|
|
630
|
+
const creator = this.state.members.find((m) => m.user.id === created_by.id);
|
|
619
631
|
if (!creator) {
|
|
620
632
|
this.state.setMembers(event.members);
|
|
621
633
|
} else {
|
|
@@ -629,7 +641,7 @@ export class Call {
|
|
|
629
641
|
// const calls = useCalls().filter((c) => c.ringing);
|
|
630
642
|
const calls = this.clientStore.calls.filter((c) => c.cid !== this.cid);
|
|
631
643
|
this.clientStore.setCalls([this, ...calls]);
|
|
632
|
-
await this.applyDeviceConfig(false);
|
|
644
|
+
await this.applyDeviceConfig(settings, false);
|
|
633
645
|
};
|
|
634
646
|
|
|
635
647
|
/**
|
|
@@ -664,7 +676,7 @@ export class Call {
|
|
|
664
676
|
this.clientStore.registerCall(this);
|
|
665
677
|
}
|
|
666
678
|
|
|
667
|
-
await this.applyDeviceConfig(false);
|
|
679
|
+
await this.applyDeviceConfig(response.call.settings, false);
|
|
668
680
|
|
|
669
681
|
return response;
|
|
670
682
|
};
|
|
@@ -695,7 +707,7 @@ export class Call {
|
|
|
695
707
|
this.clientStore.registerCall(this);
|
|
696
708
|
}
|
|
697
709
|
|
|
698
|
-
await this.applyDeviceConfig(false);
|
|
710
|
+
await this.applyDeviceConfig(response.call.settings, false);
|
|
699
711
|
|
|
700
712
|
return response;
|
|
701
713
|
};
|
|
@@ -820,20 +832,35 @@ export class Call {
|
|
|
820
832
|
// we don't need to send JoinRequest if we are re-using an existing healthy SFU client
|
|
821
833
|
if (previousSfuClient !== sfuClient) {
|
|
822
834
|
// prepare a generic SDP and send it to the SFU.
|
|
823
|
-
//
|
|
835
|
+
// these are throw-away SDPs that the SFU will use to determine
|
|
824
836
|
// the capabilities of the client (codec support, etc.)
|
|
825
|
-
const
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
+
const [subscriberSdp, publisherSdp] = await Promise.all([
|
|
838
|
+
getGenericSdp('recvonly'),
|
|
839
|
+
getGenericSdp('sendonly'),
|
|
840
|
+
]);
|
|
841
|
+
const isReconnecting =
|
|
842
|
+
this.reconnectStrategy !== WebsocketReconnectStrategy.UNSPECIFIED;
|
|
843
|
+
const reconnectDetails = isReconnecting
|
|
844
|
+
? this.getReconnectDetails(data?.migrating_from, previousSessionId)
|
|
845
|
+
: undefined;
|
|
846
|
+
const preferredPublishOptions = !isReconnecting
|
|
847
|
+
? this.getPreferredPublishOptions()
|
|
848
|
+
: this.currentPublishOptions || [];
|
|
849
|
+
const preferredSubscribeOptions = !isReconnecting
|
|
850
|
+
? this.getPreferredSubscribeOptions()
|
|
851
|
+
: [];
|
|
852
|
+
const { callState, fastReconnectDeadlineSeconds, publishOptions } =
|
|
853
|
+
await sfuClient.join({
|
|
854
|
+
subscriberSdp,
|
|
855
|
+
publisherSdp,
|
|
856
|
+
clientDetails,
|
|
857
|
+
fastReconnect: performingFastReconnect,
|
|
858
|
+
reconnectDetails,
|
|
859
|
+
preferredPublishOptions,
|
|
860
|
+
preferredSubscribeOptions,
|
|
861
|
+
});
|
|
862
|
+
|
|
863
|
+
this.currentPublishOptions = publishOptions;
|
|
837
864
|
this.fastReconnectDeadlineSeconds = fastReconnectDeadlineSeconds;
|
|
838
865
|
if (callState) {
|
|
839
866
|
this.state.updateFromSfuCallState(
|
|
@@ -863,18 +890,16 @@ export class Call {
|
|
|
863
890
|
connectionConfig,
|
|
864
891
|
clientDetails,
|
|
865
892
|
statsOptions,
|
|
893
|
+
publishOptions: this.currentPublishOptions || [],
|
|
866
894
|
closePreviousInstances: !performingMigration,
|
|
867
895
|
});
|
|
868
896
|
}
|
|
869
897
|
|
|
870
898
|
// make sure we only track connection timing if we are not calling this method as part of a reconnection flow
|
|
871
899
|
if (!performingRejoin && !performingFastReconnect && !performingMigration) {
|
|
872
|
-
this.sfuStatsReporter?.
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
connectionTimeSeconds: (Date.now() - connectStartTime) / 1000,
|
|
876
|
-
},
|
|
877
|
-
});
|
|
900
|
+
this.sfuStatsReporter?.sendConnectionTime(
|
|
901
|
+
(Date.now() - connectStartTime) / 1000,
|
|
902
|
+
);
|
|
878
903
|
}
|
|
879
904
|
|
|
880
905
|
if (performingRejoin) {
|
|
@@ -891,8 +916,8 @@ export class Call {
|
|
|
891
916
|
|
|
892
917
|
// device settings should be applied only once, we don't have to
|
|
893
918
|
// re-apply them on later reconnections or server-side data fetches
|
|
894
|
-
if (!this.deviceSettingsAppliedOnce) {
|
|
895
|
-
await this.applyDeviceConfig(true);
|
|
919
|
+
if (!this.deviceSettingsAppliedOnce && this.state.settings) {
|
|
920
|
+
await this.applyDeviceConfig(this.state.settings, true);
|
|
896
921
|
this.deviceSettingsAppliedOnce = true;
|
|
897
922
|
}
|
|
898
923
|
|
|
@@ -902,6 +927,8 @@ export class Call {
|
|
|
902
927
|
// we will spam the other participants with push notifications and `call.ring` events.
|
|
903
928
|
delete this.joinCallData?.ring;
|
|
904
929
|
delete this.joinCallData?.notify;
|
|
930
|
+
// reset the reconnect strategy to unspecified after a successful reconnection
|
|
931
|
+
this.reconnectStrategy = WebsocketReconnectStrategy.UNSPECIFIED;
|
|
905
932
|
|
|
906
933
|
this.logger('info', `Joined call ${this.cid}`);
|
|
907
934
|
};
|
|
@@ -916,7 +943,8 @@ export class Call {
|
|
|
916
943
|
): ReconnectDetails => {
|
|
917
944
|
const strategy = this.reconnectStrategy;
|
|
918
945
|
const performingRejoin = strategy === WebsocketReconnectStrategy.REJOIN;
|
|
919
|
-
const announcedTracks =
|
|
946
|
+
const announcedTracks =
|
|
947
|
+
this.publisher?.getAnnouncedTracksForReconnect() || [];
|
|
920
948
|
return {
|
|
921
949
|
strategy,
|
|
922
950
|
announcedTracks,
|
|
@@ -927,6 +955,62 @@ export class Call {
|
|
|
927
955
|
};
|
|
928
956
|
};
|
|
929
957
|
|
|
958
|
+
/**
|
|
959
|
+
* Prepares the preferred codec for the call.
|
|
960
|
+
* This is an experimental client feature and subject to change.
|
|
961
|
+
* @internal
|
|
962
|
+
*/
|
|
963
|
+
private getPreferredPublishOptions = (): PublishOption[] => {
|
|
964
|
+
const { preferredCodec, fmtpLine, preferredBitrate, maxSimulcastLayers } =
|
|
965
|
+
this.clientPublishOptions || {};
|
|
966
|
+
if (!preferredCodec && !preferredBitrate && !maxSimulcastLayers) return [];
|
|
967
|
+
|
|
968
|
+
const codec = preferredCodec
|
|
969
|
+
? Codec.create({ name: preferredCodec.split('/').pop(), fmtp: fmtpLine })
|
|
970
|
+
: undefined;
|
|
971
|
+
|
|
972
|
+
const preferredPublishOptions = [
|
|
973
|
+
PublishOption.create({
|
|
974
|
+
trackType: TrackType.VIDEO,
|
|
975
|
+
codec,
|
|
976
|
+
bitrate: preferredBitrate,
|
|
977
|
+
maxSpatialLayers: maxSimulcastLayers,
|
|
978
|
+
}),
|
|
979
|
+
];
|
|
980
|
+
|
|
981
|
+
const screenShareSettings = this.screenShare.getSettings();
|
|
982
|
+
if (screenShareSettings) {
|
|
983
|
+
preferredPublishOptions.push(
|
|
984
|
+
PublishOption.create({
|
|
985
|
+
trackType: TrackType.SCREEN_SHARE,
|
|
986
|
+
fps: screenShareSettings.maxFramerate,
|
|
987
|
+
bitrate: screenShareSettings.maxBitrate,
|
|
988
|
+
}),
|
|
989
|
+
);
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
return preferredPublishOptions;
|
|
993
|
+
};
|
|
994
|
+
|
|
995
|
+
/**
|
|
996
|
+
* Prepares the preferred options for subscribing to tracks.
|
|
997
|
+
* This is an experimental client feature and subject to change.
|
|
998
|
+
* @internal
|
|
999
|
+
*/
|
|
1000
|
+
private getPreferredSubscribeOptions = (): SubscribeOption[] => {
|
|
1001
|
+
const { subscriberCodec, subscriberFmtpLine } =
|
|
1002
|
+
this.clientPublishOptions || {};
|
|
1003
|
+
if (!subscriberCodec || !subscriberFmtpLine) return [];
|
|
1004
|
+
return [
|
|
1005
|
+
SubscribeOption.create({
|
|
1006
|
+
trackType: TrackType.VIDEO,
|
|
1007
|
+
codecs: [
|
|
1008
|
+
{ name: subscriberCodec.split('/').pop(), fmtp: subscriberFmtpLine },
|
|
1009
|
+
],
|
|
1010
|
+
}),
|
|
1011
|
+
];
|
|
1012
|
+
};
|
|
1013
|
+
|
|
930
1014
|
/**
|
|
931
1015
|
* Performs an ICE restart on both the Publisher and Subscriber Peer Connections.
|
|
932
1016
|
* Uses the provided SFU client to restore the ICE connection.
|
|
@@ -965,6 +1049,7 @@ export class Call {
|
|
|
965
1049
|
connectionConfig: RTCConfiguration;
|
|
966
1050
|
statsOptions: StatsOptions;
|
|
967
1051
|
clientDetails: ClientDetails;
|
|
1052
|
+
publishOptions: PublishOption[];
|
|
968
1053
|
closePreviousInstances: boolean;
|
|
969
1054
|
}) => {
|
|
970
1055
|
const {
|
|
@@ -972,10 +1057,11 @@ export class Call {
|
|
|
972
1057
|
connectionConfig,
|
|
973
1058
|
clientDetails,
|
|
974
1059
|
statsOptions,
|
|
1060
|
+
publishOptions,
|
|
975
1061
|
closePreviousInstances,
|
|
976
1062
|
} = opts;
|
|
977
1063
|
if (closePreviousInstances && this.subscriber) {
|
|
978
|
-
this.subscriber.
|
|
1064
|
+
this.subscriber.dispose();
|
|
979
1065
|
}
|
|
980
1066
|
this.subscriber = new Subscriber({
|
|
981
1067
|
sfuClient,
|
|
@@ -999,18 +1085,14 @@ export class Call {
|
|
|
999
1085
|
const isAnonymous = this.streamClient.user?.type === 'anonymous';
|
|
1000
1086
|
if (!isAnonymous) {
|
|
1001
1087
|
if (closePreviousInstances && this.publisher) {
|
|
1002
|
-
this.publisher.
|
|
1088
|
+
this.publisher.dispose();
|
|
1003
1089
|
}
|
|
1004
|
-
const audioSettings = this.state.settings?.audio;
|
|
1005
|
-
const isDtxEnabled = !!audioSettings?.opus_dtx_enabled;
|
|
1006
|
-
const isRedEnabled = !!audioSettings?.redundant_coding_enabled;
|
|
1007
1090
|
this.publisher = new Publisher({
|
|
1008
1091
|
sfuClient,
|
|
1009
1092
|
dispatcher: this.dispatcher,
|
|
1010
1093
|
state: this.state,
|
|
1011
1094
|
connectionConfig,
|
|
1012
|
-
|
|
1013
|
-
isRedEnabled,
|
|
1095
|
+
publishOptions,
|
|
1014
1096
|
logTag: String(this.sfuClientTag),
|
|
1015
1097
|
onUnrecoverableError: () => {
|
|
1016
1098
|
this.reconnect(WebsocketReconnectStrategy.REJOIN).catch((err) => {
|
|
@@ -1200,19 +1282,14 @@ export class Call {
|
|
|
1200
1282
|
* @internal
|
|
1201
1283
|
*/
|
|
1202
1284
|
private reconnectFast = async () => {
|
|
1203
|
-
|
|
1285
|
+
const reconnectStartTime = Date.now();
|
|
1204
1286
|
this.reconnectStrategy = WebsocketReconnectStrategy.FAST;
|
|
1205
1287
|
this.state.setCallingState(CallingState.RECONNECTING);
|
|
1206
1288
|
await this.join(this.joinCallData);
|
|
1207
|
-
this.sfuStatsReporter?.
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
timeSeconds: (Date.now() - reconnectStartTime) / 1000,
|
|
1212
|
-
strategy: WebsocketReconnectStrategy.FAST,
|
|
1213
|
-
},
|
|
1214
|
-
},
|
|
1215
|
-
});
|
|
1289
|
+
this.sfuStatsReporter?.sendReconnectionTime(
|
|
1290
|
+
WebsocketReconnectStrategy.FAST,
|
|
1291
|
+
(Date.now() - reconnectStartTime) / 1000,
|
|
1292
|
+
);
|
|
1216
1293
|
};
|
|
1217
1294
|
|
|
1218
1295
|
/**
|
|
@@ -1220,21 +1297,16 @@ export class Call {
|
|
|
1220
1297
|
* @internal
|
|
1221
1298
|
*/
|
|
1222
1299
|
private reconnectRejoin = async () => {
|
|
1223
|
-
|
|
1300
|
+
const reconnectStartTime = Date.now();
|
|
1224
1301
|
this.reconnectStrategy = WebsocketReconnectStrategy.REJOIN;
|
|
1225
1302
|
this.state.setCallingState(CallingState.RECONNECTING);
|
|
1226
1303
|
await this.join(this.joinCallData);
|
|
1227
1304
|
await this.restorePublishedTracks();
|
|
1228
1305
|
this.restoreSubscribedTracks();
|
|
1229
|
-
this.sfuStatsReporter?.
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
timeSeconds: (Date.now() - reconnectStartTime) / 1000,
|
|
1234
|
-
strategy: WebsocketReconnectStrategy.REJOIN,
|
|
1235
|
-
},
|
|
1236
|
-
},
|
|
1237
|
-
});
|
|
1306
|
+
this.sfuStatsReporter?.sendReconnectionTime(
|
|
1307
|
+
WebsocketReconnectStrategy.REJOIN,
|
|
1308
|
+
(Date.now() - reconnectStartTime) / 1000,
|
|
1309
|
+
);
|
|
1238
1310
|
};
|
|
1239
1311
|
|
|
1240
1312
|
/**
|
|
@@ -1242,7 +1314,7 @@ export class Call {
|
|
|
1242
1314
|
* @internal
|
|
1243
1315
|
*/
|
|
1244
1316
|
private reconnectMigrate = async () => {
|
|
1245
|
-
|
|
1317
|
+
const reconnectStartTime = Date.now();
|
|
1246
1318
|
const currentSfuClient = this.sfuClient;
|
|
1247
1319
|
if (!currentSfuClient) {
|
|
1248
1320
|
throw new Error('Cannot migrate without an active SFU client');
|
|
@@ -1281,21 +1353,16 @@ export class Call {
|
|
|
1281
1353
|
// the `migrationTask`
|
|
1282
1354
|
this.state.setCallingState(CallingState.JOINED);
|
|
1283
1355
|
} finally {
|
|
1284
|
-
currentSubscriber?.
|
|
1285
|
-
currentPublisher?.
|
|
1356
|
+
currentSubscriber?.dispose();
|
|
1357
|
+
currentPublisher?.dispose();
|
|
1286
1358
|
|
|
1287
1359
|
// and close the previous SFU client, without specifying close code
|
|
1288
1360
|
currentSfuClient.close();
|
|
1289
1361
|
}
|
|
1290
|
-
this.sfuStatsReporter?.
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
timeSeconds: (Date.now() - reconnectStartTime) / 1000,
|
|
1295
|
-
strategy: WebsocketReconnectStrategy.MIGRATE,
|
|
1296
|
-
},
|
|
1297
|
-
},
|
|
1298
|
-
});
|
|
1362
|
+
this.sfuStatsReporter?.sendReconnectionTime(
|
|
1363
|
+
WebsocketReconnectStrategy.MIGRATE,
|
|
1364
|
+
(Date.now() - reconnectStartTime) / 1000,
|
|
1365
|
+
);
|
|
1299
1366
|
};
|
|
1300
1367
|
|
|
1301
1368
|
/**
|
|
@@ -1384,22 +1451,16 @@ export class Call {
|
|
|
1384
1451
|
// the tracks need to be restored in their original order of publishing
|
|
1385
1452
|
// otherwise, we might get `m-lines order mismatch` errors
|
|
1386
1453
|
for (const trackType of this.trackPublishOrder) {
|
|
1454
|
+
let mediaStream: MediaStream | undefined;
|
|
1387
1455
|
switch (trackType) {
|
|
1388
1456
|
case TrackType.AUDIO:
|
|
1389
|
-
|
|
1390
|
-
if (audioStream) {
|
|
1391
|
-
await this.publishAudioStream(audioStream);
|
|
1392
|
-
}
|
|
1457
|
+
mediaStream = this.microphone.state.mediaStream;
|
|
1393
1458
|
break;
|
|
1394
1459
|
case TrackType.VIDEO:
|
|
1395
|
-
|
|
1396
|
-
if (videoStream) await this.publishVideoStream(videoStream);
|
|
1460
|
+
mediaStream = this.camera.state.mediaStream;
|
|
1397
1461
|
break;
|
|
1398
1462
|
case TrackType.SCREEN_SHARE:
|
|
1399
|
-
|
|
1400
|
-
if (screenShareStream) {
|
|
1401
|
-
await this.publishScreenShareStream(screenShareStream);
|
|
1402
|
-
}
|
|
1463
|
+
mediaStream = this.screenShare.state.mediaStream;
|
|
1403
1464
|
break;
|
|
1404
1465
|
// screen share audio can't exist without a screen share, so we handle it there
|
|
1405
1466
|
case TrackType.SCREEN_SHARE_AUDIO:
|
|
@@ -1409,6 +1470,8 @@ export class Call {
|
|
|
1409
1470
|
ensureExhausted(trackType, 'Unknown track type');
|
|
1410
1471
|
break;
|
|
1411
1472
|
}
|
|
1473
|
+
|
|
1474
|
+
if (mediaStream) await this.publish(mediaStream, trackType);
|
|
1412
1475
|
}
|
|
1413
1476
|
};
|
|
1414
1477
|
|
|
@@ -1424,136 +1487,112 @@ export class Call {
|
|
|
1424
1487
|
|
|
1425
1488
|
/**
|
|
1426
1489
|
* Starts publishing the given video stream to the call.
|
|
1427
|
-
*
|
|
1428
|
-
*
|
|
1429
|
-
* Consecutive calls to this method will replace the previously published stream.
|
|
1430
|
-
* The previous video stream will be stopped.
|
|
1431
|
-
*
|
|
1432
|
-
* @param videoStream the video stream to publish.
|
|
1490
|
+
* @deprecated use `call.publish()`.
|
|
1433
1491
|
*/
|
|
1434
1492
|
publishVideoStream = async (videoStream: MediaStream) => {
|
|
1435
|
-
|
|
1436
|
-
// joining is in progress, and we should wait until the client is ready
|
|
1437
|
-
await this.sfuClient.joinTask;
|
|
1438
|
-
|
|
1439
|
-
if (!this.permissionsContext.hasPermission(OwnCapability.SEND_VIDEO)) {
|
|
1440
|
-
throw new Error('No permission to publish video');
|
|
1441
|
-
}
|
|
1442
|
-
|
|
1443
|
-
if (!this.publisher) throw new Error('Publisher is not initialized');
|
|
1444
|
-
|
|
1445
|
-
const [videoTrack] = videoStream.getVideoTracks();
|
|
1446
|
-
if (!videoTrack) throw new Error('There is no video track in the stream');
|
|
1447
|
-
|
|
1448
|
-
if (!this.trackPublishOrder.includes(TrackType.VIDEO)) {
|
|
1449
|
-
this.trackPublishOrder.push(TrackType.VIDEO);
|
|
1450
|
-
}
|
|
1451
|
-
|
|
1452
|
-
await this.publisher.publishStream(
|
|
1453
|
-
videoStream,
|
|
1454
|
-
videoTrack,
|
|
1455
|
-
TrackType.VIDEO,
|
|
1456
|
-
this.publishOptions,
|
|
1457
|
-
);
|
|
1493
|
+
await this.publish(videoStream, TrackType.VIDEO);
|
|
1458
1494
|
};
|
|
1459
1495
|
|
|
1460
1496
|
/**
|
|
1461
1497
|
* Starts publishing the given audio stream to the call.
|
|
1462
|
-
*
|
|
1463
|
-
*
|
|
1464
|
-
* Consecutive calls to this method will replace the audio stream that is currently being published.
|
|
1465
|
-
* The previous audio stream will be stopped.
|
|
1466
|
-
*
|
|
1467
|
-
* @param audioStream the audio stream to publish.
|
|
1498
|
+
* @deprecated use `call.publish()`
|
|
1468
1499
|
*/
|
|
1469
1500
|
publishAudioStream = async (audioStream: MediaStream) => {
|
|
1470
|
-
|
|
1471
|
-
// joining is in progress, and we should wait until the client is ready
|
|
1472
|
-
await this.sfuClient.joinTask;
|
|
1473
|
-
|
|
1474
|
-
if (!this.permissionsContext.hasPermission(OwnCapability.SEND_AUDIO)) {
|
|
1475
|
-
throw new Error('No permission to publish audio');
|
|
1476
|
-
}
|
|
1477
|
-
|
|
1478
|
-
if (!this.publisher) throw new Error('Publisher is not initialized');
|
|
1479
|
-
|
|
1480
|
-
const [audioTrack] = audioStream.getAudioTracks();
|
|
1481
|
-
if (!audioTrack) throw new Error('There is no audio track in the stream');
|
|
1482
|
-
|
|
1483
|
-
if (!this.trackPublishOrder.includes(TrackType.AUDIO)) {
|
|
1484
|
-
this.trackPublishOrder.push(TrackType.AUDIO);
|
|
1485
|
-
}
|
|
1486
|
-
await this.publisher.publishStream(
|
|
1487
|
-
audioStream,
|
|
1488
|
-
audioTrack,
|
|
1489
|
-
TrackType.AUDIO,
|
|
1490
|
-
);
|
|
1501
|
+
await this.publish(audioStream, TrackType.AUDIO);
|
|
1491
1502
|
};
|
|
1492
1503
|
|
|
1493
1504
|
/**
|
|
1494
1505
|
* Starts publishing the given screen-share stream to the call.
|
|
1495
|
-
*
|
|
1496
|
-
* Consecutive calls to this method will replace the previous screen-share stream.
|
|
1497
|
-
* The previous screen-share stream will be stopped.
|
|
1498
|
-
*
|
|
1499
|
-
* @param screenShareStream the screen-share stream to publish.
|
|
1506
|
+
* @deprecated use `call.publish()`
|
|
1500
1507
|
*/
|
|
1501
1508
|
publishScreenShareStream = async (screenShareStream: MediaStream) => {
|
|
1509
|
+
await this.publish(screenShareStream, TrackType.SCREEN_SHARE);
|
|
1510
|
+
};
|
|
1511
|
+
|
|
1512
|
+
/**
|
|
1513
|
+
* Publishes the given media stream.
|
|
1514
|
+
*
|
|
1515
|
+
* @param mediaStream the media stream to publish.
|
|
1516
|
+
* @param trackType the type of the track to announce.
|
|
1517
|
+
*/
|
|
1518
|
+
publish = async (mediaStream: MediaStream, trackType: TrackType) => {
|
|
1502
1519
|
if (!this.sfuClient) throw new Error(`Call not joined yet.`);
|
|
1503
1520
|
// joining is in progress, and we should wait until the client is ready
|
|
1504
1521
|
await this.sfuClient.joinTask;
|
|
1505
1522
|
|
|
1506
|
-
if (!this.permissionsContext.
|
|
1507
|
-
throw new Error(
|
|
1523
|
+
if (!this.permissionsContext.canPublish(trackType)) {
|
|
1524
|
+
throw new Error(`No permission to publish ${TrackType[trackType]}`);
|
|
1508
1525
|
}
|
|
1509
1526
|
|
|
1510
1527
|
if (!this.publisher) throw new Error('Publisher is not initialized');
|
|
1511
1528
|
|
|
1512
|
-
const [
|
|
1513
|
-
|
|
1514
|
-
|
|
1529
|
+
const [track] = isAudioTrackType(trackType)
|
|
1530
|
+
? mediaStream.getAudioTracks()
|
|
1531
|
+
: mediaStream.getVideoTracks();
|
|
1532
|
+
|
|
1533
|
+
if (!track) {
|
|
1534
|
+
throw new Error(
|
|
1535
|
+
`There is no ${TrackType[trackType]} track in the stream`,
|
|
1536
|
+
);
|
|
1515
1537
|
}
|
|
1516
1538
|
|
|
1517
|
-
if (
|
|
1518
|
-
|
|
1539
|
+
if (track.readyState === 'ended') {
|
|
1540
|
+
throw new Error(`Can't publish ended tracks.`);
|
|
1519
1541
|
}
|
|
1520
|
-
const opts: PublishOptions = {
|
|
1521
|
-
screenShareSettings: this.screenShare.getSettings(),
|
|
1522
|
-
};
|
|
1523
|
-
await this.publisher.publishStream(
|
|
1524
|
-
screenShareStream,
|
|
1525
|
-
screenShareTrack,
|
|
1526
|
-
TrackType.SCREEN_SHARE,
|
|
1527
|
-
opts,
|
|
1528
|
-
);
|
|
1529
1542
|
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1543
|
+
pushToIfMissing(this.trackPublishOrder, trackType);
|
|
1544
|
+
await this.publisher.publish(track, trackType);
|
|
1545
|
+
|
|
1546
|
+
const trackTypes = [trackType];
|
|
1547
|
+
if (trackType === TrackType.SCREEN_SHARE) {
|
|
1548
|
+
const [audioTrack] = mediaStream.getAudioTracks();
|
|
1549
|
+
if (audioTrack) {
|
|
1550
|
+
pushToIfMissing(this.trackPublishOrder, TrackType.SCREEN_SHARE_AUDIO);
|
|
1551
|
+
await this.publisher.publish(audioTrack, TrackType.SCREEN_SHARE_AUDIO);
|
|
1552
|
+
trackTypes.push(TrackType.SCREEN_SHARE_AUDIO);
|
|
1534
1553
|
}
|
|
1535
|
-
await this.publisher.publishStream(
|
|
1536
|
-
screenShareStream,
|
|
1537
|
-
screenShareAudioTrack,
|
|
1538
|
-
TrackType.SCREEN_SHARE_AUDIO,
|
|
1539
|
-
opts,
|
|
1540
|
-
);
|
|
1541
1554
|
}
|
|
1555
|
+
|
|
1556
|
+
await this.updateLocalStreamState(mediaStream, ...trackTypes);
|
|
1542
1557
|
};
|
|
1543
1558
|
|
|
1544
1559
|
/**
|
|
1545
1560
|
* Stops publishing the given track type to the call, if it is currently being published.
|
|
1546
|
-
* Underlying track will be stopped and removed from the publisher.
|
|
1547
1561
|
*
|
|
1548
|
-
* @param
|
|
1549
|
-
* @param stopTrack if `true` the track will be stopped, else it will be just disabled
|
|
1562
|
+
* @param trackTypes the track types to stop publishing.
|
|
1550
1563
|
*/
|
|
1551
|
-
stopPublish = async (
|
|
1552
|
-
this.
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1564
|
+
stopPublish = async (...trackTypes: TrackType[]) => {
|
|
1565
|
+
if (!this.sfuClient || !this.publisher) return;
|
|
1566
|
+
this.publisher.stopTracks(...trackTypes);
|
|
1567
|
+
await this.updateLocalStreamState(undefined, ...trackTypes);
|
|
1568
|
+
};
|
|
1569
|
+
|
|
1570
|
+
/**
|
|
1571
|
+
* Updates the call state with the new stream.
|
|
1572
|
+
*
|
|
1573
|
+
* @param mediaStream the new stream to update the call state with.
|
|
1574
|
+
* If undefined, the stream will be removed from the call state.
|
|
1575
|
+
* @param trackTypes the track types to update the call state with.
|
|
1576
|
+
*/
|
|
1577
|
+
private updateLocalStreamState = async (
|
|
1578
|
+
mediaStream: MediaStream | undefined,
|
|
1579
|
+
...trackTypes: TrackType[]
|
|
1580
|
+
) => {
|
|
1581
|
+
if (!this.sfuClient || !this.sfuClient.sessionId) return;
|
|
1582
|
+
await this.notifyTrackMuteState(!mediaStream, ...trackTypes);
|
|
1583
|
+
|
|
1584
|
+
const { sessionId } = this.sfuClient;
|
|
1585
|
+
for (const trackType of trackTypes) {
|
|
1586
|
+
const streamStateProp = trackTypeToParticipantStreamKey(trackType);
|
|
1587
|
+
if (!streamStateProp) continue;
|
|
1588
|
+
|
|
1589
|
+
this.state.updateParticipant(sessionId, (p) => ({
|
|
1590
|
+
publishedTracks: mediaStream
|
|
1591
|
+
? pushToIfMissing([...p.publishedTracks], trackType)
|
|
1592
|
+
: p.publishedTracks.filter((t) => t !== trackType),
|
|
1593
|
+
[streamStateProp]: mediaStream,
|
|
1594
|
+
}));
|
|
1595
|
+
}
|
|
1557
1596
|
};
|
|
1558
1597
|
|
|
1559
1598
|
/**
|
|
@@ -1562,9 +1601,20 @@ export class Call {
|
|
|
1562
1601
|
* @internal
|
|
1563
1602
|
* @param options the options to use.
|
|
1564
1603
|
*/
|
|
1565
|
-
updatePublishOptions(options:
|
|
1566
|
-
this.
|
|
1567
|
-
|
|
1604
|
+
updatePublishOptions = (options: ClientPublishOptions) => {
|
|
1605
|
+
this.logger(
|
|
1606
|
+
'warn',
|
|
1607
|
+
'[call.updatePublishOptions]: You are manually overriding the publish options for this call. ' +
|
|
1608
|
+
'This is not recommended, and it can cause call stability/compatibility issues. Use with caution.',
|
|
1609
|
+
);
|
|
1610
|
+
if (this.state.callingState === CallingState.JOINED) {
|
|
1611
|
+
this.logger(
|
|
1612
|
+
'warn',
|
|
1613
|
+
'Updating publish options after joining the call does not have an effect',
|
|
1614
|
+
);
|
|
1615
|
+
}
|
|
1616
|
+
this.clientPublishOptions = { ...this.clientPublishOptions, ...options };
|
|
1617
|
+
};
|
|
1568
1618
|
|
|
1569
1619
|
/**
|
|
1570
1620
|
* Notifies the SFU that a noise cancellation process has started.
|
|
@@ -1588,6 +1638,17 @@ export class Call {
|
|
|
1588
1638
|
});
|
|
1589
1639
|
};
|
|
1590
1640
|
|
|
1641
|
+
/**
|
|
1642
|
+
* Notifies the SFU about the mute state of the given track types.
|
|
1643
|
+
* @internal
|
|
1644
|
+
*/
|
|
1645
|
+
notifyTrackMuteState = async (muted: boolean, ...trackTypes: TrackType[]) => {
|
|
1646
|
+
if (!this.sfuClient) return;
|
|
1647
|
+
await this.sfuClient.updateMuteStates(
|
|
1648
|
+
trackTypes.map((trackType) => ({ trackType, muted })),
|
|
1649
|
+
);
|
|
1650
|
+
};
|
|
1651
|
+
|
|
1591
1652
|
/**
|
|
1592
1653
|
* Will enhance the reported stats with additional participant-specific information (`callStatsReport$` state [store variable](./StreamVideoClient.md/#readonlystatestore)).
|
|
1593
1654
|
* This is usually helpful when detailed stats for a specific participant are needed.
|
|
@@ -2202,93 +2263,18 @@ export class Call {
|
|
|
2202
2263
|
*
|
|
2203
2264
|
* @internal
|
|
2204
2265
|
*/
|
|
2205
|
-
applyDeviceConfig = async (
|
|
2206
|
-
|
|
2266
|
+
applyDeviceConfig = async (
|
|
2267
|
+
settings: CallSettingsResponse,
|
|
2268
|
+
publish: boolean,
|
|
2269
|
+
) => {
|
|
2270
|
+
await this.camera.apply(settings.video, publish).catch((err) => {
|
|
2207
2271
|
this.logger('warn', 'Camera init failed', err);
|
|
2208
2272
|
});
|
|
2209
|
-
await this.
|
|
2273
|
+
await this.microphone.apply(settings.audio, publish).catch((err) => {
|
|
2210
2274
|
this.logger('warn', 'Mic init failed', err);
|
|
2211
2275
|
});
|
|
2212
2276
|
};
|
|
2213
2277
|
|
|
2214
|
-
private initCamera = async (options: { setStatus: boolean }) => {
|
|
2215
|
-
// Wait for any in progress camera operation
|
|
2216
|
-
await this.camera.statusChangeSettled();
|
|
2217
|
-
|
|
2218
|
-
if (
|
|
2219
|
-
this.state.localParticipant?.videoStream ||
|
|
2220
|
-
!this.permissionsContext.hasPermission('send-video')
|
|
2221
|
-
) {
|
|
2222
|
-
return;
|
|
2223
|
-
}
|
|
2224
|
-
|
|
2225
|
-
// Set camera direction if it's not yet set
|
|
2226
|
-
if (!this.camera.state.direction && !this.camera.state.selectedDevice) {
|
|
2227
|
-
let defaultDirection: CameraDirection = 'front';
|
|
2228
|
-
const backendSetting = this.state.settings?.video.camera_facing;
|
|
2229
|
-
if (backendSetting) {
|
|
2230
|
-
defaultDirection = backendSetting === 'front' ? 'front' : 'back';
|
|
2231
|
-
}
|
|
2232
|
-
this.camera.state.setDirection(defaultDirection);
|
|
2233
|
-
}
|
|
2234
|
-
|
|
2235
|
-
// Set target resolution
|
|
2236
|
-
const targetResolution = this.state.settings?.video.target_resolution;
|
|
2237
|
-
if (targetResolution) {
|
|
2238
|
-
await this.camera.selectTargetResolution(targetResolution);
|
|
2239
|
-
}
|
|
2240
|
-
|
|
2241
|
-
if (options.setStatus) {
|
|
2242
|
-
// Publish already that was set before we joined
|
|
2243
|
-
if (
|
|
2244
|
-
this.camera.enabled &&
|
|
2245
|
-
this.camera.state.mediaStream &&
|
|
2246
|
-
!this.publisher?.isPublishing(TrackType.VIDEO)
|
|
2247
|
-
) {
|
|
2248
|
-
await this.publishVideoStream(this.camera.state.mediaStream);
|
|
2249
|
-
}
|
|
2250
|
-
|
|
2251
|
-
// Start camera if backend config specifies, and there is no local setting
|
|
2252
|
-
if (
|
|
2253
|
-
this.camera.state.status === undefined &&
|
|
2254
|
-
this.state.settings?.video.camera_default_on
|
|
2255
|
-
) {
|
|
2256
|
-
await this.camera.enable();
|
|
2257
|
-
}
|
|
2258
|
-
}
|
|
2259
|
-
};
|
|
2260
|
-
|
|
2261
|
-
private initMic = async (options: { setStatus: boolean }) => {
|
|
2262
|
-
// Wait for any in progress mic operation
|
|
2263
|
-
await this.microphone.statusChangeSettled();
|
|
2264
|
-
|
|
2265
|
-
if (
|
|
2266
|
-
this.state.localParticipant?.audioStream ||
|
|
2267
|
-
!this.permissionsContext.hasPermission('send-audio')
|
|
2268
|
-
) {
|
|
2269
|
-
return;
|
|
2270
|
-
}
|
|
2271
|
-
|
|
2272
|
-
if (options.setStatus) {
|
|
2273
|
-
// Publish media stream that was set before we joined
|
|
2274
|
-
if (
|
|
2275
|
-
this.microphone.enabled &&
|
|
2276
|
-
this.microphone.state.mediaStream &&
|
|
2277
|
-
!this.publisher?.isPublishing(TrackType.AUDIO)
|
|
2278
|
-
) {
|
|
2279
|
-
await this.publishAudioStream(this.microphone.state.mediaStream);
|
|
2280
|
-
}
|
|
2281
|
-
|
|
2282
|
-
// Start mic if backend config specifies, and there is no local setting
|
|
2283
|
-
if (
|
|
2284
|
-
this.microphone.state.status === undefined &&
|
|
2285
|
-
this.state.settings?.audio.mic_default_on
|
|
2286
|
-
) {
|
|
2287
|
-
await this.microphone.enable();
|
|
2288
|
-
}
|
|
2289
|
-
}
|
|
2290
|
-
};
|
|
2291
|
-
|
|
2292
2278
|
/**
|
|
2293
2279
|
* Will begin tracking the given element for visibility changes within the
|
|
2294
2280
|
* configured viewport element (`call.setViewport`).
|