livekit-client 2.1.1 → 2.1.3

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 (54) hide show
  1. package/dist/livekit-client.esm.mjs +215 -22
  2. package/dist/livekit-client.esm.mjs.map +1 -1
  3. package/dist/livekit-client.umd.js +1 -1
  4. package/dist/livekit-client.umd.js.map +1 -1
  5. package/dist/src/api/SignalClient.d.ts.map +1 -1
  6. package/dist/src/index.d.ts +1 -1
  7. package/dist/src/index.d.ts.map +1 -1
  8. package/dist/src/room/RTCEngine.d.ts +2 -1
  9. package/dist/src/room/RTCEngine.d.ts.map +1 -1
  10. package/dist/src/room/RegionUrlProvider.d.ts +1 -0
  11. package/dist/src/room/RegionUrlProvider.d.ts.map +1 -1
  12. package/dist/src/room/Room.d.ts +5 -2
  13. package/dist/src/room/Room.d.ts.map +1 -1
  14. package/dist/src/room/events.d.ts +20 -1
  15. package/dist/src/room/events.d.ts.map +1 -1
  16. package/dist/src/room/participant/Participant.d.ts +2 -1
  17. package/dist/src/room/participant/Participant.d.ts.map +1 -1
  18. package/dist/src/room/track/RemoteTrack.d.ts +1 -0
  19. package/dist/src/room/track/RemoteTrack.d.ts.map +1 -1
  20. package/dist/src/room/track/Track.d.ts +7 -0
  21. package/dist/src/room/track/Track.d.ts.map +1 -1
  22. package/dist/src/room/track/TrackPublication.d.ts +3 -1
  23. package/dist/src/room/track/TrackPublication.d.ts.map +1 -1
  24. package/dist/src/room/types.d.ts +8 -0
  25. package/dist/src/room/types.d.ts.map +1 -1
  26. package/dist/src/room/utils.d.ts +3 -1
  27. package/dist/src/room/utils.d.ts.map +1 -1
  28. package/dist/src/version.d.ts +1 -1
  29. package/dist/ts4.2/src/index.d.ts +1 -1
  30. package/dist/ts4.2/src/room/RTCEngine.d.ts +2 -1
  31. package/dist/ts4.2/src/room/RegionUrlProvider.d.ts +1 -0
  32. package/dist/ts4.2/src/room/Room.d.ts +5 -2
  33. package/dist/ts4.2/src/room/events.d.ts +20 -1
  34. package/dist/ts4.2/src/room/participant/Participant.d.ts +2 -1
  35. package/dist/ts4.2/src/room/track/RemoteTrack.d.ts +1 -0
  36. package/dist/ts4.2/src/room/track/Track.d.ts +7 -0
  37. package/dist/ts4.2/src/room/track/TrackPublication.d.ts +3 -1
  38. package/dist/ts4.2/src/room/types.d.ts +8 -0
  39. package/dist/ts4.2/src/room/utils.d.ts +3 -1
  40. package/dist/ts4.2/src/version.d.ts +1 -1
  41. package/package.json +8 -8
  42. package/src/api/SignalClient.ts +3 -1
  43. package/src/index.ts +1 -1
  44. package/src/room/RTCEngine.ts +51 -10
  45. package/src/room/RegionUrlProvider.ts +5 -0
  46. package/src/room/Room.ts +29 -2
  47. package/src/room/events.ts +23 -0
  48. package/src/room/participant/Participant.ts +5 -1
  49. package/src/room/track/RemoteTrack.ts +16 -0
  50. package/src/room/track/Track.ts +9 -0
  51. package/src/room/track/TrackPublication.ts +3 -1
  52. package/src/room/types.ts +9 -0
  53. package/src/room/utils.ts +17 -2
  54. package/src/version.ts +1 -1
@@ -1,7 +1,7 @@
1
1
  import { Encryption_Type } from '@livekit/protocol';
2
2
  import type { SubscriptionError, TrackInfo, UpdateSubscription, UpdateTrackSettings } from '@livekit/protocol';
3
3
  import type TypedEventEmitter from 'typed-emitter';
