livekit-client 2.15.4 → 2.15.6
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/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 +373 -164
- package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
- package/dist/livekit-client.esm.mjs +982 -643
- 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/e2ee/E2eeManager.d.ts.map +1 -1
- package/dist/src/e2ee/worker/FrameCryptor.d.ts +0 -47
- 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/e2ee/worker/sifPayload.d.ts +22 -0
- package/dist/src/e2ee/worker/sifPayload.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/{ts4.2/src/room → src/room/data-stream/incoming}/StreamReader.d.ts +82 -56
- 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 +32 -19
- package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
- package/dist/src/room/track/LocalTrack.d.ts +7 -2
- package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
- package/dist/src/room/track/RemoteVideoTrack.d.ts +1 -0
- package/dist/src/room/track/RemoteVideoTrack.d.ts.map +1 -1
- package/dist/src/room/track/Track.d.ts +4 -1
- package/dist/src/room/track/Track.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/src/room/utils.d.ts +8 -0
- package/dist/src/room/utils.d.ts.map +1 -1
- package/dist/ts4.2/src/e2ee/worker/FrameCryptor.d.ts +0 -47
- package/dist/ts4.2/src/e2ee/worker/naluUtils.d.ts +27 -0
- package/dist/ts4.2/src/e2ee/worker/sifPayload.d.ts +22 -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/{src/room → ts4.2/src/room/data-stream/incoming}/StreamReader.d.ts +82 -56
- 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 +32 -19
- package/dist/ts4.2/src/room/track/LocalTrack.d.ts +7 -2
- package/dist/ts4.2/src/room/track/RemoteVideoTrack.d.ts +1 -0
- package/dist/ts4.2/src/room/track/Track.d.ts +4 -1
- package/dist/ts4.2/src/room/types.d.ts +17 -1
- package/dist/ts4.2/src/room/utils.d.ts +8 -0
- package/package.json +7 -7
- package/src/e2ee/E2eeManager.ts +18 -1
- package/src/e2ee/worker/FrameCryptor.ts +56 -157
- package/src/e2ee/worker/e2ee.worker.ts +6 -1
- package/src/e2ee/worker/naluUtils.ts +328 -0
- package/src/e2ee/worker/sifPayload.ts +75 -0
- package/src/index.ts +2 -2
- package/src/room/Room.ts +104 -208
- 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 +39 -295
- package/src/room/track/LocalAudioTrack.ts +2 -2
- package/src/room/track/LocalTrack.ts +70 -50
- package/src/room/track/RemoteVideoTrack.ts +12 -2
- package/src/room/track/Track.ts +10 -1
- package/src/room/types.ts +22 -1
- package/src/room/utils.ts +14 -5
- package/dist/src/e2ee/worker/SifGuard.d.ts +0 -11
- package/dist/src/e2ee/worker/SifGuard.d.ts.map +0 -1
- package/dist/src/room/StreamReader.d.ts.map +0 -1
- package/dist/src/room/StreamWriter.d.ts.map +0 -1
- package/dist/ts4.2/src/e2ee/worker/SifGuard.d.ts +0 -11
- package/src/e2ee/worker/SifGuard.ts +0 -47
- package/src/room/StreamReader.ts +0 -170
@@ -484,8 +484,6 @@ const KEY_PROVIDER_DEFAULTS = {
|
|
484
484
|
failureTolerance: DECRYPTION_FAILURE_TOLERANCE,
|
485
485
|
keyringSize: 16
|
486
486
|
};
|
487
|
-
const MAX_SIF_COUNT = 100;
|
488
|
-
const MAX_SIF_DURATION = 2000;
|
489
487
|
|
490
488
|
class LivekitError extends Error {
|
491
489
|
constructor(code, message) {
|
@@ -503,6 +501,22 @@ var ConnectionErrorReason;
|
|
503
501
|
ConnectionErrorReason[ConnectionErrorReason["LeaveRequest"] = 4] = "LeaveRequest";
|
504
502
|
ConnectionErrorReason[ConnectionErrorReason["Timeout"] = 5] = "Timeout";
|
505
503
|
})(ConnectionErrorReason || (ConnectionErrorReason = {}));
|
504
|
+
// NOTE: matches with https://github.com/livekit/client-sdk-swift/blob/f37bbd260d61e165084962db822c79f995f1a113/Sources/LiveKit/DataStream/StreamError.swift#L17
|
505
|
+
var DataStreamErrorReason;
|
506
|
+
(function (DataStreamErrorReason) {
|
507
|
+
// Unable to open a stream with the same ID more than once.
|
508
|
+
DataStreamErrorReason[DataStreamErrorReason["AlreadyOpened"] = 0] = "AlreadyOpened";
|
509
|
+
// Stream closed abnormally by remote participant.
|
510
|
+
DataStreamErrorReason[DataStreamErrorReason["AbnormalEnd"] = 1] = "AbnormalEnd";
|
511
|
+
// Incoming chunk data could not be decoded.
|
512
|
+
DataStreamErrorReason[DataStreamErrorReason["DecodeFailed"] = 2] = "DecodeFailed";
|
513
|
+
// Read length exceeded total length specified in stream header.
|
514
|
+
DataStreamErrorReason[DataStreamErrorReason["LengthExceeded"] = 3] = "LengthExceeded";
|
515
|
+
// Read length less than total length specified in stream header.
|
516
|
+
DataStreamErrorReason[DataStreamErrorReason["Incomplete"] = 4] = "Incomplete";
|
517
|
+
// Unable to register a stream handler more than once.
|
518
|
+
DataStreamErrorReason[DataStreamErrorReason["HandlerAlreadyRegistered"] = 7] = "HandlerAlreadyRegistered";
|
519
|
+
})(DataStreamErrorReason || (DataStreamErrorReason = {}));
|
506
520
|
var MediaDeviceFailure;
|
507
521
|
(function (MediaDeviceFailure) {
|
508
522
|
// user rejected permissions
|
@@ -1077,40 +1091,307 @@ function writeRbsp(data_in) {
|
|
1077
1091
|
return new Uint8Array(dataOut);
|
1078
1092
|
}
|
1079
1093
|
|
1080
|
-
|
1081
|
-
|
1082
|
-
|
1083
|
-
|
1084
|
-
|
1094
|
+
/**
|
1095
|
+
* NALU (Network Abstraction Layer Unit) utilities for H.264 and H.265 video processing
|
1096
|
+
* Contains functions for parsing and working with NALUs in video frames
|
1097
|
+
*/
|
1098
|
+
/**
|
1099
|
+
* Mask for extracting NALU type from H.264 header byte
|
1100
|
+
*/
|
1101
|
+
const kH264NaluTypeMask = 0x1f;
|
1102
|
+
/**
|
1103
|
+
* H.264 NALU types according to RFC 6184
|
1104
|
+
*/
|
1105
|
+
var H264NALUType;
|
1106
|
+
(function (H264NALUType) {
|
1107
|
+
/** Coded slice of a non-IDR picture */
|
1108
|
+
H264NALUType[H264NALUType["SLICE_NON_IDR"] = 1] = "SLICE_NON_IDR";
|
1109
|
+
/** Coded slice data partition A */
|
1110
|
+
H264NALUType[H264NALUType["SLICE_PARTITION_A"] = 2] = "SLICE_PARTITION_A";
|
1111
|
+
/** Coded slice data partition B */
|
1112
|
+
H264NALUType[H264NALUType["SLICE_PARTITION_B"] = 3] = "SLICE_PARTITION_B";
|
1113
|
+
/** Coded slice data partition C */
|
1114
|
+
H264NALUType[H264NALUType["SLICE_PARTITION_C"] = 4] = "SLICE_PARTITION_C";
|
1115
|
+
/** Coded slice of an IDR picture */
|
1116
|
+
H264NALUType[H264NALUType["SLICE_IDR"] = 5] = "SLICE_IDR";
|
1117
|
+
/** Supplemental enhancement information */
|
1118
|
+
H264NALUType[H264NALUType["SEI"] = 6] = "SEI";
|
1119
|
+
/** Sequence parameter set */
|
1120
|
+
H264NALUType[H264NALUType["SPS"] = 7] = "SPS";
|
1121
|
+
/** Picture parameter set */
|
1122
|
+
H264NALUType[H264NALUType["PPS"] = 8] = "PPS";
|
1123
|
+
/** Access unit delimiter */
|
1124
|
+
H264NALUType[H264NALUType["AUD"] = 9] = "AUD";
|
1125
|
+
/** End of sequence */
|
1126
|
+
H264NALUType[H264NALUType["END_SEQ"] = 10] = "END_SEQ";
|
1127
|
+
/** End of stream */
|
1128
|
+
H264NALUType[H264NALUType["END_STREAM"] = 11] = "END_STREAM";
|
1129
|
+
/** Filler data */
|
1130
|
+
H264NALUType[H264NALUType["FILLER_DATA"] = 12] = "FILLER_DATA";
|
1131
|
+
/** Sequence parameter set extension */
|
1132
|
+
H264NALUType[H264NALUType["SPS_EXT"] = 13] = "SPS_EXT";
|
1133
|
+
/** Prefix NAL unit */
|
1134
|
+
H264NALUType[H264NALUType["PREFIX_NALU"] = 14] = "PREFIX_NALU";
|
1135
|
+
/** Subset sequence parameter set */
|
1136
|
+
H264NALUType[H264NALUType["SUBSET_SPS"] = 15] = "SUBSET_SPS";
|
1137
|
+
/** Depth parameter set */
|
1138
|
+
H264NALUType[H264NALUType["DPS"] = 16] = "DPS";
|
1139
|
+
// 17, 18 reserved
|
1140
|
+
/** Coded slice of an auxiliary coded picture without partitioning */
|
1141
|
+
H264NALUType[H264NALUType["SLICE_AUX"] = 19] = "SLICE_AUX";
|
1142
|
+
/** Coded slice extension */
|
1143
|
+
H264NALUType[H264NALUType["SLICE_EXT"] = 20] = "SLICE_EXT";
|
1144
|
+
/** Coded slice extension for a depth view component or a 3D-AVC texture view component */
|
1145
|
+
H264NALUType[H264NALUType["SLICE_LAYER_EXT"] = 21] = "SLICE_LAYER_EXT";
|
1146
|
+
// 22, 23 reserved
|
1147
|
+
})(H264NALUType || (H264NALUType = {}));
|
1148
|
+
/**
|
1149
|
+
* H.265/HEVC NALU types according to ITU-T H.265
|
1150
|
+
*/
|
1151
|
+
var H265NALUType;
|
1152
|
+
(function (H265NALUType) {
|
1153
|
+
/** Coded slice segment of a non-TSA, non-STSA trailing picture */
|
1154
|
+
H265NALUType[H265NALUType["TRAIL_N"] = 0] = "TRAIL_N";
|
1155
|
+
/** Coded slice segment of a non-TSA, non-STSA trailing picture */
|
1156
|
+
H265NALUType[H265NALUType["TRAIL_R"] = 1] = "TRAIL_R";
|
1157
|
+
/** Coded slice segment of a TSA picture */
|
1158
|
+
H265NALUType[H265NALUType["TSA_N"] = 2] = "TSA_N";
|
1159
|
+
/** Coded slice segment of a TSA picture */
|
1160
|
+
H265NALUType[H265NALUType["TSA_R"] = 3] = "TSA_R";
|
1161
|
+
/** Coded slice segment of an STSA picture */
|
1162
|
+
H265NALUType[H265NALUType["STSA_N"] = 4] = "STSA_N";
|
1163
|
+
/** Coded slice segment of an STSA picture */
|
1164
|
+
H265NALUType[H265NALUType["STSA_R"] = 5] = "STSA_R";
|
1165
|
+
/** Coded slice segment of a RADL picture */
|
1166
|
+
H265NALUType[H265NALUType["RADL_N"] = 6] = "RADL_N";
|
1167
|
+
/** Coded slice segment of a RADL picture */
|
1168
|
+
H265NALUType[H265NALUType["RADL_R"] = 7] = "RADL_R";
|
1169
|
+
/** Coded slice segment of a RASL picture */
|
1170
|
+
H265NALUType[H265NALUType["RASL_N"] = 8] = "RASL_N";
|
1171
|
+
/** Coded slice segment of a RASL picture */
|
1172
|
+
H265NALUType[H265NALUType["RASL_R"] = 9] = "RASL_R";
|
1173
|
+
// 10-15 reserved
|
1174
|
+
/** Coded slice segment of a BLA picture */
|
1175
|
+
H265NALUType[H265NALUType["BLA_W_LP"] = 16] = "BLA_W_LP";
|
1176
|
+
/** Coded slice segment of a BLA picture */
|
1177
|
+
H265NALUType[H265NALUType["BLA_W_RADL"] = 17] = "BLA_W_RADL";
|
1178
|
+
/** Coded slice segment of a BLA picture */
|
1179
|
+
H265NALUType[H265NALUType["BLA_N_LP"] = 18] = "BLA_N_LP";
|
1180
|
+
/** Coded slice segment of an IDR picture */
|
1181
|
+
H265NALUType[H265NALUType["IDR_W_RADL"] = 19] = "IDR_W_RADL";
|
1182
|
+
/** Coded slice segment of an IDR picture */
|
1183
|
+
H265NALUType[H265NALUType["IDR_N_LP"] = 20] = "IDR_N_LP";
|
1184
|
+
/** Coded slice segment of a CRA picture */
|
1185
|
+
H265NALUType[H265NALUType["CRA_NUT"] = 21] = "CRA_NUT";
|
1186
|
+
// 22-31 reserved
|
1187
|
+
/** Video parameter set */
|
1188
|
+
H265NALUType[H265NALUType["VPS_NUT"] = 32] = "VPS_NUT";
|
1189
|
+
/** Sequence parameter set */
|
1190
|
+
H265NALUType[H265NALUType["SPS_NUT"] = 33] = "SPS_NUT";
|
1191
|
+
/** Picture parameter set */
|
1192
|
+
H265NALUType[H265NALUType["PPS_NUT"] = 34] = "PPS_NUT";
|
1193
|
+
/** Access unit delimiter */
|
1194
|
+
H265NALUType[H265NALUType["AUD_NUT"] = 35] = "AUD_NUT";
|
1195
|
+
/** End of sequence */
|
1196
|
+
H265NALUType[H265NALUType["EOS_NUT"] = 36] = "EOS_NUT";
|
1197
|
+
/** End of bitstream */
|
1198
|
+
H265NALUType[H265NALUType["EOB_NUT"] = 37] = "EOB_NUT";
|
1199
|
+
/** Filler data */
|
1200
|
+
H265NALUType[H265NALUType["FD_NUT"] = 38] = "FD_NUT";
|
1201
|
+
/** Supplemental enhancement information */
|
1202
|
+
H265NALUType[H265NALUType["PREFIX_SEI_NUT"] = 39] = "PREFIX_SEI_NUT";
|
1203
|
+
/** Supplemental enhancement information */
|
1204
|
+
H265NALUType[H265NALUType["SUFFIX_SEI_NUT"] = 40] = "SUFFIX_SEI_NUT";
|
1205
|
+
// 41-47 reserved
|
1206
|
+
// 48-63 unspecified
|
1207
|
+
})(H265NALUType || (H265NALUType = {}));
|
1208
|
+
/**
|
1209
|
+
* Parse H.264 NALU type from the first byte of a NALU
|
1210
|
+
* @param startByte First byte of the NALU
|
1211
|
+
* @returns H.264 NALU type
|
1212
|
+
*/
|
1213
|
+
function parseH264NALUType(startByte) {
|
1214
|
+
return startByte & kH264NaluTypeMask;
|
1215
|
+
}
|
1216
|
+
/**
|
1217
|
+
* Parse H.265 NALU type from the first byte of a NALU
|
1218
|
+
* @param firstByte First byte of the NALU
|
1219
|
+
* @returns H.265 NALU type
|
1220
|
+
*/
|
1221
|
+
function parseH265NALUType(firstByte) {
|
1222
|
+
// In H.265, NALU type is in bits 1-6 (shifted right by 1)
|
1223
|
+
return firstByte >> 1 & 0x3f;
|
1224
|
+
}
|
1225
|
+
/**
|
1226
|
+
* Check if H.264 NALU type is a slice (IDR or non-IDR)
|
1227
|
+
* @param naluType H.264 NALU type
|
1228
|
+
* @returns True if the NALU is a slice
|
1229
|
+
*/
|
1230
|
+
function isH264SliceNALU(naluType) {
|
1231
|
+
return naluType === H264NALUType.SLICE_IDR || naluType === H264NALUType.SLICE_NON_IDR;
|
1232
|
+
}
|
1233
|
+
/**
|
1234
|
+
* Check if H.265 NALU type is a slice
|
1235
|
+
* @param naluType H.265 NALU type
|
1236
|
+
* @returns True if the NALU is a slice
|
1237
|
+
*/
|
1238
|
+
function isH265SliceNALU(naluType) {
|
1239
|
+
return (
|
1240
|
+
// VCL NALUs (Video Coding Layer) - slice segments
|
1241
|
+
naluType === H265NALUType.TRAIL_N || naluType === H265NALUType.TRAIL_R || naluType === H265NALUType.TSA_N || naluType === H265NALUType.TSA_R || naluType === H265NALUType.STSA_N || naluType === H265NALUType.STSA_R || naluType === H265NALUType.RADL_N || naluType === H265NALUType.RADL_R || naluType === H265NALUType.RASL_N || naluType === H265NALUType.RASL_R || naluType === H265NALUType.BLA_W_LP || naluType === H265NALUType.BLA_W_RADL || naluType === H265NALUType.BLA_N_LP || naluType === H265NALUType.IDR_W_RADL || naluType === H265NALUType.IDR_N_LP || naluType === H265NALUType.CRA_NUT
|
1242
|
+
);
|
1243
|
+
}
|
1244
|
+
/**
|
1245
|
+
* Detect codec type by examining NALU types in the data
|
1246
|
+
* @param data Frame data
|
1247
|
+
* @param naluIndices Indices where NALUs start
|
1248
|
+
* @returns Detected codec type
|
1249
|
+
*/
|
1250
|
+
function detectCodecFromNALUs(data, naluIndices) {
|
1251
|
+
for (const naluIndex of naluIndices) {
|
1252
|
+
if (isH264SliceNALU(parseH264NALUType(data[naluIndex]))) return 'h264';
|
1253
|
+
if (isH265SliceNALU(parseH265NALUType(data[naluIndex]))) return 'h265';
|
1085
1254
|
}
|
1086
|
-
|
1087
|
-
|
1088
|
-
|
1089
|
-
|
1090
|
-
|
1255
|
+
return 'unknown';
|
1256
|
+
}
|
1257
|
+
/**
|
1258
|
+
* Find the first slice NALU and return the number of unencrypted bytes
|
1259
|
+
* @param data Frame data
|
1260
|
+
* @param naluIndices Indices where NALUs start
|
1261
|
+
* @param codec Codec type to use for parsing
|
1262
|
+
* @returns Number of unencrypted bytes (index + 2) or null if no slice found
|
1263
|
+
*/
|
1264
|
+
function findSliceNALUUnencryptedBytes(data, naluIndices, codec) {
|
1265
|
+
for (const index of naluIndices) {
|
1266
|
+
if (codec === 'h265') {
|
1267
|
+
const type = parseH265NALUType(data[index]);
|
1268
|
+
if (isH265SliceNALU(type)) {
|
1269
|
+
return index + 2;
|
1270
|
+
}
|
1271
|
+
} else {
|
1272
|
+
const type = parseH264NALUType(data[index]);
|
1273
|
+
if (isH264SliceNALU(type)) {
|
1274
|
+
return index + 2;
|
1275
|
+
}
|
1276
|
+
}
|
1091
1277
|
}
|
1092
|
-
|
1093
|
-
|
1094
|
-
|
1278
|
+
return null;
|
1279
|
+
}
|
1280
|
+
/**
|
1281
|
+
* Find all NALU start indices in a byte stream
|
1282
|
+
* Supports both H.264 and H.265 with 3-byte and 4-byte start codes
|
1283
|
+
*
|
1284
|
+
* This function slices the NALUs present in the supplied buffer, assuming it is already byte-aligned.
|
1285
|
+
* Code adapted from https://github.com/medooze/h264-frame-parser/blob/main/lib/NalUnits.ts to return indices only
|
1286
|
+
*
|
1287
|
+
* @param stream Byte stream containing NALUs
|
1288
|
+
* @returns Array of indices where NALUs start (after the start code)
|
1289
|
+
*/
|
1290
|
+
function findNALUIndices(stream) {
|
1291
|
+
const result = [];
|
1292
|
+
let start = 0,
|
1293
|
+
pos = 0,
|
1294
|
+
searchLength = stream.length - 3; // Changed to -3 to handle 4-byte start codes
|
1295
|
+
while (pos < searchLength) {
|
1296
|
+
// skip until end of current NALU - check for both 3-byte and 4-byte start codes
|
1297
|
+
while (pos < searchLength) {
|
1298
|
+
// Check for 4-byte start code: 0x00 0x00 0x00 0x01
|
1299
|
+
if (pos < searchLength - 1 && stream[pos] === 0 && stream[pos + 1] === 0 && stream[pos + 2] === 0 && stream[pos + 3] === 1) {
|
1300
|
+
break;
|
1301
|
+
}
|
1302
|
+
// Check for 3-byte start code: 0x00 0x00 0x01
|
1303
|
+
if (stream[pos] === 0 && stream[pos + 1] === 0 && stream[pos + 2] === 1) {
|
1304
|
+
break;
|
1305
|
+
}
|
1306
|
+
pos++;
|
1307
|
+
}
|
1308
|
+
if (pos >= searchLength) pos = stream.length;
|
1309
|
+
// remove trailing zeros from current NALU
|
1310
|
+
let end = pos;
|
1311
|
+
while (end > start && stream[end - 1] === 0) end--;
|
1312
|
+
// save current NALU
|
1313
|
+
if (start === 0) {
|
1314
|
+
if (end !== start) throw TypeError('byte stream contains leading data');
|
1095
1315
|
} else {
|
1096
|
-
|
1316
|
+
result.push(start);
|
1097
1317
|
}
|
1098
|
-
|
1099
|
-
|
1100
|
-
|
1101
|
-
|
1102
|
-
Date.now() - this.lastSifReceivedAt > MAX_SIF_DURATION) {
|
1103
|
-
this.reset();
|
1318
|
+
// begin new NALU - determine start code length
|
1319
|
+
let startCodeLength = 3;
|
1320
|
+
if (pos < stream.length - 3 && stream[pos] === 0 && stream[pos + 1] === 0 && stream[pos + 2] === 0 && stream[pos + 3] === 1) {
|
1321
|
+
startCodeLength = 4;
|
1104
1322
|
}
|
1323
|
+
start = pos = pos + startCodeLength;
|
1105
1324
|
}
|
1106
|
-
|
1107
|
-
|
1325
|
+
return result;
|
1326
|
+
}
|
1327
|
+
/**
|
1328
|
+
* Process NALU data for frame encryption, detecting codec and finding unencrypted bytes
|
1329
|
+
* @param data Frame data
|
1330
|
+
* @param knownCodec Known codec from other sources (optional)
|
1331
|
+
* @returns NALU processing result
|
1332
|
+
*/
|
1333
|
+
function processNALUsForEncryption(data, knownCodec) {
|
1334
|
+
const naluIndices = findNALUIndices(data);
|
1335
|
+
const detectedCodec = knownCodec !== null && knownCodec !== void 0 ? knownCodec : detectCodecFromNALUs(data, naluIndices);
|
1336
|
+
if (detectedCodec === 'unknown') {
|
1337
|
+
return {
|
1338
|
+
unencryptedBytes: 0,
|
1339
|
+
detectedCodec,
|
1340
|
+
requiresNALUProcessing: false
|
1341
|
+
};
|
1108
1342
|
}
|
1109
|
-
|
1110
|
-
|
1111
|
-
|
1112
|
-
this.sifSequenceStartedAt = undefined;
|
1343
|
+
const unencryptedBytes = findSliceNALUUnencryptedBytes(data, naluIndices, detectedCodec);
|
1344
|
+
if (unencryptedBytes === null) {
|
1345
|
+
throw new TypeError('Could not find NALU');
|
1113
1346
|
}
|
1347
|
+
return {
|
1348
|
+
unencryptedBytes,
|
1349
|
+
detectedCodec,
|
1350
|
+
requiresNALUProcessing: true
|
1351
|
+
};
|
1352
|
+
}
|
1353
|
+
|
1354
|
+
/**
|
1355
|
+
* Create a crypto hash using Web Crypto API for secure comparison operations
|
1356
|
+
*/
|
1357
|
+
function cryptoHash(data) {
|
1358
|
+
return __awaiter(this, void 0, void 0, function* () {
|
1359
|
+
const hashBuffer = yield crypto.subtle.digest('SHA-256', data);
|
1360
|
+
const hashArray = new Uint8Array(hashBuffer);
|
1361
|
+
return Array.from(hashArray).map(b => b.toString(16).padStart(2, '0')).join('');
|
1362
|
+
});
|
1363
|
+
}
|
1364
|
+
/**
|
1365
|
+
* Pre-computed SHA-256 hashes for secure comparison operations
|
1366
|
+
*/
|
1367
|
+
const CryptoHashes = {
|
1368
|
+
VP8KeyFrame8x8: 'ef0161653d8b2b23aad46624b420af1d03ce48950e9fc85718028f91b50f9219',
|
1369
|
+
H264KeyFrame2x2SPS: 'f0a0e09647d891d6d50aa898bce7108090375d0d55e50a2bb21147afee558e44',
|
1370
|
+
H264KeyFrame2x2PPS: '61d9665eed71b6d424ae9539330a3bdd5cb386d4d781c808219a6e36750493a7',
|
1371
|
+
H264KeyFrame2x2IDR: 'faffc26b68a2fc09096fa20f3351e706398b6f838a7500c8063472c2e476e90d',
|
1372
|
+
OpusSilenceFrame: 'aad8d31fc56b2802ca500e58c2fb9d0b29ad71bb7cb52cd6530251eade188988'
|
1373
|
+
};
|
1374
|
+
/**
|
1375
|
+
* Check if a byte array matches any of the known SIF payload frame types using secure crypto hashes
|
1376
|
+
*/
|
1377
|
+
function identifySifPayload(data) {
|
1378
|
+
return __awaiter(this, void 0, void 0, function* () {
|
1379
|
+
const hash = yield cryptoHash(data);
|
1380
|
+
switch (hash) {
|
1381
|
+
case CryptoHashes.VP8KeyFrame8x8:
|
1382
|
+
return 'vp8';
|
1383
|
+
case CryptoHashes.H264KeyFrame2x2SPS:
|
1384
|
+
return 'h264';
|
1385
|
+
case CryptoHashes.H264KeyFrame2x2PPS:
|
1386
|
+
return 'h264';
|
1387
|
+
case CryptoHashes.H264KeyFrame2x2IDR:
|
1388
|
+
return 'h264';
|
1389
|
+
case CryptoHashes.OpusSilenceFrame:
|
1390
|
+
return 'opus';
|
1391
|
+
default:
|
1392
|
+
return null;
|
1393
|
+
}
|
1394
|
+
});
|
1114
1395
|
}
|
1115
1396
|
|
1116
1397
|
const encryptionEnabledMap = new Map();
|
@@ -1137,7 +1418,6 @@ class FrameCryptor extends BaseFrameCryptor {
|
|
1137
1418
|
this.rtpMap = new Map();
|
1138
1419
|
this.keyProviderOptions = opts.keyProviderOptions;
|
1139
1420
|
this.sifTrailer = (_a = opts.sifTrailer) !== null && _a !== void 0 ? _a : Uint8Array.from([]);
|
1140
|
-
this.sifGuard = new SifGuard();
|
1141
1421
|
}
|
1142
1422
|
get logContext() {
|
1143
1423
|
return {
|
@@ -1161,7 +1441,6 @@ class FrameCryptor extends BaseFrameCryptor {
|
|
1161
1441
|
}
|
1162
1442
|
this.participantIdentity = id;
|
1163
1443
|
this.keys = keys;
|
1164
|
-
this.sifGuard.reset();
|
1165
1444
|
}
|
1166
1445
|
unsetParticipant() {
|
1167
1446
|
workerLogger.debug('unsetting participant', this.logContext);
|
@@ -1294,7 +1573,7 @@ class FrameCryptor extends BaseFrameCryptor {
|
|
1294
1573
|
newDataWithoutHeader.set(new Uint8Array(cipherText)); // add ciphertext.
|
1295
1574
|
newDataWithoutHeader.set(new Uint8Array(iv), cipherText.byteLength); // append IV.
|
1296
1575
|
newDataWithoutHeader.set(frameTrailer, cipherText.byteLength + iv.byteLength); // append frame trailer.
|
1297
|
-
if (frameInfo.
|
1576
|
+
if (frameInfo.requiresNALUProcessing) {
|
1298
1577
|
newDataWithoutHeader = writeRbsp(newDataWithoutHeader);
|
1299
1578
|
}
|
1300
1579
|
var newData = new Uint8Array(frameHeader.byteLength + newDataWithoutHeader.byteLength);
|
@@ -1323,22 +1602,17 @@ class FrameCryptor extends BaseFrameCryptor {
|
|
1323
1602
|
if (!this.isEnabled() ||
|
1324
1603
|
// skip for decryption for empty dtx frames
|
1325
1604
|
encodedFrame.data.byteLength === 0) {
|
1326
|
-
workerLogger.debug('skipping empty frame', this.logContext);
|
1327
|
-
this.sifGuard.recordUserFrame();
|
1328
1605
|
return controller.enqueue(encodedFrame);
|
1329
1606
|
}
|
1330
1607
|
if (isFrameServerInjected(encodedFrame.data, this.sifTrailer)) {
|
1331
|
-
|
1332
|
-
|
1333
|
-
|
1334
|
-
encodedFrame.data = encodedFrame.data.slice(0, encodedFrame.data.byteLength - this.sifTrailer.byteLength);
|
1608
|
+
encodedFrame.data = encodedFrame.data.slice(0, encodedFrame.data.byteLength - this.sifTrailer.byteLength);
|
1609
|
+
if (yield identifySifPayload(encodedFrame.data)) {
|
1610
|
+
workerLogger.debug('enqueue SIF', this.logContext);
|
1335
1611
|
return controller.enqueue(encodedFrame);
|
1336
1612
|
} else {
|
1337
|
-
workerLogger.warn('SIF
|
1613
|
+
workerLogger.warn('Unexpected SIF frame payload, dropping frame', this.logContext);
|
1338
1614
|
return;
|
1339
1615
|
}
|
1340
|
-
} else {
|
1341
|
-
this.sifGuard.recordUserFrame();
|
1342
1616
|
}
|
1343
1617
|
const data = new Uint8Array(encodedFrame.data);
|
1344
1618
|
const keyIndex = data[encodedFrame.data.byteLength - 1];
|
@@ -1402,7 +1676,7 @@ class FrameCryptor extends BaseFrameCryptor {
|
|
1402
1676
|
try {
|
1403
1677
|
const frameHeader = new Uint8Array(encodedFrame.data, 0, frameInfo.unencryptedBytes);
|
1404
1678
|
var encryptedData = new Uint8Array(encodedFrame.data, frameHeader.length, encodedFrame.data.byteLength - frameHeader.length);
|
1405
|
-
if (frameInfo.
|
1679
|
+
if (frameInfo.requiresNALUProcessing && needsRbspUnescaping(encryptedData)) {
|
1406
1680
|
encryptedData = parseRbsp(encryptedData);
|
1407
1681
|
const newUint8 = new Uint8Array(frameHeader.byteLength + encryptedData.byteLength);
|
1408
1682
|
newUint8.set(frameHeader);
|
@@ -1504,56 +1778,59 @@ class FrameCryptor extends BaseFrameCryptor {
|
|
1504
1778
|
}
|
1505
1779
|
getUnencryptedBytes(frame) {
|
1506
1780
|
var _a;
|
1507
|
-
|
1508
|
-
|
1509
|
-
|
1510
|
-
|
1511
|
-
|
1512
|
-
|
1513
|
-
|
1514
|
-
|
1515
|
-
|
1516
|
-
|
1517
|
-
|
1518
|
-
|
1519
|
-
|
1520
|
-
|
1521
|
-
|
1522
|
-
|
1523
|
-
|
1524
|
-
|
1525
|
-
|
1526
|
-
|
1527
|
-
|
1528
|
-
|
1529
|
-
|
1530
|
-
|
1531
|
-
|
1532
|
-
|
1533
|
-
|
1534
|
-
|
1535
|
-
|
1536
|
-
|
1537
|
-
|
1538
|
-
|
1539
|
-
|
1540
|
-
|
1541
|
-
|
1542
|
-
|
1543
|
-
|
1544
|
-
|
1545
|
-
|
1546
|
-
|
1547
|
-
|
1548
|
-
|
1549
|
-
// no op, we just continue and fallback to vp8
|
1781
|
+
// Handle audio frames
|
1782
|
+
if (!isVideoFrame(frame)) {
|
1783
|
+
return {
|
1784
|
+
unencryptedBytes: UNENCRYPTED_BYTES.audio,
|
1785
|
+
requiresNALUProcessing: false
|
1786
|
+
};
|
1787
|
+
}
|
1788
|
+
// Detect and track codec changes
|
1789
|
+
const detectedCodec = (_a = this.getVideoCodec(frame)) !== null && _a !== void 0 ? _a : this.videoCodec;
|
1790
|
+
if (detectedCodec !== this.detectedCodec) {
|
1791
|
+
workerLogger.debug('detected different codec', Object.assign({
|
1792
|
+
detectedCodec,
|
1793
|
+
oldCodec: this.detectedCodec
|
1794
|
+
}, this.logContext));
|
1795
|
+
this.detectedCodec = detectedCodec;
|
1796
|
+
}
|
1797
|
+
// Check for unsupported codecs
|
1798
|
+
if (detectedCodec === 'av1') {
|
1799
|
+
throw new Error("".concat(detectedCodec, " is not yet supported for end to end encryption"));
|
1800
|
+
}
|
1801
|
+
// Handle VP8/VP9 codecs (no NALU processing needed)
|
1802
|
+
if (detectedCodec === 'vp8') {
|
1803
|
+
return {
|
1804
|
+
unencryptedBytes: UNENCRYPTED_BYTES[frame.type],
|
1805
|
+
requiresNALUProcessing: false
|
1806
|
+
};
|
1807
|
+
}
|
1808
|
+
if (detectedCodec === 'vp9') {
|
1809
|
+
return {
|
1810
|
+
unencryptedBytes: 0,
|
1811
|
+
requiresNALUProcessing: false
|
1812
|
+
};
|
1813
|
+
}
|
1814
|
+
// Try NALU processing for H.264/H.265 codecs
|
1815
|
+
try {
|
1816
|
+
const knownCodec = detectedCodec === 'h264' || detectedCodec === 'h265' ? detectedCodec : undefined;
|
1817
|
+
const naluResult = processNALUsForEncryption(new Uint8Array(frame.data), knownCodec);
|
1818
|
+
if (naluResult.requiresNALUProcessing) {
|
1819
|
+
return {
|
1820
|
+
unencryptedBytes: naluResult.unencryptedBytes,
|
1821
|
+
requiresNALUProcessing: true
|
1822
|
+
};
|
1550
1823
|
}
|
1551
|
-
|
1552
|
-
|
1553
|
-
|
1554
|
-
|
1555
|
-
return frameInfo;
|
1824
|
+
} catch (e) {
|
1825
|
+
workerLogger.debug('NALU processing failed, falling back to VP8 handling', Object.assign({
|
1826
|
+
error: e
|
1827
|
+
}, this.logContext));
|
1556
1828
|
}
|
1829
|
+
// Fallback to VP8 handling
|
1830
|
+
return {
|
1831
|
+
unencryptedBytes: UNENCRYPTED_BYTES[frame.type],
|
1832
|
+
requiresNALUProcessing: false
|
1833
|
+
};
|
1557
1834
|
}
|
1558
1835
|
/**
|
1559
1836
|
* inspects frame payloadtype if available and maps it to the codec specified in rtpMap
|
@@ -1567,80 +1844,6 @@ class FrameCryptor extends BaseFrameCryptor {
|
|
1567
1844
|
return codec;
|
1568
1845
|
}
|
1569
1846
|
}
|
1570
|
-
/**
|
1571
|
-
* Slice the NALUs present in the supplied buffer, assuming it is already byte-aligned
|
1572
|
-
* code adapted from https://github.com/medooze/h264-frame-parser/blob/main/lib/NalUnits.ts to return indices only
|
1573
|
-
*/
|
1574
|
-
function findNALUIndices(stream) {
|
1575
|
-
const result = [];
|
1576
|
-
let start = 0,
|
1577
|
-
pos = 0,
|
1578
|
-
searchLength = stream.length - 2;
|
1579
|
-
while (pos < searchLength) {
|
1580
|
-
// skip until end of current NALU
|
1581
|
-
while (pos < searchLength && !(stream[pos] === 0 && stream[pos + 1] === 0 && stream[pos + 2] === 1)) pos++;
|
1582
|
-
if (pos >= searchLength) pos = stream.length;
|
1583
|
-
// remove trailing zeros from current NALU
|
1584
|
-
let end = pos;
|
1585
|
-
while (end > start && stream[end - 1] === 0) end--;
|
1586
|
-
// save current NALU
|
1587
|
-
if (start === 0) {
|
1588
|
-
if (end !== start) throw TypeError('byte stream contains leading data');
|
1589
|
-
} else {
|
1590
|
-
result.push(start);
|
1591
|
-
}
|
1592
|
-
// begin new NALU
|
1593
|
-
start = pos = pos + 3;
|
1594
|
-
}
|
1595
|
-
return result;
|
1596
|
-
}
|
1597
|
-
function parseNALUType(startByte) {
|
1598
|
-
return startByte & kNaluTypeMask;
|
1599
|
-
}
|
1600
|
-
const kNaluTypeMask = 0x1f;
|
1601
|
-
var NALUType;
|
1602
|
-
(function (NALUType) {
|
1603
|
-
/** Coded slice of a non-IDR picture */
|
1604
|
-
NALUType[NALUType["SLICE_NON_IDR"] = 1] = "SLICE_NON_IDR";
|
1605
|
-
/** Coded slice data partition A */
|
1606
|
-
NALUType[NALUType["SLICE_PARTITION_A"] = 2] = "SLICE_PARTITION_A";
|
1607
|
-
/** Coded slice data partition B */
|
1608
|
-
NALUType[NALUType["SLICE_PARTITION_B"] = 3] = "SLICE_PARTITION_B";
|
1609
|
-
/** Coded slice data partition C */
|
1610
|
-
NALUType[NALUType["SLICE_PARTITION_C"] = 4] = "SLICE_PARTITION_C";
|
1611
|
-
/** Coded slice of an IDR picture */
|
1612
|
-
NALUType[NALUType["SLICE_IDR"] = 5] = "SLICE_IDR";
|
1613
|
-
/** Supplemental enhancement information */
|
1614
|
-
NALUType[NALUType["SEI"] = 6] = "SEI";
|
1615
|
-
/** Sequence parameter set */
|
1616
|
-
NALUType[NALUType["SPS"] = 7] = "SPS";
|
1617
|
-
/** Picture parameter set */
|
1618
|
-
NALUType[NALUType["PPS"] = 8] = "PPS";
|
1619
|
-
/** Access unit delimiter */
|
1620
|
-
NALUType[NALUType["AUD"] = 9] = "AUD";
|
1621
|
-
/** End of sequence */
|
1622
|
-
NALUType[NALUType["END_SEQ"] = 10] = "END_SEQ";
|
1623
|
-
/** End of stream */
|
1624
|
-
NALUType[NALUType["END_STREAM"] = 11] = "END_STREAM";
|
1625
|
-
/** Filler data */
|
1626
|
-
NALUType[NALUType["FILLER_DATA"] = 12] = "FILLER_DATA";
|
1627
|
-
/** Sequence parameter set extension */
|
1628
|
-
NALUType[NALUType["SPS_EXT"] = 13] = "SPS_EXT";
|
1629
|
-
/** Prefix NAL unit */
|
1630
|
-
NALUType[NALUType["PREFIX_NALU"] = 14] = "PREFIX_NALU";
|
1631
|
-
/** Subset sequence parameter set */
|
1632
|
-
NALUType[NALUType["SUBSET_SPS"] = 15] = "SUBSET_SPS";
|
1633
|
-
/** Depth parameter set */
|
1634
|
-
NALUType[NALUType["DPS"] = 16] = "DPS";
|
1635
|
-
// 17, 18 reserved
|
1636
|
-
/** Coded slice of an auxiliary coded picture without partitioning */
|
1637
|
-
NALUType[NALUType["SLICE_AUX"] = 19] = "SLICE_AUX";
|
1638
|
-
/** Coded slice extension */
|
1639
|
-
NALUType[NALUType["SLICE_EXT"] = 20] = "SLICE_EXT";
|
1640
|
-
/** Coded slice extension for a depth view component or a 3D-AVC texture view component */
|
1641
|
-
NALUType[NALUType["SLICE_LAYER_EXT"] = 21] = "SLICE_LAYER_EXT";
|
1642
|
-
// 22, 23 reserved
|
1643
|
-
})(NALUType || (NALUType = {}));
|
1644
1847
|
/**
|
1645
1848
|
* we use a magic frame trailer to detect whether a frame is injected
|
1646
1849
|
* by the livekit server and thus to be treated as unencrypted
|
@@ -1894,6 +2097,11 @@ onmessage = ev => {
|
|
1894
2097
|
break;
|
1895
2098
|
case 'updateCodec':
|
1896
2099
|
getTrackCryptor(data.participantIdentity, data.trackId).setVideoCodec(data.codec);
|
2100
|
+
workerLogger.info('updated codec', {
|
2101
|
+
participantIdentity: data.participantIdentity,
|
2102
|
+
trackId: data.trackId,
|
2103
|
+
codec: data.codec
|
2104
|
+
});
|
1897
2105
|
break;
|
1898
2106
|
case 'setRTPMap':
|
1899
2107
|
// this is only used for the local participant
|
@@ -1943,7 +2151,8 @@ function getTrackCryptor(participantIdentity, trackId) {
|
|
1943
2151
|
let cryptor = cryptors[0];
|
1944
2152
|
if (!cryptor) {
|
1945
2153
|
workerLogger.info('creating new cryptor for', {
|
1946
|
-
participantIdentity
|
2154
|
+
participantIdentity,
|
2155
|
+
trackId
|
1947
2156
|
});
|
1948
2157
|
if (!keyProviderOptions) {
|
1949
2158
|
throw Error('Missing keyProvider options');
|