From 31962181cb2418ebf1fc5ea30750977d9462bf09 Mon Sep 17 00:00:00 2001 From: Felipe Santos Date: Tue, 25 Nov 2025 16:37:08 -0300 Subject: [PATCH 01/26] Add `#timeout` param for ffmpeg source --- README.md | 7 +++++-- internal/ffmpeg/ffmpeg.go | 20 ++++++++++++++++---- internal/ffmpeg/ffmpeg_test.go | 5 +++++ 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index f27afeb0..1861433d 100644 --- a/README.md +++ b/README.md @@ -371,9 +371,11 @@ But you can override them via YAML config. You can also add your own formats to ```yaml ffmpeg: bin: ffmpeg # path to ffmpeg binary + global: "-hide_banner" + timeout: 5 # default timeout in seconds for rtsp inputs h264: "-codec:v libx264 -g:v 30 -preset:v superfast -tune:v zerolatency -profile:v main -level:v 4.1" mycodec: "-any args that supported by ffmpeg..." - myinput: "-fflags nobuffer -flags low_delay -timeout 5000000 -i {input}" + myinput: "-fflags nobuffer -flags low_delay -timeout {timeout} -i {input}" myraw: "-ss 00:00:20" ``` @@ -383,9 +385,10 @@ ffmpeg: - You can use `width` and/or `height` params, important with transcoding (ex. `#video=h264#width=1280`) - You can use `drawtext` to add a timestamp (ex. `drawtext=x=2:y=2:fontsize=12:fontcolor=white:box=1:boxcolor=black`) - This will greatly increase the CPU of the server, even with hardware acceleration +- You can use `timeout` param to set RTSP input timeout in seconds (ex. `#timeout=10`) - You can use `raw` param for any additional FFmpeg arguments (ex. `#raw=-vf transpose=1`) - You can use `input` param to override default input template (ex. `#input=rtsp/udp` will change RTSP transport from TCP to UDP+TCP) - - You can use raw input value (ex. `#input=-timeout 5000000 -i {input}`) + - You can use raw input value (ex. `#input=-timeout {timeout} -i {input}`) - You can add your own input templates Read more about [hardware acceleration](https://github.com/AlexxIT/go2rtc/wiki/Hardware-acceleration). diff --git a/internal/ffmpeg/ffmpeg.go b/internal/ffmpeg/ffmpeg.go index 242c151d..d945c0ff 100644 --- a/internal/ffmpeg/ffmpeg.go +++ b/internal/ffmpeg/ffmpeg.go @@ -2,6 +2,7 @@ package ffmpeg import ( "net/url" + "strconv" "strings" "github.com/AlexxIT/go2rtc/internal/api" @@ -60,13 +61,14 @@ func Init() { var defaults = map[string]string{ "bin": "ffmpeg", "global": "-hide_banner", + "timeout": "5", // inputs - "file": "-re -i {input}", - "http": "-fflags nobuffer -flags low_delay -i {input}", - "rtsp": "-fflags nobuffer -flags low_delay -timeout 5000000 -user_agent go2rtc/ffmpeg -rtsp_flags prefer_tcp -i {input}", + "file": "-re -i {input}", + "http": "-fflags nobuffer -flags low_delay -i {input}", + "rtsp": "-fflags nobuffer -flags low_delay -timeout {timeout} -user_agent go2rtc/ffmpeg -rtsp_flags prefer_tcp -i {input}", - "rtsp/udp": "-fflags nobuffer -flags low_delay -timeout 5000000 -user_agent go2rtc/ffmpeg -i {input}", + "rtsp/udp": "-fflags nobuffer -flags low_delay -timeout {timeout} -user_agent go2rtc/ffmpeg -i {input}", // output "output": "-user_agent ffmpeg/go2rtc -rtsp_transport tcp -f rtsp {output}", @@ -169,6 +171,16 @@ func inputTemplate(name, s string, query url.Values) string { } else { template = defaults[name] } + if strings.Contains(template, "{timeout}") { + timeout := query.Get("timeout") + if timeout == "" { + timeout = defaults["timeout"] + } + if i, _ := strconv.Atoi(timeout); i > 0 { + timeout = strconv.Itoa(i * 1000000) + } + template = strings.Replace(template, "{timeout}", timeout, 1) + } return strings.Replace(template, "{input}", s, 1) } diff --git a/internal/ffmpeg/ffmpeg_test.go b/internal/ffmpeg/ffmpeg_test.go index 30052d78..b9d02183 100644 --- a/internal/ffmpeg/ffmpeg_test.go +++ b/internal/ffmpeg/ffmpeg_test.go @@ -123,6 +123,11 @@ func TestParseArgsIpCam(t *testing.T) { source: "rtmp://example.com#input=rtsp/udp", expect: `ffmpeg -hide_banner -fflags nobuffer -flags low_delay -timeout 5000000 -user_agent go2rtc/ffmpeg -i rtmp://example.com -c copy -user_agent ffmpeg/go2rtc -rtsp_transport tcp -f rtsp {output}`, }, + { + name: "[RTSP] custom timeout", + source: "rtsp://example.com#timeout=10", + expect: `ffmpeg -hide_banner -allowed_media_types video+audio -fflags nobuffer -flags low_delay -timeout 10000000 -user_agent go2rtc/ffmpeg -rtsp_flags prefer_tcp -i rtsp://example.com -c copy -user_agent ffmpeg/go2rtc -rtsp_transport tcp -f rtsp {output}`, + }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { From 1e5def35c95973df6a5ad6e0d84d4e382a416037 Mon Sep 17 00:00:00 2001 From: Sergey Krashevich Date: Sun, 14 Dec 2025 04:47:27 +0300 Subject: [PATCH 02/26] monaco initial --- www/config.html | 445 ++++++++++++++++++++++++++++++++++++++++---- www/schema.json | 477 ++++++++++++++++++++++++++++++++++++++++++++++++ www/static.go | 1 + 3 files changed, 888 insertions(+), 35 deletions(-) create mode 100644 www/schema.json diff --git a/www/config.html b/www/config.html index 7a712a3b..345bddd3 100644 --- a/www/config.html +++ b/www/config.html @@ -4,11 +4,17 @@ config - go2rtc - @@ -22,44 +28,413 @@
+ diff --git a/www/schema.json b/www/schema.json new file mode 100644 index 00000000..fbf8c022 --- /dev/null +++ b/www/schema.json @@ -0,0 +1,477 @@ +{ + "$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" + }, + "local_auth": { + "description": "Will use Auth header from local network", + "type": "boolean", + "default": false + }, + "base_path": { + "type": "string", + "default": "" + }, + "static_dir": { + "type": "string", + "default": "" + }, + "origin": { + "type": "string", + "default": "*" + }, + "tls_listen": { + "$ref": "#/definitions/listen" + }, + "tls_cert": { + "type": "string", + "examples": [ + "/etc/ssl/certs/my_certificate.pem" + ] + }, + "tls_key": { + "type": "string", + "examples": [ + "/etc/ssl/private/my_certificate_key.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": { + "type": "string", + "examples": [ + "/config/go2rtc.yaml" + ] + }, + "api": { + "type": "object", + "properties": { + "listen": { + "default": ":8124", + "$ref": "#/definitions/listen" + } + } + } + } + }, + "homekit": { + "type": "object", + "properties": { + "pin": { + "type": "string", + "default": "123-45-678" + }, + "name": { + "type": "string", + "default": "go2rtc" + }, + "device_id": { + "type": "string", + "default": "" + }, + "device_private": { + "type": "string", + "default": "" + }, + "category_id": { + "type": "integer", + "default": 2 + }, + "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": { + "description": "ngrok start --all --config ngrok.yaml", + "type": "string" + } + } + }, + "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" + }, + "password": { + "type": "string" + }, + "default_query": { + "type": "string", + "default": "" + }, + "pkt_size": { + "type": "integer", + "default": 0 + } + } + }, + "srtp": { + "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#hardware#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", + "$ref": "#/definitions/listen" + }, + "candidates": { + "type": "array", + "items": { + "type": "string", + "examples": [ + "192.168.1.123", + "stun:stun.l.google.com:19302" + ] + } + }, + "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": { + "type": "object", + "properties": { + "pwd": { + "type": "string" + }, + "src": { + "type": "string" + } + } + } + } + } + } +} diff --git a/www/static.go b/www/static.go index 01f50906..064fec36 100644 --- a/www/static.go +++ b/www/static.go @@ -4,4 +4,5 @@ import "embed" //go:embed *.html //go:embed *.js +//go:embed *.json var Static embed.FS From ce9138b3548c8734453395c55376e15d9c928337 Mon Sep 17 00:00:00 2001 From: Sergey Krashevich Date: Sun, 14 Dec 2025 04:52:35 +0300 Subject: [PATCH 03/26] update monaco to 0.55.1 --- www/config.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/www/config.html b/www/config.html index 345bddd3..62cceab4 100644 --- a/www/config.html +++ b/www/config.html @@ -28,10 +28,10 @@
- + + - - +go2rtc +github.com/AlexxIT/go2rtc - \ No newline at end of file + diff --git a/website/webtorrent/index.html b/website/webtorrent/index.html new file mode 100644 index 00000000..6a4b9057 --- /dev/null +++ b/website/webtorrent/index.html @@ -0,0 +1,189 @@ + + + + + webtorrent - go2rtc + + + + +
+ + + +
+ + + + + \ No newline at end of file diff --git a/www/README.md b/www/README.md index 4da49485..e4d36cd2 100644 --- a/www/README.md +++ b/www/README.md @@ -81,11 +81,11 @@ https://webrtc.org/getting-started/unified-plan-transition-guide?hl=en ```html - + - + - + ``` ## Useful links diff --git a/www/links.html b/www/links.html index f2b35c22..466d30a5 100644 --- a/www/links.html +++ b/www/links.html @@ -186,7 +186,7 @@ Telegram: rtmps://xxx-x.rtmp.t.me/s/xxxxxxxxxx:xxxxxxxxxxxxxxxxxxxxxx document.getElementById('local').href = `webrtc.html?${direction}=${src}&media=${media}`; const share = document.getElementById('shareget'); - share.href = `https://alexxit.github.io/go2rtc/#${share.dataset.auth}&media=${media}`; + share.href = `https://go2rtc.org/webtorrent/#${share.dataset.auth}&media=${media}`; } function share(method) { From a09e1b2f902c342fba7521e29c626147698f3d37 Mon Sep 17 00:00:00 2001 From: Alex X Date: Sat, 27 Dec 2025 17:43:19 +0300 Subject: [PATCH 25/26] Update openapi --- api/openapi.yaml | 770 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 687 insertions(+), 83 deletions(-) diff --git a/api/openapi.yaml b/api/openapi.yaml index 6ec0b492..b6110572 100644 --- a/api/openapi.yaml +++ b/api/openapi.yaml @@ -2,8 +2,8 @@ openapi: 3.1.0 info: title: go2rtc + version: 1.9.13 license: { name: MIT,url: https://opensource.org/licenses/MIT } - version: 1.0.0 contact: { url: https://github.com/AlexxIT/go2rtc } description: | Ultimate camera streaming application with support RTSP, RTMP, HTTP-FLV, WebRTC, MSE, HLS, MP4, MJPEG, HomeKit, FFmpeg, etc. @@ -11,6 +11,28 @@ info: servers: - url: http://localhost:1984 +tags: + - name: Application + description: "[Module: API](https://github.com/AlexxIT/go2rtc#module-api)" + - name: Config + description: "[Configuration](https://github.com/AlexxIT/go2rtc#configuration)" + - name: Streams list + description: "[Module: Streams](https://github.com/AlexxIT/go2rtc#module-streams)" + - name: Consume stream + - name: HLS + - name: Snapshot + - name: Produce stream + - name: WebSocket + description: "WebSocket API endpoint: `/api/ws` (see `api/README.md`)" + - name: Discovery + - name: HomeKit + - name: ONVIF + - name: RTSPtoWebRTC + - name: WebTorrent + description: "[Module: WebTorrent](https://github.com/AlexxIT/go2rtc#module-webtorrent)" + - name: FFmpeg + - name: Debug + components: parameters: stream_src_path: @@ -20,6 +42,7 @@ components: required: true schema: { type: string } example: camera1 + stream_dst_path: name: dst in: path @@ -27,6 +50,7 @@ components: required: true schema: { type: string } example: camera1 + stream_src_query: name: src in: query @@ -34,6 +58,15 @@ components: required: true schema: { type: string } example: camera1 + + hls_session_id_path: + name: id + in: path + description: HLS session ID (passed as query param `id`) + required: true + schema: { type: string } + example: DvmHdd9w + mp4_filter: name: mp4 in: query @@ -43,6 +76,7 @@ components: type: string enum: [ "", flac, all ] example: flac + video_filter: name: video in: query @@ -51,6 +85,7 @@ components: type: string enum: [ "", all, h264, h265, mjpeg ] example: h264,h265 + audio_filter: name: audio in: query @@ -59,35 +94,20 @@ components: type: string enum: [ "", all, aac, opus, pcm, pcmu, pcma ] example: aac + responses: discovery: description: "" content: application/json: example: { streams: [ { "name": "Camera 1","url": "..." } ] } + webtorrent: description: "" content: application/json: example: { share: AKDypPy4zz, pwd: H0Km1HLTTP } -tags: - - name: Application - description: "[Module: API](https://github.com/AlexxIT/go2rtc#module-api)" - - name: Config - description: "[Configuration](https://github.com/AlexxIT/go2rtc#configuration)" - - name: Streams list - description: "[Module: Streams](https://github.com/AlexxIT/go2rtc#module-streams)" - - name: Consume stream - - name: Snapshot - - name: Produce stream - - name: Discovery - - name: ONVIF - - name: RTSPtoWebRTC - - name: WebTorrent - description: "[Module: WebTorrent](https://github.com/AlexxIT/go2rtc#module-webtorrent)" - - name: Debug - paths: /api: get: @@ -98,7 +118,17 @@ paths: description: "" content: application/json: - example: { config_path: "/config/go2rtc.yaml",host: "192.168.1.123:1984",rtsp: { listen: ":8554",default_query: "video&audio" },version: "1.5.0" } + schema: + type: object + properties: + config_path: { type: string, example: "/config/go2rtc.yaml" } + host: { type: string, example: "192.168.1.123:1984" } + rtsp: + type: object + properties: + listen: { type: string, example: ":8554" } + default_query: { type: string, example: "video&audio" } + version: { type: string, example: "1.9.12" } /api/exit: post: @@ -112,17 +142,39 @@ paths: schema: { type: integer } example: 100 responses: - default: - description: Default response + default: + description: "" /api/restart: post: - summary: Restart Daemon + summary: Restart daemon description: Restarts the daemon. tags: [ Application ] responses: - default: - description: Default response + default: + description: "" + + /api/log: + get: + summary: Get in-memory logs buffer + description: | + Returns current log output from the in-memory circular buffer. + tags: [ Application ] + responses: + "200": + description: OK + content: + application/jsonlines: + example: | + {"level":"info","version":"1.9.13","platform":"linux/amd64","revision":"dfe4755","time":1766841087331,"message":"go2rtc"} + delete: + summary: Clear in-memory logs buffer + tags: [ Application ] + responses: + "200": + description: "" + content: + text/plain: { example: "" } /api/config: get: @@ -133,6 +185,8 @@ paths: description: "" content: application/yaml: { example: "streams:..." } + "404": + description: Config file not found post: summary: Rewrite main config file tags: [ Config ] @@ -140,8 +194,8 @@ paths: content: "*/*": { example: "streams:..." } responses: - default: - description: Default response + default: + description: "" patch: summary: Merge changes to main config file tags: [ Config ] @@ -149,8 +203,8 @@ paths: content: "*/*": { example: "streams:..." } responses: - default: - description: Default response + default: + description: "" @@ -162,7 +216,16 @@ paths: "200": description: "" content: - application/json: { example: { camera1: { producers: [ ],consumers: [ ] } } } + application/json: + schema: + type: object + additionalProperties: + type: object + properties: + producers: + type: array + consumers: + type: array put: summary: Create new stream tags: [ Streams list ] @@ -180,8 +243,8 @@ paths: schema: { type: string } example: camera1 responses: - default: - description: Default response + default: + description: "" patch: summary: Update stream source tags: [ Streams list ] @@ -199,8 +262,8 @@ paths: schema: { type: string } example: camera1 responses: - default: - description: Default response + default: + description: "" delete: summary: Delete stream tags: [ Streams list ] @@ -212,8 +275,8 @@ paths: schema: { type: string } example: camera1 responses: - default: - description: Default response + default: + description: "" post: summary: Send stream from source to destination description: "[Stream to camera](https://github.com/AlexxIT/go2rtc#stream-to-camera)" @@ -232,10 +295,26 @@ paths: schema: { type: string } example: camera1 responses: - default: - description: Default response - + default: + description: "" + /api/streams.dot: + get: + summary: Get streams graph in Graphviz DOT format + tags: [ Streams list ] + parameters: + - name: src + in: query + description: Stream name filter. Repeat `src` to include multiple streams. + required: false + schema: { type: string } + example: camera1 + responses: + "200": + description: OK + content: + text/vnd.graphviz: + example: "digraph { ... }" /api/preload: get: @@ -245,7 +324,17 @@ paths: "200": description: "" content: - application/json: { example: { camera1: "video&audio", camera2: "video" } } + application/json: + schema: + type: object + additionalProperties: + type: object + properties: + consumer: + type: object + query: + type: string + example: "video&audio" put: summary: Preload new stream tags: [ Streams list ] @@ -275,8 +364,8 @@ paths: schema: { type: string } example: all,aac,opus,... responses: - default: - description: Default response + default: + description: "" delete: summary: Delete preloaded stream tags: [ Streams list ] @@ -288,9 +377,22 @@ paths: schema: { type: string } example: "camera1" responses: - default: - description: Default response + default: + description: "" + /api/schemes: + get: + summary: Get supported source URL schemes + tags: [ Streams list ] + responses: + "200": + description: OK + content: + application/json: + schema: + type: array + items: { type: string } + example: [ rtsp, rtmp, webrtc, ffmpeg, hass ] /api/streams?src={src}: @@ -304,7 +406,17 @@ paths: description: "" content: application/json: - example: { producers: [ { url: "rtsp://rtsp:12345678@192.168.1.123/av_stream/ch0" } ], consumers: [ ] } + schema: + type: object + additionalProperties: + type: object + properties: + producers: + type: array + items: { type: object } + consumers: + type: array + items: { type: object } /api/webrtc?src={src}: post: @@ -324,7 +436,6 @@ paths: application/json: { example: { type: offer, sdp: "v=0..." } } "application/sdp": { example: "v=0..." } "*/*": { example: "v=0..." } - responses: "200": description: "Response on JSON or raw SDP" @@ -355,6 +466,16 @@ paths: required: false schema: { type: string } example: camera1.mp4 + - name: rotate + in: query + description: "Rotate video (degrees). Supported values: 90, 180, 270." + required: false + schema: { type: integer, enum: [ 90, 180, 270 ] } + - name: scale + in: query + description: Scale video in format `width:height` + required: false + schema: { type: string, example: "1280:720" } - $ref: "#/components/parameters/mp4_filter" - $ref: "#/components/parameters/video_filter" - $ref: "#/components/parameters/audio_filter" @@ -367,7 +488,7 @@ paths: get: summary: Get stream in HLS format description: "[Module: HLS](https://github.com/AlexxIT/go2rtc#module-hls)" - tags: [ Consume stream ] + tags: [ Consume stream, HLS ] parameters: - $ref: "#/components/parameters/stream_src_path" - $ref: "#/components/parameters/mp4_filter" @@ -378,6 +499,62 @@ paths: description: "" content: { application/vnd.apple.mpegurl: { example: "" } } + /api/hls/playlist.m3u8?id={id}: + get: + summary: Get HLS media playlist for an active session + tags: [ HLS ] + parameters: + - $ref: "#/components/parameters/hls_session_id_path" + responses: + "200": + description: OK + content: + application/vnd.apple.mpegurl: { example: "" } + "404": + description: Session not found + + /api/hls/segment.ts?id={id}: + get: + summary: Get HLS MPEG-TS segment for an active session + tags: [ HLS ] + parameters: + - $ref: "#/components/parameters/hls_session_id_path" + responses: + "200": + description: OK + content: + video/mp2t: { example: "" } + "404": + description: Segment or session not found + + /api/hls/init.mp4?id={id}: + get: + summary: Get HLS fMP4 init segment for an active session + tags: [ HLS ] + parameters: + - $ref: "#/components/parameters/hls_session_id_path" + responses: + "200": + description: OK + content: + video/mp4: { example: "" } + "404": + description: Segment or session not found + + /api/hls/segment.m4s?id={id}: + get: + summary: Get HLS fMP4 media segment for an active session + tags: [ HLS ] + parameters: + - $ref: "#/components/parameters/hls_session_id_path" + responses: + "200": + description: OK + content: + video/iso.segment: { example: "" } + "404": + description: Segment or session not found + /api/stream.mjpeg?src={src}: get: summary: Get stream in MJPEG format @@ -390,7 +567,91 @@ paths: description: "" content: { multipart/x-mixed-replace: { example: "" } } + /api/stream.ascii?src={src}: + get: + summary: Get stream in ASCII-art format (ANSI escape codes) + description: "[Module: MJPEG](https://github.com/AlexxIT/go2rtc#module-mjpeg)" + tags: [ Consume stream ] + parameters: + - $ref: "#/components/parameters/stream_src_path" + - name: color + in: query + description: Foreground mode (`8`, `256`, `rgb` or ANSI SGR code) + required: false + schema: { type: string } + - name: back + in: query + description: Background mode (`8`, `256`, `rgb` or ANSI SGR code) + required: false + schema: { type: string } + - name: text + in: query + description: Charset preset (empty/default, `block`) or custom characters + required: false + schema: { type: string } + responses: + "200": + description: OK + content: + text/plain: { example: "" } + "404": + description: Stream not found + /api/stream.y4m?src={src}: + get: + summary: Get stream in YUV4MPEG2 format (y4m) + tags: [ Consume stream ] + parameters: + - $ref: "#/components/parameters/stream_src_path" + responses: + "200": + description: OK + content: + application/octet-stream: { example: "" } + "404": + description: Stream not found + + /api/stream.ts?src={src}: + get: + summary: Get stream in MPEG-TS format + tags: [ Consume stream ] + parameters: + - $ref: "#/components/parameters/stream_src_path" + responses: + "200": + description: OK + content: + video/mp2t: { example: "" } + "404": + description: Stream not found + + /api/stream.aac?src={src}: + get: + summary: Get stream audio in AAC (ADTS) format + tags: [ Consume stream ] + parameters: + - $ref: "#/components/parameters/stream_src_path" + responses: + "200": + description: OK + content: + audio/aac: { example: "" } + "404": + description: Stream not found + + /api/stream.flv?src={src}: + get: + summary: Get stream in FLV format + tags: [ Consume stream ] + parameters: + - $ref: "#/components/parameters/stream_src_path" + responses: + "200": + description: OK + content: + video/x-flv: { example: "" } + "404": + description: Stream not found /api/frame.jpeg?src={src}: get: @@ -399,10 +660,37 @@ paths: tags: [ Snapshot ] parameters: - $ref: "#/components/parameters/stream_src_path" + - name: name + in: query + description: Optional stream name to create/update if `src` is a URL + required: false + schema: { type: string } + - name: width + in: query + description: "Scale output width (alias: `w`)" + required: false + schema: { type: integer, minimum: 1 } + - name: height + in: query + description: "Scale output height (alias: `h`)" + required: false + schema: { type: integer, minimum: 1 } + - name: rotate + in: query + description: "Rotate output (degrees). Supported values: 90, 180, 270." + required: false + schema: { type: integer, enum: [ 90, 180, 270 ] } + - name: hardware + in: query + description: "Hardware acceleration engine for FFmpeg snapshot transcoding (alias: `hw`)" + required: false + schema: { type: string } responses: - 200: + "200": description: "" - content: { image/jpeg: { example: "" } } + content: + image/jpeg: { example: "" } + /api/frame.mp4?src={src}: get: summary: Get snapshot in MP4 format @@ -410,23 +698,51 @@ paths: tags: [ Snapshot ] parameters: - $ref: "#/components/parameters/stream_src_path" + - name: filename + in: query + description: Download as a file with this name + required: false + schema: { type: string } + example: camera1.mp4 responses: 200: description: "" - content: { video/mp4: { example: "" } } + content: + video/mp4: { example: "" } /api/webrtc?dst={dst}: post: - summary: Post stream in WebRTC format + summary: Post stream in WebRTC format (WHIP) description: "[Incoming: WebRTC/WHIP](https://github.com/AlexxIT/go2rtc#incoming-webrtcwhip)" tags: [ Produce stream ] parameters: - $ref: "#/components/parameters/stream_dst_path" responses: - default: - description: Default response + "201": + description: Created + headers: + Location: + description: Resource URL for session + schema: { type: string } + content: + application/sdp: { example: "v=0..." } + "404": + description: Stream not found + + /api/stream?dst={dst}: + post: + summary: Post stream in auto-detected format + description: | + Incoming source with automatic format detection. Use for pushing a stream into an existing `dst` stream. + tags: [ Produce stream ] + parameters: + - $ref: "#/components/parameters/stream_dst_path" + responses: + default: + description: "" + /api/stream.flv?dst={dst}: post: summary: Post stream in FLV format @@ -435,8 +751,9 @@ paths: parameters: - $ref: "#/components/parameters/stream_dst_path" responses: - default: - description: Default response + default: + description: "" + /api/stream.ts?dst={dst}: post: summary: Post stream in MPEG-TS format @@ -445,8 +762,9 @@ paths: parameters: - $ref: "#/components/parameters/stream_dst_path" responses: - default: - description: Default response + default: + description: "" + /api/stream.mjpeg?dst={dst}: post: summary: Post stream in MJPEG format @@ -455,10 +773,55 @@ paths: parameters: - $ref: "#/components/parameters/stream_dst_path" responses: - default: - description: Default response + default: + description: "" + /api/ffmpeg: + post: + summary: Play file/live/TTS into a stream via FFmpeg + description: | + Helper endpoint for "stream to camera" scenarios. + Exactly one of `file`, `live`, `text` should be provided. + tags: [ FFmpeg ] + parameters: + - name: dst + in: query + description: Destination stream name + required: true + schema: { type: string } + example: camera1 + - name: file + in: query + description: Input URL to treat as file (`#input=file`) + required: false + schema: { type: string } + example: "http://example.com/song.mp3" + - name: live + in: query + description: Live input URL + required: false + schema: { type: string } + example: "http://example.com/live.mp3" + - name: text + in: query + description: Text-to-speech phrase + required: false + schema: { type: string } + example: "Hello" + - name: voice + in: query + description: Optional TTS voice (engine-dependent) + required: false + schema: { type: string } + responses: + "200": + description: OK + "400": + description: Invalid parameters + "404": + description: Stream not found + /api/dvrip: get: @@ -466,8 +829,7 @@ paths: description: "[Source: DVRIP](https://github.com/AlexxIT/go2rtc#source-dvrip)" tags: [ Discovery ] responses: - default: - description: Default response + "200": { $ref: "#/components/responses/discovery" } /api/ffmpeg/devices: get: @@ -475,56 +837,275 @@ paths: description: "[Source: FFmpeg Device](https://github.com/AlexxIT/go2rtc#source-ffmpeg-device)" tags: [ Discovery ] responses: - default: - description: Default response + "200": { $ref: "#/components/responses/discovery" } + /api/ffmpeg/hardware: get: summary: FFmpeg hardware transcoding discovery description: "[Hardware acceleration](https://github.com/AlexxIT/go2rtc/wiki/Hardware-acceleration)" tags: [ Discovery ] responses: - default: - description: Default response + "200": { $ref: "#/components/responses/discovery" } + + /api/v4l2: + get: + summary: V4L2 video devices discovery (Linux) + tags: [ Discovery ] + responses: + "200": { $ref: "#/components/responses/discovery" } + + /api/alsa: + get: + summary: ALSA audio devices discovery (Linux) + tags: [ Discovery ] + responses: + "200": { $ref: "#/components/responses/discovery" } + + /api/gopro: + get: + summary: GoPro cameras discovery + tags: [ Discovery ] + responses: + "200": { $ref: "#/components/responses/discovery" } + + /api/ring: + get: + summary: Ring cameras discovery + description: | + Provide either `email`/`password` (and optional `code` for 2FA) or `refresh_token`. + If 2FA is required, returns a JSON prompt instead of sources. + tags: [ Discovery ] + parameters: + - name: email + in: query + required: false + schema: { type: string } + - name: password + in: query + required: false + schema: { type: string } + - name: code + in: query + required: false + schema: { type: string } + - name: refresh_token + in: query + required: false + schema: { type: string } + responses: + "200": + description: OK + content: + application/json: { example: "" } + + /api/tuya: + get: + summary: Tuya cameras discovery + tags: [ Discovery ] + parameters: + - name: region + in: query + description: Tuya API host (region) + required: true + schema: { type: string } + example: "openapi.tuyaus.com" + - name: email + in: query + required: true + schema: { type: string } + - name: password + in: query + required: true + schema: { type: string } + responses: + "200": { $ref: "#/components/responses/discovery" } + "400": + description: Invalid parameters + "404": + description: No cameras found + /api/hass: get: summary: Home Assistant cameras discovery description: "[Source: Hass](https://github.com/AlexxIT/go2rtc#source-hass)" tags: [ Discovery ] responses: - default: - description: Default response - /api/homekit: + "200": { $ref: "#/components/responses/discovery" } + "404": { description: No Hass config } + + /api/discovery/homekit: get: summary: HomeKit cameras discovery description: "[Source: HomeKit](https://github.com/AlexxIT/go2rtc#source-homekit)" tags: [ Discovery ] responses: - default: - description: Default response + "200": { $ref: "#/components/responses/discovery" } + /api/nest: get: summary: Nest cameras discovery tags: [ Discovery ] + parameters: + - name: client_id + in: query + required: true + schema: { type: string } + - name: client_secret + in: query + required: true + schema: { type: string } + - name: refresh_token + in: query + required: true + schema: { type: string } + - name: project_id + in: query + required: true + schema: { type: string } responses: - default: - description: Default response + "200": { $ref: "#/components/responses/discovery" } + /api/onvif: get: summary: ONVIF cameras discovery description: "[Source: ONVIF](https://github.com/AlexxIT/go2rtc#source-onvif)" tags: [ Discovery ] + parameters: + - name: src + in: query + description: Optional ONVIF device URL to enumerate profiles + required: false + schema: { type: string } + example: "onvif://user:pass@192.168.1.50:80" responses: - default: - description: Default response + "200": { $ref: "#/components/responses/discovery" } + /api/roborock: get: - summary: Roborock vacuums discovery + summary: Roborock vacuums discovery (requires prior auth) description: "[Source: Roborock](https://github.com/AlexxIT/go2rtc#source-roborock)" tags: [ Discovery ] responses: - default: - description: Default response + "200": { $ref: "#/components/responses/discovery" } + "404": + description: No auth + post: + summary: Roborock login and discovery + tags: [ Discovery ] + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + properties: + username: { type: string } + password: { type: string } + required: [ username, password ] + responses: + "200": { $ref: "#/components/responses/discovery" } + /api/homekit: + get: + summary: Get HomeKit servers state + tags: [ HomeKit ] + parameters: + - name: id + in: query + description: Optional stream name (server ID) + required: false + schema: { type: string } + responses: + "200": + description: OK + content: + application/json: { example: "" } + "404": + description: Server not found + post: + summary: Pair HomeKit camera and create/update stream + tags: [ HomeKit ] + parameters: + - name: id + in: query + description: Stream name to create/update + required: true + schema: { type: string } + - name: src + in: query + description: HomeKit URL (without pin) + required: true + schema: { type: string } + - name: pin + in: query + description: HomeKit PIN + required: true + schema: { type: string } + responses: + "200": + description: OK + delete: + summary: Unpair HomeKit camera and delete stream + tags: [ HomeKit ] + parameters: + - name: id + in: query + description: Stream name / server ID + required: true + schema: { type: string } + responses: + "200": + description: OK + "404": + description: Stream not found + + /api/homekit/accessories: + get: + summary: Get HomeKit accessories JSON for a stream + tags: [ HomeKit ] + parameters: + - name: id + in: query + description: Stream name + required: true + schema: { type: string } + responses: + "200": + description: OK + content: + application/json: { example: { } } + "404": + description: Stream not found + + /pair-setup: + post: + summary: HomeKit Pair Setup (HAP) + description: HomeKit Accessory Protocol endpoint (TLV8). + tags: [ HomeKit ] + requestBody: + required: true + content: + application/pairing+tlv8: { example: "" } + responses: + "200": + description: OK + content: + application/pairing+tlv8: { example: "" } + + /pair-verify: + post: + summary: HomeKit Pair Verify (HAP) + description: HomeKit Accessory Protocol endpoint (TLV8). + tags: [ HomeKit ] + requestBody: + required: true + content: + application/pairing+tlv8: { example: "" } + responses: + "200": + description: OK + content: + application/pairing+tlv8: { example: "" } /onvif/: @@ -533,8 +1114,8 @@ paths: description: Simple realisation of the ONVIF protocol. Accepts any suburl requests tags: [ ONVIF ] responses: - default: - description: Default response + default: + description: "" @@ -544,8 +1125,33 @@ paths: description: Simple API for support [RTSPtoWebRTC](https://www.home-assistant.io/integrations/rtsp_to_webrtc/) integration tags: [ RTSPtoWebRTC ] responses: - default: - description: Default response + default: + description: "" + + + /api/ws: + get: + summary: WebSocket endpoint + description: | + Upgrade to WebSocket and exchange JSON messages: + - Request: `{ "type": "...", "value": ... }` + - Response: `{ "type": "...", "value": ... }` + + Supported message types depend on enabled modules (see `api/README.md`). + tags: [ WebSocket ] + parameters: + - name: src + in: query + description: Stream name (consumer) + required: false + schema: { type: string } + - name: dst + in: query + description: Stream name (producer) + required: false + schema: { type: string } + responses: + "101": { description: Switching Protocols } @@ -564,15 +1170,13 @@ paths: - $ref: "#/components/parameters/stream_src_path" responses: 200: { $ref: "#/components/responses/webtorrent" } - delete: summary: Delete WebTorrent share tags: [ WebTorrent ] parameters: - $ref: "#/components/parameters/stream_src_path" responses: - default: - description: Default response + default: { description: "" } /api/webtorrent: get: From 4dae65a5351e415266ceb9589c2494e8dcc8da9f Mon Sep 17 00:00:00 2001 From: Alex X Date: Wed, 31 Dec 2025 17:55:47 +0300 Subject: [PATCH 26/26] Fix audio sample rate for some xiaomi cameras #2006 --- pkg/xiaomi/miss/client.go | 18 ++++++++++++++++++ pkg/xiaomi/producer.go | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/pkg/xiaomi/miss/client.go b/pkg/xiaomi/miss/client.go index 470c0e0e..f7157ccf 100644 --- a/pkg/xiaomi/miss/client.go +++ b/pkg/xiaomi/miss/client.go @@ -271,6 +271,24 @@ type Packet struct { Payload []byte } +func (p *Packet) SampleRate() uint32 { + // flag: 1 0011 000 - sample rate 16000 + // flag: 100 00 01 0000 000 - sample rate 8000 + v := (p.Flags >> 3) & 0b1111 + if v != 0 { + return 16000 + } + return 8000 +} + +//func (p *Packet) AudioUnknown1() byte { +// return byte((p.Flags >> 7) & 0b11) +//} +// +//func (p *Packet) AudioUnknown2() byte { +// return byte((p.Flags >> 9) & 0b11) +//} + func GenerateKey() ([]byte, []byte, error) { public, private, err := box.GenerateKey(rand.Reader) if err != nil { diff --git a/pkg/xiaomi/producer.go b/pkg/xiaomi/producer.go index 09ba7360..dcd419b8 100644 --- a/pkg/xiaomi/producer.go +++ b/pkg/xiaomi/producer.go @@ -110,7 +110,7 @@ func probe(client *miss.Client, channel, quality, audio uint8) ([]*core.Media, e } case miss.CodecPCMA: if acodec == nil { - acodec = &core.Codec{Name: core.CodecPCMA, ClockRate: 8000} + acodec = &core.Codec{Name: core.CodecPCMA, ClockRate: pkt.SampleRate()} } case miss.CodecOPUS: if acodec == nil {