For continuous-TS delivery (/play/<token>/ts) the client's whole playback buffer
is the initial prebuffer burst — the feed loop then runs in real time, so the
buffer never grows past it. On a cold on-demand start the server began serving
the instant the playlist first appeared, when only the 1-2 fast-start (2s)
segments existed, so selectSegments could only return ~2s and the client was
stranded at ~2s of buffer regardless of client_prebuffer.
Wait until the playlist actually holds client_prebuffer seconds (bounded by
on_demand_wait_time, bailing if the stream stops) before the first read. Clients
only — restreamer chains keep their low-latency behaviour; warm streams already
satisfy the condition and don't wait. Adds SegmentReader::playlistBufferedSeconds()
(sum of #EXTINF) as the gate.
Validated live on stream 567: at cold start OLD served 1 seg / 2.0s; NEW served
5 seg / 10.0s after a 0.2s wait (fast-start pre-generates segments, so the added
startup latency is negligible).
Verified: php -l, phpunit (SegmentReaderTest 10/10), make gates.
An on-demand stream emits short (2s) fast-start segments before it ramps up to
seg_time. getPlaylistSegments picked intval(client_prebuffer / seg_time)
segments -- with the default 10s prebuffer / 10s nominal seg_time that is 1
segment, i.e. only ~2s of actual buffer at cold start, which players surfaced as
"caches 2 seconds". Persistent streams (segments already ~10s) were unaffected:
1 segment = 10s.
Select the newest segments whose #EXTINF durations sum to at least the requested
prebuffer seconds instead, so client_prebuffer means real seconds regardless of
segment length. Fast-start (hls_init_time 2) is kept. Falls back to the old
nominal count when the playlist has no #EXTINF lines; the -1 (all) and 0 (index)
modes are unchanged. Split into a pure selectSegments()/parseSegmentDurations()
so the selection is unit tested.
Verified: phpstan level 5 green, phpunit 405/405, make gates green.