prismcast 1.5.2 → 1.7.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 +44 -28
- package/dist/app.d.ts +5 -0
- package/dist/app.js +63 -5
- 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 +10 -34
- package/dist/browser/channelSelection.js +31 -96
- package/dist/browser/channelSelection.js.map +1 -1
- package/dist/browser/display.d.ts +19 -0
- package/dist/browser/display.js +16 -11
- package/dist/browser/display.js.map +1 -1
- package/dist/browser/index.d.ts +31 -54
- package/dist/browser/index.js +334 -272
- package/dist/browser/index.js.map +1 -1
- package/dist/browser/login.d.ts +65 -0
- package/dist/browser/login.js +193 -0
- package/dist/browser/login.js.map +1 -0
- package/dist/browser/precaching.js +15 -3
- package/dist/browser/precaching.js.map +1 -1
- package/dist/browser/tuning/comcastPolymer.d.ts +23 -0
- package/dist/browser/tuning/comcastPolymer.js +655 -0
- package/dist/browser/tuning/comcastPolymer.js.map +1 -0
- package/dist/browser/tuning/cox.d.ts +2 -0
- package/dist/browser/tuning/cox.js +14 -0
- package/dist/browser/tuning/cox.js.map +1 -0
- package/dist/browser/tuning/directv.js +14 -1
- package/dist/browser/tuning/directv.js.map +1 -1
- package/dist/browser/tuning/fox.js +14 -2
- package/dist/browser/tuning/fox.js.map +1 -1
- package/dist/browser/tuning/hbo.js +12 -1
- package/dist/browser/tuning/hbo.js.map +1 -1
- package/dist/browser/tuning/hulu.js +24 -1
- package/dist/browser/tuning/hulu.js.map +1 -1
- package/dist/browser/tuning/shared.d.ts +46 -0
- package/dist/browser/tuning/shared.js +100 -0
- package/dist/browser/tuning/shared.js.map +1 -0
- package/dist/browser/tuning/sling.js +35 -5
- package/dist/browser/tuning/sling.js.map +1 -1
- package/dist/browser/tuning/spectrum.js +13 -1
- package/dist/browser/tuning/spectrum.js.map +1 -1
- package/dist/browser/tuning/thumbnailRow.js +1 -1
- package/dist/browser/tuning/thumbnailRow.js.map +1 -1
- package/dist/browser/tuning/tileClick.js +1 -1
- package/dist/browser/tuning/tileClick.js.map +1 -1
- package/dist/browser/tuning/xfinity.d.ts +2 -0
- package/dist/browser/tuning/xfinity.js +14 -0
- package/dist/browser/tuning/xfinity.js.map +1 -0
- package/dist/browser/tuning/youtubeTv.js +15 -1
- package/dist/browser/tuning/youtubeTv.js.map +1 -1
- package/dist/browser/video.d.ts +20 -0
- package/dist/browser/video.js +168 -124
- package/dist/browser/video.js.map +1 -1
- package/dist/channels/index.d.ts +2 -2
- package/dist/channels/index.js +2075 -683
- package/dist/channels/index.js.map +1 -1
- package/dist/config/health.d.ts +28 -28
- package/dist/config/health.js +72 -54
- package/dist/config/health.js.map +1 -1
- package/dist/config/index.js +29 -64
- package/dist/config/index.js.map +1 -1
- package/dist/config/paths.d.ts +12 -0
- package/dist/config/paths.js +16 -0
- package/dist/config/paths.js.map +1 -1
- package/dist/config/profiles.d.ts +3 -2
- package/dist/config/profiles.js +45 -34
- package/dist/config/profiles.js.map +1 -1
- package/dist/config/providerPacks.js +4 -1
- package/dist/config/providerPacks.js.map +1 -1
- package/dist/config/providers.d.ts +19 -5
- package/dist/config/providers.js +123 -93
- package/dist/config/providers.js.map +1 -1
- package/dist/config/sites.d.ts +28 -0
- package/dist/config/sites.js +104 -127
- package/dist/config/sites.js.map +1 -1
- package/dist/config/userChannels.d.ts +53 -2
- package/dist/config/userChannels.js +222 -13
- package/dist/config/userChannels.js.map +1 -1
- package/dist/config/userConfig.d.ts +2 -1
- package/dist/config/userConfig.js +6 -15
- package/dist/config/userConfig.js.map +1 -1
- package/dist/config/userProfiles.d.ts +0 -1
- package/dist/config/userProfiles.js +18 -10
- package/dist/config/userProfiles.js.map +1 -1
- package/dist/hdhr/channelMap.d.ts +1 -1
- package/dist/hdhr/channelMap.js +4 -4
- package/dist/hdhr/channelMap.js.map +1 -1
- package/dist/index.js +25 -22
- package/dist/index.js.map +1 -1
- package/dist/native/decrypt.d.ts +32 -0
- package/dist/native/decrypt.js +85 -0
- package/dist/native/decrypt.js.map +1 -0
- package/dist/native/index.d.ts +61 -0
- package/dist/native/index.js +342 -0
- package/dist/native/index.js.map +1 -0
- package/dist/native/intercept.d.ts +35 -0
- package/dist/native/intercept.js +184 -0
- package/dist/native/intercept.js.map +1 -0
- package/dist/native/probe.d.ts +49 -0
- package/dist/native/probe.js +294 -0
- package/dist/native/probe.js.map +1 -0
- package/dist/native/proxy.d.ts +54 -0
- package/dist/native/proxy.js +947 -0
- package/dist/native/proxy.js.map +1 -0
- package/dist/routes/components.d.ts +43 -0
- package/dist/routes/components.js +122 -0
- package/dist/routes/components.js.map +1 -1
- package/dist/routes/config/channels/index.d.ts +2 -2
- package/dist/routes/config/channels/index.js +1 -1
- package/dist/routes/config/channels/index.js.map +1 -1
- package/dist/routes/config/channels/routes.js +443 -65
- package/dist/routes/config/channels/routes.js.map +1 -1
- package/dist/routes/config/channels/table.d.ts +76 -0
- package/dist/routes/config/channels/table.js +369 -89
- package/dist/routes/config/channels/table.js.map +1 -1
- package/dist/routes/config/index.d.ts +1 -1
- package/dist/routes/config/index.js +1 -1
- package/dist/routes/config/index.js.map +1 -1
- package/dist/routes/config/providers.d.ts +6 -6
- package/dist/routes/config/providers.js +73 -99
- package/dist/routes/config/providers.js.map +1 -1
- package/dist/routes/debug.js +5 -5
- package/dist/routes/debug.js.map +1 -1
- package/dist/routes/hls.js +12 -2
- package/dist/routes/hls.js.map +1 -1
- package/dist/routes/index.d.ts +0 -15
- package/dist/routes/index.js +2 -16
- package/dist/routes/index.js.map +1 -1
- package/dist/routes/providers.js +106 -3
- package/dist/routes/providers.js.map +1 -1
- package/dist/routes/root/content.js +124 -47
- package/dist/routes/root/content.js.map +1 -1
- package/dist/routes/root/index.js +2 -1
- package/dist/routes/root/index.js.map +1 -1
- package/dist/routes/root/scripts/channels.js +772 -312
- package/dist/routes/root/scripts/channels.js.map +1 -1
- package/dist/routes/root/scripts/config.js +596 -544
- package/dist/routes/root/scripts/config.js.map +1 -1
- package/dist/routes/root/scripts/index.d.ts +1 -0
- package/dist/routes/root/scripts/index.js +1 -0
- package/dist/routes/root/scripts/index.js.map +1 -1
- package/dist/routes/root/scripts/shared.d.ts +6 -0
- package/dist/routes/root/scripts/shared.js +391 -0
- package/dist/routes/root/scripts/shared.js.map +1 -0
- package/dist/routes/root/scripts/status.js +49 -33
- package/dist/routes/root/scripts/status.js.map +1 -1
- package/dist/routes/root/styles.js +117 -10
- package/dist/routes/root/styles.js.map +1 -1
- package/dist/service/commands.js +2 -1
- package/dist/service/commands.js.map +1 -1
- package/dist/streaming/fmp4Segmenter.d.ts +5 -0
- package/dist/streaming/fmp4Segmenter.js +146 -42
- package/dist/streaming/fmp4Segmenter.js.map +1 -1
- package/dist/streaming/hls.d.ts +24 -19
- package/dist/streaming/hls.js +603 -297
- package/dist/streaming/hls.js.map +1 -1
- package/dist/streaming/hlsResume.d.ts +16 -3
- package/dist/streaming/hlsResume.js +35 -8
- package/dist/streaming/hlsResume.js.map +1 -1
- package/dist/streaming/hlsSegments.d.ts +41 -2
- package/dist/streaming/hlsSegments.js +131 -28
- package/dist/streaming/hlsSegments.js.map +1 -1
- package/dist/streaming/lifecycle.js +27 -13
- package/dist/streaming/lifecycle.js.map +1 -1
- package/dist/streaming/monitor.d.ts +1 -0
- package/dist/streaming/monitor.js +862 -474
- package/dist/streaming/monitor.js.map +1 -1
- package/dist/streaming/mp4Parser.d.ts +52 -6
- package/dist/streaming/mp4Parser.js +156 -30
- package/dist/streaming/mp4Parser.js.map +1 -1
- package/dist/streaming/mpegts.d.ts +2 -2
- package/dist/streaming/mpegts.js +156 -109
- package/dist/streaming/mpegts.js.map +1 -1
- package/dist/streaming/playlistBuilder.d.ts +38 -0
- package/dist/streaming/playlistBuilder.js +66 -0
- package/dist/streaming/playlistBuilder.js.map +1 -0
- package/dist/streaming/preroll.d.ts +126 -0
- package/dist/streaming/preroll.js +434 -0
- package/dist/streaming/preroll.js.map +1 -0
- package/dist/streaming/pretune.d.ts +8 -0
- package/dist/streaming/pretune.js +226 -0
- package/dist/streaming/pretune.js.map +1 -0
- package/dist/streaming/recovery.d.ts +0 -6
- package/dist/streaming/recovery.js +0 -8
- package/dist/streaming/recovery.js.map +1 -1
- package/dist/streaming/registry.d.ts +57 -9
- package/dist/streaming/registry.js +26 -7
- package/dist/streaming/registry.js.map +1 -1
- package/dist/streaming/setup.d.ts +8 -1
- package/dist/streaming/setup.js +71 -27
- package/dist/streaming/setup.js.map +1 -1
- package/dist/streaming/showInfo.d.ts +29 -4
- package/dist/streaming/showInfo.js +210 -38
- package/dist/streaming/showInfo.js.map +1 -1
- package/dist/streaming/statusEmitter.d.ts +20 -6
- package/dist/streaming/statusEmitter.js +31 -7
- package/dist/streaming/statusEmitter.js.map +1 -1
- package/dist/types/channels.d.ts +32 -0
- package/dist/types/config.d.ts +2 -2
- package/dist/types/index.d.ts +2 -2
- package/dist/types/index.js.map +1 -1
- package/dist/types/profiles.d.ts +13 -1
- package/dist/types/selection.d.ts +5 -1
- package/dist/types/shared.d.ts +1 -1
- package/dist/types/streaming.d.ts +7 -0
- package/dist/utils/chromeFetch.d.ts +20 -0
- package/dist/utils/chromeFetch.js +43 -0
- package/dist/utils/chromeFetch.js.map +1 -0
- package/dist/utils/debugFilter.d.ts +0 -5
- package/dist/utils/debugFilter.js +20 -11
- package/dist/utils/debugFilter.js.map +1 -1
- package/dist/utils/delay.d.ts +28 -0
- package/dist/utils/delay.js +35 -1
- package/dist/utils/delay.js.map +1 -1
- package/dist/utils/evaluate.js +6 -11
- package/dist/utils/evaluate.js.map +1 -1
- package/dist/utils/ffmpeg.d.ts +14 -12
- package/dist/utils/ffmpeg.js +114 -134
- package/dist/utils/ffmpeg.js.map +1 -1
- package/dist/utils/format.d.ts +6 -0
- package/dist/utils/format.js +8 -0
- package/dist/utils/format.js.map +1 -1
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/index.js +3 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/logEmitter.js +1 -1
- package/dist/utils/logEmitter.js.map +1 -1
- package/dist/utils/pid.d.ts +40 -0
- package/dist/utils/pid.js +83 -0
- package/dist/utils/pid.js.map +1 -0
- package/dist/utils/retry.d.ts +15 -9
- package/dist/utils/retry.js +7 -23
- package/dist/utils/retry.js.map +1 -1
- package/dist/utils/timing.d.ts +5 -0
- package/dist/utils/timing.js +13 -0
- package/dist/utils/timing.js.map +1 -0
- package/dist/utils/version.js +2 -2
- package/dist/utils/version.js.map +1 -1
- package/package.json +7 -7
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import type { Express } from "express";
|
|
2
|
+
import type { PlaylistSegmentEntry } from "./playlistBuilder.js";
|
|
3
|
+
export type PrerollCodec = "h264" | "hevc";
|
|
4
|
+
/**
|
|
5
|
+
* Returns true when the specified preroll variant has been generated and is ready to serve.
|
|
6
|
+
* @param codec - The preroll codec variant to check.
|
|
7
|
+
*/
|
|
8
|
+
export declare function isPrerollReady(codec: PrerollCodec): boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Returns the number of preroll segments available for the specified codec. Used by the segmenter to determine the preroll-to-real segment index boundary.
|
|
11
|
+
* @param codec - The preroll codec variant.
|
|
12
|
+
* @returns The count of preroll media segments.
|
|
13
|
+
*/
|
|
14
|
+
export declare function getPrerollSegmentCount(codec: PrerollCodec): number;
|
|
15
|
+
/**
|
|
16
|
+
* Returns the duration of a specific preroll segment in seconds. Used by the segmenter's generatePlaylist() for EXTINF values when preroll entries are in the sliding
|
|
17
|
+
* window.
|
|
18
|
+
* @param codec - The preroll codec variant.
|
|
19
|
+
* @param index - The zero-based segment index.
|
|
20
|
+
* @returns The segment duration in seconds.
|
|
21
|
+
*/
|
|
22
|
+
export declare function getPrerollSegmentDuration(codec: PrerollCodec, index: number): number;
|
|
23
|
+
/**
|
|
24
|
+
* Returns the total duration of all preroll segments in seconds. Used by the fmp4Segmenter to compute PTS offsets that make Chrome's real content timestamps continue
|
|
25
|
+
* from where the preroll ended, eliminating the PTS discontinuity at the preroll-to-live boundary.
|
|
26
|
+
* @param codec - The preroll codec variant.
|
|
27
|
+
* @returns The sum of all preroll segment durations in seconds.
|
|
28
|
+
*/
|
|
29
|
+
export declare function getPrerollTotalDurationSec(codec: PrerollCodec): number;
|
|
30
|
+
/**
|
|
31
|
+
* Returns the maximum duration across all preroll segments for the specified codec, rounded up to the nearest integer. Used for TARGETDURATION computation in the
|
|
32
|
+
* composite playlist.
|
|
33
|
+
* @param codec - The preroll codec variant.
|
|
34
|
+
* @returns The ceiling of the maximum preroll segment duration.
|
|
35
|
+
*/
|
|
36
|
+
export declare function getPrerollMaxDuration(codec: PrerollCodec): number;
|
|
37
|
+
/**
|
|
38
|
+
* Returns the preroll codec that should be used for new streams. HEVC is preferred when the GPU supports HEVC hardware encoding (matching the capture codec), with
|
|
39
|
+
* H.264 as the fallback. If the preferred variant was not generated (e.g., HEVC generation failed), falls back to whichever variant is available.
|
|
40
|
+
* @returns The codec to use for preroll, or "h264" as the default.
|
|
41
|
+
*/
|
|
42
|
+
export declare function getPrerollCodec(): PrerollCodec;
|
|
43
|
+
/**
|
|
44
|
+
* Generates preroll fMP4 variants at startup. Uses the bundled ffmpeg-for-homebridge binary (which includes both libx264 and libx265) to guarantee encoder
|
|
45
|
+
* availability regardless of the user's system FFmpeg installation. The H.264 variant is always generated. The HEVC variant is generated when the GPU supports HEVC
|
|
46
|
+
* hardware encoding, so the preroll codec can match the capture codec at the preroll-to-live boundary.
|
|
47
|
+
*
|
|
48
|
+
* Each variant spawns FFmpeg to create PREROLL_TOTAL_DURATION seconds of black frame + silence as fragmented MP4, then splits the output into an init segment
|
|
49
|
+
* (ftyp + moov) and individual media segments (moof + mdat pairs) using the MP4 box parser. Each segment has naturally monotonic PTS because it comes from a
|
|
50
|
+
* continuous FFmpeg encode. If the bundled FFmpeg is unavailable or a variant fails, the system degrades gracefully — the blocking stream setup path is used instead.
|
|
51
|
+
*/
|
|
52
|
+
export declare function generatePreroll(): Promise<void>;
|
|
53
|
+
/**
|
|
54
|
+
* Options for computing the composite playlist sliding window when preroll is active.
|
|
55
|
+
*/
|
|
56
|
+
export interface PrerollWindowOptions {
|
|
57
|
+
currentSegmentIndex: number;
|
|
58
|
+
maxSegments: number;
|
|
59
|
+
prerollSegmentCount: number;
|
|
60
|
+
realSegmentCount: number;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Computes the sliding window start index for a composite playlist containing both preroll and real segments. The start index is determined by three constraints:
|
|
64
|
+
* (1) never negative, (2) the standard sliding window rule (current index minus window size), and (3) the preroll cap that limits how many preroll entries appear in
|
|
65
|
+
* the window. The third constraint is the key addition — it prevents clients from playing through many seconds of remaining preroll before reaching live content.
|
|
66
|
+
*
|
|
67
|
+
* @param options - Window computation parameters.
|
|
68
|
+
* @returns The start index for the composite playlist window.
|
|
69
|
+
*/
|
|
70
|
+
export declare function computePrerollWindow(options: PrerollWindowOptions): number;
|
|
71
|
+
/**
|
|
72
|
+
* Options for building preroll segment entries.
|
|
73
|
+
*/
|
|
74
|
+
export interface PrerollEntryOptions {
|
|
75
|
+
baseUrl: string;
|
|
76
|
+
codec: PrerollCodec;
|
|
77
|
+
extension: string;
|
|
78
|
+
prerollSegmentCount: number;
|
|
79
|
+
startIndex: number;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Builds an array of playlist segment entries for preroll segments within the given index range. Each entry has an absolute URL pointing to the global /preroll/ route
|
|
83
|
+
* and the duration extracted from the preroll segment cache. No per-segment metadata tags (DISCONTINUITY, PROGRAM-DATE-TIME, etc.) are set — preroll is synthetic
|
|
84
|
+
* placeholder content.
|
|
85
|
+
*
|
|
86
|
+
* @param options - Entry construction parameters.
|
|
87
|
+
* @returns Ordered array of preroll segment entries for the builder.
|
|
88
|
+
*/
|
|
89
|
+
export declare function buildPrerollEntries(options: PrerollEntryOptions): PlaylistSegmentEntry[];
|
|
90
|
+
/**
|
|
91
|
+
* Computes how many preroll segments should be visible based on elapsed wall-clock time since the preroll started. The initial window (PREROLL_INITIAL_WINDOW segments)
|
|
92
|
+
* is revealed immediately so the client has enough content to begin playback per the HLS 3-from-end rule. Additional segments are revealed one at a time as wall-clock
|
|
93
|
+
* time passes — each new segment appears when enough time has elapsed for the client to have consumed the prior content beyond the initial window.
|
|
94
|
+
*
|
|
95
|
+
* @param codec - The preroll codec variant.
|
|
96
|
+
* @param prerollStartTime - Wall-clock time when the preroll began.
|
|
97
|
+
* @returns The number of preroll segments to reveal.
|
|
98
|
+
*/
|
|
99
|
+
export declare function computeProgressiveReveal(codec: PrerollCodec, prerollStartTime: Date): number;
|
|
100
|
+
/**
|
|
101
|
+
* Generates a progressive HLS playlist referencing the global preroll segments for the specified codec. The playlist simulates a live stream by revealing segments
|
|
102
|
+
* based on elapsed wall-clock time since the preroll started. On each client poll, more segments become visible — the client sees new content appear and keeps playing
|
|
103
|
+
* without stalling.
|
|
104
|
+
*
|
|
105
|
+
* The playlist uses absolute URLs because preroll segments are served at global routes (/preroll/:codec/*) while the playlist itself is served under /hls/:name/.
|
|
106
|
+
* Without absolute URLs, the client would request /hls/:name/preroll/... which does not exist. The playlist omits #EXT-X-ENDLIST so it behaves as a live playlist —
|
|
107
|
+
* the client polls for updates and receives the real content playlist when ready.
|
|
108
|
+
*
|
|
109
|
+
* PROGRAM-DATE-TIME is intentionally omitted from the preroll playlist. Preroll is synthetic placeholder content — assigning it wall-clock timestamps would be
|
|
110
|
+
* misleading and would create a backward time jump at the preroll-to-live boundary (preroll PDT would overshoot real content PDT because the preroll covers 30
|
|
111
|
+
* seconds of content but real content typically arrives in ~15 seconds). PDT is emitted only on real segments once the segmenter produces them.
|
|
112
|
+
*
|
|
113
|
+
* @param baseUrl - The server's external URL (e.g., "http://192.168.1.100:5589").
|
|
114
|
+
* @param codec - The preroll codec variant.
|
|
115
|
+
* @param startingSequence - The MEDIA-SEQUENCE offset. Zero for fresh starts. For resume streams, this is set to the saved segment index so the preroll
|
|
116
|
+
* playlist continues from the prior session's sequence range rather than restarting at 0.
|
|
117
|
+
* @param prerollStartTime - Wall-clock time when the preroll began. Used to compute elapsed time and determine how many segments to reveal.
|
|
118
|
+
* @returns The complete HLS playlist string, or an empty string if the preroll is not ready.
|
|
119
|
+
*/
|
|
120
|
+
export declare function generatePrerollPlaylist(baseUrl: string, codec: PrerollCodec, startingSequence: number, prerollStartTime: Date): string;
|
|
121
|
+
/**
|
|
122
|
+
* Registers the preroll segment routes on the Express application. Routes are codec-prefixed (/preroll/:codec/init.mp4 and /preroll/:codec/segmentN.m4s) so each
|
|
123
|
+
* variant is served at a distinct path. The cached preroll buffers are served with immutable cache headers since the content never changes after generation.
|
|
124
|
+
* @param app - The Express application.
|
|
125
|
+
*/
|
|
126
|
+
export declare function setupPrerollRoutes(app: Express): void;
|
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
import { LOG, getBundledFFmpegPath } from "../utils/index.js";
|
|
2
|
+
import { createMP4BoxParser, offsetMoofTimestamps, parseMoovTrackInfo } from "./mp4Parser.js";
|
|
3
|
+
import { CONFIG } from "../config/index.js";
|
|
4
|
+
import { buildPlaylist } from "./playlistBuilder.js";
|
|
5
|
+
import { getEffectiveViewport } from "../config/presets.js";
|
|
6
|
+
import { getGpuCapabilities } from "../browser/display.js";
|
|
7
|
+
import { spawn } from "node:child_process";
|
|
8
|
+
// Total duration of preroll content in seconds. FFmpeg generates this much continuous black+silence fMP4, which is then split into individual segments at keyframe
|
|
9
|
+
// boundaries. The configured duration provides ample coverage for the slowest providers with the progressive playlist delivering content in real time for the full
|
|
10
|
+
// tune duration.
|
|
11
|
+
const PREROLL_TOTAL_DURATION = 30;
|
|
12
|
+
// Number of segments to reveal immediately on the first preroll playlist request. The HLS spec (RFC 8216 Section 6.3.3) says a client should start playback 3 target
|
|
13
|
+
// durations from the end of a live playlist, so 4 segments gives the client 3 playable segments plus one buffer.
|
|
14
|
+
const PREROLL_INITIAL_WINDOW = 4;
|
|
15
|
+
// Cached preroll variants keyed by codec label ("h264", "hevc"). Generated once at startup, reused for all streams. The H.264 variant is always generated; the HEVC
|
|
16
|
+
// variant is generated when the GPU supports HEVC hardware encoding.
|
|
17
|
+
const prerollVariants = new Map();
|
|
18
|
+
// Public Accessors.
|
|
19
|
+
/**
|
|
20
|
+
* Returns true when the specified preroll variant has been generated and is ready to serve.
|
|
21
|
+
* @param codec - The preroll codec variant to check.
|
|
22
|
+
*/
|
|
23
|
+
export function isPrerollReady(codec) {
|
|
24
|
+
const variant = prerollVariants.get(codec);
|
|
25
|
+
return (variant !== undefined) && (variant.mediaSegments.length > 0);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Returns the number of preroll segments available for the specified codec. Used by the segmenter to determine the preroll-to-real segment index boundary.
|
|
29
|
+
* @param codec - The preroll codec variant.
|
|
30
|
+
* @returns The count of preroll media segments.
|
|
31
|
+
*/
|
|
32
|
+
export function getPrerollSegmentCount(codec) {
|
|
33
|
+
return prerollVariants.get(codec)?.mediaSegments.length ?? 0;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Returns the duration of a specific preroll segment in seconds. Used by the segmenter's generatePlaylist() for EXTINF values when preroll entries are in the sliding
|
|
37
|
+
* window.
|
|
38
|
+
* @param codec - The preroll codec variant.
|
|
39
|
+
* @param index - The zero-based segment index.
|
|
40
|
+
* @returns The segment duration in seconds.
|
|
41
|
+
*/
|
|
42
|
+
export function getPrerollSegmentDuration(codec, index) {
|
|
43
|
+
return prerollVariants.get(codec)?.durations[index] ?? 2;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Returns the total duration of all preroll segments in seconds. Used by the fmp4Segmenter to compute PTS offsets that make Chrome's real content timestamps continue
|
|
47
|
+
* from where the preroll ended, eliminating the PTS discontinuity at the preroll-to-live boundary.
|
|
48
|
+
* @param codec - The preroll codec variant.
|
|
49
|
+
* @returns The sum of all preroll segment durations in seconds.
|
|
50
|
+
*/
|
|
51
|
+
export function getPrerollTotalDurationSec(codec) {
|
|
52
|
+
const variant = prerollVariants.get(codec);
|
|
53
|
+
if (!variant) {
|
|
54
|
+
return 0;
|
|
55
|
+
}
|
|
56
|
+
let total = 0;
|
|
57
|
+
for (const duration of variant.durations) {
|
|
58
|
+
total += duration;
|
|
59
|
+
}
|
|
60
|
+
return total;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Returns the maximum duration across all preroll segments for the specified codec, rounded up to the nearest integer. Used for TARGETDURATION computation in the
|
|
64
|
+
* composite playlist.
|
|
65
|
+
* @param codec - The preroll codec variant.
|
|
66
|
+
* @returns The ceiling of the maximum preroll segment duration.
|
|
67
|
+
*/
|
|
68
|
+
export function getPrerollMaxDuration(codec) {
|
|
69
|
+
const variant = prerollVariants.get(codec);
|
|
70
|
+
if (!variant || (variant.durations.length === 0)) {
|
|
71
|
+
return 2;
|
|
72
|
+
}
|
|
73
|
+
let max = 0;
|
|
74
|
+
for (const duration of variant.durations) {
|
|
75
|
+
if (duration > max) {
|
|
76
|
+
max = duration;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return Math.ceil(max);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Returns the preroll codec that should be used for new streams. HEVC is preferred when the GPU supports HEVC hardware encoding (matching the capture codec), with
|
|
83
|
+
* H.264 as the fallback. If the preferred variant was not generated (e.g., HEVC generation failed), falls back to whichever variant is available.
|
|
84
|
+
* @returns The codec to use for preroll, or "h264" as the default.
|
|
85
|
+
*/
|
|
86
|
+
export function getPrerollCodec() {
|
|
87
|
+
const preferHevc = getGpuCapabilities()?.hevcHardwareEncoding === true;
|
|
88
|
+
const preferred = preferHevc ? "hevc" : "h264";
|
|
89
|
+
if (isPrerollReady(preferred)) {
|
|
90
|
+
return preferred;
|
|
91
|
+
}
|
|
92
|
+
// Fall back: if the preferred variant failed to generate, use the other one.
|
|
93
|
+
const fallback = preferHevc ? "h264" : "hevc";
|
|
94
|
+
if (isPrerollReady(fallback)) {
|
|
95
|
+
return fallback;
|
|
96
|
+
}
|
|
97
|
+
return "h264";
|
|
98
|
+
}
|
|
99
|
+
// Preroll Generation.
|
|
100
|
+
/**
|
|
101
|
+
* Generates preroll fMP4 variants at startup. Uses the bundled ffmpeg-for-homebridge binary (which includes both libx264 and libx265) to guarantee encoder
|
|
102
|
+
* availability regardless of the user's system FFmpeg installation. The H.264 variant is always generated. The HEVC variant is generated when the GPU supports HEVC
|
|
103
|
+
* hardware encoding, so the preroll codec can match the capture codec at the preroll-to-live boundary.
|
|
104
|
+
*
|
|
105
|
+
* Each variant spawns FFmpeg to create PREROLL_TOTAL_DURATION seconds of black frame + silence as fragmented MP4, then splits the output into an init segment
|
|
106
|
+
* (ftyp + moov) and individual media segments (moof + mdat pairs) using the MP4 box parser. Each segment has naturally monotonic PTS because it comes from a
|
|
107
|
+
* continuous FFmpeg encode. If the bundled FFmpeg is unavailable or a variant fails, the system degrades gracefully — the blocking stream setup path is used instead.
|
|
108
|
+
*/
|
|
109
|
+
export async function generatePreroll() {
|
|
110
|
+
const ffmpegBin = getBundledFFmpegPath();
|
|
111
|
+
if (!ffmpegBin) {
|
|
112
|
+
LOG.warn("Bundled FFmpeg is not available. Preroll generation skipped — startup playlists will have no segments.");
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
// Use the effective viewport to match the resolution Chrome MediaRecorder will produce. This prevents a resolution mismatch at the preroll-to-live discontinuity
|
|
116
|
+
// boundary.
|
|
117
|
+
const viewport = getEffectiveViewport(CONFIG);
|
|
118
|
+
const size = String(viewport.width) + "x" + String(viewport.height);
|
|
119
|
+
// Generate the H.264 variant. Baseline profile and level 3.1 match Chrome's MediaRecorder output (confirmed via parseMoovCodecConfig telemetry). Slow preset and
|
|
120
|
+
// CRF 18 produce higher quality — acceptable for a short duration of simple content generated once at startup.
|
|
121
|
+
await generateVariant(ffmpegBin, "h264", [
|
|
122
|
+
"-c:v", "libx264", "-preset", "slow", "-tune", "stillimage", "-profile:v", "baseline", "-level", "3.1", "-pix_fmt", "yuv420p",
|
|
123
|
+
"-crf", "18"
|
|
124
|
+
], size);
|
|
125
|
+
// Generate the HEVC variant when the GPU supports HEVC hardware encoding. The libx265 encoder does not support the -tune stillimage option (libx264-specific), so
|
|
126
|
+
// we omit it. The -tag:v hvc1 flag ensures the output uses the hvc1 box type (matching Chrome's MediaRecorder HEVC output) rather than libx265's default hev1.
|
|
127
|
+
if (getGpuCapabilities()?.hevcHardwareEncoding === true) {
|
|
128
|
+
await generateVariant(ffmpegBin, "hevc", [
|
|
129
|
+
"-c:v", "libx265", "-preset", "slow", "-profile:v", "main", "-pix_fmt", "yuv420p",
|
|
130
|
+
"-crf", "18", "-tag:v", "hvc1"
|
|
131
|
+
], size);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Generates a single preroll variant (H.264 or HEVC) and stores it in the variant cache. Constructs the FFmpeg argument list from shared parameters (input sources,
|
|
136
|
+
* duration, GOP size, audio codec, fMP4 output flags) combined with the codec-specific video encoder arguments.
|
|
137
|
+
* @param ffmpegBin - Path to the FFmpeg executable.
|
|
138
|
+
* @param codec - The codec label for this variant.
|
|
139
|
+
* @param videoArgs - Codec-specific FFmpeg arguments for the video encoder (e.g., "-c:v libx264 -preset slow ...").
|
|
140
|
+
* @param size - The output resolution as "WxH".
|
|
141
|
+
*/
|
|
142
|
+
async function generateVariant(ffmpegBin, codec, videoArgs, size) {
|
|
143
|
+
const args = [
|
|
144
|
+
"-hide_banner", "-nostats", "-loglevel", "warning",
|
|
145
|
+
"-f", "lavfi", "-i", "color=black:size=" + size + ":rate=30:duration=" + String(PREROLL_TOTAL_DURATION),
|
|
146
|
+
"-f", "lavfi", "-i", "anullsrc=r=48000:cl=stereo",
|
|
147
|
+
"-t", String(PREROLL_TOTAL_DURATION),
|
|
148
|
+
...videoArgs,
|
|
149
|
+
"-g", "60",
|
|
150
|
+
"-c:a", "aac", "-b:a", "128000",
|
|
151
|
+
"-f", "mp4",
|
|
152
|
+
"-movflags", "frag_keyframe+empty_moov+default_base_moof+skip_sidx+skip_trailer",
|
|
153
|
+
"-flush_packets", "1",
|
|
154
|
+
"pipe:1"
|
|
155
|
+
];
|
|
156
|
+
try {
|
|
157
|
+
const output = await spawnAndCollect(ffmpegBin, args);
|
|
158
|
+
const variant = splitPrerollBuffers(output);
|
|
159
|
+
if (variant && (variant.mediaSegments.length > 0)) {
|
|
160
|
+
prerollVariants.set(codec, variant);
|
|
161
|
+
const totalSize = variant.mediaSegments.reduce((sum, seg) => sum + seg.length, 0);
|
|
162
|
+
LOG.debug("streaming:preroll", "Preroll %s ready: %d segments, init=%d bytes, media=%d bytes total.", codec, variant.mediaSegments.length, variant.initSegment.length, totalSize);
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
LOG.warn("Preroll %s generation produced incomplete output.", codec);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
LOG.warn("Preroll %s generation failed: %s.", codec, error instanceof Error ? error.message : String(error));
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
// Internal Helpers.
|
|
173
|
+
/**
|
|
174
|
+
* Spawns FFmpeg with the given arguments and collects all stdout output into a single Buffer. Used for one-shot operations like preroll generation where the entire
|
|
175
|
+
* output fits in memory.
|
|
176
|
+
* @param ffmpegBin - Path to the FFmpeg executable.
|
|
177
|
+
* @param args - FFmpeg command-line arguments.
|
|
178
|
+
* @returns Promise resolving to the complete stdout output as a Buffer.
|
|
179
|
+
*/
|
|
180
|
+
async function spawnAndCollect(ffmpegBin, args) {
|
|
181
|
+
return new Promise((resolve, reject) => {
|
|
182
|
+
const chunks = [];
|
|
183
|
+
const ffmpeg = spawn(ffmpegBin, args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
184
|
+
ffmpeg.stdout.on("data", (chunk) => {
|
|
185
|
+
chunks.push(chunk);
|
|
186
|
+
});
|
|
187
|
+
ffmpeg.stderr.on("data", (data) => {
|
|
188
|
+
const message = data.toString().trim();
|
|
189
|
+
if (message.length > 0) {
|
|
190
|
+
LOG.debug("streaming:preroll", "FFmpeg: %s", message);
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
ffmpeg.on("error", (error) => {
|
|
194
|
+
reject(error);
|
|
195
|
+
});
|
|
196
|
+
ffmpeg.on("exit", (code) => {
|
|
197
|
+
if (code !== 0) {
|
|
198
|
+
reject(new Error("FFmpeg exited with code " + String(code) + "."));
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
resolve(Buffer.concat(chunks));
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Splits an fMP4 buffer into an init segment (ftyp + moov) and individual media segments (moof + mdat pairs) using the MP4 box parser. The init segment contains codec
|
|
207
|
+
* configuration needed by the HLS client to initialize its decoder. Each moof+mdat pair becomes a distinct segment buffer with naturally monotonic PTS timestamps.
|
|
208
|
+
*
|
|
209
|
+
* Duration extraction: the moov box is parsed with parseMoovTrackInfo() to get per-track timescales. Each moof is then parsed with offsetMoofTimestamps() (with an
|
|
210
|
+
* empty offset map for pure pass-through) to extract per-track trun durations. The maximum duration across tracks (converted to seconds via timescale) becomes the
|
|
211
|
+
* segment's EXTINF duration.
|
|
212
|
+
*
|
|
213
|
+
* @param data - The complete fMP4 output from FFmpeg.
|
|
214
|
+
* @returns The parsed variant, or null if the output was incomplete.
|
|
215
|
+
*/
|
|
216
|
+
function splitPrerollBuffers(data) {
|
|
217
|
+
const initBoxes = [];
|
|
218
|
+
const mediaSegments = [];
|
|
219
|
+
const durations = [];
|
|
220
|
+
let moovBox = null;
|
|
221
|
+
// Accumulator for the current moof+mdat pair. A segment is finalized when the mdat following a moof arrives.
|
|
222
|
+
let pendingMoof = null;
|
|
223
|
+
const parser = createMP4BoxParser((box) => {
|
|
224
|
+
if ((box.type === "ftyp") || (box.type === "moov")) {
|
|
225
|
+
initBoxes.push(box.data);
|
|
226
|
+
if (box.type === "moov") {
|
|
227
|
+
moovBox = box.data;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
else if (box.type === "moof") {
|
|
231
|
+
// Start a new segment. If there's an orphaned moof without a following mdat (shouldn't happen with well-formed fMP4), it gets overwritten.
|
|
232
|
+
pendingMoof = box.data;
|
|
233
|
+
}
|
|
234
|
+
else if (box.type === "mdat") {
|
|
235
|
+
// Finalize the current segment by combining the pending moof with this mdat.
|
|
236
|
+
if (pendingMoof) {
|
|
237
|
+
mediaSegments.push(Buffer.concat([pendingMoof, box.data]));
|
|
238
|
+
pendingMoof = null;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
parser.push(data);
|
|
243
|
+
parser.flush();
|
|
244
|
+
if (initBoxes.length === 0) {
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
const initSegment = Buffer.concat(initBoxes);
|
|
248
|
+
// Extract per-segment durations from the moov timescales and moof trun sample durations. The moov provides the timescale for converting raw sample durations to
|
|
249
|
+
// seconds. Each moof's traf/trun boxes contain the accumulated sample durations for that fragment.
|
|
250
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- moovBox is set inside the parser callback; TS can't track closure mutations.
|
|
251
|
+
if (moovBox && (mediaSegments.length > 0)) {
|
|
252
|
+
const trackInfoMap = parseMoovTrackInfo(moovBox);
|
|
253
|
+
// Build a timescale lookup from trackId to timescale.
|
|
254
|
+
const timescales = new Map();
|
|
255
|
+
for (const [trackId, info] of trackInfoMap) {
|
|
256
|
+
timescales.set(trackId, info.timescale);
|
|
257
|
+
}
|
|
258
|
+
// Extract duration from each segment's moof box. The offsetMoofTimestamps function with an empty offset map acts as a pure reader — it returns per-track
|
|
259
|
+
// durations without modifying the buffer (it writes back the same tfdt values it read since the offset is 0). A fresh map is created per segment to prevent
|
|
260
|
+
// state accumulation across iterations.
|
|
261
|
+
for (const segment of mediaSegments) {
|
|
262
|
+
// The segment buffer is moof+mdat concatenated. The moof is the first box — parse it to find its size.
|
|
263
|
+
if (segment.length < 8) {
|
|
264
|
+
durations.push(2);
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
const moofSize = segment.readUInt32BE(0);
|
|
268
|
+
const moofData = segment.subarray(0, moofSize);
|
|
269
|
+
// Extract per-track durations from the moof. Take the maximum across tracks and convert to seconds.
|
|
270
|
+
const trackResults = offsetMoofTimestamps(moofData, new Map());
|
|
271
|
+
let maxDurationSec = 0;
|
|
272
|
+
for (const [trackId, result] of trackResults) {
|
|
273
|
+
const timescale = timescales.get(trackId);
|
|
274
|
+
if (timescale && (result.duration > 0n)) {
|
|
275
|
+
const seconds = Number(result.duration) / timescale;
|
|
276
|
+
if (seconds > maxDurationSec) {
|
|
277
|
+
maxDurationSec = seconds;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
// Fall back to 2 seconds if duration extraction failed (shouldn't happen with well-formed FFmpeg output).
|
|
282
|
+
durations.push(maxDurationSec > 0 ? maxDurationSec : 2);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return { durations, initSegment, mediaSegments };
|
|
286
|
+
}
|
|
287
|
+
// Preroll Compositor.
|
|
288
|
+
// Maximum number of preroll entries allowed in a composite playlist window. When the composite playlist first appears, the client may be mid-stream in the progressive
|
|
289
|
+
// preroll playlist at a low segment index. Limiting preroll entries forces MEDIA-SEQUENCE past the client's current position, causing it to seek forward to near the
|
|
290
|
+
// live edge rather than playing through many seconds of remaining preroll before reaching real content.
|
|
291
|
+
const MAX_PREROLL_IN_WINDOW = 3;
|
|
292
|
+
/**
|
|
293
|
+
* Computes the sliding window start index for a composite playlist containing both preroll and real segments. The start index is determined by three constraints:
|
|
294
|
+
* (1) never negative, (2) the standard sliding window rule (current index minus window size), and (3) the preroll cap that limits how many preroll entries appear in
|
|
295
|
+
* the window. The third constraint is the key addition — it prevents clients from playing through many seconds of remaining preroll before reaching live content.
|
|
296
|
+
*
|
|
297
|
+
* @param options - Window computation parameters.
|
|
298
|
+
* @returns The start index for the composite playlist window.
|
|
299
|
+
*/
|
|
300
|
+
export function computePrerollWindow(options) {
|
|
301
|
+
const totalAvailable = options.prerollSegmentCount + options.realSegmentCount;
|
|
302
|
+
const windowSize = Math.min(totalAvailable, options.maxSegments);
|
|
303
|
+
return Math.max(0, options.currentSegmentIndex - windowSize, options.prerollSegmentCount - MAX_PREROLL_IN_WINDOW);
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Builds an array of playlist segment entries for preroll segments within the given index range. Each entry has an absolute URL pointing to the global /preroll/ route
|
|
307
|
+
* and the duration extracted from the preroll segment cache. No per-segment metadata tags (DISCONTINUITY, PROGRAM-DATE-TIME, etc.) are set — preroll is synthetic
|
|
308
|
+
* placeholder content.
|
|
309
|
+
*
|
|
310
|
+
* @param options - Entry construction parameters.
|
|
311
|
+
* @returns Ordered array of preroll segment entries for the builder.
|
|
312
|
+
*/
|
|
313
|
+
export function buildPrerollEntries(options) {
|
|
314
|
+
const entries = [];
|
|
315
|
+
for (let i = options.startIndex; i < options.prerollSegmentCount; i++) {
|
|
316
|
+
entries.push({
|
|
317
|
+
duration: getPrerollSegmentDuration(options.codec, i),
|
|
318
|
+
url: options.baseUrl + "/preroll/" + options.codec + "/segment" + String(i) + options.extension
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
return entries;
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Computes how many preroll segments should be visible based on elapsed wall-clock time since the preroll started. The initial window (PREROLL_INITIAL_WINDOW segments)
|
|
325
|
+
* is revealed immediately so the client has enough content to begin playback per the HLS 3-from-end rule. Additional segments are revealed one at a time as wall-clock
|
|
326
|
+
* time passes — each new segment appears when enough time has elapsed for the client to have consumed the prior content beyond the initial window.
|
|
327
|
+
*
|
|
328
|
+
* @param codec - The preroll codec variant.
|
|
329
|
+
* @param prerollStartTime - Wall-clock time when the preroll began.
|
|
330
|
+
* @returns The number of preroll segments to reveal.
|
|
331
|
+
*/
|
|
332
|
+
export function computeProgressiveReveal(codec, prerollStartTime) {
|
|
333
|
+
const variant = prerollVariants.get(codec);
|
|
334
|
+
if (!variant) {
|
|
335
|
+
return 0;
|
|
336
|
+
}
|
|
337
|
+
const totalSegments = variant.mediaSegments.length;
|
|
338
|
+
const elapsedSec = (Date.now() - prerollStartTime.getTime()) / 1000;
|
|
339
|
+
let revealCount = Math.min(PREROLL_INITIAL_WINDOW, totalSegments);
|
|
340
|
+
// Compute the total duration of the initial window. Segments beyond this threshold are revealed progressively — each one becomes visible when elapsed time
|
|
341
|
+
// exceeds the cumulative duration of all segments before it, minus the initial window's duration (since those were available from the start).
|
|
342
|
+
let initialWindowDuration = 0;
|
|
343
|
+
for (let i = 0; i < Math.min(PREROLL_INITIAL_WINDOW, totalSegments); i++) {
|
|
344
|
+
initialWindowDuration += variant.durations[i] ?? 2;
|
|
345
|
+
}
|
|
346
|
+
let cumulativeDuration = initialWindowDuration;
|
|
347
|
+
for (let i = PREROLL_INITIAL_WINDOW; i < totalSegments; i++) {
|
|
348
|
+
cumulativeDuration += variant.durations[i] ?? 2;
|
|
349
|
+
if (elapsedSec < (cumulativeDuration - initialWindowDuration)) {
|
|
350
|
+
break;
|
|
351
|
+
}
|
|
352
|
+
revealCount = i + 1;
|
|
353
|
+
}
|
|
354
|
+
return revealCount;
|
|
355
|
+
}
|
|
356
|
+
// Playlist Generation.
|
|
357
|
+
/**
|
|
358
|
+
* Generates a progressive HLS playlist referencing the global preroll segments for the specified codec. The playlist simulates a live stream by revealing segments
|
|
359
|
+
* based on elapsed wall-clock time since the preroll started. On each client poll, more segments become visible — the client sees new content appear and keeps playing
|
|
360
|
+
* without stalling.
|
|
361
|
+
*
|
|
362
|
+
* The playlist uses absolute URLs because preroll segments are served at global routes (/preroll/:codec/*) while the playlist itself is served under /hls/:name/.
|
|
363
|
+
* Without absolute URLs, the client would request /hls/:name/preroll/... which does not exist. The playlist omits #EXT-X-ENDLIST so it behaves as a live playlist —
|
|
364
|
+
* the client polls for updates and receives the real content playlist when ready.
|
|
365
|
+
*
|
|
366
|
+
* PROGRAM-DATE-TIME is intentionally omitted from the preroll playlist. Preroll is synthetic placeholder content — assigning it wall-clock timestamps would be
|
|
367
|
+
* misleading and would create a backward time jump at the preroll-to-live boundary (preroll PDT would overshoot real content PDT because the preroll covers 30
|
|
368
|
+
* seconds of content but real content typically arrives in ~15 seconds). PDT is emitted only on real segments once the segmenter produces them.
|
|
369
|
+
*
|
|
370
|
+
* @param baseUrl - The server's external URL (e.g., "http://192.168.1.100:5589").
|
|
371
|
+
* @param codec - The preroll codec variant.
|
|
372
|
+
* @param startingSequence - The MEDIA-SEQUENCE offset. Zero for fresh starts. For resume streams, this is set to the saved segment index so the preroll
|
|
373
|
+
* playlist continues from the prior session's sequence range rather than restarting at 0.
|
|
374
|
+
* @param prerollStartTime - Wall-clock time when the preroll began. Used to compute elapsed time and determine how many segments to reveal.
|
|
375
|
+
* @returns The complete HLS playlist string, or an empty string if the preroll is not ready.
|
|
376
|
+
*/
|
|
377
|
+
export function generatePrerollPlaylist(baseUrl, codec, startingSequence, prerollStartTime) {
|
|
378
|
+
if (!isPrerollReady(codec)) {
|
|
379
|
+
return "";
|
|
380
|
+
}
|
|
381
|
+
const revealCount = computeProgressiveReveal(codec, prerollStartTime);
|
|
382
|
+
const entries = buildPrerollEntries({ baseUrl, codec, extension: ".m4s", prerollSegmentCount: revealCount, startIndex: 0 });
|
|
383
|
+
return buildPlaylist({
|
|
384
|
+
initialMapUri: baseUrl + "/preroll/" + codec + "/init.mp4",
|
|
385
|
+
mediaSequence: startingSequence,
|
|
386
|
+
targetDuration: getPrerollMaxDuration(codec),
|
|
387
|
+
version: 7
|
|
388
|
+
}, entries);
|
|
389
|
+
}
|
|
390
|
+
// Route Setup.
|
|
391
|
+
/**
|
|
392
|
+
* Registers the preroll segment routes on the Express application. Routes are codec-prefixed (/preroll/:codec/init.mp4 and /preroll/:codec/segmentN.m4s) so each
|
|
393
|
+
* variant is served at a distinct path. The cached preroll buffers are served with immutable cache headers since the content never changes after generation.
|
|
394
|
+
* @param app - The Express application.
|
|
395
|
+
*/
|
|
396
|
+
export function setupPrerollRoutes(app) {
|
|
397
|
+
app.get("/preroll/:codec/init.mp4", (req, res) => {
|
|
398
|
+
const codec = req.params.codec;
|
|
399
|
+
const variant = ((codec === "h264") || (codec === "hevc")) ? prerollVariants.get(codec) : undefined;
|
|
400
|
+
if (!variant) {
|
|
401
|
+
res.status(404).send("Preroll not available.");
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
res.setHeader("Cache-Control", "public, max-age=86400");
|
|
405
|
+
res.setHeader("Content-Type", "video/mp4");
|
|
406
|
+
res.send(variant.initSegment);
|
|
407
|
+
});
|
|
408
|
+
// Parameterized route serves individual preroll segment buffers. Each segment contains a distinct moof+mdat pair with unique PTS values from the continuous FFmpeg
|
|
409
|
+
// output. The init.mp4 exact route above takes priority over this catch-all for init segment requests.
|
|
410
|
+
app.get("/preroll/:codec/:segment", (req, res) => {
|
|
411
|
+
const codec = req.params.codec;
|
|
412
|
+
const variant = ((codec === "h264") || (codec === "hevc")) ? prerollVariants.get(codec) : undefined;
|
|
413
|
+
if (!variant) {
|
|
414
|
+
res.status(404).send("Preroll not available.");
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
// Extract the segment index from the filename (e.g., "segment5.m4s" -> 5).
|
|
418
|
+
const segmentParam = req.params.segment;
|
|
419
|
+
const match = (typeof segmentParam === "string") ? /^segment(\d+)\.m4s$/.exec(segmentParam) : null;
|
|
420
|
+
if (!match) {
|
|
421
|
+
res.status(404).send("Preroll not available.");
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
const index = parseInt(match[1], 10);
|
|
425
|
+
if ((index < 0) || (index >= variant.mediaSegments.length)) {
|
|
426
|
+
res.status(404).send("Preroll segment not found.");
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
res.setHeader("Cache-Control", "public, max-age=86400");
|
|
430
|
+
res.setHeader("Content-Type", "video/mp4");
|
|
431
|
+
res.send(variant.mediaSegments[index]);
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
//# sourceMappingURL=preroll.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preroll.js","sourceRoot":"","sources":["../../src/streaming/preroll.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Starts the pretune polling loop. Should be called on server startup after show info polling is initialized.
|
|
3
|
+
*/
|
|
4
|
+
export declare function startPretunePolling(): void;
|
|
5
|
+
/**
|
|
6
|
+
* Stops the pretune polling loop and clears all pending timers. Should be called on server shutdown.
|
|
7
|
+
*/
|
|
8
|
+
export declare function stopPretunePolling(): void;
|