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 "services/media_session/media_controller.h"
6
7 #include <set>
8
9 #include "base/memory/weak_ptr.h"
10 #include "mojo/public/cpp/bindings/remote.h"
11 #include "services/media_session/audio_focus_request.h"
12 #include "services/media_session/public/cpp/media_image_manager.h"
13
14 namespace media_session {
15
16 // ImageObserverHolder will hold each mojo image observer with the image
17 // size and type preferences it specified when the observer was added.
18 class MediaController::ImageObserverHolder {
19 public:
ImageObserverHolder(MediaController * owner,mojom::MediaSessionImageType type,int minimum_size_px,int desired_size_px,mojo::PendingRemote<mojom::MediaControllerImageObserver> observer,const std::vector<MediaImage> & current_images)20 ImageObserverHolder(
21 MediaController* owner,
22 mojom::MediaSessionImageType type,
23 int minimum_size_px,
24 int desired_size_px,
25 mojo::PendingRemote<mojom::MediaControllerImageObserver> observer,
26 const std::vector<MediaImage>& current_images)
27 : manager_(minimum_size_px, desired_size_px),
28 owner_(owner),
29 type_(type),
30 minimum_size_px_(minimum_size_px),
31 desired_size_px_(desired_size_px),
32 observer_(std::move(observer)) {
33 // Set a connection error handler so that we will remove observers that have
34 // had an error / been closed.
35 observer_.set_disconnect_handler(base::BindOnce(
36 &MediaController::CleanupImageObservers, base::Unretained(owner_)));
37
38 // Flush the observer with the latest state.
39 ImagesChanged(current_images);
40 }
41
42 ~ImageObserverHolder() = default;
43
is_valid() const44 bool is_valid() const { return observer_.is_connected(); }
45
type() const46 mojom::MediaSessionImageType type() const { return type_; }
47
ImagesChanged(const std::vector<MediaImage> & images)48 void ImagesChanged(const std::vector<MediaImage>& images) {
49 base::Optional<MediaImage> image = manager_.SelectImage(images);
50
51 // If we could not find an image then we should call with an empty image to
52 // flush the observer.
53 if (!image) {
54 ClearImage();
55 return;
56 }
57
58 DCHECK(owner_->session_->ipc());
59 owner_->session_->GetMediaImageBitmap(
60 *image, minimum_size_px_, desired_size_px_,
61 base::BindOnce(&MediaController::ImageObserverHolder::OnImage,
62 weak_ptr_factory_.GetWeakPtr()));
63 }
64
ClearImage()65 void ClearImage() {
66 if (!did_send_image_last_)
67 return;
68 did_send_image_last_ = false;
69 observer_->MediaControllerImageChanged(type_, SkBitmap());
70 }
71
72 private:
OnImage(const SkBitmap & image)73 void OnImage(const SkBitmap& image) {
74 did_send_image_last_ = true;
75 observer_->MediaControllerImageChanged(type_, image);
76 }
77
78 media_session::MediaImageManager manager_;
79
80 MediaController* const owner_;
81
82 mojom::MediaSessionImageType const type_;
83
84 int const minimum_size_px_;
85
86 int const desired_size_px_;
87
88 mojo::Remote<mojom::MediaControllerImageObserver> observer_;
89
90 // Whether the last information sent to the observer was an image.
91 bool did_send_image_last_ = false;
92
93 base::WeakPtrFactory<ImageObserverHolder> weak_ptr_factory_{this};
94
95 DISALLOW_COPY_AND_ASSIGN(ImageObserverHolder);
96 };
97
MediaController()98 MediaController::MediaController() {
99 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
100 }
101
102 MediaController::~MediaController() = default;
103
Suspend()104 void MediaController::Suspend() {
105 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
106
107 if (session_)
108 session_->PerformUIAction(mojom::MediaSessionAction::kPause);
109 }
110
Resume()111 void MediaController::Resume() {
112 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
113
114 if (session_)
115 session_->PerformUIAction(mojom::MediaSessionAction::kPlay);
116 }
117
Stop()118 void MediaController::Stop() {
119 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
120
121 if (session_)
122 session_->PerformUIAction(mojom::MediaSessionAction::kStop);
123 }
124
ToggleSuspendResume()125 void MediaController::ToggleSuspendResume() {
126 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
127
128 if (session_info_.is_null())
129 return;
130
131 switch (session_info_->playback_state) {
132 case mojom::MediaPlaybackState::kPlaying:
133 Suspend();
134 break;
135 case mojom::MediaPlaybackState::kPaused:
136 Resume();
137 break;
138 }
139 }
140
AddObserver(mojo::PendingRemote<mojom::MediaControllerObserver> observer)141 void MediaController::AddObserver(
142 mojo::PendingRemote<mojom::MediaControllerObserver> observer) {
143 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
144
145 mojo::Remote<mojom::MediaControllerObserver> media_controller_observer(
146 std::move(observer));
147 if (session_) {
148 media_controller_observer->MediaSessionChanged(session_->id());
149 } else {
150 media_controller_observer->MediaSessionChanged(base::nullopt);
151 }
152
153 // Flush the new observer with the current state.
154 media_controller_observer->MediaSessionInfoChanged(session_info_.Clone());
155 media_controller_observer->MediaSessionMetadataChanged(session_metadata_);
156 media_controller_observer->MediaSessionActionsChanged(session_actions_);
157 media_controller_observer->MediaSessionPositionChanged(session_position_);
158
159 observers_.Add(std::move(media_controller_observer));
160 }
161
MediaSessionInfoChanged(mojom::MediaSessionInfoPtr info)162 void MediaController::MediaSessionInfoChanged(mojom::MediaSessionInfoPtr info) {
163 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
164
165 for (auto& observer : observers_)
166 observer->MediaSessionInfoChanged(info.Clone());
167
168 session_info_ = std::move(info);
169 }
170
MediaSessionMetadataChanged(const base::Optional<MediaMetadata> & metadata)171 void MediaController::MediaSessionMetadataChanged(
172 const base::Optional<MediaMetadata>& metadata) {
173 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
174
175 for (auto& observer : observers_)
176 observer->MediaSessionMetadataChanged(metadata);
177
178 session_metadata_ = metadata;
179 }
180
MediaSessionActionsChanged(const std::vector<mojom::MediaSessionAction> & actions)181 void MediaController::MediaSessionActionsChanged(
182 const std::vector<mojom::MediaSessionAction>& actions) {
183 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
184
185 for (auto& observer : observers_)
186 observer->MediaSessionActionsChanged(actions);
187
188 session_actions_ = actions;
189 }
190
MediaSessionPositionChanged(const base::Optional<media_session::MediaPosition> & position)191 void MediaController::MediaSessionPositionChanged(
192 const base::Optional<media_session::MediaPosition>& position) {
193 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
194
195 for (auto& observer : observers_)
196 observer->MediaSessionPositionChanged(position);
197
198 session_position_ = position;
199 }
200
MediaSessionImagesChanged(const base::flat_map<mojom::MediaSessionImageType,std::vector<MediaImage>> & images)201 void MediaController::MediaSessionImagesChanged(
202 const base::flat_map<mojom::MediaSessionImageType, std::vector<MediaImage>>&
203 images) {
204 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
205
206 // Work out which image types have changed.
207 std::set<mojom::MediaSessionImageType> types_changed;
208 for (const auto& entry : images) {
209 auto it = session_images_.find(entry.first);
210 if (it != session_images_.end() && entry.second == it->second)
211 continue;
212
213 types_changed.insert(entry.first);
214 }
215
216 session_images_ = images;
217
218 for (auto& holder : image_observers_) {
219 auto it = session_images_.find(holder->type());
220
221 if (it == session_images_.end()) {
222 // No image of this type is available from the session so we should clear
223 // any image the observers might have.
224 holder->ClearImage();
225 } else if (base::Contains(types_changed, holder->type())) {
226 holder->ImagesChanged(it->second);
227 }
228 }
229 }
230
PreviousTrack()231 void MediaController::PreviousTrack() {
232 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
233
234 if (session_)
235 session_->ipc()->PreviousTrack();
236 }
237
NextTrack()238 void MediaController::NextTrack() {
239 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
240
241 if (session_)
242 session_->ipc()->NextTrack();
243 }
244
Seek(base::TimeDelta seek_time)245 void MediaController::Seek(base::TimeDelta seek_time) {
246 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
247
248 if (session_)
249 session_->ipc()->Seek(seek_time);
250 }
251
ObserveImages(mojom::MediaSessionImageType type,int minimum_size_px,int desired_size_px,mojo::PendingRemote<mojom::MediaControllerImageObserver> observer)252 void MediaController::ObserveImages(
253 mojom::MediaSessionImageType type,
254 int minimum_size_px,
255 int desired_size_px,
256 mojo::PendingRemote<mojom::MediaControllerImageObserver> observer) {
257 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
258
259 auto it = session_images_.find(type);
260
261 image_observers_.push_back(std::make_unique<ImageObserverHolder>(
262 this, type, minimum_size_px, desired_size_px, std::move(observer),
263 it == session_images_.end() ? std::vector<MediaImage>() : it->second));
264 }
265
SeekTo(base::TimeDelta seek_time)266 void MediaController::SeekTo(base::TimeDelta seek_time) {
267 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
268
269 if (session_)
270 session_->ipc()->SeekTo(seek_time);
271 }
272
ScrubTo(base::TimeDelta seek_time)273 void MediaController::ScrubTo(base::TimeDelta seek_time) {
274 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
275
276 if (session_)
277 session_->ipc()->ScrubTo(seek_time);
278 }
279
EnterPictureInPicture()280 void MediaController::EnterPictureInPicture() {
281 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
282
283 if (session_)
284 session_->ipc()->EnterPictureInPicture();
285 }
286
ExitPictureInPicture()287 void MediaController::ExitPictureInPicture() {
288 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
289
290 if (session_)
291 session_->ipc()->ExitPictureInPicture();
292 }
293
SetAudioSinkId(const base::Optional<std::string> & id)294 void MediaController::SetAudioSinkId(const base::Optional<std::string>& id) {
295 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
296
297 if (session_)
298 session_->ipc()->SetAudioSinkId(id);
299 }
300
SetMediaSession(AudioFocusRequest * session)301 void MediaController::SetMediaSession(AudioFocusRequest* session) {
302 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
303
304 DCHECK(session);
305
306 if (session_ == session)
307 return;
308
309 Reset();
310
311 session_ = session;
312
313 // We should always notify the observers that the media session has changed.
314 for (auto& observer : observers_)
315 observer->MediaSessionChanged(session->id());
316
317 // Add |this| as an observer for |session|.
318 session->ipc()->AddObserver(session_receiver_.BindNewPipeAndPassRemote());
319 }
320
ClearMediaSession()321 void MediaController::ClearMediaSession() {
322 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
323
324 if (!session_)
325 return;
326
327 Reset();
328
329 // If we are no longer bound to a session we should flush the observers
330 // with empty data.
331 for (auto& observer : observers_) {
332 observer->MediaSessionChanged(base::nullopt);
333 observer->MediaSessionInfoChanged(nullptr);
334 observer->MediaSessionMetadataChanged(base::nullopt);
335 observer->MediaSessionActionsChanged(
336 std::vector<mojom::MediaSessionAction>());
337 observer->MediaSessionPositionChanged(base::nullopt);
338 }
339
340 for (auto& holder : image_observers_)
341 holder->ClearImage();
342 }
343
BindToInterface(mojo::PendingReceiver<mojom::MediaController> receiver)344 void MediaController::BindToInterface(
345 mojo::PendingReceiver<mojom::MediaController> receiver) {
346 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
347 receivers_.Add(this, std::move(receiver));
348 }
349
FlushForTesting()350 void MediaController::FlushForTesting() {
351 receivers_.FlushForTesting();
352 }
353
CleanupImageObservers()354 void MediaController::CleanupImageObservers() {
355 base::EraseIf(image_observers_,
356 [](const auto& holder) { return !holder->is_valid(); });
357 }
358
Reset()359 void MediaController::Reset() {
360 session_ = nullptr;
361 session_receiver_.reset();
362 session_info_.reset();
363 session_metadata_.reset();
364 session_actions_.clear();
365 session_images_.clear();
366 session_position_.reset();
367 }
368
369 } // namespace media_session
370