| // Copyright 2022 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/ui/views/overlay/video_overlay_window_views.h" |
| |
| #include <memory> |
| #include <string> |
| |
| #include "base/check.h" |
| #include "base/feature_list.h" |
| #include "base/functional/bind.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/strings/strcat.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/time/time.h" |
| #include "base/timer/timer.h" |
| #include "build/build_config.h" |
| #include "chrome/browser/media/media_engagement_service.h" |
| #include "chrome/browser/picture_in_picture/picture_in_picture_occlusion_tracker.h" |
| #include "chrome/browser/picture_in_picture/picture_in_picture_window_manager.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/browser_finder.h" |
| #include "chrome/browser/ui/color/chrome_color_id.h" |
| #include "chrome/browser/ui/views/overlay/back_to_tab_button.h" |
| #include "chrome/browser/ui/views/overlay/back_to_tab_label_button.h" |
| #include "chrome/browser/ui/views/overlay/close_image_button.h" |
| #include "chrome/browser/ui/views/overlay/constants.h" |
| #include "chrome/browser/ui/views/overlay/hang_up_button.h" |
| #include "chrome/browser/ui/views/overlay/minimize_button.h" |
| #include "chrome/browser/ui/views/overlay/overlay_controls_fade_animation.h" |
| #include "chrome/browser/ui/views/overlay/overlay_window_live_caption_button.h" |
| #include "chrome/browser/ui/views/overlay/overlay_window_live_caption_dialog.h" |
| #include "chrome/browser/ui/views/overlay/playback_image_button.h" |
| #include "chrome/browser/ui/views/overlay/resize_handle_button.h" |
| #include "chrome/browser/ui/views/overlay/simple_overlay_window_image_button.h" |
| #include "chrome/browser/ui/views/overlay/skip_ad_label_button.h" |
| #include "chrome/browser/ui/views/overlay/toggle_camera_button.h" |
| #include "chrome/browser/ui/views/overlay/toggle_microphone_button.h" |
| #include "chrome/browser/ui/views/picture_in_picture/picture_in_picture_tucker.h" |
| #include "chrome/grit/generated_resources.h" |
| #include "components/global_media_controls/public/format_duration.h" |
| #include "components/vector_icons/vector_icons.h" |
| #include "content/public/browser/media_session.h" |
| #include "content/public/browser/picture_in_picture_window_controller.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/web_contents.h" |
| #include "media/base/media_switches.h" |
| #include "media/base/video_util.h" |
| #include "services/media_session/public/cpp/media_image_manager.h" |
| #include "ui/base/hit_test.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/base/metadata/metadata_header_macros.h" |
| #include "ui/base/metadata/metadata_impl_macros.h" |
| #include "ui/base/mojom/ui_base_types.mojom-shared.h" |
| #include "ui/compositor/compositor.h" |
| #include "ui/compositor/layer.h" |
| #include "ui/display/display.h" |
| #include "ui/display/screen.h" |
| #include "ui/gfx/canvas.h" |
| #include "ui/gfx/favicon_size.h" |
| #include "ui/gfx/geometry/resize_utils.h" |
| #include "ui/gfx/geometry/skia_conversions.h" |
| #include "ui/views/controls/image_view.h" |
| #include "ui/views/controls/label.h" |
| #include "ui/views/widget/widget_delegate.h" |
| #include "ui/views/window/frame_view.h" |
| #include "url/origin.h" |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| #include "ash/public/cpp/window_properties.h" // nogncheck |
| #include "chromeos/ui/base/app_types.h" |
| #include "chromeos/ui/base/chromeos_ui_constants.h" |
| #include "chromeos/ui/base/window_properties.h" |
| #include "ui/aura/client/aura_constants.h" |
| #include "ui/aura/window.h" |
| #endif |
| |
| #if BUILDFLAG(IS_WIN) |
| #include "chrome/browser/shell_integration_win.h" |
| #include "content/public/browser/render_widget_host_view.h" |
| #include "ui/aura/window.h" |
| #include "ui/aura/window_tree_host.h" |
| #include "ui/base/ime/text_input_client.h" |
| #include "ui/base/ime/win/tsf_input_scope.h" |
| #include "ui/base/win/shell.h" |
| #endif |
| |
| #if BUILDFLAG(IS_MAC) |
| #include "chrome/browser/ui/views/overlay/video_overlay_window_native_widget_mac.h" |
| #endif // BUILDFLAG(IS_MAC) |
| |
| namespace { |
| |
| // Lower bound size of the window is a fixed value to allow for minimal sizes |
| // on UI affordances, such as buttons. |
| constexpr gfx::Size kMinWindowSize(284, 160); |
| |
| constexpr int kOverlayBorderThickness = 10; |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| // The opacity of the resize handle control. |
| constexpr double kResizeHandleOpacity = 0.38; |
| #endif |
| |
| // Size of a primary control. |
| constexpr gfx::Size kPrimaryControlSize(52, 52); |
| |
| // Margin from the bottom of the window for primary controls. |
| constexpr int kPrimaryControlBottomMargin = 0; |
| |
| // Size of a secondary control. |
| constexpr gfx::Size kSecondaryControlSize(36, 36); |
| |
| // Margin from the bottom of the window for secondary controls. |
| constexpr int kSecondaryControlBottomMargin = 8; |
| |
| // Margin between controls. |
| constexpr int kControlMargin = 16; |
| |
| // Minimum padding between the overlay view, if shown, and the window. |
| constexpr gfx::Size kOverlayViewPadding(64, 46); |
| |
| // Size for action buttons in the 2024 UI. |
| constexpr gfx::Size kActionButtonSize(28, 28); |
| |
| // The amount of time the seek buttons next to the play button seek. |
| constexpr base::TimeDelta kSeekTime = base::Seconds(10); |
| |
| // The size of the view containing the favicon. |
| constexpr gfx::Size kFaviconSize(24, 24); |
| |
| // The size of the favicon image itself. |
| constexpr gfx::Size kFaviconIconSize(16, 16); |
| |
| gfx::Size ScaleImageSizeToFitView(const gfx::Size& image_size, |
| const gfx::Size& view_size) { |
| const float scale = |
| std::max(view_size.width() / static_cast<float>(image_size.width()), |
| view_size.height() / static_cast<float>(image_size.height())); |
| return gfx::ScaleToFlooredSize(image_size, scale); |
| } |
| |
| // Converts the given bitmap to the correct color type. |
| gfx::ImageSkia GetCorrectColorTypeImage(const SkBitmap& bitmap) { |
| if (bitmap.info().colorType() == kN32_SkColorType) { |
| return gfx::ImageSkia::CreateFrom1xBitmap(bitmap); |
| } |
| SkImageInfo color_type_info = bitmap.info().makeColorType(kN32_SkColorType); |
| SkBitmap color_type_copy; |
| if (!color_type_copy.tryAllocPixels(color_type_info)) { |
| return gfx::ImageSkia(); |
| } |
| if (!bitmap.readPixels(color_type_info, color_type_copy.getPixels(), |
| color_type_copy.rowBytes(), 0, 0)) { |
| return gfx::ImageSkia(); |
| } |
| return gfx::ImageSkia::CreateFrom1xBitmap(color_type_copy); |
| } |
| |
| // Returns the quadrant the VideoOverlayWindowViews is primarily in on the |
| // current work area. |
| VideoOverlayWindowViews::WindowQuadrant GetCurrentWindowQuadrant( |
| const gfx::Rect window_bounds, |
| content::PictureInPictureWindowController* controller) { |
| const gfx::Rect work_area = |
| display::Screen::Get() |
| ->GetDisplayNearestWindow( |
| controller->GetWebContents()->GetTopLevelNativeWindow()) |
| .work_area(); |
| const gfx::Point window_center = window_bounds.CenterPoint(); |
| |
| // Check which quadrant the center of the window appears in. |
| const bool top = window_center.y() < work_area.height() / 2; |
| if (window_center.x() < work_area.width() / 2) { |
| return top ? VideoOverlayWindowViews::WindowQuadrant::kTopLeft |
| : VideoOverlayWindowViews::WindowQuadrant::kBottomLeft; |
| } |
| return top ? VideoOverlayWindowViews::WindowQuadrant::kTopRight |
| : VideoOverlayWindowViews::WindowQuadrant::kBottomRight; |
| } |
| |
| template <typename T> |
| T* AddChildView(std::vector<std::unique_ptr<views::View>>* views, |
| std::unique_ptr<T> child) { |
| views->push_back(std::move(child)); |
| return static_cast<T*>(views->back().get()); |
| } |
| |
| bool Use2024UI() { |
| return base::FeatureList::IsEnabled( |
| media::kVideoPictureInPictureControlsUpdate2024); |
| } |
| |
| class WindowBackgroundView : public views::View { |
| METADATA_HEADER(WindowBackgroundView, views::View) |
| |
| public: |
| WindowBackgroundView() = default; |
| WindowBackgroundView(const WindowBackgroundView&) = delete; |
| WindowBackgroundView& operator=(const WindowBackgroundView&) = delete; |
| ~WindowBackgroundView() override = default; |
| |
| void OnThemeChanged() override { |
| views::View::OnThemeChanged(); |
| layer()->SetColor(GetColorProvider()->GetColor(kColorPipWindowBackground)); |
| } |
| }; |
| |
| BEGIN_METADATA(WindowBackgroundView) |
| END_METADATA |
| |
| class ControlsBackgroundView : public views::View { |
| METADATA_HEADER(ControlsBackgroundView, views::View) |
| |
| public: |
| ControlsBackgroundView() = default; |
| ControlsBackgroundView(const ControlsBackgroundView&) = delete; |
| ControlsBackgroundView& operator=(const ControlsBackgroundView&) = delete; |
| ~ControlsBackgroundView() override = default; |
| |
| void OnThemeChanged() override { |
| views::View::OnThemeChanged(); |
| const SkColor color = GetColorProvider()->GetColor( |
| Use2024UI() ? kColorPipWindowScrimFull |
| : kColorPipWindowControlsBackground); |
| layer()->SetColor(SkColorSetA(color, SK_AlphaOPAQUE)); |
| layer()->SetOpacity(static_cast<float>(SkColorGetA(color)) / |
| SK_AlphaOPAQUE); |
| } |
| }; |
| |
| BEGIN_METADATA(ControlsBackgroundView) |
| END_METADATA |
| |
| class GradientBackground : public views::Background { |
| public: |
| GradientBackground(SkColor4f top_color, SkColor4f bottom_color) |
| : top_color_(top_color), bottom_color_(bottom_color) {} |
| GradientBackground(const GradientBackground&) = delete; |
| GradientBackground& operator=(const GradientBackground&) = delete; |
| ~GradientBackground() override = default; |
| |
| void Paint(gfx::Canvas* canvas, views::View* view) const override { |
| gfx::Rect draw_bounds = view->GetContentsBounds(); |
| const SkColor4f colors[2] = {top_color_, bottom_color_}; |
| const SkPoint points[2] = { |
| gfx::PointToSkPoint(draw_bounds.top_center()), |
| gfx::PointToSkPoint(draw_bounds.bottom_center())}; |
| cc::PaintFlags flags; |
| flags.setAntiAlias(true); |
| flags.setStyle(cc::PaintFlags::kFill_Style); |
| flags.setShader(cc::PaintShader::MakeLinearGradient(points, colors, nullptr, |
| 2, SkTileMode::kClamp)); |
| canvas->DrawRect(draw_bounds, flags); |
| } |
| |
| const SkColor4f top_color_; |
| const SkColor4f bottom_color_; |
| }; |
| |
| } // namespace |
| |
| // OverlayWindow implementation of FrameView. |
| class OverlayWindowFrameView : public views::FrameView { |
| METADATA_HEADER(OverlayWindowFrameView, views::FrameView) |
| |
| public: |
| explicit OverlayWindowFrameView(views::Widget* widget) : widget_(widget) {} |
| |
| OverlayWindowFrameView(const OverlayWindowFrameView&) = delete; |
| OverlayWindowFrameView& operator=(const OverlayWindowFrameView&) = delete; |
| |
| ~OverlayWindowFrameView() override = default; |
| |
| // views::FrameView: |
| gfx::Rect GetBoundsForClientView() const override { return bounds(); } |
| gfx::Rect GetWindowBoundsForClientBounds( |
| const gfx::Rect& client_bounds) const override { |
| return bounds(); |
| } |
| int NonClientHitTest(const gfx::Point& point) override { |
| // Outside of the window bounds, do nothing. |
| if (!bounds().Contains(point)) { |
| return HTNOWHERE; |
| } |
| |
| constexpr int kResizeAreaCornerSize = 16; |
| int window_component = GetHTComponentForFrame( |
| point, gfx::Insets(kOverlayBorderThickness), kResizeAreaCornerSize, |
| kResizeAreaCornerSize, GetWidget()->widget_delegate()->CanResize()); |
| |
| // The overlay controls should take and handle user interaction. |
| VideoOverlayWindowViews* window = |
| static_cast<VideoOverlayWindowViews*>(widget_); |
| if (window->ControlsHitTestContainsPoint(point)) { |
| return window_component; |
| } |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| // If the resize handle is clicked on, we want to force the hit test to |
| // force a resize drag. |
| if (window->AreControlsVisible() && |
| window->GetResizeHandleControlsBounds().Contains(point)) { |
| return window->GetResizeHTComponent(); |
| } |
| #endif |
| |
| // If the live caption dialog is open, then we'll want to capture all mouse |
| // clicks within the window so we can use them to close the dialog when the |
| // user clicks outside of it. |
| if (!window->GetLiveCaptionDialogBounds().IsEmpty()) { |
| return window_component; |
| } |
| |
| // Allows for dragging and resizing the window. |
| return (window_component == HTNOWHERE) ? HTCAPTION : window_component; |
| } |
| #if BUILDFLAG(IS_CHROMEOS) |
| void UpdateWindowRoundedCorners() override { |
| // The first call to occurs in `UpdateWindowRoundedCorners()`. However, the |
| // layer is initialized after the widget is initialized, hence the null |
| // check. |
| ui::Layer* root_view_layer = GetWidget()->GetRootView()->layer(); |
| if (root_view_layer) { |
| const gfx::RoundedCornersF window_radii( |
| chromeos::kPipRoundedCornerRadius); |
| |
| root_view_layer->SetRoundedCornerRadius(window_radii); |
| root_view_layer->SetIsFastRoundedCorner(true); |
| } |
| } |
| #endif |
| |
| // views::ViewTargeterDelegate: |
| bool DoesIntersectRect(const View* target, |
| const gfx::Rect& rect) const override { |
| DCHECK_EQ(target, this); |
| return false; |
| } |
| |
| private: |
| raw_ptr<views::Widget> widget_; |
| }; |
| |
| BEGIN_METADATA(OverlayWindowFrameView) |
| END_METADATA |
| |
| // OverlayWindow implementation of WidgetDelegate. |
| class OverlayWindowWidgetDelegate : public views::WidgetDelegate { |
| public: |
| OverlayWindowWidgetDelegate() { |
| SetCanResize(true); |
| SetModalType(ui::mojom::ModalType::kNone); |
| // While not shown, the title is still used to identify the window in the |
| // window switcher. |
| SetShowTitle(false); |
| SetTitle(IDS_PICTURE_IN_PICTURE_TITLE_TEXT); |
| SetOwnedByWidget(OwnedByWidgetPassKey()); |
| } |
| |
| OverlayWindowWidgetDelegate(const OverlayWindowWidgetDelegate&) = delete; |
| OverlayWindowWidgetDelegate& operator=(const OverlayWindowWidgetDelegate&) = |
| delete; |
| |
| ~OverlayWindowWidgetDelegate() override = default; |
| |
| // views::WidgetDelegate: |
| std::unique_ptr<views::FrameView> CreateFrameView( |
| views::Widget* widget) override { |
| return std::make_unique<OverlayWindowFrameView>(widget); |
| } |
| }; |
| |
| // static |
| std::unique_ptr<VideoOverlayWindowViews> VideoOverlayWindowViews::Create( |
| content::VideoPictureInPictureWindowController* controller) { |
| // Can't use make_unique(), which doesn't have access to the private |
| // constructor. It's important that the constructor be private, because it |
| // doesn't initialize the object fully. |
| auto overlay_window = |
| base::WrapUnique(new VideoOverlayWindowViews(controller)); |
| |
| // The 2024 updated controls use dark mode colors. |
| if (Use2024UI()) { |
| overlay_window->SetColorModeOverride( |
| ui::ColorProviderKey::ColorMode::kDark); |
| } |
| |
| overlay_window->CalculateAndUpdateWindowBounds(); |
| overlay_window->SetUpViews(); |
| |
| views::Widget::InitParams params( |
| views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET, |
| views::Widget::InitParams::TYPE_WINDOW); |
| // Just to have any non-empty bounds as required by Init(). The window is |
| // resized to fit the video that is embedded right afterwards, anyway. |
| params.bounds = gfx::Rect(overlay_window->GetMinimumSize()); |
| params.z_order = ui::ZOrderLevel::kFloatingWindow; |
| params.visible_on_all_workspaces = true; |
| params.remove_standard_frame = true; |
| params.name = "PictureInPictureWindow"; |
| params.layer_type = ui::LAYER_NOT_DRAWN; |
| params.delegate = new OverlayWindowWidgetDelegate(); |
| |
| // Fade in animation is disabled for Document and Video Picture-in-Picture on |
| // Windows. On Windows, resizable windows can not be translucent. See |
| // crbug.com/425711450. |
| #if !BUILDFLAG(IS_WIN) |
| if (base::FeatureList::IsEnabled( |
| media::kPictureInPictureShowWindowAnimation)) { |
| params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent; |
| } |
| #endif |
| |
| #if BUILDFLAG(IS_MAC) |
| // On Mac, we override the default native widget with our own subclass, which |
| // allows us to get the default window styling (e.g. corner radius) even |
| // though we're using `views::Widget::InitParams::remove_standard_frame`. |
| params.native_widget = |
| new VideoOverlayWindowNativeWidgetMac(overlay_window.get()); |
| #endif // BUILDFLAG(IS_MAC) |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| params.init_properties_container.SetProperty(chromeos::kAppTypeKey, |
| chromeos::AppType::BROWSER); |
| params.rounded_corners = |
| gfx::RoundedCornersF(chromeos::kPipRoundedCornerRadius); |
| #endif |
| |
| overlay_window->Init(std::move(params)); |
| overlay_window->OnRootViewReady(); |
| |
| #if BUILDFLAG(IS_WIN) |
| std::wstring app_user_model_id; |
| Browser* browser = chrome::FindBrowserWithTab(controller->GetWebContents()); |
| if (browser) { |
| const base::FilePath& profile_path = browser->profile()->GetPath(); |
| // Set the window app id to GetAppUserModelIdForApp if the original window |
| // is an app window, GetAppUserModelIdForBrowser if it's a browser window. |
| app_user_model_id = |
| browser->is_type_app() |
| ? shell_integration::win::GetAppUserModelIdForApp( |
| base::UTF8ToWide(browser->app_name()), profile_path) |
| : shell_integration::win::GetAppUserModelIdForBrowser(profile_path); |
| if (!app_user_model_id.empty()) { |
| ui::win::SetAppIdForWindow( |
| app_user_model_id, |
| overlay_window->GetNativeWindow()->GetHost()->GetAcceleratedWidget()); |
| } |
| } |
| |
| // Default to private input scope in the case where we don't have a |
| // TextInputClient, such as when this is running inside an InnerWebContents. |
| InputScope input_scope = IS_PRIVATE; |
| ui::TextInputClient* text_input_client = overlay_window->GetController() |
| ->GetWebContents() |
| ->GetRenderWidgetHostView() |
| ->GetTextInputClient(); |
| if (text_input_client && text_input_client->ShouldDoLearning()) { |
| input_scope = IS_DEFAULT; |
| } |
| |
| ui::tsf_inputscope::SetInputScope( |
| overlay_window->GetNativeWindow()->GetHost()->GetAcceleratedWidget(), |
| input_scope); |
| |
| #endif // BUILDFLAG(IS_WIN) |
| |
| PictureInPictureOcclusionTracker* tracker = |
| PictureInPictureWindowManager::GetInstance()->GetOcclusionTracker(); |
| if (tracker) { |
| tracker->OnPictureInPictureWidgetOpened(overlay_window.get()); |
| } |
| |
| return overlay_window; |
| } |
| |
| // static |
| std::unique_ptr<content::VideoOverlayWindow> |
| content::VideoOverlayWindow::Create( |
| content::VideoPictureInPictureWindowController* controller) { |
| return VideoOverlayWindowViews::Create(controller); |
| } |
| |
| VideoOverlayWindowViews::VideoOverlayWindowViews( |
| content::VideoPictureInPictureWindowController* controller) |
| : controller_(controller), |
| min_size_(kMinWindowSize), |
| hide_controls_timer_( |
| FROM_HERE, |
| base::Milliseconds(2500), |
| base::BindRepeating( |
| &VideoOverlayWindowViews::UpdateControlsVisibility, |
| base::Unretained(this), |
| false /* is_visible */, |
| true /* should_animate */)), |
| enable_controls_after_move_timer_( |
| FROM_HERE, |
| VideoOverlayWindowViews::kControlHideDelayAfterMove, |
| base::BindRepeating( |
| &VideoOverlayWindowViews::ReEnableControlsAfterMove, |
| base::Unretained(this))) { |
| display::Screen::Get()->AddObserver(this); |
| } |
| |
| VideoOverlayWindowViews::~VideoOverlayWindowViews() { |
| if (overlay_view_) { |
| overlay_view_->RemoveObserver(this); |
| } |
| display::Screen::Get()->RemoveObserver(this); |
| PictureInPictureWindowManager::GetInstance()->OnPictureInPictureWindowHidden( |
| this); |
| } |
| |
| gfx::Size& VideoOverlayWindowViews::GetNaturalSize() { |
| return natural_size_; |
| } |
| |
| gfx::Rect VideoOverlayWindowViews::CalculateAndUpdateWindowBounds() { |
| gfx::Rect work_area = GetWorkAreaForWindow(); |
| |
| UpdateMaxSize(work_area); |
| |
| const gfx::Rect bounds = GetBounds(); |
| |
| gfx::Size window_size = bounds.size(); |
| if (!has_been_shown_) { |
| window_size = gfx::Size(work_area.width() / 5, work_area.height() / 5); |
| } |
| |
| // Even though we define the minimum and maximum sizes for our views::Widget, |
| // it's possible for the current size to be outside of those bounds |
| // transiently on some platforms, so we need to cap it. |
| window_size.SetToMin(max_size_); |
| window_size.SetToMax(GetMinimumSize()); |
| |
| // Determine the window size by fitting |natural_size_| within |window_size|, |
| // keeping to |natural_size_|'s aspect ratio. |
| if (!natural_size_.IsEmpty()) { |
| float aspect_ratio = (float)natural_size_.width() / natural_size_.height(); |
| |
| WindowQuadrant quadrant = GetCurrentWindowQuadrant(bounds, GetController()); |
| gfx::ResizeEdge resize_edge; |
| switch (quadrant) { |
| case WindowQuadrant::kBottomRight: |
| resize_edge = gfx::ResizeEdge::kTopLeft; |
| break; |
| case WindowQuadrant::kBottomLeft: |
| resize_edge = gfx::ResizeEdge::kTopRight; |
| break; |
| case WindowQuadrant::kTopLeft: |
| resize_edge = gfx::ResizeEdge::kBottomRight; |
| break; |
| case WindowQuadrant::kTopRight: |
| resize_edge = gfx::ResizeEdge::kBottomLeft; |
| break; |
| } |
| |
| // Update the window size to adhere to the aspect ratio. |
| gfx::Rect window_rect(bounds.origin(), window_size); |
| gfx::SizeRectToAspectRatio(resize_edge, aspect_ratio, GetMinimumSize(), |
| max_size_, &window_rect); |
| window_size = window_rect.size(); |
| |
| UpdateLayerBoundsWithLetterboxing(window_size); |
| } |
| |
| // Use the previous window origin location, if exists. |
| gfx::Point origin = bounds.origin(); |
| |
| int window_diff_width = work_area.right() - window_size.width(); |
| int window_diff_height = work_area.bottom() - window_size.height(); |
| |
| // Keep a margin distance of 2% the average of the two window size |
| // differences, keeping the margins consistent. |
| int buffer = (window_diff_width + window_diff_height) / 2 * 0.02; |
| |
| gfx::Point default_origin = |
| gfx::Point(window_diff_width - buffer, window_diff_height - buffer); |
| |
| if (has_been_shown_) { |
| // Make sure window is displayed entirely in the work area. |
| origin.SetToMin(default_origin); |
| } else { |
| origin = default_origin; |
| } |
| |
| return gfx::Rect(origin, window_size); |
| } |
| |
| void VideoOverlayWindowViews::OnNativeFocus() { |
| UpdateControlsVisibility(true); |
| views::Widget::OnNativeFocus(); |
| } |
| |
| void VideoOverlayWindowViews::OnNativeBlur() { |
| // Controls should be hidden when there is no more focus on the window. This |
| // is used for tabbing and touch interactions. For mouse interactions, the |
| // window cannot be blurred before the ui::EventType::kMouseExited event is |
| // handled. |
| UpdateControlsVisibility(false); |
| |
| views::Widget::OnNativeBlur(); |
| } |
| |
| gfx::Size VideoOverlayWindowViews::GetMinimumSize() const { |
| if (IsOverlayViewShown()) { |
| // Make sure that our minimum is sufficiently large to enclose the bubble, |
| // plus some margin to make it look nicer. |
| gfx::Size overlay_size = |
| overlay_view_->GetBubbleSize() + kOverlayViewPadding; |
| overlay_size.SetToMax(min_size_); |
| return overlay_size; |
| } |
| return min_size_; |
| } |
| |
| gfx::Size VideoOverlayWindowViews::GetMaximumSize() const { |
| return max_size_; |
| } |
| |
| void VideoOverlayWindowViews::OnNativeWidgetMove() { |
| // Hide the controls when the window is moving. The controls will reappear |
| // when the user interacts with the window again. Only called once, at the |
| // start of movement because we do not want to clobber updates from other |
| // requesters. |
| if (!is_moving_) { |
| UpdateControlsVisibility(false, /*should_animate=*/false); |
| } |
| |
| is_moving_ = true; |
| enable_controls_after_move_timer_.Reset(); |
| |
| // Update the maximum size of the widget in case we have moved to another |
| // window. |
| UpdateMaxSize(GetWorkAreaForWindow()); |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| // Update the positioning of some icons when the window is moved. |
| WindowQuadrant quadrant = |
| GetCurrentWindowQuadrant(GetBounds(), GetController()); |
| |
| // In the 2024 UI, there are icons on both sides of the top bar, so moving the |
| // close button doesn't make sense. Only move it when using the older |
| // controls. |
| if (!Use2024UI()) { |
| close_controls_view_->SetPosition(GetBounds().size(), quadrant); |
| } |
| |
| UpdateResizeHandleBounds(quadrant); |
| #endif |
| |
| views::Widget::OnNativeWidgetMove(); |
| } |
| |
| void VideoOverlayWindowViews::OnNativeWidgetSizeChanged( |
| const gfx::Size& new_size) { |
| // Hide the controls when the window is being resized. The controls will |
| // reappear when the user interacts with the window again. |
| UpdateControlsVisibility(false); |
| |
| // Update the view layers to scale to |new_size|. |
| UpdateLayerBoundsWithLetterboxing(new_size); |
| |
| views::Widget::OnNativeWidgetSizeChanged(new_size); |
| } |
| |
| void VideoOverlayWindowViews::OnKeyEvent(ui::KeyEvent* event) { |
| // Every time a user uses a keyboard to interact on the window, restart the |
| // timer to automatically hide the controls. |
| hide_controls_timer_.Reset(); |
| |
| // Any keystroke will make the controls visible, if not already. The Tab key |
| // needs to be handled separately. |
| // If the controls are already visible, this is a no-op. |
| if (event->type() == ui::EventType::kKeyPressed || |
| event->key_code() == ui::VKEY_TAB) { |
| UpdateControlsVisibility(true); |
| } |
| |
| // On Windows, the Alt+F4 keyboard combination closes the window. Only handle |
| // closure on key press so Close() is not called a second time when the key |
| // is released. |
| #if BUILDFLAG(IS_WIN) |
| if (event->type() == ui::EventType::kKeyPressed && event->IsAltDown() && |
| event->key_code() == ui::VKEY_F4) { |
| CloseAndPauseIfAvailable(); |
| event->SetHandled(); |
| } |
| #endif // BUILDFLAG(IS_WIN) |
| |
| // If there is no focus affordance on the buttons and play/pause button is |
| // visible, only handle space key for TogglePlayPause(). |
| views::View* focused_view = GetFocusManager()->GetFocusedView(); |
| if (!focused_view && event->type() == ui::EventType::kKeyPressed && |
| event->key_code() == ui::VKEY_SPACE && show_play_pause_button_) { |
| TogglePlayPause(); |
| event->SetHandled(); |
| } |
| |
| MaybeUpdateMeetsUserInteraction(*event); |
| |
| views::Widget::OnKeyEvent(event); |
| } |
| |
| void VideoOverlayWindowViews::OnMouseEvent(ui::MouseEvent* event) { |
| switch (event->type()) { |
| // Only show the media controls when the mouse is hovering over the |
| // window. |
| case ui::EventType::kMouseMoved: |
| case ui::EventType::kMouseEntered: |
| UpdateControlsVisibility(true); |
| break; |
| |
| case ui::EventType::kMouseExited: { |
| // On Windows, ui::EventType::kMouseExited is triggered when hovering over |
| // the media controls because of the HitTest. This check ensures the |
| // controls are visible if the mouse is still over the window. |
| // We also check that the user isn't currently dragging the progress bar, |
| // since setting visibility to false during the drag will prevent the drag |
| // from functioning properly (and we'll lose the drag end). |
| const bool should_update_control_visibility = |
| !GetWindowBackgroundView()->bounds().Contains(event->location()) && |
| progress_view_drag_state_ == |
| global_media_controls::DragState::kDragEnded; |
| if (should_update_control_visibility) { |
| UpdateControlsVisibility(false); |
| } |
| break; |
| } |
| |
| case ui::EventType::kMousePressed: |
| // Hide the live caption dialog if it's visible and the user clicks |
| // outside of it. |
| if (live_caption_dialog_ && live_caption_dialog_->GetVisible() && |
| !GetLiveCaptionDialogBounds().Contains(event->location()) && |
| !GetLiveCaptionButtonBounds().Contains(event->location())) { |
| SetLiveCaptionDialogVisibility(false); |
| return; |
| } |
| break; |
| |
| default: |
| break; |
| } |
| |
| // If the user interacts with the window using a mouse, stop the timer to |
| // automatically hide the controls. |
| hide_controls_timer_.Reset(); |
| |
| MaybeUpdateMeetsUserInteraction(*event); |
| |
| views::Widget::OnMouseEvent(event); |
| } |
| |
| bool VideoOverlayWindowViews::OnGestureEventHandledOrIgnored( |
| ui::GestureEvent* event) { |
| if (event->type() != ui::EventType::kGestureTap) { |
| return true; |
| } |
| |
| // Every time a user taps on the window, restart the timer to automatically |
| // hide the controls. |
| hide_controls_timer_.Reset(); |
| |
| // If the controls were not shown, make them visible. All controls related |
| // layers are expected to have the same visibility. |
| // TODO(apacible): This placeholder logic should be updated with touchscreen |
| // specific investigation. https://crbug/854373 |
| if (!AreControlsVisible()) { |
| UpdateControlsVisibility(true); |
| return true; |
| } |
| return false; |
| } |
| |
| void VideoOverlayWindowViews::ReEnableControlsAfterMove() { |
| is_moving_ = false; |
| |
| if (queued_controls_visibility_status_) { |
| UpdateControlsVisibility( |
| queued_controls_visibility_status_->is_visible, |
| queued_controls_visibility_status_->should_animate); |
| } |
| queued_controls_visibility_status_.reset(); |
| } |
| |
| void VideoOverlayWindowViews::ForceControlsVisibleForTesting( |
| bool controls_visible, |
| std::optional<bool> title_and_scrim_visible) { |
| force_controls_visible_ = controls_visible; |
| force_title_and_scrim_visible_ = title_and_scrim_visible; |
| UpdateControlsVisibility(controls_visible, /*should_animate=*/false); |
| } |
| |
| void VideoOverlayWindowViews::StopForcingControlsVisibleForTesting() { |
| force_controls_visible_.reset(); |
| force_title_and_scrim_visible_.reset(); |
| } |
| |
| void VideoOverlayWindowViews::FireEnableControlsAfterMoveTimerForTesting() { |
| if (!enable_controls_after_move_timer_.IsRunning()) { |
| return; |
| } |
| enable_controls_after_move_timer_.Stop(); |
| ReEnableControlsAfterMove(); |
| } |
| |
| bool VideoOverlayWindowViews::AreControlsVisible() const { |
| // If we're animating to a visibility state, then we'll act as if we're in |
| // that state. |
| if (fade_animation_) { |
| return (fade_animation_->type() == |
| OverlayControlsFadeAnimation::Type::kToShown); |
| } |
| return GetControlsContainerView()->layer()->opacity() > 0; |
| } |
| |
| void VideoOverlayWindowViews::UpdateControlsVisibility(bool is_visible, |
| bool should_animate) { |
| if (is_moving_) { |
| // If we've already queued a visibility change for the same visibility, then |
| // only animate if both should animate (which matches what would have |
| // happened if both visibility change updates were allowed to happen). |
| if (queued_controls_visibility_status_.has_value() && |
| queued_controls_visibility_status_->is_visible == is_visible) { |
| queued_controls_visibility_status_->should_animate = |
| queued_controls_visibility_status_->should_animate && should_animate; |
| } else { |
| // Otherwise, queue this visibility change as-is. |
| queued_controls_visibility_status_ = {is_visible, should_animate}; |
| } |
| return; |
| } |
| |
| // We should not change visibility while the progress bar is being dragged, as |
| // this gets us stuck in the `kDragStarted` state. |
| if (progress_view_drag_state_ == |
| global_media_controls::DragState::kDragStarted) { |
| return; |
| } |
| |
| // If the overlay view is shown, then the other controls are always hidden. |
| const bool wanted_visibility = |
| !IsOverlayViewShown() && force_controls_visible_.value_or(is_visible); |
| |
| // The title and scrim can be hidden if the overlay window is trusted or meets |
| // the user interaction criteria. |
| const bool can_hide_title_and_scrim = |
| IsTrustedForMediaPlayback() || meets_user_interaction_; |
| |
| // If the controls are becoming visible, and the title and scrim can be |
| // hidden, stop the initial hide timer. |
| if (wanted_visibility && can_hide_title_and_scrim) { |
| initial_title_hide_timer_.Stop(); |
| } |
| |
| // The title and controls top scrim are visible if: |
| // * The controls are, or |
| // * We are in the initial "show" period, or |
| // * The overlay window does not meet the user interaction criteria and is |
| // not trusted for media playback |
| const bool title_is_visible = |
| force_title_and_scrim_visible_.has_value() |
| ? force_title_and_scrim_visible_.value() |
| : (wanted_visibility && Use2024UI()) || |
| initial_title_hide_timer_.IsRunning() || |
| (Use2024UI() && !can_hide_title_and_scrim); |
| |
| if (should_animate) { |
| // Animate the title and top scrim. |
| if (title_is_visible != AreTitleAndScrimVisible()) { |
| title_fade_animation_ = std::make_unique<OverlayControlsFadeAnimation>( |
| *GetTitleView(), title_is_visible |
| ? OverlayControlsFadeAnimation::Type::kToShown |
| : OverlayControlsFadeAnimation::Type::kToHidden); |
| controls_top_scrim_fade_animation_ = |
| std::make_unique<OverlayControlsFadeAnimation>( |
| *GetControlsTopScrimView(), |
| title_is_visible ? OverlayControlsFadeAnimation::Type::kToShown |
| : OverlayControlsFadeAnimation::Type::kToHidden); |
| |
| title_fade_animation_->Start(); |
| controls_top_scrim_fade_animation_->Start(); |
| } |
| |
| // Animate the main controls. |
| if (wanted_visibility != AreControlsVisible()) { |
| fade_animation_ = std::make_unique<OverlayControlsFadeAnimation>( |
| *GetControlsContainerView(), |
| wanted_visibility ? OverlayControlsFadeAnimation::Type::kToShown |
| : OverlayControlsFadeAnimation::Type::kToHidden); |
| fade_animation_->Start(); |
| } |
| } else { |
| // Instantly set the opacity for the title, top scrim and main controls. |
| title_fade_animation_.reset(); |
| controls_top_scrim_fade_animation_.reset(); |
| fade_animation_.reset(); |
| |
| if (Use2024UI()) { |
| GetTitleView()->layer()->SetOpacity(title_is_visible ? 1.0 : 0.0); |
| GetControlsTopScrimView()->layer()->SetOpacity(title_is_visible ? 1.0 |
| : 0.0); |
| } |
| GetControlsContainerView()->layer()->SetOpacity(wanted_visibility ? 1.0 |
| : 0.0); |
| GetControlsContainerView()->SetVisible(wanted_visibility); |
| } |
| } |
| |
| void VideoOverlayWindowViews::UpdateControlsBounds() { |
| // If controls are hidden, let's update controls bounds immediately. |
| // Otherwise, wait a bit before updating controls bounds to avoid too many |
| // changes happening too quickly. |
| if (!AreControlsVisible()) { |
| OnUpdateControlsBounds(); |
| return; |
| } |
| |
| update_controls_bounds_timer_ = std::make_unique<base::OneShotTimer>(); |
| update_controls_bounds_timer_->Start( |
| FROM_HERE, base::Seconds(1), |
| base::BindOnce(&VideoOverlayWindowViews::OnUpdateControlsBounds, |
| base::Unretained(this))); |
| } |
| |
| bool VideoOverlayWindowViews::IsLayoutPendingForTesting() const { |
| return update_controls_bounds_timer_ && |
| update_controls_bounds_timer_->IsRunning(); |
| } |
| |
| void VideoOverlayWindowViews::FinishTuckAnimationForTesting() { |
| if (tucker_) { |
| tucker_->FinishAnimationForTesting(); // IN-TEST |
| } |
| } |
| |
| bool VideoOverlayWindowViews::AreTitleAndScrimVisibleForTesting() const { |
| return AreTitleAndScrimVisible(); |
| } |
| |
| void VideoOverlayWindowViews::OnDisplayMetricsChanged( |
| const display::Display& display, |
| uint32_t changed_metrics) { |
| // Some display metric changes, such as display scaling, can affect the work |
| // area, so max size needs to be updated. |
| if (changed_metrics & display::DisplayObserver::DISPLAY_METRIC_WORK_AREA && |
| display.id() == display::Screen::Get() |
| ->GetDisplayNearestWindow(GetNativeWindow()) |
| .id()) { |
| UpdateMaxSize(GetWorkAreaForWindow()); |
| } |
| } |
| |
| void VideoOverlayWindowViews::OnViewVisibilityChanged( |
| views::View* observed_view, |
| views::View* starting_view, |
| bool visible) { |
| // If the visibility is changing due to a parent view/widget, then we don't |
| // care about it. |
| if (starting_view != overlay_view_) { |
| return; |
| } |
| |
| // The visibility of `overlay_view_` affects our minimum size. |
| OnSizeConstraintsChanged(); |
| } |
| |
| void VideoOverlayWindowViews::SetForcedTucking(bool tuck) { |
| if (!tucker_) { |
| tucker_ = std::make_unique<PictureInPictureTucker>(*this); |
| } |
| is_tucking_forced_ = tuck; |
| if (tuck) { |
| tucker_->Tuck(); |
| } else { |
| tucker_->Untuck(); |
| } |
| } |
| |
| void VideoOverlayWindowViews::OnAutoPipSettingOverlayViewHidden() { |
| // If there is an existing overlay view, remove it now. |
| RemoveOverlayViewIfExists(); |
| } |
| |
| gfx::Rect VideoOverlayWindowViews::GetWorkAreaForWindow() const { |
| return display::Screen::Get() |
| ->GetDisplayNearestWindow( |
| native_widget() && IsVisible() |
| ? GetNativeWindow() |
| : GetController()->GetWebContents()->GetTopLevelNativeWindow()) |
| .work_area(); |
| } |
| |
| void VideoOverlayWindowViews::UpdateMaxSize(const gfx::Rect& work_area) { |
| // An empty |work_area| is not valid, but it is sometimes reported as a |
| // transient value. |
| if (work_area.IsEmpty()) { |
| return; |
| } |
| |
| auto new_max_size = |
| gfx::Size(work_area.width() * 0.8, work_area.height() * 0.8); |
| |
| // Ensure |new_max_size| is not smaller than |min_size_|, or else we will |
| // crash. |
| new_max_size.SetToMax(min_size_); |
| |
| // Make sure we only run the logic to update the current size if the maximum |
| // size actually changes. Running it unconditionally means also running it |
| // when DPI <-> pixel computations introduce off-by-1 errors, which leads to |
| // incorrect window sizing/positioning. |
| if (new_max_size == max_size_) { |
| return; |
| } |
| |
| max_size_ = new_max_size; |
| |
| if (!native_widget()) { |
| return; |
| } |
| |
| // native_widget() is required for OnSizeConstraintsChanged. |
| OnSizeConstraintsChanged(); |
| |
| if (GetBounds().width() <= max_size_.width() && |
| GetBounds().height() <= max_size_.height()) { |
| return; |
| } |
| |
| gfx::Size clamped_size = GetBounds().size(); |
| clamped_size.SetToMin(max_size_); |
| SetSize(clamped_size); |
| } |
| |
| bool VideoOverlayWindowViews::ControlsHitTestContainsPoint( |
| const gfx::Point& point) { |
| if (overlay_view_) { |
| // Let the overlay view consume this event if it wants to. If not, then |
| // ignore any of our controls as well. This will still permit dragging the |
| // window by any parts that aren't consumed by the overlay view. |
| gfx::Point point_in_screen = |
| views::View::ConvertPointToScreen(non_client_view(), point); |
| return overlay_view_->WantsEvent(point_in_screen); |
| } |
| |
| if (!AreControlsVisible()) { |
| return false; |
| } |
| if (GetBackToTabControlsBounds().Contains(point) || |
| GetSkipAdControlsBounds().Contains(point) || |
| GetCloseControlsBounds().Contains(point) || |
| GetMinimizeControlsBounds().Contains(point) || |
| GetPlayPauseControlsBounds().Contains(point) || |
| GetReplay10SecondsButtonBounds().Contains(point) || |
| GetForward10SecondsButtonBounds().Contains(point) || |
| GetNextTrackControlsBounds().Contains(point) || |
| GetPreviousTrackControlsBounds().Contains(point) || |
| GetToggleMicrophoneButtonBounds().Contains(point) || |
| GetToggleCameraButtonBounds().Contains(point) || |
| GetHangUpButtonBounds().Contains(point) || |
| GetPreviousSlideControlsBounds().Contains(point) || |
| GetNextSlideControlsBounds().Contains(point) || |
| GetProgressViewBounds().Contains(point) || |
| GetLiveCaptionButtonBounds().Contains(point) || |
| GetLiveCaptionDialogBounds().Contains(point)) { |
| return true; |
| } |
| return false; |
| } |
| |
| content::PictureInPictureWindowController* |
| VideoOverlayWindowViews::GetController() const { |
| return controller_; |
| } |
| |
| views::View* VideoOverlayWindowViews::GetWindowBackgroundView() const { |
| return window_background_view_; |
| } |
| |
| views::View* VideoOverlayWindowViews::GetControlsContainerView() const { |
| return controls_container_view_; |
| } |
| |
| views::View* VideoOverlayWindowViews::GetTitleView() const { |
| return title_view_; |
| } |
| |
| views::View* VideoOverlayWindowViews::GetControlsTopScrimView() const { |
| return controls_top_scrim_view_; |
| } |
| |
| void VideoOverlayWindowViews::SetUpViews() { |
| // View that is displayed when video is hidden. ------------------------------ |
| // Adding an extra pixel to width/height makes sure controls background cover |
| // entirely window when platform has fractional scale applied. |
| auto window_background_view = std::make_unique<WindowBackgroundView>(); |
| auto video_view = std::make_unique<views::View>(); |
| auto controls_scrim_view = std::make_unique<ControlsBackgroundView>(); |
| auto controls_container_view = std::make_unique<views::View>(); |
| auto title_view = std::make_unique<views::View>(); |
| auto close_controls_view = std::make_unique<CloseImageButton>( |
| base::BindRepeating(&VideoOverlayWindowViews::CloseAndPauseIfAvailable, |
| base::Unretained(this))); |
| |
| auto play_pause_controls_view = |
| std::make_unique<PlaybackImageButton>(base::BindRepeating( |
| [](VideoOverlayWindowViews* overlay) { overlay->TogglePlayPause(); }, |
| base::Unretained(this))); |
| |
| // These controls may be different (or even nonexistent) depending on whether |
| // the 2024 updated UI is enabled. |
| std::unique_ptr<views::View> playback_controls_container_view; |
| std::unique_ptr<views::View> vc_controls_container_view; |
| std::unique_ptr<views::View> controls_top_scrim_view; |
| std::unique_ptr<views::View> controls_bottom_scrim_view; |
| std::unique_ptr<views::ImageView> favicon_view; |
| std::unique_ptr<views::Label> origin; |
| std::unique_ptr<OverlayWindowMinimizeButton> minimize_button; |
| std::unique_ptr<OverlayWindowBackToTabButton> back_to_tab_button; |
| std::unique_ptr<BackToTabLabelButton> back_to_tab_label_button; |
| std::unique_ptr<SimpleOverlayWindowImageButton> replay_10_seconds_button; |
| std::unique_ptr<SimpleOverlayWindowImageButton> forward_10_seconds_button; |
| std::unique_ptr<SimpleOverlayWindowImageButton> previous_track_controls_view; |
| std::unique_ptr<SimpleOverlayWindowImageButton> next_track_controls_view; |
| std::unique_ptr<SimpleOverlayWindowImageButton> previous_slide_controls_view; |
| std::unique_ptr<SimpleOverlayWindowImageButton> next_slide_controls_view; |
| std::unique_ptr<SkipAdLabelButton> skip_ad_controls_view; |
| std::unique_ptr<ToggleMicrophoneButton> toggle_microphone_button; |
| std::unique_ptr<ToggleCameraButton> toggle_camera_button; |
| std::unique_ptr<HangUpButton> hang_up_button; |
| std::unique_ptr<global_media_controls::MediaProgressView> progress_view; |
| std::unique_ptr<views::Label> timestamp; |
| std::unique_ptr<views::Label> live_status; |
| std::unique_ptr<OverlayWindowLiveCaptionButton> live_caption_button; |
| std::unique_ptr<OverlayWindowLiveCaptionDialog> live_caption_dialog; |
| |
| if (Use2024UI()) { |
| play_pause_controls_view->SetSize({kCenterButtonSize, kCenterButtonSize}); |
| playback_controls_container_view = std::make_unique<views::View>(); |
| vc_controls_container_view = std::make_unique<views::View>(); |
| controls_top_scrim_view = std::make_unique<views::View>(); |
| controls_top_scrim_view->SetBackground(std::make_unique<GradientBackground>( |
| SkColor4f::FromColor( |
| GetColorProvider()->GetColor(kColorPipWindowScrimTopGradientStart)), |
| SkColor4f::FromColor( |
| GetColorProvider()->GetColor(kColorPipWindowScrimTopGradientEnd)))); |
| controls_bottom_scrim_view = std::make_unique<views::View>(); |
| controls_bottom_scrim_view->SetBackground( |
| std::make_unique<GradientBackground>( |
| SkColor4f::FromColor(GetColorProvider()->GetColor( |
| kColorPipWindowScrimBottomGradientStart)), |
| SkColor4f::FromColor(GetColorProvider()->GetColor( |
| kColorPipWindowScrimBottomGradientEnd)))); |
| favicon_view = std::make_unique<views::ImageView>(); |
| favicon_view->SetSize(kFaviconSize); |
| origin = std::make_unique<views::Label>(std::u16string(), |
| views::style::CONTEXT_LABEL, |
| views::style::STYLE_BODY_4); |
| origin->SetEnabledColor(ui::kColorSysOnSurface); |
| origin->SetBackgroundColor(SK_ColorTRANSPARENT); |
| origin->SetHorizontalAlignment(gfx::ALIGN_LEFT); |
| origin->SetElideBehavior(gfx::ELIDE_HEAD); |
| minimize_button = std::make_unique< |
| OverlayWindowMinimizeButton>(base::BindRepeating( |
| [](VideoOverlayWindowViews* overlay) { |
| PictureInPictureWindowManager::GetInstance() |
| ->ExitPictureInPictureViaWindowUi( |
| PictureInPictureWindowManager::UiBehavior::kCloseWindowOnly); |
| }, |
| base::Unretained(this))); |
| back_to_tab_button = |
| std::make_unique<OverlayWindowBackToTabButton>(base::BindRepeating( |
| [](VideoOverlayWindowViews* overlay) { |
| PictureInPictureWindowManager::GetInstance() |
| ->ExitPictureInPictureViaWindowUi( |
| PictureInPictureWindowManager::UiBehavior:: |
| kCloseWindowAndFocusOpener); |
| }, |
| base::Unretained(this))); |
| replay_10_seconds_button = std::make_unique<SimpleOverlayWindowImageButton>( |
| base::BindRepeating( |
| [](VideoOverlayWindowViews* overlay) { |
| overlay->Replay10Seconds(); |
| }, |
| base::Unretained(this)), |
| vector_icons::kReplay10Icon, |
| l10n_util::GetStringUTF16(IDS_PICTURE_IN_PICTURE_REPLAY_10_TEXT)); |
| replay_10_seconds_button->SetSize(kActionButtonSize); |
| forward_10_seconds_button = |
| std::make_unique<SimpleOverlayWindowImageButton>( |
| base::BindRepeating( |
| [](VideoOverlayWindowViews* overlay) { |
| overlay->Forward10Seconds(); |
| }, |
| base::Unretained(this)), |
| vector_icons::kForward10Icon, |
| l10n_util::GetStringUTF16(IDS_PICTURE_IN_PICTURE_FORWARD_10_TEXT)); |
| forward_10_seconds_button->SetSize(kActionButtonSize); |
| previous_track_controls_view = |
| std::make_unique<SimpleOverlayWindowImageButton>( |
| base::BindRepeating( |
| [](VideoOverlayWindowViews* overlay) { |
| if (overlay->show_previous_track_button_) { |
| overlay->controller_->PreviousTrack(); |
| } else if (overlay->show_previous_slide_button_) { |
| overlay->controller_->PreviousSlide(); |
| } |
| }, |
| base::Unretained(this)), |
| vector_icons::kSkipPreviousIcon, |
| l10n_util::GetStringUTF16( |
| IDS_PICTURE_IN_PICTURE_PREVIOUS_TRACK_CONTROL_ACCESSIBLE_TEXT)); |
| previous_track_controls_view->SetSize(kActionButtonSize); |
| next_track_controls_view = std::make_unique<SimpleOverlayWindowImageButton>( |
| base::BindRepeating( |
| [](VideoOverlayWindowViews* overlay) { |
| if (overlay->show_next_track_button_) { |
| overlay->controller_->NextTrack(); |
| } else if (overlay->show_next_slide_button_) { |
| overlay->controller_->NextSlide(); |
| } |
| }, |
| base::Unretained(this)), |
| vector_icons::kSkipNextIcon, |
| l10n_util::GetStringUTF16( |
| IDS_PICTURE_IN_PICTURE_NEXT_TRACK_CONTROL_ACCESSIBLE_TEXT)); |
| next_track_controls_view->SetSize(kActionButtonSize); |
| // `base::Unretained()` is okay here since we own the progress view. |
| progress_view = std::make_unique<global_media_controls::MediaProgressView>( |
| /*use_squiggly_line=*/false, |
| /*playing_foreground_color_id=*/ui::kColorSysPrimary, |
| /*playing_background_color_id=*/ui::kColorSysStateDisabled, |
| /*paused_foreground_color_id=*/ui::kColorSysStateDisabled, |
| /*paused_background_color_id=*/ui::kColorSysStateDisabled, |
| /*focus_ring_color_id=*/ui::kColorSysStateFocusRing, |
| /*drag_state_change_callback=*/ |
| base::BindRepeating( |
| &VideoOverlayWindowViews::OnProgressDragStateChanged, |
| base::Unretained(this)), |
| /*playback_state_change_for_dragging_callback=*/ |
| base::BindRepeating( |
| &VideoOverlayWindowViews::ChangePlaybackStateForProgressDrag, |
| base::Unretained(this)), |
| /*seek_callback=*/ |
| base::BindRepeating( |
| &VideoOverlayWindowViews::SeekForProgressBarInteraction, |
| base::Unretained(this)), |
| /*on_update_progress_callback=*/ |
| base::BindRepeating( |
| &VideoOverlayWindowViews::OnProgressViewUpdateCurrentTime, |
| base::Unretained(this))); |
| timestamp = std::make_unique<views::Label>(std::u16string(), |
| views::style::CONTEXT_LABEL, |
| views::style::STYLE_BODY_4); |
| timestamp->SetEnabledColor(ui::kColorSysOnSurfaceSubtle); |
| timestamp->SetBackgroundColor(SK_ColorTRANSPARENT); |
| timestamp->SetHorizontalAlignment(gfx::ALIGN_LEFT); |
| live_status = std::make_unique<views::Label>( |
| l10n_util::GetStringUTF16(IDS_PICTURE_IN_PICTURE_LIVE_STATUS_TEXT), |
| views::style::CONTEXT_LABEL, views::style::STYLE_CAPTION_BOLD); |
| live_status->SetEnabledColor(ui::kColorSysTonalContainer); |
| live_status->SetBorder(views::CreateEmptyBorder(gfx::Insets::VH(0, 4))); |
| live_status->SetBackground( |
| views::CreateRoundedRectBackground(ui::kColorSysOnTonalContainer, 4)); |
| live_status->SetVisible(false); |
| live_caption_button = |
| std::make_unique<OverlayWindowLiveCaptionButton>(base::BindRepeating( |
| &VideoOverlayWindowViews::OnLiveCaptionButtonPressed, |
| base::Unretained(this))); |
| live_caption_button->SetSize(kActionButtonSize); |
| live_caption_button->SetIsLiveCaptionDialogOpen(false); |
| live_caption_dialog = std::make_unique<OverlayWindowLiveCaptionDialog>( |
| Profile::FromBrowserContext( |
| controller_->GetWebContents()->GetBrowserContext())); |
| live_caption_dialog->SetVisible(false); |
| toggle_microphone_button = |
| std::make_unique<ToggleMicrophoneButton>(base::BindRepeating( |
| [](VideoOverlayWindowViews* overlay) { |
| overlay->controller_->ToggleMicrophone(); |
| }, |
| base::Unretained(this))); |
| toggle_microphone_button->SetVisible(false); |
| toggle_microphone_button->SetSize(kActionButtonSize); |
| toggle_camera_button = |
| std::make_unique<ToggleCameraButton>(base::BindRepeating( |
| [](VideoOverlayWindowViews* overlay) { |
| overlay->controller_->ToggleCamera(); |
| }, |
| base::Unretained(this))); |
| toggle_camera_button->SetVisible(false); |
| toggle_camera_button->SetSize(kActionButtonSize); |
| hang_up_button = std::make_unique<HangUpButton>(base::BindRepeating( |
| [](VideoOverlayWindowViews* overlay) { |
| overlay->controller_->HangUp(); |
| }, |
| base::Unretained(this))); |
| hang_up_button->SetSize({kCenterButtonSize, kCenterButtonSize}); |
| hang_up_button->SetVisible(false); |
| } else { |
| back_to_tab_label_button = |
| std::make_unique<BackToTabLabelButton>(base::BindRepeating( |
| [](VideoOverlayWindowViews* overlay) { |
| PictureInPictureWindowManager::GetInstance() |
| ->ExitPictureInPictureViaWindowUi( |
| PictureInPictureWindowManager::UiBehavior:: |
| kCloseWindowAndFocusOpener); |
| }, |
| base::Unretained(this))); |
| previous_track_controls_view = |
| std::make_unique<SimpleOverlayWindowImageButton>( |
| base::BindRepeating( |
| [](VideoOverlayWindowViews* overlay) { |
| overlay->controller_->PreviousTrack(); |
| }, |
| base::Unretained(this)), |
| vector_icons::kMediaPreviousTrackIcon, |
| l10n_util::GetStringUTF16( |
| IDS_PICTURE_IN_PICTURE_PREVIOUS_TRACK_CONTROL_ACCESSIBLE_TEXT)); |
| next_track_controls_view = std::make_unique<SimpleOverlayWindowImageButton>( |
| base::BindRepeating( |
| [](VideoOverlayWindowViews* overlay) { |
| overlay->controller_->NextTrack(); |
| }, |
| base::Unretained(this)), |
| vector_icons::kMediaNextTrackIcon, |
| l10n_util::GetStringUTF16( |
| IDS_PICTURE_IN_PICTURE_NEXT_TRACK_CONTROL_ACCESSIBLE_TEXT)); |
| previous_slide_controls_view = |
| std::make_unique<SimpleOverlayWindowImageButton>( |
| base::BindRepeating( |
| [](VideoOverlayWindowViews* overlay) { |
| overlay->controller_->PreviousSlide(); |
| }, |
| base::Unretained(this)), |
| vector_icons::kMediaPreviousTrackIcon, |
| l10n_util::GetStringUTF16( |
| IDS_PICTURE_IN_PICTURE_PREVIOUS_SLIDE_CONTROL_ACCESSIBLE_TEXT)); |
| next_slide_controls_view = std::make_unique<SimpleOverlayWindowImageButton>( |
| base::BindRepeating( |
| [](VideoOverlayWindowViews* overlay) { |
| overlay->controller_->NextSlide(); |
| }, |
| base::Unretained(this)), |
| vector_icons::kMediaNextTrackIcon, |
| l10n_util::GetStringUTF16( |
| IDS_PICTURE_IN_PICTURE_NEXT_SLIDE_CONTROL_ACCESSIBLE_TEXT)); |
| skip_ad_controls_view = |
| std::make_unique<SkipAdLabelButton>(base::BindRepeating( |
| [](VideoOverlayWindowViews* overlay) { |
| overlay->controller_->SkipAd(); |
| }, |
| base::Unretained(this))); |
| toggle_microphone_button = |
| std::make_unique<ToggleMicrophoneButton>(base::BindRepeating( |
| [](VideoOverlayWindowViews* overlay) { |
| overlay->controller_->ToggleMicrophone(); |
| }, |
| base::Unretained(this))); |
| toggle_camera_button = |
| std::make_unique<ToggleCameraButton>(base::BindRepeating( |
| [](VideoOverlayWindowViews* overlay) { |
| overlay->controller_->ToggleCamera(); |
| }, |
| base::Unretained(this))); |
| hang_up_button = std::make_unique<HangUpButton>(base::BindRepeating( |
| [](VideoOverlayWindowViews* overlay) { |
| overlay->controller_->HangUp(); |
| }, |
| base::Unretained(this))); |
| } |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| auto resize_handle_view = |
| std::make_unique<ResizeHandleButton>(views::Button::PressedCallback()); |
| #endif |
| |
| window_background_view->SetPaintToLayer(ui::LAYER_SOLID_COLOR); |
| window_background_view->layer()->SetName("WindowBackgroundView"); |
| |
| // view::View that holds the video. ----------------------------------------- |
| video_view->SetPaintToLayer(ui::LAYER_TEXTURED); |
| video_view->layer()->SetMasksToBounds(true); |
| video_view->layer()->SetFillsBoundsOpaquely(false); |
| video_view->layer()->SetName("VideoView"); |
| |
| // views::View that holds the scrim, which appears with the controls. ------- |
| controls_scrim_view->SetPaintToLayer(ui::LAYER_SOLID_COLOR); |
| controls_scrim_view->layer()->SetName("ControlsScrimView"); |
| |
| // views::View that is a parent of all the controls. Makes hiding and showing |
| // all the controls at once easier. |
| controls_container_view->SetPaintToLayer(ui::LAYER_NOT_DRAWN); |
| controls_container_view->layer()->SetFillsBoundsOpaquely(false); |
| controls_container_view->layer()->SetName("ControlsContainerView"); |
| |
| // views::View that closes the window. -------------------------------------- |
| close_controls_view->SetPaintToLayer(ui::LAYER_TEXTURED); |
| close_controls_view->layer()->SetFillsBoundsOpaquely(false); |
| close_controls_view->layer()->SetName("CloseControlsView"); |
| |
| if (Use2024UI()) { |
| // Contains controls for playback. ---------------------------------------- |
| playback_controls_container_view->SetPaintToLayer(ui::LAYER_NOT_DRAWN); |
| playback_controls_container_view->layer()->SetFillsBoundsOpaquely(false); |
| playback_controls_container_view->layer()->SetName( |
| "PlaybackControlsContainerView"); |
| |
| // Contains controls for video conferencing. ------------------------------ |
| vc_controls_container_view->SetPaintToLayer(ui::LAYER_NOT_DRAWN); |
| vc_controls_container_view->layer()->SetFillsBoundsOpaquely(false); |
| vc_controls_container_view->layer()->SetName("VcControlsContainerView"); |
| |
| // The scrim for the top controls. ---------------------------------------- |
| controls_top_scrim_view->SetPaintToLayer(ui::LAYER_TEXTURED); |
| controls_top_scrim_view->layer()->SetFillsBoundsOpaquely(false); |
| controls_top_scrim_view->layer()->SetName("ControlsTopScrimView"); |
| |
| // The scrim for the bottom controls. ------------------------------------- |
| controls_bottom_scrim_view->SetPaintToLayer(ui::LAYER_TEXTURED); |
| controls_bottom_scrim_view->layer()->SetFillsBoundsOpaquely(false); |
| controls_bottom_scrim_view->layer()->SetName("ControlsBottomScrimView"); |
| |
| // views::View that displays the website's favicon. ----------------------- |
| favicon_view->SetPaintToLayer(ui::LAYER_TEXTURED); |
| favicon_view->layer()->SetFillsBoundsOpaquely(false); |
| favicon_view->layer()->SetName("FaviconView"); |
| |
| // Displays the source title (website's origin or extension/PWA name). ---- |
| origin->SetPaintToLayer(ui::LAYER_TEXTURED); |
| origin->layer()->SetFillsBoundsOpaquely(false); |
| origin->layer()->SetName("Origin"); |
| |
| // views::View that closes the window without pausing. -------------------- |
| minimize_button->SetPaintToLayer(ui::LAYER_TEXTURED); |
| minimize_button->layer()->SetFillsBoundsOpaquely(false); |
| minimize_button->layer()->SetName("OverlayWindowMinimizeButton"); |
| |
| // views::View that closes the window and focuses initiator tab. ---------- |
| back_to_tab_button->SetPaintToLayer(ui::LAYER_TEXTURED); |
| back_to_tab_button->layer()->SetFillsBoundsOpaquely(false); |
| back_to_tab_button->layer()->SetName("BackToTabControlsView"); |
| |
| // views::View that displays the window title. The window title consists of |
| // the origin and favicon. Always displayed together with the controls top |
| // scrim view. |
| title_view->SetPaintToLayer(ui::LAYER_TEXTURED); |
| title_view->layer()->SetFillsBoundsOpaquely(false); |
| title_view->layer()->SetName("TitleView"); |
| } else { |
| // views::View that closes the window and focuses initiator tab. ---------- |
| CHECK(back_to_tab_label_button); |
| back_to_tab_label_button->SetPaintToLayer(ui::LAYER_TEXTURED); |
| back_to_tab_label_button->layer()->SetFillsBoundsOpaquely(false); |
| back_to_tab_label_button->layer()->SetName("BackToTabControlsView"); |
| } |
| |
| // views::View that holds the previous-track image button. ------------------ |
| previous_track_controls_view->SetPaintToLayer(ui::LAYER_TEXTURED); |
| previous_track_controls_view->layer()->SetFillsBoundsOpaquely(false); |
| previous_track_controls_view->layer()->SetName("PreviousTrackControlsView"); |
| |
| // views::View that toggles play/pause/replay. ------------------------------ |
| play_pause_controls_view->SetPaintToLayer(ui::LAYER_TEXTURED); |
| play_pause_controls_view->layer()->SetFillsBoundsOpaquely(false); |
| play_pause_controls_view->layer()->SetName("PlayPauseControlsView"); |
| play_pause_controls_view->SetPlaybackState( |
| controller_->IsPlayerActive() ? kPlaying : kPaused); |
| |
| // views::View that holds the next-track image button. ---------------------- |
| next_track_controls_view->SetPaintToLayer(ui::LAYER_TEXTURED); |
| next_track_controls_view->layer()->SetFillsBoundsOpaquely(false); |
| next_track_controls_view->layer()->SetName("NextTrackControlsView"); |
| |
| if (Use2024UI()) { |
| replay_10_seconds_button->SetPaintToLayer(ui::LAYER_TEXTURED); |
| replay_10_seconds_button->layer()->SetFillsBoundsOpaquely(false); |
| replay_10_seconds_button->layer()->SetName("Replay10SecondsButton"); |
| |
| forward_10_seconds_button->SetPaintToLayer(ui::LAYER_TEXTURED); |
| forward_10_seconds_button->layer()->SetFillsBoundsOpaquely(false); |
| forward_10_seconds_button->layer()->SetName("Forward10SecondsButton"); |
| |
| progress_view->SetPaintToLayer(ui::LAYER_TEXTURED); |
| progress_view->layer()->SetFillsBoundsOpaquely(false); |
| progress_view->layer()->SetName("ProgressView"); |
| |
| timestamp->SetPaintToLayer(ui::LAYER_TEXTURED); |
| timestamp->layer()->SetFillsBoundsOpaquely(false); |
| timestamp->layer()->SetName("Timestamp"); |
| |
| live_status->SetPaintToLayer(ui::LAYER_TEXTURED); |
| live_status->layer()->SetFillsBoundsOpaquely(false); |
| live_status->layer()->SetName("LiveStatus"); |
| |
| live_caption_button->SetPaintToLayer(ui::LAYER_TEXTURED); |
| live_caption_button->layer()->SetFillsBoundsOpaquely(false); |
| live_caption_button->layer()->SetName("LiveCaptionButton"); |
| |
| live_caption_dialog->SetPaintToLayer(ui::LAYER_TEXTURED); |
| live_caption_dialog->layer()->SetFillsBoundsOpaquely(false); |
| live_caption_dialog->layer()->SetName("LiveCaptionDialog"); |
| } else { |
| // views::View that holds the skip-ad label button. |
| // ------------------------- |
| skip_ad_controls_view->SetPaintToLayer(ui::LAYER_TEXTURED); |
| skip_ad_controls_view->layer()->SetFillsBoundsOpaquely(true); |
| skip_ad_controls_view->layer()->SetName("SkipAdControlsView"); |
| |
| previous_slide_controls_view->SetPaintToLayer(ui::LAYER_TEXTURED); |
| previous_slide_controls_view->layer()->SetFillsBoundsOpaquely(false); |
| previous_slide_controls_view->layer()->SetName("PreviousSlideButton"); |
| |
| next_slide_controls_view->SetPaintToLayer(ui::LAYER_TEXTURED); |
| next_slide_controls_view->layer()->SetFillsBoundsOpaquely(false); |
| next_slide_controls_view->layer()->SetName("NextSlideButton"); |
| } |
| |
| toggle_microphone_button->SetPaintToLayer(ui::LAYER_TEXTURED); |
| toggle_microphone_button->layer()->SetFillsBoundsOpaquely(false); |
| toggle_microphone_button->layer()->SetName("ToggleMicrophoneButton"); |
| |
| toggle_camera_button->SetPaintToLayer(ui::LAYER_TEXTURED); |
| toggle_camera_button->layer()->SetFillsBoundsOpaquely(false); |
| toggle_camera_button->layer()->SetName("ToggleCameraButton"); |
| |
| hang_up_button->SetPaintToLayer(ui::LAYER_TEXTURED); |
| hang_up_button->layer()->SetFillsBoundsOpaquely(false); |
| hang_up_button->layer()->SetName("HangUpButton"); |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| // views::View that shows the affordance that the window can be resized. ---- |
| resize_handle_view->SetPaintToLayer(ui::LAYER_TEXTURED); |
| resize_handle_view->layer()->SetFillsBoundsOpaquely(false); |
| resize_handle_view->layer()->SetName("ResizeHandleView"); |
| resize_handle_view->layer()->SetOpacity(kResizeHandleOpacity); |
| #endif |
| |
| // Set up view::Views hierarchy. -------------------------------------------- |
| window_background_view_ = |
| AddChildView(&view_holder_, std::move(window_background_view)); |
| video_view_ = AddChildView(&view_holder_, std::move(video_view)); |
| controls_scrim_view_ = |
| controls_container_view->AddChildView(std::move(controls_scrim_view)); |
| if (Use2024UI()) { |
| controls_bottom_scrim_view_ = controls_container_view->AddChildView( |
| std::move(controls_bottom_scrim_view)); |
| playback_controls_container_view_ = controls_container_view->AddChildView( |
| std::move(playback_controls_container_view)); |
| vc_controls_container_view_ = controls_container_view->AddChildView( |
| std::move(vc_controls_container_view)); |
| } |
| |
| // For the 2024 UI, playback and VC controls are in separate containers. |
| views::View* playback_container = |
| Use2024UI() ? playback_controls_container_view_.get() |
| : controls_container_view.get(); |
| views::View* vc_container = Use2024UI() ? vc_controls_container_view_.get() |
| : controls_container_view.get(); |
| |
| // Even though most controls are on both the updated UI and the legacy UI, |
| // they are ordered differently (so that focus order matches UI order), so |
| // here we have separate sections for inserting UI elements. |
| if (Use2024UI()) { |
| // Initialize the favicon view with the default icon. |
| favicon_view_ = title_view->AddChildView(std::move(favicon_view)); |
| UpdateFavicon(gfx::ImageSkia()); |
| |
| origin_ = title_view->AddChildView(std::move(origin)); |
| minimize_button_ = |
| controls_container_view->AddChildView(std::move(minimize_button)); |
| back_to_tab_button_ = |
| controls_container_view->AddChildView(std::move(back_to_tab_button)); |
| close_controls_view_ = |
| controls_container_view->AddChildView(std::move(close_controls_view)); |
| |
| replay_10_seconds_button_ = playback_controls_container_view_->AddChildView( |
| std::move(replay_10_seconds_button)); |
| play_pause_controls_view_ = |
| playback_container->AddChildView(std::move(play_pause_controls_view)); |
| forward_10_seconds_button_ = |
| playback_controls_container_view_->AddChildView( |
| std::move(forward_10_seconds_button)); |
| |
| previous_track_controls_view_ = playback_container->AddChildView( |
| std::move(previous_track_controls_view)); |
| progress_view_ = playback_controls_container_view_->AddChildView( |
| std::move(progress_view)); |
| next_track_controls_view_ = |
| playback_container->AddChildView(std::move(next_track_controls_view)); |
| |
| timestamp_ = |
| playback_controls_container_view_->AddChildView(std::move(timestamp)); |
| live_status_ = |
| playback_controls_container_view_->AddChildView(std::move(live_status)); |
| |
| live_caption_button_ = playback_controls_container_view_->AddChildView( |
| std::move(live_caption_button)); |
| live_caption_dialog_ = |
| controls_container_view->AddChildView(std::move(live_caption_dialog)); |
| |
| toggle_camera_button_ = |
| vc_container->AddChildView(std::move(toggle_camera_button)); |
| hang_up_button_ = vc_container->AddChildView(std::move(hang_up_button)); |
| toggle_microphone_button_ = |
| vc_container->AddChildView(std::move(toggle_microphone_button)); |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| resize_handle_view_ = |
| controls_container_view->AddChildView(std::move(resize_handle_view)); |
| #endif |
| |
| // The top scrim is added before the other views so it is drawn behind them. |
| controls_top_scrim_view_ = |
| AddChildView(&view_holder_, std::move(controls_top_scrim_view)); |
| controls_container_view_ = |
| AddChildView(&view_holder_, std::move(controls_container_view)); |
| title_view_ = AddChildView(&view_holder_, std::move(title_view)); |
| } else { |
| // !Use2024UI(): |
| close_controls_view_ = |
| controls_container_view->AddChildView(std::move(close_controls_view)); |
| CHECK(back_to_tab_label_button); |
| back_to_tab_label_button_ = controls_container_view->AddChildView( |
| std::move(back_to_tab_label_button)); |
| previous_track_controls_view_ = playback_container->AddChildView( |
| std::move(previous_track_controls_view)); |
| previous_slide_controls_view_ = controls_container_view->AddChildView( |
| std::move(previous_slide_controls_view)); |
| play_pause_controls_view_ = |
| playback_container->AddChildView(std::move(play_pause_controls_view)); |
| next_track_controls_view_ = |
| playback_container->AddChildView(std::move(next_track_controls_view)); |
| next_slide_controls_view_ = controls_container_view->AddChildView( |
| std::move(next_slide_controls_view)); |
| skip_ad_controls_view_ = |
| controls_container_view->AddChildView(std::move(skip_ad_controls_view)); |
| toggle_microphone_button_ = |
| vc_container->AddChildView(std::move(toggle_microphone_button)); |
| toggle_camera_button_ = |
| vc_container->AddChildView(std::move(toggle_camera_button)); |
| hang_up_button_ = vc_container->AddChildView(std::move(hang_up_button)); |
| #if BUILDFLAG(IS_CHROMEOS) |
| resize_handle_view_ = |
| controls_container_view->AddChildView(std::move(resize_handle_view)); |
| #endif |
| controls_container_view_ = |
| AddChildView(&view_holder_, std::move(controls_container_view)); |
| } |
| } |
| |
| void VideoOverlayWindowViews::OnRootViewReady() { |
| #if BUILDFLAG(IS_CHROMEOS) |
| GetNativeWindow()->SetProperty(ash::kWindowPipTypeKey, true); |
| highlight_border_overlay_ = |
| std::make_unique<HighlightBorderOverlay>(this, nullptr); |
| #endif // BUILDFLAG(IS_CHROMEOS) |
| |
| GetRootView()->SetPaintToLayer(ui::LAYER_TEXTURED); |
| GetRootView()->layer()->SetName("RootView"); |
| GetRootView()->layer()->SetMasksToBounds(true); |
| |
| views::View* const contents_view = GetContentsView(); |
| for (std::unique_ptr<views::View>& child : view_holder_) { |
| contents_view->AddChildView(std::move(child)); |
| } |
| view_holder_.clear(); |
| |
| // Don't show the controls until the mouse hovers over the window. |
| UpdateControlsVisibility(false, /*should_animate=*/false); |
| } |
| |
| void VideoOverlayWindowViews::UpdateLayerBoundsWithLetterboxing( |
| gfx::Size window_size) { |
| // This is the case when the window is initially created or the video surface |
| // id has not been embedded. |
| if (!native_widget() || GetBounds().IsEmpty() || GetNaturalSize().IsEmpty()) { |
| return; |
| } |
| |
| gfx::Rect letterbox_region = media::ComputeLetterboxRegion( |
| gfx::Rect(gfx::Point(0, 0), window_size), GetNaturalSize()); |
| if (letterbox_region.IsEmpty()) { |
| return; |
| } |
| |
| // To avoid black stripes in the window when integer window dimensions don't |
| // correspond to the video aspect ratio exactly (e.g. 854x480 for 16:9 |
| // video) force the letterbox region size to be equal to the window size. |
| const float aspect_ratio = |
| static_cast<float>(GetNaturalSize().width()) / GetNaturalSize().height(); |
| if (aspect_ratio > 1 && window_size.height() == letterbox_region.height()) { |
| const int height_from_width = |
| base::ClampRound(window_size.width() / aspect_ratio); |
| if (height_from_width == window_size.height()) { |
| letterbox_region.set_width(window_size.width()); |
| } |
| } else if (aspect_ratio <= 1 && |
| window_size.width() == letterbox_region.width()) { |
| const int width_from_height = |
| base::ClampRound(window_size.height() * aspect_ratio); |
| if (width_from_height == window_size.width()) { |
| letterbox_region.set_height(window_size.height()); |
| } |
| } |
| |
| const gfx::Rect video_bounds( |
| gfx::Point((window_size.width() - letterbox_region.size().width()) / 2, |
| (window_size.height() - letterbox_region.size().height()) / 2), |
| letterbox_region.size()); |
| |
| // Update the layout of the controls. |
| UpdateControlsBounds(); |
| |
| // Update the surface layer bounds to scale with window size changes. |
| window_background_view_->SetBoundsRect( |
| gfx::Rect(gfx::Point(0, 0), GetBounds().size())); |
| video_view_->SetBoundsRect(video_bounds); |
| if (video_view_->layer()->has_external_content()) { |
| video_view_->layer()->SetSurfaceSize(video_bounds.size()); |
| } |
| |
| if (IsOverlayViewShown()) { |
| overlay_view_->SetBoundsRect(gfx::Rect(GetBounds().size())); |
| } |
| |
| // Notify the controller that the bounds have changed. |
| controller_->UpdateLayerBounds(); |
| } |
| |
| void VideoOverlayWindowViews::OnUpdateControlsBounds() { |
| controls_container_view_->SetSize(GetBounds().size()); |
| |
| // Adding an extra pixel to width/height makes sure the scrim covers the |
| // entire window when the platform has fractional scaling applied. |
| gfx::Rect larger_window_bounds = gfx::Rect(GetBounds().size()); |
| larger_window_bounds.Inset(-1); |
| controls_scrim_view_->SetBoundsRect(larger_window_bounds); |
| |
| WindowQuadrant quadrant = GetCurrentWindowQuadrant(GetBounds(), controller_); |
| close_controls_view_->SetPosition(GetBounds().size(), quadrant); |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| UpdateResizeHandleBounds(quadrant); |
| #endif |
| |
| // The 2024 updated UI lays out controls differently. |
| if (Use2024UI()) { |
| constexpr int kTopControlsHeight = 34; |
| constexpr int kBottomControlsHeight = 64; |
| constexpr int kTopScrimHeight = 160; |
| constexpr int kBottomScrimHeight = 160; |
| constexpr int kFaviconLeftMargin = 8; |
| constexpr int kFaviconTopMargin = 5; |
| constexpr int kFaviconRightMargin = 4; |
| constexpr int kOriginTopMargin = 5; |
| constexpr int kOriginHeight = 24; |
| constexpr int kOriginRightMargin = 80; |
| constexpr int kProgressBarHeight = 26; |
| constexpr int kCenterControlMargin = 16; |
| constexpr int kBottomControlsHorizontalMargin = 12; |
| constexpr int kBottomControlsVerticalMargin = 4; |
| constexpr int kTimestampHorizontalMargin = 16; |
| constexpr int kTimestampVerticalMargin = 10; |
| constexpr int kTimestampHeight = 16; |
| |
| gfx::Rect bounds = GetBounds(); |
| bounds.set_origin({0, 0}); |
| |
| // The top bar takes up the first `kTopControlsHeight` pixels of vertical |
| // space. |
| gfx::Rect top_controls_bounds(bounds.x(), bounds.y(), bounds.width(), |
| kTopControlsHeight); |
| |
| // The bottom controls take up the last `kBottomControlsHeight` pixels of |
| // vertical space. |
| gfx::Rect bottom_controls_bounds( |
| bounds.x(), bounds.y() + bounds.height() - kBottomControlsHeight, |
| bounds.width(), kBottomControlsHeight); |
| |
| // The rest of the vertical space is used for the middle controls area. |
| gfx::Rect middle_controls_bounds = gfx::BoundingRect( |
| top_controls_bounds.bottom_left(), bottom_controls_bounds.top_right()); |
| |
| playback_controls_container_view_->SetSize(bounds.size()); |
| vc_controls_container_view_->SetSize(bounds.size()); |
| controls_top_scrim_view_->SetBoundsRect( |
| {top_controls_bounds.x(), top_controls_bounds.y(), |
| top_controls_bounds.width(), kTopScrimHeight}); |
| controls_bottom_scrim_view_->SetBoundsRect( |
| {bottom_controls_bounds.x(), |
| bottom_controls_bounds.y() + bottom_controls_bounds.height() - |
| kBottomScrimHeight, |
| bottom_controls_bounds.width(), kBottomScrimHeight}); |
| |
| gfx::Rect favicon_view_bounds({top_controls_bounds.x() + kFaviconLeftMargin, |
| top_controls_bounds.y() + kFaviconTopMargin}, |
| kFaviconSize); |
| favicon_view_->SetPosition(favicon_view_bounds.origin()); |
| |
| gfx::Point origin_position( |
| favicon_view_bounds.right() + kFaviconRightMargin, kOriginTopMargin); |
| origin_->SetPosition(origin_position); |
| origin_->SetSize( |
| {top_controls_bounds.width() - origin_position.x() - kOriginRightMargin, |
| kOriginHeight}); |
| |
| minimize_button_->SetPosition(GetBounds().size()); |
| back_to_tab_button_->SetPosition(GetBounds().size()); |
| |
| // Positioning of the middle row of controls. |
| const gfx::Point center_control_position( |
| middle_controls_bounds.CenterPoint().x() - kCenterButtonSize / 2, |
| middle_controls_bounds.CenterPoint().y() - kCenterButtonSize / 2); |
| const gfx::Point center_left_control_position( |
| center_control_position.x() - kCenterControlMargin - |
| kActionButtonSize.width(), |
| middle_controls_bounds.CenterPoint().y() - |
| kActionButtonSize.height() / 2); |
| const gfx::Point center_right_control_position( |
| center_control_position.x() + kCenterButtonSize + kCenterControlMargin, |
| middle_controls_bounds.CenterPoint().y() - |
| kActionButtonSize.height() / 2); |
| |
| // If any VC control is visible, then we will hide the playback controls |
| // and just show VC controls. |
| hang_up_button_->SetVisible(show_hang_up_button_); |
| toggle_camera_button_->SetVisible(show_toggle_camera_button_); |
| toggle_microphone_button_->SetVisible(show_toggle_microphone_button_); |
| if (show_toggle_camera_button_ || show_toggle_microphone_button_ || |
| show_hang_up_button_) { |
| hang_up_button_->SetPosition(center_control_position); |
| toggle_camera_button_->SetPosition(center_left_control_position); |
| toggle_microphone_button_->SetPosition(center_right_control_position); |
| vc_controls_container_view_->SetVisible(true); |
| playback_controls_container_view_->SetVisible(false); |
| return; |
| } |
| playback_controls_container_view_->SetVisible(true); |
| vc_controls_container_view_->SetVisible(false); |
| |
| play_pause_controls_view_->SetPosition(center_control_position); |
| replay_10_seconds_button_->SetPosition(center_left_control_position); |
| forward_10_seconds_button_->SetPosition(center_right_control_position); |
| |
| // The previous and next track buttons are placed on the top left/right |
| // edges of the bottom controls area. |
| previous_track_controls_view_->SetPosition( |
| {bottom_controls_bounds.x() + kBottomControlsHorizontalMargin, |
| bottom_controls_bounds.y() + kBottomControlsVerticalMargin}); |
| next_track_controls_view_->SetPosition( |
| {bottom_controls_bounds.x() + bottom_controls_bounds.width() - |
| (kBottomControlsHorizontalMargin + kActionButtonSize.width()), |
| bottom_controls_bounds.y() + kBottomControlsVerticalMargin}); |
| |
| // The previous and next track buttons are always both visible if at least |
| // one of them is visible. |
| const bool should_enable_prev = |
| show_previous_track_button_ || show_previous_slide_button_; |
| const bool should_enable_next = |
| show_next_track_button_ || show_next_slide_button_; |
| const bool should_show_prev_next = should_enable_prev || should_enable_next; |
| previous_track_controls_view_->SetVisible(should_show_prev_next); |
| next_track_controls_view_->SetVisible(should_show_prev_next); |
| previous_track_controls_view_->SetEnabled(should_enable_prev); |
| next_track_controls_view_->SetEnabled(should_enable_next); |
| |
| // The progress bars should take up all the space that is left after the |
| // previous and next buttons. Here we calculate how much horizontal space |
| // one of those buttons takes up and use that to calculate the width and x |
| // position of the progress view. |
| constexpr int kPreviousNextTrackWidthPlusHorizontalMargins = |
| kBottomControlsHorizontalMargin + kActionButtonSize.width(); |
| const int used_horizontal_space_left_of_progress_bar = |
| should_show_prev_next ? kPreviousNextTrackWidthPlusHorizontalMargins |
| : kBottomControlsHorizontalMargin; |
| progress_view_->SetPosition( |
| {bottom_controls_bounds.x() + |
| used_horizontal_space_left_of_progress_bar, |
| bottom_controls_bounds.y() + kBottomControlsVerticalMargin}); |
| progress_view_->SetSize( |
| {bounds.width() - (2 * used_horizontal_space_left_of_progress_bar), |
| kProgressBarHeight}); |
| |
| gfx::Point timestamp_position( |
| bottom_controls_bounds.x() + kTimestampHorizontalMargin, |
| bottom_controls_bounds.y() + bottom_controls_bounds.height() - |
| kTimestampVerticalMargin - kTimestampHeight); |
| const int max_timestamp_width = |
| bottom_controls_bounds.width() - (2 * kTimestampHorizontalMargin); |
| timestamp_->SetPosition(timestamp_position); |
| timestamp_->SetSize({max_timestamp_width, kTimestampHeight}); |
| timestamp_->SetVisible(!is_live_); |
| |
| live_status_->SetPosition(timestamp_position); |
| live_status_->SetMaximumWidthSingleLine(max_timestamp_width); |
| live_status_->SetSize( |
| {live_status_->GetPreferredSize({max_timestamp_width, kTimestampHeight}) |
| .width(), |
| kTimestampHeight}); |
| live_status_->SetVisible(is_live_); |
| |
| gfx::Rect live_caption_button_bounds( |
| bottom_controls_bounds.right() - kBottomControlsHorizontalMargin - |
| kActionButtonSize.width(), |
| bottom_controls_bounds.bottom() - kBottomControlsVerticalMargin - |
| kActionButtonSize.height(), |
| live_caption_button_->width(), live_caption_button_->height()); |
| |
| live_caption_button_->SetPosition(live_caption_button_bounds.origin()); |
| |
| live_caption_dialog_->SetPosition( |
| {live_caption_button_bounds.right() - live_caption_dialog_->width(), |
| live_caption_button_bounds.y() - live_caption_dialog_->height()}); |
| |
| // The play/pause button and replay/forward 10 seconds buttons should not be |
| // visible while dragging the progress bar or for live media. |
| const bool is_dragging_progress_bar = |
| progress_view_drag_state_ == |
| global_media_controls::DragState::kDragStarted; |
| play_pause_controls_view_->SetVisible(show_play_pause_button_ && |
| !is_dragging_progress_bar); |
| replay_10_seconds_button_->SetVisible(!is_dragging_progress_bar && |
| !is_live_); |
| forward_10_seconds_button_->SetVisible(!is_dragging_progress_bar && |
| !is_live_); |
| |
| return; |
| } |
| |
| CHECK(back_to_tab_label_button_); |
| back_to_tab_label_button_->SetWindowSize(GetBounds().size()); |
| |
| skip_ad_controls_view_->SetPosition(GetBounds().size()); |
| |
| // Following controls order matters: |
| // #1 Previous track |
| // #2 Previous slide |
| // #3 Play/Pause |
| // #4 Next track |
| // #5 Next slide |
| // #6 Toggle microphone |
| // #7 Toggle camera |
| // #8 Hang up |
| std::vector<views::ImageButton*> visible_controls_views; |
| if (show_previous_track_button_) { |
| visible_controls_views.push_back(previous_track_controls_view_); |
| } |
| if (show_previous_slide_button_) { |
| visible_controls_views.push_back(previous_slide_controls_view_); |
| } |
| if (show_play_pause_button_) { |
| visible_controls_views.push_back(play_pause_controls_view_); |
| } |
| if (show_next_track_button_) { |
| visible_controls_views.push_back(next_track_controls_view_); |
| } |
| if (show_next_slide_button_) { |
| visible_controls_views.push_back(next_slide_controls_view_); |
| } |
| if (show_toggle_microphone_button_) { |
| visible_controls_views.push_back(toggle_microphone_button_); |
| } |
| if (show_toggle_camera_button_) { |
| visible_controls_views.push_back(toggle_camera_button_); |
| } |
| if (show_hang_up_button_) { |
| visible_controls_views.push_back(hang_up_button_); |
| } |
| |
| if (visible_controls_views.size() > 4) { |
| visible_controls_views.resize(4); |
| } |
| |
| int mid_window_x = GetBounds().size().width() / 2; |
| int primary_control_y = GetBounds().size().height() - |
| kPrimaryControlSize.height() - |
| kPrimaryControlBottomMargin; |
| int secondary_control_y = GetBounds().size().height() - |
| kSecondaryControlSize.height() - |
| kSecondaryControlBottomMargin; |
| |
| switch (visible_controls_views.size()) { |
| case 0: |
| break; |
| case 1: { |
| /* | --- --- [ ] --- --- | */ |
| visible_controls_views[0]->SetSize(kSecondaryControlSize); |
| visible_controls_views[0]->SetPosition( |
| gfx::Point(mid_window_x - kSecondaryControlSize.width() / 2, |
| secondary_control_y)); |
| break; |
| } |
| case 2: { |
| /* | ----- [ ] [ ] ----- | */ |
| visible_controls_views[0]->SetSize(kSecondaryControlSize); |
| visible_controls_views[0]->SetPosition(gfx::Point( |
| mid_window_x - kControlMargin / 2 - kSecondaryControlSize.width(), |
| secondary_control_y)); |
| |
| visible_controls_views[1]->SetSize(kSecondaryControlSize); |
| visible_controls_views[1]->SetPosition( |
| gfx::Point(mid_window_x + kControlMargin / 2, secondary_control_y)); |
| break; |
| } |
| case 3: { |
| /* | --- [ ] [ ] [ ] --- | */ |
| // Middle control is primary only if it's play/pause control. |
| if (visible_controls_views[1] == play_pause_controls_view_) { |
| visible_controls_views[0]->SetSize(kSecondaryControlSize); |
| visible_controls_views[0]->SetPosition( |
| gfx::Point(mid_window_x - kPrimaryControlSize.width() / 2 - |
| kControlMargin - kSecondaryControlSize.width(), |
| secondary_control_y)); |
| |
| visible_controls_views[1]->SetSize(kPrimaryControlSize); |
| visible_controls_views[1]->SetPosition(gfx::Point( |
| mid_window_x - kPrimaryControlSize.width() / 2, primary_control_y)); |
| |
| visible_controls_views[2]->SetSize(kSecondaryControlSize); |
| visible_controls_views[2]->SetPosition(gfx::Point( |
| mid_window_x + kPrimaryControlSize.width() / 2 + kControlMargin, |
| secondary_control_y)); |
| } else { |
| visible_controls_views[0]->SetSize(kSecondaryControlSize); |
| visible_controls_views[0]->SetPosition( |
| gfx::Point(mid_window_x - kSecondaryControlSize.width() / 2 - |
| kControlMargin - kSecondaryControlSize.width(), |
| secondary_control_y)); |
| |
| visible_controls_views[1]->SetSize(kSecondaryControlSize); |
| visible_controls_views[1]->SetPosition( |
| gfx::Point(mid_window_x - kSecondaryControlSize.width() / 2, |
| secondary_control_y)); |
| |
| visible_controls_views[2]->SetSize(kSecondaryControlSize); |
| visible_controls_views[2]->SetPosition(gfx::Point( |
| mid_window_x + kSecondaryControlSize.width() / 2 + kControlMargin, |
| secondary_control_y)); |
| } |
| break; |
| } |
| case 4: { |
| /* | - [ ] [ ] [ ] [ ] - | */ |
| visible_controls_views[0]->SetSize(kSecondaryControlSize); |
| visible_controls_views[0]->SetPosition( |
| gfx::Point(mid_window_x - kControlMargin * 3 / 2 - |
| kSecondaryControlSize.width() * 2, |
| secondary_control_y)); |
| |
| visible_controls_views[1]->SetSize(kSecondaryControlSize); |
| visible_controls_views[1]->SetPosition(gfx::Point( |
| mid_window_x - kControlMargin / 2 - kSecondaryControlSize.width(), |
| secondary_control_y)); |
| |
| visible_controls_views[2]->SetSize(kSecondaryControlSize); |
| visible_controls_views[2]->SetPosition( |
| gfx::Point(mid_window_x + kControlMargin / 2, secondary_control_y)); |
| |
| visible_controls_views[3]->SetSize(kSecondaryControlSize); |
| visible_controls_views[3]->SetPosition(gfx::Point( |
| mid_window_x + kControlMargin * 3 / 2 + kSecondaryControlSize.width(), |
| secondary_control_y)); |
| break; |
| } |
| default: |
| NOTREACHED(); |
| } |
| |
| // This will actually update the visibility of a control that was just added |
| // or removed, see SetPlayPauseButtonVisibility(), etc. |
| previous_track_controls_view_->SetVisible(show_previous_track_button_); |
| play_pause_controls_view_->SetVisible(show_play_pause_button_); |
| next_track_controls_view_->SetVisible(show_next_track_button_); |
| skip_ad_controls_view_->SetVisible(show_skip_ad_button_); |
| toggle_microphone_button_->SetVisible(show_toggle_microphone_button_); |
| toggle_camera_button_->SetVisible(show_toggle_camera_button_); |
| hang_up_button_->SetVisible(show_hang_up_button_); |
| previous_slide_controls_view_->SetVisible(show_previous_slide_button_); |
| next_slide_controls_view_->SetVisible(show_next_slide_button_); |
| } |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| void VideoOverlayWindowViews::UpdateResizeHandleBounds( |
| WindowQuadrant quadrant) { |
| resize_handle_view_->SetPosition(GetBounds().size(), quadrant); |
| GetNativeWindow()->SetProperty( |
| ash::kWindowPipResizeHandleBoundsKey, |
| new gfx::Rect(GetResizeHandleControlsBounds())); |
| } |
| #endif |
| |
| bool VideoOverlayWindowViews::IsActive() const { |
| return views::Widget::IsActive(); |
| } |
| |
| void VideoOverlayWindowViews::Close() { |
| views::Widget::Close(); |
| MaybeUnregisterFrameSinkHierarchy(); |
| if (fade_animator_) { |
| fade_animator_->CancelAndReset(); |
| } |
| PictureInPictureWindowManager::GetInstance()->OnPictureInPictureWindowHidden( |
| this); |
| } |
| |
| void VideoOverlayWindowViews::ShowInactive() { |
| // Fade in animation is disabled for Document and Video Picture-in-Picture on |
| // Windows. On Windows, resizable windows can not be translucent. See |
| // crbug.com/425711450. |
| #if BUILDFLAG(IS_WIN) |
| views::Widget::ShowInactive(); |
| #else |
| if (base::FeatureList::IsEnabled( |
| media::kPictureInPictureShowWindowAnimation)) { |
| if (!fade_animator_) { |
| fade_animator_ = std::make_unique<PictureInPictureWidgetFadeAnimator>(); |
| } |
| fade_animator_->AnimateShowWindow( |
| this, |
| PictureInPictureWidgetFadeAnimator::WidgetShowType::kShowInactive); |
| } else { |
| views::Widget::ShowInactive(); |
| } |
| #endif |
| |
| views::Widget::SetVisibleOnAllWorkspaces(true); |
| #if BUILDFLAG(IS_CHROMEOS) |
| non_client_view()->frame_view()->UpdateWindowRoundedCorners(); |
| #endif |
| |
| // If there is an existing overlay view, remove it now. |
| RemoveOverlayViewIfExists(); |
| |
| // TODO(crbug.com/40278613): Confirm whether the anchor should remain as |
| // FLOAT. |
| auto overlay_view = |
| get_overlay_view_cb_ |
| ? get_overlay_view_cb_.Run() |
| : PictureInPictureWindowManager::GetInstance()->GetOverlayView( |
| window_background_view_, views::BubbleBorder::Arrow::FLOAT); |
| // Re-add it if needed. |
| if (overlay_view) { |
| overlay_view_ = GetContentsView()->AddChildView(std::move(overlay_view)); |
| overlay_view_->views::View::AddObserver(this); |
| overlay_view_->set_delegate(this); |
| // Also update the bounds, since that's already happened for everything |
| // else, potentially, during widget resize. |
| overlay_view_->SetBoundsRect(gfx::Rect(GetBounds().size())); |
| overlay_view_->ShowBubble(GetNativeView()); |
| SetBounds(CalculateAndUpdateWindowBounds()); |
| } |
| |
| if (Use2024UI()) { |
| // When the window is first shown, make the title and top controls |
| // visible for a few seconds. |
| initial_title_hide_timer_.Start( |
| FROM_HERE, kTitleShowDuration, |
| // base::Unretained() is safe since the callback will not be called |
| // after `initial_title_hide_timer_` is destroyed, and it is owned by |
| // this object. |
| base::BindOnce(&VideoOverlayWindowViews::OnInitialTitleTimerFired, |
| base::Unretained(this))); |
| // The controls are not visible, but the title should be. |
| UpdateControlsVisibility(false); |
| } |
| |
| // If this is not the first time the window is shown, this will be a no-op. |
| has_been_shown_ = true; |
| |
| // If we're still tucked from a previous session and it's no longer necessary, |
| // then untuck now. |
| if (is_tucking_forced_ && !PictureInPictureWindowManager::GetInstance() |
| ->IsPictureInPictureForceTucked()) { |
| SetForcedTucking(false); |
| } |
| PictureInPictureWindowManager::GetInstance()->OnPictureInPictureWindowShown( |
| this); |
| } |
| |
| void VideoOverlayWindowViews::Hide() { |
| // If there is an existing overlay view, remove it now. |
| RemoveOverlayViewIfExists(); |
| views::Widget::Hide(); |
| if (fade_animator_) { |
| fade_animator_->CancelAndReset(); |
| } |
| MaybeUnregisterFrameSinkHierarchy(); |
| PictureInPictureWindowManager::GetInstance()->OnPictureInPictureWindowHidden( |
| this); |
| } |
| |
| bool VideoOverlayWindowViews::IsVisible() const { |
| return views::Widget::IsVisible(); |
| } |
| |
| gfx::Rect VideoOverlayWindowViews::GetBounds() { |
| if (!native_widget()) { |
| return gfx::Rect(); |
| } |
| |
| return base::FeatureList::IsEnabled(media::kUseWindowBoundsForPip) |
| ? GetWindowBoundsInScreen() |
| : GetRestoredBounds(); |
| } |
| |
| void VideoOverlayWindowViews::UpdateNaturalSize(const gfx::Size& natural_size) { |
| DCHECK(!natural_size.IsEmpty()); |
| natural_size_ = natural_size; |
| SetAspectRatio(gfx::SizeF(natural_size_)); |
| |
| // Update the views::Widget bounds to adhere to sizing spec. This will also |
| // update the layout of the controls. |
| SetBounds(CalculateAndUpdateWindowBounds()); |
| if (is_tucking_forced_) { |
| tucker_->Tuck(); |
| } |
| } |
| |
| void VideoOverlayWindowViews::SetPlaybackState(PlaybackState playback_state) { |
| playback_state_for_testing_ = playback_state; |
| play_pause_controls_view_->SetPlaybackState(playback_state); |
| } |
| |
| void VideoOverlayWindowViews::SetPlayPauseButtonVisibility(bool is_visible) { |
| if (show_play_pause_button_ == is_visible) { |
| return; |
| } |
| |
| show_play_pause_button_ = is_visible; |
| UpdateControlsBounds(); |
| } |
| |
| void VideoOverlayWindowViews::SetSkipAdButtonVisibility(bool is_visible) { |
| if (show_skip_ad_button_ == is_visible) { |
| return; |
| } |
| |
| show_skip_ad_button_ = is_visible; |
| UpdateControlsBounds(); |
| } |
| |
| void VideoOverlayWindowViews::SetPreviousSlideButtonVisibility( |
| bool is_visible) { |
| if (show_previous_slide_button_ == is_visible) { |
| return; |
| } |
| |
| show_previous_slide_button_ = is_visible; |
| UpdateControlsBounds(); |
| } |
| |
| void VideoOverlayWindowViews::SetNextSlideButtonVisibility(bool is_visible) { |
| if (show_next_slide_button_ == is_visible) { |
| return; |
| } |
| |
| show_next_slide_button_ = is_visible; |
| UpdateControlsBounds(); |
| } |
| |
| void VideoOverlayWindowViews::SetNextTrackButtonVisibility(bool is_visible) { |
| if (show_next_track_button_ == is_visible) { |
| return; |
| } |
| |
| show_next_track_button_ = is_visible; |
| UpdateControlsBounds(); |
| } |
| |
| void VideoOverlayWindowViews::SetPreviousTrackButtonVisibility( |
| bool is_visible) { |
| if (show_previous_track_button_ == is_visible) { |
| return; |
| } |
| |
| show_previous_track_button_ = is_visible; |
| UpdateControlsBounds(); |
| } |
| |
| void VideoOverlayWindowViews::SetMicrophoneMuted(bool muted) { |
| toggle_microphone_button_->SetMutedState(muted); |
| } |
| |
| void VideoOverlayWindowViews::SetCameraState(bool turned_on) { |
| toggle_camera_button_->SetCameraState(turned_on); |
| } |
| |
| void VideoOverlayWindowViews::SetToggleMicrophoneButtonVisibility( |
| bool is_visible) { |
| if (show_toggle_microphone_button_ == is_visible) { |
| return; |
| } |
| |
| show_toggle_microphone_button_ = is_visible; |
| UpdateControlsBounds(); |
| } |
| |
| void VideoOverlayWindowViews::SetToggleCameraButtonVisibility(bool is_visible) { |
| if (show_toggle_camera_button_ == is_visible) { |
| return; |
| } |
| |
| show_toggle_camera_button_ = is_visible; |
| UpdateControlsBounds(); |
| } |
| |
| void VideoOverlayWindowViews::SetHangUpButtonVisibility(bool is_visible) { |
| if (show_hang_up_button_ == is_visible) { |
| return; |
| } |
| |
| show_hang_up_button_ = is_visible; |
| UpdateControlsBounds(); |
| } |
| |
| void VideoOverlayWindowViews::SetMediaPosition( |
| const media_session::MediaPosition& position) { |
| if (!Use2024UI()) { |
| return; |
| } |
| position_ = position; |
| progress_view_->UpdateProgress(position); |
| UpdateTimestampLabel(position_.GetPosition(), position_.duration()); |
| } |
| |
| void VideoOverlayWindowViews::SetSourceTitle( |
| const std::u16string& source_title) { |
| if (Use2024UI()) { |
| origin_->SetText(source_title); |
| } |
| } |
| |
| void VideoOverlayWindowViews::SetFaviconImages( |
| const std::vector<media_session::MediaImage>& images) { |
| if (!Use2024UI()) { |
| return; |
| } |
| |
| media_session::MediaImageManager manager(gfx::kFaviconSize, |
| gfx::kFaviconSize); |
| std::optional<media_session::MediaImage> image = manager.SelectImage(images); |
| |
| if (!image) { |
| UpdateFavicon(gfx::ImageSkia()); |
| return; |
| } |
| |
| controller_->GetMediaImage( |
| *image, gfx::kFaviconSize, gfx::kFaviconSize, |
| base::BindOnce(&VideoOverlayWindowViews::OnFaviconReceived, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void VideoOverlayWindowViews::SetSurfaceId(const viz::SurfaceId& surface_id) { |
| // The PiP window may have a previous surface set. If the window stays open |
| // since then, we need to unregister the previous frame sink; otherwise the |
| // surface frame sink should already be removed when the window closed. |
| MaybeUnregisterFrameSinkHierarchy(); |
| |
| // Add the new frame sink to the PiP window and set the surface. |
| GetCompositor()->AddChildFrameSink(surface_id.frame_sink_id()); |
| has_registered_frame_sink_hierarchy_ = true; |
| video_view_->layer()->SetShowSurface( |
| surface_id, GetBounds().size(), |
| GetColorProvider()->GetColor(kColorPipWindowBackground), |
| cc::DeadlinePolicy::UseDefaultDeadline(), |
| true /* stretch_content_to_fill_bounds */); |
| } |
| |
| void VideoOverlayWindowViews::OnNativeWidgetDestroying() { |
| views::Widget::OnNativeWidgetDestroying(); |
| MaybeUnregisterFrameSinkHierarchy(); |
| } |
| |
| void VideoOverlayWindowViews::OnNativeWidgetDestroyed() { |
| views::Widget::OnNativeWidgetDestroyed(); |
| controller_->OnWindowDestroyed( |
| /*should_pause_video=*/show_play_pause_button_); |
| } |
| |
| // When the PiP window is moved to different displays on Chrome OS, we need to |
| // re-parent the frame sink since the compositor will change. After |
| // OnNativeWidgetRemovingFromCompositor() is called, the window layer containing |
| // the compositor will be removed in Window::RemoveChildImpl(), and |
| // OnNativeWidgetAddedToCompositor() is called once another compositor is added. |
| void VideoOverlayWindowViews::OnNativeWidgetAddedToCompositor() { |
| if (!has_registered_frame_sink_hierarchy_ && GetCurrentFrameSinkId()) { |
| GetCompositor()->AddChildFrameSink(*GetCurrentFrameSinkId()); |
| has_registered_frame_sink_hierarchy_ = true; |
| } |
| } |
| |
| void VideoOverlayWindowViews::OnNativeWidgetRemovingFromCompositor() { |
| MaybeUnregisterFrameSinkHierarchy(); |
| } |
| |
| void VideoOverlayWindowViews::OnGestureEvent(ui::GestureEvent* event) { |
| MaybeUpdateMeetsUserInteraction(*event); |
| |
| if (OnGestureEventHandledOrIgnored(event)) { |
| return; |
| } |
| |
| if (live_caption_dialog_ && live_caption_dialog_->GetVisible()) { |
| if (!GetLiveCaptionDialogBounds().Contains(event->location())) { |
| // Hide the live caption dialog if it's visible and the user taps outside |
| // of it. |
| SetLiveCaptionDialogVisibility(false); |
| event->SetHandled(); |
| return; |
| } |
| |
| // Otherwise, let the live caption dialog handle the gesture. |
| live_caption_dialog_->OnGestureTapEvent(event); |
| return; |
| } |
| |
| if (GetBackToTabControlsBounds().Contains(event->location())) { |
| controller_->CloseAndFocusInitiator(); |
| event->SetHandled(); |
| } else if (GetSkipAdControlsBounds().Contains(event->location())) { |
| controller_->SkipAd(); |
| event->SetHandled(); |
| } else if (GetCloseControlsBounds().Contains(event->location())) { |
| CloseAndPauseIfAvailable(); |
| event->SetHandled(); |
| } else if (GetMinimizeControlsBounds().Contains(event->location())) { |
| PictureInPictureWindowManager::GetInstance() |
| ->ExitPictureInPictureViaWindowUi( |
| PictureInPictureWindowManager::UiBehavior::kCloseWindowOnly); |
| event->SetHandled(); |
| } else if (GetPlayPauseControlsBounds().Contains(event->location())) { |
| TogglePlayPause(); |
| event->SetHandled(); |
| } else if (GetNextTrackControlsBounds().Contains(event->location())) { |
| controller_->NextTrack(); |
| event->SetHandled(); |
| } else if (GetPreviousTrackControlsBounds().Contains(event->location())) { |
| controller_->PreviousTrack(); |
| event->SetHandled(); |
| } else if (GetToggleMicrophoneButtonBounds().Contains(event->location())) { |
| controller_->ToggleMicrophone(); |
| event->SetHandled(); |
| } else if (GetToggleCameraButtonBounds().Contains(event->location())) { |
| controller_->ToggleCamera(); |
| event->SetHandled(); |
| } else if (GetHangUpButtonBounds().Contains(event->location())) { |
| controller_->HangUp(); |
| event->SetHandled(); |
| } else if (GetReplay10SecondsButtonBounds().Contains(event->location())) { |
| Replay10Seconds(); |
| event->SetHandled(); |
| } else if (GetForward10SecondsButtonBounds().Contains(event->location())) { |
| Forward10Seconds(); |
| event->SetHandled(); |
| } else if (GetLiveCaptionButtonBounds().Contains(event->location())) { |
| OnLiveCaptionButtonPressed(); |
| event->SetHandled(); |
| } |
| } |
| |
| gfx::Rect VideoOverlayWindowViews::GetBackToTabControlsBounds() { |
| if (Use2024UI()) { |
| return back_to_tab_button_->GetMirroredBounds(); |
| } |
| CHECK(back_to_tab_label_button_); |
| return back_to_tab_label_button_->GetMirroredBounds(); |
| } |
| |
| gfx::Rect VideoOverlayWindowViews::GetSkipAdControlsBounds() { |
| // The 2024 UI does not yet have a skip ad button implemented. |
| if (Use2024UI()) { |
| return gfx::Rect(); |
| } |
| return skip_ad_controls_view_->GetMirroredBounds(); |
| } |
| |
| gfx::Rect VideoOverlayWindowViews::GetCloseControlsBounds() { |
| return close_controls_view_->GetMirroredBounds(); |
| } |
| |
| gfx::Rect VideoOverlayWindowViews::GetMinimizeControlsBounds() { |
| if (!Use2024UI()) { |
| return gfx::Rect(); |
| } |
| return minimize_button_->GetMirroredBounds(); |
| } |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| gfx::Rect VideoOverlayWindowViews::GetResizeHandleControlsBounds() { |
| return resize_handle_view_->GetMirroredBounds(); |
| } |
| #endif |
| |
| gfx::Rect VideoOverlayWindowViews::GetPlayPauseControlsBounds() { |
| return play_pause_controls_view_->GetMirroredBounds(); |
| } |
| |
| gfx::Rect VideoOverlayWindowViews::GetReplay10SecondsButtonBounds() { |
| if (!Use2024UI()) { |
| return gfx::Rect(); |
| } |
| return replay_10_seconds_button_->GetMirroredBounds(); |
| } |
| |
| gfx::Rect VideoOverlayWindowViews::GetForward10SecondsButtonBounds() { |
| if (!Use2024UI()) { |
| return gfx::Rect(); |
| } |
| return forward_10_seconds_button_->GetMirroredBounds(); |
| } |
| |
| gfx::Rect VideoOverlayWindowViews::GetNextTrackControlsBounds() { |
| return next_track_controls_view_->GetMirroredBounds(); |
| } |
| |
| gfx::Rect VideoOverlayWindowViews::GetPreviousTrackControlsBounds() { |
| return previous_track_controls_view_->GetMirroredBounds(); |
| } |
| |
| gfx::Rect VideoOverlayWindowViews::GetToggleMicrophoneButtonBounds() { |
| return toggle_microphone_button_->GetMirroredBounds(); |
| } |
| |
| gfx::Rect VideoOverlayWindowViews::GetToggleCameraButtonBounds() { |
| return toggle_camera_button_->GetMirroredBounds(); |
| } |
| |
| gfx::Rect VideoOverlayWindowViews::GetHangUpButtonBounds() { |
| return hang_up_button_->GetMirroredBounds(); |
| } |
| |
| gfx::Rect VideoOverlayWindowViews::GetPreviousSlideControlsBounds() { |
| // The 2024 UI combines the previous slide button with the previous track |
| // button. |
| if (Use2024UI()) { |
| return gfx::Rect(); |
| } |
| return previous_slide_controls_view_->GetMirroredBounds(); |
| } |
| |
| gfx::Rect VideoOverlayWindowViews::GetNextSlideControlsBounds() { |
| // The 2024 UI combines the next slide button with the next track button. |
| if (Use2024UI()) { |
| return gfx::Rect(); |
| } |
| return next_slide_controls_view_->GetMirroredBounds(); |
| } |
| |
| gfx::Rect VideoOverlayWindowViews::GetProgressViewBounds() { |
| if (!Use2024UI()) { |
| return gfx::Rect(); |
| } |
| return progress_view_->GetMirroredBounds(); |
| } |
| |
| gfx::Rect VideoOverlayWindowViews::GetLiveCaptionButtonBounds() { |
| if (!Use2024UI()) { |
| return gfx::Rect(); |
| } |
| return live_caption_button_->GetMirroredBounds(); |
| } |
| |
| gfx::Rect VideoOverlayWindowViews::GetLiveCaptionDialogBounds() { |
| if (!Use2024UI() || !live_caption_dialog_->GetVisible()) { |
| return gfx::Rect(); |
| } |
| return live_caption_dialog_->GetMirroredBounds(); |
| } |
| |
| bool VideoOverlayWindowViews::HasHighMediaEngagement( |
| const url::Origin& origin) const { |
| MediaEngagementService* service = |
| MediaEngagementService::Get(Profile::FromBrowserContext( |
| GetController()->GetWebContents()->GetBrowserContext())); |
| if (!service) { |
| return false; |
| } |
| |
| return service->HasHighEngagement(origin); |
| } |
| |
| bool VideoOverlayWindowViews::IsTrustedForMediaPlayback() const { |
| content::MediaSession* media_session = |
| content::MediaSession::GetIfExists(GetController()->GetWebContents()); |
| if (!media_session) { |
| return false; |
| } |
| |
| content::RenderFrameHost* rfh = media_session->GetRoutedFrame(); |
| if (rfh == nullptr) { |
| return false; |
| } |
| |
| if (!rfh->IsInPrimaryMainFrame()) { |
| return false; |
| } |
| |
| const url::Origin origin = rfh->GetLastCommittedOrigin(); |
| if (origin.GetURL().SchemeIsFile()) { |
| return true; |
| } |
| |
| return HasHighMediaEngagement(origin); |
| } |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| int VideoOverlayWindowViews::GetResizeHTComponent() const { |
| return resize_handle_view_->GetHTComponent(); |
| } |
| #endif |
| |
| void VideoOverlayWindowViews::TogglePlayPause() { |
| // Retrieve expected active state based on what command was sent in |
| // TogglePlayPause() since the IPC message may not have been propagated |
| // the media player yet. |
| bool is_active = controller_->TogglePlayPause(); |
| play_pause_controls_view_->SetPlaybackState(is_active ? kPlaying : kPaused); |
| } |
| |
| void VideoOverlayWindowViews::Replay10Seconds() { |
| controller_->SeekTo( |
| std::max(base::Seconds(0), position_.GetPosition() - kSeekTime)); |
| } |
| |
| void VideoOverlayWindowViews::Forward10Seconds() { |
| controller_->SeekTo( |
| std::min(position_.GetPosition() + kSeekTime, position_.duration())); |
| } |
| |
| void VideoOverlayWindowViews::CloseAndPauseIfAvailable() { |
| // Only pause the video if play/pause is available. |
| const bool should_pause_video = !!show_play_pause_button_; |
| PictureInPictureWindowManager::GetInstance()->ExitPictureInPictureViaWindowUi( |
| should_pause_video |
| ? PictureInPictureWindowManager::UiBehavior::kCloseWindowAndPauseVideo |
| : PictureInPictureWindowManager::UiBehavior::kCloseWindowOnly); |
| } |
| |
| PlaybackImageButton* |
| VideoOverlayWindowViews::play_pause_controls_view_for_testing() const { |
| return play_pause_controls_view_; |
| } |
| |
| SimpleOverlayWindowImageButton* |
| VideoOverlayWindowViews::replay_10_seconds_button_for_testing() const { |
| return replay_10_seconds_button_; |
| } |
| |
| SimpleOverlayWindowImageButton* |
| VideoOverlayWindowViews::forward_10_seconds_button_for_testing() const { |
| return forward_10_seconds_button_; |
| } |
| |
| SimpleOverlayWindowImageButton* |
| VideoOverlayWindowViews::next_track_controls_view_for_testing() const { |
| return next_track_controls_view_; |
| } |
| |
| SimpleOverlayWindowImageButton* |
| VideoOverlayWindowViews::previous_track_controls_view_for_testing() const { |
| return previous_track_controls_view_; |
| } |
| |
| SkipAdLabelButton* VideoOverlayWindowViews::skip_ad_controls_view_for_testing() |
| const { |
| return skip_ad_controls_view_; |
| } |
| |
| ToggleMicrophoneButton* |
| VideoOverlayWindowViews::toggle_microphone_button_for_testing() const { |
| return toggle_microphone_button_; |
| } |
| |
| ToggleCameraButton* VideoOverlayWindowViews::toggle_camera_button_for_testing() |
| const { |
| return toggle_camera_button_; |
| } |
| |
| HangUpButton* VideoOverlayWindowViews::hang_up_button_for_testing() const { |
| return hang_up_button_; |
| } |
| |
| SimpleOverlayWindowImageButton* |
| VideoOverlayWindowViews::next_slide_controls_view_for_testing() const { |
| return next_slide_controls_view_; |
| } |
| |
| SimpleOverlayWindowImageButton* |
| VideoOverlayWindowViews::previous_slide_controls_view_for_testing() const { |
| return previous_slide_controls_view_; |
| } |
| |
| global_media_controls::MediaProgressView* |
| VideoOverlayWindowViews::progress_view_for_testing() const { |
| return progress_view_; |
| } |
| |
| views::Label* VideoOverlayWindowViews::timestamp_for_testing() const { |
| return timestamp_; |
| } |
| |
| views::Label* VideoOverlayWindowViews::live_status_for_testing() const { |
| return live_status_; |
| } |
| |
| OverlayWindowLiveCaptionButton* |
| VideoOverlayWindowViews::live_caption_button_for_testing() const { |
| return live_caption_button_; |
| } |
| |
| OverlayWindowLiveCaptionDialog* |
| VideoOverlayWindowViews::live_caption_dialog_for_testing() const { |
| return live_caption_dialog_; |
| } |
| |
| views::ImageView* VideoOverlayWindowViews::favicon_view_for_testing() const { |
| return favicon_view_; |
| } |
| |
| views::Label* VideoOverlayWindowViews::origin_for_testing() const { |
| return origin_; |
| } |
| |
| CloseImageButton* VideoOverlayWindowViews::close_button_for_testing() const { |
| return close_controls_view_; |
| } |
| |
| OverlayWindowMinimizeButton* |
| VideoOverlayWindowViews::minimize_button_for_testing() const { |
| return minimize_button_; |
| } |
| |
| OverlayWindowBackToTabButton* |
| VideoOverlayWindowViews::back_to_tab_button_for_testing() const { |
| return back_to_tab_button_; |
| } |
| |
| gfx::Point VideoOverlayWindowViews::close_image_position_for_testing() const { |
| return close_controls_view_->origin(); |
| } |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| gfx::Point VideoOverlayWindowViews::resize_handle_position_for_testing() const { |
| return resize_handle_view_->origin(); |
| } |
| #endif |
| |
| VideoOverlayWindowViews::PlaybackState |
| VideoOverlayWindowViews::playback_state_for_testing() const { |
| return playback_state_for_testing_; |
| } |
| |
| ui::Layer* VideoOverlayWindowViews::video_layer_for_testing() const { |
| return video_view_->layer(); |
| } |
| |
| views::View* VideoOverlayWindowViews::title_view_for_testing() const { |
| return title_view_; |
| } |
| |
| views::View* VideoOverlayWindowViews::controls_top_scrim_view_for_testing() |
| const { |
| return controls_top_scrim_view_; |
| } |
| |
| base::OneShotTimer& |
| VideoOverlayWindowViews::initial_title_hide_timer_for_testing() { |
| return initial_title_hide_timer_; |
| } |
| |
| const viz::FrameSinkId* VideoOverlayWindowViews::GetCurrentFrameSinkId() const { |
| if (auto* surface = video_view_->layer()->GetSurfaceId()) { |
| return &surface->frame_sink_id(); |
| } |
| |
| return nullptr; |
| } |
| |
| void VideoOverlayWindowViews::MaybeUnregisterFrameSinkHierarchy() { |
| if (has_registered_frame_sink_hierarchy_) { |
| DCHECK(GetCurrentFrameSinkId()); |
| GetCompositor()->RemoveChildFrameSink(*GetCurrentFrameSinkId()); |
| has_registered_frame_sink_hierarchy_ = false; |
| } |
| } |
| |
| bool VideoOverlayWindowViews::IsOverlayViewShown() const { |
| return overlay_view_ && overlay_view_->GetVisible(); |
| } |
| |
| void VideoOverlayWindowViews::RemoveOverlayViewIfExists() { |
| if (overlay_view_) { |
| // Remove and delete the outgoing view. Note the trailing `T` on the method |
| // name -- this removes `overlay_view_` and returns a unique_ptr to it which |
| // we then discard. Without the `T`, it returns nothing and frees nothing. |
| overlay_view_->RemoveObserver(this); |
| GetContentsView()->RemoveChildViewT(overlay_view_.ExtractAsDangling()); |
| OnSizeConstraintsChanged(); |
| } |
| } |
| |
| void VideoOverlayWindowViews::OnProgressDragStateChanged( |
| global_media_controls::DragState drag_state) { |
| progress_view_drag_state_ = drag_state; |
| OnUpdateControlsBounds(); |
| } |
| |
| void VideoOverlayWindowViews::ChangePlaybackStateForProgressDrag( |
| global_media_controls::PlaybackStateChangeForDragging |
| playback_state_change) { |
| if (playback_state_change == |
| global_media_controls::PlaybackStateChangeForDragging:: |
| kPauseForDraggingStarted) { |
| controller_->Pause(); |
| } else { |
| controller_->Play(); |
| } |
| } |
| |
| void VideoOverlayWindowViews::SeekForProgressBarInteraction( |
| double seek_progress) { |
| controller_->SeekTo(seek_progress * position_.duration()); |
| } |
| |
| void VideoOverlayWindowViews::OnProgressViewUpdateCurrentTime( |
| base::TimeDelta current_time) { |
| UpdateTimestampLabel(current_time, position_.duration()); |
| } |
| |
| void VideoOverlayWindowViews::UpdateTimestampLabel(base::TimeDelta current_time, |
| base::TimeDelta duration) { |
| bool was_live = is_live_; |
| is_live_ = duration.is_max(); |
| |
| if (!is_live_) { |
| timestamp_->SetText(base::StrCat( |
| {global_media_controls::GetFormattedDuration(current_time), u" / ", |
| global_media_controls::GetFormattedDuration(duration)})); |
| } |
| if (was_live != is_live_) { |
| OnUpdateControlsBounds(); |
| } |
| } |
| |
| void VideoOverlayWindowViews::OnLiveCaptionButtonPressed() { |
| SetLiveCaptionDialogVisibility(!live_caption_dialog_->GetVisible()); |
| } |
| |
| void VideoOverlayWindowViews::SetLiveCaptionDialogVisibility( |
| bool wanted_visibility) { |
| if (wanted_visibility == live_caption_dialog_->GetVisible()) { |
| return; |
| } |
| live_caption_dialog_->SetVisible(wanted_visibility); |
| live_caption_button_->SetIsLiveCaptionDialogOpen(wanted_visibility); |
| |
| views::View* controls_to_be_disabled_when_live_caption_is_open[] = { |
| minimize_button_.get(), |
| back_to_tab_button_.get(), |
| close_controls_view_.get(), |
| replay_10_seconds_button_.get(), |
| play_pause_controls_view_.get(), |
| forward_10_seconds_button_.get(), |
| previous_track_controls_view_.get(), |
| progress_view_.get(), |
| next_track_controls_view_.get(), |
| toggle_camera_button_.get(), |
| toggle_microphone_button_.get(), |
| hang_up_button_.get()}; |
| for (auto* control : controls_to_be_disabled_when_live_caption_is_open) { |
| control->SetEnabled(!wanted_visibility); |
| } |
| } |
| |
| void VideoOverlayWindowViews::OnFaviconReceived(const SkBitmap& image) { |
| UpdateFavicon(GetCorrectColorTypeImage(image)); |
| } |
| |
| void VideoOverlayWindowViews::UpdateFavicon(const gfx::ImageSkia& favicon) { |
| if (favicon.isNull()) { |
| favicon_view_->SetImage(ui::ImageModel::FromVectorIcon( |
| vector_icons::kGlobeIcon, ui::kColorSysOnSurface, |
| kFaviconIconSize.width())); |
| } else { |
| favicon_view_->SetImageSize( |
| ScaleImageSizeToFitView(favicon.size(), kFaviconIconSize)); |
| favicon_view_->SetImage(ui::ImageModel::FromImageSkia(favicon)); |
| } |
| } |
| |
| void VideoOverlayWindowViews::OnInitialTitleTimerFired() { |
| if (user_interacted_before_timer_fired_) { |
| meets_user_interaction_ = true; |
| } |
| UpdateControlsVisibility(false); |
| } |
| |
| bool VideoOverlayWindowViews::AreTitleAndScrimVisible() const { |
| if (!Use2024UI()) { |
| return false; |
| } |
| |
| if (title_fade_animation_) { |
| // The title and scrim are animated together, so their animations should |
| // either both exist or both not exist. |
| DCHECK(controls_top_scrim_fade_animation_); |
| DCHECK_EQ(title_fade_animation_->type(), |
| controls_top_scrim_fade_animation_->type()); |
| return (title_fade_animation_->type() == |
| OverlayControlsFadeAnimation::Type::kToShown); |
| } |
| |
| // If no animation is active, check the opacity of the layers. They should |
| // also be in sync. |
| DCHECK(!controls_top_scrim_fade_animation_); |
| DCHECK_EQ(GetTitleView()->layer()->opacity(), |
| GetControlsTopScrimView()->layer()->opacity()); |
| return GetTitleView()->layer()->opacity() > 0; |
| } |
| |
| void VideoOverlayWindowViews::MaybeUpdateMeetsUserInteraction( |
| const ui::Event& event) { |
| if (meets_user_interaction_) { |
| return; |
| } |
| |
| if (event.type() != ui::EventType::kKeyPressed && |
| event.type() != ui::EventType::kGestureTap && |
| event.type() != ui::EventType::kMousePressed) { |
| return; |
| } |
| |
| if (initial_title_hide_timer_.IsRunning()) { |
| user_interacted_before_timer_fired_ = true; |
| } else { |
| meets_user_interaction_ = true; |
| } |
| } |