@stream-io/video-client 1.20.0 → 1.20.2
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 +14 -0
- package/dist/index.browser.es.js +157 -128
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +157 -128
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +157 -128
- package/dist/index.es.js.map +1 -1
- package/dist/src/Call.d.ts +1 -1
- package/dist/src/devices/BrowserPermission.d.ts +1 -0
- package/dist/src/devices/InputMediaDeviceManagerState.d.ts +7 -2
- package/dist/src/gen/coordinator/index.d.ts +7 -0
- package/dist/src/types.d.ts +7 -2
- package/package.json +1 -1
- package/src/Call.ts +12 -10
- package/src/__tests__/Call.autodrop.test.ts +101 -0
- package/src/devices/BrowserPermission.ts +4 -0
- package/src/devices/InputMediaDeviceManager.ts +135 -123
- package/src/devices/InputMediaDeviceManagerState.ts +12 -2
- package/src/devices/__tests__/mocks.ts +1 -0
- package/src/events/__tests__/call.test.ts +5 -1
- package/src/events/call.ts +8 -4
- package/src/events/internal.ts +1 -1
- package/src/gen/coordinator/index.ts +7 -0
- package/src/stats/SfuStatsReporter.ts +4 -4
- package/src/store/stateStore.ts +1 -1
- package/src/types.ts +8 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).
|
|
4
4
|
|
|
5
|
+
## [1.20.2](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.20.1...@stream-io/video-client-1.20.2) (2025-05-01)
|
|
6
|
+
|
|
7
|
+
### Bug Fixes
|
|
8
|
+
|
|
9
|
+
- add options for 4K RTMP and Recording ([#1775](https://github.com/GetStream/stream-video-js/issues/1775)) ([c09213d](https://github.com/GetStream/stream-video-js/commit/c09213df5fc8a46f5a8c5c1ef18f07fd05e1d547))
|
|
10
|
+
- use timeout reason when auto-dropping calls (instead of decline) ([#1776](https://github.com/GetStream/stream-video-js/issues/1776)) ([a043148](https://github.com/GetStream/stream-video-js/commit/a04314814e728c3d05d53c8940e9c223fec18fcc))
|
|
11
|
+
|
|
12
|
+
## [1.20.1](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.20.0...@stream-io/video-client-1.20.1) (2025-04-29)
|
|
13
|
+
|
|
14
|
+
### Bug Fixes
|
|
15
|
+
|
|
16
|
+
- dispose media stream if it cannot be published ([#1771](https://github.com/GetStream/stream-video-js/issues/1771)) ([83fbfd7](https://github.com/GetStream/stream-video-js/commit/83fbfd7bb77bd9a06d6955e6b48bb8238e573f57))
|
|
17
|
+
- use more granular permission state for stats reporter ([#1774](https://github.com/GetStream/stream-video-js/issues/1774)) ([55afdfc](https://github.com/GetStream/stream-video-js/commit/55afdfcdac55fad25ba32978caf55a2f25f7580b))
|
|
18
|
+
|
|
5
19
|
## [1.20.0](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.19.3...@stream-io/video-client-1.20.0) (2025-04-24)
|
|
6
20
|
|
|
7
21
|
- bump test timeout ([7d922ed](https://github.com/GetStream/stream-video-js/commit/7d922ed34c46851a257fb36ee644f1ff5e4cb917))
|
package/dist/index.browser.es.js
CHANGED
|
@@ -117,6 +117,7 @@ const FrameRecordingSettingsRequestQualityEnum = {
|
|
|
117
117
|
_720P: '720p',
|
|
118
118
|
_1080P: '1080p',
|
|
119
119
|
_1440P: '1440p',
|
|
120
|
+
_2160P: '2160p',
|
|
120
121
|
};
|
|
121
122
|
/**
|
|
122
123
|
* @export
|
|
@@ -189,11 +190,13 @@ const RTMPBroadcastRequestQualityEnum = {
|
|
|
189
190
|
_720P: '720p',
|
|
190
191
|
_1080P: '1080p',
|
|
191
192
|
_1440P: '1440p',
|
|
193
|
+
_2160P: '2160p',
|
|
192
194
|
PORTRAIT_360X640: 'portrait-360x640',
|
|
193
195
|
PORTRAIT_480X854: 'portrait-480x854',
|
|
194
196
|
PORTRAIT_720X1280: 'portrait-720x1280',
|
|
195
197
|
PORTRAIT_1080X1920: 'portrait-1080x1920',
|
|
196
198
|
PORTRAIT_1440X2560: 'portrait-1440x2560',
|
|
199
|
+
PORTRAIT_2160X3840: 'portrait-2160x3840',
|
|
197
200
|
};
|
|
198
201
|
/**
|
|
199
202
|
* @export
|
|
@@ -204,11 +207,13 @@ const RTMPSettingsRequestQualityEnum = {
|
|
|
204
207
|
_720P: '720p',
|
|
205
208
|
_1080P: '1080p',
|
|
206
209
|
_1440P: '1440p',
|
|
210
|
+
_2160P: '2160p',
|
|
207
211
|
PORTRAIT_360X640: 'portrait-360x640',
|
|
208
212
|
PORTRAIT_480X854: 'portrait-480x854',
|
|
209
213
|
PORTRAIT_720X1280: 'portrait-720x1280',
|
|
210
214
|
PORTRAIT_1080X1920: 'portrait-1080x1920',
|
|
211
215
|
PORTRAIT_1440X2560: 'portrait-1440x2560',
|
|
216
|
+
PORTRAIT_2160X3840: 'portrait-2160x3840',
|
|
212
217
|
};
|
|
213
218
|
/**
|
|
214
219
|
* @export
|
|
@@ -227,11 +232,13 @@ const RecordSettingsRequestQualityEnum = {
|
|
|
227
232
|
_720P: '720p',
|
|
228
233
|
_1080P: '1080p',
|
|
229
234
|
_1440P: '1440p',
|
|
235
|
+
_2160P: '2160p',
|
|
230
236
|
PORTRAIT_360X640: 'portrait-360x640',
|
|
231
237
|
PORTRAIT_480X854: 'portrait-480x854',
|
|
232
238
|
PORTRAIT_720X1280: 'portrait-720x1280',
|
|
233
239
|
PORTRAIT_1080X1920: 'portrait-1080x1920',
|
|
234
240
|
PORTRAIT_1440X2560: 'portrait-1440x2560',
|
|
241
|
+
PORTRAIT_2160X3840: 'portrait-2160x3840',
|
|
235
242
|
};
|
|
236
243
|
/**
|
|
237
244
|
* @export
|
|
@@ -4058,7 +4065,7 @@ class StreamVideoWriteableStateStore {
|
|
|
4058
4065
|
continue;
|
|
4059
4066
|
logger('info', `User disconnected, leaving call: ${call.cid}`);
|
|
4060
4067
|
await call
|
|
4061
|
-
.leave({
|
|
4068
|
+
.leave({ message: 'client.disconnectUser() called' })
|
|
4062
4069
|
.catch((err) => {
|
|
4063
4070
|
logger('error', `Error leaving call: ${call.cid}`, err);
|
|
4064
4071
|
});
|
|
@@ -5643,7 +5650,7 @@ const aggregate = (stats) => {
|
|
|
5643
5650
|
return report;
|
|
5644
5651
|
};
|
|
5645
5652
|
|
|
5646
|
-
const version = "1.20.
|
|
5653
|
+
const version = "1.20.2";
|
|
5647
5654
|
const [major, minor, patch] = version.split('.');
|
|
5648
5655
|
let sdkInfo = {
|
|
5649
5656
|
type: SdkType.PLAIN_JAVASCRIPT,
|
|
@@ -5784,9 +5791,9 @@ class SfuStatsReporter {
|
|
|
5784
5791
|
this.logger = getLogger(['SfuStatsReporter']);
|
|
5785
5792
|
this.inputDevices = new Map();
|
|
5786
5793
|
this.observeDevice = (device, kind) => {
|
|
5787
|
-
const {
|
|
5794
|
+
const { browserPermissionState$ } = device.state;
|
|
5788
5795
|
this.unsubscribeDevicePermissionsSubscription?.();
|
|
5789
|
-
this.unsubscribeDevicePermissionsSubscription = createSubscription(combineLatest([
|
|
5796
|
+
this.unsubscribeDevicePermissionsSubscription = createSubscription(combineLatest([browserPermissionState$, this.state.ownCapabilities$]), ([browserPermissionState, ownCapabilities]) => {
|
|
5790
5797
|
// cleanup the previous listDevices() subscription in case
|
|
5791
5798
|
// permissions or capabilities have changed.
|
|
5792
5799
|
// we will subscribe again if everything is in order.
|
|
@@ -5794,7 +5801,7 @@ class SfuStatsReporter {
|
|
|
5794
5801
|
const hasCapability = kind === 'mic'
|
|
5795
5802
|
? ownCapabilities.includes(OwnCapability.SEND_AUDIO)
|
|
5796
5803
|
: ownCapabilities.includes(OwnCapability.SEND_VIDEO);
|
|
5797
|
-
if (
|
|
5804
|
+
if (browserPermissionState !== 'granted' || !hasCapability) {
|
|
5798
5805
|
this.inputDevices.set(kind, {
|
|
5799
5806
|
currentDevice: '',
|
|
5800
5807
|
availableDevices: [],
|
|
@@ -7601,13 +7608,17 @@ const watchCallRejected = (call) => {
|
|
|
7601
7608
|
.every((m) => rejectedBy[m.user_id]);
|
|
7602
7609
|
if (everyoneElseRejected) {
|
|
7603
7610
|
call.logger('info', 'everyone rejected, leaving the call');
|
|
7604
|
-
await call.leave({
|
|
7611
|
+
await call.leave({
|
|
7612
|
+
reject: true,
|
|
7613
|
+
reason: 'cancel',
|
|
7614
|
+
message: 'ring: everyone rejected',
|
|
7615
|
+
});
|
|
7605
7616
|
}
|
|
7606
7617
|
}
|
|
7607
7618
|
else {
|
|
7608
7619
|
if (rejectedBy[eventCall.created_by.id]) {
|
|
7609
7620
|
call.logger('info', 'call creator rejected, leaving call');
|
|
7610
|
-
await call.leave({
|
|
7621
|
+
await call.leave({ message: 'ring: creator rejected' });
|
|
7611
7622
|
}
|
|
7612
7623
|
}
|
|
7613
7624
|
};
|
|
@@ -7621,7 +7632,7 @@ const watchCallEnded = (call) => {
|
|
|
7621
7632
|
if (callingState !== CallingState.IDLE &&
|
|
7622
7633
|
callingState !== CallingState.LEFT) {
|
|
7623
7634
|
call
|
|
7624
|
-
.leave({
|
|
7635
|
+
.leave({ message: 'call.ended event received', reject: false })
|
|
7625
7636
|
.catch((err) => {
|
|
7626
7637
|
call.logger('error', 'Failed to leave call after call.ended ', err);
|
|
7627
7638
|
});
|
|
@@ -7641,7 +7652,7 @@ const watchSfuCallEnded = (call) => {
|
|
|
7641
7652
|
// update the call state to reflect the call has ended.
|
|
7642
7653
|
call.state.setEndedAt(new Date());
|
|
7643
7654
|
const reason = CallEndedReason[e.reason];
|
|
7644
|
-
await call.leave({
|
|
7655
|
+
await call.leave({ message: `callEnded received: ${reason}` });
|
|
7645
7656
|
}
|
|
7646
7657
|
catch (err) {
|
|
7647
7658
|
call.logger('error', 'Failed to leave call after being ended by the SFU', err);
|
|
@@ -7708,7 +7719,7 @@ const watchLiveEnded = (dispatcher, call) => {
|
|
|
7708
7719
|
return;
|
|
7709
7720
|
call.state.setBackstage(true);
|
|
7710
7721
|
if (!call.permissionsContext.hasPermission(OwnCapability.JOIN_BACKSTAGE)) {
|
|
7711
|
-
call.leave({
|
|
7722
|
+
call.leave({ message: 'live ended' }).catch((err) => {
|
|
7712
7723
|
call.logger('error', 'Failed to leave call after live ended', err);
|
|
7713
7724
|
});
|
|
7714
7725
|
}
|
|
@@ -8728,6 +8739,9 @@ class BrowserPermission {
|
|
|
8728
8739
|
// Instead of checking if a permission is granted, we check if it isn't denied
|
|
8729
8740
|
map((state) => state !== 'denied'));
|
|
8730
8741
|
}
|
|
8742
|
+
asStateObservable() {
|
|
8743
|
+
return this.getStateObservable();
|
|
8744
|
+
}
|
|
8731
8745
|
getIsPromptingObservable() {
|
|
8732
8746
|
return this.getStateObservable().pipe(map((state) => state === 'prompting'));
|
|
8733
8747
|
}
|
|
@@ -9254,122 +9268,130 @@ class InputMediaDeviceManager {
|
|
|
9254
9268
|
this.logger('debug', 'Starting stream');
|
|
9255
9269
|
let stream;
|
|
9256
9270
|
let rootStream;
|
|
9257
|
-
|
|
9258
|
-
this.
|
|
9259
|
-
|
|
9260
|
-
|
|
9261
|
-
|
|
9262
|
-
|
|
9263
|
-
|
|
9264
|
-
|
|
9265
|
-
|
|
9266
|
-
|
|
9267
|
-
|
|
9268
|
-
|
|
9269
|
-
|
|
9270
|
-
|
|
9271
|
-
|
|
9272
|
-
|
|
9273
|
-
|
|
9274
|
-
|
|
9275
|
-
|
|
9276
|
-
|
|
9277
|
-
|
|
9278
|
-
|
|
9279
|
-
|
|
9280
|
-
|
|
9281
|
-
|
|
9282
|
-
|
|
9283
|
-
|
|
9284
|
-
|
|
9285
|
-
|
|
9286
|
-
|
|
9287
|
-
|
|
9288
|
-
|
|
9289
|
-
|
|
9290
|
-
|
|
9291
|
-
|
|
9292
|
-
|
|
9293
|
-
|
|
9294
|
-
|
|
9295
|
-
|
|
9296
|
-
|
|
9297
|
-
|
|
9298
|
-
|
|
9299
|
-
parentTrack.
|
|
9300
|
-
|
|
9301
|
-
|
|
9302
|
-
|
|
9303
|
-
|
|
9304
|
-
|
|
9305
|
-
|
|
9306
|
-
|
|
9307
|
-
|
|
9308
|
-
|
|
9309
|
-
|
|
9310
|
-
|
|
9311
|
-
|
|
9312
|
-
|
|
9313
|
-
|
|
9314
|
-
|
|
9271
|
+
try {
|
|
9272
|
+
if (this.state.mediaStream &&
|
|
9273
|
+
this.getTracks().every((t) => t.readyState === 'live')) {
|
|
9274
|
+
stream = this.state.mediaStream;
|
|
9275
|
+
this.enableTracks();
|
|
9276
|
+
}
|
|
9277
|
+
else {
|
|
9278
|
+
const defaultConstraints = this.state.defaultConstraints;
|
|
9279
|
+
const constraints = {
|
|
9280
|
+
...defaultConstraints,
|
|
9281
|
+
deviceId: this.state.selectedDevice
|
|
9282
|
+
? { exact: this.state.selectedDevice }
|
|
9283
|
+
: undefined,
|
|
9284
|
+
};
|
|
9285
|
+
/**
|
|
9286
|
+
* Chains two media streams together.
|
|
9287
|
+
*
|
|
9288
|
+
* In our case, filters MediaStreams are derived from their parent MediaStream.
|
|
9289
|
+
* However, once a child filter's track is stopped,
|
|
9290
|
+
* the tracks of the parent MediaStream aren't automatically stopped.
|
|
9291
|
+
* This leads to a situation where the camera indicator light is still on
|
|
9292
|
+
* even though the user stopped publishing video.
|
|
9293
|
+
*
|
|
9294
|
+
* This function works around this issue by stopping the parent MediaStream's tracks
|
|
9295
|
+
* as well once the child filter's tracks are stopped.
|
|
9296
|
+
*
|
|
9297
|
+
* It works by patching the stop() method of the child filter's tracks to also stop
|
|
9298
|
+
* the parent MediaStream's tracks of the same type. Here we assume that
|
|
9299
|
+
* the parent MediaStream has only one track of each type.
|
|
9300
|
+
*
|
|
9301
|
+
* @param parentStream the parent MediaStream. Omit for the root stream.
|
|
9302
|
+
*/
|
|
9303
|
+
const chainWith = (parentStream) => async (filterStream) => {
|
|
9304
|
+
if (!parentStream)
|
|
9305
|
+
return filterStream;
|
|
9306
|
+
// TODO OL: take care of track.enabled property as well
|
|
9307
|
+
const parent = await parentStream;
|
|
9308
|
+
filterStream.getTracks().forEach((track) => {
|
|
9309
|
+
const originalStop = track.stop;
|
|
9310
|
+
track.stop = function stop() {
|
|
9311
|
+
originalStop.call(track);
|
|
9312
|
+
parent.getTracks().forEach((parentTrack) => {
|
|
9313
|
+
if (parentTrack.kind === track.kind) {
|
|
9314
|
+
parentTrack.stop();
|
|
9315
|
+
}
|
|
9316
|
+
});
|
|
9317
|
+
};
|
|
9318
|
+
});
|
|
9319
|
+
parent.getTracks().forEach((parentTrack) => {
|
|
9320
|
+
// When the parent stream abruptly ends, we propagate the event
|
|
9321
|
+
// to the filter stream.
|
|
9322
|
+
// This usually happens when the camera/microphone permissions
|
|
9323
|
+
// are revoked or when the device is disconnected.
|
|
9324
|
+
const handleParentTrackEnded = () => {
|
|
9325
|
+
filterStream.getTracks().forEach((track) => {
|
|
9326
|
+
if (parentTrack.kind !== track.kind)
|
|
9327
|
+
return;
|
|
9328
|
+
track.stop();
|
|
9329
|
+
track.dispatchEvent(new Event('ended')); // propagate the event
|
|
9330
|
+
});
|
|
9331
|
+
};
|
|
9332
|
+
parentTrack.addEventListener('ended', handleParentTrackEnded);
|
|
9333
|
+
this.subscriptions.push(() => {
|
|
9334
|
+
parentTrack.removeEventListener('ended', handleParentTrackEnded);
|
|
9315
9335
|
});
|
|
9316
|
-
};
|
|
9317
|
-
|
|
9336
|
+
});
|
|
9337
|
+
return filterStream;
|
|
9338
|
+
};
|
|
9339
|
+
// the rootStream represents the stream coming from the actual device
|
|
9340
|
+
// e.g. camera or microphone stream
|
|
9341
|
+
rootStream = this.getStream(constraints);
|
|
9342
|
+
// we publish the last MediaStream of the chain
|
|
9343
|
+
stream = await this.filters.reduce((parent, entry) => parent
|
|
9344
|
+
.then((inputStream) => {
|
|
9345
|
+
const { stop, output } = entry.start(inputStream);
|
|
9346
|
+
entry.stop = stop;
|
|
9347
|
+
return output;
|
|
9348
|
+
})
|
|
9349
|
+
.then(chainWith(parent), (error) => {
|
|
9350
|
+
this.logger('warn', 'Filter failed to start and will be ignored', error);
|
|
9351
|
+
return parent;
|
|
9352
|
+
}), rootStream);
|
|
9353
|
+
}
|
|
9354
|
+
if (this.call.state.callingState === CallingState.JOINED) {
|
|
9355
|
+
await this.publishStream(stream);
|
|
9356
|
+
}
|
|
9357
|
+
if (this.state.mediaStream !== stream) {
|
|
9358
|
+
this.state.setMediaStream(stream, await rootStream);
|
|
9359
|
+
const handleTrackEnded = async () => {
|
|
9360
|
+
await this.statusChangeSettled();
|
|
9361
|
+
if (this.enabled) {
|
|
9362
|
+
this.isTrackStoppedDueToTrackEnd = true;
|
|
9363
|
+
setTimeout(() => {
|
|
9364
|
+
this.isTrackStoppedDueToTrackEnd = false;
|
|
9365
|
+
}, 2000);
|
|
9366
|
+
await this.disable();
|
|
9367
|
+
}
|
|
9368
|
+
};
|
|
9369
|
+
const createTrackMuteHandler = (muted) => () => {
|
|
9370
|
+
if (!isMobile() || this.trackType !== TrackType.VIDEO)
|
|
9371
|
+
return;
|
|
9372
|
+
this.call.notifyTrackMuteState(muted, this.trackType).catch((err) => {
|
|
9373
|
+
this.logger('warn', 'Error while notifying track mute state', err);
|
|
9374
|
+
});
|
|
9375
|
+
};
|
|
9376
|
+
stream.getTracks().forEach((track) => {
|
|
9377
|
+
const muteHandler = createTrackMuteHandler(true);
|
|
9378
|
+
const unmuteHandler = createTrackMuteHandler(false);
|
|
9379
|
+
track.addEventListener('mute', muteHandler);
|
|
9380
|
+
track.addEventListener('unmute', unmuteHandler);
|
|
9381
|
+
track.addEventListener('ended', handleTrackEnded);
|
|
9318
9382
|
this.subscriptions.push(() => {
|
|
9319
|
-
|
|
9383
|
+
track.removeEventListener('mute', muteHandler);
|
|
9384
|
+
track.removeEventListener('unmute', unmuteHandler);
|
|
9385
|
+
track.removeEventListener('ended', handleTrackEnded);
|
|
9320
9386
|
});
|
|
9321
9387
|
});
|
|
9322
|
-
|
|
9323
|
-
};
|
|
9324
|
-
// the rootStream represents the stream coming from the actual device
|
|
9325
|
-
// e.g. camera or microphone stream
|
|
9326
|
-
rootStream = this.getStream(constraints);
|
|
9327
|
-
// we publish the last MediaStream of the chain
|
|
9328
|
-
stream = await this.filters.reduce((parent, entry) => parent
|
|
9329
|
-
.then((inputStream) => {
|
|
9330
|
-
const { stop, output } = entry.start(inputStream);
|
|
9331
|
-
entry.stop = stop;
|
|
9332
|
-
return output;
|
|
9333
|
-
})
|
|
9334
|
-
.then(chainWith(parent), (error) => {
|
|
9335
|
-
this.logger('warn', 'Filter failed to start and will be ignored', error);
|
|
9336
|
-
return parent;
|
|
9337
|
-
}), rootStream);
|
|
9338
|
-
}
|
|
9339
|
-
if (this.call.state.callingState === CallingState.JOINED) {
|
|
9340
|
-
await this.publishStream(stream);
|
|
9388
|
+
}
|
|
9341
9389
|
}
|
|
9342
|
-
|
|
9343
|
-
|
|
9344
|
-
|
|
9345
|
-
|
|
9346
|
-
|
|
9347
|
-
this.isTrackStoppedDueToTrackEnd = true;
|
|
9348
|
-
setTimeout(() => {
|
|
9349
|
-
this.isTrackStoppedDueToTrackEnd = false;
|
|
9350
|
-
}, 2000);
|
|
9351
|
-
await this.disable();
|
|
9352
|
-
}
|
|
9353
|
-
};
|
|
9354
|
-
const createTrackMuteHandler = (muted) => () => {
|
|
9355
|
-
if (!isMobile() || this.trackType !== TrackType.VIDEO)
|
|
9356
|
-
return;
|
|
9357
|
-
this.call.notifyTrackMuteState(muted, this.trackType).catch((err) => {
|
|
9358
|
-
this.logger('warn', 'Error while notifying track mute state', err);
|
|
9359
|
-
});
|
|
9360
|
-
};
|
|
9361
|
-
stream.getTracks().forEach((track) => {
|
|
9362
|
-
const muteHandler = createTrackMuteHandler(true);
|
|
9363
|
-
const unmuteHandler = createTrackMuteHandler(false);
|
|
9364
|
-
track.addEventListener('mute', muteHandler);
|
|
9365
|
-
track.addEventListener('unmute', unmuteHandler);
|
|
9366
|
-
track.addEventListener('ended', handleTrackEnded);
|
|
9367
|
-
this.subscriptions.push(() => {
|
|
9368
|
-
track.removeEventListener('mute', muteHandler);
|
|
9369
|
-
track.removeEventListener('unmute', unmuteHandler);
|
|
9370
|
-
track.removeEventListener('ended', handleTrackEnded);
|
|
9371
|
-
});
|
|
9372
|
-
});
|
|
9390
|
+
catch (err) {
|
|
9391
|
+
if (rootStream) {
|
|
9392
|
+
disposeOfMediaStream(await rootStream);
|
|
9393
|
+
}
|
|
9394
|
+
throw err;
|
|
9373
9395
|
}
|
|
9374
9396
|
}
|
|
9375
9397
|
get mediaDeviceKind() {
|
|
@@ -9491,6 +9513,9 @@ class InputMediaDeviceManagerState {
|
|
|
9491
9513
|
this.hasBrowserPermission$ = permission
|
|
9492
9514
|
? permission.asObservable().pipe(shareReplay(1))
|
|
9493
9515
|
: of(true);
|
|
9516
|
+
this.browserPermissionState$ = permission
|
|
9517
|
+
? permission.asStateObservable().pipe(shareReplay(1))
|
|
9518
|
+
: of('prompt');
|
|
9494
9519
|
this.isPromptingPermission$ = permission
|
|
9495
9520
|
? permission.getIsPromptingObservable().pipe(shareReplay(1))
|
|
9496
9521
|
: of(false);
|
|
@@ -10556,7 +10581,7 @@ class Call {
|
|
|
10556
10581
|
const currentUserId = this.currentUserId;
|
|
10557
10582
|
if (currentUserId && blockedUserIds.includes(currentUserId)) {
|
|
10558
10583
|
this.logger('info', 'Leaving call because of being blocked');
|
|
10559
|
-
await this.leave({
|
|
10584
|
+
await this.leave({ message: 'user blocked' }).catch((err) => {
|
|
10560
10585
|
this.logger('error', 'Error leaving call after being blocked', err);
|
|
10561
10586
|
});
|
|
10562
10587
|
}
|
|
@@ -10709,7 +10734,7 @@ class Call {
|
|
|
10709
10734
|
/**
|
|
10710
10735
|
* Leave the call and stop the media streams that were published by the call.
|
|
10711
10736
|
*/
|
|
10712
|
-
this.leave = async ({ reject, reason
|
|
10737
|
+
this.leave = async ({ reject, reason, message } = {}) => {
|
|
10713
10738
|
await withoutConcurrency(this.joinLeaveConcurrencyTag, async () => {
|
|
10714
10739
|
const callingState = this.state.callingState;
|
|
10715
10740
|
if (callingState === CallingState.LEFT) {
|
|
@@ -10727,7 +10752,7 @@ class Call {
|
|
|
10727
10752
|
}
|
|
10728
10753
|
if (callingState === CallingState.RINGING && reject !== false) {
|
|
10729
10754
|
if (reject) {
|
|
10730
|
-
await this.reject('decline');
|
|
10755
|
+
await this.reject(reason ?? 'decline');
|
|
10731
10756
|
}
|
|
10732
10757
|
else {
|
|
10733
10758
|
// if reject was undefined, we still have to cancel the call automatically
|
|
@@ -10746,7 +10771,7 @@ class Call {
|
|
|
10746
10771
|
this.subscriber = undefined;
|
|
10747
10772
|
this.publisher?.dispose();
|
|
10748
10773
|
this.publisher = undefined;
|
|
10749
|
-
await this.sfuClient?.leaveAndClose(reason);
|
|
10774
|
+
await this.sfuClient?.leaveAndClose(message ?? reason ?? 'user is leaving the call');
|
|
10750
10775
|
this.sfuClient = undefined;
|
|
10751
10776
|
this.dynascaleManager.setSfuClient(undefined);
|
|
10752
10777
|
this.state.setCallingState(CallingState.LEFT);
|
|
@@ -11453,7 +11478,7 @@ class Call {
|
|
|
11453
11478
|
if (strategy === WebsocketReconnectStrategy.UNSPECIFIED)
|
|
11454
11479
|
return;
|
|
11455
11480
|
if (strategy === WebsocketReconnectStrategy.DISCONNECT) {
|
|
11456
|
-
this.leave({
|
|
11481
|
+
this.leave({ message: 'SFU instructed to disconnect' }).catch((err) => {
|
|
11457
11482
|
this.logger('warn', `Can't leave call after disconnect request`, err);
|
|
11458
11483
|
});
|
|
11459
11484
|
}
|
|
@@ -12083,7 +12108,11 @@ class Call {
|
|
|
12083
12108
|
// e.g. it was already accepted and joined
|
|
12084
12109
|
if (this.state.callingState !== CallingState.RINGING)
|
|
12085
12110
|
return;
|
|
12086
|
-
this.leave({
|
|
12111
|
+
this.leave({
|
|
12112
|
+
reject: true,
|
|
12113
|
+
reason: 'timeout',
|
|
12114
|
+
message: `ringing timeout - ${this.isCreatedByMe ? 'no one accepted' : `user didn't interact with incoming call screen`}`,
|
|
12115
|
+
}).catch((err) => {
|
|
12087
12116
|
this.logger('error', 'Failed to drop call', err);
|
|
12088
12117
|
});
|
|
12089
12118
|
}, timeoutInMs);
|
|
@@ -13458,7 +13487,7 @@ class StreamClient {
|
|
|
13458
13487
|
this.getUserAgent = () => {
|
|
13459
13488
|
if (!this.cachedUserAgent) {
|
|
13460
13489
|
const { clientAppIdentifier = {} } = this.options;
|
|
13461
|
-
const { sdkName = 'js', sdkVersion = "1.20.
|
|
13490
|
+
const { sdkName = 'js', sdkVersion = "1.20.2", ...extras } = clientAppIdentifier;
|
|
13462
13491
|
this.cachedUserAgent = [
|
|
13463
13492
|
`stream-video-${sdkName}-v${sdkVersion}`,
|
|
13464
13493
|
...Object.entries(extras).map(([key, value]) => `${key}=${value}`),
|