livekit-client 2.15.4 → 2.15.5

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 (62) 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 +329 -124
  4. package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
  5. package/dist/livekit-client.esm.mjs +834 -541
  6. package/dist/livekit-client.esm.mjs.map +1 -1
  7. package/dist/livekit-client.umd.js +1 -1
  8. package/dist/livekit-client.umd.js.map +1 -1
  9. package/dist/src/e2ee/worker/FrameCryptor.d.ts +0 -46
  10. package/dist/src/e2ee/worker/FrameCryptor.d.ts.map +1 -1
  11. package/dist/src/e2ee/worker/naluUtils.d.ts +27 -0
  12. package/dist/src/e2ee/worker/naluUtils.d.ts.map +1 -0
  13. package/dist/src/index.d.ts +2 -2
  14. package/dist/src/index.d.ts.map +1 -1
  15. package/dist/src/room/Room.d.ts +6 -10
  16. package/dist/src/room/Room.d.ts.map +1 -1
  17. package/dist/src/room/data-stream/incoming/IncomingDataStreamManager.d.ts +20 -0
  18. package/dist/src/room/data-stream/incoming/IncomingDataStreamManager.d.ts.map +1 -0
  19. package/dist/src/room/{StreamReader.d.ts → data-stream/incoming/StreamReader.d.ts} +32 -6
  20. package/dist/src/room/data-stream/incoming/StreamReader.d.ts.map +1 -0
  21. package/dist/src/room/data-stream/outgoing/OutgoingDataStreamManager.d.ts +27 -0
  22. package/dist/src/room/data-stream/outgoing/OutgoingDataStreamManager.d.ts.map +1 -0
  23. package/dist/src/room/{StreamWriter.d.ts → data-stream/outgoing/StreamWriter.d.ts} +1 -1
  24. package/dist/src/room/data-stream/outgoing/StreamWriter.d.ts.map +1 -0
  25. package/dist/src/room/errors.d.ts +13 -0
  26. package/dist/src/room/errors.d.ts.map +1 -1
  27. package/dist/src/room/participant/LocalParticipant.d.ts +32 -19
  28. package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
  29. package/dist/src/room/track/LocalTrack.d.ts +7 -2
  30. package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
  31. package/dist/src/room/types.d.ts +17 -1
  32. package/dist/src/room/types.d.ts.map +1 -1
  33. package/dist/ts4.2/src/e2ee/worker/FrameCryptor.d.ts +0 -46
  34. package/dist/ts4.2/src/e2ee/worker/naluUtils.d.ts +27 -0
  35. package/dist/ts4.2/src/index.d.ts +2 -2
  36. package/dist/ts4.2/src/room/Room.d.ts +6 -10
  37. package/dist/ts4.2/src/room/data-stream/incoming/IncomingDataStreamManager.d.ts +20 -0
  38. package/dist/ts4.2/src/room/{StreamReader.d.ts → data-stream/incoming/StreamReader.d.ts} +32 -6
  39. package/dist/ts4.2/src/room/data-stream/outgoing/OutgoingDataStreamManager.d.ts +27 -0
  40. package/dist/ts4.2/src/room/{StreamWriter.d.ts → data-stream/outgoing/StreamWriter.d.ts} +1 -1
  41. package/dist/ts4.2/src/room/errors.d.ts +13 -0
  42. package/dist/ts4.2/src/room/participant/LocalParticipant.d.ts +32 -19
  43. package/dist/ts4.2/src/room/track/LocalTrack.d.ts +7 -2
  44. package/dist/ts4.2/src/room/types.d.ts +17 -1
  45. package/package.json +1 -1
  46. package/src/e2ee/worker/FrameCryptor.ts +48 -139
  47. package/src/e2ee/worker/naluUtils.ts +328 -0
  48. package/src/index.ts +2 -2
  49. package/src/room/Room.ts +93 -206
  50. package/src/room/data-stream/incoming/IncomingDataStreamManager.ts +247 -0
  51. package/src/room/data-stream/incoming/StreamReader.ts +317 -0
  52. package/src/room/data-stream/outgoing/OutgoingDataStreamManager.ts +316 -0
  53. package/src/room/{StreamWriter.ts → data-stream/outgoing/StreamWriter.ts} +1 -1
  54. package/src/room/errors.ts +34 -0
  55. package/src/room/participant/LocalParticipant.ts +39 -295
  56. package/src/room/track/LocalAudioTrack.ts +2 -2
  57. package/src/room/track/LocalTrack.ts +65 -48
  58. package/src/room/types.ts +22 -1
  59. package/src/room/utils.ts +2 -2
  60. package/dist/src/room/StreamReader.d.ts.map +0 -1
  61. package/dist/src/room/StreamWriter.d.ts.map +0 -1
  62. package/src/room/StreamReader.ts +0 -170
