@apocaliss92/nodelink-js 0.4.6 → 0.4.8

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/index.js CHANGED
@@ -8,6 +8,7 @@ import {
8
8
  DUAL_LENS_MODELS,
9
9
  DUAL_LENS_SINGLE_MOTION_MODELS,
10
10
  Intercom,
11
+ MpegTsMuxer,
11
12
  NVR_HUB_EXACT_TYPES,
12
13
  NVR_HUB_MODEL_PATTERNS,
13
14
  ReolinkBaichuanApi,
@@ -20,6 +21,7 @@ import {
20
21
  createNativeStream,
21
22
  createNullLogger,
22
23
  createTaggedLogger,
24
+ decideSleepInferenceTransition,
23
25
  decodeHeader,
24
26
  discoverReolinkDevices,
25
27
  discoverViaArpTable,
@@ -43,7 +45,7 @@ import {
43
45
  parseSupportXml,
44
46
  setGlobalLogger,
45
47
  xmlIndicatesFloodlight
46
- } from "./chunk-F2Y5U3YP.js";
48
+ } from "./chunk-VBYF3BQX.js";
47
49
  import {
48
50
  AesStreamDecryptor,
49
51
  BC_AES_IV,
@@ -223,7 +225,7 @@ import {
223
225
  testChannelStreams,
224
226
  xmlEscape,
225
227
  zipDirectory
226
- } from "./chunk-TR3V5FTO.js";
228
+ } from "./chunk-YKKQDUKU.js";
227
229
 
228
230
  // src/reolink/baichuan/HlsSessionManager.ts
229
231
  var withTimeout = async (p, ms, label) => {
@@ -4725,6 +4727,7 @@ var Go2rtcTcpServer = class _Go2rtcTcpServer extends EventEmitter2 {
4725
4727
  gracePeriodMs;
4726
4728
  prebufferMaxMs;
4727
4729
  maxBufferBytes;
4730
+ streamTimeoutMs;
4728
4731
  prestartStream;
4729
4732
  active = false;
4730
4733
  server;
@@ -4738,8 +4741,16 @@ var Go2rtcTcpServer = class _Go2rtcTcpServer extends EventEmitter2 {
4738
4741
  connectedClients = /* @__PURE__ */ new Set();
4739
4742
  clientSockets = /* @__PURE__ */ new Map();
4740
4743
  stopGraceTimer;
4744
+ // Stream health monitoring
4745
+ lastFrameAt = 0;
4746
+ streamHealthTimer;
4747
+ totalFramesReceived = 0;
4748
+ totalVideoFramesWritten = 0;
4741
4749
  // Prebuffer
4742
4750
  prebuffer = [];
4751
+ // Audio metadata — populated on first valid ADTS AAC frame.
4752
+ // Exposed via getAudioInfo() for the stream-diagnostics feature.
4753
+ audioInfo = null;
4743
4754
  constructor(options) {
4744
4755
  super();
4745
4756
  this.api = options.api;
@@ -4753,6 +4764,7 @@ var Go2rtcTcpServer = class _Go2rtcTcpServer extends EventEmitter2 {
4753
4764
  this.gracePeriodMs = options.gracePeriodMs ?? 3e4;
4754
4765
  this.prebufferMaxMs = options.prebufferMs ?? 3e3;
4755
4766
  this.maxBufferBytes = options.maxBufferBytes ?? 1e8;
4767
+ this.streamTimeoutMs = options.streamTimeoutMs ?? 15e3;
4756
4768
  this.prestartStream = options.prestartStream ?? true;
4757
4769
  }
4758
4770
  // -----------------------------------------------------------------------
@@ -4791,6 +4803,7 @@ var Go2rtcTcpServer = class _Go2rtcTcpServer extends EventEmitter2 {
4791
4803
  if (!this.active) return;
4792
4804
  this.active = false;
4793
4805
  clearTimeout(this.stopGraceTimer);
4806
+ this.stopStreamHealthMonitor();
4794
4807
  for (const [id, sock] of this.clientSockets) {
4795
4808
  sock.destroy();
4796
4809
  this.connectedClients.delete(id);
@@ -4821,6 +4834,45 @@ var Go2rtcTcpServer = class _Go2rtcTcpServer extends EventEmitter2 {
4821
4834
  return this.connectedClients.size;
4822
4835
  }
4823
4836
  // -----------------------------------------------------------------------
4837
+ // Diagnostic subscription API (implements DiagnosticStreamServer)
4838
+ //
4839
+ // Matches the shape of BaichuanRtspServer's diagnostic API so the
4840
+ // stream-diagnostic feature in the Manager app can drive either backend
4841
+ // with identical code.
4842
+ // -----------------------------------------------------------------------
4843
+ /**
4844
+ * Subscribe to the raw native stream for diagnostic purposes.
4845
+ * The subscriber receives the same frames the MPEG-TS muxer consumes
4846
+ * (pre-muxing). Counts as a "consumer" so the native stream is kept alive
4847
+ * for the lifetime of the subscription. If the stream is not already
4848
+ * running (battery camera, prestart=false), this starts it.
4849
+ */
4850
+ async subscribeDiagnostic(id) {
4851
+ this.connectedClients.add(`diag:${id}`);
4852
+ if (!this.nativeStreamActive) {
4853
+ await this.startNativeStream();
4854
+ }
4855
+ if (!this.nativeFanout) {
4856
+ this.connectedClients.delete(`diag:${id}`);
4857
+ throw new Error(
4858
+ "Go2rtcTcpServer: native stream failed to start \u2014 cannot subscribe diagnostic"
4859
+ );
4860
+ }
4861
+ return this.nativeFanout.subscribe(`diag:${id}`);
4862
+ }
4863
+ /** Unsubscribe a diagnostic session and release its consumer slot. */
4864
+ unsubscribeDiagnostic(id) {
4865
+ this.removeClient(`diag:${id}`, "diagnostic unsubscribe");
4866
+ }
4867
+ /**
4868
+ * Returns ADTS AAC audio metadata detected from the native stream, or
4869
+ * null if no audio frame has been observed yet (e.g. video-only cameras
4870
+ * or before the first audio packet arrives).
4871
+ */
4872
+ getAudioInfo() {
4873
+ return this.audioInfo;
4874
+ }
4875
+ // -----------------------------------------------------------------------
4824
4876
  // Client handling
4825
4877
  // -----------------------------------------------------------------------
4826
4878
  handleClient(socket) {
@@ -4844,12 +4896,12 @@ var Go2rtcTcpServer = class _Go2rtcTcpServer extends EventEmitter2 {
4844
4896
  `[Go2rtcTcpServer] feedClient error id=${clientId}: ${err}`
4845
4897
  );
4846
4898
  });
4847
- const cleanup = () => {
4848
- this.removeClient(clientId);
4899
+ const cleanup = (reason) => {
4900
+ this.removeClient(clientId, reason);
4849
4901
  socket.destroy();
4850
4902
  };
4851
- socket.on("error", cleanup);
4852
- socket.on("close", cleanup);
4903
+ socket.on("error", (err) => cleanup(`error: ${err.message}`));
4904
+ socket.on("close", (hadError) => cleanup(hadError ? "close (with error)" : "close (clean)"));
4853
4905
  }
4854
4906
  async feedClient(clientId, socket) {
4855
4907
  const fanoutDeadline = Date.now() + 3e4;
@@ -4865,6 +4917,7 @@ var Go2rtcTcpServer = class _Go2rtcTcpServer extends EventEmitter2 {
4865
4917
  }
4866
4918
  if (!this.active || !this.nativeFanout) return;
4867
4919
  const subscription = this.nativeFanout.subscribe(clientId);
4920
+ let muxer = null;
4868
4921
  const prebufferSnap = this.prebuffer.slice();
4869
4922
  let lastIdrIdx = -1;
4870
4923
  for (let i = prebufferSnap.length - 1; i >= 0; i--) {
@@ -4878,9 +4931,21 @@ var Go2rtcTcpServer = class _Go2rtcTcpServer extends EventEmitter2 {
4878
4931
  this.logger.info?.(
4879
4932
  `[Go2rtcTcpServer] prebuffer replay client=${clientId} frames=${replay.length}`
4880
4933
  );
4934
+ if (!muxer) {
4935
+ muxer = new MpegTsMuxer({
4936
+ videoType: this.detectedVideoType ?? "H264",
4937
+ includeAudio: true
4938
+ });
4939
+ }
4881
4940
  for (const entry of replay) {
4882
4941
  if (socket.destroyed) return;
4883
- socket.write(entry.data);
4942
+ let ts;
4943
+ if (!entry.audio) {
4944
+ ts = muxer.muxVideo(entry.data, entry.pts, entry.isKeyframe);
4945
+ } else {
4946
+ ts = muxer.muxAudio(entry.data, entry.pts);
4947
+ }
4948
+ if (ts.length > 0) socket.write(ts);
4884
4949
  }
4885
4950
  }
4886
4951
  let seenKeyframe = lastIdrIdx >= 0;
@@ -4899,17 +4964,35 @@ var Go2rtcTcpServer = class _Go2rtcTcpServer extends EventEmitter2 {
4899
4964
  break;
4900
4965
  }
4901
4966
  liveFrameCount++;
4902
- const annexB = this.convertFrame(frame);
4967
+ if (frame.audio) {
4968
+ if (muxer) {
4969
+ const pts2 = frame.microseconds ?? Date.now() * 1e3;
4970
+ const ts2 = muxer.muxAudio(frame.data, pts2);
4971
+ if (ts2.length > 0) socket.write(ts2);
4972
+ }
4973
+ continue;
4974
+ }
4975
+ const annexB = this.convertVideoFrame(frame);
4903
4976
  if (!annexB) continue;
4977
+ const isKf = this.isAnnexBKeyframe(annexB, frame.videoType);
4904
4978
  if (!seenKeyframe) {
4905
- if (!this.isAnnexBKeyframe(annexB, frame.videoType)) continue;
4979
+ if (!isKf) continue;
4906
4980
  seenKeyframe = true;
4907
4981
  this.logger.info?.(
4908
4982
  `[Go2rtcTcpServer] first live keyframe client=${clientId} after ${liveFrameCount} frames`
4909
4983
  );
4984
+ if (!muxer) {
4985
+ muxer = new MpegTsMuxer({
4986
+ videoType: frame.videoType ?? this.detectedVideoType ?? "H264",
4987
+ includeAudio: true
4988
+ });
4989
+ }
4910
4990
  }
4911
- socket.write(annexB);
4991
+ const pts = frame.microseconds ?? Date.now() * 1e3;
4992
+ const ts = muxer.muxVideo(annexB, pts, isKf);
4993
+ socket.write(ts);
4912
4994
  liveVideoWritten++;
4995
+ this.totalVideoFramesWritten++;
4913
4996
  if (Date.now() - lastLogAt > 1e4) {
4914
4997
  this.logger.info?.(
4915
4998
  `[Go2rtcTcpServer] live stats client=${clientId} received=${liveFrameCount} written=${liveVideoWritten} bufLen=${socket.writableLength}`
@@ -4936,14 +5019,11 @@ var Go2rtcTcpServer = class _Go2rtcTcpServer extends EventEmitter2 {
4936
5019
  // Frame conversion
4937
5020
  // -----------------------------------------------------------------------
4938
5021
  /**
4939
- * Convert a native frame to wire-ready Annex-B.
4940
- * Audio frames are skipped raw TCP carries only video (Annex-B).
4941
- * go2rtc auto-detects the codec from SPS/PPS/VPS NALUs.
5022
+ * Convert a native video frame to Annex-B.
5023
+ * Returns null for audio frames (handled separately by muxAudio).
4942
5024
  */
4943
- convertFrame(frame) {
4944
- if (frame.audio) {
4945
- return null;
4946
- }
5025
+ convertVideoFrame(frame) {
5026
+ if (frame.audio) return null;
4947
5027
  if (frame.data.length === 0) return null;
4948
5028
  try {
4949
5029
  if (frame.videoType === "H264") {
@@ -5007,10 +5087,71 @@ var Go2rtcTcpServer = class _Go2rtcTcpServer extends EventEmitter2 {
5007
5087
  return nals;
5008
5088
  }
5009
5089
  // -----------------------------------------------------------------------
5090
+ // ADTS AAC parsing (used for audio metadata exposed via getAudioInfo)
5091
+ // -----------------------------------------------------------------------
5092
+ /** True if `b` starts with an ADTS AAC syncword (0xFFF). */
5093
+ static isAdtsAacFrame(b) {
5094
+ return b.length >= 2 && b[0] === 255 && (b[1] & 240) === 240;
5095
+ }
5096
+ /**
5097
+ * Parse an ADTS header into {sampleRate, channels, AudioSpecificConfig hex}.
5098
+ * Returns null when the buffer is not a valid ADTS frame.
5099
+ */
5100
+ static parseAdtsSamplingInfo(b) {
5101
+ if (b.length < 7) return null;
5102
+ if (!_Go2rtcTcpServer.isAdtsAacFrame(b)) return null;
5103
+ const samplingIndex = b[2] >> 2 & 15;
5104
+ const sampleRates = [
5105
+ 96e3,
5106
+ 88200,
5107
+ 64e3,
5108
+ 48e3,
5109
+ 44100,
5110
+ 32e3,
5111
+ 24e3,
5112
+ 22050,
5113
+ 16e3,
5114
+ 12e3,
5115
+ 11025,
5116
+ 8e3,
5117
+ 7350
5118
+ ];
5119
+ const sampleRate = sampleRates[samplingIndex] ?? null;
5120
+ if (!sampleRate) return null;
5121
+ const channelConfig = (b[2] & 1) << 2 | b[3] >> 6 & 3;
5122
+ const channels = channelConfig === 0 ? 1 : channelConfig;
5123
+ const profile = b[2] >> 6 & 3;
5124
+ const audioObjectType = profile + 1;
5125
+ const asc = audioObjectType << 11 | samplingIndex << 7 | channelConfig << 3;
5126
+ const configHex = Buffer.from([asc >> 8 & 255, asc & 255]).toString(
5127
+ "hex"
5128
+ );
5129
+ return { sampleRate, channels, configHex };
5130
+ }
5131
+ // -----------------------------------------------------------------------
5010
5132
  // Native stream management
5011
5133
  // -----------------------------------------------------------------------
5012
5134
  async startNativeStream() {
5013
5135
  if (this.nativeStreamActive) return;
5136
+ if (!this.api.isReady) {
5137
+ if (this.api.isClosed) {
5138
+ this.logger.warn?.(
5139
+ `[Go2rtcTcpServer] API has been explicitly closed \u2014 stream cannot start`
5140
+ );
5141
+ return;
5142
+ }
5143
+ try {
5144
+ this.logger.info?.(
5145
+ `[Go2rtcTcpServer] API not ready (idle disconnect?), calling ensureConnected`
5146
+ );
5147
+ await this.api.ensureConnected();
5148
+ } catch (e) {
5149
+ this.logger.warn?.(
5150
+ `[Go2rtcTcpServer] ensureConnected failed, aborting stream start: ${e}`
5151
+ );
5152
+ return;
5153
+ }
5154
+ }
5014
5155
  this.nativeStreamActive = true;
5015
5156
  let dedicatedClient;
5016
5157
  if (this.deviceId) {
@@ -5029,6 +5170,7 @@ var Go2rtcTcpServer = class _Go2rtcTcpServer extends EventEmitter2 {
5029
5170
  this.logger.info?.(
5030
5171
  `[Go2rtcTcpServer] native stream starting channel=${this.channel} profile=${this.profile} dedicated=${!!dedicatedClient}`
5031
5172
  );
5173
+ let hadFrames = false;
5032
5174
  this.nativeFanout = new NativeStreamFanout({
5033
5175
  maxQueueItems: 200,
5034
5176
  createSource: () => createNativeStream(this.api, this.channel, this.profile, {
@@ -5036,17 +5178,37 @@ var Go2rtcTcpServer = class _Go2rtcTcpServer extends EventEmitter2 {
5036
5178
  ...dedicatedClient ? { client: dedicatedClient } : {}
5037
5179
  }),
5038
5180
  onFrame: (frame) => {
5181
+ hadFrames = true;
5182
+ this.lastFrameAt = Date.now();
5183
+ this.totalFramesReceived++;
5039
5184
  if (!frame.audio && (frame.videoType === "H264" || frame.videoType === "H265")) {
5040
5185
  this.detectedVideoType = frame.videoType;
5041
5186
  }
5042
- const wireData = this.convertFrame(frame);
5043
- if (!wireData || wireData.length === 0) return;
5044
- const isKeyframe = !frame.audio && this.isAnnexBKeyframe(wireData, frame.videoType);
5187
+ let prebufData;
5188
+ let isKeyframe;
5189
+ if (frame.audio) {
5190
+ if (frame.data.length === 0) return;
5191
+ if (!this.audioInfo) {
5192
+ const parsed = _Go2rtcTcpServer.parseAdtsSamplingInfo(frame.data);
5193
+ if (parsed) {
5194
+ this.audioInfo = { codec: "aac-adts", ...parsed };
5195
+ }
5196
+ }
5197
+ prebufData = frame.data;
5198
+ isKeyframe = false;
5199
+ } else {
5200
+ const annexB = this.convertVideoFrame(frame);
5201
+ if (!annexB || annexB.length === 0) return;
5202
+ prebufData = annexB;
5203
+ isKeyframe = this.isAnnexBKeyframe(annexB, frame.videoType);
5204
+ }
5205
+ const pts = frame.microseconds ?? Date.now() * 1e3;
5045
5206
  this.prebuffer.push({
5046
- data: Buffer.from(wireData),
5207
+ data: Buffer.from(prebufData),
5047
5208
  time: Date.now(),
5048
5209
  isKeyframe,
5049
- audio: frame.audio
5210
+ audio: frame.audio,
5211
+ pts
5050
5212
  });
5051
5213
  const cutoff = Date.now() - this.prebufferMaxMs;
5052
5214
  let trimIdx = 0;
@@ -5062,23 +5224,47 @@ var Go2rtcTcpServer = class _Go2rtcTcpServer extends EventEmitter2 {
5062
5224
  if (!this.nativeStreamActive) return;
5063
5225
  this.nativeStreamActive = false;
5064
5226
  this.nativeFanout = null;
5227
+ this.stopStreamHealthMonitor();
5228
+ const silenceMs = this.lastFrameAt > 0 ? Date.now() - this.lastFrameAt : -1;
5229
+ const diagnosis = silenceMs > this.streamTimeoutMs ? "camera stopped sending frames" : silenceMs >= 0 ? "stream source closed" : "no frames were ever received";
5230
+ this.logger.warn?.(
5231
+ `[Go2rtcTcpServer] native stream ended diagnosis="${diagnosis}" lastFrame=${silenceMs >= 0 ? `${(silenceMs / 1e3).toFixed(1)}s ago` : "never"} totalRx=${this.totalFramesReceived} clients=${this.connectedClients.size}`
5232
+ );
5065
5233
  if (this.dedicatedSessionRelease) {
5066
5234
  this.dedicatedSessionRelease().catch(() => {
5067
5235
  });
5068
5236
  this.dedicatedSessionRelease = void 0;
5069
5237
  }
5070
- if (this.active && (this.connectedClients.size > 0 || this.prestartStream)) {
5238
+ if (!this.prestartStream) {
5071
5239
  this.logger.info?.(
5072
- `[Go2rtcTcpServer] native stream ended, restarting (clients=${this.connectedClients.size}, prestart=${this.prestartStream})`
5240
+ `[Go2rtcTcpServer] battery native stream ended hadFrames=${hadFrames} channel=${this.channel} profile=${this.profile} \u2014 dropping ${this.connectedClients.size} client(s) to prevent wake loop`
5241
+ );
5242
+ for (const [, sock] of this.clientSockets) {
5243
+ sock.destroy();
5244
+ }
5245
+ } else if (this.active) {
5246
+ if (typeof this.api.isStreamProfileRejected === "function" && this.api.isStreamProfileRejected(this.channel, this.profile)) {
5247
+ this.logger.warn?.(
5248
+ `[Go2rtcTcpServer] profile rejected by device channel=${this.channel} profile=${this.profile} \u2014 not restarting`
5249
+ );
5250
+ for (const [, sock] of this.clientSockets) {
5251
+ sock.destroy();
5252
+ }
5253
+ return;
5254
+ }
5255
+ this.logger.info?.(
5256
+ `[Go2rtcTcpServer] restarting native stream (clients=${this.connectedClients.size}, prestart=${this.prestartStream})`
5073
5257
  );
5074
5258
  this.startNativeStream();
5075
5259
  }
5076
5260
  }
5077
5261
  });
5078
5262
  this.nativeFanout.start();
5263
+ this.startStreamHealthMonitor();
5079
5264
  }
5080
5265
  async stopNativeStream() {
5081
5266
  this.nativeStreamActive = false;
5267
+ this.stopStreamHealthMonitor();
5082
5268
  const fanout = this.nativeFanout;
5083
5269
  this.nativeFanout = null;
5084
5270
  if (fanout) {
@@ -5092,14 +5278,50 @@ var Go2rtcTcpServer = class _Go2rtcTcpServer extends EventEmitter2 {
5092
5278
  }
5093
5279
  }
5094
5280
  // -----------------------------------------------------------------------
5281
+ // Stream health monitoring
5282
+ // -----------------------------------------------------------------------
5283
+ startStreamHealthMonitor() {
5284
+ this.stopStreamHealthMonitor();
5285
+ if (this.streamTimeoutMs <= 0) return;
5286
+ this.lastFrameAt = Date.now();
5287
+ this.streamHealthTimer = setInterval(() => {
5288
+ if (!this.nativeStreamActive || !this.active) {
5289
+ this.stopStreamHealthMonitor();
5290
+ return;
5291
+ }
5292
+ const silenceMs = Date.now() - this.lastFrameAt;
5293
+ if (silenceMs > this.streamTimeoutMs) {
5294
+ this.logger.warn?.(
5295
+ `[Go2rtcTcpServer] stream inactivity timeout: no frames for ${(silenceMs / 1e3).toFixed(1)}s (threshold=${this.streamTimeoutMs}ms), totalReceived=${this.totalFramesReceived} clients=${this.connectedClients.size} \u2014 forcing stream restart`
5296
+ );
5297
+ this.stopStreamHealthMonitor();
5298
+ const fanout = this.nativeFanout;
5299
+ if (fanout) {
5300
+ this.nativeStreamActive = false;
5301
+ this.nativeFanout = null;
5302
+ fanout.stop().catch(() => {
5303
+ });
5304
+ }
5305
+ }
5306
+ }, Math.min(this.streamTimeoutMs / 2, 5e3));
5307
+ }
5308
+ stopStreamHealthMonitor() {
5309
+ if (this.streamHealthTimer) {
5310
+ clearInterval(this.streamHealthTimer);
5311
+ this.streamHealthTimer = void 0;
5312
+ }
5313
+ }
5314
+ // -----------------------------------------------------------------------
5095
5315
  // Client lifecycle
5096
5316
  // -----------------------------------------------------------------------
5097
- removeClient(clientId) {
5317
+ removeClient(clientId, reason) {
5098
5318
  if (!this.connectedClients.has(clientId)) return;
5099
5319
  this.connectedClients.delete(clientId);
5100
5320
  this.clientSockets.delete(clientId);
5321
+ const silenceMs = this.lastFrameAt > 0 ? Date.now() - this.lastFrameAt : -1;
5322
+ const silenceInfo = silenceMs >= 0 ? ` lastFrame=${(silenceMs / 1e3).toFixed(1)}s ago` : "";
5101
5323
  this.logger.info?.(
5102
- `[Go2rtcTcpServer] client disconnected id=${clientId} remaining=${this.connectedClients.size}`
5324
+ `[Go2rtcTcpServer] client disconnected id=${clientId} reason=${reason ?? "unknown"} remaining=${this.connectedClients.size} totalRx=${this.totalFramesReceived} totalTx=${this.totalVideoFramesWritten}${silenceInfo}`
5103
5325
  );
5104
5326
  this.emit("clientDisconnected", clientId);
5105
5327
  if (this.connectedClients.size === 0 && !this.prestartStream) {
@@ -7704,6 +7926,7 @@ export {
7704
7926
  HlsSessionManager,
7705
7927
  Intercom,
7706
7928
  MjpegTransformer,
7929
+ MpegTsMuxer,
7707
7930
  NVR_HUB_EXACT_TYPES,
7708
7931
  NVR_HUB_MODEL_PATTERNS,
7709
7932
  ReolinkBaichuanApi,
@@ -7762,6 +7985,7 @@ export {
7762
7985
  createRfc4571TcpServerForReplay,
7763
7986
  createRtspProxyServer,
7764
7987
  createTaggedLogger,
7988
+ decideSleepInferenceTransition,
7765
7989
  decideVideoclipTranscodeMode,
7766
7990
  decodeHeader,
7767
7991
  deriveAesKey,