@stream-io/video-client 1.11.3 → 1.11.5
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 +14 -0
- package/dist/index.browser.es.js +128 -45
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +128 -45
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +128 -45
- package/dist/index.es.js.map +1 -1
- package/dist/src/StreamVideoClient.d.ts +2 -3
- package/dist/src/coordinator/connection/client.d.ts +11 -8
- package/dist/src/coordinator/connection/connection.d.ts +5 -4
- package/dist/src/helpers/promise.d.ts +18 -0
- package/dist/src/helpers/sdp-munging.d.ts +4 -0
- package/dist/src/rtc/Publisher.d.ts +1 -0
- package/dist/src/types.d.ts +6 -0
- package/package.json +1 -1
- package/src/StreamVideoClient.ts +14 -19
- package/src/__tests__/Call.test.ts +2 -2
- package/src/coordinator/connection/client.ts +33 -14
- package/src/coordinator/connection/connection.ts +14 -19
- package/src/helpers/__tests__/sdp-munging.test.ts +168 -1
- package/src/helpers/promise.ts +47 -0
- package/src/helpers/sdp-munging.ts +55 -0
- package/src/rtc/Publisher.ts +24 -0
- package/src/rtc/codecs.ts +1 -1
- package/src/types.ts +6 -0
|
@@ -21,8 +21,7 @@ export declare class StreamVideoClient {
|
|
|
21
21
|
protected readonly writeableStateStore: StreamVideoWriteableStateStore;
|
|
22
22
|
streamClient: StreamClient;
|
|
23
23
|
protected eventHandlersToUnregister: Array<() => void>;
|
|
24
|
-
|
|
25
|
-
protected disconnectionPromise: Promise<void> | undefined;
|
|
24
|
+
private readonly connectionConcurrencyTag;
|
|
26
25
|
private static _instanceMap;
|
|
27
26
|
/**
|
|
28
27
|
* You should create only one instance of `StreamVideoClient`.
|
|
@@ -159,5 +158,5 @@ export declare class StreamVideoClient {
|
|
|
159
158
|
* @param user the user to connect.
|
|
160
159
|
* @param tokenOrProvider a token or a function that returns a token.
|
|
161
160
|
*/
|
|
162
|
-
protected connectAnonymousUser: (user: UserWithId, tokenOrProvider: TokenOrProvider) => Promise<void
|
|
161
|
+
protected connectAnonymousUser: (user: UserWithId, tokenOrProvider: TokenOrProvider) => Promise<void>;
|
|
163
162
|
}
|
|
@@ -4,7 +4,7 @@ import { TokenManager } from './token_manager';
|
|
|
4
4
|
import { WSConnectionFallback } from './connection_fallback';
|
|
5
5
|
import { AllClientEvents, AllClientEventTypes, APIErrorResponse, ClientEventListener, ConnectAPIResponse, ErrorFromResponse, Logger, StreamClientOptions, StreamVideoEvent, TokenOrProvider, User, UserWithId } from './types';
|
|
6
6
|
import { InsightMetrics } from './insights';
|
|
7
|
-
import { CreateGuestResponse } from '../../gen/coordinator';
|
|
7
|
+
import { ConnectedEvent, CreateGuestResponse } from '../../gen/coordinator';
|
|
8
8
|
export declare class StreamClient {
|
|
9
9
|
_user?: UserWithId;
|
|
10
10
|
anonymous: boolean;
|
|
@@ -28,14 +28,14 @@ export declare class StreamClient {
|
|
|
28
28
|
wsBaseURL?: string;
|
|
29
29
|
wsConnection: StableWSConnection | null;
|
|
30
30
|
wsFallback?: WSConnectionFallback;
|
|
31
|
-
|
|
31
|
+
private wsPromiseSafe;
|
|
32
32
|
consecutiveFailures: number;
|
|
33
33
|
insightMetrics: InsightMetrics;
|
|
34
34
|
defaultWSTimeoutWithFallback: number;
|
|
35
35
|
defaultWSTimeout: number;
|
|
36
36
|
resolveConnectionId?: Function;
|
|
37
37
|
rejectConnectionId?: Function;
|
|
38
|
-
|
|
38
|
+
private connectionIdPromiseSafe?;
|
|
39
39
|
guestUserCreatePromise?: Promise<CreateGuestResponse>;
|
|
40
40
|
/**
|
|
41
41
|
* Initialize a client.
|
|
@@ -64,7 +64,7 @@ export declare class StreamClient {
|
|
|
64
64
|
*
|
|
65
65
|
* @return {ConnectAPIResponse} Returns a promise that resolves when the connection is setup
|
|
66
66
|
*/
|
|
67
|
-
connectUser: (user: UserWithId, userTokenOrProvider: TokenOrProvider) => Promise<void |
|
|
67
|
+
connectUser: (user: UserWithId, userTokenOrProvider: TokenOrProvider) => Promise<void | ConnectedEvent>;
|
|
68
68
|
_setToken: (user: UserWithId, userTokenOrProvider: TokenOrProvider, isAnonymous: boolean) => Promise<void>;
|
|
69
69
|
_setUser: (user: UserWithId) => void;
|
|
70
70
|
/**
|
|
@@ -84,7 +84,7 @@ export declare class StreamClient {
|
|
|
84
84
|
/**
|
|
85
85
|
* Creates a new WebSocket connection with the current user. Returns empty promise, if there is an active connection
|
|
86
86
|
*/
|
|
87
|
-
openConnection: () => Promise<
|
|
87
|
+
openConnection: () => Promise<ConnectedEvent | undefined>;
|
|
88
88
|
/**
|
|
89
89
|
* Disconnects the websocket and removes the user from client.
|
|
90
90
|
*
|
|
@@ -94,7 +94,7 @@ export declare class StreamClient {
|
|
|
94
94
|
disconnectUser: (timeout?: number) => Promise<void>;
|
|
95
95
|
connectGuestUser: (user: User & {
|
|
96
96
|
type: "guest";
|
|
97
|
-
}) => Promise<void |
|
|
97
|
+
}) => Promise<void | ConnectedEvent>;
|
|
98
98
|
/**
|
|
99
99
|
* connectAnonymousUser - Set an anonymous user and open a WebSocket connection
|
|
100
100
|
*/
|
|
@@ -117,7 +117,10 @@ export declare class StreamClient {
|
|
|
117
117
|
/**
|
|
118
118
|
* sets up the this.connectionIdPromise
|
|
119
119
|
*/
|
|
120
|
-
_setupConnectionIdPromise: () =>
|
|
120
|
+
_setupConnectionIdPromise: () => void;
|
|
121
|
+
get connectionIdPromise(): Promise<string | undefined> | undefined;
|
|
122
|
+
get isConnectionIsPromisePending(): boolean;
|
|
123
|
+
get wsPromise(): Promise<ConnectedEvent | undefined> | undefined;
|
|
121
124
|
_logApiRequest: (type: string, url: string, data: unknown, config: AxiosRequestConfig & {
|
|
122
125
|
config?: AxiosRequestConfig & {
|
|
123
126
|
maxBodyLength?: number;
|
|
@@ -143,7 +146,7 @@ export declare class StreamClient {
|
|
|
143
146
|
/**
|
|
144
147
|
* @private
|
|
145
148
|
*/
|
|
146
|
-
connect: () => Promise<
|
|
149
|
+
connect: () => Promise<ConnectedEvent | undefined>;
|
|
147
150
|
/**
|
|
148
151
|
* Check the connectivity with server for warmup purpose.
|
|
149
152
|
*
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import WebSocket from 'isomorphic-ws';
|
|
2
2
|
import { StreamClient } from './client';
|
|
3
|
-
import type {
|
|
3
|
+
import type { LogLevel, UR } from './types';
|
|
4
4
|
import type { ConnectedEvent } from '../../gen/coordinator';
|
|
5
5
|
/**
|
|
6
6
|
* StableWSConnection - A WS connection that reconnects upon failure.
|
|
@@ -21,7 +21,7 @@ import type { ConnectedEvent } from '../../gen/coordinator';
|
|
|
21
21
|
*/
|
|
22
22
|
export declare class StableWSConnection {
|
|
23
23
|
connectionID?: string;
|
|
24
|
-
|
|
24
|
+
private connectionOpenSafe?;
|
|
25
25
|
authenticationSent: boolean;
|
|
26
26
|
consecutiveFailures: number;
|
|
27
27
|
pingInterval: number;
|
|
@@ -52,13 +52,13 @@ export declare class StableWSConnection {
|
|
|
52
52
|
* the default 15s timeout allows between 2~3 tries
|
|
53
53
|
* @return {ConnectAPIResponse<ConnectedEvent>} Promise that completes once the first health check message is received
|
|
54
54
|
*/
|
|
55
|
-
connect(timeout?: number): Promise<
|
|
55
|
+
connect(timeout?: number): Promise<ConnectedEvent | undefined>;
|
|
56
56
|
/**
|
|
57
57
|
* _waitForHealthy polls the promise connection to see if its resolved until it times out
|
|
58
58
|
* the default 15s timeout allows between 2~3 tries
|
|
59
59
|
* @param timeout duration(ms)
|
|
60
60
|
*/
|
|
61
|
-
_waitForHealthy(timeout?: number): Promise<
|
|
61
|
+
_waitForHealthy(timeout?: number): Promise<ConnectedEvent | undefined>;
|
|
62
62
|
/**
|
|
63
63
|
* Builds and returns the url for websocket.
|
|
64
64
|
* @private
|
|
@@ -126,6 +126,7 @@ export declare class StableWSConnection {
|
|
|
126
126
|
* _setupPromise - sets up the this.connectOpen promise
|
|
127
127
|
*/
|
|
128
128
|
_setupConnectionPromise: () => void;
|
|
129
|
+
get connectionOpen(): Promise<ConnectedEvent> | undefined;
|
|
129
130
|
/**
|
|
130
131
|
* Schedules a next health check ping for websocket.
|
|
131
132
|
*/
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface SafePromise<T> {
|
|
2
|
+
(): Promise<T>;
|
|
3
|
+
checkPending(): boolean;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Saving a long-lived reference to a promise that can reject can be unsafe,
|
|
7
|
+
* since rejecting the promise causes an unhandled rejection error (even if the
|
|
8
|
+
* rejection is handled everywhere promise result is expected).
|
|
9
|
+
*
|
|
10
|
+
* To avoid that, we add both resolution and rejection handlers to the promise.
|
|
11
|
+
* That way, the saved promise never rejects. A callback is provided as return
|
|
12
|
+
* value to build a *new* promise, that resolves and rejects along with
|
|
13
|
+
* the original promise.
|
|
14
|
+
* @param promise Promise to wrap, which possibly rejects
|
|
15
|
+
* @returns Callback to build a new promise, which resolves and rejects along
|
|
16
|
+
* with the original promise
|
|
17
|
+
*/
|
|
18
|
+
export declare function makeSafePromise<T>(promise: Promise<T>): SafePromise<T>;
|
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
* Returns an SDP with DTX enabled or disabled.
|
|
3
3
|
*/
|
|
4
4
|
export declare const toggleDtx: (sdp: string, enable: boolean) => string;
|
|
5
|
+
/**
|
|
6
|
+
* Returns and SDP with all the codecs except the given codec removed.
|
|
7
|
+
*/
|
|
8
|
+
export declare const preserveCodec: (sdp: string, mid: string, codec: RTCRtpCodec) => string;
|
|
5
9
|
/**
|
|
6
10
|
* Enables high-quality audio through SDP munging for the given trackMid.
|
|
7
11
|
*
|
|
@@ -124,6 +124,7 @@ export declare class Publisher {
|
|
|
124
124
|
* @param options the optional offer options to use.
|
|
125
125
|
*/
|
|
126
126
|
private negotiate;
|
|
127
|
+
private removeUnpreferredCodecs;
|
|
127
128
|
private enableHighQualityAudio;
|
|
128
129
|
/**
|
|
129
130
|
* Returns a list of tracks that are currently being published.
|
package/dist/src/types.d.ts
CHANGED
|
@@ -129,6 +129,12 @@ export type PublishOptions = {
|
|
|
129
129
|
* Use with caution.
|
|
130
130
|
*/
|
|
131
131
|
forceCodec?: PreferredCodec;
|
|
132
|
+
/**
|
|
133
|
+
* When using a preferred codec, force the use of a single codec.
|
|
134
|
+
* Enabling this, it will remove all other supported codecs from the SDP.
|
|
135
|
+
* Defaults to false.
|
|
136
|
+
*/
|
|
137
|
+
forceSingleCodec?: boolean;
|
|
132
138
|
/**
|
|
133
139
|
* The preferred scalability to use when publishing the video stream.
|
|
134
140
|
* Applicable only for SVC codecs.
|
package/package.json
CHANGED
package/src/StreamVideoClient.ts
CHANGED
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
import { getLogger, logToConsole, setLogger } from './logger';
|
|
31
31
|
import { getSdkInfo } from './client-details';
|
|
32
32
|
import { SdkType } from './gen/video/sfu/models/models';
|
|
33
|
+
import { withoutConcurrency } from './helpers/concurrency';
|
|
33
34
|
|
|
34
35
|
/**
|
|
35
36
|
* A `StreamVideoClient` instance lets you communicate with our API, and authenticate users.
|
|
@@ -51,8 +52,9 @@ export class StreamVideoClient {
|
|
|
51
52
|
streamClient: StreamClient;
|
|
52
53
|
|
|
53
54
|
protected eventHandlersToUnregister: Array<() => void> = [];
|
|
54
|
-
|
|
55
|
-
|
|
55
|
+
private readonly connectionConcurrencyTag = Symbol(
|
|
56
|
+
'connectionConcurrencyTag',
|
|
57
|
+
);
|
|
56
58
|
|
|
57
59
|
private static _instanceMap: Map<string, StreamVideoClient> = new Map();
|
|
58
60
|
|
|
@@ -209,12 +211,11 @@ export class StreamVideoClient {
|
|
|
209
211
|
return this.streamClient.connectGuestUser(user);
|
|
210
212
|
};
|
|
211
213
|
}
|
|
212
|
-
this.connectionPromise = this.disconnectionPromise
|
|
213
|
-
? this.disconnectionPromise.then(() => connectUser())
|
|
214
|
-
: connectUser();
|
|
215
214
|
|
|
216
|
-
|
|
217
|
-
|
|
215
|
+
const connectUserResponse = await withoutConcurrency(
|
|
216
|
+
this.connectionConcurrencyTag,
|
|
217
|
+
() => connectUser(),
|
|
218
|
+
);
|
|
218
219
|
// connectUserResponse will be void if connectUser called twice for the same user
|
|
219
220
|
if (connectUserResponse?.me) {
|
|
220
221
|
this.writeableStateStore.setConnectedUser(connectUserResponse.me);
|
|
@@ -316,19 +317,15 @@ export class StreamVideoClient {
|
|
|
316
317
|
* https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent
|
|
317
318
|
*/
|
|
318
319
|
disconnectUser = async (timeout?: number) => {
|
|
319
|
-
if (!this.streamClient.user
|
|
320
|
+
if (!this.streamClient.user) {
|
|
320
321
|
return;
|
|
321
322
|
}
|
|
322
323
|
const userId = this.streamClient.user?.id;
|
|
323
324
|
const apiKey = this.streamClient.key;
|
|
324
325
|
const disconnectUser = () => this.streamClient.disconnectUser(timeout);
|
|
325
|
-
this.
|
|
326
|
-
|
|
327
|
-
: disconnectUser();
|
|
328
|
-
this.disconnectionPromise.finally(
|
|
329
|
-
() => (this.disconnectionPromise = undefined),
|
|
326
|
+
await withoutConcurrency(this.connectionConcurrencyTag, () =>
|
|
327
|
+
disconnectUser(),
|
|
330
328
|
);
|
|
331
|
-
await this.disconnectionPromise;
|
|
332
329
|
if (userId) {
|
|
333
330
|
StreamVideoClient._instanceMap.delete(apiKey + userId);
|
|
334
331
|
}
|
|
@@ -556,10 +553,8 @@ export class StreamVideoClient {
|
|
|
556
553
|
) => {
|
|
557
554
|
const connectAnonymousUser = () =>
|
|
558
555
|
this.streamClient.connectAnonymousUser(user, tokenOrProvider);
|
|
559
|
-
this.
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
this.connectionPromise.finally(() => (this.connectionPromise = undefined));
|
|
563
|
-
return this.connectionPromise;
|
|
556
|
+
return await withoutConcurrency(this.connectionConcurrencyTag, () =>
|
|
557
|
+
connectAnonymousUser(),
|
|
558
|
+
);
|
|
564
559
|
};
|
|
565
560
|
}
|
|
@@ -271,8 +271,8 @@ describe('muting logic', () => {
|
|
|
271
271
|
.mockImplementation(() => Promise.resolve({ duration: '0ms' }));
|
|
272
272
|
});
|
|
273
273
|
|
|
274
|
-
it('should mute self', () => {
|
|
275
|
-
call.muteSelf('audio');
|
|
274
|
+
it('should mute self', async () => {
|
|
275
|
+
await call.muteSelf('audio');
|
|
276
276
|
|
|
277
277
|
expect(spy).toHaveBeenCalledWith(userId, 'audio');
|
|
278
278
|
});
|
|
@@ -38,7 +38,12 @@ import {
|
|
|
38
38
|
} from './types';
|
|
39
39
|
import { InsightMetrics, postInsights } from './insights';
|
|
40
40
|
import { getLocationHint } from './location';
|
|
41
|
-
import {
|
|
41
|
+
import {
|
|
42
|
+
ConnectedEvent,
|
|
43
|
+
CreateGuestRequest,
|
|
44
|
+
CreateGuestResponse,
|
|
45
|
+
} from '../../gen/coordinator';
|
|
46
|
+
import { makeSafePromise, type SafePromise } from '../../helpers/promise';
|
|
42
47
|
|
|
43
48
|
export class StreamClient {
|
|
44
49
|
_user?: UserWithId;
|
|
@@ -67,14 +72,14 @@ export class StreamClient {
|
|
|
67
72
|
wsBaseURL?: string;
|
|
68
73
|
wsConnection: StableWSConnection | null;
|
|
69
74
|
wsFallback?: WSConnectionFallback;
|
|
70
|
-
|
|
75
|
+
private wsPromiseSafe: SafePromise<ConnectedEvent | undefined> | null;
|
|
71
76
|
consecutiveFailures: number;
|
|
72
77
|
insightMetrics: InsightMetrics;
|
|
73
78
|
defaultWSTimeoutWithFallback: number;
|
|
74
79
|
defaultWSTimeout: number;
|
|
75
80
|
resolveConnectionId?: Function;
|
|
76
81
|
rejectConnectionId?: Function;
|
|
77
|
-
|
|
82
|
+
private connectionIdPromiseSafe?: SafePromise<string | undefined>;
|
|
78
83
|
guestUserCreatePromise?: Promise<CreateGuestResponse>;
|
|
79
84
|
|
|
80
85
|
/**
|
|
@@ -155,7 +160,7 @@ export class StreamClient {
|
|
|
155
160
|
|
|
156
161
|
// WS connection is initialized when setUser is called
|
|
157
162
|
this.wsConnection = null;
|
|
158
|
-
this.
|
|
163
|
+
this.wsPromiseSafe = null;
|
|
159
164
|
this.setUserPromise = null;
|
|
160
165
|
|
|
161
166
|
// mapping between channel groups and configs
|
|
@@ -340,12 +345,13 @@ export class StreamClient {
|
|
|
340
345
|
);
|
|
341
346
|
}
|
|
342
347
|
|
|
343
|
-
|
|
348
|
+
const wsPromise = this.wsPromiseSafe?.();
|
|
349
|
+
if (this.wsConnection?.isConnecting && wsPromise) {
|
|
344
350
|
this.logger(
|
|
345
351
|
'info',
|
|
346
352
|
'client:openConnection() - connection already in progress',
|
|
347
353
|
);
|
|
348
|
-
return
|
|
354
|
+
return await wsPromise;
|
|
349
355
|
}
|
|
350
356
|
|
|
351
357
|
if (
|
|
@@ -357,14 +363,15 @@ export class StreamClient {
|
|
|
357
363
|
'client:openConnection() - openConnection called twice, healthy connection already exists',
|
|
358
364
|
);
|
|
359
365
|
|
|
360
|
-
return
|
|
366
|
+
return;
|
|
361
367
|
}
|
|
362
368
|
|
|
363
369
|
this._setupConnectionIdPromise();
|
|
364
370
|
|
|
365
371
|
this.clientID = `${this.userID}--${randomId()}`;
|
|
366
|
-
|
|
367
|
-
|
|
372
|
+
const newWsPromise = this.connect();
|
|
373
|
+
this.wsPromiseSafe = makeSafePromise(newWsPromise);
|
|
374
|
+
return await newWsPromise;
|
|
368
375
|
};
|
|
369
376
|
|
|
370
377
|
/**
|
|
@@ -388,7 +395,7 @@ export class StreamClient {
|
|
|
388
395
|
|
|
389
396
|
this.tokenManager.reset();
|
|
390
397
|
|
|
391
|
-
this.
|
|
398
|
+
this.connectionIdPromiseSafe = undefined;
|
|
392
399
|
this.rejectConnectionId = undefined;
|
|
393
400
|
this.resolveConnectionId = undefined;
|
|
394
401
|
};
|
|
@@ -481,16 +488,28 @@ export class StreamClient {
|
|
|
481
488
|
/**
|
|
482
489
|
* sets up the this.connectionIdPromise
|
|
483
490
|
*/
|
|
484
|
-
_setupConnectionIdPromise =
|
|
491
|
+
_setupConnectionIdPromise = () => {
|
|
485
492
|
/** a promise that is resolved once connection id is set */
|
|
486
|
-
this.
|
|
487
|
-
(resolve, reject) => {
|
|
493
|
+
this.connectionIdPromiseSafe = makeSafePromise(
|
|
494
|
+
new Promise<string | undefined>((resolve, reject) => {
|
|
488
495
|
this.resolveConnectionId = resolve;
|
|
489
496
|
this.rejectConnectionId = reject;
|
|
490
|
-
},
|
|
497
|
+
}),
|
|
491
498
|
);
|
|
492
499
|
};
|
|
493
500
|
|
|
501
|
+
get connectionIdPromise() {
|
|
502
|
+
return this.connectionIdPromiseSafe?.();
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
get isConnectionIsPromisePending() {
|
|
506
|
+
return this.connectionIdPromiseSafe?.checkPending() ?? false;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
get wsPromise() {
|
|
510
|
+
return this.wsPromiseSafe?.();
|
|
511
|
+
}
|
|
512
|
+
|
|
494
513
|
_logApiRequest = (
|
|
495
514
|
type: string,
|
|
496
515
|
url: string,
|
|
@@ -8,20 +8,15 @@ import {
|
|
|
8
8
|
import {
|
|
9
9
|
addConnectionEventListeners,
|
|
10
10
|
convertErrorToJson,
|
|
11
|
-
isPromisePending,
|
|
12
11
|
KnownCodes,
|
|
13
12
|
randomId,
|
|
14
13
|
removeConnectionEventListeners,
|
|
15
14
|
retryInterval,
|
|
16
15
|
sleep,
|
|
17
16
|
} from './utils';
|
|
18
|
-
import type {
|
|
19
|
-
ConnectAPIResponse,
|
|
20
|
-
LogLevel,
|
|
21
|
-
StreamVideoEvent,
|
|
22
|
-
UR,
|
|
23
|
-
} from './types';
|
|
17
|
+
import type { LogLevel, StreamVideoEvent, UR } from './types';
|
|
24
18
|
import type { ConnectedEvent, WSAuthMessage } from '../../gen/coordinator';
|
|
19
|
+
import { makeSafePromise, type SafePromise } from '../../helpers/promise';
|
|
25
20
|
|
|
26
21
|
// Type guards to check WebSocket error type
|
|
27
22
|
const isCloseEvent = (
|
|
@@ -54,7 +49,7 @@ const isErrorEvent = (
|
|
|
54
49
|
export class StableWSConnection {
|
|
55
50
|
// local vars
|
|
56
51
|
connectionID?: string;
|
|
57
|
-
|
|
52
|
+
private connectionOpenSafe?: SafePromise<ConnectedEvent>;
|
|
58
53
|
authenticationSent: boolean;
|
|
59
54
|
consecutiveFailures: number;
|
|
60
55
|
pingInterval: number;
|
|
@@ -338,13 +333,7 @@ export class StableWSConnection {
|
|
|
338
333
|
await this.client.tokenManager.loadToken();
|
|
339
334
|
}
|
|
340
335
|
|
|
341
|
-
|
|
342
|
-
if (this.client.connectionIdPromise) {
|
|
343
|
-
if (await isPromisePending(this.client.connectionIdPromise)) {
|
|
344
|
-
mustSetupConnectionIdPromise = false;
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
if (mustSetupConnectionIdPromise) {
|
|
336
|
+
if (!this.client.isConnectionIsPromisePending) {
|
|
348
337
|
this.client._setupConnectionIdPromise();
|
|
349
338
|
}
|
|
350
339
|
this._setupConnectionPromise();
|
|
@@ -747,12 +736,18 @@ export class StableWSConnection {
|
|
|
747
736
|
_setupConnectionPromise = () => {
|
|
748
737
|
this.isResolved = false;
|
|
749
738
|
/** a promise that is resolved once ws.open is called */
|
|
750
|
-
this.
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
739
|
+
this.connectionOpenSafe = makeSafePromise(
|
|
740
|
+
new Promise<ConnectedEvent>((resolve, reject) => {
|
|
741
|
+
this.resolvePromise = resolve;
|
|
742
|
+
this.rejectPromise = reject;
|
|
743
|
+
}),
|
|
744
|
+
);
|
|
754
745
|
};
|
|
755
746
|
|
|
747
|
+
get connectionOpen() {
|
|
748
|
+
return this.connectionOpenSafe?.();
|
|
749
|
+
}
|
|
750
|
+
|
|
756
751
|
/**
|
|
757
752
|
* Schedules a next health check ping for websocket.
|
|
758
753
|
*/
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
enableHighQualityAudio,
|
|
4
|
+
preserveCodec,
|
|
5
|
+
toggleDtx,
|
|
6
|
+
} from '../sdp-munging';
|
|
3
7
|
import { initialSdp as HQAudioSDP } from './hq-audio-sdp';
|
|
4
8
|
|
|
5
9
|
describe('sdp-munging', () => {
|
|
@@ -21,4 +25,167 @@ a=maxptime:40`;
|
|
|
21
25
|
expect(sdpWithHighQualityAudio).toContain('maxaveragebitrate=510000');
|
|
22
26
|
expect(sdpWithHighQualityAudio).toContain('stereo=1');
|
|
23
27
|
});
|
|
28
|
+
|
|
29
|
+
it('preserves the preferred codec', () => {
|
|
30
|
+
const sdp = `v=0
|
|
31
|
+
o=- 8608371809202407637 2 IN IP4 127.0.0.1
|
|
32
|
+
s=-
|
|
33
|
+
t=0 0
|
|
34
|
+
a=extmap-allow-mixed
|
|
35
|
+
a=msid-semantic: WMS 52fafc21-b8bb-4f4f-8072-86a29cb6590e
|
|
36
|
+
a=group:BUNDLE 0
|
|
37
|
+
m=video 9 UDP/TLS/RTP/SAVPF 98 100 99 101
|
|
38
|
+
c=IN IP4 0.0.0.0
|
|
39
|
+
a=rtpmap:98 VP9/90000
|
|
40
|
+
a=rtpmap:99 rtx/90000
|
|
41
|
+
a=rtpmap:100 VP9/90000
|
|
42
|
+
a=rtpmap:101 rtx/90000
|
|
43
|
+
a=fmtp:98 profile-id=0
|
|
44
|
+
a=fmtp:99 apt=98
|
|
45
|
+
a=fmtp:100 profile-id=2
|
|
46
|
+
a=fmtp:101 apt=100
|
|
47
|
+
a=rtcp:9 IN IP4 0.0.0.0
|
|
48
|
+
a=rtcp-fb:98 goog-remb
|
|
49
|
+
a=rtcp-fb:98 transport-cc
|
|
50
|
+
a=rtcp-fb:98 ccm fir
|
|
51
|
+
a=rtcp-fb:98 nack
|
|
52
|
+
a=rtcp-fb:98 nack pli
|
|
53
|
+
a=rtcp-fb:100 goog-remb
|
|
54
|
+
a=rtcp-fb:100 transport-cc
|
|
55
|
+
a=rtcp-fb:100 ccm fir
|
|
56
|
+
a=rtcp-fb:100 nack
|
|
57
|
+
a=rtcp-fb:100 nack pli
|
|
58
|
+
a=extmap:1 urn:ietf:params:rtp-hdrext:toffset
|
|
59
|
+
a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
|
|
60
|
+
a=extmap:3 urn:3gpp:video-orientation
|
|
61
|
+
a=extmap:4 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
|
|
62
|
+
a=extmap:5 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay
|
|
63
|
+
a=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type
|
|
64
|
+
a=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-timing
|
|
65
|
+
a=extmap:8 http://www.webrtc.org/experiments/rtp-hdrext/color-space
|
|
66
|
+
a=extmap:9 urn:ietf:params:rtp-hdrext:sdes:mid
|
|
67
|
+
a=extmap:10 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id
|
|
68
|
+
a=extmap:11 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id
|
|
69
|
+
a=extmap:12 https://aomediacodec.github.io/av1-rtp-spec/#dependency-descriptor-rtp-header-extension
|
|
70
|
+
a=extmap:14 http://www.webrtc.org/experiments/rtp-hdrext/video-layers-allocation00
|
|
71
|
+
a=setup:actpass
|
|
72
|
+
a=mid:0
|
|
73
|
+
a=msid:52fafc21-b8bb-4f4f-8072-86a29cb6590e 1bd1c5c2-d3cc-4490-ac0c-70b187242232
|
|
74
|
+
a=sendonly
|
|
75
|
+
a=ice-ufrag:LvRk
|
|
76
|
+
a=ice-pwd:IpBRr2Rrg9TkOgayjYqALhPY
|
|
77
|
+
a=fingerprint:sha-256 18:DE:8F:ED:E6:A2:0C:99:A8:25:AB:C9:F8:3D:91:4C:3E:9F:B4:1F:22:87:A7:3C:85:8F:F3:51:09:A7:E3:FA
|
|
78
|
+
a=ice-options:trickle
|
|
79
|
+
a=ssrc:3192778601 cname:yYSN5R+RG2j3luO7
|
|
80
|
+
a=ssrc:3192778601 msid:52fafc21-b8bb-4f4f-8072-86a29cb6590e 1bd1c5c2-d3cc-4490-ac0c-70b187242232
|
|
81
|
+
a=ssrc:283365205 cname:yYSN5R+RG2j3luO7
|
|
82
|
+
a=ssrc:283365205 msid:52fafc21-b8bb-4f4f-8072-86a29cb6590e 1bd1c5c2-d3cc-4490-ac0c-70b187242232
|
|
83
|
+
a=ssrc-group:FID 3192778601 283365205
|
|
84
|
+
a=rtcp-mux
|
|
85
|
+
a=rtcp-rsize`;
|
|
86
|
+
const target = preserveCodec(sdp, '0', {
|
|
87
|
+
mimeType: 'video/VP9',
|
|
88
|
+
clockRate: 90000,
|
|
89
|
+
sdpFmtpLine: 'profile-id=0',
|
|
90
|
+
});
|
|
91
|
+
expect(target).toContain('VP9');
|
|
92
|
+
expect(target).not.toContain('profile-id=2');
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('handles ios munging', () => {
|
|
96
|
+
const sdp = `v=0
|
|
97
|
+
o=- 525780719364332676 2 IN IP4 127.0.0.1
|
|
98
|
+
s=-
|
|
99
|
+
t=0 0
|
|
100
|
+
a=group:BUNDLE 0
|
|
101
|
+
a=extmap-allow-mixed
|
|
102
|
+
a=msid-semantic: WMS BF3AFE62-88F8-4189-99D7-7CAE159205E3
|
|
103
|
+
m=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 127 103 35 36 104 105 106
|
|
104
|
+
c=IN IP4 0.0.0.0
|
|
105
|
+
a=rtcp:9 IN IP4 0.0.0.0
|
|
106
|
+
a=ice-ufrag:SAkq
|
|
107
|
+
a=ice-pwd:FYHHro0VWRO8CjI/M1VG5vRw
|
|
108
|
+
a=ice-options:trickle renomination
|
|
109
|
+
a=fingerprint:sha-256 03:5B:16:0E:E1:7B:FE:4F:9A:5C:AC:CF:08:21:4B:49:CE:53:79:E6:97:AE:4E:73:F8:43:34:C3:11:F7:6D:E7
|
|
110
|
+
a=setup:actpass
|
|
111
|
+
a=mid:0
|
|
112
|
+
a=extmap:1 urn:ietf:params:rtp-hdrext:toffset
|
|
113
|
+
a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
|
|
114
|
+
a=extmap:3 urn:3gpp:video-orientation
|
|
115
|
+
a=extmap:4 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
|
|
116
|
+
a=extmap:5 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay
|
|
117
|
+
a=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type
|
|
118
|
+
a=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-timing
|
|
119
|
+
a=extmap:8 http://www.webrtc.org/experiments/rtp-hdrext/color-space
|
|
120
|
+
a=extmap:9 urn:ietf:params:rtp-hdrext:sdes:mid
|
|
121
|
+
a=extmap:10 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id
|
|
122
|
+
a=extmap:11 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id
|
|
123
|
+
a=extmap:12 https://aomediacodec.github.io/av1-rtp-spec/#dependency-descriptor-rtp-header-extension
|
|
124
|
+
a=extmap:14 http://www.webrtc.org/experiments/rtp-hdrext/video-layers-allocation00
|
|
125
|
+
a=sendonly
|
|
126
|
+
a=msid:BF3AFE62-88F8-4189-99D7-7CAE159205E3 6013DC02-A0A5-43A9-9D41-9D4A89648A42
|
|
127
|
+
a=rtcp-mux
|
|
128
|
+
a=rtcp-rsize
|
|
129
|
+
a=rtpmap:96 H264/90000
|
|
130
|
+
a=rtcp-fb:96 goog-remb
|
|
131
|
+
a=rtcp-fb:96 transport-cc
|
|
132
|
+
a=rtcp-fb:96 ccm fir
|
|
133
|
+
a=rtcp-fb:96 nack
|
|
134
|
+
a=rtcp-fb:96 nack pli
|
|
135
|
+
a=fmtp:96 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=640c29
|
|
136
|
+
a=rtpmap:97 rtx/90000
|
|
137
|
+
a=fmtp:97 apt=96
|
|
138
|
+
a=rtpmap:98 H264/90000
|
|
139
|
+
a=rtcp-fb:98 goog-remb
|
|
140
|
+
a=rtcp-fb:98 transport-cc
|
|
141
|
+
a=rtcp-fb:98 ccm fir
|
|
142
|
+
a=rtcp-fb:98 nack
|
|
143
|
+
a=rtcp-fb:98 nack pli
|
|
144
|
+
a=fmtp:98 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e029
|
|
145
|
+
a=rtpmap:99 rtx/90000
|
|
146
|
+
a=fmtp:99 apt=98
|
|
147
|
+
a=rtpmap:100 VP8/90000
|
|
148
|
+
a=rtcp-fb:100 goog-remb
|
|
149
|
+
a=rtcp-fb:100 transport-cc
|
|
150
|
+
a=rtcp-fb:100 ccm fir
|
|
151
|
+
a=rtcp-fb:100 nack
|
|
152
|
+
a=rtcp-fb:100 nack pli
|
|
153
|
+
a=rtpmap:101 rtx/90000
|
|
154
|
+
a=fmtp:101 apt=100
|
|
155
|
+
a=rtpmap:127 VP9/90000
|
|
156
|
+
a=rtcp-fb:127 goog-remb
|
|
157
|
+
a=rtcp-fb:127 transport-cc
|
|
158
|
+
a=rtcp-fb:127 ccm fir
|
|
159
|
+
a=rtcp-fb:127 nack
|
|
160
|
+
a=rtcp-fb:127 nack pli
|
|
161
|
+
a=rtpmap:103 rtx/90000
|
|
162
|
+
a=fmtp:103 apt=127
|
|
163
|
+
a=rtpmap:35 AV1/90000
|
|
164
|
+
a=rtcp-fb:35 goog-remb
|
|
165
|
+
a=rtcp-fb:35 transport-cc
|
|
166
|
+
a=rtcp-fb:35 ccm fir
|
|
167
|
+
a=rtcp-fb:35 nack
|
|
168
|
+
a=rtcp-fb:35 nack pli
|
|
169
|
+
a=rtpmap:36 rtx/90000
|
|
170
|
+
a=fmtp:36 apt=35
|
|
171
|
+
a=rtpmap:104 red/90000
|
|
172
|
+
a=rtpmap:105 rtx/90000
|
|
173
|
+
a=fmtp:105 apt=104
|
|
174
|
+
a=rtpmap:106 ulpfec/90000
|
|
175
|
+
a=rid:q send
|
|
176
|
+
a=rid:h send
|
|
177
|
+
a=rid:f send
|
|
178
|
+
a=simulcast:send q;h;f`;
|
|
179
|
+
const target = preserveCodec(sdp, '0', {
|
|
180
|
+
mimeType: 'video/H264',
|
|
181
|
+
clockRate: 90000,
|
|
182
|
+
sdpFmtpLine:
|
|
183
|
+
'profile-level-id=42e029;packetization-mode=1;level-asymmetry-allowed=1',
|
|
184
|
+
});
|
|
185
|
+
expect(target).toContain('H264');
|
|
186
|
+
expect(target).toContain('profile-level-id=42e029');
|
|
187
|
+
expect(target).not.toContain('profile-level-id=640c29');
|
|
188
|
+
expect(target).not.toContain('VP9');
|
|
189
|
+
expect(target).not.toContain('AV1');
|
|
190
|
+
});
|
|
24
191
|
});
|