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