@stream-io/video-client 1.38.2 → 1.39.2
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 +25 -0
- package/dist/index.browser.es.js +93 -20
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +93 -20
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +93 -20
- package/dist/index.es.js.map +1 -1
- package/dist/src/StreamVideoClient.d.ts +5 -0
- package/dist/src/rtc/helpers/sdp.d.ts +1 -0
- package/dist/src/types.d.ts +8 -0
- package/package.json +8 -3
- package/src/StreamVideoClient.ts +40 -6
- package/src/__tests__/StreamVideoClient.test.ts +106 -0
- package/src/devices/devices.ts +3 -1
- package/src/rpc/__tests__/createClient.test.ts +41 -0
- package/src/rpc/createClient.ts +10 -13
- package/src/rtc/Publisher.ts +23 -4
- package/src/rtc/helpers/__tests__/sdp.startBitrate.test.ts +112 -0
- package/src/rtc/helpers/sdp.ts +40 -0
- package/src/types.ts +8 -0
package/dist/index.es.js
CHANGED
|
@@ -3731,22 +3731,23 @@ const withRequestLogger = (logger, level) => {
|
|
|
3731
3731
|
};
|
|
3732
3732
|
};
|
|
3733
3733
|
const withRequestTracer = (trace) => {
|
|
3734
|
+
const exclusions = new Set(['SendStats']);
|
|
3735
|
+
const responseInclusions = new Set(['SetPublisher']);
|
|
3734
3736
|
const traceError = (name, input, err) => trace(`${name}OnFailure`, [err, input]);
|
|
3735
|
-
const exclusions = {
|
|
3736
|
-
SendStats: true,
|
|
3737
|
-
};
|
|
3738
3737
|
return {
|
|
3739
3738
|
interceptUnary(next, method, input, options) {
|
|
3740
|
-
|
|
3739
|
+
const name = method.name;
|
|
3740
|
+
if (exclusions.has(name))
|
|
3741
3741
|
return next(method, input, options);
|
|
3742
|
-
|
|
3743
|
-
trace(method.name, input);
|
|
3742
|
+
trace(name, input);
|
|
3744
3743
|
const unaryCall = next(method, input, options);
|
|
3745
3744
|
unaryCall.then((invocation) => {
|
|
3746
|
-
const
|
|
3747
|
-
if (
|
|
3748
|
-
traceError(
|
|
3749
|
-
|
|
3745
|
+
const response = invocation.response;
|
|
3746
|
+
if (response.error)
|
|
3747
|
+
traceError(name, input, response.error);
|
|
3748
|
+
if (responseInclusions.has(name))
|
|
3749
|
+
trace(`${name}Response`, response);
|
|
3750
|
+
}, (error) => traceError(name, input, error));
|
|
3750
3751
|
return unaryCall;
|
|
3751
3752
|
},
|
|
3752
3753
|
};
|
|
@@ -3948,6 +3949,37 @@ const extractMid = (transceiver, transceiverInitIndex, sdp) => {
|
|
|
3948
3949
|
return '';
|
|
3949
3950
|
return String(transceiverInitIndex);
|
|
3950
3951
|
};
|
|
3952
|
+
/*
|
|
3953
|
+
* Sets the start bitrate for the VP9 and H264 codecs in the SDP.
|
|
3954
|
+
*
|
|
3955
|
+
* @param offerSdp the offer SDP to modify.
|
|
3956
|
+
* @param startBitrate the start bitrate in kbps to set. Default is 1000 kbps.
|
|
3957
|
+
*/
|
|
3958
|
+
const setStartBitrate = (offerSdp, maxBitrateKbps, startBitrateFactor, targetMid) => {
|
|
3959
|
+
// start bitrate should be between 300kbps and max-bitrate-kbps
|
|
3960
|
+
const startBitrate = Math.max(Math.min(maxBitrateKbps, startBitrateFactor * maxBitrateKbps), 300);
|
|
3961
|
+
const parsedSdp = parse(offerSdp);
|
|
3962
|
+
const targetCodecs = new Set(['av1', 'vp9', 'h264']);
|
|
3963
|
+
for (const media of parsedSdp.media) {
|
|
3964
|
+
if (media.type !== 'video')
|
|
3965
|
+
continue;
|
|
3966
|
+
if (String(media.mid) !== targetMid)
|
|
3967
|
+
continue;
|
|
3968
|
+
for (const rtp of media.rtp) {
|
|
3969
|
+
if (!targetCodecs.has(rtp.codec.toLowerCase()))
|
|
3970
|
+
continue;
|
|
3971
|
+
for (const fmtp of media.fmtp) {
|
|
3972
|
+
if (fmtp.payload === rtp.payload) {
|
|
3973
|
+
if (!fmtp.config.includes('x-google-start-bitrate')) {
|
|
3974
|
+
fmtp.config += `;x-google-start-bitrate=${startBitrate}`;
|
|
3975
|
+
}
|
|
3976
|
+
break;
|
|
3977
|
+
}
|
|
3978
|
+
}
|
|
3979
|
+
}
|
|
3980
|
+
}
|
|
3981
|
+
return write(parsedSdp);
|
|
3982
|
+
};
|
|
3951
3983
|
/**
|
|
3952
3984
|
* Enables stereo in the answer SDP based on the offered stereo in the offer SDP.
|
|
3953
3985
|
*
|
|
@@ -5994,7 +6026,7 @@ const getSdkVersion = (sdk) => {
|
|
|
5994
6026
|
return sdk ? `${sdk.major}.${sdk.minor}.${sdk.patch}` : '0.0.0-development';
|
|
5995
6027
|
};
|
|
5996
6028
|
|
|
5997
|
-
const version = "1.
|
|
6029
|
+
const version = "1.39.2";
|
|
5998
6030
|
const [major, minor, patch] = version.split('.');
|
|
5999
6031
|
let sdkInfo = {
|
|
6000
6032
|
type: SdkType.PLAIN_JAVASCRIPT,
|
|
@@ -7822,10 +7854,25 @@ class Publisher extends BasePeerConnection {
|
|
|
7822
7854
|
this.isIceRestarting = options?.iceRestart ?? false;
|
|
7823
7855
|
await this.pc.setLocalDescription(offer);
|
|
7824
7856
|
const { sdp: baseSdp = '' } = offer;
|
|
7825
|
-
const { dangerouslyForceCodec, fmtpLine } = this.clientPublishOptions || {};
|
|
7826
|
-
|
|
7857
|
+
const { dangerouslyForceCodec, dangerouslySetStartBitrateFactor, fmtpLine, } = this.clientPublishOptions || {};
|
|
7858
|
+
let sdp = dangerouslyForceCodec
|
|
7827
7859
|
? removeCodecsExcept(baseSdp, dangerouslyForceCodec, fmtpLine)
|
|
7828
7860
|
: baseSdp;
|
|
7861
|
+
if (dangerouslySetStartBitrateFactor) {
|
|
7862
|
+
this.transceiverCache.items().forEach((t) => {
|
|
7863
|
+
if (t.publishOption.trackType !== TrackType.VIDEO)
|
|
7864
|
+
return;
|
|
7865
|
+
const maxBitrateBps = t.publishOption.bitrate;
|
|
7866
|
+
const trackId = t.transceiver.sender.track?.id;
|
|
7867
|
+
if (!trackId)
|
|
7868
|
+
return;
|
|
7869
|
+
const mid = tracks.find((x) => x.trackId === trackId)?.mid;
|
|
7870
|
+
if (!mid)
|
|
7871
|
+
return;
|
|
7872
|
+
sdp = setStartBitrate(sdp, maxBitrateBps / 1000, // convert to kbps
|
|
7873
|
+
dangerouslySetStartBitrateFactor, mid);
|
|
7874
|
+
});
|
|
7875
|
+
}
|
|
7829
7876
|
const { response } = await this.sfuClient.setPublisher({ sdp, tracks });
|
|
7830
7877
|
if (response.error)
|
|
7831
7878
|
throw new NegotiationError(response.error);
|
|
@@ -9931,6 +9978,9 @@ const getDevices = (permission, kind, tracer) => {
|
|
|
9931
9978
|
const checkIfAudioOutputChangeSupported = () => {
|
|
9932
9979
|
if (typeof document === 'undefined')
|
|
9933
9980
|
return false;
|
|
9981
|
+
// Safari uses WebAudio API for playing audio, so we check the AudioContext prototype
|
|
9982
|
+
if (isSafari())
|
|
9983
|
+
return 'setSinkId' in AudioContext.prototype;
|
|
9934
9984
|
const element = document.createElement('audio');
|
|
9935
9985
|
return 'setSinkId' in element;
|
|
9936
9986
|
};
|
|
@@ -14991,7 +15041,7 @@ class StreamClient {
|
|
|
14991
15041
|
this.getUserAgent = () => {
|
|
14992
15042
|
if (!this.cachedUserAgent) {
|
|
14993
15043
|
const { clientAppIdentifier = {} } = this.options;
|
|
14994
|
-
const { sdkName = 'js', sdkVersion = "1.
|
|
15044
|
+
const { sdkName = 'js', sdkVersion = "1.39.2", ...extras } = clientAppIdentifier;
|
|
14995
15045
|
this.cachedUserAgent = [
|
|
14996
15046
|
`stream-video-${sdkName}-v${sdkVersion}`,
|
|
14997
15047
|
...Object.entries(extras).map(([key, value]) => `${key}=${value}`),
|
|
@@ -15195,12 +15245,7 @@ class StreamVideoClient {
|
|
|
15195
15245
|
.map((call) => call.cid);
|
|
15196
15246
|
if (callsToReWatch.length <= 0)
|
|
15197
15247
|
return;
|
|
15198
|
-
this.
|
|
15199
|
-
this.queryCalls({
|
|
15200
|
-
watch: true,
|
|
15201
|
-
filter_conditions: { cid: { $in: callsToReWatch } },
|
|
15202
|
-
sort: [{ field: 'cid', direction: 1 }],
|
|
15203
|
-
}).catch((err) => {
|
|
15248
|
+
this.rewatchCalls(callsToReWatch).catch((err) => {
|
|
15204
15249
|
this.logger.error('Failed to re-watch calls', err);
|
|
15205
15250
|
});
|
|
15206
15251
|
}));
|
|
@@ -15264,6 +15309,34 @@ class StreamVideoClient {
|
|
|
15264
15309
|
this.logger.error(`Failed to init call from event ${e.type}`, err);
|
|
15265
15310
|
}
|
|
15266
15311
|
};
|
|
15312
|
+
/**
|
|
15313
|
+
* Rewatches the given calls with retry logic.
|
|
15314
|
+
* @param callsToReWatch array of call IDs to rewatch
|
|
15315
|
+
*/
|
|
15316
|
+
this.rewatchCalls = async (callsToReWatch) => {
|
|
15317
|
+
this.logger.info(`Rewatching calls ${callsToReWatch.join(', ')}`);
|
|
15318
|
+
const maxRetries = 3;
|
|
15319
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
15320
|
+
try {
|
|
15321
|
+
this.logger.info(`Rewatching calls ${callsToReWatch.join(', ')} attempt ${attempt + 1}`);
|
|
15322
|
+
await this.queryCalls({
|
|
15323
|
+
watch: true,
|
|
15324
|
+
filter_conditions: { cid: { $in: callsToReWatch } },
|
|
15325
|
+
});
|
|
15326
|
+
return;
|
|
15327
|
+
}
|
|
15328
|
+
catch (err) {
|
|
15329
|
+
if (err instanceof ErrorFromResponse && err.unrecoverable) {
|
|
15330
|
+
throw err;
|
|
15331
|
+
}
|
|
15332
|
+
this.logger.warn(`Failed to re-watch calls (attempt ${attempt + 1}/${maxRetries}), retrying.`, err);
|
|
15333
|
+
if (attempt === maxRetries - 1) {
|
|
15334
|
+
throw err;
|
|
15335
|
+
}
|
|
15336
|
+
}
|
|
15337
|
+
await sleep(retryInterval(attempt));
|
|
15338
|
+
}
|
|
15339
|
+
};
|
|
15267
15340
|
/**
|
|
15268
15341
|
* Connects the given user to the client.
|
|
15269
15342
|
* Only one user can connect at a time, if you want to change users, call `disconnectUser` before connecting a new user.
|