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 "apparmor.h"
23 
24 #include "mpris/media_player2.h"
25 #include "mpris/metadata.h"
26 #include "mpris/player.h"
27 #include "mpris/playlists.h"
28 #include "mpris/service.h"
29 
30 #include "player_configuration.h"
31 #include "the_session_bus.h"
32 #include "xesam.h"
33 
34 #include <core/dbus/message.h>
35 #include <core/dbus/object.h>
36 #include <core/dbus/types/object_path.h>
37 
38 #include <core/posix/this_process.h>
39 
40 #include <map>
41 #include <regex>
42 #include <sstream>
43 
44 namespace dbus = core::dbus;
45 namespace media = core::ubuntu::media;
46 
47 namespace
48 {
49 core::Signal<void> the_empty_signal;
50 }
51 
53 {
55  : impl(impl),
56  object(impl->access_service()->add_object_for_path(
57  dbus::traits::Service<media::Service>::object_path())),
58  dbus_stub(impl->access_bus()),
59  exported(impl->access_bus(), resolver)
60  {
61  object->install_method_handler<mpris::Service::CreateSession>(
62  std::bind(
64  this,
65  std::placeholders::_1));
66  object->install_method_handler<mpris::Service::CreateFixedSession>(
67  std::bind(
69  this,
70  std::placeholders::_1));
71  object->install_method_handler<mpris::Service::ResumeSession>(
72  std::bind(
74  this,
75  std::placeholders::_1));
76  object->install_method_handler<mpris::Service::PauseOtherSessions>(
77  std::bind(
79  this,
80  std::placeholders::_1));
81  }
82 
83  std::pair<std::string, media::Player::PlayerKey> create_session_info()
84  {
85  static unsigned int session_counter = 0;
86 
87  unsigned int current_session = session_counter++;
88 
89  std::stringstream ss;
90  ss << "/core/ubuntu/media/Service/sessions/" << current_session;
91 
92  return std::make_pair(ss.str(), media::Player::PlayerKey(current_session));
93  }
94 
95  void handle_create_session(const core::dbus::Message::Ptr& msg)
96  {
97  auto session_info = create_session_info();
98 
99  dbus::types::ObjectPath op{session_info.first};
100  media::Player::PlayerKey key{session_info.second};
101 
102  dbus_stub.get_connection_app_armor_security_async(msg->sender(), [this, msg, op, key](const std::string& profile)
103  {
104  media::Player::Configuration config
105  {
106  profile,
107  key,
108  impl->access_bus(),
109  impl->access_service()->add_object_for_path(op)
110  };
111 
112  try
113  {
114  auto session = impl->create_session(config);
115 
116  bool inserted = false;
117  std::tie(std::ignore, inserted)
118  = session_store.insert(std::make_pair(key, session));
119 
120  if (!inserted)
121  throw std::runtime_error("Problem persisting session in session store.");
122 
123 
124  auto reply = dbus::Message::make_method_return(msg);
125  reply->writer() << op;
126 
127  impl->access_bus()->send(reply);
128  } catch(const std::runtime_error& e)
129  {
130  auto reply = dbus::Message::make_error(
131  msg,
133  e.what());
134  impl->access_bus()->send(reply);
135  }
136  });
137  }
138 
139  void handle_create_fixed_session(const core::dbus::Message::Ptr& msg)
140  {
141  dbus_stub.get_connection_app_armor_security_async(msg->sender(), [this, msg](const std::string& profile)
142  {
143  try
144  {
145  std::string name;
146  msg->reader() >> name;
147 
148  if (fixed_session_store.count(name) == 0) {
149  // Create new session
150  auto session_info = create_session_info();
151 
152  dbus::types::ObjectPath op{session_info.first};
153  media::Player::PlayerKey key{session_info.second};
154 
155  media::Player::Configuration config
156  {
157  profile,
158  key,
159  impl->access_bus(),
160  impl->access_service()->add_object_for_path(op)
161  };
162 
163  auto session = impl->create_session(config);
164  session->lifetime().set(media::Player::Lifetime::resumable);
165 
166  bool inserted = false;
167  std::tie(std::ignore, inserted)
168  = session_store.insert(std::make_pair(key, session));
169 
170  if (!inserted)
171  throw std::runtime_error("Problem persisting session in session store.");
172 
173  fixed_session_store.insert(std::make_pair(name, key));
174 
175  auto reply = dbus::Message::make_method_return(msg);
176  reply->writer() << op;
177 
178  impl->access_bus()->send(reply);
179  }
180  else {
181  // Resume previous session
182  auto key = fixed_session_store[name];
183  if (session_store.count(key) == 0) {
184  auto reply = dbus::Message::make_error(
185  msg,
187  "Unable to locate player session");
188  impl->access_bus()->send(reply);
189  return;
190  }
191 
192  std::stringstream ss;
193  ss << "/core/ubuntu/media/Service/sessions/" << key;
194  dbus::types::ObjectPath op{ss.str()};
195 
196  auto reply = dbus::Message::make_method_return(msg);
197  reply->writer() << op;
198 
199  impl->access_bus()->send(reply);
200  }
201  } catch(const std::runtime_error& e)
202  {
203  auto reply = dbus::Message::make_error(
204  msg,
206  e.what());
207  impl->access_bus()->send(reply);
208  }
209  });
210  }
211 
212  void handle_resume_session(const core::dbus::Message::Ptr& msg)
213  {
214  dbus_stub.get_connection_app_armor_security_async(msg->sender(), [this, msg](const std::string&)
215  {
216  try
217  {
218  Player::PlayerKey key;
219  msg->reader() >> key;
220 
221  if (session_store.count(key) == 0) {
222  auto reply = dbus::Message::make_error(
223  msg,
225  "Unable to locate player session");
226  impl->access_bus()->send(reply);
227  return;
228  }
229 
230  std::stringstream ss;
231  ss << "/core/ubuntu/media/Service/sessions/" << key;
232  dbus::types::ObjectPath op{ss.str()};
233 
234  auto reply = dbus::Message::make_method_return(msg);
235  reply->writer() << op;
236 
237  impl->access_bus()->send(reply);
238  } catch(const std::runtime_error& e)
239  {
240  auto reply = dbus::Message::make_error(
241  msg,
243  e.what());
244  impl->access_bus()->send(reply);
245  }
246  });
247  }
248 
249  void handle_pause_other_sessions(const core::dbus::Message::Ptr& msg)
250  {
251  std::cout << __PRETTY_FUNCTION__ << std::endl;
252  Player::PlayerKey key;
253  msg->reader() >> key;
254  impl->pause_other_sessions(key);
255 
256  auto reply = dbus::Message::make_method_return(msg);
257  impl->access_bus()->send(reply);
258  }
259 
261  dbus::Object::Ptr object;
262 
263  // We query the apparmor profile to obtain an identity for players.
265  // We track all running player instances.
266  std::map<media::Player::PlayerKey, std::shared_ptr<media::Player>> session_store;
267  std::map<std::string, media::Player::PlayerKey> fixed_session_store;
268  // We expose the entire service as an MPRIS player.
269  struct Exported
270  {
272  {
274  // TODO(tvoss): These three elements really should be configurable.
275  defaults.identity = "core::media::Hub";
276  defaults.desktop_entry = "mediaplayer-app";
277  defaults.supported_mime_types = {"audio/mpeg3"};
278 
279  return defaults;
280  }
281 
283  {
285 
286  // Disabled as track list is not fully implemented yet.
287  defaults.can_go_next = false;
288  // Disabled as track list is not fully implemented yet.
289  defaults.can_go_previous = false;
290 
291  return defaults;
292  }
293 
294  static std::string service_name()
295  {
296  static const bool export_to_indicator_sound_via_mpris
297  {
298  core::posix::this_process::env::get("UBUNTU_MEDIA_HUB_EXPORT_TO_INDICATOR_VIA_MPRIS", "0") == "1"
299  };
300 
301  return export_to_indicator_sound_via_mpris ? "org.mpris.MediaPlayer2.MediaHub" :
302  "hidden.org.mpris.MediaPlayer2.MediaHub";
303  }
304 
305  explicit Exported(const dbus::Bus::Ptr& bus, const media::CoverArtResolver& cover_art_resolver)
306  : bus{bus},
307  service{dbus::Service::add_service(bus, service_name())},
308  object{service->add_object_for_path(dbus::types::ObjectPath{"/org/mpris/MediaPlayer2"})},
313  {
314  object->install_method_handler<core::dbus::interfaces::Properties::GetAll>([this](const core::dbus::Message::Ptr& msg)
315  {
316  // Extract the interface
317  std::string itf; msg->reader() >> itf;
318  core::dbus::Message::Ptr reply = core::dbus::Message::make_method_return(msg);
319 
320  if (itf == mpris::Player::name())
321  reply->writer() << player.get_all_properties();
322  else if (itf == mpris::MediaPlayer2::name())
323  reply->writer() << media_player.get_all_properties();
324  else if (itf == mpris::Playlists::name())
325  reply->writer() << playlists.get_all_properties();
326 
327  Exported::bus->send(reply);
328  });
329 
330  // Setup method handlers for mpris::Player methods.
331  auto next = [this](const core::dbus::Message::Ptr& msg)
332  {
333  auto sp = current_player.lock();
334 
335  if (sp)
336  sp->next();
337 
338  Exported::bus->send(core::dbus::Message::make_method_return(msg));
339  };
340  object->install_method_handler<mpris::Player::Next>(next);
341 
342  auto previous = [this](const core::dbus::Message::Ptr& msg)
343  {
344  auto sp = current_player.lock();
345 
346  if (sp)
347  sp->previous();
348 
349  Exported::bus->send(core::dbus::Message::make_method_return(msg));
350  };
351  object->install_method_handler<mpris::Player::Previous>(previous);
352 
353  auto pause = [this](const core::dbus::Message::Ptr& msg)
354  {
355  auto sp = current_player.lock();
356 
357  if (sp)
358  sp->pause();
359 
360  Exported::bus->send(core::dbus::Message::make_method_return(msg));
361  };
362  object->install_method_handler<mpris::Player::Pause>(pause);
363 
364  auto stop = [this](const core::dbus::Message::Ptr& msg)
365  {
366  auto sp = current_player.lock();
367 
368  if (sp)
369  sp->stop();
370 
371  Exported::bus->send(core::dbus::Message::make_method_return(msg));
372  };
373  object->install_method_handler<mpris::Player::Stop>(stop);
374 
375  auto play = [this](const core::dbus::Message::Ptr& msg)
376  {
377  auto sp = current_player.lock();
378 
379  if (sp)
380  sp->play();
381 
382  Exported::bus->send(core::dbus::Message::make_method_return(msg));
383  };
384  object->install_method_handler<mpris::Player::Play>(play);
385 
386  auto play_pause = [this](const core::dbus::Message::Ptr& msg)
387  {
388  auto sp = current_player.lock();
389 
390  if (sp)
391  {
392  if (sp->playback_status() == media::Player::PlaybackStatus::playing)
393  sp->pause();
394  else if (sp->playback_status() != media::Player::PlaybackStatus::null)
395  sp->play();
396  }
397 
398  Exported::bus->send(core::dbus::Message::make_method_return(msg));
399  };
400  object->install_method_handler<mpris::Player::PlayPause>(play_pause);
401  }
402 
403  void set_current_player(const std::shared_ptr<media::Player>& cp)
404  {
406 
407  // We will not keep the object alive.
408  current_player = cp;
409 
410  // And announce that we can be controlled again.
411  player.properties.can_control->set(false);
412 
413  // We wire up player state changes
414  connections.seeked_to = cp->seeked_to().connect([this](std::uint64_t position)
415  {
416  player.signals.seeked_to->emit(position);
417  });
418 
419  connections.duration_changed = cp->duration().changed().connect([this](std::uint64_t duration)
420  {
421  player.properties.duration->set(duration);
422  });
423 
424  connections.position_changed = cp->position().changed().connect([this](std::uint64_t position)
425  {
426  player.properties.position->set(position);
427  });
428 
429  connections.playback_status_changed = cp->playback_status().changed().connect([this](core::ubuntu::media::Player::PlaybackStatus status)
430  {
432  });
433 
434  connections.loop_status_changed = cp->loop_status().changed().connect([this](core::ubuntu::media::Player::LoopStatus status)
435  {
437  });
438 
439  connections.meta_data_changed = cp->meta_data_for_current_track().changed().connect([this](const core::ubuntu::media::Track::MetaData& md)
440  {
442 
443  bool has_title = md.count(xesam::Title::name) > 0;
444  bool has_album_name = md.count(xesam::Album::name) > 0;
445  bool has_artist_name = md.count(xesam::Artist::name) > 0;
446 
447  if (has_title)
448  dict[xesam::Title::name] = dbus::types::Variant::encode(md.get(xesam::Title::name));
449  if (has_album_name)
450  dict[xesam::Album::name] = dbus::types::Variant::encode(md.get(xesam::Album::name));
451  if (has_artist_name)
452  dict[xesam::Artist::name] = dbus::types::Variant::encode(md.get(xesam::Artist::name));
453 
454  dict[mpris::metadata::ArtUrl::name] = dbus::types::Variant::encode(
456  has_title ? md.get(xesam::Title::name) : "",
457  has_album_name ? md.get(xesam::Album::name) : "",
458  has_artist_name ? md.get(xesam::Artist::name) : ""));
459 
461  wrap[mpris::Player::Properties::Metadata::name()] = dbus::types::Variant::encode(dict);
462 
464  std::make_tuple(
465  dbus::traits::Service<mpris::Player::Properties::Metadata::Interface>::interface_name(),
466  wrap,
467  std::vector<std::string>()));
468  });
469  }
470 
472  {
473  current_player.reset();
474 
475  // We disconnect all previous event connections.
476  connections.seeked_to.disconnect();
477  connections.duration_changed.disconnect();
478  connections.position_changed.disconnect();
479  connections.playback_status_changed.disconnect();
480  connections.loop_status_changed.disconnect();
481  connections.meta_data_changed.disconnect();
482 
483  // And announce that we cannot be controlled anymore.
484  player.properties.can_control->set(false);
485  }
486 
487  void unset_if_current(const std::shared_ptr<media::Player>& cp)
488  {
489  if (cp == current_player.lock())
491  }
492 
493  dbus::Bus::Ptr bus;
494  dbus::Service::Ptr service;
495  dbus::Object::Ptr object;
496 
500 
501  // Helper to resolve (title, artist, album) tuples to cover art.
503  // The actual player instance.
504  std::weak_ptr<media::Player> current_player;
505  // We track event connections.
506  struct
507  {
508  core::Connection seeked_to
509  {
510  the_empty_signal.connect([](){})
511  };
512  core::Connection duration_changed
513  {
514  the_empty_signal.connect([](){})
515  };
516  core::Connection position_changed
517  {
518  the_empty_signal.connect([](){})
519  };
520  core::Connection playback_status_changed
521  {
522  the_empty_signal.connect([](){})
523  };
524  core::Connection loop_status_changed
525  {
526  the_empty_signal.connect([](){})
527  };
528  core::Connection meta_data_changed
529  {
530  the_empty_signal.connect([](){})
531  };
532  } connections;
533  } exported;
534 };
535 
537  : dbus::Skeleton<media::Service>(the_session_bus()),
538  d(new Private(this, resolver))
539 {
540 }
541 
543 {
544 }
545 
547 {
548  return d->session_store.count(key) > 0;
549 }
550 
551 std::shared_ptr<media::Player> media::ServiceSkeleton::player_for_key(const media::Player::PlayerKey& key) const
552 {
553  return d->session_store.at(key);
554 }
555 
557 {
558  for (const auto& pair : d->session_store)
559  enumerator(pair.first, pair.second);
560 }
561 
563 {
564  if (not has_player_for_key(key))
565  return;
566 
567  d->exported.set_current_player(player_for_key(key));
568 }
569 
571 {
572  if (not has_player_for_key(key))
573  return;
574 
575  auto player = player_for_key(key);
576 
577  d->session_store.erase(key);
578  d->exported.unset_if_current(player);
579  // All non-durable fixed sessions are also removed
580  for (auto it: d->fixed_session_store) {
581  if (it.second == key) {
582  d->fixed_session_store.erase(it.first);
583  break;
584  }
585  }
586 }
587 
589 {
590  access_bus()->run();
591 }
592 
594 {
595  access_bus()->stop();
596 }
std::weak_ptr< media::Player > current_player
std::shared_ptr< core::dbus::Property< Properties::Duration > > duration
Definition: player.h:353
void set_current_player_for_key(const Player::PlayerKey &key)
Properties::CanGoPrevious::ValueType can_go_previous
Definition: player.h:184
Private(media::ServiceSkeleton *impl, const media::CoverArtResolver &resolver)
media::ServiceSkeleton * impl
ServiceSkeleton(const CoverArtResolver &cover_art_resolver=always_missing_cover_art_resolver())
bool has_player_for_key(const Player::PlayerKey &key) const
std::shared_ptr< core::dbus::Property< Properties::LoopStatus > > loop_status
Definition: player.h:343
static const std::string & name()
Definition: player.h:49
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:183
static const std::string & name()
Definition: media_player2.h:38
void unset_if_current(const std::shared_ptr< media::Player > &cp)
static const char * from(core::ubuntu::media::Player::PlaybackStatus status)
Definition: player.h:83
struct mpris::Player::Skeleton::@14 properties
Tag::ValueType get() const
Definition: track.h:70
void handle_resume_session(const core::dbus::Message::Ptr &msg)
void enumerate_players(const PlayerEnumerator &enumerator) const
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:106
static const std::string & name()
Definition: service.h:65
static const char * from(core::ubuntu::media::Player::LoopStatus status)
Definition: player.h:59
static mpris::Player::Skeleton::Configuration::Defaults player_defaults()
std::map< std::string, core::dbus::types::Variant > get_all_properties()
Definition: playlists.h:192
org::freedesktop::dbus::DBus::Stub dbus_stub
std::pair< std::string, media::Player::PlayerKey > create_session_info()
std::function< void(const core::ubuntu::media::Player::PlayerKey &, const std::shared_ptr< core::ubuntu::media::Player > &) > PlayerEnumerator
Exported(const dbus::Bus::Ptr &bus, const media::CoverArtResolver &cover_art_resolver)
std::function< std::string(const std::string &, const std::string &, const std::string &)> CoverArtResolver
void remove_player_for_key(const Player::PlayerKey &key)
void get_connection_app_armor_security_async(const std::string &name, std::function< void(const std::string &)> handler)
Definition: apparmor.h:75
Dictionary get_all_properties()
Definition: player.h:301
struct mpris::Player::Skeleton::@15 signals
struct media::ServiceSkeleton::Private::Exported::@19 connections
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:360
std::map< std::string, media::Player::PlayerKey > fixed_session_store
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:341
std::shared_ptr< Player > player_for_key(const Player::PlayerKey &key) const
std::shared_ptr< core::dbus::Property< Properties::CanControl > > can_control
Definition: player.h:335
std::shared_ptr< core::dbus::Property< Properties::Position > > position
Definition: player.h:352
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:370
std::map< media::Player::PlayerKey, std::shared_ptr< media::Player > > session_store
static const std::string & name()
Definition: playlists.h:54