@stream-io/video-client 0.3.12 → 0.3.14
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 +614 -391
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +613 -390
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +614 -391
- package/dist/index.es.js.map +1 -1
- package/dist/src/Call.d.ts +6 -1
- package/dist/src/devices/CameraManager.d.ts +1 -2
- package/dist/src/devices/InputMediaDeviceManager.d.ts +10 -3
- package/dist/src/devices/MicrophoneManager.d.ts +1 -2
- package/dist/src/devices/SpeakerManager.d.ts +28 -0
- package/dist/src/devices/SpeakerState.d.ts +64 -0
- package/dist/src/devices/__tests__/SpeakerManager.test.d.ts +1 -0
- package/dist/src/rtc/Publisher.d.ts +6 -0
- package/dist/src/types.d.ts +2 -0
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
- package/src/Call.ts +91 -6
- package/src/StreamSfuClient.ts +4 -0
- package/src/coordinator/connection/location.ts +2 -2
- package/src/devices/CameraManager.ts +7 -8
- package/src/devices/InputMediaDeviceManager.ts +58 -12
- package/src/devices/MicrophoneManager.ts +3 -8
- package/src/devices/SpeakerManager.ts +50 -0
- package/src/devices/SpeakerState.ts +90 -0
- package/src/devices/__tests__/InputMediaDeviceManager.test.ts +5 -12
- package/src/devices/__tests__/SpeakerManager.test.ts +66 -0
- package/src/devices/__tests__/mocks.ts +4 -0
- package/src/helpers/DynascaleManager.ts +25 -7
- package/src/helpers/__tests__/DynascaleManager.test.ts +33 -2
- package/src/rtc/Publisher.ts +35 -7
- package/src/rtc/__tests__/Publisher.test.ts +1 -0
- package/src/rtc/codecs.ts +0 -2
- package/src/types.ts +2 -0
package/dist/index.es.js
CHANGED
|
@@ -4,7 +4,7 @@ import { ServiceType, stackIntercept } from '@protobuf-ts/runtime-rpc';
|
|
|
4
4
|
import axios, { AxiosHeaders } from 'axios';
|
|
5
5
|
export { AxiosError } from 'axios';
|
|
6
6
|
import { TwirpFetchTransport } from '@protobuf-ts/twirp-transport';
|
|
7
|
-
import { ReplaySubject, BehaviorSubject, map as map$2, takeWhile, distinctUntilChanged as distinctUntilChanged$1, distinctUntilKeyChanged, Observable, debounceTime, concatMap, from, shareReplay, merge,
|
|
7
|
+
import { ReplaySubject, BehaviorSubject, map as map$2, takeWhile, distinctUntilChanged as distinctUntilChanged$1, distinctUntilKeyChanged, combineLatest, Observable, debounceTime, concatMap, from, shareReplay, merge, filter, pairwise, tap, debounce, timer } from 'rxjs';
|
|
8
8
|
import * as SDP from 'sdp-transform';
|
|
9
9
|
import { UAParser } from 'ua-parser-js';
|
|
10
10
|
import WebSocket from 'isomorphic-ws';
|
|
@@ -6493,6 +6493,9 @@ class Publisher {
|
|
|
6493
6493
|
// by an external factor as permission revokes, device disconnected, etc.
|
|
6494
6494
|
// keep in mind that `track.stop()` doesn't trigger this event.
|
|
6495
6495
|
track.addEventListener('ended', handleTrackEnded);
|
|
6496
|
+
if (!track.enabled) {
|
|
6497
|
+
track.enabled = true;
|
|
6498
|
+
}
|
|
6496
6499
|
transceiver = this.pc.addTransceiver(track, {
|
|
6497
6500
|
direction: 'sendonly',
|
|
6498
6501
|
streams: trackType === TrackType.VIDEO || trackType === TrackType.SCREEN_SHARE
|
|
@@ -6530,16 +6533,25 @@ class Publisher {
|
|
|
6530
6533
|
* @param stopTrack specifies whether track should be stopped or just disabled
|
|
6531
6534
|
*/
|
|
6532
6535
|
this.unpublishStream = (trackType, stopTrack) => __awaiter(this, void 0, void 0, function* () {
|
|
6536
|
+
var _b;
|
|
6533
6537
|
const transceiver = this.pc
|
|
6534
6538
|
.getTransceivers()
|
|
6535
6539
|
.find((t) => t === this.transceiverRegistry[trackType] && t.sender.track);
|
|
6536
6540
|
if (transceiver &&
|
|
6537
6541
|
transceiver.sender.track &&
|
|
6538
|
-
|
|
6542
|
+
(stopTrack
|
|
6543
|
+
? transceiver.sender.track.readyState === 'live'
|
|
6544
|
+
: transceiver.sender.track.enabled)) {
|
|
6539
6545
|
stopTrack
|
|
6540
6546
|
? transceiver.sender.track.stop()
|
|
6541
6547
|
: (transceiver.sender.track.enabled = false);
|
|
6542
|
-
|
|
6548
|
+
// We don't need to notify SFU if unpublishing in response to remote soft mute
|
|
6549
|
+
if (!((_b = this.state.localParticipant) === null || _b === void 0 ? void 0 : _b.publishedTracks.includes(trackType))) {
|
|
6550
|
+
return;
|
|
6551
|
+
}
|
|
6552
|
+
else {
|
|
6553
|
+
return this.notifyTrackMuteStateChanged(undefined, transceiver.sender.track, trackType, true);
|
|
6554
|
+
}
|
|
6543
6555
|
}
|
|
6544
6556
|
});
|
|
6545
6557
|
/**
|
|
@@ -6548,6 +6560,21 @@ class Publisher {
|
|
|
6548
6560
|
* @param trackType the track type to check.
|
|
6549
6561
|
*/
|
|
6550
6562
|
this.isPublishing = (trackType) => {
|
|
6563
|
+
const transceiverForTrackType = this.transceiverRegistry[trackType];
|
|
6564
|
+
if (transceiverForTrackType && transceiverForTrackType.sender) {
|
|
6565
|
+
const sender = transceiverForTrackType.sender;
|
|
6566
|
+
return (!!sender.track &&
|
|
6567
|
+
sender.track.readyState === 'live' &&
|
|
6568
|
+
sender.track.enabled);
|
|
6569
|
+
}
|
|
6570
|
+
return false;
|
|
6571
|
+
};
|
|
6572
|
+
/**
|
|
6573
|
+
* Returns true if the given track type is currently live
|
|
6574
|
+
*
|
|
6575
|
+
* @param trackType the track type to check.
|
|
6576
|
+
*/
|
|
6577
|
+
this.isLive = (trackType) => {
|
|
6551
6578
|
const transceiverForTrackType = this.transceiverRegistry[trackType];
|
|
6552
6579
|
if (transceiverForTrackType && transceiverForTrackType.sender) {
|
|
6553
6580
|
const sender = transceiverForTrackType.sender;
|
|
@@ -6588,9 +6615,9 @@ class Publisher {
|
|
|
6588
6615
|
});
|
|
6589
6616
|
};
|
|
6590
6617
|
this.updateVideoPublishQuality = (enabledRids) => __awaiter(this, void 0, void 0, function* () {
|
|
6591
|
-
var
|
|
6618
|
+
var _c;
|
|
6592
6619
|
logger$3('info', 'Update publish quality, requested rids by SFU:', enabledRids);
|
|
6593
|
-
const videoSender = (
|
|
6620
|
+
const videoSender = (_c = this.transceiverRegistry[TrackType.VIDEO]) === null || _c === void 0 ? void 0 : _c.sender;
|
|
6594
6621
|
if (!videoSender) {
|
|
6595
6622
|
logger$3('warn', 'Update publish quality, no video sender found.');
|
|
6596
6623
|
return;
|
|
@@ -6688,8 +6715,8 @@ class Publisher {
|
|
|
6688
6715
|
* @param options the optional offer options to use.
|
|
6689
6716
|
*/
|
|
6690
6717
|
this.negotiate = (options) => __awaiter(this, void 0, void 0, function* () {
|
|
6691
|
-
var
|
|
6692
|
-
this.isIceRestarting = (
|
|
6718
|
+
var _d;
|
|
6719
|
+
this.isIceRestarting = (_d = options === null || options === void 0 ? void 0 : options.iceRestart) !== null && _d !== void 0 ? _d : false;
|
|
6693
6720
|
const offer = yield this.pc.createOffer(options);
|
|
6694
6721
|
offer.sdp = this.mungeCodecs(offer.sdp);
|
|
6695
6722
|
const trackInfos = this.getCurrentTrackInfos(offer.sdp);
|
|
@@ -7477,6 +7504,9 @@ const retryable = (rpc, logger) => __awaiter(void 0, void 0, void 0, function* (
|
|
|
7477
7504
|
retryAttempt++;
|
|
7478
7505
|
} while (((_a = rpcCallResult.response.error) === null || _a === void 0 ? void 0 : _a.shouldRetry) &&
|
|
7479
7506
|
retryAttempt < MAX_RETRIES);
|
|
7507
|
+
if (rpcCallResult.response.error) {
|
|
7508
|
+
throw rpcCallResult.response.error;
|
|
7509
|
+
}
|
|
7480
7510
|
return rpcCallResult;
|
|
7481
7511
|
});
|
|
7482
7512
|
|
|
@@ -9513,15 +9543,26 @@ class DynascaleManager {
|
|
|
9513
9543
|
}
|
|
9514
9544
|
});
|
|
9515
9545
|
});
|
|
9516
|
-
const sinkIdSubscription =
|
|
9517
|
-
|
|
9546
|
+
const sinkIdSubscription = combineLatest([
|
|
9547
|
+
this.call.state.localParticipant$,
|
|
9548
|
+
this.call.speaker.state.selectedDevice$,
|
|
9549
|
+
]).subscribe(([p, selectedDevice]) => {
|
|
9550
|
+
var _a;
|
|
9551
|
+
const deviceId = ((_a = getSdkInfo()) === null || _a === void 0 ? void 0 : _a.type) === SdkType.REACT
|
|
9552
|
+
? p === null || p === void 0 ? void 0 : p.audioOutputDeviceId
|
|
9553
|
+
: selectedDevice;
|
|
9554
|
+
if ('setSinkId' in audioElement) {
|
|
9518
9555
|
// @ts-expect-error setSinkId is not yet in the lib
|
|
9519
|
-
audioElement.setSinkId(
|
|
9556
|
+
audioElement.setSinkId(deviceId);
|
|
9520
9557
|
}
|
|
9521
9558
|
});
|
|
9559
|
+
const volumeSubscription = this.call.speaker.state.volume$.subscribe((volume) => {
|
|
9560
|
+
audioElement.volume = volume;
|
|
9561
|
+
});
|
|
9522
9562
|
audioElement.autoplay = true;
|
|
9523
9563
|
return () => {
|
|
9524
9564
|
sinkIdSubscription.unsubscribe();
|
|
9565
|
+
volumeSubscription.unsubscribe();
|
|
9525
9566
|
updateMediaStreamSubscription.unsubscribe();
|
|
9526
9567
|
};
|
|
9527
9568
|
};
|
|
@@ -9668,258 +9709,133 @@ const CallTypes = new CallTypesRegistry([
|
|
|
9668
9709
|
}),
|
|
9669
9710
|
]);
|
|
9670
9711
|
|
|
9671
|
-
|
|
9672
|
-
|
|
9673
|
-
|
|
9674
|
-
|
|
9675
|
-
|
|
9676
|
-
|
|
9677
|
-
|
|
9678
|
-
|
|
9679
|
-
|
|
9680
|
-
|
|
9681
|
-
|
|
9682
|
-
|
|
9683
|
-
|
|
9684
|
-
|
|
9685
|
-
|
|
9686
|
-
|
|
9687
|
-
|
|
9688
|
-
|
|
9689
|
-
|
|
9690
|
-
|
|
9691
|
-
|
|
9692
|
-
|
|
9693
|
-
|
|
9694
|
-
|
|
9695
|
-
|
|
9696
|
-
|
|
9697
|
-
|
|
9698
|
-
|
|
9699
|
-
|
|
9700
|
-
|
|
9701
|
-
|
|
9702
|
-
|
|
9703
|
-
* The default constraints used to request audio devices.
|
|
9704
|
-
*/
|
|
9705
|
-
const audioDeviceConstraints = {
|
|
9706
|
-
audio: {
|
|
9707
|
-
autoGainControl: true,
|
|
9708
|
-
noiseSuppression: true,
|
|
9709
|
-
echoCancellation: true,
|
|
9710
|
-
},
|
|
9711
|
-
};
|
|
9712
|
-
/**
|
|
9713
|
-
* The default constraints used to request video devices.
|
|
9714
|
-
*/
|
|
9715
|
-
const videoDeviceConstraints = {
|
|
9716
|
-
video: {
|
|
9717
|
-
width: 1280,
|
|
9718
|
-
height: 720,
|
|
9719
|
-
},
|
|
9720
|
-
};
|
|
9721
|
-
// Audio and video devices are requested in two separate requests: that way users will be presented with two separate prompts -> they can give access to just camera, or just microphone
|
|
9722
|
-
const deviceChange$ = new Observable((subscriber) => {
|
|
9723
|
-
var _a, _b;
|
|
9724
|
-
const deviceChangeHandler = () => subscriber.next();
|
|
9725
|
-
(_b = (_a = navigator.mediaDevices).addEventListener) === null || _b === void 0 ? void 0 : _b.call(_a, 'devicechange', deviceChangeHandler);
|
|
9726
|
-
return () => {
|
|
9727
|
-
var _a, _b;
|
|
9728
|
-
return (_b = (_a = navigator.mediaDevices).removeEventListener) === null || _b === void 0 ? void 0 : _b.call(_a, 'devicechange', deviceChangeHandler);
|
|
9729
|
-
};
|
|
9730
|
-
}).pipe(debounceTime(500), concatMap(() => from(navigator.mediaDevices.enumerateDevices())), shareReplay(1));
|
|
9731
|
-
const audioDevices$ = merge(getDevices(audioDeviceConstraints), deviceChange$).pipe(shareReplay(1));
|
|
9732
|
-
const videoDevices$ = merge(getDevices(videoDeviceConstraints), deviceChange$).pipe(shareReplay(1));
|
|
9733
|
-
/**
|
|
9734
|
-
* Prompts the user for a permission to use audio devices (if not already granted) and lists the available 'audioinput' devices, if devices are added/removed the list is updated.
|
|
9735
|
-
*
|
|
9736
|
-
* @angular It's recommended to use the [`DeviceManagerService`](./DeviceManagerService.md) for a higher level API, use this low-level method only if the `DeviceManagerService` doesn't suit your requirements.
|
|
9737
|
-
* @returns
|
|
9738
|
-
*/
|
|
9739
|
-
const getAudioDevices = () => audioDevices$.pipe(map$2((values) => values.filter((d) => d.kind === 'audioinput')));
|
|
9740
|
-
/**
|
|
9741
|
-
* Prompts the user for a permission to use video devices (if not already granted) and lists the available 'videoinput' devices, if devices are added/removed the list is updated.
|
|
9742
|
-
*
|
|
9743
|
-
* @angular It's recommended to use the [`DeviceManagerService`](./DeviceManagerService.md) for a higher level API, use this low-level method only if the `DeviceManagerService` doesn't suit your requirements.
|
|
9744
|
-
* @returns
|
|
9745
|
-
*/
|
|
9746
|
-
const getVideoDevices = () => videoDevices$.pipe(map$2((values) => values.filter((d) => d.kind === 'videoinput' && d.deviceId.length)));
|
|
9747
|
-
/**
|
|
9748
|
-
* Prompts the user for a permission to use audio devices (if not already granted) and lists the available 'audiooutput' devices, if devices are added/removed the list is updated. Selecting 'audiooutput' device only makes sense if [the browser has support for changing audio output on 'audio' elements](#checkifaudiooutputchangesupported)
|
|
9749
|
-
*
|
|
9750
|
-
* @angular It's recommended to use the [`DeviceManagerService`](./DeviceManagerService.md) for a higher level API, use this low-level method only if the `DeviceManagerService` doesn't suit your requirements.
|
|
9751
|
-
* @returns
|
|
9752
|
-
*/
|
|
9753
|
-
const getAudioOutputDevices = () => {
|
|
9754
|
-
return audioDevices$.pipe(map$2((values) => values.filter((d) => d.kind === 'audiooutput')));
|
|
9755
|
-
};
|
|
9756
|
-
const getStream = (constraints) => __awaiter(void 0, void 0, void 0, function* () {
|
|
9757
|
-
try {
|
|
9758
|
-
return yield navigator.mediaDevices.getUserMedia(constraints);
|
|
9712
|
+
class InputMediaDeviceManagerState {
|
|
9713
|
+
constructor(disableMode = 'stop-tracks') {
|
|
9714
|
+
this.disableMode = disableMode;
|
|
9715
|
+
this.statusSubject = new BehaviorSubject(undefined);
|
|
9716
|
+
this.mediaStreamSubject = new BehaviorSubject(undefined);
|
|
9717
|
+
this.selectedDeviceSubject = new BehaviorSubject(undefined);
|
|
9718
|
+
/**
|
|
9719
|
+
* Gets the current value of an observable, or undefined if the observable has
|
|
9720
|
+
* not emitted a value yet.
|
|
9721
|
+
*
|
|
9722
|
+
* @param observable$ the observable to get the value from.
|
|
9723
|
+
*/
|
|
9724
|
+
this.getCurrentValue = getCurrentValue;
|
|
9725
|
+
/**
|
|
9726
|
+
* Updates the value of the provided Subject.
|
|
9727
|
+
* An `update` can either be a new value or a function which takes
|
|
9728
|
+
* the current value and returns a new value.
|
|
9729
|
+
*
|
|
9730
|
+
* @internal
|
|
9731
|
+
*
|
|
9732
|
+
* @param subject the subject to update.
|
|
9733
|
+
* @param update the update to apply to the subject.
|
|
9734
|
+
* @return the updated value.
|
|
9735
|
+
*/
|
|
9736
|
+
this.setCurrentValue = setCurrentValue;
|
|
9737
|
+
this.mediaStream$ = this.mediaStreamSubject.asObservable();
|
|
9738
|
+
this.selectedDevice$ = this.selectedDeviceSubject
|
|
9739
|
+
.asObservable()
|
|
9740
|
+
.pipe(distinctUntilChanged$1());
|
|
9741
|
+
this.status$ = this.statusSubject
|
|
9742
|
+
.asObservable()
|
|
9743
|
+
.pipe(distinctUntilChanged$1());
|
|
9759
9744
|
}
|
|
9760
|
-
|
|
9761
|
-
|
|
9762
|
-
|
|
9763
|
-
|
|
9764
|
-
|
|
9765
|
-
throw e;
|
|
9745
|
+
/**
|
|
9746
|
+
* The device status
|
|
9747
|
+
*/
|
|
9748
|
+
get status() {
|
|
9749
|
+
return this.getCurrentValue(this.status$);
|
|
9766
9750
|
}
|
|
9767
|
-
|
|
9768
|
-
|
|
9769
|
-
|
|
9770
|
-
|
|
9771
|
-
|
|
9772
|
-
* @angular It's recommended to use the [`DeviceManagerService`](./DeviceManagerService.md) for a higher level API, use this low-level method only if the `DeviceManagerService` doesn't suit your requirements.
|
|
9773
|
-
* @param trackConstraints the constraints to use when requesting the stream.
|
|
9774
|
-
* @returns the new `MediaStream` fulfilling the given constraints.
|
|
9775
|
-
*/
|
|
9776
|
-
const getAudioStream = (trackConstraints) => __awaiter(void 0, void 0, void 0, function* () {
|
|
9777
|
-
const constraints = {
|
|
9778
|
-
audio: Object.assign(Object.assign({}, audioDeviceConstraints.audio), trackConstraints),
|
|
9779
|
-
};
|
|
9780
|
-
return getStream(constraints);
|
|
9781
|
-
});
|
|
9782
|
-
/**
|
|
9783
|
-
* Returns a video media stream that fulfills the given constraints.
|
|
9784
|
-
* If no constraints are provided, it uses the browser's default ones.
|
|
9785
|
-
*
|
|
9786
|
-
* @angular It's recommended to use the [`DeviceManagerService`](./DeviceManagerService.md) for a higher level API, use this low-level method only if the `DeviceManagerService` doesn't suit your requirements.
|
|
9787
|
-
* @param trackConstraints the constraints to use when requesting the stream.
|
|
9788
|
-
* @returns a new `MediaStream` fulfilling the given constraints.
|
|
9789
|
-
*/
|
|
9790
|
-
const getVideoStream = (trackConstraints) => __awaiter(void 0, void 0, void 0, function* () {
|
|
9791
|
-
const constraints = {
|
|
9792
|
-
video: Object.assign(Object.assign({}, videoDeviceConstraints.video), trackConstraints),
|
|
9793
|
-
};
|
|
9794
|
-
return getStream(constraints);
|
|
9795
|
-
});
|
|
9796
|
-
/**
|
|
9797
|
-
* Prompts the user for a permission to share a screen.
|
|
9798
|
-
* If the user grants the permission, a screen sharing stream is returned. Throws otherwise.
|
|
9799
|
-
*
|
|
9800
|
-
* The callers of this API are responsible to handle the possible errors.
|
|
9801
|
-
*
|
|
9802
|
-
* @angular It's recommended to use the [`DeviceManagerService`](./DeviceManagerService.md) for a higher level API, use this low-level method only if the `DeviceManagerService` doesn't suit your requirements.
|
|
9803
|
-
*
|
|
9804
|
-
* @param options any additional options to pass to the [`getDisplayMedia`](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getDisplayMedia) API.
|
|
9805
|
-
*/
|
|
9806
|
-
const getScreenShareStream = (options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
9807
|
-
try {
|
|
9808
|
-
return yield navigator.mediaDevices.getDisplayMedia(Object.assign({ video: true, audio: false }, options));
|
|
9751
|
+
/**
|
|
9752
|
+
* The currently selected device
|
|
9753
|
+
*/
|
|
9754
|
+
get selectedDevice() {
|
|
9755
|
+
return this.getCurrentValue(this.selectedDevice$);
|
|
9809
9756
|
}
|
|
9810
|
-
|
|
9811
|
-
|
|
9812
|
-
|
|
9757
|
+
/**
|
|
9758
|
+
* The current media stream, or `undefined` if the device is currently disabled.
|
|
9759
|
+
*/
|
|
9760
|
+
get mediaStream() {
|
|
9761
|
+
return this.getCurrentValue(this.mediaStream$);
|
|
9813
9762
|
}
|
|
9814
|
-
|
|
9815
|
-
|
|
9816
|
-
|
|
9817
|
-
|
|
9818
|
-
|
|
9819
|
-
|
|
9820
|
-
break;
|
|
9821
|
-
case 'videoinput':
|
|
9822
|
-
devices$ = getVideoDevices();
|
|
9823
|
-
break;
|
|
9824
|
-
case 'audiooutput':
|
|
9825
|
-
devices$ = getAudioOutputDevices();
|
|
9826
|
-
break;
|
|
9763
|
+
/**
|
|
9764
|
+
* @internal
|
|
9765
|
+
* @param status
|
|
9766
|
+
*/
|
|
9767
|
+
setStatus(status) {
|
|
9768
|
+
this.setCurrentValue(this.statusSubject, status);
|
|
9827
9769
|
}
|
|
9828
|
-
|
|
9829
|
-
|
|
9830
|
-
|
|
9831
|
-
|
|
9832
|
-
|
|
9833
|
-
|
|
9834
|
-
|
|
9835
|
-
|
|
9836
|
-
|
|
9837
|
-
const watchForDisconnectedAudioDevice = (deviceId$) => {
|
|
9838
|
-
return watchForDisconnectedDevice('audioinput', deviceId$);
|
|
9839
|
-
};
|
|
9840
|
-
/**
|
|
9841
|
-
* Notifies the subscriber if a given 'videoinput' device is disconnected
|
|
9842
|
-
*
|
|
9843
|
-
* @angular It's recommended to use the [`DeviceManagerService`](./DeviceManagerService.md) for a higher level API, use this low-level method only if the `DeviceManagerService` doesn't suit your requirements.
|
|
9844
|
-
* @param deviceId$ an Observable that specifies which device to watch for
|
|
9845
|
-
* @returns
|
|
9846
|
-
*/
|
|
9847
|
-
const watchForDisconnectedVideoDevice = (deviceId$) => {
|
|
9848
|
-
return watchForDisconnectedDevice('videoinput', deviceId$);
|
|
9849
|
-
};
|
|
9850
|
-
/**
|
|
9851
|
-
* Notifies the subscriber if a given 'audiooutput' device is disconnected
|
|
9852
|
-
*
|
|
9853
|
-
* @angular It's recommended to use the [`DeviceManagerService`](./DeviceManagerService.md) for a higher level API, use this low-level method only if the `DeviceManagerService` doesn't suit your requirements.
|
|
9854
|
-
* @param deviceId$ an Observable that specifies which device to watch for
|
|
9855
|
-
* @returns
|
|
9856
|
-
*/
|
|
9857
|
-
const watchForDisconnectedAudioOutputDevice = (deviceId$) => {
|
|
9858
|
-
return watchForDisconnectedDevice('audiooutput', deviceId$);
|
|
9859
|
-
};
|
|
9860
|
-
const watchForAddedDefaultDevice = (kind) => {
|
|
9861
|
-
let devices$;
|
|
9862
|
-
switch (kind) {
|
|
9863
|
-
case 'audioinput':
|
|
9864
|
-
devices$ = getAudioDevices();
|
|
9865
|
-
break;
|
|
9866
|
-
case 'videoinput':
|
|
9867
|
-
devices$ = getVideoDevices();
|
|
9868
|
-
break;
|
|
9869
|
-
case 'audiooutput':
|
|
9870
|
-
devices$ = getAudioOutputDevices();
|
|
9871
|
-
break;
|
|
9872
|
-
default:
|
|
9873
|
-
throw new Error('Unknown MediaDeviceKind', kind);
|
|
9770
|
+
/**
|
|
9771
|
+
* @internal
|
|
9772
|
+
* @param stream
|
|
9773
|
+
*/
|
|
9774
|
+
setMediaStream(stream) {
|
|
9775
|
+
this.setCurrentValue(this.mediaStreamSubject, stream);
|
|
9776
|
+
if (stream) {
|
|
9777
|
+
this.setDevice(this.getDeviceIdFromStream(stream));
|
|
9778
|
+
}
|
|
9874
9779
|
}
|
|
9875
|
-
|
|
9876
|
-
|
|
9877
|
-
|
|
9878
|
-
|
|
9879
|
-
|
|
9880
|
-
|
|
9881
|
-
prevDefault.groupId !== currentDefault.groupId);
|
|
9882
|
-
}), map$2(() => true));
|
|
9883
|
-
};
|
|
9884
|
-
/**
|
|
9885
|
-
* Notifies the subscriber about newly added default audio input device.
|
|
9886
|
-
* @returns Observable<boolean>
|
|
9887
|
-
*/
|
|
9888
|
-
const watchForAddedDefaultAudioDevice = () => watchForAddedDefaultDevice('audioinput');
|
|
9889
|
-
/**
|
|
9890
|
-
* Notifies the subscriber about newly added default audio output device.
|
|
9891
|
-
* @returns Observable<boolean>
|
|
9892
|
-
*/
|
|
9893
|
-
const watchForAddedDefaultAudioOutputDevice = () => watchForAddedDefaultDevice('audiooutput');
|
|
9894
|
-
/**
|
|
9895
|
-
* Notifies the subscriber about newly added default video input device.
|
|
9896
|
-
* @returns Observable<boolean>
|
|
9897
|
-
*/
|
|
9898
|
-
const watchForAddedDefaultVideoDevice = () => watchForAddedDefaultDevice('videoinput');
|
|
9899
|
-
/**
|
|
9900
|
-
* Deactivates MediaStream (stops and removes tracks) to be later garbage collected
|
|
9901
|
-
*
|
|
9902
|
-
* @param stream MediaStream
|
|
9903
|
-
* @returns void
|
|
9904
|
-
*/
|
|
9905
|
-
const disposeOfMediaStream = (stream) => {
|
|
9906
|
-
if (!stream.active)
|
|
9907
|
-
return;
|
|
9908
|
-
stream.getTracks().forEach((track) => {
|
|
9909
|
-
track.stop();
|
|
9910
|
-
stream.removeTrack(track);
|
|
9911
|
-
});
|
|
9912
|
-
// @ts-expect-error release() is present in react-native-webrtc and must be called to dispose the stream
|
|
9913
|
-
if (typeof stream.release === 'function') {
|
|
9914
|
-
// @ts-expect-error
|
|
9915
|
-
stream.release();
|
|
9780
|
+
/**
|
|
9781
|
+
* @internal
|
|
9782
|
+
* @param stream
|
|
9783
|
+
*/
|
|
9784
|
+
setDevice(deviceId) {
|
|
9785
|
+
this.setCurrentValue(this.selectedDeviceSubject, deviceId);
|
|
9916
9786
|
}
|
|
9917
|
-
}
|
|
9787
|
+
}
|
|
9788
|
+
|
|
9789
|
+
class CameraManagerState extends InputMediaDeviceManagerState {
|
|
9790
|
+
constructor() {
|
|
9791
|
+
super('stop-tracks');
|
|
9792
|
+
this.directionSubject = new BehaviorSubject(undefined);
|
|
9793
|
+
this.direction$ = this.directionSubject
|
|
9794
|
+
.asObservable()
|
|
9795
|
+
.pipe(distinctUntilChanged$1());
|
|
9796
|
+
}
|
|
9797
|
+
/**
|
|
9798
|
+
* The preferred camera direction
|
|
9799
|
+
* front - means the camera facing the user
|
|
9800
|
+
* back - means the camera facing the environment
|
|
9801
|
+
*/
|
|
9802
|
+
get direction() {
|
|
9803
|
+
return this.getCurrentValue(this.direction$);
|
|
9804
|
+
}
|
|
9805
|
+
/**
|
|
9806
|
+
* @internal
|
|
9807
|
+
*/
|
|
9808
|
+
setDirection(direction) {
|
|
9809
|
+
this.setCurrentValue(this.directionSubject, direction);
|
|
9810
|
+
}
|
|
9811
|
+
/**
|
|
9812
|
+
* @internal
|
|
9813
|
+
*/
|
|
9814
|
+
setMediaStream(stream) {
|
|
9815
|
+
var _a;
|
|
9816
|
+
super.setMediaStream(stream);
|
|
9817
|
+
if (stream) {
|
|
9818
|
+
// RN getSettings() doesn't return facingMode, so we don't verify camera direction
|
|
9819
|
+
const direction = isReactNative()
|
|
9820
|
+
? this.direction
|
|
9821
|
+
: ((_a = stream.getVideoTracks()[0]) === null || _a === void 0 ? void 0 : _a.getSettings().facingMode) === 'environment'
|
|
9822
|
+
? 'back'
|
|
9823
|
+
: 'front';
|
|
9824
|
+
this.setDirection(direction);
|
|
9825
|
+
}
|
|
9826
|
+
}
|
|
9827
|
+
getDeviceIdFromStream(stream) {
|
|
9828
|
+
var _a;
|
|
9829
|
+
return (_a = stream.getVideoTracks()[0]) === null || _a === void 0 ? void 0 : _a.getSettings().deviceId;
|
|
9830
|
+
}
|
|
9831
|
+
}
|
|
9918
9832
|
|
|
9919
9833
|
class InputMediaDeviceManager {
|
|
9920
|
-
constructor(call, state) {
|
|
9834
|
+
constructor(call, state, trackType) {
|
|
9921
9835
|
this.call = call;
|
|
9922
9836
|
this.state = state;
|
|
9837
|
+
this.trackType = trackType;
|
|
9838
|
+
this.logger = getLogger([`${TrackType[trackType].toLowerCase()} manager`]);
|
|
9923
9839
|
}
|
|
9924
9840
|
/**
|
|
9925
9841
|
* Lists the available audio/video devices
|
|
@@ -10027,31 +9943,66 @@ class InputMediaDeviceManager {
|
|
|
10027
9943
|
});
|
|
10028
9944
|
}
|
|
10029
9945
|
muteStream(stopTracks = true) {
|
|
9946
|
+
var _a;
|
|
10030
9947
|
return __awaiter(this, void 0, void 0, function* () {
|
|
10031
9948
|
if (!this.state.mediaStream) {
|
|
10032
9949
|
return;
|
|
10033
9950
|
}
|
|
9951
|
+
this.logger('debug', `${stopTracks ? 'Stopping' : 'Disabling'} stream`);
|
|
10034
9952
|
if (this.call.state.callingState === CallingState.JOINED) {
|
|
10035
9953
|
yield this.stopPublishStream(stopTracks);
|
|
10036
9954
|
}
|
|
10037
|
-
|
|
10038
|
-
|
|
10039
|
-
|
|
10040
|
-
|
|
10041
|
-
|
|
10042
|
-
|
|
9955
|
+
this.muteLocalStream(stopTracks);
|
|
9956
|
+
if (((_a = this.getTrack()) === null || _a === void 0 ? void 0 : _a.readyState) === 'ended') {
|
|
9957
|
+
// @ts-expect-error release() is present in react-native-webrtc and must be called to dispose the stream
|
|
9958
|
+
if (typeof this.state.mediaStream.release === 'function') {
|
|
9959
|
+
// @ts-expect-error
|
|
9960
|
+
this.state.mediaStream.release();
|
|
9961
|
+
}
|
|
10043
9962
|
this.state.setMediaStream(undefined);
|
|
10044
9963
|
}
|
|
10045
9964
|
});
|
|
10046
9965
|
}
|
|
9966
|
+
muteTrack() {
|
|
9967
|
+
const track = this.getTrack();
|
|
9968
|
+
if (!track || !track.enabled) {
|
|
9969
|
+
return;
|
|
9970
|
+
}
|
|
9971
|
+
track.enabled = false;
|
|
9972
|
+
}
|
|
9973
|
+
unmuteTrack() {
|
|
9974
|
+
const track = this.getTrack();
|
|
9975
|
+
if (!track || track.enabled) {
|
|
9976
|
+
return;
|
|
9977
|
+
}
|
|
9978
|
+
track.enabled = true;
|
|
9979
|
+
}
|
|
9980
|
+
stopTrack() {
|
|
9981
|
+
const track = this.getTrack();
|
|
9982
|
+
if (!track || track.readyState === 'ended') {
|
|
9983
|
+
return;
|
|
9984
|
+
}
|
|
9985
|
+
track.stop();
|
|
9986
|
+
}
|
|
9987
|
+
muteLocalStream(stopTracks) {
|
|
9988
|
+
if (!this.state.mediaStream) {
|
|
9989
|
+
return;
|
|
9990
|
+
}
|
|
9991
|
+
stopTracks ? this.stopTrack() : this.muteTrack();
|
|
9992
|
+
}
|
|
10047
9993
|
unmuteStream() {
|
|
9994
|
+
var _a;
|
|
10048
9995
|
return __awaiter(this, void 0, void 0, function* () {
|
|
9996
|
+
this.logger('debug', 'Starting stream');
|
|
10049
9997
|
let stream;
|
|
10050
|
-
if (this.state.mediaStream) {
|
|
9998
|
+
if (this.state.mediaStream && ((_a = this.getTrack()) === null || _a === void 0 ? void 0 : _a.readyState) === 'live') {
|
|
10051
9999
|
stream = this.state.mediaStream;
|
|
10052
|
-
this.
|
|
10000
|
+
this.unmuteTrack();
|
|
10053
10001
|
}
|
|
10054
10002
|
else {
|
|
10003
|
+
if (this.state.mediaStream) {
|
|
10004
|
+
this.stopTrack();
|
|
10005
|
+
}
|
|
10055
10006
|
const constraints = { deviceId: this.state.selectedDevice };
|
|
10056
10007
|
stream = yield this.getStream(constraints);
|
|
10057
10008
|
}
|
|
@@ -10063,130 +10014,257 @@ class InputMediaDeviceManager {
|
|
|
10063
10014
|
}
|
|
10064
10015
|
}
|
|
10065
10016
|
|
|
10066
|
-
|
|
10067
|
-
|
|
10068
|
-
|
|
10069
|
-
|
|
10070
|
-
|
|
10071
|
-
|
|
10072
|
-
|
|
10073
|
-
|
|
10074
|
-
|
|
10075
|
-
|
|
10076
|
-
|
|
10077
|
-
|
|
10078
|
-
|
|
10079
|
-
|
|
10080
|
-
|
|
10081
|
-
|
|
10082
|
-
|
|
10083
|
-
|
|
10084
|
-
|
|
10085
|
-
|
|
10086
|
-
|
|
10087
|
-
|
|
10088
|
-
|
|
10089
|
-
|
|
10090
|
-
|
|
10091
|
-
|
|
10092
|
-
|
|
10093
|
-
|
|
10094
|
-
|
|
10095
|
-
|
|
10096
|
-
|
|
10097
|
-
|
|
10098
|
-
|
|
10099
|
-
|
|
10100
|
-
|
|
10101
|
-
|
|
10102
|
-
|
|
10103
|
-
|
|
10104
|
-
|
|
10105
|
-
|
|
10106
|
-
|
|
10107
|
-
|
|
10108
|
-
|
|
10109
|
-
|
|
10110
|
-
|
|
10111
|
-
|
|
10112
|
-
|
|
10113
|
-
|
|
10114
|
-
|
|
10115
|
-
|
|
10116
|
-
|
|
10117
|
-
|
|
10118
|
-
|
|
10119
|
-
|
|
10120
|
-
|
|
10121
|
-
|
|
10122
|
-
|
|
10123
|
-
|
|
10124
|
-
|
|
10125
|
-
|
|
10126
|
-
|
|
10127
|
-
|
|
10128
|
-
|
|
10129
|
-
|
|
10130
|
-
|
|
10131
|
-
|
|
10132
|
-
|
|
10017
|
+
const getDevices = (constraints) => {
|
|
10018
|
+
return new Observable((subscriber) => {
|
|
10019
|
+
navigator.mediaDevices
|
|
10020
|
+
.getUserMedia(constraints)
|
|
10021
|
+
.then((media) => {
|
|
10022
|
+
// in Firefox, devices can be enumerated after userMedia is requested
|
|
10023
|
+
// and permissions granted. Otherwise, device labels are empty
|
|
10024
|
+
navigator.mediaDevices.enumerateDevices().then((devices) => {
|
|
10025
|
+
subscriber.next(devices);
|
|
10026
|
+
// If we stop the tracks before enumerateDevices -> the labels won't show up in Firefox
|
|
10027
|
+
disposeOfMediaStream(media);
|
|
10028
|
+
subscriber.complete();
|
|
10029
|
+
});
|
|
10030
|
+
})
|
|
10031
|
+
.catch((error) => {
|
|
10032
|
+
getLogger(['devices'])('error', 'Failed to get devices', error);
|
|
10033
|
+
subscriber.error(error);
|
|
10034
|
+
});
|
|
10035
|
+
});
|
|
10036
|
+
};
|
|
10037
|
+
/**
|
|
10038
|
+
* [Tells if the browser supports audio output change on 'audio' elements](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/setSinkId).
|
|
10039
|
+
*
|
|
10040
|
+
* @angular It's recommended to use the [`DeviceManagerService`](./DeviceManagerService.md) for a higher level API, use this low-level method only if the `DeviceManagerService` doesn't suit your requirements.
|
|
10041
|
+
*/
|
|
10042
|
+
const checkIfAudioOutputChangeSupported = () => {
|
|
10043
|
+
if (typeof document === 'undefined')
|
|
10044
|
+
return false;
|
|
10045
|
+
const element = document.createElement('audio');
|
|
10046
|
+
return element.sinkId !== undefined;
|
|
10047
|
+
};
|
|
10048
|
+
/**
|
|
10049
|
+
* The default constraints used to request audio devices.
|
|
10050
|
+
*/
|
|
10051
|
+
const audioDeviceConstraints = {
|
|
10052
|
+
audio: {
|
|
10053
|
+
autoGainControl: true,
|
|
10054
|
+
noiseSuppression: true,
|
|
10055
|
+
echoCancellation: true,
|
|
10056
|
+
},
|
|
10057
|
+
};
|
|
10058
|
+
/**
|
|
10059
|
+
* The default constraints used to request video devices.
|
|
10060
|
+
*/
|
|
10061
|
+
const videoDeviceConstraints = {
|
|
10062
|
+
video: {
|
|
10063
|
+
width: 1280,
|
|
10064
|
+
height: 720,
|
|
10065
|
+
},
|
|
10066
|
+
};
|
|
10067
|
+
// Audio and video devices are requested in two separate requests: that way users will be presented with two separate prompts -> they can give access to just camera, or just microphone
|
|
10068
|
+
const deviceChange$ = new Observable((subscriber) => {
|
|
10069
|
+
var _a, _b;
|
|
10070
|
+
const deviceChangeHandler = () => subscriber.next();
|
|
10071
|
+
(_b = (_a = navigator.mediaDevices).addEventListener) === null || _b === void 0 ? void 0 : _b.call(_a, 'devicechange', deviceChangeHandler);
|
|
10072
|
+
return () => {
|
|
10073
|
+
var _a, _b;
|
|
10074
|
+
return (_b = (_a = navigator.mediaDevices).removeEventListener) === null || _b === void 0 ? void 0 : _b.call(_a, 'devicechange', deviceChangeHandler);
|
|
10075
|
+
};
|
|
10076
|
+
}).pipe(debounceTime(500), concatMap(() => from(navigator.mediaDevices.enumerateDevices())), shareReplay(1));
|
|
10077
|
+
const audioDevices$ = merge(getDevices(audioDeviceConstraints), deviceChange$).pipe(shareReplay(1));
|
|
10078
|
+
const videoDevices$ = merge(getDevices(videoDeviceConstraints), deviceChange$).pipe(shareReplay(1));
|
|
10079
|
+
/**
|
|
10080
|
+
* Prompts the user for a permission to use audio devices (if not already granted) and lists the available 'audioinput' devices, if devices are added/removed the list is updated.
|
|
10081
|
+
*
|
|
10082
|
+
* @angular It's recommended to use the [`DeviceManagerService`](./DeviceManagerService.md) for a higher level API, use this low-level method only if the `DeviceManagerService` doesn't suit your requirements.
|
|
10083
|
+
* @returns
|
|
10084
|
+
*/
|
|
10085
|
+
const getAudioDevices = () => audioDevices$.pipe(map$2((values) => values.filter((d) => d.kind === 'audioinput')));
|
|
10086
|
+
/**
|
|
10087
|
+
* Prompts the user for a permission to use video devices (if not already granted) and lists the available 'videoinput' devices, if devices are added/removed the list is updated.
|
|
10088
|
+
*
|
|
10089
|
+
* @angular It's recommended to use the [`DeviceManagerService`](./DeviceManagerService.md) for a higher level API, use this low-level method only if the `DeviceManagerService` doesn't suit your requirements.
|
|
10090
|
+
* @returns
|
|
10091
|
+
*/
|
|
10092
|
+
const getVideoDevices = () => videoDevices$.pipe(map$2((values) => values.filter((d) => d.kind === 'videoinput' && d.deviceId.length)));
|
|
10093
|
+
/**
|
|
10094
|
+
* Prompts the user for a permission to use audio devices (if not already granted) and lists the available 'audiooutput' devices, if devices are added/removed the list is updated. Selecting 'audiooutput' device only makes sense if [the browser has support for changing audio output on 'audio' elements](#checkifaudiooutputchangesupported)
|
|
10095
|
+
*
|
|
10096
|
+
* @angular It's recommended to use the [`DeviceManagerService`](./DeviceManagerService.md) for a higher level API, use this low-level method only if the `DeviceManagerService` doesn't suit your requirements.
|
|
10097
|
+
* @returns
|
|
10098
|
+
*/
|
|
10099
|
+
const getAudioOutputDevices = () => {
|
|
10100
|
+
return audioDevices$.pipe(map$2((values) => values.filter((d) => d.kind === 'audiooutput')));
|
|
10101
|
+
};
|
|
10102
|
+
const getStream = (constraints) => __awaiter(void 0, void 0, void 0, function* () {
|
|
10103
|
+
try {
|
|
10104
|
+
return yield navigator.mediaDevices.getUserMedia(constraints);
|
|
10133
10105
|
}
|
|
10134
|
-
|
|
10135
|
-
|
|
10136
|
-
|
|
10137
|
-
|
|
10138
|
-
|
|
10139
|
-
|
|
10106
|
+
catch (e) {
|
|
10107
|
+
getLogger(['devices'])('error', `Failed get user media`, {
|
|
10108
|
+
error: e,
|
|
10109
|
+
constraints: constraints,
|
|
10110
|
+
});
|
|
10111
|
+
throw e;
|
|
10140
10112
|
}
|
|
10141
|
-
}
|
|
10142
|
-
|
|
10143
|
-
|
|
10144
|
-
|
|
10145
|
-
|
|
10146
|
-
|
|
10147
|
-
|
|
10148
|
-
|
|
10149
|
-
|
|
10113
|
+
});
|
|
10114
|
+
/**
|
|
10115
|
+
* Returns an audio media stream that fulfills the given constraints.
|
|
10116
|
+
* If no constraints are provided, it uses the browser's default ones.
|
|
10117
|
+
*
|
|
10118
|
+
* @angular It's recommended to use the [`DeviceManagerService`](./DeviceManagerService.md) for a higher level API, use this low-level method only if the `DeviceManagerService` doesn't suit your requirements.
|
|
10119
|
+
* @param trackConstraints the constraints to use when requesting the stream.
|
|
10120
|
+
* @returns the new `MediaStream` fulfilling the given constraints.
|
|
10121
|
+
*/
|
|
10122
|
+
const getAudioStream = (trackConstraints) => __awaiter(void 0, void 0, void 0, function* () {
|
|
10123
|
+
const constraints = {
|
|
10124
|
+
audio: Object.assign(Object.assign({}, audioDeviceConstraints.audio), trackConstraints),
|
|
10125
|
+
};
|
|
10126
|
+
return getStream(constraints);
|
|
10127
|
+
});
|
|
10128
|
+
/**
|
|
10129
|
+
* Returns a video media stream that fulfills the given constraints.
|
|
10130
|
+
* If no constraints are provided, it uses the browser's default ones.
|
|
10131
|
+
*
|
|
10132
|
+
* @angular It's recommended to use the [`DeviceManagerService`](./DeviceManagerService.md) for a higher level API, use this low-level method only if the `DeviceManagerService` doesn't suit your requirements.
|
|
10133
|
+
* @param trackConstraints the constraints to use when requesting the stream.
|
|
10134
|
+
* @returns a new `MediaStream` fulfilling the given constraints.
|
|
10135
|
+
*/
|
|
10136
|
+
const getVideoStream = (trackConstraints) => __awaiter(void 0, void 0, void 0, function* () {
|
|
10137
|
+
const constraints = {
|
|
10138
|
+
video: Object.assign(Object.assign({}, videoDeviceConstraints.video), trackConstraints),
|
|
10139
|
+
};
|
|
10140
|
+
return getStream(constraints);
|
|
10141
|
+
});
|
|
10142
|
+
/**
|
|
10143
|
+
* Prompts the user for a permission to share a screen.
|
|
10144
|
+
* If the user grants the permission, a screen sharing stream is returned. Throws otherwise.
|
|
10145
|
+
*
|
|
10146
|
+
* The callers of this API are responsible to handle the possible errors.
|
|
10147
|
+
*
|
|
10148
|
+
* @angular It's recommended to use the [`DeviceManagerService`](./DeviceManagerService.md) for a higher level API, use this low-level method only if the `DeviceManagerService` doesn't suit your requirements.
|
|
10149
|
+
*
|
|
10150
|
+
* @param options any additional options to pass to the [`getDisplayMedia`](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getDisplayMedia) API.
|
|
10151
|
+
*/
|
|
10152
|
+
const getScreenShareStream = (options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
10153
|
+
try {
|
|
10154
|
+
return yield navigator.mediaDevices.getDisplayMedia(Object.assign({ video: true, audio: false }, options));
|
|
10150
10155
|
}
|
|
10151
|
-
|
|
10152
|
-
|
|
10153
|
-
|
|
10154
|
-
* back - means the camera facing the environment
|
|
10155
|
-
*/
|
|
10156
|
-
get direction() {
|
|
10157
|
-
return this.getCurrentValue(this.direction$);
|
|
10156
|
+
catch (e) {
|
|
10157
|
+
getLogger(['devices'])('error', 'Failed to get screen share stream', e);
|
|
10158
|
+
throw e;
|
|
10158
10159
|
}
|
|
10159
|
-
|
|
10160
|
-
|
|
10161
|
-
|
|
10162
|
-
|
|
10163
|
-
|
|
10160
|
+
});
|
|
10161
|
+
const watchForDisconnectedDevice = (kind, deviceId$) => {
|
|
10162
|
+
let devices$;
|
|
10163
|
+
switch (kind) {
|
|
10164
|
+
case 'audioinput':
|
|
10165
|
+
devices$ = getAudioDevices();
|
|
10166
|
+
break;
|
|
10167
|
+
case 'videoinput':
|
|
10168
|
+
devices$ = getVideoDevices();
|
|
10169
|
+
break;
|
|
10170
|
+
case 'audiooutput':
|
|
10171
|
+
devices$ = getAudioOutputDevices();
|
|
10172
|
+
break;
|
|
10164
10173
|
}
|
|
10165
|
-
|
|
10166
|
-
|
|
10167
|
-
|
|
10168
|
-
|
|
10169
|
-
|
|
10170
|
-
|
|
10171
|
-
|
|
10172
|
-
|
|
10173
|
-
|
|
10174
|
-
|
|
10175
|
-
|
|
10176
|
-
|
|
10177
|
-
|
|
10178
|
-
|
|
10179
|
-
|
|
10174
|
+
return combineLatest([devices$, deviceId$]).pipe(filter(([devices, deviceId]) => !!deviceId && !devices.find((d) => d.deviceId === deviceId)), map$2(() => true));
|
|
10175
|
+
};
|
|
10176
|
+
/**
|
|
10177
|
+
* Notifies the subscriber if a given 'audioinput' device is disconnected
|
|
10178
|
+
*
|
|
10179
|
+
* @angular It's recommended to use the [`DeviceManagerService`](./DeviceManagerService.md) for a higher level API, use this low-level method only if the `DeviceManagerService` doesn't suit your requirements.
|
|
10180
|
+
* @param deviceId$ an Observable that specifies which device to watch for
|
|
10181
|
+
* @returns
|
|
10182
|
+
*/
|
|
10183
|
+
const watchForDisconnectedAudioDevice = (deviceId$) => {
|
|
10184
|
+
return watchForDisconnectedDevice('audioinput', deviceId$);
|
|
10185
|
+
};
|
|
10186
|
+
/**
|
|
10187
|
+
* Notifies the subscriber if a given 'videoinput' device is disconnected
|
|
10188
|
+
*
|
|
10189
|
+
* @angular It's recommended to use the [`DeviceManagerService`](./DeviceManagerService.md) for a higher level API, use this low-level method only if the `DeviceManagerService` doesn't suit your requirements.
|
|
10190
|
+
* @param deviceId$ an Observable that specifies which device to watch for
|
|
10191
|
+
* @returns
|
|
10192
|
+
*/
|
|
10193
|
+
const watchForDisconnectedVideoDevice = (deviceId$) => {
|
|
10194
|
+
return watchForDisconnectedDevice('videoinput', deviceId$);
|
|
10195
|
+
};
|
|
10196
|
+
/**
|
|
10197
|
+
* Notifies the subscriber if a given 'audiooutput' device is disconnected
|
|
10198
|
+
*
|
|
10199
|
+
* @angular It's recommended to use the [`DeviceManagerService`](./DeviceManagerService.md) for a higher level API, use this low-level method only if the `DeviceManagerService` doesn't suit your requirements.
|
|
10200
|
+
* @param deviceId$ an Observable that specifies which device to watch for
|
|
10201
|
+
* @returns
|
|
10202
|
+
*/
|
|
10203
|
+
const watchForDisconnectedAudioOutputDevice = (deviceId$) => {
|
|
10204
|
+
return watchForDisconnectedDevice('audiooutput', deviceId$);
|
|
10205
|
+
};
|
|
10206
|
+
const watchForAddedDefaultDevice = (kind) => {
|
|
10207
|
+
let devices$;
|
|
10208
|
+
switch (kind) {
|
|
10209
|
+
case 'audioinput':
|
|
10210
|
+
devices$ = getAudioDevices();
|
|
10211
|
+
break;
|
|
10212
|
+
case 'videoinput':
|
|
10213
|
+
devices$ = getVideoDevices();
|
|
10214
|
+
break;
|
|
10215
|
+
case 'audiooutput':
|
|
10216
|
+
devices$ = getAudioOutputDevices();
|
|
10217
|
+
break;
|
|
10218
|
+
default:
|
|
10219
|
+
throw new Error('Unknown MediaDeviceKind', kind);
|
|
10180
10220
|
}
|
|
10181
|
-
|
|
10182
|
-
|
|
10183
|
-
|
|
10221
|
+
return devices$.pipe(pairwise(), filter(([prev, current]) => {
|
|
10222
|
+
const prevDefault = prev.find((device) => device.deviceId === 'default');
|
|
10223
|
+
const currentDefault = current.find((device) => device.deviceId === 'default');
|
|
10224
|
+
return !!(current.length > prev.length &&
|
|
10225
|
+
prevDefault &&
|
|
10226
|
+
currentDefault &&
|
|
10227
|
+
prevDefault.groupId !== currentDefault.groupId);
|
|
10228
|
+
}), map$2(() => true));
|
|
10229
|
+
};
|
|
10230
|
+
/**
|
|
10231
|
+
* Notifies the subscriber about newly added default audio input device.
|
|
10232
|
+
* @returns Observable<boolean>
|
|
10233
|
+
*/
|
|
10234
|
+
const watchForAddedDefaultAudioDevice = () => watchForAddedDefaultDevice('audioinput');
|
|
10235
|
+
/**
|
|
10236
|
+
* Notifies the subscriber about newly added default audio output device.
|
|
10237
|
+
* @returns Observable<boolean>
|
|
10238
|
+
*/
|
|
10239
|
+
const watchForAddedDefaultAudioOutputDevice = () => watchForAddedDefaultDevice('audiooutput');
|
|
10240
|
+
/**
|
|
10241
|
+
* Notifies the subscriber about newly added default video input device.
|
|
10242
|
+
* @returns Observable<boolean>
|
|
10243
|
+
*/
|
|
10244
|
+
const watchForAddedDefaultVideoDevice = () => watchForAddedDefaultDevice('videoinput');
|
|
10245
|
+
/**
|
|
10246
|
+
* Deactivates MediaStream (stops and removes tracks) to be later garbage collected
|
|
10247
|
+
*
|
|
10248
|
+
* @param stream MediaStream
|
|
10249
|
+
* @returns void
|
|
10250
|
+
*/
|
|
10251
|
+
const disposeOfMediaStream = (stream) => {
|
|
10252
|
+
if (!stream.active)
|
|
10253
|
+
return;
|
|
10254
|
+
stream.getTracks().forEach((track) => {
|
|
10255
|
+
track.stop();
|
|
10256
|
+
stream.removeTrack(track);
|
|
10257
|
+
});
|
|
10258
|
+
// @ts-expect-error release() is present in react-native-webrtc and must be called to dispose the stream
|
|
10259
|
+
if (typeof stream.release === 'function') {
|
|
10260
|
+
// @ts-expect-error
|
|
10261
|
+
stream.release();
|
|
10184
10262
|
}
|
|
10185
|
-
}
|
|
10263
|
+
};
|
|
10186
10264
|
|
|
10187
10265
|
class CameraManager extends InputMediaDeviceManager {
|
|
10188
10266
|
constructor(call) {
|
|
10189
|
-
super(call, new CameraManagerState());
|
|
10267
|
+
super(call, new CameraManagerState(), TrackType.VIDEO);
|
|
10190
10268
|
this.targetResolution = {
|
|
10191
10269
|
width: 1280,
|
|
10192
10270
|
height: 720,
|
|
@@ -10238,6 +10316,7 @@ class CameraManager extends InputMediaDeviceManager {
|
|
|
10238
10316
|
if (width !== this.targetResolution.width ||
|
|
10239
10317
|
height !== this.targetResolution.height)
|
|
10240
10318
|
yield this.applySettingsToStream();
|
|
10319
|
+
this.logger('debug', `${width}x${height} target resolution applied to media stream`);
|
|
10241
10320
|
}
|
|
10242
10321
|
});
|
|
10243
10322
|
}
|
|
@@ -10261,13 +10340,9 @@ class CameraManager extends InputMediaDeviceManager {
|
|
|
10261
10340
|
stopPublishStream(stopTracks) {
|
|
10262
10341
|
return this.call.stopPublish(TrackType.VIDEO, stopTracks);
|
|
10263
10342
|
}
|
|
10264
|
-
|
|
10265
|
-
var _a;
|
|
10266
|
-
(_a = this.state.mediaStream) === null || _a === void 0 ? void 0 : _a.getVideoTracks().forEach((t) => (t.enabled = false));
|
|
10267
|
-
}
|
|
10268
|
-
unmuteTracks() {
|
|
10343
|
+
getTrack() {
|
|
10269
10344
|
var _a;
|
|
10270
|
-
(_a = this.state.mediaStream) === null || _a === void 0 ? void 0 : _a.getVideoTracks()
|
|
10345
|
+
return (_a = this.state.mediaStream) === null || _a === void 0 ? void 0 : _a.getVideoTracks()[0];
|
|
10271
10346
|
}
|
|
10272
10347
|
}
|
|
10273
10348
|
|
|
@@ -10283,7 +10358,7 @@ class MicrophoneManagerState extends InputMediaDeviceManagerState {
|
|
|
10283
10358
|
|
|
10284
10359
|
class MicrophoneManager extends InputMediaDeviceManager {
|
|
10285
10360
|
constructor(call) {
|
|
10286
|
-
super(call, new MicrophoneManagerState());
|
|
10361
|
+
super(call, new MicrophoneManagerState(), TrackType.AUDIO);
|
|
10287
10362
|
}
|
|
10288
10363
|
getDevices() {
|
|
10289
10364
|
return getAudioDevices();
|
|
@@ -10297,13 +10372,119 @@ class MicrophoneManager extends InputMediaDeviceManager {
|
|
|
10297
10372
|
stopPublishStream(stopTracks) {
|
|
10298
10373
|
return this.call.stopPublish(TrackType.AUDIO, stopTracks);
|
|
10299
10374
|
}
|
|
10300
|
-
|
|
10375
|
+
getTrack() {
|
|
10301
10376
|
var _a;
|
|
10302
|
-
(_a = this.state.mediaStream) === null || _a === void 0 ? void 0 : _a.getAudioTracks()
|
|
10377
|
+
return (_a = this.state.mediaStream) === null || _a === void 0 ? void 0 : _a.getAudioTracks()[0];
|
|
10303
10378
|
}
|
|
10304
|
-
|
|
10305
|
-
|
|
10306
|
-
|
|
10379
|
+
}
|
|
10380
|
+
|
|
10381
|
+
class SpeakerState {
|
|
10382
|
+
constructor() {
|
|
10383
|
+
this.selectedDeviceSubject = new BehaviorSubject('');
|
|
10384
|
+
this.volumeSubject = new BehaviorSubject(1);
|
|
10385
|
+
/**
|
|
10386
|
+
* [Tells if the browser supports audio output change on 'audio' elements](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/setSinkId).
|
|
10387
|
+
*/
|
|
10388
|
+
this.isDeviceSelectionSupported = checkIfAudioOutputChangeSupported();
|
|
10389
|
+
/**
|
|
10390
|
+
* Gets the current value of an observable, or undefined if the observable has
|
|
10391
|
+
* not emitted a value yet.
|
|
10392
|
+
*
|
|
10393
|
+
* @param observable$ the observable to get the value from.
|
|
10394
|
+
*/
|
|
10395
|
+
this.getCurrentValue = getCurrentValue;
|
|
10396
|
+
/**
|
|
10397
|
+
* Updates the value of the provided Subject.
|
|
10398
|
+
* An `update` can either be a new value or a function which takes
|
|
10399
|
+
* the current value and returns a new value.
|
|
10400
|
+
*
|
|
10401
|
+
* @internal
|
|
10402
|
+
*
|
|
10403
|
+
* @param subject the subject to update.
|
|
10404
|
+
* @param update the update to apply to the subject.
|
|
10405
|
+
* @return the updated value.
|
|
10406
|
+
*/
|
|
10407
|
+
this.setCurrentValue = setCurrentValue;
|
|
10408
|
+
this.selectedDevice$ = this.selectedDeviceSubject
|
|
10409
|
+
.asObservable()
|
|
10410
|
+
.pipe(distinctUntilChanged$1());
|
|
10411
|
+
this.volume$ = this.volumeSubject
|
|
10412
|
+
.asObservable()
|
|
10413
|
+
.pipe(distinctUntilChanged$1());
|
|
10414
|
+
}
|
|
10415
|
+
/**
|
|
10416
|
+
* The currently selected device
|
|
10417
|
+
*
|
|
10418
|
+
* Note: this feature is not supported in React Native
|
|
10419
|
+
*/
|
|
10420
|
+
get selectedDevice() {
|
|
10421
|
+
return this.getCurrentValue(this.selectedDevice$);
|
|
10422
|
+
}
|
|
10423
|
+
/**
|
|
10424
|
+
* The currently selected volume
|
|
10425
|
+
*
|
|
10426
|
+
* Note: this feature is not supported in React Native
|
|
10427
|
+
*/
|
|
10428
|
+
get volume() {
|
|
10429
|
+
return this.getCurrentValue(this.volume$);
|
|
10430
|
+
}
|
|
10431
|
+
/**
|
|
10432
|
+
* @internal
|
|
10433
|
+
* @param deviceId
|
|
10434
|
+
*/
|
|
10435
|
+
setDevice(deviceId) {
|
|
10436
|
+
this.setCurrentValue(this.selectedDeviceSubject, deviceId);
|
|
10437
|
+
}
|
|
10438
|
+
/**
|
|
10439
|
+
* @internal
|
|
10440
|
+
* @param volume
|
|
10441
|
+
*/
|
|
10442
|
+
setVolume(volume) {
|
|
10443
|
+
this.setCurrentValue(this.volumeSubject, volume);
|
|
10444
|
+
}
|
|
10445
|
+
}
|
|
10446
|
+
|
|
10447
|
+
class SpeakerManager {
|
|
10448
|
+
constructor() {
|
|
10449
|
+
this.state = new SpeakerState();
|
|
10450
|
+
}
|
|
10451
|
+
/**
|
|
10452
|
+
* Lists the available audio output devices
|
|
10453
|
+
*
|
|
10454
|
+
* Note: It prompts the user for a permission to use devices (if not already granted)
|
|
10455
|
+
*
|
|
10456
|
+
* @returns an Observable that will be updated if a device is connected or disconnected
|
|
10457
|
+
*/
|
|
10458
|
+
listDevices() {
|
|
10459
|
+
return getAudioOutputDevices();
|
|
10460
|
+
}
|
|
10461
|
+
/**
|
|
10462
|
+
* Select device
|
|
10463
|
+
*
|
|
10464
|
+
* Note: this method is not supported in React Native
|
|
10465
|
+
*
|
|
10466
|
+
* @param deviceId empty string means the system default
|
|
10467
|
+
*/
|
|
10468
|
+
select(deviceId) {
|
|
10469
|
+
if (isReactNative()) {
|
|
10470
|
+
throw new Error('This feature is not supported in React Native');
|
|
10471
|
+
}
|
|
10472
|
+
this.state.setDevice(deviceId);
|
|
10473
|
+
}
|
|
10474
|
+
/**
|
|
10475
|
+
* Set the volume of the audio elements
|
|
10476
|
+
* @param volume a number between 0 and 1
|
|
10477
|
+
*
|
|
10478
|
+
* Note: this method is not supported in React Native
|
|
10479
|
+
*/
|
|
10480
|
+
setVolume(volume) {
|
|
10481
|
+
if (isReactNative()) {
|
|
10482
|
+
throw new Error('This feature is not supported in React Native');
|
|
10483
|
+
}
|
|
10484
|
+
if (volume && (volume < 0 || volume > 1)) {
|
|
10485
|
+
throw new Error('Volume must be between 0 and 1');
|
|
10486
|
+
}
|
|
10487
|
+
this.state.setVolume(volume);
|
|
10307
10488
|
}
|
|
10308
10489
|
}
|
|
10309
10490
|
|
|
@@ -10879,7 +11060,7 @@ class Call {
|
|
|
10879
11060
|
*/
|
|
10880
11061
|
this.stopPublish = (trackType, stopTrack = true) => __awaiter(this, void 0, void 0, function* () {
|
|
10881
11062
|
var _j;
|
|
10882
|
-
this.logger('info', `stopPublish ${TrackType[trackType]}`);
|
|
11063
|
+
this.logger('info', `stopPublish ${TrackType[trackType]}, stop tracks: ${stopTrack}`);
|
|
10883
11064
|
yield ((_j = this.publisher) === null || _j === void 0 ? void 0 : _j.unpublishStream(trackType, stopTrack));
|
|
10884
11065
|
});
|
|
10885
11066
|
/**
|
|
@@ -10981,6 +11162,8 @@ class Call {
|
|
|
10981
11162
|
*
|
|
10982
11163
|
*
|
|
10983
11164
|
* @param deviceId the selected device, `undefined` means the user wants to use the system's default audio output
|
|
11165
|
+
*
|
|
11166
|
+
* @deprecated use `call.speaker` instead
|
|
10984
11167
|
*/
|
|
10985
11168
|
this.setAudioOutputDevice = (deviceId) => {
|
|
10986
11169
|
if (!this.sfuClient)
|
|
@@ -11455,6 +11638,28 @@ class Call {
|
|
|
11455
11638
|
this.leaveCallHooks.add(createSubscription(this.trackSubscriptionsSubject.pipe(debounce((v) => timer(v.type)), map$2((v) => v.data)), (subscriptions) => { var _a; return (_a = this.sfuClient) === null || _a === void 0 ? void 0 : _a.updateSubscriptions(subscriptions); }));
|
|
11456
11639
|
this.camera = new CameraManager(this);
|
|
11457
11640
|
this.microphone = new MicrophoneManager(this);
|
|
11641
|
+
this.state.localParticipant$.subscribe((p) => __awaiter(this, void 0, void 0, function* () {
|
|
11642
|
+
var _l, _m;
|
|
11643
|
+
// Mute via device manager
|
|
11644
|
+
// If integrator doesn't use device manager, we mute using stopPublish
|
|
11645
|
+
if (!(p === null || p === void 0 ? void 0 : p.publishedTracks.includes(TrackType.VIDEO)) &&
|
|
11646
|
+
((_l = this.publisher) === null || _l === void 0 ? void 0 : _l.isPublishing(TrackType.VIDEO))) {
|
|
11647
|
+
this.logger('info', `Local participant's video track is muted remotely`);
|
|
11648
|
+
yield this.camera.disable();
|
|
11649
|
+
if (this.publisher.isPublishing(TrackType.VIDEO)) {
|
|
11650
|
+
this.stopPublish(TrackType.VIDEO);
|
|
11651
|
+
}
|
|
11652
|
+
}
|
|
11653
|
+
if (!(p === null || p === void 0 ? void 0 : p.publishedTracks.includes(TrackType.AUDIO)) &&
|
|
11654
|
+
((_m = this.publisher) === null || _m === void 0 ? void 0 : _m.isPublishing(TrackType.AUDIO))) {
|
|
11655
|
+
this.logger('info', `Local participant's audio track is muted remotely`);
|
|
11656
|
+
yield this.microphone.disable();
|
|
11657
|
+
if (this.publisher.isPublishing(TrackType.AUDIO)) {
|
|
11658
|
+
this.stopPublish(TrackType.AUDIO);
|
|
11659
|
+
}
|
|
11660
|
+
}
|
|
11661
|
+
}));
|
|
11662
|
+
this.speaker = new SpeakerManager();
|
|
11458
11663
|
}
|
|
11459
11664
|
registerEffects() {
|
|
11460
11665
|
this.leaveCallHooks.add(
|
|
@@ -11479,9 +11684,27 @@ class Call {
|
|
|
11479
11684
|
};
|
|
11480
11685
|
for (const [permission, trackType] of Object.entries(permissionToTrackType)) {
|
|
11481
11686
|
const hasPermission = this.permissionsContext.hasPermission(permission);
|
|
11482
|
-
if (!hasPermission &&
|
|
11483
|
-
this.
|
|
11687
|
+
if (!hasPermission &&
|
|
11688
|
+
(this.publisher.isPublishing(trackType) ||
|
|
11689
|
+
this.publisher.isLive(trackType))) {
|
|
11690
|
+
// Stop tracks, then notify device manager
|
|
11691
|
+
this.stopPublish(trackType)
|
|
11692
|
+
.catch((err) => {
|
|
11484
11693
|
this.logger('error', `Error stopping publish ${trackType}`, err);
|
|
11694
|
+
})
|
|
11695
|
+
.then(() => {
|
|
11696
|
+
if (trackType === TrackType.VIDEO &&
|
|
11697
|
+
this.camera.state.status === 'enabled') {
|
|
11698
|
+
this.camera
|
|
11699
|
+
.disable()
|
|
11700
|
+
.catch((err) => this.logger('error', `Error disabling camera after pemission revoked`, err));
|
|
11701
|
+
}
|
|
11702
|
+
if (trackType === TrackType.AUDIO &&
|
|
11703
|
+
this.microphone.state.status === 'enabled') {
|
|
11704
|
+
this.microphone
|
|
11705
|
+
.disable()
|
|
11706
|
+
.catch((err) => this.logger('error', `Error disabling microphone after pemission revoked`, err));
|
|
11707
|
+
}
|
|
11485
11708
|
});
|
|
11486
11709
|
}
|
|
11487
11710
|
}
|
|
@@ -12745,11 +12968,11 @@ class WSConnectionFallback {
|
|
|
12745
12968
|
}
|
|
12746
12969
|
}
|
|
12747
12970
|
|
|
12748
|
-
const version = '0.3.
|
|
12971
|
+
const version = '0.3.14';
|
|
12749
12972
|
|
|
12750
12973
|
const logger = getLogger(['location']);
|
|
12751
12974
|
const HINT_URL = `https://hint.stream-io-video.com/`;
|
|
12752
|
-
const getLocationHint = (hintUrl = HINT_URL, timeout =
|
|
12975
|
+
const getLocationHint = (hintUrl = HINT_URL, timeout = 2000) => __awaiter(void 0, void 0, void 0, function* () {
|
|
12753
12976
|
const abortController = new AbortController();
|
|
12754
12977
|
const timeoutId = setTimeout(() => abortController.abort(), timeout);
|
|
12755
12978
|
try {
|
|
@@ -12762,7 +12985,7 @@ const getLocationHint = (hintUrl = HINT_URL, timeout = 1500) => __awaiter(void 0
|
|
|
12762
12985
|
return awsPop.substring(0, 3); // AMS1-P2 -> AMS
|
|
12763
12986
|
}
|
|
12764
12987
|
catch (e) {
|
|
12765
|
-
logger('
|
|
12988
|
+
logger('warn', `Failed to get location hint from ${HINT_URL}`, e);
|
|
12766
12989
|
return 'ERR';
|
|
12767
12990
|
}
|
|
12768
12991
|
finally {
|