@bnhf/prismcast 1.3.4-2026.2.19
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/LICENSE.md +7 -0
- package/README.md +347 -0
- package/bin/prismcast +6 -0
- package/dist/app.d.ts +6 -0
- package/dist/app.js +315 -0
- package/dist/app.js.map +1 -0
- package/dist/browser/cdp.d.ts +38 -0
- package/dist/browser/cdp.js +155 -0
- package/dist/browser/cdp.js.map +1 -0
- package/dist/browser/channelSelection.d.ts +65 -0
- package/dist/browser/channelSelection.js +202 -0
- package/dist/browser/channelSelection.js.map +1 -0
- package/dist/browser/display.d.ts +34 -0
- package/dist/browser/display.js +54 -0
- package/dist/browser/display.js.map +1 -0
- package/dist/browser/index.d.ts +205 -0
- package/dist/browser/index.js +1205 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/browser/tuning/fox.d.ts +2 -0
- package/dist/browser/tuning/fox.js +83 -0
- package/dist/browser/tuning/fox.js.map +1 -0
- package/dist/browser/tuning/hbo.d.ts +2 -0
- package/dist/browser/tuning/hbo.js +237 -0
- package/dist/browser/tuning/hbo.js.map +1 -0
- package/dist/browser/tuning/hulu.d.ts +2 -0
- package/dist/browser/tuning/hulu.js +550 -0
- package/dist/browser/tuning/hulu.js.map +1 -0
- package/dist/browser/tuning/sling.d.ts +2 -0
- package/dist/browser/tuning/sling.js +518 -0
- package/dist/browser/tuning/sling.js.map +1 -0
- package/dist/browser/tuning/thumbnailRow.d.ts +2 -0
- package/dist/browser/tuning/thumbnailRow.js +108 -0
- package/dist/browser/tuning/thumbnailRow.js.map +1 -0
- package/dist/browser/tuning/tileClick.d.ts +2 -0
- package/dist/browser/tuning/tileClick.js +103 -0
- package/dist/browser/tuning/tileClick.js.map +1 -0
- package/dist/browser/tuning/youtubeTv.d.ts +2 -0
- package/dist/browser/tuning/youtubeTv.js +182 -0
- package/dist/browser/tuning/youtubeTv.js.map +1 -0
- package/dist/browser/video.d.ts +289 -0
- package/dist/browser/video.js +996 -0
- package/dist/browser/video.js.map +1 -0
- package/dist/channels/index.d.ts +3 -0
- package/dist/channels/index.js +392 -0
- package/dist/channels/index.js.map +1 -0
- package/dist/config/index.d.ts +53 -0
- package/dist/config/index.js +233 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/presets.d.ts +98 -0
- package/dist/config/presets.js +241 -0
- package/dist/config/presets.js.map +1 -0
- package/dist/config/profiles.d.ts +79 -0
- package/dist/config/profiles.js +245 -0
- package/dist/config/profiles.js.map +1 -0
- package/dist/config/providers.d.ts +120 -0
- package/dist/config/providers.js +450 -0
- package/dist/config/providers.js.map +1 -0
- package/dist/config/sites.d.ts +22 -0
- package/dist/config/sites.js +377 -0
- package/dist/config/sites.js.map +1 -0
- package/dist/config/userChannels.d.ts +178 -0
- package/dist/config/userChannels.js +543 -0
- package/dist/config/userChannels.js.map +1 -0
- package/dist/config/userConfig.d.ts +235 -0
- package/dist/config/userConfig.js +913 -0
- package/dist/config/userConfig.js.map +1 -0
- package/dist/hdhr/channelMap.d.ts +21 -0
- package/dist/hdhr/channelMap.js +82 -0
- package/dist/hdhr/channelMap.js.map +1 -0
- package/dist/hdhr/deviceId.d.ts +11 -0
- package/dist/hdhr/deviceId.js +84 -0
- package/dist/hdhr/deviceId.js.map +1 -0
- package/dist/hdhr/discover.d.ts +6 -0
- package/dist/hdhr/discover.js +155 -0
- package/dist/hdhr/discover.js.map +1 -0
- package/dist/hdhr/index.d.ts +9 -0
- package/dist/hdhr/index.js +87 -0
- package/dist/hdhr/index.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +144 -0
- package/dist/index.js.map +1 -0
- package/dist/routes/assets.d.ts +6 -0
- package/dist/routes/assets.js +79 -0
- package/dist/routes/assets.js.map +1 -0
- package/dist/routes/auth.d.ts +6 -0
- package/dist/routes/auth.js +77 -0
- package/dist/routes/auth.js.map +1 -0
- package/dist/routes/channels.d.ts +6 -0
- package/dist/routes/channels.js +40 -0
- package/dist/routes/channels.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 +72 -0
- package/dist/routes/config.js +1977 -0
- package/dist/routes/config.js.map +1 -0
- package/dist/routes/debug.d.ts +6 -0
- package/dist/routes/debug.js +274 -0
- package/dist/routes/debug.js.map +1 -0
- package/dist/routes/health.d.ts +6 -0
- package/dist/routes/health.js +85 -0
- package/dist/routes/health.js.map +1 -0
- package/dist/routes/hls.d.ts +6 -0
- package/dist/routes/hls.js +25 -0
- package/dist/routes/hls.js.map +1 -0
- package/dist/routes/index.d.ts +19 -0
- package/dist/routes/index.js +49 -0
- package/dist/routes/index.js.map +1 -0
- package/dist/routes/logs.d.ts +6 -0
- package/dist/routes/logs.js +164 -0
- package/dist/routes/logs.js.map +1 -0
- package/dist/routes/mpegts.d.ts +6 -0
- package/dist/routes/mpegts.js +19 -0
- package/dist/routes/mpegts.js.map +1 -0
- package/dist/routes/play.d.ts +6 -0
- package/dist/routes/play.js +18 -0
- package/dist/routes/play.js.map +1 -0
- package/dist/routes/playlist.d.ts +36 -0
- package/dist/routes/playlist.js +134 -0
- package/dist/routes/playlist.js.map +1 -0
- package/dist/routes/root.d.ts +6 -0
- package/dist/routes/root.js +2920 -0
- package/dist/routes/root.js.map +1 -0
- package/dist/routes/streams.d.ts +6 -0
- package/dist/routes/streams.js +88 -0
- package/dist/routes/streams.js.map +1 -0
- package/dist/routes/theme.d.ts +15 -0
- package/dist/routes/theme.js +275 -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 +391 -0
- package/dist/service/commands.js.map +1 -0
- package/dist/service/generators.d.ts +33 -0
- package/dist/service/generators.js +432 -0
- package/dist/service/generators.js.map +1 -0
- package/dist/service/index.d.ts +2 -0
- package/dist/service/index.js +7 -0
- package/dist/service/index.js.map +1 -0
- package/dist/streaming/clients.d.ts +48 -0
- package/dist/streaming/clients.js +114 -0
- package/dist/streaming/clients.js.map +1 -0
- package/dist/streaming/fmp4Segmenter.d.ts +61 -0
- package/dist/streaming/fmp4Segmenter.js +461 -0
- package/dist/streaming/fmp4Segmenter.js.map +1 -0
- package/dist/streaming/hls.d.ts +120 -0
- package/dist/streaming/hls.js +722 -0
- package/dist/streaming/hls.js.map +1 -0
- package/dist/streaming/hlsSegments.d.ts +54 -0
- package/dist/streaming/hlsSegments.js +162 -0
- package/dist/streaming/hlsSegments.js.map +1 -0
- package/dist/streaming/lifecycle.d.ts +33 -0
- package/dist/streaming/lifecycle.js +185 -0
- package/dist/streaming/lifecycle.js.map +1 -0
- package/dist/streaming/monitor.d.ts +74 -0
- package/dist/streaming/monitor.js +1310 -0
- package/dist/streaming/monitor.js.map +1 -0
- package/dist/streaming/mp4Parser.d.ts +74 -0
- package/dist/streaming/mp4Parser.js +566 -0
- package/dist/streaming/mp4Parser.js.map +1 -0
- package/dist/streaming/mpegts.d.ts +14 -0
- package/dist/streaming/mpegts.js +248 -0
- package/dist/streaming/mpegts.js.map +1 -0
- package/dist/streaming/registry.d.ts +119 -0
- package/dist/streaming/registry.js +127 -0
- package/dist/streaming/registry.js.map +1 -0
- package/dist/streaming/setup.d.ts +135 -0
- package/dist/streaming/setup.js +670 -0
- package/dist/streaming/setup.js.map +1 -0
- package/dist/streaming/showInfo.d.ts +30 -0
- package/dist/streaming/showInfo.js +362 -0
- package/dist/streaming/showInfo.js.map +1 -0
- package/dist/streaming/statusEmitter.d.ts +125 -0
- package/dist/streaming/statusEmitter.js +139 -0
- package/dist/streaming/statusEmitter.js.map +1 -0
- package/dist/types/index.d.ts +403 -0
- package/dist/types/index.js +6 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/debugFilter.d.ts +38 -0
- package/dist/utils/debugFilter.js +157 -0
- package/dist/utils/debugFilter.js.map +1 -0
- package/dist/utils/delay.d.ts +6 -0
- package/dist/utils/delay.js +15 -0
- package/dist/utils/delay.js.map +1 -0
- package/dist/utils/errors.d.ts +15 -0
- package/dist/utils/errors.js +40 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/evaluate.d.ts +51 -0
- package/dist/utils/evaluate.js +124 -0
- package/dist/utils/evaluate.js.map +1 -0
- package/dist/utils/ffmpeg.d.ts +65 -0
- package/dist/utils/ffmpeg.js +317 -0
- package/dist/utils/ffmpeg.js.map +1 -0
- package/dist/utils/fileLogger.d.ts +25 -0
- package/dist/utils/fileLogger.js +248 -0
- package/dist/utils/fileLogger.js.map +1 -0
- package/dist/utils/format.d.ts +16 -0
- package/dist/utils/format.js +46 -0
- package/dist/utils/format.js.map +1 -0
- package/dist/utils/html.d.ts +6 -0
- package/dist/utils/html.js +24 -0
- package/dist/utils/html.js.map +1 -0
- package/dist/utils/index.d.ts +15 -0
- package/dist/utils/index.js +20 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/logEmitter.d.ts +17 -0
- package/dist/utils/logEmitter.js +30 -0
- package/dist/utils/logEmitter.js.map +1 -0
- package/dist/utils/logger.d.ts +82 -0
- package/dist/utils/logger.js +219 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/m3u.d.ts +32 -0
- package/dist/utils/m3u.js +148 -0
- package/dist/utils/m3u.js.map +1 -0
- package/dist/utils/morganStream.d.ts +7 -0
- package/dist/utils/morganStream.js +33 -0
- package/dist/utils/morganStream.js.map +1 -0
- package/dist/utils/platform.d.ts +64 -0
- package/dist/utils/platform.js +157 -0
- package/dist/utils/platform.js.map +1 -0
- package/dist/utils/retry.d.ts +15 -0
- package/dist/utils/retry.js +82 -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/dist/utils/version.d.ts +37 -0
- package/dist/utils/version.js +228 -0
- package/dist/utils/version.js.map +1 -0
- package/package.json +92 -0
- package/prismcast.png +0 -0
- package/prismcast.svg +74 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Express } from "express";
|
|
2
|
+
/**
|
|
3
|
+
* Configures all HTTP endpoints on the Express application.
|
|
4
|
+
* @param app - The Express application.
|
|
5
|
+
*/
|
|
6
|
+
export declare function setupRoutes(app: Express): void;
|
|
7
|
+
export { setupAssetEndpoints } from "./assets.js";
|
|
8
|
+
export { setupAuthEndpoint } from "./auth.js";
|
|
9
|
+
export { setupChannelsEndpoint } from "./channels.js";
|
|
10
|
+
export { setupConfigEndpoint } from "./config.js";
|
|
11
|
+
export { setupDebugEndpoint } from "./debug.js";
|
|
12
|
+
export { setupHealthEndpoint } from "./health.js";
|
|
13
|
+
export { setupHLSRoutes } from "./hls.js";
|
|
14
|
+
export { setupLogsEndpoint } from "./logs.js";
|
|
15
|
+
export { setupMpegTsRoutes } from "./mpegts.js";
|
|
16
|
+
export { setupPlayEndpoint } from "./play.js";
|
|
17
|
+
export { generatePlaylistContent, resolveBaseUrl, setupPlaylistEndpoint } from "./playlist.js";
|
|
18
|
+
export { setupRootEndpoint } from "./root.js";
|
|
19
|
+
export { setupStreamsEndpoint } from "./streams.js";
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { setupAssetEndpoints } from "./assets.js";
|
|
2
|
+
import { setupAuthEndpoint } from "./auth.js";
|
|
3
|
+
import { setupChannelsEndpoint } from "./channels.js";
|
|
4
|
+
import { setupConfigEndpoint } from "./config.js";
|
|
5
|
+
import { setupDebugEndpoint } from "./debug.js";
|
|
6
|
+
import { setupHLSRoutes } from "./hls.js";
|
|
7
|
+
import { setupHealthEndpoint } from "./health.js";
|
|
8
|
+
import { setupLogsEndpoint } from "./logs.js";
|
|
9
|
+
import { setupMpegTsRoutes } from "./mpegts.js";
|
|
10
|
+
import { setupPlayEndpoint } from "./play.js";
|
|
11
|
+
import { setupPlaylistEndpoint } from "./playlist.js";
|
|
12
|
+
import { setupRootEndpoint } from "./root.js";
|
|
13
|
+
import { setupStreamsEndpoint } from "./streams.js";
|
|
14
|
+
/* This module aggregates all route setup functions and provides a single function to configure all HTTP endpoints on the Express application.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Configures all HTTP endpoints on the Express application.
|
|
18
|
+
* @param app - The Express application.
|
|
19
|
+
*/
|
|
20
|
+
export function setupRoutes(app) {
|
|
21
|
+
setupAssetEndpoints(app);
|
|
22
|
+
setupAuthEndpoint(app);
|
|
23
|
+
setupChannelsEndpoint(app);
|
|
24
|
+
setupConfigEndpoint(app);
|
|
25
|
+
setupDebugEndpoint(app);
|
|
26
|
+
setupHealthEndpoint(app);
|
|
27
|
+
setupHLSRoutes(app);
|
|
28
|
+
setupLogsEndpoint(app);
|
|
29
|
+
setupMpegTsRoutes(app);
|
|
30
|
+
setupPlayEndpoint(app);
|
|
31
|
+
setupPlaylistEndpoint(app);
|
|
32
|
+
setupRootEndpoint(app);
|
|
33
|
+
setupStreamsEndpoint(app);
|
|
34
|
+
}
|
|
35
|
+
// Re-export individual setup functions for selective use if needed.
|
|
36
|
+
export { setupAssetEndpoints } from "./assets.js";
|
|
37
|
+
export { setupAuthEndpoint } from "./auth.js";
|
|
38
|
+
export { setupChannelsEndpoint } from "./channels.js";
|
|
39
|
+
export { setupConfigEndpoint } from "./config.js";
|
|
40
|
+
export { setupDebugEndpoint } from "./debug.js";
|
|
41
|
+
export { setupHealthEndpoint } from "./health.js";
|
|
42
|
+
export { setupHLSRoutes } from "./hls.js";
|
|
43
|
+
export { setupLogsEndpoint } from "./logs.js";
|
|
44
|
+
export { setupMpegTsRoutes } from "./mpegts.js";
|
|
45
|
+
export { setupPlayEndpoint } from "./play.js";
|
|
46
|
+
export { generatePlaylistContent, resolveBaseUrl, setupPlaylistEndpoint } from "./playlist.js";
|
|
47
|
+
export { setupRootEndpoint } from "./root.js";
|
|
48
|
+
export { setupStreamsEndpoint } from "./streams.js";
|
|
49
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/routes/index.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAEpD;GACG;AAEH;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,GAAY;IAEtC,mBAAmB,CAAC,GAAG,CAAC,CAAC;IACzB,iBAAiB,CAAC,GAAG,CAAC,CAAC;IACvB,qBAAqB,CAAC,GAAG,CAAC,CAAC;IAC3B,mBAAmB,CAAC,GAAG,CAAC,CAAC;IACzB,kBAAkB,CAAC,GAAG,CAAC,CAAC;IACxB,mBAAmB,CAAC,GAAG,CAAC,CAAC;IACzB,cAAc,CAAC,GAAG,CAAC,CAAC;IACpB,iBAAiB,CAAC,GAAG,CAAC,CAAC;IACvB,iBAAiB,CAAC,GAAG,CAAC,CAAC;IACvB,iBAAiB,CAAC,GAAG,CAAC,CAAC;IACvB,qBAAqB,CAAC,GAAG,CAAC,CAAC;IAC3B,iBAAiB,CAAC,GAAG,CAAC,CAAC;IACvB,oBAAoB,CAAC,GAAG,CAAC,CAAC;AAC5B,CAAC;AAED,oEAAoE;AACpE,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,EAAE,uBAAuB,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAC/F,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { isConsoleLogging, subscribeToLogs } from "../utils/index.js";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import os from "os";
|
|
4
|
+
import path from "path";
|
|
5
|
+
const { promises: fsPromises } = fs;
|
|
6
|
+
/* The log file uses a consistent format that can be parsed with a regular expression. Each line starts with a bracketed timestamp, optionally followed by a bracketed
|
|
7
|
+
* level indicator, then the message content. Log files may contain ANSI color codes for terminal viewing, which are stripped before parsing.
|
|
8
|
+
*/
|
|
9
|
+
// Pattern to match ANSI escape sequences (SGR - Select Graphic Rendition).
|
|
10
|
+
// eslint-disable-next-line no-control-regex
|
|
11
|
+
const ANSI_PATTERN = /\x1b\[[0-9;]*m/g;
|
|
12
|
+
// Pattern to match log entries: [timestamp] optional [LEVEL] or [LEVEL:category] message. The category suffix handles the new DEBUG:category format while
|
|
13
|
+
// remaining backward-compatible with plain [DEBUG] entries from older log files.
|
|
14
|
+
const LOG_LINE_PATTERN = /^\[(\d{4}\/\d{2}\/\d{2} \d{2}:\d{2}:\d{2}\.\d{3})\] (?:\[(WARN|ERROR|DEBUG(?::[^\]]+)?)\] )?(.*)$/;
|
|
15
|
+
/**
|
|
16
|
+
* Strips ANSI escape codes from a string. Used to clean log file lines that may contain terminal color codes.
|
|
17
|
+
* @param text - The text that may contain ANSI codes.
|
|
18
|
+
* @returns The text with all ANSI codes removed.
|
|
19
|
+
*/
|
|
20
|
+
function stripAnsiCodes(text) {
|
|
21
|
+
return text.replace(ANSI_PATTERN, "");
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Parses a single log line into a structured entry.
|
|
25
|
+
* @param line - The raw log line from the file.
|
|
26
|
+
* @returns The parsed log entry, or null if the line does not match the expected format.
|
|
27
|
+
*/
|
|
28
|
+
function parseLogLine(line) {
|
|
29
|
+
// Strip ANSI color codes before parsing.
|
|
30
|
+
const cleanLine = stripAnsiCodes(line);
|
|
31
|
+
const match = LOG_LINE_PATTERN.exec(cleanLine);
|
|
32
|
+
if (!match) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
const [, timestamp, levelStr, message] = match;
|
|
36
|
+
let level = "info";
|
|
37
|
+
let categoryTag;
|
|
38
|
+
if (levelStr?.startsWith("DEBUG")) {
|
|
39
|
+
level = "debug";
|
|
40
|
+
// Extract the category suffix from "DEBUG:tuning:hulu" → "tuning:hulu". This preserves category information for web UI rendering so file-loaded entries
|
|
41
|
+
// display the same [DEBUG:category] badge as live SSE entries.
|
|
42
|
+
const colonIndex = levelStr.indexOf(":");
|
|
43
|
+
if (colonIndex !== -1) {
|
|
44
|
+
categoryTag = levelStr.substring(colonIndex + 1);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
else if (levelStr === "WARN") {
|
|
48
|
+
level = "warn";
|
|
49
|
+
}
|
|
50
|
+
else if (levelStr === "ERROR") {
|
|
51
|
+
level = "error";
|
|
52
|
+
}
|
|
53
|
+
const entry = { level, message, timestamp };
|
|
54
|
+
if (categoryTag) {
|
|
55
|
+
entry.categoryTag = categoryTag;
|
|
56
|
+
}
|
|
57
|
+
return entry;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Reads and parses the log file, returning the most recent entries.
|
|
61
|
+
* @param lines - Maximum number of lines to return.
|
|
62
|
+
* @param levelFilter - Optional level filter (error, warn, info, or undefined for all).
|
|
63
|
+
* @returns The parsed log entries and metadata.
|
|
64
|
+
*/
|
|
65
|
+
async function readLogEntries(lines, levelFilter) {
|
|
66
|
+
// Check if using console logging mode (no file logs available).
|
|
67
|
+
if (isConsoleLogging()) {
|
|
68
|
+
return { entries: [], filtered: 0, mode: "console", total: 0 };
|
|
69
|
+
}
|
|
70
|
+
const logFilePath = path.join(os.homedir(), ".prismcast", "prismcast.log");
|
|
71
|
+
try {
|
|
72
|
+
const content = await fsPromises.readFile(logFilePath, "utf-8");
|
|
73
|
+
const allLines = content.split("\n").filter((line) => line.trim().length > 0);
|
|
74
|
+
// Parse all lines into entries.
|
|
75
|
+
const allEntries = [];
|
|
76
|
+
for (const line of allLines) {
|
|
77
|
+
const entry = parseLogLine(line);
|
|
78
|
+
if (entry) {
|
|
79
|
+
allEntries.push(entry);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const total = allEntries.length;
|
|
83
|
+
// Apply level filter if specified.
|
|
84
|
+
let filteredEntries = allEntries;
|
|
85
|
+
if (levelFilter && ["error", "info", "warn"].includes(levelFilter)) {
|
|
86
|
+
filteredEntries = allEntries.filter((entry) => entry.level === levelFilter);
|
|
87
|
+
}
|
|
88
|
+
const filtered = filteredEntries.length;
|
|
89
|
+
// Return the most recent entries (last N lines).
|
|
90
|
+
const recentEntries = filteredEntries.slice(-lines);
|
|
91
|
+
return { entries: recentEntries, filtered, mode: "file", total };
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
// File does not exist or is unreadable.
|
|
95
|
+
if (error.code === "ENOENT") {
|
|
96
|
+
return { entries: [], filtered: 0, mode: "file", total: 0 };
|
|
97
|
+
}
|
|
98
|
+
throw error;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/* The /logs endpoint provides access to recent application log entries. It supports query parameters for filtering and limiting results, and returns JSON data
|
|
102
|
+
* suitable for both API consumption and the landing page log viewer.
|
|
103
|
+
*/
|
|
104
|
+
/**
|
|
105
|
+
* Creates the logs endpoint for viewing application log entries.
|
|
106
|
+
* @param app - The Express application.
|
|
107
|
+
*/
|
|
108
|
+
export function setupLogsEndpoint(app) {
|
|
109
|
+
app.get("/logs", async (req, res) => {
|
|
110
|
+
// Parse query parameters.
|
|
111
|
+
const linesParam = parseInt(req.query.lines, 10);
|
|
112
|
+
const lines = (!isNaN(linesParam) && (linesParam > 0) && (linesParam <= 1000)) ? linesParam : 100;
|
|
113
|
+
const level = req.query.level;
|
|
114
|
+
try {
|
|
115
|
+
const logsResponse = await readLogEntries(lines, level);
|
|
116
|
+
res.json(logsResponse);
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
res.status(500).json({
|
|
120
|
+
entries: [],
|
|
121
|
+
error: "Failed to read log file.",
|
|
122
|
+
filtered: 0,
|
|
123
|
+
mode: "file",
|
|
124
|
+
total: 0
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
/* The /logs/stream endpoint provides real-time log entries via Server-Sent Events. Connected clients receive log entries as they are written, eliminating the need
|
|
129
|
+
* for polling. The connection remains open until the client disconnects.
|
|
130
|
+
*/
|
|
131
|
+
app.get("/logs/stream", (req, res) => {
|
|
132
|
+
// Set SSE headers. The Content-Type must be text/event-stream for the browser to recognize this as an SSE connection. Cache-Control prevents proxies from buffering
|
|
133
|
+
// the stream, and Connection: keep-alive ensures the connection stays open.
|
|
134
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
135
|
+
res.setHeader("Connection", "keep-alive");
|
|
136
|
+
res.setHeader("Content-Type", "text/event-stream");
|
|
137
|
+
// Disable response buffering to ensure events are sent immediately.
|
|
138
|
+
res.flushHeaders();
|
|
139
|
+
// Optional level filter from query parameter.
|
|
140
|
+
const levelFilter = req.query.level;
|
|
141
|
+
const validLevels = ["error", "info", "warn"];
|
|
142
|
+
const filterLevel = (levelFilter && validLevels.includes(levelFilter)) ? levelFilter : null;
|
|
143
|
+
// Subscribe to log entries and send them as SSE events.
|
|
144
|
+
const unsubscribe = subscribeToLogs((entry) => {
|
|
145
|
+
// Apply level filter if specified.
|
|
146
|
+
if (filterLevel && (entry.level !== filterLevel)) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
// Format the entry as an SSE event. Each event consists of "data:" lines followed by a blank line.
|
|
150
|
+
const eventData = JSON.stringify(entry);
|
|
151
|
+
res.write("data: " + eventData + "\n\n");
|
|
152
|
+
});
|
|
153
|
+
// Send a named heartbeat event every 30 seconds to keep the connection alive through proxies and allow clients to detect staleness.
|
|
154
|
+
const heartbeatInterval = setInterval(() => {
|
|
155
|
+
res.write("event: heartbeat\ndata: \n\n");
|
|
156
|
+
}, 30000);
|
|
157
|
+
// Clean up when the client disconnects.
|
|
158
|
+
req.on("close", () => {
|
|
159
|
+
clearInterval(heartbeatInterval);
|
|
160
|
+
unsubscribe();
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
//# sourceMappingURL=logs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logs.js","sourceRoot":"","sources":["../../src/routes/logs.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { handleMpegTsStream } from "../streaming/mpegts.js";
|
|
2
|
+
/* This module registers the HTTP route for MPEG-TS streaming:
|
|
3
|
+
*
|
|
4
|
+
* - GET /stream/:name - Returns a continuous MPEG-TS byte stream for a channel (starts stream if needed)
|
|
5
|
+
*
|
|
6
|
+
* This endpoint is used by HDHomeRun-compatible clients (such as Plex) that expect raw MPEG-TS when tuning a channel. The stream shares the same underlying capture
|
|
7
|
+
* as HLS — no additional browser tabs or capture sessions are created.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Sets up MPEG-TS streaming routes on the Express application.
|
|
11
|
+
* @param app - The Express application.
|
|
12
|
+
*/
|
|
13
|
+
export function setupMpegTsRoutes(app) {
|
|
14
|
+
// MPEG-TS stream endpoint for HDHomeRun-compatible clients.
|
|
15
|
+
app.get("/stream/:name", (req, res) => {
|
|
16
|
+
void handleMpegTsStream(req, res);
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=mpegts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mpegts.js","sourceRoot":"","sources":["../../src/routes/mpegts.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAE5D;;;;;;GAMG;AAEH;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,GAAY;IAE5C,4DAA4D;IAC5D,GAAG,CAAC,GAAG,CAAC,eAAe,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;QAEpC,KAAK,kBAAkB,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IACpC,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { handlePlayStream } from "../streaming/hls.js";
|
|
2
|
+
/* This module registers the HTTP route for ad-hoc URL streaming:
|
|
3
|
+
*
|
|
4
|
+
* - GET /play?url=<url>&profile=<name> - Starts an HLS stream for an arbitrary URL and redirects to the HLS playlist path.
|
|
5
|
+
*
|
|
6
|
+
* This endpoint enables streaming URLs that are not predefined as channels. The stream is identified by a synthetic key derived from the URL hash, and the client is
|
|
7
|
+
* redirected to /hls/<key>/stream.m3u8 where existing HLS handlers serve the content.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Sets up the ad-hoc streaming route on the Express application.
|
|
11
|
+
* @param app - The Express application.
|
|
12
|
+
*/
|
|
13
|
+
export function setupPlayEndpoint(app) {
|
|
14
|
+
app.get("/play", (req, res) => {
|
|
15
|
+
void handlePlayStream(req, res);
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=play.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"play.js","sourceRoot":"","sources":["../../src/routes/play.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAEvD;;;;;;GAMG;AAEH;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,GAAY;IAE5C,GAAG,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;QAE5B,KAAK,gBAAgB,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IAClC,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Express, Request } from "express";
|
|
2
|
+
interface ProviderFilter {
|
|
3
|
+
readonly exclude: boolean;
|
|
4
|
+
readonly tags: string[];
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Resolves the base URL from an incoming request by examining headers in priority order. This ensures that playlist URLs and other generated links use the same
|
|
8
|
+
* host and protocol that the client used to connect, even when behind a reverse proxy. The resolution order is:
|
|
9
|
+
*
|
|
10
|
+
* 1. X-Forwarded-Host header (set by reverse proxies like nginx, Traefik)
|
|
11
|
+
* 2. Host header (standard HTTP/1.1 header)
|
|
12
|
+
* 3. Fallback to configured server host and port
|
|
13
|
+
*
|
|
14
|
+
* For protocol, Express's req.protocol already respects X-Forwarded-Proto when trust proxy is enabled.
|
|
15
|
+
*
|
|
16
|
+
* @param req - The Express request object.
|
|
17
|
+
* @returns The base URL (e.g., "http://localhost:5589" or "https://myserver.example.com").
|
|
18
|
+
*/
|
|
19
|
+
export declare function resolveBaseUrl(req: Request): string;
|
|
20
|
+
/**
|
|
21
|
+
* Generates the M3U playlist content for display on the landing page or the playlist endpoint. The playlist includes all configured video channels with their
|
|
22
|
+
* stream URLs dynamically constructed from the provided base URL.
|
|
23
|
+
* @param baseUrl - The base URL to use for stream URLs (e.g., "http://localhost:5589").
|
|
24
|
+
* @param filter - Optional provider filter based on the currently selected provider for each channel. In include mode, only channels whose selected provider matches
|
|
25
|
+
* a filter tag are included. In exclude mode, channels whose selected provider matches any filter tag are excluded. When omitted, all channels are included.
|
|
26
|
+
* @returns The M3U playlist content.
|
|
27
|
+
*/
|
|
28
|
+
export declare function generatePlaylistContent(baseUrl: string, filter?: ProviderFilter): string;
|
|
29
|
+
/**
|
|
30
|
+
* Creates the playlist endpoint that serves an M3U playlist in Channels DVR format. The playlist lists all configured channels with their stream URLs, allowing
|
|
31
|
+
* Channels DVR to import them as custom channels. The endpoint dynamically constructs URLs using the request host header so the playlist works regardless of how
|
|
32
|
+
* the server is accessed (localhost, IP address, or hostname).
|
|
33
|
+
* @param app - The Express application.
|
|
34
|
+
*/
|
|
35
|
+
export declare function setupPlaylistEndpoint(app: Express): void;
|
|
36
|
+
export {};
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { getAllProviderTags, getProviderTagForChannel, resolveProviderKey } from "../config/providers.js";
|
|
2
|
+
import { CONFIG } from "../config/index.js";
|
|
3
|
+
import { getAllChannels } from "../config/userChannels.js";
|
|
4
|
+
import { resolveProfile } from "../config/profiles.js";
|
|
5
|
+
/**
|
|
6
|
+
* Parses and validates a provider filter query parameter. The parameter is a comma-separated list of provider tags with optional `-` prefix for exclusion mode. All
|
|
7
|
+
* tags must be either include (no prefix) or exclude (`-` prefix) — mixing is not allowed. Tags are case-insensitive and validated against known provider tags.
|
|
8
|
+
* @param param - The raw query parameter string (e.g., "yttv,sling" or "-hulu,-sling").
|
|
9
|
+
* @returns An object with `filter` on success, or `error` with a descriptive message and `validTags` list on failure.
|
|
10
|
+
*/
|
|
11
|
+
function parseProviderFilter(param) {
|
|
12
|
+
const tokens = param.split(",").map((t) => t.trim().toLowerCase()).filter((t) => t.length > 0);
|
|
13
|
+
if (tokens.length === 0) {
|
|
14
|
+
return { error: "Empty provider filter.", validTags: [] };
|
|
15
|
+
}
|
|
16
|
+
// Classify tokens as include or exclude based on the `-` prefix.
|
|
17
|
+
const excludeTokens = [];
|
|
18
|
+
const includeTokens = [];
|
|
19
|
+
for (const token of tokens) {
|
|
20
|
+
if (token.startsWith("-")) {
|
|
21
|
+
excludeTokens.push(token.slice(1));
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
includeTokens.push(token);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
// Reject mixed mode — all tokens must be either include or exclude.
|
|
28
|
+
if ((excludeTokens.length > 0) && (includeTokens.length > 0)) {
|
|
29
|
+
return { error: "Cannot mix include and exclude filters. Use either \"tag1,tag2\" (include) or \"-tag1,-tag2\" (exclude).", validTags: [] };
|
|
30
|
+
}
|
|
31
|
+
const isExclude = excludeTokens.length > 0;
|
|
32
|
+
const tags = isExclude ? excludeTokens : includeTokens;
|
|
33
|
+
// Validate all tags against known provider tags.
|
|
34
|
+
const allTags = getAllProviderTags();
|
|
35
|
+
const knownTags = new Set(allTags.map((p) => p.tag));
|
|
36
|
+
const unknownTags = tags.filter((tag) => !knownTags.has(tag));
|
|
37
|
+
if (unknownTags.length > 0) {
|
|
38
|
+
return { error: "Unknown provider tag(s): " + unknownTags.join(", ") + ".", validTags: allTags.map((p) => p.tag).sort() };
|
|
39
|
+
}
|
|
40
|
+
return { filter: { exclude: isExclude, tags } };
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Resolves the base URL from an incoming request by examining headers in priority order. This ensures that playlist URLs and other generated links use the same
|
|
44
|
+
* host and protocol that the client used to connect, even when behind a reverse proxy. The resolution order is:
|
|
45
|
+
*
|
|
46
|
+
* 1. X-Forwarded-Host header (set by reverse proxies like nginx, Traefik)
|
|
47
|
+
* 2. Host header (standard HTTP/1.1 header)
|
|
48
|
+
* 3. Fallback to configured server host and port
|
|
49
|
+
*
|
|
50
|
+
* For protocol, Express's req.protocol already respects X-Forwarded-Proto when trust proxy is enabled.
|
|
51
|
+
*
|
|
52
|
+
* @param req - The Express request object.
|
|
53
|
+
* @returns The base URL (e.g., "http://localhost:5589" or "https://myserver.example.com").
|
|
54
|
+
*/
|
|
55
|
+
export function resolveBaseUrl(req) {
|
|
56
|
+
// Express's req.protocol already handles X-Forwarded-Proto when trust proxy is enabled, so we can use it directly.
|
|
57
|
+
const protocol = req.protocol;
|
|
58
|
+
// Check X-Forwarded-Host first (may contain multiple hosts if proxied through multiple layers, take the first one). Then fall back to the standard Host header,
|
|
59
|
+
// and finally to the configured server settings.
|
|
60
|
+
const forwardedHost = req.get("x-forwarded-host");
|
|
61
|
+
const host = forwardedHost ? forwardedHost.split(",")[0].trim() : req.get("host");
|
|
62
|
+
const fallbackHost = CONFIG.server.host + ":" + String(CONFIG.server.port);
|
|
63
|
+
const resolvedHost = host ?? fallbackHost;
|
|
64
|
+
return protocol + "://" + resolvedHost;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Generates the M3U playlist content for display on the landing page or the playlist endpoint. The playlist includes all configured video channels with their
|
|
68
|
+
* stream URLs dynamically constructed from the provided base URL.
|
|
69
|
+
* @param baseUrl - The base URL to use for stream URLs (e.g., "http://localhost:5589").
|
|
70
|
+
* @param filter - Optional provider filter based on the currently selected provider for each channel. In include mode, only channels whose selected provider matches
|
|
71
|
+
* a filter tag are included. In exclude mode, channels whose selected provider matches any filter tag are excluded. When omitted, all channels are included.
|
|
72
|
+
* @returns The M3U playlist content.
|
|
73
|
+
*/
|
|
74
|
+
export function generatePlaylistContent(baseUrl, filter) {
|
|
75
|
+
const channels = getAllChannels();
|
|
76
|
+
const lines = ["#EXTM3U", ""];
|
|
77
|
+
const channelNames = Object.keys(channels).sort();
|
|
78
|
+
for (const name of channelNames) {
|
|
79
|
+
// Apply the provider filter if specified.
|
|
80
|
+
if (filter) {
|
|
81
|
+
const selectedKey = resolveProviderKey(name);
|
|
82
|
+
const selectedTag = getProviderTagForChannel(selectedKey);
|
|
83
|
+
const hasMatch = filter.tags.includes(selectedTag);
|
|
84
|
+
// In include mode, skip channels whose selected provider doesn't match any filter tag. In exclude mode, skip channels whose selected provider matches a filter tag.
|
|
85
|
+
if (filter.exclude ? hasMatch : !hasMatch) {
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
const channel = channels[name];
|
|
90
|
+
// Skip channels that are marked as static pages since they are not video streams.
|
|
91
|
+
const profile = resolveProfile(channel.profile);
|
|
92
|
+
if (profile.noVideo) {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
// We use the channel key as the channel-id and the friendly name for display. HLS URLs are used for Channels DVR compatibility.
|
|
96
|
+
const displayName = channel.name ?? name;
|
|
97
|
+
const streamUrl = baseUrl + "/hls/" + name + "/stream.m3u8";
|
|
98
|
+
// Build the EXTINF line with required channel-id attribute and tvg-name for the friendly display name. Include tvc-guide-stationid for Gracenote guide data
|
|
99
|
+
// when a stationId is defined.
|
|
100
|
+
const stationIdAttr = channel.stationId ? " tvc-guide-stationid=\"" + channel.stationId + "\"" : "";
|
|
101
|
+
const extinfLine = "#EXTINF:-1 channel-id=\"" + name + "\" tvg-name=\"" + displayName + "\"" + stationIdAttr + "," + displayName;
|
|
102
|
+
lines.push(extinfLine);
|
|
103
|
+
lines.push(streamUrl);
|
|
104
|
+
lines.push("");
|
|
105
|
+
}
|
|
106
|
+
return lines.join("\n");
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Creates the playlist endpoint that serves an M3U playlist in Channels DVR format. The playlist lists all configured channels with their stream URLs, allowing
|
|
110
|
+
* Channels DVR to import them as custom channels. The endpoint dynamically constructs URLs using the request host header so the playlist works regardless of how
|
|
111
|
+
* the server is accessed (localhost, IP address, or hostname).
|
|
112
|
+
* @param app - The Express application.
|
|
113
|
+
*/
|
|
114
|
+
export function setupPlaylistEndpoint(app) {
|
|
115
|
+
// GET /playlist - Returns the M3U playlist file. Supports optional ?provider= query parameter for filtering channels by streaming provider.
|
|
116
|
+
app.get("/playlist", (req, res) => {
|
|
117
|
+
const baseUrl = resolveBaseUrl(req);
|
|
118
|
+
const providerParam = typeof req.query.provider === "string" ? req.query.provider.trim() : undefined;
|
|
119
|
+
let filter;
|
|
120
|
+
// Parse and validate the provider filter if specified.
|
|
121
|
+
if (providerParam) {
|
|
122
|
+
const result = parseProviderFilter(providerParam);
|
|
123
|
+
if ("error" in result) {
|
|
124
|
+
res.status(400).json({ error: result.error, validTags: result.validTags });
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
filter = result.filter;
|
|
128
|
+
}
|
|
129
|
+
const playlist = generatePlaylistContent(baseUrl, filter);
|
|
130
|
+
res.set("Content-Type", "audio/x-mpegurl");
|
|
131
|
+
res.send(playlist);
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=playlist.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"playlist.js","sourceRoot":"","sources":["../../src/routes/playlist.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Express } from "express";
|
|
2
|
+
/**
|
|
3
|
+
* Configures the root endpoint that serves as a landing page with a tabbed interface containing usage documentation, API reference, playlist, and log viewer.
|
|
4
|
+
* @param app - The Express application.
|
|
5
|
+
*/
|
|
6
|
+
export declare function setupRootEndpoint(app: Express): void;
|