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

@ -305,6 +305,80 @@ func TestStreamServer_SetFile_SwapsProvider(t *testing.T) {
}
}
// TestStreamServer_Health_NoFile verifica que /health devuelve streaming:false
// cuando no hay archivo configurado.
func TestStreamServer_Health_NoFile(t *testing.T) {
srv := NewStreamServer(0)
ctx := context.Background()
if err := srv.Listen(ctx); err != nil {
t.Fatalf("Listen() error: %v", err)
}
defer srv.Shutdown(ctx)
healthURL := fmt.Sprintf("http://127.0.0.1:%d/health", srv.Port())
resp, err := http.Get(healthURL)
if err != nil {
t.Fatalf("GET /health: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Errorf("status = %d, want 200", resp.StatusCode)
}
ct := resp.Header.Get("Content-Type")
if !strings.Contains(ct, "application/json") {
t.Errorf("Content-Type = %q, want application/json", ct)
}
body, _ := io.ReadAll(resp.Body)
bodyStr := string(body)
if !strings.Contains(bodyStr, `"streaming":false`) {
t.Errorf("body = %q, want streaming:false", bodyStr)
}
if !strings.Contains(bodyStr, `"status":"ok"`) {
t.Errorf("body = %q, want status:ok", bodyStr)
}
}
// TestStreamServer_Health_WithFile verifica que /health devuelve streaming:true
// y el nombre del archivo cuando hay un archivo configurado.
func TestStreamServer_Health_WithFile(t *testing.T) {
srv := NewStreamServer(0)
ctx := context.Background()
if err := srv.Listen(ctx); err != nil {
t.Fatalf("Listen() error: %v", err)
}
defer srv.Shutdown(ctx)
provider := newFakeProvider("pelicula.mkv", []byte("contenido de prueba"))
srv.SetFile(provider, "task-health-test")
healthURL := fmt.Sprintf("http://127.0.0.1:%d/health", srv.Port())
resp, err := http.Get(healthURL)
if err != nil {
t.Fatalf("GET /health: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Errorf("status = %d, want 200", resp.StatusCode)
}
body, _ := io.ReadAll(resp.Body)
bodyStr := string(body)
if !strings.Contains(bodyStr, `"streaming":true`) {
t.Errorf("body = %q, want streaming:true", bodyStr)
}
if !strings.Contains(bodyStr, "pelicula.mkv") {
t.Errorf("body = %q, want file name pelicula.mkv", bodyStr)
}
if !strings.Contains(bodyStr, "task-hea") { // primeros 8 chars de "task-health-test"
t.Errorf("body = %q, want task short ID", bodyStr)
}
}
// TestStreamServer_MKV_ContentType verifica que el Content-Type para .mkv
// es el correcto.
func TestStreamServer_MKV_ContentType(t *testing.T) {