prismcast 1.5.2 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -28
- package/dist/app.d.ts +5 -0
- package/dist/app.js +63 -5
- package/dist/app.js.map +1 -1
- package/dist/browser/cdp.js +1 -1
- package/dist/browser/cdp.js.map +1 -1
- package/dist/browser/channelSelection.d.ts +10 -34
- package/dist/browser/channelSelection.js +31 -96
- package/dist/browser/channelSelection.js.map +1 -1
- package/dist/browser/display.d.ts +19 -0
- package/dist/browser/display.js +16 -11
- package/dist/browser/display.js.map +1 -1
- package/dist/browser/index.d.ts +31 -54
- package/dist/browser/index.js +334 -272
- package/dist/browser/index.js.map +1 -1
- package/dist/browser/login.d.ts +65 -0
- package/dist/browser/login.js +193 -0
- package/dist/browser/login.js.map +1 -0
- package/dist/browser/precaching.js +15 -3
- package/dist/browser/precaching.js.map +1 -1
- package/dist/browser/tuning/comcastPolymer.d.ts +23 -0
- package/dist/browser/tuning/comcastPolymer.js +655 -0
- package/dist/browser/tuning/comcastPolymer.js.map +1 -0
- package/dist/browser/tuning/cox.d.ts +2 -0
- package/dist/browser/tuning/cox.js +14 -0
- package/dist/browser/tuning/cox.js.map +1 -0
- package/dist/browser/tuning/directv.js +14 -1
- package/dist/browser/tuning/directv.js.map +1 -1
- package/dist/browser/tuning/fox.js +14 -2
- package/dist/browser/tuning/fox.js.map +1 -1
- package/dist/browser/tuning/hbo.js +12 -1
- package/dist/browser/tuning/hbo.js.map +1 -1
- package/dist/browser/tuning/hulu.js +24 -1
- package/dist/browser/tuning/hulu.js.map +1 -1
- package/dist/browser/tuning/shared.d.ts +46 -0
- package/dist/browser/tuning/shared.js +100 -0
- package/dist/browser/tuning/shared.js.map +1 -0
- package/dist/browser/tuning/sling.js +35 -5
- package/dist/browser/tuning/sling.js.map +1 -1
- package/dist/browser/tuning/spectrum.js +13 -1
- package/dist/browser/tuning/spectrum.js.map +1 -1
- package/dist/browser/tuning/thumbnailRow.js +1 -1
- package/dist/browser/tuning/thumbnailRow.js.map +1 -1
- package/dist/browser/tuning/tileClick.js +1 -1
- package/dist/browser/tuning/tileClick.js.map +1 -1
- package/dist/browser/tuning/xfinity.d.ts +2 -0
- package/dist/browser/tuning/xfinity.js +14 -0
- package/dist/browser/tuning/xfinity.js.map +1 -0
- package/dist/browser/tuning/youtubeTv.js +15 -1
- package/dist/browser/tuning/youtubeTv.js.map +1 -1
- package/dist/browser/video.d.ts +20 -0
- package/dist/browser/video.js +168 -124
- package/dist/browser/video.js.map +1 -1
- package/dist/channels/index.d.ts +2 -2
- package/dist/channels/index.js +2075 -683
- package/dist/channels/index.js.map +1 -1
- package/dist/config/health.d.ts +28 -28
- package/dist/config/health.js +72 -54
- package/dist/config/health.js.map +1 -1
- package/dist/config/index.js +29 -64
- package/dist/config/index.js.map +1 -1
- package/dist/config/paths.d.ts +12 -0
- package/dist/config/paths.js +16 -0
- package/dist/config/paths.js.map +1 -1
- package/dist/config/profiles.d.ts +3 -2
- package/dist/config/profiles.js +45 -34
- package/dist/config/profiles.js.map +1 -1
- package/dist/config/providerPacks.js +4 -1
- package/dist/config/providerPacks.js.map +1 -1
- package/dist/config/providers.d.ts +19 -5
- package/dist/config/providers.js +123 -93
- package/dist/config/providers.js.map +1 -1
- package/dist/config/sites.d.ts +28 -0
- package/dist/config/sites.js +104 -127
- package/dist/config/sites.js.map +1 -1
- package/dist/config/userChannels.d.ts +53 -2
- package/dist/config/userChannels.js +222 -13
- package/dist/config/userChannels.js.map +1 -1
- package/dist/config/userConfig.d.ts +2 -1
- package/dist/config/userConfig.js +6 -15
- package/dist/config/userConfig.js.map +1 -1
- package/dist/config/userProfiles.d.ts +0 -1
- package/dist/config/userProfiles.js +18 -10
- package/dist/config/userProfiles.js.map +1 -1
- package/dist/hdhr/channelMap.d.ts +1 -1
- package/dist/hdhr/channelMap.js +4 -4
- package/dist/hdhr/channelMap.js.map +1 -1
- package/dist/index.js +25 -22
- package/dist/index.js.map +1 -1
- package/dist/native/decrypt.d.ts +32 -0
- package/dist/native/decrypt.js +85 -0
- package/dist/native/decrypt.js.map +1 -0
- package/dist/native/index.d.ts +61 -0
- package/dist/native/index.js +342 -0
- package/dist/native/index.js.map +1 -0
- package/dist/native/intercept.d.ts +35 -0
- package/dist/native/intercept.js +184 -0
- package/dist/native/intercept.js.map +1 -0
- package/dist/native/probe.d.ts +49 -0
- package/dist/native/probe.js +294 -0
- package/dist/native/probe.js.map +1 -0
- package/dist/native/proxy.d.ts +54 -0
- package/dist/native/proxy.js +947 -0
- package/dist/native/proxy.js.map +1 -0
- package/dist/routes/components.d.ts +43 -0
- package/dist/routes/components.js +122 -0
- package/dist/routes/components.js.map +1 -1
- package/dist/routes/config/channels/index.d.ts +2 -2
- package/dist/routes/config/channels/index.js +1 -1
- package/dist/routes/config/channels/index.js.map +1 -1
- package/dist/routes/config/channels/routes.js +443 -65
- package/dist/routes/config/channels/routes.js.map +1 -1
- package/dist/routes/config/channels/table.d.ts +76 -0
- package/dist/routes/config/channels/table.js +369 -89
- package/dist/routes/config/channels/table.js.map +1 -1
- package/dist/routes/config/index.d.ts +1 -1
- package/dist/routes/config/index.js +1 -1
- package/dist/routes/config/index.js.map +1 -1
- package/dist/routes/config/providers.d.ts +6 -6
- package/dist/routes/config/providers.js +73 -99
- package/dist/routes/config/providers.js.map +1 -1
- package/dist/routes/debug.js +5 -5
- package/dist/routes/debug.js.map +1 -1
- package/dist/routes/hls.js +12 -2
- package/dist/routes/hls.js.map +1 -1
- package/dist/routes/index.d.ts +0 -15
- package/dist/routes/index.js +2 -16
- package/dist/routes/index.js.map +1 -1
- package/dist/routes/providers.js +106 -3
- package/dist/routes/providers.js.map +1 -1
- package/dist/routes/root/content.js +124 -47
- package/dist/routes/root/content.js.map +1 -1
- package/dist/routes/root/index.js +2 -1
- package/dist/routes/root/index.js.map +1 -1
- package/dist/routes/root/scripts/channels.js +772 -312
- package/dist/routes/root/scripts/channels.js.map +1 -1
- package/dist/routes/root/scripts/config.js +596 -544
- package/dist/routes/root/scripts/config.js.map +1 -1
- package/dist/routes/root/scripts/index.d.ts +1 -0
- package/dist/routes/root/scripts/index.js +1 -0
- package/dist/routes/root/scripts/index.js.map +1 -1
- package/dist/routes/root/scripts/shared.d.ts +6 -0
- package/dist/routes/root/scripts/shared.js +391 -0
- package/dist/routes/root/scripts/shared.js.map +1 -0
- package/dist/routes/root/scripts/status.js +49 -33
- package/dist/routes/root/scripts/status.js.map +1 -1
- package/dist/routes/root/styles.js +117 -10
- package/dist/routes/root/styles.js.map +1 -1
- package/dist/service/commands.js +2 -1
- package/dist/service/commands.js.map +1 -1
- package/dist/streaming/fmp4Segmenter.d.ts +5 -0
- package/dist/streaming/fmp4Segmenter.js +146 -42
- package/dist/streaming/fmp4Segmenter.js.map +1 -1
- package/dist/streaming/hls.d.ts +24 -19
- package/dist/streaming/hls.js +603 -297
- package/dist/streaming/hls.js.map +1 -1
- package/dist/streaming/hlsResume.d.ts +16 -3
- package/dist/streaming/hlsResume.js +35 -8
- package/dist/streaming/hlsResume.js.map +1 -1
- package/dist/streaming/hlsSegments.d.ts +41 -2
- package/dist/streaming/hlsSegments.js +131 -28
- package/dist/streaming/hlsSegments.js.map +1 -1
- package/dist/streaming/lifecycle.js +27 -13
- package/dist/streaming/lifecycle.js.map +1 -1
- package/dist/streaming/monitor.d.ts +1 -0
- package/dist/streaming/monitor.js +862 -474
- package/dist/streaming/monitor.js.map +1 -1
- package/dist/streaming/mp4Parser.d.ts +52 -6
- package/dist/streaming/mp4Parser.js +156 -30
- package/dist/streaming/mp4Parser.js.map +1 -1
- package/dist/streaming/mpegts.d.ts +2 -2
- package/dist/streaming/mpegts.js +156 -109
- package/dist/streaming/mpegts.js.map +1 -1
- package/dist/streaming/playlistBuilder.d.ts +38 -0
- package/dist/streaming/playlistBuilder.js +66 -0
- package/dist/streaming/playlistBuilder.js.map +1 -0
- package/dist/streaming/preroll.d.ts +126 -0
- package/dist/streaming/preroll.js +434 -0
- package/dist/streaming/preroll.js.map +1 -0
- package/dist/streaming/pretune.d.ts +8 -0
- package/dist/streaming/pretune.js +226 -0
- package/dist/streaming/pretune.js.map +1 -0
- package/dist/streaming/recovery.d.ts +0 -6
- package/dist/streaming/recovery.js +0 -8
- package/dist/streaming/recovery.js.map +1 -1
- package/dist/streaming/registry.d.ts +57 -9
- package/dist/streaming/registry.js +26 -7
- package/dist/streaming/registry.js.map +1 -1
- package/dist/streaming/setup.d.ts +8 -1
- package/dist/streaming/setup.js +71 -27
- package/dist/streaming/setup.js.map +1 -1
- package/dist/streaming/showInfo.d.ts +29 -4
- package/dist/streaming/showInfo.js +210 -38
- package/dist/streaming/showInfo.js.map +1 -1
- package/dist/streaming/statusEmitter.d.ts +20 -6
- package/dist/streaming/statusEmitter.js +31 -7
- package/dist/streaming/statusEmitter.js.map +1 -1
- package/dist/types/channels.d.ts +32 -0
- package/dist/types/config.d.ts +2 -2
- package/dist/types/index.d.ts +2 -2
- package/dist/types/index.js.map +1 -1
- package/dist/types/profiles.d.ts +13 -1
- package/dist/types/selection.d.ts +5 -1
- package/dist/types/shared.d.ts +1 -1
- package/dist/types/streaming.d.ts +7 -0
- package/dist/utils/chromeFetch.d.ts +20 -0
- package/dist/utils/chromeFetch.js +43 -0
- package/dist/utils/chromeFetch.js.map +1 -0
- package/dist/utils/debugFilter.d.ts +0 -5
- package/dist/utils/debugFilter.js +20 -11
- package/dist/utils/debugFilter.js.map +1 -1
- package/dist/utils/delay.d.ts +28 -0
- package/dist/utils/delay.js +35 -1
- package/dist/utils/delay.js.map +1 -1
- package/dist/utils/evaluate.js +6 -11
- package/dist/utils/evaluate.js.map +1 -1
- package/dist/utils/ffmpeg.d.ts +14 -12
- package/dist/utils/ffmpeg.js +114 -134
- package/dist/utils/ffmpeg.js.map +1 -1
- package/dist/utils/format.d.ts +6 -0
- package/dist/utils/format.js +8 -0
- package/dist/utils/format.js.map +1 -1
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/index.js +3 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/logEmitter.js +1 -1
- package/dist/utils/logEmitter.js.map +1 -1
- package/dist/utils/pid.d.ts +40 -0
- package/dist/utils/pid.js +83 -0
- package/dist/utils/pid.js.map +1 -0
- package/dist/utils/retry.d.ts +15 -9
- package/dist/utils/retry.js +7 -23
- package/dist/utils/retry.js.map +1 -1
- package/dist/utils/timing.d.ts +5 -0
- package/dist/utils/timing.js +13 -0
- package/dist/utils/timing.js.map +1 -0
- package/dist/utils/version.js +2 -2
- package/dist/utils/version.js.map +1 -1
- package/package.json +7 -7
package/dist/config/sites.js
CHANGED
|
@@ -4,11 +4,14 @@ import { getUserDomains } from "./userProfiles.js";
|
|
|
4
4
|
* and many have unique quirks like auto-muting or requiring specific fullscreen methods. Rather than scattering site-specific conditionals throughout the streaming
|
|
5
5
|
* code, we define "site profiles" that describe each site's behavior in a declarative way.
|
|
6
6
|
*
|
|
7
|
-
* The profile system has
|
|
7
|
+
* The profile system has four components:
|
|
8
8
|
*
|
|
9
|
-
* 1. SITE_PROFILES:
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* 1. SITE_PROFILES: General-purpose behavior configurations that users can select for custom channels. These describe common player implementation patterns and
|
|
10
|
+
* are shown in UI dropdowns and the provider wizard. Profiles can inherit from other profiles using the "extends" property.
|
|
11
|
+
*
|
|
12
|
+
* 1b. PROVIDER_PROFILES: Internal profiles tied to specific provider modules (Hulu, YouTube TV, Sling, etc.). These have channel selection strategies and
|
|
13
|
+
* selectors tightly coupled to a streaming service's DOM structure. They are never shown in user-facing profile lists — users targeting these services should
|
|
14
|
+
* use the predefined channels directly. Provider profiles can extend general profiles (e.g., fullscreenApi) and profile resolution checks both tables.
|
|
12
15
|
*
|
|
13
16
|
* 2. DOMAIN_CONFIG: A mapping from domain patterns to site profiles and provider display names. When streaming a URL, we check if it matches any known domain and
|
|
14
17
|
* use the corresponding profile. Provider display names give friendly labels (e.g., "Hulu" instead of "hulu.com") for the UI source column and provider
|
|
@@ -36,7 +39,7 @@ import { getUserDomains } from "./userProfiles.js";
|
|
|
36
39
|
* - fullscreenApi: Sites requiring the JavaScript requestFullscreen() API
|
|
37
40
|
* - staticPage: Non-video pages captured as static visual content
|
|
38
41
|
*
|
|
39
|
-
*
|
|
42
|
+
* General derived profiles (extends a base, user-selectable):
|
|
40
43
|
* - keyboardDynamic: Keyboard fullscreen + network idle wait (extends keyboardFullscreen)
|
|
41
44
|
* - keyboardMultiVideo: Keyboard fullscreen + multi-video selection (extends keyboardFullscreen)
|
|
42
45
|
* - keyboardIframe: Keyboard fullscreen + iframe handling (extends keyboardFullscreen)
|
|
@@ -44,16 +47,13 @@ import { getUserDomains } from "./userProfiles.js";
|
|
|
44
47
|
* - clickToPlayKeyboard: Click to start playback + keyboard fullscreen (extends keyboardFullscreen)
|
|
45
48
|
* - brightcove: Brightcove players using API fullscreen + network idle wait (extends fullscreenApi)
|
|
46
49
|
* - clickToPlayApi: Click to start playback + API fullscreen (extends fullscreenApi)
|
|
47
|
-
* - disneyNow: DisneyNOW player with play button overlay + multi-video (extends clickToPlayApi)
|
|
48
50
|
* - embeddedPlayer: Iframe-based players using fullscreen API (extends fullscreenApi)
|
|
49
51
|
* - apiMultiVideo: API fullscreen + multi-video + auto-play tile channel selection via matchSelector (extends fullscreenApi)
|
|
50
|
-
* - disneyPlus: API fullscreen + multi-video + tile selection via matchSelector with play button modal (extends fullscreenApi)
|
|
51
|
-
* - huluLive: Hulu Live TV with guide grid channel selection + fullscreen button (extends fullscreenApi)
|
|
52
52
|
* - embeddedDynamicMultiVideo: Embedded + network idle + multi-video selection (extends embeddedPlayer)
|
|
53
53
|
* - embeddedVolumeLock: Embedded + volume property locking (extends embeddedPlayer)
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
* -
|
|
54
|
+
*
|
|
55
|
+
* Provider profiles (internal, not user-selectable — in PROVIDER_PROFILES table or registered by provider modules):
|
|
56
|
+
* - coxStream, directvStream, disneyNow, disneyPlus, foxLive, hboMax, huluLive, slingLive, spectrum, xfinityStream, youtubeTV
|
|
57
57
|
*
|
|
58
58
|
* Each profile includes a description field documenting its purpose. This is metadata only - it's stripped during profile resolution and exists purely for
|
|
59
59
|
* documentation.
|
|
@@ -72,11 +72,13 @@ export const SITE_PROFILES = {
|
|
|
72
72
|
},
|
|
73
73
|
// Profile for sites using the Brightcove player platform. Brightcove players require waiting for network activity to settle before the video player is fully
|
|
74
74
|
// initialized. The player dynamically loads its configuration and stream manifest, so waitForNetworkIdle ensures we don't try to interact with the player before
|
|
75
|
-
// it's ready. Uses the JavaScript fullscreen API rather than keyboard shortcuts because Brightcove intercepts keyboard events.
|
|
75
|
+
// it's ready. Uses the JavaScript fullscreen API rather than keyboard shortcuts because Brightcove intercepts keyboard events. Uses selectReadyVideo because
|
|
76
|
+
// pages may have multiple video elements (preroll ads alongside the main player), and the ad video reaches readyState >= 3 before the main player.
|
|
76
77
|
brightcove: {
|
|
77
78
|
category: "api",
|
|
78
79
|
description: "Brightcove player sites requiring network idle wait and API fullscreen.",
|
|
79
80
|
extends: "fullscreenApi",
|
|
81
|
+
selectReadyVideo: true,
|
|
80
82
|
summary: "Brightcove players (network wait)",
|
|
81
83
|
waitForNetworkIdle: true
|
|
82
84
|
},
|
|
@@ -98,44 +100,6 @@ export const SITE_PROFILES = {
|
|
|
98
100
|
extends: "keyboardFullscreen",
|
|
99
101
|
summary: "Click-to-play ('f' key fullscreen)"
|
|
100
102
|
},
|
|
101
|
-
// Profile for DirecTV Stream (stream.directv.com) live guide. The guide page is a React Native for Web SPA where all ~152 channel logos are always in the DOM
|
|
102
|
-
// (not virtualized). The primary tuning mechanism bypasses DOM interaction entirely by injecting into webpack internals — capturing __webpack_require__ via the
|
|
103
|
-
// chunk push array, extracting the Redux store from the React fiber tree, and dispatching the playConsumable action to switch channels. The fallback uses logo
|
|
104
|
-
// aria-label matching with DOM click (coordinate clicks are blocked by an invisible overlay). Uses selectReadyVideo because the page has multiple video elements.
|
|
105
|
-
directvStream: {
|
|
106
|
-
category: "multiChannel",
|
|
107
|
-
channelSelection: { strategy: "directvGrid" },
|
|
108
|
-
description: "DirecTV Stream with direct tune via webpack injection. Set Channel Selector to the channel name as shown in the guide (e.g., CNN, ESPN, NBC).",
|
|
109
|
-
extends: "fullscreenApi",
|
|
110
|
-
selectReadyVideo: true,
|
|
111
|
-
summary: "DirecTV Stream (guide grid, needs selector)"
|
|
112
|
-
},
|
|
113
|
-
// Profile for DisneyNOW (disneynow.com) which has a play button overlay that must be clicked to start playback and multiple video elements on the page.
|
|
114
|
-
disneyNow: {
|
|
115
|
-
category: "api",
|
|
116
|
-
clickSelector: ".overlay__button button",
|
|
117
|
-
description: "DisneyNOW player with play button overlay and multiple video elements.",
|
|
118
|
-
extends: "clickToPlayApi",
|
|
119
|
-
selectReadyVideo: true,
|
|
120
|
-
summary: "DisneyNOW player"
|
|
121
|
-
},
|
|
122
|
-
// Profile for Disney+ live channels. The live channel shelf displays tiles with network logos. Clicking a tile opens an entity modal with a "WATCH LIVE" button
|
|
123
|
-
// (playSelector) that must be clicked to start the stream. Extends fullscreenApi for requestFullscreen() behavior. The player uses Web Components with Shadow DOM
|
|
124
|
-
// for its controls — the native <toggle-fullscreen> button cannot be clicked by Puppeteer (Shadow DOM boundary), so fullscreen is handled entirely by the inherited
|
|
125
|
-
// requestFullscreen() API. The controls toolbar is hidden via hideSelector to prevent it from appearing in the captured stream. Uses selectReadyVideo because the
|
|
126
|
-
// page has multiple video elements (previews, ads, main content).
|
|
127
|
-
disneyPlus: {
|
|
128
|
-
category: "multiChannel",
|
|
129
|
-
channelSelection: {
|
|
130
|
-
matchSelector: "img[src*=\"{channel}\" i]", playSelector: "[data-testid=\"live-modal-watch-live-action-button\"]",
|
|
131
|
-
scrollToBottom: true, strategy: "tileClick"
|
|
132
|
-
},
|
|
133
|
-
description: "Disney+ live channels with tile selection and play button modal. Channel Selector is interpolated into matchSelector to find the element.",
|
|
134
|
-
extends: "fullscreenApi",
|
|
135
|
-
hideSelector: ".controls__footer__wrapper",
|
|
136
|
-
selectReadyVideo: true,
|
|
137
|
-
summary: "Disney+ (tile + play button, needs selector)"
|
|
138
|
-
},
|
|
139
103
|
// Profile for iframe-embedded players that also have multiple video elements (ads, placeholders, main content) and need network activity to settle. The
|
|
140
104
|
// selectReadyVideo flag ensures we find the video with actual content rather than an ad placeholder. Combines iframe handling with API-based fullscreen.
|
|
141
105
|
embeddedDynamicMultiVideo: {
|
|
@@ -165,17 +129,6 @@ export const SITE_PROFILES = {
|
|
|
165
129
|
lockVolumeProperties: true,
|
|
166
130
|
summary: "Embedded players that auto-mute"
|
|
167
131
|
},
|
|
168
|
-
// Profile for Fox.com live channel guide grid. The guide page presents all channels in a non-virtualized grid with station codes in the channel logo button
|
|
169
|
-
// titles (e.g., FOXD2C, FNC, FS1). The channelSelector property matches against these station codes. Clicking the channel logo button is an SPA state
|
|
170
|
-
// transition — the player at the top of the page switches channels without navigation. The grid renders dynamically after page load, so the strategy waits
|
|
171
|
-
// for GuideChannelContainer elements before scanning.
|
|
172
|
-
foxLive: {
|
|
173
|
-
category: "multiChannel",
|
|
174
|
-
channelSelection: { strategy: "foxGrid" },
|
|
175
|
-
description: "Fox.com live channel guide. Set Channel Selector to the station code (e.g., BTN, FOXD2C, FS1).",
|
|
176
|
-
extends: "fullscreenApi",
|
|
177
|
-
summary: "Fox Live (guide grid, needs selector)"
|
|
178
|
-
},
|
|
179
132
|
// Base profile for sites that require the JavaScript fullscreen API (element.requestFullscreen()) instead of keyboard shortcuts. Many modern players intercept
|
|
180
133
|
// keyboard events for their own controls, making the f key unreliable. Calling requestFullscreen() directly on the video element bypasses the player's keyboard
|
|
181
134
|
// handling and reliably enters fullscreen mode.
|
|
@@ -185,31 +138,6 @@ export const SITE_PROFILES = {
|
|
|
185
138
|
summary: "Sites needing JavaScript fullscreen",
|
|
186
139
|
useRequestFullscreen: true
|
|
187
140
|
},
|
|
188
|
-
// Profile for HBO Max live channels (play.hbomax.com). The HBO brand page contains a "Distribution Channels" rail showing all 5 live linear channels (HBO, HBO
|
|
189
|
-
// Hits, HBO Drama, HBO Comedy, HBO Movies) as tiles. The hboGrid strategy discovers the HBO tab URL from the homepage menu bar, navigates to it, then scrapes the
|
|
190
|
-
// channel rail for the watch URL matching the channelSelector name. Extends fullscreenApi for requestFullscreen() behavior inherited by the watch page.
|
|
191
|
-
hboMax: {
|
|
192
|
-
category: "multiChannel",
|
|
193
|
-
channelSelection: { strategy: "hboGrid" },
|
|
194
|
-
description: "HBO Max with live channel rail selection. Set Channel Selector to the channel name (e.g., HBO, HBO Hits).",
|
|
195
|
-
extends: "fullscreenApi",
|
|
196
|
-
summary: "HBO Max (live channels, needs selector)"
|
|
197
|
-
},
|
|
198
|
-
// Profile for Hulu Live TV which presents a guide grid of live channels. The channel list is revealed by clicking a tab (listSelector), then the desired channel
|
|
199
|
-
// is found by matching img.alt text. Uses the fullscreen API (inherited from fullscreenApi) plus a dedicated fullscreen button selector for the player's native
|
|
200
|
-
// maximize control. Requires selectReadyVideo because the page may have multiple video elements (ads, previews, main content). Uses waitForNetworkIdle because
|
|
201
|
-
// Hulu's SPA has heavy async initialization that often prevents the load event from firing within the retryOperation timeout; the graceful networkidle2 fallback
|
|
202
|
-
// in navigateToPage() allows execution to continue to channel selection even when background requests are still pending.
|
|
203
|
-
huluLive: {
|
|
204
|
-
category: "multiChannel",
|
|
205
|
-
channelSelection: { listSelector: "#CHANNELS", playSelector: "[data-testid=\"generic-tile-thumbnail\"]", strategy: "guideGrid" },
|
|
206
|
-
description: "Hulu Live TV with guide grid channel selection. Set Channel Selector to the channel's internal guide name (may differ from logo).",
|
|
207
|
-
extends: "fullscreenApi",
|
|
208
|
-
fullscreenSelector: "[aria-label=\"Maximize\"]",
|
|
209
|
-
selectReadyVideo: true,
|
|
210
|
-
summary: "Hulu Live TV (guide grid, needs selector)",
|
|
211
|
-
waitForNetworkIdle: true
|
|
212
|
-
},
|
|
213
141
|
// Profile for sites that use keyboard fullscreen and also need time for network activity to settle before the player is fully initialized. These sites dynamically
|
|
214
142
|
// load their player and content. The waitForNetworkIdle flag ensures we don't try to interact with the player until all initial network requests have completed.
|
|
215
143
|
keyboardDynamic: {
|
|
@@ -256,29 +184,6 @@ export const SITE_PROFILES = {
|
|
|
256
184
|
selectReadyVideo: true,
|
|
257
185
|
summary: "Multi-video sites ('f' key fullscreen)"
|
|
258
186
|
},
|
|
259
|
-
// Profile for Sling TV (watch.sling.com) live guide grid. The A-Z guide at /dashboard/grid_guide/grid_guide_a_z renders a virtualized grid of ~638 rows
|
|
260
|
-
// (120px each) with channel identification via data-testid="channel-{NAME}" attributes. The slingGrid strategy performs binary search on .guide-cell scrollTop
|
|
261
|
-
// to locate the target channel, then clicks the on-now program cell which navigates to a player page where a single <video> element auto-plays. Extends
|
|
262
|
-
// fullscreenApi for requestFullscreen() behavior. Does not use waitForNetworkIdle — the strategy's own waitForSelector on channel entries is the readiness
|
|
263
|
-
// signal, and the SPA's persistent connections would delay network idle unnecessarily.
|
|
264
|
-
slingLive: {
|
|
265
|
-
category: "multiChannel",
|
|
266
|
-
channelSelection: { strategy: "slingGrid" },
|
|
267
|
-
description: "Sling TV with guide grid channel selection. Set Channel Selector to the channel's internal guide name (may differ from logo).",
|
|
268
|
-
extends: "fullscreenApi",
|
|
269
|
-
summary: "Sling TV (guide grid, needs selector)"
|
|
270
|
-
},
|
|
271
|
-
// Profile for Spectrum TV (watch.spectrum.net) live guide grid. The guide page at /guide presents all ~442 streamable channels in a non-virtualized AngularJS
|
|
272
|
-
// DOM. Channel headers provide callsigns, channel numbers, and Gracenote station IDs (tmsid from logo image URLs). The spectrumGrid strategy reads all channels
|
|
273
|
-
// in a single evaluate pass, caches them, and navigates directly to /livetv?tmsid={stationId} — no clicking, no SPA state changes, no overlays. The channelSelector
|
|
274
|
-
// matches against clean channel names (e.g., "ESPN", "CNN", "NBC") with callsign suffix tolerance and affiliate network name resolution.
|
|
275
|
-
spectrum: {
|
|
276
|
-
category: "multiChannel",
|
|
277
|
-
channelSelection: { strategy: "spectrumGrid" },
|
|
278
|
-
description: "Spectrum TV with guide grid channel selection. Set Channel Selector to the channel name (e.g., ESPN, CNN, NBC).",
|
|
279
|
-
extends: "fullscreenApi",
|
|
280
|
-
summary: "Spectrum TV (guide grid, needs selector)"
|
|
281
|
-
},
|
|
282
187
|
// Profile for non-video pages that should be captured as static visual content. Examples include weather displays (weatherscan.net), maps (windy.com), and
|
|
283
188
|
// diagnostic pages. The noVideo flag tells the streaming code not to wait for a video element or set up playback monitoring - just capture whatever is displayed.
|
|
284
189
|
staticPage: {
|
|
@@ -286,19 +191,39 @@ export const SITE_PROFILES = {
|
|
|
286
191
|
description: "Base profile for non-video pages captured as static visual content.",
|
|
287
192
|
noVideo: true,
|
|
288
193
|
summary: "Static pages (no video)"
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
/* Provider profiles are internal profiles tied to specific provider modules. They have channel selection strategies, selectors, and flags tightly coupled to a
|
|
197
|
+
* specific streaming service's DOM structure and are not user-selectable. They live in a separate table from SITE_PROFILES so that UI-facing code (profile
|
|
198
|
+
* dropdowns, provider wizard, profile validation) can show only general-purpose profiles without maintaining an exclusion list. The extends mechanism works across
|
|
199
|
+
* both tables — a provider profile can extend a general profile (e.g., fullscreenApi) and profile resolution checks both tables transparently.
|
|
200
|
+
*/
|
|
201
|
+
export const PROVIDER_PROFILES = {
|
|
202
|
+
// Profile for DisneyNOW (disneynow.com) which has a play button overlay that must be clicked to start playback and multiple video elements on the page.
|
|
203
|
+
disneyNow: {
|
|
204
|
+
category: "api",
|
|
205
|
+
clickSelector: ".overlay__button button",
|
|
206
|
+
description: "DisneyNOW player with play button overlay and multiple video elements.",
|
|
207
|
+
extends: "clickToPlayApi",
|
|
208
|
+
selectReadyVideo: true,
|
|
209
|
+
summary: "DisneyNOW player"
|
|
289
210
|
},
|
|
290
|
-
// Profile for
|
|
291
|
-
//
|
|
292
|
-
//
|
|
293
|
-
//
|
|
294
|
-
//
|
|
295
|
-
|
|
211
|
+
// Profile for Disney+ live channels. The live channel shelf displays tiles with network logos. Clicking a tile opens an entity modal with a "WATCH LIVE" button
|
|
212
|
+
// (playSelector) that must be clicked to start the stream. Extends fullscreenApi for requestFullscreen() behavior. The player uses Web Components with Shadow DOM
|
|
213
|
+
// for its controls — the native <toggle-fullscreen> button cannot be clicked by Puppeteer (Shadow DOM boundary), so fullscreen is handled entirely by the inherited
|
|
214
|
+
// requestFullscreen() API. The controls toolbar is hidden via hideSelector to prevent it from appearing in the captured stream. Uses selectReadyVideo because the
|
|
215
|
+
// page has multiple video elements (previews, ads, main content).
|
|
216
|
+
disneyPlus: {
|
|
296
217
|
category: "multiChannel",
|
|
297
|
-
channelSelection: {
|
|
298
|
-
|
|
218
|
+
channelSelection: {
|
|
219
|
+
matchSelector: "img[src*=\"{channel}\" i]", playSelector: "[data-testid=\"live-modal-watch-live-action-button\"]",
|
|
220
|
+
scrollToBottom: true, strategy: "tileClick"
|
|
221
|
+
},
|
|
222
|
+
description: "Disney+ live channels with tile selection and play button modal. Channel Selector is interpolated into matchSelector to find the element.",
|
|
299
223
|
extends: "fullscreenApi",
|
|
224
|
+
hideSelector: ".controls__footer__wrapper",
|
|
300
225
|
selectReadyVideo: true,
|
|
301
|
-
summary: "
|
|
226
|
+
summary: "Disney+ (tile + play button, needs selector)"
|
|
302
227
|
}
|
|
303
228
|
};
|
|
304
229
|
/* This mapping associates domain keys with site profiles, provider display names, and provider filter tags. Most keys are concise second-level domains
|
|
@@ -314,15 +239,16 @@ export const DOMAIN_CONFIG = {
|
|
|
314
239
|
"abc.com": { profile: "keyboardMultiVideo", provider: "ABC.com" },
|
|
315
240
|
"aetv.com": { profile: "fullscreenApi", provider: "A&E" },
|
|
316
241
|
"bet.com": { profile: "fullscreenApi", provider: "BET.com" },
|
|
317
|
-
"c-span.org": { profile: "brightcove", provider: "C-SPAN.org" },
|
|
318
|
-
"cbs.com": { profile: "keyboardIframe", provider: "CBS.com" },
|
|
242
|
+
"c-span.org": { dismissSelector: ".videoAdUiSkipButtonExperimentalText", profile: "brightcove", provider: "C-SPAN.org" },
|
|
243
|
+
"cbs.com": { dismissSelector: "#mvpd__getstarted", profile: "keyboardIframe", provider: "CBS.com" },
|
|
319
244
|
"cnbc.com": { profile: "fullscreenApi", provider: "CNBC.com" },
|
|
320
245
|
"cnn.com": { profile: "fullscreenApi", provider: "CNN.com" },
|
|
321
246
|
"disneynow.com": { profile: "disneyNow", provider: "DisneyNOW" },
|
|
322
|
-
"disneyplus.com": {
|
|
247
|
+
"disneyplus.com": { iconUrl: "https://static-assets.bamgrid.com/product/disneyplus/favicons/apple-touch-icon-aurora.d3af81fe0571b495a3c80ff8c3d0c8e7.png",
|
|
248
|
+
profile: "disneyPlus", provider: "Disney+", providerTag: "disneyplus" },
|
|
323
249
|
"espn.com": { profile: "keyboardMultiVideo", provider: "ESPN.com" },
|
|
324
250
|
"foodnetwork.com": { profile: "fullscreenApi", provider: "Food Network" },
|
|
325
|
-
"fox.com": { loginUrl: "https://www.fox.com", profile: "foxLive", provider: "Fox", providerTag: "
|
|
251
|
+
"fox.com": { loginUrl: "https://www.fox.com", profile: "foxLive", provider: "Fox One", providerTag: "foxone" },
|
|
326
252
|
"foxbusiness.com": { profile: "embeddedDynamicMultiVideo", provider: "Fox Business" },
|
|
327
253
|
"foxnews.com": { profile: "embeddedDynamicMultiVideo", provider: "Fox News" },
|
|
328
254
|
"foxsports.com": { profile: "fullscreenApi", provider: "Fox Sports" },
|
|
@@ -332,29 +258,35 @@ export const DOMAIN_CONFIG = {
|
|
|
332
258
|
"golfchannel.com": { profile: "fullscreenApi", provider: "Golf Channel" },
|
|
333
259
|
"hbomax.com": { profile: "hboMax", provider: "HBO Max", providerTag: "hbomax" },
|
|
334
260
|
"history.com": { profile: "fullscreenApi", provider: "History.com" },
|
|
335
|
-
"hulu.com": { profile: "huluLive", provider: "Hulu", providerTag: "hulu" },
|
|
261
|
+
"hulu.com": { iconUrl: "https://www.hulu.com/static/icons/apple-touch-icon.png", profile: "huluLive", provider: "Hulu", providerTag: "hulu" },
|
|
336
262
|
"lakeshorepbs.org": { profile: "embeddedPlayer", provider: "Lakeshore PBS" },
|
|
337
263
|
"ms.now": { profile: "keyboardDynamic", provider: "MS NOW" },
|
|
338
264
|
"mylifetime.com": { profile: "fullscreenApi", provider: "Lifetime" },
|
|
339
265
|
"nationalgeographic.com": { profile: "keyboardDynamicMultiVideo", provider: "Nat Geo" },
|
|
340
266
|
"nba.com": { profile: "fullscreenApi", provider: "NBA.com" },
|
|
341
267
|
"nbc.com": { maxContinuousPlayback: 4, profile: "keyboardDynamic", provider: "NBC.com" },
|
|
342
|
-
"paramountplus.com": {
|
|
268
|
+
"paramountplus.com": { dismissSelector: ".ppp-watch", iconUrl: "https://www.paramountplus.com/assets/images/pplus_App_Icon-Blue-144x144.png",
|
|
269
|
+
profile: "fullscreenApi", provider: "Paramount+", providerTag: "paramountplus" },
|
|
343
270
|
"sling.com": { profile: "embeddedVolumeLock", provider: "Sling TV" },
|
|
344
271
|
"starz.com": { profile: "fullscreenApi", provider: "Starz" },
|
|
345
272
|
"stream.directv.com": { loginUrl: "https://stream.directv.com", profile: "directvStream", provider: "DirecTV Stream", providerTag: "directv" },
|
|
346
273
|
"tbs.com": { profile: "fullscreenApi", provider: "TBS.com" },
|
|
347
274
|
"tntdrama.com": { profile: "fullscreenApi", provider: "TNT" },
|
|
348
275
|
"trutv.com": { profile: "fullscreenApi", provider: "truTV" },
|
|
349
|
-
"tv.youtube.com": { profile: "youtubeTV", provider: "YouTube TV", providerTag: "yttv" },
|
|
350
|
-
"usanetwork.com": {
|
|
276
|
+
"tv.youtube.com": { iconUrl: "https://www.youtube.com/yts/img/favicon_144-vfliLAfaB.png", profile: "youtubeTV", provider: "YouTube TV", providerTag: "yttv" },
|
|
277
|
+
"usanetwork.com": { iconUrl: "https://usanetwork.asset.viewlift.com/images/brand/2025/09/30/usa_favicon-1759230411162.ico",
|
|
278
|
+
profile: "keyboardDynamicMultiVideo", provider: "USA Network", providerTag: "usa" },
|
|
351
279
|
"vh1.com": { profile: "fullscreenApi", provider: "VH1.com" },
|
|
352
280
|
"watch.sling.com": { profile: "slingLive", provider: "Sling TV", providerTag: "sling" },
|
|
353
|
-
"watch.spectrum.net": {
|
|
281
|
+
"watch.spectrum.net": { iconUrl: "https://watch.spectrum.net/assets/17.28.0/images/apple-touch-icon.png", profile: "spectrum", provider: "Spectrum TV",
|
|
282
|
+
providerTag: "spectrum" },
|
|
354
283
|
"watchhallmarktv.com": { profile: "fullscreenApi", provider: "Hallmark" },
|
|
284
|
+
"watchtv.cox.com": { iconUrl: "https://watchtv.cox.com/partners/cox/images/favicon-128x128.png", profile: "coxStream", provider: "Cox Contour TV",
|
|
285
|
+
providerTag: "cox" },
|
|
355
286
|
"weatherscan.net": { profile: "staticPage", provider: "Weatherscan" },
|
|
356
287
|
"windy.com": { profile: "staticPage", provider: "Windy" },
|
|
357
288
|
"wttw.com": { profile: "fullscreenApi", provider: "WTTW" },
|
|
289
|
+
"xfinity.com": { profile: "xfinityStream", provider: "Xfinity Stream", providerTag: "xfinity" },
|
|
358
290
|
"youtube.com": { profile: "keyboardDynamic", provider: "YouTube" }
|
|
359
291
|
};
|
|
360
292
|
/**
|
|
@@ -391,6 +323,47 @@ export function getDomainConfig(url) {
|
|
|
391
323
|
}
|
|
392
324
|
return DOMAIN_CONFIG[conciseDomain];
|
|
393
325
|
}
|
|
326
|
+
// Provider module profiles registered at import time via registerProviderModuleProfile(). Provider modules define their profiles alongside their tuning code and
|
|
327
|
+
// register them here so the profile resolution system can find them without importing from browser/channelSelection.ts (which would create circular dependencies).
|
|
328
|
+
const providerModuleProfiles = new Map();
|
|
329
|
+
/**
|
|
330
|
+
* Registers a provider module's profile. Called by the coordinator in channelSelection.ts at module evaluation time to make provider profiles available to the
|
|
331
|
+
* profile resolution system. This avoids circular dependencies — sites.ts doesn't need to import from browser/ modules.
|
|
332
|
+
* @param name - The profile name (e.g., "huluLive", "slingLive").
|
|
333
|
+
* @param profile - The SiteProfile definition.
|
|
334
|
+
*/
|
|
335
|
+
export function registerProviderModuleProfile(name, profile) {
|
|
336
|
+
if ((name in SITE_PROFILES) || (name in PROVIDER_PROFILES)) {
|
|
337
|
+
throw new Error("Provider module profile '" + name + "' collides with an existing static profile. Use a unique profileName.");
|
|
338
|
+
}
|
|
339
|
+
providerModuleProfiles.set(name, profile);
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Looks up a built-in profile by name, checking the general SITE_PROFILES table, the static PROVIDER_PROFILES table, and dynamically registered provider module
|
|
343
|
+
* profiles. This is the single lookup function for all built-in profile resolution — callers should use this instead of accessing any table directly.
|
|
344
|
+
* @param name - The profile name to look up.
|
|
345
|
+
* @returns The matching SiteProfile, or undefined if not found in any source.
|
|
346
|
+
*/
|
|
347
|
+
export function getBuiltinProfile(name) {
|
|
348
|
+
return SITE_PROFILES[name] ?? PROVIDER_PROFILES[name] ?? providerModuleProfiles.get(name);
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Returns true if the given profile name is a provider-specific profile (either in the static PROVIDER_PROFILES table or registered by a provider module). Used
|
|
352
|
+
* by user profile validation to prevent users from extending provider-specific profiles that are tightly coupled to a streaming service's DOM structure.
|
|
353
|
+
* @param name - The profile name to check.
|
|
354
|
+
* @returns True if the profile is a provider profile.
|
|
355
|
+
*/
|
|
356
|
+
export function isProviderProfile(name) {
|
|
357
|
+
return (name in PROVIDER_PROFILES) || providerModuleProfiles.has(name);
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Returns all registered provider module profiles as an iterable of [name, profile] pairs. Used by the validation system to include dynamically registered
|
|
361
|
+
* profiles in inheritance chain checks.
|
|
362
|
+
* @returns Iterable of [name, SiteProfile] pairs.
|
|
363
|
+
*/
|
|
364
|
+
export function getRegisteredProviderModuleProfiles() {
|
|
365
|
+
return providerModuleProfiles.entries();
|
|
366
|
+
}
|
|
394
367
|
/* The default profile provides baseline behavior for sites not explicitly listed in the domain mapping or channel definitions. These settings work for most
|
|
395
368
|
* standard HTML5 video players that follow common conventions. Each flag is explicitly set to its default value for documentation purposes and to ensure
|
|
396
369
|
* predictable behavior - we don't rely on implicit defaults.
|
|
@@ -415,6 +388,8 @@ export const DEFAULT_SITE_PROFILE = {
|
|
|
415
388
|
clickSelector: null,
|
|
416
389
|
// Don't click to play - most sites start automatically or via other mechanisms.
|
|
417
390
|
clickToPlay: false,
|
|
391
|
+
// No dismiss selector - most sites don't show intermittent modals.
|
|
392
|
+
dismissSelector: null,
|
|
418
393
|
// No fullscreen key - many players work without explicit fullscreen.
|
|
419
394
|
fullscreenKey: null,
|
|
420
395
|
// No fullscreen button selector - most sites don't have a dedicated fullscreen button we need to click.
|
|
@@ -433,6 +408,8 @@ export const DEFAULT_SITE_PROFILE = {
|
|
|
433
408
|
selectReadyVideo: false,
|
|
434
409
|
// Don't use requestFullscreen() API.
|
|
435
410
|
useRequestFullscreen: false,
|
|
411
|
+
// No per-domain video timeout override - use the global default.
|
|
412
|
+
videoTimeout: null,
|
|
436
413
|
// Don't wait for network idle - assume player is ready on page load.
|
|
437
414
|
waitForNetworkIdle: false
|
|
438
415
|
};
|
package/dist/config/sites.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sites.js","sourceRoot":"","sources":["../../src/config/sites.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAEnD
|
|
1
|
+
{"version":3,"file":"sites.js","sourceRoot":"","sources":["../../src/config/sites.ts"],"names":[],"mappings":"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"}
|
|
@@ -39,8 +39,10 @@ export declare function getChannelsParseErrorMessage(): string | undefined;
|
|
|
39
39
|
export declare function loadUserChannels(): Promise<UserChannelsLoadResult>;
|
|
40
40
|
/**
|
|
41
41
|
* Saves user channels to the channels file and updates the in-memory cache. Changes take effect immediately for new stream requests without requiring a server
|
|
42
|
-
* restart. Creates the data directory if it doesn't exist. Provider selections are also saved if any exist.
|
|
43
|
-
*
|
|
42
|
+
* restart. Creates the data directory if it doesn't exist. Provider selections are also saved if any exist. No-op deltas for predefined channel keys are
|
|
43
|
+
* normalized before saving: fields that match the predefined value or null-clear a field the predefined doesn't have are stripped. If the delta becomes empty
|
|
44
|
+
* after normalization, the entry is removed entirely. This ensures that any code path that writes deltas (inline edit, auto-number, browse modal, full edit)
|
|
45
|
+
* produces clean channels.json output without each handler needing to optimize its own delta.
|
|
44
46
|
* @param channels - The channels to save (full definitions or delta overrides).
|
|
45
47
|
* @throws If the file cannot be written.
|
|
46
48
|
*/
|
|
@@ -85,6 +87,9 @@ export declare function resolveStoredChannel(key: string, stored: StoredChannel)
|
|
|
85
87
|
*
|
|
86
88
|
* Override entries produce a new resolved Channel object (via resolveStoredChannel()), which is a different reference from PREDEFINED_CHANNELS[key]. The provider
|
|
87
89
|
* system (providers.ts) relies on this reference difference to detect user overrides via isUserOverride(). Predefined-only entries preserve the original reference.
|
|
90
|
+
*
|
|
91
|
+
* The returned channel field is provider-resolved: when a non-default provider is selected for a channel, the entry's channel reflects the selected variant's URL,
|
|
92
|
+
* channelSelector, stationId, and channelNumber. The entry's key always remains the canonical key.
|
|
88
93
|
* @returns Sorted array of channel listing entries.
|
|
89
94
|
*/
|
|
90
95
|
export declare function getChannelListing(): ChannelListingEntry[];
|
|
@@ -111,6 +116,44 @@ export declare function getPredefinedChannel(key: string): Channel | undefined;
|
|
|
111
116
|
* @returns True if the channel is predefined.
|
|
112
117
|
*/
|
|
113
118
|
export declare function isPredefinedChannel(key: string): boolean;
|
|
119
|
+
/**
|
|
120
|
+
* Returns the East canonical key for a Pacific channel. Pacific canonicals are auto-generated by generatePacificDefinitions with a "p" suffix (e.g., "disneyp"
|
|
121
|
+
* from "disney"). When a Pacific canonical's logo or other brand-level metadata is unavailable, the East counterpart provides a fallback. Returns undefined
|
|
122
|
+
* when the key is not a Pacific canonical (doesn't end with "p" or the base key isn't predefined).
|
|
123
|
+
* @param key - The channel key to check.
|
|
124
|
+
* @returns The East canonical key if this is a Pacific channel, undefined otherwise.
|
|
125
|
+
*/
|
|
126
|
+
export declare function getEastCanonicalKey(key: string): string | undefined;
|
|
127
|
+
/**
|
|
128
|
+
* Bulk-populates the logo cache from a stationId-to-URL map. Called by showInfo.ts after fetching logo data from the DVR. Existing entries are overwritten with
|
|
129
|
+
* fresh data to pick up any logo URL changes (e.g., network rebrands).
|
|
130
|
+
* @param logos - Map of Gracenote station ID to logo URL.
|
|
131
|
+
*/
|
|
132
|
+
export declare function setChannelLogos(logos: Map<string, string>): void;
|
|
133
|
+
/**
|
|
134
|
+
* Sets a single logo cache entry. Called by showInfo.ts after a TMS station name search for a single channel (tier 2 population or channel add/edit).
|
|
135
|
+
* @param stationId - The Gracenote station ID.
|
|
136
|
+
* @param url - The logo URL.
|
|
137
|
+
*/
|
|
138
|
+
export declare function setChannelLogo(stationId: string, url: string): void;
|
|
139
|
+
/**
|
|
140
|
+
* Clears the logo cache. Called on shutdown to release memory.
|
|
141
|
+
*/
|
|
142
|
+
export declare function clearChannelLogos(): void;
|
|
143
|
+
/**
|
|
144
|
+
* Resolves a channel key to its effective Gracenote station ID. Pacific channels (e.g., "bravop") resolve to their east counterpart's station ID since
|
|
145
|
+
* they share the same brand logo. User channel overrides that change the stationId are reflected. Returns undefined if the channel has no stationId.
|
|
146
|
+
* @param channelKey - The channel key to resolve (e.g., "amc", "bravop").
|
|
147
|
+
* @returns The Gracenote station ID, or undefined.
|
|
148
|
+
*/
|
|
149
|
+
export declare function getChannelStationId(channelKey: string): string | undefined;
|
|
150
|
+
/**
|
|
151
|
+
* Returns the logo URL for a channel from the cache. Pacific channels (e.g., "bravop") are resolved to their east counterpart automatically. The cache is
|
|
152
|
+
* populated by showInfo.ts from the Channels DVR API - this function only reads from it.
|
|
153
|
+
* @param channelKey - The channel key to look up (e.g., "amc", "bravop"). Pacific keys resolve to their east variant internally.
|
|
154
|
+
* @returns The logo URL if cached, undefined otherwise.
|
|
155
|
+
*/
|
|
156
|
+
export declare function getChannelLogo(channelKey: string): string | undefined;
|
|
114
157
|
/**
|
|
115
158
|
* Checks if a channel key exists in the user channels.
|
|
116
159
|
* @param key - The channel key to check.
|
|
@@ -180,6 +223,14 @@ export declare function isChannelAvailable(key: string): boolean;
|
|
|
180
223
|
* @returns Error message if invalid, undefined if valid.
|
|
181
224
|
*/
|
|
182
225
|
export declare function validateChannelKey(key: string, isNew: boolean): string | undefined;
|
|
226
|
+
/**
|
|
227
|
+
* Validates a channel number for range and uniqueness. Returns an error message if invalid, undefined if valid. Empty string means no channel number (valid).
|
|
228
|
+
* This is the single source of truth for channel number validation, shared by the full edit handler and the inline-edit handler.
|
|
229
|
+
* @param value - The channel number as a string (from form input or inline edit). Empty means "no number."
|
|
230
|
+
* @param excludeKey - The channel key being edited, excluded from the duplicate check.
|
|
231
|
+
* @returns Error message if invalid, undefined if valid.
|
|
232
|
+
*/
|
|
233
|
+
export declare function validateChannelNumber(value: string, excludeKey: string): string | undefined;
|
|
183
234
|
/**
|
|
184
235
|
* Validates a channel URL.
|
|
185
236
|
* @param url - The URL to validate.
|