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