livekit-client 1.9.1 → 1.9.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (106) hide show
  1. package/dist/livekit-client.esm.mjs +3338 -3037
  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 +98 -97
  6. package/dist/src/api/SignalClient.d.ts.map +1 -1
  7. package/dist/src/connectionHelper/ConnectionCheck.d.ts +25 -24
  8. package/dist/src/connectionHelper/ConnectionCheck.d.ts.map +1 -1
  9. package/dist/src/connectionHelper/checks/Checker.d.ts +58 -58
  10. package/dist/src/connectionHelper/checks/publishAudio.d.ts +5 -5
  11. package/dist/src/connectionHelper/checks/publishVideo.d.ts +5 -5
  12. package/dist/src/connectionHelper/checks/reconnect.d.ts +5 -5
  13. package/dist/src/connectionHelper/checks/turn.d.ts +5 -5
  14. package/dist/src/connectionHelper/checks/webrtc.d.ts +5 -5
  15. package/dist/src/connectionHelper/checks/websocket.d.ts +5 -5
  16. package/dist/src/index.d.ts +34 -31
  17. package/dist/src/index.d.ts.map +1 -1
  18. package/dist/src/logger.d.ts +25 -25
  19. package/dist/src/options.d.ts +98 -98
  20. package/dist/src/proto/google/protobuf/timestamp.d.ts +145 -145
  21. package/dist/src/proto/livekit_models.d.ts +2300 -2300
  22. package/dist/src/proto/livekit_rtc.d.ts +14032 -14032
  23. package/dist/src/room/DefaultReconnectPolicy.d.ts +7 -7
  24. package/dist/src/room/DeviceManager.d.ts +8 -8
  25. package/dist/src/room/PCTransport.d.ts +37 -37
  26. package/dist/src/room/PCTransport.d.ts.map +1 -1
  27. package/dist/src/room/RTCEngine.d.ts +126 -120
  28. package/dist/src/room/RTCEngine.d.ts.map +1 -1
  29. package/dist/src/room/ReconnectPolicy.d.ts +23 -23
  30. package/dist/src/room/RegionUrlProvider.d.ts +13 -13
  31. package/dist/src/room/Room.d.ts +232 -229
  32. package/dist/src/room/Room.d.ts.map +1 -1
  33. package/dist/src/room/defaults.d.ts +7 -7
  34. package/dist/src/room/defaults.d.ts.map +1 -1
  35. package/dist/src/room/errors.d.ts +42 -42
  36. package/dist/src/room/events.d.ts +455 -455
  37. package/dist/src/room/participant/LocalParticipant.d.ts +170 -170
  38. package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
  39. package/dist/src/room/participant/Participant.d.ts +93 -93
  40. package/dist/src/room/participant/ParticipantTrackPermission.d.ts +25 -25
  41. package/dist/src/room/participant/RemoteParticipant.d.ts +52 -51
  42. package/dist/src/room/participant/RemoteParticipant.d.ts.map +1 -1
  43. package/dist/src/room/participant/publishUtils.d.ts +19 -18
  44. package/dist/src/room/participant/publishUtils.d.ts.map +1 -1
  45. package/dist/src/room/stats.d.ts +66 -66
  46. package/dist/src/room/timers.d.ts +12 -12
  47. package/dist/src/room/track/LocalAudioTrack.d.ts +24 -24
  48. package/dist/src/room/track/LocalAudioTrack.d.ts.map +1 -1
  49. package/dist/src/room/track/LocalTrack.d.ts +43 -43
  50. package/dist/src/room/track/LocalTrackPublication.d.ts +37 -37
  51. package/dist/src/room/track/LocalVideoTrack.d.ts +53 -53
  52. package/dist/src/room/track/LocalVideoTrack.d.ts.map +1 -1
  53. package/dist/src/room/track/RemoteAudioTrack.d.ts +52 -52
  54. package/dist/src/room/track/RemoteAudioTrack.d.ts.map +1 -1
  55. package/dist/src/room/track/RemoteTrack.d.ts +14 -14
  56. package/dist/src/room/track/RemoteTrackPublication.d.ts +60 -60
  57. package/dist/src/room/track/RemoteVideoTrack.d.ts +52 -52
  58. package/dist/src/room/track/RemoteVideoTrack.d.ts.map +1 -1
  59. package/dist/src/room/track/Track.d.ts +124 -124
  60. package/dist/src/room/track/TrackPublication.d.ts +67 -67
  61. package/dist/src/room/track/create.d.ts +23 -23
  62. package/dist/src/room/track/create.d.ts.map +1 -1
  63. package/dist/src/room/track/options.d.ts +255 -247
  64. package/dist/src/room/track/options.d.ts.map +1 -1
  65. package/dist/src/room/track/types.d.ts +22 -22
  66. package/dist/src/room/track/utils.d.ts +13 -13
  67. package/dist/src/room/types.d.ts +25 -25
  68. package/dist/src/room/utils.d.ts +86 -85
  69. package/dist/src/room/utils.d.ts.map +1 -1
  70. package/dist/src/test/MockMediaStreamTrack.d.ts +25 -25
  71. package/dist/src/test/mocks.d.ts +10 -10
  72. package/dist/src/version.d.ts +2 -2
  73. package/dist/ts4.2/src/api/SignalClient.d.ts +1 -0
  74. package/dist/ts4.2/src/connectionHelper/ConnectionCheck.d.ts +2 -1
  75. package/dist/ts4.2/src/index.d.ts +8 -6
  76. package/dist/ts4.2/src/room/PCTransport.d.ts +1 -1
  77. package/dist/ts4.2/src/room/RTCEngine.d.ts +7 -1
  78. package/dist/ts4.2/src/room/Room.d.ts +3 -0
  79. package/dist/ts4.2/src/room/defaults.d.ts +1 -1
  80. package/dist/ts4.2/src/room/participant/LocalParticipant.d.ts +4 -4
  81. package/dist/ts4.2/src/room/participant/RemoteParticipant.d.ts +2 -1
  82. package/dist/ts4.2/src/room/participant/publishUtils.d.ts +2 -1
  83. package/dist/ts4.2/src/room/track/LocalAudioTrack.d.ts +1 -1
  84. package/dist/ts4.2/src/room/track/LocalVideoTrack.d.ts +1 -1
  85. package/dist/ts4.2/src/room/track/RemoteAudioTrack.d.ts +1 -1
  86. package/dist/ts4.2/src/room/track/create.d.ts +1 -1
  87. package/dist/ts4.2/src/room/track/options.d.ts +10 -2
  88. package/dist/ts4.2/src/room/utils.d.ts +1 -0
  89. package/package.json +12 -12
  90. package/src/api/SignalClient.ts +19 -16
  91. package/src/connectionHelper/ConnectionCheck.ts +2 -1
  92. package/src/index.ts +13 -8
  93. package/src/room/PCTransport.ts +9 -5
  94. package/src/room/RTCEngine.ts +44 -4
  95. package/src/room/Room.ts +87 -42
  96. package/src/room/defaults.ts +6 -4
  97. package/src/room/participant/LocalParticipant.ts +10 -9
  98. package/src/room/participant/RemoteParticipant.ts +2 -1
  99. package/src/room/participant/publishUtils.ts +7 -5
  100. package/src/room/track/LocalAudioTrack.ts +2 -1
  101. package/src/room/track/LocalVideoTrack.ts +3 -2
  102. package/src/room/track/RemoteAudioTrack.ts +2 -1
  103. package/src/room/track/RemoteVideoTrack.ts +4 -8
  104. package/src/room/track/create.ts +2 -2
  105. package/src/room/track/options.ts +23 -7
  106. package/src/room/utils.ts +15 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "livekit-client",
