livekit-client 1.15.3 → 1.15.5

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 (39) hide show
  1. package/dist/livekit-client.esm.mjs +91 -50
  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/proto/livekit_models_pb.d.ts +11 -1
  6. package/dist/src/proto/livekit_models_pb.d.ts.map +1 -1
  7. package/dist/src/room/RTCEngine.d.ts +4 -1
  8. package/dist/src/room/RTCEngine.d.ts.map +1 -1
  9. package/dist/src/room/Room.d.ts.map +1 -1
  10. package/dist/src/room/events.d.ts +4 -1
  11. package/dist/src/room/events.d.ts.map +1 -1
  12. package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
  13. package/dist/src/room/participant/Participant.d.ts +6 -0
  14. package/dist/src/room/participant/Participant.d.ts.map +1 -1
  15. package/dist/src/room/stats.d.ts +1 -0
  16. package/dist/src/room/stats.d.ts.map +1 -1
  17. package/dist/src/room/track/RemoteVideoTrack.d.ts.map +1 -1
  18. package/dist/src/room/track/Track.d.ts +0 -3
  19. package/dist/src/room/track/Track.d.ts.map +1 -1
  20. package/dist/src/version.d.ts +1 -1
  21. package/dist/ts4.2/src/proto/livekit_models_pb.d.ts +11 -1
  22. package/dist/ts4.2/src/room/RTCEngine.d.ts +4 -0
  23. package/dist/ts4.2/src/room/events.d.ts +4 -1
  24. package/dist/ts4.2/src/room/participant/Participant.d.ts +6 -0
  25. package/dist/ts4.2/src/room/stats.d.ts +1 -0
  26. package/dist/ts4.2/src/room/track/Track.d.ts +0 -3
  27. package/dist/ts4.2/src/version.d.ts +1 -1
  28. package/package.json +2 -2
  29. package/src/proto/livekit_models_pb.ts +15 -1
  30. package/src/proto/livekit_rtc_pb.ts +1 -1
  31. package/src/room/RTCEngine.ts +13 -0
  32. package/src/room/Room.ts +26 -8
  33. package/src/room/events.ts +3 -0
  34. package/src/room/participant/LocalParticipant.ts +4 -6
  35. package/src/room/participant/Participant.ts +11 -0
  36. package/src/room/stats.ts +2 -0
  37. package/src/room/track/RemoteVideoTrack.ts +8 -0
  38. package/src/room/track/Track.ts +34 -48
  39. package/src/version.ts +1 -1
package/src/room/Room.ts CHANGED
@@ -195,7 +195,10 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
195
195
  );
196
196
  }
197
197
  if (this.options.audioOutput?.deviceId) {
198
- this.switchActiveDevice('audiooutput', unwrapConstraint(this.options.audioOutput.deviceId));
198
+ this.switchActiveDevice(
199
+ 'audiooutput',
200
+ unwrapConstraint(this.options.audioOutput.deviceId),
201
+ ).catch((e) => log.warn(`Could not set audio output: ${e.message}`));
199
202
  }
200
203
 
201
204
  if (this.options.e2ee) {
@@ -865,19 +868,29 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
865
868
  };
866
869
 
867
870
  startVideo = async () => {
871
+ const elements: HTMLMediaElement[] = [];
868
872
  for (const p of this.participants.values()) {
869
873
  p.videoTracks.forEach((tr) => {
870
874
  tr.track?.attachedElements.forEach((el) => {
871
- el.play().catch((e) => {
872
- if (e.name === 'NotAllowedError') {
873
- log.warn(
874
- 'Resuming video playback failed, make sure you call `startVideo` directly in a user gesture handler',
875
- );
876
- }
877
- });
875
+ if (!elements.includes(el)) {
876
+ elements.push(el);
877
+ }
878
878
  });
879
879
  });
880
880
  }
881
+ await Promise.all(elements.map((el) => el.play()))
882
+ .then(() => {
883
+ this.handleVideoPlaybackStarted();
884
+ })
885
+ .catch((e) => {
886
+ if (e.name === 'NotAllowedError') {
887
+ this.handleVideoPlaybackFailed();
888
+ } else {
889
+ log.warn(
890
+ 'Resuming video playback failed, make sure you call `startVideo` directly in a user gesture handler',
891
+ );
892
+ }
893
+ });
881
894
  };
