prismcast 1.5.0 → 1.5.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 (54) hide show
  1. package/dist/app.js +18 -0
  2. package/dist/app.js.map +1 -1
  3. package/dist/browser/channelSelection.js +2 -1
  4. package/dist/browser/channelSelection.js.map +1 -1
  5. package/dist/browser/precaching.js +5 -1
  6. package/dist/browser/precaching.js.map +1 -1
  7. package/dist/browser/tuning/directv.js +56 -6
  8. package/dist/browser/tuning/directv.js.map +1 -1
  9. package/dist/browser/tuning/spectrum.d.ts +2 -0
  10. package/dist/browser/tuning/spectrum.js +410 -0
  11. package/dist/browser/tuning/spectrum.js.map +1 -0
  12. package/dist/channels/index.js +170 -11
  13. package/dist/channels/index.js.map +1 -1
  14. package/dist/config/paths.d.ts +5 -0
  15. package/dist/config/paths.js +7 -0
  16. package/dist/config/paths.js.map +1 -1
  17. package/dist/config/providers.d.ts +2 -2
  18. package/dist/config/providers.js +16 -12
  19. package/dist/config/providers.js.map +1 -1
  20. package/dist/config/sites.js +12 -0
  21. package/dist/config/sites.js.map +1 -1
  22. package/dist/config/userChannels.d.ts +29 -8
  23. package/dist/config/userChannels.js +48 -23
  24. package/dist/config/userChannels.js.map +1 -1
  25. package/dist/config/userProfiles.js +1 -1
  26. package/dist/config/userProfiles.js.map +1 -1
  27. package/dist/routes/config/channels/routes.js +5 -3
  28. package/dist/routes/config/channels/routes.js.map +1 -1
  29. package/dist/routes/config/channels/table.d.ts +1 -1
  30. package/dist/routes/config/channels/table.js +55 -45
  31. package/dist/routes/config/channels/table.js.map +1 -1
  32. package/dist/routes/root/content.js +7 -4
  33. package/dist/routes/root/content.js.map +1 -1
  34. package/dist/routes/root/scripts/config.js +52 -28
  35. package/dist/routes/root/scripts/config.js.map +1 -1
  36. package/dist/routes/root/styles.js +5 -10
  37. package/dist/routes/root/styles.js.map +1 -1
  38. package/dist/streaming/fmp4Segmenter.js +2 -2
  39. package/dist/streaming/fmp4Segmenter.js.map +1 -1
  40. package/dist/streaming/hls.js +11 -0
  41. package/dist/streaming/hls.js.map +1 -1
  42. package/dist/streaming/hlsResume.d.ts +41 -0
  43. package/dist/streaming/hlsResume.js +153 -0
  44. package/dist/streaming/hlsResume.js.map +1 -0
  45. package/dist/streaming/hlsSegments.d.ts +6 -0
  46. package/dist/streaming/hlsSegments.js +8 -0
  47. package/dist/streaming/hlsSegments.js.map +1 -1
  48. package/dist/streaming/monitor.js +18 -18
  49. package/dist/streaming/monitor.js.map +1 -1
  50. package/dist/types/profiles.d.ts +3 -1
  51. package/dist/types/selection.d.ts +1 -0
  52. package/dist/utils/debugFilter.js +1 -0
  53. package/dist/utils/debugFilter.js.map +1 -1
  54. package/package.json +1 -1