3
- "version": "1.9.1",
3
+ "version": "1.9.3",
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",
@@ -51,8 +51,8 @@
51
51
  "webrtc-adapter": "^8.1.1"
52
52
  },
53
53
  "devDependencies": {
54
- "@babel/core": "7.21.4",
55
- "@babel/preset-env": "7.21.4",
54
+ "@babel/core": "7.21.8",
55
+ "@babel/preset-env": "7.21.5",
56
56
  "@changesets/cli": "2.26.1",
57
57
  "@livekit/changesets-changelog-github": "^0.0.4",
58
58
  "@rollup/plugin-babel": "6.0.3",
@@ -61,31 +61,31 @@
61
61
  "@rollup/plugin-node-resolve": "15.0.2",
62
62
  "@rollup/plugin-terser": "^0.4.0",
63
63
  "@trivago/prettier-plugin-sort-imports": "^4.1.1",
64
- "@types/jest": "29.5.0",
64
+ "@types/jest": "29.5.1",
65
65
  "@types/sdp-transform": "2.4.6",
66
66
  "@types/ua-parser-js": "0.7.36",
67
67
  "@types/ws": "8.5.4",
68
- "@typescript-eslint/eslint-plugin": "5.58.0",
69
- "@typescript-eslint/parser": "5.58.0",
68
+ "@typescript-eslint/eslint-plugin": "5.59.2",
69
+ "@typescript-eslint/parser": "5.59.2",
70
70
  "downlevel-dts": "^0.11.0",
71
- "eslint": "8.38.0",
71
+ "eslint": "8.39.0",
72
72
  "eslint-config-airbnb-typescript": "17.0.0",
73
73
  "eslint-config-prettier": "8.8.0",
74
74
  "eslint-plugin-import": "2.27.5",
75
75
  "gh-pages": "5.0.0",
76
76
  "jest": "29.5.0",
77
77
  "prettier": "^2.8.8",
78
- "rollup": "3.20.2",
78
+ "rollup": "3.21.3",
79
79
  "rollup-plugin-delete": "^2.0.0",
80
80
  "rollup-plugin-filesize": "10.0.0",
81
81
  "rollup-plugin-re": "1.0.7",
82
82
  "rollup-plugin-typescript2": "0.34.1",
83
83
  "ts-jest": "29.1.0",
84
- "ts-proto": "1.146.0",
85
- "typedoc": "0.24.1",
84
+ "ts-proto": "1.147.1",
85
+ "typedoc": "0.24.6",
86
86
  "typedoc-plugin-no-inherit": "1.4.0",
87
- "typescript": "4.9.5",
88
- "vite": "4.2.1"
87
+ "typescript": "5.0.4",
88
+ "vite": "4.3.4"
89
89
  },
