Improve support ONVIF client
This commit is contained in:
+22
-6
@@ -11,6 +11,7 @@ import (
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
@@ -123,17 +124,32 @@ func apiOnvif(w http.ResponseWriter, r *http.Request) {
|
||||
var items []api.Stream
|
||||
|
||||
if src == "" {
|
||||
hosts, err := onvif.DiscoveryStreamingHosts()
|
||||
urls, err := onvif.DiscoveryStreamingURLs()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
for _, host := range hosts {
|
||||
items = append(items, api.Stream{
|
||||
Name: host,
|
||||
URL: "onvif://user:pass@" + host,
|
||||
})
|
||||
for _, rawURL := range urls {
|
||||
u, err := url.Parse(rawURL)
|
||||
if err != nil {
|
||||
log.Warn().Str("url", rawURL).Msg("[onvif] broken")
|
||||
continue
|
||||
}
|
||||
|
||||
if u.Scheme != "http" {
|
||||
log.Warn().Str("url", rawURL).Msg("[onvif] unsupported")
|
||||
continue
|
||||
}
|
||||
|
||||
u.Scheme = "onvif"
|
||||
u.User = url.UserPassword("user", "pass")
|
||||
|
||||
if u.Path == onvif.PathDevice {
|
||||
u.Path = ""
|
||||
}
|
||||
|
||||
items = append(items, api.Stream{Name: u.Host, URL: u.String()})
|
||||
}
|
||||
} else {
|
||||
client, err := onvif.NewClient(src)
|
||||
|
||||
+43
-21
@@ -17,6 +17,10 @@ import (
|
||||
|
||||
type Client struct {
|
||||
url *url.URL
|
||||
|
||||
deviceURL string
|
||||
mediaURL string
|
||||
imaginURL string
|
||||
}
|
||||
|
||||
func NewClient(rawURL string) (*Client, error) {
|
||||
@@ -24,7 +28,25 @@ func NewClient(rawURL string) (*Client, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Client{url: u}, nil
|
||||
|
||||
baseURL := "http://" + u.Host
|
||||
|
||||
client := &Client{url: u}
|
||||
if u.Path == "" {
|
||||
client.deviceURL = baseURL + PathDevice
|
||||
} else {
|
||||
client.deviceURL = baseURL + u.Path
|
||||
}
|
||||
|
||||
b, err := client.GetCapabilities()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client.mediaURL = FindTagValue(b, "Media.+?XAddr")
|
||||
client.imaginURL = FindTagValue(b, "Imaging.+?XAddr")
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func (c *Client) GetURI() (string, error) {
|
||||
@@ -39,7 +61,7 @@ func (c *Client) GetURI() (string, error) {
|
||||
return "", err
|
||||
}
|
||||
if i >= len(tokens) {
|
||||
return "", errors.New("wrong subtype")
|
||||
return "", errors.New("onvif: wrong subtype")
|
||||
}
|
||||
token = tokens[i]
|
||||
}
|
||||
@@ -104,7 +126,7 @@ func (c *Client) HasSnapshots() bool {
|
||||
|
||||
func (c *Client) GetCapabilities() ([]byte, error) {
|
||||
return c.Request(
|
||||
PathDevice,
|
||||
c.deviceURL,
|
||||
`<tds:GetCapabilities xmlns:tds="http://www.onvif.org/ver10/device/wsdl">
|
||||
<tds:Category>All</tds:Category>
|
||||
</tds:GetCapabilities>`,
|
||||
@@ -113,25 +135,25 @@ func (c *Client) GetCapabilities() ([]byte, error) {
|
||||
|
||||
func (c *Client) GetNetworkInterfaces() ([]byte, error) {
|
||||
return c.Request(
|
||||
PathDevice, `<tds:GetNetworkInterfaces xmlns:tds="http://www.onvif.org/ver10/device/wsdl"/>`,
|
||||
c.deviceURL, `<tds:GetNetworkInterfaces xmlns:tds="http://www.onvif.org/ver10/device/wsdl"/>`,
|
||||
)
|
||||
}
|
||||
|
||||
func (c *Client) GetDeviceInformation() ([]byte, error) {
|
||||
return c.Request(
|
||||
PathDevice, `<tds:GetDeviceInformation xmlns:tds="http://www.onvif.org/ver10/device/wsdl"/>`,
|
||||
c.deviceURL, `<tds:GetDeviceInformation xmlns:tds="http://www.onvif.org/ver10/device/wsdl"/>`,
|
||||
)
|
||||
}
|
||||
|
||||
func (c *Client) GetProfiles() ([]byte, error) {
|
||||
return c.Request(
|
||||
PathMedia, `<trt:GetProfiles xmlns:trt="http://www.onvif.org/ver10/media/wsdl"/>`,
|
||||
c.mediaURL, `<trt:GetProfiles xmlns:trt="http://www.onvif.org/ver10/media/wsdl"/>`,
|
||||
)
|
||||
}
|
||||
|
||||
func (c *Client) GetStreamUri(token string) ([]byte, error) {
|
||||
return c.Request(
|
||||
PathMedia,
|
||||
c.mediaURL,
|
||||
`<trt:GetStreamUri xmlns:trt="http://www.onvif.org/ver10/media/wsdl" xmlns:tt="http://www.onvif.org/ver10/schema">
|
||||
<trt:StreamSetup>
|
||||
<tt:Stream>RTP-Unicast</tt:Stream>
|
||||
@@ -144,8 +166,8 @@ func (c *Client) GetStreamUri(token string) ([]byte, error) {
|
||||
|
||||
func (c *Client) GetSnapshotUri(token string) ([]byte, error) {
|
||||
return c.Request(
|
||||
PathMedia,
|
||||
`<trt:GetSnapshotUri xmlns:trt="http://www.onvif.org/ver10/media/wsdl">
|
||||
c.imaginURL,
|
||||
`<trt:GetSnapshotUri xmlns:trt="http://www.onvif.org/ver10/media/wsdl">
|
||||
<trt:ProfileToken>`+token+`</trt:ProfileToken>
|
||||
</trt:GetSnapshotUri>`,
|
||||
)
|
||||
@@ -153,26 +175,26 @@ func (c *Client) GetSnapshotUri(token string) ([]byte, error) {
|
||||
|
||||
func (c *Client) GetSystemDateAndTime() ([]byte, error) {
|
||||
return c.Request(
|
||||
PathDevice, `<tds:GetSystemDateAndTime xmlns:tds="http://www.onvif.org/ver10/device/wsdl"/>`,
|
||||
c.deviceURL, `<tds:GetSystemDateAndTime xmlns:tds="http://www.onvif.org/ver10/device/wsdl"/>`,
|
||||
)
|
||||
}
|
||||
|
||||
func (c *Client) GetServiceCapabilities() ([]byte, error) {
|
||||
// some cameras answer GetServiceCapabilities for media only for path = "/onvif/media"
|
||||
return c.Request(
|
||||
PathMedia, `<trt:GetServiceCapabilities xmlns:trt="http://www.onvif.org/ver10/media/wsdl"/>`,
|
||||
c.mediaURL, `<trt:GetServiceCapabilities xmlns:trt="http://www.onvif.org/ver10/media/wsdl"/>`,
|
||||
)
|
||||
}
|
||||
|
||||
func (c *Client) SystemReboot() ([]byte, error) {
|
||||
return c.Request(
|
||||
PathDevice, `<tds:SystemReboot xmlns:tds="http://www.onvif.org/ver10/device/wsdl"/>`,
|
||||
c.deviceURL, `<tds:SystemReboot xmlns:tds="http://www.onvif.org/ver10/device/wsdl"/>`,
|
||||
)
|
||||
}
|
||||
|
||||
func (c *Client) GetServices() ([]byte, error) {
|
||||
return c.Request(
|
||||
PathDevice, `<tds:GetServices xmlns:tds="http://www.onvif.org/ver10/device/wsdl">
|
||||
c.deviceURL, `<tds:GetServices xmlns:tds="http://www.onvif.org/ver10/device/wsdl">
|
||||
<tds:IncludeCapability>true</tds:IncludeCapability>
|
||||
</tds:GetServices>`,
|
||||
)
|
||||
@@ -180,11 +202,15 @@ func (c *Client) GetServices() ([]byte, error) {
|
||||
|
||||
func (c *Client) GetScopes() ([]byte, error) {
|
||||
return c.Request(
|
||||
PathDevice, `<tds:GetScopes xmlns:tds="http://www.onvif.org/ver10/device/wsdl" />`,
|
||||
c.deviceURL, `<tds:GetScopes xmlns:tds="http://www.onvif.org/ver10/device/wsdl" />`,
|
||||
)
|
||||
}
|
||||
|
||||
func (c *Client) Request(path, body string) ([]byte, error) {
|
||||
func (c *Client) Request(url, body string) ([]byte, error) {
|
||||
if url == "" {
|
||||
return nil, errors.New("onvif: unsupported service")
|
||||
}
|
||||
|
||||
buf := bytes.NewBuffer(nil)
|
||||
buf.WriteString(
|
||||
`<?xml version="1.0" encoding="UTF-8"?><s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">`,
|
||||
@@ -213,11 +239,7 @@ func (c *Client) Request(path, body string) ([]byte, error) {
|
||||
buf.WriteString(`<s:Body>` + body + `</s:Body></s:Envelope>`)
|
||||
|
||||
client := &http.Client{Timeout: time.Second * 5000}
|
||||
res, err := client.Post(
|
||||
"http://"+c.url.Host+path,
|
||||
`application/soap+xml;charset=utf-8`,
|
||||
buf,
|
||||
)
|
||||
res, err := client.Post(url, `application/soap+xml;charset=utf-8`, buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -226,7 +248,7 @@ func (c *Client) Request(path, body string) ([]byte, error) {
|
||||
b, err := io.ReadAll(res.Body)
|
||||
|
||||
if err == nil && res.StatusCode != http.StatusOK {
|
||||
err = errors.New(res.Status)
|
||||
err = errors.New("onvif: " + res.Status + " for " + url)
|
||||
}
|
||||
|
||||
return b, err
|
||||
|
||||
+11
-22
@@ -3,7 +3,6 @@ package onvif
|
||||
import (
|
||||
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||
"net"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -12,11 +11,10 @@ import (
|
||||
|
||||
const (
|
||||
PathDevice = "/onvif/device_service"
|
||||
PathMedia = "/onvif/media_service"
|
||||
)
|
||||
|
||||
func FindTagValue(b []byte, tag string) string {
|
||||
re := regexp.MustCompile(tag + `[^>]*>([^<]+)`)
|
||||
re := regexp.MustCompile(`<[^/>]*` + tag + `[^>]*>([^<]+)`)
|
||||
m := re.FindSubmatch(b)
|
||||
if len(m) != 2 {
|
||||
return ""
|
||||
@@ -30,7 +28,7 @@ func UUID() string {
|
||||
return s[:8] + "-" + s[8:12] + "-" + s[12:16] + "-" + s[16:20] + "-" + s[20:]
|
||||
}
|
||||
|
||||
func DiscoveryStreamingHosts() ([]string, error) {
|
||||
func DiscoveryStreamingURLs() ([]string, error) {
|
||||
conn, err := net.ListenUDP("udp4", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -66,7 +64,7 @@ func DiscoveryStreamingHosts() ([]string, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var hosts []string
|
||||
var urls []string
|
||||
|
||||
b := make([]byte, 8192)
|
||||
for {
|
||||
@@ -75,37 +73,28 @@ func DiscoveryStreamingHosts() ([]string, error) {
|
||||
break
|
||||
}
|
||||
|
||||
//log.Printf("[onvif] discovery response addr=%s:\n%s", addr, b[:n])
|
||||
|
||||
// ignore printers, etc
|
||||
if !strings.Contains(string(b[:n]), "onvif") {
|
||||
continue
|
||||
}
|
||||
|
||||
//log.Printf("[onvif] discovery response:\n%s", b[:n])
|
||||
|
||||
rawURL := FindTagValue(b[:n], "XAddrs")
|
||||
if rawURL == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
u, err := url.Parse(rawURL)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if u.Scheme != "http" {
|
||||
url := FindTagValue(b[:n], "XAddrs")
|
||||
if url == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// fix some buggy cameras
|
||||
// <wsdd:XAddrs>http://0.0.0.0:8080/onvif/device_service</wsdd:XAddrs>
|
||||
if strings.HasPrefix(u.Host, "0.0.0.0") {
|
||||
u.Host = addr.IP.String() + u.Host[7:]
|
||||
if strings.HasPrefix(url, "http://0.0.0.0") {
|
||||
url = "http://" + addr.IP.String() + url[14:]
|
||||
}
|
||||
|
||||
hosts = append(hosts, u.Host)
|
||||
urls = append(urls, url)
|
||||
}
|
||||
|
||||
return hosts, nil
|
||||
return urls, nil
|
||||
}
|
||||
|
||||
func atoi(s string) int {
|
||||
|
||||
@@ -60,6 +60,7 @@
|
||||
<script>
|
||||
async function getStreams(url, tableID) {
|
||||
const table = document.getElementById(tableID)
|
||||
table.innerText = 'loading...'
|
||||
|
||||
const r = typeof url === 'string' ? await fetch(url, {cache: 'no-cache'}) : url
|
||||
if (!r.ok) {
|
||||
|
||||
Reference in New Issue
Block a user