@bnhf/prismcast 1.3.4-2026.2.19

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 (234) hide show
  1. package/LICENSE.md +7 -0
  2. package/README.md +347 -0
  3. package/bin/prismcast +6 -0
  4. package/dist/app.d.ts +6 -0
  5. package/dist/app.js +315 -0
  6. package/dist/app.js.map +1 -0
  7. package/dist/browser/cdp.d.ts +38 -0
  8. package/dist/browser/cdp.js +155 -0
  9. package/dist/browser/cdp.js.map +1 -0
  10. package/dist/browser/channelSelection.d.ts +65 -0
  11. package/dist/browser/channelSelection.js +202 -0
  12. package/dist/browser/channelSelection.js.map +1 -0
  13. package/dist/browser/display.d.ts +34 -0
  14. package/dist/browser/display.js +54 -0
  15. package/dist/browser/display.js.map +1 -0
  16. package/dist/browser/index.d.ts +205 -0
  17. package/dist/browser/index.js +1205 -0
  18. package/dist/browser/index.js.map +1 -0
  19. package/dist/browser/tuning/fox.d.ts +2 -0
  20. package/dist/browser/tuning/fox.js +83 -0
  21. package/dist/browser/tuning/fox.js.map +1 -0
  22. package/dist/browser/tuning/hbo.d.ts +2 -0
  23. package/dist/browser/tuning/hbo.js +237 -0
  24. package/dist/browser/tuning/hbo.js.map +1 -0
  25. package/dist/browser/tuning/hulu.d.ts +2 -0
  26. package/dist/browser/tuning/hulu.js +550 -0
  27. package/dist/browser/tuning/hulu.js.map +1 -0
  28. package/dist/browser/tuning/sling.d.ts +2 -0
  29. package/dist/browser/tuning/sling.js +518 -0
  30. package/dist/browser/tuning/sling.js.map +1 -0
  31. package/dist/browser/tuning/thumbnailRow.d.ts +2 -0
  32. package/dist/browser/tuning/thumbnailRow.js +108 -0
  33. package/dist/browser/tuning/thumbnailRow.js.map +1 -0
  34. package/dist/browser/tuning/tileClick.d.ts +2 -0
  35. package/dist/browser/tuning/tileClick.js +103 -0
  36. package/dist/browser/tuning/tileClick.js.map +1 -0
  37. package/dist/browser/tuning/youtubeTv.d.ts +2 -0
  38. package/dist/browser/tuning/youtubeTv.js +182 -0
  39. package/dist/browser/tuning/youtubeTv.js.map +1 -0
  40. package/dist/browser/video.d.ts +289 -0
  41. package/dist/browser/video.js +996 -0
  42. package/dist/browser/video.js.map +1 -0
  43. package/dist/channels/index.d.ts +3 -0
  44. package/dist/channels/index.js +392 -0
  45. package/dist/channels/index.js.map +1 -0
  46. package/dist/config/index.d.ts +53 -0
  47. package/dist/config/index.js +233 -0
  48. package/dist/config/index.js.map +1 -0
  49. package/dist/config/presets.d.ts +98 -0
  50. package/dist/config/presets.js +241 -0
  51. package/dist/config/presets.js.map +1 -0
  52. package/dist/config/profiles.d.ts +79 -0
  53. package/dist/config/profiles.js +245 -0
  54. package/dist/config/profiles.js.map +1 -0
  55. package/dist/config/providers.d.ts +120 -0
  56. package/dist/config/providers.js +450 -0
  57. package/dist/config/providers.js.map +1 -0
  58. package/dist/config/sites.d.ts +22 -0
  59. package/dist/config/sites.js +377 -0
  60. package/dist/config/sites.js.map +1 -0
  61. package/dist/config/userChannels.d.ts +178 -0
  62. package/dist/config/userChannels.js +543 -0
  63. package/dist/config/userChannels.js.map +1 -0
  64. package/dist/config/userConfig.d.ts +235 -0
  65. package/dist/config/userConfig.js +913 -0
  66. package/dist/config/userConfig.js.map +1 -0
  67. package/dist/hdhr/channelMap.d.ts +21 -0
  68. package/dist/hdhr/channelMap.js +82 -0
  69. package/dist/hdhr/channelMap.js.map +1 -0
  70. package/dist/hdhr/deviceId.d.ts +11 -0
  71. package/dist/hdhr/deviceId.js +84 -0
  72. package/dist/hdhr/deviceId.js.map +1 -0
  73. package/dist/hdhr/discover.d.ts +6 -0
  74. package/dist/hdhr/discover.js +155 -0
  75. package/dist/hdhr/discover.js.map +1 -0
  76. package/dist/hdhr/index.d.ts +9 -0
  77. package/dist/hdhr/index.js +87 -0
  78. package/dist/hdhr/index.js.map +1 -0
  79. package/dist/index.d.ts +1 -0
  80. package/dist/index.js +144 -0
  81. package/dist/index.js.map +1 -0
  82. package/dist/routes/assets.d.ts +6 -0
  83. package/dist/routes/assets.js +79 -0
  84. package/dist/routes/assets.js.map +1 -0
  85. package/dist/routes/auth.d.ts +6 -0
  86. package/dist/routes/auth.js +77 -0
  87. package/dist/routes/auth.js.map +1 -0
  88. package/dist/routes/channels.d.ts +6 -0
  89. package/dist/routes/channels.js +40 -0
  90. package/dist/routes/channels.js.map +1 -0
  91. package/dist/routes/components.d.ts +138 -0
  92. package/dist/routes/components.js +210 -0
  93. package/dist/routes/components.js.map +1 -0
  94. package/dist/routes/config.d.ts +72 -0
  95. package/dist/routes/config.js +1977 -0
  96. package/dist/routes/config.js.map +1 -0
  97. package/dist/routes/debug.d.ts +6 -0
  98. package/dist/routes/debug.js +274 -0
  99. package/dist/routes/debug.js.map +1 -0
  100. package/dist/routes/health.d.ts +6 -0
  101. package/dist/routes/health.js +85 -0
  102. package/dist/routes/health.js.map +1 -0
  103. package/dist/routes/hls.d.ts +6 -0
  104. package/dist/routes/hls.js +25 -0
  105. package/dist/routes/hls.js.map +1 -0
  106. package/dist/routes/index.d.ts +19 -0
  107. package/dist/routes/index.js +49 -0
  108. package/dist/routes/index.js.map +1 -0
  109. package/dist/routes/logs.d.ts +6 -0
  110. package/dist/routes/logs.js +164 -0
  111. package/dist/routes/logs.js.map +1 -0
  112. package/dist/routes/mpegts.d.ts +6 -0
  113. package/dist/routes/mpegts.js +19 -0
  114. package/dist/routes/mpegts.js.map +1 -0
  115. package/dist/routes/play.d.ts +6 -0
  116. package/dist/routes/play.js +18 -0
  117. package/dist/routes/play.js.map +1 -0
  118. package/dist/routes/playlist.d.ts +36 -0
  119. package/dist/routes/playlist.js +134 -0
  120. package/dist/routes/playlist.js.map +1 -0
  121. package/dist/routes/root.d.ts +6 -0
  122. package/dist/routes/root.js +2920 -0
  123. package/dist/routes/root.js.map +1 -0
  124. package/dist/routes/streams.d.ts +6 -0
  125. package/dist/routes/streams.js +88 -0
  126. package/dist/routes/streams.js.map +1 -0
  127. package/dist/routes/theme.d.ts +15 -0
  128. package/dist/routes/theme.js +275 -0
  129. package/dist/routes/theme.js.map +1 -0
  130. package/dist/routes/ui.d.ts +56 -0
  131. package/dist/routes/ui.js +354 -0
  132. package/dist/routes/ui.js.map +1 -0
  133. package/dist/service/commands.d.ts +41 -0
  134. package/dist/service/commands.js +391 -0
  135. package/dist/service/commands.js.map +1 -0
  136. package/dist/service/generators.d.ts +33 -0
  137. package/dist/service/generators.js +432 -0
  138. package/dist/service/generators.js.map +1 -0
  139. package/dist/service/index.d.ts +2 -0
  140. package/dist/service/index.js +7 -0
  141. package/dist/service/index.js.map +1 -0
  142. package/dist/streaming/clients.d.ts +48 -0
  143. package/dist/streaming/clients.js +114 -0
  144. package/dist/streaming/clients.js.map +1 -0
  145. package/dist/streaming/fmp4Segmenter.d.ts +61 -0
  146. package/dist/streaming/fmp4Segmenter.js +461 -0
  147. package/dist/streaming/fmp4Segmenter.js.map +1 -0
  148. package/dist/streaming/hls.d.ts +120 -0
  149. package/dist/streaming/hls.js +722 -0
  150. package/dist/streaming/hls.js.map +1 -0
  151. package/dist/streaming/hlsSegments.d.ts +54 -0
  152. package/dist/streaming/hlsSegments.js +162 -0
  153. package/dist/streaming/hlsSegments.js.map +1 -0
  154. package/dist/streaming/lifecycle.d.ts +33 -0
  155. package/dist/streaming/lifecycle.js +185 -0
  156. package/dist/streaming/lifecycle.js.map +1 -0
  157. package/dist/streaming/monitor.d.ts +74 -0
  158. package/dist/streaming/monitor.js +1310 -0
  159. package/dist/streaming/monitor.js.map +1 -0
  160. package/dist/streaming/mp4Parser.d.ts +74 -0
  161. package/dist/streaming/mp4Parser.js +566 -0
  162. package/dist/streaming/mp4Parser.js.map +1 -0
  163. package/dist/streaming/mpegts.d.ts +14 -0
  164. package/dist/streaming/mpegts.js +248 -0
  165. package/dist/streaming/mpegts.js.map +1 -0
  166. package/dist/streaming/registry.d.ts +119 -0
  167. package/dist/streaming/registry.js +127 -0
  168. package/dist/streaming/registry.js.map +1 -0
  169. package/dist/streaming/setup.d.ts +135 -0
  170. package/dist/streaming/setup.js +670 -0
  171. package/dist/streaming/setup.js.map +1 -0
  172. package/dist/streaming/showInfo.d.ts +30 -0
  173. package/dist/streaming/showInfo.js +362 -0
  174. package/dist/streaming/showInfo.js.map +1 -0
  175. package/dist/streaming/statusEmitter.d.ts +125 -0
  176. package/dist/streaming/statusEmitter.js +139 -0
  177. package/dist/streaming/statusEmitter.js.map +1 -0
  178. package/dist/types/index.d.ts +403 -0
  179. package/dist/types/index.js +6 -0
  180. package/dist/types/index.js.map +1 -0
  181. package/dist/utils/debugFilter.d.ts +38 -0
  182. package/dist/utils/debugFilter.js +157 -0
  183. package/dist/utils/debugFilter.js.map +1 -0
  184. package/dist/utils/delay.d.ts +6 -0
  185. package/dist/utils/delay.js +15 -0
  186. package/dist/utils/delay.js.map +1 -0
  187. package/dist/utils/errors.d.ts +15 -0
  188. package/dist/utils/errors.js +40 -0
  189. package/dist/utils/errors.js.map +1 -0
  190. package/dist/utils/evaluate.d.ts +51 -0
  191. package/dist/utils/evaluate.js +124 -0
  192. package/dist/utils/evaluate.js.map +1 -0
  193. package/dist/utils/ffmpeg.d.ts +65 -0
  194. package/dist/utils/ffmpeg.js +317 -0
  195. package/dist/utils/ffmpeg.js.map +1 -0
  196. package/dist/utils/fileLogger.d.ts +25 -0
  197. package/dist/utils/fileLogger.js +248 -0
  198. package/dist/utils/fileLogger.js.map +1 -0
  199. package/dist/utils/format.d.ts +16 -0
  200. package/dist/utils/format.js +46 -0
  201. package/dist/utils/format.js.map +1 -0
  202. package/dist/utils/html.d.ts +6 -0
  203. package/dist/utils/html.js +24 -0
  204. package/dist/utils/html.js.map +1 -0
  205. package/dist/utils/index.d.ts +15 -0
  206. package/dist/utils/index.js +20 -0
  207. package/dist/utils/index.js.map +1 -0
  208. package/dist/utils/logEmitter.d.ts +17 -0
  209. package/dist/utils/logEmitter.js +30 -0
  210. package/dist/utils/logEmitter.js.map +1 -0
  211. package/dist/utils/logger.d.ts +82 -0
  212. package/dist/utils/logger.js +219 -0
  213. package/dist/utils/logger.js.map +1 -0
  214. package/dist/utils/m3u.d.ts +32 -0
  215. package/dist/utils/m3u.js +148 -0
  216. package/dist/utils/m3u.js.map +1 -0
  217. package/dist/utils/morganStream.d.ts +7 -0
  218. package/dist/utils/morganStream.js +33 -0
  219. package/dist/utils/morganStream.js.map +1 -0
  220. package/dist/utils/platform.d.ts +64 -0
  221. package/dist/utils/platform.js +157 -0
  222. package/dist/utils/platform.js.map +1 -0
  223. package/dist/utils/retry.d.ts +15 -0
  224. package/dist/utils/retry.js +82 -0
  225. package/dist/utils/retry.js.map +1 -0
  226. package/dist/utils/streamContext.d.ts +28 -0
  227. package/dist/utils/streamContext.js +33 -0
  228. package/dist/utils/streamContext.js.map +1 -0
  229. package/dist/utils/version.d.ts +37 -0
  230. package/dist/utils/version.js +228 -0
  231. package/dist/utils/version.js.map +1 -0
  232. package/package.json +92 -0
  233. package/prismcast.png +0 -0
  234. package/prismcast.svg +74 -0
