prismcast 1.5.2 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (239) hide show
  1. package/README.md +44 -28
  2. package/dist/app.d.ts +5 -0
  3. package/dist/app.js +63 -5
  4. package/dist/app.js.map +1 -1
  5. package/dist/browser/cdp.js +1 -1
  6. package/dist/browser/cdp.js.map +1 -1
  7. package/dist/browser/channelSelection.d.ts +10 -34
  8. package/dist/browser/channelSelection.js +31 -96
  9. package/dist/browser/channelSelection.js.map +1 -1
  10. package/dist/browser/display.d.ts +19 -0
  11. package/dist/browser/display.js +16 -11
  12. package/dist/browser/display.js.map +1 -1
  13. package/dist/browser/index.d.ts +31 -54
  14. package/dist/browser/index.js +334 -272
  15. package/dist/browser/index.js.map +1 -1
  16. package/dist/browser/login.d.ts +65 -0
  17. package/dist/browser/login.js +193 -0
  18. package/dist/browser/login.js.map +1 -0
  19. package/dist/browser/precaching.js +15 -3
  20. package/dist/browser/precaching.js.map +1 -1
  21. package/dist/browser/tuning/comcastPolymer.d.ts +23 -0
  22. package/dist/browser/tuning/comcastPolymer.js +655 -0
  23. package/dist/browser/tuning/comcastPolymer.js.map +1 -0
  24. package/dist/browser/tuning/cox.d.ts +2 -0
  25. package/dist/browser/tuning/cox.js +14 -0
  26. package/dist/browser/tuning/cox.js.map +1 -0
  27. package/dist/browser/tuning/directv.js +14 -1
  28. package/dist/browser/tuning/directv.js.map +1 -1
  29. package/dist/browser/tuning/fox.js +14 -2
  30. package/dist/browser/tuning/fox.js.map +1 -1
  31. package/dist/browser/tuning/hbo.js +12 -1
  32. package/dist/browser/tuning/hbo.js.map +1 -1
  33. package/dist/browser/tuning/hulu.js +24 -1
  34. package/dist/browser/tuning/hulu.js.map +1 -1
  35. package/dist/browser/tuning/shared.d.ts +46 -0
  36. package/dist/browser/tuning/shared.js +100 -0
  37. package/dist/browser/tuning/shared.js.map +1 -0
  38. package/dist/browser/tuning/sling.js +35 -5
  39. package/dist/browser/tuning/sling.js.map +1 -1
  40. package/dist/browser/tuning/spectrum.js +13 -1
  41. package/dist/browser/tuning/spectrum.js.map +1 -1
  42. package/dist/browser/tuning/thumbnailRow.js +1 -1
  43. package/dist/browser/tuning/thumbnailRow.js.map +1 -1
  44. package/dist/browser/tuning/tileClick.js +1 -1
  45. package/dist/browser/tuning/tileClick.js.map +1 -1
  46. package/dist/browser/tuning/xfinity.d.ts +2 -0
  47. package/dist/browser/tuning/xfinity.js +14 -0
  48. package/dist/browser/tuning/xfinity.js.map +1 -0
  49. package/dist/browser/tuning/youtubeTv.js +15 -1
  50. package/dist/browser/tuning/youtubeTv.js.map +1 -1
  51. package/dist/browser/video.d.ts +20 -0
  52. package/dist/browser/video.js +168 -124
  53. package/dist/browser/video.js.map +1 -1
  54. package/dist/channels/index.d.ts +2 -2
  55. package/dist/channels/index.js +2075 -683
  56. package/dist/channels/index.js.map +1 -1
  57. package/dist/config/health.d.ts +28 -28
  58. package/dist/config/health.js +72 -54
  59. package/dist/config/health.js.map +1 -1
  60. package/dist/config/index.js +29 -64
  61. package/dist/config/index.js.map +1 -1
  62. package/dist/config/paths.d.ts +12 -0
  63. package/dist/config/paths.js +16 -0
  64. package/dist/config/paths.js.map +1 -1
  65. package/dist/config/profiles.d.ts +3 -2
  66. package/dist/config/profiles.js +45 -34
  67. package/dist/config/profiles.js.map +1 -1
  68. package/dist/config/providerPacks.js +4 -1
  69. package/dist/config/providerPacks.js.map +1 -1
  70. package/dist/config/providers.d.ts +19 -5
  71. package/dist/config/providers.js +123 -93
  72. package/dist/config/providers.js.map +1 -1
  73. package/dist/config/sites.d.ts +28 -0
  74. package/dist/config/sites.js +104 -127
  75. package/dist/config/sites.js.map +1 -1
  76. package/dist/config/userChannels.d.ts +53 -2
  77. package/dist/config/userChannels.js +222 -13
  78. package/dist/config/userChannels.js.map +1 -1
  79. package/dist/config/userConfig.d.ts +2 -1
  80. package/dist/config/userConfig.js +6 -15
  81. package/dist/config/userConfig.js.map +1 -1
  82. package/dist/config/userProfiles.d.ts +0 -1
  83. package/dist/config/userProfiles.js +18 -10
  84. package/dist/config/userProfiles.js.map +1 -1
  85. package/dist/hdhr/channelMap.d.ts +1 -1
  86. package/dist/hdhr/channelMap.js +4 -4
  87. package/dist/hdhr/channelMap.js.map +1 -1
  88. package/dist/index.js +25 -22
  89. package/dist/index.js.map +1 -1
  90. package/dist/native/decrypt.d.ts +32 -0
  91. package/dist/native/decrypt.js +85 -0
  92. package/dist/native/decrypt.js.map +1 -0
  93. package/dist/native/index.d.ts +61 -0
  94. package/dist/native/index.js +342 -0
  95. package/dist/native/index.js.map +1 -0
  96. package/dist/native/intercept.d.ts +35 -0
  97. package/dist/native/intercept.js +184 -0
  98. package/dist/native/intercept.js.map +1 -0
  99. package/dist/native/probe.d.ts +49 -0
  100. package/dist/native/probe.js +294 -0
  101. package/dist/native/probe.js.map +1 -0
  102. package/dist/native/proxy.d.ts +54 -0
  103. package/dist/native/proxy.js +947 -0
  104. package/dist/native/proxy.js.map +1 -0
  105. package/dist/routes/components.d.ts +43 -0
  106. package/dist/routes/components.js +122 -0
  107. package/dist/routes/components.js.map +1 -1
  108. package/dist/routes/config/channels/index.d.ts +2 -2
  109. package/dist/routes/config/channels/index.js +1 -1
  110. package/dist/routes/config/channels/index.js.map +1 -1
  111. package/dist/routes/config/channels/routes.js +443 -65
  112. package/dist/routes/config/channels/routes.js.map +1 -1
  113. package/dist/routes/config/channels/table.d.ts +76 -0
  114. package/dist/routes/config/channels/table.js +369 -89
  115. package/dist/routes/config/channels/table.js.map +1 -1
  116. package/dist/routes/config/index.d.ts +1 -1
  117. package/dist/routes/config/index.js +1 -1
  118. package/dist/routes/config/index.js.map +1 -1
  119. package/dist/routes/config/providers.d.ts +6 -6
  120. package/dist/routes/config/providers.js +73 -99
  121. package/dist/routes/config/providers.js.map +1 -1
  122. package/dist/routes/debug.js +5 -5
  123. package/dist/routes/debug.js.map +1 -1
  124. package/dist/routes/hls.js +12 -2
  125. package/dist/routes/hls.js.map +1 -1
  126. package/dist/routes/index.d.ts +0 -15
  127. package/dist/routes/index.js +2 -16
  128. package/dist/routes/index.js.map +1 -1
  129. package/dist/routes/providers.js +106 -3
  130. package/dist/routes/providers.js.map +1 -1
  131. package/dist/routes/root/content.js +124 -47
  132. package/dist/routes/root/content.js.map +1 -1
  133. package/dist/routes/root/index.js +2 -1
  134. package/dist/routes/root/index.js.map +1 -1
  135. package/dist/routes/root/scripts/channels.js +772 -312
  136. package/dist/routes/root/scripts/channels.js.map +1 -1
  137. package/dist/routes/root/scripts/config.js +596 -544
  138. package/dist/routes/root/scripts/config.js.map +1 -1
  139. package/dist/routes/root/scripts/index.d.ts +1 -0
  140. package/dist/routes/root/scripts/index.js +1 -0
  141. package/dist/routes/root/scripts/index.js.map +1 -1
  142. package/dist/routes/root/scripts/shared.d.ts +6 -0
  143. package/dist/routes/root/scripts/shared.js +391 -0
  144. package/dist/routes/root/scripts/shared.js.map +1 -0
  145. package/dist/routes/root/scripts/status.js +49 -33
  146. package/dist/routes/root/scripts/status.js.map +1 -1
  147. package/dist/routes/root/styles.js +117 -10
  148. package/dist/routes/root/styles.js.map +1 -1
  149. package/dist/service/commands.js +2 -1
  150. package/dist/service/commands.js.map +1 -1
  151. package/dist/streaming/fmp4Segmenter.d.ts +5 -0
  152. package/dist/streaming/fmp4Segmenter.js +146 -42
  153. package/dist/streaming/fmp4Segmenter.js.map +1 -1
  154. package/dist/streaming/hls.d.ts +24 -19
  155. package/dist/streaming/hls.js +603 -297
  156. package/dist/streaming/hls.js.map +1 -1
  157. package/dist/streaming/hlsResume.d.ts +16 -3
  158. package/dist/streaming/hlsResume.js +35 -8
  159. package/dist/streaming/hlsResume.js.map +1 -1
  160. package/dist/streaming/hlsSegments.d.ts +41 -2
  161. package/dist/streaming/hlsSegments.js +131 -28
  162. package/dist/streaming/hlsSegments.js.map +1 -1
  163. package/dist/streaming/lifecycle.js +27 -13
  164. package/dist/streaming/lifecycle.js.map +1 -1
  165. package/dist/streaming/monitor.d.ts +1 -0
  166. package/dist/streaming/monitor.js +862 -474
  167. package/dist/streaming/monitor.js.map +1 -1
  168. package/dist/streaming/mp4Parser.d.ts +52 -6
  169. package/dist/streaming/mp4Parser.js +156 -30
  170. package/dist/streaming/mp4Parser.js.map +1 -1
  171. package/dist/streaming/mpegts.d.ts +2 -2
  172. package/dist/streaming/mpegts.js +156 -109
  173. package/dist/streaming/mpegts.js.map +1 -1
  174. package/dist/streaming/playlistBuilder.d.ts +38 -0
  175. package/dist/streaming/playlistBuilder.js +66 -0
  176. package/dist/streaming/playlistBuilder.js.map +1 -0
  177. package/dist/streaming/preroll.d.ts +126 -0
  178. package/dist/streaming/preroll.js +434 -0
  179. package/dist/streaming/preroll.js.map +1 -0
  180. package/dist/streaming/pretune.d.ts +8 -0
  181. package/dist/streaming/pretune.js +226 -0
  182. package/dist/streaming/pretune.js.map +1 -0
  183. package/dist/streaming/recovery.d.ts +0 -6
  184. package/dist/streaming/recovery.js +0 -8
  185. package/dist/streaming/recovery.js.map +1 -1
  186. package/dist/streaming/registry.d.ts +57 -9
  187. package/dist/streaming/registry.js +26 -7
  188. package/dist/streaming/registry.js.map +1 -1
  189. package/dist/streaming/setup.d.ts +8 -1
  190. package/dist/streaming/setup.js +71 -27
  191. package/dist/streaming/setup.js.map +1 -1
  192. package/dist/streaming/showInfo.d.ts +29 -4
  193. package/dist/streaming/showInfo.js +210 -38
  194. package/dist/streaming/showInfo.js.map +1 -1
  195. package/dist/streaming/statusEmitter.d.ts +20 -6
  196. package/dist/streaming/statusEmitter.js +31 -7
  197. package/dist/streaming/statusEmitter.js.map +1 -1
  198. package/dist/types/channels.d.ts +32 -0
  199. package/dist/types/config.d.ts +2 -2
  200. package/dist/types/index.d.ts +2 -2
  201. package/dist/types/index.js.map +1 -1
  202. package/dist/types/profiles.d.ts +13 -1
  203. package/dist/types/selection.d.ts +5 -1
  204. package/dist/types/shared.d.ts +1 -1
  205. package/dist/types/streaming.d.ts +7 -0
  206. package/dist/utils/chromeFetch.d.ts +20 -0
  207. package/dist/utils/chromeFetch.js +43 -0
  208. package/dist/utils/chromeFetch.js.map +1 -0
  209. package/dist/utils/debugFilter.d.ts +0 -5
  210. package/dist/utils/debugFilter.js +20 -11
  211. package/dist/utils/debugFilter.js.map +1 -1
  212. package/dist/utils/delay.d.ts +28 -0
  213. package/dist/utils/delay.js +35 -1
  214. package/dist/utils/delay.js.map +1 -1
  215. package/dist/utils/evaluate.js +6 -11
  216. package/dist/utils/evaluate.js.map +1 -1
  217. package/dist/utils/ffmpeg.d.ts +14 -12
  218. package/dist/utils/ffmpeg.js +114 -134
  219. package/dist/utils/ffmpeg.js.map +1 -1
  220. package/dist/utils/format.d.ts +6 -0
  221. package/dist/utils/format.js +8 -0
  222. package/dist/utils/format.js.map +1 -1
  223. package/dist/utils/index.d.ts +3 -0
  224. package/dist/utils/index.js +3 -0
  225. package/dist/utils/index.js.map +1 -1
  226. package/dist/utils/logEmitter.js +1 -1
  227. package/dist/utils/logEmitter.js.map +1 -1
  228. package/dist/utils/pid.d.ts +40 -0
  229. package/dist/utils/pid.js +83 -0
  230. package/dist/utils/pid.js.map +1 -0
  231. package/dist/utils/retry.d.ts +15 -9
  232. package/dist/utils/retry.js +7 -23
  233. package/dist/utils/retry.js.map +1 -1
  234. package/dist/utils/timing.d.ts +5 -0
  235. package/dist/utils/timing.js +13 -0
  236. package/dist/utils/timing.js.map +1 -0
  237. package/dist/utils/version.js +2 -2
  238. package/dist/utils/version.js.map +1 -1
  239. package/package.json +7 -7
