@stream-io/video-client 1.35.0 → 1.36.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/dist/index.browser.es.js +217 -236
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +239 -240
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +217 -236
- 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 +3 -2
- package/dist/src/store/CallState.d.ts +1 -1
- package/package.json +4 -3
- package/src/Call.ts +36 -48
- 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 +19 -22
- package/src/rtc/Dispatcher.ts +4 -4
- package/src/rtc/IceTrickleBuffer.ts +5 -5
- package/src/rtc/Publisher.ts +8 -8
- package/src/rtc/Subscriber.ts +12 -16
- package/src/rtc/signal.ts +7 -7
- 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/devices/devices.ts
CHANGED
|
@@ -9,12 +9,12 @@ import {
|
|
|
9
9
|
startWith,
|
|
10
10
|
tap,
|
|
11
11
|
} from 'rxjs';
|
|
12
|
-
import { getLogger } from '../logger';
|
|
13
12
|
import { BrowserPermission } from './BrowserPermission';
|
|
14
13
|
import { lazy } from '../helpers/lazy';
|
|
15
14
|
import { isFirefox } from '../helpers/browsers';
|
|
16
15
|
import { dumpStream, Tracer } from '../stats';
|
|
17
16
|
import { getCurrentValue } from '../store/rxUtils';
|
|
17
|
+
import { videoLoggerSystem } from '../logger';
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
20
|
* Returns an Observable that emits the list of available devices
|
|
@@ -266,15 +266,16 @@ export const getAudioStream = async (
|
|
|
266
266
|
if (isNotFoundOrOverconstrainedError(error) && trackConstraints?.deviceId) {
|
|
267
267
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
268
268
|
const { deviceId, ...relaxedConstraints } = trackConstraints;
|
|
269
|
-
|
|
270
|
-
'
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
269
|
+
videoLoggerSystem
|
|
270
|
+
.getLogger('devices')
|
|
271
|
+
.warn(
|
|
272
|
+
'Failed to get audio stream, will try again with relaxed constraints',
|
|
273
|
+
{ error, constraints, relaxedConstraints },
|
|
274
|
+
);
|
|
274
275
|
return getAudioStream(relaxedConstraints);
|
|
275
276
|
}
|
|
276
277
|
|
|
277
|
-
getLogger(
|
|
278
|
+
videoLoggerSystem.getLogger('devices').error('Failed to get audio stream', {
|
|
278
279
|
error,
|
|
279
280
|
constraints,
|
|
280
281
|
});
|
|
@@ -310,15 +311,16 @@ export const getVideoStream = async (
|
|
|
310
311
|
if (isNotFoundOrOverconstrainedError(error) && trackConstraints?.deviceId) {
|
|
311
312
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
312
313
|
const { deviceId, ...relaxedConstraints } = trackConstraints;
|
|
313
|
-
|
|
314
|
-
'
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
314
|
+
videoLoggerSystem
|
|
315
|
+
.getLogger('devices')
|
|
316
|
+
.warn(
|
|
317
|
+
'Failed to get video stream, will try again with relaxed constraints',
|
|
318
|
+
{ error, constraints, relaxedConstraints },
|
|
319
|
+
);
|
|
318
320
|
return getVideoStream(relaxedConstraints);
|
|
319
321
|
}
|
|
320
322
|
|
|
321
|
-
getLogger(
|
|
323
|
+
videoLoggerSystem.getLogger('devices').error('Failed to get video stream', {
|
|
322
324
|
error,
|
|
323
325
|
constraints,
|
|
324
326
|
});
|
|
@@ -373,7 +375,9 @@ export const getScreenShareStream = async (
|
|
|
373
375
|
return stream;
|
|
374
376
|
} catch (e) {
|
|
375
377
|
tracer?.trace(`${tag}OnFailure`, (e as Error).name);
|
|
376
|
-
|
|
378
|
+
videoLoggerSystem
|
|
379
|
+
.getLogger('devices')
|
|
380
|
+
.error('Failed to get screen share stream', e);
|
|
377
381
|
throw e;
|
|
378
382
|
}
|
|
379
383
|
};
|
package/src/events/call.ts
CHANGED
|
@@ -38,8 +38,7 @@ export const watchCallRejected = (call: Call) => {
|
|
|
38
38
|
const { session: callSession } = eventCall;
|
|
39
39
|
|
|
40
40
|
if (!callSession) {
|
|
41
|
-
call.logger(
|
|
42
|
-
'warn',
|
|
41
|
+
call.logger.warn(
|
|
43
42
|
'No call session provided. Ignoring call.rejected event.',
|
|
44
43
|
event,
|
|
45
44
|
);
|
|
@@ -49,8 +48,7 @@ export const watchCallRejected = (call: Call) => {
|
|
|
49
48
|
const rejectedBy = callSession.rejected_by;
|
|
50
49
|
const { members, callingState } = call.state;
|
|
51
50
|
if (callingState !== CallingState.RINGING) {
|
|
52
|
-
call.logger(
|
|
53
|
-
'info',
|
|
51
|
+
call.logger.info(
|
|
54
52
|
'Call is not in ringing mode (it is either accepted or rejected already). Ignoring call.rejected event.',
|
|
55
53
|
event,
|
|
56
54
|
);
|
|
@@ -61,7 +59,7 @@ export const watchCallRejected = (call: Call) => {
|
|
|
61
59
|
.filter((m) => m.user_id !== call.currentUserId)
|
|
62
60
|
.every((m) => rejectedBy[m.user_id]);
|
|
63
61
|
if (everyoneElseRejected) {
|
|
64
|
-
call.logger('
|
|
62
|
+
call.logger.info('everyone rejected, leaving the call');
|
|
65
63
|
await call.leave({
|
|
66
64
|
reject: true,
|
|
67
65
|
reason: 'cancel',
|
|
@@ -70,7 +68,7 @@ export const watchCallRejected = (call: Call) => {
|
|
|
70
68
|
}
|
|
71
69
|
} else {
|
|
72
70
|
if (rejectedBy[eventCall.created_by.id]) {
|
|
73
|
-
call.logger('
|
|
71
|
+
call.logger.info('call creator rejected, leaving call');
|
|
74
72
|
await call.leave({ message: 'ring: creator rejected' });
|
|
75
73
|
}
|
|
76
74
|
}
|
|
@@ -90,7 +88,7 @@ export const watchCallEnded = (call: Call) => {
|
|
|
90
88
|
call
|
|
91
89
|
.leave({ message: 'call.ended event received', reject: false })
|
|
92
90
|
.catch((err) => {
|
|
93
|
-
call.logger('
|
|
91
|
+
call.logger.error('Failed to leave call after call.ended ', err);
|
|
94
92
|
});
|
|
95
93
|
}
|
|
96
94
|
};
|
|
@@ -117,8 +115,7 @@ export const watchSfuCallEnded = (call: Call) => {
|
|
|
117
115
|
const reason = CallEndedReason[e.reason];
|
|
118
116
|
await call.leave({ message: `callEnded received: ${reason}` });
|
|
119
117
|
} catch (err) {
|
|
120
|
-
call.logger(
|
|
121
|
-
'error',
|
|
118
|
+
call.logger.error(
|
|
122
119
|
'Failed to leave call after being ended by the SFU',
|
|
123
120
|
err,
|
|
124
121
|
);
|
package/src/events/internal.ts
CHANGED
|
@@ -2,7 +2,6 @@ import { Dispatcher } from '../rtc';
|
|
|
2
2
|
import { Call } from '../Call';
|
|
3
3
|
import { CallState } from '../store';
|
|
4
4
|
import { StreamVideoParticipantPatches } from '../types';
|
|
5
|
-
import { getLogger } from '../logger';
|
|
6
5
|
import { pushToIfMissing, removeFromIfPresent } from '../helpers/array';
|
|
7
6
|
import type {
|
|
8
7
|
InboundStateNotification,
|
|
@@ -13,6 +12,7 @@ import {
|
|
|
13
12
|
WebsocketReconnectStrategy,
|
|
14
13
|
} from '../gen/video/sfu/models/models';
|
|
15
14
|
import { OwnCapability } from '../gen/coordinator';
|
|
15
|
+
import { videoLoggerSystem } from '../logger';
|
|
16
16
|
|
|
17
17
|
export const watchConnectionQualityChanged = (
|
|
18
18
|
dispatcher: Dispatcher,
|
|
@@ -60,7 +60,7 @@ export const watchLiveEnded = (dispatcher: Dispatcher, call: Call) => {
|
|
|
60
60
|
call.state.setBackstage(true);
|
|
61
61
|
if (!call.permissionsContext.hasPermission(OwnCapability.JOIN_BACKSTAGE)) {
|
|
62
62
|
call.leave({ message: 'live ended' }).catch((err) => {
|
|
63
|
-
call.logger('
|
|
63
|
+
call.logger.error('Failed to leave call after live ended', err);
|
|
64
64
|
});
|
|
65
65
|
}
|
|
66
66
|
});
|
|
@@ -72,9 +72,9 @@ export const watchLiveEnded = (dispatcher: Dispatcher, call: Call) => {
|
|
|
72
72
|
export const watchSfuErrorReports = (dispatcher: Dispatcher) => {
|
|
73
73
|
return dispatcher.on('error', (e) => {
|
|
74
74
|
if (!e.error) return;
|
|
75
|
-
const logger = getLogger(
|
|
75
|
+
const logger = videoLoggerSystem.getLogger('SfuClient');
|
|
76
76
|
const { error, reconnectStrategy } = e;
|
|
77
|
-
logger('
|
|
77
|
+
logger.error('SFU reported error', {
|
|
78
78
|
code: ErrorCode[error.code],
|
|
79
79
|
reconnectStrategy: WebsocketReconnectStrategy[reconnectStrategy],
|
|
80
80
|
message: error.message,
|
package/src/events/mutes.ts
CHANGED
|
@@ -18,8 +18,7 @@ export const handleRemoteSoftMute = (call: Call) => {
|
|
|
18
18
|
sessionId === localParticipant?.sessionId
|
|
19
19
|
) {
|
|
20
20
|
const logger = call.logger;
|
|
21
|
-
logger(
|
|
22
|
-
'info',
|
|
21
|
+
logger.info(
|
|
23
22
|
`Local participant's ${TrackType[type]} track is muted remotely`,
|
|
24
23
|
);
|
|
25
24
|
try {
|
|
@@ -33,14 +32,10 @@ export const handleRemoteSoftMute = (call: Call) => {
|
|
|
33
32
|
) {
|
|
34
33
|
await call.screenShare.disable();
|
|
35
34
|
} else {
|
|
36
|
-
logger(
|
|
37
|
-
'warn',
|
|
38
|
-
'Unsupported track type to soft mute',
|
|
39
|
-
TrackType[type],
|
|
40
|
-
);
|
|
35
|
+
logger.warn('Unsupported track type to soft mute', TrackType[type]);
|
|
41
36
|
}
|
|
42
37
|
} catch (error) {
|
|
43
|
-
logger('
|
|
38
|
+
logger.error('Failed to stop publishing', error);
|
|
44
39
|
}
|
|
45
40
|
}
|
|
46
41
|
});
|
|
@@ -15,7 +15,6 @@ import {
|
|
|
15
15
|
takeWhile,
|
|
16
16
|
} from 'rxjs';
|
|
17
17
|
import { ViewportTracker } from './ViewportTracker';
|
|
18
|
-
import { getLogger } from '../logger';
|
|
19
18
|
import { isFirefox, isSafari } from './browsers';
|
|
20
19
|
import {
|
|
21
20
|
hasScreenShare,
|
|
@@ -27,6 +26,7 @@ import type { CallState } from '../store';
|
|
|
27
26
|
import type { StreamSfuClient } from '../StreamSfuClient';
|
|
28
27
|
import { SpeakerManager } from '../devices';
|
|
29
28
|
import { getCurrentValue, setCurrentValue } from '../store/rxUtils';
|
|
29
|
+
import { videoLoggerSystem } from '../logger';
|
|
30
30
|
|
|
31
31
|
const DEFAULT_VIEWPORT_VISIBILITY_STATE: Record<
|
|
32
32
|
VideoTrackType,
|
|
@@ -66,7 +66,7 @@ export class DynascaleManager {
|
|
|
66
66
|
*/
|
|
67
67
|
readonly viewportTracker = new ViewportTracker();
|
|
68
68
|
|
|
69
|
-
private logger = getLogger(
|
|
69
|
+
private logger = videoLoggerSystem.getLogger('DynascaleManager');
|
|
70
70
|
private callState: CallState;
|
|
71
71
|
private speaker: SpeakerManager;
|
|
72
72
|
private audioContext: AudioContext | undefined;
|
|
@@ -212,7 +212,7 @@ export class DynascaleManager {
|
|
|
212
212
|
this.sfuClient
|
|
213
213
|
?.updateSubscriptions(this.trackSubscriptions)
|
|
214
214
|
.catch((err: unknown) => {
|
|
215
|
-
this.logger(
|
|
215
|
+
this.logger.debug(`Failed to update track subscriptions`, err);
|
|
216
216
|
});
|
|
217
217
|
};
|
|
218
218
|
|
|
@@ -324,7 +324,7 @@ export class DynascaleManager {
|
|
|
324
324
|
// is not visible (e.g., has display: none).
|
|
325
325
|
// we treat this as "unsubscription" as we don't want to keep
|
|
326
326
|
// consuming bandwidth for a video that is not visible on the screen.
|
|
327
|
-
this.logger(
|
|
327
|
+
this.logger.debug(`Ignoring 0x0 dimension`, boundParticipant);
|
|
328
328
|
dimension = undefined;
|
|
329
329
|
}
|
|
330
330
|
this.callState.updateParticipantTracks(trackType, {
|
|
@@ -468,7 +468,7 @@ export class DynascaleManager {
|
|
|
468
468
|
setTimeout(() => {
|
|
469
469
|
videoElement.srcObject = source ?? null;
|
|
470
470
|
videoElement.play().catch((e) => {
|
|
471
|
-
this.logger(
|
|
471
|
+
this.logger.warn(`Failed to play stream`, e);
|
|
472
472
|
});
|
|
473
473
|
// we add extra delay until we attempt to force-play
|
|
474
474
|
// the participant's media stream in Firefox and Safari,
|
|
@@ -519,14 +519,14 @@ export class DynascaleManager {
|
|
|
519
519
|
if (!deviceId) return;
|
|
520
520
|
if ('setSinkId' in audioElement) {
|
|
521
521
|
audioElement.setSinkId(deviceId).catch((e) => {
|
|
522
|
-
this.logger(
|
|
522
|
+
this.logger.warn(`Can't to set AudioElement sinkId`, e);
|
|
523
523
|
});
|
|
524
524
|
}
|
|
525
525
|
|
|
526
526
|
if (audioContext && 'setSinkId' in audioContext) {
|
|
527
527
|
// @ts-expect-error setSinkId is not available in all browsers
|
|
528
528
|
audioContext.setSinkId(deviceId).catch((e) => {
|
|
529
|
-
this.logger(
|
|
529
|
+
this.logger.warn(`Can't to set AudioContext sinkId`, e);
|
|
530
530
|
});
|
|
531
531
|
}
|
|
532
532
|
};
|
|
@@ -574,7 +574,7 @@ export class DynascaleManager {
|
|
|
574
574
|
// we will play audio directly through the audio element in other browsers
|
|
575
575
|
audioElement.muted = false;
|
|
576
576
|
audioElement.play().catch((e) => {
|
|
577
|
-
this.logger(
|
|
577
|
+
this.logger.warn(`Failed to play audio stream`, e);
|
|
578
578
|
});
|
|
579
579
|
}
|
|
580
580
|
|
|
@@ -630,7 +630,7 @@ export class DynascaleManager {
|
|
|
630
630
|
if (this.audioContext?.state === 'suspended') {
|
|
631
631
|
this.audioContext
|
|
632
632
|
.resume()
|
|
633
|
-
.catch((err) => this.logger(
|
|
633
|
+
.catch((err) => this.logger.warn(`Can't resume audio context`, err))
|
|
634
634
|
.then(() => {
|
|
635
635
|
document.removeEventListener('click', this.resumeAudioContext);
|
|
636
636
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { BaseStats } from '../stats';
|
|
2
2
|
import { SoundStateChangeHandler } from './sound-detector';
|
|
3
3
|
import { flatten } from '../stats/utils';
|
|
4
|
-
import {
|
|
4
|
+
import { videoLoggerSystem } from '../logger';
|
|
5
5
|
|
|
6
6
|
export class RNSpeechDetector {
|
|
7
7
|
private pc1 = new RTCPeerConnection({});
|
|
@@ -64,8 +64,8 @@ export class RNSpeechDetector {
|
|
|
64
64
|
this.stop();
|
|
65
65
|
};
|
|
66
66
|
} catch (error) {
|
|
67
|
-
const logger = getLogger(
|
|
68
|
-
logger('error
|
|
67
|
+
const logger = videoLoggerSystem.getLogger('RNSpeechDetector');
|
|
68
|
+
logger.error('error handling permissions: ', error);
|
|
69
69
|
return () => {};
|
|
70
70
|
}
|
|
71
71
|
}
|
|
@@ -164,8 +164,8 @@ export class RNSpeechDetector {
|
|
|
164
164
|
}
|
|
165
165
|
}
|
|
166
166
|
} catch (error) {
|
|
167
|
-
const logger = getLogger(
|
|
168
|
-
logger('error
|
|
167
|
+
const logger = videoLoggerSystem.getLogger('RNSpeechDetector');
|
|
168
|
+
logger.error('error checking audio level from stats', error);
|
|
169
169
|
}
|
|
170
170
|
};
|
|
171
171
|
|
|
@@ -4,7 +4,6 @@ import type {
|
|
|
4
4
|
TokenOrProvider,
|
|
5
5
|
User,
|
|
6
6
|
} from '../coordinator/connection/types';
|
|
7
|
-
import { getLogger } from '../logger';
|
|
8
7
|
import { StreamClient } from '../coordinator/connection/client';
|
|
9
8
|
import { getSdkInfo } from './client-details';
|
|
10
9
|
import { SdkType } from '../gen/video/sfu/models/models';
|
|
@@ -50,12 +49,11 @@ export const createCoordinatorClient = (
|
|
|
50
49
|
options: StreamClientOptions | undefined,
|
|
51
50
|
) => {
|
|
52
51
|
const clientAppIdentifier = getClientAppIdentifier(options);
|
|
53
|
-
|
|
52
|
+
|
|
54
53
|
return new StreamClient(apiKey, {
|
|
55
54
|
persistUserOnConnectionFailure: true,
|
|
56
55
|
...options,
|
|
57
56
|
clientAppIdentifier,
|
|
58
|
-
logger: coordinatorLogger,
|
|
59
57
|
});
|
|
60
58
|
};
|
|
61
59
|
|
package/src/logger.ts
CHANGED
|
@@ -1,17 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as scopedLogger from '@stream-io/logger';
|
|
2
2
|
import { isReactNative } from './helpers/platforms';
|
|
3
|
-
|
|
4
|
-
// log levels, sorted by verbosity
|
|
5
|
-
export const logLevels: Record<LogLevel, number> = Object.freeze({
|
|
6
|
-
trace: 0,
|
|
7
|
-
debug: 1,
|
|
8
|
-
info: 2,
|
|
9
|
-
warn: 3,
|
|
10
|
-
error: 4,
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
let logger: Logger | undefined;
|
|
14
|
-
let level: LogLevel = 'info';
|
|
3
|
+
import type { Logger } from './coordinator/connection/types';
|
|
15
4
|
|
|
16
5
|
export const logToConsole: Logger = (logLevel, message, ...args) => {
|
|
17
6
|
let logMethod;
|
|
@@ -46,26 +35,12 @@ export const logToConsole: Logger = (logLevel, message, ...args) => {
|
|
|
46
35
|
logMethod(message, ...args);
|
|
47
36
|
};
|
|
48
37
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
};
|
|
38
|
+
/**
|
|
39
|
+
* @internal
|
|
40
|
+
*/
|
|
41
|
+
export type ScopedLogger = scopedLogger.Logger;
|
|
55
42
|
|
|
56
|
-
export
|
|
57
|
-
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
export const getLogLevel = (): LogLevel => level;
|
|
43
|
+
export { LogLevelEnum } from '@stream-io/logger';
|
|
44
|
+
export type { LogLevel, Sink } from '@stream-io/logger';
|
|
61
45
|
|
|
62
|
-
export const
|
|
63
|
-
const loggerMethod = logger || logToConsole;
|
|
64
|
-
const tags = (withTags || []).filter(Boolean).join(':');
|
|
65
|
-
const result: Logger = (logLevel, message, ...args) => {
|
|
66
|
-
if (logLevels[logLevel] >= logLevels[level]) {
|
|
67
|
-
loggerMethod(logLevel, `[${tags}]: ${message}`, ...args);
|
|
68
|
-
}
|
|
69
|
-
};
|
|
70
|
-
return result;
|
|
71
|
-
};
|
|
46
|
+
export const videoLoggerSystem = scopedLogger.createLoggerSystem();
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
import { TwirpFetchTransport } from '@protobuf-ts/twirp-transport';
|
|
9
9
|
import { NextUnaryFn, UnaryCall } from '@protobuf-ts/runtime-rpc';
|
|
10
10
|
import { promiseWithResolvers } from '../../helpers/promise';
|
|
11
|
+
import { ScopedLogger } from '../../logger';
|
|
11
12
|
|
|
12
13
|
describe('createClient', () => {
|
|
13
14
|
it('should create a client with TwirpFetchTransport', () => {
|
|
@@ -30,7 +31,10 @@ describe('createClient', () => {
|
|
|
30
31
|
|
|
31
32
|
it('withRequestLogger should log the request', () => {
|
|
32
33
|
const logger = vi.fn();
|
|
33
|
-
const interceptor = withRequestLogger(
|
|
34
|
+
const interceptor = withRequestLogger(
|
|
35
|
+
{ debug: logger } as unknown as ScopedLogger,
|
|
36
|
+
'debug',
|
|
37
|
+
);
|
|
34
38
|
const next = vi.fn().mockReturnValue({});
|
|
35
39
|
// @ts-expect-error - private field
|
|
36
40
|
interceptor.interceptUnary(next, { name: 'test' }, null, null);
|
package/src/rpc/createClient.ts
CHANGED
|
@@ -10,9 +10,10 @@ import {
|
|
|
10
10
|
TwirpOptions,
|
|
11
11
|
} from '@protobuf-ts/twirp-transport';
|
|
12
12
|
import { SignalServerClient } from '../gen/video/sfu/signal_rpc/signal.client';
|
|
13
|
-
import { Logger, LogLevel } from '../coordinator/connection/types';
|
|
14
13
|
import type { Trace } from '../stats';
|
|
15
14
|
import type { SfuResponseWithError } from './retryable';
|
|
15
|
+
import type { ScopedLogger } from '../logger';
|
|
16
|
+
import type { LogLevel } from '@stream-io/logger';
|
|
16
17
|
|
|
17
18
|
const defaultOptions: TwirpOptions = {
|
|
18
19
|
baseUrl: '',
|
|
@@ -40,7 +41,7 @@ export const withHeaders = (
|
|
|
40
41
|
};
|
|
41
42
|
|
|
42
43
|
export const withRequestLogger = (
|
|
43
|
-
logger:
|
|
44
|
+
logger: ScopedLogger,
|
|
44
45
|
level: LogLevel,
|
|
45
46
|
): RpcInterceptor => {
|
|
46
47
|
return {
|
|
@@ -51,7 +52,7 @@ export const withRequestLogger = (
|
|
|
51
52
|
options: RpcOptions,
|
|
52
53
|
): UnaryCall => {
|
|
53
54
|
const invocation = next(method, input, options);
|
|
54
|
-
logger(
|
|
55
|
+
logger[level](`Invoked SFU RPC method ${method.name}`, {
|
|
55
56
|
request: invocation.request,
|
|
56
57
|
headers: invocation.requestHeaders,
|
|
57
58
|
response: invocation.response,
|
package/src/rpc/retryable.ts
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
import { TwirpErrorCode } from '@protobuf-ts/twirp-transport';
|
|
7
7
|
import { retryInterval, sleep } from '../coordinator/connection/utils';
|
|
8
8
|
import { Error as SfuError } from '../gen/video/sfu/models/models';
|
|
9
|
-
import {
|
|
9
|
+
import { videoLoggerSystem } from '../logger';
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* An internal interface which asserts that "retryable" SFU responses
|
|
@@ -48,7 +48,9 @@ export const retryable = async <
|
|
|
48
48
|
err.code === TwirpErrorCode[TwirpErrorCode.cancelled];
|
|
49
49
|
const isAborted = signal?.aborted ?? false;
|
|
50
50
|
if (isRequestCancelled || isAborted) throw err;
|
|
51
|
-
|
|
51
|
+
videoLoggerSystem
|
|
52
|
+
.getLogger('sfu-client', { tags: ['rpc'] })
|
|
53
|
+
.debug(`rpc failed (${attempt})`, err);
|
|
52
54
|
attempt++;
|
|
53
55
|
}
|
|
54
56
|
} while (!result || result.response.error?.shouldRetry);
|
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type {
|
|
3
|
-
CallEventListener,
|
|
4
|
-
Logger,
|
|
5
|
-
} from '../coordinator/connection/types';
|
|
1
|
+
import { ScopedLogger, videoLoggerSystem } from '../logger';
|
|
2
|
+
import type { CallEventListener } from '../coordinator/connection/types';
|
|
6
3
|
import { CallingState, CallState } from '../store';
|
|
7
4
|
import { createSafeAsyncSubscription } from '../store/rxUtils';
|
|
8
5
|
import {
|
|
@@ -23,7 +20,7 @@ import { BasePeerConnectionOpts, OnReconnectionNeeded } from './types';
|
|
|
23
20
|
* @internal
|
|
24
21
|
*/
|
|
25
22
|
export abstract class BasePeerConnection {
|
|
26
|
-
protected readonly logger:
|
|
23
|
+
protected readonly logger: ScopedLogger;
|
|
27
24
|
protected readonly peerType: PeerType;
|
|
28
25
|
protected readonly pc: RTCPeerConnection;
|
|
29
26
|
protected readonly state: CallState;
|
|
@@ -67,10 +64,10 @@ export abstract class BasePeerConnection {
|
|
|
67
64
|
this.dispatcher = dispatcher;
|
|
68
65
|
this.iceRestartDelay = iceRestartDelay;
|
|
69
66
|
this.onReconnectionNeeded = onReconnectionNeeded;
|
|
70
|
-
this.logger = getLogger(
|
|
67
|
+
this.logger = videoLoggerSystem.getLogger(
|
|
71
68
|
peerType === PeerType.SUBSCRIBER ? 'Subscriber' : 'Publisher',
|
|
72
|
-
tag,
|
|
73
|
-
|
|
69
|
+
{ tags: [tag] },
|
|
70
|
+
);
|
|
74
71
|
this.pc = this.createPeerConnection(connectionConfig);
|
|
75
72
|
this.stats = new StatsTracer(this.pc, peerType, this.trackIdToTrackType);
|
|
76
73
|
if (enableTracing) {
|
|
@@ -142,7 +139,7 @@ export abstract class BasePeerConnection {
|
|
|
142
139
|
protected tryRestartIce = () => {
|
|
143
140
|
this.restartIce().catch((e) => {
|
|
144
141
|
const reason = 'restartICE() failed, initiating reconnect';
|
|
145
|
-
this.logger(
|
|
142
|
+
this.logger.error(reason, e);
|
|
146
143
|
const strategy =
|
|
147
144
|
e instanceof NegotiationError &&
|
|
148
145
|
e.error.code === ErrorCode.PARTICIPANT_SIGNAL_LOST
|
|
@@ -165,7 +162,7 @@ export abstract class BasePeerConnection {
|
|
|
165
162
|
const lockKey = `pc.${this.lock}.${event}`;
|
|
166
163
|
withoutConcurrency(lockKey, async () => fn(e)).catch((err) => {
|
|
167
164
|
if (this.isDisposed) return;
|
|
168
|
-
this.logger(
|
|
165
|
+
this.logger.warn(`Error handling ${event}`, err);
|
|
169
166
|
});
|
|
170
167
|
}),
|
|
171
168
|
);
|
|
@@ -187,7 +184,7 @@ export abstract class BasePeerConnection {
|
|
|
187
184
|
async (candidate) => {
|
|
188
185
|
return this.pc.addIceCandidate(candidate).catch((e) => {
|
|
189
186
|
if (this.isDisposed) return;
|
|
190
|
-
this.logger(
|
|
187
|
+
this.logger.warn(`ICE candidate error`, e, candidate);
|
|
191
188
|
});
|
|
192
189
|
},
|
|
193
190
|
);
|
|
@@ -240,7 +237,7 @@ export abstract class BasePeerConnection {
|
|
|
240
237
|
private onIceCandidate = (e: RTCPeerConnectionIceEvent) => {
|
|
241
238
|
const { candidate } = e;
|
|
242
239
|
if (!candidate) {
|
|
243
|
-
this.logger('
|
|
240
|
+
this.logger.debug('null ice candidate');
|
|
244
241
|
return;
|
|
245
242
|
}
|
|
246
243
|
|
|
@@ -249,7 +246,7 @@ export abstract class BasePeerConnection {
|
|
|
249
246
|
.iceTrickle({ peerType: this.peerType, iceCandidate })
|
|
250
247
|
.catch((err) => {
|
|
251
248
|
if (this.isDisposed) return;
|
|
252
|
-
this.logger(
|
|
249
|
+
this.logger.warn(`ICETrickle failed`, err);
|
|
253
250
|
});
|
|
254
251
|
};
|
|
255
252
|
|
|
@@ -272,7 +269,7 @@ export abstract class BasePeerConnection {
|
|
|
272
269
|
*/
|
|
273
270
|
private onConnectionStateChange = async () => {
|
|
274
271
|
const state = this.pc.connectionState;
|
|
275
|
-
this.logger(
|
|
272
|
+
this.logger.debug(`Connection state changed`, state);
|
|
276
273
|
if (this.tracer && (state === 'connected' || state === 'failed')) {
|
|
277
274
|
try {
|
|
278
275
|
const stats = await this.stats.get();
|
|
@@ -299,7 +296,7 @@ export abstract class BasePeerConnection {
|
|
|
299
296
|
*/
|
|
300
297
|
private onIceConnectionStateChange = () => {
|
|
301
298
|
const state = this.pc.iceConnectionState;
|
|
302
|
-
this.logger(
|
|
299
|
+
this.logger.debug(`ICE connection state changed`, state);
|
|
303
300
|
this.handleConnectionStateUpdate(state);
|
|
304
301
|
};
|
|
305
302
|
|
|
@@ -316,14 +313,14 @@ export abstract class BasePeerConnection {
|
|
|
316
313
|
switch (state) {
|
|
317
314
|
case 'failed':
|
|
318
315
|
// in the `failed` state, we try to restart ICE immediately
|
|
319
|
-
this.logger('
|
|
316
|
+
this.logger.info('restartICE due to failed connection');
|
|
320
317
|
this.tryRestartIce();
|
|
321
318
|
break;
|
|
322
319
|
|
|
323
320
|
case 'disconnected':
|
|
324
321
|
// in the `disconnected` state, we schedule a restartICE() after a delay
|
|
325
322
|
// as the browser might recover the connection in the meantime
|
|
326
|
-
this.logger('
|
|
323
|
+
this.logger.info('disconnected connection, scheduling restartICE');
|
|
327
324
|
clearTimeout(this.iceRestartTimeout);
|
|
328
325
|
this.iceRestartTimeout = setTimeout(() => {
|
|
329
326
|
const currentState = this.pc.iceConnectionState;
|
|
@@ -336,7 +333,7 @@ export abstract class BasePeerConnection {
|
|
|
336
333
|
case 'connected':
|
|
337
334
|
// in the `connected` state, we clear the ice restart timeout if it exists
|
|
338
335
|
if (this.iceRestartTimeout) {
|
|
339
|
-
this.logger('
|
|
336
|
+
this.logger.info('connected connection, canceling restartICE');
|
|
340
337
|
clearTimeout(this.iceRestartTimeout);
|
|
341
338
|
this.iceRestartTimeout = undefined;
|
|
342
339
|
}
|
|
@@ -352,20 +349,20 @@ export abstract class BasePeerConnection {
|
|
|
352
349
|
e instanceof RTCPeerConnectionIceErrorEvent
|
|
353
350
|
? `${e.errorCode}: ${e.errorText}`
|
|
354
351
|
: e;
|
|
355
|
-
this.logger('
|
|
352
|
+
this.logger.debug('ICE Candidate error', errorMessage);
|
|
356
353
|
};
|
|
357
354
|
|
|
358
355
|
/**
|
|
359
356
|
* Handles the ICE gathering state change event.
|
|
360
357
|
*/
|
|
361
358
|
private onIceGatherChange = () => {
|
|
362
|
-
this.logger(
|
|
359
|
+
this.logger.debug(`ICE Gathering State`, this.pc.iceGatheringState);
|
|
363
360
|
};
|
|
364
361
|
|
|
365
362
|
/**
|
|
366
363
|
* Handles the signaling state change event.
|
|
367
364
|
*/
|
|
368
365
|
private onSignalingChange = () => {
|
|
369
|
-
this.logger(
|
|
366
|
+
this.logger.debug(`Signaling state changed`, this.pc.signalingState);
|
|
370
367
|
};
|
|
371
368
|
}
|
package/src/rtc/Dispatcher.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CallEventListener, EventTypes } from '../coordinator/connection/types';
|
|
2
2
|
import type { SfuEvent } from '../gen/video/sfu/event/events';
|
|
3
|
-
import {
|
|
3
|
+
import { videoLoggerSystem } from '../logger';
|
|
4
4
|
|
|
5
5
|
export type SfuEventKinds = NonNullable<SfuEvent['eventPayload']['oneofKind']>;
|
|
6
6
|
export type AllSfuEvents = {
|
|
@@ -53,7 +53,7 @@ export const isSfuEvent = (
|
|
|
53
53
|
};
|
|
54
54
|
|
|
55
55
|
export class Dispatcher {
|
|
56
|
-
private readonly logger = getLogger(
|
|
56
|
+
private readonly logger = videoLoggerSystem.getLogger('Dispatcher');
|
|
57
57
|
private subscribers: Partial<
|
|
58
58
|
Record<SfuEventKinds, CallEventListener<any>[] | undefined>
|
|
59
59
|
> = {};
|
|
@@ -65,14 +65,14 @@ export class Dispatcher {
|
|
|
65
65
|
const eventKind = message.eventPayload.oneofKind;
|
|
66
66
|
if (!eventKind) return;
|
|
67
67
|
const payload = message.eventPayload[eventKind];
|
|
68
|
-
this.logger(
|
|
68
|
+
this.logger.debug(`Dispatching ${eventKind}, tag=${tag}`, payload);
|
|
69
69
|
const listeners = this.subscribers[eventKind];
|
|
70
70
|
if (!listeners) return;
|
|
71
71
|
for (const fn of listeners) {
|
|
72
72
|
try {
|
|
73
73
|
fn(payload);
|
|
74
74
|
} catch (e) {
|
|
75
|
-
this.logger('
|
|
75
|
+
this.logger.warn('Listener failed with error', e);
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
78
|
};
|