90
90
  "browserslist": [
91
91
  "safari >= 11",
@@ -321,14 +321,8 @@ export class SignalClient {
321
321
  };
322
322
 
323
323
  this.ws.onclose = (ev: CloseEvent) => {
324
- if (!this.isConnected) return;
325
-
326
- log.debug(`websocket connection closed: ${ev.reason}`);
327
- this.isConnected = false;
328
- if (this.onClose) {
329
- this.onClose(ev.reason);
330
- }
331
- this.ws = undefined;
324
+ log.warn(`websocket closed`, { ev });
325
+ this.handleOnClose(ev.reason);
332
326
  };
333
327
  });
334
328
  }
@@ -351,12 +345,14 @@ export class SignalClient {
351
345
  }
352
346
  });
353
347
 
354
- this.ws.close();
355
- // 250ms grace period for ws to close gracefully
356
- await Promise.race([closePromise, sleep(250)]);
348
+ if (this.ws.readyState < this.ws.CLOSING) {
349
+ this.ws.close();
350
+ // 250ms grace period for ws to close gracefully
351
+ await Promise.race([closePromise, sleep(250)]);
352
+ }
353
+ this.ws = undefined;
354
+ this.clearPingInterval();
357
355
  }
358
- this.ws = undefined;
359
- this.clearPingInterval();
360
356
  } finally {
361
357
  unlock();
362
358
  }
@@ -618,6 +614,15 @@ export class SignalClient {
618
614
  this.isReconnecting = false;
619
615
  }
620
616
 
617
+ private async handleOnClose(reason: string) {
618
+ if (!this.isConnected) return;
619
+ await this.close();
620
+ log.debug(`websocket connection closed: ${reason}`);
621
+ if (this.onClose) {
622
+ this.onClose(reason);
623
+ }
624
+ }
625
+
621
626
  private handleWSError(ev: Event) {
622
627
  log.error('websocket error', ev);
623
628
  }
