libavcodec apparently holds onto any error that is not AVERROR_EOF when
a read fails. This means that reading until EOF after an aborted read
results in us receiving an AVERROR_EXIT in FFmpegDemuxer instead of
AVERROR_EOF, which causes the playback system to enter an error state
without decoding all frames in the file.
Instead, just always return AVERROR_EOF, and check if the read was
aborted in FFmpegDemuxer instead to return the correct error category
from there.
PlaybackManager's ref counting was only used to keep it alive in a few
callbacks. Instead, the callbacks can use weak references that can only
be used from the thread that the PlaybackManager was created on, to
ensure that the PlaybackManager can't be destroyed while being
accessed.
This ensures that:
- The PlaybackManager is destroyed immediately when it is reassigned
by HTMLMediaElement
- No callbacks are invoked after that point
This fixes the crash initially being addressed by #8081. The test from
that PR has been included as a regression test.
If a set_time()'s callbacks were still pending, but a resume() went
through, the audio timing could get a bit confused. Instead, check if
m_temporary_time is set before resuming, since that would indicate that
the set_time() callbacks will call resume() when the time is stable
again.
Also, multiple set_time() calls will now only start one set of tasks,
and m_temporary_time is used to store the time passed to the last call.
Thus, set_time() doesn't conflict with itself, but no calls to
set_time() are ignored still.
We only need to take a strong reference to the main event loop when
an error occurred in order to invoke the callback on the main thread.
By taking this lock for the entire duration of the thread, we were
preventing the main thread from exiting if the init thread hangs.
For web audio, I reckon an occasional misjudged channel layout is
better than more frequent exceptions.
Signed PCM is normalized with unsigned max divided by 2, not
signed max. If you divide by the signed max (32767), you get headroom
that can exceed the threshold below -1.0. It's not audible, this mostly
matters for tests that assume correct normalization. But it turns out
there's no shortage of "golden ears" jackholes out there who swear they
can hear the difference.
If a data provider enters the suspended state, then is requested to
exit its thread, it could exit handle_suspension() without creating a
decoder. After this, we weren't checking if we should continue handling
seeks/data decoding. Thus, we got to push_data_and_decode_some_frames()
without a decoder and crashed.
Instead, always skip an iteration of the thread loop when the suspend
finishes, so that the loop has a change to exit.
This makes things a little clearer when we might crash due to the
decoder being null. Instead of a VERIFY being hit in OwnPtr.h with no
line numbers on macOS, we'll get `VERIFICATION FAILED: m_decoder`.
If we don't clear this, then a seek to a timestamp after the
presentation timestamp of the last frame will not result in us decoding
and displaying that last frame again.
If we encountered EOS while seeking forward, instead of returning the
last keyframe, we would return an error. This prevents us from decoding
the last frame if we're seeking to a timestamp after its presentation
timestamp.
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.
...and abstract away the stream/cursor blocking/aborting functionality
so that demuxers can implement or ignore those methods as they see fit.
This is a step towards implementing a wrapper demuxer for MSE streams.
It's not necessary to keep around an instance of AVFormatContext in
FFmpegDemuxer, so instead just copy out the info we need for our
implementation and then destroy it so that our stream cursor is freed.
We could hit a VERIFY in RefPtr if there was a seek in flight while the
PlaybackManager was being destroyed, since finishing a seek would run
DisplayingVideoSink::resume_updates() which would then check if there
is a new frame to display.
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.
We were allowing Matroska blocks with fixed-size lacing to contain
frames with non-divisible sizes. This should not be possible, as it
inherently means that trailing bytes will be discarded.
We now have a valid and invalid testcase for fixed-size lacing to
ensure our handling remains correct.
We don't actually need a Vector stack of bytes read for each element
we're reading out of a Matroska file, we already have the C++ stack
in which we can store the start and end of the master elements we're
reading.
This fixes an issue where seeks while parsing master elements would not
increment m_octets_read, so the master element could continue reading
further than intended.
This could cause a BlockGroup followed by a SimpleBlock to read as if
the BlockGroup contained the SimpleBlock, meaning that SampleIterator
would skip the SimpleBlock.
A test is added to ensure this doesn't regress again.
In order to free up memory when a video is paused for an extended
period, we add a new Suspended state to PlaybackManager which tells the
data providers to suspend. The data providers will handle this signal
by disposing of their entire decoded data queue and flushing their
decoder.
When initially creating a PlaybackManager, and when resuming to a
paused state, the delay before suspension will be much lower than when
pausing from any other state. This is intended to prevent media
elements from consuming memory for long when decoding the first frame
for display, as well as to allow the data providers to suspend much
more quickly after a seek while paused.
Currently, resuming playback doesn't display much of a delay on my
MacBook, though that may change once we completely tear down the
decoder in the suspended state. It may also be exacerbated by using
hardware decoders due more complex decoder initialization.
This saves us from having our own color conversion code, which was
taking up a fair amount of time in VideoDataProvider. With this change,
we should be able to play high resolution videos without interruptions
on machines where the CPU can keep up with decoding.
In order to make this change, ImmutableBitmap is now able to be
constructed with YUV data instead of an RBG bitmap. It holds onto a
YUVData instance that stores the buffers of image data, since Skia
itself doesn't take ownership of them.
In order to support greater than 8 bits of color depth, we normalize
the 10- or 12-bit color values into a 16-bit range.
This was incorrectly advancing the passed-in iterator, which could
leave it at the position of a non-keyframe and cause decoding errors.
Instead, store the original iterator non-optionally, and return that
when the it is the best position for the seek target.
Note that `pa_buffer_attr.tlength` is in bytes, not frames (see
`/usr/include/pulse/def.h`). I think the previous code asked for a
buffer that is 1/8 what the latency calculation wants (for float32
stereo), resulting in too frequent write callbacks, higher wakeup/CPU
churn, higher risk of crackles under load.
Pass the Streamer through parsing methods during initialization instead
of storing the cursor as a member. This makes Reader effectively
read-only after construction, improving thread safety since the cursor
is no longer shared state.
This ensures that we're using the reader for the particular thread that
the block was read from, avoiding any race conditions between seeks and
reads across threads.
This is malformed input, but it's useful for testing MSE buffers and
doesn't cause any additional issues.
It won't actually be used for MSE playback, as that will be able to
detect a format change that precedes the new initialization data
containing the new EBML and Segment elements.