fix(stream): fix black screen on remote/Tailscale streaming

Three root-cause fixes for VLC showing a black screen when opening a
stream from a different network or via Tailscale:

1. PrioritizeTail: when VLC opens an MKV/MP4 stream it immediately seeks
   to the end of the file to read the container index (seekhead/moov
   atom). For active torrents those end-pieces aren't downloaded yet, so
   the reader blocks indefinitely. PrioritizeTail() opens a background
   reader positioned at the last 5 MB, keeping those pieces at high
   priority until ctx is cancelled or they finish downloading.

2. /health endpoint: GET /health returns a lightweight JSON response
   {"status":"ok","streaming":bool,...} so connectivity can be tested
   with a simple curl from any device before involving VLC.

3. Per-request logging: every incoming /stream request now logs the
   client IP and Range header, making it trivial to confirm whether
   remote/Tailscale clients are reaching the server at all.
This commit is contained in:
Deivid Soto 2026-04-09 16:15:41 +02:00
parent 7eaf357680
commit f1b4f2e327
5 changed files with 185 additions and 0 deletions

View file

@ -71,6 +71,7 @@ func NewStreamServer(port int) *StreamServer {
func (ss *StreamServer) Listen(ctx context.Context) error {
mux := http.NewServeMux()
mux.HandleFunc("/stream", ss.handler)
mux.HandleFunc("/health", ss.healthHandler)
// SO_REUSEADDR allows immediate rebind if the port is in TIME_WAIT (e.g. after agent restart)
lc := net.ListenConfig{
@ -234,9 +235,52 @@ func (ss *StreamServer) Shutdown(ctx context.Context) error {
return nil
}
// healthHandler responde con el estado del servidor en JSON.
// Útil para diagnosticar conectividad desde redes remotas o Tailscale:
//
// curl http://<tailscale-ip>:<port>/health
func (ss *StreamServer) healthHandler(w http.ResponseWriter, r *http.Request) {
ss.mu.RLock()
provider := ss.provider
taskID := ss.taskID
ss.mu.RUnlock()
clientIP, _, _ := net.SplitHostPort(r.RemoteAddr)
type healthResponse struct {
Status string `json:"status"`
Streaming bool `json:"streaming"`
File string `json:"file,omitempty"`
Task string `json:"task,omitempty"`
Port int `json:"port"`
Client string `json:"client"`
}
resp := healthResponse{
Status: "ok",
Port: ss.port,
Client: clientIP,
}
if provider != nil {
resp.Streaming = true
resp.File = provider.FileName()
resp.Task = taskID
if len(resp.Task) > 8 {
resp.Task = resp.Task[:8]
}
}
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Cache-Control", "no-cache")
json.NewEncoder(w).Encode(resp) //nolint:errcheck
}
func (ss *StreamServer) handler(w http.ResponseWriter, r *http.Request) {
ss.lastActivity.Store(time.Now().UnixNano())
// Log every incoming request — essential for diagnosing remote/Tailscale issues.
clientIP, _, _ := net.SplitHostPort(r.RemoteAddr)
log.Printf("[stream] %s /stream from %s Range:%q", r.Method, clientIP, r.Header.Get("Range"))
// Get current provider (may be nil if no file is being served)
ss.mu.RLock()
provider := ss.provider