livekit-client 2.18.8 → 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 (193) 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 +2898 -2431
  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/LocalDataTrack.d.ts +2 -1
  48. package/dist/src/room/data-track/LocalDataTrack.d.ts.map +1 -1
  49. package/dist/src/room/data-track/RemoteDataTrack.d.ts +5 -1
  50. package/dist/src/room/data-track/RemoteDataTrack.d.ts.map +1 -1
  51. package/dist/src/room/data-track/depacketizer.d.ts +12 -4
  52. package/dist/src/room/data-track/depacketizer.d.ts.map +1 -1
  53. package/dist/src/room/data-track/frame.d.ts +3 -3
  54. package/dist/src/room/data-track/frame.d.ts.map +1 -1
  55. package/dist/src/room/data-track/incoming/IncomingDataTrackManager.d.ts +3 -1
  56. package/dist/src/room/data-track/incoming/IncomingDataTrackManager.d.ts.map +1 -1
  57. package/dist/src/room/data-track/incoming/pipeline.d.ts +4 -1
  58. package/dist/src/room/data-track/incoming/pipeline.d.ts.map +1 -1
  59. package/dist/src/room/data-track/outgoing/OutgoingDataTrackManager.d.ts +2 -2
  60. package/dist/src/room/data-track/outgoing/OutgoingDataTrackManager.d.ts.map +1 -1
  61. package/dist/src/room/data-track/outgoing/types.d.ts +4 -3
  62. package/dist/src/room/data-track/outgoing/types.d.ts.map +1 -1
  63. package/dist/src/room/data-track/packet/extensions.d.ts +4 -4
  64. package/dist/src/room/data-track/packet/extensions.d.ts.map +1 -1
  65. package/dist/src/room/data-track/packet/index.d.ts +5 -5
  66. package/dist/src/room/data-track/packet/index.d.ts.map +1 -1
  67. package/dist/src/room/data-track/packet/serializable.d.ts +1 -1
  68. package/dist/src/room/data-track/packet/serializable.d.ts.map +1 -1
  69. package/dist/src/room/data-track/types.d.ts +7 -0
  70. package/dist/src/room/data-track/types.d.ts.map +1 -1
  71. package/dist/src/room/events.d.ts +2 -2
  72. package/dist/src/room/participant/LocalParticipant.d.ts +3 -1
  73. package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
  74. package/dist/src/room/participant/Participant.d.ts +1 -1
  75. package/dist/src/room/participant/Participant.d.ts.map +1 -1
  76. package/dist/src/room/track/PacketTrailerExtractor.d.ts +19 -0
  77. package/dist/src/room/track/PacketTrailerExtractor.d.ts.map +1 -0
  78. package/dist/src/room/track/RemoteVideoTrack.d.ts +16 -0
  79. package/dist/src/room/track/RemoteVideoTrack.d.ts.map +1 -1
  80. package/dist/src/room/track/Track.d.ts +1 -1
  81. package/dist/src/room/track/Track.d.ts.map +1 -1
  82. package/dist/src/room/track/create.d.ts.map +1 -1
  83. package/dist/src/room/track/options.d.ts +10 -0
  84. package/dist/src/room/track/options.d.ts.map +1 -1
  85. package/dist/src/room/track/utils.d.ts.map +1 -1
  86. package/dist/src/room/utils.d.ts +4 -3
  87. package/dist/src/room/utils.d.ts.map +1 -1
  88. package/dist/src/test/MockMediaStreamTrack.d.ts.map +1 -1
  89. package/dist/src/utils/dataPacketBuffer.d.ts +1 -1
  90. package/dist/src/utils/dataPacketBuffer.d.ts.map +1 -1
  91. package/dist/src/version.d.ts +1 -1
  92. package/dist/ts4.2/api/SignalClient.d.ts +2 -1
  93. package/dist/ts4.2/e2ee/E2eeManager.d.ts +8 -7
  94. package/dist/ts4.2/e2ee/types.d.ts +35 -8
  95. package/dist/ts4.2/e2ee/utils.d.ts +5 -5
  96. package/dist/ts4.2/e2ee/worker/DataCryptor.d.ts +5 -5
  97. package/dist/ts4.2/e2ee/worker/FrameCryptor.d.ts +21 -4
  98. package/dist/ts4.2/e2ee/worker/naluUtils.d.ts +1 -1
  99. package/dist/ts4.2/e2ee/worker/sifPayload.d.ts +7 -7
  100. package/dist/ts4.2/index.d.ts +5 -1
  101. package/dist/ts4.2/options.d.ts +7 -0
  102. package/dist/ts4.2/packetTrailer/PacketTrailerManager.d.ts +49 -0
  103. package/dist/ts4.2/packetTrailer/packetTrailer.d.ts +32 -0
  104. package/dist/ts4.2/packetTrailer/types.d.ts +57 -0
  105. package/dist/ts4.2/packetTrailer/utils.d.ts +9 -0
  106. package/dist/ts4.2/packetTrailer/worker/packetTrailer.worker.d.ts +2 -0
  107. package/dist/ts4.2/room/RTCEngine.d.ts +2 -1
  108. package/dist/ts4.2/room/Room.d.ts +3 -1
  109. package/dist/ts4.2/room/data-track/LocalDataTrack.d.ts +2 -1
  110. package/dist/ts4.2/room/data-track/RemoteDataTrack.d.ts +5 -1
  111. package/dist/ts4.2/room/data-track/depacketizer.d.ts +12 -4
  112. package/dist/ts4.2/room/data-track/frame.d.ts +3 -3
  113. package/dist/ts4.2/room/data-track/incoming/IncomingDataTrackManager.d.ts +3 -1
  114. package/dist/ts4.2/room/data-track/incoming/pipeline.d.ts +4 -1
  115. package/dist/ts4.2/room/data-track/outgoing/OutgoingDataTrackManager.d.ts +2 -2
  116. package/dist/ts4.2/room/data-track/outgoing/types.d.ts +4 -3
  117. package/dist/ts4.2/room/data-track/packet/extensions.d.ts +4 -4
  118. package/dist/ts4.2/room/data-track/packet/index.d.ts +5 -5
  119. package/dist/ts4.2/room/data-track/packet/serializable.d.ts +1 -1
  120. package/dist/ts4.2/room/data-track/types.d.ts +7 -0
  121. package/dist/ts4.2/room/events.d.ts +2 -2
  122. package/dist/ts4.2/room/participant/LocalParticipant.d.ts +3 -1
  123. package/dist/ts4.2/room/participant/Participant.d.ts +1 -1
  124. package/dist/ts4.2/room/track/PacketTrailerExtractor.d.ts +19 -0
  125. package/dist/ts4.2/room/track/RemoteVideoTrack.d.ts +16 -0
  126. package/dist/ts4.2/room/track/Track.d.ts +1 -1
  127. package/dist/ts4.2/room/track/options.d.ts +10 -0
  128. package/dist/ts4.2/room/utils.d.ts +4 -3
  129. package/dist/ts4.2/utils/dataPacketBuffer.d.ts +1 -1
  130. package/dist/ts4.2/version.d.ts +1 -1
  131. package/package.json +24 -16
  132. package/src/api/SignalClient.test.ts +102 -10
  133. package/src/api/SignalClient.ts +4 -2
  134. package/src/api/WebSocketStream.test.ts +0 -1
  135. package/src/e2ee/E2eeManager.ts +82 -30
  136. package/src/e2ee/types.ts +37 -8
  137. package/src/e2ee/utils.ts +7 -6
  138. package/src/e2ee/worker/DataCryptor.ts +6 -6
  139. package/src/e2ee/worker/FrameCryptor.test.ts +177 -4
  140. package/src/e2ee/worker/FrameCryptor.ts +94 -14
  141. package/src/e2ee/worker/ParticipantKeyHandler.test.ts +4 -4
  142. package/src/e2ee/worker/e2ee.worker.ts +13 -5
  143. package/src/e2ee/worker/naluUtils.ts +4 -4
  144. package/src/e2ee/worker/sifPayload.ts +10 -8
  145. package/src/index.ts +7 -0
  146. package/src/options.ts +8 -0
  147. package/src/packetTrailer/PacketTrailerManager.test.ts +172 -0
  148. package/src/packetTrailer/PacketTrailerManager.ts +250 -0
  149. package/src/packetTrailer/packetTrailer.test.ts +174 -0
  150. package/src/packetTrailer/packetTrailer.ts +276 -0
  151. package/src/packetTrailer/types.ts +75 -0
  152. package/src/packetTrailer/utils.test.ts +105 -0
  153. package/src/packetTrailer/utils.ts +50 -0
  154. package/src/packetTrailer/worker/packetTrailer.worker.ts +155 -0
  155. package/src/packetTrailer/worker/tsconfig.json +14 -0
  156. package/src/room/BackOffStrategy.test.ts +1 -1
  157. package/src/room/RTCEngine.test.ts +219 -0
  158. package/src/room/RTCEngine.ts +86 -20
  159. package/src/room/Room.test.ts +62 -1
  160. package/src/room/Room.ts +28 -5
  161. package/src/room/data-track/LocalDataTrack.ts +15 -7
  162. package/src/room/data-track/RemoteDataTrack.ts +8 -1
  163. package/src/room/data-track/depacketizer.test.ts +433 -1
  164. package/src/room/data-track/depacketizer.ts +79 -61
  165. package/src/room/data-track/frame.ts +2 -2
  166. package/src/room/data-track/incoming/IncomingDataTrackManager.test.ts +194 -0
  167. package/src/room/data-track/incoming/IncomingDataTrackManager.ts +21 -1
  168. package/src/room/data-track/incoming/pipeline.ts +13 -2
  169. package/src/room/data-track/outgoing/OutgoingDataTrackManager.test.ts +350 -198
  170. package/src/room/data-track/outgoing/OutgoingDataTrackManager.ts +9 -3
  171. package/src/room/data-track/outgoing/types.ts +4 -3
  172. package/src/room/data-track/packet/extensions.ts +2 -2
  173. package/src/room/data-track/packet/index.ts +6 -6
  174. package/src/room/data-track/packet/serializable.ts +1 -1
  175. package/src/room/data-track/types.ts +8 -0
  176. package/src/room/events.ts +2 -2
  177. package/src/room/participant/LocalParticipant.test.ts +81 -0
  178. package/src/room/participant/LocalParticipant.ts +48 -7
  179. package/src/room/participant/Participant.ts +1 -1
  180. package/src/room/participant/publishUtils.ts +1 -1
  181. package/src/room/track/PacketTrailerExtractor.ts +43 -0
  182. package/src/room/track/RemoteVideoTrack.ts +23 -2
  183. package/src/room/track/Track.ts +1 -1
  184. package/src/room/track/create.ts +0 -4
  185. package/src/room/track/options.ts +11 -0
  186. package/src/room/track/record.ts +1 -1
  187. package/src/room/track/utils.ts +4 -1
  188. package/src/room/utils.test.ts +14 -1
  189. package/src/room/utils.ts +17 -3
  190. package/src/test/MockMediaStreamTrack.ts +0 -1
  191. package/src/type-polyfills/non-shared-typed-arrays.d.ts +6 -0
  192. package/src/utils/dataPacketBuffer.ts +1 -1
  193. package/src/version.ts +1 -1