4
- import type { LoggerOptions } from '../types';
4
+ import type { LoggerOptions, TranscriptionSegment } from '../types';
5
5
  import LocalAudioTrack from './LocalAudioTrack';
6
6
  import LocalVideoTrack from './LocalVideoTrack';
7
7
  import RemoteAudioTrack from './RemoteAudioTrack';
@@ -72,6 +72,8 @@ export type PublicationEventCallbacks = {
72
72
  unsubscribed: (track: RemoteTrack) => void;
73
73
  subscriptionStatusChanged: (status: TrackPublication.SubscriptionStatus, prevStatus: TrackPublication.SubscriptionStatus) => void;
74
74
  subscriptionFailed: (error: SubscriptionError) => void;
75
+ transcriptionReceived: (transcription: TranscriptionSegment[]) => void;
76
+ timeSyncUpdate: (timestamp: number) => void;
75
77
  };
76
78
  export {};
77
79
  //# sourceMappingURL=TrackPublication.d.ts.map
@@ -35,4 +35,12 @@ export type LoggerOptions = {
35
35
  loggerName?: string;
36
36
  loggerContextCb?: () => Record<string, unknown>;
37
37
  };
38
+ export interface TranscriptionSegment {
39
+ id: string;
40
+ text: string;
41
+ language: string;
42
+ startTime: number;
43
+ endTime: number;
44
+ final: boolean;
45
+ }
38
46
  //# sourceMappingURL=types.d.ts.map
@@ -1,7 +1,8 @@
1
- import { ClientInfo } from '@livekit/protocol';
1
+ import { ClientInfo, Transcription as TranscriptionModel } from '@livekit/protocol';
2
2
  import type LocalAudioTrack from './track/LocalAudioTrack';
3
3
  import type RemoteAudioTrack from './track/RemoteAudioTrack';
4
4
  import { VideoCodec } from './track/options';
5
+ import type { TranscriptionSegment } from './types';
5
6
  export declare const ddExtensionURI = "https://aomediacodec.github.io/av1-rtp-spec/#dependency-descriptor-rtp-header-extension";
6
7
  export declare function unpackStreamId(packed: string): string[];
7
8
  export declare function sleep(duration: number): Promise<void>;
@@ -93,4 +94,5 @@ export declare function isVideoCodec(maybeCodec: string): maybeCodec is VideoCod
93
94
  export declare function unwrapConstraint(constraint: ConstrainDOMString): string;
94
95
  export declare function toWebsocketUrl(url: string): string;
95
96
  export declare function toHttpUrl(url: string): string;
97
+ export declare function extractTranscriptionSegments(transcription: TranscriptionModel): TranscriptionSegment[];
96
98
  //# sourceMappingURL=utils.d.ts.map
@@ -1,3 +1,3 @@
1
1
  export declare const version: string;
2
- export declare const protocolVersion = 12;
2
+ export declare const protocolVersion = 13;
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.1.1",
3
+ "version": "2.1.3",
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.13.0",
39
+ "@livekit/protocol": "1.15.0",
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": "^8.1.1"
47
47
  },
48
48
  "devDependencies": {
49
- "@babel/core": "7.24.4",
50
- "@babel/preset-env": "7.24.4",
49
+ "@babel/core": "7.24.5",
50
+ "@babel/preset-env": "7.24.5",
51
51
  "@bufbuild/protoc-gen-es": "^1.3.0",
52
52
  "@changesets/cli": "2.27.1",
53
53
  "@livekit/changesets-changelog-github": "^0.0.4",
@@ -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.14.0",
76
+ "rollup": "4.17.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.25.12",
81
+ "typedoc": "0.25.13",
82
82
  "typedoc-plugin-no-inherit": "1.4.0",
83
- "typescript": "5.4.3",
84
- "vite": "5.0.13",
83
+ "typescript": "5.4.5",
84
+ "vite": "5.2.10",
85
85
  "vitest": "^1.0.0"
86
86
  },
