livekit-client 2.4.2 → 2.5.1

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 (80) hide show
  1. package/README.md +8 -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 +374 -102
  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 +5 -0
  17. package/dist/src/room/Room.d.ts.map +1 -1
  18. package/dist/src/room/errors.d.ts +4 -3
  19. package/dist/src/room/errors.d.ts.map +1 -1
  20. package/dist/src/room/events.d.ts +12 -3
  21. package/dist/src/room/events.d.ts.map +1 -1
  22. package/dist/src/room/participant/LocalParticipant.d.ts +5 -2
  23. package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
  24. package/dist/src/room/participant/Participant.d.ts +1 -0
  25. package/dist/src/room/participant/Participant.d.ts.map +1 -1
  26. package/dist/src/room/participant/RemoteParticipant.d.ts +1 -1
  27. package/dist/src/room/participant/RemoteParticipant.d.ts.map +1 -1
  28. package/dist/src/room/timers.d.ts +4 -4
  29. package/dist/src/room/timers.d.ts.map +1 -1
  30. package/dist/src/room/track/RemoteAudioTrack.d.ts +1 -1
  31. package/dist/src/room/track/RemoteAudioTrack.d.ts.map +1 -1
  32. package/dist/src/room/track/RemoteTrack.d.ts +12 -2
  33. package/dist/src/room/track/RemoteTrack.d.ts.map +1 -1
  34. package/dist/src/room/track/RemoteVideoTrack.d.ts +1 -1
  35. package/dist/src/room/track/RemoteVideoTrack.d.ts.map +1 -1
  36. package/dist/src/room/track/options.d.ts +1 -1
  37. package/dist/src/room/types.d.ts +2 -0
  38. package/dist/src/room/types.d.ts.map +1 -1
  39. package/dist/src/room/utils.d.ts +1 -1
  40. package/dist/src/room/utils.d.ts.map +1 -1
  41. package/dist/src/version.d.ts +1 -1
  42. package/dist/ts4.2/src/api/SignalClient.d.ts +3 -2
  43. package/dist/ts4.2/src/room/PCTransport.d.ts +1 -1
  44. package/dist/ts4.2/src/room/RTCEngine.d.ts +4 -3
  45. package/dist/ts4.2/src/room/Room.d.ts +5 -0
  46. package/dist/ts4.2/src/room/errors.d.ts +4 -3
  47. package/dist/ts4.2/src/room/events.d.ts +12 -3
  48. package/dist/ts4.2/src/room/participant/LocalParticipant.d.ts +5 -2
  49. package/dist/ts4.2/src/room/participant/Participant.d.ts +1 -0
  50. package/dist/ts4.2/src/room/participant/RemoteParticipant.d.ts +1 -1
  51. package/dist/ts4.2/src/room/timers.d.ts +4 -4
  52. package/dist/ts4.2/src/room/track/RemoteAudioTrack.d.ts +1 -1
  53. package/dist/ts4.2/src/room/track/RemoteTrack.d.ts +12 -2
  54. package/dist/ts4.2/src/room/track/RemoteVideoTrack.d.ts +1 -1
  55. package/dist/ts4.2/src/room/track/options.d.ts +1 -1
  56. package/dist/ts4.2/src/room/types.d.ts +2 -0
  57. package/dist/ts4.2/src/room/utils.d.ts +1 -1
  58. package/dist/ts4.2/src/version.d.ts +1 -1
  59. package/package.json +10 -10
  60. package/src/api/SignalClient.ts +12 -6
  61. package/src/connectionHelper/checks/publishAudio.ts +4 -1
  62. package/src/connectionHelper/checks/publishVideo.ts +6 -3
  63. package/src/room/PCTransport.ts +4 -1
  64. package/src/room/RTCEngine.ts +11 -5
  65. package/src/room/Room.ts +42 -5
  66. package/src/room/errors.ts +7 -3
  67. package/src/room/events.ts +12 -1
  68. package/src/room/participant/LocalParticipant.ts +125 -84
  69. package/src/room/participant/Participant.ts +1 -0
  70. package/src/room/participant/RemoteParticipant.ts +1 -1
  71. package/src/room/timers.ts +15 -6
  72. package/src/room/track/LocalVideoTrack.test.ts +60 -0
  73. package/src/room/track/LocalVideoTrack.ts +1 -1
  74. package/src/room/track/RemoteAudioTrack.ts +1 -1
  75. package/src/room/track/RemoteTrack.ts +38 -2
  76. package/src/room/track/RemoteVideoTrack.ts +2 -2
  77. package/src/room/track/options.ts +1 -1
  78. package/src/room/types.ts +2 -0
  79. package/src/room/utils.ts +10 -0
  80. package/src/version.ts +1 -1
