livekit-client 2.17.2 → 2.18.0

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 (188) hide show
  1. package/README.md +7 -5
  2. package/dist/livekit-client.e2ee.worker.js +1 -1
  3. package/dist/livekit-client.e2ee.worker.js.map +1 -1
  4. package/dist/livekit-client.e2ee.worker.mjs +20 -14
  5. package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
  6. package/dist/livekit-client.esm.mjs +295 -193
  7. package/dist/livekit-client.esm.mjs.map +1 -1
  8. package/dist/livekit-client.umd.js +1 -1
  9. package/dist/livekit-client.umd.js.map +1 -1
  10. package/dist/src/e2ee/E2eeManager.d.ts +2 -0
  11. package/dist/src/e2ee/E2eeManager.d.ts.map +1 -1
  12. package/dist/src/e2ee/KeyProvider.d.ts +2 -0
  13. package/dist/src/e2ee/KeyProvider.d.ts.map +1 -1
  14. package/dist/src/e2ee/events.d.ts +1 -1
  15. package/dist/src/e2ee/events.d.ts.map +1 -1
  16. package/dist/src/e2ee/types.d.ts +1 -0
  17. package/dist/src/e2ee/types.d.ts.map +1 -1
  18. package/dist/src/e2ee/worker/ParticipantKeyHandler.d.ts +2 -2
  19. package/dist/src/e2ee/worker/ParticipantKeyHandler.d.ts.map +1 -1
  20. package/dist/src/index.d.ts +2 -2
  21. package/dist/src/index.d.ts.map +1 -1
  22. package/dist/src/room/PCTransport.d.ts +1 -4
  23. package/dist/src/room/PCTransport.d.ts.map +1 -1
  24. package/dist/src/room/RTCEngine.d.ts +3 -1
  25. package/dist/src/room/RTCEngine.d.ts.map +1 -1
  26. package/dist/src/room/Room.d.ts.map +1 -1
  27. package/dist/src/room/data-stream/incoming/IncomingDataStreamManager.d.ts.map +1 -1
  28. package/dist/src/room/data-stream/incoming/StreamReader.d.ts +2 -4
  29. package/dist/src/room/data-stream/incoming/StreamReader.d.ts.map +1 -1
  30. package/dist/src/room/data-track/LocalDataTrack.d.ts +25 -0
  31. package/dist/src/room/data-track/LocalDataTrack.d.ts.map +1 -0
  32. package/dist/src/room/data-track/RemoteDataTrack.d.ts +46 -0
  33. package/dist/src/room/data-track/RemoteDataTrack.d.ts.map +1 -0
  34. package/dist/src/room/data-track/depacketizer.d.ts +4 -4
  35. package/dist/src/room/data-track/depacketizer.d.ts.map +1 -1
  36. package/dist/src/room/data-track/e2ee.d.ts +12 -0
  37. package/dist/src/room/data-track/e2ee.d.ts.map +1 -0
  38. package/dist/src/room/data-track/handle.d.ts +7 -8
  39. package/dist/src/room/data-track/handle.d.ts.map +1 -1
  40. package/dist/src/room/data-track/incoming/IncomingDataTrackManager.d.ts +96 -0
  41. package/dist/src/room/data-track/incoming/IncomingDataTrackManager.d.ts.map +1 -0
  42. package/dist/src/room/data-track/incoming/errors.d.ts +24 -0
  43. package/dist/src/room/data-track/incoming/errors.d.ts.map +1 -0
  44. package/dist/src/room/data-track/incoming/pipeline.d.ts +37 -0
  45. package/dist/src/room/data-track/incoming/pipeline.d.ts.map +1 -0
  46. package/dist/src/room/data-track/incoming/types.d.ts +20 -0
  47. package/dist/src/room/data-track/incoming/types.d.ts.map +1 -0
  48. package/dist/src/room/data-track/outgoing/OutgoingDataTrackManager.d.ts +77 -0
  49. package/dist/src/room/data-track/outgoing/OutgoingDataTrackManager.d.ts.map +1 -0
  50. package/dist/src/room/data-track/outgoing/errors.d.ts +64 -0
  51. package/dist/src/room/data-track/outgoing/errors.d.ts.map +1 -0
  52. package/dist/src/room/data-track/outgoing/pipeline.d.ts +22 -0
  53. package/dist/src/room/data-track/outgoing/pipeline.d.ts.map +1 -0
  54. package/dist/src/room/data-track/outgoing/types.d.ts +30 -0
  55. package/dist/src/room/data-track/outgoing/types.d.ts.map +1 -0
  56. package/dist/src/room/data-track/packet/errors.d.ts +2 -4
  57. package/dist/src/room/data-track/packet/errors.d.ts.map +1 -1
  58. package/dist/src/room/data-track/packet/extensions.d.ts +4 -4
  59. package/dist/src/room/data-track/packet/extensions.d.ts.map +1 -1
  60. package/dist/src/room/data-track/packet/index.d.ts +5 -5
  61. package/dist/src/room/data-track/packet/index.d.ts.map +1 -1
  62. package/dist/src/room/data-track/packet/serializable.d.ts +4 -4
  63. package/dist/src/room/data-track/packet/serializable.d.ts.map +1 -1
  64. package/dist/src/room/data-track/packetizer.d.ts +4 -4
  65. package/dist/src/room/data-track/packetizer.d.ts.map +1 -1
  66. package/dist/src/room/data-track/track-interfaces.d.ts +23 -0
  67. package/dist/src/room/data-track/track-interfaces.d.ts.map +1 -0
  68. package/dist/src/room/data-track/types.d.ts +10 -0
  69. package/dist/src/room/data-track/types.d.ts.map +1 -0
  70. package/dist/src/room/debounce.d.ts +11 -0
  71. package/dist/src/room/debounce.d.ts.map +1 -0
  72. package/dist/src/room/events.d.ts +1 -1
  73. package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
  74. package/dist/src/room/participant/RemoteParticipant.d.ts +1 -1
  75. package/dist/src/room/token-source/TokenSource.d.ts +1 -1
  76. package/dist/src/room/token-source/TokenSource.d.ts.map +1 -1
  77. package/dist/src/room/token-source/types.d.ts +1 -0
  78. package/dist/src/room/token-source/types.d.ts.map +1 -1
  79. package/dist/src/room/track/LocalAudioTrack.d.ts +1 -1
  80. package/dist/src/room/track/LocalAudioTrack.d.ts.map +1 -1
  81. package/dist/src/room/track/LocalTrack.d.ts +1 -1
  82. package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
  83. package/dist/src/room/types.d.ts +0 -2
  84. package/dist/src/room/types.d.ts.map +1 -1
  85. package/dist/src/room/utils.d.ts +6 -1
  86. package/dist/src/room/utils.d.ts.map +1 -1
  87. package/dist/src/utils/abort-signal-polyfill.d.ts +13 -0
  88. package/dist/src/utils/abort-signal-polyfill.d.ts.map +1 -0
  89. package/dist/src/utils/subscribeToEvents.d.ts +15 -0
  90. package/dist/src/utils/subscribeToEvents.d.ts.map +1 -0
  91. package/dist/ts4.2/e2ee/E2eeManager.d.ts +2 -0
  92. package/dist/ts4.2/e2ee/KeyProvider.d.ts +2 -0
  93. package/dist/ts4.2/e2ee/events.d.ts +1 -1
  94. package/dist/ts4.2/e2ee/types.d.ts +1 -0
  95. package/dist/ts4.2/e2ee/worker/ParticipantKeyHandler.d.ts +2 -2
  96. package/dist/ts4.2/index.d.ts +2 -2
  97. package/dist/ts4.2/room/PCTransport.d.ts +1 -6
  98. package/dist/ts4.2/room/RTCEngine.d.ts +3 -1
  99. package/dist/ts4.2/room/data-stream/incoming/StreamReader.d.ts +2 -4
  100. package/dist/ts4.2/room/data-track/LocalDataTrack.d.ts +25 -0
  101. package/dist/ts4.2/room/data-track/RemoteDataTrack.d.ts +46 -0
  102. package/dist/ts4.2/room/data-track/depacketizer.d.ts +4 -4
  103. package/dist/ts4.2/room/data-track/e2ee.d.ts +12 -0
  104. package/dist/ts4.2/room/data-track/handle.d.ts +7 -8
  105. package/dist/ts4.2/room/data-track/incoming/IncomingDataTrackManager.d.ts +99 -0
  106. package/dist/ts4.2/room/data-track/incoming/errors.d.ts +24 -0
  107. package/dist/ts4.2/room/data-track/incoming/pipeline.d.ts +37 -0
  108. package/dist/ts4.2/room/data-track/incoming/types.d.ts +20 -0
  109. package/dist/ts4.2/room/data-track/outgoing/OutgoingDataTrackManager.d.ts +77 -0
  110. package/dist/ts4.2/room/data-track/outgoing/errors.d.ts +64 -0
  111. package/dist/ts4.2/room/data-track/outgoing/pipeline.d.ts +22 -0
  112. package/dist/ts4.2/room/data-track/outgoing/types.d.ts +30 -0
  113. package/dist/ts4.2/room/data-track/packet/errors.d.ts +2 -4
  114. package/dist/ts4.2/room/data-track/packet/extensions.d.ts +4 -4
  115. package/dist/ts4.2/room/data-track/packet/index.d.ts +5 -6
  116. package/dist/ts4.2/room/data-track/packet/serializable.d.ts +4 -4
  117. package/dist/ts4.2/room/data-track/packetizer.d.ts +4 -4
  118. package/dist/ts4.2/room/data-track/track-interfaces.d.ts +23 -0
  119. package/dist/ts4.2/room/data-track/types.d.ts +10 -0
  120. package/dist/ts4.2/room/debounce.d.ts +11 -0
  121. package/dist/ts4.2/room/events.d.ts +1 -1
  122. package/dist/ts4.2/room/participant/RemoteParticipant.d.ts +1 -1
  123. package/dist/ts4.2/room/token-source/TokenSource.d.ts +1 -1
  124. package/dist/ts4.2/room/token-source/types.d.ts +1 -0
  125. package/dist/ts4.2/room/track/LocalAudioTrack.d.ts +1 -1
  126. package/dist/ts4.2/room/track/LocalTrack.d.ts +1 -1
  127. package/dist/ts4.2/room/types.d.ts +0 -2
  128. package/dist/ts4.2/room/utils.d.ts +6 -1
  129. package/dist/ts4.2/utils/abort-signal-polyfill.d.ts +13 -0
  130. package/dist/ts4.2/utils/subscribeToEvents.d.ts +15 -0
  131. package/package.json +7 -6
  132. package/src/e2ee/E2eeManager.ts +9 -5
  133. package/src/e2ee/KeyProvider.ts +10 -1
  134. package/src/e2ee/events.ts +1 -1
  135. package/src/e2ee/types.ts +1 -0
  136. package/src/e2ee/worker/ParticipantKeyHandler.ts +7 -4
  137. package/src/e2ee/worker/e2ee.worker.ts +20 -10
  138. package/src/index.ts +2 -2
  139. package/src/room/PCTransport.ts +1 -1
  140. package/src/room/RTCEngine.ts +8 -1
  141. package/src/room/Room.ts +13 -7
  142. package/src/room/data-stream/incoming/IncomingDataStreamManager.ts +5 -25
  143. package/src/room/data-stream/incoming/StreamReader.ts +56 -73
  144. package/src/room/data-stream/outgoing/OutgoingDataStreamManager.ts +2 -2
  145. package/src/room/data-track/LocalDataTrack.ts +53 -0
  146. package/src/room/data-track/RemoteDataTrack.ts +82 -0
  147. package/src/room/data-track/depacketizer.test.ts +1 -1
  148. package/src/room/data-track/depacketizer.ts +5 -17
  149. package/src/room/data-track/e2ee.ts +15 -0
  150. package/src/room/data-track/frame.ts +1 -1
  151. package/src/room/data-track/handle.test.ts +1 -1
  152. package/src/room/data-track/handle.ts +10 -21
  153. package/src/room/data-track/incoming/IncomingDataTrackManager.test.ts +570 -0
  154. package/src/room/data-track/incoming/IncomingDataTrackManager.ts +537 -0
  155. package/src/room/data-track/incoming/errors.ts +57 -0
  156. package/src/room/data-track/incoming/pipeline.ts +116 -0
  157. package/src/room/data-track/incoming/types.ts +22 -0
  158. package/src/room/data-track/outgoing/OutgoingDataTrackManager.test.ts +412 -0
  159. package/src/room/data-track/outgoing/OutgoingDataTrackManager.ts +308 -0
  160. package/src/room/data-track/outgoing/errors.ts +157 -0
  161. package/src/room/data-track/outgoing/pipeline.ts +72 -0
  162. package/src/room/data-track/outgoing/types.ts +36 -0
  163. package/src/room/data-track/packet/errors.ts +2 -14
  164. package/src/room/data-track/packet/extensions.ts +4 -4
  165. package/src/room/data-track/packet/index.ts +6 -8
  166. package/src/room/data-track/packet/serializable.ts +4 -4
  167. package/src/room/data-track/packetizer.test.ts +3 -3
  168. package/src/room/data-track/packetizer.ts +9 -8
  169. package/src/room/data-track/track-interfaces.ts +53 -0
  170. package/src/room/data-track/types.ts +11 -0
  171. package/src/room/debounce.ts +115 -0
  172. package/src/room/events.ts +1 -1
  173. package/src/room/participant/LocalParticipant.ts +2 -0
  174. package/src/room/participant/RemoteParticipant.ts +1 -1
  175. package/src/room/token-source/TokenSource.ts +8 -2
  176. package/src/room/token-source/types.ts +4 -0
  177. package/src/room/track/LocalAudioTrack.ts +6 -3
  178. package/src/room/track/LocalTrack.ts +10 -5
  179. package/src/room/track/LocalVideoTrack.ts +1 -1
  180. package/src/room/track/RemoteVideoTrack.ts +1 -1
  181. package/src/room/types.ts +0 -2
  182. package/src/room/utils.ts +7 -2
  183. package/src/utils/abort-signal-polyfill.ts +63 -0
  184. package/src/utils/subscribeToEvents.ts +80 -0
  185. package/dist/src/utils/throws.d.ts +0 -36
  186. package/dist/src/utils/throws.d.ts.map +0 -1
  187. package/dist/ts4.2/utils/throws.d.ts +0 -39
  188. package/src/utils/throws.ts +0 -42
