910e62b5创建于 1月15日历史提交
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/media/active_media_session_controller.h"

#include <utility>
#include <vector>

#include "base/containers/contains.h"
#include "base/metrics/histogram_macros.h"
#include "base/unguessable_token.h"
#include "content/browser/browser_main_loop.h"
#include "content/browser/media/media_keys_listener_manager_impl.h"
#include "content/public/browser/media_keys_listener_manager.h"
#include "content/public/browser/media_session_service.h"
#include "services/media_session/public/mojom/media_session.mojom.h"
#include "ui/base/accelerators/accelerator.h"
#include "ui/base/accelerators/media_keys_util.h"

namespace content {

using media_session::mojom::MediaSessionAction;

ActiveMediaSessionController::ActiveMediaSessionController(
    base::UnguessableToken request_id)
    : request_id_(request_id) {
  // Connect to the MediaControllerManager and create a MediaController that
  // controls the session given by `request_id`.
  GetMediaSessionService().BindMediaControllerManager(
      controller_manager_remote_.BindNewPipeAndPassReceiver());

  if (request_id == base::UnguessableToken::Null()) {
    // Create a controller that automatically follows the active session.
    controller_manager_remote_->CreateActiveMediaController(
        media_controller_remote_.BindNewPipeAndPassReceiver());
  } else {
    // Create a controller for the media session with ID `request_id`.
    controller_manager_remote_->CreateMediaControllerForSession(
        media_controller_remote_.BindNewPipeAndPassReceiver(), request_id);
  }

  // Observe the media controller for changes to playback state and
  // supported actions.
  media_controller_remote_->AddObserver(
      media_controller_observer_receiver_.BindNewPipeAndPassRemote());
}

ActiveMediaSessionController::~ActiveMediaSessionController() = default;

void ActiveMediaSessionController::MediaSessionInfoChanged(
    media_session::mojom::MediaSessionInfoPtr session_info) {
  MediaKeysListenerManagerImpl* media_keys_listener_manager_impl =
      BrowserMainLoop::GetInstance()->media_keys_listener_manager();
  DCHECK(media_keys_listener_manager_impl);

  session_info_ = std::move(session_info);
  media_keys_listener_manager_impl->SetIsMediaPlaying(
      session_info_ && session_info_->playback_state ==
                           media_session::mojom::MediaPlaybackState::kPlaying);
}

void ActiveMediaSessionController::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|.
  // This loop is what tells the media keys listener manager to stop watching
  // next/previous when a new tab is active because next/previous are in
  // actions_ but NOT in |actions|.
  for (const MediaSessionAction& action : actions_) {
    std::optional<ui::KeyboardCode> action_key_code =
        MediaSessionActionToKeyCode(action);
    if (!action_key_code.has_value())
      continue;
    if (!base::Contains(actions, action))
      media_keys_listener_manager->StopWatchingMediaKey(*action_key_code, this,
                                                        request_id_);
  }

  // Populate |actions_| with the new MediaSessionActions and start listening
  // to necessary media keys.
  actions_.clear();
  for (const MediaSessionAction& action : actions) {
    std::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, request_id_)) {
        actions_.insert(action);
      }
    } else {
      // If there is no media key associated with this action, then just add it
      // to the list of actions we listen to (since we can receive certain
      // non-key actions like SeekTo).
      actions_.insert(action);
    }
  }
}

void ActiveMediaSessionController::MediaSessionPositionChanged(
    const std::optional<media_session::MediaPosition>& position) {
  position_ = position;
}

void ActiveMediaSessionController::FlushForTesting() {
  media_controller_remote_.FlushForTesting();  // IN-TEST
}

void ActiveMediaSessionController::OnMediaKeysAccelerator(
    const ui::Accelerator& accelerator) {
  // Ignore key released events.
  if (accelerator.key_state() == ui::Accelerator::KeyState::RELEASED)
    return;

  MaybePerformAction(KeyCodeToMediaSessionAction(accelerator.key_code()));
}

void ActiveMediaSessionController::OnNext() {
  MaybePerformAction(MediaSessionAction::kNextTrack);
}

void ActiveMediaSessionController::OnPrevious() {
  MaybePerformAction(MediaSessionAction::kPreviousTrack);
}

void ActiveMediaSessionController::OnPlay() {
  MaybePerformAction(MediaSessionAction::kPlay);
}

