diff --git a/browser/ui/views/overlay/brave_video_overlay_window_views.cc b/browser/ui/views/overlay/brave_video_overlay_window_views.cc index c1b13ac719aa1..c76c843fbbb63 100644 --- a/browser/ui/views/overlay/brave_video_overlay_window_views.cc +++ b/browser/ui/views/overlay/brave_video_overlay_window_views.cc @@ -24,6 +24,7 @@ #include "chrome/browser/ui/views/overlay/simple_overlay_window_image_button.h" #include "chrome/browser/ui/views/overlay/toggle_camera_button.h" #include "chrome/browser/ui/views/overlay/toggle_microphone_button.h" +#include "ui/base/hit_test.h" #include "ui/gfx/canvas.h" namespace { @@ -46,7 +47,9 @@ std::u16string ToString(const media_session::MediaPosition& position) { ToString(position.duration())}); } -class Seeker : public views::Slider, public views::SliderListener { +class Seeker : public views::Slider, + public views::SliderListener, + public views::ViewTargeterDelegate { public: METADATA_HEADER(Seeker); @@ -63,6 +66,8 @@ class Seeker : public views::Slider, public views::SliderListener { SetRenderingStyle(RenderingStyle::kMinimalStyle); SetPreferredSize(gfx::Size(kPreferredHeight, kPreferredHeight)); thumb_animation_.SetSlideDuration(base::Milliseconds(150)); + + SetEventTargeter(std::make_unique(this)); } ~Seeker() override = default; @@ -145,8 +150,28 @@ class Seeker : public views::Slider, public views::SliderListener { listener_->SliderDragEnded(sender); } + // views::ViewTargeterDelegate: + bool DoesIntersectRect(const views::View* target, + const gfx::Rect& rect) const override { + if (!GetEnabled() || !IsDrawn()) { + return false; + } + + // Exclude resize area for the window from hit test. + // Note that we're using half of the width of resize area specified in + // video_overlay_window_views.cc for corners. + constexpr int kResizeAreaWidth = 8; + auto seeker_bounds = GetLocalBounds(); + seeker_bounds.Inset(gfx::Insets::TLBR(0, kResizeAreaWidth, + (kPreferredHeight - kLineHeight) / 2, + kResizeAreaWidth)); + return target == this && rect.Intersects(seeker_bounds); + } + private: - bool ShouldShowThumb() const { return dragging_ || IsMouseHovered(); } + bool ShouldShowThumb() const { + return GetEnabled() && (dragging_ || IsMouseHovered()); + } raw_ptr listener_ = nullptr; @@ -180,14 +205,12 @@ void BraveVideoOverlayWindowViews::SetUpViews() { timestamp_->layer()->SetFillsBoundsOpaquely(false); timestamp_->layer()->SetName("Timestamp"); - // Unlike other controls, seeker should be visible even when mouse is not - // hovered on this window. So seeker is attached to the root view directly. - auto seeker = std::make_unique(this); - seeker_ = seeker.get(); + seeker_ = + controls_container_view_->AddChildView(std::make_unique(this)); - // views in |view_holder_| will be added as child of contents view in - // VideoOverlayWindowViews::OnRootViewReady(). - view_holder_.push_back(std::move(seeker)); + // Before we get the media position, we should hide timestamp and seeker. + timestamp_->SetVisible(false); + seeker_->SetVisible(false); } void BraveVideoOverlayWindowViews::OnUpdateControlsBounds() { @@ -316,7 +339,9 @@ void BraveVideoOverlayWindowViews::SetPlaybackState( bool BraveVideoOverlayWindowViews::ControlsHitTestContainsPoint( const gfx::Point& point) { - if (seeker_->GetMirroredBounds().Contains(point)) { + gfx::Point point_in_seeker = views::View::ConvertPointToTarget( + seeker_->parent(), seeker_.get(), point); + if (seeker_->HitTestPoint(point_in_seeker)) { return true; } @@ -331,6 +356,7 @@ void BraveVideoOverlayWindowViews::ShowInactive() { VideoOverlayWindowViews::ShowInactive(); UpdateTimestampPeriodically(); } + void BraveVideoOverlayWindowViews::Close() { timestamp_update_timer_.Stop(); VideoOverlayWindowViews::Close(); @@ -341,6 +367,35 @@ void BraveVideoOverlayWindowViews::Hide() { VideoOverlayWindowViews::Hide(); } +int BraveVideoOverlayWindowViews::GetNonClientComponent( + const gfx::Point& point) { + if (seeker_ && seeker_->GetWidget() == this) { + gfx::Point point_in_seeker(point); + views::View::ConvertPointFromWidget(seeker_.get(), &point_in_seeker); + if (seeker_->HitTestPoint(point_in_seeker)) { + // We want to handel mouse event on seeker when it's visible, rather than + // consider it as non-client area + return HTCLIENT; + } + } + + return VideoOverlayWindowViews::GetNonClientComponent(point); +} + +void BraveVideoOverlayWindowViews::OnKeyEvent(ui::KeyEvent* event) { + if (event->type() == ui::ET_KEY_PRESSED && media_position_) { + if (event->key_code() == ui::VKEY_LEFT) { + controller_->SeekTo(media_position_->GetPosition() - base::Seconds(10)); + } else if (event->key_code() == ui::VKEY_RIGHT) { + controller_->SeekTo(media_position_->GetPosition() + base::Seconds(10)); + } + event->SetHandled(); + return; + } + + VideoOverlayWindowViews::OnKeyEvent(event); +} + void BraveVideoOverlayWindowViews::SliderValueChanged( views::Slider* sender, float value, @@ -392,7 +447,13 @@ void BraveVideoOverlayWindowViews::UpdateTimestampPosition() { void BraveVideoOverlayWindowViews::UpdateTimestampPeriodically() { // Update timestamp related UI controls - if (media_position_) { + + // We don't need to show seeker and timestamp when the duration is less than + // a second, infinite or zero. + if (media_position_ && + (!media_position_->duration().is_inf() && + !media_position_->duration().is_zero() && + static_cast(media_position_->duration().InSecondsF()) > 0)) { auto new_time = ToString(*media_position_); if (new_time != timestamp_->GetText()) { timestamp_->SetText(new_time); @@ -407,7 +468,6 @@ void BraveVideoOverlayWindowViews::UpdateTimestampPeriodically() { if (!seeker_->GetVisible()) { seeker_->SetVisible(true); } - } else { timestamp_->SetText({}); seeker_->SetValue(0); diff --git a/browser/ui/views/overlay/brave_video_overlay_window_views.h b/browser/ui/views/overlay/brave_video_overlay_window_views.h index da7fbd12290a1..d006fb826001a 100644 --- a/browser/ui/views/overlay/brave_video_overlay_window_views.h +++ b/browser/ui/views/overlay/brave_video_overlay_window_views.h @@ -34,6 +34,8 @@ class BraveVideoOverlayWindowViews : public VideoOverlayWindowViews, void ShowInactive() override; void Close() override; void Hide() override; + int GetNonClientComponent(const gfx::Point& point) override; + void OnKeyEvent(ui::KeyEvent* event) override; // views::SliderListener: void SliderValueChanged(views::Slider* sender, diff --git a/chromium_src/content/browser/picture_in_picture/video_picture_in_picture_window_controller_impl.cc b/chromium_src/content/browser/picture_in_picture/video_picture_in_picture_window_controller_impl.cc index 6c3e73af0d73e..77fb1df16a933 100644 --- a/chromium_src/content/browser/picture_in_picture/video_picture_in_picture_window_controller_impl.cc +++ b/chromium_src/content/browser/picture_in_picture/video_picture_in_picture_window_controller_impl.cc @@ -21,12 +21,16 @@ } // Update seeker enabled state whenever actions are updated. -#define SetSkipAdButtonVisibility(SKIP_BUTTON_VISIBILITY) \ - SetSkipAdButtonVisibility(SKIP_BUTTON_VISIBILITY); \ - media_session_action_seek_to_handled_ = \ - MediaSessionImpl::Get(web_contents()) \ - ->ShouldRouteAction( \ - media_session::mojom::MediaSessionAction::kSeekTo); \ +// Note that we allow seeking when media session is controllable referring to +// upstream code. +// https://source.chromium.org/chromium/chromium/src/+/main:content/browser/media/session/media_session_impl.cc;l=1687;drc=c686e8f4fd379312469fe018f5c390e9c8f20d0d +#define SetSkipAdButtonVisibility(SKIP_BUTTON_VISIBILITY) \ + SetSkipAdButtonVisibility(SKIP_BUTTON_VISIBILITY); \ + auto* session = MediaSessionImpl::Get(web_contents()); \ + media_session_action_seek_to_handled_ = \ + session->ShouldRouteAction( \ + media_session::mojom::MediaSessionAction::kSeekTo) || \ + session->IsControllable(); \ window_->SetSeekerEnabled(media_session_action_seek_to_handled_) #include "src/content/browser/picture_in_picture/video_picture_in_picture_window_controller_impl.cc"