LibWeb: Paint video frames from YUV data

A video element should record video as video, not as generic external
bitmap content. Add VideoFrameSource and a dedicated display-list
command so the display-list player receives the current
Media::VideoFrame directly.

The Skia player can now upload YUV pixmaps from the frame when a GPU
context is available, without teaching the ordinary ImmutableBitmap
image cache about media formats. If GPU upload is unavailable, the
fallback explicitly converts the frame through YUVData::to_bitmap().

This gives video painting a clear extension point for future frame
backends, such as hardware frames or other planar formats, while
keeping bitmap drawing focused on immutable pixel snapshots.
This commit is contained in:
Aliaksandr Kalenik
2026-05-05 11:44:23 +02:00
committed by Gregory Bertilson
parent febd85f417
commit 9a348c8338
Notes: github-actions[bot] 2026-05-05 19:41:07 +00:00
15 changed files with 180 additions and 29 deletions

View File

@@ -6,7 +6,6 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibGfx/ImmutableBitmap.h>
#include <LibJS/Runtime/Promise.h>
#include <LibMedia/IncrementallyPopulatedStream.h>
#include <LibMedia/PlaybackManager.h>
@@ -1532,11 +1531,11 @@ void HTMLMediaElement::set_audio_track_enabled(Badge<AudioTrack>, GC::Ptr<HTML::
m_playback_manager->disable_an_audio_track(audio_track->track_in_playback_manager());
}
Painting::ExternalContentSource& HTMLMediaElement::ensure_external_content_source()
Painting::VideoFrameSource& HTMLMediaElement::ensure_video_frame_source()
{
if (!m_external_content_source)
m_external_content_source = Painting::ExternalContentSource::create();
return *m_external_content_source;
if (!m_video_frame_source)
m_video_frame_source = Painting::VideoFrameSource::create();
return *m_video_frame_source;
}
void HTMLMediaElement::set_selected_video_track(Badge<VideoTrack>, GC::Ptr<HTML::VideoTrack> video_track)
@@ -1546,8 +1545,8 @@ void HTMLMediaElement::set_selected_video_track(Badge<VideoTrack>, GC::Ptr<HTML:
if (video_track && !m_playback_manager->video_tracks().contains_slow(video_track->track_in_playback_manager()))
return;
if (m_external_content_source)
m_external_content_source->clear();
if (m_video_frame_source)
m_video_frame_source->clear();
auto previous_track = m_selected_video_track;
@@ -1556,13 +1555,10 @@ void HTMLMediaElement::set_selected_video_track(Badge<VideoTrack>, GC::Ptr<HTML:
m_selected_video_track_sink = m_playback_manager->get_or_create_the_displaying_video_sink_for_track(video_track->track_in_playback_manager());
auto sink_update_result = m_selected_video_track_sink->update();
if (sink_update_result == Media::DisplayingVideoSinkUpdateResult::NewFrameAvailable) {
if (auto current_frame = m_selected_video_track_sink->current_frame()) {
auto bitmap_or_error = current_frame->to_immutable_bitmap();
if (bitmap_or_error.is_error())
dbgln("Could not convert video frame to bitmap: {}", bitmap_or_error.release_error());
else
ensure_external_content_source().update(bitmap_or_error.release_value());
}
if (auto current_frame = m_selected_video_track_sink->current_frame())
ensure_video_frame_source().update(move(current_frame));
else if (m_video_frame_source)
m_video_frame_source->clear();
update_intrinsic_video_dimensions();
set_needs_repaint();
} else if (auto* video_element = as_if<HTMLVideoElement>(this)) {
@@ -1585,13 +1581,10 @@ void HTMLMediaElement::update_video_frame_and_timeline()
if (m_selected_video_track_sink) {
auto sink_update_result = m_selected_video_track_sink->update();
if (sink_update_result == Media::DisplayingVideoSinkUpdateResult::NewFrameAvailable) {
if (auto current_frame = m_selected_video_track_sink->current_frame()) {
auto bitmap_or_error = current_frame->to_immutable_bitmap();
if (bitmap_or_error.is_error())
dbgln("Could not convert video frame to bitmap: {}", bitmap_or_error.release_error());
else
ensure_external_content_source().update(bitmap_or_error.release_value());
}
if (auto current_frame = m_selected_video_track_sink->current_frame())
ensure_video_frame_source().update(move(current_frame));
else if (m_video_frame_source)
m_video_frame_source->clear();
update_intrinsic_video_dimensions();
set_needs_repaint();
}