882
895
 
883
896
  /**
@@ -1494,6 +1507,11 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
1494
1507
  if (this.options.expWebAudioMix) {
1495
1508
  participant.setAudioContext(this.audioContext);
1496
1509
  }
1510
+ if (this.options.audioOutput?.deviceId) {
1511
+ participant
1512
+ .setAudioOutput(this.options.audioOutput)
1513
+ .catch((e) => log.warn(`Could not set audio output: ${e.message}`));
1514
+ }
1497
1515
  return participant;
1498
1516
  }
1499
1517
 
@@ -486,6 +486,9 @@ export enum EngineEvent {
486
486
  ConnectionQualityUpdate = 'connectionQualityUpdate',
487
487
  SubscriptionError = 'subscriptionError',
488
488
  SubscriptionPermissionUpdate = 'subscriptionPermissionUpdate',
489
+ RemoteMute = 'remoteMute',
490
+ SubscribedQualityUpdate = 'subscribedQualityUpdate',
491
+ LocalTrackUnpublished = 'localTrackUnpublished',
489
492
  }
490
493
 
491
494
  export enum TrackEvent {
@@ -127,7 +127,7 @@ export default class LocalParticipant extends Participant {
127
127
  */
128
128
  setupEngine(engine: RTCEngine) {
129
129
  this.engine = engine;
130
- this.engine.client.onRemoteMuteChanged = (trackSid: string, muted: boolean) => {
130
+ this.engine.on(EngineEvent.RemoteMute, (trackSid: string, muted: boolean) => {
131
131
  const pub = this.tracks.get(trackSid);
132
132
  if (!pub || !pub.track) {
133
133
  return;
@@ -137,11 +137,7 @@ export default class LocalParticipant extends Participant {
137
137
  } else {
138
138
  pub.unmute();
139
139
  }
140
- };
141
-
142
- this.engine.client.onSubscribedQualityUpdate = this.handleSubscribedQualityUpdate;
143
-
144
- this.engine.client.onLocalTrackUnpublished = this.handleLocalTrackUnpublished;
140
+ });
145
141
 
146
142
  this.engine
147
143
  .on(EngineEvent.Connected, this.handleReconnected)
@@ -149,6 +145,8 @@ export default class LocalParticipant extends Participant {
149
145
  .on(EngineEvent.SignalResumed, this.handleReconnected)
150
146
  .on(EngineEvent.Restarting, this.handleReconnecting)
151
147
  .on(EngineEvent.Resuming, this.handleReconnecting)
148
+ .on(EngineEvent.LocalTrackUnpublished, this.handleLocalTrackUnpublished)
149
+ .on(EngineEvent.SubscribedQualityUpdate, this.handleSubscribedQualityUpdate)
152
150
  .on(EngineEvent.Disconnected, this.handleDisconnected);
153
151
  }
154
152
 
@@ -21,6 +21,11 @@ export enum ConnectionQuality {
21
21
  Excellent = 'excellent',
22
22
  Good = 'good',
23
23
  Poor = 'poor',
24
+ /**
25
+ * Indicates that a participant has temporarily (or permanently) lost connection to LiveKit.
26
+ * For permanent disconnection a `ParticipantDisconnected` event will be emitted after a timeout
27
+ */
28
+ Lost = 'lost',
24
29
  Unknown = 'unknown',
25
30
  }
26
31
 
@@ -32,6 +37,8 @@ function qualityFromProto(q: ProtoQuality): ConnectionQuality {
32
37
  return ConnectionQuality.Good;
33
38
  case ProtoQuality.POOR:
34
39
  return ConnectionQuality.Poor;
40
+ case ProtoQuality.LOST:
41
+ return ConnectionQuality.Lost;
35
42
  default:
36
43
  return ConnectionQuality.Unknown;
37
44
  }
@@ -77,6 +84,10 @@ export default class Participant extends (EventEmitter as new () => TypedEmitter
77
84
  return this.tracks.size > 0 && Array.from(this.tracks.values()).every((tr) => tr.isEncrypted);
78
85
  }
79
86
 
87
+ get isAgent() {
88
+ return this.permissions?.agent ?? false;
89
+ }
90
+
80
91
  /** @internal */
81
92
  constructor(sid: string, identity: string, name?: string, metadata?: string) {
82
93
  super();
package/src/room/stats.ts CHANGED
@@ -106,6 +106,8 @@ export interface VideoReceiverStats extends ReceiverStats {
106
106
  nackCount?: number;
107
107
 
108
108
  decoderImplementation?: string;
109
+
110
+ mimeType?: string;
109
111
  }
110
112
 
111
113
  export function computeBitrate<T extends ReceiverStats | SenderStats>(
@@ -169,8 +169,11 @@ export default class RemoteVideoTrack extends RemoteTrack {
169
169
 
170
170
  const stats = await this.receiver.getStats();
171
171
  let receiverStats: VideoReceiverStats | undefined;
172
+ let codecID = '';
173
+ let codecs = new Map<string, any>();
172
174
  stats.forEach((v) => {
173
175
  if (v.type === 'inbound-rtp') {
176
+ codecID = v.codecId;
174
177
  receiverStats = {
175
178
  type: 'video',
176
179
  framesDecoded: v.framesDecoded,
@@ -188,8 +191,13 @@ export default class RemoteVideoTrack extends RemoteTrack {
188
191
  bytesReceived: v.bytesReceived,
189
192
  decoderImplementation: v.decoderImplementation,
190
193
  };
194
+ } else if (v.type === 'codec') {
195
+ codecs.set(v.id, v);
191
196
  }
192
197
  });
198
+ if (receiverStats && codecID !== '' && codecs.get(codecID)) {
199
+ receiverStats.mimeType = codecs.get(codecID).mimeType;
200
+ }
193
201
  return receiverStats;
194
202
  }
195
203
 
@@ -1,7 +1,7 @@
1
1
  import { EventEmitter } from 'events';
2
- import { debounce } from 'ts-debounce';
3
2
  import type TypedEventEmitter from 'typed-emitter';
4
3
  import type { SignalClient } from '../../api/SignalClient';
4
+ import log from '../../logger';
5
5
  import { TrackSource, TrackType } from '../../proto/livekit_models_pb';
6
6
  import { StreamState as ProtoStreamState } from '../../proto/livekit_rtc_pb';
7
7
  import { TrackEvent } from '../events';
@@ -113,9 +113,6 @@ export abstract class Track extends (EventEmitter as new () => TypedEventEmitter
113
113
 
114
114
  if (!this.attachedElements.includes(element)) {
115
115
  this.attachedElements.push(element);
116
- // listen to suspend events in order to detect auto playback issues
117
- element.addEventListener('suspend', this.handleElementSuspended);
118
- element.addEventListener('playing', this.handleElementPlay);
119
116
  }
120
117
 
121
118
  // even if we believe it's already attached to the element, it's possible
@@ -125,27 +122,38 @@ export abstract class Track extends (EventEmitter as new () => TypedEventEmitter
125
122
 
126
123
  // handle auto playback failures
127
124
  const allMediaStreamTracks = (element.srcObject as MediaStream).getTracks();
128
- if (allMediaStreamTracks.some((tr) => tr.kind === 'audio')) {
129
- // manually play audio to detect audio playback status
130
- element
131
- .play()
132
- .then(() => {
133
- this.emit(TrackEvent.AudioPlaybackStarted);
134
- })
135
- .catch((e) => {
136
- // If audio playback isn't allowed make sure we still play back the video
137
- if (
138
- element &&
139
- allMediaStreamTracks.some((tr) => tr.kind === 'video') &&
140
- e.name === 'NotAllowedError'
141
- ) {
142
- element.muted = true;
143
- element.play().catch(() => {
144
- // catch for Safari, exceeded options at this point to automatically play the media element
145
- });
146
- }
147
- });
148
- }
125
+ const hasAudio = allMediaStreamTracks.some((tr) => tr.kind === 'audio');
126
+
127
+ // manually play media to detect auto playback status
128
+ element
129
+ .play()
130
+ .then(() => {
131
+ this.emit(hasAudio ? TrackEvent.AudioPlaybackStarted : TrackEvent.VideoPlaybackStarted);
132
+ })
133
+ .catch((e) => {
134
+ if (e.name === 'NotAllowedError') {
135
+ this.emit(hasAudio ? TrackEvent.AudioPlaybackFailed : TrackEvent.VideoPlaybackFailed, e);
136
+ } else if (e.name === 'AbortError') {
137
+ // commonly triggered by another `play` request, only log for debugging purposes
138
+ log.debug(
139
+ `${hasAudio ? 'audio' : 'video'} playback aborted, likely due to new play request`,
140
+ );
141
+ } else {
142
+ log.warn(`could not playback ${hasAudio ? 'audio' : 'video'}`, e);
143
+ }
144
+ // If audio playback isn't allowed make sure we still play back the video
145
+ if (
146
+ hasAudio &&
147
+ element &&
148
+ allMediaStreamTracks.some((tr) => tr.kind === 'video') &&
149
+ e.name === 'NotAllowedError'
150
+ ) {
151
+ element.muted = true;
152
+ element.play().catch(() => {
153
+ // catch for Safari, exceeded options at this point to automatically play the media element
154
+ });
155
+ }
156
+ });
149
157
 
150
158
  this.emit(TrackEvent.ElementAttached, element);
151
159
  return element;
@@ -170,8 +178,6 @@ export abstract class Track extends (EventEmitter as new () => TypedEventEmitter
170
178
  if (idx >= 0) {
171
179
  this.attachedElements.splice(idx, 1);
172
180
  this.recycleElement(element);
173
- element.removeEventListener('suspend', this.handleElementSuspended);
174
- element.removeEventListener('playing', this.handleElementPlay);
175
181
  this.emit(TrackEvent.ElementDetached, element);
176
182
  }
177
183
  return element;
@@ -182,8 +188,6 @@ export abstract class Track extends (EventEmitter as new () => TypedEventEmitter
182
188
  detachTrack(this.mediaStreamTrack, elm);
183
189
  detached.push(elm);
184
190
  this.recycleElement(elm);
185
- elm.removeEventListener('suspend', this.handleElementSuspended);
186
- elm.removeEventListener('playing', this.handleElementPlay);
187
191
  this.emit(TrackEvent.ElementDetached, elm);
188
192
  });
189
193
 
@@ -270,24 +274,6 @@ export abstract class Track extends (EventEmitter as new () => TypedEventEmitter
270
274
  document.removeEventListener('visibilitychange', this.appVisibilityChangedListener);
271
275
  }
272
276
  }
273
-
274
- private handleElementSuspended = () => {
275
- this.debouncedPlaybackStateChange(false);
276
- };
277
-
278
- private handleElementPlay = () => {
279
- this.debouncedPlaybackStateChange(true);
280
- };
281
-
282
- private debouncedPlaybackStateChange = debounce((allowed: boolean) => {
283
- // we debounce this as Safari triggers both `playing` and `suspend` shortly after one another
284
- // in order not to raise the wrong event, we debounce the call to make sure we only emit the correct status
285
- if (this.kind === Track.Kind.Audio) {
286
- this.emit(allowed ? TrackEvent.AudioPlaybackStarted : TrackEvent.AudioPlaybackFailed);
287
- } else if (this.kind === Track.Kind.Video) {
288
- this.emit(allowed ? TrackEvent.VideoPlaybackStarted : TrackEvent.VideoPlaybackFailed);
289
- }
290
- }, 300);
291
277
  }
292
278
 
293
279
  export function attachToElement(track: MediaStreamTrack, element: HTMLMediaElement) {
@@ -340,7 +326,7 @@ export function attachToElement(track: MediaStreamTrack, element: HTMLMediaEleme
340
326
  // when the window is backgrounded before the first frame is drawn
341
327
  // manually calling play here seems to fix that
342
328
  element.play().catch(() => {
343
- /** do nothing, we watch the `suspended` event do deal with these failures */
329
+ /** do nothing */
344
330
  });
345
331
  }, 0);
346
332
  }
package/src/version.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  import { version as v } from '../package.json';
2
2
 
3
3
  export const version = v;
4
- export const protocolVersion = 10;
4
+ export const protocolVersion = 11;