@stream-io/video-client 1.7.4 → 1.8.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 +14 -0
- package/dist/index.browser.es.js +191 -94
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +190 -93
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +191 -94
- package/dist/index.es.js.map +1 -1
- package/dist/src/Call.d.ts +18 -14
- package/dist/src/helpers/DynascaleManager.d.ts +41 -4
- package/dist/src/helpers/RNSpeechDetector.d.ts +2 -0
- package/dist/src/store/CallState.d.ts +11 -1
- package/package.json +1 -1
- package/src/Call.ts +45 -121
- package/src/events/call.ts +2 -3
- package/src/helpers/DynascaleManager.ts +176 -20
- package/src/helpers/RNSpeechDetector.ts +18 -0
- package/src/helpers/__tests__/DynascaleManager.test.ts +79 -112
- package/src/store/CallState.ts +43 -3
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.8.1](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.8.0...@stream-io/video-client-1.8.1) (2024-10-10)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* mic not fully released in some cases ([#1515](https://github.com/GetStream/stream-video-js/issues/1515)) ([b7bf90b](https://github.com/GetStream/stream-video-js/commit/b7bf90b9b1a83fb80d01a82ebee8754343963ae5))
|
|
11
|
+
|
|
12
|
+
## [1.8.0](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.7.4...@stream-io/video-client-1.8.0) (2024-10-02)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Features
|
|
16
|
+
|
|
17
|
+
* manual video quality selection ([#1486](https://github.com/GetStream/stream-video-js/issues/1486)) ([3a754af](https://github.com/GetStream/stream-video-js/commit/3a754afa1bd13d038b1023520ec8a5296ad2669e))
|
|
18
|
+
|
|
5
19
|
## [1.7.4](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.7.3...@stream-io/video-client-1.7.4) (2024-10-02)
|
|
6
20
|
|
|
7
21
|
|
package/dist/index.browser.es.js
CHANGED
|
@@ -5,7 +5,7 @@ import axios, { AxiosHeaders } from 'axios';
|
|
|
5
5
|
export { AxiosError } from 'axios';
|
|
6
6
|
import { TwirpFetchTransport, TwirpErrorCode } from '@protobuf-ts/twirp-transport';
|
|
7
7
|
import { UAParser } from 'ua-parser-js';
|
|
8
|
-
import { ReplaySubject, combineLatest, BehaviorSubject, map as map$1, shareReplay, distinctUntilChanged, takeWhile, distinctUntilKeyChanged, fromEventPattern, startWith, concatMap, merge, from, fromEvent, debounceTime, pairwise, of
|
|
8
|
+
import { ReplaySubject, combineLatest, BehaviorSubject, map as map$1, shareReplay, distinctUntilChanged, takeWhile, distinctUntilKeyChanged, fromEventPattern, startWith, concatMap, merge, from, fromEvent, debounceTime, pairwise, of } from 'rxjs';
|
|
9
9
|
import * as SDP from 'sdp-transform';
|
|
10
10
|
import WebSocket$1 from 'isomorphic-ws';
|
|
11
11
|
import { fromByteArray } from 'base64-js';
|
|
@@ -3020,7 +3020,7 @@ const retryable = async (rpc, signal) => {
|
|
|
3020
3020
|
return result;
|
|
3021
3021
|
};
|
|
3022
3022
|
|
|
3023
|
-
const version = "1.
|
|
3023
|
+
const version = "1.8.1";
|
|
3024
3024
|
const [major, minor, patch] = version.split('.');
|
|
3025
3025
|
let sdkInfo = {
|
|
3026
3026
|
type: SdkType.PLAIN_JAVASCRIPT,
|
|
@@ -4291,6 +4291,34 @@ class CallState {
|
|
|
4291
4291
|
return p;
|
|
4292
4292
|
}));
|
|
4293
4293
|
};
|
|
4294
|
+
/**
|
|
4295
|
+
* Update track subscription configuration for one or more participants.
|
|
4296
|
+
* You have to create a subscription for each participant for all the different kinds of tracks you want to receive.
|
|
4297
|
+
* You can only subscribe for tracks after the participant started publishing the given kind of track.
|
|
4298
|
+
*
|
|
4299
|
+
* @param trackType the kind of subscription to update.
|
|
4300
|
+
* @param changes the list of subscription changes to do.
|
|
4301
|
+
* @param type the debounce type to use for the update.
|
|
4302
|
+
*/
|
|
4303
|
+
this.updateParticipantTracks = (trackType, changes) => {
|
|
4304
|
+
return this.updateParticipants(Object.entries(changes).reduce((acc, [sessionId, change]) => {
|
|
4305
|
+
if (change.dimension) {
|
|
4306
|
+
change.dimension.height = Math.ceil(change.dimension.height);
|
|
4307
|
+
change.dimension.width = Math.ceil(change.dimension.width);
|
|
4308
|
+
}
|
|
4309
|
+
const prop = trackType === 'videoTrack'
|
|
4310
|
+
? 'videoDimension'
|
|
4311
|
+
: trackType === 'screenShareTrack'
|
|
4312
|
+
? 'screenShareDimension'
|
|
4313
|
+
: undefined;
|
|
4314
|
+
if (prop) {
|
|
4315
|
+
acc[sessionId] = {
|
|
4316
|
+
[prop]: change.dimension,
|
|
4317
|
+
};
|
|
4318
|
+
}
|
|
4319
|
+
return acc;
|
|
4320
|
+
}, {}));
|
|
4321
|
+
};
|
|
4294
4322
|
/**
|
|
4295
4323
|
* Updates the call state with the data received from the server.
|
|
4296
4324
|
*
|
|
@@ -6264,9 +6292,8 @@ const watchCallRejected = (call) => {
|
|
|
6264
6292
|
const watchCallEnded = (call) => {
|
|
6265
6293
|
return function onCallEnded() {
|
|
6266
6294
|
const { callingState } = call.state;
|
|
6267
|
-
if (callingState
|
|
6268
|
-
callingState
|
|
6269
|
-
callingState === CallingState.JOINING) {
|
|
6295
|
+
if (callingState !== CallingState.IDLE &&
|
|
6296
|
+
callingState !== CallingState.LEFT) {
|
|
6270
6297
|
call.leave({ reason: 'call.ended event received' }).catch((err) => {
|
|
6271
6298
|
call.logger('error', 'Failed to leave call after call.ended ', err);
|
|
6272
6299
|
});
|
|
@@ -7071,6 +7098,7 @@ const DEFAULT_VIEWPORT_VISIBILITY_STATE = {
|
|
|
7071
7098
|
videoTrack: VisibilityState.UNKNOWN,
|
|
7072
7099
|
screenShareTrack: VisibilityState.UNKNOWN,
|
|
7073
7100
|
};
|
|
7101
|
+
const globalOverrideKey = Symbol('globalOverrideKey');
|
|
7074
7102
|
/**
|
|
7075
7103
|
* A manager class that handles dynascale related tasks like:
|
|
7076
7104
|
*
|
|
@@ -7087,12 +7115,64 @@ class DynascaleManager {
|
|
|
7087
7115
|
*
|
|
7088
7116
|
* @param call the call to manage.
|
|
7089
7117
|
*/
|
|
7090
|
-
constructor(
|
|
7118
|
+
constructor(callState, speaker) {
|
|
7091
7119
|
/**
|
|
7092
7120
|
* The viewport tracker instance.
|
|
7093
7121
|
*/
|
|
7094
7122
|
this.viewportTracker = new ViewportTracker();
|
|
7095
7123
|
this.logger = getLogger(['DynascaleManager']);
|
|
7124
|
+
this.pendingSubscriptionsUpdate = null;
|
|
7125
|
+
this.videoTrackSubscriptionOverridesSubject = new BehaviorSubject({});
|
|
7126
|
+
this.videoTrackSubscriptionOverrides$ = this.videoTrackSubscriptionOverridesSubject.asObservable();
|
|
7127
|
+
this.incomingVideoSettings$ = this.videoTrackSubscriptionOverrides$.pipe(map$1((overrides) => {
|
|
7128
|
+
const { [globalOverrideKey]: globalSettings, ...participants } = overrides;
|
|
7129
|
+
return {
|
|
7130
|
+
enabled: globalSettings?.enabled !== false,
|
|
7131
|
+
preferredResolution: globalSettings?.enabled
|
|
7132
|
+
? globalSettings.dimension
|
|
7133
|
+
: undefined,
|
|
7134
|
+
participants: Object.fromEntries(Object.entries(participants).map(([sessionId, participantOverride]) => [
|
|
7135
|
+
sessionId,
|
|
7136
|
+
{
|
|
7137
|
+
enabled: participantOverride?.enabled !== false,
|
|
7138
|
+
preferredResolution: participantOverride?.enabled
|
|
7139
|
+
? participantOverride.dimension
|
|
7140
|
+
: undefined,
|
|
7141
|
+
},
|
|
7142
|
+
])),
|
|
7143
|
+
isParticipantVideoEnabled: (sessionId) => overrides[sessionId]?.enabled ??
|
|
7144
|
+
overrides[globalOverrideKey]?.enabled ??
|
|
7145
|
+
true,
|
|
7146
|
+
};
|
|
7147
|
+
}), shareReplay(1));
|
|
7148
|
+
this.setVideoTrackSubscriptionOverrides = (override, sessionIds) => {
|
|
7149
|
+
if (!sessionIds) {
|
|
7150
|
+
return setCurrentValue(this.videoTrackSubscriptionOverridesSubject, override ? { [globalOverrideKey]: override } : {});
|
|
7151
|
+
}
|
|
7152
|
+
return setCurrentValue(this.videoTrackSubscriptionOverridesSubject, (overrides) => ({
|
|
7153
|
+
...overrides,
|
|
7154
|
+
...Object.fromEntries(sessionIds.map((id) => [id, override])),
|
|
7155
|
+
}));
|
|
7156
|
+
};
|
|
7157
|
+
this.applyTrackSubscriptions = (debounceType = DebounceType.SLOW) => {
|
|
7158
|
+
if (this.pendingSubscriptionsUpdate) {
|
|
7159
|
+
clearTimeout(this.pendingSubscriptionsUpdate);
|
|
7160
|
+
}
|
|
7161
|
+
const updateSubscriptions = () => {
|
|
7162
|
+
this.pendingSubscriptionsUpdate = null;
|
|
7163
|
+
this.sfuClient
|
|
7164
|
+
?.updateSubscriptions(this.trackSubscriptions)
|
|
7165
|
+
.catch((err) => {
|
|
7166
|
+
this.logger('debug', `Failed to update track subscriptions`, err);
|
|
7167
|
+
});
|
|
7168
|
+
};
|
|
7169
|
+
if (debounceType) {
|
|
7170
|
+
this.pendingSubscriptionsUpdate = setTimeout(updateSubscriptions, debounceType);
|
|
7171
|
+
}
|
|
7172
|
+
else {
|
|
7173
|
+
updateSubscriptions();
|
|
7174
|
+
}
|
|
7175
|
+
};
|
|
7096
7176
|
/**
|
|
7097
7177
|
* Will begin tracking the given element for visibility changes within the
|
|
7098
7178
|
* configured viewport element (`call.setViewport`).
|
|
@@ -7104,7 +7184,7 @@ class DynascaleManager {
|
|
|
7104
7184
|
*/
|
|
7105
7185
|
this.trackElementVisibility = (element, sessionId, trackType) => {
|
|
7106
7186
|
const cleanup = this.viewportTracker.observe(element, (entry) => {
|
|
7107
|
-
this.
|
|
7187
|
+
this.callState.updateParticipant(sessionId, (participant) => {
|
|
7108
7188
|
const previousVisibilityState = participant.viewportVisibilityState ??
|
|
7109
7189
|
DEFAULT_VIEWPORT_VISIBILITY_STATE;
|
|
7110
7190
|
// observer triggers when the element is "moved" to be a fullscreen element
|
|
@@ -7126,7 +7206,7 @@ class DynascaleManager {
|
|
|
7126
7206
|
// reset visibility state to UNKNOWN upon cleanup
|
|
7127
7207
|
// so that the layouts that are not actively observed
|
|
7128
7208
|
// can still function normally (runtime layout switching)
|
|
7129
|
-
this.
|
|
7209
|
+
this.callState.updateParticipant(sessionId, (participant) => {
|
|
7130
7210
|
const previousVisibilityState = participant.viewportVisibilityState ??
|
|
7131
7211
|
DEFAULT_VIEWPORT_VISIBILITY_STATE;
|
|
7132
7212
|
return {
|
|
@@ -7163,7 +7243,7 @@ class DynascaleManager {
|
|
|
7163
7243
|
* @param trackType the kind of video.
|
|
7164
7244
|
*/
|
|
7165
7245
|
this.bindVideoElement = (videoElement, sessionId, trackType) => {
|
|
7166
|
-
const boundParticipant = this.
|
|
7246
|
+
const boundParticipant = this.callState.findParticipantBySessionId(sessionId);
|
|
7167
7247
|
if (!boundParticipant)
|
|
7168
7248
|
return;
|
|
7169
7249
|
const requestTrackWithDimensions = (debounceType, dimension) => {
|
|
@@ -7175,9 +7255,12 @@ class DynascaleManager {
|
|
|
7175
7255
|
this.logger('debug', `Ignoring 0x0 dimension`, boundParticipant);
|
|
7176
7256
|
dimension = undefined;
|
|
7177
7257
|
}
|
|
7178
|
-
this.
|
|
7258
|
+
this.callState.updateParticipantTracks(trackType, {
|
|
7259
|
+
[sessionId]: { dimension },
|
|
7260
|
+
});
|
|
7261
|
+
this.applyTrackSubscriptions(debounceType);
|
|
7179
7262
|
};
|
|
7180
|
-
const participant$ = this.
|
|
7263
|
+
const participant$ = this.callState.participants$.pipe(map$1((participants) => participants.find((participant) => participant.sessionId === sessionId)), takeWhile((participant) => !!participant), distinctUntilChanged(), shareReplay({ bufferSize: 1, refCount: true }));
|
|
7181
7264
|
/**
|
|
7182
7265
|
* Since the video elements are now being removed from the DOM (React SDK) upon
|
|
7183
7266
|
* visibility change, this subscription is not in use an stays here only for the
|
|
@@ -7292,10 +7375,10 @@ class DynascaleManager {
|
|
|
7292
7375
|
* @returns a cleanup function that will unbind the audio element.
|
|
7293
7376
|
*/
|
|
7294
7377
|
this.bindAudioElement = (audioElement, sessionId, trackType) => {
|
|
7295
|
-
const participant = this.
|
|
7378
|
+
const participant = this.callState.findParticipantBySessionId(sessionId);
|
|
7296
7379
|
if (!participant || participant.isLocalParticipant)
|
|
7297
7380
|
return;
|
|
7298
|
-
const participant$ = this.
|
|
7381
|
+
const participant$ = this.callState.participants$.pipe(map$1((participants) => participants.find((p) => p.sessionId === sessionId)), takeWhile((p) => !!p), distinctUntilChanged(), shareReplay({ bufferSize: 1, refCount: true }));
|
|
7299
7382
|
const updateMediaStreamSubscription = participant$
|
|
7300
7383
|
.pipe(distinctUntilKeyChanged(trackType === 'screenShareAudioTrack'
|
|
7301
7384
|
? 'screenShareAudioStream'
|
|
@@ -7315,7 +7398,7 @@ class DynascaleManager {
|
|
|
7315
7398
|
// audio output device shall be set after the audio element is played
|
|
7316
7399
|
// otherwise, the browser will not pick it up, and will always
|
|
7317
7400
|
// play audio through the system's default device
|
|
7318
|
-
const { selectedDevice } = this.
|
|
7401
|
+
const { selectedDevice } = this.speaker.state;
|
|
7319
7402
|
if (selectedDevice && 'setSinkId' in audioElement) {
|
|
7320
7403
|
audioElement.setSinkId(selectedDevice);
|
|
7321
7404
|
}
|
|
@@ -7324,13 +7407,13 @@ class DynascaleManager {
|
|
|
7324
7407
|
});
|
|
7325
7408
|
const sinkIdSubscription = !('setSinkId' in audioElement)
|
|
7326
7409
|
? null
|
|
7327
|
-
: this.
|
|
7410
|
+
: this.speaker.state.selectedDevice$.subscribe((deviceId) => {
|
|
7328
7411
|
if (deviceId) {
|
|
7329
7412
|
audioElement.setSinkId(deviceId);
|
|
7330
7413
|
}
|
|
7331
7414
|
});
|
|
7332
7415
|
const volumeSubscription = combineLatest([
|
|
7333
|
-
this.
|
|
7416
|
+
this.speaker.state.volume$,
|
|
7334
7417
|
participant$.pipe(distinctUntilKeyChanged('audioVolume')),
|
|
7335
7418
|
]).subscribe(([volume, p]) => {
|
|
7336
7419
|
audioElement.volume = p.audioVolume ?? volume;
|
|
@@ -7342,7 +7425,50 @@ class DynascaleManager {
|
|
|
7342
7425
|
updateMediaStreamSubscription.unsubscribe();
|
|
7343
7426
|
};
|
|
7344
7427
|
};
|
|
7345
|
-
this.
|
|
7428
|
+
this.callState = callState;
|
|
7429
|
+
this.speaker = speaker;
|
|
7430
|
+
}
|
|
7431
|
+
setSfuClient(sfuClient) {
|
|
7432
|
+
this.sfuClient = sfuClient;
|
|
7433
|
+
}
|
|
7434
|
+
get trackSubscriptions() {
|
|
7435
|
+
const subscriptions = [];
|
|
7436
|
+
for (const p of this.callState.remoteParticipants) {
|
|
7437
|
+
// NOTE: audio tracks don't have to be requested explicitly
|
|
7438
|
+
// as the SFU will implicitly subscribe us to all of them,
|
|
7439
|
+
// once they become available.
|
|
7440
|
+
if (p.videoDimension && hasVideo(p)) {
|
|
7441
|
+
const override = this.videoTrackSubscriptionOverrides[p.sessionId] ??
|
|
7442
|
+
this.videoTrackSubscriptionOverrides[globalOverrideKey];
|
|
7443
|
+
if (override?.enabled !== false) {
|
|
7444
|
+
subscriptions.push({
|
|
7445
|
+
userId: p.userId,
|
|
7446
|
+
sessionId: p.sessionId,
|
|
7447
|
+
trackType: TrackType.VIDEO,
|
|
7448
|
+
dimension: override?.dimension ?? p.videoDimension,
|
|
7449
|
+
});
|
|
7450
|
+
}
|
|
7451
|
+
}
|
|
7452
|
+
if (p.screenShareDimension && hasScreenShare(p)) {
|
|
7453
|
+
subscriptions.push({
|
|
7454
|
+
userId: p.userId,
|
|
7455
|
+
sessionId: p.sessionId,
|
|
7456
|
+
trackType: TrackType.SCREEN_SHARE,
|
|
7457
|
+
dimension: p.screenShareDimension,
|
|
7458
|
+
});
|
|
7459
|
+
}
|
|
7460
|
+
if (hasScreenShareAudio(p)) {
|
|
7461
|
+
subscriptions.push({
|
|
7462
|
+
userId: p.userId,
|
|
7463
|
+
sessionId: p.sessionId,
|
|
7464
|
+
trackType: TrackType.SCREEN_SHARE_AUDIO,
|
|
7465
|
+
});
|
|
7466
|
+
}
|
|
7467
|
+
}
|
|
7468
|
+
return subscriptions;
|
|
7469
|
+
}
|
|
7470
|
+
get videoTrackSubscriptionOverrides() {
|
|
7471
|
+
return getCurrentValue(this.videoTrackSubscriptionOverrides$);
|
|
7346
7472
|
}
|
|
7347
7473
|
}
|
|
7348
7474
|
|
|
@@ -8599,9 +8725,11 @@ class RNSpeechDetector {
|
|
|
8599
8725
|
*/
|
|
8600
8726
|
async start() {
|
|
8601
8727
|
try {
|
|
8728
|
+
this.cleanupAudioStream();
|
|
8602
8729
|
const audioStream = await navigator.mediaDevices.getUserMedia({
|
|
8603
8730
|
audio: true,
|
|
8604
8731
|
});
|
|
8732
|
+
this.audioStream = audioStream;
|
|
8605
8733
|
this.pc1.addEventListener('icecandidate', async (e) => {
|
|
8606
8734
|
await this.pc2.addIceCandidate(e.candidate);
|
|
8607
8735
|
});
|
|
@@ -8631,6 +8759,7 @@ class RNSpeechDetector {
|
|
|
8631
8759
|
stop() {
|
|
8632
8760
|
this.pc1.close();
|
|
8633
8761
|
this.pc2.close();
|
|
8762
|
+
this.cleanupAudioStream();
|
|
8634
8763
|
if (this.intervalId) {
|
|
8635
8764
|
clearInterval(this.intervalId);
|
|
8636
8765
|
}
|
|
@@ -8667,6 +8796,18 @@ class RNSpeechDetector {
|
|
|
8667
8796
|
clearInterval(this.intervalId);
|
|
8668
8797
|
};
|
|
8669
8798
|
}
|
|
8799
|
+
cleanupAudioStream() {
|
|
8800
|
+
if (!this.audioStream) {
|
|
8801
|
+
return;
|
|
8802
|
+
}
|
|
8803
|
+
this.audioStream.getTracks().forEach((track) => track.stop());
|
|
8804
|
+
if (
|
|
8805
|
+
// @ts-expect-error release() is present in react-native-webrtc
|
|
8806
|
+
typeof this.audioStream.release === 'function') {
|
|
8807
|
+
// @ts-expect-error called to dispose the stream in RN
|
|
8808
|
+
this.audioStream.release();
|
|
8809
|
+
}
|
|
8810
|
+
}
|
|
8670
8811
|
}
|
|
8671
8812
|
|
|
8672
8813
|
class MicrophoneManager extends InputMediaDeviceManager {
|
|
@@ -9161,10 +9302,6 @@ class Call {
|
|
|
9161
9302
|
* The state of this call.
|
|
9162
9303
|
*/
|
|
9163
9304
|
this.state = new CallState();
|
|
9164
|
-
/**
|
|
9165
|
-
* The DynascaleManager instance.
|
|
9166
|
-
*/
|
|
9167
|
-
this.dynascaleManager = new DynascaleManager(this);
|
|
9168
9305
|
/**
|
|
9169
9306
|
* The permissions context of this call.
|
|
9170
9307
|
*/
|
|
@@ -9174,7 +9311,6 @@ class Call {
|
|
|
9174
9311
|
* @private
|
|
9175
9312
|
*/
|
|
9176
9313
|
this.dispatcher = new Dispatcher();
|
|
9177
|
-
this.trackSubscriptionsSubject = new BehaviorSubject({ type: DebounceType.MEDIUM, data: [] });
|
|
9178
9314
|
this.sfuClientTag = 0;
|
|
9179
9315
|
this.reconnectConcurrencyTag = Symbol('reconnectConcurrencyTag');
|
|
9180
9316
|
this.reconnectAttempts = 0;
|
|
@@ -9315,6 +9451,7 @@ class Call {
|
|
|
9315
9451
|
this.publisher = undefined;
|
|
9316
9452
|
await this.sfuClient?.leaveAndClose(reason);
|
|
9317
9453
|
this.sfuClient = undefined;
|
|
9454
|
+
this.dynascaleManager.setSfuClient(undefined);
|
|
9318
9455
|
this.state.setCallingState(CallingState.LEFT);
|
|
9319
9456
|
// Call all leave call hooks, e.g. to clean up global event handlers
|
|
9320
9457
|
this.leaveCallHooks.forEach((hook) => hook());
|
|
@@ -9477,6 +9614,7 @@ class Call {
|
|
|
9477
9614
|
})
|
|
9478
9615
|
: previousSfuClient;
|
|
9479
9616
|
this.sfuClient = sfuClient;
|
|
9617
|
+
this.dynascaleManager.setSfuClient(sfuClient);
|
|
9480
9618
|
const clientDetails = getClientDetails();
|
|
9481
9619
|
// we don't need to send JoinRequest if we are re-using an existing healthy SFU client
|
|
9482
9620
|
if (previousSfuClient !== sfuClient) {
|
|
@@ -9549,11 +9687,10 @@ class Call {
|
|
|
9549
9687
|
const strategy = this.reconnectStrategy;
|
|
9550
9688
|
const performingRejoin = strategy === WebsocketReconnectStrategy.REJOIN;
|
|
9551
9689
|
const announcedTracks = this.publisher?.getAnnouncedTracks() || [];
|
|
9552
|
-
const subscribedTracks = getCurrentValue(this.trackSubscriptionsSubject);
|
|
9553
9690
|
return {
|
|
9554
9691
|
strategy,
|
|
9555
9692
|
announcedTracks,
|
|
9556
|
-
subscriptions:
|
|
9693
|
+
subscriptions: this.dynascaleManager.trackSubscriptions,
|
|
9557
9694
|
reconnectAttempt: this.reconnectAttempts,
|
|
9558
9695
|
fromSfuId: migratingFromSfuId || '',
|
|
9559
9696
|
previousSessionId: performingRejoin ? previousSessionId || '' : '',
|
|
@@ -9923,7 +10060,7 @@ class Call {
|
|
|
9923
10060
|
const { remoteParticipants } = this.state;
|
|
9924
10061
|
if (remoteParticipants.length <= 0)
|
|
9925
10062
|
return;
|
|
9926
|
-
this.
|
|
10063
|
+
this.dynascaleManager.applyTrackSubscriptions(undefined);
|
|
9927
10064
|
};
|
|
9928
10065
|
/**
|
|
9929
10066
|
* Starts publishing the given video stream to the call.
|
|
@@ -10046,71 +10183,6 @@ class Call {
|
|
|
10046
10183
|
this.logger('warn', 'Failed to notify stop of noise cancellation', err);
|
|
10047
10184
|
});
|
|
10048
10185
|
};
|
|
10049
|
-
/**
|
|
10050
|
-
* Update track subscription configuration for one or more participants.
|
|
10051
|
-
* You have to create a subscription for each participant for all the different kinds of tracks you want to receive.
|
|
10052
|
-
* You can only subscribe for tracks after the participant started publishing the given kind of track.
|
|
10053
|
-
*
|
|
10054
|
-
* @param trackType the kind of subscription to update.
|
|
10055
|
-
* @param changes the list of subscription changes to do.
|
|
10056
|
-
* @param type the debounce type to use for the update.
|
|
10057
|
-
*/
|
|
10058
|
-
this.updateSubscriptionsPartial = (trackType, changes, type = DebounceType.SLOW) => {
|
|
10059
|
-
const participants = this.state.updateParticipants(Object.entries(changes).reduce((acc, [sessionId, change]) => {
|
|
10060
|
-
if (change.dimension) {
|
|
10061
|
-
change.dimension.height = Math.ceil(change.dimension.height);
|
|
10062
|
-
change.dimension.width = Math.ceil(change.dimension.width);
|
|
10063
|
-
}
|
|
10064
|
-
const prop = trackType === 'videoTrack'
|
|
10065
|
-
? 'videoDimension'
|
|
10066
|
-
: trackType === 'screenShareTrack'
|
|
10067
|
-
? 'screenShareDimension'
|
|
10068
|
-
: undefined;
|
|
10069
|
-
if (prop) {
|
|
10070
|
-
acc[sessionId] = {
|
|
10071
|
-
[prop]: change.dimension,
|
|
10072
|
-
};
|
|
10073
|
-
}
|
|
10074
|
-
return acc;
|
|
10075
|
-
}, {}));
|
|
10076
|
-
this.updateSubscriptions(participants, type);
|
|
10077
|
-
};
|
|
10078
|
-
this.updateSubscriptions = (participants, type = DebounceType.SLOW) => {
|
|
10079
|
-
const subscriptions = [];
|
|
10080
|
-
for (const p of participants) {
|
|
10081
|
-
// we don't want to subscribe to our own tracks
|
|
10082
|
-
if (p.isLocalParticipant)
|
|
10083
|
-
continue;
|
|
10084
|
-
// NOTE: audio tracks don't have to be requested explicitly
|
|
10085
|
-
// as the SFU will implicitly subscribe us to all of them,
|
|
10086
|
-
// once they become available.
|
|
10087
|
-
if (p.videoDimension && hasVideo(p)) {
|
|
10088
|
-
subscriptions.push({
|
|
10089
|
-
userId: p.userId,
|
|
10090
|
-
sessionId: p.sessionId,
|
|
10091
|
-
trackType: TrackType.VIDEO,
|
|
10092
|
-
dimension: p.videoDimension,
|
|
10093
|
-
});
|
|
10094
|
-
}
|
|
10095
|
-
if (p.screenShareDimension && hasScreenShare(p)) {
|
|
10096
|
-
subscriptions.push({
|
|
10097
|
-
userId: p.userId,
|
|
10098
|
-
sessionId: p.sessionId,
|
|
10099
|
-
trackType: TrackType.SCREEN_SHARE,
|
|
10100
|
-
dimension: p.screenShareDimension,
|
|
10101
|
-
});
|
|
10102
|
-
}
|
|
10103
|
-
if (hasScreenShareAudio(p)) {
|
|
10104
|
-
subscriptions.push({
|
|
10105
|
-
userId: p.userId,
|
|
10106
|
-
sessionId: p.sessionId,
|
|
10107
|
-
trackType: TrackType.SCREEN_SHARE_AUDIO,
|
|
10108
|
-
});
|
|
10109
|
-
}
|
|
10110
|
-
}
|
|
10111
|
-
// schedule update
|
|
10112
|
-
this.trackSubscriptionsSubject.next({ type, data: subscriptions });
|
|
10113
|
-
};
|
|
10114
10186
|
/**
|
|
10115
10187
|
* Will enhance the reported stats with additional participant-specific information (`callStatsReport$` state [store variable](./StreamVideoClient.md/#readonlystatestore)).
|
|
10116
10188
|
* This is usually helpful when detailed stats for a specific participant are needed.
|
|
@@ -10690,6 +10762,33 @@ class Call {
|
|
|
10690
10762
|
imageElement.removeEventListener('error', handleError);
|
|
10691
10763
|
};
|
|
10692
10764
|
};
|
|
10765
|
+
/**
|
|
10766
|
+
* Specify preference for incoming video resolution. The preference will
|
|
10767
|
+
* be matched as close as possible, but actual resolution will depend
|
|
10768
|
+
* on the video source quality and client network conditions. Will enable
|
|
10769
|
+
* incoming video, if previously disabled.
|
|
10770
|
+
*
|
|
10771
|
+
* @param resolution preferred resolution, or `undefined` to clear preference
|
|
10772
|
+
* @param sessionIds optionally specify session ids of the participants this
|
|
10773
|
+
* preference has effect on. Affects all participants by default.
|
|
10774
|
+
*/
|
|
10775
|
+
this.setPreferredIncomingVideoResolution = (resolution, sessionIds) => {
|
|
10776
|
+
this.dynascaleManager.setVideoTrackSubscriptionOverrides(resolution
|
|
10777
|
+
? {
|
|
10778
|
+
enabled: true,
|
|
10779
|
+
dimension: resolution,
|
|
10780
|
+
}
|
|
10781
|
+
: undefined, sessionIds);
|
|
10782
|
+
this.dynascaleManager.applyTrackSubscriptions();
|
|
10783
|
+
};
|
|
10784
|
+
/**
|
|
10785
|
+
* Enables or disables incoming video from all remote call participants,
|
|
10786
|
+
* and removes any preference for preferred resolution.
|
|
10787
|
+
*/
|
|
10788
|
+
this.setIncomingVideoEnabled = (enabled) => {
|
|
10789
|
+
this.dynascaleManager.setVideoTrackSubscriptionOverrides(enabled ? undefined : { enabled: false });
|
|
10790
|
+
this.dynascaleManager.applyTrackSubscriptions();
|
|
10791
|
+
};
|
|
10693
10792
|
this.type = type;
|
|
10694
10793
|
this.id = id;
|
|
10695
10794
|
this.cid = `${type}:${id}`;
|
|
@@ -10711,6 +10810,7 @@ class Call {
|
|
|
10711
10810
|
this.microphone = new MicrophoneManager(this);
|
|
10712
10811
|
this.speaker = new SpeakerManager(this);
|
|
10713
10812
|
this.screenShare = new ScreenShareManager(this);
|
|
10813
|
+
this.dynascaleManager = new DynascaleManager(this.state, this.speaker);
|
|
10714
10814
|
}
|
|
10715
10815
|
async setup() {
|
|
10716
10816
|
await withoutConcurrency(this.joinLeaveConcurrencyTag, async () => {
|
|
@@ -10723,9 +10823,6 @@ class Call {
|
|
|
10723
10823
|
this.leaveCallHooks.add(registerEventHandlers(this, this.dispatcher));
|
|
10724
10824
|
this.registerEffects();
|
|
10725
10825
|
this.registerReconnectHandlers();
|
|
10726
|
-
this.leaveCallHooks.add(createSubscription(this.trackSubscriptionsSubject.pipe(debounce((v) => timer(v.type)), map$1((v) => v.data)), (subscriptions) => this.sfuClient?.updateSubscriptions(subscriptions).catch((err) => {
|
|
10727
|
-
this.logger('debug', `Failed to update track subscriptions`, err);
|
|
10728
|
-
})));
|
|
10729
10826
|
if (this.state.callingState === CallingState.LEFT) {
|
|
10730
10827
|
this.state.setCallingState(CallingState.IDLE);
|
|
10731
10828
|
}
|
|
@@ -12373,7 +12470,7 @@ class StreamClient {
|
|
|
12373
12470
|
});
|
|
12374
12471
|
};
|
|
12375
12472
|
this.getUserAgent = () => {
|
|
12376
|
-
const version = "1.
|
|
12473
|
+
const version = "1.8.1";
|
|
12377
12474
|
return (this.userAgent ||
|
|
12378
12475
|
`stream-video-javascript-client-${this.node ? 'node' : 'browser'}-${version}`);
|
|
12379
12476
|
};
|