@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/dist/index.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 https from 'https';
|
|
11
11
|
import WebSocket$1 from 'isomorphic-ws';
|
|
@@ -3021,7 +3021,7 @@ const retryable = async (rpc, signal) => {
|
|
|
3021
3021
|
return result;
|
|
3022
3022
|
};
|
|
3023
3023
|
|
|
3024
|
-
const version = "1.
|
|
3024
|
+
const version = "1.8.1";
|
|
3025
3025
|
const [major, minor, patch] = version.split('.');
|
|
3026
3026
|
let sdkInfo = {
|
|
3027
3027
|
type: SdkType.PLAIN_JAVASCRIPT,
|
|
@@ -4292,6 +4292,34 @@ class CallState {
|
|
|
4292
4292
|
return p;
|
|
4293
4293
|
}));
|
|
4294
4294
|
};
|
|
4295
|
+
/**
|
|
4296
|
+
* Update track subscription configuration for one or more participants.
|
|
4297
|
+
* You have to create a subscription for each participant for all the different kinds of tracks you want to receive.
|
|
4298
|
+
* You can only subscribe for tracks after the participant started publishing the given kind of track.
|
|
4299
|
+
*
|
|
4300
|
+
* @param trackType the kind of subscription to update.
|
|
4301
|
+
* @param changes the list of subscription changes to do.
|
|
4302
|
+
* @param type the debounce type to use for the update.
|
|
4303
|
+
*/
|
|
4304
|
+
this.updateParticipantTracks = (trackType, changes) => {
|
|
4305
|
+
return this.updateParticipants(Object.entries(changes).reduce((acc, [sessionId, change]) => {
|
|
4306
|
+
if (change.dimension) {
|
|
4307
|
+
change.dimension.height = Math.ceil(change.dimension.height);
|
|
4308
|
+
change.dimension.width = Math.ceil(change.dimension.width);
|
|
4309
|
+
}
|
|
4310
|
+
const prop = trackType === 'videoTrack'
|
|
4311
|
+
? 'videoDimension'
|
|
4312
|
+
: trackType === 'screenShareTrack'
|
|
4313
|
+
? 'screenShareDimension'
|
|
4314
|
+
: undefined;
|
|
4315
|
+
if (prop) {
|
|
4316
|
+
acc[sessionId] = {
|
|
4317
|
+
[prop]: change.dimension,
|
|
4318
|
+
};
|
|
4319
|
+
}
|
|
4320
|
+
return acc;
|
|
4321
|
+
}, {}));
|
|
4322
|
+
};
|
|
4295
4323
|
/**
|
|
4296
4324
|
* Updates the call state with the data received from the server.
|
|
4297
4325
|
*
|
|
@@ -6265,9 +6293,8 @@ const watchCallRejected = (call) => {
|
|
|
6265
6293
|
const watchCallEnded = (call) => {
|
|
6266
6294
|
return function onCallEnded() {
|
|
6267
6295
|
const { callingState } = call.state;
|
|
6268
|
-
if (callingState
|
|
6269
|
-
callingState
|
|
6270
|
-
callingState === CallingState.JOINING) {
|
|
6296
|
+
if (callingState !== CallingState.IDLE &&
|
|
6297
|
+
callingState !== CallingState.LEFT) {
|
|
6271
6298
|
call.leave({ reason: 'call.ended event received' }).catch((err) => {
|
|
6272
6299
|
call.logger('error', 'Failed to leave call after call.ended ', err);
|
|
6273
6300
|
});
|
|
@@ -7072,6 +7099,7 @@ const DEFAULT_VIEWPORT_VISIBILITY_STATE = {
|
|
|
7072
7099
|
videoTrack: VisibilityState.UNKNOWN,
|
|
7073
7100
|
screenShareTrack: VisibilityState.UNKNOWN,
|
|
7074
7101
|
};
|
|
7102
|
+
const globalOverrideKey = Symbol('globalOverrideKey');
|
|
7075
7103
|
/**
|
|
7076
7104
|
* A manager class that handles dynascale related tasks like:
|
|
7077
7105
|
*
|
|
@@ -7088,12 +7116,64 @@ class DynascaleManager {
|
|
|
7088
7116
|
*
|
|
7089
7117
|
* @param call the call to manage.
|
|
7090
7118
|
*/
|
|
7091
|
-
constructor(
|
|
7119
|
+
constructor(callState, speaker) {
|
|
7092
7120
|
/**
|
|
7093
7121
|
* The viewport tracker instance.
|
|
7094
7122
|
*/
|
|
7095
7123
|
this.viewportTracker = new ViewportTracker();
|
|
7096
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
|
+
};
|
|
7097
7177
|
/**
|
|
7098
7178
|
* Will begin tracking the given element for visibility changes within the
|
|
7099
7179
|
* configured viewport element (`call.setViewport`).
|
|
@@ -7105,7 +7185,7 @@ class DynascaleManager {
|
|
|
7105
7185
|
*/
|
|
7106
7186
|
this.trackElementVisibility = (element, sessionId, trackType) => {
|
|
7107
7187
|
const cleanup = this.viewportTracker.observe(element, (entry) => {
|
|
7108
|
-
this.
|
|
7188
|
+
this.callState.updateParticipant(sessionId, (participant) => {
|
|
7109
7189
|
const previousVisibilityState = participant.viewportVisibilityState ??
|
|
7110
7190
|
DEFAULT_VIEWPORT_VISIBILITY_STATE;
|
|
7111
7191
|
// observer triggers when the element is "moved" to be a fullscreen element
|
|
@@ -7127,7 +7207,7 @@ class DynascaleManager {
|
|
|
7127
7207
|
// reset visibility state to UNKNOWN upon cleanup
|
|
7128
7208
|
// so that the layouts that are not actively observed
|
|
7129
7209
|
// can still function normally (runtime layout switching)
|
|
7130
|
-
this.
|
|
7210
|
+
this.callState.updateParticipant(sessionId, (participant) => {
|
|
7131
7211
|
const previousVisibilityState = participant.viewportVisibilityState ??
|
|
7132
7212
|
DEFAULT_VIEWPORT_VISIBILITY_STATE;
|
|
7133
7213
|
return {
|
|
@@ -7164,7 +7244,7 @@ class DynascaleManager {
|
|
|
7164
7244
|
* @param trackType the kind of video.
|
|
7165
7245
|
*/
|
|
7166
7246
|
this.bindVideoElement = (videoElement, sessionId, trackType) => {
|
|
7167
|
-
const boundParticipant = this.
|
|
7247
|
+
const boundParticipant = this.callState.findParticipantBySessionId(sessionId);
|
|
7168
7248
|
if (!boundParticipant)
|
|
7169
7249
|
return;
|
|
7170
7250
|
const requestTrackWithDimensions = (debounceType, dimension) => {
|
|
@@ -7176,9 +7256,12 @@ class DynascaleManager {
|
|
|
7176
7256
|
this.logger('debug', `Ignoring 0x0 dimension`, boundParticipant);
|
|
7177
7257
|
dimension = undefined;
|
|
7178
7258
|
}
|
|
7179
|
-
this.
|
|
7259
|
+
this.callState.updateParticipantTracks(trackType, {
|
|
7260
|
+
[sessionId]: { dimension },
|
|
7261
|
+
});
|
|
7262
|
+
this.applyTrackSubscriptions(debounceType);
|
|
7180
7263
|
};
|
|
7181
|
-
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 }));
|
|
7182
7265
|
/**
|
|
7183
7266
|
* Since the video elements are now being removed from the DOM (React SDK) upon
|
|
7184
7267
|
* visibility change, this subscription is not in use an stays here only for the
|
|
@@ -7293,10 +7376,10 @@ class DynascaleManager {
|
|
|
7293
7376
|
* @returns a cleanup function that will unbind the audio element.
|
|
7294
7377
|
*/
|
|
7295
7378
|
this.bindAudioElement = (audioElement, sessionId, trackType) => {
|
|
7296
|
-
const participant = this.
|
|
7379
|
+
const participant = this.callState.findParticipantBySessionId(sessionId);
|
|
7297
7380
|
if (!participant || participant.isLocalParticipant)
|
|
7298
7381
|
return;
|
|
7299
|
-
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 }));
|
|
7300
7383
|
const updateMediaStreamSubscription = participant$
|
|
7301
7384
|
.pipe(distinctUntilKeyChanged(trackType === 'screenShareAudioTrack'
|
|
7302
7385
|
? 'screenShareAudioStream'
|
|
@@ -7316,7 +7399,7 @@ class DynascaleManager {
|
|
|
7316
7399
|
// audio output device shall be set after the audio element is played
|
|
7317
7400
|
// otherwise, the browser will not pick it up, and will always
|
|
7318
7401
|
// play audio through the system's default device
|
|
7319
|
-
const { selectedDevice } = this.
|
|
7402
|
+
const { selectedDevice } = this.speaker.state;
|
|
7320
7403
|
if (selectedDevice && 'setSinkId' in audioElement) {
|
|
7321
7404
|
audioElement.setSinkId(selectedDevice);
|
|
7322
7405
|
}
|
|
@@ -7325,13 +7408,13 @@ class DynascaleManager {
|
|
|
7325
7408
|
});
|
|
7326
7409
|
const sinkIdSubscription = !('setSinkId' in audioElement)
|
|
7327
7410
|
? null
|
|
7328
|
-
: this.
|
|
7411
|
+
: this.speaker.state.selectedDevice$.subscribe((deviceId) => {
|
|
7329
7412
|
if (deviceId) {
|
|
7330
7413
|
audioElement.setSinkId(deviceId);
|
|
7331
7414
|
}
|
|
7332
7415
|
});
|
|
7333
7416
|
const volumeSubscription = combineLatest([
|
|
7334
|
-
this.
|
|
7417
|
+
this.speaker.state.volume$,
|
|
7335
7418
|
participant$.pipe(distinctUntilKeyChanged('audioVolume')),
|
|
7336
7419
|
]).subscribe(([volume, p]) => {
|
|
7337
7420
|
audioElement.volume = p.audioVolume ?? volume;
|
|
@@ -7343,7 +7426,50 @@ class DynascaleManager {
|
|
|
7343
7426
|
updateMediaStreamSubscription.unsubscribe();
|
|
7344
7427
|
};
|
|
7345
7428
|
};
|
|
7346
|
-
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$);
|
|
7347
7473
|
}
|
|
7348
7474
|
}
|
|
7349
7475
|
|
|
@@ -8600,9 +8726,11 @@ class RNSpeechDetector {
|
|
|
8600
8726
|
*/
|
|
8601
8727
|
async start() {
|
|
8602
8728
|
try {
|
|
8729
|
+
this.cleanupAudioStream();
|
|
8603
8730
|
const audioStream = await navigator.mediaDevices.getUserMedia({
|
|
8604
8731
|
audio: true,
|
|
8605
8732
|
});
|
|
8733
|
+
this.audioStream = audioStream;
|
|
8606
8734
|
this.pc1.addEventListener('icecandidate', async (e) => {
|
|
8607
8735
|
await this.pc2.addIceCandidate(e.candidate);
|
|
8608
8736
|
});
|
|
@@ -8632,6 +8760,7 @@ class RNSpeechDetector {
|
|
|
8632
8760
|
stop() {
|
|
8633
8761
|
this.pc1.close();
|
|
8634
8762
|
this.pc2.close();
|
|
8763
|
+
this.cleanupAudioStream();
|
|
8635
8764
|
if (this.intervalId) {
|
|
8636
8765
|
clearInterval(this.intervalId);
|
|
8637
8766
|
}
|
|
@@ -8668,6 +8797,18 @@ class RNSpeechDetector {
|
|
|
8668
8797
|
clearInterval(this.intervalId);
|
|
8669
8798
|
};
|
|
8670
8799
|
}
|
|
8800
|
+
cleanupAudioStream() {
|
|
8801
|
+
if (!this.audioStream) {
|
|
8802
|
+
return;
|
|
8803
|
+
}
|
|
8804
|
+
this.audioStream.getTracks().forEach((track) => track.stop());
|
|
8805
|
+
if (
|
|
8806
|
+
// @ts-expect-error release() is present in react-native-webrtc
|
|
8807
|
+
typeof this.audioStream.release === 'function') {
|
|
8808
|
+
// @ts-expect-error called to dispose the stream in RN
|
|
8809
|
+
this.audioStream.release();
|
|
8810
|
+
}
|
|
8811
|
+
}
|
|
8671
8812
|
}
|
|
8672
8813
|
|
|
8673
8814
|
class MicrophoneManager extends InputMediaDeviceManager {
|
|
@@ -9162,10 +9303,6 @@ class Call {
|
|
|
9162
9303
|
* The state of this call.
|
|
9163
9304
|
*/
|
|
9164
9305
|
this.state = new CallState();
|
|
9165
|
-
/**
|
|
9166
|
-
* The DynascaleManager instance.
|
|
9167
|
-
*/
|
|
9168
|
-
this.dynascaleManager = new DynascaleManager(this);
|
|
9169
9306
|
/**
|
|
9170
9307
|
* The permissions context of this call.
|
|
9171
9308
|
*/
|
|
@@ -9175,7 +9312,6 @@ class Call {
|
|
|
9175
9312
|
* @private
|
|
9176
9313
|
*/
|
|
9177
9314
|
this.dispatcher = new Dispatcher();
|
|
9178
|
-
this.trackSubscriptionsSubject = new BehaviorSubject({ type: DebounceType.MEDIUM, data: [] });
|
|
9179
9315
|
this.sfuClientTag = 0;
|
|
9180
9316
|
this.reconnectConcurrencyTag = Symbol('reconnectConcurrencyTag');
|
|
9181
9317
|
this.reconnectAttempts = 0;
|
|
@@ -9316,6 +9452,7 @@ class Call {
|
|
|
9316
9452
|
this.publisher = undefined;
|
|
9317
9453
|
await this.sfuClient?.leaveAndClose(reason);
|
|
9318
9454
|
this.sfuClient = undefined;
|
|
9455
|
+
this.dynascaleManager.setSfuClient(undefined);
|
|
9319
9456
|
this.state.setCallingState(CallingState.LEFT);
|
|
9320
9457
|
// Call all leave call hooks, e.g. to clean up global event handlers
|
|
9321
9458
|
this.leaveCallHooks.forEach((hook) => hook());
|
|
@@ -9478,6 +9615,7 @@ class Call {
|
|
|
9478
9615
|
})
|
|
9479
9616
|
: previousSfuClient;
|
|
9480
9617
|
this.sfuClient = sfuClient;
|
|
9618
|
+
this.dynascaleManager.setSfuClient(sfuClient);
|
|
9481
9619
|
const clientDetails = getClientDetails();
|
|
9482
9620
|
// we don't need to send JoinRequest if we are re-using an existing healthy SFU client
|
|
9483
9621
|
if (previousSfuClient !== sfuClient) {
|
|
@@ -9550,11 +9688,10 @@ class Call {
|
|
|
9550
9688
|
const strategy = this.reconnectStrategy;
|
|
9551
9689
|
const performingRejoin = strategy === WebsocketReconnectStrategy.REJOIN;
|
|
9552
9690
|
const announcedTracks = this.publisher?.getAnnouncedTracks() || [];
|
|
9553
|
-
const subscribedTracks = getCurrentValue(this.trackSubscriptionsSubject);
|
|
9554
9691
|
return {
|
|
9555
9692
|
strategy,
|
|
9556
9693
|
announcedTracks,
|
|
9557
|
-
subscriptions:
|
|
9694
|
+
subscriptions: this.dynascaleManager.trackSubscriptions,
|
|
9558
9695
|
reconnectAttempt: this.reconnectAttempts,
|
|
9559
9696
|
fromSfuId: migratingFromSfuId || '',
|
|
9560
9697
|
previousSessionId: performingRejoin ? previousSessionId || '' : '',
|
|
@@ -9924,7 +10061,7 @@ class Call {
|
|
|
9924
10061
|
const { remoteParticipants } = this.state;
|
|
9925
10062
|
if (remoteParticipants.length <= 0)
|
|
9926
10063
|
return;
|
|
9927
|
-
this.
|
|
10064
|
+
this.dynascaleManager.applyTrackSubscriptions(undefined);
|
|
9928
10065
|
};
|
|
9929
10066
|
/**
|
|
9930
10067
|
* Starts publishing the given video stream to the call.
|
|
@@ -10047,71 +10184,6 @@ class Call {
|
|
|
10047
10184
|
this.logger('warn', 'Failed to notify stop of noise cancellation', err);
|
|
10048
10185
|
});
|
|
10049
10186
|
};
|
|
10050
|
-
/**
|
|
10051
|
-
* Update track subscription configuration for one or more participants.
|
|
10052
|
-
* You have to create a subscription for each participant for all the different kinds of tracks you want to receive.
|
|
10053
|
-
* You can only subscribe for tracks after the participant started publishing the given kind of track.
|
|
10054
|
-
*
|
|
10055
|
-
* @param trackType the kind of subscription to update.
|
|
10056
|
-
* @param changes the list of subscription changes to do.
|
|
10057
|
-
* @param type the debounce type to use for the update.
|
|
10058
|
-
*/
|
|
10059
|
-
this.updateSubscriptionsPartial = (trackType, changes, type = DebounceType.SLOW) => {
|
|
10060
|
-
const participants = this.state.updateParticipants(Object.entries(changes).reduce((acc, [sessionId, change]) => {
|
|
10061
|
-
if (change.dimension) {
|
|
10062
|
-
change.dimension.height = Math.ceil(change.dimension.height);
|
|
10063
|
-
change.dimension.width = Math.ceil(change.dimension.width);
|
|
10064
|
-
}
|
|
10065
|
-
const prop = trackType === 'videoTrack'
|
|
10066
|
-
? 'videoDimension'
|
|
10067
|
-
: trackType === 'screenShareTrack'
|
|
10068
|
-
? 'screenShareDimension'
|
|
10069
|
-
: undefined;
|
|
10070
|
-
if (prop) {
|
|
10071
|
-
acc[sessionId] = {
|
|
10072
|
-
[prop]: change.dimension,
|
|
10073
|
-
};
|
|
10074
|
-
}
|
|
10075
|
-
return acc;
|
|
10076
|
-
}, {}));
|
|
10077
|
-
this.updateSubscriptions(participants, type);
|
|
10078
|
-
};
|
|
10079
|
-
this.updateSubscriptions = (participants, type = DebounceType.SLOW) => {
|
|
10080
|
-
const subscriptions = [];
|
|
10081
|
-
for (const p of participants) {
|
|
10082
|
-
// we don't want to subscribe to our own tracks
|
|
10083
|
-
if (p.isLocalParticipant)
|
|
10084
|
-
continue;
|
|
10085
|
-
// NOTE: audio tracks don't have to be requested explicitly
|
|
10086
|
-
// as the SFU will implicitly subscribe us to all of them,
|
|
10087
|
-
// once they become available.
|
|
10088
|
-
if (p.videoDimension && hasVideo(p)) {
|
|
10089
|
-
subscriptions.push({
|
|
10090
|
-
userId: p.userId,
|
|
10091
|
-
sessionId: p.sessionId,
|
|
10092
|
-
trackType: TrackType.VIDEO,
|
|
10093
|
-
dimension: p.videoDimension,
|
|
10094
|
-
});
|
|
10095
|
-
}
|
|
10096
|
-
if (p.screenShareDimension && hasScreenShare(p)) {
|
|
10097
|
-
subscriptions.push({
|
|
10098
|
-
userId: p.userId,
|
|
10099
|
-
sessionId: p.sessionId,
|
|
10100
|
-
trackType: TrackType.SCREEN_SHARE,
|
|
10101
|
-
dimension: p.screenShareDimension,
|
|
10102
|
-
});
|
|
10103
|
-
}
|
|
10104
|
-
if (hasScreenShareAudio(p)) {
|
|
10105
|
-
subscriptions.push({
|
|
10106
|
-
userId: p.userId,
|
|
10107
|
-
sessionId: p.sessionId,
|
|
10108
|
-
trackType: TrackType.SCREEN_SHARE_AUDIO,
|
|
10109
|
-
});
|
|
10110
|
-
}
|
|
10111
|
-
}
|
|
10112
|
-
// schedule update
|
|
10113
|
-
this.trackSubscriptionsSubject.next({ type, data: subscriptions });
|
|
10114
|
-
};
|
|
10115
10187
|
/**
|
|
10116
10188
|
* Will enhance the reported stats with additional participant-specific information (`callStatsReport$` state [store variable](./StreamVideoClient.md/#readonlystatestore)).
|
|
10117
10189
|
* This is usually helpful when detailed stats for a specific participant are needed.
|
|
@@ -10691,6 +10763,33 @@ class Call {
|
|
|
10691
10763
|
imageElement.removeEventListener('error', handleError);
|
|
10692
10764
|
};
|
|
10693
10765
|
};
|
|
10766
|
+
/**
|
|
10767
|
+
* Specify preference for incoming video resolution. The preference will
|
|
10768
|
+
* be matched as close as possible, but actual resolution will depend
|
|
10769
|
+
* on the video source quality and client network conditions. Will enable
|
|
10770
|
+
* incoming video, if previously disabled.
|
|
10771
|
+
*
|
|
10772
|
+
* @param resolution preferred resolution, or `undefined` to clear preference
|
|
10773
|
+
* @param sessionIds optionally specify session ids of the participants this
|
|
10774
|
+
* preference has effect on. Affects all participants by default.
|
|
10775
|
+
*/
|
|
10776
|
+
this.setPreferredIncomingVideoResolution = (resolution, sessionIds) => {
|
|
10777
|
+
this.dynascaleManager.setVideoTrackSubscriptionOverrides(resolution
|
|
10778
|
+
? {
|
|
10779
|
+
enabled: true,
|
|
10780
|
+
dimension: resolution,
|
|
10781
|
+
}
|
|
10782
|
+
: undefined, sessionIds);
|
|
10783
|
+
this.dynascaleManager.applyTrackSubscriptions();
|
|
10784
|
+
};
|
|
10785
|
+
/**
|
|
10786
|
+
* Enables or disables incoming video from all remote call participants,
|
|
10787
|
+
* and removes any preference for preferred resolution.
|
|
10788
|
+
*/
|
|
10789
|
+
this.setIncomingVideoEnabled = (enabled) => {
|
|
10790
|
+
this.dynascaleManager.setVideoTrackSubscriptionOverrides(enabled ? undefined : { enabled: false });
|
|
10791
|
+
this.dynascaleManager.applyTrackSubscriptions();
|
|
10792
|
+
};
|
|
10694
10793
|
this.type = type;
|
|
10695
10794
|
this.id = id;
|
|
10696
10795
|
this.cid = `${type}:${id}`;
|
|
@@ -10712,6 +10811,7 @@ class Call {
|
|
|
10712
10811
|
this.microphone = new MicrophoneManager(this);
|
|
10713
10812
|
this.speaker = new SpeakerManager(this);
|
|
10714
10813
|
this.screenShare = new ScreenShareManager(this);
|
|
10814
|
+
this.dynascaleManager = new DynascaleManager(this.state, this.speaker);
|
|
10715
10815
|
}
|
|
10716
10816
|
async setup() {
|
|
10717
10817
|
await withoutConcurrency(this.joinLeaveConcurrencyTag, async () => {
|
|
@@ -10724,9 +10824,6 @@ class Call {
|
|
|
10724
10824
|
this.leaveCallHooks.add(registerEventHandlers(this, this.dispatcher));
|
|
10725
10825
|
this.registerEffects();
|
|
10726
10826
|
this.registerReconnectHandlers();
|
|
10727
|
-
this.leaveCallHooks.add(createSubscription(this.trackSubscriptionsSubject.pipe(debounce((v) => timer(v.type)), map$1((v) => v.data)), (subscriptions) => this.sfuClient?.updateSubscriptions(subscriptions).catch((err) => {
|
|
10728
|
-
this.logger('debug', `Failed to update track subscriptions`, err);
|
|
10729
|
-
})));
|
|
10730
10827
|
if (this.state.callingState === CallingState.LEFT) {
|
|
10731
10828
|
this.state.setCallingState(CallingState.IDLE);
|
|
10732
10829
|
}
|
|
@@ -12372,7 +12469,7 @@ class StreamClient {
|
|
|
12372
12469
|
});
|
|
12373
12470
|
};
|
|
12374
12471
|
this.getUserAgent = () => {
|
|
12375
|
-
const version = "1.
|
|
12472
|
+
const version = "1.8.1";
|
|
12376
12473
|
return (this.userAgent ||
|
|
12377
12474
|
`stream-video-javascript-client-${this.node ? 'node' : 'browser'}-${version}`);
|
|
12378
12475
|
};
|