Files
Honza fc4ed4fc47
CI / test (push) Successful in 53s
CI / build (amd64) (push) Successful in 2m17s
CI / build (arm64) (push) Successful in 10m39s
CI / manifest (push) Successful in 8s
Fix channel worker stalling when stopping ffmpeg with unread stdout
2026-10-09 08:38:12 +02:00

2.3 KiB

Changelog

All notable changes to this project are documented in this file.

[0.1.0] - Unreleased

Added

  • Initial implementation: yt-dlp based resolver (merging separate video/audio renditions when YouTube serves a live stream split that way), ffmpeg restreaming, per-channel supervisor worker with proactive URL-expiry refresh and reactive restart-with-backoff, broadcast fan-out to multiple HTTP clients, aiohttp server exposing an M3U playlist and per-channel MPEG-TS streams for Jellyfin Live TV.
  • Docker Compose deployment (Dockerfile, docker-compose.yml) and configuration templates (channels.example.yaml, .env.example).
  • Gitea Actions CI (.gitea/workflows/ci.yml): ruff, mypy and pytest, then native amd64 and arm64 image builds on separate runners (no emulation), merged into one multi-arch tag (:devel from devel, :latest from release, plus a commit-SHA tag). Per-architecture tags carry an -amd64 / -arm64 suffix.
  • Unit test suite covering config parsing, URL expiry parsing, ffmpeg argument building, playlist generation, and broadcast fan-out.

Fixed

  • Stopping ffmpeg (on every proactive URL refresh) no longer stalls. The worker stops reading ffmpeg's stdout when it breaks out to refresh, and asyncio's Process.wait() does not return until every pipe reaches EOF, so stop() always ran into the terminate timeout ("did not exit in time, killing it") and could leave a channel worker stuck for good, silently ending that channel's stream. stop() now drains the leftover stdout while waiting for ffmpeg to exit.
  • /stream/{channel_id} now forces the connection closed after each response (Connection: close) instead of allowing keep-alive. Jellyfin's M3U tuner client (SharedHttpStream) was reusing a pooled keep-alive connection still mid-stream from an earlier request and reading raw MPEG-TS bytes as if they were a fresh HTTP response, failing with "Received an invalid status line".
  • The yt-dlp resolver now correctly classifies which of the two requested_formats entries is audio when a live stream is split into separate video-only and audio-only renditions; it previously relied on acodec being set, which yt-dlp does not consistently do for audio-only formats, causing every resolve to fail.