@stream-io/video-client 1.21.0 → 1.22.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 +13 -0
- package/dist/index.browser.es.js +142 -133
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +142 -133
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.es.js +142 -133
- 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/rtc/BasePeerConnection.d.ts +1 -0
- package/dist/src/stats/SfuStatsReporter.d.ts +4 -1
- package/dist/src/stats/utils.d.ts +14 -0
- package/index.ts +0 -4
- package/package.json +10 -10
- package/src/Call.ts +5 -2
- 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/rtc/BasePeerConnection.ts +8 -3
- package/src/rtc/Publisher.ts +1 -1
- package/src/stats/SfuStatsReporter.ts +9 -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/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).
|
|
4
4
|
|
|
5
|
+
## [1.22.0](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.21.0...@stream-io/video-client-1.22.0) (2025-05-08)
|
|
6
|
+
|
|
7
|
+
### Features
|
|
8
|
+
|
|
9
|
+
- Expo 53 Swift Config Plugin and React Native 0.79 compatibility ([#1714](https://github.com/GetStream/stream-video-js/issues/1714)) ([380331e](https://github.com/GetStream/stream-video-js/commit/380331e11fd6182c3111413aa25689a669dd3c9c))
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
- graceful handling of LIVE_ENDED CallEnded reason ([#1783](https://github.com/GetStream/stream-video-js/issues/1783)) ([ff54390](https://github.com/GetStream/stream-video-js/commit/ff54390099e10c550b8bbac42658080a65007a30))
|
|
14
|
+
- isolate mediaDevices traces ([#1779](https://github.com/GetStream/stream-video-js/issues/1779)) ([d8623f0](https://github.com/GetStream/stream-video-js/commit/d8623f0b06a6229bff96ea01dd1f2b851b7d3558)), closes [#1765](https://github.com/GetStream/stream-video-js/issues/1765)
|
|
15
|
+
- make camera.flip() work more reliably with older devices ([#1781](https://github.com/GetStream/stream-video-js/issues/1781)) ([9dfbc55](https://github.com/GetStream/stream-video-js/commit/9dfbc556155c1ae9b528b50b140313c4decb024f)), closes [#1679](https://github.com/GetStream/stream-video-js/issues/1679)
|
|
16
|
+
- use scoped locking for PeerConnection events ([#1785](https://github.com/GetStream/stream-video-js/issues/1785)) ([b0f93e8](https://github.com/GetStream/stream-video-js/commit/b0f93e83e70520b527efd94e9192ac7dca031864))
|
|
17
|
+
|
|
5
18
|
## [1.21.0](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.20.2...@stream-io/video-client-1.21.0) (2025-05-02)
|
|
6
19
|
|
|
7
20
|
### Features
|
package/dist/index.browser.es.js
CHANGED
|
@@ -8,75 +8,6 @@ import { ReplaySubject, combineLatest, BehaviorSubject, map, shareReplay, distin
|
|
|
8
8
|
import { UAParser } from 'ua-parser-js';
|
|
9
9
|
import { parse } from 'sdp-transform';
|
|
10
10
|
|
|
11
|
-
class Tracer {
|
|
12
|
-
constructor(id) {
|
|
13
|
-
this.buffer = [];
|
|
14
|
-
this.enabled = true;
|
|
15
|
-
this.setEnabled = (enabled) => {
|
|
16
|
-
if (this.enabled === enabled)
|
|
17
|
-
return;
|
|
18
|
-
this.enabled = enabled;
|
|
19
|
-
this.buffer = [];
|
|
20
|
-
};
|
|
21
|
-
this.trace = (tag, data) => {
|
|
22
|
-
if (!this.enabled)
|
|
23
|
-
return;
|
|
24
|
-
this.buffer.push([tag, this.id, data, Date.now()]);
|
|
25
|
-
};
|
|
26
|
-
this.take = () => {
|
|
27
|
-
const snapshot = this.buffer;
|
|
28
|
-
this.buffer = [];
|
|
29
|
-
return {
|
|
30
|
-
snapshot,
|
|
31
|
-
rollback: () => {
|
|
32
|
-
this.buffer.unshift(...snapshot);
|
|
33
|
-
},
|
|
34
|
-
};
|
|
35
|
-
};
|
|
36
|
-
this.dispose = () => {
|
|
37
|
-
this.buffer = [];
|
|
38
|
-
};
|
|
39
|
-
this.id = id;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const tracer = new Tracer(null);
|
|
44
|
-
if (typeof navigator !== 'undefined' &&
|
|
45
|
-
typeof navigator.mediaDevices !== 'undefined') {
|
|
46
|
-
const dumpStream = (stream) => ({
|
|
47
|
-
id: stream.id,
|
|
48
|
-
tracks: stream.getTracks().map((track) => ({
|
|
49
|
-
id: track.id,
|
|
50
|
-
kind: track.kind,
|
|
51
|
-
label: track.label,
|
|
52
|
-
enabled: track.enabled,
|
|
53
|
-
muted: track.muted,
|
|
54
|
-
readyState: track.readyState,
|
|
55
|
-
})),
|
|
56
|
-
});
|
|
57
|
-
const trace = tracer.trace;
|
|
58
|
-
const target = navigator.mediaDevices;
|
|
59
|
-
for (const method of ['getUserMedia', 'getDisplayMedia']) {
|
|
60
|
-
const original = target[method];
|
|
61
|
-
if (!original)
|
|
62
|
-
continue;
|
|
63
|
-
let mark = 0;
|
|
64
|
-
target[method] = async function tracedMethod(constraints) {
|
|
65
|
-
const tag = `navigator.mediaDevices.${method}.${mark++}`;
|
|
66
|
-
trace(tag, constraints);
|
|
67
|
-
try {
|
|
68
|
-
const stream = await original.call(target, constraints);
|
|
69
|
-
trace(`${tag}.OnSuccess`, dumpStream(stream));
|
|
70
|
-
return stream;
|
|
71
|
-
}
|
|
72
|
-
catch (err) {
|
|
73
|
-
trace(`${tag}.OnFailure`, err.name);
|
|
74
|
-
throw err;
|
|
75
|
-
}
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
11
|
/* tslint:disable */
|
|
81
12
|
/**
|
|
82
13
|
* @export
|
|
@@ -5413,6 +5344,20 @@ const flatten = (report) => {
|
|
|
5413
5344
|
});
|
|
5414
5345
|
return stats;
|
|
5415
5346
|
};
|
|
5347
|
+
/**
|
|
5348
|
+
* Dump the provided MediaStream into a JSON object.
|
|
5349
|
+
*/
|
|
5350
|
+
const dumpStream = (stream) => ({
|
|
5351
|
+
id: stream.id,
|
|
5352
|
+
tracks: stream.getTracks().map((track) => ({
|
|
5353
|
+
id: track.id,
|
|
5354
|
+
kind: track.kind,
|
|
5355
|
+
label: track.label,
|
|
5356
|
+
enabled: track.enabled,
|
|
5357
|
+
muted: track.muted,
|
|
5358
|
+
readyState: track.readyState,
|
|
5359
|
+
})),
|
|
5360
|
+
});
|
|
5416
5361
|
const getSdkSignature = (clientDetails) => {
|
|
5417
5362
|
const { sdk, ...platform } = clientDetails;
|
|
5418
5363
|
const sdkName = getSdkName(sdk);
|
|
@@ -5701,7 +5646,7 @@ const aggregate = (stats) => {
|
|
|
5701
5646
|
return report;
|
|
5702
5647
|
};
|
|
5703
5648
|
|
|
5704
|
-
const version = "1.
|
|
5649
|
+
const version = "1.22.0";
|
|
5705
5650
|
const [major, minor, patch] = version.split('.');
|
|
5706
5651
|
let sdkInfo = {
|
|
5707
5652
|
type: SdkType.PLAIN_JAVASCRIPT,
|
|
@@ -5838,7 +5783,7 @@ const getClientDetails = async () => {
|
|
|
5838
5783
|
};
|
|
5839
5784
|
|
|
5840
5785
|
class SfuStatsReporter {
|
|
5841
|
-
constructor(sfuClient, { options, clientDetails, subscriber, publisher, microphone, camera, state, unifiedSessionId, }) {
|
|
5786
|
+
constructor(sfuClient, { options, clientDetails, subscriber, publisher, microphone, camera, state, tracer, unifiedSessionId, }) {
|
|
5842
5787
|
this.logger = getLogger(['SfuStatsReporter']);
|
|
5843
5788
|
this.inputDevices = new Map();
|
|
5844
5789
|
this.observeDevice = (device, kind) => {
|
|
@@ -5904,10 +5849,10 @@ class SfuStatsReporter {
|
|
|
5904
5849
|
}
|
|
5905
5850
|
const subscriberTrace = this.subscriber.tracer?.take();
|
|
5906
5851
|
const publisherTrace = this.publisher?.tracer?.take();
|
|
5907
|
-
const
|
|
5852
|
+
const tracer = this.tracer.take();
|
|
5908
5853
|
const sfuTrace = this.sfuClient.getTrace();
|
|
5909
5854
|
const traces = [
|
|
5910
|
-
...
|
|
5855
|
+
...tracer.snapshot,
|
|
5911
5856
|
...(sfuTrace?.snapshot ?? []),
|
|
5912
5857
|
...(publisherTrace?.snapshot ?? []),
|
|
5913
5858
|
...(subscriberTrace?.snapshot ?? []),
|
|
@@ -5936,7 +5881,7 @@ class SfuStatsReporter {
|
|
|
5936
5881
|
catch (err) {
|
|
5937
5882
|
publisherTrace?.rollback();
|
|
5938
5883
|
subscriberTrace?.rollback();
|
|
5939
|
-
|
|
5884
|
+
tracer.rollback();
|
|
5940
5885
|
sfuTrace?.rollback();
|
|
5941
5886
|
throw err;
|
|
5942
5887
|
}
|
|
@@ -5979,6 +5924,7 @@ class SfuStatsReporter {
|
|
|
5979
5924
|
this.microphone = microphone;
|
|
5980
5925
|
this.camera = camera;
|
|
5981
5926
|
this.state = state;
|
|
5927
|
+
this.tracer = tracer;
|
|
5982
5928
|
this.unifiedSessionId = unifiedSessionId;
|
|
5983
5929
|
const { sdk, browser } = clientDetails;
|
|
5984
5930
|
this.sdkName = getSdkName(sdk);
|
|
@@ -6264,6 +6210,38 @@ const getCodecFromStats = (stats, codecId) => {
|
|
|
6264
6210
|
});
|
|
6265
6211
|
};
|
|
6266
6212
|
|
|
6213
|
+
class Tracer {
|
|
6214
|
+
constructor(id) {
|
|
6215
|
+
this.buffer = [];
|
|
6216
|
+
this.enabled = true;
|
|
6217
|
+
this.setEnabled = (enabled) => {
|
|
6218
|
+
if (this.enabled === enabled)
|
|
6219
|
+
return;
|
|
6220
|
+
this.enabled = enabled;
|
|
6221
|
+
this.buffer = [];
|
|
6222
|
+
};
|
|
6223
|
+
this.trace = (tag, data) => {
|
|
6224
|
+
if (!this.enabled)
|
|
6225
|
+
return;
|
|
6226
|
+
this.buffer.push([tag, this.id, data, Date.now()]);
|
|
6227
|
+
};
|
|
6228
|
+
this.take = () => {
|
|
6229
|
+
const snapshot = this.buffer;
|
|
6230
|
+
this.buffer = [];
|
|
6231
|
+
return {
|
|
6232
|
+
snapshot,
|
|
6233
|
+
rollback: () => {
|
|
6234
|
+
this.buffer.unshift(...snapshot);
|
|
6235
|
+
},
|
|
6236
|
+
};
|
|
6237
|
+
};
|
|
6238
|
+
this.dispose = () => {
|
|
6239
|
+
this.buffer = [];
|
|
6240
|
+
};
|
|
6241
|
+
this.id = id;
|
|
6242
|
+
}
|
|
6243
|
+
}
|
|
6244
|
+
|
|
6267
6245
|
/**
|
|
6268
6246
|
* A base class for the `Publisher` and `Subscriber` classes.
|
|
6269
6247
|
* @internal
|
|
@@ -6277,13 +6255,15 @@ class BasePeerConnection {
|
|
|
6277
6255
|
this.isDisposed = false;
|
|
6278
6256
|
this.trackIdToTrackType = new Map();
|
|
6279
6257
|
this.subscriptions = [];
|
|
6258
|
+
this.lock = Math.random().toString(36).slice(2);
|
|
6280
6259
|
/**
|
|
6281
6260
|
* Handles events synchronously.
|
|
6282
6261
|
* Consecutive events are queued and executed one after the other.
|
|
6283
6262
|
*/
|
|
6284
6263
|
this.on = (event, fn) => {
|
|
6285
6264
|
this.subscriptions.push(this.dispatcher.on(event, (e) => {
|
|
6286
|
-
|
|
6265
|
+
const lockKey = `pc.${this.lock}.${event}`;
|
|
6266
|
+
withoutConcurrency(lockKey, async () => fn(e)).catch((err) => {
|
|
6287
6267
|
if (this.isDisposed)
|
|
6288
6268
|
return;
|
|
6289
6269
|
this.logger('warn', `Error handling ${event}`, err);
|
|
@@ -6442,9 +6422,12 @@ class BasePeerConnection {
|
|
|
6442
6422
|
this.pc.addEventListener('connectionstatechange', this.onConnectionStateChange);
|
|
6443
6423
|
this.stats = new StatsTracer(this.pc, peerType, this.trackIdToTrackType);
|
|
6444
6424
|
if (enableTracing) {
|
|
6445
|
-
const tag = `${logTag}-${peerType === PeerType.SUBSCRIBER ? 'sub' : 'pub'}
|
|
6425
|
+
const tag = `${logTag}-${peerType === PeerType.SUBSCRIBER ? 'sub' : 'pub'}`;
|
|
6446
6426
|
this.tracer = new Tracer(tag);
|
|
6447
|
-
this.tracer.trace('create',
|
|
6427
|
+
this.tracer.trace('create', {
|
|
6428
|
+
url: sfuClient.edgeName,
|
|
6429
|
+
...connectionConfig,
|
|
6430
|
+
});
|
|
6448
6431
|
traceRTCPeerConnection(this.pc, this.tracer.trace);
|
|
6449
6432
|
}
|
|
6450
6433
|
}
|
|
@@ -6999,7 +6982,7 @@ class Publisher extends BasePeerConnection {
|
|
|
6999
6982
|
* @param options the optional offer options to use.
|
|
7000
6983
|
*/
|
|
7001
6984
|
this.negotiate = async (options) => {
|
|
7002
|
-
return withoutConcurrency(
|
|
6985
|
+
return withoutConcurrency(`publisher.negotiate.${this.lock}`, async () => {
|
|
7003
6986
|
const offer = await this.pc.createOffer(options);
|
|
7004
6987
|
const tracks = this.getAnnouncedTracks(offer.sdp);
|
|
7005
6988
|
if (!tracks.length)
|
|
@@ -7907,6 +7890,13 @@ const watchSfuCallEnded = (call) => {
|
|
|
7907
7890
|
if (call.state.callingState === CallingState.LEFT)
|
|
7908
7891
|
return;
|
|
7909
7892
|
try {
|
|
7893
|
+
if (e.reason === CallEndedReason.LIVE_ENDED) {
|
|
7894
|
+
call.state.setBackstage(true);
|
|
7895
|
+
// don't leave the call if the user has permission to join backstage
|
|
7896
|
+
const { hasPermission } = call.permissionsContext;
|
|
7897
|
+
if (hasPermission(OwnCapability.JOIN_BACKSTAGE))
|
|
7898
|
+
return;
|
|
7899
|
+
}
|
|
7910
7900
|
// `call.ended` event arrived after the call is already left
|
|
7911
7901
|
// and all event handlers are detached. We need to manually
|
|
7912
7902
|
// update the call state to reflect the call has ended.
|
|
@@ -9119,15 +9109,25 @@ const getVideoDevices = lazy(() => {
|
|
|
9119
9109
|
const getAudioOutputDevices = lazy(() => {
|
|
9120
9110
|
return merge(getDeviceChangeObserver(), getAudioBrowserPermission().asObservable()).pipe(startWith(undefined), concatMap(() => getDevices(getAudioBrowserPermission(), 'audiooutput')), shareReplay(1));
|
|
9121
9111
|
});
|
|
9122
|
-
|
|
9123
|
-
|
|
9124
|
-
|
|
9125
|
-
|
|
9126
|
-
|
|
9127
|
-
|
|
9128
|
-
|
|
9112
|
+
let getUserMediaExecId = 0;
|
|
9113
|
+
const getStream = async (constraints, tracer) => {
|
|
9114
|
+
const tag = `navigator.mediaDevices.getUserMedia.${getUserMediaExecId++}.`;
|
|
9115
|
+
try {
|
|
9116
|
+
tracer?.trace(tag, constraints);
|
|
9117
|
+
const stream = await navigator.mediaDevices.getUserMedia(constraints);
|
|
9118
|
+
tracer?.trace(`${tag}OnSuccess`, dumpStream(stream));
|
|
9119
|
+
if (isFirefox()) {
|
|
9120
|
+
// When enumerating devices, Firefox will hide device labels unless there's been
|
|
9121
|
+
// an active user media stream on the page. So we force device list updates after
|
|
9122
|
+
// every successful getUserMedia call.
|
|
9123
|
+
navigator.mediaDevices.dispatchEvent(new Event('devicechange'));
|
|
9124
|
+
}
|
|
9125
|
+
return stream;
|
|
9126
|
+
}
|
|
9127
|
+
catch (error) {
|
|
9128
|
+
tracer?.trace(`${tag}OnFailure`, error.name);
|
|
9129
|
+
throw error;
|
|
9129
9130
|
}
|
|
9130
|
-
return stream;
|
|
9131
9131
|
};
|
|
9132
9132
|
function isNotFoundOrOverconstrainedError(error) {
|
|
9133
9133
|
if (!error || typeof error !== 'object') {
|
|
@@ -9151,11 +9151,11 @@ function isNotFoundOrOverconstrainedError(error) {
|
|
|
9151
9151
|
* Returns an audio media stream that fulfills the given constraints.
|
|
9152
9152
|
* If no constraints are provided, it uses the browser's default ones.
|
|
9153
9153
|
*
|
|
9154
|
-
* @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.
|
|
9155
9154
|
* @param trackConstraints the constraints to use when requesting the stream.
|
|
9156
|
-
* @
|
|
9155
|
+
* @param tracer the tracer to use for tracing the stream creation.
|
|
9156
|
+
* @returns a new `MediaStream` fulfilling the given constraints.
|
|
9157
9157
|
*/
|
|
9158
|
-
const getAudioStream = async (trackConstraints) => {
|
|
9158
|
+
const getAudioStream = async (trackConstraints, tracer) => {
|
|
9159
9159
|
const constraints = {
|
|
9160
9160
|
audio: {
|
|
9161
9161
|
...audioDeviceConstraints.audio,
|
|
@@ -9167,7 +9167,7 @@ const getAudioStream = async (trackConstraints) => {
|
|
|
9167
9167
|
throwOnNotAllowed: true,
|
|
9168
9168
|
forcePrompt: true,
|
|
9169
9169
|
});
|
|
9170
|
-
return await getStream(constraints);
|
|
9170
|
+
return await getStream(constraints, tracer);
|
|
9171
9171
|
}
|
|
9172
9172
|
catch (error) {
|
|
9173
9173
|
if (isNotFoundOrOverconstrainedError(error) && trackConstraints?.deviceId) {
|
|
@@ -9187,11 +9187,11 @@ const getAudioStream = async (trackConstraints) => {
|
|
|
9187
9187
|
* Returns a video media stream that fulfills the given constraints.
|
|
9188
9188
|
* If no constraints are provided, it uses the browser's default ones.
|
|
9189
9189
|
*
|
|
9190
|
-
* @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.
|
|
9191
9190
|
* @param trackConstraints the constraints to use when requesting the stream.
|
|
9191
|
+
* @param tracer the tracer to use for tracing the stream creation.
|
|
9192
9192
|
* @returns a new `MediaStream` fulfilling the given constraints.
|
|
9193
9193
|
*/
|
|
9194
|
-
const getVideoStream = async (trackConstraints) => {
|
|
9194
|
+
const getVideoStream = async (trackConstraints, tracer) => {
|
|
9195
9195
|
const constraints = {
|
|
9196
9196
|
video: {
|
|
9197
9197
|
...videoDeviceConstraints.video,
|
|
@@ -9203,7 +9203,7 @@ const getVideoStream = async (trackConstraints) => {
|
|
|
9203
9203
|
throwOnNotAllowed: true,
|
|
9204
9204
|
forcePrompt: true,
|
|
9205
9205
|
});
|
|
9206
|
-
return await getStream(constraints);
|
|
9206
|
+
return await getStream(constraints, tracer);
|
|
9207
9207
|
}
|
|
9208
9208
|
catch (error) {
|
|
9209
9209
|
if (isNotFoundOrOverconstrainedError(error) && trackConstraints?.deviceId) {
|
|
@@ -9219,19 +9219,21 @@ const getVideoStream = async (trackConstraints) => {
|
|
|
9219
9219
|
throw error;
|
|
9220
9220
|
}
|
|
9221
9221
|
};
|
|
9222
|
+
let getDisplayMediaExecId = 0;
|
|
9222
9223
|
/**
|
|
9223
9224
|
* Prompts the user for a permission to share a screen.
|
|
9224
9225
|
* If the user grants the permission, a screen sharing stream is returned. Throws otherwise.
|
|
9225
9226
|
*
|
|
9226
9227
|
* The callers of this API are responsible to handle the possible errors.
|
|
9227
9228
|
*
|
|
9228
|
-
* @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.
|
|
9229
|
-
*
|
|
9230
9229
|
* @param options any additional options to pass to the [`getDisplayMedia`](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getDisplayMedia) API.
|
|
9230
|
+
* @param tracer the tracer to use for tracing the stream creation.
|
|
9231
9231
|
*/
|
|
9232
|
-
const getScreenShareStream = async (options) => {
|
|
9232
|
+
const getScreenShareStream = async (options, tracer) => {
|
|
9233
|
+
const tag = `navigator.mediaDevices.getDisplayMedia.${getDisplayMediaExecId++}.`;
|
|
9233
9234
|
try {
|
|
9234
|
-
|
|
9235
|
+
tracer?.trace(tag, options);
|
|
9236
|
+
const stream = await navigator.mediaDevices.getDisplayMedia({
|
|
9235
9237
|
video: true,
|
|
9236
9238
|
audio: {
|
|
9237
9239
|
channelCount: {
|
|
@@ -9245,8 +9247,11 @@ const getScreenShareStream = async (options) => {
|
|
|
9245
9247
|
systemAudio: 'include',
|
|
9246
9248
|
...options,
|
|
9247
9249
|
});
|
|
9250
|
+
tracer?.trace(`${tag}OnSuccess`, dumpStream(stream));
|
|
9251
|
+
return stream;
|
|
9248
9252
|
}
|
|
9249
9253
|
catch (e) {
|
|
9254
|
+
tracer?.trace(`${tag}OnFailure`, e.name);
|
|
9250
9255
|
getLogger(['devices'])('error', 'Failed to get screen share stream', e);
|
|
9251
9256
|
throw e;
|
|
9252
9257
|
}
|
|
@@ -9284,9 +9289,6 @@ const isMobile = () => /Mobi/i.test(navigator.userAgent);
|
|
|
9284
9289
|
|
|
9285
9290
|
class InputMediaDeviceManager {
|
|
9286
9291
|
constructor(call, state, trackType) {
|
|
9287
|
-
this.call = call;
|
|
9288
|
-
this.state = state;
|
|
9289
|
-
this.trackType = trackType;
|
|
9290
9292
|
/**
|
|
9291
9293
|
* if true, stops the media stream when call is left
|
|
9292
9294
|
*/
|
|
@@ -9304,6 +9306,9 @@ class InputMediaDeviceManager {
|
|
|
9304
9306
|
this.dispose = () => {
|
|
9305
9307
|
this.subscriptions.forEach((s) => s());
|
|
9306
9308
|
};
|
|
9309
|
+
this.call = call;
|
|
9310
|
+
this.state = state;
|
|
9311
|
+
this.trackType = trackType;
|
|
9307
9312
|
this.logger = getLogger([`${TrackType[trackType].toLowerCase()} manager`]);
|
|
9308
9313
|
if (deviceIds$ &&
|
|
9309
9314
|
!isReactNative() &&
|
|
@@ -9900,32 +9905,32 @@ class CameraManager extends InputMediaDeviceManager {
|
|
|
9900
9905
|
* @param direction the direction of the camera to select.
|
|
9901
9906
|
*/
|
|
9902
9907
|
async selectDirection(direction) {
|
|
9903
|
-
if (this.isDirectionSupportedByDevice()) {
|
|
9904
|
-
|
|
9905
|
-
|
|
9906
|
-
|
|
9907
|
-
|
|
9908
|
-
|
|
9909
|
-
|
|
9910
|
-
|
|
9911
|
-
|
|
9912
|
-
|
|
9913
|
-
|
|
9914
|
-
|
|
9915
|
-
|
|
9916
|
-
|
|
9917
|
-
|
|
9918
|
-
|
|
9919
|
-
|
|
9920
|
-
|
|
9921
|
-
|
|
9922
|
-
|
|
9923
|
-
|
|
9908
|
+
if (!this.isDirectionSupportedByDevice()) {
|
|
9909
|
+
this.logger('warn', 'Setting direction is not supported on this device');
|
|
9910
|
+
return;
|
|
9911
|
+
}
|
|
9912
|
+
// providing both device id and direction doesn't work, so we deselect the device
|
|
9913
|
+
this.state.setDirection(direction);
|
|
9914
|
+
this.state.setDevice(undefined);
|
|
9915
|
+
if (isReactNative()) {
|
|
9916
|
+
const videoTrack = this.getTracks()[0];
|
|
9917
|
+
await videoTrack?.applyConstraints({
|
|
9918
|
+
facingMode: direction === 'front' ? 'user' : 'environment',
|
|
9919
|
+
});
|
|
9920
|
+
return;
|
|
9921
|
+
}
|
|
9922
|
+
this.getTracks().forEach((track) => track.stop());
|
|
9923
|
+
try {
|
|
9924
|
+
await this.unmuteStream();
|
|
9925
|
+
}
|
|
9926
|
+
catch (error) {
|
|
9927
|
+
if (error instanceof Error && error.name === 'NotReadableError') {
|
|
9928
|
+
// the camera is already in use, and the device can't use it unless it's released.
|
|
9929
|
+
// in that case, we need to stop the stream and start it again.
|
|
9930
|
+
await this.muteStream();
|
|
9924
9931
|
await this.unmuteStream();
|
|
9925
9932
|
}
|
|
9926
|
-
|
|
9927
|
-
else {
|
|
9928
|
-
this.logger('warn', 'Camera direction ignored for desktop devices');
|
|
9933
|
+
throw error;
|
|
9929
9934
|
}
|
|
9930
9935
|
}
|
|
9931
9936
|
/**
|
|
@@ -10010,7 +10015,7 @@ class CameraManager extends InputMediaDeviceManager {
|
|
|
10010
10015
|
constraints.facingMode =
|
|
10011
10016
|
this.state.direction === 'front' ? 'user' : 'environment';
|
|
10012
10017
|
}
|
|
10013
|
-
return getVideoStream(constraints);
|
|
10018
|
+
return getVideoStream(constraints, this.call.tracer);
|
|
10014
10019
|
}
|
|
10015
10020
|
}
|
|
10016
10021
|
|
|
@@ -10421,7 +10426,7 @@ class MicrophoneManager extends InputMediaDeviceManager {
|
|
|
10421
10426
|
return getAudioDevices();
|
|
10422
10427
|
}
|
|
10423
10428
|
getStream(constraints) {
|
|
10424
|
-
return getAudioStream(constraints);
|
|
10429
|
+
return getAudioStream(constraints, this.call.tracer);
|
|
10425
10430
|
}
|
|
10426
10431
|
async startSpeakingWhileMutedDetection(deviceId) {
|
|
10427
10432
|
await withoutConcurrency(this.soundDetectorConcurrencyTag, async () => {
|
|
@@ -10564,7 +10569,7 @@ class ScreenShareManager extends InputMediaDeviceManager {
|
|
|
10564
10569
|
if (!this.state.audioEnabled) {
|
|
10565
10570
|
constraints.audio = false;
|
|
10566
10571
|
}
|
|
10567
|
-
return getScreenShareStream(constraints);
|
|
10572
|
+
return getScreenShareStream(constraints, this.call.tracer);
|
|
10568
10573
|
}
|
|
10569
10574
|
async stopPublishStream() {
|
|
10570
10575
|
return this.call.stopPublish(TrackType.SCREEN_SHARE, TrackType.SCREEN_SHARE_AUDIO);
|
|
@@ -10573,18 +10578,19 @@ class ScreenShareManager extends InputMediaDeviceManager {
|
|
|
10573
10578
|
* Overrides the default `select` method to throw an error.
|
|
10574
10579
|
*/
|
|
10575
10580
|
async select() {
|
|
10576
|
-
throw new Error('
|
|
10581
|
+
throw new Error('Not supported');
|
|
10577
10582
|
}
|
|
10578
10583
|
}
|
|
10579
10584
|
|
|
10580
10585
|
class SpeakerState {
|
|
10581
|
-
constructor() {
|
|
10586
|
+
constructor(tracer) {
|
|
10582
10587
|
this.selectedDeviceSubject = new BehaviorSubject('');
|
|
10583
10588
|
this.volumeSubject = new BehaviorSubject(1);
|
|
10584
10589
|
/**
|
|
10585
10590
|
* [Tells if the browser supports audio output change on 'audio' elements](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/setSinkId).
|
|
10586
10591
|
*/
|
|
10587
10592
|
this.isDeviceSelectionSupported = checkIfAudioOutputChangeSupported();
|
|
10593
|
+
this.tracer = tracer;
|
|
10588
10594
|
this.selectedDevice$ = this.selectedDeviceSubject
|
|
10589
10595
|
.asObservable()
|
|
10590
10596
|
.pipe(distinctUntilChanged());
|
|
@@ -10614,7 +10620,7 @@ class SpeakerState {
|
|
|
10614
10620
|
*/
|
|
10615
10621
|
setDevice(deviceId) {
|
|
10616
10622
|
setCurrentValue(this.selectedDeviceSubject, deviceId);
|
|
10617
|
-
tracer.trace('navigator.mediaDevices.setSinkId', deviceId);
|
|
10623
|
+
this.tracer.trace('navigator.mediaDevices.setSinkId', deviceId);
|
|
10618
10624
|
}
|
|
10619
10625
|
/**
|
|
10620
10626
|
* @internal
|
|
@@ -10627,7 +10633,6 @@ class SpeakerState {
|
|
|
10627
10633
|
|
|
10628
10634
|
class SpeakerManager {
|
|
10629
10635
|
constructor(call) {
|
|
10630
|
-
this.state = new SpeakerState();
|
|
10631
10636
|
this.subscriptions = [];
|
|
10632
10637
|
/**
|
|
10633
10638
|
* Disposes the manager.
|
|
@@ -10638,6 +10643,7 @@ class SpeakerManager {
|
|
|
10638
10643
|
this.subscriptions.forEach((s) => s.unsubscribe());
|
|
10639
10644
|
};
|
|
10640
10645
|
this.call = call;
|
|
10646
|
+
this.state = new SpeakerState(call.tracer);
|
|
10641
10647
|
if (deviceIds$ && !isReactNative()) {
|
|
10642
10648
|
this.subscriptions.push(combineLatest([deviceIds$, this.state.selectedDevice$]).subscribe(([devices, deviceId]) => {
|
|
10643
10649
|
if (!deviceId) {
|
|
@@ -10731,6 +10737,7 @@ class Call {
|
|
|
10731
10737
|
* The permissions context of this call.
|
|
10732
10738
|
*/
|
|
10733
10739
|
this.permissionsContext = new PermissionsContext();
|
|
10740
|
+
this.tracer = new Tracer(null);
|
|
10734
10741
|
/**
|
|
10735
10742
|
* The event dispatcher instance dedicated to this Call instance.
|
|
10736
10743
|
* @private
|
|
@@ -11459,7 +11466,6 @@ class Call {
|
|
|
11459
11466
|
},
|
|
11460
11467
|
});
|
|
11461
11468
|
}
|
|
11462
|
-
tracer.setEnabled(enableTracing);
|
|
11463
11469
|
this.statsReporter?.stop();
|
|
11464
11470
|
this.statsReporter = createStatsReporter({
|
|
11465
11471
|
subscriber: this.subscriber,
|
|
@@ -11467,6 +11473,7 @@ class Call {
|
|
|
11467
11473
|
state: this.state,
|
|
11468
11474
|
datacenter: sfuClient.edgeName,
|
|
11469
11475
|
});
|
|
11476
|
+
this.tracer.setEnabled(enableTracing);
|
|
11470
11477
|
this.sfuStatsReporter?.stop();
|
|
11471
11478
|
if (statsOptions?.reporting_interval_ms > 0) {
|
|
11472
11479
|
this.unifiedSessionId ?? (this.unifiedSessionId = sfuClient.sessionId);
|
|
@@ -11478,6 +11485,7 @@ class Call {
|
|
|
11478
11485
|
microphone: this.microphone,
|
|
11479
11486
|
camera: this.camera,
|
|
11480
11487
|
state: this.state,
|
|
11488
|
+
tracer: this.tracer,
|
|
11481
11489
|
unifiedSessionId: this.unifiedSessionId,
|
|
11482
11490
|
});
|
|
11483
11491
|
this.sfuStatsReporter.start();
|
|
@@ -11711,6 +11719,7 @@ class Call {
|
|
|
11711
11719
|
}
|
|
11712
11720
|
});
|
|
11713
11721
|
const unregisterNetworkChanged = this.streamClient.on('network.changed', (e) => {
|
|
11722
|
+
this.tracer.trace('network.changed', e);
|
|
11714
11723
|
if (!e.online) {
|
|
11715
11724
|
this.logger('debug', '[Reconnect] Going offline');
|
|
11716
11725
|
if (!this.hasJoinedOnce)
|
|
@@ -13714,7 +13723,7 @@ class StreamClient {
|
|
|
13714
13723
|
this.getUserAgent = () => {
|
|
13715
13724
|
if (!this.cachedUserAgent) {
|
|
13716
13725
|
const { clientAppIdentifier = {} } = this.options;
|
|
13717
|
-
const { sdkName = 'js', sdkVersion = "1.
|
|
13726
|
+
const { sdkName = 'js', sdkVersion = "1.22.0", ...extras } = clientAppIdentifier;
|
|
13718
13727
|
this.cachedUserAgent = [
|
|
13719
13728
|
`stream-video-${sdkName}-v${sdkVersion}`,
|
|
13720
13729
|
...Object.entries(extras).map(([key, value]) => `${key}=${value}`),
|