chromium/chromeos/ash/services/assistant/media_session/assistant_media_session.cc

// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chromeos/ash/services/assistant/media_session/assistant_media_session.h"

#include <utility>

#include "base/functional/bind.h"
#include "base/memory/scoped_refptr.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/sequenced_task_runner.h"
#include "chromeos/ash/services/assistant/media_host.h"
#include "chromeos/ash/services/assistant/public/cpp/assistant_browser_delegate.h"
#include "chromeos/ash/services/libassistant/public/mojom/media_controller.mojom.h"
#include "services/media_session/public/cpp/features.h"

// A macro which ensures we are running on the main thread.
#define ENSURE_MAIN_THREAD(method, ...)                                     \
  if (!main_task_runner_->RunsTasksInCurrentSequence()) {                   \
    main_task_runner_->PostTask(                                            \
        FROM_HERE,                                                          \
        base::BindOnce(method, weak_factory_.GetWeakPtr(), ##__VA_ARGS__)); \
    return;                                                                 \
  }

namespace ash::assistant {

namespace {

using media_session::mojom::AudioFocusType;
using media_session::mojom::MediaPlaybackState;
using media_session::mojom::MediaSessionInfo;

const char kAudioFocusSourceName[] = "assistant";

}  // namespace

AssistantMediaSession::AssistantMediaSession(MediaHost* host)
    : host_(host),
      main_task_runner_(base::SequencedTaskRunner::GetCurrentDefault()) {}

AssistantMediaSession::~AssistantMediaSession() {
  AbandonAudioFocusIfNeeded();
}

void AssistantMediaSession::GetMediaSessionInfo(
    GetMediaSessionInfoCallback callback) {
  std::move(callback).Run(session_info_.Clone());
}

void AssistantMediaSession::AddObserver(
    mojo::PendingRemote<media_session::mojom::MediaSessionObserver> observer) {
  ENSURE_MAIN_THREAD(&AssistantMediaSession::AddObserver, std::move(observer));
  mojo::Remote<media_session::mojom::MediaSessionObserver>
      media_session_observer(std::move(observer));
  media_session_observer->MediaSessionInfoChanged(session_info_.Clone());
  media_session_observer->MediaSessionMetadataChanged(metadata_);
  observers_.Add(std::move(media_session_observer));
}

void AssistantMediaSession::GetDebugInfo(GetDebugInfoCallback callback) {
  std::move(callback).Run(media_session::mojom::MediaSessionDebugInfo::New());
}

void AssistantMediaSession::GetVisibility(GetVisibilityCallback callback) {
  std::move(callback).Run(false);
}

void AssistantMediaSession::StartDucking() {
  if (!IsSessionStateActive())
    return;
  SetAudioFocusInfo(MediaSessionInfo::SessionState::kDucking,
                    audio_focus_type_);
}

void AssistantMediaSession::StopDucking() {
  if (!IsSessionStateDucking())
    return;
  SetAudioFocusInfo(MediaSessionInfo::SessionState::kActive, audio_focus_type_);
}

void AssistantMediaSession::Suspend(SuspendType suspend_type) {
  if (!IsSessionStateActive() && !IsSessionStateDucking())
    return;
  SetAudioFocusInfo(MediaSessionInfo::SessionState::kSuspended,
                    audio_focus_type_);
  host_->PauseInternalMediaPlayer();
}

void AssistantMediaSession::Resume(SuspendType suspend_type) {
  if (!IsSessionStateSuspended())
    return;
  SetAudioFocusInfo(MediaSessionInfo::SessionState::kActive, audio_focus_type_);
  host_->ResumeInternalMediaPlayer();
}

void AssistantMediaSession::RequestAudioFocus(AudioFocusType audio_focus_type) {
  if (!base::FeatureList::IsEnabled(
          media_session::features::kMediaSessionService)) {
    return;
  }

  if (audio_focus_request_client_.is_bound()) {
    // We have an existing request so we should request an updated focus type.
    audio_focus_request_client_->RequestAudioFocus(
        session_info_.Clone(), audio_focus_type,
        base::BindOnce(&AssistantMediaSession::FinishAudioFocusRequest,
                       base::Unretained(this), audio_focus_type));
    return;
  }

  EnsureServiceConnection();

  // Create a mojo interface pointer to our media session.
  receiver_.reset();
  audio_focus_manager_->RequestAudioFocus(
      audio_focus_request_client_.BindNewPipeAndPassReceiver(),
      receiver_.BindNewPipeAndPassRemote(), session_info_.Clone(),
      audio_focus_type,
      base::BindOnce(&AssistantMediaSession::FinishInitialAudioFocusRequest,
                     base::Unretained(this), audio_focus_type));
}

void AssistantMediaSession::AbandonAudioFocusIfNeeded() {
  if (!base::FeatureList::IsEnabled(
          media_session::features::kMediaSessionService)) {
    return;
  }

  if (IsSessionStateInactive())
    return;

  SetAudioFocusInfo(MediaSessionInfo::SessionState::kInactive,
                    audio_focus_type_);

  if (!audio_focus_request_client_.is_bound())
    return;

  audio_focus_request_client_->AbandonAudioFocus();
  audio_focus_request_client_.reset();
  audio_focus_manager_.reset();
  internal_audio_focus_id_ = base::UnguessableToken::Null();
}

void AssistantMediaSession::NotifyMediaSessionMetadataChanged(
    const libassistant::mojom::MediaState& status) {
  media_session::MediaMetadata metadata;

  if (!status.metadata.is_null()) {
    metadata.title = base::UTF8ToUTF16(status.metadata->title);
    metadata.artist = base::UTF8ToUTF16(status.metadata->artist);
    metadata.album = base::UTF8ToUTF16(status.metadata->album);
  }

  bool metadata_changed = metadata_ != metadata;
  if (!metadata_changed)
    return;

  metadata_ = metadata;

  for (auto& observer : observers_)
    observer->MediaSessionMetadataChanged(this->metadata_);
}

base::WeakPtr<AssistantMediaSession> AssistantMediaSession::GetWeakPtr() {
  return weak_factory_.GetWeakPtr();
}

bool AssistantMediaSession::IsSessionStateActive() const {
  return session_info_.state == MediaSessionInfo::SessionState::kActive;
}

bool AssistantMediaSession::IsSessionStateDucking() const {
  return session_info_.state == MediaSessionInfo::SessionState::kDucking;
}

bool AssistantMediaSession::IsSessionStateSuspended() const {
  return session_info_.state == MediaSessionInfo::SessionState::kSuspended;
}

bool AssistantMediaSession::IsSessionStateInactive() const {
  return session_info_.state == MediaSessionInfo::SessionState::kInactive;
}

void AssistantMediaSession::SetInternalAudioFocusIdForTesting(
    const base::UnguessableToken& token) {
  internal_audio_focus_id_ = token;
}

void AssistantMediaSession::EnsureServiceConnection() {
  DCHECK(base::FeatureList::IsEnabled(
      media_session::features::kMediaSessionService));

  if (audio_focus_manager_.is_bound() && audio_focus_manager_.is_connected())
    return;

  audio_focus_manager_.reset();

  AssistantBrowserDelegate::Get()->RequestAudioFocusManager(
      audio_focus_manager_.BindNewPipeAndPassReceiver());
  audio_focus_manager_->SetSource(base::UnguessableToken::Create(),
                                  kAudioFocusSourceName);
}

void AssistantMediaSession::FinishAudioFocusRequest(
    AudioFocusType audio_focus_type) {
  DCHECK(audio_focus_request_client_.is_bound());

  SetAudioFocusInfo(MediaSessionInfo::SessionState::kActive, audio_focus_type);
}

void AssistantMediaSession::FinishInitialAudioFocusRequest(
    AudioFocusType audio_focus_type,
    const base::UnguessableToken& request_id) {
  internal_audio_focus_id_ = request_id;
  FinishAudioFocusRequest(audio_focus_type);
}

void AssistantMediaSession::SetAudioFocusInfo(
    MediaSessionInfo::SessionState audio_focus_state,
    AudioFocusType audio_focus_type) {
  if (audio_focus_state == session_info_.state &&
      audio_focus_type == audio_focus_type_) {
    return;
  }

  // Update |session_info_| and |audio_focus_type_|.
  session_info_.state = audio_focus_state;
  audio_focus_type_ = audio_focus_type;
  session_info_.is_controllable =
      !IsSessionStateInactive() &&
      (audio_focus_type != AudioFocusType::kGainTransient);

  NotifyMediaSessionInfoChanged();
}

void AssistantMediaSession::NotifyMediaSessionInfoChanged() {
  ENSURE_MAIN_THREAD(&AssistantMediaSession::NotifyMediaSessionInfoChanged);
  if (audio_focus_request_client_.is_bound())
    audio_focus_request_client_->MediaSessionInfoChanged(session_info_.Clone());

  for (auto& observer : observers_)
    observer->MediaSessionInfoChanged(session_info_.Clone());
}

}  // namespace ash::assistant