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