livekit-client 2.4.1 → 2.5.0

Sign up to get free protection for your applications and to get access to all the features.
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;