Music Hub  ..
A session-wide music playback service
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros
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  */
18 
19 #include "service_skeleton.h"
20 
21 #include "apparmor.h"
22 
23 #include "mpris/media_player2.h"
24 #include "mpris/metadata.h"
25 #include "mpris/player.h"
26 #include "mpris/playlists.h"
27 #include "mpris/service.h"
28 
29 #include "player_configuration.h"
30 #include "the_session_bus.h"
31 #include "xesam.h"
32 
33 #include <core/dbus/message.h>
34 #include <core/dbus/object.h>
35 #include <core/dbus/types/object_path.h>
36 
37 #include <core/posix/this_process.h>
38 
39 #include <map>
40 #include <regex>
41 #include <sstream>
42 
43 namespace dbus = core::dbus;
44 namespace media = core::ubuntu::media;
45 
46 namespace
47 {
48 core::Signal<void> the_empty_signal;
49 }
50 
52 {
54  : impl(impl),
55  object(impl->access_service()->add_object_for_path(
56  dbus::traits::Service<media::Service>::object_path())),
57  dbus_stub(impl->access_bus()),
58  exported(impl->access_bus(), resolver)
59  {
60  object->install_method_handler<mpris::Service::CreateSession>(
61  std::bind(
63  this,
64  std::placeholders::_1));
65  object->install_method_handler<mpris::Service::PauseOtherSessions>(
66  std::bind(
68  this,
69  std::placeholders::_1));
70  }
71 
72  void handle_create_session(const core::dbus::Message::Ptr& msg)
73  {
74  static unsigned int session_counter = 0;
75 
76  std::stringstream ss;
77  ss << "/core/ubuntu/media/Service/sessions/" << session_counter++;
78 
79  dbus::types::ObjectPath op{ss.str()};
80  media::Player::PlayerKey key{session_counter};
81 
82  dbus_stub.get_connection_app_armor_security_async(msg->sender(), [this, msg, op, key](const std::string& profile)
83  {
84  media::Player::Configuration config
85  {
86  profile,
87  key,
88  impl->access_bus(),
89  impl->access_service()->add_object_for_path(op)
90  };
91 
92  try
93  {
94  auto session = impl->create_session(config);
95 
96  bool inserted = false;
97  std::tie(std::ignore, inserted)
98  = session_store.insert(std::make_pair(key, session));
99 
100  if (!inserted)
101  throw std::runtime_error("Problem persisting session in session store.");
102 
103  auto reply = dbus::Message::make_method_return(msg);
104  reply->writer() << op;
105 
106  impl->access_bus()->send(reply);
107  } catch(const std::runtime_error& e)
108  {
109  auto reply = dbus::Message::make_error(
110  msg,
112  e.what());
113  impl->access_bus()->send(reply);
114  }
115  });
116  }
117 
118  void handle_pause_other_sessions(const core::dbus::Message::Ptr& msg)
119  {
120  std::cout << __PRETTY_FUNCTION__ << std::endl;
121  Player::PlayerKey key;
122  msg->reader() >> key;
123  impl->pause_other_sessions(key);
124 
125  auto reply = dbus::Message::make_method_return(msg);
126  impl->access_bus()->send(reply);
127  }
128 
130  dbus::Object::Ptr object;
131 
132  // We query the apparmor profile to obtain an identity for players.
134  // We track all running player instances.
135  std::map<media::Player::PlayerKey, std::shared_ptr<media::Player>> session_store;
136  // We expose the entire service as an MPRIS player.
137  struct Exported
138  {
140  {
142  // TODO(tvoss): These three elements really should be configurable.
143  defaults.identity = "core::media::Hub";
144  defaults.desktop_entry = "mediaplayer-app";
145  defaults.supported_mime_types = {"audio/mpeg3"};
146 
147  return defaults;
148  }
149 
151  {
153 
154  // Disabled as track list is not fully implemented yet.
155  defaults.can_go_next = false;
156  // Disabled as track list is not fully implemented yet.
157  defaults.can_go_previous = false;
158 
159  return defaults;
160  }
161 
162  static std::string service_name()
163  {
164  static const bool export_to_indicator_sound_via_mpris
165  {
166  core::posix::this_process::env::get("UBUNTU_MEDIA_HUB_EXPORT_TO_INDICATOR_VIA_MPRIS", "0") == "1"
167  };
168 
169  return export_to_indicator_sound_via_mpris ? "org.mpris.MediaPlayer2.MediaHub" :
170  "hidden.org.mpris.MediaPlayer2.MediaHub";
171  }
172 
173  explicit Exported(const dbus::Bus::Ptr& bus, const media::CoverArtResolver& cover_art_resolver)
174  : bus{bus},
175  service{dbus::Service::add_service(bus, service_name())},
176  object{service->add_object_for_path(dbus::types::ObjectPath{"/org/mpris/MediaPlayer2"})},
181  {
182  object->install_method_handler<core::dbus::interfaces::Properties::GetAll>([this](const core::dbus::Message::Ptr& msg)
183  {
184  // Extract the interface
185  std::string itf; msg->reader() >> itf;
186  core::dbus::Message::Ptr reply = core::dbus::Message::make_method_return(msg);
187 
188  if (itf == mpris::Player::name())
189  reply->writer() << player.get_all_properties();
190  else if (itf == mpris::MediaPlayer2::name())
191  reply->writer() << media_player.get_all_properties();
192  else if (itf == mpris::Playlists::name())
193  reply->writer() << playlists.get_all_properties();
194 
195  Exported::bus->send(reply);
196  });
197 
198  // Setup method handlers for mpris::Player methods.
199  auto next = [this](const core::dbus::Message::Ptr& msg)
200  {
201  auto sp = current_player.lock();
202 
203  if (sp)
204  sp->next();
205 
206  Exported::bus->send(core::dbus::Message::make_method_return(msg));
207  };
208  object->install_method_handler<mpris::Player::Next>(next);
209 
210  auto previous = [this](const core::dbus::Message::Ptr& msg)
211  {
212  auto sp = current_player.lock();
213 
214  if (sp)
215  sp->previous();
216 
217  Exported::bus->send(core::dbus::Message::make_method_return(msg));
218  };
219  object->install_method_handler<mpris::Player::Previous>(previous);
220 
221  auto pause = [this](const core::dbus::Message::Ptr& msg)
222  {
223  auto sp = current_player.lock();
224 
225  if (sp)
226  sp->pause();
227 
228  Exported::bus->send(core::dbus::Message::make_method_return(msg));
229  };
230  object->install_method_handler<mpris::Player::Pause>(pause);
231 
232  auto stop = [this](const core::dbus::Message::Ptr& msg)
233  {
234  auto sp = current_player.lock();
235 
236  if (sp)
237  sp->stop();
238 
239  Exported::bus->send(core::dbus::Message::make_method_return(msg));
240  };
241  object->install_method_handler<mpris::Player::Stop>(stop);
242 
243  auto play = [this](const core::dbus::Message::Ptr& msg)
244  {
245  auto sp = current_player.lock();
246 
247  if (sp)
248  sp->play();
249 
250  Exported::bus->send(core::dbus::Message::make_method_return(msg));
251  };
252  object->install_method_handler<mpris::Player::Play>(play);
253 
254  auto play_pause = [this](const core::dbus::Message::Ptr& msg)
255  {
256  auto sp = current_player.lock();
257 
258  if (sp)
259  {
260  if (sp->playback_status() == media::Player::PlaybackStatus::playing)
261  sp->pause();
262  else if (sp->playback_status() != media::Player::PlaybackStatus::null)
263  sp->play();
264  }
265 
266  Exported::bus->send(core::dbus::Message::make_method_return(msg));
267  };
268  object->install_method_handler<mpris::Player::PlayPause>(play_pause);
269  }
270 
271  void set_current_player(const std::shared_ptr<media::Player>& cp)
272  {
274 
275  // We will not keep the object alive.
276  current_player = cp;
277 
278  // And announce that we can be controlled again.
279  player.properties.can_control->set(false);
280 
281  // We wire up player state changes
282  connections.seeked_to = cp->seeked_to().connect([this](std::uint64_t position)
283  {
284  player.signals.seeked_to->emit(position);
285  });
286 
287  connections.duration_changed = cp->duration().changed().connect([this](std::uint64_t duration)
288  {
289  player.properties.duration->set(duration);
290  });
291 
292  connections.position_changed = cp->position().changed().connect([this](std::uint64_t position)
293  {
294  player.properties.position->set(position);
295  });
296 
297  connections.playback_status_changed = cp->playback_status().changed().connect([this](core::ubuntu::media::Player::PlaybackStatus status)
298  {
300  });
301 
302  connections.loop_status_changed = cp->loop_status().changed().connect([this](core::ubuntu::media::Player::LoopStatus status)
303  {
305  });
306 
307  connections.meta_data_changed = cp->meta_data_for_current_track().changed().connect([this](const core::ubuntu::media::Track::MetaData& md)
308  {
310 
311  bool has_title = md.count(xesam::Title::name) > 0;
312  bool has_album_name = md.count(xesam::Album::name) > 0;
313  bool has_artist_name = md.count(xesam::Artist::name) > 0;
314 
315  if (has_title)
316  dict[xesam::Title::name] = dbus::types::Variant::encode(md.get(xesam::Title::name));
317  if (has_album_name)
318  dict[xesam::Album::name] = dbus::types::Variant::encode(md.get(xesam::Album::name));
319  if (has_artist_name)
320  dict[xesam::Artist::name] = dbus::types::Variant::encode(md.get(xesam::Artist::name));
321 
322  dict[mpris::metadata::ArtUrl::name] = dbus::types::Variant::encode(
324  has_title ? md.get(xesam::Title::name) : "",
325  has_album_name ? md.get(xesam::Album::name) : "",
326  has_artist_name ? md.get(xesam::Artist::name) : ""));
327 
329  wrap[mpris::Player::Properties::Metadata::name()] = dbus::types::Variant::encode(dict);
330 
332  std::make_tuple(
333  dbus::traits::Service<mpris::Player::Properties::Metadata::Interface>::interface_name(),
334  wrap,
335  std::vector<std::string>()));
336  });
337  }
338 
340  {
341  current_player.reset();
342 
343  // We disconnect all previous event connections.
344  connections.seeked_to.disconnect();
345  connections.duration_changed.disconnect();
346  connections.position_changed.disconnect();
347  connections.playback_status_changed.disconnect();
348  connections.loop_status_changed.disconnect();
349  connections.meta_data_changed.disconnect();
350 
351  // And announce that we cannot be controlled anymore.
352  player.properties.can_control->set(false);
353  }
354 
355  void unset_if_current(const std::shared_ptr<media::Player>& cp)
356  {
357  if (cp == current_player.lock())
359  }
360 
361  dbus::Bus::Ptr bus;
362  dbus::Service::Ptr service;
363  dbus::Object::Ptr object;
364 
368 
369  // Helper to resolve (title, artist, album) tuples to cover art.
371  // The actual player instance.
372  std::weak_ptr<media::Player> current_player;
373  // We track event connections.
374  struct
375  {
376  core::Connection seeked_to
377  {
378  the_empty_signal.connect([](){})
379  };
380  core::Connection duration_changed
381  {
382  the_empty_signal.connect([](){})
383  };
384  core::Connection position_changed
385  {
386  the_empty_signal.connect([](){})
387  };
388  core::Connection playback_status_changed
389  {
390  the_empty_signal.connect([](){})
391  };
392  core::Connection loop_status_changed
393  {
394  the_empty_signal.connect([](){})
395  };
396  core::Connection meta_data_changed
397  {
398  the_empty_signal.connect([](){})
399  };
400  } connections;
401  } exported;
402 };
403 
405  : dbus::Skeleton<media::Service>(the_session_bus()),
406  d(new Private(this, resolver))
407 {
408 }
409 
411 {
412 }
413 
415 {
416  return d->session_store.count(key) > 0;
417 }
418 
419 std::shared_ptr<media::Player> media::ServiceSkeleton::player_for_key(const media::Player::PlayerKey& key) const
420 {
421  return d->session_store.at(key);
422 }
423 
425 {
426  for (const auto& pair : d->session_store)
427  enumerator(pair.first, pair.second);
428 }
429 
431 {
432  if (not has_player_for_key(key))
433  return;
434 
435  d->exported.set_current_player(player_for_key(key));
436 }
437 
439 {
440  if (not has_player_for_key(key))
441  return;
442 
443  auto player = player_for_key(key);
444 
445  d->session_store.erase(key);
446  d->exported.unset_if_current(player);
447 }
448 
450 {
451  access_bus()->run();
452 }
453 
455 {
456  access_bus()->stop();
457 }
std::weak_ptr< media::Player > current_player
std::shared_ptr< core::dbus::Property< Properties::Duration > > duration
Definition: player.h:331
void set_current_player_for_key(const Player::PlayerKey &key)
Properties::CanGoPrevious::ValueType can_go_previous
Definition: player.h:179
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:323
static const std::string & name()
Definition: player.h:49
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:178
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 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 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::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:283
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:338
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:321
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:315
std::shared_ptr< core::dbus::Property< Properties::Position > > position
Definition: player.h:330
struct media::ServiceSkeleton::Private::Exported exported
void handle_pause_other_sessions(const core::dbus::Message::Ptr &msg)
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:346
std::map< media::Player::PlayerKey, std::shared_ptr< media::Player > > session_store
static const std::string & name()
Definition: playlists.h:54