@stream-io/video-client 1.31.0 → 1.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/dist/index.browser.es.js +350 -83
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +351 -84
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +350 -83
- package/dist/index.es.js.map +1 -1
- package/dist/src/Call.d.ts +3 -2
- package/dist/src/StreamVideoClient.d.ts +2 -0
- package/dist/src/coordinator/connection/types.d.ts +4 -0
- package/dist/src/devices/AudioDeviceManager.d.ts +25 -0
- package/dist/src/devices/AudioDeviceManagerState.d.ts +24 -0
- package/dist/src/devices/CameraManager.d.ts +2 -2
- package/dist/src/devices/CameraManagerState.d.ts +3 -4
- package/dist/src/devices/{InputMediaDeviceManager.d.ts → DeviceManager.d.ts} +6 -6
- package/dist/src/devices/{InputMediaDeviceManagerState.d.ts → DeviceManagerState.d.ts} +4 -4
- package/dist/src/devices/MicrophoneManager.d.ts +5 -3
- package/dist/src/devices/MicrophoneManagerState.d.ts +6 -10
- package/dist/src/devices/ScreenShareManager.d.ts +4 -2
- package/dist/src/devices/ScreenShareState.d.ts +6 -2
- package/dist/src/devices/SpeakerState.d.ts +4 -4
- package/dist/src/devices/index.d.ts +2 -2
- package/dist/src/gen/coordinator/index.d.ts +169 -2
- package/dist/src/gen/video/sfu/event/events.d.ts +8 -0
- package/dist/src/gen/video/sfu/models/models.d.ts +43 -0
- package/dist/src/rtc/BasePeerConnection.d.ts +2 -12
- package/dist/src/rtc/Publisher.d.ts +9 -6
- package/dist/src/rtc/Subscriber.d.ts +2 -1
- package/dist/src/rtc/TransceiverCache.d.ts +10 -11
- package/dist/src/rtc/index.d.ts +1 -1
- package/dist/src/rtc/{videoLayers.d.ts → layers.d.ts} +7 -1
- package/dist/src/rtc/types.d.ts +31 -0
- package/package.json +3 -2
- package/src/Call.ts +19 -12
- package/src/StreamVideoClient.ts +42 -3
- package/src/__tests__/Call.publishing.test.ts +14 -3
- package/src/__tests__/StreamVideoClient.api.test.ts +1 -1
- package/src/coordinator/connection/types.ts +5 -0
- package/src/devices/AudioDeviceManager.ts +61 -0
- package/src/devices/AudioDeviceManagerState.ts +44 -0
- package/src/devices/CameraManager.ts +4 -4
- package/src/devices/CameraManagerState.ts +9 -8
- package/src/devices/{InputMediaDeviceManager.ts → DeviceManager.ts} +11 -8
- package/src/devices/{InputMediaDeviceManagerState.ts → DeviceManagerState.ts} +7 -4
- package/src/devices/MicrophoneManager.ts +26 -6
- package/src/devices/MicrophoneManagerState.ts +18 -19
- package/src/devices/ScreenShareManager.ts +23 -4
- package/src/devices/ScreenShareState.ts +11 -3
- package/src/devices/SpeakerState.ts +6 -14
- package/src/devices/__tests__/CameraManager.test.ts +1 -0
- package/src/devices/__tests__/{InputMediaDeviceManager.test.ts → DeviceManager.test.ts} +4 -4
- package/src/devices/__tests__/{InputMediaDeviceManagerFilters.test.ts → DeviceManagerFilters.test.ts} +4 -4
- package/src/devices/__tests__/{InputMediaDeviceManagerState.test.ts → DeviceManagerState.test.ts} +2 -2
- package/src/devices/__tests__/MicrophoneManager.test.ts +41 -1
- package/src/devices/__tests__/NoiseCancellationStub.ts +3 -1
- package/src/devices/__tests__/ScreenShareManager.test.ts +5 -1
- package/src/devices/index.ts +2 -2
- package/src/events/__tests__/internal.test.ts +25 -11
- package/src/gen/coordinator/index.ts +169 -2
- package/src/gen/video/sfu/event/events.ts +14 -0
- package/src/gen/video/sfu/models/models.ts +65 -0
- package/src/rtc/BasePeerConnection.ts +1 -16
- package/src/rtc/Publisher.ts +74 -31
- package/src/rtc/Subscriber.ts +2 -4
- package/src/rtc/TransceiverCache.ts +23 -27
- package/src/rtc/__tests__/Publisher.test.ts +61 -29
- package/src/rtc/__tests__/{videoLayers.test.ts → layers.test.ts} +76 -1
- package/src/rtc/index.ts +2 -1
- package/src/rtc/{videoLayers.ts → layers.ts} +28 -7
- package/src/rtc/types.ts +44 -0
- package/src/sorting/presets.ts +2 -2
- package/src/store/CallState.ts +36 -10
- package/src/store/__tests__/CallState.test.ts +20 -2
package/dist/index.cjs.js
CHANGED
|
@@ -115,6 +115,7 @@ const OwnCapability = {
|
|
|
115
115
|
REMOVE_CALL_MEMBER: 'remove-call-member',
|
|
116
116
|
SCREENSHARE: 'screenshare',
|
|
117
117
|
SEND_AUDIO: 'send-audio',
|
|
118
|
+
SEND_CLOSED_CAPTIONS_CALL: 'send-closed-captions-call',
|
|
118
119
|
SEND_VIDEO: 'send-video',
|
|
119
120
|
START_BROADCAST_CALL: 'start-broadcast-call',
|
|
120
121
|
START_CLOSED_CAPTIONS_CALL: 'start-closed-captions-call',
|
|
@@ -960,6 +961,24 @@ var ParticipantSource;
|
|
|
960
961
|
*/
|
|
961
962
|
ParticipantSource[ParticipantSource["SRT"] = 5] = "SRT";
|
|
962
963
|
})(ParticipantSource || (ParticipantSource = {}));
|
|
964
|
+
/**
|
|
965
|
+
* @generated from protobuf enum stream.video.sfu.models.AudioBitrateProfile
|
|
966
|
+
*/
|
|
967
|
+
var AudioBitrateProfile;
|
|
968
|
+
(function (AudioBitrateProfile) {
|
|
969
|
+
/**
|
|
970
|
+
* @generated from protobuf enum value: AUDIO_BITRATE_PROFILE_VOICE_STANDARD_UNSPECIFIED = 0;
|
|
971
|
+
*/
|
|
972
|
+
AudioBitrateProfile[AudioBitrateProfile["VOICE_STANDARD_UNSPECIFIED"] = 0] = "VOICE_STANDARD_UNSPECIFIED";
|
|
973
|
+
/**
|
|
974
|
+
* @generated from protobuf enum value: AUDIO_BITRATE_PROFILE_VOICE_HIGH_QUALITY = 1;
|
|
975
|
+
*/
|
|
976
|
+
AudioBitrateProfile[AudioBitrateProfile["VOICE_HIGH_QUALITY"] = 1] = "VOICE_HIGH_QUALITY";
|
|
977
|
+
/**
|
|
978
|
+
* @generated from protobuf enum value: AUDIO_BITRATE_PROFILE_MUSIC_HIGH_QUALITY = 2;
|
|
979
|
+
*/
|
|
980
|
+
AudioBitrateProfile[AudioBitrateProfile["MUSIC_HIGH_QUALITY"] = 2] = "MUSIC_HIGH_QUALITY";
|
|
981
|
+
})(AudioBitrateProfile || (AudioBitrateProfile = {}));
|
|
963
982
|
/**
|
|
964
983
|
* @generated from protobuf enum stream.video.sfu.models.ErrorCode
|
|
965
984
|
*/
|
|
@@ -1573,6 +1592,13 @@ class PublishOption$Type extends runtime.MessageType {
|
|
|
1573
1592
|
kind: 'scalar',
|
|
1574
1593
|
T: 8 /*ScalarType.BOOL*/,
|
|
1575
1594
|
},
|
|
1595
|
+
{
|
|
1596
|
+
no: 10,
|
|
1597
|
+
name: 'audio_bitrate_profiles',
|
|
1598
|
+
kind: 'message',
|
|
1599
|
+
repeat: 2 /*RepeatType.UNPACKED*/,
|
|
1600
|
+
T: () => AudioBitrate,
|
|
1601
|
+
},
|
|
1576
1602
|
]);
|
|
1577
1603
|
}
|
|
1578
1604
|
}
|
|
@@ -1636,6 +1662,28 @@ let ICETrickle$Type$1 = class ICETrickle$Type extends runtime.MessageType {
|
|
|
1636
1662
|
*/
|
|
1637
1663
|
const ICETrickle$1 = new ICETrickle$Type$1();
|
|
1638
1664
|
// @generated message type with reflection information, may provide speed optimized methods
|
|
1665
|
+
class AudioBitrate$Type extends runtime.MessageType {
|
|
1666
|
+
constructor() {
|
|
1667
|
+
super('stream.video.sfu.models.AudioBitrate', [
|
|
1668
|
+
{
|
|
1669
|
+
no: 1,
|
|
1670
|
+
name: 'profile',
|
|
1671
|
+
kind: 'enum',
|
|
1672
|
+
T: () => [
|
|
1673
|
+
'stream.video.sfu.models.AudioBitrateProfile',
|
|
1674
|
+
AudioBitrateProfile,
|
|
1675
|
+
'AUDIO_BITRATE_PROFILE_',
|
|
1676
|
+
],
|
|
1677
|
+
},
|
|
1678
|
+
{ no: 2, name: 'bitrate', kind: 'scalar', T: 5 /*ScalarType.INT32*/ },
|
|
1679
|
+
]);
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
/**
|
|
1683
|
+
* @generated MessageType for protobuf message stream.video.sfu.models.AudioBitrate
|
|
1684
|
+
*/
|
|
1685
|
+
const AudioBitrate = new AudioBitrate$Type();
|
|
1686
|
+
// @generated message type with reflection information, may provide speed optimized methods
|
|
1639
1687
|
class TrackInfo$Type extends runtime.MessageType {
|
|
1640
1688
|
constructor() {
|
|
1641
1689
|
super('stream.video.sfu.models.TrackInfo', [
|
|
@@ -1986,6 +2034,8 @@ var models = /*#__PURE__*/Object.freeze({
|
|
|
1986
2034
|
get AndroidThermalState () { return AndroidThermalState; },
|
|
1987
2035
|
AppleState: AppleState,
|
|
1988
2036
|
get AppleThermalState () { return AppleThermalState; },
|
|
2037
|
+
AudioBitrate: AudioBitrate,
|
|
2038
|
+
get AudioBitrateProfile () { return AudioBitrateProfile; },
|
|
1989
2039
|
Browser: Browser,
|
|
1990
2040
|
Call: Call$1,
|
|
1991
2041
|
get CallEndedReason () { return CallEndedReason; },
|
|
@@ -2951,6 +3001,12 @@ class JoinRequest$Type extends runtime.MessageType {
|
|
|
2951
3001
|
super('stream.video.sfu.event.JoinRequest', [
|
|
2952
3002
|
{ no: 1, name: 'token', kind: 'scalar', T: 9 /*ScalarType.STRING*/ },
|
|
2953
3003
|
{ no: 2, name: 'session_id', kind: 'scalar', T: 9 /*ScalarType.STRING*/ },
|
|
3004
|
+
{
|
|
3005
|
+
no: 13,
|
|
3006
|
+
name: 'unified_session_id',
|
|
3007
|
+
kind: 'scalar',
|
|
3008
|
+
T: 9 /*ScalarType.STRING*/,
|
|
3009
|
+
},
|
|
2954
3010
|
{
|
|
2955
3011
|
no: 3,
|
|
2956
3012
|
name: 'subscriber_sdp',
|
|
@@ -4654,11 +4710,11 @@ const ifInvisibleOrUnknownBy = conditional((a, b) => a.viewportVisibilityState?.
|
|
|
4654
4710
|
/**
|
|
4655
4711
|
* The default sorting preset.
|
|
4656
4712
|
*/
|
|
4657
|
-
const defaultSortPreset = combineComparators(
|
|
4713
|
+
const defaultSortPreset = combineComparators(screenSharing, pinned, ifInvisibleBy(combineComparators(dominantSpeaker, speaking, reactionType('raised-hand'), publishingVideo, publishingAudio)));
|
|
4658
4714
|
/**
|
|
4659
4715
|
* The sorting preset for speaker layout.
|
|
4660
4716
|
*/
|
|
4661
|
-
const speakerLayoutSortPreset = combineComparators(
|
|
4717
|
+
const speakerLayoutSortPreset = combineComparators(screenSharing, pinned, dominantSpeaker, ifInvisibleBy(combineComparators(speaking, reactionType('raised-hand'), publishingVideo, publishingAudio)));
|
|
4662
4718
|
/**
|
|
4663
4719
|
* The sorting preset for layouts that don't render all participants but
|
|
4664
4720
|
* instead, render them in pages.
|
|
@@ -5005,14 +5061,32 @@ class CallState {
|
|
|
5005
5061
|
* @param pins the latest pins from the server.
|
|
5006
5062
|
*/
|
|
5007
5063
|
this.setServerSidePins = (pins) => {
|
|
5008
|
-
const
|
|
5009
|
-
|
|
5064
|
+
const now = Date.now();
|
|
5065
|
+
const unknownSymbol = Symbol('unknown');
|
|
5066
|
+
// generate a lookup table of pinnedAt timestamps by userId and sessionId
|
|
5067
|
+
// if there are multiple pins for the same userId, then we set the pinnedAt
|
|
5068
|
+
// to `unknown` (for that userId lookup) so that we don't apply any pin for that participant
|
|
5069
|
+
// this is to avoid conflicts during reconstruction of the pin state after reconnections
|
|
5070
|
+
// as sessionIds can change
|
|
5071
|
+
const pinnedAtByIdentifier = pins.reduce((lookup, pin, index) => {
|
|
5072
|
+
var _a;
|
|
5073
|
+
const pinnedAt = now + (pins.length - index);
|
|
5074
|
+
if (lookup[pin.userId]) {
|
|
5075
|
+
lookup[pin.userId] = unknownSymbol;
|
|
5076
|
+
}
|
|
5077
|
+
else {
|
|
5078
|
+
lookup[pin.userId] = pinnedAt;
|
|
5079
|
+
}
|
|
5080
|
+
lookup[_a = pin.sessionId] ?? (lookup[_a] = pinnedAt);
|
|
5010
5081
|
return lookup;
|
|
5011
5082
|
}, {});
|
|
5012
5083
|
return this.setParticipants((participants) => participants.map((participant) => {
|
|
5013
|
-
|
|
5084
|
+
// first check by sessionId as that is 100% correct, then by attempt reconstruction by userId
|
|
5085
|
+
const serverSidePinnedAt = pinnedAtByIdentifier[participant.sessionId] ??
|
|
5086
|
+
pinnedAtByIdentifier[participant.userId];
|
|
5014
5087
|
// the participant is newly pinned
|
|
5015
|
-
if (serverSidePinnedAt
|
|
5088
|
+
if (typeof serverSidePinnedAt === 'number' &&
|
|
5089
|
+
typeof participant.pin?.pinnedAt !== 'number') {
|
|
5016
5090
|
return {
|
|
5017
5091
|
...participant,
|
|
5018
5092
|
pin: {
|
|
@@ -5023,7 +5097,8 @@ class CallState {
|
|
|
5023
5097
|
}
|
|
5024
5098
|
// the participant is no longer pinned server side
|
|
5025
5099
|
// we need to reset the pin
|
|
5026
|
-
if (
|
|
5100
|
+
if (typeof serverSidePinnedAt !== 'number' &&
|
|
5101
|
+
participant.pin?.isLocalPin === false) {
|
|
5027
5102
|
return {
|
|
5028
5103
|
...participant,
|
|
5029
5104
|
pin: undefined,
|
|
@@ -5760,7 +5835,7 @@ const getSdkVersion = (sdk) => {
|
|
|
5760
5835
|
return sdk ? `${sdk.major}.${sdk.minor}.${sdk.patch}` : '0.0.0-development';
|
|
5761
5836
|
};
|
|
5762
5837
|
|
|
5763
|
-
const version = "1.
|
|
5838
|
+
const version = "1.33.0";
|
|
5764
5839
|
const [major, minor, patch] = version.split('.');
|
|
5765
5840
|
let sdkInfo = {
|
|
5766
5841
|
type: SdkType.PLAIN_JAVASCRIPT,
|
|
@@ -6958,15 +7033,24 @@ class TransceiverCache {
|
|
|
6958
7033
|
/**
|
|
6959
7034
|
* Adds a transceiver to the cache.
|
|
6960
7035
|
*/
|
|
6961
|
-
this.add = (
|
|
6962
|
-
this.cache.push(
|
|
6963
|
-
this.transceiverOrder.push(transceiver);
|
|
7036
|
+
this.add = (bundle) => {
|
|
7037
|
+
this.cache.push(bundle);
|
|
7038
|
+
this.transceiverOrder.push(bundle.transceiver);
|
|
6964
7039
|
};
|
|
6965
7040
|
/**
|
|
6966
7041
|
* Gets the transceiver for the given publish option.
|
|
6967
7042
|
*/
|
|
6968
7043
|
this.get = (publishOption) => {
|
|
6969
|
-
return this.
|
|
7044
|
+
return this.cache.find((bundle) => bundle.publishOption.id === publishOption.id &&
|
|
7045
|
+
bundle.publishOption.trackType === publishOption.trackType);
|
|
7046
|
+
};
|
|
7047
|
+
/**
|
|
7048
|
+
* Updates the cached bundle with the given patch.
|
|
7049
|
+
*/
|
|
7050
|
+
this.update = (publishOption, patch) => {
|
|
7051
|
+
const bundle = this.get(publishOption);
|
|
7052
|
+
if (bundle)
|
|
7053
|
+
Object.assign(bundle, patch);
|
|
6970
7054
|
};
|
|
6971
7055
|
/**
|
|
6972
7056
|
* Checks if the cache has the given publish option.
|
|
@@ -7012,10 +7096,6 @@ class TransceiverCache {
|
|
|
7012
7096
|
this.layers.push({ publishOption, layers });
|
|
7013
7097
|
}
|
|
7014
7098
|
};
|
|
7015
|
-
this.findTransceiver = (publishOption) => {
|
|
7016
|
-
return this.cache.find((item) => item.publishOption.id === publishOption.id &&
|
|
7017
|
-
item.publishOption.trackType === publishOption.trackType);
|
|
7018
|
-
};
|
|
7019
7099
|
this.findLayer = (publishOption) => {
|
|
7020
7100
|
return this.layers.find((item) => item.publishOption.id === publishOption.id &&
|
|
7021
7101
|
item.publishOption.trackType === publishOption.trackType);
|
|
@@ -7073,10 +7153,20 @@ const toTrackType = (trackType) => {
|
|
|
7073
7153
|
};
|
|
7074
7154
|
const isAudioTrackType = (trackType) => trackType === TrackType.AUDIO || trackType === TrackType.SCREEN_SHARE_AUDIO;
|
|
7075
7155
|
|
|
7076
|
-
|
|
7077
|
-
|
|
7078
|
-
|
|
7079
|
-
|
|
7156
|
+
/**
|
|
7157
|
+
* Prepares the audio layer for the given track.
|
|
7158
|
+
* Based on the provided audio bitrate profile, we apply the appropriate bitrate.
|
|
7159
|
+
*/
|
|
7160
|
+
const computeAudioLayers = (publishOption, options) => {
|
|
7161
|
+
const { audioBitrateProfile } = options;
|
|
7162
|
+
const profileConfig = publishOption.audioBitrateProfiles?.find((config) => config.profile === audioBitrateProfile);
|
|
7163
|
+
const maxBitrate = profileConfig?.bitrate ||
|
|
7164
|
+
{
|
|
7165
|
+
[AudioBitrateProfile.VOICE_STANDARD_UNSPECIFIED]: 64000,
|
|
7166
|
+
[AudioBitrateProfile.VOICE_HIGH_QUALITY]: 128000,
|
|
7167
|
+
[AudioBitrateProfile.MUSIC_HIGH_QUALITY]: 128000,
|
|
7168
|
+
}[audioBitrateProfile || AudioBitrateProfile.VOICE_STANDARD_UNSPECIFIED];
|
|
7169
|
+
return [{ maxBitrate }];
|
|
7080
7170
|
};
|
|
7081
7171
|
/**
|
|
7082
7172
|
* In SVC, we need to send only one video encoding (layer).
|
|
@@ -7089,7 +7179,7 @@ const toSvcEncodings = (layers) => {
|
|
|
7089
7179
|
if (!layers)
|
|
7090
7180
|
return;
|
|
7091
7181
|
// we take the highest quality layer, and we assign it to `q` encoder.
|
|
7092
|
-
const withRid = (rid) => (
|
|
7182
|
+
const withRid = (rid) => (layer) => layer.rid === rid;
|
|
7093
7183
|
const highestLayer = layers.find(withRid('f')) ||
|
|
7094
7184
|
layers.find(withRid('h')) ||
|
|
7095
7185
|
layers.find(withRid('q'));
|
|
@@ -7143,7 +7233,8 @@ const computeVideoLayers = (videoTrack, publishOption) => {
|
|
|
7143
7233
|
rid,
|
|
7144
7234
|
width: Math.round(width / downscaleFactor),
|
|
7145
7235
|
height: Math.round(height / downscaleFactor),
|
|
7146
|
-
maxBitrate: Math.round(maxBitrate / bitrateFactor) ||
|
|
7236
|
+
maxBitrate: Math.round(maxBitrate / bitrateFactor) ||
|
|
7237
|
+
{ q: 300000, h: 750000, f: 1250000 }[rid],
|
|
7147
7238
|
maxFramerate: fps,
|
|
7148
7239
|
};
|
|
7149
7240
|
if (svcCodec) {
|
|
@@ -7309,8 +7400,9 @@ class Publisher extends BasePeerConnection {
|
|
|
7309
7400
|
*
|
|
7310
7401
|
* @param track the track to publish.
|
|
7311
7402
|
* @param trackType the track type to publish.
|
|
7403
|
+
* @param options the publish options to use.
|
|
7312
7404
|
*/
|
|
7313
|
-
this.publish = async (track, trackType) => {
|
|
7405
|
+
this.publish = async (track, trackType, options = {}) => {
|
|
7314
7406
|
if (!this.publishOptions.some((o) => o.trackType === trackType)) {
|
|
7315
7407
|
throw new Error(`No publish options found for ${TrackType[trackType]}`);
|
|
7316
7408
|
}
|
|
@@ -7320,13 +7412,13 @@ class Publisher extends BasePeerConnection {
|
|
|
7320
7412
|
// create a clone of the track as otherwise the same trackId will
|
|
7321
7413
|
// appear in the SDP in multiple transceivers
|
|
7322
7414
|
const trackToPublish = this.cloneTrack(track);
|
|
7323
|
-
const transceiver = this.transceiverCache.get(publishOption);
|
|
7415
|
+
const { transceiver } = this.transceiverCache.get(publishOption) || {};
|
|
7324
7416
|
if (!transceiver) {
|
|
7325
|
-
await this.addTransceiver(trackToPublish, publishOption);
|
|
7417
|
+
await this.addTransceiver(trackToPublish, publishOption, options);
|
|
7326
7418
|
}
|
|
7327
7419
|
else {
|
|
7328
7420
|
const previousTrack = transceiver.sender.track;
|
|
7329
|
-
await this.updateTransceiver(transceiver, trackToPublish, trackType);
|
|
7421
|
+
await this.updateTransceiver(transceiver, trackToPublish, trackType, options);
|
|
7330
7422
|
if (!isReactNative()) {
|
|
7331
7423
|
this.stopTrack(previousTrack);
|
|
7332
7424
|
}
|
|
@@ -7336,11 +7428,13 @@ class Publisher extends BasePeerConnection {
|
|
|
7336
7428
|
/**
|
|
7337
7429
|
* Adds a new transceiver carrying the given track to the peer connection.
|
|
7338
7430
|
*/
|
|
7339
|
-
this.addTransceiver = async (track, publishOption) => {
|
|
7340
|
-
const
|
|
7431
|
+
this.addTransceiver = async (track, publishOption, options) => {
|
|
7432
|
+
const encodings = isAudioTrackType(publishOption.trackType)
|
|
7433
|
+
? computeAudioLayers(publishOption, options)
|
|
7434
|
+
: computeVideoLayers(track, publishOption);
|
|
7341
7435
|
const sendEncodings = isSvcCodec(publishOption.codec?.name)
|
|
7342
|
-
? toSvcEncodings(
|
|
7343
|
-
:
|
|
7436
|
+
? toSvcEncodings(encodings)
|
|
7437
|
+
: encodings;
|
|
7344
7438
|
const transceiver = this.pc.addTransceiver(track, {
|
|
7345
7439
|
direction: 'sendonly',
|
|
7346
7440
|
sendEncodings,
|
|
@@ -7350,20 +7444,49 @@ class Publisher extends BasePeerConnection {
|
|
|
7350
7444
|
await transceiver.sender.setParameters(params);
|
|
7351
7445
|
const trackType = publishOption.trackType;
|
|
7352
7446
|
this.logger('debug', `Added ${TrackType[trackType]} transceiver`);
|
|
7353
|
-
this.transceiverCache.add(publishOption, transceiver);
|
|
7447
|
+
this.transceiverCache.add({ publishOption, transceiver, options });
|
|
7354
7448
|
this.trackIdToTrackType.set(track.id, trackType);
|
|
7355
7449
|
await this.negotiate();
|
|
7356
7450
|
};
|
|
7357
7451
|
/**
|
|
7358
7452
|
* Updates the transceiver with the given track and track type.
|
|
7359
7453
|
*/
|
|
7360
|
-
this.updateTransceiver = async (transceiver, track, trackType) => {
|
|
7454
|
+
this.updateTransceiver = async (transceiver, track, trackType, options = {}) => {
|
|
7361
7455
|
const sender = transceiver.sender;
|
|
7362
7456
|
if (sender.track)
|
|
7363
7457
|
this.trackIdToTrackType.delete(sender.track.id);
|
|
7364
7458
|
await sender.replaceTrack(track);
|
|
7365
7459
|
if (track)
|
|
7366
7460
|
this.trackIdToTrackType.set(track.id, trackType);
|
|
7461
|
+
if (isAudioTrackType(trackType)) {
|
|
7462
|
+
await this.updateAudioPublishOptions(trackType, options);
|
|
7463
|
+
}
|
|
7464
|
+
};
|
|
7465
|
+
/**
|
|
7466
|
+
* Updates the publish options for the given track type.
|
|
7467
|
+
*/
|
|
7468
|
+
this.updateAudioPublishOptions = async (trackType, options) => {
|
|
7469
|
+
for (const publishOption of this.publishOptions) {
|
|
7470
|
+
if (publishOption.trackType !== trackType)
|
|
7471
|
+
continue;
|
|
7472
|
+
const bundle = this.transceiverCache.get(publishOption);
|
|
7473
|
+
if (!bundle)
|
|
7474
|
+
continue;
|
|
7475
|
+
const { transceiver, options: current } = bundle;
|
|
7476
|
+
if (current.audioBitrateProfile !== options.audioBitrateProfile) {
|
|
7477
|
+
const encodings = computeAudioLayers(publishOption, options);
|
|
7478
|
+
if (encodings && encodings.length > 0) {
|
|
7479
|
+
const params = transceiver.sender.getParameters();
|
|
7480
|
+
const [currentEncoding] = params.encodings;
|
|
7481
|
+
const [targetEncoding] = encodings;
|
|
7482
|
+
if (currentEncoding.maxBitrate !== targetEncoding.maxBitrate) {
|
|
7483
|
+
currentEncoding.maxBitrate = targetEncoding.maxBitrate;
|
|
7484
|
+
}
|
|
7485
|
+
await transceiver.sender.setParameters(params);
|
|
7486
|
+
}
|
|
7487
|
+
}
|
|
7488
|
+
this.transceiverCache.update(publishOption, { options });
|
|
7489
|
+
}
|
|
7367
7490
|
};
|
|
7368
7491
|
/**
|
|
7369
7492
|
* Synchronizes the current Publisher state with the provided publish options.
|
|
@@ -7378,12 +7501,12 @@ class Publisher extends BasePeerConnection {
|
|
|
7378
7501
|
continue;
|
|
7379
7502
|
const item = this.transceiverCache.find((i) => !!i.transceiver.sender.track &&
|
|
7380
7503
|
i.publishOption.trackType === trackType);
|
|
7381
|
-
if (!item
|
|
7504
|
+
if (!item)
|
|
7382
7505
|
continue;
|
|
7383
7506
|
// take the track from the existing transceiver for the same track type,
|
|
7384
7507
|
// clone it and publish it with the new publish options
|
|
7385
7508
|
const track = this.cloneTrack(item.transceiver.sender.track);
|
|
7386
|
-
await this.addTransceiver(track, publishOption);
|
|
7509
|
+
await this.addTransceiver(track, publishOption, item.options);
|
|
7387
7510
|
}
|
|
7388
7511
|
// stop publishing with options not required anymore -> [vp9]
|
|
7389
7512
|
for (const item of this.transceiverCache.items()) {
|
|
@@ -7563,11 +7686,9 @@ class Publisher extends BasePeerConnection {
|
|
|
7563
7686
|
this.getAnnouncedTracks = (sdp) => {
|
|
7564
7687
|
const trackInfos = [];
|
|
7565
7688
|
for (const bundle of this.transceiverCache.items()) {
|
|
7566
|
-
|
|
7567
|
-
const track = transceiver.sender.track;
|
|
7568
|
-
if (!track)
|
|
7689
|
+
if (!bundle.transceiver.sender.track)
|
|
7569
7690
|
continue;
|
|
7570
|
-
trackInfos.push(this.toTrackInfo(
|
|
7691
|
+
trackInfos.push(this.toTrackInfo(bundle, sdp));
|
|
7571
7692
|
}
|
|
7572
7693
|
return trackInfos;
|
|
7573
7694
|
};
|
|
@@ -7580,17 +7701,18 @@ class Publisher extends BasePeerConnection {
|
|
|
7580
7701
|
const sdp = this.pc.localDescription?.sdp;
|
|
7581
7702
|
const trackInfos = [];
|
|
7582
7703
|
for (const publishOption of this.publishOptions) {
|
|
7583
|
-
const
|
|
7584
|
-
if (!
|
|
7704
|
+
const bundle = this.transceiverCache.get(publishOption);
|
|
7705
|
+
if (!bundle || !bundle.transceiver.sender.track)
|
|
7585
7706
|
continue;
|
|
7586
|
-
trackInfos.push(this.toTrackInfo(
|
|
7707
|
+
trackInfos.push(this.toTrackInfo(bundle, sdp));
|
|
7587
7708
|
}
|
|
7588
7709
|
return trackInfos;
|
|
7589
7710
|
};
|
|
7590
7711
|
/**
|
|
7591
7712
|
* Converts the given transceiver to a `TrackInfo` object.
|
|
7592
7713
|
*/
|
|
7593
|
-
this.toTrackInfo = (
|
|
7714
|
+
this.toTrackInfo = (bundle, sdp) => {
|
|
7715
|
+
const { transceiver, publishOption } = bundle;
|
|
7594
7716
|
const track = transceiver.sender.track;
|
|
7595
7717
|
const isTrackLive = track.readyState === 'live';
|
|
7596
7718
|
const layers = isTrackLive
|
|
@@ -7598,15 +7720,16 @@ class Publisher extends BasePeerConnection {
|
|
|
7598
7720
|
: this.transceiverCache.getLayers(publishOption);
|
|
7599
7721
|
this.transceiverCache.setLayers(publishOption, layers);
|
|
7600
7722
|
const isAudioTrack = isAudioTrackType(publishOption.trackType);
|
|
7601
|
-
const isStereo = isAudioTrack && track.getSettings().channelCount === 2;
|
|
7602
7723
|
const transceiverIndex = this.transceiverCache.indexOf(transceiver);
|
|
7603
7724
|
const audioSettings = this.state.settings?.audio;
|
|
7725
|
+
const stereo = publishOption.trackType === TrackType.SCREEN_SHARE_AUDIO ||
|
|
7726
|
+
(isAudioTrack && !!audioSettings?.hifi_audio_enabled);
|
|
7604
7727
|
return {
|
|
7605
7728
|
trackId: track.id,
|
|
7606
7729
|
layers: toVideoLayers(layers),
|
|
7607
7730
|
trackType: publishOption.trackType,
|
|
7608
7731
|
mid: extractMid(transceiver, transceiverIndex, sdp),
|
|
7609
|
-
stereo
|
|
7732
|
+
stereo,
|
|
7610
7733
|
dtx: isAudioTrack && !!audioSettings?.opus_dtx_enabled,
|
|
7611
7734
|
red: isAudioTrack && !!audioSettings?.redundant_coding_enabled,
|
|
7612
7735
|
muted: !isTrackLive,
|
|
@@ -9890,7 +10013,7 @@ function resolveDeviceId(deviceId, kind) {
|
|
|
9890
10013
|
*/
|
|
9891
10014
|
const isMobile = () => /Mobi/i.test(navigator.userAgent);
|
|
9892
10015
|
|
|
9893
|
-
class
|
|
10016
|
+
class DeviceManager {
|
|
9894
10017
|
constructor(call, state, trackType) {
|
|
9895
10018
|
/**
|
|
9896
10019
|
* if true, stops the media stream when call is left
|
|
@@ -10097,8 +10220,8 @@ class InputMediaDeviceManager {
|
|
|
10097
10220
|
}
|
|
10098
10221
|
});
|
|
10099
10222
|
}
|
|
10100
|
-
publishStream(stream) {
|
|
10101
|
-
return this.call.publish(stream, this.trackType);
|
|
10223
|
+
publishStream(stream, options) {
|
|
10224
|
+
return this.call.publish(stream, this.trackType, options);
|
|
10102
10225
|
}
|
|
10103
10226
|
stopPublishStream() {
|
|
10104
10227
|
return this.call.stopPublish(this.trackType);
|
|
@@ -10334,16 +10457,15 @@ class InputMediaDeviceManager {
|
|
|
10334
10457
|
}
|
|
10335
10458
|
}
|
|
10336
10459
|
|
|
10337
|
-
class
|
|
10460
|
+
class DeviceManagerState {
|
|
10338
10461
|
/**
|
|
10339
|
-
* Constructs new InputMediaDeviceManagerState instance.
|
|
10462
|
+
* Constructs a new InputMediaDeviceManagerState instance.
|
|
10340
10463
|
*
|
|
10341
10464
|
* @param disableMode the disable mode to use.
|
|
10342
10465
|
* @param permission the BrowserPermission to use for querying.
|
|
10343
10466
|
* `undefined` means no permission is required.
|
|
10344
10467
|
*/
|
|
10345
|
-
constructor(disableMode
|
|
10346
|
-
this.disableMode = disableMode;
|
|
10468
|
+
constructor(disableMode, permission) {
|
|
10347
10469
|
this.statusSubject = new rxjs.BehaviorSubject(undefined);
|
|
10348
10470
|
this.optimisticStatusSubject = new rxjs.BehaviorSubject(undefined);
|
|
10349
10471
|
this.mediaStreamSubject = new rxjs.BehaviorSubject(undefined);
|
|
@@ -10374,6 +10496,7 @@ class InputMediaDeviceManagerState {
|
|
|
10374
10496
|
* The default constraints for the device.
|
|
10375
10497
|
*/
|
|
10376
10498
|
this.defaultConstraints$ = this.defaultConstraintsSubject.asObservable();
|
|
10499
|
+
this.disableMode = disableMode;
|
|
10377
10500
|
this.hasBrowserPermission$ = permission
|
|
10378
10501
|
? permission.asObservable().pipe(rxjs.shareReplay(1))
|
|
10379
10502
|
: rxjs.of(true);
|
|
@@ -10460,10 +10583,15 @@ class InputMediaDeviceManagerState {
|
|
|
10460
10583
|
}
|
|
10461
10584
|
}
|
|
10462
10585
|
|
|
10463
|
-
class CameraManagerState extends
|
|
10586
|
+
class CameraManagerState extends DeviceManagerState {
|
|
10464
10587
|
constructor() {
|
|
10465
10588
|
super('stop-tracks', getVideoBrowserPermission());
|
|
10466
10589
|
this.directionSubject = new rxjs.BehaviorSubject(undefined);
|
|
10590
|
+
/**
|
|
10591
|
+
* Observable that emits the preferred camera direction
|
|
10592
|
+
* front - means the camera facing the user
|
|
10593
|
+
* back - means the camera facing the environment
|
|
10594
|
+
*/
|
|
10467
10595
|
this.direction$ = this.directionSubject
|
|
10468
10596
|
.asObservable()
|
|
10469
10597
|
.pipe(rxjs.distinctUntilChanged());
|
|
@@ -10503,7 +10631,7 @@ class CameraManagerState extends InputMediaDeviceManagerState {
|
|
|
10503
10631
|
}
|
|
10504
10632
|
}
|
|
10505
10633
|
|
|
10506
|
-
class CameraManager extends
|
|
10634
|
+
class CameraManager extends DeviceManager {
|
|
10507
10635
|
/**
|
|
10508
10636
|
* Constructs a new CameraManager.
|
|
10509
10637
|
*
|
|
@@ -10649,18 +10777,87 @@ class CameraManager extends InputMediaDeviceManager {
|
|
|
10649
10777
|
}
|
|
10650
10778
|
}
|
|
10651
10779
|
|
|
10652
|
-
|
|
10780
|
+
/**
|
|
10781
|
+
* Base class for High Fidelity enabled Device Managers.
|
|
10782
|
+
*/
|
|
10783
|
+
class AudioDeviceManager extends DeviceManager {
|
|
10784
|
+
/**
|
|
10785
|
+
* Sets the audio bitrate profile and stereo mode.
|
|
10786
|
+
*/
|
|
10787
|
+
async setAudioBitrateProfile(profile) {
|
|
10788
|
+
if (!this.call.state.settings?.audio.hifi_audio_enabled) {
|
|
10789
|
+
throw new Error('High Fidelity audio is not enabled for this call');
|
|
10790
|
+
}
|
|
10791
|
+
this.doSetAudioBitrateProfile(profile);
|
|
10792
|
+
this.state.setAudioBitrateProfile(profile);
|
|
10793
|
+
if (this.enabled) {
|
|
10794
|
+
await this.applySettingsToStream();
|
|
10795
|
+
}
|
|
10796
|
+
}
|
|
10797
|
+
/**
|
|
10798
|
+
* Overrides the default `publishStream` method to inject the audio bitrate profile.
|
|
10799
|
+
*/
|
|
10800
|
+
publishStream(stream, options) {
|
|
10801
|
+
return super.publishStream(stream, {
|
|
10802
|
+
audioBitrateProfile: this.state.audioBitrateProfile,
|
|
10803
|
+
...options,
|
|
10804
|
+
});
|
|
10805
|
+
}
|
|
10806
|
+
}
|
|
10807
|
+
/**
|
|
10808
|
+
* Prepares a new MediaTrackConstraints set based on the provided arguments.
|
|
10809
|
+
*/
|
|
10810
|
+
const createAudioConstraints = (profile) => {
|
|
10811
|
+
const stereo = profile === AudioBitrateProfile.MUSIC_HIGH_QUALITY;
|
|
10812
|
+
return {
|
|
10813
|
+
echoCancellation: !stereo,
|
|
10814
|
+
noiseSuppression: !stereo,
|
|
10815
|
+
autoGainControl: !stereo,
|
|
10816
|
+
channelCount: { ideal: stereo ? 2 : 1 },
|
|
10817
|
+
};
|
|
10818
|
+
};
|
|
10819
|
+
|
|
10820
|
+
/**
|
|
10821
|
+
* Base state class for High Fidelity enabled device managers.
|
|
10822
|
+
*/
|
|
10823
|
+
class AudioDeviceManagerState extends DeviceManagerState {
|
|
10824
|
+
/**
|
|
10825
|
+
* Constructs a new AudioDeviceManagerState instance.
|
|
10826
|
+
*/
|
|
10827
|
+
constructor(disableMode, permission, profile) {
|
|
10828
|
+
super(disableMode, permission);
|
|
10829
|
+
this.audioBitrateProfileSubject = new rxjs.BehaviorSubject(profile);
|
|
10830
|
+
this.audioBitrateProfile$ = this.audioBitrateProfileSubject
|
|
10831
|
+
.asObservable()
|
|
10832
|
+
.pipe(rxjs.distinctUntilChanged());
|
|
10833
|
+
}
|
|
10834
|
+
/**
|
|
10835
|
+
* Returns the current audio bitrate profile.
|
|
10836
|
+
*/
|
|
10837
|
+
get audioBitrateProfile() {
|
|
10838
|
+
return getCurrentValue(this.audioBitrateProfile$);
|
|
10839
|
+
}
|
|
10840
|
+
/**
|
|
10841
|
+
* Sets the audio bitrate profile and stereo mode.
|
|
10842
|
+
*/
|
|
10843
|
+
setAudioBitrateProfile(profile) {
|
|
10844
|
+
setCurrentValue(this.audioBitrateProfileSubject, profile);
|
|
10845
|
+
}
|
|
10846
|
+
}
|
|
10847
|
+
|
|
10848
|
+
class MicrophoneManagerState extends AudioDeviceManagerState {
|
|
10653
10849
|
constructor(disableMode) {
|
|
10654
|
-
super(disableMode, getAudioBrowserPermission());
|
|
10850
|
+
super(disableMode, getAudioBrowserPermission(), AudioBitrateProfile.VOICE_STANDARD_UNSPECIFIED);
|
|
10655
10851
|
this.speakingWhileMutedSubject = new rxjs.BehaviorSubject(false);
|
|
10852
|
+
/**
|
|
10853
|
+
* An Observable that emits `true` if the user's microphone is muted, but they're speaking.
|
|
10854
|
+
*/
|
|
10656
10855
|
this.speakingWhileMuted$ = this.speakingWhileMutedSubject
|
|
10657
10856
|
.asObservable()
|
|
10658
10857
|
.pipe(rxjs.distinctUntilChanged());
|
|
10659
10858
|
}
|
|
10660
10859
|
/**
|
|
10661
|
-
* `true` if the user's microphone is muted but they'
|
|
10662
|
-
*
|
|
10663
|
-
* This feature is not available in the React Native SDK.
|
|
10860
|
+
* `true` if the user's microphone is muted but they're speaking.
|
|
10664
10861
|
*/
|
|
10665
10862
|
get speakingWhileMuted() {
|
|
10666
10863
|
return getCurrentValue(this.speakingWhileMuted$);
|
|
@@ -10893,7 +11090,7 @@ class RNSpeechDetector {
|
|
|
10893
11090
|
}
|
|
10894
11091
|
}
|
|
10895
11092
|
|
|
10896
|
-
class MicrophoneManager extends
|
|
11093
|
+
class MicrophoneManager extends AudioDeviceManager {
|
|
10897
11094
|
constructor(call, disableMode = 'stop-tracks') {
|
|
10898
11095
|
super(call, new MicrophoneManagerState(disableMode), TrackType.AUDIO);
|
|
10899
11096
|
this.speakingWhileMutedNotificationEnabled = true;
|
|
@@ -11089,6 +11286,21 @@ class MicrophoneManager extends InputMediaDeviceManager {
|
|
|
11089
11286
|
getStream(constraints) {
|
|
11090
11287
|
return getAudioStream(constraints, this.call.tracer);
|
|
11091
11288
|
}
|
|
11289
|
+
doSetAudioBitrateProfile(profile) {
|
|
11290
|
+
this.setDefaultConstraints({
|
|
11291
|
+
...this.state.defaultConstraints,
|
|
11292
|
+
...createAudioConstraints(profile),
|
|
11293
|
+
});
|
|
11294
|
+
if (this.noiseCancellation) {
|
|
11295
|
+
const disableAudioProcessing = profile === AudioBitrateProfile.MUSIC_HIGH_QUALITY;
|
|
11296
|
+
if (disableAudioProcessing) {
|
|
11297
|
+
this.noiseCancellation.disable(); // disable for high quality music mode
|
|
11298
|
+
}
|
|
11299
|
+
else {
|
|
11300
|
+
this.noiseCancellation.enable(); // restore it for other modes if available
|
|
11301
|
+
}
|
|
11302
|
+
}
|
|
11303
|
+
}
|
|
11092
11304
|
async startSpeakingWhileMutedDetection(deviceId) {
|
|
11093
11305
|
await withoutConcurrency(this.soundDetectorConcurrencyTag, async () => {
|
|
11094
11306
|
await this.stopSpeakingWhileMutedDetection();
|
|
@@ -11125,9 +11337,12 @@ class MicrophoneManager extends InputMediaDeviceManager {
|
|
|
11125
11337
|
}
|
|
11126
11338
|
}
|
|
11127
11339
|
|
|
11128
|
-
class ScreenShareState extends
|
|
11340
|
+
class ScreenShareState extends AudioDeviceManagerState {
|
|
11341
|
+
/**
|
|
11342
|
+
* Constructs a new ScreenShareState instance.
|
|
11343
|
+
*/
|
|
11129
11344
|
constructor() {
|
|
11130
|
-
super(
|
|
11345
|
+
super('stop-tracks', undefined, AudioBitrateProfile.MUSIC_HIGH_QUALITY);
|
|
11131
11346
|
this.audioEnabledSubject = new rxjs.BehaviorSubject(true);
|
|
11132
11347
|
this.settingsSubject = new rxjs.BehaviorSubject(undefined);
|
|
11133
11348
|
/**
|
|
@@ -11176,7 +11391,7 @@ class ScreenShareState extends InputMediaDeviceManagerState {
|
|
|
11176
11391
|
}
|
|
11177
11392
|
}
|
|
11178
11393
|
|
|
11179
|
-
class ScreenShareManager extends
|
|
11394
|
+
class ScreenShareManager extends AudioDeviceManager {
|
|
11180
11395
|
constructor(call) {
|
|
11181
11396
|
super(call, new ScreenShareState(), TrackType.SCREEN_SHARE);
|
|
11182
11397
|
}
|
|
@@ -11186,6 +11401,7 @@ class ScreenShareManager extends InputMediaDeviceManager {
|
|
|
11186
11401
|
const maybeTargetResolution = settings?.screensharing.target_resolution;
|
|
11187
11402
|
if (maybeTargetResolution) {
|
|
11188
11403
|
this.setDefaultConstraints({
|
|
11404
|
+
...this.state.defaultConstraints,
|
|
11189
11405
|
video: {
|
|
11190
11406
|
width: maybeTargetResolution.width,
|
|
11191
11407
|
height: maybeTargetResolution.height,
|
|
@@ -11242,6 +11458,19 @@ class ScreenShareManager extends InputMediaDeviceManager {
|
|
|
11242
11458
|
}
|
|
11243
11459
|
return stream;
|
|
11244
11460
|
}
|
|
11461
|
+
doSetAudioBitrateProfile(profile) {
|
|
11462
|
+
const { defaultConstraints } = this.state;
|
|
11463
|
+
const baseAudioConstraints = typeof defaultConstraints?.audio !== 'boolean'
|
|
11464
|
+
? defaultConstraints?.audio
|
|
11465
|
+
: null;
|
|
11466
|
+
this.setDefaultConstraints({
|
|
11467
|
+
...defaultConstraints,
|
|
11468
|
+
audio: {
|
|
11469
|
+
...baseAudioConstraints,
|
|
11470
|
+
...createAudioConstraints(profile),
|
|
11471
|
+
},
|
|
11472
|
+
});
|
|
11473
|
+
}
|
|
11245
11474
|
async stopPublishStream() {
|
|
11246
11475
|
return this.call.stopPublish(TrackType.SCREEN_SHARE, TrackType.SCREEN_SHARE_AUDIO);
|
|
11247
11476
|
}
|
|
@@ -11255,19 +11484,27 @@ class ScreenShareManager extends InputMediaDeviceManager {
|
|
|
11255
11484
|
|
|
11256
11485
|
class SpeakerState {
|
|
11257
11486
|
constructor(tracer) {
|
|
11487
|
+
this.tracer = tracer;
|
|
11258
11488
|
this.selectedDeviceSubject = new rxjs.BehaviorSubject('');
|
|
11259
11489
|
this.volumeSubject = new rxjs.BehaviorSubject(1);
|
|
11260
11490
|
/**
|
|
11261
11491
|
* [Tells if the browser supports audio output change on 'audio' elements](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/setSinkId).
|
|
11262
11492
|
*/
|
|
11263
11493
|
this.isDeviceSelectionSupported = checkIfAudioOutputChangeSupported();
|
|
11264
|
-
|
|
11494
|
+
/**
|
|
11495
|
+
* An Observable that emits the currently selected device
|
|
11496
|
+
*
|
|
11497
|
+
* Note: this feature is not supported in React Native
|
|
11498
|
+
*/
|
|
11265
11499
|
this.selectedDevice$ = this.selectedDeviceSubject
|
|
11266
11500
|
.asObservable()
|
|
11267
11501
|
.pipe(rxjs.distinctUntilChanged());
|
|
11268
|
-
|
|
11269
|
-
|
|
11270
|
-
|
|
11502
|
+
/**
|
|
11503
|
+
* An Observable that emits the currently selected volume
|
|
11504
|
+
*
|
|
11505
|
+
* Note: this feature is not supported in React Native
|
|
11506
|
+
*/
|
|
11507
|
+
this.volume$ = this.volumeSubject.asObservable().pipe(rxjs.distinctUntilChanged());
|
|
11271
11508
|
}
|
|
11272
11509
|
/**
|
|
11273
11510
|
* The currently selected device
|
|
@@ -11871,7 +12108,6 @@ class Call {
|
|
|
11871
12108
|
if ([exports.CallingState.JOINED, exports.CallingState.JOINING].includes(callingState)) {
|
|
11872
12109
|
throw new Error(`Illegal State: call.join() shall be called only once`);
|
|
11873
12110
|
}
|
|
11874
|
-
this.state.setCallingState(exports.CallingState.JOINING);
|
|
11875
12111
|
// we will count the number of join failures per SFU.
|
|
11876
12112
|
// once the number of failures reaches 2, we will piggyback on the `migrating_from`
|
|
11877
12113
|
// field to force the coordinator to provide us another SFU
|
|
@@ -11900,8 +12136,6 @@ class Call {
|
|
|
11900
12136
|
joinData.migrating_from = sfuId;
|
|
11901
12137
|
}
|
|
11902
12138
|
if (attempt === maxJoinRetries - 1) {
|
|
11903
|
-
// restore the previous call state if the join-flow fails
|
|
11904
|
-
this.state.setCallingState(callingState);
|
|
11905
12139
|
throw err;
|
|
11906
12140
|
}
|
|
11907
12141
|
}
|
|
@@ -11961,6 +12195,7 @@ class Call {
|
|
|
11961
12195
|
})
|
|
11962
12196
|
: previousSfuClient;
|
|
11963
12197
|
this.sfuClient = sfuClient;
|
|
12198
|
+
this.unifiedSessionId ?? (this.unifiedSessionId = sfuClient.sessionId);
|
|
11964
12199
|
this.dynascaleManager.setSfuClient(sfuClient);
|
|
11965
12200
|
const clientDetails = await getClientDetails();
|
|
11966
12201
|
// we don't need to send JoinRequest if we are re-using an existing healthy SFU client
|
|
@@ -11984,6 +12219,7 @@ class Call {
|
|
|
11984
12219
|
: [];
|
|
11985
12220
|
try {
|
|
11986
12221
|
const { callState, fastReconnectDeadlineSeconds, publishOptions } = await sfuClient.join({
|
|
12222
|
+
unifiedSessionId: this.unifiedSessionId,
|
|
11987
12223
|
subscriberSdp,
|
|
11988
12224
|
publisherSdp,
|
|
11989
12225
|
clientDetails,
|
|
@@ -12029,6 +12265,7 @@ class Call {
|
|
|
12029
12265
|
statsOptions,
|
|
12030
12266
|
publishOptions: this.currentPublishOptions || [],
|
|
12031
12267
|
closePreviousInstances: !performingMigration,
|
|
12268
|
+
unifiedSessionId: this.unifiedSessionId,
|
|
12032
12269
|
});
|
|
12033
12270
|
}
|
|
12034
12271
|
// make sure we only track connection timing if we are not calling this method as part of a reconnection flow
|
|
@@ -12155,7 +12392,7 @@ class Call {
|
|
|
12155
12392
|
* @internal
|
|
12156
12393
|
*/
|
|
12157
12394
|
this.initPublisherAndSubscriber = (opts) => {
|
|
12158
|
-
const { sfuClient, connectionConfig, clientDetails, statsOptions, publishOptions, closePreviousInstances, } = opts;
|
|
12395
|
+
const { sfuClient, connectionConfig, clientDetails, statsOptions, publishOptions, closePreviousInstances, unifiedSessionId, } = opts;
|
|
12159
12396
|
const { enable_rtc_stats: enableTracing } = statsOptions;
|
|
12160
12397
|
if (closePreviousInstances && this.subscriber) {
|
|
12161
12398
|
this.subscriber.dispose();
|
|
@@ -12210,7 +12447,6 @@ class Call {
|
|
|
12210
12447
|
this.tracer.setEnabled(enableTracing);
|
|
12211
12448
|
this.sfuStatsReporter?.stop();
|
|
12212
12449
|
if (statsOptions?.reporting_interval_ms > 0) {
|
|
12213
|
-
this.unifiedSessionId ?? (this.unifiedSessionId = sfuClient.sessionId);
|
|
12214
12450
|
this.sfuStatsReporter = new SfuStatsReporter(sfuClient, {
|
|
12215
12451
|
clientDetails,
|
|
12216
12452
|
options: statsOptions,
|
|
@@ -12220,7 +12456,7 @@ class Call {
|
|
|
12220
12456
|
camera: this.camera,
|
|
12221
12457
|
state: this.state,
|
|
12222
12458
|
tracer: this.tracer,
|
|
12223
|
-
unifiedSessionId
|
|
12459
|
+
unifiedSessionId,
|
|
12224
12460
|
});
|
|
12225
12461
|
this.sfuStatsReporter.start();
|
|
12226
12462
|
}
|
|
@@ -12593,10 +12829,11 @@ class Call {
|
|
|
12593
12829
|
*
|
|
12594
12830
|
* @param mediaStream the media stream to publish.
|
|
12595
12831
|
* @param trackType the type of the track to announce.
|
|
12832
|
+
* @param options the publish options.
|
|
12596
12833
|
*/
|
|
12597
|
-
this.publish = async (mediaStream, trackType) => {
|
|
12834
|
+
this.publish = async (mediaStream, trackType, options) => {
|
|
12598
12835
|
if (!this.sfuClient)
|
|
12599
|
-
throw new Error(`Call not joined yet
|
|
12836
|
+
throw new Error(`Call is not joined yet`);
|
|
12600
12837
|
// joining is in progress, and we should wait until the client is ready
|
|
12601
12838
|
await this.sfuClient.joinTask;
|
|
12602
12839
|
if (!this.permissionsContext.canPublish(trackType)) {
|
|
@@ -12614,14 +12851,15 @@ class Call {
|
|
|
12614
12851
|
throw new Error(`Can't publish ended tracks.`);
|
|
12615
12852
|
}
|
|
12616
12853
|
pushToIfMissing(this.trackPublishOrder, trackType);
|
|
12617
|
-
await this.publisher.publish(track, trackType);
|
|
12854
|
+
await this.publisher.publish(track, trackType, options);
|
|
12618
12855
|
const trackTypes = [trackType];
|
|
12619
12856
|
if (trackType === TrackType.SCREEN_SHARE) {
|
|
12620
12857
|
const [audioTrack] = mediaStream.getAudioTracks();
|
|
12621
12858
|
if (audioTrack) {
|
|
12622
|
-
|
|
12623
|
-
|
|
12624
|
-
|
|
12859
|
+
const screenShareAudio = TrackType.SCREEN_SHARE_AUDIO;
|
|
12860
|
+
pushToIfMissing(this.trackPublishOrder, screenShareAudio);
|
|
12861
|
+
await this.publisher.publish(audioTrack, screenShareAudio, options);
|
|
12862
|
+
trackTypes.push(screenShareAudio);
|
|
12625
12863
|
}
|
|
12626
12864
|
}
|
|
12627
12865
|
if (track.kind === 'video') {
|
|
@@ -14540,7 +14778,7 @@ class StreamClient {
|
|
|
14540
14778
|
this.getUserAgent = () => {
|
|
14541
14779
|
if (!this.cachedUserAgent) {
|
|
14542
14780
|
const { clientAppIdentifier = {} } = this.options;
|
|
14543
|
-
const { sdkName = 'js', sdkVersion = "1.
|
|
14781
|
+
const { sdkName = 'js', sdkVersion = "1.33.0", ...extras } = clientAppIdentifier;
|
|
14544
14782
|
this.cachedUserAgent = [
|
|
14545
14783
|
`stream-video-${sdkName}-v${sdkVersion}`,
|
|
14546
14784
|
...Object.entries(extras).map(([key, value]) => `${key}=${value}`),
|
|
@@ -14729,6 +14967,7 @@ class StreamVideoClient {
|
|
|
14729
14967
|
this.effectsRegistered = false;
|
|
14730
14968
|
this.eventHandlersToUnregister = [];
|
|
14731
14969
|
this.connectionConcurrencyTag = Symbol('connectionConcurrencyTag');
|
|
14970
|
+
this.rejectCallWhenBusy = false;
|
|
14732
14971
|
this.registerClientInstance = (apiKey, user) => {
|
|
14733
14972
|
const instanceKey = getInstanceKey(apiKey, user);
|
|
14734
14973
|
if (StreamVideoClient._instances.has(instanceKey)) {
|
|
@@ -14774,7 +15013,16 @@ class StreamVideoClient {
|
|
|
14774
15013
|
let call = this.writeableStateStore.findCall(e.call.type, e.call.id);
|
|
14775
15014
|
if (call) {
|
|
14776
15015
|
if (ringing) {
|
|
14777
|
-
|
|
15016
|
+
if (this.shouldRejectCall(call.cid)) {
|
|
15017
|
+
this.logger('info', `Leaving call with busy reject reason ${call.cid} because user is busy`);
|
|
15018
|
+
// remove the instance from the state store
|
|
15019
|
+
await call.leave();
|
|
15020
|
+
// explicitly reject the call with busy reason as calling state was not ringing before and leave would not call it therefore
|
|
15021
|
+
await call.reject('busy');
|
|
15022
|
+
}
|
|
15023
|
+
else {
|
|
15024
|
+
await call.updateFromRingingEvent(e);
|
|
15025
|
+
}
|
|
14778
15026
|
}
|
|
14779
15027
|
else {
|
|
14780
15028
|
call.state.updateFromCallResponse(e.call);
|
|
@@ -14789,11 +15037,19 @@ class StreamVideoClient {
|
|
|
14789
15037
|
clientStore: this.writeableStateStore,
|
|
14790
15038
|
ringing,
|
|
14791
15039
|
});
|
|
14792
|
-
call.state.updateFromCallResponse(e.call);
|
|
14793
15040
|
if (ringing) {
|
|
14794
|
-
|
|
15041
|
+
if (this.shouldRejectCall(call.cid)) {
|
|
15042
|
+
this.logger('info', `Rejecting call ${call.cid} because user is busy`);
|
|
15043
|
+
// call is not in the state store yet, so just reject api is enough
|
|
15044
|
+
await call.reject('busy');
|
|
15045
|
+
}
|
|
15046
|
+
else {
|
|
15047
|
+
await call.updateFromRingingEvent(e);
|
|
15048
|
+
await call.get();
|
|
15049
|
+
}
|
|
14795
15050
|
}
|
|
14796
15051
|
else {
|
|
15052
|
+
call.state.updateFromCallResponse(e.call);
|
|
14797
15053
|
this.writeableStateStore.registerCall(call);
|
|
14798
15054
|
this.logger('info', `New call created and registered: ${call.cid}`);
|
|
14799
15055
|
}
|
|
@@ -15060,6 +15316,16 @@ class StreamVideoClient {
|
|
|
15060
15316
|
this.connectAnonymousUser = async (user, tokenOrProvider) => {
|
|
15061
15317
|
return withoutConcurrency(this.connectionConcurrencyTag, () => this.streamClient.connectAnonymousUser(user, tokenOrProvider));
|
|
15062
15318
|
};
|
|
15319
|
+
this.shouldRejectCall = (currentCallId) => {
|
|
15320
|
+
if (!this.rejectCallWhenBusy)
|
|
15321
|
+
return false;
|
|
15322
|
+
const hasOngoingRingingCall = this.state.calls.some((c) => c.cid !== currentCallId &&
|
|
15323
|
+
c.ringing &&
|
|
15324
|
+
c.state.callingState !== exports.CallingState.IDLE &&
|
|
15325
|
+
c.state.callingState !== exports.CallingState.LEFT &&
|
|
15326
|
+
c.state.callingState !== exports.CallingState.RECONNECTING_FAILED);
|
|
15327
|
+
return hasOngoingRingingCall;
|
|
15328
|
+
};
|
|
15063
15329
|
const apiKey = typeof apiKeyOrArgs === 'string' ? apiKeyOrArgs : apiKeyOrArgs.apiKey;
|
|
15064
15330
|
const clientOptions = typeof apiKeyOrArgs === 'string' ? opts : apiKeyOrArgs.options;
|
|
15065
15331
|
if (clientOptions?.enableTimerWorker)
|
|
@@ -15067,6 +15333,7 @@ class StreamVideoClient {
|
|
|
15067
15333
|
const rootLogger = clientOptions?.logger || logToConsole;
|
|
15068
15334
|
setLogger(rootLogger, clientOptions?.logLevel || 'warn');
|
|
15069
15335
|
this.logger = getLogger(['client']);
|
|
15336
|
+
this.rejectCallWhenBusy = clientOptions?.rejectCallWhenBusy ?? false;
|
|
15070
15337
|
this.streamClient = createCoordinatorClient(apiKey, clientOptions);
|
|
15071
15338
|
this.writeableStateStore = new StreamVideoWriteableStateStore();
|
|
15072
15339
|
this.readOnlyStateStore = new StreamVideoReadOnlyStateStore(this.writeableStateStore);
|
|
@@ -15122,6 +15389,8 @@ exports.CallTypes = CallTypes;
|
|
|
15122
15389
|
exports.CameraManager = CameraManager;
|
|
15123
15390
|
exports.CameraManagerState = CameraManagerState;
|
|
15124
15391
|
exports.CreateDeviceRequestPushProviderEnum = CreateDeviceRequestPushProviderEnum;
|
|
15392
|
+
exports.DeviceManager = DeviceManager;
|
|
15393
|
+
exports.DeviceManagerState = DeviceManagerState;
|
|
15125
15394
|
exports.DynascaleManager = DynascaleManager;
|
|
15126
15395
|
exports.ErrorFromResponse = ErrorFromResponse;
|
|
15127
15396
|
exports.FrameRecordingSettingsRequestModeEnum = FrameRecordingSettingsRequestModeEnum;
|
|
@@ -15129,8 +15398,6 @@ exports.FrameRecordingSettingsRequestQualityEnum = FrameRecordingSettingsRequest
|
|
|
15129
15398
|
exports.FrameRecordingSettingsResponseModeEnum = FrameRecordingSettingsResponseModeEnum;
|
|
15130
15399
|
exports.IngressAudioEncodingOptionsRequestChannelsEnum = IngressAudioEncodingOptionsRequestChannelsEnum;
|
|
15131
15400
|
exports.IngressVideoLayerRequestCodecEnum = IngressVideoLayerRequestCodecEnum;
|
|
15132
|
-
exports.InputMediaDeviceManager = InputMediaDeviceManager;
|
|
15133
|
-
exports.InputMediaDeviceManagerState = InputMediaDeviceManagerState;
|
|
15134
15401
|
exports.LayoutSettingsRequestNameEnum = LayoutSettingsRequestNameEnum;
|
|
15135
15402
|
exports.MicrophoneManager = MicrophoneManager;
|
|
15136
15403
|
exports.MicrophoneManagerState = MicrophoneManagerState;
|