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