prismcast 1.9.0 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (360) hide show
  1. package/dist/app.d.ts +1 -1
  2. package/dist/app.js +27 -9
  3. package/dist/app.js.map +1 -1
  4. package/dist/browser/cdp.js +7 -7
  5. package/dist/browser/cdp.js.map +1 -1
  6. package/dist/browser/channelSelection.d.ts +41 -11
  7. package/dist/browser/channelSelection.js +75 -24
  8. package/dist/browser/channelSelection.js.map +1 -1
  9. package/dist/browser/display.d.ts +1 -1
  10. package/dist/browser/index.d.ts +8 -8
  11. package/dist/browser/index.js +42 -43
  12. package/dist/browser/index.js.map +1 -1
  13. package/dist/browser/login.d.ts +2 -2
  14. package/dist/browser/login.js +1 -1
  15. package/dist/browser/manifestInterceptor.d.ts +77 -0
  16. package/dist/browser/manifestInterceptor.js +323 -0
  17. package/dist/browser/manifestInterceptor.js.map +1 -0
  18. package/dist/browser/precaching.js +4 -4
  19. package/dist/browser/tuning/comcastPolymer.d.ts +1 -1
  20. package/dist/browser/tuning/cox.d.ts +1 -1
  21. package/dist/browser/tuning/directv.d.ts +1 -1
  22. package/dist/browser/tuning/directv.js +61 -62
  23. package/dist/browser/tuning/directv.js.map +1 -1
  24. package/dist/browser/tuning/fox.d.ts +1 -1
  25. package/dist/browser/tuning/fox.js +171 -14
  26. package/dist/browser/tuning/fox.js.map +1 -1
  27. package/dist/browser/tuning/hbo.d.ts +1 -1
  28. package/dist/browser/tuning/hbo.js +6 -6
  29. package/dist/browser/tuning/hbo.js.map +1 -1
  30. package/dist/browser/tuning/hulu.d.ts +1 -1
  31. package/dist/browser/tuning/hulu.js +117 -110
  32. package/dist/browser/tuning/hulu.js.map +1 -1
  33. package/dist/browser/tuning/shared.d.ts +3 -3
  34. package/dist/browser/tuning/shared.js +5 -5
  35. package/dist/browser/tuning/sling.d.ts +1 -1
  36. package/dist/browser/tuning/sling.js +28 -28
  37. package/dist/browser/tuning/sling.js.map +1 -1
  38. package/dist/browser/tuning/spectrum.d.ts +1 -1
  39. package/dist/browser/tuning/spectrum.js +21 -22
  40. package/dist/browser/tuning/spectrum.js.map +1 -1
  41. package/dist/browser/tuning/thumbnailRow.d.ts +1 -1
  42. package/dist/browser/tuning/thumbnailRow.js +1 -1
  43. package/dist/browser/tuning/tileClick.d.ts +1 -1
  44. package/dist/browser/tuning/tileClick.js +5 -5
  45. package/dist/browser/tuning/xfinity.d.ts +1 -1
  46. package/dist/browser/tuning/youtubeTv.d.ts +1 -1
  47. package/dist/browser/tuning/youtubeTv.js +17 -18
  48. package/dist/browser/tuning/youtubeTv.js.map +1 -1
  49. package/dist/browser/video.d.ts +20 -6
  50. package/dist/browser/video.js +30 -38
  51. package/dist/browser/video.js.map +1 -1
  52. package/dist/channels/index.d.ts +1 -2
  53. package/dist/channels/index.js +73 -40
  54. package/dist/channels/index.js.map +1 -1
  55. package/dist/config/channelForm.d.ts +58 -0
  56. package/dist/config/channelForm.js +129 -0
  57. package/dist/config/channelForm.js.map +1 -0
  58. package/dist/config/consistencyProbe.d.ts +7 -0
  59. package/dist/config/consistencyProbe.js +143 -0
  60. package/dist/config/consistencyProbe.js.map +1 -0
  61. package/dist/config/health.d.ts +4 -3
  62. package/dist/config/health.js +75 -62
  63. package/dist/config/health.js.map +1 -1
  64. package/dist/config/index.d.ts +1 -1
  65. package/dist/config/index.js +19 -12
  66. package/dist/config/index.js.map +1 -1
  67. package/dist/config/paths.d.ts +9 -3
  68. package/dist/config/paths.js +12 -4
  69. package/dist/config/paths.js.map +1 -1
  70. package/dist/config/persistence.context.d.ts +8 -0
  71. package/dist/config/persistence.context.js +30 -0
  72. package/dist/config/persistence.context.js.map +1 -0
  73. package/dist/config/persistence.d.ts +110 -12
  74. package/dist/config/persistence.js +351 -32
  75. package/dist/config/persistence.js.map +1 -1
  76. package/dist/config/presets.d.ts +14 -4
  77. package/dist/config/presets.js +84 -75
  78. package/dist/config/presets.js.map +1 -1
  79. package/dist/config/profiles.d.ts +2 -2
  80. package/dist/config/profiles.js +7 -9
  81. package/dist/config/profiles.js.map +1 -1
  82. package/dist/config/servicePacks.d.ts +2 -2
  83. package/dist/config/servicePacks.js +28 -28
  84. package/dist/config/servicePacks.js.map +1 -1
  85. package/dist/config/services.d.ts +62 -36
  86. package/dist/config/services.js +150 -136
  87. package/dist/config/services.js.map +1 -1
  88. package/dist/config/sites.d.ts +3 -3
  89. package/dist/config/sites.js +16 -16
  90. package/dist/config/sites.js.map +1 -1
  91. package/dist/config/userChannels.d.ts +500 -41
  92. package/dist/config/userChannels.js +1201 -312
  93. package/dist/config/userChannels.js.map +1 -1
  94. package/dist/config/userConfig.d.ts +103 -10
  95. package/dist/config/userConfig.js +277 -117
  96. package/dist/config/userConfig.js.map +1 -1
  97. package/dist/config/userProfiles.d.ts +4 -2
  98. package/dist/config/userProfiles.js +72 -26
  99. package/dist/config/userProfiles.js.map +1 -1
  100. package/dist/hdhr/channelMap.js +8 -11
  101. package/dist/hdhr/channelMap.js.map +1 -1
  102. package/dist/hdhr/deviceId.js +10 -9
  103. package/dist/hdhr/deviceId.js.map +1 -1
  104. package/dist/hdhr/discover.js +4 -5
  105. package/dist/hdhr/discover.js.map +1 -1
  106. package/dist/hdhr/index.js +6 -10
  107. package/dist/hdhr/index.js.map +1 -1
  108. package/dist/index.js +12 -12
  109. package/dist/index.js.map +1 -1
  110. package/dist/native/codecInference.d.ts +44 -0
  111. package/dist/native/codecInference.js +327 -0
  112. package/dist/native/codecInference.js.map +1 -0
  113. package/dist/native/decrypt.d.ts +1 -1
  114. package/dist/native/index.d.ts +5 -5
  115. package/dist/native/index.js +42 -64
  116. package/dist/native/index.js.map +1 -1
  117. package/dist/native/probe.d.ts +27 -9
  118. package/dist/native/probe.js +158 -67
  119. package/dist/native/probe.js.map +1 -1
  120. package/dist/native/proxy.d.ts +4 -2
  121. package/dist/native/proxy.js +52 -49
  122. package/dist/native/proxy.js.map +1 -1
  123. package/dist/native/tokenExpiry.d.ts +22 -0
  124. package/dist/native/tokenExpiry.js +153 -0
  125. package/dist/native/tokenExpiry.js.map +1 -0
  126. package/dist/routes/auth.js +6 -10
  127. package/dist/routes/auth.js.map +1 -1
  128. package/dist/routes/components.js +2 -2
  129. package/dist/routes/components.js.map +1 -1
  130. package/dist/routes/config/channels/endpoints/browse.d.ts +6 -0
  131. package/dist/routes/config/channels/endpoints/browse.js +195 -0
  132. package/dist/routes/config/channels/endpoints/browse.js.map +1 -0
  133. package/dist/routes/config/channels/endpoints/bulk.d.ts +6 -0
  134. package/dist/routes/config/channels/endpoints/bulk.js +128 -0
  135. package/dist/routes/config/channels/endpoints/bulk.js.map +1 -0
  136. package/dist/routes/config/channels/endpoints/crud.d.ts +6 -0
  137. package/dist/routes/config/channels/endpoints/crud.js +504 -0
  138. package/dist/routes/config/channels/endpoints/crud.js.map +1 -0
  139. package/dist/routes/config/channels/endpoints/importExport.d.ts +6 -0
  140. package/dist/routes/config/channels/endpoints/importExport.js +131 -0
  141. package/dist/routes/config/channels/endpoints/importExport.js.map +1 -0
  142. package/dist/routes/config/channels/endpoints/predefined.d.ts +6 -0
  143. package/dist/routes/config/channels/endpoints/predefined.js +78 -0
  144. package/dist/routes/config/channels/endpoints/predefined.js.map +1 -0
  145. package/dist/routes/config/channels/endpoints/prefs.d.ts +6 -0
  146. package/dist/routes/config/channels/endpoints/prefs.js +58 -0
  147. package/dist/routes/config/channels/endpoints/prefs.js.map +1 -0
  148. package/dist/routes/config/channels/endpoints/service.d.ts +6 -0
  149. package/dist/routes/config/channels/endpoints/service.js +149 -0
  150. package/dist/routes/config/channels/endpoints/service.js.map +1 -0
  151. package/dist/routes/config/channels/endpoints/tags.d.ts +6 -0
  152. package/dist/routes/config/channels/endpoints/tags.js +185 -0
  153. package/dist/routes/config/channels/endpoints/tags.js.map +1 -0
  154. package/dist/routes/config/channels/http/handler.d.ts +10 -0
  155. package/dist/routes/config/channels/http/handler.js +20 -0
  156. package/dist/routes/config/channels/http/handler.js.map +1 -0
  157. package/dist/routes/config/channels/http/playlistHint.d.ts +25 -0
  158. package/dist/routes/config/channels/http/playlistHint.js +40 -0
  159. package/dist/routes/config/channels/http/playlistHint.js.map +1 -0
  160. package/dist/routes/config/channels/http/serviceWarning.d.ts +10 -0
  161. package/dist/routes/config/channels/http/serviceWarning.js +28 -0
  162. package/dist/routes/config/channels/http/serviceWarning.js.map +1 -0
  163. package/dist/routes/config/channels/index.d.ts +3 -3
  164. package/dist/routes/config/channels/index.js +1 -1
  165. package/dist/routes/config/channels/index.js.map +1 -1
  166. package/dist/routes/config/channels/{routes.d.ts → setup.d.ts} +1 -1
  167. package/dist/routes/config/channels/setup.js +23 -0
  168. package/dist/routes/config/channels/setup.js.map +1 -0
  169. package/dist/routes/config/channels/table.d.ts +9 -9
  170. package/dist/routes/config/channels/table.js +124 -76
  171. package/dist/routes/config/channels/table.js.map +1 -1
  172. package/dist/routes/config/http/envelope.d.ts +111 -0
  173. package/dist/routes/config/http/envelope.js +103 -0
  174. package/dist/routes/config/http/envelope.js.map +1 -0
  175. package/dist/routes/config/index.d.ts +9 -9
  176. package/dist/routes/config/index.js +2 -2
  177. package/dist/routes/config/index.js.map +1 -1
  178. package/dist/routes/config/services.js +69 -68
  179. package/dist/routes/config/services.js.map +1 -1
  180. package/dist/routes/config/settings.d.ts +1 -1
  181. package/dist/routes/config/settings.js +45 -38
  182. package/dist/routes/config/settings.js.map +1 -1
  183. package/dist/routes/debug.js +12 -11
  184. package/dist/routes/debug.js.map +1 -1
  185. package/dist/routes/icons.js +2 -2
  186. package/dist/routes/icons.js.map +1 -1
  187. package/dist/routes/logs.js +19 -30
  188. package/dist/routes/logs.js.map +1 -1
  189. package/dist/routes/mpegts.js +1 -1
  190. package/dist/routes/playlist.d.ts +1 -1
  191. package/dist/routes/playlist.js +37 -26
  192. package/dist/routes/playlist.js.map +1 -1
  193. package/dist/routes/root/content.js +27 -6
  194. package/dist/routes/root/content.js.map +1 -1
  195. package/dist/routes/root/index.js +2 -5
  196. package/dist/routes/root/index.js.map +1 -1
  197. package/dist/routes/root/scripts/channels.js +16 -16
  198. package/dist/routes/root/scripts/channels.js.map +1 -1
  199. package/dist/routes/root/scripts/config.js +59 -59
  200. package/dist/routes/root/scripts/config.js.map +1 -1
  201. package/dist/routes/root/scripts/index.d.ts +4 -4
  202. package/dist/routes/root/scripts/shared.js +24 -10
  203. package/dist/routes/root/scripts/shared.js.map +1 -1
  204. package/dist/routes/root/scripts/status.d.ts +4 -0
  205. package/dist/routes/root/scripts/status.handlers.d.ts +182 -0
  206. package/dist/routes/root/scripts/status.handlers.js +713 -0
  207. package/dist/routes/root/scripts/status.handlers.js.map +1 -0
  208. package/dist/routes/root/scripts/status.js +86 -488
  209. package/dist/routes/root/scripts/status.js.map +1 -1
  210. package/dist/routes/root/styles.js +5 -5
  211. package/dist/routes/services.js +18 -15
  212. package/dist/routes/services.js.map +1 -1
  213. package/dist/routes/sse.d.ts +16 -0
  214. package/dist/routes/sse.js +29 -0
  215. package/dist/routes/sse.js.map +1 -0
  216. package/dist/routes/streams.js +10 -23
  217. package/dist/routes/streams.js.map +1 -1
  218. package/dist/routes/ui.d.ts +2 -2
  219. package/dist/routes/upgrade.js +6 -7
  220. package/dist/routes/upgrade.js.map +1 -1
  221. package/dist/service/commands.context.d.ts +6 -0
  222. package/dist/service/commands.context.js +47 -0
  223. package/dist/service/commands.context.js.map +1 -0
  224. package/dist/service/commands.d.ts +53 -8
  225. package/dist/service/commands.js +171 -188
  226. package/dist/service/commands.js.map +1 -1
  227. package/dist/service/generators.context.d.ts +6 -0
  228. package/dist/service/generators.context.js +45 -0
  229. package/dist/service/generators.context.js.map +1 -0
  230. package/dist/service/generators.d.ts +56 -16
  231. package/dist/service/generators.js +499 -383
  232. package/dist/service/generators.js.map +1 -1
  233. package/dist/service/index.d.ts +2 -2
  234. package/dist/streaming/clients.d.ts +2 -2
  235. package/dist/streaming/clients.js +2 -2
  236. package/dist/streaming/codec.d.ts +2 -2
  237. package/dist/streaming/codec.js +0 -1
  238. package/dist/streaming/codec.js.map +1 -1
  239. package/dist/streaming/fmp4Segmenter.d.ts +2 -2
  240. package/dist/streaming/fmp4Segmenter.js +12 -12
  241. package/dist/streaming/hls.d.ts +3 -3
  242. package/dist/streaming/hls.js +81 -70
  243. package/dist/streaming/hls.js.map +1 -1
  244. package/dist/streaming/hlsResume.d.ts +3 -3
  245. package/dist/streaming/hlsResume.js +5 -5
  246. package/dist/streaming/hlsSegments.d.ts +5 -2
  247. package/dist/streaming/hlsSegments.js +14 -9
  248. package/dist/streaming/hlsSegments.js.map +1 -1
  249. package/dist/streaming/monitor.d.ts +12 -12
  250. package/dist/streaming/monitor.js +80 -54
  251. package/dist/streaming/monitor.js.map +1 -1
  252. package/dist/streaming/mp4Parser.d.ts +34 -4
  253. package/dist/streaming/mp4Parser.js +111 -29
  254. package/dist/streaming/mp4Parser.js.map +1 -1
  255. package/dist/streaming/mpegts.d.ts +1 -1
  256. package/dist/streaming/mpegts.js +13 -10
  257. package/dist/streaming/mpegts.js.map +1 -1
  258. package/dist/streaming/playlistBuilder.d.ts +1 -1
  259. package/dist/streaming/playlistBuilder.js +2 -2
  260. package/dist/streaming/preroll.d.ts +9 -9
  261. package/dist/streaming/preroll.js +39 -41
  262. package/dist/streaming/preroll.js.map +1 -1
  263. package/dist/streaming/pretune.js +4 -4
  264. package/dist/streaming/pretune.js.map +1 -1
  265. package/dist/streaming/recovery.d.ts +3 -3
  266. package/dist/streaming/recovery.js +4 -4
  267. package/dist/streaming/registry.d.ts +6 -6
  268. package/dist/streaming/setup.d.ts +9 -8
  269. package/dist/streaming/setup.js +97 -49
  270. package/dist/streaming/setup.js.map +1 -1
  271. package/dist/streaming/showInfo.d.ts +9 -3
  272. package/dist/streaming/showInfo.js +26 -18
  273. package/dist/streaming/showInfo.js.map +1 -1
  274. package/dist/streaming/statusEmitter.d.ts +4 -4
  275. package/dist/streaming/statusEmitter.js +2 -2
  276. package/dist/types/channels.d.ts +133 -34
  277. package/dist/types/channels.js +39 -1
  278. package/dist/types/channels.js.map +1 -1
  279. package/dist/types/config.d.ts +20 -1
  280. package/dist/types/index.d.ts +9 -8
  281. package/dist/types/index.js +1 -0
  282. package/dist/types/index.js.map +1 -1
  283. package/dist/types/profiles.d.ts +5 -4
  284. package/dist/types/selection.d.ts +48 -4
  285. package/dist/types/streaming.d.ts +1 -1
  286. package/dist/upgrade/commands.context.d.ts +6 -0
  287. package/dist/upgrade/commands.context.js +29 -0
  288. package/dist/upgrade/commands.context.js.map +1 -0
  289. package/dist/upgrade/commands.d.ts +29 -2
  290. package/dist/upgrade/commands.js +87 -134
  291. package/dist/upgrade/commands.js.map +1 -1
  292. package/dist/upgrade/detection.context.d.ts +6 -0
  293. package/dist/upgrade/detection.context.js +24 -0
  294. package/dist/upgrade/detection.context.js.map +1 -0
  295. package/dist/upgrade/detection.d.ts +140 -10
  296. package/dist/upgrade/detection.js +153 -66
  297. package/dist/upgrade/detection.js.map +1 -1
  298. package/dist/upgrade/index.d.ts +2 -2
  299. package/dist/utils/chromeFetch.d.ts +1 -1
  300. package/dist/utils/chromeFetch.js +1 -1
  301. package/dist/utils/clock.d.ts +14 -0
  302. package/dist/utils/clock.js +28 -0
  303. package/dist/utils/clock.js.map +1 -0
  304. package/dist/utils/debugFilter.d.ts +1 -1
  305. package/dist/utils/debugFilter.js +6 -4
  306. package/dist/utils/debugFilter.js.map +1 -1
  307. package/dist/utils/delay.d.ts +2 -3
  308. package/dist/utils/delay.js +10 -16
  309. package/dist/utils/delay.js.map +1 -1
  310. package/dist/utils/evaluate.js +12 -14
  311. package/dist/utils/evaluate.js.map +1 -1
  312. package/dist/utils/ffmpeg.context.d.ts +6 -0
  313. package/dist/utils/ffmpeg.context.js +31 -0
  314. package/dist/utils/ffmpeg.context.js.map +1 -0
  315. package/dist/utils/ffmpeg.d.ts +85 -29
  316. package/dist/utils/ffmpeg.js +126 -129
  317. package/dist/utils/ffmpeg.js.map +1 -1
  318. package/dist/utils/fileLogger.d.ts +18 -2
  319. package/dist/utils/fileLogger.js +58 -30
  320. package/dist/utils/fileLogger.js.map +1 -1
  321. package/dist/utils/format.d.ts +1 -1
  322. package/dist/utils/format.js +2 -2
  323. package/dist/utils/format.js.map +1 -1
  324. package/dist/utils/html.js +1 -3
  325. package/dist/utils/html.js.map +1 -1
  326. package/dist/utils/index.d.ts +21 -20
  327. package/dist/utils/index.js +1 -0
  328. package/dist/utils/index.js.map +1 -1
  329. package/dist/utils/logger.js +16 -20
  330. package/dist/utils/logger.js.map +1 -1
  331. package/dist/utils/m3u.d.ts +18 -3
  332. package/dist/utils/m3u.js +28 -14
  333. package/dist/utils/m3u.js.map +1 -1
  334. package/dist/utils/memo.d.ts +19 -0
  335. package/dist/utils/memo.js +53 -0
  336. package/dist/utils/memo.js.map +1 -0
  337. package/dist/utils/morganStream.js +2 -2
  338. package/dist/utils/morganStream.js.map +1 -1
  339. package/dist/utils/pid.d.ts +2 -2
  340. package/dist/utils/pid.js +2 -2
  341. package/dist/utils/platform.d.ts +1 -1
  342. package/dist/utils/platform.js +3 -3
  343. package/dist/utils/platform.js.map +1 -1
  344. package/dist/utils/retry.d.ts +2 -0
  345. package/dist/utils/retry.js +9 -7
  346. package/dist/utils/retry.js.map +1 -1
  347. package/dist/utils/sanitize.d.ts +2 -2
  348. package/dist/utils/sanitize.js +4 -4
  349. package/dist/utils/timing.d.ts +6 -3
  350. package/dist/utils/timing.js +10 -6
  351. package/dist/utils/timing.js.map +1 -1
  352. package/dist/utils/version.d.ts +3 -2
  353. package/dist/utils/version.js +14 -27
  354. package/dist/utils/version.js.map +1 -1
  355. package/package.json +13 -7
  356. package/dist/native/intercept.d.ts +0 -35
  357. package/dist/native/intercept.js +0 -181
  358. package/dist/native/intercept.js.map +0 -1
  359. package/dist/routes/config/channels/routes.js +0 -1505
  360. package/dist/routes/config/channels/routes.js.map +0 -1
