@stream-io/video-client 1.23.2 → 1.23.3
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 +10 -0
- package/dist/index.browser.es.js +228 -140
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +227 -139
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +228 -140
- package/dist/index.es.js.map +1 -1
- package/dist/src/helpers/DynascaleManager.d.ts +7 -0
- package/dist/src/store/CallState.d.ts +11 -1
- package/package.json +3 -3
- package/src/Call.ts +1 -0
- package/src/devices/InputMediaDeviceManager.ts +139 -138
- package/src/devices/MicrophoneManager.ts +1 -0
- package/src/devices/__tests__/InputMediaDeviceManager.test.ts +2 -2
- package/src/helpers/DynascaleManager.ts +94 -27
- package/src/helpers/__tests__/DynascaleManager.test.ts +179 -3
- package/src/rtc/__tests__/mocks/webrtc.mocks.ts +32 -0
- package/src/store/CallState.ts +19 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).
|
|
4
4
|
|
|
5
|
+
## [1.23.3](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.23.2...@stream-io/video-client-1.23.3) (2025-06-02)
|
|
6
|
+
|
|
7
|
+
- remove TODO ([9cfea4b](https://github.com/GetStream/stream-video-js/commit/9cfea4b54284cdd680a6d666436dedc5fd8956c3))
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
- inconsistent device state if applySettingsToStream fails ([#1808](https://github.com/GetStream/stream-video-js/issues/1808)) ([73d66c2](https://github.com/GetStream/stream-video-js/commit/73d66c2eaa7eca52b9d41b39f8f9fd0a0ce240ef))
|
|
12
|
+
- test ([e0b93aa](https://github.com/GetStream/stream-video-js/commit/e0b93aaa13f22f0db30b61e6230aff40ba8fd92a))
|
|
13
|
+
- use AudioContext for Safari ([#1810](https://github.com/GetStream/stream-video-js/issues/1810)) ([63542f4](https://github.com/GetStream/stream-video-js/commit/63542f419efa475c7acf50f053621ace74a1eff4))
|
|
14
|
+
|
|
5
15
|
## [1.23.2](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.23.1...@stream-io/video-client-1.23.2) (2025-05-22)
|
|
6
16
|
|
|
7
17
|
### Bug Fixes
|
package/dist/index.browser.es.js
CHANGED
|
@@ -4,7 +4,7 @@ import { ServiceType, stackIntercept, RpcError } from '@protobuf-ts/runtime-rpc'
|
|
|
4
4
|
import axios from 'axios';
|
|
5
5
|
export { AxiosError } from 'axios';
|
|
6
6
|
import { TwirpFetchTransport, TwirpErrorCode } from '@protobuf-ts/twirp-transport';
|
|
7
|
-
import { ReplaySubject, combineLatest, BehaviorSubject,
|
|
7
|
+
import { ReplaySubject, combineLatest, BehaviorSubject, shareReplay, map, distinctUntilChanged, takeWhile, distinctUntilKeyChanged, fromEventPattern, startWith, concatMap, merge, from, fromEvent, debounceTime, pairwise, of } from 'rxjs';
|
|
8
8
|
import { UAParser } from 'ua-parser-js';
|
|
9
9
|
import { parse } from 'sdp-transform';
|
|
10
10
|
|
|
@@ -5031,6 +5031,9 @@ class CallState {
|
|
|
5031
5031
|
return nextQueue.slice(-maxVisibleCaptions);
|
|
5032
5032
|
});
|
|
5033
5033
|
};
|
|
5034
|
+
this.rawParticipants$ = this.participantsSubject
|
|
5035
|
+
.asObservable()
|
|
5036
|
+
.pipe(shareReplay({ bufferSize: 1, refCount: true }));
|
|
5034
5037
|
this.participants$ = this.participantsSubject.asObservable().pipe(
|
|
5035
5038
|
// maintain stable-sort by mutating the participants stored
|
|
5036
5039
|
// in the original subject
|
|
@@ -5204,6 +5207,12 @@ class CallState {
|
|
|
5204
5207
|
get participants() {
|
|
5205
5208
|
return this.getCurrentValue(this.participants$);
|
|
5206
5209
|
}
|
|
5210
|
+
/**
|
|
5211
|
+
* The stable list of participants in the current call, unsorted.
|
|
5212
|
+
*/
|
|
5213
|
+
get rawParticipants() {
|
|
5214
|
+
return this.getCurrentValue(this.rawParticipants$);
|
|
5215
|
+
}
|
|
5207
5216
|
/**
|
|
5208
5217
|
* The local participant in the current call.
|
|
5209
5218
|
*/
|
|
@@ -5676,7 +5685,7 @@ const aggregate = (stats) => {
|
|
|
5676
5685
|
return report;
|
|
5677
5686
|
};
|
|
5678
5687
|
|
|
5679
|
-
const version = "1.23.
|
|
5688
|
+
const version = "1.23.3";
|
|
5680
5689
|
const [major, minor, patch] = version.split('.');
|
|
5681
5690
|
let sdkInfo = {
|
|
5682
5691
|
type: SdkType.PLAIN_JAVASCRIPT,
|
|
@@ -8433,6 +8442,20 @@ class DynascaleManager {
|
|
|
8433
8442
|
true,
|
|
8434
8443
|
};
|
|
8435
8444
|
}), shareReplay(1));
|
|
8445
|
+
/**
|
|
8446
|
+
* Disposes the allocated resources and closes the audio context if it was created.
|
|
8447
|
+
*/
|
|
8448
|
+
this.dispose = async () => {
|
|
8449
|
+
if (this.pendingSubscriptionsUpdate) {
|
|
8450
|
+
clearTimeout(this.pendingSubscriptionsUpdate);
|
|
8451
|
+
}
|
|
8452
|
+
const context = this.getOrCreateAudioContext();
|
|
8453
|
+
if (context && context.state !== 'closed') {
|
|
8454
|
+
document.removeEventListener('click', this.resumeAudioContext);
|
|
8455
|
+
await context.close();
|
|
8456
|
+
this.audioContext = undefined;
|
|
8457
|
+
}
|
|
8458
|
+
};
|
|
8436
8459
|
this.setVideoTrackSubscriptionOverrides = (override, sessionIds) => {
|
|
8437
8460
|
if (!sessionIds) {
|
|
8438
8461
|
return setCurrentValue(this.videoTrackSubscriptionOverridesSubject, override ? { [globalOverrideKey]: override } : {});
|
|
@@ -8548,7 +8571,7 @@ class DynascaleManager {
|
|
|
8548
8571
|
});
|
|
8549
8572
|
this.applyTrackSubscriptions(debounceType);
|
|
8550
8573
|
};
|
|
8551
|
-
const participant$ = this.callState.participants$.pipe(map((
|
|
8574
|
+
const participant$ = this.callState.participants$.pipe(map((ps) => ps.find((p) => p.sessionId === sessionId)), takeWhile((participant) => !!participant), distinctUntilChanged(), shareReplay({ bufferSize: 1, refCount: true }));
|
|
8552
8575
|
/**
|
|
8553
8576
|
* Since the video elements are now being removed from the DOM (React SDK) upon
|
|
8554
8577
|
* visibility change, this subscription is not in use an stays here only for the
|
|
@@ -8676,7 +8699,24 @@ class DynascaleManager {
|
|
|
8676
8699
|
const participant = this.callState.findParticipantBySessionId(sessionId);
|
|
8677
8700
|
if (!participant || participant.isLocalParticipant)
|
|
8678
8701
|
return;
|
|
8679
|
-
const participant$ = this.callState.participants$.pipe(map((
|
|
8702
|
+
const participant$ = this.callState.participants$.pipe(map((ps) => ps.find((p) => p.sessionId === sessionId)), takeWhile((p) => !!p), distinctUntilChanged(), shareReplay({ bufferSize: 1, refCount: true }));
|
|
8703
|
+
const updateSinkId = (deviceId, audioContext) => {
|
|
8704
|
+
if (!deviceId)
|
|
8705
|
+
return;
|
|
8706
|
+
if ('setSinkId' in audioElement) {
|
|
8707
|
+
audioElement.setSinkId(deviceId).catch((e) => {
|
|
8708
|
+
this.logger('warn', `Can't to set AudioElement sinkId`, e);
|
|
8709
|
+
});
|
|
8710
|
+
}
|
|
8711
|
+
if (audioContext && 'setSinkId' in audioContext) {
|
|
8712
|
+
// @ts-expect-error setSinkId is not available in all browsers
|
|
8713
|
+
audioContext.setSinkId(deviceId).catch((e) => {
|
|
8714
|
+
this.logger('warn', `Can't to set AudioContext sinkId`, e);
|
|
8715
|
+
});
|
|
8716
|
+
}
|
|
8717
|
+
};
|
|
8718
|
+
let sourceNode = undefined;
|
|
8719
|
+
let gainNode = undefined;
|
|
8680
8720
|
const updateMediaStreamSubscription = participant$
|
|
8681
8721
|
.pipe(distinctUntilKeyChanged(trackType === 'screenShareAudioTrack'
|
|
8682
8722
|
? 'screenShareAudioStream'
|
|
@@ -8689,40 +8729,82 @@ class DynascaleManager {
|
|
|
8689
8729
|
return;
|
|
8690
8730
|
setTimeout(() => {
|
|
8691
8731
|
audioElement.srcObject = source ?? null;
|
|
8692
|
-
if (
|
|
8732
|
+
if (!source)
|
|
8733
|
+
return;
|
|
8734
|
+
// Safari has a special quirk that prevents playing audio until the user
|
|
8735
|
+
// interacts with the page or focuses on the tab where the call happens.
|
|
8736
|
+
// This is a workaround for the issue where:
|
|
8737
|
+
// - A and B are in a call
|
|
8738
|
+
// - A switches to another tab
|
|
8739
|
+
// - B mutes their microphone and unmutes it
|
|
8740
|
+
// - A does not hear B's unmuted audio until they focus the tab
|
|
8741
|
+
const audioContext = this.getOrCreateAudioContext();
|
|
8742
|
+
if (audioContext) {
|
|
8743
|
+
// we will play audio through the audio context in Safari
|
|
8744
|
+
audioElement.muted = true;
|
|
8745
|
+
sourceNode?.disconnect();
|
|
8746
|
+
sourceNode = audioContext.createMediaStreamSource(source);
|
|
8747
|
+
gainNode ?? (gainNode = audioContext.createGain());
|
|
8748
|
+
gainNode.gain.value = p.audioVolume ?? this.speaker.state.volume;
|
|
8749
|
+
sourceNode.connect(gainNode).connect(audioContext.destination);
|
|
8750
|
+
this.resumeAudioContext();
|
|
8751
|
+
}
|
|
8752
|
+
else {
|
|
8753
|
+
// we will play audio directly through the audio element in other browsers
|
|
8754
|
+
audioElement.muted = false;
|
|
8693
8755
|
audioElement.play().catch((e) => {
|
|
8694
|
-
this.logger('warn', `Failed to play stream`, e);
|
|
8756
|
+
this.logger('warn', `Failed to play audio stream`, e);
|
|
8695
8757
|
});
|
|
8696
|
-
// audio output device shall be set after the audio element is played
|
|
8697
|
-
// otherwise, the browser will not pick it up, and will always
|
|
8698
|
-
// play audio through the system's default device
|
|
8699
|
-
const { selectedDevice } = this.speaker.state;
|
|
8700
|
-
if (selectedDevice && 'setSinkId' in audioElement) {
|
|
8701
|
-
audioElement.setSinkId(selectedDevice);
|
|
8702
|
-
}
|
|
8703
8758
|
}
|
|
8759
|
+
const { selectedDevice } = this.speaker.state;
|
|
8760
|
+
if (selectedDevice)
|
|
8761
|
+
updateSinkId(selectedDevice, audioContext);
|
|
8704
8762
|
});
|
|
8705
8763
|
});
|
|
8706
8764
|
const sinkIdSubscription = !('setSinkId' in audioElement)
|
|
8707
8765
|
? null
|
|
8708
8766
|
: this.speaker.state.selectedDevice$.subscribe((deviceId) => {
|
|
8709
|
-
|
|
8710
|
-
|
|
8711
|
-
}
|
|
8767
|
+
const audioContext = this.getOrCreateAudioContext();
|
|
8768
|
+
updateSinkId(deviceId, audioContext);
|
|
8712
8769
|
});
|
|
8713
8770
|
const volumeSubscription = combineLatest([
|
|
8714
8771
|
this.speaker.state.volume$,
|
|
8715
8772
|
participant$.pipe(distinctUntilKeyChanged('audioVolume')),
|
|
8716
8773
|
]).subscribe(([volume, p]) => {
|
|
8717
|
-
|
|
8774
|
+
const participantVolume = p.audioVolume ?? volume;
|
|
8775
|
+
audioElement.volume = participantVolume;
|
|
8776
|
+
if (gainNode)
|
|
8777
|
+
gainNode.gain.value = participantVolume;
|
|
8718
8778
|
});
|
|
8719
8779
|
audioElement.autoplay = true;
|
|
8720
8780
|
return () => {
|
|
8721
8781
|
sinkIdSubscription?.unsubscribe();
|
|
8722
8782
|
volumeSubscription.unsubscribe();
|
|
8723
8783
|
updateMediaStreamSubscription.unsubscribe();
|
|
8784
|
+
audioElement.srcObject = null;
|
|
8785
|
+
sourceNode?.disconnect();
|
|
8786
|
+
gainNode?.disconnect();
|
|
8724
8787
|
};
|
|
8725
8788
|
};
|
|
8789
|
+
this.getOrCreateAudioContext = () => {
|
|
8790
|
+
if (this.audioContext || !isSafari())
|
|
8791
|
+
return this.audioContext;
|
|
8792
|
+
const context = new AudioContext();
|
|
8793
|
+
if (context.state === 'suspended') {
|
|
8794
|
+
document.addEventListener('click', this.resumeAudioContext);
|
|
8795
|
+
}
|
|
8796
|
+
return (this.audioContext = context);
|
|
8797
|
+
};
|
|
8798
|
+
this.resumeAudioContext = () => {
|
|
8799
|
+
if (this.audioContext?.state === 'suspended') {
|
|
8800
|
+
this.audioContext
|
|
8801
|
+
.resume()
|
|
8802
|
+
.catch((err) => this.logger('warn', `Can't resume audio context`, err))
|
|
8803
|
+
.then(() => {
|
|
8804
|
+
document.removeEventListener('click', this.resumeAudioContext);
|
|
8805
|
+
});
|
|
8806
|
+
}
|
|
8807
|
+
};
|
|
8726
8808
|
this.callState = callState;
|
|
8727
8809
|
this.speaker = speaker;
|
|
8728
8810
|
}
|
|
@@ -9505,10 +9587,22 @@ class InputMediaDeviceManager {
|
|
|
9505
9587
|
}
|
|
9506
9588
|
}
|
|
9507
9589
|
async applySettingsToStream() {
|
|
9508
|
-
await withCancellation(this.statusChangeConcurrencyTag, async () => {
|
|
9590
|
+
await withCancellation(this.statusChangeConcurrencyTag, async (signal) => {
|
|
9509
9591
|
if (this.enabled) {
|
|
9510
|
-
|
|
9511
|
-
|
|
9592
|
+
try {
|
|
9593
|
+
await this.muteStream();
|
|
9594
|
+
this.state.setStatus('disabled');
|
|
9595
|
+
if (signal.aborted) {
|
|
9596
|
+
return;
|
|
9597
|
+
}
|
|
9598
|
+
await this.unmuteStream();
|
|
9599
|
+
this.state.setStatus('enabled');
|
|
9600
|
+
}
|
|
9601
|
+
finally {
|
|
9602
|
+
if (!signal.aborted) {
|
|
9603
|
+
this.state.setPendingStatus(this.state.status);
|
|
9604
|
+
}
|
|
9605
|
+
}
|
|
9512
9606
|
}
|
|
9513
9607
|
});
|
|
9514
9608
|
}
|
|
@@ -9574,130 +9668,122 @@ class InputMediaDeviceManager {
|
|
|
9574
9668
|
this.logger('debug', 'Starting stream');
|
|
9575
9669
|
let stream;
|
|
9576
9670
|
let rootStream;
|
|
9577
|
-
|
|
9578
|
-
|
|
9579
|
-
|
|
9580
|
-
|
|
9581
|
-
|
|
9582
|
-
|
|
9583
|
-
|
|
9584
|
-
|
|
9585
|
-
|
|
9586
|
-
|
|
9587
|
-
|
|
9588
|
-
|
|
9589
|
-
|
|
9590
|
-
|
|
9591
|
-
|
|
9592
|
-
|
|
9593
|
-
|
|
9594
|
-
|
|
9595
|
-
|
|
9596
|
-
|
|
9597
|
-
|
|
9598
|
-
|
|
9599
|
-
|
|
9600
|
-
|
|
9601
|
-
|
|
9602
|
-
|
|
9603
|
-
|
|
9604
|
-
|
|
9605
|
-
|
|
9606
|
-
|
|
9607
|
-
|
|
9608
|
-
|
|
9609
|
-
|
|
9610
|
-
if (!parentStream)
|
|
9611
|
-
return filterStream;
|
|
9612
|
-
// TODO OL: take care of track.enabled property as well
|
|
9613
|
-
const parent = await parentStream;
|
|
9614
|
-
filterStream.getTracks().forEach((track) => {
|
|
9615
|
-
const originalStop = track.stop;
|
|
9616
|
-
track.stop = function stop() {
|
|
9617
|
-
originalStop.call(track);
|
|
9618
|
-
parent.getTracks().forEach((parentTrack) => {
|
|
9619
|
-
if (parentTrack.kind === track.kind) {
|
|
9620
|
-
parentTrack.stop();
|
|
9621
|
-
}
|
|
9622
|
-
});
|
|
9623
|
-
};
|
|
9624
|
-
});
|
|
9625
|
-
parent.getTracks().forEach((parentTrack) => {
|
|
9626
|
-
// When the parent stream abruptly ends, we propagate the event
|
|
9627
|
-
// to the filter stream.
|
|
9628
|
-
// This usually happens when the camera/microphone permissions
|
|
9629
|
-
// are revoked or when the device is disconnected.
|
|
9630
|
-
const handleParentTrackEnded = () => {
|
|
9631
|
-
filterStream.getTracks().forEach((track) => {
|
|
9632
|
-
if (parentTrack.kind !== track.kind)
|
|
9633
|
-
return;
|
|
9634
|
-
track.stop();
|
|
9635
|
-
track.dispatchEvent(new Event('ended')); // propagate the event
|
|
9636
|
-
});
|
|
9637
|
-
};
|
|
9638
|
-
parentTrack.addEventListener('ended', handleParentTrackEnded);
|
|
9639
|
-
this.subscriptions.push(() => {
|
|
9640
|
-
parentTrack.removeEventListener('ended', handleParentTrackEnded);
|
|
9641
|
-
});
|
|
9642
|
-
});
|
|
9671
|
+
if (this.state.mediaStream &&
|
|
9672
|
+
this.getTracks().every((t) => t.readyState === 'live')) {
|
|
9673
|
+
stream = this.state.mediaStream;
|
|
9674
|
+
this.enableTracks();
|
|
9675
|
+
}
|
|
9676
|
+
else {
|
|
9677
|
+
const defaultConstraints = this.state.defaultConstraints;
|
|
9678
|
+
const constraints = {
|
|
9679
|
+
...defaultConstraints,
|
|
9680
|
+
deviceId: this.state.selectedDevice
|
|
9681
|
+
? { exact: this.state.selectedDevice }
|
|
9682
|
+
: undefined,
|
|
9683
|
+
};
|
|
9684
|
+
/**
|
|
9685
|
+
* Chains two media streams together.
|
|
9686
|
+
*
|
|
9687
|
+
* In our case, filters MediaStreams are derived from their parent MediaStream.
|
|
9688
|
+
* However, once a child filter's track is stopped,
|
|
9689
|
+
* the tracks of the parent MediaStream aren't automatically stopped.
|
|
9690
|
+
* This leads to a situation where the camera indicator light is still on
|
|
9691
|
+
* even though the user stopped publishing video.
|
|
9692
|
+
*
|
|
9693
|
+
* This function works around this issue by stopping the parent MediaStream's tracks
|
|
9694
|
+
* as well once the child filter's tracks are stopped.
|
|
9695
|
+
*
|
|
9696
|
+
* It works by patching the stop() method of the child filter's tracks to also stop
|
|
9697
|
+
* the parent MediaStream's tracks of the same type. Here we assume that
|
|
9698
|
+
* the parent MediaStream has only one track of each type.
|
|
9699
|
+
*
|
|
9700
|
+
* @param parentStream the parent MediaStream. Omit for the root stream.
|
|
9701
|
+
*/
|
|
9702
|
+
const chainWith = (parentStream) => async (filterStream) => {
|
|
9703
|
+
if (!parentStream)
|
|
9643
9704
|
return filterStream;
|
|
9644
|
-
|
|
9645
|
-
|
|
9646
|
-
|
|
9647
|
-
|
|
9648
|
-
|
|
9649
|
-
|
|
9650
|
-
|
|
9651
|
-
|
|
9652
|
-
|
|
9653
|
-
|
|
9654
|
-
|
|
9655
|
-
|
|
9656
|
-
|
|
9657
|
-
|
|
9658
|
-
|
|
9659
|
-
|
|
9660
|
-
|
|
9661
|
-
|
|
9662
|
-
|
|
9663
|
-
|
|
9664
|
-
|
|
9665
|
-
|
|
9666
|
-
|
|
9667
|
-
|
|
9668
|
-
|
|
9669
|
-
|
|
9670
|
-
|
|
9671
|
-
}, 2000);
|
|
9672
|
-
await this.disable();
|
|
9673
|
-
}
|
|
9674
|
-
};
|
|
9675
|
-
const createTrackMuteHandler = (muted) => () => {
|
|
9676
|
-
if (!isMobile() || this.trackType !== TrackType.VIDEO)
|
|
9677
|
-
return;
|
|
9678
|
-
this.call.notifyTrackMuteState(muted, this.trackType).catch((err) => {
|
|
9679
|
-
this.logger('warn', 'Error while notifying track mute state', err);
|
|
9680
|
-
});
|
|
9681
|
-
};
|
|
9682
|
-
stream.getTracks().forEach((track) => {
|
|
9683
|
-
const muteHandler = createTrackMuteHandler(true);
|
|
9684
|
-
const unmuteHandler = createTrackMuteHandler(false);
|
|
9685
|
-
track.addEventListener('mute', muteHandler);
|
|
9686
|
-
track.addEventListener('unmute', unmuteHandler);
|
|
9687
|
-
track.addEventListener('ended', handleTrackEnded);
|
|
9705
|
+
// TODO OL: take care of track.enabled property as well
|
|
9706
|
+
const parent = await parentStream;
|
|
9707
|
+
filterStream.getTracks().forEach((track) => {
|
|
9708
|
+
const originalStop = track.stop;
|
|
9709
|
+
track.stop = function stop() {
|
|
9710
|
+
originalStop.call(track);
|
|
9711
|
+
parent.getTracks().forEach((parentTrack) => {
|
|
9712
|
+
if (parentTrack.kind === track.kind) {
|
|
9713
|
+
parentTrack.stop();
|
|
9714
|
+
}
|
|
9715
|
+
});
|
|
9716
|
+
};
|
|
9717
|
+
});
|
|
9718
|
+
parent.getTracks().forEach((parentTrack) => {
|
|
9719
|
+
// When the parent stream abruptly ends, we propagate the event
|
|
9720
|
+
// to the filter stream.
|
|
9721
|
+
// This usually happens when the camera/microphone permissions
|
|
9722
|
+
// are revoked or when the device is disconnected.
|
|
9723
|
+
const handleParentTrackEnded = () => {
|
|
9724
|
+
filterStream.getTracks().forEach((track) => {
|
|
9725
|
+
if (parentTrack.kind !== track.kind)
|
|
9726
|
+
return;
|
|
9727
|
+
track.stop();
|
|
9728
|
+
track.dispatchEvent(new Event('ended')); // propagate the event
|
|
9729
|
+
});
|
|
9730
|
+
};
|
|
9731
|
+
parentTrack.addEventListener('ended', handleParentTrackEnded);
|
|
9688
9732
|
this.subscriptions.push(() => {
|
|
9689
|
-
|
|
9690
|
-
track.removeEventListener('unmute', unmuteHandler);
|
|
9691
|
-
track.removeEventListener('ended', handleTrackEnded);
|
|
9733
|
+
parentTrack.removeEventListener('ended', handleParentTrackEnded);
|
|
9692
9734
|
});
|
|
9693
9735
|
});
|
|
9694
|
-
|
|
9736
|
+
return filterStream;
|
|
9737
|
+
};
|
|
9738
|
+
// the rootStream represents the stream coming from the actual device
|
|
9739
|
+
// e.g. camera or microphone stream
|
|
9740
|
+
rootStream = this.getStream(constraints);
|
|
9741
|
+
// we publish the last MediaStream of the chain
|
|
9742
|
+
stream = await this.filters.reduce((parent, entry) => parent
|
|
9743
|
+
.then((inputStream) => {
|
|
9744
|
+
const { stop, output } = entry.start(inputStream);
|
|
9745
|
+
entry.stop = stop;
|
|
9746
|
+
return output;
|
|
9747
|
+
})
|
|
9748
|
+
.then(chainWith(parent), (error) => {
|
|
9749
|
+
this.logger('warn', 'Filter failed to start and will be ignored', error);
|
|
9750
|
+
return parent;
|
|
9751
|
+
}), rootStream);
|
|
9695
9752
|
}
|
|
9696
|
-
|
|
9697
|
-
|
|
9698
|
-
|
|
9699
|
-
|
|
9700
|
-
|
|
9753
|
+
if (this.call.state.callingState === CallingState.JOINED) {
|
|
9754
|
+
await this.publishStream(stream);
|
|
9755
|
+
}
|
|
9756
|
+
if (this.state.mediaStream !== stream) {
|
|
9757
|
+
this.state.setMediaStream(stream, await rootStream);
|
|
9758
|
+
const handleTrackEnded = async () => {
|
|
9759
|
+
await this.statusChangeSettled();
|
|
9760
|
+
if (this.enabled) {
|
|
9761
|
+
this.isTrackStoppedDueToTrackEnd = true;
|
|
9762
|
+
setTimeout(() => {
|
|
9763
|
+
this.isTrackStoppedDueToTrackEnd = false;
|
|
9764
|
+
}, 2000);
|
|
9765
|
+
await this.disable();
|
|
9766
|
+
}
|
|
9767
|
+
};
|
|
9768
|
+
const createTrackMuteHandler = (muted) => () => {
|
|
9769
|
+
if (!isMobile() || this.trackType !== TrackType.VIDEO)
|
|
9770
|
+
return;
|
|
9771
|
+
this.call.notifyTrackMuteState(muted, this.trackType).catch((err) => {
|
|
9772
|
+
this.logger('warn', 'Error while notifying track mute state', err);
|
|
9773
|
+
});
|
|
9774
|
+
};
|
|
9775
|
+
stream.getTracks().forEach((track) => {
|
|
9776
|
+
const muteHandler = createTrackMuteHandler(true);
|
|
9777
|
+
const unmuteHandler = createTrackMuteHandler(false);
|
|
9778
|
+
track.addEventListener('mute', muteHandler);
|
|
9779
|
+
track.addEventListener('unmute', unmuteHandler);
|
|
9780
|
+
track.addEventListener('ended', handleTrackEnded);
|
|
9781
|
+
this.subscriptions.push(() => {
|
|
9782
|
+
track.removeEventListener('mute', muteHandler);
|
|
9783
|
+
track.removeEventListener('unmute', unmuteHandler);
|
|
9784
|
+
track.removeEventListener('ended', handleTrackEnded);
|
|
9785
|
+
});
|
|
9786
|
+
});
|
|
9701
9787
|
}
|
|
9702
9788
|
}
|
|
9703
9789
|
get mediaDeviceKind() {
|
|
@@ -10424,6 +10510,7 @@ class MicrophoneManager extends InputMediaDeviceManager {
|
|
|
10424
10510
|
await this.disableNoiseCancellation().catch((err) => {
|
|
10425
10511
|
this.logger('warn', 'Failed to disable noise cancellation', err);
|
|
10426
10512
|
});
|
|
10513
|
+
throw e;
|
|
10427
10514
|
}
|
|
10428
10515
|
}
|
|
10429
10516
|
/**
|
|
@@ -11060,6 +11147,7 @@ class Call {
|
|
|
11060
11147
|
await this.sfuClient?.leaveAndClose(message ?? reason ?? 'user is leaving the call');
|
|
11061
11148
|
this.sfuClient = undefined;
|
|
11062
11149
|
this.dynascaleManager.setSfuClient(undefined);
|
|
11150
|
+
await this.dynascaleManager.dispose();
|
|
11063
11151
|
this.state.setCallingState(CallingState.LEFT);
|
|
11064
11152
|
this.state.setParticipants([]);
|
|
11065
11153
|
this.state.dispose();
|
|
@@ -13783,7 +13871,7 @@ class StreamClient {
|
|
|
13783
13871
|
this.getUserAgent = () => {
|
|
13784
13872
|
if (!this.cachedUserAgent) {
|
|
13785
13873
|
const { clientAppIdentifier = {} } = this.options;
|
|
13786
|
-
const { sdkName = 'js', sdkVersion = "1.23.
|
|
13874
|
+
const { sdkName = 'js', sdkVersion = "1.23.3", ...extras } = clientAppIdentifier;
|
|
13787
13875
|
this.cachedUserAgent = [
|
|
13788
13876
|
`stream-video-${sdkName}-v${sdkVersion}`,
|
|
13789
13877
|
...Object.entries(extras).map(([key, value]) => `${key}=${value}`),
|