feat(stream): device-aware remux (HEVC/AV1 + non-aac audio) + TTFF timers
Hueco #3 / 3c (CLI). NewRemuxSource now copies the video for any browser-decodable codec: h264, or HEVC/AV1 when the web says the device decodes them (caps). HEVC is muxed with -tag:v hvc1 (Apple requirement), and non-aac audio (ac3/eac3/dts) is transcoded to aac while the video is still copied (ActionRemuxAudio) — this covers the very common h264+ac3 mkv. Startup instrumentation for time-to-first-frame diagnosis: - remux branch logs [probe=.. spawn=..] - transcodeSource logs 'first fMP4 bytes after ..' (ffmpeg → first output) - serveGrowing logs reads that block >250ms (client seeking ahead of the live edge) + the first read's offset vs produced/estimated size. Verified: caps gate (hls without caps, remux with), hvc1 retag (ffprobe of the /stream output = hevc/hvc1), HEVC playback confirmed on a real iPhone Safari over Tailscale. LAN timeline: probe 16ms, spawn 1ms, first byte 201ms, no serveGrowing blocks.
This commit is contained in:
parent
c18876471c
commit
957d499658
4 changed files with 59 additions and 4 deletions
|
|
@ -624,6 +624,7 @@ func runDaemonStart() error {
|
|||
// over /stream — no video re-encode, no HLS. The web decided this from
|
||||
// scan metadata + version gate; we still need ffmpeg (copy uses it).
|
||||
if sess.PlayMethod == "remux" {
|
||||
tStart := time.Now()
|
||||
probeCtx, cancelProbe := context.WithTimeout(ctx, 15*time.Second)
|
||||
probe, perr := engine.ProbeFile(probeCtx, tcRuntime.FFprobePath, filePath)
|
||||
cancelProbe()
|
||||
|
|
@ -631,6 +632,7 @@ func runDaemonStart() error {
|
|||
log.Printf("[stream %s] remux probe failed: %v", agent.ShortID(sess.SessionID), perr)
|
||||
return
|
||||
}
|
||||
tProbe := time.Now()
|
||||
remuxCtx, remuxCancel := context.WithCancel(ctx)
|
||||
src, serr := engine.NewRemuxSource(remuxCtx, filePath, probe, tcRuntime.FFmpegPath, sess.FileName)
|
||||
if serr != nil {
|
||||
|
|
@ -642,7 +644,13 @@ func runDaemonStart() error {
|
|||
// cancel stops the ffmpeg copy; SetGrowingFile/ClearFile also Close()
|
||||
// the source, so the temp file is always cleaned up.
|
||||
playerSessionRegistry.add(sess.SessionID, func() { remuxCancel(); streamSrv.ClearFile() })
|
||||
log.Printf("[stream %s] remux (copy) → fMP4: %s", agent.ShortID(sess.SessionID), filepath.Base(filePath))
|
||||
// Startup timing (TTFF diagnosis): probe = ffprobe on the source;
|
||||
// spawn = ffmpeg launch + tmp setup. First-fMP4-byte is logged by the
|
||||
// source itself; serveGrowing logs any client read that blocks waiting
|
||||
// for ffmpeg to catch up.
|
||||
log.Printf("[stream %s] remux (copy) → fMP4: %s [probe=%v spawn=%v]",
|
||||
agent.ShortID(sess.SessionID), filepath.Base(filePath),
|
||||
tProbe.Sub(tStart).Round(time.Millisecond), time.Since(tProbe).Round(time.Millisecond))
|
||||
go func() {
|
||||
rctx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||
defer cancel()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue