mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 04:38:33 +00:00
## Summary Fixes three issues identified in the [post-merge review](https://github.com/heygen-com/hyperframes/pull/596#pullrequestreview-4214283515) of PR #596: - **P1 (cache bypass):** When `extractCacheDir` is set, extracted frames live outside `compiledDir`, so `createCompiledFrameSrcResolver` rejects them and every frame falls back to base64 data URIs. Fix: symlink cached frame directories into `compiledDir/__hyperframes_video_frames/` after extraction and remap `framePaths` so the served-frame fast path works. - **P2 (pooled browser stale state):** `closeCaptureSession` force-killed the Chrome process on timeout via raw `SIGKILL` without clearing `pooledBrowser` / `pooledBrowserRefCount`, leaving other sessions with a dead browser reference. Fix: add `forceReleaseBrowser()` in `browserManager` that atomically clears pool state before killing the process. - **P3 (reserved chars in URLs):** `createCompiledFrameSrcResolver` encodes path segments with `encodeURIComponent`, but the file server used `c.req.path` (which only applies `decodeURI`) to look up files on disk. Video IDs containing `#`, `?`, or `%` produced 404s. Fix: apply `decodeURIComponent` per path segment in the file server's catch-all route. ## Test plan - [x] `createCompiledFrameSrcResolver` tests: symlinked cache paths resolve to served URLs; cache-external paths return null; reserved characters encode correctly - [x] `forceReleaseBrowser` tests: kills process + disconnects; tolerates already-killed process - [x] `createFileServer` test: `video%231/frame.jpg` serves file from `video#1/frame.jpg` on disk - [x] Typecheck: engine + producer pass - [x] Lint + format: 0 warnings, 0 errors
This commit is contained in:
@@ -16,6 +16,7 @@ import { quantizeTimeToFrame } from "@hyperframes/core";
|
||||
import {
|
||||
acquireBrowser,
|
||||
releaseBrowser,
|
||||
forceReleaseBrowser,
|
||||
buildChromeArgs,
|
||||
resolveHeadlessShellPath,
|
||||
type CaptureMode,
|
||||
@@ -95,27 +96,6 @@ async function waitForCloseWithTimeout(promise: Promise<unknown>): Promise<boole
|
||||
return !timedOut;
|
||||
}
|
||||
|
||||
function forceKillBrowserProcess(browser: Browser): void {
|
||||
const browserProcess = (
|
||||
browser as unknown as {
|
||||
process?: () => { kill: (signal?: NodeJS.Signals) => boolean; killed?: boolean } | null;
|
||||
}
|
||||
).process?.();
|
||||
|
||||
if (browserProcess && !browserProcess.killed) {
|
||||
try {
|
||||
browserProcess.kill("SIGKILL");
|
||||
} catch {
|
||||
// Best-effort cleanup after Puppeteer close has already timed out.
|
||||
}
|
||||
}
|
||||
try {
|
||||
browser.disconnect();
|
||||
} catch {
|
||||
// Best-effort cleanup after Puppeteer close has already timed out.
|
||||
}
|
||||
}
|
||||
|
||||
export async function createCaptureSession(
|
||||
serverUrl: string,
|
||||
outputDir: string,
|
||||
@@ -718,7 +698,8 @@ export async function closeCaptureSession(session: CaptureSession): Promise<void
|
||||
const pageClosed = await waitForCloseWithTimeout(session.page.close());
|
||||
if (!pageClosed) {
|
||||
console.warn("[FrameCapture] Timed out closing page; forcing browser process shutdown");
|
||||
forceKillBrowserProcess(session.browser);
|
||||
forceReleaseBrowser(session.browser);
|
||||
session.browserReleased = true;
|
||||
}
|
||||
session.pageReleased = true;
|
||||
}
|
||||
@@ -728,7 +709,7 @@ export async function closeCaptureSession(session: CaptureSession): Promise<void
|
||||
);
|
||||
if (!browserClosed) {
|
||||
console.warn("[FrameCapture] Timed out closing browser; forcing browser process shutdown");
|
||||
forceKillBrowserProcess(session.browser);
|
||||
forceReleaseBrowser(session.browser);
|
||||
}
|
||||
session.browserReleased = true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user