prismcast 1.10.2 → 1.11.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 (693) hide show
  1. package/dist/app.d.ts +6 -3
  2. package/dist/app.d.ts.map +1 -0
  3. package/dist/app.js +140 -86
  4. package/dist/app.js.map +1 -1
  5. package/dist/browser/blockedPage.d.ts +80 -0
  6. package/dist/browser/blockedPage.d.ts.map +1 -0
  7. package/dist/browser/blockedPage.js +209 -0
  8. package/dist/browser/blockedPage.js.map +1 -0
  9. package/dist/browser/browserSupervisor.d.ts +80 -0
  10. package/dist/browser/browserSupervisor.d.ts.map +1 -0
  11. package/dist/browser/browserSupervisor.js +186 -0
  12. package/dist/browser/browserSupervisor.js.map +1 -0
  13. package/dist/browser/cdp.d.ts +1 -0
  14. package/dist/browser/cdp.d.ts.map +1 -0
  15. package/dist/browser/cdp.js +6 -5
  16. package/dist/browser/cdp.js.map +1 -1
  17. package/dist/browser/channelSelection.d.ts +45 -14
  18. package/dist/browser/channelSelection.d.ts.map +1 -0
  19. package/dist/browser/channelSelection.js +81 -18
  20. package/dist/browser/channelSelection.js.map +1 -1
  21. package/dist/browser/consent.d.ts +152 -0
  22. package/dist/browser/consent.d.ts.map +1 -0
  23. package/dist/browser/consent.js +418 -0
  24. package/dist/browser/consent.js.map +1 -0
  25. package/dist/browser/display.d.ts +1 -0
  26. package/dist/browser/display.d.ts.map +1 -0
  27. package/dist/browser/hlsPlaylistObserver.d.ts +55 -0
  28. package/dist/browser/hlsPlaylistObserver.d.ts.map +1 -0
  29. package/dist/browser/hlsPlaylistObserver.js +132 -0
  30. package/dist/browser/hlsPlaylistObserver.js.map +1 -0
  31. package/dist/browser/index.d.ts +67 -40
  32. package/dist/browser/index.d.ts.map +1 -0
  33. package/dist/browser/index.js +508 -300
  34. package/dist/browser/index.js.map +1 -1
  35. package/dist/browser/launchGovernor.d.ts +89 -0
  36. package/dist/browser/launchGovernor.d.ts.map +1 -0
  37. package/dist/browser/launchGovernor.js +96 -0
  38. package/dist/browser/launchGovernor.js.map +1 -0
  39. package/dist/browser/login.d.ts +10 -2
  40. package/dist/browser/login.d.ts.map +1 -0
  41. package/dist/browser/login.js +36 -5
  42. package/dist/browser/login.js.map +1 -1
  43. package/dist/browser/manifestInterceptor.d.ts +102 -49
  44. package/dist/browser/manifestInterceptor.d.ts.map +1 -0
  45. package/dist/browser/manifestInterceptor.js +219 -237
  46. package/dist/browser/manifestInterceptor.js.map +1 -1
  47. package/dist/browser/pageStaleness.d.ts +30 -0
  48. package/dist/browser/pageStaleness.d.ts.map +1 -0
  49. package/dist/browser/pageStaleness.js +76 -0
  50. package/dist/browser/pageStaleness.js.map +1 -0
  51. package/dist/browser/precaching.d.ts +95 -3
  52. package/dist/browser/precaching.d.ts.map +1 -0
  53. package/dist/browser/precaching.js +499 -65
  54. package/dist/browser/precaching.js.map +1 -1
  55. package/dist/browser/tabNetworkObserver.d.ts +49 -0
  56. package/dist/browser/tabNetworkObserver.d.ts.map +1 -0
  57. package/dist/browser/tabNetworkObserver.js +144 -0
  58. package/dist/browser/tabNetworkObserver.js.map +1 -0
  59. package/dist/browser/tuning/cache.d.ts +61 -0
  60. package/dist/browser/tuning/cache.d.ts.map +1 -0
  61. package/dist/browser/tuning/cache.js +62 -0
  62. package/dist/browser/tuning/cache.js.map +1 -0
  63. package/dist/browser/tuning/comcastPolymer.d.ts +3 -2
  64. package/dist/browser/tuning/comcastPolymer.d.ts.map +1 -0
  65. package/dist/browser/tuning/comcastPolymer.js +79 -102
  66. package/dist/browser/tuning/comcastPolymer.js.map +1 -1
  67. package/dist/browser/tuning/cox.d.ts +1 -0
  68. package/dist/browser/tuning/cox.d.ts.map +1 -0
  69. package/dist/browser/tuning/directv.d.ts +1 -0
  70. package/dist/browser/tuning/directv.d.ts.map +1 -0
  71. package/dist/browser/tuning/directv.js +52 -46
  72. package/dist/browser/tuning/directv.js.map +1 -1
  73. package/dist/browser/tuning/fox.d.ts +1 -0
  74. package/dist/browser/tuning/fox.d.ts.map +1 -0
  75. package/dist/browser/tuning/fox.js +13 -12
  76. package/dist/browser/tuning/fox.js.map +1 -1
  77. package/dist/browser/tuning/gridSearch.d.ts +68 -0
  78. package/dist/browser/tuning/gridSearch.d.ts.map +1 -0
  79. package/dist/browser/tuning/gridSearch.js +85 -0
  80. package/dist/browser/tuning/gridSearch.js.map +1 -0
  81. package/dist/browser/tuning/hbo.d.ts +1 -0
  82. package/dist/browser/tuning/hbo.d.ts.map +1 -0
  83. package/dist/browser/tuning/hbo.js +73 -45
  84. package/dist/browser/tuning/hbo.js.map +1 -1
  85. package/dist/browser/tuning/hulu.d.ts +1 -0
  86. package/dist/browser/tuning/hulu.d.ts.map +1 -0
  87. package/dist/browser/tuning/hulu.js +128 -173
  88. package/dist/browser/tuning/hulu.js.map +1 -1
  89. package/dist/browser/tuning/shared.d.ts +75 -5
  90. package/dist/browser/tuning/shared.d.ts.map +1 -0
  91. package/dist/browser/tuning/shared.js +216 -6
  92. package/dist/browser/tuning/shared.js.map +1 -1
  93. package/dist/browser/tuning/sling.d.ts +1 -0
  94. package/dist/browser/tuning/sling.d.ts.map +1 -0
  95. package/dist/browser/tuning/sling.js +105 -126
  96. package/dist/browser/tuning/sling.js.map +1 -1
  97. package/dist/browser/tuning/spectrum.d.ts +1 -0
  98. package/dist/browser/tuning/spectrum.d.ts.map +1 -0
  99. package/dist/browser/tuning/spectrum.js +80 -125
  100. package/dist/browser/tuning/spectrum.js.map +1 -1
  101. package/dist/browser/tuning/thumbnailRow.d.ts +1 -0
  102. package/dist/browser/tuning/thumbnailRow.d.ts.map +1 -0
  103. package/dist/browser/tuning/thumbnailRow.js +8 -2
  104. package/dist/browser/tuning/thumbnailRow.js.map +1 -1
  105. package/dist/browser/tuning/tileClick.d.ts +1 -0
  106. package/dist/browser/tuning/tileClick.d.ts.map +1 -0
  107. package/dist/browser/tuning/tileClick.js +3 -2
  108. package/dist/browser/tuning/tileClick.js.map +1 -1
  109. package/dist/browser/tuning/xfinity.d.ts +1 -0
  110. package/dist/browser/tuning/xfinity.d.ts.map +1 -0
  111. package/dist/browser/tuning/youtubeTv.d.ts +1 -0
  112. package/dist/browser/tuning/youtubeTv.d.ts.map +1 -0
  113. package/dist/browser/tuning/youtubeTv.js +74 -127
  114. package/dist/browser/tuning/youtubeTv.js.map +1 -1
  115. package/dist/browser/video.d.ts +54 -14
  116. package/dist/browser/video.d.ts.map +1 -0
  117. package/dist/browser/video.js +296 -138
  118. package/dist/browser/video.js.map +1 -1
  119. package/dist/channels/index.d.ts +1 -0
  120. package/dist/channels/index.d.ts.map +1 -0
  121. package/dist/channels/index.js +12 -9
  122. package/dist/channels/index.js.map +1 -1
  123. package/dist/config/channelForm.d.ts +15 -1
  124. package/dist/config/channelForm.d.ts.map +1 -0
  125. package/dist/config/channelForm.js +28 -12
  126. package/dist/config/channelForm.js.map +1 -1
  127. package/dist/config/consistencyProbe.d.ts +1 -0
  128. package/dist/config/consistencyProbe.d.ts.map +1 -0
  129. package/dist/config/consistencyProbe.js +27 -21
  130. package/dist/config/consistencyProbe.js.map +1 -1
  131. package/dist/config/health.d.ts +41 -8
  132. package/dist/config/health.d.ts.map +1 -0
  133. package/dist/config/health.js +189 -38
  134. package/dist/config/health.js.map +1 -1
  135. package/dist/config/index.d.ts +55 -3
  136. package/dist/config/index.d.ts.map +1 -0
  137. package/dist/config/index.js +320 -69
  138. package/dist/config/index.js.map +1 -1
  139. package/dist/config/paths.d.ts +10 -9
  140. package/dist/config/paths.d.ts.map +1 -0
  141. package/dist/config/paths.js +11 -11
  142. package/dist/config/paths.js.map +1 -1
  143. package/dist/config/persistence.context.d.ts +1 -0
  144. package/dist/config/persistence.context.d.ts.map +1 -0
  145. package/dist/config/persistence.d.ts +13 -10
  146. package/dist/config/persistence.d.ts.map +1 -0
  147. package/dist/config/persistence.js +40 -21
  148. package/dist/config/persistence.js.map +1 -1
  149. package/dist/config/presets.d.ts +1 -0
  150. package/dist/config/presets.d.ts.map +1 -0
  151. package/dist/config/profiles.d.ts +22 -15
  152. package/dist/config/profiles.d.ts.map +1 -0
  153. package/dist/config/profiles.js +81 -47
  154. package/dist/config/profiles.js.map +1 -1
  155. package/dist/config/providerLineups.d.ts +47 -0
  156. package/dist/config/providerLineups.d.ts.map +1 -0
  157. package/dist/config/providerLineups.js +137 -0
  158. package/dist/config/providerLineups.js.map +1 -0
  159. package/dist/config/reactivity.d.ts +84 -0
  160. package/dist/config/reactivity.d.ts.map +1 -0
  161. package/dist/config/reactivity.js +226 -0
  162. package/dist/config/reactivity.js.map +1 -0
  163. package/dist/config/servicePacks.d.ts +18 -2
  164. package/dist/config/servicePacks.d.ts.map +1 -0
  165. package/dist/config/servicePacks.js +53 -11
  166. package/dist/config/servicePacks.js.map +1 -1
  167. package/dist/config/services.d.ts +21 -15
  168. package/dist/config/services.d.ts.map +1 -0
  169. package/dist/config/services.js +35 -24
  170. package/dist/config/services.js.map +1 -1
  171. package/dist/config/sites.d.ts +6 -4
  172. package/dist/config/sites.d.ts.map +1 -0
  173. package/dist/config/sites.js +15 -11
  174. package/dist/config/sites.js.map +1 -1
  175. package/dist/config/userChannels.d.ts +129 -30
  176. package/dist/config/userChannels.d.ts.map +1 -0
  177. package/dist/config/userChannels.js +333 -68
  178. package/dist/config/userChannels.js.map +1 -1
  179. package/dist/config/userConfig.d.ts +14 -9
  180. package/dist/config/userConfig.d.ts.map +1 -0
  181. package/dist/config/userConfig.js +87 -25
  182. package/dist/config/userConfig.js.map +1 -1
  183. package/dist/config/userProfiles.d.ts +10 -6
  184. package/dist/config/userProfiles.d.ts.map +1 -0
  185. package/dist/config/userProfiles.js +64 -39
  186. package/dist/config/userProfiles.js.map +1 -1
  187. package/dist/hdhr/channelMap.d.ts +1 -0
  188. package/dist/hdhr/channelMap.d.ts.map +1 -0
  189. package/dist/hdhr/deviceId.d.ts +1 -0
  190. package/dist/hdhr/deviceId.d.ts.map +1 -0
  191. package/dist/hdhr/discover.d.ts +1 -0
  192. package/dist/hdhr/discover.d.ts.map +1 -0
  193. package/dist/hdhr/discover.js +48 -47
  194. package/dist/hdhr/discover.js.map +1 -1
  195. package/dist/hdhr/getHandlers.d.ts +29 -0
  196. package/dist/hdhr/getHandlers.d.ts.map +1 -0
  197. package/dist/hdhr/getHandlers.js +196 -0
  198. package/dist/hdhr/getHandlers.js.map +1 -0
  199. package/dist/hdhr/identity.d.ts +36 -0
  200. package/dist/hdhr/identity.d.ts.map +1 -0
  201. package/dist/hdhr/identity.js +51 -0
  202. package/dist/hdhr/identity.js.map +1 -0
  203. package/dist/hdhr/index.d.ts +22 -4
  204. package/dist/hdhr/index.d.ts.map +1 -0
  205. package/dist/hdhr/index.js +229 -54
  206. package/dist/hdhr/index.js.map +1 -1
  207. package/dist/hdhr/protocol.d.ts +75 -0
  208. package/dist/hdhr/protocol.d.ts.map +1 -0
  209. package/dist/hdhr/protocol.js +264 -0
  210. package/dist/hdhr/protocol.js.map +1 -0
  211. package/dist/hdhr/tunerState.d.ts +24 -0
  212. package/dist/hdhr/tunerState.d.ts.map +1 -0
  213. package/dist/hdhr/tunerState.js +67 -0
  214. package/dist/hdhr/tunerState.js.map +1 -0
  215. package/dist/hdhr/udp.d.ts +43 -0
  216. package/dist/hdhr/udp.d.ts.map +1 -0
  217. package/dist/hdhr/udp.js +277 -0
  218. package/dist/hdhr/udp.js.map +1 -0
  219. package/dist/identity.d.ts +1 -0
  220. package/dist/identity.d.ts.map +1 -0
  221. package/dist/identity.js +13 -11
  222. package/dist/identity.js.map +1 -1
  223. package/dist/index.d.ts +1 -0
  224. package/dist/index.d.ts.map +1 -0
  225. package/dist/index.js +11 -12
  226. package/dist/index.js.map +1 -1
  227. package/dist/native/codecInference.d.ts +1 -0
  228. package/dist/native/codecInference.d.ts.map +1 -0
  229. package/dist/native/codecInference.js +50 -5
  230. package/dist/native/codecInference.js.map +1 -1
  231. package/dist/native/decrypt.d.ts +6 -2
  232. package/dist/native/decrypt.d.ts.map +1 -0
  233. package/dist/native/decrypt.js +15 -8
  234. package/dist/native/decrypt.js.map +1 -1
  235. package/dist/native/index.d.ts +40 -17
  236. package/dist/native/index.d.ts.map +1 -0
  237. package/dist/native/index.js +281 -120
  238. package/dist/native/index.js.map +1 -1
  239. package/dist/native/probe.d.ts +114 -18
  240. package/dist/native/probe.d.ts.map +1 -0
  241. package/dist/native/probe.js +452 -115
  242. package/dist/native/probe.js.map +1 -1
  243. package/dist/native/proxy.d.ts +57 -5
  244. package/dist/native/proxy.d.ts.map +1 -0
  245. package/dist/native/proxy.js +604 -170
  246. package/dist/native/proxy.js.map +1 -1
  247. package/dist/native/tokenExpiry.d.ts +1 -0
  248. package/dist/native/tokenExpiry.d.ts.map +1 -0
  249. package/dist/native/tokenExpiry.js +1 -1
  250. package/dist/routes/assets.d.ts +1 -0
  251. package/dist/routes/assets.d.ts.map +1 -0
  252. package/dist/routes/assets.js +4 -2
  253. package/dist/routes/assets.js.map +1 -1
  254. package/dist/routes/auth.d.ts +1 -0
  255. package/dist/routes/auth.d.ts.map +1 -0
  256. package/dist/routes/auth.js +0 -1
  257. package/dist/routes/auth.js.map +1 -1
  258. package/dist/routes/cdp.d.ts +5 -5
  259. package/dist/routes/cdp.d.ts.map +1 -0
  260. package/dist/routes/cdp.js +59 -20
  261. package/dist/routes/cdp.js.map +1 -1
  262. package/dist/routes/channels.d.ts +1 -0
  263. package/dist/routes/channels.d.ts.map +1 -0
  264. package/dist/routes/channels.js +2 -1
  265. package/dist/routes/channels.js.map +1 -1
  266. package/dist/routes/clientActions.d.ts +98 -0
  267. package/dist/routes/clientActions.d.ts.map +1 -0
  268. package/dist/routes/clientActions.js +113 -0
  269. package/dist/routes/clientActions.js.map +1 -0
  270. package/dist/routes/components.d.ts +5 -3
  271. package/dist/routes/components.d.ts.map +1 -0
  272. package/dist/routes/components.js +36 -39
  273. package/dist/routes/components.js.map +1 -1
  274. package/dist/routes/config/channels/endpoints/browse.d.ts +1 -0
  275. package/dist/routes/config/channels/endpoints/browse.d.ts.map +1 -0
  276. package/dist/routes/config/channels/endpoints/browse.js +23 -10
  277. package/dist/routes/config/channels/endpoints/browse.js.map +1 -1
  278. package/dist/routes/config/channels/endpoints/bulk.d.ts +1 -0
  279. package/dist/routes/config/channels/endpoints/bulk.d.ts.map +1 -0
  280. package/dist/routes/config/channels/endpoints/bulk.js +5 -3
  281. package/dist/routes/config/channels/endpoints/bulk.js.map +1 -1
  282. package/dist/routes/config/channels/endpoints/crud.d.ts +1 -0
  283. package/dist/routes/config/channels/endpoints/crud.d.ts.map +1 -0
  284. package/dist/routes/config/channels/endpoints/crud.js +85 -93
  285. package/dist/routes/config/channels/endpoints/crud.js.map +1 -1
  286. package/dist/routes/config/channels/endpoints/importExport.d.ts +1 -0
  287. package/dist/routes/config/channels/endpoints/importExport.d.ts.map +1 -0
  288. package/dist/routes/config/channels/endpoints/importExport.js +6 -3
  289. package/dist/routes/config/channels/endpoints/importExport.js.map +1 -1
  290. package/dist/routes/config/channels/endpoints/predefined.d.ts +1 -0
  291. package/dist/routes/config/channels/endpoints/predefined.d.ts.map +1 -0
  292. package/dist/routes/config/channels/endpoints/prefs.d.ts +1 -0
  293. package/dist/routes/config/channels/endpoints/prefs.d.ts.map +1 -0
  294. package/dist/routes/config/channels/endpoints/prefs.js +2 -1
  295. package/dist/routes/config/channels/endpoints/prefs.js.map +1 -1
  296. package/dist/routes/config/channels/endpoints/service.d.ts +2 -1
  297. package/dist/routes/config/channels/endpoints/service.d.ts.map +1 -0
  298. package/dist/routes/config/channels/endpoints/service.js +3 -1
  299. package/dist/routes/config/channels/endpoints/service.js.map +1 -1
  300. package/dist/routes/config/channels/endpoints/tags.d.ts +1 -0
  301. package/dist/routes/config/channels/endpoints/tags.d.ts.map +1 -0
  302. package/dist/routes/config/channels/endpoints/tags.js +13 -7
  303. package/dist/routes/config/channels/endpoints/tags.js.map +1 -1
  304. package/dist/routes/config/channels/healthBridge.d.ts +24 -0
  305. package/dist/routes/config/channels/healthBridge.d.ts.map +1 -0
  306. package/dist/routes/config/channels/healthBridge.js +71 -0
  307. package/dist/routes/config/channels/healthBridge.js.map +1 -0
  308. package/dist/routes/config/channels/http/handler.d.ts +1 -0
  309. package/dist/routes/config/channels/http/handler.d.ts.map +1 -0
  310. package/dist/routes/config/channels/http/playlistHint.d.ts +2 -1
  311. package/dist/routes/config/channels/http/playlistHint.d.ts.map +1 -0
  312. package/dist/routes/config/channels/http/playlistHint.js +16 -2
  313. package/dist/routes/config/channels/http/playlistHint.js.map +1 -1
  314. package/dist/routes/config/channels/http/serviceWarning.d.ts +4 -2
  315. package/dist/routes/config/channels/http/serviceWarning.d.ts.map +1 -0
  316. package/dist/routes/config/channels/http/serviceWarning.js +3 -2
  317. package/dist/routes/config/channels/http/serviceWarning.js.map +1 -1
  318. package/dist/routes/config/channels/index.d.ts +1 -0
  319. package/dist/routes/config/channels/index.d.ts.map +1 -0
  320. package/dist/routes/config/channels/setup.d.ts +1 -0
  321. package/dist/routes/config/channels/setup.d.ts.map +1 -0
  322. package/dist/routes/config/channels/table.d.ts +3 -3
  323. package/dist/routes/config/channels/table.d.ts.map +1 -0
  324. package/dist/routes/config/channels/table.js +148 -107
  325. package/dist/routes/config/channels/table.js.map +1 -1
  326. package/dist/routes/config/http/envelope.d.ts +1 -0
  327. package/dist/routes/config/http/envelope.d.ts.map +1 -0
  328. package/dist/routes/config/index.d.ts +35 -3
  329. package/dist/routes/config/index.d.ts.map +1 -0
  330. package/dist/routes/config/index.js +51 -2
  331. package/dist/routes/config/index.js.map +1 -1
  332. package/dist/routes/config/services.d.ts +5 -4
  333. package/dist/routes/config/services.d.ts.map +1 -0
  334. package/dist/routes/config/services.js +57 -43
  335. package/dist/routes/config/services.js.map +1 -1
  336. package/dist/routes/config/settings.d.ts +1 -0
  337. package/dist/routes/config/settings.d.ts.map +1 -0
  338. package/dist/routes/config/settings.js +98 -49
  339. package/dist/routes/config/settings.js.map +1 -1
  340. package/dist/routes/debug.d.ts +1 -0
  341. package/dist/routes/debug.d.ts.map +1 -0
  342. package/dist/routes/debug.js +277 -139
  343. package/dist/routes/debug.js.map +1 -1
  344. package/dist/routes/health.d.ts +39 -1
  345. package/dist/routes/health.d.ts.map +1 -0
  346. package/dist/routes/health.js +33 -27
  347. package/dist/routes/health.js.map +1 -1
  348. package/dist/routes/hls.d.ts +1 -0
  349. package/dist/routes/hls.d.ts.map +1 -0
  350. package/dist/routes/icons.d.ts +1 -0
  351. package/dist/routes/icons.d.ts.map +1 -0
  352. package/dist/routes/icons.js +1 -1
  353. package/dist/routes/index.d.ts +1 -0
  354. package/dist/routes/index.d.ts.map +1 -0
  355. package/dist/routes/logs.d.ts +1 -0
  356. package/dist/routes/logs.d.ts.map +1 -0
  357. package/dist/routes/logs.js +14 -4
  358. package/dist/routes/logs.js.map +1 -1
  359. package/dist/routes/mpegts.d.ts +1 -0
  360. package/dist/routes/mpegts.d.ts.map +1 -0
  361. package/dist/routes/play.d.ts +1 -0
  362. package/dist/routes/play.d.ts.map +1 -0
  363. package/dist/routes/playlist.d.ts +1 -0
  364. package/dist/routes/playlist.d.ts.map +1 -0
  365. package/dist/routes/playlist.js +7 -2
  366. package/dist/routes/playlist.js.map +1 -1
  367. package/dist/routes/root/content.d.ts +8 -5
  368. package/dist/routes/root/content.d.ts.map +1 -0
  369. package/dist/routes/root/content.js +29 -26
  370. package/dist/routes/root/content.js.map +1 -1
  371. package/dist/routes/root/index.d.ts +2 -1
  372. package/dist/routes/root/index.d.ts.map +1 -0
  373. package/dist/routes/root/index.js +26 -21
  374. package/dist/routes/root/index.js.map +1 -1
  375. package/dist/routes/root/scripts/channels.d.ts +6 -0
  376. package/dist/routes/root/scripts/channels.d.ts.map +1 -0
  377. package/dist/routes/root/scripts/channels.js +197 -118
  378. package/dist/routes/root/scripts/channels.js.map +1 -1
  379. package/dist/routes/root/scripts/clientEscape.d.ts +22 -0
  380. package/dist/routes/root/scripts/clientEscape.d.ts.map +1 -0
  381. package/dist/routes/root/scripts/clientEscape.js +41 -0
  382. package/dist/routes/root/scripts/clientEscape.js.map +1 -0
  383. package/dist/routes/root/scripts/clientUrl.d.ts +23 -0
  384. package/dist/routes/root/scripts/clientUrl.d.ts.map +1 -0
  385. package/dist/routes/root/scripts/clientUrl.js +44 -0
  386. package/dist/routes/root/scripts/clientUrl.js.map +1 -0
  387. package/dist/routes/root/scripts/config.d.ts +6 -0
  388. package/dist/routes/root/scripts/config.d.ts.map +1 -0
  389. package/dist/routes/root/scripts/config.js +110 -24
  390. package/dist/routes/root/scripts/config.js.map +1 -1
  391. package/dist/routes/root/scripts/index.d.ts +1 -0
  392. package/dist/routes/root/scripts/index.d.ts.map +1 -0
  393. package/dist/routes/root/scripts/shared.d.ts +1 -0
  394. package/dist/routes/root/scripts/shared.d.ts.map +1 -0
  395. package/dist/routes/root/scripts/shared.js +112 -26
  396. package/dist/routes/root/scripts/shared.js.map +1 -1
  397. package/dist/routes/root/scripts/status.d.ts +1 -0
  398. package/dist/routes/root/scripts/status.d.ts.map +1 -0
  399. package/dist/routes/root/scripts/status.handlers.d.ts +23 -33
  400. package/dist/routes/root/scripts/status.handlers.d.ts.map +1 -0
  401. package/dist/routes/root/scripts/status.handlers.js +46 -92
  402. package/dist/routes/root/scripts/status.handlers.js.map +1 -1
  403. package/dist/routes/root/scripts/status.js +21 -8
  404. package/dist/routes/root/scripts/status.js.map +1 -1
  405. package/dist/routes/root/styles.d.ts +1 -0
  406. package/dist/routes/root/styles.d.ts.map +1 -0
  407. package/dist/routes/root/styles.js +7 -4
  408. package/dist/routes/root/styles.js.map +1 -1
  409. package/dist/routes/services.d.ts +13 -1
  410. package/dist/routes/services.d.ts.map +1 -0
  411. package/dist/routes/services.js +81 -68
  412. package/dist/routes/services.js.map +1 -1
  413. package/dist/routes/sse.d.ts +1 -0
  414. package/dist/routes/sse.d.ts.map +1 -0
  415. package/dist/routes/sse.js +1 -1
  416. package/dist/routes/sse.js.map +1 -1
  417. package/dist/routes/streams.d.ts +2 -1
  418. package/dist/routes/streams.d.ts.map +1 -0
  419. package/dist/routes/streams.js +6 -4
  420. package/dist/routes/streams.js.map +1 -1
  421. package/dist/routes/theme.d.ts +3 -2
  422. package/dist/routes/theme.d.ts.map +1 -0
  423. package/dist/routes/theme.js +6 -5
  424. package/dist/routes/theme.js.map +1 -1
  425. package/dist/routes/ui.d.ts +3 -1
  426. package/dist/routes/ui.d.ts.map +1 -0
  427. package/dist/routes/ui.js +16 -4
  428. package/dist/routes/ui.js.map +1 -1
  429. package/dist/routes/upgrade.d.ts +1 -0
  430. package/dist/routes/upgrade.d.ts.map +1 -0
  431. package/dist/routes/upgrade.js +6 -2
  432. package/dist/routes/upgrade.js.map +1 -1
  433. package/dist/service/commands.context.d.ts +1 -0
  434. package/dist/service/commands.context.d.ts.map +1 -0
  435. package/dist/service/commands.context.js +4 -3
  436. package/dist/service/commands.context.js.map +1 -1
  437. package/dist/service/commands.d.ts +1 -0
  438. package/dist/service/commands.d.ts.map +1 -0
  439. package/dist/service/commands.js +10 -13
  440. package/dist/service/commands.js.map +1 -1
  441. package/dist/service/generators.context.d.ts +1 -0
  442. package/dist/service/generators.context.d.ts.map +1 -0
  443. package/dist/service/generators.context.js +1 -1
  444. package/dist/service/generators.context.js.map +1 -1
  445. package/dist/service/generators.d.ts +4 -3
  446. package/dist/service/generators.d.ts.map +1 -0
  447. package/dist/service/generators.js +111 -31
  448. package/dist/service/generators.js.map +1 -1
  449. package/dist/service/index.d.ts +1 -0
  450. package/dist/service/index.d.ts.map +1 -0
  451. package/dist/streaming/captureLock.d.ts +44 -0
  452. package/dist/streaming/captureLock.d.ts.map +1 -0
  453. package/dist/streaming/captureLock.js +95 -0
  454. package/dist/streaming/captureLock.js.map +1 -0
  455. package/dist/streaming/captureSession.d.ts +31 -0
  456. package/dist/streaming/captureSession.d.ts.map +1 -0
  457. package/dist/streaming/captureSession.js +54 -0
  458. package/dist/streaming/captureSession.js.map +1 -0
  459. package/dist/streaming/clients.d.ts +1 -0
  460. package/dist/streaming/clients.d.ts.map +1 -0
  461. package/dist/streaming/codec.d.ts +1 -0
  462. package/dist/streaming/codec.d.ts.map +1 -0
  463. package/dist/streaming/fmp4Segmenter.d.ts +29 -2
  464. package/dist/streaming/fmp4Segmenter.d.ts.map +1 -0
  465. package/dist/streaming/fmp4Segmenter.js +79 -26
  466. package/dist/streaming/fmp4Segmenter.js.map +1 -1
  467. package/dist/streaming/hls.d.ts +18 -7
  468. package/dist/streaming/hls.d.ts.map +1 -0
  469. package/dist/streaming/hls.js +220 -120
  470. package/dist/streaming/hls.js.map +1 -1
  471. package/dist/streaming/hlsResume.d.ts +1 -0
  472. package/dist/streaming/hlsResume.d.ts.map +1 -0
  473. package/dist/streaming/hlsResume.js +1 -1
  474. package/dist/streaming/hlsResume.js.map +1 -1
  475. package/dist/streaming/hlsSegments.d.ts +59 -2
  476. package/dist/streaming/hlsSegments.d.ts.map +1 -0
  477. package/dist/streaming/hlsSegments.js +147 -17
  478. package/dist/streaming/hlsSegments.js.map +1 -1
  479. package/dist/streaming/lifecycle.d.ts +4 -1
  480. package/dist/streaming/lifecycle.d.ts.map +1 -0
  481. package/dist/streaming/lifecycle.js +65 -85
  482. package/dist/streaming/lifecycle.js.map +1 -1
  483. package/dist/streaming/monitor.d.ts +6 -4
  484. package/dist/streaming/monitor.d.ts.map +1 -0
  485. package/dist/streaming/monitor.js +343 -145
  486. package/dist/streaming/monitor.js.map +1 -1
  487. package/dist/streaming/mp4Parser.d.ts +5 -4
  488. package/dist/streaming/mp4Parser.d.ts.map +1 -0
  489. package/dist/streaming/mp4Parser.js +36 -13
  490. package/dist/streaming/mp4Parser.js.map +1 -1
  491. package/dist/streaming/mpegts.d.ts +16 -0
  492. package/dist/streaming/mpegts.d.ts.map +1 -0
  493. package/dist/streaming/mpegts.js +61 -17
  494. package/dist/streaming/mpegts.js.map +1 -1
  495. package/dist/streaming/playlistBuilder.d.ts +1 -0
  496. package/dist/streaming/playlistBuilder.d.ts.map +1 -0
  497. package/dist/streaming/preroll.d.ts +32 -5
  498. package/dist/streaming/preroll.d.ts.map +1 -0
  499. package/dist/streaming/preroll.js +70 -32
  500. package/dist/streaming/preroll.js.map +1 -1
  501. package/dist/streaming/pretune.d.ts +26 -1
  502. package/dist/streaming/pretune.d.ts.map +1 -0
  503. package/dist/streaming/pretune.js +22 -24
  504. package/dist/streaming/pretune.js.map +1 -1
  505. package/dist/streaming/pretuneTimers.d.ts +24 -0
  506. package/dist/streaming/pretuneTimers.d.ts.map +1 -0
  507. package/dist/streaming/pretuneTimers.js +56 -0
  508. package/dist/streaming/pretuneTimers.js.map +1 -0
  509. package/dist/streaming/recovery.d.ts +139 -5
  510. package/dist/streaming/recovery.d.ts.map +1 -0
  511. package/dist/streaming/recovery.js +160 -16
  512. package/dist/streaming/recovery.js.map +1 -1
  513. package/dist/streaming/registry.d.ts +27 -9
  514. package/dist/streaming/registry.d.ts.map +1 -0
  515. package/dist/streaming/registry.js +21 -3
  516. package/dist/streaming/registry.js.map +1 -1
  517. package/dist/streaming/setup.d.ts +157 -29
  518. package/dist/streaming/setup.d.ts.map +1 -0
  519. package/dist/streaming/setup.js +1034 -458
  520. package/dist/streaming/setup.js.map +1 -1
  521. package/dist/streaming/showInfo.d.ts +21 -1
  522. package/dist/streaming/showInfo.d.ts.map +1 -0
  523. package/dist/streaming/showInfo.js +31 -16
  524. package/dist/streaming/showInfo.js.map +1 -1
  525. package/dist/streaming/statusEmitter.d.ts +10 -10
  526. package/dist/streaming/statusEmitter.d.ts.map +1 -0
  527. package/dist/streaming/statusEmitter.js +5 -8
  528. package/dist/streaming/statusEmitter.js.map +1 -1
  529. package/dist/types/channels.d.ts +16 -4
  530. package/dist/types/channels.d.ts.map +1 -0
  531. package/dist/types/channels.js +4 -2
  532. package/dist/types/channels.js.map +1 -1
  533. package/dist/types/config.d.ts +16 -5
  534. package/dist/types/config.d.ts.map +1 -0
  535. package/dist/types/index.d.ts +3 -2
  536. package/dist/types/index.d.ts.map +1 -0
  537. package/dist/types/profiles.d.ts +6 -4
  538. package/dist/types/profiles.d.ts.map +1 -0
  539. package/dist/types/selection.d.ts +31 -3
  540. package/dist/types/selection.d.ts.map +1 -0
  541. package/dist/types/selection.js +4 -2
  542. package/dist/types/selection.js.map +1 -1
  543. package/dist/types/shared.d.ts +1 -0
  544. package/dist/types/shared.d.ts.map +1 -0
  545. package/dist/types/streaming.d.ts +8 -0
  546. package/dist/types/streaming.d.ts.map +1 -0
  547. package/dist/upgrade/commands.context.d.ts +1 -0
  548. package/dist/upgrade/commands.context.d.ts.map +1 -0
  549. package/dist/upgrade/commands.context.js +14 -10
  550. package/dist/upgrade/commands.context.js.map +1 -1
  551. package/dist/upgrade/commands.d.ts +8 -15
  552. package/dist/upgrade/commands.d.ts.map +1 -0
  553. package/dist/upgrade/commands.js +27 -28
  554. package/dist/upgrade/commands.js.map +1 -1
  555. package/dist/upgrade/detection.context.d.ts +1 -0
  556. package/dist/upgrade/detection.context.d.ts.map +1 -0
  557. package/dist/upgrade/detection.context.js +4 -1
  558. package/dist/upgrade/detection.context.js.map +1 -1
  559. package/dist/upgrade/detection.d.ts +9 -7
  560. package/dist/upgrade/detection.d.ts.map +1 -0
  561. package/dist/upgrade/detection.js +23 -54
  562. package/dist/upgrade/detection.js.map +1 -1
  563. package/dist/upgrade/index.d.ts +2 -0
  564. package/dist/upgrade/index.d.ts.map +1 -0
  565. package/dist/upgrade/index.js +1 -0
  566. package/dist/upgrade/index.js.map +1 -1
  567. package/dist/upgrade/lifecycle.context.d.ts +20 -0
  568. package/dist/upgrade/lifecycle.context.d.ts.map +1 -0
  569. package/dist/upgrade/lifecycle.context.js +47 -0
  570. package/dist/upgrade/lifecycle.context.js.map +1 -0
  571. package/dist/upgrade/lifecycle.d.ts +72 -0
  572. package/dist/upgrade/lifecycle.d.ts.map +1 -0
  573. package/dist/upgrade/lifecycle.js +175 -0
  574. package/dist/upgrade/lifecycle.js.map +1 -0
  575. package/dist/upgrade/pathHandle.d.ts +30 -0
  576. package/dist/upgrade/pathHandle.d.ts.map +1 -0
  577. package/dist/upgrade/pathHandle.js +102 -0
  578. package/dist/upgrade/pathHandle.js.map +1 -0
  579. package/dist/utils/bootSession.context.d.ts +16 -0
  580. package/dist/utils/bootSession.context.d.ts.map +1 -0
  581. package/dist/utils/bootSession.context.js +60 -0
  582. package/dist/utils/bootSession.context.js.map +1 -0
  583. package/dist/utils/bootSession.d.ts +17 -0
  584. package/dist/utils/bootSession.d.ts.map +1 -0
  585. package/dist/utils/bootSession.js +20 -0
  586. package/dist/utils/bootSession.js.map +1 -0
  587. package/dist/utils/chromeFetch.d.ts +1 -0
  588. package/dist/utils/chromeFetch.d.ts.map +1 -0
  589. package/dist/utils/cliOutput.d.ts +1 -0
  590. package/dist/utils/cliOutput.d.ts.map +1 -0
  591. package/dist/utils/clock.d.ts +5 -4
  592. package/dist/utils/clock.d.ts.map +1 -0
  593. package/dist/utils/clock.js +19 -16
  594. package/dist/utils/clock.js.map +1 -1
  595. package/dist/utils/debugFilter.d.ts +9 -1
  596. package/dist/utils/debugFilter.d.ts.map +1 -0
  597. package/dist/utils/debugFilter.js +60 -33
  598. package/dist/utils/debugFilter.js.map +1 -1
  599. package/dist/utils/delay.d.ts +48 -15
  600. package/dist/utils/delay.d.ts.map +1 -0
  601. package/dist/utils/delay.js +73 -20
  602. package/dist/utils/delay.js.map +1 -1
  603. package/dist/utils/errors.d.ts +32 -0
  604. package/dist/utils/errors.d.ts.map +1 -0
  605. package/dist/utils/errors.js +45 -4
  606. package/dist/utils/errors.js.map +1 -1
  607. package/dist/utils/evaluate.d.ts +1 -0
  608. package/dist/utils/evaluate.d.ts.map +1 -0
  609. package/dist/utils/evaluate.js +29 -23
  610. package/dist/utils/evaluate.js.map +1 -1
  611. package/dist/utils/ffmpeg.context.d.ts +1 -0
  612. package/dist/utils/ffmpeg.context.d.ts.map +1 -0
  613. package/dist/utils/ffmpeg.context.js.map +1 -1
  614. package/dist/utils/ffmpeg.d.ts +29 -7
  615. package/dist/utils/ffmpeg.d.ts.map +1 -0
  616. package/dist/utils/ffmpeg.js +32 -14
  617. package/dist/utils/ffmpeg.js.map +1 -1
  618. package/dist/utils/fileLogger.d.ts +3 -1
  619. package/dist/utils/fileLogger.d.ts.map +1 -0
  620. package/dist/utils/fileLogger.js +64 -29
  621. package/dist/utils/fileLogger.js.map +1 -1
  622. package/dist/utils/format.d.ts +14 -1
  623. package/dist/utils/format.d.ts.map +1 -0
  624. package/dist/utils/format.js +19 -5
  625. package/dist/utils/format.js.map +1 -1
  626. package/dist/utils/index.d.ts +4 -0
  627. package/dist/utils/index.d.ts.map +1 -0
  628. package/dist/utils/index.js +3 -0
  629. package/dist/utils/index.js.map +1 -1
  630. package/dist/utils/logEmitter.d.ts +1 -0
  631. package/dist/utils/logEmitter.d.ts.map +1 -0
  632. package/dist/utils/logger.d.ts +1 -0
  633. package/dist/utils/logger.d.ts.map +1 -0
  634. package/dist/utils/logger.js +10 -6
  635. package/dist/utils/logger.js.map +1 -1
  636. package/dist/utils/m3u.d.ts +3 -2
  637. package/dist/utils/m3u.d.ts.map +1 -0
  638. package/dist/utils/m3u.js +4 -3
  639. package/dist/utils/m3u.js.map +1 -1
  640. package/dist/utils/markup.d.ts +13 -1
  641. package/dist/utils/markup.d.ts.map +1 -0
  642. package/dist/utils/markup.js +28 -4
  643. package/dist/utils/markup.js.map +1 -1
  644. package/dist/utils/memo.d.ts +3 -2
  645. package/dist/utils/memo.d.ts.map +1 -0
  646. package/dist/utils/memo.js +3 -3
  647. package/dist/utils/morganStream.d.ts +1 -0
  648. package/dist/utils/morganStream.d.ts.map +1 -0
  649. package/dist/utils/network.d.ts +1 -0
  650. package/dist/utils/network.d.ts.map +1 -0
  651. package/dist/utils/network.js +2 -2
  652. package/dist/utils/pid.d.ts +3 -25
  653. package/dist/utils/pid.d.ts.map +1 -0
  654. package/dist/utils/pid.js +10 -49
  655. package/dist/utils/pid.js.map +1 -1
  656. package/dist/utils/platform.d.ts +3 -1
  657. package/dist/utils/platform.d.ts.map +1 -0
  658. package/dist/utils/platform.js +2 -1
  659. package/dist/utils/platform.js.map +1 -1
  660. package/dist/utils/processInspector.context.d.ts +8 -0
  661. package/dist/utils/processInspector.context.d.ts.map +1 -0
  662. package/dist/utils/processInspector.context.js +106 -0
  663. package/dist/utils/processInspector.context.js.map +1 -0
  664. package/dist/utils/processInspector.d.ts +55 -0
  665. package/dist/utils/processInspector.d.ts.map +1 -0
  666. package/dist/utils/processInspector.js +127 -0
  667. package/dist/utils/processInspector.js.map +1 -0
  668. package/dist/utils/retry.d.ts +25 -2
  669. package/dist/utils/retry.d.ts.map +1 -0
  670. package/dist/utils/retry.js +33 -6
  671. package/dist/utils/retry.js.map +1 -1
  672. package/dist/utils/runtimeIdentity.context.d.ts +7 -0
  673. package/dist/utils/runtimeIdentity.context.d.ts.map +1 -0
  674. package/dist/utils/runtimeIdentity.context.js +43 -0
  675. package/dist/utils/runtimeIdentity.context.js.map +1 -0
  676. package/dist/utils/runtimeIdentity.d.ts +98 -0
  677. package/dist/utils/runtimeIdentity.d.ts.map +1 -0
  678. package/dist/utils/runtimeIdentity.js +181 -0
  679. package/dist/utils/runtimeIdentity.js.map +1 -0
  680. package/dist/utils/sanitize.d.ts +1 -0
  681. package/dist/utils/sanitize.d.ts.map +1 -0
  682. package/dist/utils/streamContext.d.ts +2 -1
  683. package/dist/utils/streamContext.d.ts.map +1 -0
  684. package/dist/utils/streamContext.js +1 -1
  685. package/dist/utils/timing.d.ts +2 -1
  686. package/dist/utils/timing.d.ts.map +1 -0
  687. package/dist/utils/timing.js +0 -5
  688. package/dist/utils/timing.js.map +1 -1
  689. package/dist/utils/version.d.ts +1 -0
  690. package/dist/utils/version.d.ts.map +1 -0
  691. package/dist/utils/version.js +47 -13
  692. package/dist/utils/version.js.map +1 -1
  693. package/package.json +26 -20
@@ -2,9 +2,12 @@
2
2
  *
3
3
  * video.ts: Video context and playback handling for PrismCast.
4
4
  */
5
- import { EvaluateAbortError, LOG, delay, evaluateWithAbort, formatError, raceWithTimeout, startTimer } from "../utils/index.js";
6
- import { invalidateDirectUrl, resolveDirectUrl, selectChannel } from "./channelSelection.js";
5
+ import { EvaluateAbortError, LOG, delay, evaluateWithAbort, extractDomain, formatError, startTimer, waitWithTimeout } from "../utils/index.js";
6
+ import { getProvidersForDomain, invalidateDirectUrl, resolveDirectUrl, selectChannel } from "./channelSelection.js";
7
7
  import { CONFIG } from "../config/index.js";
8
+ import { classifyBlockedPage } from "./blockedPage.js";
9
+ import { markDomainAuthRequired } from "../config/health.js";
10
+ import { startOverlayHandling } from "./consent.js";
8
11
  /* These functions manage the video element lifecycle for streaming capture. The key challenges we solve:
9
12
  *
10
13
  * 1. Video context resolution: Video elements may be in the main page or nested inside iframes. Some streaming sites (like those using Brightcove or JW Player
@@ -53,7 +56,7 @@ export function buildVideoSelectorType(profile) {
53
56
  * The one exception is checkVideoPresence(), which needs all video elements (count, max readyState) rather than selecting one. It uses the inline selection pattern
54
57
  * since its requirements differ from the standard single-element selection.
55
58
  *
56
- * - The selectorType parameter MUST be passed as the first argument to evaluateWithAbort
59
+ * - selectorType must be the first element of the args array passed to evaluateWithAbort.
57
60
  */
