blob: e02dd87cc4852c3cb440f6ec2416aeefbab3781e [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromecast/ui/media_overlay_impl.h"
#include <utility>
#include "base/single_thread_task_runner.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "chromecast/app/grit/shell_resources.h"
#include "chromecast/graphics/cast_window_manager.h"
#include "media/base/audio_decoder_config.h"
#include "ui/aura/window.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/color_utils.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/views/background.h"
#include "ui/views/border.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/progress_bar.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/layout/layout_provider.h"
#include "ui/views/view.h"
#include "ui/views/widget/widget.h"
namespace chromecast {
namespace {
constexpr SkColor kBackgroundColor = SkColorSetA(SK_ColorBLACK, 200);
constexpr int kElementSpacing = 16;
constexpr int kVolumeBarHeight = 16;
constexpr int kVolumePopupPadding = 16;
constexpr int kVolumePopupBottomInset = 32;
constexpr base::TimeDelta kUiHideDelay = base::TimeDelta::FromSeconds(3);
} // namespace
MediaOverlayImpl::MediaOverlayImpl(CastWindowManager* window_manager)
: window_manager_(window_manager),
ui_task_runner_(base::ThreadTaskRunnerHandle::Get()),
controller_(nullptr),
volume_icon_image_(ui::ResourceBundle::GetSharedInstance().GetImageNamed(
IDR_CAST_SHELL_SPEAKER_ICON)),
toast_font_list_(gfx::FontList("Helvetica, 28px")),
weak_factory_(this) {
DCHECK(window_manager_);
gfx::Rect window_bounds = window_manager_->GetRootWindow()->bounds();
auto container_view = std::make_unique<views::View>();
auto layout = std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical,
gfx::Insets(0, window_bounds.width() / 4, kVolumePopupBottomInset,
window_bounds.width() / 4),
0);
layout->set_main_axis_alignment(views::BoxLayout::MainAxisAlignment::kEnd);
layout->set_cross_axis_alignment(
views::BoxLayout::CrossAxisAlignment::kStretch);
container_view->SetLayoutManager(std::move(layout));
AddToast(container_view.get());
AddVolumeBar(container_view.get());
overlay_widget_ =
CreateOverlayWidget(window_bounds, std::move(container_view));
overlay_widget_->Show();
volume_panel_->SetVisible(false);
toast_label_->SetVisible(false);
media::MediaPipelineObserver::AddObserver(this);
}
MediaOverlayImpl::~MediaOverlayImpl() {
media::MediaPipelineObserver::RemoveObserver(this);
}
void MediaOverlayImpl::AddVolumeBar(views::View* container) {
std::unique_ptr<views::ImageView> volume_icon =
std::make_unique<views::ImageView>();
volume_icon->SetImage(volume_icon_image_.ToImageSkia());
volume_icon->SetImageSize(gfx::Size(25, 19));
volume_bar_ = new views::ProgressBar(kVolumeBarHeight, false);
volume_bar_->SetBackgroundColor(kBackgroundColor);
volume_panel_ = new views::View();
auto layout = std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kHorizontal,
gfx::Insets(kVolumePopupPadding), kElementSpacing);
auto* layout_ptr = volume_panel_->SetLayoutManager(std::move(layout));
layout_ptr->set_cross_axis_alignment(
views::BoxLayout::CrossAxisAlignment::kCenter);
volume_panel_->SetBackground(views::CreateSolidBackground(kBackgroundColor));
volume_panel_->AddChildView(volume_icon.release());
volume_panel_->AddChildView(volume_bar_);
layout_ptr->SetFlexForView(volume_bar_, 1);
container->AddChildView(volume_panel_);
}
void MediaOverlayImpl::AddToast(views::View* container) {
toast_label_ = new views::Label(base::UTF8ToUTF16(""));
toast_label_->SetFontList(toast_font_list_);
toast_label_->SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_CENTER);
// Background color of the label is set to auto-compute the best
// constrasting color for the displayed text. If the background color is not
// opaque, we need to take into account the color underneath, which in worst
// case scenario will be white
toast_label_->SetBackgroundColor(
color_utils::GetResultingPaintColor(kBackgroundColor, SK_ColorWHITE));
toast_label_->SetBackground(views::CreateSolidBackground(kBackgroundColor));
toast_label_->SetBorder(
views::CreateEmptyBorder(gfx::Insets(kVolumePopupPadding)));
toast_label_->SetMultiLine(true);
container->AddChildView(toast_label_);
}
void MediaOverlayImpl::SetController(Controller* controller) {
controller_ = controller;
NotifyController();
}
void MediaOverlayImpl::ShowMessage(const base::string16& message) {
if (!ui_task_runner_->BelongsToCurrentThread()) {
ui_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&MediaOverlayImpl::ShowMessage,
weak_factory_.GetWeakPtr(), message));
return;
}
ShowToast(message);
}
void MediaOverlayImpl::ShowVolumeBar(float volume) {
if (!ui_task_runner_->BelongsToCurrentThread()) {
ui_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&MediaOverlayImpl::ShowVolumeBar,
weak_factory_.GetWeakPtr(), volume));
return;
}
volume_bar_->SetValue(volume);
volume_panel_->SetVisible(true);
overlay_widget_->GetContentsView()->Layout();
volume_widget_timer_.Start(FROM_HERE, kUiHideDelay, this,
&MediaOverlayImpl::HideVolumeWidget);
}
void MediaOverlayImpl::HideVolumeWidget() {
volume_panel_->SetVisible(false);
}
void MediaOverlayImpl::ShowToast(const base::string16& text) {
toast_label_->SetText(text);
toast_label_->SizeToFit(volume_panel_->bounds().width());
toast_label_->SetVisible(true);
overlay_widget_->GetContentsView()->Layout();
toast_visible_timer_.Start(FROM_HERE, kUiHideDelay, this,
&MediaOverlayImpl::HideToast);
}
void MediaOverlayImpl::HideToast() {
toast_label_->SetVisible(false);
}
std::unique_ptr<views::Widget> MediaOverlayImpl::CreateOverlayWidget(
const gfx::Rect& bounds,
std::unique_ptr<views::View> content_view) {
std::unique_ptr<views::Widget> widget = std::make_unique<views::Widget>();
views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
params.context = window_manager_->GetRootWindow();
params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent;
params.bounds = bounds;
params.accept_events = false;
widget->Init(std::move(params));
widget->SetContentsView(std::move(content_view));
window_manager_->SetZOrder(widget->GetNativeView(), mojom::ZOrder::VOLUME);
return widget;
}
void MediaOverlayImpl::OnAudioPipelineInitialized(
media::MediaPipelineImpl* pipeline,
const ::media::AudioDecoderConfig& config) {
if (config.codec() == ::media::AudioCodec::kCodecAC3 ||
config.codec() == ::media::AudioCodec::kCodecEAC3 ||
config.codec() == ::media::AudioCodec::kCodecMpegHAudio) {
passthrough_pipelines_.insert(pipeline);
}
NotifyController();
}
void MediaOverlayImpl::OnPipelineDestroyed(media::MediaPipelineImpl* pipeline) {
passthrough_pipelines_.erase(pipeline);
NotifyController();
}
void MediaOverlayImpl::NotifyController() {
if (!controller_)
return;
controller_->SetSurroundSoundInUse(!passthrough_pipelines_.empty());
}
} // namespace chromecast