blob: f7cf052573f6c57f7c2b842366df7fbbf631a161 [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 "content/browser/media/hardware_key_media_controller.h"
#include <algorithm>
#include <utility>
#include <vector>
#include "base/metrics/histogram_macros.h"
#include "content/public/browser/media_keys_listener_manager.h"
#include "services/media_session/public/mojom/constants.mojom.h"
#include "services/service_manager/public/cpp/connector.h"
#include "ui/base/accelerators/accelerator.h"
namespace content {
using media_session::mojom::MediaSessionAction;
HardwareKeyMediaController::HardwareKeyMediaController(
service_manager::Connector* connector) {
// |connector| can be null in tests.
if (!connector)
return;
// Connect to the MediaControllerManager and create a MediaController that
// controls the active session.
media_session::mojom::MediaControllerManagerPtr controller_manager_ptr;
connector->BindInterface(media_session::mojom::kServiceName,
mojo::MakeRequest(&controller_manager_ptr));
controller_manager_ptr->CreateActiveMediaController(
mojo::MakeRequest(&media_controller_ptr_));
// Observe the active media session for changes to playback state and
// supported actions.
media_session::mojom::MediaSessionObserverPtr media_session_observer;
media_session_observer_binding_.Bind(
mojo::MakeRequest(&media_session_observer));
media_controller_ptr_->AddObserver(std::move(media_session_observer));
}
HardwareKeyMediaController::~HardwareKeyMediaController() = default;
void HardwareKeyMediaController::MediaSessionInfoChanged(
media_session::mojom::MediaSessionInfoPtr session_info) {
session_info_ = std::move(session_info);
}
void HardwareKeyMediaController::MediaSessionActionsChanged(
const std::vector<MediaSessionAction>& actions) {
MediaKeysListenerManager* media_keys_listener_manager =
MediaKeysListenerManager::GetInstance();
DCHECK(media_keys_listener_manager);
// Stop listening to any keys that are currently being watched, but aren't in
// |actions|.
for (const MediaSessionAction& action : actions_) {
base::Optional<ui::KeyboardCode> action_key_code =
MediaSessionActionToKeyCode(action);
// We only store supported actions in |actions_|, so we should always get a
// value from |MediaSessionActionToKeyCode()| here.
DCHECK(action_key_code.has_value());
if (std::find(actions.begin(), actions.end(), action) == actions.end())
media_keys_listener_manager->StopWatchingMediaKey(*action_key_code, this);
}
// Populate |actions_| with the new MediaSessionActions and start listening
// to necessary media keys.
actions_.clear();
for (const MediaSessionAction& action : actions) {
base::Optional<ui::KeyboardCode> action_key_code =
MediaSessionActionToKeyCode(action);
if (action_key_code.has_value()) {
// It's okay to call this even on keys we're already listening to, since
// it's a no-op in that case.
if (media_keys_listener_manager->StartWatchingMediaKey(*action_key_code,
this)) {
actions_.insert(action);
}
}
}
}
void HardwareKeyMediaController::FlushForTesting() {
media_controller_ptr_.FlushForTesting();
}
void HardwareKeyMediaController::OnMediaKeysAccelerator(
const ui::Accelerator& accelerator) {
// Ignore key released events.
if (accelerator.key_state() == ui::Accelerator::KeyState::RELEASED)
return;
MediaSessionAction action =
KeyCodeToMediaSessionAction(accelerator.key_code());
// Ignore if we don't support the action.
if (!SupportsAction(action))
return;
PerformAction(action);
}
bool HardwareKeyMediaController::SupportsAction(
MediaSessionAction action) const {
return actions_.contains(action);
}
void HardwareKeyMediaController::PerformAction(MediaSessionAction action) {
DCHECK(SupportsAction(action));
switch (action) {
case MediaSessionAction::kPreviousTrack:
media_controller_ptr_->PreviousTrack();
RecordAction(MediaHardwareKeyAction::kActionPreviousTrack);
return;
case MediaSessionAction::kPlay:
media_controller_ptr_->Resume();
RecordAction(MediaHardwareKeyAction::kActionPlay);
return;
case MediaSessionAction::kPause:
media_controller_ptr_->Suspend();
RecordAction(MediaHardwareKeyAction::kActionPause);
return;
case MediaSessionAction::kNextTrack:
media_controller_ptr_->NextTrack();
RecordAction(MediaHardwareKeyAction::kActionNextTrack);
return;
case MediaSessionAction::kStop:
media_controller_ptr_->Stop();
RecordAction(MediaHardwareKeyAction::kActionStop);
return;
case MediaSessionAction::kSeekBackward:
case MediaSessionAction::kSeekForward:
case MediaSessionAction::kSkipAd:
NOTREACHED();
return;
}
}
void HardwareKeyMediaController::RecordAction(MediaHardwareKeyAction action) {
UMA_HISTOGRAM_ENUMERATION("Media.HardwareKeyPressed", action);
}
MediaSessionAction HardwareKeyMediaController::KeyCodeToMediaSessionAction(
ui::KeyboardCode key_code) const {
switch (key_code) {
case ui::KeyboardCode::VKEY_MEDIA_PLAY_PAUSE:
if (session_info_ &&
session_info_->playback_state ==
media_session::mojom::MediaPlaybackState::kPlaying) {
return MediaSessionAction::kPause;
}
return MediaSessionAction::kPlay;
case ui::KeyboardCode::VKEY_MEDIA_STOP:
return MediaSessionAction::kStop;
case ui::KeyboardCode::VKEY_MEDIA_NEXT_TRACK:
return MediaSessionAction::kNextTrack;
case ui::KeyboardCode::VKEY_MEDIA_PREV_TRACK:
return MediaSessionAction::kPreviousTrack;
default:
NOTREACHED();
return MediaSessionAction::kPlay;
}
}
base::Optional<ui::KeyboardCode>
HardwareKeyMediaController::MediaSessionActionToKeyCode(
MediaSessionAction action) const {
switch (action) {
case MediaSessionAction::kPlay:
case MediaSessionAction::kPause:
return ui::KeyboardCode::VKEY_MEDIA_PLAY_PAUSE;
case MediaSessionAction::kStop:
return ui::KeyboardCode::VKEY_MEDIA_STOP;
case MediaSessionAction::kNextTrack:
return ui::KeyboardCode::VKEY_MEDIA_NEXT_TRACK;
case MediaSessionAction::kPreviousTrack:
return ui::KeyboardCode::VKEY_MEDIA_PREV_TRACK;
case MediaSessionAction::kSeekBackward:
case MediaSessionAction::kSeekForward:
case MediaSessionAction::kSkipAd:
return base::nullopt;
}
}
} // namespace content