@@ -0,0 +1,410 @@
1
+ import { LOG, evaluateWithAbort, formatError } from "../../utils/index.js";
2
+ import { CONFIG } from "../../config/index.js";
3
+ import { logAvailableChannels } from "../channelSelection.js";
4
+ // Base URL for Spectrum TV watch page navigation.
5
+ const SPECTRUM_BASE_URL = "https://watch.spectrum.net";
6
+ // Unified channel cache for Spectrum TV. Maps lowercased lookup keys (callsigns like "espnhd", stripped display names like "espn", and network names like "nbc")
7
+ // to their combined discovery and tuning data. Multiple keys may reference the same entry. Populated during the first tune (when the strategy enumerates all
8
+ // streamable channels from the non-virtualized guide grid) or the first discovery call. Both tuning (via findSpectrumChannel) and discovery (via
9
+ // getCachedChannels / discoverSpectrumChannels) read from this single cache. Cleared on browser disconnect via clearSpectrumCache().
10
+ const spectrumChannelCache = new Map();
11
+ // Tracks consecutive guide page loads that discover zero channels. When this reaches the recovery threshold, the strategy clears Spectrum site data (cache
12
+ // storage) via CDP and reloads the guide to break out of a degraded state where the guide grid container renders but channel entries are not populated. Reset
13
+ // to zero on any successful discovery (> 0 channels found) or on browser restart (via clearSpectrumCache).
14
+ let consecutiveEmptyDiscoveries = 0;
15
+ // Number of consecutive empty discoveries before attempting site data recovery via CDP.
16
+ const EMPTY_DISCOVERY_RECOVERY_THRESHOLD = 3;
17
+ // Regex pattern for detecting local affiliates and subchannels. Matches "{Name} ({CallSign})" with optional trailing " HD"/" DT" suffix and optional
18
+ // "East"/"West" direction.
19
+ const PARENTHETICAL_PATTERN = /^(.+?) \(([^)]+)\)(?: (?:HD|DT)(?: (?:East|West))?)?$/;
20
+ // Regex pattern for stripping trailing technology and direction suffixes from display names. Handles " HD", " DT", " HD East", " HD West", " DT East", " DT West".
21
+ const SUFFIX_PATTERN = / (?:HD|DT)(?: (?:East|West))?$/;
22
+ // Broadcast network names used for affiliate detection. When a channel's rowheader matches the parenthetical pattern and the name before "(" is one of these
23
+ // networks, the channel is tagged as an affiliate with the network name as channelSelector. Other parenthetical channels (subchannels like "Buzzr (WTVT)") use
24
+ // the pre-parenthetical name as channelSelector instead.
25
+ const BROADCAST_NETWORKS = new Set([
26
+ "abc", "cbs", "cw", "fox", "nbc", "pbs"
27
+ ]);
28
+ /**
29
+ * Extracts all streamable channels from the Spectrum guide DOM in a single evaluate round-trip. Reads channel headers for callsigns, channel numbers, and
30
+ * Gracenote station IDs (from logo image URLs), then reads rowheaders for human-readable display names, joining by channel-index. Deduplicates by tmsid
31
+ * (first occurrence wins) to eliminate legacy mirror ranges (1000+/1200+). Filters out non-streamable channels via the :not(.nonstreamable) selector.
32
+ * @param page - The Puppeteer page object positioned on the Spectrum guide.
33
+ * @returns Array of raw channel data with callsign, channel number, display name, and tmsid.
34
+ */
35
+ async function discoverGuideChannels(page) {
36
+ return await evaluateWithAbort(page, () => {
37
+ const results = [];
38
+ const seenTmsids = new Set();
39
+ // Build a channel-index to rowheader display name map from the program grid section. Rowheaders use the format "Channel {number} {display name}" — we
40
+ // extract just the display name portion by stripping the "Channel {number} " prefix.
41
+ const rowheaderMap = new Map();
42
+ for (const span of Array.from(document.querySelectorAll("span[id^=\"rowheader-\"]"))) {
43
+ const id = span.getAttribute("id") ?? "";
44
+ const index = id.replace("rowheader-", "");
45
+ const text = span.textContent.trim();
46
+ // Strip the "Channel {number} " prefix to get the display name. The prefix always follows this format.
47
+ const prefixMatch = /^Channel \d+ (.+)$/.exec(text);
48
+ if (prefixMatch) {
49
+ rowheaderMap.set(index, prefixMatch[1]);
50
+ }
51
+ }
52
+ // Read all streamable channel headers. Non-streamable channels are excluded by the :not(.nonstreamable) CSS selector.
53
+ for (const li of Array.from(document.querySelectorAll("li.channel-header-row:not(.nonstreamable)"))) {
54
+ const callsignEl = li.querySelector("p.callsign");
55
+ const numberEl = li.querySelector("p.channel-number");
56
+ const logoImg = li.querySelector("img.channel-logo");
57
+ const channelIndex = li.getAttribute("channel-index") ?? "";
58
+ if (!callsignEl || !numberEl || !logoImg) {
59
+ continue;
60
+ }
61
+ const callsign = callsignEl.textContent.trim();
62
+ const channelNumber = numberEl.textContent.trim();
63
+ // Extract the Gracenote station ID from the logo image URL. The format is: /guide/{tmsid}?width=50&sourceType=colorhybrid.
64
+ const src = logoImg.getAttribute("src") ?? "";
65
+ const tmsidMatch = /\/guide\/(\d+)\?/.exec(src);
66
+ if (!tmsidMatch) {
67
+ continue;
68
+ }
69
+ const tmsid = tmsidMatch[1];
70
+ // Deduplicate by tmsid — first occurrence wins (lowest channel number). This eliminates legacy mirror ranges (1000+/1200+) that share the same tmsid
71
+ // with primary entries but have no program listing data.
72
+ if (seenTmsids.has(tmsid)) {
73
+ continue;
74
+ }
75
+ seenTmsids.add(tmsid);
76
+ // Look up the human-readable display name from the rowheader map. Fall back to the callsign if no rowheader exists (unlikely but defensive).
77
+ const displayName = rowheaderMap.get(channelIndex) ?? callsign;
78
+ // Filter out audio-only Music Choice channels (callsigns MC01-MC50, display names like "~MC01:") and Spectrum-internal overflow channels (callsigns
79
+ // SPCTRM1-SPCTRM20, display names like "Spectrum1"). These are streamable in the guide but are not useful for video capture or channel discovery.
80
+ if (displayName.startsWith("~") || /^Spectrum\d/.test(displayName)) {
81
+ continue;
82
+ }
83
+ results.push({ callsign, channelNumber, displayName, tmsid });
84
+ }
85
+ return results;
86
+ }, []);
87
+ }
88
+ /**
89
+ * Attempts to recover from a degraded Spectrum guide state by clearing cached site data via CDP and reloading the guide page. This targets a failure mode where
90
+ * the guide grid container renders but channel entries are not populated, which can be caused by stale AngularJS template or API response caches. Cookies and
91
+ * login session state are preserved — only caching layers are cleared.
92
+ * @param page - The Puppeteer page object.
93
+ * @returns Discovered channels after recovery, or an empty array if recovery failed.
94
+ */
95
+ async function attemptGuideRecovery(page) {
96
+ LOG.warn("Clearing Spectrum TV cached site data to recover from empty guide.");
97
+ // Clear cache storage for the Spectrum origin. Cookies and login session state are deliberately preserved to avoid forcing re-authentication.
98
+ try {
99
+ const client = await page.createCDPSession();
100
+ await client.send("Storage.clearDataForOrigin", { origin: SPECTRUM_BASE_URL, storageTypes: "cache_storage" });
101
+ await client.detach();
102
+ }
103
+ catch (error) {
104
+ LOG.warn("Failed to clear Spectrum TV site data: %s.", formatError(error));
105
+ return [];
106
+ }
107
+ // Reload the guide page with fresh state.
108
+ try {
109
+ await page.goto(SPECTRUM_BASE_URL + "/guide", { timeout: CONFIG.streaming.navigationTimeout, waitUntil: "load" });
110
+ }
111
+ catch (error) {
112
+ LOG.warn("Failed to reload Spectrum TV guide after clearing site data: %s.", formatError(error));
113
+ return [];
114
+ }
115
+ // Wait for the guide grid to render after reload.
116
+ try {
117
+ await page.waitForSelector("li.channel-header-row", { timeout: CONFIG.streaming.videoTimeout });
118
+ }
119
+ catch {
120
+ LOG.warn("Spectrum TV guide grid did not load after clearing site data.");
121
+ return [];
122
+ }
123
+ // Re-attempt channel discovery on the reloaded page.
124
+ const channels = await discoverGuideChannels(page);
125
+ if (channels.length > 0) {
126
+ LOG.info("Spectrum TV guide recovery succeeded — discovered %s channels after clearing site data.", channels.length);
127
+ }
128
+ else {
129
+ LOG.warn("Spectrum TV guide still empty after clearing site data.");
130
+ }
131
+ return channels;
132
+ }
133
+ /**
134
+ * Populates the unified channel cache from raw guide channel data. For each channel, builds a DiscoveredChannel with affiliate detection and pairs it with the
135
+ * tmsid for watch URL construction. Creates multiple cache keys per channel: (a) lowercased callsign, (b) lowercased display name stripped of " HD"/" DT"
136
+ * suffix, and (c) lowercased network name for affiliates (unless the key is already taken by a cable channel). When a channel's rowheader matches the "{Name}
137
+ * ({CallSign})" pattern and the name is a known broadcast network, it is tagged as an affiliate with the network name as channelSelector. Other parenthetical
138
+ * channels (subchannels) use the pre-parenthetical name as channelSelector.
139
+ * @param rawChannels - Array of raw channel data from discoverGuideChannels().
140
+ */
141
+ function populateSpectrumChannelCache(rawChannels) {
142
+ spectrumChannelCache.clear();
143
+ // First pass: build all entries and store under callsign key. Track network name keys separately to avoid overwriting cable channels in the second pass.
144
+ const entries = [];
145
+ for (const ch of rawChannels) {
146
+ const stripped = ch.displayName.replace(SUFFIX_PATTERN, "");
147
+ const parenthetical = PARENTHETICAL_PATTERN.exec(ch.displayName);
148
+ let channelSelector;
149
+ let affiliate;
150
+ let networkKey;
151
+ if (parenthetical) {
152
+ const preName = parenthetical[1];
153
+ // Check if this is a broadcast affiliate (e.g., "NBC (WFLA) HD") or a subchannel (e.g., "Buzzr (WTVT)").
154
+ if (BROADCAST_NETWORKS.has(preName.toLowerCase())) {
155
+ channelSelector = preName;
156
+ affiliate = preName;
157
+ networkKey = preName.toLowerCase();
158
+ }
159
+ else {
160
+ // Subchannel or non-broadcast parenthetical — use the pre-parenthetical name as channelSelector.
161
+ channelSelector = preName;
162
+ }
163
+ }
164
+ else {
165
+ // Cable channel — use the stripped display name as channelSelector.
166
+ channelSelector = stripped;
167
+ }
168
+ const discovered = {
169
+ ...(affiliate ? { affiliate } : {}),
170
+ channelSelector,
171
+ name: stripped,
172
+ stationId: ch.tmsid
173
+ };
174
+ const spectrumEntry = { discovered, tmsid: ch.tmsid };
175
+ // Store under lowercased callsign (primary key).
176
+ spectrumChannelCache.set(ch.callsign.toLowerCase(), spectrumEntry);
177
+ entries.push({ entry: spectrumEntry, networkKey, strippedKey: stripped.toLowerCase() });
178
+ }
179
+ // Second pass: add stripped display name keys and network name keys. Cable channel names take precedence over affiliate network names when keys collide (e.g.,
180
+ // a cable channel named "Fox" would keep its key over a "FOX (WTVT)" affiliate entry).
181
+ for (const { entry, networkKey, strippedKey } of entries) {
182
+ // Add stripped display name key if not already taken. First-write wins — if two channels strip to the same name, the first (lower channel number) keeps it.
183
+ if (!spectrumChannelCache.has(strippedKey)) {
184
+ spectrumChannelCache.set(strippedKey, entry);
185
+ }
186
+ // Add network name key for affiliates if not already taken by a cable channel or another affiliate.
187
+ if (networkKey && !spectrumChannelCache.has(networkKey)) {
188
+ spectrumChannelCache.set(networkKey, entry);
189
+ }
190
+ }
191
+ }
192
+ /**
193
+ * Looks up a channel in the unified cache using tiered matching logic:
194
+ *
195
+ * 1. Exact match: cache key equals the lowercased input (matches callsigns like "espnhd" and stripped names like "espn" and network names like "nbc").
196
+ * 2. HD/DT suffix tolerance: input + "hd" or input + "dt" matches a cache key (e.g., "espn" → "espnhd", "wfla" → "wfladt").
197
+ * 3. Display name iteration: iterate all cache entries, check if discovered.name (lowercased) equals input. Catches long display names like "Discovery Channel"
198
+ * when only the callsign-derived key was cached.
199
+ *
200
+ * When a non-exact match succeeds, the result is cached under the input key for O(1) lookup on subsequent calls. This function doubles as the resolveDirectUrl
201
+ * hook — after the first tune populates the cache via channel discovery, every subsequent Spectrum tune resolves here without loading the guide page.
202
+ * @param channelName - The channelSelector value (e.g., "ESPN", "NBC", "Discovery Channel").
203
+ * @returns The matching cache entry or null if no match is found.
204
+ */
205
+ function findSpectrumChannel(channelName) {
206
+ const lower = channelName.toLowerCase();
207
+ // Tier 1: Exact match on any cache key (callsigns, stripped names, network names).
208
+ const exact = spectrumChannelCache.get(lower);
209
+ if (exact) {
210
+ return exact;
211
+ }
212
+ // Tier 2: HD/DT suffix tolerance. Try appending common technology suffixes.
213
+ const hdMatch = spectrumChannelCache.get(lower + "hd");
214
+ if (hdMatch) {
215
+ spectrumChannelCache.set(lower, hdMatch);
216
+ return hdMatch;
217
+ }
218
+ const dtMatch = spectrumChannelCache.get(lower + "dt");
219
+ if (dtMatch) {
220
+ spectrumChannelCache.set(lower, dtMatch);
221
+ return dtMatch;
222
+ }
223
+ // Tier 3: Display name iteration. Check if any entry's discovered.name matches the input. This catches channels with long display names (e.g.,
224
+ // "Discovery Channel") that may not have a matching cache key because the callsign was "DSCHD" and the stripped key "discovery channel" was already checked
225
+ // in tier 1. This tier is the fallback for any names not covered by the first two tiers.
226
+ const seen = new Set();
227
+ for (const entry of spectrumChannelCache.values()) {
228
+ if (seen.has(entry)) {
229
+ continue;
230
+ }
231
+ seen.add(entry);
232
+ if (entry.discovered.name.toLowerCase() === lower) {
233
+ spectrumChannelCache.set(lower, entry);
234
+ return entry;
235
+ }
236
+ }
237
+ return null;
238
+ }
239
+ /**
240
+ * Invalidates the cached Spectrum channel entry for the given channel selector. Called when a cached URL fails to produce a working stream. Deletes the
241
+ * channelSelector key — the original keys from channel discovery are left intact and will be refreshed on the next strategy run when the guide page is reloaded.
242
+ * @param channelSelector - The channel selector string to invalidate.
243
+ */
244
+ function invalidateSpectrumDirectUrl(channelSelector) {
245
+ spectrumChannelCache.delete(channelSelector.toLowerCase());
246
+ }
247
+ /**
248
+ * Clears the Spectrum channel cache. Called by clearChannelSelectionCaches() in the coordinator when the browser restarts, since a fresh browser session
249
+ * may have different channel availability.
250
+ */
251
+ function clearSpectrumCache() {
252
+ spectrumChannelCache.clear();
253
+ consecutiveEmptyDiscoveries = 0;
254
+ }
255
+ /**
256
+ * Derives a DiscoveredChannel array from the unified channel cache, deduplicating alias entries via Set reference equality. Multiple cache keys may point to
257
+ * the same SpectrumChannelEntry (callsign key, stripped name key, network name key, and any alias keys created by findSpectrumChannel). Without deduplication,
258
+ * iterating cache values would produce duplicate entries. Sorts by name before returning.
259
+ * @returns Sorted, deduplicated array of discovered channels.
260
+ */
261
+ function buildSpectrumDiscoveredChannels() {
262
+ const channels = [];
263
+ const seen = new Set();
264
+ for (const entry of spectrumChannelCache.values()) {
265
+ if (seen.has(entry)) {
266
+ continue;
267
+ }
268
+ seen.add(entry);
269
+ channels.push(entry.discovered);
270
+ }
271
+ channels.sort((a, b) => a.name.localeCompare(b.name));
272
+ return channels;
273
+ }
274
+ /**
275
+ * Spectrum TV grid strategy: discovers all streamable channels from the non-virtualized guide at watch.spectrum.net/guide in a single evaluate pass, populating
276
+ * the unified channel cache so that subsequent tunes to any Spectrum channel resolve via findSpectrumChannel() without loading the guide page. All ~442 streamable
277
+ * channel rows are present in the DOM simultaneously (AngularJS, non-virtualized), so one querySelectorAll captures every channel's callsign, display name, and
278
+ * Gracenote station ID.
279
+ *
280
+ * The selection process:
281
+ * 1. Wait for li.channel-header-row elements to confirm the guide grid has loaded (AngularJS rendering gate).
282
+ * 2. Discover all channels: extract callsigns, channel numbers, tmsids, and display names from channel headers and rowheaders.
283
+ * 3. If no channels are discovered (degraded guide state), attempt recovery by clearing cached site data via CDP and reloading.
284
+ * 4. Populate the unified channel cache with all discovered channels.
285
+ * 5. Look up the target channel using tiered matching (exact, suffix tolerance, display name) against the cache.
286
+ * 6. Navigate to the matched watch URL via page.goto() — direct URL navigation with no clicking.
287
+ * @param page - The Puppeteer page object.
288
+ * @param profile - The resolved site profile with a non-null channelSelector (channel name, e.g., "ESPN", "CNN", "NBC").
289
+ * @returns Result object with success status and optional failure reason.
290
+ */
291
+ async function spectrumGridStrategy(page, profile) {
292
+ const channelName = profile.channelSelector;
293
+ // Wait for the guide grid to render. All streamable channels load simultaneously (non-virtualized AngularJS), so once any row exists, all channels are
294
+ // queryable.
295
+ try {
296
+ await page.waitForSelector("li.channel-header-row", { timeout: CONFIG.streaming.videoTimeout });
297
+ }
298
+ catch {
299
+ return { reason: "Spectrum guide grid did not load.", success: false };
300
+ }
301
+ // Discover all channels from the guide grid.
302
+ let allChannels = await discoverGuideChannels(page);
303
+ // If the guide loaded but no channels were discovered, the guide is in a degraded state — the grid container rendered but channel entries were not populated.
304
+ // This can happen when stale AngularJS template or API response caches become inconsistent. Track consecutive occurrences and attempt recovery by clearing
305
+ // cached site data once the threshold is reached.
306
+ if (allChannels.length === 0) {
307
+ consecutiveEmptyDiscoveries++;
308
+ LOG.warn("Spectrum TV guide loaded but no channels were discovered (%s consecutive). The guide may be in a degraded state.", consecutiveEmptyDiscoveries);
309
+ if (consecutiveEmptyDiscoveries >= EMPTY_DISCOVERY_RECOVERY_THRESHOLD) {
310
+ allChannels = await attemptGuideRecovery(page);
311
+ }
312
+ }
313
+ // If we still have no channels after the initial discovery and any recovery attempt, there is nothing to search or cache.
314
+ if (allChannels.length === 0) {
315
+ return { reason: "Spectrum guide is empty — no channels were discovered.", success: false };
316
+ }
317
+ // Successful discovery — reset the consecutive empty counter and repopulate the unified channel cache. Always repopulate rather than skipping when the cache
318
+ // has entries, because invalidated entries need to be restored with fresh data from the guide.
319
+ consecutiveEmptyDiscoveries = 0;
320
+ populateSpectrumChannelCache(allChannels);
321
+ LOG.debug("tuning:spectrum", "Discovered %s Spectrum channels.", allChannels.length);
322
+ // Look up the target channel using tiered matching against the populated cache.
323
+ const entry = findSpectrumChannel(channelName);
324
+ if (!entry) {
325
+ // Channel not found. Log available channels as a diagnostic to help users identify their market's channel names.
326
+ logAvailableChannels({
327
+ availableChannels: buildSpectrumDiscoveredChannels().map((ch) => ch.name).sort(),
328
+ channelName,
329
+ guideUrl: "https://watch.spectrum.net/guide",
330
+ presetSuffix: "-spectrum",
331
+ providerName: "Spectrum TV"
332
+ });
333
+ return { reason: "Channel \"" + channelName + "\" not found in Spectrum guide.", success: false };
334
+ }
335
+ LOG.debug("tuning:spectrum", "Navigating to Spectrum watch URL for %s (tmsid: %s).", channelName, entry.tmsid);
336
+ try {
337
+ await page.goto(SPECTRUM_BASE_URL + "/livetv?tmsid=" + entry.tmsid, { timeout: CONFIG.streaming.navigationTimeout, waitUntil: "load" });
338
+ }
339
+ catch (error) {
340
+ return { reason: "Failed to navigate to Spectrum watch page: " + formatError(error) + ".", success: false };
341
+ }
342
+ return { success: true };
343
+ }
344
+ /**
345
+ * Async wrapper around findSpectrumChannel for the ChannelStrategyEntry.resolveDirectUrl contract. The page parameter is unused because Spectrum watch URLs are
346
+ * resolved purely from the in-memory cache populated during the initial guide page discovery.
347
+ * @param channelSelector - The channel selector string (e.g., "ESPN", "CNN", "NBC").
348
+ * @param _page - Unused. Present to satisfy the async resolveDirectUrl signature.
349
+ * @returns The cached watch URL or null.
350
+ */
351
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/require-await
352
+ async function resolveSpectrumDirectUrl(channelSelector, _page) {
353
+ const entry = findSpectrumChannel(channelSelector);
354
+ if (!entry) {
355
+ return null;
356
+ }
357
+ return SPECTRUM_BASE_URL + "/livetv?tmsid=" + entry.tmsid;
358
+ }
359
+ /**
360
+ * Discovers all channels from the Spectrum TV guide. Returns cached results if the unified channel cache is populated from a prior tune or discovery call.
361
+ * Otherwise, waits for the first channel header row to confirm the guide has rendered, then extracts all streamable channels via discoverGuideChannels() and
362
+ * populates the cache (unless empty, to allow retries on transient failures).
363
+ * @param page - The Puppeteer page object.
364
+ * @returns Array of discovered channels with affiliate tagging and Gracenote station IDs.
365
+ */
366
+ async function discoverSpectrumChannels(page) {
367
+ // Return from the unified cache if already populated.
368
+ if (spectrumChannelCache.size > 0) {
369
+ return buildSpectrumDiscoveredChannels();
370
+ }
371
+ // Wait for at least one channel header row to confirm the guide grid has rendered.
372
+ try {
373
+ await page.waitForSelector("li.channel-header-row", { timeout: CONFIG.streaming.videoTimeout });
374
+ }
375
+ catch {
376
+ return [];
377
+ }
378
+ const allChannels = await discoverGuideChannels(page);
379
+ // Do not cache empty results — leave the cache empty so subsequent calls retry the full walk. Empty results can indicate no subscription or transient failures.
380
+ if (allChannels.length === 0) {
381
+ return [];
382
+ }
383
+ populateSpectrumChannelCache(allChannels);
384
+ return buildSpectrumDiscoveredChannels();
385
+ }
386
+ /**
387
+ * Returns cached discovered channels from the unified channel cache, or null if the cache is empty (no prior tune or discovery call has enumerated the lineup).
388
+ * @returns Sorted array of discovered channels or null.
389
+ */
390
+ function getSpectrumCachedChannels() {
391
+ if (spectrumChannelCache.size === 0) {
392
+ return null;
393
+ }
394
+ return buildSpectrumDiscoveredChannels();
395
+ }
396
+ export const spectrumProvider = {
397
+ discoverChannels: discoverSpectrumChannels,
398
+ getCachedChannels: getSpectrumCachedChannels,
399
+ guideUrl: "https://watch.spectrum.net/guide",
400
+ label: "Spectrum TV",
401
+ slug: "spectrum",
402
+ strategy: {
403
+ clearCache: clearSpectrumCache,
404
+ execute: spectrumGridStrategy,
405
+ invalidateDirectUrl: invalidateSpectrumDirectUrl,
406
+ resolveDirectUrl: resolveSpectrumDirectUrl
407
+ },
408
+ strategyName: "spectrumGrid"
409
+ };
410
+ //# sourceMappingURL=spectrum.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spectrum.js","sourceRoot":"","sources":["../../../src/browser/tuning/spectrum.ts"],"names":[],"mappings":"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"}