prismcast 1.2.0 → 1.3.0

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 (129) hide show
  1. package/README.md +26 -1
  2. package/dist/app.js +29 -2
  3. package/dist/app.js.map +1 -1
  4. package/dist/browser/cdp.js +1 -1
  5. package/dist/browser/cdp.js.map +1 -1
  6. package/dist/browser/channelSelection.d.ts +14 -4
  7. package/dist/browser/channelSelection.js +48 -704
  8. package/dist/browser/channelSelection.js.map +1 -1
  9. package/dist/browser/index.d.ts +18 -4
  10. package/dist/browser/index.js +205 -39
  11. package/dist/browser/index.js.map +1 -1
  12. package/dist/browser/tuning/fox.d.ts +19 -0
  13. package/dist/browser/tuning/fox.js +61 -0
  14. package/dist/browser/tuning/fox.js.map +1 -0
  15. package/dist/browser/tuning/hbo.d.ts +22 -0
  16. package/dist/browser/tuning/hbo.js +182 -0
  17. package/dist/browser/tuning/hbo.js.map +1 -0
  18. package/dist/browser/tuning/hulu.d.ts +30 -0
  19. package/dist/browser/tuning/hulu.js +514 -0
  20. package/dist/browser/tuning/hulu.js.map +1 -0
  21. package/dist/browser/tuning/sling.d.ts +22 -0
  22. package/dist/browser/tuning/sling.js +287 -0
  23. package/dist/browser/tuning/sling.js.map +1 -0
  24. package/dist/browser/tuning/thumbnailRow.d.ts +18 -0
  25. package/dist/browser/tuning/thumbnailRow.js +107 -0
  26. package/dist/browser/tuning/thumbnailRow.js.map +1 -0
  27. package/dist/browser/tuning/tileClick.d.ts +18 -0
  28. package/dist/browser/tuning/tileClick.js +102 -0
  29. package/dist/browser/tuning/tileClick.js.map +1 -0
  30. package/dist/browser/tuning/youtubeTv.d.ts +17 -0
  31. package/dist/browser/tuning/youtubeTv.js +99 -0
  32. package/dist/browser/tuning/youtubeTv.js.map +1 -0
  33. package/dist/browser/video.d.ts +2 -2
  34. package/dist/browser/video.js +18 -8
  35. package/dist/browser/video.js.map +1 -1
  36. package/dist/channels/index.js +131 -12
  37. package/dist/channels/index.js.map +1 -1
  38. package/dist/config/profiles.d.ts +5 -5
  39. package/dist/config/profiles.js +35 -11
  40. package/dist/config/profiles.js.map +1 -1
  41. package/dist/config/providers.d.ts +48 -4
  42. package/dist/config/providers.js +182 -22
  43. package/dist/config/providers.js.map +1 -1
  44. package/dist/config/sites.d.ts +10 -0
  45. package/dist/config/sites.js +96 -17
  46. package/dist/config/sites.js.map +1 -1
  47. package/dist/config/userChannels.js +21 -3
  48. package/dist/config/userChannels.js.map +1 -1
  49. package/dist/config/userConfig.d.ts +2 -1
  50. package/dist/config/userConfig.js +12 -4
  51. package/dist/config/userConfig.js.map +1 -1
  52. package/dist/index.js +10 -4
  53. package/dist/index.js.map +1 -1
  54. package/dist/routes/auth.js +6 -0
  55. package/dist/routes/auth.js.map +1 -1
  56. package/dist/routes/config.d.ts +5 -0
  57. package/dist/routes/config.js +273 -35
  58. package/dist/routes/config.js.map +1 -1
  59. package/dist/routes/debug.d.ts +6 -0
  60. package/dist/routes/debug.js +274 -0
  61. package/dist/routes/debug.js.map +1 -0
  62. package/dist/routes/index.d.ts +1 -0
  63. package/dist/routes/index.js +3 -0
  64. package/dist/routes/index.js.map +1 -1
  65. package/dist/routes/logs.js +17 -5
  66. package/dist/routes/logs.js.map +1 -1
  67. package/dist/routes/root.js +507 -128
  68. package/dist/routes/root.js.map +1 -1
  69. package/dist/routes/theme.js +7 -0
  70. package/dist/routes/theme.js.map +1 -1
  71. package/dist/service/commands.js +8 -8
  72. package/dist/service/commands.js.map +1 -1
  73. package/dist/service/generators.js +22 -16
  74. package/dist/service/generators.js.map +1 -1
  75. package/dist/streaming/fmp4Segmenter.d.ts +7 -0
  76. package/dist/streaming/fmp4Segmenter.js +124 -15
  77. package/dist/streaming/fmp4Segmenter.js.map +1 -1
  78. package/dist/streaming/hls.d.ts +74 -6
  79. package/dist/streaming/hls.js +212 -130
  80. package/dist/streaming/hls.js.map +1 -1
  81. package/dist/streaming/hlsSegments.js +11 -4
  82. package/dist/streaming/hlsSegments.js.map +1 -1
  83. package/dist/streaming/lifecycle.js +1 -1
  84. package/dist/streaming/lifecycle.js.map +1 -1
  85. package/dist/streaming/monitor.d.ts +1 -1
  86. package/dist/streaming/monitor.js +202 -53
  87. package/dist/streaming/monitor.js.map +1 -1
  88. package/dist/streaming/mp4Parser.d.ts +29 -1
  89. package/dist/streaming/mp4Parser.js +243 -18
  90. package/dist/streaming/mp4Parser.js.map +1 -1
  91. package/dist/streaming/mpegts.d.ts +5 -2
  92. package/dist/streaming/mpegts.js +122 -29
  93. package/dist/streaming/mpegts.js.map +1 -1
  94. package/dist/streaming/registry.d.ts +1 -1
  95. package/dist/streaming/registry.js.map +1 -1
  96. package/dist/streaming/setup.d.ts +13 -1
  97. package/dist/streaming/setup.js +136 -36
  98. package/dist/streaming/setup.js.map +1 -1
  99. package/dist/streaming/showInfo.js +9 -9
  100. package/dist/streaming/showInfo.js.map +1 -1
  101. package/dist/streaming/statusEmitter.js +5 -6
  102. package/dist/streaming/statusEmitter.js.map +1 -1
  103. package/dist/types/index.d.ts +16 -3
  104. package/dist/types/index.js +5 -1
  105. package/dist/types/index.js.map +1 -1
  106. package/dist/utils/debugFilter.d.ts +38 -0
  107. package/dist/utils/debugFilter.js +157 -0
  108. package/dist/utils/debugFilter.js.map +1 -0
  109. package/dist/utils/evaluate.js.map +1 -1
  110. package/dist/utils/ffmpeg.d.ts +5 -0
  111. package/dist/utils/ffmpeg.js +13 -2
  112. package/dist/utils/ffmpeg.js.map +1 -1
  113. package/dist/utils/fileLogger.d.ts +2 -1
  114. package/dist/utils/fileLogger.js +4 -2
  115. package/dist/utils/fileLogger.js.map +1 -1
  116. package/dist/utils/index.d.ts +1 -0
  117. package/dist/utils/index.js +1 -0
  118. package/dist/utils/index.js.map +1 -1
  119. package/dist/utils/logEmitter.d.ts +1 -0
  120. package/dist/utils/logEmitter.js.map +1 -1
  121. package/dist/utils/logger.d.ts +9 -8
  122. package/dist/utils/logger.js +52 -28
  123. package/dist/utils/logger.js.map +1 -1
  124. package/dist/utils/retry.d.ts +1 -1
  125. package/dist/utils/retry.js +3 -3
  126. package/dist/utils/retry.js.map +1 -1
  127. package/dist/utils/version.js +4 -9
  128. package/dist/utils/version.js.map +1 -1
  129. package/package.json +6 -6
