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 
23 #include "service_implementation.h"
24 
25 #include "apparmor/ubuntu.h"
26 #include "audio/output_observer.h"
27 #include "client_death_observer.h"
28 #include "player_configuration.h"
29 #include "player_skeleton.h"
30 #include "player_implementation.h"
31 #include "power/battery_observer.h"
32 #include "power/state_controller.h"
33 #include "recorder_observer.h"
34 #include "telephony/call_monitor.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 
51 namespace media = core::ubuntu::media;
52 
53 using 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)
60  : configuration(configuration),
61  resume_key(std::numeric_limits<std::uint32_t>::max()),
62  battery_observer(media::power::make_platform_default_battery_observer(configuration.external_services)),
63  power_state_controller(media::power::make_platform_default_state_controller(configuration.external_services)),
64  display_state_lock(power_state_controller->display_state_lock()),
65  client_death_observer(media::platform_default_client_death_observer()),
66  recorder_observer(media::make_platform_default_recorder_observer()),
67  audio_output_observer(media::audio::make_platform_default_output_observer()),
68  request_context_resolver(media::apparmor::ubuntu::make_platform_default_request_context_resolver(configuration.external_services)),
69  request_authenticator(media::apparmor::ubuntu::make_platform_default_request_authenticator()),
70  audio_output_state(media::audio::OutputState::Speaker),
71  call_monitor(media::telephony::make_platform_default_call_monitor())
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 
95 media::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 
183 std::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_service.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 
236 void media::ServiceImplementation::detach_session(const std::string&, const media::Player::Configuration&)
237 {
238  // no impl
239 }
240 
241 std::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 
247 void media::ServiceImplementation::destroy_session(const std::string&, const media::Player::Configuration&)
248 {
249  // no impl
250 }
251 
252 std::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 
297 void 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 
313 void 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 
340 void 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 
365 const 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 
372 const 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 }
StateController::Ptr make_platform_default_state_controller(core::ubuntu::media::helper::ExternalServices &)
const core::Signal< void > & service_reconnected() const
Signals when the media-hub server reappears from the bus.
media::power::BatteryObserver::Ptr battery_observer
std::list< std::pair< media::Player::PlayerKey, bool > > paused_sessions
#define MH_INFO(...)
Definition: logger.h:125
media::ServiceImplementation::Configuration configuration
RequestAuthenticator::Ptr make_platform_default_request_authenticator()
STL namespace.
media::apparmor::ubuntu::RequestContextResolver::Ptr request_context_resolver
media::ClientDeathObserver::Ptr client_death_observer
media::RecorderObserver::Ptr recorder_observer
std::shared_ptr< ClientDeathObserver > Ptr
ServiceImplementation(const Configuration &configuration)
RequestContextResolver::Ptr make_platform_default_request_context_resolver(helper::ExternalServices &es)
#define MH_WARNING(...)
Definition: logger.h:127
media::telephony::CallMonitor::Ptr call_monitor
CallMonitor::Ptr make_platform_default_call_monitor()
void pause_other_sessions(Player::PlayerKey key)
Pauses sessions other than the supplied one.
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.
const core::Signal< void > & service_disconnected() const
Signals when the media-hub server disappears from the bus.
media::power::StateController::Ptr power_state_controller
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
RecorderObserver::Ptr make_platform_default_recorder_observer()
media::apparmor::ubuntu::RequestAuthenticator::Ptr request_authenticator
OutputObserver::Ptr make_platform_default_output_observer()
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< RecorderObserver > Ptr
Private(const ServiceImplementation::Configuration &configuration)
virtual Player::PlayerKey key() const
ClientDeathObserver::Ptr platform_default_client_death_observer()
void detach_session(const std::string &, const Player::Configuration &)
Detaches a UUID-identified session for later resuming.
core::ubuntu::media::power::BatteryObserver::Ptr make_platform_default_battery_observer(core::ubuntu::media::helper::ExternalServices &)
media::audio::OutputObserver::Ptr audio_output_observer
std::shared_ptr< Player > create_fixed_session(const std::string &name, const Player::Configuration &)
Creates a fixed-named session with the media-hub service.
media::power::StateController::Lock< media::power::DisplayState >::Ptr display_state_lock