livekit-client 2.4.1 → 2.5.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 (57) hide show
  1. package/README.md +4 -4
  2. package/dist/livekit-client.e2ee.worker.js.map +1 -1
  3. package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
  4. package/dist/livekit-client.esm.mjs +109 -34
  5. package/dist/livekit-client.esm.mjs.map +1 -1
  6. package/dist/livekit-client.umd.js +1 -1
  7. package/dist/livekit-client.umd.js.map +1 -1
  8. package/dist/src/api/SignalClient.d.ts +3 -2
  9. package/dist/src/api/SignalClient.d.ts.map +1 -1
  10. package/dist/src/connectionHelper/checks/publishAudio.d.ts.map +1 -1
  11. package/dist/src/connectionHelper/checks/publishVideo.d.ts.map +1 -1
  12. package/dist/src/room/PCTransport.d.ts +1 -1
  13. package/dist/src/room/PCTransport.d.ts.map +1 -1
  14. package/dist/src/room/RTCEngine.d.ts +4 -3
  15. package/dist/src/room/RTCEngine.d.ts.map +1 -1
  16. package/dist/src/room/Room.d.ts.map +1 -1
  17. package/dist/src/room/errors.d.ts +4 -3
  18. package/dist/src/room/errors.d.ts.map +1 -1
  19. package/dist/src/room/events.d.ts +2 -1
  20. package/dist/src/room/events.d.ts.map +1 -1
  21. package/dist/src/room/participant/LocalParticipant.d.ts +1 -1
  22. package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
  23. package/dist/src/room/participant/RemoteParticipant.d.ts +1 -1
  24. package/dist/src/room/participant/RemoteParticipant.d.ts.map +1 -1
  25. package/dist/src/room/track/RemoteAudioTrack.d.ts +1 -1
  26. package/dist/src/room/track/RemoteAudioTrack.d.ts.map +1 -1
  27. package/dist/src/room/track/RemoteTrack.d.ts +12 -2
  28. package/dist/src/room/track/RemoteTrack.d.ts.map +1 -1
  29. package/dist/src/room/track/RemoteVideoTrack.d.ts +1 -1
  30. package/dist/src/room/track/RemoteVideoTrack.d.ts.map +1 -1
  31. package/dist/src/version.d.ts +1 -1
  32. package/dist/ts4.2/src/api/SignalClient.d.ts +3 -2
  33. package/dist/ts4.2/src/room/PCTransport.d.ts +1 -1
  34. package/dist/ts4.2/src/room/RTCEngine.d.ts +4 -3
  35. package/dist/ts4.2/src/room/errors.d.ts +4 -3
  36. package/dist/ts4.2/src/room/events.d.ts +2 -1
  37. package/dist/ts4.2/src/room/participant/LocalParticipant.d.ts +1 -1
  38. package/dist/ts4.2/src/room/participant/RemoteParticipant.d.ts +1 -1
  39. package/dist/ts4.2/src/room/track/RemoteAudioTrack.d.ts +1 -1
  40. package/dist/ts4.2/src/room/track/RemoteTrack.d.ts +12 -2
  41. package/dist/ts4.2/src/room/track/RemoteVideoTrack.d.ts +1 -1
  42. package/dist/ts4.2/src/version.d.ts +1 -1
  43. package/package.json +10 -10
  44. package/src/api/SignalClient.ts +12 -6
  45. package/src/connectionHelper/checks/publishAudio.ts +4 -1
  46. package/src/connectionHelper/checks/publishVideo.ts +6 -3
  47. package/src/room/PCTransport.ts +1 -0
  48. package/src/room/RTCEngine.ts +10 -4
  49. package/src/room/Room.ts +15 -5
  50. package/src/room/errors.ts +7 -3
  51. package/src/room/events.ts +2 -1
  52. package/src/room/participant/LocalParticipant.ts +13 -8
  53. package/src/room/participant/RemoteParticipant.ts +1 -1
  54. package/src/room/track/RemoteAudioTrack.ts +1 -1
  55. package/src/room/track/RemoteTrack.ts +38 -2
  56. package/src/room/track/RemoteVideoTrack.ts +2 -2
  57. package/src/version.ts +1 -1
