livekit-client 1.5.0 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. package/dist/livekit-client.esm.mjs +2031 -5393
  2. package/dist/livekit-client.esm.mjs.map +1 -1
  3. package/dist/livekit-client.umd.js +1 -1
  4. package/dist/livekit-client.umd.js.map +1 -1
  5. package/dist/src/api/SignalClient.d.ts +3 -2
  6. package/dist/src/api/SignalClient.d.ts.map +1 -1
  7. package/dist/src/connectionHelper/ConnectionCheck.d.ts +1 -1
  8. package/dist/src/connectionHelper/ConnectionCheck.d.ts.map +1 -1
  9. package/dist/src/connectionHelper/checks/Checker.d.ts +4 -4
  10. package/dist/src/connectionHelper/checks/Checker.d.ts.map +1 -1
  11. package/dist/src/index.d.ts +3 -2
  12. package/dist/src/index.d.ts.map +1 -1
  13. package/dist/src/logger.d.ts +3 -3
  14. package/dist/src/logger.d.ts.map +1 -1
  15. package/dist/src/options.d.ts +4 -1
  16. package/dist/src/options.d.ts.map +1 -1
  17. package/dist/src/proto/google/protobuf/timestamp.d.ts +4 -4
  18. package/dist/src/proto/google/protobuf/timestamp.d.ts.map +1 -1
  19. package/dist/src/proto/livekit_models.d.ts +4 -4
  20. package/dist/src/proto/livekit_models.d.ts.map +1 -1
  21. package/dist/src/proto/livekit_rtc.d.ts +4 -4
  22. package/dist/src/proto/livekit_rtc.d.ts.map +1 -1
  23. package/dist/src/room/RTCEngine.d.ts +4 -3
  24. package/dist/src/room/RTCEngine.d.ts.map +1 -1
  25. package/dist/src/room/Room.d.ts +21 -4
  26. package/dist/src/room/Room.d.ts.map +1 -1
  27. package/dist/src/room/events.d.ts +4 -0
  28. package/dist/src/room/events.d.ts.map +1 -1
  29. package/dist/src/room/participant/Participant.d.ts +1 -1
  30. package/dist/src/room/participant/Participant.d.ts.map +1 -1
  31. package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
  32. package/dist/src/room/track/RemoteVideoTrack.d.ts.map +1 -1
  33. package/dist/src/room/track/Track.d.ts +2 -1
  34. package/dist/src/room/track/Track.d.ts.map +1 -1
  35. package/dist/src/room/track/TrackPublication.d.ts +1 -1
  36. package/dist/src/room/track/TrackPublication.d.ts.map +1 -1
  37. package/dist/src/room/track/options.d.ts +3 -3
  38. package/dist/src/room/track/options.d.ts.map +1 -1
  39. package/dist/src/room/track/types.d.ts +3 -3
  40. package/dist/src/room/track/types.d.ts.map +1 -1
  41. package/dist/src/room/types.d.ts +13 -0
  42. package/dist/src/room/types.d.ts.map +1 -0
  43. package/dist/src/room/utils.d.ts +44 -0
  44. package/dist/src/room/utils.d.ts.map +1 -1
  45. package/dist/ts4.2/src/api/SignalClient.d.ts +3 -2
  46. package/dist/ts4.2/src/connectionHelper/ConnectionCheck.d.ts +1 -1
  47. package/dist/ts4.2/src/connectionHelper/checks/Checker.d.ts +4 -4
  48. package/dist/ts4.2/src/index.d.ts +3 -2
  49. package/dist/ts4.2/src/logger.d.ts +3 -3
  50. package/dist/ts4.2/src/options.d.ts +4 -1
  51. package/dist/ts4.2/src/proto/google/protobuf/timestamp.d.ts +4 -4
  52. package/dist/ts4.2/src/proto/livekit_models.d.ts +4 -4
  53. package/dist/ts4.2/src/proto/livekit_rtc.d.ts +4 -4
  54. package/dist/ts4.2/src/room/RTCEngine.d.ts +4 -3
  55. package/dist/ts4.2/src/room/Room.d.ts +21 -4
  56. package/dist/ts4.2/src/room/events.d.ts +4 -0
  57. package/dist/ts4.2/src/room/participant/Participant.d.ts +1 -1
  58. package/dist/ts4.2/src/room/track/Track.d.ts +2 -1
  59. package/dist/ts4.2/src/room/track/TrackPublication.d.ts +1 -1
  60. package/dist/ts4.2/src/room/track/options.d.ts +3 -3
  61. package/dist/ts4.2/src/room/track/types.d.ts +3 -3
  62. package/dist/ts4.2/src/room/types.d.ts +13 -0
  63. package/dist/ts4.2/src/room/utils.d.ts +44 -0
  64. package/package.json +21 -21
  65. package/src/api/SignalClient.ts +40 -16
  66. package/src/connectionHelper/checks/turn.ts +1 -1
  67. package/src/connectionHelper/checks/websocket.ts +1 -1
  68. package/src/index.ts +5 -0
  69. package/src/options.ts +5 -1
  70. package/src/room/RTCEngine.ts +35 -26
  71. package/src/room/Room.ts +209 -61
  72. package/src/room/events.ts +4 -0
  73. package/src/room/participant/LocalParticipant.ts +3 -3
  74. package/src/room/participant/publishUtils.ts +1 -1
  75. package/src/room/track/LocalAudioTrack.ts +1 -1
  76. package/src/room/track/LocalTrack.ts +1 -0
  77. package/src/room/track/LocalVideoTrack.ts +1 -1
  78. package/src/room/track/RemoteVideoTrack.ts +4 -0
  79. package/src/room/track/Track.ts +1 -0
  80. package/src/room/types.ts +12 -0
  81. package/src/room/utils.ts +150 -12
