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/streaming/hls.js
CHANGED
|
@@ -1,23 +1,19 @@
|
|
|
1
|
-
/* Copyright(C) 2024-2026, HJD (https://github.com/hjdhjd). All rights reserved.
|
|
2
|
-
*
|
|
3
|
-
* hls.ts: HLS streaming request handlers for PrismCast.
|
|
4
|
-
*/
|
|
5
|
-
import { getEffectiveCaptureCodec, isCaptureHardwareAccelerated } from "./codec.js";
|
|
6
1
|
import { LOG, formatError, runWithStreamContext, startTimer } from "../utils/index.js";
|
|
7
|
-
import { StreamSetupError, createPageWithCapture, generateStreamId, setupStream } from "./setup.js";
|
|
8
|
-
import {
|
|
2
|
+
import { StreamSetupError, createPageWithCapture, generateStreamId, reestablishChannelManifest, setupStream, validateStreamUrl } from "./setup.js";
|
|
3
|
+
import { buildProbeCacheStamp, clearProbeCache } from "../native/probe.js";
|
|
4
|
+
import { cancelPrerollTimer, createHLSState, getAllStreams, getNextStreamId, getStream, getStreamCount, registerStream, updateLastAccess } from "./registry.js";
|
|
9
5
|
import { createInitialStreamStatus, emitStreamAdded } from "./statusEmitter.js";
|
|
10
6
|
import { deleteResumeData, getResumeSegmentIndex, peekResumeData } from "./hlsResume.js";
|
|
11
7
|
import { emitCurrentSystemStatus, isLoginModeActive, unregisterManagedPage } from "../browser/index.js";
|
|
12
8
|
import { generatePrerollPlaylist, getPrerollCodec, getPrerollSegmentCount, isPrerollReady } from "./preroll.js";
|
|
13
9
|
import { getAllChannels, getChannelLogo, isPredefinedChannelDisabled } from "../config/userChannels.js";
|
|
14
|
-
import { getAudioPlaylist, getAudioSegment, getInitSegment, getPlaylist, getSegment, getVideoPlaylist, waitForPlaylist } from "./hlsSegments.js";
|
|
10
|
+
import { getAudioPlaylist, getAudioSegment, getInitSegment, getNamedInitSegment, getPlaylist, getSegment, getVideoPlaylist, waitForPlaylist } from "./hlsSegments.js";
|
|
15
11
|
import { getAuthDomainForChannel, getResolvedChannel, getServiceTagForChannel, isChannelAvailableByService, resolveServiceKey } from "../config/services.js";
|
|
16
12
|
import { getChannelStreamId, isTerminationInitiated, setChannelStreamId, terminateStream } from "./lifecycle.js";
|
|
13
|
+
import { getEffectiveCaptureCodec, isCaptureHardwareAccelerated } from "./codec.js";
|
|
17
14
|
import { markChannelFailure, markChannelSuccess } from "../config/health.js";
|
|
18
15
|
import { CONFIG } from "../config/index.js";
|
|
19
16
|
import { attemptNativeStreaming } from "../native/index.js";
|
|
20
|
-
import { clearProbeCache } from "../native/probe.js";
|
|
21
17
|
import { createFMP4Segmenter } from "./fmp4Segmenter.js";
|
|
22
18
|
import { createHash } from "node:crypto";
|
|
23
19
|
import { getProviderBySlug } from "../browser/channelSelection.js";
|
|
@@ -41,14 +37,15 @@ import { triggerShowNameUpdate } from "./showInfo.js";
|
|
|
41
37
|
* the proxy takes over. Otherwise, createCaptureSegmenter() creates the fMP4 segmenter and pipes the capture stream. When the first real playlist arrives
|
|
42
38
|
* (from either the segmenter or native proxy), the preroll timer is cancelled and the client receives live content on the next poll.
|
|
43
39
|
*
|
|
44
|
-
* Shared streams: If multiple clients request the same channel (or the same ad-hoc URL with the same profile
|
|
45
|
-
* stream creation, and subsequent clients get the existing playlist and segments. Ad-hoc streams are identified by a
|
|
46
|
-
* URL and
|
|
40
|
+
* Shared streams: If multiple clients request the same channel (or the same ad-hoc URL with the same profile, selector, clickToPlay, and clickSelector), they share
|
|
41
|
+
* one stream. The first client triggers stream creation, and subsequent clients get the existing playlist and segments. Ad-hoc streams are identified by a
|
|
42
|
+
* synthetic key ("play-<hash>") derived from the URL, profile, selector, clickToPlay, and clickSelector, allowing them to use the same channelToStreamId
|
|
43
|
+
* deduplication mechanism as predefined channels.
|
|
47
44
|
*/
|
|
48
45
|
// Delay before seeding the preroll playlist in milliseconds. If stream setup completes before this timer fires, the preroll is skipped and the client receives real
|
|
49
46
|
// content directly. This ensures fast-tuning services (native HLS at 2-3s, most capture services at 4-7s) never see preroll, while slow services (Xfinity/Cox at
|
|
50
47
|
// 13-15s) get preroll content after the delay to prevent HTTP timeouts.
|
|
51
|
-
const PREROLL_DELAY_MS =
|
|
48
|
+
const PREROLL_DELAY_MS = 9000;
|
|
52
49
|
/**
|
|
53
50
|
* Builds the onError/onStop callbacks for createFMP4Segmenter. Both callbacks share the same termination chain (skip-if-already-terminating guard, log error,
|
|
54
51
|
* terminate stream, emit current system status); the only differences between callsites are the log-message and termination-reason suffixes used to mark the
|
|
@@ -83,8 +80,8 @@ function buildSegmenterTerminationHandlers(streamId, channelName, options = {})
|
|
|
83
80
|
// Login mode error body used by both validateChannel() and handlePlayStream() to ensure consistent response format.
|
|
84
81
|
const LOGIN_MODE_BODY = { error: "Login in progress", message: "Please complete authentication before starting new streams." };
|
|
85
82
|
/**
|
|
86
|
-
* Validates a channel name for streaming. Performs all fast, synchronous checks: disabled status, service resolution, channel lookup,
|
|
87
|
-
* discriminated union so callers can handle success and failure without coupling to Express response objects.
|
|
83
|
+
* Validates a channel name for streaming. Performs all fast, synchronous checks: disabled status, service resolution, channel lookup, service-filter availability,
|
|
84
|
+
* and login mode. Returns a discriminated union so callers can handle success and failure without coupling to Express response objects.
|
|
88
85
|
*
|
|
89
86
|
* This is extracted from ensureChannelStream() so it can be called by both HLS and MPEG-TS code paths without duplicating the validation logic.
|
|
90
87
|
*
|
|
@@ -107,7 +104,8 @@ export function validateChannel(channelName) {
|
|
|
107
104
|
if (!channel && (resolvedKey !== channelName)) {
|
|
108
105
|
LOG.warn("Service '%s' not found for channel '%s'. Using default service.", resolvedKey, channelName);
|
|
109
106
|
}
|
|
110
|
-
//
|
|
107
|
+
// effectiveChannel may be undefined: getResolvedChannel can return undefined and the getAllChannels() fallback is also possibly-undefined under
|
|
108
|
+
// noUncheckedIndexedAccess, so we guard before use.
|
|
111
109
|
if (!effectiveChannel) {
|
|
112
110
|
return { body: "Channel not found.", statusCode: 404, valid: false };
|
|
113
111
|
}
|
|
@@ -139,6 +137,34 @@ export function sendValidationError(validation, res) {
|
|
|
139
137
|
res.status(validation.statusCode).send(validation.body);
|
|
140
138
|
}
|
|
141
139
|
}
|
|
140
|
+
// Capacity Reservation.
|
|
141
|
+
/**
|
|
142
|
+
* Pure predicate for the concurrent-stream capacity decision. Returns true when a new stream may be admitted given the current active count and the configured
|
|
143
|
+
* limit. Extracted as a standalone function so the boundary arithmetic is pinned by a unit test without driving a browser. The count passed in must be the count
|
|
144
|
+
* BEFORE the new stream's pending entry is registered, so the new stream is naturally excluded from its own capacity check; admitting the final slot
|
|
145
|
+
* (active === maxConcurrent - 1) returns true.
|
|
146
|
+
* @param activeCount - Number of streams currently registered, excluding the stream being admitted.
|
|
147
|
+
* @param maxConcurrent - The configured concurrent-stream limit.
|
|
148
|
+
* @returns True if a slot is available for the new stream.
|
|
149
|
+
*/
|
|
150
|
+
export function hasStreamCapacity(activeCount, maxConcurrent) {
|
|
151
|
+
return activeCount < maxConcurrent;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Reserves a concurrent-stream slot at the registration site - the single source of truth for the capacity decision across both the preroll path
|
|
155
|
+
* (ensureChannelStream) and the blocking path (initializeStream). It evaluates capacity against the current registry count BEFORE the new stream's pending entry is
|
|
156
|
+
* registered, so the new stream is excluded from its own check. setupStream never re-checks capacity; this registration-site reservation is the sole authority for
|
|
157
|
+
* the decision, so at the boundary (one slot free) the registration-site gate admits the stream and no downstream check can double-count it against its own slot.
|
|
158
|
+
* When the limit is reached, a single idle stream is reclaimed to free a slot; reservation succeeds when either a slot was already free or one was reclaimed.
|
|
159
|
+
* @returns True if a slot was reserved (room existed or an idle stream was reclaimed), false if the limit is reached and no idle stream could be reclaimed.
|
|
160
|
+
*/
|
|
161
|
+
function reserveStreamSlot() {
|
|
162
|
+
if (hasStreamCapacity(getStreamCount(), CONFIG.streaming.maxConcurrentStreams)) {
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
// At capacity. Reclaim the longest-idle stream to make room; reservation succeeds only if one was actually freed.
|
|
166
|
+
return reclaimIdleStream();
|
|
167
|
+
}
|
|
142
168
|
// Public Endpoint Handlers.
|
|
143
169
|
/**
|
|
144
170
|
* Ensures a stream is running for a channel. If no stream exists, registers a pending stream with a preroll playlist (when available) and launches async setup. If a
|
|
@@ -174,15 +200,15 @@ export async function ensureChannelStream(channelName, req, res) {
|
|
|
174
200
|
// stream setup runs asynchronously. When the preroll is not available (FFmpeg missing or failed), fall back to blocking initialization.
|
|
175
201
|
const prerollCodec = getPrerollCodec();
|
|
176
202
|
if (isPrerollReady(prerollCodec)) {
|
|
177
|
-
//
|
|
178
|
-
//
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
203
|
+
// Reserve a capacity slot before registering the pending entry. The pending entry occupies a registry slot the instant it is registered, so the capacity
|
|
204
|
+
// decision must happen here - while the new stream is still excluded from the count - to avoid a later self-counting check rejecting it after the client has
|
|
205
|
+
// already received a preroll playlist. reserveStreamSlot is the single source of truth for this decision and reclaims an idle stream when at the limit. On
|
|
206
|
+
// failure we send a proper 503 here, before any registration or preroll response.
|
|
207
|
+
if (!reserveStreamSlot()) {
|
|
208
|
+
res.setHeader("Retry-After", "10");
|
|
209
|
+
res.setHeader("X-HDHomeRun-Error", "All Tuners In Use");
|
|
210
|
+
res.status(503).send("Maximum concurrent streams (" + String(CONFIG.streaming.maxConcurrentStreams) + ") reached. Try again later.");
|
|
211
|
+
return null;
|
|
186
212
|
}
|
|
187
213
|
const clientAddress = req.ip ?? req.socket.remoteAddress ?? null;
|
|
188
214
|
const pending = registerPendingStream(channelName, validation.channel, clientAddress, req, prerollCodec);
|
|
@@ -258,8 +284,10 @@ export function handleHLSSegment(req, res) {
|
|
|
258
284
|
sendSegment(initSegment, "init.mp4", res);
|
|
259
285
|
return;
|
|
260
286
|
}
|
|
261
|
-
// Handle media segments (.m4s and .ts). Check
|
|
262
|
-
|
|
287
|
+
// Handle media segments (.m4s and .ts) and the named initialization segments a native fMP4 relay serves. Check the video and audio segment stores, then the
|
|
288
|
+
// relay's per-track init store - a name the relay minted for an upstream #EXT-X-MAP reference resolves here rather than through the literal branch above,
|
|
289
|
+
// which belongs to the capture path's single init slot.
|
|
290
|
+
const segment = getSegment(streamId, segmentName) ?? getAudioSegment(streamId, segmentName) ?? getNamedInitSegment(streamId, segmentName);
|
|
263
291
|
if (!segment) {
|
|
264
292
|
res.status(404).send("Segment not found.");
|
|
265
293
|
return;
|
|
@@ -307,13 +335,13 @@ export function handleHLSVariantPlaylist(req, res) {
|
|
|
307
335
|
}
|
|
308
336
|
// Ad-Hoc Streaming.
|
|
309
337
|
/**
|
|
310
|
-
* Handles ad-hoc stream requests for arbitrary URLs. Generates a deterministic synthetic key from the URL
|
|
311
|
-
* to the standard HLS playlist path. This enables streaming URLs that are not predefined as channels.
|
|
338
|
+
* Handles ad-hoc stream requests for arbitrary URLs. Generates a deterministic synthetic key from the URL, profile, selector, clickToPlay, and clickSelector, starts
|
|
339
|
+
* a stream if none exists, and redirects to the standard HLS playlist path. This enables streaming URLs that are not predefined as channels.
|
|
312
340
|
*
|
|
313
|
-
* The synthetic key includes the profile so that the same URL with different
|
|
314
|
-
* predefined channel names.
|
|
341
|
+
* The synthetic key includes the profile, selector, clickToPlay, and clickSelector so that the same URL with different options produces separate streams. The
|
|
342
|
+
* "play-" prefix prevents collisions with predefined channel names.
|
|
315
343
|
*
|
|
316
|
-
* Route: GET /play?url=<url>&profile=<name>
|
|
344
|
+
* Route: GET /play?url=<url>&profile=<name>&selector=<selector>&clickToPlay=<bool>&clickSelector=<selector>
|
|
317
345
|
*
|
|
318
346
|
* @param req - Express request object.
|
|
319
347
|
* @param res - Express response object.
|
|
@@ -342,6 +370,18 @@ export async function handlePlayStream(req, res) {
|
|
|
342
370
|
res.status(503).json(LOGIN_MODE_BODY);
|
|
343
371
|
return;
|
|
344
372
|
}
|
|
373
|
+
/* Reject a URL that cannot work before anything is reserved on its behalf. initializeStream takes a concurrent-stream slot and registers a pending entry
|
|
374
|
+
* before the deeper validation inside setupStream runs, so a URL destined to be refused would otherwise hold capacity a real stream could need, and would
|
|
375
|
+
* reach the redirect probe that issues an outbound request on its behalf first. The placement is deliberate in both directions: ahead of initializeStream so
|
|
376
|
+
* nothing is reserved, and behind the login-mode check so a URL submitted during login still answers 503 rather than 400. The same validator also runs inside
|
|
377
|
+
* setupStream, which is where channel-based streams meet it; one shared rule called from each entry point, not a second copy of the rule.
|
|
378
|
+
*/
|
|
379
|
+
const validation = validateStreamUrl(url);
|
|
380
|
+
if (!validation.valid) {
|
|
381
|
+
LOG.error("Invalid URL requested: %s - %s.", url, validation.reason ?? "Unknown error");
|
|
382
|
+
res.status(400).send(validation.reason ?? "Invalid URL.");
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
345
385
|
// Capture client IP for Channels DVR API integration.
|
|
346
386
|
const clientAddress = req.ip ?? req.socket.remoteAddress ?? null;
|
|
347
387
|
// Start a new ad-hoc stream. initializeStream handles pending registration, capture setup, segmenter creation, and event emission.
|
|
@@ -381,7 +421,7 @@ async function sendPlaylistResponse(streamId, clientAddress, res) {
|
|
|
381
421
|
// The playlist may not be populated yet if the deferred preroll timer hasn't fired and the segmenter hasn't produced content. Wait for the playlistReady promise
|
|
382
422
|
// which resolves when either source provides a playlist. The 30-second timeout covers pathological cases like setup hanging.
|
|
383
423
|
if (!playlist) {
|
|
384
|
-
const ready = await waitForPlaylist(streamId,
|
|
424
|
+
const ready = await waitForPlaylist(streamId, 30000);
|
|
385
425
|
if (ready) {
|
|
386
426
|
playlist = getPlaylist(streamId);
|
|
387
427
|
}
|
|
@@ -431,18 +471,6 @@ function sendSegment(data, segmentName, res) {
|
|
|
431
471
|
res.send(data);
|
|
432
472
|
}
|
|
433
473
|
// Stream Lifecycle.
|
|
434
|
-
/**
|
|
435
|
-
* Cleans up resources from a stream that was terminated during setup before the segmenter was stored in the registry. This handles the rare race condition where
|
|
436
|
-
* another code path (e.g., circuit breaker) calls terminateStream() between registerStream() and the segmenter assignment.
|
|
437
|
-
*
|
|
438
|
-
* terminateStream() already cleaned up the page, monitor, channel mapping, and registry entry. We only need to stop the segmenter, which was created after
|
|
439
|
-
* termination occurred and therefore wasn't cleaned up.
|
|
440
|
-
* @param segmenter - The orphaned fMP4 segmenter instance to stop.
|
|
441
|
-
*/
|
|
442
|
-
function cleanupOrphanedSetup(segmenter) {
|
|
443
|
-
LOG.debug("streaming:setup", "Stream was terminated during setup. Stopping orphaned segmenter.");
|
|
444
|
-
segmenter.stop();
|
|
445
|
-
}
|
|
446
474
|
// Maps vertical resolution heights to standard display labels. Used by formatNativeQuality() to convert "1920x1080" -> "1080p".
|
|
447
475
|
const RESOLUTION_LABELS = { "1080": "1080p", "2160": "4K", "360": "360p", "480": "480p", "720": "720p" };
|
|
448
476
|
/**
|
|
@@ -506,26 +534,18 @@ function createTabReplacementHandler(numericStreamId, streamId, channelName, url
|
|
|
506
534
|
// Get the current init segment, segment index, and per-track timestamps from the old segmenter before stopping it. The init segment enables discontinuity
|
|
507
535
|
// suppression when codec parameters are unchanged, the segment index allows the new segmenter to continue numbering, and the track timestamps ensure monotonic
|
|
508
536
|
// baseMediaDecodeTime across capture restarts.
|
|
509
|
-
const
|
|
510
|
-
const
|
|
511
|
-
const
|
|
512
|
-
const
|
|
513
|
-
const
|
|
514
|
-
|
|
515
|
-
//
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
if (stream.segmenter) {
|
|
522
|
-
LOG.debug("recovery:tab", "Stopping current segmenter for tab replacement.");
|
|
523
|
-
stream.segmenter.stop();
|
|
524
|
-
}
|
|
525
|
-
// Stop the FFmpeg process if it exists.
|
|
526
|
-
if (stream.ffmpegProcess) {
|
|
527
|
-
LOG.debug("recovery:tab", "Stopping FFmpeg process for tab replacement.");
|
|
528
|
-
stream.ffmpegProcess.kill();
|
|
537
|
+
const oldSegmenter = stream.captureSession?.segmenter;
|
|
538
|
+
const currentInitSegment = oldSegmenter?.getInitSegment();
|
|
539
|
+
const currentInitVersion = oldSegmenter?.getInitVersion() ?? 0;
|
|
540
|
+
const currentSegmentIndex = oldSegmenter?.getSegmentIndex() ?? 0;
|
|
541
|
+
const currentSessionStats = oldSegmenter?.getSessionStats();
|
|
542
|
+
const currentTrackTimestamps = oldSegmenter?.getTrackTimestamps();
|
|
543
|
+
// Dispose the OLD capture pipeline. The CaptureSession kills the FFmpeg child first (setting its shuttingDown flag before the capture stream's EOF can reach
|
|
544
|
+
// FFmpeg's stdin), then destroys the capture stream (which MUST happen before the old page is closed below, so chrome.tabCapture releases the capture and the
|
|
545
|
+
// new getStream() does not hang with "Cannot capture a tab with an active stream"), then stops the segmenter. The new pipeline is constructed fresh further down.
|
|
546
|
+
if (stream.captureSession) {
|
|
547
|
+
LOG.debug("recovery:tab", "Disposing old capture pipeline for tab replacement.");
|
|
548
|
+
stream.captureSession.dispose();
|
|
529
549
|
}
|
|
530
550
|
// Close the current page. The page may be null for pending entries whose async setup has not yet completed.
|
|
531
551
|
const oldPage = stream.page;
|
|
@@ -544,6 +564,7 @@ function createTabReplacementHandler(numericStreamId, streamId, channelName, url
|
|
|
544
564
|
try {
|
|
545
565
|
captureResult = await createPageWithCapture({
|
|
546
566
|
comment: metadataComment,
|
|
567
|
+
numericStreamId,
|
|
547
568
|
onFFmpegError: (error) => {
|
|
548
569
|
LOG.error("FFmpeg error during tab replacement recovery: %s.", formatError(error));
|
|
549
570
|
onCircuitBreak();
|
|
@@ -559,6 +580,21 @@ function createTabReplacementHandler(numericStreamId, streamId, channelName, url
|
|
|
559
580
|
return null;
|
|
560
581
|
}
|
|
561
582
|
LOG.debug("timing:tab", "New page with capture created. (+%sms)", tabElapsed());
|
|
583
|
+
// Re-check the stream before adopting the fresh resources. The awaited createPageWithCapture above yields the event loop, so the stream can terminate mid-await;
|
|
584
|
+
// the entry writes below would then attach the new page and capture session to an entry that termination already disposed, orphaning both. Tear the fresh resources
|
|
585
|
+
// down in the same order the old-pipeline teardown above uses - dispose the capture session first so chrome.tabCapture releases cleanly, then unregister the
|
|
586
|
+
// managed page, then close it - and abandon the replacement.
|
|
587
|
+
if (!getStream(numericStreamId)) {
|
|
588
|
+
captureResult.captureSession.dispose();
|
|
589
|
+
unregisterManagedPage(captureResult.page);
|
|
590
|
+
if (!captureResult.page.isClosed()) {
|
|
591
|
+
captureResult.page.close().catch((error) => {
|
|
592
|
+
LOG.debug("recovery:tab", "Page close error during aborted tab replacement: %s.", formatError(error));
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
LOG.debug("recovery:tab", "Stream %s terminated during tab replacement - discarded the fresh page and capture session.", streamId);
|
|
596
|
+
return null;
|
|
597
|
+
}
|
|
562
598
|
// Create a new segmenter for the new capture stream. Continue from the current segment index for playlist continuity, pass the per-track timestamp counters
|
|
563
599
|
// for monotonic baseMediaDecodeTime, and mark the first segment with a discontinuity tag so clients know the stream parameters may have changed.
|
|
564
600
|
const newSegmenter = createFMP4Segmenter({
|
|
@@ -571,13 +607,11 @@ function createTabReplacementHandler(numericStreamId, streamId, channelName, url
|
|
|
571
607
|
startingSegmentIndex: currentSegmentIndex,
|
|
572
608
|
streamId: numericStreamId
|
|
573
609
|
});
|
|
574
|
-
//
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
stream.
|
|
610
|
+
// Wire the new segmenter into the new capture session (attachSegmenter pipes the session's capture output into it), then install the session and page on the
|
|
611
|
+
// registry entry.
|
|
612
|
+
captureResult.captureSession.attachSegmenter(newSegmenter);
|
|
613
|
+
stream.captureSession = captureResult.captureSession;
|
|
578
614
|
stream.page = captureResult.page;
|
|
579
|
-
stream.rawCaptureStream = captureResult.rawCaptureStream;
|
|
580
|
-
stream.segmenter = newSegmenter;
|
|
581
615
|
LOG.info("Tab replacement complete. New capture started with segment continuity.");
|
|
582
616
|
LOG.debug("timing:tab", "Tab replacement complete. Total: %sms.", tabElapsed());
|
|
583
617
|
return {
|
|
@@ -598,6 +632,13 @@ function createTabReplacementHandler(numericStreamId, streamId, channelName, url
|
|
|
598
632
|
*/
|
|
599
633
|
export async function initializeStream(options) {
|
|
600
634
|
const { channel, channelName, url } = options;
|
|
635
|
+
// Reserve a capacity slot before registering the pending entry. This is the same single-source-of-truth gate used by the preroll path, applied here so the
|
|
636
|
+
// blocking callers (pretune, MPEG-TS, ad-hoc play) gate capacity at the registration site - while the new stream is still excluded from the count - rather than
|
|
637
|
+
// relying on a downstream self-counting check that would double-count this stream against its own slot and spuriously reject the final slot. On failure we throw
|
|
638
|
+
// the same StreamSetupError(503) the callers already handle, so their error responses and HDHomeRun headers are unchanged.
|
|
639
|
+
if (!reserveStreamSlot()) {
|
|
640
|
+
throw new StreamSetupError("Concurrent stream limit reached.", 503, "Maximum concurrent streams (" + String(CONFIG.streaming.maxConcurrentStreams) + ") reached. Try again later.");
|
|
641
|
+
}
|
|
601
642
|
// Allocate stream IDs. For predefined channels, use the channel name for the stream ID prefix. For ad-hoc streams, omit it so generateStreamId derives a prefix
|
|
602
643
|
// from the URL (e.g., "foxsports-abc123"), which is more informative in logs.
|
|
603
644
|
const numericStreamId = getNextStreamId();
|
|
@@ -636,7 +677,8 @@ function registerPendingStream(channelName, channel, clientAddress, req, codec)
|
|
|
636
677
|
const baseUrl = protocol + "://" + (host ?? fallbackHost);
|
|
637
678
|
// Create HLS state with a deferred preroll timer. The timer fires after PREROLL_DELAY_MS - if stream setup hasn't completed by then, preroll is seeded and the
|
|
638
679
|
// playlist response is unblocked. For resume streams, the preroll's MEDIA-SEQUENCE is offset by the saved segment index so it continues from the prior session's
|
|
639
|
-
// sequence range. The timer is
|
|
680
|
+
// sequence range. The timer is disarmed through cancelPrerollTimer() once real content arrives or preroll state is otherwise invalidated, so it cannot fire against
|
|
681
|
+
// state that has moved on.
|
|
640
682
|
const hls = createHLSState();
|
|
641
683
|
// Snapshot the resume segment index once at registration. Both the preroll timer callback and completeStreamSetup() use this single snapshot, eliminating the TTL
|
|
642
684
|
// race that would occur if each read the resume map independently at different times.
|
|
@@ -673,9 +715,9 @@ function createPendingEntry(options) {
|
|
|
673
715
|
const { channel, channelName, hls, numericStreamId, streamIdStr, url } = options;
|
|
674
716
|
registerStream({
|
|
675
717
|
captureCodec: null,
|
|
718
|
+
captureSession: null,
|
|
676
719
|
channelName: channel?.name ?? null,
|
|
677
720
|
clientAddress: options.clientAddress ?? null,
|
|
678
|
-
ffmpegProcess: null,
|
|
679
721
|
hardwareAccelerated: false,
|
|
680
722
|
hls,
|
|
681
723
|
id: numericStreamId,
|
|
@@ -683,17 +725,18 @@ function createPendingEntry(options) {
|
|
|
683
725
|
lastPlaylistRequest: Date.now(),
|
|
684
726
|
storeKey: channelName
|
|
685
727
|
},
|
|
728
|
+
monitor: null,
|
|
686
729
|
mpegTsClientCount: 0,
|
|
687
730
|
nativeBandwidth: 0,
|
|
731
|
+
nativeContainer: null,
|
|
688
732
|
nativeProxy: null,
|
|
689
733
|
nativeResolution: null,
|
|
690
734
|
page: null,
|
|
691
735
|
preTuned: options.preTuned ?? false,
|
|
736
|
+
probeIdentity: null,
|
|
692
737
|
profile: null,
|
|
693
|
-
|
|
694
|
-
segmenter: null,
|
|
738
|
+
reestablishManifest: null,
|
|
695
739
|
startTime: options.streamStartTime ?? new Date(),
|
|
696
|
-
stopMonitor: null,
|
|
697
740
|
streamIdStr,
|
|
698
741
|
streamingMode: "capture",
|
|
699
742
|
url
|
|
@@ -730,8 +773,9 @@ function handleSetupFailure(numericStreamId, channelName, channel, error, logErr
|
|
|
730
773
|
}
|
|
731
774
|
}
|
|
732
775
|
/**
|
|
733
|
-
* Attempts to upgrade the stream from capture to native HLS.
|
|
734
|
-
* pipeline, suppresses page audio, and updates the registry entry. Returns the codec and formatted quality string on success, or
|
|
776
|
+
* Attempts to upgrade the stream from capture to native HLS. Consumes the manifest interception already finalized by setupStream(), probes the manifest, creates a
|
|
777
|
+
* native proxy, stops the capture pipeline, suppresses page audio, and updates the registry entry. Returns the codec and formatted quality string on success, or
|
|
778
|
+
* null if native is not viable.
|
|
735
779
|
* @param setup - The stream setup result from setupStream().
|
|
736
780
|
* @param numericStreamId - The stream's numeric ID.
|
|
737
781
|
* @param channelName - The channel key for logging and cache operations.
|
|
@@ -750,6 +794,12 @@ async function startNativeProxy(setup, numericStreamId, channelName, url, mpegTs
|
|
|
750
794
|
// is read dynamically from the stream's HLS state at playlist generation time.
|
|
751
795
|
const pendingForNative = getStream(numericStreamId);
|
|
752
796
|
const nativePrerollSegmentCount = pendingForNative?.hls.prerollSegmentCount ?? 0;
|
|
797
|
+
// The re-establishment capability closes over the stream's own tune facts here, in the layer that owns them: a token-refresh reload re-acquires its
|
|
798
|
+
// manifest through the stream's real tune rather than a bare navigation. The page stays a parameter rather than a captured value because the callers
|
|
799
|
+
// own the page reference - the monitor hands its own current page to every recovery action it drives.
|
|
800
|
+
const reestablishManifest = (page) => {
|
|
801
|
+
return reestablishChannelManifest({ channelName: setup.channelName, page, profile: setup.profile, streamIdStr: setup.streamId, url });
|
|
802
|
+
};
|
|
753
803
|
const nativeResult = await attemptNativeStreaming({
|
|
754
804
|
channelName,
|
|
755
805
|
interceptionPromise: setup.manifestInterception.promise,
|
|
@@ -763,9 +813,23 @@ async function startNativeProxy(setup, numericStreamId, channelName, url, mpegTs
|
|
|
763
813
|
LOG.warn("Native proxy error for %s: %s. Falling back to capture.", channelName, error);
|
|
764
814
|
clearProbeCache(channelName);
|
|
765
815
|
},
|
|
816
|
+
onFeedApplied: (metadata) => {
|
|
817
|
+
// The registry write stays in the streaming layer, exactly as the error report above does: native reports upward and the layer that owns the entry is the
|
|
818
|
+
// layer that writes it. A refresh can land on a different rung of the service's ladder, so the status display reads the quality the stream is actually
|
|
819
|
+
// serving rather than the one it tuned to. An entry that is already gone means the stream is terminating, which is nothing to record.
|
|
820
|
+
const refreshed = getStream(numericStreamId);
|
|
821
|
+
if (!refreshed) {
|
|
822
|
+
return;
|
|
823
|
+
}
|
|
824
|
+
refreshed.captureCodec = metadata.codec;
|
|
825
|
+
refreshed.nativeBandwidth = metadata.bandwidth;
|
|
826
|
+
refreshed.nativeResolution = metadata.resolution;
|
|
827
|
+
},
|
|
766
828
|
page: setup.page,
|
|
767
829
|
prerollCodec: pendingForNative?.hls.prerollCodec ?? "h264",
|
|
768
830
|
prerollSegmentCount: nativePrerollSegmentCount,
|
|
831
|
+
probeIdentity: setup.probeIdentity,
|
|
832
|
+
reestablishManifest,
|
|
769
833
|
streamId: numericStreamId,
|
|
770
834
|
streamIdStr: setup.streamId,
|
|
771
835
|
url
|
|
@@ -778,18 +842,25 @@ async function startNativeProxy(setup, numericStreamId, channelName, url, mpegTs
|
|
|
778
842
|
nativeResult.proxy.stop();
|
|
779
843
|
return null;
|
|
780
844
|
}
|
|
781
|
-
//
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
845
|
+
// Dispose the capture pipeline - native streaming replaces it entirely. No segmenter has been attached yet (native is attempted before createCaptureSegmenter), so
|
|
846
|
+
// disposal kills the FFmpeg child and destroys the capture stream.
|
|
847
|
+
setup.captureSession.dispose();
|
|
848
|
+
// Consume the persisted capture-mode resume data for this channel. Resume data seeds an fMP4 segmenter's starting sequence and init segment for capture continuity
|
|
849
|
+
// across restarts; createCaptureSegmenter consumes it on the capture path. On this native-upgrade path that segmenter is never created, so without this delete the
|
|
850
|
+
// resume entry would linger until TTL and could be mis-applied to a later capture-mode stream for the same channel. Native streaming carries its own MPEG-TS
|
|
851
|
+
// sequencing, so the capture resume state is obsolete the moment we commit to native.
|
|
852
|
+
deleteResumeData(channelName);
|
|
788
853
|
// Update the registry entry to reflect native mode. For streams with separate audio, clear preroll state - preroll is muxed video+audio and can't be
|
|
789
854
|
// split into separate renditions. For muxed-audio streams, preserve preroll state so the proxy can build composite playlists with preroll entries.
|
|
790
|
-
|
|
855
|
+
//
|
|
856
|
+
// The separate-audio path disarms the preroll timer alongside nulling the fields. The timer's callback closed over its own base-URL and codec copies at
|
|
857
|
+
// registration, so nulling the fields silences only sendPlaylistResponse's per-poll regeneration, not the one-shot overwrite the callback would still perform when
|
|
858
|
+
// it fires. Disarming here completes the invalidation. One residual is inherent to preroll-for-hasAudio: if the native tune already exceeded PREROLL_DELAY_MS the
|
|
859
|
+
// timer has already fired, so the nulled fields freeze the already-written preroll playlist until the proxy's first real content arrives - bounded, not a stall.
|
|
860
|
+
currentStream.captureSession = null;
|
|
791
861
|
currentStream.hls.hasAudio = nativeResult.hasAudio;
|
|
792
862
|
if (nativeResult.hasAudio) {
|
|
863
|
+
cancelPrerollTimer(currentStream.hls);
|
|
793
864
|
currentStream.hls.prerollBaseUrl = null;
|
|
794
865
|
currentStream.hls.prerollCodec = null;
|
|
795
866
|
currentStream.hls.prerollSegmentCount = 0;
|
|
@@ -797,15 +868,20 @@ async function startNativeProxy(setup, numericStreamId, channelName, url, mpegTs
|
|
|
797
868
|
currentStream.captureCodec = nativeResult.codec;
|
|
798
869
|
currentStream.hardwareAccelerated = false;
|
|
799
870
|
currentStream.nativeBandwidth = nativeResult.bandwidth;
|
|
871
|
+
currentStream.nativeContainer = nativeResult.container;
|
|
800
872
|
currentStream.nativeProxy = nativeResult.proxy;
|
|
801
873
|
currentStream.nativeResolution = nativeResult.resolution;
|
|
802
|
-
currentStream.
|
|
874
|
+
currentStream.reestablishManifest = reestablishManifest;
|
|
803
875
|
currentStream.streamingMode = "native";
|
|
804
|
-
// Start the native proxy.
|
|
805
|
-
//
|
|
806
|
-
// first
|
|
876
|
+
// Start the native proxy. Init readiness is conditional on the container. An MPEG-TS source carries its own PAT/PMT codec configuration in every segment and
|
|
877
|
+
// has no separate init to wait for, so readiness is signaled here and MPEG-TS clients are released immediately rather than blocking on waitForInitSegment()
|
|
878
|
+
// until it times out. An fMP4 source does have an init, and the store signals readiness when the video track's first one lands - video because the consumer
|
|
879
|
+
// waiting on this signal is the remux path, which resolves the video init. A null container reaches this only on a feed the DRM path abandoned, so it takes
|
|
880
|
+
// the immediate signal rather than deadlocking readiness on an init that will never arrive.
|
|
807
881
|
nativeResult.proxy.start();
|
|
808
|
-
|
|
882
|
+
if (nativeResult.container !== "fmp4") {
|
|
883
|
+
currentStream.hls.signalInitSegmentReady();
|
|
884
|
+
}
|
|
809
885
|
// Suppress audio on the browser page. The page stays alive for token refresh but the video element's audio is not part of the native stream - without
|
|
810
886
|
// suppression, it plays audibly on the local machine.
|
|
811
887
|
await suppressPageAudio(setup.page);
|
|
@@ -813,12 +889,13 @@ async function startNativeProxy(setup, numericStreamId, channelName, url, mpegTs
|
|
|
813
889
|
return { codec: nativeResult.codec, quality: formatNativeQuality(nativeResult.bandwidth, nativeResult.codec, nativeResult.resolution) };
|
|
814
890
|
}
|
|
815
891
|
/**
|
|
816
|
-
* Creates the fMP4 segmenter for capture mode streams. Reads resume data, creates the segmenter with preroll and resume configuration,
|
|
817
|
-
*
|
|
892
|
+
* Creates the fMP4 segmenter for capture mode streams. Reads resume data, creates the segmenter with preroll and resume configuration, and attaches it to the
|
|
893
|
+
* capture session (which pipes the session's capture output into it). Resume data is consumed only after the segmenter is successfully attached to a non-disposed
|
|
894
|
+
* session, ensuring it survives if the stream was terminated during setup.
|
|
818
895
|
* @param setup - The stream setup result from setupStream().
|
|
819
896
|
* @param numericStreamId - The stream's numeric ID.
|
|
820
897
|
* @param channelName - The channel key for resume data and logging.
|
|
821
|
-
* @returns True if the segmenter was
|
|
898
|
+
* @returns True if the segmenter was attached, false if the stream was terminated during setup (the session was already disposed).
|
|
822
899
|
*/
|
|
823
900
|
function createCaptureSegmenter(setup, numericStreamId, channelName) {
|
|
824
901
|
// Peek at resume data from a previous shutdown without consuming it. The data is consumed (deleted) only after the segmenter is successfully created and
|
|
@@ -852,25 +929,25 @@ function createCaptureSegmenter(setup, numericStreamId, channelName) {
|
|
|
852
929
|
...buildSegmenterTerminationHandlers(numericStreamId, channelName),
|
|
853
930
|
streamId: numericStreamId
|
|
854
931
|
});
|
|
855
|
-
//
|
|
856
|
-
segmenter.
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
932
|
+
// Attach the segmenter to the capture session. attachSegmenter pipes the session's capture output into the segmenter. If the stream was terminated mid-setup, the
|
|
933
|
+
// session is already disposed and attachSegmenter stops the orphan segmenter instead of wiring it - the single home for that rare-race cleanup.
|
|
934
|
+
setup.captureSession.attachSegmenter(segmenter);
|
|
935
|
+
if (setup.captureSession.disposed) {
|
|
936
|
+
// The stream was terminated during setup (rare race). attachSegmenter already stopped the orphan; leave the resume data intact so the next stream start for this
|
|
937
|
+
// channel can retry with it rather than losing it to an HLS sequence reset.
|
|
938
|
+
return false;
|
|
939
|
+
}
|
|
940
|
+
// The segmenter is created, piped, and owned by the session. Consume the resume data so it is not reused on a subsequent stream start for the same channel.
|
|
941
|
+
if (resumeData) {
|
|
942
|
+
deleteResumeData(channelName);
|
|
866
943
|
}
|
|
867
|
-
|
|
868
|
-
cleanupOrphanedSetup(segmenter);
|
|
869
|
-
return false;
|
|
944
|
+
return true;
|
|
870
945
|
}
|
|
871
946
|
/**
|
|
872
947
|
* Completes the async portion of stream initialization. Creates the browser page, navigates to the URL, sets up capture, creates the segmenter, and fills in the
|
|
873
|
-
* pending registry entry.
|
|
948
|
+
* pending registry entry. If the pending entry was terminated during setup (e.g., idle timeout) before this function reaches it, it releases the now-orphaned
|
|
949
|
+
* setup resources via setup.cleanup() and returns null. On a thrown error, it does not clean up the pending entry itself - callers reach handleSetupFailure(), which
|
|
950
|
+
* terminates the pending entry via terminateStream().
|
|
874
951
|
*
|
|
875
952
|
* This is the Phase 2 of the two-phase stream initialization. For the non-blocking HLS path, it runs as fire-and-forget via `void`. For the blocking path
|
|
876
953
|
* (initializeStream), it is awaited directly.
|
|
@@ -881,11 +958,31 @@ function createCaptureSegmenter(setup, numericStreamId, channelName) {
|
|
|
881
958
|
*/
|
|
882
959
|
async function completeStreamSetup(options) {
|
|
883
960
|
const { channel, channelName, channelSelector, clickSelector, clickToPlay, mpegTsClient, numericStreamId, profileOverride, streamIdStr, url } = options;
|
|
884
|
-
|
|
961
|
+
/* Build the probe-cache identity once, here, and hand it onward as a value. This is the only frame that holds both halves of it on every entry path: the true
|
|
962
|
+
* per-stream key (channelName is the route key for a predefined tune, the binding hash for an ad-hoc /play, the channel id for a pretune) and the binding
|
|
963
|
+
* inputs the stamp is derived from. Nothing downstream may build one - setupStream's own channelName is deliberately undefined for ad-hoc streams, and a
|
|
964
|
+
* stamp assembled from a frame holding only part of the binding would silently describe a different stream than the one the tune reaches.
|
|
965
|
+
*
|
|
966
|
+
* The effective profile is the query override when one was supplied, otherwise the stored profile: an overridden tune reaches a different stream than the
|
|
967
|
+
* stored profile would, so the stamp honestly differs and an entry cached under the stored profile re-probes once under the override rather than answering
|
|
968
|
+
* for a page it never described.
|
|
969
|
+
*/
|
|
970
|
+
const probeIdentity = {
|
|
971
|
+
key: channelName,
|
|
972
|
+
stamp: buildProbeCacheStamp(channel ? { channelSelector: channel.channelSelector, profile: profileOverride ?? channel.profile, url } :
|
|
973
|
+
{ channelSelector, profile: profileOverride, url })
|
|
974
|
+
};
|
|
975
|
+
/* Circuit breaker callback - terminates this stream on unrecoverable errors, by the id the closure was built for. Resolving the id through the channel index
|
|
976
|
+
* at fire time could reach a different stream: a channel re-tuned while this callback was in flight is registered under the same name with a newer id, and the
|
|
977
|
+
* breaker's verdict belongs to the stream whose monitor reached it.
|
|
978
|
+
*
|
|
979
|
+
* The registry-presence guard is what makes a repeated or late invocation silent. terminateStream's own guard covers only a termination already underway - its
|
|
980
|
+
* tracking entry is removed when that termination completes - so a second call from an independent site (an FFmpeg error handler firing after the monitor
|
|
981
|
+
* already terminated the stream) would otherwise log a second termination summary and re-emit the stream-removed event for a stream long gone.
|
|
982
|
+
*/
|
|
885
983
|
const onCircuitBreak = () => {
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
terminateStream(currentStreamId, channelName, "too many errors");
|
|
984
|
+
if (getStream(numericStreamId)) {
|
|
985
|
+
terminateStream(numericStreamId, channelName, "too many errors");
|
|
889
986
|
void emitCurrentSystemStatus();
|
|
890
987
|
}
|
|
891
988
|
};
|
|
@@ -893,10 +990,10 @@ async function completeStreamSetup(options) {
|
|
|
893
990
|
const tabReplacementFactory = (_, streamId, profile, metadataComment) => {
|
|
894
991
|
return createTabReplacementHandler(numericStreamId, streamId, channelName, url, profile, metadataComment, onCircuitBreak);
|
|
895
992
|
};
|
|
896
|
-
//
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
993
|
+
// Capacity was already reserved at the registration site (reserveStreamSlot in ensureChannelStream for the preroll path, and in initializeStream for the blocking
|
|
994
|
+
// path) before this stream's pending entry was registered, so the new stream is excluded from its own capacity check. We deliberately do NOT re-check or reclaim
|
|
995
|
+
// here: the pending entry is now counted, so a count-based check would double-count this stream against its own slot and could evict a healthy peer at the
|
|
996
|
+
// legitimate boundary. The registration-site reservation is the single source of truth for the capacity decision.
|
|
900
997
|
// Pass the pre-allocated IDs to setupStream so it uses them instead of generating new ones. This ensures the abort controller, health monitor, and tab replacement
|
|
901
998
|
// handler all reference the same stream identity as the pending registry entry. Pass channelName only for predefined channels - for ad-hoc streams, omitting it
|
|
902
999
|
// causes generateStreamId to derive the stream ID string from the URL (e.g., "foxsports-abc123"), which is more informative in logs.
|
|
@@ -908,6 +1005,7 @@ async function completeStreamSetup(options) {
|
|
|
908
1005
|
clickToPlay: channel ? undefined : clickToPlay,
|
|
909
1006
|
numericStreamId,
|
|
910
1007
|
onTabReplacementFactory: tabReplacementFactory,
|
|
1008
|
+
probeIdentity,
|
|
911
1009
|
profileOverride,
|
|
912
1010
|
streamId: streamIdStr,
|
|
913
1011
|
url
|
|
@@ -921,12 +1019,12 @@ async function completeStreamSetup(options) {
|
|
|
921
1019
|
});
|
|
922
1020
|
return null;
|
|
923
1021
|
}
|
|
924
|
-
stream.
|
|
1022
|
+
stream.captureSession = setup.captureSession;
|
|
1023
|
+
stream.monitor = setup.monitor;
|
|
925
1024
|
stream.page = setup.page;
|
|
1025
|
+
stream.probeIdentity = setup.probeIdentity;
|
|
926
1026
|
stream.profile = setup.profile;
|
|
927
|
-
stream.rawCaptureStream = setup.rawCaptureStream;
|
|
928
1027
|
stream.startTime = setup.startTime;
|
|
929
|
-
stream.stopMonitor = setup.stopMonitor;
|
|
930
1028
|
stream.url = setup.url;
|
|
931
1029
|
// Continue within stream context for consistent logging.
|
|
932
1030
|
return runWithStreamContext({ channelName: channel?.name, streamId: setup.streamId, url: setup.url }, async () => {
|
|
@@ -953,6 +1051,8 @@ async function completeStreamSetup(options) {
|
|
|
953
1051
|
}
|
|
954
1052
|
const effectiveCodec = getEffectiveCaptureCodec();
|
|
955
1053
|
const captureHwAccel = isCaptureHardwareAccelerated();
|
|
1054
|
+
// Prefix the codec label with U+26A1, the high-voltage / lightning-bolt glyph, as a visual marker in the "Streaming..." log line that capture is
|
|
1055
|
+
// hardware-accelerated. The glyph is written as an escape so the source stays ASCII; it renders as the lightning bolt in the log output.
|
|
956
1056
|
const ffmpegCodec = captureHwAccel ? ("\u26A1 " + effectiveCodec.toUpperCase()) : effectiveCodec.toUpperCase();
|
|
957
1057
|
const captureMode = (streamingMode === "native") ? ("native HLS" + nativeQuality) :
|
|
958
1058
|
(CONFIG.streaming.captureMode === "ffmpeg" ? "FFmpeg [" + ffmpegCodec + "]" : "Native fMP4");
|