fix: proxy MJPEG stream through NextNVR to avoid cross-origin blocking

This commit is contained in:
Claus Lohmar 2026-08-05 13:04:44 +01:00
parent f248b796c6
commit b68171f370
3 changed files with 40 additions and 3 deletions

36
api.go
View file

@ -4,6 +4,7 @@ package main
import (
"encoding/json"
"io"
"net"
"net/http"
"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
// 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) {

View file

@ -106,9 +106,9 @@ function updateFocus() {
const cam = cameras[focusIdx];
document.getElementById('focus-title').textContent = cam.name;
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');
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() {
const strip = document.getElementById('focus-thumbs');

View file

@ -63,6 +63,9 @@ func (s *Server) registerRoutes() {
s.mux.HandleFunc("/api/status", s.handleStatus)
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).
s.mux.Handle("/recordings/", http.StripPrefix("/recordings/",
http.FileServer(http.Dir(s.config.Storage.RecordingsPath))))