Music Hub  ..
A session-wide music playback service
service_skeleton.cpp
Go to the documentation of this file.
1 /*
2  * Copyright © 2013-2014 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  * Jim Hodapp <jim.hodapp@canonical.com>
18  */
19 
20 #include "service_skeleton.h"
21 
22 #include "mpris/media_player2.h"
23 #include "mpris/metadata.h"
24 #include "mpris/player.h"
25 #include "mpris/playlists.h"
26 #include "mpris/service.h"
27 
28 #include "player_configuration.h"
29 #include "the_session_bus.h"
30 #include "xesam.h"
31 
32 #include <core/dbus/message.h>
33 #include <core/dbus/object.h>
34 #include <core/dbus/types/object_path.h>
35 
36 #include <core/posix/this_process.h>
37 
38 #include <map>
39 #include <regex>
40 #include <sstream>
41 
42 namespace dbus = core::dbus;
43 namespace media = core::ubuntu::media;
44 
45 namespace
46 {
47 core::Signal<void> the_empty_signal;
48 }
49 
51 {
52  Private(media::ServiceSkeleton* impl, const ServiceSkeleton::Configuration& config)
53  : impl(impl),
54  object(impl->access_service()->add_object_for_path(
55  dbus::traits::Service<media::Service>::object_path())),
56  exported(impl->access_bus(), config.cover_art_resolver),
57  configuration(config)
58  {
59  object->install_method_handler<mpris::Service::CreateSession>(
60  std::bind(
62  this,
63  std::placeholders::_1));
64  object->install_method_handler<mpris::Service::CreateFixedSession>(
65  std::bind(
67  this,
68  std::placeholders::_1));
69  object->install_method_handler<mpris::Service::ResumeSession>(
70  std::bind(
72  this,
73  std::placeholders::_1));
74  object->install_method_handler<mpris::Service::PauseOtherSessions>(
75  std::bind(
77  this,
78  std::placeholders::_1));
79  }
80 
81  std::pair<std::string, media::Player::PlayerKey> create_session_info()
82  {
83  static unsigned int session_counter = 0;
84 
85  unsigned int current_session = session_counter++;
86 
87  std::stringstream ss;
88  ss << "/core/ubuntu/media/Service/sessions/" << current_session;
89 
90  return std::make_pair(ss.str(), media::Player::PlayerKey(current_session));
91  }
92 
93  void handle_create_session(const core::dbus::Message::Ptr& msg)
94  {
95  auto session_info = create_session_info();
96 
97  dbus::types::ObjectPath op{session_info.first};
98  media::Player::PlayerKey key{session_info.second};
99 
100  media::Player::Configuration config
101  {
102  key,
103  impl->access_bus(),
104  impl->access_service()->add_object_for_path(op)
105  };
106 
107  try
108  {
109  configuration.player_store->add_player_for_key(key, impl->create_session(config));
110  auto reply = dbus::Message::make_method_return(msg);
111  reply->writer() << op;
112 
113  impl->access_bus()->send(reply);
114  } catch(const std::runtime_error& e)
115  {
116  auto reply = dbus::Message::make_error(
117  msg,
119  e.what());
120  impl->access_bus()->send(reply);
121  }
122  }
123 
124  void handle_create_fixed_session(const core::dbus::Message::Ptr& msg)
125  {
126  try
127  {
128  std::string name;
129  msg->reader() >> name;
130 
131  if (named_player_map.count(name) == 0) {
132  // Create new session
133  auto session_info = create_session_info();
134 
135  dbus::types::ObjectPath op{session_info.first};
136  media::Player::PlayerKey key{session_info.second};
137 
138  media::Player::Configuration config
139  {
140  key,
141  impl->access_bus(),
142  impl->access_service()->add_object_for_path(op)
143  };
144 
145  auto session = impl->create_session(config);
146  session->lifetime().set(media::Player::Lifetime::resumable);
147 
148  configuration.player_store->add_player_for_key(key, session);
149 
150 
151  named_player_map.insert(std::make_pair(name, key));
152 
153  auto reply = dbus::Message::make_method_return(msg);
154  reply->writer() << op;
155 
156  impl->access_bus()->send(reply);
157  }
158  else {
159  // Resume previous session
160  auto key = named_player_map.at(name);
161  if (not configuration.player_store->has_player_for_key(key)) {
162  auto reply = dbus::Message::make_error(
163  msg,
165  "Unable to locate player session");
166  impl->access_bus()->send(reply);
167  return;
168  }
169 
170  std::stringstream ss;
171  ss << "/core/ubuntu/media/Service/sessions/" << key;
172  dbus::types::ObjectPath op{ss.str()};
173 
174  auto reply = dbus::Message::make_method_return(msg);
175  reply->writer() << op;
176 
177  impl->access_bus()->send(reply);
178  }
179  } catch(const std::runtime_error& e)
180  {
181  auto reply = dbus::Message::make_error(
182  msg,
184  e.what());
185  impl->access_bus()->send(reply);
186  }
187  }
188 
189  void handle_resume_session(const core::dbus::Message::Ptr& msg)
190  {
191  try
192  {
193  Player::PlayerKey key;
194  msg->reader() >> key;
195 
196  if (not configuration.player_store->has_player_for_key(key)) {
197  auto reply = dbus::Message::make_error(
198  msg,
200  "Unable to locate player session");
201  impl->access_bus()->send(reply);
202  return;
203  }
204 
205  std::stringstream ss;
206  ss << "/core/ubuntu/media/Service/sessions/" << key;
207  dbus::types::ObjectPath op{ss.str()};
208 
209  auto reply = dbus::Message::make_method_return(msg);
210  reply->writer() << op;
211 
212  impl->access_bus()->send(reply);
213  } catch(const std::runtime_error& e)
214  {
215  auto reply = dbus::Message::make_error(
216  msg,
218  e.what());
219  impl->access_bus()->send(reply);
220  }
221  }
222 
223  void handle_pause_other_sessions(const core::dbus::Message::Ptr& msg)
224  {
225  std::cout << __PRETTY_FUNCTION__ << std::endl;
226  Player::PlayerKey key;
227  msg->reader() >> key;
228  impl->pause_other_sessions(key);
229 
230  auto reply = dbus::Message::make_method_return(msg);
231  impl->access_bus()->send(reply);
232  }
233 
235  dbus::Object::Ptr object;
236 
237  // We remember all our creation time arguments.
238  ServiceSkeleton::Configuration configuration;
239  // We map named/fixed player instances to their respective keys.
240  std::map<std::string, media::Player::PlayerKey> named_player_map;
241  // We expose the entire service as an MPRIS player.
242  struct Exported
243  {
245  {
247  // TODO(tvoss): These three elements really should be configurable.
248  defaults.identity = "core::media::Hub";
249  defaults.desktop_entry = "mediaplayer-app";
250  defaults.supported_mime_types = {"audio/mpeg3"};
251 
252  return defaults;
253  }
254 
256  {
258 
259  // Disabled as track list is not fully implemented yet.
260  defaults.can_go_next = false;
261  // Disabled as track list is not fully implemented yet.
262  defaults.can_go_previous = false;
263 
264  return defaults;
265  }
266 
267  static std::string service_name()
268  {
269  static const bool export_to_indicator_sound_via_mpris
270  {
271  core::posix::this_process::env::get("UBUNTU_MEDIA_HUB_EXPORT_TO_INDICATOR_VIA_MPRIS", "0") == "1"
272  };
273 
274  return export_to_indicator_sound_via_mpris ? "org.mpris.MediaPlayer2.MediaHub" :
275  "hidden.org.mpris.MediaPlayer2.MediaHub";
276  }
277 
278  explicit Exported(const dbus::Bus::Ptr& bus, const media::CoverArtResolver& cover_art_resolver)
279  : bus{bus},
280  service{dbus::Service::add_service(bus, service_name())},
281  object{service->add_object_for_path(dbus::types::ObjectPath{"/org/mpris/MediaPlayer2"})},
286  {
287  object->install_method_handler<core::dbus::interfaces::Properties::GetAll>([this](const core::dbus::Message::Ptr& msg)
288  {
289  // Extract the interface
290  std::string itf; msg->reader() >> itf;
291  core::dbus::Message::Ptr reply = core::dbus::Message::make_method_return(msg);
292 
293  if (itf == mpris::Player::name())
294  reply->writer() << player.get_all_properties();
295  else if (itf == mpris::MediaPlayer2::name())
296  reply->writer() << media_player.get_all_properties();
297  else if (itf == mpris::Playlists::name())
298  reply->writer() << playlists.get_all_properties();
299 
300  Exported::bus->send(reply);
301  });
302 
303  // Setup method handlers for mpris::Player methods.
304  auto next = [this](const core::dbus::Message::Ptr& msg)
305  {
306  auto sp = current_player.lock();
307 
308  if (sp)
309  sp->next();
310 
311  Exported::bus->send(core::dbus::Message::make_method_return(msg));
312  };
313  object->install_method_handler<mpris::Player::Next>(next);
314 
315  auto previous = [this](const core::dbus::Message::Ptr& msg)
316  {
317  auto sp = current_player.lock();
318 
319  if (sp)
320  sp->previous();
321 
322  Exported::bus->send(core::dbus::Message::make_method_return(msg));
323  };
324  object->install_method_handler<mpris::Player::Previous>(previous);
325 
326  auto pause = [this](const core::dbus::Message::Ptr& msg)
327  {
328  auto sp = current_player.lock();
329 
330  if (sp)
331  sp->pause();
332 
333  Exported::bus->send(core::dbus::Message::make_method_return(msg));
334  };
335  object->install_method_handler<mpris::Player::Pause>(pause);
336 
337  auto stop = [this](const core::dbus::Message::Ptr& msg)
338  {
339  auto sp = current_player.lock();
340 
341  if (sp)
342  sp->stop();
343 
344  Exported::bus->send(core::dbus::Message::make_method_return(msg));
345  };
346  object->install_method_handler<mpris::Player::Stop>(stop);
347 
348  auto play = [this](const core::dbus::Message::Ptr& msg)
349  {
350  auto sp = current_player.lock();
351 
352  if (sp)
353  sp->play();
354 
355  Exported::bus->send(core::dbus::Message::make_method_return(msg));
356  };
357  object->install_method_handler<mpris::Player::Play>(play);
358 
359  auto play_pause = [this](const core::dbus::Message::Ptr& msg)
360  {
361  auto sp = current_player.lock();
362 
363  if (sp)
364  {
365  if (sp->playback_status() == media::Player::PlaybackStatus::playing)
366  sp->pause();
367  else if (sp->playback_status() != media::Player::PlaybackStatus::null)
368  sp->play();
369  }
370 
371  Exported::bus->send(core::dbus::Message::make_method_return(msg));
372  };
373  object->install_method_handler<mpris::Player::PlayPause>(play_pause);
374  }
375 
376  void set_current_player(const std::shared_ptr<media::Player>& cp)
377  {
379 
380  // We will not keep the object alive.
381  current_player = cp;
382 
383  // And announce that we can be controlled again.
384  player.properties.can_control->set(false);
385 
386  // We wire up player state changes
387  connections.seeked_to = cp->seeked_to().connect([this](std::uint64_t position)
388  {
389  player.signals.seeked_to->emit(position);
390  });
391 
392  connections.duration_changed = cp->duration().changed().connect([this](std::uint64_t duration)
393  {
394  player.properties.duration->set(duration);
395  });
396 
397  connections.position_changed = cp->position().changed().connect([this](std::uint64_t position)
398  {
399  player.properties.position->set(position);
400  });
401 
402  connections.playback_status_changed = cp->playback_status().changed().connect([this](core::ubuntu::media::Player::PlaybackStatus status)
403  {
405  });
406 
407  connections.loop_status_changed = cp->loop_status().changed().connect([this](core::ubuntu::media::Player::LoopStatus status)
408  {
410  });
411 
412  connections.meta_data_changed = cp->meta_data_for_current_track().changed().connect([this](const core::ubuntu::media::Track::MetaData& md)
413  {
415 
416  bool has_title = md.count(xesam::Title::name) > 0;
417  bool has_album_name = md.count(xesam::Album::name) > 0;
418  bool has_artist_name = md.count(xesam::Artist::name) > 0;
419 
420  if (has_title)
421  dict[xesam::Title::name] = dbus::types::Variant::encode(md.get(xesam::Title::name));
422  if (has_album_name)
423  dict[xesam::Album::name] = dbus::types::Variant::encode(md.get(xesam::Album::name));
424  if (has_artist_name)
425  dict[xesam::Artist::name] = dbus::types::Variant::encode(md.get(xesam::Artist::name));
426 
427  dict[mpris::metadata::ArtUrl::name] = dbus::types::Variant::encode(
429  has_title ? md.get(xesam::Title::name) : "",
430  has_album_name ? md.get(xesam::Album::name) : "",
431  has_artist_name ? md.get(xesam::Artist::name) : ""));
432 
434  wrap[mpris::Player::Properties::Metadata::name()] = dbus::types::Variant::encode(dict);
435 
437  std::make_tuple(
438  dbus::traits::Service<mpris::Player::Properties::Metadata::Interface>::interface_name(),
439  wrap,
440  std::vector<std::string>()));
441  });
442  }
443 
445  {
446  current_player.reset();
447 
448  // We disconnect all previous event connections.
449  connections.seeked_to.disconnect();
450  connections.duration_changed.disconnect();
451  connections.position_changed.disconnect();
452  connections.playback_status_changed.disconnect();
453  connections.loop_status_changed.disconnect();
454  connections.meta_data_changed.disconnect();
455 
456  // And announce that we cannot be controlled anymore.
457  player.properties.can_control->set(false);
458  }
459 
460  void unset_if_current(const std::shared_ptr<media::Player>& cp)
461  {
462  if (cp == current_player.lock())
464  }
465 
466  dbus::Bus::Ptr bus;
467  dbus::Service::Ptr service;
468  dbus::Object::Ptr object;
469 
473 
474  // The CoverArtResolver used by the exported player.
476  // The actual player instance.
477  std::weak_ptr<media::Player> current_player;
478  // We track event connections.
479  struct
480  {
481  core::Connection seeked_to
482  {
483  the_empty_signal.connect([](){})
484  };
485  core::Connection duration_changed
486  {
487  the_empty_signal.connect([](){})
488  };
489  core::Connection position_changed
490  {
491  the_empty_signal.connect([](){})
492  };
493  core::Connection playback_status_changed
494  {
495  the_empty_signal.connect([](){})
496  };
497  core::Connection loop_status_changed
498  {
499  the_empty_signal.connect([](){})
500  };
501  core::Connection meta_data_changed
502  {
503  the_empty_signal.connect([](){})
504  };
505  } connections;
506  } exported;
507 };
508 
509 media::ServiceSkeleton::ServiceSkeleton(const Configuration& configuration)
510  : dbus::Skeleton<media::Service>(the_session_bus()),
511  d(new Private(this, configuration))
512 {
513 }
514 
516 {
517 }
518 
519 std::shared_ptr<media::Player> media::ServiceSkeleton::create_session(const media::Player::Configuration& config)
520 {
521  return d->configuration.impl->create_session(config);
522 }
523 
524 std::shared_ptr<media::Player> media::ServiceSkeleton::create_fixed_session(const std::string& name, const media::Player::Configuration&config)
525 {
526  return d->configuration.impl->create_fixed_session(name, config);
527 }
528 
529 std::shared_ptr<media::Player> media::ServiceSkeleton::resume_session(media::Player::PlayerKey key)
530 {
531  return d->configuration.impl->resume_session(key);
532 }
533 
535 {
536  d->configuration.impl->pause_other_sessions(key);
537 }
538 
540 {
541  access_bus()->run();
542 }
543 
545 {
546  access_bus()->stop();
547 }
std::weak_ptr< media::Player > current_player
struct mpris::Player::Skeleton::@16 signals
std::shared_ptr< core::dbus::Property< Properties::Duration > > duration
Definition: player.h:370
Properties::CanGoPrevious::ValueType can_go_previous
Definition: player.h:200
media::ServiceSkeleton * impl
std::shared_ptr< core::dbus::Property< Properties::LoopStatus > > loop_status
Definition: player.h:360
Private(media::ServiceSkeleton *impl, const ServiceSkeleton::Configuration &config)
static const std::string & name()
Definition: player.h:53
void handle_create_fixed_session(const core::dbus::Message::Ptr &msg)
static mpris::MediaPlayer2::Skeleton::Configuration::Defaults media_player_defaults()
Properties::SupportedMimeTypes::ValueType supported_mime_types
void handle_create_session(const core::dbus::Message::Ptr &msg)
Properties::CanGoNext::ValueType can_go_next
Definition: player.h:199
static const std::string & name()
Definition: media_player2.h:38
void unset_if_current(const std::shared_ptr< media::Player > &cp)
struct mpris::Player::Skeleton::@15 properties
static const char * from(core::ubuntu::media::Player::PlaybackStatus status)
Definition: player.h:87
struct media::ServiceSkeleton::Private::Exported::@25 connections
Tag::ValueType get() const
Definition: track.h:70
void handle_resume_session(const core::dbus::Message::Ptr &msg)
mpris::MediaPlayer2::Skeleton media_player
void set_current_player(const std::shared_ptr< media::Player > &cp)
std::map< std::string, core::dbus::types::Variant > Dictionary
Definition: player.h:121
static const std::string & name()
Definition: service.h:65
static const char * from(core::ubuntu::media::Player::LoopStatus status)
Definition: player.h:63
static mpris::Player::Skeleton::Configuration::Defaults player_defaults()
std::shared_ptr< Player > resume_session(Player::PlayerKey)
std::map< std::string, core::dbus::types::Variant > get_all_properties()
Definition: playlists.h:192
ServiceSkeleton(const Configuration &configuration)
std::pair< std::string, media::Player::PlayerKey > create_session_info()
Exported(const dbus::Bus::Ptr &bus, const media::CoverArtResolver &cover_art_resolver)
void pause_other_sessions(Player::PlayerKey key)
std::function< std::string(const std::string &, const std::string &, const std::string &)> CoverArtResolver
ServiceSkeleton::Configuration configuration
Dictionary get_all_properties()
Definition: player.h:318
static const std::string & name()
Definition: service.h:41
Properties::DesktopEntry::ValueType desktop_entry
core::dbus::Bus::Ptr the_session_bus()
core::dbus::Signal< Signals::Seeked, Signals::Seeked::ArgumentType >::Ptr seeked_to
Definition: player.h:377
static constexpr const char * name
Definition: metadata.h:49
std::size_t count() const
Definition: track.h:57
std::shared_ptr< core::dbus::Property< Properties::PlaybackStatus > > playback_status
Definition: player.h:358
std::shared_ptr< core::dbus::Property< Properties::CanControl > > can_control
Definition: player.h:352
std::map< std::string, media::Player::PlayerKey > named_player_map
std::shared_ptr< core::dbus::Property< Properties::Position > > position
Definition: player.h:369
std::shared_ptr< Player > create_fixed_session(const std::string &name, const Player::Configuration &)
struct media::ServiceSkeleton::Private::Exported exported
void handle_pause_other_sessions(const core::dbus::Message::Ptr &msg)
static const std::string & name()
Definition: service.h:53
std::map< std::string, core::dbus::types::Variant > get_all_properties()
dbus::Signal< core::dbus::interfaces::Properties::Signals::PropertiesChanged, core::dbus::interfaces::Properties::Signals::PropertiesChanged::ArgumentType >::Ptr properties_changed
Definition: player.h:388
static const std::string & name()
Definition: playlists.h:54
std::shared_ptr< Player > create_session(const Player::Configuration &)