diff --git a/api.go b/api.go index 65951cd..31b1b3c 100644 --- a/api.go +++ b/api.go @@ -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) { diff --git a/public/app.js b/public/app.js index 94bcd4d..dd0366d 100644 --- a/public/app.js +++ b/public/app.js @@ -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 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'); diff --git a/server.go b/server.go index 0cd3088..c6ead5d 100644 --- a/server.go +++ b/server.go @@ -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))))