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.
Files changed (59) hide show
  1. package/dist/browser/channelSelection.d.ts +17 -3
  2. package/dist/browser/channelSelection.js +79 -33
  3. package/dist/browser/channelSelection.js.map +1 -1
  4. package/dist/browser/index.js +2 -4
  5. package/dist/browser/index.js.map +1 -1
  6. package/dist/browser/tuning/fox.d.ts +2 -19
  7. package/dist/browser/tuning/fox.js +2 -1
  8. package/dist/browser/tuning/fox.js.map +1 -1
  9. package/dist/browser/tuning/hbo.d.ts +2 -22
  10. package/dist/browser/tuning/hbo.js +86 -56
  11. package/dist/browser/tuning/hbo.js.map +1 -1
  12. package/dist/browser/tuning/hulu.d.ts +2 -30
  13. package/dist/browser/tuning/hulu.js +26 -2
  14. package/dist/browser/tuning/hulu.js.map +1 -1
  15. package/dist/browser/tuning/sling.d.ts +2 -22
  16. package/dist/browser/tuning/sling.js +230 -10
  17. package/dist/browser/tuning/sling.js.map +1 -1
  18. package/dist/browser/tuning/thumbnailRow.d.ts +2 -18
  19. package/dist/browser/tuning/thumbnailRow.js +2 -1
  20. package/dist/browser/tuning/thumbnailRow.js.map +1 -1
  21. package/dist/browser/tuning/tileClick.d.ts +2 -18
  22. package/dist/browser/tuning/tileClick.js +2 -1
  23. package/dist/browser/tuning/tileClick.js.map +1 -1
  24. package/dist/browser/tuning/youtubeTv.d.ts +2 -17
  25. package/dist/browser/tuning/youtubeTv.js +133 -93
  26. package/dist/browser/tuning/youtubeTv.js.map +1 -1
  27. package/dist/browser/video.d.ts +47 -12
  28. package/dist/browser/video.js +187 -56
  29. package/dist/browser/video.js.map +1 -1
  30. package/dist/channels/index.js +10 -2
  31. package/dist/channels/index.js.map +1 -1
  32. package/dist/config/profiles.js +2 -4
  33. package/dist/config/profiles.js.map +1 -1
  34. package/dist/config/providers.d.ts +1 -1
  35. package/dist/config/providers.js +5 -3
  36. package/dist/config/providers.js.map +1 -1
  37. package/dist/config/sites.js +2 -4
  38. package/dist/config/sites.js.map +1 -1
  39. package/dist/config/userConfig.js +6 -7
  40. package/dist/config/userConfig.js.map +1 -1
  41. package/dist/routes/config.js +16 -0
  42. package/dist/routes/config.js.map +1 -1
  43. package/dist/routes/logs.js +2 -2
  44. package/dist/routes/logs.js.map +1 -1
  45. package/dist/routes/root.js +84 -29
  46. package/dist/routes/root.js.map +1 -1
  47. package/dist/routes/streams.js +2 -2
  48. package/dist/routes/streams.js.map +1 -1
  49. package/dist/streaming/monitor.js +97 -85
  50. package/dist/streaming/monitor.js.map +1 -1
  51. package/dist/streaming/mpegts.js +4 -2
  52. package/dist/streaming/mpegts.js.map +1 -1
  53. package/dist/streaming/setup.js +19 -7
  54. package/dist/streaming/setup.js.map +1 -1
  55. package/dist/types/index.d.ts +33 -0
  56. package/dist/utils/ffmpeg.d.ts +6 -0
  57. package/dist/utils/ffmpeg.js +16 -0
  58. package/dist/utils/ffmpeg.js.map +1 -1
  59. 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
