prismcast 1.1.0 → 1.2.1

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 (136) hide show
  1. package/README.md +20 -4
  2. package/dist/app.js +5 -20
  3. package/dist/app.js.map +1 -1
  4. package/dist/browser/cdp.js +1 -4
  5. package/dist/browser/cdp.js.map +1 -1
  6. package/dist/browser/channelSelection.d.ts +5 -0
  7. package/dist/browser/channelSelection.js +803 -53
  8. package/dist/browser/channelSelection.js.map +1 -1
  9. package/dist/browser/display.js +1 -4
  10. package/dist/browser/display.js.map +1 -1
  11. package/dist/browser/index.js +10 -28
  12. package/dist/browser/index.js.map +1 -1
  13. package/dist/browser/video.d.ts +24 -10
  14. package/dist/browser/video.js +101 -44
  15. package/dist/browser/video.js.map +1 -1
  16. package/dist/channels/index.d.ts +1 -17
  17. package/dist/channels/index.js +246 -137
  18. package/dist/channels/index.js.map +1 -1
  19. package/dist/config/index.js +10 -9
  20. package/dist/config/index.js.map +1 -1
  21. package/dist/config/presets.js +1 -4
  22. package/dist/config/presets.js.map +1 -1
  23. package/dist/config/profiles.d.ts +14 -13
  24. package/dist/config/profiles.js +47 -287
  25. package/dist/config/profiles.js.map +1 -1
  26. package/dist/config/providers.d.ts +75 -0
  27. package/dist/config/providers.js +283 -0
  28. package/dist/config/providers.js.map +1 -0
  29. package/dist/config/sites.d.ts +20 -0
  30. package/dist/config/sites.js +350 -0
  31. package/dist/config/sites.js.map +1 -0
  32. package/dist/config/userChannels.d.ts +15 -3
  33. package/dist/config/userChannels.js +85 -33
  34. package/dist/config/userChannels.js.map +1 -1
  35. package/dist/config/userConfig.d.ts +1 -0
  36. package/dist/config/userConfig.js +12 -26
  37. package/dist/config/userConfig.js.map +1 -1
  38. package/dist/hdhr/channelMap.js +3 -6
  39. package/dist/hdhr/channelMap.js.map +1 -1
  40. package/dist/hdhr/deviceId.js +1 -4
  41. package/dist/hdhr/deviceId.js.map +1 -1
  42. package/dist/hdhr/discover.js.map +1 -1
  43. package/dist/hdhr/index.js +1 -4
  44. package/dist/hdhr/index.js.map +1 -1
  45. package/dist/index.js +4 -16
  46. package/dist/index.js.map +1 -1
  47. package/dist/routes/assets.js +1 -4
  48. package/dist/routes/assets.js.map +1 -1
  49. package/dist/routes/auth.js +4 -2
  50. package/dist/routes/auth.js.map +1 -1
  51. package/dist/routes/channels.js +2 -2
  52. package/dist/routes/channels.js.map +1 -1
  53. package/dist/routes/components.js.map +1 -1
  54. package/dist/routes/config.js +163 -34
  55. package/dist/routes/config.js.map +1 -1
  56. package/dist/routes/health.js +1 -4
  57. package/dist/routes/health.js.map +1 -1
  58. package/dist/routes/hls.js +1 -4
  59. package/dist/routes/hls.js.map +1 -1
  60. package/dist/routes/index.js +1 -4
  61. package/dist/routes/index.js.map +1 -1
  62. package/dist/routes/logs.js +3 -12
  63. package/dist/routes/logs.js.map +1 -1
  64. package/dist/routes/mpegts.js +1 -4
  65. package/dist/routes/mpegts.js.map +1 -1
  66. package/dist/routes/play.js +1 -4
  67. package/dist/routes/play.js.map +1 -1
  68. package/dist/routes/playlist.js +2 -5
  69. package/dist/routes/playlist.js.map +1 -1
  70. package/dist/routes/root.js +43 -12
  71. package/dist/routes/root.js.map +1 -1
  72. package/dist/routes/streams.js +2 -8
  73. package/dist/routes/streams.js.map +1 -1
  74. package/dist/routes/theme.js +1 -4
  75. package/dist/routes/theme.js.map +1 -1
  76. package/dist/routes/ui.js +1 -4
  77. package/dist/routes/ui.js.map +1 -1
  78. package/dist/service/commands.js +1 -4
  79. package/dist/service/commands.js.map +1 -1
  80. package/dist/service/generators.js +4 -16
  81. package/dist/service/generators.js.map +1 -1
  82. package/dist/streaming/clients.js.map +1 -1
  83. package/dist/streaming/fmp4Segmenter.d.ts +1 -0
  84. package/dist/streaming/fmp4Segmenter.js +7 -12
  85. package/dist/streaming/fmp4Segmenter.js.map +1 -1
  86. package/dist/streaming/hls.d.ts +3 -0
  87. package/dist/streaming/hls.js +39 -21
  88. package/dist/streaming/hls.js.map +1 -1
  89. package/dist/streaming/hlsSegments.js +1 -4
  90. package/dist/streaming/hlsSegments.js.map +1 -1
  91. package/dist/streaming/lifecycle.js +1 -4
  92. package/dist/streaming/lifecycle.js.map +1 -1
  93. package/dist/streaming/monitor.d.ts +1 -0
  94. package/dist/streaming/monitor.js +296 -76
  95. package/dist/streaming/monitor.js.map +1 -1
  96. package/dist/streaming/mp4Parser.js.map +1 -1
  97. package/dist/streaming/mpegts.js +1 -4
  98. package/dist/streaming/mpegts.js.map +1 -1
  99. package/dist/streaming/registry.d.ts +7 -0
  100. package/dist/streaming/registry.js +10 -0
  101. package/dist/streaming/registry.js.map +1 -1
  102. package/dist/streaming/setup.d.ts +4 -1
  103. package/dist/streaming/setup.js +35 -33
  104. package/dist/streaming/setup.js.map +1 -1
  105. package/dist/streaming/showInfo.js +3 -6
  106. package/dist/streaming/showInfo.js.map +1 -1
  107. package/dist/streaming/statusEmitter.d.ts +2 -0
  108. package/dist/streaming/statusEmitter.js +2 -4
  109. package/dist/streaming/statusEmitter.js.map +1 -1
  110. package/dist/types/index.d.ts +34 -2
  111. package/dist/utils/errors.js +1 -4
  112. package/dist/utils/errors.js.map +1 -1
  113. package/dist/utils/evaluate.js +3 -6
  114. package/dist/utils/evaluate.js.map +1 -1
  115. package/dist/utils/ffmpeg.js +2 -8
  116. package/dist/utils/ffmpeg.js.map +1 -1
  117. package/dist/utils/fileLogger.js +9 -30
  118. package/dist/utils/fileLogger.js.map +1 -1
  119. package/dist/utils/format.d.ts +7 -0
  120. package/dist/utils/format.js +20 -0
  121. package/dist/utils/format.js.map +1 -1
  122. package/dist/utils/html.js +1 -4
  123. package/dist/utils/html.js.map +1 -1
  124. package/dist/utils/logEmitter.js +1 -4
  125. package/dist/utils/logEmitter.js.map +1 -1
  126. package/dist/utils/logger.js +4 -16
  127. package/dist/utils/logger.js.map +1 -1
  128. package/dist/utils/m3u.js +1 -4
  129. package/dist/utils/m3u.js.map +1 -1
  130. package/dist/utils/morganStream.js +1 -4
  131. package/dist/utils/morganStream.js.map +1 -1
  132. package/dist/utils/platform.js.map +1 -1
  133. package/dist/utils/retry.js +1 -4
  134. package/dist/utils/retry.js.map +1 -1
  135. package/dist/utils/streamContext.js.map +1 -1
  136. package/package.json +3 -3
