fix: proxy MJPEG stream through NextNVR to avoid cross-origin blocking
This commit is contained in:
parent
f248b796c6
commit
b68171f370
3 changed files with 40 additions and 3 deletions
36
api.go
36
api.go
|
|
@ -4,6 +4,7 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
|
@ -118,7 +119,40 @@ func (s *Server) handleConfig(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleScan triggers ONVIF network discovery.
|
// handleStream proxies MJPEG streams from go2rtc to avoid cross-origin issues.
|
||||||
|
// GET /stream/{cam_id}?type=sub (default: sub stream via go2rtc)
|
||||||
|
func (s *Server) handleStream(w http.ResponseWriter, r *http.Request) {
|
||||||
|
camID := strings.TrimPrefix(r.URL.Path, "/stream/")
|
||||||
|
if camID == "" {
|
||||||
|
http.Error(w, "camera ID required", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
streamType := r.URL.Query().Get("type")
|
||||||
|
if streamType == "" {
|
||||||
|
streamType = "sub"
|
||||||
|
}
|
||||||
|
|
||||||
|
go2rtcURL := "http://127.0.0.1:1984/api/stream.mjpeg?src=" + camID + "_" + streamType
|
||||||
|
|
||||||
|
resp, err := http.Get(go2rtcURL)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "stream unavailable", http.StatusServiceUnavailable)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
// Copy headers from go2rtc.
|
||||||
|
for k, v := range resp.Header {
|
||||||
|
for _, vv := range v {
|
||||||
|
w.Header().Add(k, vv)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.WriteHeader(resp.StatusCode)
|
||||||
|
|
||||||
|
// Stream the MJPEG data directly to the client.
|
||||||
|
io.Copy(w, resp.Body)
|
||||||
|
}
|
||||||
// POST /api/scan
|
// POST /api/scan
|
||||||
// Body: {"method":"range","from":"192.168.1.200","to":"192.168.1.210","username":"admin","password":"..."}
|
// Body: {"method":"range","from":"192.168.1.200","to":"192.168.1.210","username":"admin","password":"..."}
|
||||||
func (s *Server) handleScan(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) handleScan(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
|
||||||
|
|
@ -106,9 +106,9 @@ function updateFocus() {
|
||||||
const cam = cameras[focusIdx];
|
const cam = cameras[focusIdx];
|
||||||
document.getElementById('focus-title').textContent = cam.name;
|
document.getElementById('focus-title').textContent = cam.name;
|
||||||
document.getElementById('focus-time').textContent = new Date().toLocaleTimeString() + ' live';
|
document.getElementById('focus-time').textContent = new Date().toLocaleTimeString() + ' live';
|
||||||
// go2rtc MJPEG stream — real live video via <img> tag, works in all browsers.
|
// go2rtc MJPEG stream proxied through NextNVR — same origin, no CORS issues.
|
||||||
const img = document.getElementById('focus-video');
|
const img = document.getElementById('focus-video');
|
||||||
img.src = `http://${location.hostname}:1984/api/stream.mjpeg?src=${cam.id}_sub`;
|
img.src = `/stream/${cam.id}?type=sub&t=${Date.now()}`;
|
||||||
}
|
}
|
||||||
function renderThumbs() {
|
function renderThumbs() {
|
||||||
const strip = document.getElementById('focus-thumbs');
|
const strip = document.getElementById('focus-thumbs');
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,9 @@ func (s *Server) registerRoutes() {
|
||||||
s.mux.HandleFunc("/api/status", s.handleStatus)
|
s.mux.HandleFunc("/api/status", s.handleStatus)
|
||||||
s.mux.HandleFunc("/api/recordings", s.handleRecordings)
|
s.mux.HandleFunc("/api/recordings", s.handleRecordings)
|
||||||
|
|
||||||
|
// Live MJPEG stream proxy — proxied through NextNVR to avoid cross-origin issues.
|
||||||
|
s.mux.HandleFunc("/stream/", s.handleStream)
|
||||||
|
|
||||||
// Static file server for recordings (actual video files on disk).
|
// Static file server for recordings (actual video files on disk).
|
||||||
s.mux.Handle("/recordings/", http.StripPrefix("/recordings/",
|
s.mux.Handle("/recordings/", http.StripPrefix("/recordings/",
|
||||||
http.FileServer(http.Dir(s.config.Storage.RecordingsPath))))
|
http.FileServer(http.Dir(s.config.Storage.RecordingsPath))))
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue