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,8 @@
2
2
  *
3
3
  * index.ts: Coordinator for native HLS streaming - manifest interception, DRM probe, and proxy lifecycle.
4
4
  */
5
- import { LOG, cancellableTimeout, formatError, startTimer } from "../utils/index.js";
5
+ import { LOG, boundedWait, formatError, startTimer } from "../utils/index.js";
6
6
  import { clearProbeCache, probeManifest } from "./probe.js";
7
- import { installManifestInterceptor, removeManifestInterceptor } from "../browser/manifestInterceptor.js";
8
7
  import { createNativeProxy } from "./proxy.js";
9
8
  import { fetchDecryptionKey } from "./decrypt.js";
10
9
  import { parseTokenExpiry } from "./tokenExpiry.js";
@@ -15,17 +14,35 @@ import { parseTokenExpiry } from "./tokenExpiry.js";
15
14
  * 2. If no manifest was intercepted: return null (fall back to capture)
16
15
  * 3. Probe the manifest for encryption type
17
16
  * 4. If DRM: log the reason, return null
18
- * 5. If clear or AES-128: create and return the native proxy
17
+ * 5. If the client is an MPEG-TS consumer and the stream has a separate audio rendition: return null (incompatible PAT/PMT, fall back to capture)
18
+ * 6. If AES-128: pre-fetch the decryption key, returning null if it is inaccessible
19
+ * 7. If clear or AES-128 (with key in hand): create and return the native proxy
19
20
  *
20
- * Token refresh: When the intercepted URL contains expiration tokens, we schedule a timer to refresh the manifest before the tokens expire. The refresh first
21
- * attempts a direct Node.js fetch of the master manifest URL (no browser involvement), falling back to a browser page reload when the master URL itself has expired.
22
- * The proxy continues serving cached segments during the refresh.
21
+ * Token refresh: When the intercepted URL contains expiration tokens, we schedule a SINGLE timer aimed at the next expiry boundary - the earlier of the master URL's
22
+ * and the polled variant URL's expirations, minus a comfortable margin. The refresh first attempts a direct Node.js fetch of the master manifest URL (no browser
23
+ * involvement), falling back to a browser page reload when the master URL itself has expired. Each refresh reschedules from the boundary it is aiming at, not from a
24
+ * shrinking-but-unchanging master expiry, so the schedule never degenerates into a per-cycle re-probe loop in the final minutes before expiry. The proxy
25
+ * continues serving cached segments during the refresh.
26
+ *
27
+ * A refresh re-runs the same ranked selection a tune runs, narrowed to the candidates the running proxy can absorb, so a stream heals in both directions as the
28
+ * service's ladder changes underneath it. A refreshed manifest offering nothing that pipeline can serve is declined outright rather than half-applied: the
29
+ * refresh reports failure, and the monitor's own escalation owns the one designed transition from relaying back to capture.
30
+ *
31
+ * Two triggers reach the refresh - the boundary timer above and the monitor's failure-triggered recovery - and they share one attempt: whichever arrives second
32
+ * awaits the one in flight and reads its real outcome, because running two against a single browser page races their navigations and the monitor steers on the
33
+ * boolean it reads back. An attempt that fails on a live proxy warns and re-arms itself, doubling the delay per consecutive failure up to the refresh margin and
34
+ * riding the proxy's one timer slot, so a stream that stumbles keeps its proactive chain instead of coasting until its tokens die.
23
35
  */
24
36
  // Time in milliseconds before token expiry to trigger a refresh. We refresh 5 minutes early to ensure the new manifest is ready before the old one expires.
25
37
  const TOKEN_REFRESH_MARGIN = 300000;
26
- // Minimum delay in milliseconds before a token refresh fires. Prevents a refresh loop when the token lifetime is shorter than TOKEN_REFRESH_MARGIN (e.g., Fox Sports
27
- // tokens expire in ~118 seconds). Without this floor, the refresh delay would be Math.max(0, 118000 - 300000) = 0ms, triggering an infinite loop of page reloads.
38
+ // Minimum delay in milliseconds before a token refresh fires. This is the absolute floor for any scheduled refresh: when a boundary lands at or in the past (an
39
+ // already-expired or imminently-expiring token), we still wait at least this long so the timer cannot fire back-to-back and thrash. It is NOT the steady-state
40
+ // cadence - inside the margin window we schedule a single refresh aimed at the actual expiry boundary, not a repeating MIN_REFRESH_DELAY poll (see scheduleTokenRefresh).
28
41
  const MIN_REFRESH_DELAY = 30000;
42
+ // The longest delay Node's setTimeout accepts: a 32-bit signed millisecond count, roughly 24.8 days. A boundary further out than that is clamped to this ceiling,
43
+ // where the refresh fires early, re-probes, and reschedules against the boundary it re-derives - one harmless extra refresh per ceiling interval. An unclamped
44
+ // delay would instead overflow the timer, which Node fires immediately, turning a distant expiry into a refresh loop running at the speed of the network.
45
+ const MAX_TIMER_DELAY_MS = 2147483647;
29
46
  // Minimum remaining token lifetime (in milliseconds) for a direct-fetched variant URL to be considered usable. If the variant URL's token expires sooner than this,
30
47
  // the direct fetch result is discarded and we fall back to a page reload to get a genuinely fresh token. This prevents handing the proxy a variant URL that expires
31
48
  // almost immediately. Set low (5s) because the proxy only needs the variant URL to survive one poll cycle (~3s). A higher threshold (e.g., 30s) would cause Fox.com
@@ -35,54 +52,56 @@ const MIN_USABLE_TOKEN_LIFETIME = 5000;
35
52
  const INTERCEPTION_AWAIT_TIMEOUT = 5000;
36
53
  /**
37
54
  * Attempts to upgrade a stream from screen capture to native HLS streaming. Returns the native proxy on success, or null if the stream is not viable for native
38
- * consumption (DRM, interception timeout, or probe failure). The proxy owns the CDP session from interception and manages its lifecycle.
55
+ * consumption (DRM, interception timeout, or probe failure).
39
56
  *
40
57
  * @param options - Options for the native streaming attempt.
41
58
  * @returns The native stream result on success, or null to fall back to capture.
42
59
  */
43
60
  export async function attemptNativeStreaming(options) {
44
- const { channelName, interceptionPromise, mpegTsClient, onError, page, streamId, streamIdStr, url } = options;
61
+ const { channelName, interceptionPromise, mpegTsClient, onError, onFeedApplied, page, probeIdentity, reestablishManifest, streamId, streamIdStr, url } = options;
45
62
  const elapsed = startTimer();
46
63
  LOG.debug("native:coordinator", "Attempting native streaming for %s.", channelName);
47
- // Await the manifest interception with a short timeout. The CDP listener was installed before navigation, so by the time we get here the manifest should
48
- // already be captured or close to it. cancellableTimeout owns the underlying setTimeout so we clear it in finally when interceptionPromise wins the race;
49
- // otherwise the ref'd timer would hold the event loop for up to INTERCEPTION_AWAIT_TIMEOUT after a successful tune.
64
+ // Await the manifest interception with a short bound. The CDP listener was installed before navigation, so by the time we get here the manifest should
65
+ // already be captured or close to it. A lapse is an ordinary outcome here - it simply means capture serves this tune - so the wait is value-shaped and its
66
+ // null joins the interception's own null on the fallback branch below.
50
67
  let interception;
51
- const timeout = cancellableTimeout(INTERCEPTION_AWAIT_TIMEOUT);
52
68
  try {
53
- const result = await Promise.race([interceptionPromise, timeout.promise]);
54
- interception = (result === false) ? null : result;
69
+ interception = await boundedWait(interceptionPromise, INTERCEPTION_AWAIT_TIMEOUT);
55
70
  }
56
71
  catch (error) {
57
72
  LOG.debug("native:coordinator", "Manifest interception error for %s: %s.", channelName, formatError(error));
58
73
  return null;
59
74
  }
60
- finally {
61
- timeout.cancel();
62
- }
63
75
  if (!interception) {
64
76
  LOG.debug("native:coordinator", "No manifest intercepted for %s in %sms. Falling back to capture.", channelName, elapsed());
65
77
  return null;
66
78
  }
67
79
  LOG.debug("native:coordinator", "Manifest intercepted for %s in %sms.", channelName, elapsed());
68
- // Probe the intercepted URL and normalize the result to a MediaFeed. The probe handles both master and media playlists transparently; this code path does not
69
- // need to know which kind arrived.
70
- const mediaFeed = await probeManifest(interception.masterManifestUrl, channelName);
80
+ /* Probe the intercepted URL and normalize the result to a MediaFeed. The probe handles both master and media playlists transparently; this code path does not
81
+ * need to know which kind arrived. Tune admission refuses a window of at most one segment: the interceptor latches the first master on the wire, and for a
82
+ * service that fronts its player with a per-session bumper that master describes the bumper rather than the channel. Declining lands on the null path below,
83
+ * where capture serves the channel the relay could not.
84
+ */
85
+ const mediaFeed = await probeManifest(interception.manifestUrl, probeIdentity, { rejectStaticPlaylists: true });
71
86
  if (!mediaFeed) {
72
87
  LOG.debug("native:coordinator", "Probe failed for %s. Falling back to capture.", channelName);
73
- removeManifestInterceptor(interception.cdpSession);
74
88
  return null;
75
89
  }
76
90
  if (mediaFeed.encryption === "drm") {
77
91
  LOG.debug("native:coordinator", "Native streaming not viable for %s: DRM-protected stream.", channelName);
78
- removeManifestInterceptor(interception.cdpSession);
79
92
  return null;
80
93
  }
81
94
  // Separate audio renditions (e.g., Google DAI on BET/VH1) cannot be served to MPEG-TS clients because the independent video and audio MPEG-TS segments have
82
95
  // incompatible PAT/PMT tables and variable packet sizes from ad splicing. HLS clients (Channels DVR) handle separate audio renditions natively via master playlist.
83
96
  if (mpegTsClient && mediaFeed.audioVariantUrl) {
84
97
  LOG.debug("native:coordinator", "Native streaming not viable for %s: separate audio rendition incompatible with MPEG-TS clients.", channelName);
85
- removeManifestInterceptor(interception.cdpSession);
98
+ return null;
99
+ }
100
+ /* The proxy is built around the feed's container, so a feed that carries none cannot become a pipeline. Only a DRM classification leaves it null, and that
101
+ * path returned above, so this guard answers a state the flow does not reach rather than inventing a container to satisfy the option's type.
102
+ */
103
+ if (mediaFeed.container === null) {
104
+ LOG.debug("native:coordinator", "Native streaming not viable for %s: the probe returned no container classification.", channelName);
86
105
  return null;
87
106
  }
88
107
  LOG.debug("native:coordinator", "Native streaming viable for %s (%s, variant: %s).", channelName, mediaFeed.encryption, mediaFeed.bestVariantUrl.slice(0, 80));
@@ -94,21 +113,20 @@ export async function attemptNativeStreaming(options) {
94
113
  prefetchedKey = await fetchDecryptionKey(mediaFeed.keyUrl);
95
114
  if (!prefetchedKey) {
96
115
  LOG.debug("native:coordinator", "Native streaming not viable for %s: decryption key inaccessible.", channelName);
97
- removeManifestInterceptor(interception.cdpSession);
98
116
  return null;
99
117
  }
100
118
  LOG.debug("native:coordinator", "AES-128 decryption key pre-fetched for %s.", channelName);
101
119
  }
102
- // Create the native proxy. The CDP session is passed so the proxy can clean it up on stop(), preventing session leaks when the stream terminates before a token
103
- // refresh occurs. For AES-128 streams, the pre-fetched key is passed so the proxy does not need to fetch it again on the first segment. The preroll segment count
120
+ // Create the native proxy. The manifest interceptor's underlying CDP session is owned and disposed internally by the interceptor - the proxy holds no session
121
+ // references. For AES-128 streams, the pre-fetched key is passed so the proxy does not need to fetch it again on the first segment. The preroll segment count
104
122
  // determines the segment index offset (reserving index space for preroll). Streams with separate audio cannot use preroll because the preroll content is muxed
105
123
  // video+audio and can't be split into separate renditions.
106
124
  const hasSeparateAudio = mediaFeed.audioVariantUrl !== null;
107
125
  const proxyPrerollSegmentCount = (!hasSeparateAudio && options.prerollSegmentCount) ? options.prerollSegmentCount : 0;
108
126
  const proxy = createNativeProxy({
109
127
  audioVariantUrl: mediaFeed.audioVariantUrl,
110
- cdpSession: interception.cdpSession,
111
128
  channelName,
129
+ container: mediaFeed.container,
112
130
  encryption: mediaFeed.encryption,
113
131
  keyUrl: mediaFeed.keyUrl,
114
132
  onError,
@@ -119,59 +137,175 @@ export async function attemptNativeStreaming(options) {
119
137
  streamIdStr,
120
138
  variantUrl: mediaFeed.bestVariantUrl
121
139
  });
122
- // Schedule token refresh if the URL contains expiration tokens.
140
+ // Schedule token refresh if either URL contains expiration tokens. We pass the variant URL the proxy will poll so the refresh boundary is the earlier of the
141
+ // master and variant expirations.
123
142
  scheduleTokenRefresh({
124
143
  channelName,
125
- masterUrl: interception.masterManifestUrl,
144
+ masterUrl: interception.manifestUrl,
145
+ onFeedApplied,
126
146
  page,
147
+ probeIdentity,
127
148
  proxy,
149
+ reestablishManifest,
128
150
  streamIdStr,
129
- url
151
+ url,
152
+ variantUrl: mediaFeed.bestVariantUrl
130
153
  });
131
154
  LOG.debug("timing:native", "Native streaming setup completed for %s in %sms.", channelName, elapsed());
132
- return { bandwidth: mediaFeed.bandwidth, codec: mediaFeed.codec, hasAudio: mediaFeed.audioVariantUrl !== null, proxy, resolution: mediaFeed.resolution };
155
+ return { bandwidth: mediaFeed.bandwidth, codec: mediaFeed.codec, container: mediaFeed.container, hasAudio: mediaFeed.audioVariantUrl !== null, proxy,
156
+ resolution: mediaFeed.resolution };
157
+ }
158
+ /**
159
+ * Computes the absolute moment (milliseconds since the Unix epoch) at which the next token refresh must occur, or null when neither URL carries an expiry token.
160
+ *
161
+ * The boundary is the earlier of the master URL's and the variant URL's expirations. The master URL governs which refresh strategy can run (a direct re-fetch is
162
+ * possible only while the master token is alive; once it expires only a page reload mints a new master), while the variant URL is what the proxy polls for segments.
163
+ * Taking the minimum guarantees the proxy never holds a dead variant URL and the refresh never tries a direct fetch against an expired master.
164
+ *
165
+ * @param masterUrl - The master manifest URL whose token gates the direct-fetch strategy.
166
+ * @param variantUrl - The variant URL the proxy polls for segments.
167
+ * @returns The earliest expiry in milliseconds since the epoch, or null when neither URL carries an expiry token.
168
+ */
169
+ function computeRefreshBoundary(masterUrl, variantUrl) {
170
+ const masterExpiry = parseTokenExpiry(masterUrl);
171
+ const variantExpiry = parseTokenExpiry(variantUrl);
172
+ if ((masterExpiry === null) && (variantExpiry === null)) {
173
+ return null;
174
+ }
175
+ // At least one expiry exists. Default the missing one to positive infinity so Math.min selects the URL that actually constrains the boundary.
176
+ return Math.min(masterExpiry ?? Number.POSITIVE_INFINITY, variantExpiry ?? Number.POSITIVE_INFINITY);
133
177
  }
134
178
  /**
135
- * Schedules a token refresh timer if the manifest URL contains an expiration token. The refresh occurs TOKEN_REFRESH_MARGIN milliseconds before the token expires.
136
- * On timer fire, the refresh first attempts a direct Node.js fetch of the master manifest URL, falling back to a browser page reload if the direct fetch fails.
179
+ * Schedules a SINGLE token refresh timer aimed at the next expiry boundary - the earlier of the master URL's and the polled variant URL's expirations. There are two
180
+ * regimes:
181
+ *
182
+ * 1. **Comfortable margin** (more than TOKEN_REFRESH_MARGIN remains): schedule the refresh TOKEN_REFRESH_MARGIN before the boundary, so the fresh manifest is ready
183
+ * well before the old one expires.
184
+ *
185
+ * 2. **Inside the margin window** (TOKEN_REFRESH_MARGIN or less remains): re-fetching the same master URL does not extend its lifetime, so re-probing it on a fixed
186
+ * cadence is wasted work. We schedule exactly one refresh aimed at the boundary itself (clamped to MIN_REFRESH_DELAY so an already-expired or imminent boundary
187
+ * cannot fire back-to-back). When that refresh fires the master token is spent, the direct fetch fails, and the page-reload path mints a genuinely new master URL
188
+ * - once, at the boundary, not every MIN_REFRESH_DELAY for the final minutes before expiry.
189
+ *
190
+ * This is the core of the busy-loop fix: each refresh reschedules from the boundary it aims at, never from a shrinking-but-unchanging master expiry, so the cadence
191
+ * is a single boundary-targeted timer rather than a per-cycle re-probe.
137
192
  *
138
193
  * @param options - Token refresh options.
139
194
  */
140
195
  function scheduleTokenRefresh(options) {
141
- const { channelName, masterUrl, page, proxy, streamIdStr, url } = options;
142
- const expiry = parseTokenExpiry(masterUrl);
143
- if (!expiry) {
144
- LOG.debug("native:token", "No token expiry found in manifest URL for %s.", channelName);
196
+ const { channelName, masterUrl, onFeedApplied, page, probeIdentity, proxy, reestablishManifest, streamIdStr, url, variantUrl } = options;
197
+ const boundary = computeRefreshBoundary(masterUrl, variantUrl);
198
+ if (boundary === null) {
199
+ LOG.debug("native:token", "No token expiry found in manifest URLs for %s.", channelName);
145
200
  return;
146
201
  }
147
- const timeUntilExpiry = expiry - Date.now();
148
- const refreshIn = Math.max(MIN_REFRESH_DELAY, timeUntilExpiry - TOKEN_REFRESH_MARGIN);
202
+ const timeUntilExpiry = boundary - Date.now();
203
+ // Outside the margin we lead the boundary by TOKEN_REFRESH_MARGIN; inside it we aim straight at the boundary so the direct fetch fails into a page reload exactly
204
+ // once. The floor of MIN_REFRESH_DELAY keeps a past-due or imminent boundary from thrashing, and the ceiling of MAX_TIMER_DELAY_MS keeps a distant one inside
205
+ // the range setTimeout can actually represent.
206
+ const lead = (timeUntilExpiry > TOKEN_REFRESH_MARGIN) ? TOKEN_REFRESH_MARGIN : 0;
207
+ const refreshIn = Math.min(Math.max(MIN_REFRESH_DELAY, timeUntilExpiry - lead), MAX_TIMER_DELAY_MS);
149
208
  LOG.debug("native:token", "Token expires in %ss for %s. Refresh scheduled in %ss.", Math.round(timeUntilExpiry / 1000), channelName, Math.round(refreshIn / 1000));
150
209
  // Store the timer handle on the proxy so it can be cancelled if the proxy is stopped before the timer fires. Pass the master URL so the refresh can attempt a
151
210
  // direct fetch before falling back to a page reload.
152
211
  const timer = setTimeout(() => {
153
- void refreshNativeManifest({ channelName, masterUrl, page, proxy, streamIdStr, url });
212
+ void refreshNativeManifest({ channelName, masterUrl, onFeedApplied, page, probeIdentity, proxy, reestablishManifest, streamIdStr, url });
154
213
  }, refreshIn);
155
214
  proxy.setTokenRefreshTimer(timer);
156
215
  }
157
216
  /**
158
- * Refreshes a native stream's manifest to obtain fresh auth tokens. Two strategies are tried in order:
217
+ * Refreshes a native stream's manifest to obtain fresh auth tokens, serialized across callers and re-armed when an attempt fails.
218
+ *
219
+ * Two triggers reach this function: the proactive timer aimed at the token's expiry boundary, and the monitor's failure-triggered recovery. They can arrive
220
+ * within the same cycle, and two refreshes running against one browser page race each other's navigation, so an attempt already in flight is the attempt every
221
+ * caller gets - the later caller awaits it and reads its real settled outcome, which matters because the monitor steers its escalation on the boolean it reads
222
+ * back here.
223
+ *
224
+ * A failed refresh on a live proxy warns and re-arms itself with a doubling delay bounded by the refresh margin, so one transient failure cannot leave a stream
225
+ * with no proactive refresh for the rest of its life. The retry rides the proxy's single timer slot, the same one the boundary schedule uses.
226
+ *
227
+ * @param options - Refresh options. masterUrl is optional - when provided, direct fetch is attempted first. probeIdentity is the stream's own, so the probe on
228
+ * either strategy reads and writes the cache under the identity the tune established.
229
+ * @returns True if the refresh succeeded (proxy updated with new manifest), false otherwise.
230
+ */
231
+ export async function refreshNativeManifest(options) {
232
+ const { channelName, proxy, streamIdStr } = options;
233
+ const streamLog = LOG.withStreamId(streamIdStr);
234
+ const pending = proxy.getPendingRefresh();
235
+ if (pending) {
236
+ return pending;
237
+ }
238
+ /* What goes into the slot is a promise that never rejects: the attempt's own throw is caught and normalized to false here, before the promise is shared, so a
239
+ * caller awaiting the slot can never be handed a rejection from machinery it did not start. The store below happens before this attempt reaches its first
240
+ * suspension point, which is what makes the slot visible to a caller arriving in the same tick.
241
+ */
242
+ const attempt = (async () => {
243
+ try {
244
+ return await runManifestRefresh(options, streamLog);
245
+ }
246
+ catch (error) {
247
+ streamLog.debug("native:token", "Manifest refresh failed for %s: %s.", channelName, formatError(error));
248
+ return false;
249
+ }
250
+ })();
251
+ proxy.setPendingRefresh(attempt);
252
+ let succeeded = false;
253
+ try {
254
+ succeeded = await attempt;
255
+ if (succeeded) {
256
+ // The stream is refreshing again, so the backoff retires: whatever fails next starts over at the floor delay.
257
+ proxy.clearRefreshFailures();
258
+ }
259
+ else if (!proxy.isStopped()) {
260
+ /* A failure on a live proxy is the field's early warning that a stream is heading for token death - every refusal inside the attempt reports at debug, so
261
+ * this is the one line an operator sees - and it re-arms the chain the failure would otherwise have ended. The delay doubles per consecutive failure up to
262
+ * the refresh margin, read from a count captured once so the warn and the timer cannot disagree. The re-arm rides the proxy's single timer slot: arming
263
+ * retires whatever is there, and stop() retires whichever is live, so there is never a second timer to leak. Nothing caps the retries because the stream's
264
+ * own lifecycle already does - a token that stays dead drives segment failures into the proxy's error threshold, and the fallback to capture that follows
265
+ * stops the proxy and its timer with it.
266
+ */
267
+ const failures = proxy.noteRefreshFailure();
268
+ const retryIn = Math.min(MIN_REFRESH_DELAY * (2 ** (failures - 1)), TOKEN_REFRESH_MARGIN);
269
+ streamLog.warn("The token refresh for %s did not complete. Retrying in %s seconds.", channelName, Math.round(retryIn / 1000));
270
+ proxy.setTokenRefreshTimer(setTimeout(() => {
271
+ void refreshNativeManifest(options);
272
+ }, retryIn));
273
+ }
274
+ }
275
+ catch (error) {
276
+ /* Only the bookkeeping above can throw here, since the attempt settles rather than rejects. Catching it keeps the outcome the attempt reached intact and,
277
+ * more to the point, keeps this promise from rejecting: both production callers void or await it without a catch of their own, so an escape would kill the
278
+ * very re-arm chain this function exists to keep alive.
279
+ */
280
+ streamLog.warn("The token refresh bookkeeping for %s did not complete: %s.", channelName, formatError(error));
281
+ }
282
+ finally {
283
+ proxy.setPendingRefresh(null);
284
+ }
285
+ return succeeded;
286
+ }
287
+ /**
288
+ * Runs one manifest refresh attempt. Two strategies are tried in order:
159
289
  *
160
290
  * 1. **Direct fetch** (if masterUrl is provided): Re-fetches the master manifest URL from Node.js without involving the browser. This avoids the visible page reload
161
291
  * that disrupts the browser tab. The CDN returns the manifest with current token values as long as the URL's own auth token hasn't expired. When the master URL
162
292
  * expires, the direct fetch returns 403 and we fall through to strategy 2.
163
293
  *
164
- * 2. **Page reload**: Navigates the browser page back to the channel URL, triggering fresh authentication via cookies. A CDP interceptor captures the new master
165
- * manifest URL with fresh tokens. This is the only path that generates genuinely new tokens and is required when the master URL itself has expired.
294
+ * 2. **Channel re-establishment**: Runs the stream's own tune again on the page through the capability the streaming layer supplied, so fresh authentication and a
295
+ * fresh master manifest arrive by exactly the semantics that established the stream - which is what keeps a guide-tuned stream on its own channel across the
296
+ * reload. This is the only path that generates genuinely new tokens and is required when the master URL itself has expired.
166
297
  *
167
298
  * L2 recovery (failure-triggered) from the monitor omits masterUrl, going straight to page reload since the stream is already failing and needs a full refresh.
168
299
  *
169
- * @param options - Refresh options. masterUrl is optional - when provided, direct fetch is attempted first.
300
+ * Every point at which this attempt resumes after an await re-checks whether the proxy stopped meanwhile, because a stream can terminate at any of them and a
301
+ * stopped proxy must not be written to.
302
+ *
303
+ * @param options - Refresh options, as handed to refreshNativeManifest.
304
+ * @param streamLog - The stream-scoped logger the caller already holds.
170
305
  * @returns True if the refresh succeeded (proxy updated with new manifest), false otherwise.
171
306
  */
172
- export async function refreshNativeManifest(options) {
173
- const { channelName, masterUrl, page, proxy, streamIdStr, url } = options;
174
- const streamLog = LOG.withStreamId(streamIdStr);
307
+ async function runManifestRefresh(options, streamLog) {
308
+ const { channelName, masterUrl, page, probeIdentity, proxy, reestablishManifest } = options;
175
309
  if (proxy.isStopped()) {
176
310
  return false;
177
311
  }
@@ -180,118 +314,145 @@ export async function refreshNativeManifest(options) {
180
314
  // Strategy 1: Direct fetch. Re-fetch the master manifest URL from Node.js to get fresh variant URLs without reloading the browser page. This works as long as the
181
315
  // master URL's own CDN auth token hasn't expired. When it does expire, probeManifest returns null (403) and we fall through to the page reload strategy.
182
316
  if (masterUrl) {
183
- const directResult = await tryDirectManifestRefresh(masterUrl, channelName, streamLog);
317
+ const directResult = await tryDirectManifestRefresh(masterUrl, proxy.getPipelineShape(), probeIdentity, streamLog);
184
318
  if (directResult) {
185
319
  // Check if the proxy was stopped during the async probe (e.g., stream terminated while probing).
186
320
  if (proxy.isStopped()) {
187
321
  return false;
188
322
  }
189
- // Direct fetch succeeded. Update the proxy with the fresh variant URL(s).
190
- proxy.updateVariantUrl(directResult.bestVariantUrl);
191
- if (directResult.audioVariantUrl) {
192
- proxy.updateAudioVariantUrl(directResult.audioVariantUrl);
323
+ /* Apply the fresh feed, carrying the master URL forward: a direct fetch does not mint a new master, and the one in hand may still serve further direct
324
+ * fetches. The boundary the next schedule aims at therefore comes from the earlier of that unchanging master expiry and the freshly-fetched variant's,
325
+ * which is what keeps a still-valid master from being re-probed on a cadence of its own.
326
+ */
327
+ const applied = applyRefreshedFeed(directResult, masterUrl, options, streamLog);
328
+ if (applied) {
329
+ streamLog.debug("native:token", "Manifest refresh completed for %s via direct fetch in %sms.", channelName, refreshElapsed());
193
330
  }
194
- streamLog.debug("native:token", "Manifest refresh completed for %s via direct fetch in %sms.", channelName, refreshElapsed());
195
- // Schedule the next refresh with the same master URL. It may still be valid for subsequent direct fetches. Once it expires, the direct fetch will fail and
196
- // the page reload fallback will generate a new master URL.
197
- scheduleTokenRefresh({
198
- channelName,
199
- masterUrl,
200
- page,
201
- proxy,
202
- streamIdStr,
203
- url
204
- });
205
- return true;
331
+ return applied;
206
332
  }
207
- // Direct fetch failed. Fall through to page reload.
333
+ /* The direct fetch produced nothing usable - the master's token has expired, the fetch failed, or the manifest offered nothing this pipeline can absorb -
334
+ * so the page reload gets the second opinion. A fresh session can mint a ladder the running pipeline fits again, and a decline that persists through the
335
+ * reload leaves through the failure path below.
336
+ */
208
337
  streamLog.debug("native:token", "Direct manifest fetch failed for %s. Falling back to page reload.", channelName);
209
338
  }
210
- // Strategy 2: Page reload. Navigate the browser page to trigger fresh authentication and intercept a new master manifest via CDP.
339
+ // Strategy 2: Channel re-establishment. Reload the page through the stream's own tune to trigger fresh authentication and intercept a new master manifest.
211
340
  if (page.isClosed()) {
212
341
  streamLog.debug("native:token", "Manifest refresh failed for %s: page is closed.", channelName);
213
342
  return false;
214
343
  }
215
344
  try {
216
- // Install a fresh interceptor on the page. For token refresh, the page navigates directly to the channel URL (no guide grid), so the first manifest captured is
217
- // the correct one. We call finalize() after navigation to resolve immediately with whatever was captured.
218
- const handle = await installManifestInterceptor(page, 20000);
219
- if (!handle) {
220
- streamLog.debug("native:token", "Manifest refresh failed for %s: could not install interceptor.", channelName);
221
- return false;
222
- }
223
- // Navigate the page back to the channel URL to trigger fresh authentication. The interceptor captures .m3u8 requests generated by this navigation. Note: this
224
- // uses a bare page.goto without the service's site profile (no waitForNetworkIdle, scroll options, etc.). This is acceptable because native streaming only
225
- // activates for services whose video players load the HLS manifest during basic page load. Services requiring profile-aware navigation for manifest delivery
226
- // would have failed the initial interception and would not be in native mode.
227
- await page.goto(url, { timeout: 30000, waitUntil: "domcontentloaded" });
228
- // Check if the proxy was stopped while we were navigating (e.g., stream terminated during the page load).
229
- if (proxy.isStopped()) {
230
- return false;
231
- }
232
- // Signal finalize and await the interception result. Token refresh always navigates directly (no guide grid), so the first manifest is correct.
233
- handle.finalize(true);
234
- const newInterception = await handle.promise;
345
+ // Re-establish the channel through the streaming layer's capability: the same tune machinery that established this stream navigates, selects, and
346
+ // adjudicates, so the interception below carries the tuned channel's manifest rather than whatever the reloaded page produced first. A null means the
347
+ // re-establishment failed or was rejected by the provider verifier; the refresh fails with it and the monitor's ladder owns the escalation.
348
+ const newInterception = await reestablishManifest(page);
235
349
  if (!newInterception) {
236
- streamLog.debug("native:token", "Manifest refresh failed for %s: no manifest intercepted after reload.", channelName);
350
+ streamLog.debug("native:token", "Manifest refresh failed for %s: the channel could not be re-established.", channelName);
237
351
  return false;
238
352
  }
239
- // Check if the proxy was stopped while we were waiting for the interception. Clean up the new session since the proxy will not take ownership of it.
353
+ // Check if the proxy was stopped while we were waiting for the interception. The interceptor has already disposed its observer on resolution; no further
354
+ // cleanup is required.
240
355
  if (proxy.isStopped()) {
241
- removeManifestInterceptor(newInterception.cdpSession);
242
356
  return false;
243
357
  }
244
- // Probe the new manifest to get the updated variant URL. We probe before handing the new CDP session to the proxy so that a probe failure does not leave the
245
- // proxy holding a reference to a session we are about to clean up.
246
- const refreshedFeed = await probeManifest(newInterception.masterManifestUrl, channelName);
358
+ // Probe the new manifest to get the updated variant URL. The interceptor has already released its observer by the time the promise resolves, so a probe
359
+ // failure here only requires giving up on this refresh attempt - no session bookkeeping to unwind.
360
+ const refreshedFeed = await probeManifest(newInterception.manifestUrl, probeIdentity, { pipelineShape: proxy.getPipelineShape() });
247
361
  if (!refreshedFeed) {
248
362
  streamLog.debug("native:token", "Manifest refresh failed for %s: probe failed on new manifest.", channelName);
249
- removeManifestInterceptor(newInterception.cdpSession);
250
363
  return false;
251
364
  }
252
- // Check if the proxy was stopped during the probe. Clean up the new session since the proxy will not take ownership of it.
365
+ // Check if the proxy was stopped during the probe.
253
366
  if (proxy.isStopped()) {
254
- removeManifestInterceptor(newInterception.cdpSession);
255
367
  return false;
256
368
  }
257
- // Probe succeeded. Hand the new CDP session to the proxy. updateCdpSession cleans up the old session internally before replacing it.
258
- proxy.updateCdpSession(newInterception.cdpSession);
259
- // Update the proxy with the new variant URL(s).
260
- proxy.updateVariantUrl(refreshedFeed.bestVariantUrl);
261
- if (refreshedFeed.audioVariantUrl) {
262
- proxy.updateAudioVariantUrl(refreshedFeed.audioVariantUrl);
369
+ // Apply the fresh feed, carrying the NEW master URL from this interception forward: subsequent direct fetches use it until it expires, and the next boundary
370
+ // is the earlier of its expiry and the freshly-probed variant's.
371
+ const applied = applyRefreshedFeed(refreshedFeed, newInterception.manifestUrl, options, streamLog);
372
+ if (applied) {
373
+ streamLog.debug("native:token", "Manifest refresh completed for %s via page reload in %sms.", channelName, refreshElapsed());
263
374
  }
264
- streamLog.debug("native:token", "Manifest refresh completed for %s via page reload in %sms.", channelName, refreshElapsed());
265
- // Schedule the next proactive token refresh with the NEW master URL from the fresh interception. Subsequent direct fetches will use this URL until it expires.
266
- scheduleTokenRefresh({
267
- channelName,
268
- masterUrl: newInterception.masterManifestUrl,
269
- page,
270
- proxy,
271
- streamIdStr,
272
- url
273
- });
274
- return true;
375
+ return applied;
275
376
  }
276
377
  catch (error) {
277
378
  streamLog.debug("native:token", "Manifest refresh failed for %s: %s.", channelName, formatError(error));
278
- // Clear the probe cache so a subsequent attempt re-probes.
279
- clearProbeCache(channelName);
379
+ // Clear the probe cache so a subsequent attempt re-probes. The clear addresses the stream's own identity, which is what every probe on this stream reads and
380
+ // writes under.
381
+ clearProbeCache(probeIdentity.key);
382
+ return false;
383
+ }
384
+ }
385
+ /**
386
+ * Applies an accepted refresh to the running proxy and schedules the next one. Both strategies converge here, so the sequence a successful refresh performs - the
387
+ * URL swaps, the quality report, the reschedule - has exactly one home, and a future check on whether the refreshed feed still describes the same channel has one
388
+ * place to live.
389
+ *
390
+ * @param feed - The accepted feed, already selected against the proxy's pipeline shape.
391
+ * @param masterUrl - The master URL the next schedule should carry, which is the caller's to name: a direct fetch reuses the master it re-fetched, a page reload
392
+ * carries the one its fresh interception produced.
393
+ * @param options - The refresh options this cycle was invoked with, threaded into the reschedule so the chain outlives this cycle.
394
+ * @param streamLog - The stream-scoped logger.
395
+ * @returns True when the feed was applied, false when it was declined.
396
+ */
397
+ function applyRefreshedFeed(feed, masterUrl, options, streamLog) {
398
+ const { channelName, onFeedApplied, page, probeIdentity, proxy, reestablishManifest, streamIdStr, url } = options;
399
+ /* A separate-audio pipeline polls two manifests, so it must be handed both URLs or neither: a video URL swapped without its audio would leave the two tracks
400
+ * on different CDN sessions. Selection admits only candidates whose audio topology matches this pipeline's, so an accepted feed arriving here without a
401
+ * rendition contradicts the selection that produced it - the refresh declines the whole application rather than performing half of it. Declining rather than
402
+ * throwing keeps a background cycle survivable: the false return is the same outcome every other refusal produces, and the caller already handles it.
403
+ */
404
+ const { separateAudio } = proxy.getPipelineShape();
405
+ const audioVariantUrl = separateAudio ? feed.audioVariantUrl : null;
406
+ if (separateAudio && (audioVariantUrl === null)) {
407
+ streamLog.debug("native:token", "Declining the refreshed feed for %s: it carries no audio rendition for a pipeline whose audio is a separate one.", channelName);
280
408
  return false;
281
409
  }
410
+ proxy.updateVariantUrl(feed.bestVariantUrl);
411
+ if (audioVariantUrl !== null) {
412
+ proxy.updateAudioVariantUrl(audioVariantUrl);
413
+ }
414
+ /* Report the fresh quality facts upward. The callback belongs to the streaming layer, which owns the registry entry these facts are written to, and its
415
+ * failure must not undo an application that has already happened: a swap that succeeded is worth more than the status display it feeds, so a throw is warned
416
+ * about and the refresh still counts as the success it was.
417
+ */
418
+ if (onFeedApplied) {
419
+ try {
420
+ onFeedApplied({ bandwidth: feed.bandwidth, codec: feed.codec, resolution: feed.resolution });
421
+ }
422
+ catch (error) {
423
+ streamLog.warn("The refreshed stream quality could not be recorded for %s: %s.", channelName, formatError(error));
424
+ }
425
+ }
426
+ scheduleTokenRefresh({
427
+ channelName,
428
+ masterUrl,
429
+ onFeedApplied,
430
+ page,
431
+ probeIdentity,
432
+ proxy,
433
+ reestablishManifest,
434
+ streamIdStr,
435
+ url,
436
+ variantUrl: feed.bestVariantUrl
437
+ });
438
+ return true;
282
439
  }
283
440
  /**
284
441
  * Attempts to refresh the manifest by directly fetching the master manifest URL from Node.js. Returns a fresh MediaFeed if the fetch succeeds and the variant URL
285
442
  * has sufficient token lifetime remaining, or null if the direct fetch should be abandoned in favor of a page reload.
286
443
  *
287
444
  * @param masterUrl - The master manifest URL to re-fetch.
288
- * @param channelName - The channel name for logging and cache keys.
445
+ * @param pipelineShape - The running proxy's compatibility envelope, which the probe selects within.
446
+ * @param probeIdentity - The stream's probe-cache identity. The master URL passing through here carries session tokens that rotate on every refresh, so it is
447
+ * never what the cache is keyed or stamped by; the stream's own identity is.
289
448
  * @param streamLog - The stream-scoped logger.
290
449
  * @returns A MediaFeed with a fresh variant URL, or null on failure.
291
450
  */
292
- async function tryDirectManifestRefresh(masterUrl, channelName, streamLog) {
293
- const mediaFeed = await probeManifest(masterUrl, channelName);
294
- if (!mediaFeed || (mediaFeed.encryption === "drm")) {
451
+ async function tryDirectManifestRefresh(masterUrl, pipelineShape, probeIdentity, streamLog) {
452
+ const mediaFeed = await probeManifest(masterUrl, probeIdentity, { pipelineShape });
453
+ // A null feed is the only refusal to read here. A constrained probe hands back nothing the running pipeline cannot absorb - a mismatched encryption kind,
454
+ // container, or audio topology comes back as null - so the admission question is answered before this line, in the one place that can answer it completely.
455
+ if (!mediaFeed) {
295
456
  return null;
296
457
  }
297
458
  // Verify the variant URL's token hasn't already expired or is about to expire. Parse the expiry from the variant URL (not the master URL) since that's what the
@@ -301,7 +462,7 @@ async function tryDirectManifestRefresh(masterUrl, channelName, streamLog) {
301
462
  if (variantExpiry) {
302
463
  const remaining = variantExpiry - Date.now();
303
464
  if (remaining < MIN_USABLE_TOKEN_LIFETIME) {
304
- streamLog.debug("native:token", "Direct fetch returned near-expired variant for %s (%ss remaining). Discarding.", channelName, Math.round(remaining / 1000));
465
+ streamLog.debug("native:token", "Direct fetch returned near-expired variant for %s (%ss remaining). Discarding.", probeIdentity.key, Math.round(remaining / 1000));
305
466
  return null;
306
467
  }
307
468
  }