| // 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 "content/browser/media/media_internals_audio_focus_helper.h" |
| |
| #include <list> |
| #include <string> |
| |
| #include "base/bind.h" |
| #include "base/containers/adapters.h" |
| #include "base/task/post_task.h" |
| #include "base/values.h" |
| #include "content/browser/media/media_internals.h" |
| #include "content/public/browser/browser_task_traits.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/web_ui.h" |
| #include "content/public/common/service_manager_connection.h" |
| #include "mojo/public/cpp/bindings/interface_request.h" |
| #include "services/media_session/public/mojom/constants.mojom.h" |
| #include "services/service_manager/public/cpp/connector.h" |
| |
| namespace content { |
| |
| namespace { |
| |
| const char kAudioFocusFunction[] = "media.onReceiveAudioFocusState"; |
| const char kAudioFocusIdKey[] = "id"; |
| const char kAudioFocusSessionsKey[] = "sessions"; |
| |
| const char kAudioFocusForceDuck[] = "ForceDuck"; |
| const char kAudioFocusPreferStop[] = "PreferStop"; |
| |
| const char kAudioFocusTypeGain[] = "Gain"; |
| const char kAudioFocusTypeGainTransient[] = "GainTransient"; |
| const char kAudioFocusTypeGainTransientMayDuck[] = "GainTransientMayDuck"; |
| const char kAudioFocusTypeAmbient[] = "Ambient"; |
| |
| const char kMediaSessionStateActive[] = "Active"; |
| const char kMediaSessionStateDucking[] = "Ducking"; |
| const char kMediaSessionStateSuspended[] = "Suspended"; |
| const char kMediaSessionStateInactive[] = "Inactive"; |
| |
| const char kMediaSessionPlaybackStatePaused[] = "Paused"; |
| const char kMediaSessionPlaybackStatePlaying[] = "Playing"; |
| |
| const char kMediaSessionIsControllable[] = "Controllable"; |
| |
| } // namespace |
| |
| MediaInternalsAudioFocusHelper::MediaInternalsAudioFocusHelper() = default; |
| |
| MediaInternalsAudioFocusHelper::~MediaInternalsAudioFocusHelper() = default; |
| |
| void MediaInternalsAudioFocusHelper::SendAudioFocusState() { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| if (!EnsureServiceConnection()) |
| return; |
| |
| // Get the audio focus state from the media session service. |
| audio_focus_ptr_->GetFocusRequests(base::BindOnce( |
| &MediaInternalsAudioFocusHelper::DidGetAudioFocusRequestList, |
| base::Unretained(this))); |
| } |
| |
| void MediaInternalsAudioFocusHelper::OnFocusGained( |
| media_session::mojom::AudioFocusRequestStatePtr session) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| base::PostTaskWithTraits( |
| FROM_HERE, {BrowserThread::UI}, |
| base::BindOnce(&MediaInternalsAudioFocusHelper::SendAudioFocusState, |
| base::Unretained(this))); |
| } |
| |
| void MediaInternalsAudioFocusHelper::OnFocusLost( |
| media_session::mojom::AudioFocusRequestStatePtr session) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| base::PostTaskWithTraits( |
| FROM_HERE, {BrowserThread::UI}, |
| base::BindOnce(&MediaInternalsAudioFocusHelper::SendAudioFocusState, |
| base::Unretained(this))); |
| } |
| |
| void MediaInternalsAudioFocusHelper::SetEnabled(bool enabled) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| enabled_ = enabled; |
| |
| EnsureServiceConnection(); |
| |
| if (!enabled) { |
| audio_focus_ptr_.reset(); |
| audio_focus_debug_ptr_.reset(); |
| binding_.Close(); |
| } |
| } |
| |
| bool MediaInternalsAudioFocusHelper::EnsureServiceConnection() { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| if (!enabled_) |
| return false; |
| |
| // |connection| and |connector| may be nullptr in some tests. |
| ServiceManagerConnection* connection = |
| ServiceManagerConnection::GetForProcess(); |
| if (!connection) |
| return false; |
| |
| service_manager::Connector* connector = connection->GetConnector(); |
| if (!connector) |
| return false; |
| |
| // Connect to the media session service. |
| if (!audio_focus_ptr_.is_bound()) { |
| connector->BindInterface(media_session::mojom::kServiceName, |
| mojo::MakeRequest(&audio_focus_ptr_)); |
| audio_focus_ptr_.set_connection_error_handler(base::BindRepeating( |
| &MediaInternalsAudioFocusHelper::OnMojoError, base::Unretained(this))); |
| } |
| |
| // Connect to the media session service debug interface. |
| if (!audio_focus_debug_ptr_.is_bound()) { |
| connector->BindInterface(media_session::mojom::kServiceName, |
| mojo::MakeRequest(&audio_focus_debug_ptr_)); |
| audio_focus_debug_ptr_.set_connection_error_handler( |
| base::BindRepeating(&MediaInternalsAudioFocusHelper::OnDebugMojoError, |
| base::Unretained(this))); |
| } |
| |
| // Add the observer to receive audio focus events. |
| if (!binding_.is_bound()) { |
| media_session::mojom::AudioFocusObserverPtr observer; |
| binding_.Bind(mojo::MakeRequest(&observer)); |
| audio_focus_ptr_->AddObserver(std::move(observer)); |
| |
| binding_.set_connection_error_handler(base::BindRepeating( |
| &MediaInternalsAudioFocusHelper::OnMojoError, base::Unretained(this))); |
| } |
| |
| return true; |
| } |
| |
| void MediaInternalsAudioFocusHelper::OnMojoError() { |
| audio_focus_ptr_.reset(); |
| binding_.Close(); |
| } |
| |
| void MediaInternalsAudioFocusHelper::OnDebugMojoError() { |
| audio_focus_debug_ptr_.reset(); |
| } |
| |
| void MediaInternalsAudioFocusHelper::DidGetAudioFocusRequestList( |
| std::vector<media_session::mojom::AudioFocusRequestStatePtr> stack) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| if (!EnsureServiceConnection()) |
| return; |
| |
| audio_focus_data_.Clear(); |
| request_state_.clear(); |
| |
| // We should go backwards through the stack so the top of the stack is |
| // always shown first in the list. |
| base::ListValue stack_data; |
| for (const auto& session : base::Reversed(stack)) { |
| if (!session->request_id.has_value()) |
| continue; |
| |
| std::string id_string = session->request_id.value().ToString(); |
| base::DictionaryValue media_session_data; |
| media_session_data.SetKey(kAudioFocusIdKey, base::Value(id_string)); |
| stack_data.GetList().push_back(std::move(media_session_data)); |
| |
| request_state_.emplace(id_string, session.Clone()); |
| |
| audio_focus_debug_ptr_->GetDebugInfoForRequest( |
| session->request_id.value(), |
| base::BindOnce( |
| &MediaInternalsAudioFocusHelper::DidGetAudioFocusDebugInfo, |
| base::Unretained(this), id_string)); |
| } |
| |
| audio_focus_data_.SetKey(kAudioFocusSessionsKey, std::move(stack_data)); |
| |
| // If the stack is empty then we should send an update to the web ui to clear |
| // the list. |
| if (stack.empty()) |
| SerializeAndSendUpdate(kAudioFocusFunction, &audio_focus_data_); |
| } |
| |
| void MediaInternalsAudioFocusHelper::DidGetAudioFocusDebugInfo( |
| const std::string& id, |
| media_session::mojom::MediaSessionDebugInfoPtr info) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| if (!EnsureServiceConnection()) |
| return; |
| |
| base::Value* sessions_list = |
| audio_focus_data_.FindKey(kAudioFocusSessionsKey); |
| DCHECK(sessions_list); |
| |
| bool updated = false; |
| for (auto& session : sessions_list->GetList()) { |
| if (session.FindKey(kAudioFocusIdKey)->GetString() != id) |
| continue; |
| |
| auto state = request_state_.find(id); |
| DCHECK(state != request_state_.end()); |
| |
| session.SetKey("name", |
| base::Value(BuildNameString(state->second, info->name))); |
| session.SetKey("owner", base::Value(info->owner)); |
| session.SetKey("state", |
| base::Value(BuildStateString(state->second, info->state))); |
| updated = true; |
| } |
| |
| if (!updated) |
| return; |
| |
| SerializeAndSendUpdate(kAudioFocusFunction, &audio_focus_data_); |
| } |
| |
| void MediaInternalsAudioFocusHelper::SerializeAndSendUpdate( |
| const std::string& function, |
| const base::Value* value) { |
| return MediaInternals::GetInstance()->SendUpdate( |
| content::WebUI::GetJavascriptCall( |
| function, std::vector<const base::Value*>(1, value))); |
| } |
| |
| std::string MediaInternalsAudioFocusHelper::BuildNameString( |
| const media_session::mojom::AudioFocusRequestStatePtr& state, |
| const std::string& provided_name) const { |
| std::stringstream stream; |
| |
| // Add the |source_name| (optional). |
| if (state->source_name.has_value()) { |
| stream << state->source_name.value(); |
| stream << ":"; |
| } |
| |
| // Add the |request_id|. |
| stream << state->request_id.value().ToString(); |
| |
| if (!provided_name.empty()) |
| stream << " " << provided_name; |
| return stream.str(); |
| } |
| |
| std::string MediaInternalsAudioFocusHelper::BuildStateString( |
| const media_session::mojom::AudioFocusRequestStatePtr& state, |
| const std::string& provided_state) const { |
| std::stringstream stream; |
| |
| // Convert the AudioFocusType mojo enum to a string. |
| switch (state->audio_focus_type) { |
| case media_session::mojom::AudioFocusType::kGain: |
| stream << " " << kAudioFocusTypeGain; |
| break; |
| case media_session::mojom::AudioFocusType::kGainTransient: |
| stream << " " << kAudioFocusTypeGainTransient; |
| break; |
| case media_session::mojom::AudioFocusType::kGainTransientMayDuck: |
| stream << " " << kAudioFocusTypeGainTransientMayDuck; |
| break; |
| case media_session::mojom::AudioFocusType::kAmbient: |
| stream << " " << kAudioFocusTypeAmbient; |
| break; |
| } |
| |
| // Convert the MediaSessionInfo::SessionState mojo enum to a string. |
| switch (state->session_info->state) { |
| case media_session::mojom::MediaSessionInfo::SessionState::kActive: |
| stream << " " << kMediaSessionStateActive; |
| break; |
| case media_session::mojom::MediaSessionInfo::SessionState::kDucking: |
| stream << " " << kMediaSessionStateDucking; |
| break; |
| case media_session::mojom::MediaSessionInfo::SessionState::kSuspended: |
| stream << " " << kMediaSessionStateSuspended; |
| break; |
| case media_session::mojom::MediaSessionInfo::SessionState::kInactive: |
| stream << " " << kMediaSessionStateInactive; |
| break; |
| } |
| |
| // Convert the MediaPlaybackState mojo enum to a string. |
| switch (state->session_info->playback_state) { |
| case media_session::mojom::MediaPlaybackState::kPaused: |
| stream << " " << kMediaSessionPlaybackStatePaused; |
| break; |
| case media_session::mojom::MediaPlaybackState::kPlaying: |
| stream << " " << kMediaSessionPlaybackStatePlaying; |
| break; |
| } |
| |
| // Convert the |force_duck| boolean into a string. |
| if (state->session_info->force_duck) |
| stream << " " << kAudioFocusForceDuck; |
| |
| // Convert the |prefer_stop_for_gain_focus_loss| boolean into a string. |
| if (state->session_info->prefer_stop_for_gain_focus_loss) |
| stream << " " << kAudioFocusPreferStop; |
| |
| // Convert the |is_controllable| boolean into a string. |
| if (state->session_info->is_controllable) |
| stream << " " << kMediaSessionIsControllable; |
| |
| if (!provided_state.empty()) |
| stream << " " << provided_state; |
| return stream.str(); |
| } |
| |
| } // namespace content |