prismcast 1.0.1 → 1.0.3
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 -0
- package/dist/app.js +292 -0
- package/dist/app.js.map +1 -0
- package/dist/browser/cdp.d.ts +38 -0
- package/dist/browser/cdp.js +158 -0
- package/dist/browser/cdp.js.map +1 -0
- package/dist/browser/channelSelection.d.ts +16 -0
- package/dist/browser/channelSelection.js +178 -0
- package/dist/browser/channelSelection.js.map +1 -0
- package/dist/browser/display.d.ts +33 -0
- package/{src/browser/display.ts → dist/browser/display.js} +14 -26
- package/dist/browser/display.js.map +1 -0
- package/dist/browser/index.d.ts +171 -0
- package/dist/browser/index.js +953 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/browser/video.d.ts +240 -0
- package/dist/browser/video.js +781 -0
- package/dist/browser/video.js.map +1 -0
- package/dist/channels/index.d.ts +19 -0
- package/dist/channels/index.js +164 -0
- package/dist/channels/index.js.map +1 -0
- package/dist/config/index.d.ts +53 -0
- package/dist/config/index.js +217 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/presets.d.ts +98 -0
- package/dist/config/presets.js +244 -0
- package/dist/config/presets.js.map +1 -0
- package/dist/config/profiles.d.ts +74 -0
- package/dist/config/profiles.js +438 -0
- package/dist/config/profiles.js.map +1 -0
- package/dist/config/userChannels.d.ts +128 -0
- package/dist/config/userChannels.js +356 -0
- package/dist/config/userChannels.js.map +1 -0
- package/dist/config/userConfig.d.ts +215 -0
- package/dist/config/userConfig.js +854 -0
- package/dist/config/userConfig.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +126 -0
- package/dist/index.js.map +1 -0
- package/dist/routes/assets.d.ts +6 -0
- package/dist/routes/assets.js +82 -0
- package/dist/routes/assets.js.map +1 -0
- package/dist/routes/auth.d.ts +6 -0
- package/dist/routes/auth.js +69 -0
- package/dist/routes/auth.js.map +1 -0
- package/dist/routes/components.d.ts +138 -0
- package/dist/routes/components.js +210 -0
- package/dist/routes/components.js.map +1 -0
- package/dist/routes/config.d.ts +66 -0
- package/dist/routes/config.js +1149 -0
- package/dist/routes/config.js.map +1 -0
- package/dist/routes/health.d.ts +6 -0
- package/dist/routes/health.js +71 -0
- package/dist/routes/health.js.map +1 -0
- package/dist/routes/hls.d.ts +6 -0
- package/{src/routes/hls.ts → dist/routes/hls.js} +10 -16
- package/dist/routes/hls.js.map +1 -0
- package/dist/routes/index.d.ts +15 -0
- package/{src/routes/index.ts → dist/routes/index.js} +11 -19
- package/dist/routes/index.js.map +1 -0
- package/dist/routes/logs.d.ts +6 -0
- package/dist/routes/logs.js +161 -0
- package/dist/routes/logs.js.map +1 -0
- package/dist/routes/playlist.d.ts +29 -0
- package/dist/routes/playlist.js +80 -0
- package/dist/routes/playlist.js.map +1 -0
- package/dist/routes/root.d.ts +6 -0
- package/dist/routes/root.js +1720 -0
- package/dist/routes/root.js.map +1 -0
- package/dist/routes/streams.d.ts +6 -0
- package/dist/routes/streams.js +96 -0
- package/dist/routes/streams.js.map +1 -0
- package/dist/routes/theme.d.ts +15 -0
- package/dist/routes/theme.js +271 -0
- package/dist/routes/theme.js.map +1 -0
- package/dist/routes/ui.d.ts +56 -0
- package/dist/routes/ui.js +354 -0
- package/dist/routes/ui.js.map +1 -0
- package/dist/service/commands.d.ts +41 -0
- package/dist/service/commands.js +394 -0
- package/dist/service/commands.js.map +1 -0
- package/dist/service/generators.d.ts +32 -0
- package/dist/service/generators.js +438 -0
- package/dist/service/generators.js.map +1 -0
- package/dist/service/index.d.ts +2 -0
- package/{src/service/index.ts → dist/service/index.js} +1 -0
- package/dist/service/index.js.map +1 -0
- package/dist/streaming/fmp4Segmenter.d.ts +26 -0
- package/dist/streaming/fmp4Segmenter.js +191 -0
- package/dist/streaming/fmp4Segmenter.js.map +1 -0
- package/dist/streaming/hls.d.ts +24 -0
- package/dist/streaming/hls.js +416 -0
- package/dist/streaming/hls.js.map +1 -0
- package/dist/streaming/hlsSegments.d.ts +47 -0
- package/{src/streaming/hlsSegments.ts → dist/streaming/hlsSegments.js} +55 -102
- package/dist/streaming/hlsSegments.js.map +1 -0
- package/dist/streaming/lifecycle.d.ts +33 -0
- package/{src/streaming/lifecycle.ts → dist/streaming/lifecycle.js} +74 -119
- package/dist/streaming/lifecycle.js.map +1 -0
- package/dist/streaming/monitor.d.ts +73 -0
- package/dist/streaming/monitor.js +967 -0
- package/dist/streaming/monitor.js.map +1 -0
- package/dist/streaming/mp4Parser.d.ts +25 -0
- package/dist/streaming/mp4Parser.js +96 -0
- package/dist/streaming/mp4Parser.js.map +1 -0
- package/dist/streaming/registry.d.ts +105 -0
- package/dist/streaming/registry.js +110 -0
- package/dist/streaming/registry.js.map +1 -0
- package/dist/streaming/setup.d.ts +112 -0
- package/dist/streaming/setup.js +413 -0
- package/dist/streaming/setup.js.map +1 -0
- package/dist/streaming/showInfo.d.ts +24 -0
- package/dist/streaming/showInfo.js +320 -0
- package/dist/streaming/showInfo.js.map +1 -0
- package/dist/streaming/statusEmitter.d.ts +118 -0
- package/dist/streaming/statusEmitter.js +143 -0
- package/dist/streaming/statusEmitter.js.map +1 -0
- package/dist/types/index.d.ts +284 -0
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/delay.d.ts +6 -0
- package/{src/utils/delay.ts → dist/utils/delay.js} +5 -7
- package/dist/utils/delay.js.map +1 -0
- package/dist/utils/errors.d.ts +15 -0
- package/{src/utils/errors.ts → dist/utils/errors.js} +13 -23
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/evaluate.d.ts +51 -0
- package/{src/utils/evaluate.ts → dist/utils/evaluate.js} +57 -105
- package/dist/utils/evaluate.js.map +1 -0
- package/dist/utils/ffmpeg.d.ts +40 -0
- package/dist/utils/ffmpeg.js +190 -0
- package/dist/utils/ffmpeg.js.map +1 -0
- package/dist/utils/fileLogger.d.ts +24 -0
- package/dist/utils/fileLogger.js +271 -0
- package/dist/utils/fileLogger.js.map +1 -0
- package/dist/utils/format.d.ts +9 -0
- package/dist/utils/format.js +26 -0
- package/dist/utils/format.js.map +1 -0
- package/dist/utils/html.d.ts +6 -0
- package/{src/utils/html.ts → dist/utils/html.js} +12 -17
- package/dist/utils/html.js.map +1 -0
- package/dist/utils/index.d.ts +12 -0
- package/{src/utils/index.ts → dist/utils/index.js} +1 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/logEmitter.d.ts +16 -0
- package/{src/utils/logEmitter.ts → dist/utils/logEmitter.js} +8 -30
- package/dist/utils/logEmitter.js.map +1 -0
- package/dist/utils/logger.d.ts +81 -0
- package/dist/utils/logger.js +207 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/morganStream.d.ts +7 -0
- package/{src/utils/morganStream.ts → dist/utils/morganStream.js} +18 -30
- package/dist/utils/morganStream.js.map +1 -0
- package/dist/utils/platform.d.ts +63 -0
- package/dist/utils/platform.js +161 -0
- package/dist/utils/platform.js.map +1 -0
- package/dist/utils/retry.d.ts +15 -0
- package/dist/utils/retry.js +85 -0
- package/dist/utils/retry.js.map +1 -0
- package/dist/utils/streamContext.d.ts +28 -0
- package/dist/utils/streamContext.js +33 -0
- package/dist/utils/streamContext.js.map +1 -0
- package/package.json +1 -1
- package/.github/ISSUE_TEMPLATE/config.yml +0 -3
- package/.github/ISSUE_TEMPLATE/feature-request.yml +0 -29
- package/.github/ISSUE_TEMPLATE/support-request.yml +0 -89
- package/.github/workflows/ci.yml +0 -84
- package/.github/workflows/issue-stale.yml +0 -27
- package/.github/workflows/lock-threads.yml +0 -28
- package/Changelog.md +0 -9
- package/eslint.config.mjs +0 -71
- package/prismcast.png +0 -0
- package/prismcast.svg +0 -74
- package/src/app.ts +0 -409
- package/src/browser/cdp.ts +0 -207
- package/src/browser/channelSelection.ts +0 -246
- package/src/browser/index.ts +0 -1254
- package/src/browser/video.ts +0 -1107
- package/src/channels/index.ts +0 -220
- package/src/config/index.ts +0 -297
- package/src/config/presets.ts +0 -390
- package/src/config/profiles.ts +0 -574
- package/src/config/userChannels.ts +0 -557
- package/src/config/userConfig.ts +0 -1310
- package/src/index.ts +0 -174
- package/src/routes/assets.ts +0 -109
- package/src/routes/auth.ts +0 -144
- package/src/routes/components.ts +0 -428
- package/src/routes/config.ts +0 -1711
- package/src/routes/health.ts +0 -99
- package/src/routes/logs.ts +0 -244
- package/src/routes/playlist.ts +0 -105
- package/src/routes/root.ts +0 -1903
- package/src/routes/streams.ts +0 -146
- package/src/routes/theme.ts +0 -310
- package/src/routes/ui.ts +0 -427
- package/src/service/commands.ts +0 -576
- package/src/service/generators.ts +0 -652
- package/src/streaming/fmp4Segmenter.ts +0 -354
- package/src/streaming/hls.ts +0 -599
- package/src/streaming/monitor.ts +0 -1489
- package/src/streaming/mp4Parser.ts +0 -178
- package/src/streaming/registry.ts +0 -276
- package/src/streaming/setup.ts +0 -695
- package/src/streaming/showInfo.ts +0 -546
- package/src/streaming/statusEmitter.ts +0 -258
- package/src/types/global.d.ts +0 -18
- package/src/types/index.ts +0 -724
- package/src/utils/ffmpeg.ts +0 -278
- package/src/utils/fileLogger.ts +0 -366
- package/src/utils/format.ts +0 -32
- package/src/utils/logger.ts +0 -268
- package/src/utils/platform.ts +0 -230
- package/src/utils/retry.ts +0 -118
- package/src/utils/streamContext.ts +0 -68
- package/tsconfig.json +0 -11
package/dist/app.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Initializes and starts the HTTP server. Before accepting connections, we validate configuration, clean up stale Chrome processes, and warm up the browser
|
|
3
|
+
* instance.
|
|
4
|
+
* @param useConsoleLogging - Whether to log to console instead of file. Defaults to false (file logging).
|
|
5
|
+
*/
|
|
6
|
+
export declare function startServer(useConsoleLogging?: boolean): Promise<void>;
|
package/dist/app.js
ADDED
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
/* Copyright(C) 2024-2026, HJD (https://github.com/hjdhjd). All rights reserved.
|
|
2
|
+
*
|
|
3
|
+
* app.ts: Express application builder for PrismCast.
|
|
4
|
+
*/
|
|
5
|
+
import { CONFIG, displayConfiguration, initializeConfiguration, validateConfiguration } from "./config/index.js";
|
|
6
|
+
import { LOG, createMorganStream, formatError, isFFmpegAvailable, setConsoleLogging } from "./utils/index.js";
|
|
7
|
+
import { closeBrowser, ensureDataDirectory, getCurrentBrowser, killStaleChromium, prepareExtension, startStalePageCleanup, stopStalePageCleanup } from "./browser/index.js";
|
|
8
|
+
import { initializeFileLogger, shutdownFileLogger } from "./utils/fileLogger.js";
|
|
9
|
+
import { startShowInfoPolling, stopShowInfoPolling } from "./streaming/showInfo.js";
|
|
10
|
+
import { cleanupIdleStreams } from "./streaming/hls.js";
|
|
11
|
+
import consoleStamp from "console-stamp";
|
|
12
|
+
import express from "express";
|
|
13
|
+
import { getAllStreams } from "./streaming/registry.js";
|
|
14
|
+
import { initializeUserChannels } from "./config/userChannels.js";
|
|
15
|
+
import morgan from "morgan";
|
|
16
|
+
import { setupRoutes } from "./routes/index.js";
|
|
17
|
+
import { terminateStream } from "./streaming/lifecycle.js";
|
|
18
|
+
import { validateProfiles } from "./config/profiles.js";
|
|
19
|
+
/*
|
|
20
|
+
* LOGGING MODE
|
|
21
|
+
*
|
|
22
|
+
* The logging mode is set at startup based on the --console CLI flag. When console logging is enabled, timestamps are added via console-stamp and output goes to
|
|
23
|
+
* stdout/stderr. When file logging is used (the default), output goes to ~/.prismcast/prismcast.log.
|
|
24
|
+
*/
|
|
25
|
+
// Track whether console logging is enabled, set during startServer().
|
|
26
|
+
let usingConsoleLogging = false;
|
|
27
|
+
/*
|
|
28
|
+
* APPLICATION STATE
|
|
29
|
+
*
|
|
30
|
+
* The HTTP server instance is stored globally so it can be closed during graceful shutdown.
|
|
31
|
+
*/
|
|
32
|
+
let server = null;
|
|
33
|
+
// Interval for idle stream cleanup.
|
|
34
|
+
let idleCleanupInterval = null;
|
|
35
|
+
/**
|
|
36
|
+
* Starts the idle cleanup interval. Runs every 10 seconds to check for idle streams and terminate them.
|
|
37
|
+
*/
|
|
38
|
+
function startIdleCleanup() {
|
|
39
|
+
if (idleCleanupInterval) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
// Check for idle streams every 10 seconds.
|
|
43
|
+
idleCleanupInterval = setInterval(() => {
|
|
44
|
+
cleanupIdleStreams();
|
|
45
|
+
}, 10000);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Stops the idle cleanup interval.
|
|
49
|
+
*/
|
|
50
|
+
function stopIdleCleanup() {
|
|
51
|
+
if (idleCleanupInterval) {
|
|
52
|
+
clearInterval(idleCleanupInterval);
|
|
53
|
+
idleCleanupInterval = null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/*
|
|
57
|
+
* GRACEFUL SHUTDOWN
|
|
58
|
+
*
|
|
59
|
+
* When the process receives a termination signal, we close all active streams and the browser before exiting. This ensures resources are released cleanly.
|
|
60
|
+
*/
|
|
61
|
+
/**
|
|
62
|
+
* Sets up signal handlers for graceful shutdown. When SIGINT or SIGTERM is received, we close all streams, the browser, and the HTTP server before exiting.
|
|
63
|
+
*/
|
|
64
|
+
function setupGracefulShutdown() {
|
|
65
|
+
let shutdownInProgress = false;
|
|
66
|
+
async function shutdown(signal) {
|
|
67
|
+
// Prevent multiple shutdown attempts if multiple signals are received.
|
|
68
|
+
if (shutdownInProgress) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
shutdownInProgress = true;
|
|
72
|
+
LOG.info("Received %s, starting graceful shutdown.", signal);
|
|
73
|
+
// Stop cleanup and polling intervals.
|
|
74
|
+
stopStalePageCleanup();
|
|
75
|
+
stopIdleCleanup();
|
|
76
|
+
stopShowInfoPolling();
|
|
77
|
+
// Terminate all streams. terminateStream() handles all cleanup including page closure and registry removal.
|
|
78
|
+
const streams = getAllStreams();
|
|
79
|
+
for (const stream of streams) {
|
|
80
|
+
terminateStream(stream.id, stream.info.storeKey, "server shutdown");
|
|
81
|
+
}
|
|
82
|
+
// Close the browser.
|
|
83
|
+
await closeBrowser();
|
|
84
|
+
// Close the HTTP server.
|
|
85
|
+
try {
|
|
86
|
+
if (server) {
|
|
87
|
+
server.close(() => {
|
|
88
|
+
LOG.info("HTTP server closed successfully.");
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
LOG.error("Error closing server during shutdown: %s.", formatError(error));
|
|
94
|
+
}
|
|
95
|
+
// Shut down file logger if in use.
|
|
96
|
+
if (!usingConsoleLogging) {
|
|
97
|
+
shutdownFileLogger();
|
|
98
|
+
}
|
|
99
|
+
process.exit(0);
|
|
100
|
+
}
|
|
101
|
+
process.on("SIGINT", () => {
|
|
102
|
+
void shutdown("SIGINT");
|
|
103
|
+
});
|
|
104
|
+
process.on("SIGTERM", () => {
|
|
105
|
+
void shutdown("SIGTERM");
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
/*
|
|
109
|
+
* APPLICATION BUILDER
|
|
110
|
+
*
|
|
111
|
+
* The buildApp function creates and configures the Express application with all middleware and routes. This is separated from the server startup to allow for
|
|
112
|
+
* testing and flexibility in deployment.
|
|
113
|
+
*/
|
|
114
|
+
/**
|
|
115
|
+
* Creates and configures the Express application with all middleware and routes.
|
|
116
|
+
* @returns The configured Express application.
|
|
117
|
+
*/
|
|
118
|
+
async function buildApp() {
|
|
119
|
+
try {
|
|
120
|
+
await prepareExtension();
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
LOG.error("Cannot build app without extension: %s.", formatError(error));
|
|
124
|
+
throw error;
|
|
125
|
+
}
|
|
126
|
+
const app = express();
|
|
127
|
+
// Trust proxy headers (X-Forwarded-Proto, X-Forwarded-Host) so that req.protocol and req.hostname reflect what the client actually used when accessing through
|
|
128
|
+
// a reverse proxy. This ensures playlist URLs match the client's connection.
|
|
129
|
+
app.set("trust proxy", true);
|
|
130
|
+
// Add body parsing middleware for form submissions (configuration page).
|
|
131
|
+
app.use(express.urlencoded({ extended: true }));
|
|
132
|
+
app.use(express.json());
|
|
133
|
+
// Configure Morgan for HTTP request logging based on httpLogLevel configuration. Morgan output goes through morganStream which handles timestamp formatting
|
|
134
|
+
// consistently for both console and file logging modes.
|
|
135
|
+
if (CONFIG.logging.httpLogLevel !== "none") {
|
|
136
|
+
const morganFormat = ":method :url from :remote-addr responded :status in :response-time ms.";
|
|
137
|
+
const morganStream = createMorganStream();
|
|
138
|
+
// Patterns for browser-initiated asset requests that return 404. These are noise from browsers automatically requesting files that don't exist.
|
|
139
|
+
const browserAssetPatterns = ["/apple-touch-icon", "/favicon", "/robots.txt", "/site.webmanifest"];
|
|
140
|
+
if (CONFIG.logging.httpLogLevel === "errors") {
|
|
141
|
+
// Log requests with 4xx or 5xx status codes, but skip 404s for common browser asset requests.
|
|
142
|
+
app.use(morgan(morganFormat, {
|
|
143
|
+
skip: (req, res) => {
|
|
144
|
+
// Log all non-error responses.
|
|
145
|
+
if (res.statusCode < 400) {
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
// Skip 404s for browser asset requests (favicon, apple-touch-icon, etc.).
|
|
149
|
+
if (res.statusCode === 404) {
|
|
150
|
+
const url = req.originalUrl || req.url;
|
|
151
|
+
if (browserAssetPatterns.some((pattern) => url.startsWith(pattern))) {
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
// Skip 503s with Retry-After header. These indicate expected temporary unavailability (e.g., stream starting up) rather than a real error.
|
|
156
|
+
if ((res.statusCode === 503) && res.getHeader("Retry-After")) {
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
return false;
|
|
160
|
+
},
|
|
161
|
+
stream: morganStream
|
|
162
|
+
}));
|
|
163
|
+
}
|
|
164
|
+
else if (CONFIG.logging.httpLogLevel === "filtered") {
|
|
165
|
+
// Log important requests while skipping high-frequency polling endpoints. We always log errors, slow requests, and critical endpoints.
|
|
166
|
+
const skipPatterns = ["/logs", "/health", "/favicon", "/logo.png", "/logo.svg"];
|
|
167
|
+
app.use(morgan(morganFormat, {
|
|
168
|
+
skip: (req, res) => {
|
|
169
|
+
// Always log errors.
|
|
170
|
+
if (res.statusCode >= 400) {
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
// Always log slow requests (over 1 second).
|
|
174
|
+
const responseTime = parseFloat(res.getHeader("X-Response-Time") || "0");
|
|
175
|
+
if (responseTime > 1000) {
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
// Always log streaming and management endpoints.
|
|
179
|
+
const url = req.originalUrl || req.url;
|
|
180
|
+
const importantPatterns = ["/stream", "/streams", "/config", "/playlist", "/debug"];
|
|
181
|
+
if (importantPatterns.some((pattern) => url.startsWith(pattern))) {
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
// Skip high-frequency endpoints when successful.
|
|
185
|
+
if (skipPatterns.some((pattern) => url.startsWith(pattern))) {
|
|
186
|
+
return true;
|
|
187
|
+
}
|
|
188
|
+
// Skip successful requests to the root landing page.
|
|
189
|
+
if ((url === "/") && (res.statusCode < 400)) {
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
// Log everything else.
|
|
193
|
+
return false;
|
|
194
|
+
},
|
|
195
|
+
stream: morganStream
|
|
196
|
+
}));
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
// Log all requests.
|
|
200
|
+
app.use(morgan(morganFormat, { stream: morganStream }));
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
// Set up all HTTP endpoints.
|
|
204
|
+
setupRoutes(app);
|
|
205
|
+
// Global error handler. Express error handlers require 4 parameters even if unused.
|
|
206
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
207
|
+
app.use((err, _req, res, _next) => {
|
|
208
|
+
LOG.error("Unhandled error in request: %s.", formatError(err));
|
|
209
|
+
if (!res.headersSent) {
|
|
210
|
+
res.status(500).send("Internal server error");
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
return app;
|
|
214
|
+
}
|
|
215
|
+
/*
|
|
216
|
+
* SERVER STARTUP
|
|
217
|
+
*
|
|
218
|
+
* The startServer function initializes and starts the HTTP server. It validates configuration, cleans up stale processes, warms up the browser, and starts the
|
|
219
|
+
* Express application.
|
|
220
|
+
*/
|
|
221
|
+
/**
|
|
222
|
+
* Initializes and starts the HTTP server. Before accepting connections, we validate configuration, clean up stale Chrome processes, and warm up the browser
|
|
223
|
+
* instance.
|
|
224
|
+
* @param useConsoleLogging - Whether to log to console instead of file. Defaults to false (file logging).
|
|
225
|
+
*/
|
|
226
|
+
export async function startServer(useConsoleLogging = false) {
|
|
227
|
+
// Set logging mode early before any log calls.
|
|
228
|
+
usingConsoleLogging = useConsoleLogging;
|
|
229
|
+
setConsoleLogging(useConsoleLogging);
|
|
230
|
+
// Apply console-stamp for timestamps only when using console logging.
|
|
231
|
+
if (useConsoleLogging) {
|
|
232
|
+
consoleStamp(console, { format: ":date(yyyy/mm/dd HH:MM:ss.l)" });
|
|
233
|
+
}
|
|
234
|
+
// Initialize configuration from file and environment variables, then validate.
|
|
235
|
+
try {
|
|
236
|
+
await initializeConfiguration();
|
|
237
|
+
validateConfiguration();
|
|
238
|
+
validateProfiles();
|
|
239
|
+
}
|
|
240
|
+
catch (error) {
|
|
241
|
+
LOG.error(formatError(error));
|
|
242
|
+
process.exit(1);
|
|
243
|
+
}
|
|
244
|
+
displayConfiguration();
|
|
245
|
+
setupGracefulShutdown();
|
|
246
|
+
// Check FFmpeg availability if using FFmpeg capture mode.
|
|
247
|
+
if (CONFIG.streaming.captureMode === "ffmpeg") {
|
|
248
|
+
const ffmpegAvailable = await isFFmpegAvailable();
|
|
249
|
+
if (!ffmpegAvailable) {
|
|
250
|
+
LOG.error("FFmpeg is not available. FFmpeg capture mode requires FFmpeg to be installed and in the system PATH.");
|
|
251
|
+
LOG.error("Either install FFmpeg or change the capture mode to 'native' in the configuration.");
|
|
252
|
+
process.exit(1);
|
|
253
|
+
}
|
|
254
|
+
LOG.info("FFmpeg is available for FFmpeg capture mode.");
|
|
255
|
+
}
|
|
256
|
+
// Ensure the data directory exists before any operations that depend on it.
|
|
257
|
+
await ensureDataDirectory();
|
|
258
|
+
// Initialize file logger if not using console logging.
|
|
259
|
+
if (!useConsoleLogging) {
|
|
260
|
+
await initializeFileLogger(CONFIG.logging.maxSize);
|
|
261
|
+
}
|
|
262
|
+
// Load user channels from ~/.prismcast/channels.json if it exists.
|
|
263
|
+
await initializeUserChannels();
|
|
264
|
+
killStaleChromium();
|
|
265
|
+
// Warm up browser.
|
|
266
|
+
try {
|
|
267
|
+
await getCurrentBrowser();
|
|
268
|
+
LOG.info("Chrome ready.");
|
|
269
|
+
}
|
|
270
|
+
catch (error) {
|
|
271
|
+
LOG.error("Failed to initialize browser during startup: %s.", formatError(error));
|
|
272
|
+
throw error;
|
|
273
|
+
}
|
|
274
|
+
// Start stale page cleanup.
|
|
275
|
+
startStalePageCleanup();
|
|
276
|
+
// Start idle cleanup.
|
|
277
|
+
startIdleCleanup();
|
|
278
|
+
// Start show info polling for Channels DVR integration.
|
|
279
|
+
startShowInfoPolling();
|
|
280
|
+
// Build and start Express application.
|
|
281
|
+
try {
|
|
282
|
+
const app = await buildApp();
|
|
283
|
+
server = app.listen(CONFIG.server.port, CONFIG.server.host, () => {
|
|
284
|
+
LOG.info("PrismCast is now listening on %s:%s.", CONFIG.server.host, CONFIG.server.port);
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
catch (error) {
|
|
288
|
+
LOG.error("Failed to build application: %s.", formatError(error));
|
|
289
|
+
throw error;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
//# sourceMappingURL=app.js.map
|
package/dist/app.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,MAAM,EAAE,oBAAoB,EAAE,uBAAuB,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAEjH,OAAO,EAAE,GAAG,EAAE,kBAAkB,EAAE,WAAW,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAC9G,OAAO,EAAE,YAAY,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,qBAAqB,EACvH,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AACjF,OAAO,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAEpF,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,YAAY,MAAM,eAAe,CAAC;AACzC,OAAO,OAAO,MAAM,SAAS,CAAC;AAC9B,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACxD,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,MAAM,MAAM,QAAQ,CAAC;AAC5B,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAC3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAExD;;;;;GAKG;AAEH,sEAAsE;AACtE,IAAI,mBAAmB,GAAG,KAAK,CAAC;AAEhC;;;;GAIG;AAEH,IAAI,MAAM,GAAkB,IAAI,CAAC;AAEjC,oCAAoC;AACpC,IAAI,mBAAmB,GAA0C,IAAI,CAAC;AAEtE;;GAEG;AACH,SAAS,gBAAgB;IAEvB,IAAG,mBAAmB,EAAE,CAAC;QAEvB,OAAO;IACT,CAAC;IAED,2CAA2C;IAC3C,mBAAmB,GAAG,WAAW,CAAC,GAAG,EAAE;QAErC,kBAAkB,EAAE,CAAC;IACvB,CAAC,EAAE,KAAK,CAAC,CAAC;AACZ,CAAC;AAED;;GAEG;AACH,SAAS,eAAe;IAEtB,IAAG,mBAAmB,EAAE,CAAC;QAEvB,aAAa,CAAC,mBAAmB,CAAC,CAAC;QACnC,mBAAmB,GAAG,IAAI,CAAC;IAC7B,CAAC;AACH,CAAC;AAED;;;;GAIG;AAEH;;GAEG;AACH,SAAS,qBAAqB;IAE5B,IAAI,kBAAkB,GAAG,KAAK,CAAC;IAE/B,KAAK,UAAU,QAAQ,CAAC,MAAc;QAEpC,uEAAuE;QACvE,IAAG,kBAAkB,EAAE,CAAC;YAEtB,OAAO;QACT,CAAC;QAED,kBAAkB,GAAG,IAAI,CAAC;QAE1B,GAAG,CAAC,IAAI,CAAC,0CAA0C,EAAE,MAAM,CAAC,CAAC;QAE7D,sCAAsC;QACtC,oBAAoB,EAAE,CAAC;QACvB,eAAe,EAAE,CAAC;QAClB,mBAAmB,EAAE,CAAC;QAEtB,4GAA4G;QAC5G,MAAM,OAAO,GAAG,aAAa,EAAE,CAAC;QAEhC,KAAI,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;YAE5B,eAAe,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,iBAAiB,CAAC,CAAC;QACtE,CAAC;QAED,qBAAqB;QACrB,MAAM,YAAY,EAAE,CAAC;QAErB,yBAAyB;QACzB,IAAI,CAAC;YAEH,IAAG,MAAM,EAAE,CAAC;gBAEV,MAAM,CAAC,KAAK,CAAC,GAAS,EAAE;oBAEtB,GAAG,CAAC,IAAI,CAAC,kCAAkC,CAAC,CAAC;gBAC/C,CAAC,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QAAC,OAAM,KAAK,EAAE,CAAC;YAEd,GAAG,CAAC,KAAK,CAAC,2CAA2C,EAAE,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC;QAC7E,CAAC;QAED,mCAAmC;QACnC,IAAG,CAAC,mBAAmB,EAAE,CAAC;YAExB,kBAAkB,EAAE,CAAC;QACvB,CAAC;QAED,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAS,EAAE;QAE9B,KAAK,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAC1B,CAAC,CAAC,CAAC;IAEH,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,GAAS,EAAE;QAE/B,KAAK,QAAQ,CAAC,SAAS,CAAC,CAAC;IAC3B,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;GAKG;AAEH;;;GAGG;AACH,KAAK,UAAU,QAAQ;IAErB,IAAI,CAAC;QAEH,MAAM,gBAAgB,EAAE,CAAC;IAC3B,CAAC;IAAC,OAAM,KAAK,EAAE,CAAC;QAEd,GAAG,CAAC,KAAK,CAAC,yCAAyC,EAAE,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC;QAEzE,MAAM,KAAK,CAAC;IACd,CAAC;IAED,MAAM,GAAG,GAAG,OAAO,EAAE,CAAC;IAEtB,+JAA+J;IAC/J,6EAA6E;IAC7E,GAAG,CAAC,GAAG,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC;IAE7B,yEAAyE;IACzE,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IAChD,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;IAExB,4JAA4J;IAC5J,wDAAwD;IACxD,IAAG,MAAM,CAAC,OAAO,CAAC,YAAY,KAAK,MAAM,EAAE,CAAC;QAE1C,MAAM,YAAY,GAAG,wEAAwE,CAAC;QAC9F,MAAM,YAAY,GAAG,kBAAkB,EAAE,CAAC;QAE1C,gJAAgJ;QAChJ,MAAM,oBAAoB,GAAG,CAAE,mBAAmB,EAAE,UAAU,EAAE,aAAa,EAAE,mBAAmB,CAAE,CAAC;QAErG,IAAG,MAAM,CAAC,OAAO,CAAC,YAAY,KAAK,QAAQ,EAAE,CAAC;YAE5C,8FAA8F;YAC9F,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,YAAY,EAAE;gBAE3B,IAAI,EAAE,CAAC,GAAG,EAAE,GAAG,EAAW,EAAE;oBAE1B,+BAA+B;oBAC/B,IAAG,GAAG,CAAC,UAAU,GAAG,GAAG,EAAE,CAAC;wBAExB,OAAO,IAAI,CAAC;oBACd,CAAC;oBAED,0EAA0E;oBAC1E,IAAG,GAAG,CAAC,UAAU,KAAK,GAAG,EAAE,CAAC;wBAE1B,MAAM,GAAG,GAAG,GAAG,CAAC,WAAW,IAAI,GAAG,CAAC,GAAG,CAAC;wBAEvC,IAAG,oBAAoB,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC;4BAEnE,OAAO,IAAI,CAAC;wBACd,CAAC;oBACH,CAAC;oBAED,2IAA2I;oBAC3I,IAAG,CAAC,GAAG,CAAC,UAAU,KAAK,GAAG,CAAC,IAAI,GAAG,CAAC,SAAS,CAAC,aAAa,CAAC,EAAE,CAAC;wBAE5D,OAAO,IAAI,CAAC;oBACd,CAAC;oBAED,OAAO,KAAK,CAAC;gBACf,CAAC;gBAED,MAAM,EAAE,YAAY;aACrB,CAAC,CAAC,CAAC;QACN,CAAC;aAAM,IAAG,MAAM,CAAC,OAAO,CAAC,YAAY,KAAK,UAAU,EAAE,CAAC;YAErD,uIAAuI;YACvI,MAAM,YAAY,GAAG,CAAE,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,WAAW,EAAE,WAAW,CAAE,CAAC;YAElF,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,YAAY,EAAE;gBAE3B,IAAI,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;oBAEjB,qBAAqB;oBACrB,IAAG,GAAG,CAAC,UAAU,IAAI,GAAG,EAAE,CAAC;wBAEzB,OAAO,KAAK,CAAC;oBACf,CAAC;oBAED,4CAA4C;oBAC5C,MAAM,YAAY,GAAG,UAAU,CAAC,GAAG,CAAC,SAAS,CAAC,iBAAiB,CAAW,IAAI,GAAG,CAAC,CAAC;oBAEnF,IAAG,YAAY,GAAG,IAAI,EAAE,CAAC;wBAEvB,OAAO,KAAK,CAAC;oBACf,CAAC;oBAED,iDAAiD;oBACjD,MAAM,GAAG,GAAG,GAAG,CAAC,WAAW,IAAI,GAAG,CAAC,GAAG,CAAC;oBACvC,MAAM,iBAAiB,GAAG,CAAE,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,QAAQ,CAAE,CAAC;oBAEtF,IAAG,iBAAiB,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC;wBAEhE,OAAO,KAAK,CAAC;oBACf,CAAC;oBAED,iDAAiD;oBACjD,IAAG,YAAY,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC;wBAE3D,OAAO,IAAI,CAAC;oBACd,CAAC;oBAED,qDAAqD;oBACrD,IAAG,CAAC,GAAG,KAAK,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,GAAG,GAAG,CAAC,EAAE,CAAC;wBAE3C,OAAO,IAAI,CAAC;oBACd,CAAC;oBAED,uBAAuB;oBACvB,OAAO,KAAK,CAAC;gBACf,CAAC;gBAED,MAAM,EAAE,YAAY;aACrB,CAAC,CAAC,CAAC;QACN,CAAC;aAAM,CAAC;YAEN,oBAAoB;YACpB,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,YAAY,EAAE,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC;QAC1D,CAAC;IACH,CAAC;IAED,6BAA6B;IAC7B,WAAW,CAAC,GAAG,CAAC,CAAC;IAEjB,oFAAoF;IACpF,6DAA6D;IAC7D,GAAG,CAAC,GAAG,CAAC,CAAC,GAAU,EAAE,IAAa,EAAE,GAAa,EAAE,KAAmB,EAAQ,EAAE;QAE9E,GAAG,CAAC,KAAK,CAAC,iCAAiC,EAAE,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC;QAE/D,IAAG,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC;YAEpB,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC;QAChD,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;GAKG;AAEH;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,iBAAiB,GAAG,KAAK;IAEzD,+CAA+C;IAC/C,mBAAmB,GAAG,iBAAiB,CAAC;IACxC,iBAAiB,CAAC,iBAAiB,CAAC,CAAC;IAErC,sEAAsE;IACtE,IAAG,iBAAiB,EAAE,CAAC;QAErB,YAAY,CAAC,OAAO,EAAE,EAAE,MAAM,EAAE,8BAA8B,EAAE,CAAC,CAAC;IACpE,CAAC;IAED,+EAA+E;IAC/E,IAAI,CAAC;QAEH,MAAM,uBAAuB,EAAE,CAAC;QAChC,qBAAqB,EAAE,CAAC;QACxB,gBAAgB,EAAE,CAAC;IACrB,CAAC;IAAC,OAAM,KAAK,EAAE,CAAC;QAEd,GAAG,CAAC,KAAK,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC;QAE9B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,oBAAoB,EAAE,CAAC;IACvB,qBAAqB,EAAE,CAAC;IAExB,0DAA0D;IAC1D,IAAG,MAAM,CAAC,SAAS,CAAC,WAAW,KAAK,QAAQ,EAAE,CAAC;QAE7C,MAAM,eAAe,GAAG,MAAM,iBAAiB,EAAE,CAAC;QAElD,IAAG,CAAC,eAAe,EAAE,CAAC;YAEpB,GAAG,CAAC,KAAK,CAAC,sGAAsG,CAAC,CAAC;YAClH,GAAG,CAAC,KAAK,CAAC,oFAAoF,CAAC,CAAC;YAEhG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QAED,GAAG,CAAC,IAAI,CAAC,8CAA8C,CAAC,CAAC;IAC3D,CAAC;IAED,4EAA4E;IAC5E,MAAM,mBAAmB,EAAE,CAAC;IAE5B,uDAAuD;IACvD,IAAG,CAAC,iBAAiB,EAAE,CAAC;QAEtB,MAAM,oBAAoB,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IACrD,CAAC;IAED,mEAAmE;IACnE,MAAM,sBAAsB,EAAE,CAAC;IAE/B,iBAAiB,EAAE,CAAC;IAEpB,mBAAmB;IACnB,IAAI,CAAC;QAEH,MAAM,iBAAiB,EAAE,CAAC;QAE1B,GAAG,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;IAC5B,CAAC;IAAC,OAAM,KAAK,EAAE,CAAC;QAEd,GAAG,CAAC,KAAK,CAAC,kDAAkD,EAAE,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC;QAElF,MAAM,KAAK,CAAC;IACd,CAAC;IAED,4BAA4B;IAC5B,qBAAqB,EAAE,CAAC;IAExB,sBAAsB;IACtB,gBAAgB,EAAE,CAAC;IAEnB,wDAAwD;IACxD,oBAAoB,EAAE,CAAC;IAEvB,uCAAuC;IACvC,IAAI,CAAC;QAEH,MAAM,GAAG,GAAG,MAAM,QAAQ,EAAE,CAAC;QAE7B,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,GAAS,EAAE;YAErE,GAAG,CAAC,IAAI,CAAC,sCAAsC,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAC3F,CAAC,CAAC,CAAC;IACL,CAAC;IAAC,OAAM,KAAK,EAAE,CAAC;QAEd,GAAG,CAAC,KAAK,CAAC,kCAAkC,EAAE,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC;QAElE,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { CDPSession, Page } from "puppeteer-core";
|
|
2
|
+
/**
|
|
3
|
+
* Executes a CDP (Chrome DevTools Protocol) operation with proper session lifecycle management. This helper handles the common pattern of:
|
|
4
|
+
* 1. Creating a CDP session attached to the page's target
|
|
5
|
+
* 2. Getting the browser window ID for the page
|
|
6
|
+
* 3. Calling the provided operation with the session and window ID
|
|
7
|
+
* 4. Gracefully handling errors when the page is closed during the operation
|
|
8
|
+
*
|
|
9
|
+
* The session is created fresh for each call rather than being reused because CDP sessions become invalid when the page navigates or closes. Creating a new
|
|
10
|
+
* session ensures we always have a valid connection.
|
|
11
|
+
* @param page - The Puppeteer page object to create a CDP session for.
|
|
12
|
+
* @param operation - An async function that receives the CDP session and window ID. The operation can use any CDP commands via session.send().
|
|
13
|
+
* @returns The result of the operation, or undefined if the page was closed or an error occurred.
|
|
14
|
+
*/
|
|
15
|
+
export declare function withCDPSession<T>(page: Page, operation: (session: CDPSession, windowId: number) => Promise<T>): Promise<T | undefined>;
|
|
16
|
+
/**
|
|
17
|
+
* Resizes the browser window to match our target viewport dimensions and optionally minimizes it. This function solves the problem of ensuring the video content
|
|
18
|
+
* area exactly matches our configured viewport size.
|
|
19
|
+
*
|
|
20
|
+
* The complication is that browser windows have "chrome" - the title bar, toolbar, borders, and other UI elements that take up space. If we set the window size
|
|
21
|
+
* to 1280x720, the actual content area will be smaller (perhaps 1280x670 after accounting for the toolbar). To get a 1280x720 content area, we need to add the
|
|
22
|
+
* chrome dimensions to our window size.
|
|
23
|
+
*
|
|
24
|
+
* This function:
|
|
25
|
+
* 1. Measures the current chrome dimensions by comparing window.outerWidth/Height to window.innerWidth/Height
|
|
26
|
+
* 2. Sets the window size to viewport + chrome dimensions, giving us the exact viewport size we want
|
|
27
|
+
* 3. Optionally minimizes the window to reduce GPU usage while still allowing capture
|
|
28
|
+
* @param page - The Puppeteer page object.
|
|
29
|
+
* @param shouldMinimize - Whether to minimize the window after resizing. Set to true for stream pages (to reduce GPU usage) and false for debug pages (where
|
|
30
|
+
* visibility is desired).
|
|
31
|
+
*/
|
|
32
|
+
export declare function resizeAndMinimizeWindow(page: Page, shouldMinimize: boolean): Promise<void>;
|
|
33
|
+
/**
|
|
34
|
+
* Un-minimizes the browser window, restoring it to normal state. This is used when the user needs to interact with the browser, such as during the authentication
|
|
35
|
+
* login flow where the user must complete TV provider authentication in the visible browser window.
|
|
36
|
+
* @param page - The Puppeteer page object.
|
|
37
|
+
*/
|
|
38
|
+
export declare function unminimizeWindow(page: Page): Promise<void>;
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { LOG, evaluateWithAbort, formatError } from "../utils/index.js";
|
|
2
|
+
import { CONFIG } from "../config/index.js";
|
|
3
|
+
import { getBrowserChrome } from "./display.js";
|
|
4
|
+
import { getEffectiveViewport } from "../config/presets.js";
|
|
5
|
+
/*
|
|
6
|
+
* CDP SESSION HELPERS
|
|
7
|
+
*
|
|
8
|
+
* The Chrome DevTools Protocol (CDP) provides low-level access to Chrome's internal state and capabilities. While Puppeteer abstracts most common operations, some
|
|
9
|
+
* features require direct CDP access:
|
|
10
|
+
*
|
|
11
|
+
* - Window management: Setting window size, position, and state (minimized, maximized, fullscreen). Puppeteer's viewport API controls the content area, but we
|
|
12
|
+
* need CDP to control the entire window including browser chrome.
|
|
13
|
+
*
|
|
14
|
+
* - Browser-level operations: Operations that affect the browser rather than a specific page, like getting the window ID for a page's target.
|
|
15
|
+
*
|
|
16
|
+
* CDP sessions are created per-page and must be managed carefully:
|
|
17
|
+
* - Sessions can fail if the page or target is closed while we're using it
|
|
18
|
+
* - The "No target with given id" error is common and expected when pages close during operations
|
|
19
|
+
* - We wrap CDP operations in try/catch to handle these transient errors gracefully
|
|
20
|
+
*
|
|
21
|
+
* The withCDPSession helper encapsulates the common pattern of creating a session, getting the window ID, performing an operation, and handling errors.
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* Executes a CDP (Chrome DevTools Protocol) operation with proper session lifecycle management. This helper handles the common pattern of:
|
|
25
|
+
* 1. Creating a CDP session attached to the page's target
|
|
26
|
+
* 2. Getting the browser window ID for the page
|
|
27
|
+
* 3. Calling the provided operation with the session and window ID
|
|
28
|
+
* 4. Gracefully handling errors when the page is closed during the operation
|
|
29
|
+
*
|
|
30
|
+
* The session is created fresh for each call rather than being reused because CDP sessions become invalid when the page navigates or closes. Creating a new
|
|
31
|
+
* session ensures we always have a valid connection.
|
|
32
|
+
* @param page - The Puppeteer page object to create a CDP session for.
|
|
33
|
+
* @param operation - An async function that receives the CDP session and window ID. The operation can use any CDP commands via session.send().
|
|
34
|
+
* @returns The result of the operation, or undefined if the page was closed or an error occurred.
|
|
35
|
+
*/
|
|
36
|
+
export async function withCDPSession(page, operation) {
|
|
37
|
+
// Early exit if the page is already closed. This prevents errors when trying to create a session for a closed page.
|
|
38
|
+
if (page.isClosed()) {
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
// Create a CDP session attached to the page's target. The session provides access to all CDP domains (Browser, Page, Network, etc.) for this specific
|
|
43
|
+
// target. Each page has its own target in Chrome's DevTools architecture.
|
|
44
|
+
const session = await page.createCDPSession();
|
|
45
|
+
// Get the browser window ID for this page. Chrome organizes pages into windows, and we need the window ID to perform window-level operations like resizing
|
|
46
|
+
// or minimizing. The Browser.getWindowForTarget command returns the window ID for the current target.
|
|
47
|
+
const windowResult = await session.send("Browser.getWindowForTarget");
|
|
48
|
+
const windowId = windowResult.windowId;
|
|
49
|
+
// If we couldn't get a window ID, the target may be in an invalid state. Return undefined to indicate the operation couldn't be performed.
|
|
50
|
+
if (!windowId) {
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
// Execute the caller's operation with the session and window ID.
|
|
54
|
+
return await operation(session, windowId);
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
const message = formatError(error);
|
|
58
|
+
// "No target with given id" is a common error that occurs when the page closes during our operation. This is expected during stream termination and
|
|
59
|
+
// shouldn't be logged as a warning since it's not actionable. We also check if the page is closed, as errors during page closure are expected.
|
|
60
|
+
if ((message.indexOf("No target with given id") === -1) && !page.isClosed()) {
|
|
61
|
+
LOG.warn("CDP operation failed: %s.", message);
|
|
62
|
+
}
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Resizes the browser window to match our target viewport dimensions and optionally minimizes it. This function solves the problem of ensuring the video content
|
|
68
|
+
* area exactly matches our configured viewport size.
|
|
69
|
+
*
|
|
70
|
+
* The complication is that browser windows have "chrome" - the title bar, toolbar, borders, and other UI elements that take up space. If we set the window size
|
|
71
|
+
* to 1280x720, the actual content area will be smaller (perhaps 1280x670 after accounting for the toolbar). To get a 1280x720 content area, we need to add the
|
|
72
|
+
* chrome dimensions to our window size.
|
|
73
|
+
*
|
|
74
|
+
* This function:
|
|
75
|
+
* 1. Measures the current chrome dimensions by comparing window.outerWidth/Height to window.innerWidth/Height
|
|
76
|
+
* 2. Sets the window size to viewport + chrome dimensions, giving us the exact viewport size we want
|
|
77
|
+
* 3. Optionally minimizes the window to reduce GPU usage while still allowing capture
|
|
78
|
+
* @param page - The Puppeteer page object.
|
|
79
|
+
* @param shouldMinimize - Whether to minimize the window after resizing. Set to true for stream pages (to reduce GPU usage) and false for debug pages (where
|
|
80
|
+
* visibility is desired).
|
|
81
|
+
*/
|
|
82
|
+
export async function resizeAndMinimizeWindow(page, shouldMinimize) {
|
|
83
|
+
// Early exit if the page is already closed.
|
|
84
|
+
if (page.isClosed()) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
// Get browser chrome dimensions. Prefer cached values from display detection, which were measured when the browser was in a known good state. Fall back to
|
|
88
|
+
// measuring via page.evaluate() if cached values aren't available (e.g., during early initialization or after cache clear).
|
|
89
|
+
let uiSize = getBrowserChrome();
|
|
90
|
+
if (!uiSize) {
|
|
91
|
+
try {
|
|
92
|
+
uiSize = await evaluateWithAbort(page, () => {
|
|
93
|
+
return {
|
|
94
|
+
// Height of chrome = total window height - content height. This includes the title bar, toolbar, and any other vertical UI elements.
|
|
95
|
+
height: window.outerHeight - window.innerHeight,
|
|
96
|
+
// Width of chrome = total window width - content width. This typically includes window borders and any side panels.
|
|
97
|
+
width: window.outerWidth - window.innerWidth
|
|
98
|
+
};
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
catch (_error) {
|
|
102
|
+
// If measuring fails (page closed, navigation in progress, etc.), silently return. The resize is not critical and will be attempted again on the next
|
|
103
|
+
// stream if needed.
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// Use CDP to set the window bounds. We add the chrome dimensions to our target viewport to get the correct total window size. CDP requires separate calls for
|
|
108
|
+
// dimensions and window state - they cannot be combined in a single call.
|
|
109
|
+
await withCDPSession(page, async (session, windowId) => {
|
|
110
|
+
// First, ensure the window is in "normal" state. If the browser launched with a window size larger than the display, Chrome may have automatically maximized
|
|
111
|
+
// the window. Setting bounds on a maximized window is ignored, so we must restore it to normal state first.
|
|
112
|
+
await session.send("Browser.setWindowBounds", {
|
|
113
|
+
bounds: { windowState: "normal" },
|
|
114
|
+
windowId
|
|
115
|
+
});
|
|
116
|
+
// Set the window size to viewport + chrome. After this, the content area will be exactly our target viewport dimensions.
|
|
117
|
+
const viewport = getEffectiveViewport(CONFIG);
|
|
118
|
+
await session.send("Browser.setWindowBounds", {
|
|
119
|
+
bounds: {
|
|
120
|
+
// Total window height = desired viewport height + chrome height (title bar, toolbar, etc.)
|
|
121
|
+
height: viewport.height + uiSize.height,
|
|
122
|
+
// Total window width = desired viewport width + chrome width (borders, etc.)
|
|
123
|
+
width: viewport.width + uiSize.width
|
|
124
|
+
},
|
|
125
|
+
windowId
|
|
126
|
+
});
|
|
127
|
+
// Optionally minimize the window to reduce GPU usage. This must be a separate CDP call because window state cannot be combined with dimensions. Minimizing
|
|
128
|
+
// doesn't stop video capture - the puppeteer-stream extension captures from the compositor rather than the visible display.
|
|
129
|
+
if (shouldMinimize) {
|
|
130
|
+
// Brief delay to allow Chrome's window manager to finish processing the resize before minimizing. Without this delay, the minimize can be ignored when the
|
|
131
|
+
// window is being significantly resized (e.g., during preset degradation from 1080p to 720p).
|
|
132
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
133
|
+
await session.send("Browser.setWindowBounds", {
|
|
134
|
+
bounds: { windowState: "minimized" },
|
|
135
|
+
windowId
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Un-minimizes the browser window, restoring it to normal state. This is used when the user needs to interact with the browser, such as during the authentication
|
|
142
|
+
* login flow where the user must complete TV provider authentication in the visible browser window.
|
|
143
|
+
* @param page - The Puppeteer page object.
|
|
144
|
+
*/
|
|
145
|
+
export async function unminimizeWindow(page) {
|
|
146
|
+
// Early exit if the page is already closed.
|
|
147
|
+
if (page.isClosed()) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
await withCDPSession(page, async (session, windowId) => {
|
|
151
|
+
// Restore the window to normal (visible) state.
|
|
152
|
+
await session.send("Browser.setWindowBounds", {
|
|
153
|
+
bounds: { windowState: "normal" },
|
|
154
|
+
windowId
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
//# sourceMappingURL=cdp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cdp.js","sourceRoot":"","sources":["../../src/browser/cdp.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,GAAG,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACxE,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAE5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAE5D;;;;;;;;;;;;;;;;;GAiBG;AAEH;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,IAAU,EACV,SAAgE;IAGhE,oHAAoH;IACpH,IAAG,IAAI,CAAC,QAAQ,EAAE,EAAE,CAAC;QAEnB,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,IAAI,CAAC;QAEH,sJAAsJ;QACtJ,0EAA0E;QAC1E,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,gBAAgB,EAAE,CAAC;QAE9C,2JAA2J;QAC3J,sGAAsG;QACtG,MAAM,YAAY,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,4BAA4B,CAA0B,CAAC;QAC/F,MAAM,QAAQ,GAAG,YAAY,CAAC,QAAQ,CAAC;QAEvC,2IAA2I;QAC3I,IAAG,CAAC,QAAQ,EAAE,CAAC;YAEb,OAAO,SAAS,CAAC;QACnB,CAAC;QAED,iEAAiE;QACjE,OAAO,MAAM,SAAS,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IAC5C,CAAC;IAAC,OAAM,KAAK,EAAE,CAAC;QAEd,MAAM,OAAO,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;QAEnC,oJAAoJ;QACpJ,+IAA+I;QAC/I,IAAG,CAAC,OAAO,CAAC,OAAO,CAAC,yBAAyB,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,CAAC;YAE3E,GAAG,CAAC,IAAI,CAAC,2BAA2B,EAAE,OAAO,CAAC,CAAC;QACjD,CAAC;QAED,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAAC,IAAU,EAAE,cAAuB;IAE/E,4CAA4C;IAC5C,IAAG,IAAI,CAAC,QAAQ,EAAE,EAAE,CAAC;QAEnB,OAAO;IACT,CAAC;IAED,2JAA2J;IAC3J,4HAA4H;IAC5H,IAAI,MAAM,GAAkB,gBAAgB,EAAE,CAAC;IAE/C,IAAG,CAAC,MAAM,EAAE,CAAC;QAEX,IAAI,CAAC;YAEH,MAAM,GAAG,MAAM,iBAAiB,CAAC,IAAI,EAAE,GAAW,EAAE;gBAElD,OAAO;oBAEL,qIAAqI;oBACrI,MAAM,EAAE,MAAM,CAAC,WAAW,GAAG,MAAM,CAAC,WAAW;oBAE/C,oHAAoH;oBACpH,KAAK,EAAE,MAAM,CAAC,UAAU,GAAG,MAAM,CAAC,UAAU;iBAC7C,CAAC;YACJ,CAAC,CAAC,CAAC;QACL,CAAC;QAAC,OAAM,MAAM,EAAE,CAAC;YAEf,sJAAsJ;YACtJ,oBAAoB;YACpB,OAAO;QACT,CAAC;IACH,CAAC;IAED,8JAA8J;IAC9J,0EAA0E;IAC1E,MAAM,cAAc,CAAC,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE;QAErD,6JAA6J;QAC7J,4GAA4G;QAC5G,MAAM,OAAO,CAAC,IAAI,CAAC,yBAAyB,EAAE;YAE5C,MAAM,EAAE,EAAE,WAAW,EAAE,QAAQ,EAAE;YACjC,QAAQ;SACT,CAAC,CAAC;QAEH,yHAAyH;QACzH,MAAM,QAAQ,GAAG,oBAAoB,CAAC,MAAM,CAAC,CAAC;QAE9C,MAAM,OAAO,CAAC,IAAI,CAAC,yBAAyB,EAAE;YAE5C,MAAM,EAAE;gBAEN,2FAA2F;gBAC3F,MAAM,EAAE,QAAQ,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM;gBAEvC,6EAA6E;gBAC7E,KAAK,EAAE,QAAQ,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK;aACrC;YACD,QAAQ;SACT,CAAC,CAAC;QAEH,2JAA2J;QAC3J,4HAA4H;QAC5H,IAAG,cAAc,EAAE,CAAC;YAElB,2JAA2J;YAC3J,8FAA8F;YAC9F,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC;YAE/D,MAAM,OAAO,CAAC,IAAI,CAAC,yBAAyB,EAAE;gBAE5C,MAAM,EAAE,EAAE,WAAW,EAAE,WAAW,EAAE;gBACpC,QAAQ;aACT,CAAC,CAAC;QACL,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,IAAU;IAE/C,4CAA4C;IAC5C,IAAG,IAAI,CAAC,QAAQ,EAAE,EAAE,CAAC;QAEnB,OAAO;IACT,CAAC;IAED,MAAM,cAAc,CAAC,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE;QAErD,gDAAgD;QAChD,MAAM,OAAO,CAAC,IAAI,CAAC,yBAAyB,EAAE;YAE5C,MAAM,EAAE,EAAE,WAAW,EAAE,QAAQ,EAAE;YACjC,QAAQ;SACT,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ChannelSelectorResult, ResolvedSiteProfile } from "../types/index.js";
|
|
2
|
+
import type { Page } from "puppeteer-core";
|
|
3
|
+
/**
|
|
4
|
+
* Selects a channel from a multi-channel player UI using the strategy specified in the profile. This is the main entry point for channel selection, called by
|
|
5
|
+
* tuneToChannel() after page navigation.
|
|
6
|
+
*
|
|
7
|
+
* The function handles:
|
|
8
|
+
* - Strategy dispatch based on profile.channelSelection.strategy
|
|
9
|
+
* - No-op for single-channel sites (strategy "none" or no channelSelector)
|
|
10
|
+
* - Logging of selection attempts and results
|
|
11
|
+
* - Timing delays before and after selection
|
|
12
|
+
* @param page - The Puppeteer page object.
|
|
13
|
+
* @param profile - The resolved site profile containing channelSelection config and channelSelector slug.
|
|
14
|
+
* @returns Result object with success status and optional failure reason.
|
|
15
|
+
*/
|
|
16
|
+
export declare function selectChannel(page: Page, profile: ResolvedSiteProfile): Promise<ChannelSelectorResult>;
|