@stream-io/video-client 0.3.11 → 0.3.13

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/dist/index.cjs.js CHANGED
@@ -5859,204 +5859,6 @@ const createSignalClient = (options) => {
5859
5859
  return new SignalServerClient(transport);
5860
5860
  };
5861
5861
 
5862
- /**
5863
- * Checks whether we are using React Native
5864
- */
5865
- const isReactNative = () => {
5866
- var _a;
5867
- if (typeof navigator === 'undefined')
5868
- return false;
5869
- return ((_a = navigator.product) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === 'reactnative';
5870
- };
5871
-
5872
- const getRtpMap = (line) => {
5873
- // Example: a=rtpmap:110 opus/48000/2
5874
- const rtpRegex = /^a=rtpmap:(\d*) ([\w\-.]*)(?:\s*\/(\d*)(?:\s*\/(\S*))?)?/;
5875
- // The first captured group is the payload type number, the second captured group is the encoding name, the third captured group is the clock rate, and the fourth captured group is any additional parameters.
5876
- const rtpMatch = rtpRegex.exec(line);
5877
- if (rtpMatch) {
5878
- return {
5879
- original: rtpMatch[0],
5880
- payload: rtpMatch[1],
5881
- codec: rtpMatch[2],
5882
- };
5883
- }
5884
- };
5885
- const getFmtp = (line) => {
5886
- // Example: a=fmtp:111 minptime=10; useinbandfec=1
5887
- const fmtpRegex = /^a=fmtp:(\d*) (.*)/;
5888
- const fmtpMatch = fmtpRegex.exec(line);
5889
- // The first captured group is the payload type number, the second captured group is any additional parameters.
5890
- if (fmtpMatch) {
5891
- return {
5892
- original: fmtpMatch[0],
5893
- payload: fmtpMatch[1],
5894
- config: fmtpMatch[2],
5895
- };
5896
- }
5897
- };
5898
- /**
5899
- * gets the media section for the specified media type.
5900
- * The media section contains the media type, port, codec, and payload type.
5901
- * Example: m=video 9 UDP/TLS/RTP/SAVPF 100 101 96 97 35 36 102 125 127
5902
- */
5903
- const getMedia = (line, mediaType) => {
5904
- const regex = new RegExp(`(m=${mediaType} \\d+ [\\w/]+) ([\\d\\s]+)`);
5905
- const match = regex.exec(line);
5906
- if (match) {
5907
- return {
5908
- original: match[0],
5909
- mediaWithPorts: match[1],
5910
- codecOrder: match[2],
5911
- };
5912
- }
5913
- };
5914
- const getMediaSection = (sdp, mediaType) => {
5915
- let media;
5916
- const rtpMap = [];
5917
- const fmtp = [];
5918
- let isTheRequiredMediaSection = false;
5919
- sdp.split(/(\r\n|\r|\n)/).forEach((line) => {
5920
- const isValidLine = /^([a-z])=(.*)/.test(line);
5921
- if (!isValidLine)
5922
- return;
5923
- /*
5924
- NOTE: according to https://www.rfc-editor.org/rfc/rfc8866.pdf
5925
- Each media description starts with an "m=" line and continues to the next media description or the end of the whole session description, whichever comes first
5926
- */
5927
- const type = line[0];
5928
- if (type === 'm') {
5929
- const _media = getMedia(line, mediaType);
5930
- isTheRequiredMediaSection = !!_media;
5931
- if (_media) {
5932
- media = _media;
5933
- }
5934
- }
5935
- else if (isTheRequiredMediaSection && type === 'a') {
5936
- const rtpMapLine = getRtpMap(line);
5937
- const fmtpLine = getFmtp(line);
5938
- if (rtpMapLine) {
5939
- rtpMap.push(rtpMapLine);
5940
- }
5941
- else if (fmtpLine) {
5942
- fmtp.push(fmtpLine);
5943
- }
5944
- }
5945
- });
5946
- if (media) {
5947
- return {
5948
- media,
5949
- rtpMap,
5950
- fmtp,
5951
- };
5952
- }
5953
- };
5954
- /**
5955
- * Returns a string of codec IDs with the preferred codec ID in front of the other codec IDs.
5956
- * It is used to ensure that a preferred codec is used when decoding a media stream.
5957
- * Example: Suppose we want to prefer VP8 which has id 96
5958
- * 1. If codec order is 100 101 96 97 35 36 102 125 127
5959
- * 2. The function returns 96 100 101 97 35 36 102 125 127
5960
- */
5961
- const moveCodecToFront = (codecOrder, preferredCodecId) => {
5962
- const codecIds = codecOrder.split(' ');
5963
- const index = codecIds.indexOf(preferredCodecId);
5964
- if (index > -1) {
5965
- codecIds.splice(index, 1);
5966
- codecIds.unshift(preferredCodecId);
5967
- }
5968
- return codecIds.join(' ');
5969
- };
5970
- /**
5971
- * Returns a string of codec IDs with the given codec ID removed
5972
- * It is used to ensure that a codec is disabled when processing a media stream.
5973
- * Example: Suppose we want to prefer RED which has id 63
5974
- * 1. If codec order is 111 63 103 104 9 102 0 8 106 105 13 110 112 113 126
5975
- * 2. The function returns 111 103 104 9 102 0 8 106 105 13 110 112 113 126
5976
- */
5977
- const removeCodecFromOrder = (codecOrder, codecIdToRemove) => {
5978
- const codecIds = codecOrder.split(' ');
5979
- return codecIds.filter((codecID) => codecID !== codecIdToRemove).join(' ');
5980
- };
5981
- /**
5982
- * Returns an SDP with the preferred codec in front of the other codecs.
5983
- * Example: Suppose we want to prefer VP8
5984
- * 1. find video media specification m=video 9 UDP/TLS/RTP/SAVPF 100 101 96 97 35 36 102 125 127
5985
- * 2. look for specified codec (VP8) a=rtpmap:96 VP8/90000
5986
- * 3. extract 96 as an identifier of VP8
5987
- * 4. move 96 to the front
5988
- * 5. now media looks like this: m=video 9 UDP/TLS/RTP/SAVPF 96 100 101 97 35 36 102 125 127
5989
- */
5990
- const setPreferredCodec = (sdp, mediaType, preferredCodec) => {
5991
- const section = getMediaSection(sdp, mediaType);
5992
- if (!section)
5993
- return sdp;
5994
- const rtpMap = section.rtpMap.find((r) => r.codec.toLowerCase() === preferredCodec.toLowerCase());
5995
- const codecId = rtpMap === null || rtpMap === void 0 ? void 0 : rtpMap.payload;
5996
- if (!codecId)
5997
- return sdp;
5998
- const newCodecOrder = moveCodecToFront(section.media.codecOrder, codecId);
5999
- return sdp.replace(section.media.original, `${section.media.mediaWithPorts} ${newCodecOrder}`);
6000
- };
6001
- /**
6002
- * Returns an SDP with the specified codec removed.
6003
- * Example: Suppose we want to remove RED
6004
- * 1. find audio media specification m=video 9 UDP/TLS/RTP/SAVPF 100 101 96 97 35 36 102 125 127
6005
- * 2. look for specified codec (RED) a=rtpmap:127 red/90000
6006
- * 3. extract 127 as an identifier of RED
6007
- * 4. remove 127 from the codec order
6008
- * 5. remove a=rtpmap:127 red/90000
6009
- * 6. remove a=fmtp:127 ...
6010
- */
6011
- const removeCodec = (sdp, mediaType, codecToRemove) => {
6012
- const section = getMediaSection(sdp, mediaType);
6013
- const mediaSection = section === null || section === void 0 ? void 0 : section.media;
6014
- if (!mediaSection) {
6015
- return sdp;
6016
- }
6017
- const rtpMap = section === null || section === void 0 ? void 0 : section.rtpMap.find((r) => r.codec.toLowerCase() === codecToRemove.toLowerCase());
6018
- const codecId = rtpMap === null || rtpMap === void 0 ? void 0 : rtpMap.payload;
6019
- if (!codecId) {
6020
- return sdp;
6021
- }
6022
- const newCodecOrder = removeCodecFromOrder(mediaSection.codecOrder, codecId);
6023
- const fmtp = section === null || section === void 0 ? void 0 : section.fmtp.find((f) => f.payload === codecId);
6024
- return sdp
6025
- .replace(mediaSection.original, `${mediaSection.mediaWithPorts} ${newCodecOrder}`)
6026
- .replace(new RegExp(`${rtpMap.original}[\r\n]+`), '') // remove the corresponding rtpmap line
6027
- .replace((fmtp === null || fmtp === void 0 ? void 0 : fmtp.original) ? new RegExp(`${fmtp === null || fmtp === void 0 ? void 0 : fmtp.original}[\r\n]+`) : '', ''); // remove the corresponding fmtp line
6028
- };
6029
- /**
6030
- * Gets the fmtp line corresponding to opus
6031
- */
6032
- const getOpusFmtp = (sdp) => {
6033
- const section = getMediaSection(sdp, 'audio');
6034
- const rtpMap = section === null || section === void 0 ? void 0 : section.rtpMap.find((r) => r.codec.toLowerCase() === 'opus');
6035
- const codecId = rtpMap === null || rtpMap === void 0 ? void 0 : rtpMap.payload;
6036
- if (codecId) {
6037
- return section === null || section === void 0 ? void 0 : section.fmtp.find((f) => f.payload === codecId);
6038
- }
6039
- };
6040
- /**
6041
- * Returns an SDP with DTX enabled or disabled.
6042
- */
6043
- const toggleDtx = (sdp, enable) => {
6044
- const opusFmtp = getOpusFmtp(sdp);
6045
- if (opusFmtp) {
6046
- const matchDtx = /usedtx=(\d)/.exec(opusFmtp.config);
6047
- const requiredDtxConfig = `usedtx=${enable ? '1' : '0'}`;
6048
- if (matchDtx) {
6049
- const newFmtp = opusFmtp.original.replace(/usedtx=(\d)/, requiredDtxConfig);
6050
- return sdp.replace(opusFmtp.original, newFmtp);
6051
- }
6052
- else {
6053
- const newFmtp = `${opusFmtp.original};${requiredDtxConfig}`;
6054
- return sdp.replace(opusFmtp.original, newFmtp);
6055
- }
6056
- }
6057
- return sdp;
6058
- };
6059
-
6060
5862
  // log levels, sorted by verbosity
6061
5863
  const logLevels = Object.freeze({
6062
5864
  trace: 0,
@@ -6148,39 +5950,13 @@ const getPreferredCodecs = (kind, preferredCodec, codecToRemove) => {
6148
5950
  logger === null || logger === void 0 ? void 0 : logger('info', `Preffered codecs: `, result);
6149
5951
  return result;
6150
5952
  };
6151
- const getGenericSdp = (direction, isRedEnabled, preferredVideoCodec) => __awaiter(void 0, void 0, void 0, function* () {
5953
+ const getGenericSdp = (direction) => __awaiter(void 0, void 0, void 0, function* () {
6152
5954
  var _a;
6153
5955
  const tempPc = new RTCPeerConnection();
6154
5956
  tempPc.addTransceiver('video', { direction });
6155
- // if ('setCodecPreferences' in videoTransceiver) {
6156
- // const videoCodecPreferences = getPreferredCodecs(
6157
- // 'audio',
6158
- // preferredVideoCodec ?? 'vp8',
6159
- // );
6160
- // videoTransceiver.setCodecPreferences([...(videoCodecPreferences ?? [])]);
6161
- // }
6162
5957
  tempPc.addTransceiver('audio', { direction });
6163
- const preferredAudioCodec = isRedEnabled ? 'red' : 'opus';
6164
- const audioCodecToRemove = !isRedEnabled ? 'red' : undefined;
6165
- // if ('setCodecPreferences' in audioTransceiver) {
6166
- // const audioCodecPreferences = getPreferredCodecs(
6167
- // 'audio',
6168
- // preferredAudioCodec,
6169
- // // audioCodecToRemove,
6170
- // );
6171
- // audioTransceiver.setCodecPreferences([...(audioCodecPreferences || [])]);
6172
- // }
6173
5958
  const offer = yield tempPc.createOffer();
6174
5959
  let sdp = (_a = offer.sdp) !== null && _a !== void 0 ? _a : '';
6175
- if (isReactNative()) {
6176
- if (preferredVideoCodec) {
6177
- sdp = setPreferredCodec(sdp, 'video', preferredVideoCodec);
6178
- }
6179
- sdp = setPreferredCodec(sdp, 'audio', preferredAudioCodec);
6180
- if (audioCodecToRemove) {
6181
- sdp = removeCodec(sdp, 'audio', audioCodecToRemove);
6182
- }
6183
- }
6184
5960
  tempPc.getTransceivers().forEach((t) => {
6185
5961
  t.stop();
6186
5962
  });
@@ -6289,6 +6065,16 @@ function getIceCandidate(candidate) {
6289
6065
  }
6290
6066
  }
6291
6067
 
6068
+ /**
6069
+ * Checks whether we are using React Native
6070
+ */
6071
+ const isReactNative = () => {
6072
+ var _a;
6073
+ if (typeof navigator === 'undefined')
6074
+ return false;
6075
+ return ((_a = navigator.product) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === 'reactnative';
6076
+ };
6077
+
6292
6078
  let sdkInfo;
6293
6079
  let osInfo;
6294
6080
  let deviceInfo;
@@ -6485,6 +6271,119 @@ const muteTypeToTrackType = (muteType) => {
6485
6271
  }
6486
6272
  };
6487
6273
 
6274
+ const getRtpMap = (line) => {
6275
+ // Example: a=rtpmap:110 opus/48000/2
6276
+ const rtpRegex = /^a=rtpmap:(\d*) ([\w\-.]*)(?:\s*\/(\d*)(?:\s*\/(\S*))?)?/;
6277
+ // The first captured group is the payload type number, the second captured group is the encoding name, the third captured group is the clock rate, and the fourth captured group is any additional parameters.
6278
+ const rtpMatch = rtpRegex.exec(line);
6279
+ if (rtpMatch) {
6280
+ return {
6281
+ original: rtpMatch[0],
6282
+ payload: rtpMatch[1],
6283
+ codec: rtpMatch[2],
6284
+ };
6285
+ }
6286
+ };
6287
+ const getFmtp = (line) => {
6288
+ // Example: a=fmtp:111 minptime=10; useinbandfec=1
6289
+ const fmtpRegex = /^a=fmtp:(\d*) (.*)/;
6290
+ const fmtpMatch = fmtpRegex.exec(line);
6291
+ // The first captured group is the payload type number, the second captured group is any additional parameters.
6292
+ if (fmtpMatch) {
6293
+ return {
6294
+ original: fmtpMatch[0],
6295
+ payload: fmtpMatch[1],
6296
+ config: fmtpMatch[2],
6297
+ };
6298
+ }
6299
+ };
6300
+ /**
6301
+ * gets the media section for the specified media type.
6302
+ * The media section contains the media type, port, codec, and payload type.
6303
+ * Example: m=video 9 UDP/TLS/RTP/SAVPF 100 101 96 97 35 36 102 125 127
6304
+ */
6305
+ const getMedia = (line, mediaType) => {
6306
+ const regex = new RegExp(`(m=${mediaType} \\d+ [\\w/]+) ([\\d\\s]+)`);
6307
+ const match = regex.exec(line);
6308
+ if (match) {
6309
+ return {
6310
+ original: match[0],
6311
+ mediaWithPorts: match[1],
6312
+ codecOrder: match[2],
6313
+ };
6314
+ }
6315
+ };
6316
+ const getMediaSection = (sdp, mediaType) => {
6317
+ let media;
6318
+ const rtpMap = [];
6319
+ const fmtp = [];
6320
+ let isTheRequiredMediaSection = false;
6321
+ sdp.split(/(\r\n|\r|\n)/).forEach((line) => {
6322
+ const isValidLine = /^([a-z])=(.*)/.test(line);
6323
+ if (!isValidLine)
6324
+ return;
6325
+ /*
6326
+ NOTE: according to https://www.rfc-editor.org/rfc/rfc8866.pdf
6327
+ Each media description starts with an "m=" line and continues to the next media description or the end of the whole session description, whichever comes first
6328
+ */
6329
+ const type = line[0];
6330
+ if (type === 'm') {
6331
+ const _media = getMedia(line, mediaType);
6332
+ isTheRequiredMediaSection = !!_media;
6333
+ if (_media) {
6334
+ media = _media;
6335
+ }
6336
+ }
6337
+ else if (isTheRequiredMediaSection && type === 'a') {
6338
+ const rtpMapLine = getRtpMap(line);
6339
+ const fmtpLine = getFmtp(line);
6340
+ if (rtpMapLine) {
6341
+ rtpMap.push(rtpMapLine);
6342
+ }
6343
+ else if (fmtpLine) {
6344
+ fmtp.push(fmtpLine);
6345
+ }
6346
+ }
6347
+ });
6348
+ if (media) {
6349
+ return {
6350
+ media,
6351
+ rtpMap,
6352
+ fmtp,
6353
+ };
6354
+ }
6355
+ };
6356
+ /**
6357
+ * Gets the fmtp line corresponding to opus
6358
+ */
6359
+ const getOpusFmtp = (sdp) => {
6360
+ const section = getMediaSection(sdp, 'audio');
6361
+ const rtpMap = section === null || section === void 0 ? void 0 : section.rtpMap.find((r) => r.codec.toLowerCase() === 'opus');
6362
+ const codecId = rtpMap === null || rtpMap === void 0 ? void 0 : rtpMap.payload;
6363
+ if (codecId) {
6364
+ return section === null || section === void 0 ? void 0 : section.fmtp.find((f) => f.payload === codecId);
6365
+ }
6366
+ };
6367
+ /**
6368
+ * Returns an SDP with DTX enabled or disabled.
6369
+ */
6370
+ const toggleDtx = (sdp, enable) => {
6371
+ const opusFmtp = getOpusFmtp(sdp);
6372
+ if (opusFmtp) {
6373
+ const matchDtx = /usedtx=(\d)/.exec(opusFmtp.config);
6374
+ const requiredDtxConfig = `usedtx=${enable ? '1' : '0'}`;
6375
+ if (matchDtx) {
6376
+ const newFmtp = opusFmtp.original.replace(/usedtx=(\d)/, requiredDtxConfig);
6377
+ return sdp.replace(opusFmtp.original, newFmtp);
6378
+ }
6379
+ else {
6380
+ const newFmtp = `${opusFmtp.original};${requiredDtxConfig}`;
6381
+ return sdp.replace(opusFmtp.original, newFmtp);
6382
+ }
6383
+ }
6384
+ return sdp;
6385
+ };
6386
+
6488
6387
  const logger$3 = getLogger(['Publisher']);
6489
6388
  /**
6490
6389
  * The `Publisher` is responsible for publishing/unpublishing media streams to/from the SFU
@@ -9634,15 +9533,26 @@ class DynascaleManager {
9634
9533
  }
9635
9534
  });
9636
9535
  });
9637
- const sinkIdSubscription = this.call.state.localParticipant$.subscribe((p) => {
9638
- if (p && p.audioOutputDeviceId && 'setSinkId' in audioElement) {
9536
+ const sinkIdSubscription = rxjs.combineLatest([
9537
+ this.call.state.localParticipant$,
9538
+ this.call.speaker.state.selectedDevice$,
9539
+ ]).subscribe(([p, selectedDevice]) => {
9540
+ var _a;
9541
+ const deviceId = ((_a = getSdkInfo()) === null || _a === void 0 ? void 0 : _a.type) === SdkType.REACT
9542
+ ? p === null || p === void 0 ? void 0 : p.audioOutputDeviceId
9543
+ : selectedDevice;
9544
+ if ('setSinkId' in audioElement) {
9639
9545
  // @ts-expect-error setSinkId is not yet in the lib
9640
- audioElement.setSinkId(p.audioOutputDeviceId);
9546
+ audioElement.setSinkId(deviceId);
9641
9547
  }
9642
9548
  });
9549
+ const volumeSubscription = this.call.speaker.state.volume$.subscribe((volume) => {
9550
+ audioElement.volume = volume;
9551
+ });
9643
9552
  audioElement.autoplay = true;
9644
9553
  return () => {
9645
9554
  sinkIdSubscription.unsubscribe();
9555
+ volumeSubscription.unsubscribe();
9646
9556
  updateMediaStreamSubscription.unsubscribe();
9647
9557
  };
9648
9558
  };
@@ -9770,24 +9680,145 @@ class CallTypesRegistry {
9770
9680
  }, {});
9771
9681
  }
9772
9682
  }
9773
- /**
9774
- * The default call types registry.
9775
- * You can use this instance to dynamically register and unregister call types.
9776
- */
9777
- const CallTypes = new CallTypesRegistry([
9778
- new CallType('default', {
9779
- sortParticipantsBy: defaultSortPreset,
9780
- }),
9781
- new CallType('development', {
9782
- sortParticipantsBy: defaultSortPreset,
9783
- }),
9784
- new CallType('livestream', {
9785
- sortParticipantsBy: livestreamOrAudioRoomSortPreset,
9786
- }),
9787
- new CallType('audio_room', {
9788
- sortParticipantsBy: livestreamOrAudioRoomSortPreset,
9789
- }),
9790
- ]);
9683
+ /**
9684
+ * The default call types registry.
9685
+ * You can use this instance to dynamically register and unregister call types.
9686
+ */
9687
+ const CallTypes = new CallTypesRegistry([
9688
+ new CallType('default', {
9689
+ sortParticipantsBy: defaultSortPreset,
9690
+ }),
9691
+ new CallType('development', {
9692
+ sortParticipantsBy: defaultSortPreset,
9693
+ }),
9694
+ new CallType('livestream', {
9695
+ sortParticipantsBy: livestreamOrAudioRoomSortPreset,
9696
+ }),
9697
+ new CallType('audio_room', {
9698
+ sortParticipantsBy: livestreamOrAudioRoomSortPreset,
9699
+ }),
9700
+ ]);
9701
+
9702
+ class InputMediaDeviceManagerState {
9703
+ constructor(disableMode = 'stop-tracks') {
9704
+ this.disableMode = disableMode;
9705
+ this.statusSubject = new rxjs.BehaviorSubject(undefined);
9706
+ this.mediaStreamSubject = new rxjs.BehaviorSubject(undefined);
9707
+ this.selectedDeviceSubject = new rxjs.BehaviorSubject(undefined);
9708
+ /**
9709
+ * Gets the current value of an observable, or undefined if the observable has
9710
+ * not emitted a value yet.
9711
+ *
9712
+ * @param observable$ the observable to get the value from.
9713
+ */
9714
+ this.getCurrentValue = getCurrentValue;
9715
+ /**
9716
+ * Updates the value of the provided Subject.
9717
+ * An `update` can either be a new value or a function which takes
9718
+ * the current value and returns a new value.
9719
+ *
9720
+ * @internal
9721
+ *
9722
+ * @param subject the subject to update.
9723
+ * @param update the update to apply to the subject.
9724
+ * @return the updated value.
9725
+ */
9726
+ this.setCurrentValue = setCurrentValue;
9727
+ this.mediaStream$ = this.mediaStreamSubject.asObservable();
9728
+ this.selectedDevice$ = this.selectedDeviceSubject
9729
+ .asObservable()
9730
+ .pipe(rxjs.distinctUntilChanged());
9731
+ this.status$ = this.statusSubject
9732
+ .asObservable()
9733
+ .pipe(rxjs.distinctUntilChanged());
9734
+ }
9735
+ /**
9736
+ * The device status
9737
+ */
9738
+ get status() {
9739
+ return this.getCurrentValue(this.status$);
9740
+ }
9741
+ /**
9742
+ * The currently selected device
9743
+ */
9744
+ get selectedDevice() {
9745
+ return this.getCurrentValue(this.selectedDevice$);
9746
+ }
9747
+ /**
9748
+ * The current media stream, or `undefined` if the device is currently disabled.
9749
+ */
9750
+ get mediaStream() {
9751
+ return this.getCurrentValue(this.mediaStream$);
9752
+ }
9753
+ /**
9754
+ * @internal
9755
+ * @param status
9756
+ */
9757
+ setStatus(status) {
9758
+ this.setCurrentValue(this.statusSubject, status);
9759
+ }
9760
+ /**
9761
+ * @internal
9762
+ * @param stream
9763
+ */
9764
+ setMediaStream(stream) {
9765
+ this.setCurrentValue(this.mediaStreamSubject, stream);
9766
+ if (stream) {
9767
+ this.setDevice(this.getDeviceIdFromStream(stream));
9768
+ }
9769
+ }
9770
+ /**
9771
+ * @internal
9772
+ * @param stream
9773
+ */
9774
+ setDevice(deviceId) {
9775
+ this.setCurrentValue(this.selectedDeviceSubject, deviceId);
9776
+ }
9777
+ }
9778
+
9779
+ class CameraManagerState extends InputMediaDeviceManagerState {
9780
+ constructor() {
9781
+ super('stop-tracks');
9782
+ this.directionSubject = new rxjs.BehaviorSubject(undefined);
9783
+ this.direction$ = this.directionSubject
9784
+ .asObservable()
9785
+ .pipe(rxjs.distinctUntilChanged());
9786
+ }
9787
+ /**
9788
+ * The preferred camera direction
9789
+ * front - means the camera facing the user
9790
+ * back - means the camera facing the environment
9791
+ */
9792
+ get direction() {
9793
+ return this.getCurrentValue(this.direction$);
9794
+ }
9795
+ /**
9796
+ * @internal
9797
+ */
9798
+ setDirection(direction) {
9799
+ this.setCurrentValue(this.directionSubject, direction);
9800
+ }
9801
+ /**
9802
+ * @internal
9803
+ */
9804
+ setMediaStream(stream) {
9805
+ var _a;
9806
+ super.setMediaStream(stream);
9807
+ if (stream) {
9808
+ // RN getSettings() doesn't return facingMode, so we don't verify camera direction
9809
+ const direction = isReactNative()
9810
+ ? this.direction
9811
+ : ((_a = stream.getVideoTracks()[0]) === null || _a === void 0 ? void 0 : _a.getSettings().facingMode) === 'environment'
9812
+ ? 'back'
9813
+ : 'front';
9814
+ this.setDirection(direction);
9815
+ }
9816
+ }
9817
+ getDeviceIdFromStream(stream) {
9818
+ var _a;
9819
+ return (_a = stream.getVideoTracks()[0]) === null || _a === void 0 ? void 0 : _a.getSettings().deviceId;
9820
+ }
9821
+ }
9791
9822
 
9792
9823
  const getDevices = (constraints) => {
9793
9824
  return new rxjs.Observable((subscriber) => {
@@ -10184,127 +10215,6 @@ class InputMediaDeviceManager {
10184
10215
  }
10185
10216
  }
10186
10217
 
10187
- class InputMediaDeviceManagerState {
10188
- constructor(disableMode = 'stop-tracks') {
10189
- this.disableMode = disableMode;
10190
- this.statusSubject = new rxjs.BehaviorSubject(undefined);
10191
- this.mediaStreamSubject = new rxjs.BehaviorSubject(undefined);
10192
- this.selectedDeviceSubject = new rxjs.BehaviorSubject(undefined);
10193
- /**
10194
- * Gets the current value of an observable, or undefined if the observable has
10195
- * not emitted a value yet.
10196
- *
10197
- * @param observable$ the observable to get the value from.
10198
- */
10199
- this.getCurrentValue = getCurrentValue;
10200
- /**
10201
- * Updates the value of the provided Subject.
10202
- * An `update` can either be a new value or a function which takes
10203
- * the current value and returns a new value.
10204
- *
10205
- * @internal
10206
- *
10207
- * @param subject the subject to update.
10208
- * @param update the update to apply to the subject.
10209
- * @return the updated value.
10210
- */
10211
- this.setCurrentValue = setCurrentValue;
10212
- this.mediaStream$ = this.mediaStreamSubject.asObservable();
10213
- this.selectedDevice$ = this.selectedDeviceSubject
10214
- .asObservable()
10215
- .pipe(rxjs.distinctUntilChanged());
10216
- this.status$ = this.statusSubject
10217
- .asObservable()
10218
- .pipe(rxjs.distinctUntilChanged());
10219
- }
10220
- /**
10221
- * The device status
10222
- */
10223
- get status() {
10224
- return this.getCurrentValue(this.status$);
10225
- }
10226
- /**
10227
- * The currently selected device
10228
- */
10229
- get selectedDevice() {
10230
- return this.getCurrentValue(this.selectedDevice$);
10231
- }
10232
- /**
10233
- * The current media stream, or `undefined` if the device is currently disabled.
10234
- */
10235
- get mediaStream() {
10236
- return this.getCurrentValue(this.mediaStream$);
10237
- }
10238
- /**
10239
- * @internal
10240
- * @param status
10241
- */
10242
- setStatus(status) {
10243
- this.setCurrentValue(this.statusSubject, status);
10244
- }
10245
- /**
10246
- * @internal
10247
- * @param stream
10248
- */
10249
- setMediaStream(stream) {
10250
- this.setCurrentValue(this.mediaStreamSubject, stream);
10251
- if (stream) {
10252
- this.setDevice(this.getDeviceIdFromStream(stream));
10253
- }
10254
- }
10255
- /**
10256
- * @internal
10257
- * @param stream
10258
- */
10259
- setDevice(deviceId) {
10260
- this.setCurrentValue(this.selectedDeviceSubject, deviceId);
10261
- }
10262
- }
10263
-
10264
- class CameraManagerState extends InputMediaDeviceManagerState {
10265
- constructor() {
10266
- super('stop-tracks');
10267
- this.directionSubject = new rxjs.BehaviorSubject(undefined);
10268
- this.direction$ = this.directionSubject
10269
- .asObservable()
10270
- .pipe(rxjs.distinctUntilChanged());
10271
- }
10272
- /**
10273
- * The preferred camera direction
10274
- * front - means the camera facing the user
10275
- * back - means the camera facing the environment
10276
- */
10277
- get direction() {
10278
- return this.getCurrentValue(this.direction$);
10279
- }
10280
- /**
10281
- * @internal
10282
- */
10283
- setDirection(direction) {
10284
- this.setCurrentValue(this.directionSubject, direction);
10285
- }
10286
- /**
10287
- * @internal
10288
- */
10289
- setMediaStream(stream) {
10290
- var _a;
10291
- super.setMediaStream(stream);
10292
- if (stream) {
10293
- // RN getSettings() doesn't return facingMode, so we don't verify camera direction
10294
- const direction = isReactNative()
10295
- ? this.direction
10296
- : ((_a = stream.getVideoTracks()[0]) === null || _a === void 0 ? void 0 : _a.getSettings().facingMode) === 'environment'
10297
- ? 'back'
10298
- : 'front';
10299
- this.setDirection(direction);
10300
- }
10301
- }
10302
- getDeviceIdFromStream(stream) {
10303
- var _a;
10304
- return (_a = stream.getVideoTracks()[0]) === null || _a === void 0 ? void 0 : _a.getSettings().deviceId;
10305
- }
10306
- }
10307
-
10308
10218
  class CameraManager extends InputMediaDeviceManager {
10309
10219
  constructor(call) {
10310
10220
  super(call, new CameraManagerState());
@@ -10428,6 +10338,116 @@ class MicrophoneManager extends InputMediaDeviceManager {
10428
10338
  }
10429
10339
  }
10430
10340
 
10341
+ class SpeakerState {
10342
+ constructor() {
10343
+ this.selectedDeviceSubject = new rxjs.BehaviorSubject('');
10344
+ this.volumeSubject = new rxjs.BehaviorSubject(1);
10345
+ /**
10346
+ * [Tells if the browser supports audio output change on 'audio' elements](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/setSinkId).
10347
+ */
10348
+ this.isDeviceSelectionSupported = checkIfAudioOutputChangeSupported();
10349
+ /**
10350
+ * Gets the current value of an observable, or undefined if the observable has
10351
+ * not emitted a value yet.
10352
+ *
10353
+ * @param observable$ the observable to get the value from.
10354
+ */
10355
+ this.getCurrentValue = getCurrentValue;
10356
+ /**
10357
+ * Updates the value of the provided Subject.
10358
+ * An `update` can either be a new value or a function which takes
10359
+ * the current value and returns a new value.
10360
+ *
10361
+ * @internal
10362
+ *
10363
+ * @param subject the subject to update.
10364
+ * @param update the update to apply to the subject.
10365
+ * @return the updated value.
10366
+ */
10367
+ this.setCurrentValue = setCurrentValue;
10368
+ this.selectedDevice$ = this.selectedDeviceSubject
10369
+ .asObservable()
10370
+ .pipe(rxjs.distinctUntilChanged());
10371
+ this.volume$ = this.volumeSubject
10372
+ .asObservable()
10373
+ .pipe(rxjs.distinctUntilChanged());
10374
+ }
10375
+ /**
10376
+ * The currently selected device
10377
+ *
10378
+ * Note: this feature is not supported in React Native
10379
+ */
10380
+ get selectedDevice() {
10381
+ return this.getCurrentValue(this.selectedDevice$);
10382
+ }
10383
+ /**
10384
+ * The currently selected volume
10385
+ *
10386
+ * Note: this feature is not supported in React Native
10387
+ */
10388
+ get volume() {
10389
+ return this.getCurrentValue(this.volume$);
10390
+ }
10391
+ /**
10392
+ * @internal
10393
+ * @param deviceId
10394
+ */
10395
+ setDevice(deviceId) {
10396
+ this.setCurrentValue(this.selectedDeviceSubject, deviceId);
10397
+ }
10398
+ /**
10399
+ * @internal
10400
+ * @param volume
10401
+ */
10402
+ setVolume(volume) {
10403
+ this.setCurrentValue(this.volumeSubject, volume);
10404
+ }
10405
+ }
10406
+
10407
+ class SpeakerManager {
10408
+ constructor() {
10409
+ this.state = new SpeakerState();
10410
+ }
10411
+ /**
10412
+ * Lists the available audio output devices
10413
+ *
10414
+ * Note: It prompts the user for a permission to use devices (if not already granted)
10415
+ *
10416
+ * @returns an Observable that will be updated if a device is connected or disconnected
10417
+ */
10418
+ listDevices() {
10419
+ return getAudioOutputDevices();
10420
+ }
10421
+ /**
10422
+ * Select device
10423
+ *
10424
+ * Note: this method is not supported in React Native
10425
+ *
10426
+ * @param deviceId empty string means the system default
10427
+ */
10428
+ select(deviceId) {
10429
+ if (isReactNative()) {
10430
+ throw new Error('This feature is not supported in React Native');
10431
+ }
10432
+ this.state.setDevice(deviceId);
10433
+ }
10434
+ /**
10435
+ * Set the volume of the audio elements
10436
+ * @param volume a number between 0 and 1
10437
+ *
10438
+ * Note: this method is not supported in React Native
10439
+ */
10440
+ setVolume(volume) {
10441
+ if (isReactNative()) {
10442
+ throw new Error('This feature is not supported in React Native');
10443
+ }
10444
+ if (volume && (volume < 0 || volume > 1)) {
10445
+ throw new Error('Volume must be between 0 and 1');
10446
+ }
10447
+ this.state.setVolume(volume);
10448
+ }
10449
+ }
10450
+
10431
10451
  /**
10432
10452
  * An object representation of a `Call`.
10433
10453
  */
@@ -10811,7 +10831,7 @@ class Call {
10811
10831
  // prepare a generic SDP and send it to the SFU.
10812
10832
  // this is a throw-away SDP that the SFU will use to determine
10813
10833
  // the capabilities of the client (codec support, etc.)
10814
- .then(() => getGenericSdp('recvonly', isRedEnabled, this.streamClient.options.preferredVideoCodec))
10834
+ .then(() => getGenericSdp('recvonly'))
10815
10835
  .then((sdp) => {
10816
10836
  var _a;
10817
10837
  const subscriptions = getCurrentValue(this.trackSubscriptionsSubject);
@@ -11102,6 +11122,8 @@ class Call {
11102
11122
  *
11103
11123
  *
11104
11124
  * @param deviceId the selected device, `undefined` means the user wants to use the system's default audio output
11125
+ *
11126
+ * @deprecated use `call.speaker` instead
11105
11127
  */
11106
11128
  this.setAudioOutputDevice = (deviceId) => {
11107
11129
  if (!this.sfuClient)
@@ -11576,6 +11598,7 @@ class Call {
11576
11598
  this.leaveCallHooks.add(createSubscription(this.trackSubscriptionsSubject.pipe(rxjs.debounce((v) => rxjs.timer(v.type)), rxjs.map((v) => v.data)), (subscriptions) => { var _a; return (_a = this.sfuClient) === null || _a === void 0 ? void 0 : _a.updateSubscriptions(subscriptions); }));
11577
11599
  this.camera = new CameraManager(this);
11578
11600
  this.microphone = new MicrophoneManager(this);
11601
+ this.speaker = new SpeakerManager();
11579
11602
  }
11580
11603
  registerEffects() {
11581
11604
  this.leaveCallHooks.add(
@@ -12866,7 +12889,7 @@ class WSConnectionFallback {
12866
12889
  }
12867
12890
  }
12868
12891
 
12869
- const version = '0.3.11';
12892
+ const version = '0.3.13';
12870
12893
 
12871
12894
  const logger = getLogger(['location']);
12872
12895
  const HINT_URL = `https://hint.stream-io-video.com/`;