Music Hub ..
A session-wide music playback service
service_implementation.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 * Ricardo Mendoza <ricardo.mendoza@canonical.com>
19 *
20 * Note: Some of the PulseAudio code was adapted from telepathy-ofono
21 */
22
24
25#include "apparmor/ubuntu.h"
29#include "player_skeleton.h"
33#include "recorder_observer.h"
35
36#include "util/timeout.h"
38
39#include <boost/asio.hpp>
40
41#include <string>
42#include <cstdint>
43#include <cstring>
44#include <map>
45#include <memory>
46#include <thread>
47#include <utility>
48
49#include <pulse/pulseaudio.h>
50
51namespace media = core::ubuntu::media;
52
53using namespace std;
54
56{
57 // Create all of the appropriate observers and helper class instances to be
58 // passed to the PlayerImplementation
59 Private(const ServiceImplementation::Configuration& configuration)
61 resume_key(std::numeric_limits<std::uint32_t>::max()),
72 {
73 }
74
75 media::ServiceImplementation::Configuration configuration;
76 // This holds the key of the multimedia role Player instance that was paused
77 // when the battery level reached 10% or 5%
79 media::power::BatteryObserver::Ptr battery_observer;
80 media::power::StateController::Ptr power_state_controller;
81 media::power::StateController::Lock<media::power::DisplayState>::Ptr display_state_lock;
84 media::audio::OutputObserver::Ptr audio_output_observer;
85 media::apparmor::ubuntu::RequestContextResolver::Ptr request_context_resolver;
86 media::apparmor::ubuntu::RequestAuthenticator::Ptr request_authenticator;
88
89 media::telephony::CallMonitor::Ptr call_monitor;
90 // Holds a pair of a Player key denoting what player to resume playback, and a bool
91 // for if it should be resumed after a phone call is hung up
92 std::list<std::pair<media::Player::PlayerKey, bool>> paused_sessions;
93};
94
95media::ServiceImplementation::ServiceImplementation(const Configuration& configuration)
96 : d(new Private(configuration))
97{
98 d->battery_observer->level().changed().connect([this](const media::power::Level& level)
99 {
100 const bool resume_play_after_phonecall = false;
101 // When the battery level hits 10% or 5%, pause all multimedia sessions.
102 // Playback will resume when the user clears the presented notification.
103 switch (level)
104 {
105 case media::power::Level::low:
106 case media::power::Level::very_low:
107 // Whatever player session is currently playing, make sure it is NOT resumed after
108 // a phonecall is hung up
109 pause_all_multimedia_sessions(resume_play_after_phonecall);
110 break;
111 default:
112 break;
113 }
114 });
115
116 d->battery_observer->is_warning_active().changed().connect([this](bool active)
117 {
118 // If the low battery level notification is no longer being displayed,
119 // resume what the user was previously playing
120 if (!active)
121 resume_multimedia_session();
122 });
123
124 d->audio_output_observer->external_output_state().changed().connect([this](audio::OutputState state)
125 {
126 const bool resume_play_after_phonecall = false;
127 switch (state)
128 {
129 case audio::OutputState::Earpiece:
130 MH_INFO("AudioOutputObserver reports that output is now Headphones/Headset.");
131 break;
132 case audio::OutputState::Speaker:
133 MH_INFO("AudioOutputObserver reports that output is now Speaker.");
134 // Whatever player session is currently playing, make sure it is NOT resumed after
135 // a phonecall is hung up
136 pause_all_multimedia_sessions(resume_play_after_phonecall);
137 break;
138 case audio::OutputState::External:
139 MH_INFO("AudioOutputObserver reports that output is now External.");
140 break;
141 }
142 d->audio_output_state = state;
143 });
144
145 d->call_monitor->on_call_state_changed().connect([this](media::telephony::CallMonitor::State state)
146 {
147 const bool resume_play_after_phonecall = true;
148 switch (state) {
149 case media::telephony::CallMonitor::State::OffHook:
150 MH_INFO("Got call started signal, pausing all multimedia sessions");
151 // Whatever player session is currently playing, make sure it gets resumed after
152 // a phonecall is hung up
153 pause_all_multimedia_sessions(resume_play_after_phonecall);
154 break;
155 case media::telephony::CallMonitor::State::OnHook:
156 MH_INFO("Got call ended signal, resuming paused multimedia sessions");
157 resume_paused_multimedia_sessions(false);
158 break;
159 }
160 });
161
162 d->recorder_observer->recording_state().changed().connect([this](RecordingState state)
163 {
164 if (state == media::RecordingState::started)
165 {
166 d->display_state_lock->request_acquire(media::power::DisplayState::on);
167 // Whatever player session is currently playing, make sure it is NOT resumed after
168 // a phonecall is hung up
169 const bool resume_play_after_phonecall = false;
170 pause_all_multimedia_sessions(resume_play_after_phonecall);
171 }
172 else if (state == media::RecordingState::stopped)
173 {
174 d->display_state_lock->request_release(media::power::DisplayState::on);
175 }
176 });
177}
178
180{
181}
182
183std::shared_ptr<media::Player> media::ServiceImplementation::create_session(
184 const media::Player::Configuration& conf)
185{
186 // Create a new Player
187 auto player = std::make_shared<media::PlayerImplementation<media::PlayerSkeleton>>
189 {
190 // Derive a PlayerSkeleton-specific Configuration based on Player::Configuration
191 media::PlayerSkeleton::Configuration
192 {
193 conf.bus,
194 conf.service,
195 conf.session,
196 conf.player_service,
197 d->request_context_resolver,
198 d->request_authenticator
199 },
200 conf.key,
201 d->client_death_observer,
202 d->power_state_controller
203 });
204
205 auto key = conf.key;
206 // *Note: on_client_disconnected() is called from a Binder thread context
207 player->on_client_disconnected().connect([this, key]()
208 {
209 // Call remove_player_for_key asynchronously otherwise deadlock can occur
210 // if called within this dispatcher context.
211 // remove_player_for_key can destroy the player instance which in turn
212 // destroys the "on_client_disconnected" signal whose destructor will wait
213 // until all dispatches are done
214 d->configuration.external_services.io_context.post([this, key]()
215 {
216 if (!d->configuration.player_store->has_player_for_key(key))
217 return;
218
219 try {
220 if (d->configuration.player_store->player_for_key(key)->lifetime() == Player::Lifetime::normal)
221 d->configuration.player_store->remove_player_for_key(key);
222 }
223 catch (const std::out_of_range &e) {
224 MH_WARNING("Failed to look up Player instance for key %d"
225 ", no valid Player instance for that key value. Removal of Player from Player store"
226 " might not have completed. This most likely means that media-hub-server has"
227 " crashed and restarted.", key);
228 return;
229 }
230 });
231 });
232
233 return player;
234}
235
236void media::ServiceImplementation::detach_session(const std::string&, const media::Player::Configuration&)
237{
238 // no impl
239}
240
241std::shared_ptr<media::Player> media::ServiceImplementation::reattach_session(const std::string&, const media::Player::Configuration&)
242{
243 // no impl
244 return std::shared_ptr<media::Player>();
245}
246
247void media::ServiceImplementation::destroy_session(const std::string&, const media::Player::Configuration&)
248{
249 // no impl
250}
251
252std::shared_ptr<media::Player> media::ServiceImplementation::create_fixed_session(const std::string&, const media::Player::Configuration&)
253{
254 // no impl
255 return std::shared_ptr<media::Player>();
256}
257
259{
260 // no impl
261 return std::shared_ptr<media::Player>();
262}
263
265{
266 MH_TRACE("");
267
268 if (not d->configuration.player_store->has_player_for_key(key))
269 {
270 MH_WARNING("Could not find Player by key: %d", key);
271 return;
272 }
273
274 const std::shared_ptr<media::Player> current_player =
275 d->configuration.player_store->player_for_key(key);
276
277 d->configuration.player_store->enumerate_players([current_player, key]
278 (const media::Player::PlayerKey& other_key,
279 const std::shared_ptr<media::Player>& other_player)
280 {
281 // Only pause a Player if all of the following criteria are met:
282 // 1) currently playing
283 // 2) not the same player as the one passed in my key
284 // 3) new Player has an audio stream role set to multimedia
285 // 4) has an audio stream role set to multimedia
286 if (other_player->playback_status() == Player::playing &&
287 other_key != key &&
288 current_player->audio_stream_role() == media::Player::multimedia &&
289 other_player->audio_stream_role() == media::Player::multimedia)
290 {
291 MH_INFO("Pausing Player with key: %d", other_key);
292 other_player->pause();
293 }
294 });
295}
296
297void media::ServiceImplementation::pause_all_multimedia_sessions(bool resume_play_after_phonecall)
298{
299 d->configuration.player_store->enumerate_players([this, resume_play_after_phonecall](const media::Player::PlayerKey& key, const std::shared_ptr<media::Player>& player)
300 {
301 if (player->playback_status() == Player::playing
302 && player->audio_stream_role() == media::Player::multimedia)
303 {
304 auto paused_player_pair = std::make_pair(key, resume_play_after_phonecall);
305 d->paused_sessions.push_back(paused_player_pair);
306 MH_INFO("Pausing Player with key: %d, resuming after phone call? %s", key,
307 (resume_play_after_phonecall ? "yes" : "no"));
308 player->pause();
309 }
310 });
311}
312
313void media::ServiceImplementation::resume_paused_multimedia_sessions(bool resume_video_sessions)
314{
315 std::for_each(d->paused_sessions.begin(), d->paused_sessions.end(),
316 [this, resume_video_sessions](const std::pair<media::Player::PlayerKey, bool> &paused_player_pair) {
317 const media::Player::PlayerKey key = paused_player_pair.first;
318 const bool resume_play_after_phonecall = paused_player_pair.second;
319 std::shared_ptr<media::Player> player;
320 try {
321 player = d->configuration.player_store->player_for_key(key);
322 }
323 catch (const std::out_of_range &e) {
324 MH_WARNING("Failed to look up Player instance for key %d"
325 ", no valid Player instance for that key value and cannot automatically resume"
326 " paused players. This most likely means that media-hub-server has crashed and"
327 " restarted.", key);
328 return;
329 }
330 // Only resume video playback if explicitly desired
331 if ((resume_video_sessions || player->is_audio_source()) && resume_play_after_phonecall)
332 player->play();
333 else
334 MH_INFO("Not auto-resuming video player session or other type of player session.");
335 });
336
337 d->paused_sessions.clear();
338}
339
340void media::ServiceImplementation::resume_multimedia_session()
341{
342 if (not d->configuration.player_store->has_player_for_key(d->resume_key))
343 return;
344
345 std::shared_ptr<media::Player> player;
346 try {
347 player = d->configuration.player_store->player_for_key(d->resume_key);
348 }
349 catch (const std::out_of_range &e) {
350 MH_WARNING("Failed to look up Player instance for key %d"
351 ", no valid Player instance for that key value and cannot automatically resume"
352 " paused Player. This most likely means that media-hub-server has crashed and"
353 " restarted.", d->resume_key);
354 return;
355 }
356
357 if (player->playback_status() == Player::paused)
358 {
359 MH_INFO("Resuming playback of Player with key: %d", d->resume_key);
360 player->play();
361 d->resume_key = std::numeric_limits<std::uint32_t>::max();
362 }
363}
364
365const core::Signal<void>& media::ServiceImplementation::service_disconnected() const
366{
367 throw std::runtime_error("This signal is only accessible from the ServiceStub");
368 static const core::Signal<void> s;
369 return s;
370}
371
372const core::Signal<void>& media::ServiceImplementation::service_reconnected() const
373{
374 throw std::runtime_error("This signal is only accessible from the ServiceStub");
375 static const core::Signal<void> s;
376 return s;
377}
virtual Player::PlayerKey key() const
void detach_session(const std::string &, const Player::Configuration &)
Detaches a UUID-identified session for later resuming.
const core::Signal< void > & service_disconnected() const
Signals when the media-hub server disappears from the bus.
std::shared_ptr< Player > create_session(const Player::Configuration &)
Creates a session with the media-hub service.
std::shared_ptr< Player > resume_session(Player::PlayerKey key)
Resumes a fixed-name session directly by player key.
ServiceImplementation(const Configuration &configuration)
void pause_other_sessions(Player::PlayerKey key)
Pauses sessions other than the supplied one.
std::shared_ptr< Player > create_fixed_session(const std::string &name, const Player::Configuration &)
Creates a fixed-named session with the media-hub service.
const core::Signal< void > & service_reconnected() const
Signals when the media-hub server reappears from the bus.
void destroy_session(const std::string &, const Player::Configuration &)
Asks the service to destroy a session. The session is destroyed when the client exits.
std::shared_ptr< Player > reattach_session(const std::string &, const Player::Configuration &)
Reattaches to a UUID-identified session that is in detached state.
#define MH_TRACE(...)
Definition: logger.h:121
#define MH_INFO(...)
Definition: logger.h:125
#define MH_WARNING(...)
Definition: logger.h:127
RequestAuthenticator::Ptr make_platform_default_request_authenticator()
RequestContextResolver::Ptr make_platform_default_request_context_resolver(helper::ExternalServices &es)
OutputObserver::Ptr make_platform_default_output_observer()
core::ubuntu::media::power::BatteryObserver::Ptr make_platform_default_battery_observer(core::ubuntu::media::helper::ExternalServices &)
StateController::Ptr make_platform_default_state_controller(core::ubuntu::media::helper::ExternalServices &)
CallMonitor::Ptr make_platform_default_call_monitor()
RecorderObserver::Ptr make_platform_default_recorder_observer()
ClientDeathObserver::Ptr platform_default_client_death_observer()
std::shared_ptr< ClientDeathObserver > Ptr
std::shared_ptr< RecorderObserver > Ptr
media::audio::OutputObserver::Ptr audio_output_observer
std::list< std::pair< media::Player::PlayerKey, bool > > paused_sessions
media::power::BatteryObserver::Ptr battery_observer
media::telephony::CallMonitor::Ptr call_monitor
media::power::StateController::Lock< media::power::DisplayState >::Ptr display_state_lock
media::apparmor::ubuntu::RequestAuthenticator::Ptr request_authenticator
media::power::StateController::Ptr power_state_controller
media::ServiceImplementation::Configuration configuration
Private(const ServiceImplementation::Configuration &configuration)
media::RecorderObserver::Ptr recorder_observer
media::apparmor::ubuntu::RequestContextResolver::Ptr request_context_resolver
media::ClientDeathObserver::Ptr client_death_observer