Music Hub  ..
A session-wide music playback service
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros
player_implementation.cpp
Go to the documentation of this file.
1 /*
2  *
3  * This program is free software: you can redistribute it and/or modify it
4  * under the terms of the GNU Lesser General Public License version 3,
5  * as published by the Free Software Foundation.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10  * GNU Lesser General Public License for more details.
11  *
12  * You should have received a copy of the GNU Lesser General Public License
13  * along with this program. If not, see <http://www.gnu.org/licenses/>.
14  *
15  * Authored by: Thomas Voß <thomas.voss@canonical.com>
16  * Jim Hodapp <jim.hodapp@canonical.com>
17  */
18 
19 #include "player_implementation.h"
20 #include "util/timeout.h"
21 
22 #include <unistd.h>
23 
24 #include "engine.h"
26 
27 #include "powerd_service.h"
28 #include "unity_screen_service.h"
29 #include "gstreamer/engine.h"
30 
31 #include <exception>
32 #include <iostream>
33 #include <mutex>
34 
35 #define UNUSED __attribute__((unused))
36 
37 namespace media = core::ubuntu::media;
38 namespace dbus = core::dbus;
39 
40 using namespace std;
41 
43 {
44  enum class wakelock_clear_t
45  {
46  WAKELOCK_CLEAR_INACTIVE,
47  WAKELOCK_CLEAR_DISPLAY,
48  WAKELOCK_CLEAR_SYSTEM,
49  WAKELOCK_CLEAR_INVALID
50  };
51 
52  Private(PlayerImplementation* parent,
53  const dbus::types::ObjectPath& session_path,
54  const std::shared_ptr<media::Service>& service,
55  PlayerImplementation::PlayerKey key)
56  : parent(parent),
57  service(service),
58  engine(std::make_shared<gstreamer::Engine>()),
59  session_path(session_path),
60  track_list(
61  new media::TrackListImplementation(
62  session_path.as_string() + "/TrackList",
63  engine->meta_data_extractor())),
64  sys_lock_name("media-hub-music-playback"),
65  disp_cookie(-1),
66  system_wakelock_count(0),
67  display_wakelock_count(0),
68  previous_state(Engine::State::stopped),
69  key(key)
70  {
71  auto bus = std::shared_ptr<dbus::Bus>(new dbus::Bus(core::dbus::WellKnownBus::system));
72  bus->install_executor(dbus::asio::make_executor(bus));
73 
74  auto stub_service = dbus::Service::use_service(bus, dbus::traits::Service<core::Powerd>::interface_name());
75  powerd_session = stub_service->object_for_path(dbus::types::ObjectPath("/com/canonical/powerd"));
76 
77  auto uscreen_stub_service = dbus::Service::use_service(bus, dbus::traits::Service<core::UScreen>::interface_name());
78  uscreen_session = uscreen_stub_service->object_for_path(dbus::types::ObjectPath("/com/canonical/Unity/Screen"));
79 
80  /*
81  * Wakelock state logic:
82  *
83  * PLAYING->READY: delay 4 seconds and try to clear current wakelock type
84  * PLAYING->PAUSED or PLAYING->STOPPED: delay 4 seconds and try to clear current wakelock type
85  * READY->PAUSED: request a new wakelock (system or display)
86  * PLAYING->PAUSED: delay 4 seconds and try to clear current wakelock type
87  */
88  engine->state().changed().connect(
89  [parent, this](const Engine::State& state)
90  {
91  switch(state)
92  {
93  case Engine::State::ready:
94  {
95  parent->playback_status().set(media::Player::ready);
96  if (previous_state == Engine::State::playing)
97  {
98  wakelock_timeout.reset(new timeout(4000, true, std::bind(&Private::clear_wakelock,
99  this, std::placeholders::_1), current_wakelock_type()));
100  }
101  break;
102  }
103  case Engine::State::playing:
104  {
105  parent->playback_status().set(media::Player::playing);
106  if (previous_state == Engine::State::stopped || previous_state == Engine::State::paused)
107  {
108  request_power_state();
109  }
110  break;
111  }
112  case Engine::State::stopped:
113  {
114  parent->playback_status().set(media::Player::stopped);
115  break;
116  }
117  case Engine::State::paused:
118  {
119  parent->playback_status().set(media::Player::paused);
120  if (previous_state == Engine::State::ready)
121  {
122  request_power_state();
123  }
124  else if (previous_state == Engine::State::playing)
125  {
126  wakelock_timeout.reset(new timeout(4000, true, std::bind(&Private::clear_wakelock,
127  this, std::placeholders::_1), current_wakelock_type()));
128  }
129  break;
130  }
131  default:
132  break;
133  };
134 
135  // Keep track of the previous Engine playback state:
136  previous_state = state;
137  });
138 
139  }
140 
142  {
143  // Make sure that we don't hold on to the wakelocks if media-hub-server
144  // ever gets restarted manually or automatically
145  clear_wakelocks();
146  }
147 
149  {
150  try
151  {
152  if (parent->is_video_source())
153  {
154  if (display_wakelock_count == 0)
155  {
156  auto result = uscreen_session->invoke_method_synchronously<core::UScreen::keepDisplayOn, int>();
157  if (result.is_error())
158  throw std::runtime_error(result.error().print());
159  disp_cookie = result.value();
160  cout << "Requested new display wakelock" << endl;
161  }
162 
163  {
164  // Keep track of how many display wakelocks have been requested
165  std::lock_guard<std::mutex> lock(wakelock_mutex);
166  ++display_wakelock_count;
167  }
168  }
169  else
170  {
171  if (system_wakelock_count == 0)
172  {
173  auto result = powerd_session->invoke_method_synchronously<core::Powerd::requestSysState, std::string>(sys_lock_name, static_cast<int>(1));
174  if (result.is_error())
175  throw std::runtime_error(result.error().print());
176  sys_cookie = result.value();
177  cout << "Requested new system wakelock" << endl;
178  }
179 
180  {
181  // Keep track of how many system wakelocks have been requested
182  std::lock_guard<std::mutex> lock(wakelock_mutex);
183  ++system_wakelock_count;
184  }
185  }
186  }
187  catch(const std::exception& e)
188  {
189  std::cerr << "Warning: failed to request power state: ";
190  std::cerr << e.what() << std::endl;
191  }
192  }
193 
194  void clear_wakelock(const wakelock_clear_t &wakelock)
195  {
196  cout << __PRETTY_FUNCTION__ << endl;
197  try
198  {
199  switch (wakelock)
200  {
201  case wakelock_clear_t::WAKELOCK_CLEAR_INACTIVE:
202  break;
203  case wakelock_clear_t::WAKELOCK_CLEAR_SYSTEM:
204  {
205  std::lock_guard<std::mutex> lock(wakelock_mutex);
206  --system_wakelock_count;
207  }
208  // Only actually clear the system wakelock once the count reaches zero
209  if (system_wakelock_count == 0)
210  {
211  cout << "Clearing system wakelock" << endl;
212  powerd_session->invoke_method_synchronously<core::Powerd::clearSysState, void>(sys_cookie);
213  sys_cookie.clear();
214  }
215  break;
216  case wakelock_clear_t::WAKELOCK_CLEAR_DISPLAY:
217  {
218  std::lock_guard<std::mutex> lock(wakelock_mutex);
219  --display_wakelock_count;
220  }
221  // Only actually clear the display wakelock once the count reaches zero
222  if (display_wakelock_count == 0)
223  {
224  cout << "Clearing display wakelock" << endl;
225  uscreen_session->invoke_method_synchronously<core::UScreen::removeDisplayOnRequest, void>(disp_cookie);
226  disp_cookie = -1;
227  }
228  break;
229  case wakelock_clear_t::WAKELOCK_CLEAR_INVALID:
230  default:
231  cerr << "Can't clear invalid wakelock type" << endl;
232  }
233  }
234  catch(const std::exception& e)
235  {
236  std::cerr << "Warning: failed to clear power state: ";
237  std::cerr << e.what() << std::endl;
238  }
239  }
240 
242  {
243  return (parent->is_video_source()) ?
244  wakelock_clear_t::WAKELOCK_CLEAR_DISPLAY : wakelock_clear_t::WAKELOCK_CLEAR_SYSTEM;
245  }
246 
248  {
249  // Clear both types of wakelocks (display and system)
250  if (system_wakelock_count > 0)
251  {
252  {
253  std::lock_guard<std::mutex> lock(wakelock_mutex);
254  system_wakelock_count = 1;
255  }
256  clear_wakelock(wakelock_clear_t::WAKELOCK_CLEAR_SYSTEM);
257  }
258  if (display_wakelock_count > 0)
259  {
260  {
261  std::lock_guard<std::mutex> lock(wakelock_mutex);
262  display_wakelock_count = 1;
263  }
264  clear_wakelock(wakelock_clear_t::WAKELOCK_CLEAR_DISPLAY);
265  }
266  }
267 
268  PlayerImplementation* parent;
269  std::shared_ptr<Service> service;
270  std::shared_ptr<Engine> engine;
271  dbus::types::ObjectPath session_path;
272  std::shared_ptr<TrackListImplementation> track_list;
273  std::shared_ptr<dbus::Object> powerd_session;
274  std::shared_ptr<dbus::Object> uscreen_session;
275  std::string sys_lock_name;
277  std::string sys_cookie;
278  std::mutex wakelock_mutex;
281  std::unique_ptr<timeout> wakelock_timeout;
282  Engine::State previous_state;
283  PlayerImplementation::PlayerKey key;
284 };
285 
287  const dbus::types::ObjectPath& session_path,
288  const std::shared_ptr<Service>& service,
289  PlayerKey key)
290  : media::PlayerSkeleton(session_path),
291  d(new Private(
292  this,
293  session_path,
294  service,
295  key))
296 {
297  // Initializing default values for properties
298  can_play().set(true);
299  can_pause().set(true);
300  can_seek().set(true);
301  can_go_previous().set(true);
302  can_go_next().set(true);
303  is_video_source().set(false);
304  is_audio_source().set(false);
305  is_shuffle().set(true);
306  playback_rate().set(1.f);
307  playback_status().set(Player::PlaybackStatus::null);
308  loop_status().set(Player::LoopStatus::none);
309  position().set(0);
310  duration().set(0);
311  audio_stream_role().set(Player::AudioStreamRole::multimedia);
312  d->engine->audio_stream_role().set(Player::AudioStreamRole::multimedia);
313 
314  // Make sure that the Position property gets updated from the Engine
315  // every time the client requests position
316  std::function<uint64_t()> position_getter = [this]()
317  {
318  return d->engine->position().get();
319  };
320  position().install(position_getter);
321 
322  // Make sure that the Duration property gets updated from the Engine
323  // every time the client requests duration
324  std::function<uint64_t()> duration_getter = [this]()
325  {
326  return d->engine->duration().get();
327  };
328  duration().install(duration_getter);
329 
330  std::function<bool()> video_type_getter = [this]()
331  {
332  return d->engine->is_video_source().get();
333  };
334  is_video_source().install(video_type_getter);
335 
336  std::function<bool()> audio_type_getter = [this]()
337  {
338  return d->engine->is_audio_source().get();
339  };
340  is_audio_source().install(audio_type_getter);
341 
342  // Make sure that the audio_stream_role property gets updated on the Engine side
343  // whenever the client side sets the role
344  audio_stream_role().changed().connect([this](media::Player::AudioStreamRole new_role)
345  {
346  d->engine->audio_stream_role().set(new_role);
347  });
348 
349  d->engine->about_to_finish_signal().connect([this]()
350  {
351  if (d->track_list->has_next())
352  {
353  Track::UriType uri = d->track_list->query_uri_for_track(d->track_list->next());
354  if (!uri.empty())
355  d->parent->open_uri(uri);
356  }
357  });
358 
359  d->engine->client_disconnected_signal().connect([this]()
360  {
361  // If the client disconnects, make sure both wakelock types
362  // are cleared
363  d->clear_wakelocks();
364  });
365 
366  d->engine->seeked_to_signal().connect([this](uint64_t value)
367  {
368  seeked_to()(value);
369  });
370 
371  d->engine->end_of_stream_signal().connect([this]()
372  {
373  end_of_stream()();
374  });
375 
376  d->engine->playback_status_changed_signal().connect([this](const Player::PlaybackStatus& status)
377  {
378  playback_status_changed()(status);
379  });
380 }
381 
383 {
384 }
385 
386 std::shared_ptr<media::TrackList> media::PlayerImplementation::track_list()
387 {
388  return d->track_list;
389 }
390 
391 // TODO: Convert this to be a property instead of sync call
393 {
394  return d->key;
395 }
396 
398 {
399  return d->engine->open_resource_for_uri(uri);
400 }
401 
402 void media::PlayerImplementation::create_video_sink(uint32_t texture_id)
403 {
404  d->engine->create_video_sink(texture_id);
405 }
406 
408 {
409  // This method is client-side only and is simply a no-op for the service side
410  return NULL;
411 }
412 
414 {
415 }
416 
418 {
419 }
420 
422 {
423  d->engine->play();
424 }
425 
427 {
428  d->engine->pause();
429 }
430 
432 {
433  d->engine->stop();
434 }
435 
437  UNUSED FrameAvailableCb cb, UNUSED void *context)
438 {
439  // This method is client-side only and is simply a no-op for the service side
440 }
441 
443  UNUSED PlaybackCompleteCb cb, UNUSED void *context)
444 {
445  // This method is client-side only and is simply a no-op for the service side
446 }
447 
448 void media::PlayerImplementation::seek_to(const std::chrono::microseconds& ms)
449 {
450  d->engine->seek_to(ms);
451 }
void * GLConsumerWrapperHybris
Definition: player.h:44
virtual void set_playback_complete_callback(PlaybackCompleteCb cb, void *context)
virtual GLConsumerWrapperHybris gl_consumer() const
virtual const core::Property< PlaybackStatus > & playback_status() const
virtual const core::Property< bool > & is_video_source() const
std::shared_ptr< TrackListImplementation > track_list
virtual const core::Signal< void > & end_of_stream() const
virtual const core::Property< bool > & can_seek() const
virtual const core::Property< bool > & is_shuffle() const
virtual const core::Property< bool > & can_go_next() const
virtual const core::Property< bool > & can_go_previous() const
Definition: bus.h:33
wakelock_clear_t current_wakelock_type() const
virtual const core::Property< uint64_t > & duration() const
virtual const core::Property< bool > & can_play() const
STL namespace.
#define UNUSED
virtual const core::Signal< uint64_t > & seeked_to() const
PlayerImplementation(const core::dbus::types::ObjectPath &session_path, const std::shared_ptr< Service > &service, PlayerKey key)
virtual const core::Property< bool > & can_pause() const
virtual const core::Property< LoopStatus > & loop_status() const
void clear_wakelock(const wakelock_clear_t &wakelock)
virtual const core::Property< PlaybackRate > & playback_rate() const
virtual void create_video_sink(uint32_t texture_id)
virtual const core::Property< AudioStreamRole > & audio_stream_role() const
Private(PlayerImplementation *parent, const dbus::types::ObjectPath &session_path, const std::shared_ptr< media::Service > &service, PlayerImplementation::PlayerKey key)
std::shared_ptr< dbus::Object > powerd_session
virtual bool open_uri(const Track::UriType &uri)
virtual void seek_to(const std::chrono::microseconds &offset)
virtual void set_frame_available_callback(FrameAvailableCb cb, void *context)
std::string UriType
Definition: track.h:40
virtual core::Signal< PlaybackStatus > & playback_status_changed()
PlayerImplementation::PlayerKey key
virtual const core::Property< bool > & is_audio_source() const
virtual std::shared_ptr< TrackList > track_list()
std::shared_ptr< dbus::Object > uscreen_session
virtual const core::Property< uint64_t > & position() const