livekit-client 2.18.9 → 2.18.10

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 (184) hide show
  1. package/dist/livekit-client.e2ee.worker.js +1 -1
  2. package/dist/livekit-client.e2ee.worker.js.map +1 -1
  3. package/dist/livekit-client.e2ee.worker.mjs +5609 -644
  4. package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
  5. package/dist/livekit-client.esm.mjs +2870 -2420
  6. package/dist/livekit-client.esm.mjs.map +1 -1
  7. package/dist/livekit-client.pt.worker.js +2 -0
  8. package/dist/livekit-client.pt.worker.js.map +1 -0
  9. package/dist/livekit-client.pt.worker.mjs +5834 -0
  10. package/dist/livekit-client.pt.worker.mjs.map +1 -0
  11. package/dist/livekit-client.umd.js +1 -1
  12. package/dist/livekit-client.umd.js.map +1 -1
  13. package/dist/src/api/SignalClient.d.ts +2 -1
  14. package/dist/src/api/SignalClient.d.ts.map +1 -1
  15. package/dist/src/e2ee/E2eeManager.d.ts +8 -7
  16. package/dist/src/e2ee/E2eeManager.d.ts.map +1 -1
  17. package/dist/src/e2ee/types.d.ts +35 -8
  18. package/dist/src/e2ee/types.d.ts.map +1 -1
  19. package/dist/src/e2ee/utils.d.ts +5 -5
  20. package/dist/src/e2ee/utils.d.ts.map +1 -1
  21. package/dist/src/e2ee/worker/DataCryptor.d.ts +5 -5
  22. package/dist/src/e2ee/worker/DataCryptor.d.ts.map +1 -1
  23. package/dist/src/e2ee/worker/FrameCryptor.d.ts +21 -4
  24. package/dist/src/e2ee/worker/FrameCryptor.d.ts.map +1 -1
  25. package/dist/src/e2ee/worker/naluUtils.d.ts +1 -1
  26. package/dist/src/e2ee/worker/naluUtils.d.ts.map +1 -1
  27. package/dist/src/e2ee/worker/sifPayload.d.ts +7 -7
  28. package/dist/src/e2ee/worker/sifPayload.d.ts.map +1 -1
  29. package/dist/src/index.d.ts +4 -1
  30. package/dist/src/index.d.ts.map +1 -1
  31. package/dist/src/options.d.ts +7 -0
  32. package/dist/src/options.d.ts.map +1 -1
  33. package/dist/src/packetTrailer/PacketTrailerManager.d.ts +49 -0
  34. package/dist/src/packetTrailer/PacketTrailerManager.d.ts.map +1 -0
  35. package/dist/src/packetTrailer/packetTrailer.d.ts +32 -0
  36. package/dist/src/packetTrailer/packetTrailer.d.ts.map +1 -0
  37. package/dist/src/packetTrailer/types.d.ts +57 -0
  38. package/dist/src/packetTrailer/types.d.ts.map +1 -0
  39. package/dist/src/packetTrailer/utils.d.ts +9 -0
  40. package/dist/src/packetTrailer/utils.d.ts.map +1 -0
  41. package/dist/src/packetTrailer/worker/packetTrailer.worker.d.ts +2 -0
  42. package/dist/src/packetTrailer/worker/packetTrailer.worker.d.ts.map +1 -0
  43. package/dist/src/room/RTCEngine.d.ts +2 -1
  44. package/dist/src/room/RTCEngine.d.ts.map +1 -1
  45. package/dist/src/room/Room.d.ts +3 -1
  46. package/dist/src/room/Room.d.ts.map +1 -1
  47. package/dist/src/room/data-track/RemoteDataTrack.d.ts +5 -1
  48. package/dist/src/room/data-track/RemoteDataTrack.d.ts.map +1 -1
  49. package/dist/src/room/data-track/depacketizer.d.ts +12 -4
  50. package/dist/src/room/data-track/depacketizer.d.ts.map +1 -1
  51. package/dist/src/room/data-track/frame.d.ts +3 -3
  52. package/dist/src/room/data-track/frame.d.ts.map +1 -1
  53. package/dist/src/room/data-track/incoming/IncomingDataTrackManager.d.ts +3 -1
  54. package/dist/src/room/data-track/incoming/IncomingDataTrackManager.d.ts.map +1 -1
  55. package/dist/src/room/data-track/incoming/pipeline.d.ts +4 -1
  56. package/dist/src/room/data-track/incoming/pipeline.d.ts.map +1 -1
  57. package/dist/src/room/data-track/outgoing/types.d.ts +2 -2
  58. package/dist/src/room/data-track/outgoing/types.d.ts.map +1 -1
  59. package/dist/src/room/data-track/packet/extensions.d.ts +4 -4
  60. package/dist/src/room/data-track/packet/extensions.d.ts.map +1 -1
  61. package/dist/src/room/data-track/packet/index.d.ts +5 -5
  62. package/dist/src/room/data-track/packet/index.d.ts.map +1 -1
  63. package/dist/src/room/data-track/packet/serializable.d.ts +1 -1
  64. package/dist/src/room/data-track/packet/serializable.d.ts.map +1 -1
  65. package/dist/src/room/data-track/types.d.ts +7 -0
  66. package/dist/src/room/data-track/types.d.ts.map +1 -1
  67. package/dist/src/room/events.d.ts +2 -2
  68. package/dist/src/room/participant/LocalParticipant.d.ts +3 -1
  69. package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
  70. package/dist/src/room/participant/Participant.d.ts +1 -1
  71. package/dist/src/room/participant/Participant.d.ts.map +1 -1
  72. package/dist/src/room/track/PacketTrailerExtractor.d.ts +19 -0
  73. package/dist/src/room/track/PacketTrailerExtractor.d.ts.map +1 -0
  74. package/dist/src/room/track/RemoteVideoTrack.d.ts +16 -0
  75. package/dist/src/room/track/RemoteVideoTrack.d.ts.map +1 -1
  76. package/dist/src/room/track/Track.d.ts +1 -1
  77. package/dist/src/room/track/Track.d.ts.map +1 -1
  78. package/dist/src/room/track/create.d.ts.map +1 -1
  79. package/dist/src/room/track/options.d.ts +10 -0
  80. package/dist/src/room/track/options.d.ts.map +1 -1
  81. package/dist/src/room/track/utils.d.ts.map +1 -1
  82. package/dist/src/room/utils.d.ts +4 -3
  83. package/dist/src/room/utils.d.ts.map +1 -1
  84. package/dist/src/test/MockMediaStreamTrack.d.ts.map +1 -1
  85. package/dist/src/utils/dataPacketBuffer.d.ts +1 -1
  86. package/dist/src/utils/dataPacketBuffer.d.ts.map +1 -1
  87. package/dist/src/version.d.ts +1 -1
  88. package/dist/ts4.2/api/SignalClient.d.ts +2 -1
  89. package/dist/ts4.2/e2ee/E2eeManager.d.ts +8 -7
  90. package/dist/ts4.2/e2ee/types.d.ts +35 -8
  91. package/dist/ts4.2/e2ee/utils.d.ts +5 -5
  92. package/dist/ts4.2/e2ee/worker/DataCryptor.d.ts +5 -5
  93. package/dist/ts4.2/e2ee/worker/FrameCryptor.d.ts +21 -4
  94. package/dist/ts4.2/e2ee/worker/naluUtils.d.ts +1 -1
  95. package/dist/ts4.2/e2ee/worker/sifPayload.d.ts +7 -7
  96. package/dist/ts4.2/index.d.ts +5 -1
  97. package/dist/ts4.2/options.d.ts +7 -0
  98. package/dist/ts4.2/packetTrailer/PacketTrailerManager.d.ts +49 -0
  99. package/dist/ts4.2/packetTrailer/packetTrailer.d.ts +32 -0
  100. package/dist/ts4.2/packetTrailer/types.d.ts +57 -0
  101. package/dist/ts4.2/packetTrailer/utils.d.ts +9 -0
  102. package/dist/ts4.2/packetTrailer/worker/packetTrailer.worker.d.ts +2 -0
  103. package/dist/ts4.2/room/RTCEngine.d.ts +2 -1
  104. package/dist/ts4.2/room/Room.d.ts +3 -1
  105. package/dist/ts4.2/room/data-track/RemoteDataTrack.d.ts +5 -1
  106. package/dist/ts4.2/room/data-track/depacketizer.d.ts +12 -4
  107. package/dist/ts4.2/room/data-track/frame.d.ts +3 -3
  108. package/dist/ts4.2/room/data-track/incoming/IncomingDataTrackManager.d.ts +3 -1
  109. package/dist/ts4.2/room/data-track/incoming/pipeline.d.ts +4 -1
  110. package/dist/ts4.2/room/data-track/outgoing/types.d.ts +2 -2
  111. package/dist/ts4.2/room/data-track/packet/extensions.d.ts +4 -4
  112. package/dist/ts4.2/room/data-track/packet/index.d.ts +5 -5
  113. package/dist/ts4.2/room/data-track/packet/serializable.d.ts +1 -1
  114. package/dist/ts4.2/room/data-track/types.d.ts +7 -0
  115. package/dist/ts4.2/room/events.d.ts +2 -2
  116. package/dist/ts4.2/room/participant/LocalParticipant.d.ts +3 -1
  117. package/dist/ts4.2/room/participant/Participant.d.ts +1 -1
  118. package/dist/ts4.2/room/track/PacketTrailerExtractor.d.ts +19 -0
  119. package/dist/ts4.2/room/track/RemoteVideoTrack.d.ts +16 -0
  120. package/dist/ts4.2/room/track/Track.d.ts +1 -1
  121. package/dist/ts4.2/room/track/options.d.ts +10 -0
  122. package/dist/ts4.2/room/utils.d.ts +4 -3
  123. package/dist/ts4.2/utils/dataPacketBuffer.d.ts +1 -1
  124. package/dist/ts4.2/version.d.ts +1 -1
  125. package/package.json +24 -16
  126. package/src/api/SignalClient.test.ts +102 -10
  127. package/src/api/SignalClient.ts +4 -2
  128. package/src/api/WebSocketStream.test.ts +0 -1
  129. package/src/e2ee/E2eeManager.ts +82 -30
  130. package/src/e2ee/types.ts +37 -8
  131. package/src/e2ee/utils.ts +7 -6
  132. package/src/e2ee/worker/DataCryptor.ts +6 -6
  133. package/src/e2ee/worker/FrameCryptor.test.ts +177 -4
  134. package/src/e2ee/worker/FrameCryptor.ts +94 -14
  135. package/src/e2ee/worker/ParticipantKeyHandler.test.ts +4 -4
  136. package/src/e2ee/worker/e2ee.worker.ts +13 -5
  137. package/src/e2ee/worker/naluUtils.ts +4 -4
  138. package/src/e2ee/worker/sifPayload.ts +10 -8
  139. package/src/index.ts +7 -0
  140. package/src/options.ts +8 -0
  141. package/src/packetTrailer/PacketTrailerManager.test.ts +172 -0
  142. package/src/packetTrailer/PacketTrailerManager.ts +250 -0
  143. package/src/packetTrailer/packetTrailer.test.ts +174 -0
  144. package/src/packetTrailer/packetTrailer.ts +276 -0
  145. package/src/packetTrailer/types.ts +75 -0
  146. package/src/packetTrailer/utils.test.ts +105 -0
  147. package/src/packetTrailer/utils.ts +50 -0
  148. package/src/packetTrailer/worker/packetTrailer.worker.ts +155 -0
  149. package/src/packetTrailer/worker/tsconfig.json +14 -0
  150. package/src/room/BackOffStrategy.test.ts +1 -1
  151. package/src/room/RTCEngine.test.ts +219 -0
  152. package/src/room/RTCEngine.ts +86 -20
  153. package/src/room/Room.test.ts +62 -1
  154. package/src/room/Room.ts +28 -5
  155. package/src/room/data-track/RemoteDataTrack.ts +8 -1
  156. package/src/room/data-track/depacketizer.test.ts +433 -1
  157. package/src/room/data-track/depacketizer.ts +79 -61
  158. package/src/room/data-track/frame.ts +2 -2
  159. package/src/room/data-track/incoming/IncomingDataTrackManager.test.ts +194 -0
  160. package/src/room/data-track/incoming/IncomingDataTrackManager.ts +21 -1
  161. package/src/room/data-track/incoming/pipeline.ts +13 -2
  162. package/src/room/data-track/outgoing/types.ts +3 -2
  163. package/src/room/data-track/packet/extensions.ts +2 -2
  164. package/src/room/data-track/packet/index.ts +6 -6
  165. package/src/room/data-track/packet/serializable.ts +1 -1
  166. package/src/room/data-track/types.ts +8 -0
  167. package/src/room/events.ts +2 -2
  168. package/src/room/participant/LocalParticipant.test.ts +81 -0
  169. package/src/room/participant/LocalParticipant.ts +48 -7
  170. package/src/room/participant/Participant.ts +1 -1
  171. package/src/room/participant/publishUtils.ts +1 -1
  172. package/src/room/track/PacketTrailerExtractor.ts +43 -0
  173. package/src/room/track/RemoteVideoTrack.ts +23 -2
  174. package/src/room/track/Track.ts +1 -1
  175. package/src/room/track/create.ts +0 -4
  176. package/src/room/track/options.ts +11 -0
  177. package/src/room/track/record.ts +1 -1
  178. package/src/room/track/utils.ts +4 -1
  179. package/src/room/utils.test.ts +14 -1
  180. package/src/room/utils.ts +17 -3
  181. package/src/test/MockMediaStreamTrack.ts +0 -1
  182. package/src/type-polyfills/non-shared-typed-arrays.d.ts +6 -0
  183. package/src/utils/dataPacketBuffer.ts +1 -1
  184. package/src/version.ts +1 -1
