1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chromeos/services/assistant/media_session/assistant_media_session.h"
6
7 #include <utility>
8
9 #include "base/bind.h"
10 #include "base/memory/scoped_refptr.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "chromeos/services/assistant/assistant_manager_service.h"
13 #include "chromeos/services/assistant/public/cpp/assistant_client.h"
14 #include "services/media_session/public/cpp/features.h"
15
16 // A macro which ensures we are running on the main thread.
17 #define ENSURE_MAIN_THREAD(method, ...) \
18 if (!main_task_runner_->RunsTasksInCurrentSequence()) { \
19 main_task_runner_->PostTask( \
20 FROM_HERE, \
21 base::BindOnce(method, weak_factory_.GetWeakPtr(), ##__VA_ARGS__)); \
22 return; \
23 }
24
25 namespace chromeos {
26 namespace assistant {
27
28 namespace {
29
30 using media_session::mojom::AudioFocusType;
31 using media_session::mojom::MediaPlaybackState;
32 using media_session::mojom::MediaSessionInfo;
33
34 const char kAudioFocusSourceName[] = "assistant";
35
36 } // namespace
37
AssistantMediaSession(AssistantManagerService * assistant_manager_service)38 AssistantMediaSession::AssistantMediaSession(
39 AssistantManagerService* assistant_manager_service)
40 : assistant_manager_service_(assistant_manager_service),
41 main_task_runner_(base::SequencedTaskRunnerHandle::Get()) {}
42
~AssistantMediaSession()43 AssistantMediaSession::~AssistantMediaSession() {
44 AbandonAudioFocusIfNeeded();
45 }
46
GetMediaSessionInfo(GetMediaSessionInfoCallback callback)47 void AssistantMediaSession::GetMediaSessionInfo(
48 GetMediaSessionInfoCallback callback) {
49 std::move(callback).Run(session_info_.Clone());
50 }
51
AddObserver(mojo::PendingRemote<media_session::mojom::MediaSessionObserver> observer)52 void AssistantMediaSession::AddObserver(
53 mojo::PendingRemote<media_session::mojom::MediaSessionObserver> observer) {
54 ENSURE_MAIN_THREAD(&AssistantMediaSession::AddObserver, std::move(observer));
55 mojo::Remote<media_session::mojom::MediaSessionObserver>
56 media_session_observer(std::move(observer));
57 media_session_observer->MediaSessionInfoChanged(session_info_.Clone());
58 media_session_observer->MediaSessionMetadataChanged(metadata_);
59 observers_.Add(std::move(media_session_observer));
60 }
61
GetDebugInfo(GetDebugInfoCallback callback)62 void AssistantMediaSession::GetDebugInfo(GetDebugInfoCallback callback) {
63 std::move(callback).Run(media_session::mojom::MediaSessionDebugInfo::New());
64 }
65
StartDucking()66 void AssistantMediaSession::StartDucking() {
67 if (!IsSessionStateActive())
68 return;
69 SetAudioFocusInfo(MediaSessionInfo::SessionState::kDucking,
70 audio_focus_type_);
71 }
72
StopDucking()73 void AssistantMediaSession::StopDucking() {
74 if (!IsSessionStateDucking())
75 return;
76 SetAudioFocusInfo(MediaSessionInfo::SessionState::kActive, audio_focus_type_);
77 }
78
Suspend(SuspendType suspend_type)79 void AssistantMediaSession::Suspend(SuspendType suspend_type) {
80 if (!IsSessionStateActive() && !IsSessionStateDucking())
81 return;
82 SetAudioFocusInfo(MediaSessionInfo::SessionState::kSuspended,
83 audio_focus_type_);
84 assistant_manager_service_->UpdateInternalMediaPlayerStatus(
85 media_session::mojom::MediaSessionAction::kPause);
86 }
87
Resume(SuspendType suspend_type)88 void AssistantMediaSession::Resume(SuspendType suspend_type) {
89 if (!IsSessionStateSuspended())
90 return;
91 SetAudioFocusInfo(MediaSessionInfo::SessionState::kActive, audio_focus_type_);
92 assistant_manager_service_->UpdateInternalMediaPlayerStatus(
93 media_session::mojom::MediaSessionAction::kPlay);
94 }
95
RequestAudioFocus(AudioFocusType audio_focus_type)96 void AssistantMediaSession::RequestAudioFocus(AudioFocusType audio_focus_type) {
97 if (!base::FeatureList::IsEnabled(
98 media_session::features::kMediaSessionService)) {
99 return;
100 }
101
102 if (audio_focus_request_client_.is_bound()) {
103 // We have an existing request so we should request an updated focus type.
104 audio_focus_request_client_->RequestAudioFocus(
105 session_info_.Clone(), audio_focus_type,
106 base::BindOnce(&AssistantMediaSession::FinishAudioFocusRequest,
107 base::Unretained(this), audio_focus_type));
108 return;
109 }
110
111 EnsureServiceConnection();
112
113 // Create a mojo interface pointer to our media session.
114 receiver_.reset();
115 audio_focus_manager_->RequestAudioFocus(
116 audio_focus_request_client_.BindNewPipeAndPassReceiver(),
117 receiver_.BindNewPipeAndPassRemote(), session_info_.Clone(),
118 audio_focus_type,
119 base::BindOnce(&AssistantMediaSession::FinishInitialAudioFocusRequest,
120 base::Unretained(this), audio_focus_type));
121 }
122
AbandonAudioFocusIfNeeded()123 void AssistantMediaSession::AbandonAudioFocusIfNeeded() {
124 if (!base::FeatureList::IsEnabled(
125 media_session::features::kMediaSessionService)) {
126 return;
127 }
128
129 if (IsSessionStateInactive())
130 return;
131
132 SetAudioFocusInfo(MediaSessionInfo::SessionState::kInactive,
133 audio_focus_type_);
134
135 if (!audio_focus_request_client_.is_bound())
136 return;
137
138 audio_focus_request_client_->AbandonAudioFocus();
139 audio_focus_request_client_.reset();
140 audio_focus_manager_.reset();
141 internal_audio_focus_id_ = base::UnguessableToken::Null();
142 }
143
NotifyMediaSessionMetadataChanged(const assistant_client::MediaStatus & status)144 void AssistantMediaSession::NotifyMediaSessionMetadataChanged(
145 const assistant_client::MediaStatus& status) {
146 ENSURE_MAIN_THREAD(&AssistantMediaSession::NotifyMediaSessionMetadataChanged,
147 status);
148 media_session::MediaMetadata metadata;
149
150 metadata.title = base::UTF8ToUTF16(status.metadata.title);
151 metadata.artist = base::UTF8ToUTF16(status.metadata.artist);
152 metadata.album = base::UTF8ToUTF16(status.metadata.album);
153
154 bool metadata_changed = metadata_ != metadata;
155 if (!metadata_changed)
156 return;
157
158 metadata_ = metadata;
159
160 current_track_ = status.track_type;
161
162 for (auto& observer : observers_)
163 observer->MediaSessionMetadataChanged(this->metadata_);
164 }
165
GetWeakPtr()166 base::WeakPtr<AssistantMediaSession> AssistantMediaSession::GetWeakPtr() {
167 return weak_factory_.GetWeakPtr();
168 }
169
IsSessionStateActive() const170 bool AssistantMediaSession::IsSessionStateActive() const {
171 return session_info_.state == MediaSessionInfo::SessionState::kActive;
172 }
173
IsSessionStateDucking() const174 bool AssistantMediaSession::IsSessionStateDucking() const {
175 return session_info_.state == MediaSessionInfo::SessionState::kDucking;
176 }
177
IsSessionStateSuspended() const178 bool AssistantMediaSession::IsSessionStateSuspended() const {
179 return session_info_.state == MediaSessionInfo::SessionState::kSuspended;
180 }
181
IsSessionStateInactive() const182 bool AssistantMediaSession::IsSessionStateInactive() const {
183 return session_info_.state == MediaSessionInfo::SessionState::kInactive;
184 }
185
EnsureServiceConnection()186 void AssistantMediaSession::EnsureServiceConnection() {
187 DCHECK(base::FeatureList::IsEnabled(
188 media_session::features::kMediaSessionService));
189
190 if (audio_focus_manager_.is_bound() && audio_focus_manager_.is_connected())
191 return;
192
193 audio_focus_manager_.reset();
194
195 AssistantClient::Get()->RequestAudioFocusManager(
196 audio_focus_manager_.BindNewPipeAndPassReceiver());
197 audio_focus_manager_->SetSource(base::UnguessableToken::Create(),
198 kAudioFocusSourceName);
199 }
200
FinishAudioFocusRequest(AudioFocusType audio_focus_type)201 void AssistantMediaSession::FinishAudioFocusRequest(
202 AudioFocusType audio_focus_type) {
203 DCHECK(audio_focus_request_client_.is_bound());
204
205 SetAudioFocusInfo(MediaSessionInfo::SessionState::kActive, audio_focus_type);
206 }
207
FinishInitialAudioFocusRequest(AudioFocusType audio_focus_type,const base::UnguessableToken & request_id)208 void AssistantMediaSession::FinishInitialAudioFocusRequest(
209 AudioFocusType audio_focus_type,
210 const base::UnguessableToken& request_id) {
211 internal_audio_focus_id_ = request_id;
212 FinishAudioFocusRequest(audio_focus_type);
213 }
214
SetAudioFocusInfo(MediaSessionInfo::SessionState audio_focus_state,AudioFocusType audio_focus_type)215 void AssistantMediaSession::SetAudioFocusInfo(
216 MediaSessionInfo::SessionState audio_focus_state,
217 AudioFocusType audio_focus_type) {
218 if (audio_focus_state == session_info_.state &&
219 audio_focus_type == audio_focus_type_) {
220 return;
221 }
222
223 // Update |session_info_| and |audio_focus_type_|.
224 session_info_.state = audio_focus_state;
225 audio_focus_type_ = audio_focus_type;
226 session_info_.is_controllable =
227 !IsSessionStateInactive() &&
228 (audio_focus_type != AudioFocusType::kGainTransient);
229
230 NotifyMediaSessionInfoChanged();
231 }
232
NotifyMediaSessionInfoChanged()233 void AssistantMediaSession::NotifyMediaSessionInfoChanged() {
234 ENSURE_MAIN_THREAD(&AssistantMediaSession::NotifyMediaSessionInfoChanged);
235 if (audio_focus_request_client_.is_bound())
236 audio_focus_request_client_->MediaSessionInfoChanged(session_info_.Clone());
237
238 for (auto& observer : observers_)
239 observer->MediaSessionInfoChanged(session_info_.Clone());
240 }
241
242 } // namespace assistant
243 } // namespace chromeos
244