Commit graph

389 commits

Author SHA1 Message Date
Zaggy1024
ae04222247 LibMedia: Don't display video frames that are late
Frames are considered late if the time is ahead by half their duration.
The fudging is necessary because Matroska (and perhaps other formats)
store their frame durations in different time units than their
timestamps.

Skipping these should make it clear when decoding is running behind,
instead of displaying the video in slow motion while audio runs at a
normal rate.

To give an accurate counting for video playback quality when it is
implemented, we'll most likely want to count all pulled frames in an
update as dropped if the last frame is dropped. Otherwise the frame
drop count will only increase at the display rate when decoding is
continually running behind.
2026-06-11 21:49:55 +02:00
Zaggy1024
c58b9be7d5 LibMedia: Stop clearing video sinks' frames early on moving seeks
This is already handled by the seeking state enum later. We could end
up displaying nothing if MovedPosition isn't immediately followed by a
displayable frame.
2026-06-11 21:49:55 +02:00
Zaggy1024
9e2a820884 LibMedia+LibWeb: End media element playback based on the pipeline EOS
Instead of comparing the current time to the duration, the playback
manager now has an explicit Ended state that jumps to the duration. The
element simply reacts to that to trigger the ended event and attribute,
along with all the other steps involved.

This moves the ended event to fire after the seeked event, which
matches other browsers' behavior. The spec doesn't explicitly say which
order they should fire in.
2026-06-11 05:49:14 -05:00
Zaggy1024
47200f9e6d LibMedia: Don't change pipeline state from Error outside seeks 2026-06-11 05:49:14 -05:00
Zaggy1024
5a120bff33 LibMedia: Use a combined status to exit the seeking/buffering states
Instead of tracking in-flight seeks across all the sinks in the seeking
state handler, move the logic to PlaybackManager to determine the
overall status and then notify the state of that status to potentially
trigger resumption.

The buffering state handler can then share essentially the same logic
instead of having the playback manager specifically track the blocked
tracks for it.
2026-06-11 05:49:14 -05:00
Zaggy1024
042b458f75 LibMedia: Stop skipping seeks to the same timestamp in PlaybackManager
Our optimizations in the pipeline to skip video seeks that land in the
already-queued data achieve largely the same result, but with more
correctness.
2026-06-11 05:49:14 -05:00
Zaggy1024
d2c993dec0 LibMedia: Route current time through the playback state handler
This has no effect on the user experience, and likely also produces the
same timestamps during a seek as before. However, it is needed in order
to ensure that the future Ended state is always at the duration.
2026-06-11 05:49:14 -05:00
Zaggy1024
e2e2635752 LibMedia: Make PipelineStatus::Pending take precedence over HaveData
This matches the mixer's behavior, it won't mix data once it reaches
any Pending status on any input.

Doing this will allow seeking to also rely on the combined status
function to determine when to resolve.
2026-06-11 05:49:14 -05:00
Zaggy1024
eeeabcaef1 LibMedia: Remove an unnecessary lock in the audio sink's state dispatch
Both fields used in the deferred invoke are written by the main thread,
we shouldn't need to worry about a race here.
2026-06-11 05:49:14 -05:00
Zaggy1024
d0edc33597 LibMedia: Make video sinks with current frames dispatch HaveData
It's not correct to dispatch Pending if we have a current frame, and
may cause seeks to remain unresolved if they check for the current
status.
2026-06-11 05:49:14 -05:00
Zaggy1024
fa0c02acb1 LibMedia: Ensure that video sink updates only resolve current seeks
The same check as in the wake handler's deferred dispatch.
2026-06-11 05:49:14 -05:00
Zaggy1024
4fcb714327 LibMedia: Only clear decoded data producers' halt status on move
We don't need to proactively reset this anymore with the break out of
halting being determined by seek IDs instead. This will ensure that a
halting status upon a fast-path seek will still remain correct.
2026-06-11 05:49:14 -05:00
Zaggy1024
5f384cbb05 LibMedia: Simplify error wait handling in the decoded data producers
Checking for a seek within the error wait loop wasn't necessary, and
unlocking/relocking could actually lose a wake anyway. Callers always
break out of the decode loop into the thread loop, so this will still
seek immediately as before.
2026-06-11 05:49:14 -05:00
Zaggy1024
c047d62279 LibMedia: Track when demuxers move position even upon errors
Otherwise, we can lose the signal if a seek is aborted after it has
moved the demuxer, making the sink not output until it reaches the time
of the last data it pulled.
2026-06-11 05:49:14 -05:00
Zaggy1024
28f670f09f LibMedia: Implement audio time stretching with WSOLA
The algorithm is ported over from Chromium's, which produces very good
results for speech, while being not objectionable for music, especially
in the background.