@@ -1,3 +1,4 @@
1
+ import type { Nullable } from "../types/index.js";
1
2
  /**
2
3
  * Represents a complete MP4 box with its type and data.
3
4
  */
@@ -43,4 +44,31 @@ export declare function iterateChildBoxes(data: Buffer, callback: (type: string,
43
44
  * @param moofData - The complete moof box buffer including its 8-byte header.
44
45
  * @returns true if the moof starts with a keyframe, false if it starts with a non-keyframe, or null if the flags could not be determined.
45
46
  */
46
- export declare function detectMoofKeyframe(moofData: Buffer): boolean | null;
47
+ export declare function detectMoofKeyframe(moofData: Buffer): Nullable<boolean>;
48
+ /**
49
+ * Rewrites the baseMediaDecodeTime in every tfdt box within a moof to produce monotonically increasing timestamps. For each traf, the function reads the track ID from
50
+ * tfhd, writes the current per-track timestamp into tfdt, then advances the counter by the total sample duration extracted from trun. This eliminates timestamp
51
+ * discontinuities caused by capture restarts (tab replacement, source reload recovery) because Chrome's original timestamps are completely overwritten.
52
+ *
53
+ * The rewrite is done in-place on the moof buffer. This is safe because the buffer is an owned copy created by the MP4 box parser (Buffer.from() in createMP4BoxParser).
54
+ *
55
+ * Per-track state is necessary because FFmpeg outputs separate moof+mdat pairs for audio and video, and each track may have a different timescale (e.g., 90000 for
56
+ * video, 48000 for audio). A single counter would produce incorrect timestamps for one track.
57
+ *
58
+ * @param moofData - The complete moof box buffer including its 8-byte header. Modified in place.
59
+ * @param trackTimestamps - Map from track_ID to the next baseMediaDecodeTime value. Updated in place with the advanced timestamps after rewriting.
60
+ * @returns Map from track_ID to the total duration (in timescale units) computed from trun sample durations. The caller uses this for diagnostics (zero durations
61
+ * indicate trun parsing issues) and sanity checking (abnormal durations from corrupt trun data). Each entry corresponds to one traf box in the moof.
62
+ */
63
+ export declare function rewriteMoofTimestamps(moofData: Buffer, trackTimestamps: Map<number, bigint>): Map<number, bigint>;
64
+ /**
65
+ * Extracts per-track timescale values from a moov (movie header) box. Each track in the moov contains a tkhd box with the track_ID and an mdia > mdhd box with the
66
+ * timescale. The timescale converts sample durations (in timescale units) to real seconds: seconds = duration / timescale. For example, a timescale of 16000 means
67
+ * each unit is 1/16000 of a second.
68
+ *
69
+ * Parsing path: moov > trak > { tkhd (track_ID), mdia > mdhd (timescale) }
70
+ *
71
+ * @param moovData - The complete moov box buffer including its 8-byte header.
72
+ * @returns Map from track_ID to timescale. Empty if no valid tracks are found.
73
+ */
74
+ export declare function parseMoovTimescales(moovData: Buffer): Map<number, number>;
@@ -1,7 +1,3 @@
1
- /* Copyright(C) 2024-2026, HJD (https://github.com/hjdhjd). All rights reserved.
2
- *
3
- * mp4Parser.ts: Low-level MP4 box parsing for PrismCast.
4
- */
5
1
  // Constants.
6
2
  // Minimum header size: 4 bytes size + 4 bytes type.
7
3
  const MIN_HEADER_SIZE = 8;
@@ -169,8 +165,8 @@ function evaluateSampleFlags(flags) {
169
165
  return true;
170
166
  }
171
167
  /**
172
- * Extracts the default_sample_flags value from a tfhd (track fragment header) box. The tfhd optionally carries default flags that apply to all samples in the
173
- * fragment when individual sample flags are not present in the trun. The default_sample_flags field is present only when tfhd flags bit 0x020 is set.
168
+ * Parses a tfhd (track fragment header) box and extracts the track ID, default sample duration, and default sample flags. This is the single source of truth for
169
+ * tfhd field extraction, used by both detectMoofKeyframe() (for sample flags) and rewriteMoofTimestamps() (for track ID and duration).
174
170
  *
175
171
  * tfhd layout (FullBox):
176
172
  * - [0-3] size, [4-7] "tfhd", [8] version, [9-11] flags, [12-15] track_ID
@@ -184,19 +180,16 @@ function evaluateSampleFlags(flags) {
184
180
  * @param data - The buffer containing the tfhd box.
185
181
  * @param offset - The byte offset of the tfhd box within the buffer.
186
182
  * @param size - The total size of the tfhd box.
187
- * @returns The default_sample_flags value, or null if the field is not present or the box is malformed.
183
+ * @returns The parsed tfhd fields, or null if the box is malformed.
188
184
  */
189
- function extractDefaultSampleFlags(data, offset, size) {
185
+ function parseTfhd(data, offset, size) {
190
186
  // Need at least the FullBox header (12 bytes) plus track_ID (4 bytes) = 16 bytes.
191
187
  if (size < 16) {
192
188
  return null;
193
189
  }
194
190
  const tfhdFlags = data.readUInt32BE(offset + 8) & 0x00FFFFFF;
195
- // default_sample_flags is only present when flag bit 0x020 is set.
196
- if (!(tfhdFlags & 0x000020)) {
197
- return null;
198
- }
199
- // Walk past optional fields that precede default_sample_flags. Each field is present only if its corresponding flag bit is set.
191
+ const trackId = data.readUInt32BE(offset + 12);
192
+ // Walk past optional fields in order. Each field is present only if its corresponding flag bit is set.
200
193
  let pos = offset + 16;
201
194
  if (tfhdFlags & 0x000001) {
202
195
  pos += 8;
@@ -204,17 +197,28 @@ function extractDefaultSampleFlags(data, offset, size) {
204
197
  if (tfhdFlags & 0x000002) {
205
198
  pos += 4;
206
199
  }
200
+ // Extract default_sample_duration if present.
201
+ let defaultSampleDuration = 0;
207
202
  if (tfhdFlags & 0x000008) {
203
+ if ((pos + 4) > (offset + size)) {
204
+ return null;
205
+ }
206
+ defaultSampleDuration = data.readUInt32BE(pos);
208
207
  pos += 4;
209
208
  }
209
+ // Skip default_sample_size.
210
210
  if (tfhdFlags & 0x000010) {
211
211
  pos += 4;
212
212
  }
213
- // Bounds check before reading default_sample_flags.
214
- if ((pos + 4) > (offset + size)) {
215
- return null;
213
+ // Extract default_sample_flags if present.
214
+ let defaultSampleFlags = null;
215
+ if (tfhdFlags & 0x000020) {
216
+ if ((pos + 4) > (offset + size)) {
217
+ return null;
218
+ }
219
+ defaultSampleFlags = data.readUInt32BE(pos);
216
220
  }
217
- return data.readUInt32BE(pos);
221
+ return { defaultSampleDuration, defaultSampleFlags, trackId };
218
222
  }
219
223
  /**
220
224
  * Extracts the sample flags for the first sample in a trun (track fragment run) box. The flags are resolved from three sources in priority order:
@@ -308,7 +312,7 @@ export function detectMoofKeyframe(moofData) {
308
312
  // spec-mandated box ordering, so defaultSampleFlags will be populated before any trun is processed.
309
313
  iterateChildBoxes(trafData, (childType, childData, childOffset, childSize) => {
310
314
  if (childType === "tfhd") {
311
- defaultSampleFlags = extractDefaultSampleFlags(childData, childOffset, childSize);
315
+ defaultSampleFlags = parseTfhd(childData, childOffset, childSize)?.defaultSampleFlags ?? null;
312
316
  }
313
317
  else if (childType === "trun") {
314
318
  const sampleFlags = extractFirstSampleFlags(childData, childOffset, childSize, defaultSampleFlags);
@@ -338,4 +342,225 @@ export function detectMoofKeyframe(moofData) {
338
342
  // No definitive signal from any traf.
339
343
  return null;
340
344
  }
345
+ // Timestamp Rewriting.
346
+ /**
347
+ * Computes the total duration of all samples in a trun (track fragment run) box. The duration is the sum of per-sample durations when present (trun flags bit 0x100),
348
+ * or sampleCount * defaultSampleDuration as a fallback. The result is returned as a BigInt for safe accumulation into the 64-bit timestamp counter.
349
+ *
350
+ * trun layout (FullBox):
351
+ * - [0-3] size, [4-7] "trun", [8] version, [9-11] flags, [12-15] sample_count
352
+ * - Optional fields after sample_count:
353
+ * 0x001: data_offset (4 bytes)
354
+ * 0x004: first_sample_flags (4 bytes)
355
+ * - Per-sample entries (each containing optional fields based on flags):
356
+ * 0x100: sample_duration (4 bytes)
357
+ * 0x200: sample_size (4 bytes)
358
+ * 0x400: sample_flags (4 bytes)
359
+ * 0x800: sample_composition_time_offset (4 bytes)
360
+ *
361
+ * @param data - The buffer containing the trun box.
362
+ * @param offset - The byte offset of the trun box within the buffer.
363
+ * @param size - The total size of the trun box.
364
+ * @param defaultSampleDuration - The default sample duration from the parent tfhd, used when per-sample durations are absent.
365
+ * @returns The total duration of all samples as a BigInt, or 0n if the box is malformed or empty.
366
+ */
367
+ function extractTrunTotalDuration(data, offset, size, defaultSampleDuration) {
368
+ // Need at least the FullBox header (12 bytes) plus sample_count (4 bytes) = 16 bytes.
369
+ if (size < 16) {
370
+ return 0n;
371
+ }
372
+ const trunFlags = data.readUInt32BE(offset + 8) & 0x00FFFFFF;
373
+ const sampleCount = data.readUInt32BE(offset + 12);
374
+ if (sampleCount === 0) {
375
+ return 0n;
376
+ }
377
+ // If per-sample durations are not present, use defaultSampleDuration * sampleCount.
378
+ if (!(trunFlags & 0x100)) {
379
+ return BigInt(defaultSampleDuration) * BigInt(sampleCount);
380
+ }
381
+ // Compute the byte size of each per-sample entry from the trun flags. Each optional field adds 4 bytes to the entry. The duration field (0x100) is always present
382
+ // here because we returned early above when it was not set.
383
+ let entrySize = 4;
384
+ if (trunFlags & 0x200) {
385
+ entrySize += 4;
386
+ }
387
+ if (trunFlags & 0x400) {
388
+ entrySize += 4;
389
+ }
390
+ if (trunFlags & 0x800) {
391
+ entrySize += 4;
392
+ }
393
+ // Walk past the optional header fields to reach the sample entries.
394
+ let pos = offset + 16;
395
+ if (trunFlags & 0x001) {
396
+ pos += 4;
397
+ }
398
+ if (trunFlags & 0x004) {
399
+ pos += 4;
400
+ }
401
+ // Sum per-sample durations. The duration field is the first field in each entry (when present), since entry fields appear in order: duration, size, flags,
402
+ // composition_time_offset.
403
+ let totalDuration = 0n;
404
+ const endPos = offset + size;
405
+ for (let i = 0; i < sampleCount; i++) {
406
+ if ((pos + 4) > endPos) {
407
+ break;
408
+ }
409
+ totalDuration += BigInt(data.readUInt32BE(pos));
410
+ pos += entrySize;
411
+ }
412
+ return totalDuration;
413
+ }
414
+ /**
415
+ * Rewrites the baseMediaDecodeTime in every tfdt box within a moof to produce monotonically increasing timestamps. For each traf, the function reads the track ID from
416
+ * tfhd, writes the current per-track timestamp into tfdt, then advances the counter by the total sample duration extracted from trun. This eliminates timestamp
417
+ * discontinuities caused by capture restarts (tab replacement, source reload recovery) because Chrome's original timestamps are completely overwritten.
418
+ *
419
+ * The rewrite is done in-place on the moof buffer. This is safe because the buffer is an owned copy created by the MP4 box parser (Buffer.from() in createMP4BoxParser).
420
+ *
421
+ * Per-track state is necessary because FFmpeg outputs separate moof+mdat pairs for audio and video, and each track may have a different timescale (e.g., 90000 for
422
+ * video, 48000 for audio). A single counter would produce incorrect timestamps for one track.
423
+ *
424
+ * @param moofData - The complete moof box buffer including its 8-byte header. Modified in place.
425
+ * @param trackTimestamps - Map from track_ID to the next baseMediaDecodeTime value. Updated in place with the advanced timestamps after rewriting.
426
+ * @returns Map from track_ID to the total duration (in timescale units) computed from trun sample durations. The caller uses this for diagnostics (zero durations
427
+ * indicate trun parsing issues) and sanity checking (abnormal durations from corrupt trun data). Each entry corresponds to one traf box in the moof.
428
+ */
429
+ export function rewriteMoofTimestamps(moofData, trackTimestamps) {
430
+ const trafDurations = new Map();
431
+ // Walk the moof's child boxes looking for traf (track fragment) boxes.
432
+ iterateChildBoxes(moofData, (type, data, offset, size) => {
433
+ if (type !== "traf") {
434
+ return;
435
+ }
436
+ // Create a subarray for this traf so we can iterate its child boxes. Buffer.subarray() shares memory with the parent, so this is O(1) with no data copying.
437
+ const trafData = data.subarray(offset, offset + size);
438
+ let tfhdInfo = null;
439
+ let totalDuration = 0n;
440
+ // Walk the traf's child boxes. The spec mandates tfhd before tfdt before trun, so we process them in order. The first pass extracts tfhd info and rewrites
441
+ // tfdt. The trun duration extraction happens in the same pass since tfhd is already available by the time trun is encountered.
442
+ iterateChildBoxes(trafData, (childType, childData, childOffset, childSize) => {
443
+ if (childType === "tfhd") {
444
+ tfhdInfo = parseTfhd(childData, childOffset, childSize);
445
+ }
446
+ else if (childType === "tfdt") {
447
+ // Rewrite baseMediaDecodeTime with the current per-track timestamp. The tfhd must precede tfdt per the ISO 14496-12 box ordering, so tfhdInfo is
448
+ // available here.
449
+ if (!tfhdInfo) {
450
+ return;
451
+ }
452
+ const currentTimestamp = trackTimestamps.get(tfhdInfo.trackId) ?? 0n;
453
+ // tfdt layout (FullBox): [0-3] size, [4-7] "tfdt", [8] version, [9-11] flags, [12+] baseMediaDecodeTime.
454
+ // Version 0: 32-bit baseMediaDecodeTime at offset 12. Version 1: 64-bit baseMediaDecodeTime at offset 12.
455
+ if (childSize < 16) {
456
+ return;
457
+ }
458
+ const version = childData.readUInt8(childOffset + 8);
459
+ if (version === 0) {
460
+ // Version 0: write as uint32 (truncate to 32 bits). Overflows at ~13 hours for 90000 Hz timescale, but version 0 tfdt boxes are inherently 32-bit.
461
+ childData.writeUInt32BE(Number(currentTimestamp & 0xffffffffn), childOffset + 12);
462
+ }
463
+ else {
464
+ // Version 1: write as uint64 (high 32 bits + low 32 bits). Requires at least 20 bytes in the tfdt box.
465
+ if (childSize < 20) {
466
+ return;
467
+ }
468
+ const high = Number((currentTimestamp >> 32n) & 0xffffffffn);
469
+ const low = Number(currentTimestamp & 0xffffffffn);
470
+ childData.writeUInt32BE(high, childOffset + 12);
471
+ childData.writeUInt32BE(low, childOffset + 16);
472
+ }
473
+ }
474
+ else if (childType === "trun") {
475
+ // Accumulate sample durations from each trun. A traf can contain multiple trun boxes (though rare in practice). The tfhd must precede trun per spec
476
+ // ordering, so tfhdInfo and its defaultSampleDuration are available here.
477
+ if (tfhdInfo) {
478
+ totalDuration += extractTrunTotalDuration(childData, childOffset, childSize, tfhdInfo.defaultSampleDuration);
479
+ }
480
+ }
481
+ });
482
+ // Advance the per-track timestamp counter by the total sample duration from this traf. A zero duration with a valid tfhd indicates a trun parsing issue —
483
+ // the caller should log a warning. We still advance (by zero) to avoid corrupting subsequent timestamps. TypeScript's control flow analysis cannot track
484
+ // mutations made inside the iterateChildBoxes callback, so tfhdInfo appears "always null" to the linter despite being set at runtime.
485
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
486
+ if (tfhdInfo) {
487
+ const info = tfhdInfo;
488
+ trafDurations.set(info.trackId, totalDuration);
489
+ const currentTimestamp = trackTimestamps.get(info.trackId) ?? 0n;
490
+ trackTimestamps.set(info.trackId, currentTimestamp + totalDuration);
491
+ }
492
+ });
493
+ return trafDurations;
494
+ }
495
+ // Moov Timescale Extraction.
496
+ /**
497
+ * Extracts per-track timescale values from a moov (movie header) box. Each track in the moov contains a tkhd box with the track_ID and an mdia > mdhd box with the
498
+ * timescale. The timescale converts sample durations (in timescale units) to real seconds: seconds = duration / timescale. For example, a timescale of 16000 means
499
+ * each unit is 1/16000 of a second.
500
+ *
501
+ * Parsing path: moov > trak > { tkhd (track_ID), mdia > mdhd (timescale) }
502
+ *
503
+ * @param moovData - The complete moov box buffer including its 8-byte header.
504
+ * @returns Map from track_ID to timescale. Empty if no valid tracks are found.
505
+ */
506
+ export function parseMoovTimescales(moovData) {
507
+ const result = new Map();
508
+ // Walk the moov's child boxes looking for trak (track) boxes.
509
+ iterateChildBoxes(moovData, (type, data, offset, size) => {
510
+ if (type !== "trak") {
511
+ return;
512
+ }
513
+ const trakData = data.subarray(offset, offset + size);
514
+ let trackId = null;
515
+ let timescale = null;
516
+ // Walk the trak's child boxes to find tkhd (track header) and mdia (media container). The spec mandates tkhd before mdia, so trackId is available before we
517
+ // need it, but we don't depend on ordering — both are extracted independently and combined after iteration.
518
+ iterateChildBoxes(trakData, (childType, childData, childOffset, childSize) => {
519
+ if (childType === "tkhd") {
520
+ // tkhd layout (FullBox): [0-3] size, [4-7] "tkhd", [8] version, [9-11] flags.
521
+ // Version 0: [12-15] creation_time, [16-19] modification_time, [20-23] track_ID.
522
+ // Version 1: [12-19] creation_time, [20-27] modification_time, [28-31] track_ID.
523
+ if (childSize < 16) {
524
+ return;
525
+ }
526
+ const version = childData.readUInt8(childOffset + 8);
527
+ if ((version === 0) && (childSize >= 24)) {
528
+ trackId = childData.readUInt32BE(childOffset + 20);
529
+ }
530
+ else if ((version === 1) && (childSize >= 32)) {
531
+ trackId = childData.readUInt32BE(childOffset + 28);
532
+ }
533
+ }
534
+ else if (childType === "mdia") {
535
+ // Walk the mdia's child boxes to find mdhd (media header) which contains the timescale.
536
+ const mdiaData = childData.subarray(childOffset, childOffset + childSize);
537
+ iterateChildBoxes(mdiaData, (mdiaChildType, mdiaChildData, mdiaChildOffset, mdiaChildSize) => {
538
+ if (mdiaChildType !== "mdhd") {
539
+ return;
540
+ }
541
+ // mdhd layout (FullBox): [0-3] size, [4-7] "mdhd", [8] version, [9-11] flags.
542
+ // Version 0: [12-15] creation_time, [16-19] modification_time, [20-23] timescale.
543
+ // Version 1: [12-19] creation_time, [20-27] modification_time, [28-31] timescale.
544
+ if (mdiaChildSize < 16) {
545
+ return;
546
+ }
547
+ const mdhdVersion = mdiaChildData.readUInt8(mdiaChildOffset + 8);
548
+ if ((mdhdVersion === 0) && (mdiaChildSize >= 24)) {
549
+ timescale = mdiaChildData.readUInt32BE(mdiaChildOffset + 20);
550
+ }
551
+ else if ((mdhdVersion === 1) && (mdiaChildSize >= 32)) {
552
+ timescale = mdiaChildData.readUInt32BE(mdiaChildOffset + 28);
553
+ }
554
+ });
555
+ }
556
+ });
557
+ // Store the track if both trackId and timescale were successfully extracted. TypeScript's control flow analysis cannot track mutations made inside the
558
+ // iterateChildBoxes callbacks, so these variables appear "always null" to the linter despite being set at runtime.
559
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
560
+ if ((trackId !== null) && (timescale !== null) && (timescale > 0)) {
561
+ result.set(trackId, timescale);
562
+ }
563
+ });
564
+ return result;
565
+ }
341
566
  //# sourceMappingURL=mp4Parser.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"mp4Parser.js","sourceRoot":"","sources":["../../src/streaming/mp4Parser.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"mp4Parser.js","sourceRoot":"","sources":["../../src/streaming/mp4Parser.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,10 @@
1
1
  import type { Request, Response } from "express";
2
2
  /**
3
- * Handles MPEG-TS stream requests. Ensures a capture is running for the requested channel, waits for the init segment, spawns a per-client FFmpeg remuxer to convert
4
- * fMP4 to MPEG-TS, and streams the output to the HTTP response.
3
+ * Handles MPEG-TS stream requests. Validates the channel, flushes HTTP headers early for new streams, then ensures a capture is running, waits for the init segment,
4
+ * spawns a per-client FFmpeg remuxer, and streams the output.
5
+ *
6
+ * For new streams, headers are flushed before stream setup begins so the client sees an immediate 200 response. This prevents timeout failures during the 4-10+
7
+ * second startup sequence. The trade-off is that error responses cannot be sent after the flush — failures are logged server-side and the connection is closed.
5
8
  *
6
9
  * Route: GET /stream/:name
7
10
  *
@@ -1,30 +1,36 @@
1
- /* Copyright(C) 2024-2026, HJD (https://github.com/hjdhjd). All rights reserved.
2
- *
3
- * mpegts.ts: MPEG-TS streaming handler for PrismCast.
4
- */
5
1
  import { LOG, formatError, spawnMpegTsRemuxer } from "../utils/index.js";
2
+ import { awaitStreamReadySilent, initializeStream, sendValidationError, validateChannel } from "./hls.js";
6
3
  import { getStream, updateLastAccess } from "./registry.js";
7
4
  import { registerClient, unregisterClient } from "./clients.js";
8
5
  import { CONFIG } from "../config/index.js";
9
- import { ensureChannelStream } from "./hls.js";
6
+ import { StreamSetupError } from "./setup.js";
7
+ import { getChannelStreamId } from "./lifecycle.js";
10
8
  import { waitForInitSegment } from "./hlsSegments.js";
11
9
  /* This module provides a continuous MPEG-TS byte stream from the same capture pipeline used for HLS. It is designed for HDHomeRun-compatible clients (such as Plex)
12
10
  * that expect raw MPEG-TS when tuning a channel. The existing capture → segmenter → HLS segments flow is unchanged. Each MPEG-TS client gets its own FFmpeg remuxer
13
11
  * that converts stored fMP4 segments to MPEG-TS with codec copy (no transcoding).
14
12
  *
15
13
  * Data flow per client:
16
- * 1. ensureChannelStream() starts or reuses an HLS capture
17
- * 2. Wait for the init segment (ftyp+moov codec configuration)
18
- * 3. Spawn FFmpeg: -f mp4 -i pipe:0 -c copy -f mpegts pipe:1
19
- * 4. Write init segment + existing media segments to FFmpeg stdin
20
- * 5. Subscribe to segment events for new segments in real time
21
- * 6. Pipe FFmpeg stdout to the HTTP response as video/mp2t
22
- * 7. On client disconnect or stream termination, kill FFmpeg and clean up
14
+ * 1. Validate channel and check for existing stream
15
+ * 2. If new stream needed, flush HTTP 200 headers immediately (so the client sees "connection accepted")
16
+ * 3. initializeStream() starts the capture, or awaitStreamReadySilent() waits for an in-progress startup
17
+ * 4. Wait for the init segment (ftyp+moov codec configuration)
18
+ * 5. Spawn FFmpeg: -f mp4 -i pipe:0 -c copy -f mpegts pipe:1
19
+ * 6. Write init segment + existing media segments to FFmpeg stdin
20
+ * 7. Subscribe to segment events for new segments in real time
21
+ * 8. Pipe FFmpeg stdout to the HTTP response as video/mp2t
22
+ * 9. On client disconnect or stream termination, kill FFmpeg and clean up
23
+ *
24
+ * The header flush in step 2 prevents client timeouts. Without it, the client receives zero bytes until the entire stream setup completes (4-10+ seconds), which may
25
+ * exceed the client's connection timeout.
23
26
  */
24
27
  // Public Endpoint Handler.
25
28
  /**
26
- * Handles MPEG-TS stream requests. Ensures a capture is running for the requested channel, waits for the init segment, spawns a per-client FFmpeg remuxer to convert
27
- * fMP4 to MPEG-TS, and streams the output to the HTTP response.
29
+ * Handles MPEG-TS stream requests. Validates the channel, flushes HTTP headers early for new streams, then ensures a capture is running, waits for the init segment,
30
+ * spawns a per-client FFmpeg remuxer, and streams the output.
31
+ *
32
+ * For new streams, headers are flushed before stream setup begins so the client sees an immediate 200 response. This prevents timeout failures during the 4-10+
33
+ * second startup sequence. The trade-off is that error responses cannot be sent after the flush — failures are logged server-side and the connection is closed.
28
34
  *
29
35
  * Route: GET /stream/:name
30
36
  *
@@ -37,23 +43,108 @@ export async function handleMpegTsStream(req, res) {
37
43
  res.status(400).send("Channel name is required.");
38
44
  return;
39
45
  }
40
- // Ensure a stream is running for this channel. This validates the channel, starts a stream if needed, and handles concurrent startup deduplication.
41
- const streamId = await ensureChannelStream(channelName, req, res);
42
- if (streamId === null) {
43
- // Error response already sent by ensureChannelStream.
46
+ // Check for an existing stream first. If one exists, we can skip validation and header flushing.
47
+ const existingStreamId = getChannelStreamId(channelName);
48
+ // Fast path: a real stream already exists. No early flush needed — the stream data will flow quickly.
49
+ if ((existingStreamId !== undefined) && (existingStreamId !== -1)) {
50
+ await serveMpegTsStream(existingStreamId, channelName, req, res);
44
51
  return;
45
52
  }
53
+ // If no existing stream or startup in progress, validate the channel before flushing headers. This ensures we can still return proper error responses for invalid
54
+ // channels, disabled channels, and login mode. Store the validated channel for use during stream initialization below.
55
+ let validatedChannel;
56
+ if (existingStreamId === undefined) {
57
+ const validation = validateChannel(channelName);
58
+ if (!validation.valid) {
59
+ sendValidationError(validation, res);
60
+ return;
61
+ }
62
+ validatedChannel = validation.channel;
63
+ }
64
+ // Flush HTTP 200 headers immediately. The client sees "connection accepted, data coming" and waits patiently. After this point, we cannot send error status codes —
65
+ // failures will close the connection with no data.
66
+ res.setHeader("Cache-Control", "no-cache");
67
+ res.setHeader("Connection", "close");
68
+ res.setHeader("Content-Type", "video/mp2t");
69
+ res.flushHeaders();
70
+ // Acquire the stream. If a startup is in progress (another request started it), poll silently. Otherwise, start a new stream via initializeStream().
71
+ let streamId;
72
+ if (existingStreamId === -1) {
73
+ // Another request is already starting this stream. Wait silently (no error responses possible after flush).
74
+ streamId = await awaitStreamReadySilent(channelName);
75
+ if (streamId === null) {
76
+ LOG.warn("MPEG-TS stream startup failed for %s (startup did not complete).", channelName);
77
+ res.end();
78
+ return;
79
+ }
80
+ }
81
+ else {
82
+ // Start a new stream directly. validatedChannel is guaranteed set: this branch runs only when existingStreamId === undefined, which requires successful
83
+ // validation above. Since headers are already flushed, errors are logged and the connection is closed.
84
+ if (!validatedChannel) {
85
+ res.end();
86
+ return;
87
+ }
88
+ try {
89
+ streamId = await initializeStream({
90
+ channel: validatedChannel,
91
+ channelName,
92
+ clientAddress: req.ip ?? req.socket.remoteAddress ?? null,
93
+ profileOverride: req.query.profile,
94
+ url: validatedChannel.url
95
+ });
96
+ }
97
+ catch (error) {
98
+ if (error instanceof StreamSetupError) {
99
+ LOG.warn("MPEG-TS stream startup failed for %s: %s.", channelName, error.userMessage);
100
+ }
101
+ else {
102
+ LOG.warn("MPEG-TS stream startup failed for %s: %s.", channelName, formatError(error));
103
+ }
104
+ res.end();
105
+ return;
106
+ }
107
+ if (streamId === null) {
108
+ LOG.warn("MPEG-TS stream startup failed for %s (terminated during setup).", channelName);
109
+ res.end();
110
+ return;
111
+ }
112
+ }
113
+ await serveMpegTsStream(streamId, channelName, req, res);
114
+ }
115
+ // Internal Helpers.
116
+ /**
117
+ * Serves the MPEG-TS stream once a stream ID is available. Waits for the init segment, spawns the FFmpeg remuxer, and pipes the output to the response. This is the
118
+ * shared implementation used by both the fast path (existing stream) and the flush path (new stream).
119
+ *
120
+ * @param streamId - The numeric stream ID.
121
+ * @param channelName - The channel name for logging.
122
+ * @param req - Express request object.
123
+ * @param res - Express response object.
124
+ */
125
+ async function serveMpegTsStream(streamId, channelName, req, res) {
46
126
  // Wait for the init segment to be available. The init segment contains codec configuration (ftyp+moov boxes) that FFmpeg needs before it can process media segments.
47
127
  const initReady = await waitForInitSegment(streamId, CONFIG.streaming.navigationTimeout);
48
128
  if (!initReady) {
49
- res.setHeader("Retry-After", "5");
50
- res.status(503).send("Stream is starting. Please retry.");
129
+ if (!res.headersSent) {
130
+ res.setHeader("Retry-After", "5");
131
+ res.status(503).send("Stream is starting. Please retry.");
132
+ }
133
+ else {
134
+ LOG.warn("MPEG-TS init segment timeout for %s.", channelName);
135
+ res.end();
136
+ }
51
137
  return;
52
138
  }
53
139
  // Get the stream from the registry and verify it's still alive with a valid init segment.
54
140
  const stream = getStream(streamId);
55
- if (!stream || !stream.hls.initSegment) {
56
- res.status(500).send("Stream no longer available.");
141
+ if (!stream?.hls.initSegment) {
142
+ if (!res.headersSent) {
143
+ res.status(500).send("Stream no longer available.");
144
+ }
145
+ else {
146
+ res.end();
147
+ }
57
148
  return;
58
149
  }
59
150
  // Capture client address for client tracking. Captured before any async operations so it remains consistent in the cleanup closure, even if the request object
@@ -74,7 +165,7 @@ export async function handleMpegTsStream(req, res) {
74
165
  // Spawn an FFmpeg process to remux fMP4 to MPEG-TS. The process reads concatenated fMP4 (init segment + media segments) from stdin and outputs a continuous
75
166
  // MPEG-TS stream on stdout. Video (H264) and audio (AAC) are copied without transcoding.
76
167
  const remuxer = spawnMpegTsRemuxer((error) => {
77
- streamLog.debug("MPEG-TS remuxer error: %s.", formatError(error));
168
+ streamLog.debug("streaming:mpegts", "MPEG-TS remuxer error: %s.", formatError(error));
78
169
  cleanup();
79
170
  if (!res.writableEnded) {
80
171
  res.end();
@@ -121,7 +212,7 @@ export async function handleMpegTsStream(req, res) {
121
212
  stream.hls.segmentEmitter.off("segment", onSegment);
122
213
  stream.hls.segmentEmitter.off("terminated", onTerminated);
123
214
  remuxer.kill();
124
- streamLog.debug("MPEG-TS client disconnected.");
215
+ streamLog.debug("streaming:mpegts", "MPEG-TS client disconnected.");
125
216
  };
126
217
  // Clean up when the client disconnects. Registered immediately after cleanup is assigned to minimize the window where a disconnect could be missed.
127
218
  req.on("close", () => {
@@ -130,10 +221,12 @@ export async function handleMpegTsStream(req, res) {
130
221
  // Subscribe to segment events BEFORE writing existing segments to avoid missing any segments added during the catchup phase.
131
222
  stream.hls.segmentEmitter.on("segment", onSegment);
132
223
  stream.hls.segmentEmitter.on("terminated", onTerminated);
133
- // Set response headers for MPEG-TS streaming.
134
- res.setHeader("Cache-Control", "no-cache");
135
- res.setHeader("Connection", "close");
136
- res.setHeader("Content-Type", "video/mp2t");
224
+ // Set response headers if they haven't been flushed yet (fast path for existing streams).
225
+ if (!res.headersSent) {
226
+ res.setHeader("Cache-Control", "no-cache");
227
+ res.setHeader("Connection", "close");
228
+ res.setHeader("Content-Type", "video/mp2t");
229
+ }
137
230
  // Pipe FFmpeg stdout to the HTTP response. When FFmpeg exits (either from stdin ending or being killed), stdout closes and the response ends automatically.
138
231
  remuxer.stdout.pipe(res);
139
232
  // Write the init segment first — FFmpeg needs the ftyp and moov boxes before it can process any media segments.
@@ -148,6 +241,6 @@ export async function handleMpegTsStream(req, res) {
148
241
  sentSegments.add(filename);
149
242
  remuxer.stdin.write(data);
150
243
  }
151
- streamLog.debug("MPEG-TS client connected.");
244
+ streamLog.debug("streaming:mpegts", "MPEG-TS client connected.");
152
245
  }
153
246
  //# sourceMappingURL=mpegts.js.map