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 "client_death_observer.h"
25 #include "engine.h"
26 #include "null_track_list.h"
28 
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 
43 template<typename Parent>
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, const media::PlayerImplementation<Parent>::Configuration& config)
56  : parent(parent),
57  config(config),
58  display_state_lock(config.power_state_controller->display_state_lock()),
59  system_state_lock(config.power_state_controller->system_state_lock()),
60  engine(std::make_shared<gstreamer::Engine>()),
61  track_list(std::make_shared<NullTrackList>()),
62  system_wakelock_count(0),
63  display_wakelock_count(0),
64  previous_state(Engine::State::stopped),
65  engine_state_change_connection(engine->state().changed().connect(make_state_change_handler())),
66  engine_playback_status_change_connection(engine->playback_status_changed_signal().connect(make_playback_status_change_handler()))
67  {
68  std::cout << "Private parent instance: " << parent << std::endl;
69  // Poor man's logging of release/acquire events.
70  display_state_lock->acquired().connect([](media::power::DisplayState state)
71  {
72  std::cout << "Acquired new display state: " << state << std::endl;
73  });
74 
75  display_state_lock->released().connect([](media::power::DisplayState state)
76  {
77  std::cout << "Released display state: " << state << std::endl;
78  });
79 
80  system_state_lock->acquired().connect([](media::power::SystemState state)
81  {
82  std::cout << "Acquired new system state: " << state << std::endl;
83  });
84 
85  system_state_lock->released().connect([](media::power::SystemState state)
86  {
87  std::cout << "Released system state: " << state << std::endl;
88  });
89  }
90 
92  {
93  // Make sure that we don't hold on to the wakelocks if media-hub-server
94  // ever gets restarted manually or automatically
95  clear_wakelocks();
96 
97  // The engine destructor can lead to a stop change state which will
98  // trigger the state change handler. Ensure the handler is not called
99  // by disconnecting the state change signal
100  engine_state_change_connection.disconnect();
101 
102  std::cout << "** Disconnecting playback_status_changed_signal connection";
103  // The engine destructor can lead to a playback status change which will
104  // trigger the playback status change handler. Ensure the handler is not called
105  // by disconnecting the playback status change signal
106  engine_playback_status_change_connection.disconnect();
107  }
108 
109  std::function<void(const Engine::State& state)> make_state_change_handler()
110  {
111  /*
112  * Wakelock state logic:
113  * PLAYING->READY or PLAYING->PAUSED or PLAYING->STOPPED: delay 4 seconds and try to clear current wakelock type
114  * ANY STATE->PLAYING: request a new wakelock (system or display)
115  */
116  return [this](const Engine::State& state)
117  {
118  std::cout << "Setting state for parent: " << parent << std::endl;
119  switch(state)
120  {
121  case Engine::State::ready:
122  {
123  parent->playback_status().set(media::Player::ready);
124  if (previous_state == Engine::State::playing)
125  {
126  timeout(4000, true, make_clear_wakelock_functor());
127  }
128  break;
129  }
130  case Engine::State::playing:
131  {
132  // We update the track meta data prior to updating the playback status.
133  // Some MPRIS clients expect this order of events.
134  parent->meta_data_for_current_track().set(std::get<1>(engine->track_meta_data().get()));
135  // And update our playback status.
136  parent->playback_status().set(media::Player::playing);
137  std::cout << "Requesting power state" << std::endl;
138  request_power_state();
139  break;
140  }
141  case Engine::State::stopped:
142  {
143  parent->playback_status().set(media::Player::stopped);
144  if (previous_state == Engine::State::playing)
145  {
146  timeout(4000, true, make_clear_wakelock_functor());
147  }
148  break;
149  }
150  case Engine::State::paused:
151  {
152  parent->playback_status().set(media::Player::paused);
153  if (previous_state == Engine::State::playing)
154  {
155  timeout(4000, true, make_clear_wakelock_functor());
156  }
157  break;
158  }
159  default:
160  break;
161  };
162 
163  // Keep track of the previous Engine playback state:
164  previous_state = state;
165  };
166  }
167 
168  std::function<void(const media::Player::PlaybackStatus& status)> make_playback_status_change_handler()
169  {
170  return [this](const media::Player::PlaybackStatus& status)
171  {
172  std::cout << "Emiting playback_status_changed for parent: " << parent << std::endl;
173  parent->emit_playback_status_changed(status);
174  };
175  }
176 
178  {
179  std::cout << __PRETTY_FUNCTION__ << std::endl;
180  try
181  {
182  if (parent->is_video_source())
183  {
184  if (++display_wakelock_count == 1)
185  {
186  std::cout << "Requesting new display wakelock." << std::endl;
187  display_state_lock->request_acquire(media::power::DisplayState::on);
188  std::cout << "Requested new display wakelock." << std::endl;
189  }
190  }
191  else
192  {
193  if (++system_wakelock_count == 1)
194  {
195  std::cout << "Requesting new system wakelock." << std::endl;
196  system_state_lock->request_acquire(media::power::SystemState::active);
197  std::cout << "Requested new system wakelock." << std::endl;
198  }
199  }
200  }
201  catch(const std::exception& e)
202  {
203  std::cerr << "Warning: failed to request power state: ";
204  std::cerr << e.what() << std::endl;
205  }
206  }
207 
208  void clear_wakelock(const wakelock_clear_t &wakelock)
209  {
210  cout << __PRETTY_FUNCTION__ << endl;
211  try
212  {
213  switch (wakelock)
214  {
215  case wakelock_clear_t::WAKELOCK_CLEAR_INACTIVE:
216  break;
217  case wakelock_clear_t::WAKELOCK_CLEAR_SYSTEM:
218  // Only actually clear the system wakelock once the count reaches zero
219  if (--system_wakelock_count == 0)
220  {
221  std::cout << "Clearing system wakelock." << std::endl;
222  system_state_lock->request_release(media::power::SystemState::active);
223  }
224  break;
225  case wakelock_clear_t::WAKELOCK_CLEAR_DISPLAY:
226  // Only actually clear the display wakelock once the count reaches zero
227  if (--display_wakelock_count == 0)
228  {
229  std::cout << "Clearing display wakelock." << std::endl;
230  display_state_lock->request_release(media::power::DisplayState::on);
231  }
232  break;
233  case wakelock_clear_t::WAKELOCK_CLEAR_INVALID:
234  default:
235  cerr << "Can't clear invalid wakelock type" << endl;
236  }
237  }
238  catch(const std::exception& e)
239  {
240  std::cerr << "Warning: failed to clear power state: ";
241  std::cerr << e.what() << std::endl;
242  }
243  }
244 
246  {
247  return (parent->is_video_source()) ?
248  wakelock_clear_t::WAKELOCK_CLEAR_DISPLAY : wakelock_clear_t::WAKELOCK_CLEAR_SYSTEM;
249  }
250 
252  {
253  // Clear both types of wakelocks (display and system)
254  if (system_wakelock_count.load() > 0)
255  {
256  system_wakelock_count = 1;
257  clear_wakelock(wakelock_clear_t::WAKELOCK_CLEAR_SYSTEM);
258  }
259  if (display_wakelock_count.load() > 0)
260  {
261  display_wakelock_count = 1;
262  clear_wakelock(wakelock_clear_t::WAKELOCK_CLEAR_DISPLAY);
263  }
264  }
265 
266  std::function<void()> make_clear_wakelock_functor()
267  {
268  // Since this functor will be executed on a separate detached thread
269  // the execution of the functor may surpass the lifetime of this Private
270  // object instance. By keeping a weak_ptr to the private object instance
271  // we can check if the object is dead before calling methods on it
272  std::weak_ptr<Private> weak_self{this->shared_from_this()};
273  auto wakelock_type = current_wakelock_type();
274  return [weak_self, wakelock_type] {
275  if (auto self = weak_self.lock())
276  self->clear_wakelock(wakelock_type);
277  };
278  }
279 
281  {
282  engine->reset();
283  }
284 
285  // Our link back to our parent.
287  // We just store the parameters passed on construction.
289  media::power::StateController::Lock<media::power::DisplayState>::Ptr display_state_lock;
290  media::power::StateController::Lock<media::power::SystemState>::Ptr system_state_lock;
291 
292  std::shared_ptr<Engine> engine;
293  std::shared_ptr<media::NullTrackList> track_list;
294  std::atomic<int> system_wakelock_count;
295  std::atomic<int> display_wakelock_count;
296  Engine::State previous_state;
297  core::Signal<> on_client_disconnected;
300 };
301 
302 template<typename Parent>
304  : Parent{config.parent},
305  d{std::make_shared<Private>(this, config)}
306 {
307  // Initialize default values for Player interface properties
308  Parent::can_play().set(true);
309  Parent::can_pause().set(true);
310  Parent::can_seek().set(true);
311  Parent::can_go_previous().set(true);
312  Parent::can_go_next().set(true);
313  Parent::is_video_source().set(false);
314  Parent::is_audio_source().set(false);
315  Parent::is_shuffle().set(true);
316  Parent::playback_rate().set(1.f);
317  Parent::playback_status().set(Player::PlaybackStatus::null);
318  Parent::loop_status().set(Player::LoopStatus::none);
319  Parent::position().set(0);
320  Parent::duration().set(0);
321  Parent::audio_stream_role().set(Player::AudioStreamRole::multimedia);
322  d->engine->audio_stream_role().set(Player::AudioStreamRole::multimedia);
323  Parent::orientation().set(Player::Orientation::rotate0);
324  Parent::lifetime().set(Player::Lifetime::normal);
325  d->engine->lifetime().set(Player::Lifetime::normal);
326 
327  // Make sure that the Position property gets updated from the Engine
328  // every time the client requests position
329  std::function<uint64_t()> position_getter = [this]()
330  {
331  return d->engine->position().get();
332  };
333  Parent::position().install(position_getter);
334 
335  // Make sure that the Duration property gets updated from the Engine
336  // every time the client requests duration
337  std::function<uint64_t()> duration_getter = [this]()
338  {
339  return d->engine->duration().get();
340  };
341  Parent::duration().install(duration_getter);
342 
343  std::function<bool()> video_type_getter = [this]()
344  {
345  return d->engine->is_video_source().get();
346  };
347  Parent::is_video_source().install(video_type_getter);
348 
349  std::function<bool()> audio_type_getter = [this]()
350  {
351  return d->engine->is_audio_source().get();
352  };
353  Parent::is_audio_source().install(audio_type_getter);
354 
355  // Make sure that the audio_stream_role property gets updated on the Engine side
356  // whenever the client side sets the role
357  Parent::audio_stream_role().changed().connect([this](media::Player::AudioStreamRole new_role)
358  {
359  d->engine->audio_stream_role().set(new_role);
360  });
361 
362  // When the value of the orientation Property is changed in the Engine by playbin,
363  // update the Player's cached value
364  d->engine->orientation().changed().connect([this](const Player::Orientation& o)
365  {
366  Parent::orientation().set(o);
367  });
368 
369  Parent::lifetime().changed().connect([this](media::Player::Lifetime lifetime)
370  {
371  d->engine->lifetime().set(lifetime);
372  });
373 
374  d->engine->about_to_finish_signal().connect([this]()
375  {
376  Parent::about_to_finish()();
377 
378  if (d->track_list->has_next())
379  {
380  Track::UriType uri = d->track_list->query_uri_for_track(d->track_list->next());
381  if (!uri.empty())
382  d->parent->open_uri(uri);
383  }
384  });
385 
386  d->engine->client_disconnected_signal().connect([this]()
387  {
388  // If the client disconnects, make sure both wakelock types
389  // are cleared
390  d->clear_wakelocks();
391  // And tell the outside world that the client has gone away
392  d->on_client_disconnected();
393  });
394 
395  d->engine->seeked_to_signal().connect([this](uint64_t value)
396  {
397  Parent::seeked_to()(value);
398  });
399 
400  d->engine->end_of_stream_signal().connect([this]()
401  {
402  Parent::end_of_stream()();
403  });
404 
405  d->engine->video_dimension_changed_signal().connect([this](const media::video::Dimensions& dimensions)
406  {
407  Parent::video_dimension_changed()(dimensions);
408  });
409 
410  d->engine->error_signal().connect([this](const Player::Error& e)
411  {
412  Parent::error()(e);
413  });
414 
415  // Everything is setup, we now subscribe to death notifications.
416  std::weak_ptr<Private> wp{d};
417 
418  d->config.client_death_observer->register_for_death_notifications_with_key(config.key);
419  d->config.client_death_observer->on_client_with_key_died().connect([wp](const media::Player::PlayerKey& died)
420  {
421  if (auto sp = wp.lock())
422  {
423  if (died != sp->config.key)
424  return;
425 
426  static const std::chrono::milliseconds timeout{1000};
427  media::timeout(timeout.count(), true, [wp]()
428  {
429  if (auto sp = wp.lock())
430  sp->on_client_died();
431  });
432  }
433  });
434 }
435 
436 template<typename Parent>
438 {
439  // Install null getters as these properties may be destroyed
440  // after the engine has been destroyed since they are owned by the
441  // base class.
442  std::function<uint64_t()> position_getter = [this]()
443  {
444  return static_cast<uint64_t>(0);
445  };
446  Parent::position().install(position_getter);
447 
448  std::function<uint64_t()> duration_getter = [this]()
449  {
450  return static_cast<uint64_t>(0);
451  };
452  Parent::duration().install(duration_getter);
453 
454  std::function<bool()> video_type_getter = [this]()
455  {
456  return false;
457  };
458  Parent::is_video_source().install(video_type_getter);
459 
460  std::function<bool()> audio_type_getter = [this]()
461  {
462  return false;
463  };
464  Parent::is_audio_source().install(audio_type_getter);
465 }
466 
467 template<typename Parent>
468 std::shared_ptr<media::TrackList> media::PlayerImplementation<Parent>::track_list()
469 {
470  return d->track_list;
471 }
472 
473 // TODO: Convert this to be a property instead of sync call
474 template<typename Parent>
476 {
477  return d->config.key;
478 }
479 
480 template<typename Parent>
481 media::video::Sink::Ptr media::PlayerImplementation<Parent>::create_gl_texture_video_sink(std::uint32_t texture_id)
482 {
483  d->engine->create_video_sink(texture_id);
484  return media::video::Sink::Ptr{};
485 }
486 
487 template<typename Parent>
489 {
490  return d->engine->open_resource_for_uri(uri);
491 }
492 
493 template<typename Parent>
495 {
496  return d->engine->open_resource_for_uri(uri, headers);
497 }
498 
499 template<typename Parent>
501 {
502 }
503 
504 template<typename Parent>
506 {
507 }
508 
509 template<typename Parent>
511 {
512  d->engine->play();
513 }
514 
515 template<typename Parent>
517 {
518  d->engine->pause();
519 }
520 
521 template<typename Parent>
523 {
524  std::cout << __PRETTY_FUNCTION__ << std::endl;
525  d->engine->stop();
526 }
527 
528 template<typename Parent>
529 void media::PlayerImplementation<Parent>::seek_to(const std::chrono::microseconds& ms)
530 {
531  d->engine->seek_to(ms);
532 }
533 
534 template<typename Parent>
536 {
537  return d->on_client_disconnected;
538 }
539 
540 template<typename Parent>
542 {
543  Parent::playback_status_changed()(status);
544 }
545 
547 
548 // For linking purposes, we have to make sure that we have all symbols included within the dso.
Private(PlayerImplementation *parent, const media::PlayerImplementation< Parent >::Configuration &config)
virtual bool open_uri(const Track::UriType &uri)
PlayerImplementation(const Configuration &configuration)
std::tuple< Height, Width > Dimensions
Height and Width of a video.
Definition: dimensions.h:139
void emit_playback_status_changed(const Player::PlaybackStatus &status)
virtual Player::PlayerKey key() const
std::shared_ptr< media::NullTrackList > track_list
Definition: bus.h:33
media::PlayerImplementation< Parent >::Configuration config
STL namespace.
std::map< std::string, std::string > HeadersType
Definition: player.h:48
wakelock_clear_t current_wakelock_type() const
media::PlayerImplementation< Parent > * parent
virtual video::Sink::Ptr create_gl_texture_video_sink(std::uint32_t texture_id)
void clear_wakelock(const wakelock_clear_t &wakelock)
media::power::StateController::Lock< media::power::DisplayState >::Ptr display_state_lock
virtual std::shared_ptr< TrackList > track_list()
const core::Signal & on_client_disconnected() const
std::string UriType
Definition: track.h:40
std::function< void(const Engine::State &state)> make_state_change_handler()
std::function< void(const media::Player::PlaybackStatus &status)> make_playback_status_change_handler()
std::function< void()> make_clear_wakelock_functor()
media::power::StateController::Lock< media::power::SystemState >::Ptr system_state_lock
virtual void seek_to(const std::chrono::microseconds &offset)