Add ONVIF stream handler for tester
- Add testOnvif(): resolves all profiles via ONVIF client, tests each RTSP stream, returns two Results per profile (onvif + rtsp) with shared screenshot - Route onvif:// URLs in worker.go alongside homekit:// - Classify onvif:// streams as recommended in test.html - Harden create.html against undefined/null URL values
This commit is contained in:
@@ -0,0 +1,104 @@
|
|||||||
|
package tester
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||||
|
"github.com/AlexxIT/go2rtc/pkg/onvif"
|
||||||
|
)
|
||||||
|
|
||||||
|
// testOnvif resolves all ONVIF profiles, tests each via RTSP,
|
||||||
|
// and adds two Results per profile (onvif:// + rtsp://).
|
||||||
|
// ex. "onvif://admin:pass@10.0.20.111" or "onvif://admin:pass@10.0.20.119:2020"
|
||||||
|
func testOnvif(s *Session, rawURL string) {
|
||||||
|
client, err := onvif.NewClient(rawURL)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tokens, err := client.GetProfilesTokens()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, token := range tokens {
|
||||||
|
profileURL := rawURL + "?subtype=" + token
|
||||||
|
|
||||||
|
pc, err := onvif.NewClient(profileURL)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
rtspURI, err := pc.GetURI()
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
testOnvifProfile(s, profileURL, rtspURI)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// testOnvifProfile tests a single RTSP stream and adds two Results (onvif + rtsp)
|
||||||
|
func testOnvifProfile(s *Session, onvifURL, rtspURL string) {
|
||||||
|
start := time.Now()
|
||||||
|
|
||||||
|
prod, err := rtspHandler(rtspURL)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer func() { _ = prod.Stop() }()
|
||||||
|
|
||||||
|
latency := time.Since(start).Milliseconds()
|
||||||
|
|
||||||
|
var codecs []string
|
||||||
|
for _, media := range prod.GetMedias() {
|
||||||
|
if media.Direction != core.DirectionRecvonly {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, codec := range media.Codecs {
|
||||||
|
codecs = append(codecs, codec.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// capture screenshot
|
||||||
|
var screenshotPath string
|
||||||
|
var width, height int
|
||||||
|
|
||||||
|
if raw, codecName := getScreenshot(prod); raw != nil {
|
||||||
|
var jpeg []byte
|
||||||
|
|
||||||
|
switch codecName {
|
||||||
|
case core.CodecH264, core.CodecH265:
|
||||||
|
jpeg = toJPEG(raw)
|
||||||
|
default:
|
||||||
|
jpeg = raw
|
||||||
|
}
|
||||||
|
|
||||||
|
if jpeg != nil {
|
||||||
|
idx := s.AddScreenshot(jpeg)
|
||||||
|
screenshotPath = fmt.Sprintf("api/test/screenshot?id=%s&i=%d", s.ID, idx)
|
||||||
|
width, height = jpegSize(jpeg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// add onvif:// result
|
||||||
|
s.AddResult(&Result{
|
||||||
|
Source: onvifURL,
|
||||||
|
Screenshot: screenshotPath,
|
||||||
|
Codecs: codecs,
|
||||||
|
Width: width,
|
||||||
|
Height: height,
|
||||||
|
LatencyMs: latency,
|
||||||
|
})
|
||||||
|
|
||||||
|
// add rtsp:// result (same screenshot, same codecs)
|
||||||
|
s.AddResult(&Result{
|
||||||
|
Source: rtspURL,
|
||||||
|
Screenshot: screenshotPath,
|
||||||
|
Codecs: codecs,
|
||||||
|
Width: width,
|
||||||
|
Height: height,
|
||||||
|
LatencyMs: latency,
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -56,6 +56,11 @@ func testURL(s *Session, rawURL string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(rawURL, "onvif://") {
|
||||||
|
testOnvif(s, rawURL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
handler := GetHandler(rawURL)
|
handler := GetHandler(rawURL)
|
||||||
if handler == nil {
|
if handler == nil {
|
||||||
return
|
return
|
||||||
|
|||||||
+6
-5
@@ -328,8 +328,9 @@
|
|||||||
|
|
||||||
// Pre-populate custom streams from "url" query parameter (supports multiple)
|
// Pre-populate custom streams from "url" query parameter (supports multiple)
|
||||||
params.getAll('url').forEach(function(u) {
|
params.getAll('url').forEach(function(u) {
|
||||||
|
if (!u || typeof u !== 'string') return;
|
||||||
u = u.trim();
|
u = u.trim();
|
||||||
if (u && u.indexOf('://') !== -1 && customStreams.indexOf(u) === -1) {
|
if (u && u !== 'undefined' && u !== 'null' && u.indexOf('://') !== -1 && customStreams.indexOf(u) === -1) {
|
||||||
customStreams.push(u);
|
customStreams.push(u);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -395,7 +396,7 @@
|
|||||||
addInput.type = 'text';
|
addInput.type = 'text';
|
||||||
addInput.placeholder = 'rtsp://user:pass@host/path or bubble://...';
|
addInput.placeholder = 'rtsp://user:pass@host/path or bubble://...';
|
||||||
addInput.spellcheck = false;
|
addInput.spellcheck = false;
|
||||||
addInput.value = pendingInput;
|
addInput.value = pendingInput || '';
|
||||||
|
|
||||||
var addBtn = document.createElement('button');
|
var addBtn = document.createElement('button');
|
||||||
addBtn.className = 'btn-add';
|
addBtn.className = 'btn-add';
|
||||||
@@ -404,7 +405,7 @@
|
|||||||
|
|
||||||
function addCustom() {
|
function addCustom() {
|
||||||
var v = addInput.value.trim();
|
var v = addInput.value.trim();
|
||||||
if (!v) return;
|
if (!v || v === 'undefined' || v === 'null') return;
|
||||||
if (v.indexOf('://') === -1) {
|
if (v.indexOf('://') === -1) {
|
||||||
showToast('URL must include protocol (rtsp://, http://, bubble://, ...)');
|
showToast('URL must include protocol (rtsp://, http://, bubble://, ...)');
|
||||||
return;
|
return;
|
||||||
@@ -592,7 +593,7 @@
|
|||||||
addInput.type = 'text';
|
addInput.type = 'text';
|
||||||
addInput.placeholder = 'rtsp://user:pass@host/path or bubble://...';
|
addInput.placeholder = 'rtsp://user:pass@host/path or bubble://...';
|
||||||
addInput.spellcheck = false;
|
addInput.spellcheck = false;
|
||||||
addInput.value = pendingInput;
|
addInput.value = pendingInput || '';
|
||||||
var addBtn = document.createElement('button');
|
var addBtn = document.createElement('button');
|
||||||
addBtn.className = 'btn-add';
|
addBtn.className = 'btn-add';
|
||||||
addBtn.type = 'button';
|
addBtn.type = 'button';
|
||||||
@@ -600,7 +601,7 @@
|
|||||||
|
|
||||||
function addCustom() {
|
function addCustom() {
|
||||||
var v = addInput.value.trim();
|
var v = addInput.value.trim();
|
||||||
if (!v) return;
|
if (!v || v === 'undefined' || v === 'null') return;
|
||||||
if (v.indexOf('://') === -1) { showToast('URL must include protocol (rtsp://, http://, bubble://, ...)'); return; }
|
if (v.indexOf('://') === -1) { showToast('URL must include protocol (rtsp://, http://, bubble://, ...)'); return; }
|
||||||
if (customStreams.indexOf(v) !== -1 || dbStreams.indexOf(v) !== -1) { showToast('This URL is already in the list'); return; }
|
if (customStreams.indexOf(v) !== -1 || dbStreams.indexOf(v) !== -1) { showToast('This URL is already in the list'); return; }
|
||||||
customStreams.push(v);
|
customStreams.push(v);
|
||||||
|
|||||||
+3
-3
@@ -423,11 +423,11 @@
|
|||||||
|
|
||||||
function classifyResult(r) {
|
function classifyResult(r) {
|
||||||
var scheme = r.source.split('://')[0] || '';
|
var scheme = r.source.split('://')[0] || '';
|
||||||
var isRtsp = scheme === 'rtsp' || scheme === 'rtsps';
|
var isRecommended = scheme === 'rtsp' || scheme === 'rtsps' || scheme === 'onvif';
|
||||||
var isHD = r.width >= 1280;
|
var isHD = r.width >= 1280;
|
||||||
|
|
||||||
if (isRtsp && isHD) return 'rec-main';
|
if (isRecommended && isHD) return 'rec-main';
|
||||||
if (isRtsp) return 'rec-sub';
|
if (isRecommended) return 'rec-sub';
|
||||||
return 'alt';
|
return 'alt';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user