Begin a clip. Idempotent: a second call while recording is a no-op.
()
| 73 | |
| 74 | /** Begin a clip. Idempotent: a second call while recording is a no-op. */ |
| 75 | start(): VideoRecStatus & { error?: string } { |
| 76 | if (this.proc) return this.status(); |
| 77 | if (!this.url) return { recording: false, error: "no camera stream" }; |
| 78 | mkdirSync(this.dir, { recursive: true }); |
| 79 | const stamp = new Date().toISOString().replace(/[:.]/g, "-").replace("Z", ""); |
| 80 | const name = `clip-${stamp}.mp4`; |
| 81 | const path = join(this.dir, name); |
| 82 | const args = [ |
| 83 | "-hide_banner", "-loglevel", "error", |
| 84 | "-rtsp_transport", "tcp", |
| 85 | "-i", this.url, |
| 86 | "-an", "-c:v", "copy", |
| 87 | "-movflags", "+frag_keyframe+empty_moov+default_base_moof", |
| 88 | "-f", "mp4", |
| 89 | path, |
| 90 | ]; |
| 91 | // stdin piped so we can send 'q' for a graceful finalize on stop. |
| 92 | const proc = spawn("ffmpeg", args, { stdio: ["pipe", "ignore", "pipe"] }); |
| 93 | this.proc = proc; |
| 94 | this.current = { name, path, startedAt: Date.now() }; |
| 95 | this.track = []; |
| 96 | let errTail = ""; |
| 97 | proc.stderr?.on("data", (c: Buffer) => { |
| 98 | errTail = (errTail + c.toString()).slice(-400); |
| 99 | }); |
| 100 | proc.on("exit", (code) => { |
| 101 | if (this.proc !== proc) return; |
| 102 | const finished = this.current; |
| 103 | this.proc = null; |
| 104 | this.current = null; |
| 105 | if (code) { |
| 106 | console.error(`[rec-video] ffmpeg exited (${code}): ${errTail.trim().split("\n").pop() ?? ""}`); |
| 107 | } |
| 108 | // Clean finish with a logged track -> auto-stabilize in the background. |
| 109 | if (finished && existsSync(`${finished.path}.track.json`)) { |
| 110 | this.autoStabilize(finished.path); |
| 111 | } |
| 112 | }); |
| 113 | console.log(`[rec-video] -> ${path}`); |
| 114 | return this.status(); |
| 115 | } |
| 116 | |
| 117 | /** Stop the current clip, finalizing the file gracefully. */ |
| 118 | stop(): void { |
nothing calls this directly
no test coverage detected