58
61
  /**
59
62
  * Injects the shared video selector helper into the browser context. Called in createPageWithCapture() (setup.ts) after page creation and before navigation. The
@@ -78,9 +81,11 @@ let fullscreenQueue = Promise.resolve();
78
81
  * Gets the current state of the video element for health monitoring. Returns null if no video element is found.
79
82
  * @param context - The frame or page containing the video element.
80
83
  * @param selectorType - The video selector type for finding the element.
84
+ * @param timeoutMs - How long to wait for the read before giving up. Omit for the evaluate wrapper's default bound; a caller that already has reason to suspect
85
+ * an unresponsive tab passes a shorter one, since it needs to tell a hung page from a live one rather than wait out a slow answer.
81
86
  * @returns The video state or null if no video found.
82
87
  */
83
- export async function getVideoState(context, selectorType) {
88
+ export async function getVideoState(context, selectorType, timeoutMs) {
84
89
  return evaluateWithAbort(context, (type) => {
85
90
  const video = window.__prismcastSelectVideo?.(type) ?? null;
86
91
  if (!video) {
@@ -98,7 +103,7 @@ export async function getVideoState(context, selectorType) {
98
103
  videoWidth: video.videoWidth,
99
104
  volume: video.volume
100
105
  };
101
- }, [selectorType]);
106
+ }, [selectorType], timeoutMs);
102
107
  }
