livekit-client 2.15.3 → 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.
- package/README.md +2 -2
- package/dist/livekit-client.e2ee.worker.js +1 -1
- package/dist/livekit-client.e2ee.worker.js.map +1 -1
- package/dist/livekit-client.e2ee.worker.mjs +329 -124
- package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
- package/dist/livekit-client.esm.mjs +868 -548
- package/dist/livekit-client.esm.mjs.map +1 -1
- package/dist/livekit-client.umd.js +1 -1
- package/dist/livekit-client.umd.js.map +1 -1
- package/dist/src/api/SignalClient.d.ts.map +1 -1
- package/dist/src/e2ee/worker/FrameCryptor.d.ts +0 -46
- package/dist/src/e2ee/worker/FrameCryptor.d.ts.map +1 -1
- package/dist/src/e2ee/worker/naluUtils.d.ts +27 -0
- package/dist/src/e2ee/worker/naluUtils.d.ts.map +1 -0
- package/dist/src/index.d.ts +2 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/room/Room.d.ts +6 -10
- package/dist/src/room/Room.d.ts.map +1 -1
- package/dist/src/room/data-stream/incoming/IncomingDataStreamManager.d.ts +20 -0
- package/dist/src/room/data-stream/incoming/IncomingDataStreamManager.d.ts.map +1 -0
- package/dist/src/room/{StreamReader.d.ts → data-stream/incoming/StreamReader.d.ts} +32 -6
- package/dist/src/room/data-stream/incoming/StreamReader.d.ts.map +1 -0
- package/dist/src/room/data-stream/outgoing/OutgoingDataStreamManager.d.ts +27 -0
- package/dist/src/room/data-stream/outgoing/OutgoingDataStreamManager.d.ts.map +1 -0
- package/dist/src/room/{StreamWriter.d.ts → data-stream/outgoing/StreamWriter.d.ts} +1 -1
- package/dist/src/room/data-stream/outgoing/StreamWriter.d.ts.map +1 -0
- package/dist/src/room/errors.d.ts +13 -0
- package/dist/src/room/errors.d.ts.map +1 -1
- package/dist/src/room/participant/LocalParticipant.d.ts +33 -20
- package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
- package/dist/src/room/track/LocalTrack.d.ts +8 -2
- package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
- package/dist/src/room/track/record.d.ts.map +1 -1
- package/dist/src/room/types.d.ts +17 -1
- package/dist/src/room/types.d.ts.map +1 -1
- package/dist/ts4.2/src/e2ee/worker/FrameCryptor.d.ts +0 -46
- package/dist/ts4.2/src/e2ee/worker/naluUtils.d.ts +27 -0
- package/dist/ts4.2/src/index.d.ts +2 -2
- package/dist/ts4.2/src/room/Room.d.ts +6 -10
- package/dist/ts4.2/src/room/data-stream/incoming/IncomingDataStreamManager.d.ts +20 -0
- package/dist/ts4.2/src/room/{StreamReader.d.ts → data-stream/incoming/StreamReader.d.ts} +32 -6
- package/dist/ts4.2/src/room/data-stream/outgoing/OutgoingDataStreamManager.d.ts +27 -0
- package/dist/ts4.2/src/room/{StreamWriter.d.ts → data-stream/outgoing/StreamWriter.d.ts} +1 -1
- package/dist/ts4.2/src/room/errors.d.ts +13 -0
- package/dist/ts4.2/src/room/participant/LocalParticipant.d.ts +33 -20
- package/dist/ts4.2/src/room/track/LocalTrack.d.ts +8 -2
- package/dist/ts4.2/src/room/types.d.ts +17 -1
- package/package.json +1 -1
- package/src/api/SignalClient.ts +6 -0
- package/src/e2ee/worker/FrameCryptor.ts +48 -139
- package/src/e2ee/worker/naluUtils.ts +328 -0
- package/src/index.ts +2 -2
- package/src/room/Room.ts +94 -207
- package/src/room/data-stream/incoming/IncomingDataStreamManager.ts +247 -0
- package/src/room/data-stream/incoming/StreamReader.ts +317 -0
- package/src/room/data-stream/outgoing/OutgoingDataStreamManager.ts +316 -0
- package/src/room/{StreamWriter.ts → data-stream/outgoing/StreamWriter.ts} +1 -1
- package/src/room/errors.ts +34 -0
- package/src/room/participant/LocalParticipant.ts +48 -299
- package/src/room/track/LocalAudioTrack.ts +2 -2
- package/src/room/track/LocalTrack.ts +75 -49
- package/src/room/track/record.ts +15 -2
- package/src/room/types.ts +22 -1
- package/src/room/utils.ts +3 -3
- package/dist/src/room/StreamReader.d.ts.map +0 -1
- package/dist/src/room/StreamWriter.d.ts.map +0 -1
- 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.
|
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.
|
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
|
-
|
588
|
+
requiresNALUProcessing: boolean;
|
588
589
|
} {
|
589
|
-
|
590
|
-
if (isVideoFrame(frame)) {
|
591
|
-
|
592
|
-
|
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
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
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
|
-
|
613
|
-
|
614
|
-
|
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
|
-
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
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
|
-
|
624
|
-
|
625
|
-
|
626
|
-
|
627
|
-
|
628
|
-
|
629
|
-
|
630
|
-
|
631
|
-
|
632
|
-
|
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
|
-
|
642
|
-
|
643
|
-
|
644
|
-
|
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,
|