@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,157 @@
1
+ /* Copyright(C) 2024-2026, HJD (https://github.com/hjdhjd). All rights reserved.
2
+ *
3
+ * debugFilter.ts: Category-based debug log filtering for PrismCast.
4
+ */
5
+ /* The debug filter provides category-based control over debug log output, inspired by the `debug` npm package. Categories use colon-separated namespaces
6
+ * (e.g., "tuning:hulu", "recovery:tab") and the PRISMCAST_DEBUG environment variable accepts comma-separated patterns with wildcard and exclusion support.
7
+ *
8
+ * Pattern syntax:
9
+ * - "*" enables all categories.
10
+ * - "category" enables an exact category or any sub-category (prefix match).
11
+ * - "-category" excludes a category or its sub-categories, even when wildcard is active.
12
+ *
13
+ * Examples:
14
+ * PRISMCAST_DEBUG=tuning:hulu Only Hulu tuning messages.
15
+ * PRISMCAST_DEBUG=recovery All recovery sub-categories (recovery:tab, recovery:nav, etc.).
16
+ * PRISMCAST_DEBUG=*,-streaming:ffmpeg,-streaming:segmenter Everything except FFmpeg and segmenter messages.
17
+ */
18
+ // Whether any debug output is configured at all. Fast-path check avoids category string work when debug is off.
19
+ let anyEnabled = false;
20
+ // Whether wildcard (*) was specified — all categories pass unless explicitly excluded.
21
+ let wildcardEnabled = false;
22
+ // Categories to include (exact or prefix match).
23
+ const includeSet = new Set();
24
+ // Categories to exclude (exact or prefix match). Takes priority over includes and wildcard.
25
+ const excludeSet = new Set();
26
+ /**
27
+ * Checks whether a category matches any pattern in the given set. A pattern matches if it equals the category exactly or if the category starts with the pattern
28
+ * followed by a colon (prefix match for sub-categories).
29
+ * @param category - The category to check.
30
+ * @param patterns - The set of patterns to match against.
31
+ * @returns True if the category matches any pattern.
32
+ */
33
+ function matchesAny(category, patterns) {
34
+ if (patterns.has(category)) {
35
+ return true;
36
+ }
37
+ for (const pattern of patterns) {
38
+ if (category.startsWith(pattern + ":")) {
39
+ return true;
40
+ }
41
+ }
42
+ return false;
43
+ }
44
+ /**
45
+ * Parses a comma-separated pattern string and configures the debug filter. Calling this function replaces any previous filter configuration.
46
+ * @param pattern - Comma-separated list of category patterns (e.g., "tuning:hulu,recovery,-streaming:ffmpeg").
47
+ */
48
+ export function initDebugFilter(pattern) {
49
+ // Reset state.
50
+ includeSet.clear();
51
+ excludeSet.clear();
52
+ wildcardEnabled = false;
53
+ anyEnabled = false;
54
+ const parts = pattern.split(",").map((p) => p.trim()).filter((p) => p.length > 0);
55
+ if (parts.length === 0) {
56
+ return;
57
+ }
58
+ for (const part of parts) {
59
+ if (part === "*") {
60
+ wildcardEnabled = true;
61
+ }
62
+ else if (part.startsWith("-")) {
63
+ excludeSet.add(part.substring(1));
64
+ }
65
+ else {
66
+ includeSet.add(part);
67
+ }
68
+ }
69
+ anyEnabled = true;
70
+ }
71
+ /**
72
+ * Checks whether a specific debug category is enabled under the current filter configuration.
73
+ * @param category - The category to check (e.g., "tuning:hulu", "recovery:tab").
74
+ * @returns True if debug output should be produced for this category.
75
+ */
76
+ export function isCategoryEnabled(category) {
77
+ if (!anyEnabled) {
78
+ return false;
79
+ }
80
+ // Excludes always win, even over wildcard.
81
+ if (matchesAny(category, excludeSet)) {
82
+ return false;
83
+ }
84
+ if (wildcardEnabled) {
85
+ return true;
86
+ }
87
+ return matchesAny(category, includeSet);
88
+ }
89
+ /**
90
+ * Fast-path check for whether any debug categories are configured. When this returns false, callers can skip category string construction entirely.
91
+ * @returns True if at least one debug category is enabled.
92
+ */
93
+ export function isAnyDebugEnabled() {
94
+ return anyEnabled;
95
+ }
96
+ /**
97
+ * Reconstructs the current filter pattern string from internal state. Returns an empty string when no debug output is configured.
98
+ * @returns The current pattern string (e.g., "*,-streaming:ffmpeg" or "tuning:hulu,recovery").
99
+ */
100
+ export function getCurrentPattern() {
101
+ if (!anyEnabled) {
102
+ return "";
103
+ }
104
+ const parts = [];
105
+ if (wildcardEnabled) {
106
+ parts.push("*");
107
+ }
108
+ // Exclude entries are prefixed with "-".
109
+ for (const entry of excludeSet) {
110
+ parts.push("-" + entry);
111
+ }
112
+ // Include entries are bare category names.
113
+ for (const entry of includeSet) {
114
+ parts.push(entry);
115
+ }
116
+ return parts.join(",");
117
+ }
118
+ /**
119
+ * Static registry of all known debug categories with descriptions. Sorted alphabetically by category. The /debug endpoint uses this to render hierarchical checkboxes.
120
+ * Parent groups (e.g., "streaming", "timing", "tuning") are derived by the UI from the colon-separated namespaces — only leaf categories are declared here.
121
+ */
122
+ export const DEBUG_CATEGORIES = [
123
+ { category: "browser", description: "Browser lifecycle: launch, close, stale page cleanup, restart." },
124
+ { category: "browser:video", description: "Video context, fullscreen, volume locking, playback." },
125
+ { category: "config", description: "Provider groups, version checking." },
126
+ { category: "recovery", description: "General recovery: browser re-minimize, monitor abort." },
127
+ { category: "recovery:context", description: "Video context: frame detachment, re-search." },
128
+ { category: "recovery:nav", description: "Page navigation recovery: new tab detection, URL validation." },
129
+ { category: "recovery:segments", description: "Segment production: self-heal detection." },
130
+ { category: "recovery:tab", description: "Tab replacement: old tab cleanup, new tab creation, retries." },
131
+ { category: "retry", description: "Retry attempts, page-closed aborts." },
132
+ { category: "streaming:ffmpeg", description: "FFmpeg stderr output, pipe errors." },
133
+ { category: "streaming:hls", description: "HLS segment storage, page close errors." },
134
+ { category: "streaming:mpegts", description: "MPEG-TS remuxer errors, client connect/disconnect." },
135
+ { category: "streaming:segmenter", description: "fMP4 parsing: keyframes, init segments, duration clamping." },
136
+ { category: "streaming:setup", description: "Stream setup: redirect resolution, profile override, capture init." },
137
+ { category: "streaming:showinfo", description: "Channels DVR show name lookups, device mapping." },
138
+ { category: "timing:browser", description: "Browser launch: process spawn, extension init, display detection." },
139
+ { category: "timing:hls", description: "HLS playlist delivery time." },
140
+ { category: "timing:recovery", description: "Recovery totals: navigation recovery, tab replacement." },
141
+ { category: "timing:startup", description: "Stream startup: init segment, first playlist, capture ready." },
142
+ { category: "timing:tab", description: "Tab replacement: old tab cleanup, new page creation." },
143
+ { category: "timing:tune", description: "Tune waterfall: navigation, channel selection, video ready." },
144
+ { category: "tuning:hbo", description: "HBO Max: tab URL discovery, channel rail, navigation." },
145
+ { category: "tuning:hulu", description: "Hulu Live guide grid: binary search, cache, click retries." },
146
+ { category: "tuning:sling", description: "Sling TV guide grid: binary search, cache, click retries." },
147
+ { category: "tuning:yttv", description: "YouTube TV EPG grid navigation." }
148
+ ];
149
+ /**
150
+ * Creates a lightweight elapsed-time closure using performance.now(). Call the returned function to get the elapsed milliseconds since creation.
151
+ * @returns A closure that returns elapsed milliseconds as a number.
152
+ */
153
+ export function startTimer() {
154
+ const start = performance.now();
155
+ return () => Math.round(performance.now() - start);
156
+ }
157
+ //# sourceMappingURL=debugFilter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"debugFilter.js","sourceRoot":"","sources":["../../src/utils/debugFilter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Creates a promise that resolves after the specified delay. This is a convenience wrapper around setTimeout that allows using async/await syntax for delays.
3
+ * @param ms - The delay duration in milliseconds.
4
+ * @returns A promise that resolves after the specified delay.
5
+ */
6
+ export declare function delay(ms: number): Promise<void>;
@@ -0,0 +1,15 @@
1
+ /* Copyright(C) 2024-2026, HJD (https://github.com/hjdhjd). All rights reserved.
2
+ *
3
+ * delay.ts: Async delay utility for PrismCast.
4
+ */
5
+ /**
6
+ * Creates a promise that resolves after the specified delay. This is a convenience wrapper around setTimeout that allows using async/await syntax for delays.
7
+ * @param ms - The delay duration in milliseconds.
8
+ * @returns A promise that resolves after the specified delay.
9
+ */
10
+ export async function delay(ms) {
11
+ return new Promise((resolve) => {
12
+ setTimeout(resolve, ms);
13
+ });
14
+ }
15
+ //# sourceMappingURL=delay.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delay.js","sourceRoot":"","sources":["../../src/utils/delay.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,KAAK,CAAC,EAAU;IAEpC,OAAO,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;QAEnC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;IAC1B,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Formats an error for logging by extracting the message if available, falling back to string conversion for non-Error objects. Trailing punctuation is stripped
3
+ * to allow callers to add consistent punctuation in their log format strings.
4
+ * @param error - The error to format.
5
+ * @returns A string representation suitable for logging, without trailing punctuation.
6
+ */
7
+ export declare function formatError(error: unknown): string;
8
+ /**
9
+ * Checks whether an error indicates that the browser page or session has been closed or is otherwise unrecoverable. These errors should cause immediate abort of
10
+ * any retry loops rather than continuing to retry an operation that will never succeed. Common unrecoverable errors include closed targets, closed sessions, and
11
+ * detached frames (which occur when the page has been closed mid-operation).
12
+ * @param error - The error to check.
13
+ * @returns True if the error indicates a closed or unrecoverable state.
14
+ */
15
+ export declare function isSessionClosedError(error: unknown): boolean;
@@ -0,0 +1,40 @@
1
+ /* Copyright(C) 2024-2026, HJD (https://github.com/hjdhjd). All rights reserved.
2
+ *
3
+ * errors.ts: Error formatting and handling utilities for PrismCast.
4
+ */
5
+ /* These utilities provide consistent error handling and formatting throughout the application. The formatError function extracts meaningful messages from various
6
+ * error types, while isSessionClosedError helps identify unrecoverable browser state errors that should abort retry loops.
7
+ */
8
+ /**
9
+ * Formats an error for logging by extracting the message if available, falling back to string conversion for non-Error objects. Trailing punctuation is stripped
10
+ * to allow callers to add consistent punctuation in their log format strings.
11
+ * @param error - The error to format.
12
+ * @returns A string representation suitable for logging, without trailing punctuation.
13
+ */
14
+ export function formatError(error) {
15
+ let message;
16
+ if (error instanceof Error) {
17
+ message = error.message;
18
+ }
19
+ else if (error && (typeof error.message === "string")) {
20
+ message = error.message;
21
+ }
22
+ else {
23
+ message = String(error);
24
+ }
25
+ // Strip trailing punctuation to prevent double punctuation when callers add their own.
26
+ return message.replace(/[.!?]+$/, "");
27
+ }
28
+ /**
29
+ * Checks whether an error indicates that the browser page or session has been closed or is otherwise unrecoverable. These errors should cause immediate abort of
30
+ * any retry loops rather than continuing to retry an operation that will never succeed. Common unrecoverable errors include closed targets, closed sessions, and
31
+ * detached frames (which occur when the page has been closed mid-operation).
32
+ * @param error - The error to check.
33
+ * @returns True if the error indicates a closed or unrecoverable state.
34
+ */
35
+ export function isSessionClosedError(error) {
36
+ const message = formatError(error);
37
+ const unrecoverablePatterns = ["Target closed", "Session closed", "detached Frame"];
38
+ return unrecoverablePatterns.some((pattern) => message.includes(pattern));
39
+ }
40
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/utils/errors.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;GAEG;AAEH;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,KAAc;IAExC,IAAI,OAAe,CAAC;IAEpB,IAAG,KAAK,YAAY,KAAK,EAAE,CAAC;QAE1B,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC;IAC1B,CAAC;SAAM,IAAG,KAAK,IAAI,CAAC,OAAQ,KAA+B,CAAC,OAAO,KAAK,QAAQ,CAAC,EAAE,CAAC;QAElF,OAAO,GAAI,KAA6B,CAAC,OAAO,CAAC;IACnD,CAAC;SAAM,CAAC;QAEN,OAAO,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAC1B,CAAC;IAED,uFAAuF;IACvF,OAAO,OAAO,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;AACxC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAAC,KAAc;IAEjD,MAAM,OAAO,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;IAEnC,MAAM,qBAAqB,GAAG,CAAE,eAAe,EAAE,gBAAgB,EAAE,gBAAgB,CAAE,CAAC;IAEtF,OAAO,qBAAqB,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC;AAC5E,CAAC"}
@@ -0,0 +1,51 @@
1
+ import type { Frame, Page } from "puppeteer-core";
2
+ /**
3
+ * Registers an AbortController for a stream. Called when a stream is created.
4
+ * @param streamIdStr - The stream ID string (e.g., "cnn-5jecl6").
5
+ * @param controller - The AbortController for this stream.
6
+ */
7
+ export declare function registerAbortController(streamIdStr: string, controller: AbortController): void;
8
+ /**
9
+ * Unregisters an AbortController for a stream. Called when a stream is terminated.
10
+ * @param streamIdStr - The stream ID string.
11
+ */
12
+ export declare function unregisterAbortController(streamIdStr: string): void;
13
+ /**
14
+ * Gets the AbortSignal for a stream, if one exists.
15
+ * @param streamIdStr - The stream ID string.
16
+ * @returns The AbortSignal if found, undefined otherwise.
17
+ */
18
+ export declare function getAbortSignal(streamIdStr: string): AbortSignal | undefined;
19
+ /**
20
+ * Gets the AbortController for a stream, if one exists.
21
+ * @param streamIdStr - The stream ID string.
22
+ * @returns The AbortController if found, undefined otherwise.
23
+ */
24
+ export declare function getAbortController(streamIdStr: string): AbortController | undefined;
25
+ /**
26
+ * Custom error class for timeout errors. This allows callers to distinguish timeout errors from other errors.
27
+ */
28
+ export declare class EvaluateTimeoutError extends Error {
29
+ constructor(timeoutMs: number);
30
+ }
31
+ /**
32
+ * Custom error class for abort errors. This allows callers to distinguish abort errors from other errors.
33
+ */
34
+ export declare class EvaluateAbortError extends Error {
35
+ constructor();
36
+ }
37
+ /**
38
+ * Executes a Puppeteer evaluate call with abort and timeout support. This wrapper provides immediate cancellation when a stream is terminated and a safety timeout to
39
+ * prevent hanging on unresponsive browsers.
40
+ *
41
+ * If running within a stream context (via AsyncLocalStorage), the abort signal for that stream is used. If no stream context is available, only the timeout is applied.
42
+ * @param context - The Page or Frame to evaluate in.
43
+ * @param pageFunction - The function to evaluate in the browser context.
44
+ * @param args - Arguments to pass to the function (optional).
45
+ * @param timeoutMs - Timeout in milliseconds (default: 15000).
46
+ * @returns The result of the evaluate call.
47
+ * @throws EvaluateAbortError if the stream was terminated.
48
+ * @throws EvaluateTimeoutError if the timeout was reached.
49
+ * @throws Any error from the underlying evaluate call.
50
+ */
51
+ export declare function evaluateWithAbort<T, Args extends unknown[]>(context: Frame | Page, pageFunction: (...args: Args) => T, args?: Args, timeoutMs?: number): Promise<T>;
@@ -0,0 +1,124 @@
1
+ import { getStreamId } from "./streamContext.js";
2
+ /* This module provides a wrapper around Puppeteer's page.evaluate() and frame.evaluate() that adds two critical safety mechanisms:
3
+ *
4
+ * 1. Abort signal: When a stream is terminated, its AbortController is triggered, immediately rejecting all pending evaluate calls for that stream. This prevents zombie
5
+ * CDP calls from hanging for 180 seconds (Puppeteer's default protocolTimeout) when the browser becomes unresponsive.
6
+ *
7
+ * 2. Timeout: A configurable timeout (default 15 seconds) provides a safety net for evaluate calls that hang. This catches cases where the browser is unresponsive but
8
+ * the stream hasn't been explicitly terminated yet.
9
+ *
10
+ * The wrapper automatically retrieves the abort signal from the stream registry using the stream context from AsyncLocalStorage. If no stream context is available
11
+ * (e.g., during browser initialization), it falls back to timeout-only behavior.
12
+ *
13
+ * IMPORTANT: When aborting or timing out, the underlying CDP call is still pending in Puppeteer - we just stop waiting for it locally. We attach a no-op .catch() to
14
+ * the evaluate promise to suppress unhandled rejection warnings when the CDP call eventually completes or times out.
15
+ */
16
+ // Default timeout for evaluate calls in milliseconds.
17
+ const DEFAULT_EVALUATE_TIMEOUT = 15000;
18
+ // Map of stream ID strings to their AbortControllers. Uses string IDs (e.g., "cnn-5jecl6") since that's what the stream context provides via AsyncLocalStorage.
19
+ const abortControllers = new Map();
20
+ /**
21
+ * Registers an AbortController for a stream. Called when a stream is created.
22
+ * @param streamIdStr - The stream ID string (e.g., "cnn-5jecl6").
23
+ * @param controller - The AbortController for this stream.
24
+ */
25
+ export function registerAbortController(streamIdStr, controller) {
26
+ abortControllers.set(streamIdStr, controller);
27
+ }
28
+ /**
29
+ * Unregisters an AbortController for a stream. Called when a stream is terminated.
30
+ * @param streamIdStr - The stream ID string.
31
+ */
32
+ export function unregisterAbortController(streamIdStr) {
33
+ abortControllers.delete(streamIdStr);
34
+ }
35
+ /**
36
+ * Gets the AbortSignal for a stream, if one exists.
37
+ * @param streamIdStr - The stream ID string.
38
+ * @returns The AbortSignal if found, undefined otherwise.
39
+ */
40
+ export function getAbortSignal(streamIdStr) {
41
+ return abortControllers.get(streamIdStr)?.signal;
42
+ }
43
+ /**
44
+ * Gets the AbortController for a stream, if one exists.
45
+ * @param streamIdStr - The stream ID string.
46
+ * @returns The AbortController if found, undefined otherwise.
47
+ */
48
+ export function getAbortController(streamIdStr) {
49
+ return abortControllers.get(streamIdStr);
50
+ }
51
+ /**
52
+ * Custom error class for timeout errors. This allows callers to distinguish timeout errors from other errors.
53
+ */
54
+ export class EvaluateTimeoutError extends Error {
55
+ constructor(timeoutMs) {
56
+ super("Evaluate timed out after " + String(timeoutMs) + "ms.");
57
+ this.name = "EvaluateTimeoutError";
58
+ }
59
+ }
60
+ /**
61
+ * Custom error class for abort errors. This allows callers to distinguish abort errors from other errors.
62
+ */
63
+ export class EvaluateAbortError extends Error {
64
+ constructor() {
65
+ super("Evaluate aborted due to stream termination.");
66
+ this.name = "EvaluateAbortError";
67
+ }
68
+ }
69
+ /**
70
+ * Executes a Puppeteer evaluate call with abort and timeout support. This wrapper provides immediate cancellation when a stream is terminated and a safety timeout to
71
+ * prevent hanging on unresponsive browsers.
72
+ *
73
+ * If running within a stream context (via AsyncLocalStorage), the abort signal for that stream is used. If no stream context is available, only the timeout is applied.
74
+ * @param context - The Page or Frame to evaluate in.
75
+ * @param pageFunction - The function to evaluate in the browser context.
76
+ * @param args - Arguments to pass to the function (optional).
77
+ * @param timeoutMs - Timeout in milliseconds (default: 15000).
78
+ * @returns The result of the evaluate call.
79
+ * @throws EvaluateAbortError if the stream was terminated.
80
+ * @throws EvaluateTimeoutError if the timeout was reached.
81
+ * @throws Any error from the underlying evaluate call.
82
+ */
83
+ export async function evaluateWithAbort(context, pageFunction, args, timeoutMs) {
84
+ const timeout = timeoutMs ?? DEFAULT_EVALUATE_TIMEOUT;
85
+ // Get stream context to find the abort signal.
86
+ const streamIdStr = getStreamId();
87
+ const signal = streamIdStr !== undefined ? getAbortSignal(streamIdStr) : undefined;
88
+ // Check if already aborted before starting.
89
+ if (signal?.aborted) {
90
+ throw new EvaluateAbortError();
91
+ }
92
+ // Start the evaluate call. We use 'as unknown as' to bypass TypeScript's strict function signature checking since Puppeteer's evaluate accepts various function
93
+ // signatures that are difficult to type precisely.
94
+ const evaluatePromise = args ?
95
+ context.evaluate(pageFunction, ...args) :
96
+ context.evaluate(pageFunction);
97
+ // Attach a no-op catch to suppress unhandled rejection warnings. When we abort or timeout, the underlying CDP call is still pending and will eventually resolve or
98
+ // reject. Without this, we'd get unhandled rejection warnings when the CDP call completes after we've moved on.
99
+ evaluatePromise.catch(() => { });
100
+ // Create the timeout promise.
101
+ const timeoutPromise = new Promise((_, reject) => {
102
+ setTimeout(() => {
103
+ reject(new EvaluateTimeoutError(timeout));
104
+ }, timeout);
105
+ });
106
+ // If we have an abort signal, create an abort promise.
107
+ if (signal) {
108
+ const abortPromise = new Promise((_, reject) => {
109
+ // Check if already aborted (race condition protection).
110
+ if (signal.aborted) {
111
+ reject(new EvaluateAbortError());
112
+ return;
113
+ }
114
+ signal.addEventListener("abort", () => {
115
+ reject(new EvaluateAbortError());
116
+ }, { once: true });
117
+ });
118
+ // Race all three: evaluate, timeout, and abort.
119
+ return Promise.race([evaluatePromise, timeoutPromise, abortPromise]);
120
+ }
121
+ // No abort signal available, just race evaluate against timeout.
122
+ return Promise.race([evaluatePromise, timeoutPromise]);
123
+ }
124
+ //# sourceMappingURL=evaluate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"evaluate.js","sourceRoot":"","sources":["../../src/utils/evaluate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,65 @@
1
+ import type { Readable, Writable } from "node:stream";
2
+ import type { ChildProcess } from "node:child_process";
3
+ /**
4
+ * Resolves the FFmpeg executable path. Checks Channels DVR (macOS, Windows, Linux), then the bundled ffmpeg-for-homebridge, then system PATH. The resolved path is
5
+ * cached for subsequent calls.
6
+ * @returns Promise resolving to the FFmpeg path if found, or undefined if not available.
7
+ */
8
+ export declare function resolveFFmpegPath(): Promise<string | undefined>;
9
+ /**
10
+ * Result from spawning an FFmpeg process.
11
+ */
12
+ export interface FFmpegProcess {
13
+ kill: () => void;
14
+ process: ChildProcess;
15
+ stdin: Writable;
16
+ stdout: Readable;
17
+ }
18
+ /**
19
+ * Spawns an FFmpeg process configured to transcode WebM (H264+Opus) to fMP4 (H264+AAC). The process reads from stdin and writes to stdout, allowing it to be
20
+ * integrated into a Node.js stream pipeline. Video is passed through unchanged; audio is transcoded from Opus to AAC for HLS compatibility.
21
+ *
22
+ * FFmpeg arguments:
23
+ * - `-hide_banner -loglevel warning`: Reduce noise, only show warnings/errors
24
+ * - `-probesize 16384`: Limit input probing to 16KB (Chrome's WebM header fits well under this) to minimize startup delay
25
+ * - `-i pipe:0`: Read input from stdin
26
+ * - `-c:v copy`: Copy video stream without re-encoding (H264 passthrough)
27
+ * - `-c:a aac -b:a <bitrate>`: Transcode audio to AAC at specified bitrate
28
+ * - `-f mp4`: Output MP4 container format
29
+ * - `-movflags frag_keyframe+empty_moov+default_base_moof`: Streaming-friendly fMP4 flags
30
+ * - `-flush_packets 1`: Flush output immediately after each packet to minimize latency
31
+ * - `pipe:1`: Write output to stdout
32
+ * @param audioBitrate - Audio bitrate in bits per second (e.g., 256000 for 256 kbps).
33
+ * @param onError - Callback invoked when FFmpeg exits unexpectedly or encounters an error.
34
+ * @param streamId - Stream identifier for logging.
35
+ * @param comment - Optional comment metadata (channel name or domain) to embed in the output.
36
+ * @returns FFmpeg process wrapper with stdin, stdout, and kill function.
37
+ */
38
+ export declare function spawnFFmpeg(audioBitrate: number, onError: (error: Error) => void, streamId?: string, comment?: string): FFmpegProcess;
39
+ /**
40
+ * Spawns an FFmpeg process configured to remux fMP4 input to MPEG-TS output with codec copy. The process reads a continuous fMP4 stream (init segment followed by
41
+ * media segments) from stdin and writes MPEG-TS to stdout. No transcoding occurs — both video (H264) and audio (AAC) are copied unchanged — so CPU usage is minimal.
42
+ *
43
+ * FFmpeg arguments:
44
+ * - `-hide_banner -loglevel warning`: Reduce noise, only show warnings/errors
45
+ * - `-probesize 16384`: Limit input probing to 16KB (fMP4 init segment is ~1.3KB) to minimize startup delay
46
+ * - `-f mp4 -i pipe:0`: Read fragmented MP4 from stdin
47
+ * - `-c copy`: Copy both video and audio codecs without transcoding
48
+ * - `-f mpegts`: Output MPEG-TS container format
49
+ * - `-mpegts_pmt_start_pid 0x0020`: Use ATSC-conventional PMT PID range instead of FFmpeg's default (0x1000). Minimum allowed value is 0x0020 (32).
50
+ * - `-mpegts_start_pid 0x0031`: Use ATSC-conventional elementary stream PIDs instead of FFmpeg's defaults (0x100+)
51
+ * - `-mpegts_service_type digital_tv`: Label the service as digital TV in the PMT service descriptor
52
+ * - `-pat_period 0.1`: Repeat PAT/PMT tables every 100ms, matching ATSC broadcast frequency
53
+ * - `-pcr_period 40`: Insert PCR timestamps every 40ms, matching ATSC broadcast convention
54
+ * - `-flush_packets 1`: Flush output immediately after each packet to minimize latency
55
+ * - `pipe:1`: Write output to stdout
56
+ * @param onError - Callback invoked when FFmpeg exits unexpectedly or encounters an error.
57
+ * @param streamId - Optional stream identifier for logging.
58
+ * @returns FFmpeg process wrapper with stdin, stdout, and kill function.
59
+ */
60
+ export declare function spawnMpegTsRemuxer(onError: (error: Error) => void, streamId?: string): FFmpegProcess;
61
+ /**
62
+ * Checks if FFmpeg is available on the system. This resolves the FFmpeg path and caches it for use by spawnFFmpeg().
63
+ * @returns Promise resolving to true if FFmpeg is available, false otherwise.
64
+ */
65
+ export declare function isFFmpegAvailable(): Promise<boolean>;