mirror of
https://github.com/AlexxIT/go2rtc.git
synced 2025-11-01 20:22:33 +08:00
139 lines
2.7 KiB
Go
139 lines
2.7 KiB
Go
package mp4
|
|
|
|
import (
|
|
"github.com/AlexxIT/go2rtc/cmd/api"
|
|
"github.com/AlexxIT/go2rtc/cmd/app"
|
|
"github.com/AlexxIT/go2rtc/cmd/streams"
|
|
"github.com/AlexxIT/go2rtc/pkg/mp4"
|
|
"github.com/rs/zerolog"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
func Init() {
|
|
log = app.GetLogger("mp4")
|
|
|
|
api.HandleWS(MsgTypeMSE, handlerWS)
|
|
|
|
api.HandleFunc("api/frame.mp4", handlerKeyframe)
|
|
api.HandleFunc("api/stream.mp4", handlerMP4)
|
|
}
|
|
|
|
var log zerolog.Logger
|
|
|
|
func handlerKeyframe(w http.ResponseWriter, r *http.Request) {
|
|
if isChromeFirst(w, r) {
|
|
return
|
|
}
|
|
|
|
src := r.URL.Query().Get("src")
|
|
stream := streams.GetOrNew(src)
|
|
if stream == nil {
|
|
return
|
|
}
|
|
|
|
exit := make(chan []byte)
|
|
|
|
cons := &mp4.Consumer{}
|
|
cons.Listen(func(msg interface{}) {
|
|
switch msg := msg.(type) {
|
|
case []byte:
|
|
exit <- msg
|
|
}
|
|
})
|
|
|
|
if err := stream.AddConsumer(cons); err != nil {
|
|
log.Error().Err(err).Msg("[api.keyframe] add consumer")
|
|
return
|
|
}
|
|
|
|
defer stream.RemoveConsumer(cons)
|
|
|
|
w.Header().Set("Content-Type", cons.MimeType())
|
|
|
|
data, err := cons.Init()
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("[api.keyframe] init")
|
|
return
|
|
}
|
|
data = append(data, <-exit...)
|
|
|
|
// Apple Safari won't show frame without length
|
|
w.Header().Set("Content-Length", strconv.Itoa(len(data)))
|
|
|
|
if _, err := w.Write(data); err != nil {
|
|
log.Error().Err(err).Msg("[api.keyframe] add consumer")
|
|
}
|
|
}
|
|
|
|
func handlerMP4(w http.ResponseWriter, r *http.Request) {
|
|
if isChromeFirst(w, r) || isSafari(w, r) {
|
|
return
|
|
}
|
|
|
|
log.Trace().Msgf("[api.mp4] %+v", r)
|
|
|
|
src := r.URL.Query().Get("src")
|
|
stream := streams.GetOrNew(src)
|
|
if stream == nil {
|
|
return
|
|
}
|
|
|
|
exit := make(chan struct{})
|
|
|
|
cons := &mp4.Consumer{}
|
|
cons.Listen(func(msg interface{}) {
|
|
switch msg := msg.(type) {
|
|
case []byte:
|
|
if _, err := w.Write(msg); err != nil {
|
|
exit <- struct{}{}
|
|
}
|
|
}
|
|
})
|
|
|
|
if err := stream.AddConsumer(cons); err != nil {
|
|
log.Error().Err(err).Msg("[api.mp4] add consumer")
|
|
return
|
|
}
|
|
|
|
defer stream.RemoveConsumer(cons)
|
|
|
|
w.Header().Set("Content-Type", cons.MimeType())
|
|
|
|
data, err := cons.Init()
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("[api.mp4] init")
|
|
return
|
|
}
|
|
|
|
if _, err = w.Write(data); err != nil {
|
|
log.Error().Err(err).Msg("[api.mp4] write")
|
|
return
|
|
}
|
|
|
|
<-exit
|
|
|
|
log.Trace().Msg("[api.mp4] close")
|
|
}
|
|
|
|
func isChromeFirst(w http.ResponseWriter, r *http.Request) bool {
|
|
// Chrome 105 does two requests: without Range and with `Range: bytes=0-`
|
|
if strings.Contains(r.UserAgent(), " Chrome/") {
|
|
if r.Header.Values("Range") == nil {
|
|
w.Header().Set("Content-Type", "video/mp4")
|
|
w.WriteHeader(http.StatusOK)
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func isSafari(w http.ResponseWriter, r *http.Request) bool {
|
|
if r.Header.Get("Range") == "bytes=0-1" {
|
|
handlerKeyframe(w, r)
|
|
return true
|
|
}
|
|
return false
|
|
}
|