Skip to content

v1.3.0 — Playback performance overhaul

Choose a tag to compare

@vitobotta vitobotta released this 02 Jul 19:23
af1105a

Major playback performance improvements

This release eliminates the frequent buffering that occurred during playback by adding a direct play path, prioritising compatible stream formats, and fixing false stall detection.

Direct play (bypass ffmpeg entirely)

For H.264/AAC MP4 content, the browser now downloads directly from the RealDebrid CDN via a transparent proxy (DirectStreamController) at network speed — no ffmpeg involved. The browser builds its own deep buffer and seeks via native Range requests, matching Stremio-level performance. This is the single biggest improvement: previously every stream went through an ffmpeg transcode proxy, even when the source was already browser-compatible.

Stream compatibility sorting

Stream listings now detect video codec, audio codec, and container from release names and sort compatible formats first:

  • Direct play ready (MP4 + H.264 + AAC) — sorted highest
  • Stream copy (MP4 + H.264, any audio) — ffmpeg copies video, light load
  • Heavy transcode (HEVC/AV1/VP9) — needs decode + re-encode, sorted last

This means the auto-selected stream and the top of the stream list favour formats that play without ffmpeg bottlenecks.

Buffering stability fixes

  • Rebuffer threshold raised from 5s → 30s: after a stall, the player now rebuilds 30 seconds of buffer before resuming, absorbing transient upstream dips instead of immediately re-stalling
  • False "Buffering..." overlay fix: the browser fires waiting not only when the buffer is empty, but also during transient decoder pipeline flushes (e.g. after sourceBuffer.remove() from buffer eviction). The player now checks whether buffered data exists before showing the overlay, eliminating false stall-recovery cycles
  • O(n²) → O(n) buffer management: the fMP4 box accumulation buffer was reallocating and copying the entire buffer on every incoming chunk (~128 MB of copying per 2 MB of video). Replaced with a pre-allocated doubling buffer
  • Correct buffer-ahead calculation: maybeStartPlayback now uses the buffered range containing currentTime instead of the last range's end, preventing premature playback starts with disjoint buffered ranges

ffmpeg performance

  • Hardware HEVC decode (-hwaccel videotoolbox) on macOS — offloads 4K HEVC decode to the media engine instead of software decode (was the #1 CPU bottleneck)
  • Multi-threaded encoding (-threads auto) for libx264 on Linux — uses all CPU cores instead of defaulting to one
  • Configurable hardware acceleration via FFMPEG_HWACCEL and FFMPEG_ENCODER env vars for production Docker deployments with GPUs (VAAPI, NVENC)
  • Larger read chunks (32 KB → 128 KB) reducing syscall overhead
  • Probe cache expanded (100 → 500 entries, 5min → 10min TTL) with thread-safe Mutex

Observability

  • New POST /streaming/stall_telemetry endpoint logs client-side stall events (position, buffer-ahead, mode, recovery count) — grep [StallTelemetry] in production logs to diagnose buffering

Infrastructure fixes

  • Tailscale/Comet host validation: user-configured provider hosts (e.g. Comet on a Tailscale 100.x.x.x IP) now bypass the private-address DNS check that was returning 400 Bad Request for all Comet streams

Comet fixes (contributed by @hazrid93 in #7)

Two bugs in CometService that together made Comet completely unusable in StreamVault:

  1. Padded base64 for debrid config: CometService encoded the Stremio debrid config with unpadded base64, which Comet's config-path parser does not accept. Comet silently failed to decode the debrid config and returned a single placeholder stream instead of real results. Fixed by dropping padding: false.

  2. Parse Comet's real metadata: CometService#parse_streams was reading Torrentio-shaped fields (title, infoHash, sources, seeders) that Comet does not send. Comet puts metadata in description and behaviorHints. Fixed to read from Comet's actual fields — streams now show real release names, file sizes, info hashes, and seeder counts instead of identical placeholder rows.


Full changelog: v1.2.0...v1.3.0