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.
Files changed (84) 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 +373 -164
  4. package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
  5. package/dist/livekit-client.esm.mjs +982 -643
  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/E2eeManager.d.ts.map +1 -1
  10. package/dist/src/e2ee/worker/FrameCryptor.d.ts +0 -47
  11. package/dist/src/e2ee/worker/FrameCryptor.d.ts.map +1 -1
  12. package/dist/src/e2ee/worker/naluUtils.d.ts +27 -0
  13. package/dist/src/e2ee/worker/naluUtils.d.ts.map +1 -0
  14. package/dist/src/e2ee/worker/sifPayload.d.ts +22 -0
  15. package/dist/src/e2ee/worker/sifPayload.d.ts.map +1 -0
  16. package/dist/src/index.d.ts +2 -2
  17. package/dist/src/index.d.ts.map +1 -1
  18. package/dist/src/room/Room.d.ts +6 -10
  19. package/dist/src/room/Room.d.ts.map +1 -1
  20. package/dist/src/room/data-stream/incoming/IncomingDataStreamManager.d.ts +20 -0
  21. package/dist/src/room/data-stream/incoming/IncomingDataStreamManager.d.ts.map +1 -0
  22. package/dist/{ts4.2/src/room → src/room/data-stream/incoming}/StreamReader.d.ts +82 -56
  23. package/dist/src/room/data-stream/incoming/StreamReader.d.ts.map +1 -0
  24. package/dist/src/room/data-stream/outgoing/OutgoingDataStreamManager.d.ts +27 -0
  25. package/dist/src/room/data-stream/outgoing/OutgoingDataStreamManager.d.ts.map +1 -0
  26. package/dist/src/room/{StreamWriter.d.ts → data-stream/outgoing/StreamWriter.d.ts} +1 -1
  27. package/dist/src/room/data-stream/outgoing/StreamWriter.d.ts.map +1 -0
  28. package/dist/src/room/errors.d.ts +13 -0
  29. package/dist/src/room/errors.d.ts.map +1 -1
  30. package/dist/src/room/participant/LocalParticipant.d.ts +32 -19
  31. package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
  32. package/dist/src/room/track/LocalTrack.d.ts +7 -2
  33. package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
  34. package/dist/src/room/track/RemoteVideoTrack.d.ts +1 -0
  35. package/dist/src/room/track/RemoteVideoTrack.d.ts.map +1 -1
  36. package/dist/src/room/track/Track.d.ts +4 -1
  37. package/dist/src/room/track/Track.d.ts.map +1 -1
  38. package/dist/src/room/types.d.ts +17 -1
  39. package/dist/src/room/types.d.ts.map +1 -1
  40. package/dist/src/room/utils.d.ts +8 -0
  41. package/dist/src/room/utils.d.ts.map +1 -1
  42. package/dist/ts4.2/src/e2ee/worker/FrameCryptor.d.ts +0 -47
  43. package/dist/ts4.2/src/e2ee/worker/naluUtils.d.ts +27 -0
  44. package/dist/ts4.2/src/e2ee/worker/sifPayload.d.ts +22 -0
  45. package/dist/ts4.2/src/index.d.ts +2 -2
  46. package/dist/ts4.2/src/room/Room.d.ts +6 -10
  47. package/dist/ts4.2/src/room/data-stream/incoming/IncomingDataStreamManager.d.ts +20 -0
  48. package/dist/{src/room → ts4.2/src/room/data-stream/incoming}/StreamReader.d.ts +82 -56
  49. package/dist/ts4.2/src/room/data-stream/outgoing/OutgoingDataStreamManager.d.ts +27 -0
  50. package/dist/ts4.2/src/room/{StreamWriter.d.ts → data-stream/outgoing/StreamWriter.d.ts} +1 -1
  51. package/dist/ts4.2/src/room/errors.d.ts +13 -0
  52. package/dist/ts4.2/src/room/participant/LocalParticipant.d.ts +32 -19
  53. package/dist/ts4.2/src/room/track/LocalTrack.d.ts +7 -2
  54. package/dist/ts4.2/src/room/track/RemoteVideoTrack.d.ts +1 -0
  55. package/dist/ts4.2/src/room/track/Track.d.ts +4 -1
  56. package/dist/ts4.2/src/room/types.d.ts +17 -1
  57. package/dist/ts4.2/src/room/utils.d.ts +8 -0
  58. package/package.json +7 -7
  59. package/src/e2ee/E2eeManager.ts +18 -1
  60. package/src/e2ee/worker/FrameCryptor.ts +56 -157
  61. package/src/e2ee/worker/e2ee.worker.ts +6 -1
  62. package/src/e2ee/worker/naluUtils.ts +328 -0
  63. package/src/e2ee/worker/sifPayload.ts +75 -0
  64. package/src/index.ts +2 -2
  65. package/src/room/Room.ts +104 -208
  66. package/src/room/data-stream/incoming/IncomingDataStreamManager.ts +247 -0
  67. package/src/room/data-stream/incoming/StreamReader.ts +317 -0
  68. package/src/room/data-stream/outgoing/OutgoingDataStreamManager.ts +316 -0
  69. package/src/room/{StreamWriter.ts → data-stream/outgoing/StreamWriter.ts} +1 -1
  70. package/src/room/errors.ts +34 -0
  71. package/src/room/participant/LocalParticipant.ts +39 -295
  72. package/src/room/track/LocalAudioTrack.ts +2 -2
  73. package/src/room/track/LocalTrack.ts +70 -50
  74. package/src/room/track/RemoteVideoTrack.ts +12 -2
  75. package/src/room/track/Track.ts +10 -1
  76. package/src/room/types.ts +22 -1
  77. package/src/room/utils.ts +14 -5
  78. package/dist/src/e2ee/worker/SifGuard.d.ts +0 -11
  79. package/dist/src/e2ee/worker/SifGuard.d.ts.map +0 -1
  80. package/dist/src/room/StreamReader.d.ts.map +0 -1
  81. package/dist/src/room/StreamWriter.d.ts.map +0 -1
  82. package/dist/ts4.2/src/e2ee/worker/SifGuard.d.ts +0 -11
  83. package/src/e2ee/worker/SifGuard.ts +0 -47
  84. 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
