livekit-client 1.5.0 → 1.6.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.
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;