chore: v0.3.1 — go2rtc auto-start, live wall snapshots, MSE focus player

This commit is contained in:
Claus Lohmar 2026-08-05 12:13:33 +01:00
parent 9de8536bdf
commit 1a017999fe
2 changed files with 29 additions and 5 deletions

17
main.go
View file

@ -45,9 +45,20 @@ func main() {
sigCh := make(chan os.Signal, 1) sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
// Initialize application.
app := &App{Config: cfg, ConfigPath: configPath}
// Start go2rtc if enabled.
if cfg.Go2RTC.Enabled {
go2rtc := NewGo2RTCManager(cfg.Go2RTC)
if err := go2rtc.Start(cfg.Cameras); err != nil {
log.Printf("go2rtc: start failed: %v", err)
}
app.go2rtc = go2rtc
}
// Start the HTTP server (blocks). // Start the HTTP server (blocks).
errCh := make(chan error, 1) errCh := make(chan error, 1)
app := &App{Config: cfg, ConfigPath: configPath}
go func() { go func() {
errCh <- app.StartServer() errCh <- app.StartServer()
}() }()
@ -92,6 +103,7 @@ type App struct {
Config Config Config Config
ConfigPath string ConfigPath string
server *Server server *Server
go2rtc *Go2RTCManager
} }
// StartServer initializes and starts the HTTP server. // StartServer initializes and starts the HTTP server.
@ -106,6 +118,9 @@ func (a *App) StartServer() error {
// Shutdown performs a graceful shutdown of all services. // Shutdown performs a graceful shutdown of all services.
func (a *App) Shutdown() { func (a *App) Shutdown() {
if a.go2rtc != nil {
a.go2rtc.Stop()
}
if a.server != nil { if a.server != nil {
a.server.Close() a.server.Close()
} }

View file

@ -70,11 +70,20 @@ function renderLiveGrid() {
tile.className = 'grid-tile' + (cam && cam.enabled ? '' : ' offline'); tile.className = 'grid-tile' + (cam && cam.enabled ? '' : ' offline');
tile.dataset.camId = cam ? cam.id : ''; tile.dataset.camId = cam ? cam.id : '';
if (cam) { if (cam && cam.enabled) {
// go2rtc snapshot endpoint — refresh every 2 seconds for live preview.
const snapshotURL = `http://${location.hostname}:1984/api/frame.jpeg?src=${cam.id}_sub`;
tile.innerHTML = ` tile.innerHTML = `
<img src="${snapshotURL}" class="grid-snap" onerror="this.style.display='none'"
style="width:100%;height:100%;object-fit:cover;position:absolute;inset:0" alt="${cam.name}">
<span class="tile-status ${cam.online ? 'online' : 'offline'}"></span> <span class="tile-status ${cam.online ? 'online' : 'offline'}"></span>
<span class="tile-label">${cam.name}</span> <span class="tile-label">${cam.name}</span>
`; `;
// Auto-refresh snapshot.
setInterval(() => {
const img = tile.querySelector('img');
if (img) img.src = snapshotURL + '&t=' + Date.now();
}, 2000);
tile.addEventListener('click', () => openFocus(cam)); tile.addEventListener('click', () => openFocus(cam));
} else { } else {
tile.innerHTML = '<span class="tile-placeholder">No Camera</span>'; tile.innerHTML = '<span class="tile-placeholder">No Camera</span>';
@ -97,10 +106,10 @@ 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';
// TODO: M3 — connect to go2rtc WebRTC stream // go2rtc MSE stream for full-quality playback.
const vid = document.getElementById('focus-video'); const vid = document.getElementById('focus-video');
vid.src = ''; vid.src = `http://${location.hostname}:1984/api/stream.m3u8?src=${cam.id}_main`;
vid.poster = ''; // placeholder until go2rtc is wired up vid.play().catch(() => {});
} }
function renderThumbs() { function renderThumbs() {
const strip = document.getElementById('focus-thumbs'); const strip = document.getElementById('focus-thumbs');