@@ -1,274 +1,23 @@
1
- import { CHANNELS } from "../channels/index.js";
2
- /*
3
- * SITE PROFILES SYSTEM
4
- *
5
- * Streaming sites implement their video players in wildly different ways. Some use standard HTML5 video with keyboard shortcuts, others embed players in iframes,
6
- * and many have unique quirks like auto-muting or requiring specific fullscreen methods. Rather than scattering site-specific conditionals throughout the streaming
7
- * code, we define "site profiles" that describe each site's behavior in a declarative way.
8
- *
9
- * The profile system has three components:
10
- *
11
- * 1. SITE_PROFILES: Named behavior configurations describing how to handle different player implementations. Profiles can inherit from other profiles using the
12
- * "extends" property, allowing us to define base profiles for common patterns (like "keyboardFullscreen" for sites using the f key) and then extend them with
13
- * site-specific variations.
14
- *
15
- * 2. DOMAIN_TO_PROFILE: A mapping from domain patterns to profile names. When streaming a URL, we check if it matches any known domain and use the corresponding
16
- * profile. This is the primary mechanism for automatically selecting the right behavior.
17
- *
18
- * 3. Channel-level profile hints: Individual channel definitions can specify an explicit profile name, overriding URL-based detection. This is useful when a
19
- * channel's URL doesn't match the expected domain pattern, or when the same domain serves multiple channel types that need different handling.
20
- *
21
- * Profile resolution happens at stream startup and the resolved profile is passed through the entire streaming pipeline. The profile flags control:
22
- * - How fullscreen is triggered (keyboard shortcut vs JavaScript API)
23
- * - Whether to search for video elements in iframes
24
- * - Which video element to select when multiple exist
25
- * - Whether to wait for network activity to settle before playback
26
- * - Whether to lock volume properties to prevent auto-muting
27
- * - Whether the page is static content (no video element expected)
28
- *
29
- * 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
30
- * handling not covered by existing profiles.
31
- */
32
- /*
33
- * SITE PROFILES
34
- *
35
- * 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
36
- * behavior patterns rather than site ownership. This makes it easier to identify the right profile when adding new channels.
37
- *
38
- * Base profiles (no extends):
39
- * - keyboardFullscreen: Sites using the f key for fullscreen toggle
40
- * - fullscreenApi: Sites requiring the JavaScript requestFullscreen() API
41
- * - staticPage: Non-video pages captured as static visual content
42
- *
43
- * Derived profiles (extends a base):
44
- * - keyboardDynamic: Keyboard fullscreen + network idle wait (extends keyboardFullscreen)
45
- * - keyboardMultiVideo: Keyboard fullscreen + multi-video selection (extends keyboardFullscreen)
46
- * - keyboardIframe: Keyboard fullscreen + iframe handling (extends keyboardFullscreen)
47
- * - keyboardDynamicMultiVideo: Keyboard + network idle + multi-video selection (extends keyboardDynamic)
48
- * - brightcove: Brightcove players using API fullscreen + network idle wait (extends fullscreenApi)
49
- * - embeddedPlayer: Iframe-based players using fullscreen API (extends fullscreenApi)
50
- * - apiMultiVideo: API fullscreen + multi-video + tile-based channel selection (extends fullscreenApi)
51
- * - embeddedDynamicMultiVideo: Embedded + network idle + multi-video selection (extends embeddedPlayer)
52
- * - embeddedVolumeLock: Embedded + volume property locking (extends embeddedPlayer)
53
- *
54
- * Each profile includes a description field documenting its purpose. This is metadata only - it's stripped during profile resolution and exists purely for
55
- * documentation.
56
- */
57
- export const SITE_PROFILES = {
58
- // 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
59
- // 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
60
- // because these sites serve video directly in the main page and have persistent connections that prevent network idle.
61
- apiMultiVideo: {
62
- channelSelection: { strategy: "tileClick" },
63
- description: "Multi-channel live TV pages requiring tile-based channel selection with API fullscreen.",
64
- extends: "fullscreenApi",
65
- selectReadyVideo: true,
66
- summary: "Multi-channel live TV (tile selection)"
67
- },
68
- // Profile for sites using the Brightcove player platform. Brightcove players require waiting for network activity to settle before the video player is fully
69
- // initialized. The player dynamically loads its configuration and stream manifest, so waitForNetworkIdle ensures we don't try to interact with the player before
70
- // it's ready. Uses the JavaScript fullscreen API rather than keyboard shortcuts because Brightcove intercepts keyboard events.
71
- brightcove: {
72
- description: "Brightcove player sites requiring network idle wait and API fullscreen.",
73
- extends: "fullscreenApi",
74
- summary: "Brightcove players (network wait)",
75
- waitForNetworkIdle: true
76
- },
77
- // Profile for iframe-embedded players that also have multiple video elements (ads, placeholders, main content) and need network activity to settle. The
78
- // selectReadyVideo flag ensures we find the video with actual content rather than an ad placeholder. Combines iframe handling with API-based fullscreen.
79
- embeddedDynamicMultiVideo: {
80
- description: "Iframe-embedded players with multiple video elements requiring network idle wait.",
81
- extends: "embeddedPlayer",
82
- selectReadyVideo: true,
83
- summary: "Embedded multi-video (network wait)",
84
- waitForNetworkIdle: true
85
- },
86
- // Intermediate profile for sites that both embed their player in an iframe AND require the JavaScript fullscreen API. Many modern players use this architecture
87
- // to isolate ad content and use programmatic fullscreen rather than keyboard shortcuts. This profile combines iframe handling with API-based fullscreen.
88
- embeddedPlayer: {
89
- description: "Intermediate base profile for iframe-embedded players using fullscreen API.",
90
- extends: "fullscreenApi",
91
- needsIframeHandling: true,
92
- summary: "Embedded iframe players"
93
- },
94
- // Profile for iframe-embedded players that aggressively mute audio after page load - likely to comply with autoplay policies or for accessibility reasons. Some
95
- // sites set video.muted = true even after we unmute it. The lockVolumeProperties flag uses Object.defineProperty to override the muted and volume getters/setters,
96
- // preventing the site from re-muting the video.
97
- embeddedVolumeLock: {
98
- description: "Iframe-embedded players that aggressively mute audio after page load.",
99
- extends: "embeddedPlayer",
100
- lockVolumeProperties: true,
101
- summary: "Embedded players that auto-mute"
102
- },
103
- // Base profile for sites that require the JavaScript fullscreen API (element.requestFullscreen()) instead of keyboard shortcuts. Many modern players intercept
104
- // keyboard events for their own controls, making the f key unreliable. Calling requestFullscreen() directly on the video element bypasses the player's keyboard
105
- // handling and reliably enters fullscreen mode.
106
- fullscreenApi: {
107
- description: "Base profile for sites requiring the JavaScript fullscreen API.",
108
- summary: "Sites needing JavaScript fullscreen",
109
- useRequestFullscreen: true
110
- },
111
- // 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
112
- // 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.
113
- keyboardDynamic: {
114
- description: "Keyboard fullscreen sites requiring network idle wait for dynamic content loading.",
115
- extends: "keyboardFullscreen",
116
- summary: "Dynamic sites ('f' key fullscreen)",
117
- waitForNetworkIdle: true
118
- },
119
- // Profile for multi-channel player pages that use keyboard fullscreen and need both network idle wait and multi-video selection. These pages present multiple
120
- // channels to choose from, and the channelSelector property in the channel definition specifies which one to select. Extends keyboardDynamic to inherit network
121
- // idle wait behavior. Uses thumbnailRow strategy for channel selection (find channel by thumbnail image URL, click adjacent show entry).
122
- keyboardDynamicMultiVideo: {
123
- channelSelection: { strategy: "thumbnailRow" },
124
- description: "Multi-channel keyboard players requiring network idle wait and video selection.",
125
- extends: "keyboardDynamic",
126
- selectReadyVideo: true,
127
- summary: "Multi-channel dynamic players"
128
- },
129
- // Base profile for sites that respond to the f key for fullscreen toggle. This is the most common fullscreen mechanism, following YouTube-style keyboard
130
- // 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.
131
- keyboardFullscreen: {
132
- description: "Base profile for sites that respond to the f key for fullscreen toggle.",
133
- fullscreenKey: "f",
134
- summary: "Standard 'f' key fullscreen"
135
- },
136
- // 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
137
- // through all frames to find it. Once found, the player responds to the standard f key for fullscreen.
138
- keyboardIframe: {
139
- description: "Keyboard fullscreen sites with video embedded in iframes.",
140
- extends: "keyboardFullscreen",
141
- needsIframeHandling: true,
142
- summary: "Iframe players ('f' key fullscreen)"
143
- },
144
- // Profile for sites using keyboard fullscreen that load multiple video elements simultaneously - placeholder videos, ad videos, and the main content. We must find
145
- // the video element that has actually loaded playable data (readyState >= 3) rather than just taking the first video element.
146
- keyboardMultiVideo: {
147
- description: "Keyboard fullscreen sites with multiple video elements requiring ready-state selection.",
148
- extends: "keyboardFullscreen",
149
- selectReadyVideo: true,
150
- summary: "Multi-video sites ('f' key fullscreen)"
151
- },
152
- // Profile for non-video pages that should be captured as static visual content. Examples include weather displays (weatherscan.net), maps (windy.com), and
153
- // 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.
154
- staticPage: {
155
- description: "Base profile for non-video pages captured as static visual content.",
156
- noVideo: true,
157
- summary: "Static pages (no video)"
158
- }
159
- };
160
- /*
161
- * DOMAIN TO PROFILE MAPPING
162
- *
163
- * This mapping associates domain patterns with profile names for automatic profile detection. When resolving a profile for a URL, we check if the URL contains any
164
- * of these domain strings. Using string containment rather than exact hostname matching allows us to handle:
165
- *
166
- * - Subdomains: "www.nbc.com" matches "nbc.com"
167
- * - Path-based matching: "example.com/live" would match "example.com"
168
- * - Regional variations: "nbc.com.au" would match "nbc.com"
1
+ /* Copyright(C) 2024-2026, HJD (https://github.com/hjdhjd). All rights reserved.
169
2
  *
170
- * The mapping is checked in Object.keys() order, so more specific domains should be listed if there's potential overlap. For example, if "news.site.com" and
171
- * "site.com" both existed, the first matching entry would be used.
172
- *
173
- * Domains not listed here will use DEFAULT_SITE_PROFILE, which works for most standard video players. Only add entries here when a site requires specific handling.
174
- */
175
- export const DOMAIN_TO_PROFILE = {
176
- // Sites with multiple video elements requiring ready-state selection.
177
- "abc.com": "keyboardMultiVideo",
178
- // Brightcove player sites requiring network idle wait.
179
- "c-span.org": "brightcove",
180
- // Keyboard fullscreen sites with iframe-embedded players.
181
- "cbs.com": "keyboardIframe",
182
- // Sites using the JavaScript fullscreen API.
183
- "cnbc.com": "fullscreenApi",
184
- "cnn.com": "fullscreenApi",
185
- // Tile-based channel selection from the shared live TV page.
186
- "disneyplus.com": "apiMultiVideo",
187
- // Sites using the JavaScript fullscreen API.
188
- "foodnetwork.com": "fullscreenApi",
189
- // Iframe-embedded players with complex multi-video setup.
190
- "foxbusiness.com": "embeddedDynamicMultiVideo",
191
- "foxnews.com": "embeddedDynamicMultiVideo",
192
- // Sites using the JavaScript fullscreen API.
193
- "foxsports.com": "fullscreenApi",
194
- // Iframe-embedded players that require volume locking.
195
- "france24.com": "embeddedVolumeLock",
196
- // Sites using the JavaScript fullscreen API.
197
- "hbomax.com": "fullscreenApi",
198
- // Keyboard fullscreen sites with dynamic content loading.
199
- "ms.now": "keyboardDynamic",
200
- // Keyboard fullscreen sites with dynamic content and multiple video elements.
201
- "nationalgeographic.com": "keyboardDynamicMultiVideo",
202
- // Keyboard fullscreen sites with dynamic content loading.
203
- "nbc.com": "keyboardDynamic",
204
- // Sites using the JavaScript fullscreen API.
205
- "paramountplus.com": "fullscreenApi",
206
- "tbs.com": "fullscreenApi",
207
- "tntdrama.com": "fullscreenApi",
208
- // Multi-channel keyboard players with dynamic content.
209
- "usanetwork.com": "keyboardDynamicMultiVideo",
210
- // Sites using the JavaScript fullscreen API.
211
- "vh1.com": "fullscreenApi",
212
- // Static pages without video content.
213
- "weatherscan.net": "staticPage",
214
- "windy.com": "staticPage",
215
- // Sites using the JavaScript fullscreen API.
216
- "wttw.com": "fullscreenApi",
217
- // Keyboard fullscreen sites with dynamic content loading.
218
- "youtube.com": "keyboardDynamic"
219
- };
220
- /*
221
- * DEFAULT SITE PROFILE
222
- *
223
- * The default profile provides baseline behavior for sites not explicitly listed in the domain mapping or channel definitions. These settings work for most
224
- * standard HTML5 video players that follow common conventions. Each flag is explicitly set to its default value for documentation purposes and to ensure
225
- * predictable behavior - we don't rely on implicit defaults.
226
- *
227
- * Sites matching the default profile:
228
- * - Use standard HTML5 video without iframe embedding
229
- * - Have a single video element on the page
230
- * - Don't require clicking to start playback
231
- * - Don't auto-mute aggressively
232
- * - Don't require waiting for network activity
233
- * - Have video content (not static pages)
234
- *
235
- * Neither keyboard fullscreen nor API fullscreen is enabled by default because many sites work fine without explicit fullscreen triggering - the video is already
236
- * 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.
3
+ * profiles.ts: Site profile resolution and validation for PrismCast.
237
4
  */