@@ -638,9 +643,7 @@ export class SignalClient {
638
643
  Date.now() - this.pingTimeoutDuration! * 1000,
639
644
  ).toUTCString()}`,
640
645
  );
641
- if (this.onClose) {
642
- this.onClose('ping timeout');
643
- }
646
+ this.handleOnClose('ping timeout');
644
647
  }, this.pingTimeoutDuration * 1000);
645
648
  }
646
649
 
@@ -1,6 +1,7 @@
1
1
  import EventEmitter from 'events';
2
2
  import type TypedEmitter from 'typed-emitter';
3
- import { CheckInfo, CheckStatus, Checker, InstantiableCheck } from './checks/Checker';
3
+ import { CheckStatus, Checker } from './checks/Checker';
4
+ import type { CheckInfo, InstantiableCheck } from './checks/Checker';
4
5
  import { PublishAudioCheck } from './checks/publishAudio';
5
6
  import { PublishVideoCheck } from './checks/publishVideo';
6
7
  import { ReconnectCheck } from './checks/reconnect';
package/src/index.ts CHANGED
@@ -14,11 +14,12 @@ import LocalVideoTrack from './room/track/LocalVideoTrack';
14
14
  import RemoteAudioTrack from './room/track/RemoteAudioTrack';
15
15
  import RemoteTrack from './room/track/RemoteTrack';
16
16
  import RemoteTrackPublication from './room/track/RemoteTrackPublication';
17
- import RemoteVideoTrack, { type ElementInfo } from './room/track/RemoteVideoTrack';
17
+ import type { ElementInfo } from './room/track/RemoteVideoTrack';
18
+ import RemoteVideoTrack from './room/track/RemoteVideoTrack';
18
19
  import { TrackPublication } from './room/track/TrackPublication';
19
20
  import type { LiveKitReactNativeInfo } from './room/types';
21
+ import type { AudioAnalyserOptions } from './room/utils';
20
22
  import {
21
- type AudioAnalyserOptions,
22
23
  createAudioAnalyser,
23
24
  getEmptyAudioStreamTrack,
24
25
  getEmptyVideoStreamTrack,
@@ -26,18 +27,19 @@ import {
26
27
  supportsAV1,
27
28
  supportsAdaptiveStream,
28
29
  supportsDynacast,
30
+ supportsVP9,
29
31
  } from './room/utils';
30
32
 
33
+ export * from './connectionHelper/ConnectionCheck';
31
34
  export * from './options';
32
35
  export * from './room/errors';
33
36
  export * from './room/events';
34
- export type { DataPublishOptions, SimulationScenario } from './room/types';
37
+ export * from './room/track/Track';
35
38
  export * from './room/track/create';
36
39
  export * from './room/track/options';
37
- export * from './room/track/Track';
38
40
  export * from './room/track/types';
41
+ export type { DataPublishOptions, SimulationScenario } from './room/types';
39
42
  export * from './version';
40
- export * from './connectionHelper/ConnectionCheck';
41
43
  export {
42
44
  setLogLevel,
43
45
  setLogExtension,
@@ -47,8 +49,8 @@ export {
47
49
  supportsAdaptiveStream,
48
50
  supportsDynacast,
49
51
  supportsAV1,
52
+ supportsVP9,
50
53
  createAudioAnalyser,
51
- AudioAnalyserOptions,
52
54
  LogLevel,
53
55
  Room,
54
56
  ConnectionState,
@@ -66,12 +68,15 @@ export {
66
68
  RemoteAudioTrack,
67
69
  RemoteVideoTrack,
68
70
  RemoteTrackPublication,
69
- ParticipantTrackPermission,
70
71
  TrackPublication,
71
72
  VideoQuality,
72
73
  ConnectionQuality,
73
- ElementInfo,
74
74
  DefaultReconnectPolicy,
75
75
  CriticalTimers,
76
+ };
77
+ export type {
78
+ ElementInfo,
79
+ ParticipantTrackPermission,
80
+ AudioAnalyserOptions,
76
81
  LiveKitReactNativeInfo,
77
82
  };
@@ -1,9 +1,10 @@
1
1
  import EventEmitter from 'events';
2
- import { MediaDescription, parse, write } from 'sdp-transform';
2
+ import { parse, write } from 'sdp-transform';
3
+ import type { MediaDescription } from 'sdp-transform';
3
4
  import { debounce } from 'ts-debounce';
4
5
  import log from '../logger';
5
6
  import { NegotiationError } from './errors';
6
- import { ddExtensionURI, isSVCCodec } from './utils';
7
+ import { ddExtensionURI, isChromiumBased, isSVCCodec } from './utils';
7
8
 
8
9
  /** @internal */
9
10
  interface TrackBitrateInfo {
@@ -35,9 +36,12 @@ export default class PCTransport extends EventEmitter {
35
36
 
36
37
  onOffer?: (offer: RTCSessionDescriptionInit) => void;
37
38
 
38
- constructor(config?: RTCConfiguration) {
39
+ constructor(config?: RTCConfiguration, mediaConstraints: Record<string, unknown> = {}) {
39
40
  super();
40
- this.pc = new RTCPeerConnection(config);
41
+ this.pc = isChromiumBased()
42
+ ? // @ts-expect-error chrome allows additional media constraints to be passed into the RTCPeerConnection constructor
43
+ new RTCPeerConnection(config, mediaConstraints)
44
+ : new RTCPeerConnection(config);
41
45
  }
42
46
 
43
47
  get isICEConnected(): boolean {
@@ -289,7 +293,7 @@ function ensureVideoDDExtensionForSVC(
289
293
  payloads?: string | undefined;
290
294
  } & MediaDescription,
291
295
  ) {
292
- const codec = media.rtp.at(0)?.codec?.toLowerCase();
296
+ const codec = media.rtp[0]?.codec?.toLowerCase();
293
297
  if (!isSVCCodec(codec)) {
294
298
  return;
295
299
  }
@@ -1,6 +1,7 @@
1
1
  import { EventEmitter } from 'events';
2
2
  import type TypedEventEmitter from 'typed-emitter';
3
- import { SignalClient, SignalOptions } from '../api/SignalClient';
3
+ import { SignalClient } from '../api/SignalClient';
4
+ import type { SignalOptions } from '../api/SignalClient';
4
5
  import log from '../logger';
5
6
  import type { InternalRoomOptions } from '../options';
6
7
  import {
@@ -77,6 +78,11 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
77
78
 
78
79
  fullReconnectOnNext: boolean = false;
79
80
 
81
+ /**
82
+ * @internal
83
+ */
84
+ latestJoinResponse?: JoinResponse;
85
+
80
86
  get isClosed() {
81
87
  return this._isClosed;
82
88
  }
@@ -171,6 +177,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
171
177
  this.joinAttempts += 1;
172
178
  const joinResponse = await this.client.join(url, token, opts, abortSignal);
173
179
  this._isClosed = false;
180
+ this.latestJoinResponse = joinResponse;
174
181
 
175
182
  this.subscriberPrimary = joinResponse.subscriberPrimary;
176
183
  if (!this.publisher) {
@@ -307,8 +314,8 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
307
314
  this.participantSid = joinResponse.participant?.sid;
308
315
 
309
316
  const rtcConfig = this.makeRTCConfiguration(joinResponse);
310
-
311
- this.publisher = new PCTransport(rtcConfig);
317
+ const googConstraints = { optional: [{ googDscp: true }] };
318
+ this.publisher = new PCTransport(rtcConfig, googConstraints);
312
319
  this.subscriber = new PCTransport(rtcConfig);
313
320
 
314
321
  this.emit(EngineEvent.TransportsCreated, this.publisher, this.subscriber);
@@ -821,7 +828,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
821
828
  }ms. giving up`,
