@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/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 {
|
|
@@ -16,18 +13,20 @@ import { StreamSfuClient } from '../StreamSfuClient';
|
|
|
16
13
|
import { AllSfuEvents, Dispatcher } from './Dispatcher';
|
|
17
14
|
import { withoutConcurrency } from '../helpers/concurrency';
|
|
18
15
|
import { StatsTracer, Tracer, traceRTCPeerConnection } from '../stats';
|
|
19
|
-
import { BasePeerConnectionOpts, OnReconnectionNeeded } from './types';
|
|
16
|
+
import type { BasePeerConnectionOpts, OnReconnectionNeeded } from './types';
|
|
17
|
+
import type { ClientPublishOptions } from '../types';
|
|
20
18
|
|
|
21
19
|
/**
|
|
22
20
|
* A base class for the `Publisher` and `Subscriber` classes.
|
|
23
21
|
* @internal
|
|
24
22
|
*/
|
|
25
23
|
export abstract class BasePeerConnection {
|
|
26
|
-
protected readonly logger:
|
|
24
|
+
protected readonly logger: ScopedLogger;
|
|
27
25
|
protected readonly peerType: PeerType;
|
|
28
26
|
protected readonly pc: RTCPeerConnection;
|
|
29
27
|
protected readonly state: CallState;
|
|
30
28
|
protected readonly dispatcher: Dispatcher;
|
|
29
|
+
protected readonly clientPublishOptions?: ClientPublishOptions;
|
|
31
30
|
protected sfuClient: StreamSfuClient;
|
|
32
31
|
|
|
33
32
|
private onReconnectionNeeded?: OnReconnectionNeeded;
|
|
@@ -58,6 +57,7 @@ export abstract class BasePeerConnection {
|
|
|
58
57
|
onReconnectionNeeded,
|
|
59
58
|
tag,
|
|
60
59
|
enableTracing,
|
|
60
|
+
clientPublishOptions,
|
|
61
61
|
iceRestartDelay = 2500,
|
|
62
62
|
}: BasePeerConnectionOpts,
|
|
63
63
|
) {
|
|
@@ -66,11 +66,12 @@ export abstract class BasePeerConnection {
|
|
|
66
66
|
this.state = state;
|
|
67
67
|
this.dispatcher = dispatcher;
|
|
68
68
|
this.iceRestartDelay = iceRestartDelay;
|
|
69
|
+
this.clientPublishOptions = clientPublishOptions;
|
|
69
70
|
this.onReconnectionNeeded = onReconnectionNeeded;
|
|
70
|
-
this.logger = getLogger(
|
|
71
|
+
this.logger = videoLoggerSystem.getLogger(
|
|
71
72
|
peerType === PeerType.SUBSCRIBER ? 'Subscriber' : 'Publisher',
|
|
72
|
-
tag,
|
|
73
|
-
|
|
73
|
+
{ tags: [tag] },
|
|
74
|
+
);
|
|
74
75
|
this.pc = this.createPeerConnection(connectionConfig);
|
|
75
76
|
this.stats = new StatsTracer(this.pc, peerType, this.trackIdToTrackType);
|
|
76
77
|
if (enableTracing) {
|
|
@@ -142,13 +143,13 @@ export abstract class BasePeerConnection {
|
|
|
142
143
|
protected tryRestartIce = () => {
|
|
143
144
|
this.restartIce().catch((e) => {
|
|
144
145
|
const reason = 'restartICE() failed, initiating reconnect';
|
|
145
|
-
this.logger(
|
|
146
|
+
this.logger.error(reason, e);
|
|
146
147
|
const strategy =
|
|
147
148
|
e instanceof NegotiationError &&
|
|
148
149
|
e.error.code === ErrorCode.PARTICIPANT_SIGNAL_LOST
|
|
149
150
|
? WebsocketReconnectStrategy.FAST
|
|
150
151
|
: WebsocketReconnectStrategy.REJOIN;
|
|
151
|
-
this.onReconnectionNeeded?.(strategy, reason);
|
|
152
|
+
this.onReconnectionNeeded?.(strategy, reason, this.peerType);
|
|
152
153
|
});
|
|
153
154
|
};
|
|
154
155
|
|
|
@@ -165,7 +166,7 @@ export abstract class BasePeerConnection {
|
|
|
165
166
|
const lockKey = `pc.${this.lock}.${event}`;
|
|
166
167
|
withoutConcurrency(lockKey, async () => fn(e)).catch((err) => {
|
|
167
168
|
if (this.isDisposed) return;
|
|
168
|
-
this.logger(
|
|
169
|
+
this.logger.warn(`Error handling ${event}`, err);
|
|
169
170
|
});
|
|
170
171
|
}),
|
|
171
172
|
);
|
|
@@ -187,7 +188,7 @@ export abstract class BasePeerConnection {
|
|
|
187
188
|
async (candidate) => {
|
|
188
189
|
return this.pc.addIceCandidate(candidate).catch((e) => {
|
|
189
190
|
if (this.isDisposed) return;
|
|
190
|
-
this.logger(
|
|
191
|
+
this.logger.warn(`ICE candidate error`, e, candidate);
|
|
191
192
|
});
|
|
192
193
|
},
|
|
193
194
|
);
|
|
@@ -240,7 +241,7 @@ export abstract class BasePeerConnection {
|
|
|
240
241
|
private onIceCandidate = (e: RTCPeerConnectionIceEvent) => {
|
|
241
242
|
const { candidate } = e;
|
|
242
243
|
if (!candidate) {
|
|
243
|
-
this.logger('
|
|
244
|
+
this.logger.debug('null ice candidate');
|
|
244
245
|
return;
|
|
245
246
|
}
|
|
246
247
|
|
|
@@ -249,7 +250,7 @@ export abstract class BasePeerConnection {
|
|
|
249
250
|
.iceTrickle({ peerType: this.peerType, iceCandidate })
|
|
250
251
|
.catch((err) => {
|
|
251
252
|
if (this.isDisposed) return;
|
|
252
|
-
this.logger(
|
|
253
|
+
this.logger.warn(`ICETrickle failed`, err);
|
|
253
254
|
});
|
|
254
255
|
};
|
|
255
256
|
|
|
@@ -272,7 +273,7 @@ export abstract class BasePeerConnection {
|
|
|
272
273
|
*/
|
|
273
274
|
private onConnectionStateChange = async () => {
|
|
274
275
|
const state = this.pc.connectionState;
|
|
275
|
-
this.logger(
|
|
276
|
+
this.logger.debug(`Connection state changed`, state);
|
|
276
277
|
if (this.tracer && (state === 'connected' || state === 'failed')) {
|
|
277
278
|
try {
|
|
278
279
|
const stats = await this.stats.get();
|
|
@@ -287,6 +288,7 @@ export abstract class BasePeerConnection {
|
|
|
287
288
|
this.onReconnectionNeeded?.(
|
|
288
289
|
WebsocketReconnectStrategy.REJOIN,
|
|
289
290
|
'Connection failed',
|
|
291
|
+
this.peerType,
|
|
290
292
|
);
|
|
291
293
|
return;
|
|
292
294
|
}
|
|
@@ -299,7 +301,7 @@ export abstract class BasePeerConnection {
|
|
|
299
301
|
*/
|
|
300
302
|
private onIceConnectionStateChange = () => {
|
|
301
303
|
const state = this.pc.iceConnectionState;
|
|
302
|
-
this.logger(
|
|
304
|
+
this.logger.debug(`ICE connection state changed`, state);
|
|
303
305
|
this.handleConnectionStateUpdate(state);
|
|
304
306
|
};
|
|
305
307
|
|
|
@@ -316,14 +318,14 @@ export abstract class BasePeerConnection {
|
|
|
316
318
|
switch (state) {
|
|
317
319
|
case 'failed':
|
|
318
320
|
// in the `failed` state, we try to restart ICE immediately
|
|
319
|
-
this.logger('
|
|
321
|
+
this.logger.info('restartICE due to failed connection');
|
|
320
322
|
this.tryRestartIce();
|
|
321
323
|
break;
|
|
322
324
|
|
|
323
325
|
case 'disconnected':
|
|
324
326
|
// in the `disconnected` state, we schedule a restartICE() after a delay
|
|
325
327
|
// as the browser might recover the connection in the meantime
|
|
326
|
-
this.logger('
|
|
328
|
+
this.logger.info('disconnected connection, scheduling restartICE');
|
|
327
329
|
clearTimeout(this.iceRestartTimeout);
|
|
328
330
|
this.iceRestartTimeout = setTimeout(() => {
|
|
329
331
|
const currentState = this.pc.iceConnectionState;
|
|
@@ -336,7 +338,7 @@ export abstract class BasePeerConnection {
|
|
|
336
338
|
case 'connected':
|
|
337
339
|
// in the `connected` state, we clear the ice restart timeout if it exists
|
|
338
340
|
if (this.iceRestartTimeout) {
|
|
339
|
-
this.logger('
|
|
341
|
+
this.logger.info('connected connection, canceling restartICE');
|
|
340
342
|
clearTimeout(this.iceRestartTimeout);
|
|
341
343
|
this.iceRestartTimeout = undefined;
|
|
342
344
|
}
|
|
@@ -352,20 +354,20 @@ export abstract class BasePeerConnection {
|
|
|
352
354
|
e instanceof RTCPeerConnectionIceErrorEvent
|
|
353
355
|
? `${e.errorCode}: ${e.errorText}`
|
|
354
356
|
: e;
|
|
355
|
-
this.logger('
|
|
357
|
+
this.logger.debug('ICE Candidate error', errorMessage);
|
|
356
358
|
};
|
|
357
359
|
|
|
358
360
|
/**
|
|
359
361
|
* Handles the ICE gathering state change event.
|
|
360
362
|
*/
|
|
361
363
|
private onIceGatherChange = () => {
|
|
362
|
-
this.logger(
|
|
364
|
+
this.logger.debug(`ICE Gathering State`, this.pc.iceGatheringState);
|
|
363
365
|
};
|
|
364
366
|
|
|
365
367
|
/**
|
|
366
368
|
* Handles the signaling state change event.
|
|
367
369
|
*/
|
|
368
370
|
private onSignalingChange = () => {
|
|
369
|
-
this.logger(
|
|
371
|
+
this.logger.debug(`Signaling state changed`, this.pc.signalingState);
|
|
370
372
|
};
|
|
371
373
|
}
|
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
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ReplaySubject } from 'rxjs';
|
|
2
2
|
import { ICETrickle } from '../gen/video/sfu/event/events';
|
|
3
3
|
import { PeerType } from '../gen/video/sfu/models/models';
|
|
4
|
-
import {
|
|
4
|
+
import { videoLoggerSystem } from '../logger';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* A buffer for ICE Candidates. Used for ICE Trickle:
|
|
@@ -20,8 +20,8 @@ export class IceTrickleBuffer {
|
|
|
20
20
|
} else if (iceTrickle.peerType === PeerType.PUBLISHER_UNSPECIFIED) {
|
|
21
21
|
this.publisherCandidates.next(iceCandidate);
|
|
22
22
|
} else {
|
|
23
|
-
const logger = getLogger(
|
|
24
|
-
logger(
|
|
23
|
+
const logger = videoLoggerSystem.getLogger('sfu-client');
|
|
24
|
+
logger.warn(`ICETrickle, Unknown peer type`, iceTrickle);
|
|
25
25
|
}
|
|
26
26
|
};
|
|
27
27
|
|
|
@@ -37,8 +37,8 @@ const toIceCandidate = (
|
|
|
37
37
|
try {
|
|
38
38
|
return JSON.parse(iceTrickle.iceCandidate);
|
|
39
39
|
} catch (e) {
|
|
40
|
-
const logger = getLogger(
|
|
41
|
-
logger(
|
|
40
|
+
const logger = videoLoggerSystem.getLogger('sfu-client');
|
|
41
|
+
logger.error(`Failed to parse ICE Trickle`, e, iceTrickle);
|
|
42
42
|
return undefined;
|
|
43
43
|
}
|
|
44
44
|
};
|
package/src/rtc/Publisher.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { BasePeerConnection } from './BasePeerConnection';
|
|
2
|
-
import {
|
|
2
|
+
import type {
|
|
3
|
+
BasePeerConnectionOpts,
|
|
3
4
|
PublishBundle,
|
|
4
|
-
PublisherConstructorOpts,
|
|
5
5
|
TrackPublishOptions,
|
|
6
6
|
} from './types';
|
|
7
7
|
import { NegotiationError } from './NegotiationError';
|
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
} from './layers';
|
|
22
22
|
import { isSvcCodec } from './codecs';
|
|
23
23
|
import { isAudioTrackType } from './helpers/tracks';
|
|
24
|
-
import { extractMid } from './helpers/sdp';
|
|
24
|
+
import { extractMid, removeCodecsExcept } from './helpers/sdp';
|
|
25
25
|
import { withoutConcurrency } from '../helpers/concurrency';
|
|
26
26
|
import { isReactNative } from '../helpers/platforms';
|
|
27
27
|
|
|
@@ -38,7 +38,10 @@ export class Publisher extends BasePeerConnection {
|
|
|
38
38
|
/**
|
|
39
39
|
* Constructs a new `Publisher` instance.
|
|
40
40
|
*/
|
|
41
|
-
constructor(
|
|
41
|
+
constructor(
|
|
42
|
+
baseOptions: BasePeerConnectionOpts,
|
|
43
|
+
publishOptions: PublishOption[],
|
|
44
|
+
) {
|
|
42
45
|
super(PeerType.PUBLISHER_UNSPECIFIED, baseOptions);
|
|
43
46
|
this.publishOptions = publishOptions;
|
|
44
47
|
|
|
@@ -136,7 +139,7 @@ export class Publisher extends BasePeerConnection {
|
|
|
136
139
|
await transceiver.sender.setParameters(params);
|
|
137
140
|
|
|
138
141
|
const trackType = publishOption.trackType;
|
|
139
|
-
this.logger(
|
|
142
|
+
this.logger.debug(`Added ${TrackType[trackType]} transceiver`);
|
|
140
143
|
this.transceiverCache.add({ publishOption, transceiver, options });
|
|
141
144
|
this.trackIdToTrackType.set(track.id, trackType);
|
|
142
145
|
|
|
@@ -273,7 +276,7 @@ export class Publisher extends BasePeerConnection {
|
|
|
273
276
|
const enabledLayers = layers.filter((l) => l.active);
|
|
274
277
|
|
|
275
278
|
const tag = 'Update publish quality:';
|
|
276
|
-
this.logger(
|
|
279
|
+
this.logger.info(`${tag} requested layers by SFU:`, enabledLayers);
|
|
277
280
|
|
|
278
281
|
const transceiverId = this.transceiverCache.find(
|
|
279
282
|
(t) =>
|
|
@@ -282,12 +285,12 @@ export class Publisher extends BasePeerConnection {
|
|
|
282
285
|
);
|
|
283
286
|
const sender = transceiverId?.transceiver.sender;
|
|
284
287
|
if (!sender) {
|
|
285
|
-
return this.logger(
|
|
288
|
+
return this.logger.warn(`${tag} no video sender found.`);
|
|
286
289
|
}
|
|
287
290
|
|
|
288
291
|
const params = sender.getParameters();
|
|
289
292
|
if (params.encodings.length === 0) {
|
|
290
|
-
return this.logger(
|
|
293
|
+
return this.logger.warn(`${tag} there are no encodings set.`);
|
|
291
294
|
}
|
|
292
295
|
|
|
293
296
|
const codecInUse = transceiverId?.publishOption.codec?.name;
|
|
@@ -343,21 +346,21 @@ export class Publisher extends BasePeerConnection {
|
|
|
343
346
|
|
|
344
347
|
const activeEncoders = params.encodings.filter((e) => e.active);
|
|
345
348
|
if (!changed) {
|
|
346
|
-
return this.logger(
|
|
349
|
+
return this.logger.info(`${tag} no change:`, activeEncoders);
|
|
347
350
|
}
|
|
348
351
|
|
|
349
352
|
await sender.setParameters(params);
|
|
350
|
-
this.logger(
|
|
353
|
+
this.logger.info(`${tag} enabled rids:`, activeEncoders);
|
|
351
354
|
};
|
|
352
355
|
|
|
353
356
|
/**
|
|
354
357
|
* Restarts the ICE connection and renegotiates with the SFU.
|
|
355
358
|
*/
|
|
356
359
|
restartIce = async (): Promise<void> => {
|
|
357
|
-
this.logger('
|
|
360
|
+
this.logger.debug('Restarting ICE connection');
|
|
358
361
|
const signalingState = this.pc.signalingState;
|
|
359
362
|
if (this.isIceRestarting || signalingState === 'have-local-offer') {
|
|
360
|
-
this.logger('
|
|
363
|
+
this.logger.debug('ICE restart is already in progress');
|
|
361
364
|
return;
|
|
362
365
|
}
|
|
363
366
|
await this.negotiate({ iceRestart: true });
|
|
@@ -378,7 +381,12 @@ export class Publisher extends BasePeerConnection {
|
|
|
378
381
|
this.isIceRestarting = options?.iceRestart ?? false;
|
|
379
382
|
await this.pc.setLocalDescription(offer);
|
|
380
383
|
|
|
381
|
-
const { sdp = '' } = offer;
|
|
384
|
+
const { sdp: baseSdp = '' } = offer;
|
|
385
|
+
const { dangerouslyForceCodec, fmtpLine } =
|
|
386
|
+
this.clientPublishOptions || {};
|
|
387
|
+
const sdp = dangerouslyForceCodec
|
|
388
|
+
? removeCodecsExcept(baseSdp, dangerouslyForceCodec, fmtpLine)
|
|
389
|
+
: baseSdp;
|
|
382
390
|
const { response } = await this.sfuClient.setPublisher({ sdp, tracks });
|
|
383
391
|
if (response.error) throw new NegotiationError(response.error);
|
|
384
392
|
|