87
87
  "scripts": {
@@ -6,6 +6,7 @@ import {
6
6
  DisconnectReason,
7
7
  JoinResponse,
8
8
  LeaveRequest,
9
+ LeaveRequest_Action,
9
10
  MuteTrackRequest,
10
11
  ParticipantInfo,
11
12
  Ping,
@@ -596,8 +597,9 @@ export class SignalClient {
596
597
  return this.sendRequest({
597
598
  case: 'leave',
598
599
  value: new LeaveRequest({
599
- canReconnect: false,
600
600
  reason: DisconnectReason.CLIENT_INITIATED,
601
+ // server doesn't process this field, keeping it here to indicate the intent of a full disconnect
602
+ action: LeaveRequest_Action.DISCONNECT,
601
603
  }),
602
604
  });
603
605
  }
package/src/index.ts CHANGED
@@ -44,7 +44,7 @@ export { facingModeFromDeviceLabel, facingModeFromLocalTrack } from './room/trac
44
44
  export * from './room/track/options';
45
45
  export * from './room/track/processor/types';
46
46
  export * from './room/track/types';
47
- export type { DataPublishOptions, SimulationScenario } from './room/types';
47
+ export type { DataPublishOptions, SimulationScenario, TranscriptionSegment } from './room/types';
48
48
  export * from './version';
49
49
  export {
50
50
  ConnectionQuality,
@@ -9,6 +9,7 @@ import {
9
9
  DisconnectReason,
10
10
  type JoinResponse,
11
11
  type LeaveRequest,
12
+ LeaveRequest_Action,
12
13
  ParticipantInfo,
13
14
  ReconnectReason,
14
15
  type ReconnectResponse,
@@ -23,6 +24,7 @@ import {
23
24
  TrackInfo,
24
25
  type TrackPublishedResponse,
25
26
  TrackUnpublishedResponse,
27
+ Transcription,
26
28
  UpdateSubscription,
27
29
  UserPacket,
28
30
  } from '@livekit/protocol';
@@ -434,7 +436,9 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
434
436
  this.emit(EngineEvent.MediaTrackAdded, ev.track, ev.streams[0], ev.receiver);
435
437
  };
436
438
 
437
- this.createDataChannels();
439
+ if (!supportOptionalDatachannel(joinResponse.serverInfo?.protocol)) {
440
+ this.createDataChannels();
441
+ }
438
442
  }
439
443
 
440
444
  private setupSignalClientCallbacks() {
@@ -503,16 +507,28 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
503
507
  this.handleDisconnect('signal', ReconnectReason.RR_SIGNAL_DISCONNECTED);
504
508
  };
505
509
 
506
- this.client.onLeave = (leave?: LeaveRequest) => {
507
- if (leave?.canReconnect) {
508
- this.fullReconnectOnNext = true;
509
- // reconnect immediately instead of waiting for next attempt
510
- this.handleDisconnect(leaveReconnect);
511
- } else {
512
- this.emit(EngineEvent.Disconnected, leave?.reason);
513
- this.close();
514
- }
510
+ this.client.onLeave = (leave: LeaveRequest) => {
515
511
  this.log.debug('client leave request', { ...this.logContext, reason: leave?.reason });
512
+ if (leave.regions && this.regionUrlProvider) {
513
+ this.log.debug('updating regions', this.logContext);
514
+ this.regionUrlProvider.setServerReportedRegions(leave.regions);
515
+ }
516
+ switch (leave.action) {
517
+ case LeaveRequest_Action.DISCONNECT:
518
+ this.emit(EngineEvent.Disconnected, leave?.reason);
519
+ this.close();
520
+ break;
521
+ case LeaveRequest_Action.RECONNECT:
522
+ this.fullReconnectOnNext = true;
523
+ // reconnect immediately instead of waiting for next attempt
524
+ this.handleDisconnect(leaveReconnect);
525
+ break;
526
+ case LeaveRequest_Action.RESUME:
527
+ // reconnect immediately instead of waiting for next attempt
528
+ this.handleDisconnect(leaveReconnect);
529
+ default:
530
+ break;
531
+ }
516
532
  };
517
533
  }
518
534
 
@@ -634,6 +650,8 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
634
650
  this.emit(EngineEvent.ActiveSpeakersUpdate, dp.value.value.speakers);
635
651
  } else if (dp.value?.case === 'user') {
636
652
  this.emit(EngineEvent.DataPacketReceived, dp.value.value, dp.kind);
653
+ } else if (dp.value?.case === 'transcription') {
654
+ this.emit(EngineEvent.TranscriptionReceived, dp.value.value);
637
655
  }
638
656
  } finally {
639
657
  unlock();
@@ -1100,11 +1118,21 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
1100
1118
  throw new ConnectionError(`${transportName} connection not set`);
1101
1119
  }
