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