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