livekit-client 1.5.0 → 1.6.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (91) hide show
  1. package/dist/livekit-client.esm.mjs +2257 -5488
  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 +3 -2
  6. package/dist/src/api/SignalClient.d.ts.map +1 -1
  7. package/dist/src/connectionHelper/ConnectionCheck.d.ts +1 -1
  8. package/dist/src/connectionHelper/ConnectionCheck.d.ts.map +1 -1
  9. package/dist/src/connectionHelper/checks/Checker.d.ts +4 -4
  10. package/dist/src/connectionHelper/checks/Checker.d.ts.map +1 -1
  11. package/dist/src/index.d.ts +3 -2
  12. package/dist/src/index.d.ts.map +1 -1
  13. package/dist/src/logger.d.ts +3 -3
  14. package/dist/src/logger.d.ts.map +1 -1
  15. package/dist/src/options.d.ts +4 -1
  16. package/dist/src/options.d.ts.map +1 -1
  17. package/dist/src/proto/google/protobuf/timestamp.d.ts +4 -4
  18. package/dist/src/proto/google/protobuf/timestamp.d.ts.map +1 -1
  19. package/dist/src/proto/livekit_models.d.ts +4 -4
  20. package/dist/src/proto/livekit_models.d.ts.map +1 -1
  21. package/dist/src/proto/livekit_rtc.d.ts +4 -4
  22. package/dist/src/proto/livekit_rtc.d.ts.map +1 -1
  23. package/dist/src/room/PCTransport.d.ts +7 -1
  24. package/dist/src/room/PCTransport.d.ts.map +1 -1
  25. package/dist/src/room/RTCEngine.d.ts +10 -4
  26. package/dist/src/room/RTCEngine.d.ts.map +1 -1
  27. package/dist/src/room/Room.d.ts +21 -4
  28. package/dist/src/room/Room.d.ts.map +1 -1
  29. package/dist/src/room/events.d.ts +5 -0
  30. package/dist/src/room/events.d.ts.map +1 -1
  31. package/dist/src/room/participant/LocalParticipant.d.ts +3 -2
  32. package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
  33. package/dist/src/room/participant/Participant.d.ts +1 -1
  34. package/dist/src/room/participant/Participant.d.ts.map +1 -1
  35. package/dist/src/room/track/LocalTrack.d.ts +1 -0
  36. package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
  37. package/dist/src/room/track/RemoteVideoTrack.d.ts.map +1 -1
  38. package/dist/src/room/track/Track.d.ts +2 -1
  39. package/dist/src/room/track/Track.d.ts.map +1 -1
  40. package/dist/src/room/track/TrackPublication.d.ts +1 -1
  41. package/dist/src/room/track/TrackPublication.d.ts.map +1 -1
  42. package/dist/src/room/track/options.d.ts +3 -3
  43. package/dist/src/room/track/options.d.ts.map +1 -1
  44. package/dist/src/room/track/types.d.ts +3 -3
  45. package/dist/src/room/track/types.d.ts.map +1 -1
  46. package/dist/src/room/types.d.ts +13 -0
  47. package/dist/src/room/types.d.ts.map +1 -0
  48. package/dist/src/room/utils.d.ts +44 -0
  49. package/dist/src/room/utils.d.ts.map +1 -1
  50. package/dist/ts4.2/src/api/SignalClient.d.ts +3 -2
  51. package/dist/ts4.2/src/connectionHelper/ConnectionCheck.d.ts +1 -1
  52. package/dist/ts4.2/src/connectionHelper/checks/Checker.d.ts +4 -4
  53. package/dist/ts4.2/src/index.d.ts +3 -2
  54. package/dist/ts4.2/src/logger.d.ts +3 -3
  55. package/dist/ts4.2/src/options.d.ts +4 -1
  56. package/dist/ts4.2/src/proto/google/protobuf/timestamp.d.ts +4 -4
  57. package/dist/ts4.2/src/proto/livekit_models.d.ts +4 -4
  58. package/dist/ts4.2/src/proto/livekit_rtc.d.ts +4 -4
  59. package/dist/ts4.2/src/room/PCTransport.d.ts +7 -1
  60. package/dist/ts4.2/src/room/RTCEngine.d.ts +10 -4
  61. package/dist/ts4.2/src/room/Room.d.ts +21 -4
  62. package/dist/ts4.2/src/room/events.d.ts +5 -0
  63. package/dist/ts4.2/src/room/participant/LocalParticipant.d.ts +3 -2
  64. package/dist/ts4.2/src/room/participant/Participant.d.ts +1 -1
  65. package/dist/ts4.2/src/room/track/LocalTrack.d.ts +1 -0
  66. package/dist/ts4.2/src/room/track/Track.d.ts +2 -1
  67. package/dist/ts4.2/src/room/track/TrackPublication.d.ts +1 -1
  68. package/dist/ts4.2/src/room/track/options.d.ts +3 -3
  69. package/dist/ts4.2/src/room/track/types.d.ts +3 -3
  70. package/dist/ts4.2/src/room/types.d.ts +13 -0
  71. package/dist/ts4.2/src/room/utils.d.ts +44 -0
  72. package/package.json +23 -23
  73. package/src/api/SignalClient.ts +40 -16
  74. package/src/connectionHelper/checks/turn.ts +1 -1
  75. package/src/connectionHelper/checks/websocket.ts +1 -1
  76. package/src/index.ts +5 -0
  77. package/src/options.ts +5 -1
  78. package/src/room/PCTransport.ts +11 -1
  79. package/src/room/RTCEngine.ts +111 -49
  80. package/src/room/Room.ts +234 -63
  81. package/src/room/events.ts +5 -0
  82. package/src/room/participant/LocalParticipant.ts +46 -22
  83. package/src/room/participant/RemoteParticipant.ts +5 -5
  84. package/src/room/participant/publishUtils.ts +1 -1
  85. package/src/room/track/LocalAudioTrack.ts +1 -1
  86. package/src/room/track/LocalTrack.ts +20 -1
  87. package/src/room/track/LocalVideoTrack.ts +1 -1
  88. package/src/room/track/RemoteVideoTrack.ts +4 -0
  89. package/src/room/track/Track.ts +22 -5
  90. package/src/room/types.ts +12 -0
  91. package/src/room/utils.ts +150 -12
