@stream-io/video-client 1.18.4 → 1.18.6
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 +17 -0
- package/dist/index.browser.es.js +47 -58
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +47 -58
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +47 -58
- package/dist/index.es.js.map +1 -1
- package/dist/src/devices/BrowserPermission.d.ts +5 -2
- package/dist/src/devices/InputMediaDeviceManagerState.d.ts +5 -0
- package/dist/src/rtc/BasePeerConnection.d.ts +1 -1
- package/dist/src/rtc/Publisher.d.ts +0 -7
- package/dist/src/rtc/TransceiverCache.d.ts +1 -5
- package/package.json +2 -2
- package/src/devices/BrowserPermission.ts +22 -8
- package/src/devices/InputMediaDeviceManagerState.ts +10 -0
- package/src/devices/__tests__/mocks.ts +1 -0
- package/src/helpers/concurrency.ts +4 -1
- package/src/rtc/BasePeerConnection.ts +2 -4
- package/src/rtc/Publisher.ts +36 -56
- package/src/rtc/TransceiverCache.ts +1 -8
- package/src/rtc/__tests__/Publisher.test.ts +8 -8
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).
|
|
4
4
|
|
|
5
|
+
## [1.18.6](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.18.5...@stream-io/video-client-1.18.6) (2025-03-13)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* ensure negotiation runs sequentially ([#1722](https://github.com/GetStream/stream-video-js/issues/1722)) ([7e166aa](https://github.com/GetStream/stream-video-js/commit/7e166aaf606c3f751068cf60bd554e6374f701d7))
|
|
11
|
+
|
|
12
|
+
## [1.18.5](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.18.4...@stream-io/video-client-1.18.5) (2025-03-12)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
* Upgrade to Next 15.2 ([#1717](https://github.com/GetStream/stream-video-js/issues/1717)) ([9b1aec3](https://github.com/GetStream/stream-video-js/commit/9b1aec3447dee611c0d900db44add6b6c89e2b8d))
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Bug Fixes
|
|
19
|
+
|
|
20
|
+
* add pending browser permission state ([#1718](https://github.com/GetStream/stream-video-js/issues/1718)) ([7f24be6](https://github.com/GetStream/stream-video-js/commit/7f24be63d33105d0688be7b5b625bc9b6aa0d3a9))
|
|
21
|
+
|
|
5
22
|
## [1.18.4](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.18.3...@stream-io/video-client-1.18.4) (2025-03-10)
|
|
6
23
|
|
|
7
24
|
|
package/dist/index.browser.es.js
CHANGED
|
@@ -3645,7 +3645,10 @@ function hasPending(tag) {
|
|
|
3645
3645
|
return pendingPromises.has(tag);
|
|
3646
3646
|
}
|
|
3647
3647
|
async function settled(tag) {
|
|
3648
|
-
|
|
3648
|
+
let pending;
|
|
3649
|
+
while ((pending = pendingPromises.get(tag))) {
|
|
3650
|
+
await pending.promise;
|
|
3651
|
+
}
|
|
3649
3652
|
}
|
|
3650
3653
|
/**
|
|
3651
3654
|
* Implements common functionality of running async functions serially, by chaining
|
|
@@ -5344,8 +5347,6 @@ class BasePeerConnection {
|
|
|
5344
5347
|
this.pc.removeEventListener('icecandidateerror', this.onIceCandidateError);
|
|
5345
5348
|
this.pc.removeEventListener('signalingstatechange', this.onSignalingChange);
|
|
5346
5349
|
this.pc.removeEventListener('iceconnectionstatechange', this.onIceConnectionStateChange);
|
|
5347
|
-
// cancel any ongoing ICE restart process
|
|
5348
|
-
withCancellation('onIceConnectionStateChange', () => Promise.resolve());
|
|
5349
5350
|
this.pc.removeEventListener('icegatheringstatechange', this.onIceGatherChange);
|
|
5350
5351
|
this.unsubscribeIceTrickle?.();
|
|
5351
5352
|
this.subscriptions.forEach((unsubscribe) => unsubscribe());
|
|
@@ -5375,12 +5376,6 @@ class TransceiverCache {
|
|
|
5375
5376
|
this.get = (publishOption) => {
|
|
5376
5377
|
return this.findTransceiver(publishOption)?.transceiver;
|
|
5377
5378
|
};
|
|
5378
|
-
/**
|
|
5379
|
-
* Gets the last transceiver for the given track type and publish option id.
|
|
5380
|
-
*/
|
|
5381
|
-
this.getWith = (trackType, id) => {
|
|
5382
|
-
return this.findTransceiver({ trackType, id })?.transceiver;
|
|
5383
|
-
};
|
|
5384
5379
|
/**
|
|
5385
5380
|
* Checks if the cache has the given publish option.
|
|
5386
5381
|
*/
|
|
@@ -5694,7 +5689,7 @@ class Publisher extends BasePeerConnection {
|
|
|
5694
5689
|
const trackToPublish = this.cloneTrack(track);
|
|
5695
5690
|
const transceiver = this.transceiverCache.get(publishOption);
|
|
5696
5691
|
if (!transceiver) {
|
|
5697
|
-
this.addTransceiver(trackToPublish, publishOption);
|
|
5692
|
+
await this.addTransceiver(trackToPublish, publishOption);
|
|
5698
5693
|
}
|
|
5699
5694
|
else {
|
|
5700
5695
|
const previousTrack = transceiver.sender.track;
|
|
@@ -5708,7 +5703,7 @@ class Publisher extends BasePeerConnection {
|
|
|
5708
5703
|
/**
|
|
5709
5704
|
* Adds a new transceiver carrying the given track to the peer connection.
|
|
5710
5705
|
*/
|
|
5711
|
-
this.addTransceiver = (track, publishOption) => {
|
|
5706
|
+
this.addTransceiver = async (track, publishOption) => {
|
|
5712
5707
|
const videoEncodings = computeVideoLayers(track, publishOption);
|
|
5713
5708
|
const sendEncodings = isSvcCodec(publishOption.codec?.name)
|
|
5714
5709
|
? toSvcEncodings(videoEncodings)
|
|
@@ -5720,6 +5715,7 @@ class Publisher extends BasePeerConnection {
|
|
|
5720
5715
|
const trackType = publishOption.trackType;
|
|
5721
5716
|
this.logger('debug', `Added ${TrackType[trackType]} transceiver`);
|
|
5722
5717
|
this.transceiverCache.add(publishOption, transceiver);
|
|
5718
|
+
await this.negotiate();
|
|
5723
5719
|
};
|
|
5724
5720
|
/**
|
|
5725
5721
|
* Synchronizes the current Publisher state with the provided publish options.
|
|
@@ -5739,7 +5735,7 @@ class Publisher extends BasePeerConnection {
|
|
|
5739
5735
|
// take the track from the existing transceiver for the same track type,
|
|
5740
5736
|
// clone it and publish it with the new publish options
|
|
5741
5737
|
const track = this.cloneTrack(item.transceiver.sender.track);
|
|
5742
|
-
this.addTransceiver(track, publishOption);
|
|
5738
|
+
await this.addTransceiver(track, publishOption);
|
|
5743
5739
|
}
|
|
5744
5740
|
// stop publishing with options not required anymore -> [vp9]
|
|
5745
5741
|
for (const item of this.transceiverCache.items()) {
|
|
@@ -5809,7 +5805,9 @@ class Publisher extends BasePeerConnection {
|
|
|
5809
5805
|
const enabledLayers = layers.filter((l) => l.active);
|
|
5810
5806
|
const tag = 'Update publish quality:';
|
|
5811
5807
|
this.logger('info', `${tag} requested layers by SFU:`, enabledLayers);
|
|
5812
|
-
const
|
|
5808
|
+
const transceiverId = this.transceiverCache.find((t) => t.publishOption.id === publishOptionId &&
|
|
5809
|
+
t.publishOption.trackType === trackType);
|
|
5810
|
+
const sender = transceiverId?.transceiver.sender;
|
|
5813
5811
|
if (!sender) {
|
|
5814
5812
|
return this.logger('warn', `${tag} no video sender found.`);
|
|
5815
5813
|
}
|
|
@@ -5817,8 +5815,8 @@ class Publisher extends BasePeerConnection {
|
|
|
5817
5815
|
if (params.encodings.length === 0) {
|
|
5818
5816
|
return this.logger('warn', `${tag} there are no encodings set.`);
|
|
5819
5817
|
}
|
|
5820
|
-
const
|
|
5821
|
-
const usesSvcCodec = codecInUse && isSvcCodec(codecInUse
|
|
5818
|
+
const codecInUse = transceiverId?.publishOption.codec?.name;
|
|
5819
|
+
const usesSvcCodec = codecInUse && isSvcCodec(codecInUse);
|
|
5822
5820
|
let changed = false;
|
|
5823
5821
|
for (const encoder of params.encodings) {
|
|
5824
5822
|
const layer = usesSvcCodec
|
|
@@ -5876,40 +5874,32 @@ class Publisher extends BasePeerConnection {
|
|
|
5876
5874
|
}
|
|
5877
5875
|
await this.negotiate({ iceRestart: true });
|
|
5878
5876
|
};
|
|
5879
|
-
this.onNegotiationNeeded = () => {
|
|
5880
|
-
withCancellation('publisher.negotiate', (signal) => this.negotiate().catch((err) => {
|
|
5881
|
-
if (signal.aborted)
|
|
5882
|
-
return;
|
|
5883
|
-
this.logger('error', `Negotiation failed.`, err);
|
|
5884
|
-
this.onUnrecoverableError?.();
|
|
5885
|
-
}));
|
|
5886
|
-
};
|
|
5887
5877
|
/**
|
|
5888
5878
|
* Initiates a new offer/answer exchange with the currently connected SFU.
|
|
5889
5879
|
*
|
|
5890
5880
|
* @param options the optional offer options to use.
|
|
5891
5881
|
*/
|
|
5892
5882
|
this.negotiate = async (options) => {
|
|
5893
|
-
|
|
5894
|
-
|
|
5895
|
-
|
|
5896
|
-
|
|
5897
|
-
|
|
5898
|
-
|
|
5899
|
-
|
|
5900
|
-
|
|
5901
|
-
|
|
5902
|
-
|
|
5903
|
-
|
|
5904
|
-
|
|
5905
|
-
|
|
5906
|
-
|
|
5907
|
-
|
|
5908
|
-
|
|
5909
|
-
|
|
5910
|
-
|
|
5911
|
-
|
|
5912
|
-
|
|
5883
|
+
return withoutConcurrency('publisher.negotiate', async () => {
|
|
5884
|
+
const offer = await this.pc.createOffer(options);
|
|
5885
|
+
const tracks = this.getAnnouncedTracks(offer.sdp);
|
|
5886
|
+
if (!tracks.length)
|
|
5887
|
+
throw new Error(`Can't negotiate without any tracks`);
|
|
5888
|
+
try {
|
|
5889
|
+
this.isIceRestarting = options?.iceRestart ?? false;
|
|
5890
|
+
await this.pc.setLocalDescription(offer);
|
|
5891
|
+
const { sdp = '' } = offer;
|
|
5892
|
+
const { response } = await this.sfuClient.setPublisher({ sdp, tracks });
|
|
5893
|
+
if (response.error)
|
|
5894
|
+
throw new Error(response.error.message);
|
|
5895
|
+
const { sdp: answerSdp } = response;
|
|
5896
|
+
await this.pc.setRemoteDescription({ type: 'answer', sdp: answerSdp });
|
|
5897
|
+
}
|
|
5898
|
+
finally {
|
|
5899
|
+
this.isIceRestarting = false;
|
|
5900
|
+
}
|
|
5901
|
+
this.addTrickledIceCandidates();
|
|
5902
|
+
});
|
|
5913
5903
|
};
|
|
5914
5904
|
/**
|
|
5915
5905
|
* Returns a list of tracks that are currently being published.
|
|
@@ -5993,7 +5983,6 @@ class Publisher extends BasePeerConnection {
|
|
|
5993
5983
|
this.clonedTracks.delete(track);
|
|
5994
5984
|
};
|
|
5995
5985
|
this.publishOptions = publishOptions;
|
|
5996
|
-
this.pc.addEventListener('negotiationneeded', this.onNegotiationNeeded);
|
|
5997
5986
|
this.on('iceRestart', (iceRestart) => {
|
|
5998
5987
|
if (iceRestart.peerType !== PeerType.PUBLISHER_UNSPECIFIED)
|
|
5999
5988
|
return;
|
|
@@ -6012,17 +6001,6 @@ class Publisher extends BasePeerConnection {
|
|
|
6012
6001
|
return this.syncPublishOptions();
|
|
6013
6002
|
});
|
|
6014
6003
|
}
|
|
6015
|
-
/**
|
|
6016
|
-
* Detaches the event handlers from the `RTCPeerConnection`.
|
|
6017
|
-
* This is useful when we want to replace the `RTCPeerConnection`
|
|
6018
|
-
* instance with a new one (in case of migration).
|
|
6019
|
-
*/
|
|
6020
|
-
detachEventHandlers() {
|
|
6021
|
-
super.detachEventHandlers();
|
|
6022
|
-
this.pc.removeEventListener('negotiationneeded', this.onNegotiationNeeded);
|
|
6023
|
-
// abort any ongoing negotiation
|
|
6024
|
-
withCancellation('publisher.negotiate', () => Promise.resolve());
|
|
6025
|
-
}
|
|
6026
6004
|
/**
|
|
6027
6005
|
* Disposes this Publisher instance.
|
|
6028
6006
|
*/
|
|
@@ -7454,7 +7432,7 @@ const aggregate = (stats) => {
|
|
|
7454
7432
|
return report;
|
|
7455
7433
|
};
|
|
7456
7434
|
|
|
7457
|
-
const version = "1.18.
|
|
7435
|
+
const version = "1.18.6";
|
|
7458
7436
|
const [major, minor, patch] = version.split('.');
|
|
7459
7437
|
let sdkInfo = {
|
|
7460
7438
|
type: SdkType.PLAIN_JAVASCRIPT,
|
|
@@ -8388,6 +8366,7 @@ class BrowserPermission {
|
|
|
8388
8366
|
}
|
|
8389
8367
|
try {
|
|
8390
8368
|
this.wasPrompted = true;
|
|
8369
|
+
this.setState('prompting');
|
|
8391
8370
|
const stream = await navigator.mediaDevices.getUserMedia(this.permission.constraints);
|
|
8392
8371
|
disposeOfMediaStream(stream);
|
|
8393
8372
|
this.setState('granted');
|
|
@@ -8411,6 +8390,7 @@ class BrowserPermission {
|
|
|
8411
8390
|
error: e,
|
|
8412
8391
|
permission: this.permission,
|
|
8413
8392
|
});
|
|
8393
|
+
this.setState('prompt');
|
|
8414
8394
|
throw e;
|
|
8415
8395
|
}
|
|
8416
8396
|
});
|
|
@@ -8422,13 +8402,19 @@ class BrowserPermission {
|
|
|
8422
8402
|
return () => this.listeners.delete(cb);
|
|
8423
8403
|
}
|
|
8424
8404
|
asObservable() {
|
|
8425
|
-
return
|
|
8405
|
+
return this.getStateObservable().pipe(
|
|
8426
8406
|
// In some browsers, the 'change' event doesn't reliably emit and hence,
|
|
8427
8407
|
// permissionState stays in 'prompt' state forever.
|
|
8428
8408
|
// Typically, this happens when a user grants one-time permission.
|
|
8429
8409
|
// Instead of checking if a permission is granted, we check if it isn't denied
|
|
8430
8410
|
map((state) => state !== 'denied'));
|
|
8431
8411
|
}
|
|
8412
|
+
getIsPromptingObservable() {
|
|
8413
|
+
return this.getStateObservable().pipe(map((state) => state === 'prompting'));
|
|
8414
|
+
}
|
|
8415
|
+
getStateObservable() {
|
|
8416
|
+
return fromEventPattern((handler) => this.listen(handler), (handler, unlisten) => unlisten());
|
|
8417
|
+
}
|
|
8432
8418
|
setState(state) {
|
|
8433
8419
|
if (this.state !== state) {
|
|
8434
8420
|
this.state = state;
|
|
@@ -9184,6 +9170,9 @@ class InputMediaDeviceManagerState {
|
|
|
9184
9170
|
this.hasBrowserPermission$ = permission
|
|
9185
9171
|
? permission.asObservable().pipe(shareReplay(1))
|
|
9186
9172
|
: of(true);
|
|
9173
|
+
this.isPromptingPermission$ = permission
|
|
9174
|
+
? permission.getIsPromptingObservable().pipe(shareReplay(1))
|
|
9175
|
+
: of(false);
|
|
9187
9176
|
}
|
|
9188
9177
|
/**
|
|
9189
9178
|
* The device status
|
|
@@ -13082,7 +13071,7 @@ class StreamClient {
|
|
|
13082
13071
|
this.getUserAgent = () => {
|
|
13083
13072
|
if (!this.cachedUserAgent) {
|
|
13084
13073
|
const { clientAppIdentifier = {} } = this.options;
|
|
13085
|
-
const { sdkName = 'js', sdkVersion = "1.18.
|
|
13074
|
+
const { sdkName = 'js', sdkVersion = "1.18.6", ...extras } = clientAppIdentifier;
|
|
13086
13075
|
this.cachedUserAgent = [
|
|
13087
13076
|
`stream-video-${sdkName}-v${sdkVersion}`,
|
|
13088
13077
|
...Object.entries(extras).map(([key, value]) => `${key}=${value}`),
|