@@ -0,0 +1,75 @@
1
+ import type { PacketTrailerFramePayload } from './packetTrailer';
2
+
3
+ export interface PacketTrailerMetadata {
4
+ userTimestamp: bigint;
5
+ frameId: number;
6
+ }
7
+
8
+ export interface PacketTrailerPublishOptions {
9
+ timestamp?: boolean;
10
+ frameId?: boolean;
11
+ }
12
+
13
+ export interface PTBaseMessage {
14
+ kind: string;
15
+ data?: unknown;
16
+ }
17
+
18
+ export interface PTInitMessage extends PTBaseMessage {
19
+ kind: 'init';
20
+ }
21
+
22
+ export interface PTInitAck extends PTBaseMessage {
23
+ kind: 'initAck';
24
+ }
25
+
26
+ export interface PTDecodeMessage extends PTBaseMessage {
27
+ kind: 'decode';
28
+ data: {
29
+ readableStream: ReadableStream;
30
+ writableStream: WritableStream;
31
+ trackId: string;
32
+ hasPacketTrailer: boolean;
33
+ };
34
+ }
35
+
36
+ export interface PTEncodeMessage extends PTBaseMessage {
37
+ kind: 'encode';
38
+ data: {
39
+ readableStream: ReadableStream;
40
+ writableStream: WritableStream;
41
+ packetTrailer?: PacketTrailerPublishOptions;
42
+ };
43
+ }
44
+
45
+ export type PTScriptTransformOptions =
46
+ | {
47
+ kind: 'decode';
48
+ trackId: string;
49
+ }
50
+ | {
51
+ kind: 'encode';
52
+ packetTrailer?: PacketTrailerPublishOptions;
53
+ };
54
+
55
+ export interface PTMetadataMessage extends PTBaseMessage {
56
+ kind: 'metadata';
57
+ data: PacketTrailerFramePayload;
58
+ }
59
+
60
+ export interface PTUpdateTrackIdMessage extends PTBaseMessage {
61
+ kind: 'updateTrackId';
62
+ data: {
63
+ oldTrackId: string;
64
+ newTrackId: string;
65
+ hasPacketTrailer: boolean;
66
+ };
67
+ }
68
+
69
+ export type PTWorkerMessage =
70
+ | PTInitMessage
71
+ | PTInitAck
72
+ | PTDecodeMessage
73
+ | PTEncodeMessage
74
+ | PTUpdateTrackIdMessage
75
+ | PTMetadataMessage;
@@ -0,0 +1,105 @@
1
+ import { PacketTrailerFeature } from '@livekit/protocol';
2
+ import { afterEach, describe, expect, it } from 'vitest';
3
+ import {
4
+ getPacketTrailerFeatures,
5
+ getPacketTrailerPublishOptions,
6
+ isPacketTrailerSupported,
7
+ } from './utils';
8
+
9
+ describe('packet trailer support', () => {
10
+ const originalRTCRtpSender = window.RTCRtpSender;
11
+ const originalRTCRtpScriptTransform = (window as unknown as { RTCRtpScriptTransform?: unknown })
12
+ .RTCRtpScriptTransform;
13
+ const originalUserAgent = navigator.userAgent;
14
+
15
+ afterEach(() => {
16
+ Object.defineProperty(window, 'RTCRtpSender', {
17
+ configurable: true,
18
+ value: originalRTCRtpSender,
19
+ writable: true,
20
+ });
21
+ Object.defineProperty(window, 'RTCRtpScriptTransform', {
22
+ configurable: true,
23
+ value: originalRTCRtpScriptTransform,
24
+ writable: true,
25
+ });
26
+ Object.defineProperty(window.navigator, 'userAgent', {
27
+ configurable: true,
28
+ value: originalUserAgent,
29
+ });
30
+ });
31
+
32
+ function stubScriptTransformSupport(userAgent: string) {
33
+ Object.defineProperty(window, 'RTCRtpSender', {
34
+ configurable: true,
35
+ value: undefined,
36
+ writable: true,
37
+ });
38
+ Object.defineProperty(window, 'RTCRtpScriptTransform', {
39
+ configurable: true,
40
+ value: class MockRTCRtpScriptTransform {},
41
+ writable: true,
42
+ });
43
+ Object.defineProperty(window.navigator, 'userAgent', {
44
+ configurable: true,
45
+ value: userAgent,
46
+ });
47
+ }
48
+
49
+ it('supports packet trailers with RTCRtpScriptTransform on Safari', () => {
50
+ stubScriptTransformSupport(
51
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_0) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15',
52
+ );
53
+
54
+ expect(isPacketTrailerSupported({ worker: {} as Worker })).toBe(true);
55
+ });
56
+
57
+ it('supports packet trailers with RTCRtpScriptTransform on Firefox', () => {
58
+ stubScriptTransformSupport(
59
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 14.0; rv:144.0) Gecko/20100101 Firefox/144.0',
60
+ );
61
+
62
+ expect(isPacketTrailerSupported({ worker: {} as Worker })).toBe(true);
63
+ });
64
+
65
+ it('does not use RTCRtpScriptTransform support on Chromium-based browsers', () => {
66
+ stubScriptTransformSupport(
67
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36',
68
+ );
69
+
70
+ expect(isPacketTrailerSupported({ worker: {} as Worker })).toBe(false);
71
+ });
72
+ });
73
+
74
+ describe('packet trailer publish features', () => {
75
+ it('maps publish options to protocol features', () => {
76
+ expect(getPacketTrailerFeatures({ timestamp: true, frameId: true })).toEqual([
77
+ PacketTrailerFeature.PTF_USER_TIMESTAMP,
78
+ PacketTrailerFeature.PTF_FRAME_ID,
79
+ ]);
80
+ expect(getPacketTrailerFeatures({ timestamp: true })).toEqual([
81
+ PacketTrailerFeature.PTF_USER_TIMESTAMP,
82
+ ]);
83
+ expect(getPacketTrailerFeatures({ frameId: true })).toEqual([
84
+ PacketTrailerFeature.PTF_FRAME_ID,
85
+ ]);
86
+ expect(getPacketTrailerFeatures()).toEqual([]);
87
+ });
88
+
89
+ it('maps protocol features to publish options', () => {
90
+ expect(
91
+ getPacketTrailerPublishOptions([
92
+ PacketTrailerFeature.PTF_USER_TIMESTAMP,
93
+ PacketTrailerFeature.PTF_FRAME_ID,
94
+ ]),
95
+ ).toEqual({ timestamp: true, frameId: true });
96
+ expect(getPacketTrailerPublishOptions([PacketTrailerFeature.PTF_USER_TIMESTAMP])).toEqual({
97
+ timestamp: true,
98
+ });
99
+ expect(getPacketTrailerPublishOptions([PacketTrailerFeature.PTF_FRAME_ID])).toEqual({
100
+ frameId: true,
101
+ });
102
+ expect(getPacketTrailerPublishOptions()).toBeUndefined();
103
+ expect(getPacketTrailerPublishOptions([])).toBeUndefined();
104
+ });
105
+ });
@@ -0,0 +1,50 @@
1
+ import { PacketTrailerFeature } from '@livekit/protocol';
2
+ import { isInsertableStreamSupported } from '../e2ee/utils';
3
+ import { isScriptTransformSupportedForWorker } from '../room/utils';
4
+ import type { PacketTrailerOptions } from './PacketTrailerManager';
5
+ import type { PacketTrailerPublishOptions } from './types';
6
+
7
+ export function shouldUsePacketTrailerScriptTransform() {
8
+ return isScriptTransformSupportedForWorker();
9
+ }
10
+
11
+ export function isPacketTrailerSupported(options?: PacketTrailerOptions) {
12
+ return (
13
+ !!options?.worker && (isInsertableStreamSupported() || shouldUsePacketTrailerScriptTransform())
14
+ );
15
+ }
16
+
17
+ export function hasPacketTrailerPublishOptions(options?: PacketTrailerPublishOptions): boolean {
18
+ return !!(options?.timestamp || options?.frameId);
19
+ }
20
+
21
+ export function getPacketTrailerFeatures(
22
+ options?: PacketTrailerPublishOptions,
23
+ ): PacketTrailerFeature[] {
24
+ const features: PacketTrailerFeature[] = [];
25
+ if (options?.timestamp) {
26
+ features.push(PacketTrailerFeature.PTF_USER_TIMESTAMP);
27
+ }
28
+ if (options?.frameId) {
29
+ features.push(PacketTrailerFeature.PTF_FRAME_ID);
30
+ }
31
+ return features;
32
+ }
33
+
34
+ export function getPacketTrailerPublishOptions(
35
+ features?: PacketTrailerFeature[],
36
+ ): PacketTrailerPublishOptions | undefined {
37
+ if (!features || features.length === 0) {
38
+ return undefined;
39
+ }
40
+
41
+ const options: PacketTrailerPublishOptions = {};
42
+ if (features.includes(PacketTrailerFeature.PTF_USER_TIMESTAMP)) {
43
+ options.timestamp = true;
44
+ }
45
+ if (features.includes(PacketTrailerFeature.PTF_FRAME_ID)) {
46
+ options.frameId = true;
47
+ }
48
+
49
+ return options.timestamp || options.frameId ? options : undefined;
50
+ }
@@ -0,0 +1,155 @@
1
+ import { appendPacketTrailerToEncodedFrame, processPacketTrailer } from '../packetTrailer';
2
+ import type {
3
+ PTMetadataMessage,
4
+ PTScriptTransformOptions,
5
+ PTWorkerMessage,
6
+ PacketTrailerPublishOptions,
7
+ } from '../types';
8
+ import { hasPacketTrailerPublishOptions } from '../utils';
9
+
10
+ /**
11
+ * Holds the trackId currently associated with a pipeline. A mutable
12
+ * wrapper is used so the transform closure always reads the latest
13
+ * trackId after a receiver gets re-bound to a new track.
14
+ */
15
+ interface PipelineState {
16
+ trackId: string;
17
+ hasPacketTrailer: boolean;
18
+ }
19
+
20
+ const pipelines = new Map<string, PipelineState>();
21
+
22
+ onmessage = (ev: MessageEvent<PTWorkerMessage>) => {
23
+ const msg = ev.data;
24
+
25
+ switch (msg.kind) {
26
+ case 'init':
27
+ postMessage({ kind: 'initAck' });
28
+ break;
29
+
30
+ case 'decode':
31
+ setupDecodeTransform(
32
+ msg.data.readableStream,
33
+ msg.data.writableStream,
34
+ msg.data.trackId,
35
+ msg.data.hasPacketTrailer,
36
+ );
37
+ break;
38
+
39
+ case 'encode':
40
+ setupEncodeTransform(
41
+ msg.data.readableStream,
42
+ msg.data.writableStream,
43
+ msg.data.packetTrailer,
44
+ );
45
+ break;
46
+
47
+ case 'updateTrackId':
48
+ updateTrackId(msg.data.oldTrackId, msg.data.newTrackId, msg.data.hasPacketTrailer);
49
+ break;
50
+
51
+ default:
52
+ break;
53
+ }
54
+ };
55
+
56
+ function setupDecodeTransform(
57
+ readable: ReadableStream,
58
+ writable: WritableStream,
59
+ trackId: string,
60
+ hasPacketTrailer: boolean,
61
+ ) {
62
+ const state: PipelineState = { trackId, hasPacketTrailer };
63
+ pipelines.set(trackId, state);
64
+
65
+ const transform = new TransformStream({
66
+ transform(
67
+ frame: RTCEncodedVideoFrame,
68
+ controller: TransformStreamDefaultController<RTCEncodedVideoFrame>,
69
+ ) {
70
+ try {
71
+ if (state.hasPacketTrailer) {
72
+ const result = processPacketTrailer(frame, state.trackId);
73
+ if (result.data) {
74
+ frame.data = result.data;
75
+ }
76
+ if (result.payload) {
77
+ const msg: PTMetadataMessage = { kind: 'metadata', data: result.payload };
78
+ postMessage(msg);
79
+ }
80
+ }
81
+ } catch {
82
+ // Never drop frames on trailer-extraction failure — pass through so
83
+ // video keeps decoding even if metadata is lost for this frame.
84
+ }
85
+ controller.enqueue(frame);
86
+ },
87
+ });
88
+
89
+ readable
90
+ .pipeThrough(transform)
91
+ .pipeTo(writable)
92
+ .catch(() => {
93
+ pipelines.delete(state.trackId);
94
+ });
95
+ }
96
+
97
+ function setupEncodeTransform(
98
+ readable: ReadableStream,
99
+ writable: WritableStream,
100
+ packetTrailer?: PacketTrailerPublishOptions,
101
+ ) {
102
+ if (!hasPacketTrailerPublishOptions(packetTrailer)) {
103
+ readable.pipeTo(writable).catch(() => {});
104
+ return;
105
+ }
106
+
107
+ let frameId = 0;
108
+ const transform = new TransformStream({
109
+ transform(
110
+ frame: RTCEncodedVideoFrame,
111
+ controller: TransformStreamDefaultController<RTCEncodedVideoFrame>,
112
+ ) {
113
+ try {
114
+ if (packetTrailer?.frameId) {
115
+ frameId = frameId === 0xffffffff ? 1 : frameId + 1;
116
+ }
117
+ appendPacketTrailerToEncodedFrame(frame, packetTrailer, frameId);
118
+ } catch {
119
+ // Never drop frames on trailer-write failure.
120
+ }
121
+ controller.enqueue(frame);
122
+ },
123
+ });
124
+
125
+ readable
126
+ .pipeThrough(transform)
127
+ .pipeTo(writable)
128
+ .catch(() => {});
129
+ }
130
+
131
+ function updateTrackId(oldTrackId: string, newTrackId: string, hasPacketTrailer: boolean) {
132
+ const state = pipelines.get(oldTrackId);
133
+ if (state) {
134
+ state.trackId = newTrackId;
135
+ state.hasPacketTrailer = hasPacketTrailer;
136
+ pipelines.delete(oldTrackId);
137
+ pipelines.set(newTrackId, state);
138
+ }
139
+ }
140
+
141
+ // Operations using RTCRtpScriptTransform.
142
+ // @ts-ignore
143
+ if (self.RTCTransformEvent) {
144
+ // @ts-ignore
145
+ self.onrtctransform = (event: RTCTransformEvent) => {
146
+ // @ts-ignore
147
+ const transformer = event.transformer;
148
+ const options = transformer.options as PTScriptTransformOptions;
149
+ if (options.kind === 'encode') {
150
+ setupEncodeTransform(transformer.readable, transformer.writable, options.packetTrailer);
151
+ } else {
152
+ setupDecodeTransform(transformer.readable, transformer.writable, options.trackId, true);
153
+ }
154
+ };
155
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "extends": "../../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "lib": [
5
+ "DOM",
6
+ "DOM.Iterable",
7
+ "ES2017",
8
+ "ES2018.Promise",
9
+ "WebWorker",
10
+ "ES2021.WeakRef",
11
+ "DOM.AsyncIterable"
12
+ ]
13
+ }
14
+ }
@@ -6,7 +6,7 @@ vi.mock('./utils', async () => {
6
6
  const actual = await vi.importActual('./utils');
7
7
  return {
8
8
  ...actual,
9
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
9
+
10
10
  sleep: vi.fn((ms: number) => Promise.resolve()),
11
11
  extractProjectFromUrl: vi.fn((url: URL) => {
12
12
  // @ts-ignore
@@ -0,0 +1,219 @@
1
+ import { afterEach, describe, expect, it, vi } from 'vitest';
2
+ import RTCEngine from './RTCEngine';
3
+ import { roomOptionDefaults } from './defaults';
4
+
5
+ describe('RTCEngine', () => {
6
+ const originalRTCRtpSender = window.RTCRtpSender;
7
+ const originalRTCRtpScriptTransform = (window as unknown as { RTCRtpScriptTransform?: unknown })
8
+ .RTCRtpScriptTransform;
9
+ const originalUserAgent = navigator.userAgent;
10
+
11
+ afterEach(() => {
12
+ Object.defineProperty(window, 'RTCRtpSender', {
13
+ configurable: true,
14
+ value: originalRTCRtpSender,
15
+ writable: true,
16
+ });
17
+ Object.defineProperty(window, 'RTCRtpScriptTransform', {
18
+ configurable: true,
19
+ value: originalRTCRtpScriptTransform,
20
+ writable: true,
21
+ });
22
+ Object.defineProperty(window.navigator, 'userAgent', {
23
+ configurable: true,
24
+ value: originalUserAgent,
25
+ });
26
+ });
27
+
28
+ function stubInsertableStreamsSupport() {
29
+ class MockRTCRtpSender {
30
+ createEncodedStreams() {}
31
+ }
32
+
33
+ Object.defineProperty(window, 'RTCRtpSender', {
34
+ configurable: true,
35
+ value: MockRTCRtpSender,
36
+ writable: true,
37
+ });
38
+ }
39
+
40
+ function stubScriptTransformSupport() {
41
+ Object.defineProperty(window, 'RTCRtpScriptTransform', {
42
+ configurable: true,
43
+ value: class MockRTCRtpScriptTransform {},
44
+ writable: true,
45
+ });
46
+ Object.defineProperty(window.navigator, 'userAgent', {
47
+ configurable: true,
48
+ value:
49
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_0) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15',
50
+ });
51
+ }
52
+
53
+ function makeRTCConfiguration(engine: RTCEngine) {
54
+ return (
55
+ engine as unknown as { makeRTCConfiguration: () => RTCConfiguration }
56
+ ).makeRTCConfiguration();
57
+ }
58
+
59
+ function setupPacketTrailerSender(engine: RTCEngine, sender: RTCRtpSender, opts = {}) {
60
+ (
61
+ engine as unknown as {
62
+ setupPacketTrailerSender: (sender: RTCRtpSender, opts?: unknown) => void;
63
+ }
64
+ ).setupPacketTrailerSender(sender, opts);
65
+ }
66
+
67
+ it('does not enable encoded insertable streams without E2EE or a packet trailer worker', () => {
68
+ stubInsertableStreamsSupport();
69
+
70
+ const engine = new RTCEngine(roomOptionDefaults);
71
+
72
+ expect(makeRTCConfiguration(engine).encodedInsertableStreams).toBeUndefined();
73
+ });
74
+
75
+ it('enables encoded insertable streams when a packet trailer worker is configured', () => {
76
+ stubInsertableStreamsSupport();
77
+
78
+ const engine = new RTCEngine({
79
+ ...roomOptionDefaults,
80
+ packetTrailer: { worker: {} as Worker },
81
+ });
82
+
83
+ expect(makeRTCConfiguration(engine).encodedInsertableStreams).toBe(true);
84
+ });
85
+
86
+ it('does not enable encoded insertable streams for packet trailers when script transforms are supported', () => {
87
+ stubInsertableStreamsSupport();
88
+ stubScriptTransformSupport();
89
+
90
+ const engine = new RTCEngine({
91
+ ...roomOptionDefaults,
92
+ packetTrailer: { worker: {} as Worker },
93
+ });
94
+
95
+ expect(makeRTCConfiguration(engine).encodedInsertableStreams).toBeUndefined();
96
+ });
97
+
98
+ it('enables encoded insertable streams for E2EE', () => {
99
+ stubInsertableStreamsSupport();
100
+
101
+ const engine = new RTCEngine(roomOptionDefaults);
102
+ (
103
+ engine as unknown as {
104
+ signalOpts: {
105
+ autoSubscribe: boolean;
106
+ maxRetries: number;
107
+ e2eeEnabled: boolean;
108
+ websocketTimeout: number;
109
+ };
110
+ }
111
+ ).signalOpts = {
112
+ autoSubscribe: true,
113
+ maxRetries: 1,
114
+ e2eeEnabled: true,
115
+ websocketTimeout: 15_000,
116
+ };
117
+
118
+ expect(makeRTCConfiguration(engine).encodedInsertableStreams).toBe(true);
119
+ });
120
+
121
+ it('does not create sender encoded streams when packetTrailer has no worker', () => {
122
+ const engine = new RTCEngine({
123
+ ...roomOptionDefaults,
124
+ packetTrailer: {} as never,
125
+ });
126
+ const createEncodedStreams = vi.fn();
127
+ const sender = {
128
+ createEncodedStreams,
129
+ } as unknown as RTCRtpSender;
130
+
131
+ setupPacketTrailerSender(engine, sender);
132
+
133
+ expect(createEncodedStreams).not.toHaveBeenCalled();
134
+ });
135
+
136
+ it('does not create sender passthrough streams for packet trailers when script transforms are supported', () => {
137
+ stubScriptTransformSupport();
138
+
139
+ const engine = new RTCEngine({
140
+ ...roomOptionDefaults,
141
+ packetTrailer: { worker: {} as Worker },
142
+ });
143
+ const createEncodedStreams = vi.fn();
144
+ const sender = {
145
+ createEncodedStreams,
146
+ } as unknown as RTCRtpSender;
147
+
148
+ setupPacketTrailerSender(engine, sender);
149
+
150
+ expect(createEncodedStreams).not.toHaveBeenCalled();
151
+ });
152
+
153
+ it('posts sender encode streams to the packet trailer worker when write features are enabled', () => {
154
+ stubInsertableStreamsSupport();
155
+
156
+ const worker = { postMessage: vi.fn() } as unknown as Worker;
157
+ const engine = new RTCEngine({
158
+ ...roomOptionDefaults,
159
+ packetTrailer: { worker },
160
+ });
161
+ const readable = {} as ReadableStream;
162
+ const writable = {} as WritableStream;
163
+ const createEncodedStreams = vi.fn(() => ({ readable, writable }));
164
+ const sender = {
165
+ createEncodedStreams,
166
+ } as unknown as RTCRtpSender;
167
+
168
+ setupPacketTrailerSender(engine, sender, { packetTrailer: { timestamp: true, frameId: true } });
169
+
170
+ expect(createEncodedStreams).toHaveBeenCalledTimes(1);
171
+ expect(worker.postMessage).toHaveBeenCalledWith(
172
+ {
173
+ kind: 'encode',
174
+ data: {
175
+ readableStream: readable,
176
+ writableStream: writable,
177
+ packetTrailer: { timestamp: true, frameId: true },
178
+ },
179
+ },
180
+ [readable, writable],
181
+ );
182
+ });
183
+
184
+ it('uses RTCRtpScriptTransform for sender packet trailer writes when supported', () => {
185
+ stubScriptTransformSupport();
186
+
187
+ const transform = {};
188
+ const RTCRtpScriptTransform = vi.fn(() => transform);
189
+ Object.defineProperty(window, 'RTCRtpScriptTransform', {
190
+ configurable: true,
191
+ value: RTCRtpScriptTransform,
192
+ writable: true,
193
+ });
194
+ Object.defineProperty(globalThis, 'RTCRtpScriptTransform', {
195
+ configurable: true,
196
+ value: RTCRtpScriptTransform,
197
+ writable: true,
198
+ });
199
+
200
+ const worker = {} as Worker;
201
+ const engine = new RTCEngine({
202
+ ...roomOptionDefaults,
203
+ packetTrailer: { worker },
204
+ });
205
+ const createEncodedStreams = vi.fn();
206
+ const sender = {
207
+ createEncodedStreams,
208
+ } as unknown as RTCRtpSender;
209
+
210
+ setupPacketTrailerSender(engine, sender, { packetTrailer: { timestamp: true } });
211
+
212
+ expect(RTCRtpScriptTransform).toHaveBeenCalledWith(worker, {
213
+ kind: 'encode',
214
+ packetTrailer: { timestamp: true },
215
+ });
216
+ expect((sender as unknown as { transform: unknown }).transform).toBe(transform);
217
+ expect(createEncodedStreams).not.toHaveBeenCalled();
218
+ });
219
+ });