@stream-io/video-client 1.35.1 → 1.36.1
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 +10 -0
- package/dist/index.browser.es.js +382 -329
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +404 -333
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +382 -329
- package/dist/index.es.js.map +1 -1
- package/dist/src/Call.d.ts +3 -2
- package/dist/src/StreamVideoClient.d.ts +3 -2
- package/dist/src/coordinator/connection/client.d.ts +3 -2
- package/dist/src/coordinator/connection/connection.d.ts +2 -1
- package/dist/src/coordinator/connection/types.d.ts +17 -1
- package/dist/src/devices/DeviceManager.d.ts +2 -2
- package/dist/src/logger.d.ts +9 -6
- package/dist/src/rpc/createClient.d.ts +3 -2
- package/dist/src/rtc/BasePeerConnection.d.ts +7 -4
- package/dist/src/rtc/Publisher.d.ts +3 -3
- package/dist/src/rtc/codecs.d.ts +3 -1
- package/dist/src/rtc/helpers/sdp.d.ts +8 -0
- package/dist/src/rtc/types.d.ts +4 -5
- package/dist/src/store/CallState.d.ts +1 -1
- package/dist/src/types.d.ts +6 -0
- package/package.json +3 -2
- package/src/Call.ts +49 -68
- package/src/StreamSfuClient.ts +11 -11
- package/src/StreamVideoClient.ts +19 -21
- package/src/coordinator/connection/client.ts +21 -30
- package/src/coordinator/connection/connection.ts +5 -4
- package/src/coordinator/connection/location.ts +4 -4
- package/src/coordinator/connection/types.ts +21 -2
- package/src/devices/BrowserPermission.ts +5 -5
- package/src/devices/CameraManager.ts +3 -4
- package/src/devices/DeviceManager.ts +11 -11
- package/src/devices/MicrophoneManager.ts +8 -8
- package/src/devices/devices.ts +18 -14
- package/src/events/call.ts +6 -9
- package/src/events/internal.ts +4 -4
- package/src/events/mutes.ts +3 -8
- package/src/helpers/DynascaleManager.ts +9 -9
- package/src/helpers/RNSpeechDetector.ts +5 -5
- package/src/helpers/clientUtils.ts +1 -3
- package/src/helpers/ensureExhausted.ts +2 -2
- package/src/logger.ts +9 -34
- package/src/rpc/__tests__/createClient.test.ts +5 -1
- package/src/rpc/createClient.ts +4 -3
- package/src/rpc/retryable.ts +4 -2
- package/src/rtc/BasePeerConnection.ts +26 -24
- package/src/rtc/Dispatcher.ts +4 -4
- package/src/rtc/IceTrickleBuffer.ts +5 -5
- package/src/rtc/Publisher.ts +21 -13
- package/src/rtc/Subscriber.ts +22 -17
- package/src/rtc/__tests__/Publisher.test.ts +12 -8
- package/src/rtc/codecs.ts +13 -2
- package/src/rtc/helpers/__tests__/sdp.codecs.test.ts +628 -0
- package/src/rtc/helpers/sdp.ts +82 -0
- package/src/rtc/signal.ts +7 -7
- package/src/rtc/types.ts +4 -4
- package/src/stats/CallStateStatsReporter.ts +4 -4
- package/src/stats/SfuStatsReporter.ts +6 -6
- package/src/store/CallState.ts +3 -3
- package/src/store/rxUtils.ts +4 -2
- package/src/store/stateStore.ts +6 -6
- package/src/types.ts +6 -0
package/dist/index.es.js
CHANGED
|
@@ -4,9 +4,11 @@ import { ServiceType, stackIntercept, RpcError } from '@protobuf-ts/runtime-rpc'
|
|
|
4
4
|
import axios from 'axios';
|
|
5
5
|
export { AxiosError } from 'axios';
|
|
6
6
|
import { TwirpFetchTransport, TwirpErrorCode } from '@protobuf-ts/twirp-transport';
|
|
7
|
+
import * as scopedLogger from '@stream-io/logger';
|
|
8
|
+
export { LogLevelEnum } from '@stream-io/logger';
|
|
9
|
+
import { parse, write } from 'sdp-transform';
|
|
7
10
|
import { ReplaySubject, combineLatest, BehaviorSubject, shareReplay, map, distinctUntilChanged, startWith, takeWhile, distinctUntilKeyChanged, fromEventPattern, concatMap, merge, from, fromEvent, tap, debounceTime, pairwise, of } from 'rxjs';
|
|
8
11
|
import { UAParser } from 'ua-parser-js';
|
|
9
|
-
import { parse, write } from 'sdp-transform';
|
|
10
12
|
import { WorkerTimer } from '@stream-io/worker-timer';
|
|
11
13
|
import https from 'https';
|
|
12
14
|
|
|
@@ -3726,7 +3728,7 @@ const withRequestLogger = (logger, level) => {
|
|
|
3726
3728
|
return {
|
|
3727
3729
|
interceptUnary: (next, method, input, options) => {
|
|
3728
3730
|
const invocation = next(method, input, options);
|
|
3729
|
-
logger(
|
|
3731
|
+
logger[level](`Invoked SFU RPC method ${method.name}`, {
|
|
3730
3732
|
request: invocation.request,
|
|
3731
3733
|
headers: invocation.requestHeaders,
|
|
3732
3734
|
response: invocation.response,
|
|
@@ -3862,16 +3864,6 @@ const isReactNative = () => {
|
|
|
3862
3864
|
return navigator.product?.toLowerCase() === 'reactnative';
|
|
3863
3865
|
};
|
|
3864
3866
|
|
|
3865
|
-
// log levels, sorted by verbosity
|
|
3866
|
-
const logLevels = Object.freeze({
|
|
3867
|
-
trace: 0,
|
|
3868
|
-
debug: 1,
|
|
3869
|
-
info: 2,
|
|
3870
|
-
warn: 3,
|
|
3871
|
-
error: 4,
|
|
3872
|
-
});
|
|
3873
|
-
let logger;
|
|
3874
|
-
let level = 'info';
|
|
3875
3867
|
const logToConsole = (logLevel, message, ...args) => {
|
|
3876
3868
|
let logMethod;
|
|
3877
3869
|
switch (logLevel) {
|
|
@@ -3903,26 +3895,7 @@ const logToConsole = (logLevel, message, ...args) => {
|
|
|
3903
3895
|
}
|
|
3904
3896
|
logMethod(message, ...args);
|
|
3905
3897
|
};
|
|
3906
|
-
const
|
|
3907
|
-
logger = l;
|
|
3908
|
-
if (lvl) {
|
|
3909
|
-
setLogLevel(lvl);
|
|
3910
|
-
}
|
|
3911
|
-
};
|
|
3912
|
-
const setLogLevel = (l) => {
|
|
3913
|
-
level = l;
|
|
3914
|
-
};
|
|
3915
|
-
const getLogLevel = () => level;
|
|
3916
|
-
const getLogger = (withTags) => {
|
|
3917
|
-
const loggerMethod = logger || logToConsole;
|
|
3918
|
-
const tags = (withTags || []).filter(Boolean).join(':');
|
|
3919
|
-
const result = (logLevel, message, ...args) => {
|
|
3920
|
-
if (logLevels[logLevel] >= logLevels[level]) {
|
|
3921
|
-
loggerMethod(logLevel, `[${tags}]: ${message}`, ...args);
|
|
3922
|
-
}
|
|
3923
|
-
};
|
|
3924
|
-
return result;
|
|
3925
|
-
};
|
|
3898
|
+
const videoLoggerSystem = scopedLogger.createLoggerSystem();
|
|
3926
3899
|
|
|
3927
3900
|
/**
|
|
3928
3901
|
* Creates a closure which wraps the given RPC call and retries invoking
|
|
@@ -3948,26 +3921,167 @@ const retryable = async (rpc, signal) => {
|
|
|
3948
3921
|
const isAborted = signal?.aborted ?? false;
|
|
3949
3922
|
if (isRequestCancelled || isAborted)
|
|
3950
3923
|
throw err;
|
|
3951
|
-
|
|
3924
|
+
videoLoggerSystem
|
|
3925
|
+
.getLogger('sfu-client', { tags: ['rpc'] })
|
|
3926
|
+
.debug(`rpc failed (${attempt})`, err);
|
|
3952
3927
|
attempt++;
|
|
3953
3928
|
}
|
|
3954
3929
|
} while (!result || result.response.error?.shouldRetry);
|
|
3955
3930
|
return result;
|
|
3956
3931
|
};
|
|
3957
3932
|
|
|
3933
|
+
/**
|
|
3934
|
+
* Extracts the mid from the transceiver or the SDP.
|
|
3935
|
+
*
|
|
3936
|
+
* @param transceiver the transceiver.
|
|
3937
|
+
* @param transceiverInitIndex the index of the transceiver in the transceiver's init array.
|
|
3938
|
+
* @param sdp the SDP.
|
|
3939
|
+
*/
|
|
3940
|
+
const extractMid = (transceiver, transceiverInitIndex, sdp) => {
|
|
3941
|
+
if (transceiver.mid)
|
|
3942
|
+
return transceiver.mid;
|
|
3943
|
+
if (!sdp)
|
|
3944
|
+
return String(transceiverInitIndex);
|
|
3945
|
+
const track = transceiver.sender.track;
|
|
3946
|
+
const parsedSdp = parse(sdp);
|
|
3947
|
+
const media = parsedSdp.media.find((m) => {
|
|
3948
|
+
return (m.type === track.kind &&
|
|
3949
|
+
// if `msid` is not present, we assume that the track is the first one
|
|
3950
|
+
(m.msid?.includes(track.id) ?? true));
|
|
3951
|
+
});
|
|
3952
|
+
if (typeof media?.mid !== 'undefined')
|
|
3953
|
+
return String(media.mid);
|
|
3954
|
+
if (transceiverInitIndex < 0)
|
|
3955
|
+
return '';
|
|
3956
|
+
return String(transceiverInitIndex);
|
|
3957
|
+
};
|
|
3958
|
+
/**
|
|
3959
|
+
* Enables stereo in the answer SDP based on the offered stereo in the offer SDP.
|
|
3960
|
+
*
|
|
3961
|
+
* @param offerSdp the offer SDP containing the stereo configuration.
|
|
3962
|
+
* @param answerSdp the answer SDP to be modified.
|
|
3963
|
+
*/
|
|
3964
|
+
const enableStereo = (offerSdp, answerSdp) => {
|
|
3965
|
+
const offeredStereoMids = new Set();
|
|
3966
|
+
const parsedOfferSdp = parse(offerSdp);
|
|
3967
|
+
for (const media of parsedOfferSdp.media) {
|
|
3968
|
+
if (media.type !== 'audio')
|
|
3969
|
+
continue;
|
|
3970
|
+
const opus = media.rtp.find((r) => r.codec === 'opus');
|
|
3971
|
+
if (!opus)
|
|
3972
|
+
continue;
|
|
3973
|
+
for (const fmtp of media.fmtp) {
|
|
3974
|
+
if (fmtp.payload === opus.payload && fmtp.config.includes('stereo=1')) {
|
|
3975
|
+
offeredStereoMids.add(media.mid);
|
|
3976
|
+
}
|
|
3977
|
+
}
|
|
3978
|
+
}
|
|
3979
|
+
// No stereo offered, return the original answerSdp
|
|
3980
|
+
if (offeredStereoMids.size === 0)
|
|
3981
|
+
return answerSdp;
|
|
3982
|
+
const parsedAnswerSdp = parse(answerSdp);
|
|
3983
|
+
for (const media of parsedAnswerSdp.media) {
|
|
3984
|
+
if (media.type !== 'audio' || !offeredStereoMids.has(media.mid))
|
|
3985
|
+
continue;
|
|
3986
|
+
const opus = media.rtp.find((r) => r.codec === 'opus');
|
|
3987
|
+
if (!opus)
|
|
3988
|
+
continue;
|
|
3989
|
+
for (const fmtp of media.fmtp) {
|
|
3990
|
+
if (fmtp.payload === opus.payload && !fmtp.config.includes('stereo=1')) {
|
|
3991
|
+
fmtp.config += ';stereo=1';
|
|
3992
|
+
}
|
|
3993
|
+
}
|
|
3994
|
+
}
|
|
3995
|
+
return write(parsedAnswerSdp);
|
|
3996
|
+
};
|
|
3997
|
+
/**
|
|
3998
|
+
* Removes all codecs from the SDP except the specified codec.
|
|
3999
|
+
*
|
|
4000
|
+
* @param sdp the SDP to modify.
|
|
4001
|
+
* @param codecMimeTypeToKeep the codec mime type to keep (video/h264 or audio/opus).
|
|
4002
|
+
* @param fmtpProfileToKeep the fmtp profile to keep (e.g. 'profile-level-id=42e01f' or multiple segments like 'profile-level-id=64001f;packetization-mode=1').
|
|
4003
|
+
*/
|
|
4004
|
+
const removeCodecsExcept = (sdp, codecMimeTypeToKeep, fmtpProfileToKeep) => {
|
|
4005
|
+
const [kind, codec] = toMimeType(codecMimeTypeToKeep).split('/');
|
|
4006
|
+
if (!kind || !codec)
|
|
4007
|
+
return sdp;
|
|
4008
|
+
const parsed = parse(sdp);
|
|
4009
|
+
for (const media of parsed.media) {
|
|
4010
|
+
if (media.type !== kind)
|
|
4011
|
+
continue;
|
|
4012
|
+
// Build a set of payloads to KEEP: all payloads whose rtp.codec matches codec
|
|
4013
|
+
let payloadsToKeep = new Set();
|
|
4014
|
+
for (const rtp of media.rtp) {
|
|
4015
|
+
if (rtp.codec.toLowerCase() !== codec)
|
|
4016
|
+
continue;
|
|
4017
|
+
payloadsToKeep.add(rtp.payload);
|
|
4018
|
+
}
|
|
4019
|
+
// If a specific fmtp profile is requested, only keep payloads whose fmtp config matches it
|
|
4020
|
+
if (fmtpProfileToKeep) {
|
|
4021
|
+
const filtered = new Set();
|
|
4022
|
+
const required = new Set(fmtpProfileToKeep.split(';'));
|
|
4023
|
+
for (const fmtp of media.fmtp) {
|
|
4024
|
+
if (payloadsToKeep.has(fmtp.payload) &&
|
|
4025
|
+
required.difference(new Set(fmtp.config.split(';'))).size === 0) {
|
|
4026
|
+
filtered.add(fmtp.payload);
|
|
4027
|
+
}
|
|
4028
|
+
}
|
|
4029
|
+
payloadsToKeep = filtered;
|
|
4030
|
+
}
|
|
4031
|
+
// If no payloads to keep AND no fmtpProfile was specified, skip modifications (preserve SDP as-is)
|
|
4032
|
+
if (payloadsToKeep.size === 0 && !fmtpProfileToKeep)
|
|
4033
|
+
continue;
|
|
4034
|
+
// Keep RTX payloads that are associated with kept primary payloads via apt
|
|
4035
|
+
// RTX mappings look like: a=fmtp:<rtxPayload> apt=<primaryPayload>
|
|
4036
|
+
for (const fmtp of media.fmtp) {
|
|
4037
|
+
const matches = /\s*apt\s*=\s*(\d+)\s*/i.exec(fmtp.config);
|
|
4038
|
+
if (!matches)
|
|
4039
|
+
continue;
|
|
4040
|
+
const primaryPayloadApt = Number(matches[1]);
|
|
4041
|
+
if (!payloadsToKeep.has(primaryPayloadApt))
|
|
4042
|
+
continue;
|
|
4043
|
+
payloadsToKeep.add(fmtp.payload);
|
|
4044
|
+
}
|
|
4045
|
+
// Filter rtp, fmtp and rtcpFb entries
|
|
4046
|
+
media.rtp = media.rtp.filter((rtp) => payloadsToKeep.has(rtp.payload));
|
|
4047
|
+
media.fmtp = media.fmtp.filter((fmtp) => payloadsToKeep.has(fmtp.payload));
|
|
4048
|
+
media.rtcpFb = media.rtcpFb?.filter((fb) => typeof fb.payload === 'number' ? payloadsToKeep.has(fb.payload) : true);
|
|
4049
|
+
// Update the m= line payload list to only the kept payloads, preserving original order
|
|
4050
|
+
const payloads = [];
|
|
4051
|
+
for (const id of (media.payloads || '').split(/\s+/)) {
|
|
4052
|
+
const payload = Number(id);
|
|
4053
|
+
if (!payloadsToKeep.has(payload))
|
|
4054
|
+
continue;
|
|
4055
|
+
payloads.push(payload);
|
|
4056
|
+
}
|
|
4057
|
+
media.payloads = payloads.join(' ');
|
|
4058
|
+
}
|
|
4059
|
+
return write(parsed);
|
|
4060
|
+
};
|
|
4061
|
+
/**
|
|
4062
|
+
* Converts the given codec to a mime-type format when necessary.
|
|
4063
|
+
* e.g.: `vp9` -> `video/vp9`
|
|
4064
|
+
*/
|
|
4065
|
+
const toMimeType = (codec, kind = 'video') => codec.includes('/') ? codec : `${kind}/${codec}`;
|
|
4066
|
+
|
|
3958
4067
|
/**
|
|
3959
4068
|
* Returns a generic SDP for the given direction.
|
|
3960
4069
|
* We use this SDP to send it as part of our JoinRequest so that the SFU
|
|
3961
4070
|
* can use it to determine the client's codec capabilities.
|
|
3962
4071
|
*
|
|
3963
4072
|
* @param direction the direction of the transceiver.
|
|
4073
|
+
* @param codecToKeep the codec mime type to keep (video/h264 or audio/opus).
|
|
4074
|
+
* @param fmtpProfileToKeep optional fmtp profile to keep.
|
|
3964
4075
|
*/
|
|
3965
|
-
const getGenericSdp = async (direction) => {
|
|
4076
|
+
const getGenericSdp = async (direction, codecToKeep, fmtpProfileToKeep) => {
|
|
3966
4077
|
const tempPc = new RTCPeerConnection();
|
|
3967
4078
|
tempPc.addTransceiver('video', { direction });
|
|
3968
4079
|
tempPc.addTransceiver('audio', { direction });
|
|
3969
4080
|
const offer = await tempPc.createOffer();
|
|
3970
|
-
const sdp =
|
|
4081
|
+
const { sdp: baseSdp = '' } = offer;
|
|
4082
|
+
const sdp = codecToKeep
|
|
4083
|
+
? removeCodecsExcept(baseSdp, codecToKeep, fmtpProfileToKeep)
|
|
4084
|
+
: baseSdp;
|
|
3971
4085
|
tempPc.getTransceivers().forEach((t) => {
|
|
3972
4086
|
t.stop?.();
|
|
3973
4087
|
});
|
|
@@ -4019,14 +4133,14 @@ const isSfuEvent = (eventName) => {
|
|
|
4019
4133
|
};
|
|
4020
4134
|
class Dispatcher {
|
|
4021
4135
|
constructor() {
|
|
4022
|
-
this.logger = getLogger(
|
|
4136
|
+
this.logger = videoLoggerSystem.getLogger('Dispatcher');
|
|
4023
4137
|
this.subscribers = {};
|
|
4024
4138
|
this.dispatch = (message, tag = '0') => {
|
|
4025
4139
|
const eventKind = message.eventPayload.oneofKind;
|
|
4026
4140
|
if (!eventKind)
|
|
4027
4141
|
return;
|
|
4028
4142
|
const payload = message.eventPayload[eventKind];
|
|
4029
|
-
this.logger(
|
|
4143
|
+
this.logger.debug(`Dispatching ${eventKind}, tag=${tag}`, payload);
|
|
4030
4144
|
const listeners = this.subscribers[eventKind];
|
|
4031
4145
|
if (!listeners)
|
|
4032
4146
|
return;
|
|
@@ -4035,7 +4149,7 @@ class Dispatcher {
|
|
|
4035
4149
|
fn(payload);
|
|
4036
4150
|
}
|
|
4037
4151
|
catch (e) {
|
|
4038
|
-
this.logger('
|
|
4152
|
+
this.logger.warn('Listener failed with error', e);
|
|
4039
4153
|
}
|
|
4040
4154
|
}
|
|
4041
4155
|
};
|
|
@@ -4071,8 +4185,8 @@ class IceTrickleBuffer {
|
|
|
4071
4185
|
this.publisherCandidates.next(iceCandidate);
|
|
4072
4186
|
}
|
|
4073
4187
|
else {
|
|
4074
|
-
const logger = getLogger(
|
|
4075
|
-
logger(
|
|
4188
|
+
const logger = videoLoggerSystem.getLogger('sfu-client');
|
|
4189
|
+
logger.warn(`ICETrickle, Unknown peer type`, iceTrickle);
|
|
4076
4190
|
}
|
|
4077
4191
|
};
|
|
4078
4192
|
this.dispose = () => {
|
|
@@ -4086,8 +4200,8 @@ const toIceCandidate = (iceTrickle) => {
|
|
|
4086
4200
|
return JSON.parse(iceTrickle.iceCandidate);
|
|
4087
4201
|
}
|
|
4088
4202
|
catch (e) {
|
|
4089
|
-
const logger = getLogger(
|
|
4090
|
-
logger(
|
|
4203
|
+
const logger = videoLoggerSystem.getLogger('sfu-client');
|
|
4204
|
+
logger.error(`Failed to parse ICE Trickle`, e, iceTrickle);
|
|
4091
4205
|
return undefined;
|
|
4092
4206
|
}
|
|
4093
4207
|
};
|
|
@@ -4263,7 +4377,9 @@ const updateValue = (subject, update) => {
|
|
|
4263
4377
|
* @param handler the handler to call when the observable emits a value.
|
|
4264
4378
|
* @param onError an optional error handler.
|
|
4265
4379
|
*/
|
|
4266
|
-
const createSubscription = (observable, handler, onError = (error) =>
|
|
4380
|
+
const createSubscription = (observable, handler, onError = (error) => videoLoggerSystem
|
|
4381
|
+
.getLogger('RxUtils')
|
|
4382
|
+
.warn('An observable emitted an error', error)) => {
|
|
4267
4383
|
const subscription = observable.subscribe({ next: handler, error: onError });
|
|
4268
4384
|
return () => {
|
|
4269
4385
|
subscription.unsubscribe();
|
|
@@ -4383,8 +4499,8 @@ class StreamVideoWriteableStateStore {
|
|
|
4383
4499
|
* @param call the call to remove
|
|
4384
4500
|
*/
|
|
4385
4501
|
this.unregisterCall = (call) => {
|
|
4386
|
-
const logger = getLogger(
|
|
4387
|
-
logger(
|
|
4502
|
+
const logger = videoLoggerSystem.getLogger('client-state');
|
|
4503
|
+
logger.trace(`Unregistering call: ${call.cid}`);
|
|
4388
4504
|
return this.setCalls((calls) => calls.filter((c) => c !== call));
|
|
4389
4505
|
};
|
|
4390
4506
|
/**
|
|
@@ -4399,15 +4515,15 @@ class StreamVideoWriteableStateStore {
|
|
|
4399
4515
|
this.connectedUserSubject.subscribe(async (user) => {
|
|
4400
4516
|
// leave all calls when the user disconnects.
|
|
4401
4517
|
if (!user) {
|
|
4402
|
-
const logger = getLogger(
|
|
4518
|
+
const logger = videoLoggerSystem.getLogger('client-state');
|
|
4403
4519
|
for (const call of this.calls) {
|
|
4404
4520
|
if (call.state.callingState === CallingState.LEFT)
|
|
4405
4521
|
continue;
|
|
4406
|
-
logger(
|
|
4522
|
+
logger.info(`User disconnected, leaving call: ${call.cid}`);
|
|
4407
4523
|
await call
|
|
4408
4524
|
.leave({ message: 'client.disconnectUser() called' })
|
|
4409
4525
|
.catch((err) => {
|
|
4410
|
-
logger(
|
|
4526
|
+
logger.error(`Error leaving call: ${call.cid}`, err);
|
|
4411
4527
|
});
|
|
4412
4528
|
}
|
|
4413
4529
|
}
|
|
@@ -4801,7 +4917,7 @@ class CallState {
|
|
|
4801
4917
|
// We keep these tracks around until we can associate them with a participant.
|
|
4802
4918
|
this.orphanedTracks = [];
|
|
4803
4919
|
this.callGrantsSubject = new ReplaySubject(1);
|
|
4804
|
-
this.logger = getLogger(
|
|
4920
|
+
this.logger = videoLoggerSystem.getLogger('CallState');
|
|
4805
4921
|
/**
|
|
4806
4922
|
* A list of comparators that are used to sort the participants.
|
|
4807
4923
|
*/
|
|
@@ -4982,7 +5098,7 @@ class CallState {
|
|
|
4982
5098
|
this.updateParticipant = (sessionId, patch) => {
|
|
4983
5099
|
const participant = this.findParticipantBySessionId(sessionId);
|
|
4984
5100
|
if (!participant) {
|
|
4985
|
-
this.logger(
|
|
5101
|
+
this.logger.warn(`Participant with sessionId ${sessionId} not found`);
|
|
4986
5102
|
return;
|
|
4987
5103
|
}
|
|
4988
5104
|
const thePatch = typeof patch === 'function' ? patch(participant) : patch;
|
|
@@ -5861,7 +5977,7 @@ const getSdkVersion = (sdk) => {
|
|
|
5861
5977
|
return sdk ? `${sdk.major}.${sdk.minor}.${sdk.patch}` : '0.0.0-development';
|
|
5862
5978
|
};
|
|
5863
5979
|
|
|
5864
|
-
const version = "1.
|
|
5980
|
+
const version = "1.36.1";
|
|
5865
5981
|
const [major, minor, patch] = version.split('.');
|
|
5866
5982
|
let sdkInfo = {
|
|
5867
5983
|
type: SdkType.PLAIN_JAVASCRIPT,
|
|
@@ -6064,7 +6180,7 @@ var browsers = /*#__PURE__*/Object.freeze({
|
|
|
6064
6180
|
* Creates a new StatsReporter instance that collects metrics about the ongoing call and reports them to the state store
|
|
6065
6181
|
*/
|
|
6066
6182
|
const createStatsReporter = ({ subscriber, publisher, state, datacenter, pollingIntervalInMs = 2000, }) => {
|
|
6067
|
-
const logger = getLogger(
|
|
6183
|
+
const logger = videoLoggerSystem.getLogger('stats');
|
|
6068
6184
|
const getRawStatsForTrack = async (kind, selector) => {
|
|
6069
6185
|
if (kind === 'subscriber' && subscriber) {
|
|
6070
6186
|
return subscriber.getStats(selector);
|
|
@@ -6123,7 +6239,7 @@ const createStatsReporter = ({ subscriber, publisher, state, datacenter, polling
|
|
|
6123
6239
|
participantStats[sessionId] = await getStatsForStream(kind, tracks);
|
|
6124
6240
|
}
|
|
6125
6241
|
catch (e) {
|
|
6126
|
-
logger(
|
|
6242
|
+
logger.warn(`Failed to collect ${kind} stats for ${userId}`, e);
|
|
6127
6243
|
}
|
|
6128
6244
|
}
|
|
6129
6245
|
}
|
|
@@ -6155,7 +6271,7 @@ const createStatsReporter = ({ subscriber, publisher, state, datacenter, polling
|
|
|
6155
6271
|
// (they are expensive) if no one is listening to them
|
|
6156
6272
|
if (state.isCallStatsReportObserved) {
|
|
6157
6273
|
await run().catch((e) => {
|
|
6158
|
-
logger('
|
|
6274
|
+
logger.debug('Failed to collect stats', e);
|
|
6159
6275
|
});
|
|
6160
6276
|
}
|
|
6161
6277
|
timeoutId = setTimeout(loop, pollingIntervalInMs);
|
|
@@ -6301,7 +6417,7 @@ const aggregate = (stats) => {
|
|
|
6301
6417
|
|
|
6302
6418
|
class SfuStatsReporter {
|
|
6303
6419
|
constructor(sfuClient, { options, clientDetails, subscriber, publisher, microphone, camera, state, tracer, unifiedSessionId, }) {
|
|
6304
|
-
this.logger = getLogger(
|
|
6420
|
+
this.logger = videoLoggerSystem.getLogger('SfuStatsReporter');
|
|
6305
6421
|
this.inputDevices = new Map();
|
|
6306
6422
|
this.observeDevice = (device, kind) => {
|
|
6307
6423
|
const { browserPermissionState$ } = device.state;
|
|
@@ -6352,7 +6468,7 @@ class SfuStatsReporter {
|
|
|
6352
6468
|
// intentionally not awaiting the promise here
|
|
6353
6469
|
// to avoid impeding with the ongoing actions.
|
|
6354
6470
|
this.run(telemetryData).catch((err) => {
|
|
6355
|
-
this.logger('
|
|
6471
|
+
this.logger.warn('Failed to send telemetry data', err);
|
|
6356
6472
|
});
|
|
6357
6473
|
};
|
|
6358
6474
|
this.run = async (telemetry) => {
|
|
@@ -6411,7 +6527,7 @@ class SfuStatsReporter {
|
|
|
6411
6527
|
clearInterval(this.intervalId);
|
|
6412
6528
|
this.intervalId = setInterval(() => {
|
|
6413
6529
|
this.run().catch((err) => {
|
|
6414
|
-
this.logger('
|
|
6530
|
+
this.logger.warn('Failed to report stats', err);
|
|
6415
6531
|
});
|
|
6416
6532
|
}, this.options.reporting_interval_ms);
|
|
6417
6533
|
};
|
|
@@ -6428,14 +6544,14 @@ class SfuStatsReporter {
|
|
|
6428
6544
|
};
|
|
6429
6545
|
this.flush = () => {
|
|
6430
6546
|
this.run().catch((err) => {
|
|
6431
|
-
this.logger('
|
|
6547
|
+
this.logger.warn('Failed to flush report stats', err);
|
|
6432
6548
|
});
|
|
6433
6549
|
};
|
|
6434
6550
|
this.scheduleOne = (timeout) => {
|
|
6435
6551
|
clearTimeout(this.timeoutId);
|
|
6436
6552
|
this.timeoutId = setTimeout(() => {
|
|
6437
6553
|
this.run().catch((err) => {
|
|
6438
|
-
this.logger('
|
|
6554
|
+
this.logger.warn('Failed to report stats', err);
|
|
6439
6555
|
});
|
|
6440
6556
|
}, timeout);
|
|
6441
6557
|
};
|
|
@@ -6782,7 +6898,7 @@ class BasePeerConnection {
|
|
|
6782
6898
|
/**
|
|
6783
6899
|
* Constructs a new `BasePeerConnection` instance.
|
|
6784
6900
|
*/
|
|
6785
|
-
constructor(peerType, { sfuClient, connectionConfig, state, dispatcher, onReconnectionNeeded, tag, enableTracing, iceRestartDelay = 2500, }) {
|
|
6901
|
+
constructor(peerType, { sfuClient, connectionConfig, state, dispatcher, onReconnectionNeeded, tag, enableTracing, clientPublishOptions, iceRestartDelay = 2500, }) {
|
|
6786
6902
|
this.isIceRestarting = false;
|
|
6787
6903
|
this.isDisposed = false;
|
|
6788
6904
|
this.trackIdToTrackType = new Map();
|
|
@@ -6806,12 +6922,12 @@ class BasePeerConnection {
|
|
|
6806
6922
|
this.tryRestartIce = () => {
|
|
6807
6923
|
this.restartIce().catch((e) => {
|
|
6808
6924
|
const reason = 'restartICE() failed, initiating reconnect';
|
|
6809
|
-
this.logger(
|
|
6925
|
+
this.logger.error(reason, e);
|
|
6810
6926
|
const strategy = e instanceof NegotiationError &&
|
|
6811
6927
|
e.error.code === ErrorCode.PARTICIPANT_SIGNAL_LOST
|
|
6812
6928
|
? WebsocketReconnectStrategy.FAST
|
|
6813
6929
|
: WebsocketReconnectStrategy.REJOIN;
|
|
6814
|
-
this.onReconnectionNeeded?.(strategy, reason);
|
|
6930
|
+
this.onReconnectionNeeded?.(strategy, reason, this.peerType);
|
|
6815
6931
|
});
|
|
6816
6932
|
};
|
|
6817
6933
|
/**
|
|
@@ -6824,7 +6940,7 @@ class BasePeerConnection {
|
|
|
6824
6940
|
withoutConcurrency(lockKey, async () => fn(e)).catch((err) => {
|
|
6825
6941
|
if (this.isDisposed)
|
|
6826
6942
|
return;
|
|
6827
|
-
this.logger(
|
|
6943
|
+
this.logger.warn(`Error handling ${event}`, err);
|
|
6828
6944
|
});
|
|
6829
6945
|
}));
|
|
6830
6946
|
};
|
|
@@ -6841,7 +6957,7 @@ class BasePeerConnection {
|
|
|
6841
6957
|
return this.pc.addIceCandidate(candidate).catch((e) => {
|
|
6842
6958
|
if (this.isDisposed)
|
|
6843
6959
|
return;
|
|
6844
|
-
this.logger(
|
|
6960
|
+
this.logger.warn(`ICE candidate error`, e, candidate);
|
|
6845
6961
|
});
|
|
6846
6962
|
});
|
|
6847
6963
|
};
|
|
@@ -6885,7 +7001,7 @@ class BasePeerConnection {
|
|
|
6885
7001
|
this.onIceCandidate = (e) => {
|
|
6886
7002
|
const { candidate } = e;
|
|
6887
7003
|
if (!candidate) {
|
|
6888
|
-
this.logger('
|
|
7004
|
+
this.logger.debug('null ice candidate');
|
|
6889
7005
|
return;
|
|
6890
7006
|
}
|
|
6891
7007
|
const iceCandidate = this.asJSON(candidate);
|
|
@@ -6894,7 +7010,7 @@ class BasePeerConnection {
|
|
|
6894
7010
|
.catch((err) => {
|
|
6895
7011
|
if (this.isDisposed)
|
|
6896
7012
|
return;
|
|
6897
|
-
this.logger(
|
|
7013
|
+
this.logger.warn(`ICETrickle failed`, err);
|
|
6898
7014
|
});
|
|
6899
7015
|
};
|
|
6900
7016
|
/**
|
|
@@ -6915,7 +7031,7 @@ class BasePeerConnection {
|
|
|
6915
7031
|
*/
|
|
6916
7032
|
this.onConnectionStateChange = async () => {
|
|
6917
7033
|
const state = this.pc.connectionState;
|
|
6918
|
-
this.logger(
|
|
7034
|
+
this.logger.debug(`Connection state changed`, state);
|
|
6919
7035
|
if (this.tracer && (state === 'connected' || state === 'failed')) {
|
|
6920
7036
|
try {
|
|
6921
7037
|
const stats = await this.stats.get();
|
|
@@ -6927,7 +7043,7 @@ class BasePeerConnection {
|
|
|
6927
7043
|
}
|
|
6928
7044
|
// we can't recover from a failed connection state (contrary to ICE)
|
|
6929
7045
|
if (state === 'failed') {
|
|
6930
|
-
this.onReconnectionNeeded?.(WebsocketReconnectStrategy.REJOIN, 'Connection failed');
|
|
7046
|
+
this.onReconnectionNeeded?.(WebsocketReconnectStrategy.REJOIN, 'Connection failed', this.peerType);
|
|
6931
7047
|
return;
|
|
6932
7048
|
}
|
|
6933
7049
|
this.handleConnectionStateUpdate(state);
|
|
@@ -6937,7 +7053,7 @@ class BasePeerConnection {
|
|
|
6937
7053
|
*/
|
|
6938
7054
|
this.onIceConnectionStateChange = () => {
|
|
6939
7055
|
const state = this.pc.iceConnectionState;
|
|
6940
|
-
this.logger(
|
|
7056
|
+
this.logger.debug(`ICE connection state changed`, state);
|
|
6941
7057
|
this.handleConnectionStateUpdate(state);
|
|
6942
7058
|
};
|
|
6943
7059
|
this.handleConnectionStateUpdate = (state) => {
|
|
@@ -6952,13 +7068,13 @@ class BasePeerConnection {
|
|
|
6952
7068
|
switch (state) {
|
|
6953
7069
|
case 'failed':
|
|
6954
7070
|
// in the `failed` state, we try to restart ICE immediately
|
|
6955
|
-
this.logger('
|
|
7071
|
+
this.logger.info('restartICE due to failed connection');
|
|
6956
7072
|
this.tryRestartIce();
|
|
6957
7073
|
break;
|
|
6958
7074
|
case 'disconnected':
|
|
6959
7075
|
// in the `disconnected` state, we schedule a restartICE() after a delay
|
|
6960
7076
|
// as the browser might recover the connection in the meantime
|
|
6961
|
-
this.logger('
|
|
7077
|
+
this.logger.info('disconnected connection, scheduling restartICE');
|
|
6962
7078
|
clearTimeout(this.iceRestartTimeout);
|
|
6963
7079
|
this.iceRestartTimeout = setTimeout(() => {
|
|
6964
7080
|
const currentState = this.pc.iceConnectionState;
|
|
@@ -6970,7 +7086,7 @@ class BasePeerConnection {
|
|
|
6970
7086
|
case 'connected':
|
|
6971
7087
|
// in the `connected` state, we clear the ice restart timeout if it exists
|
|
6972
7088
|
if (this.iceRestartTimeout) {
|
|
6973
|
-
this.logger('
|
|
7089
|
+
this.logger.info('connected connection, canceling restartICE');
|
|
6974
7090
|
clearTimeout(this.iceRestartTimeout);
|
|
6975
7091
|
this.iceRestartTimeout = undefined;
|
|
6976
7092
|
}
|
|
@@ -6984,30 +7100,28 @@ class BasePeerConnection {
|
|
|
6984
7100
|
const errorMessage = e instanceof RTCPeerConnectionIceErrorEvent
|
|
6985
7101
|
? `${e.errorCode}: ${e.errorText}`
|
|
6986
7102
|
: e;
|
|
6987
|
-
this.logger('
|
|
7103
|
+
this.logger.debug('ICE Candidate error', errorMessage);
|
|
6988
7104
|
};
|
|
6989
7105
|
/**
|
|
6990
7106
|
* Handles the ICE gathering state change event.
|
|
6991
7107
|
*/
|
|
6992
7108
|
this.onIceGatherChange = () => {
|
|
6993
|
-
this.logger(
|
|
7109
|
+
this.logger.debug(`ICE Gathering State`, this.pc.iceGatheringState);
|
|
6994
7110
|
};
|
|
6995
7111
|
/**
|
|
6996
7112
|
* Handles the signaling state change event.
|
|
6997
7113
|
*/
|
|
6998
7114
|
this.onSignalingChange = () => {
|
|
6999
|
-
this.logger(
|
|
7115
|
+
this.logger.debug(`Signaling state changed`, this.pc.signalingState);
|
|
7000
7116
|
};
|
|
7001
7117
|
this.peerType = peerType;
|
|
7002
7118
|
this.sfuClient = sfuClient;
|
|
7003
7119
|
this.state = state;
|
|
7004
7120
|
this.dispatcher = dispatcher;
|
|
7005
7121
|
this.iceRestartDelay = iceRestartDelay;
|
|
7122
|
+
this.clientPublishOptions = clientPublishOptions;
|
|
7006
7123
|
this.onReconnectionNeeded = onReconnectionNeeded;
|
|
7007
|
-
this.logger = getLogger([
|
|
7008
|
-
peerType === PeerType.SUBSCRIBER ? 'Subscriber' : 'Publisher',
|
|
7009
|
-
tag,
|
|
7010
|
-
]);
|
|
7124
|
+
this.logger = videoLoggerSystem.getLogger(peerType === PeerType.SUBSCRIBER ? 'Subscriber' : 'Publisher', { tags: [tag] });
|
|
7011
7125
|
this.pc = this.createPeerConnection(connectionConfig);
|
|
7012
7126
|
this.stats = new StatsTracer(this.pc, peerType, this.trackIdToTrackType);
|
|
7013
7127
|
if (enableTracing) {
|
|
@@ -7130,7 +7244,7 @@ class TransceiverCache {
|
|
|
7130
7244
|
}
|
|
7131
7245
|
|
|
7132
7246
|
const ensureExhausted = (x, message) => {
|
|
7133
|
-
getLogger(
|
|
7247
|
+
videoLoggerSystem.getLogger('helpers').warn(message, x);
|
|
7134
7248
|
};
|
|
7135
7249
|
|
|
7136
7250
|
const trackTypeToParticipantStreamKey = (trackType) => {
|
|
@@ -7340,71 +7454,6 @@ const withSimulcastConstraints = (width, height, optimalVideoLayers, useSingleLa
|
|
|
7340
7454
|
}));
|
|
7341
7455
|
};
|
|
7342
7456
|
|
|
7343
|
-
/**
|
|
7344
|
-
* Extracts the mid from the transceiver or the SDP.
|
|
7345
|
-
*
|
|
7346
|
-
* @param transceiver the transceiver.
|
|
7347
|
-
* @param transceiverInitIndex the index of the transceiver in the transceiver's init array.
|
|
7348
|
-
* @param sdp the SDP.
|
|
7349
|
-
*/
|
|
7350
|
-
const extractMid = (transceiver, transceiverInitIndex, sdp) => {
|
|
7351
|
-
if (transceiver.mid)
|
|
7352
|
-
return transceiver.mid;
|
|
7353
|
-
if (!sdp)
|
|
7354
|
-
return String(transceiverInitIndex);
|
|
7355
|
-
const track = transceiver.sender.track;
|
|
7356
|
-
const parsedSdp = parse(sdp);
|
|
7357
|
-
const media = parsedSdp.media.find((m) => {
|
|
7358
|
-
return (m.type === track.kind &&
|
|
7359
|
-
// if `msid` is not present, we assume that the track is the first one
|
|
7360
|
-
(m.msid?.includes(track.id) ?? true));
|
|
7361
|
-
});
|
|
7362
|
-
if (typeof media?.mid !== 'undefined')
|
|
7363
|
-
return String(media.mid);
|
|
7364
|
-
if (transceiverInitIndex < 0)
|
|
7365
|
-
return '';
|
|
7366
|
-
return String(transceiverInitIndex);
|
|
7367
|
-
};
|
|
7368
|
-
/**
|
|
7369
|
-
* Enables stereo in the answer SDP based on the offered stereo in the offer SDP.
|
|
7370
|
-
*
|
|
7371
|
-
* @param offerSdp the offer SDP containing the stereo configuration.
|
|
7372
|
-
* @param answerSdp the answer SDP to be modified.
|
|
7373
|
-
*/
|
|
7374
|
-
const enableStereo = (offerSdp, answerSdp) => {
|
|
7375
|
-
const offeredStereoMids = new Set();
|
|
7376
|
-
const parsedOfferSdp = parse(offerSdp);
|
|
7377
|
-
for (const media of parsedOfferSdp.media) {
|
|
7378
|
-
if (media.type !== 'audio')
|
|
7379
|
-
continue;
|
|
7380
|
-
const opus = media.rtp.find((r) => r.codec === 'opus');
|
|
7381
|
-
if (!opus)
|
|
7382
|
-
continue;
|
|
7383
|
-
for (const fmtp of media.fmtp) {
|
|
7384
|
-
if (fmtp.payload === opus.payload && fmtp.config.includes('stereo=1')) {
|
|
7385
|
-
offeredStereoMids.add(media.mid);
|
|
7386
|
-
}
|
|
7387
|
-
}
|
|
7388
|
-
}
|
|
7389
|
-
// No stereo offered, return the original answerSdp
|
|
7390
|
-
if (offeredStereoMids.size === 0)
|
|
7391
|
-
return answerSdp;
|
|
7392
|
-
const parsedAnswerSdp = parse(answerSdp);
|
|
7393
|
-
for (const media of parsedAnswerSdp.media) {
|
|
7394
|
-
if (media.type !== 'audio' || !offeredStereoMids.has(media.mid))
|
|
7395
|
-
continue;
|
|
7396
|
-
const opus = media.rtp.find((r) => r.codec === 'opus');
|
|
7397
|
-
if (!opus)
|
|
7398
|
-
continue;
|
|
7399
|
-
for (const fmtp of media.fmtp) {
|
|
7400
|
-
if (fmtp.payload === opus.payload && !fmtp.config.includes('stereo=1')) {
|
|
7401
|
-
fmtp.config += ';stereo=1';
|
|
7402
|
-
}
|
|
7403
|
-
}
|
|
7404
|
-
}
|
|
7405
|
-
return write(parsedAnswerSdp);
|
|
7406
|
-
};
|
|
7407
|
-
|
|
7408
7457
|
/**
|
|
7409
7458
|
* The `Publisher` is responsible for publishing/unpublishing media streams to/from the SFU
|
|
7410
7459
|
*
|
|
@@ -7414,7 +7463,7 @@ class Publisher extends BasePeerConnection {
|
|
|
7414
7463
|
/**
|
|
7415
7464
|
* Constructs a new `Publisher` instance.
|
|
7416
7465
|
*/
|
|
7417
|
-
constructor(
|
|
7466
|
+
constructor(baseOptions, publishOptions) {
|
|
7418
7467
|
super(PeerType.PUBLISHER_UNSPECIFIED, baseOptions);
|
|
7419
7468
|
this.transceiverCache = new TransceiverCache();
|
|
7420
7469
|
this.clonedTracks = new Set();
|
|
@@ -7469,7 +7518,7 @@ class Publisher extends BasePeerConnection {
|
|
|
7469
7518
|
params.degradationPreference = 'maintain-framerate';
|
|
7470
7519
|
await transceiver.sender.setParameters(params);
|
|
7471
7520
|
const trackType = publishOption.trackType;
|
|
7472
|
-
this.logger(
|
|
7521
|
+
this.logger.debug(`Added ${TrackType[trackType]} transceiver`);
|
|
7473
7522
|
this.transceiverCache.add({ publishOption, transceiver, options });
|
|
7474
7523
|
this.trackIdToTrackType.set(track.id, trackType);
|
|
7475
7524
|
await this.negotiate();
|
|
@@ -7589,16 +7638,16 @@ class Publisher extends BasePeerConnection {
|
|
|
7589
7638
|
const { trackType, layers, publishOptionId } = videoSender;
|
|
7590
7639
|
const enabledLayers = layers.filter((l) => l.active);
|
|
7591
7640
|
const tag = 'Update publish quality:';
|
|
7592
|
-
this.logger(
|
|
7641
|
+
this.logger.info(`${tag} requested layers by SFU:`, enabledLayers);
|
|
7593
7642
|
const transceiverId = this.transceiverCache.find((t) => t.publishOption.id === publishOptionId &&
|
|
7594
7643
|
t.publishOption.trackType === trackType);
|
|
7595
7644
|
const sender = transceiverId?.transceiver.sender;
|
|
7596
7645
|
if (!sender) {
|
|
7597
|
-
return this.logger(
|
|
7646
|
+
return this.logger.warn(`${tag} no video sender found.`);
|
|
7598
7647
|
}
|
|
7599
7648
|
const params = sender.getParameters();
|
|
7600
7649
|
if (params.encodings.length === 0) {
|
|
7601
|
-
return this.logger(
|
|
7650
|
+
return this.logger.warn(`${tag} there are no encodings set.`);
|
|
7602
7651
|
}
|
|
7603
7652
|
const codecInUse = transceiverId?.publishOption.codec?.name;
|
|
7604
7653
|
const usesSvcCodec = codecInUse && isSvcCodec(codecInUse);
|
|
@@ -7642,19 +7691,19 @@ class Publisher extends BasePeerConnection {
|
|
|
7642
7691
|
}
|
|
7643
7692
|
const activeEncoders = params.encodings.filter((e) => e.active);
|
|
7644
7693
|
if (!changed) {
|
|
7645
|
-
return this.logger(
|
|
7694
|
+
return this.logger.info(`${tag} no change:`, activeEncoders);
|
|
7646
7695
|
}
|
|
7647
7696
|
await sender.setParameters(params);
|
|
7648
|
-
this.logger(
|
|
7697
|
+
this.logger.info(`${tag} enabled rids:`, activeEncoders);
|
|
7649
7698
|
};
|
|
7650
7699
|
/**
|
|
7651
7700
|
* Restarts the ICE connection and renegotiates with the SFU.
|
|
7652
7701
|
*/
|
|
7653
7702
|
this.restartIce = async () => {
|
|
7654
|
-
this.logger('
|
|
7703
|
+
this.logger.debug('Restarting ICE connection');
|
|
7655
7704
|
const signalingState = this.pc.signalingState;
|
|
7656
7705
|
if (this.isIceRestarting || signalingState === 'have-local-offer') {
|
|
7657
|
-
this.logger('
|
|
7706
|
+
this.logger.debug('ICE restart is already in progress');
|
|
7658
7707
|
return;
|
|
7659
7708
|
}
|
|
7660
7709
|
await this.negotiate({ iceRestart: true });
|
|
@@ -7673,7 +7722,11 @@ class Publisher extends BasePeerConnection {
|
|
|
7673
7722
|
try {
|
|
7674
7723
|
this.isIceRestarting = options?.iceRestart ?? false;
|
|
7675
7724
|
await this.pc.setLocalDescription(offer);
|
|
7676
|
-
const { sdp = '' } = offer;
|
|
7725
|
+
const { sdp: baseSdp = '' } = offer;
|
|
7726
|
+
const { dangerouslyForceCodec, fmtpLine } = this.clientPublishOptions || {};
|
|
7727
|
+
const sdp = dangerouslyForceCodec
|
|
7728
|
+
? removeCodecsExcept(baseSdp, dangerouslyForceCodec, fmtpLine)
|
|
7729
|
+
: baseSdp;
|
|
7677
7730
|
const { response } = await this.sfuClient.setPublisher({ sdp, tracks });
|
|
7678
7731
|
if (response.error)
|
|
7679
7732
|
throw new NegotiationError(response.error);
|
|
@@ -7816,13 +7869,13 @@ class Subscriber extends BasePeerConnection {
|
|
|
7816
7869
|
* Restarts the ICE connection and renegotiates with the SFU.
|
|
7817
7870
|
*/
|
|
7818
7871
|
this.restartIce = async () => {
|
|
7819
|
-
this.logger('
|
|
7872
|
+
this.logger.debug('Restarting ICE connection');
|
|
7820
7873
|
if (this.pc.signalingState === 'have-remote-offer') {
|
|
7821
|
-
this.logger('
|
|
7874
|
+
this.logger.debug('ICE restart is already in progress');
|
|
7822
7875
|
return;
|
|
7823
7876
|
}
|
|
7824
7877
|
if (this.pc.connectionState === 'new') {
|
|
7825
|
-
this.logger(
|
|
7878
|
+
this.logger.debug(`ICE connection is not yet established, skipping restart.`);
|
|
7826
7879
|
return;
|
|
7827
7880
|
}
|
|
7828
7881
|
const previousIsIceRestarting = this.isIceRestarting;
|
|
@@ -7845,25 +7898,25 @@ class Subscriber extends BasePeerConnection {
|
|
|
7845
7898
|
// example: `e3f6aaf8-b03d-4911-be36-83f47d37a76a:TRACK_TYPE_VIDEO`
|
|
7846
7899
|
const [trackId, rawTrackType] = primaryStream.id.split(':');
|
|
7847
7900
|
const participantToUpdate = this.state.participants.find((p) => p.trackLookupPrefix === trackId);
|
|
7848
|
-
this.logger(
|
|
7901
|
+
this.logger.debug(`[onTrack]: Got remote ${rawTrackType} track for userId: ${participantToUpdate?.userId}`, e.track.id, e.track);
|
|
7849
7902
|
const trackDebugInfo = `${participantToUpdate?.userId} ${rawTrackType}:${trackId}`;
|
|
7850
7903
|
e.track.addEventListener('mute', () => {
|
|
7851
|
-
this.logger(
|
|
7904
|
+
this.logger.info(`[onTrack]: Track muted: ${trackDebugInfo}`);
|
|
7852
7905
|
});
|
|
7853
7906
|
e.track.addEventListener('unmute', () => {
|
|
7854
|
-
this.logger(
|
|
7907
|
+
this.logger.info(`[onTrack]: Track unmuted: ${trackDebugInfo}`);
|
|
7855
7908
|
});
|
|
7856
7909
|
e.track.addEventListener('ended', () => {
|
|
7857
|
-
this.logger(
|
|
7910
|
+
this.logger.info(`[onTrack]: Track ended: ${trackDebugInfo}`);
|
|
7858
7911
|
this.state.removeOrphanedTrack(primaryStream.id);
|
|
7859
7912
|
});
|
|
7860
7913
|
const trackType = toTrackType(rawTrackType);
|
|
7861
7914
|
if (!trackType) {
|
|
7862
|
-
return this.logger(
|
|
7915
|
+
return this.logger.error(`Unknown track type: ${rawTrackType}`);
|
|
7863
7916
|
}
|
|
7864
7917
|
this.trackIdToTrackType.set(e.track.id, trackType);
|
|
7865
7918
|
if (!participantToUpdate) {
|
|
7866
|
-
this.logger(
|
|
7919
|
+
this.logger.warn(`[onTrack]: Received track for unknown participant: ${trackId}`, e);
|
|
7867
7920
|
this.state.registerOrphanedTrack({
|
|
7868
7921
|
id: primaryStream.id,
|
|
7869
7922
|
trackLookupPrefix: trackId,
|
|
@@ -7874,7 +7927,7 @@ class Subscriber extends BasePeerConnection {
|
|
|
7874
7927
|
}
|
|
7875
7928
|
const streamKindProp = trackTypeToParticipantStreamKey(trackType);
|
|
7876
7929
|
if (!streamKindProp) {
|
|
7877
|
-
this.logger(
|
|
7930
|
+
this.logger.error(`Unknown track type: ${rawTrackType}`);
|
|
7878
7931
|
return;
|
|
7879
7932
|
}
|
|
7880
7933
|
// get the previous stream to dispose it later
|
|
@@ -7887,7 +7940,7 @@ class Subscriber extends BasePeerConnection {
|
|
|
7887
7940
|
});
|
|
7888
7941
|
// now, dispose the previous stream if it exists
|
|
7889
7942
|
if (previousStream) {
|
|
7890
|
-
this.logger(
|
|
7943
|
+
this.logger.info(`[onTrack]: Cleaning up previous remote ${e.track.kind} tracks for userId: ${participantToUpdate.userId}`);
|
|
7891
7944
|
previousStream.getTracks().forEach((t) => {
|
|
7892
7945
|
t.stop();
|
|
7893
7946
|
previousStream.removeTrack(t);
|
|
@@ -7903,6 +7956,10 @@ class Subscriber extends BasePeerConnection {
|
|
|
7903
7956
|
const answer = await this.pc.createAnswer();
|
|
7904
7957
|
if (answer.sdp) {
|
|
7905
7958
|
answer.sdp = enableStereo(subscriberOffer.sdp, answer.sdp);
|
|
7959
|
+
const { dangerouslyForceCodec, subscriberFmtpLine } = this.clientPublishOptions || {};
|
|
7960
|
+
if (dangerouslyForceCodec) {
|
|
7961
|
+
answer.sdp = removeCodecsExcept(answer.sdp, dangerouslyForceCodec, subscriberFmtpLine);
|
|
7962
|
+
}
|
|
7906
7963
|
}
|
|
7907
7964
|
await this.pc.setLocalDescription(answer);
|
|
7908
7965
|
await this.sfuClient.sendAnswer({
|
|
@@ -7914,7 +7971,7 @@ class Subscriber extends BasePeerConnection {
|
|
|
7914
7971
|
this.pc.addEventListener('track', this.handleOnTrack);
|
|
7915
7972
|
this.on('subscriberOffer', async (subscriberOffer) => {
|
|
7916
7973
|
return this.negotiate(subscriberOffer).catch((err) => {
|
|
7917
|
-
this.logger(
|
|
7974
|
+
this.logger.error(`Negotiation failed.`, err);
|
|
7918
7975
|
});
|
|
7919
7976
|
});
|
|
7920
7977
|
}
|
|
@@ -7931,20 +7988,20 @@ class Subscriber extends BasePeerConnection {
|
|
|
7931
7988
|
|
|
7932
7989
|
const createWebSocketSignalChannel = (opts) => {
|
|
7933
7990
|
const { endpoint, onMessage, tag, tracer } = opts;
|
|
7934
|
-
const logger = getLogger(
|
|
7935
|
-
logger('
|
|
7991
|
+
const logger = videoLoggerSystem.getLogger('SfuClientWS', { tags: [tag] });
|
|
7992
|
+
logger.debug('Creating signaling WS channel:', endpoint);
|
|
7936
7993
|
const ws = new WebSocket(endpoint);
|
|
7937
7994
|
ws.binaryType = 'arraybuffer'; // do we need this?
|
|
7938
7995
|
ws.addEventListener('error', (e) => {
|
|
7939
|
-
logger('
|
|
7996
|
+
logger.error('Signaling WS channel error', e);
|
|
7940
7997
|
tracer?.trace('signal.ws.error', e);
|
|
7941
7998
|
});
|
|
7942
7999
|
ws.addEventListener('close', (e) => {
|
|
7943
|
-
logger('
|
|
8000
|
+
logger.info('Signaling WS channel is closed', e);
|
|
7944
8001
|
tracer?.trace('signal.ws.close', e);
|
|
7945
8002
|
});
|
|
7946
8003
|
ws.addEventListener('open', (e) => {
|
|
7947
|
-
logger('
|
|
8004
|
+
logger.info('Signaling WS channel is open', e);
|
|
7948
8005
|
tracer?.trace('signal.ws.open', e);
|
|
7949
8006
|
});
|
|
7950
8007
|
ws.addEventListener('message', (e) => {
|
|
@@ -7956,7 +8013,7 @@ const createWebSocketSignalChannel = (opts) => {
|
|
|
7956
8013
|
}
|
|
7957
8014
|
catch (err) {
|
|
7958
8015
|
const message = 'Failed to decode a message. Check whether the Proto models match.';
|
|
7959
|
-
logger(
|
|
8016
|
+
logger.error(message, { event: e, error: err });
|
|
7960
8017
|
tracer?.trace('signal.ws.message.error', message);
|
|
7961
8018
|
}
|
|
7962
8019
|
});
|
|
@@ -8157,14 +8214,14 @@ class StreamSfuClient {
|
|
|
8157
8214
|
this.close = (code = StreamSfuClient.NORMAL_CLOSURE, reason) => {
|
|
8158
8215
|
this.isClosingClean = code !== StreamSfuClient.ERROR_CONNECTION_UNHEALTHY;
|
|
8159
8216
|
if (this.signalWs.readyState === WebSocket.OPEN) {
|
|
8160
|
-
this.logger(
|
|
8217
|
+
this.logger.debug(`Closing SFU WS connection: ${code} - ${reason}`);
|
|
8161
8218
|
this.signalWs.close(code, `js-client: ${reason}`);
|
|
8162
8219
|
this.signalWs.removeEventListener('close', this.handleWebSocketClose);
|
|
8163
8220
|
}
|
|
8164
8221
|
this.dispose();
|
|
8165
8222
|
};
|
|
8166
8223
|
this.dispose = () => {
|
|
8167
|
-
this.logger('
|
|
8224
|
+
this.logger.debug('Disposing SFU client');
|
|
8168
8225
|
this.unsubscribeIceTrickle();
|
|
8169
8226
|
this.unsubscribeNetworkChanged();
|
|
8170
8227
|
clearInterval(this.keepAliveInterval);
|
|
@@ -8184,7 +8241,7 @@ class StreamSfuClient {
|
|
|
8184
8241
|
await this.notifyLeave(reason);
|
|
8185
8242
|
}
|
|
8186
8243
|
catch (err) {
|
|
8187
|
-
this.logger('
|
|
8244
|
+
this.logger.debug('Error notifying SFU about leaving call', err);
|
|
8188
8245
|
}
|
|
8189
8246
|
this.close(StreamSfuClient.NORMAL_CLOSURE, reason.substring(0, 115));
|
|
8190
8247
|
};
|
|
@@ -8305,10 +8362,10 @@ class StreamSfuClient {
|
|
|
8305
8362
|
await this.signalReady(); // wait for the signal ws to be open
|
|
8306
8363
|
const msgJson = SfuRequest.toJson(message);
|
|
8307
8364
|
if (this.signalWs.readyState !== WebSocket.OPEN) {
|
|
8308
|
-
this.logger('
|
|
8365
|
+
this.logger.debug('Signal WS is not open. Skipping message', msgJson);
|
|
8309
8366
|
return;
|
|
8310
8367
|
}
|
|
8311
|
-
this.logger(
|
|
8368
|
+
this.logger.debug(`Sending message to: ${this.edgeName}`, msgJson);
|
|
8312
8369
|
this.signalWs.send(SfuRequest.toBinary(message));
|
|
8313
8370
|
};
|
|
8314
8371
|
this.keepAlive = () => {
|
|
@@ -8316,7 +8373,7 @@ class StreamSfuClient {
|
|
|
8316
8373
|
timers.clearInterval(this.keepAliveInterval);
|
|
8317
8374
|
this.keepAliveInterval = timers.setInterval(() => {
|
|
8318
8375
|
this.ping().catch((e) => {
|
|
8319
|
-
this.logger('
|
|
8376
|
+
this.logger.error('Error sending healthCheckRequest to SFU', e);
|
|
8320
8377
|
});
|
|
8321
8378
|
}, this.pingIntervalInMs);
|
|
8322
8379
|
};
|
|
@@ -8339,7 +8396,7 @@ class StreamSfuClient {
|
|
|
8339
8396
|
this.edgeName = server.edge_name;
|
|
8340
8397
|
this.joinResponseTimeout = joinResponseTimeout;
|
|
8341
8398
|
this.tag = tag;
|
|
8342
|
-
this.logger = getLogger(
|
|
8399
|
+
this.logger = videoLoggerSystem.getLogger('SfuClient', { tags: [tag] });
|
|
8343
8400
|
this.tracer = enableTracing
|
|
8344
8401
|
? new Tracer(`${tag}-${this.edgeName}`)
|
|
8345
8402
|
: undefined;
|
|
@@ -8348,7 +8405,8 @@ class StreamSfuClient {
|
|
|
8348
8405
|
interceptors: [
|
|
8349
8406
|
withHeaders({ Authorization: `Bearer ${token}` }),
|
|
8350
8407
|
this.tracer && withRequestTracer(this.tracer.trace),
|
|
8351
|
-
getLogLevel() === 'trace' &&
|
|
8408
|
+
this.logger.getLogLevel() === 'trace' &&
|
|
8409
|
+
withRequestLogger(this.logger, 'trace'),
|
|
8352
8410
|
].filter((v) => !!v),
|
|
8353
8411
|
});
|
|
8354
8412
|
// Special handling for the ICETrickle kind of events.
|
|
@@ -8436,13 +8494,13 @@ const watchCallRejected = (call) => {
|
|
|
8436
8494
|
const { call: eventCall } = event;
|
|
8437
8495
|
const { session: callSession } = eventCall;
|
|
8438
8496
|
if (!callSession) {
|
|
8439
|
-
call.logger('
|
|
8497
|
+
call.logger.warn('No call session provided. Ignoring call.rejected event.', event);
|
|
8440
8498
|
return;
|
|
8441
8499
|
}
|
|
8442
8500
|
const rejectedBy = callSession.rejected_by;
|
|
8443
8501
|
const { members, callingState } = call.state;
|
|
8444
8502
|
if (callingState !== CallingState.RINGING) {
|
|
8445
|
-
call.logger('
|
|
8503
|
+
call.logger.info('Call is not in ringing mode (it is either accepted or rejected already). Ignoring call.rejected event.', event);
|
|
8446
8504
|
return;
|
|
8447
8505
|
}
|
|
8448
8506
|
if (call.isCreatedByMe) {
|
|
@@ -8450,7 +8508,7 @@ const watchCallRejected = (call) => {
|
|
|
8450
8508
|
.filter((m) => m.user_id !== call.currentUserId)
|
|
8451
8509
|
.every((m) => rejectedBy[m.user_id]);
|
|
8452
8510
|
if (everyoneElseRejected) {
|
|
8453
|
-
call.logger('
|
|
8511
|
+
call.logger.info('everyone rejected, leaving the call');
|
|
8454
8512
|
await call.leave({
|
|
8455
8513
|
reject: true,
|
|
8456
8514
|
reason: 'cancel',
|
|
@@ -8460,7 +8518,7 @@ const watchCallRejected = (call) => {
|
|
|
8460
8518
|
}
|
|
8461
8519
|
else {
|
|
8462
8520
|
if (rejectedBy[eventCall.created_by.id]) {
|
|
8463
|
-
call.logger('
|
|
8521
|
+
call.logger.info('call creator rejected, leaving call');
|
|
8464
8522
|
await call.leave({ message: 'ring: creator rejected' });
|
|
8465
8523
|
}
|
|
8466
8524
|
}
|
|
@@ -8477,7 +8535,7 @@ const watchCallEnded = (call) => {
|
|
|
8477
8535
|
call
|
|
8478
8536
|
.leave({ message: 'call.ended event received', reject: false })
|
|
8479
8537
|
.catch((err) => {
|
|
8480
|
-
call.logger('
|
|
8538
|
+
call.logger.error('Failed to leave call after call.ended ', err);
|
|
8481
8539
|
});
|
|
8482
8540
|
}
|
|
8483
8541
|
};
|
|
@@ -8505,7 +8563,7 @@ const watchSfuCallEnded = (call) => {
|
|
|
8505
8563
|
await call.leave({ message: `callEnded received: ${reason}` });
|
|
8506
8564
|
}
|
|
8507
8565
|
catch (err) {
|
|
8508
|
-
call.logger('
|
|
8566
|
+
call.logger.error('Failed to leave call after being ended by the SFU', err);
|
|
8509
8567
|
}
|
|
8510
8568
|
});
|
|
8511
8569
|
};
|
|
@@ -8588,7 +8646,7 @@ const watchLiveEnded = (dispatcher, call) => {
|
|
|
8588
8646
|
call.state.setBackstage(true);
|
|
8589
8647
|
if (!call.permissionsContext.hasPermission(OwnCapability.JOIN_BACKSTAGE)) {
|
|
8590
8648
|
call.leave({ message: 'live ended' }).catch((err) => {
|
|
8591
|
-
call.logger('
|
|
8649
|
+
call.logger.error('Failed to leave call after live ended', err);
|
|
8592
8650
|
});
|
|
8593
8651
|
}
|
|
8594
8652
|
});
|
|
@@ -8600,9 +8658,9 @@ const watchSfuErrorReports = (dispatcher) => {
|
|
|
8600
8658
|
return dispatcher.on('error', (e) => {
|
|
8601
8659
|
if (!e.error)
|
|
8602
8660
|
return;
|
|
8603
|
-
const logger = getLogger(
|
|
8661
|
+
const logger = videoLoggerSystem.getLogger('SfuClient');
|
|
8604
8662
|
const { error, reconnectStrategy } = e;
|
|
8605
|
-
logger('
|
|
8663
|
+
logger.error('SFU reported error', {
|
|
8606
8664
|
code: ErrorCode[error.code],
|
|
8607
8665
|
reconnectStrategy: WebsocketReconnectStrategy[reconnectStrategy],
|
|
8608
8666
|
message: error.message,
|
|
@@ -8656,7 +8714,7 @@ const handleRemoteSoftMute = (call) => {
|
|
|
8656
8714
|
if (cause === TrackUnpublishReason.MODERATION &&
|
|
8657
8715
|
sessionId === localParticipant?.sessionId) {
|
|
8658
8716
|
const logger = call.logger;
|
|
8659
|
-
logger(
|
|
8717
|
+
logger.info(`Local participant's ${TrackType[type]} track is muted remotely`);
|
|
8660
8718
|
try {
|
|
8661
8719
|
if (type === TrackType.VIDEO) {
|
|
8662
8720
|
await call.camera.disable();
|
|
@@ -8669,11 +8727,11 @@ const handleRemoteSoftMute = (call) => {
|
|
|
8669
8727
|
await call.screenShare.disable();
|
|
8670
8728
|
}
|
|
8671
8729
|
else {
|
|
8672
|
-
logger('
|
|
8730
|
+
logger.warn('Unsupported track type to soft mute', TrackType[type]);
|
|
8673
8731
|
}
|
|
8674
8732
|
}
|
|
8675
8733
|
catch (error) {
|
|
8676
|
-
logger('
|
|
8734
|
+
logger.error('Failed to stop publishing', error);
|
|
8677
8735
|
}
|
|
8678
8736
|
}
|
|
8679
8737
|
});
|
|
@@ -8994,7 +9052,7 @@ class DynascaleManager {
|
|
|
8994
9052
|
* The viewport tracker instance.
|
|
8995
9053
|
*/
|
|
8996
9054
|
this.viewportTracker = new ViewportTracker();
|
|
8997
|
-
this.logger = getLogger(
|
|
9055
|
+
this.logger = videoLoggerSystem.getLogger('DynascaleManager');
|
|
8998
9056
|
this.pendingSubscriptionsUpdate = null;
|
|
8999
9057
|
this.videoTrackSubscriptionOverridesSubject = new BehaviorSubject({});
|
|
9000
9058
|
this.videoTrackSubscriptionOverrides$ = this.videoTrackSubscriptionOverridesSubject.asObservable();
|
|
@@ -9051,7 +9109,7 @@ class DynascaleManager {
|
|
|
9051
9109
|
this.sfuClient
|
|
9052
9110
|
?.updateSubscriptions(this.trackSubscriptions)
|
|
9053
9111
|
.catch((err) => {
|
|
9054
|
-
this.logger(
|
|
9112
|
+
this.logger.debug(`Failed to update track subscriptions`, err);
|
|
9055
9113
|
});
|
|
9056
9114
|
};
|
|
9057
9115
|
if (debounceType) {
|
|
@@ -9140,7 +9198,7 @@ class DynascaleManager {
|
|
|
9140
9198
|
// is not visible (e.g., has display: none).
|
|
9141
9199
|
// we treat this as "unsubscription" as we don't want to keep
|
|
9142
9200
|
// consuming bandwidth for a video that is not visible on the screen.
|
|
9143
|
-
this.logger(
|
|
9201
|
+
this.logger.debug(`Ignoring 0x0 dimension`, boundParticipant);
|
|
9144
9202
|
dimension = undefined;
|
|
9145
9203
|
}
|
|
9146
9204
|
this.callState.updateParticipantTracks(trackType, {
|
|
@@ -9245,7 +9303,7 @@ class DynascaleManager {
|
|
|
9245
9303
|
setTimeout(() => {
|
|
9246
9304
|
videoElement.srcObject = source ?? null;
|
|
9247
9305
|
videoElement.play().catch((e) => {
|
|
9248
|
-
this.logger(
|
|
9306
|
+
this.logger.warn(`Failed to play stream`, e);
|
|
9249
9307
|
});
|
|
9250
9308
|
// we add extra delay until we attempt to force-play
|
|
9251
9309
|
// the participant's media stream in Firefox and Safari,
|
|
@@ -9282,13 +9340,13 @@ class DynascaleManager {
|
|
|
9282
9340
|
return;
|
|
9283
9341
|
if ('setSinkId' in audioElement) {
|
|
9284
9342
|
audioElement.setSinkId(deviceId).catch((e) => {
|
|
9285
|
-
this.logger(
|
|
9343
|
+
this.logger.warn(`Can't to set AudioElement sinkId`, e);
|
|
9286
9344
|
});
|
|
9287
9345
|
}
|
|
9288
9346
|
if (audioContext && 'setSinkId' in audioContext) {
|
|
9289
9347
|
// @ts-expect-error setSinkId is not available in all browsers
|
|
9290
9348
|
audioContext.setSinkId(deviceId).catch((e) => {
|
|
9291
|
-
this.logger(
|
|
9349
|
+
this.logger.warn(`Can't to set AudioContext sinkId`, e);
|
|
9292
9350
|
});
|
|
9293
9351
|
}
|
|
9294
9352
|
};
|
|
@@ -9330,7 +9388,7 @@ class DynascaleManager {
|
|
|
9330
9388
|
// we will play audio directly through the audio element in other browsers
|
|
9331
9389
|
audioElement.muted = false;
|
|
9332
9390
|
audioElement.play().catch((e) => {
|
|
9333
|
-
this.logger(
|
|
9391
|
+
this.logger.warn(`Failed to play audio stream`, e);
|
|
9334
9392
|
});
|
|
9335
9393
|
}
|
|
9336
9394
|
const { selectedDevice } = this.speaker.state;
|
|
@@ -9382,7 +9440,7 @@ class DynascaleManager {
|
|
|
9382
9440
|
if (this.audioContext?.state === 'suspended') {
|
|
9383
9441
|
this.audioContext
|
|
9384
9442
|
.resume()
|
|
9385
|
-
.catch((err) => this.logger(
|
|
9443
|
+
.catch((err) => this.logger.warn(`Can't resume audio context`, err))
|
|
9386
9444
|
.then(() => {
|
|
9387
9445
|
document.removeEventListener('click', this.resumeAudioContext);
|
|
9388
9446
|
});
|
|
@@ -9619,7 +9677,7 @@ class BrowserPermission {
|
|
|
9619
9677
|
this.disposeController = new AbortController();
|
|
9620
9678
|
this.wasPrompted = false;
|
|
9621
9679
|
this.listeners = new Set();
|
|
9622
|
-
this.logger = getLogger(
|
|
9680
|
+
this.logger = videoLoggerSystem.getLogger('permissions');
|
|
9623
9681
|
const signal = this.disposeController.signal;
|
|
9624
9682
|
this.ready = (async () => {
|
|
9625
9683
|
const assumeGranted = () => {
|
|
@@ -9645,7 +9703,7 @@ class BrowserPermission {
|
|
|
9645
9703
|
}
|
|
9646
9704
|
}
|
|
9647
9705
|
catch (err) {
|
|
9648
|
-
this.logger('
|
|
9706
|
+
this.logger.debug('Failed to query permission status', err);
|
|
9649
9707
|
assumeGranted();
|
|
9650
9708
|
}
|
|
9651
9709
|
})();
|
|
@@ -9684,7 +9742,7 @@ class BrowserPermission {
|
|
|
9684
9742
|
typeof e === 'object' &&
|
|
9685
9743
|
'name' in e &&
|
|
9686
9744
|
(e.name === 'NotAllowedError' || e.name === 'SecurityError')) {
|
|
9687
|
-
this.logger('
|
|
9745
|
+
this.logger.info('Browser permission was not granted', {
|
|
9688
9746
|
permission: this.permission,
|
|
9689
9747
|
});
|
|
9690
9748
|
this.setState('denied');
|
|
@@ -9693,7 +9751,7 @@ class BrowserPermission {
|
|
|
9693
9751
|
}
|
|
9694
9752
|
return false;
|
|
9695
9753
|
}
|
|
9696
|
-
this.logger(
|
|
9754
|
+
this.logger.error(`Failed to getUserMedia`, {
|
|
9697
9755
|
error: e,
|
|
9698
9756
|
permission: this.permission,
|
|
9699
9757
|
});
|
|
@@ -9914,10 +9972,12 @@ const getAudioStream = async (trackConstraints, tracer) => {
|
|
|
9914
9972
|
if (isNotFoundOrOverconstrainedError(error) && trackConstraints?.deviceId) {
|
|
9915
9973
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
9916
9974
|
const { deviceId, ...relaxedConstraints } = trackConstraints;
|
|
9917
|
-
|
|
9975
|
+
videoLoggerSystem
|
|
9976
|
+
.getLogger('devices')
|
|
9977
|
+
.warn('Failed to get audio stream, will try again with relaxed constraints', { error, constraints, relaxedConstraints });
|
|
9918
9978
|
return getAudioStream(relaxedConstraints);
|
|
9919
9979
|
}
|
|
9920
|
-
getLogger(
|
|
9980
|
+
videoLoggerSystem.getLogger('devices').error('Failed to get audio stream', {
|
|
9921
9981
|
error,
|
|
9922
9982
|
constraints,
|
|
9923
9983
|
});
|
|
@@ -9950,10 +10010,12 @@ const getVideoStream = async (trackConstraints, tracer) => {
|
|
|
9950
10010
|
if (isNotFoundOrOverconstrainedError(error) && trackConstraints?.deviceId) {
|
|
9951
10011
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
9952
10012
|
const { deviceId, ...relaxedConstraints } = trackConstraints;
|
|
9953
|
-
|
|
10013
|
+
videoLoggerSystem
|
|
10014
|
+
.getLogger('devices')
|
|
10015
|
+
.warn('Failed to get video stream, will try again with relaxed constraints', { error, constraints, relaxedConstraints });
|
|
9954
10016
|
return getVideoStream(relaxedConstraints);
|
|
9955
10017
|
}
|
|
9956
|
-
getLogger(
|
|
10018
|
+
videoLoggerSystem.getLogger('devices').error('Failed to get video stream', {
|
|
9957
10019
|
error,
|
|
9958
10020
|
constraints,
|
|
9959
10021
|
});
|
|
@@ -10002,7 +10064,9 @@ const getScreenShareStream = async (options, tracer) => {
|
|
|
10002
10064
|
}
|
|
10003
10065
|
catch (e) {
|
|
10004
10066
|
tracer?.trace(`${tag}OnFailure`, e.name);
|
|
10005
|
-
|
|
10067
|
+
videoLoggerSystem
|
|
10068
|
+
.getLogger('devices')
|
|
10069
|
+
.error('Failed to get screen share stream', e);
|
|
10006
10070
|
throw e;
|
|
10007
10071
|
}
|
|
10008
10072
|
};
|
|
@@ -10064,7 +10128,7 @@ class DeviceManager {
|
|
|
10064
10128
|
this.call = call;
|
|
10065
10129
|
this.state = state;
|
|
10066
10130
|
this.trackType = trackType;
|
|
10067
|
-
this.logger = getLogger(
|
|
10131
|
+
this.logger = videoLoggerSystem.getLogger(`${TrackType[trackType].toLowerCase()} manager`);
|
|
10068
10132
|
this.setup();
|
|
10069
10133
|
}
|
|
10070
10134
|
setup() {
|
|
@@ -10227,6 +10291,7 @@ class DeviceManager {
|
|
|
10227
10291
|
}
|
|
10228
10292
|
}
|
|
10229
10293
|
async applySettingsToStream() {
|
|
10294
|
+
console.log('applySettingsToStream ');
|
|
10230
10295
|
await withCancellation(this.statusChangeConcurrencyTag, async (signal) => {
|
|
10231
10296
|
if (this.enabled) {
|
|
10232
10297
|
try {
|
|
@@ -10259,7 +10324,7 @@ class DeviceManager {
|
|
|
10259
10324
|
const mediaStream = this.state.mediaStream;
|
|
10260
10325
|
if (!mediaStream)
|
|
10261
10326
|
return;
|
|
10262
|
-
this.logger(
|
|
10327
|
+
this.logger.debug(`${stopTracks ? 'Stopping' : 'Disabling'} stream`);
|
|
10263
10328
|
if (this.call.state.callingState === CallingState.JOINED) {
|
|
10264
10329
|
await this.stopPublishStream();
|
|
10265
10330
|
}
|
|
@@ -10305,7 +10370,7 @@ class DeviceManager {
|
|
|
10305
10370
|
}
|
|
10306
10371
|
}
|
|
10307
10372
|
async unmuteStream() {
|
|
10308
|
-
this.logger('
|
|
10373
|
+
this.logger.debug('Starting stream');
|
|
10309
10374
|
let stream;
|
|
10310
10375
|
let rootStream;
|
|
10311
10376
|
if (this.state.mediaStream &&
|
|
@@ -10386,7 +10451,7 @@ class DeviceManager {
|
|
|
10386
10451
|
return output;
|
|
10387
10452
|
})
|
|
10388
10453
|
.then(chainWith(parent), (error) => {
|
|
10389
|
-
this.logger('
|
|
10454
|
+
this.logger.warn('Filter failed to start and will be ignored', error);
|
|
10390
10455
|
return parent;
|
|
10391
10456
|
}), rootStream);
|
|
10392
10457
|
}
|
|
@@ -10409,7 +10474,7 @@ class DeviceManager {
|
|
|
10409
10474
|
if (!isMobile() || this.trackType !== TrackType.VIDEO)
|
|
10410
10475
|
return;
|
|
10411
10476
|
this.call.notifyTrackMuteState(muted, this.trackType).catch((err) => {
|
|
10412
|
-
this.logger('
|
|
10477
|
+
this.logger.warn('Error while notifying track mute state', err);
|
|
10413
10478
|
});
|
|
10414
10479
|
};
|
|
10415
10480
|
stream.getTracks().forEach((track) => {
|
|
@@ -10473,7 +10538,7 @@ class DeviceManager {
|
|
|
10473
10538
|
}
|
|
10474
10539
|
}
|
|
10475
10540
|
catch (err) {
|
|
10476
|
-
this.logger('
|
|
10541
|
+
this.logger.warn('Unexpected error while handling disconnected or replaced device', err);
|
|
10477
10542
|
}
|
|
10478
10543
|
}));
|
|
10479
10544
|
}
|
|
@@ -10680,7 +10745,7 @@ class CameraManager extends DeviceManager {
|
|
|
10680
10745
|
*/
|
|
10681
10746
|
async selectDirection(direction) {
|
|
10682
10747
|
if (!this.isDirectionSupportedByDevice()) {
|
|
10683
|
-
this.logger('
|
|
10748
|
+
this.logger.warn('Setting direction is not supported on this device');
|
|
10684
10749
|
return;
|
|
10685
10750
|
}
|
|
10686
10751
|
if (isReactNative()) {
|
|
@@ -10731,7 +10796,7 @@ class CameraManager extends DeviceManager {
|
|
|
10731
10796
|
}
|
|
10732
10797
|
catch (error) {
|
|
10733
10798
|
// couldn't enable device, target resolution will be applied the next time user attempts to start the device
|
|
10734
|
-
this.logger('
|
|
10799
|
+
this.logger.warn('could not apply target resolution', error);
|
|
10735
10800
|
}
|
|
10736
10801
|
}
|
|
10737
10802
|
if (this.enabled && this.state.mediaStream) {
|
|
@@ -10742,7 +10807,7 @@ class CameraManager extends DeviceManager {
|
|
|
10742
10807
|
if (width !== this.targetResolution.width ||
|
|
10743
10808
|
height !== this.targetResolution.height) {
|
|
10744
10809
|
await this.applySettingsToStream();
|
|
10745
|
-
this.logger(
|
|
10810
|
+
this.logger.debug(`${width}x${height} target resolution applied to media stream`);
|
|
10746
10811
|
}
|
|
10747
10812
|
}
|
|
10748
10813
|
}
|
|
@@ -11006,8 +11071,8 @@ class RNSpeechDetector {
|
|
|
11006
11071
|
};
|
|
11007
11072
|
}
|
|
11008
11073
|
catch (error) {
|
|
11009
|
-
const logger = getLogger(
|
|
11010
|
-
logger('error
|
|
11074
|
+
const logger = videoLoggerSystem.getLogger('RNSpeechDetector');
|
|
11075
|
+
logger.error('error handling permissions: ', error);
|
|
11011
11076
|
return () => { };
|
|
11012
11077
|
}
|
|
11013
11078
|
}
|
|
@@ -11091,8 +11156,8 @@ class RNSpeechDetector {
|
|
|
11091
11156
|
}
|
|
11092
11157
|
}
|
|
11093
11158
|
catch (error) {
|
|
11094
|
-
const logger = getLogger(
|
|
11095
|
-
logger('error
|
|
11159
|
+
const logger = videoLoggerSystem.getLogger('RNSpeechDetector');
|
|
11160
|
+
logger.error('error checking audio level from stats', error);
|
|
11096
11161
|
}
|
|
11097
11162
|
};
|
|
11098
11163
|
// Call checkAudioLevel periodically (every 100ms)
|
|
@@ -11152,7 +11217,7 @@ class MicrophoneManager extends AudioDeviceManager {
|
|
|
11152
11217
|
}
|
|
11153
11218
|
}
|
|
11154
11219
|
catch (err) {
|
|
11155
|
-
this.logger('
|
|
11220
|
+
this.logger.warn('Could not enable speaking while muted', err);
|
|
11156
11221
|
}
|
|
11157
11222
|
}));
|
|
11158
11223
|
this.subscriptions.push(createSubscription(this.call.state.callingState$, (callingState) => {
|
|
@@ -11175,7 +11240,7 @@ class MicrophoneManager extends AudioDeviceManager {
|
|
|
11175
11240
|
}
|
|
11176
11241
|
})
|
|
11177
11242
|
.catch((err) => {
|
|
11178
|
-
this.logger(
|
|
11243
|
+
this.logger.warn(`Failed to enable noise cancellation`, err);
|
|
11179
11244
|
return this.call.notifyNoiseCancellationStopped();
|
|
11180
11245
|
});
|
|
11181
11246
|
}
|
|
@@ -11183,7 +11248,7 @@ class MicrophoneManager extends AudioDeviceManager {
|
|
|
11183
11248
|
this.noiseCancellationRegistration
|
|
11184
11249
|
.then(() => this.noiseCancellation?.disable())
|
|
11185
11250
|
.catch((err) => {
|
|
11186
|
-
this.logger(
|
|
11251
|
+
this.logger.warn(`Failed to disable noise cancellation`, err);
|
|
11187
11252
|
});
|
|
11188
11253
|
}
|
|
11189
11254
|
}));
|
|
@@ -11212,12 +11277,12 @@ class MicrophoneManager extends AudioDeviceManager {
|
|
|
11212
11277
|
this.call.tracer.trace('noiseCancellation.enabled', enabled);
|
|
11213
11278
|
if (enabled) {
|
|
11214
11279
|
this.call.notifyNoiseCancellationStarting().catch((err) => {
|
|
11215
|
-
this.logger(
|
|
11280
|
+
this.logger.warn(`notifyNoiseCancellationStart failed`, err);
|
|
11216
11281
|
});
|
|
11217
11282
|
}
|
|
11218
11283
|
else {
|
|
11219
11284
|
this.call.notifyNoiseCancellationStopped().catch((err) => {
|
|
11220
|
-
this.logger(
|
|
11285
|
+
this.logger.warn(`notifyNoiseCancellationStop failed`, err);
|
|
11221
11286
|
});
|
|
11222
11287
|
}
|
|
11223
11288
|
});
|
|
@@ -11246,9 +11311,9 @@ class MicrophoneManager extends AudioDeviceManager {
|
|
|
11246
11311
|
}
|
|
11247
11312
|
}
|
|
11248
11313
|
catch (e) {
|
|
11249
|
-
this.logger('
|
|
11314
|
+
this.logger.warn('Failed to enable noise cancellation', e);
|
|
11250
11315
|
await this.disableNoiseCancellation().catch((err) => {
|
|
11251
|
-
this.logger('
|
|
11316
|
+
this.logger.warn('Failed to disable noise cancellation', err);
|
|
11252
11317
|
});
|
|
11253
11318
|
throw e;
|
|
11254
11319
|
}
|
|
@@ -11261,7 +11326,7 @@ class MicrophoneManager extends AudioDeviceManager {
|
|
|
11261
11326
|
.then(() => this.noiseCancellation?.disable())
|
|
11262
11327
|
.then(() => this.noiseCancellationChangeUnsubscribe?.())
|
|
11263
11328
|
.catch((err) => {
|
|
11264
|
-
this.logger('
|
|
11329
|
+
this.logger.warn('Failed to unregister noise cancellation', err);
|
|
11265
11330
|
});
|
|
11266
11331
|
this.call.tracer.trace('noiseCancellation.disabled', true);
|
|
11267
11332
|
await this.call.notifyNoiseCancellationStopped();
|
|
@@ -11769,9 +11834,9 @@ class Call {
|
|
|
11769
11834
|
return;
|
|
11770
11835
|
const currentUserId = this.currentUserId;
|
|
11771
11836
|
if (currentUserId && blockedUserIds.includes(currentUserId)) {
|
|
11772
|
-
this.logger('
|
|
11837
|
+
this.logger.info('Leaving call because of being blocked');
|
|
11773
11838
|
await this.leave({ message: 'user blocked' }).catch((err) => {
|
|
11774
|
-
this.logger('
|
|
11839
|
+
this.logger.error('Error leaving call after being blocked', err);
|
|
11775
11840
|
});
|
|
11776
11841
|
}
|
|
11777
11842
|
}));
|
|
@@ -11807,7 +11872,7 @@ class Call {
|
|
|
11807
11872
|
if ((isAcceptedElsewhere || isRejectedByMe) &&
|
|
11808
11873
|
!hasPending(this.joinLeaveConcurrencyTag)) {
|
|
11809
11874
|
this.leave().catch(() => {
|
|
11810
|
-
this.logger('
|
|
11875
|
+
this.logger.error('Could not leave a call that was accepted or rejected elsewhere');
|
|
11811
11876
|
});
|
|
11812
11877
|
}
|
|
11813
11878
|
}));
|
|
@@ -11887,7 +11952,7 @@ class Call {
|
|
|
11887
11952
|
}
|
|
11888
11953
|
}
|
|
11889
11954
|
catch (err) {
|
|
11890
|
-
this.logger(
|
|
11955
|
+
this.logger.error(`Can't disable mic/camera/screenshare after revoked permissions`, err);
|
|
11891
11956
|
}
|
|
11892
11957
|
}
|
|
11893
11958
|
};
|
|
@@ -12148,13 +12213,13 @@ class Call {
|
|
|
12148
12213
|
maxJoinRetries = Math.max(maxJoinRetries, 1);
|
|
12149
12214
|
for (let attempt = 0; attempt < maxJoinRetries; attempt++) {
|
|
12150
12215
|
try {
|
|
12151
|
-
this.logger(
|
|
12216
|
+
this.logger.trace(`Joining call (${attempt})`, this.cid);
|
|
12152
12217
|
await this.doJoin(data);
|
|
12153
12218
|
delete joinData.migrating_from;
|
|
12154
12219
|
break;
|
|
12155
12220
|
}
|
|
12156
12221
|
catch (err) {
|
|
12157
|
-
this.logger(
|
|
12222
|
+
this.logger.warn(`Failed to join call (${attempt})`, this.cid);
|
|
12158
12223
|
if (err instanceof ErrorFromResponse && err.unrecoverable) {
|
|
12159
12224
|
// if the error is unrecoverable, we should not retry as that signals
|
|
12160
12225
|
// that connectivity is good, but the coordinator doesn't allow the user
|
|
@@ -12184,7 +12249,7 @@ class Call {
|
|
|
12184
12249
|
const connectStartTime = Date.now();
|
|
12185
12250
|
const callingState = this.state.callingState;
|
|
12186
12251
|
this.joinCallData = data;
|
|
12187
|
-
this.logger('
|
|
12252
|
+
this.logger.debug('Starting join flow');
|
|
12188
12253
|
this.state.setCallingState(CallingState.JOINING);
|
|
12189
12254
|
const performingMigration = this.reconnectStrategy === WebsocketReconnectStrategy.MIGRATE;
|
|
12190
12255
|
const performingRejoin = this.reconnectStrategy === WebsocketReconnectStrategy.REJOIN;
|
|
@@ -12235,9 +12300,10 @@ class Call {
|
|
|
12235
12300
|
// prepare a generic SDP and send it to the SFU.
|
|
12236
12301
|
// these are throw-away SDPs that the SFU will use to determine
|
|
12237
12302
|
// the capabilities of the client (codec support, etc.)
|
|
12303
|
+
const { dangerouslyForceCodec, fmtpLine, subscriberFmtpLine } = this.clientPublishOptions || {};
|
|
12238
12304
|
const [subscriberSdp, publisherSdp] = await Promise.all([
|
|
12239
|
-
getGenericSdp('recvonly'),
|
|
12240
|
-
getGenericSdp('sendonly'),
|
|
12305
|
+
getGenericSdp('recvonly', dangerouslyForceCodec, subscriberFmtpLine),
|
|
12306
|
+
getGenericSdp('sendonly', dangerouslyForceCodec, fmtpLine),
|
|
12241
12307
|
]);
|
|
12242
12308
|
const isReconnecting = this.reconnectStrategy !== WebsocketReconnectStrategy.UNSPECIFIED;
|
|
12243
12309
|
const reconnectDetails = isReconnecting
|
|
@@ -12269,7 +12335,7 @@ class Call {
|
|
|
12269
12335
|
}
|
|
12270
12336
|
}
|
|
12271
12337
|
catch (error) {
|
|
12272
|
-
this.logger('
|
|
12338
|
+
this.logger.warn('Join SFU request failed', error);
|
|
12273
12339
|
sfuClient.close(StreamSfuClient.JOIN_FAILED, 'Join request failed, connection considered unhealthy');
|
|
12274
12340
|
// restore the previous call state if the join-flow fails
|
|
12275
12341
|
this.state.setCallingState(callingState);
|
|
@@ -12326,7 +12392,7 @@ class Call {
|
|
|
12326
12392
|
// reset the reconnect strategy to unspecified after a successful reconnection
|
|
12327
12393
|
this.reconnectStrategy = WebsocketReconnectStrategy.UNSPECIFIED;
|
|
12328
12394
|
this.reconnectReason = '';
|
|
12329
|
-
this.logger(
|
|
12395
|
+
this.logger.info(`Joined call ${this.cid}`);
|
|
12330
12396
|
};
|
|
12331
12397
|
/**
|
|
12332
12398
|
* Prepares Reconnect Details object.
|
|
@@ -12429,20 +12495,22 @@ class Call {
|
|
|
12429
12495
|
if (closePreviousInstances && this.subscriber) {
|
|
12430
12496
|
this.subscriber.dispose();
|
|
12431
12497
|
}
|
|
12432
|
-
|
|
12498
|
+
const basePeerConnectionOptions = {
|
|
12433
12499
|
sfuClient,
|
|
12434
12500
|
dispatcher: this.dispatcher,
|
|
12435
12501
|
state: this.state,
|
|
12436
12502
|
connectionConfig,
|
|
12437
12503
|
tag: sfuClient.tag,
|
|
12438
12504
|
enableTracing,
|
|
12439
|
-
|
|
12505
|
+
clientPublishOptions: this.clientPublishOptions,
|
|
12506
|
+
onReconnectionNeeded: (kind, reason, peerType) => {
|
|
12440
12507
|
this.reconnect(kind, reason).catch((err) => {
|
|
12441
|
-
const message = `[Reconnect] Error reconnecting after a
|
|
12442
|
-
this.logger(
|
|
12508
|
+
const message = `[Reconnect] Error reconnecting, after a ${PeerType[peerType]} error: ${reason}`;
|
|
12509
|
+
this.logger.warn(message, err);
|
|
12443
12510
|
});
|
|
12444
12511
|
},
|
|
12445
|
-
}
|
|
12512
|
+
};
|
|
12513
|
+
this.subscriber = new Subscriber(basePeerConnectionOptions);
|
|
12446
12514
|
// anonymous users can't publish anything hence, there is no need
|
|
12447
12515
|
// to create Publisher Peer Connection for them
|
|
12448
12516
|
const isAnonymous = this.streamClient.user?.type === 'anonymous';
|
|
@@ -12450,21 +12518,7 @@ class Call {
|
|
|
12450
12518
|
if (closePreviousInstances && this.publisher) {
|
|
12451
12519
|
this.publisher.dispose();
|
|
12452
12520
|
}
|
|
12453
|
-
this.publisher = new Publisher(
|
|
12454
|
-
sfuClient,
|
|
12455
|
-
dispatcher: this.dispatcher,
|
|
12456
|
-
state: this.state,
|
|
12457
|
-
connectionConfig,
|
|
12458
|
-
publishOptions,
|
|
12459
|
-
tag: sfuClient.tag,
|
|
12460
|
-
enableTracing,
|
|
12461
|
-
onReconnectionNeeded: (kind, reason) => {
|
|
12462
|
-
this.reconnect(kind, reason).catch((err) => {
|
|
12463
|
-
const message = `[Reconnect] Error reconnecting after a publisher error: ${reason}`;
|
|
12464
|
-
this.logger('warn', message, err);
|
|
12465
|
-
});
|
|
12466
|
-
},
|
|
12467
|
-
});
|
|
12521
|
+
this.publisher = new Publisher(basePeerConnectionOptions, publishOptions);
|
|
12468
12522
|
}
|
|
12469
12523
|
this.statsReporter?.stop();
|
|
12470
12524
|
if (this.statsReportingIntervalInMs > 0) {
|
|
@@ -12530,7 +12584,7 @@ class Call {
|
|
|
12530
12584
|
* @param reason the reason for the closure.
|
|
12531
12585
|
*/
|
|
12532
12586
|
this.handleSfuSignalClose = (sfuClient, reason) => {
|
|
12533
|
-
this.logger('
|
|
12587
|
+
this.logger.debug('[Reconnect] SFU signal connection closed');
|
|
12534
12588
|
const { callingState } = this.state;
|
|
12535
12589
|
if (
|
|
12536
12590
|
// SFU WS closed before we finished current join,
|
|
@@ -12551,7 +12605,7 @@ class Call {
|
|
|
12551
12605
|
? WebsocketReconnectStrategy.FAST
|
|
12552
12606
|
: WebsocketReconnectStrategy.REJOIN;
|
|
12553
12607
|
this.reconnect(strategy, reason).catch((err) => {
|
|
12554
|
-
this.logger('
|
|
12608
|
+
this.logger.warn('[Reconnect] Error reconnecting', err);
|
|
12555
12609
|
});
|
|
12556
12610
|
};
|
|
12557
12611
|
/**
|
|
@@ -12587,7 +12641,7 @@ class Call {
|
|
|
12587
12641
|
const shouldGiveUpReconnecting = this.disconnectionTimeoutSeconds > 0 &&
|
|
12588
12642
|
reconnectingTime / 1000 > this.disconnectionTimeoutSeconds;
|
|
12589
12643
|
if (shouldGiveUpReconnecting) {
|
|
12590
|
-
this.logger('
|
|
12644
|
+
this.logger.warn('[Reconnect] Stopping reconnection attempts after reaching disconnection timeout');
|
|
12591
12645
|
await markAsReconnectingFailed();
|
|
12592
12646
|
return;
|
|
12593
12647
|
}
|
|
@@ -12599,11 +12653,11 @@ class Call {
|
|
|
12599
12653
|
try {
|
|
12600
12654
|
// wait until the network is available
|
|
12601
12655
|
await this.networkAvailableTask?.promise;
|
|
12602
|
-
this.logger(
|
|
12656
|
+
this.logger.info(`[Reconnect] Reconnecting with strategy ${WebsocketReconnectStrategy[this.reconnectStrategy]}`);
|
|
12603
12657
|
switch (this.reconnectStrategy) {
|
|
12604
12658
|
case WebsocketReconnectStrategy.UNSPECIFIED:
|
|
12605
12659
|
case WebsocketReconnectStrategy.DISCONNECT:
|
|
12606
|
-
this.logger(
|
|
12660
|
+
this.logger.debug(`[Reconnect] No-op strategy ${currentStrategy}`);
|
|
12607
12661
|
break;
|
|
12608
12662
|
case WebsocketReconnectStrategy.FAST:
|
|
12609
12663
|
await this.reconnectFast();
|
|
@@ -12622,13 +12676,13 @@ class Call {
|
|
|
12622
12676
|
}
|
|
12623
12677
|
catch (error) {
|
|
12624
12678
|
if (this.state.callingState === CallingState.OFFLINE) {
|
|
12625
|
-
this.logger(
|
|
12679
|
+
this.logger.debug(`[Reconnect] Can't reconnect while offline, stopping reconnection attempts`);
|
|
12626
12680
|
break;
|
|
12627
12681
|
// we don't need to handle the error if the call is offline
|
|
12628
12682
|
// network change event will trigger the reconnection
|
|
12629
12683
|
}
|
|
12630
12684
|
if (error instanceof ErrorFromResponse && error.unrecoverable) {
|
|
12631
|
-
this.logger(
|
|
12685
|
+
this.logger.warn(`[Reconnect] Can't reconnect due to coordinator unrecoverable error`, error);
|
|
12632
12686
|
await markAsReconnectingFailed();
|
|
12633
12687
|
return;
|
|
12634
12688
|
}
|
|
@@ -12649,12 +12703,12 @@ class Call {
|
|
|
12649
12703
|
? WebsocketReconnectStrategy.REJOIN
|
|
12650
12704
|
: WebsocketReconnectStrategy.FAST;
|
|
12651
12705
|
this.reconnectStrategy = nextStrategy;
|
|
12652
|
-
this.logger(
|
|
12706
|
+
this.logger.info(`[Reconnect] ${currentStrategy} (${this.reconnectAttempts}) failed. Attempting with ${WebsocketReconnectStrategy[nextStrategy]}`, error);
|
|
12653
12707
|
}
|
|
12654
12708
|
} while (this.state.callingState !== CallingState.JOINED &&
|
|
12655
12709
|
this.state.callingState !== CallingState.RECONNECTING_FAILED &&
|
|
12656
12710
|
this.state.callingState !== CallingState.LEFT);
|
|
12657
|
-
this.logger('
|
|
12711
|
+
this.logger.info('[Reconnect] Reconnection flow finished');
|
|
12658
12712
|
});
|
|
12659
12713
|
};
|
|
12660
12714
|
/**
|
|
@@ -12736,7 +12790,7 @@ class Call {
|
|
|
12736
12790
|
this.registerReconnectHandlers = () => {
|
|
12737
12791
|
// handles the legacy "goAway" event
|
|
12738
12792
|
const unregisterGoAway = this.on('goAway', () => {
|
|
12739
|
-
this.reconnect(WebsocketReconnectStrategy.MIGRATE, 'goAway').catch((err) => this.logger('
|
|
12793
|
+
this.reconnect(WebsocketReconnectStrategy.MIGRATE, 'goAway').catch((err) => this.logger.warn('[Reconnect] Error reconnecting', err));
|
|
12740
12794
|
});
|
|
12741
12795
|
// handles the "error" event, through which the SFU can request a reconnect
|
|
12742
12796
|
const unregisterOnError = this.on('error', (e) => {
|
|
@@ -12745,19 +12799,19 @@ class Call {
|
|
|
12745
12799
|
return;
|
|
12746
12800
|
if (strategy === WebsocketReconnectStrategy.DISCONNECT) {
|
|
12747
12801
|
this.leave({ message: 'SFU instructed to disconnect' }).catch((err) => {
|
|
12748
|
-
this.logger(
|
|
12802
|
+
this.logger.warn(`Can't leave call after disconnect request`, err);
|
|
12749
12803
|
});
|
|
12750
12804
|
}
|
|
12751
12805
|
else {
|
|
12752
12806
|
this.reconnect(strategy, error?.message || 'SFU Error').catch((err) => {
|
|
12753
|
-
this.logger('
|
|
12807
|
+
this.logger.warn('[Reconnect] Error reconnecting', err);
|
|
12754
12808
|
});
|
|
12755
12809
|
}
|
|
12756
12810
|
});
|
|
12757
12811
|
const unregisterNetworkChanged = this.streamClient.on('network.changed', (e) => {
|
|
12758
12812
|
this.tracer.trace('network.changed', e);
|
|
12759
12813
|
if (!e.online) {
|
|
12760
|
-
this.logger('
|
|
12814
|
+
this.logger.debug('[Reconnect] Going offline');
|
|
12761
12815
|
if (!this.hasJoinedOnce)
|
|
12762
12816
|
return;
|
|
12763
12817
|
this.lastOfflineTimestamp = Date.now();
|
|
@@ -12774,7 +12828,7 @@ class Call {
|
|
|
12774
12828
|
}
|
|
12775
12829
|
}
|
|
12776
12830
|
this.reconnect(strategy, 'Going online').catch((err) => {
|
|
12777
|
-
this.logger('
|
|
12831
|
+
this.logger.warn('[Reconnect] Error reconnecting after going online', err);
|
|
12778
12832
|
});
|
|
12779
12833
|
});
|
|
12780
12834
|
this.networkAvailableTask = networkAvailableTask;
|
|
@@ -12782,7 +12836,7 @@ class Call {
|
|
|
12782
12836
|
this.state.setCallingState(CallingState.OFFLINE);
|
|
12783
12837
|
}
|
|
12784
12838
|
else {
|
|
12785
|
-
this.logger('
|
|
12839
|
+
this.logger.debug('[Reconnect] Going online');
|
|
12786
12840
|
this.sfuClient?.close(StreamSfuClient.DISPOSE_OLD_SOCKET, 'Closing WS to reconnect after going online');
|
|
12787
12841
|
// we went online, release the previous waiters and reset the state
|
|
12788
12842
|
this.networkAvailableTask?.resolve();
|
|
@@ -12944,10 +12998,10 @@ class Call {
|
|
|
12944
12998
|
* @param options the options to use.
|
|
12945
12999
|
*/
|
|
12946
13000
|
this.updatePublishOptions = (options) => {
|
|
12947
|
-
this.logger('
|
|
13001
|
+
this.logger.warn('[call.updatePublishOptions]: You are manually overriding the publish options for this call. ' +
|
|
12948
13002
|
'This is not recommended, and it can cause call stability/compatibility issues. Use with caution.');
|
|
12949
13003
|
if (this.state.callingState === CallingState.JOINED) {
|
|
12950
|
-
this.logger('
|
|
13004
|
+
this.logger.warn('Updating publish options after joining the call does not have an effect');
|
|
12951
13005
|
}
|
|
12952
13006
|
this.clientPublishOptions = { ...this.clientPublishOptions, ...options };
|
|
12953
13007
|
};
|
|
@@ -12958,7 +13012,7 @@ class Call {
|
|
|
12958
13012
|
*/
|
|
12959
13013
|
this.notifyNoiseCancellationStarting = async () => {
|
|
12960
13014
|
return this.sfuClient?.startNoiseCancellation().catch((err) => {
|
|
12961
|
-
this.logger('
|
|
13015
|
+
this.logger.warn('Failed to notify start of noise cancellation', err);
|
|
12962
13016
|
});
|
|
12963
13017
|
};
|
|
12964
13018
|
/**
|
|
@@ -12968,7 +13022,7 @@ class Call {
|
|
|
12968
13022
|
*/
|
|
12969
13023
|
this.notifyNoiseCancellationStopped = async () => {
|
|
12970
13024
|
return this.sfuClient?.stopNoiseCancellation().catch((err) => {
|
|
12971
|
-
this.logger('
|
|
13025
|
+
this.logger.warn('Failed to notify stop of noise cancellation', err);
|
|
12972
13026
|
});
|
|
12973
13027
|
};
|
|
12974
13028
|
/**
|
|
@@ -13402,7 +13456,7 @@ class Call {
|
|
|
13402
13456
|
reason: 'timeout',
|
|
13403
13457
|
message: `ringing timeout - ${this.isCreatedByMe ? 'no one accepted' : `user didn't interact with incoming call screen`}`,
|
|
13404
13458
|
}).catch((err) => {
|
|
13405
|
-
this.logger('
|
|
13459
|
+
this.logger.error('Failed to drop call', err);
|
|
13406
13460
|
});
|
|
13407
13461
|
}, timeoutInMs);
|
|
13408
13462
|
};
|
|
@@ -13514,10 +13568,10 @@ class Call {
|
|
|
13514
13568
|
*/
|
|
13515
13569
|
this.applyDeviceConfig = async (settings, publish) => {
|
|
13516
13570
|
await this.camera.apply(settings.video, publish).catch((err) => {
|
|
13517
|
-
this.logger('
|
|
13571
|
+
this.logger.warn('Camera init failed', err);
|
|
13518
13572
|
});
|
|
13519
13573
|
await this.microphone.apply(settings.audio, publish).catch((err) => {
|
|
13520
|
-
this.logger('
|
|
13574
|
+
this.logger.warn('Mic init failed', err);
|
|
13521
13575
|
});
|
|
13522
13576
|
};
|
|
13523
13577
|
/**
|
|
@@ -13669,7 +13723,7 @@ class Call {
|
|
|
13669
13723
|
this.streamClient = streamClient;
|
|
13670
13724
|
this.clientStore = clientStore;
|
|
13671
13725
|
this.streamClientBasePath = `/call/${this.type}/${this.id}`;
|
|
13672
|
-
this.logger = getLogger(
|
|
13726
|
+
this.logger = videoLoggerSystem.getLogger('Call');
|
|
13673
13727
|
const callTypeConfig = CallTypes.get(type);
|
|
13674
13728
|
const participantSorter = sortParticipantsBy || callTypeConfig.options.sortParticipantsBy;
|
|
13675
13729
|
if (participantSorter) {
|
|
@@ -13756,7 +13810,7 @@ const APIErrorCodes = {
|
|
|
13756
13810
|
class StableWSConnection {
|
|
13757
13811
|
constructor(client) {
|
|
13758
13812
|
this._log = (msg, extra = {}, level = 'info') => {
|
|
13759
|
-
this.client.logger(
|
|
13813
|
+
this.client.logger[level](`connection:${msg}`, extra);
|
|
13760
13814
|
};
|
|
13761
13815
|
this.setClient = (client) => {
|
|
13762
13816
|
this.client = client;
|
|
@@ -13802,12 +13856,12 @@ class StableWSConnection {
|
|
|
13802
13856
|
return;
|
|
13803
13857
|
const user = this.client.user;
|
|
13804
13858
|
if (!user) {
|
|
13805
|
-
this.client.logger(
|
|
13859
|
+
this.client.logger.error(`User not set, can't connect to WS`);
|
|
13806
13860
|
return;
|
|
13807
13861
|
}
|
|
13808
13862
|
const token = this.client._getToken();
|
|
13809
13863
|
if (!token) {
|
|
13810
|
-
this.client.logger(
|
|
13864
|
+
this.client.logger.error(`Token not set, can't connect authenticate`);
|
|
13811
13865
|
return;
|
|
13812
13866
|
}
|
|
13813
13867
|
const authMessage = JSON.stringify({
|
|
@@ -14444,7 +14498,7 @@ class TokenManager {
|
|
|
14444
14498
|
}
|
|
14445
14499
|
|
|
14446
14500
|
const getLocationHint = async (hintUrl = `https://hint.stream-io-video.com/`, timeout = 2000, maxAttempts = 3) => {
|
|
14447
|
-
const logger = getLogger(
|
|
14501
|
+
const logger = videoLoggerSystem.getLogger('location-hint');
|
|
14448
14502
|
let attempt = 0;
|
|
14449
14503
|
let locationHint = 'ERR';
|
|
14450
14504
|
do {
|
|
@@ -14456,11 +14510,11 @@ const getLocationHint = async (hintUrl = `https://hint.stream-io-video.com/`, ti
|
|
|
14456
14510
|
signal: abortController.signal,
|
|
14457
14511
|
});
|
|
14458
14512
|
const awsPop = response.headers.get('x-amz-cf-pop') || 'ERR';
|
|
14459
|
-
logger(
|
|
14513
|
+
logger.debug(`Location header: ${awsPop}`);
|
|
14460
14514
|
locationHint = awsPop.substring(0, 3); // AMS1-P2 -> AMS
|
|
14461
14515
|
}
|
|
14462
14516
|
catch (e) {
|
|
14463
|
-
logger(
|
|
14517
|
+
logger.warn(`Failed to get location hint from ${hintUrl}`, e);
|
|
14464
14518
|
locationHint = 'ERR';
|
|
14465
14519
|
}
|
|
14466
14520
|
finally {
|
|
@@ -14521,14 +14575,14 @@ class StreamClient {
|
|
|
14521
14575
|
* If the user id remains the same we don't throw error
|
|
14522
14576
|
*/
|
|
14523
14577
|
if (this.userID === user.id && this.connectUserTask) {
|
|
14524
|
-
this.logger('
|
|
14578
|
+
this.logger.warn('Consecutive calls to connectUser is detected, ideally you should only call this function once in your app.');
|
|
14525
14579
|
return this.connectUserTask;
|
|
14526
14580
|
}
|
|
14527
14581
|
if (this.userID) {
|
|
14528
14582
|
throw new Error('Use client.disconnect() before trying to connect as a different user. connectUser was called twice.');
|
|
14529
14583
|
}
|
|
14530
14584
|
if ((this.secret || this.node) && !this.options.allowServerSideConnect) {
|
|
14531
|
-
this.logger('
|
|
14585
|
+
this.logger.warn('Please do not use connectUser server side. Use our @stream-io/node-sdk instead: https://getstream.io/video/docs/api/');
|
|
14532
14586
|
}
|
|
14533
14587
|
// we generate the client id client side
|
|
14534
14588
|
this.userID = user.id;
|
|
@@ -14586,11 +14640,11 @@ class StreamClient {
|
|
|
14586
14640
|
}
|
|
14587
14641
|
const wsPromise = this.wsPromiseSafe?.();
|
|
14588
14642
|
if (this.wsConnection?.isConnecting && wsPromise) {
|
|
14589
|
-
this.logger('
|
|
14643
|
+
this.logger.info('client:openConnection() - connection already in progress');
|
|
14590
14644
|
return await wsPromise;
|
|
14591
14645
|
}
|
|
14592
14646
|
if (this.wsConnection?.isHealthy && this._hasConnectionID()) {
|
|
14593
|
-
this.logger('
|
|
14647
|
+
this.logger.info('client:openConnection() - openConnection called twice, healthy connection already exists');
|
|
14594
14648
|
return;
|
|
14595
14649
|
}
|
|
14596
14650
|
this._setupConnectionIdPromise();
|
|
@@ -14606,7 +14660,7 @@ class StreamClient {
|
|
|
14606
14660
|
* https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent
|
|
14607
14661
|
*/
|
|
14608
14662
|
this.disconnectUser = async (timeout) => {
|
|
14609
|
-
this.logger('
|
|
14663
|
+
this.logger.info('client:disconnect() - Disconnecting the client');
|
|
14610
14664
|
// remove the user specific fields
|
|
14611
14665
|
delete this.user;
|
|
14612
14666
|
delete this._user;
|
|
@@ -14653,7 +14707,7 @@ class StreamClient {
|
|
|
14653
14707
|
if (!this.listeners[eventName]) {
|
|
14654
14708
|
this.listeners[eventName] = [];
|
|
14655
14709
|
}
|
|
14656
|
-
this.logger(
|
|
14710
|
+
this.logger.debug(`Adding listener for ${eventName} event`);
|
|
14657
14711
|
this.listeners[eventName]?.push(callback);
|
|
14658
14712
|
return () => {
|
|
14659
14713
|
this.off(eventName, callback);
|
|
@@ -14666,7 +14720,7 @@ class StreamClient {
|
|
|
14666
14720
|
if (!this.listeners[eventName]) {
|
|
14667
14721
|
this.listeners[eventName] = [];
|
|
14668
14722
|
}
|
|
14669
|
-
this.logger(
|
|
14723
|
+
this.logger.debug(`Removing listener for ${eventName} event`);
|
|
14670
14724
|
this.listeners[eventName] = this.listeners[eventName]?.filter((value) => value !== callback);
|
|
14671
14725
|
};
|
|
14672
14726
|
/**
|
|
@@ -14680,17 +14734,17 @@ class StreamClient {
|
|
|
14680
14734
|
}));
|
|
14681
14735
|
};
|
|
14682
14736
|
this._logApiRequest = (type, url, data, config) => {
|
|
14683
|
-
if (getLogLevel() !== 'trace')
|
|
14737
|
+
if (this.logger.getLogLevel() !== 'trace')
|
|
14684
14738
|
return;
|
|
14685
|
-
this.logger(
|
|
14739
|
+
this.logger.trace(`client: ${type} - Request - ${url}`, {
|
|
14686
14740
|
payload: data,
|
|
14687
14741
|
config,
|
|
14688
14742
|
});
|
|
14689
14743
|
};
|
|
14690
14744
|
this._logApiResponse = (type, url, response) => {
|
|
14691
|
-
if (getLogLevel() !== 'trace')
|
|
14745
|
+
if (this.logger.getLogLevel() !== 'trace')
|
|
14692
14746
|
return;
|
|
14693
|
-
this.logger(
|
|
14747
|
+
this.logger.trace(`client:${type} - Response - url: ${url} > status ${response.status}`, {
|
|
14694
14748
|
response,
|
|
14695
14749
|
});
|
|
14696
14750
|
};
|
|
@@ -14747,13 +14801,13 @@ class StreamClient {
|
|
|
14747
14801
|
this.consecutiveFailures += 1;
|
|
14748
14802
|
const { response } = e;
|
|
14749
14803
|
if (!response || !isErrorResponse(response)) {
|
|
14750
|
-
this.logger(
|
|
14804
|
+
this.logger.error(`client:${type} url: ${url}`, e);
|
|
14751
14805
|
throw e;
|
|
14752
14806
|
}
|
|
14753
14807
|
const { data: responseData, status } = response;
|
|
14754
14808
|
const isTokenExpired = responseData.code === KnownCodes.TOKEN_EXPIRED;
|
|
14755
14809
|
if (isTokenExpired && !this.tokenManager.isStatic()) {
|
|
14756
|
-
this.logger(
|
|
14810
|
+
this.logger.warn(`client:${type}: url: ${url}`, response);
|
|
14757
14811
|
if (this.consecutiveFailures > 1) {
|
|
14758
14812
|
await sleep(retryInterval(this.consecutiveFailures));
|
|
14759
14813
|
}
|
|
@@ -14762,7 +14816,7 @@ class StreamClient {
|
|
|
14762
14816
|
return await this.doAxiosRequest(type, url, data, options);
|
|
14763
14817
|
}
|
|
14764
14818
|
else {
|
|
14765
|
-
this.logger(
|
|
14819
|
+
this.logger.error(`client:${type} url: ${url}`, response);
|
|
14766
14820
|
throw new ErrorFromResponse({
|
|
14767
14821
|
message: `Stream error code ${responseData.code}: ${responseData.message}`,
|
|
14768
14822
|
code: responseData.code ?? null,
|
|
@@ -14793,7 +14847,7 @@ class StreamClient {
|
|
|
14793
14847
|
});
|
|
14794
14848
|
};
|
|
14795
14849
|
this.dispatchEvent = (event) => {
|
|
14796
|
-
this.logger(
|
|
14850
|
+
this.logger.debug(`Dispatching event: ${event.type}`, event);
|
|
14797
14851
|
if (!this.listeners)
|
|
14798
14852
|
return;
|
|
14799
14853
|
// call generic listeners
|
|
@@ -14818,13 +14872,13 @@ class StreamClient {
|
|
|
14818
14872
|
throw Error('clientID is not set');
|
|
14819
14873
|
// The StableWSConnection handles all the reconnection logic.
|
|
14820
14874
|
this.wsConnection = new StableWSConnection(this);
|
|
14821
|
-
this.logger('
|
|
14875
|
+
this.logger.info('StreamClient.connect: this.wsConnection.connect()');
|
|
14822
14876
|
return await this.wsConnection.connect(this.defaultWSTimeout);
|
|
14823
14877
|
};
|
|
14824
14878
|
this.getUserAgent = () => {
|
|
14825
14879
|
if (!this.cachedUserAgent) {
|
|
14826
14880
|
const { clientAppIdentifier = {} } = this.options;
|
|
14827
|
-
const { sdkName = 'js', sdkVersion = "1.
|
|
14881
|
+
const { sdkName = 'js', sdkVersion = "1.36.1", ...extras } = clientAppIdentifier;
|
|
14828
14882
|
this.cachedUserAgent = [
|
|
14829
14883
|
`stream-video-${sdkName}-v${sdkVersion}`,
|
|
14830
14884
|
...Object.entries(extras).map(([key, value]) => `${key}=${value}`),
|
|
@@ -14875,11 +14929,11 @@ class StreamClient {
|
|
|
14875
14929
|
};
|
|
14876
14930
|
this.updateNetworkConnectionStatus = (event) => {
|
|
14877
14931
|
if (event.type === 'offline') {
|
|
14878
|
-
this.logger('
|
|
14932
|
+
this.logger.debug('device went offline');
|
|
14879
14933
|
this.dispatchEvent({ type: 'network.changed', online: false });
|
|
14880
14934
|
}
|
|
14881
14935
|
else if (event.type === 'online') {
|
|
14882
|
-
this.logger('
|
|
14936
|
+
this.logger.debug('device went online');
|
|
14883
14937
|
this.dispatchEvent({ type: 'network.changed', online: true });
|
|
14884
14938
|
}
|
|
14885
14939
|
};
|
|
@@ -14927,9 +14981,7 @@ class StreamClient {
|
|
|
14927
14981
|
this.tokenManager = new TokenManager(this.secret);
|
|
14928
14982
|
this.consecutiveFailures = 0;
|
|
14929
14983
|
this.defaultWSTimeout = this.options.defaultWsTimeout ?? 15000;
|
|
14930
|
-
this.logger =
|
|
14931
|
-
? inputOptions.logger
|
|
14932
|
-
: () => null;
|
|
14984
|
+
this.logger = videoLoggerSystem.getLogger('coordinator');
|
|
14933
14985
|
}
|
|
14934
14986
|
get connectionIdPromise() {
|
|
14935
14987
|
return this.connectionIdPromiseSafe?.();
|
|
@@ -14974,12 +15026,10 @@ const getClientAppIdentifier = (options) => {
|
|
|
14974
15026
|
*/
|
|
14975
15027
|
const createCoordinatorClient = (apiKey, options) => {
|
|
14976
15028
|
const clientAppIdentifier = getClientAppIdentifier(options);
|
|
14977
|
-
const coordinatorLogger = getLogger(['coordinator']);
|
|
14978
15029
|
return new StreamClient(apiKey, {
|
|
14979
15030
|
persistUserOnConnectionFailure: true,
|
|
14980
15031
|
...options,
|
|
14981
15032
|
clientAppIdentifier,
|
|
14982
|
-
logger: coordinatorLogger,
|
|
14983
15033
|
});
|
|
14984
15034
|
};
|
|
14985
15035
|
/**
|
|
@@ -15017,7 +15067,7 @@ class StreamVideoClient {
|
|
|
15017
15067
|
this.registerClientInstance = (apiKey, user) => {
|
|
15018
15068
|
const instanceKey = getInstanceKey(apiKey, user);
|
|
15019
15069
|
if (StreamVideoClient._instances.has(instanceKey)) {
|
|
15020
|
-
this.logger(
|
|
15070
|
+
this.logger.warn(`A StreamVideoClient already exists for ${user.id}; Prefer using getOrCreateInstance method`);
|
|
15021
15071
|
}
|
|
15022
15072
|
StreamVideoClient._instances.set(instanceKey, this);
|
|
15023
15073
|
};
|
|
@@ -15032,13 +15082,13 @@ class StreamVideoClient {
|
|
|
15032
15082
|
.map((call) => call.cid);
|
|
15033
15083
|
if (callsToReWatch.length <= 0)
|
|
15034
15084
|
return;
|
|
15035
|
-
this.logger(
|
|
15085
|
+
this.logger.info(`Rewatching calls ${callsToReWatch.join(', ')}`);
|
|
15036
15086
|
this.queryCalls({
|
|
15037
15087
|
watch: true,
|
|
15038
15088
|
filter_conditions: { cid: { $in: callsToReWatch } },
|
|
15039
15089
|
sort: [{ field: 'cid', direction: 1 }],
|
|
15040
15090
|
}).catch((err) => {
|
|
15041
|
-
this.logger('
|
|
15091
|
+
this.logger.error('Failed to re-watch calls', err);
|
|
15042
15092
|
});
|
|
15043
15093
|
}));
|
|
15044
15094
|
this.effectsRegistered = true;
|
|
@@ -15049,7 +15099,7 @@ class StreamVideoClient {
|
|
|
15049
15099
|
*/
|
|
15050
15100
|
this.initCallFromEvent = async (e) => {
|
|
15051
15101
|
if (this.state.connectedUser?.id === e.call.created_by.id) {
|
|
15052
|
-
this.logger(
|
|
15102
|
+
this.logger.debug(`Ignoring ${e.type} event sent by the current user`);
|
|
15053
15103
|
return;
|
|
15054
15104
|
}
|
|
15055
15105
|
try {
|
|
@@ -15060,7 +15110,7 @@ class StreamVideoClient {
|
|
|
15060
15110
|
if (call) {
|
|
15061
15111
|
if (ringing) {
|
|
15062
15112
|
if (this.shouldRejectCall(call.cid)) {
|
|
15063
|
-
this.logger(
|
|
15113
|
+
this.logger.info(`Leaving call with busy reject reason ${call.cid} because user is busy`);
|
|
15064
15114
|
// remove the instance from the state store
|
|
15065
15115
|
await call.leave();
|
|
15066
15116
|
// explicitly reject the call with busy reason as calling state was not ringing before and leave would not call it therefore
|
|
@@ -15085,7 +15135,7 @@ class StreamVideoClient {
|
|
|
15085
15135
|
});
|
|
15086
15136
|
if (ringing) {
|
|
15087
15137
|
if (this.shouldRejectCall(call.cid)) {
|
|
15088
|
-
this.logger(
|
|
15138
|
+
this.logger.info(`Rejecting call ${call.cid} because user is busy`);
|
|
15089
15139
|
// call is not in the state store yet, so just reject api is enough
|
|
15090
15140
|
await call.reject('busy');
|
|
15091
15141
|
}
|
|
@@ -15097,12 +15147,12 @@ class StreamVideoClient {
|
|
|
15097
15147
|
else {
|
|
15098
15148
|
call.state.updateFromCallResponse(e.call);
|
|
15099
15149
|
this.writeableStateStore.registerCall(call);
|
|
15100
|
-
this.logger(
|
|
15150
|
+
this.logger.info(`New call created and registered: ${call.cid}`);
|
|
15101
15151
|
}
|
|
15102
15152
|
});
|
|
15103
15153
|
}
|
|
15104
15154
|
catch (err) {
|
|
15105
|
-
this.logger(
|
|
15155
|
+
this.logger.error(`Failed to init call from event ${e.type}`, err);
|
|
15106
15156
|
}
|
|
15107
15157
|
};
|
|
15108
15158
|
/**
|
|
@@ -15126,13 +15176,13 @@ class StreamVideoClient {
|
|
|
15126
15176
|
const errorQueue = [];
|
|
15127
15177
|
for (let attempt = 0; attempt < maxConnectUserRetries; attempt++) {
|
|
15128
15178
|
try {
|
|
15129
|
-
this.logger(
|
|
15179
|
+
this.logger.trace(`Connecting user (${attempt})`, user);
|
|
15130
15180
|
return user.type === 'guest'
|
|
15131
15181
|
? await client.connectGuestUser(user)
|
|
15132
15182
|
: await client.connectUser(user, tokenOrProvider);
|
|
15133
15183
|
}
|
|
15134
15184
|
catch (err) {
|
|
15135
|
-
this.logger(
|
|
15185
|
+
this.logger.warn(`Failed to connect a user (${attempt})`, err);
|
|
15136
15186
|
errorQueue.push(err);
|
|
15137
15187
|
if (attempt === maxConnectUserRetries - 1) {
|
|
15138
15188
|
onConnectUserError?.(err, errorQueue);
|
|
@@ -15377,8 +15427,11 @@ class StreamVideoClient {
|
|
|
15377
15427
|
if (clientOptions?.enableTimerWorker)
|
|
15378
15428
|
enableTimerWorker();
|
|
15379
15429
|
const rootLogger = clientOptions?.logger || logToConsole;
|
|
15380
|
-
|
|
15381
|
-
|
|
15430
|
+
videoLoggerSystem.configureLoggers({
|
|
15431
|
+
default: { sink: rootLogger, level: clientOptions?.logLevel || 'warn' },
|
|
15432
|
+
...clientOptions?.logOptions,
|
|
15433
|
+
});
|
|
15434
|
+
this.logger = videoLoggerSystem.getLogger('client');
|
|
15382
15435
|
this.rejectCallWhenBusy = clientOptions?.rejectCallWhenBusy ?? false;
|
|
15383
15436
|
this.streamClient = createCoordinatorClient(apiKey, clientOptions);
|
|
15384
15437
|
this.writeableStateStore = new StreamVideoWriteableStateStore();
|
|
@@ -15391,7 +15444,7 @@ class StreamVideoClient {
|
|
|
15391
15444
|
this.registerClientInstance(apiKey, user);
|
|
15392
15445
|
const tokenOrProvider = createTokenOrProvider(apiKeyOrArgs);
|
|
15393
15446
|
this.connectUser(user, tokenOrProvider).catch((err) => {
|
|
15394
|
-
this.logger('
|
|
15447
|
+
this.logger.error('Failed to connect', err);
|
|
15395
15448
|
});
|
|
15396
15449
|
}
|
|
15397
15450
|
}
|
|
@@ -15421,5 +15474,5 @@ class StreamVideoClient {
|
|
|
15421
15474
|
}
|
|
15422
15475
|
StreamVideoClient._instances = new Map();
|
|
15423
15476
|
|
|
15424
|
-
export { AudioSettingsRequestDefaultDeviceEnum, AudioSettingsResponseDefaultDeviceEnum, browsers as Browsers, Call, CallState, CallType, CallTypes, CallingState, CameraManager, CameraManagerState, CreateDeviceRequestPushProviderEnum, DebounceType, DeviceManager, DeviceManagerState, DynascaleManager, ErrorFromResponse, FrameRecordingSettingsRequestModeEnum, FrameRecordingSettingsRequestQualityEnum, FrameRecordingSettingsResponseModeEnum, IngressAudioEncodingOptionsRequestChannelsEnum, IngressSourceRequestFpsEnum, IngressVideoLayerRequestCodecEnum, LayoutSettingsRequestNameEnum, MicrophoneManager, MicrophoneManagerState, NoiseCancellationSettingsModeEnum, OwnCapability, RNSpeechDetector, RTMPBroadcastRequestQualityEnum, RTMPSettingsRequestQualityEnum, RecordSettingsRequestModeEnum, RecordSettingsRequestQualityEnum, rxUtils as RxUtils, ScreenShareManager, ScreenShareState, events as SfuEvents, models as SfuModels, SpeakerManager, SpeakerState, StartClosedCaptionsRequestLanguageEnum, StartTranscriptionRequestLanguageEnum, StreamSfuClient, StreamVideoClient, StreamVideoReadOnlyStateStore, StreamVideoWriteableStateStore, TranscriptionSettingsRequestClosedCaptionModeEnum, TranscriptionSettingsRequestLanguageEnum, TranscriptionSettingsRequestModeEnum, TranscriptionSettingsResponseClosedCaptionModeEnum, TranscriptionSettingsResponseLanguageEnum, TranscriptionSettingsResponseModeEnum, VideoSettingsRequestCameraFacingEnum, VideoSettingsResponseCameraFacingEnum, ViewportTracker, VisibilityState, checkIfAudioOutputChangeSupported, combineComparators, conditional, createSoundDetector, defaultSortPreset, descending, deviceIds$, disposeOfMediaStream, dominantSpeaker, getAudioBrowserPermission, getAudioDevices, getAudioOutputDevices, getAudioStream, getClientDetails, getDeviceState,
|
|
15477
|
+
export { AudioSettingsRequestDefaultDeviceEnum, AudioSettingsResponseDefaultDeviceEnum, browsers as Browsers, Call, CallState, CallType, CallTypes, CallingState, CameraManager, CameraManagerState, CreateDeviceRequestPushProviderEnum, DebounceType, DeviceManager, DeviceManagerState, DynascaleManager, ErrorFromResponse, FrameRecordingSettingsRequestModeEnum, FrameRecordingSettingsRequestQualityEnum, FrameRecordingSettingsResponseModeEnum, IngressAudioEncodingOptionsRequestChannelsEnum, IngressSourceRequestFpsEnum, IngressVideoLayerRequestCodecEnum, LayoutSettingsRequestNameEnum, MicrophoneManager, MicrophoneManagerState, NoiseCancellationSettingsModeEnum, OwnCapability, RNSpeechDetector, RTMPBroadcastRequestQualityEnum, RTMPSettingsRequestQualityEnum, RecordSettingsRequestModeEnum, RecordSettingsRequestQualityEnum, rxUtils as RxUtils, ScreenShareManager, ScreenShareState, events as SfuEvents, models as SfuModels, SpeakerManager, SpeakerState, StartClosedCaptionsRequestLanguageEnum, StartTranscriptionRequestLanguageEnum, StreamSfuClient, StreamVideoClient, StreamVideoReadOnlyStateStore, StreamVideoWriteableStateStore, TranscriptionSettingsRequestClosedCaptionModeEnum, TranscriptionSettingsRequestLanguageEnum, TranscriptionSettingsRequestModeEnum, TranscriptionSettingsResponseClosedCaptionModeEnum, TranscriptionSettingsResponseLanguageEnum, TranscriptionSettingsResponseModeEnum, VideoSettingsRequestCameraFacingEnum, VideoSettingsResponseCameraFacingEnum, ViewportTracker, VisibilityState, checkIfAudioOutputChangeSupported, combineComparators, conditional, createSoundDetector, defaultSortPreset, descending, deviceIds$, disposeOfMediaStream, dominantSpeaker, getAudioBrowserPermission, getAudioDevices, getAudioOutputDevices, getAudioStream, getClientDetails, getDeviceState, getScreenShareStream, getSdkInfo, getVideoBrowserPermission, getVideoDevices, getVideoStream, getWebRTCInfo, hasAudio, hasPausedTrack, hasScreenShare, hasScreenShareAudio, hasVideo, isPinned, livestreamOrAudioRoomSortPreset, logToConsole, name, noopComparator, paginatedLayoutSortPreset, pinned, publishingAudio, publishingVideo, reactionType, resolveDeviceId, role, screenSharing, setDeviceInfo, setOSInfo, setPowerState, setSdkInfo, setThermalState, setWebRTCInfo, speakerLayoutSortPreset, speaking, videoLoggerSystem, withParticipantSource };
|
|
15425
15478
|
//# sourceMappingURL=index.es.js.map
|