Merge branch 'master' into rtsp-backchannel

This commit is contained in:
seydx
2024-11-13 11:23:16 +01:00
8 changed files with 47 additions and 11 deletions
+4 -3
View File
@@ -170,7 +170,7 @@ Available modules:
- [api](#module-api) - HTTP API (important for WebRTC support) - [api](#module-api) - HTTP API (important for WebRTC support)
- [rtsp](#module-rtsp) - RTSP Server (important for FFmpeg support) - [rtsp](#module-rtsp) - RTSP Server (important for FFmpeg support)
- [webrtc](#module-webrtc) - WebRTC Server - [webrtc](#module-webrtc) - WebRTC Server
- [mp4](#module-mp4) - MSE, MP4 stream and MP4 shapshot Server - [mp4](#module-mp4) - MSE, MP4 stream and MP4 snapshot Server
- [hls](#module-hls) - HLS TS or fMP4 stream Server - [hls](#module-hls) - HLS TS or fMP4 stream Server
- [mjpeg](#module-mjpeg) - MJPEG Server - [mjpeg](#module-mjpeg) - MJPEG Server
- [ffmpeg](#source-ffmpeg) - FFmpeg integration - [ffmpeg](#source-ffmpeg) - FFmpeg integration
@@ -648,10 +648,11 @@ This source type support Roborock vacuums with cameras. Known working models:
- Roborock S6 MaxV - only video (the vacuum has no microphone) - Roborock S6 MaxV - only video (the vacuum has no microphone)
- Roborock S7 MaxV - video and two way audio - Roborock S7 MaxV - video and two way audio
- Roborock Qrevo MaxV - video and two way audio
Source support load Roborock credentials from Home Assistant [custom integration](https://github.com/humbertogontijo/homeassistant-roborock). Otherwise, you need to log in to your Roborock account (MiHome account is not supported). Go to: go2rtc WebUI > Add webpage. Copy `roborock://...` source for your vacuum and paste it to `go2rtc.yaml` config. Source support load Roborock credentials from Home Assistant [custom integration](https://github.com/humbertogontijo/homeassistant-roborock) or the [core integration](https://www.home-assistant.io/integrations/roborock). Otherwise, you need to log in to your Roborock account (MiHome account is not supported). Go to: go2rtc WebUI > Add webpage. Copy `roborock://...` source for your vacuum and paste it to `go2rtc.yaml` config.
If you have graphic pin for your vacuum - add it as numeric pin (lines: 123, 456, 678) to the end of the roborock-link. If you have graphic pin for your vacuum - add it as numeric pin (lines: 123, 456, 789) to the end of the roborock-link.
#### Source: WebRTC #### Source: WebRTC
+3 -3
View File
@@ -19,15 +19,15 @@ go2rtc -c log.format=text -c /config/go2rtc.yaml -c rtsp.listen='' -c /usr/local
## Environment variables ## Environment variables
Also go2rtc support templates for using environment variables in any part of config: There is support for loading external variables into the config. First, they will be attempted to be loaded from [credential files](https://systemd.io/CREDENTIALS). If `CREDENTIALS_DIRECTORY` is not set, then the key will be loaded from an environment variable. If no environment variable is set, then the string will be left as-is.
```yaml ```yaml
streams: streams:
camera1: rtsp://rtsp:${CAMERA_PASSWORD}@192.168.1.123/av_stream/ch0 camera1: rtsp://rtsp:${CAMERA_PASSWORD}@192.168.1.123/av_stream/ch0
rtsp: rtsp:
username: ${RTSP_USER:admin} # "admin" if env "RTSP_USER" not set username: ${RTSP_USER:admin} # "admin" if "RTSP_USER" not set
password: ${RTSP_PASS:secret} # "secret" if env "RTSP_PASS" not set password: ${RTSP_PASS:secret} # "secret" if "RTSP_PASS" not set
``` ```
## JSON Schema ## JSON Schema
+5
View File
@@ -179,6 +179,7 @@ func parseArgs(s string) *ffmpeg.Args {
Version: verAV, Version: verAV,
} }
var source = s
var query url.Values var query url.Values
if i := strings.IndexByte(s, '#'); i >= 0 { if i := strings.IndexByte(s, '#'); i >= 0 {
query = streams.ParseQuery(s[i+1:]) query = streams.ParseQuery(s[i+1:])
@@ -221,6 +222,10 @@ func parseArgs(s string) *ffmpeg.Args {
default: default:
s += "?video&audio" s += "?video&audio"
} }
s += "&source=ffmpeg:" + url.QueryEscape(source)
for _, v := range query["query"] {
s += "&" + v
}
args.Input = inputTemplate("rtsp", s, query) args.Input = inputTemplate("rtsp", s, query)
} else if i = strings.Index(s, "?"); i > 0 { } else if i = strings.Index(s, "?"); i > 0 {
switch s[:i] { switch s[:i] {
+13 -2
View File
@@ -148,6 +148,7 @@ func tcpHandler(conn *rtsp.Conn) {
var closer func() var closer func()
trace := log.Trace().Enabled() trace := log.Trace().Enabled()
level := zerolog.WarnLevel
conn.Listen(func(msg any) { conn.Listen(func(msg any) {
if trace { if trace {
@@ -206,8 +207,18 @@ func tcpHandler(conn *rtsp.Conn) {
conn.PacketSize = uint16(core.Atoi(s)) conn.PacketSize = uint16(core.Atoi(s))
} }
// param name like ffmpeg style https://ffmpeg.org/ffmpeg-protocols.html
if s := query.Get("log_level"); s != "" {
if lvl, err := zerolog.ParseLevel(s); err == nil {
level = lvl
}
}
// will help to protect looping requests to same source
conn.Connection.Source = query.Get("source")
if err := stream.AddConsumer(conn); err != nil { if err := stream.AddConsumer(conn); err != nil {
log.Warn().Err(err).Str("stream", name).Msg("[rtsp]") log.WithLevel(level).Err(err).Str("stream", name).Msg("[rtsp]")
return return
} }
@@ -245,7 +256,7 @@ func tcpHandler(conn *rtsp.Conn) {
if err := conn.Accept(); err != nil { if err := conn.Accept(); err != nil {
if err != io.EOF { if err != io.EOF {
log.Warn().Err(err).Caller().Send() log.WithLevel(level).Err(err).Caller().Send()
} }
if closer != nil { if closer != nil {
closer() closer()
+8 -2
View File
@@ -22,6 +22,12 @@ func (s *Stream) AddConsumer(cons core.Consumer) (err error) {
producers: producers:
for prodN, prod := range s.producers { for prodN, prod := range s.producers {
// check for loop request, ex. `camera1: ffmpeg:camera1`
if info, ok := cons.(core.Info); ok && prod.url == info.GetSource() {
log.Trace().Msgf("[streams] skip cons=%d prod=%d", consN, prodN)
continue
}
if prodErrors[prodN] != nil { if prodErrors[prodN] != nil {
log.Trace().Msgf("[streams] skip cons=%d prod=%d", consN, prodN) log.Trace().Msgf("[streams] skip cons=%d prod=%d", consN, prodN)
continue continue
@@ -129,7 +135,7 @@ func formatError(consMedias, prodMedias []*core.Media, prodErrors []error) error
for _, media := range prodMedias { for _, media := range prodMedias {
if media.Direction == core.DirectionRecvonly { if media.Direction == core.DirectionRecvonly {
for _, codec := range media.Codecs { for _, codec := range media.Codecs {
prod = appendString(prod, codec.PrintName()) prod = appendString(prod, media.Kind+":"+codec.PrintName())
} }
} }
} }
@@ -137,7 +143,7 @@ func formatError(consMedias, prodMedias []*core.Media, prodErrors []error) error
for _, media := range consMedias { for _, media := range consMedias {
if media.Direction == core.DirectionSendonly { if media.Direction == core.DirectionSendonly {
for _, codec := range media.Codecs { for _, codec := range media.Codecs {
cons = appendString(cons, codec.PrintName()) cons = appendString(cons, media.Kind+":"+codec.PrintName())
} }
} }
} }
+1 -1
View File
@@ -36,7 +36,7 @@ import (
) )
func main() { func main() {
app.Version = "1.9.6" app.Version = "1.9.7"
// 1. Core modules: app, api/ws, streams // 1. Core modules: app, api/ws, streams
+5
View File
@@ -25,6 +25,7 @@ type Info interface {
SetSource(string) SetSource(string)
SetURL(string) SetURL(string)
WithRequest(*http.Request) WithRequest(*http.Request)
GetSource() string
} }
// Connection just like webrtc.PeerConnection // Connection just like webrtc.PeerConnection
@@ -123,6 +124,10 @@ func (c *Connection) WithRequest(r *http.Request) {
c.UserAgent = r.UserAgent() c.UserAgent = r.UserAgent()
} }
func (c *Connection) GetSource() string {
return c.Source
}
// Create like os.Create, init Consumer with existing Transport // Create like os.Create, init Consumer with existing Transport
func Create(w io.Writer) (*Connection, error) { func Create(w io.Writer) (*Connection, error) {
return &Connection{Transport: w}, nil return &Connection{Transport: w}, nil
+8
View File
@@ -3,6 +3,7 @@ package shell
import ( import (
"os" "os"
"os/signal" "os/signal"
"path/filepath"
"regexp" "regexp"
"strings" "strings"
"syscall" "syscall"
@@ -51,6 +52,13 @@ func ReplaceEnvVars(text string) string {
dok = true dok = true
} }
if dir, vok := os.LookupEnv("CREDENTIALS_DIRECTORY"); vok {
value, err := os.ReadFile(filepath.Join(dir, key))
if err == nil {
return strings.TrimSpace(string(value))
}
}
if value, vok := os.LookupEnv(key); vok { if value, vok := os.LookupEnv(key); vok {
return value return value
} }