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 
45 #include <pulse/pulseaudio.h>
46 
47 #include "util/timeout.h"
48 
49 namespace media = core::ubuntu::media;
50 
51 using namespace std;
52 
54 {
55  Private(const ServiceImplementation::Configuration& configuration)
56  : configuration(configuration),
57  resume_key(std::numeric_limits<std::uint32_t>::max()),
58  battery_observer(media::power::make_platform_default_battery_observer(configuration.external_services)),
59  power_state_controller(media::power::make_platform_default_state_controller(configuration.external_services)),
60  display_state_lock(power_state_controller->display_state_lock()),
61  client_death_observer(media::platform_default_client_death_observer()),
62  recorder_observer(media::make_platform_default_recorder_observer()),
63  audio_output_observer(media::audio::make_platform_default_output_observer()),
64  request_context_resolver(media::apparmor::ubuntu::make_platform_default_request_context_resolver(configuration.external_services)),
65  request_authenticator(media::apparmor::ubuntu::make_platform_default_request_authenticator()),
66  audio_output_state(media::audio::OutputState::Speaker),
67  call_monitor(media::telephony::make_platform_default_call_monitor())
68  {
69  }
70 
71  media::ServiceImplementation::Configuration configuration;
72  // This holds the key of the multimedia role Player instance that was paused
73  // when the battery level reached 10% or 5%
75  media::power::BatteryObserver::Ptr battery_observer;
76  media::power::StateController::Ptr power_state_controller;
77  media::power::StateController::Lock<media::power::DisplayState>::Ptr display_state_lock;
80  media::audio::OutputObserver::Ptr audio_output_observer;
81  media::apparmor::ubuntu::RequestContextResolver::Ptr request_context_resolver;
82  media::apparmor::ubuntu::RequestAuthenticator::Ptr request_authenticator;
84 
85  media::telephony::CallMonitor::Ptr call_monitor;
86  std::list<media::Player::PlayerKey> paused_sessions;
87 };
88 
89 media::ServiceImplementation::ServiceImplementation(const Configuration& configuration)
90  : d(new Private(configuration))
91 {
92  d->battery_observer->level().changed().connect([this](const media::power::Level& level)
93  {
94  // When the battery level hits 10% or 5%, pause all multimedia sessions.
95  // Playback will resume when the user clears the presented notification.
96  switch (level)
97  {
98  case media::power::Level::low:
99  case media::power::Level::very_low:
100  pause_all_multimedia_sessions();
101  break;
102  default:
103  break;
104  }
105  });
106 
107  d->battery_observer->is_warning_active().changed().connect([this](bool active)
108  {
109  // If the low battery level notification is no longer being displayed,
110  // resume what the user was previously playing
111  if (!active)
112  resume_multimedia_session();
113  });
114 
115  d->audio_output_observer->external_output_state().changed().connect([this](audio::OutputState state)
116  {
117  switch (state)
118  {
119  case audio::OutputState::Earpiece:
120  std::cout << "AudioOutputObserver reports that output is now Headphones/Headset." << std::endl;
121  break;
122  case audio::OutputState::Speaker:
123  std::cout << "AudioOutputObserver reports that output is now Speaker." << std::endl;
124  pause_all_multimedia_sessions();
125  break;
126  case audio::OutputState::External:
127  std::cout << "AudioOutputObserver reports that output is now External." << std::endl;
128  break;
129  }
130  d->audio_output_state = state;
131  });
132 
133  d->call_monitor->on_call_state_changed().connect([this](media::telephony::CallMonitor::State state)
134  {
135  switch (state) {
136  case media::telephony::CallMonitor::State::OffHook:
137  std::cout << "Got call started signal, pausing all multimedia sessions" << std::endl;
138  pause_all_multimedia_sessions();
139  break;
140  case media::telephony::CallMonitor::State::OnHook:
141  std::cout << "Got call ended signal, resuming paused multimedia sessions" << std::endl;
142  resume_paused_multimedia_sessions(false);
143  break;
144  }
145  });
146 
147  d->recorder_observer->recording_state().changed().connect([this](RecordingState state)
148  {
149  if (state == media::RecordingState::started)
150  {
151  d->display_state_lock->request_acquire(media::power::DisplayState::on);
152  pause_all_multimedia_sessions();
153  }
154  else if (state == media::RecordingState::stopped)
155  {
156  d->display_state_lock->request_release(media::power::DisplayState::on);
157  }
158  });
159 }
160 
162 {
163 }
164 
165 std::shared_ptr<media::Player> media::ServiceImplementation::create_session(
166  const media::Player::Configuration& conf)
167 {
168  auto player = std::make_shared<media::PlayerImplementation<media::PlayerSkeleton>>(media::PlayerImplementation<media::PlayerSkeleton>::Configuration
169  {
170  media::PlayerSkeleton::Configuration
171  {
172  conf.bus,
173  conf.session,
174  d->request_context_resolver,
175  d->request_authenticator
176  },
177  conf.key,
178  d->client_death_observer,
179  d->power_state_controller
180  });
181 
182  auto key = conf.key;
183  player->on_client_disconnected().connect([this, key]()
184  {
185  // Call remove_player_for_key asynchronously otherwise deadlock can occur
186  // if called within this dispatcher context.
187  // remove_player_for_key can destroy the player instance which in turn
188  // destroys the "on_client_disconnected" signal whose destructor will wait
189  // until all dispatches are done
190  d->configuration.external_services.io_service.post([this, key]()
191  {
192  if (!d->configuration.player_store->has_player_for_key(key))
193  return;
194 
195  if (d->configuration.player_store->player_for_key(key)->lifetime() == Player::Lifetime::normal)
196  d->configuration.player_store->remove_player_for_key(key);
197  });
198  });
199 
200  return player;
201 }
202 
203 std::shared_ptr<media::Player> media::ServiceImplementation::create_fixed_session(const std::string&, const media::Player::Configuration&)
204 {
205  // no impl
206  return std::shared_ptr<media::Player>();
207 }
208 
210 {
211  // no impl
212  return std::shared_ptr<media::Player>();
213 }
214 
216 {
217  if (not d->configuration.player_store->has_player_for_key(key))
218  {
219  cerr << "Could not find Player by key: " << key << endl;
220  return;
221  }
222 
223  auto current_player = d->configuration.player_store->player_for_key(key);
224 
225  // We immediately make the player known as new current player.
226  if (current_player->audio_stream_role() == media::Player::multimedia)
227  d->configuration.player_store->set_current_player_for_key(key);
228 
229  d->configuration.player_store->enumerate_players([current_player, key](const media::Player::PlayerKey& other_key, const std::shared_ptr<media::Player>& other_player)
230  {
231  // Only pause a Player if all of the following criteria are met:
232  // 1) currently playing
233  // 2) not the same player as the one passed in my key
234  // 3) new Player has an audio stream role set to multimedia
235  // 4) has an audio stream role set to multimedia
236  if (other_player->playback_status() == Player::playing &&
237  other_key != key &&
238  current_player->audio_stream_role() == media::Player::multimedia &&
239  other_player->audio_stream_role() == media::Player::multimedia)
240  {
241  cout << "Pausing Player with key: " << other_key << endl;
242  other_player->pause();
243  }
244  });
245 }
246 
247 void media::ServiceImplementation::pause_all_multimedia_sessions()
248 {
249  d->configuration.player_store->enumerate_players([this](const media::Player::PlayerKey& key, const std::shared_ptr<media::Player>& player)
250  {
251  if (player->playback_status() == Player::playing
252  && player->audio_stream_role() == media::Player::multimedia)
253  {
254  d->paused_sessions.push_back(key);
255  std::cout << "Pausing Player with key: " << key << std::endl;
256  player->pause();
257  }
258  });
259 }
260 
261 void media::ServiceImplementation::resume_paused_multimedia_sessions(bool resume_video_sessions)
262 {
263  std::for_each(d->paused_sessions.begin(), d->paused_sessions.end(), [this, resume_video_sessions](const media::Player::PlayerKey& key) {
264  auto player = d->configuration.player_store->player_for_key(key);
265  // Only resume video playback if explicitly desired
266  if (resume_video_sessions || player->is_audio_source())
267  player->play();
268  else
269  std::cout << "Not auto-resuming video playback session." << std::endl;
270  });
271 
272  d->paused_sessions.clear();
273 }
274 
275 void media::ServiceImplementation::resume_multimedia_session()
276 {
277  if (not d->configuration.player_store->has_player_for_key(d->resume_key))
278  return;
279 
280  auto player = d->configuration.player_store->player_for_key(d->resume_key);
281 
282  if (player->playback_status() == Player::paused)
283  {
284  cout << "Resuming playback of Player with key: " << d->resume_key << endl;
285  player->play();
286  d->resume_key = std::numeric_limits<std::uint32_t>::max();
287  }
288 }
StateController::Ptr make_platform_default_state_controller(core::ubuntu::media::helper::ExternalServices &)
media::power::BatteryObserver::Ptr battery_observer
virtual Player::PlayerKey key() const
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)
media::telephony::CallMonitor::Ptr call_monitor
CallMonitor::Ptr make_platform_default_call_monitor()
void pause_other_sessions(Player::PlayerKey key)
std::shared_ptr< Player > create_session(const Player::Configuration &)
std::shared_ptr< Player > resume_session(Player::PlayerKey key)
media::power::StateController::Ptr power_state_controller
RecorderObserver::Ptr make_platform_default_recorder_observer()
media::apparmor::ubuntu::RequestAuthenticator::Ptr request_authenticator
OutputObserver::Ptr make_platform_default_output_observer()
std::shared_ptr< RecorderObserver > Ptr
Private(const ServiceImplementation::Configuration &configuration)
ClientDeathObserver::Ptr platform_default_client_death_observer()
core::ubuntu::media::power::BatteryObserver::Ptr make_platform_default_battery_observer(core::ubuntu::media::helper::ExternalServices &)
std::list< media::Player::PlayerKey > paused_sessions
media::audio::OutputObserver::Ptr audio_output_observer
std::shared_ptr< Player > create_fixed_session(const std::string &name, const Player::Configuration &)
media::power::StateController::Lock< media::power::DisplayState >::Ptr display_state_lock