1102
1120
 
1121
+ let needNegotiation = false;
1122
+ if (!subscriber && !this.dataChannelForKind(kind, subscriber)) {
1123
+ this.createDataChannels();
1124
+ needNegotiation = true;
1125
+ }
1126
+
1103
1127
  if (
1128
+ !needNegotiation &&
1104
1129
  !subscriber &&
1105
1130
  !this.pcManager.publisher.isICEConnected &&
1106
1131
  this.pcManager.publisher.getICEConnectionState() !== 'checking'
1107
1132
  ) {
1133
+ needNegotiation = true;
1134
+ }
1135
+ if (needNegotiation) {
1108
1136
  // start negotiation
1109
1137
  this.negotiate();
1110
1138
  }
@@ -1165,6 +1193,14 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
1165
1193
  }
1166
1194
 
1167
1195
  this.pcManager.requirePublisher();
1196
+ // don't negotiate without any transceivers or data channel, it will generate sdp without ice frag then negotiate failed
1197
+ if (
1198
+ this.pcManager.publisher.getTransceivers().length == 0 &&
1199
+ !this.lossyDC &&
1200
+ !this.reliableDC
1201
+ ) {
1202
+ this.createDataChannels();
1203
+ }
1168
1204
 
1169
1205
  const abortController = new AbortController();
1170
1206
 
@@ -1357,6 +1393,7 @@ export type EngineEventCallbacks = {
1357
1393
  ) => void;
1358
1394
  activeSpeakersUpdate: (speakers: Array<SpeakerInfo>) => void;
1359
1395
  dataPacketReceived: (userPacket: UserPacket, kind: DataPacket_Kind) => void;
1396
+ transcriptionReceived: (transcription: Transcription) => void;
1360
1397
  transportsCreated: (publisher: PCTransport, subscriber: PCTransport) => void;
1361
1398
  /** @internal */
1362
1399
  trackSenderAdded: (track: Track, sender: RTCRtpSender) => void;
@@ -1374,3 +1411,7 @@ export type EngineEventCallbacks = {
1374
1411
  remoteMute: (trackSid: string, muted: boolean) => void;
1375
1412
  offline: () => void;
1376
1413
  };
1414
+
1415
+ function supportOptionalDatachannel(protocol: number | undefined): boolean {
1416
+ return protocol !== undefined && protocol > 13;
1417
+ }
@@ -75,6 +75,11 @@ export class RegionUrlProvider {
75
75
  );
76
76
  }
77
77
  }
78
+
79
+ setServerReportedRegions(regions: RegionSettings) {
80
+ this.regionSettings = regions;
81
+ this.lastUpdateAt = Date.now();
82
+ }
78
83
  }
79
84
 
