livekit-client 2.0.9 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. package/dist/livekit-client.e2ee.worker.js +1 -1
  2. package/dist/livekit-client.e2ee.worker.js.map +1 -1
  3. package/dist/livekit-client.e2ee.worker.mjs +203 -140
  4. package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
  5. package/dist/livekit-client.esm.mjs +3792 -6746
  6. package/dist/livekit-client.esm.mjs.map +1 -1
  7. package/dist/livekit-client.umd.js +1 -1
  8. package/dist/livekit-client.umd.js.map +1 -1
  9. package/dist/src/api/SignalClient.d.ts +2 -1
  10. package/dist/src/api/SignalClient.d.ts.map +1 -1
  11. package/dist/src/connectionHelper/ConnectionCheck.d.ts +3 -2
  12. package/dist/src/connectionHelper/ConnectionCheck.d.ts.map +1 -1
  13. package/dist/src/e2ee/worker/FrameCryptor.d.ts.map +1 -1
  14. package/dist/src/index.d.ts +3 -2
  15. package/dist/src/index.d.ts.map +1 -1
  16. package/dist/src/room/RTCEngine.d.ts +1 -0
  17. package/dist/src/room/RTCEngine.d.ts.map +1 -1
  18. package/dist/src/room/events.d.ts +5 -1
  19. package/dist/src/room/events.d.ts.map +1 -1
  20. package/dist/src/room/participant/LocalParticipant.d.ts +1 -0
  21. package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
  22. package/dist/src/room/participant/publishUtils.d.ts.map +1 -1
  23. package/dist/src/room/stats.d.ts +6 -3
  24. package/dist/src/room/stats.d.ts.map +1 -1
  25. package/dist/src/room/track/LocalAudioTrack.d.ts +7 -0
  26. package/dist/src/room/track/LocalAudioTrack.d.ts.map +1 -1
  27. package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
  28. package/dist/src/room/track/LocalTrackPublication.d.ts +3 -2
  29. package/dist/src/room/track/LocalTrackPublication.d.ts.map +1 -1
  30. package/dist/src/room/track/LocalVideoTrack.d.ts.map +1 -1
  31. package/dist/src/room/track/Track.d.ts +2 -1
  32. package/dist/src/room/track/Track.d.ts.map +1 -1
  33. package/dist/src/room/track/options.d.ts +1 -1
  34. package/dist/src/room/track/options.d.ts.map +1 -1
  35. package/dist/src/room/utils.d.ts +3 -0
  36. package/dist/src/room/utils.d.ts.map +1 -1
  37. package/dist/ts4.2/src/api/SignalClient.d.ts +2 -1
  38. package/dist/ts4.2/src/connectionHelper/ConnectionCheck.d.ts +3 -2
  39. package/dist/ts4.2/src/index.d.ts +3 -2
  40. package/dist/ts4.2/src/room/RTCEngine.d.ts +1 -0
  41. package/dist/ts4.2/src/room/events.d.ts +5 -1
  42. package/dist/ts4.2/src/room/participant/LocalParticipant.d.ts +1 -0
  43. package/dist/ts4.2/src/room/stats.d.ts +6 -3
  44. package/dist/ts4.2/src/room/track/LocalAudioTrack.d.ts +7 -0
  45. package/dist/ts4.2/src/room/track/LocalTrackPublication.d.ts +3 -2
  46. package/dist/ts4.2/src/room/track/Track.d.ts +2 -1
  47. package/dist/ts4.2/src/room/track/options.d.ts +1 -1
  48. package/dist/ts4.2/src/room/utils.d.ts +3 -0
  49. package/package.json +10 -10
  50. package/src/api/SignalClient.ts +9 -0
  51. package/src/connectionHelper/ConnectionCheck.ts +6 -3
  52. package/src/e2ee/worker/FrameCryptor.ts +0 -1
  53. package/src/e2ee/worker/e2ee.worker.ts +3 -1
  54. package/src/index.ts +3 -0
  55. package/src/room/RTCEngine.ts +11 -1
  56. package/src/room/events.ts +5 -0
  57. package/src/room/participant/LocalParticipant.ts +14 -0
  58. package/src/room/participant/publishUtils.ts +2 -1
  59. package/src/room/stats.ts +9 -3
  60. package/src/room/track/LocalAudioTrack.ts +40 -0
  61. package/src/room/track/LocalTrack.ts +4 -2
  62. package/src/room/track/LocalTrackPublication.ts +28 -2
  63. package/src/room/track/LocalVideoTrack.ts +16 -7
  64. package/src/room/track/Track.ts +13 -0
  65. package/src/room/track/options.ts +22 -1
  66. package/src/room/utils.ts +3 -0
@@ -1,4 +1,4 @@
1
- import { StreamState as ProtoStreamState, TrackSource, TrackType } from '@livekit/protocol';
1
+ import { AudioTrackFeature, StreamState as ProtoStreamState, TrackSource, TrackType } from '@livekit/protocol';
2
2
  import type TypedEventEmitter from 'typed-emitter';
3
3
  import type { SignalClient } from '../../api/SignalClient';
4
4
  import { StructuredLogger } from '../../logger';
@@ -136,6 +136,7 @@ export type TrackEventCallbacks = {
136
136
  upstreamPaused: (track: any) => void;
137
137
  upstreamResumed: (track: any) => void;
138
138
  trackProcessorUpdate: (processor?: TrackProcessor<Track.Kind, any>) => void;
139
+ audioTrackFeatureUpdate: (track: any, feature: AudioTrackFeature, enabled: boolean) => void;
139
140
  };
140
141
  export {};
141
142
  //# sourceMappingURL=Track.d.ts.map
@@ -263,7 +263,7 @@ export declare function isBackupCodec(codec: string): codec is BackupVideoCodec;
263
263
  /**
264
264
  * scalability modes for svc.
265
265
  */
266
- export type ScalabilityMode = 'L1T3' | 'L2T3' | 'L2T3_KEY' | 'L3T3' | 'L3T3_KEY';
266
+ export type ScalabilityMode = 'L1T1' | 'L1T2' | 'L1T3' | 'L2T1' | 'L2T1h' | 'L2T1_KEY' | 'L2T2' | 'L2T2h' | 'L2T2_KEY' | 'L2T3' | 'L2T3h' | 'L2T3_KEY' | 'L3T1' | 'L3T1h' | 'L3T1_KEY' | 'L3T2' | 'L3T2h' | 'L3T2_KEY' | 'L3T3' | 'L3T3h' | 'L3T3_KEY';
267
267
  export declare namespace AudioPresets {
268
268
  const telephone: AudioPreset;
269
269
  const speech: AudioPreset;
@@ -79,6 +79,9 @@ export declare function createAudioAnalyser(track: LocalAudioTrack | RemoteAudio
79
79
  analyser: AnalyserNode;
80
80
  cleanup: () => Promise<void>;
81
81
  };
82
+ /**
83
+ * @internal
84
+ */
82
85
  export declare class Mutex {
83
86
  private _locking;
84
87
  private _locks;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "livekit-client",
3
- "version": "2.0.9",
3
+ "version": "2.1.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",
@@ -36,7 +36,7 @@
36
36
  "author": "David Zhao <david@davidzhao.com>",
37
37
  "license": "Apache-2.0",
38
38
  "dependencies": {
39
- "@livekit/protocol": "1.10.4",
39
+ "@livekit/protocol": "1.13.0",
40
40
  "events": "^3.3.0",
41
41
  "loglevel": "^1.8.0",
42
42
  "sdp-transform": "^2.14.1",
@@ -46,8 +46,8 @@
46
46
  "webrtc-adapter": "^8.1.1"
47
47
  },
48
48
  "devDependencies": {
49
- "@babel/core": "7.23.9",
50
- "@babel/preset-env": "7.23.9",
49
+ "@babel/core": "7.24.4",
50
+ "@babel/preset-env": "7.24.4",
51
51
  "@bufbuild/protoc-gen-es": "^1.3.0",
52
52
  "@changesets/cli": "2.27.1",
53
53
  "@livekit/changesets-changelog-github": "^0.0.4",
@@ -65,23 +65,23 @@
65
65
  "@typescript-eslint/eslint-plugin": "5.62.0",
66
66
  "@typescript-eslint/parser": "5.62.0",
67
67
  "downlevel-dts": "^0.11.0",
68
- "eslint": "8.56.0",
69
- "eslint-config-airbnb-typescript": "17.1.0",
68
+ "eslint": "8.57.0",
69
+ "eslint-config-airbnb-typescript": "18.0.0",
70
70
  "eslint-config-prettier": "9.1.0",
71
71
  "eslint-plugin-ecmascript-compat": "^3.0.0",
72
72
  "eslint-plugin-import": "2.29.1",
73
73
  "gh-pages": "6.1.1",
74
74
  "jsdom": "^24.0.0",
75
75
  "prettier": "^3.0.0",
76
- "rollup": "4.9.6",
76
+ "rollup": "4.14.0",
77
77
  "rollup-plugin-delete": "^2.0.0",
78
78
  "rollup-plugin-re": "1.0.7",
79
79
  "rollup-plugin-typescript2": "0.36.0",
80
80
  "size-limit": "^8.2.4",
81
- "typedoc": "0.25.7",
81
+ "typedoc": "0.25.12",
82
82
  "typedoc-plugin-no-inherit": "1.4.0",
83
- "typescript": "5.3.3",
84
- "vite": "5.0.12",
83
+ "typescript": "5.4.3",
84
+ "vite": "5.0.13",
85
85
  "vitest": "^1.0.0"
86
86
  },
87
87
  "scripts": {
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  AddTrackRequest,
3
+ AudioTrackFeature,
3
4
  ClientInfo,
4
5
  ConnectionQualityUpdate,
5
6
  DisconnectReason,
@@ -27,6 +28,7 @@ import {
27
28
  TrackPublishedResponse,
28
29
  TrackUnpublishedResponse,
29
30
  TrickleRequest,
31
+ UpdateLocalAudioTrack,
30
32
  UpdateParticipantMetadata,
31
33
  UpdateSubscription,
32
34
  UpdateTrackSettings,
@@ -583,6 +585,13 @@ export class SignalClient {
583
585
  ]);
584
586
  }
585
587
 
588
+ sendUpdateLocalAudioTrack(trackSid: string, features: AudioTrackFeature[]) {
589
+ return this.sendRequest({
590
+ case: 'updateAudioTrack',
591
+ value: new UpdateLocalAudioTrack({ trackSid, features }),
592
+ });
593
+ }
594
+
586
595
  sendLeave() {
587
596
  return this.sendRequest({
588
597
  case: 'leave',
@@ -1,7 +1,7 @@
1
1
  import { EventEmitter } from 'events';
2
2
  import type TypedEmitter from 'typed-emitter';
3
+ import type { CheckInfo, CheckerOptions, InstantiableCheck } from './checks/Checker';
3
4
  import { CheckStatus, Checker } from './checks/Checker';
4
- import type { CheckInfo, InstantiableCheck } from './checks/Checker';
5
5
  import { PublishAudioCheck } from './checks/publishAudio';
6
6
  import { PublishVideoCheck } from './checks/publishVideo';
7
7
  import { ReconnectCheck } from './checks/reconnect';
@@ -16,12 +16,15 @@ export class ConnectionCheck extends (EventEmitter as new () => TypedEmitter<Con
16
16
 
17
17
  url: string;
18
18
 
19
+ options: CheckerOptions = {};
20
+
19
21
  private checkResults: Map<number, CheckInfo> = new Map();
20
22
 
21
- constructor(url: string, token: string) {
23
+ constructor(url: string, token: string, options: CheckerOptions = {}) {
22
24
  super();
23
25
  this.url = url;
24
26
  this.token = token;
27
+ this.options = options;
25
28
  }
26
29
 
27
30
  private getNextCheckId() {
@@ -50,7 +53,7 @@ export class ConnectionCheck extends (EventEmitter as new () => TypedEmitter<Con
50
53
 
51
54
  async createAndRunCheck<T extends Checker>(check: InstantiableCheck<T>) {
52
55
  const checkId = this.getNextCheckId();
53
- const test = new check(this.url, this.token);
56
+ const test = new check(this.url, this.token, this.options);
54
57
  const handleUpdate = (info: CheckInfo) => {
55
58
  this.updateCheck(checkId, info);
56
59
  };
@@ -607,7 +607,6 @@ export class FrameCryptor extends BaseFrameCryptor {
607
607
  if (this.rtpMap.size === 0) {
608
608
  return undefined;
609
609
  }
610
- // @ts-expect-error payloadType is not yet part of the typescript definition and currently not supported in Safari
611
610
  const payloadType = frame.getMetadata().payloadType;
612
611
  const codec = payloadType ? this.rtpMap.get(payloadType) : undefined;
613
612
  return codec;
@@ -245,9 +245,11 @@ function handleSifTrailer(trailer: Uint8Array) {
245
245
  if (self.RTCTransformEvent) {
246
246
  workerLogger.debug('setup transform event');
247
247
  // @ts-ignore
248
- self.onrtctransform = (event) => {
248
+ self.onrtctransform = (event: RTCTransformEvent) => {
249
+ // @ts-ignore .transformer property is part of RTCTransformEvent
249
250
  const transformer = event.transformer;
250
251
  workerLogger.debug('transformer', transformer);
252
+ // @ts-ignore monkey patching non standard flag
251
253
  transformer.handled = true;
252
254
  const { kind, participantIdentity, trackId, codec } = transformer.options;
253
255
  const cryptor = getTrackCryptor(participantIdentity, trackId);
package/src/index.ts CHANGED
@@ -20,6 +20,7 @@ import { TrackPublication } from './room/track/TrackPublication';
20
20
  import type { LiveKitReactNativeInfo } from './room/types';
21
21
  import type { AudioAnalyserOptions } from './room/utils';
22
22
  import {
23
+ Mutex,
23
24
  createAudioAnalyser,
24
25
  getEmptyAudioStreamTrack,
25
26
  getEmptyVideoStreamTrack,
@@ -32,6 +33,7 @@ import {
32
33
  import { getBrowser } from './utils/browserParser';
33
34
 
34
35
  export * from './connectionHelper/ConnectionCheck';
36
+ export * from './connectionHelper/checks/Checker';
35
37
  export * from './e2ee';
36
38
  export * from './options';
37
39
  export * from './room/errors';
@@ -79,6 +81,7 @@ export {
79
81
  supportsAdaptiveStream,
80
82
  supportsDynacast,
81
83
  supportsVP9,
84
+ Mutex,
82
85
  };
83
86
  export type {
84
87
  AudioAnalyserOptions,
@@ -169,6 +169,8 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
169
169
 
170
170
  private loggerOptions: LoggerOptions;
171
171
 
172
+ private publisherConnectionPromise: Promise<void> | undefined;
173
+
172
174
  constructor(private options: InternalRoomOptions) {
173
175
  super();
174
176
  this.log = getLogger(options.loggerName ?? LoggerNames.Engine);
@@ -398,6 +400,11 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
398
400
  this.pcManager.onDataChannel = this.handleDataChannel;
399
401
  this.pcManager.onStateChange = async (connectionState, publisherState, subscriberState) => {
400
402
  this.log.debug(`primary PC state changed ${connectionState}`, this.logContext);
403
+
404
+ if (['closed', 'disconnected', 'failed'].includes(publisherState)) {
405
+ // reset publisher connection promise
406
+ this.publisherConnectionPromise = undefined;
407
+ }
401
408
  if (connectionState === PCTransportState.CONNECTED) {
402
409
  const shouldEmit = this.pcState === PCState.New;
403
410
  this.pcState = PCState.Connected;
@@ -1179,7 +1186,10 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
1179
1186
  }
1180
1187
 
1181
1188
  private async ensurePublisherConnected(kind: DataPacket_Kind) {
1182
- await this.ensureDataTransportConnected(kind, false);
1189
+ if (!this.publisherConnectionPromise) {
1190
+ this.publisherConnectionPromise = this.ensureDataTransportConnected(kind, false);
1191
+ }
1192
+ await this.publisherConnectionPromise;
1183
1193
  }
1184
1194
 
1185
1195
  /* @internal */
@@ -557,4 +557,9 @@ export enum TrackEvent {
557
557
  * @internal
558
558
  */
559
559
  TrackProcessorUpdate = 'trackProcessorUpdate',
560
+
561
+ /**
562
+ * @internal
563
+ */
564
+ AudioTrackFeatureUpdate = 'audioTrackFeatureUpdate',
560
565
  }
@@ -685,6 +685,7 @@ export default class LocalParticipant extends Participant {
685
685
  track.on(TrackEvent.Ended, this.handleTrackEnded);
686
686
  track.on(TrackEvent.UpstreamPaused, this.onTrackUpstreamPaused);
687
687
  track.on(TrackEvent.UpstreamResumed, this.onTrackUpstreamResumed);
688
+ track.on(TrackEvent.AudioTrackFeatureUpdate, this.onTrackFeatureUpdate);
688
689
 
689
690
  // create track publication from track
690
691
  const req = new AddTrackRequest({
@@ -1037,6 +1038,7 @@ export default class LocalParticipant extends Participant {
1037
1038
  track.off(TrackEvent.Ended, this.handleTrackEnded);
1038
1039
  track.off(TrackEvent.UpstreamPaused, this.onTrackUpstreamPaused);
1039
1040
  track.off(TrackEvent.UpstreamResumed, this.onTrackUpstreamResumed);
1041
+ track.off(TrackEvent.AudioTrackFeatureUpdate, this.onTrackFeatureUpdate);
1040
1042
 
1041
1043
  if (stopOnUnpublish === undefined) {
1042
1044
  stopOnUnpublish = this.roomOptions?.stopLocalTrackOnUnpublish ?? true;
@@ -1293,6 +1295,18 @@ export default class LocalParticipant extends Participant {
1293
1295
  this.onTrackMuted(track, track.isMuted);
1294
1296
  };
1295
1297
 
1298
+ private onTrackFeatureUpdate = (track: LocalAudioTrack) => {
1299
+ const pub = this.audioTrackPublications.get(track.sid!);
1300
+ if (!pub) {
1301
+ this.log.warn(
1302
+ `Could not update local audio track settings, missing publication for track ${track.sid}`,
1303
+ this.logContext,
1304
+ );
1305
+ return;
1306
+ }
1307
+ this.engine.client.sendUpdateLocalAudioTrack(pub.trackSid, pub.getTrackFeatures());
1308
+ };
1309
+
1296
1310
  private handleSubscribedQualityUpdate = async (update: SubscribedQualityUpdate) => {
1297
1311
  if (!this.roomOptions?.dynacast) {
1298
1312
  return;
@@ -150,11 +150,12 @@ export function computeVideoEncodings(
150
150
  isSafari() ||
151
151
  (browser?.name === 'Chrome' && compareVersions(browser?.version, '113') < 0)
152
152
  ) {
153
+ const bitratesRatio = sm.suffix == 'h' ? 2 : 3;
153
154
  for (let i = 0; i < sm.spatial; i += 1) {
154
155
  // in legacy SVC, scaleResolutionDownBy cannot be set
155
156
  encodings.push({
156
157
  rid: videoRids[2 - i],
157
- maxBitrate: videoEncoding.maxBitrate / 3 ** i,
158
+ maxBitrate: videoEncoding.maxBitrate / bitratesRatio ** i,
158
159
  maxFramerate: original.encoding.maxFramerate,
159
160
  });
160
161
  }
package/src/room/stats.ts CHANGED
@@ -42,14 +42,20 @@ export interface VideoSenderStats extends SenderStats {
42
42
 
43
43
  frameHeight: number;
44
44
 
45
+ framesPerSecond: number;
46
+
45
47
  framesSent: number;
46
48
 
47
49
  // bandwidth, cpu, other, none
48
- qualityLimitationReason: string;
50
+ qualityLimitationReason?: string;
51
+
52
+ qualityLimitationDurations?: Record<string, number>;
53
+
54
+ qualityLimitationResolutionChanges?: number;
49
55
 
50
- qualityLimitationResolutionChanges: number;
56
+ retransmittedPacketsSent?: number;
51
57
 
52
- retransmittedPacketsSent: number;
58
+ targetBitrate: number;
53
59
  }
54
60
 
55
61
  interface ReceiverStats {
@@ -1,3 +1,4 @@
1
+ import { AudioTrackFeature } from '@livekit/protocol';
1
2
  import { TrackEvent } from '../events';
2
3
  import { computeBitrate, monitorFrequency } from '../stats';
3
4
  import type { AudioSenderStats } from '../stats';
@@ -15,8 +16,17 @@ export default class LocalAudioTrack extends LocalTrack<Track.Kind.Audio> {
15
16
 
16
17
  private prevStats?: AudioSenderStats;
17
18
 
19
+ private isKrispNoiseFilterEnabled = false;
20
+
18
21
  protected processor?: TrackProcessor<Track.Kind.Audio, AudioProcessorOptions> | undefined;
19
22
 
23
+ /**
24
+ * boolean indicating whether enhanced noise cancellation is currently being used on this track
25
+ */
26
+ get enhancedNoiseCancellation() {
27
+ return this.isKrispNoiseFilterEnabled;
28
+ }
29
+
20
30
  /**
21
31
  *
22
32
  * @param mediaTrack
@@ -152,6 +162,28 @@ export default class LocalAudioTrack extends LocalTrack<Track.Kind.Audio> {
152
162
  this.prevStats = stats;
153
163
  };
154
164
 
165
+ private handleKrispNoiseFilterEnable = () => {
166
+ this.isKrispNoiseFilterEnabled = true;
167
+ this.log.debug(`Krisp noise filter enabled`, this.logContext);
168
+ this.emit(
169
+ TrackEvent.AudioTrackFeatureUpdate,
170
+ this,
171
+ AudioTrackFeature.TF_ENHANCED_NOISE_CANCELLATION,
172
+ true,
173
+ );
174
+ };
175
+
176
+ private handleKrispNoiseFilterDisable = () => {
177
+ this.isKrispNoiseFilterEnabled = false;
178
+ this.log.debug(`Krisp noise filter disabled`, this.logContext);
179
+ this.emit(
180
+ TrackEvent.AudioTrackFeatureUpdate,
181
+ this,
182
+ AudioTrackFeature.TF_ENHANCED_NOISE_CANCELLATION,
183
+ false,
184
+ );
185
+ };
186
+
155
187
  async setProcessor(processor: TrackProcessor<Track.Kind.Audio, AudioProcessorOptions>) {
156
188
  const unlock = await this.processorLock.lock();
157
189
  try {
@@ -175,6 +207,14 @@ export default class LocalAudioTrack extends LocalTrack<Track.Kind.Audio> {
175
207
  this.processor = processor;
176
208
  if (this.processor.processedTrack) {
177
209
  await this.sender?.replaceTrack(this.processor.processedTrack);
210
+ this.processor.processedTrack.addEventListener(
211
+ 'enable-lk-krisp-noise-filter',
212
+ this.handleKrispNoiseFilterEnable,
213
+ );
214
+ this.processor.processedTrack.addEventListener(
215
+ 'disable-lk-krisp-noise-filter',
216
+ this.handleKrispNoiseFilterDisable,
217
+ );
178
218
  }
179
219
  this.emit(TrackEvent.TrackProcessorUpdate, this.processor);
180
220
  } finally {
@@ -448,6 +448,10 @@ export default abstract class LocalTrack<
448
448
  const unlock = await this.processorLock.lock();
449
449
  try {
450
450
  this.log.debug('setting up processor', this.logContext);
451
+
452
+ this.processorElement =
453
+ this.processorElement ?? (document.createElement(this.kind) as HTMLMediaElement);
454
+
451
455
  const processorOptions = {
452
456
  kind: this.kind,
453
457
  track: this._mediaStreamTrack,
@@ -461,8 +465,6 @@ export default abstract class LocalTrack<
461
465
  if (this.kind === 'unknown') {
462
466
  throw TypeError('cannot set processor on track of unknown kind');
463
467
  }
464
- this.processorElement =
465
- this.processorElement ?? (document.createElement(this.kind) as HTMLMediaElement);
466
468
 
467
469
  attachToElement(this._mediaStreamTrack, this.processorElement);
468
470
  this.processorElement.muted = true;
@@ -1,7 +1,7 @@
1
- import type { TrackInfo } from '@livekit/protocol';
1
+ import { AudioTrackFeature, TrackInfo } from '@livekit/protocol';
2
2
  import { TrackEvent } from '../events';
3
3
  import type { LoggerOptions } from '../types';
4
- import type LocalAudioTrack from './LocalAudioTrack';
4
+ import LocalAudioTrack from './LocalAudioTrack';
5
5
  import type LocalTrack from './LocalTrack';
6
6
  import type LocalVideoTrack from './LocalVideoTrack';
7
7
  import type { Track } from './Track';
@@ -82,6 +82,32 @@ export default class LocalTrackPublication extends TrackPublication {
82
82
  await this.track?.resumeUpstream();
83
83
  }
84
84
 
85
+ getTrackFeatures() {
86
+ if (this.track instanceof LocalAudioTrack) {
87
+ const settings = this.track!.mediaStreamTrack.getSettings();
88
+ const features: Set<AudioTrackFeature> = new Set();
89
+ if (settings.autoGainControl) {
90
+ features.add(AudioTrackFeature.TF_AUTO_GAIN_CONTROL);
91
+ }
92
+ if (settings.echoCancellation) {
93
+ features.add(AudioTrackFeature.TF_ECHO_CANCELLATION);
94
+ }
95
+ if (settings.noiseSuppression) {
96
+ features.add(AudioTrackFeature.TF_NOISE_SUPPRESSION);
97
+ }
98
+ if (settings.channelCount && settings.channelCount > 1) {
99
+ features.add(AudioTrackFeature.TF_STEREO);
100
+ }
101
+ if (!this.options?.dtx) {
102
+ features.add(AudioTrackFeature.TF_STEREO);
103
+ }
104
+ if (this.track.enhancedNoiseCancellation) {
105
+ features.add(AudioTrackFeature.TF_ENHANCED_NOISE_CANCELLATION);
106
+ }
107
+ return Array.from(features.values());
108
+ } else return [];
109
+ }
110
+
85
111
  handleTrackEnded = () => {
86
112
  this.emit(TrackEvent.Ended);
87
113
  };
@@ -180,17 +180,20 @@ export default class LocalVideoTrack extends LocalTrack<Track.Kind.Video> {
180
180
  streamId: v.id,
181
181
  frameHeight: v.frameHeight,
182
182
  frameWidth: v.frameWidth,
183
+ framesPerSecond: v.framesPerSecond,
184
+ framesSent: v.framesSent,
183
185
  firCount: v.firCount,
184
186
  pliCount: v.pliCount,
185
187
  nackCount: v.nackCount,
186
188
  packetsSent: v.packetsSent,
187
189
  bytesSent: v.bytesSent,
188
- framesSent: v.framesSent,
189
- timestamp: v.timestamp,
190
- rid: v.rid ?? v.id,
191
- retransmittedPacketsSent: v.retransmittedPacketsSent,
192
190
  qualityLimitationReason: v.qualityLimitationReason,
191
+ qualityLimitationDurations: v.qualityLimitationDurations,
193
192
  qualityLimitationResolutionChanges: v.qualityLimitationResolutionChanges,
193
+ rid: v.rid ?? v.id,
194
+ retransmittedPacketsSent: v.retransmittedPacketsSent,
195
+ targetBitrate: v.targetBitrate,
196
+ timestamp: v.timestamp,
194
197
  };
195
198
 
196
199
  // locate the appropriate remote-inbound-rtp item
@@ -205,6 +208,8 @@ export default class LocalVideoTrack extends LocalTrack<Track.Kind.Video> {
205
208
  }
206
209
  });
207
210
 
211
+ // make sure highest res layer is always first
212
+ items.sort((a, b) => (b.frameWidth ?? 0) - (a.frameWidth ?? 0));
208
213
  return items;
209
214
  }
210
215
 
@@ -567,13 +572,17 @@ export function videoLayersFromEncodings(
567
572
  const encodingSM = encodings[0].scalabilityMode as string;
568
573
  const sm = new ScalabilityMode(encodingSM);
569
574
  const layers = [];
575
+ const resRatio = sm.suffix == 'h' ? 1.5 : 2;
576
+ const bitratesRatio = sm.suffix == 'h' ? 2 : 3;
570
577
  for (let i = 0; i < sm.spatial; i += 1) {
571
578
  layers.push(
572
579
  new VideoLayer({
573
580
  quality: VideoQuality.HIGH - i,
574
- width: Math.ceil(width / 2 ** i),
575
- height: Math.ceil(height / 2 ** i),
576
- bitrate: encodings[0].maxBitrate ? Math.ceil(encodings[0].maxBitrate / 3 ** i) : 0,
581
+ width: Math.ceil(width / resRatio ** i),
582
+ height: Math.ceil(height / resRatio ** i),
583
+ bitrate: encodings[0].maxBitrate
584
+ ? Math.ceil(encodings[0].maxBitrate / bitratesRatio ** i)
585
+ : 0,
577
586
  ssrc: 0,
578
587
  }),
579
588
  );
@@ -1,4 +1,5 @@
1
1
  import {
2
+ AudioTrackFeature,
2
3
  VideoQuality as ProtoQuality,
3
4
  StreamState as ProtoStreamState,
4
5
  TrackSource,
@@ -300,6 +301,17 @@ export abstract class Track<
300
301
 
301
302
  protected async handleAppVisibilityChanged() {
302
303
  this.isInBackground = document.visibilityState === 'hidden';
304
+ if (!this.isInBackground && this.kind === Track.Kind.Video) {
305
+ setTimeout(
306
+ () =>
307
+ this.attachedElements.forEach((el) =>
308
+ el.play().catch(() => {
309
+ /** catch clause necessary for Safari */
310
+ }),
311
+ ),
312
+ 0,
313
+ );
314
+ }
303
315
  }
304
316
 
305
317
  protected addAppVisibilityListener() {
@@ -504,4 +516,5 @@ export type TrackEventCallbacks = {
504
516
  upstreamPaused: (track: any) => void;
505
517
  upstreamResumed: (track: any) => void;
506
518
  trackProcessorUpdate: (processor?: TrackProcessor<Track.Kind, any>) => void;
519
+ audioTrackFeatureUpdate: (track: any, feature: AudioTrackFeature, enabled: boolean) => void;
507
520
  };
@@ -350,7 +350,28 @@ export function isBackupCodec(codec: string): codec is BackupVideoCodec {
350
350
  /**
351
351
  * scalability modes for svc.
352
352
  */
353
- export type ScalabilityMode = 'L1T3' | 'L2T3' | 'L2T3_KEY' | 'L3T3' | 'L3T3_KEY';
353
+ export type ScalabilityMode =
354
+ | 'L1T1'
355
+ | 'L1T2'
356
+ | 'L1T3'
357
+ | 'L2T1'
358
+ | 'L2T1h'
359
+ | 'L2T1_KEY'
360
+ | 'L2T2'
361
+ | 'L2T2h'
362
+ | 'L2T2_KEY'
363
+ | 'L2T3'
364
+ | 'L2T3h'
365
+ | 'L2T3_KEY'
366
+ | 'L3T1'
367
+ | 'L3T1h'
368
+ | 'L3T1_KEY'
369
+ | 'L3T2'
370
+ | 'L3T2h'
371
+ | 'L3T2_KEY'
372
+ | 'L3T3'
373
+ | 'L3T3h'
374
+ | 'L3T3_KEY';
354
375
 
355
376
  export namespace AudioPresets {
356
377
  export const telephone: AudioPreset = {
package/src/room/utils.ts CHANGED
@@ -447,6 +447,9 @@ export function createAudioAnalyser(
447
447
  return { calculateVolume, analyser, cleanup };
448
448
  }
449
449
 
450
+ /**
451
+ * @internal
452
+ */
450
453
  export class Mutex {
451
454
  private _locking: Promise<void>;
452
455