- class SifGuard {
1081
- constructor() {
1082
- this.consecutiveSifCount = 0;
1083
- this.lastSifReceivedAt = 0;
1084
- this.userFramesSinceSif = 0;
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
- recordSif() {
1087
- var _a;
1088
- this.consecutiveSifCount += 1;
1089
- (_a = this.sifSequenceStartedAt) !== null && _a !== void 0 ? _a : this.sifSequenceStartedAt = Date.now();
1090
- this.lastSifReceivedAt = Date.now();
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
- recordUserFrame() {
1093
- if (this.sifSequenceStartedAt === undefined) {
1094
- return;
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
- this.userFramesSinceSif += 1;
1316
+ result.push(start);
1097
1317
  }
1098
- if (
1099
- // reset if we received more user frames than SIFs
1100
- this.userFramesSinceSif > this.consecutiveSifCount ||
1101
- // also reset if we got a new user frame and the latest SIF frame hasn't been updated in a while
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
- isSifAllowed() {
1107
- return this.consecutiveSifCount < MAX_SIF_COUNT && (this.sifSequenceStartedAt === undefined || Date.now() - this.sifSequenceStartedAt < MAX_SIF_DURATION);
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
- reset() {
1110
- this.userFramesSinceSif = 0;
1111
- this.consecutiveSifCount = 0;
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.isH264) {
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
- workerLogger.debug('enqueue SIF', this.logContext);
1332
- this.sifGuard.recordSif();
1333
- if (this.sifGuard.isSifAllowed()) {
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 limit reached, dropping frame');
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.isH264 && needsRbspUnescaping(encryptedData)) {
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
- var frameInfo = {
1508
- unencryptedBytes: 0,
1509
- isH264: false
1510
- };
1511
- if (isVideoFrame(frame)) {
1512
- let detectedCodec = (_a = this.getVideoCodec(frame)) !== null && _a !== void 0 ? _a : this.videoCodec;
1513
- if (detectedCodec !== this.detectedCodec) {
1514
- workerLogger.debug('detected different codec', Object.assign({
1515
- detectedCodec,
1516
- oldCodec: this.detectedCodec
1517
- }, this.logContext));
1518
- this.detectedCodec = detectedCodec;
1519
- }
1520
- if (detectedCodec === 'av1') {
1521
- throw new Error("".concat(detectedCodec, " is not yet supported for end to end encryption"));
1522
- }
1523
- if (detectedCodec === 'vp8') {
1524
- frameInfo.unencryptedBytes = UNENCRYPTED_BYTES[frame.type];
1525
- } else if (detectedCodec === 'vp9') {
1526
- frameInfo.unencryptedBytes = 0;
1527
- return frameInfo;
1528
- }
1529
- const data = new Uint8Array(frame.data);
1530
- try {
1531
- const naluIndices = findNALUIndices(data);
1532
- // if the detected codec is undefined we test whether it _looks_ like a h264 frame as a best guess
1533
- frameInfo.isH264 = detectedCodec === 'h264' || naluIndices.some(naluIndex => [NALUType.SLICE_IDR, NALUType.SLICE_NON_IDR].includes(parseNALUType(data[naluIndex])));
1534
- if (frameInfo.isH264) {
1535
- for (const index of naluIndices) {
1536
- let type = parseNALUType(data[index]);
1537
- switch (type) {
1538
- case NALUType.SLICE_IDR:
1539
- case NALUType.SLICE_NON_IDR:
1540
- frameInfo.unencryptedBytes = index + 2;
1541
- return frameInfo;
1542
- default:
1543
- break;
1544
- }
1545
- }
1546
- throw new TypeError('Could not find NALU');
1547
- }
1548
- } catch (e) {
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
- frameInfo.unencryptedBytes = UNENCRYPTED_BYTES[frame.type];
1552
- return frameInfo;
1553
- } else {
1554
- frameInfo.unencryptedBytes = UNENCRYPTED_BYTES.audio;
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');