prismcast 1.3.1 → 1.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser/channelSelection.d.ts +17 -3
- package/dist/browser/channelSelection.js +79 -33
- package/dist/browser/channelSelection.js.map +1 -1
- package/dist/browser/index.js +2 -4
- package/dist/browser/index.js.map +1 -1
- package/dist/browser/tuning/fox.d.ts +2 -19
- package/dist/browser/tuning/fox.js +2 -1
- package/dist/browser/tuning/fox.js.map +1 -1
- package/dist/browser/tuning/hbo.d.ts +2 -22
- package/dist/browser/tuning/hbo.js +86 -56
- package/dist/browser/tuning/hbo.js.map +1 -1
- package/dist/browser/tuning/hulu.d.ts +2 -30
- package/dist/browser/tuning/hulu.js +26 -2
- package/dist/browser/tuning/hulu.js.map +1 -1
- package/dist/browser/tuning/sling.d.ts +2 -22
- package/dist/browser/tuning/sling.js +230 -10
- package/dist/browser/tuning/sling.js.map +1 -1
- package/dist/browser/tuning/thumbnailRow.d.ts +2 -18
- package/dist/browser/tuning/thumbnailRow.js +2 -1
- package/dist/browser/tuning/thumbnailRow.js.map +1 -1
- package/dist/browser/tuning/tileClick.d.ts +2 -18
- package/dist/browser/tuning/tileClick.js +2 -1
- package/dist/browser/tuning/tileClick.js.map +1 -1
- package/dist/browser/tuning/youtubeTv.d.ts +2 -17
- package/dist/browser/tuning/youtubeTv.js +133 -93
- package/dist/browser/tuning/youtubeTv.js.map +1 -1
- package/dist/browser/video.d.ts +47 -12
- package/dist/browser/video.js +187 -56
- package/dist/browser/video.js.map +1 -1
- package/dist/channels/index.js +10 -2
- package/dist/channels/index.js.map +1 -1
- package/dist/config/profiles.js +2 -4
- package/dist/config/profiles.js.map +1 -1
- package/dist/config/providers.d.ts +1 -1
- package/dist/config/providers.js +5 -3
- package/dist/config/providers.js.map +1 -1
- package/dist/config/sites.js +2 -4
- package/dist/config/sites.js.map +1 -1
- package/dist/config/userConfig.js +6 -7
- package/dist/config/userConfig.js.map +1 -1
- package/dist/routes/config.js +16 -0
- package/dist/routes/config.js.map +1 -1
- package/dist/routes/logs.js +2 -2
- package/dist/routes/logs.js.map +1 -1
- package/dist/routes/root.js +84 -29
- package/dist/routes/root.js.map +1 -1
- package/dist/routes/streams.js +2 -2
- package/dist/routes/streams.js.map +1 -1
- package/dist/streaming/monitor.js +97 -85
- package/dist/streaming/monitor.js.map +1 -1
- package/dist/streaming/mpegts.js +4 -2
- package/dist/streaming/mpegts.js.map +1 -1
- package/dist/streaming/setup.js +19 -7
- package/dist/streaming/setup.js.map +1 -1
- package/dist/types/index.d.ts +33 -0
- package/dist/utils/ffmpeg.d.ts +6 -0
- package/dist/utils/ffmpeg.js +16 -0
- package/dist/utils/ffmpeg.js.map +1 -1
- package/package.json +1 -1
|
@@ -3,6 +3,11 @@ import { CONFIG } from "../../config/index.js";
|
|
|
3
3
|
import { logAvailableChannels } from "../channelSelection.js";
|
|
4
4
|
// Base URL for YouTube TV watch page navigation.
|
|
5
5
|
const YOUTUBE_TV_BASE_URL = "https://tv.youtube.com";
|
|
6
|
+
// Module-level cache for watch URLs discovered during channel selection. On the first tune to any YTTV channel, the strategy performs a bulk scrape of all ~256
|
|
7
|
+
// channels in the non-virtualized EPG grid, populating this cache with every channel's watch URL keyed by its lowercased guide name (e.g., "cnn", "nbc 5", "wgn").
|
|
8
|
+
// Subsequent tunes to any YTTV channel resolve via findWatchUrl() in resolveDirectUrl, skipping guide navigation entirely. Cleared on browser disconnect via
|
|
9
|
+
// clearYttvCache().
|
|
10
|
+
const yttvWatchUrlCache = new Map();
|
|
6
11
|
// Known alternate channel names for affiliates that vary by market. CW appears as "WGN" in some markets. PBS affiliates appear under local call letters (e.g.,
|
|
7
12
|
// WTTW, KQED) or branded names (e.g., "Cascade PBS", "Lakeshore PBS") rather than "PBS", so we list the major market call letters and branded names to cover most
|
|
8
13
|
// users. Each alternate is tried after the primary name fails both exact and prefix+digit matching. Users in smaller markets override via custom channel entries with
|
|
@@ -15,20 +20,85 @@ const CHANNEL_ALTERNATES = {
|
|
|
15
20
|
]
|
|
16
21
|
};
|
|
17
22
|
/**
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
23
|
+
* Looks up a watch URL in the cache using the same three-tier matching logic as the guide grid DOM query. The tiers are tried in order for each name in the
|
|
24
|
+
* candidate list (primary channelSelector first, then any CHANNEL_ALTERNATES):
|
|
25
|
+
*
|
|
26
|
+
* 1. Exact match: cache key equals the lowercased name (e.g., "cnn" matches "cnn").
|
|
27
|
+
* 2. Prefix+digit: cache key starts with the name followed by a space and a digit. Catches local affiliates displayed as "{Network} {Number}" (e.g., "nbc 5",
|
|
28
|
+
* "abc 7") while excluding unrelated channels (e.g., "nbc sports chicago").
|
|
29
|
+
* 3. Parenthetical suffix: cache key starts with the name followed by " (". Catches timezone/region variants like "magnolia network (pacific)".
|
|
30
|
+
*
|
|
31
|
+
* When a non-exact match succeeds, the result is also cached under the primary channelSelector key for O(1) lookup on subsequent calls. This function doubles as
|
|
32
|
+
* the resolveDirectUrl hook — after the first tune populates the cache via bulk scrape, every subsequent YTTV tune resolves here without loading the guide page.
|
|
33
|
+
* @param channelName - The channelSelector value (e.g., "CNN", "NBC", "CW").
|
|
34
|
+
* @returns The full watch URL or null if no match is found.
|
|
35
|
+
*/
|
|
36
|
+
function findWatchUrl(channelName) {
|
|
37
|
+
const lower = channelName.toLowerCase();
|
|
38
|
+
// Build the candidate list: primary name first, then any known alternates for markets where the affiliate uses a different name. The eslint disable is needed
|
|
39
|
+
// because TypeScript's Record indexing doesn't capture that the key may not exist at runtime.
|
|
40
|
+
const alternates = CHANNEL_ALTERNATES[lower];
|
|
41
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
42
|
+
const namesToTry = alternates ? [lower, ...alternates.map((a) => a.toLowerCase())] : [lower];
|
|
43
|
+
for (const name of namesToTry) {
|
|
44
|
+
// Tier 1: Exact match.
|
|
45
|
+
const exact = yttvWatchUrlCache.get(name);
|
|
46
|
+
if (exact) {
|
|
47
|
+
// Cache under the primary channelSelector key if we matched via an alternate name, so subsequent lookups are O(1).
|
|
48
|
+
if (name !== lower) {
|
|
49
|
+
yttvWatchUrlCache.set(lower, exact);
|
|
50
|
+
}
|
|
51
|
+
return exact;
|
|
52
|
+
}
|
|
53
|
+
// Tier 2: Prefix+digit match for local affiliates. Iterate all cache entries to find one whose key starts with "{name} " followed by a digit, matching the
|
|
54
|
+
// "{Network} {Number}" pattern (e.g., "nbc 5", "abc 7") while excluding unrelated channels (e.g., "nbc sports chicago").
|
|
55
|
+
for (const [key, url] of yttvWatchUrlCache) {
|
|
56
|
+
if (key.startsWith(name + " ") && (key.length > name.length + 1) && (key.charCodeAt(name.length + 1) >= 48) && (key.charCodeAt(name.length + 1) <= 57)) {
|
|
57
|
+
yttvWatchUrlCache.set(lower, url);
|
|
58
|
+
return url;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// Tier 3: Parenthetical suffix match for timezone/region variants. Find a cache entry whose key starts with "{name} (" to catch channels like
|
|
62
|
+
// "magnolia network (pacific)" or "the filipino channel (pacific)".
|
|
63
|
+
for (const [key, url] of yttvWatchUrlCache) {
|
|
64
|
+
if (key.startsWith(name + " (")) {
|
|
65
|
+
yttvWatchUrlCache.set(lower, url);
|
|
66
|
+
return url;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Invalidates the cached YouTube TV watch URL for the given channel selector. Called when a cached URL fails to produce a working stream. Deletes the
|
|
74
|
+
* channelSelector key — the bulk-scraped guide-name entries are left intact and will be refreshed on the next strategy run when the guide page is reloaded.
|
|
75
|
+
* @param channelSelector - The channel selector string to invalidate.
|
|
76
|
+
*/
|
|
77
|
+
function invalidateYttvDirectUrl(channelSelector) {
|
|
78
|
+
yttvWatchUrlCache.delete(channelSelector.toLowerCase());
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Clears all cached YouTube TV watch URLs. Called by clearChannelSelectionCaches() in the coordinator when the browser restarts.
|
|
82
|
+
*/
|
|
83
|
+
function clearYttvCache() {
|
|
84
|
+
yttvWatchUrlCache.clear();
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* YouTube TV grid strategy: scrapes all watch URLs from the non-virtualized EPG grid at tv.youtube.com/live in a single pass, populating the module-level cache so
|
|
88
|
+
* that subsequent tunes to any YTTV channel resolve via findWatchUrl() without loading the guide page. All ~256 channel rows are present in the DOM simultaneously,
|
|
89
|
+
* so one querySelectorAll captures every channel's name and watch URL.
|
|
21
90
|
*
|
|
22
91
|
* The selection process:
|
|
23
92
|
* 1. Wait for ytu-epg-row elements to confirm the guide grid has loaded.
|
|
24
|
-
* 2.
|
|
25
|
-
* 3.
|
|
26
|
-
* 4.
|
|
93
|
+
* 2. Bulk scrape all channels: extract aria-label names and watch/ hrefs from every thumbnail endpoint.
|
|
94
|
+
* 3. Populate the watch URL cache with all discovered channels.
|
|
95
|
+
* 4. Look up the target channel using tiered matching (exact, prefix+digit, parenthetical, alternates) against the cache.
|
|
96
|
+
* 5. Navigate to the matched watch URL via page.goto().
|
|
27
97
|
* @param page - The Puppeteer page object.
|
|
28
98
|
* @param profile - The resolved site profile with a non-null channelSelector (channel name, e.g., "CNN", "ESPN", "NBC").
|
|
29
99
|
* @returns Result object with success status and optional failure reason.
|
|
30
100
|
*/
|
|
31
|
-
|
|
101
|
+
async function youtubeGridStrategy(page, profile) {
|
|
32
102
|
const channelName = profile.channelSelector;
|
|
33
103
|
// Wait for the EPG grid to render. All ~256 rows load simultaneously (no virtualization), so once any row exists, all channels are queryable.
|
|
34
104
|
try {
|
|
@@ -37,99 +107,52 @@ export async function youtubeGridStrategy(page, profile) {
|
|
|
37
107
|
catch {
|
|
38
108
|
return { reason: "YouTube TV guide grid did not load.", success: false };
|
|
39
109
|
}
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
// 2. Prefix+digit: aria-label starts with "watch {Name} " followed by a digit. This catches local affiliates displayed as "{Network} {Number}" (e.g., "NBC 5",
|
|
49
|
-
// "ABC 7", "FOX 32") while excluding unrelated channels (e.g., "NBC Sports Chicago").
|
|
50
|
-
// 3. Parenthetical suffix: aria-label starts with "watch {Name} (" to match timezone/region variants like "Magnolia Network (Pacific)" or
|
|
51
|
-
// "The Filipino Channel (Pacific)".
|
|
52
|
-
const watchPath = await evaluateWithAbort(page, (names) => {
|
|
53
|
-
// Helper to extract and validate a watch URL from an anchor element. Returns the href if it points to a streamable watch page, null otherwise.
|
|
54
|
-
const extractWatchHref = (anchor) => {
|
|
55
|
-
if (!anchor) {
|
|
56
|
-
return null;
|
|
57
|
-
}
|
|
58
|
-
const href = anchor.getAttribute("href");
|
|
59
|
-
// Validate the href points to a streamable watch page. Channels with "live" or "browse/" hrefs are premium add-ons or info pages that cannot be streamed.
|
|
60
|
-
if (!href?.startsWith("watch/")) {
|
|
61
|
-
return null;
|
|
62
|
-
}
|
|
63
|
-
return href;
|
|
64
|
-
};
|
|
65
|
-
// Try each name in order. The primary channel name is tried first, followed by any known alternates.
|
|
66
|
-
for (const name of names) {
|
|
67
|
-
// Tier 1: Exact match. The CSS "i" flag enables case-insensitive matching to handle variations in capitalization between the channel selector and the guide.
|
|
68
|
-
const exactSelector = "ytu-endpoint.tenx-thumb[aria-label=\"watch " + name + "\" i] a";
|
|
69
|
-
const exactResult = extractWatchHref(document.querySelector(exactSelector));
|
|
70
|
-
if (exactResult) {
|
|
71
|
-
return exactResult;
|
|
110
|
+
// Bulk scrape all channels from the grid in a single evaluate round-trip. For each thumbnail endpoint with a valid watch/ href, extract the channel name (from
|
|
111
|
+
// the aria-label, stripping the "watch " prefix) and the watch path. Channels with "live" or "browse/" hrefs are premium add-ons or info pages and are excluded.
|
|
112
|
+
const allChannels = await evaluateWithAbort(page, () => {
|
|
113
|
+
const results = [];
|
|
114
|
+
for (const thumb of Array.from(document.querySelectorAll("ytu-endpoint.tenx-thumb[aria-label]"))) {
|
|
115
|
+
const label = thumb.getAttribute("aria-label") ?? "";
|
|
116
|
+
if (!label.startsWith("watch ")) {
|
|
117
|
+
continue;
|
|
72
118
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
for (const candidate of Array.from(candidates)) {
|
|
79
|
-
const parent = candidate.closest("ytu-endpoint.tenx-thumb");
|
|
80
|
-
const label = parent?.getAttribute("aria-label") ?? "";
|
|
81
|
-
const suffix = label.slice(prefix.length);
|
|
82
|
-
// Accept only if the remainder starts with a digit — this is the local affiliate channel number.
|
|
83
|
-
if ((suffix.length > 0) && (suffix.charCodeAt(0) >= 48) && (suffix.charCodeAt(0) <= 57)) {
|
|
84
|
-
return extractWatchHref(candidate);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
// Tier 3: Parenthetical suffix match for timezone/region variants. Channels like "Magnolia Network (Pacific)" or "The Filipino Channel (Pacific)" have the
|
|
88
|
-
// base name followed by a space and a parenthetical. The CSS selector matches aria-labels starting with "watch {Name} (" to catch these variants.
|
|
89
|
-
const parenSelector = "ytu-endpoint.tenx-thumb[aria-label^=\"watch " + name + " (\" i] a";
|
|
90
|
-
const parenResult = extractWatchHref(document.querySelector(parenSelector));
|
|
91
|
-
if (parenResult) {
|
|
92
|
-
return parenResult;
|
|
119
|
+
const anchor = thumb.querySelector("a");
|
|
120
|
+
const href = anchor?.getAttribute("href") ?? "";
|
|
121
|
+
// Only include channels with streamable watch URLs. Channels with "live" or "browse/" hrefs are premium add-ons or info pages.
|
|
122
|
+
if (href.startsWith("watch/")) {
|
|
123
|
+
results.push({ name: label.slice(6), watchPath: href });
|
|
93
124
|
}
|
|
94
125
|
}
|
|
95
|
-
return
|
|
96
|
-
}, [
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
for (const alt of alts) {
|
|
114
|
-
additionalKnownNames.push(alt);
|
|
115
|
-
}
|
|
126
|
+
return results;
|
|
127
|
+
}, []);
|
|
128
|
+
// Populate the watch URL cache with all discovered channels. This makes every subsequent YTTV tune a cache hit via resolveDirectUrl, skipping guide navigation
|
|
129
|
+
// entirely. Cache keys are lowercased guide names (e.g., "cnn", "nbc 5", "wgn"). The tiered matching in findWatchUrl() handles channelSelector-to-guide-name
|
|
130
|
+
// resolution (e.g., "NBC" finds "nbc 5" via prefix+digit matching, "CW" finds "wgn" via CHANNEL_ALTERNATES).
|
|
131
|
+
for (const ch of allChannels) {
|
|
132
|
+
yttvWatchUrlCache.set(ch.name.toLowerCase(), YOUTUBE_TV_BASE_URL + "/" + ch.watchPath);
|
|
133
|
+
}
|
|
134
|
+
LOG.debug("tuning:yttv", "Cached %s YouTube TV watch URLs.", yttvWatchUrlCache.size);
|
|
135
|
+
// Look up the target channel using tiered matching against the populated cache.
|
|
136
|
+
const watchUrl = findWatchUrl(channelName);
|
|
137
|
+
if (!watchUrl) {
|
|
138
|
+
// Channel not found. Log available channels as a diagnostic to help users identify their market's channel names and create user-defined channels with the
|
|
139
|
+
// correct channelSelector value. Build additional known names from CHANNEL_ALTERNATES values so they are also filtered out of the diagnostic list.
|
|
140
|
+
const additionalKnownNames = [];
|
|
141
|
+
for (const alts of Object.values(CHANNEL_ALTERNATES)) {
|
|
142
|
+
for (const alt of alts) {
|
|
143
|
+
additionalKnownNames.push(alt);
|
|
116
144
|
}
|
|
117
|
-
logAvailableChannels({
|
|
118
|
-
additionalKnownNames,
|
|
119
|
-
availableChannels,
|
|
120
|
-
channelName,
|
|
121
|
-
guideUrl: "https://tv.youtube.com/live",
|
|
122
|
-
presetSuffix: "-yttv",
|
|
123
|
-
providerName: "YouTube TV"
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
catch {
|
|
127
|
-
// Diagnostic dump is best-effort. Don't let it mask the real channel selection failure.
|
|
128
145
|
}
|
|
146
|
+
logAvailableChannels({
|
|
147
|
+
additionalKnownNames,
|
|
148
|
+
availableChannels: allChannels.map((ch) => ch.name).sort(),
|
|
149
|
+
channelName,
|
|
150
|
+
guideUrl: "https://tv.youtube.com/live",
|
|
151
|
+
presetSuffix: "-yttv",
|
|
152
|
+
providerName: "YouTube TV"
|
|
153
|
+
});
|
|
129
154
|
return { reason: "Channel \"" + channelName + "\" not found in YouTube TV guide.", success: false };
|
|
130
155
|
}
|
|
131
|
-
// Navigate directly to the watch URL. This auto-starts playback without any click interaction needed.
|
|
132
|
-
const watchUrl = YOUTUBE_TV_BASE_URL + "/" + watchPath;
|
|
133
156
|
LOG.debug("tuning:yttv", "Navigating to YouTube TV watch URL for %s.", channelName);
|
|
134
157
|
try {
|
|
135
158
|
await page.goto(watchUrl, { timeout: CONFIG.streaming.navigationTimeout, waitUntil: "load" });
|
|
@@ -139,4 +162,21 @@ export async function youtubeGridStrategy(page, profile) {
|
|
|
139
162
|
}
|
|
140
163
|
return { success: true };
|
|
141
164
|
}
|
|
165
|
+
/**
|
|
166
|
+
* Async wrapper around findWatchUrl for the ChannelStrategyEntry.resolveDirectUrl contract. The page parameter is unused because YTTV watch URLs are resolved
|
|
167
|
+
* purely from the in-memory cache populated during the first guide page scrape.
|
|
168
|
+
* @param channelSelector - The channel selector string (e.g., "CNN", "ESPN", "NBC").
|
|
169
|
+
* @param _page - Unused. Present to satisfy the async resolveDirectUrl signature.
|
|
170
|
+
* @returns The cached watch URL or null.
|
|
171
|
+
*/
|
|
172
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/require-await
|
|
173
|
+
async function resolveYttvDirectUrl(channelSelector, _page) {
|
|
174
|
+
return findWatchUrl(channelSelector);
|
|
175
|
+
}
|
|
176
|
+
export const yttvStrategy = {
|
|
177
|
+
clearCache: clearYttvCache,
|
|
178
|
+
execute: youtubeGridStrategy,
|
|
179
|
+
invalidateDirectUrl: invalidateYttvDirectUrl,
|
|
180
|
+
resolveDirectUrl: resolveYttvDirectUrl
|
|
181
|
+
};
|
|
142
182
|
//# sourceMappingURL=youtubeTv.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"youtubeTv.js","sourceRoot":"","sources":["../../../src/browser/tuning/youtubeTv.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,GAAG,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC3E,OAAO,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAE/C,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAE9D,iDAAiD;AACjD,MAAM,mBAAmB,GAAG,wBAAwB,CAAC;AAErD,+JAA+J;AAC/J,kKAAkK;AAClK,sKAAsK;AACtK,mDAAmD;AACnD,MAAM,kBAAkB,GAA6B;IAEnD,IAAI,EAAE,CAAC,KAAK,CAAC;IACb,KAAK,EAAE;QACL,aAAa,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,EAAE,KAAK,EAAE,QAAQ;QACtJ,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;KAClH;CACF,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,
|
|
1
|
+
{"version":3,"file":"youtubeTv.js","sourceRoot":"","sources":["../../../src/browser/tuning/youtubeTv.ts"],"names":[],"mappings":"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"}
|
package/dist/browser/video.d.ts
CHANGED
|
@@ -144,8 +144,9 @@ export declare function waitForVideoReady(context: Frame | Page, profile: Resolv
|
|
|
144
144
|
* - cursor: none - Hides the mouse cursor for cleaner capture
|
|
145
145
|
* @param context - The frame or page containing the video element.
|
|
146
146
|
* @param selectorType - The video selector type for finding the element.
|
|
147
|
+
* @param important - When true, applies styles with !important priority to override site JavaScript that actively fights style changes.
|
|
147
148
|
*/
|
|
148
|
-
export declare function applyVideoStyles(context: Frame | Page, selectorType: VideoSelectorType): Promise<void>;
|
|
149
|
+
export declare function applyVideoStyles(context: Frame | Page, selectorType: VideoSelectorType, important?: boolean): Promise<void>;
|
|
149
150
|
/**
|
|
150
151
|
* Locks the volume properties on the video element to prevent the site's JavaScript from muting our stream. Some sites (like France24) aggressively mute videos
|
|
151
152
|
* or lower volume in response to various events. They may reset volume on play, on focus, on visibility change, or on a timer.
|
|
@@ -174,22 +175,52 @@ export declare function lockVolumeProperties(context: Frame | Page, selectorType
|
|
|
174
175
|
* @param selectorType - The video selector type for finding the element.
|
|
175
176
|
*/
|
|
176
177
|
export declare function triggerFullscreen(page: Page, context: Frame | Page, profile: ResolvedSiteProfile, selectorType: VideoSelectorType): Promise<void>;
|
|
178
|
+
/**
|
|
179
|
+
* Verifies that the video element is filling the viewport, indicating that fullscreen styling was successfully applied. This function checks the video element's
|
|
180
|
+
* bounding rectangle against the viewport dimensions to determine if the video appears fullscreen.
|
|
181
|
+
*
|
|
182
|
+
* The verification allows for some tolerance because:
|
|
183
|
+
* - The video may have letterboxing/pillarboxing due to aspect ratio differences
|
|
184
|
+
* - Some browsers report slightly smaller dimensions due to scrollbars or UI chrome
|
|
185
|
+
* - CSS rounding may cause small discrepancies
|
|
186
|
+
*
|
|
187
|
+
* We require the video to fill at least 85% of the viewport in at least one dimension (the constraining dimension for aspect ratio) and at least 50% in the
|
|
188
|
+
* other dimension to catch obviously broken cases.
|
|
189
|
+
* @param context - The frame or page containing the video element.
|
|
190
|
+
* @param selectorType - The video selector type for finding the element.
|
|
191
|
+
* @returns True if the video appears to be fullscreen, false if it does not, or null if the check could not be performed (e.g. context destroyed).
|
|
192
|
+
*/
|
|
193
|
+
export declare function verifyFullscreen(context: Frame | Page, selectorType: VideoSelectorType): Promise<Nullable<boolean>>;
|
|
177
194
|
/**
|
|
178
195
|
* Ensures the video is displayed fullscreen with verification and retry logic. This function orchestrates the fullscreen process:
|
|
179
196
|
*
|
|
180
197
|
* 1. Initial attempt: Apply CSS styles and trigger fullscreen API
|
|
181
|
-
* 2. Verify: Check
|
|
182
|
-
* 3. Simple retry: If verification fails,
|
|
183
|
-
* 4. Escalate: If simple retries fail, apply aggressive fullscreen techniques
|
|
198
|
+
* 2. Verify: Check video dimensions and, for fullscreenApi profiles, confirm document.fullscreenElement is set
|
|
199
|
+
* 3. Simple retry: If verification fails, click the video for user activation and retry (the Fullscreen API requires a recent user gesture)
|
|
200
|
+
* 4. Escalate: If simple retries fail, apply aggressive fullscreen techniques with a final Fullscreen API re-trigger
|
|
184
201
|
*
|
|
185
|
-
* The retry approach handles
|
|
186
|
-
*
|
|
202
|
+
* The retry approach handles both timing issues (page still initializing) and user activation issues (requestFullscreen requires a recent user gesture).
|
|
203
|
+
* On retry, clicking the video provides fresh activation so the subsequent requestFullscreen() call can succeed. Escalation to aggressive techniques is a
|
|
204
|
+
* last resort that may break site functionality but ensures video fills the viewport.
|
|
187
205
|
* @param page - The Puppeteer page object for keyboard input.
|
|
188
206
|
* @param context - The frame or page containing the video element.
|
|
189
207
|
* @param profile - The site profile indicating fullscreen method.
|
|
190
208
|
* @param selectorType - The video selector type for finding the element.
|
|
209
|
+
* @param skipNativeFullscreen - When true, skips Fullscreen API-specific actions (click-for-activation, native fullscreen verification, API retries). CSS styling
|
|
210
|
+
* and keyboard shortcuts still run. Used during monitor recovery where user activation is unavailable and click-for-activation can interfere with playback.
|
|
211
|
+
*/
|
|
212
|
+
export declare function ensureFullscreen(page: Page, context: Frame | Page, profile: ResolvedSiteProfile, selectorType: VideoSelectorType, skipNativeFullscreen?: boolean): Promise<void>;
|
|
213
|
+
/**
|
|
214
|
+
* Options for ensurePlayback() that control recovery behavior.
|
|
191
215
|
*/
|
|
192
|
-
|
|
216
|
+
interface EnsurePlaybackOptions {
|
|
217
|
+
/** The escalation level (1-2). Level 1 is basic play/unmute recovery. Level 2 adds video source reload. Defaults to 1. */
|
|
218
|
+
recoveryLevel?: number;
|
|
219
|
+
/** When true, skips native Fullscreen API actions (click-for-activation, API verification, API retries) during the fullscreen step. CSS styling and keyboard
|
|
220
|
+
* shortcuts still run. Used by the monitor during recovery where user activation is unavailable and click-for-activation can toggle playback state. The
|
|
221
|
+
* monitor's own lightweight fullscreen maintenance loop handles ongoing CSS reapplication independently. Defaults to false. */
|
|
222
|
+
skipNativeFullscreen?: boolean;
|
|
223
|
+
}
|
|
193
224
|
/**
|
|
194
225
|
* Ensures the video is playing with proper audio settings. This is the core playback function that handles both initial setup and recovery from stalls. It is
|
|
195
226
|
* designed to be idempotent - safe to call multiple times without adverse effects.
|
|
@@ -199,8 +230,8 @@ export declare function ensureFullscreen(page: Page, context: Frame | Page, prof
|
|
|
199
230
|
* LEVEL 1 - Basic recovery (default):
|
|
200
231
|
* - Set muted=false and volume=1
|
|
201
232
|
* - Call play() if video is paused
|
|
202
|
-
* - Ensure fullscreen with
|
|
203
|
-
*
|
|
233
|
+
* - Ensure fullscreen with CSS styling, keyboard shortcuts, and dimension verification. When skipNativeFullscreen is set, Fullscreen API-specific actions are
|
|
234
|
+
* skipped because user activation is unavailable and click-for-activation can interfere with playback recovery.
|
|
204
235
|
* - Lock volume properties if profile requires it
|
|
205
236
|
*
|
|
206
237
|
* LEVEL 2 - Reload video source:
|
|
@@ -214,9 +245,9 @@ export declare function ensureFullscreen(page: Page, context: Frame | Page, prof
|
|
|
214
245
|
* @param page - The Puppeteer page object.
|
|
215
246
|
* @param context - The frame or page containing the video element.
|
|
216
247
|
* @param profile - The site profile containing all behavior flags.
|
|
217
|
-
* @param
|
|
248
|
+
* @param options - Optional recovery configuration. Omit for initial tune (full fullscreen behavior, level 1).
|
|
218
249
|
*/
|
|
219
|
-
export declare function ensurePlayback(page: Page, context: Frame | Page, profile: ResolvedSiteProfile,
|
|
250
|
+
export declare function ensurePlayback(page: Page, context: Frame | Page, profile: ResolvedSiteProfile, options?: EnsurePlaybackOptions): Promise<void>;
|
|
220
251
|
/**
|
|
221
252
|
* Performs all post-navigation channel initialization: selects the channel, finds the video context, clicks to play if needed, waits for video readiness, and
|
|
222
253
|
* ensures playback with fullscreen styling. This function is separated from navigateToPage() so that retryOperation() in setup.ts can wrap only navigation with a
|
|
@@ -228,14 +259,17 @@ export declare function ensurePlayback(page: Page, context: Frame | Page, profil
|
|
|
228
259
|
*
|
|
229
260
|
* @param page - The Puppeteer page object.
|
|
230
261
|
* @param profile - The site profile containing all behavior flags.
|
|
262
|
+
* @param skipChannelSelection - When true, skip the channel selection phase entirely. Used when navigating directly to a cached watch URL that already targets
|
|
263
|
+
* the correct channel — only video detection, playback, and fullscreen setup are needed.
|
|
231
264
|
* @returns The video context (frame or page) for subsequent monitoring.
|
|
232
265
|
*/
|
|
233
|
-
export declare function initializePlayback(page: Page, profile: ResolvedSiteProfile): Promise<TuneResult>;
|
|
266
|
+
export declare function initializePlayback(page: Page, profile: ResolvedSiteProfile, skipChannelSelection?: boolean): Promise<TuneResult>;
|
|
234
267
|
/**
|
|
235
268
|
* Tunes to a channel by navigating to the URL and initializing video playback. This is the single source of truth for channel initialization, used by both initial
|
|
236
269
|
* stream setup and recovery. Having one authoritative function ensures consistent behavior and prevents code divergence between setup and recovery paths.
|
|
237
270
|
*
|
|
238
271
|
* The tuning process:
|
|
272
|
+
* 0. Check cache: If a direct watch URL is cached, navigate to it and skip channel selection. On failure, invalidate and fall through.
|
|
239
273
|
* 1. Navigate: Load the target URL using site-appropriate wait conditions
|
|
240
274
|
* 2. Select channel: For multi-channel players, click the desired channel in the UI
|
|
241
275
|
* 3. Find video: Locate the video element (which may be in an iframe)
|
|
@@ -252,3 +286,4 @@ export declare function initializePlayback(page: Page, profile: ResolvedSiteProf
|
|
|
252
286
|
* @returns The video context (frame or page) for subsequent monitoring.
|
|
253
287
|
*/
|
|
254
288
|
export declare function tuneToChannel(page: Page, url: string, profile: ResolvedSiteProfile): Promise<TuneResult>;
|
|
289
|
+
export {};
|