@@ -1,5 +1,5 @@
1
1
  import type { AddTrackRequest, ConnectionQualityUpdate, JoinResponse, StreamStateUpdate, SubscriptionPermissionUpdate, SubscriptionResponse } from '@livekit/protocol';
2
- import { DataPacket, DataPacket_Kind, DisconnectReason, ErrorResponse, ParticipantInfo, Room as RoomModel, SpeakerInfo, SubscribedQualityUpdate, TrackInfo, TrackUnpublishedResponse, Transcription } from '@livekit/protocol';
2
+ import { DataPacket, DataPacket_Kind, DisconnectReason, ParticipantInfo, RequestResponse, Room as RoomModel, SpeakerInfo, SubscribedQualityUpdate, TrackInfo, TrackUnpublishedResponse, Transcription } from '@livekit/protocol';
3
3
  import type TypedEventEmitter from 'typed-emitter';
4
4
  import type { SignalOptions } from '../api/SignalClient';
5
5
  import { SignalClient } from '../api/SignalClient';
@@ -136,7 +136,7 @@ export type EngineEventCallbacks = {
136
136
  signalResumed: () => void;
137
137
  signalRestarted: (joinResp: JoinResponse) => void;
138
138
  closing: () => void;
139
- mediaTrackAdded: (track: MediaStreamTrack, streams: MediaStream, receiver?: RTCRtpReceiver) => void;
139
+ mediaTrackAdded: (track: MediaStreamTrack, streams: MediaStream, receiver: RTCRtpReceiver) => void;
140
140
  activeSpeakersUpdate: (speakers: Array<SpeakerInfo>) => void;
141
141
  dataPacketReceived: (packet: DataPacket) => void;
142
142
  transcriptionReceived: (transcription: Transcription) => void;
@@ -154,9 +154,10 @@ export type EngineEventCallbacks = {
154
154
  subscriptionPermissionUpdate: (update: SubscriptionPermissionUpdate) => void;
155
155
  subscribedQualityUpdate: (update: SubscribedQualityUpdate) => void;
156
156
  localTrackUnpublished: (unpublishedResponse: TrackUnpublishedResponse) => void;
157
+ localTrackSubscribed: (trackSid: string) => void;
157
158
  remoteMute: (trackSid: string, muted: boolean) => void;
158
159
  offline: () => void;
159
- signalRequestError: (error: ErrorResponse) => void;
160
+ signalRequestResponse: (response: RequestResponse) => void;
160
161
  };
161
162
  export {};
162
163
  //# sourceMappingURL=RTCEngine.d.ts.map
@@ -67,6 +67,10 @@ declare class Room extends Room_base {
67
67
  private log;
68
68
  private bufferedEvents;
69
69
  private isResuming;
70
+ /**
71
+ * map to store first point in time when a particular transcription segment was received
72
+ */
73
+ private transcriptionReceivedTimes;
70
74
  /**
71
75
  * Creates a new Room, the primary construct for a LiveKit session.
72
76
  * @param options
@@ -264,5 +268,6 @@ export type RoomEventCallbacks = {
264
268
  encryptionError: (error: Error) => void;
265
269
  dcBufferStatusChanged: (isLow: boolean, kind: DataPacket_Kind) => void;
266
270
  activeDeviceChanged: (kind: MediaDeviceKind, deviceId: string) => void;
271
+ localTrackSubscribed: (publication: LocalTrackPublication, participant: LocalParticipant) => void;
267
272
  };
268
273
  //# sourceMappingURL=Room.d.ts.map
@@ -1,4 +1,4 @@
1
- import { ErrorResponse_Reason } from '@livekit/protocol';
1
+ import { RequestResponse_Reason } from '@livekit/protocol';
2
2
  export declare class LivekitError extends Error {
3
3
  code: number;
4
4
  constructor(code: number, message?: string);
@@ -33,9 +33,10 @@ export declare class NegotiationError extends LivekitError {
33
33
  export declare class PublishDataError extends LivekitError {
34
34
  constructor(message?: string);
35
35
  }
36
+ export type RequestErrorReason = Exclude<RequestResponse_Reason, RequestResponse_Reason.OK> | 'TimeoutError';
36
37
  export declare class SignalRequestError extends LivekitError {
37
- reason: ErrorResponse_Reason;
38
- constructor(message: string, reason?: ErrorResponse_Reason);
38
+ reason: RequestErrorReason;
39
+ constructor(message: string, reason: RequestErrorReason);
39
40
  }
40
41
  export declare enum MediaDeviceFailure {
41
42
  PermissionDenied = "PermissionDenied",
@@ -282,7 +282,11 @@ export declare enum RoomEvent {
282
282
  * Triggered by a call to room.switchActiveDevice
283
283
  * args: (kind: MediaDeviceKind, deviceId: string)
284
284
  */
285
- ActiveDeviceChanged = "activeDeviceChanged"
285
+ ActiveDeviceChanged = "activeDeviceChanged",
286
+ /**
287
+ * fired when the first remote participant has subscribed to the localParticipant's track
288
+ */
289
+ LocalTrackSubscribed = "localTrackSubscribed"
286
290
  }
287
291
  export declare enum ParticipantEvent {
288
292
  /**
@@ -442,7 +446,11 @@ export declare enum ParticipantEvent {
442
446
  * all users.
443
447
  * When a participant's attributes changed, this event will be emitted with the changed attributes
444
448
  */
445
- AttributesChanged = "attributesChanged"
449
+ AttributesChanged = "attributesChanged",
450
+ /**
451
+ * fired on local participant only, when the first remote participant has subscribed to the track specified in the payload
452
+ */
453
+ LocalTrackSubscribed = "localTrackSubscribed"
446
454
  }
447
455
  /** @internal */
448
456
  export declare enum EngineEvent {
@@ -471,8 +479,9 @@ export declare enum EngineEvent {
471
479
  RemoteMute = "remoteMute",
472
480
  SubscribedQualityUpdate = "subscribedQualityUpdate",
473
481
  LocalTrackUnpublished = "localTrackUnpublished",
482
+ LocalTrackSubscribed = "localTrackSubscribed",
474
483
  Offline = "offline",
475
- SignalRequestError = "signalRequestError"
484
+ SignalRequestResponse = "signalRequestResponse"
476
485
  }
477
486
  export declare enum TrackEvent {
478
487
  Message = "message",
@@ -1,4 +1,4 @@
1
- import { ParticipantInfo, ParticipantPermission } from '@livekit/protocol';
1
+ import { Codec, ParticipantInfo, ParticipantPermission } from '@livekit/protocol';
2
2
  import type { InternalRoomOptions } from '../../options';
3
3
  import type RTCEngine from '../RTCEngine';
4
4
  import LocalTrack from '../track/LocalTrack';
@@ -27,6 +27,7 @@ export default class LocalParticipant extends Participant {
27
27
  private encryptionType;
28
28
  private reconnectFuture?;
29
29
  private pendingSignalRequests;
30
+ private enabledPublishVideoCodecs;
30
31
  /** @internal */
31
32
  constructor(sid: string, identity: string, engine: RTCEngine, options: InternalRoomOptions);
32
33
  get lastCameraError(): Error | undefined;
@@ -41,7 +42,7 @@ export default class LocalParticipant extends Participant {
41
42
  private handleReconnecting;
42
43
  private handleReconnected;
43
44
  private handleDisconnected;
44
- private handleSignalRequestError;
45
+ private handleSignalRequestResponse;
45
46
  /**
46
47
  * Sets and updates the metadata of the local participant.
47
48
  * Note: this requires `canUpdateOwnMetadata` permission.
@@ -152,6 +153,8 @@ export default class LocalParticipant extends Participant {
152
153
  */
153
154
  setTrackSubscriptionPermissions(allParticipantsAllowed: boolean, participantTrackPermissions?: ParticipantTrackPermission[]): void;
154
155
  /** @internal */
156
+ setEnabledPublishCodecs(codecs: Codec[]): void;
157
+ /** @internal */
155
158
  updateInfo(info: ParticipantInfo): boolean;
156
159
  private updateTrackSubscriptionPermissions;
157
160
  /** @internal */
@@ -121,5 +121,6 @@ export type ParticipantEventCallbacks = {
121
121
  participantPermissionsChanged: (prevPermissions?: ParticipantPermission) => void;
122
122
  trackSubscriptionStatusChanged: (publication: RemoteTrackPublication, status: TrackPublication.SubscriptionStatus) => void;
123
123
  attributesChanged: (changedAttributes: Record<string, string>) => void;
124
+ localTrackSubscribed: (trackPublication: LocalTrackPublication) => void;
124
125
  };
125
126
  //# sourceMappingURL=Participant.d.ts.map
@@ -37,7 +37,7 @@ export default class RemoteParticipant extends Participant {
37
37
  */
38
38
  getVolume(source?: Track.Source.Microphone | Track.Source.ScreenShareAudio): number | undefined;
39
39
  /** @internal */
40
- addSubscribedMediaTrack(mediaTrack: MediaStreamTrack, sid: Track.SID, mediaStream: MediaStream, receiver?: RTCRtpReceiver, adaptiveStreamSettings?: AdaptiveStreamSettings, triesLeft?: number): RemoteTrackPublication | undefined;
40
+ addSubscribedMediaTrack(mediaTrack: MediaStreamTrack, sid: Track.SID, mediaStream: MediaStream, receiver: RTCRtpReceiver, adaptiveStreamSettings?: AdaptiveStreamSettings, triesLeft?: number): RemoteTrackPublication | undefined;
41
41
  /** @internal */
42
42
  get hasMetadata(): boolean;
43
43
  /**
@@ -4,9 +4,9 @@
4
4
  * that the timer fires on time.
5
5
  */
6
6
  export default class CriticalTimers {
7
- static setTimeout: (callback: (args: void) => void, ms?: number | undefined) => NodeJS.Timeout;
8
- static setInterval: (callback: (args: void) => void, ms?: number | undefined) => NodeJS.Timeout;
9
- static clearTimeout: (timeoutId: string | number | NodeJS.Timeout | undefined) => void;
10
- static clearInterval: (intervalId: string | number | NodeJS.Timeout | undefined) => void;
7
+ static setTimeout: (...args: Parameters<typeof setTimeout>) => ReturnType<typeof setTimeout>;
8
+ static setInterval: (...args: Parameters<typeof setInterval>) => ReturnType<typeof setInterval>;
9
+ static clearTimeout: (...args: Parameters<typeof clearTimeout>) => ReturnType<typeof clearTimeout>;
10
+ static clearInterval: (...args: Parameters<typeof clearInterval>) => ReturnType<typeof clearInterval>;
11
11
  }
12
12
  //# sourceMappingURL=timers.d.ts.map
@@ -11,7 +11,7 @@ export default class RemoteAudioTrack extends RemoteTrack<Track.Kind.Audio> {
11
11
  private sourceNode?;
12
12
  private webAudioPluginNodes;
13
13
  private sinkId?;
14
- constructor(mediaTrack: MediaStreamTrack, sid: string, receiver?: RTCRtpReceiver, audioContext?: AudioContext, audioOutput?: AudioOutputOptions, loggerOptions?: LoggerOptions);
14
+ constructor(mediaTrack: MediaStreamTrack, sid: string, receiver: RTCRtpReceiver, audioContext?: AudioContext, audioOutput?: AudioOutputOptions, loggerOptions?: LoggerOptions);
15
15
  /**
16
16
  * sets the volume for all attached audio elements
17
17
  */
@@ -2,8 +2,8 @@ import type { LoggerOptions } from '../types';
2
2
  import { Track } from './Track';
3
3
  export default abstract class RemoteTrack<TrackKind extends Track.Kind = Track.Kind> extends Track<TrackKind> {
4
4
  /** @internal */
5
- receiver?: RTCRtpReceiver;
6
- constructor(mediaTrack: MediaStreamTrack, sid: string, kind: TrackKind, receiver?: RTCRtpReceiver, loggerOptions?: LoggerOptions);
5
+ receiver: RTCRtpReceiver | undefined;
6
+ constructor(mediaTrack: MediaStreamTrack, sid: string, kind: TrackKind, receiver: RTCRtpReceiver, loggerOptions?: LoggerOptions);
7
7
  /** @internal */
8
8
  setMuted(muted: boolean): void;
9
9
  /** @internal */
@@ -17,6 +17,16 @@ export default abstract class RemoteTrack<TrackKind extends Track.Kind = Track.K
17
17
  * @returns Promise<RTCStatsReport> | undefined
18
18
  */
19
19
  getRTCStatsReport(): Promise<RTCStatsReport | undefined>;
20
+ /**
21
+ * Allows to set a playout delay (in seconds) for this track.
22
+ * A higher value allows for more buffering of the track in the browser
23
+ * and will result in a delay of media being played back of `delayInSeconds`
24
+ */
25
+ setPlayoutDelay(delayInSeconds: number): void;
26
+ /**
27
+ * Returns the current playout delay (in seconds) of this track.
28
+ */
29
+ getPlayoutDelay(): number;
20
30
  startMonitor(): void;
21
31
  protected abstract monitorReceiver(): void;
22
32
  registerTimeSyncUpdate(): void;
@@ -8,7 +8,7 @@ export default class RemoteVideoTrack extends RemoteTrack<Track.Kind.Video> {
8
8
  private adaptiveStreamSettings?;
9
9
  private lastVisible?;
10
10
  private lastDimensions?;
11
- constructor(mediaTrack: MediaStreamTrack, sid: string, receiver?: RTCRtpReceiver, adaptiveStreamSettings?: AdaptiveStreamSettings, loggerOptions?: LoggerOptions);
11
+ constructor(mediaTrack: MediaStreamTrack, sid: string, receiver: RTCRtpReceiver, adaptiveStreamSettings?: AdaptiveStreamSettings, loggerOptions?: LoggerOptions);
12
12
  get isAdaptiveStream(): boolean;
13
13
  /**
14
14
  * Note: When using adaptiveStream, you need to use remoteVideoTrack.attach() to add the track to a HTMLVideoElement, otherwise your video tracks might never start
@@ -53,7 +53,7 @@ export interface TrackPublishDefaults {
53
53
  */
54
54
  simulcast?: boolean;
55
55
  /**
56
- * scalability mode for svc codecs, defaults to 'L3T3'.
56
+ * scalability mode for svc codecs, defaults to 'L3T3_KEY'.
57
57
  * for svc codecs, simulcast is disabled.
58
58
  */
59
59
  scalabilityMode?: ScalabilityMode;
@@ -42,5 +42,7 @@ export interface TranscriptionSegment {
42
42
  startTime: number;
43
43
  endTime: number;
44
44
  final: boolean;
45
+ firstReceivedTime: number;
46
+ lastReceivedTime: number;
45
47
  }
46
48
  //# sourceMappingURL=types.d.ts.map
@@ -95,5 +95,5 @@ export declare function unwrapConstraint(constraint: ConstrainDOMString): string
95
95
  export declare function unwrapConstraint(constraint: ConstrainULong): number;
96
96
  export declare function toWebsocketUrl(url: string): string;
97
97
  export declare function toHttpUrl(url: string): string;
98
- export declare function extractTranscriptionSegments(transcription: TranscriptionModel): TranscriptionSegment[];
98
+ export declare function extractTranscriptionSegments(transcription: TranscriptionModel, firstReceivedTimesMap: Map<string, number>): TranscriptionSegment[];
99
99
  //# sourceMappingURL=utils.d.ts.map
@@ -1,3 +1,3 @@
1
1
  export declare const version: string;
2
- export declare const protocolVersion = 13;
2
+ export declare const protocolVersion = 15;
3
3
  //# sourceMappingURL=version.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "livekit-client",
3
- "version": "2.4.2",
3
+ "version": "2.5.1",
4
4
  "description": "JavaScript/TypeScript client SDK for LiveKit",
5
5
  "main": "./dist/livekit-client.umd.js",
6
6
  "unpkg": "./dist/livekit-client.umd.js",
@@ -36,7 +36,7 @@
36
36
  "author": "David Zhao <david@davidzhao.com>",
37
37
  "license": "Apache-2.0",
38
38
  "dependencies": {
39
- "@livekit/protocol": "1.19.1",
39
+ "@livekit/protocol": "1.20.1",
40
40
  "events": "^3.3.0",
41
41
  "loglevel": "^1.8.0",
42
42
  "sdp-transform": "^2.14.1",
@@ -46,8 +46,8 @@
46
46
  "webrtc-adapter": "^9.0.0"
47
47
  },
48
48
  "devDependencies": {
49
- "@babel/core": "7.24.7",
50
- "@babel/preset-env": "7.24.7",
49
+ "@babel/core": "7.25.2",
50
+ "@babel/preset-env": "7.25.3",
51
51
  "@bufbuild/protoc-gen-es": "^1.3.0",
52
52
  "@changesets/cli": "2.27.7",
53
53
  "@livekit/changesets-changelog-github": "^0.0.4",
@@ -62,8 +62,8 @@
62
62
  "@types/events": "^3.0.0",
63
63
  "@types/sdp-transform": "2.4.9",
64
64
  "@types/ua-parser-js": "0.7.39",
65
- "@typescript-eslint/eslint-plugin": "7.15.0",
66
- "@typescript-eslint/parser": "7.15.0",
65
+ "@typescript-eslint/eslint-plugin": "7.18.0",
66
+ "@typescript-eslint/parser": "7.18.0",
67
67
  "downlevel-dts": "^0.11.0",
68
68
  "eslint": "8.57.0",
69
69
  "eslint-config-airbnb-typescript": "18.0.0",
@@ -73,15 +73,15 @@
73
73
  "gh-pages": "6.1.1",
74
74
  "jsdom": "^24.0.0",
75
75
  "prettier": "^3.0.0",
76
- "rollup": "4.18.0",
76
+ "rollup": "4.19.2",
77
77
  "rollup-plugin-delete": "^2.0.0",
78
78
  "rollup-plugin-re": "1.0.7",
79
79
  "rollup-plugin-typescript2": "0.36.0",
80
80
  "size-limit": "^8.2.4",
81
- "typedoc": "0.26.3",
81
+ "typedoc": "0.26.5",
82
82
  "typedoc-plugin-no-inherit": "1.4.0",
83
- "typescript": "5.5.3",
84
- "vite": "5.3.2",
83
+ "typescript": "5.5.4",
84
+ "vite": "5.3.5",
85
85
  "vitest": "^1.0.0"
86
86
  },
87
87
  "scripts": {
@@ -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) {
@@ -23,6 +23,8 @@ eliminate this issue.
23
23
  */
24
24
  const startBitrateForSVC = 0.7;
25
25
 
26
+ const debounceInterval = 20;
27
+
26
28
  export const PCEvents = {
27
29
  NegotiationStarted: 'negotiationStarted',
28
30
  NegotiationComplete: 'negotiationComplete',
@@ -228,7 +230,7 @@ export default class PCTransport extends EventEmitter {
228
230
  throw e;
229
231
  }
230
232
  }
231
- }, 100);
233
+ }, debounceInterval);
232
234
 
233
235
  async createAndSendOffer(options?: RTCOfferOptions) {
234
236
  if (this.onOffer === undefined) {
@@ -260,6 +262,7 @@ export default class PCTransport extends EventEmitter {
260
262
  // actually negotiate
261
263
  this.log.debug('starting to negotiate', this.logContext);
262
264
  const offer = await this.pc.createOffer(options);
265
+ this.log.debug('original offer', { sdp: offer.sdp, ...this.logContext });
263
266
 
264
267
  const sdpParsed = parse(offer.sdp ?? '');
265
268
  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 */
@@ -231,7 +232,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
231
232
  }
232
233
 
233
234
  // create offer
234
- if (!this.subscriberPrimary) {
235
+ if (!this.subscriberPrimary || joinResponse.fastPublish) {
235
236
  this.negotiate();
236
237
  }
237
238
 
@@ -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
@@ -162,6 +162,11 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
162
162
 
163
163
  private isResuming: boolean = false;
164
164
 
165
+ /**
166
+ * map to store first point in time when a particular transcription segment was received
167
+ */
168
+ private transcriptionReceivedTimes: Map<string, number>;
169
+
165
170
  /**
166
171
  * Creates a new Room, the primary construct for a LiveKit session.
167
172
  * @param options
@@ -174,6 +179,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
174
179
  this.options = { ...roomOptionDefaults, ...options };
175
180
 
176
181
  this.log = getLogger(this.options.loggerName ?? LoggerNames.Room);
182
+ this.transcriptionReceivedTimes = new Map();
177
183
 
178
184
  this.options.audioCaptureDefaults = {
179
185
  ...audioDefaults,
@@ -328,7 +334,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
328
334
  .on(EngineEvent.SubscriptionPermissionUpdate, this.handleSubscriptionPermissionUpdate)
329
335
  .on(
330
336
  EngineEvent.MediaTrackAdded,
331
- (mediaTrack: MediaStreamTrack, stream: MediaStream, receiver?: RTCRtpReceiver) => {
337
+ (mediaTrack: MediaStreamTrack, stream: MediaStream, receiver: RTCRtpReceiver) => {
332
338
  this.onTrackAdded(mediaTrack, stream, receiver);
333
339
  },
334
340
  )
@@ -370,6 +376,24 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
370
376
  })
371
377
  .on(EngineEvent.DCBufferStatusChanged, (status, kind) => {
372
378
  this.emit(RoomEvent.DCBufferStatusChanged, status, kind);
379
+ })
380
+ .on(EngineEvent.LocalTrackSubscribed, (subscribedSid) => {
381
+ const trackPublication = this.localParticipant
382
+ .getTrackPublications()
383
+ .find(({ trackSid }) => trackSid === subscribedSid) as LocalTrackPublication | undefined;
384
+ if (!trackPublication) {
385
+ this.log.warn(
386
+ 'could not find local track subscription for subscribed event',
387
+ this.logContext,
388
+ );
389
+ return;
390
+ }
391
+ this.localParticipant.emit(ParticipantEvent.LocalTrackSubscribed, trackPublication);
392
+ this.emitWhenConnected(
393
+ RoomEvent.LocalTrackSubscribed,
394
+ trackPublication,
395
+ this.localParticipant,
396
+ );
373
397
  });
374
398
 
375
399
  if (this.localParticipant) {
@@ -608,6 +632,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
608
632
 
609
633
  this.localParticipant.sid = pi.sid;
610
634
  this.localParticipant.identity = pi.identity;
635
+ this.localParticipant.setEnabledPublishCodecs(joinResponse.enabledPublishCodecs);
611
636
 
612
637
  if (this.options.e2ee && this.e2eeManager) {
613
638
  try {
@@ -1152,7 +1177,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
1152
1177
  private onTrackAdded(
1153
1178
  mediaTrack: MediaStreamTrack,
1154
1179
  stream: MediaStream,
1155
- receiver?: RTCRtpReceiver,
1180
+ receiver: RTCRtpReceiver,
1156
1181
  ) {
1157
1182
  // don't fire onSubscribed when connecting
1158
1183
  // WebRTC fires onTrack as soon as setRemoteDescription is called on the offer
@@ -1274,6 +1299,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
1274
1299
  this.clearConnectionReconcile();
1275
1300
  this.isResuming = false;
1276
1301
  this.bufferedEvents = [];
1302
+ this.transcriptionReceivedTimes.clear();
1277
1303
  if (this.state === ConnectionState.Disconnected) {
1278
1304
  return;
1279
1305
  }
@@ -1535,7 +1561,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
1535
1561
  : this.getParticipantByIdentity(transcription.transcribedParticipantIdentity);
1536
1562
  const publication = participant?.trackPublications.get(transcription.trackId);
1537
1563
 
1538
- const segments = extractTranscriptionSegments(transcription);
1564
+ const segments = extractTranscriptionSegments(transcription, this.transcriptionReceivedTimes);
1539
1565
 
1540
1566
  publication?.emit(TrackEvent.TranscriptionReceived, segments);
1541
1567
  participant?.emit(ParticipantEvent.TranscriptionReceived, segments, publication);
@@ -2055,7 +2081,12 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
2055
2081
  sid: Math.floor(Math.random() * 10_000).toString(),
2056
2082
  type: TrackType.AUDIO,
2057
2083
  });
2058
- p.addSubscribedMediaTrack(dummyVideo, videoTrack.sid, new MediaStream([dummyVideo]));
2084
+ p.addSubscribedMediaTrack(
2085
+ dummyVideo,
2086
+ videoTrack.sid,
2087
+ new MediaStream([dummyVideo]),
2088
+ new RTCRtpReceiver(),
2089
+ );
2059
2090
  info.tracks = [...info.tracks, videoTrack];
2060
2091
  }
2061
2092
  if (participantOptions.audio) {
@@ -2065,7 +2096,12 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
2065
2096
  sid: Math.floor(Math.random() * 10_000).toString(),
2066
2097
  type: TrackType.AUDIO,
2067
2098
  });
2068
- p.addSubscribedMediaTrack(dummyTrack, audioTrack.sid, new MediaStream([dummyTrack]));
2099
+ p.addSubscribedMediaTrack(
2100
+ dummyTrack,
2101
+ audioTrack.sid,
2102
+ new MediaStream([dummyTrack]),
2103
+ new RTCRtpReceiver(),
2104
+ );
2069
2105
  info.tracks = [...info.tracks, audioTrack];
2070
2106
  }
2071
2107
 
@@ -2192,4 +2228,5 @@ export type RoomEventCallbacks = {
2192
2228
  encryptionError: (error: Error) => void;
2193
2229
  dcBufferStatusChanged: (isLow: boolean, kind: DataPacket_Kind) => void;
2194
2230
  activeDeviceChanged: (kind: MediaDeviceKind, deviceId: string) => void;
2231
+ localTrackSubscribed: (publication: LocalTrackPublication, participant: LocalParticipant) => void;
2195
2232
  };
@@ -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
  }