unarr/internal/engine/watch_reporter.go
Deivid Soto 0dafeaa70d feat(stream): report watch progress to API via HTTP Range tracking
Track the highest byte offset served by the stream server to estimate
playback progress (0-100%). A WatchReporter goroutine sends progress
to POST /api/internal/agent/watch-progress every 10s during streaming.

- Add maxByteOffset + totalFileSize to StreamServer for Range tracking
- Add FileSize() to fileProvider interface (all 3 providers)
- New WatchReporter: periodic progress reporter tied to daemon context
- New WatchProgressUpdate type with optional progress/position/duration
- Wire reporter into all 3 stream paths (task stream, disk stream, active download stream)
2026-04-01 12:16:45 +02:00

68 lines
1.7 KiB
Go

package engine
import (
"context"
"log"
"time"
"github.com/torrentclaw/unarr/internal/agent"
)
// WatchReporter periodically sends watch progress to the API based on
// HTTP Range request tracking from the StreamServer.
type WatchReporter struct {
client *agent.Client
server *StreamServer
taskID string
lastSentPct int // last progress percentage reported (0-100)
}
// NewWatchReporter creates a reporter that tracks playback progress via Range offsets.
func NewWatchReporter(client *agent.Client, server *StreamServer, taskID string) *WatchReporter {
return &WatchReporter{
client: client,
server: server,
taskID: taskID,
}
}
// Run reports watch progress every 10 seconds until the context is cancelled.
// A final report is sent on shutdown using a short independent timeout.
func (wr *WatchReporter) Run(ctx context.Context) {
ticker := time.NewTicker(10 * time.Second)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
// Final report on shutdown — use background context since parent is cancelled.
finalCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
wr.sendReport(finalCtx)
cancel()
return
case <-ticker.C:
wr.sendReport(ctx)
}
}
}
func (wr *WatchReporter) sendReport(ctx context.Context) {
pct, _ := wr.server.EstimatedProgress()
if pct == 0 || pct == wr.lastSentPct {
return
}
wr.lastSentPct = pct
update := agent.WatchProgressUpdate{
TaskID: wr.taskID,
Source: "range",
Progress: &pct,
}
reportCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
if err := wr.client.ReportWatchProgress(reportCtx, update); err != nil {
log.Printf("[%s] watch-progress: report failed: %v", wr.taskID[:8], err)
}
}