@@ -0,0 +1,377 @@
1
+ import { extractDomain } from "../utils/index.js";
2
+ /* Streaming sites implement their video players in wildly different ways. Some use standard HTML5 video with keyboard shortcuts, others embed players in iframes,
3
+ * and many have unique quirks like auto-muting or requiring specific fullscreen methods. Rather than scattering site-specific conditionals throughout the streaming
4
+ * code, we define "site profiles" that describe each site's behavior in a declarative way.
5
+ *
6
+ * The profile system has three components:
7
+ *
8
+ * 1. SITE_PROFILES: Named behavior configurations describing how to handle different player implementations. Profiles can inherit from other profiles using the
9
+ * "extends" property, allowing us to define base profiles for common patterns (like "keyboardFullscreen" for sites using the f key) and then extend them with
10
+ * site-specific variations.
11
+ *
12
+ * 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
13
+ * use the corresponding profile. Provider display names give friendly labels (e.g., "Hulu" instead of "hulu.com") for the UI source column and provider
14
+ * dropdowns. This is the primary mechanism for automatically selecting the right behavior and generating friendly display names.
15
+ *
16
+ * 3. Channel-level profile hints: Individual channel definitions can specify an explicit profile name, overriding URL-based detection. This is useful when a
17
+ * channel's URL doesn't match the expected domain pattern, or when the same domain serves multiple channel types that need different handling.
18
+ *
19
+ * Profile resolution happens at stream startup and the resolved profile is passed through the entire streaming pipeline. The profile flags control:
20
+ * - How fullscreen is triggered (keyboard shortcut vs JavaScript API)
21
+ * - Whether to search for video elements in iframes
22
+ * - Which video element to select when multiple exist
23
+ * - Whether to wait for network activity to settle before playback
24
+ * - Whether to lock volume properties to prevent auto-muting
25
+ * - Whether the page is static content (no video element expected)
26
+ *
27
+ * When adding support for a new streaming site, first check if an existing profile matches its behavior. Only create a new profile if the site requires unique
28
+ * handling not covered by existing profiles.
29
+ */
30
+ /* Each profile defines a set of behavior flags that control how we interact with the video player. Profiles are organized in an inheritance hierarchy based on
31
+ * behavior patterns rather than site ownership. This makes it easier to identify the right profile when adding new channels.
32
+ *
33
+ * Base profiles (no extends):
34
+ * - keyboardFullscreen: Sites using the f key for fullscreen toggle
35
+ * - fullscreenApi: Sites requiring the JavaScript requestFullscreen() API
36
+ * - staticPage: Non-video pages captured as static visual content
37
+ *
38
+ * Derived profiles (extends a base):
39
+ * - keyboardDynamic: Keyboard fullscreen + network idle wait (extends keyboardFullscreen)
40
+ * - keyboardMultiVideo: Keyboard fullscreen + multi-video selection (extends keyboardFullscreen)
41
+ * - keyboardIframe: Keyboard fullscreen + iframe handling (extends keyboardFullscreen)
42
+ * - keyboardDynamicMultiVideo: Keyboard + network idle + multi-video selection (extends keyboardDynamic)
43
+ * - clickToPlayKeyboard: Click to start playback + keyboard fullscreen (extends keyboardFullscreen)
44
+ * - brightcove: Brightcove players using API fullscreen + network idle wait (extends fullscreenApi)
45
+ * - clickToPlayApi: Click to start playback + API fullscreen (extends fullscreenApi)
46
+ * - disneyNow: DisneyNOW player with play button overlay + multi-video (extends clickToPlayApi)
47
+ * - embeddedPlayer: Iframe-based players using fullscreen API (extends fullscreenApi)
48
+ * - apiMultiVideo: API fullscreen + multi-video + tile-based channel selection (extends fullscreenApi)
49
+ * - huluLive: Hulu Live TV with guide grid channel selection + fullscreen button (extends fullscreenApi)
50
+ * - embeddedDynamicMultiVideo: Embedded + network idle + multi-video selection (extends embeddedPlayer)
51
+ * - embeddedVolumeLock: Embedded + volume property locking (extends embeddedPlayer)
52
+ * - foxLive: Fox.com live guide grid with station code channel selection (extends fullscreenApi)
53
+ * - slingLive: Sling TV with virtualized A-Z guide grid channel selection (extends fullscreenApi)
54
+ * - youtubeTV: YouTube TV with non-virtualized EPG grid channel selection (extends fullscreenApi)
55
+ *
56
+ * Each profile includes a description field documenting its purpose. This is metadata only - it's stripped during profile resolution and exists purely for
57
+ * documentation.
58
+ */
59
+ export const SITE_PROFILES = {
60
+ // Profile for multi-channel live TV pages that present a grid or shelf of live channel tiles requiring tile-based selection followed by a play button click. Uses
61
+ // the fullscreen API and multi-video selection to find the actively playing stream after channel selection. Does not use iframe handling or network idle wait
62
+ // because these sites serve video directly in the main page and have persistent connections that prevent network idle.
63
+ apiMultiVideo: {
64
+ category: "multiChannel",
65
+ channelSelection: { strategy: "tileClick" },
66
+ description: "Multi-channel sites with tile-based channel grid. Set Channel Selector to a unique string from the channel's tile image URL.",
67
+ extends: "fullscreenApi",
68
+ selectReadyVideo: true,
69
+ summary: "Multi-channel (tile selection, needs selector)"
70
+ },
71
+ // Profile for sites using the Brightcove player platform. Brightcove players require waiting for network activity to settle before the video player is fully
72
+ // initialized. The player dynamically loads its configuration and stream manifest, so waitForNetworkIdle ensures we don't try to interact with the player before
73
+ // it's ready. Uses the JavaScript fullscreen API rather than keyboard shortcuts because Brightcove intercepts keyboard events.
74
+ brightcove: {
75
+ category: "api",
76
+ description: "Brightcove player sites requiring network idle wait and API fullscreen.",
77
+ extends: "fullscreenApi",
78
+ summary: "Brightcove players (network wait)",
79
+ waitForNetworkIdle: true
80
+ },
81
+ // Profile for sites that require clicking to start playback. Some players don't autoplay and need user interaction to begin. Uses the JavaScript fullscreen API.
82
+ // Set clickSelector in the profile or channel definition to specify a play button element; otherwise clicks the video element directly.
83
+ clickToPlayApi: {
84
+ category: "api",
85
+ clickToPlay: true,
86
+ description: "Sites requiring a click to start playback, using the JavaScript fullscreen API. Use clickSelector for play button overlays.",
87
+ extends: "fullscreenApi",
88
+ summary: "Click-to-play (API fullscreen)"
89
+ },
90
+ // Profile for sites that require clicking to start playback, using keyboard 'f' for fullscreen. Use this when clickToPlayApi doesn't work for fullscreen but the
91
+ // site responds to the 'f' key. Set clickSelector in the profile or channel definition to specify a play button element.
92
+ clickToPlayKeyboard: {
93
+ category: "keyboard",
94
+ clickToPlay: true,
95
+ description: "Sites requiring a click to start playback, using the 'f' key for fullscreen. Use clickSelector for play button overlays.",
96
+ extends: "keyboardFullscreen",
97
+ summary: "Click-to-play ('f' key fullscreen)"
98
+ },
99
+ // 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.
100
+ disneyNow: {
101
+ category: "api",
102
+ clickSelector: ".overlay__button button",
103
+ description: "DisneyNOW player with play button overlay and multiple video elements.",
104
+ extends: "clickToPlayApi",
105
+ selectReadyVideo: true,
106
+ summary: "DisneyNOW player"
107
+ },
108
+ // Profile for iframe-embedded players that also have multiple video elements (ads, placeholders, main content) and need network activity to settle. The
109
+ // selectReadyVideo flag ensures we find the video with actual content rather than an ad placeholder. Combines iframe handling with API-based fullscreen.
110
+ embeddedDynamicMultiVideo: {
111
+ category: "api",
112
+ description: "Iframe-embedded players with multiple video elements requiring network idle wait.",
113
+ extends: "embeddedPlayer",
114
+ selectReadyVideo: true,
115
+ summary: "Embedded multi-video (network wait)",
116
+ waitForNetworkIdle: true
117
+ },
118
+ // Intermediate profile for sites that both embed their player in an iframe AND require the JavaScript fullscreen API. Many modern players use this architecture
119
+ // to isolate ad content and use programmatic fullscreen rather than keyboard shortcuts. This profile combines iframe handling with API-based fullscreen.
120
+ embeddedPlayer: {
121
+ category: "api",
122
+ description: "Intermediate base profile for iframe-embedded players using fullscreen API.",
123
+ extends: "fullscreenApi",
124
+ needsIframeHandling: true,
125
+ summary: "Embedded iframe players"
126
+ },
127
+ // Profile for iframe-embedded players that aggressively mute audio after page load - likely to comply with autoplay policies or for accessibility reasons. Some
128
+ // sites set video.muted = true even after we unmute it. The lockVolumeProperties flag uses Object.defineProperty to override the muted and volume getters/setters,
129
+ // preventing the site from re-muting the video.
130
+ embeddedVolumeLock: {
131
+ category: "api",
132
+ description: "Iframe-embedded players that aggressively mute audio after page load.",
133
+ extends: "embeddedPlayer",
134
+ lockVolumeProperties: true,
135
+ summary: "Embedded players that auto-mute"
136
+ },
137
+ // 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
138
+ // titles (e.g., FOXD2C, FNC, FS1). The channelSelector property matches against these station codes. Clicking the channel logo button is an SPA state
139
+ // transition — the player at the top of the page switches channels without navigation. The grid renders dynamically after page load, so the strategy waits
140
+ // for GuideChannelContainer elements before scanning.
141
+ foxLive: {
142
+ category: "multiChannel",
143
+ channelSelection: { strategy: "foxGrid" },
144
+ description: "Fox.com live channel guide. Set Channel Selector to the station code (e.g., BTN, FOXD2C, FS1).",
145
+ extends: "fullscreenApi",
146
+ summary: "Fox Live (guide grid, needs selector)"
147
+ },
148
+ // Base profile for sites that require the JavaScript fullscreen API (element.requestFullscreen()) instead of keyboard shortcuts. Many modern players intercept
149
+ // keyboard events for their own controls, making the f key unreliable. Calling requestFullscreen() directly on the video element bypasses the player's keyboard
150
+ // handling and reliably enters fullscreen mode.
151
+ fullscreenApi: {
152
+ category: "api",
153
+ description: "Base profile for sites requiring the JavaScript fullscreen API.",
154
+ summary: "Sites needing JavaScript fullscreen",
155
+ useRequestFullscreen: true
156
+ },
157
+ // 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
158
+ // 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
159
+ // channel rail for the watch URL matching the channelSelector name. Extends fullscreenApi for requestFullscreen() behavior inherited by the watch page.
160
+ hboMax: {
161
+ category: "multiChannel",
162
+ channelSelection: { strategy: "hboGrid" },
163
+ description: "HBO Max with live channel rail selection. Set Channel Selector to the channel name (e.g., HBO, HBO Hits).",
164
+ extends: "fullscreenApi",
165
+ summary: "HBO Max (live channels, needs selector)"
166
+ },
167
+ // 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
168
+ // 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
169
+ // maximize control. Requires selectReadyVideo because the page may have multiple video elements (ads, previews, main content). Uses waitForNetworkIdle because
170
+ // Hulu's SPA has heavy async initialization that often prevents the load event from firing within the retryOperation timeout; the graceful networkidle2 fallback
171
+ // in navigateToPage() allows execution to continue to channel selection even when background requests are still pending.
172
+ huluLive: {
173
+ category: "multiChannel",
174
+ channelSelection: { listSelector: "#CHANNELS", playSelector: "[data-testid=\"generic-tile-thumbnail\"]", strategy: "guideGrid" },
175
+ description: "Hulu Live TV with guide grid channel selection. Set Channel Selector to the channel's internal guide name (may differ from logo).",
176
+ extends: "fullscreenApi",
177
+ fullscreenSelector: "[aria-label=\"Maximize\"]",
178
+ selectReadyVideo: true,
179
+ summary: "Hulu Live TV (guide grid, needs selector)",
180
+ waitForNetworkIdle: true
181
+ },
182
+ // 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
183
+ // 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.
184
+ keyboardDynamic: {
185
+ category: "keyboard",
186
+ description: "Keyboard fullscreen sites requiring network idle wait for dynamic content loading.",
187
+ extends: "keyboardFullscreen",
188
+ summary: "Dynamic sites ('f' key fullscreen)",
189
+ waitForNetworkIdle: true
190
+ },
191
+ // Profile for multi-channel player pages that use keyboard fullscreen and need both network idle wait and multi-video selection. These pages present multiple
192
+ // channels to choose from, and the channelSelector property in the channel definition specifies which one to select. Extends keyboardDynamic to inherit network
193
+ // idle wait behavior. Uses thumbnailRow strategy for channel selection (find channel by thumbnail image URL, click adjacent show entry).
194
+ keyboardDynamicMultiVideo: {
195
+ category: "multiChannel",
196
+ channelSelection: { strategy: "thumbnailRow" },
197
+ description: "Multi-channel sites with thumbnail row layout. Set Channel Selector to a unique string from the channel's thumbnail image URL.",
198
+ extends: "keyboardDynamic",
199
+ selectReadyVideo: true,
200
+ summary: "Multi-channel (thumbnail row, needs selector)"
201
+ },
202
+ // Base profile for sites that respond to the f key for fullscreen toggle. This is the most common fullscreen mechanism, following YouTube-style keyboard
203
+ // shortcuts. The f key is sent as a keyboard event to the page, triggering the player's built-in fullscreen toggle. This works with most standard video players.
204
+ keyboardFullscreen: {
205
+ category: "keyboard",
206
+ description: "Base profile for sites that respond to the f key for fullscreen toggle.",
207
+ fullscreenKey: "f",
208
+ summary: "Standard 'f' key fullscreen"
209
+ },
210
+ // Profile for sites using keyboard fullscreen with video players embedded in iframes. The video element is not directly in the main page DOM, so we need to search
211
+ // through all frames to find it. Once found, the player responds to the standard f key for fullscreen.
212
+ keyboardIframe: {
213
+ category: "keyboard",
214
+ description: "Keyboard fullscreen sites with video embedded in iframes.",
215
+ extends: "keyboardFullscreen",
216
+ needsIframeHandling: true,
217
+ summary: "Iframe players ('f' key fullscreen)"
218
+ },
219
+ // Profile for sites using keyboard fullscreen that load multiple video elements simultaneously - placeholder videos, ad videos, and the main content. We must find
220
+ // the video element that has actually loaded playable data (readyState >= 3) rather than just taking the first video element.
221
+ keyboardMultiVideo: {
222
+ category: "keyboard",
223
+ description: "Keyboard fullscreen sites with multiple video elements requiring ready-state selection.",
224
+ extends: "keyboardFullscreen",
225
+ selectReadyVideo: true,
226
+ summary: "Multi-video sites ('f' key fullscreen)"
227
+ },
228
+ // 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
229
+ // (120px each) with channel identification via data-testid="channel-{NAME}" attributes. The slingGrid strategy performs binary search on .guide-cell scrollTop
230
+ // 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
231
+ // fullscreenApi for requestFullscreen() behavior. Does not use waitForNetworkIdle — the strategy's own waitForSelector on channel entries is the readiness
232
+ // signal, and the SPA's persistent connections would delay network idle unnecessarily.
233
+ slingLive: {
234
+ category: "multiChannel",
235
+ channelSelection: { strategy: "slingGrid" },
236
+ description: "Sling TV with guide grid channel selection. Set Channel Selector to the channel's internal guide name (may differ from logo).",
237
+ extends: "fullscreenApi",
238
+ summary: "Sling TV (guide grid, needs selector)"
239
+ },
240
+ // Profile for non-video pages that should be captured as static visual content. Examples include weather displays (weatherscan.net), maps (windy.com), and
241
+ // 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.
242
+ staticPage: {
243
+ category: "special",
244
+ description: "Base profile for non-video pages captured as static visual content.",
245
+ noVideo: true,
246
+ summary: "Static pages (no video)"
247
+ },
248
+ // Profile for YouTube TV (tv.youtube.com/live). The guide grid renders all ~256 channel rows in the DOM simultaneously (no virtualization), each containing a
249
+ // direct watch URL. The youtubeGrid strategy performs a single querySelector to find the target channel's watch link via aria-label, extracts the URL, and
250
+ // navigates directly — no scrolling, clicking, or timing workarounds needed. Uses selectReadyVideo because the watch page has ~36 video elements (live preview
251
+ // thumbnails from the guide) but only one active stream with readyState >= 3 and videoWidth > 0. Extends fullscreenApi because requestFullscreen() works
252
+ // directly on the active video element without gesture requirements.
253
+ youtubeTV: {
254
+ category: "multiChannel",
255
+ channelSelection: { strategy: "youtubeGrid" },
256
+ description: "YouTube TV with EPG grid channel selection. Set Channel Selector to the channel name as shown in the guide (e.g., CNN, ESPN, NBC).",
257
+ extends: "fullscreenApi",
258
+ selectReadyVideo: true,
259
+ summary: "YouTube TV (guide grid, needs selector)"
260
+ }
261
+ };
262
+ /* This mapping associates domain keys with site profiles, provider display names, and provider filter tags. Most keys are concise second-level domains
263
+ * (e.g., "nbc.com", "foodnetwork.com") matching the output of extractDomain(). Keys can also be full hostnames (e.g., "tv.youtube.com") for subdomain-specific
264
+ * overrides — getDomainConfig() tries the full hostname first, then falls back to the concise domain, so "tv.youtube.com" takes precedence over "youtube.com"
265
+ * when the URL matches.
266
+ *
267
+ * Domains without a profile entry will use DEFAULT_SITE_PROFILE, which works for most standard video players. Domains without a provider entry will display the
268
+ * concise domain string (e.g., "hulu.com") in the UI. Entries with a providerTag participate in the provider filter system — channels whose canonical URL maps to
269
+ * a tagged domain are identified as belonging to that subscription service rather than being tagged as "direct" (free network sites).
270
+ */
271
+ export const DOMAIN_CONFIG = {
272
+ "abc.com": { profile: "keyboardMultiVideo", provider: "ABC.com" },
273
+ "aetv.com": { profile: "fullscreenApi", provider: "A&E" },
274
+ "bet.com": { profile: "fullscreenApi", provider: "BET.com" },
275
+ "c-span.org": { profile: "brightcove", provider: "C-SPAN.org" },
276
+ "cbs.com": { profile: "keyboardIframe", provider: "CBS.com" },
277
+ "cnbc.com": { profile: "fullscreenApi", provider: "CNBC.com" },
278
+ "cnn.com": { profile: "fullscreenApi", provider: "CNN.com" },
279
+ "disneynow.com": { profile: "disneyNow", provider: "DisneyNOW" },
280
+ "disneyplus.com": { profile: "apiMultiVideo", provider: "Disney+", providerTag: "disneyplus" },
281
+ "espn.com": { profile: "keyboardMultiVideo", provider: "ESPN.com" },
282
+ "foodnetwork.com": { profile: "fullscreenApi", provider: "Food Network" },
283
+ "fox.com": { loginUrl: "https://www.fox.com", profile: "foxLive", provider: "Fox", providerTag: "foxcom" },
284
+ "foxbusiness.com": { profile: "embeddedDynamicMultiVideo", provider: "Fox Business" },
285
+ "foxnews.com": { profile: "embeddedDynamicMultiVideo", provider: "Fox News" },
286
+ "foxsports.com": { profile: "fullscreenApi", provider: "Fox Sports" },
287
+ "france24.com": { profile: "embeddedVolumeLock", provider: "France 24" },
288
+ "fyi.tv": { profile: "fullscreenApi", provider: "FYI" },
289
+ "golfchannel.com": { profile: "fullscreenApi", provider: "Golf Channel" },
290
+ "hbomax.com": { profile: "hboMax", provider: "HBO Max", providerTag: "hbomax" },
291
+ "history.com": { profile: "fullscreenApi", provider: "History.com" },
292
+ "hulu.com": { profile: "huluLive", provider: "Hulu (Live Guide)", providerTag: "hulu" },
293
+ "lakeshorepbs.org": { profile: "embeddedPlayer", provider: "Lakeshore PBS" },
294
+ "ms.now": { profile: "keyboardDynamic", provider: "MS NOW" },
295
+ "mylifetime.com": { profile: "fullscreenApi", provider: "Lifetime" },
296
+ "nationalgeographic.com": { profile: "keyboardDynamicMultiVideo", provider: "Nat Geo" },
297
+ "nba.com": { profile: "fullscreenApi", provider: "NBA.com" },
298
+ "nbc.com": { maxContinuousPlayback: 4, profile: "keyboardDynamic", provider: "NBC.com" },
299
+ "paramountplus.com": { profile: "fullscreenApi", provider: "Paramount+", providerTag: "paramountplus" },
300
+ "sling.com": { profile: "embeddedVolumeLock", provider: "Sling TV" },
301
+ "tbs.com": { profile: "fullscreenApi", provider: "TBS.com" },
302
+ "tntdrama.com": { profile: "fullscreenApi", provider: "TNT" },
303
+ "trutv.com": { profile: "fullscreenApi", provider: "truTV" },
304
+ "tv.youtube.com": { profile: "youtubeTV", provider: "YouTube TV", providerTag: "yttv" },
305
+ "usanetwork.com": { profile: "keyboardDynamicMultiVideo", provider: "USA Network", providerTag: "usa" },
306
+ "vh1.com": { profile: "fullscreenApi", provider: "VH1.com" },
307
+ "watch.sling.com": { profile: "slingLive", provider: "Sling TV (Live Guide)", providerTag: "sling" },
308
+ "watchhallmarktv.com": { profile: "fullscreenApi", provider: "Hallmark" },
309
+ "weatherscan.net": { profile: "staticPage", provider: "Weatherscan" },
310
+ "windy.com": { profile: "staticPage", provider: "Windy" },
311
+ "wttw.com": { profile: "fullscreenApi", provider: "WTTW" },
312
+ "youtube.com": { profile: "keyboardDynamic", provider: "YouTube" }
313
+ };
314
+ /**
315
+ * Resolves a URL to its DOMAIN_CONFIG entry by trying the full hostname first for subdomain-specific overrides, then falling back to the concise domain (last two
316
+ * hostname parts). This allows entries like "tv.youtube.com" to override the base "youtube.com" entry when the URL matches the more-specific subdomain.
317
+ * @param url - The URL to resolve a domain configuration for.
318
+ * @returns The matching DomainConfig entry, or undefined if no match is found.
319
+ */
320
+ export function getDomainConfig(url) {
321
+ try {
322
+ const hostname = new URL(url).hostname;
323
+ // Try the full hostname first for subdomain-specific overrides (e.g., "tv.youtube.com" before "youtube.com").
324
+ const hostnameMatch = DOMAIN_CONFIG[hostname];
325
+ if (hostnameMatch) {
326
+ return hostnameMatch;
327
+ }
328
+ }
329
+ catch {
330
+ // Invalid URL — fall through to concise domain lookup.
331
+ }
332
+ return DOMAIN_CONFIG[extractDomain(url)];
333
+ }
334
+ /* The default profile provides baseline behavior for sites not explicitly listed in the domain mapping or channel definitions. These settings work for most
335
+ * standard HTML5 video players that follow common conventions. Each flag is explicitly set to its default value for documentation purposes and to ensure
336
+ * predictable behavior - we don't rely on implicit defaults.
337
+ *
338
+ * Sites matching the default profile:
339
+ * - Use standard HTML5 video without iframe embedding
340
+ * - Have a single video element on the page
341
+ * - Don't require clicking to start playback
342
+ * - Don't auto-mute aggressively
343
+ * - Don't require waiting for network activity
344
+ * - Have video content (not static pages)
345
+ *
346
+ * Neither keyboard fullscreen nor API fullscreen is enabled by default because many sites work fine without explicit fullscreen triggering - the video is already
347
+ * displayed at full size in the viewport. Fullscreen is only needed when the player has visible controls or surrounding content that we want to hide.
348
+ */
349
+ export const DEFAULT_SITE_PROFILE = {
350
+ // No channel selection - single-channel sites don't need it.
351
+ channelSelection: { strategy: "none" },
352
+ // No channel selector - this is only used for multi-channel player pages.
353
+ channelSelector: null,
354
+ // No click selector - when clickToPlay is true, click the video element by default.
355
+ clickSelector: null,
356
+ // Don't click to play - most sites start automatically or via other mechanisms.
357
+ clickToPlay: false,
358
+ // No fullscreen key - many players work without explicit fullscreen.
359
+ fullscreenKey: null,
360
+ // No fullscreen button selector - most sites don't have a dedicated fullscreen button we need to click.
361
+ fullscreenSelector: null,
362
+ // Don't lock volume properties - most sites don't aggressively mute.
363
+ lockVolumeProperties: false,
364
+ // No continuous playback limit - most sites allow indefinite streaming.
365
+ maxContinuousPlayback: null,
366
+ // Don't search iframes - assume video is in main page DOM.
367
+ needsIframeHandling: false,
368
+ // Expect video content - wait for video element.
369
+ noVideo: false,
370
+ // Use first video element - assume only one video exists.
371
+ selectReadyVideo: false,
372
+ // Don't use requestFullscreen() API.
373
+ useRequestFullscreen: false,
374
+ // Don't wait for network idle - assume player is ready on page load.
375
+ waitForNetworkIdle: false
376
+ };
377
+ //# sourceMappingURL=sites.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sites.js","sourceRoot":"","sources":["../../src/config/sites.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,178 @@
1
+ import type { Channel, ChannelListingEntry, ChannelMap } from "../types/index.js";
2
+ /**
3
+ * User-defined channel with all channel properties.
4
+ */
5
+ export type UserChannel = Channel;
6
+ /**
7
+ * Map of channel keys to user-defined channel configurations.
8
+ */
9
+ export type UserChannelMap = ChannelMap;
10
+ /**
11
+ * Result of loading user channels from the file.
12
+ */
13
+ export interface UserChannelsLoadResult {
14
+ channels: UserChannelMap;
15
+ parseError: boolean;
16
+ parseErrorMessage?: string;
17
+ providerSelections: Record<string, string>;
18
+ }
19
+ /**
20
+ * Returns the path to the user channels file.
21
+ * @returns The absolute path to ~/.prismcast/channels.json.
22
+ */
23
+ export declare function getUserChannelsFilePath(): string;
24
+ /**
25
+ * Returns whether the user channels file had a parse error.
26
+ * @returns True if the channels file exists but contains invalid JSON.
27
+ */
28
+ export declare function hasChannelsParseError(): boolean;
29
+ /**
30
+ * Returns the parse error message if the channels file had a parse error.
31
+ * @returns The error message or undefined.
32
+ */
33
+ export declare function getChannelsParseErrorMessage(): string | undefined;
34
+ /**
35
+ * Loads user channels from the channels file. Returns an empty map if the file doesn't exist, and sets parseError if the file exists but contains invalid JSON.
36
+ * The file can contain a special `providerSelections` key with user's provider preferences, which is extracted separately from channels.
37
+ * @returns The loaded channels with parse status and provider selections.
38
+ */
39
+ export declare function loadUserChannels(): Promise<UserChannelsLoadResult>;
40
+ /**
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
+ * @param channels - The channels to save.
44
+ * @throws If the file cannot be written.
45
+ */
46
+ export declare function saveUserChannels(channels: UserChannelMap): Promise<void>;
47
+ /**
48
+ * Deletes a user channel by key.
49
+ * @param key - The channel key to delete.
50
+ * @throws If the file cannot be read or written.
51
+ */
52
+ export declare function deleteUserChannel(key: string): Promise<void>;
53
+ /**
54
+ * Resets all user channels by deleting the channels file.
55
+ * @throws If the file exists but cannot be deleted.
56
+ */
57
+ export declare function resetUserChannels(): Promise<void>;
58
+ /**
59
+ * Initializes user channels by loading them from the file. This should be called once at server startup. Also builds provider groups and loads provider selections.
60
+ */
61
+ export declare function initializeUserChannels(): Promise<void>;
62
+ /**
63
+ * Returns the full channel listing with source classification and enabled status. This is the authoritative merge point for predefined and user channels — all
64
+ * code that needs a merged view of channels should use this function (or getAllChannels() which delegates to it).
65
+ *
66
+ * For each channel key, the source is classified as:
67
+ * - "predefined": exists only in predefined channels
68
+ * - "user": exists only in user channels
69
+ * - "override": exists in both (user channel data takes precedence)
70
+ *
71
+ * The enabled field reflects whether the channel is available for streaming. Predefined-only channels can be disabled via configuration; user and override
72
+ * channels are always enabled.
73
+ *
74
+ * Provider variants (non-canonical keys in provider groups) are filtered out from this listing — they are accessed via the provider selection mechanism instead.
75
+ *
76
+ * IMPORTANT: This function preserves object references from PREDEFINED_CHANNELS and loadedUserChannels. The provider system (providers.ts) relies on this behavior
77
+ * to detect user overrides via reference comparison. Do not clone channel objects when building the listing.
78
+ * @returns Sorted array of channel listing entries.
79
+ */
80
+ export declare function getChannelListing(): ChannelListingEntry[];
81
+ /**
82
+ * Returns all available channels (predefined + user), with user channels taking precedence on key conflicts. Disabled predefined channels are excluded unless they
83
+ * have a user override. Built on top of getChannelListing() to ensure a single merging code path.
84
+ * @returns The merged channel map with disabled predefined channels filtered out.
85
+ */
86
+ export declare function getAllChannels(): ChannelMap;
87
+ /**
88
+ * Returns the loaded user channels (without predefined channels).
89
+ * @returns The user channel map.
90
+ */
91
+ export declare function getUserChannels(): UserChannelMap;
92
+ /**
93
+ * Checks if a channel key exists in the predefined channels.
94
+ * @param key - The channel key to check.
95
+ * @returns True if the channel is predefined.
96
+ */
97
+ export declare function isPredefinedChannel(key: string): boolean;
98
+ /**
99
+ * Checks if a channel key exists in the user channels.
100
+ * @param key - The channel key to check.
101
+ * @returns True if the channel is user-defined.
102
+ */
103
+ export declare function isUserChannel(key: string): boolean;
104
+ /**
105
+ * Checks if a user channel overrides a predefined channel (same key exists in both).
106
+ * @param key - The channel key to check.
107
+ * @returns True if the user channel overrides a predefined channel.
108
+ */
109
+ export declare function isOverrideChannel(key: string): boolean;
110
+ /**
111
+ * Checks if a predefined channel is disabled.
112
+ * @param key - The channel key to check.
113
+ * @returns True if the channel is predefined and disabled.
114
+ */
115
+ export declare function isPredefinedChannelDisabled(key: string): boolean;
116
+ /**
117
+ * Returns the list of disabled predefined channel keys.
118
+ * @returns Array of disabled channel keys.
119
+ */
120
+ export declare function getDisabledPredefinedChannels(): string[];
121
+ /**
122
+ * Returns all predefined channels regardless of disabled state. Used by the UI to show all predefined channels including disabled ones.
123
+ * @returns The predefined channel map.
124
+ */
125
+ export declare function getPredefinedChannels(): ChannelMap;
126
+ /**
127
+ * Checks if a channel is available for streaming. A channel is available if it exists in the merged channel map returned by getAllChannels(), which already
128
+ * excludes disabled predefined channels (unless overridden by a user channel).
129
+ * @param key - The channel key to check.
130
+ * @returns True if the channel can be streamed.
131
+ */
132
+ export declare function isChannelAvailable(key: string): boolean;
133
+ /**
134
+ * Validates a channel key for format and uniqueness.
135
+ * @param key - The channel key to validate.
136
+ * @param isNew - True if this is a new channel (checks for duplicates among user channels).
137
+ * @returns Error message if invalid, undefined if valid.
138
+ */
139
+ export declare function validateChannelKey(key: string, isNew: boolean): string | undefined;
140
+ /**
141
+ * Validates a channel URL.
142
+ * @param url - The URL to validate.
143
+ * @returns Error message if invalid, undefined if valid.
144
+ */
145
+ export declare function validateChannelUrl(url: string): string | undefined;
146
+ /**
147
+ * Validates a channel name.
148
+ * @param name - The name to validate.
149
+ * @returns Error message if invalid, undefined if valid.
150
+ */
151
+ export declare function validateChannelName(name: string): string | undefined;
152
+ /**
153
+ * Validates a profile name.
154
+ * @param profile - The profile name to validate (can be empty for autodetect).
155
+ * @param validProfiles - Array of valid profile names.
156
+ * @returns Error message if invalid, undefined if valid.
157
+ */
158
+ export declare function validateChannelProfile(profile: string | undefined, validProfiles: string[]): string | undefined;
159
+ /**
160
+ * Result of validating imported channels.
161
+ */
162
+ export interface ChannelsValidationResult {
163
+ channels: UserChannelMap;
164
+ errors: string[];
165
+ valid: boolean;
166
+ }
167
+ /**
168
+ * Validates an imported channels object for structure and content.
169
+ * @param data - The raw imported data to validate.
170
+ * @param validProfiles - Array of valid profile names.
171
+ * @returns Validation result with errors if invalid.
172
+ */
173
+ export declare function validateImportedChannels(data: unknown, validProfiles: string[]): ChannelsValidationResult;
174
+ /**
175
+ * Saves the current provider selections to the channels file. This triggers a full file save including all user channels.
176
+ * @throws If the file cannot be written.
177
+ */
178
+ export declare function saveProviderSelections(): Promise<void>;