Other algorithms were tested.

Phase vocoders:
- Bungee
- Signalsmith Stretch
- pvdoneright
All three of these exhibited the usual phase shifting artifacts,
causing speech to sound slightly shifted into the high end. Speech is
the main thing we want to optimize for, so these aren't ideal.

Sonic (TD-PSOLA) performs better than WSOLA for speech, especially at
rates higher than 2x, but makes background sounds/music garbled and
unpleasant. It is still worth considering for speech clarity, and could
be added as an optional feature.
2026-06-06 19:58:17 -05:00
Zaggy1024
543db2b828 LibMedia: Seek the mixer when toggling audio tracks
This has two benefits:
- Pausing and then disabling a track no longer has lingering audio from
  the disabled track
- The mixer starts mixing from the current time even after it has
  advanced past EOS, so it doesn't have to mix audio that won't play
2026-06-06 19:58:17 -05:00
Zaggy1024
dd72dfadff LibMedia: Pause the audio sink when its rate is set to 0 2026-06-06 19:58:17 -05:00
Zaggy1024
f0ab344a4e LibMedia: Handle outputting silence after EOS in the audio sink
This allows nodes downstream of the mixer to know the exact frame at
which EOS is reached.

More concepts have been introduced into PipelineStatus.h to make each
condition in the pipeline clearer about its intent.
2026-06-06 19:58:17 -05:00
Zaggy1024
fad02e6bab LibMedia: Move AudioPlaybackSink's sample spec into its thread data
This will allow it to be used from the processor thread in subsequent
commits.
2026-06-06 19:58:17 -05:00
Zaggy1024
769dbd6888 LibMedia: Avoid sleeping if the audio processor sees a ready status
This could cause an upstream transition from Pending to HaveData to
never unblock the audio processor thread if that transition happened
between setting the wait flag and re-checking the status for
MovedPosition at the start of the processor loop.

This wasn't observable with the existing pipeline simply because it was
near impossible to run down the upstream data to get a Pending status.
2026-06-06 19:58:17 -05:00
Zaggy1024
30843326f6 LibMedia: Reset downstream sleep status before calling the wake handler
This allows the downstream node to query status and sleep again
synchronously, which is needed for nodes that need multiple input
blocks before they can produce output and wake their downstream nodes.
2026-06-06 19:58:17 -05:00
Zaggy1024
7ddbc2d7dd LibWeb+LibMedia: Wire up setting the pipeline's playback rate
However, the playback rate is still limited to 1.0 at the element,
because audio stretching is not yet implemented.
2026-06-06 19:58:17 -05:00
Zaggy1024
47207b69e6 LibMedia: Remap audio time based on block durations in media time
This will allow the media time to advance faster or slower on a per-
block basis, so that playback rate doesn't lose timing.
2026-06-06 19:58:17 -05:00
Zaggy1024
13d04e44c2 LibMedia: Move AudioBlock's timing info into a separate class
This will be used to account for playback rate in audio timing.
2026-06-06 19:58:17 -05:00
Zaggy1024
ee66ccb4af LibMedia: Add information to AudioBlock to allow time scaling
By decoupling media duration from frame duration, we can allow audio
blocks to make up more or less time independent of the amount of actual
data.
2026-06-06 19:58:17 -05:00
Zaggy1024
9ca1db3bb8 LibMedia: Rename timing getters in AudioBlock
This new naming will make timescale modifications more comprehensible.
2026-06-06 19:58:17 -05:00
Zaggy1024
c8c64e4819 LibMedia: Store raw audio data as planar
This avoids strided loads for vectorized audio data processing loops.
2026-06-06 19:58:17 -05:00
Andreas Kling
aad293dacc LibMedia: Use direct references to main event loops
Pass direct Core::EventLoop references through media producer and
playback plumbing that posts work back to the main thread. The browser
process main loops stay alive for the process lifetime, so these paths
no longer need weak event loop references.

