Commit Graph

229 Commits

Author SHA1 Message Date
Jonathan Gamble
410e17ab29 LibMedia: Fix signed PCM to float sample normalization 2026-02-25 01:50:00 -06:00
Zaggy1024
4778c6a53b LibMedia: Deal with aborts in FFmpegDemuxer instead of the IO context
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.
2026-02-24 16:55:40 -06:00
Zaggy1024
6859c708c1 LibMedia: Add a formatter for DecoderErrorCategory
...and use it in the DecoderError formatter, as the category is often
more relevant than the error message.
2026-02-24 16:55:40 -06:00
Zaggy1024
470248e00d LibMedia+LibWeb: Stop ref counting PlaybackManager
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.
2026-02-23 08:49:13 +00:00
Zaggy1024
7405376938 LibMedia: Prevent conflicts between AudioMixingSink resume and set_time
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.
2026-02-23 07:27:31 +01:00
Zaggy1024
af2ac67be3 LibMedia: Don't lock the main loop while initializing a media source
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.
2026-02-18 13:13:32 -06:00
Zaggy1024
7dd0c70ee5 LibMedia: Avoid a ref count of the stream when adding a media source 2026-02-18 13:13:32 -06:00
Zaggy1024
b223b3edf0 LibMedia: Allow clearing incremental streams' data request callbacks 2026-02-18 13:13:32 -06:00
Zaggy1024
af45418fbf Everywhere: Rename IncrementallyPopulatedStream::reached_end_of_body
This needs to be called even if we haven't reached the end of the body,
so let's call it close() instead.
2026-02-18 13:13:32 -06:00
Timothy Flynn
ea32502947 Everywhere: Run clang-format
The following command was used to clang-format these files:

    clang-format-21 -i $(find . \
        -not \( -path "./\.*" -prune \) \
        -not \( -path "./Build/*" -prune \) \
        -not \( -path "./Toolchain/*" -prune \) \
        -type f -name "*.cpp" -o -name "*.mm" -o -name "*.h")
