diff --git a/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.cpp
index 3f464e8e38..5ccf71895a 100644
--- a/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.cpp
+++ b/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.cpp
@@ -389,6 +389,9 @@ void HTMLMediaElement::volume_or_muted_attribute_changed()
// FIXME: Then, if the media element is not allowed to play, the user agent must run the internal pause steps for the media element.
+ if (auto* layout_node = this->layout_node())
+ layout_node->set_needs_display();
+
on_volume_change();
}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.h b/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.h
index 74ec7c65fb..c0c4b2d7b5 100644
--- a/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.h
+++ b/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.h
@@ -103,6 +103,7 @@ public:
Optional control_box_rect;
Optional playback_button_rect;
Optional timeline_rect;
+ Optional speaker_button_rect;
};
CachedLayoutBoxes& cached_layout_boxes(Badge) const { return m_layout_boxes; }
diff --git a/Userland/Libraries/LibWeb/Painting/MediaPaintable.cpp b/Userland/Libraries/LibWeb/Painting/MediaPaintable.cpp
index d840a6d9c7..9996d4153e 100644
--- a/Userland/Libraries/LibWeb/Painting/MediaPaintable.cpp
+++ b/Userland/Libraries/LibWeb/Painting/MediaPaintable.cpp
@@ -61,6 +61,7 @@ void MediaPaintable::paint_media_controls(PaintContext& context, HTML::HTMLMedia
paint_control_bar_playback_button(context, media_element, components, mouse_position);
paint_control_bar_timeline(context, media_element, components, mouse_position);
paint_control_bar_timestamp(context, components);
+ paint_control_bar_speaker(context, media_element, components, mouse_position);
}
MediaPaintable::Components MediaPaintable::compute_control_bar_components(PaintContext& context, HTML::HTMLMediaElement const& media_element, DevicePixelRect media_rect) const
@@ -82,6 +83,13 @@ MediaPaintable::Components MediaPaintable::compute_control_bar_components(PaintC
components.playback_button_rect.set_width(playback_button_rect_width);
remaining_rect.take_from_left(playback_button_rect_width);
+ components.speaker_button_size = context.rounded_device_pixels(30);
+ if (components.speaker_button_size <= remaining_rect.width()) {
+ components.speaker_button_rect = remaining_rect;
+ components.speaker_button_rect.take_from_left(remaining_rect.width() - components.speaker_button_size);
+ remaining_rect.take_from_right(components.speaker_button_size + component_padding);
+ }
+
auto current_time = human_readable_digital_time(round(media_element.current_time()));
auto duration = human_readable_digital_time(isnan(media_element.duration()) ? 0 : round(media_element.duration()));
components.timestamp = String::formatted("{} / {}", current_time, duration).release_value_but_fixme_should_propagate_errors();
@@ -105,6 +113,7 @@ MediaPaintable::Components MediaPaintable::compute_control_bar_components(PaintC
media_element.cached_layout_boxes({}).control_box_rect = context.scale_to_css_rect(components.control_box_rect);
media_element.cached_layout_boxes({}).playback_button_rect = context.scale_to_css_rect(components.playback_button_rect);
media_element.cached_layout_boxes({}).timeline_rect = context.scale_to_css_rect(components.timeline_rect);
+ media_element.cached_layout_boxes({}).speaker_button_rect = context.scale_to_css_rect(components.speaker_button_rect);
return components;
}
@@ -182,6 +191,52 @@ void MediaPaintable::paint_control_bar_timestamp(PaintContext& context, Componen
context.painter().draw_text(components.timestamp_rect.to_type(), components.timestamp, *components.timestamp_font, Gfx::TextAlignment::CenterLeft, Color::White);
}
+void MediaPaintable::paint_control_bar_speaker(PaintContext& context, HTML::HTMLMediaElement const& media_element, Components const& components, Optional const& mouse_position)
+{
+ if (components.speaker_button_rect.is_empty())
+ return;
+
+ auto speaker_button_width = context.rounded_device_pixels(20);
+ auto speaker_button_height = context.rounded_device_pixels(15);
+
+ auto speaker_button_offset_x = (components.speaker_button_rect.width() - speaker_button_width) / 2;
+ auto speaker_button_offset_y = (components.speaker_button_rect.height() - speaker_button_height) / 2;
+ auto speaker_button_location = components.speaker_button_rect.top_left().translated(speaker_button_offset_x, speaker_button_offset_y);
+
+ auto device_point = [&](double x, double y) {
+ auto position = context.rounded_device_point({ x, y }) + speaker_button_location;
+ return position.to_type().to_type();
+ };
+
+ auto speaker_button_is_hovered = mouse_position.has_value() && components.speaker_button_rect.contains(*mouse_position);
+ auto speaker_button_color = control_button_color(speaker_button_is_hovered);
+
+ Gfx::AntiAliasingPainter painter { context.painter() };
+ Gfx::Path path;
+
+ path.move_to(device_point(0, 4));
+ path.line_to(device_point(5, 4));
+ path.line_to(device_point(11, 0));
+ path.line_to(device_point(11, 15));
+ path.line_to(device_point(5, 11));
+ path.line_to(device_point(0, 11));
+ path.line_to(device_point(0, 4));
+ path.close();
+ painter.fill_path(path, speaker_button_color, Gfx::Painter::WindingRule::EvenOdd);
+
+ path.clear();
+ path.move_to(device_point(13, 3));
+ path.quadratic_bezier_curve_to(device_point(16, 7.5), device_point(13, 12));
+ path.move_to(device_point(14, 0));
+ path.quadratic_bezier_curve_to(device_point(20, 7.5), device_point(14, 15));
+ painter.stroke_path(path, speaker_button_color, 1);
+
+ if (media_element.muted()) {
+ painter.draw_line(device_point(0, 0), device_point(20, 15), Color::Red, 2);
+ painter.draw_line(device_point(0, 15), device_point(20, 0), Color::Red, 2);
+ }
+}
+
MediaPaintable::DispatchEventOfSameName MediaPaintable::handle_mouseup(Badge, CSSPixelPoint position, unsigned button, unsigned)
{
if (button != GUI::MouseButton::Primary)
@@ -221,6 +276,11 @@ MediaPaintable::DispatchEventOfSameName MediaPaintable::handle_mouseup(Badgecontains(position)) {
+ media_element.set_muted(!media_element.muted());
+ return DispatchEventOfSameName::Yes;
+ }
+
return DispatchEventOfSameName::No;
}
diff --git a/Userland/Libraries/LibWeb/Painting/MediaPaintable.h b/Userland/Libraries/LibWeb/Painting/MediaPaintable.h
index 96d2309b31..73d75a0e7b 100644
--- a/Userland/Libraries/LibWeb/Painting/MediaPaintable.h
+++ b/Userland/Libraries/LibWeb/Painting/MediaPaintable.h
@@ -34,6 +34,9 @@ private:
String timestamp;
RefPtr timestamp_font;
DevicePixelRect timestamp_rect;
+
+ DevicePixelRect speaker_button_rect;
+ DevicePixels speaker_button_size;
};
virtual bool wants_mouse_events() const override { return true; }
@@ -44,6 +47,7 @@ private:
static void paint_control_bar_playback_button(PaintContext&, HTML::HTMLMediaElement const&, Components const&, Optional const& mouse_position);
static void paint_control_bar_timeline(PaintContext&, HTML::HTMLMediaElement const&, Components const&, Optional const& mouse_position);
static void paint_control_bar_timestamp(PaintContext&, Components const&);
+ static void paint_control_bar_speaker(PaintContext&, HTML::HTMLMediaElement const&, Components const& components, Optional const& mouse_position);
};
}