@@ -6,14 +6,29 @@ import { join } from "node:path";
6
6
  import { spawn } from "node:child_process";
7
7
  // The ffmpeg-for-homebridge package has incorrect type definitions (declares named export but JS uses default export). Cast to the correct type.
8
8
  const ffmpegPath = ffmpegForHomebridge;
9
- /* When using WebM capture mode, Chrome's MediaRecorder outputs WebM container with H264 video and Opus audio. For HLS compatibility, we need fMP4 container with
10
- * H264 video and AAC audio. FFmpeg handles this conversion:
9
+ // FFmpeg stderr noise patterns that are suppressed from logging. The -nostats flag suppresses most progress output, but some FFmpeg builds or versions may still
10
+ // emit these patterns. The filter acts as a safety net to keep logs clean.
11
+ const FFMPEG_NOISE_PATTERNS = ["Press [q] to stop", "frame=", "size=", "time=", "bitrate=", "speed="];
12
+ /**
13
+ * Returns the path to the bundled ffmpeg-for-homebridge binary if it exists on disk. This binary ships as an npm dependency and has a full encoder set (libx264,
14
+ * libx265, zoompan, overlay) regardless of what the user has installed. Used by preroll generation to guarantee HEVC encoding availability without depending on
15
+ * the Channels DVR FFmpeg (which has a minimal encoder set) or a system installation.
16
+ * @returns The absolute path to the bundled FFmpeg, or undefined if the package did not resolve or the binary is missing.
17
+ */
18
+ export function getBundledFFmpegPath() {
19
+ if (ffmpegPath && existsSync(ffmpegPath)) {
20
+ return ffmpegPath;
21
+ }
22
+ return undefined;
23
+ }
24
+ /* When using Matroska capture mode, Chrome's MediaRecorder outputs a Matroska container with video (H264 or HEVC depending on hardware capabilities) and Opus audio.
25
+ * For HLS compatibility, we need an fMP4 container with AAC audio. FFmpeg handles this conversion:
11
26
  *
12
27
  * - Video: Passed through unchanged (copy codec) - no quality loss, minimal CPU
13
28
  * - Audio: Transcoded from Opus to AAC - lightweight operation
14
- * - Container: Converted from WebM to fragmented MP4 with streaming-friendly flags
29
+ * - Container: Converted from Matroska to fragmented MP4 with streaming-friendly flags
15
30
  *
16
- * The FFmpeg process runs for the lifetime of the stream, reading WebM from stdin and writing fMP4 to stdout. This output feeds directly into the existing fMP4
31
+ * The FFmpeg process runs for the lifetime of the stream, reading Matroska from stdin and writing fMP4 to stdout. This output feeds directly into the existing fMP4
17
32
  * segmenter.
18
33
  */