238
- export const DEFAULT_SITE_PROFILE = {
239
- // No channel selection - single-channel sites don't need it.
240
- channelSelection: { strategy: "none" },
241
- // No channel selector - this is only used for multi-channel player pages.
242
- channelSelector: null,
243
- // Don't click to play - most sites start automatically or via other mechanisms.
244
- clickToPlay: false,
245
- // No fullscreen key - many players work without explicit fullscreen.
246
- fullscreenKey: null,
247
- // Don't lock volume properties - most sites don't aggressively mute.
248
- lockVolumeProperties: false,
249
- // Don't search iframes - assume video is in main page DOM.
250
- needsIframeHandling: false,
251
- // Expect video content - wait for video element.
252
- noVideo: false,
253
- // Use first video element - assume only one video exists.
254
- selectReadyVideo: false,
255
- // Don't use requestFullscreen() API.
256
- useRequestFullscreen: false,
257
- // Don't wait for network idle - assume player is ready on page load.
258
- waitForNetworkIdle: false
259
- };
5
+ import { DEFAULT_SITE_PROFILE, DOMAIN_CONFIG, SITE_PROFILES, getDomainConfig } from "./sites.js";
6
+ import { CHANNELS } from "../channels/index.js";
7
+ // Re-export site data so existing consumers can import from either module.
8
+ export { DEFAULT_SITE_PROFILE, DOMAIN_CONFIG, SITE_PROFILES, getDomainConfig };
260
9
  /*
261
- * PROFILE RESOLUTION
262
- *
263
10
  * Profile resolution is the process of determining which behavior flags to use for a given stream. The resolution process handles inheritance, merging parent and
264
11
  * child profile properties, and falling back to defaults for unspecified flags.
265
12
  *
266
13
  * Resolution order (highest to lowest priority):
14
+ *
267
15
  * 1. Channel's explicit profile property (if specified)
268
- * 2. URL-based detection via DOMAIN_TO_PROFILE
16
+ * 2. URL-based detection via DOMAIN_CONFIG
269
17
  * 3. DEFAULT_SITE_PROFILE
270
18
  *
271
19
  * Within a profile, inheritance works as follows:
20
+ *
272
21
  * 1. Start with DEFAULT_SITE_PROFILE for base values
273
22
  * 2. Apply parent profile properties (if extends is set)
274
23
  * 3. Apply current profile properties (overriding parent)
@@ -281,6 +30,7 @@ export const DEFAULT_SITE_PROFILE = {
281
30
  * is resolved recursively, with child profile properties overriding parent properties.
282
31
  *
283
32
  * The resolution process:
33
+ *
284
34
  * 1. Start with a copy of DEFAULT_SITE_PROFILE
285
35
  * 2. If the profile extends another, recursively resolve the parent and merge its properties
286
36
  * 3. Merge the current profile's properties, overriding any inherited values
@@ -309,20 +59,17 @@ export function resolveProfile(profileName) {
309
59
  const parent = resolveProfile(profile.extends);
310
60
  resolved = { ...resolved, ...parent };
311
61
  }
312
- // Apply current profile properties, excluding metadata fields that should not be inherited. The description and extends properties are for documentation and
313
- // inheritance specification only - they should not appear in the resolved profile.
62
+ // Apply current profile properties, excluding metadata fields that should not be in the resolved profile. The category, description, extends, and summary
63
+ // properties are for UI categorization, documentation, inheritance specification, and UI display only.
314
64
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
315
- const { description: _description, extends: _extends, ...profileFlags } = profile;
65
+ const { category: _category, description: _description, extends: _extends, summary: _summary, ...profileFlags } = profile;
316
66
  resolved = { ...resolved, ...profileFlags };
317
67
  return resolved;
318
68
  }
319
69
  /**
320
- * Resolves the site profile for a given URL by checking the domain against DOMAIN_TO_PROFILE. This function handles partial domain matching to support subdomains
321
- * and path variations. Falls back to the default profile if no matching domain is found.
322
- *
323
- * The matching is done by checking if the URL contains the domain string anywhere. This is simpler than hostname extraction and handles edge cases like URLs with
324
- * unusual formatting.
325
- *
70
+ * Resolves the site profile for a given URL by looking it up in DOMAIN_CONFIG via getDomainConfig(), which tries the full hostname first for subdomain-specific
71
+ * overrides before falling back to the concise domain. Falls back to the default profile if no matching domain is found or the matching domain has no profile
72
+ * configured.
326
73
  * @param url - The URL to resolve a profile for.
327
74
  * @returns The site profile containing behavior flags.
328
75
  */
