Merge branch 'master' into feat-log-terminal-check
This commit is contained in:
+9
-1
@@ -13,6 +13,7 @@ import (
|
|||||||
|
|
||||||
"github.com/AlexxIT/go2rtc/pkg/shell"
|
"github.com/AlexxIT/go2rtc/pkg/shell"
|
||||||
"github.com/AlexxIT/go2rtc/pkg/yaml"
|
"github.com/AlexxIT/go2rtc/pkg/yaml"
|
||||||
|
"github.com/rs/zerolog"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -117,9 +118,16 @@ func Init() {
|
|||||||
Mod map[string]string `yaml:"log"`
|
Mod map[string]string `yaml:"log"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cfg.Mod = map[string]string{
|
||||||
|
"format": "color",
|
||||||
|
"level": "info",
|
||||||
|
"output": "stdout", // TODO: change to stderr someday
|
||||||
|
"time": zerolog.TimeFormatUnixMs,
|
||||||
|
}
|
||||||
|
|
||||||
LoadConfig(&cfg)
|
LoadConfig(&cfg)
|
||||||
|
|
||||||
log.Logger = NewLogger(cfg.Mod["format"], cfg.Mod["level"])
|
log.Logger = NewLogger(cfg.Mod)
|
||||||
|
|
||||||
modules = cfg.Mod
|
modules = cfg.Mod
|
||||||
|
|
||||||
|
|||||||
+44
-15
@@ -9,29 +9,58 @@ import (
|
|||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
var MemoryLog *circularBuffer
|
var MemoryLog = newBuffer(16)
|
||||||
|
|
||||||
func NewLogger(format string, level string) zerolog.Logger {
|
func NewLogger(config map[string]string) zerolog.Logger {
|
||||||
var writer io.Writer = os.Stdout
|
var writer io.Writer
|
||||||
|
|
||||||
if format != "json" {
|
// support output only to memory
|
||||||
writer = zerolog.ConsoleWriter{
|
switch config["output"] {
|
||||||
Out: writer, TimeFormat: "15:04:05.000", NoColor: (format == "text" || !shell.IsInteractive(os.Stdout.Fd())),
|
case "stderr":
|
||||||
|
writer = os.Stderr
|
||||||
|
case "stdout":
|
||||||
|
writer = os.Stdout
|
||||||
|
}
|
||||||
|
|
||||||
|
timeFormat := config["time"]
|
||||||
|
|
||||||
|
if writer != nil {
|
||||||
|
switch format := config["format"]; format {
|
||||||
|
case "color", "text":
|
||||||
|
if timeFormat != "" {
|
||||||
|
writer = &zerolog.ConsoleWriter{
|
||||||
|
Out: writer,
|
||||||
|
NoColor: format == "text" || !shell.IsInteractive(os.Stdout.Fd()),
|
||||||
|
TimeFormat: "15:04:05.000",
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
writer = &zerolog.ConsoleWriter{
|
||||||
|
Out: writer,
|
||||||
|
NoColor: format == "text" || !shell.IsInteractive(os.Stdout.Fd()),
|
||||||
|
PartsOrder: []string{
|
||||||
|
zerolog.LevelFieldName,
|
||||||
|
zerolog.CallerFieldName,
|
||||||
|
zerolog.MessageFieldName,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "json": // none
|
||||||
}
|
}
|
||||||
|
|
||||||
|
writer = zerolog.MultiLevelWriter(writer, MemoryLog)
|
||||||
|
} else {
|
||||||
|
writer = MemoryLog
|
||||||
}
|
}
|
||||||
|
|
||||||
MemoryLog = newBuffer(16)
|
logger := zerolog.New(writer)
|
||||||
|
|
||||||
writer = zerolog.MultiLevelWriter(writer, MemoryLog)
|
if timeFormat != "" {
|
||||||
|
zerolog.TimeFieldFormat = timeFormat
|
||||||
zerolog.TimeFieldFormat = zerolog.TimeFormatUnixMs
|
logger = logger.With().Timestamp().Logger()
|
||||||
|
|
||||||
lvl, err := zerolog.ParseLevel(level)
|
|
||||||
if err != nil || lvl == zerolog.NoLevel {
|
|
||||||
lvl = zerolog.InfoLevel
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return zerolog.New(writer).With().Timestamp().Logger().Level(lvl)
|
lvl, _ := zerolog.ParseLevel(config["level"])
|
||||||
|
return logger.Level(lvl)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetLogger(module string) zerolog.Logger {
|
func GetLogger(module string) zerolog.Logger {
|
||||||
|
|||||||
+39
-20
@@ -4,7 +4,7 @@ import (
|
|||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
@@ -68,8 +68,9 @@ func execHandle(rawURL string) (core.Producer, error) {
|
|||||||
|
|
||||||
args := shell.QuoteSplit(rawURL[5:]) // remove `exec:`
|
args := shell.QuoteSplit(rawURL[5:]) // remove `exec:`
|
||||||
cmd := exec.Command(args[0], args[1:]...)
|
cmd := exec.Command(args[0], args[1:]...)
|
||||||
if log.Debug().Enabled() {
|
cmd.Stderr = &logWriter{
|
||||||
cmd.Stderr = os.Stderr
|
buf: make([]byte, 512),
|
||||||
|
debug: log.Debug().Enabled(),
|
||||||
}
|
}
|
||||||
|
|
||||||
if path == "" {
|
if path == "" {
|
||||||
@@ -104,18 +105,10 @@ func handlePipe(_ string, cmd *exec.Cmd, query url.Values) (core.Producer, error
|
|||||||
|
|
||||||
log.Debug().Stringer("launch", time.Since(ts)).Msg("[exec] run pipe")
|
log.Debug().Stringer("launch", time.Since(ts)).Msg("[exec] run pipe")
|
||||||
|
|
||||||
return prod, err
|
return prod, fmt.Errorf("exec/pipe: %w\n%s", err, cmd.Stderr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleRTSP(url string, cmd *exec.Cmd, path string) (core.Producer, error) {
|
func handleRTSP(url string, cmd *exec.Cmd, path string) (core.Producer, error) {
|
||||||
stderr := limitBuffer{buf: make([]byte, 512)}
|
|
||||||
|
|
||||||
if cmd.Stderr != nil {
|
|
||||||
cmd.Stderr = io.MultiWriter(cmd.Stderr, &stderr)
|
|
||||||
} else {
|
|
||||||
cmd.Stderr = &stderr
|
|
||||||
}
|
|
||||||
|
|
||||||
if log.Trace().Enabled() {
|
if log.Trace().Enabled() {
|
||||||
cmd.Stdout = os.Stdout
|
cmd.Stdout = os.Stdout
|
||||||
}
|
}
|
||||||
@@ -150,10 +143,10 @@ func handleRTSP(url string, cmd *exec.Cmd, path string) (core.Producer, error) {
|
|||||||
case <-time.After(time.Second * 60):
|
case <-time.After(time.Second * 60):
|
||||||
_ = cmd.Process.Kill()
|
_ = cmd.Process.Kill()
|
||||||
log.Error().Str("url", url).Msg("[exec] timeout")
|
log.Error().Str("url", url).Msg("[exec] timeout")
|
||||||
return nil, errors.New("timeout")
|
return nil, errors.New("exec: timeout")
|
||||||
case <-done:
|
case <-done:
|
||||||
// limit message size
|
// limit message size
|
||||||
return nil, errors.New("exec: " + stderr.String())
|
return nil, fmt.Errorf("exec/rtsp\n%s", cmd.Stderr)
|
||||||
case prod := <-waiter:
|
case prod := <-waiter:
|
||||||
log.Debug().Stringer("launch", time.Since(ts)).Msg("[exec] run rtsp")
|
log.Debug().Stringer("launch", time.Since(ts)).Msg("[exec] run rtsp")
|
||||||
return prod, nil
|
return prod, nil
|
||||||
@@ -168,21 +161,47 @@ var (
|
|||||||
waitersMu sync.Mutex
|
waitersMu sync.Mutex
|
||||||
)
|
)
|
||||||
|
|
||||||
type limitBuffer struct {
|
type logWriter struct {
|
||||||
buf []byte
|
buf []byte
|
||||||
n int
|
debug bool
|
||||||
|
n int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *limitBuffer) String() string {
|
func (l *logWriter) String() string {
|
||||||
if l.n == len(l.buf) {
|
if l.n == len(l.buf) {
|
||||||
return string(l.buf) + "..."
|
return string(l.buf) + "..."
|
||||||
}
|
}
|
||||||
return string(l.buf[:l.n])
|
return string(l.buf[:l.n])
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *limitBuffer) Write(p []byte) (int, error) {
|
func (l *logWriter) Write(p []byte) (n int, err error) {
|
||||||
if l.n < cap(l.buf) {
|
if l.n < cap(l.buf) {
|
||||||
l.n += copy(l.buf[l.n:], p)
|
l.n += copy(l.buf[l.n:], p)
|
||||||
}
|
}
|
||||||
return len(p), nil
|
n = len(p)
|
||||||
|
if l.debug {
|
||||||
|
if p = trimSpace(p); p != nil {
|
||||||
|
log.Debug().Msgf("[exec] %s", p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func trimSpace(b []byte) []byte {
|
||||||
|
start := 0
|
||||||
|
stop := len(b)
|
||||||
|
for ; start < stop; start++ {
|
||||||
|
if b[start] >= ' ' {
|
||||||
|
break // trim all ASCII before 0x20
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for ; ; stop-- {
|
||||||
|
if stop == start {
|
||||||
|
return nil // skip empty output
|
||||||
|
}
|
||||||
|
if b[stop-1] > ' ' {
|
||||||
|
break // trim all ASCII before 0x21
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return b[start:stop]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,15 +19,22 @@ import (
|
|||||||
func Init() {
|
func Init() {
|
||||||
var cfg struct {
|
var cfg struct {
|
||||||
Mod map[string]string `yaml:"ffmpeg"`
|
Mod map[string]string `yaml:"ffmpeg"`
|
||||||
|
Log struct {
|
||||||
|
Level string `yaml:"ffmpeg"`
|
||||||
|
} `yaml:"log"`
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg.Mod = defaults // will be overriden from yaml
|
cfg.Mod = defaults // will be overriden from yaml
|
||||||
|
cfg.Log.Level = "error"
|
||||||
|
|
||||||
app.LoadConfig(&cfg)
|
app.LoadConfig(&cfg)
|
||||||
|
|
||||||
if app.GetLogger("exec").GetLevel() >= 0 {
|
// zerolog levels: trace debug info warn error fatal panic disabled
|
||||||
defaults["global"] += " -v error"
|
// FFmpeg levels: trace debug verbose info warning error fatal panic quiet
|
||||||
|
if cfg.Log.Level == "warn" {
|
||||||
|
cfg.Log.Level = "warning"
|
||||||
}
|
}
|
||||||
|
defaults["global"] += " -v " + cfg.Log.Level
|
||||||
|
|
||||||
streams.RedirectFunc("ffmpeg", func(url string) (string, error) {
|
streams.RedirectFunc("ffmpeg", func(url string) (string, error) {
|
||||||
if _, err := Version(); err != nil {
|
if _, err := Version(); err != nil {
|
||||||
|
|||||||
@@ -0,0 +1,486 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"title": "go2rtc",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"definitions": {
|
||||||
|
"listen": {
|
||||||
|
"type": "string",
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"pattern": ":[0-9]{1,5}$"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"const": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"log_level": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"trace",
|
||||||
|
"debug",
|
||||||
|
"info",
|
||||||
|
"warn",
|
||||||
|
"error"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"api": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"listen": {
|
||||||
|
"default": ":1984",
|
||||||
|
"examples": [
|
||||||
|
"127.0.0.1:8080"
|
||||||
|
],
|
||||||
|
"$ref": "#/definitions/listen"
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"type": "string",
|
||||||
|
"examples": [
|
||||||
|
"admin"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"base_path": {
|
||||||
|
"type": "string",
|
||||||
|
"examples": [
|
||||||
|
"/go2rtc"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"static_dir": {
|
||||||
|
"type": "string",
|
||||||
|
"examples": [
|
||||||
|
"/var/www"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"origin": {
|
||||||
|
"type": "string",
|
||||||
|
"const": "*"
|
||||||
|
},
|
||||||
|
"tls_listen": {
|
||||||
|
"$ref": "#/definitions/listen"
|
||||||
|
},
|
||||||
|
"tls_cert": {
|
||||||
|
"type": "string",
|
||||||
|
"examples": [
|
||||||
|
"-----BEGIN CERTIFICATE-----",
|
||||||
|
"/ssl/fullchain.pem"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"tls_key": {
|
||||||
|
"type": "string",
|
||||||
|
"examples": [
|
||||||
|
"-----BEGIN PRIVATE KEY-----",
|
||||||
|
"/ssl/privkey.pem"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"unix_listen": {
|
||||||
|
"type": "string",
|
||||||
|
"examples": [
|
||||||
|
"/tmp/go2rtc.sock"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ffmpeg": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"bin": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "ffmpeg"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": {
|
||||||
|
"description": "FFmpeg template",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"hass": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"config": {
|
||||||
|
"description": "Home Assistant config directory path",
|
||||||
|
"type": "string",
|
||||||
|
"examples": [
|
||||||
|
"/config"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"homekit": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"pin": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "19550224",
|
||||||
|
"pattern": "^[0-9]{8}$"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"device_id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"device_private": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"pairings": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"log": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"format": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "color",
|
||||||
|
"enum": [
|
||||||
|
"color",
|
||||||
|
"json",
|
||||||
|
"text"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"level": {
|
||||||
|
"description": "Defaul log level",
|
||||||
|
"default": "info",
|
||||||
|
"$ref": "#/definitions/log_level"
|
||||||
|
},
|
||||||
|
"output": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "stdout",
|
||||||
|
"enum": [
|
||||||
|
"",
|
||||||
|
"stdout",
|
||||||
|
"stderr"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"time": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "UNIXMS",
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"",
|
||||||
|
"UNIXMS",
|
||||||
|
"UNIXMICRO",
|
||||||
|
"UNIXNANO",
|
||||||
|
"2006-01-02T15:04:05Z07:00",
|
||||||
|
"2006-01-02T15:04:05.999999999Z07:00"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"api": {
|
||||||
|
"$ref": "#/definitions/log_level"
|
||||||
|
},
|
||||||
|
"echo": {
|
||||||
|
"$ref": "#/definitions/log_level"
|
||||||
|
},
|
||||||
|
"exec": {
|
||||||
|
"description": "Value `exec: debug` will print stderr",
|
||||||
|
"$ref": "#/definitions/log_level"
|
||||||
|
},
|
||||||
|
"expr": {
|
||||||
|
"$ref": "#/definitions/log_level"
|
||||||
|
},
|
||||||
|
"ffmpeg": {
|
||||||
|
"description": "Will only be displayed with `exec: debug` setting",
|
||||||
|
"default": "error",
|
||||||
|
"$ref": "#/definitions/log_level"
|
||||||
|
},
|
||||||
|
"hass": {
|
||||||
|
"$ref": "#/definitions/log_level"
|
||||||
|
},
|
||||||
|
"hls": {
|
||||||
|
"$ref": "#/definitions/log_level"
|
||||||
|
},
|
||||||
|
"homekit": {
|
||||||
|
"$ref": "#/definitions/log_level"
|
||||||
|
},
|
||||||
|
"mp4": {
|
||||||
|
"$ref": "#/definitions/log_level"
|
||||||
|
},
|
||||||
|
"ngrok": {
|
||||||
|
"$ref": "#/definitions/log_level"
|
||||||
|
},
|
||||||
|
"onvif": {
|
||||||
|
"$ref": "#/definitions/log_level"
|
||||||
|
},
|
||||||
|
"rtmp": {
|
||||||
|
"$ref": "#/definitions/log_level"
|
||||||
|
},
|
||||||
|
"rtsp": {
|
||||||
|
"$ref": "#/definitions/log_level"
|
||||||
|
},
|
||||||
|
"streams": {
|
||||||
|
"$ref": "#/definitions/log_level"
|
||||||
|
},
|
||||||
|
"webrtc": {
|
||||||
|
"$ref": "#/definitions/log_level"
|
||||||
|
},
|
||||||
|
"webtorrent": {
|
||||||
|
"$ref": "#/definitions/log_level"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ngrok": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"command": {
|
||||||
|
"type": "string",
|
||||||
|
"examples": [
|
||||||
|
"ngrok tcp 8555 --authtoken xxx",
|
||||||
|
"ngrok start --all --config ngrok.yaml"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"publish": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"examples": [
|
||||||
|
"rtmp://xxx.rtmp.youtube.com/live2/xxxx-xxxx-xxxx-xxxx-xxxx",
|
||||||
|
"rtmps://xxx-x.rtmp.t.me/s/xxxxxxxxxx:xxxxxxxxxxxxxxxxxxxxxx"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rtmp": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"listen": {
|
||||||
|
"examples": [
|
||||||
|
":1935"
|
||||||
|
],
|
||||||
|
"$ref": "#/definitions/listen"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rtsp": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"listen": {
|
||||||
|
"default": ":8554",
|
||||||
|
"$ref": "#/definitions/listen"
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"type": "string",
|
||||||
|
"examples": [
|
||||||
|
"admin"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"default_query": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "video&audio"
|
||||||
|
},
|
||||||
|
"pkt_size": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"srtp": {
|
||||||
|
"description": "SRTP server for HomeKit",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"listen": {
|
||||||
|
"default": ":8443",
|
||||||
|
"$ref": "#/definitions/listen"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"streams": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"title": "Stream",
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"description": "Source",
|
||||||
|
"type": "string",
|
||||||
|
"examples": [
|
||||||
|
"rtsp://username:password@192.168.1.123/cam/realmonitor?channel=1&subtype=0&unicast=true&proto=Onvif",
|
||||||
|
"rtsp://username:password@192.168.1.123/stream1",
|
||||||
|
"rtsp://username:password@192.168.1.123/h264Preview_01_main",
|
||||||
|
"rtmp://192.168.1.123/bcs/channel0_main.bcs?channel=0&stream=0&user=username&password=password",
|
||||||
|
"http://192.168.1.123/flv?port=1935&app=bcs&stream=channel0_main.bcs&user=username&password=password",
|
||||||
|
"http://username:password@192.168.1.123/cgi-bin/snapshot.cgi?channel=1",
|
||||||
|
"ffmpeg:media.mp4#video=h264#hadware#width=1920#height=1080#rotate=180#audio=copy",
|
||||||
|
"ffmpeg:virtual?video=testsrc&size=4K#video=h264#hardware#bitrate=50M",
|
||||||
|
"bubble://username:password@192.168.1.123:34567/bubble/live?ch=0&stream=0",
|
||||||
|
"dvrip://username:password@192.168.1.123:34567?channel=0&subtype=0",
|
||||||
|
"exec:ffmpeg -re -i media.mp4 -c copy -rtsp_transport tcp -f rtsp {output}",
|
||||||
|
"isapi://username:password@192.168.1.123:80/",
|
||||||
|
"kasa://username:password@192.168.1.123:19443/https/stream/mixed",
|
||||||
|
"onvif://username:password@192.168.1.123:80?subtype=0",
|
||||||
|
"tapo://password@192.168.1.123:8800?channel=0&subtype=0",
|
||||||
|
"webtorrent:?share=xxx&pwd=xxx"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"description": "Source",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"webrtc": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"listen": {
|
||||||
|
"default": ":8555/tcp",
|
||||||
|
"type": "string",
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"pattern": ":[0-9]{1,5}(/tcp|/udp)?$"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"const": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"candidates": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/listen/anyOf/0"
|
||||||
|
},
|
||||||
|
"examples": [
|
||||||
|
"216.58.210.174:8555",
|
||||||
|
"stun:8555",
|
||||||
|
"home.duckdns.org:8555"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"ice_servers": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"urls": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string",
|
||||||
|
"examples": [
|
||||||
|
"stun:stun.l.google.com:19302",
|
||||||
|
"turn:123.123.123.123:3478"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"credential": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"filters": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"candidates": {
|
||||||
|
"description": "Keep only these candidates",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"interfaces": {
|
||||||
|
"description": "Keep only these interfaces",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ips": {
|
||||||
|
"description": "Keep only these IP-addresses",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"networks": {
|
||||||
|
"description": "Use only these network types",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"enum": [
|
||||||
|
"tcp4",
|
||||||
|
"tcp6",
|
||||||
|
"udp4",
|
||||||
|
"udp6"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"udp_ports": {
|
||||||
|
"description": "Use only these UDP ports range [min, max]",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"maxItems": 2,
|
||||||
|
"minItems": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"webtorrent": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"trackers": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"shares": {
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"pwd": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"src": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+8
-3
@@ -56,7 +56,7 @@
|
|||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th style="width: 130px">Time</th>
|
<th style="width: 100px">Time</th>
|
||||||
<th style="width: 40px">Level</th>
|
<th style="width: 40px">Level</th>
|
||||||
<th>Message</th>
|
<th>Message</th>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -98,11 +98,16 @@
|
|||||||
lines = lines.reverse();
|
lines = lines.reverse();
|
||||||
}
|
}
|
||||||
return lines.map(line => {
|
return lines.map(line => {
|
||||||
const ts = new Date(line['time']);
|
const ts = new Date(line['time']).toLocaleString(undefined, {
|
||||||
|
hour: 'numeric',
|
||||||
|
minute: 'numeric',
|
||||||
|
second: 'numeric',
|
||||||
|
fractionalSecondDigits: 3
|
||||||
|
});
|
||||||
const msg = Object.keys(line).reduce((msg, key) => {
|
const msg = Object.keys(line).reduce((msg, key) => {
|
||||||
return KEYS.indexOf(key) < 0 ? `${msg} ${key}=${line[key]}` : msg;
|
return KEYS.indexOf(key) < 0 ? `${msg} ${key}=${line[key]}` : msg;
|
||||||
}, line['message']);
|
}, line['message']);
|
||||||
return `<tr class="${line['level']}"><td>${ts.toLocaleString()}</td><td>${escapeHTML(line['level'])}</td><td>${escapeHTML(msg)}</td></tr>`;
|
return `<tr class="${line['level']}"><td>${ts}</td><td>${line['level']}</td><td>${escapeHTML(msg)}</td></tr>`;
|
||||||
}).join('');
|
}).join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user