Fix: Transition-Race, Auto-Reload nach Deploy, Playlist-Latenz < 1s
- hideAllContent() prüft opacity bevor display=none gesetzt wird (verhindert Race mit displayItem) - Neuer /api/startup-token Endpoint: Browser erkennt Agent-Neustart und reloaded automatisch - MQTT-Debounce von 3s auf 500ms, Browser-Poll von 30s auf 5s reduziert für sub-sekunden Playlist-Updates Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
585cb83ed0
commit
6931181916
2 changed files with 57 additions and 7 deletions
|
|
@ -12,7 +12,8 @@ import (
|
|||
const (
|
||||
// debounceDuration is the minimum interval between two callback invocations.
|
||||
// Any MQTT message arriving while the timer is still running resets it.
|
||||
debounceDuration = 3 * time.Second
|
||||
// 500ms reicht aus um Bursts zu absorbieren, ohne die Latenz merklich zu erhöhen.
|
||||
debounceDuration = 500 * time.Millisecond
|
||||
|
||||
// playlistChangedTopicTemplate is the topic the backend publishes to.
|
||||
playlistChangedTopic = "signage/screen/%s/playlist-changed"
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
|
|
@ -50,12 +51,14 @@ type SysInfo struct {
|
|||
type Server struct {
|
||||
listenAddr string
|
||||
nowFn func() NowPlaying
|
||||
startupToken string // zufälliger Token der sich bei jedem Start ändert
|
||||
}
|
||||
|
||||
// New creates a Server. listenAddr is e.g. "127.0.0.1:8090".
|
||||
// nowFn is called on each request and returns the current playback state.
|
||||
func New(listenAddr string, nowFn func() NowPlaying) *Server {
|
||||
return &Server{listenAddr: listenAddr, nowFn: nowFn}
|
||||
token := fmt.Sprintf("%016x", rand.Uint64())
|
||||
return &Server{listenAddr: listenAddr, nowFn: nowFn, startupToken: token}
|
||||
}
|
||||
|
||||
// Run starts the HTTP server and blocks until ctx is cancelled.
|
||||
|
|
@ -69,6 +72,7 @@ func (s *Server) Run(ctx context.Context) error {
|
|||
mux.HandleFunc("GET /player", s.handlePlayer)
|
||||
mux.HandleFunc("GET /api/now-playing", s.handleNowPlaying)
|
||||
mux.HandleFunc("GET /api/sysinfo", handleSysInfo)
|
||||
mux.HandleFunc("GET /api/startup-token", s.handleStartupToken)
|
||||
mux.Handle("GET /assets/", http.StripPrefix("/assets/", http.FileServer(http.FS(sub))))
|
||||
|
||||
srv := &http.Server{Handler: mux}
|
||||
|
|
@ -99,6 +103,14 @@ func (s *Server) handleNowPlaying(w http.ResponseWriter, _ *http.Request) {
|
|||
json.NewEncoder(w).Encode(s.nowFn()) //nolint:errcheck
|
||||
}
|
||||
|
||||
// handleStartupToken gibt einen zufälligen Token zurück der sich bei jedem
|
||||
// Agent-Start ändert. Der Browser erkennt daran, dass der Agent neu gestartet
|
||||
// wurde und lädt die Seite automatisch neu.
|
||||
func (s *Server) handleStartupToken(w http.ResponseWriter, _ *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]string{"token": s.startupToken}) //nolint:errcheck
|
||||
}
|
||||
|
||||
func handleSysInfo(w http.ResponseWriter, _ *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(collectSysInfo()) //nolint:errcheck
|
||||
|
|
@ -326,6 +338,11 @@ const playerHTML = `<!DOCTYPE html>
|
|||
// Versteckt alle Content-Elemente vor dem Anzeigen des richtigen Typs.
|
||||
// Blendet zunächst auf opacity:0 aus und entfernt display erst nach der
|
||||
// Transition (500ms), damit der Fade-Out sichtbar ist.
|
||||
//
|
||||
// Race-Condition-Fix: Das setTimeout-Callback prüft vor dem display=none,
|
||||
// ob das Element noch opacity=0 hat. Falls displayItem() das Element
|
||||
// inzwischen wieder auf display=block+opacity=1 gesetzt hat, wird es
|
||||
// nicht fälschlicherweise versteckt.
|
||||
function hideAllContent() {
|
||||
// Laufendes Video sofort stoppen damit kein Audio weiterläuft.
|
||||
videoView.pause();
|
||||
|
|
@ -335,7 +352,14 @@ const playerHTML = `<!DOCTYPE html>
|
|||
if (el.style.display !== 'none') {
|
||||
el.style.opacity = '0';
|
||||
(function(e) {
|
||||
setTimeout(function() { e.style.display = 'none'; }, 500);
|
||||
setTimeout(function() {
|
||||
// Nur verstecken wenn das Element noch ausgeblendet ist
|
||||
// (opacity=0 oder leer). Falls displayItem() es inzwischen
|
||||
// wieder sichtbar gemacht hat, nicht anfassen.
|
||||
if (e.style.opacity === '0' || e.style.opacity === '') {
|
||||
e.style.display = 'none';
|
||||
}
|
||||
}, 500);
|
||||
})(el);
|
||||
}
|
||||
});
|
||||
|
|
@ -533,10 +557,11 @@ const playerHTML = `<!DOCTYPE html>
|
|||
|
||||
function startSlowPoll() {
|
||||
if (slowPollInterval) return;
|
||||
// Playlist alle 5s prüfen (fängt MQTT-getriggerte Backend-Änderungen schnell ab).
|
||||
// Sysinfo läuft separat alle 30s (weiter unten).
|
||||
slowPollInterval = setInterval(function() {
|
||||
pollNowPlaying();
|
||||
pollSysInfo();
|
||||
}, 30000);
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
function pollNowPlaying() {
|
||||
|
|
@ -567,6 +592,30 @@ const playerHTML = `<!DOCTYPE html>
|
|||
startSlowPoll();
|
||||
}
|
||||
}, 60000);
|
||||
|
||||
// ── Auto-Reload bei Agent-Neustart ───────────────────────────────
|
||||
// Der Agent gibt bei jedem Start einen neuen zufälligen Token zurück.
|
||||
// Falls sich der Token ändert, hat der Agent neu gestartet → Seite neu laden.
|
||||
var knownStartupToken = null;
|
||||
|
||||
function pollStartupToken() {
|
||||
fetch('/api/startup-token')
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(d) {
|
||||
if (!d || !d.token) return;
|
||||
if (knownStartupToken === null) {
|
||||
// Erster Aufruf: Token merken, kein Reload.
|
||||
knownStartupToken = d.token;
|
||||
} else if (knownStartupToken !== d.token) {
|
||||
// Token hat sich geändert → Agent wurde neu gestartet.
|
||||
window.location.reload();
|
||||
}
|
||||
})
|
||||
.catch(function() {}); // Agent offline → ignorieren
|
||||
}
|
||||
|
||||
pollStartupToken();
|
||||
setInterval(pollStartupToken, 5000); // alle 5s prüfen
|
||||
</script>
|
||||
</body>
|
||||
</html>`
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue