livekit-client 0.16.4 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/dist/api/RequestQueue.js +6 -6
  2. package/dist/api/RequestQueue.js.map +1 -1
  3. package/dist/api/SignalClient.d.ts +3 -0
  4. package/dist/api/SignalClient.js +23 -3
  5. package/dist/api/SignalClient.js.map +1 -1
  6. package/dist/connect.js +1 -1
  7. package/dist/connect.js.map +1 -1
  8. package/dist/options.d.ts +7 -2
  9. package/dist/proto/livekit_models.d.ts +33 -0
  10. package/dist/proto/livekit_models.js +213 -3
  11. package/dist/proto/livekit_models.js.map +1 -1
  12. package/dist/proto/livekit_rtc.d.ts +15 -1
  13. package/dist/proto/livekit_rtc.js +128 -2
  14. package/dist/proto/livekit_rtc.js.map +1 -1
  15. package/dist/room/RTCEngine.d.ts +3 -2
  16. package/dist/room/RTCEngine.js +12 -8
  17. package/dist/room/RTCEngine.js.map +1 -1
  18. package/dist/room/Room.js +19 -8
  19. package/dist/room/Room.js.map +1 -1
  20. package/dist/room/events.d.ts +6 -1
  21. package/dist/room/events.js +6 -1
  22. package/dist/room/events.js.map +1 -1
  23. package/dist/room/participant/LocalParticipant.d.ts +3 -1
  24. package/dist/room/participant/LocalParticipant.js +17 -1
  25. package/dist/room/participant/LocalParticipant.js.map +1 -1
  26. package/dist/room/participant/RemoteParticipant.d.ts +2 -1
  27. package/dist/room/participant/RemoteParticipant.js +3 -3
  28. package/dist/room/participant/RemoteParticipant.js.map +1 -1
  29. package/dist/room/participant/publishUtils.d.ts +6 -0
  30. package/dist/room/participant/publishUtils.js +65 -24
  31. package/dist/room/participant/publishUtils.js.map +1 -1
  32. package/dist/room/participant/publishUtils.test.js +35 -5
  33. package/dist/room/participant/publishUtils.test.js.map +1 -1
  34. package/dist/room/track/LocalAudioTrack.d.ts +2 -0
  35. package/dist/room/track/LocalAudioTrack.js +23 -0
  36. package/dist/room/track/LocalAudioTrack.js.map +1 -1
  37. package/dist/room/track/LocalTrack.d.ts +4 -0
  38. package/dist/room/track/LocalTrack.js +34 -0
  39. package/dist/room/track/LocalTrack.js.map +1 -1
  40. package/dist/room/track/LocalVideoTrack.d.ts +1 -0
  41. package/dist/room/track/LocalVideoTrack.js +13 -0
  42. package/dist/room/track/LocalVideoTrack.js.map +1 -1
  43. package/dist/room/track/RemoteTrack.d.ts +1 -0
  44. package/dist/room/track/RemoteTrack.js +1 -0
  45. package/dist/room/track/RemoteTrack.js.map +1 -1
  46. package/dist/room/track/RemoteVideoTrack.d.ts +4 -2
  47. package/dist/room/track/RemoteVideoTrack.js +25 -11
  48. package/dist/room/track/RemoteVideoTrack.js.map +1 -1
  49. package/dist/room/track/Track.d.ts +4 -1
  50. package/dist/room/track/Track.js +20 -1
  51. package/dist/room/track/Track.js.map +1 -1
  52. package/dist/room/track/defaults.js +2 -2
  53. package/dist/room/track/defaults.js.map +1 -1
  54. package/dist/room/track/options.d.ts +65 -15
  55. package/dist/room/track/options.js +38 -0
  56. package/dist/room/track/options.js.map +1 -1
  57. package/dist/room/track/types.d.ts +11 -0
  58. package/dist/room/track/utils.d.ts +10 -0
  59. package/dist/room/track/utils.js +46 -1
  60. package/dist/room/track/utils.js.map +1 -1
  61. package/dist/room/utils.d.ts +1 -0
  62. package/dist/room/utils.js +5 -1
  63. package/dist/room/utils.js.map +1 -1
  64. package/dist/version.d.ts +1 -1
  65. package/dist/version.js +1 -1
  66. package/package.json +1 -1
  67. package/src/api/RequestQueue.ts +7 -7
  68. package/src/api/SignalClient.ts +31 -4
  69. package/src/connect.ts +1 -1
  70. package/src/options.ts +12 -3
  71. package/src/proto/livekit_models.ts +249 -0
  72. package/src/proto/livekit_rtc.ts +155 -0
  73. package/src/room/RTCEngine.ts +16 -9
  74. package/src/room/Room.ts +17 -7
  75. package/src/room/events.ts +6 -1
  76. package/src/room/participant/LocalParticipant.ts +23 -4
  77. package/src/room/participant/RemoteParticipant.ts +4 -4
  78. package/src/room/participant/publishUtils.test.ts +46 -6
  79. package/src/room/participant/publishUtils.ts +72 -27
  80. package/src/room/track/LocalAudioTrack.ts +19 -1
  81. package/src/room/track/LocalTrack.ts +36 -0
  82. package/src/room/track/LocalVideoTrack.ts +9 -1
  83. package/src/room/track/RemoteTrack.ts +2 -0
  84. package/src/room/track/RemoteVideoTrack.ts +20 -9
  85. package/src/room/track/Track.ts +14 -2
  86. package/src/room/track/defaults.ts +2 -2
  87. package/src/room/track/options.ts +55 -3
  88. package/src/room/track/types.ts +12 -0
  89. package/src/room/track/utils.ts +39 -0
  90. package/src/room/utils.ts +4 -0
  91. package/src/version.ts +1 -1
