Compare commits
52 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a87dafbbec | |||
| 742cb7699b | |||
| 43449e7b08 | |||
| 33512e73bd | |||
| b367ffee6d | |||
| 69447df6b3 | |||
| a6eac4ff02 | |||
| 1eaf879a76 | |||
| c9ae6dcc03 | |||
| befa6bd356 | |||
| 100ab62ab4 | |||
| a0f999d9c9 | |||
| 9bda2f7e60 | |||
| 54b19999c6 | |||
| aa3c081352 | |||
| 2d16ee8884 | |||
| ec96a14807 | |||
| af72548a43 | |||
| 6d85b36f47 | |||
| 28830a697d | |||
| 5d3953a948 | |||
| 4d6432d38d | |||
| bcbebd5a36 | |||
| 50e2a626a6 | |||
| f4fe8c3769 | |||
| e42085a237 | |||
| a060b3447c | |||
| d7784b24c6 | |||
| 39645cb3d8 | |||
| 36166caccc | |||
| 0f1dc73d55 | |||
| 6b29c37433 | |||
| 535bacf9d6 | |||
| e6fb4081f7 | |||
| eb04fafaa4 | |||
| b4ed738d17 | |||
| 6a9ae93fa1 | |||
| 2dd47654e6 | |||
| c27e735c17 | |||
| 8bc65e4c91 | |||
| 0a476a74b3 | |||
| b5be4ce03b | |||
| f291f1d827 | |||
| 041ce885c7 | |||
| df16f28825 | |||
| a8867bc3cb | |||
| b2b115ec9c | |||
| 95de3a1f3e | |||
| dd4376cd37 | |||
| 20d45bff92 | |||
| 4ad67e9f6f | |||
| e367940bd9 |
@@ -23,7 +23,7 @@ Ultimate camera streaming application with support RTSP, WebRTC, HomeKit, FFmpeg
|
|||||||
- mixing tracks from different sources to single stream
|
- mixing tracks from different sources to single stream
|
||||||
- auto match client supported codecs
|
- auto match client supported codecs
|
||||||
- [2-way audio](#two-way-audio) for some cameras
|
- [2-way audio](#two-way-audio) for some cameras
|
||||||
- streaming from private networks via [Ngrok](#module-ngrok)
|
- streaming from private networks via [ngrok](#module-ngrok)
|
||||||
- can be [integrated to](#module-api) any smart home platform or be used as [standalone app](#go2rtc-binary)
|
- can be [integrated to](#module-api) any smart home platform or be used as [standalone app](#go2rtc-binary)
|
||||||
|
|
||||||
**Inspired by:**
|
**Inspired by:**
|
||||||
@@ -54,11 +54,13 @@ Ultimate camera streaming application with support RTSP, WebRTC, HomeKit, FFmpeg
|
|||||||
* [Source: FFmpeg Device](#source-ffmpeg-device)
|
* [Source: FFmpeg Device](#source-ffmpeg-device)
|
||||||
* [Source: Exec](#source-exec)
|
* [Source: Exec](#source-exec)
|
||||||
* [Source: Echo](#source-echo)
|
* [Source: Echo](#source-echo)
|
||||||
|
* [Source: Expr](#source-expr)
|
||||||
* [Source: HomeKit](#source-homekit)
|
* [Source: HomeKit](#source-homekit)
|
||||||
* [Source: Bubble](#source-bubble)
|
* [Source: Bubble](#source-bubble)
|
||||||
* [Source: DVRIP](#source-dvrip)
|
* [Source: DVRIP](#source-dvrip)
|
||||||
* [Source: Tapo](#source-tapo)
|
* [Source: Tapo](#source-tapo)
|
||||||
* [Source: Kasa](#source-kasa)
|
* [Source: Kasa](#source-kasa)
|
||||||
|
* [Source: GoPro](#source-gopro)
|
||||||
* [Source: Ivideon](#source-ivideon)
|
* [Source: Ivideon](#source-ivideon)
|
||||||
* [Source: Hass](#source-hass)
|
* [Source: Hass](#source-hass)
|
||||||
* [Source: ISAPI](#source-isapi)
|
* [Source: ISAPI](#source-isapi)
|
||||||
@@ -75,7 +77,7 @@ Ultimate camera streaming application with support RTSP, WebRTC, HomeKit, FFmpeg
|
|||||||
* [Module: WebRTC](#module-webrtc)
|
* [Module: WebRTC](#module-webrtc)
|
||||||
* [Module: HomeKit](#module-homekit)
|
* [Module: HomeKit](#module-homekit)
|
||||||
* [Module: WebTorrent](#module-webtorrent)
|
* [Module: WebTorrent](#module-webtorrent)
|
||||||
* [Module: Ngrok](#module-ngrok)
|
* [Module: ngrok](#module-ngrok)
|
||||||
* [Module: Hass](#module-hass)
|
* [Module: Hass](#module-hass)
|
||||||
* [Module: MP4](#module-mp4)
|
* [Module: MP4](#module-mp4)
|
||||||
* [Module: HLS](#module-hls)
|
* [Module: HLS](#module-hls)
|
||||||
@@ -125,7 +127,7 @@ Don't forget to fix the rights `chmod +x go2rtc_xxx_xxx` on Linux and Mac.
|
|||||||
|
|
||||||
### go2rtc: Docker
|
### go2rtc: Docker
|
||||||
|
|
||||||
Container [alexxit/go2rtc](https://hub.docker.com/r/alexxit/go2rtc) with support `amd64`, `386`, `arm64`, `arm`. This container is the same as [Home Assistant Add-on](#go2rtc-home-assistant-add-on), but can be used separately from Home Assistant. Container has preinstalled [FFmpeg](#source-ffmpeg), [Ngrok](#module-ngrok) and [Python](#source-echo).
|
Container [alexxit/go2rtc](https://hub.docker.com/r/alexxit/go2rtc) with support `amd64`, `386`, `arm64`, `arm`. This container is the same as [Home Assistant Add-on](#go2rtc-home-assistant-add-on), but can be used separately from Home Assistant. Container has preinstalled [FFmpeg](#source-ffmpeg), [ngrok](#module-ngrok) and [Python](#source-echo).
|
||||||
|
|
||||||
### go2rtc: Home Assistant Add-on
|
### go2rtc: Home Assistant Add-on
|
||||||
|
|
||||||
@@ -168,7 +170,7 @@ Available modules:
|
|||||||
- [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
|
||||||
- [ngrok](#module-ngrok) - Ngrok integration (external access for private network)
|
- [ngrok](#module-ngrok) - ngrok integration (external access for private network)
|
||||||
- [hass](#module-hass) - Home Assistant integration
|
- [hass](#module-hass) - Home Assistant integration
|
||||||
- [log](#module-log) - logs config
|
- [log](#module-log) - logs config
|
||||||
|
|
||||||
@@ -186,11 +188,13 @@ Available source types:
|
|||||||
- [ffmpeg:device](#source-ffmpeg-device) - local USB Camera or Webcam
|
- [ffmpeg:device](#source-ffmpeg-device) - local USB Camera or Webcam
|
||||||
- [exec](#source-exec) - get media from external app output
|
- [exec](#source-exec) - get media from external app output
|
||||||
- [echo](#source-echo) - get stream link from bash or python
|
- [echo](#source-echo) - get stream link from bash or python
|
||||||
|
- [expr](#source-expr) - get stream link via built-in expression language
|
||||||
- [homekit](#source-homekit) - streaming from HomeKit Camera
|
- [homekit](#source-homekit) - streaming from HomeKit Camera
|
||||||
- [bubble](#source-bubble) - streaming from ESeeCloud/dvr163 NVR
|
- [bubble](#source-bubble) - streaming from ESeeCloud/dvr163 NVR
|
||||||
- [dvrip](#source-dvrip) - streaming from DVR-IP NVR
|
- [dvrip](#source-dvrip) - streaming from DVR-IP NVR
|
||||||
- [tapo](#source-tapo) - TP-Link Tapo cameras with [two way audio](#two-way-audio) support
|
- [tapo](#source-tapo) - TP-Link Tapo cameras with [two way audio](#two-way-audio) support
|
||||||
- [kasa](#source-tapo) - TP-Link Kasa cameras
|
- [kasa](#source-tapo) - TP-Link Kasa cameras
|
||||||
|
- [gopro](#source-gopro) - GoPro cameras
|
||||||
- [ivideon](#source-ivideon) - public cameras from [Ivideon](https://tv.ivideon.com/) service
|
- [ivideon](#source-ivideon) - public cameras from [Ivideon](https://tv.ivideon.com/) service
|
||||||
- [hass](#source-hass) - Home Assistant integration
|
- [hass](#source-hass) - Home Assistant integration
|
||||||
- [isapi](#source-isapi) - two way audio for Hikvision (ISAPI) cameras
|
- [isapi](#source-isapi) - two way audio for Hikvision (ISAPI) cameras
|
||||||
@@ -426,6 +430,10 @@ streams:
|
|||||||
apple_hls: echo:python3 hls.py https://developer.apple.com/streaming/examples/basic-stream-osx-ios5.html
|
apple_hls: echo:python3 hls.py https://developer.apple.com/streaming/examples/basic-stream-osx-ios5.html
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Source: Expr
|
||||||
|
|
||||||
|
Like `echo` source, but uses the built-in [expr](https://github.com/antonmedv/expr) expression language ([read more](https://github.com/AlexxIT/go2rtc/blob/master/internal/expr/README.md)).
|
||||||
|
|
||||||
#### Source: HomeKit
|
#### Source: HomeKit
|
||||||
|
|
||||||
**Important:**
|
**Important:**
|
||||||
@@ -514,6 +522,10 @@ streams:
|
|||||||
kasa: kasa://user:pass@192.168.1.123:19443/https/stream/mixed
|
kasa: kasa://user:pass@192.168.1.123:19443/https/stream/mixed
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Source: GoPro
|
||||||
|
|
||||||
|
Support streaming from [GoPro](https://gopro.com/) cameras, connected via USB or Wi-Fi to Linux, Mac, Windows. [Read more](https://github.com/AlexxIT/go2rtc/tree/master/internal/gopro).
|
||||||
|
|
||||||
#### Source: Ivideon
|
#### Source: Ivideon
|
||||||
|
|
||||||
Support public cameras from service [Ivideon](https://tv.ivideon.com/).
|
Support public cameras from service [Ivideon](https://tv.ivideon.com/).
|
||||||
@@ -849,7 +861,7 @@ webrtc:
|
|||||||
|
|
||||||
**Private IP**
|
**Private IP**
|
||||||
|
|
||||||
- setup integration with [Ngrok service](#module-ngrok)
|
- setup integration with [ngrok service](#module-ngrok)
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
ngrok:
|
ngrok:
|
||||||
@@ -951,29 +963,29 @@ Link example: https://alexxit.github.io/go2rtc/#share=02SNtgjKXY&pwd=wznEQqznxW&
|
|||||||
|
|
||||||
TODO: article how it works...
|
TODO: article how it works...
|
||||||
|
|
||||||
### Module: Ngrok
|
### Module: ngrok
|
||||||
|
|
||||||
With Ngrok integration you can get external access to your streams in situation when you have Internet with private IP-address.
|
With ngrok integration you can get external access to your streams in situations when you have Internet with private IP-address.
|
||||||
|
|
||||||
- Ngrok preistalled for **Docker** and **Hass Add-on** users
|
- ngrok is pre-installed for **Docker** and **Hass Add-on** users
|
||||||
- you may need external access for two different things:
|
- you may need external access for two different things:
|
||||||
- WebRTC stream, so you need tunnel WebRTC TCP port (ex. 8555)
|
- WebRTC stream, so you need tunnel WebRTC TCP port (ex. 8555)
|
||||||
- go2rtc web interface, so you need tunnel API HTTP port (ex. 1984)
|
- go2rtc web interface, so you need tunnel API HTTP port (ex. 1984)
|
||||||
- Ngrok support authorization for your web interface
|
- ngrok support authorization for your web interface
|
||||||
- Ngrok automatically adds HTTPS to your web interface
|
- ngrok automatically adds HTTPS to your web interface
|
||||||
|
|
||||||
Ngrok free subscription limitations:
|
The ngrok free subscription has the following limitations:
|
||||||
|
|
||||||
- you will always get random external address (not a problem for webrtc stream)
|
- You can reserve a free domain for serving the web interface, but the TCP address you get will always be random and change with each restart of the ngrok agent (not a problem for webrtc stream)
|
||||||
- you can forward multiple ports but use only one Ngrok app
|
- You can forward multiple ports from a single agent, but you can only run one ngrok agent on the free plan
|
||||||
|
|
||||||
go2rtc will automatically get your external TCP address (if you enable it in ngrok config) and use it with WebRTC connection (if you enable it in webrtc config).
|
go2rtc will automatically get your external TCP address (if you enable it in ngrok config) and use it with WebRTC connection (if you enable it in webrtc config).
|
||||||
|
|
||||||
You need manually download [Ngrok agent app](https://ngrok.com/download) for your OS and register in [Ngrok service](https://ngrok.com/).
|
You need to manually download the [ngrok agent app](https://ngrok.com/download) for your OS and register with the [ngrok service](https://ngrok.com/signup).
|
||||||
|
|
||||||
**Tunnel for only WebRTC Stream**
|
**Tunnel for only WebRTC Stream**
|
||||||
|
|
||||||
You need to add your [Ngrok token](https://dashboard.ngrok.com/get-started/your-authtoken) and WebRTC TCP port to YAML:
|
You need to add your [ngrok authtoken](https://dashboard.ngrok.com/get-started/your-authtoken) and WebRTC TCP port to YAML:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
ngrok:
|
ngrok:
|
||||||
@@ -989,7 +1001,7 @@ ngrok:
|
|||||||
command: ngrok start --all --config ngrok.yaml
|
command: ngrok start --all --config ngrok.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
Ngrok config example:
|
ngrok config example:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
version: "2"
|
version: "2"
|
||||||
@@ -1005,6 +1017,8 @@ tunnels:
|
|||||||
proto: tcp
|
proto: tcp
|
||||||
```
|
```
|
||||||
|
|
||||||
|
See the [ngrok agent documentation](https://ngrok.com/docs/agent/config/) for more details on the ngrok configuration file.
|
||||||
|
|
||||||
### Module: Hass
|
### Module: Hass
|
||||||
|
|
||||||
The best and easiest way to use go2rtc inside the Home Assistant is to install the custom integration [WebRTC Camera](#go2rtc-home-assistant-integration) and custom lovelace card.
|
The best and easiest way to use go2rtc inside the Home Assistant is to install the custom integration [WebRTC Camera](#go2rtc-home-assistant-integration) and custom lovelace card.
|
||||||
@@ -1144,7 +1158,7 @@ webrtc:
|
|||||||
- external access to WebRTC TCP port is not a problem, because it used only for transmit encrypted media data
|
- external access to WebRTC TCP port is not a problem, because it used only for transmit encrypted media data
|
||||||
- anyway you need to open this port to your local network and to the Internet in order for WebRTC to work
|
- anyway you need to open this port to your local network and to the Internet in order for WebRTC to work
|
||||||
|
|
||||||
If you need Web interface protection without Home Assistant Add-on - you need to use reverse proxy, like [Nginx](https://nginx.org/), [Caddy](https://caddyserver.com/), [Ngrok](https://ngrok.com/), etc.
|
If you need Web interface protection without Home Assistant Add-on - you need to use reverse proxy, like [Nginx](https://nginx.org/), [Caddy](https://caddyserver.com/), [ngrok](https://ngrok.com/), etc.
|
||||||
|
|
||||||
PS. Additionally WebRTC will try to use the 8555 UDP port for transmit encrypted media. It works without problems on the local network. And sometimes also works for external access, even if you haven't opened this port on your router ([read more](https://en.wikipedia.org/wiki/UDP_hole_punching)). But for stable external WebRTC access, you need to open the 8555 port on your router for both TCP and UDP.
|
PS. Additionally WebRTC will try to use the 8555 UDP port for transmit encrypted media. It works without problems on the local network. And sometimes also works for external access, even if you haven't opened this port on your router ([read more](https://en.wikipedia.org/wiki/UDP_hole_punching)). But for stable external WebRTC access, you need to open the 8555 port on your router for both TCP and UDP.
|
||||||
|
|
||||||
@@ -1175,17 +1189,14 @@ Some examples:
|
|||||||
|
|
||||||
`AVC/H.264` video can be played almost anywhere. But `HEVC/H.265` has a lot of limitations in supporting with different devices and browsers. It's all about patents and money, you can't do anything about it.
|
`AVC/H.264` video can be played almost anywhere. But `HEVC/H.265` has a lot of limitations in supporting with different devices and browsers. It's all about patents and money, you can't do anything about it.
|
||||||
|
|
||||||
| Device | WebRTC | MSE | HTTP | HLS |
|
| Device | WebRTC | MSE | HTTP* | HLS |
|
||||||
|---------------------|-------------------------------|-------------------------------|------------------------------------|------------------------|
|
|--------------------------------------------------------------------------|-----------------------------------------|-----------------------------------------|----------------------------------------------|-----------------------------|
|
||||||
| *latency* | best | medium | bad | bad |
|
| *latency* | best | medium | bad | bad |
|
||||||
| Desktop Chrome 107+ | H264, OPUS, PCMU, PCMA | H264, H265*, AAC, FLAC*, OPUS | H264, H265*, AAC, FLAC*, OPUS, MP3 | no |
|
| - Desktop Chrome 107+ <br/> - Desktop Edge <br/> - Android Chrome 107+ | H264 <br/> PCMU, PCMA <br/> OPUS | H264, H265* <br/> AAC, FLAC* <br/> OPUS | H264, H265* <br/> AAC, FLAC* <br/> OPUS, MP3 | no |
|
||||||
| Desktop Edge | H264, OPUS, PCMU, PCMA | H264, H265*, AAC, FLAC*, OPUS | H264, H265*, AAC, FLAC*, OPUS, MP3 | no |
|
| Desktop Firefox | H264 <br/> PCMU, PCMA <br/> OPUS | H264 <br/> AAC, FLAC* <br/> OPUS | H264 <br/> AAC, FLAC* <br/> OPUS | no |
|
||||||
| Android Chrome 107+ | H264, OPUS, PCMU, PCMA | H264, H265*, AAC, FLAC*, OPUS | H264, H265*, AAC, FLAC*, OPUS, MP3 | no |
|
| - Desktop Safari 14+ <br/> - iPad Safari 14+ <br/> - iPhone Safari 17.1+ | H264, H265* <br/> PCMU, PCMA <br/> OPUS | H264, H265 <br/> AAC, FLAC* | **no!** | H264, H265 <br/> AAC, FLAC* |
|
||||||
| Desktop Firefox | H264, OPUS, PCMU, PCMA | H264, AAC, FLAC*, OPUS | H264, AAC, FLAC*, OPUS | no |
|
| iPhone Safari 14+ | H264, H265* <br/> PCMU, PCMA <br/> OPUS | **no!** | **no!** | H264, H265 <br/> AAC, FLAC* |
|
||||||
| Desktop Safari 14+ | H264, H265*, OPUS, PCMU, PCMA | H264, H265, AAC, FLAC* | **no!** | H264, H265, AAC, FLAC* |
|
| macOS [Hass App][1] | no | no | no | H264, H265 <br/> AAC, FLAC* |
|
||||||
| iPad Safari 14+ | H264, H265*, OPUS, PCMU, PCMA | H264, H265, AAC, FLAC* | **no!** | H264, H265, AAC, FLAC* |
|
|
||||||
| iPhone Safari 14+ | H264, H265*, OPUS, PCMU, PCMA | **no!** | **no!** | H264, H265, AAC, FLAC* |
|
|
||||||
| macOS [Hass App][1] | no | no | no | H264, H265, AAC, FLAC* |
|
|
||||||
|
|
||||||
[1]: https://apps.apple.com/app/home-assistant/id1099568401
|
[1]: https://apps.apple.com/app/home-assistant/id1099568401
|
||||||
|
|
||||||
@@ -1285,9 +1296,15 @@ streams:
|
|||||||
- [ioBroker.euSec](https://github.com/bropat/ioBroker.eusec) - [ioBroker](https://www.iobroker.net/) adapter for control Eufy security devices
|
- [ioBroker.euSec](https://github.com/bropat/ioBroker.eusec) - [ioBroker](https://www.iobroker.net/) adapter for control Eufy security devices
|
||||||
- [wz_mini_hacks](https://github.com/gtxaspec/wz_mini_hacks) - Custom firmware for Wyze cameras
|
- [wz_mini_hacks](https://github.com/gtxaspec/wz_mini_hacks) - Custom firmware for Wyze cameras
|
||||||
- [MMM-go2rtc](https://github.com/Anonym-tsk/MMM-go2rtc) - MagicMirror² Module
|
- [MMM-go2rtc](https://github.com/Anonym-tsk/MMM-go2rtc) - MagicMirror² Module
|
||||||
|
|
||||||
|
**Distributions**
|
||||||
|
|
||||||
- [Alpine Linux](https://pkgs.alpinelinux.org/packages?name=go2rtc)
|
- [Alpine Linux](https://pkgs.alpinelinux.org/packages?name=go2rtc)
|
||||||
|
- [Gentoo](https://github.com/inode64/inode64-overlay/tree/main/media-video/go2rtc)
|
||||||
- [NixOS](https://search.nixos.org/packages?query=go2rtc)
|
- [NixOS](https://search.nixos.org/packages?query=go2rtc)
|
||||||
- [Proxmox Helper Scripts](https://tteck.github.io/Proxmox/)
|
- [Proxmox Helper Scripts](https://tteck.github.io/Proxmox/)
|
||||||
|
- [QNAP](https://www.myqnap.org/product/go2rtc/)
|
||||||
|
- [Synology NAS](https://synocommunity.com/package/go2rtc)
|
||||||
- [Unraid](https://unraid.net/community/apps?q=go2rtc)
|
- [Unraid](https://unraid.net/community/apps?q=go2rtc)
|
||||||
|
|
||||||
## Cameras experience
|
## Cameras experience
|
||||||
|
|||||||
+8
-1
@@ -115,7 +115,14 @@ paths:
|
|||||||
default:
|
default:
|
||||||
description: Default response
|
description: Default response
|
||||||
|
|
||||||
|
/api/restart:
|
||||||
|
post:
|
||||||
|
summary: Restart Daemon
|
||||||
|
description: Restarts the daemon.
|
||||||
|
tags: [ Application ]
|
||||||
|
responses:
|
||||||
|
default:
|
||||||
|
description: Default response
|
||||||
|
|
||||||
/api/config:
|
/api/config:
|
||||||
get:
|
get:
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 202 KiB After Width: | Height: | Size: 154 KiB |
@@ -3,42 +3,43 @@ module github.com/AlexxIT/go2rtc
|
|||||||
go 1.21
|
go 1.21
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/gorilla/websocket v1.5.0
|
github.com/antonmedv/expr v1.15.3
|
||||||
github.com/miekg/dns v1.1.55
|
github.com/gorilla/websocket v1.5.1
|
||||||
|
github.com/miekg/dns v1.1.57
|
||||||
github.com/pion/ice/v2 v2.3.11
|
github.com/pion/ice/v2 v2.3.11
|
||||||
github.com/pion/interceptor v0.1.19
|
github.com/pion/interceptor v0.1.25
|
||||||
github.com/pion/rtcp v1.2.10
|
github.com/pion/rtcp v1.2.12
|
||||||
github.com/pion/rtp v1.8.1
|
github.com/pion/rtp v1.8.3
|
||||||
github.com/pion/sdp/v3 v3.0.6
|
github.com/pion/sdp/v3 v3.0.6
|
||||||
github.com/pion/srtp/v2 v2.0.17
|
github.com/pion/srtp/v2 v2.0.18
|
||||||
github.com/pion/stun v0.6.1
|
github.com/pion/stun v0.6.1
|
||||||
github.com/pion/webrtc/v3 v3.2.19
|
github.com/pion/webrtc/v3 v3.2.22
|
||||||
github.com/rs/zerolog v1.30.0
|
github.com/rs/zerolog v1.31.0
|
||||||
github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3
|
github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3
|
||||||
github.com/sigurn/crc8 v0.0.0-20220107193325-2243fe600f9f
|
github.com/sigurn/crc8 v0.0.0-20220107193325-2243fe600f9f
|
||||||
github.com/stretchr/testify v1.8.4
|
github.com/stretchr/testify v1.8.4
|
||||||
github.com/tadglines/go-pkgs v0.0.0-20210623144937-b983b20f54f9
|
github.com/tadglines/go-pkgs v0.0.0-20210623144937-b983b20f54f9
|
||||||
golang.org/x/crypto v0.13.0
|
golang.org/x/crypto v0.15.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/google/uuid v1.3.1 // indirect
|
github.com/google/uuid v1.4.0 // indirect
|
||||||
github.com/kr/pretty v0.2.1 // indirect
|
github.com/kr/pretty v0.2.1 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/pion/datachannel v1.5.5 // indirect
|
github.com/pion/datachannel v1.5.5 // indirect
|
||||||
github.com/pion/dtls/v2 v2.2.7 // indirect
|
github.com/pion/dtls/v2 v2.2.8 // indirect
|
||||||
github.com/pion/logging v0.2.2 // indirect
|
github.com/pion/logging v0.2.2 // indirect
|
||||||
github.com/pion/mdns v0.0.9 // indirect
|
github.com/pion/mdns v0.0.9 // indirect
|
||||||
github.com/pion/randutil v0.1.0 // indirect
|
github.com/pion/randutil v0.1.0 // indirect
|
||||||
github.com/pion/sctp v1.8.9 // indirect
|
github.com/pion/sctp v1.8.9 // indirect
|
||||||
github.com/pion/transport/v2 v2.2.4 // indirect
|
github.com/pion/transport/v2 v2.2.4 // indirect
|
||||||
github.com/pion/turn/v2 v2.1.3 // indirect
|
github.com/pion/turn/v2 v2.1.4 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
golang.org/x/mod v0.12.0 // indirect
|
golang.org/x/mod v0.14.0 // indirect
|
||||||
golang.org/x/net v0.15.0 // indirect
|
golang.org/x/net v0.18.0 // indirect
|
||||||
golang.org/x/sys v0.12.0 // indirect
|
golang.org/x/sys v0.14.0 // indirect
|
||||||
golang.org/x/tools v0.13.0 // indirect
|
golang.org/x/tools v0.15.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
github.com/antonmedv/expr v1.15.3 h1:q3hOJZNvLvhqE8OHBs1cFRdbXFNKuA+bHmRaI+AmRmI=
|
||||||
|
github.com/antonmedv/expr v1.15.3/go.mod h1:0E/6TxnOlRNp81GMzX9QfDPAmHo2Phg00y4JUv1ihsE=
|
||||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
@@ -21,8 +23,12 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
|||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
|
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
|
||||||
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
|
||||||
|
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
|
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
|
||||||
|
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
|
||||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
||||||
@@ -30,15 +36,17 @@ github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfn
|
|||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
|
||||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
|
||||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/miekg/dns v1.1.56 h1:5imZaSeoRNvpM9SzWNhEcP9QliKiz20/dA2QabIGVnE=
|
||||||
|
github.com/miekg/dns v1.1.56/go.mod h1:cRm6Oo2C8TY9ZS/TqsSrseAcncm74lfK5G+ikN2SWWY=
|
||||||
|
github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM=
|
||||||
|
github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk=
|
||||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
@@ -52,11 +60,15 @@ github.com/pion/datachannel v1.5.5 h1:10ef4kwdjije+M9d7Xm9im2Y3O6A6ccQb0zcqZcJew
|
|||||||
github.com/pion/datachannel v1.5.5/go.mod h1:iMz+lECmfdCMqFRhXhcA/219B0SQlbpoR2V118yimL0=
|
github.com/pion/datachannel v1.5.5/go.mod h1:iMz+lECmfdCMqFRhXhcA/219B0SQlbpoR2V118yimL0=
|
||||||
github.com/pion/dtls/v2 v2.2.7 h1:cSUBsETxepsCSFSxC3mc/aDo14qQLMSL+O6IjG28yV8=
|
github.com/pion/dtls/v2 v2.2.7 h1:cSUBsETxepsCSFSxC3mc/aDo14qQLMSL+O6IjG28yV8=
|
||||||
github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s=
|
github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s=
|
||||||
|
github.com/pion/dtls/v2 v2.2.8 h1:BUroldfiIbV9jSnC6cKOMnyiORRWrWWpV11JUyEu5OA=
|
||||||
|
github.com/pion/dtls/v2 v2.2.8/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s=
|
||||||
github.com/pion/ice/v2 v2.3.11 h1:rZjVmUwyT55cmN8ySMpL7rsS8KYsJERsrxJLLxpKhdw=
|
github.com/pion/ice/v2 v2.3.11 h1:rZjVmUwyT55cmN8ySMpL7rsS8KYsJERsrxJLLxpKhdw=
|
||||||
github.com/pion/ice/v2 v2.3.11/go.mod h1:hPcLC3kxMa+JGRzMHqQzjoSj3xtE9F+eoncmXLlCL4E=
|
github.com/pion/ice/v2 v2.3.11/go.mod h1:hPcLC3kxMa+JGRzMHqQzjoSj3xtE9F+eoncmXLlCL4E=
|
||||||
github.com/pion/interceptor v0.1.18/go.mod h1:tpvvF4cPM6NGxFA1DUMbhabzQBxdWMATDGEUYOR9x6I=
|
github.com/pion/interceptor v0.1.18/go.mod h1:tpvvF4cPM6NGxFA1DUMbhabzQBxdWMATDGEUYOR9x6I=
|
||||||
github.com/pion/interceptor v0.1.19 h1:tq0TGBzuZQqipyBhaC1mVUCfCh8XjDKUuibq9rIl5t4=
|
github.com/pion/interceptor v0.1.22 h1:khhimAF0/VmGaIfeE+bA3X1jm0lD8C8HOGcU7vpWcPA=
|
||||||
github.com/pion/interceptor v0.1.19/go.mod h1:VANhFxdJezB8mwToMMmrmyHyP9gym6xLqIUch31xryg=
|
github.com/pion/interceptor v0.1.22/go.mod h1:wkbPYAak5zKsfpVDYMtEfWEy8D4zL+rpxCxPImLOg3Y=
|
||||||
|
github.com/pion/interceptor v0.1.25 h1:pwY9r7P6ToQ3+IF0bajN0xmk/fNw/suTgaTdlwTDmhc=
|
||||||
|
github.com/pion/interceptor v0.1.25/go.mod h1:wkbPYAak5zKsfpVDYMtEfWEy8D4zL+rpxCxPImLOg3Y=
|
||||||
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
|
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
|
||||||
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
|
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
|
||||||
github.com/pion/mdns v0.0.8/go.mod h1:hYE72WX8WDveIhg7fmXgMKivD3Puklk0Ymzog0lSyaI=
|
github.com/pion/mdns v0.0.8/go.mod h1:hYE72WX8WDveIhg7fmXgMKivD3Puklk0Ymzog0lSyaI=
|
||||||
@@ -66,8 +78,13 @@ github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
|
|||||||
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
|
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
|
||||||
github.com/pion/rtcp v1.2.10 h1:nkr3uj+8Sp97zyItdN60tE/S6vk4al5CPRR6Gejsdjc=
|
github.com/pion/rtcp v1.2.10 h1:nkr3uj+8Sp97zyItdN60tE/S6vk4al5CPRR6Gejsdjc=
|
||||||
github.com/pion/rtcp v1.2.10/go.mod h1:ztfEwXZNLGyF1oQDttz/ZKIBaeeg/oWbRYqzBM9TL1I=
|
github.com/pion/rtcp v1.2.10/go.mod h1:ztfEwXZNLGyF1oQDttz/ZKIBaeeg/oWbRYqzBM9TL1I=
|
||||||
github.com/pion/rtp v1.8.1 h1:26OxTc6lKg/qLSGir5agLyj0QKaOv8OP5wps2SFnVNQ=
|
github.com/pion/rtcp v1.2.12 h1:bKWiX93XKgDZENEXCijvHRU/wRifm6JV5DGcH6twtSM=
|
||||||
|
github.com/pion/rtcp v1.2.12/go.mod h1:sn6qjxvnwyAkkPzPULIbVqSKI5Dv54Rv7VG0kNxh9L4=
|
||||||
github.com/pion/rtp v1.8.1/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU=
|
github.com/pion/rtp v1.8.1/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU=
|
||||||
|
github.com/pion/rtp v1.8.2 h1:oKMM0K1/QYQ5b5qH+ikqDSZRipP5mIxPJcgcvw5sH0w=
|
||||||
|
github.com/pion/rtp v1.8.2/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU=
|
||||||
|
github.com/pion/rtp v1.8.3 h1:VEHxqzSVQxCkKDSHro5/4IUUG1ea+MFdqR2R3xSpNU8=
|
||||||
|
github.com/pion/rtp v1.8.3/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU=
|
||||||
github.com/pion/sctp v1.8.5/go.mod h1:SUFFfDpViyKejTAdwD1d/HQsCu+V/40cCs2nZIvC3s0=
|
github.com/pion/sctp v1.8.5/go.mod h1:SUFFfDpViyKejTAdwD1d/HQsCu+V/40cCs2nZIvC3s0=
|
||||||
github.com/pion/sctp v1.8.8/go.mod h1:igF9nZBrjh5AtmKc7U30jXltsFHicFCXSmWA2GWRaWs=
|
github.com/pion/sctp v1.8.8/go.mod h1:igF9nZBrjh5AtmKc7U30jXltsFHicFCXSmWA2GWRaWs=
|
||||||
github.com/pion/sctp v1.8.9 h1:TP5ZVxV5J7rz7uZmbyvnUvsn7EJ2x/5q9uhsTtXbI3g=
|
github.com/pion/sctp v1.8.9 h1:TP5ZVxV5J7rz7uZmbyvnUvsn7EJ2x/5q9uhsTtXbI3g=
|
||||||
@@ -76,6 +93,8 @@ github.com/pion/sdp/v3 v3.0.6 h1:WuDLhtuFUUVpTfus9ILC4HRyHsW6TdugjEX/QY9OiUw=
|
|||||||
github.com/pion/sdp/v3 v3.0.6/go.mod h1:iiFWFpQO8Fy3S5ldclBkpXqmWy02ns78NOKoLLL0YQw=
|
github.com/pion/sdp/v3 v3.0.6/go.mod h1:iiFWFpQO8Fy3S5ldclBkpXqmWy02ns78NOKoLLL0YQw=
|
||||||
github.com/pion/srtp/v2 v2.0.17 h1:ECuOk+7uIpY6HUlTb0nXhfvu4REG2hjtC4ronYFCZE4=
|
github.com/pion/srtp/v2 v2.0.17 h1:ECuOk+7uIpY6HUlTb0nXhfvu4REG2hjtC4ronYFCZE4=
|
||||||
github.com/pion/srtp/v2 v2.0.17/go.mod h1:y5WSHcJY4YfNB/5r7ca5YjHeIr1H3LM1rKArGGs8jMc=
|
github.com/pion/srtp/v2 v2.0.17/go.mod h1:y5WSHcJY4YfNB/5r7ca5YjHeIr1H3LM1rKArGGs8jMc=
|
||||||
|
github.com/pion/srtp/v2 v2.0.18 h1:vKpAXfawO9RtTRKZJbG4y0v1b11NZxQnxRl85kGuUlo=
|
||||||
|
github.com/pion/srtp/v2 v2.0.18/go.mod h1:0KJQjA99A6/a0DOVTu1PhDSw0CXF2jTkqOoMg3ODqdA=
|
||||||
github.com/pion/stun v0.6.1 h1:8lp6YejULeHBF8NmV8e2787BogQhduZugh5PdhDyyN4=
|
github.com/pion/stun v0.6.1 h1:8lp6YejULeHBF8NmV8e2787BogQhduZugh5PdhDyyN4=
|
||||||
github.com/pion/stun v0.6.1/go.mod h1:/hO7APkX4hZKu/D0f2lHzNyvdkTGtIy3NDmLR7kSz/8=
|
github.com/pion/stun v0.6.1/go.mod h1:/hO7APkX4hZKu/D0f2lHzNyvdkTGtIy3NDmLR7kSz/8=
|
||||||
github.com/pion/transport v0.14.1 h1:XSM6olwW+o8J4SCmOBb/BpwZypkHeyM0PGFCxNQBr40=
|
github.com/pion/transport v0.14.1 h1:XSM6olwW+o8J4SCmOBb/BpwZypkHeyM0PGFCxNQBr40=
|
||||||
@@ -87,16 +106,19 @@ github.com/pion/transport/v2 v2.2.4 h1:41JJK6DZQYSeVLxILA2+F4ZkKb4Xd/tFJZRFZQ9QA
|
|||||||
github.com/pion/transport/v2 v2.2.4/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0=
|
github.com/pion/transport/v2 v2.2.4/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0=
|
||||||
github.com/pion/transport/v3 v3.0.1 h1:gDTlPJwROfSfz6QfSi0ZmeCSkFcnWWiiR9ES0ouANiM=
|
github.com/pion/transport/v3 v3.0.1 h1:gDTlPJwROfSfz6QfSi0ZmeCSkFcnWWiiR9ES0ouANiM=
|
||||||
github.com/pion/transport/v3 v3.0.1/go.mod h1:UY7kiITrlMv7/IKgd5eTUcaahZx5oUN3l9SzK5f5xE0=
|
github.com/pion/transport/v3 v3.0.1/go.mod h1:UY7kiITrlMv7/IKgd5eTUcaahZx5oUN3l9SzK5f5xE0=
|
||||||
github.com/pion/turn/v2 v2.1.3 h1:pYxTVWG2gpC97opdRc5IGsQ1lJ9O/IlNhkzj7MMrGAA=
|
|
||||||
github.com/pion/turn/v2 v2.1.3/go.mod h1:huEpByKKHix2/b9kmTAM3YoX6MKP+/D//0ClgUYR2fY=
|
github.com/pion/turn/v2 v2.1.3/go.mod h1:huEpByKKHix2/b9kmTAM3YoX6MKP+/D//0ClgUYR2fY=
|
||||||
github.com/pion/webrtc/v3 v3.2.19 h1:XNu5e62mkzafw1qYuKtQ+Dviw4JpbzC/SLx3zZt49JY=
|
github.com/pion/turn/v2 v2.1.4 h1:2xn8rduI5W6sCZQkEnIUDAkrBQNl2eYIBCHMZ3QMmP8=
|
||||||
github.com/pion/webrtc/v3 v3.2.19/go.mod h1:vVURQTBOG5BpWKOJz3nlr23NfTDeyKVmubRNqzQp+Tg=
|
github.com/pion/turn/v2 v2.1.4/go.mod h1:huEpByKKHix2/b9kmTAM3YoX6MKP+/D//0ClgUYR2fY=
|
||||||
|
github.com/pion/webrtc/v3 v3.2.21 h1:c8fy5JcqJkAQBwwy3Sk9huQLTBUSqaggyRlv9Lnh2zY=
|
||||||
|
github.com/pion/webrtc/v3 v3.2.21/go.mod h1:vVURQTBOG5BpWKOJz3nlr23NfTDeyKVmubRNqzQp+Tg=
|
||||||
|
github.com/pion/webrtc/v3 v3.2.22 h1:Hno262T7+V56MgUO30O0ZirZmVSvbXtnau31SB0WSpc=
|
||||||
|
github.com/pion/webrtc/v3 v3.2.22/go.mod h1:1CaT2fcZzZ6VZA+O1i9yK2DU4EOcXVvSbWG9pr5jefs=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||||
github.com/rs/zerolog v1.30.0 h1:SymVODrcRsaRaSInD9yQtKbtWqwsfoPcRff/oRXLj4c=
|
github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A=
|
||||||
github.com/rs/zerolog v1.30.0/go.mod h1:/tk+P47gFdPXq4QYjvCmT5/Gsug2nagsFWBWhAiSi1w=
|
github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
|
||||||
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
|
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
|
||||||
github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3 h1:aQKxg3+2p+IFXXg97McgDGT5zcMrQoi0EICZs8Pgchs=
|
github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3 h1:aQKxg3+2p+IFXXg97McgDGT5zcMrQoi0EICZs8Pgchs=
|
||||||
github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3/go.mod h1:9/etS5gpQq9BJsJMWg1wpLbfuSnkm8dPF6FdW2JXVhA=
|
github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3/go.mod h1:9/etS5gpQq9BJsJMWg1wpLbfuSnkm8dPF6FdW2JXVhA=
|
||||||
@@ -124,13 +146,18 @@ golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE
|
|||||||
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
|
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
|
||||||
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
|
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
|
||||||
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
||||||
golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
|
|
||||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||||
|
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
|
||||||
|
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||||
|
golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=
|
||||||
|
golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
|
golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY=
|
||||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
|
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
|
||||||
|
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
@@ -146,15 +173,19 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
|||||||
golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
|
golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
|
||||||
golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
|
golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
|
||||||
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
|
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
|
||||||
golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
|
|
||||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||||
|
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
||||||
|
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||||
|
golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg=
|
||||||
|
golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=
|
||||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||||
|
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
|
||||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@@ -167,8 +198,6 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
@@ -181,8 +210,11 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|||||||
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
|
|
||||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
||||||
|
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
|
||||||
|
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
@@ -209,8 +241,10 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
|
|||||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ=
|
golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc=
|
||||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
|
||||||
|
golang.org/x/tools v0.15.0 h1:zdAyfUGbYmuVokhzVmghFl2ZJh5QhcfebBgmVPFYA+8=
|
||||||
|
golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
|||||||
+73
-67
@@ -10,6 +10,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
"github.com/AlexxIT/go2rtc/internal/app"
|
"github.com/AlexxIT/go2rtc/internal/app"
|
||||||
"github.com/AlexxIT/go2rtc/pkg/shell"
|
"github.com/AlexxIT/go2rtc/pkg/shell"
|
||||||
@@ -19,25 +20,26 @@ import (
|
|||||||
func Init() {
|
func Init() {
|
||||||
var cfg struct {
|
var cfg struct {
|
||||||
Mod struct {
|
Mod struct {
|
||||||
Listen string `yaml:"listen"`
|
Listen string `yaml:"listen"`
|
||||||
Username string `yaml:"username"`
|
Username string `yaml:"username"`
|
||||||
Password string `yaml:"password"`
|
Password string `yaml:"password"`
|
||||||
BasePath string `yaml:"base_path"`
|
BasePath string `yaml:"base_path"`
|
||||||
StaticDir string `yaml:"static_dir"`
|
StaticDir string `yaml:"static_dir"`
|
||||||
Origin string `yaml:"origin"`
|
Origin string `yaml:"origin"`
|
||||||
TLSListen string `yaml:"tls_listen"`
|
TLSListen string `yaml:"tls_listen"`
|
||||||
TLSCert string `yaml:"tls_cert"`
|
TLSCert string `yaml:"tls_cert"`
|
||||||
TLSKey string `yaml:"tls_key"`
|
TLSKey string `yaml:"tls_key"`
|
||||||
|
UnixListen string `yaml:"unix_listen"`
|
||||||
} `yaml:"api"`
|
} `yaml:"api"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// default config
|
// default config
|
||||||
cfg.Mod.Listen = "0.0.0.0:1984"
|
cfg.Mod.Listen = ":1984"
|
||||||
|
|
||||||
// load config from YAML
|
// load config from YAML
|
||||||
app.LoadConfig(&cfg)
|
app.LoadConfig(&cfg)
|
||||||
|
|
||||||
if cfg.Mod.Listen == "" {
|
if cfg.Mod.Listen == "" && cfg.Mod.UnixListen == "" && cfg.Mod.TLSListen == "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,16 +53,6 @@ func Init() {
|
|||||||
HandleFunc("api/exit", exitHandler)
|
HandleFunc("api/exit", exitHandler)
|
||||||
HandleFunc("api/restart", restartHandler)
|
HandleFunc("api/restart", restartHandler)
|
||||||
|
|
||||||
// ensure we can listen without errors
|
|
||||||
var err error
|
|
||||||
ln, err = net.Listen("tcp", cfg.Mod.Listen)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal().Err(err).Msg("[api] listen")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info().Str("addr", cfg.Mod.Listen).Msg("[api] listen")
|
|
||||||
|
|
||||||
Handler = http.DefaultServeMux // 4th
|
Handler = http.DefaultServeMux // 4th
|
||||||
|
|
||||||
if cfg.Mod.Origin == "*" {
|
if cfg.Mod.Origin == "*" {
|
||||||
@@ -75,59 +67,74 @@ func Init() {
|
|||||||
Handler = middlewareLog(Handler) // 1st
|
Handler = middlewareLog(Handler) // 1st
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
if cfg.Mod.Listen != "" {
|
||||||
s := http.Server{}
|
go listen("tcp", cfg.Mod.Listen)
|
||||||
s.Handler = Handler
|
}
|
||||||
if err = s.Serve(ln); err != nil {
|
|
||||||
log.Fatal().Err(err).Msg("[api] serve")
|
if cfg.Mod.UnixListen != "" {
|
||||||
}
|
_ = syscall.Unlink(cfg.Mod.UnixListen)
|
||||||
}()
|
go listen("unix", cfg.Mod.UnixListen)
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize the HTTPS server
|
// Initialize the HTTPS server
|
||||||
if cfg.Mod.TLSListen != "" && cfg.Mod.TLSCert != "" && cfg.Mod.TLSKey != "" {
|
if cfg.Mod.TLSListen != "" && cfg.Mod.TLSCert != "" && cfg.Mod.TLSKey != "" {
|
||||||
var cert tls.Certificate
|
go tlsListen("tcp", cfg.Mod.TLSListen, cfg.Mod.TLSCert, cfg.Mod.TLSKey)
|
||||||
if strings.IndexByte(cfg.Mod.TLSCert, '\n') < 0 && strings.IndexByte(cfg.Mod.TLSKey, '\n') < 0 {
|
|
||||||
// check if file path
|
|
||||||
cert, err = tls.LoadX509KeyPair(cfg.Mod.TLSCert, cfg.Mod.TLSKey)
|
|
||||||
} else {
|
|
||||||
// if text file content
|
|
||||||
cert, err = tls.X509KeyPair([]byte(cfg.Mod.TLSCert), []byte(cfg.Mod.TLSKey))
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Caller().Send()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
tlsListener, err := net.Listen("tcp", cfg.Mod.TLSListen)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal().Err(err).Caller().Send()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info().Str("addr", cfg.Mod.TLSListen).Msg("[api] tls listen")
|
|
||||||
|
|
||||||
tlsServer := &http.Server{
|
|
||||||
Handler: Handler,
|
|
||||||
TLSConfig: &tls.Config{
|
|
||||||
Certificates: []tls.Certificate{cert},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
if err := tlsServer.ServeTLS(tlsListener, "", ""); err != nil {
|
|
||||||
log.Fatal().Err(err).Msg("[api] tls serve")
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Port() int {
|
func listen(network, address string) {
|
||||||
if ln == nil {
|
ln, err := net.Listen(network, address)
|
||||||
return 0
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("[api] listen")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info().Str("addr", address).Msg("[api] listen")
|
||||||
|
|
||||||
|
if network == "tcp" {
|
||||||
|
Port = ln.Addr().(*net.TCPAddr).Port
|
||||||
|
}
|
||||||
|
|
||||||
|
server := http.Server{Handler: Handler}
|
||||||
|
if err = server.Serve(ln); err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("[api] serve")
|
||||||
}
|
}
|
||||||
return ln.Addr().(*net.TCPAddr).Port
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func tlsListen(network, address, certFile, keyFile string) {
|
||||||
|
var cert tls.Certificate
|
||||||
|
var err error
|
||||||
|
if strings.IndexByte(certFile, '\n') < 0 && strings.IndexByte(keyFile, '\n') < 0 {
|
||||||
|
// check if file path
|
||||||
|
cert, err = tls.LoadX509KeyPair(certFile, keyFile)
|
||||||
|
} else {
|
||||||
|
// if text file content
|
||||||
|
cert, err = tls.X509KeyPair([]byte(certFile), []byte(keyFile))
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Caller().Send()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ln, err := net.Listen(network, address)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("[api] tls listen")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info().Str("addr", address).Msg("[api] tls listen")
|
||||||
|
|
||||||
|
server := &http.Server{
|
||||||
|
Handler: Handler,
|
||||||
|
TLSConfig: &tls.Config{Certificates: []tls.Certificate{cert}},
|
||||||
|
}
|
||||||
|
if err = server.ServeTLS(ln, "", ""); err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("[api] tls serve")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var Port int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
MimeJSON = "application/json"
|
MimeJSON = "application/json"
|
||||||
MimeText = "text/plain"
|
MimeText = "text/plain"
|
||||||
@@ -187,7 +194,7 @@ func middlewareLog(next http.Handler) http.Handler {
|
|||||||
|
|
||||||
func middlewareAuth(username, password string, next http.Handler) http.Handler {
|
func middlewareAuth(username, password string, next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if !strings.HasPrefix(r.RemoteAddr, "127.") && !strings.HasPrefix(r.RemoteAddr, "[::1]") {
|
if !strings.HasPrefix(r.RemoteAddr, "127.") && !strings.HasPrefix(r.RemoteAddr, "[::1]") && r.RemoteAddr != "@" {
|
||||||
user, pass, ok := r.BasicAuth()
|
user, pass, ok := r.BasicAuth()
|
||||||
if !ok || user != username || pass != password {
|
if !ok || user != username || pass != password {
|
||||||
w.Header().Set("Www-Authenticate", `Basic realm="go2rtc"`)
|
w.Header().Set("Www-Authenticate", `Basic realm="go2rtc"`)
|
||||||
@@ -209,7 +216,6 @@ func middlewareCORS(next http.Handler) http.Handler {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
var ln net.Listener
|
|
||||||
var mu sync.Mutex
|
var mu sync.Mutex
|
||||||
|
|
||||||
func apiHandler(w http.ResponseWriter, r *http.Request) {
|
func apiHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|||||||
+1
-1
@@ -17,7 +17,7 @@ import (
|
|||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
var Version = ""
|
var Version = "1.8.4"
|
||||||
var UserAgent = "go2rtc/" + Version
|
var UserAgent = "go2rtc/" + Version
|
||||||
|
|
||||||
var ConfigPath string
|
var ConfigPath string
|
||||||
|
|||||||
@@ -0,0 +1,91 @@
|
|||||||
|
# Expr
|
||||||
|
|
||||||
|
[Expr](https://github.com/antonmedv/expr) - expression language and expression evaluation for Go.
|
||||||
|
|
||||||
|
- [language definition](https://expr.medv.io/docs/Language-Definition) - takes best from JS, Python, Jinja2 syntax
|
||||||
|
- your expression should return a link of any supported source
|
||||||
|
- expression supports multiple operation, but:
|
||||||
|
- all operations must be separated by a semicolon
|
||||||
|
- all operations, except the last one, must declare a new variable (`let s = "abc";`)
|
||||||
|
- the last operation should return a string
|
||||||
|
- go2rtc supports additional functions:
|
||||||
|
- `fetch` - JS-like HTTP requests
|
||||||
|
- `match` - JS-like RegExp queries
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
**Two way audio for Dahua VTO**
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
streams:
|
||||||
|
dahua_vto: |
|
||||||
|
expr: let host = "admin:password@192.168.1.123";
|
||||||
|
fetch("http://"+host+"/cgi-bin/configManager.cgi?action=setConfig&Encode[0].MainFormat[0].Audio.Compression=G.711A&Encode[0].MainFormat[0].Audio.Frequency=8000").ok
|
||||||
|
? "rtsp://"+host+"/cam/realmonitor?channel=1&subtype=0&unicast=true&proto=Onvif" : ""
|
||||||
|
```
|
||||||
|
|
||||||
|
**dom.ru**
|
||||||
|
|
||||||
|
You can get credentials via:
|
||||||
|
|
||||||
|
- https://github.com/alexmorbo/domru (file `/share/domru/accounts`)
|
||||||
|
- https://github.com/ad/domru
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
streams:
|
||||||
|
dom_ru: |
|
||||||
|
expr: let camera = "99999999"; let token = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; let operator = 99;
|
||||||
|
fetch("https://myhome.novotelecom.ru/rest/v1/forpost/cameras/"+camera+"/video", {
|
||||||
|
headers: {Authorization: "Bearer "+token, Operator: operator}
|
||||||
|
}).json().data.URL
|
||||||
|
```
|
||||||
|
|
||||||
|
**Parse HLS files from Apple**
|
||||||
|
|
||||||
|
Same example in two languages - python and expr.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
streams:
|
||||||
|
example_python: |
|
||||||
|
echo:python -c 'from urllib.request import urlopen; import re
|
||||||
|
|
||||||
|
# url1 = "https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_16x9/bipbop_16x9_variant.m3u8"
|
||||||
|
html1 = urlopen("https://developer.apple.com/streaming/examples/basic-stream-osx-ios5.html").read().decode("utf-8")
|
||||||
|
url1 = re.search(r"https.+?m3u8", html1)[0]
|
||||||
|
|
||||||
|
# url2 = "gear1/prog_index.m3u8"
|
||||||
|
html2 = urlopen(url1).read().decode("utf-8")
|
||||||
|
url2 = re.search(r"^[a-z0-1/_]+\.m3u8$", html2, flags=re.MULTILINE)[0]
|
||||||
|
|
||||||
|
# url3 = "https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_16x9/gear1/prog_index.m3u8"
|
||||||
|
url3 = url1[:url1.rindex("/")+1] + url2
|
||||||
|
|
||||||
|
print("ffmpeg:" + url3 + "#video=copy")'
|
||||||
|
|
||||||
|
example_expr: |
|
||||||
|
expr:
|
||||||
|
|
||||||
|
let html1 = fetch("https://developer.apple.com/streaming/examples/basic-stream-osx-ios5.html").text;
|
||||||
|
let url1 = match(html1, "https.+?m3u8")[0];
|
||||||
|
|
||||||
|
let html2 = fetch(url1).text;
|
||||||
|
let url2 = match(html2, "^[a-z0-1/_]+\\.m3u8$", "m")[0];
|
||||||
|
|
||||||
|
let url3 = url1[:lastIndexOf(url1, "/")+1] + url2;
|
||||||
|
|
||||||
|
"ffmpeg:" + url3 + "#video=copy"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Comparsion
|
||||||
|
|
||||||
|
| expr | python | js |
|
||||||
|
|------------------------------|----------------------------|--------------------------------|
|
||||||
|
| let x = 1; | x = 1 | let x = 1 |
|
||||||
|
| {a: 1, b: 2} | {"a": 1, "b": 2} | {a: 1, b: 2} |
|
||||||
|
| let r = fetch(url, {method}) | r = request(method, url) | r = await fetch(url, {method}) |
|
||||||
|
| r.ok | r.ok | r.ok |
|
||||||
|
| r.status | r.status_code | r.status |
|
||||||
|
| r.text | r.text | await r.text() |
|
||||||
|
| r.json() | r.json() | await r.json() |
|
||||||
|
| r.headers | r.headers | r.headers |
|
||||||
|
| let m = match(text, "abc") | m = re.search("abc", text) | let m = text.match(/abc/) |
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/AlexxIT/go2rtc/internal/app"
|
||||||
|
"github.com/AlexxIT/go2rtc/internal/streams"
|
||||||
|
"github.com/AlexxIT/go2rtc/pkg/expr"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Init() {
|
||||||
|
log := app.GetLogger("expr")
|
||||||
|
|
||||||
|
streams.RedirectFunc("expr", func(url string) (string, error) {
|
||||||
|
v, err := expr.Run(url[5:])
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debug().Msgf("[expr] url=%s", url)
|
||||||
|
|
||||||
|
if url = v.(string); url == "" {
|
||||||
|
return "", errors.New("expr: result is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
return url, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -64,18 +64,22 @@ var defaults = map[string]string{
|
|||||||
// `-async 1` or `-min_comp 0` - force frame_size=960, important for WebRTC audio quality
|
// `-async 1` or `-min_comp 0` - force frame_size=960, important for WebRTC audio quality
|
||||||
"opus": "-c:a libopus -application:a lowdelay -frame_duration 20 -min_comp 0",
|
"opus": "-c:a libopus -application:a lowdelay -frame_duration 20 -min_comp 0",
|
||||||
"pcmu": "-c:a pcm_mulaw -ar:a 8000 -ac:a 1",
|
"pcmu": "-c:a pcm_mulaw -ar:a 8000 -ac:a 1",
|
||||||
|
"pcmu/8000": "-c:a pcm_mulaw -ar:a 8000 -ac:a 1",
|
||||||
"pcmu/16000": "-c:a pcm_mulaw -ar:a 16000 -ac:a 1",
|
"pcmu/16000": "-c:a pcm_mulaw -ar:a 16000 -ac:a 1",
|
||||||
"pcmu/48000": "-c:a pcm_mulaw -ar:a 48000 -ac:a 1",
|
"pcmu/48000": "-c:a pcm_mulaw -ar:a 48000 -ac:a 1",
|
||||||
"pcma": "-c:a pcm_alaw -ar:a 8000 -ac:a 1",
|
"pcma": "-c:a pcm_alaw -ar:a 8000 -ac:a 1",
|
||||||
|
"pcma/8000": "-c:a pcm_alaw -ar:a 8000 -ac:a 1",
|
||||||
"pcma/16000": "-c:a pcm_alaw -ar:a 16000 -ac:a 1",
|
"pcma/16000": "-c:a pcm_alaw -ar:a 16000 -ac:a 1",
|
||||||
"pcma/48000": "-c:a pcm_alaw -ar:a 48000 -ac:a 1",
|
"pcma/48000": "-c:a pcm_alaw -ar:a 48000 -ac:a 1",
|
||||||
"aac": "-c:a aac", // keep sample rate and channels
|
"aac": "-c:a aac", // keep sample rate and channels
|
||||||
"aac/16000": "-c:a aac -ar:a 16000 -ac:a 1",
|
"aac/16000": "-c:a aac -ar:a 16000 -ac:a 1",
|
||||||
"mp3": "-c:a libmp3lame -q:a 8",
|
"mp3": "-c:a libmp3lame -q:a 8",
|
||||||
"pcm": "-c:a pcm_s16be -ar:a 8000 -ac:a 1",
|
"pcm": "-c:a pcm_s16be -ar:a 8000 -ac:a 1",
|
||||||
|
"pcm/8000": "-c:a pcm_s16be -ar:a 8000 -ac:a 1",
|
||||||
"pcm/16000": "-c:a pcm_s16be -ar:a 16000 -ac:a 1",
|
"pcm/16000": "-c:a pcm_s16be -ar:a 16000 -ac:a 1",
|
||||||
"pcm/48000": "-c:a pcm_s16be -ar:a 48000 -ac:a 1",
|
"pcm/48000": "-c:a pcm_s16be -ar:a 48000 -ac:a 1",
|
||||||
"pcml": "-c:a pcm_s16le -ar:a 8000 -ac:a 1",
|
"pcml": "-c:a pcm_s16le -ar:a 8000 -ac:a 1",
|
||||||
|
"pcml/8000": "-c:a pcm_s16le -ar:a 8000 -ac:a 1",
|
||||||
"pcml/44100": "-c:a pcm_s16le -ar:a 44100 -ac:a 1",
|
"pcml/44100": "-c:a pcm_s16le -ar:a 44100 -ac:a 1",
|
||||||
|
|
||||||
// hardware Intel and AMD on Linux
|
// hardware Intel and AMD on Linux
|
||||||
|
|||||||
@@ -213,3 +213,14 @@ func TestDeckLink(t *testing.T) {
|
|||||||
args := parseArgs(`DeckLink SDI (2)#video=h264#hardware=vaapi#input=-format_code Hp29 -f decklink -i "{input}"`)
|
args := parseArgs(`DeckLink SDI (2)#video=h264#hardware=vaapi#input=-format_code Hp29 -f decklink -i "{input}"`)
|
||||||
require.Equal(t, `ffmpeg -hide_banner -hwaccel vaapi -hwaccel_output_format vaapi -hwaccel_flags allow_profile_mismatch -format_code Hp29 -f decklink -i "DeckLink SDI (2)" -c:v h264_vaapi -g 50 -bf 0 -profile:v high -level:v 4.1 -sei:v 0 -an -vf "format=vaapi|nv12,hwupload,scale_vaapi=out_range=tv" -user_agent ffmpeg/go2rtc -rtsp_transport tcp -f rtsp {output}`, args.String())
|
require.Equal(t, `ffmpeg -hide_banner -hwaccel vaapi -hwaccel_output_format vaapi -hwaccel_flags allow_profile_mismatch -format_code Hp29 -f decklink -i "DeckLink SDI (2)" -c:v h264_vaapi -g 50 -bf 0 -profile:v high -level:v 4.1 -sei:v 0 -an -vf "format=vaapi|nv12,hwupload,scale_vaapi=out_range=tv" -user_agent ffmpeg/go2rtc -rtsp_transport tcp -f rtsp {output}`, args.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDrawText(t *testing.T) {
|
||||||
|
args := parseArgs("http:///example.com#video=h264#drawtext=fontsize=12")
|
||||||
|
require.Equal(t, `ffmpeg -hide_banner -fflags nobuffer -flags low_delay -i http:///example.com -c:v libx264 -g 50 -profile:v high -level:v 4.1 -preset:v superfast -tune:v zerolatency -pix_fmt:v yuv420p -an -vf "drawtext=fontsize=12:text='%{localtime\:%Y-%m-%d %X}'" -user_agent ffmpeg/go2rtc -rtsp_transport tcp -f rtsp {output}`, args.String())
|
||||||
|
|
||||||
|
args = parseArgs("http:///example.com#video=h264#width=640#drawtext=fontsize=12")
|
||||||
|
require.Equal(t, `ffmpeg -hide_banner -fflags nobuffer -flags low_delay -i http:///example.com -c:v libx264 -g 50 -profile:v high -level:v 4.1 -preset:v superfast -tune:v zerolatency -pix_fmt:v yuv420p -an -vf "scale=640:-1,drawtext=fontsize=12:text='%{localtime\:%Y-%m-%d %X}'" -user_agent ffmpeg/go2rtc -rtsp_transport tcp -f rtsp {output}`, args.String())
|
||||||
|
|
||||||
|
args = parseArgs("http:///example.com#video=h264#width=640#drawtext=fontsize=12#hardware=vaapi")
|
||||||
|
require.Equal(t, `ffmpeg -hide_banner -hwaccel vaapi -hwaccel_output_format nv12 -hwaccel_flags allow_profile_mismatch -fflags nobuffer -flags low_delay -i http:///example.com -c:v h264_vaapi -g 50 -bf 0 -profile:v high -level:v 4.1 -sei:v 0 -an -vf "scale=640:-1:out_color_matrix=bt709:out_range=tv,drawtext=fontsize=12:text='%{localtime\:%Y-%m-%d %X}',hwupload" -user_agent ffmpeg/go2rtc -rtsp_transport tcp -f rtsp {output}`, args.String())
|
||||||
|
}
|
||||||
|
|||||||
@@ -58,11 +58,13 @@ func MakeHardware(args *ffmpeg.Args, engine string, defaults map[string]string)
|
|||||||
case EngineVAAPI:
|
case EngineVAAPI:
|
||||||
args.Codecs[i] = defaults[name+"/"+engine]
|
args.Codecs[i] = defaults[name+"/"+engine]
|
||||||
|
|
||||||
fixYCbCrRange(args)
|
|
||||||
|
|
||||||
if !args.HasFilters("drawtext=") {
|
if !args.HasFilters("drawtext=") {
|
||||||
args.Input = "-hwaccel vaapi -hwaccel_output_format vaapi -hwaccel_flags allow_profile_mismatch " + args.Input
|
args.Input = "-hwaccel vaapi -hwaccel_output_format vaapi -hwaccel_flags allow_profile_mismatch " + args.Input
|
||||||
|
|
||||||
|
if name == "h264" {
|
||||||
|
fixPixelFormat(args)
|
||||||
|
}
|
||||||
|
|
||||||
for i, filter := range args.Filters {
|
for i, filter := range args.Filters {
|
||||||
if strings.HasPrefix(filter, "scale=") {
|
if strings.HasPrefix(filter, "scale=") {
|
||||||
args.Filters[i] = "scale_vaapi=" + filter[6:]
|
args.Filters[i] = "scale_vaapi=" + filter[6:]
|
||||||
@@ -157,20 +159,23 @@ func cut(s string, sep byte, pos int) string {
|
|||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
// fixYCbCrRange convert jpeg/pc range to mpeg/tv range
|
// fixPixelFormat:
|
||||||
// vaapi(pc, bt709, progressive) == yuvj420p (jpeg/full/pc)
|
// - good h264 pixel: yuv420p(tv, bt709) == yuv420p (mpeg/limited/tv)
|
||||||
// vaapi(tv, bt709, progressive) == yuv420p (mpeg/limited/tv)
|
// - bad h264 pixel: yuvj420p(pc, bt709) == yuvj420p (jpeg/full/pc)
|
||||||
// https://ffmpeg.org/ffmpeg-all.html#scale-1
|
// - bad jpeg pixel: yuvj422p(pc, bt470bg)
|
||||||
func fixYCbCrRange(args *ffmpeg.Args) {
|
func fixPixelFormat(args *ffmpeg.Args) {
|
||||||
|
// in my tests this filters has same CPU/GPU load:
|
||||||
|
// - "hwupload"
|
||||||
|
// - "hwupload,scale_vaapi=out_color_matrix=bt709:out_range=tv"
|
||||||
|
// - "hwupload,scale_vaapi=out_color_matrix=bt709:out_range=tv:format=nv12"
|
||||||
|
const fixPixFmt = "out_color_matrix=bt709:out_range=tv:format=nv12"
|
||||||
|
|
||||||
for i, filter := range args.Filters {
|
for i, filter := range args.Filters {
|
||||||
if strings.HasPrefix(filter, "scale=") {
|
if strings.HasPrefix(filter, "scale=") {
|
||||||
if !strings.Contains(filter, "out_range=") {
|
args.Filters[i] = filter + ":" + fixPixFmt
|
||||||
args.Filters[i] = filter + ":out_range=tv"
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// scale=out_color_matrix=bt709:out_range=tv
|
args.Filters = append(args.Filters, "scale="+fixPixFmt)
|
||||||
args.Filters = append(args.Filters, "scale=out_range=tv")
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
# GoPro
|
||||||
|
|
||||||
|
Supported models: HERO9, HERO10, HERO11, HERO12.
|
||||||
|
Supported OS: Linux, Mac, Windows, [HassOS](https://www.home-assistant.io/installation/)
|
||||||
|
|
||||||
|
The other camera models have different APIs. I will try to add them in the next versions.
|
||||||
|
|
||||||
|
## Config
|
||||||
|
|
||||||
|
- USB-connected cameras create a new network interface in the system
|
||||||
|
- Linux users do not need to install anything
|
||||||
|
- Windows users should install the [network driver](https://community.gopro.com/s/article/GoPro-Webcam)
|
||||||
|
- if the camera is detected but the stream does not start - you need to disable firewall
|
||||||
|
|
||||||
|
1. Discover camera address: WebUI > Add > GoPro
|
||||||
|
2. Add camera to config
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
streams:
|
||||||
|
hero12: gopro://172.20.100.51
|
||||||
|
```
|
||||||
|
|
||||||
|
## Useful links
|
||||||
|
|
||||||
|
- https://gopro.github.io/OpenGoPro/
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package gopro
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/AlexxIT/go2rtc/internal/api"
|
||||||
|
"github.com/AlexxIT/go2rtc/internal/streams"
|
||||||
|
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||||
|
"github.com/AlexxIT/go2rtc/pkg/gopro"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Init() {
|
||||||
|
streams.HandleFunc("gopro", handleGoPro)
|
||||||
|
|
||||||
|
api.HandleFunc("api/gopro", apiGoPro)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleGoPro(rawURL string) (core.Producer, error) {
|
||||||
|
return gopro.Dial(rawURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
func apiGoPro(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var items []*api.Source
|
||||||
|
|
||||||
|
for _, host := range gopro.Discovery() {
|
||||||
|
items = append(items, &api.Source{Name: host, URL: "gopro://" + host})
|
||||||
|
}
|
||||||
|
|
||||||
|
api.ResponseSources(w, items)
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package homekit
|
package homekit
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -97,7 +98,7 @@ func Init() {
|
|||||||
|
|
||||||
srv.mdns = &mdns.ServiceEntry{
|
srv.mdns = &mdns.ServiceEntry{
|
||||||
Name: name,
|
Name: name,
|
||||||
Port: uint16(api.Port()),
|
Port: uint16(api.Port),
|
||||||
Info: map[string]string{
|
Info: map[string]string{
|
||||||
hap.TXTConfigNumber: "1",
|
hap.TXTConfigNumber: "1",
|
||||||
hap.TXTFeatureFlags: "0",
|
hap.TXTFeatureFlags: "0",
|
||||||
@@ -134,6 +135,10 @@ var log zerolog.Logger
|
|||||||
var servers map[string]*server
|
var servers map[string]*server
|
||||||
|
|
||||||
func streamHandler(url string) (core.Producer, error) {
|
func streamHandler(url string) (core.Producer, error) {
|
||||||
|
if srtp.Server == nil {
|
||||||
|
return nil, errors.New("homekit: can't work without SRTP server")
|
||||||
|
}
|
||||||
|
|
||||||
return homekit.Dial(url, srtp.Server)
|
return homekit.Dial(url, srtp.Server)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/AlexxIT/go2rtc/internal/api"
|
||||||
"github.com/AlexxIT/go2rtc/internal/streams"
|
"github.com/AlexxIT/go2rtc/internal/streams"
|
||||||
"github.com/AlexxIT/go2rtc/pkg/core"
|
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||||
"github.com/AlexxIT/go2rtc/pkg/hls"
|
"github.com/AlexxIT/go2rtc/pkg/hls"
|
||||||
@@ -22,6 +23,8 @@ func Init() {
|
|||||||
streams.HandleFunc("httpx", handleHTTP)
|
streams.HandleFunc("httpx", handleHTTP)
|
||||||
|
|
||||||
streams.HandleFunc("tcp", handleTCP)
|
streams.HandleFunc("tcp", handleTCP)
|
||||||
|
|
||||||
|
api.HandleFunc("api/stream", apiStream)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleHTTP(rawURL string) (core.Producer, error) {
|
func handleHTTP(rawURL string) (core.Producer, error) {
|
||||||
@@ -89,3 +92,26 @@ func handleTCP(rawURL string) (core.Producer, error) {
|
|||||||
|
|
||||||
return magic.Open(conn)
|
return magic.Open(conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func apiStream(w http.ResponseWriter, r *http.Request) {
|
||||||
|
dst := r.URL.Query().Get("dst")
|
||||||
|
stream := streams.Get(dst)
|
||||||
|
if stream == nil {
|
||||||
|
http.Error(w, api.StreamNotFound, http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := magic.Open(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.AddProducer(client)
|
||||||
|
defer stream.RemoveProducer(client)
|
||||||
|
|
||||||
|
if err = client.Start(); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -56,19 +56,17 @@ func inputMpegTS(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
res := &http.Response{Body: r.Body, Request: r}
|
client, err := mpegts.Open(r.Body)
|
||||||
client, err := mpegts.Open(res.Body)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
stream.AddProducer(client)
|
stream.AddProducer(client)
|
||||||
|
defer stream.RemoveProducer(client)
|
||||||
|
|
||||||
if err = client.Start(); err != nil {
|
if err = client.Start(); err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
stream.RemoveProducer(client)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,13 @@ package ngrok
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/AlexxIT/go2rtc/internal/app"
|
"github.com/AlexxIT/go2rtc/internal/app"
|
||||||
"github.com/AlexxIT/go2rtc/internal/webrtc"
|
"github.com/AlexxIT/go2rtc/internal/webrtc"
|
||||||
"github.com/AlexxIT/go2rtc/pkg/ngrok"
|
"github.com/AlexxIT/go2rtc/pkg/ngrok"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"net"
|
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func Init() {
|
func Init() {
|
||||||
@@ -39,7 +40,7 @@ func Init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Addr: "//localhost:8555", URL: "tcp://1.tcp.eu.ngrok.io:12345"
|
// Addr: "//localhost:8555", URL: "tcp://1.tcp.eu.ngrok.io:12345"
|
||||||
if msg.Addr == "//localhost:"+webrtc.Port && strings.HasPrefix(msg.URL, "tcp://") {
|
if strings.HasPrefix(msg.Addr, "//localhost:") && strings.HasPrefix(msg.URL, "tcp://") {
|
||||||
// don't know if really necessary use IP
|
// don't know if really necessary use IP
|
||||||
address, err := ConvertHostToIP(msg.URL[6:])
|
address, err := ConvertHostToIP(msg.URL[6:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -49,7 +50,7 @@ func Init() {
|
|||||||
|
|
||||||
log.Info().Str("addr", address).Msg("[ngrok] add external candidate for WebRTC")
|
log.Info().Str("addr", address).Msg("[ngrok] add external candidate for WebRTC")
|
||||||
|
|
||||||
webrtc.AddCandidate(address)
|
webrtc.AddCandidate(address, "tcp")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ func Init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// default config
|
// default config
|
||||||
conf.Mod.Listen = "0.0.0.0:8554"
|
conf.Mod.Listen = ":8554"
|
||||||
conf.Mod.DefaultQuery = "video&audio"
|
conf.Mod.DefaultQuery = "video&audio"
|
||||||
|
|
||||||
app.LoadConfig(&conf)
|
app.LoadConfig(&conf)
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ func Init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// default config
|
// default config
|
||||||
cfg.Mod.Listen = "0.0.0.0:8443"
|
cfg.Mod.Listen = ":8443"
|
||||||
|
|
||||||
// load config from YAML
|
// load config from YAML
|
||||||
app.LoadConfig(&cfg)
|
app.LoadConfig(&cfg)
|
||||||
|
|||||||
@@ -1,3 +1,14 @@
|
|||||||
|
## Config
|
||||||
|
|
||||||
|
- supported TCP: fixed port (default), disabled
|
||||||
|
- supported UDP: random port (default), fixed port
|
||||||
|
|
||||||
|
| Config examples | TCP | UDP |
|
||||||
|
|-----------------------|-------|--------|
|
||||||
|
| `listen: ":8555/tcp"` | fixed | random |
|
||||||
|
| `listen: ":8555"` | fixed | fixed |
|
||||||
|
| `listen: ""` | no | random |
|
||||||
|
|
||||||
## Userful links
|
## Userful links
|
||||||
|
|
||||||
- https://www.ietf.org/archive/id/draft-ietf-wish-whip-01.html
|
- https://www.ietf.org/archive/id/draft-ietf-wish-whip-01.html
|
||||||
|
|||||||
@@ -1,58 +1,66 @@
|
|||||||
package webrtc
|
package webrtc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net"
|
||||||
|
|
||||||
"github.com/AlexxIT/go2rtc/internal/api/ws"
|
"github.com/AlexxIT/go2rtc/internal/api/ws"
|
||||||
"github.com/AlexxIT/go2rtc/pkg/webrtc"
|
"github.com/AlexxIT/go2rtc/pkg/webrtc"
|
||||||
"github.com/pion/sdp/v3"
|
"github.com/pion/sdp/v3"
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Address struct {
|
type Address struct {
|
||||||
Host string
|
Host string
|
||||||
Port int
|
Port string
|
||||||
|
Network string
|
||||||
|
Offset int
|
||||||
}
|
}
|
||||||
|
|
||||||
var addresses []Address
|
func (a *Address) Marshal() string {
|
||||||
|
host := a.Host
|
||||||
func AddCandidate(address string) {
|
if host == "stun" {
|
||||||
var port int
|
ip, err := webrtc.GetCachedPublicIP()
|
||||||
|
if err != nil {
|
||||||
// try to get port from address string
|
return ""
|
||||||
if i := strings.LastIndexByte(address, ':'); i > 0 {
|
|
||||||
if v, _ := strconv.Atoi(address[i+1:]); v != 0 {
|
|
||||||
address = address[:i]
|
|
||||||
port = v
|
|
||||||
}
|
}
|
||||||
|
host = ip.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// use default WebRTC port
|
switch a.Network {
|
||||||
if port == 0 {
|
case "udp":
|
||||||
port, _ = strconv.Atoi(Port)
|
return webrtc.CandidateManualHostUDP(host, a.Port, a.Offset)
|
||||||
|
case "tcp":
|
||||||
|
return webrtc.CandidateManualHostTCPPassive(host, a.Port, a.Offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
addresses = append(addresses, Address{Host: address, Port: port})
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var addresses []*Address
|
||||||
|
|
||||||
|
func AddCandidate(address, network string) {
|
||||||
|
host, port, err := net.SplitHostPort(address)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
offset := -1 - len(addresses) // every next candidate will have a lower priority
|
||||||
|
|
||||||
|
switch network {
|
||||||
|
case "tcp", "udp":
|
||||||
|
addresses = append(addresses, &Address{host, port, network, offset})
|
||||||
|
default:
|
||||||
|
addresses = append(
|
||||||
|
addresses, &Address{host, port, "udp", offset}, &Address{host, port, "tcp", offset},
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetCandidates() (candidates []string) {
|
func GetCandidates() (candidates []string) {
|
||||||
for _, address := range addresses {
|
for _, address := range addresses {
|
||||||
// using stun server for receive public IP-address
|
if candidate := address.Marshal(); candidate != "" {
|
||||||
if address.Host == "stun" {
|
candidates = append(candidates, candidate)
|
||||||
ip, err := webrtc.GetCachedPublicIP()
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// this is a copy, original host unchanged
|
|
||||||
address.Host = ip.String()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
candidates = append(
|
|
||||||
candidates,
|
|
||||||
webrtc.CandidateManualHostUDP(address.Host, address.Port),
|
|
||||||
webrtc.CandidateManualHostTCPPassive(address.Host, address.Port),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ func go2rtcClient(url string) (core.Producer, error) {
|
|||||||
switch msg := msg.(type) {
|
switch msg := msg.(type) {
|
||||||
case *pion.ICECandidate:
|
case *pion.ICECandidate:
|
||||||
s := msg.ToJSON().Candidate
|
s := msg.ToJSON().Candidate
|
||||||
log.Trace().Str("candidate", s).Msg("[webrtc] local")
|
log.Trace().Str("candidate", s).Msg("[webrtc] local ")
|
||||||
_ = conn.WriteJSON(&ws.Message{Type: "webrtc/candidate", Value: s})
|
_ = conn.WriteJSON(&ws.Message{Type: "webrtc/candidate", Value: s})
|
||||||
|
|
||||||
case pion.PeerConnectionState:
|
case pion.PeerConnectionState:
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ func kinesisClient(rawURL string, query url.Values, desc string) (core.Producer,
|
|||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
// 3. Create Peer Connection
|
// 3. Create Peer Connection
|
||||||
api, err := webrtc.NewAPI("")
|
api, err := webrtc.NewAPI()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ func openIPCClient(rawURL string, query url.Values) (core.Producer, error) {
|
|||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
// 3. Create Peer Connection
|
// 3. Create Peer Connection
|
||||||
api, err := webrtc.NewAPI("")
|
api, err := webrtc.NewAPI()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -195,9 +195,7 @@ func inputWebRTC(w http.ResponseWriter, r *http.Request) {
|
|||||||
case pion.PeerConnectionState:
|
case pion.PeerConnectionState:
|
||||||
if msg == pion.PeerConnectionStateClosed {
|
if msg == pion.PeerConnectionStateClosed {
|
||||||
stream.RemoveProducer(prod)
|
stream.RemoveProducer(prod)
|
||||||
if _, ok := sessions[id]; ok {
|
delete(sessions, id)
|
||||||
delete(sessions, id)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
+16
-12
@@ -2,7 +2,7 @@ package webrtc
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"net"
|
"strings"
|
||||||
|
|
||||||
"github.com/AlexxIT/go2rtc/internal/api"
|
"github.com/AlexxIT/go2rtc/internal/api"
|
||||||
"github.com/AlexxIT/go2rtc/internal/api/ws"
|
"github.com/AlexxIT/go2rtc/internal/api/ws"
|
||||||
@@ -23,7 +23,7 @@ func Init() {
|
|||||||
} `yaml:"webrtc"`
|
} `yaml:"webrtc"`
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg.Mod.Listen = "0.0.0.0:8555/tcp"
|
cfg.Mod.Listen = ":8555/tcp"
|
||||||
cfg.Mod.IceServers = []pion.ICEServer{
|
cfg.Mod.IceServers = []pion.ICEServer{
|
||||||
{URLs: []string{"stun:stun.l.google.com:19302"}},
|
{URLs: []string{"stun:stun.l.google.com:19302"}},
|
||||||
}
|
}
|
||||||
@@ -32,10 +32,20 @@ func Init() {
|
|||||||
|
|
||||||
log = app.GetLogger("webrtc")
|
log = app.GetLogger("webrtc")
|
||||||
|
|
||||||
address := cfg.Mod.Listen
|
address, network, _ := strings.Cut(cfg.Mod.Listen, "/")
|
||||||
|
|
||||||
|
var candidateHost []string
|
||||||
|
for _, candidate := range cfg.Mod.Candidates {
|
||||||
|
if strings.HasPrefix(candidate, "host:") {
|
||||||
|
candidateHost = append(candidateHost, candidate[5:])
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
AddCandidate(candidate, network)
|
||||||
|
}
|
||||||
|
|
||||||
// create pionAPI with custom codecs list and custom network settings
|
// create pionAPI with custom codecs list and custom network settings
|
||||||
serverAPI, err := webrtc.NewAPI(address)
|
serverAPI, err := webrtc.NewServerAPI(address, network, candidateHost)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Caller().Send()
|
log.Error().Err(err).Caller().Send()
|
||||||
return
|
return
|
||||||
@@ -46,9 +56,8 @@ func Init() {
|
|||||||
|
|
||||||
if address != "" {
|
if address != "" {
|
||||||
log.Info().Str("addr", address).Msg("[webrtc] listen")
|
log.Info().Str("addr", address).Msg("[webrtc] listen")
|
||||||
_, Port, _ = net.SplitHostPort(address)
|
|
||||||
|
|
||||||
clientAPI, _ = webrtc.NewAPI("")
|
clientAPI, _ = webrtc.NewAPI()
|
||||||
}
|
}
|
||||||
|
|
||||||
pionConf := pion.Configuration{
|
pionConf := pion.Configuration{
|
||||||
@@ -65,10 +74,6 @@ func Init() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, candidate := range cfg.Mod.Candidates {
|
|
||||||
AddCandidate(candidate)
|
|
||||||
}
|
|
||||||
|
|
||||||
// async WebRTC server (two API versions)
|
// async WebRTC server (two API versions)
|
||||||
ws.HandleFunc("webrtc", asyncHandler)
|
ws.HandleFunc("webrtc", asyncHandler)
|
||||||
ws.HandleFunc("webrtc/offer", asyncHandler)
|
ws.HandleFunc("webrtc/offer", asyncHandler)
|
||||||
@@ -81,7 +86,6 @@ func Init() {
|
|||||||
streams.HandleFunc("webrtc", streamsHandler)
|
streams.HandleFunc("webrtc", streamsHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
var Port string
|
|
||||||
var log zerolog.Logger
|
var log zerolog.Logger
|
||||||
|
|
||||||
var PeerConnection func(active bool) (*pion.PeerConnection, error)
|
var PeerConnection func(active bool) (*pion.PeerConnection, error)
|
||||||
@@ -138,7 +142,7 @@ func asyncHandler(tr *ws.Transport, msg *ws.Message) error {
|
|||||||
_ = sendAnswer.Wait()
|
_ = sendAnswer.Wait()
|
||||||
|
|
||||||
s := msg.ToJSON().Candidate
|
s := msg.ToJSON().Candidate
|
||||||
log.Trace().Str("candidate", s).Msg("[webrtc] local")
|
log.Trace().Str("candidate", s).Msg("[webrtc] local ")
|
||||||
tr.Write(&ws.Message{Type: "webrtc/candidate", Value: s})
|
tr.Write(&ws.Message{Type: "webrtc/candidate", Value: s})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -9,7 +9,9 @@ import (
|
|||||||
"github.com/AlexxIT/go2rtc/internal/dvrip"
|
"github.com/AlexxIT/go2rtc/internal/dvrip"
|
||||||
"github.com/AlexxIT/go2rtc/internal/echo"
|
"github.com/AlexxIT/go2rtc/internal/echo"
|
||||||
"github.com/AlexxIT/go2rtc/internal/exec"
|
"github.com/AlexxIT/go2rtc/internal/exec"
|
||||||
|
"github.com/AlexxIT/go2rtc/internal/expr"
|
||||||
"github.com/AlexxIT/go2rtc/internal/ffmpeg"
|
"github.com/AlexxIT/go2rtc/internal/ffmpeg"
|
||||||
|
"github.com/AlexxIT/go2rtc/internal/gopro"
|
||||||
"github.com/AlexxIT/go2rtc/internal/hass"
|
"github.com/AlexxIT/go2rtc/internal/hass"
|
||||||
"github.com/AlexxIT/go2rtc/internal/hls"
|
"github.com/AlexxIT/go2rtc/internal/hls"
|
||||||
"github.com/AlexxIT/go2rtc/internal/homekit"
|
"github.com/AlexxIT/go2rtc/internal/homekit"
|
||||||
@@ -34,8 +36,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
app.Version = "1.8.0"
|
|
||||||
|
|
||||||
// 1. Core modules: app, api/ws, streams
|
// 1. Core modules: app, api/ws, streams
|
||||||
|
|
||||||
app.Init() // init config and logs
|
app.Init() // init config and logs
|
||||||
@@ -78,10 +78,12 @@ func main() {
|
|||||||
homekit.Init() // homekit source
|
homekit.Init() // homekit source
|
||||||
nest.Init() // nest source
|
nest.Init() // nest source
|
||||||
bubble.Init() // bubble source
|
bubble.Init() // bubble source
|
||||||
|
expr.Init() // expr source
|
||||||
|
gopro.Init() // gopro source
|
||||||
|
|
||||||
// 6. Helper modules
|
// 6. Helper modules
|
||||||
|
|
||||||
ngrok.Init() // Ngrok module
|
ngrok.Init() // ngrok module
|
||||||
srtp.Init() // SRTP server
|
srtp.Init() // SRTP server
|
||||||
debug.Init() // debug API
|
debug.Init() // debug API
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -10,7 +10,7 @@ import (
|
|||||||
|
|
||||||
func IsADTS(b []byte) bool {
|
func IsADTS(b []byte) bool {
|
||||||
_ = b[1]
|
_ = b[1]
|
||||||
return len(b) > 7 && b[0] == 0xFF && b[1]&0xF0 == 0xF0
|
return len(b) > 7 && b[0] == 0xFF && b[1]&0xF6 == 0xF0
|
||||||
}
|
}
|
||||||
|
|
||||||
func ADTSToCodec(b []byte) *core.Codec {
|
func ADTSToCodec(b []byte) *core.Codec {
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
package aac
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||||
|
"github.com/pion/rtp"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Producer struct {
|
||||||
|
core.SuperProducer
|
||||||
|
rd *bufio.Reader
|
||||||
|
cl io.Closer
|
||||||
|
}
|
||||||
|
|
||||||
|
func Open(r io.Reader) (*Producer, error) {
|
||||||
|
rd := bufio.NewReader(r)
|
||||||
|
|
||||||
|
b, err := rd.Peek(8)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
codec := ADTSToCodec(b)
|
||||||
|
|
||||||
|
prod := &Producer{rd: rd, cl: r.(io.Closer)}
|
||||||
|
prod.Type = "ADTS producer"
|
||||||
|
prod.Medias = []*core.Media{
|
||||||
|
{
|
||||||
|
Kind: core.KindAudio,
|
||||||
|
Direction: core.DirectionRecvonly,
|
||||||
|
Codecs: []*core.Codec{codec},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return prod, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Producer) Start() error {
|
||||||
|
for {
|
||||||
|
b, err := c.rd.Peek(6)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
auSize := ReadADTSSize(b)
|
||||||
|
payload := make([]byte, 2+2+auSize)
|
||||||
|
if _, err = io.ReadFull(c.rd, payload[4:]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Recv += int(auSize)
|
||||||
|
|
||||||
|
if len(c.Receivers) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
payload[1] = 16 // header size in bits
|
||||||
|
binary.BigEndian.PutUint16(payload[2:], auSize<<3)
|
||||||
|
|
||||||
|
pkt := &rtp.Packet{
|
||||||
|
Header: rtp.Header{Timestamp: core.Now90000()},
|
||||||
|
Payload: payload,
|
||||||
|
}
|
||||||
|
c.Receivers[0].WriteRTP(pkt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Producer) Stop() error {
|
||||||
|
_ = c.SuperProducer.Close()
|
||||||
|
return c.cl.Close()
|
||||||
|
}
|
||||||
+5
-1
@@ -28,9 +28,13 @@ func RTPDepay(handler core.HandlerFunc) core.HandlerFunc {
|
|||||||
headers := packet.Payload[2 : 2+headersSize]
|
headers := packet.Payload[2 : 2+headersSize]
|
||||||
units := packet.Payload[2+headersSize:]
|
units := packet.Payload[2+headersSize:]
|
||||||
|
|
||||||
for len(headers) > 0 {
|
for len(headers) >= 2 {
|
||||||
unitSize := binary.BigEndian.Uint16(headers) >> 3
|
unitSize := binary.BigEndian.Uint16(headers) >> 3
|
||||||
|
|
||||||
|
if len(units) < int(unitSize) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
unit := units[:unitSize]
|
unit := units[:unitSize]
|
||||||
|
|
||||||
headers = headers[2:]
|
headers = headers[2:]
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
const ProbeSize = 1024 * 1024 // 1MB
|
// ProbeSize
|
||||||
|
// in my tests MPEG-TS 40Mbit/s 4K-video require more than 1MB for probe
|
||||||
|
const ProbeSize = 5 * 1024 * 1024 // 5MB
|
||||||
|
|
||||||
const (
|
const (
|
||||||
BufferDisable = 0
|
BufferDisable = 0
|
||||||
|
|||||||
+3
-5
@@ -117,9 +117,9 @@ func (s *Sender) HandleRTP(track *Receiver) {
|
|||||||
|
|
||||||
if GetKind(track.Codec.Name) == KindVideo {
|
if GetKind(track.Codec.Name) == KindVideo {
|
||||||
if track.Codec.IsRTP() {
|
if track.Codec.IsRTP() {
|
||||||
// H.264 2560x1440 4096kbs can have 700+ packets between 25 frames
|
// in my tests 40Mbit/s 4K-video can generate up to 1500 items
|
||||||
// H.265 5120x1440 can have 700+ packets between two keyframes
|
// for the h264.RTPDepay => RTPPay queue
|
||||||
bufferSize = 1000
|
bufferSize = 5000
|
||||||
} else {
|
} else {
|
||||||
bufferSize = 50
|
bufferSize = 50
|
||||||
}
|
}
|
||||||
@@ -140,9 +140,7 @@ func (s *Sender) HandleRTP(track *Receiver) {
|
|||||||
go func() {
|
go func() {
|
||||||
// read packets from buffer channel until it will be closed
|
// read packets from buffer channel until it will be closed
|
||||||
for packet := range buffer {
|
for packet := range buffer {
|
||||||
s.mu.Lock()
|
|
||||||
s.bytes += len(packet.Payload)
|
s.bytes += len(packet.Payload)
|
||||||
s.mu.Unlock()
|
|
||||||
s.Handler(packet)
|
s.Handler(packet)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,115 @@
|
|||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"regexp"
|
||||||
|
|
||||||
|
"github.com/AlexxIT/go2rtc/pkg/tcp"
|
||||||
|
"github.com/antonmedv/expr"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newRequest(method, url string, headers map[string]any) (*http.Request, error) {
|
||||||
|
if method == "" {
|
||||||
|
method = "GET"
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest(method, url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range headers {
|
||||||
|
req.Header.Set(k, fmt.Sprintf("%v", v))
|
||||||
|
}
|
||||||
|
|
||||||
|
return req, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func regExp(params ...any) (*regexp.Regexp, error) {
|
||||||
|
exp := params[0].(string)
|
||||||
|
if len(params) >= 2 {
|
||||||
|
// support:
|
||||||
|
// i case-insensitive (default false)
|
||||||
|
// m multi-line mode: ^ and $ match begin/end line (default false)
|
||||||
|
// s let . match \n (default false)
|
||||||
|
// https://pkg.go.dev/regexp/syntax
|
||||||
|
flags := params[1].(string)
|
||||||
|
exp = "(?" + flags + ")" + exp
|
||||||
|
}
|
||||||
|
return regexp.Compile(exp)
|
||||||
|
}
|
||||||
|
|
||||||
|
var Options = []expr.Option{
|
||||||
|
expr.Function(
|
||||||
|
"fetch",
|
||||||
|
func(params ...any) (any, error) {
|
||||||
|
var req *http.Request
|
||||||
|
var err error
|
||||||
|
|
||||||
|
url := params[0].(string)
|
||||||
|
|
||||||
|
if len(params) == 2 {
|
||||||
|
options := params[1].(map[string]any)
|
||||||
|
method, _ := options["method"].(string)
|
||||||
|
headers, _ := options["headers"].(map[string]any)
|
||||||
|
req, err = newRequest(method, url, headers)
|
||||||
|
} else {
|
||||||
|
req, err = http.NewRequest("GET", url, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := tcp.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
b, _ := io.ReadAll(res.Body)
|
||||||
|
|
||||||
|
return map[string]any{
|
||||||
|
"ok": res.StatusCode < 400,
|
||||||
|
"status": res.Status,
|
||||||
|
"text": string(b),
|
||||||
|
"json": func() (v any) {
|
||||||
|
_ = json.Unmarshal(b, &v)
|
||||||
|
return
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
//new(func(url string) map[string]any),
|
||||||
|
//new(func(url string, options map[string]any) map[string]any),
|
||||||
|
),
|
||||||
|
expr.Function(
|
||||||
|
"match",
|
||||||
|
func(params ...any) (any, error) {
|
||||||
|
re, err := regExp(params[1:]...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
str := params[0].(string)
|
||||||
|
return re.FindStringSubmatch(str), nil
|
||||||
|
},
|
||||||
|
//new(func(str, expr string) []string),
|
||||||
|
//new(func(str, expr, flags string) []string),
|
||||||
|
),
|
||||||
|
expr.Function(
|
||||||
|
"RegExp",
|
||||||
|
func(params ...any) (any, error) {
|
||||||
|
return regExp(params)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
func Run(input string) (any, error) {
|
||||||
|
program, err := expr.Compile(input, Options...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return expr.Run(program, nil)
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMatchHost(t *testing.T) {
|
||||||
|
v, err := Run(`
|
||||||
|
let url = "rtsp://user:pass@192.168.1.123/cam/realmonitor?...";
|
||||||
|
let host = match(url, "//[^/]+")[0][2:];
|
||||||
|
host
|
||||||
|
`)
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.Equal(t, "user:pass@192.168.1.123", v)
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package gopro
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Discovery() (urls []string) {
|
||||||
|
ints, err := net.Interfaces()
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// The socket address for USB connections is 172.2X.1YZ.51:8080
|
||||||
|
// https://gopro.github.io/OpenGoPro/http_2_0#socket-address
|
||||||
|
re := regexp.MustCompile(`^172\.2\d\.1\d\d\.`)
|
||||||
|
|
||||||
|
for _, itf := range ints {
|
||||||
|
addrs, err := itf.Addrs()
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, addr := range addrs {
|
||||||
|
host := addr.String()
|
||||||
|
if !re.MatchString(host) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
host = host[:11] + "51" // 172.2x.1xx.xxx
|
||||||
|
res, err := http.Get("http://" + host + ":8080/gopro/webcam/status")
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
_ = res.Body.Close()
|
||||||
|
|
||||||
|
urls = append(urls, host)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
package gopro
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||||
|
"github.com/AlexxIT/go2rtc/pkg/mpegts"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Dial(rawURL string) (core.Producer, error) {
|
||||||
|
u, err := url.Parse(rawURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
r := &listener{host: u.Host}
|
||||||
|
|
||||||
|
if err = r.command("/gopro/webcam/stop"); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = r.listen(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = r.command("/gopro/webcam/start"); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return mpegts.Open(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
type listener struct {
|
||||||
|
conn net.PacketConn
|
||||||
|
host string
|
||||||
|
packet []byte
|
||||||
|
packets chan []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *listener) Read(p []byte) (n int, err error) {
|
||||||
|
if r.packet == nil {
|
||||||
|
var ok bool
|
||||||
|
if r.packet, ok = <-r.packets; !ok {
|
||||||
|
return 0, io.EOF // channel closed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
n = copy(p, r.packet)
|
||||||
|
|
||||||
|
if n < len(r.packet) {
|
||||||
|
r.packet = r.packet[n:]
|
||||||
|
} else {
|
||||||
|
r.packet = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *listener) Close() error {
|
||||||
|
return r.conn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *listener) command(api string) error {
|
||||||
|
client := &http.Client{Timeout: 5 * time.Second}
|
||||||
|
|
||||||
|
res, err := client.Get("http://" + r.host + ":8080" + api)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = res.Body.Close()
|
||||||
|
|
||||||
|
if res.StatusCode != http.StatusOK {
|
||||||
|
return errors.New("gopro: wrong response: " + res.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *listener) listen() (err error) {
|
||||||
|
if r.conn, err = net.ListenPacket("udp4", ":8554"); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
r.packets = make(chan []byte, 1024)
|
||||||
|
go r.worker()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *listener) worker() {
|
||||||
|
b := make([]byte, 1500)
|
||||||
|
for {
|
||||||
|
if err := r.conn.SetReadDeadline(time.Now().Add(3 * time.Second)); err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
n, _, err := r.conn.ReadFrom(b)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
packet := make([]byte, n)
|
||||||
|
copy(packet, b)
|
||||||
|
|
||||||
|
r.packets <- packet
|
||||||
|
}
|
||||||
|
|
||||||
|
close(r.packets)
|
||||||
|
|
||||||
|
_ = r.command("/gopro/webcam/stop")
|
||||||
|
}
|
||||||
@@ -29,6 +29,12 @@ func RTPDepay(codec *core.Codec, handler core.HandlerFunc) core.HandlerFunc {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Memory overflow protection. Can happen if we miss a lot of packets with the marker.
|
||||||
|
// https://github.com/AlexxIT/go2rtc/issues/675
|
||||||
|
if len(buf) > 5*1024*1024 {
|
||||||
|
buf = buf[: 0 : 512*1024]
|
||||||
|
}
|
||||||
|
|
||||||
// Fix TP-Link Tapo TC70: sends SPS and PPS with packet.Marker = true
|
// Fix TP-Link Tapo TC70: sends SPS and PPS with packet.Marker = true
|
||||||
// Reolink Duo 2: sends SPS with Marker and PPS without
|
// Reolink Duo 2: sends SPS with Marker and PPS without
|
||||||
if packet.Marker && len(payload) < PSMaxSize {
|
if packet.Marker && len(payload) < PSMaxSize {
|
||||||
|
|||||||
+3
-2
@@ -2,10 +2,11 @@ package hass
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
"github.com/AlexxIT/go2rtc/pkg/core"
|
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||||
"github.com/AlexxIT/go2rtc/pkg/webrtc"
|
"github.com/AlexxIT/go2rtc/pkg/webrtc"
|
||||||
pion "github.com/pion/webrtc/v3"
|
pion "github.com/pion/webrtc/v3"
|
||||||
"net/url"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
@@ -48,7 +49,7 @@ func NewClient(rawURL string) (*Client, error) {
|
|||||||
defer hassAPI.Close()
|
defer hassAPI.Close()
|
||||||
|
|
||||||
// 2. Create WebRTC client
|
// 2. Create WebRTC client
|
||||||
rtcAPI, err := webrtc.NewAPI("")
|
rtcAPI, err := webrtc.NewAPI()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,13 +70,15 @@ func (c *Producer) Start() error {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
pkt := &rtp.Packet{
|
if len(c.Receivers) > 0 {
|
||||||
Header: rtp.Header{Timestamp: core.Now90000()},
|
pkt := &rtp.Packet{
|
||||||
Payload: annexb.EncodeToAVCC(buf[:i], true),
|
Header: rtp.Header{Timestamp: core.Now90000()},
|
||||||
}
|
Payload: annexb.EncodeToAVCC(buf[:i], true),
|
||||||
c.Receivers[0].WriteRTP(pkt)
|
}
|
||||||
|
c.Receivers[0].WriteRTP(pkt)
|
||||||
|
|
||||||
//log.Printf("[AVC] %v, len: %d", h264.Types(pkt.Payload), len(pkt.Payload))
|
//log.Printf("[AVC] %v, len: %d", h264.Types(pkt.Payload), len(pkt.Payload))
|
||||||
|
}
|
||||||
|
|
||||||
buf = buf[i:]
|
buf = buf[i:]
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-1
@@ -6,6 +6,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
|
"github.com/AlexxIT/go2rtc/pkg/aac"
|
||||||
"github.com/AlexxIT/go2rtc/pkg/core"
|
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||||
"github.com/AlexxIT/go2rtc/pkg/flv"
|
"github.com/AlexxIT/go2rtc/pkg/flv"
|
||||||
"github.com/AlexxIT/go2rtc/pkg/h264/annexb"
|
"github.com/AlexxIT/go2rtc/pkg/h264/annexb"
|
||||||
@@ -33,6 +34,9 @@ func Open(r io.Reader) (core.Producer, error) {
|
|||||||
case bytes.HasPrefix(b, []byte(flv.Signature)):
|
case bytes.HasPrefix(b, []byte(flv.Signature)):
|
||||||
return flv.Open(rd)
|
return flv.Open(rd)
|
||||||
|
|
||||||
|
case bytes.HasPrefix(b, []byte{0xFF, 0xF1}):
|
||||||
|
return aac.Open(rd)
|
||||||
|
|
||||||
case bytes.HasPrefix(b, []byte("--")):
|
case bytes.HasPrefix(b, []byte("--")):
|
||||||
return multipart.Open(rd)
|
return multipart.Open(rd)
|
||||||
|
|
||||||
@@ -40,5 +44,16 @@ func Open(r io.Reader) (core.Producer, error) {
|
|||||||
return mpegts.Open(rd)
|
return mpegts.Open(rd)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, errors.New("magic: unsupported header: " + hex.EncodeToString(b))
|
// support MJPEG with trash on start
|
||||||
|
// https://github.com/AlexxIT/go2rtc/issues/747
|
||||||
|
if b, err = rd.Peek(4096); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if i := bytes.Index(b, []byte{0xFF, 0xD8, 0xFF, 0xDB}); i > 0 {
|
||||||
|
_, _ = io.ReadFull(rd, make([]byte, i))
|
||||||
|
return mjpeg.Open(rd)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.New("magic: unsupported header: " + hex.EncodeToString(b[:4]))
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -219,7 +219,7 @@ func (b *Browser) ListenMulticastUDP() error {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
b.Recv, err = lc2.ListenPacket(ctx, "udp4", "0.0.0.0:5353")
|
b.Recv, err = lc2.ListenPacket(ctx, "udp4", ":5353")
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-2
@@ -2,10 +2,11 @@ package nest
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
"github.com/AlexxIT/go2rtc/pkg/core"
|
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||||
"github.com/AlexxIT/go2rtc/pkg/webrtc"
|
"github.com/AlexxIT/go2rtc/pkg/webrtc"
|
||||||
pion "github.com/pion/webrtc/v3"
|
pion "github.com/pion/webrtc/v3"
|
||||||
"net/url"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
@@ -34,7 +35,7 @@ func NewClient(rawURL string) (*Client, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
rtcAPI, err := webrtc.NewAPI("")
|
rtcAPI, err := webrtc.NewAPI()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ func (c *Client) Connect() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 4. Create Peer Connection
|
// 4. Create Peer Connection
|
||||||
api, err := webrtc.NewAPI("")
|
api, err := webrtc.NewAPI()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-3
@@ -38,9 +38,8 @@ func UnmarshalSDP(rawSDP []byte) ([]*core.Media, error) {
|
|||||||
|
|
||||||
// Fix invalid media type (errSDPInvalidValue) caused by
|
// Fix invalid media type (errSDPInvalidValue) caused by
|
||||||
// some TP-LINK IP camera, e.g. TL-IPC44GW
|
// some TP-LINK IP camera, e.g. TL-IPC44GW
|
||||||
rawSDP = bytes.ReplaceAll(rawSDP, []byte("m=application/TP-LINK "), []byte("m=application "))
|
m := regexp.MustCompile("m=application/[^ ]+")
|
||||||
// more tplink ipcams
|
rawSDP = m.ReplaceAll(rawSDP, []byte("m=application"))
|
||||||
rawSDP = bytes.ReplaceAll(rawSDP, []byte("m=application/tp-link "), []byte("m=application "))
|
|
||||||
|
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
rawSDP = append(rawSDP, '\n')
|
rawSDP = append(rawSDP, '\n')
|
||||||
|
|||||||
+26
-1
@@ -1,8 +1,9 @@
|
|||||||
package rtsp
|
package rtsp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestURLParse(t *testing.T) {
|
func TestURLParse(t *testing.T) {
|
||||||
@@ -107,3 +108,27 @@ a=sendonly`
|
|||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Len(t, medias, 3)
|
assert.Len(t, medias, 3)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBugSDP4(t *testing.T) {
|
||||||
|
s := `v=0
|
||||||
|
o=- 14665860 31787219 1 IN IP4 10.0.0.94
|
||||||
|
s=Session streamed by "MERCURY RTSP Server"
|
||||||
|
t=0 0
|
||||||
|
m=video 0 RTP/AVP 96
|
||||||
|
c=IN IP4 0.0.0.0
|
||||||
|
b=AS:4096
|
||||||
|
a=range:npt=0-
|
||||||
|
a=control:track1
|
||||||
|
a=rtpmap:96 H264/90000
|
||||||
|
a=fmtp:96 packetization-mode=1; profile-level-id=640016; sprop-parameter-sets=Z2QAFqzGoCgPaEAAAAMAQAAAB6E=,aOqPLA==
|
||||||
|
m=audio 0 RTP/AVP 8
|
||||||
|
a=rtpmap:8 PCMA/8000
|
||||||
|
a=control:track2
|
||||||
|
m=application/MERCURY 0 RTP/AVP smart/1/90000
|
||||||
|
a=rtpmap:95 MERCURY/90000
|
||||||
|
a=control:track3
|
||||||
|
`
|
||||||
|
medias, err := UnmarshalSDP([]byte(s))
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Len(t, medias, 3)
|
||||||
|
}
|
||||||
|
|||||||
+18
-25
@@ -14,35 +14,28 @@ func QuoteSplit(s string) []string {
|
|||||||
var a []string
|
var a []string
|
||||||
|
|
||||||
for len(s) > 0 {
|
for len(s) > 0 {
|
||||||
is := strings.IndexByte(s, ' ')
|
switch c := s[0]; c {
|
||||||
if is >= 0 {
|
case '\t', '\n', '\r', ' ': // unicode.IsSpace
|
||||||
// skip prefix and double spaces
|
s = s[1:]
|
||||||
if is == 0 {
|
case '"', '\'': // quote chars
|
||||||
// goto next symbol
|
if i := strings.IndexByte(s[1:], c); i > 0 {
|
||||||
s = s[1:]
|
a = append(a, s[1:i+1])
|
||||||
continue
|
s = s[i+2:]
|
||||||
|
} else {
|
||||||
|
return nil // error
|
||||||
}
|
}
|
||||||
|
default:
|
||||||
// check if quote in word
|
i := strings.IndexAny(s, "\t\n\r ")
|
||||||
if i := strings.IndexByte(s[:is], '"'); i >= 0 {
|
if i > 0 {
|
||||||
// search quote end
|
a = append(a, s[:i])
|
||||||
if is = strings.Index(s, `" `); is > 0 {
|
s = s[i:]
|
||||||
is += 1
|
} else {
|
||||||
} else {
|
a = append(a, s)
|
||||||
is = -1
|
s = ""
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if is >= 0 {
|
|
||||||
a = append(a, strings.ReplaceAll(s[:is], `"`, ""))
|
|
||||||
s = s[is+1:]
|
|
||||||
} else {
|
|
||||||
//add last word
|
|
||||||
a = append(a, s)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package shell
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestQuoteSplit(t *testing.T) {
|
||||||
|
s := `
|
||||||
|
python "-c" 'import time
|
||||||
|
print("time", time.time())'
|
||||||
|
`
|
||||||
|
require.Equal(t, []string{"python", "-c", "import time\nprint(\"time\", time.time())"}, QuoteSplit(s))
|
||||||
|
|
||||||
|
s = `ffmpeg -i video="0" -i "DeckLink SDI (2)"`
|
||||||
|
require.Equal(t, []string{"ffmpeg", "-i", "video=\"0\"", "-i", "DeckLink SDI (2)"}, QuoteSplit(s))
|
||||||
|
}
|
||||||
+2
-1
@@ -57,7 +57,8 @@ func (s *Server) DelSession(session *Session) {
|
|||||||
|
|
||||||
delete(s.sessions, session.Remote.SSRC)
|
delete(s.sessions, session.Remote.SSRC)
|
||||||
|
|
||||||
if len(s.sessions) == 0 {
|
// check s.conn for https://github.com/AlexxIT/go2rtc/issues/734
|
||||||
|
if len(s.sessions) == 0 && s.conn != nil {
|
||||||
_ = s.conn.Close()
|
_ = s.conn.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+79
-20
@@ -1,10 +1,12 @@
|
|||||||
package tapo
|
package tapo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/aes"
|
"crypto/aes"
|
||||||
"crypto/cipher"
|
"crypto/cipher"
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
|
"crypto/sha256"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -14,6 +16,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/AlexxIT/go2rtc/pkg/core"
|
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||||
"github.com/AlexxIT/go2rtc/pkg/mpegts"
|
"github.com/AlexxIT/go2rtc/pkg/mpegts"
|
||||||
@@ -62,33 +65,19 @@ func (c *Client) newConn() (net.Conn, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// support raw username/password
|
|
||||||
username := u.User.Username()
|
|
||||||
password, _ := u.User.Password()
|
|
||||||
|
|
||||||
// or cloud password in place of username
|
|
||||||
if password == "" {
|
|
||||||
password = fmt.Sprintf("%16X", md5.Sum([]byte(username)))
|
|
||||||
username = "admin"
|
|
||||||
u.User = url.UserPassword(username, password)
|
|
||||||
}
|
|
||||||
|
|
||||||
u.Scheme = "http"
|
|
||||||
u.Path = "/stream"
|
|
||||||
if u.Port() == "" {
|
if u.Port() == "" {
|
||||||
u.Host += ":8800"
|
u.Host += ":8800"
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: fix closing connection
|
req, err := http.NewRequest("POST", "http://"+u.Host+"/stream", nil)
|
||||||
ctx, pconn := tcp.WithConn()
|
|
||||||
req, err := http.NewRequestWithContext(ctx, "POST", u.String(), nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
req.URL.User = u.User
|
||||||
req.Header.Set("Content-Type", "multipart/mixed; boundary=--client-stream-boundary--")
|
req.Header.Set("Content-Type", "multipart/mixed; boundary=--client-stream-boundary--")
|
||||||
|
|
||||||
res, err := tcp.Do(req)
|
conn, res, err := dial(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -98,13 +87,16 @@ func (c *Client) newConn() (net.Conn, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if c.decrypt == nil {
|
if c.decrypt == nil {
|
||||||
c.newDectypter(res, username, password)
|
c.newDectypter(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
return *pconn, nil
|
return conn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) newDectypter(res *http.Response, username, password string) {
|
func (c *Client) newDectypter(res *http.Response) {
|
||||||
|
username := res.Request.URL.User.Username()
|
||||||
|
password, _ := res.Request.URL.User.Password()
|
||||||
|
|
||||||
// extract nonce from response
|
// extract nonce from response
|
||||||
// cipher="AES_128_CBC" username="admin" padding="PKCS7_16" algorithm="MD5" nonce="***"
|
// cipher="AES_128_CBC" username="admin" padding="PKCS7_16" algorithm="MD5" nonce="***"
|
||||||
nonce := res.Header.Get("Key-Exchange")
|
nonce := res.Header.Get("Key-Exchange")
|
||||||
@@ -244,3 +236,70 @@ func (c *Client) Request(conn net.Conn, body []byte) (string, error) {
|
|||||||
return v.Params.SessionID, nil
|
return v.Params.SessionID, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func dial(req *http.Request) (net.Conn, *http.Response, error) {
|
||||||
|
conn, err := net.DialTimeout("tcp", req.URL.Host, core.ConnDialTimeout)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
username := req.URL.User.Username()
|
||||||
|
password, _ := req.URL.User.Password()
|
||||||
|
req.URL.User = nil
|
||||||
|
|
||||||
|
if err = req.Write(conn); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
r := bufio.NewReader(conn)
|
||||||
|
|
||||||
|
res, err := http.ReadResponse(r, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
auth := res.Header.Get("WWW-Authenticate")
|
||||||
|
|
||||||
|
if res.StatusCode != http.StatusUnauthorized || !strings.HasPrefix(auth, "Digest") {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if password == "" {
|
||||||
|
// support cloud password in place of username
|
||||||
|
if strings.Contains(auth, `encrypt_type="3"`) {
|
||||||
|
password = fmt.Sprintf("%32X", sha256.Sum256([]byte(username)))
|
||||||
|
} else {
|
||||||
|
password = fmt.Sprintf("%16X", md5.Sum([]byte(username)))
|
||||||
|
}
|
||||||
|
username = "admin"
|
||||||
|
}
|
||||||
|
|
||||||
|
realm := tcp.Between(auth, `realm="`, `"`)
|
||||||
|
nonce := tcp.Between(auth, `nonce="`, `"`)
|
||||||
|
qop := tcp.Between(auth, `qop="`, `"`)
|
||||||
|
uri := req.URL.RequestURI()
|
||||||
|
ha1 := tcp.HexMD5(username, realm, password)
|
||||||
|
ha2 := tcp.HexMD5(req.Method, uri)
|
||||||
|
nc := "00000001"
|
||||||
|
cnonce := "00000001"
|
||||||
|
response := tcp.HexMD5(ha1, nonce, nc, cnonce, qop, ha2)
|
||||||
|
|
||||||
|
header := fmt.Sprintf(
|
||||||
|
`Digest username="%s", realm="%s", nonce="%s", uri="%s", qop=%s, nc=%s, cnonce="%s", response="%s"`,
|
||||||
|
username, realm, nonce, uri, qop, nc, cnonce, response,
|
||||||
|
)
|
||||||
|
|
||||||
|
req.Header.Set("Authorization", header)
|
||||||
|
|
||||||
|
if err = req.Write(conn); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if res, err = http.ReadResponse(r, req); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req.URL.User = url.UserPassword(username, password)
|
||||||
|
|
||||||
|
return conn, res, nil
|
||||||
|
}
|
||||||
|
|||||||
+52
-25
@@ -2,6 +2,7 @@ package tcp
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
@@ -12,7 +13,24 @@ import (
|
|||||||
|
|
||||||
// Do - http.Client with support Digest Authorization
|
// Do - http.Client with support Digest Authorization
|
||||||
func Do(req *http.Request) (*http.Response, error) {
|
func Do(req *http.Request) (*http.Response, error) {
|
||||||
if secureClient == nil {
|
var secure *tls.Config
|
||||||
|
|
||||||
|
switch req.URL.Scheme {
|
||||||
|
case "httpx":
|
||||||
|
secure = &tls.Config{InsecureSkipVerify: true}
|
||||||
|
req.URL.Scheme = "https"
|
||||||
|
case "https":
|
||||||
|
if hostname := req.URL.Hostname(); IsIP(hostname) {
|
||||||
|
secure = &tls.Config{InsecureSkipVerify: true}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if secure != nil {
|
||||||
|
ctx := context.WithValue(req.Context(), secureKey, secure)
|
||||||
|
req = req.WithContext(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
if client == nil {
|
||||||
transport := http.DefaultTransport.(*http.Transport).Clone()
|
transport := http.DefaultTransport.(*http.Transport).Clone()
|
||||||
|
|
||||||
dial := transport.DialContext
|
dial := transport.DialContext
|
||||||
@@ -23,33 +41,38 @@ func Do(req *http.Request) (*http.Response, error) {
|
|||||||
}
|
}
|
||||||
return conn, err
|
return conn, err
|
||||||
}
|
}
|
||||||
|
transport.DialTLSContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
|
conn, err := dial(ctx, network, addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
secureClient = &http.Client{
|
var conf *tls.Config
|
||||||
|
if v, ok := ctx.Value(secureKey).(*tls.Config); ok {
|
||||||
|
conf = v
|
||||||
|
} else if host, _, err := net.SplitHostPort(addr); err != nil {
|
||||||
|
conf = &tls.Config{ServerName: addr}
|
||||||
|
} else {
|
||||||
|
conf = &tls.Config{ServerName: host}
|
||||||
|
}
|
||||||
|
|
||||||
|
tlsConn := tls.Client(conn, conf)
|
||||||
|
if err = tlsConn.Handshake(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if pconn, ok := ctx.Value(connKey).(*net.Conn); ok {
|
||||||
|
*pconn = tlsConn
|
||||||
|
}
|
||||||
|
return tlsConn, err
|
||||||
|
}
|
||||||
|
|
||||||
|
client = &http.Client{
|
||||||
Timeout: time.Second * 5000,
|
Timeout: time.Second * 5000,
|
||||||
Transport: transport,
|
Transport: transport,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var client *http.Client
|
|
||||||
|
|
||||||
if req.URL.Scheme == "httpx" || (req.URL.Scheme == "https" && IsIP(req.URL.Hostname())) {
|
|
||||||
req.URL.Scheme = "https"
|
|
||||||
|
|
||||||
if insecureClient == nil {
|
|
||||||
transport := secureClient.Transport.(*http.Transport).Clone()
|
|
||||||
transport.TLSClientConfig.InsecureSkipVerify = true
|
|
||||||
|
|
||||||
insecureClient = &http.Client{
|
|
||||||
Timeout: secureClient.Timeout,
|
|
||||||
Transport: transport,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
client = insecureClient
|
|
||||||
} else {
|
|
||||||
client = secureClient
|
|
||||||
}
|
|
||||||
|
|
||||||
user := req.URL.User
|
user := req.URL.User
|
||||||
|
|
||||||
// Hikvision won't answer on Basic auth with any headers
|
// Hikvision won't answer on Basic auth with any headers
|
||||||
@@ -88,7 +111,7 @@ func Do(req *http.Request) (*http.Response, error) {
|
|||||||
response := HexMD5(ha1, nonce, ha2)
|
response := HexMD5(ha1, nonce, ha2)
|
||||||
header = fmt.Sprintf(
|
header = fmt.Sprintf(
|
||||||
`Digest username="%s", realm="%s", nonce="%s", uri="%s", response="%s"`,
|
`Digest username="%s", realm="%s", nonce="%s", uri="%s", response="%s"`,
|
||||||
user, realm, nonce, uri, response,
|
username, realm, nonce, uri, response,
|
||||||
)
|
)
|
||||||
case "auth":
|
case "auth":
|
||||||
nc := "00000001"
|
nc := "00000001"
|
||||||
@@ -112,8 +135,12 @@ func Do(req *http.Request) (*http.Response, error) {
|
|||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var secureClient, insecureClient *http.Client
|
var client *http.Client
|
||||||
var connKey struct{}
|
|
||||||
|
type key string
|
||||||
|
|
||||||
|
var connKey = key("conn")
|
||||||
|
var secureKey = key("secure")
|
||||||
|
|
||||||
func WithConn() (context.Context, *net.Conn) {
|
func WithConn() (context.Context, *net.Conn) {
|
||||||
pconn := new(net.Conn)
|
pconn := new(net.Conn)
|
||||||
|
|||||||
+23
-20
@@ -1,18 +1,21 @@
|
|||||||
package webrtc
|
package webrtc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/pion/ice/v2"
|
"net"
|
||||||
|
|
||||||
"github.com/pion/interceptor"
|
"github.com/pion/interceptor"
|
||||||
"github.com/pion/webrtc/v3"
|
"github.com/pion/webrtc/v3"
|
||||||
"net"
|
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ReceiveMTU = Ethernet MTU (1500) - IP Header (20) - UDP Header (8)
|
// ReceiveMTU = Ethernet MTU (1500) - IP Header (20) - UDP Header (8)
|
||||||
// https://ffmpeg.org/ffmpeg-all.html#Muxer
|
// https://ffmpeg.org/ffmpeg-all.html#Muxer
|
||||||
const ReceiveMTU = 1472
|
const ReceiveMTU = 1472
|
||||||
|
|
||||||
func NewAPI(address string) (*webrtc.API, error) {
|
func NewAPI() (*webrtc.API, error) {
|
||||||
|
return NewServerAPI("", "", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewServerAPI(address, network string, candidateHost []string) (*webrtc.API, error) {
|
||||||
// for debug logs add to env: `PION_LOG_DEBUG=all`
|
// for debug logs add to env: `PION_LOG_DEBUG=all`
|
||||||
m := &webrtc.MediaEngine{}
|
m := &webrtc.MediaEngine{}
|
||||||
//if err := m.RegisterDefaultCodecs(); err != nil {
|
//if err := m.RegisterDefaultCodecs(); err != nil {
|
||||||
@@ -34,33 +37,33 @@ func NewAPI(address string) (*webrtc.API, error) {
|
|||||||
return name != "hassio" && name != "docker0"
|
return name != "hassio" && name != "docker0"
|
||||||
})
|
})
|
||||||
|
|
||||||
// disable mDNS listener
|
|
||||||
s.SetICEMulticastDNSMode(ice.MulticastDNSModeDisabled)
|
|
||||||
|
|
||||||
// UDP6 may have problems with DNS resolving for STUN servers
|
|
||||||
s.SetNetworkTypes([]webrtc.NetworkType{
|
|
||||||
webrtc.NetworkTypeUDP4, webrtc.NetworkTypeTCP4,
|
|
||||||
})
|
|
||||||
|
|
||||||
// fix https://github.com/pion/webrtc/pull/2407
|
// fix https://github.com/pion/webrtc/pull/2407
|
||||||
s.SetDTLSInsecureSkipHelloVerify(true)
|
s.SetDTLSInsecureSkipHelloVerify(true)
|
||||||
|
|
||||||
s.SetReceiveMTU(ReceiveMTU)
|
s.SetReceiveMTU(ReceiveMTU)
|
||||||
|
|
||||||
|
s.SetNAT1To1IPs(candidateHost, webrtc.ICECandidateTypeHost)
|
||||||
|
|
||||||
|
// by default enable IPv4 + IPv6 modes
|
||||||
|
s.SetNetworkTypes([]webrtc.NetworkType{
|
||||||
|
webrtc.NetworkTypeUDP4, webrtc.NetworkTypeTCP4,
|
||||||
|
webrtc.NetworkTypeUDP6, webrtc.NetworkTypeTCP6,
|
||||||
|
})
|
||||||
|
|
||||||
if address != "" {
|
if address != "" {
|
||||||
address, network, _ := strings.Cut(address, "/")
|
|
||||||
if network == "" || network == "udp" {
|
|
||||||
if ln, err := net.ListenPacket("udp4", address); err == nil {
|
|
||||||
udpMux := webrtc.NewICEUDPMux(nil, ln)
|
|
||||||
s.SetICEUDPMux(udpMux)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if network == "" || network == "tcp" {
|
if network == "" || network == "tcp" {
|
||||||
if ln, err := net.Listen("tcp4", address); err == nil {
|
if ln, err := net.Listen("tcp", address); err == nil {
|
||||||
tcpMux := webrtc.NewICETCPMux(nil, ln, 8)
|
tcpMux := webrtc.NewICETCPMux(nil, ln, 8)
|
||||||
s.SetICETCPMux(tcpMux)
|
s.SetICETCPMux(tcpMux)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if network == "" || network == "udp" {
|
||||||
|
if ln, err := net.ListenPacket("udp", address); err == nil {
|
||||||
|
udpMux := webrtc.NewICEUDPMux(nil, ln)
|
||||||
|
s.SetICEUDPMux(udpMux)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return webrtc.NewAPI(
|
return webrtc.NewAPI(
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestClient(t *testing.T) {
|
func TestClient(t *testing.T) {
|
||||||
api, err := NewAPI("")
|
api, err := NewAPI()
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
pc, err := api.NewPeerConnection(webrtc.Configuration{})
|
pc, err := api.NewPeerConnection(webrtc.Configuration{})
|
||||||
|
|||||||
+12
-18
@@ -260,17 +260,15 @@ func MimeType(codec *core.Codec) string {
|
|||||||
// for server reflexive candidates, 110 for peer reflexive candidates,
|
// for server reflexive candidates, 110 for peer reflexive candidates,
|
||||||
// and 0 for relayed candidates.
|
// and 0 for relayed candidates.
|
||||||
|
|
||||||
// We use new priority 120 for Manual Host. It is lower than real Host,
|
const PriorityTypeHostUDP = (1 << 24) * int(126)
|
||||||
// but more then any other candidates.
|
const PriorityTypeHostTCP = (1 << 24) * int(126-27)
|
||||||
|
const PriorityLocalUDP = (1 << 8) * int(65535)
|
||||||
|
const PriorityLocalTCPPassive = (1 << 8) * int((1<<13)*4+8191)
|
||||||
|
const PriorityComponentRTP = 1 * int(256-ice.ComponentRTP)
|
||||||
|
|
||||||
const PriorityManualHost = (1 << 24) * uint32(120)
|
func CandidateManualHostUDP(host, port string, offset int) string {
|
||||||
const PriorityLocalUDP = (1 << 8) * uint32(65535)
|
|
||||||
const PriorityLocalTCPPassive = (1 << 8) * uint32((1<<13)*4+8191)
|
|
||||||
const PriorityComponentRTP = uint32(256 - ice.ComponentRTP)
|
|
||||||
|
|
||||||
func CandidateManualHostUDP(host string, port int) string {
|
|
||||||
foundation := crc32.ChecksumIEEE([]byte("host" + host + "udp4"))
|
foundation := crc32.ChecksumIEEE([]byte("host" + host + "udp4"))
|
||||||
priority := PriorityManualHost + PriorityLocalUDP + PriorityComponentRTP
|
priority := PriorityTypeHostUDP + PriorityLocalUDP + PriorityComponentRTP + offset
|
||||||
|
|
||||||
// 1. Foundation
|
// 1. Foundation
|
||||||
// 2. Component, always 1 because RTP
|
// 2. Component, always 1 because RTP
|
||||||
@@ -279,19 +277,15 @@ func CandidateManualHostUDP(host string, port int) string {
|
|||||||
// 5. Host - IP4 or IP6 or domain name
|
// 5. Host - IP4 or IP6 or domain name
|
||||||
// 6. Port
|
// 6. Port
|
||||||
// 7. typ host
|
// 7. typ host
|
||||||
return fmt.Sprintf(
|
return fmt.Sprintf("candidate:%d 1 udp %d %s %s typ host", foundation, priority, host, port)
|
||||||
"candidate:%d 1 udp %d %s %d typ host",
|
|
||||||
foundation, priority, host, port,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func CandidateManualHostTCPPassive(address string, port int) string {
|
func CandidateManualHostTCPPassive(host, port string, offset int) string {
|
||||||
foundation := crc32.ChecksumIEEE([]byte("host" + address + "tcp4"))
|
foundation := crc32.ChecksumIEEE([]byte("host" + host + "tcp4"))
|
||||||
priority := PriorityManualHost + PriorityLocalTCPPassive + PriorityComponentRTP
|
priority := PriorityTypeHostTCP + PriorityLocalTCPPassive + PriorityComponentRTP + offset
|
||||||
|
|
||||||
return fmt.Sprintf(
|
return fmt.Sprintf(
|
||||||
"candidate:%d 1 tcp %d %s %d typ host tcptype passive",
|
"candidate:%d 1 tcp %d %s %s typ host tcptype passive", foundation, priority, host, port,
|
||||||
foundation, priority, address, port,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -246,6 +246,18 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<button id="gopro">GoPro</button>
|
||||||
|
<div class="module">
|
||||||
|
<table id="gopro-table"></table>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
document.getElementById('gopro').addEventListener('click', async ev => {
|
||||||
|
ev.target.nextElementSibling.style.display = 'block';
|
||||||
|
await getSources('gopro-table', 'api/gopro');
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
<button id="hass">Home Assistant</button>
|
<button id="hass">Home Assistant</button>
|
||||||
<div class="module">
|
<div class="module">
|
||||||
<table id="hass-table"></table>
|
<table id="hass-table"></table>
|
||||||
|
|||||||
Reference in New Issue
Block a user