prismcast 1.5.2 → 1.6.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/channelSelection.js +14 -2
- package/dist/browser/channelSelection.js.map +1 -1
- package/dist/browser/index.d.ts +23 -9
- package/dist/browser/index.js +205 -88
- package/dist/browser/index.js.map +1 -1
- package/dist/browser/precaching.js +3 -3
- package/dist/browser/precaching.js.map +1 -1
- package/dist/browser/tuning/directv.js +13 -0
- package/dist/browser/tuning/directv.js.map +1 -1
- package/dist/browser/tuning/fox.js +12 -0
- package/dist/browser/tuning/fox.js.map +1 -1
- package/dist/browser/tuning/hbo.js +11 -0
- package/dist/browser/tuning/hbo.js.map +1 -1
- package/dist/browser/tuning/hulu.js +23 -0
- package/dist/browser/tuning/hulu.js.map +1 -1
- package/dist/browser/tuning/sling.js +34 -4
- package/dist/browser/tuning/sling.js.map +1 -1
- package/dist/browser/tuning/spectrum.js +12 -0
- package/dist/browser/tuning/spectrum.js.map +1 -1
- package/dist/browser/tuning/xfinity.d.ts +2 -0
- package/dist/browser/tuning/xfinity.js +607 -0
- package/dist/browser/tuning/xfinity.js.map +1 -0
- package/dist/browser/tuning/youtubeTv.js +14 -0
- package/dist/browser/tuning/youtubeTv.js.map +1 -1
- package/dist/browser/video.d.ts +2 -0
- package/dist/browser/video.js +99 -17
- package/dist/browser/video.js.map +1 -1
- package/dist/channels/index.js +173 -56
- package/dist/channels/index.js.map +1 -1
- package/dist/config/health.d.ts +28 -28
- package/dist/config/health.js +52 -52
- package/dist/config/health.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/providers.d.ts +7 -0
- package/dist/config/providers.js +14 -0
- package/dist/config/providers.js.map +1 -1
- package/dist/config/sites.d.ts +28 -0
- package/dist/config/sites.js +93 -122
- package/dist/config/sites.js.map +1 -1
- package/dist/config/userChannels.d.ts +3 -0
- package/dist/config/userChannels.js +10 -3
- package/dist/config/userChannels.js.map +1 -1
- package/dist/config/userConfig.d.ts +1 -0
- package/dist/config/userConfig.js +4 -0
- 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/index.js +16 -7
- 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 +58 -0
- package/dist/native/index.js +341 -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 +48 -0
- package/dist/native/probe.js +270 -0
- package/dist/native/probe.js.map +1 -0
- package/dist/native/proxy.d.ts +52 -0
- package/dist/native/proxy.js +904 -0
- package/dist/native/proxy.js.map +1 -0
- package/dist/routes/config/channels/table.js +19 -25
- package/dist/routes/config/channels/table.js.map +1 -1
- package/dist/routes/config/providers.js +4 -4
- 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 +1 -0
- package/dist/routes/index.js +3 -0
- package/dist/routes/index.js.map +1 -1
- package/dist/routes/root/content.js +80 -20
- package/dist/routes/root/content.js.map +1 -1
- package/dist/routes/root/scripts/status.js +14 -13
- package/dist/routes/root/scripts/status.js.map +1 -1
- package/dist/routes/root/styles.js +4 -2
- package/dist/routes/root/styles.js.map +1 -1
- package/dist/streaming/fmp4Segmenter.d.ts +3 -0
- package/dist/streaming/fmp4Segmenter.js +143 -42
- package/dist/streaming/fmp4Segmenter.js.map +1 -1
- package/dist/streaming/hls.d.ts +24 -19
- package/dist/streaming/hls.js +486 -261
- package/dist/streaming/hls.js.map +1 -1
- package/dist/streaming/hlsResume.d.ts +7 -0
- package/dist/streaming/hlsResume.js +22 -2
- package/dist/streaming/hlsResume.js.map +1 -1
- package/dist/streaming/hlsSegments.d.ts +42 -2
- package/dist/streaming/hlsSegments.js +109 -8
- package/dist/streaming/hlsSegments.js.map +1 -1
- package/dist/streaming/lifecycle.js +20 -2
- package/dist/streaming/lifecycle.js.map +1 -1
- package/dist/streaming/monitor.d.ts +1 -0
- package/dist/streaming/monitor.js +369 -21
- 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 +106 -0
- package/dist/streaming/preroll.js +373 -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/registry.d.ts +24 -3
- package/dist/streaming/registry.js +23 -1
- package/dist/streaming/registry.js.map +1 -1
- package/dist/streaming/setup.d.ts +7 -0
- package/dist/streaming/setup.js +27 -6
- package/dist/streaming/setup.js.map +1 -1
- package/dist/streaming/showInfo.d.ts +24 -0
- package/dist/streaming/showInfo.js +55 -3
- package/dist/streaming/showInfo.js.map +1 -1
- package/dist/streaming/statusEmitter.d.ts +6 -4
- package/dist/streaming/statusEmitter.js +9 -3
- package/dist/streaming/statusEmitter.js.map +1 -1
- package/dist/types/channels.d.ts +1 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/profiles.d.ts +8 -1
- package/dist/types/selection.d.ts +4 -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.js +15 -0
- package/dist/utils/debugFilter.js.map +1 -1
- package/dist/utils/delay.d.ts +16 -0
- package/dist/utils/delay.js +15 -1
- package/dist/utils/delay.js.map +1 -1
- package/dist/utils/ffmpeg.d.ts +1 -7
- package/dist/utils/ffmpeg.js +86 -127
- package/dist/utils/ffmpeg.js.map +1 -1
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.js +2 -0
- package/dist/utils/index.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/package.json +5 -5
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"statusEmitter.js","sourceRoot":"","sources":["../../src/streaming/statusEmitter.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"statusEmitter.js","sourceRoot":"","sources":["../../src/streaming/statusEmitter.ts"],"names":[],"mappings":"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"}
|
package/dist/types/channels.d.ts
CHANGED
package/dist/types/index.d.ts
CHANGED
|
@@ -3,5 +3,5 @@ export type { Channel, ChannelDelta, ChannelListingEntry, ChannelMap, ProviderGr
|
|
|
3
3
|
export type { ChannelSelectionConfig, ChannelSelectionStrategy, DomainConfig, ProfileCategory, ProfileResolutionResult, ProfilesValidationResult, ProviderPack, ResolvedSiteProfile, SiteProfile, UserProfilesFile, UserProfilesLoadResult } from "./profiles.js";
|
|
4
4
|
export type { ChannelSelectionProfile, ChannelSelectorResult, ChannelStrategyEntry, ChannelStrategyHandler, ClickTarget, DiscoveredChannel, ProviderModule, TuneResult, UiSize } from "./selection.js";
|
|
5
5
|
export type { ChannelSortField, Nullable, SortDirection } from "./shared.js";
|
|
6
|
-
export type { HealthStatus, StreamInfo, StreamListItem, StreamListResponse, UrlValidation, UrlValidationResult, VideoSelectorType, VideoState } from "./streaming.js";
|
|
6
|
+
export type { HealthStatus, StreamInfo, StreamListItem, StreamListResponse, StreamingMode, UrlValidation, UrlValidationResult, VideoSelectorType, VideoState } from "./streaming.js";
|
|
7
7
|
export { isChannelSelectionProfile } from "./selection.js";
|
package/dist/types/profiles.d.ts
CHANGED
|
@@ -19,9 +19,11 @@ import type { Nullable } from "./shared.js";
|
|
|
19
19
|
* USA Network.
|
|
20
20
|
* - "tileClick": Find channel element using the profile's matchSelector (defaults to image URL matching), click tile, then optionally click play button if
|
|
21
21
|
* playSelector is configured. Used by Disney+.
|
|
22
|
+
* - "xfinityDirect": Wait for the Polymer SPA's channelMap to populate, find the target channel by callSign, and call `_watchChannelEventHandler` to switch
|
|
23
|
+
* channels in-page. A fire-and-forget poll dismisses the Watch Now modal if it appears. Used by Xfinity Stream.
|
|
22
24
|
* - "youtubeGrid": Find channel by aria-label in a non-virtualized EPG grid, extract the watch URL, and navigate directly. Used by YouTube TV.
|
|
23
25
|
*/
|
|
24
|
-
export type ChannelSelectionStrategy = "directvGrid" | "foxGrid" | "guideGrid" | "hboGrid" | "none" | "slingGrid" | "spectrumGrid" | "thumbnailRow" | "tileClick" | "youtubeGrid";
|
|
26
|
+
export type ChannelSelectionStrategy = "directvGrid" | "foxGrid" | "guideGrid" | "hboGrid" | "none" | "slingGrid" | "spectrumGrid" | "thumbnailRow" | "tileClick" | "xfinityDirect" | "youtubeGrid";
|
|
25
27
|
/**
|
|
26
28
|
* Configuration for channel selection behavior within a site profile.
|
|
27
29
|
*/
|
|
@@ -53,6 +55,7 @@ export interface SiteProfile {
|
|
|
53
55
|
channelSelector?: Nullable<string>;
|
|
54
56
|
clickSelector?: Nullable<string>;
|
|
55
57
|
clickToPlay?: boolean;
|
|
58
|
+
dismissSelector?: Nullable<string>;
|
|
56
59
|
description?: string;
|
|
57
60
|
extends?: string;
|
|
58
61
|
summary?: string;
|
|
@@ -75,6 +78,7 @@ export interface ResolvedSiteProfile {
|
|
|
75
78
|
channelSelector: Nullable<string>;
|
|
76
79
|
clickSelector: Nullable<string>;
|
|
77
80
|
clickToPlay: boolean;
|
|
81
|
+
dismissSelector: Nullable<string>;
|
|
78
82
|
fullscreenKey: Nullable<string>;
|
|
79
83
|
fullscreenSelector: Nullable<string>;
|
|
80
84
|
hideSelector: Nullable<string>;
|
|
@@ -84,6 +88,7 @@ export interface ResolvedSiteProfile {
|
|
|
84
88
|
noVideo: boolean;
|
|
85
89
|
selectReadyVideo: boolean;
|
|
86
90
|
useRequestFullscreen: boolean;
|
|
91
|
+
videoTimeout: Nullable<number>;
|
|
87
92
|
waitForNetworkIdle: boolean;
|
|
88
93
|
}
|
|
89
94
|
/**
|
|
@@ -99,11 +104,13 @@ export interface ProfileResolutionResult {
|
|
|
99
104
|
* configuration and/or a provider display name for friendly UI labels. Used by both built-in domain mappings in sites.ts and user-defined mappings in profiles.json.
|
|
100
105
|
*/
|
|
101
106
|
export interface DomainConfig {
|
|
107
|
+
dismissSelector?: string;
|
|
102
108
|
loginUrl?: string;
|
|
103
109
|
maxContinuousPlayback?: number;
|
|
104
110
|
profile?: string;
|
|
105
111
|
provider?: string;
|
|
106
112
|
providerTag?: string;
|
|
113
|
+
videoTimeout?: number;
|
|
107
114
|
}
|
|
108
115
|
/**
|
|
109
116
|
* Storage format for profiles.json. Contains user-defined site profiles and domain mappings that extend or override the built-in configurations.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ChannelSelectionStrategy, ResolvedSiteProfile } from "./profiles.js";
|
|
1
|
+
import type { ChannelSelectionStrategy, ResolvedSiteProfile, SiteProfile } from "./profiles.js";
|
|
2
2
|
import type { Frame, Page } from "puppeteer-core";
|
|
3
3
|
import type { Nullable } from "./shared.js";
|
|
4
4
|
export interface ChannelSelectionProfile extends ResolvedSiteProfile {
|
|
@@ -72,8 +72,11 @@ export interface ProviderModule {
|
|
|
72
72
|
guideUrl: string;
|
|
73
73
|
handlesOwnNavigation?: boolean;
|
|
74
74
|
label: string;
|
|
75
|
+
profile?: SiteProfile;
|
|
76
|
+
profileName?: string;
|
|
75
77
|
slug: string;
|
|
76
78
|
strategy: ChannelStrategyEntry;
|
|
79
|
+
tinySegmentThreshold?: number;
|
|
77
80
|
strategyName: ChannelSelectionStrategy;
|
|
78
81
|
validatePrecache?: (channels: DiscoveredChannel[]) => boolean;
|
|
79
82
|
validateTune?: (channelSelector: string) => boolean;
|
|
@@ -24,6 +24,8 @@ export interface VideoState {
|
|
|
24
24
|
paused: boolean;
|
|
25
25
|
readyState: number;
|
|
26
26
|
time: number;
|
|
27
|
+
videoHeight: number;
|
|
28
|
+
videoWidth: number;
|
|
27
29
|
volume: number;
|
|
28
30
|
}
|
|
29
31
|
/**
|
|
@@ -42,6 +44,11 @@ export interface UrlValidationResult {
|
|
|
42
44
|
* Alias for UrlValidationResult maintained for backward compatibility with existing code.
|
|
43
45
|
*/
|
|
44
46
|
export type UrlValidation = UrlValidationResult;
|
|
47
|
+
/**
|
|
48
|
+
* Streaming mode for an active stream. "capture" uses Chrome screen capture via puppeteer-stream. "native" intercepts the provider's HLS stream and consumes it
|
|
49
|
+
* directly in Node, bypassing screen capture entirely.
|
|
50
|
+
*/
|
|
51
|
+
export type StreamingMode = "capture" | "native";
|
|
45
52
|
/**
|
|
46
53
|
* Health check response structure returned by the /health endpoint.
|
|
47
54
|
*/
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Nullable } from "../types/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Sets the Chrome User-Agent string. Called by browser/index.ts when the browser launches (with the UA) and when it disconnects or closes (with null).
|
|
4
|
+
* @param ua - The User-Agent string from the Chrome browser, or null to clear.
|
|
5
|
+
*/
|
|
6
|
+
export declare function setChromeUserAgent(ua: Nullable<string>): void;
|
|
7
|
+
/**
|
|
8
|
+
* Returns the current Chrome User-Agent string, or null if the browser is not running.
|
|
9
|
+
* @returns The Chrome User-Agent string or null.
|
|
10
|
+
*/
|
|
11
|
+
export declare function getChromeUserAgent(): Nullable<string>;
|
|
12
|
+
/**
|
|
13
|
+
* Fetch wrapper that injects the Chrome User-Agent header into outgoing requests. When the browser is running, the User-Agent header is set to match Chrome's
|
|
14
|
+
* actual UA string so CDN requests appear to originate from the same browser. When the browser is not running, falls through to plain fetch() with no UA
|
|
15
|
+
* injection. Any caller-supplied User-Agent header takes precedence and is not overwritten.
|
|
16
|
+
* @param url - The URL to fetch.
|
|
17
|
+
* @param init - Optional fetch options (method, headers, signal, etc.).
|
|
18
|
+
* @returns The fetch Response.
|
|
19
|
+
*/
|
|
20
|
+
export declare function chromeFetch(url: string | URL, init?: RequestInit): Promise<Response>;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/* When PrismCast fetches manifests, segments, decryption keys, and other resources from provider CDNs, the requests should appear to originate from the same Chrome
|
|
2
|
+
* browser that is rendering the page. This module stores the Chrome User-Agent string (captured at browser launch) and provides a drop-in fetch() wrapper that
|
|
3
|
+
* injects it into outgoing requests. The setter/getter pattern avoids a circular dependency between utils/ and browser/ — browser/index.ts calls the setter at
|
|
4
|
+
* launch and cleanup, while consumer modules import chromeFetch() without importing from browser/.
|
|
5
|
+
*/
|
|
6
|
+
// The Chrome User-Agent string captured at browser launch. Null when the browser is not running.
|
|
7
|
+
let currentUserAgent = null;
|
|
8
|
+
/**
|
|
9
|
+
* Sets the Chrome User-Agent string. Called by browser/index.ts when the browser launches (with the UA) and when it disconnects or closes (with null).
|
|
10
|
+
* @param ua - The User-Agent string from the Chrome browser, or null to clear.
|
|
11
|
+
*/
|
|
12
|
+
export function setChromeUserAgent(ua) {
|
|
13
|
+
currentUserAgent = ua;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Returns the current Chrome User-Agent string, or null if the browser is not running.
|
|
17
|
+
* @returns The Chrome User-Agent string or null.
|
|
18
|
+
*/
|
|
19
|
+
export function getChromeUserAgent() {
|
|
20
|
+
return currentUserAgent;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Fetch wrapper that injects the Chrome User-Agent header into outgoing requests. When the browser is running, the User-Agent header is set to match Chrome's
|
|
24
|
+
* actual UA string so CDN requests appear to originate from the same browser. When the browser is not running, falls through to plain fetch() with no UA
|
|
25
|
+
* injection. Any caller-supplied User-Agent header takes precedence and is not overwritten.
|
|
26
|
+
* @param url - The URL to fetch.
|
|
27
|
+
* @param init - Optional fetch options (method, headers, signal, etc.).
|
|
28
|
+
* @returns The fetch Response.
|
|
29
|
+
*/
|
|
30
|
+
export async function chromeFetch(url, init) {
|
|
31
|
+
const ua = currentUserAgent;
|
|
32
|
+
if (!ua) {
|
|
33
|
+
return fetch(url, init);
|
|
34
|
+
}
|
|
35
|
+
// Merge the Chrome UA into the request headers. We use the Headers constructor to normalize whatever format the caller passed (plain object, Headers instance,
|
|
36
|
+
// or array of key-value pairs), then set User-Agent only if the caller did not already provide one.
|
|
37
|
+
const headers = new Headers(init?.headers);
|
|
38
|
+
if (!headers.has("User-Agent")) {
|
|
39
|
+
headers.set("User-Agent", ua);
|
|
40
|
+
}
|
|
41
|
+
return fetch(url, { ...init, headers });
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=chromeFetch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chromeFetch.js","sourceRoot":"","sources":["../../src/utils/chromeFetch.ts"],"names":[],"mappings":"AAMA;;;;GAIG;AAEH,iGAAiG;AACjG,IAAI,gBAAgB,GAAqB,IAAI,CAAC;AAE9C;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,EAAoB;IAErD,gBAAgB,GAAG,EAAE,CAAC;AACxB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,kBAAkB;IAEhC,OAAO,gBAAgB,CAAC;AAC1B,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,GAAiB,EAAE,IAAkB;IAErE,MAAM,EAAE,GAAG,gBAAgB,CAAC;IAE5B,IAAG,CAAC,EAAE,EAAE,CAAC;QAEP,OAAO,KAAK,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IAC1B,CAAC;IAED,+JAA+J;IAC/J,oGAAoG;IACpG,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAE3C,IAAG,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,EAAE,CAAC;QAE9B,OAAO,CAAC,GAAG,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;IAChC,CAAC;IAED,OAAO,KAAK,CAAC,GAAG,EAAE,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;AAC1C,CAAC"}
|
|
@@ -123,20 +123,34 @@ export const DEBUG_CATEGORIES = [
|
|
|
123
123
|
{ category: "browser", description: "Browser lifecycle: launch, close, stale page cleanup, restart." },
|
|
124
124
|
{ category: "browser:video", description: "Video context, fullscreen, volume locking, playback." },
|
|
125
125
|
{ category: "config", description: "Provider groups, version checking." },
|
|
126
|
+
{ category: "native:coordinator", description: "Native streaming decisions: interception result, probe result, capture teardown, proxy start." },
|
|
127
|
+
{ category: "native:decrypt", description: "AES-128 decryption: key fetch, IV source (explicit vs. sequence), segment sizes." },
|
|
128
|
+
{ category: "native:intercept", description: "CDP manifest interception: listener installed, .m3u8 URLs observed, master identified, timeout." },
|
|
129
|
+
{ category: "native:manifest", description: "Raw variant manifest body from provider — verbose, use for diagnosing DAI/ad stitching issues." },
|
|
130
|
+
{ category: "native:monitor", description: "Native health: segment staleness, error counts, L2/L3 recovery actions, capture fallback." },
|
|
131
|
+
{ category: "native:probe", description: "DRM probe: variant count, bandwidths, encryption classification, key accessibility, cache hit/miss." },
|
|
132
|
+
{ category: "native:proxy", description: "Manifest polling, segment fetch/store, playlist generation, segment rotation, key rotation." },
|
|
133
|
+
{ category: "native:token", description: "Token refresh: expiry parsed, timer scheduled, refresh triggered, manifest acquired." },
|
|
126
134
|
{ category: "precache", description: "Channel lineup precaching: deferred runs, provider filter skips." },
|
|
127
135
|
{ category: "recovery", description: "General recovery: browser re-minimize, monitor abort." },
|
|
128
136
|
{ category: "recovery:context", description: "Video context: frame detachment, re-search." },
|
|
129
137
|
{ category: "recovery:nav", description: "Page navigation recovery: new tab detection, URL validation." },
|
|
138
|
+
{ category: "recovery:resolution", description: "Video resolution monitoring: ABR degradation detection, grace periods, recovery escalation." },
|
|
130
139
|
{ category: "recovery:segments", description: "Segment production: self-heal detection." },
|
|
131
140
|
{ category: "recovery:tab", description: "Tab replacement: old tab cleanup, new tab creation, retries." },
|
|
141
|
+
{ category: "recovery:tracks", description: "Track composition: video traf presence in below-threshold segments." },
|
|
132
142
|
{ category: "retry", description: "Retry attempts, page-closed aborts." },
|
|
133
143
|
{ category: "streaming:ffmpeg", description: "FFmpeg stderr output, pipe errors." },
|
|
134
144
|
{ category: "streaming:hls", description: "HLS segment storage, page close errors." },
|
|
145
|
+
{ category: "streaming:mpegts", description: "MPEG-TS client connections, FFmpeg remuxer spawn and errors." },
|
|
146
|
+
{ category: "streaming:preroll", description: "Preroll lifecycle: fMP4 generation, preroll playlist delivery, transition to live content." },
|
|
147
|
+
{ category: "streaming:pretune", description: "Predictive channel pretuning: DVR job polling, timer scheduling, tune triggers." },
|
|
135
148
|
{ category: "streaming:segmenter", description: "fMP4 parsing: keyframes, init segments, duration clamping." },
|
|
136
149
|
{ category: "streaming:setup", description: "Stream setup: redirect resolution, profile override, capture init." },
|
|
137
150
|
{ category: "streaming:showinfo", description: "Channels DVR show name lookups, device mapping." },
|
|
138
151
|
{ category: "timing:browser", description: "Browser launch: process spawn, extension init, display detection." },
|
|
139
152
|
{ category: "timing:hls", description: "HLS playlist delivery time." },
|
|
153
|
+
{ category: "timing:native", description: "Native streaming: interception latency, probe latency, first segment, refresh duration." },
|
|
140
154
|
{ category: "timing:recovery", description: "Recovery totals: navigation recovery, tab replacement." },
|
|
141
155
|
{ category: "timing:startup", description: "Stream startup: init segment, first playlist, capture ready." },
|
|
142
156
|
{ category: "timing:tab", description: "Tab replacement: old tab cleanup, new page creation." },
|
|
@@ -147,6 +161,7 @@ export const DEBUG_CATEGORIES = [
|
|
|
147
161
|
{ category: "tuning:sling", description: "Sling TV guide grid: binary search, cache, click retries." },
|
|
148
162
|
{ category: "tuning:spectrum", description: "Spectrum TV guide grid: channel discovery, cache, direct URL navigation." },
|
|
149
163
|
{ category: "tuning:tileClick", description: "Tile click strategy: scroll phase, matchSelector poll, play button retries, modal dismiss." },
|
|
164
|
+
{ category: "tuning:xfinity", description: "Xfinity Stream: channelmap API interception, SPA channel switching, Watch Now modal dismissal." },
|
|
150
165
|
{ category: "tuning:yttv", description: "YouTube TV EPG grid navigation." }
|
|
151
166
|
];
|
|
152
167
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"debugFilter.js","sourceRoot":"","sources":["../../src/utils/debugFilter.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"debugFilter.js","sourceRoot":"","sources":["../../src/utils/debugFilter.ts"],"names":[],"mappings":"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"}
|
package/dist/utils/delay.d.ts
CHANGED
|
@@ -1,3 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A cancellable timeout that can be used with Promise.race. The promise resolves to false when the timeout fires, and cancel() clears the timer to prevent it
|
|
3
|
+
* from holding an event loop reference after the race is won by another promise.
|
|
4
|
+
*/
|
|
5
|
+
export interface CancellableTimeout {
|
|
6
|
+
cancel: () => void;
|
|
7
|
+
promise: Promise<false>;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Creates a cancellable timeout for use with Promise.race. Returns a promise that resolves to false after the specified delay, and a cancel function that clears
|
|
11
|
+
* the timer. This avoids the split declaration-then-assignment pattern that requires definite assignment assertions when managing timer IDs alongside
|
|
12
|
+
* Promise.race.
|
|
13
|
+
* @param ms - The timeout duration in milliseconds.
|
|
14
|
+
* @returns An object with the timeout promise and a cancel function.
|
|
15
|
+
*/
|
|
16
|
+
export declare function cancellableTimeout(ms: number): CancellableTimeout;
|
|
1
17
|
/**
|
|
2
18
|
* Creates a promise that resolves after the specified delay. This is a convenience wrapper around setTimeout that allows using async/await syntax for delays.
|
|
3
19
|
* @param ms - The delay duration in milliseconds.
|
package/dist/utils/delay.js
CHANGED
|
@@ -1,7 +1,21 @@
|
|
|
1
1
|
/* Copyright(C) 2024-2026, HJD (https://github.com/hjdhjd). All rights reserved.
|
|
2
2
|
*
|
|
3
|
-
* delay.ts: Async delay
|
|
3
|
+
* delay.ts: Async delay and timeout utilities for PrismCast.
|
|
4
4
|
*/
|
|
5
|
+
/**
|
|
6
|
+
* Creates a cancellable timeout for use with Promise.race. Returns a promise that resolves to false after the specified delay, and a cancel function that clears
|
|
7
|
+
* the timer. This avoids the split declaration-then-assignment pattern that requires definite assignment assertions when managing timer IDs alongside
|
|
8
|
+
* Promise.race.
|
|
9
|
+
* @param ms - The timeout duration in milliseconds.
|
|
10
|
+
* @returns An object with the timeout promise and a cancel function.
|
|
11
|
+
*/
|
|
12
|
+
export function cancellableTimeout(ms) {
|
|
13
|
+
let timer;
|
|
14
|
+
const promise = new Promise((resolve) => {
|
|
15
|
+
timer = setTimeout(() => { resolve(false); }, ms);
|
|
16
|
+
});
|
|
17
|
+
return { cancel: () => { clearTimeout(timer); }, promise };
|
|
18
|
+
}
|
|
5
19
|
/**
|
|
6
20
|
* Creates a promise that resolves after the specified delay. This is a convenience wrapper around setTimeout that allows using async/await syntax for delays.
|
|
7
21
|
* @param ms - The delay duration in milliseconds.
|
package/dist/utils/delay.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"delay.js","sourceRoot":"","sources":["../../src/utils/delay.ts"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"delay.js","sourceRoot":"","sources":["../../src/utils/delay.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAYH;;;;;;GAMG;AACH,MAAM,UAAU,kBAAkB,CAAC,EAAU;IAE3C,IAAI,KAAoC,CAAC;IAEzC,MAAM,OAAO,GAAG,IAAI,OAAO,CAAQ,CAAC,OAAO,EAAE,EAAE;QAE7C,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACpD,CAAC,CAAC,CAAC;IAEH,OAAO,EAAE,MAAM,EAAE,GAAS,EAAE,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC;AACnE,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,KAAK,CAAC,EAAU;IAEpC,OAAO,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;QAEnC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;IAC1B,CAAC,CAAC,CAAC;AACL,CAAC"}
|
package/dist/utils/ffmpeg.d.ts
CHANGED
|
@@ -45,13 +45,7 @@ export declare function spawnFFmpeg(audioBitrate: number, onError: (error: Error
|
|
|
45
45
|
* - `-probesize 16384`: Limit input probing to 16KB (fMP4 init segment is ~1.3KB) to minimize startup delay
|
|
46
46
|
* - `-f mp4 -i pipe:0`: Read fragmented MP4 from stdin
|
|
47
47
|
* - `-c copy`: Copy both video and audio codecs without transcoding
|
|
48
|
-
* -
|
|
49
|
-
* - `-mpegts_pmt_start_pid 0x0020`: Use ATSC-conventional PMT PID range instead of FFmpeg's default (0x1000). Minimum allowed value is 0x0020 (32).
|
|
50
|
-
* - `-mpegts_start_pid 0x0031`: Use ATSC-conventional elementary stream PIDs instead of FFmpeg's defaults (0x100+)
|
|
51
|
-
* - `-mpegts_service_type digital_tv`: Label the service as digital TV in the PMT service descriptor
|
|
52
|
-
* - `-pat_period 0.1`: Repeat PAT/PMT tables every 100ms, matching ATSC broadcast frequency
|
|
53
|
-
* - `-pcr_period 40`: Insert PCR timestamps every 40ms, matching ATSC broadcast convention
|
|
54
|
-
* - `-flush_packets 1`: Flush output immediately after each packet to minimize latency
|
|
48
|
+
* - MPEG-TS output flags: See MPEGTS_OUTPUT_FLAGS for ATSC-conventional settings
|
|
55
49
|
* - `pipe:1`: Write output to stdout
|
|
56
50
|
* @param onError - Callback invoked when FFmpeg exits unexpectedly or encounters an error.
|
|
57
51
|
* @param streamId - Optional stream identifier for logging.
|
package/dist/utils/ffmpeg.js
CHANGED
|
@@ -102,98 +102,75 @@ export async function resolveFFmpegPath() {
|
|
|
102
102
|
cachedFFmpegPath = undefined;
|
|
103
103
|
return undefined;
|
|
104
104
|
}
|
|
105
|
+
// MPEG-TS Output Flags.
|
|
106
|
+
/* MPEG-TS muxer flags for the fMP4-to-MPEG-TS remuxer. Tuned to produce output resembling a real HDHomeRun CONNECT DUO (HDTC-2US) ATSC transport stream.
|
|
107
|
+
* Plex's transcoder may make assumptions about stream structure based on the reported device model (PID assignments, PAT/PMT frequency). Using ATSC-conventional
|
|
108
|
+
* values avoids "Invalid argument" failures when Plex tries to transcode the live session for remote clients. These are pure container metadata changes — the actual
|
|
109
|
+
* A/V data is untouched by -c copy.
|
|
110
|
+
*/
|
|
111
|
+
const MPEGTS_OUTPUT_FLAGS = [
|
|
112
|
+
"-f", "mpegts",
|
|
113
|
+
"-mpegts_pmt_start_pid", "0x0020",
|
|
114
|
+
"-mpegts_start_pid", "0x0031",
|
|
115
|
+
"-mpegts_service_type", "digital_tv",
|
|
116
|
+
"-pat_period", "0.1",
|
|
117
|
+
"-pcr_period", "40",
|
|
118
|
+
"-flush_packets", "1"
|
|
119
|
+
];
|
|
120
|
+
// Internal Helper.
|
|
105
121
|
/**
|
|
106
|
-
* Spawns an FFmpeg process
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
* FFmpeg
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
* - `-i pipe:0`: Read input from stdin
|
|
113
|
-
* - `-c:v copy`: Copy video stream without re-encoding (H264 passthrough)
|
|
114
|
-
* - `-c:a aac -b:a <bitrate>`: Transcode audio to AAC at specified bitrate
|
|
115
|
-
* - `-f mp4`: Output MP4 container format
|
|
116
|
-
* - `-movflags frag_keyframe+empty_moov+default_base_moof`: Streaming-friendly fMP4 flags
|
|
117
|
-
* - `-flush_packets 1`: Flush output immediately after each packet to minimize latency
|
|
118
|
-
* - `pipe:1`: Write output to stdout
|
|
119
|
-
* @param audioBitrate - Audio bitrate in bits per second (e.g., 256000 for 256 kbps).
|
|
120
|
-
* @param onError - Callback invoked when FFmpeg exits unexpectedly or encounters an error.
|
|
121
|
-
* @param streamId - Stream identifier for logging.
|
|
122
|
-
* @param comment - Optional comment metadata (channel name or domain) to embed in the output.
|
|
122
|
+
* Spawns an FFmpeg process with shared lifecycle management. Handles stderr logging with noise filtering, exit and error callbacks, and graceful shutdown for all
|
|
123
|
+
* FFmpeg use cases. Callers provide the FFmpeg arguments and a descriptive label; this helper handles everything else.
|
|
124
|
+
* @param options.args - FFmpeg command-line arguments.
|
|
125
|
+
* @param options.label - Descriptive label for log messages and error strings (e.g., "FFmpeg", "MPEG-TS remuxer").
|
|
126
|
+
* @param options.onError - Callback invoked when FFmpeg exits unexpectedly or encounters an error.
|
|
127
|
+
* @param options.streamId - Optional stream identifier for log prefixing.
|
|
123
128
|
* @returns FFmpeg process wrapper with stdin, stdout, and kill function.
|
|
124
129
|
*/
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
const ffmpegPath = cachedFFmpegPath ?? "ffmpeg";
|
|
129
|
-
// Use Apple's AudioToolbox AAC encoder on macOS for better quality and performance. Fall back to FFmpeg's built-in AAC encoder on other platforms.
|
|
130
|
-
const aacEncoder = process.platform === "darwin" ? "aac_at" : "aac";
|
|
131
|
-
const ffmpegArgs = [
|
|
132
|
-
"-hide_banner",
|
|
133
|
-
"-loglevel", "warning",
|
|
134
|
-
"-probesize", "16384",
|
|
135
|
-
"-i", "pipe:0",
|
|
136
|
-
"-c:v", "copy",
|
|
137
|
-
"-c:a", aacEncoder,
|
|
138
|
-
"-b:a", String(audioBitrate),
|
|
139
|
-
"-f", "mp4",
|
|
140
|
-
"-movflags", "frag_keyframe+empty_moov+default_base_moof+skip_sidx+skip_trailer",
|
|
141
|
-
"-flush_packets", "1"
|
|
142
|
-
];
|
|
143
|
-
// Add metadata comment if provided. This embeds "PrismCast - <channel>" in the output for identification.
|
|
144
|
-
if (comment) {
|
|
145
|
-
ffmpegArgs.push("-metadata", "comment=PrismCast - " + comment);
|
|
146
|
-
}
|
|
147
|
-
ffmpegArgs.push("pipe:1");
|
|
148
|
-
const ffmpeg = spawn(ffmpegPath, ffmpegArgs, {
|
|
149
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
150
|
-
});
|
|
130
|
+
function spawnFFmpegProcess({ args, label, onError, streamId }) {
|
|
131
|
+
const ffmpegBin = cachedFFmpegPath ?? "ffmpeg";
|
|
132
|
+
const ffmpeg = spawn(ffmpegBin, args, { stdio: ["pipe", "pipe", "pipe"] });
|
|
151
133
|
const logPrefix = streamId ? "[" + streamId + "] " : "";
|
|
152
134
|
// Track whether graceful shutdown has been initiated. When true, we suppress error callbacks because any exit (whether from SIGTERM or stdin close) is expected.
|
|
153
135
|
let shuttingDown = false;
|
|
154
|
-
// Log FFmpeg stderr output (warnings and errors).
|
|
136
|
+
// Log FFmpeg stderr output (warnings and errors). Suppress noise patterns that are not actionable.
|
|
155
137
|
ffmpeg.stderr.on("data", (data) => {
|
|
156
|
-
// Suppress warnings during shutdown - truncated input warnings are expected when the capture stream closes.
|
|
157
138
|
if (shuttingDown) {
|
|
158
139
|
return;
|
|
159
140
|
}
|
|
160
141
|
const message = data.toString().trim();
|
|
161
|
-
// Filter out common noise that isn't actionable.
|
|
162
142
|
const noisePatterns = ["Press [q] to stop", "frame=", "size=", "time=", "bitrate=", "speed="];
|
|
163
143
|
if (noisePatterns.some((pattern) => message.includes(pattern))) {
|
|
164
144
|
return;
|
|
165
145
|
}
|
|
166
146
|
if (message.length > 0) {
|
|
167
|
-
LOG.debug("streaming:ffmpeg", "%
|
|
147
|
+
LOG.debug("streaming:ffmpeg", "%s%s: %s", logPrefix, label, message);
|
|
168
148
|
}
|
|
169
149
|
});
|
|
170
150
|
// Handle FFmpeg process exit.
|
|
171
151
|
ffmpeg.on("exit", (code, signal) => {
|
|
172
|
-
// During graceful shutdown, any exit is expected (whether from SIGTERM or stdin closing).
|
|
173
152
|
if (shuttingDown) {
|
|
174
153
|
return;
|
|
175
154
|
}
|
|
176
155
|
if (signal === "SIGTERM") {
|
|
177
|
-
// Normal termination via kill()
|
|
156
|
+
// Normal termination via kill() — do not treat as error.
|
|
178
157
|
return;
|
|
179
158
|
}
|
|
180
159
|
if ((code !== null) && (code !== 0)) {
|
|
181
|
-
onError(new Error("
|
|
160
|
+
onError(new Error(label + " exited with code " + String(code) + "."));
|
|
182
161
|
}
|
|
183
162
|
else if (signal) {
|
|
184
|
-
onError(new Error("
|
|
163
|
+
onError(new Error(label + " killed by signal " + signal + "."));
|
|
185
164
|
}
|
|
186
165
|
});
|
|
187
166
|
// Handle spawn errors (e.g., FFmpeg not found).
|
|
188
167
|
ffmpeg.on("error", (error) => {
|
|
189
|
-
// During graceful shutdown, suppress errors from stdin pipe closing.
|
|
190
168
|
if (shuttingDown) {
|
|
191
169
|
return;
|
|
192
170
|
}
|
|
193
171
|
onError(error);
|
|
194
172
|
});
|
|
195
|
-
// Kill function for graceful shutdown. Sets the shuttingDown flag before sending SIGTERM so that any exit
|
|
196
|
-
// stream ending) is treated as normal termination.
|
|
173
|
+
// Kill function for graceful shutdown. Sets the shuttingDown flag before sending SIGTERM so that any exit is treated as normal termination.
|
|
197
174
|
const kill = () => {
|
|
198
175
|
shuttingDown = true;
|
|
199
176
|
if (!ffmpeg.killed) {
|
|
@@ -207,6 +184,54 @@ export function spawnFFmpeg(audioBitrate, onError, streamId, comment) {
|
|
|
207
184
|
stdout: ffmpeg.stdout
|
|
208
185
|
};
|
|
209
186
|
}
|
|
187
|
+
// Public FFmpeg Spawners.
|
|
188
|
+
/**
|
|
189
|
+
* Spawns an FFmpeg process configured to transcode WebM (H264+Opus) to fMP4 (H264+AAC). The process reads from stdin and writes to stdout, allowing it to be
|
|
190
|
+
* integrated into a Node.js stream pipeline. Video is passed through unchanged; audio is transcoded from Opus to AAC for HLS compatibility.
|
|
191
|
+
*
|
|
192
|
+
* FFmpeg arguments:
|
|
193
|
+
* - `-hide_banner -loglevel warning`: Reduce noise, only show warnings/errors
|
|
194
|
+
* - `-probesize 16384`: Limit input probing to 16KB (Chrome's WebM header fits well under this) to minimize startup delay
|
|
195
|
+
* - `-i pipe:0`: Read input from stdin
|
|
196
|
+
* - `-c:v copy`: Copy video stream without re-encoding (H264 passthrough)
|
|
197
|
+
* - `-c:a aac -b:a <bitrate>`: Transcode audio to AAC at specified bitrate
|
|
198
|
+
* - `-f mp4`: Output MP4 container format
|
|
199
|
+
* - `-movflags frag_keyframe+empty_moov+default_base_moof`: Streaming-friendly fMP4 flags
|
|
200
|
+
* - `-flush_packets 1`: Flush output immediately after each packet to minimize latency
|
|
201
|
+
* - `pipe:1`: Write output to stdout
|
|
202
|
+
* @param audioBitrate - Audio bitrate in bits per second (e.g., 256000 for 256 kbps).
|
|
203
|
+
* @param onError - Callback invoked when FFmpeg exits unexpectedly or encounters an error.
|
|
204
|
+
* @param streamId - Stream identifier for logging.
|
|
205
|
+
* @param comment - Optional comment metadata (channel name or domain) to embed in the output.
|
|
206
|
+
* @returns FFmpeg process wrapper with stdin, stdout, and kill function.
|
|
207
|
+
*/
|
|
208
|
+
export function spawnFFmpeg(audioBitrate, onError, streamId, comment) {
|
|
209
|
+
// Use Apple's AudioToolbox AAC encoder on macOS for better quality and performance. Fall back to FFmpeg's built-in AAC encoder on other platforms.
|
|
210
|
+
const aacEncoder = process.platform === "darwin" ? "aac_at" : "aac";
|
|
211
|
+
const ffmpegArgs = [
|
|
212
|
+
"-hide_banner",
|
|
213
|
+
"-loglevel", "warning",
|
|
214
|
+
"-probesize", "16384",
|
|
215
|
+
"-i", "pipe:0",
|
|
216
|
+
"-c:v", "copy",
|
|
217
|
+
"-c:a", aacEncoder,
|
|
218
|
+
"-b:a", String(audioBitrate),
|
|
219
|
+
"-f", "mp4",
|
|
220
|
+
"-movflags", "frag_keyframe+empty_moov+default_base_moof+skip_sidx+skip_trailer",
|
|
221
|
+
"-flush_packets", "1"
|
|
222
|
+
];
|
|
223
|
+
// Add metadata comment if provided. This embeds "PrismCast - <channel>" in the output for identification.
|
|
224
|
+
if (comment) {
|
|
225
|
+
ffmpegArgs.push("-metadata", "comment=PrismCast - " + comment);
|
|
226
|
+
}
|
|
227
|
+
ffmpegArgs.push("pipe:1");
|
|
228
|
+
return spawnFFmpegProcess({
|
|
229
|
+
args: ffmpegArgs,
|
|
230
|
+
label: "FFmpeg",
|
|
231
|
+
onError,
|
|
232
|
+
streamId
|
|
233
|
+
});
|
|
234
|
+
}
|
|
210
235
|
/**
|
|
211
236
|
* Spawns an FFmpeg process configured to remux fMP4 input to MPEG-TS output with codec copy. The process reads a continuous fMP4 stream (init segment followed by
|
|
212
237
|
* media segments) from stdin and writes MPEG-TS to stdout. No transcoding occurs — both video (H264) and audio (AAC) are copied unchanged — so CPU usage is minimal.
|
|
@@ -216,24 +241,13 @@ export function spawnFFmpeg(audioBitrate, onError, streamId, comment) {
|
|
|
216
241
|
* - `-probesize 16384`: Limit input probing to 16KB (fMP4 init segment is ~1.3KB) to minimize startup delay
|
|
217
242
|
* - `-f mp4 -i pipe:0`: Read fragmented MP4 from stdin
|
|
218
243
|
* - `-c copy`: Copy both video and audio codecs without transcoding
|
|
219
|
-
* -
|
|
220
|
-
* - `-mpegts_pmt_start_pid 0x0020`: Use ATSC-conventional PMT PID range instead of FFmpeg's default (0x1000). Minimum allowed value is 0x0020 (32).
|
|
221
|
-
* - `-mpegts_start_pid 0x0031`: Use ATSC-conventional elementary stream PIDs instead of FFmpeg's defaults (0x100+)
|
|
222
|
-
* - `-mpegts_service_type digital_tv`: Label the service as digital TV in the PMT service descriptor
|
|
223
|
-
* - `-pat_period 0.1`: Repeat PAT/PMT tables every 100ms, matching ATSC broadcast frequency
|
|
224
|
-
* - `-pcr_period 40`: Insert PCR timestamps every 40ms, matching ATSC broadcast convention
|
|
225
|
-
* - `-flush_packets 1`: Flush output immediately after each packet to minimize latency
|
|
244
|
+
* - MPEG-TS output flags: See MPEGTS_OUTPUT_FLAGS for ATSC-conventional settings
|
|
226
245
|
* - `pipe:1`: Write output to stdout
|
|
227
246
|
* @param onError - Callback invoked when FFmpeg exits unexpectedly or encounters an error.
|
|
228
247
|
* @param streamId - Optional stream identifier for logging.
|
|
229
248
|
* @returns FFmpeg process wrapper with stdin, stdout, and kill function.
|
|
230
249
|
*/
|
|
231
250
|
export function spawnMpegTsRemuxer(onError, streamId) {
|
|
232
|
-
const ffmpegBin = cachedFFmpegPath ?? "ffmpeg";
|
|
233
|
-
// MPEG-TS muxer flags are tuned to produce output resembling a real HDHomeRun CONNECT DUO (HDTC-2US) ATSC transport stream. Plex's transcoder may make
|
|
234
|
-
// assumptions about stream structure based on the reported device model (PID assignments, PAT/PMT frequency). Using ATSC-conventional values avoids "Invalid
|
|
235
|
-
// argument" failures when Plex tries to transcode the live session for remote clients. These are pure container metadata changes — the actual A/V data is
|
|
236
|
-
// untouched by -c copy.
|
|
237
251
|
const ffmpegArgs = [
|
|
238
252
|
"-hide_banner",
|
|
239
253
|
"-loglevel", "warning",
|
|
@@ -241,70 +255,15 @@ export function spawnMpegTsRemuxer(onError, streamId) {
|
|
|
241
255
|
"-f", "mp4",
|
|
242
256
|
"-i", "pipe:0",
|
|
243
257
|
"-c", "copy",
|
|
244
|
-
|
|
245
|
-
"-mpegts_pmt_start_pid", "0x0020",
|
|
246
|
-
"-mpegts_start_pid", "0x0031",
|
|
247
|
-
"-mpegts_service_type", "digital_tv",
|
|
248
|
-
"-pat_period", "0.1",
|
|
249
|
-
"-pcr_period", "40",
|
|
250
|
-
"-flush_packets", "1",
|
|
258
|
+
...MPEGTS_OUTPUT_FLAGS,
|
|
251
259
|
"pipe:1"
|
|
252
260
|
];
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
let shuttingDown = false;
|
|
259
|
-
// Log FFmpeg stderr output (warnings and errors).
|
|
260
|
-
ffmpeg.stderr.on("data", (data) => {
|
|
261
|
-
if (shuttingDown) {
|
|
262
|
-
return;
|
|
263
|
-
}
|
|
264
|
-
const message = data.toString().trim();
|
|
265
|
-
const noisePatterns = ["Press [q] to stop", "frame=", "size=", "time=", "bitrate=", "speed="];
|
|
266
|
-
if (noisePatterns.some((pattern) => message.includes(pattern))) {
|
|
267
|
-
return;
|
|
268
|
-
}
|
|
269
|
-
if (message.length > 0) {
|
|
270
|
-
LOG.debug("streaming:ffmpeg", "%sMPEG-TS remuxer: %s", logPrefix, message);
|
|
271
|
-
}
|
|
261
|
+
return spawnFFmpegProcess({
|
|
262
|
+
args: ffmpegArgs,
|
|
263
|
+
label: "MPEG-TS remuxer",
|
|
264
|
+
onError,
|
|
265
|
+
streamId
|
|
272
266
|
});
|
|
273
|
-
// Handle FFmpeg process exit.
|
|
274
|
-
ffmpeg.on("exit", (code, signal) => {
|
|
275
|
-
if (shuttingDown) {
|
|
276
|
-
return;
|
|
277
|
-
}
|
|
278
|
-
if (signal === "SIGTERM") {
|
|
279
|
-
return;
|
|
280
|
-
}
|
|
281
|
-
if ((code !== null) && (code !== 0)) {
|
|
282
|
-
onError(new Error("MPEG-TS remuxer exited with code " + String(code) + "."));
|
|
283
|
-
}
|
|
284
|
-
else if (signal) {
|
|
285
|
-
onError(new Error("MPEG-TS remuxer killed by signal " + signal + "."));
|
|
286
|
-
}
|
|
287
|
-
});
|
|
288
|
-
// Handle spawn errors (e.g., FFmpeg not found).
|
|
289
|
-
ffmpeg.on("error", (error) => {
|
|
290
|
-
if (shuttingDown) {
|
|
291
|
-
return;
|
|
292
|
-
}
|
|
293
|
-
onError(error);
|
|
294
|
-
});
|
|
295
|
-
// Kill function for graceful shutdown.
|
|
296
|
-
const kill = () => {
|
|
297
|
-
shuttingDown = true;
|
|
298
|
-
if (!ffmpeg.killed) {
|
|
299
|
-
ffmpeg.kill("SIGTERM");
|
|
300
|
-
}
|
|
301
|
-
};
|
|
302
|
-
return {
|
|
303
|
-
kill,
|
|
304
|
-
process: ffmpeg,
|
|
305
|
-
stdin: ffmpeg.stdin,
|
|
306
|
-
stdout: ffmpeg.stdout
|
|
307
|
-
};
|
|
308
267
|
}
|
|
309
268
|
/**
|
|
310
269
|
* Checks if FFmpeg is available on the system. This resolves the FFmpeg path and caches it for use by spawnFFmpeg().
|