80
85
  function getCloudConfigUrl(serverUrl: URL) {
package/src/room/Room.ts CHANGED
@@ -4,6 +4,7 @@ import {
4
4
  DisconnectReason,
5
5
  JoinResponse,
6
6
  LeaveRequest,
7
+ LeaveRequest_Action,
7
8
  ParticipantInfo,
8
9
  ParticipantInfo_State,
9
10
  ParticipantPermission,
@@ -18,6 +19,8 @@ import {
18
19
  TrackInfo,
19
20
  TrackSource,
20
21
  TrackType,
22
+ Transcription as TranscriptionModel,
23
+ TranscriptionSegment as TranscriptionSegmentModel,
21
24
  UserPacket,
22
25
  protoInt64,
23
26
  } from '@livekit/protocol';
@@ -61,11 +64,12 @@ import type { TrackPublication } from './track/TrackPublication';
61
64
  import type { TrackProcessor } from './track/processor/types';
62
65
  import type { AdaptiveStreamSettings } from './track/types';
63
66
  import { getNewAudioContext, sourceToKind } from './track/utils';
64
- import type { SimulationOptions, SimulationScenario } from './types';
67
+ import type { SimulationOptions, SimulationScenario, TranscriptionSegment } from './types';
65
68
  import {
66
69
  Future,
67
70
  Mutex,
68
71
  createDummyVideoStreamTrack,
72
+ extractTranscriptionSegments,
69
73
  getEmptyAudioStreamTrack,
70
74
  isBrowserSupported,
71
75
  isCloud,
@@ -330,6 +334,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
330
334
  })
331
335
  .on(EngineEvent.ActiveSpeakersUpdate, this.handleActiveSpeakersUpdate)
332
336
  .on(EngineEvent.DataPacketReceived, this.handleDataPacket)
337
+ .on(EngineEvent.TranscriptionReceived, this.handleTranscription)
333
338
  .on(EngineEvent.Resuming, () => {
334
339
  this.clearConnectionReconcile();
335
340
  this.isResuming = true;
@@ -855,7 +860,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
855
860
  onLeave(
856
861
  new LeaveRequest({
857
862
  reason: DisconnectReason.CLIENT_INITIATED,
858
- canReconnect: true,
863
+ action: LeaveRequest_Action.RECONNECT,
859
864
  }),
860
865
  );
861
866
  }
@@ -1471,6 +1476,23 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
1471
1476
  participant?.emit(ParticipantEvent.DataReceived, userPacket.payload, kind);
1472
1477
  };
1473
1478
 
1479
+ bufferedSegments: Map<string, TranscriptionSegmentModel> = new Map();
1480
+
1481
+ private handleTranscription = (transcription: TranscriptionModel) => {
1482
+ // find the participant
1483
+ const participant =
1484
+ transcription.participantIdentity === this.localParticipant.identity
1485
+ ? this.localParticipant
1486
+ : this.remoteParticipants.get(transcription.participantIdentity);
1487
+ const publication = participant?.trackPublications.get(transcription.trackId);
1488
+
1489
+ const segments = extractTranscriptionSegments(transcription);
1490
+
1491
+ publication?.emit(TrackEvent.TranscriptionReceived, segments);
1492
+ participant?.emit(ParticipantEvent.TranscriptionReceived, segments, publication);
1493
+ this.emit(RoomEvent.TranscriptionReceived, segments, participant, publication);
1494
+ };
1495
+
1474
1496
  private handleAudioPlaybackStarted = () => {
1475
1497
  if (this.canPlaybackAudio) {
1476
1498
  return;
@@ -2071,6 +2093,11 @@ export type RoomEventCallbacks = {
2071
2093
  kind?: DataPacket_Kind,
2072
2094
  topic?: string,
2073
2095
  ) => void;
2096
+ transcriptionReceived: (
2097
+ transcription: TranscriptionSegment[],
2098
+ participant?: Participant,
2099
+ publication?: TrackPublication,
2100
+ ) => void;
2074
2101
  connectionQualityChanged: (quality: ConnectionQuality, participant: Participant) => void;
2075
2102
  mediaDevicesError: (error: Error) => void;
2076
2103
  trackStreamStateChanged: (
@@ -197,6 +197,12 @@ export enum RoomEvent {
197
197
  */
198
198
  DataReceived = 'dataReceived',
199
199
 
200
+ /**
201
+ * Transcription received from a participant's track.
202
+ * @beta
203
+ */
204
+ TranscriptionReceived = 'transcriptionReceived',
205
+
200
206
  /**
201
207
  * Connection quality was changed for a Participant. It'll receive updates
202
208
  * from the local participant, as well as any [[RemoteParticipant]]s that we are
@@ -402,6 +408,12 @@ export enum ParticipantEvent {
402
408
  */
403
409
  DataReceived = 'dataReceived',
404
410
 
411
+ /**
412
+ * Transcription received from this participant as data source.
413
+ * @beta
414
+ */
415
+ TranscriptionReceived = 'transcriptionReceived',
416
+
405
417
  /**
406
418
  * Has speaking status changed for the current participant
407
419
  *
@@ -479,6 +491,7 @@ export enum EngineEvent {
479
491
  MediaTrackAdded = 'mediaTrackAdded',
480
492
  ActiveSpeakersUpdate = 'activeSpeakersUpdate',
481
493
  DataPacketReceived = 'dataPacketReceived',
494
+ TranscriptionReceived = 'transcriptionReceived',
482
495
  RTPVideoMapUpdate = 'rtpVideoMapUpdate',
483
496
  DCBufferStatusChanged = 'dcBufferStatusChanged',
484
497
  ParticipantUpdate = 'participantUpdate',
@@ -562,4 +575,14 @@ export enum TrackEvent {
562
575
  * @internal
563
576
  */
564
577
  AudioTrackFeatureUpdate = 'audioTrackFeatureUpdate',
578
+
579
+ /**
580
+ * @beta
581
+ */
582
+ TranscriptionReceived = 'transcriptionReceived',
583
+
584
+ /**
585
+ * @experimental
586
+ */
587
+ TimeSyncUpdate = 'timeSyncUpdate',
565
588
  }
@@ -16,7 +16,7 @@ import type RemoteTrack from '../track/RemoteTrack';
16
16
  import type RemoteTrackPublication from '../track/RemoteTrackPublication';
17
17
  import { Track } from '../track/Track';
18
18
  import type { TrackPublication } from '../track/TrackPublication';
19
- import type { LoggerOptions } from '../types';
19
+ import type { LoggerOptions, TranscriptionSegment } from '../types';
20
20
 
21
21
  export enum ConnectionQuality {
22
22
  Excellent = 'excellent',
@@ -329,6 +329,10 @@ export type ParticipantEventCallbacks = {
329
329
  participantMetadataChanged: (prevMetadata: string | undefined, participant?: any) => void;
330
330
  participantNameChanged: (name: string) => void;
331
331
  dataReceived: (payload: Uint8Array, kind: DataPacket_Kind) => void;
332
+ transcriptionReceived: (
333
+ transcription: TranscriptionSegment[],
334
+ publication?: TrackPublication,
335
+ ) => void;
332
336
  isSpeakingChanged: (speaking: boolean) => void;
333
337
  connectionQualityChanged: (connectionQuality: ConnectionQuality) => void;
334
338
  trackStreamStateChanged: (
@@ -77,7 +77,23 @@ export default abstract class RemoteTrack<
77
77
  if (!this.monitorInterval) {
78
78
  this.monitorInterval = setInterval(() => this.monitorReceiver(), monitorFrequency);
79
79
  }
80
+ this.registerTimeSyncUpdate();
80
81
  }
81
82
 
82
83
  protected abstract monitorReceiver(): void;
84
+
85
+ registerTimeSyncUpdate() {
86
+ const loop = () => {
87
+ this.timeSyncHandle = requestAnimationFrame(() => loop());
88
+ const sources = this.receiver?.getSynchronizationSources()[0];
89
+ if (sources) {
90
+ const { timestamp, rtpTimestamp } = sources;
91
+ if (rtpTimestamp && this.rtpTimestamp !== rtpTimestamp) {
92
+ this.emit(TrackEvent.TimeSyncUpdate, { timestamp, rtpTimestamp });
93
+ this.rtpTimestamp = rtpTimestamp;
94
+ }
95
+ }
96
+ };
97
+ loop();
98
+ }
83
99
  }
@@ -53,6 +53,9 @@ export abstract class Track<
53
53
  */
54
54
  streamState: Track.StreamState = Track.StreamState.Active;
55
55
 
56
+ /** @internal */
57
+ rtpTimestamp: number | undefined;
58
+
56
59
  protected _mediaStreamTrack: MediaStreamTrack;
57
60
 
58
61
  protected _mediaStreamID: string;
@@ -63,6 +66,8 @@ export abstract class Track<
63
66
 
64
67
  private loggerContextCb: LoggerOptions['loggerContextCb'];
65
68
 
69
+ protected timeSyncHandle: number | undefined;
70
+
66
71
  protected _currentBitrate: number = 0;
67
72
 
68
73
  protected monitorInterval?: ReturnType<typeof setInterval>;
@@ -255,6 +260,9 @@ export abstract class Track<
255
260
  if (this.monitorInterval) {
256
261
  clearInterval(this.monitorInterval);
257
262
  }
263
+ if (this.timeSyncHandle) {
264
+ cancelAnimationFrame(this.timeSyncHandle);
265
+ }
258
266
  }
259
267
 
260
268
  /** @internal */
@@ -517,4 +525,5 @@ export type TrackEventCallbacks = {
517
525
  upstreamResumed: (track: any) => void;
518
526
  trackProcessorUpdate: (processor?: TrackProcessor<Track.Kind, any>) => void;
519
527
  audioTrackFeatureUpdate: (track: any, feature: AudioTrackFeature, enabled: boolean) => void;
528
+ timeSyncUpdate: (update: { timestamp: number; rtpTimestamp: number }) => void;
520
529
  };
@@ -9,7 +9,7 @@ import { EventEmitter } from 'events';
9
9
  import type TypedEventEmitter from 'typed-emitter';
10
10
  import log, { LoggerNames, getLogger } from '../../logger';
11
11
  import { TrackEvent } from '../events';
12
- import type { LoggerOptions } from '../types';
12
+ import type { LoggerOptions, TranscriptionSegment } from '../types';
13
13
  import LocalAudioTrack from './LocalAudioTrack';
14
14
  import LocalVideoTrack from './LocalVideoTrack';
15
15
  import RemoteAudioTrack from './RemoteAudioTrack';
@@ -174,4 +174,6 @@ export type PublicationEventCallbacks = {
174
174
  prevStatus: TrackPublication.SubscriptionStatus,
175
175
  ) => void;
176
176
  subscriptionFailed: (error: SubscriptionError) => void;
177
+ transcriptionReceived: (transcription: TranscriptionSegment[]) => void;
178
+ timeSyncUpdate: (timestamp: number) => void;
177
179
  };
package/src/room/types.ts CHANGED
@@ -55,3 +55,12 @@ export type LoggerOptions = {
55
55
  loggerName?: string;
56
56
  loggerContextCb?: () => Record<string, unknown>;
57
57
  };
58
+
59
+ export interface TranscriptionSegment {
60
+ id: string;
61
+ text: string;
62
+ language: string;
63
+ startTime: number;
64
+ endTime: number;
65
+ final: boolean;
66
+ }
package/src/room/utils.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { ClientInfo, ClientInfo_SDK } from '@livekit/protocol';
1
+ import { ClientInfo, ClientInfo_SDK, Transcription as TranscriptionModel } from '@livekit/protocol';
2
2
  import { getBrowser } from '../utils/browserParser';
3
3
  import { protocolVersion, version } from '../version';
4
4
  import CriticalTimers from './timers';
@@ -6,7 +6,7 @@ import type LocalAudioTrack from './track/LocalAudioTrack';
6
6
  import type RemoteAudioTrack from './track/RemoteAudioTrack';
7
7
  import { VideoCodec, videoCodecs } from './track/options';
8
8
  import { getNewAudioContext } from './track/utils';
9
- import type { LiveKitReactNativeInfo } from './types';
9
+ import type { LiveKitReactNativeInfo, TranscriptionSegment } from './types';
10
10
 
11
11
  const separator = '|';
12
12
  export const ddExtensionURI =
@@ -527,3 +527,18 @@ export function toHttpUrl(url: string): string {
527
527
  }
528
528
  return url;
529
529
  }
530
+
531
+ export function extractTranscriptionSegments(
532
+ transcription: TranscriptionModel,
533
+ ): TranscriptionSegment[] {
534
+ return transcription.segments.map(({ id, text, language, startTime, endTime, final }) => {
535
+ return {
536
+ id,
537
+ text,
538
+ startTime: Number.parseInt(startTime.toString()),
539
+ endTime: Number.parseInt(endTime.toString()),
540
+ final,
541
+ language,
542
+ };
543
+ });
544
+ }
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 = 12;
4
+ export const protocolVersion = 13;