@@ -1,8 +1,10 @@
1
1
  /* Copyright(C) 2024-2026, HJD (https://github.com/hjdhjd). All rights reserved.
2
2
  *
3
- * hlsSegments.ts: HLS segment storage functions for PrismCast.
3
+ * hlsSegments.ts: HLS segment storage functions for PrismCast. The wait-for-readiness helpers (waitForPlaylist, waitForInitSegment) route their timeout race
4
+ * through the Clock port (see utils/clock.ts) so tests can deterministically simulate "promise resolves before timeout" vs "timeout fires before promise"
5
+ * without depending on real timers - the race is exactly the pattern Node's synchronous mock.timers.tick cannot drive reliably.
4
6
  */
5
- import { LOG, raceWithTimeout } from "../utils/index.js";
7
+ import { LOG, realClock } from "../utils/index.js";
6
8
  import { CONFIG } from "../config/index.js";
7
9
  import { getStream } from "./registry.js";
8
10
  /* This module provides functions for storing and retrieving HLS segments, playlists, and init segments. All data is stored in the stream registry's HLSState, which is
@@ -218,7 +220,7 @@ export function updatePlaylist(streamId, content) {
218
220
  stream.hls.signalPlaylistReady();
219
221
  // Cancel the deferred preroll timer now that real content is available. The timer may still be running for native streams where browser setup completed quickly
220
222
  // but the proxy's first poll cycle took longer than the preroll delay (PREROLL_DELAY_MS). Without this, the timer would fire after real content is already
221
- // flowing, uselessly seeding preroll state. For streams where the timer already fired (preroll is active), this is a no-op — the timer handle is already null.
223
+ // flowing, uselessly seeding preroll state. For streams where the timer already fired (preroll is active), this is a no-op - the timer handle is already null.
222
224
  if (stream.hls.prerollTimer) {
223
225
  clearTimeout(stream.hls.prerollTimer);
224
226
  stream.hls.prerollTimer = null;
@@ -240,19 +242,21 @@ export function getPlaylist(streamId) {
240
242
  * Waits for the first playlist to be available for a stream.
241
243
  * @param streamId - The numeric stream ID.
242
244
  * @param timeout - Maximum time to wait in milliseconds.
245
+ * @param clock - Clock used for the timeout race. Defaults to realClock; tests inject a fake to drive the race deterministically.
243
246
  * @returns True if playlist is ready, false if timeout or stream not found.
244
247
  */
