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