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