livekit-client 1.1.8 → 1.1.9

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 (36) hide show
  1. package/README.md +1 -0
  2. package/dist/livekit-client.esm.mjs +460 -118
  3. package/dist/livekit-client.esm.mjs.map +1 -1
  4. package/dist/livekit-client.umd.js +1 -1
  5. package/dist/livekit-client.umd.js.map +1 -1
  6. package/dist/src/api/SignalClient.d.ts +2 -1
  7. package/dist/src/api/SignalClient.d.ts.map +1 -1
  8. package/dist/src/index.d.ts +4 -3
  9. package/dist/src/index.d.ts.map +1 -1
  10. package/dist/src/proto/livekit_models.d.ts +234 -0
  11. package/dist/src/proto/livekit_models.d.ts.map +1 -1
  12. package/dist/src/proto/livekit_rtc.d.ts +944 -6
  13. package/dist/src/proto/livekit_rtc.d.ts.map +1 -1
  14. package/dist/src/room/RTCEngine.d.ts +2 -2
  15. package/dist/src/room/RTCEngine.d.ts.map +1 -1
  16. package/dist/src/room/Room.d.ts +2 -2
  17. package/dist/src/room/Room.d.ts.map +1 -1
  18. package/dist/src/room/track/LocalAudioTrack.d.ts.map +1 -1
  19. package/dist/src/room/track/LocalTrack.d.ts +2 -0
  20. package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
  21. package/dist/src/room/track/LocalVideoTrack.d.ts.map +1 -1
  22. package/dist/src/room/track/RemoteTrack.d.ts.map +1 -1
  23. package/package.json +3 -1
  24. package/src/api/SignalClient.ts +19 -6
  25. package/src/index.ts +6 -2
  26. package/src/proto/livekit_models.ts +179 -4
  27. package/src/proto/livekit_rtc.ts +14 -1
  28. package/src/room/RTCEngine.ts +3 -2
  29. package/src/room/Room.ts +10 -8
  30. package/src/room/track/LocalAudioTrack.ts +16 -12
  31. package/src/room/track/LocalTrack.ts +37 -25
  32. package/src/room/track/LocalVideoTrack.ts +15 -11
  33. package/src/room/track/RemoteTrack.ts +1 -0
  34. package/dist/src/api/RequestQueue.d.ts +0 -13
  35. package/dist/src/api/RequestQueue.d.ts.map +0 -1
  36. package/src/api/RequestQueue.ts +0 -53
@@ -213,6 +213,68 @@ export function clientConfigSettingToJSON(object: ClientConfigSetting): string {
213
213
  }
214
214
  }
215
215
 