822
829
  );
823
830
  this.emit(EngineEvent.Disconnected);
824
- this.close();
831
+ await this.close();
825
832
  }
826
833
  } finally {
827
834
  this.attemptingReconnect = false;
@@ -1120,6 +1127,39 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
1120
1127
  await this.ensureDataTransportConnected(kind, false);
1121
1128
  }
1122
1129
 
1130
+ /* @internal */
1131
+ verifyTransport(): boolean {
1132
+ // primary connection
1133
+ if (!this.primaryPC) {
1134
+ return false;
1135
+ }
1136
+ if (
1137
+ this.primaryPC.connectionState === 'closed' ||
1138
+ this.primaryPC.connectionState === 'failed'
1139
+ ) {
1140
+ return false;
1141
+ }
1142
+
1143
+ // also verify publisher connection if it's needed or different
1144
+ if (this.hasPublished && this.subscriberPrimary) {
1145
+ if (!this.publisher) {
1146
+ return false;
1147
+ }
1148
+ if (
1149
+ this.publisher.pc.connectionState === 'closed' ||
1150
+ this.publisher.pc.connectionState === 'failed'
1151
+ ) {
1152
+ return false;
1153
+ }
1154
+ }
1155
+
1156
+ // ensure signal is connected
1157
+ if (!this.client.ws || this.client.ws.readyState === WebSocket.CLOSED) {
1158
+ return false;
1159
+ }
1160
+ return true;
1161
+ }
1162
+
1123
1163
  /** @internal */
