@stream-io/video-client 1.30.1 → 1.32.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 +47 -137
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +47 -137
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +47 -137
- package/dist/index.es.js.map +1 -1
- package/dist/src/StreamVideoClient.d.ts +2 -0
- package/dist/src/coordinator/connection/types.d.ts +4 -0
- package/dist/src/gen/video/sfu/event/events.d.ts +8 -0
- package/dist/src/timers/index.d.ts +2 -21
- package/package.json +3 -3
- package/src/Call.ts +6 -6
- package/src/StreamVideoClient.ts +42 -3
- package/src/coordinator/connection/types.ts +5 -0
- package/src/gen/video/sfu/event/events.ts +14 -0
- package/src/timers/index.ts +2 -127
- package/dist/src/timers/types.d.ts +0 -12
- package/dist/src/timers/worker.build.d.ts +0 -3
- package/dist/src/timers/worker.d.ts +0 -1
- package/src/timers/types.ts +0 -15
- package/src/timers/worker.build.ts +0 -28
- package/src/timers/worker.ts +0 -36
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).
|
|
4
4
|
|
|
5
|
+
## [1.32.0](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.31.0...@stream-io/video-client-1.32.0) (2025-09-29)
|
|
6
|
+
|
|
7
|
+
### Features
|
|
8
|
+
|
|
9
|
+
- **react-native:** reject call when busy ([#1856](https://github.com/GetStream/stream-video-js/issues/1856)) ([b60bc7c](https://github.com/GetStream/stream-video-js/commit/b60bc7cd2dc2e09d52496d7b5cb593cac4b89485))
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
- restore calling state after unrecoverable join fail ([#1935](https://github.com/GetStream/stream-video-js/issues/1935)) ([8ab0168](https://github.com/GetStream/stream-video-js/commit/8ab01680d01cc47f9cf48078634358507f0c109d))
|
|
14
|
+
- send unifiedSessionId in the initial join request ([#1934](https://github.com/GetStream/stream-video-js/issues/1934)) ([e6a533d](https://github.com/GetStream/stream-video-js/commit/e6a533d7e926086ac5930ebfb4648dade449d15a))
|
|
15
|
+
|
|
16
|
+
## [1.31.0](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.30.1...@stream-io/video-client-1.31.0) (2025-09-17)
|
|
17
|
+
|
|
18
|
+
### Features
|
|
19
|
+
|
|
20
|
+
- introduce @stream-io/worker-timers ([94c962b](https://github.com/GetStream/stream-video-js/commit/94c962b2c5f731c152771b7803a59664fa925477))
|
|
21
|
+
|
|
22
|
+
### Bug Fixes
|
|
23
|
+
|
|
24
|
+
- **video-filters:** prevent background tab throttling ([#1920](https://github.com/GetStream/stream-video-js/issues/1920)) ([f93d5cc](https://github.com/GetStream/stream-video-js/commit/f93d5cc5785957c7f181fcaf689ec366df9e646b))
|
|
25
|
+
|
|
5
26
|
## [1.30.1](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.30.0...@stream-io/video-client-1.30.1) (2025-09-16)
|
|
6
27
|
|
|
7
28
|
### Bug Fixes
|
package/dist/index.browser.es.js
CHANGED
|
@@ -7,6 +7,7 @@ import { TwirpFetchTransport, TwirpErrorCode } from '@protobuf-ts/twirp-transpor
|
|
|
7
7
|
import { ReplaySubject, combineLatest, BehaviorSubject, shareReplay, map, distinctUntilChanged, startWith, takeWhile, distinctUntilKeyChanged, fromEventPattern, concatMap, merge, from, fromEvent, tap, debounceTime, pairwise, of } from 'rxjs';
|
|
8
8
|
import { UAParser } from 'ua-parser-js';
|
|
9
9
|
import { parse, write } from 'sdp-transform';
|
|
10
|
+
import { WorkerTimer } from '@stream-io/worker-timer';
|
|
10
11
|
|
|
11
12
|
/* tslint:disable */
|
|
12
13
|
/**
|
|
@@ -2948,6 +2949,12 @@ class JoinRequest$Type extends MessageType {
|
|
|
2948
2949
|
super('stream.video.sfu.event.JoinRequest', [
|
|
2949
2950
|
{ no: 1, name: 'token', kind: 'scalar', T: 9 /*ScalarType.STRING*/ },
|
|
2950
2951
|
{ no: 2, name: 'session_id', kind: 'scalar', T: 9 /*ScalarType.STRING*/ },
|
|
2952
|
+
{
|
|
2953
|
+
no: 13,
|
|
2954
|
+
name: 'unified_session_id',
|
|
2955
|
+
kind: 'scalar',
|
|
2956
|
+
T: 9 /*ScalarType.STRING*/,
|
|
2957
|
+
},
|
|
2951
2958
|
{
|
|
2952
2959
|
no: 3,
|
|
2953
2960
|
name: 'subscriber_sdp',
|
|
@@ -5757,7 +5764,7 @@ const getSdkVersion = (sdk) => {
|
|
|
5757
5764
|
return sdk ? `${sdk.major}.${sdk.minor}.${sdk.patch}` : '0.0.0-development';
|
|
5758
5765
|
};
|
|
5759
5766
|
|
|
5760
|
-
const version = "1.
|
|
5767
|
+
const version = "1.32.0";
|
|
5761
5768
|
const [major, minor, patch] = version.split('.');
|
|
5762
5769
|
let sdkInfo = {
|
|
5763
5770
|
type: SdkType.PLAIN_JAVASCRIPT,
|
|
@@ -7894,137 +7901,12 @@ function lazy(factory) {
|
|
|
7894
7901
|
};
|
|
7895
7902
|
}
|
|
7896
7903
|
|
|
7897
|
-
// Do not modify this file manually. Instead, edit worker.ts
|
|
7898
|
-
// and the run ./generate-timer-worker.sh
|
|
7899
|
-
const timerWorker = {
|
|
7900
|
-
src: `const timerIdMapping = new Map();
|
|
7901
|
-
self.addEventListener('message', (event) => {
|
|
7902
|
-
const request = event.data;
|
|
7903
|
-
switch (request.type) {
|
|
7904
|
-
case 'setTimeout':
|
|
7905
|
-
case 'setInterval':
|
|
7906
|
-
timerIdMapping.set(request.id, (request.type === 'setTimeout' ? setTimeout : setInterval)(() => {
|
|
7907
|
-
tick(request.id);
|
|
7908
|
-
if (request.type === 'setTimeout') {
|
|
7909
|
-
timerIdMapping.delete(request.id);
|
|
7910
|
-
}
|
|
7911
|
-
}, request.timeout));
|
|
7912
|
-
break;
|
|
7913
|
-
case 'clearTimeout':
|
|
7914
|
-
case 'clearInterval':
|
|
7915
|
-
(request.type === 'clearTimeout' ? clearTimeout : clearInterval)(timerIdMapping.get(request.id));
|
|
7916
|
-
timerIdMapping.delete(request.id);
|
|
7917
|
-
break;
|
|
7918
|
-
}
|
|
7919
|
-
});
|
|
7920
|
-
function tick(id) {
|
|
7921
|
-
const message = { type: 'tick', id };
|
|
7922
|
-
self.postMessage(message);
|
|
7923
|
-
}`,
|
|
7924
|
-
};
|
|
7925
|
-
|
|
7926
|
-
class TimerWorker {
|
|
7927
|
-
constructor() {
|
|
7928
|
-
this.currentTimerId = 1;
|
|
7929
|
-
this.callbacks = new Map();
|
|
7930
|
-
this.fallback = false;
|
|
7931
|
-
}
|
|
7932
|
-
setup({ useTimerWorker = true } = {}) {
|
|
7933
|
-
if (!useTimerWorker) {
|
|
7934
|
-
this.fallback = true;
|
|
7935
|
-
return;
|
|
7936
|
-
}
|
|
7937
|
-
try {
|
|
7938
|
-
const source = timerWorker.src;
|
|
7939
|
-
const blob = new Blob([source], {
|
|
7940
|
-
type: 'application/javascript; charset=utf-8',
|
|
7941
|
-
});
|
|
7942
|
-
const script = URL.createObjectURL(blob);
|
|
7943
|
-
this.worker = new Worker(script, { name: 'str-timer-worker' });
|
|
7944
|
-
this.worker.addEventListener('message', (event) => {
|
|
7945
|
-
const { type, id } = event.data;
|
|
7946
|
-
if (type === 'tick') {
|
|
7947
|
-
this.callbacks.get(id)?.();
|
|
7948
|
-
}
|
|
7949
|
-
});
|
|
7950
|
-
}
|
|
7951
|
-
catch (err) {
|
|
7952
|
-
getLogger(['timer-worker'])('error', err);
|
|
7953
|
-
this.fallback = true;
|
|
7954
|
-
}
|
|
7955
|
-
}
|
|
7956
|
-
destroy() {
|
|
7957
|
-
this.callbacks.clear();
|
|
7958
|
-
this.worker?.terminate();
|
|
7959
|
-
this.worker = undefined;
|
|
7960
|
-
this.fallback = false;
|
|
7961
|
-
}
|
|
7962
|
-
get ready() {
|
|
7963
|
-
return this.fallback || Boolean(this.worker);
|
|
7964
|
-
}
|
|
7965
|
-
setInterval(callback, timeout) {
|
|
7966
|
-
return this.setTimer('setInterval', callback, timeout);
|
|
7967
|
-
}
|
|
7968
|
-
clearInterval(id) {
|
|
7969
|
-
this.clearTimer('clearInterval', id);
|
|
7970
|
-
}
|
|
7971
|
-
setTimeout(callback, timeout) {
|
|
7972
|
-
return this.setTimer('setTimeout', callback, timeout);
|
|
7973
|
-
}
|
|
7974
|
-
clearTimeout(id) {
|
|
7975
|
-
this.clearTimer('clearTimeout', id);
|
|
7976
|
-
}
|
|
7977
|
-
setTimer(type, callback, timeout) {
|
|
7978
|
-
if (!this.ready) {
|
|
7979
|
-
this.setup();
|
|
7980
|
-
}
|
|
7981
|
-
if (this.fallback) {
|
|
7982
|
-
return (type === 'setTimeout' ? setTimeout : setInterval)(callback, timeout);
|
|
7983
|
-
}
|
|
7984
|
-
const id = this.getTimerId();
|
|
7985
|
-
this.callbacks.set(id, () => {
|
|
7986
|
-
callback();
|
|
7987
|
-
// Timeouts are one-off operations, so no need to keep callback reference
|
|
7988
|
-
// after timer has fired
|
|
7989
|
-
if (type === 'setTimeout') {
|
|
7990
|
-
this.callbacks.delete(id);
|
|
7991
|
-
}
|
|
7992
|
-
});
|
|
7993
|
-
this.sendMessage({ type, id, timeout });
|
|
7994
|
-
return id;
|
|
7995
|
-
}
|
|
7996
|
-
clearTimer(type, id) {
|
|
7997
|
-
if (!id) {
|
|
7998
|
-
return;
|
|
7999
|
-
}
|
|
8000
|
-
if (!this.ready) {
|
|
8001
|
-
this.setup();
|
|
8002
|
-
}
|
|
8003
|
-
if (this.fallback) {
|
|
8004
|
-
(type === 'clearTimeout' ? clearTimeout : clearInterval)(id);
|
|
8005
|
-
return;
|
|
8006
|
-
}
|
|
8007
|
-
this.callbacks.delete(id);
|
|
8008
|
-
this.sendMessage({ type, id });
|
|
8009
|
-
}
|
|
8010
|
-
getTimerId() {
|
|
8011
|
-
return this.currentTimerId++;
|
|
8012
|
-
}
|
|
8013
|
-
sendMessage(message) {
|
|
8014
|
-
if (!this.worker) {
|
|
8015
|
-
throw new Error("Cannot use timer worker before it's set up");
|
|
8016
|
-
}
|
|
8017
|
-
this.worker.postMessage(message);
|
|
8018
|
-
}
|
|
8019
|
-
}
|
|
8020
7904
|
let timerWorkerEnabled = false;
|
|
8021
7905
|
const enableTimerWorker = () => {
|
|
8022
7906
|
timerWorkerEnabled = true;
|
|
8023
7907
|
};
|
|
8024
7908
|
const getTimers = lazy(() => {
|
|
8025
|
-
|
|
8026
|
-
instance.setup({ useTimerWorker: timerWorkerEnabled });
|
|
8027
|
-
return instance;
|
|
7909
|
+
return new WorkerTimer({ useWorker: timerWorkerEnabled });
|
|
8028
7910
|
});
|
|
8029
7911
|
|
|
8030
7912
|
/**
|
|
@@ -11993,7 +11875,6 @@ class Call {
|
|
|
11993
11875
|
if ([CallingState.JOINED, CallingState.JOINING].includes(callingState)) {
|
|
11994
11876
|
throw new Error(`Illegal State: call.join() shall be called only once`);
|
|
11995
11877
|
}
|
|
11996
|
-
this.state.setCallingState(CallingState.JOINING);
|
|
11997
11878
|
// we will count the number of join failures per SFU.
|
|
11998
11879
|
// once the number of failures reaches 2, we will piggyback on the `migrating_from`
|
|
11999
11880
|
// field to force the coordinator to provide us another SFU
|
|
@@ -12022,8 +11903,6 @@ class Call {
|
|
|
12022
11903
|
joinData.migrating_from = sfuId;
|
|
12023
11904
|
}
|
|
12024
11905
|
if (attempt === maxJoinRetries - 1) {
|
|
12025
|
-
// restore the previous call state if the join-flow fails
|
|
12026
|
-
this.state.setCallingState(callingState);
|
|
12027
11906
|
throw err;
|
|
12028
11907
|
}
|
|
12029
11908
|
}
|
|
@@ -12083,6 +11962,7 @@ class Call {
|
|
|
12083
11962
|
})
|
|
12084
11963
|
: previousSfuClient;
|
|
12085
11964
|
this.sfuClient = sfuClient;
|
|
11965
|
+
this.unifiedSessionId ?? (this.unifiedSessionId = sfuClient.sessionId);
|
|
12086
11966
|
this.dynascaleManager.setSfuClient(sfuClient);
|
|
12087
11967
|
const clientDetails = await getClientDetails();
|
|
12088
11968
|
// we don't need to send JoinRequest if we are re-using an existing healthy SFU client
|
|
@@ -12106,6 +11986,7 @@ class Call {
|
|
|
12106
11986
|
: [];
|
|
12107
11987
|
try {
|
|
12108
11988
|
const { callState, fastReconnectDeadlineSeconds, publishOptions } = await sfuClient.join({
|
|
11989
|
+
unifiedSessionId: this.unifiedSessionId,
|
|
12109
11990
|
subscriberSdp,
|
|
12110
11991
|
publisherSdp,
|
|
12111
11992
|
clientDetails,
|
|
@@ -12151,6 +12032,7 @@ class Call {
|
|
|
12151
12032
|
statsOptions,
|
|
12152
12033
|
publishOptions: this.currentPublishOptions || [],
|
|
12153
12034
|
closePreviousInstances: !performingMigration,
|
|
12035
|
+
unifiedSessionId: this.unifiedSessionId,
|
|
12154
12036
|
});
|
|
12155
12037
|
}
|
|
12156
12038
|
// make sure we only track connection timing if we are not calling this method as part of a reconnection flow
|
|
@@ -12277,7 +12159,7 @@ class Call {
|
|
|
12277
12159
|
* @internal
|
|
12278
12160
|
*/
|
|
12279
12161
|
this.initPublisherAndSubscriber = (opts) => {
|
|
12280
|
-
const { sfuClient, connectionConfig, clientDetails, statsOptions, publishOptions, closePreviousInstances, } = opts;
|
|
12162
|
+
const { sfuClient, connectionConfig, clientDetails, statsOptions, publishOptions, closePreviousInstances, unifiedSessionId, } = opts;
|
|
12281
12163
|
const { enable_rtc_stats: enableTracing } = statsOptions;
|
|
12282
12164
|
if (closePreviousInstances && this.subscriber) {
|
|
12283
12165
|
this.subscriber.dispose();
|
|
@@ -12332,7 +12214,6 @@ class Call {
|
|
|
12332
12214
|
this.tracer.setEnabled(enableTracing);
|
|
12333
12215
|
this.sfuStatsReporter?.stop();
|
|
12334
12216
|
if (statsOptions?.reporting_interval_ms > 0) {
|
|
12335
|
-
this.unifiedSessionId ?? (this.unifiedSessionId = sfuClient.sessionId);
|
|
12336
12217
|
this.sfuStatsReporter = new SfuStatsReporter(sfuClient, {
|
|
12337
12218
|
clientDetails,
|
|
12338
12219
|
options: statsOptions,
|
|
@@ -12342,7 +12223,7 @@ class Call {
|
|
|
12342
12223
|
camera: this.camera,
|
|
12343
12224
|
state: this.state,
|
|
12344
12225
|
tracer: this.tracer,
|
|
12345
|
-
unifiedSessionId
|
|
12226
|
+
unifiedSessionId,
|
|
12346
12227
|
});
|
|
12347
12228
|
this.sfuStatsReporter.start();
|
|
12348
12229
|
}
|
|
@@ -14664,7 +14545,7 @@ class StreamClient {
|
|
|
14664
14545
|
this.getUserAgent = () => {
|
|
14665
14546
|
if (!this.cachedUserAgent) {
|
|
14666
14547
|
const { clientAppIdentifier = {} } = this.options;
|
|
14667
|
-
const { sdkName = 'js', sdkVersion = "1.
|
|
14548
|
+
const { sdkName = 'js', sdkVersion = "1.32.0", ...extras } = clientAppIdentifier;
|
|
14668
14549
|
this.cachedUserAgent = [
|
|
14669
14550
|
`stream-video-${sdkName}-v${sdkVersion}`,
|
|
14670
14551
|
...Object.entries(extras).map(([key, value]) => `${key}=${value}`),
|
|
@@ -14853,6 +14734,7 @@ class StreamVideoClient {
|
|
|
14853
14734
|
this.effectsRegistered = false;
|
|
14854
14735
|
this.eventHandlersToUnregister = [];
|
|
14855
14736
|
this.connectionConcurrencyTag = Symbol('connectionConcurrencyTag');
|
|
14737
|
+
this.rejectCallWhenBusy = false;
|
|
14856
14738
|
this.registerClientInstance = (apiKey, user) => {
|
|
14857
14739
|
const instanceKey = getInstanceKey(apiKey, user);
|
|
14858
14740
|
if (StreamVideoClient._instances.has(instanceKey)) {
|
|
@@ -14898,7 +14780,16 @@ class StreamVideoClient {
|
|
|
14898
14780
|
let call = this.writeableStateStore.findCall(e.call.type, e.call.id);
|
|
14899
14781
|
if (call) {
|
|
14900
14782
|
if (ringing) {
|
|
14901
|
-
|
|
14783
|
+
if (this.shouldRejectCall(call.cid)) {
|
|
14784
|
+
this.logger('info', `Leaving call with busy reject reason ${call.cid} because user is busy`);
|
|
14785
|
+
// remove the instance from the state store
|
|
14786
|
+
await call.leave();
|
|
14787
|
+
// explicitly reject the call with busy reason as calling state was not ringing before and leave would not call it therefore
|
|
14788
|
+
await call.reject('busy');
|
|
14789
|
+
}
|
|
14790
|
+
else {
|
|
14791
|
+
await call.updateFromRingingEvent(e);
|
|
14792
|
+
}
|
|
14902
14793
|
}
|
|
14903
14794
|
else {
|
|
14904
14795
|
call.state.updateFromCallResponse(e.call);
|
|
@@ -14913,11 +14804,19 @@ class StreamVideoClient {
|
|
|
14913
14804
|
clientStore: this.writeableStateStore,
|
|
14914
14805
|
ringing,
|
|
14915
14806
|
});
|
|
14916
|
-
call.state.updateFromCallResponse(e.call);
|
|
14917
14807
|
if (ringing) {
|
|
14918
|
-
|
|
14808
|
+
if (this.shouldRejectCall(call.cid)) {
|
|
14809
|
+
this.logger('info', `Rejecting call ${call.cid} because user is busy`);
|
|
14810
|
+
// call is not in the state store yet, so just reject api is enough
|
|
14811
|
+
await call.reject('busy');
|
|
14812
|
+
}
|
|
14813
|
+
else {
|
|
14814
|
+
await call.updateFromRingingEvent(e);
|
|
14815
|
+
await call.get();
|
|
14816
|
+
}
|
|
14919
14817
|
}
|
|
14920
14818
|
else {
|
|
14819
|
+
call.state.updateFromCallResponse(e.call);
|
|
14921
14820
|
this.writeableStateStore.registerCall(call);
|
|
14922
14821
|
this.logger('info', `New call created and registered: ${call.cid}`);
|
|
14923
14822
|
}
|
|
@@ -15184,6 +15083,16 @@ class StreamVideoClient {
|
|
|
15184
15083
|
this.connectAnonymousUser = async (user, tokenOrProvider) => {
|
|
15185
15084
|
return withoutConcurrency(this.connectionConcurrencyTag, () => this.streamClient.connectAnonymousUser(user, tokenOrProvider));
|
|
15186
15085
|
};
|
|
15086
|
+
this.shouldRejectCall = (currentCallId) => {
|
|
15087
|
+
if (!this.rejectCallWhenBusy)
|
|
15088
|
+
return false;
|
|
15089
|
+
const hasOngoingRingingCall = this.state.calls.some((c) => c.cid !== currentCallId &&
|
|
15090
|
+
c.ringing &&
|
|
15091
|
+
c.state.callingState !== CallingState.IDLE &&
|
|
15092
|
+
c.state.callingState !== CallingState.LEFT &&
|
|
15093
|
+
c.state.callingState !== CallingState.RECONNECTING_FAILED);
|
|
15094
|
+
return hasOngoingRingingCall;
|
|
15095
|
+
};
|
|
15187
15096
|
const apiKey = typeof apiKeyOrArgs === 'string' ? apiKeyOrArgs : apiKeyOrArgs.apiKey;
|
|
15188
15097
|
const clientOptions = typeof apiKeyOrArgs === 'string' ? opts : apiKeyOrArgs.options;
|
|
15189
15098
|
if (clientOptions?.enableTimerWorker)
|
|
@@ -15191,6 +15100,7 @@ class StreamVideoClient {
|
|
|
15191
15100
|
const rootLogger = clientOptions?.logger || logToConsole;
|
|
15192
15101
|
setLogger(rootLogger, clientOptions?.logLevel || 'warn');
|
|
15193
15102
|
this.logger = getLogger(['client']);
|
|
15103
|
+
this.rejectCallWhenBusy = clientOptions?.rejectCallWhenBusy ?? false;
|
|
15194
15104
|
this.streamClient = createCoordinatorClient(apiKey, clientOptions);
|
|
15195
15105
|
this.writeableStateStore = new StreamVideoWriteableStateStore();
|
|
15196
15106
|
this.readOnlyStateStore = new StreamVideoReadOnlyStateStore(this.writeableStateStore);
|