@stream-io/video-client 1.36.0 → 1.36.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -0
- package/dist/index.browser.es.js +168 -96
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +168 -96
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +168 -96
- package/dist/index.es.js.map +1 -1
- package/dist/src/rtc/BasePeerConnection.d.ts +4 -2
- package/dist/src/rtc/Publisher.d.ts +3 -3
- package/dist/src/rtc/codecs.d.ts +3 -1
- package/dist/src/rtc/helpers/sdp.d.ts +8 -0
- package/dist/src/rtc/types.d.ts +4 -5
- package/dist/src/types.d.ts +6 -0
- package/package.json +1 -1
- package/src/Call.ts +14 -21
- package/src/rtc/BasePeerConnection.ts +7 -2
- package/src/rtc/Publisher.ts +13 -5
- package/src/rtc/Subscriber.ts +10 -1
- package/src/rtc/__tests__/Publisher.test.ts +12 -8
- package/src/rtc/codecs.ts +13 -2
- package/src/rtc/helpers/__tests__/sdp.codecs.test.ts +628 -0
- package/src/rtc/helpers/sdp.ts +82 -0
- package/src/rtc/types.ts +4 -4
- package/src/types.ts +6 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).
|
|
4
4
|
|
|
5
|
+
## [1.36.1](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.36.0...@stream-io/video-client-1.36.1) (2025-11-12)
|
|
6
|
+
|
|
7
|
+
- enforce the client to publish options on SDP level ([#1976](https://github.com/GetStream/stream-video-js/issues/1976)) ([1d93f72](https://github.com/GetStream/stream-video-js/commit/1d93f72cb4395aaf9b487eb66e0c3b6a8111aca4))
|
|
8
|
+
|
|
5
9
|
## [1.36.0](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.35.1...@stream-io/video-client-1.36.0) (2025-10-30)
|
|
6
10
|
|
|
7
11
|
### Features
|
package/dist/index.browser.es.js
CHANGED
|
@@ -6,9 +6,9 @@ export { AxiosError } from 'axios';
|
|
|
6
6
|
import { TwirpFetchTransport, TwirpErrorCode } from '@protobuf-ts/twirp-transport';
|
|
7
7
|
import * as scopedLogger from '@stream-io/logger';
|
|
8
8
|
export { LogLevelEnum } from '@stream-io/logger';
|
|
9
|
+
import { parse, write } from 'sdp-transform';
|
|
9
10
|
import { ReplaySubject, combineLatest, BehaviorSubject, shareReplay, map, distinctUntilChanged, startWith, takeWhile, distinctUntilKeyChanged, fromEventPattern, concatMap, merge, from, fromEvent, tap, debounceTime, pairwise, of } from 'rxjs';
|
|
10
11
|
import { UAParser } from 'ua-parser-js';
|
|
11
|
-
import { parse, write } from 'sdp-transform';
|
|
12
12
|
import { WorkerTimer } from '@stream-io/worker-timer';
|
|
13
13
|
|
|
14
14
|
/* tslint:disable */
|
|
@@ -3929,19 +3929,158 @@ const retryable = async (rpc, signal) => {
|
|
|
3929
3929
|
return result;
|
|
3930
3930
|
};
|
|
3931
3931
|
|
|
3932
|
+
/**
|
|
3933
|
+
* Extracts the mid from the transceiver or the SDP.
|
|
3934
|
+
*
|
|
3935
|
+
* @param transceiver the transceiver.
|
|
3936
|
+
* @param transceiverInitIndex the index of the transceiver in the transceiver's init array.
|
|
3937
|
+
* @param sdp the SDP.
|
|
3938
|
+
*/
|
|
3939
|
+
const extractMid = (transceiver, transceiverInitIndex, sdp) => {
|
|
3940
|
+
if (transceiver.mid)
|
|
3941
|
+
return transceiver.mid;
|
|
3942
|
+
if (!sdp)
|
|
3943
|
+
return String(transceiverInitIndex);
|
|
3944
|
+
const track = transceiver.sender.track;
|
|
3945
|
+
const parsedSdp = parse(sdp);
|
|
3946
|
+
const media = parsedSdp.media.find((m) => {
|
|
3947
|
+
return (m.type === track.kind &&
|
|
3948
|
+
// if `msid` is not present, we assume that the track is the first one
|
|
3949
|
+
(m.msid?.includes(track.id) ?? true));
|
|
3950
|
+
});
|
|
3951
|
+
if (typeof media?.mid !== 'undefined')
|
|
3952
|
+
return String(media.mid);
|
|
3953
|
+
if (transceiverInitIndex < 0)
|
|
3954
|
+
return '';
|
|
3955
|
+
return String(transceiverInitIndex);
|
|
3956
|
+
};
|
|
3957
|
+
/**
|
|
3958
|
+
* Enables stereo in the answer SDP based on the offered stereo in the offer SDP.
|
|
3959
|
+
*
|
|
3960
|
+
* @param offerSdp the offer SDP containing the stereo configuration.
|
|
3961
|
+
* @param answerSdp the answer SDP to be modified.
|
|
3962
|
+
*/
|
|
3963
|
+
const enableStereo = (offerSdp, answerSdp) => {
|
|
3964
|
+
const offeredStereoMids = new Set();
|
|
3965
|
+
const parsedOfferSdp = parse(offerSdp);
|
|
3966
|
+
for (const media of parsedOfferSdp.media) {
|
|
3967
|
+
if (media.type !== 'audio')
|
|
3968
|
+
continue;
|
|
3969
|
+
const opus = media.rtp.find((r) => r.codec === 'opus');
|
|
3970
|
+
if (!opus)
|
|
3971
|
+
continue;
|
|
3972
|
+
for (const fmtp of media.fmtp) {
|
|
3973
|
+
if (fmtp.payload === opus.payload && fmtp.config.includes('stereo=1')) {
|
|
3974
|
+
offeredStereoMids.add(media.mid);
|
|
3975
|
+
}
|
|
3976
|
+
}
|
|
3977
|
+
}
|
|
3978
|
+
// No stereo offered, return the original answerSdp
|
|
3979
|
+
if (offeredStereoMids.size === 0)
|
|
3980
|
+
return answerSdp;
|
|
3981
|
+
const parsedAnswerSdp = parse(answerSdp);
|
|
3982
|
+
for (const media of parsedAnswerSdp.media) {
|
|
3983
|
+
if (media.type !== 'audio' || !offeredStereoMids.has(media.mid))
|
|
3984
|
+
continue;
|
|
3985
|
+
const opus = media.rtp.find((r) => r.codec === 'opus');
|
|
3986
|
+
if (!opus)
|
|
3987
|
+
continue;
|
|
3988
|
+
for (const fmtp of media.fmtp) {
|
|
3989
|
+
if (fmtp.payload === opus.payload && !fmtp.config.includes('stereo=1')) {
|
|
3990
|
+
fmtp.config += ';stereo=1';
|
|
3991
|
+
}
|
|
3992
|
+
}
|
|
3993
|
+
}
|
|
3994
|
+
return write(parsedAnswerSdp);
|
|
3995
|
+
};
|
|
3996
|
+
/**
|
|
3997
|
+
* Removes all codecs from the SDP except the specified codec.
|
|
3998
|
+
*
|
|
3999
|
+
* @param sdp the SDP to modify.
|
|
4000
|
+
* @param codecMimeTypeToKeep the codec mime type to keep (video/h264 or audio/opus).
|
|
4001
|
+
* @param fmtpProfileToKeep the fmtp profile to keep (e.g. 'profile-level-id=42e01f' or multiple segments like 'profile-level-id=64001f;packetization-mode=1').
|
|
4002
|
+
*/
|
|
4003
|
+
const removeCodecsExcept = (sdp, codecMimeTypeToKeep, fmtpProfileToKeep) => {
|
|
4004
|
+
const [kind, codec] = toMimeType(codecMimeTypeToKeep).split('/');
|
|
4005
|
+
if (!kind || !codec)
|
|
4006
|
+
return sdp;
|
|
4007
|
+
const parsed = parse(sdp);
|
|
4008
|
+
for (const media of parsed.media) {
|
|
4009
|
+
if (media.type !== kind)
|
|
4010
|
+
continue;
|
|
4011
|
+
// Build a set of payloads to KEEP: all payloads whose rtp.codec matches codec
|
|
4012
|
+
let payloadsToKeep = new Set();
|
|
4013
|
+
for (const rtp of media.rtp) {
|
|
4014
|
+
if (rtp.codec.toLowerCase() !== codec)
|
|
4015
|
+
continue;
|
|
4016
|
+
payloadsToKeep.add(rtp.payload);
|
|
4017
|
+
}
|
|
4018
|
+
// If a specific fmtp profile is requested, only keep payloads whose fmtp config matches it
|
|
4019
|
+
if (fmtpProfileToKeep) {
|
|
4020
|
+
const filtered = new Set();
|
|
4021
|
+
const required = new Set(fmtpProfileToKeep.split(';'));
|
|
4022
|
+
for (const fmtp of media.fmtp) {
|
|
4023
|
+
if (payloadsToKeep.has(fmtp.payload) &&
|
|
4024
|
+
required.difference(new Set(fmtp.config.split(';'))).size === 0) {
|
|
4025
|
+
filtered.add(fmtp.payload);
|
|
4026
|
+
}
|
|
4027
|
+
}
|
|
4028
|
+
payloadsToKeep = filtered;
|
|
4029
|
+
}
|
|
4030
|
+
// If no payloads to keep AND no fmtpProfile was specified, skip modifications (preserve SDP as-is)
|
|
4031
|
+
if (payloadsToKeep.size === 0 && !fmtpProfileToKeep)
|
|
4032
|
+
continue;
|
|
4033
|
+
// Keep RTX payloads that are associated with kept primary payloads via apt
|
|
4034
|
+
// RTX mappings look like: a=fmtp:<rtxPayload> apt=<primaryPayload>
|
|
4035
|
+
for (const fmtp of media.fmtp) {
|
|
4036
|
+
const matches = /\s*apt\s*=\s*(\d+)\s*/i.exec(fmtp.config);
|
|
4037
|
+
if (!matches)
|
|
4038
|
+
continue;
|
|
4039
|
+
const primaryPayloadApt = Number(matches[1]);
|
|
4040
|
+
if (!payloadsToKeep.has(primaryPayloadApt))
|
|
4041
|
+
continue;
|
|
4042
|
+
payloadsToKeep.add(fmtp.payload);
|
|
4043
|
+
}
|
|
4044
|
+
// Filter rtp, fmtp and rtcpFb entries
|
|
4045
|
+
media.rtp = media.rtp.filter((rtp) => payloadsToKeep.has(rtp.payload));
|
|
4046
|
+
media.fmtp = media.fmtp.filter((fmtp) => payloadsToKeep.has(fmtp.payload));
|
|
4047
|
+
media.rtcpFb = media.rtcpFb?.filter((fb) => typeof fb.payload === 'number' ? payloadsToKeep.has(fb.payload) : true);
|
|
4048
|
+
// Update the m= line payload list to only the kept payloads, preserving original order
|
|
4049
|
+
const payloads = [];
|
|
4050
|
+
for (const id of (media.payloads || '').split(/\s+/)) {
|
|
4051
|
+
const payload = Number(id);
|
|
4052
|
+
if (!payloadsToKeep.has(payload))
|
|
4053
|
+
continue;
|
|
4054
|
+
payloads.push(payload);
|
|
4055
|
+
}
|
|
4056
|
+
media.payloads = payloads.join(' ');
|
|
4057
|
+
}
|
|
4058
|
+
return write(parsed);
|
|
4059
|
+
};
|
|
4060
|
+
/**
|
|
4061
|
+
* Converts the given codec to a mime-type format when necessary.
|
|
4062
|
+
* e.g.: `vp9` -> `video/vp9`
|
|
4063
|
+
*/
|
|
4064
|
+
const toMimeType = (codec, kind = 'video') => codec.includes('/') ? codec : `${kind}/${codec}`;
|
|
4065
|
+
|
|
3932
4066
|
/**
|
|
3933
4067
|
* Returns a generic SDP for the given direction.
|
|
3934
4068
|
* We use this SDP to send it as part of our JoinRequest so that the SFU
|
|
3935
4069
|
* can use it to determine the client's codec capabilities.
|
|
3936
4070
|
*
|
|
3937
4071
|
* @param direction the direction of the transceiver.
|
|
4072
|
+
* @param codecToKeep the codec mime type to keep (video/h264 or audio/opus).
|
|
4073
|
+
* @param fmtpProfileToKeep optional fmtp profile to keep.
|
|
3938
4074
|
*/
|
|
3939
|
-
const getGenericSdp = async (direction) => {
|
|
4075
|
+
const getGenericSdp = async (direction, codecToKeep, fmtpProfileToKeep) => {
|
|
3940
4076
|
const tempPc = new RTCPeerConnection();
|
|
3941
4077
|
tempPc.addTransceiver('video', { direction });
|
|
3942
4078
|
tempPc.addTransceiver('audio', { direction });
|
|
3943
4079
|
const offer = await tempPc.createOffer();
|
|
3944
|
-
const sdp =
|
|
4080
|
+
const { sdp: baseSdp = '' } = offer;
|
|
4081
|
+
const sdp = codecToKeep
|
|
4082
|
+
? removeCodecsExcept(baseSdp, codecToKeep, fmtpProfileToKeep)
|
|
4083
|
+
: baseSdp;
|
|
3945
4084
|
tempPc.getTransceivers().forEach((t) => {
|
|
3946
4085
|
t.stop?.();
|
|
3947
4086
|
});
|
|
@@ -5837,7 +5976,7 @@ const getSdkVersion = (sdk) => {
|
|
|
5837
5976
|
return sdk ? `${sdk.major}.${sdk.minor}.${sdk.patch}` : '0.0.0-development';
|
|
5838
5977
|
};
|
|
5839
5978
|
|
|
5840
|
-
const version = "1.36.
|
|
5979
|
+
const version = "1.36.1";
|
|
5841
5980
|
const [major, minor, patch] = version.split('.');
|
|
5842
5981
|
let sdkInfo = {
|
|
5843
5982
|
type: SdkType.PLAIN_JAVASCRIPT,
|
|
@@ -6758,7 +6897,7 @@ class BasePeerConnection {
|
|
|
6758
6897
|
/**
|
|
6759
6898
|
* Constructs a new `BasePeerConnection` instance.
|
|
6760
6899
|
*/
|
|
6761
|
-
constructor(peerType, { sfuClient, connectionConfig, state, dispatcher, onReconnectionNeeded, tag, enableTracing, iceRestartDelay = 2500, }) {
|
|
6900
|
+
constructor(peerType, { sfuClient, connectionConfig, state, dispatcher, onReconnectionNeeded, tag, enableTracing, clientPublishOptions, iceRestartDelay = 2500, }) {
|
|
6762
6901
|
this.isIceRestarting = false;
|
|
6763
6902
|
this.isDisposed = false;
|
|
6764
6903
|
this.trackIdToTrackType = new Map();
|
|
@@ -6787,7 +6926,7 @@ class BasePeerConnection {
|
|
|
6787
6926
|
e.error.code === ErrorCode.PARTICIPANT_SIGNAL_LOST
|
|
6788
6927
|
? WebsocketReconnectStrategy.FAST
|
|
6789
6928
|
: WebsocketReconnectStrategy.REJOIN;
|
|
6790
|
-
this.onReconnectionNeeded?.(strategy, reason);
|
|
6929
|
+
this.onReconnectionNeeded?.(strategy, reason, this.peerType);
|
|
6791
6930
|
});
|
|
6792
6931
|
};
|
|
6793
6932
|
/**
|
|
@@ -6903,7 +7042,7 @@ class BasePeerConnection {
|
|
|
6903
7042
|
}
|
|
6904
7043
|
// we can't recover from a failed connection state (contrary to ICE)
|
|
6905
7044
|
if (state === 'failed') {
|
|
6906
|
-
this.onReconnectionNeeded?.(WebsocketReconnectStrategy.REJOIN, 'Connection failed');
|
|
7045
|
+
this.onReconnectionNeeded?.(WebsocketReconnectStrategy.REJOIN, 'Connection failed', this.peerType);
|
|
6907
7046
|
return;
|
|
6908
7047
|
}
|
|
6909
7048
|
this.handleConnectionStateUpdate(state);
|
|
@@ -6979,6 +7118,7 @@ class BasePeerConnection {
|
|
|
6979
7118
|
this.state = state;
|
|
6980
7119
|
this.dispatcher = dispatcher;
|
|
6981
7120
|
this.iceRestartDelay = iceRestartDelay;
|
|
7121
|
+
this.clientPublishOptions = clientPublishOptions;
|
|
6982
7122
|
this.onReconnectionNeeded = onReconnectionNeeded;
|
|
6983
7123
|
this.logger = videoLoggerSystem.getLogger(peerType === PeerType.SUBSCRIBER ? 'Subscriber' : 'Publisher', { tags: [tag] });
|
|
6984
7124
|
this.pc = this.createPeerConnection(connectionConfig);
|
|
@@ -7313,71 +7453,6 @@ const withSimulcastConstraints = (width, height, optimalVideoLayers, useSingleLa
|
|
|
7313
7453
|
}));
|
|
7314
7454
|
};
|
|
7315
7455
|
|
|
7316
|
-
/**
|
|
7317
|
-
* Extracts the mid from the transceiver or the SDP.
|
|
7318
|
-
*
|
|
7319
|
-
* @param transceiver the transceiver.
|
|
7320
|
-
* @param transceiverInitIndex the index of the transceiver in the transceiver's init array.
|
|
7321
|
-
* @param sdp the SDP.
|
|
7322
|
-
*/
|
|
7323
|
-
const extractMid = (transceiver, transceiverInitIndex, sdp) => {
|
|
7324
|
-
if (transceiver.mid)
|
|
7325
|
-
return transceiver.mid;
|
|
7326
|
-
if (!sdp)
|
|
7327
|
-
return String(transceiverInitIndex);
|
|
7328
|
-
const track = transceiver.sender.track;
|
|
7329
|
-
const parsedSdp = parse(sdp);
|
|
7330
|
-
const media = parsedSdp.media.find((m) => {
|
|
7331
|
-
return (m.type === track.kind &&
|
|
7332
|
-
// if `msid` is not present, we assume that the track is the first one
|
|
7333
|
-
(m.msid?.includes(track.id) ?? true));
|
|
7334
|
-
});
|
|
7335
|
-
if (typeof media?.mid !== 'undefined')
|
|
7336
|
-
return String(media.mid);
|
|
7337
|
-
if (transceiverInitIndex < 0)
|
|
7338
|
-
return '';
|
|
7339
|
-
return String(transceiverInitIndex);
|
|
7340
|
-
};
|
|
7341
|
-
/**
|
|
7342
|
-
* Enables stereo in the answer SDP based on the offered stereo in the offer SDP.
|
|
7343
|
-
*
|
|
7344
|
-
* @param offerSdp the offer SDP containing the stereo configuration.
|
|
7345
|
-
* @param answerSdp the answer SDP to be modified.
|
|
7346
|
-
*/
|
|
7347
|
-
const enableStereo = (offerSdp, answerSdp) => {
|
|
7348
|
-
const offeredStereoMids = new Set();
|
|
7349
|
-
const parsedOfferSdp = parse(offerSdp);
|
|
7350
|
-
for (const media of parsedOfferSdp.media) {
|
|
7351
|
-
if (media.type !== 'audio')
|
|
7352
|
-
continue;
|
|
7353
|
-
const opus = media.rtp.find((r) => r.codec === 'opus');
|
|
7354
|
-
if (!opus)
|
|
7355
|
-
continue;
|
|
7356
|
-
for (const fmtp of media.fmtp) {
|
|
7357
|
-
if (fmtp.payload === opus.payload && fmtp.config.includes('stereo=1')) {
|
|
7358
|
-
offeredStereoMids.add(media.mid);
|
|
7359
|
-
}
|
|
7360
|
-
}
|
|
7361
|
-
}
|
|
7362
|
-
// No stereo offered, return the original answerSdp
|
|
7363
|
-
if (offeredStereoMids.size === 0)
|
|
7364
|
-
return answerSdp;
|
|
7365
|
-
const parsedAnswerSdp = parse(answerSdp);
|
|
7366
|
-
for (const media of parsedAnswerSdp.media) {
|
|
7367
|
-
if (media.type !== 'audio' || !offeredStereoMids.has(media.mid))
|
|
7368
|
-
continue;
|
|
7369
|
-
const opus = media.rtp.find((r) => r.codec === 'opus');
|
|
7370
|
-
if (!opus)
|
|
7371
|
-
continue;
|
|
7372
|
-
for (const fmtp of media.fmtp) {
|
|
7373
|
-
if (fmtp.payload === opus.payload && !fmtp.config.includes('stereo=1')) {
|
|
7374
|
-
fmtp.config += ';stereo=1';
|
|
7375
|
-
}
|
|
7376
|
-
}
|
|
7377
|
-
}
|
|
7378
|
-
return write(parsedAnswerSdp);
|
|
7379
|
-
};
|
|
7380
|
-
|
|
7381
7456
|
/**
|
|
7382
7457
|
* The `Publisher` is responsible for publishing/unpublishing media streams to/from the SFU
|
|
7383
7458
|
*
|
|
@@ -7387,7 +7462,7 @@ class Publisher extends BasePeerConnection {
|
|
|
7387
7462
|
/**
|
|
7388
7463
|
* Constructs a new `Publisher` instance.
|
|
7389
7464
|
*/
|
|
7390
|
-
constructor(
|
|
7465
|
+
constructor(baseOptions, publishOptions) {
|
|
7391
7466
|
super(PeerType.PUBLISHER_UNSPECIFIED, baseOptions);
|
|
7392
7467
|
this.transceiverCache = new TransceiverCache();
|
|
7393
7468
|
this.clonedTracks = new Set();
|
|
@@ -7646,7 +7721,11 @@ class Publisher extends BasePeerConnection {
|
|
|
7646
7721
|
try {
|
|
7647
7722
|
this.isIceRestarting = options?.iceRestart ?? false;
|
|
7648
7723
|
await this.pc.setLocalDescription(offer);
|
|
7649
|
-
const { sdp = '' } = offer;
|
|
7724
|
+
const { sdp: baseSdp = '' } = offer;
|
|
7725
|
+
const { dangerouslyForceCodec, fmtpLine } = this.clientPublishOptions || {};
|
|
7726
|
+
const sdp = dangerouslyForceCodec
|
|
7727
|
+
? removeCodecsExcept(baseSdp, dangerouslyForceCodec, fmtpLine)
|
|
7728
|
+
: baseSdp;
|
|
7650
7729
|
const { response } = await this.sfuClient.setPublisher({ sdp, tracks });
|
|
7651
7730
|
if (response.error)
|
|
7652
7731
|
throw new NegotiationError(response.error);
|
|
@@ -7876,6 +7955,10 @@ class Subscriber extends BasePeerConnection {
|
|
|
7876
7955
|
const answer = await this.pc.createAnswer();
|
|
7877
7956
|
if (answer.sdp) {
|
|
7878
7957
|
answer.sdp = enableStereo(subscriberOffer.sdp, answer.sdp);
|
|
7958
|
+
const { dangerouslyForceCodec, subscriberFmtpLine } = this.clientPublishOptions || {};
|
|
7959
|
+
if (dangerouslyForceCodec) {
|
|
7960
|
+
answer.sdp = removeCodecsExcept(answer.sdp, dangerouslyForceCodec, subscriberFmtpLine);
|
|
7961
|
+
}
|
|
7879
7962
|
}
|
|
7880
7963
|
await this.pc.setLocalDescription(answer);
|
|
7881
7964
|
await this.sfuClient.sendAnswer({
|
|
@@ -12216,9 +12299,10 @@ class Call {
|
|
|
12216
12299
|
// prepare a generic SDP and send it to the SFU.
|
|
12217
12300
|
// these are throw-away SDPs that the SFU will use to determine
|
|
12218
12301
|
// the capabilities of the client (codec support, etc.)
|
|
12302
|
+
const { dangerouslyForceCodec, fmtpLine, subscriberFmtpLine } = this.clientPublishOptions || {};
|
|
12219
12303
|
const [subscriberSdp, publisherSdp] = await Promise.all([
|
|
12220
|
-
getGenericSdp('recvonly'),
|
|
12221
|
-
getGenericSdp('sendonly'),
|
|
12304
|
+
getGenericSdp('recvonly', dangerouslyForceCodec, subscriberFmtpLine),
|
|
12305
|
+
getGenericSdp('sendonly', dangerouslyForceCodec, fmtpLine),
|
|
12222
12306
|
]);
|
|
12223
12307
|
const isReconnecting = this.reconnectStrategy !== WebsocketReconnectStrategy.UNSPECIFIED;
|
|
12224
12308
|
const reconnectDetails = isReconnecting
|
|
@@ -12410,20 +12494,22 @@ class Call {
|
|
|
12410
12494
|
if (closePreviousInstances && this.subscriber) {
|
|
12411
12495
|
this.subscriber.dispose();
|
|
12412
12496
|
}
|
|
12413
|
-
|
|
12497
|
+
const basePeerConnectionOptions = {
|
|
12414
12498
|
sfuClient,
|
|
12415
12499
|
dispatcher: this.dispatcher,
|
|
12416
12500
|
state: this.state,
|
|
12417
12501
|
connectionConfig,
|
|
12418
12502
|
tag: sfuClient.tag,
|
|
12419
12503
|
enableTracing,
|
|
12420
|
-
|
|
12504
|
+
clientPublishOptions: this.clientPublishOptions,
|
|
12505
|
+
onReconnectionNeeded: (kind, reason, peerType) => {
|
|
12421
12506
|
this.reconnect(kind, reason).catch((err) => {
|
|
12422
|
-
const message = `[Reconnect] Error reconnecting after a
|
|
12507
|
+
const message = `[Reconnect] Error reconnecting, after a ${PeerType[peerType]} error: ${reason}`;
|
|
12423
12508
|
this.logger.warn(message, err);
|
|
12424
12509
|
});
|
|
12425
12510
|
},
|
|
12426
|
-
}
|
|
12511
|
+
};
|
|
12512
|
+
this.subscriber = new Subscriber(basePeerConnectionOptions);
|
|
12427
12513
|
// anonymous users can't publish anything hence, there is no need
|
|
12428
12514
|
// to create Publisher Peer Connection for them
|
|
12429
12515
|
const isAnonymous = this.streamClient.user?.type === 'anonymous';
|
|
@@ -12431,21 +12517,7 @@ class Call {
|
|
|
12431
12517
|
if (closePreviousInstances && this.publisher) {
|
|
12432
12518
|
this.publisher.dispose();
|
|
12433
12519
|
}
|
|
12434
|
-
this.publisher = new Publisher(
|
|
12435
|
-
sfuClient,
|
|
12436
|
-
dispatcher: this.dispatcher,
|
|
12437
|
-
state: this.state,
|
|
12438
|
-
connectionConfig,
|
|
12439
|
-
publishOptions,
|
|
12440
|
-
tag: sfuClient.tag,
|
|
12441
|
-
enableTracing,
|
|
12442
|
-
onReconnectionNeeded: (kind, reason) => {
|
|
12443
|
-
this.reconnect(kind, reason).catch((err) => {
|
|
12444
|
-
const message = `[Reconnect] Error reconnecting after a publisher error: ${reason}`;
|
|
12445
|
-
this.logger.warn(message, err);
|
|
12446
|
-
});
|
|
12447
|
-
},
|
|
12448
|
-
});
|
|
12520
|
+
this.publisher = new Publisher(basePeerConnectionOptions, publishOptions);
|
|
12449
12521
|
}
|
|
12450
12522
|
this.statsReporter?.stop();
|
|
12451
12523
|
if (this.statsReportingIntervalInMs > 0) {
|
|
@@ -14807,7 +14879,7 @@ class StreamClient {
|
|
|
14807
14879
|
this.getUserAgent = () => {
|
|
14808
14880
|
if (!this.cachedUserAgent) {
|
|
14809
14881
|
const { clientAppIdentifier = {} } = this.options;
|
|
14810
|
-
const { sdkName = 'js', sdkVersion = "1.36.
|
|
14882
|
+
const { sdkName = 'js', sdkVersion = "1.36.1", ...extras } = clientAppIdentifier;
|
|
14811
14883
|
this.cachedUserAgent = [
|
|
14812
14884
|
`stream-video-${sdkName}-v${sdkVersion}`,
|
|
14813
14885
|
...Object.entries(extras).map(([key, value]) => `${key}=${value}`),
|