From d730413cba9ee3db00ab1e69b899d0e3995e38be Mon Sep 17 00:00:00 2001 From: tian <11429339@qq.com> Date: Thu, 7 May 2026 15:03:52 +0800 Subject: [PATCH] feat: proxy HLS through backend to eliminate CORS issues --- internal/web/ui.go | 31 ++++++++++++++++++++++++++ internal/web/ui/templates/monitor.html | 8 ++++--- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/internal/web/ui.go b/internal/web/ui.go index 8103146..313ab86 100644 --- a/internal/web/ui.go +++ b/internal/web/ui.go @@ -662,6 +662,7 @@ func (u *UI) Routes() (chi.Router, error) { r.Get("/diagnostics", u.pageDiagnostics) r.Get("/alarms", u.pageAlarms) r.Get("/monitor", u.pageMonitor) + r.Get("/hls/{deviceID}/*", u.proxyHLS) r.Get("/api/monitor/channels", u.apiMonitorChannels) r.Get("/recognition", u.pageRecognition) r.Get("/logs", u.pageLogs) @@ -3891,3 +3892,33 @@ func (u *UI) apiMonitorChannels(w http.ResponseWriter, r *http.Request) { w.WriteHeader(code) w.Write(body) } + +func (u *UI) proxyHLS(w http.ResponseWriter, r *http.Request) { + deviceID := chi.URLParam(r, "deviceID") + dev, ok := u.findDevice(deviceID) + if !ok { + http.Error(w, "device not found", http.StatusNotFound) + return + } + hlsPath := chi.URLParam(r, "*") + if hlsPath == "" { + http.Error(w, "missing path", http.StatusBadRequest) + return + } + body, code, err := u.agent.Do("GET", dev.IP, dev.MediaPort, "/"+hlsPath, nil) + if err != nil { + http.Error(w, err.Error(), http.StatusBadGateway) + return + } + // Set HLS-friendly headers + if strings.HasSuffix(hlsPath, ".m3u8") { + w.Header().Set("Content-Type", "application/vnd.apple.mpegurl") + w.Header().Set("Access-Control-Allow-Origin", "*") + } else if strings.HasSuffix(hlsPath, ".ts") { + w.Header().Set("Content-Type", "video/mp2t") + w.Header().Set("Access-Control-Allow-Origin", "*") + } + w.Header().Set("Cache-Control", "no-cache") + w.WriteHeader(code) + w.Write(body) +} diff --git a/internal/web/ui/templates/monitor.html b/internal/web/ui/templates/monitor.html index 35288d1..762562c 100644 --- a/internal/web/ui/templates/monitor.html +++ b/internal/web/ui/templates/monitor.html @@ -34,7 +34,7 @@ function loadAll() { var promises = devices.map(function(dev) { return fetch('/ui/api/monitor/channels?device_id=' + encodeURIComponent(dev.id)) .then(function(r) { return r.json(); }) - .then(function(data) { return (data.channels||[]).map(function(ch) { ch._dev = dev.name; return ch; }); }) + .then(function(data) { return (data.channels||[]).map(function(ch) { ch._dev = dev.name; ch._devId = dev.id; return ch; }); }) .catch(function() { return []; }); }); @@ -49,6 +49,7 @@ function loadAll() { wall.style.gridTemplateColumns = "repeat(" + cols + ",minmax(0,1fr))"; var html = ""; all.forEach(function(ch, i) { + var proxyUrl = '/hls/' + ch._devId + '/hls/' + ch.name + '/index.m3u8'; html += '
'; html += '
'; html += '' + esc(ch._dev) + ' ยท ' + esc(ch.name) + ''; @@ -63,12 +64,13 @@ function loadAll() { wall.innerHTML = html; // Initialize HLS players all.forEach(function(ch, i) { - if (!ch.hls_url) return; + if (!ch.hls_url || !ch._devId) return; + var proxyUrl = '/hls/' + ch._devId + '/hls/' + ch.name + '/index.m3u8'; var video = document.getElementById('v' + i); if (!video) return; if (Hls.isSupported()) { var hls = new Hls(); - hls.loadSource(ch.hls_url); + hls.loadSource(proxyUrl); hls.attachMedia(video); } else if (video.canPlayType('application/vnd.apple.mpegurl')) { video.src = ch.hls_url;