@@ -4,7 +4,6 @@ import {
4
4
  ClientInfo,
5
5
  ConnectionQualityUpdate,
6
6
  DisconnectReason,
7
- ErrorResponse,
8
7
  JoinResponse,
9
8
  LeaveRequest,
10
9
  LeaveRequest_Action,
@@ -13,6 +12,7 @@ import {
13
12
  Ping,
14
13
  ReconnectReason,
15
14
  ReconnectResponse,
15
+ RequestResponse,
16
16
  Room,
17
17
  SessionDescription,
18
18
  SignalRequest,
@@ -142,7 +142,9 @@ export class SignalClient {
142
142
 
143
143
  onLeave?: (leave: LeaveRequest) => void;
144
144
 
145
- onErrorResponse?: (error: ErrorResponse) => void;
145
+ onRequestResponse?: (response: RequestResponse) => void;
146
+
147
+ onLocalTrackSubscribed?: (trackSid: string) => void;
146
148
 
147
149
  connectOptions?: ConnectOpts;
148
150
 
@@ -440,6 +442,7 @@ export class SignalClient {
440
442
  async close(updateState: boolean = true) {
441
443
  const unlock = await this.closingLock.lock();
442
444
  try {
445
+ this.clearPingInterval();
443
446
  if (updateState) {
444
447
  this.state = SignalConnectionState.DISCONNECTING;
445
448
  }
@@ -470,7 +473,6 @@ export class SignalClient {
470
473
  if (updateState) {
471
474
  this.state = SignalConnectionState.DISCONNECTED;
472
475
  }
473
- this.clearPingInterval();
474
476
  unlock();
475
477
  }
476
478
  }
@@ -739,9 +741,13 @@ export class SignalClient {
739
741
  this.rtt = Date.now() - Number.parseInt(msg.value.lastPingTimestamp.toString());
740
742
  this.resetPingTimeout();
741
743
  pingHandled = true;
742
- } else if (msg.case === 'errorResponse') {
743
- if (this.onErrorResponse) {
744
- this.onErrorResponse(msg.value);
744
+ } else if (msg.case === 'requestResponse') {
745
+ if (this.onRequestResponse) {
746
+ this.onRequestResponse(msg.value);
747
+ }
748
+ } else if (msg.case === 'trackSubscribed') {
749
+ if (this.onLocalTrackSubscribed) {
750
+ this.onLocalTrackSubscribed(msg.value.trackSid);
745
751
  }
746
752
  } else {
747
753
  this.log.debug('unsupported message', { ...this.logContext, msgCase: msg.case });
@@ -21,7 +21,10 @@ export class PublishAudioCheck extends Checker {
21
21
  }
22
22
  let numPackets = 0;
23
23
  stats.forEach((stat) => {
24
- if (stat.type === 'outbound-rtp' && stat.mediaType === 'audio') {
24
+ if (
25
+ stat.type === 'outbound-rtp' &&
26
+ (stat.kind === 'audio' || (!stat.kind && stat.mediaType === 'audio'))
27
+ ) {
25
28
  numPackets = stat.packetsSent;
26
29
  }
27
30
  });
@@ -12,7 +12,7 @@ export class PublishVideoCheck extends Checker {
12
12
  const track = await createLocalVideoTrack();
13
13
  room.localParticipant.publishTrack(track);
14
14
  // wait for a few seconds to publish
15
- await new Promise((resolve) => setTimeout(resolve, 3000));
15
+ await new Promise((resolve) => setTimeout(resolve, 5000));
16
16
 
17
17
  // verify RTC stats that it's publishing
18
18
  const stats = await track.sender?.getStats();
@@ -21,8 +21,11 @@ export class PublishVideoCheck extends Checker {
21
21
  }
22
22
  let numPackets = 0;
23
23
  stats.forEach((stat) => {
24
- if (stat.type === 'outbound-rtp' && stat.mediaType === 'video') {
25
- numPackets = stat.packetsSent;
24
+ if (
25
+ stat.type === 'outbound-rtp' &&
26
+ (stat.kind === 'video' || (!stat.kind && stat.mediaType === 'video'))
27
+ ) {
28
+ numPackets += stat.packetsSent;
26
29
  }
27
30
  });
28
31
  if (numPackets === 0) {
@@ -260,6 +260,7 @@ export default class PCTransport extends EventEmitter {
260
260
  // actually negotiate
261
261
  this.log.debug('starting to negotiate', this.logContext);
262
262
  const offer = await this.pc.createOffer(options);
263
+ this.log.debug('original offer', { sdp: offer.sdp, ...this.logContext });
263
264
 
264
265
  const sdpParsed = parse(offer.sdp ?? '');
265
266
  sdpParsed.media.forEach((media) => {
@@ -7,13 +7,13 @@ import {
7
7
  DataPacket,
8
8
  DataPacket_Kind,
9
9
  DisconnectReason,
10
- ErrorResponse,
11
10
  type JoinResponse,
12
11
  type LeaveRequest,
13
12
  LeaveRequest_Action,
14
13
  ParticipantInfo,
15
14
  ReconnectReason,
16
15
  type ReconnectResponse,
16
+ RequestResponse,
17
17
  Room as RoomModel,
18
18
  SignalTarget,
19
19
  SpeakerInfo,
@@ -194,7 +194,8 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
194
194
  this.emit(EngineEvent.SubscriptionPermissionUpdate, update);
195
195
  this.client.onSpeakersChanged = (update) => this.emit(EngineEvent.SpeakersChanged, update);
196
196
  this.client.onStreamStateUpdate = (update) => this.emit(EngineEvent.StreamStateChanged, update);
197
- this.client.onErrorResponse = (error) => this.emit(EngineEvent.SignalRequestError, error);
197
+ this.client.onRequestResponse = (response) =>
198
+ this.emit(EngineEvent.SignalRequestResponse, response);
198
199
  }
199
200
 
200
201
  /** @internal */
@@ -493,6 +494,10 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
493
494
  this.emit(EngineEvent.LocalTrackUnpublished, response);
494
495
  };
495
496
 
497
+ this.client.onLocalTrackSubscribed = (trackSid: string) => {
498
+ this.emit(EngineEvent.LocalTrackSubscribed, trackSid);
499
+ };
500
+
496
501
  this.client.onTokenRefresh = (token: string) => {
497
502
  this.token = token;
498
503
  };
@@ -1393,7 +1398,7 @@ export type EngineEventCallbacks = {
1393
1398
  mediaTrackAdded: (
1394
1399
  track: MediaStreamTrack,
1395
1400
  streams: MediaStream,
1396
- receiver?: RTCRtpReceiver,
1401
+ receiver: RTCRtpReceiver,
1397
1402
  ) => void;
1398
1403
  activeSpeakersUpdate: (speakers: Array<SpeakerInfo>) => void;
1399
1404
  dataPacketReceived: (packet: DataPacket) => void;
@@ -1412,9 +1417,10 @@ export type EngineEventCallbacks = {
1412
1417
  subscriptionPermissionUpdate: (update: SubscriptionPermissionUpdate) => void;
1413
1418
  subscribedQualityUpdate: (update: SubscribedQualityUpdate) => void;
1414
1419
  localTrackUnpublished: (unpublishedResponse: TrackUnpublishedResponse) => void;
1420
+ localTrackSubscribed: (trackSid: string) => void;
1415
1421
  remoteMute: (trackSid: string, muted: boolean) => void;
1416
1422
  offline: () => void;
1417
- signalRequestError: (error: ErrorResponse) => void;
1423
+ signalRequestResponse: (response: RequestResponse) => void;
1418
1424
  };
1419
1425
 
1420
1426
  function supportOptionalDatachannel(protocol: number | undefined): boolean {
package/src/room/Room.ts CHANGED
@@ -328,7 +328,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
328
328
  .on(EngineEvent.SubscriptionPermissionUpdate, this.handleSubscriptionPermissionUpdate)
329
329
  .on(
330
330
  EngineEvent.MediaTrackAdded,
331
- (mediaTrack: MediaStreamTrack, stream: MediaStream, receiver?: RTCRtpReceiver) => {
331
+ (mediaTrack: MediaStreamTrack, stream: MediaStream, receiver: RTCRtpReceiver) => {
332
332
  this.onTrackAdded(mediaTrack, stream, receiver);
333
333
  },
334
334
  )
@@ -520,7 +520,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
520
520
  return;
521
521
  }
522
522
  }
523
- if (nextUrl) {
523
+ if (nextUrl && !this.abortController?.signal.aborted) {
524
524
  this.log.info(
525
525
  `Initial connection failed with ConnectionError: ${e.message}. Retrying with another region: ${nextUrl}`,
526
526
  this.logContext,
@@ -1152,7 +1152,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
1152
1152
  private onTrackAdded(
1153
1153
  mediaTrack: MediaStreamTrack,
1154
1154
  stream: MediaStream,
1155
- receiver?: RTCRtpReceiver,
1155
+ receiver: RTCRtpReceiver,
1156
1156
  ) {
1157
1157
  // don't fire onSubscribed when connecting
1158
1158
  // WebRTC fires onTrack as soon as setRemoteDescription is called on the offer
@@ -2055,7 +2055,12 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
2055
2055
  sid: Math.floor(Math.random() * 10_000).toString(),
2056
2056
  type: TrackType.AUDIO,
2057
2057
  });
2058
- p.addSubscribedMediaTrack(dummyVideo, videoTrack.sid, new MediaStream([dummyVideo]));
2058
+ p.addSubscribedMediaTrack(
2059
+ dummyVideo,
2060
+ videoTrack.sid,
2061
+ new MediaStream([dummyVideo]),
2062
+ new RTCRtpReceiver(),
2063
+ );
2059
2064
  info.tracks = [...info.tracks, videoTrack];
2060
2065
  }
2061
2066
  if (participantOptions.audio) {
@@ -2065,7 +2070,12 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
2065
2070
  sid: Math.floor(Math.random() * 10_000).toString(),
2066
2071
  type: TrackType.AUDIO,
2067
2072
  });
2068
- p.addSubscribedMediaTrack(dummyTrack, audioTrack.sid, new MediaStream([dummyTrack]));
2073
+ p.addSubscribedMediaTrack(
2074
+ dummyTrack,
2075
+ audioTrack.sid,
2076
+ new MediaStream([dummyTrack]),
2077
+ new RTCRtpReceiver(),
2078
+ );
2069
2079
  info.tracks = [...info.tracks, audioTrack];
2070
2080
  }
2071
2081
 
@@ -1,4 +1,4 @@
1
- import { ErrorResponse_Reason } from '@livekit/protocol';
1
+ import { RequestResponse_Reason } from '@livekit/protocol';
2
2
 
3
3
  export class LivekitError extends Error {
4
4
  code: number;
@@ -65,10 +65,14 @@ export class PublishDataError extends LivekitError {
65
65
  }
66
66
  }
67
67
 
68
+ export type RequestErrorReason =
69
+ | Exclude<RequestResponse_Reason, RequestResponse_Reason.OK>
70
+ | 'TimeoutError';
71
+
68
72
  export class SignalRequestError extends LivekitError {
69
- reason: ErrorResponse_Reason;
73
+ reason: RequestErrorReason;
70
74
 
71
- constructor(message: string, reason: ErrorResponse_Reason = ErrorResponse_Reason.UNKNOWN) {
75
+ constructor(message: string, reason: RequestErrorReason) {
72
76
  super(15, message);
73
77
  this.reason = reason;
74
78
  }
@@ -538,8 +538,9 @@ export enum EngineEvent {
538
538
  RemoteMute = 'remoteMute',
539
539
  SubscribedQualityUpdate = 'subscribedQualityUpdate',
540
540
  LocalTrackUnpublished = 'localTrackUnpublished',
541
+ LocalTrackSubscribed = 'localTrackSubscribed',
541
542
  Offline = 'offline',
542
- SignalRequestError = 'signalRequestError',
543
+ SignalRequestResponse = 'signalRequestResponse',
543
544
  }
544
545
 
545
546
  export enum TrackEvent {
@@ -3,9 +3,10 @@ import {
3
3
  DataPacket,
4
4
  DataPacket_Kind,
5
5
  Encryption_Type,
6
- ErrorResponse,
7
6
  ParticipantInfo,
8
7
  ParticipantPermission,
8
+ RequestResponse,
9
+ RequestResponse_Reason,
9
10
  SimulcastCodec,
10
11
  SubscribedQualityUpdate,
11
12
  TrackUnpublishedResponse,
@@ -177,7 +178,7 @@ export default class LocalParticipant extends Participant {
177
178
  .on(EngineEvent.LocalTrackUnpublished, this.handleLocalTrackUnpublished)
178
179
  .on(EngineEvent.SubscribedQualityUpdate, this.handleSubscribedQualityUpdate)
179
180
  .on(EngineEvent.Disconnected, this.handleDisconnected)
180
- .on(EngineEvent.SignalRequestError, this.handleSignalRequestError);
181
+ .on(EngineEvent.SignalRequestResponse, this.handleSignalRequestResponse);
181
182
  }
182
183
 
183
184
  private handleReconnecting = () => {
@@ -200,11 +201,13 @@ export default class LocalParticipant extends Participant {
200
201
  }
201
202
  };
202
203
 
203
- private handleSignalRequestError = (error: ErrorResponse) => {
204
- const { requestId, reason, message } = error;
205
- const failedRequest = this.pendingSignalRequests.get(requestId);
206
- if (failedRequest) {
207
- failedRequest.reject(new SignalRequestError(message, reason));
204
+ private handleSignalRequestResponse = (response: RequestResponse) => {
205
+ const { requestId, reason, message } = response;
206
+ const targetRequest = this.pendingSignalRequests.get(requestId);
207
+ if (targetRequest) {
208
+ if (reason !== RequestResponse_Reason.OK) {
209
+ targetRequest.reject(new SignalRequestError(message, reason));
210
+ }
208
211
  this.pendingSignalRequests.delete(requestId);
209
212
  }
210
213
  };
@@ -278,7 +281,9 @@ export default class LocalParticipant extends Participant {
278
281
  }
279
282
  await sleep(50);
280
283
  }
281
- reject(new SignalRequestError('Request to update local metadata timed out'));
284
+ reject(
285
+ new SignalRequestError('Request to update local metadata timed out', 'TimeoutError'),
286
+ );
282
287
  } catch (e: any) {
283
288
  if (e instanceof Error) reject(e);
284
289
  }
@@ -164,7 +164,7 @@ export default class RemoteParticipant extends Participant {
164
164
  mediaTrack: MediaStreamTrack,
165
165
  sid: Track.SID,
166
166
  mediaStream: MediaStream,
167
- receiver?: RTCRtpReceiver,
167
+ receiver: RTCRtpReceiver,
168
168
  adaptiveStreamSettings?: AdaptiveStreamSettings,
169
169
  triesLeft?: number,
170
170
  ) {
@@ -25,7 +25,7 @@ export default class RemoteAudioTrack extends RemoteTrack<Track.Kind.Audio> {
25
25
  constructor(
26
26
  mediaTrack: MediaStreamTrack,
27
27
  sid: string,
28
- receiver?: RTCRtpReceiver,
28
+ receiver: RTCRtpReceiver,
29
29
  audioContext?: AudioContext,
30
30
  audioOutput?: AudioOutputOptions,
31
31
  loggerOptions?: LoggerOptions,
@@ -8,13 +8,13 @@ export default abstract class RemoteTrack<
8
8
  TrackKind extends Track.Kind = Track.Kind,
9
9
  > extends Track<TrackKind> {
10
10
  /** @internal */
11
- receiver?: RTCRtpReceiver;
11
+ receiver: RTCRtpReceiver | undefined;
12
12
 
13
13
  constructor(
14
14
  mediaTrack: MediaStreamTrack,
15
15
  sid: string,
16
16
  kind: TrackKind,
17
- receiver?: RTCRtpReceiver,
17
+ receiver: RTCRtpReceiver,
18
18
  loggerOptions?: LoggerOptions,
19
19
  ) {
20
20
  super(mediaTrack, kind, loggerOptions);
@@ -39,6 +39,9 @@ export default abstract class RemoteTrack<
39
39
  const onRemoveTrack = (event: MediaStreamTrackEvent) => {
40
40
  if (event.track === this._mediaStreamTrack) {
41
41
  stream.removeEventListener('removetrack', onRemoveTrack);
42
+ if (this.receiver && 'playoutDelayHint' in this.receiver) {
43
+ this.receiver.playoutDelayHint = undefined;
44
+ }
42
45
  this.receiver = undefined;
43
46
  this._currentBitrate = 0;
44
47
  this.emit(TrackEvent.Ended, this);
@@ -73,6 +76,39 @@ export default abstract class RemoteTrack<
73
76
  return statsReport;
74
77
  }
75
78
 
79
+ /**
80
+ * Allows to set a playout delay (in seconds) for this track.
81
+ * A higher value allows for more buffering of the track in the browser
82
+ * and will result in a delay of media being played back of `delayInSeconds`
83
+ */
84
+ setPlayoutDelay(delayInSeconds: number): void {
85
+ if (this.receiver) {
86
+ if ('playoutDelayHint' in this.receiver) {
87
+ this.receiver.playoutDelayHint = delayInSeconds;
88
+ } else {
89
+ this.log.warn('Playout delay not supported in this browser');
90
+ }
91
+ } else {
92
+ this.log.warn('Cannot set playout delay, track already ended');
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Returns the current playout delay (in seconds) of this track.
98
+ */
99
+ getPlayoutDelay(): number {
100
+ if (this.receiver) {
101
+ if ('playoutDelayHint' in this.receiver) {
102
+ return this.receiver.playoutDelayHint as number;
103
+ } else {
104
+ this.log.warn('Playout delay not supported in this browser');
105
+ }
106
+ } else {
107
+ this.log.warn('Cannot get playout delay, track already ended');
108
+ }
109
+ return 0;
110
+ }
111
+
76
112
  /* @internal */
77
113
  startMonitor() {
78
114
  if (!this.monitorInterval) {
@@ -26,7 +26,7 @@ export default class RemoteVideoTrack extends RemoteTrack<Track.Kind.Video> {
26
26
  constructor(
27
27
  mediaTrack: MediaStreamTrack,
28
28
  sid: string,
29
- receiver?: RTCRtpReceiver,
29
+ receiver: RTCRtpReceiver,
30
30
  adaptiveStreamSettings?: AdaptiveStreamSettings,
31
31
  loggerOptions?: LoggerOptions,
32
32
  ) {
@@ -226,7 +226,7 @@ export default class RemoteVideoTrack extends RemoteTrack<Track.Kind.Video> {
226
226
  );
227
227
 
228
228
  const backgroundPause =
229
- this.adaptiveStreamSettings?.pauseVideoInBackground ?? true // default to true
229
+ (this.adaptiveStreamSettings?.pauseVideoInBackground ?? true) // default to true
230
230
  ? this.isInBackground
231
231
  : false;
232
232
  const isPiPMode = this.elementInfos.some((info) => info.pictureInPicture);
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 = 13;
4
+ export const protocolVersion = 15;