103
108
  /**
104
109
  * Enforces volume settings on the video element. Sets muted to false and volume to 1. This is called periodically during health monitoring to counter sites that
@@ -116,13 +121,17 @@ export async function enforceVideoVolume(context, selectorType) {
116
121
  }, [selectorType]);
117
122
  }
118
123
  /**
119
- * Mutes all video elements on the page to suppress wrong-channel audio during tuning. When a page loads (e.g. Hulu's /live), a default livestream auto-plays before
120
- * channel selection can switch to the target channel. Since the capture pipeline is already running, this audio bleeds into the stream. Muting preemptively silences it.
121
- * This is best-effort: if no video elements exist yet, or the page is in a transitional state, the error is silently ignored. The unmute happens naturally when
122
- * ensurePlayback() calls startVideoPlayback() after tuning completes.
124
+ * Mutes every video element currently on the page, silencing audio that does not belong to the channel being established. A multi-channel page auto-plays a
125
+ * default livestream on load (Hulu's /live, for example), and with a capture pipeline already running that audio bleeds into the stream until channel selection
126
+ * reaches the requested channel; a page reloaded to re-establish a channel meets the same default again. Two moments call for it: where a tune begins, and where
127
+ * a re-establishment's playback initialization settles - the latter because playback establishment unmutes by a direct property write that an already-playing
128
+ * element keeps until something mutes it again.
129
+ *
130
+ * Best-effort: a page with no video elements yet, or one in a transitional state, swallows the failure rather than failing the caller. The unmute that matters
131
+ * happens naturally when startVideoPlayback() runs on the tuned channel.
123
132
  * @param page - The Puppeteer page object.
124
133
  */
