@stream-io/video-client 1.21.0 → 1.22.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 +19 -0
- package/dist/index.browser.es.js +171 -143
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +171 -143
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.es.js +171 -143
- package/dist/index.es.js.map +1 -1
- package/dist/src/Call.d.ts +2 -0
- package/dist/src/devices/InputMediaDeviceManager.d.ts +3 -3
- package/dist/src/devices/SpeakerState.d.ts +3 -1
- package/dist/src/devices/devices.d.ts +8 -8
- package/dist/src/events/call.d.ts +1 -1
- package/dist/src/gen/video/sfu/models/models.d.ts +8 -0
- package/dist/src/rtc/BasePeerConnection.d.ts +1 -0
- package/dist/src/stats/SfuStatsReporter.d.ts +5 -1
- package/dist/src/stats/utils.d.ts +14 -0
- package/index.ts +0 -4
- package/package.json +10 -10
- package/src/Call.ts +15 -8
- package/src/devices/CameraManager.ts +27 -23
- package/src/devices/InputMediaDeviceManager.ts +8 -5
- package/src/devices/MicrophoneManager.ts +1 -1
- package/src/devices/ScreenShareManager.ts +2 -2
- package/src/devices/SpeakerManager.ts +2 -1
- package/src/devices/SpeakerState.ts +6 -3
- package/src/devices/__tests__/CameraManager.test.ts +43 -27
- package/src/devices/__tests__/MicrophoneManager.test.ts +5 -3
- package/src/devices/__tests__/ScreenShareManager.test.ts +5 -1
- package/src/devices/__tests__/mocks.ts +2 -3
- package/src/devices/devices.ts +38 -16
- package/src/events/__tests__/call.test.ts +23 -0
- package/src/events/call.ts +12 -1
- package/src/gen/video/sfu/models/models.ts +14 -0
- package/src/rtc/BasePeerConnection.ts +11 -5
- package/src/rtc/Publisher.ts +5 -1
- package/src/rtc/__tests__/Publisher.test.ts +2 -2
- package/src/rtc/__tests__/Subscriber.test.ts +2 -2
- package/src/rtc/__tests__/mocks/webrtc.mocks.ts +1 -1
- package/src/rtc/__tests__/videoLayers.test.ts +44 -0
- package/src/rtc/videoLayers.ts +6 -3
- package/src/stats/SfuStatsReporter.ts +15 -5
- package/src/stats/utils.ts +15 -0
- package/dist/src/stats/rtc/mediaDevices.d.ts +0 -2
- package/src/stats/rtc/mediaDevices.ts +0 -43
package/dist/src/Call.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { CallState } from './store';
|
|
|
3
3
|
import type { AcceptCallResponse, BlockUserResponse, CallRingEvent, CallSettingsResponse, CollectUserFeedbackRequest, CollectUserFeedbackResponse, DeleteCallRequest, DeleteCallResponse, EndCallResponse, GetCallReportResponse, GetCallResponse, GetOrCreateCallRequest, GetOrCreateCallResponse, GoLiveRequest, GoLiveResponse, JoinCallResponse, ListRecordingsResponse, ListTranscriptionsResponse, MuteUsersResponse, PinRequest, PinResponse, QueryCallMembersRequest, QueryCallMembersResponse, RejectCallResponse, RequestPermissionRequest, RequestPermissionResponse, SendCallEventResponse, SendReactionRequest, SendReactionResponse, StartClosedCaptionsRequest, StartClosedCaptionsResponse, StartFrameRecordingRequest, StartFrameRecordingResponse, StartHLSBroadcastingResponse, StartRecordingRequest, StartRecordingResponse, StartRTMPBroadcastsRequest, StartRTMPBroadcastsResponse, StartTranscriptionRequest, StartTranscriptionResponse, StopAllRTMPBroadcastsResponse, StopClosedCaptionsRequest, StopClosedCaptionsResponse, StopFrameRecordingResponse, StopHLSBroadcastingResponse, StopLiveRequest, StopLiveResponse, StopRecordingResponse, StopRTMPBroadcastsResponse, StopTranscriptionResponse, UnblockUserResponse, UnpinRequest, UnpinResponse, UpdateCallMembersRequest, UpdateCallMembersResponse, UpdateCallRequest, UpdateCallResponse, UpdateUserPermissionsRequest, UpdateUserPermissionsResponse, VideoDimension } from './gen/coordinator';
|
|
4
4
|
import { AudioTrackType, CallConstructor, CallLeaveOptions, ClientPublishOptions, ClosedCaptionsSettings, JoinCallData, TrackMuteType, VideoTrackType } from './types';
|
|
5
5
|
import { TrackType } from './gen/video/sfu/models/models';
|
|
6
|
+
import { Tracer } from './stats';
|
|
6
7
|
import { DynascaleManager } from './helpers/DynascaleManager';
|
|
7
8
|
import { PermissionsContext } from './permissions';
|
|
8
9
|
import { StreamClient } from './coordinator/connection/client';
|
|
@@ -64,6 +65,7 @@ export declare class Call {
|
|
|
64
65
|
* The permissions context of this call.
|
|
65
66
|
*/
|
|
66
67
|
readonly permissionsContext: PermissionsContext;
|
|
68
|
+
readonly tracer: Tracer;
|
|
67
69
|
readonly logger: Logger;
|
|
68
70
|
/**
|
|
69
71
|
* The event dispatcher instance dedicated to this Call instance.
|
|
@@ -5,14 +5,14 @@ import { Logger } from '../coordinator/connection/types';
|
|
|
5
5
|
import { TrackType } from '../gen/video/sfu/models/models';
|
|
6
6
|
import { MediaStreamFilter, MediaStreamFilterRegistrationResult } from './filters';
|
|
7
7
|
export declare abstract class InputMediaDeviceManager<T extends InputMediaDeviceManagerState<C>, C = MediaTrackConstraints> {
|
|
8
|
-
protected readonly call: Call;
|
|
9
|
-
readonly state: T;
|
|
10
|
-
protected readonly trackType: TrackType;
|
|
11
8
|
/**
|
|
12
9
|
* if true, stops the media stream when call is left
|
|
13
10
|
*/
|
|
14
11
|
stopOnLeave: boolean;
|
|
15
12
|
logger: Logger;
|
|
13
|
+
state: T;
|
|
14
|
+
protected readonly call: Call;
|
|
15
|
+
protected readonly trackType: TrackType;
|
|
16
16
|
protected subscriptions: Function[];
|
|
17
17
|
private isTrackStoppedDueToTrackEnd;
|
|
18
18
|
private filters;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { BehaviorSubject, Observable } from 'rxjs';
|
|
2
|
+
import { Tracer } from '../stats';
|
|
2
3
|
export declare class SpeakerState {
|
|
3
4
|
protected selectedDeviceSubject: BehaviorSubject<string>;
|
|
4
5
|
protected volumeSubject: BehaviorSubject<number>;
|
|
@@ -18,7 +19,8 @@ export declare class SpeakerState {
|
|
|
18
19
|
* Note: this feature is not supported in React Native
|
|
19
20
|
*/
|
|
20
21
|
volume$: Observable<number>;
|
|
21
|
-
|
|
22
|
+
private tracer;
|
|
23
|
+
constructor(tracer: Tracer);
|
|
22
24
|
/**
|
|
23
25
|
* The currently selected device
|
|
24
26
|
*
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { BrowserPermission } from './BrowserPermission';
|
|
2
|
+
import { Tracer } from '../stats';
|
|
2
3
|
/**
|
|
3
4
|
* Tells if the browser supports audio output change on 'audio' elements,
|
|
4
5
|
* see https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/setSinkId.
|
|
@@ -39,31 +40,30 @@ export declare const getAudioOutputDevices: () => import("rxjs").Observable<Medi
|
|
|
39
40
|
* Returns an audio media stream that fulfills the given constraints.
|
|
40
41
|
* If no constraints are provided, it uses the browser's default ones.
|
|
41
42
|
*
|
|
42
|
-
* @angular It's recommended to use the [`DeviceManagerService`](./DeviceManagerService.md) for a higher level API, use this low-level method only if the `DeviceManagerService` doesn't suit your requirements.
|
|
43
43
|
* @param trackConstraints the constraints to use when requesting the stream.
|
|
44
|
-
* @
|
|
44
|
+
* @param tracer the tracer to use for tracing the stream creation.
|
|
45
|
+
* @returns a new `MediaStream` fulfilling the given constraints.
|
|
45
46
|
*/
|
|
46
|
-
export declare const getAudioStream: (trackConstraints?: MediaTrackConstraints) => Promise<MediaStream>;
|
|
47
|
+
export declare const getAudioStream: (trackConstraints?: MediaTrackConstraints, tracer?: Tracer) => Promise<MediaStream>;
|
|
47
48
|
/**
|
|
48
49
|
* Returns a video media stream that fulfills the given constraints.
|
|
49
50
|
* If no constraints are provided, it uses the browser's default ones.
|
|
50
51
|
*
|
|
51
|
-
* @angular It's recommended to use the [`DeviceManagerService`](./DeviceManagerService.md) for a higher level API, use this low-level method only if the `DeviceManagerService` doesn't suit your requirements.
|
|
52
52
|
* @param trackConstraints the constraints to use when requesting the stream.
|
|
53
|
+
* @param tracer the tracer to use for tracing the stream creation.
|
|
53
54
|
* @returns a new `MediaStream` fulfilling the given constraints.
|
|
54
55
|
*/
|
|
55
|
-
export declare const getVideoStream: (trackConstraints?: MediaTrackConstraints) => Promise<MediaStream>;
|
|
56
|
+
export declare const getVideoStream: (trackConstraints?: MediaTrackConstraints, tracer?: Tracer) => Promise<MediaStream>;
|
|
56
57
|
/**
|
|
57
58
|
* Prompts the user for a permission to share a screen.
|
|
58
59
|
* If the user grants the permission, a screen sharing stream is returned. Throws otherwise.
|
|
59
60
|
*
|
|
60
61
|
* The callers of this API are responsible to handle the possible errors.
|
|
61
62
|
*
|
|
62
|
-
* @angular It's recommended to use the [`DeviceManagerService`](./DeviceManagerService.md) for a higher level API, use this low-level method only if the `DeviceManagerService` doesn't suit your requirements.
|
|
63
|
-
*
|
|
64
63
|
* @param options any additional options to pass to the [`getDisplayMedia`](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getDisplayMedia) API.
|
|
64
|
+
* @param tracer the tracer to use for tracing the stream creation.
|
|
65
65
|
*/
|
|
66
|
-
export declare const getScreenShareStream: (options?: DisplayMediaStreamOptions) => Promise<MediaStream>;
|
|
66
|
+
export declare const getScreenShareStream: (options?: DisplayMediaStreamOptions, tracer?: Tracer | undefined) => Promise<MediaStream>;
|
|
67
67
|
export declare const deviceIds$: import("rxjs").Observable<MediaDeviceInfo[]> | undefined;
|
|
68
68
|
/**
|
|
69
69
|
* Deactivates MediaStream (stops and removes tracks) to be later garbage collected
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Call } from '../Call';
|
|
2
|
-
import
|
|
2
|
+
import { CallAcceptedEvent, CallRejectedEvent } from '../gen/coordinator';
|
|
3
3
|
/**
|
|
4
4
|
* Event handler that watched the delivery of `call.accepted`.
|
|
5
5
|
* Once the event is received, the call is joined.
|
|
@@ -285,6 +285,14 @@ export interface PublishOption {
|
|
|
285
285
|
* @generated from protobuf field: int32 id = 8;
|
|
286
286
|
*/
|
|
287
287
|
id: number;
|
|
288
|
+
/**
|
|
289
|
+
* If true, instructs the publisher to send only the highest available simulcast layer,
|
|
290
|
+
* disabling all lower layers. This applies to simulcast encodings.
|
|
291
|
+
* For SVC codecs, prefer using the L1T3 (single spatial, 3 temporal layers) mode instead.
|
|
292
|
+
*
|
|
293
|
+
* @generated from protobuf field: bool use_single_layer = 9;
|
|
294
|
+
*/
|
|
295
|
+
useSingleLayer: boolean;
|
|
288
296
|
}
|
|
289
297
|
/**
|
|
290
298
|
* @generated from protobuf message stream.video.sfu.models.Codec
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { StreamSfuClient } from '../StreamSfuClient';
|
|
2
2
|
import { StatsOptions } from '../gen/coordinator';
|
|
3
3
|
import { Publisher, Subscriber } from '../rtc';
|
|
4
|
+
import { Tracer } from './rtc';
|
|
4
5
|
import { ClientDetails, WebsocketReconnectStrategy } from '../gen/video/sfu/models/models';
|
|
5
6
|
import { CameraManager, MicrophoneManager } from '../devices';
|
|
6
7
|
import { CallState } from '../store';
|
|
@@ -12,6 +13,7 @@ export type SfuStatsReporterOptions = {
|
|
|
12
13
|
microphone: MicrophoneManager;
|
|
13
14
|
camera: CameraManager;
|
|
14
15
|
state: CallState;
|
|
16
|
+
tracer: Tracer;
|
|
15
17
|
unifiedSessionId: string;
|
|
16
18
|
};
|
|
17
19
|
export declare class SfuStatsReporter {
|
|
@@ -23,6 +25,7 @@ export declare class SfuStatsReporter {
|
|
|
23
25
|
private readonly microphone;
|
|
24
26
|
private readonly camera;
|
|
25
27
|
private readonly state;
|
|
28
|
+
private readonly tracer;
|
|
26
29
|
private readonly unifiedSessionId;
|
|
27
30
|
private intervalId;
|
|
28
31
|
private timeoutId;
|
|
@@ -32,7 +35,7 @@ export declare class SfuStatsReporter {
|
|
|
32
35
|
private readonly sdkVersion;
|
|
33
36
|
private readonly webRTCVersion;
|
|
34
37
|
private readonly inputDevices;
|
|
35
|
-
constructor(sfuClient: StreamSfuClient, { options, clientDetails, subscriber, publisher, microphone, camera, state, unifiedSessionId, }: SfuStatsReporterOptions);
|
|
38
|
+
constructor(sfuClient: StreamSfuClient, { options, clientDetails, subscriber, publisher, microphone, camera, state, tracer, unifiedSessionId, }: SfuStatsReporterOptions);
|
|
36
39
|
private observeDevice;
|
|
37
40
|
sendConnectionTime: (connectionTimeSeconds: number) => void;
|
|
38
41
|
sendReconnectionTime: (strategy: WebsocketReconnectStrategy, timeSeconds: number) => void;
|
|
@@ -40,5 +43,6 @@ export declare class SfuStatsReporter {
|
|
|
40
43
|
private run;
|
|
41
44
|
start: () => void;
|
|
42
45
|
stop: () => void;
|
|
46
|
+
flush: () => void;
|
|
43
47
|
scheduleOne: (timeout: number) => void;
|
|
44
48
|
}
|
|
@@ -5,6 +5,20 @@ import { ClientDetails, Sdk } from '../gen/video/sfu/models/models';
|
|
|
5
5
|
* @param report the report to flatten.
|
|
6
6
|
*/
|
|
7
7
|
export declare const flatten: (report: RTCStatsReport) => RTCStats[];
|
|
8
|
+
/**
|
|
9
|
+
* Dump the provided MediaStream into a JSON object.
|
|
10
|
+
*/
|
|
11
|
+
export declare const dumpStream: (stream: MediaStream) => {
|
|
12
|
+
id: string;
|
|
13
|
+
tracks: {
|
|
14
|
+
id: string;
|
|
15
|
+
kind: string;
|
|
16
|
+
label: string;
|
|
17
|
+
enabled: boolean;
|
|
18
|
+
muted: boolean;
|
|
19
|
+
readyState: MediaStreamTrackState;
|
|
20
|
+
}[];
|
|
21
|
+
};
|
|
8
22
|
export declare const getSdkSignature: (clientDetails: ClientDetails) => {
|
|
9
23
|
os?: import("../gen/video/sfu/models/models").OS;
|
|
10
24
|
browser?: import("../gen/video/sfu/models/models").Browser;
|
package/index.ts
CHANGED
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
import 'webrtc-adapter';
|
|
2
2
|
|
|
3
|
-
// side effect: we patch the mediaDevices APIs here
|
|
4
|
-
// so we can intercept invocations and collect statistics
|
|
5
|
-
import './src/stats/rtc/mediaDevices';
|
|
6
|
-
|
|
7
3
|
export * from './src/gen/coordinator';
|
|
8
4
|
export * from './src/coordinator/connection/types';
|
|
9
5
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stream-io/video-client",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.22.1",
|
|
4
4
|
"main": "dist/index.cjs.js",
|
|
5
5
|
"module": "dist/index.es.js",
|
|
6
6
|
"browser": "dist/index.browser.es.js",
|
|
@@ -40,19 +40,19 @@
|
|
|
40
40
|
"@openapitools/openapi-generator-cli": "^2.13.4",
|
|
41
41
|
"@rollup/plugin-replace": "^6.0.2",
|
|
42
42
|
"@rollup/plugin-typescript": "^12.1.2",
|
|
43
|
-
"@stream-io/audio-filters-web": "^0.
|
|
44
|
-
"@stream-io/node-sdk": "^0.4.
|
|
43
|
+
"@stream-io/audio-filters-web": "^0.3.0",
|
|
44
|
+
"@stream-io/node-sdk": "^0.4.24",
|
|
45
45
|
"@types/sdp-transform": "^2.4.9",
|
|
46
46
|
"@types/ua-parser-js": "^0.7.39",
|
|
47
|
-
"@vitest/coverage-v8": "^3.
|
|
48
|
-
"dotenv": "^16.
|
|
47
|
+
"@vitest/coverage-v8": "^3.1.3",
|
|
48
|
+
"dotenv": "^16.5.0",
|
|
49
49
|
"happy-dom": "^11.0.2",
|
|
50
50
|
"prettier": "^3.5.3",
|
|
51
51
|
"rimraf": "^6.0.1",
|
|
52
|
-
"rollup": "^4.
|
|
53
|
-
"typescript": "^5.8.
|
|
54
|
-
"vite": "^6.
|
|
55
|
-
"vitest": "^3.
|
|
56
|
-
"vitest-mock-extended": "^3.0
|
|
52
|
+
"rollup": "^4.40.2",
|
|
53
|
+
"typescript": "^5.8.3",
|
|
54
|
+
"vite": "^6.3.5",
|
|
55
|
+
"vitest": "^3.1.3",
|
|
56
|
+
"vitest-mock-extended": "^3.1.0"
|
|
57
57
|
}
|
|
58
58
|
}
|
package/src/Call.ts
CHANGED
|
@@ -120,8 +120,8 @@ import {
|
|
|
120
120
|
getSdkSignature,
|
|
121
121
|
SfuStatsReporter,
|
|
122
122
|
StatsReporter,
|
|
123
|
+
Tracer,
|
|
123
124
|
} from './stats';
|
|
124
|
-
import { tracer as mediaStatsTracer } from './stats/rtc/mediaDevices';
|
|
125
125
|
import { DynascaleManager } from './helpers/DynascaleManager';
|
|
126
126
|
import { PermissionsContext } from './permissions';
|
|
127
127
|
import { CallTypes } from './CallType';
|
|
@@ -220,6 +220,7 @@ export class Call {
|
|
|
220
220
|
* The permissions context of this call.
|
|
221
221
|
*/
|
|
222
222
|
readonly permissionsContext = new PermissionsContext();
|
|
223
|
+
readonly tracer = new Tracer(null);
|
|
223
224
|
readonly logger: Logger;
|
|
224
225
|
|
|
225
226
|
/**
|
|
@@ -590,6 +591,7 @@ export class Call {
|
|
|
590
591
|
this.statsReporter?.stop();
|
|
591
592
|
this.statsReporter = undefined;
|
|
592
593
|
|
|
594
|
+
this.sfuStatsReporter?.flush();
|
|
593
595
|
this.sfuStatsReporter?.stop();
|
|
594
596
|
this.sfuStatsReporter = undefined;
|
|
595
597
|
|
|
@@ -1012,7 +1014,7 @@ export class Call {
|
|
|
1012
1014
|
);
|
|
1013
1015
|
}
|
|
1014
1016
|
|
|
1015
|
-
if (performingRejoin) {
|
|
1017
|
+
if (performingRejoin && isWsHealthy) {
|
|
1016
1018
|
const strategy = WebsocketReconnectStrategy[this.reconnectStrategy];
|
|
1017
1019
|
await previousSfuClient?.leaveAndClose(
|
|
1018
1020
|
`Closing previous WS after reconnect with strategy: ${strategy}`,
|
|
@@ -1225,7 +1227,6 @@ export class Call {
|
|
|
1225
1227
|
});
|
|
1226
1228
|
}
|
|
1227
1229
|
|
|
1228
|
-
mediaStatsTracer.setEnabled(enableTracing);
|
|
1229
1230
|
this.statsReporter?.stop();
|
|
1230
1231
|
this.statsReporter = createStatsReporter({
|
|
1231
1232
|
subscriber: this.subscriber,
|
|
@@ -1234,6 +1235,7 @@ export class Call {
|
|
|
1234
1235
|
datacenter: sfuClient.edgeName,
|
|
1235
1236
|
});
|
|
1236
1237
|
|
|
1238
|
+
this.tracer.setEnabled(enableTracing);
|
|
1237
1239
|
this.sfuStatsReporter?.stop();
|
|
1238
1240
|
if (statsOptions?.reporting_interval_ms > 0) {
|
|
1239
1241
|
this.unifiedSessionId ??= sfuClient.sessionId;
|
|
@@ -1245,6 +1247,7 @@ export class Call {
|
|
|
1245
1247
|
microphone: this.microphone,
|
|
1246
1248
|
camera: this.camera,
|
|
1247
1249
|
state: this.state,
|
|
1250
|
+
tracer: this.tracer,
|
|
1248
1251
|
unifiedSessionId: this.unifiedSessionId,
|
|
1249
1252
|
});
|
|
1250
1253
|
this.sfuStatsReporter.start();
|
|
@@ -1336,16 +1339,12 @@ export class Call {
|
|
|
1336
1339
|
): Promise<void> => {
|
|
1337
1340
|
if (
|
|
1338
1341
|
this.state.callingState === CallingState.RECONNECTING ||
|
|
1342
|
+
this.state.callingState === CallingState.MIGRATING ||
|
|
1339
1343
|
this.state.callingState === CallingState.RECONNECTING_FAILED
|
|
1340
1344
|
)
|
|
1341
1345
|
return;
|
|
1342
1346
|
|
|
1343
1347
|
return withoutConcurrency(this.reconnectConcurrencyTag, async () => {
|
|
1344
|
-
this.logger(
|
|
1345
|
-
'info',
|
|
1346
|
-
`[Reconnect] Reconnecting with strategy ${WebsocketReconnectStrategy[strategy]}`,
|
|
1347
|
-
);
|
|
1348
|
-
|
|
1349
1348
|
const reconnectStartTime = Date.now();
|
|
1350
1349
|
this.reconnectStrategy = strategy;
|
|
1351
1350
|
this.reconnectReason = reason;
|
|
@@ -1372,6 +1371,12 @@ export class Call {
|
|
|
1372
1371
|
try {
|
|
1373
1372
|
// wait until the network is available
|
|
1374
1373
|
await this.networkAvailableTask?.promise;
|
|
1374
|
+
|
|
1375
|
+
this.logger(
|
|
1376
|
+
'info',
|
|
1377
|
+
`[Reconnect] Reconnecting with strategy ${WebsocketReconnectStrategy[this.reconnectStrategy]}`,
|
|
1378
|
+
);
|
|
1379
|
+
|
|
1375
1380
|
switch (this.reconnectStrategy) {
|
|
1376
1381
|
case WebsocketReconnectStrategy.UNSPECIFIED:
|
|
1377
1382
|
case WebsocketReconnectStrategy.DISCONNECT:
|
|
@@ -1426,6 +1431,7 @@ export class Call {
|
|
|
1426
1431
|
this.state.callingState !== CallingState.RECONNECTING_FAILED &&
|
|
1427
1432
|
this.state.callingState !== CallingState.LEFT
|
|
1428
1433
|
);
|
|
1434
|
+
this.logger('info', '[Reconnect] Reconnection flow finished');
|
|
1429
1435
|
});
|
|
1430
1436
|
};
|
|
1431
1437
|
|
|
@@ -1548,6 +1554,7 @@ export class Call {
|
|
|
1548
1554
|
const unregisterNetworkChanged = this.streamClient.on(
|
|
1549
1555
|
'network.changed',
|
|
1550
1556
|
(e) => {
|
|
1557
|
+
this.tracer.trace('network.changed', e);
|
|
1551
1558
|
if (!e.online) {
|
|
1552
1559
|
this.logger('debug', '[Reconnect] Going offline');
|
|
1553
1560
|
if (!this.hasJoinedOnce) return;
|
|
@@ -33,30 +33,34 @@ export class CameraManager extends InputMediaDeviceManager<CameraManagerState> {
|
|
|
33
33
|
* @param direction the direction of the camera to select.
|
|
34
34
|
*/
|
|
35
35
|
async selectDirection(direction: Exclude<CameraDirection, undefined>) {
|
|
36
|
-
if (this.isDirectionSupportedByDevice()) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
36
|
+
if (!this.isDirectionSupportedByDevice()) {
|
|
37
|
+
this.logger('warn', 'Setting direction is not supported on this device');
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// providing both device id and direction doesn't work, so we deselect the device
|
|
42
|
+
this.state.setDirection(direction);
|
|
43
|
+
this.state.setDevice(undefined);
|
|
44
|
+
|
|
45
|
+
if (isReactNative()) {
|
|
46
|
+
const videoTrack = this.getTracks()[0] as MediaStreamTrack | undefined;
|
|
47
|
+
await videoTrack?.applyConstraints({
|
|
48
|
+
facingMode: direction === 'front' ? 'user' : 'environment',
|
|
49
|
+
});
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
this.getTracks().forEach((track) => track.stop());
|
|
54
|
+
try {
|
|
55
|
+
await this.unmuteStream();
|
|
56
|
+
} catch (error) {
|
|
57
|
+
if (error instanceof Error && error.name === 'NotReadableError') {
|
|
58
|
+
// the camera is already in use, and the device can't use it unless it's released.
|
|
59
|
+
// in that case, we need to stop the stream and start it again.
|
|
60
|
+
await this.muteStream();
|
|
56
61
|
await this.unmuteStream();
|
|
57
62
|
}
|
|
58
|
-
|
|
59
|
-
this.logger('warn', 'Camera direction ignored for desktop devices');
|
|
63
|
+
throw error;
|
|
60
64
|
}
|
|
61
65
|
}
|
|
62
66
|
|
|
@@ -158,6 +162,6 @@ export class CameraManager extends InputMediaDeviceManager<CameraManagerState> {
|
|
|
158
162
|
constraints.facingMode =
|
|
159
163
|
this.state.direction === 'front' ? 'user' : 'environment';
|
|
160
164
|
}
|
|
161
|
-
return getVideoStream(constraints);
|
|
165
|
+
return getVideoStream(constraints, this.call.tracer);
|
|
162
166
|
}
|
|
163
167
|
}
|
|
@@ -30,6 +30,10 @@ export abstract class InputMediaDeviceManager<
|
|
|
30
30
|
stopOnLeave = true;
|
|
31
31
|
logger: Logger;
|
|
32
32
|
|
|
33
|
+
state: T;
|
|
34
|
+
|
|
35
|
+
protected readonly call: Call;
|
|
36
|
+
protected readonly trackType: TrackType;
|
|
33
37
|
protected subscriptions: Function[] = [];
|
|
34
38
|
private isTrackStoppedDueToTrackEnd = false;
|
|
35
39
|
private filters: MediaStreamFilterEntry[] = [];
|
|
@@ -38,11 +42,10 @@ export abstract class InputMediaDeviceManager<
|
|
|
38
42
|
'filterRegistrationConcurrencyTag',
|
|
39
43
|
);
|
|
40
44
|
|
|
41
|
-
protected constructor(
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
) {
|
|
45
|
+
protected constructor(call: Call, state: T, trackType: TrackType) {
|
|
46
|
+
this.call = call;
|
|
47
|
+
this.state = state;
|
|
48
|
+
this.trackType = trackType;
|
|
46
49
|
this.logger = getLogger([`${TrackType[trackType].toLowerCase()} manager`]);
|
|
47
50
|
if (
|
|
48
51
|
deviceIds$ &&
|
|
@@ -235,7 +235,7 @@ export class MicrophoneManager extends InputMediaDeviceManager<MicrophoneManager
|
|
|
235
235
|
protected getStream(
|
|
236
236
|
constraints: MediaTrackConstraints,
|
|
237
237
|
): Promise<MediaStream> {
|
|
238
|
-
return getAudioStream(constraints);
|
|
238
|
+
return getAudioStream(constraints, this.call.tracer);
|
|
239
239
|
}
|
|
240
240
|
|
|
241
241
|
private async startSpeakingWhileMutedDetection(deviceId?: string) {
|
|
@@ -76,7 +76,7 @@ export class ScreenShareManager extends InputMediaDeviceManager<
|
|
|
76
76
|
if (!this.state.audioEnabled) {
|
|
77
77
|
constraints.audio = false;
|
|
78
78
|
}
|
|
79
|
-
return getScreenShareStream(constraints);
|
|
79
|
+
return getScreenShareStream(constraints, this.call.tracer);
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
protected async stopPublishStream(): Promise<void> {
|
|
@@ -90,6 +90,6 @@ export class ScreenShareManager extends InputMediaDeviceManager<
|
|
|
90
90
|
* Overrides the default `select` method to throw an error.
|
|
91
91
|
*/
|
|
92
92
|
async select(): Promise<void> {
|
|
93
|
-
throw new Error('
|
|
93
|
+
throw new Error('Not supported');
|
|
94
94
|
}
|
|
95
95
|
}
|
|
@@ -5,12 +5,13 @@ import { SpeakerState } from './SpeakerState';
|
|
|
5
5
|
import { deviceIds$, getAudioOutputDevices } from './devices';
|
|
6
6
|
|
|
7
7
|
export class SpeakerManager {
|
|
8
|
-
|
|
8
|
+
readonly state: SpeakerState;
|
|
9
9
|
private subscriptions: Subscription[] = [];
|
|
10
10
|
private readonly call: Call;
|
|
11
11
|
|
|
12
12
|
constructor(call: Call) {
|
|
13
13
|
this.call = call;
|
|
14
|
+
this.state = new SpeakerState(call.tracer);
|
|
14
15
|
if (deviceIds$ && !isReactNative()) {
|
|
15
16
|
this.subscriptions.push(
|
|
16
17
|
combineLatest([deviceIds$!, this.state.selectedDevice$]).subscribe(
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { BehaviorSubject, distinctUntilChanged, Observable } from 'rxjs';
|
|
2
2
|
import { RxUtils } from '../store';
|
|
3
3
|
import { checkIfAudioOutputChangeSupported } from './devices';
|
|
4
|
-
import {
|
|
4
|
+
import { Tracer } from '../stats';
|
|
5
5
|
|
|
6
6
|
export class SpeakerState {
|
|
7
7
|
protected selectedDeviceSubject = new BehaviorSubject<string>('');
|
|
@@ -25,7 +25,10 @@ export class SpeakerState {
|
|
|
25
25
|
*/
|
|
26
26
|
volume$: Observable<number>;
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
private tracer: Tracer;
|
|
29
|
+
|
|
30
|
+
constructor(tracer: Tracer) {
|
|
31
|
+
this.tracer = tracer;
|
|
29
32
|
this.selectedDevice$ = this.selectedDeviceSubject
|
|
30
33
|
.asObservable()
|
|
31
34
|
.pipe(distinctUntilChanged());
|
|
@@ -58,7 +61,7 @@ export class SpeakerState {
|
|
|
58
61
|
*/
|
|
59
62
|
setDevice(deviceId: string) {
|
|
60
63
|
RxUtils.setCurrentValue(this.selectedDeviceSubject, deviceId);
|
|
61
|
-
|
|
64
|
+
this.tracer.trace('navigator.mediaDevices.setSinkId', deviceId);
|
|
62
65
|
}
|
|
63
66
|
|
|
64
67
|
/**
|
|
@@ -14,6 +14,7 @@ import { TrackType } from '../../gen/video/sfu/models/models';
|
|
|
14
14
|
import { CameraManager } from '../CameraManager';
|
|
15
15
|
import { of } from 'rxjs';
|
|
16
16
|
import { PermissionsContext } from '../../permissions';
|
|
17
|
+
import { Tracer } from '../../stats';
|
|
17
18
|
|
|
18
19
|
const getVideoStream = vi.hoisted(() =>
|
|
19
20
|
vi.fn(() => Promise.resolve(mockVideoStream())),
|
|
@@ -78,11 +79,14 @@ describe('CameraManager', () => {
|
|
|
78
79
|
it('get stream', async () => {
|
|
79
80
|
await manager.enable();
|
|
80
81
|
|
|
81
|
-
expect(getVideoStream).toHaveBeenCalledWith(
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
82
|
+
expect(getVideoStream).toHaveBeenCalledWith(
|
|
83
|
+
{
|
|
84
|
+
deviceId: undefined,
|
|
85
|
+
width: 1280,
|
|
86
|
+
height: 720,
|
|
87
|
+
},
|
|
88
|
+
expect.any(Tracer),
|
|
89
|
+
);
|
|
86
90
|
});
|
|
87
91
|
|
|
88
92
|
it('should get device id from stream', async () => {
|
|
@@ -133,29 +137,38 @@ describe('CameraManager', () => {
|
|
|
133
137
|
|
|
134
138
|
await manager.enable();
|
|
135
139
|
|
|
136
|
-
expect(getVideoStream).toHaveBeenCalledWith(
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
140
|
+
expect(getVideoStream).toHaveBeenCalledWith(
|
|
141
|
+
{
|
|
142
|
+
deviceId: undefined,
|
|
143
|
+
width: 1280,
|
|
144
|
+
height: 720,
|
|
145
|
+
},
|
|
146
|
+
expect.any(Tracer),
|
|
147
|
+
);
|
|
141
148
|
|
|
142
149
|
await manager.selectDirection('front');
|
|
143
150
|
|
|
144
|
-
expect(getVideoStream).toHaveBeenCalledWith(
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
151
|
+
expect(getVideoStream).toHaveBeenCalledWith(
|
|
152
|
+
{
|
|
153
|
+
deviceId: undefined,
|
|
154
|
+
width: 1280,
|
|
155
|
+
height: 720,
|
|
156
|
+
facingMode: 'user',
|
|
157
|
+
},
|
|
158
|
+
expect.any(Tracer),
|
|
159
|
+
);
|
|
150
160
|
|
|
151
161
|
await manager.selectDirection('back');
|
|
152
162
|
|
|
153
|
-
expect(getVideoStream).toHaveBeenCalledWith(
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
163
|
+
expect(getVideoStream).toHaveBeenCalledWith(
|
|
164
|
+
{
|
|
165
|
+
deviceId: undefined,
|
|
166
|
+
facingMode: 'environment',
|
|
167
|
+
width: 1280,
|
|
168
|
+
height: 720,
|
|
169
|
+
},
|
|
170
|
+
expect.any(Tracer),
|
|
171
|
+
);
|
|
159
172
|
});
|
|
160
173
|
|
|
161
174
|
it(`shouldn't set deviceId and facingMode at the same time`, async () => {
|
|
@@ -163,11 +176,14 @@ describe('CameraManager', () => {
|
|
|
163
176
|
|
|
164
177
|
await manager.flip();
|
|
165
178
|
|
|
166
|
-
expect(getVideoStream).toHaveBeenCalledWith(
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
179
|
+
expect(getVideoStream).toHaveBeenCalledWith(
|
|
180
|
+
{
|
|
181
|
+
facingMode: 'environment',
|
|
182
|
+
width: 1280,
|
|
183
|
+
height: 720,
|
|
184
|
+
},
|
|
185
|
+
expect.any(Tracer),
|
|
186
|
+
);
|
|
171
187
|
|
|
172
188
|
const deviceId = mockVideoDevices[1].deviceId;
|
|
173
189
|
await manager.select(deviceId);
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
SoundStateChangeHandler,
|
|
25
25
|
} from '../../helpers/sound-detector';
|
|
26
26
|
import { PermissionsContext } from '../../permissions';
|
|
27
|
+
import { Tracer } from '../../stats';
|
|
27
28
|
|
|
28
29
|
vi.mock('../devices.ts', () => {
|
|
29
30
|
console.log('MOCKING devices API');
|
|
@@ -92,9 +93,10 @@ describe('MicrophoneManager', () => {
|
|
|
92
93
|
it('get stream', async () => {
|
|
93
94
|
await manager.enable();
|
|
94
95
|
|
|
95
|
-
expect(getAudioStream).toHaveBeenCalledWith(
|
|
96
|
-
deviceId: undefined,
|
|
97
|
-
|
|
96
|
+
expect(getAudioStream).toHaveBeenCalledWith(
|
|
97
|
+
{ deviceId: undefined },
|
|
98
|
+
expect.any(Tracer),
|
|
99
|
+
);
|
|
98
100
|
});
|
|
99
101
|
|
|
100
102
|
it('should get device id from stream', async () => {
|