package http import ( "time" "mime/multipart" "net/textproto" "strconv" "net/http" "strings" "os" "html/template" ) func NewServer(port string) (error) { server := http.NewServeMux() server_setup(server) if err := http.ListenAndServe(":" + port, server); err != nil { return err } return nil } func server_setup(server *http.ServeMux) { server.HandleFunc("/live/", serve_main_page) // main page with the video element, separate for each streamkey server.HandleFunc("/list/", serve_live_playlist) // uri returns the playlist file for the given stream key server.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static/")))) // returns static files (eventually for the local hls player implementation) server.HandleFunc("/vid/", serve_vid_segment) // serves the appropriate static video segment server.HandleFunc("/img/", serve_img_loop) } func serve_vid_segment(w http.ResponseWriter, r *http.Request) { w.Header().Set("Cache-Control", "no-cache") base_dir, _ := os.UserHomeDir() tmp := strings.Split(strings.TrimPrefix(r.URL.Path, "/vid/"), "/") stream_key := tmp[0] segment_file := tmp[1] http.ServeFile(w, r, base_dir + "/live/" + stream_key + "/" + segment_file) } func serve_live_playlist(w http.ResponseWriter, r *http.Request) { w.Header().Set("Cache-Control", "no-cache") stream_id := strings.TrimPrefix(r.URL.Path, "/list/") base_dir, _ := os.UserHomeDir() stream_playlist_path := base_dir + "/live/" + stream_id + "/stream.m3u8" if _, err := os.Stat(stream_playlist_path); err != nil { http.NotFound(w, r) } else { http.ServeFile(w, r, stream_playlist_path) } } func serve_main_page(w http.ResponseWriter, r *http.Request) { w.Header().Set("Access-Control-Allow-Origin", "*") // bad jank for hls.js loading from cdn. cross-site js bad. page := template.Must(template.ParseFiles("static/index.html")) stream_user := strings.TrimPrefix(r.URL.Path, "/live/") page.Execute(w, stream_user) // literally only to define the uri for playlist loading, should be a script but eh } func serve_img_loop(w http.ResponseWriter, r *http.Request) { w.Header().Set("Cache-Control", "no-cache") stream_id := strings.TrimPrefix(r.URL.Path, "/img/") base_dir, _ := os.UserHomeDir() img_path := base_dir + "/live/" + stream_id + "/out.jpg" writer := multipart.NewWriter(w) w.Header().Set("Content-Type", "multipart/x-mixed-replace; boundary=" + writer.Boundary()) for { img, err := os.ReadFile(img_path) if err != nil { continue } part, err := writer.CreatePart(textproto.MIMEHeader{"Content-Type": {"image/jpeg"}, "Content-Length": {strconv.Itoa(len(img))}}) if err != nil { break } part.Write(img) time.Sleep(33 * time.Millisecond) } writer.Close() }