1124
1164
  negotiate(): Promise<void> {
1125
1165
  // observe signal state
package/src/room/Room.ts CHANGED
@@ -45,6 +45,7 @@ import LocalParticipant from './participant/LocalParticipant';
45
45
  import type Participant from './participant/Participant';
46
46
  import type { ConnectionQuality } from './participant/Participant';
47
47
  import RemoteParticipant from './participant/RemoteParticipant';
48
+ import CriticalTimers from './timers';
48
49
  import LocalAudioTrack from './track/LocalAudioTrack';
49
50
  import LocalTrackPublication from './track/LocalTrackPublication';
50
51
  import LocalVideoTrack from './track/LocalVideoTrack';
@@ -73,6 +74,8 @@ export enum ConnectionState {
73
74
  Reconnecting = 'reconnecting',
74
75
  }
75
76
 
77
+ const connectionReconcileFrequency = 2 * 1000;
78
+
76
79
  /** @deprecated RoomState has been renamed to [[ConnectionState]] */
77
80
  export const RoomState = ConnectionState;
78
81
 
@@ -126,6 +129,8 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
126
129
 
127
130
  private cachedParticipantSids: Array<string>;
128
131
 
132
+ private connectionReconcileInterval?: ReturnType<typeof setInterval>;
133
+
129
134
  /**
130
135
  * Creates a new Room, the primary construct for a LiveKit session.
131
136
  * @param options
@@ -189,7 +194,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
189
194
  }
190
195
 
191
196
  private maybeCreateEngine() {
192
- if (this.engine) {
197
+ if (this.engine && !this.engine.isClosed) {
193
198
  return;
194
199
  }
195
200
 
@@ -215,6 +220,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
215
220
  .on(EngineEvent.ActiveSpeakersUpdate, this.handleActiveSpeakersUpdate)
216
221
  .on(EngineEvent.DataPacketReceived, this.handleDataPacket)
217
222
  .on(EngineEvent.Resuming, () => {
223
+ this.clearConnectionReconcile();
218
224
  if (this.setAndEmitConnectionState(ConnectionState.Reconnecting)) {
219
225
  this.emit(RoomEvent.Reconnecting);
220
226
  }
@@ -223,6 +229,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
223
229
  .on(EngineEvent.Resumed, () => {
224
230
  this.setAndEmitConnectionState(ConnectionState.Connected);
225
231
  this.emit(RoomEvent.Reconnected);
232
+ this.registerConnectionReconcile();
226
233
  this.updateSubscriptions();
227
234
 
228
235
  // once reconnected, figure out if any participants connected during reconnect and emit events for it
@@ -488,6 +495,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
488
495
  }
489
496
  this.setAndEmitConnectionState(ConnectionState.Connected);
490
497
  this.emit(RoomEvent.Connected);
498
+ this.registerConnectionReconcile();
491
499
  };
492
500
 
493
501
  /**
@@ -829,6 +837,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
829
837
  }
830
838
 
831
839
  private handleRestarting = () => {
840
+ this.clearConnectionReconcile();
832
841
  // also unwind existing participants & existing subscriptions
833
842
  for (const p of this.participants.values()) {
834
843
  this.handleParticipantDisconnected(p.sid, p);
@@ -894,6 +903,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
894
903
  }
895
904
  this.setAndEmitConnectionState(ConnectionState.Connected);
896
905
  this.emit(RoomEvent.Reconnected);
906
+ this.registerConnectionReconcile();
897
907
 
898
908
  // emit participant connected events after connection has been re-established
899
909
  this.participants.forEach((participant) => {
@@ -902,57 +912,61 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
902
912
  };
903
913
 
904
914
  private handleDisconnect(shouldStopTracks = true, reason?: DisconnectReason) {
915
+ this.clearConnectionReconcile();
905
916
  if (this.state === ConnectionState.Disconnected) {
906
917
  return;
907
918
  }
908
919
 
909
- this.participants.forEach((p) => {
910
- p.tracks.forEach((pub) => {
911
- p.unpublishTrack(pub.trackSid);
920
+ try {
921
+ this.participants.forEach((p) => {
922
+ p.tracks.forEach((pub) => {
923
+ p.unpublishTrack(pub.trackSid);
924
+ });
912
925
  });
913
- });
914
926
 
915
- this.localParticipant.tracks.forEach((pub) => {
916
- if (pub.track) {
917
- this.localParticipant.unpublishTrack(pub.track, shouldStopTracks);
918
- }
919
- if (shouldStopTracks) {
920
- pub.track?.detach();
921
- pub.track?.stop();
922
- }
923
- });
927
+ this.localParticipant.tracks.forEach((pub) => {
928
+ if (pub.track) {
929
+ this.localParticipant.unpublishTrack(pub.track, shouldStopTracks);
930
+ }
931
+ if (shouldStopTracks) {
932
+ pub.track?.detach();
933
+ pub.track?.stop();
934
+ }
935
+ });
924
936
 
925
- this.localParticipant
926
- .off(ParticipantEvent.ParticipantMetadataChanged, this.onLocalParticipantMetadataChanged)
927
- .off(ParticipantEvent.ParticipantNameChanged, this.onLocalParticipantNameChanged)
928
- .off(ParticipantEvent.TrackMuted, this.onLocalTrackMuted)
929
- .off(ParticipantEvent.TrackUnmuted, this.onLocalTrackUnmuted)
930
- .off(ParticipantEvent.LocalTrackPublished, this.onLocalTrackPublished)
931
- .off(ParticipantEvent.LocalTrackUnpublished, this.onLocalTrackUnpublished)
932
- .off(ParticipantEvent.ConnectionQualityChanged, this.onLocalConnectionQualityChanged)
933
- .off(ParticipantEvent.MediaDevicesError, this.onMediaDevicesError)
934
- .off(
935
- ParticipantEvent.ParticipantPermissionsChanged,
936
- this.onLocalParticipantPermissionsChanged,
937
- );
937
+ this.localParticipant
938
+ .off(ParticipantEvent.ParticipantMetadataChanged, this.onLocalParticipantMetadataChanged)
939
+ .off(ParticipantEvent.ParticipantNameChanged, this.onLocalParticipantNameChanged)
940
+ .off(ParticipantEvent.TrackMuted, this.onLocalTrackMuted)
941
+ .off(ParticipantEvent.TrackUnmuted, this.onLocalTrackUnmuted)
942
+ .off(ParticipantEvent.LocalTrackPublished, this.onLocalTrackPublished)
943
+ .off(ParticipantEvent.LocalTrackUnpublished, this.onLocalTrackUnpublished)
944
+ .off(ParticipantEvent.ConnectionQualityChanged, this.onLocalConnectionQualityChanged)
945
+ .off(ParticipantEvent.MediaDevicesError, this.onMediaDevicesError)
946
+ .off(
947
+ ParticipantEvent.ParticipantPermissionsChanged,
948
+ this.onLocalParticipantPermissionsChanged,
949
+ );
938
950
 
939
- this.localParticipant.tracks.clear();
940
- this.localParticipant.videoTracks.clear();
941
- this.localParticipant.audioTracks.clear();
951
+ this.localParticipant.tracks.clear();
952
+ this.localParticipant.videoTracks.clear();
953
+ this.localParticipant.audioTracks.clear();
942
954
 
943
- this.participants.clear();
944
- this.activeSpeakers = [];
945
- if (this.audioContext && typeof this.options.expWebAudioMix === 'boolean') {
946
- this.audioContext.close();
947
- this.audioContext = undefined;
948
- }
949
- if (isWeb()) {
950
- window.removeEventListener('beforeunload', this.onPageLeave);
951
- window.removeEventListener('pagehide', this.onPageLeave);
952
- navigator.mediaDevices?.removeEventListener('devicechange', this.handleDeviceChange);
955
+ this.participants.clear();
956
+ this.activeSpeakers = [];
957
+ if (this.audioContext && typeof this.options.expWebAudioMix === 'boolean') {
958
+ this.audioContext.close();
959
+ this.audioContext = undefined;
960
+ }
961
+ if (isWeb()) {
962
+ window.removeEventListener('beforeunload', this.onPageLeave);
963
+ window.removeEventListener('pagehide', this.onPageLeave);
964
+ navigator.mediaDevices?.removeEventListener('devicechange', this.handleDeviceChange);
965
+ }
966
+ } finally {
967
+ this.setAndEmitConnectionState(ConnectionState.Disconnected);
968
+ this.emit(RoomEvent.Disconnected, reason);
953
969
  }
954
- this.setAndEmitConnectionState(ConnectionState.Disconnected);
955
- this.emit(RoomEvent.Disconnected, reason);
956
970
  }
957
971
 
958
972
  private handleParticipantUpdates = (participantInfos: ParticipantInfo[]) => {
@@ -1341,6 +1355,37 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
1341
1355
  }
1342
1356
  }
1343
1357
 
1358
+ private registerConnectionReconcile() {
1359
+ this.clearConnectionReconcile();
1360
+ let consecutiveFailures = 0;
1361
+ this.connectionReconcileInterval = CriticalTimers.setInterval(() => {
1362
+ if (
1363
+ // ensure we didn't tear it down
1364
+ !this.engine ||
1365
+ // engine detected close, but Room missed it
1366
+ this.engine.isClosed ||
1367
+ // transports failed without notifying engine
1368
+ !this.engine.verifyTransport()
1369
+ ) {
1370
+ consecutiveFailures++;
1371
+ log.warn('detected connection state mismatch', { numFailures: consecutiveFailures });
1372
+ if (consecutiveFailures >= 3)
1373
+ this.handleDisconnect(
1374
+ this.options.stopLocalTrackOnUnpublish,
1375
+ DisconnectReason.UNKNOWN_REASON,
1376
+ );
1377
+ } else {
1378
+ consecutiveFailures = 0;
1379
+ }
1380
+ }, connectionReconcileFrequency);
1381
+ }
1382
+
1383
+ private clearConnectionReconcile() {
1384
+ if (this.connectionReconcileInterval) {
1385
+ CriticalTimers.clearInterval(this.connectionReconcileInterval);
1386
+ }
1387
+ }
1388
+
1344
1389
  private setAndEmitConnectionState(state: ConnectionState): boolean {
1345
1390
  if (state === this.state) {
1346
1391
  // unchanged
@@ -1,16 +1,18 @@
1
1
  import type { InternalRoomConnectOptions, InternalRoomOptions } from '../options';
2
2
  import DefaultReconnectPolicy from './DefaultReconnectPolicy';
3
- import {
3
+ import { AudioPresets, ScreenSharePresets, VideoPresets } from './track/options';
4
+ import type {
4
5
  AudioCaptureOptions,
5
- AudioPresets,
6
- ScreenSharePresets,
7
6
  TrackPublishDefaults,
8
7
  VideoCaptureOptions,
9
- VideoPresets,
10
8
  } from './track/options';
11
9
 
12
10
  export const publishDefaults: TrackPublishDefaults = {
11
+ /**
12
+ * @deprecated
13
+ */
13
14
  audioBitrate: AudioPresets.music.maxBitrate,
15
+ audioPreset: AudioPresets.music,
14
16
  dtx: true,
15
17
  red: true,
16
18
  forceStereo: false,
@@ -18,22 +18,21 @@ import LocalTrack from '../track/LocalTrack';
18
18
  import LocalTrackPublication from '../track/LocalTrackPublication';
19
19
  import LocalVideoTrack, { videoLayersFromEncodings } from '../track/LocalVideoTrack';
20
20
  import { Track } from '../track/Track';
21
- import {
21
+ import { ScreenSharePresets, isBackupCodec, isCodecEqual } from '../track/options';
22
+ import type {
22
23
  AudioCaptureOptions,
23
24
  BackupVideoCodec,
24
25
  CreateLocalTracksOptions,
25
26
  ScreenShareCaptureOptions,
26
- ScreenSharePresets,
27
27
  TrackPublishOptions,
28
28
  VideoCaptureOptions,
29
- isBackupCodec,
30
- isCodecEqual,
31
29
  } from '../track/options';
32
30
  import { constraintsForOptions, mergeDefaultOptions } from '../track/utils';
33
31
  import type { DataPublishOptions } from '../types';
34
32
  import { Future, isFireFox, isSVCCodec, isSafari, isWeb, supportsAV1, supportsVP9 } from '../utils';
35
33
  import Participant from './Participant';
36
- import { ParticipantTrackPermission, trackPermissionToProto } from './ParticipantTrackPermission';
34
+ import { trackPermissionToProto } from './ParticipantTrackPermission';
35
+ import type { ParticipantTrackPermission } from './ParticipantTrackPermission';
37
36
  import RemoteParticipant from './RemoteParticipant';
38
37
  import {
39
38
  computeTrackBackupEncodings,
@@ -151,7 +150,7 @@ export default class LocalParticipant extends Participant {
151
150
 
152
151
  /**
153
152
  * Sets and updates the metadata of the local participant.
154
- * Note: this requires `CanUpdateOwnMetadata` permission encoded in the token.
153
+ * Note: this requires `canUpdateOwnMetadata` permission encoded in the token.
155
154
  * @param metadata
156
155
  */
157
156
  setMetadata(metadata: string): void {
@@ -161,7 +160,7 @@ export default class LocalParticipant extends Participant {
161
160
 
162
161
  /**
163
162
  * Sets and updates the name of the local participant.
164
- * Note: this requires `CanUpdateOwnMetadata` permission encoded in the token.
163
+ * Note: this requires `canUpdateOwnMetadata` permission encoded in the token.
165
164
  * @param metadata
166
165
  */
167
166
  setName(name: string): void {
@@ -651,10 +650,12 @@ export default class LocalParticipant extends Participant {
651
650
  opts,
652
651
  );
653
652
  req.layers = videoLayersFromEncodings(req.width, req.height, simEncodings ?? encodings);
654
- } else if (track.kind === Track.Kind.Audio && opts.audioBitrate) {
653
+ } else if (track.kind === Track.Kind.Audio) {
655
654
  encodings = [
656
655
  {
657
- maxBitrate: opts.audioBitrate,
656
+ maxBitrate: opts.audioPreset?.maxBitrate ?? opts.audioBitrate,
657
+ priority: opts.audioPreset?.priority ?? 'high',
658
+ networkPriority: opts.audioPreset?.priority ?? 'high',
658
659
  },
659
660
  ];
660
661
  }
@@ -11,7 +11,8 @@ import { Track } from '../track/Track';
11
11
  import type { TrackPublication } from '../track/TrackPublication';
12
12
  import type { AudioOutputOptions } from '../track/options';
13
13
  import type { AdaptiveStreamSettings } from '../track/types';
14
- import Participant, { ParticipantEventCallbacks } from './Participant';
14
+ import Participant from './Participant';
15
+ import type { ParticipantEventCallbacks } from './Participant';
15
16
 
16
17
  export default class RemoteParticipant extends Participant {
17
18
  audioTracks: Map<string, RemoteTrackPublication>;
@@ -3,15 +3,12 @@ import { TrackInvalidError } from '../errors';
3
3
  import LocalAudioTrack from '../track/LocalAudioTrack';
4
4
  import LocalVideoTrack from '../track/LocalVideoTrack';
5
5
  import { Track } from '../track/Track';
6
- import {
6
+ import { ScreenSharePresets, VideoPreset, VideoPresets, VideoPresets43 } from '../track/options';
7
+ import type {
7
8
  BackupVideoCodec,
8
- ScreenSharePresets,
9
9
  TrackPublishOptions,
10
10
  VideoCodec,
11
11
  VideoEncoding,
12
- VideoPreset,
13
- VideoPresets,
14
- VideoPresets43,
15
12
  } from '../track/options';
16
13
  import { isSVCCodec } from '../utils';
17
14
 
@@ -61,6 +58,7 @@ export const computeDefaultScreenShareSimulcastPresets = (fromPreset: VideoPrese
61
58
  ),
62
59
  ),
63
60
  t.fps,
61
+ fromPreset.encoding.priority,
64
62
  ),
65
63
  );
66
64
  };
@@ -317,6 +315,10 @@ function encodingsFromPresets(
317
315
  if (preset.encoding.maxFramerate) {
318
316
  encoding.maxFramerate = preset.encoding.maxFramerate;
319
317
  }
318
+ if (preset.encoding.priority) {
319
+ encoding.priority = preset.encoding.priority;
320
+ encoding.networkPriority = preset.encoding.priority;
321
+ }
320
322
  encodings.push(encoding);
321
323
  });
322
324
  return encodings;