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  // We update the track meta data prior to updating the playback status.
106  // Some MPRIS clients expect this order of events.
107  parent->meta_data_for_current_track().set(std::get<1>(engine->track_meta_data().get()));
108  // And update our playback status.
109  parent->playback_status().set(media::Player::playing);
110  if (previous_state == Engine::State::stopped || previous_state == Engine::State::paused)
111  {
112  request_power_state();
113  }
114  break;
115  }
116  case Engine::State::stopped:
117  {
118  parent->playback_status().set(media::Player::stopped);
119  break;
120  }
121  case Engine::State::paused:
122  {
123  parent->playback_status().set(media::Player::paused);
124  if (previous_state == Engine::State::ready)
125  {
126  request_power_state();
127  }
128  else if (previous_state == Engine::State::playing)
129  {
130  wakelock_timeout.reset(new timeout(4000, true, std::bind(&Private::clear_wakelock,
131  this, std::placeholders::_1), current_wakelock_type()));
132  }
133  break;
134  }
135  default:
136  break;
137  };
138 
139  // Keep track of the previous Engine playback state:
140  previous_state = state;
141  });
142  }
143 
145  {
146  // Make sure that we don't hold on to the wakelocks if media-hub-server
147  // ever gets restarted manually or automatically
148  clear_wakelocks();
149  }
150 
152  {
153  try
154  {
155  if (parent->is_video_source())
156  {
157  if (display_wakelock_count == 0)
158  {
159  auto result = uscreen_session->invoke_method_synchronously<core::UScreen::keepDisplayOn, int>();
160  if (result.is_error())
161  throw std::runtime_error(result.error().print());
162  disp_cookie = result.value();
163  cout << "Requested new display wakelock" << endl;
164  }
165 
166  {
167  // Keep track of how many display wakelocks have been requested
168  std::lock_guard<std::mutex> lock(wakelock_mutex);
169  ++display_wakelock_count;
170  }
171  }
172  else
173  {
174  if (system_wakelock_count == 0)
175  {
176  auto result = powerd_session->invoke_method_synchronously<core::Powerd::requestSysState, std::string>(sys_lock_name, static_cast<int>(1));
177  if (result.is_error())
178  throw std::runtime_error(result.error().print());
179  sys_cookie = result.value();
180  cout << "Requested new system wakelock" << endl;
181  }
182 
183  {
184  // Keep track of how many system wakelocks have been requested
185  std::lock_guard<std::mutex> lock(wakelock_mutex);
186  ++system_wakelock_count;
187  }
188  }
189  }
190  catch(const std::exception& e)
191  {
192  std::cerr << "Warning: failed to request power state: ";
193  std::cerr << e.what() << std::endl;
194  }
195  }
196 
197  void clear_wakelock(const wakelock_clear_t &wakelock)
198  {
199  cout << __PRETTY_FUNCTION__ << endl;
200  try
201  {
202  switch (wakelock)
203  {
204  case wakelock_clear_t::WAKELOCK_CLEAR_INACTIVE:
205  break;
206  case wakelock_clear_t::WAKELOCK_CLEAR_SYSTEM:
207  {
208  std::lock_guard<std::mutex> lock(wakelock_mutex);
209  --system_wakelock_count;
210  }
211  // Only actually clear the system wakelock once the count reaches zero
212  if (system_wakelock_count == 0)
213  {
214  cout << "Clearing system wakelock" << endl;
215  powerd_session->invoke_method_synchronously<core::Powerd::clearSysState, void>(sys_cookie);
216  sys_cookie.clear();
217  }
218  break;
219  case wakelock_clear_t::WAKELOCK_CLEAR_DISPLAY:
220  {
221  std::lock_guard<std::mutex> lock(wakelock_mutex);
222  --display_wakelock_count;
223  }
224  // Only actually clear the display wakelock once the count reaches zero
225  if (display_wakelock_count == 0)
226  {
227  cout << "Clearing display wakelock" << endl;
228  uscreen_session->invoke_method_synchronously<core::UScreen::removeDisplayOnRequest, void>(disp_cookie);
229  disp_cookie = -1;
230  }
231  break;
232  case wakelock_clear_t::WAKELOCK_CLEAR_INVALID:
233  default:
234  cerr << "Can't clear invalid wakelock type" << endl;
235  }
236  }
237  catch(const std::exception& e)
238  {
239  std::cerr << "Warning: failed to clear power state: ";
240  std::cerr << e.what() << std::endl;
241  }
242  }
243 
245  {
246  return (parent->is_video_source()) ?
247  wakelock_clear_t::WAKELOCK_CLEAR_DISPLAY : wakelock_clear_t::WAKELOCK_CLEAR_SYSTEM;
248  }
249 
251  {
252  // Clear both types of wakelocks (display and system)
253  if (system_wakelock_count > 0)
254  {
255  {
256  std::lock_guard<std::mutex> lock(wakelock_mutex);
257  system_wakelock_count = 1;
258  }
259  clear_wakelock(wakelock_clear_t::WAKELOCK_CLEAR_SYSTEM);
260  }
261  if (display_wakelock_count > 0)
262  {
263  {
264  std::lock_guard<std::mutex> lock(wakelock_mutex);
265  display_wakelock_count = 1;
266  }
267  clear_wakelock(wakelock_clear_t::WAKELOCK_CLEAR_DISPLAY);
268  }
269  }
270 
271  PlayerImplementation* parent;
272  std::shared_ptr<Service> service;
273  std::shared_ptr<Engine> engine;
274  dbus::types::ObjectPath session_path;
275  std::shared_ptr<TrackListImplementation> track_list;
276  std::shared_ptr<dbus::Object> powerd_session;
277  std::shared_ptr<dbus::Object> uscreen_session;
278  std::string sys_lock_name;
280  std::string sys_cookie;
281  std::mutex wakelock_mutex;
284  std::unique_ptr<timeout> wakelock_timeout;
285  Engine::State previous_state;
286  PlayerImplementation::PlayerKey key;
287  core::Signal<> on_client_disconnected;
288 };
289 
291  const std::string& identity,
292  const std::shared_ptr<core::dbus::Bus>& bus,
293  const std::shared_ptr<core::dbus::Object>& session,
294  const std::shared_ptr<Service>& service,
295  PlayerKey key)
296  : media::PlayerSkeleton
297  {
298  media::PlayerSkeleton::Configuration
299  {
300  bus,
301  session,
302  identity
303  }
304  },
305  d(new Private(
306  this,
307  session->path(),
308  service,
309  key))
310 {
311  // Initializing default values for properties
312  can_play().set(true);
313  can_pause().set(true);
314  can_seek().set(true);
315  can_go_previous().set(true);
316  can_go_next().set(true);
317  is_video_source().set(false);
318  is_audio_source().set(false);
319  is_shuffle().set(true);
320  playback_rate().set(1.f);
321  playback_status().set(Player::PlaybackStatus::null);
322  loop_status().set(Player::LoopStatus::none);
323  position().set(0);
324  duration().set(0);
325  audio_stream_role().set(Player::AudioStreamRole::multimedia);
326  d->engine->audio_stream_role().set(Player::AudioStreamRole::multimedia);
327 
328  // Make sure that the Position property gets updated from the Engine
329  // every time the client requests position
330  std::function<uint64_t()> position_getter = [this]()
331  {
332  return d->engine->position().get();
333  };
334  position().install(position_getter);
335 
336  // Make sure that the Duration property gets updated from the Engine
337  // every time the client requests duration
338  std::function<uint64_t()> duration_getter = [this]()
339  {
340  return d->engine->duration().get();
341  };
342  duration().install(duration_getter);
343 
344  std::function<bool()> video_type_getter = [this]()
345  {
346  return d->engine->is_video_source().get();
347  };
348  is_video_source().install(video_type_getter);
349 
350  std::function<bool()> audio_type_getter = [this]()
351  {
352  return d->engine->is_audio_source().get();
353  };
354  is_audio_source().install(audio_type_getter);
355 
356  // Make sure that the audio_stream_role property gets updated on the Engine side
357  // whenever the client side sets the role
358  audio_stream_role().changed().connect([this](media::Player::AudioStreamRole new_role)
359  {
360  d->engine->audio_stream_role().set(new_role);
361  });
362 
363  d->engine->about_to_finish_signal().connect([this]()
364  {
365  if (d->track_list->has_next())
366  {
367  Track::UriType uri = d->track_list->query_uri_for_track(d->track_list->next());
368  if (!uri.empty())
369  d->parent->open_uri(uri);
370  }
371  });
372 
373  d->engine->client_disconnected_signal().connect([this]()
374  {
375  // If the client disconnects, make sure both wakelock types
376  // are cleared
377  d->clear_wakelocks();
378  // And tell the outside world that the client has gone away
379  d->on_client_disconnected();
380  });
381 
382  d->engine->seeked_to_signal().connect([this](uint64_t value)
383  {
384  seeked_to()(value);
385  });
386 
387  d->engine->end_of_stream_signal().connect([this]()
388  {
389  end_of_stream()();
390  });
391 
392  d->engine->playback_status_changed_signal().connect([this](const Player::PlaybackStatus& status)
393  {
394  playback_status_changed()(status);
395  });
396 }
397 
399 {
400 }
401 
402 std::shared_ptr<media::TrackList> media::PlayerImplementation::track_list()
403 {
404  return d->track_list;
405 }
406 
407 // TODO: Convert this to be a property instead of sync call
409 {
410  return d->key;
411 }
412 
414 {
415  return d->engine->open_resource_for_uri(uri);
416 }
417 
418 void media::PlayerImplementation::create_video_sink(uint32_t texture_id)
419 {
420  d->engine->create_video_sink(texture_id);
421 }
422 
424 {
425  // This method is client-side only and is simply a no-op for the service side
426  return NULL;
427 }
428 
430 {
431 }
432 
434 {
435 }
436 
438 {
439  d->engine->play();
440 }
441 
443 {
444  d->engine->pause();
445 }
446 
448 {
449  std::cout << __PRETTY_FUNCTION__ << std::endl;
450  d->engine->stop();
451 }
452 
454  UNUSED FrameAvailableCb cb, UNUSED void *context)
455 {
456  // This method is client-side only and is simply a no-op for the service side
457 }
458 
460  UNUSED PlaybackCompleteCb cb, UNUSED void *context)
461 {
462  // This method is client-side only and is simply a no-op for the service side
463 }
464 
465 void media::PlayerImplementation::seek_to(const std::chrono::microseconds& ms)
466 {
467  d->engine->seek_to(ms);
468 }
469 
470 const core::Signal<>& media::PlayerImplementation::on_client_disconnected() const
471 {
472  return d->on_client_disconnected;
473 }
void * GLConsumerWrapperHybris
Definition: player.h:44
virtual void set_playback_complete_callback(PlaybackCompleteCb cb, void *context)
const core::Signal & on_client_disconnected() const
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< int64_t > & duration() 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< int64_t > & position() 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< bool > & can_play() const
STL namespace.
#define UNUSED
virtual const core::Property< bool > & can_pause() const
virtual const core::Property< LoopStatus > & loop_status() const
void clear_wakelock(const wakelock_clear_t &wakelock)
void(* FrameAvailableCb)(void *context)
Definition: player.h:48
virtual const core::Signal< int64_t > & seeked_to() const
virtual const core::Property< PlaybackRate > & playback_rate() const
PlayerImplementation(const std::string &identity, const std::shared_ptr< core::dbus::Bus > &bus, const std::shared_ptr< core::dbus::Object > &session, const std::shared_ptr< Service > &service, PlayerKey key)
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)
void(* PlaybackCompleteCb)(void *context)
Definition: player.h:49
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