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 <boost/uuid/uuid.hpp>
39 #include <boost/uuid/uuid_generators.hpp>
40 #include <boost/uuid/uuid_io.hpp>
41 
42 #include <map>
43 #include <regex>
44 #include <sstream>
45 
46 namespace dbus = core::dbus;
47 namespace media = core::ubuntu::media;
48 
49 using namespace std;
50 
51 namespace
52 {
53 core::Signal<void> the_empty_signal;
54 }
55 
57 {
58  Private(media::ServiceSkeleton* impl, const ServiceSkeleton::Configuration& config)
59  : request_context_resolver(media::apparmor::ubuntu::make_platform_default_request_context_resolver(config.external_services)),
60  impl(impl),
61  object(impl->access_service()->add_object_for_path(
62  dbus::traits::Service<media::Service>::object_path())),
63  configuration(config),
64  exported(impl->access_bus(), config.cover_art_resolver, impl)
65  {
66  object->install_method_handler<mpris::Service::CreateSession>(
67  std::bind(
68  &Private::handle_create_session,
69  this,
70  std::placeholders::_1));
71  object->install_method_handler<mpris::Service::DetachSession>(
72  std::bind(
73  &Private::handle_detach_session,
74  this,
75  std::placeholders::_1));
76  object->install_method_handler<mpris::Service::ReattachSession>(
77  std::bind(
78  &Private::handle_reattach_session,
79  this,
80  std::placeholders::_1));
81  object->install_method_handler<mpris::Service::DestroySession>(
82  std::bind(
83  &Private::handle_destroy_session,
84  this,
85  std::placeholders::_1));
86  object->install_method_handler<mpris::Service::CreateFixedSession>(
87  std::bind(
88  &Private::handle_create_fixed_session,
89  this,
90  std::placeholders::_1));
91  object->install_method_handler<mpris::Service::ResumeSession>(
92  std::bind(
93  &Private::handle_resume_session,
94  this,
95  std::placeholders::_1));
96  object->install_method_handler<mpris::Service::SetCurrentPlayer>(
97  std::bind(
98  &Private::handle_set_current_player,
99  this,
100  std::placeholders::_1));
101  object->install_method_handler<mpris::Service::PauseOtherSessions>(
102  std::bind(
103  &Private::handle_pause_other_sessions,
104  this,
105  std::placeholders::_1));
106  }
107 
108  std::tuple<std::string, media::Player::PlayerKey, std::string> create_session_info()
109  {
110  static unsigned int session_counter = 0;
111 
112  const unsigned int current_session = session_counter++;
113  boost::uuids::uuid uuid = gen();
114 
115  std::stringstream ss;
116  ss << "/core/ubuntu/media/Service/sessions/" << current_session;
117 
118  return std::make_tuple(ss.str(), media::Player::PlayerKey(current_session), to_string(uuid));
119  }
120 
121  void handle_create_session(const core::dbus::Message::Ptr& msg)
122  {
123  auto session_info = create_session_info();
124 
125  dbus::types::ObjectPath op{std::get<0>(session_info)};
126  media::Player::PlayerKey key{std::get<1>(session_info)};
127  std::string uuid{std::get<2>(session_info)};
128 
129  media::Player::Configuration config
130  {
131  key,
132  impl->access_bus(),
133  impl->access_service(),
134  impl->access_service()->add_object_for_path(op)
135  };
136 
137  cout << "Session created by request of: " << msg->sender()
138  << ", key: " << key << ", uuid: " << uuid
139  << ", path:" << op << std::endl;
140 
141  try
142  {
143  const std::shared_ptr<media::Player> player {impl->create_session(config)};
144  configuration.player_store->add_player_for_key(key, player);
145  uuid_player_map.emplace(std::make_pair(uuid, key));
146 
147  request_context_resolver->resolve_context_for_dbus_name_async(msg->sender(),
148  [this, key, msg](const media::apparmor::ubuntu::Context& context)
149  {
150  fprintf(stderr, "%s():%d -- app_name='%s', attached\n", __func__, __LINE__, context.str().c_str());
151  player_owner_map.emplace(std::make_pair(key, std::make_tuple(context.str(), true, msg->sender())));
152  });
153 
154  auto reply = dbus::Message::make_method_return(msg);
155  reply->writer() << std::make_tuple(op, uuid);
156 
157  impl->access_bus()->send(reply);
158  } catch(const std::runtime_error& e)
159  {
160  auto reply = dbus::Message::make_error(
161  msg,
163  e.what());
164  impl->access_bus()->send(reply);
165  }
166  }
167 
168  void handle_detach_session(const core::dbus::Message::Ptr& msg)
169  {
170  try
171  {
172  std::string uuid;
173  msg->reader() >> uuid;
174 
175  // Make sure we don't try to do a lookup if the map is empty
176  if (!uuid_player_map.empty())
177  {
178  const auto key = uuid_player_map.at(uuid);
179 
180  if (player_owner_map.count(key) != 0) {
181  auto info = player_owner_map.at(key);
182  // Check if session is attached(1) and that the detachment
183  // request comes from the same peer(2) that created the session.
184  if (std::get<1>(info) && (std::get<2>(info) == msg->sender())) { // Player is attached
185  std::get<1>(info) = false; // Detached
186  std::get<2>(info).clear(); // Clear registered sender/peer
187 
188  auto player = configuration.player_store->player_for_key(key);
189  player->lifetime().set(media::Player::Lifetime::resumable);
190  }
191  }
192  }
193 
194  auto reply = dbus::Message::make_method_return(msg);
195  impl->access_bus()->send(reply);
196 
197  } catch(const std::runtime_error& e)
198  {
199  auto reply = dbus::Message::make_error(
200  msg,
202  e.what());
203  impl->access_bus()->send(reply);
204  }
205  }
206 
207  void handle_reattach_session(const core::dbus::Message::Ptr& msg)
208  {
209  try
210  {
211  std::string uuid;
212  msg->reader() >> uuid;
213 
214  if (uuid_player_map.count(uuid) != 0)
215  {
216  const auto key = uuid_player_map.at(uuid);
217  if (not configuration.player_store->has_player_for_key(key))
218  {
219  auto reply = dbus::Message::make_error(
220  msg,
222  "Unable to locate player session");
223  impl->access_bus()->send(reply);
224  return;
225  }
226  std::stringstream ss;
227  ss << "/core/ubuntu/media/Service/sessions/" << key;
228  dbus::types::ObjectPath op{ss.str()};
229 
230  request_context_resolver->resolve_context_for_dbus_name_async(msg->sender(),
231  [this, msg, key, op](const media::apparmor::ubuntu::Context& context)
232  {
233  auto info = player_owner_map.at(key);
234  fprintf(stderr, "%s():%d -- reattach app_name='%s', info='%s', '%s'\n", __func__, __LINE__, context.str().c_str(), std::get<0>(info).c_str(), std::get<2>(info).c_str());
235  if (std::get<0>(info) == context.str()) {
236  std::get<1>(info) = true; // Set to Attached
237  std::get<2>(info) = msg->sender(); // Register new owner
238 
239  // Signal player reconnection
240  auto player = configuration.player_store->player_for_key(key);
241  player->reconnect();
242  // We only care to allow the MPRIS controls to apply to multimedia player (i.e. audio, video)
243  if (player->audio_stream_role() == media::Player::AudioStreamRole::multimedia)
244  {
245  std::cout << "Setting current_player" << std::endl;
246  exported.set_current_player(player);
247  }
248 
249  auto reply = dbus::Message::make_method_return(msg);
250  reply->writer() << op;
251 
252  impl->access_bus()->send(reply);
253  }
254  else {
255  auto reply = dbus::Message::make_error(
256  msg,
258  "Invalid permissions for the requested session");
259  impl->access_bus()->send(reply);
260  return;
261  }
262  });
263  }
264  else {
265  auto reply = dbus::Message::make_error(
266  msg,
268  "Invalid session");
269  impl->access_bus()->send(reply);
270  return;
271  }
272  } catch(const std::runtime_error& e)
273  {
274  auto reply = dbus::Message::make_error(
275  msg,
277  e.what());
278  impl->access_bus()->send(reply);
279  }
280  }
281 
282  void handle_destroy_session(const core::dbus::Message::Ptr& msg)
283  {
284  try
285  {
286  std::string uuid;
287  msg->reader() >> uuid;
288 
289  if (uuid_player_map.count(uuid) != 0) {
290  const auto key = uuid_player_map.at(uuid);
291  if (not configuration.player_store->has_player_for_key(key)) {
292  auto reply = dbus::Message::make_error(
293  msg,
295  "Unable to locate player session");
296  impl->access_bus()->send(reply);
297  return;
298  }
299 
300  // Remove control entries from the map, at this point
301  // the session is no longer usable.
302  uuid_player_map.erase(uuid);
303 
304  request_context_resolver->resolve_context_for_dbus_name_async(msg->sender(),
305  [this, msg, key](const media::apparmor::ubuntu::Context& context)
306  {
307  auto info = player_owner_map.at(key);
308  fprintf(stderr, "%s():%d -- Destroying app_name='%s', info='%s', '%s'\n", __func__, __LINE__, context.str().c_str(), std::get<0>(info).c_str(), std::get<2>(info).c_str());
309  if (std::get<0>(info) == context.str()) {
310  player_owner_map.erase(key);
311 
312  // Reset lifecycle to non-resumable on the now-abandoned session
313  auto player = configuration.player_store->player_for_key(key);
314 
315  // Delete player instance by abandonment
316  player->lifetime().set(media::Player::Lifetime::normal);
317  player->abandon();
318 
319  auto reply = dbus::Message::make_method_return(msg);
320  impl->access_bus()->send(reply);
321  }
322  else {
323  auto reply = dbus::Message::make_error(
324  msg,
326  "Invalid permissions for the requested session");
327  impl->access_bus()->send(reply);
328  return;
329  }
330  });
331  }
332  else {
333  auto reply = dbus::Message::make_error(
334  msg,
336  "Invalid session");
337  impl->access_bus()->send(reply);
338  return;
339  }
340  } catch(const std::runtime_error& e)
341  {
342  auto reply = dbus::Message::make_error(
343  msg,
345  e.what());
346  impl->access_bus()->send(reply);
347  }
348  }
349 
350  void handle_create_fixed_session(const core::dbus::Message::Ptr& msg)
351  {
352  try
353  {
354  std::string name;
355  msg->reader() >> name;
356 
357  if (named_player_map.count(name) == 0) {
358  // Create new session
359  auto session_info = create_session_info();
360 
361  dbus::types::ObjectPath op{std::get<0>(session_info)};
362  media::Player::PlayerKey key{std::get<1>(session_info)};
363 
364  media::Player::Configuration config
365  {
366  key,
367  impl->access_bus(),
368  impl->access_service(),
369  impl->access_service()->add_object_for_path(op)
370  };
371 
372  auto session = impl->create_session(config);
373  session->lifetime().set(media::Player::Lifetime::resumable);
374 
375  configuration.player_store->add_player_for_key(key, session);
376 
377  named_player_map.insert(std::make_pair(name, key));
378 
379  auto reply = dbus::Message::make_method_return(msg);
380  reply->writer() << op;
381 
382  impl->access_bus()->send(reply);
383  }
384  else {
385  // Resume previous session
386  const auto key = named_player_map.at(name);
387  if (not configuration.player_store->has_player_for_key(key)) {
388  auto reply = dbus::Message::make_error(
389  msg,
391  "Unable to locate player session");
392  impl->access_bus()->send(reply);
393  return;
394  }
395 
396  std::stringstream ss;
397  ss << "/core/ubuntu/media/Service/sessions/" << key;
398  dbus::types::ObjectPath op{ss.str()};
399 
400  auto reply = dbus::Message::make_method_return(msg);
401  reply->writer() << op;
402 
403  impl->access_bus()->send(reply);
404  }
405  } catch(const std::runtime_error& e)
406  {
407  auto reply = dbus::Message::make_error(
408  msg,
410  e.what());
411  impl->access_bus()->send(reply);
412  }
413  }
414 
415  void handle_resume_session(const core::dbus::Message::Ptr& msg)
416  {
417  try
418  {
419  Player::PlayerKey key;
420  msg->reader() >> key;
421 
422  if (not configuration.player_store->has_player_for_key(key)) {
423  auto reply = dbus::Message::make_error(
424  msg,
426  "Unable to locate player session");
427  impl->access_bus()->send(reply);
428  return;
429  }
430 
431  std::stringstream ss;
432  ss << "/core/ubuntu/media/Service/sessions/" << key;
433  dbus::types::ObjectPath op{ss.str()};
434 
435  auto reply = dbus::Message::make_method_return(msg);
436  reply->writer() << op;
437 
438  impl->access_bus()->send(reply);
439  } catch(const std::runtime_error& e)
440  {
441  auto reply = dbus::Message::make_error(
442  msg,
444  e.what());
445  impl->access_bus()->send(reply);
446  }
447  }
448 
449  void handle_set_current_player(const core::dbus::Message::Ptr& msg)
450  {
451  Player::PlayerKey key;
452  msg->reader() >> key;
453 
454  core::dbus::Message::Ptr reply;
455  if (not configuration.player_store->has_player_for_key(key))
456  {
457  std::cerr << __PRETTY_FUNCTION__ << " player key not found - " << key << std::endl;
458  reply = dbus::Message::make_error(
459  msg,
461  "Player key not found");
462  }
463  else
464  {
465  try {
466  impl->set_current_player(key);
467  reply = dbus::Message::make_method_return(msg);
468  }
469  catch (const std::out_of_range &e) {
470  std::cerr << "Failed to look up Player instance for key " << key
471  << ", no valid Player instance for that key value and cannot set current player."
472  << " This most likely means that media-hub-server has crashed and restarted."
473  << std::endl;
474  reply = dbus::Message::make_error(
475  msg,
477  "Player key not found");
478  }
479  }
480 
481  impl->access_bus()->send(reply);
482  }
483 
484  void handle_pause_other_sessions(const core::dbus::Message::Ptr& msg)
485  {
486  Player::PlayerKey key;
487  msg->reader() >> key;
488  core::dbus::Message::Ptr reply;
489  try {
490  impl->pause_other_sessions(key);
491  reply = dbus::Message::make_method_return(msg);
492  }
493  catch (const std::out_of_range &e) {
494  std::cerr << "Failed to look up Player instance for key " << key
495  << ", no valid Player instance for that key value and cannot pause other Players."
496  << " This most likely means that media-hub-server has crashed and restarted."
497  << std::endl;
498  reply = dbus::Message::make_error(
499  msg,
501  "Player key not found");
502  }
503 
504  impl->access_bus()->send(reply);
505  }
506 
507  media::apparmor::ubuntu::RequestContextResolver::Ptr request_context_resolver;
509  dbus::Object::Ptr object;
510 
511  // We remember all our creation time arguments.
512  ServiceSkeleton::Configuration configuration;
513  // We map named/fixed player instances to their respective keys.
514  std::map<std::string, media::Player::PlayerKey> named_player_map;
515  // We map UUIDs to their respective keys.
516  std::map<std::string, media::Player::PlayerKey> uuid_player_map;
517  // We keep a list of keys and their respective owners and states.
518  // value: (owner context, attached state, attached dbus name)
519  std::map<media::Player::PlayerKey, std::tuple<std::string, bool, std::string>> player_owner_map;
520 
521  boost::uuids::random_generator gen;
522 
523  // We expose the entire service as an MPRIS player.
524  struct Exported
525  {
527  {
529  // TODO(tvoss): These three elements really should be configurable.
530  defaults.identity = "core::media::Hub";
531  defaults.desktop_entry = "mediaplayer-app";
532  defaults.supported_mime_types = {"audio/mpeg3"};
533 
534  return defaults;
535  }
536 
538  {
540 
541  return defaults;
542  }
543 
544  explicit Exported(const dbus::Bus::Ptr& bus, const media::CoverArtResolver& cover_art_resolver,
546  : bus{bus},
547  /* Export MediaHub service interface on dbus */
548  service{dbus::Service::add_service(bus, "org.mpris.MediaPlayer2.MediaHub")},
549  object{service->add_object_for_path(dbus::types::ObjectPath{"/org/mpris/MediaPlayer2"})},
550  media_player{mpris::MediaPlayer2::Skeleton::Configuration{bus, object, media_player_defaults()}},
551  player{mpris::Player::Skeleton::Configuration{bus, object, player_defaults()}},
553  cover_art_resolver{cover_art_resolver},
554  impl{impl}
555  {
556  object->install_method_handler<core::dbus::interfaces::Properties::GetAll>([this](const core::dbus::Message::Ptr& msg)
557  {
558  // Extract the interface
559  std::string interface;
560  msg->reader() >> interface;
561  core::dbus::Message::Ptr reply = core::dbus::Message::make_method_return(msg);
562 
563  if (interface == mpris::Player::name())
564  reply->writer() << player.get_all_properties();
565  else if (interface == mpris::MediaPlayer2::name())
566  reply->writer() << media_player.get_all_properties();
567  else if (interface == mpris::Playlists::name())
568  reply->writer() << playlists.get_all_properties();
569 
570  Exported::bus->send(reply);
571  });
572 
573  // Setup method handlers for mpris::Player methods.
574  auto next = [this](const core::dbus::Message::Ptr& msg)
575  {
576  const auto sp = current_player.lock();
577 
578  if (is_multimedia_role())
579  sp->next();
580 
581  Exported::bus->send(core::dbus::Message::make_method_return(msg));
582  };
583  object->install_method_handler<mpris::Player::Next>(next);
584 
585  auto previous = [this](const core::dbus::Message::Ptr& msg)
586  {
587  const auto sp = current_player.lock();
588 
589  if (is_multimedia_role())
590  sp->previous();
591 
592  Exported::bus->send(core::dbus::Message::make_method_return(msg));
593  };
594  object->install_method_handler<mpris::Player::Previous>(previous);
595 
596  auto pause = [this](const core::dbus::Message::Ptr& msg)
597  {
598  const auto sp = current_player.lock();
599 
600  if (is_multimedia_role() and sp->can_pause())
601  sp->pause();
602 
603  Exported::bus->send(core::dbus::Message::make_method_return(msg));
604  };
605  object->install_method_handler<mpris::Player::Pause>(pause);
606 
607  auto stop = [this](const core::dbus::Message::Ptr& msg)
608  {
609  const auto sp = current_player.lock();
610 
611  if (is_multimedia_role())
612  sp->stop();
613 
614  Exported::bus->send(core::dbus::Message::make_method_return(msg));
615  };
616  object->install_method_handler<mpris::Player::Stop>(stop);
617 
618  auto play = [this, impl](const core::dbus::Message::Ptr& msg)
619  {
620  const auto sp = current_player.lock();
621 
622  if (is_multimedia_role() and sp->can_play())
623  {
624  // Make sure other player sessions that are already playing
625  // are paused before triggering new player (sp) to play
626  if (impl)
627  impl->pause_other_sessions(sp->key());
628 
629  sp->play();
630  }
631 
632  Exported::bus->send(core::dbus::Message::make_method_return(msg));
633  };
634  object->install_method_handler<mpris::Player::Play>(play);
635 
636  auto play_pause = [this, impl](const core::dbus::Message::Ptr& msg)
637  {
638  const auto sp = current_player.lock();
639 
640  if (is_multimedia_role())
641  {
642  if (sp->playback_status() == media::Player::PlaybackStatus::playing
643  and sp->can_pause())
644  sp->pause();
645  else if (sp->playback_status() != media::Player::PlaybackStatus::null
646  and sp->can_play())
647  {
648  // Make sure other player sessions that are already playing
649  // are paused before triggering new player (sp) to play
650  if (impl)
651  impl->pause_other_sessions(sp->key());
652 
653  sp->play();
654  }
655  }
656 
657  Exported::bus->send(core::dbus::Message::make_method_return(msg));
658  };
659  object->install_method_handler<mpris::Player::PlayPause>(play_pause);
660  }
661 
662  inline bool is_multimedia_role()
663  {
664  const auto sp = current_player.lock();
665 
666  return (sp ? sp->audio_stream_role() == media::Player::AudioStreamRole::multimedia : false);
667  }
668 
669  void set_current_player(const std::shared_ptr<media::Player>& cp)
670  {
671  std::cout << "*** " << __PRETTY_FUNCTION__ << std::endl;
672  // We will not keep the object alive.
673  current_player = cp;
674 
675  // And announce that we can be controlled again.
676  player.properties.can_control->set(true);
677 
678  // We wire up player state changes
679  connections.seeked_to = cp->seeked_to().connect([this](std::uint64_t position)
680  {
681  player.signals.seeked_to->emit(position);
682  });
683 
684  connections.duration_changed = cp->duration().changed().connect([this](std::uint64_t duration)
685  {
686  player.properties.duration->set(duration);
687  });
688 
689  connections.position_changed = cp->position().changed().connect([this](std::uint64_t position)
690  {
691  player.properties.position->set(position);
692  });
693 
694  connections.playback_status_changed = cp->playback_status().changed().connect(
696  {
697  player.properties.playback_status->set(mpris::Player::PlaybackStatus::from(status));
698  });
699 
700  connections.loop_status_changed = cp->loop_status().changed().connect(
702  {
703  player.properties.loop_status->set(mpris::Player::LoopStatus::from(status));
704  });
705 
706  connections.can_play_changed = cp->can_play().changed().connect(
707  [this](bool can_play)
708  {
709  player.properties.can_play->set(can_play);
710  });
711 
712  connections.can_pause_changed = cp->can_pause().changed().connect(
713  [this](bool can_pause)
714  {
715  player.properties.can_pause->set(can_pause);
716  });
717 
718  connections.can_go_previous_changed = cp->can_go_previous().changed().connect(
719  [this](bool can_go_previous)
720  {
721  player.properties.can_go_previous->set(can_go_previous);
722  });
723 
724  connections.can_go_next_changed = cp->can_go_next().changed().connect(
725  [this](bool can_go_next)
726  {
727  player.properties.can_go_next->set(can_go_next);
728  });
729 
730  // Sync property values between session and player mpris::Player instances
731  // TODO Getters from media::Player actually return values from a
732  // mpris::Player::Skeleton instance different from "player". Each of them use
733  // different DBus object paths, /core/ubuntu/media/Service/sessions/<n>
734  // and /org/mpris/MediaPlayer2 (this is the one enforced by the MPRIS spec).
735  // Discuss why this is needed with tvoss.
736  player.properties.duration->set(cp->duration().get());
737  player.properties.position->set(cp->position().get());
738  player.properties.playback_status->set(mpris::Player::PlaybackStatus::from(
739  cp->playback_status().get()));
740  player.properties.loop_status->set(mpris::Player::LoopStatus::from(
741  cp->loop_status().get()));
742  player.properties.can_play->set(cp->can_play().get());
743  player.properties.can_pause->set(cp->can_pause().get());
744  player.properties.can_go_previous->set(cp->can_go_previous().get());
745  player.properties.can_go_next->set(cp->can_go_next().get());
746 
747 #if 0
748  // TODO cover_art_resolver() is not implemented yet
749  connections.meta_data_changed = cp->meta_data_for_current_track().changed().connect(
750  [this](const core::ubuntu::media::Track::MetaData& md)
751  {
753 
754  bool has_title = md.count(xesam::Title::name) > 0;
755  bool has_album_name = md.count(xesam::Album::name) > 0;
756  bool has_artist_name = md.count(xesam::Artist::name) > 0;
757 
758  if (has_title)
759  dict[xesam::Title::name] = dbus::types::Variant::encode(md.get(xesam::Title::name));
760  if (has_album_name)
761  dict[xesam::Album::name] = dbus::types::Variant::encode(md.get(xesam::Album::name));
762  if (has_artist_name)
763  dict[xesam::Artist::name] = dbus::types::Variant::encode(md.get(xesam::Artist::name));
764 
765  dict[mpris::metadata::ArtUrl::name] = dbus::types::Variant::encode(
766  cover_art_resolver(
767  has_title ? md.get(xesam::Title::name) : "",
768  has_album_name ? md.get(xesam::Album::name) : "",
769  has_artist_name ? md.get(xesam::Artist::name) : ""));
770 
772  wrap[mpris::Player::Properties::Metadata::name()] = dbus::types::Variant::encode(dict);
773 
774  player.signals.properties_changed->emit(
775  std::make_tuple(
776  dbus::traits::Service<
777  mpris::Player::Properties::Metadata::Interface>
778  ::interface_name(),
779  wrap,
780  std::vector<std::string>()));
781  });
782 #endif
783  }
784 
786  {
787  std::cout << __PRETTY_FUNCTION__ << std::endl;
788  // And announce that we can no longer be controlled.
789  player.properties.can_control->set(false);
790  current_player.reset();
791  }
792 
793  dbus::Bus::Ptr bus;
794  dbus::Service::Ptr service;
795  dbus::Object::Ptr object;
796 
800 
801  // The CoverArtResolver used by the exported player.
803  // The actual player instance.
804  std::weak_ptr<media::Player> current_player;
805 
807 
808  // We track event connections.
809  struct
810  {
811  core::Connection seeked_to
812  {
813  the_empty_signal.connect([](){})
814  };
815  core::Connection duration_changed
816  {
817  the_empty_signal.connect([](){})
818  };
819  core::Connection position_changed
820  {
821  the_empty_signal.connect([](){})
822  };
823  core::Connection playback_status_changed
824  {
825  the_empty_signal.connect([](){})
826  };
827  core::Connection loop_status_changed
828  {
829  the_empty_signal.connect([](){})
830  };
831  core::Connection can_play_changed
832  {
833  the_empty_signal.connect([](){})
834  };
835  core::Connection can_pause_changed
836  {
837  the_empty_signal.connect([](){})
838  };
839  core::Connection can_go_previous_changed
840  {
841  the_empty_signal.connect([](){})
842  };
843  core::Connection can_go_next_changed
844  {
845  the_empty_signal.connect([](){})
846  };
847  core::Connection meta_data_changed
848  {
849  the_empty_signal.connect([](){})
850  };
851  } connections;
852  } exported;
853 };
854 
855 media::ServiceSkeleton::ServiceSkeleton(const Configuration& configuration)
856  : dbus::Skeleton<media::Service>(the_session_bus()),
857  d(new Private(this, configuration))
858 {
859 }
860 
862 {
863 }
864 
865 std::shared_ptr<media::Player> media::ServiceSkeleton::create_session(const media::Player::Configuration& config)
866 {
867  return d->configuration.impl->create_session(config);
868 }
869 
870 void media::ServiceSkeleton::detach_session(const std::string& uuid, const media::Player::Configuration& config)
871 {
872  return d->configuration.impl->detach_session(uuid, config);
873 }
874 
875 std::shared_ptr<media::Player> media::ServiceSkeleton::reattach_session(const std::string& uuid, const media::Player::Configuration& config)
876 {
877  return d->configuration.impl->reattach_session(uuid, config);
878 }
879 
880 void media::ServiceSkeleton::destroy_session(const std::string& uuid, const media::Player::Configuration& config)
881 {
882  return d->configuration.impl->destroy_session(uuid, config);
883 }
884 
885 std::shared_ptr<media::Player> media::ServiceSkeleton::create_fixed_session(const std::string& name, const media::Player::Configuration&config)
886 {
887  return d->configuration.impl->create_fixed_session(name, config);
888 }
889 
890 std::shared_ptr<media::Player> media::ServiceSkeleton::resume_session(media::Player::PlayerKey key)
891 {
892  return d->configuration.impl->resume_session(key);
893 }
894 
896 {
897  const std::shared_ptr<media::Player> player =
898  d->configuration.player_store->player_for_key(key);
899  // We only care to allow the MPRIS controls to apply to multimedia player (i.e. audio, video)
900  if (player->audio_stream_role() == media::Player::AudioStreamRole::multimedia)
901  d->exported.set_current_player(player);
902 }
903 
905 {
906  d->configuration.impl->pause_other_sessions(key);
907 }
908 
910 {
911  access_bus()->run();
912 }
913 
915 {
916  access_bus()->stop();
917 }
std::weak_ptr< media::Player > current_player
static const std::string & name()
Definition: service.h:113
void handle_reattach_session(const core::dbus::Message::Ptr &msg)
media::ServiceSkeleton * impl
std::map< std::string, media::Player::PlayerKey > uuid_player_map
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()
Exported(const dbus::Bus::Ptr &bus, const media::CoverArtResolver &cover_art_resolver, media::ServiceSkeleton *impl)
Properties::SupportedMimeTypes::ValueType supported_mime_types
STL namespace.
void handle_create_session(const core::dbus::Message::Ptr &msg)
std::map< media::Player::PlayerKey, std::tuple< std::string, bool, std::string > > player_owner_map
static const std::string & name()
Definition: media_player2.h:38
static const char * from(core::ubuntu::media::Player::PlaybackStatus status)
Definition: player.h:87
static const std::string & name()
Definition: service.h:77
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)
static const std::string & name()
Definition: service.h:53
std::map< std::string, core::dbus::types::Variant > Dictionary
Definition: player.h:137
static const std::string & name()
Definition: service.h:101
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)
void handle_destroy_session(const core::dbus::Message::Ptr &msg)
RequestContextResolver::Ptr make_platform_default_request_context_resolver(helper::ExternalServices &es)
ServiceSkeleton(const Configuration &configuration)
std::shared_ptr< Player > reattach_session(const std::string &, const Player::Configuration &)
void handle_set_current_player(const core::dbus::Message::Ptr &msg)
void pause_other_sessions(Player::PlayerKey key)
std::tuple< std::string, media::Player::PlayerKey, std::string > create_session_info()
std::function< std::string(const std::string &, const std::string &, const std::string &)> CoverArtResolver
ServiceSkeleton::Configuration configuration
void handle_detach_session(const core::dbus::Message::Ptr &msg)
static const std::string & name()
Definition: service.h:41
Properties::DesktopEntry::ValueType desktop_entry
core::dbus::Bus::Ptr the_session_bus()
static const std::string & name()
Definition: service.h:65
void set_current_player(Player::PlayerKey key)
void detach_session(const std::string &, const Player::Configuration &)
static constexpr const char * name
Definition: metadata.h:49
std::size_t count() const
Definition: track.h:57
media::apparmor::ubuntu::RequestContextResolver::Ptr request_context_resolver
void destroy_session(const std::string &, const media::Player::Configuration &)
std::map< std::string, media::Player::PlayerKey > named_player_map
std::shared_ptr< Player > create_fixed_session(const std::string &name, const Player::Configuration &)
void handle_pause_other_sessions(const core::dbus::Message::Ptr &msg)
static const std::string & name()
Definition: service.h:89
static const std::string & name()
Definition: playlists.h:54
std::shared_ptr< Player > create_session(const Player::Configuration &)
boost::uuids::random_generator gen