From 5f2a4930dd3e6d0e35d1f378f985090de0f4088b Mon Sep 17 00:00:00 2001 From: the-m-monk Date: Tue, 30 Dec 2025 01:55:26 +1000 Subject: [PATCH] Full m3u8 generation --- internal/httpserver/api/hls.go | 80 +++++++++++++++++++++++++++++++++- 1 file changed, 79 insertions(+), 1 deletion(-) diff --git a/internal/httpserver/api/hls.go b/internal/httpserver/api/hls.go index 3f7064b..e45b165 100644 --- a/internal/httpserver/api/hls.go +++ b/internal/httpserver/api/hls.go @@ -1,5 +1,83 @@ package api -func RegisterHlsEndpoints() { +import ( + "bytes" + "fmt" + "math" + "net/http" + "os/exec" + "strconv" + "strings" +) +func RegisterHlsEndpoints() { + http.HandleFunc("/hls/index.m3u8", EndpointHlsM3U8) +} + +/* +TODO: endpoint for clients to query a video's: + video tracks, + audio tracks, + captions +Then add support to existing endpoints for specifiying these 3 options +*/ + +func EndpointHlsM3U8(w http.ResponseWriter, r *http.Request) { + //TODO: clean up this mess + options := strings.Split(strings.Split(strings.Replace(r.URL.String(), "%20", " ", -1), "?")[1], "&") + + //TODO: make the order of options not hardcoded + libPathName := strings.TrimPrefix(options[0], "lib=") + + var lib Library + + for _, l := range Libraries { + if l.PathName == libPathName { + lib = l + break + } + } + + filePath := lib.Path + strings.TrimPrefix(options[1], "path=") + + ffprobe := exec.Command( + "ffprobe", + "-v", "error", + "-show_entries", "format=duration", + "-of", "default=noprint_wrappers=1:nokey=1", + filePath, + ) + + var ffprobeStdout bytes.Buffer + ffprobe.Stdout = &ffprobeStdout + + err := ffprobe.Run() + if err != nil { + http.Error(w, "Internal server error", http.StatusInternalServerError) + return + } + + fileLenStr := strings.TrimSuffix(ffprobeStdout.String(), "\n") + fileLenFloat, err := strconv.ParseFloat(fileLenStr, 64) + if err != nil { + http.Error(w, "Internal server error", http.StatusInternalServerError) + return + } + fileLenFloat = math.Ceil(fileLenFloat/6.0) * 6.0 + fileLenInt := int(fileLenFloat) + + w.Header().Set("Content-Type", "application/vnd.apple.mpegurl") + + fmt.Fprintln(w, "#EXTM3U") + fmt.Fprintln(w, "#EXT-X-VERSION:7") + //TODO: make this configurable + fmt.Fprintln(w, "#EXT-X-TARGETDURATION:6") + fmt.Fprintln(w, "#EXT-X-MEDIA-SEQUENCE:0") + + for i := 0; i < fileLenInt/6; i++ { + fmt.Fprintln(w, "#EXTINF:6.0,") + fmt.Fprintf(w, "/hls/segment/%d.mp4?lib=%s&path=%s\n", i, lib.PathName, strings.TrimPrefix(options[1], "path=")) + } + + fmt.Fprintln(w, "#EXT-X-ENDLIST") }