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