19
34
  /* FFmpeg can be located in several places depending on how it was installed. We check in order of preference:
@@ -102,98 +117,74 @@ export async function resolveFFmpegPath() {
102
117
  cachedFFmpegPath = undefined;
103
118
  return undefined;
104
119
  }
120
+ // MPEG-TS Output Flags.
121
+ /* MPEG-TS muxer flags for the fMP4-to-MPEG-TS remuxer. Tuned to produce output resembling a real HDHomeRun CONNECT DUO (HDTC-2US) ATSC transport stream.
122
+ * Plex's transcoder may make assumptions about stream structure based on the reported device model (PID assignments, PAT/PMT frequency). Using ATSC-conventional
123
+ * values avoids "Invalid argument" failures when Plex tries to transcode the live session for remote clients. These are pure container metadata changes — the actual
124
+ * A/V data is untouched by -c copy.
125
+ */
126
+ const MPEGTS_OUTPUT_FLAGS = [
127
+ "-f", "mpegts",
128
+ "-mpegts_pmt_start_pid", "0x0020",
129
+ "-mpegts_start_pid", "0x0031",
130
+ "-mpegts_service_type", "digital_tv",
131
+ "-pat_period", "0.1",
132
+ "-pcr_period", "40",
133
+ "-flush_packets", "1"
134
+ ];
135
+ // Internal Helper.
105
136
  /**
106
- * Spawns an FFmpeg process configured to transcode WebM (H264+Opus) to fMP4 (H264+AAC). The process reads from stdin and writes to stdout, allowing it to be
107
- * integrated into a Node.js stream pipeline. Video is passed through unchanged; audio is transcoded from Opus to AAC for HLS compatibility.
108
- *
109
- * FFmpeg arguments:
110
- * - `-hide_banner -loglevel warning`: Reduce noise, only show warnings/errors
111
- * - `-probesize 16384`: Limit input probing to 16KB (Chrome's WebM header fits well under this) to minimize startup delay
112
- * - `-i pipe:0`: Read input from stdin
113
- * - `-c:v copy`: Copy video stream without re-encoding (H264 passthrough)
114
- * - `-c:a aac -b:a <bitrate>`: Transcode audio to AAC at specified bitrate
115
- * - `-f mp4`: Output MP4 container format
116
- * - `-movflags frag_keyframe+empty_moov+default_base_moof`: Streaming-friendly fMP4 flags
117
- * - `-flush_packets 1`: Flush output immediately after each packet to minimize latency
118
- * - `pipe:1`: Write output to stdout
119
- * @param audioBitrate - Audio bitrate in bits per second (e.g., 256000 for 256 kbps).
120
- * @param onError - Callback invoked when FFmpeg exits unexpectedly or encounters an error.
121
- * @param streamId - Stream identifier for logging.
122
- * @param comment - Optional comment metadata (channel name or domain) to embed in the output.
137
+ * Spawns an FFmpeg process with shared lifecycle management. Handles stderr logging with noise filtering, exit and error callbacks, and graceful shutdown for all
138
+ * FFmpeg use cases. Callers provide the FFmpeg arguments and a descriptive label; this helper handles everything else.
139
+ * @param options.args - FFmpeg command-line arguments.
140
+ * @param options.label - Descriptive label for log messages and error strings (e.g., "FFmpeg", "MPEG-TS remuxer").
141
+ * @param options.onError - Callback invoked when FFmpeg exits unexpectedly or encounters an error.
142
+ * @param options.streamId - Optional stream identifier for log prefixing.
123
143
  * @returns FFmpeg process wrapper with stdin, stdout, and kill function.
124
144
  */
