package main import ( "crypto/tls" "encoding/json" "fmt" "io" "local/args" "log" "net/http" "os" "os/exec" "os/signal" "path" "syscall" "time" "golang.org/x/sys/unix" ) var rtspCmd *exec.Cmd var done bool var as *args.ArgSet var rtspAddr string var ss string func main() { as = args.NewArgSet() as.Append(args.INT, "p", "port to listen on", "8101") as.Append(args.STRING, "d", "dir to serve", ".") as.Append(args.STRING, "rtsp", "rtsp ip:port", "192.168.0.96:8554") as.Append(args.STRING, "rtsp-pass", "rtsp password", "fwees123") as.Append(args.STRING, "input", "input override", "") as.Append(args.STRING, "hls", "hsl params", "-hls_time 3 -hls_list_size 10 -hls_flags +delete_segments+append_list+omit_endlist") as.Append(args.STRING, "ss", "time to seek to", "00:00:00") as.Append(args.INT, "kbps", "stream kbps", "500") if err := as.Parse(); err != nil { panic(err) } rtspAddr = as.GetString("rtsp") ss = as.GetString("ss") stream := path.Join(as.GetString("d"), "stream") os.RemoveAll(stream) if err := os.MkdirAll(stream, os.ModePerm); err != nil { panic(err) } go rtsp() for { _, err := os.Stat(path.Join(stream, "test.m3u8")) if err == nil { break } else if !os.IsNotExist(err) { panic(err) } time.Sleep(time.Second * 1) } h := http.FileServer(http.Dir(as.GetString("d"))) go func() { if err := http.ListenAndServe(fmt.Sprintf(":%d", as.GetInt("p")), http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodPost && path.Dir(r.URL.Path) == "/player" { switch path.Base(r.URL.Path) { case "set": var address struct { Address string `json:"address"` } err := json.NewDecoder(r.Body).Decode(&address) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } rtspAddr = address.Address killStream() log.Printf("new rtsp: %s", rtspAddr) case "seek": var ts struct { TS string `json:"ts"` } err := json.NewDecoder(r.Body).Decode(&ts) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } ss = ts.TS killStream() log.Printf("new ss: %s", ss) case "pause": stopStream() log.Printf("stopped stream") case "resume": startStream() log.Printf("resumed stream") default: log.Printf("bad cmd: %q", r.URL.Path) http.NotFound(w, r) } } else { h.ServeHTTP(w, r) } })); err != nil { panic(err) } }() stop := make(chan os.Signal) signal.Notify(stop, os.Interrupt) <-stop permaKillStream() os.RemoveAll(stream) } func rtsp() { log.Println("installing...") install := exec.Command("bash", "-c", ` if ! which ffmpeg; then apk add --no-cache ffmpeg \ || sudo apk add --no-cache ffmpeg \ || (apt update; apt -y install ffmpeg) \ || (apt -y update; apt -y install ffmpeg) \ || (sudo apt -y update; sudo apt -y install ffmpeg) \ || (apt-get update; apt-get -y install ffmpeg) \ || (apt-get -y update; apt-get -y install ffmpeg) \ || (sudo apt-get -y update; sudo apt-get -y install ffmpeg) \ || true fi if ! which ffmpeg; then exit 499 fi `) if o, err := install.StdoutPipe(); err != nil { panic(err) } else { go io.Copy(os.Stdout, o) } if o, err := install.StderrPipe(); err != nil { panic(err) } else { go io.Copy(os.Stderr, o) } if err := install.Run(); err != nil { panic(err) } log.Println("looping...") for !done { log.Println("streaming...") i := `-rtsp_transport tcp -stimeout 10000000 -i rtsp://` + rtspAddr + `/unicast -an` if input := as.GetString("input"); len(input) > 0 { i = input } s := "" if len(ss) > 0 { s = "-ss " + ss } rtspCmd = exec.Command("bash", "-c", ` mkdir -p `+path.Join(as.GetString("d"), "stream")+`; rm -rf `+path.Join(as.GetString("d"), "stream", "*")+`; exec ffmpeg \ -re \ -nostats \ -loglevel warning \ -fflags nobuffer \ `+s+` \ `+i+` \ -preset ultrafast \ -c:v copy \ -b:v `+fmt.Sprint(as.GetInt("kbps"))+`k \ -flags +cgop \ -g 30 \ `+as.GetString("hls")+` \ `+path.Join(as.GetString("d"), "stream", "test.m3u8")+` `) if o, err := rtspCmd.StdoutPipe(); err != nil { panic(err) } else { go io.Copy(os.Stdout, o) } if o, err := rtspCmd.StderrPipe(); err != nil { panic(err) } else { go io.Copy(os.Stderr, o) } log.Println("starting rtsp cmd", rtspCmd) if err := rtspCmd.Start(); err != nil { panic(err) } log.Println("waiting rtsp cmd") log.Println(rtspCmd.Wait()) } } func startStream() { signalStream(syscall.Signal(unix.SIGCONT)) } func stopStream() { signalStream(syscall.Signal(unix.SIGSTOP)) } func killStream() { signalStream(syscall.Signal(unix.SIGKILL)) } func permaKillStream() { done = true killStream() } func signalStream(s syscall.Signal) { for rtspCmd == nil { log.Println("rtspCmdis nil") time.Sleep(time.Second * 3) } for rtspCmd.Process == nil { log.Println("rtspCmd.Process is nil") time.Sleep(time.Second * 3) } log.Println(s, rtspCmd.Process.Signal(os.Signal(s))) } func rebootCam() { c := &http.Client{Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}} r, err := http.NewRequest("GET", "https://"+rtspAddr+"/cgi-bin/action.cgi?cmd=reboot", nil) if err != nil { panic(err) } pass := as.GetString("rtsp-pass") r.SetBasicAuth("root", pass) resp, err := c.Do(r) if err != nil { panic(err) } resp.Body.Close() if resp.StatusCode != http.StatusOK { panic(resp.StatusCode) } }