@@ -0,0 +1,13 @@
1
+ export type SimulationOptions = {
2
+ publish?: {
3
+ audio?: boolean;
4
+ video?: boolean;
5
+ };
6
+ participants?: {
7
+ count?: number;
8
+ aspectRatios?: Array<number>;
9
+ audio?: boolean;
10
+ video?: boolean;
11
+ };
12
+ };
13
+ //# sourceMappingURL=types.d.ts.map
@@ -1,4 +1,6 @@
1
1
  import { ClientInfo } from '../proto/livekit_models';
2
+ import type LocalAudioTrack from './track/LocalAudioTrack';
3
+ import type RemoteAudioTrack from './track/RemoteAudioTrack';
2
4
  export declare function unpackStreamId(packed: string): string[];
3
5
  export declare function sleep(duration: number): Promise<void>;
4
6
  /** @internal */
@@ -24,6 +26,7 @@ export interface ObservableMediaElement extends HTMLMediaElement {
24
26
  }
25
27
  export declare function getClientInfo(): ClientInfo;
26
28
  export declare function getEmptyVideoStreamTrack(): MediaStreamTrack;
29
+ export declare function createDummyVideoStreamTrack(width?: number, height?: number, enabled?: boolean, paintContent?: boolean): MediaStreamTrack;
27
30
  export declare function getEmptyAudioStreamTrack(): MediaStreamTrack;
28
31
  export declare class Future<T> {
29
32
  promise: Promise<T>;
@@ -32,4 +35,45 @@ export declare class Future<T> {
32
35
  onFinally?: () => void;
33
36
  constructor(futureBase?: (resolve: (arg: T) => void, reject: (e: any) => void) => void, onFinally?: () => void);
34
37
  }
38
+ export type AudioAnalyserOptions = {
39
+ /**
40
+ * If set to true, the analyser will use a cloned version of the underlying mediastreamtrack, which won't be impacted by muting the track.
41
+ * Useful for local tracks when implementing things like "seems like you're muted, but trying to speak".
42
+ * Defaults to false
43
+ */
44
+ cloneTrack?: boolean;
45
+ /**
46
+ * see https://developer.mozilla.org/en-US/docs/Web/API/AnalyserNode/fftSize
47
+ */
48
+ fftSize?: number;
49
+ /**
50
+ * see https://developer.mozilla.org/en-US/docs/Web/API/AnalyserNode/smoothingTimeConstant
51
+ */
52
+ smoothingTimeConstant?: number;
53
+ /**
54
+ * see https://developer.mozilla.org/en-US/docs/Web/API/AnalyserNode/minDecibels
55
+ */
56
+ minDecibels?: number;
57
+ /**
58
+ * see https://developer.mozilla.org/en-US/docs/Web/API/AnalyserNode/maxDecibels
59
+ */
60
+ maxDecibels?: number;
61
+ };
62
+ /**
63
+ * Creates and returns an analyser web audio node that is attached to the provided track.
64
+ * Additionally returns a convenience method `calculateVolume` to perform instant volume readings on that track.
65
+ * Call the returned `cleanup` function to close the audioContext that has been created for the instance of this helper
66
+ */
67
+ export declare function createAudioAnalyser(track: LocalAudioTrack | RemoteAudioTrack, options?: AudioAnalyserOptions): {
68
+ calculateVolume: () => number;
69
+ analyser: AnalyserNode;
70
+ cleanup: () => void;
71
+ };
72
+ export declare class Mutex {
73
+ private _locking;
74
+ private _locks;
75
+ constructor();
76
+ isLocked(): boolean;
77
+ lock(): Promise<() => void>;
78
+ }
35
79
  //# sourceMappingURL=utils.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "livekit-client",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "description": "JavaScript/TypeScript client SDK for LiveKit",
5
5
  "main": "./dist/livekit-client.umd.js",
6
6
  "unpkg": "./dist/livekit-client.umd.js",
@@ -26,7 +26,7 @@
26
26
  "author": "David Zhao <david@davidzhao.com>",
27
27
  "license": "Apache-2.0",
28
28
  "scripts": {
29
- "build": "rollup --config && yarn downlevel-dts",
29
+ "build": "rollup --config --bundleConfigAsCjs && yarn downlevel-dts",
30
30
  "build:watch": "rollup --watch --config rollup.config.js",
31
31
  "build-docs": "typedoc",
32
32
  "proto": "protoc --plugin=node_modules/ts-proto/protoc-gen-ts_proto --ts_proto_opt=esModuleInterop=true --ts_proto_out=./src/proto --ts_proto_opt=outputClientImpl=false,useOptionals=messages,oneof=unions -I./protocol ./protocol/livekit_rtc.proto ./protocol/livekit_models.proto",
@@ -51,40 +51,40 @@
51
51
  "webrtc-adapter": "^8.1.1"
52
52
  },
53
53
  "devDependencies": {
54
- "@babel/core": "7.19.3",
55
- "@babel/preset-env": "7.19.3",
54
+ "@babel/core": "7.20.5",
55
+ "@babel/preset-env": "7.20.2",
56
56
  "@changesets/changelog-github": "0.4.7",
57
- "@changesets/cli": "2.25.0",
58
- "@rollup/plugin-babel": "5.3.1",
59
- "@rollup/plugin-commonjs": "22.0.2",
60
- "@rollup/plugin-json": "4.1.0",
61
- "@rollup/plugin-node-resolve": "14.1.0",
62
- "@types/jest": "29.1.1",
57
+ "@changesets/cli": "2.25.2",
58
+ "@rollup/plugin-babel": "6.0.3",
59
+ "@rollup/plugin-commonjs": "23.0.4",
60
+ "@rollup/plugin-json": "5.0.2",
61
+ "@rollup/plugin-node-resolve": "15.0.1",
62
+ "@types/jest": "29.2.4",
63
63
  "@types/sdp-transform": "2.4.5",
64
64
  "@types/ua-parser-js": "0.7.36",
65
65
  "@types/ws": "8.5.3",
66
- "@typescript-eslint/eslint-plugin": "5.38.1",
67
- "@typescript-eslint/parser": "5.38.1",
66
+ "@typescript-eslint/eslint-plugin": "5.46.0",
67
+ "@typescript-eslint/parser": "5.46.0",
68
68
  "downlevel-dts": "^0.11.0",
69
- "eslint": "8.24.0",
69
+ "eslint": "8.29.0",
70
70
  "eslint-config-airbnb-typescript": "17.0.0",
71
71
  "eslint-config-prettier": "8.5.0",
72
72
  "eslint-plugin-import": "2.26.0",
73
73
  "gh-pages": "4.0.0",
74
- "jest": "29.1.2",
75
- "prettier": "2.7.1",
76
- "rollup": "2.79.1",
74
+ "jest": "29.3.1",
75
+ "prettier": "2.8.1",
76
+ "rollup": "3.7.0",
77
77
  "rollup-plugin-delete": "^2.0.0",
78
78
  "rollup-plugin-filesize": "9.1.2",
79
79
  "rollup-plugin-re": "1.0.7",
80
80
  "rollup-plugin-terser": "7.0.2",
81
- "rollup-plugin-typescript2": "0.34.0",
81
+ "rollup-plugin-typescript2": "0.34.1",
82
82
  "ts-jest": "29.0.3",
83
- "ts-proto": "1.126.1",
84
- "typedoc": "0.23.15",
83
+ "ts-proto": "1.135.0",
84
+ "typedoc": "0.23.21",
85
85
  "typedoc-plugin-no-inherit": "1.4.0",
86
- "typescript": "4.8.4",
87
- "vite": "3.1.8"
86
+ "typescript": "4.9.4",
87
+ "vite": "3.2.5"
88
88
  },
89
89
  "browserslist": [
90
90
  "safari >= 11",
@@ -30,7 +30,7 @@ import {
30
30
  UpdateTrackSettings,
31
31
  } from '../proto/livekit_rtc';
32
32
  import { ConnectionError, ConnectionErrorReason } from '../room/errors';
33
- import { getClientInfo, sleep } from '../room/utils';
33
+ import { getClientInfo, Mutex, sleep } from '../room/utils';
34
34
 
35
35
  // internal options
36
36
  interface ConnectOpts {
@@ -139,12 +139,15 @@ export class SignalClient {
139
139
 
140
140
  private pingInterval: ReturnType<typeof setInterval> | undefined;
141
141
 
142
+ private closingLock: Mutex;
143
+
142
144
  constructor(useJSON: boolean = false) {
143
145
  this.isConnected = false;
144
146
  this.isReconnecting = false;
145
147
  this.useJSON = useJSON;
146
148
  this.requestQueue = new Queue();
147
149
  this.queuedRequests = [];
150
+ this.closingLock = new Mutex();
148
151
  }
149
152
 
150
153
  async join(
@@ -190,9 +193,9 @@ export class SignalClient {
190
193
  const clientInfo = getClientInfo();
191
194
  const params = createConnectionParams(token, clientInfo, opts);
192
195
 
193
- return new Promise<JoinResponse | void>((resolve, reject) => {
194
- const abortHandler = () => {
195
- this.close();
196
+ return new Promise<JoinResponse | void>(async (resolve, reject) => {
197
+ const abortHandler = async () => {
198
+ await this.close();
196
199
  reject(new ConnectionError('room connection has been cancelled'));
197
200
  };
198
201
 
@@ -202,7 +205,7 @@ export class SignalClient {
202
205
  abortSignal?.addEventListener('abort', abortHandler);
203
206
  log.debug(`connecting to ${url + params}`);
204
207
  if (this.ws) {
205
- this.close();
208
+ await this.close();
206
209
  }
207
210
  this.ws = new WebSocket(url + params);
208
211
  this.ws.binaryType = 'arraybuffer';
@@ -277,7 +280,11 @@ export class SignalClient {
277
280
  }
278
281
  resolve(resp.message.join);
279
282
  } else {
280
- reject(new ConnectionError('did not receive join response'));
283
+ reject(
284
+ new ConnectionError(
285
+ `did not receive join response, got ${resp.message?.$case} instead`,
286
+ ),
287
+ );
281
288
  }
282
289
  return;
283
290
  }
@@ -301,16 +308,33 @@ export class SignalClient {
301
308
  });
302
309
  }
303
310
 
304
- close() {
305
- this.isConnected = false;
306
- if (this.ws) {
307
- this.ws.onclose = null;
308
- this.ws.onmessage = null;
309
- this.ws.onopen = null;
310
- this.ws.close();
311
+ async close() {
312
+ const unlock = await this.closingLock.lock();
313
+ try {
314
+ this.isConnected = false;
315
+ if (this.ws) {
316
+ this.ws.onclose = null;
317
+ this.ws.onmessage = null;
318
+ this.ws.onopen = null;
319
+
320
+ // calling `ws.close()` only starts the closing handshake (CLOSING state), prefer to wait until state is actually CLOSED
321
+ const closePromise = new Promise((resolve) => {
322
+ if (this.ws) {
323
+ this.ws.onclose = resolve;
324
+ } else {
325
+ resolve(true);
326
+ }
327
+ });
328
+
329
+ this.ws.close();
330
+ // 250ms grace period for ws to close gracefully
331
+ await Promise.race([closePromise, sleep(250)]);
332
+ }
333
+ this.ws = undefined;
334
+ this.clearPingInterval();
335
+ } finally {
336
+ unlock();
311
337
  }
312
- this.ws = undefined;
313
- this.clearPingInterval();
314
338
  }
315
339
 
316
340
  // initial offer after joining
@@ -441,7 +465,7 @@ export class SignalClient {
441
465
  if (this.signalLatency) {
442
466
  await sleep(this.signalLatency);
443
467
  }
444
- if (!this.ws || this.ws.readyState < this.ws.OPEN) {
468
+ if (!this.ws || this.ws.readyState !== this.ws.OPEN) {
445
469
  log.error(`cannot send signal request before connected, type: ${message?.$case}`);
446
470
  return;
447
471
  }
@@ -37,7 +37,7 @@ export class TURNCheck extends Checker {
37
37
  } else if (hasTURN && !hasTLS) {
38
38
  this.appendWarning('TURN is configured server side, but TURN/TLS is unavailable.');
39
39
  }
40
- signalClient.close();
40
+ await signalClient.close();
41
41
  if (this.connectOptions?.rtcConfig?.iceServers || hasTURN) {
42
42
  await this.room!.connect(this.url, this.token, {
43
43
  rtcConfig: {
@@ -17,6 +17,6 @@ export class WebSocketCheck extends Checker {
17
17
  maxRetries: 0,
18
18
  });
19
19
  this.appendMessage(`Connected to server, version ${joinRes.serverVersion}.`);
20
- signalClient.close();
20
+ await signalClient.close();
21
21
  }
22
22
  }
package/src/index.ts CHANGED
@@ -23,8 +23,11 @@ import {
23
23
  supportsAdaptiveStream,
24
24
  supportsAV1,
25
25
  supportsDynacast,
26
+ createAudioAnalyser,
26
27
  } from './room/utils';
27
28
 
29
+ import type { AudioAnalyserOptions } from './room/utils';
30
+
28
31
  export * from './options';
29
32
  export * from './room/errors';
30
33
  export * from './room/events';
@@ -43,6 +46,8 @@ export {
43
46
  supportsAdaptiveStream,
44
47
  supportsDynacast,
45
48
  supportsAV1,
49
+ createAudioAnalyser,
50
+ AudioAnalyserOptions,
46
51
  LogLevel,
47
52
  Room,
48
53
  ConnectionState,
package/src/options.ts CHANGED
@@ -7,6 +7,10 @@ import type {
7
7
  } from './room/track/options';
8
8
  import type { AdaptiveStreamSettings } from './room/track/types';
9
9
 
10
+ export interface WebAudioSettings {
11
+ audioContext: AudioContext;
12
+ }
13
+
10
14
  /**
11
15
  * @internal
12
16
  */
@@ -72,7 +76,7 @@ export interface InternalRoomOptions {
72
76
  * experimental flag, mix all audio tracks in web audio
73
77
  */
74
78
 
75
- expWebAudioMix: boolean;
79
+ expWebAudioMix: boolean | WebAudioSettings;
76
80
  }
77
81
 
78
82
  /**
@@ -38,6 +38,7 @@ import type { TrackPublishOptions, VideoCodec } from './track/options';
38
38
  import { Track } from './track/Track';
39
39
  import {
40
40
  isWeb,
41
+ Mutex,
41
42
  sleep,
42
43
  supportsAddTrack,
43
44
  supportsSetCodecPreferences,
@@ -130,12 +131,15 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
130
131
  /** specifies how often an initial join connection is allowed to retry */
131
132
  private maxJoinAttempts: number = 1;
132
133
 
134
+ private closingLock: Mutex;
135
+
133
136
  constructor(private options: InternalRoomOptions) {
134
137
  super();
135
138
  this.client = new SignalClient();
136
139
  this.client.signalLatency = this.options.expSignalLatency;
137
140
  this.reconnectPolicy = this.options.reconnectPolicy;
138
141
  this.registerOnLineListener();
142
+ this.closingLock = new Mutex();
139
143
  }
140
144
 
141
145
  async join(
@@ -179,30 +183,35 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
179
183
  }
180
184
  }
181
185
 
182
- close() {
183
- this._isClosed = true;
184
- this.removeAllListeners();
185
- this.deregisterOnLineListener();
186
- this.clearPendingReconnect();
187
- if (this.publisher && this.publisher.pc.signalingState !== 'closed') {
188
- this.publisher.pc.getSenders().forEach((sender) => {
189
- try {
190
- // TODO: react-native-webrtc doesn't have removeTrack yet.
191
- if (this.publisher?.pc.removeTrack) {
192
- this.publisher?.pc.removeTrack(sender);
186
+ async close() {
187
+ const unlock = await this.closingLock.lock();
188
+ try {
189
+ this._isClosed = true;
190
+ this.removeAllListeners();
191
+ this.deregisterOnLineListener();
192
+ this.clearPendingReconnect();
193
+ if (this.publisher && this.publisher.pc.signalingState !== 'closed') {
194
+ this.publisher.pc.getSenders().forEach((sender) => {
195
+ try {
196
+ // TODO: react-native-webrtc doesn't have removeTrack yet.
197
+ if (this.publisher?.pc.removeTrack) {
198
+ this.publisher?.pc.removeTrack(sender);
199
+ }
200
+ } catch (e) {
201
+ log.warn('could not removeTrack', { error: e });
193
202
  }
194
- } catch (e) {
195
- log.warn('could not removeTrack', { error: e });
196
- }
197
- });
198
- this.publisher.close();
199
- this.publisher = undefined;
200
- }
201
- if (this.subscriber) {
202
- this.subscriber.close();
203
- this.subscriber = undefined;
203
+ });
204
+ this.publisher.close();
205
+ this.publisher = undefined;
206
+ }
207
+ if (this.subscriber) {
208
+ this.subscriber.close();
209
+ this.subscriber = undefined;
210
+ }
211
+ await this.client.close();
212
+ } finally {
213
+ unlock();
204
214
  }
205
- this.client.close();
206
215
  }
207
216
 
208
217
  addTrack(req: AddTrackRequest): Promise<TrackInfo> {
@@ -335,7 +344,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
335
344
  const shouldEmit = this.pcState === PCState.New;
336
345
  this.pcState = PCState.Connected;
337
346
  if (shouldEmit) {
338
- this.emit(EngineEvent.Connected);
347
+ this.emit(EngineEvent.Connected, joinResponse);
339
348
  }
340
349
  } else if (primaryPC.connectionState === 'failed') {
341
350
  // on Safari, PeerConnection will switch to 'disconnected' during renegotiation
@@ -784,9 +793,9 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
784
793
  }
785
794
 
786
795
  if (this.client.isConnected) {
787
- this.client.sendLeave();
796
+ await this.client.sendLeave();
788
797
  }
789
- this.client.close();
798
+ await this.client.close();
790
799
  this.primaryPC = undefined;
791
800
  this.publisher?.close();
792
801
  this.publisher = undefined;
@@ -1040,7 +1049,7 @@ async function getConnectedAddress(pc: RTCPeerConnection): Promise<string | unde
1040
1049
  class SignalReconnectError extends Error {}
1041
1050
 
1042
1051
  export type EngineEventCallbacks = {
1043
- connected: () => void;
1052
+ connected: (joinResp: JoinResponse) => void;
1044
1053
  disconnected: (reason?: DisconnectReason) => void;
1045
1054
  resuming: () => void;
1046
1055
  resumed: () => void;