Update LibMedia tests to pass their stack event loop directly to the
producer helpers.
2026-06-05 09:18:39 +02:00
Andreas Kling
164ed80244 Meta: Enable exit-time destructor warnings for libraries
Enable -Wexit-time-destructors for all in-tree library targets and
update process-lifetime library statics so they no longer register
exit-time destructors. Long-lived caches, lookup tables, singleton
registries, and generated constants now use NeverDestroyed or leaked
references where the data is intended to live until process exit.

Update LibWeb, LibLine, and the binding generators so regenerated
sources follow the same rule instead of reintroducing destructed
statics.
2026-06-04 19:20:49 +02:00
Callum Law
3e0ae3b5d2 LibMedia: Make PlaybackManager::weak private
This is only used internally
2026-06-02 22:22:22 -05:00
Zaggy1024
ece7b115f4 LibMedia: Don't synchronously resolve seeks within video sink updates
Fixes an extremely rare flake in the resize-event-during-playback.html
test when seeks are resolved in DisplayingVideoSink::update(). When
they are resolved there, the media element would queue the `seeked`
event before the `resize` event, causing the event ordering in the test
to be incorrect.

This would normally not be hit due to seeks primarily being resolved by
the wake handler of the input.
2026-06-02 04:54:48 -05:00
Zaggy1024
c88b1ca8f7 LibMedia: Reduce the amount of WAV data loaded before playback starts
Apparently FFmpeg probes WAV files for 32 packets before returning the
stream info. With the default of 19200 bytes per packet, this could end
up waiting for up to 5 seconds of data to be downloaded.

Setting the max_size option only affects WAV and W64 in our build of
libavformat. No other formats we care about will be affected.
2026-05-28 10:30:20 -05:00
Zaggy1024
c901896972 LibMedia: Implement buffered range scanning for Ogg
Unfortunately, this necessarily involves parsing codec frames to get
their durations. Ogg's granule positions indicate the last sample of
the last complete frame of the page, so the navigator has to determine
the durations of every packet in the page to offset it back to its
start.

Vorbis is an especially involved codec to determine that for, since it
has initial data defining a mapping of indices to big or small block
sizes that has to be held onto, and that mapping is preceded by a bunch
of conditionally parsed bits. Also, a frame's block size calculation
involves the block size of the previous frame for an overlap add. To
avoid bringing that complexity directly into LibMedia, we delegate the
parsing/calculation to FFmpeg's av_vorbis_parse functions.
2026-05-28 10:30:20 -05:00
Zaggy1024
76a5c2b91e LibMedia: Factor out Opus frame duration parsing from Matroska::Reader
This will be useful for Ogg buffered ranges.
2026-05-28 10:30:20 -05:00
Zaggy1024
16f5a3405f LibMedia: Implement buffered ranges for MP3
A new MP3Navigator class is added, which determines timestamps for byte
positions by resyncing to a frame and then interpolating between known
points on either side. The known points start out as the first frame's
position in the file at timestamp 0, and EOF at the timestamp for
FFmpeg's file duration estimate. New buffered ranges are interpolated
between those two points, but also between the end of a prior range and
the start of the next.

