livekit-client 1.9.7 → 1.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (99) hide show
  1. package/dist/livekit-client.esm.mjs +2972 -2583
  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 +2 -1
  6. package/dist/src/api/SignalClient.d.ts.map +1 -1
  7. package/dist/src/connectionHelper/ConnectionCheck.d.ts +2 -3
  8. package/dist/src/connectionHelper/ConnectionCheck.d.ts.map +1 -1
  9. package/dist/src/connectionHelper/checks/Checker.d.ts +2 -3
  10. package/dist/src/connectionHelper/checks/Checker.d.ts.map +1 -1
  11. package/dist/src/index.d.ts +1 -0
  12. package/dist/src/index.d.ts.map +1 -1
  13. package/dist/src/proto/livekit_models.d.ts +108 -10
  14. package/dist/src/proto/livekit_models.d.ts.map +1 -1
  15. package/dist/src/proto/livekit_rtc.d.ts +513 -194
  16. package/dist/src/proto/livekit_rtc.d.ts.map +1 -1
  17. package/dist/src/room/PCTransport.d.ts +1 -1
  18. package/dist/src/room/PCTransport.d.ts.map +1 -1
  19. package/dist/src/room/RTCEngine.d.ts +2 -4
  20. package/dist/src/room/RTCEngine.d.ts.map +1 -1
  21. package/dist/src/room/Room.d.ts +6 -6
  22. package/dist/src/room/Room.d.ts.map +1 -1
  23. package/dist/src/room/events.d.ts +5 -1
  24. package/dist/src/room/events.d.ts.map +1 -1
  25. package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
  26. package/dist/src/room/participant/Participant.d.ts +4 -6
  27. package/dist/src/room/participant/Participant.d.ts.map +1 -1
  28. package/dist/src/room/participant/RemoteParticipant.d.ts +2 -1
  29. package/dist/src/room/participant/RemoteParticipant.d.ts.map +1 -1
  30. package/dist/src/room/participant/publishUtils.d.ts +8 -0
  31. package/dist/src/room/participant/publishUtils.d.ts.map +1 -1
  32. package/dist/src/room/track/LocalTrack.d.ts +33 -0
  33. package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
  34. package/dist/src/room/track/LocalVideoTrack.d.ts +1 -1
  35. package/dist/src/room/track/LocalVideoTrack.d.ts.map +1 -1
  36. package/dist/src/room/track/RemoteTrackPublication.d.ts +4 -1
  37. package/dist/src/room/track/RemoteTrackPublication.d.ts.map +1 -1
  38. package/dist/src/room/track/RemoteVideoTrack.d.ts +1 -0
  39. package/dist/src/room/track/RemoteVideoTrack.d.ts.map +1 -1
  40. package/dist/src/room/track/Track.d.ts +2 -4
  41. package/dist/src/room/track/Track.d.ts.map +1 -1
  42. package/dist/src/room/track/TrackPublication.d.ts +4 -5
  43. package/dist/src/room/track/TrackPublication.d.ts.map +1 -1
  44. package/dist/src/room/track/options.d.ts +14 -5
  45. package/dist/src/room/track/options.d.ts.map +1 -1
  46. package/dist/src/room/track/processor/types.d.ts +19 -0
  47. package/dist/src/room/track/processor/types.d.ts.map +1 -0
  48. package/dist/src/room/track/types.d.ts +2 -1
  49. package/dist/src/room/track/types.d.ts.map +1 -1
  50. package/dist/src/room/utils.d.ts.map +1 -1
  51. package/dist/ts4.2/src/api/SignalClient.d.ts +2 -1
  52. package/dist/ts4.2/src/connectionHelper/ConnectionCheck.d.ts +2 -3
  53. package/dist/ts4.2/src/connectionHelper/checks/Checker.d.ts +2 -3
  54. package/dist/ts4.2/src/index.d.ts +1 -0
  55. package/dist/ts4.2/src/proto/livekit_models.d.ts +126 -12
  56. package/dist/ts4.2/src/proto/livekit_rtc.d.ts +617 -254
  57. package/dist/ts4.2/src/room/PCTransport.d.ts +1 -1
  58. package/dist/ts4.2/src/room/RTCEngine.d.ts +2 -4
  59. package/dist/ts4.2/src/room/Room.d.ts +6 -6
  60. package/dist/ts4.2/src/room/events.d.ts +5 -1
  61. package/dist/ts4.2/src/room/participant/Participant.d.ts +4 -6
  62. package/dist/ts4.2/src/room/participant/RemoteParticipant.d.ts +2 -1
  63. package/dist/ts4.2/src/room/participant/publishUtils.d.ts +8 -0
  64. package/dist/ts4.2/src/room/track/LocalTrack.d.ts +33 -0
  65. package/dist/ts4.2/src/room/track/LocalVideoTrack.d.ts +1 -1
  66. package/dist/ts4.2/src/room/track/RemoteTrackPublication.d.ts +4 -1
  67. package/dist/ts4.2/src/room/track/RemoteVideoTrack.d.ts +1 -0
  68. package/dist/ts4.2/src/room/track/Track.d.ts +2 -4
  69. package/dist/ts4.2/src/room/track/TrackPublication.d.ts +4 -5
  70. package/dist/ts4.2/src/room/track/options.d.ts +14 -5
  71. package/dist/ts4.2/src/room/track/processor/types.d.ts +19 -0
  72. package/dist/ts4.2/src/room/track/types.d.ts +2 -1
  73. package/package.json +14 -15
  74. package/src/api/SignalClient.ts +8 -1
  75. package/src/connectionHelper/ConnectionCheck.ts +2 -3
  76. package/src/connectionHelper/checks/Checker.ts +2 -3
  77. package/src/index.ts +1 -0
  78. package/src/logger.ts +4 -4
  79. package/src/proto/google/protobuf/timestamp.ts +3 -3
  80. package/src/proto/livekit_models.ts +254 -161
  81. package/src/proto/livekit_rtc.ts +334 -180
  82. package/src/room/PCTransport.ts +1 -1
  83. package/src/room/RTCEngine.ts +4 -4
  84. package/src/room/Room.ts +67 -12
  85. package/src/room/events.ts +4 -0
  86. package/src/room/participant/LocalParticipant.ts +33 -5
  87. package/src/room/participant/Participant.ts +4 -5
  88. package/src/room/participant/RemoteParticipant.ts +8 -4
  89. package/src/room/participant/publishUtils.ts +47 -20
  90. package/src/room/track/LocalTrack.ts +180 -57
  91. package/src/room/track/LocalVideoTrack.ts +98 -33
  92. package/src/room/track/RemoteTrackPublication.ts +8 -1
  93. package/src/room/track/RemoteVideoTrack.ts +23 -6
  94. package/src/room/track/Track.ts +5 -7
  95. package/src/room/track/TrackPublication.ts +4 -5
  96. package/src/room/track/options.ts +14 -5
  97. package/src/room/track/processor/types.ts +20 -0
  98. package/src/room/track/types.ts +2 -1
  99. package/src/room/utils.ts +6 -3
@@ -1,4 +1,4 @@
1
- import { EventEmitter } from 'events';
1
+ import EventEmitter from 'eventemitter3';
2
2
  import { parse, write } from 'sdp-transform';
3
3
  import type { MediaDescription } from 'sdp-transform';
4
4
  import { debounce } from 'ts-debounce';
@@ -1,5 +1,4 @@
1
- import { EventEmitter } from 'events';
2
- import type TypedEventEmitter from 'typed-emitter';
1
+ import EventEmitter from 'eventemitter3';
3
2
  import { SignalClient } from '../api/SignalClient';
4
3
  import type { SignalOptions } from '../api/SignalClient';
5
4
  import log from '../logger';
@@ -65,7 +64,7 @@ enum PCState {
65
64
  }
66
65
 
67
66
  /** @internal */
68
- export default class RTCEngine extends (EventEmitter as new () => TypedEventEmitter<EngineEventCallbacks>) {
67
+ export default class RTCEngine extends EventEmitter<EngineEventCallbacks> {
69
68
  publisher?: PCTransport;
70
69
 
71
70
  subscriber?: PCTransport;
@@ -1046,6 +1045,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
1046
1045
  };
1047
1046
  this.once(EngineEvent.Restarted, onRestarted);
1048
1047
  this.once(EngineEvent.Disconnected, onDisconnected);
1048
+ this.once(EngineEvent.Closing, onDisconnected);
1049
1049
  });
