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.
- package/README.md +26 -1
- package/dist/app.js +29 -2
- package/dist/app.js.map +1 -1
- package/dist/browser/cdp.js +1 -1
- package/dist/browser/cdp.js.map +1 -1
- package/dist/browser/channelSelection.d.ts +14 -4
- package/dist/browser/channelSelection.js +48 -704
- package/dist/browser/channelSelection.js.map +1 -1
- package/dist/browser/index.d.ts +18 -4
- package/dist/browser/index.js +205 -39
- package/dist/browser/index.js.map +1 -1
- package/dist/browser/tuning/fox.d.ts +19 -0
- package/dist/browser/tuning/fox.js +61 -0
- package/dist/browser/tuning/fox.js.map +1 -0
- package/dist/browser/tuning/hbo.d.ts +22 -0
- package/dist/browser/tuning/hbo.js +182 -0
- package/dist/browser/tuning/hbo.js.map +1 -0
- package/dist/browser/tuning/hulu.d.ts +30 -0
- package/dist/browser/tuning/hulu.js +514 -0
- package/dist/browser/tuning/hulu.js.map +1 -0
- package/dist/browser/tuning/sling.d.ts +22 -0
- package/dist/browser/tuning/sling.js +287 -0
- package/dist/browser/tuning/sling.js.map +1 -0
- package/dist/browser/tuning/thumbnailRow.d.ts +18 -0
- package/dist/browser/tuning/thumbnailRow.js +107 -0
- package/dist/browser/tuning/thumbnailRow.js.map +1 -0
- package/dist/browser/tuning/tileClick.d.ts +18 -0
- package/dist/browser/tuning/tileClick.js +102 -0
- package/dist/browser/tuning/tileClick.js.map +1 -0
- package/dist/browser/tuning/youtubeTv.d.ts +17 -0
- package/dist/browser/tuning/youtubeTv.js +99 -0
- package/dist/browser/tuning/youtubeTv.js.map +1 -0
- package/dist/browser/video.d.ts +2 -2
- package/dist/browser/video.js +18 -8
- package/dist/browser/video.js.map +1 -1
- package/dist/channels/index.js +131 -12
- package/dist/channels/index.js.map +1 -1
- package/dist/config/profiles.d.ts +5 -5
- package/dist/config/profiles.js +35 -11
- package/dist/config/profiles.js.map +1 -1
- package/dist/config/providers.d.ts +48 -4
- package/dist/config/providers.js +182 -22
- package/dist/config/providers.js.map +1 -1
- package/dist/config/sites.d.ts +10 -0
- package/dist/config/sites.js +96 -17
- package/dist/config/sites.js.map +1 -1
- package/dist/config/userChannels.js +21 -3
- package/dist/config/userChannels.js.map +1 -1
- package/dist/config/userConfig.d.ts +2 -1
- package/dist/config/userConfig.js +12 -4
- package/dist/config/userConfig.js.map +1 -1
- package/dist/index.js +10 -4
- package/dist/index.js.map +1 -1
- package/dist/routes/auth.js +6 -0
- package/dist/routes/auth.js.map +1 -1
- package/dist/routes/config.d.ts +5 -0
- package/dist/routes/config.js +273 -35
- package/dist/routes/config.js.map +1 -1
- package/dist/routes/debug.d.ts +6 -0
- package/dist/routes/debug.js +274 -0
- package/dist/routes/debug.js.map +1 -0
- package/dist/routes/index.d.ts +1 -0
- package/dist/routes/index.js +3 -0
- package/dist/routes/index.js.map +1 -1
- package/dist/routes/logs.js +17 -5
- package/dist/routes/logs.js.map +1 -1
- package/dist/routes/root.js +507 -128
- package/dist/routes/root.js.map +1 -1
- package/dist/routes/theme.js +7 -0
- package/dist/routes/theme.js.map +1 -1
- package/dist/service/commands.js +8 -8
- package/dist/service/commands.js.map +1 -1
- package/dist/service/generators.js +22 -16
- package/dist/service/generators.js.map +1 -1
- package/dist/streaming/fmp4Segmenter.d.ts +7 -0
- package/dist/streaming/fmp4Segmenter.js +124 -15
- package/dist/streaming/fmp4Segmenter.js.map +1 -1
- package/dist/streaming/hls.d.ts +74 -6
- package/dist/streaming/hls.js +212 -130
- package/dist/streaming/hls.js.map +1 -1
- package/dist/streaming/hlsSegments.js +11 -4
- package/dist/streaming/hlsSegments.js.map +1 -1
- package/dist/streaming/lifecycle.js +1 -1
- package/dist/streaming/lifecycle.js.map +1 -1
- package/dist/streaming/monitor.d.ts +1 -1
- package/dist/streaming/monitor.js +202 -53
- package/dist/streaming/monitor.js.map +1 -1
- package/dist/streaming/mp4Parser.d.ts +29 -1
- package/dist/streaming/mp4Parser.js +243 -18
- package/dist/streaming/mp4Parser.js.map +1 -1
- package/dist/streaming/mpegts.d.ts +5 -2
- package/dist/streaming/mpegts.js +122 -29
- package/dist/streaming/mpegts.js.map +1 -1
- package/dist/streaming/registry.d.ts +1 -1
- package/dist/streaming/registry.js.map +1 -1
- package/dist/streaming/setup.d.ts +13 -1
- package/dist/streaming/setup.js +136 -36
- package/dist/streaming/setup.js.map +1 -1
- package/dist/streaming/showInfo.js +9 -9
- package/dist/streaming/showInfo.js.map +1 -1
- package/dist/streaming/statusEmitter.js +5 -6
- package/dist/streaming/statusEmitter.js.map +1 -1
- package/dist/types/index.d.ts +16 -3
- package/dist/types/index.js +5 -1
- package/dist/types/index.js.map +1 -1
- package/dist/utils/debugFilter.d.ts +38 -0
- package/dist/utils/debugFilter.js +157 -0
- package/dist/utils/debugFilter.js.map +1 -0
- package/dist/utils/evaluate.js.map +1 -1
- package/dist/utils/ffmpeg.d.ts +5 -0
- package/dist/utils/ffmpeg.js +13 -2
- package/dist/utils/ffmpeg.js.map +1 -1
- package/dist/utils/fileLogger.d.ts +2 -1
- package/dist/utils/fileLogger.js +4 -2
- package/dist/utils/fileLogger.js.map +1 -1
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/logEmitter.d.ts +1 -0
- package/dist/utils/logEmitter.js.map +1 -1
- package/dist/utils/logger.d.ts +9 -8
- package/dist/utils/logger.js +52 -28
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/retry.d.ts +1 -1
- package/dist/utils/retry.js +3 -3
- package/dist/utils/retry.js.map +1 -1
- package/dist/utils/version.js +4 -9
- package/dist/utils/version.js.map +1 -1
- 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
|
|
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
|
-
*
|
|
173
|
-
*
|
|
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
|
|
183
|
+
* @returns The parsed tfhd fields, or null if the box is malformed.
|
|
188
184
|
*/
|
|
189
|
-
function
|
|
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
|
-
|
|
196
|
-
if
|
|
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
|
-
//
|
|
214
|
-
|
|
215
|
-
|
|
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
|
|
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 =
|
|
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":"AAAA;;;GAGG;AAmDH,aAAa;AAEb,oDAAoD;AACpD,MAAM,eAAe,GAAG,CAAC,CAAC;AAE1B,mFAAmF;AACnF,MAAM,oBAAoB,GAAG,EAAE,CAAC;AAEhC,oBAAoB;AAEpB;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,KAAqB;IAEtD,2CAA2C;IAC3C,IAAI,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAE7B;;OAEG;IACH,SAAS,aAAa;QAEpB,gEAAgE;QAChE,OAAM,MAAM,CAAC,MAAM,IAAI,eAAe,EAAE,CAAC;YAEvC,uCAAuC;YACvC,MAAM,SAAS,GAAG,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;YAEzC,6BAA6B;YAC7B,IAAI,OAAe,CAAC;YACpB,IAAI,UAAkB,CAAC;YAEvB,IAAG,SAAS,KAAK,CAAC,EAAE,CAAC;gBAEnB,gDAAgD;gBAChD,IAAG,MAAM,CAAC,MAAM,GAAG,oBAAoB,EAAE,CAAC;oBAExC,2CAA2C;oBAC3C,OAAO;gBACT,CAAC;gBAED,4JAA4J;gBAC5J,0DAA0D;gBAC1D,MAAM,gBAAgB,GAAG,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;gBAChD,MAAM,eAAe,GAAG,MAAM,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC;gBAEhD,+CAA+C;gBAC/C,IAAG,gBAAgB,GAAG,CAAC,EAAE,CAAC;oBAExB,kHAAkH;oBAClH,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;oBAE5B,SAAS;gBACX,CAAC;gBAED,OAAO,GAAG,eAAe,CAAC;gBAC1B,UAAU,GAAG,oBAAoB,CAAC;YACpC,CAAC;iBAAM,IAAG,SAAS,KAAK,CAAC,EAAE,CAAC;gBAE1B,sGAAsG;gBACtG,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;gBAE5B,SAAS;YACX,CAAC;iBAAM,CAAC;gBAEN,OAAO,GAAG,SAAS,CAAC;gBACpB,UAAU,GAAG,eAAe,CAAC;YAC/B,CAAC;YAED,2DAA2D;YAC3D,IAAG,OAAO,GAAG,UAAU,EAAE,CAAC;gBAExB,gDAAgD;gBAChD,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;gBAE5B,SAAS;YACX,CAAC;YAED,qCAAqC;YACrC,IAAG,MAAM,CAAC,MAAM,GAAG,OAAO,EAAE,CAAC;gBAE3B,uBAAuB;gBACvB,OAAO;YACT,CAAC;YAED,4BAA4B;YAC5B,MAAM,OAAO,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;YAC5C,MAAM,OAAO,GAAG,MAAM,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;YAE/C,gBAAgB;YAChB,KAAK,CAAC;gBAEJ,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC;gBAC1B,IAAI,EAAE,OAAO;gBACb,IAAI,EAAE,OAAO;aACd,CAAC,CAAC;YAEH,oCAAoC;YACpC,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QACpC,CAAC;IACH,CAAC;IAED,OAAO;QAEL,KAAK,EAAE,GAAS,EAAE;YAEhB,6EAA6E;YAC7E,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAC3B,CAAC;QAED,IAAI,EAAE,CAAC,KAAa,EAAQ,EAAE;YAE5B,sCAAsC;YACtC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAE,MAAM,EAAE,KAAK,CAAE,CAAC,CAAC;YAE1C,+BAA+B;YAC/B,aAAa,EAAE,CAAC;QAClB,CAAC;KACF,CAAC;AACJ,CAAC;AAED,sBAAsB;AAEtB;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAY,EAAE,QAA4E;IAE1H,IAAI,GAAG,GAAG,eAAe,CAAC;IAE1B,OAAM,CAAC,GAAG,GAAG,eAAe,CAAC,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QAE7C,MAAM,SAAS,GAAG,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC;QAEzC,IAAI,OAAe,CAAC;QAEpB,IAAG,SAAS,KAAK,CAAC,EAAE,CAAC;YAEnB,wDAAwD;YACxD,IAAG,CAAC,GAAG,GAAG,oBAAoB,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;gBAE9C,OAAO;YACT,CAAC;YAED,wCAAwC;YACxC,IAAG,IAAI,CAAC,YAAY,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC;gBAElC,OAAO;YACT,CAAC;YAED,OAAO,GAAG,IAAI,CAAC,YAAY,CAAC,GAAG,GAAG,EAAE,CAAC,CAAC;QACxC,CAAC;aAAM,IAAG,CAAC,SAAS,GAAG,eAAe,CAAC,IAAI,CAAC,SAAS,KAAK,CAAC,CAAC,EAAE,CAAC;YAE7D,6DAA6D;YAC7D,OAAO;QACT,CAAC;aAAM,CAAC;YAEN,OAAO,GAAG,SAAS,CAAC;QACtB,CAAC;QAED,+CAA+C;QAC/C,IAAG,CAAC,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;YAEjC,OAAO;QACT,CAAC;QAED,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,GAAG,GAAG,CAAC,EAAE,GAAG,GAAG,CAAC,CAAC,CAAC;QAEzD,QAAQ,CAAC,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC;QAEtC,GAAG,IAAI,OAAO,CAAC;IACjB,CAAC;AACH,CAAC;AAED,sBAAsB;AAEtB;;;;;;;;;;;;;;;;GAgBG;AACH,SAAS,mBAAmB,CAAC,KAAa;IAExC,MAAM,eAAe,GAAG,CAAC,KAAK,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC;IAC9C,MAAM,SAAS,GAAG,CAAC,KAAK,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC;IAExC,2GAA2G;IAC3G,IAAG,eAAe,KAAK,CAAC,EAAE,CAAC;QAEzB,OAAO,KAAK,CAAC;IACf,CAAC;IAED,kHAAkH;IAClH,IAAG,eAAe,KAAK,CAAC,EAAE,CAAC;QAEzB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,2EAA2E;IAC3E,IAAG,SAAS,KAAK,CAAC,EAAE,CAAC;QAEnB,OAAO,KAAK,CAAC;IACf,CAAC;IAED,8GAA8G;IAC9G,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,SAAS,yBAAyB,CAAC,IAAY,EAAE,MAAc,EAAE,IAAY;IAE3E,kFAAkF;IAClF,IAAG,IAAI,GAAG,EAAE,EAAE,CAAC;QAEb,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,SAAS,GAAG,IAAI,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,UAAU,CAAC;IAE7D,mEAAmE;IACnE,IAAG,CAAC,CAAC,SAAS,GAAG,QAAQ,CAAC,EAAE,CAAC;QAE3B,OAAO,IAAI,CAAC;IACd,CAAC;IAED,gIAAgI;IAChI,IAAI,GAAG,GAAG,MAAM,GAAG,EAAE,CAAC;IAEtB,IAAG,SAAS,GAAG,QAAQ,EAAE,CAAC;QAExB,GAAG,IAAI,CAAC,CAAC;IACX,CAAC;IAED,IAAG,SAAS,GAAG,QAAQ,EAAE,CAAC;QAExB,GAAG,IAAI,CAAC,CAAC;IACX,CAAC;IAED,IAAG,SAAS,GAAG,QAAQ,EAAE,CAAC;QAExB,GAAG,IAAI,CAAC,CAAC;IACX,CAAC;IAED,IAAG,SAAS,GAAG,QAAQ,EAAE,CAAC;QAExB,GAAG,IAAI,CAAC,CAAC;IACX,CAAC;IAED,oDAAoD;IACpD,IAAG,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC;QAE/B,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC;AAChC,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,SAAS,uBAAuB,CAAC,IAAY,EAAE,MAAc,EAAE,IAAY,EAAE,kBAAiC;IAE5G,sFAAsF;IACtF,IAAG,IAAI,GAAG,EAAE,EAAE,CAAC;QAEb,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,SAAS,GAAG,IAAI,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,UAAU,CAAC;IAC7D,MAAM,WAAW,GAAG,IAAI,CAAC,YAAY,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;IAEnD,wCAAwC;IACxC,IAAG,WAAW,KAAK,CAAC,EAAE,CAAC;QAErB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,GAAG,GAAG,MAAM,GAAG,EAAE,CAAC;IAEtB,mCAAmC;IACnC,IAAG,SAAS,GAAG,KAAK,EAAE,CAAC;QAErB,GAAG,IAAI,CAAC,CAAC;IACX,CAAC;IAED,4FAA4F;IAC5F,IAAG,SAAS,GAAG,KAAK,EAAE,CAAC;QAErB,IAAG,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC;YAE/B,OAAO,IAAI,CAAC;QACd,CAAC;QAED,OAAO,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC;IAChC,CAAC;IAED,8JAA8J;IAC9J,0GAA0G;IAC1G,IAAG,SAAS,GAAG,KAAK,EAAE,CAAC;QAErB,0HAA0H;QAC1H,IAAG,SAAS,GAAG,KAAK,EAAE,CAAC;YAErB,GAAG,IAAI,CAAC,CAAC;QACX,CAAC;QAED,IAAG,SAAS,GAAG,KAAK,EAAE,CAAC;YAErB,GAAG,IAAI,CAAC,CAAC;QACX,CAAC;QAED,IAAG,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC;YAE/B,OAAO,IAAI,CAAC;QACd,CAAC;QAED,OAAO,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC;IAChC,CAAC;IAED,8DAA8D;IAC9D,OAAO,kBAAkB,CAAC;AAC5B,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,kBAAkB,CAAC,QAAgB;IAEjD,IAAI,mBAAmB,GAAG,KAAK,CAAC;IAChC,IAAI,sBAAsB,GAAG,KAAK,CAAC;IAEnC,uEAAuE;IACvE,iBAAiB,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE;QAEvD,IAAG,IAAI,KAAK,MAAM,EAAE,CAAC;YAEnB,OAAO;QACT,CAAC;QAED,4JAA4J;QAC5J,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC;QAEtD,IAAI,kBAAkB,GAAkB,IAAI,CAAC;QAE7C,6JAA6J;QAC7J,oGAAoG;QACpG,iBAAiB,CAAC,QAAQ,EAAE,CAAC,SAAS,EAAE,SAAS,EAAE,WAAW,EAAE,SAAS,EAAE,EAAE;YAE3E,IAAG,SAAS,KAAK,MAAM,EAAE,CAAC;gBAExB,kBAAkB,GAAG,yBAAyB,CAAC,SAAS,EAAE,WAAW,EAAE,SAAS,CAAC,CAAC;YACpF,CAAC;iBAAM,IAAG,SAAS,KAAK,MAAM,EAAE,CAAC;gBAE/B,MAAM,WAAW,GAAG,uBAAuB,CAAC,SAAS,EAAE,WAAW,EAAE,SAAS,EAAE,kBAAkB,CAAC,CAAC;gBAEnG,IAAG,WAAW,KAAK,IAAI,EAAE,CAAC;oBAExB,MAAM,UAAU,GAAG,mBAAmB,CAAC,WAAW,CAAC,CAAC;oBAEpD,IAAG,UAAU,EAAE,CAAC;wBAEd,mBAAmB,GAAG,IAAI,CAAC;oBAC7B,CAAC;yBAAM,CAAC;wBAEN,sBAAsB,GAAG,IAAI,CAAC;oBAChC,CAAC;gBACH,CAAC;YACH,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,oKAAoK;IACpK,kKAAkK;IAClK,qJAAqJ;IACrJ,uEAAuE;IACvE,IAAG,sBAAsB,EAAE,CAAC;QAE1B,OAAO,KAAK,CAAC;IACf,CAAC;IAED,uEAAuE;IACvE,IAAG,mBAAmB,EAAE,CAAC;QAEvB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,sCAAsC;IACtC,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
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.
|
|
4
|
-
*
|
|
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
|
*
|
package/dist/streaming/mpegts.js
CHANGED
|
@@ -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 {
|
|
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.
|
|
17
|
-
* 2.
|
|
18
|
-
* 3.
|
|
19
|
-
* 4.
|
|
20
|
-
* 5.
|
|
21
|
-
* 6.
|
|
22
|
-
* 7.
|
|
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.
|
|
27
|
-
*
|
|
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
|
-
//
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
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.
|
|
50
|
-
|
|
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
|
|
56
|
-
res.
|
|
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
|
|
134
|
-
res.
|
|
135
|
-
|
|
136
|
-
|
|
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
|