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.
- package/dist/app.d.ts +6 -3
- package/dist/app.d.ts.map +1 -0
- package/dist/app.js +140 -86
- package/dist/app.js.map +1 -1
- package/dist/browser/blockedPage.d.ts +80 -0
- package/dist/browser/blockedPage.d.ts.map +1 -0
- package/dist/browser/blockedPage.js +209 -0
- package/dist/browser/blockedPage.js.map +1 -0
- package/dist/browser/browserSupervisor.d.ts +80 -0
- package/dist/browser/browserSupervisor.d.ts.map +1 -0
- package/dist/browser/browserSupervisor.js +186 -0
- package/dist/browser/browserSupervisor.js.map +1 -0
- package/dist/browser/cdp.d.ts +1 -0
- package/dist/browser/cdp.d.ts.map +1 -0
- package/dist/browser/cdp.js +6 -5
- package/dist/browser/cdp.js.map +1 -1
- package/dist/browser/channelSelection.d.ts +45 -14
- package/dist/browser/channelSelection.d.ts.map +1 -0
- package/dist/browser/channelSelection.js +81 -18
- package/dist/browser/channelSelection.js.map +1 -1
- package/dist/browser/consent.d.ts +152 -0
- package/dist/browser/consent.d.ts.map +1 -0
- package/dist/browser/consent.js +418 -0
- package/dist/browser/consent.js.map +1 -0
- package/dist/browser/display.d.ts +1 -0
- package/dist/browser/display.d.ts.map +1 -0
- package/dist/browser/hlsPlaylistObserver.d.ts +55 -0
- package/dist/browser/hlsPlaylistObserver.d.ts.map +1 -0
- package/dist/browser/hlsPlaylistObserver.js +132 -0
- package/dist/browser/hlsPlaylistObserver.js.map +1 -0
- package/dist/browser/index.d.ts +67 -40
- package/dist/browser/index.d.ts.map +1 -0
- package/dist/browser/index.js +508 -300
- package/dist/browser/index.js.map +1 -1
- package/dist/browser/launchGovernor.d.ts +89 -0
- package/dist/browser/launchGovernor.d.ts.map +1 -0
- package/dist/browser/launchGovernor.js +96 -0
- package/dist/browser/launchGovernor.js.map +1 -0
- package/dist/browser/login.d.ts +10 -2
- package/dist/browser/login.d.ts.map +1 -0
- package/dist/browser/login.js +36 -5
- package/dist/browser/login.js.map +1 -1
- package/dist/browser/manifestInterceptor.d.ts +102 -49
- package/dist/browser/manifestInterceptor.d.ts.map +1 -0
- package/dist/browser/manifestInterceptor.js +219 -237
- package/dist/browser/manifestInterceptor.js.map +1 -1
- package/dist/browser/pageStaleness.d.ts +30 -0
- package/dist/browser/pageStaleness.d.ts.map +1 -0
- package/dist/browser/pageStaleness.js +76 -0
- package/dist/browser/pageStaleness.js.map +1 -0
- package/dist/browser/precaching.d.ts +95 -3
- package/dist/browser/precaching.d.ts.map +1 -0
- package/dist/browser/precaching.js +499 -65
- package/dist/browser/precaching.js.map +1 -1
- package/dist/browser/tabNetworkObserver.d.ts +49 -0
- package/dist/browser/tabNetworkObserver.d.ts.map +1 -0
- package/dist/browser/tabNetworkObserver.js +144 -0
- package/dist/browser/tabNetworkObserver.js.map +1 -0
- package/dist/browser/tuning/cache.d.ts +61 -0
- package/dist/browser/tuning/cache.d.ts.map +1 -0
- package/dist/browser/tuning/cache.js +62 -0
- package/dist/browser/tuning/cache.js.map +1 -0
- package/dist/browser/tuning/comcastPolymer.d.ts +3 -2
- package/dist/browser/tuning/comcastPolymer.d.ts.map +1 -0
- package/dist/browser/tuning/comcastPolymer.js +79 -102
- package/dist/browser/tuning/comcastPolymer.js.map +1 -1
- package/dist/browser/tuning/cox.d.ts +1 -0
- package/dist/browser/tuning/cox.d.ts.map +1 -0
- package/dist/browser/tuning/directv.d.ts +1 -0
- package/dist/browser/tuning/directv.d.ts.map +1 -0
- package/dist/browser/tuning/directv.js +52 -46
- package/dist/browser/tuning/directv.js.map +1 -1
- package/dist/browser/tuning/fox.d.ts +1 -0
- package/dist/browser/tuning/fox.d.ts.map +1 -0
- package/dist/browser/tuning/fox.js +13 -12
- package/dist/browser/tuning/fox.js.map +1 -1
- package/dist/browser/tuning/gridSearch.d.ts +68 -0
- package/dist/browser/tuning/gridSearch.d.ts.map +1 -0
- package/dist/browser/tuning/gridSearch.js +85 -0
- package/dist/browser/tuning/gridSearch.js.map +1 -0
- package/dist/browser/tuning/hbo.d.ts +1 -0
- package/dist/browser/tuning/hbo.d.ts.map +1 -0
- package/dist/browser/tuning/hbo.js +73 -45
- package/dist/browser/tuning/hbo.js.map +1 -1
- package/dist/browser/tuning/hulu.d.ts +1 -0
- package/dist/browser/tuning/hulu.d.ts.map +1 -0
- package/dist/browser/tuning/hulu.js +128 -173
- package/dist/browser/tuning/hulu.js.map +1 -1
- package/dist/browser/tuning/shared.d.ts +75 -5
- package/dist/browser/tuning/shared.d.ts.map +1 -0
- package/dist/browser/tuning/shared.js +216 -6
- package/dist/browser/tuning/shared.js.map +1 -1
- package/dist/browser/tuning/sling.d.ts +1 -0
- package/dist/browser/tuning/sling.d.ts.map +1 -0
- package/dist/browser/tuning/sling.js +105 -126
- package/dist/browser/tuning/sling.js.map +1 -1
- package/dist/browser/tuning/spectrum.d.ts +1 -0
- package/dist/browser/tuning/spectrum.d.ts.map +1 -0
- package/dist/browser/tuning/spectrum.js +80 -125
- package/dist/browser/tuning/spectrum.js.map +1 -1
- package/dist/browser/tuning/thumbnailRow.d.ts +1 -0
- package/dist/browser/tuning/thumbnailRow.d.ts.map +1 -0
- package/dist/browser/tuning/thumbnailRow.js +8 -2
- package/dist/browser/tuning/thumbnailRow.js.map +1 -1
- package/dist/browser/tuning/tileClick.d.ts +1 -0
- package/dist/browser/tuning/tileClick.d.ts.map +1 -0
- package/dist/browser/tuning/tileClick.js +3 -2
- package/dist/browser/tuning/tileClick.js.map +1 -1
- package/dist/browser/tuning/xfinity.d.ts +1 -0
- package/dist/browser/tuning/xfinity.d.ts.map +1 -0
- package/dist/browser/tuning/youtubeTv.d.ts +1 -0
- package/dist/browser/tuning/youtubeTv.d.ts.map +1 -0
- package/dist/browser/tuning/youtubeTv.js +74 -127
- package/dist/browser/tuning/youtubeTv.js.map +1 -1
- package/dist/browser/video.d.ts +54 -14
- package/dist/browser/video.d.ts.map +1 -0
- package/dist/browser/video.js +296 -138
- package/dist/browser/video.js.map +1 -1
- package/dist/channels/index.d.ts +1 -0
- package/dist/channels/index.d.ts.map +1 -0
- package/dist/channels/index.js +12 -9
- package/dist/channels/index.js.map +1 -1
- package/dist/config/channelForm.d.ts +15 -1
- package/dist/config/channelForm.d.ts.map +1 -0
- package/dist/config/channelForm.js +28 -12
- package/dist/config/channelForm.js.map +1 -1
- package/dist/config/consistencyProbe.d.ts +1 -0
- package/dist/config/consistencyProbe.d.ts.map +1 -0
- package/dist/config/consistencyProbe.js +27 -21
- package/dist/config/consistencyProbe.js.map +1 -1
- package/dist/config/health.d.ts +41 -8
- package/dist/config/health.d.ts.map +1 -0
- package/dist/config/health.js +189 -38
- package/dist/config/health.js.map +1 -1
- package/dist/config/index.d.ts +55 -3
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +320 -69
- package/dist/config/index.js.map +1 -1
- package/dist/config/paths.d.ts +10 -9
- package/dist/config/paths.d.ts.map +1 -0
- package/dist/config/paths.js +11 -11
- package/dist/config/paths.js.map +1 -1
- package/dist/config/persistence.context.d.ts +1 -0
- package/dist/config/persistence.context.d.ts.map +1 -0
- package/dist/config/persistence.d.ts +13 -10
- package/dist/config/persistence.d.ts.map +1 -0
- package/dist/config/persistence.js +40 -21
- package/dist/config/persistence.js.map +1 -1
- package/dist/config/presets.d.ts +1 -0
- package/dist/config/presets.d.ts.map +1 -0
- package/dist/config/profiles.d.ts +22 -15
- package/dist/config/profiles.d.ts.map +1 -0
- package/dist/config/profiles.js +81 -47
- package/dist/config/profiles.js.map +1 -1
- package/dist/config/providerLineups.d.ts +47 -0
- package/dist/config/providerLineups.d.ts.map +1 -0
- package/dist/config/providerLineups.js +137 -0
- package/dist/config/providerLineups.js.map +1 -0
- package/dist/config/reactivity.d.ts +84 -0
- package/dist/config/reactivity.d.ts.map +1 -0
- package/dist/config/reactivity.js +226 -0
- package/dist/config/reactivity.js.map +1 -0
- package/dist/config/servicePacks.d.ts +18 -2
- package/dist/config/servicePacks.d.ts.map +1 -0
- package/dist/config/servicePacks.js +53 -11
- package/dist/config/servicePacks.js.map +1 -1
- package/dist/config/services.d.ts +21 -15
- package/dist/config/services.d.ts.map +1 -0
- package/dist/config/services.js +35 -24
- package/dist/config/services.js.map +1 -1
- package/dist/config/sites.d.ts +6 -4
- package/dist/config/sites.d.ts.map +1 -0
- package/dist/config/sites.js +15 -11
- package/dist/config/sites.js.map +1 -1
- package/dist/config/userChannels.d.ts +129 -30
- package/dist/config/userChannels.d.ts.map +1 -0
- package/dist/config/userChannels.js +333 -68
- package/dist/config/userChannels.js.map +1 -1
- package/dist/config/userConfig.d.ts +14 -9
- package/dist/config/userConfig.d.ts.map +1 -0
- package/dist/config/userConfig.js +87 -25
- package/dist/config/userConfig.js.map +1 -1
- package/dist/config/userProfiles.d.ts +10 -6
- package/dist/config/userProfiles.d.ts.map +1 -0
- package/dist/config/userProfiles.js +64 -39
- package/dist/config/userProfiles.js.map +1 -1
- package/dist/hdhr/channelMap.d.ts +1 -0
- package/dist/hdhr/channelMap.d.ts.map +1 -0
- package/dist/hdhr/deviceId.d.ts +1 -0
- package/dist/hdhr/deviceId.d.ts.map +1 -0
- package/dist/hdhr/discover.d.ts +1 -0
- package/dist/hdhr/discover.d.ts.map +1 -0
- package/dist/hdhr/discover.js +48 -47
- package/dist/hdhr/discover.js.map +1 -1
- package/dist/hdhr/getHandlers.d.ts +29 -0
- package/dist/hdhr/getHandlers.d.ts.map +1 -0
- package/dist/hdhr/getHandlers.js +196 -0
- package/dist/hdhr/getHandlers.js.map +1 -0
- package/dist/hdhr/identity.d.ts +36 -0
- package/dist/hdhr/identity.d.ts.map +1 -0
- package/dist/hdhr/identity.js +51 -0
- package/dist/hdhr/identity.js.map +1 -0
- package/dist/hdhr/index.d.ts +22 -4
- package/dist/hdhr/index.d.ts.map +1 -0
- package/dist/hdhr/index.js +229 -54
- package/dist/hdhr/index.js.map +1 -1
- package/dist/hdhr/protocol.d.ts +75 -0
- package/dist/hdhr/protocol.d.ts.map +1 -0
- package/dist/hdhr/protocol.js +264 -0
- package/dist/hdhr/protocol.js.map +1 -0
- package/dist/hdhr/tunerState.d.ts +24 -0
- package/dist/hdhr/tunerState.d.ts.map +1 -0
- package/dist/hdhr/tunerState.js +67 -0
- package/dist/hdhr/tunerState.js.map +1 -0
- package/dist/hdhr/udp.d.ts +43 -0
- package/dist/hdhr/udp.d.ts.map +1 -0
- package/dist/hdhr/udp.js +277 -0
- package/dist/hdhr/udp.js.map +1 -0
- package/dist/identity.d.ts +1 -0
- package/dist/identity.d.ts.map +1 -0
- package/dist/identity.js +13 -11
- package/dist/identity.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -12
- package/dist/index.js.map +1 -1
- package/dist/native/codecInference.d.ts +1 -0
- package/dist/native/codecInference.d.ts.map +1 -0
- package/dist/native/codecInference.js +50 -5
- package/dist/native/codecInference.js.map +1 -1
- package/dist/native/decrypt.d.ts +6 -2
- package/dist/native/decrypt.d.ts.map +1 -0
- package/dist/native/decrypt.js +15 -8
- package/dist/native/decrypt.js.map +1 -1
- package/dist/native/index.d.ts +40 -17
- package/dist/native/index.d.ts.map +1 -0
- package/dist/native/index.js +281 -120
- package/dist/native/index.js.map +1 -1
- package/dist/native/probe.d.ts +114 -18
- package/dist/native/probe.d.ts.map +1 -0
- package/dist/native/probe.js +452 -115
- package/dist/native/probe.js.map +1 -1
- package/dist/native/proxy.d.ts +57 -5
- package/dist/native/proxy.d.ts.map +1 -0
- package/dist/native/proxy.js +604 -170
- package/dist/native/proxy.js.map +1 -1
- package/dist/native/tokenExpiry.d.ts +1 -0
- package/dist/native/tokenExpiry.d.ts.map +1 -0
- package/dist/native/tokenExpiry.js +1 -1
- package/dist/routes/assets.d.ts +1 -0
- package/dist/routes/assets.d.ts.map +1 -0
- package/dist/routes/assets.js +4 -2
- package/dist/routes/assets.js.map +1 -1
- package/dist/routes/auth.d.ts +1 -0
- package/dist/routes/auth.d.ts.map +1 -0
- package/dist/routes/auth.js +0 -1
- package/dist/routes/auth.js.map +1 -1
- package/dist/routes/cdp.d.ts +5 -5
- package/dist/routes/cdp.d.ts.map +1 -0
- package/dist/routes/cdp.js +59 -20
- package/dist/routes/cdp.js.map +1 -1
- package/dist/routes/channels.d.ts +1 -0
- package/dist/routes/channels.d.ts.map +1 -0
- package/dist/routes/channels.js +2 -1
- package/dist/routes/channels.js.map +1 -1
- package/dist/routes/clientActions.d.ts +98 -0
- package/dist/routes/clientActions.d.ts.map +1 -0
- package/dist/routes/clientActions.js +113 -0
- package/dist/routes/clientActions.js.map +1 -0
- package/dist/routes/components.d.ts +5 -3
- package/dist/routes/components.d.ts.map +1 -0
- package/dist/routes/components.js +36 -39
- package/dist/routes/components.js.map +1 -1
- package/dist/routes/config/channels/endpoints/browse.d.ts +1 -0
- package/dist/routes/config/channels/endpoints/browse.d.ts.map +1 -0
- package/dist/routes/config/channels/endpoints/browse.js +23 -10
- package/dist/routes/config/channels/endpoints/browse.js.map +1 -1
- package/dist/routes/config/channels/endpoints/bulk.d.ts +1 -0
- package/dist/routes/config/channels/endpoints/bulk.d.ts.map +1 -0
- package/dist/routes/config/channels/endpoints/bulk.js +5 -3
- package/dist/routes/config/channels/endpoints/bulk.js.map +1 -1
- package/dist/routes/config/channels/endpoints/crud.d.ts +1 -0
- package/dist/routes/config/channels/endpoints/crud.d.ts.map +1 -0
- package/dist/routes/config/channels/endpoints/crud.js +85 -93
- package/dist/routes/config/channels/endpoints/crud.js.map +1 -1
- package/dist/routes/config/channels/endpoints/importExport.d.ts +1 -0
- package/dist/routes/config/channels/endpoints/importExport.d.ts.map +1 -0
- package/dist/routes/config/channels/endpoints/importExport.js +6 -3
- package/dist/routes/config/channels/endpoints/importExport.js.map +1 -1
- package/dist/routes/config/channels/endpoints/predefined.d.ts +1 -0
- package/dist/routes/config/channels/endpoints/predefined.d.ts.map +1 -0
- package/dist/routes/config/channels/endpoints/prefs.d.ts +1 -0
- package/dist/routes/config/channels/endpoints/prefs.d.ts.map +1 -0
- package/dist/routes/config/channels/endpoints/prefs.js +2 -1
- package/dist/routes/config/channels/endpoints/prefs.js.map +1 -1
- package/dist/routes/config/channels/endpoints/service.d.ts +2 -1
- package/dist/routes/config/channels/endpoints/service.d.ts.map +1 -0
- package/dist/routes/config/channels/endpoints/service.js +3 -1
- package/dist/routes/config/channels/endpoints/service.js.map +1 -1
- package/dist/routes/config/channels/endpoints/tags.d.ts +1 -0
- package/dist/routes/config/channels/endpoints/tags.d.ts.map +1 -0
- package/dist/routes/config/channels/endpoints/tags.js +13 -7
- package/dist/routes/config/channels/endpoints/tags.js.map +1 -1
- package/dist/routes/config/channels/healthBridge.d.ts +24 -0
- package/dist/routes/config/channels/healthBridge.d.ts.map +1 -0
- package/dist/routes/config/channels/healthBridge.js +71 -0
- package/dist/routes/config/channels/healthBridge.js.map +1 -0
- package/dist/routes/config/channels/http/handler.d.ts +1 -0
- package/dist/routes/config/channels/http/handler.d.ts.map +1 -0
- package/dist/routes/config/channels/http/playlistHint.d.ts +2 -1
- package/dist/routes/config/channels/http/playlistHint.d.ts.map +1 -0
- package/dist/routes/config/channels/http/playlistHint.js +16 -2
- package/dist/routes/config/channels/http/playlistHint.js.map +1 -1
- package/dist/routes/config/channels/http/serviceWarning.d.ts +4 -2
- package/dist/routes/config/channels/http/serviceWarning.d.ts.map +1 -0
- package/dist/routes/config/channels/http/serviceWarning.js +3 -2
- package/dist/routes/config/channels/http/serviceWarning.js.map +1 -1
- package/dist/routes/config/channels/index.d.ts +1 -0
- package/dist/routes/config/channels/index.d.ts.map +1 -0
- package/dist/routes/config/channels/setup.d.ts +1 -0
- package/dist/routes/config/channels/setup.d.ts.map +1 -0
- package/dist/routes/config/channels/table.d.ts +3 -3
- package/dist/routes/config/channels/table.d.ts.map +1 -0
- package/dist/routes/config/channels/table.js +148 -107
- package/dist/routes/config/channels/table.js.map +1 -1
- package/dist/routes/config/http/envelope.d.ts +1 -0
- package/dist/routes/config/http/envelope.d.ts.map +1 -0
- package/dist/routes/config/index.d.ts +35 -3
- package/dist/routes/config/index.d.ts.map +1 -0
- package/dist/routes/config/index.js +51 -2
- package/dist/routes/config/index.js.map +1 -1
- package/dist/routes/config/services.d.ts +5 -4
- package/dist/routes/config/services.d.ts.map +1 -0
- package/dist/routes/config/services.js +57 -43
- package/dist/routes/config/services.js.map +1 -1
- package/dist/routes/config/settings.d.ts +1 -0
- package/dist/routes/config/settings.d.ts.map +1 -0
- package/dist/routes/config/settings.js +98 -49
- package/dist/routes/config/settings.js.map +1 -1
- package/dist/routes/debug.d.ts +1 -0
- package/dist/routes/debug.d.ts.map +1 -0
- package/dist/routes/debug.js +277 -139
- package/dist/routes/debug.js.map +1 -1
- package/dist/routes/health.d.ts +39 -1
- package/dist/routes/health.d.ts.map +1 -0
- package/dist/routes/health.js +33 -27
- package/dist/routes/health.js.map +1 -1
- package/dist/routes/hls.d.ts +1 -0
- package/dist/routes/hls.d.ts.map +1 -0
- package/dist/routes/icons.d.ts +1 -0
- package/dist/routes/icons.d.ts.map +1 -0
- package/dist/routes/icons.js +1 -1
- package/dist/routes/index.d.ts +1 -0
- package/dist/routes/index.d.ts.map +1 -0
- package/dist/routes/logs.d.ts +1 -0
- package/dist/routes/logs.d.ts.map +1 -0
- package/dist/routes/logs.js +14 -4
- package/dist/routes/logs.js.map +1 -1
- package/dist/routes/mpegts.d.ts +1 -0
- package/dist/routes/mpegts.d.ts.map +1 -0
- package/dist/routes/play.d.ts +1 -0
- package/dist/routes/play.d.ts.map +1 -0
- package/dist/routes/playlist.d.ts +1 -0
- package/dist/routes/playlist.d.ts.map +1 -0
- package/dist/routes/playlist.js +7 -2
- package/dist/routes/playlist.js.map +1 -1
- package/dist/routes/root/content.d.ts +8 -5
- package/dist/routes/root/content.d.ts.map +1 -0
- package/dist/routes/root/content.js +29 -26
- package/dist/routes/root/content.js.map +1 -1
- package/dist/routes/root/index.d.ts +2 -1
- package/dist/routes/root/index.d.ts.map +1 -0
- package/dist/routes/root/index.js +26 -21
- package/dist/routes/root/index.js.map +1 -1
- package/dist/routes/root/scripts/channels.d.ts +6 -0
- package/dist/routes/root/scripts/channels.d.ts.map +1 -0
- package/dist/routes/root/scripts/channels.js +197 -118
- package/dist/routes/root/scripts/channels.js.map +1 -1
- package/dist/routes/root/scripts/clientEscape.d.ts +22 -0
- package/dist/routes/root/scripts/clientEscape.d.ts.map +1 -0
- package/dist/routes/root/scripts/clientEscape.js +41 -0
- package/dist/routes/root/scripts/clientEscape.js.map +1 -0
- package/dist/routes/root/scripts/clientUrl.d.ts +23 -0
- package/dist/routes/root/scripts/clientUrl.d.ts.map +1 -0
- package/dist/routes/root/scripts/clientUrl.js +44 -0
- package/dist/routes/root/scripts/clientUrl.js.map +1 -0
- package/dist/routes/root/scripts/config.d.ts +6 -0
- package/dist/routes/root/scripts/config.d.ts.map +1 -0
- package/dist/routes/root/scripts/config.js +110 -24
- package/dist/routes/root/scripts/config.js.map +1 -1
- package/dist/routes/root/scripts/index.d.ts +1 -0
- package/dist/routes/root/scripts/index.d.ts.map +1 -0
- package/dist/routes/root/scripts/shared.d.ts +1 -0
- package/dist/routes/root/scripts/shared.d.ts.map +1 -0
- package/dist/routes/root/scripts/shared.js +112 -26
- package/dist/routes/root/scripts/shared.js.map +1 -1
- package/dist/routes/root/scripts/status.d.ts +1 -0
- package/dist/routes/root/scripts/status.d.ts.map +1 -0
- package/dist/routes/root/scripts/status.handlers.d.ts +23 -33
- package/dist/routes/root/scripts/status.handlers.d.ts.map +1 -0
- package/dist/routes/root/scripts/status.handlers.js +46 -92
- package/dist/routes/root/scripts/status.handlers.js.map +1 -1
- package/dist/routes/root/scripts/status.js +21 -8
- package/dist/routes/root/scripts/status.js.map +1 -1
- package/dist/routes/root/styles.d.ts +1 -0
- package/dist/routes/root/styles.d.ts.map +1 -0
- package/dist/routes/root/styles.js +7 -4
- package/dist/routes/root/styles.js.map +1 -1
- package/dist/routes/services.d.ts +13 -1
- package/dist/routes/services.d.ts.map +1 -0
- package/dist/routes/services.js +81 -68
- package/dist/routes/services.js.map +1 -1
- package/dist/routes/sse.d.ts +1 -0
- package/dist/routes/sse.d.ts.map +1 -0
- package/dist/routes/sse.js +1 -1
- package/dist/routes/sse.js.map +1 -1
- package/dist/routes/streams.d.ts +2 -1
- package/dist/routes/streams.d.ts.map +1 -0
- package/dist/routes/streams.js +6 -4
- package/dist/routes/streams.js.map +1 -1
- package/dist/routes/theme.d.ts +3 -2
- package/dist/routes/theme.d.ts.map +1 -0
- package/dist/routes/theme.js +6 -5
- package/dist/routes/theme.js.map +1 -1
- package/dist/routes/ui.d.ts +3 -1
- package/dist/routes/ui.d.ts.map +1 -0
- package/dist/routes/ui.js +16 -4
- package/dist/routes/ui.js.map +1 -1
- package/dist/routes/upgrade.d.ts +1 -0
- package/dist/routes/upgrade.d.ts.map +1 -0
- package/dist/routes/upgrade.js +6 -2
- package/dist/routes/upgrade.js.map +1 -1
- package/dist/service/commands.context.d.ts +1 -0
- package/dist/service/commands.context.d.ts.map +1 -0
- package/dist/service/commands.context.js +4 -3
- package/dist/service/commands.context.js.map +1 -1
- package/dist/service/commands.d.ts +1 -0
- package/dist/service/commands.d.ts.map +1 -0
- package/dist/service/commands.js +10 -13
- package/dist/service/commands.js.map +1 -1
- package/dist/service/generators.context.d.ts +1 -0
- package/dist/service/generators.context.d.ts.map +1 -0
- package/dist/service/generators.context.js +1 -1
- package/dist/service/generators.context.js.map +1 -1
- package/dist/service/generators.d.ts +4 -3
- package/dist/service/generators.d.ts.map +1 -0
- package/dist/service/generators.js +111 -31
- package/dist/service/generators.js.map +1 -1
- package/dist/service/index.d.ts +1 -0
- package/dist/service/index.d.ts.map +1 -0
- package/dist/streaming/captureLock.d.ts +44 -0
- package/dist/streaming/captureLock.d.ts.map +1 -0
- package/dist/streaming/captureLock.js +95 -0
- package/dist/streaming/captureLock.js.map +1 -0
- package/dist/streaming/captureSession.d.ts +31 -0
- package/dist/streaming/captureSession.d.ts.map +1 -0
- package/dist/streaming/captureSession.js +54 -0
- package/dist/streaming/captureSession.js.map +1 -0
- package/dist/streaming/clients.d.ts +1 -0
- package/dist/streaming/clients.d.ts.map +1 -0
- package/dist/streaming/codec.d.ts +1 -0
- package/dist/streaming/codec.d.ts.map +1 -0
- package/dist/streaming/fmp4Segmenter.d.ts +29 -2
- package/dist/streaming/fmp4Segmenter.d.ts.map +1 -0
- package/dist/streaming/fmp4Segmenter.js +79 -26
- package/dist/streaming/fmp4Segmenter.js.map +1 -1
- package/dist/streaming/hls.d.ts +18 -7
- package/dist/streaming/hls.d.ts.map +1 -0
- package/dist/streaming/hls.js +220 -120
- package/dist/streaming/hls.js.map +1 -1
- package/dist/streaming/hlsResume.d.ts +1 -0
- package/dist/streaming/hlsResume.d.ts.map +1 -0
- package/dist/streaming/hlsResume.js +1 -1
- package/dist/streaming/hlsResume.js.map +1 -1
- package/dist/streaming/hlsSegments.d.ts +59 -2
- package/dist/streaming/hlsSegments.d.ts.map +1 -0
- package/dist/streaming/hlsSegments.js +147 -17
- package/dist/streaming/hlsSegments.js.map +1 -1
- package/dist/streaming/lifecycle.d.ts +4 -1
- package/dist/streaming/lifecycle.d.ts.map +1 -0
- package/dist/streaming/lifecycle.js +65 -85
- package/dist/streaming/lifecycle.js.map +1 -1
- package/dist/streaming/monitor.d.ts +6 -4
- package/dist/streaming/monitor.d.ts.map +1 -0
- package/dist/streaming/monitor.js +343 -145
- package/dist/streaming/monitor.js.map +1 -1
- package/dist/streaming/mp4Parser.d.ts +5 -4
- package/dist/streaming/mp4Parser.d.ts.map +1 -0
- package/dist/streaming/mp4Parser.js +36 -13
- package/dist/streaming/mp4Parser.js.map +1 -1
- package/dist/streaming/mpegts.d.ts +16 -0
- package/dist/streaming/mpegts.d.ts.map +1 -0
- package/dist/streaming/mpegts.js +61 -17
- package/dist/streaming/mpegts.js.map +1 -1
- package/dist/streaming/playlistBuilder.d.ts +1 -0
- package/dist/streaming/playlistBuilder.d.ts.map +1 -0
- package/dist/streaming/preroll.d.ts +32 -5
- package/dist/streaming/preroll.d.ts.map +1 -0
- package/dist/streaming/preroll.js +70 -32
- package/dist/streaming/preroll.js.map +1 -1
- package/dist/streaming/pretune.d.ts +26 -1
- package/dist/streaming/pretune.d.ts.map +1 -0
- package/dist/streaming/pretune.js +22 -24
- package/dist/streaming/pretune.js.map +1 -1
- package/dist/streaming/pretuneTimers.d.ts +24 -0
- package/dist/streaming/pretuneTimers.d.ts.map +1 -0
- package/dist/streaming/pretuneTimers.js +56 -0
- package/dist/streaming/pretuneTimers.js.map +1 -0
- package/dist/streaming/recovery.d.ts +139 -5
- package/dist/streaming/recovery.d.ts.map +1 -0
- package/dist/streaming/recovery.js +160 -16
- package/dist/streaming/recovery.js.map +1 -1
- package/dist/streaming/registry.d.ts +27 -9
- package/dist/streaming/registry.d.ts.map +1 -0
- package/dist/streaming/registry.js +21 -3
- package/dist/streaming/registry.js.map +1 -1
- package/dist/streaming/setup.d.ts +157 -29
- package/dist/streaming/setup.d.ts.map +1 -0
- package/dist/streaming/setup.js +1034 -458
- package/dist/streaming/setup.js.map +1 -1
- package/dist/streaming/showInfo.d.ts +21 -1
- package/dist/streaming/showInfo.d.ts.map +1 -0
- package/dist/streaming/showInfo.js +31 -16
- package/dist/streaming/showInfo.js.map +1 -1
- package/dist/streaming/statusEmitter.d.ts +10 -10
- package/dist/streaming/statusEmitter.d.ts.map +1 -0
- package/dist/streaming/statusEmitter.js +5 -8
- package/dist/streaming/statusEmitter.js.map +1 -1
- package/dist/types/channels.d.ts +16 -4
- package/dist/types/channels.d.ts.map +1 -0
- package/dist/types/channels.js +4 -2
- package/dist/types/channels.js.map +1 -1
- package/dist/types/config.d.ts +16 -5
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/index.d.ts +3 -2
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/profiles.d.ts +6 -4
- package/dist/types/profiles.d.ts.map +1 -0
- package/dist/types/selection.d.ts +31 -3
- package/dist/types/selection.d.ts.map +1 -0
- package/dist/types/selection.js +4 -2
- package/dist/types/selection.js.map +1 -1
- package/dist/types/shared.d.ts +1 -0
- package/dist/types/shared.d.ts.map +1 -0
- package/dist/types/streaming.d.ts +8 -0
- package/dist/types/streaming.d.ts.map +1 -0
- package/dist/upgrade/commands.context.d.ts +1 -0
- package/dist/upgrade/commands.context.d.ts.map +1 -0
- package/dist/upgrade/commands.context.js +14 -10
- package/dist/upgrade/commands.context.js.map +1 -1
- package/dist/upgrade/commands.d.ts +8 -15
- package/dist/upgrade/commands.d.ts.map +1 -0
- package/dist/upgrade/commands.js +27 -28
- package/dist/upgrade/commands.js.map +1 -1
- package/dist/upgrade/detection.context.d.ts +1 -0
- package/dist/upgrade/detection.context.d.ts.map +1 -0
- package/dist/upgrade/detection.context.js +4 -1
- package/dist/upgrade/detection.context.js.map +1 -1
- package/dist/upgrade/detection.d.ts +9 -7
- package/dist/upgrade/detection.d.ts.map +1 -0
- package/dist/upgrade/detection.js +23 -54
- package/dist/upgrade/detection.js.map +1 -1
- package/dist/upgrade/index.d.ts +2 -0
- package/dist/upgrade/index.d.ts.map +1 -0
- package/dist/upgrade/index.js +1 -0
- package/dist/upgrade/index.js.map +1 -1
- package/dist/upgrade/lifecycle.context.d.ts +20 -0
- package/dist/upgrade/lifecycle.context.d.ts.map +1 -0
- package/dist/upgrade/lifecycle.context.js +47 -0
- package/dist/upgrade/lifecycle.context.js.map +1 -0
- package/dist/upgrade/lifecycle.d.ts +72 -0
- package/dist/upgrade/lifecycle.d.ts.map +1 -0
- package/dist/upgrade/lifecycle.js +175 -0
- package/dist/upgrade/lifecycle.js.map +1 -0
- package/dist/upgrade/pathHandle.d.ts +30 -0
- package/dist/upgrade/pathHandle.d.ts.map +1 -0
- package/dist/upgrade/pathHandle.js +102 -0
- package/dist/upgrade/pathHandle.js.map +1 -0
- package/dist/utils/bootSession.context.d.ts +16 -0
- package/dist/utils/bootSession.context.d.ts.map +1 -0
- package/dist/utils/bootSession.context.js +60 -0
- package/dist/utils/bootSession.context.js.map +1 -0
- package/dist/utils/bootSession.d.ts +17 -0
- package/dist/utils/bootSession.d.ts.map +1 -0
- package/dist/utils/bootSession.js +20 -0
- package/dist/utils/bootSession.js.map +1 -0
- package/dist/utils/chromeFetch.d.ts +1 -0
- package/dist/utils/chromeFetch.d.ts.map +1 -0
- package/dist/utils/cliOutput.d.ts +1 -0
- package/dist/utils/cliOutput.d.ts.map +1 -0
- package/dist/utils/clock.d.ts +5 -4
- package/dist/utils/clock.d.ts.map +1 -0
- package/dist/utils/clock.js +19 -16
- package/dist/utils/clock.js.map +1 -1
- package/dist/utils/debugFilter.d.ts +9 -1
- package/dist/utils/debugFilter.d.ts.map +1 -0
- package/dist/utils/debugFilter.js +60 -33
- package/dist/utils/debugFilter.js.map +1 -1
- package/dist/utils/delay.d.ts +48 -15
- package/dist/utils/delay.d.ts.map +1 -0
- package/dist/utils/delay.js +73 -20
- package/dist/utils/delay.js.map +1 -1
- package/dist/utils/errors.d.ts +32 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +45 -4
- package/dist/utils/errors.js.map +1 -1
- package/dist/utils/evaluate.d.ts +1 -0
- package/dist/utils/evaluate.d.ts.map +1 -0
- package/dist/utils/evaluate.js +29 -23
- package/dist/utils/evaluate.js.map +1 -1
- package/dist/utils/ffmpeg.context.d.ts +1 -0
- package/dist/utils/ffmpeg.context.d.ts.map +1 -0
- package/dist/utils/ffmpeg.context.js.map +1 -1
- package/dist/utils/ffmpeg.d.ts +29 -7
- package/dist/utils/ffmpeg.d.ts.map +1 -0
- package/dist/utils/ffmpeg.js +32 -14
- package/dist/utils/ffmpeg.js.map +1 -1
- package/dist/utils/fileLogger.d.ts +3 -1
- package/dist/utils/fileLogger.d.ts.map +1 -0
- package/dist/utils/fileLogger.js +64 -29
- package/dist/utils/fileLogger.js.map +1 -1
- package/dist/utils/format.d.ts +14 -1
- package/dist/utils/format.d.ts.map +1 -0
- package/dist/utils/format.js +19 -5
- package/dist/utils/format.js.map +1 -1
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +3 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/logEmitter.d.ts +1 -0
- package/dist/utils/logEmitter.d.ts.map +1 -0
- package/dist/utils/logger.d.ts +1 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +10 -6
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/m3u.d.ts +3 -2
- package/dist/utils/m3u.d.ts.map +1 -0
- package/dist/utils/m3u.js +4 -3
- package/dist/utils/m3u.js.map +1 -1
- package/dist/utils/markup.d.ts +13 -1
- package/dist/utils/markup.d.ts.map +1 -0
- package/dist/utils/markup.js +28 -4
- package/dist/utils/markup.js.map +1 -1
- package/dist/utils/memo.d.ts +3 -2
- package/dist/utils/memo.d.ts.map +1 -0
- package/dist/utils/memo.js +3 -3
- package/dist/utils/morganStream.d.ts +1 -0
- package/dist/utils/morganStream.d.ts.map +1 -0
- package/dist/utils/network.d.ts +1 -0
- package/dist/utils/network.d.ts.map +1 -0
- package/dist/utils/network.js +2 -2
- package/dist/utils/pid.d.ts +3 -25
- package/dist/utils/pid.d.ts.map +1 -0
- package/dist/utils/pid.js +10 -49
- package/dist/utils/pid.js.map +1 -1
- package/dist/utils/platform.d.ts +3 -1
- package/dist/utils/platform.d.ts.map +1 -0
- package/dist/utils/platform.js +2 -1
- package/dist/utils/platform.js.map +1 -1
- package/dist/utils/processInspector.context.d.ts +8 -0
- package/dist/utils/processInspector.context.d.ts.map +1 -0
- package/dist/utils/processInspector.context.js +106 -0
- package/dist/utils/processInspector.context.js.map +1 -0
- package/dist/utils/processInspector.d.ts +55 -0
- package/dist/utils/processInspector.d.ts.map +1 -0
- package/dist/utils/processInspector.js +127 -0
- package/dist/utils/processInspector.js.map +1 -0
- package/dist/utils/retry.d.ts +25 -2
- package/dist/utils/retry.d.ts.map +1 -0
- package/dist/utils/retry.js +33 -6
- package/dist/utils/retry.js.map +1 -1
- package/dist/utils/runtimeIdentity.context.d.ts +7 -0
- package/dist/utils/runtimeIdentity.context.d.ts.map +1 -0
- package/dist/utils/runtimeIdentity.context.js +43 -0
- package/dist/utils/runtimeIdentity.context.js.map +1 -0
- package/dist/utils/runtimeIdentity.d.ts +98 -0
- package/dist/utils/runtimeIdentity.d.ts.map +1 -0
- package/dist/utils/runtimeIdentity.js +181 -0
- package/dist/utils/runtimeIdentity.js.map +1 -0
- package/dist/utils/sanitize.d.ts +1 -0
- package/dist/utils/sanitize.d.ts.map +1 -0
- package/dist/utils/streamContext.d.ts +2 -1
- package/dist/utils/streamContext.d.ts.map +1 -0
- package/dist/utils/streamContext.js +1 -1
- package/dist/utils/timing.d.ts +2 -1
- package/dist/utils/timing.d.ts.map +1 -0
- package/dist/utils/timing.js +0 -5
- package/dist/utils/timing.js.map +1 -1
- package/dist/utils/version.d.ts +1 -0
- package/dist/utils/version.d.ts.map +1 -0
- package/dist/utils/version.js +47 -13
- package/dist/utils/version.js.map +1 -1
- package/package.json +26 -20
package/dist/browser/video.js
CHANGED
|
@@ -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,
|
|
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
|
-
* -
|
|
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
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
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
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
|
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
|
|
275
|
-
//
|
|
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.
|
|
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
|
-
//
|
|
357
|
-
//
|
|
358
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
|
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()
|
|
655
|
-
*
|
|
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
|
|
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
|
|
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
|
-
*
|
|
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
|
-
*
|
|
939
|
-
*
|
|
940
|
-
*
|
|
941
|
-
*
|
|
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
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
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
|
-
*
|
|
961
|
-
*
|
|
962
|
-
*
|
|
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
|
|
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
|
|
967
|
-
const
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
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
|
-
*
|
|
1002
|
-
*
|
|
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
|
-
//
|
|
1017
|
-
//
|
|
1018
|
-
//
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
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
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
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
|
-
|
|
1051
|
-
|
|
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
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
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
|
|
1069
|
-
*
|
|
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,
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
}
|