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  // Initializing default values for 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 
317  // Make sure that the Position property gets updated from the Engine
318  // every time the client requests position
319  std::function<uint64_t()> position_getter = [this]()
320  {
321  return d->engine->position().get();
322  };
323  position().install(position_getter);
324 
325  // Make sure that the Duration property gets updated from the Engine
326  // every time the client requests duration
327  std::function<uint64_t()> duration_getter = [this]()
328  {
329  return d->engine->duration().get();
330  };
331  duration().install(duration_getter);
332 
333  std::function<bool()> video_type_getter = [this]()
334  {
335  return d->engine->is_video_source().get();
336  };
337  is_video_source().install(video_type_getter);
338 
339  std::function<bool()> audio_type_getter = [this]()
340  {
341  return d->engine->is_audio_source().get();
342  };
343  is_audio_source().install(audio_type_getter);
344 
345  // Make sure that the audio_stream_role property gets updated on the Engine side
346  // whenever the client side sets the role
347  audio_stream_role().changed().connect([this](media::Player::AudioStreamRole new_role)
348  {
349  d->engine->audio_stream_role().set(new_role);
350  });
351 
352  d->engine->about_to_finish_signal().connect([this]()
353  {
354  if (d->track_list->has_next())
355  {
356  Track::UriType uri = d->track_list->query_uri_for_track(d->track_list->next());
357  if (!uri.empty())
358  d->parent->open_uri(uri);
359  }
360  });
361 
362  d->engine->client_disconnected_signal().connect([this]()
363  {
364  // If the client disconnects, make sure both wakelock types
365  // are cleared
366  d->clear_wakelocks();
367  // And tell the outside world that the client has gone away
368  d->on_client_disconnected();
369  });
370 
371  d->engine->seeked_to_signal().connect([this](uint64_t value)
372  {
373  seeked_to()(value);
374  });
375 
376  d->engine->end_of_stream_signal().connect([this]()
377  {
378  end_of_stream()();
379  });
380 
381  d->engine->playback_status_changed_signal().connect([this](const Player::PlaybackStatus& status)
382  {
383  playback_status_changed()(status);
384  });
385 }
386 
388 {
389  // Install null getters as these properties may be destroyed
390  // after the engine has been destroyed since they are owned by the
391  // base class.
392  std::function<uint64_t()> position_getter = [this]()
393  {
394  return static_cast<uint64_t>(0);
395  };
396  position().install(position_getter);
397 
398  std::function<uint64_t()> duration_getter = [this]()
399  {
400  return static_cast<uint64_t>(0);
401  };
402  duration().install(duration_getter);
403 
404  std::function<bool()> video_type_getter = [this]()
405  {
406  return false;
407  };
408  is_video_source().install(video_type_getter);
409 
410  std::function<bool()> audio_type_getter = [this]()
411  {
412  return false;
413  };
414  is_audio_source().install(audio_type_getter);
415 }
416 
417 std::shared_ptr<media::TrackList> media::PlayerImplementation::track_list()
418 {
419  return d->track_list;
420 }
421 
422 // TODO: Convert this to be a property instead of sync call
424 {
425  return d->key;
426 }
427 
429 {
430  return d->engine->open_resource_for_uri(uri);
431 }
432 
433 void media::PlayerImplementation::create_video_sink(uint32_t texture_id)
434 {
435  d->engine->create_video_sink(texture_id);
436 }
437 
439 {
440  // This method is client-side only and is simply a no-op for the service side
441  return NULL;
442 }
443 
445 {
446 }
447 
449 {
450 }
451 
453 {
454  d->engine->play();
455 }
456 
458 {
459  d->engine->pause();
460 }
461 
463 {
464  std::cout << __PRETTY_FUNCTION__ << std::endl;
465  d->engine->stop();
466 }
467 
469  UNUSED FrameAvailableCb cb, UNUSED void *context)
470 {
471  // This method is client-side only and is simply a no-op for the service side
472 }
473 
475  UNUSED PlaybackCompleteCb cb, UNUSED void *context)
476 {
477  // This method is client-side only and is simply a no-op for the service side
478 }
479 
480 void media::PlayerImplementation::seek_to(const std::chrono::microseconds& ms)
481 {
482  d->engine->seek_to(ms);
483 }
484 
485 const core::Signal<>& media::PlayerImplementation::on_client_disconnected() const
486 {
487  return d->on_client_disconnected;
488 }
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)
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