245
- export async function waitForPlaylist(streamId, timeout) {
246
- return waitForReady(streamId, async (stream) => stream.hls.playlistReady, timeout);
248
+ export async function waitForPlaylist(streamId, timeout, clock = realClock) {
249
+ return waitForReady(streamId, async (stream) => stream.hls.playlistReady, timeout, clock);
247
250
  }
248
251
  /**
249
252
  * Waits for the first init segment to be available for a stream. Used by MPEG-TS consumers to wait for codec configuration before starting their FFmpeg remuxer.
250
253
  * @param streamId - The numeric stream ID.
251
254
  * @param timeout - Maximum time to wait in milliseconds.
255
+ * @param clock - Clock used for the timeout race. Defaults to realClock; tests inject a fake to drive the race deterministically.
252
256
  * @returns True if init segment is ready, false if timeout or stream not found.
253
257
  */
254
- export async function waitForInitSegment(streamId, timeout) {
255
- return waitForReady(streamId, async (stream) => stream.hls.initSegmentReady, timeout);
258
+ export async function waitForInitSegment(streamId, timeout, clock = realClock) {
259
+ return waitForReady(streamId, async (stream) => stream.hls.initSegmentReady, timeout, clock);
256
260
  }
257
261
  // Internal Helpers.
258
262
  /**
@@ -261,13 +265,14 @@ export async function waitForInitSegment(streamId, timeout) {
261
265
  * @param streamId - The numeric stream ID.
262
266
  * @param getPromise - Accessor that extracts the readiness promise from the stream's HLS state.
263
267
  * @param timeout - Maximum time to wait in milliseconds.
268
+ * @param clock - Clock providing the timeout race; the public wrappers thread their own clock through.
264
269
  * @returns True if ready before timeout, false otherwise.
265
270
  */
266
- async function waitForReady(streamId, getPromise, timeout) {
271
+ async function waitForReady(streamId, getPromise, timeout, clock) {
267
272
  const stream = getStream(streamId);
268
273
  if (!stream) {
269
274
  return false;
270
275
  }
271
- return raceWithTimeout(getPromise(stream).then(() => true), timeout).catch(() => false);
276
+ return clock.raceWithTimeout(getPromise(stream).then(() => true), timeout).catch(() => false);
272
277
  }
273
278
  //# sourceMappingURL=hlsSegments.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"hlsSegments.js","sourceRoot":"","sources":["../../src/streaming/hlsSegments.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,GAAG,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAE5C,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAE1C;;;;;;;;;;;;;GAaG;AAEH,4BAA4B;AAE5B;;;;;;;;GAQG;AACH,SAAS,iBAAiB,CAAC,MAA2B,EAAE,QAA6B,EAAE,SAAqC,EAAE,QAAgB,EAAE,IAAY;IAE1J,0EAA0E;IAC1E,MAAM,OAAO,GAAG,SAAS,KAAK,cAAc,CAAC;IAE7C,QAAQ,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IAE7B,gEAAgE;IAChE,IAAG,OAAO,EAAE,CAAC;QAEX,MAAM,CAAC,GAAG,CAAC,iBAAiB,IAAI,IAAI,CAAC,MAAM,CAAC;IAC9C,CAAC;SAAM,CAAC;QAEN,MAAM,CAAC,GAAG,CAAC,YAAY,IAAI,IAAI,CAAC,MAAM,CAAC;IACzC,CAAC;IAED,0HAA0H;IAC1H,MAAM,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAC;IAE1D,kHAAkH;IAClH,OAAM,QAAQ,CAAC,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC;QAE7C,MAAM,SAAS,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC;QAE/C,IAAG,SAAS,KAAK,SAAS,EAAE,CAAC;YAE3B,MAAM;QACR,CAAC;QAED,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAExC,IAAG,OAAO,EAAE,CAAC;YAEX,IAAG,OAAO,EAAE,CAAC;gBAEX,MAAM,CAAC,GAAG,CAAC,iBAAiB,IAAI,OAAO,CAAC,MAAM,CAAC;YACjD,CAAC;iBAAM,CAAC;gBAEN,MAAM,CAAC,GAAG,CAAC,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC;YAC5C,CAAC;QACH,CAAC;QAED,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IAC7B,CAAC;AACH,CAAC;AAED,sBAAsB;AAEtB;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAC,QAAgB,EAAE,QAAgB,EAAE,IAAY;IAE3E,MAAM,MAAM,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;IAEnC,IAAG,CAAC,MAAM,EAAE,CAAC;QAEX,GAAG,CAAC,KAAK,CAAC,eAAe,EAAE,mDAAmD,EAAE,QAAQ,CAAC,CAAC;QAE1F,OAAO;IACT,CAAC;IAED,iBAAiB,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAC;AAC5E,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CAAC,QAAgB,EAAE,QAAgB;IAE3D,OAAO,SAAS,CAAC,QAAQ,CAAC,EAAE,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;AACzD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,QAAgB;IAE9C,OAAO,SAAS,CAAC,QAAQ,CAAC,EAAE,GAAG,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,CAAC;AACrD,CAAC;AAED,4BAA4B;AAE5B;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,QAAgB,EAAE,QAAgB,EAAE,IAAY;IAEhF,MAAM,MAAM,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;IAEnC,IAAG,CAAC,MAAM,EAAE,CAAC;QAEX,GAAG,CAAC,KAAK,CAAC,eAAe,EAAE,yDAAyD,EAAE,QAAQ,CAAC,CAAC;QAEhG,OAAO;IACT,CAAC;IAED,iBAAiB,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,aAAa,EAAE,cAAc,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAC;AACtF,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAAC,QAAgB,EAAE,QAAgB;IAEhE,OAAO,SAAS,CAAC,QAAQ,CAAC,EAAE,GAAG,CAAC,aAAa,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;AAC9D,CAAC;AAED,6BAA6B;AAE7B;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,QAAgB,EAAE,OAAe;IAEnE,MAAM,MAAM,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;IAEnC,IAAG,CAAC,MAAM,EAAE,CAAC;QAEX,GAAG,CAAC,KAAK,CAAC,eAAe,EAAE,2DAA2D,EAAE,QAAQ,CAAC,CAAC;QAElG,OAAO;IACT,CAAC;IAED,MAAM,CAAC,GAAG,CAAC,aAAa,GAAG,OAAO,CAAC;AACrC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,QAAgB;IAE/C,2HAA2H;IAC3H,OAAO,SAAS,CAAC,QAAQ,CAAC,EAAE,GAAG,CAAC,aAAa,IAAI,SAAS,CAAC;AAC7D,CAAC;AAED,sDAAsD;AAEtD;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAAC,QAAgB,EAAE,OAAe;IAEnE,MAAM,MAAM,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;IAEnC,IAAG,CAAC,MAAM,EAAE,CAAC;QAEX,GAAG,CAAC,KAAK,CAAC,eAAe,EAAE,2DAA2D,EAAE,QAAQ,CAAC,CAAC;QAElG,OAAO;IACT,CAAC;IAED,MAAM,CAAC,GAAG,CAAC,aAAa,GAAG,OAAO,CAAC;AACrC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,QAAgB;IAE/C,2HAA2H;IAC3H,OAAO,SAAS,CAAC,QAAQ,CAAC,EAAE,GAAG,CAAC,aAAa,IAAI,SAAS,CAAC;AAC7D,CAAC;AAED,2BAA2B;AAE3B;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,QAAgB,EAAE,IAAY;IAE7D,MAAM,MAAM,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;IAEnC,IAAG,CAAC,MAAM,EAAE,CAAC;QAEX,GAAG,CAAC,KAAK,CAAC,eAAe,EAAE,wDAAwD,EAAE,QAAQ,CAAC,CAAC;QAE/F,OAAO;IACT,CAAC;IAED,MAAM,WAAW,GAAG,MAAM,CAAC,GAAG,CAAC,WAAW,KAAK,IAAI,CAAC;IAEpD,MAAM,CAAC,GAAG,CAAC,WAAW,GAAG,IAAI,CAAC;IAE9B,+DAA+D;IAC/D,MAAM,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC;IAEpD,IAAG,WAAW,EAAE,CAAC;QAEf,MAAM,CAAC,GAAG,CAAC,sBAAsB,EAAE,CAAC;QAEpC,MAAM,OAAO,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QAE9E,GAAG,CAAC,KAAK,CAAC,gBAAgB,EAAE,4BAA4B,EAAE,OAAO,CAAC,CAAC;IACrE,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,QAAgB;IAE7C,MAAM,MAAM,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;IAEnC,IAAG,CAAC,MAAM,EAAE,CAAC;QAEX,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,OAAO,MAAM,CAAC,GAAG,CAAC,WAAW,IAAI,SAAS,CAAC;AAC7C,CAAC;AAED,uBAAuB;AAEvB;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,QAAgB,EAAE,OAAe;IAE9D,MAAM,MAAM,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;IAEnC,IAAG,CAAC,MAAM,EAAE,CAAC;QAEX,GAAG,CAAC,KAAK,CAAC,eAAe,EAAE,qDAAqD,EAAE,QAAQ,CAAC,CAAC;QAE5F,OAAO;IACT,CAAC;IAED,MAAM,mBAAmB,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,eAAe,CAAC;IAExD,MAAM,CAAC,GAAG,CAAC,QAAQ,GAAG,OAAO,CAAC;IAE9B,IAAG,mBAAmB,EAAE,CAAC;QAEvB,MAAM,CAAC,GAAG,CAAC,eAAe,GAAG,IAAI,CAAC;QAClC,MAAM,CAAC,GAAG,CAAC,mBAAmB,EAAE,CAAC;QAEjC,gKAAgK;QAChK,2JAA2J;QAC3J,+JAA+J;QAC/J,IAAG,MAAM,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;YAE3B,YAAY,CAAC,MAAM,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;YACtC,MAAM,CAAC,GAAG,CAAC,YAAY,GAAG,IAAI,CAAC;QACjC,CAAC;QAED,MAAM,OAAO,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QAE9E,GAAG,CAAC,KAAK,CAAC,mBAAmB,EAAE,oCAAoC,EAAE,QAAQ,CAAC,CAAC;QAC/E,GAAG,CAAC,KAAK,CAAC,gBAAgB,EAAE,8BAA8B,EAAE,OAAO,CAAC,CAAC;IACvE,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,QAAgB;IAE1C,OAAO,SAAS,CAAC,QAAQ,CAAC,EAAE,GAAG,CAAC,QAAQ,CAAC;AAC3C,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,QAAgB,EAAE,OAAe;IAErE,OAAO,YAAY,CAAC,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC;AACrF,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,QAAgB,EAAE,OAAe;IAExE,OAAO,YAAY,CAAC,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,gBAAgB,EAAE,OAAO,CAAC,CAAC;AACxF,CAAC;AAED,oBAAoB;AAEpB;;;;;;;GAOG;AACH,KAAK,UAAU,YAAY,CAAC,QAAgB,EAAE,UAA0D,EAAE,OAAe;IAEvH,MAAM,MAAM,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;IAEnC,IAAG,CAAC,MAAM,EAAE,CAAC;QAEX,OAAO,KAAK,CAAC;IACf,CAAC;IAED,OAAO,eAAe,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC;AAC1F,CAAC"}
1
+ {"version":3,"file":"hlsSegments.js","sourceRoot":"","sources":["../../src/streaming/hlsSegments.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,16 @@
1
- import type { RecoveryMetrics, TabReplacementResult } from "./recovery.js";
1
+ import type { RecoveryMetrics, TabReplacementResult } from "./recovery.ts";
2
2
  import type { Frame, Page } from "puppeteer-core";
3
- import type { Nullable, ResolvedSiteProfile } from "../types/index.js";
3
+ import type { Nullable, ResolvedSiteProfile } from "../types/index.ts";
4
+ /**
5
+ * Stream info passed to the monitor for status updates.
6
+ */
7
+ export interface MonitorStreamInfo {
8
+ channelName: Nullable<string>;
9
+ numericStreamId: number;
10
+ serviceName: string;
11
+ serviceTag?: string;
12
+ startTime: Date;
13
+ }
4
14
  /**
5
15
  * Monitors video playback health and attempts escalating recovery when issues are detected. This function runs on an interval, checking video state and triggering
6
16
  * increasingly aggressive recovery actions when playback stalls, pauses, or errors occur.
@@ -25,14 +35,4 @@ import type { Nullable, ResolvedSiteProfile } from "../types/index.js";
25
35
  * If null/undefined, tab replacement is not available and timeouts will eventually trip the circuit breaker.
26
36
  * @returns A cleanup function to stop the monitor.
27
37
  */
28
- /**
29
- * Stream info passed to the monitor for status updates.
30
- */
31
- export interface MonitorStreamInfo {
32
- channelName: Nullable<string>;
33
- numericStreamId: number;
34
- serviceName: string;
35
- serviceTag?: string;
36
- startTime: Date;
37
- }
38
38
  export declare function monitorPlaybackHealth(page: Page, context: Frame | Page, profile: ResolvedSiteProfile, url: string, streamId: string, streamInfo: MonitorStreamInfo, onCircuitBreak: () => void, onTabReplacement?: () => Promise<Nullable<TabReplacementResult>>): () => RecoveryMetrics;
@@ -12,6 +12,30 @@ import { getProviderBySlug } from "../browser/channelSelection.js";
12
12
  import { getShowName } from "./showInfo.js";
13
13
  import { refreshNativeManifest } from "../native/index.js";
14
14
  import { resizeAndMinimizeWindow } from "../browser/cdp.js";
15
+ /**
16
+ * Monitors video playback health and attempts escalating recovery when issues are detected. This function runs on an interval, checking video state and triggering
17
+ * increasingly aggressive recovery actions when playback stalls, pauses, or errors occur.
18
+ *
19
+ * The monitor includes a circuit breaker that terminates the stream after a configurable number of consecutive failures within a time window. This prevents endless
20
+ * recovery attempts on fundamentally broken streams.
21
+ *
22
+ * Tab replacement recovery: When the browser tab becomes unresponsive (3+ consecutive evaluate timeouts), the monitor triggers tab replacement via the
23
+ * onTabReplacement callback. This closes the hung tab, creates a fresh one with new capture, and continues the stream. This is more reliable than page.goto-based
24
+ * recovery because a hung tab may not respond to navigation commands.
25
+ *
26
+ * The returned cleanup function should be called when the stream ends to stop monitoring and release resources.
27
+ *
28
+ * @param page - The Puppeteer page object.
29
+ * @param context - The frame or page containing the video element.
30
+ * @param profile - The site profile for behavior configuration.
31
+ * @param url - The URL of the stream, needed for page reload recovery.
32
+ * @param streamId - A concise identifier for the stream, used in log messages.
33
+ * @param streamInfo - Stream metadata for status updates.
34
+ * @param onCircuitBreak - Callback function called when circuit breaker trips.
35
+ * @param onTabReplacement - Optional callback for tab replacement recovery. When provided and 3+ consecutive timeouts occur, this is called to replace the hung tab.
36
+ * If null/undefined, tab replacement is not available and timeouts will eventually trip the circuit breaker.
37
+ * @returns A cleanup function to stop the monitor.
38
+ */
15
39
  export function monitorPlaybackHealth(page, context, profile, url, streamId, streamInfo, onCircuitBreak, onTabReplacement) {
16
40
  /* Monitor state. These track the video's behavior over time and control recovery decisions. Mutable variables are organized into typed state objects by subsystem
17
41
  * (recovery, segments, native health, resolution) to clarify ownership and interaction boundaries. Variables that don't belong to a specific subsystem remain as
@@ -56,29 +80,31 @@ export function monitorPlaybackHealth(page, context, profile, url, streamId, str
56
80
  // Last issue tracking for UI display. Stores what triggered recovery and when, so users can see stream history.
57
81
  let lastIssueType = null;
58
82
  let lastIssueTime = null;
59
- // Recovery grace period. After a recovery action, we wait before checking for new issues to give the action time to take effect. L1 (play/unmute) is a quick
60
- // action. L2 (source reload) and L3 (page reload) need more time for rebuffering/navigation.
61
- const recoveryGracePeriods = [0, 3000, 10000, 10000]; // L0, L1, L2, L3 in milliseconds.
62
- // Segment stall timeout. After L2/L3 recovery completes, if no new segments are produced within this timeout, the capture pipeline is considered dead and we
63
- // escalate directly to tab replacement. This catches the case where recovery reports success but the MediaRecorder/FFmpeg pipeline has silently died.
64
- const SEGMENT_STALL_TIMEOUT = 10000; // 10 seconds.
83
+ // Recovery grace periods in milliseconds, indexed by recovery level (L0 = no recovery, L1 = play/unmute, L2 = source reload, L3 = page reload). After a recovery
84
+ // action we wait this long before checking for new issues to give the action time to take effect. L1 is a quick action. L2 and L3 need more time for
85
+ // rebuffering or navigation to complete.
86
+ const recoveryGracePeriods = [0, 3_000, 10_000, 10_000];
87
+ // Segment stall timeout (10 seconds). After L2/L3 recovery completes, if no new segments are produced within this window, the capture pipeline is considered
88
+ // dead and we escalate directly to tab replacement. This catches the case where recovery reports success but the MediaRecorder/FFmpeg pipeline has silently died.
89
+ const SEGMENT_STALL_TIMEOUT = 10_000;
65
90
  // Tiny segment detection thresholds. Used for continuous segment size monitoring to detect dead capture pipelines. When video capture dies but audio continues,
66
91
  // segments contain only audio data. Audio is transcoded at a controlled bitrate (max 512Kbps), so audio-only segments are at most ~192KB for 3-second segments.
67
- // The 500KB threshold catches both dead captures (18 bytes) and audio-only captures while staying well below the smallest video preset (480p/3Mbps ≈ 750KB/segment).
68
- const TINY_SEGMENT_THRESHOLD = 512000; // 500KB - segments below this indicate dead or degraded capture.
69
- const TINY_SEGMENT_COUNT_TRIGGER = 10; // Default trigger count: 10 consecutive tiny segments (~20 seconds with 2-second segments).
92
+ // The 500KB threshold catches both dead captures (18 bytes) and audio-only captures while staying well below the smallest video preset (480p/3Mbps ~ 750KB/segment).
93
+ // The default count trigger (10) requires roughly 20 seconds of consecutive tiny segments before action is taken, balancing responsiveness against false positives.
94
+ const TINY_SEGMENT_THRESHOLD = 512_000;
95
+ const TINY_SEGMENT_COUNT_TRIGGER = 10;
70
96
  // Resolve the service-specific tiny segment count threshold once at monitor startup. Services with extended static content (e.g., Xfinity commercial
71
97
  // placeholders) set a higher value to tolerate longer periods of small segments without false positive tab replacements. Dead capture pipelines (segments with
72
98
  // no video trafs) always use TINY_SEGMENT_COUNT_TRIGGER regardless of this setting.
73
99
  const providerModule = streamInfo.serviceTag ? getProviderBySlug(streamInfo.serviceTag) : undefined;
74
100
  const providerTinySegmentThreshold = providerModule?.tinySegmentThreshold ?? TINY_SEGMENT_COUNT_TRIGGER;
75
101
  // Segment staleness timeout. When no new segments have been produced for this duration, the capture pipeline is considered dead even though the video element may
76
- // appear healthy. This catches the case where Chrome's MediaRecorder silently stops emitting data without raising an error — the input stream stays "open" but no
102
+ // appear healthy. This catches the case where Chrome's MediaRecorder silently stops emitting data without raising an error - the input stream stays "open" but no
77
103
  // data events fire. The 20-second threshold is 4x the maximum expected moof delivery interval (5 seconds) to avoid false positives during normal bursty delivery.
78
- const SEGMENT_STALENESS_TIMEOUT = 20000; // 20 seconds.
104
+ const SEGMENT_STALENESS_TIMEOUT = 20_000;
79
105
  // Resolution degradation detection. When the video element's intrinsic resolution is significantly below the configured viewport, the service's ABR is delivering
80
- // low-quality content. The threshold is expressed as a ratio — if either dimension is below this fraction of the viewport, the resolution is considered degraded.
81
- // 50% catches clear ABR degradation (768×432 on 1080p = 40%) while allowing legitimate 720p content on 1080p (67% > 50%).
106
+ // low-quality content. The threshold is expressed as a ratio - if either dimension is below this fraction of the viewport, the resolution is considered degraded.
107
+ // 50% catches clear ABR degradation (768x432 on 1080p = 40%) while allowing legitimate 720p content on 1080p (67% > 50%).
82
108
  const RESOLUTION_RATIO_THRESHOLD = 0.5;
83
109
  // Grace period in milliseconds after stream start and after each recovery action. Gives ABR time to ramp up before flagging degradation.
84
110
  const RESOLUTION_GRACE_PERIOD = 30000;
@@ -95,7 +121,7 @@ export function monitorPlaybackHealth(page, context, profile, url, streamId, str
95
121
  // Pre-compute the selector type string for video element selection. This is passed to evaluate() calls.
96
122
  const selectorType = buildVideoSelectorType(profile);
97
123
  // Capture stream context for re-establishing on each interval tick. AsyncLocalStorage context is lost when entering setInterval callbacks. The show name
98
- // resolver is lazy — it reads from the live show name cache at log time, so messages always reflect the current program even as shows change mid-stream.
124
+ // resolver is lazy - it reads from the live show name cache at log time, so messages always reflect the current program even as shows change mid-stream.
99
125
  const streamContext = {
100
126
  channelName: streamInfo.channelName ?? undefined,
101
127
  showNameResolver: () => getShowName(streamInfo.numericStreamId),
@@ -146,12 +172,12 @@ export function monitorPlaybackHealth(page, context, profile, url, streamId, str
146
172
  * Checks segment delivery health for native streams. Detects stalled streams by comparing the proxy's segment index and last segment timestamp against thresholds.
147
173
  * Recovery follows three escalation levels per the plan:
148
174
  *
149
- * - L1: Re-fetch manifest (handled by the proxy's internal retry loop — consecutive failures up to the threshold)
175
+ * - L1: Re-fetch manifest (handled by the proxy's internal retry loop - consecutive failures up to the threshold)
150
176
  * - L2: Reload page for fresh tokens (same mechanism as proactive token refresh, but triggered by segment staleness)
151
177
  * - L3: Fall back to capture mode via tab replacement (stops native proxy, creates fresh page with capture pipeline)
152
178
  *
153
179
  * Note: The `recoveryState.inProgress` guard at the top of the interval callback prevents re-entry during async L2/L3 recovery. The native path does not need its
154
- * own guard — it reuses the shared flag.
180
+ * own guard - it reuses the shared flag.
155
181
  *
156
182
  * @param entry - The stream registry entry for the native stream.
157
183
  */
@@ -212,7 +238,7 @@ export function monitorPlaybackHealth(page, context, profile, url, streamId, str
212
238
  }
213
239
  const staleSec = Math.round(stalenessMs / 1000);
214
240
  LOG.debug("native:monitor", "Native stream stalled for %s. No new segments in %ss (threshold: %ss).", storeKey, staleSec, Math.round(stalenessThreshold / 1000));
215
- // L2: At 4× target duration, attempt a page reload for fresh tokens. This recovers from auth expiry where the manifest URL is still valid but segment URLs
241
+ // L2: At 4x target duration, attempt a page reload for fresh tokens. This recovers from auth expiry where the manifest URL is still valid but segment URLs
216
242
  // are rejected. The proxy continues serving cached segments during the reload.
217
243
  if ((stalenessMs > (targetDuration * 4 * 1000)) && (nativeHealthState.recoveryAttempts === 0)) {
218
244
  LOG.warn("Native stream stalled for %s. No new segments in %ss. Attempting recovery.", storeKey, staleSec);
@@ -222,7 +248,7 @@ export function monitorPlaybackHealth(page, context, profile, url, streamId, str
222
248
  });
223
249
  return;
224
250
  }
225
- // L3: At 6× target duration (or if L2 was already attempted), fall back to capture mode via tab replacement.
251
+ // L3: At 6x target duration (or if L2 was already attempted), fall back to capture mode via tab replacement.
226
252
  if ((stalenessMs > (targetDuration * 6 * 1000)) || ((stalenessMs > (targetDuration * 4 * 1000)) && (nativeHealthState.recoveryAttempts > 0))) {
227
253
  LOG.warn("Falling back to capture mode for %s: native streaming stalled after recovery attempt.", storeKey);
228
254
  void runWithStreamContext(streamContext, async () => {
@@ -484,7 +510,7 @@ export function monitorPlaybackHealth(page, context, profile, url, streamId, str
484
510
  }
485
511
  /**
486
512
  * Checks the page reload rate limit. Prunes expired timestamps, checks if the limit has been reached, and records the current timestamp if allowed. Callers are
487
- * responsible for logging and handling the rate-limited case — consequences differ by context (circuit break, deferral, fallback to L2).
513
+ * responsible for logging and handling the rate-limited case - consequences differ by context (circuit break, deferral, fallback to L2).
488
514
  * @returns True if a page reload is allowed and the timestamp has been recorded, false if the rate limit has been reached.
489
515
  */
490
516
  function isPageReloadAllowed() {
@@ -510,7 +536,7 @@ export function monitorPlaybackHealth(page, context, profile, url, streamId, str
510
536
  */
511
537
  function setRecoveryGracePeriod(level) {
512
538
  pendingReMinimize = true;
513
- recoveryState.graceUntil = Date.now() + recoveryGracePeriods[level];
539
+ recoveryState.graceUntil = Date.now() + (recoveryGracePeriods[level] ?? 0);
514
540
  }
515
541
  /**
516
542
  * Handles tab replacement failure by checking the circuit breaker. If the breaker trips, terminates the stream. Returns the appropriate outcome for the caller.
@@ -520,7 +546,7 @@ export function monitorPlaybackHealth(page, context, profile, url, streamId, str
520
546
  function handleTabReplacementFailure(context) {
521
547
  const cbResult = checkCircuitBreaker(circuitBreaker, Date.now());
522
548
  if (cbResult.shouldTrip) {
523
- LOG.error("Recovery exhausted (%s) — terminating stream.", context);
549
+ LOG.error("Recovery exhausted (%s) - terminating stream.", context);
524
550
  clearInterval(interval);
525
551
  onCircuitBreak();
526
552
  return { outcome: "terminated" };
@@ -557,13 +583,13 @@ export function monitorPlaybackHealth(page, context, profile, url, streamId, str
557
583
  // Clear stale recovery metrics so the deferred-success check does not falsely log "Recovered" from leftover state set by recordRecoveryAttempt.
558
584
  metrics.currentRecoveryStartTime = null;
559
585
  metrics.currentRecoveryMethod = null;
560
- LOG.warn("Tab replacement unsuccessful after retry — stream will be terminated.");
586
+ LOG.warn("Tab replacement unsuccessful after retry - stream will be terminated.");
561
587
  const failureOutcome = handleTabReplacementFailure(context);
562
588
  // If the circuit breaker did not trip but the old page is gone (handler destroyed it before createPageWithCapture failed), the stream is unrecoverable. The
563
- // next monitor tick would silently clear the interval via currentPage.isClosed() with no termination log, no status emission, and no cleanup — creating a
589
+ // next monitor tick would silently clear the interval via currentPage.isClosed() with no termination log, no status emission, and no cleanup - creating a
564
590
  // zombie entry in the registry. Terminate explicitly instead.
565
591
  if ((failureOutcome.outcome === "failed") && currentPage.isClosed()) {
566
- LOG.error("Tab replacement failed and the original page is no longer available — terminating stream.");
592
+ LOG.error("Tab replacement failed and the original page is no longer available - terminating stream.");
567
593
  clearInterval(interval);
568
594
  onCircuitBreak();
569
595
  return { outcome: "terminated" };
@@ -595,7 +621,7 @@ export function monitorPlaybackHealth(page, context, profile, url, streamId, str
595
621
  recordRecoveryAttempt(metrics, RECOVERY_METHODS.tabReplacement);
596
622
  try {
597
623
  let result = await onTabReplacement();
598
- // First attempt failed — retry once. See idempotency notes in the JSDoc above.
624
+ // First attempt failed - retry once. See idempotency notes in the JSDoc above.
599
625
  if (!result) {
600
626
  LOG.debug("recovery:tab", "Tab replacement attempt 1/2 failed. Retrying...");
601
627
  try {
@@ -612,7 +638,7 @@ export function monitorPlaybackHealth(page, context, profile, url, streamId, str
612
638
  return handleExhaustedTabReplacement("tab replacement unsuccessful");
613
639
  }
614
640
  catch (error) {
615
- // Unexpected error (not from onTabReplacement — those are caught internally by the handler in hls.ts and return null). Guard against registry corruption,
641
+ // Unexpected error (not from onTabReplacement - those are caught internally by the handler in hls.ts and return null). Guard against registry corruption,
616
642
  // getStream failures, or other unexpected errors.
617
643
  LOG.debug("recovery:tab", "Tab replacement attempt 1/2 failed: %s. Retrying...", formatError(error));
618
644
  try {
@@ -684,7 +710,7 @@ export function monitorPlaybackHealth(page, context, profile, url, streamId, str
684
710
  * Handles the case where no video element was found in the current context. Applies a grace period before triggering recovery: first failure waits, second failure
685
711
  * attempts frame re-search, third failure escalates to full page navigation. Distinguishes "no video element" from "video exists but not ready" (buffering).
686
712
  *
687
- * Always exits the tick — no path falls through to subsequent health checks.
713
+ * Always exits the tick - no path falls through to subsequent health checks.
688
714
  * @param now - Current timestamp for timing calculations.
689
715
  */
690
716
  async function handleVideoNotFound(now) {
@@ -737,11 +763,11 @@ export function monitorPlaybackHealth(page, context, profile, url, streamId, str
737
763
  return;
738
764
  }
739
765
  // After 3+ consecutive failures, escalate to full page navigation recovery.
740
- LOG.warn("Video element not found — recovering via %s.", RECOVERY_METHODS.pageNavigation);
766
+ LOG.warn("Video element not found - recovering via %s.", RECOVERY_METHODS.pageNavigation);
741
767
  // Check circuit breaker for too many failures.
742
768
  const cbResult = checkCircuitBreaker(circuitBreaker, now);
743
769
  if (cbResult.shouldTrip) {
744
- LOG.error("Recovery failed after %s attempts — terminating stream.", cbResult.totalCount);
770
+ LOG.error("Recovery failed after %s attempts - terminating stream.", cbResult.totalCount);
745
771
  clearInterval(interval);
746
772
  onCircuitBreak();
747
773
  return;
@@ -756,7 +782,7 @@ export function monitorPlaybackHealth(page, context, profile, url, streamId, str
756
782
  recordRecoveryAttempt(metrics, RECOVERY_METHODS.pageNavigation);
757
783
  // Check page reload limit before attempting recovery.
758
784
  if (!isPageReloadAllowed()) {
759
- LOG.error("Page navigation rate limit reached (%s in %s minutes) — cannot recover without video element.", CONFIG.playback.maxPageReloads, Math.round(CONFIG.playback.pageReloadWindow / 60000));
785
+ LOG.error("Page navigation rate limit reached (%s in %s minutes) - cannot recover without video element.", CONFIG.playback.maxPageReloads, Math.round(CONFIG.playback.pageReloadWindow / 60000));
760
786
  clearInterval(interval);
761
787
  onCircuitBreak();
762
788
  return;
@@ -806,14 +832,14 @@ export function monitorPlaybackHealth(page, context, profile, url, streamId, str
806
832
  const entry = getStream(streamInfo.numericStreamId);
807
833
  const currentIndex = entry?.segmenter?.getSegmentIndex() ?? null;
808
834
  if ((currentIndex !== null) && (currentIndex > segmentState.preRecoveryIndex)) {
809
- // Segments are flowing — recovery actually succeeded. Clear tracking state.
835
+ // Segments are flowing - recovery actually succeeded. Clear tracking state.
810
836
  segmentState.preRecoveryIndex = null;
811
837
  segmentState.waitStartTime = null;
812
838
  segmentState.productionStalled = false;
813
839
  }
814
840
  else if ((now - segmentState.waitStartTime) > SEGMENT_STALL_TIMEOUT) {
815
841
  // No new segments for SEGMENT_STALL_TIMEOUT after recovery grace period. The capture pipeline is dead.
816
- LOG.warn("No segments produced for %ss after recovery — capture pipeline may have stalled.", SEGMENT_STALL_TIMEOUT / 1000);
842
+ LOG.warn("No segments produced for %ss after recovery - capture pipeline may have stalled.", SEGMENT_STALL_TIMEOUT / 1000);
817
843
  segmentState.productionStalled = true;
818
844
  }
819
845
  }
@@ -834,7 +860,7 @@ export function monitorPlaybackHealth(page, context, profile, url, streamId, str
834
860
  const effectiveThreshold = (hasVideo === false) ? TINY_SEGMENT_COUNT_TRIGGER : providerTinySegmentThreshold;
835
861
  LOG.debug("recovery:tracks", "Below-threshold segment: %d bytes, hasVideo=%s, consecutive=%d, threshold=%d.", segmentSize, String(hasVideo), segmentState.consecutiveTinySegments, effectiveThreshold);
836
862
  if (segmentState.consecutiveTinySegments >= effectiveThreshold) {
837
- LOG.warn("Detected %d consecutive undersized segments (%dKB) — capture pipeline may have stalled.", segmentState.consecutiveTinySegments, Math.round(segmentSize / 1024));
863
+ LOG.warn("Detected %d consecutive undersized segments (%dKB) - capture pipeline may have stalled.", segmentState.consecutiveTinySegments, Math.round(segmentSize / 1024));
838
864
  // Trigger tab replacement if available, otherwise let circuit breaker handle it via segmentState.productionStalled.
839
865
  if (onTabReplacement && !recoveryState.inProgress) {
840
866
  await executeTabReplacement("tiny segments");
@@ -859,7 +885,7 @@ export function monitorPlaybackHealth(page, context, profile, url, streamId, str
859
885
  else if (sizeCheckEntry && (segmentState.lastCheckedIndex > 0) && !withinRecoveryGrace &&
860
886
  ((now - segmentState.lastSegmentAdvanceTime) > SEGMENT_STALENESS_TIMEOUT)) {
861
887
  /* Segment staleness detection. The segment index has not advanced for longer than SEGMENT_STALENESS_TIMEOUT. This catches the case where Chrome's
862
- * MediaRecorder silently stops emitting data — the input Readable stream stays "open" (no end/error events) but no data events fire. The segmenter
888
+ * MediaRecorder silently stops emitting data - the input Readable stream stays "open" (no end/error events) but no data events fire. The segmenter
863
889
  * receives nothing, produces no new segments, and the playlist freezes at the last known sequence number. The video element on the page continues
864
890
  * playing normally (currentTime advances, no errors), so all video health checks pass. Without this check, the stale playlist persists indefinitely.
865
891
  *
@@ -867,7 +893,7 @@ export function monitorPlaybackHealth(page, context, profile, url, streamId, str
867
893
  * during stream startup before the first segment has been produced. The recovery grace guard prevents false triggering during legitimate pauses (e.g.,
868
894
  * after tab replacement while the new capture pipeline is initializing).
869
895
  */
870
- LOG.warn("No new segments produced for %ss — capture pipeline may have stalled.", SEGMENT_STALENESS_TIMEOUT / 1000);
896
+ LOG.warn("No new segments produced for %ss - capture pipeline may have stalled.", SEGMENT_STALENESS_TIMEOUT / 1000);
871
897
  if (onTabReplacement && !recoveryState.inProgress) {
872
898
  await executeTabReplacement("segment staleness");
873
899
  return true;
@@ -912,7 +938,7 @@ export function monitorPlaybackHealth(page, context, profile, url, streamId, str
912
938
  LOG.warn("Video resolution has been degraded for %ss (%s\u00d7%s in %s\u00d7%s viewport). Attempting recovery via %s.", String(degradedDuration), String(state.videoWidth), String(state.videoHeight), String(viewport.width), String(viewport.height), RECOVERY_METHODS.pageNavigation);
913
939
  recoveryState.inProgress = true;
914
940
  if (!isPageReloadAllowed()) {
915
- LOG.warn("Resolution recovery deferred — page navigation rate limit reached (%s in %s minutes).", CONFIG.playback.maxPageReloads, Math.round(CONFIG.playback.pageReloadWindow / 60000));
941
+ LOG.warn("Resolution recovery deferred - page navigation rate limit reached (%s in %s minutes).", CONFIG.playback.maxPageReloads, Math.round(CONFIG.playback.pageReloadWindow / 60000));
916
942
  // Defer by pushing grace end forward to avoid re-triggering every 2 seconds.
917
943
  resolutionState.graceEnd = now + RESOLUTION_GRACE_PERIOD;
918
944
  recoveryState.inProgress = false;
@@ -947,7 +973,7 @@ export function monitorPlaybackHealth(page, context, profile, url, streamId, str
947
973
  resolutionState.graceEnd = now + RESOLUTION_GRACE_PERIOD;
948
974
  return true;
949
975
  }
950
- // Tab replacement not available — skip directly to acceptance.
976
+ // Tab replacement not available - skip directly to acceptance.
951
977
  resolutionState.recoveryAttempt = 2;
952
978
  }
953
979
  // Acceptance: resolution still degraded after both recovery attempts. Log once and stop retrying.
@@ -994,12 +1020,12 @@ export function monitorPlaybackHealth(page, context, profile, url, streamId, str
994
1020
  const elapsedHours = (elapsedMs / 3600000).toFixed(1);
995
1021
  LOG.info("Proactive reload after %sh of continuous playback (site limit: %sh). Reloading page to prevent stream cutoff.", elapsedHours, String(profile.maxContinuousPlayback));
996
1022
  recoveryState.inProgress = true;
997
- // Check page reload rate limit. Proactive reload is best-effort maintenance — if the reload budget is exhausted from recent error recoveries, we gracefully
1023
+ // Check page reload rate limit. Proactive reload is best-effort maintenance - if the reload budget is exhausted from recent error recoveries, we gracefully
998
1024
  // yield. If the site eventually cuts the stream, normal error recovery handles it.
999
1025
  if (!isPageReloadAllowed()) {
1000
- LOG.warn("Proactive reload deferred — page navigation rate limit reached (%s in %s minutes).", CONFIG.playback.maxPageReloads, Math.round(CONFIG.playback.pageReloadWindow / 60000));
1026
+ LOG.warn("Proactive reload deferred - page navigation rate limit reached (%s in %s minutes).", CONFIG.playback.maxPageReloads, Math.round(CONFIG.playback.pageReloadWindow / 60000));
1001
1027
  // Set a grace period to prevent this deferral from re-triggering every 2 seconds while the rate limit remains in effect. We set recoveryState.graceUntil
1002
- // directly rather than calling setRecoveryGracePeriod() because no recovery action was performed — the window state is unchanged and pendingReMinimize
1028
+ // directly rather than calling setRecoveryGracePeriod() because no recovery action was performed - the window state is unchanged and pendingReMinimize
1003
1029
  // should not be set.
1004
1030
  recoveryState.graceUntil = now + recoveryGracePeriods[3];
1005
1031
  recoveryState.inProgress = false;
@@ -1043,7 +1069,7 @@ export function monitorPlaybackHealth(page, context, profile, url, streamId, str
1043
1069
  // Segment production stall handling. When segments stopped flowing after L2/L3 recovery, the capture pipeline is dead and normal recovery won't help. Skip the
1044
1070
  // escalation ladder and go directly to tab replacement if available.
1045
1071
  if (segmentState.productionStalled && onTabReplacement) {
1046
- LOG.warn("Capture pipeline still stalled — escalating to %s.", RECOVERY_METHODS.tabReplacement);
1072
+ LOG.warn("Capture pipeline still stalled - escalating to %s.", RECOVERY_METHODS.tabReplacement);
1047
1073
  await executeTabReplacement("capture pipeline stalled");
1048
1074
  return true;
1049
1075
  }
@@ -1051,7 +1077,7 @@ export function monitorPlaybackHealth(page, context, profile, url, streamId, str
1051
1077
  const cbResult = checkCircuitBreaker(circuitBreaker, now);
1052
1078
  if (cbResult.shouldTrip) {
1053
1079
  const elapsedSeconds = circuitBreaker.firstFailureTime ? Math.round((now - circuitBreaker.firstFailureTime) / 1000) : 0;
1054
- LOG.error("Recovery failed after %s attempts in %ss — terminating stream.", cbResult.totalCount, elapsedSeconds);
1080
+ LOG.error("Recovery failed after %s attempts in %ss - terminating stream.", cbResult.totalCount, elapsedSeconds);
1055
1081
  clearInterval(interval);
1056
1082
  onCircuitBreak();
1057
1083
  return true;
@@ -1084,11 +1110,11 @@ export function monitorPlaybackHealth(page, context, profile, url, streamId, str
1084
1110
  lastIssueTime = now;
1085
1111
  // If a previous recovery was pending (L1 or L2 that didn't result in healthy playback), log that it was unsuccessful before starting the new attempt.
1086
1112
  if (metrics.currentRecoveryMethod !== null) {
1087
- LOG.warn("%s did not resolve the issue — escalating to %s.", capitalize(metrics.currentRecoveryMethod), recoveryMethod);
1113
+ LOG.warn("%s did not resolve the issue - escalating to %s.", capitalize(metrics.currentRecoveryMethod), recoveryMethod);
1088
1114
  }
1089
1115
  else {
1090
1116
  const issueDesc = getIssueDescription(issueCategory);
1091
- LOG.warn("Playback %s — recovering via %s.", issueDesc, recoveryMethod);
1117
+ LOG.warn("Playback %s - recovering via %s.", issueDesc, recoveryMethod);
1092
1118
  }
1093
1119
  recordRecoveryAttempt(metrics, recoveryMethod);
1094
1120
  // For L2/L3 recovery, record the current segment index so we can verify segments are flowing after recovery completes.
@@ -1107,21 +1133,21 @@ export function monitorPlaybackHealth(page, context, profile, url, streamId, str
1107
1133
  recoveryState.sourceReloadAttempted = true;
1108
1134
  markStreamDiscontinuity();
1109
1135
  }
1110
- recoveryState.graceUntil = now + recoveryGracePeriods[recoveryState.escalationLevel];
1136
+ recoveryState.graceUntil = now + (recoveryGracePeriods[recoveryState.escalationLevel] ?? 0);
1111
1137
  resetResolutionState();
1112
1138
  }
1113
1139
  else {
1114
1140
  // Level 3: Page navigation recovery.
1115
1141
  // Safety check: If page navigation has failed twice consecutively, fall back to source reload.
1116
1142
  if (consecutiveNavigationFailures >= 2) {
1117
- LOG.warn("Page navigation has failed %s consecutive times — falling back to source reload.", consecutiveNavigationFailures);
1143
+ LOG.warn("Page navigation has failed %s consecutive times - falling back to source reload.", consecutiveNavigationFailures);
1118
1144
  recoveryState.escalationLevel = 2;
1119
1145
  consecutiveNavigationFailures = 0;
1120
1146
  recoveryState.sourceReloadAttempted = false;
1121
1147
  }
1122
1148
  else {
1123
1149
  if (!isPageReloadAllowed()) {
1124
- LOG.warn("Page navigation rate limit reached (%s in %s minutes) — falling back to source reload.", CONFIG.playback.maxPageReloads, Math.round(CONFIG.playback.pageReloadWindow / 60000));
1150
+ LOG.warn("Page navigation rate limit reached (%s in %s minutes) - falling back to source reload.", CONFIG.playback.maxPageReloads, Math.round(CONFIG.playback.pageReloadWindow / 60000));
1125
1151
  recoveryState.escalationLevel = 2;
1126
1152
  recoveryState.sourceReloadAttempted = false;
1127
1153
  }
@@ -1156,14 +1182,14 @@ export function monitorPlaybackHealth(page, context, profile, url, streamId, str
1156
1182
  }
1157
1183
  /* Main monitoring interval. This runs every MONITOR_INTERVAL milliseconds to check video state and trigger recovery when needed.
1158
1184
  *
1159
- * IMPORTANT: Early returns must call emitStatusUpdate() before returning (except when the stream is terminating, e.g., page closed or circuit breaker tripped). This
1185
+ * Every early return must call emitStatusUpdate() before returning (except when the stream is terminating, e.g., page closed or circuit breaker tripped). This
1160
1186
  * ensures SSE clients always have current status data (duration, memory, health) even during recovery, buffering, or video search periods. Without this, the
1161
1187
  * streamStatuses map becomes stale and new SSE connections receive outdated snapshots.
1162
1188
  *
1163
- * CHECK ORDER MATTERS: The recoveryState.inProgress check must come BEFORE the currentPage.isClosed() check. During tab replacement, the old page is intentionally
1164
- * closed while the handler creates a new page. If we check isClosed() first, we would terminate the interval while recovery is still in progress, causing status
1165
- * updates to stop permanently. The sequence is: (1) intervalCleared for explicit cleanup, (2) recoveryState.inProgress to continue during recovery, (3) isClosed()
1166
- * for unexpected page termination outside of recovery.
1189
+ * Check ordering: the recoveryState.inProgress check must come before the currentPage.isClosed() check. During tab replacement, the old page is intentionally
1190
+ * closed while the handler creates a new page; if we checked isClosed() first, we would terminate the interval while recovery is still in progress, causing
1191
+ * status updates to stop permanently. The required sequence is: (1) intervalCleared for explicit cleanup, (2) recoveryState.inProgress to continue during
1192
+ * recovery, (3) isClosed() for unexpected page termination outside of recovery.
1167
1193
  */
1168
1194
  const interval = setInterval(() => {
1169
1195
  // Stop monitoring if cleanup was requested.
@@ -1298,7 +1324,7 @@ export function monitorPlaybackHealth(page, context, profile, url, streamId, str
1298
1324
  await resizeAndMinimizeWindow(currentPage);
1299
1325
  }
1300
1326
  /* Fullscreen reinforcement. Some streaming sites (notably Hulu) revert the video to a mini-player or PiP layout in response to browser state changes such as
1301
- * window minimization or visibility events. Because the video continues playing normally in the smaller frame, no existing recovery condition is triggered — the
1327
+ * window minimization or visibility events. Because the video continues playing normally in the smaller frame, no existing recovery condition is triggered - the
1302
1328
  * health monitor sees healthy, progressing playback while the captured frame shows a small video in the corner of the viewport. We verify that the video fills
1303
1329
  * the viewport on every healthy tick and re-apply CSS fullscreen styling when it shrinks. The response is graduated: basic CSS first (sufficient for
1304
1330
  * well-behaved sites like Hulu), escalating to !important priority only if basic styles don't hold by the next tick. The readyState guard prevents false
@@ -1411,7 +1437,7 @@ export function monitorPlaybackHealth(page, context, profile, url, streamId, str
1411
1437
  lastIssueTime = Date.now();
1412
1438
  // After 3 consecutive timeouts, attempt tab replacement if the callback is available.
1413
1439
  if ((consecutiveTimeouts >= 3) && onTabReplacement) {
1414
- LOG.warn("Tab unresponsive — recovering via %s.", RECOVERY_METHODS.tabReplacement);
1440
+ LOG.warn("Tab unresponsive - recovering via %s.", RECOVERY_METHODS.tabReplacement);
1415
1441
  await executeTabReplacement("tab unresponsive");
1416
1442
  return;
1417
1443
  }