125
- export function spawnFFmpeg(audioBitrate, onError, streamId, comment) {
126
- // Use the cached FFmpeg path from resolveFFmpegPath(). This should always be set because isFFmpegAvailable() is called during startup, which populates the cache.
127
- // If somehow not set, fall back to "ffmpeg" and let spawn handle the error.
128
- const ffmpegPath = cachedFFmpegPath ?? "ffmpeg";
129
- // Use Apple's AudioToolbox AAC encoder on macOS for better quality and performance. Fall back to FFmpeg's built-in AAC encoder on other platforms.
130
- const aacEncoder = process.platform === "darwin" ? "aac_at" : "aac";
131
- const ffmpegArgs = [
132
- "-hide_banner",
133
- "-loglevel", "warning",
134
- "-probesize", "16384",
135
- "-i", "pipe:0",
136
- "-c:v", "copy",
137
- "-c:a", aacEncoder,
138
- "-b:a", String(audioBitrate),
139
- "-f", "mp4",
140
- "-movflags", "frag_keyframe+empty_moov+default_base_moof+skip_sidx+skip_trailer",
141
- "-flush_packets", "1"
142
- ];
143
- // Add metadata comment if provided. This embeds "PrismCast - <channel>" in the output for identification.
144
- if (comment) {
145
- ffmpegArgs.push("-metadata", "comment=PrismCast - " + comment);
146
- }
147
- ffmpegArgs.push("pipe:1");
148
- const ffmpeg = spawn(ffmpegPath, ffmpegArgs, {
149
- stdio: ["pipe", "pipe", "pipe"]
150
- });
145
+ function spawnFFmpegProcess({ args, label, onError, streamId }) {
146
+ const ffmpegBin = cachedFFmpegPath ?? "ffmpeg";
147
+ const ffmpeg = spawn(ffmpegBin, args, { stdio: ["pipe", "pipe", "pipe"] });
151
148
  const logPrefix = streamId ? "[" + streamId + "] " : "";
152
149
  // Track whether graceful shutdown has been initiated. When true, we suppress error callbacks because any exit (whether from SIGTERM or stdin close) is expected.
153
150
  let shuttingDown = false;
154
- // Log FFmpeg stderr output (warnings and errors). stderr is guaranteed to be a Readable since we set stdio: ["pipe", "pipe", "pipe"].
151
+ // Log FFmpeg stderr output (warnings and errors). Suppress noise patterns that are not actionable.
155
152
  ffmpeg.stderr.on("data", (data) => {
156
- // Suppress warnings during shutdown - truncated input warnings are expected when the capture stream closes.
157
153
  if (shuttingDown) {
158
154
  return;
159
155
  }
160
156
  const message = data.toString().trim();
161
- // Filter out common noise that isn't actionable.
162
- const noisePatterns = ["Press [q] to stop", "frame=", "size=", "time=", "bitrate=", "speed="];
163
- if (noisePatterns.some((pattern) => message.includes(pattern))) {
157
+ if (FFMPEG_NOISE_PATTERNS.some((pattern) => message.includes(pattern))) {
164
158
  return;
165
159
  }
166
160
  if (message.length > 0) {
167
- LOG.debug("streaming:ffmpeg", "%sFFmpeg: %s", logPrefix, message);
161
+ LOG.debug("streaming:ffmpeg", "%s%s: %s", logPrefix, label, message);
168
162
  }
169
163
  });
170
164
  // Handle FFmpeg process exit.
171
165
  ffmpeg.on("exit", (code, signal) => {
172
- // During graceful shutdown, any exit is expected (whether from SIGTERM or stdin closing).
173
166
  if (shuttingDown) {
174
167
  return;
175
168
  }
176
169
  if (signal === "SIGTERM") {
177
- // Normal termination via kill() - don't treat as error.
170
+ // Normal termination via kill() — do not treat as error.
178
171
  return;
179
172
  }
180
173
  if ((code !== null) && (code !== 0)) {
181
- onError(new Error("FFmpeg exited with code " + String(code) + "."));
174
+ onError(new Error(label + " exited with code " + String(code) + "."));
182
175
  }
183
176
  else if (signal) {
184
- onError(new Error("FFmpeg killed by signal " + signal + "."));
177
+ onError(new Error(label + " killed by signal " + signal + "."));
185
178
  }
186
179
  });
187
180
  // Handle spawn errors (e.g., FFmpeg not found).
188
181
  ffmpeg.on("error", (error) => {
189
- // During graceful shutdown, suppress errors from stdin pipe closing.
190
182
  if (shuttingDown) {
191
183
  return;
192
184
  }
193
185
  onError(error);
194
186
  });
195
- // Kill function for graceful shutdown. Sets the shuttingDown flag before sending SIGTERM so that any exit (whether from SIGTERM or stdin closing due to capture
196
- // stream ending) is treated as normal termination.
187
+ // Kill function for graceful shutdown. Sets the shuttingDown flag before sending SIGTERM so that any exit is treated as normal termination.
197
188
  const kill = () => {
198
189
  shuttingDown = true;
199
190
  if (!ffmpeg.killed) {
@@ -207,104 +198,93 @@ export function spawnFFmpeg(audioBitrate, onError, streamId, comment) {
207
198
  stdout: ffmpeg.stdout
208
199
  };
209
200
  }
201
+ // Public FFmpeg Spawners.
202
+ /**
203
+ * Spawns an FFmpeg process configured to remux Matroska to fMP4. Video is passed through unchanged (codec copy); audio is transcoded from Opus to AAC for HLS
204
+ * compatibility. The video codec (H264 or HEVC) is determined by Chrome's MediaRecorder based on hardware capabilities. The process reads from stdin and writes
205
+ * to stdout, allowing it to be integrated into a Node.js stream pipeline.
206
+ *
207
+ * FFmpeg arguments:
208
+ * - `-hide_banner -loglevel warning`: Reduce noise, only show warnings/errors
209
+ * - `-probesize 16384`: Limit input probing to 16KB (Chrome's Matroska header fits well under this) to minimize startup delay
210
+ * - `-i pipe:0`: Read input from stdin
211
+ * - `-c:v copy`: Copy video stream without re-encoding (passthrough)
212
+ * - `-c:a aac -b:a <bitrate>`: Transcode audio to AAC at specified bitrate
213
+ * - `-f mp4`: Output MP4 container format
214
+ * - `-movflags frag_keyframe+empty_moov+default_base_moof`: Streaming-friendly fMP4 flags
215
+ * - `-flush_packets 1`: Flush output immediately after each packet to minimize latency
216
+ * - `pipe:1`: Write output to stdout
217
+ * @param audioBitrate - Audio bitrate in bits per second (e.g., 256000 for 256 kbps).
218
+ * @param onError - Callback invoked when FFmpeg exits unexpectedly or encounters an error.
219
+ * @param streamId - Stream identifier for logging.
220
+ * @param comment - Optional comment metadata (channel name or domain) to embed in the output.
221
+ * @returns FFmpeg process wrapper with stdin, stdout, and kill function.
222
+ */
223
+ export function spawnFFmpeg(audioBitrate, onError, streamId, comment) {
224
+ // Use Apple's AudioToolbox AAC encoder on macOS for better quality and performance. Fall back to FFmpeg's built-in AAC encoder on other platforms.
225
+ const aacEncoder = process.platform === "darwin" ? "aac_at" : "aac";
226
+ const ffmpegArgs = [
227
+ "-hide_banner",
228
+ "-nostats",
229
+ "-loglevel", "warning",
230
+ "-fflags", "+discardcorrupt",
231
+ "-err_detect", "ignore_err",
232
+ "-probesize", "16384",
233
+ "-i", "pipe:0",
234
+ "-c:v", "copy",
235
+ "-c:a", aacEncoder,
236
+ "-b:a", String(audioBitrate),
237
+ "-f", "mp4",
238
+ "-movflags", "frag_keyframe+empty_moov+default_base_moof+skip_sidx+skip_trailer",
239
+ "-flush_packets", "1"
240
+ ];
241
+ // Add metadata comment if provided. This embeds "PrismCast - <channel>" in the output for identification.
242
+ if (comment) {
243
+ ffmpegArgs.push("-metadata", "comment=PrismCast - " + comment);
244
+ }
245
+ ffmpegArgs.push("pipe:1");
246
+ return spawnFFmpegProcess({
247
+ args: ffmpegArgs,
248
+ label: "FFmpeg",
249
+ onError,
250
+ streamId
251
+ });
252
+ }
210
253
  /**
211
254
  * Spawns an FFmpeg process configured to remux fMP4 input to MPEG-TS output with codec copy. The process reads a continuous fMP4 stream (init segment followed by
212
- * media segments) from stdin and writes MPEG-TS to stdout. No transcoding occurs — both video (H264) and audio (AAC) are copied unchanged — so CPU usage is minimal.
255
+ * media segments) from stdin and writes MPEG-TS to stdout. No transcoding occurs — both video and audio are copied unchanged — so CPU usage is minimal.
213
256
  *
214
257
  * FFmpeg arguments:
215
258
  * - `-hide_banner -loglevel warning`: Reduce noise, only show warnings/errors
216
259
  * - `-probesize 16384`: Limit input probing to 16KB (fMP4 init segment is ~1.3KB) to minimize startup delay
217
260
  * - `-f mp4 -i pipe:0`: Read fragmented MP4 from stdin
218
261
  * - `-c copy`: Copy both video and audio codecs without transcoding
219
- * - `-f mpegts`: Output MPEG-TS container format
220
- * - `-mpegts_pmt_start_pid 0x0020`: Use ATSC-conventional PMT PID range instead of FFmpeg's default (0x1000). Minimum allowed value is 0x0020 (32).
221
- * - `-mpegts_start_pid 0x0031`: Use ATSC-conventional elementary stream PIDs instead of FFmpeg's defaults (0x100+)
222
- * - `-mpegts_service_type digital_tv`: Label the service as digital TV in the PMT service descriptor
223
- * - `-pat_period 0.1`: Repeat PAT/PMT tables every 100ms, matching ATSC broadcast frequency
224
- * - `-pcr_period 40`: Insert PCR timestamps every 40ms, matching ATSC broadcast convention
225
- * - `-flush_packets 1`: Flush output immediately after each packet to minimize latency
262
+ * - MPEG-TS output flags: See MPEGTS_OUTPUT_FLAGS for ATSC-conventional settings
226
263
  * - `pipe:1`: Write output to stdout
227
264
  * @param onError - Callback invoked when FFmpeg exits unexpectedly or encounters an error.
228
265
  * @param streamId - Optional stream identifier for logging.
229
266
  * @returns FFmpeg process wrapper with stdin, stdout, and kill function.
230
267
  */
231
268
  export function spawnMpegTsRemuxer(onError, streamId) {
232
- const ffmpegBin = cachedFFmpegPath ?? "ffmpeg";
233
- // MPEG-TS muxer flags are tuned to produce output resembling a real HDHomeRun CONNECT DUO (HDTC-2US) ATSC transport stream. Plex's transcoder may make
234
- // assumptions about stream structure based on the reported device model (PID assignments, PAT/PMT frequency). Using ATSC-conventional values avoids "Invalid
235
- // argument" failures when Plex tries to transcode the live session for remote clients. These are pure container metadata changes — the actual A/V data is
236
- // untouched by -c copy.
237
269
  const ffmpegArgs = [
238
270
  "-hide_banner",
271
+ "-nostats",
239
272
  "-loglevel", "warning",
273
+ "-fflags", "+discardcorrupt",
274
+ "-err_detect", "ignore_err",
240
275
  "-probesize", "16384",
241
276
  "-f", "mp4",
242
277
  "-i", "pipe:0",
243
278
  "-c", "copy",
244
- "-f", "mpegts",
245
- "-mpegts_pmt_start_pid", "0x0020",
246
- "-mpegts_start_pid", "0x0031",
247
- "-mpegts_service_type", "digital_tv",
248
- "-pat_period", "0.1",
249
- "-pcr_period", "40",
250
- "-flush_packets", "1",
279
+ ...MPEGTS_OUTPUT_FLAGS,
251
280
  "pipe:1"
252
281
  ];
253
- const ffmpeg = spawn(ffmpegBin, ffmpegArgs, {
254
- stdio: ["pipe", "pipe", "pipe"]
255
- });
256
- const logPrefix = streamId ? "[" + streamId + "] " : "";
257
- // Track whether graceful shutdown has been initiated. When true, we suppress error callbacks because any exit is expected.
258
- let shuttingDown = false;
259
- // Log FFmpeg stderr output (warnings and errors).
260
- ffmpeg.stderr.on("data", (data) => {
261
- if (shuttingDown) {
262
- return;
263
- }
264
- const message = data.toString().trim();
265
- const noisePatterns = ["Press [q] to stop", "frame=", "size=", "time=", "bitrate=", "speed="];
266
- if (noisePatterns.some((pattern) => message.includes(pattern))) {
267
- return;
268
- }
269
- if (message.length > 0) {
270
- LOG.debug("streaming:ffmpeg", "%sMPEG-TS remuxer: %s", logPrefix, message);
271
- }
272
- });
273
- // Handle FFmpeg process exit.
274
- ffmpeg.on("exit", (code, signal) => {
275
- if (shuttingDown) {
276
- return;
277
- }
278
- if (signal === "SIGTERM") {
279
- return;
280
- }
281
- if ((code !== null) && (code !== 0)) {
282
- onError(new Error("MPEG-TS remuxer exited with code " + String(code) + "."));
283
- }
284
- else if (signal) {
285
- onError(new Error("MPEG-TS remuxer killed by signal " + signal + "."));
286
- }
282
+ return spawnFFmpegProcess({
283
+ args: ffmpegArgs,
284
+ label: "MPEG-TS remuxer",
285
+ onError,
286
+ streamId
287
287
  });
288
- // Handle spawn errors (e.g., FFmpeg not found).
289
- ffmpeg.on("error", (error) => {
290
- if (shuttingDown) {
291
- return;
292
- }
293
- onError(error);
294
- });
295
- // Kill function for graceful shutdown.
296
- const kill = () => {
297
- shuttingDown = true;
298
- if (!ffmpeg.killed) {
299
- ffmpeg.kill("SIGTERM");
300
- }
301
- };
302
- return {
303
- kill,
304
- process: ffmpeg,
305
- stdin: ffmpeg.stdin,
306
- stdout: ffmpeg.stdout
307
- };
308
288
  }
309
289
  /**
310
290
  * Checks if FFmpeg is available on the system. This resolves the FFmpeg path and caches it for use by spawnFFmpeg().
@@ -1 +1 @@
1
- {"version":3,"file":"ffmpeg.js","sourceRoot":"","sources":["../../src/utils/ffmpeg.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ffmpeg.js","sourceRoot":"","sources":["../../src/utils/ffmpeg.ts"],"names":[],"mappings":"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"}
@@ -21,3 +21,9 @@ export declare function formatTimeAgo(timestamp: number): string;
21
21
  * @returns The concise domain, or the original URL if parsing fails.
22
22
  */
23
23
  export declare function extractDomain(url: string): string;
24
+ /**
25
+ * Capitalizes the first letter of a string.
26
+ * @param str - The string to capitalize.
27
+ * @returns The string with the first letter capitalized.
28
+ */
29
+ export declare function capitalize(str: string): string;
@@ -65,4 +65,12 @@ export function extractDomain(url) {
65
65
  return url;
66
66
  }
67
67
  }
68
+ /**
69
+ * Capitalizes the first letter of a string.
70
+ * @param str - The string to capitalize.
71
+ * @returns The string with the first letter capitalized.
72
+ */
73
+ export function capitalize(str) {
74
+ return str.charAt(0).toUpperCase() + str.slice(1);
75
+ }
68
76
  //# sourceMappingURL=format.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"format.js","sourceRoot":"","sources":["../../src/utils/format.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAAC,EAAU;IAEvC,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC;IAC3C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC;IAC9C,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,YAAY,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;IACvD,MAAM,OAAO,GAAG,YAAY,GAAG,EAAE,CAAC;IAElC,IAAG,KAAK,GAAG,CAAC,EAAE,CAAC;QAEb,OAAO,CAAE,MAAM,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,EAAE,GAAG,CAAE,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAChE,CAAC;IAED,IAAG,OAAO,GAAG,CAAC,EAAE,CAAC;QAEf,OAAO,CAAE,MAAM,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,EAAE,GAAG,CAAE,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAClE,CAAC;IAED,OAAO,CAAE,MAAM,CAAC,OAAO,CAAC,EAAE,GAAG,CAAE,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AAC3C,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,SAAiB;IAE7C,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC,GAAG,IAAI,CAAC,CAAC;IAE5D,IAAG,OAAO,GAAG,EAAE,EAAE,CAAC;QAEhB,OAAO,UAAU,CAAC;IACpB,CAAC;IAED,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,EAAE,CAAC,CAAC;IAEzC,IAAG,OAAO,GAAG,EAAE,EAAE,CAAC;QAEhB,OAAO,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC;IAC5E,CAAC;IAED,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,EAAE,CAAC,CAAC;IAEvC,IAAG,KAAK,GAAG,EAAE,EAAE,CAAC;QAEd,OAAO,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC;IACpE,CAAC;IAED,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,EAAE,CAAC,CAAC;IAEpC,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC;AAChE,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,GAAW;IAEvC,IAAI,CAAC;QAEH,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC;QACvC,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAElC,uHAAuH;QACvH,IAAG,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAEpB,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACnC,CAAC;QAED,OAAO,QAAQ,CAAC;IAClB,CAAC;IAAC,MAAM,CAAC;QAEP,OAAO,GAAG,CAAC;IACb,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"format.js","sourceRoot":"","sources":["../../src/utils/format.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,4 @@
1
+ export * from "./chromeFetch.js";
1
2
  export * from "./debugFilter.js";
2
3
  export * from "./delay.js";
3
4
  export * from "./errors.js";
@@ -9,8 +10,10 @@ export * from "./logEmitter.js";
9
10
  export * from "./logger.js";
10
11
  export * from "./m3u.js";
11
12
  export * from "./morganStream.js";
13
+ export * from "./pid.js";
12
14
  export * from "./platform.js";
13
15
  export * from "./retry.js";
14
16
  export * from "./sanitize.js";
15
17
  export * from "./streamContext.js";
18
+ export * from "./timing.js";
16
19
  export * from "./version.js";
@@ -2,6 +2,7 @@
2
2
  *
3
3
  * index.ts: Utility module exports for PrismCast.
4
4
  */
5
+ export * from "./chromeFetch.js";
5
6
  export * from "./debugFilter.js";
6
7
  export * from "./delay.js";
7
8
  export * from "./errors.js";
@@ -13,9 +14,11 @@ export * from "./logEmitter.js";
13
14
  export * from "./logger.js";
14
15
  export * from "./m3u.js";
15
16
  export * from "./morganStream.js";
17
+ export * from "./pid.js";
16
18
  export * from "./platform.js";
17
19
  export * from "./retry.js";
18
20
  export * from "./sanitize.js";
19
21
  export * from "./streamContext.js";
22
+ export * from "./timing.js";
20
23
  export * from "./version.js";
21
24
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,cAAc,kBAAkB,CAAC;AACjC,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,UAAU,CAAC;AACzB,cAAc,mBAAmB,CAAC;AAClC,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,oBAAoB,CAAC;AACnC,cAAc,cAAc,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,UAAU,CAAC;AACzB,cAAc,mBAAmB,CAAC;AAClC,cAAc,UAAU,CAAC;AACzB,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,oBAAoB,CAAC;AACnC,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC"}
@@ -2,7 +2,7 @@
2
2
  *
3
3
  * logEmitter.ts: Event emitter for real-time log streaming via SSE.
4
4
  */
5
- import { EventEmitter } from "events";
5
+ import { EventEmitter } from "node:events";
6
6
  /* A singleton EventEmitter that broadcasts log entries to all subscribed SSE clients. When a log entry is written via LOG.info/warn/error, the entry is emitted here
7
7
  * for real-time streaming to connected browsers.
8
8
  */
@@ -1 +1 @@
1
- {"version":3,"file":"logEmitter.js","sourceRoot":"","sources":["../../src/utils/logEmitter.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AActC;;GAEG;AAEH,MAAM,UAAU,GAAG,IAAI,YAAY,EAAE,CAAC;AAEtC,oJAAoJ;AACpJ,UAAU,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC;AAEhC;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,KAAe;IAE1C,UAAU,CAAC,IAAI,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;AAChC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,QAAmC;IAEjE,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;IAE/B,OAAO,GAAS,EAAE;QAEhB,UAAU,CAAC,GAAG,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;IAClC,CAAC,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"logEmitter.js","sourceRoot":"","sources":["../../src/utils/logEmitter.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAc3C;;GAEG;AAEH,MAAM,UAAU,GAAG,IAAI,YAAY,EAAE,CAAC;AAEtC,oJAAoJ;AACpJ,UAAU,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC;AAEhC;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,KAAe;IAE1C,UAAU,CAAC,IAAI,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;AAChC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,QAAmC;IAEjE,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;IAE/B,OAAO,GAAS,EAAE;QAEhB,UAAU,CAAC,GAAG,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;IAClC,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,40 @@
1
+ import type { Nullable } from "../types/index.js";
2
+ /**
3
+ * Checks whether a process with the given PID is still running. Uses the signal 0 technique: process.kill(pid, 0) throws ESRCH if the process does not exist,
4
+ * returns successfully if it does. EPERM (permission denied) means the process exists but belongs to another user — treated as "still running" since we cannot
5
+ * kill it anyway. This is cross-platform and works on macOS, Linux, and Windows without external tools.
6
+ * @param pid - The process ID to check.
7
+ * @returns True if the process is running, false if it has exited.
8
+ */
9
+ export declare function isProcessRunning(pid: number): boolean;
10
+ /**
11
+ * Reads a PID from a file on disk. Returns null if the file does not exist, is empty, or contains non-numeric content. ENOENT is expected on first run or after a
12
+ * clean shutdown and is silently ignored.
13
+ * @param filePath - The absolute path to the PID file.
14
+ * @param label - A descriptive label for log messages (e.g., "Chrome", "server").
15
+ * @param log - Optional logger for non-ENOENT read errors. When omitted (e.g., before the logger is initialized), errors are silently ignored.
16
+ * @returns The stored process ID, or null if unavailable.
17
+ */
18
+ export declare function readPidFile(filePath: string, label: string, log?: {
19
+ warn: (message: string, ...args: unknown[]) => void;
20
+ }): Nullable<number>;
21
+ /**
22
+ * Writes a PID to a file on disk. Used to persist the current process ID so that the next startup can detect a running instance or clean up an orphaned process.
23
+ * @param filePath - The absolute path to the PID file.
24
+ * @param pid - The process ID to write.
25
+ * @param label - A descriptive label for log messages (e.g., "Chrome", "server").
26
+ * @param log - Optional logger for write errors. When omitted, errors are silently ignored.
27
+ */
28
+ export declare function writePidFile(filePath: string, pid: number, label: string, log?: {
29
+ warn: (message: string, ...args: unknown[]) => void;
30
+ }): void;
31
+ /**
32
+ * Removes a PID file from disk. Called during graceful shutdown and from exit handlers as a fallback. ENOENT is expected when the file has already been removed and
33
+ * is silently ignored.
34
+ * @param filePath - The absolute path to the PID file.
35
+ * @param label - A descriptive label for log messages (e.g., "Chrome", "server").
36
+ * @param log - Optional logger for non-ENOENT removal errors. When omitted, errors are silently ignored.
37
+ */
38
+ export declare function clearPidFile(filePath: string, label: string, log?: {
39
+ warn: (message: string, ...args: unknown[]) => void;
40
+ }): void;
@@ -0,0 +1,83 @@
1
+ import fs from "node:fs";
2
+ /* PID files provide a lightweight mechanism for tracking running processes across restarts and crashes. Each PID file stores a single process ID as a plain text
3
+ * integer. On startup, the stored PID is checked via signal 0 to determine whether the process is still alive. Stale PID files from crashed or terminated processes
4
+ * are handled gracefully — ENOENT on read and ESRCH on signal check are both expected and silently ignored.
5
+ *
6
+ * This module is intentionally synchronous. PID file operations run in contexts where the event loop may not be available (process.on("exit") handlers), so all
7
+ * I/O uses the synchronous fs API and process.kill() for signaling.
8
+ */
9
+ /**
10
+ * Checks whether a process with the given PID is still running. Uses the signal 0 technique: process.kill(pid, 0) throws ESRCH if the process does not exist,
11
+ * returns successfully if it does. EPERM (permission denied) means the process exists but belongs to another user — treated as "still running" since we cannot
12
+ * kill it anyway. This is cross-platform and works on macOS, Linux, and Windows without external tools.
13
+ * @param pid - The process ID to check.
14
+ * @returns True if the process is running, false if it has exited.
15
+ */
16
+ export function isProcessRunning(pid) {
17
+ try {
18
+ process.kill(pid, 0);
19
+ return true;
20
+ }
21
+ catch (error) {
22
+ // EPERM means the process exists but we lack permission to signal it. Treat as running.
23
+ return error.code === "EPERM";
24
+ }
25
+ }
26
+ /**
27
+ * Reads a PID from a file on disk. Returns null if the file does not exist, is empty, or contains non-numeric content. ENOENT is expected on first run or after a
28
+ * clean shutdown and is silently ignored.
29
+ * @param filePath - The absolute path to the PID file.
30
+ * @param label - A descriptive label for log messages (e.g., "Chrome", "server").
31
+ * @param log - Optional logger for non-ENOENT read errors. When omitted (e.g., before the logger is initialized), errors are silently ignored.
32
+ * @returns The stored process ID, or null if unavailable.
33
+ */
34
+ export function readPidFile(filePath, label, log) {
35
+ try {
36
+ const content = fs.readFileSync(filePath, "utf-8").trim();
37
+ const pid = parseInt(content, 10);
38
+ if (!isNaN(pid)) {
39
+ return pid;
40
+ }
41
+ }
42
+ catch (error) {
43
+ if ((error.code !== "ENOENT") && log) {
44
+ log.warn("Failed to read %s PID file: %s.", label, error.message);
45
+ }
46
+ }
47
+ return null;
48
+ }
49
+ /**
50
+ * Writes a PID to a file on disk. Used to persist the current process ID so that the next startup can detect a running instance or clean up an orphaned process.
51
+ * @param filePath - The absolute path to the PID file.
52
+ * @param pid - The process ID to write.
53
+ * @param label - A descriptive label for log messages (e.g., "Chrome", "server").
54
+ * @param log - Optional logger for write errors. When omitted, errors are silently ignored.
55
+ */
56
+ export function writePidFile(filePath, pid, label, log) {
57
+ try {
58
+ fs.writeFileSync(filePath, String(pid), "utf-8");
59
+ }
60
+ catch (error) {
61
+ if (log) {
62
+ log.warn("Failed to write %s PID file: %s.", label, error.message);
63
+ }
64
+ }
65
+ }
66
+ /**
67
+ * Removes a PID file from disk. Called during graceful shutdown and from exit handlers as a fallback. ENOENT is expected when the file has already been removed and
68
+ * is silently ignored.
69
+ * @param filePath - The absolute path to the PID file.
70
+ * @param label - A descriptive label for log messages (e.g., "Chrome", "server").
71
+ * @param log - Optional logger for non-ENOENT removal errors. When omitted, errors are silently ignored.
72
+ */
73
+ export function clearPidFile(filePath, label, log) {
74
+ try {
75
+ fs.unlinkSync(filePath);
76
+ }
77
+ catch (error) {
78
+ if ((error.code !== "ENOENT") && log) {
79
+ log.warn("Failed to remove %s PID file: %s.", label, error.message);
80
+ }
81
+ }
82
+ }
83
+ //# sourceMappingURL=pid.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pid.js","sourceRoot":"","sources":["../../src/utils/pid.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,MAAM,SAAS,CAAC;AAEzB;;;;;;GAMG;AAEH;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,GAAW;IAE1C,IAAI,CAAC;QAEH,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QAErB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,OAAM,KAAc,EAAE,CAAC;QAEvB,wFAAwF;QACxF,OAAQ,KAA+B,CAAC,IAAI,KAAK,OAAO,CAAC;IAC3D,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,WAAW,CAAC,QAAgB,EAAE,KAAa,EAAE,GAA6D;IAExH,IAAI,CAAC;QAEH,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;QAC1D,MAAM,GAAG,GAAG,QAAQ,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;QAElC,IAAG,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;YAEf,OAAO,GAAG,CAAC;QACb,CAAC;IACH,CAAC;IAAC,OAAM,KAAc,EAAE,CAAC;QAEvB,IAAG,CAAE,KAA+B,CAAC,IAAI,KAAK,QAAQ,CAAC,IAAI,GAAG,EAAE,CAAC;YAE/D,GAAG,CAAC,IAAI,CAAC,iCAAiC,EAAE,KAAK,EAAG,KAAe,CAAC,OAAO,CAAC,CAAC;QAC/E,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAC,QAAgB,EAAE,GAAW,EAAE,KAAa,EAAE,GAA6D;IAEtI,IAAI,CAAC;QAEH,EAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC,CAAC;IACnD,CAAC;IAAC,OAAM,KAAc,EAAE,CAAC;QAEvB,IAAG,GAAG,EAAE,CAAC;YAEP,GAAG,CAAC,IAAI,CAAC,kCAAkC,EAAE,KAAK,EAAG,KAAe,CAAC,OAAO,CAAC,CAAC;QAChF,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAC,QAAgB,EAAE,KAAa,EAAE,GAA6D;IAEzH,IAAI,CAAC;QAEH,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;IAC1B,CAAC;IAAC,OAAM,KAAc,EAAE,CAAC;QAEvB,IAAG,CAAE,KAA+B,CAAC,IAAI,KAAK,QAAQ,CAAC,IAAI,GAAG,EAAE,CAAC;YAE/D,GAAG,CAAC,IAAI,CAAC,mCAAmC,EAAE,KAAK,EAAG,KAAe,CAAC,OAAO,CAAC,CAAC;QACjF,CAAC;IACH,CAAC;AACH,CAAC"}