blob: b3346758bc6d1af25486b30bbd19d075872832db [file] [log] [blame]
// Copyright 2018 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 "services/media_session/audio_focus_request.h"
#include "services/media_session/audio_focus_manager.h"
namespace media_session {
AudioFocusRequest::AudioFocusRequest(
base::WeakPtr<AudioFocusManager> owner,
mojo::PendingReceiver<mojom::AudioFocusRequestClient> receiver,
mojo::PendingRemote<mojom::MediaSession> session,
mojom::MediaSessionInfoPtr session_info,
mojom::AudioFocusType audio_focus_type,
const base::UnguessableToken& id,
const std::string& source_name,
const base::UnguessableToken& group_id,
const base::UnguessableToken& identity)
: session_(std::move(session)),
session_info_(std::move(session_info)),
audio_focus_type_(audio_focus_type),
receiver_(this, std::move(receiver)),
id_(id),
source_name_(source_name),
group_id_(group_id),
identity_(identity),
owner_(std::move(owner)) {
// Listen for mojo errors.
receiver_.set_disconnect_handler(base::BindOnce(
&AudioFocusRequest::OnConnectionError, base::Unretained(this)));
session_.set_disconnect_handler(base::BindOnce(
&AudioFocusRequest::OnConnectionError, base::Unretained(this)));
}
AudioFocusRequest::~AudioFocusRequest() = default;
void AudioFocusRequest::RequestAudioFocus(
mojom::MediaSessionInfoPtr session_info,
mojom::AudioFocusType type,
RequestAudioFocusCallback callback) {
SetSessionInfo(std::move(session_info));
if (session_info_->state == mojom::MediaSessionInfo::SessionState::kActive &&
owner_->IsSessionOnTopOfAudioFocusStack(id(), type)) {
// Return early is this session is already on top of the stack.
std::move(callback).Run();
return;
}
// Remove this AudioFocusRequest for the audio focus stack.
std::unique_ptr<AudioFocusRequest> row =
owner_->RemoveFocusEntryIfPresent(id());
DCHECK(row);
owner_->RequestAudioFocusInternal(std::move(row), type);
std::move(callback).Run();
}
void AudioFocusRequest::AbandonAudioFocus() {
owner_->AbandonAudioFocusInternal(id_);
}
void AudioFocusRequest::MediaSessionInfoChanged(
mojom::MediaSessionInfoPtr info) {
bool suspended_change =
(info->state == mojom::MediaSessionInfo::SessionState::kSuspended ||
IsSuspended()) &&
info->state != session_info_->state;
SetSessionInfo(std::move(info));
// If we have transitioned to/from a suspended state then we should
// re-enforce audio focus.
if (suspended_change)
owner_->EnforceAudioFocus();
}
bool AudioFocusRequest::IsSuspended() const {
return session_info_->state ==
mojom::MediaSessionInfo::SessionState::kSuspended;
}
mojom::AudioFocusRequestStatePtr AudioFocusRequest::ToAudioFocusRequestState()
const {
auto request = mojom::AudioFocusRequestState::New();
request->session_info = session_info_.Clone();
request->audio_focus_type = audio_focus_type_;
request->request_id = id_;
request->source_name = source_name_;
return request;
}
void AudioFocusRequest::BindToMediaController(
mojo::PendingReceiver<mojom::MediaController> receiver) {
if (!controller_) {
controller_ = std::make_unique<MediaController>();
controller_->SetMediaSession(this);
}
controller_->BindToInterface(std::move(receiver));
}
void AudioFocusRequest::Suspend(const EnforcementState& state) {
DCHECK(!session_info_->force_duck);
// In most cases if we stop or suspend we should call the ::Suspend method
// on the media session. The only exception is if the session has the
// |prefer_stop_for_gain_focus_loss| bit set in which case we should use
// ::Stop and ::Suspend respectively.
if (state.should_stop && session_info_->prefer_stop_for_gain_focus_loss) {
session_->Stop(mojom::MediaSession::SuspendType::kSystem);
} else {
was_suspended_ = was_suspended_ || state.should_suspend;
session_->Suspend(mojom::MediaSession::SuspendType::kSystem);
}
}
void AudioFocusRequest::ReleaseTransientHold() {
DCHECK(!session_info_->force_duck);
if (!was_suspended_)
return;
was_suspended_ = false;
if (delayed_action_) {
PerformUIAction(*delayed_action_);
delayed_action_.reset();
return;
}
session_->Resume(mojom::MediaSession::SuspendType::kSystem);
}
void AudioFocusRequest::PerformUIAction(mojom::MediaSessionAction action) {
// If the session was temporarily suspended by the service then we should
// delay the action until the session is resumed.
if (was_suspended_) {
delayed_action_ = action;
return;
}
switch (action) {
case mojom::MediaSessionAction::kPause:
session_->Suspend(mojom::MediaSession::SuspendType::kUI);
break;
case mojom::MediaSessionAction::kPlay:
session_->Resume(mojom::MediaSession::SuspendType::kUI);
break;
case mojom::MediaSessionAction::kStop:
session_->Stop(mojom::MediaSession::SuspendType::kUI);
break;
default:
// Only UI transport actions are supported.
NOTREACHED();
}
}
void AudioFocusRequest::GetMediaImageBitmap(
const MediaImage& image,
int minimum_size_px,
int desired_size_px,
GetMediaImageBitmapCallback callback) {
session_->GetMediaImageBitmap(
image, minimum_size_px, desired_size_px,
base::BindOnce(&AudioFocusRequest::OnImageDownloaded,
base::Unretained(this), std::move(callback)));
}
void AudioFocusRequest::SetSessionInfo(
mojom::MediaSessionInfoPtr session_info) {
bool is_controllable_changed =
session_info_->is_controllable != session_info->is_controllable;
session_info_ = std::move(session_info);
if (is_controllable_changed)
owner_->MaybeUpdateActiveSession();
}
void AudioFocusRequest::OnConnectionError() {
// Since we have multiple pathways that can call |OnConnectionError| we
// should use the |encountered_error_| bit to make sure we abandon focus
// just the first time.
if (encountered_error_)
return;
encountered_error_ = true;
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&AudioFocusManager::AbandonAudioFocusInternal,
owner_, id_));
}
void AudioFocusRequest::OnImageDownloaded(GetMediaImageBitmapCallback callback,
const SkBitmap& bitmap) {
std::move(callback).Run(bitmap);
}
} // namespace media_session