@@ -1,7 +1,7 @@
1
1
  import type { DataStream_Chunk } from '@livekit/protocol';
2
2
  import { DataStreamError, DataStreamErrorReason } from '../../errors';
3
3
  import type { BaseStreamInfo, ByteStreamInfo, TextStreamInfo } from '../../types';
4
- import { Future, bigIntToNumber } from '../../utils';
4
+ import { bigIntToNumber } from '../../utils';
5
5
 
6
6
  export type BaseStreamReaderReadAllOpts = {
7
7
  /** An AbortSignal can be used to terminate reads early. */
@@ -17,8 +17,6 @@ abstract class BaseStreamReader<T extends BaseStreamInfo> {
17
17
 
18
18
  protected bytesReceived: number;
19
19
 
20
- protected outOfBandFailureRejectingFuture?: Future<never, Error>;
21
-
22
20
  get info() {
23
21
  return this._info;
24
22
  }
@@ -42,17 +40,11 @@ abstract class BaseStreamReader<T extends BaseStreamInfo> {
42
40
  }
43
41
  }
44
42
 
45
- constructor(
46
- info: T,
47
- stream: ReadableStream<DataStream_Chunk>,
48
- totalByteSize?: number,
49
- outOfBandFailureRejectingFuture?: Future<never, Error>,
50
- ) {
43
+ constructor(info: T, stream: ReadableStream<DataStream_Chunk>, totalByteSize?: number) {
51
44
  this.reader = stream;
52
45
  this.totalByteSize = totalByteSize;
53
46
  this._info = info;
54
47
  this.bytesReceived = 0;
55
- this.outOfBandFailureRejectingFuture = outOfBandFailureRejectingFuture;
56
48
  }
57
49
 
58
50
  protected abstract handleChunkReceived(chunk: DataStream_Chunk): void;
@@ -79,48 +71,44 @@ export class ByteStreamReader extends BaseStreamReader<ByteStreamInfo> {
79
71
 
80
72
  [Symbol.asyncIterator]() {
81
73
  const reader = this.reader.getReader();
82
-
83
- let rejectingSignalFuture = new Future<never, Error>();
84
- let activeSignal: AbortSignal | null = null;
85
- let onAbort: (() => void) | null = null;
86
- if (this.signal) {
87
- const signal = this.signal;
88
- onAbort = () => {
89
- rejectingSignalFuture.reject?.(signal.reason);
90
- };
91
- signal.addEventListener('abort', onAbort);
92
- activeSignal = signal;
93
- }
74
+ // Suppress unhandled rejection on reader.closed — errors are
75
+ // already propagated through reader.read() to the consumer.
76
+ reader.closed.catch(() => {});
94
77
 
95
78
  const cleanup = () => {
96
79
  reader.releaseLock();
97
-
98
- if (activeSignal && onAbort) {
99
- activeSignal.removeEventListener('abort', onAbort);
100
- }
101
-
102
80
  this.signal = undefined;
103
81
  };
104
82
 
105
83
  return {
106
84
  next: async (): Promise<IteratorResult<Uint8Array>> => {
107
85
  try {
108
- const { done, value } = await Promise.race([
109
- reader.read(),
110
- // Rejects if this.signal is aborted
111
- rejectingSignalFuture.promise,
112
- // Rejects if something external says it should, like a participant disconnecting, etc
113
- this.outOfBandFailureRejectingFuture?.promise ??
114
- new Promise<never>(() => {
115
- /* never resolves */
116
- }),
117
- ]);
118
- if (done) {
86
+ const signal = this.signal;
87
+ if (signal?.aborted) {
88
+ throw signal.reason;
89
+ }
90
+ const result = await new Promise<ReadableStreamReadResult<DataStream_Chunk>>(
91
+ (resolve, reject) => {
92
+ if (signal) {
93
+ const onAbort = () => reject(signal.reason);
94
+ signal.addEventListener('abort', onAbort, { once: true });
95
+ reader
96
+ .read()
97
+ .then(resolve, reject)
98
+ .finally(() => {
99
+ signal.removeEventListener('abort', onAbort);
100
+ });
101
+ } else {
102
+ reader.read().then(resolve, reject);
103
+ }
104
+ },
105
+ );
106
+ if (result.done) {
119
107
  this.validateBytesReceived(true);
120
108
  return { done: true, value: undefined as any };
121
109
  } else {
122
- this.handleChunkReceived(value);
123
- return { done: false, value: value.content };
110
+ this.handleChunkReceived(result.value);
111
+ return { done: false, value: result.value.content };
124
112
  }
125
113
  } catch (err) {
126
114
  cleanup();
@@ -175,9 +163,8 @@ export class TextStreamReader extends BaseStreamReader<TextStreamInfo> {
175
163
  info: TextStreamInfo,
176
164
  stream: ReadableStream<DataStream_Chunk>,
177
165
  totalChunkCount?: number,
178
- outOfBandFailureRejectingFuture?: Future<never, Error>,
179
166
  ) {
180
- super(info, stream, totalChunkCount, outOfBandFailureRejectingFuture);
167
+ super(info, stream, totalChunkCount);
181
168
  this.receivedChunks = new Map();
182
169
  }
183
170
 
@@ -211,55 +198,51 @@ export class TextStreamReader extends BaseStreamReader<TextStreamInfo> {
211
198
  */
212
199
  [Symbol.asyncIterator]() {
213
200
  const reader = this.reader.getReader();
201
+ // Suppress unhandled rejection on reader.closed — errors are
202
+ // already propagated through reader.read() to the consumer.
203
+ reader.closed.catch(() => {});
214
204
  const decoder = new TextDecoder('utf-8', { fatal: true });
215
-
216
- let rejectingSignalFuture = new Future<never, Error>();
217
- let activeSignal: AbortSignal | null = null;
218
- let onAbort: (() => void) | null = null;
219
- if (this.signal) {
220
- const signal = this.signal;
221
- onAbort = () => {
222
- rejectingSignalFuture.reject?.(signal.reason);
223
- };
224
- signal.addEventListener('abort', onAbort);
225
- activeSignal = signal;
226
- }
205
+ const signal = this.signal;
227
206
 
228
207
  const cleanup = () => {
229
208
  reader.releaseLock();
230
-
231
- if (activeSignal && onAbort) {
232
- activeSignal.removeEventListener('abort', onAbort);
233
- }
234
-
235
209
  this.signal = undefined;
236
210
  };
237
211
 
238
212
  return {
239
213
  next: async (): Promise<IteratorResult<string>> => {
240
214
  try {
241
- const { done, value } = await Promise.race([
242
- reader.read(),
243
- // Rejects if this.signal is aborted
244
- rejectingSignalFuture.promise,
245
- // Rejects if something external says it should, like a participant disconnecting, etc
246
- this.outOfBandFailureRejectingFuture?.promise ??
247
- new Promise<never>(() => {
248
- /* never resolves */
249
- }),
250
- ]);
251
- if (done) {
215
+ if (signal?.aborted) {
216
+ throw signal.reason;
217
+ }
218
+ const result = await new Promise<ReadableStreamReadResult<DataStream_Chunk>>(
219
+ (resolve, reject) => {
220
+ if (signal) {
221
+ const onAbort = () => reject(signal.reason);
222
+ signal.addEventListener('abort', onAbort, { once: true });
223
+ reader
224
+ .read()
225
+ .then(resolve, reject)
226
+ .finally(() => {
227
+ signal.removeEventListener('abort', onAbort);
228
+ });
229
+ } else {
230
+ reader.read().then(resolve, reject);
231
+ }
232
+ },
233
+ );
234
+ if (result.done) {
252
235
  this.validateBytesReceived(true);
253
236
  return { done: true, value: undefined };
254
237
  } else {
255
- this.handleChunkReceived(value);
238
+ this.handleChunkReceived(result.value);
256
239
 
257
240
  let decodedResult;
258
241
  try {
259
- decodedResult = decoder.decode(value.content);
242
+ decodedResult = decoder.decode(result.value.content);
260
243
  } catch (err) {
261
244
  throw new DataStreamError(
262
- `Cannot decode datastream chunk ${value.chunkIndex} as text: ${err}`,
245
+ `Cannot decode datastream chunk ${result.value.chunkIndex} as text: ${err}`,
263
246
  DataStreamErrorReason.DecodeFailed,
264
247
  );
265
248
  }
@@ -114,7 +114,7 @@ export default class OutgoingDataStreamManager {
114
114
  mimeType: info.mimeType,
115
115
  topic: info.topic,
116
116
  timestamp: numberToBigInt(info.timestamp),
117
- totalLength: numberToBigInt(options?.totalSize),
117
+ totalLength: numberToBigInt(info.size),
118
118
  attributes: info.attributes,
119
119
  contentHeader: {
120
120
  case: 'textHeader',
@@ -240,7 +240,7 @@ export default class OutgoingDataStreamManager {
240
240
  };
241
241
 
242
242
  const header = new DataStream_Header({
243
- totalLength: numberToBigInt(info.size ?? 0),
243
+ totalLength: numberToBigInt(info.size),
244
244
  mimeType: info.mimeType,
245
245
  streamId,
246
246
  topic: info.topic,
@@ -0,0 +1,53 @@
1
+ import type { DataTrackFrame } from './frame';
2
+ import type OutgoingDataTrackManager from './outgoing/OutgoingDataTrackManager';
3
+ import {
4
+ DataTrackSymbol,
5
+ type IDataTrack,
6
+ type ILocalTrack,
7
+ TrackSymbol,
8
+ } from './track-interfaces';
9
+ import type { DataTrackInfo } from './types';
10
+
11
+ export default class LocalDataTrack implements ILocalTrack, IDataTrack {
12
+ readonly trackSymbol = TrackSymbol;
13
+
14
+ readonly isLocal = true;
15
+
16
+ readonly typeSymbol = DataTrackSymbol;
17
+
18
+ info: DataTrackInfo;
19
+
20
+ protected manager: OutgoingDataTrackManager;
21
+
22
+ /** @internal */
23
+ constructor(info: DataTrackInfo, manager: OutgoingDataTrackManager) {
24
+ this.info = info;
25
+ this.manager = manager;
26
+ }
27
+
28
+ /** The raw descriptor from the manager containing the internal state for this local track. */
29
+ protected get descriptor() {
30
+ return this.manager.getDescriptor(this.info.pubHandle);
31
+ }
32
+
33
+ isPublished() {
34
+ return this.descriptor?.type === 'active';
35
+ }
36
+
37
+ /** Try pushing a frame to subscribers of the track.
38
+ *
39
+ * Pushing a frame can fail for several reasons:
40
+ *
41
+ * - The track has been unpublished by the local participant or SFU
42
+ * - The room is no longer connected
43
+ */
44
+ tryPush(payload: DataTrackFrame['payload']) {
45
+ try {
46
+ return this.manager.tryProcessAndSend(this.info.pubHandle, payload);
47
+ } catch (err) {
48
+ // NOTE: wrapping in the bare try/catch like this means that the Throws<...> type doesn't
49
+ // propegate upwards into the public interface.
50
+ throw err;
51
+ }
52
+ }
53
+ }
@@ -0,0 +1,82 @@
1
+ import type Participant from '../participant/Participant';
2
+ import type { DataTrackFrame } from './frame';
3
+ import type IncomingDataTrackManager from './incoming/IncomingDataTrackManager';
4
+ import {
5
+ DataTrackSymbol,
6
+ type IDataTrack,
7
+ type IRemoteTrack,
8
+ TrackSymbol,
9
+ } from './track-interfaces';
10
+ import { type DataTrackInfo } from './types';
11
+
12
+ type RemoteDataTrackOptions = {
13
+ publisherIdentity: Participant['identity'];
14
+ };
15
+
16
+ export type RemoteDataTrackSubscribeOptions = {
17
+ signal?: AbortSignal;
18
+
19
+ /** The number of {@link DataTrackFrame}s to hold in the ReadableStream before disgarding extra
20
+ * frames. Defaults to 4, but this may not be good enough for especially high frequency data. */
21
+ highWaterMark?: number;
22
+ };
23
+
24
+ export default class RemoteDataTrack implements IRemoteTrack, IDataTrack {
25
+ readonly trackSymbol = TrackSymbol;
26
+
27
+ readonly isLocal = false;
28
+
29
+ readonly typeSymbol = DataTrackSymbol;
30
+
31
+ info: DataTrackInfo;
32
+
33
+ publisherIdentity: Participant['identity'];
34
+
35
+ protected manager: IncomingDataTrackManager;
36
+
37
+ /** @internal */
38
+ constructor(
39
+ info: DataTrackInfo,
40
+ manager: IncomingDataTrackManager,
41
+ options: RemoteDataTrackOptions,
42
+ ) {
43
+ this.info = info;
44
+ this.manager = manager;
45
+ this.publisherIdentity = options.publisherIdentity;
46
+ }
47
+
48
+ /** Subscribes to the data track to receive frames.
49
+ *
50
+ * # Returns
51
+ *
52
+ * A stream that yields {@link DataTrackFrame}s as they arrive.
53
+ *
54
+ * # Multiple Subscriptions
55
+ *
56
+ * An application may call `subscribe` more than once to process frames in
57
+ * multiple places. For example, one async task might plot values on a graph
58
+ * while another writes them to a file.
59
+ *
60
+ * Internally, only the first call to `subscribe` communicates with the SFU and
61
+ * allocates the resources required to receive frames. Additional subscriptions
62
+ * reuse the same underlying pipeline and do not trigger additional signaling.
63
+ *
64
+ * Note that newly created subscriptions only receive frames published after
65
+ * the initial subscription is established.
66
+ */
67
+ async subscribe(
68
+ options?: RemoteDataTrackSubscribeOptions,
69
+ ): Promise<ReadableStream<DataTrackFrame>> {
70
+ try {
71
+ const stream = await this.manager.subscribeRequest(
72
+ this.info.sid,
73
+ options?.signal,
74
+ options?.highWaterMark,
75
+ );
76
+ return stream;
77
+ } catch (err) {
78
+ // NOTE: Rethrow errors to break Throws<...> type boundary
79
+ throw err;
80
+ }
81
+ }
82
+ }
@@ -1,6 +1,6 @@
1
1
  /* eslint-disable @typescript-eslint/no-unused-vars */
2
2
  import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
3
- import { DataTrackDepacketizer } from './depacketizer';
3
+ import DataTrackDepacketizer from './depacketizer';
4
4
  import { DataTrackHandle } from './handle';
5
5
  import { DataTrackPacket, DataTrackPacketHeader, FrameMarker } from './packet';
6
6
  import { DataTrackTimestamp, WrapAroundUnsignedInt } from './utils';
@@ -1,5 +1,5 @@
1
+ import { type Throws } from '@livekit/throws-transformer/throws';
1
2
  import { LoggerNames, getLogger } from '../../logger';
2
- import { type Throws } from '../../utils/throws';
3
3
  import { LivekitReasonedError } from '../errors';
4
4
  import { type DataTrackFrame } from './frame';
5
5
  import { DataTrackPacket, FrameMarker } from './packet';
@@ -21,7 +21,7 @@ type PartialFrame = {
21
21
 
22
22
  /** An error indicating a frame was dropped. */
23
23
  export class DataTrackDepacketizerDropError<
24
- Reason extends DataTrackDepacketizerDropReason,
24
+ Reason extends DataTrackDepacketizerDropReason = DataTrackDepacketizerDropReason,
25
25
  > extends LivekitReasonedError<Reason> {
26
26
  readonly name = 'DataTrackDepacketizerDropError';
27
27
 
@@ -85,7 +85,7 @@ type PushOptions = {
85
85
  errorOnPartialFrames: boolean;
86
86
  };
87
87
 
88
- export class DataTrackDepacketizer {
88
+ export default class DataTrackDepacketizer {
89
89
  /** Maximum number of packets to buffer per frame before dropping. */
90
90
  static MAX_BUFFER_PACKETS = 128;
91
91
 
@@ -99,13 +99,7 @@ export class DataTrackDepacketizer {
99
99
  push(
100
100
  packet: DataTrackPacket,
101
101
  options?: PushOptions,
102
- ): Throws<
103
- DataTrackFrame | null,
104
- | DataTrackDepacketizerDropError<DataTrackDepacketizerDropReason.Interrupted>
105
- | DataTrackDepacketizerDropError<DataTrackDepacketizerDropReason.BufferFull>
106
- | DataTrackDepacketizerDropError<DataTrackDepacketizerDropReason.UnknownFrame>
107
- | DataTrackDepacketizerDropError<DataTrackDepacketizerDropReason.Incomplete>
108
- > {
102
+ ): Throws<DataTrackFrame | null, DataTrackDepacketizerDropError> {
109
103
  switch (packet.header.marker) {
110
104
  case FrameMarker.Single:
111
105
  return this.frameFromSingle(packet, options);
@@ -191,13 +185,7 @@ export class DataTrackDepacketizer {
191
185
  /** Push to the existing partial frame. */
192
186
  private pushToPartial(
193
187
  packet: DataTrackPacket,
194
- ): Throws<
195
- DataTrackFrame | null,
196
- | DataTrackDepacketizerDropError<DataTrackDepacketizerDropReason.Interrupted>
197
- | DataTrackDepacketizerDropError<DataTrackDepacketizerDropReason.UnknownFrame>
198
- | DataTrackDepacketizerDropError<DataTrackDepacketizerDropReason.BufferFull>
199
- | DataTrackDepacketizerDropError<DataTrackDepacketizerDropReason.Incomplete>
200
- > {
188
+ ): Throws<DataTrackFrame | null, DataTrackDepacketizerDropError> {
201
189
  if (packet.header.marker !== FrameMarker.Inter && packet.header.marker !== FrameMarker.Final) {
202
190
  // @throws-transformer ignore - this should be treated as a "panic" and not be caught
203
191
  throw new Error(
@@ -0,0 +1,15 @@
1
+ export type EncryptedPayload = {
2
+ payload: Uint8Array;
3
+ iv: Uint8Array; // NOTE: should be 12 bytes long
4
+ keyIndex: number;
5
+ };
6
+
7
+ export type EncryptionProvider = {
8
+ // FIXME: add in explicit `Throws<..., EncryptionError>`?
9
+ encrypt(payload: Uint8Array): EncryptedPayload;
10
+ };
11
+
12
+ export type DecryptionProvider = {
13
+ // FIXME: add in explicit `Throws<..., DecryptionError>`?
14
+ decrypt(payload: EncryptedPayload, senderIdentity: string): Uint8Array;
15
+ };
@@ -1,5 +1,5 @@
1
1
  import { DataTrackExtensions } from './packet/extensions';
2
- import { DataTrackPacketizer } from './packetizer';
2
+ import DataTrackPacketizer from './packetizer';
3
3
 
4
4
  /** A pair of payload bytes and packet extensions which can be fed into a {@link DataTrackPacketizer}. */
5
5
  export type DataTrackFrame = {
@@ -4,7 +4,7 @@ import { DataTrackHandle } from './handle';
4
4
 
5
5
  describe('DataTrackHandle', () => {
6
6
  it('should parse handle raw inputs', () => {
7
- expect(DataTrackHandle.fromNumber(3).value).toEqual(3);
7
+ expect(DataTrackHandle.fromNumber(3)).toEqual(3);
8
8
  expect(() => DataTrackHandle.fromNumber(0)).toThrow('0x0 is a reserved value');
9
9
  expect(() => DataTrackHandle.fromNumber(9999999)).toThrow(
10
10
  'Value too large to be a valid track handle',
@@ -1,4 +1,4 @@
1
- import { type Throws } from '../../utils/throws';
1
+ import { type Throws } from '@livekit/throws-transformer/throws';
2
2
  import { LivekitReasonedError } from '../errors';
3
3
  import { U16_MAX_SIZE } from './utils';
4
4
 
@@ -41,40 +41,29 @@ export class DataTrackHandleError<
41
41
  }
42
42
  }
43
43
 
44
- export class DataTrackHandle {
45
- public value: number;
46
-
47
- static fromNumber(
48
- raw: number,
49
- ): Throws<
50
- DataTrackHandle,
51
- | DataTrackHandleError<DataTrackHandleErrorReason.TooLarge>
52
- | DataTrackHandleError<DataTrackHandleErrorReason.Reserved>
53
- > {
44
+ export type DataTrackHandle = number;
45
+ export const DataTrackHandle = {
46
+ fromNumber(raw: number): Throws<DataTrackHandle, DataTrackHandleError> {
54
47
  if (raw === 0) {
55
48
  throw DataTrackHandleError.reserved(raw);
56
49
  }
57
50
  if (raw > U16_MAX_SIZE) {
58
51
  throw DataTrackHandleError.tooLarge();
59
52
  }
60
- return new DataTrackHandle(raw);
61
- }
62
-
63
- constructor(raw: number) {
64
- this.value = raw;
65
- }
66
- }
53
+ return raw;
54
+ },
55
+ };
67
56
 
68
57
  /** Manage allocating new handles which don't conflict over the lifetime of the client. */
69
58
  export class DataTrackHandleAllocator {
70
- static value = 0;
59
+ value = 0;
71
60
 
72
61
  /** Returns a unique track handle for the next publication, if one can be obtained. */
73
- static get(): DataTrackHandle | null {
62
+ get(): DataTrackHandle | null {
74
63
  this.value += 1;
75
64
  if (this.value > U16_MAX_SIZE) {
76
65
  return null;
77
66
  }
78
- return new DataTrackHandle(this.value);
67
+ return this.value;
79
68
  }
80
69
  }