Music Hub  ..
A session-wide music playback service
player_implementation.cpp
Go to the documentation of this file.
1 /*
2  * Copyright © 2013-2015 Canonical Ltd.
3  *
4  * This program is free software: you can redistribute it and/or modify it
5  * under the terms of the GNU Lesser General Public License version 3,
6  * as published by the Free Software Foundation.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11  * GNU Lesser General Public License for more details.
12  *
13  * You should have received a copy of the GNU Lesser General Public License
14  * along with this program. If not, see <http://www.gnu.org/licenses/>.
15  *
16  * Authored by: Thomas Voß <thomas.voss@canonical.com>
17  * Jim Hodapp <jim.hodapp@canonical.com>
18  */
19 
20 #include "player_implementation.h"
21 #include "util/timeout.h"
22 
23 #include <unistd.h>
24 
25 #include "client_death_observer.h"
26 #include "engine.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<TrackListImplementation>(
62  config.parent.bus,
63  config.parent.service->add_object_for_path(
64  dbus::types::ObjectPath(config.parent.session->path().as_string() + "/TrackList")),
65  engine->meta_data_extractor(),
66  config.parent.request_context_resolver,
67  config.parent.request_authenticator)),
68  system_wakelock_count(0),
69  display_wakelock_count(0),
70  previous_state(Engine::State::stopped),
71  engine_state_change_connection(engine->state().changed().connect(make_state_change_handler())),
72  engine_playback_status_change_connection(engine->playback_status_changed_signal().connect(make_playback_status_change_handler())),
73  doing_abandon(false)
74  {
75  // Poor man's logging of release/acquire events.
76  display_state_lock->acquired().connect([](media::power::DisplayState state)
77  {
78  std::cout << "Acquired new display state: " << state << std::endl;
79  });
80 
81  display_state_lock->released().connect([](media::power::DisplayState state)
82  {
83  std::cout << "Released display state: " << state << std::endl;
84  });
85 
86  system_state_lock->acquired().connect([](media::power::SystemState state)
87  {
88  std::cout << "Acquired new system state: " << state << std::endl;
89  });
90 
91  system_state_lock->released().connect([](media::power::SystemState state)
92  {
93  std::cout << "Released system state: " << state << std::endl;
94  });
95  }
96 
98  {
99  // Make sure that we don't hold on to the wakelocks if media-hub-server
100  // ever gets restarted manually or automatically
101  clear_wakelocks();
102 
103  // The engine destructor can lead to a stop change state which will
104  // trigger the state change handler. Ensure the handler is not called
105  // by disconnecting the state change signal
106  engine_state_change_connection.disconnect();
107 
108  // The engine destructor can lead to a playback status change which will
109  // trigger the playback status change handler. Ensure the handler is not called
110  // by disconnecting the playback status change signal
111  engine_playback_status_change_connection.disconnect();
112  }
113 
114  std::function<void(const Engine::State& state)> make_state_change_handler()
115  {
116  /*
117  * Wakelock state logic:
118  * PLAYING->READY or PLAYING->PAUSED or PLAYING->STOPPED: delay 4 seconds and try to clear current wakelock type
119  * ANY STATE->PLAYING: request a new wakelock (system or display)
120  */
121  return [this](const Engine::State& state)
122  {
123  std::cout << "Setting state for parent: " << parent << std::endl;
124  switch(state)
125  {
126  case Engine::State::ready:
127  {
128  parent->playback_status().set(media::Player::ready);
129  if (previous_state == Engine::State::playing)
130  {
131  timeout(4000, true, make_clear_wakelock_functor());
132  }
133  break;
134  }
135  case Engine::State::playing:
136  {
137  // We update the track meta data prior to updating the playback status.
138  // Some MPRIS clients expect this order of events.
139  parent->meta_data_for_current_track().set(std::get<1>(engine->track_meta_data().get()));
140  // And update our playback status.
141  parent->playback_status().set(media::Player::playing);
142  std::cout << "Requesting power state" << std::endl;
143  request_power_state();
144  break;
145  }
146  case Engine::State::stopped:
147  {
148  parent->playback_status().set(media::Player::stopped);
149  if (previous_state == Engine::State::playing)
150  {
151  timeout(4000, true, make_clear_wakelock_functor());
152  }
153  break;
154  }
155  case Engine::State::paused:
156  {
157  parent->playback_status().set(media::Player::paused);
158  if (previous_state == Engine::State::playing)
159  {
160  timeout(4000, true, make_clear_wakelock_functor());
161  }
162  break;
163  }
164  default:
165  break;
166  };
167 
168  // Keep track of the previous Engine playback state:
169  previous_state = state;
170  };
171  }
172 
173  std::function<void(const media::Player::PlaybackStatus& status)> make_playback_status_change_handler()
174  {
175  return [this](const media::Player::PlaybackStatus& status)
176  {
177  std::cout << "Emiting playback_status_changed signal: " << status << std::endl;
178  parent->emit_playback_status_changed(status);
179  };
180  }
181 
183  {
184  std::cout << __PRETTY_FUNCTION__ << std::endl;
185  try
186  {
187  if (parent->is_video_source())
188  {
189  if (++display_wakelock_count == 1)
190  {
191  std::cout << "Requesting new display wakelock." << std::endl;
192  display_state_lock->request_acquire(media::power::DisplayState::on);
193  std::cout << "Requested new display wakelock." << std::endl;
194  }
195  }
196  else
197  {
198  if (++system_wakelock_count == 1)
199  {
200  std::cout << "Requesting new system wakelock." << std::endl;
201  system_state_lock->request_acquire(media::power::SystemState::active);
202  std::cout << "Requested new system wakelock." << std::endl;
203  }
204  }
205  }
206  catch(const std::exception& e)
207  {
208  std::cerr << "Warning: failed to request power state: ";
209  std::cerr << e.what() << std::endl;
210  }
211  }
212 
213  void clear_wakelock(const wakelock_clear_t &wakelock)
214  {
215  cout << __PRETTY_FUNCTION__ << endl;
216  try
217  {
218  switch (wakelock)
219  {
220  case wakelock_clear_t::WAKELOCK_CLEAR_INACTIVE:
221  break;
222  case wakelock_clear_t::WAKELOCK_CLEAR_SYSTEM:
223  // Only actually clear the system wakelock once the count reaches zero
224  if (--system_wakelock_count == 0)
225  {
226  std::cout << "Clearing system wakelock." << std::endl;
227  system_state_lock->request_release(media::power::SystemState::active);
228  }
229  break;
230  case wakelock_clear_t::WAKELOCK_CLEAR_DISPLAY:
231  // Only actually clear the display wakelock once the count reaches zero
232  if (--display_wakelock_count == 0)
233  {
234  std::cout << "Clearing display wakelock." << std::endl;
235  display_state_lock->request_release(media::power::DisplayState::on);
236  }
237  break;
238  case wakelock_clear_t::WAKELOCK_CLEAR_INVALID:
239  default:
240  cerr << "Can't clear invalid wakelock type" << endl;
241  }
242  }
243  catch(const std::exception& e)
244  {
245  std::cerr << "Warning: failed to clear power state: ";
246  std::cerr << e.what() << std::endl;
247  }
248  }
249 
251  {
252  return (parent->is_video_source()) ?
253  wakelock_clear_t::WAKELOCK_CLEAR_DISPLAY : wakelock_clear_t::WAKELOCK_CLEAR_SYSTEM;
254  }
255 
257  {
258  // Clear both types of wakelocks (display and system)
259  if (system_wakelock_count.load() > 0)
260  {
261  system_wakelock_count = 1;
262  clear_wakelock(wakelock_clear_t::WAKELOCK_CLEAR_SYSTEM);
263  }
264  if (display_wakelock_count.load() > 0)
265  {
266  display_wakelock_count = 1;
267  clear_wakelock(wakelock_clear_t::WAKELOCK_CLEAR_DISPLAY);
268  }
269  }
270 
271  std::function<void()> make_clear_wakelock_functor()
272  {
273  // Since this functor will be executed on a separate detached thread
274  // the execution of the functor may surpass the lifetime of this Private
275  // object instance. By keeping a weak_ptr to the private object instance
276  // we can check if the object is dead before calling methods on it
277  std::weak_ptr<Private> weak_self{this->shared_from_this()};
278  auto wakelock_type = current_wakelock_type();
279  return [weak_self, wakelock_type] {
280  if (auto self = weak_self.lock())
281  self->clear_wakelock(wakelock_type);
282  };
283  }
284 
286  {
287  engine->reset();
288  }
289 
291  {
292  const Track::UriType uri = track_list->query_uri_for_track(id);
293  if (!uri.empty())
294  {
295  // Using a TrackList for playback, added tracks via add_track(), but open_uri hasn't been called yet
296  // to load a media resource
297  std::cout << "Calling d->engine->open_resource_for_uri() for first track added only: " << uri << std::endl;
298  std::cout << "\twith a Track::Id: " << id << std::endl;
299  static const bool do_pipeline_reset = false;
300  engine->open_resource_for_uri(uri, do_pipeline_reset);
301  }
302  }
303 
304  // Our link back to our parent.
306  // We just store the parameters passed on construction.
308  media::power::StateController::Lock<media::power::DisplayState>::Ptr display_state_lock;
309  media::power::StateController::Lock<media::power::SystemState>::Ptr system_state_lock;
310 
311  std::shared_ptr<Engine> engine;
312  std::shared_ptr<media::TrackListImplementation> track_list;
313  std::atomic<int> system_wakelock_count;
314  std::atomic<int> display_wakelock_count;
315  Engine::State previous_state;
316  core::Signal<> on_client_disconnected;
319  // Prevent the TrackList from auto advancing to the next track
320  std::mutex doing_go_to_track;
321  std::atomic<bool> doing_abandon;
322 };
323 
324 template<typename Parent>
326  : Parent{config.parent},
327  d{std::make_shared<Private>(this, config)}
328 {
329  // Initialize default values for Player interface properties
330  Parent::can_play().set(true);
331  Parent::can_pause().set(true);
332  Parent::can_seek().set(true);
333  Parent::can_go_previous().set(false);
334  Parent::can_go_next().set(false);
335  Parent::is_video_source().set(false);
336  Parent::is_audio_source().set(false);
337  Parent::shuffle().set(false);
338  Parent::playback_rate().set(1.f);
339  Parent::playback_status().set(Player::PlaybackStatus::null);
340  Parent::loop_status().set(Player::LoopStatus::none);
341  Parent::position().set(0);
342  Parent::duration().set(0);
343  Parent::audio_stream_role().set(Player::AudioStreamRole::multimedia);
344  d->engine->audio_stream_role().set(Player::AudioStreamRole::multimedia);
345  Parent::orientation().set(Player::Orientation::rotate0);
346  Parent::lifetime().set(Player::Lifetime::normal);
347  d->engine->lifetime().set(Player::Lifetime::normal);
348 
349  // Make sure that the Position property gets updated from the Engine
350  // every time the client requests position
351  std::function<uint64_t()> position_getter = [this]()
352  {
353  return d->engine->position().get();
354  };
355  Parent::position().install(position_getter);
356 
357  // Make sure that the Duration property gets updated from the Engine
358  // every time the client requests duration
359  std::function<uint64_t()> duration_getter = [this]()
360  {
361  return d->engine->duration().get();
362  };
363  Parent::duration().install(duration_getter);
364 
365  std::function<bool()> video_type_getter = [this]()
366  {
367  return d->engine->is_video_source().get();
368  };
369  Parent::is_video_source().install(video_type_getter);
370 
371  std::function<bool()> audio_type_getter = [this]()
372  {
373  return d->engine->is_audio_source().get();
374  };
375  Parent::is_audio_source().install(audio_type_getter);
376 
377  std::function<bool()> can_go_next_getter = [this]()
378  {
379  return d->track_list->has_next();
380  };
381  Parent::can_go_next().install(can_go_next_getter);
382 
383  std::function<bool()> can_go_previous_getter = [this]()
384  {
385  return d->track_list->has_previous();
386  };
387  Parent::can_go_previous().install(can_go_previous_getter);
388 
389  // When the client changes the loop status, make sure to update the TrackList
390  Parent::loop_status().changed().connect([this](media::Player::LoopStatus loop_status)
391  {
392  std::cout << "LoopStatus: " << loop_status << std::endl;
393  d->track_list->on_loop_status_changed(loop_status);
394  });
395 
396  // When the client changes the shuffle setting, make sure to update the TrackList
397  Parent::shuffle().changed().connect([this](bool shuffle)
398  {
399  d->track_list->on_shuffle_changed(shuffle);
400  });
401 
402  // Make sure that the audio_stream_role property gets updated on the Engine side
403  // whenever the client side sets the role
404  Parent::audio_stream_role().changed().connect([this](media::Player::AudioStreamRole new_role)
405  {
406  d->engine->audio_stream_role().set(new_role);
407  });
408 
409  // When the value of the orientation Property is changed in the Engine by playbin,
410  // update the Player's cached value
411  d->engine->orientation().changed().connect([this](const Player::Orientation& o)
412  {
413  Parent::orientation().set(o);
414  });
415 
416  Parent::lifetime().changed().connect([this](media::Player::Lifetime lifetime)
417  {
418  d->engine->lifetime().set(lifetime);
419  });
420 
421  d->engine->about_to_finish_signal().connect([this]()
422  {
423  if (d->doing_abandon)
424  return;
425 
426  // Prevent on_go_to_track from executing as it's not needed in this case. on_go_to_track
427  // (see the lambda below) is only needed when the client explicitly calls next() not during
428  // the about_to_finish condition
429  d->doing_go_to_track.lock();
430 
431  Parent::about_to_finish()();
432 
433  const media::Track::Id prev_track_id = d->track_list->current();
434  // Make sure that the TrackList keeps advancing. The logic for what gets played next,
435  // if anything at all, occurs in TrackListSkeleton::next()
436  const Track::UriType uri = d->track_list->query_uri_for_track(d->track_list->next());
437  if (prev_track_id != d->track_list->current() && !uri.empty())
438  {
439  std::cout << "Advancing to next track on playbin: " << uri << std::endl;
440  static const bool do_pipeline_reset = false;
441  d->engine->open_resource_for_uri(uri, do_pipeline_reset);
442  }
443 
444  d->doing_go_to_track.unlock();
445  });
446 
447  d->engine->client_disconnected_signal().connect([this]()
448  {
449  // If the client disconnects, make sure both wakelock types
450  // are cleared
451  d->clear_wakelocks();
452  d->track_list->reset();
453  // And tell the outside world that the client has gone away
454  d->on_client_disconnected();
455  });
456 
457  d->engine->seeked_to_signal().connect([this](uint64_t value)
458  {
459  Parent::seeked_to()(value);
460  });
461 
462  d->engine->end_of_stream_signal().connect([this]()
463  {
464  Parent::end_of_stream()();
465  });
466 
467  d->engine->video_dimension_changed_signal().connect([this](const media::video::Dimensions& dimensions)
468  {
469  Parent::video_dimension_changed()(dimensions);
470  });
471 
472  d->engine->error_signal().connect([this](const Player::Error& e)
473  {
474  Parent::error()(e);
475  });
476 
477  d->track_list->on_end_of_tracklist().connect([this]()
478  {
479  if (d->engine->state() != gstreamer::Engine::State::ready
480  && d->engine->state() != gstreamer::Engine::State::stopped)
481  {
482  std::cout << "End of tracklist reached, stopping playback" << std::endl;
483  d->engine->stop();
484  }
485  });
486 
487 
488  d->track_list->on_go_to_track().connect([this](std::pair<const media::Track::Id, bool> p)
489  {
490  // This lambda needs to be mutually exclusive with the about_to_finish lambda above
491  const bool locked = d->doing_go_to_track.try_lock();
492  // If the try_lock fails, it means that about_to_finish lambda above has it locked and it will
493  // call d->engine->open_resource_for_uri()
494  if (!locked)
495  return;
496 
497  const media::Track::Id id = p.first;
498  const bool toggle_player_state = p.second;
499 
500  if (toggle_player_state)
501  d->engine->stop();
502 
503  const Track::UriType uri = d->track_list->query_uri_for_track(id);
504  if (!uri.empty())
505  {
506  std::cout << "Setting next track on playbin (on_go_to_track signal): " << uri << std::endl;
507  std::cout << "\twith a Track::Id: " << id << std::endl;
508  static const bool do_pipeline_reset = true;
509  d->engine->open_resource_for_uri(uri, do_pipeline_reset);
510  }
511 
512  if (toggle_player_state)
513  d->engine->play();
514 
515  d->doing_go_to_track.unlock();
516  });
517 
518  d->track_list->on_track_added().connect([this](const media::Track::Id& id)
519  {
520  std::cout << "** Track was added, handling in PlayerImplementation" << std::endl;
521  if (d->track_list->tracks()->size() == 1)
522  d->open_first_track_from_tracklist(id);
523  });
524 
525  // Everything is setup, we now subscribe to death notifications.
526  std::weak_ptr<Private> wp{d};
527 
528  d->config.client_death_observer->register_for_death_notifications_with_key(config.key);
529  d->config.client_death_observer->on_client_with_key_died().connect([wp](const media::Player::PlayerKey& died)
530  {
531  if (auto sp = wp.lock())
532  {
533  if (sp->doing_abandon)
534  return;
535 
536  if (died != sp->config.key)
537  return;
538 
539  static const std::chrono::milliseconds timeout{1000};
540  media::timeout(timeout.count(), true, [wp]()
541  {
542  if (auto sp = wp.lock())
543  sp->on_client_died();
544  });
545  }
546  });
547 }
548 
549 template<typename Parent>
551 {
552  // Install null getters as these properties may be destroyed
553  // after the engine has been destroyed since they are owned by the
554  // base class.
555  std::function<uint64_t()> position_getter = [this]()
556  {
557  return static_cast<uint64_t>(0);
558  };
559  Parent::position().install(position_getter);
560 
561  std::function<uint64_t()> duration_getter = [this]()
562  {
563  return static_cast<uint64_t>(0);
564  };
565  Parent::duration().install(duration_getter);
566 
567  std::function<bool()> video_type_getter = [this]()
568  {
569  return false;
570  };
571  Parent::is_video_source().install(video_type_getter);
572 
573  std::function<bool()> audio_type_getter = [this]()
574  {
575  return false;
576  };
577  Parent::is_audio_source().install(audio_type_getter);
578 }
579 
580 template<typename Parent>
582 {
583  // No impl for now, as not needed internally.
584  return std::string{};
585 }
586 
587 template<typename Parent>
589 {
590  d->config.client_death_observer->register_for_death_notifications_with_key(d->config.key);
591 }
592 
593 template<typename Parent>
595 {
596  // Signal client disconnection due to abandonment of player
597  d->doing_abandon = true;
598  d->on_client_died();
599 }
600 
601 template<typename Parent>
602 std::shared_ptr<media::TrackList> media::PlayerImplementation<Parent>::track_list()
603 {
604  return d->track_list;
605 }
606 
607 // TODO: Convert this to be a property instead of sync call
608 template<typename Parent>
610 {
611  return d->config.key;
612 }
613 
614 template<typename Parent>
615 media::video::Sink::Ptr media::PlayerImplementation<Parent>::create_gl_texture_video_sink(std::uint32_t texture_id)
616 {
617  d->engine->create_video_sink(texture_id);
618  return media::video::Sink::Ptr{};
619 }
620 
621 template<typename Parent>
623 {
624  d->track_list->reset();
625  const bool ret = d->engine->open_resource_for_uri(uri, false);
626  // Don't set new track as the current track to play since we're calling open_resource_for_uri above
627  static const bool make_current = false;
628  d->track_list->add_track_with_uri_at(uri, media::TrackList::after_empty_track(), make_current);
629  return ret;
630 }
631 
632 template<typename Parent>
634 {
635  return d->engine->open_resource_for_uri(uri, headers);
636 }
637 
638 template<typename Parent>
640 {
641  d->track_list->next();
642 }
643 
644 template<typename Parent>
646 {
647  d->track_list->previous();
648 }
649 
650 template<typename Parent>
652 {
653  d->engine->play();
654 }
655 
656 template<typename Parent>
658 {
659  d->engine->pause();
660 }
661 
662 template<typename Parent>
664 {
665  std::cout << __PRETTY_FUNCTION__ << std::endl;
666  d->engine->stop();
667 }
668 
669 template<typename Parent>
670 void media::PlayerImplementation<Parent>::seek_to(const std::chrono::microseconds& ms)
671 {
672  d->engine->seek_to(ms);
673 }
674 
675 template<typename Parent>
677 {
678  return d->on_client_disconnected;
679 }
680 
681 template<typename Parent>
683 {
684  Parent::playback_status_changed()(status);
685 }
686 
688 
689 // 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
Definition: bus.h:33
std::shared_ptr< media::TrackListImplementation > track_list
media::PlayerImplementation< Parent >::Configuration config
STL namespace.
std::map< std::string, std::string > HeadersType
Definition: player.h:49
wakelock_clear_t current_wakelock_type() const
media::PlayerImplementation< Parent > * parent
virtual std::string uuid() const
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
void open_first_track_from_tracklist(const media::Track::Id &id)
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()
static const Track::Id & after_empty_track()
Definition: track_list.cpp:23
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)