livekit-client 2.1.1 → 2.1.3

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