This was unintentionally clobbering the prior modification to the
request position. Requesting data slightly earlier in the file is
intended to ensure that we have data if the demuxer decides to seek
backwards.
Returning early if an added chunk didn't overlap the previous one could
result in overlapping chunks or 0-byte gaps if a chunk was after but
within the new one. Instead, fall through to allow the merging to
continue as normal.
Also, make the joining condition check if the new chunk spans multiple
existing chunks to merge/remove those.
This is necessary to allow the main thread to read out frames to
determine the buffered ranges, without affecting the request position
of an IncrementallyPopulatedStream or causing a deadlock waiting for
data to come in.
We would ignore whether the callback/event loop had been set up for the
stream. Tests appending enough data for it to start a new request after
the new data would cause a crash.
This commit splits out synchronization primitives from LibThreading into
LibSync. This is because LibThreading depends on LibCore, while LibCore
needs the synchronization primitives from LibThreading. This worked
while they were header only, but when I tried to add an implementation
file it ran into the circular dependency. To abstract away the pthread
implementation using cpp files is necessary so the synchronization
primitives were moved to a separate library.
The way that other classes interact with IncrementallyPopulatedStream
is now through a virtual interface MediaStream and MediaStreamCursor.
This way, we can have simpler implementations of reading media data
that will not require an RB tree and synchronization.
In the early exit for adding a new chunk to m_chunks when no chunk
exists before this one, we were not previously waking any cursors that
may be waiting for this data. This would not generally be an issue when
data is coming in in small chunks, since a second chunk will cause it
to wake, but a test case which only appended one chunk exposed this
bug.
This makes media playback able to start without having to wait for data
to sequentially download, especially when seeking the media to a
timestamp residing in data that hasn't loaded yet.
Initially, the HTMLMediaElement will request the file without range a
range request. Then, if the IncrementallyPopulatedStream finds that it
needs data that is not yet available, it will decide whether to wait
for that data to be received through the current request, or start a
new request that is closer to the required data.
In this commit, it assumes that the server will support range requests.
Store the response's `Content-Length` (when available) as an "expected
size" on `IncrementallyPopulatedStream`.
This allows `IncrementallyPopulatedStream::size()` to return a
meaningful total length early, instead of blocking until EOF. That's
important for the FFmpeg MP4 demuxer, which queries the stream size
immediately after initialization.
Additionally, use the expected size to pre-reserve the underlying
`ByteBuffer` capacity, avoiding repeated reallocations as chunks are
appended.
`IncrementallyPopulatedStream::Cursor` now tracks whether it's currently
blocked inside a wait for more bytes, allowing higher layers to
distinguish "no frames yet" from "decoder is idle".
Enter buffering when `DisplayingVideoSink` runs out of frames and the
associated `VideoDataProvider` is blocked waiting for data to arrive.
Exit buffering once decoding refills the frame queue.
For now, buffering behaves like paused, but it gives us an explicit
state to hook UI into.
When a seek is requested while a previous seek is still blocked waiting
for not yet available bytes, we want to abandon the old request
immediately and start processing the new one.
...a shared byte stream that can be appended to as network data arrives.
The stream supports creating multiple independent `Cursor`s, each with
its own read position. This matches our demuxing needs, where different
audio/video tracks may read from the same underlying data concurrently.
`Cursor::read_into()` and `Cursor::seek()` block until the requested
range is available (or the stream is closed). This is intentional: the
FFmpeg `avio_alloc_context()` read callback cannot reliably surface
`EAGAIN` without putting the demuxer into an error state that
requires recovery via seeking, so we instead wait until we can satisfy
the read.