Music Hub  ..
A session-wide music playback service
track_list_skeleton.cpp
Go to the documentation of this file.
1 /*
2  * Copyright © 2015 Canonical Ltd.
3  *
4  * This program is free software: you can redistribute it and/or modify it
5  * under the terms of the GNU Lesser General Public License version 3,
6  * as published by the Free Software Foundation.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY {} without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11  * GNU Lesser General Public License for more details.
12  *
13  * You should have received a copy of the GNU Lesser General Public License
14  * along with this program. If not, see <http://www.gnu.org/licenses/>.
15  *
16  * Authored by: Thomas Voß <thomas.voss@canonical.com>
17  */
18 
19 #include "track_list_skeleton.h"
21 
22 #include <core/media/player.h>
23 #include <core/media/track_list.h>
24 
25 #include "codec.h"
26 #include "property_stub.h"
27 #include "track_list_traits.h"
28 #include "the_session_bus.h"
29 
30 #include "mpris/track_list.h"
31 
32 #include <core/dbus/object.h>
33 #include <core/dbus/property.h>
34 #include <core/dbus/types/object_path.h>
35 #include <core/dbus/types/variant.h>
36 #include <core/dbus/types/stl/map.h>
37 #include <core/dbus/types/stl/vector.h>
38 
39 #include <iostream>
40 #include <limits>
41 
42 namespace dbus = core::dbus;
43 namespace media = core::ubuntu::media;
44 
46 {
47  Private(media::TrackListSkeleton* impl, const dbus::Bus::Ptr& bus, const dbus::Object::Ptr& object,
48  const apparmor::ubuntu::RequestContextResolver::Ptr& request_context_resolver,
49  const media::apparmor::ubuntu::RequestAuthenticator::Ptr& request_authenticator)
50  : impl(impl),
51  bus(bus),
52  object(object),
53  request_context_resolver(request_context_resolver),
54  request_authenticator(request_authenticator),
56  current_track(skeleton.properties.tracks->get().begin()),
57  empty_iterator(skeleton.properties.tracks->get().begin()),
58  loop_status(media::Player::LoopStatus::none),
59  signals
60  {
65  }
66  {
67  }
68 
69  void handle_get_tracks_metadata(const core::dbus::Message::Ptr& msg)
70  {
71  media::Track::Id track;
72  msg->reader() >> track;
73 
74  const auto meta_data = impl->query_meta_data_for_track(track);
75 
76  const auto reply = dbus::Message::make_method_return(msg);
77  reply->writer() << *meta_data;
78  bus->send(reply);
79  }
80 
81  void handle_get_tracks_uri(const core::dbus::Message::Ptr& msg)
82  {
83  media::Track::Id track;
84  msg->reader() >> track;
85 
86  const auto uri = impl->query_uri_for_track(track);
87 
88  const auto reply = dbus::Message::make_method_return(msg);
89  reply->writer() << uri;
90  bus->send(reply);
91  }
92 
93  void handle_add_track_with_uri_at(const core::dbus::Message::Ptr& msg)
94  {
95  std::cout << "*** " << __PRETTY_FUNCTION__ << std::endl;
96  request_context_resolver->resolve_context_for_dbus_name_async(msg->sender(), [this, msg](const media::apparmor::ubuntu::Context& context)
97  {
98  Track::UriType uri; media::Track::Id after; bool make_current;
99  msg->reader() >> uri >> after >> make_current;
100 
101  // Make sure the client has adequate apparmor permissions to open the URI
102  const auto result = request_authenticator->authenticate_open_uri_request(context, uri);
103 
104  auto reply = dbus::Message::make_method_return(msg);
105  // Only add the track to the TrackList if it passes the apparmor permissions check
106  if (std::get<0>(result))
107  impl->add_track_with_uri_at(uri, after, make_current);
108  else
109  std::cerr << "Warning: Not adding track " << uri <<
110  " to TrackList because of inadequate client apparmor permissions." << std::endl;
111 
112  bus->send(reply);
113  });
114  }
115 
116  void handle_remove_track(const core::dbus::Message::Ptr& msg)
117  {
118  media::Track::Id track;
119  msg->reader() >> track;
120 
121  impl->remove_track(track);
122 
123  auto reply = dbus::Message::make_method_return(msg);
124  bus->send(reply);
125  }
126 
127  void handle_go_to(const core::dbus::Message::Ptr& msg)
128  {
129  media::Track::Id track;
130  msg->reader() >> track;
131 
132  current_track = std::find(skeleton.properties.tracks->get().begin(), skeleton.properties.tracks->get().end(), track);
133  const bool toggle_player_state = true;
134  impl->go_to(track, toggle_player_state);
135 
136  auto reply = dbus::Message::make_method_return(msg);
137  bus->send(reply);
138  }
139 
140  void handle_reset(const core::dbus::Message::Ptr& msg)
141  {
142  impl->reset();
143 
144  auto reply = dbus::Message::make_method_return(msg);
145  bus->send(reply);
146  }
147 
149  dbus::Bus::Ptr bus;
150  dbus::Object::Ptr object;
151  media::apparmor::ubuntu::RequestContextResolver::Ptr request_context_resolver;
152  media::apparmor::ubuntu::RequestAuthenticator::Ptr request_authenticator;
153 
158 
159  struct Signals
160  {
161  typedef core::dbus::Signal<mpris::TrackList::Signals::TrackAdded, mpris::TrackList::Signals::TrackAdded::ArgumentType> DBusTrackAddedSignal;
162  typedef core::dbus::Signal<mpris::TrackList::Signals::TrackRemoved, mpris::TrackList::Signals::TrackRemoved::ArgumentType> DBusTrackRemovedSignal;
163  typedef core::dbus::Signal<mpris::TrackList::Signals::TrackChanged, mpris::TrackList::Signals::TrackChanged::ArgumentType> DBusTrackChangedSignal;
164  typedef core::dbus::Signal<mpris::TrackList::Signals::TrackListReplaced, mpris::TrackList::Signals::TrackListReplaced::ArgumentType> DBusTrackListReplacedSignal;
165 
166  Signals(const std::shared_ptr<DBusTrackAddedSignal>& remote_track_added,
167  const std::shared_ptr<DBusTrackRemovedSignal>& remote_track_removed,
168  const std::shared_ptr<DBusTrackChangedSignal>& remote_track_changed,
169  const std::shared_ptr<DBusTrackListReplacedSignal>& remote_track_list_replaced)
170  {
171  // Connect all of the MPRIS interface signals to be emitted over dbus
172  on_track_added.connect([remote_track_added](const media::Track::Id &id)
173  {
174  remote_track_added->emit(id);
175  });
176 
177  on_track_removed.connect([remote_track_removed](const media::Track::Id &id)
178  {
179  remote_track_removed->emit(id);
180  });
181 
182  on_track_changed.connect([remote_track_changed](const media::Track::Id &id)
183  {
184  remote_track_changed->emit(id);
185  });
186 
187  on_track_list_replaced.connect([remote_track_list_replaced](const media::TrackList::ContainerTrackIdTuple &tltuple)
188  {
189  remote_track_list_replaced->emit(tltuple);
190  });
191  }
192 
193  core::Signal<Track::Id> on_track_added;
194  core::Signal<Track::Id> on_track_removed;
195  core::Signal<Track::Id> on_track_changed;
196  core::Signal<TrackList::ContainerTrackIdTuple> on_track_list_replaced;
197  core::Signal<std::pair<Track::Id, bool>> on_go_to_track;
198  core::Signal<void> on_end_of_tracklist;
199  } signals;
200 };
201 
202 media::TrackListSkeleton::TrackListSkeleton(const core::dbus::Bus::Ptr& bus, const core::dbus::Object::Ptr& object,
203  const media::apparmor::ubuntu::RequestContextResolver::Ptr& request_context_resolver,
204  const media::apparmor::ubuntu::RequestAuthenticator::Ptr& request_authenticator)
205  : d(new Private(this, bus, object, request_context_resolver, request_authenticator))
206 {
207  d->object->install_method_handler<mpris::TrackList::GetTracksMetadata>(
209  std::ref(d),
210  std::placeholders::_1));
211 
212  d->object->install_method_handler<mpris::TrackList::GetTracksUri>(
214  std::ref(d),
215  std::placeholders::_1));
216 
217  d->object->install_method_handler<mpris::TrackList::AddTrack>(
219  std::ref(d),
220  std::placeholders::_1));
221 
222  d->object->install_method_handler<mpris::TrackList::RemoveTrack>(
223  std::bind(&Private::handle_remove_track,
224  std::ref(d),
225  std::placeholders::_1));
226 
227  d->object->install_method_handler<mpris::TrackList::GoTo>(
228  std::bind(&Private::handle_go_to,
229  std::ref(d),
230  std::placeholders::_1));
231 
232  d->object->install_method_handler<mpris::TrackList::Reset>(
233  std::bind(&Private::handle_reset,
234  std::ref(d),
235  std::placeholders::_1));
236 }
237 
239 {
240 }
241 
243 {
244  if (tracks().get().empty())
245  return false;
246 
247  const auto next_track = std::next(current_iterator());
248  return !is_last_track(next_track);
249 }
250 
252 {
253  if (tracks().get().empty())
254  return false;
255 
256  // If we are looping over the entire list, then there is always a previous track
257  if (d->loop_status == media::Player::LoopStatus::playlist)
258  return true;
259 
260  return d->current_track != std::begin(tracks().get());
261 }
262 
264 {
265  return it == std::begin(tracks().get());
266 }
267 
269 {
270  return it == std::end(tracks().get());
271 }
272 
274 {
275  std::cout << __PRETTY_FUNCTION__ << std::endl;
276  if (tracks().get().empty())
277  return *(d->empty_iterator);
278 
279  const auto next_track = std::next(current_iterator());
280  bool do_go_to_next_track = false;
281 
282  // End of the track reached so loop around to the beginning of the track
283  if (d->loop_status == media::Player::LoopStatus::track)
284  {
285  std::cout << "Looping on the current track since LoopStatus is set to track" << std::endl;
286  do_go_to_next_track = true;
287  }
288  // End of the tracklist reached so loop around to the beginning of the tracklist
289  else if (d->loop_status == media::Player::LoopStatus::playlist && not has_next())
290  {
291  std::cout << "Looping on the tracklist since LoopStatus is set to playlist" << std::endl;
292  d->current_track = tracks().get().begin();
293  do_go_to_next_track = true;
294  }
295  else
296  {
297  // Next track is not the last track
298  if (not is_last_track(next_track))
299  {
300  std::cout << "Advancing to next track: " << *(next_track) << std::endl;
301  d->current_track = next_track;
302  do_go_to_next_track = true;
303  }
304  // At the end of the tracklist and not set to loop, so we stop advancing the tracklist
305  else
306  {
307  std::cout << "End of tracklist reached, not advancing to next since LoopStatus is set to none" << std::endl;
309  }
310  }
311 
312  if (do_go_to_next_track)
313  {
315  // Don't automatically call stop() and play() in player_implementation.cpp on_go_to_track()
316  // since this breaks video playback when using open_uri() (stop() and play() are unwanted in
317  // this scenario since the qtubuntu-media will handle this automatically)
318  const bool toggle_player_state = false;
319  const media::Track::Id id = *(current_iterator());
320  const std::pair<const media::Track::Id, bool> p = std::make_pair(id, toggle_player_state);
321  // Signal the PlayerImplementation to play the next track
322  on_go_to_track()(p);
323  }
324 
325  return *(current_iterator());
326 }
327 
329 {
330  std::cout << __PRETTY_FUNCTION__ << std::endl;
331  if (tracks().get().empty())
332  return *(d->empty_iterator);
333 
334  bool do_go_to_previous_track = false;
335 
336  // Loop on the current track forever
337  if (d->loop_status == media::Player::LoopStatus::track)
338  {
339  std::cout << "Looping on the current track..." << std::endl;
340  do_go_to_previous_track = true;
341  }
342  // Loop over the whole playlist and repeat
343  else if (d->loop_status == media::Player::LoopStatus::playlist && is_first_track(current_iterator()))
344  {
345  std::cout << "Looping on the entire TrackList..." << std::endl;
346  d->current_track = std::prev(tracks().get().end());
347  do_go_to_previous_track = true;
348  }
349  else
350  {
351  // Current track is not the first track
352  if (not is_first_track(current_iterator()))
353  {
354  // Keep returning the previous track until the first track is reached
355  d->current_track = std::prev(current_iterator());
356  do_go_to_previous_track = true;
357  }
358  // At the beginning of the tracklist and not set to loop, so we stop advancing the tracklist
359  else
360  {
361  std::cout << "Beginning of tracklist reached, not advancing to previous since LoopStatus is set to none" << std::endl;
363  }
364  }
365 
366  if (do_go_to_previous_track)
367  {
369  // Don't automatically call stop() and play() in player_implementation.cpp on_go_to_track()
370  // since this breaks video playback when using open_uri() (stop() and play() are unwanted in
371  // this scenario since the qtubuntu-media will handle this automatically)
372  const bool toggle_player_state = false;
373  const media::Track::Id id = *(current_iterator());
374  const std::pair<const media::Track::Id, bool> p = std::make_pair(id, toggle_player_state);
375  on_go_to_track()(p);
376  }
377 
378  return *(current_iterator());
379 }
380 
382 {
383  return *(current_iterator());
384 }
385 
387 {
388  // Prevent the TrackList from sitting at the end which will cause
389  // a segfault when calling current()
390  if (tracks().get().size() && (d->current_track == d->empty_iterator))
391  {
392  std::cout << "Wrapping d->current_track back to begin()" << std::endl;
393  d->current_track = d->skeleton.properties.tracks->get().begin();
394  }
395  else if (tracks().get().empty())
396  {
397  std::cerr << "TrackList is empty therefore there is no valid current track" << std::endl;
398  }
399 
400  return d->current_track;
401 }
402 
404 {
405  // If all tracks got removed then we need to keep a sane current
406  // iterator for further use.
407  if (tracks().get().empty())
408  d->current_track = d->empty_iterator;
409 }
410 
411 const core::Property<bool>& media::TrackListSkeleton::can_edit_tracks() const
412 {
413  return *d->skeleton.properties.can_edit_tracks;
414 }
415 
416 core::Property<bool>& media::TrackListSkeleton::can_edit_tracks()
417 {
418  return *d->skeleton.properties.can_edit_tracks;
419 }
420 
421 core::Property<media::TrackList::Container>& media::TrackListSkeleton::tracks()
422 {
423  return *d->skeleton.properties.tracks;
424 }
425 
427 {
428  d->loop_status = loop_status;
429 }
430 
432 {
433  return d->loop_status;
434 }
435 
437 {
438  if (shuffle)
439  shuffle_tracks();
440  else
441  {
442  // Save the current Track::Id of what's currently playing to restore after unshuffle
443  const media::Track::Id current_id = *(current_iterator());
444 
446 
447  // Since we use assign() in unshuffle_tracks, which invalidates existing iterators, we need
448  // to make sure that current is pointing to the right place
449  auto it = std::find(tracks().get().begin(), tracks().get().end(), current_id);
450  if (it != tracks().get().end())
451  d->current_track = it;
452  }
453 }
454 
455 const core::Property<media::TrackList::Container>& media::TrackListSkeleton::tracks() const
456 {
457  return *d->skeleton.properties.tracks;
458 }
459 
460 const core::Signal<media::TrackList::ContainerTrackIdTuple>& media::TrackListSkeleton::on_track_list_replaced() const
461 {
462  // Print the TrackList instance
463  std::cout << *this << std::endl;
464  return d->signals.on_track_list_replaced;
465 }
466 
467 const core::Signal<media::Track::Id>& media::TrackListSkeleton::on_track_added() const
468 {
469  return d->signals.on_track_added;
470 }
471 
472 const core::Signal<media::Track::Id>& media::TrackListSkeleton::on_track_removed() const
473 {
474  return d->signals.on_track_removed;
475 }
476 
477 const core::Signal<media::Track::Id>& media::TrackListSkeleton::on_track_changed() const
478 {
479  return d->signals.on_track_changed;
480 }
481 
482 const core::Signal<std::pair<media::Track::Id, bool>>& media::TrackListSkeleton::on_go_to_track() const
483 {
484  return d->signals.on_go_to_track;
485 }
486 
487 const core::Signal<void>& media::TrackListSkeleton::on_end_of_tracklist() const
488 {
489  return d->signals.on_end_of_tracklist;
490 }
491 
492 core::Signal<media::TrackList::ContainerTrackIdTuple>& media::TrackListSkeleton::on_track_list_replaced()
493 {
494  return d->signals.on_track_list_replaced;
495 }
496 
497 core::Signal<media::Track::Id>& media::TrackListSkeleton::on_track_added()
498 {
499  return d->signals.on_track_added;
500 }
501 
502 core::Signal<media::Track::Id>& media::TrackListSkeleton::on_track_removed()
503 {
504  return d->signals.on_track_removed;
505 }
506 
507 core::Signal<media::Track::Id>& media::TrackListSkeleton::on_track_changed()
508 {
509  return d->signals.on_track_changed;
510 }
511 
512 core::Signal<std::pair<media::Track::Id, bool>>& media::TrackListSkeleton::on_go_to_track()
513 {
514  return d->signals.on_go_to_track;
515 }
516 
518 {
519  return d->signals.on_end_of_tracklist;
520 }
521 
523 {
524  std::cout << __PRETTY_FUNCTION__ << std::endl;
525  d->current_track = d->empty_iterator;
526 }
527 
528 // operator<< pretty prints the given TrackList to the given output stream.
529 inline std::ostream& media::operator<<(std::ostream& out, const media::TrackList& tracklist)
530 {
531  auto non_const_tl = const_cast<media::TrackList*>(&tracklist);
532  out << "TrackList\n---------------" << std::endl;
533  for (const media::Track::Id &id : tracklist.tracks().get())
534  {
535  // '*' denotes the current track
536  out << "\t" << ((dynamic_cast<media::TrackListSkeleton*>(non_const_tl)->current() == id) ? "*" : "");
537  out << "Track Id: " << id << std::endl;
538  out << "\t\turi: " << dynamic_cast<media::TrackListImplementation*>(non_const_tl)->query_uri_for_track(id) << std::endl;
539  }
540 
541  out << "---------------\nEnd TrackList" << std::endl;
542  return out;
543 }
544 
core::dbus::Signal< mpris::TrackList::Signals::TrackAdded, mpris::TrackList::Signals::TrackAdded::ArgumentType > DBusTrackAddedSignal
const core::Property< Container > & tracks() const
virtual const core::Property< Container > & tracks() const =0
struct mpris::TrackList::Skeleton::@20 signals
std::shared_ptr< core::dbus::Property< Properties::Tracks > > tracks
Definition: track_list.h:173
const core::Signal< std::pair< Track::Id, bool > > & on_go_to_track() const
core::Signal< TrackList::ContainerTrackIdTuple > on_track_list_replaced
const core::Signal< Track::Id > & on_track_changed() const
media::Player::LoopStatus loop_status
core::dbus::Signal< mpris::TrackList::Signals::TrackRemoved, mpris::TrackList::Signals::TrackRemoved::ArgumentType > DBusTrackRemovedSignal
const core::Signal< Track::Id > & on_track_removed() const
void handle_remove_track(const core::dbus::Message::Ptr &msg)
std::ostream & operator<<(std::ostream &out, Player::PlaybackStatus status)
Definition: player.h:188
core::dbus::Signal< Signals::TrackListReplaced, Signals::TrackListReplaced::ArgumentType >::Ptr tracklist_replaced
Definition: track_list.h:179
const core::Signal< void > & on_end_of_tracklist() const
void handle_get_tracks_metadata(const core::dbus::Message::Ptr &msg)
std::tuple< std::vector< Track::Id >, Track::Id > ContainerTrackIdTuple
Definition: track_list.h:44
core::ubuntu::media::Player::LoopStatus loop_status() const
void handle_go_to(const core::dbus::Message::Ptr &msg)
const core::Signal< ContainerTrackIdTuple > & on_track_list_replaced() const
void handle_add_track_with_uri_at(const core::dbus::Message::Ptr &msg)
void handle_reset(const core::dbus::Message::Ptr &msg)
media::apparmor::ubuntu::RequestAuthenticator::Ptr request_authenticator
virtual void unshuffle_tracks()=0
const core::Signal< Track::Id > & on_track_added() const
TrackList::ConstIterator current_track
core::dbus::Signal< Signals::TrackAdded, Signals::TrackAdded::ArgumentType >::Ptr track_added
Definition: track_list.h:180
bool is_last_track(const ConstIterator &it)
void on_loop_status_changed(const core::ubuntu::media::Player::LoopStatus &loop_status)
virtual Track::UriType query_uri_for_track(const Track::Id &id)=0
struct media::TrackListSkeleton::Private::Signals signals
mpris::TrackList::Skeleton skeleton
core::dbus::Signal< mpris::TrackList::Signals::TrackListReplaced, mpris::TrackList::Signals::TrackListReplaced::ArgumentType > DBusTrackListReplacedSignal
core::Signal< std::pair< Track::Id, bool > > on_go_to_track
Signals(const std::shared_ptr< DBusTrackAddedSignal > &remote_track_added, const std::shared_ptr< DBusTrackRemovedSignal > &remote_track_removed, const std::shared_ptr< DBusTrackChangedSignal > &remote_track_changed, const std::shared_ptr< DBusTrackListReplacedSignal > &remote_track_list_replaced)
std::string UriType
Definition: track.h:40
virtual void shuffle_tracks()=0
bool is_first_track(const ConstIterator &it)
core::dbus::Signal< mpris::TrackList::Signals::TrackChanged, mpris::TrackList::Signals::TrackChanged::ArgumentType > DBusTrackChangedSignal
media::apparmor::ubuntu::RequestContextResolver::Ptr request_context_resolver
const core::Property< bool > & can_edit_tracks() const
Container::const_iterator ConstIterator
Definition: track_list.h:46
core::dbus::Signal< Signals::TrackRemoved, Signals::TrackRemoved::ArgumentType >::Ptr track_removed
Definition: track_list.h:181
struct mpris::TrackList::Skeleton::@19 properties
const TrackList::ConstIterator & current_iterator()
core::dbus::Signal< Signals::TrackChanged, Signals::TrackChanged::ArgumentType >::Ptr track_changed
Definition: track_list.h:182
TrackListSkeleton(const core::dbus::Bus::Ptr &bus, const core::dbus::Object::Ptr &object, const core::ubuntu::media::apparmor::ubuntu::RequestContextResolver::Ptr &request_context_resolver, const core::ubuntu::media::apparmor::ubuntu::RequestAuthenticator::Ptr &request_authenticator)
void handle_get_tracks_uri(const core::dbus::Message::Ptr &msg)
TrackList::ConstIterator empty_iterator
Private(media::TrackListSkeleton *impl, const dbus::Bus::Ptr &bus, const dbus::Object::Ptr &object, const apparmor::ubuntu::RequestContextResolver::Ptr &request_context_resolver, const media::apparmor::ubuntu::RequestAuthenticator::Ptr &request_authenticator)