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