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
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
48namespace dbus = core::dbus;
49namespace media = core::ubuntu::media;
50
51using namespace std;
52
53namespace
54{
55core::Signal<void> the_empty_signal;
56}
57
59{
60 Private(media::ServiceSkeleton* impl, const ServiceSkeleton::Configuration& config)
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(
71 this,
72 std::placeholders::_1));
73 object->install_method_handler<mpris::Service::DetachSession>(
74 std::bind(
76 this,
77 std::placeholders::_1));
78 object->install_method_handler<mpris::Service::ReattachSession>(
79 std::bind(
81 this,
82 std::placeholders::_1));
83 object->install_method_handler<mpris::Service::DestroySession>(
84 std::bind(
86 this,
87 std::placeholders::_1));
88 object->install_method_handler<mpris::Service::CreateFixedSession>(
89 std::bind(
91 this,
92 std::placeholders::_1));
93 object->install_method_handler<mpris::Service::ResumeSession>(
94 std::bind(
96 this,
97 std::placeholders::_1));
98 object->install_method_handler<mpris::Service::PauseOtherSessions>(
99 std::bind(
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,
249 mpris::Service::Errors::ReattachingSession::name(),
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,
318 mpris::Service::Errors::DestroyingSession::name(),
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()}},
544 playlists{mpris::Playlists::Skeleton::Configuration{bus, object, mpris::Playlists::Skeleton::Configuration::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
863media::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
873std::shared_ptr<media::Player> media::ServiceSkeleton::create_session(const media::Player::Configuration& config)
874{
875 return d->configuration.impl->create_session(config);
876}
877
878void 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
883std::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
888void 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
893std::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
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
939const 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
946const 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(Player::PlayerKey key)
bool is_current_player(Player::PlayerKey key) const
virtual const core::Signal< void > & service_reconnected() const
std::shared_ptr< Player > reattach_session(const std::string &, const Player::Configuration &)
void destroy_session(const std::string &, const media::Player::Configuration &)
virtual const core::Signal< void > & service_disconnected() const
std::shared_ptr< Player > create_fixed_session(const std::string &name, const Player::Configuration &)
ServiceSkeleton(const Configuration &configuration)
void pause_other_sessions(Player::PlayerKey key)
std::shared_ptr< Player > create_session(const Player::Configuration &)
std::shared_ptr< Player > resume_session(Player::PlayerKey)
void detach_session(const std::string &, const Player::Configuration &)
#define MH_TRACE(...)
Definition: logger.h:121
#define MH_WARNING(...)
Definition: logger.h:127
#define MH_DEBUG(...)
Definition: logger.h:123
RequestContextResolver::Ptr make_platform_default_request_context_resolver(helper::ExternalServices &es)
core::dbus::Bus::Ptr the_session_bus()
std::function< std::string(const std::string &, const std::string &, const std::string &)> CoverArtResolver
void set_current_player(media::Player::PlayerKey key)
mpris::MediaPlayer2::Skeleton media_player
ServiceSkeleton::Configuration service_skel_config
static mpris::MediaPlayer2::Skeleton::Configuration::Defaults media_player_defaults()
bool is_current_player(media::Player::PlayerKey key)
Exported(const dbus::Bus::Ptr &bus, const media::CoverArtResolver &cover_art_resolver, media::ServiceSkeleton *impl, const ServiceSkeleton::Configuration &config)
static mpris::Player::Skeleton::Configuration::Defaults player_defaults()
std::map< std::string, media::Player::PlayerKey > named_player_map
std::map< media::Player::PlayerKey, std::tuple< std::string, bool, std::string > > player_owner_map
void handle_detach_session(const core::dbus::Message::Ptr &msg)
void handle_create_fixed_session(const core::dbus::Message::Ptr &msg)
ServiceSkeleton::Configuration configuration
std::tuple< std::string, media::Player::PlayerKey, std::string > create_session_info()
struct media::ServiceSkeleton::Private::Exported exported
std::map< std::string, media::Player::PlayerKey > uuid_player_map
media::ServiceSkeleton * impl
void handle_resume_session(const core::dbus::Message::Ptr &msg)
void handle_pause_other_sessions(const core::dbus::Message::Ptr &msg)
void handle_reattach_session(const core::dbus::Message::Ptr &msg)
void handle_destroy_session(const core::dbus::Message::Ptr &msg)
media::apparmor::ubuntu::RequestContextResolver::Ptr request_context_resolver
void handle_set_current_player(const core::dbus::Message::Ptr &msg)
Private(media::ServiceSkeleton *impl, const ServiceSkeleton::Configuration &config)
void handle_create_session(const core::dbus::Message::Ptr &msg)
boost::uuids::random_generator gen
Properties::SupportedMimeTypes::ValueType supported_mime_types
Properties::DesktopEntry::ValueType desktop_entry
static const std::string & name()
Definition: media_player2.h:38
static const char * from(core::ubuntu::media::Player::LoopStatus status)
Definition: player.h:65
static const char * from(core::ubuntu::media::Player::PlaybackStatus status)
Definition: player.h:89
static const std::vector< std::string > & the_empty_list_of_invalidated_properties()
Definition: player.h:197
std::map< std::string, core::dbus::types::Variant > Dictionary
Definition: player.h:139
static const std::string & name()
Definition: player.h:55
static const std::string & name()
Definition: playlists.h:54
static const std::string & name()
Definition: service.h:89
static const std::string & name()
Definition: service.h:41
static const std::string & name()
Definition: service.h:77
static const std::string & name()
Definition: service.h:53
static const std::string & name()
Definition: service.h:113
static const std::string & name()
Definition: service.h:65
static const std::string & name()
Definition: service.h:101