125
- async function muteExistingVideos(page) {
134
+ export async function muteExistingVideos(page) {
126
135
  try {
127
136
  await page.evaluate(() => {
128
137
  for (const video of Array.from(document.querySelectorAll("video"))) {
@@ -149,14 +158,21 @@ export async function suppressPageAudio(page) {
149
158
  // Override HTMLMediaElement.prototype.play to mute before playback. This runs before site JavaScript on all future navigations (including token refresh via
150
159
  // page.goto), matching the pattern established in precaching.ts. The override persists on the same page instance - a new page created by tab replacement won't
151
160
  // inherit it.
152
- await page.evaluateOnNewDocument(() => {
153
- // eslint-disable-next-line @typescript-eslint/unbound-method -- Prototype interception: originalPlay is captured here and invoked with .call(this) below.
154
- const originalPlay = HTMLMediaElement.prototype.play;
155
- HTMLMediaElement.prototype.play = async function () {
156
- this.muted = true;
157
- return originalPlay.call(this);
158
- };
159
- });
161
+ try {
162
+ await page.evaluateOnNewDocument(() => {
163
+ // eslint-disable-next-line @typescript-eslint/unbound-method -- Prototype interception: originalPlay is captured here and invoked with .call(this) below.
164
+ const originalPlay = HTMLMediaElement.prototype.play;
165
+ HTMLMediaElement.prototype.play = async function () {
166
+ this.muted = true;
167
+ return originalPlay.call(this);
168
+ };
169
+ });
170
+ }
171
+ catch {
172
+ // Best-effort, exactly like the immediate mute below. The page may be mid-navigation or tearing down as the stream switches to native delivery, and a failed
173
+ // override must not abort the switch it rides along with. Future navigations on this page then play unmuted until the next suppression pass runs, which is
174
+ // the cost the best-effort contract accepts.
175
+ }
160
176
  // Mute any videos that are already playing. The prototype override only affects future play() calls, so existing playback needs a direct mute.
161
177
  try {
162
178
  await page.evaluate(() => {
@@ -256,8 +272,8 @@ export async function startVideoPlayback(context, selectorType) {
256
272
  /**
257
273
  * Navigates a browser page to the specified URL with site-appropriate wait conditions. The navigation strategy depends on the site's player implementation:
258
274
  *
259
- * - waitForNetworkIdle=true: Wait for network activity to settle (no requests for 500ms). This ensures all JavaScript has loaded and the player is fully
260
- * initialized. Used for sites with complex async initialization.
275
+ * - waitForNetworkIdle=true: Wait for network activity to settle, allowing up to 2 concurrent connections for 500ms. This ensures all JavaScript has loaded
276
+ * and the player is fully initialized. Used for sites with complex async initialization.
261
277
  *
262
278
  * - waitForNetworkIdle=false: Return as soon as the page fires load event. Used for sites that have persistent connections or polling that would prevent
263
279
  * networkidle from ever completing.
@@ -271,8 +287,8 @@ export async function startVideoPlayback(context, selectorType) {
271
287
  export async function navigateToPage(page, url, profile) {
272
288
  if (profile.waitForNetworkIdle) {
273
289
  try {
274
- // Wait for network idle (no requests for 500ms). This ensures complex JavaScript players have fully initialized. The networkidle2 strategy allows up
275
- // to 2 concurrent requests, which handles sites with persistent connections for analytics.
290
+ // Wait for network activity to settle. This ensures complex JavaScript players have fully initialized. The networkidle2 strategy allows up to 2
291
+ // concurrent requests, which handles sites with persistent connections for analytics.
276
292
  await page.goto(url, { timeout: CONFIG.streaming.navigationTimeout, waitUntil: "networkidle2" });
277
293
  }
278
294
  catch (error) {
@@ -293,6 +309,29 @@ export async function navigateToPage(page, url, profile) {
293
309
  await page.goto(url);
294
310
  }
295
311
  }
312
+ /**
313
+ * Reloads the current page, mirroring navigateToPage's wait strategy. Two callers, one mechanism. An accepted embedded-player consent gate only creates the player
314
+ * iframe on a fresh load, so a reload re-renders the page with consent persisted and the video resolves on the second pass. A guide that never rendered is the
315
+ * other: the reload is what gives channel selection a fresh surface to read.
316
+ * @param page - The Puppeteer page object.
317
+ * @param profile - The site profile, whose waitForNetworkIdle flag selects the reload wait condition.
318
+ */
319
+ async function reloadPage(page, profile) {
320
+ const waitUntil = profile.waitForNetworkIdle ? "networkidle2" : "load";
321
+ try {
322
+ await page.reload({ timeout: CONFIG.streaming.navigationTimeout, waitUntil });
323
+ }
324
+ catch (error) {
325
+ // A reload timeout is often non-fatal - the player may already be present even if some background requests never settled. Mirror navigateToPage: warn and
326
+ // continue on timeout, propagate other errors.
327
+ if (error && (error.name === "TimeoutError")) {
328
+ LOG.warn("Page reload timed out after %sms.", CONFIG.streaming.navigationTimeout);
329
+ }
330
+ else {
331
+ throw error;
332
+ }
333
+ }
334
+ }
296
335
  /**
297
336
  * Finds the appropriate context (frame or page) containing the video element. Some streaming sites embed their video player in an iframe, which creates a
298
337
  * separate document context. We need to find this iframe and operate within it to access the video element.
@@ -316,7 +355,7 @@ export async function findVideoContext(page, profile) {
316
355
  await page.waitForSelector("iframe", { timeout: CONFIG.streaming.videoTimeout });
317
356
  // Poll for a video element to appear in any iframe. Complex embedded players (Brightcove, JW Player, etc.) load additional resources and scripts after the
318
357
  // iframe element appears, so the video may not be immediately available. We retry the search with brief pauses, using the configured delay as the overall
319
- // timeout ceiling. This replaces a fixed delay with early exit - if the video appears quickly, we proceed immediately.
358
+ // timeout ceiling. Polling with early exit avoids waiting the full delay when the video appears quickly.
320
359
  const deadline = Date.now() + CONFIG.playback.iframeInitDelay;
321
360
  let iframeSearchComplete = false;
322
361
  let lastFrameCount = 0;
@@ -353,16 +392,18 @@ export async function findVideoContext(page, profile) {
353
392
  await delay(200);
354
393
  }
355
394
  }
356
- // If no iframe contains a video, fall back to the main page. This is a potential issue for iframe-handling profiles since we expected the video to be in an
357
- // iframe. Log a warning and verify the main page actually has a video element.
358
- LOG.warn("No iframe contained video element. Falling back to main page context (searched %s frames).", Math.max(0, lastFrameCount - 1));
395
+ // No iframe contained a video, so fall back to the main page. This is a designed branch of the function's contract, not an error: it is reached transiently and
396
+ // recoverably whenever the embedded player has not loaded yet - most commonly on a consent-gated site's first pass, where the player iframe is withheld until the
397
+ // overlay handler accepts the consent and reloads. The authoritative failure signal, if the context genuinely has no playable video, is waitForVideoReady's
398
+ // timeout downstream; this fallback notice is therefore a DEBUG breadcrumb under browser:video rather than a WARN that would cry wolf on every gated tune.
399
+ LOG.debug("browser:video", "No iframe contained a video element; falling back to the main page context (searched %s frames).", Math.max(0, lastFrameCount - 1));
359
400
  // Check if the main page actually contains a video element.
360
401
  try {
361
402
  const mainPageHasVideo = await evaluateWithAbort(page, () => {
362
403
  return !!document.querySelector("video");
363
404
  });
364
405
  if (!mainPageHasVideo) {
365
- LOG.warn("Main page fallback: no video element found in main page either.");
406
+ LOG.debug("browser:video", "Main page fallback: no video element found in the main page either.");
366
407
  }
367
408
  }
368
409
  catch (_error) {
@@ -384,12 +425,15 @@ export async function findVideoContext(page, profile) {
384
425
  * or playback issues.
385
426
  * @param context - The frame or page containing the video element.
386
427
  * @param profile - The site profile with video selection preferences.
428
+ * @param signal - Optional abort signal; when aborted the wait is abandoned without emitting the timeout warning, so the caller can supersede it after accepting an
429
+ * embedded-player consent gate that triggers a reload.
387
430
  */
388
- export async function waitForVideoReady(context, profile) {
431
+ export async function waitForVideoReady(context, profile, signal) {
389
432
  // Use the per-domain video timeout if configured, otherwise fall back to the global default.
390
433
  const timeout = profile.videoTimeout ?? CONFIG.streaming.videoTimeout;
391
- // First, wait for any video element to appear in the DOM. This catches cases where the video element is created dynamically by JavaScript.
392
- await context.waitForSelector("video", { timeout });
434
+ // First, wait for any video element to appear in the DOM. This catches cases where the video element is created dynamically by JavaScript. The optional abort
435
+ // signal lets a caller abandon the wait cleanly - used when an embedded-player consent gate is accepted and the page is about to be reloaded.
436
+ await context.waitForSelector("video", { signal, timeout });
393
437
  // Scroll the video into view to satisfy Chrome's Intersection Observer autoplay policy. Chrome suppresses autoplay for offscreen videos, preventing them from
394
438
  // buffering and reaching readyState >= 3. Scrolling the video into the viewport unblocks autoplay. This is a no-op when the video is already visible.
395
439
  const postScrollState = await context.evaluate(() => {
@@ -412,17 +456,22 @@ export async function waitForVideoReady(context, profile) {
412
456
  return Array.from(videos).some((v) => {
413
457
  return v.readyState >= 3;
414
458
  });
415
- }, { timeout });
459
+ }, { signal, timeout });
416
460
  }
417
461
  else {
418
462
  // For standard sites with a single video, wait for that specific video to reach readyState >= 3.
419
463
  await context.waitForFunction(() => {
420
464
  const video = document.querySelector("video");
421
465
  return !!video && (video.readyState >= 3);
422
- }, { timeout });
466
+ }, { signal, timeout });
423
467
  }
424
468
  }
425
469
  catch (error) {
470
+ // An aborted wait is not a genuine readiness failure - the caller deliberately abandoned it (for example, after accepting an embedded-player consent gate that
471
+ // triggers a reload). Rethrow without the misleading timeout diagnostics so it can be ignored upstream.
472
+ if (signal?.aborted) {
473
+ throw error;
474
+ }
426
475
  // Capture the video element's state at the moment of timeout to aid diagnosis.
427
476
  const timeoutState = await context.evaluate(() => {
428
477
  const videos = document.querySelectorAll("video");
@@ -484,7 +533,7 @@ export async function applyVideoStyles(context, selectorType, important = false)
484
533
  * This function uses Object.defineProperty to intercept property access, making it impossible for site JavaScript to change muted or volume values. The property
485
534
  * descriptors are set to configurable: true so the browser can still access the underlying values for playback.
486
535
  *
487
- * The function is idempotent - a __volumeLocked flag on the video element prevents applying the lock multiple times.
536
+ * The function is safe to call more than once - a __volumeLocked flag on the video element prevents applying the lock multiple times.
488
537
  * @param context - The frame or page containing the video element.
489
538
  * @param selectorType - The video selector type for finding the element.
490
539
  */
@@ -530,6 +579,9 @@ export async function lockVolumeProperties(context, selectorType) {
530
579
  /**
531
580
  * Triggers fullscreen mode using the appropriate method for the site. Different sites have different fullscreen implementations:
532
581
  *
582
+ * - Fullscreen button (fullscreenSelector): When configured, this is tried first by clicking the site's own fullscreen control, which uses the site's native
583
+ * mechanism and is the most reliable approach when the button is present.
584
+ *
533
585
  * - Keyboard shortcuts (fullscreenKey): Many players use "f" as a keyboard shortcut for fullscreen. We send this keypress to activate the player's native
534
586
  * fullscreen mode.
535
587
  *
@@ -537,7 +589,7 @@ export async function lockVolumeProperties(context, selectorType) {
537
589
  * prompts or be blocked by CSP, but works on many sites.
538
590
  *
539
591
  * Note that we also apply CSS-based fullscreen styling separately (in applyVideoStyles), which provides a reliable fallback when native fullscreen methods fail.
540
- * @param page - The Puppeteer page object for keyboard input.
592
+ * @param page - The Puppeteer page object, used for keyboard input and for clicking the fullscreen button when fullscreenSelector is configured.
541
593
  * @param context - The frame or page containing the video element.
542
594
  * @param profile - The site profile indicating fullscreen method.
543
595
  * @param selectorType - The video selector type for finding the element.
@@ -651,8 +703,10 @@ async function isNativeFullscreenActive(context) {
651
703
  * dispatches real pointer events that Chrome recognizes as user gestures. The click may toggle play/pause on some players - the health monitor handles
652
704
  * re-starting playback if needed.
653
705
  *
654
- * Note: page.mouse.click() uses page-level coordinates, while getBoundingClientRect() in an iframe returns iframe-relative coordinates. This function works
655
- * correctly because all profiles with useRequestFullscreen are non-iframe (needsIframeHandling is false).
706
+ * Note: page.mouse.click() uses page-level coordinates, while getBoundingClientRect() inside an iframe returns iframe-relative coordinates. The iframe-embedded
707
+ * fullscreenApi profiles (the embeddedPlayer family) set both useRequestFullscreen and needsIframeHandling, so for those tunes the computed point can be offset
708
+ * from the real video position. That is tolerated here: the click exists only to register a trusted user gesture for transient activation, which any in-page
709
+ * click satisfies - it does not need to land precisely on the video - and a missed activation is recovered by the health monitor.
656
710
  * @param page - The Puppeteer page object.
657
711
  * @param context - The frame or page containing the video element.
658
712
  * @param selectorType - The video selector type for finding the element.
@@ -747,7 +801,8 @@ async function applyAggressiveFullscreen(context, selectorType) {
747
801
  export async function ensureFullscreen(page, context, profile, selectorType, skipNativeFullscreen) {
748
802
  const useNativeFullscreen = profile.useRequestFullscreen && !skipNativeFullscreen;
749
803
  // Inject a persistent stylesheet to hide site-specific overlay elements (e.g., player control bars, toolbars) that would otherwise appear in the captured stream.
750
- // The style tag persists for the page lifetime and is idempotent - duplicate injections from recovery calls are harmless since CSS rules are deduplicated.
804
+ // The style tag persists for the page lifetime. Duplicate injections from recovery calls are harmless because the reapplied rules produce the same computed
805
+ // style, though each call does add another <style> tag to the DOM.
751
806
  if (profile.hideSelector) {
752
807
  const css = profile.hideSelector + " { display: none !important; }";
753
808
  await page.addStyleTag({ content: css }).catch(() => { });
@@ -761,7 +816,7 @@ export async function ensureFullscreen(page, context, profile, selectorType, ski
761
816
  const FULLSCREEN_QUEUE_TIMEOUT = 10000;
762
817
  fullscreenQueue = fullscreenQueue.then(async () => {
763
818
  try {
764
- await raceWithTimeout(runFullscreenSequence(page, context, profile, selectorType, true), FULLSCREEN_QUEUE_TIMEOUT, new Error("Fullscreen queue entry timed out."));
819
+ await waitWithTimeout(runFullscreenSequence(page, context, profile, selectorType, true), FULLSCREEN_QUEUE_TIMEOUT, new Error("Fullscreen queue entry timed out."));
765
820
  }
766
821
  catch (error) {
767
822
  LOG.warn("Fullscreen queue entry failed: %s.", formatError(error));
@@ -879,7 +934,7 @@ async function runFullscreenSequence(page, context, profile, selectorType, useNa
879
934
  }
880
935
  /**
881
936
  * Ensures the video is playing with proper audio settings. This is the core playback function that handles both initial setup and recovery from stalls. It is
882
- * designed to be idempotent - safe to call multiple times without adverse effects.
937
+ * safe to call more than once without adverse effects.
883
938
  *
884
939
  * Recovery escalation levels (higher levels include all lower-level actions):
885
940
  *
@@ -934,62 +989,71 @@ export async function ensurePlayback(page, context, profile, options) {
934
989
  await lockVolumeProperties(context, selectorType);
935
990
  }
936
991
  }
992
+ const defaultVideoTuneDeps = { classifyBlockedPage, getProvidersForDomain, selectChannel, startOverlayHandling };
937
993
  /**
938
- * Dismisses any stale overlay or modal that may be covering the guide grid. After a failed click attempt on the on-now cell, the playback overlay or entity modal
939
- * can remain open, obscuring the guide and preventing subsequent channel selection attempts from locating guide rows. Pressing Escape closes most modal overlays
940
- * in React-based SPAs.
941
- * @param page - The Puppeteer page object.
994
+ * Diagnoses a failed tune by classifying the still-open page, which holds the evidence of why the tune failed. A confirmed provider authentication wall marks the
995
+ * domain needs-sign-in and replaces the generic failure with an actionable error; a wall on a domain with no registered provider is reported without marking (there
996
+ * is no channel-table row to point at, and an unmarked ad-hoc entry would have no clearing path); a consent overlay surfaces the standing detect-and-guide text;
997
+ * anything unrecognized - including a classification that outruns its own time budget - rethrows the original failure unchanged.
998
+ * @param page - The Puppeteer page object, still open at the point of failure.
999
+ * @param requestedUrl - The URL this tune navigated to; its registrable domain selects the provider and receives any needs-sign-in mark.
1000
+ * @param originalError - The failure that triggered the diagnosis, rethrown as-is when the page classifies as unknown.
1001
+ * @param deps - The injected tune collaborators used to resolve the provider and classify the blocked page.
1002
+ * @returns Never resolves - every classification outcome throws.
942
1003
  */
943
- async function dismissGuideOverlay(page) {
944
- try {
945
- await page.keyboard.press("Escape");
946
- // Brief delay for the overlay dismiss animation to complete and the guide grid to re-render.
947
- await delay(500);
948
- }
949
- catch (error) {
950
- // Overlay dismissal is best-effort. The overlay may not exist, or the page may be in a state where keyboard input is ignored.
951
- LOG.debug("browser:video", "Could not dismiss guide overlay: %s.", formatError(error));
1004
+ async function diagnoseBlockedTune(page, requestedUrl, originalError, deps) {
1005
+ const domain = extractDomain(requestedUrl);
1006
+ // Resolve the provider whose guide lives on this domain, when one is registered. Its declared auth wall indicators (when defined) sharpen the classification,
1007
+ // and a provider match is what authorizes the needs-sign-in mark below.
1008
+ const provider = deps.getProvidersForDomain(domain)[0];
1009
+ // classifyBlockedPage is never-throwing and self-bounded, so the call needs no timeout wrapper and no fallback catch: a timed-out or unreadable page classifies
1010
+ // unknown internally, and the original failure stands.
1011
+ const classification = await deps.classifyBlockedPage(page, { indicators: provider?.authWallIndicators, requestedUrl });
1012
+ switch (classification.kind) {
1013
+ case "authWall": {
1014
+ // A wall on a registered provider's domain is marked before the throw, so the stream setup layer reads the needs-sign-in state while composing the
1015
+ // client-visible message for this very failure.
1016
+ if (provider) {
1017
+ markDomainAuthRequired(domain);
1018
+ LOG.warn("The tune failed because %s is presenting an authentication wall (%s). Sign in from the channel table's login icon.", provider.label, classification.evidence);
1019
+ throw new Error("Tune failed: " + provider.label + " is presenting an authentication wall (" + classification.evidence + "). Sign in from the channel " +
1020
+ "table's login icon.");
1021
+ }
1022
+ throw new Error("Tune failed: " + domain + " is presenting an authentication wall (" + classification.evidence + ").");
1023
+ }
1024
+ case "consentOverlay": {
1025
+ // A consent or cookie prompt is blocking the page. Surface actionable guidance in place of the cryptic underlying failure - the viewer dismisses it once in
1026
+ // setup or login mode and the choice persists.
1027
+ throw new Error("This site is displaying a consent or cookie prompt that is blocking playback. Open it once in setup or login mode and dismiss the prompt - " +
1028
+ "your choice is remembered.");
1029
+ }
1030
+ default: {
1031
+ // Nothing recognizable on the page - the original failure is the most truthful report we have.
1032
+ throw originalError;
1033
+ }
952
1034
  }
953
1035
  }
954
- // Maximum duration in milliseconds for the dismiss modal poll. The poll checks for the modal element at regular intervals during the first few seconds of the
955
- // video wait. If the modal hasn't appeared within this window, it's not coming.
956
- const DISMISS_POLL_DURATION = 5000;
957
- // Interval in milliseconds between dismiss modal poll checks.
958
- const DISMISS_POLL_INTERVAL = 500;
959
1036
  /**
960
- * Polls for an intermittent modal element and clicks it if found. Runs as a fire-and-forget background task alongside waitForVideoReady() to catch modals that
961
- * render after page load but before video starts playing. The first check is immediate (zero-wait), followed by up to DISMISS_POLL_DURATION of periodic rechecks.
962
- * Once the modal is found and clicked (or the poll window expires), the function resolves silently - it never throws.
1037
+ * Runs a span of the tune under a phase-scoped overlay-handling poll. Creates the abort controller, launches the poll fire-and-forget for the given phase, hands the
1038
+ * body an abort() it calls before diagnosing a blocked page (so the poll cannot keep clicking mid-classification), and always aborts the poll when the body settles.
1039
+ * The video wait manages its own poll directly because it composes the poll with the embed-gate race and the paired reload; every other span of the tune uses this
1040
+ * helper, which is why the phase is typed to exclude videoWait. The poll is launched through the injected deps, so every phase shares the single startOverlayHandling
1041
+ * call the video wait already uses.
963
1042
  * @param page - The Puppeteer page object.
964
- * @param selector - The CSS selector for the modal element to dismiss.
1043
+ * @param profile - The resolved site profile.
1044
+ * @param phase - The overlay phase covering this span (any phase but videoWait, whose choreography is bespoke).
1045
+ * @param deps - The injected tune collaborators; the covering poll is launched through deps.startOverlayHandling.
1046
+ * @param run - The span to run; receives an abort() that stops the covering poll immediately, for use before a blocked-page diagnosis.
1047
+ * @returns The span's result.
965
1048
  */
966
- async function dismissModalPoll(page, selector) {
967
- const checks = Math.ceil(DISMISS_POLL_DURATION / DISMISS_POLL_INTERVAL);
968
- for (let i = 0; i < checks; i++) {
969
- // Delay between checks, but not before the first one - the first check is immediate.
970
- if (i > 0) {
971
- // eslint-disable-next-line no-await-in-loop
972
- await delay(DISMISS_POLL_INTERVAL);
973
- }
974
- try {
975
- // eslint-disable-next-line no-await-in-loop
976
- const dismissed = await page.evaluate((sel) => {
977
- const el = document.querySelector(sel);
978
- if (el) {
979
- el.click();
980
- return true;
981
- }
982
- return false;
983
- }, selector);
984
- if (dismissed) {
985
- LOG.debug("browser:video", "Dismissed modal via '%s'.", selector);
986
- return;
987
- }
988
- }
989
- catch {
990
- // Page may have navigated or closed during the poll. Silently stop polling.
991
- return;
992
- }
1049
+ async function withOverlayGuard(page, profile, phase, deps, run) {
1050
+ const controller = new AbortController();
1051
+ void deps.startOverlayHandling(page, profile, { phase, signal: controller.signal });
1052
+ try {
1053
+ return await run(() => { controller.abort(); });
1054
+ }
1055
+ finally {
1056
+ controller.abort();
993
1057
  }
994
1058
  }
995
1059
  /**
@@ -998,66 +1062,154 @@ async function dismissModalPoll(page, selector) {
998
1062
  * timeout, while channel selection and video setup run with their own internal time budgets (click retry loops, videoTimeout, etc.) without being killed by the
999
1063
  * navigation timeout.
1000
1064
  *
1001
- * For guideGrid channel selection failures, the function attempts a single retry after dismissing any stale overlay that may be covering the guide grid. This
1002
- * handles the case where a failed click attempt left an overlay open, causing subsequent locateOnNowCell calls to fail.
1065
+ * When channel selection fails because the guide surface itself never rendered, the function reloads the page and retries selection once. That failure class is
1066
+ * transient - a rail whose lazy tiles never populated, an SPA shell that did not come up - and a reload is what cures it. A failure on a guide that did render,
1067
+ * such as a channel name that is simply not in the lineup, gets no retry: reading the same rendered guide again would fail the same way, more slowly.
1003
1068
  *
1004
1069
  * @param page - The Puppeteer page object.
1005
1070
  * @param profile - The site profile containing all behavior flags.
1006
1071
  * @param options - Optional behaviors. See InitializePlaybackOptions.
1072
+ * @param deps - Injected tune collaborators for testing; defaults to defaultVideoTuneDeps in production.
1007
1073
  * @returns The video context (frame or page) for subsequent monitoring, and a directTune flag when the channel was tuned via API interception.
1008
1074
  */
1009
- export async function initializePlayback(page, profile, options = {}) {
1075
+ export async function initializePlayback(page, profile, options = {}, deps = defaultVideoTuneDeps) {
1010
1076
  const elapsed = startTimer();
1011
- const { persistResolution, skipChannelSelection = false } = options;
1077
+ const { persistResolution, requestedUrl, skipChannelSelection = false } = options;
1012
1078
  // Mute any existing video elements to suppress wrong-channel audio during tuning. On SPA-based providers (Hulu, Fox, USA Network, etc.), a default livestream
1013
1079
  // auto-plays when the page loads. Since the capture pipeline is already running, this audio bleeds into the stream until channel selection completes and
1014
1080
  // ensurePlayback() unmutes the correct channel. For providers where no video exists yet, this is a harmless no-op.
1015
1081
  await muteExistingVideos(page);
1016
- // For multi-channel players (like usanetwork.com/live with multiple channels), select the desired channel from the UI. The selectChannel function checks the
1017
- // profile's channelSelection strategy and channelSelector to determine if/how to select a channel. Skipped when navigating directly to a cached watch URL,
1018
- // since the URL already targets the correct channel.
1019
- let directTune = false;
1020
- if (!skipChannelSelection) {
1021
- let channelResult = await selectChannel(page, profile, { persistResolution });
1022
- if (!channelResult.success) {
1023
- // For guideGrid strategy, a stale overlay from a previous failed click attempt may be covering the guide. Dismiss it and retry channel selection once.
1024
- if (profile.channelSelection.strategy === "guideGrid") {
1025
- LOG.warn("Guide grid channel selection failed: %s. Dismissing overlay and retrying.", channelResult.reason ?? "Unknown reason");
1026
- await dismissGuideOverlay(page);
1027
- channelResult = await selectChannel(page, profile, { persistResolution });
1028
- }
1082
+ // The channel-selection walk, video-context resolution, and click-to-play run under a tuneSetup overlay poll, so a cookie banner or per-site modal that appears
1083
+ // during those phases is dismissed rather than silently burning their budgets. The guard aborts the poll at the phase boundary (its finally) before the video
1084
+ // wait launches its own poll, and each in-span failure aborts it explicitly before the blocked-page diagnosis so the poll cannot keep clicking mid-classification.
1085
+ const setup = await withOverlayGuard(page, profile, "tuneSetup", deps, async (abortGuard) => {
1086
+ // For multi-channel players (like usanetwork.com/live with multiple channels), select the desired channel from the UI. The selectChannel function checks the
1087
+ // profile's channelSelection strategy and channelSelector to determine if/how to select a channel. Skipped when navigating directly to a cached watch URL,
1088
+ // since the URL already targets the correct channel.
1089
+ let directTune = false;
1090
+ if (!skipChannelSelection) {
1091
+ let channelResult = await deps.selectChannel(page, profile, { persistResolution });
1029
1092
  if (!channelResult.success) {
1030
- throw new Error("Channel selection failed: " + (channelResult.reason ?? "Unknown reason."));
1093
+ /* The guide surface never rendered, so there was nothing for the strategy to read. That is the transient class - lazy tiles that never populated, an SPA
1094
+ * shell that did not come up, a stale overlay left across the grid - and a reload clears all of it at once, more thoroughly than dismissing anything
1095
+ * would. This is the one whole-selection retry in the system: providers own the cheap retries inside a single attempt, the coordinator owns this one.
1096
+ *
1097
+ * The budget it spends: the retry only fires after a selection has already failed, which for most providers costs up to videoTimeout and for Hulu - whose
1098
+ * grid strategy chains a list wait and a two-phase rows wait - closer to half a minute. The reload adds up to navigationTimeout, and a second selection
1099
+ * follows it. Hulu's worst case therefore runs past the 45-second establishment ceiling and is truncated by it, failing exactly as the un-retried attempt
1100
+ * was already going to fail. The ceiling is deliberately left where it is: the retry can never make a tune worse than the certain failure it replaces.
1101
+ */
1102
+ if (channelResult.guideUnavailable === true) {
1103
+ LOG.warn("Channel selection failed because the guide did not render: %s. Reloading the page and retrying once.", channelResult.reason ?? "Unknown reason");
1104
+ await reloadPage(page, profile);
1105
+ // The reloaded page auto-plays its default channel again, and capture is live for the whole tune, so that audio would bleed into the recording until the
1106
+ // second selection lands. The re-establishment path mutes after its own reload for the same reason.
1107
+ await muteExistingVideos(page);
1108
+ channelResult = await deps.selectChannel(page, profile, { persistResolution });
1109
+ }
1110
+ if (!channelResult.success) {
1111
+ const selectionError = new Error("Channel selection failed: " + (channelResult.reason ?? "Unknown reason."));
1112
+ // With the requested URL in hand, diagnose the still-open page before failing - the selection may have failed because a sign-in wall or consent prompt is
1113
+ // where the guide should be. An unknown classification rethrows this same error, so the undiagnosed outcome is identical to the plain throw below.
1114
+ if (requestedUrl !== undefined) {
1115
+ // Stop the tuneSetup poll before probing the page, so it cannot keep clicking mid-classification; the guard's finally remains the safety net.
1116
+ abortGuard();
1117
+ await diagnoseBlockedTune(page, requestedUrl, selectionError, deps);
1118
+ }
1119
+ throw selectionError;
1120
+ }
1031
1121
  }
1122
+ directTune = channelResult.directTune ?? false;
1123
+ LOG.debug("timing:tune", "Channel selection complete. (+%sms)", elapsed());
1032
1124
  }
1033
- directTune = channelResult.directTune ?? false;
1034
- LOG.debug("timing:tune", "Channel selection complete. (+%sms)", elapsed());
1035
- }
1036
- // Find the video context, which may be an iframe for embedded players. Some streaming sites embed their video player in an iframe, requiring us to search
1037
- // through frames to find the one containing the video element.
1038
- const context = await findVideoContext(page, profile);
1039
- LOG.debug("timing:tune", "Video context found. (+%sms)", elapsed());
1040
- // For clickToPlay sites, we need to click an element to start playback. These players require user interaction to begin playing, even with autoplay enabled. If
1041
- // clickSelector is set, we click that element (typically a play button overlay); otherwise we click the video element directly.
1042
- if (profile.clickToPlay) {
1043
- const clickTarget = profile.clickSelector ?? "video";
1125
+ // Find the video context, which may be an iframe for embedded players. Some streaming sites embed their video player in an iframe, requiring us to search
1126
+ // through frames to find the one containing the video element. For iframe-handling profiles this can reject with a raw selector timeout when the player iframe
1127
+ // never appears, so with the requested URL in hand the failure routes through the blocked-page diagnosis; an unknown classification rethrows the original
1128
+ // rejection unchanged.
1129
+ let context;
1044
1130
  try {
1045
- // Wait for the click target to appear in the DOM. Play button overlays may be rendered after initial page load.
1046
- await context.waitForSelector(clickTarget, { timeout: CONFIG.streaming.videoTimeout });
1047
- await context.click(clickTarget);
1048
- LOG.debug("timing:tune", "Click-to-play complete. (+%sms)", elapsed());
1131
+ context = await findVideoContext(page, profile);
1132
+ }
1133
+ catch (contextError) {
1134
+ if (requestedUrl !== undefined) {
1135
+ // Stop the tuneSetup poll before probing the page, so it cannot keep clicking mid-classification; the guard's finally remains the safety net.
1136
+ abortGuard();
1137
+ await diagnoseBlockedTune(page, requestedUrl, contextError, deps);
1138
+ }
1139
+ throw contextError;
1049
1140
  }
1050
- catch (clickError) {
1051
- LOG.warn("Could not click %s to initiate playback: %s.", clickTarget, formatError(clickError));
1141
+ LOG.debug("timing:tune", "Video context found. (+%sms)", elapsed());
1142
+ // For clickToPlay sites, we need to click an element to start playback. These players require user interaction to begin playing, even with autoplay enabled. If
1143
+ // clickSelector is set, we click that element (typically a play button overlay); otherwise we click the video element directly.
1144
+ if (profile.clickToPlay) {
1145
+ const clickTarget = profile.clickSelector ?? "video";
1146
+ try {
1147
+ // Wait for the click target to appear in the DOM. Play button overlays may be rendered after initial page load.
1148
+ await context.waitForSelector(clickTarget, { timeout: CONFIG.streaming.videoTimeout });
1149
+ await context.click(clickTarget);
1150
+ LOG.debug("timing:tune", "Click-to-play complete. (+%sms)", elapsed());
1151
+ }
1152
+ catch (clickError) {
1153
+ LOG.warn("Could not click %s to initiate playback: %s.", clickTarget, formatError(clickError));
1154
+ }
1155
+ }
1156
+ return { context, directTune };
1157
+ });
1158
+ let context = setup.context;
1159
+ const directTune = setup.directTune;
1160
+ // Wait for the video to become playable (readyState >= 3), while a fire-and-forget overlay-handling poll runs concurrently to reject cookie-consent banners,
1161
+ // accept embedded-player consent gates, and dismiss the profile's per-site modal. The poll never blocks the wait. If it accepts an embed gate, the in-flight wait
1162
+ // is abandoned and the page is reloaded once so the now-permitted player iframe is created and the video resolves on the second pass. If the wait fails with a
1163
+ // consent prompt still blocking the page, an actionable detect-and-guide error replaces the cryptic selector timeout.
1164
+ // Signals an embed-gate acceptance, so the video wait below can be raced against it. The gate resolves to "gate"; the video wait resolves to "video".
1165
+ const embedGate = Promise.withResolvers();
1166
+ // Stops the overlay poll once the wait settles, so it does not keep interacting with a page that is already done.
1167
+ const overlayController = new AbortController();
1168
+ // Abandons the in-flight video wait when an embed gate is accepted, so the superseded wait rejects silently instead of logging a misleading readiness timeout.
1169
+ const waitController = new AbortController();
1170
+ void deps.startOverlayHandling(page, profile, {
1171
+ onEmbedGateAccepted: () => {
1172
+ embedGate.resolve("gate");
1173
+ },
1174
+ phase: "videoWait",
1175
+ signal: overlayController.signal
1176
+ });
1177
+ try {
1178
+ // Race the video wait against an embed-gate acceptance. A present gate is accepted within a few seconds, so it reliably wins against the much longer video
1179
+ // timeout; a plain successful tune resolves "video" with the overlay poll a cheap no-op. A genuine readiness timeout rejects the race and is handled below.
1180
+ const outcome = await Promise.race([waitForVideoReady(context, profile, waitController.signal).then(() => "video"), embedGate.promise]);
1181
+ if (outcome === "gate") {
1182
+ // The site withheld the player iframe behind a consent gate that we accepted; consent now persists, so a single reload re-renders the page with the player
1183
+ // present and the video resolves on the second pass. Abandon the now-superseded first wait so it does not log a spurious timeout as the page reloads.
1184
+ LOG.debug("browser:consent", "Embedded-player consent gate accepted; reloading to resolve the player.");
1185
+ waitController.abort();
1186
+ await reloadPage(page, profile);
1187
+ // The consent granted on the first pass persists, so the reload re-renders the page with the player iframe present and the video resolves. Context resolution
1188
+ // and this second wait run under the postGateReload poll, so a cookie banner or per-site modal that re-renders after the reload is still cleared. That poll's
1189
+ // policy forbids the embed-gate accept: a second acceptance would re-detect unrelated consent overlays elsewhere on the page (carousel video tiles on a site
1190
+ // like france24), scroll one into view, and click it - yanking the main player offscreen and suppressing its autoplay. The phase makes that misfire
1191
+ // structurally impossible while keeping cookie rejection and modal dismissal live. A consent overlay that genuinely persists past the reload falls through to
1192
+ // the detect-and-guide path below.
1193
+ context = await withOverlayGuard(page, profile, "postGateReload", deps, async () => {
1194
+ const reloadedContext = await findVideoContext(page, profile);
1195
+ await waitForVideoReady(reloadedContext, profile);
1196
+ return reloadedContext;
1197
+ });
1052
1198
  }
1053
1199
  }
1054
- // Wait for video to be ready (readyState >= 3). This ensures enough data is buffered for playback to begin smoothly. If a dismissSelector is configured, launch
1055
- // a background poll that checks for the modal during the first 5 seconds of the video wait. The poll is fire-and-forget - it never blocks the video wait. If the
1056
- // modal appears and is clicked, the video becomes unblocked and waitForVideoReady resolves normally.
1057
- if (profile.dismissSelector) {
1058
- void dismissModalPoll(page, profile.dismissSelector);
1200
+ catch (videoError) {
1201
+ // Playback never became ready. With the requested URL in hand, diagnose the still-open page - a sign-in wall or a still-blocking consent prompt found there
1202
+ // replaces the cryptic selector timeout with an actionable error, and an unrecognized page rethrows this same rejection unchanged.
1203
+ if (requestedUrl !== undefined) {
1204
+ // Stop the overlay poll before probing the page, so it cannot keep clicking mid-classification. The finally below remains the safety net for every exit.
1205
+ overlayController.abort();
1206
+ await diagnoseBlockedTune(page, requestedUrl, videoError, deps);
1207
+ }
1208
+ throw videoError;
1209
+ }
1210
+ finally {
1211
+ overlayController.abort();
1059
1212
  }
1060
- await waitForVideoReady(context, profile);
1061
1213
  LOG.debug("timing:tune", "Video ready. (+%sms)", elapsed());
1062
1214
  // Ensure playback is started, unmuted, and fullscreen. This applies CSS styling, triggers native fullscreen, and enforces volume settings.
1063
1215
  await ensurePlayback(page, context, profile);
@@ -1065,11 +1217,14 @@ export async function initializePlayback(page, profile, options = {}) {
1065
1217
  return { context, directTune };
1066
1218
  }
1067
1219
  /**
1068
- * Tunes to a channel by navigating to the URL and initializing video playback. This is the single source of truth for channel initialization, used by both initial
1069
- * stream setup and recovery. Having one authoritative function ensures consistent behavior and prevents code divergence between setup and recovery paths.
1220
+ * Tunes to a channel by navigating to the URL and initializing video playback. This is the recovery path's tune: the playback monitor calls it to bring a stream
1221
+ * back by navigating the page again. Initial stream setup and native re-establishment reach the same phases through establishChannelPlayback in streaming/setup.ts,
1222
+ * which composes navigation and playback initialization with the per-path policy each of those callers carries. Both routes land in initializePlayback, so the
1223
+ * channel selection, video detection, and playback steps a recovered stream runs are the steps a fresh one ran.
1070
1224
  *
1071
1225
  * The tuning process:
1072
- * 0. Check cache: If a direct watch URL is cached, navigate to it and skip channel selection. On failure, invalidate and fall through.
1226
+ * 0. Check cache: If a direct watch URL is cached, navigate to it and skip channel selection. On failure, fall through, and let the cache coordinator classify
1227
+ * the cause to decide whether the entry survives.
1073
1228
  * 1. Navigate: Load the target URL using site-appropriate wait conditions
1074
1229
  * 2. Select channel: For multi-channel players, click the desired channel in the UI
1075
1230
  * 3. Find video: Locate the video element (which may be in an iframe)
@@ -1083,12 +1238,13 @@ export async function initializePlayback(page, profile, options = {}) {
1083
1238
  * @param page - The Puppeteer page object.
1084
1239
  * @param url - The URL to navigate to.
1085
1240
  * @param profile - The site profile containing all behavior flags.
1086
- * @returns The video context (frame or page) for subsequent monitoring.
1241
+ * @returns The video context (frame or page) for subsequent monitoring, and a directTune flag when the channel was tuned via API interception.
1087
1242
  */
1088
1243
  export async function tuneToChannel(page, url, profile) {
1089
1244
  const tuneElapsed = startTimer();
1090
- // Check for a direct watch URL. If available, navigate directly to it and skip channel selection, avoiding guide page navigation entirely. On failure,
1091
- // invalidate the cache entry and fall through to the normal guide-based flow.
1245
+ // Check for a direct watch URL. If available, navigate directly to it and skip channel selection, avoiding guide page navigation entirely. On failure, hand the
1246
+ // cause to the cache coordinator - which keeps the entry when the failure describes a dead page rather than a bad URL - and fall through to the normal
1247
+ // guide-based flow either way.
1092
1248
  const cachedUrl = await resolveDirectUrl(profile, page);
1093
1249
  if (cachedUrl) {
1094
1250
  try {
@@ -1100,15 +1256,17 @@ export async function tuneToChannel(page, url, profile) {
1100
1256
  return result;
1101
1257
  }
1102
1258
  catch (error) {
1103
- invalidateDirectUrl(profile);
1259
+ invalidateDirectUrl(profile, error);
1104
1260
  LOG.warn("Cached direct URL failed for %s: %s. Falling back to guide navigation.", profile.channelSelector ?? "unknown", formatError(error));
1105
1261
  }
1106
1262
  }
1107
1263
  // Normal flow: navigate to the guide page URL and perform full channel selection.
1108
1264
  await navigateToPage(page, url, profile);
1109
1265
  LOG.debug("timing:tune", "Navigation complete. (+%sms)", tuneElapsed());
1110
- // Perform all post-navigation initialization: channel selection, video context resolution, click to play, video readiness, and fullscreen.
1111
- const result = await initializePlayback(page, profile);
1266
+ // Perform all post-navigation initialization: channel selection, video context resolution, click to play, video readiness, and fullscreen. The guide URL rides
1267
+ // along so a failed tune can diagnose the blocked page; the cached-direct attempt above deliberately does not pass it, since its failure is swallowed and retried
1268
+ // through this authoritative path.
1269
+ const result = await initializePlayback(page, profile, { requestedUrl: url });
1112
1270
  LOG.debug("timing:tune", "Tune complete. Total: %sms.", tuneElapsed());
1113
1271
  return result;
1114
1272
  }