216
+ export enum DisconnectReason {
217
+ UNKNOWN_REASON = 0,
218
+ CLIENT_INITIATED = 1,
219
+ DUPLICATE_IDENTITY = 2,
220
+ SERVER_SHUTDOWN = 3,
221
+ PARTICIPANT_REMOVED = 4,
222
+ ROOM_DELETED = 5,
223
+ STATE_MISMATCH = 6,
224
+ UNRECOGNIZED = -1,
225
+ }
226
+
227
+ export function disconnectReasonFromJSON(object: any): DisconnectReason {
228
+ switch (object) {
229
+ case 0:
230
+ case 'UNKNOWN_REASON':
231
+ return DisconnectReason.UNKNOWN_REASON;
232
+ case 1:
233
+ case 'CLIENT_INITIATED':
234
+ return DisconnectReason.CLIENT_INITIATED;
235
+ case 2:
236
+ case 'DUPLICATE_IDENTITY':
237
+ return DisconnectReason.DUPLICATE_IDENTITY;
238
+ case 3:
239
+ case 'SERVER_SHUTDOWN':
240
+ return DisconnectReason.SERVER_SHUTDOWN;
241
+ case 4:
242
+ case 'PARTICIPANT_REMOVED':
243
+ return DisconnectReason.PARTICIPANT_REMOVED;
244
+ case 5:
245
+ case 'ROOM_DELETED':
246
+ return DisconnectReason.ROOM_DELETED;
247
+ case 6:
248
+ case 'STATE_MISMATCH':
249
+ return DisconnectReason.STATE_MISMATCH;
250
+ case -1:
251
+ case 'UNRECOGNIZED':
252
+ default:
253
+ return DisconnectReason.UNRECOGNIZED;
254
+ }
255
+ }
256
+
257
+ export function disconnectReasonToJSON(object: DisconnectReason): string {
258
+ switch (object) {
259
+ case DisconnectReason.UNKNOWN_REASON:
260
+ return 'UNKNOWN_REASON';
261
+ case DisconnectReason.CLIENT_INITIATED:
262
+ return 'CLIENT_INITIATED';
263
+ case DisconnectReason.DUPLICATE_IDENTITY:
264
+ return 'DUPLICATE_IDENTITY';
265
+ case DisconnectReason.SERVER_SHUTDOWN:
266
+ return 'SERVER_SHUTDOWN';
267
+ case DisconnectReason.PARTICIPANT_REMOVED:
268
+ return 'PARTICIPANT_REMOVED';
269
+ case DisconnectReason.ROOM_DELETED:
270
+ return 'ROOM_DELETED';
271
+ case DisconnectReason.STATE_MISMATCH:
272
+ return 'STATE_MISMATCH';
273
+ default:
274
+ return 'UNKNOWN';
275
+ }
276
+ }
277
+
216
278
  export interface Room {
217
279
  sid: string;
218
280
  name: string;
@@ -315,6 +377,7 @@ export interface SimulcastCodecInfo {
315
377
  mimeType: string;
316
378
  mid: string;
317
379
  cid: string;
380
+ layers: VideoLayer[];
318
381
  }
319
382
 
320
383
  export interface TrackInfo {
@@ -499,12 +562,17 @@ export interface ClientConfiguration {
499
562
  video?: VideoConfiguration;
500
563
  screen?: VideoConfiguration;
501
564
  resumeConnection: ClientConfigSetting;
565
+ disabledCodecs?: DisabledCodecs;
502
566
  }
503
567
 
504
568
  export interface VideoConfiguration {
505
569
  hardwareEncoder: ClientConfigSetting;
506
570
  }
507
571
 
572
+ export interface DisabledCodecs {
573
+ codecs: Codec[];
574
+ }
575
+
508
576
  export interface RTPStats {
509
577
  startTime?: Date;
510
578
  endTime?: Date;
@@ -531,7 +599,9 @@ export interface RTPStats {
531
599
  jitterMax: number;
532
600
  gapHistogram: { [key: number]: number };
533
601
  nacks: number;
602
+ nackAcks: number;
534
603
  nackMisses: number;
604
+ nackRepeated: number;
535
605
  plis: number;
536
606
  lastPli?: Date;
537
607
  firs: number;
@@ -1015,7 +1085,7 @@ export const ParticipantInfo = {
1015
1085
  };
1016
1086
 
1017
1087
  function createBaseSimulcastCodecInfo(): SimulcastCodecInfo {
1018
- return { mimeType: '', mid: '', cid: '' };
1088
+ return { mimeType: '', mid: '', cid: '', layers: [] };
1019
1089
  }
1020
1090
 
1021
1091
  export const SimulcastCodecInfo = {
@@ -1029,6 +1099,9 @@ export const SimulcastCodecInfo = {
1029
1099
  if (message.cid !== '') {
1030
1100
  writer.uint32(26).string(message.cid);
1031
1101
  }
1102
+ for (const v of message.layers) {
1103
+ VideoLayer.encode(v!, writer.uint32(34).fork()).ldelim();
1104
+ }
1032
1105
  return writer;
1033
1106
  },
1034
1107
 
@@ -1048,6 +1121,9 @@ export const SimulcastCodecInfo = {
1048
1121
  case 3:
1049
1122
  message.cid = reader.string();
1050
1123
  break;
1124
+ case 4:
1125
+ message.layers.push(VideoLayer.decode(reader, reader.uint32()));
1126
+ break;
1051
1127
  default:
1052
1128
  reader.skipType(tag & 7);
1053
1129
  break;
@@ -1061,6 +1137,9 @@ export const SimulcastCodecInfo = {
1061
1137
  mimeType: isSet(object.mimeType) ? String(object.mimeType) : '',
1062
1138
  mid: isSet(object.mid) ? String(object.mid) : '',
1063
1139
  cid: isSet(object.cid) ? String(object.cid) : '',
1140
+ layers: Array.isArray(object?.layers)
1141
+ ? object.layers.map((e: any) => VideoLayer.fromJSON(e))
1142
+ : [],
1064
1143
  };
1065
1144
  },
1066
1145
 
@@ -1069,6 +1148,11 @@ export const SimulcastCodecInfo = {
1069
1148
  message.mimeType !== undefined && (obj.mimeType = message.mimeType);
1070
1149
  message.mid !== undefined && (obj.mid = message.mid);
1071
1150
  message.cid !== undefined && (obj.cid = message.cid);
1151
+ if (message.layers) {
1152
+ obj.layers = message.layers.map((e) => (e ? VideoLayer.toJSON(e) : undefined));
1153
+ } else {
1154
+ obj.layers = [];
1155
+ }
1072
1156
  return obj;
1073
1157
  },
1074
1158
 
@@ -1077,6 +1161,7 @@ export const SimulcastCodecInfo = {
1077
1161
  message.mimeType = object.mimeType ?? '';
1078
1162
  message.mid = object.mid ?? '';
1079
1163
  message.cid = object.cid ?? '';
1164
+ message.layers = object.layers?.map((e) => VideoLayer.fromPartial(e)) || [];
1080
1165
  return message;
1081
1166
  },
1082
1167
  };
@@ -1820,7 +1905,7 @@ export const ClientInfo = {
1820
1905
  };
1821
1906
 
1822
1907
  function createBaseClientConfiguration(): ClientConfiguration {
1823
- return { video: undefined, screen: undefined, resumeConnection: 0 };
1908
+ return { video: undefined, screen: undefined, resumeConnection: 0, disabledCodecs: undefined };
1824
1909
  }
1825
1910
 
1826
1911
  export const ClientConfiguration = {
@@ -1834,6 +1919,9 @@ export const ClientConfiguration = {
1834
1919
  if (message.resumeConnection !== 0) {
1835
1920
  writer.uint32(24).int32(message.resumeConnection);
1836
1921
  }
1922
+ if (message.disabledCodecs !== undefined) {
1923
+ DisabledCodecs.encode(message.disabledCodecs, writer.uint32(34).fork()).ldelim();
1924
+ }
1837
1925
  return writer;
1838
1926
  },
1839
1927
 
@@ -1853,6 +1941,9 @@ export const ClientConfiguration = {
1853
1941
  case 3:
1854
1942
  message.resumeConnection = reader.int32() as any;
1855
1943
  break;
1944
+ case 4:
1945
+ message.disabledCodecs = DisabledCodecs.decode(reader, reader.uint32());
1946
+ break;
1856
1947
  default:
1857
1948
  reader.skipType(tag & 7);
1858
1949
  break;
@@ -1868,6 +1959,9 @@ export const ClientConfiguration = {
1868
1959
  resumeConnection: isSet(object.resumeConnection)
1869
1960
  ? clientConfigSettingFromJSON(object.resumeConnection)
1870
1961
  : 0,
1962
+ disabledCodecs: isSet(object.disabledCodecs)
1963
+ ? DisabledCodecs.fromJSON(object.disabledCodecs)
1964
+ : undefined,
1871
1965
  };
1872
1966
  },
1873
1967
 
@@ -1879,6 +1973,10 @@ export const ClientConfiguration = {
1879
1973
  (obj.screen = message.screen ? VideoConfiguration.toJSON(message.screen) : undefined);
1880
1974
  message.resumeConnection !== undefined &&
1881
1975
  (obj.resumeConnection = clientConfigSettingToJSON(message.resumeConnection));
1976
+ message.disabledCodecs !== undefined &&
1977
+ (obj.disabledCodecs = message.disabledCodecs
1978
+ ? DisabledCodecs.toJSON(message.disabledCodecs)
1979
+ : undefined);
1882
1980
  return obj;
1883
1981
  },
1884
1982
 
@@ -1895,6 +1993,10 @@ export const ClientConfiguration = {
1895
1993
  ? VideoConfiguration.fromPartial(object.screen)
1896
1994
  : undefined;
1897
1995
  message.resumeConnection = object.resumeConnection ?? 0;
1996
+ message.disabledCodecs =
1997
+ object.disabledCodecs !== undefined && object.disabledCodecs !== null
1998
+ ? DisabledCodecs.fromPartial(object.disabledCodecs)
1999
+ : undefined;
1898
2000
  return message;
1899
2001
  },
1900
2002
  };
@@ -1951,6 +2053,59 @@ export const VideoConfiguration = {
1951
2053
  },
1952
2054
  };
1953
2055
 
2056
+ function createBaseDisabledCodecs(): DisabledCodecs {
2057
+ return { codecs: [] };
2058
+ }
2059
+
2060
+ export const DisabledCodecs = {
2061
+ encode(message: DisabledCodecs, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer {
2062
+ for (const v of message.codecs) {
2063
+ Codec.encode(v!, writer.uint32(10).fork()).ldelim();
2064
+ }
2065
+ return writer;
2066
+ },
2067
+
2068
+ decode(input: _m0.Reader | Uint8Array, length?: number): DisabledCodecs {
2069
+ const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input);
2070
+ let end = length === undefined ? reader.len : reader.pos + length;
2071
+ const message = createBaseDisabledCodecs();
2072
+ while (reader.pos < end) {
2073
+ const tag = reader.uint32();
2074
+ switch (tag >>> 3) {
2075
+ case 1:
2076
+ message.codecs.push(Codec.decode(reader, reader.uint32()));
2077
+ break;
2078
+ default:
2079
+ reader.skipType(tag & 7);
2080
+ break;
2081
+ }
2082
+ }
2083
+ return message;
2084
+ },
2085
+
2086
+ fromJSON(object: any): DisabledCodecs {
2087
+ return {
2088
+ codecs: Array.isArray(object?.codecs) ? object.codecs.map((e: any) => Codec.fromJSON(e)) : [],
2089
+ };
2090
+ },
2091
+
2092
+ toJSON(message: DisabledCodecs): unknown {
2093
+ const obj: any = {};
2094
+ if (message.codecs) {
2095
+ obj.codecs = message.codecs.map((e) => (e ? Codec.toJSON(e) : undefined));
2096
+ } else {
2097
+ obj.codecs = [];
2098
+ }
2099
+ return obj;
2100
+ },
2101
+
2102
+ fromPartial<I extends Exact<DeepPartial<DisabledCodecs>, I>>(object: I): DisabledCodecs {
2103
+ const message = createBaseDisabledCodecs();
2104
+ message.codecs = object.codecs?.map((e) => Codec.fromPartial(e)) || [];
2105
+ return message;
2106
+ },
2107
+ };
2108
+
1954
2109
  function createBaseRTPStats(): RTPStats {
1955
2110
  return {
1956
2111
  startTime: undefined,
@@ -1978,7 +2133,9 @@ function createBaseRTPStats(): RTPStats {
1978
2133
  jitterMax: 0,
1979
2134
  gapHistogram: {},
1980
2135
  nacks: 0,
2136
+ nackAcks: 0,
1981
2137
  nackMisses: 0,
2138
+ nackRepeated: 0,
1982
2139
  plis: 0,
1983
2140
  lastPli: undefined,
1984
2141
  firs: 0,
@@ -2072,9 +2229,15 @@ export const RTPStats = {
2072
2229
  if (message.nacks !== 0) {
2073
2230
  writer.uint32(200).uint32(message.nacks);
2074
2231
  }
2232
+ if (message.nackAcks !== 0) {
2233
+ writer.uint32(296).uint32(message.nackAcks);
2234
+ }
2075
2235
  if (message.nackMisses !== 0) {
2076
2236
  writer.uint32(208).uint32(message.nackMisses);
2077
2237
  }
2238
+ if (message.nackRepeated !== 0) {
2239
+ writer.uint32(304).uint32(message.nackRepeated);
2240
+ }
2078
2241
  if (message.plis !== 0) {
2079
2242
  writer.uint32(216).uint32(message.plis);
2080
2243
  }
@@ -2193,9 +2356,15 @@ export const RTPStats = {
2193
2356
  case 25:
2194
2357
  message.nacks = reader.uint32();
2195
2358
  break;
2359
+ case 37:
2360
+ message.nackAcks = reader.uint32();
2361
+ break;
2196
2362
  case 26:
2197
2363
  message.nackMisses = reader.uint32();
2198
2364
  break;
2365
+ case 38:
2366
+ message.nackRepeated = reader.uint32();
2367
+ break;
2199
2368
  case 27:
2200
2369
  message.plis = reader.uint32();
2201
2370
  break;
@@ -2273,7 +2442,9 @@ export const RTPStats = {
2273
2442
  )
2274
2443
  : {},
2275
2444
  nacks: isSet(object.nacks) ? Number(object.nacks) : 0,
2445
+ nackAcks: isSet(object.nackAcks) ? Number(object.nackAcks) : 0,
2276
2446
  nackMisses: isSet(object.nackMisses) ? Number(object.nackMisses) : 0,
2447
+ nackRepeated: isSet(object.nackRepeated) ? Number(object.nackRepeated) : 0,
2277
2448
  plis: isSet(object.plis) ? Number(object.plis) : 0,
2278
2449
  lastPli: isSet(object.lastPli) ? fromJsonTimestamp(object.lastPli) : undefined,
2279
2450
  firs: isSet(object.firs) ? Number(object.firs) : 0,
@@ -2327,7 +2498,9 @@ export const RTPStats = {
2327
2498
  });
2328
2499
  }
2329
2500
  message.nacks !== undefined && (obj.nacks = Math.round(message.nacks));
2501
+ message.nackAcks !== undefined && (obj.nackAcks = Math.round(message.nackAcks));
2330
2502
  message.nackMisses !== undefined && (obj.nackMisses = Math.round(message.nackMisses));
2503
+ message.nackRepeated !== undefined && (obj.nackRepeated = Math.round(message.nackRepeated));
2331
2504
  message.plis !== undefined && (obj.plis = Math.round(message.plis));
2332
2505
  message.lastPli !== undefined && (obj.lastPli = message.lastPli.toISOString());
2333
2506
  message.firs !== undefined && (obj.firs = Math.round(message.firs));
@@ -2376,7 +2549,9 @@ export const RTPStats = {
2376
2549
  return acc;
2377
2550
  }, {});
2378
2551
  message.nacks = object.nacks ?? 0;
2552
+ message.nackAcks = object.nackAcks ?? 0;
2379
2553
  message.nackMisses = object.nackMisses ?? 0;
2554
+ message.nackRepeated = object.nackRepeated ?? 0;
2380
2555
  message.plis = object.plis ?? 0;
2381
2556
  message.lastPli = object.lastPli ?? undefined;
2382
2557
  message.firs = object.firs ?? 0;
@@ -2480,9 +2655,9 @@ const btoa: (bin: string) => string =
2480
2655
  globalThis.btoa || ((bin) => globalThis.Buffer.from(bin, 'binary').toString('base64'));
2481
2656
  function base64FromBytes(arr: Uint8Array): string {
2482
2657
  const bin: string[] = [];
2483
- arr.forEach((byte) => {
2658
+ for (const byte of arr) {
2484
2659
  bin.push(String.fromCharCode(byte));
2485
- });
2660
+ }
2486
2661
  return btoa(bin.join(''));
2487
2662
  }
2488
2663
 
@@ -9,6 +9,7 @@ import {
9
9
  ClientConfiguration,
10
10
  TrackInfo,
11
11
  VideoQuality,
12
+ DisconnectReason,
12
13
  ConnectionQuality,
13
14
  VideoLayer,
14
15
  ParticipantTracks,
@@ -19,6 +20,8 @@ import {
19
20
  trackSourceToJSON,
20
21
  videoQualityFromJSON,
21
22
  videoQualityToJSON,
23
+ disconnectReasonFromJSON,
24
+ disconnectReasonToJSON,
22
25
  connectionQualityFromJSON,
23
26
  connectionQualityToJSON,
24
27
  } from './livekit_models';
@@ -283,6 +286,7 @@ export interface LeaveRequest {
283
286
  * indicates clients should attempt full-reconnect sequence
284
287
  */
285
288
  canReconnect: boolean;
289
+ reason: DisconnectReason;
286
290
  }
287
291
 
288
292
  /** message to indicate published video track dimensions are changing */
@@ -1836,7 +1840,7 @@ export const UpdateTrackSettings = {
1836
1840
  };
1837
1841
 
1838
1842
  function createBaseLeaveRequest(): LeaveRequest {
1839
- return { canReconnect: false };
1843
+ return { canReconnect: false, reason: 0 };
1840
1844
  }
1841
1845
 
1842
1846
  export const LeaveRequest = {
@@ -1844,6 +1848,9 @@ export const LeaveRequest = {
1844
1848
  if (message.canReconnect === true) {
1845
1849
  writer.uint32(8).bool(message.canReconnect);
1846
1850
  }
1851
+ if (message.reason !== 0) {
1852
+ writer.uint32(16).int32(message.reason);
1853
+ }
1847
1854
  return writer;
1848
1855
  },
1849
1856
 
@@ -1857,6 +1864,9 @@ export const LeaveRequest = {
1857
1864
  case 1:
1858
1865
  message.canReconnect = reader.bool();
1859
1866
  break;
1867
+ case 2:
1868
+ message.reason = reader.int32() as any;
1869
+ break;
1860
1870
  default:
1861
1871
  reader.skipType(tag & 7);
1862
1872
  break;
@@ -1868,18 +1878,21 @@ export const LeaveRequest = {
1868
1878
  fromJSON(object: any): LeaveRequest {
1869
1879
  return {
1870
1880
  canReconnect: isSet(object.canReconnect) ? Boolean(object.canReconnect) : false,
1881
+ reason: isSet(object.reason) ? disconnectReasonFromJSON(object.reason) : 0,
1871
1882
  };
1872
1883
  },
1873
1884
 
1874
1885
  toJSON(message: LeaveRequest): unknown {
1875
1886
  const obj: any = {};
1876
1887
  message.canReconnect !== undefined && (obj.canReconnect = message.canReconnect);
1888
+ message.reason !== undefined && (obj.reason = disconnectReasonToJSON(message.reason));
1877
1889
  return obj;
1878
1890
  },
1879
1891
 
1880
1892
  fromPartial<I extends Exact<DeepPartial<LeaveRequest>, I>>(object: I): LeaveRequest {
1881
1893
  const message = createBaseLeaveRequest();
1882
1894
  message.canReconnect = object.canReconnect ?? false;
1895
+ message.reason = object.reason ?? 0;
1883
1896
  return message;
1884
1897
  },
1885
1898
  };
@@ -7,6 +7,7 @@ import {
7
7
  ClientConfiguration,
8
8
  DataPacket,
9
9
  DataPacket_Kind,
10
+ DisconnectReason,
10
11
  SpeakerInfo,
11
12
  TrackInfo,
12
13
  UserPacket,
@@ -360,7 +361,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
360
361
  this.fullReconnectOnNext = true;
361
362
  this.primaryPC = undefined;
362
363
  } else {
363
- this.emit(EngineEvent.Disconnected);
364
+ this.emit(EngineEvent.Disconnected, leave?.reason);
364
365
  this.close();
365
366
  }
366
367
  log.trace('leave request', { leave });
@@ -716,7 +717,7 @@ class SignalReconnectError extends Error {}
716
717
 
717
718
  export type EngineEventCallbacks = {
718
719
  connected: () => void;
719
- disconnected: () => void;
720
+ disconnected: (reason?: DisconnectReason) => void;
720
721
  resuming: () => void;
721
722
  resumed: () => void;
722
723
  restarting: () => void;
package/src/room/Room.ts CHANGED
@@ -5,6 +5,7 @@ import log from '../logger';
5
5
  import { RoomConnectOptions, RoomOptions } from '../options';
6
6
  import {
7
7
  DataPacket_Kind,
8
+ DisconnectReason,
8
9
  ParticipantInfo,
9
10
  ParticipantInfo_State,
10
11
  ParticipantPermission,
@@ -150,8 +151,8 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
150
151
  this.onTrackAdded(mediaTrack, stream, receiver);
151
152
  },
152
153
  )
153
- .on(EngineEvent.Disconnected, () => {
154
- this.handleDisconnect(this.options.stopLocalTrackOnUnpublish);
154
+ .on(EngineEvent.Disconnected, (reason?: DisconnectReason) => {
155
+ this.handleDisconnect(this.options.stopLocalTrackOnUnpublish, reason);
155
156
  })
156
157
  .on(EngineEvent.ActiveSpeakersUpdate, this.handleActiveSpeakersUpdate)
157
158
  .on(EngineEvent.DataPacketReceived, this.handleDataPacket)
@@ -551,14 +552,15 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
551
552
  // at that time, ICE connectivity has not been established so the track is not
552
553
  // technically subscribed.
553
554
  // We'll defer these events until when the room is connected or eventually disconnected.
554
- if (this.state === ConnectionState.Connecting || this.state === ConnectionState.Reconnecting) {
555
- setTimeout(() => {
555
+ if (this.connectFuture) {
556
+ this.connectFuture.promise.then(() => {
556
557
  this.onTrackAdded(mediaTrack, stream, receiver);
557
- }, 50);
558
+ });
558
559
  return;
559
560
  }
560
561
  if (this.state === ConnectionState.Disconnected) {
561
562
  log.warn('skipping incoming track after Room disconnected');
563
+ return;
562
564
  }
563
565
  const parts = unpackStreamId(stream.id);
564
566
  const participantId = parts[0];
@@ -644,7 +646,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
644
646
  );
645
647
  };
646
648
 
647
- private handleDisconnect(shouldStopTracks = true) {
649
+ private handleDisconnect(shouldStopTracks = true, reason?: DisconnectReason) {
648
650
  this.participants.forEach((p) => {
649
651
  p.tracks.forEach((pub) => {
650
652
  p.unpublishTrack(pub.trackSid);
@@ -672,7 +674,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
672
674
  navigator.mediaDevices?.removeEventListener('devicechange', this.handleDeviceChange);
673
675
  }
674
676
  this.setAndEmitConnectionState(ConnectionState.Disconnected);
675
- this.emit(RoomEvent.Disconnected);
677
+ this.emit(RoomEvent.Disconnected, reason);
676
678
  }
677
679
 
678
680
  private handleParticipantUpdates = (participantInfos: ParticipantInfo[]) => {
@@ -1090,7 +1092,7 @@ export default Room;
1090
1092
  export type RoomEventCallbacks = {
1091
1093
  reconnecting: () => void;
1092
1094
  reconnected: () => void;
1093
- disconnected: () => void;
1095
+ disconnected: (reason?: DisconnectReason) => void;
1094
1096
  /** @deprecated stateChanged has been renamed to connectionStateChanged */
1095
1097
  stateChanged: (state: ConnectionState) => void;
1096
1098
  connectionStateChanged: (state: ConnectionState) => void;
@@ -35,22 +35,26 @@ export default class LocalAudioTrack extends LocalTrack {
35
35
  }
36
36
 
37
37
  async mute(): Promise<LocalAudioTrack> {
38
- // disabled special handling as it will cause BT headsets to switch communication modes
39
- if (this.source === Track.Source.Microphone && this.stopOnMute) {
40
- log.debug('stopping mic track');
41
- // also stop the track, so that microphone indicator is turned off
42
- this._mediaStreamTrack.stop();
43
- }
44
- await super.mute();
38
+ await this.muteQueue.run(async () => {
39
+ // disabled special handling as it will cause BT headsets to switch communication modes
40
+ if (this.source === Track.Source.Microphone && this.stopOnMute && !this.isUserProvided) {
41
+ log.debug('stopping mic track');
42
+ // also stop the track, so that microphone indicator is turned off
43
+ this._mediaStreamTrack.stop();
44
+ }
45
+ await super.mute();
46
+ });
45
47
  return this;
46
48
  }
47
49
 
48
50
  async unmute(): Promise<LocalAudioTrack> {
49
- if (this.source === Track.Source.Microphone && this.stopOnMute && !this.isUserProvided) {
50
- log.debug('reacquiring mic track');
51
- await this.restartTrack();
52
- }
53
- await super.unmute();
51
+ await this.muteQueue.run(async () => {
52
+ if (this.source === Track.Source.Microphone && this.stopOnMute && !this.isUserProvided) {
53
+ log.debug('reacquiring mic track');
54
+ await this.restartTrack();
55
+ }
56
+ await super.unmute();
57
+ });
54
58
  return this;
55
59
  }
56
60
 
@@ -1,9 +1,10 @@
1
+ import Queue from 'async-await-queue';
1
2
  import log from '../../logger';
2
3
  import DeviceManager from '../DeviceManager';
3
4
  import { TrackInvalidError } from '../errors';
4
5
  import { TrackEvent } from '../events';
5
- import { VideoCodec } from './options';
6
6
  import { getEmptyAudioStreamTrack, getEmptyVideoStreamTrack, isMobile } from '../utils';
7
+ import { VideoCodec } from './options';
7
8
  import { attachToElement, detachTrack, Track } from './Track';
8
9
 
9
10
  export default class LocalTrack extends Track {
@@ -21,6 +22,8 @@ export default class LocalTrack extends Track {
21
22
 
22
23
  protected providedByUser: boolean;
23
24
 
25
+ protected muteQueue: Queue;
26
+
24
27
  protected constructor(
25
28
  mediaTrack: MediaStreamTrack,
26
29
  kind: Track.Kind,
@@ -33,6 +36,7 @@ export default class LocalTrack extends Track {
33
36
  this.reacquireTrack = false;
34
37
  this.wasMuted = false;
35
38
  this.providedByUser = userProvidedTrack;
39
+ this.muteQueue = new Queue();
36
40
  }
37
41
 
38
42
  get id(): string {
@@ -101,7 +105,9 @@ export default class LocalTrack extends Track {
101
105
  // on Safari, the old audio track must be stopped before attempting to acquire
102
106
  // the new track, otherwise the new track will stop with
103
107
  // 'A MediaStreamTrack ended due to a capture failure`
104
- this._mediaStreamTrack.stop();
108
+ if (!this.providedByUser) {
109
+ this._mediaStreamTrack.stop();
110
+ }
105
111
 
106
112
  track.addEventListener('ended', this.handleEnded);
107
113
  log.debug('replace MediaStreamTrack');
@@ -170,6 +176,7 @@ export default class LocalTrack extends Track {
170
176
  }
171
177
 
172
178
  protected setTrackMuted(muted: boolean) {
179
+ log.debug(`setting ${this.kind} track ${muted ? 'muted' : 'unmuted'}`);
173
180
  if (this.isMuted === muted) {
174
181
  return;
175
182
  }
@@ -215,31 +222,36 @@ export default class LocalTrack extends Track {
215
222
  };
216
223
 
217
224
  async pauseUpstream() {
218
- if (this._isUpstreamPaused === true) {
219
- return;
220
- }
221
- if (!this.sender) {
222
- log.warn('unable to pause upstream for an unpublished track');
223
- return;
224
- }
225
- this._isUpstreamPaused = true;
226
- this.emit(TrackEvent.UpstreamPaused, this);
227
- const emptyTrack =
228
- this.kind === Track.Kind.Audio ? getEmptyAudioStreamTrack() : getEmptyVideoStreamTrack();
229
- await this.sender.replaceTrack(emptyTrack);
225
+ this.muteQueue.run(async () => {
226
+ if (this._isUpstreamPaused === true) {
227
+ return;
228
+ }
229
+ if (!this.sender) {
230
+ log.warn('unable to pause upstream for an unpublished track');
231
+ return;
232
+ }
233
+
234
+ this._isUpstreamPaused = true;
235
+ this.emit(TrackEvent.UpstreamPaused, this);
236
+ const emptyTrack =
237
+ this.kind === Track.Kind.Audio ? getEmptyAudioStreamTrack() : getEmptyVideoStreamTrack();
238
+ await this.sender.replaceTrack(emptyTrack);
239
+ });
230
240
  }
231
241
 
232
242
  async resumeUpstream() {
233
- if (this._isUpstreamPaused === false) {
234
- return;
235
- }
236
- if (!this.sender) {
237
- log.warn('unable to resume upstream for an unpublished track');
238
- return;
239
- }
240
- this._isUpstreamPaused = false;
241
- this.emit(TrackEvent.UpstreamResumed, this);
242
-
243
- await this.sender.replaceTrack(this._mediaStreamTrack);
243
+ this.muteQueue.run(async () => {
244
+ if (this._isUpstreamPaused === false) {
245
+ return;
246
+ }
247
+ if (!this.sender) {
248
+ log.warn('unable to resume upstream for an unpublished track');
249
+ return;
250
+ }
251
+ this._isUpstreamPaused = false;
252
+ this.emit(TrackEvent.UpstreamResumed, this);
253
+
254
+ await this.sender.replaceTrack(this._mediaStreamTrack);
255
+ });
244
256
  }
245
257
  }