void ActiveMediaSessionController::OnPause() {
  MaybePerformAction(MediaSessionAction::kPause);
}

void ActiveMediaSessionController::OnPlayPause() {
  if (session_info_ && session_info_->playback_state ==
                           media_session::mojom::MediaPlaybackState::kPlaying) {
    MaybePerformAction(MediaSessionAction::kPause);
    return;
  }
  MaybePerformAction(MediaSessionAction::kPlay);
}

void ActiveMediaSessionController::OnStop() {
  MaybePerformAction(MediaSessionAction::kStop);
}

void ActiveMediaSessionController::OnSeek(const base::TimeDelta& time) {
  media_controller_remote_->Seek(time);
}

void ActiveMediaSessionController::OnSeekTo(const base::TimeDelta& time) {
  if (base::Contains(actions_,
                     media_session::mojom::MediaSessionAction::kSeekTo)) {
    media_controller_remote_->SeekTo(time);
  } else if (position_) {
    auto time_diff =
        time - position_->GetPositionAtTime(base::TimeTicks::Now());
    media_controller_remote_->Seek(time_diff);
  }
}

void ActiveMediaSessionController::MaybePerformAction(
    MediaSessionAction action) {
  // Ignore if we don't support the action.
  if (!SupportsAction(action))
    return;

  PerformAction(action);
}

bool ActiveMediaSessionController::SupportsAction(
    MediaSessionAction action) const {
  return actions_.contains(action);
}

void ActiveMediaSessionController::PerformAction(MediaSessionAction action) {
  DCHECK(SupportsAction(action));
  switch (action) {
    case MediaSessionAction::kPreviousTrack:
      media_controller_remote_->PreviousTrack();
      ui::RecordMediaHardwareKeyAction(
          ui::MediaHardwareKeyAction::kPreviousTrack);
      return;
    case MediaSessionAction::kPlay:
      media_controller_remote_->Resume();
      ui::RecordMediaHardwareKeyAction(ui::MediaHardwareKeyAction::kPlay);
      return;
    case MediaSessionAction::kPause:
      media_controller_remote_->Suspend();
      ui::RecordMediaHardwareKeyAction(ui::MediaHardwareKeyAction::kPause);
      return;
    case MediaSessionAction::kNextTrack:
      media_controller_remote_->NextTrack();
      ui::RecordMediaHardwareKeyAction(ui::MediaHardwareKeyAction::kNextTrack);
      return;
    case MediaSessionAction::kStop:
      media_controller_remote_->Stop();
      ui::RecordMediaHardwareKeyAction(ui::MediaHardwareKeyAction::kStop);
      return;
    case MediaSessionAction::kSeekBackward:
    case MediaSessionAction::kSeekForward:
    case MediaSessionAction::kSkipAd:
    case MediaSessionAction::kSeekTo:
    case MediaSessionAction::kScrubTo:
    case MediaSessionAction::kEnterPictureInPicture:
    case MediaSessionAction::kExitPictureInPicture:
    case MediaSessionAction::kSwitchAudioDevice:
    case MediaSessionAction::kToggleMicrophone:
    case MediaSessionAction::kToggleCamera:
    case MediaSessionAction::kHangUp:
    case MediaSessionAction::kRaise:
    case MediaSessionAction::kSetMute:
    case MediaSessionAction::kPreviousSlide:
    case MediaSessionAction::kNextSlide:
    case MediaSessionAction::kEnterAutoPictureInPicture:
      NOTREACHED();
  }
}

MediaSessionAction ActiveMediaSessionController::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();
  }
}

std::optional<ui::KeyboardCode>
ActiveMediaSessionController::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:
    case MediaSessionAction::kSeekTo:
    case MediaSessionAction::kScrubTo:
    case MediaSessionAction::kEnterPictureInPicture:
    case MediaSessionAction::kExitPictureInPicture:
    case MediaSessionAction::kSwitchAudioDevice:
    case MediaSessionAction::kToggleMicrophone:
    case MediaSessionAction::kToggleCamera:
    case MediaSessionAction::kHangUp:
    case MediaSessionAction::kRaise:
    case MediaSessionAction::kSetMute:
    case MediaSessionAction::kPreviousSlide:
    case MediaSessionAction::kNextSlide:
    case MediaSessionAction::kEnterAutoPictureInPicture:
      return std::nullopt;
  }
}

}  // namespace content