livekit-client 2.4.2 → 2.5.1

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