@@ -11,6 +11,7 @@ import type { DecodeRatchetOptions, KeyProviderOptions, KeySet, RatchetResult }
11
11
  import { deriveKeys, isVideoFrame, needsRbspUnescaping, parseRbsp, writeRbsp } from '../utils';
12
12
  import type { ParticipantKeyHandler } from './ParticipantKeyHandler';
13
13
  import { SifGuard } from './SifGuard';
14
+ import { processNALUsForEncryption } from './naluUtils';
14
15
 
15
16
  export const encryptionEnabledMap: Map<string, boolean> = new Map();
16
17
 
@@ -304,7 +305,7 @@ export class FrameCryptor extends BaseFrameCryptor {
304
305
  newDataWithoutHeader.set(new Uint8Array(iv), cipherText.byteLength); // append IV.
305
306
  newDataWithoutHeader.set(frameTrailer, cipherText.byteLength + iv.byteLength); // append frame trailer.
306
307
 
307
- if (frameInfo.isH264) {
308
+ if (frameInfo.requiresNALUProcessing) {
308
309
  newDataWithoutHeader = writeRbsp(newDataWithoutHeader);
309
310
  }
310
311
 
@@ -441,7 +442,7 @@ export class FrameCryptor extends BaseFrameCryptor {
441
442
  frameHeader.length,
442
443
  encodedFrame.data.byteLength - frameHeader.length,
443
444
  );
444
- if (frameInfo.isH264 && needsRbspUnescaping(encryptedData)) {
445
+ if (frameInfo.requiresNALUProcessing && needsRbspUnescaping(encryptedData)) {
445
446
  encryptedData = parseRbsp(encryptedData);
446
447
  const newUint8 = new Uint8Array(frameHeader.byteLength + encryptedData.byteLength);
447
448
  newUint8.set(frameHeader);
@@ -584,66 +585,58 @@ export class FrameCryptor extends BaseFrameCryptor {
584
585
 
585
586
  private getUnencryptedBytes(frame: RTCEncodedVideoFrame | RTCEncodedAudioFrame): {
586
587
  unencryptedBytes: number;
587
- isH264: boolean;
588
+ requiresNALUProcessing: boolean;
588
589
  } {
589
- var frameInfo = { unencryptedBytes: 0, isH264: false };
590
- if (isVideoFrame(frame)) {
591
- let detectedCodec = this.getVideoCodec(frame) ?? this.videoCodec;
592
- if (detectedCodec !== this.detectedCodec) {
593
- workerLogger.debug('detected different codec', {
594
- detectedCodec,
595
- oldCodec: this.detectedCodec,
596
- ...this.logContext,
597
- });
598
- this.detectedCodec = detectedCodec;
599
- }
600
-
601
- if (detectedCodec === 'av1') {
602
- throw new Error(`${detectedCodec} is not yet supported for end to end encryption`);
603
- }
590
+ // Handle audio frames
591
+ if (!isVideoFrame(frame)) {
592
+ return { unencryptedBytes: UNENCRYPTED_BYTES.audio, requiresNALUProcessing: false };
593
+ }
604
594
 
605
- if (detectedCodec === 'vp8') {
606
- frameInfo.unencryptedBytes = UNENCRYPTED_BYTES[frame.type];
607
- } else if (detectedCodec === 'vp9') {
608
- frameInfo.unencryptedBytes = 0;
609
- return frameInfo;
610
- }
595
+ // Detect and track codec changes
596
+ const detectedCodec = this.getVideoCodec(frame) ?? this.videoCodec;
597
+ if (detectedCodec !== this.detectedCodec) {
598
+ workerLogger.debug('detected different codec', {
599
+ detectedCodec,
600
+ oldCodec: this.detectedCodec,
601
+ ...this.logContext,
602
+ });
603
+ this.detectedCodec = detectedCodec;
604
+ }
611
605
 
612
- const data = new Uint8Array(frame.data);
613
- try {
614
- const naluIndices = findNALUIndices(data);
606
+ // Check for unsupported codecs
607
+ if (detectedCodec === 'av1') {
608
+ throw new Error(`${detectedCodec} is not yet supported for end to end encryption`);
609
+ }
615
610
 
616
- // if the detected codec is undefined we test whether it _looks_ like a h264 frame as a best guess
617
- frameInfo.isH264 =
618
- detectedCodec === 'h264' ||
619
- naluIndices.some((naluIndex) =>
620
- [NALUType.SLICE_IDR, NALUType.SLICE_NON_IDR].includes(parseNALUType(data[naluIndex])),
621
- );
611
+ // Handle VP8/VP9 codecs (no NALU processing needed)
612
+ if (detectedCodec === 'vp8') {
613
+ return { unencryptedBytes: UNENCRYPTED_BYTES[frame.type], requiresNALUProcessing: false };
614
+ }
615
+ if (detectedCodec === 'vp9') {
616
+ return { unencryptedBytes: 0, requiresNALUProcessing: false };
617
+ }
622
618
 
623
- if (frameInfo.isH264) {
624
- for (const index of naluIndices) {
625
- let type = parseNALUType(data[index]);
626
- switch (type) {
627
- case NALUType.SLICE_IDR:
628
- case NALUType.SLICE_NON_IDR:
629
- frameInfo.unencryptedBytes = index + 2;
630
- return frameInfo;
631
- default:
632
- break;
633
- }
634
- }
635
- throw new TypeError('Could not find NALU');
636
- }
637
- } catch (e) {
638
- // no op, we just continue and fallback to vp8
619
+ // Try NALU processing for H.264/H.265 codecs
620
+ try {
621
+ const knownCodec =
622
+ detectedCodec === 'h264' || detectedCodec === 'h265' ? detectedCodec : undefined;
623
+ const naluResult = processNALUsForEncryption(new Uint8Array(frame.data), knownCodec);
624
+
625
+ if (naluResult.requiresNALUProcessing) {
626
+ return {
627
+ unencryptedBytes: naluResult.unencryptedBytes,
628
+ requiresNALUProcessing: true,
629
+ };
639
630
  }
640
-
641
- frameInfo.unencryptedBytes = UNENCRYPTED_BYTES[frame.type];
642
- return frameInfo;
643
- } else {
644
- frameInfo.unencryptedBytes = UNENCRYPTED_BYTES.audio;
645
- return frameInfo;
631
+ } catch (e) {
632
+ workerLogger.debug('NALU processing failed, falling back to VP8 handling', {
633
+ error: e,
634
+ ...this.logContext,
635
+ });
646
636
  }
637
+
638
+ // Fallback to VP8 handling
639
+ return { unencryptedBytes: UNENCRYPTED_BYTES[frame.type], requiresNALUProcessing: false };
647
640
  }
648
641
 
649
642
  /**
@@ -659,90 +652,6 @@ export class FrameCryptor extends BaseFrameCryptor {
659
652
  }
660
653
  }
661
654
 
662
- /**
663
- * Slice the NALUs present in the supplied buffer, assuming it is already byte-aligned
664
- * code adapted from https://github.com/medooze/h264-frame-parser/blob/main/lib/NalUnits.ts to return indices only
665
- */
666
- export function findNALUIndices(stream: Uint8Array): number[] {
667
- const result: number[] = [];
668
- let start = 0,
669
- pos = 0,
670
- searchLength = stream.length - 2;
671
- while (pos < searchLength) {
672
- // skip until end of current NALU
673
- while (
674
- pos < searchLength &&
675
- !(stream[pos] === 0 && stream[pos + 1] === 0 && stream[pos + 2] === 1)
676
- )
677
- pos++;
678
- if (pos >= searchLength) pos = stream.length;
679
- // remove trailing zeros from current NALU
680
- let end = pos;
681
- while (end > start && stream[end - 1] === 0) end--;
682
- // save current NALU
683
- if (start === 0) {
684
- if (end !== start) throw TypeError('byte stream contains leading data');
685
- } else {
686
- result.push(start);
687
- }
688
- // begin new NALU
689
- start = pos = pos + 3;
690
- }
691
- return result;
692
- }
693
-
694
- export function parseNALUType(startByte: number): NALUType {
695
- return startByte & kNaluTypeMask;
696
- }
697
-
698
- const kNaluTypeMask = 0x1f;
699
-
700
- export enum NALUType {
701
- /** Coded slice of a non-IDR picture */
702
- SLICE_NON_IDR = 1,
703
- /** Coded slice data partition A */
704
- SLICE_PARTITION_A = 2,
705
- /** Coded slice data partition B */
706
- SLICE_PARTITION_B = 3,
707
- /** Coded slice data partition C */
708
- SLICE_PARTITION_C = 4,
709
- /** Coded slice of an IDR picture */
710
- SLICE_IDR = 5,
711
- /** Supplemental enhancement information */
712
- SEI = 6,
713
- /** Sequence parameter set */
714
- SPS = 7,
715
- /** Picture parameter set */
716
- PPS = 8,
717
- /** Access unit delimiter */
718
- AUD = 9,
719
- /** End of sequence */
720
- END_SEQ = 10,
721
- /** End of stream */
722
- END_STREAM = 11,
723
- /** Filler data */
724
- FILLER_DATA = 12,
725
- /** Sequence parameter set extension */
726
- SPS_EXT = 13,
727
- /** Prefix NAL unit */
728
- PREFIX_NALU = 14,
729
- /** Subset sequence parameter set */
730
- SUBSET_SPS = 15,
731
- /** Depth parameter set */
732
- DPS = 16,
733
-
734
- // 17, 18 reserved
735
-
736
- /** Coded slice of an auxiliary coded picture without partitioning */
737
- SLICE_AUX = 19,
738
- /** Coded slice extension */
739
- SLICE_EXT = 20,
740
- /** Coded slice extension for a depth view component or a 3D-AVC texture view component */
741
- SLICE_LAYER_EXT = 21,
742
-
743
- // 22, 23 reserved
744
- }
745
-
746
655
  /**
747
656
  * we use a magic frame trailer to detect whether a frame is injected
748
657
  * by the livekit server and thus to be treated as unencrypted
@@ -0,0 +1,328 @@
1
+ /**
2
+ * NALU (Network Abstraction Layer Unit) utilities for H.264 and H.265 video processing
3
+ * Contains functions for parsing and working with NALUs in video frames
4
+ */
5
+
6
+ /**
7
+ * Mask for extracting NALU type from H.264 header byte
8
+ */
9
+ const kH264NaluTypeMask = 0x1f;
10
+
11
+ /**
12
+ * H.264 NALU types according to RFC 6184
13
+ */
14
+ enum H264NALUType {
15
+ /** Coded slice of a non-IDR picture */
16
+ SLICE_NON_IDR = 1,
17
+ /** Coded slice data partition A */
18
+ SLICE_PARTITION_A = 2,
19
+ /** Coded slice data partition B */
20
+ SLICE_PARTITION_B = 3,
21
+ /** Coded slice data partition C */
22
+ SLICE_PARTITION_C = 4,
23
+ /** Coded slice of an IDR picture */
24
+ SLICE_IDR = 5,
25
+ /** Supplemental enhancement information */
26
+ SEI = 6,
27
+ /** Sequence parameter set */
28
+ SPS = 7,
29
+ /** Picture parameter set */
30
+ PPS = 8,
31
+ /** Access unit delimiter */
32
+ AUD = 9,
33
+ /** End of sequence */
34
+ END_SEQ = 10,
35
+ /** End of stream */
36
+ END_STREAM = 11,
37
+ /** Filler data */
38
+ FILLER_DATA = 12,
39
+ /** Sequence parameter set extension */
40
+ SPS_EXT = 13,
41
+ /** Prefix NAL unit */
42
+ PREFIX_NALU = 14,
43
+ /** Subset sequence parameter set */
44
+ SUBSET_SPS = 15,
45
+ /** Depth parameter set */
46
+ DPS = 16,
47
+
48
+ // 17, 18 reserved
49
+
50
+ /** Coded slice of an auxiliary coded picture without partitioning */
51
+ SLICE_AUX = 19,
52
+ /** Coded slice extension */
53
+ SLICE_EXT = 20,
54
+ /** Coded slice extension for a depth view component or a 3D-AVC texture view component */
55
+ SLICE_LAYER_EXT = 21,
56
+
57
+ // 22, 23 reserved
58
+ }
59
+
60
+ /**
61
+ * H.265/HEVC NALU types according to ITU-T H.265
62
+ */
63
+ enum H265NALUType {
64
+ /** Coded slice segment of a non-TSA, non-STSA trailing picture */
65
+ TRAIL_N = 0,
66
+ /** Coded slice segment of a non-TSA, non-STSA trailing picture */
67
+ TRAIL_R = 1,
68
+ /** Coded slice segment of a TSA picture */
69
+ TSA_N = 2,
70
+ /** Coded slice segment of a TSA picture */
71
+ TSA_R = 3,
72
+ /** Coded slice segment of an STSA picture */
73
+ STSA_N = 4,
74
+ /** Coded slice segment of an STSA picture */
75
+ STSA_R = 5,
76
+ /** Coded slice segment of a RADL picture */
77
+ RADL_N = 6,
78
+ /** Coded slice segment of a RADL picture */
79
+ RADL_R = 7,
80
+ /** Coded slice segment of a RASL picture */
81
+ RASL_N = 8,
82
+ /** Coded slice segment of a RASL picture */
83
+ RASL_R = 9,
84
+
85
+ // 10-15 reserved
86
+
87
+ /** Coded slice segment of a BLA picture */
88
+ BLA_W_LP = 16,
89
+ /** Coded slice segment of a BLA picture */
90
+ BLA_W_RADL = 17,
91
+ /** Coded slice segment of a BLA picture */
92
+ BLA_N_LP = 18,
93
+ /** Coded slice segment of an IDR picture */
94
+ IDR_W_RADL = 19,
95
+ /** Coded slice segment of an IDR picture */
96
+ IDR_N_LP = 20,
97
+ /** Coded slice segment of a CRA picture */
98
+ CRA_NUT = 21,
99
+
100
+ // 22-31 reserved
101
+
102
+ /** Video parameter set */
103
+ VPS_NUT = 32,
104
+ /** Sequence parameter set */
105
+ SPS_NUT = 33,
106
+ /** Picture parameter set */
107
+ PPS_NUT = 34,
108
+ /** Access unit delimiter */
109
+ AUD_NUT = 35,
110
+ /** End of sequence */
111
+ EOS_NUT = 36,
112
+ /** End of bitstream */
113
+ EOB_NUT = 37,
114
+ /** Filler data */
115
+ FD_NUT = 38,
116
+ /** Supplemental enhancement information */
117
+ PREFIX_SEI_NUT = 39,
118
+ /** Supplemental enhancement information */
119
+ SUFFIX_SEI_NUT = 40,
120
+
121
+ // 41-47 reserved
122
+ // 48-63 unspecified
123
+ }
124
+
125
+ /**
126
+ * Parse H.264 NALU type from the first byte of a NALU
127
+ * @param startByte First byte of the NALU
128
+ * @returns H.264 NALU type
129
+ */
130
+ function parseH264NALUType(startByte: number): H264NALUType {
131
+ return startByte & kH264NaluTypeMask;
132
+ }
133
+
134
+ /**
135
+ * Parse H.265 NALU type from the first byte of a NALU
136
+ * @param firstByte First byte of the NALU
137
+ * @returns H.265 NALU type
138
+ */
139
+ function parseH265NALUType(firstByte: number): H265NALUType {
140
+ // In H.265, NALU type is in bits 1-6 (shifted right by 1)
141
+ return (firstByte >> 1) & 0x3f;
142
+ }
143
+
144
+ /**
145
+ * Check if H.264 NALU type is a slice (IDR or non-IDR)
146
+ * @param naluType H.264 NALU type
147
+ * @returns True if the NALU is a slice
148
+ */
149
+ function isH264SliceNALU(naluType: H264NALUType): boolean {
150
+ return naluType === H264NALUType.SLICE_IDR || naluType === H264NALUType.SLICE_NON_IDR;
151
+ }
152
+
153
+ /**
154
+ * Check if H.265 NALU type is a slice
155
+ * @param naluType H.265 NALU type
156
+ * @returns True if the NALU is a slice
157
+ */
158
+ function isH265SliceNALU(naluType: H265NALUType): boolean {
159
+ return (
160
+ // VCL NALUs (Video Coding Layer) - slice segments
161
+ naluType === H265NALUType.TRAIL_N ||
162
+ naluType === H265NALUType.TRAIL_R ||
163
+ naluType === H265NALUType.TSA_N ||
164
+ naluType === H265NALUType.TSA_R ||
165
+ naluType === H265NALUType.STSA_N ||
166
+ naluType === H265NALUType.STSA_R ||
167
+ naluType === H265NALUType.RADL_N ||
168
+ naluType === H265NALUType.RADL_R ||
169
+ naluType === H265NALUType.RASL_N ||
170
+ naluType === H265NALUType.RASL_R ||
171
+ naluType === H265NALUType.BLA_W_LP ||
172
+ naluType === H265NALUType.BLA_W_RADL ||
173
+ naluType === H265NALUType.BLA_N_LP ||
174
+ naluType === H265NALUType.IDR_W_RADL ||
175
+ naluType === H265NALUType.IDR_N_LP ||
176
+ naluType === H265NALUType.CRA_NUT
177
+ );
178
+ }
179
+
180
+ /**
181
+ * Detected codec type from NALU analysis
182
+ */
183
+ export type DetectedCodec = 'h264' | 'h265' | 'unknown';
184
+
185
+ /**
186
+ * Result of NALU processing for frame encryption
187
+ */
188
+ export interface NALUProcessingResult {
189
+ /** Number of unencrypted bytes at the start of the frame */
190
+ unencryptedBytes: number;
191
+ /** Detected codec type */
192
+ detectedCodec: DetectedCodec;
193
+ /** Whether this frame requires NALU processing */
194
+ requiresNALUProcessing: boolean;
195
+ }
196
+
197
+ /**
198
+ * Detect codec type by examining NALU types in the data
199
+ * @param data Frame data
200
+ * @param naluIndices Indices where NALUs start
201
+ * @returns Detected codec type
202
+ */
203
+ function detectCodecFromNALUs(data: Uint8Array, naluIndices: number[]): DetectedCodec {
204
+ for (const naluIndex of naluIndices) {
205
+ if (isH264SliceNALU(parseH264NALUType(data[naluIndex]))) return 'h264';
206
+ if (isH265SliceNALU(parseH265NALUType(data[naluIndex]))) return 'h265';
207
+ }
208
+ return 'unknown';
209
+ }
210
+
211
+ /**
212
+ * Find the first slice NALU and return the number of unencrypted bytes
213
+ * @param data Frame data
214
+ * @param naluIndices Indices where NALUs start
215
+ * @param codec Codec type to use for parsing
216
+ * @returns Number of unencrypted bytes (index + 2) or null if no slice found
217
+ */
218
+ function findSliceNALUUnencryptedBytes(
219
+ data: Uint8Array,
220
+ naluIndices: number[],
221
+ codec: 'h264' | 'h265',
222
+ ): number | null {
223
+ for (const index of naluIndices) {
224
+ if (codec === 'h265') {
225
+ const type = parseH265NALUType(data[index]);
226
+ if (isH265SliceNALU(type)) {
227
+ return index + 2;
228
+ }
229
+ } else {
230
+ const type = parseH264NALUType(data[index]);
231
+ if (isH264SliceNALU(type)) {
232
+ return index + 2;
233
+ }
234
+ }
235
+ }
236
+ return null;
237
+ }
238
+
239
+ /**
240
+ * Find all NALU start indices in a byte stream
241
+ * Supports both H.264 and H.265 with 3-byte and 4-byte start codes
242
+ *
243
+ * This function slices the NALUs present in the supplied buffer, assuming it is already byte-aligned.
244
+ * Code adapted from https://github.com/medooze/h264-frame-parser/blob/main/lib/NalUnits.ts to return indices only
245
+ *
246
+ * @param stream Byte stream containing NALUs
247
+ * @returns Array of indices where NALUs start (after the start code)
248
+ */
249
+ function findNALUIndices(stream: Uint8Array): number[] {
250
+ const result: number[] = [];
251
+ let start = 0,
252
+ pos = 0,
253
+ searchLength = stream.length - 3; // Changed to -3 to handle 4-byte start codes
254
+
255
+ while (pos < searchLength) {
256
+ // skip until end of current NALU - check for both 3-byte and 4-byte start codes
257
+ while (pos < searchLength) {
258
+ // Check for 4-byte start code: 0x00 0x00 0x00 0x01
259
+ if (
260
+ pos < searchLength - 1 &&
261
+ stream[pos] === 0 &&
262
+ stream[pos + 1] === 0 &&
263
+ stream[pos + 2] === 0 &&
264
+ stream[pos + 3] === 1
265
+ ) {
266
+ break;
267
+ }
268
+ // Check for 3-byte start code: 0x00 0x00 0x01
269
+ if (stream[pos] === 0 && stream[pos + 1] === 0 && stream[pos + 2] === 1) {
270
+ break;
271
+ }
272
+ pos++;
273
+ }
274
+
275
+ if (pos >= searchLength) pos = stream.length;
276
+
277
+ // remove trailing zeros from current NALU
278
+ let end = pos;
279
+ while (end > start && stream[end - 1] === 0) end--;
280
+
281
+ // save current NALU
282
+ if (start === 0) {
283
+ if (end !== start) throw TypeError('byte stream contains leading data');
284
+ } else {
285
+ result.push(start);
286
+ }
287
+
288
+ // begin new NALU - determine start code length
289
+ let startCodeLength = 3;
290
+ if (
291
+ pos < stream.length - 3 &&
292
+ stream[pos] === 0 &&
293
+ stream[pos + 1] === 0 &&
294
+ stream[pos + 2] === 0 &&
295
+ stream[pos + 3] === 1
296
+ ) {
297
+ startCodeLength = 4;
298
+ }
299
+
300
+ start = pos = pos + startCodeLength;
301
+ }
302
+ return result;
303
+ }
304
+
305
+ /**
306
+ * Process NALU data for frame encryption, detecting codec and finding unencrypted bytes
307
+ * @param data Frame data
308
+ * @param knownCodec Known codec from other sources (optional)
309
+ * @returns NALU processing result
310
+ */
311
+ export function processNALUsForEncryption(
312
+ data: Uint8Array,
313
+ knownCodec?: 'h264' | 'h265',
314
+ ): NALUProcessingResult {
315
+ const naluIndices = findNALUIndices(data);
316
+ const detectedCodec = knownCodec ?? detectCodecFromNALUs(data, naluIndices);
317
+
318
+ if (detectedCodec === 'unknown') {
319
+ return { unencryptedBytes: 0, detectedCodec, requiresNALUProcessing: false };
320
+ }
321
+
322
+ const unencryptedBytes = findSliceNALUUnencryptedBytes(data, naluIndices, detectedCodec);
323
+ if (unencryptedBytes === null) {
324
+ throw new TypeError('Could not find NALU');
325
+ }
326
+
327
+ return { unencryptedBytes, detectedCodec, requiresNALUProcessing: true };
328
+ }
package/src/index.ts CHANGED
@@ -62,8 +62,8 @@ export { facingModeFromDeviceLabel, facingModeFromLocalTrack } from './room/trac
62
62
  export * from './room/track/options';
63
63
  export * from './room/track/processor/types';
64
64
  export * from './room/track/types';
65
- export type * from './room/StreamReader';
66
- export type * from './room/StreamWriter';
65
+ export type * from './room/data-stream/incoming/StreamReader';
66
+ export type * from './room/data-stream/outgoing/StreamWriter';
67
67
  export type {
68
68
  DataPublishOptions,
69
69
  SimulationScenario,