@@ -113,7 +113,7 @@ export default class LocalVideoTrack extends LocalTrack {
113
113
  }
114
114
 
115
115
  async getSenderStats(): Promise<VideoSenderStats[]> {
116
- if (!this.sender) {
116
+ if (!this.sender?.getStats) {
117
117
  return [];
118
118
  }
119
119
 
@@ -118,6 +118,10 @@ export default class RemoteVideoTrack extends RemoteTrack {
118
118
  * @internal
119
119
  */
120
120
  stopObservingElementInfo(elementInfo: ElementInfo) {
121
+ if (!this.isAdaptiveStream) {
122
+ log.warn('stopObservingElementInfo ignored');
123
+ return;
124
+ }
121
125
  const stopElementInfos = this.elementInfos.filter((info) => info === elementInfo);
122
126
  for (const info of stopElementInfos) {
123
127
  info.stopObserving();
@@ -121,7 +121,9 @@ export abstract class Track extends (EventEmitter as new () => TypedEventEmitter
121
121
  // we'll want to re-attach it in that case
122
122
  attachToElement(this._mediaStreamTrack, element);
123
123
 
124
- if (element instanceof HTMLAudioElement) {
124
+ // handle auto playback failures
125
+ const allMediaStreamTracks = (element.srcObject as MediaStream).getTracks();
126
+ if (allMediaStreamTracks.some((tr) => tr.kind === 'audio')) {
125
127
  // manually play audio to detect audio playback status
126
128
  element
127
129
  .play()
@@ -130,6 +132,17 @@ export abstract class Track extends (EventEmitter as new () => TypedEventEmitter
130
132
  })
131
133
  .catch((e) => {
132
134
  this.emit(TrackEvent.AudioPlaybackFailed, e);
135
+ // If audio playback isn't allowed make sure we still play back the video
136
+ if (
137
+ element &&
138
+ allMediaStreamTracks.some((tr) => tr.kind === 'video') &&
139
+ e.name === 'NotAllowedError'
140
+ ) {
141
+ element.muted = true;
142
+ element.play().catch(() => {
143
+ // catch for Safari, exceeded options at this point to automatically play the media element
144
+ });
145
+ }
133
146
  });
134
147
  }
135
148
 
@@ -259,6 +272,13 @@ export function attachToElement(track: MediaStreamTrack, element: HTMLMediaEleme
259
272
  mediaStream.addTrack(track);
260
273
  }
261
274
 
275
+ element.autoplay = true;
276
+ // In case there are no audio tracks present on the mediastream, we set the element as muted to ensure autoplay works
277
+ element.muted = mediaStream.getAudioTracks().length === 0;
278
+ if (element instanceof HTMLVideoElement) {
279
+ element.playsInline = true;
280
+ }
281
+
262
282
  // avoid flicker
263
283
  if (element.srcObject !== mediaStream) {
264
284
  element.srcObject = mediaStream;
@@ -280,10 +300,6 @@ export function attachToElement(track: MediaStreamTrack, element: HTMLMediaEleme
280
300
  }, 0);
281
301
  }
282
302
  }
283
- element.autoplay = true;
284
- if (element instanceof HTMLVideoElement) {
285
- element.playsInline = true;
286
- }
287
303
  }
288
304
 
289
305
  /** @internal */
@@ -398,6 +414,7 @@ export type TrackEventCallbacks = {
398
414
  message: () => void;
399
415
  muted: (track?: any) => void;
400
416
  unmuted: (track?: any) => void;
417
+ restarted: (track?: any) => void;
401
418
  ended: (track?: any) => void;
402
419
  updateSettings: () => void;
403
420
  updateSubscription: () => void;
@@ -0,0 +1,12 @@
1
+ export type SimulationOptions = {
2
+ publish?: {
3
+ audio?: boolean;
4
+ video?: boolean;
5
+ };
6
+ participants?: {
7
+ count?: number;
8
+ aspectRatios?: Array<number>;
9
+ audio?: boolean;
10
+ video?: boolean;
11
+ };
12
+ };
package/src/room/utils.ts CHANGED
@@ -1,6 +1,9 @@
1
1
  import UAParser from 'ua-parser-js';
2
2
  import { ClientInfo, ClientInfo_SDK } from '../proto/livekit_models';
3
3
  import { protocolVersion, version } from '../version';
4
+ import type LocalAudioTrack from './track/LocalAudioTrack';
5
+ import type RemoteAudioTrack from './track/RemoteAudioTrack';
6
+ import { getNewAudioContext } from './track/utils';
4
7
 
5
8
  const separator = '|';
6
9
 
@@ -178,22 +181,41 @@ let emptyVideoStreamTrack: MediaStreamTrack | undefined;
178
181
 
179
182
  export function getEmptyVideoStreamTrack() {
180
183
  if (!emptyVideoStreamTrack) {
181
- const canvas = document.createElement('canvas');
182
- // the canvas size is set to 16, because electron apps seem to fail with smaller values
183
- canvas.width = 16;
184
- canvas.height = 16;
185
- canvas.getContext('2d')?.fillRect(0, 0, canvas.width, canvas.height);
186
- // @ts-ignore
187
- const emptyStream = canvas.captureStream();
188
- [emptyVideoStreamTrack] = emptyStream.getTracks();
189
- if (!emptyVideoStreamTrack) {
190
- throw Error('Could not get empty media stream video track');
191
- }
192
- emptyVideoStreamTrack.enabled = false;
184
+ emptyVideoStreamTrack = createDummyVideoStreamTrack();
193
185
  }
194
186
  return emptyVideoStreamTrack;
195
187
  }
196
188
 
189
+ export function createDummyVideoStreamTrack(
190
+ width: number = 16,
191
+ height: number = 16,
192
+ enabled: boolean = false,
193
+ paintContent: boolean = false,
194
+ ) {
195
+ const canvas = document.createElement('canvas');
196
+ // the canvas size is set to 16 by default, because electron apps seem to fail with smaller values
197
+ canvas.width = width;
198
+ canvas.height = height;
199
+ const ctx = canvas.getContext('2d');
200
+ ctx?.fillRect(0, 0, canvas.width, canvas.height);
201
+ if (paintContent && ctx) {
202
+ ctx.beginPath();
203
+ ctx.arc(width / 2, height / 2, 50, 0, Math.PI * 2, true);
204
+ ctx.closePath();
205
+ ctx.fillStyle = 'grey';
206
+ ctx.fill();
207
+ }
208
+ // @ts-ignore
209
+ const dummyStream = canvas.captureStream();
210
+ const [dummyTrack] = dummyStream.getTracks();
211
+ if (!dummyTrack) {
212
+ throw Error('Could not get empty media stream video track');
213
+ }
214
+ dummyTrack.enabled = enabled;
215
+
216
+ return dummyTrack;
217
+ }
218
+
197
219
  let emptyAudioStreamTrack: MediaStreamTrack | undefined;
198
220
 
199
221
  export function getEmptyAudioStreamTrack() {
@@ -236,3 +258,119 @@ export class Future<T> {
236
258
  }).finally(() => this.onFinally?.());
237
259
  }
238
260
  }
261
+
262
+ export type AudioAnalyserOptions = {
263
+ /**
264
+ * If set to true, the analyser will use a cloned version of the underlying mediastreamtrack, which won't be impacted by muting the track.
265
+ * Useful for local tracks when implementing things like "seems like you're muted, but trying to speak".
266
+ * Defaults to false
267
+ */
268
+ cloneTrack?: boolean;
269
+ /**
270
+ * see https://developer.mozilla.org/en-US/docs/Web/API/AnalyserNode/fftSize
271
+ */
272
+ fftSize?: number;
273
+ /**
274
+ * see https://developer.mozilla.org/en-US/docs/Web/API/AnalyserNode/smoothingTimeConstant
275
+ */
276
+ smoothingTimeConstant?: number;
277
+ /**
278
+ * see https://developer.mozilla.org/en-US/docs/Web/API/AnalyserNode/minDecibels
279
+ */
280
+ minDecibels?: number;
281
+ /**
282
+ * see https://developer.mozilla.org/en-US/docs/Web/API/AnalyserNode/maxDecibels
283
+ */
284
+ maxDecibels?: number;
285
+ };
286
+
287
+ /**
288
+ * Creates and returns an analyser web audio node that is attached to the provided track.
289
+ * Additionally returns a convenience method `calculateVolume` to perform instant volume readings on that track.
290
+ * Call the returned `cleanup` function to close the audioContext that has been created for the instance of this helper
291
+ */
292
+ export function createAudioAnalyser(
293
+ track: LocalAudioTrack | RemoteAudioTrack,
294
+ options?: AudioAnalyserOptions,
295
+ ) {
296
+ const opts = {
297
+ cloneTrack: false,
298
+ fftSize: 2048,
299
+ smoothingTimeConstant: 0.8,
300
+ minDecibels: -100,
301
+ maxDecibels: -80,
302
+ ...options,
303
+ };
304
+ const audioContext = getNewAudioContext();
305
+
306
+ if (!audioContext) {
307
+ throw new Error('Audio Context not supported on this browser');
308
+ }
309
+ const streamTrack = opts.cloneTrack ? track.mediaStreamTrack.clone() : track.mediaStreamTrack;
310
+ const mediaStreamSource = audioContext.createMediaStreamSource(new MediaStream([streamTrack]));
311
+ const analyser = audioContext.createAnalyser();
312
+ analyser.minDecibels = opts.minDecibels;
313
+ analyser.maxDecibels = opts.maxDecibels;
314
+ analyser.fftSize = opts.fftSize;
315
+ analyser.smoothingTimeConstant = opts.smoothingTimeConstant;
316
+
317
+ mediaStreamSource.connect(analyser);
318
+ const dataArray = new Uint8Array(analyser.frequencyBinCount);
319
+
320
+ /**
321
+ * Calculates the current volume of the track in the range from 0 to 1
322
+ */
323
+ const calculateVolume = () => {
324
+ analyser.getByteFrequencyData(dataArray);
325
+ let sum = 0;
326
+ for (const amplitude of dataArray) {
327
+ sum += Math.pow(amplitude / 255, 2);
328
+ }
329
+ const volume = Math.sqrt(sum / dataArray.length);
330
+ return volume;
331
+ };
332
+
333
+ const cleanup = () => {
334
+ audioContext.close();
335
+ if (opts.cloneTrack) {
336
+ streamTrack.stop();
337
+ }
338
+ };
339
+
340
+ return { calculateVolume, analyser, cleanup };
341
+ }
342
+
343
+ export class Mutex {
344
+ private _locking: Promise<void>;
345
+
346
+ private _locks: number;
347
+
348
+ constructor() {
349
+ this._locking = Promise.resolve();
350
+ this._locks = 0;
351
+ }
352
+
353
+ isLocked() {
354
+ return this._locks > 0;
355
+ }
356
+
357
+ lock() {
358
+ this._locks += 1;
359
+
360
+ let unlockNext: () => void;
361
+
362
+ const willLock = new Promise<void>(
363
+ (resolve) =>
364
+ (unlockNext = () => {
365
+ this._locks -= 1;
366
+ resolve();
367
+ }),
368
+ );
369
+
370
+ const willUnlock = this._locking.then(() => unlockNext);
371
+
372
+ this._locking = this._locking.then(() => willLock);
373
+
374
+ return willUnlock;
375
+ }
376
+ }