- * YouTube TV grid strategy: finds a channel in the non-virtualized EPG grid at tv.youtube.com/live by querying the aria-label attribute on thumbnail endpoint
19
- * elements. All ~256 channel rows are present in the DOM simultaneously, so a single querySelector locates the target channel. The strategy extracts the watch
20
- * URL from the matching anchor element and navigates directly — no scrolling, clicking, or timing workarounds needed.
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. Find the target channel using a case-insensitive aria-label CSS selector.
25
- * 3. Extract the href attribute and validate it starts with "watch/" (not "live" or "browse/").
26
- * 4. Navigate to the full watch URL via page.goto().
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
- export async function youtubeGridStrategy(page, profile) {
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
- // Build the list of names to try: the primary name first, then any known alternates for markets where the affiliate uses a different name. The eslint disable is
41
- // needed because TypeScript's Record indexing doesn't capture that the key may not exist at runtime.
42
- const alternates = CHANNEL_ALTERNATES[channelName.toLowerCase()];
43
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
44
- const namesToTry = alternates ? [channelName, ...alternates] : [channelName];
45
- // Find the watch URL for the target channel. For each name in the list, we try three matching tiers:
46
- //
47
- // 1. Exact match: aria-label="watch {Name}" (case-insensitive).
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
- // Tier 2: Prefix + digit match for local affiliates. Find all thumbnails whose aria-label starts with "watch {Name} " and filter to those where the next
74
- // character is a digit, matching the "{Network} {Number}" pattern (e.g., "NBC 5", "ABC 7") while excluding unrelated channels (e.g., "NBC Sports Chicago").
75
- const prefixSelector = "ytu-endpoint.tenx-thumb[aria-label^=\"watch " + name + " \" i] a";
76
- const candidates = document.querySelectorAll(prefixSelector);
77
- const prefix = "watch " + name + " ";
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 null;
96
- }, [namesToTry]);
97
- if (!watchPath) {
98
- // Channel not found. Query all available channel names from the guide grid and log them as a diagnostic to help users identify their market's channel names
99
- // and create user-defined channels with the correct channelSelector value.
100
- try {
101
- const availableChannels = await evaluateWithAbort(page, () => {
102
- return Array.from(document.querySelectorAll("ytu-endpoint.tenx-thumb[aria-label]"))
103
- .map((el) => {
104
- const label = el.getAttribute("aria-label") ?? "";
105
- return label.startsWith("watch ") ? label.slice(6) : "";
106
- })
107
- .filter((name) => name.length > 0)
108
- .sort();
109
- }, []);
110
- // Build additional known names from CHANNEL_ALTERNATES values so they are also filtered out of the diagnostic list.
111
- const additionalKnownNames = [];
112
- for (const alts of Object.values(CHANNEL_ALTERNATES)) {
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":"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"}
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,gKAAgK;AAChK,mKAAmK;AACnK,6JAA6J;AAC7J,oBAAoB;AACpB,MAAM,iBAAiB,GAAG,IAAI,GAAG,EAAkB,CAAC;AAEpD,+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,SAAS,YAAY,CAAC,WAAmB;IAEvC,MAAM,KAAK,GAAG,WAAW,CAAC,WAAW,EAAE,CAAC;IAExC,8JAA8J;IAC9J,8FAA8F;IAC9F,MAAM,UAAU,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAC;IAE7C,uEAAuE;IACvE,MAAM,UAAU,GAAG,UAAU,CAAC,CAAC,CAAC,CAAE,KAAK,EAAE,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAE,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;IAE/F,KAAI,MAAM,IAAI,IAAI,UAAU,EAAE,CAAC;QAE7B,uBAAuB;QACvB,MAAM,KAAK,GAAG,iBAAiB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAE1C,IAAG,KAAK,EAAE,CAAC;YAET,mHAAmH;YACnH,IAAG,IAAI,KAAK,KAAK,EAAE,CAAC;gBAElB,iBAAiB,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;YACtC,CAAC;YAED,OAAO,KAAK,CAAC;QACf,CAAC;QAED,2JAA2J;QAC3J,yHAAyH;QACzH,KAAI,MAAM,CAAE,GAAG,EAAE,GAAG,CAAE,IAAI,iBAAiB,EAAE,CAAC;YAE5C,IAAG,GAAG,CAAC,UAAU,CAAC,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC;gBAEtJ,iBAAiB,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;gBAElC,OAAO,GAAG,CAAC;YACb,CAAC;QACH,CAAC;QAED,8IAA8I;QAC9I,oEAAoE;QACpE,KAAI,MAAM,CAAE,GAAG,EAAE,GAAG,CAAE,IAAI,iBAAiB,EAAE,CAAC;YAE5C,IAAG,GAAG,CAAC,UAAU,CAAC,IAAI,GAAG,IAAI,CAAC,EAAE,CAAC;gBAE/B,iBAAiB,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;gBAElC,OAAO,GAAG,CAAC;YACb,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;GAIG;AACH,SAAS,uBAAuB,CAAC,eAAuB;IAEtD,iBAAiB,CAAC,MAAM,CAAC,eAAe,CAAC,WAAW,EAAE,CAAC,CAAC;AAC1D,CAAC;AAED;;GAEG;AACH,SAAS,cAAc;IAErB,iBAAiB,CAAC,KAAK,EAAE,CAAC;AAC5B,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,KAAK,UAAU,mBAAmB,CAAC,IAAU,EAAE,OAAgC;IAE7E,MAAM,WAAW,GAAG,OAAO,CAAC,eAAe,CAAC;IAE5C,8IAA8I;IAC9I,IAAI,CAAC;QAEH,MAAM,IAAI,CAAC,eAAe,CAAC,aAAa,EAAE,EAAE,OAAO,EAAE,MAAM,CAAC,SAAS,CAAC,YAAY,EAAE,CAAC,CAAC;IACxF,CAAC;IAAC,MAAM,CAAC;QAEP,OAAO,EAAE,MAAM,EAAE,qCAAqC,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IAC3E,CAAC;IAED,+JAA+J;IAC/J,iKAAiK;IACjK,MAAM,WAAW,GAAG,MAAM,iBAAiB,CAAC,IAAI,EAAE,GAA0C,EAAE;QAE5F,MAAM,OAAO,GAA0C,EAAE,CAAC;QAE1D,KAAI,MAAM,KAAK,IAAI,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,gBAAgB,CAAC,qCAAqC,CAAC,CAAC,EAAE,CAAC;YAEhG,MAAM,KAAK,GAAG,KAAK,CAAC,YAAY,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC;YAErD,IAAG,CAAC,KAAK,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;gBAE/B,SAAS;YACX,CAAC;YAED,MAAM,MAAM,GAAG,KAAK,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;YACxC,MAAM,IAAI,GAAG,MAAM,EAAE,YAAY,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;YAEhD,+HAA+H;YAC/H,IAAG,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;gBAE7B,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YAC1D,CAAC;QACH,CAAC;QAED,OAAO,OAAO,CAAC;IACjB,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,+JAA+J;IAC/J,6JAA6J;IAC7J,6GAA6G;IAC7G,KAAI,MAAM,EAAE,IAAI,WAAW,EAAE,CAAC;QAE5B,iBAAiB,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,mBAAmB,GAAG,GAAG,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IACzF,CAAC;IAED,GAAG,CAAC,KAAK,CAAC,aAAa,EAAE,kCAAkC,EAAE,iBAAiB,CAAC,IAAI,CAAC,CAAC;IAErF,gFAAgF;IAChF,MAAM,QAAQ,GAAG,YAAY,CAAC,WAAW,CAAC,CAAC;IAE3C,IAAG,CAAC,QAAQ,EAAE,CAAC;QAEb,0JAA0J;QAC1J,mJAAmJ;QACnJ,MAAM,oBAAoB,GAAa,EAAE,CAAC;QAE1C,KAAI,MAAM,IAAI,IAAI,MAAM,CAAC,MAAM,CAAC,kBAAkB,CAAC,EAAE,CAAC;YAEpD,KAAI,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;gBAEtB,oBAAoB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACjC,CAAC;QACH,CAAC;QAED,oBAAoB,CAAC;YAEnB,oBAAoB;YACpB,iBAAiB,EAAE,WAAW,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE;YAC1D,WAAW;YACX,QAAQ,EAAE,6BAA6B;YACvC,YAAY,EAAE,OAAO;YACrB,YAAY,EAAE,YAAY;SAC3B,CAAC,CAAC;QAEH,OAAO,EAAE,MAAM,EAAE,YAAY,GAAG,WAAW,GAAG,mCAAmC,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IACtG,CAAC;IAED,GAAG,CAAC,KAAK,CAAC,aAAa,EAAE,4CAA4C,EAAE,WAAW,CAAC,CAAC;IAEpF,IAAI,CAAC;QAEH,MAAM,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,OAAO,EAAE,MAAM,CAAC,SAAS,CAAC,iBAAiB,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC,CAAC;IAChG,CAAC;IAAC,OAAM,KAAK,EAAE,CAAC;QAEd,OAAO,EAAE,MAAM,EAAE,+CAA+C,GAAG,WAAW,CAAC,KAAK,CAAC,GAAG,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IAChH,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AAC3B,CAAC;AAED;;;;;;GAMG;AACH,+FAA+F;AAC/F,KAAK,UAAU,oBAAoB,CAAC,eAAuB,EAAE,KAAW;IAEtE,OAAO,YAAY,CAAC,eAAe,CAAC,CAAC;AACvC,CAAC;AAED,MAAM,CAAC,MAAM,YAAY,GAAyB;IAEhD,UAAU,EAAE,cAAc;IAC1B,OAAO,EAAE,mBAAmB;IAC5B,mBAAmB,EAAE,uBAAuB;IAC5C,gBAAgB,EAAE,oBAAoB;CACvC,CAAC"}
@@ -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 if video is filling the viewport
182
- * 3. Simple retry: If verification fails, wait and retry the same approach (timing issues are common)
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 the common case where fullscreen fails due to timing - the page may still be initializing when we first attempt. Escalation to
186
- * aggressive techniques is a last resort that may break site functionality but ensures video fills the viewport.
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
- export declare function ensureFullscreen(page: Page, context: Frame | Page, profile: ResolvedSiteProfile, selectorType: VideoSelectorType): Promise<void>;
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 verification and retry (applies CSS, triggers fullscreen API/keyboard, verifies video fills viewport, escalates to aggressive
203
- * techniques if needed)
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 recoveryLevel - The escalation level (1-2). Defaults to 1.
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, recoveryLevel?: number): Promise<void>;
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 {};