@@ -26,32 +26,38 @@ export function mediaTrackToLocalTrack(
26
26
  }
27
27
 
28
28
  /* @internal */
29
- export const presets169 = [
30
- VideoPresets.qvga,
31
- VideoPresets.vga,
32
- VideoPresets.qhd,
33
- VideoPresets.hd,
34
- VideoPresets.fhd,
35
- ];
29
+ export const presets169 = Object.values(VideoPresets);
30
+
31
+ /* @internal */
32
+ export const presets43 = Object.values(VideoPresets43);
33
+
34
+ /* @internal */
35
+ export const presetsScreenShare = Object.values(ScreenSharePresets);
36
36
 
37
37
  /* @internal */
38
- export const presets43 = [
39
- VideoPresets43.qvga,
40
- VideoPresets43.vga,
41
- VideoPresets43.qhd,
42
- VideoPresets43.hd,
43
- VideoPresets43.fhd,
38
+ export const defaultSimulcastPresets169 = [
39
+ VideoPresets.h180,
40
+ VideoPresets.h360,
44
41
  ];
45
42
 
46
43
  /* @internal */
47
- export const presetsScreenShare = [
48
- ScreenSharePresets.vga,
49
- ScreenSharePresets.hd_8,
50
- ScreenSharePresets.hd_15,
51
- ScreenSharePresets.fhd_15,
52
- ScreenSharePresets.fhd_30,
44
+ export const defaultSimulcastPresets43 = [
45
+ VideoPresets43.h180,
46
+ VideoPresets43.h360,
53
47
  ];
54
48
 
49
+ /* @internal */
50
+ export const computeDefaultScreenShareSimulcastPresets = (fromPreset: VideoPreset) => {
51
+ const layers = [{ scaleResolutionDownBy: 2, fps: 3 }];
52
+ return layers.map((t) => new VideoPreset(
53
+ Math.floor(fromPreset.width / t.scaleResolutionDownBy),
54
+ Math.floor(fromPreset.height / t.scaleResolutionDownBy),
55
+ Math.max(150_000, Math.floor(fromPreset.encoding.maxBitrate
56
+ / (t.scaleResolutionDownBy ** 2 * ((fromPreset.encoding.maxFramerate ?? 30) / t.fps)))),
57
+ t.fps,
58
+ ));
59
+ };
60
+
55
61
  const videoRids = ['q', 'h', 'f'];
56
62
 
57
63
  /* @internal */
@@ -65,7 +71,7 @@ export function computeVideoEncodings(
65
71
  if (isScreenShare) {
66
72
  videoEncoding = options?.screenShareEncoding;
67
73
  }
68
- const useSimulcast = !isScreenShare && options?.simulcast;
74
+ const useSimulcast = options?.simulcast;
69
75
 
70
76
  if ((!videoEncoding && !useSimulcast) || !width || !height) {
71
77
  // when we aren't simulcasting, will need to return a single encoding without
@@ -82,16 +88,22 @@ export function computeVideoEncodings(
82
88
  if (!useSimulcast) {
83
89
  return [videoEncoding];
84
90
  }
85
-
86
- const presets = presetsForResolution(isScreenShare, width, height);
91
+ const original = new VideoPreset(
92
+ width, height, videoEncoding.maxBitrate, videoEncoding.maxFramerate,
93
+ );
94
+ let presets: Array<VideoPreset> = [];
95
+ if (isScreenShare) {
96
+ presets = sortPresets(options?.screenShareSimulcastLayers)
97
+ ?? defaultSimulcastLayers(isScreenShare, original);
98
+ } else {
99
+ presets = sortPresets(options?.videoSimulcastLayers)
100
+ ?? defaultSimulcastLayers(isScreenShare, original);
101
+ }
87
102
  let midPreset: VideoPreset | undefined;
88
103
  const lowPreset = presets[0];
89
104
  if (presets.length > 1) {
90
- [,midPreset] = presets;
105
+ [, midPreset] = presets;
91
106
  }
92
- const original = new VideoPreset(
93
- width, height, videoEncoding.maxBitrate, videoEncoding.maxFramerate,
94
- );
95
107
 
96
108
  // NOTE:
97
109
  // 1. Ordering of these encodings is important. Chrome seems
@@ -108,7 +120,7 @@ export function computeVideoEncodings(
108
120
  lowPreset, midPreset, original,
109
121
  ]);
110
122
  }
111
- if (size >= 500) {
123
+ if (size >= 480) {
112
124
  return encodingsFromPresets(width, height, [
113
125
  lowPreset, original,
114
126
  ]);
@@ -155,6 +167,21 @@ export function presetsForResolution(
155
167
  return presets43;
156
168
  }
157
169
 
170
+ /* @internal */
171
+ export function defaultSimulcastLayers(
172
+ isScreenShare: boolean, original: VideoPreset,
173
+ ): VideoPreset[] {
174
+ if (isScreenShare) {
175
+ return computeDefaultScreenShareSimulcastPresets(original);
176
+ }
177
+ const { width, height } = original;
178
+ const aspect = width > height ? width / height : height / width;
179
+ if (Math.abs(aspect - 16.0 / 9) < Math.abs(aspect - 4.0 / 3)) {
180
+ return defaultSimulcastPresets169;
181
+ }
182
+ return defaultSimulcastPresets43;
183
+ }
184
+
158
185
  // presets should be ordered by low, medium, high
159
186
  function encodingsFromPresets(
160
187
  width: number,
@@ -178,3 +205,21 @@ function encodingsFromPresets(
178
205
  });
179
206
  return encodings;
180
207
  }
208
+
209
+ /** @internal */
210
+ export function sortPresets(presets: Array<VideoPreset> | undefined) {
211
+ if (!presets) return;
212
+ return presets.sort((a, b) => {
213
+ const { encoding: aEnc } = a;
214
+ const { encoding: bEnc } = b;
215
+
216
+ if (aEnc.maxBitrate > bEnc.maxBitrate) {
217
+ return 1;
218
+ }
219
+ if (aEnc.maxBitrate < bEnc.maxBitrate) return -1;
220
+ if (aEnc.maxBitrate === bEnc.maxBitrate && aEnc.maxFramerate && bEnc.maxFramerate) {
221
+ return aEnc.maxFramerate > bEnc.maxFramerate ? 1 : -1;
222
+ }
223
+ return 0;
224
+ });
225
+ }
@@ -1,9 +1,10 @@
1
1
  import log from '../../logger';
2
+ import { TrackEvent } from '../events';
2
3
  import { AudioSenderStats, computeBitrate, monitorFrequency } from '../stats';
3
4
  import LocalTrack from './LocalTrack';
4
5
  import { AudioCaptureOptions } from './options';
5
6
  import { Track } from './Track';
6
- import { constraintsForOptions } from './utils';
7
+ import { constraintsForOptions, detectSilence } from './utils';
7
8
 
8
9
  export default class LocalAudioTrack extends LocalTrack {
9
10
  sender?: RTCRtpSender;
@@ -18,6 +19,7 @@ export default class LocalAudioTrack extends LocalTrack {
18
19
  constraints?: MediaTrackConstraints,
19
20
  ) {
20
21
  super(mediaTrack, Track.Kind.Audio, constraints);
22
+ this.checkForSilence();
21
23
  }
22
24
 
23
25
  async setDeviceId(deviceId: string) {
@@ -61,6 +63,12 @@ export default class LocalAudioTrack extends LocalTrack {
61
63
  await this.restart(constraints);
62
64
  }
63
65
 
66
+ protected async restart(constraints?: MediaTrackConstraints): Promise<LocalTrack> {
67
+ const track = await super.restart(constraints);
68
+ this.checkForSilence();
69
+ return track;
70
+ }
71
+
64
72
  /* @internal */
65
73
  startMonitor() {
66
74
  setTimeout(() => {
@@ -116,4 +124,14 @@ export default class LocalAudioTrack extends LocalTrack {
116
124
 
117
125
  return audioStats;
118
126
  }
127
+
128
+ async checkForSilence() {
129
+ const trackIsSilent = await detectSilence(this);
130
+ if (trackIsSilent) {
131
+ if (!this.isMuted) {
132
+ log.warn('silence detected on local audio track');
133
+ }
134
+ this.emit(TrackEvent.AudioSilenceDetected);
135
+ }
136
+ }
119
137
  }
@@ -2,6 +2,7 @@ import log from '../../logger';
2
2
  import DeviceManager from '../DeviceManager';
3
3
  import { TrackInvalidError } from '../errors';
4
4
  import { TrackEvent } from '../events';
5
+ import { isMobile } from '../utils';
5
6
  import { attachToElement, detachTrack, Track } from './Track';
6
7
 
7
8
  export default class LocalTrack extends Track {
@@ -10,12 +11,18 @@ export default class LocalTrack extends Track {
10
11
 
11
12
  protected constraints: MediaTrackConstraints;
12
13
 
14
+ protected wasMuted: boolean;
15
+
16
+ protected reacquireTrack: boolean;
17
+
13
18
  protected constructor(
14
19
  mediaTrack: MediaStreamTrack, kind: Track.Kind, constraints?: MediaTrackConstraints,
15
20
  ) {
16
21
  super(mediaTrack, kind);
17
22
  this.mediaStreamTrack.addEventListener('ended', this.handleEnded);
18
23
  this.constraints = constraints ?? mediaTrack.getConstraints();
24
+ this.reacquireTrack = false;
25
+ this.wasMuted = false;
19
26
  }
20
27
 
21
28
  get id(): string {
@@ -118,7 +125,36 @@ export default class LocalTrack extends Track {
118
125
  this.emit(muted ? TrackEvent.Muted : TrackEvent.Unmuted, this);
119
126
  }
120
127
 
128
+ protected get needsReAcquisition(): boolean {
129
+ return this.mediaStreamTrack.readyState !== 'live'
130
+ || this.mediaStreamTrack.muted
131
+ || !this.mediaStreamTrack.enabled
132
+ || this.reacquireTrack;
133
+ }
134
+
135
+ protected async handleAppVisibilityChanged() {
136
+ await super.handleAppVisibilityChanged();
137
+ if (!isMobile()) return;
138
+ log.debug('visibility changed, is in Background: ', this.isInBackground);
139
+
140
+ if (!this.isInBackground && this.needsReAcquisition) {
141
+ log.debug('track needs to be reaquired, restarting', this.source);
142
+ await this.restart();
143
+ this.reacquireTrack = false;
144
+ // Restore muted state if had to be restarted
145
+ this.setTrackMuted(this.wasMuted);
146
+ }
147
+
148
+ // store muted state each time app goes to background
149
+ if (this.isInBackground) {
150
+ this.wasMuted = this.isMuted;
151
+ }
152
+ }
153
+
121
154
  private handleEnded = () => {
155
+ if (this.isInBackground) {
156
+ this.reacquireTrack = true;
157
+ }
122
158
  this.emit(TrackEvent.Ended, this);
123
159
  };
124
160
  }
@@ -3,7 +3,7 @@ import log from '../../logger';
3
3
  import { VideoLayer, VideoQuality } from '../../proto/livekit_models';
4
4
  import { SubscribedQuality } from '../../proto/livekit_rtc';
5
5
  import { computeBitrate, monitorFrequency, VideoSenderStats } from '../stats';
6
- import { isFireFox } from '../utils';
6
+ import { isFireFox, isMobile } from '../utils';
7
7
  import LocalTrack from './LocalTrack';
8
8
  import { VideoCaptureOptions } from './options';
9
9
  import { Track } from './Track';
@@ -238,6 +238,14 @@ export default class LocalVideoTrack extends LocalTrack {
238
238
  this.monitorSender();
239
239
  }, monitorFrequency);
240
240
  };
241
+
242
+ protected async handleAppVisibilityChanged() {
243
+ await super.handleAppVisibilityChanged();
244
+ if (!isMobile()) return;
245
+ if (this.isInBackground && this.source === Track.Source.Camera) {
246
+ this.mediaStreamTrack.enabled = false;
247
+ }
248
+ }
241
249
  }
242
250
 
243
251
  export function videoQualityForRid(rid: string): VideoQuality {
@@ -6,6 +6,8 @@ export default abstract class RemoteTrack extends Track {
6
6
  /** @internal */
7
7
  receiver?: RTCRtpReceiver;
8
8
 
9
+ streamState: Track.StreamState = Track.StreamState.Active;
10
+
9
11
  constructor(
10
12
  mediaTrack: MediaStreamTrack,
11
13
  sid: string,
@@ -4,6 +4,7 @@ import { computeBitrate, monitorFrequency, VideoReceiverStats } from '../stats';
4
4
  import { getIntersectionObserver, getResizeObserver, ObservableMediaElement } from '../utils';
5
5
  import RemoteTrack from './RemoteTrack';
6
6
  import { attachToElement, detachTrack, Track } from './Track';
7
+ import { AdaptiveStreamSettings } from './types';
7
8
 
8
9
  const REACTION_DELAY = 100;
9
10
 
@@ -15,7 +16,7 @@ export default class RemoteVideoTrack extends RemoteTrack {
15
16
 
16
17
  private elementInfos: ElementInfo[] = [];
17
18
 
18
- private adaptiveStream?: boolean;
19
+ private adaptiveStreamSettings?: AdaptiveStreamSettings;
19
20
 
20
21
  private lastVisible?: boolean;
21
22
 
@@ -25,14 +26,14 @@ export default class RemoteVideoTrack extends RemoteTrack {
25
26
  mediaTrack: MediaStreamTrack,
26
27
  sid: string,
27
28
  receiver?: RTCRtpReceiver,
28
- adaptiveStream?: boolean,
29
+ adaptiveStreamSettings?: AdaptiveStreamSettings,
29
30
  ) {
30
31
  super(mediaTrack, sid, Track.Kind.Video, receiver);
31
- this.adaptiveStream = adaptiveStream;
32
+ this.adaptiveStreamSettings = adaptiveStreamSettings;
32
33
  }
33
34
 
34
35
  get isAdaptiveStream(): boolean {
35
- return this.adaptiveStream ?? false;
36
+ return this.adaptiveStreamSettings !== undefined;
36
37
  }
37
38
 
38
39
  /** @internal */
@@ -60,7 +61,7 @@ export default class RemoteVideoTrack extends RemoteTrack {
60
61
 
61
62
  // It's possible attach is called multiple times on an element. When that's
62
63
  // the case, we'd want to avoid adding duplicate elementInfos
63
- if (this.adaptiveStream
64
+ if (this.adaptiveStreamSettings
64
65
  && this.elementInfos.find((info) => info.element === element) === undefined
65
66
  ) {
66
67
  this.elementInfos.push({
@@ -164,6 +165,12 @@ export default class RemoteVideoTrack extends RemoteTrack {
164
165
  this.updateVisibility();
165
166
  };
166
167
 
168
+ protected async handleAppVisibilityChanged() {
169
+ if (!this.isAdaptiveStream) return;
170
+ await super.handleAppVisibilityChanged();
171
+ this.updateVisibility();
172
+ }
173
+
167
174
  private readonly debouncedHandleResize = debounce(() => {
168
175
  this.updateDimensions();
169
176
  }, REACTION_DELAY);
@@ -173,7 +180,7 @@ export default class RemoteVideoTrack extends RemoteTrack {
173
180
  (prev, info) => Math.max(prev, info.visibilityChangedAt || 0),
174
181
  0,
175
182
  );
176
- const isVisible = this.elementInfos.some((info) => info.visible);
183
+ const isVisible = this.elementInfos.some((info) => info.visible) && !this.isInBackground;
177
184
 
178
185
  if (this.lastVisible === isVisible) {
179
186
  return;
@@ -195,9 +202,13 @@ export default class RemoteVideoTrack extends RemoteTrack {
195
202
  let maxWidth = 0;
196
203
  let maxHeight = 0;
197
204
  for (const info of this.elementInfos) {
198
- if (info.element.clientWidth + info.element.clientHeight > maxWidth + maxHeight) {
199
- maxWidth = info.element.clientWidth;
200
- maxHeight = info.element.clientHeight;
205
+ const pixelDensity = this.adaptiveStreamSettings?.pixelDensity ?? 1;
206
+ const pixelDensityValue = pixelDensity === 'screen' ? window.devicePixelRatio : pixelDensity;
207
+ const currentElementWidth = info.element.clientWidth * pixelDensityValue;
208
+ const currentElementHeight = info.element.clientHeight * pixelDensityValue;
209
+ if (currentElementWidth + currentElementHeight > maxWidth + maxHeight) {
210
+ maxWidth = currentElementWidth;
211
+ maxHeight = currentElementHeight;
201
212
  }
202
213
  }
203
214
 
@@ -18,10 +18,10 @@ export class Track extends (EventEmitter as new () => TypedEventEmitter<TrackEve
18
18
 
19
19
  isMuted: boolean = false;
20
20
 
21
- streamState: Track.StreamState = Track.StreamState.Active;
22
-
23
21
  source: Track.Source;
24
22
 
23
+ protected isInBackground: boolean;
24
+
25
25
  /**
26
26
  * sid is set after track is published to server, or if it's a remote track
27
27
  */
@@ -34,6 +34,8 @@ export class Track extends (EventEmitter as new () => TypedEventEmitter<TrackEve
34
34
  this.kind = kind;
35
35
  this.mediaStreamTrack = mediaTrack;
36
36
  this.source = Track.Source.Unknown;
37
+ this.isInBackground = document.visibilityState === 'hidden';
38
+ document.addEventListener('visibilitychange', this.appVisibilityChangedListener);
37
39
  }
38
40
 
39
41
  /** current receive bits per second */
@@ -131,6 +133,7 @@ export class Track extends (EventEmitter as new () => TypedEventEmitter<TrackEve
131
133
 
132
134
  stop() {
133
135
  this.mediaStreamTrack.stop();
136
+ document.removeEventListener('visibilitychange', this.appVisibilityChangedListener);
134
137
  }
135
138
 
136
139
  protected enable() {
@@ -156,6 +159,14 @@ export class Track extends (EventEmitter as new () => TypedEventEmitter<TrackEve
156
159
  }
157
160
  }
158
161
  }
162
+
163
+ appVisibilityChangedListener = () => {
164
+ this.handleAppVisibilityChanged();
165
+ };
166
+
167
+ protected async handleAppVisibilityChanged() {
168
+ this.isInBackground = document.visibilityState === 'hidden';
169
+ }
159
170
  }
160
171
 
161
172
  /** @internal */
@@ -318,6 +329,7 @@ export type TrackEventCallbacks = {
318
329
  updateSubscription: () => void,
319
330
  audioPlaybackStarted: () => void,
320
331
  audioPlaybackFailed: (error: Error) => void,
332
+ audioSilenceDetected: () => void,
321
333
  visibilityChanged: (visible: boolean, track?: any) => void,
322
334
  videoDimensionsChanged: (dimensions: Track.Dimensions, track?: any) => void,
323
335
  };
@@ -7,7 +7,7 @@ export const publishDefaults: TrackPublishDefaults = {
7
7
  audioBitrate: AudioPresets.speech.maxBitrate,
8
8
  dtx: true,
9
9
  simulcast: true,
10
- screenShareEncoding: ScreenSharePresets.hd_15.encoding,
10
+ screenShareEncoding: ScreenSharePresets.h720fps15.encoding,
11
11
  stopMicTrackOnMute: false,
12
12
  };
13
13
 
@@ -19,5 +19,5 @@ export const audioDefaults: AudioCaptureOptions = {
19
19
  };
20
20
 
21
21
  export const videoDefaults: VideoCaptureOptions = {
22
- resolution: VideoPresets.qhd.resolution,
22
+ resolution: VideoPresets.h540.resolution,
23
23
  };
@@ -33,6 +33,20 @@ export interface TrackPublishDefaults {
33
33
  */
34
34
  simulcast?: boolean;
35
35
 
36
+ /**
37
+ * custom video simulcast layers for camera tracks, defaults to h180, h360, h540
38
+ * You can specify up to two custom layers that will be used instead of
39
+ * the LiveKit default layers.
40
+ * Note: the layers need to be ordered from lowest to highest quality
41
+ */
42
+ videoSimulcastLayers?: Array<VideoPreset>;
43
+
44
+ /**
45
+ * custom video simulcast layers for screen tracks
46
+ * Note: the layers need to be ordered from lowest to highest quality
47
+ */
48
+ screenShareSimulcastLayers?: Array<VideoPreset>;
49
+
36
50
  /**
37
51
  * For local tracks, stop the underlying MediaStreamTrack when the track is muted (or paused)
38
52
  * on some platforms, this option is necessary to disable the microphone recording indicator.
@@ -202,28 +216,66 @@ export namespace AudioPresets {
202
216
  * Sane presets for video resolution/encoding
203
217
  */
204
218
  export const VideoPresets = {
219
+ h90: new VideoPreset(160, 90, 60_000, 15),
220
+ h180: new VideoPreset(320, 180, 120_000, 15),
221
+ h216: new VideoPreset(384, 216, 180_000, 15),
222
+ h360: new VideoPreset(640, 360, 300_000, 20),
223
+ h540: new VideoPreset(960, 540, 600_000, 25),
224
+ h720: new VideoPreset(1280, 720, 2_000_000, 30),
225
+ h1080: new VideoPreset(1920, 1080, 3_000_000, 30),
226
+ h1440: new VideoPreset(2560, 1440, 5_000_000, 30),
227
+ h2160: new VideoPreset(3840, 2160, 8_000_000, 30),
228
+ /** @deprecated */
205
229
  qvga: new VideoPreset(320, 180, 120_000, 10),
230
+ /** @deprecated */
206
231
  vga: new VideoPreset(640, 360, 300_000, 20),
232
+ /** @deprecated */
207
233
  qhd: new VideoPreset(960, 540, 600_000, 25),
234
+ /** @deprecated */
208
235
  hd: new VideoPreset(1280, 720, 2_000_000, 30),
236
+ /** @deprecated */
209
237
  fhd: new VideoPreset(1920, 1080, 3_000_000, 30),
210
- };
238
+ } as const;
211
239
 
212
240
  /**
213
241
  * Four by three presets
214
242
  */
215
243
  export const VideoPresets43 = {
244
+ h120: new VideoPreset(160, 120, 80_000, 15),
245
+ h180: new VideoPreset(240, 180, 100_000, 15),
246
+ h240: new VideoPreset(320, 240, 150_000, 15),
247
+ h360: new VideoPreset(480, 360, 225_000, 20),
248
+ h480: new VideoPreset(640, 480, 300_000, 20),
249
+ h540: new VideoPreset(720, 540, 450_000, 25),
250
+ h720: new VideoPreset(960, 720, 1_500_000, 30),
251
+ h1080: new VideoPreset(1440, 1080, 2_500_000, 30),
252
+ h1440: new VideoPreset(1920, 1440, 3_500_000, 30),
253
+ /** @deprecated */
216
254
  qvga: new VideoPreset(240, 180, 90_000, 10),
255
+ /** @deprecated */
217
256
  vga: new VideoPreset(480, 360, 225_000, 20),
257
+ /** @deprecated */
218
258
  qhd: new VideoPreset(720, 540, 450_000, 25),
259
+ /** @deprecated */
219
260
  hd: new VideoPreset(960, 720, 1_500_000, 30),
261
+ /** @deprecated */
220
262
  fhd: new VideoPreset(1440, 1080, 2_800_000, 30),
221
- };
263
+ } as const;
222
264
 
223
265
  export const ScreenSharePresets = {
266
+ h360fps3: new VideoPreset(640, 360, 200_000, 3),
267
+ h720fps5: new VideoPreset(1280, 720, 400_000, 5),
268
+ h720fps15: new VideoPreset(1280, 720, 1_000_000, 15),
269
+ h1080fps15: new VideoPreset(1920, 1080, 1_500_000, 15),
270
+ h1080fps30: new VideoPreset(1920, 1080, 3_000_000, 30),
271
+ /** @deprecated */
224
272
  vga: new VideoPreset(640, 360, 200_000, 3),
273
+ /** @deprecated */
225
274
  hd_8: new VideoPreset(1280, 720, 400_000, 5),
275
+ /** @deprecated */
226
276
  hd_15: new VideoPreset(1280, 720, 1_000_000, 15),
277
+ /** @deprecated */
227
278
  fhd_15: new VideoPreset(1920, 1080, 1_500_000, 15),
279
+ /** @deprecated */
228
280
  fhd_30: new VideoPreset(1920, 1080, 3_000_000, 30),
229
- };
281
+ } as const;
@@ -6,3 +6,15 @@ import type RemoteVideoTrack from './RemoteVideoTrack';
6
6
  export type RemoteTrack = RemoteAudioTrack | RemoteVideoTrack;
7
7
  export type AudioTrack = RemoteAudioTrack | LocalAudioTrack;
8
8
  export type VideoTrack = RemoteVideoTrack | LocalVideoTrack;
9
+
10
+ export type AdaptiveStreamSettings = {
11
+ /**
12
+ * Set a custom pixel density, defaults to 1
13
+ * When streaming videos on a ultra high definition screen this setting
14
+ * let's you account for the devicePixelRatio of those screens.
15
+ * Set it to `screen` to use the actual pixel density of the screen
16
+ * Note: this might significantly increase the bandwidth consumed by people
17
+ * streaming on high definition screens.
18
+ */
19
+ pixelDensity?: number | 'screen'
20
+ };
@@ -1,7 +1,9 @@
1
+ import { sleep } from '../utils';
1
2
  import {
2
3
  AudioCaptureOptions, CreateLocalTracksOptions,
3
4
  VideoCaptureOptions,
4
5
  } from './options';
6
+ import { AudioTrack } from './types';
5
7
 
6
8
  export function mergeDefaultOptions(
7
9
  options?: CreateLocalTracksOptions,
@@ -74,3 +76,40 @@ export function constraintsForOptions(options: CreateLocalTracksOptions): MediaS
74
76
  }
75
77
  return constraints;
76
78
  }
79
+ /**
80
+ * This function detects silence on a given [[Track]] instance.
81
+ * Returns true if the track seems to be entirely silent.
82
+ */
83
+ export async function detectSilence(
84
+ track: AudioTrack,
85
+ timeOffset = 200,
86
+ ): Promise<boolean> {
87
+ const ctx = getNewAudioContext();
88
+ if (ctx) {
89
+ const analyser = ctx.createAnalyser();
90
+ analyser.fftSize = 2048;
91
+
92
+ const bufferLength = analyser.frequencyBinCount;
93
+ const dataArray = new Uint8Array(bufferLength);
94
+ const source = ctx.createMediaStreamSource(new MediaStream([track.mediaStreamTrack]));
95
+
96
+ source.connect(analyser);
97
+ await sleep(timeOffset);
98
+ analyser.getByteTimeDomainData(dataArray);
99
+ const someNoise = dataArray.some((sample) => sample !== 128 && sample !== 0);
100
+ ctx.close();
101
+ return !someNoise;
102
+ }
103
+ return false;
104
+ }
105
+
106
+ /**
107
+ * @internal
108
+ */
109
+ export function getNewAudioContext(): AudioContext | void {
110
+ // @ts-ignore
111
+ const AudioContext = window.AudioContext || window.webkitAudioContext;
112
+ if (AudioContext) {
113
+ return new AudioContext();
114
+ }
115
+ }
package/src/room/utils.ts CHANGED
@@ -23,6 +23,10 @@ export function isSafari(): boolean {
23
23
  return /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
24
24
  }
25
25
 
26
+ export function isMobile(): boolean {
27
+ return /Tablet|iPad|Mobile|Android|BlackBerry/.test(navigator.userAgent);
28
+ }
29
+
26
30
  function roDispatchCallback(entries: ResizeObserverEntry[]) {
27
31
  for (const entry of entries) {
28
32
  (entry.target as ObservableMediaElement).handleResize(entry);
package/src/version.ts CHANGED
@@ -1,2 +1,2 @@
1
- export const version = '0.16.4';
1
+ export const version = '0.16.6';
2
2
  export const protocolVersion = 6;