files: seek anywhere in a re-encode; it restarts from there

A re-encode's playlist is now the whole film on a fixed 4 s grid. A segment
that isn't on disk and isn't coming soon moves the encode there; runs stop
short of segments already made, and gaps are filled after. Every run is cut
the same way (-ss, -output_ts_offset start+10, frag_discont), so segments
from different runs join. Repackaging keeps its growing playlist.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Mitchell-Johnson
2026-10-20 13:08:52 -07:00
co-authored by Claude Opus 5.5
parent ff1373e838
commit 450a9d3327
8 changed files with 660 additions and 45 deletions
+27 -3
View File
@@ -146,24 +146,48 @@ func (s *Server) playStop(w http.ResponseWriter, r *http.Request) {
}
// playFile: GET /api/files/play/{id}/{file} — the playlist, the init segment, a segment.
// A seekable stream's playlist is written whole, up front; a segment of it that isn't on disk
// yet is waited for, or re-encoded from there if the encode isn't heading that way (a seek).
func (s *Server) playFile(w http.ResponseWriter, r *http.Request) {
st, ok := s.plays.Find(r.PathValue("id"))
if !ok {
jsonErr(w, 404, "no such stream")
return
}
p, ok := st.File(r.PathValue("file"))
name := r.PathValue("file")
p, ok := st.File(name)
if !ok {
jsonErr(w, 404, "not a stream file")
return
}
w.Header().Set("Cache-Control", "no-store")
w.Header().Set("X-Content-Type-Options", "nosniff")
if st.Seekable {
if name == "index.m3u8" {
w.Header().Set("Content-Type", "application/vnd.apple.mpegurl")
_, _ = w.Write(st.Playlist())
return
}
if i, isSeg := media.SegmentIndex(name); isSeg {
sp, err := st.Segment(r.Context(), i)
switch {
case errors.Is(err, media.ErrNotInFilm):
jsonErr(w, 404, err.Error())
return
case r.Context().Err() != nil:
return // the player moved on
case err != nil:
jsonErr(w, 503, err.Error())
return
}
p = sp
}
}
switch filepath.Ext(p) {
case ".m3u8":
w.Header().Set("Content-Type", "application/vnd.apple.mpegurl")
default:
w.Header().Set("Content-Type", "video/mp4")
}
w.Header().Set("Cache-Control", "no-store")
w.Header().Set("X-Content-Type-Options", "nosniff")
http.ServeFile(w, r, p)
}