@@ -0,0 +1,81 @@
1
+ import { PacketTrailerFeature } from '@livekit/protocol';
2
+ import { describe, expect, it, vi } from 'vitest';
3
+ import type LocalTrack from '../track/LocalTrack';
4
+ import { Track } from '../track/Track';
5
+ import type { TrackPublishOptions } from '../track/options';
6
+ import LocalParticipant from './LocalParticipant';
7
+
8
+ type PacketTrailerTestParticipant = {
9
+ canPublishPacketTrailer: () => boolean;
10
+ log: { warn: ReturnType<typeof vi.fn> };
11
+ normalizeRequestedPacketTrailerOptions: (
12
+ track: LocalTrack,
13
+ opts: TrackPublishOptions,
14
+ ) => PacketTrailerFeature[];
15
+ };
16
+
17
+ function makeParticipant(canPublishPacketTrailer: boolean) {
18
+ const participant = Object.create(LocalParticipant.prototype) as PacketTrailerTestParticipant;
19
+ participant.canPublishPacketTrailer = () => canPublishPacketTrailer;
20
+ participant.log = { warn: vi.fn() };
21
+ return participant;
22
+ }
23
+
24
+ function makeTrack(kind: Track.Kind) {
25
+ return {
26
+ kind,
27
+ sid: 'track-sid',
28
+ source: kind === Track.Kind.Video ? Track.Source.Camera : Track.Source.Microphone,
29
+ isMuted: false,
30
+ mediaStreamID: 'stream-id',
31
+ mediaStreamTrack: {
32
+ enabled: true,
33
+ id: 'media-track-id',
34
+ },
35
+ } as unknown as LocalTrack;
36
+ }
37
+
38
+ describe('LocalParticipant packet trailer publish options', () => {
39
+ it('normalizes requested video packet trailer options to advertised features', () => {
40
+ const participant = makeParticipant(true);
41
+ const opts: TrackPublishOptions = { packetTrailer: { timestamp: true, frameId: true } };
42
+
43
+ const features = participant.normalizeRequestedPacketTrailerOptions(
44
+ makeTrack(Track.Kind.Video),
45
+ opts,
46
+ );
47
+
48
+ expect(features).toEqual([
49
+ PacketTrailerFeature.PTF_USER_TIMESTAMP,
50
+ PacketTrailerFeature.PTF_FRAME_ID,
51
+ ]);
52
+ expect(opts.packetTrailer).toEqual({ timestamp: true, frameId: true });
53
+ });
54
+
55
+ it('clears packet trailer options for non-video tracks', () => {
56
+ const participant = makeParticipant(true);
57
+ const opts: TrackPublishOptions = { packetTrailer: { timestamp: true } };
58
+
59
+ const features = participant.normalizeRequestedPacketTrailerOptions(
60
+ makeTrack(Track.Kind.Audio),
61
+ opts,
62
+ );
63
+
64
+ expect(features).toEqual([]);
65
+ expect(opts.packetTrailer).toBeUndefined();
66
+ });
67
+
68
+ it('clears packet trailer options when publishing packet trailers is unsupported', () => {
69
+ const participant = makeParticipant(false);
70
+ const opts: TrackPublishOptions = { packetTrailer: { frameId: true } };
71
+
72
+ const features = participant.normalizeRequestedPacketTrailerOptions(
73
+ makeTrack(Track.Kind.Video),
74
+ opts,
75
+ );
76
+
77
+ expect(features).toEqual([]);
78
+ expect(opts.packetTrailer).toBeUndefined();
79
+ expect(participant.log.warn).toHaveBeenCalledOnce();
80
+ });
81
+ });
@@ -9,6 +9,7 @@ import {
9
9
  DataPacket_Kind,
10
10
  Encryption_Type,
11
11
  JoinResponse,
12
+ PacketTrailerFeature,
12
13
  ParticipantInfo,
13
14
  RequestResponse,
14
15
  RequestResponse_Reason,
@@ -25,6 +26,12 @@ import {
25
26
  } from '@livekit/protocol';
26
27
  import { SignalConnectionState } from '../../api/SignalClient';
27
28
  import type { InternalRoomOptions } from '../../options';
29
+ import {
30
+ getPacketTrailerFeatures,
31
+ getPacketTrailerPublishOptions,
32
+ hasPacketTrailerPublishOptions,
33
+ isPacketTrailerSupported,
34
+ } from '../../packetTrailer/utils';
28
35
  import TypedPromise from '../../utils/TypedPromise';
29
36
  import { PCTransportState } from '../PCTransportManager';
30
37
  import type RTCEngine from '../RTCEngine';
@@ -343,7 +350,7 @@ export default class LocalParticipant extends Participant {
343
350
  break;
344
351
  default:
345
352
  error = DataTrackPublishError.unknown(response.reason, response.message);
346
- return;
353
+ break;
347
354
  }
348
355
 
349
356
  this.roomOutgoingDataTrackManager.receivedSfuPublishResponse(
@@ -1109,6 +1116,8 @@ export default class LocalParticipant extends Participant {
1109
1116
  if (isLocalAudioTrack(track) && track.hasPreConnectBuffer) {
1110
1117
  audioFeatures.push(AudioTrackFeature.TF_PRECONNECT_BUFFER);
1111
1118
  }
1119
+ const packetTrailerFeatures: PacketTrailerFeature[] =
1120
+ this.normalizeRequestedPacketTrailerOptions(track, opts);
1112
1121
 
1113
1122
  // create track publication from track
1114
1123
  const req = new AddTrackRequest({
@@ -1125,15 +1134,13 @@ export default class LocalParticipant extends Participant {
1125
1134
  stream: opts?.stream,
1126
1135
  backupCodecPolicy: opts?.backupCodecPolicy as BackupCodecPolicy,
1127
1136
  audioFeatures,
1137
+ packetTrailerFeatures,
1128
1138
  });
1129
1139
 
1130
1140
  // compute encodings and layers for video
1131
1141
  let encodings: RTCRtpEncodingParameters[] | undefined;
1132
1142
  if (track.kind === Track.Kind.Video) {
1133
- let dims: Track.Dimensions = {
1134
- width: 0,
1135
- height: 0,
1136
- };
1143
+ let dims: Track.Dimensions;
1137
1144
  try {
1138
1145
  dims = await track.waitForDimensions();
1139
1146
  } catch (e) {
@@ -1240,6 +1247,9 @@ export default class LocalParticipant extends Participant {
1240
1247
  }
1241
1248
 
1242
1249
  track.sender = await this.engine.createSender(track, opts, encodings);
1250
+ if (isLocalVideoTrack(track)) {
1251
+ track.publishOptions = opts;
1252
+ }
1243
1253
  this.emit(ParticipantEvent.LocalSenderCreated, track.sender, track);
1244
1254
 
1245
1255
  if (isLocalVideoTrack(track)) {
@@ -1307,7 +1317,7 @@ export default class LocalParticipant extends Participant {
1307
1317
  reject(err);
1308
1318
  }
1309
1319
  });
1310
- if (this.enabledPublishVideoCodecs.length > 0) {
1320
+ if (this.enabledPublishVideoCodecs.length > 0 && packetTrailerFeatures.length === 0) {
1311
1321
  const rets = await Promise.all([addTrackPromise, negotiate()]);
1312
1322
  ti = rets[0];
1313
1323
  } else {
@@ -1438,6 +1448,34 @@ export default class LocalParticipant extends Participant {
1438
1448
  return publication;
1439
1449
  }
1440
1450
 
1451
+ private canPublishPacketTrailer() {
1452
+ return !!(
1453
+ this.roomOptions.e2ee ||
1454
+ this.roomOptions.encryption ||
1455
+ isPacketTrailerSupported(this.roomOptions.packetTrailer)
1456
+ );
1457
+ }
1458
+
1459
+ private normalizeRequestedPacketTrailerOptions(track: LocalTrack, opts: TrackPublishOptions) {
1460
+ if (track.kind !== Track.Kind.Video || !hasPacketTrailerPublishOptions(opts.packetTrailer)) {
1461
+ opts.packetTrailer = undefined;
1462
+ return [];
1463
+ }
1464
+
1465
+ if (!this.canPublishPacketTrailer()) {
1466
+ this.log.warn('packet trailer transform not supported; not advertising features', {
1467
+ ...this.logContext,
1468
+ ...getLogContextFromTrack(track),
1469
+ });
1470
+ opts.packetTrailer = undefined;
1471
+ return [];
1472
+ }
1473
+
1474
+ const features = getPacketTrailerFeatures(opts.packetTrailer);
1475
+ opts.packetTrailer = getPacketTrailerPublishOptions(features);
1476
+ return features;
1477
+ }
1478
+
1441
1479
  override get isLocal(): boolean {
1442
1480
  return true;
1443
1481
  }
@@ -1490,12 +1528,15 @@ export default class LocalParticipant extends Participant {
1490
1528
  if (!simulcastTrack) {
1491
1529
  return;
1492
1530
  }
1531
+ const packetTrailerFeatures = this.normalizeRequestedPacketTrailerOptions(track, opts);
1532
+
1493
1533
  const req = new AddTrackRequest({
1494
1534
  cid: simulcastTrack.mediaStreamTrack.id,
1495
1535
  type: Track.kindToProto(track.kind),
1496
1536
  muted: track.isMuted,
1497
1537
  source: Track.sourceToProto(track.source),
1498
1538
  sid: track.sid,
1539
+ packetTrailerFeatures,
1499
1540
  simulcastCodecs: [
1500
1541
  {
1501
1542
  codec: opts.videoCodec,
@@ -1703,7 +1744,7 @@ export default class LocalParticipant extends Participant {
1703
1744
  * @param data Uint8Array of the payload. To send string data, use TextEncoder.encode
1704
1745
  * @param options optionally specify a `reliable`, `topic` and `destination`
1705
1746
  */
1706
- async publishData(data: Uint8Array, options: DataPublishOptions = {}): Promise<void> {
1747
+ async publishData(data: NonSharedUint8Array, options: DataPublishOptions = {}): Promise<void> {
1707
1748
  const kind = options.reliable ? DataChannelKind.RELIABLE : DataChannelKind.LOSSY;
1708
1749
  const dataPacketKind = options.reliable ? DataPacket_Kind.RELIABLE : DataPacket_Kind.LOSSY;
1709
1750
  const destinationIdentities = options.destinationIdentities;
@@ -413,7 +413,7 @@ export type ParticipantEventCallbacks = {
413
413
  participantMetadataChanged: (prevMetadata: string | undefined, participant?: any) => void;
414
414
  participantNameChanged: (name: string) => void;
415
415
  dataReceived: (
416
- payload: Uint8Array,
416
+ payload: NonSharedUint8Array,
417
417
  kind: DataPacket_Kind,
418
418
  encryptionType?: Encryption_Type,
419
419
  ) => void;
@@ -194,7 +194,7 @@ export function computeVideoEncodings(
194
194
  return [videoEncoding];
195
195
  }
196
196
 
197
- let presets: Array<VideoPreset> = [];
197
+ let presets: Array<VideoPreset>;
198
198
  if (isScreenShare) {
199
199
  presets =
200
200
  sortPresets(options?.screenShareSimulcastLayers) ??
@@ -0,0 +1,43 @@
1
+ import type { PacketTrailerMetadata } from '../../packetTrailer/types';
2
+
3
+ const MAX_ENTRIES = 300;
4
+
5
+ /**
6
+ * Caches packet trailer metadata extracted from received video frames,
7
+ * keyed by RTP timestamp so it can be looked up when the frame is displayed.
8
+ *
9
+ * Metadata is populated either by the packet trailer worker managed by
10
+ * `PacketTrailerManager` (non-E2EE) or by the E2EE FrameCryptor worker
11
+ * after decryption (E2EE).
12
+ *
13
+ * @experimental
14
+ */
15
+ export class PacketTrailerExtractor {
16
+ private metadataMap = new Map<number, PacketTrailerMetadata>();
17
+
18
+ private activeSsrc: number = 0;
19
+
20
+ storeMetadata(rtpTimestamp: number, ssrc: number, metadata: PacketTrailerMetadata) {
21
+ // Simulcast layer switch: SSRC changed, flush stale entries from old layer.
22
+ if (this.activeSsrc !== 0 && this.activeSsrc !== ssrc) {
23
+ this.metadataMap.clear();
24
+ }
25
+ this.activeSsrc = ssrc;
26
+
27
+ while (this.metadataMap.size >= MAX_ENTRIES) {
28
+ const evicted = this.metadataMap.keys().next().value!;
29
+ this.metadataMap.delete(evicted);
30
+ }
31
+
32
+ this.metadataMap.set(rtpTimestamp, metadata);
33
+ }
34
+
35
+ lookupMetadata(rtpTimestamp: number): PacketTrailerMetadata | undefined {
36
+ return this.metadataMap.get(rtpTimestamp);
37
+ }
38
+
39
+ dispose() {
40
+ this.metadataMap.clear();
41
+ this.activeSsrc = 0;
42
+ }
43
+ }
@@ -1,3 +1,4 @@
1
+ import type { PacketTrailerMetadata } from '../../packetTrailer/types';
1
2
  import { debounce } from '../debounce';
2
3
  import { TrackEvent } from '../events';
3
4
  import type { VideoReceiverStats } from '../stats';
@@ -6,6 +7,7 @@ import CriticalTimers from '../timers';
6
7
  import type { LoggerOptions } from '../types';
7
8
  import type { ObservableMediaElement } from '../utils';
8
9
  import { getDevicePixelRatio, getIntersectionObserver, getResizeObserver, isWeb } from '../utils';
10
+ import type { PacketTrailerExtractor } from './PacketTrailerExtractor';
9
11
  import RemoteTrack from './RemoteTrack';
10
12
  import { Track, attachToElement, detachTrack } from './Track';
11
13
  import type { AdaptiveStreamSettings } from './types';
@@ -23,6 +25,9 @@ export default class RemoteVideoTrack extends RemoteTrack<Track.Kind.Video> {
23
25
 
24
26
  private lastDimensions?: Track.Dimensions;
25
27
 
28
+ /** @internal */
29
+ packetTrailerExtractor?: PacketTrailerExtractor;
30
+
26
31
  constructor(
27
32
  mediaTrack: MediaStreamTrack,
28
33
  sid: string,
@@ -38,6 +43,23 @@ export default class RemoteVideoTrack extends RemoteTrack<Track.Kind.Video> {
38
43
  return this.adaptiveStreamSettings !== undefined;
39
44
  }
40
45
 
46
+ /**
47
+ * Look up frame-level metadata for a given RTP timestamp.
48
+ * Use with the `TrackEvent.TimeSyncUpdate` event to correlate displayed frames
49
+ * with their capture-time metadata.
50
+ *
51
+ * Requires the room to be configured with the `packetTrailer` worker option
52
+ * and the publishing track to have packet trailer features enabled.
53
+ *
54
+ */
55
+ lookupFrameMetadata({
56
+ rtpTimestamp,
57
+ }: {
58
+ rtpTimestamp: number;
59
+ }): PacketTrailerMetadata | undefined {
60
+ return this.packetTrailerExtractor?.lookupMetadata(rtpTimestamp);
61
+ }
62
+
41
63
  override setStreamState(value: Track.StreamState) {
42
64
  super.setStreamState(value);
43
65
  this.log.debug('setStreamState', value);
@@ -140,12 +162,11 @@ export default class RemoteVideoTrack extends RemoteTrack<Track.Kind.Video> {
140
162
  detach(): HTMLMediaElement[];
141
163
  detach(element: HTMLMediaElement): HTMLMediaElement;
142
164
  detach(element?: HTMLMediaElement): HTMLMediaElement | HTMLMediaElement[] {
143
- let detachedElements: HTMLMediaElement[] = [];
144
165
  if (element) {
145
166
  this.stopObservingElement(element);
146
167
  return super.detach(element);
147
168
  }
148
- detachedElements = super.detach();
169
+ const detachedElements = super.detach();
149
170
 
150
171
  for (const e of detachedElements) {
151
172
  this.stopObservingElement(e);
@@ -537,6 +537,6 @@ export type TrackEventCallbacks = {
537
537
  trackProcessorUpdate: (processor?: TrackProcessor<Track.Kind, any>) => void;
538
538
  audioTrackFeatureUpdate: (track: any, feature: AudioTrackFeature, enabled: boolean) => void;
539
539
  timeSyncUpdate: (update: { timestamp: number; rtpTimestamp: number }) => void;
540
- preConnectBufferFlushed: (buffer: Uint8Array[]) => void;
540
+ preConnectBufferFlushed: (buffer: NonSharedUint8Array[]) => void;
541
541
  cpuConstrained: () => void;
542
542
  };
@@ -111,10 +111,6 @@ export async function createLocalTracks(
111
111
  return await Promise.all(
112
112
  stream.getTracks().map(async (mediaStreamTrack) => {
113
113
  const isAudio = mediaStreamTrack.kind === 'audio';
114
- let trackOptions = isAudio ? opts!.audio : opts!.video;
115
- if (typeof trackOptions === 'boolean' || !trackOptions) {
116
- trackOptions = {};
117
- }
118
114
  let trackConstraints: MediaTrackConstraints | undefined;
119
115
  const conOrBool = isAudio ? constraints.audio : constraints.video;
120
116
  if (typeof conOrBool !== 'boolean') {
@@ -1,3 +1,4 @@
1
+ import type { PacketTrailerPublishOptions } from '../../packetTrailer/types';
1
2
  import type { Track } from './Track';
2
3
  import type {
3
4
  AudioProcessorOptions,
@@ -128,6 +129,16 @@ export interface TrackPublishDefaults {
128
129
  * Defaults to false.
129
130
  */
130
131
  preConnectBuffer?: boolean;
132
+
133
+ /**
134
+ * Packet trailer metadata to append to published video frames.
135
+ *
136
+ * Requires either room-level packet trailer worker configuration or E2EE,
137
+ * because encoded frame transforms are used to write the trailer.
138
+ *
139
+ * @experimental
140
+ */
141
+ packetTrailer?: PacketTrailerPublishOptions;
131
142
  }
132
143
 
133
144
  /**
@@ -51,7 +51,7 @@ export class LocalTrackRecorder<T extends LocalTrack> extends RecorderBase {
51
51
  start: (controller) => {
52
52
  streamController = controller;
53
53
  dataListener = async (event: BlobEvent) => {
54
- let data: Uint8Array;
54
+ let data: NonSharedUint8Array;
55
55
 
56
56
  if (event.data.arrayBuffer) {
57
57
  const arrayBuffer = await event.data.arrayBuffer();
@@ -280,7 +280,10 @@ export function getLogContextFromTrack(track: Track | TrackPublication): Record<
280
280
  }
281
281
 
282
282
  export function supportsSynchronizationSources(): boolean {
283
- return typeof RTCRtpReceiver !== 'undefined' && 'getSynchronizationSources' in RTCRtpReceiver;
283
+ return (
284
+ typeof RTCRtpReceiver !== 'undefined' &&
285
+ typeof RTCRtpReceiver.prototype.getSynchronizationSources === 'function'
286
+ );
284
287
  }
285
288
 
286
289
  export function diffAttributes(
@@ -1,5 +1,6 @@
1
+ import { ClientInfo_Capability } from '@livekit/protocol';
1
2
  import { describe, expect, it } from 'vitest';
2
- import { extractMaxAgeFromRequestHeaders, splitUtf8, toWebsocketUrl } from './utils';
3
+ import { extractMaxAgeFromRequestHeaders, getClientInfo, splitUtf8, toWebsocketUrl } from './utils';
3
4
 
4
5
  describe('toWebsocketUrl', () => {
5
6
  it('leaves wss urls alone', () => {
@@ -15,6 +16,18 @@ describe('toWebsocketUrl', () => {
15
16
  });
16
17
  });
17
18
 
19
+ describe('getClientInfo', () => {
20
+ it('does not advertise packet trailer capability by default', () => {
21
+ expect(getClientInfo().capabilities).toEqual([]);
22
+ });
23
+
24
+ it('advertises packet trailer capability when provided', () => {
25
+ expect(getClientInfo([ClientInfo_Capability.CAP_PACKET_TRAILER]).capabilities).toEqual([
26
+ ClientInfo_Capability.CAP_PACKET_TRAILER,
27
+ ]);
28
+ });
29
+ });
30
+
18
31
  describe('splitUtf8', () => {
19
32
  it('splits a string into chunks of the given size', () => {
20
33
  expect(splitUtf8('hello world', 5)).toEqual([
package/src/room/utils.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  ChatMessage as ChatMessageModel,
3
3
  ClientInfo,
4
+ ClientInfo_Capability,
4
5
  ClientInfo_SDK,
5
6
  DisconnectReason,
6
7
  Transcription as TranscriptionModel,
@@ -179,6 +180,18 @@ export function isChromiumBased(): boolean {
179
180
  return !!browser && browser.name === 'Chrome' && browser.os !== 'iOS';
180
181
  }
181
182
 
183
+ export function isScriptTransformSupportedForWorker(): boolean {
184
+ // Chrome occasionally throws an `InvalidState` error when using script transforms directly after introducing this API in 141.
185
+ // Disabling it for Chrome based browsers until the API has stabilized.
186
+ // @ts-ignore
187
+ return (
188
+ typeof window !== 'undefined' &&
189
+ // @ts-ignore
190
+ typeof window.RTCRtpScriptTransform !== 'undefined' &&
191
+ !isChromiumBased()
192
+ );
193
+ }
194
+
182
195
  export function isSafari(): boolean {
183
196
  return getBrowser()?.name === 'Safari';
184
197
  }
@@ -364,8 +377,9 @@ export interface ObservableMediaElement extends HTMLMediaElement {
364
377
  handleVisibilityChanged: (entry: IntersectionObserverEntry) => void;
365
378
  }
366
379
 
367
- export function getClientInfo(): ClientInfo {
380
+ export function getClientInfo(capabilities?: ClientInfo_Capability[]): ClientInfo {
368
381
  const info = new ClientInfo({
382
+ capabilities,
369
383
  sdk: ClientInfo_SDK.JS,
370
384
  protocol: protocolVersion,
371
385
  version,
@@ -740,12 +754,12 @@ export function isRemoteParticipant(p: Participant): p is RemoteParticipant {
740
754
  return !p.isLocal;
741
755
  }
742
756
 
743
- export function splitUtf8(s: string, n: number): Uint8Array[] {
757
+ export function splitUtf8(s: string, n: number): NonSharedUint8Array[] {
744
758
  if (n < 4) {
745
759
  throw new Error('n must be at least 4 due to utf8 encoding rules');
746
760
  }
747
761
  // adapted from https://stackoverflow.com/a/6043797
748
- const result: Uint8Array[] = [];
762
+ const result: NonSharedUint8Array[] = [];
749
763
  let encoded = new TextEncoder().encode(s);
750
764
  while (encoded.length > n) {
751
765
  let k = n;
@@ -1,4 +1,3 @@
1
- /* eslint-disable @typescript-eslint/no-unused-vars */
2
1
  // @ts-ignore
3
2
  export default class MockMediaStreamTrack implements MediaStreamTrack {
4
3
  contentHint: string = '';
@@ -0,0 +1,6 @@
1
+ // As of TS 5.7, `Uint8Array` is generic over its backing buffer (`Uint8Array<ArrayBufferLike>`),
2
+ // which includes `SharedArrayBuffer`. Many Web APIs (WebCrypto, structured clone, RTCDataChannel)
3
+ // only accept the non-shared variant `Uint8Array<ArrayBuffer>`. Using `ReturnType<typeof Uint8Array.from>`
4
+ // resolves to that non-shared variant on TS versions that support the generic, while remaining
5
+ // equivalent to plain `Uint8Array` on older versions — so this alias works across the range we support.
6
+ type NonSharedUint8Array = ReturnType<typeof Uint8Array.from>;
@@ -1,5 +1,5 @@
1
1
  export interface DataPacketItem {
2
- data: Uint8Array;
2
+ data: NonSharedUint8Array;
3
3
  sequence: number;
4
4
  }
5
5
 
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 = 16;
4
+ export const protocolVersion = 17;