Music Hub  ..
A session-wide music playback service
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros
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  */
19 
20 #include "service_implementation.h"
21 
24 #include "player_configuration.h"
25 #include "player_implementation.h"
26 
27 #include <boost/asio.hpp>
28 
29 #include <cstdint>
30 #include <map>
31 #include <memory>
32 #include <thread>
33 
34 #include "util/timeout.h"
35 #include "unity_screen_service.h"
36 #include <hybris/media/media_recorder_layer.h>
37 
38 namespace media = core::ubuntu::media;
39 
40 using namespace std;
41 
43 {
45  : resume_key(std::numeric_limits<std::uint32_t>::max()),
46  keep_alive(io_service),
47  disp_cookie(0),
48  call_monitor(new CallMonitor)
49  {
50  bus = std::shared_ptr<dbus::Bus>(new dbus::Bus(core::dbus::WellKnownBus::session));
51  bus->install_executor(dbus::asio::make_executor(bus, io_service));
52  worker = std::move(std::thread([this]()
53  {
54  bus->run();
55  }));
56 
57  // Connect the property change signal that will allow media-hub to take appropriate action
58  // when the battery level reaches critical
59  auto stub_service = dbus::Service::use_service(bus, "com.canonical.indicator.power");
60  indicator_power_session = stub_service->object_for_path(dbus::types::ObjectPath("/com/canonical/indicator/power/Battery"));
61  power_level = indicator_power_session->get_property<core::IndicatorPower::PowerLevel>();
62  is_warning = indicator_power_session->get_property<core::IndicatorPower::IsWarning>();
63 
64  // Obtain session with Unity.Screen so that we request state when doing recording
65  auto bus = std::shared_ptr<dbus::Bus>(new dbus::Bus(core::dbus::WellKnownBus::system));
66  bus->install_executor(dbus::asio::make_executor(bus));
67 
68  auto uscreen_stub_service = dbus::Service::use_service(bus, dbus::traits::Service<core::UScreen>::interface_name());
69  uscreen_session = uscreen_stub_service->object_for_path(dbus::types::ObjectPath("/com/canonical/Unity/Screen"));
70 
71  observer = android_media_recorder_observer_new();
72  android_media_recorder_observer_set_cb(observer, &Private::media_recording_started_callback, this);
73  }
74 
76  {
77  bus->stop();
78 
79  if (worker.joinable())
80  worker.join();
81  }
82 
83  void media_recording_started(bool started)
84  {
85  if (uscreen_session == nullptr)
86  return;
87 
88  if (started)
89  {
90  if (disp_cookie > 0)
91  return;
92 
93  auto result = uscreen_session->invoke_method_synchronously<core::UScreen::keepDisplayOn, int>();
94  if (result.is_error())
95  throw std::runtime_error(result.error().print());
96  disp_cookie = result.value();
97  }
98  else
99  {
100  if (disp_cookie != -1)
101  {
102  timeout(4000, true, [this](){
103  this->uscreen_session->invoke_method_synchronously<core::UScreen::removeDisplayOnRequest, void>(this->disp_cookie);
104  this->disp_cookie = -1;
105  });
106  }
107  }
108  }
109 
110  static void media_recording_started_callback(bool started, void *context)
111  {
112  if (context == nullptr)
113  return;
114 
115  auto p = static_cast<Private*>(context);
116  p->media_recording_started(started);
117  }
118 
119  // This holds the key of the multimedia role Player instance that was paused
120  // when the battery level reached 10% or 5%
122  std::thread worker;
123  dbus::Bus::Ptr bus;
124  boost::asio::io_service io_service;
125  boost::asio::io_service::work keep_alive;
126  std::shared_ptr<dbus::Object> indicator_power_session;
127  std::shared_ptr<core::dbus::Property<core::IndicatorPower::PowerLevel>> power_level;
128  std::shared_ptr<core::dbus::Property<core::IndicatorPower::IsWarning>> is_warning;
130  std::shared_ptr<dbus::Object> uscreen_session;
131  MediaRecorderObserver *observer;
132  std::unique_ptr<CallMonitor> call_monitor;
133  std::list<media::Player::PlayerKey> paused_sessions;
134 };
135 
137 {
138  cout << __PRETTY_FUNCTION__ << endl;
139 
140  d->power_level->changed().connect([this](const core::IndicatorPower::PowerLevel::ValueType &level)
141  {
142  // When the battery level hits 10% or 5%, pause all multimedia sessions.
143  // Playback will resume when the user clears the presented notification.
144  if (level == "low" || level == "very_low")
145  pause_all_multimedia_sessions();
146  });
147 
148  d->is_warning->changed().connect([this](const core::IndicatorPower::IsWarning::ValueType &notifying)
149  {
150  // If the low battery level notification is no longer being displayed,
151  // resume what the user was previously playing
152  if (!notifying)
153  resume_multimedia_session();
154  });
155 
156  d->call_monitor->on_change([this](CallMonitor::State state) {
157  switch (state) {
159  pause_all_multimedia_sessions();
160  break;
161  case CallMonitor::OnHook:
162  resume_paused_multimedia_sessions();
163  break;
164  }
165  });
166 }
167 
169 {
170 }
171 
172 std::shared_ptr<media::Player> media::ServiceImplementation::create_session(
173  const media::Player::Configuration& conf)
174 {
175  auto player = std::make_shared<media::PlayerImplementation>(
176  conf.identity, conf.bus, conf.session, shared_from_this(), conf.key);
177 
178  auto key = conf.key;
179  player->on_client_disconnected().connect([this, key]()
180  {
181  // Call remove_player_for_key asynchronously otherwise deadlock can occur
182  // if called within this dispatcher context.
183  // remove_player_for_key can destroy the player instance which in turn
184  // destroys the "on_client_disconnected" signal whose destructor will wait
185  // until all dispatches are done
186  d->io_service.post([this, key]()
187  {
189  });
190  });
191 
192  return player;
193 }
194 
196 {
197  if (not has_player_for_key(key))
198  {
199  cerr << "Could not find Player by key: " << key << endl;
200  return;
201  }
202 
203  auto current_player = player_for_key(key);
204 
205  // We immediately make the player known as new current player.
206  if (current_player->audio_stream_role() == media::Player::multimedia)
208 
209  enumerate_players([current_player, key](const media::Player::PlayerKey& other_key, const std::shared_ptr<media::Player>& other_player)
210  {
211  // Only pause a Player if all of the following criteria are met:
212  // 1) currently playing
213  // 2) not the same player as the one passed in my key
214  // 3) new Player has an audio stream role set to multimedia
215  // 4) has an audio stream role set to multimedia
216  if (other_player->playback_status() == Player::playing &&
217  other_key != key &&
218  current_player->audio_stream_role() == media::Player::multimedia &&
219  other_player->audio_stream_role() == media::Player::multimedia)
220  {
221  cout << "Pausing Player with key: " << other_key << endl;
222  other_player->pause();
223  }
224  });
225 }
226 
227 void media::ServiceImplementation::pause_all_multimedia_sessions()
228 {
229  enumerate_players([this](const media::Player::PlayerKey& key, const std::shared_ptr<media::Player>& player)
230  {
231  if (player->playback_status() == Player::playing
232  && player->audio_stream_role() == media::Player::multimedia)
233  {
234  d->paused_sessions.push_back(key);
235  player->pause();
236  }
237  });
238 }
239 
240 void media::ServiceImplementation::resume_paused_multimedia_sessions()
241 {
242  std::for_each(d->paused_sessions.begin(), d->paused_sessions.end(), [this](const media::Player::PlayerKey& key) {
243  player_for_key(key)->play();
244  });
245 
246  d->paused_sessions.clear();
247 }
248 
249 void media::ServiceImplementation::resume_multimedia_session()
250 {
251  if (not has_player_for_key(d->resume_key))
252  return;
253 
254  auto player = player_for_key(d->resume_key);
255 
256  if (player->playback_status() == Player::paused)
257  {
258  cout << "Resuming playback of Player with key: " << d->resume_key << endl;
259  player->play();
260  d->resume_key = std::numeric_limits<std::uint32_t>::max();
261  }
262 }
void set_current_player_for_key(const Player::PlayerKey &key)
bool has_player_for_key(const Player::PlayerKey &key) const
STL namespace.
void enumerate_players(const PlayerEnumerator &enumerator) const
std::shared_ptr< core::dbus::Property< core::IndicatorPower::PowerLevel > > power_level
static void media_recording_started_callback(bool started, void *context)
void pause_other_sessions(Player::PlayerKey key)
std::shared_ptr< Player > create_session(const Player::Configuration &)
void remove_player_for_key(const Player::PlayerKey &key)
std::shared_ptr< core::dbus::Property< core::IndicatorPower::IsWarning > > is_warning
std::shared_ptr< dbus::Object > uscreen_session
std::shared_ptr< Player > player_for_key(const Player::PlayerKey &key) const
std::shared_ptr< dbus::Object > indicator_power_session
std::list< media::Player::PlayerKey > paused_sessions
std::unique_ptr< CallMonitor > call_monitor