@@ -331,16 +78,18 @@ export function getProfileForUrl(url) {
331
78
  if (!url) {
332
79
  return { profile: { ...DEFAULT_SITE_PROFILE }, profileName: "default" };
333
80
  }
334
- // Check each known domain pattern to see if the URL contains it. The first match wins, so more specific domains should be listed first in DOMAIN_TO_PROFILE
335
- // if there's potential overlap.
336
- for (const domain of Object.keys(DOMAIN_TO_PROFILE)) {
337
- if (url.indexOf(domain) !== -1) {
338
- const profileName = DOMAIN_TO_PROFILE[domain];
339
- return { profile: resolveProfile(profileName), profileName };
340
- }
81
+ // Look up the domain configuration, trying the full hostname first for subdomain-specific overrides (e.g., "tv.youtube.com" before "youtube.com").
82
+ const config = getDomainConfig(url);
83
+ // Resolve the profile from the domain configuration, falling back to the default profile for unrecognized domains or domains without a profile entry.
84
+ const profile = config?.profile ? resolveProfile(config.profile) : { ...DEFAULT_SITE_PROFILE };
85
+ const profileName = config?.profile ?? "default";
86
+ // Merge domain-level properties that represent site policies rather than player behaviors. maxContinuousPlayback is a site-imposed session limit, not a player
87
+ // characteristic, so it lives in DOMAIN_CONFIG rather than in site profiles. Note: getProfileForChannel() performs this same merge for the explicit-profile path
88
+ // where this function is bypassed. If adding new domain-level policies here, update getProfileForChannel() as well.
89
+ if (config?.maxContinuousPlayback !== undefined) {
90
+ profile.maxContinuousPlayback = config.maxContinuousPlayback;
341
91
  }
342
- // No matching domain found - use the default profile.
343
- return { profile: { ...DEFAULT_SITE_PROFILE }, profileName: "default" };
92
+ return { profile, profileName };
344
93
  }
345
94
  /**
346
95
  * Resolves the site profile for a channel. Channels can explicitly declare their profile by name, which takes precedence over URL-based detection. This is useful
@@ -380,6 +129,15 @@ export function getProfileForChannel(channel) {
380
129
  profile = { ...DEFAULT_SITE_PROFILE };
381
130
  profileName = "default";
382
131
  }
132
+ // Merge domain-level site policies that apply regardless of how the profile was resolved. These represent site-imposed constraints (like session duration limits)
133
+ // rather than player behaviors, so they must always be applied based on the channel's URL even when the player profile is explicitly overridden. For the URL-based
134
+ // path above, getProfileForUrl() already merges these — the re-application here is idempotent. For the explicit-profile path, this fills the gap.
135
+ if (channel.url) {
136
+ const domainConfig = getDomainConfig(channel.url);
137
+ if (domainConfig?.maxContinuousPlayback !== undefined) {
138
+ profile = { ...profile, maxContinuousPlayback: domainConfig.maxContinuousPlayback };
139
+ }
140
+ }
383
141
  // Merge channel-specific properties into the profile. Currently only channelSelector is supported, which specifies a CSS selector for the channel button in
384
142
  // multi-channel player pages. The channelSelector on the channel overrides any channelSelector from the profile.
385
143
  if (channel.channelSelector) {
@@ -388,8 +146,6 @@ export function getProfileForChannel(channel) {
388
146
  return { profile, profileName };
389
147
  }
390
148
  /*
391
- * PROFILE VALIDATION
392
- *
393
149
  * Before starting the server, we validate all profile configurations to catch errors early. Invalid configurations would cause runtime failures that are difficult
394
150
  * to diagnose:
395
151
  *
@@ -406,6 +162,7 @@ export function getProfileForChannel(channel) {
406
162
  * function runs at startup before the server begins accepting connections.
407
163
  *
408
164
  * Validation checks:
165
+ *
409
166
  * 1. Circular inheritance detection - walks the extends chain for each profile to detect cycles
410
167
  * 2. Invalid extends references - ensures all extends targets exist
411
168
  * 3. Domain mapping validation - ensures all domain profile references exist
@@ -438,11 +195,13 @@ export function validateProfiles() {
438
195
  }
439
196
  }
440
197
  }
441
- // Validate all domain-to-profile mappings reference existing profiles. This catches typos in DOMAIN_TO_PROFILE.
442
- for (const [domain, domainProfileName] of Object.entries(DOMAIN_TO_PROFILE)) {
443
- const domainProfile = SITE_PROFILES[domainProfileName];
444
- if (!domainProfile) {
445
- errors.push(["Domain ", domain, " references non-existent profile: ", domainProfileName].join(""));
198
+ // Validate all domain-to-profile mappings reference existing profiles. This catches typos in DOMAIN_CONFIG profile entries.
199
+ for (const [domain, config] of Object.entries(DOMAIN_CONFIG)) {
200
+ if (config.profile) {
201
+ const domainProfile = SITE_PROFILES[config.profile];
202
+ if (!domainProfile) {
203
+ errors.push(["Domain ", domain, " references non-existent profile: ", config.profile].join(""));
204
+ }
446
205
  }
447
206
  }
448
207
  // Validate all channel profile references point to existing profiles. This catches typos in channel definitions.
@@ -459,14 +218,15 @@ export function validateProfiles() {
459
218
  }
460
219
  }
461
220
  /**
462
- * Returns all profiles with their descriptions and summaries, sorted alphabetically by name. Used by the channel configuration UI to populate the profile
463
- * dropdown with tooltips and the profile reference section.
221
+ * Returns all profiles with their descriptions, categories, and summaries, sorted alphabetically by name. Used by the channel configuration UI to populate the
222
+ * profile dropdown with tooltips and the profile reference section.
464
223
  * @returns Array of profile info objects.
465
224
  */
466
225
  export function getProfiles() {
467
226
  return Object.keys(SITE_PROFILES).sort().map((name) => {
468
227
  const profile = SITE_PROFILES[name];
469
228
  return {
229
+ category: profile.category ?? "special",
470
230
  description: profile.description ?? "",
471
231
  name,
472
232
  summary: profile.summary ?? profile.description ?? ""
@@ -1 +1 @@
1
- {"version":3,"file":"profiles.js","sourceRoot":"","sources":["../../src/config/profiles.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"profiles.js","sourceRoot":"","sources":["../../src/config/profiles.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,75 @@
1
+ import type { Channel, ChannelMap, ProviderGroup } from "../types/index.js";
2
+ /**
3
+ * Builds provider groups by scanning all channels and grouping them by key patterns. A key like "espn-disneyplus" is a variant of "espn" because it starts with
4
+ * "espn-". Should be called at startup after channels are loaded.
5
+ * @param channels - The merged channel map (predefined + user channels).
6
+ */
7
+ export declare function buildProviderGroups(channels: ChannelMap): void;
8
+ /**
9
+ * Resolves a URL to a friendly provider display name. Checks DOMAIN_CONFIG via getDomainConfig() for a provider name, trying the full hostname first for
10
+ * subdomain-specific overrides before falling back to the concise domain. Returns the raw domain string if no provider name is configured.
11
+ * @param url - The URL to resolve a provider display name for.
12
+ * @returns The provider display name, or the concise domain if no provider name is configured.
13
+ */
14
+ export declare function getProviderDisplayName(url: string): string;
15
+ /**
16
+ * Gets the provider group for a channel key. Works with both canonical and variant keys.
17
+ * @param key - Any channel key in the group.
18
+ * @returns The provider group if the channel is part of a multi-provider group, undefined otherwise.
19
+ */
20
+ export declare function getProviderGroup(key: string): ProviderGroup | undefined;
21
+ /**
22
+ * Checks if a channel key is a non-canonical provider variant. Used to filter variants from channel listings.
23
+ * @param key - The channel key to check.
24
+ * @returns True if the key is a variant (not canonical) in a provider group.
25
+ */
26
+ export declare function isProviderVariant(key: string): boolean;
27
+ /**
28
+ * Checks if a channel has multiple provider options. Used to determine whether to show a provider dropdown in the UI.
29
+ * @param key - The channel key to check.
30
+ * @returns True if the channel has more than one provider variant.
31
+ */
32
+ export declare function hasMultipleProviders(key: string): boolean;
33
+ /**
34
+ * Gets the canonical key for any channel key. For variant keys, returns the canonical key. For non-grouped or canonical keys, returns the input unchanged.
35
+ * Handles the PREDEFINED_SUFFIX used when a user has overridden a predefined channel.
36
+ * @param key - Any channel key.
37
+ * @returns The canonical key for the channel's provider group, or the input key if not part of a group.
38
+ */
39
+ export declare function getCanonicalKey(key: string): string;
40
+ /**
41
+ * Sets the user's provider selections. Called when loading from channels.json.
42
+ * @param selections - Provider selections keyed by canonical channel key.
43
+ */
44
+ export declare function setProviderSelections(selections: Record<string, string>): void;
45
+ /**
46
+ * Gets all provider selections.
47
+ * @returns Copy of the provider selections object.
48
+ */
49
+ export declare function getProviderSelections(): Record<string, string>;
50
+ /**
51
+ * Gets the provider selection for a specific channel.
52
+ * @param canonicalKey - The canonical channel key.
53
+ * @returns The selected provider key, or undefined if using the default.
54
+ */
55
+ export declare function getProviderSelection(canonicalKey: string): string | undefined;
56
+ /**
57
+ * Sets the provider selection for a channel.
58
+ * @param canonicalKey - The canonical channel key.
59
+ * @param providerKey - The selected provider key.
60
+ */
61
+ export declare function setProviderSelection(canonicalKey: string, providerKey: string): void;
62
+ /**
63
+ * Resolves a canonical channel key to the actual channel key based on user selection. If the user has selected a specific provider for this channel, returns that
64
+ * provider's key. Otherwise returns the canonical key (default provider).
65
+ * @param canonicalKey - The canonical channel key.
66
+ * @returns The resolved provider key to use for streaming.
67
+ */
68
+ export declare function resolveProviderKey(canonicalKey: string): string;
69
+ /**
70
+ * Gets a channel with inheritance applied. For provider variants, this merges the variant's properties with inherited properties from the canonical entry.
71
+ * Inherited properties: `name`, `stationId`. Explicit properties on the variant take precedence.
72
+ * @param key - The channel key (canonical or variant).
73
+ * @returns The complete channel with inheritance applied, or undefined if the channel doesn't exist.
74
+ */
75
+ export declare function getResolvedChannel(key: string): Channel | undefined;