We can't control whether the instantiation mutex is held when
~Weakable() is called, so we need to implement this via a static raw
pointer instead to ensure that all operations on it are effectively
atomic.
This is important for seeking Opus tracks, as Opus needs to decode a
certain amount of input data before its output converges. Without this,
audio after a seek can sound muffled briefly.
We don't need to put these in a union, it's not gonna save us much
space. The reader also may find both audio and video elements, in which
case this code would produce undefined behavior.
Also, with this change, we can use the default values from the spec.
This prevents PlaybackManager's seek while enabling an audio track from
causing the AudioMixingSink to push audio blocks forward unnecessarily.
Previously, the seek would cause the initial block or blocks to repeat
from the perspective of AudioMixingSink, so it would think that it
needs to shift the first block after the seek forward by a few samples.
By moving this to the AudioDataProvider, we can clear the last sample
index every time the decoder is flushed, ensuring that the block
shifting always makes sense.
By doing this in AudioMixingSink instead of the Decoder
implementations, we avoid having to duplicate this shifting logic
across multiple implementations.
This also fixes an issue where multiple audio blocks occupying the same
timestamp would be skipped while seeking, causing a significant break
in audio.
Otherwise, if the sample iterator resides in a block with multiple
frames before the seek, the demuxer will output all the remaining
frames from that block before moving on to the block at the seeked
position.
This lets the FFmpeg H.264 decoder know when we've reached the end of
the available data, so that it will output the remaining buffered video
frames.
Not sure why it needs this signal to output the buffered frames, but
surely there's a good reason. :^)
This allows us to read WebM files that have blocks with additional data
attached to each block for an alpha frame. For now, only the main coded
data is used, the additional data containing the alpha channel is
ignored.
We already take locks for the important parts of the data providers'
handle_seek methods, but holding the lock for the entire duration of
the method call prevents cancelling a seek. With this change, locks are
only only held in the thread's loops when updating the loop conditions
and waiting, allowing the seek ID to increase during a seek.
When accurate seeking, we can assume that the chosen timestamp will be
the seek's target timestamp. Therefore, we can run the audio seeks
simultaneously with the video seeks to allow them to finish (probably)
before the video seeks themselves complete, making seeking slightly
more responsive.
With the previous setup setting the time directly on the main thread,
the following could occur:
- HTMLMediaElement temporarily pauses playback to begin a seek.
- AudioMixingSink starts an audio task to drain audio and suspend.
- HTMLMediaElement starts PlaybackManager seeking to a new position.
- AudioDataProvider completes the seek to the new position.
- The PlaybackManager tells AudioMixingSink to set the media time,
which it does so synchronously on the main thread.
- At this point, the time provider corresponds to the new position.
- The pause completes, and a deferred invocation sets media time to
its old position again.
This would result in the timeline showing the wrong position after a
seek on rare occasions.
Instead, always queue up a drain and suspend when setting the sink's
media time. This ensures that updating the time always occurs after the
pause has completed.
Also, since setting the time is asynchronous now, we need to store the
target time until the seeking drain completes. Otherwise, we still
briefly see the previous playback position after a seek.
This implementation allows:
- Accurate seeking to an exact timestamp
- Seeking to the keyframe before a timestamp
- Seeking to the keyframe after a timestamp
These three options will be used to satisfy the playback position
selection in the media element's seeking steps.
By default, MatroskaDemuxer chooses not to seek if the current frame
is closer to the seek target than the keyframe that precedes the seek
target. However, it can be desirable to seek to a keyframe anyway, so
let's allow that.
Reader::seek_to_random_access_point() isn't actually guaranteed to
return a sample iterator that has already gotten a block timestamp.
This verify passes in almost every case, but if we happen to seek to a
timestamp before the second keyframe, we'd crash.