Since MP3 can have variable bitrate without declaring it in the file
header, we have to allow buffered ranges to shift forward as new data
arrives to make room for underestimated durations. This is done for all
ranges following the first that has been appended to, keeping the start
of the current range consistent, so that subsequent seeks within that
range remain consistent.

Seeking is also implemented within the navigator to ensure that the
byte<->timestamp mapping is consistent and the buffered ranges begin
exactly where the seek landed.
2026-05-28 10:30:20 -05:00
Zaggy1024
9f788cb80f LibMedia: Implement CBR seeking through ContainerNavigator
This mainly acts as a proof of concept for manual seeking
implementations, which may prove useful for other formats than WAV.
2026-05-28 10:30:20 -05:00
Zaggy1024
454830c7cc LibMedia: Always clear EOF/error flags in AVIOContext when seeking
We could end up seeked within AVIOContext's internal buffer without it
checking whether new data could be buffered without hitting EOF again,
so we could get an unexpected read error.
2026-05-28 10:30:20 -05:00
Zaggy1024
5bb108dd5c LibMedia: Calculate accurate buffered ranges for FLAC
Add a new FLACNavigator class that can scan for frames both forward and
backward from a byte offset to get the exact buffered ranges. The reads
are done in chunks, which keeps each call pretty cheap.

The ranges themselves are resolved by its ScanningContainerNavigator
base class, which reconciles cached ranges against the incoming ones to
avoid repeating work unnecessarily. With those combined optimizations,
the calls normally take under 1 microsecond.
2026-05-28 10:30:20 -05:00
Zaggy1024
9f1a4d7536 LibMedia: Add some reading helpers to MediaStreamCursor 2026-05-28 10:30:20 -05:00
Zaggy1024
3098c0fb62 LibMedia: Implement buffered ranges for FFmpegDemuxer
The default mode is to grab the index and use it to determine the start
and end of the buffered ranges. This works well for MP4.

For WAV, the index is incomplete, so instead use a much simpler method,
just determining the ranges based on the constant bitrate of the file.

This is implemented through a new virtual ContainerNavigator class.
2026-05-28 10:30:20 -05:00
Zaggy1024
16f1f19aa3 LibMedia: Move the bitwise operators for FrameFlags to Media:: 2026-05-28 10:30:20 -05:00
Zaggy1024
8576527d8a LibMedia: Actually adjust incremental stream's request positions
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.
2026-05-28 10:30:20 -05:00
Zaggy1024
7d3dd2d641 LibMedia: Implement buffered time range scanning for Matroska 2026-05-28 10:30:20 -05:00
Zaggy1024
be089ce24c LibMedia: Make some Matroska::Reader functions const
These don't need to be mutable.

has_cues_for_track() was also removed, as it was unused.
2026-05-28 10:30:20 -05:00
Zaggy1024
52b6b40493 LibMedia: Allow Matroska::SampleIterator to iterate all tracks 2026-05-28 10:30:20 -05:00
Zaggy1024
36ddc5015a LibMedia: Return an error if a Matroska octet couldn't be read 2026-05-28 10:30:20 -05:00
Zaggy1024
fea33aa02f LibMedia+Tests: Add a method to remove data from incremental streams
This will later be used to test updating of buffered time ranges when
data is removed. It will also be needed when data eviction is
implemented.
2026-05-28 10:30:20 -05:00
Zaggy1024
617f50813a LibMedia: Ensure that overlapped incremental stream chunks are merged
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.
2026-05-28 10:30:20 -05:00
Zaggy1024
c4514c27ab LibMedia: Add a method to get the available bytes from a MediaStream
This will be used to determine the buffered time ranges.
2026-05-28 10:30:20 -05:00
Zaggy1024
c7eee655c2 LibMedia: Split out Matroska::SampleIterator and Streamer 2026-05-28 10:30:20 -05:00