@stream-io/video-client 1.20.0 → 1.20.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 +7 -0
- package/dist/index.browser.es.js +130 -116
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +130 -116
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +130 -116
- package/dist/index.es.js.map +1 -1
- package/dist/src/devices/BrowserPermission.d.ts +1 -0
- package/dist/src/devices/InputMediaDeviceManagerState.d.ts +7 -2
- package/package.json +1 -1
- 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/stats/SfuStatsReporter.ts +4 -4
package/dist/index.cjs.js
CHANGED
|
@@ -5645,7 +5645,7 @@ const aggregate = (stats) => {
|
|
|
5645
5645
|
return report;
|
|
5646
5646
|
};
|
|
5647
5647
|
|
|
5648
|
-
const version = "1.20.
|
|
5648
|
+
const version = "1.20.1";
|
|
5649
5649
|
const [major, minor, patch] = version.split('.');
|
|
5650
5650
|
let sdkInfo = {
|
|
5651
5651
|
type: SdkType.PLAIN_JAVASCRIPT,
|
|
@@ -5786,9 +5786,9 @@ class SfuStatsReporter {
|
|
|
5786
5786
|
this.logger = getLogger(['SfuStatsReporter']);
|
|
5787
5787
|
this.inputDevices = new Map();
|
|
5788
5788
|
this.observeDevice = (device, kind) => {
|
|
5789
|
-
const {
|
|
5789
|
+
const { browserPermissionState$ } = device.state;
|
|
5790
5790
|
this.unsubscribeDevicePermissionsSubscription?.();
|
|
5791
|
-
this.unsubscribeDevicePermissionsSubscription = createSubscription(rxjs.combineLatest([
|
|
5791
|
+
this.unsubscribeDevicePermissionsSubscription = createSubscription(rxjs.combineLatest([browserPermissionState$, this.state.ownCapabilities$]), ([browserPermissionState, ownCapabilities]) => {
|
|
5792
5792
|
// cleanup the previous listDevices() subscription in case
|
|
5793
5793
|
// permissions or capabilities have changed.
|
|
5794
5794
|
// we will subscribe again if everything is in order.
|
|
@@ -5796,7 +5796,7 @@ class SfuStatsReporter {
|
|
|
5796
5796
|
const hasCapability = kind === 'mic'
|
|
5797
5797
|
? ownCapabilities.includes(OwnCapability.SEND_AUDIO)
|
|
5798
5798
|
: ownCapabilities.includes(OwnCapability.SEND_VIDEO);
|
|
5799
|
-
if (
|
|
5799
|
+
if (browserPermissionState !== 'granted' || !hasCapability) {
|
|
5800
5800
|
this.inputDevices.set(kind, {
|
|
5801
5801
|
currentDevice: '',
|
|
5802
5802
|
availableDevices: [],
|
|
@@ -8730,6 +8730,9 @@ class BrowserPermission {
|
|
|
8730
8730
|
// Instead of checking if a permission is granted, we check if it isn't denied
|
|
8731
8731
|
rxjs.map((state) => state !== 'denied'));
|
|
8732
8732
|
}
|
|
8733
|
+
asStateObservable() {
|
|
8734
|
+
return this.getStateObservable();
|
|
8735
|
+
}
|
|
8733
8736
|
getIsPromptingObservable() {
|
|
8734
8737
|
return this.getStateObservable().pipe(rxjs.map((state) => state === 'prompting'));
|
|
8735
8738
|
}
|
|
@@ -9256,122 +9259,130 @@ class InputMediaDeviceManager {
|
|
|
9256
9259
|
this.logger('debug', 'Starting stream');
|
|
9257
9260
|
let stream;
|
|
9258
9261
|
let rootStream;
|
|
9259
|
-
|
|
9260
|
-
this.
|
|
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
|
-
|
|
9300
|
-
|
|
9301
|
-
parentTrack.
|
|
9302
|
-
|
|
9303
|
-
|
|
9304
|
-
|
|
9305
|
-
|
|
9306
|
-
|
|
9307
|
-
|
|
9308
|
-
|
|
9309
|
-
|
|
9310
|
-
|
|
9311
|
-
|
|
9312
|
-
|
|
9313
|
-
|
|
9314
|
-
|
|
9315
|
-
|
|
9316
|
-
|
|
9262
|
+
try {
|
|
9263
|
+
if (this.state.mediaStream &&
|
|
9264
|
+
this.getTracks().every((t) => t.readyState === 'live')) {
|
|
9265
|
+
stream = this.state.mediaStream;
|
|
9266
|
+
this.enableTracks();
|
|
9267
|
+
}
|
|
9268
|
+
else {
|
|
9269
|
+
const defaultConstraints = this.state.defaultConstraints;
|
|
9270
|
+
const constraints = {
|
|
9271
|
+
...defaultConstraints,
|
|
9272
|
+
deviceId: this.state.selectedDevice
|
|
9273
|
+
? { exact: this.state.selectedDevice }
|
|
9274
|
+
: undefined,
|
|
9275
|
+
};
|
|
9276
|
+
/**
|
|
9277
|
+
* Chains two media streams together.
|
|
9278
|
+
*
|
|
9279
|
+
* In our case, filters MediaStreams are derived from their parent MediaStream.
|
|
9280
|
+
* However, once a child filter's track is stopped,
|
|
9281
|
+
* the tracks of the parent MediaStream aren't automatically stopped.
|
|
9282
|
+
* This leads to a situation where the camera indicator light is still on
|
|
9283
|
+
* even though the user stopped publishing video.
|
|
9284
|
+
*
|
|
9285
|
+
* This function works around this issue by stopping the parent MediaStream's tracks
|
|
9286
|
+
* as well once the child filter's tracks are stopped.
|
|
9287
|
+
*
|
|
9288
|
+
* It works by patching the stop() method of the child filter's tracks to also stop
|
|
9289
|
+
* the parent MediaStream's tracks of the same type. Here we assume that
|
|
9290
|
+
* the parent MediaStream has only one track of each type.
|
|
9291
|
+
*
|
|
9292
|
+
* @param parentStream the parent MediaStream. Omit for the root stream.
|
|
9293
|
+
*/
|
|
9294
|
+
const chainWith = (parentStream) => async (filterStream) => {
|
|
9295
|
+
if (!parentStream)
|
|
9296
|
+
return filterStream;
|
|
9297
|
+
// TODO OL: take care of track.enabled property as well
|
|
9298
|
+
const parent = await parentStream;
|
|
9299
|
+
filterStream.getTracks().forEach((track) => {
|
|
9300
|
+
const originalStop = track.stop;
|
|
9301
|
+
track.stop = function stop() {
|
|
9302
|
+
originalStop.call(track);
|
|
9303
|
+
parent.getTracks().forEach((parentTrack) => {
|
|
9304
|
+
if (parentTrack.kind === track.kind) {
|
|
9305
|
+
parentTrack.stop();
|
|
9306
|
+
}
|
|
9307
|
+
});
|
|
9308
|
+
};
|
|
9309
|
+
});
|
|
9310
|
+
parent.getTracks().forEach((parentTrack) => {
|
|
9311
|
+
// When the parent stream abruptly ends, we propagate the event
|
|
9312
|
+
// to the filter stream.
|
|
9313
|
+
// This usually happens when the camera/microphone permissions
|
|
9314
|
+
// are revoked or when the device is disconnected.
|
|
9315
|
+
const handleParentTrackEnded = () => {
|
|
9316
|
+
filterStream.getTracks().forEach((track) => {
|
|
9317
|
+
if (parentTrack.kind !== track.kind)
|
|
9318
|
+
return;
|
|
9319
|
+
track.stop();
|
|
9320
|
+
track.dispatchEvent(new Event('ended')); // propagate the event
|
|
9321
|
+
});
|
|
9322
|
+
};
|
|
9323
|
+
parentTrack.addEventListener('ended', handleParentTrackEnded);
|
|
9324
|
+
this.subscriptions.push(() => {
|
|
9325
|
+
parentTrack.removeEventListener('ended', handleParentTrackEnded);
|
|
9317
9326
|
});
|
|
9318
|
-
};
|
|
9319
|
-
|
|
9327
|
+
});
|
|
9328
|
+
return filterStream;
|
|
9329
|
+
};
|
|
9330
|
+
// the rootStream represents the stream coming from the actual device
|
|
9331
|
+
// e.g. camera or microphone stream
|
|
9332
|
+
rootStream = this.getStream(constraints);
|
|
9333
|
+
// we publish the last MediaStream of the chain
|
|
9334
|
+
stream = await this.filters.reduce((parent, entry) => parent
|
|
9335
|
+
.then((inputStream) => {
|
|
9336
|
+
const { stop, output } = entry.start(inputStream);
|
|
9337
|
+
entry.stop = stop;
|
|
9338
|
+
return output;
|
|
9339
|
+
})
|
|
9340
|
+
.then(chainWith(parent), (error) => {
|
|
9341
|
+
this.logger('warn', 'Filter failed to start and will be ignored', error);
|
|
9342
|
+
return parent;
|
|
9343
|
+
}), rootStream);
|
|
9344
|
+
}
|
|
9345
|
+
if (this.call.state.callingState === exports.CallingState.JOINED) {
|
|
9346
|
+
await this.publishStream(stream);
|
|
9347
|
+
}
|
|
9348
|
+
if (this.state.mediaStream !== stream) {
|
|
9349
|
+
this.state.setMediaStream(stream, await rootStream);
|
|
9350
|
+
const handleTrackEnded = async () => {
|
|
9351
|
+
await this.statusChangeSettled();
|
|
9352
|
+
if (this.enabled) {
|
|
9353
|
+
this.isTrackStoppedDueToTrackEnd = true;
|
|
9354
|
+
setTimeout(() => {
|
|
9355
|
+
this.isTrackStoppedDueToTrackEnd = false;
|
|
9356
|
+
}, 2000);
|
|
9357
|
+
await this.disable();
|
|
9358
|
+
}
|
|
9359
|
+
};
|
|
9360
|
+
const createTrackMuteHandler = (muted) => () => {
|
|
9361
|
+
if (!isMobile() || this.trackType !== TrackType.VIDEO)
|
|
9362
|
+
return;
|
|
9363
|
+
this.call.notifyTrackMuteState(muted, this.trackType).catch((err) => {
|
|
9364
|
+
this.logger('warn', 'Error while notifying track mute state', err);
|
|
9365
|
+
});
|
|
9366
|
+
};
|
|
9367
|
+
stream.getTracks().forEach((track) => {
|
|
9368
|
+
const muteHandler = createTrackMuteHandler(true);
|
|
9369
|
+
const unmuteHandler = createTrackMuteHandler(false);
|
|
9370
|
+
track.addEventListener('mute', muteHandler);
|
|
9371
|
+
track.addEventListener('unmute', unmuteHandler);
|
|
9372
|
+
track.addEventListener('ended', handleTrackEnded);
|
|
9320
9373
|
this.subscriptions.push(() => {
|
|
9321
|
-
|
|
9374
|
+
track.removeEventListener('mute', muteHandler);
|
|
9375
|
+
track.removeEventListener('unmute', unmuteHandler);
|
|
9376
|
+
track.removeEventListener('ended', handleTrackEnded);
|
|
9322
9377
|
});
|
|
9323
9378
|
});
|
|
9324
|
-
|
|
9325
|
-
};
|
|
9326
|
-
// the rootStream represents the stream coming from the actual device
|
|
9327
|
-
// e.g. camera or microphone stream
|
|
9328
|
-
rootStream = this.getStream(constraints);
|
|
9329
|
-
// we publish the last MediaStream of the chain
|
|
9330
|
-
stream = await this.filters.reduce((parent, entry) => parent
|
|
9331
|
-
.then((inputStream) => {
|
|
9332
|
-
const { stop, output } = entry.start(inputStream);
|
|
9333
|
-
entry.stop = stop;
|
|
9334
|
-
return output;
|
|
9335
|
-
})
|
|
9336
|
-
.then(chainWith(parent), (error) => {
|
|
9337
|
-
this.logger('warn', 'Filter failed to start and will be ignored', error);
|
|
9338
|
-
return parent;
|
|
9339
|
-
}), rootStream);
|
|
9340
|
-
}
|
|
9341
|
-
if (this.call.state.callingState === exports.CallingState.JOINED) {
|
|
9342
|
-
await this.publishStream(stream);
|
|
9379
|
+
}
|
|
9343
9380
|
}
|
|
9344
|
-
|
|
9345
|
-
|
|
9346
|
-
|
|
9347
|
-
|
|
9348
|
-
|
|
9349
|
-
this.isTrackStoppedDueToTrackEnd = true;
|
|
9350
|
-
setTimeout(() => {
|
|
9351
|
-
this.isTrackStoppedDueToTrackEnd = false;
|
|
9352
|
-
}, 2000);
|
|
9353
|
-
await this.disable();
|
|
9354
|
-
}
|
|
9355
|
-
};
|
|
9356
|
-
const createTrackMuteHandler = (muted) => () => {
|
|
9357
|
-
if (!isMobile() || this.trackType !== TrackType.VIDEO)
|
|
9358
|
-
return;
|
|
9359
|
-
this.call.notifyTrackMuteState(muted, this.trackType).catch((err) => {
|
|
9360
|
-
this.logger('warn', 'Error while notifying track mute state', err);
|
|
9361
|
-
});
|
|
9362
|
-
};
|
|
9363
|
-
stream.getTracks().forEach((track) => {
|
|
9364
|
-
const muteHandler = createTrackMuteHandler(true);
|
|
9365
|
-
const unmuteHandler = createTrackMuteHandler(false);
|
|
9366
|
-
track.addEventListener('mute', muteHandler);
|
|
9367
|
-
track.addEventListener('unmute', unmuteHandler);
|
|
9368
|
-
track.addEventListener('ended', handleTrackEnded);
|
|
9369
|
-
this.subscriptions.push(() => {
|
|
9370
|
-
track.removeEventListener('mute', muteHandler);
|
|
9371
|
-
track.removeEventListener('unmute', unmuteHandler);
|
|
9372
|
-
track.removeEventListener('ended', handleTrackEnded);
|
|
9373
|
-
});
|
|
9374
|
-
});
|
|
9381
|
+
catch (err) {
|
|
9382
|
+
if (rootStream) {
|
|
9383
|
+
disposeOfMediaStream(await rootStream);
|
|
9384
|
+
}
|
|
9385
|
+
throw err;
|
|
9375
9386
|
}
|
|
9376
9387
|
}
|
|
9377
9388
|
get mediaDeviceKind() {
|
|
@@ -9493,6 +9504,9 @@ class InputMediaDeviceManagerState {
|
|
|
9493
9504
|
this.hasBrowserPermission$ = permission
|
|
9494
9505
|
? permission.asObservable().pipe(rxjs.shareReplay(1))
|
|
9495
9506
|
: rxjs.of(true);
|
|
9507
|
+
this.browserPermissionState$ = permission
|
|
9508
|
+
? permission.asStateObservable().pipe(rxjs.shareReplay(1))
|
|
9509
|
+
: rxjs.of('prompt');
|
|
9496
9510
|
this.isPromptingPermission$ = permission
|
|
9497
9511
|
? permission.getIsPromptingObservable().pipe(rxjs.shareReplay(1))
|
|
9498
9512
|
: rxjs.of(false);
|
|
@@ -13458,7 +13472,7 @@ class StreamClient {
|
|
|
13458
13472
|
this.getUserAgent = () => {
|
|
13459
13473
|
if (!this.cachedUserAgent) {
|
|
13460
13474
|
const { clientAppIdentifier = {} } = this.options;
|
|
13461
|
-
const { sdkName = 'js', sdkVersion = "1.20.
|
|
13475
|
+
const { sdkName = 'js', sdkVersion = "1.20.1", ...extras } = clientAppIdentifier;
|
|
13462
13476
|
this.cachedUserAgent = [
|
|
13463
13477
|
`stream-video-${sdkName}-v${sdkVersion}`,
|
|
13464
13478
|
...Object.entries(extras).map(([key, value]) => `${key}=${value}`),
|