@stream-io/video-client 1.7.4 → 1.8.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 +7 -0
- package/dist/index.browser.es.js +174 -91
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +173 -90
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +174 -91
- 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/store/CallState.d.ts +11 -1
- package/package.json +1 -1
- package/src/Call.ts +45 -121
- package/src/helpers/DynascaleManager.ts +176 -20
- package/src/helpers/__tests__/DynascaleManager.test.ts +79 -112
- package/src/store/CallState.ts +43 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).
|
|
4
4
|
|
|
5
|
+
## [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)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* manual video quality selection ([#1486](https://github.com/GetStream/stream-video-js/issues/1486)) ([3a754af](https://github.com/GetStream/stream-video-js/commit/3a754afa1bd13d038b1023520ec8a5296ad2669e))
|
|
11
|
+
|
|
5
12
|
## [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
13
|
|
|
7
14
|
|
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.0";
|
|
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
|
*
|
|
@@ -7071,6 +7099,7 @@ const DEFAULT_VIEWPORT_VISIBILITY_STATE = {
|
|
|
7071
7099
|
videoTrack: VisibilityState.UNKNOWN,
|
|
7072
7100
|
screenShareTrack: VisibilityState.UNKNOWN,
|
|
7073
7101
|
};
|
|
7102
|
+
const globalOverrideKey = Symbol('globalOverrideKey');
|
|
7074
7103
|
/**
|
|
7075
7104
|
* A manager class that handles dynascale related tasks like:
|
|
7076
7105
|
*
|
|
@@ -7087,12 +7116,64 @@ class DynascaleManager {
|
|
|
7087
7116
|
*
|
|
7088
7117
|
* @param call the call to manage.
|
|
7089
7118
|
*/
|
|
7090
|
-
constructor(
|
|
7119
|
+
constructor(callState, speaker) {
|
|
7091
7120
|
/**
|
|
7092
7121
|
* The viewport tracker instance.
|
|
7093
7122
|
*/
|
|
7094
7123
|
this.viewportTracker = new ViewportTracker();
|
|
7095
7124
|
this.logger = getLogger(['DynascaleManager']);
|
|
7125
|
+
this.pendingSubscriptionsUpdate = null;
|
|
7126
|
+
this.videoTrackSubscriptionOverridesSubject = new BehaviorSubject({});
|
|
7127
|
+
this.videoTrackSubscriptionOverrides$ = this.videoTrackSubscriptionOverridesSubject.asObservable();
|
|
7128
|
+
this.incomingVideoSettings$ = this.videoTrackSubscriptionOverrides$.pipe(map$1((overrides) => {
|
|
7129
|
+
const { [globalOverrideKey]: globalSettings, ...participants } = overrides;
|
|
7130
|
+
return {
|
|
7131
|
+
enabled: globalSettings?.enabled !== false,
|
|
7132
|
+
preferredResolution: globalSettings?.enabled
|
|
7133
|
+
? globalSettings.dimension
|
|
7134
|
+
: undefined,
|
|
7135
|
+
participants: Object.fromEntries(Object.entries(participants).map(([sessionId, participantOverride]) => [
|
|
7136
|
+
sessionId,
|
|
7137
|
+
{
|
|
7138
|
+
enabled: participantOverride?.enabled !== false,
|
|
7139
|
+
preferredResolution: participantOverride?.enabled
|
|
7140
|
+
? participantOverride.dimension
|
|
7141
|
+
: undefined,
|
|
7142
|
+
},
|
|
7143
|
+
])),
|
|
7144
|
+
isParticipantVideoEnabled: (sessionId) => overrides[sessionId]?.enabled ??
|
|
7145
|
+
overrides[globalOverrideKey]?.enabled ??
|
|
7146
|
+
true,
|
|
7147
|
+
};
|
|
7148
|
+
}), shareReplay(1));
|
|
7149
|
+
this.setVideoTrackSubscriptionOverrides = (override, sessionIds) => {
|
|
7150
|
+
if (!sessionIds) {
|
|
7151
|
+
return setCurrentValue(this.videoTrackSubscriptionOverridesSubject, override ? { [globalOverrideKey]: override } : {});
|
|
7152
|
+
}
|
|
7153
|
+
return setCurrentValue(this.videoTrackSubscriptionOverridesSubject, (overrides) => ({
|
|
7154
|
+
...overrides,
|
|
7155
|
+
...Object.fromEntries(sessionIds.map((id) => [id, override])),
|
|
7156
|
+
}));
|
|
7157
|
+
};
|
|
7158
|
+
this.applyTrackSubscriptions = (debounceType = DebounceType.SLOW) => {
|
|
7159
|
+
if (this.pendingSubscriptionsUpdate) {
|
|
7160
|
+
clearTimeout(this.pendingSubscriptionsUpdate);
|
|
7161
|
+
}
|
|
7162
|
+
const updateSubscriptions = () => {
|
|
7163
|
+
this.pendingSubscriptionsUpdate = null;
|
|
7164
|
+
this.sfuClient
|
|
7165
|
+
?.updateSubscriptions(this.trackSubscriptions)
|
|
7166
|
+
.catch((err) => {
|
|
7167
|
+
this.logger('debug', `Failed to update track subscriptions`, err);
|
|
7168
|
+
});
|
|
7169
|
+
};
|
|
7170
|
+
if (debounceType) {
|
|
7171
|
+
this.pendingSubscriptionsUpdate = setTimeout(updateSubscriptions, debounceType);
|
|
7172
|
+
}
|
|
7173
|
+
else {
|
|
7174
|
+
updateSubscriptions();
|
|
7175
|
+
}
|
|
7176
|
+
};
|
|
7096
7177
|
/**
|
|
7097
7178
|
* Will begin tracking the given element for visibility changes within the
|
|
7098
7179
|
* configured viewport element (`call.setViewport`).
|
|
@@ -7104,7 +7185,7 @@ class DynascaleManager {
|
|
|
7104
7185
|
*/
|
|
7105
7186
|
this.trackElementVisibility = (element, sessionId, trackType) => {
|
|
7106
7187
|
const cleanup = this.viewportTracker.observe(element, (entry) => {
|
|
7107
|
-
this.
|
|
7188
|
+
this.callState.updateParticipant(sessionId, (participant) => {
|
|
7108
7189
|
const previousVisibilityState = participant.viewportVisibilityState ??
|
|
7109
7190
|
DEFAULT_VIEWPORT_VISIBILITY_STATE;
|
|
7110
7191
|
// observer triggers when the element is "moved" to be a fullscreen element
|
|
@@ -7126,7 +7207,7 @@ class DynascaleManager {
|
|
|
7126
7207
|
// reset visibility state to UNKNOWN upon cleanup
|
|
7127
7208
|
// so that the layouts that are not actively observed
|
|
7128
7209
|
// can still function normally (runtime layout switching)
|
|
7129
|
-
this.
|
|
7210
|
+
this.callState.updateParticipant(sessionId, (participant) => {
|
|
7130
7211
|
const previousVisibilityState = participant.viewportVisibilityState ??
|
|
7131
7212
|
DEFAULT_VIEWPORT_VISIBILITY_STATE;
|
|
7132
7213
|
return {
|
|
@@ -7163,7 +7244,7 @@ class DynascaleManager {
|
|
|
7163
7244
|
* @param trackType the kind of video.
|
|
7164
7245
|
*/
|
|
7165
7246
|
this.bindVideoElement = (videoElement, sessionId, trackType) => {
|
|
7166
|
-
const boundParticipant = this.
|
|
7247
|
+
const boundParticipant = this.callState.findParticipantBySessionId(sessionId);
|
|
7167
7248
|
if (!boundParticipant)
|
|
7168
7249
|
return;
|
|
7169
7250
|
const requestTrackWithDimensions = (debounceType, dimension) => {
|
|
@@ -7175,9 +7256,12 @@ class DynascaleManager {
|
|
|
7175
7256
|
this.logger('debug', `Ignoring 0x0 dimension`, boundParticipant);
|
|
7176
7257
|
dimension = undefined;
|
|
7177
7258
|
}
|
|
7178
|
-
this.
|
|
7259
|
+
this.callState.updateParticipantTracks(trackType, {
|
|
7260
|
+
[sessionId]: { dimension },
|
|
7261
|
+
});
|
|
7262
|
+
this.applyTrackSubscriptions(debounceType);
|
|
7179
7263
|
};
|
|
7180
|
-
const participant$ = this.
|
|
7264
|
+
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
7265
|
/**
|
|
7182
7266
|
* Since the video elements are now being removed from the DOM (React SDK) upon
|
|
7183
7267
|
* visibility change, this subscription is not in use an stays here only for the
|
|
@@ -7292,10 +7376,10 @@ class DynascaleManager {
|
|
|
7292
7376
|
* @returns a cleanup function that will unbind the audio element.
|
|
7293
7377
|
*/
|
|
7294
7378
|
this.bindAudioElement = (audioElement, sessionId, trackType) => {
|
|
7295
|
-
const participant = this.
|
|
7379
|
+
const participant = this.callState.findParticipantBySessionId(sessionId);
|
|
7296
7380
|
if (!participant || participant.isLocalParticipant)
|
|
7297
7381
|
return;
|
|
7298
|
-
const participant$ = this.
|
|
7382
|
+
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
7383
|
const updateMediaStreamSubscription = participant$
|
|
7300
7384
|
.pipe(distinctUntilKeyChanged(trackType === 'screenShareAudioTrack'
|
|
7301
7385
|
? 'screenShareAudioStream'
|
|
@@ -7315,7 +7399,7 @@ class DynascaleManager {
|
|
|
7315
7399
|
// audio output device shall be set after the audio element is played
|
|
7316
7400
|
// otherwise, the browser will not pick it up, and will always
|
|
7317
7401
|
// play audio through the system's default device
|
|
7318
|
-
const { selectedDevice } = this.
|
|
7402
|
+
const { selectedDevice } = this.speaker.state;
|
|
7319
7403
|
if (selectedDevice && 'setSinkId' in audioElement) {
|
|
7320
7404
|
audioElement.setSinkId(selectedDevice);
|
|
7321
7405
|
}
|
|
@@ -7324,13 +7408,13 @@ class DynascaleManager {
|
|
|
7324
7408
|
});
|
|
7325
7409
|
const sinkIdSubscription = !('setSinkId' in audioElement)
|
|
7326
7410
|
? null
|
|
7327
|
-
: this.
|
|
7411
|
+
: this.speaker.state.selectedDevice$.subscribe((deviceId) => {
|
|
7328
7412
|
if (deviceId) {
|
|
7329
7413
|
audioElement.setSinkId(deviceId);
|
|
7330
7414
|
}
|
|
7331
7415
|
});
|
|
7332
7416
|
const volumeSubscription = combineLatest([
|
|
7333
|
-
this.
|
|
7417
|
+
this.speaker.state.volume$,
|
|
7334
7418
|
participant$.pipe(distinctUntilKeyChanged('audioVolume')),
|
|
7335
7419
|
]).subscribe(([volume, p]) => {
|
|
7336
7420
|
audioElement.volume = p.audioVolume ?? volume;
|
|
@@ -7342,7 +7426,50 @@ class DynascaleManager {
|
|
|
7342
7426
|
updateMediaStreamSubscription.unsubscribe();
|
|
7343
7427
|
};
|
|
7344
7428
|
};
|
|
7345
|
-
this.
|
|
7429
|
+
this.callState = callState;
|
|
7430
|
+
this.speaker = speaker;
|
|
7431
|
+
}
|
|
7432
|
+
setSfuClient(sfuClient) {
|
|
7433
|
+
this.sfuClient = sfuClient;
|
|
7434
|
+
}
|
|
7435
|
+
get trackSubscriptions() {
|
|
7436
|
+
const subscriptions = [];
|
|
7437
|
+
for (const p of this.callState.remoteParticipants) {
|
|
7438
|
+
// NOTE: audio tracks don't have to be requested explicitly
|
|
7439
|
+
// as the SFU will implicitly subscribe us to all of them,
|
|
7440
|
+
// once they become available.
|
|
7441
|
+
if (p.videoDimension && hasVideo(p)) {
|
|
7442
|
+
const override = this.videoTrackSubscriptionOverrides[p.sessionId] ??
|
|
7443
|
+
this.videoTrackSubscriptionOverrides[globalOverrideKey];
|
|
7444
|
+
if (override?.enabled !== false) {
|
|
7445
|
+
subscriptions.push({
|
|
7446
|
+
userId: p.userId,
|
|
7447
|
+
sessionId: p.sessionId,
|
|
7448
|
+
trackType: TrackType.VIDEO,
|
|
7449
|
+
dimension: override?.dimension ?? p.videoDimension,
|
|
7450
|
+
});
|
|
7451
|
+
}
|
|
7452
|
+
}
|
|
7453
|
+
if (p.screenShareDimension && hasScreenShare(p)) {
|
|
7454
|
+
subscriptions.push({
|
|
7455
|
+
userId: p.userId,
|
|
7456
|
+
sessionId: p.sessionId,
|
|
7457
|
+
trackType: TrackType.SCREEN_SHARE,
|
|
7458
|
+
dimension: p.screenShareDimension,
|
|
7459
|
+
});
|
|
7460
|
+
}
|
|
7461
|
+
if (hasScreenShareAudio(p)) {
|
|
7462
|
+
subscriptions.push({
|
|
7463
|
+
userId: p.userId,
|
|
7464
|
+
sessionId: p.sessionId,
|
|
7465
|
+
trackType: TrackType.SCREEN_SHARE_AUDIO,
|
|
7466
|
+
});
|
|
7467
|
+
}
|
|
7468
|
+
}
|
|
7469
|
+
return subscriptions;
|
|
7470
|
+
}
|
|
7471
|
+
get videoTrackSubscriptionOverrides() {
|
|
7472
|
+
return getCurrentValue(this.videoTrackSubscriptionOverrides$);
|
|
7346
7473
|
}
|
|
7347
7474
|
}
|
|
7348
7475
|
|
|
@@ -9161,10 +9288,6 @@ class Call {
|
|
|
9161
9288
|
* The state of this call.
|
|
9162
9289
|
*/
|
|
9163
9290
|
this.state = new CallState();
|
|
9164
|
-
/**
|
|
9165
|
-
* The DynascaleManager instance.
|
|
9166
|
-
*/
|
|
9167
|
-
this.dynascaleManager = new DynascaleManager(this);
|
|
9168
9291
|
/**
|
|
9169
9292
|
* The permissions context of this call.
|
|
9170
9293
|
*/
|
|
@@ -9174,7 +9297,6 @@ class Call {
|
|
|
9174
9297
|
* @private
|
|
9175
9298
|
*/
|
|
9176
9299
|
this.dispatcher = new Dispatcher();
|
|
9177
|
-
this.trackSubscriptionsSubject = new BehaviorSubject({ type: DebounceType.MEDIUM, data: [] });
|
|
9178
9300
|
this.sfuClientTag = 0;
|
|
9179
9301
|
this.reconnectConcurrencyTag = Symbol('reconnectConcurrencyTag');
|
|
9180
9302
|
this.reconnectAttempts = 0;
|
|
@@ -9315,6 +9437,7 @@ class Call {
|
|
|
9315
9437
|
this.publisher = undefined;
|
|
9316
9438
|
await this.sfuClient?.leaveAndClose(reason);
|
|
9317
9439
|
this.sfuClient = undefined;
|
|
9440
|
+
this.dynascaleManager.setSfuClient(undefined);
|
|
9318
9441
|
this.state.setCallingState(CallingState.LEFT);
|
|
9319
9442
|
// Call all leave call hooks, e.g. to clean up global event handlers
|
|
9320
9443
|
this.leaveCallHooks.forEach((hook) => hook());
|
|
@@ -9477,6 +9600,7 @@ class Call {
|
|
|
9477
9600
|
})
|
|
9478
9601
|
: previousSfuClient;
|
|
9479
9602
|
this.sfuClient = sfuClient;
|
|
9603
|
+
this.dynascaleManager.setSfuClient(sfuClient);
|
|
9480
9604
|
const clientDetails = getClientDetails();
|
|
9481
9605
|
// we don't need to send JoinRequest if we are re-using an existing healthy SFU client
|
|
9482
9606
|
if (previousSfuClient !== sfuClient) {
|
|
@@ -9549,11 +9673,10 @@ class Call {
|
|
|
9549
9673
|
const strategy = this.reconnectStrategy;
|
|
9550
9674
|
const performingRejoin = strategy === WebsocketReconnectStrategy.REJOIN;
|
|
9551
9675
|
const announcedTracks = this.publisher?.getAnnouncedTracks() || [];
|
|
9552
|
-
const subscribedTracks = getCurrentValue(this.trackSubscriptionsSubject);
|
|
9553
9676
|
return {
|
|
9554
9677
|
strategy,
|
|
9555
9678
|
announcedTracks,
|
|
9556
|
-
subscriptions:
|
|
9679
|
+
subscriptions: this.dynascaleManager.trackSubscriptions,
|
|
9557
9680
|
reconnectAttempt: this.reconnectAttempts,
|
|
9558
9681
|
fromSfuId: migratingFromSfuId || '',
|
|
9559
9682
|
previousSessionId: performingRejoin ? previousSessionId || '' : '',
|
|
@@ -9923,7 +10046,7 @@ class Call {
|
|
|
9923
10046
|
const { remoteParticipants } = this.state;
|
|
9924
10047
|
if (remoteParticipants.length <= 0)
|
|
9925
10048
|
return;
|
|
9926
|
-
this.
|
|
10049
|
+
this.dynascaleManager.applyTrackSubscriptions(undefined);
|
|
9927
10050
|
};
|
|
9928
10051
|
/**
|
|
9929
10052
|
* Starts publishing the given video stream to the call.
|
|
@@ -10046,71 +10169,6 @@ class Call {
|
|
|
10046
10169
|
this.logger('warn', 'Failed to notify stop of noise cancellation', err);
|
|
10047
10170
|
});
|
|
10048
10171
|
};
|
|
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
10172
|
/**
|
|
10115
10173
|
* Will enhance the reported stats with additional participant-specific information (`callStatsReport$` state [store variable](./StreamVideoClient.md/#readonlystatestore)).
|
|
10116
10174
|
* This is usually helpful when detailed stats for a specific participant are needed.
|
|
@@ -10690,6 +10748,33 @@ class Call {
|
|
|
10690
10748
|
imageElement.removeEventListener('error', handleError);
|
|
10691
10749
|
};
|
|
10692
10750
|
};
|
|
10751
|
+
/**
|
|
10752
|
+
* Specify preference for incoming video resolution. The preference will
|
|
10753
|
+
* be matched as close as possible, but actual resolution will depend
|
|
10754
|
+
* on the video source quality and client network conditions. Will enable
|
|
10755
|
+
* incoming video, if previously disabled.
|
|
10756
|
+
*
|
|
10757
|
+
* @param resolution preferred resolution, or `undefined` to clear preference
|
|
10758
|
+
* @param sessionIds optionally specify session ids of the participants this
|
|
10759
|
+
* preference has effect on. Affects all participants by default.
|
|
10760
|
+
*/
|
|
10761
|
+
this.setPreferredIncomingVideoResolution = (resolution, sessionIds) => {
|
|
10762
|
+
this.dynascaleManager.setVideoTrackSubscriptionOverrides(resolution
|
|
10763
|
+
? {
|
|
10764
|
+
enabled: true,
|
|
10765
|
+
dimension: resolution,
|
|
10766
|
+
}
|
|
10767
|
+
: undefined, sessionIds);
|
|
10768
|
+
this.dynascaleManager.applyTrackSubscriptions();
|
|
10769
|
+
};
|
|
10770
|
+
/**
|
|
10771
|
+
* Enables or disables incoming video from all remote call participants,
|
|
10772
|
+
* and removes any preference for preferred resolution.
|
|
10773
|
+
*/
|
|
10774
|
+
this.setIncomingVideoEnabled = (enabled) => {
|
|
10775
|
+
this.dynascaleManager.setVideoTrackSubscriptionOverrides(enabled ? undefined : { enabled: false });
|
|
10776
|
+
this.dynascaleManager.applyTrackSubscriptions();
|
|
10777
|
+
};
|
|
10693
10778
|
this.type = type;
|
|
10694
10779
|
this.id = id;
|
|
10695
10780
|
this.cid = `${type}:${id}`;
|
|
@@ -10711,6 +10796,7 @@ class Call {
|
|
|
10711
10796
|
this.microphone = new MicrophoneManager(this);
|
|
10712
10797
|
this.speaker = new SpeakerManager(this);
|
|
10713
10798
|
this.screenShare = new ScreenShareManager(this);
|
|
10799
|
+
this.dynascaleManager = new DynascaleManager(this.state, this.speaker);
|
|
10714
10800
|
}
|
|
10715
10801
|
async setup() {
|
|
10716
10802
|
await withoutConcurrency(this.joinLeaveConcurrencyTag, async () => {
|
|
@@ -10723,9 +10809,6 @@ class Call {
|
|
|
10723
10809
|
this.leaveCallHooks.add(registerEventHandlers(this, this.dispatcher));
|
|
10724
10810
|
this.registerEffects();
|
|
10725
10811
|
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
10812
|
if (this.state.callingState === CallingState.LEFT) {
|
|
10730
10813
|
this.state.setCallingState(CallingState.IDLE);
|
|
10731
10814
|
}
|
|
@@ -12373,7 +12456,7 @@ class StreamClient {
|
|
|
12373
12456
|
});
|
|
12374
12457
|
};
|
|
12375
12458
|
this.getUserAgent = () => {
|
|
12376
|
-
const version = "1.
|
|
12459
|
+
const version = "1.8.0";
|
|
12377
12460
|
return (this.userAgent ||
|
|
12378
12461
|
`stream-video-javascript-client-${this.node ? 'node' : 'browser'}-${version}`);
|
|
12379
12462
|
};
|