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 <memory>
32 #include <exception>
33 #include <iostream>
34 #include <mutex>
35 
36 #define UNUSED __attribute__((unused))
37 
38 namespace media = core::ubuntu::media;
39 namespace dbus = core::dbus;
40 
41 using namespace std;
42 
44  public std::enable_shared_from_this<Private>
45 {
46  enum class wakelock_clear_t
47  {
48  WAKELOCK_CLEAR_INACTIVE,
49  WAKELOCK_CLEAR_DISPLAY,
50  WAKELOCK_CLEAR_SYSTEM,
51  WAKELOCK_CLEAR_INVALID
52  };
53 
54  Private(PlayerImplementation* parent,
55  const dbus::types::ObjectPath& session_path,
56  const std::shared_ptr<media::Service>& service,
57  PlayerImplementation::PlayerKey key)
58  : parent(parent),
59  service(service),
60  engine(std::make_shared<gstreamer::Engine>()),
61  session_path(session_path),
62  track_list(
63  new media::TrackListImplementation(
64  session_path.as_string() + "/TrackList",
65  engine->meta_data_extractor())),
66  sys_lock_name("media-hub-music-playback"),
67  disp_cookie(-1),
68  system_wakelock_count(0),
69  display_wakelock_count(0),
70  previous_state(Engine::State::stopped),
71  key(key),
72  engine_state_change_connection(engine->state().changed().connect(make_state_change_handler()))
73  {
74  auto bus = std::shared_ptr<dbus::Bus>(new dbus::Bus(core::dbus::WellKnownBus::system));
75  bus->install_executor(dbus::asio::make_executor(bus));
76 
77  auto stub_service = dbus::Service::use_service(bus, dbus::traits::Service<core::Powerd>::interface_name());
78  powerd_session = stub_service->object_for_path(dbus::types::ObjectPath("/com/canonical/powerd"));
79 
80  auto uscreen_stub_service = dbus::Service::use_service(bus, dbus::traits::Service<core::UScreen>::interface_name());
81  uscreen_session = uscreen_stub_service->object_for_path(dbus::types::ObjectPath("/com/canonical/Unity/Screen"));
82  }
83 
85  {
86  // Make sure that we don't hold on to the wakelocks if media-hub-server
87  // ever gets restarted manually or automatically
88  clear_wakelocks();
89 
90  // The engine destructor can lead to a stop change state which will
91  // trigger the state change handler. Ensure the handler is not called
92  // by disconnecting the state change signal
93  engine_state_change_connection.disconnect();
94  }
95 
96  std::function<void(const Engine::State& state)> make_state_change_handler()
97  {
98  /*
99  * Wakelock state logic:
100  * PLAYING->READY or PLAYING->PAUSED or PLAYING->STOPPED: delay 4 seconds and try to clear current wakelock type
101  * ANY STATE->PLAYING: request a new wakelock (system or display)
102  */
103  return [this](const Engine::State& state)
104  {
105  switch(state)
106  {
107  case Engine::State::ready:
108  {
109  parent->playback_status().set(media::Player::ready);
110  if (previous_state == Engine::State::playing)
111  {
112  timeout(4000, true, make_clear_wakelock_functor());
113  }
114  break;
115  }
116  case Engine::State::playing:
117  {
118  // We update the track meta data prior to updating the playback status.
119  // Some MPRIS clients expect this order of events.
120  parent->meta_data_for_current_track().set(std::get<1>(engine->track_meta_data().get()));
121  // And update our playback status.
122  parent->playback_status().set(media::Player::playing);
123  request_power_state();
124  break;
125  }
126  case Engine::State::stopped:
127  {
128  parent->playback_status().set(media::Player::stopped);
129  if (previous_state == Engine::State::playing)
130  {
131  timeout(4000, true, make_clear_wakelock_functor());
132  }
133  break;
134  }
135  case Engine::State::paused:
136  {
137  parent->playback_status().set(media::Player::paused);
138  if (previous_state == Engine::State::playing)
139  {
140  timeout(4000, true, make_clear_wakelock_functor());
141  }
142  break;
143  }
144  default:
145  break;
146  };
147 
148  // Keep track of the previous Engine playback state:
149  previous_state = state;
150  };
151  }
152 
154  {
155  try
156  {
157  if (parent->is_video_source())
158  {
159  if (++display_wakelock_count == 1)
160  {
161  auto result = uscreen_session->invoke_method_synchronously<core::UScreen::keepDisplayOn, int>();
162  if (result.is_error())
163  throw std::runtime_error(result.error().print());
164  disp_cookie = result.value();
165  cout << "Requested new display wakelock" << endl;
166  }
167  }
168  else
169  {
170  if (++system_wakelock_count == 1)
171  {
172  auto result = powerd_session->invoke_method_synchronously<core::Powerd::requestSysState, std::string>(sys_lock_name, static_cast<int>(1));
173  if (result.is_error())
174  throw std::runtime_error(result.error().print());
175  sys_cookie = result.value();
176  cout << "Requested new system wakelock" << endl;
177  }
178  }
179  }
180  catch(const std::exception& e)
181  {
182  std::cerr << "Warning: failed to request power state: ";
183  std::cerr << e.what() << std::endl;
184  }
185  }
186 
187  void clear_wakelock(const wakelock_clear_t &wakelock)
188  {
189  cout << __PRETTY_FUNCTION__ << endl;
190  try
191  {
192  switch (wakelock)
193  {
194  case wakelock_clear_t::WAKELOCK_CLEAR_INACTIVE:
195  break;
196  case wakelock_clear_t::WAKELOCK_CLEAR_SYSTEM:
197  // Only actually clear the system wakelock once the count reaches zero
198  if (--system_wakelock_count == 0)
199  {
200  cout << "Clearing system wakelock" << endl;
201  powerd_session->invoke_method_synchronously<core::Powerd::clearSysState, void>(sys_cookie);
202  sys_cookie.clear();
203  }
204  break;
205  case wakelock_clear_t::WAKELOCK_CLEAR_DISPLAY:
206  // Only actually clear the display wakelock once the count reaches zero
207  if (--display_wakelock_count == 0)
208  {
209  cout << "Clearing display wakelock" << endl;
210  uscreen_session->invoke_method_synchronously<core::UScreen::removeDisplayOnRequest, void>(disp_cookie);
211  disp_cookie = -1;
212  }
213  break;
214  case wakelock_clear_t::WAKELOCK_CLEAR_INVALID:
215  default:
216  cerr << "Can't clear invalid wakelock type" << endl;
217  }
218  }
219  catch(const std::exception& e)
220  {
221  std::cerr << "Warning: failed to clear power state: ";
222  std::cerr << e.what() << std::endl;
223  }
224  }
225 
227  {
228  return (parent->is_video_source()) ?
229  wakelock_clear_t::WAKELOCK_CLEAR_DISPLAY : wakelock_clear_t::WAKELOCK_CLEAR_SYSTEM;
230  }
231 
233  {
234  // Clear both types of wakelocks (display and system)
235  if (system_wakelock_count.load() > 0)
236  {
237  system_wakelock_count = 1;
238  clear_wakelock(wakelock_clear_t::WAKELOCK_CLEAR_SYSTEM);
239  }
240  if (display_wakelock_count.load() > 0)
241  {
242  display_wakelock_count = 1;
243  clear_wakelock(wakelock_clear_t::WAKELOCK_CLEAR_DISPLAY);
244  }
245  }
246 
247  std::function<void()> make_clear_wakelock_functor()
248  {
249  // Since this functor will be executed on a separate detached thread
250  // the execution of the functor may surpass the lifetime of this Private
251  // object instance. By keeping a weak_ptr to the private object instance
252  // we can check if the object is dead before calling methods on it
253  std::weak_ptr<Private> weak_self{shared_from_this()};
254  auto wakelock_type = current_wakelock_type();
255  return [weak_self, wakelock_type] {
256  if (auto self = weak_self.lock())
257  self->clear_wakelock(wakelock_type);
258  };
259  }
260 
261  PlayerImplementation* parent;
262  std::shared_ptr<Service> service;
263  std::shared_ptr<Engine> engine;
264  dbus::types::ObjectPath session_path;
265  std::shared_ptr<TrackListImplementation> track_list;
266  std::shared_ptr<dbus::Object> powerd_session;
267  std::shared_ptr<dbus::Object> uscreen_session;
268  std::string sys_lock_name;
270  std::string sys_cookie;
271  std::atomic<int> system_wakelock_count;
272  std::atomic<int> display_wakelock_count;
273  Engine::State previous_state;
274  PlayerImplementation::PlayerKey key;
275  core::Signal<> on_client_disconnected;
277 };
278 
280  const std::string& identity,
281  const std::shared_ptr<core::dbus::Bus>& bus,
282  const std::shared_ptr<core::dbus::Object>& session,
283  const std::shared_ptr<Service>& service,
284  PlayerKey key)
285  : media::PlayerSkeleton
286  {
287  media::PlayerSkeleton::Configuration
288  {
289  bus,
290  session,
291  identity
292  }
293  },
294  d(make_shared<Private>(
295  this,
296  session->path(),
297  service,
298  key))
299 {
300  // Initialize default values for Player interface properties
301  can_play().set(true);
302  can_pause().set(true);
303  can_seek().set(true);
304  can_go_previous().set(true);
305  can_go_next().set(true);
306  is_video_source().set(false);
307  is_audio_source().set(false);
308  is_shuffle().set(true);
309  playback_rate().set(1.f);
310  playback_status().set(Player::PlaybackStatus::null);
311  loop_status().set(Player::LoopStatus::none);
312  position().set(0);
313  duration().set(0);
314  audio_stream_role().set(Player::AudioStreamRole::multimedia);
315  d->engine->audio_stream_role().set(Player::AudioStreamRole::multimedia);
316  orientation().set(Player::Orientation::rotate0);
317 
318  // Make sure that the Position property gets updated from the Engine
319  // every time the client requests position
320  std::function<uint64_t()> position_getter = [this]()
321  {
322  return d->engine->position().get();
323  };
324  position().install(position_getter);
325 
326  // Make sure that the Duration property gets updated from the Engine
327  // every time the client requests duration
328  std::function<uint64_t()> duration_getter = [this]()
329  {
330  return d->engine->duration().get();
331  };
332  duration().install(duration_getter);
333 
334  std::function<bool()> video_type_getter = [this]()
335  {
336  return d->engine->is_video_source().get();
337  };
338  is_video_source().install(video_type_getter);
339 
340  std::function<bool()> audio_type_getter = [this]()
341  {
342  return d->engine->is_audio_source().get();
343  };
344  is_audio_source().install(audio_type_getter);
345 
346  // Make sure that the audio_stream_role property gets updated on the Engine side
347  // whenever the client side sets the role
348  audio_stream_role().changed().connect([this](media::Player::AudioStreamRole new_role)
349  {
350  d->engine->audio_stream_role().set(new_role);
351  });
352 
353  // When the value of the orientation Property is changed in the Engine by playbin,
354  // update the Player's cached value
355  d->engine->orientation().changed().connect([this](const Player::Orientation& o)
356  {
357  orientation().set(o);
358  });
359 
360  d->engine->about_to_finish_signal().connect([this]()
361  {
362  if (d->track_list->has_next())
363  {
364  Track::UriType uri = d->track_list->query_uri_for_track(d->track_list->next());
365  if (!uri.empty())
366  d->parent->open_uri(uri);
367  }
368  });
369 
370  d->engine->client_disconnected_signal().connect([this]()
371  {
372  // If the client disconnects, make sure both wakelock types
373  // are cleared
374  d->clear_wakelocks();
375  // And tell the outside world that the client has gone away
376  d->on_client_disconnected();
377  });
378 
379  d->engine->seeked_to_signal().connect([this](uint64_t value)
380  {
381  seeked_to()(value);
382  });
383 
384  d->engine->end_of_stream_signal().connect([this]()
385  {
386  end_of_stream()();
387  });
388 
389  d->engine->playback_status_changed_signal().connect([this](const Player::PlaybackStatus& status)
390  {
391  playback_status_changed()(status);
392  });
393 
394  d->engine->video_dimension_changed_signal().connect([this](uint32_t height, uint32_t width)
395  {
396  uint64_t mask = 0;
397  // Left most 32 bits are for height, right most 32 bits are for width
398  mask = (static_cast<uint64_t>(height) << 32) | static_cast<uint64_t>(width);
399  video_dimension_changed()(mask);
400  });
401 }
402 
404 {
405  // Install null getters as these properties may be destroyed
406  // after the engine has been destroyed since they are owned by the
407  // base class.
408  std::function<uint64_t()> position_getter = [this]()
409  {
410  return static_cast<uint64_t>(0);
411  };
412  position().install(position_getter);
413 
414  std::function<uint64_t()> duration_getter = [this]()
415  {
416  return static_cast<uint64_t>(0);
417  };
418  duration().install(duration_getter);
419 
420  std::function<bool()> video_type_getter = [this]()
421  {
422  return false;
423  };
424  is_video_source().install(video_type_getter);
425 
426  std::function<bool()> audio_type_getter = [this]()
427  {
428  return false;
429  };
430  is_audio_source().install(audio_type_getter);
431 }
432 
433 std::shared_ptr<media::TrackList> media::PlayerImplementation::track_list()
434 {
435  return d->track_list;
436 }
437 
438 // TODO: Convert this to be a property instead of sync call
440 {
441  return d->key;
442 }
443 
445 {
446  return d->engine->open_resource_for_uri(uri);
447 }
448 
449 void media::PlayerImplementation::create_video_sink(uint32_t texture_id)
450 {
451  d->engine->create_video_sink(texture_id);
452 }
453 
455 {
456  // This method is client-side only and is simply a no-op for the service side
457  return NULL;
458 }
459 
461 {
462 }
463 
465 {
466 }
467 
469 {
470  d->engine->play();
471 }
472 
474 {
475  d->engine->pause();
476 }
477 
479 {
480  std::cout << __PRETTY_FUNCTION__ << std::endl;
481  d->engine->stop();
482 }
483 
485  UNUSED FrameAvailableCb cb, UNUSED void *context)
486 {
487  // This method is client-side only and is simply a no-op for the service side
488 }
489 
491  UNUSED PlaybackCompleteCb cb, UNUSED void *context)
492 {
493  // This method is client-side only and is simply a no-op for the service side
494 }
495 
496 void media::PlayerImplementation::seek_to(const std::chrono::microseconds& ms)
497 {
498  d->engine->seek_to(ms);
499 }
500 
501 const core::Signal<>& media::PlayerImplementation::on_client_disconnected() const
502 {
503  return d->on_client_disconnected;
504 }
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
virtual const core::Signal< uint64_t > & video_dimension_changed() 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 const core::Property< Orientation > & orientation() const
virtual bool open_uri(const Track::UriType &uri)
std::function< void()> make_clear_wakelock_functor()
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
std::function< void(const Engine::State &state)> make_state_change_handler()
virtual std::shared_ptr< TrackList > track_list()
std::shared_ptr< dbus::Object > uscreen_session