2026-02-18 08:02:45 -05:00
Ben Wiederhake
dab83d35d1 AK: Remove unused include from ByteString 2026-02-17 12:38:51 +00:00
Jonathan Gamble
0dea87110e LibMedia: Fix multi-channel decode for dumb containers (like WAV)
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.
2026-02-13 17:57:19 -06:00
Zaggy1024
786bc63aaa LibMedia: Don't try to decode after destroying suspended data providers
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.
2026-02-09 09:04:21 +01:00
Zaggy1024
a9591a1d5c LibMedia: Assert that we have a decoder where needed in data providers
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`.
2026-02-09 09:04:21 +01:00
Zaggy1024
0c6de059d9 LibMedia: Clear Matroska::SampleIterator's last timestamp at EOS
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.
2026-02-06 13:28:09 +01:00
Zaggy1024
9fafd32bb7 LibMedia: Silence the EOS error when seeking in Matroska::Reader
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.
2026-02-06 13:28:09 +01:00
Zaggy1024
a05da1b7d0 LibMedia: Make Matroska::SampleIterator movable again
It seems that at some point it became non-movable, causing some
warnings where we were trying to avoid unnecessary copies.
2026-02-06 13:28:09 +01:00
Zaggy1024
876f31dcef LibMedia: Check the data providers' exit state in wait loops
Without these, we may not exit the thread if we enter these loops while
exiting.
2026-01-30 17:27:44 -06:00
Zaggy1024
847edc405e LibMedia: Avoid a recursive lock to check exit state in data providers 2026-01-30 17:27:44 -06:00
Zaggy1024
27d413c193 LibMedia: Dispose FFmpegDemuxer's tracks' format contexts
This fixes a leak that was oddly enough only just detected in CI.
2026-01-30 16:40:21 -06:00
Zaggy1024
972438c4d7 LibMedia: Abstract the interface of IncrementallyPopulatedStream
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.
2026-01-30 10:02:00 -06:00
Zaggy1024
75231e63b1 LibMedia: Only pass Demuxer to the data providers
...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.
2026-01-30 10:02:00 -06:00
Zaggy1024
ba8676fbd5 LibMedia: Mark MatroskaDemuxer override methods virtual 2026-01-30 10:02:00 -06:00
Zaggy1024
31cc0c0a45 LibMedia: Save info from FFmpeg's AVFormatContext and destroy it
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.
2026-01-30 10:02:00 -06:00
Zaggy1024
17726f76e5 LibMedia: Skip DisplayingVideoSink::update() if the provider is null
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.
2026-01-30 16:58:38 +01:00
Zaggy1024
4482f91827 LibMedia: Wake IncrementallyPopulatedStream upon adding a new chunk
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.
2026-01-29 18:06:02 -06:00
Zaggy1024
1b06792e8f LibMedia+LibWeb: Use range requests to fulfill media data
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.
2026-01-29 05:22:27 -06:00
Zaggy1024
f06105bf3d LibMedia+Meta: Add IncrementallyPopulatedStream::create_from_data
Data will be copied when added to the stream anyway, so callers should
be able to pass ReadonlyBytes instead of ByteBuffer&&.
2026-01-29 05:22:27 -06:00
Zaggy1024
a5153d05e7 LibMedia: Avoid unnecessary stream RefPtr copies in Matroska::Reader 2026-01-29 05:22:27 -06:00
Zaggy1024
ee95de40d6 LibMedia: Validate fixed-size Matroska frames
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.
2026-01-28 16:12:22 -06:00
Zaggy1024
f0d7d1d5f5 LibMedia: Track Matroska master element ends with position()
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.
2026-01-28 14:48:03 -06:00
Zaggy1024
5687adffb3 LibMedia: Avoid reentrant locks in data providers' suspension methods
This was causing a freeze when IncrementallyPopulatedStream was
blocking on unavailable data.
2026-01-27 13:23:57 -06:00
Tim Ledbetter
1aa68689ff LibMedia: Remove default_code_points_if_unspecified()
This function is no longer needed as CICP values are now defaulted
downstream.
2026-01-27 11:34:24 -06:00
Tim Ledbetter
0d08d143ff LibMedia: Treat reserved CICP values as unspecified
This aligns with other engines, which treat reserved or invalid CICP
values equivalently to unspecified and default to BT.709.
2026-01-27 11:34:24 -06:00
Zaggy1024
e2635af2ed Everywhere: Move the thread name parameter for Thread constructors
The name parameter formats very poorly when a lambda is passed to
Thread, so let's instead put it first now that all Threads are named.
2026-01-26 15:51:46 -06:00
Zaggy1024
d2a1d727ac Everywhere: Give unnamed threads names 2026-01-26 15:51:46 -06:00
Zaggy1024
2867f87592 Everywhere: Shorten existing thread names to 15 characters or less
pthread_setname_np only accepts 16-byte null-terminated strings, so any
names longer than this will need to be truncated.
2026-01-26 15:51:46 -06:00
Zaggy1024
c4e95079e1 LibMedia: Destroy decoders while data providers are suspended
This doesn't appear to result in much of a delay in resumption,
creating the decoder takes about 1ms.
2026-01-26 15:49:07 -06:00
Zaggy1024
f9081fbde6 LibMedia: Dispose future media data and flush decoders when idle
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.
2026-01-26 15:49:07 -06:00
Federico Tedin
f256597d55 LibMedia: Avoid crashing when PlaybackStream can't be created 2026-01-25 18:14:15 -06:00
Zaggy1024
e6dbcccb99 LibGfx+LibMedia: Send video frames to Skia as subsampled YUV
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.
2026-01-22 19:44:36 +01:00
Zaggy1024
35fc061795 LibMedia: Return the original sample iterator in Matroska slow seeks
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.
2026-01-21 11:35:33 +01:00
Jonathan Gamble
d821508775 LibMedia: Fix byte/frame mismatch in conversion to buffer size
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.
2026-01-17 02:20:20 -06:00
Zaggy1024
9a421ffe9f LibMedia: Make demuxers thread-safe and remove MutexedDemuxer 2026-01-07 00:13:32 +01:00
Aliaksandr Kalenik
4261d73781 LibMedia: Remove m_stream_cursor from Matroska::Reader
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.
2026-01-07 00:13:32 +01:00
Zaggy1024
b77980b4cc LibMedia: Move Matroska's get_frames method to SampleIterator
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.
2026-01-07 00:13:32 +01:00
Zaggy1024
5da08f59b0 LibMedia: Remove a function declaration from Matroska::SampleIterator
This was unused.
2026-01-07 00:13:32 +01:00
Zaggy1024
0170111bb5 LibMedia: Stop lazily parsing data in Matroska::Reader
This wasn't really gaining us anything in practice, and it makes it
harder to make sample iterators thread-safe.
2026-01-07 00:13:32 +01:00
Zaggy1024
40c170dac3 LibMedia: Handle nested Matroska Segment elements
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.
2026-01-07 00:13:32 +01:00
Zaggy1024
8360f2e6f8 LibMedia: Don't error if the Matroska Cues element is not found
We can handle seeking without cues, so there's no reason to fail here.
2026-01-07 00:13:32 +01:00