- Timeshift TS seeking returned the wrong bytes: the start file was
estimated from the average file size, files before it were never skipped
(an empty `if` where a `continue` belonged) and the in-file offset came
out negative, so every catch-up seek streamed from the archive's first
byte; the range end was ignored too. The served range is now mapped
exactly onto the minute files (first file from its .offset).
- The timeshift throttle never reset its chunk counter (vod.php's copy
did), so past vod_limit_perc every chunk paused a whole second.
- A shared HttpRange parser (RFC 7233 single ranges) replaces the inline
copies: suffix ranges (bytes=-N) died on PHP 8 arithmetic, an
unsatisfiable range answered with the resource's range instead of
`bytes */size`, and Accept-Ranges said "0-<len>" instead of "bytes".
VOD never reads past a bounded range's end.
- Direct-proxy VOD reads the source's headers with cURL: get_headers()
goes through the https stream wrapper, which does not work under
PHP-FPM here, and returned Content-Length as an array after a redirect.
The upstream is asked for exactly the requested range, and curl's
verbose output no longer goes to the FPM log on every request.
- The limiter spares the requesting connection by uuid (VOD/timeshift).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>