1050
1050
  };
1051
1051
 
@@ -1172,7 +1172,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
1172
1172
  this.hasPublished = true;
1173
1173
 
1174
1174
  const handleClosed = () => {
1175
- log.debug('engine disconnected while negotiation was ongoing');
1175
+ log.warn('engine disconnected while negotiation was ongoing');
1176
1176
  cleanup();
1177
1177
  resolve();
1178
1178
  return;
package/src/room/Room.ts CHANGED
@@ -1,5 +1,4 @@
1
- import { EventEmitter } from 'events';
2
- import type TypedEmitter from 'typed-emitter';
1
+ import EventEmitter from 'eventemitter3';
3
2
  import 'webrtc-adapter';
4
3
  import { toProtoSessionDescription } from '../api/SignalClient';
5
4
  import log from '../logger';
@@ -18,6 +17,7 @@ import {
18
17
  Room as RoomModel,
19
18
  ServerInfo,
20
19
  SpeakerInfo,
20
+ SubscriptionError,
21
21
  TrackInfo,
22
22
  TrackSource,
23
23
  TrackType,
@@ -29,6 +29,7 @@ import {
29
29
  SimulateScenario,
30
30
  StreamStateUpdate,
31
31
  SubscriptionPermissionUpdate,
32
+ SubscriptionResponse,
32
33
  } from '../proto/livekit_rtc';
33
34
  import DeviceManager from './DeviceManager';
34
35
  import RTCEngine from './RTCEngine';
@@ -63,6 +64,7 @@ import {
63
64
  createDummyVideoStreamTrack,
64
65
  getEmptyAudioStreamTrack,
65
66
  isCloud,
67
+ isSafari,
66
68
  isWeb,
67
69
  supportsSetSinkId,
68
70
  unpackStreamId,
@@ -88,7 +90,7 @@ export const RoomState = ConnectionState;
88
90
  *
89
91
  * @noInheritDoc
90
92
  */
91
- class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>) {
93
+ class Room extends EventEmitter<RoomEventCallbacks> {
92
94
  state: ConnectionState = ConnectionState.Disconnected;
93
95
 
94
96
  /** map of sid: [[RemoteParticipant]] */
@@ -138,7 +140,6 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
138
140
  */
139
141
  constructor(options?: RoomOptions) {
140
142
  super();
141
- this.setMaxListeners(100);
142
143
  this.participants = new Map();
143
144
  this.cachedParticipantSids = [];
144
145
  this.identityToSid = new Map();
@@ -207,6 +208,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
207
208
  this.engine.client.onStreamStateUpdate = this.handleStreamStateUpdate;
208
209
  this.engine.client.onSubscriptionPermissionUpdate = this.handleSubscriptionPermissionUpdate;
209
210
  this.engine.client.onConnectionQuality = this.handleConnectionQualityUpdate;
211
+ this.engine.client.onSubscriptionError = this.handleSubscriptionError;
210
212
 
211
213
  this.engine
212
214
  .on(
@@ -492,6 +494,9 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
492
494
  // capturing both 'pagehide' and 'beforeunload' to capture broadest set of browser behaviors
493
495
  window.addEventListener('pagehide', this.onPageLeave);
494
496
  window.addEventListener('beforeunload', this.onPageLeave);
497
+ }
498
+ if (isWeb()) {
499
+ document.addEventListener('freeze', this.onPageLeave);
495
500
  navigator.mediaDevices?.addEventListener('devicechange', this.handleDeviceChange);
496
501
  }
497
502
  this.setAndEmitConnectionState(ConnectionState.Connected);
@@ -654,8 +659,31 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
654
659
  */
655
660
  async startAudio() {
656
661
  await this.acquireAudioContext();
657
-
658
662
  const elements: Array<HTMLMediaElement> = [];
663
+
664
+ if (isSafari()) {
665
+ /**
666
+ * iOS Safari blocks audio element playback if
667
+ * - user is not publishing audio themselves and
668
+ * - no other audio source is playing
669
+ *
670
+ * as a workaround, we create an audio element with an empty track, so that
671
+ * silent audio is always playing
672
+ */
673
+ const audioId = 'livekit-dummy-audio-el';
674
+ let dummyAudioEl = document.getElementById(audioId) as HTMLAudioElement | null;
675
+ if (!dummyAudioEl) {
676
+ dummyAudioEl = document.createElement('audio');
677
+ dummyAudioEl.autoplay = true;
678
+ dummyAudioEl.hidden = true;
679
+ const track = getEmptyAudioStreamTrack();
680
+ track.enabled = true;
681
+ dummyAudioEl.srcObject = new MediaStream([track]);
682
+ document.body.append(dummyAudioEl);
683
+ }
684
+ elements.push(dummyAudioEl);
685
+ }
686
+
659
687
  this.participants.forEach((p) => {
660
688
  p.audioTracks.forEach((t) => {
661
689
  if (t.track) {
@@ -962,6 +990,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
962
990
  if (isWeb()) {
963
991
  window.removeEventListener('beforeunload', this.onPageLeave);
964
992
  window.removeEventListener('pagehide', this.onPageLeave);
993
+ window.removeEventListener('freeze', this.onPageLeave);
965
994
  navigator.mediaDevices?.removeEventListener('devicechange', this.handleDeviceChange);
966
995
  }
967
996
  } finally {
@@ -1114,6 +1143,21 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
1114
1143
  pub.setAllowed(update.allowed);
1115
1144
  };
1116
1145
 
1146
+ private handleSubscriptionError = (update: SubscriptionResponse) => {
1147
+ const participant = Array.from(this.participants.values()).find((p) =>
1148
+ p.tracks.has(update.trackSid),
1149
+ );
1150
+ if (!participant) {
1151
+ return;
1152
+ }
1153
+ const pub = participant.getTrackPublication(update.trackSid);
1154
+ if (!pub) {
1155
+ return;
1156
+ }
1157
+
1158
+ pub.setSubscriptionError(update.err);
1159
+ };
1160
+
1117
1161
  private handleDataPacket = (userPacket: UserPacket, kind: DataPacket_Kind) => {
1118
1162
  // find the participant
1119
1163
  const participant = this.participants.get(userPacket.participantSid);
@@ -1280,6 +1324,9 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
1280
1324
  .on(ParticipantEvent.TrackSubscriptionStatusChanged, (pub, status) => {
1281
1325
  this.emitWhenConnected(RoomEvent.TrackSubscriptionStatusChanged, pub, status, participant);
1282
1326
  })
1327
+ .on(ParticipantEvent.TrackSubscriptionFailed, (trackSid, error) => {
1328
+ this.emit(RoomEvent.TrackSubscriptionFailed, trackSid, participant, error);
1329
+ })
1283
1330
  .on(ParticipantEvent.TrackSubscriptionPermissionChanged, (pub, status) => {
1284
1331
  this.emitWhenConnected(
1285
1332
  RoomEvent.TrackSubscriptionPermissionChanged,
@@ -1399,9 +1446,9 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
1399
1446
  return true;
1400
1447
  }
1401
1448
 
1402
- private emitWhenConnected<E extends keyof RoomEventCallbacks>(
1403
- event: E,
1404
- ...args: Parameters<RoomEventCallbacks[E]>
1449
+ private emitWhenConnected<T extends EventEmitter.EventNames<RoomEventCallbacks>>(
1450
+ event: T,
1451
+ ...args: EventEmitter.EventArgs<RoomEventCallbacks, T>
1405
1452
  ): boolean {
1406
1453
  if (this.state === ConnectionState.Connected) {
1407
1454
  return this.emit(event, ...args);
@@ -1578,10 +1625,14 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
1578
1625
  }
1579
1626
 
1580
1627
  // /** @internal */
1581
- emit<E extends keyof RoomEventCallbacks>(
1582
- event: E,
1583
- ...args: Parameters<RoomEventCallbacks[E]>
1628
+ emit<T extends EventEmitter.EventNames<RoomEventCallbacks>>(
1629
+ event: T,
1630
+ ...args: EventEmitter.EventArgs<RoomEventCallbacks, T>
1584
1631
  ): boolean {
1632
+ // emit<E extends keyof RoomEventCallbacks>(
1633
+ // event: E,
1634
+ // ...args: Parameters<RoomEventCallbacks[E]>
1635
+ // ): boolean {
1585
1636
  // active speaker updates are too spammy
1586
1637
  if (event !== RoomEvent.ActiveSpeakersChanged) {
1587
1638
  log.debug(`room event ${event}`, { event, args });
@@ -1609,7 +1660,11 @@ export type RoomEventCallbacks = {
1609
1660
  publication: RemoteTrackPublication,
1610
1661
  participant: RemoteParticipant,
1611
1662
  ) => void;
1612
- trackSubscriptionFailed: (trackSid: string, participant: RemoteParticipant) => void;
1663
+ trackSubscriptionFailed: (
1664
+ trackSid: string,
1665
+ participant: RemoteParticipant,
1666
+ reason?: SubscriptionError,
1667
+ ) => void;
1613
1668
  trackUnpublished: (publication: RemoteTrackPublication, participant: RemoteParticipant) => void;
1614
1669
  trackUnsubscribed: (
1615
1670
  track: RemoteTrack,
@@ -508,4 +508,8 @@ export enum TrackEvent {
508
508
  * Fires on RemoteTrackPublication
509
509
  */
510
510
  SubscriptionStatusChanged = 'subscriptionStatusChanged',
511
+ /**
512
+ * Fires on RemoteTrackPublication
513
+ */
514
+ SubscriptionFailed = 'subscriptionFailed',
511
515
  }
@@ -143,8 +143,11 @@ export default class LocalParticipant extends Participant {
143
143
  };
144
144
 
145
145
  private handleDisconnected = () => {
146
- this.reconnectFuture?.reject?.('Got disconnected during publishing attempt');
147
- this.reconnectFuture = undefined;
146
+ if (this.reconnectFuture) {
147
+ this.reconnectFuture.promise.catch((e) => log.warn(e));
148
+ this.reconnectFuture?.reject?.('Got disconnected during reconnection attempt');
149
+ this.reconnectFuture = undefined;
150
+ }
148
151
  };
149
152
 
150
153
  /**
@@ -625,8 +628,8 @@ export default class LocalParticipant extends Participant {
625
628
  // for svc codecs, disable simulcast and use vp8 for backup codec
626
629
  if (track instanceof LocalVideoTrack) {
627
630
  if (isSVCCodec(opts.videoCodec)) {
628
- // set scalabilityMode to 'L3T3' by default
629
- opts.scalabilityMode = opts.scalabilityMode ?? 'L3T3';
631
+ // set scalabilityMode to 'L3T3_KEY' by default
632
+ opts.scalabilityMode = opts.scalabilityMode ?? 'L3T3_KEY';
630
633
  }
631
634
 
632
635
  // set up backup
@@ -647,6 +650,16 @@ export default class LocalParticipant extends Participant {
647
650
  enableSimulcastLayers: true,
648
651
  },
649
652
  ];
653
+ } else if (opts.videoCodec) {
654
+ // pass codec info to sfu so it can prefer codec for the client which don't support
655
+ // setCodecPreferences
656
+ req.simulcastCodecs = [
657
+ {
658
+ codec: opts.videoCodec,
659
+ cid: track.mediaStreamTrack.id,
660
+ enableSimulcastLayers: opts.simulcast ?? false,
661
+ },
662
+ ];
650
663
  }
651
664
  }
652
665
 
@@ -656,7 +669,12 @@ export default class LocalParticipant extends Participant {
656
669
  dims.height,
657
670
  opts,
658
671
  );
659
- req.layers = videoLayersFromEncodings(req.width, req.height, simEncodings ?? encodings);
672
+ req.layers = videoLayersFromEncodings(
673
+ req.width,
674
+ req.height,
675
+ encodings,
676
+ isSVCCodec(opts.videoCodec),
677
+ );
660
678
  } else if (track.kind === Track.Kind.Audio) {
661
679
  encodings = [
662
680
  {
@@ -850,6 +868,16 @@ export default class LocalParticipant extends Participant {
850
868
  trackSender
851
869
  ) {
852
870
  try {
871
+ for (const transceiver of this.engine.publisher.pc.getTransceivers()) {
872
+ // if sender is not currently sending (after replaceTrack(null))
873
+ // removeTrack would have no effect.
874
+ // to ensure we end up successfully removing the track, manually set
875
+ // the transceiver to inactive
876
+ if (transceiver.sender === trackSender) {
877
+ transceiver.direction = 'inactive';
878
+ negotiationNeeded = true;
879
+ }
880
+ }
853
881
  if (this.engine.removeTrack(trackSender)) {
854
882
  negotiationNeeded = true;
855
883
  }
@@ -1,11 +1,11 @@
1
- import { EventEmitter } from 'events';
2
- import type TypedEmitter from 'typed-emitter';
1
+ import EventEmitter from 'eventemitter3';
3
2
  import log from '../../logger';
4
3
  import {
5
4
  DataPacket_Kind,
6
5
  ParticipantInfo,
7
6
  ParticipantPermission,
8
7
  ConnectionQuality as ProtoQuality,
8
+ SubscriptionError,
9
9
  } from '../../proto/livekit_models';
10
10
  import { ParticipantEvent, TrackEvent } from '../events';
11
11
  import type LocalTrackPublication from '../track/LocalTrackPublication';
@@ -34,7 +34,7 @@ function qualityFromProto(q: ProtoQuality): ConnectionQuality {
34
34
  }
35
35
  }
36
36
 
37
- export default class Participant extends (EventEmitter as new () => TypedEmitter<ParticipantEventCallbacks>) {
37
+ export default class Participant extends EventEmitter<ParticipantEventCallbacks> {
38
38
  protected participantInfo?: ParticipantInfo;
39
39
 
40
40
  audioTracks: Map<string, TrackPublication>;
@@ -71,7 +71,6 @@ export default class Participant extends (EventEmitter as new () => TypedEmitter
71
71
  /** @internal */
72
72
  constructor(sid: string, identity: string, name?: string, metadata?: string) {
73
73
  super();
74
- this.setMaxListeners(100);
75
74
  this.sid = sid;
76
75
  this.identity = identity;
77
76
  this.name = name;
@@ -265,7 +264,7 @@ export default class Participant extends (EventEmitter as new () => TypedEmitter
265
264
  export type ParticipantEventCallbacks = {
266
265
  trackPublished: (publication: RemoteTrackPublication) => void;
267
266
  trackSubscribed: (track: RemoteTrack, publication: RemoteTrackPublication) => void;
268
- trackSubscriptionFailed: (trackSid: string) => void;
267
+ trackSubscriptionFailed: (trackSid: string, reason?: SubscriptionError) => void;
269
268
  trackUnpublished: (publication: RemoteTrackPublication) => void;
270
269
  trackUnsubscribed: (track: RemoteTrack, publication: RemoteTrackPublication) => void;
271
270
  trackMuted: (publication: TrackPublication) => void;
@@ -1,6 +1,7 @@
1
+ import type EventEmitter from 'eventemitter3';
1
2
  import type { SignalClient } from '../../api/SignalClient';
2
3
  import log from '../../logger';
3
- import type { ParticipantInfo } from '../../proto/livekit_models';
4
+ import type { ParticipantInfo, SubscriptionError } from '../../proto/livekit_models';
4
5
  import type { UpdateSubscription, UpdateTrackSettings } from '../../proto/livekit_rtc';
5
6
  import { ParticipantEvent, TrackEvent } from '../events';
6
7
  import RemoteAudioTrack from '../track/RemoteAudioTrack';
@@ -81,6 +82,9 @@ export default class RemoteParticipant extends Participant {
81
82
  publication.on(TrackEvent.Unsubscribed, (previousTrack: RemoteTrack) => {
82
83
  this.emit(ParticipantEvent.TrackUnsubscribed, previousTrack, publication);
83
84
  });
85
+ publication.on(TrackEvent.SubscriptionFailed, (error: SubscriptionError) => {
86
+ this.emit(ParticipantEvent.TrackSubscriptionFailed, publication.trackSid, error);
87
+ });
84
88
  }
85
89
 
86
90
  getTrack(source: Track.Source): RemoteTrackPublication | undefined {
@@ -342,9 +346,9 @@ export default class RemoteParticipant extends Participant {
342
346
  }
343
347
 
344
348
  /** @internal */
345
- emit<E extends keyof ParticipantEventCallbacks>(
346
- event: E,
347
- ...args: Parameters<ParticipantEventCallbacks[E]>
349
+ emit<T extends EventEmitter.EventNames<ParticipantEventCallbacks>>(
350
+ event: T,
351
+ ...args: EventEmitter.EventArgs<ParticipantEventCallbacks, T>
348
352
  ): boolean {
349
353
  log.trace('participant event', { participant: this.sid, event, args });
350
354
  return super.emit(event, ...args);
@@ -123,29 +123,25 @@ export function computeVideoEncodings(
123
123
  if (scalabilityMode && isSVCCodec(videoCodec)) {
124
124
  log.debug(`using svc with scalabilityMode ${scalabilityMode}`);
125
125
 
126
- const encodings: RTCRtpEncodingParameters[] = [];
126
+ const sm = new ScalabilityMode(scalabilityMode);
127
127
 
128
- // svc use first encoding as the original, so we sort encoding from high to low
129
- switch (scalabilityMode) {
130
- case 'L3T3':
131
- for (let i = 0; i < 3; i += 1) {
132
- encodings.push({
133
- rid: videoRids[2 - i],
134
- scaleResolutionDownBy: 2 ** i,
135
- maxBitrate: videoEncoding.maxBitrate / 3 ** i,
136
- /* @ts-ignore */
137
- maxFramerate: original.encoding.maxFramerate,
138
- /* @ts-ignore */
139
- scalabilityMode: 'L3T3',
140
- });
141
- }
142
- log.debug('encodings', encodings);
143
- return encodings;
128
+ const encodings: RTCRtpEncodingParameters[] = [];
144
129
 
145
- default:
146
- // TODO : support other scalability modes
147
- throw new Error(`unsupported scalabilityMode: ${scalabilityMode}`);
130
+ if (sm.spatial > 3) {
131
+ throw new Error(`unsupported scalabilityMode: ${scalabilityMode}`);
132
+ }
133
+ for (let i = 0; i < sm.spatial; i += 1) {
134
+ encodings.push({
135
+ rid: videoRids[2 - i],
136
+ maxBitrate: videoEncoding.maxBitrate / 3 ** i,
137
+ /* @ts-ignore */
138
+ maxFramerate: original.encoding.maxFramerate,
139
+ });
148
140
  }
141
+ /* @ts-ignore */
142
+ encodings[0].scalabilityMode = scalabilityMode;
143
+ log.debug('encodings', encodings);
144
+ return encodings;
149
145
  }
150
146
 
151
147
  if (!useSimulcast) {
@@ -368,3 +364,34 @@ export function sortPresets(presets: Array<VideoPreset> | undefined) {
368
364
  return 0;
369
365
  });
370
366
  }
367
+
368
+ /** @internal */
369
+ export class ScalabilityMode {
370
+ spatial: number;
371
+
372
+ temporal: number;
373
+
374
+ suffix: undefined | 'h' | '_KEY' | '_KEY_SHIFT';
375
+
376
+ constructor(scalabilityMode: string) {
377
+ const results = scalabilityMode.match(/^L(\d)T(\d)(h|_KEY|_KEY_SHIFT){0,1}$/);
378
+ if (!results) {
379
+ throw new Error('invalid scalability mode');
380
+ }
381
+
382
+ this.spatial = parseInt(results[1]);
383
+ this.temporal = parseInt(results[2]);
384
+ if (results.length > 3) {
385
+ switch (results[3]) {
386
+ case 'h':
387
+ case '_KEY':
388
+ case '_KEY_SHIFT':
389
+ this.suffix = results[3];
390
+ }
391
+ }
392
+ }
393
+
394
+ toString(): string {
395
+ return `L${this.spatial}T${this.temporal}${this.suffix ?? ''}`;
396
+ }
397
+ }