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 <core/media/service.h>
21 
22 #include "player_implementation.h"
23 #include "service_skeleton.h"
24 #include "util/timeout.h"
25 
26 #include <unistd.h>
27 #include <ctime>
28 
29 #include "client_death_observer.h"
30 #include "engine.h"
32 #include "xesam.h"
33 
34 #include "gstreamer/engine.h"
35 
37 
38 #include <memory>
39 #include <exception>
40 #include <mutex>
41 
42 #define UNUSED __attribute__((unused))
43 
44 namespace media = core::ubuntu::media;
45 namespace dbus = core::dbus;
46 
47 using namespace std;
48 
49 template<typename Parent>
51  public std::enable_shared_from_this<Private>
52 {
53  enum class wakelock_clear_t
54  {
55  WAKELOCK_CLEAR_INACTIVE,
56  WAKELOCK_CLEAR_DISPLAY,
57  WAKELOCK_CLEAR_SYSTEM,
58  WAKELOCK_CLEAR_INVALID
59  };
60 
61  Private(PlayerImplementation* parent, const media::PlayerImplementation<Parent>::Configuration& config)
62  : parent(parent),
63  config(config),
64  display_state_lock(config.power_state_controller->display_state_lock()),
65  system_state_lock(config.power_state_controller->system_state_lock()),
66  engine(std::make_shared<gstreamer::Engine>(config.key)),
67  track_list(std::make_shared<TrackListImplementation>(
68  config.parent.bus,
69  config.parent.service->add_object_for_path(
70  dbus::types::ObjectPath(config.parent.session->path().as_string() + "/TrackList")),
71  engine->meta_data_extractor(),
72  config.parent.request_context_resolver,
73  config.parent.request_authenticator)),
74  system_wakelock_count(0),
75  display_wakelock_count(0),
76  previous_state(Engine::State::stopped),
77  engine_state_change_connection(engine->state().changed().connect(make_state_change_handler())),
78  engine_playback_status_change_connection(engine->playback_status_changed_signal().connect(make_playback_status_change_handler())),
79  doing_abandon(false)
80  {
81  // Poor man's logging of release/acquire events.
82  display_state_lock->acquired().connect([](media::power::DisplayState state)
83  {
84  MH_INFO("Acquired new display state: %s", state);
85  });
86 
87  display_state_lock->released().connect([](media::power::DisplayState state)
88  {
89  MH_INFO("Released display state: %s", state);
90  });
91 
92  system_state_lock->acquired().connect([](media::power::SystemState state)
93  {
94  MH_INFO("Acquired new system state: %s", state);
95  });
96 
97  system_state_lock->released().connect([](media::power::SystemState state)
98  {
99  MH_INFO("Released system state: %s", state);
100  });
101  }
102 
104  {
105  // Make sure that we don't hold on to the wakelocks if media-hub-server
106  // ever gets restarted manually or automatically
107  clear_wakelocks();
108 
109  // The engine destructor can lead to a stop change state which will
110  // trigger the state change handler. Ensure the handler is not called
111  // by disconnecting the state change signal
112  engine_state_change_connection.disconnect();
113 
114  // The engine destructor can lead to a playback status change which will
115  // trigger the playback status change handler. Ensure the handler is not called
116  // by disconnecting the playback status change signal
117  engine_playback_status_change_connection.disconnect();
118  }
119 
120  std::function<void(const Engine::State& state)> make_state_change_handler()
121  {
122  /*
123  * Wakelock state logic:
124  * PLAYING->READY or PLAYING->PAUSED or PLAYING->STOPPED: delay 4 seconds and try to clear current wakelock type
125  * ANY STATE->PLAYING: request a new wakelock (system or display)
126  */
127  return [this](const Engine::State& state)
128  {
129  MH_DEBUG("Setting state for parent: %s", parent);
130  switch(state)
131  {
132  case Engine::State::ready:
133  {
134  parent->playback_status().set(media::Player::ready);
135  if (previous_state == Engine::State::playing)
136  {
137  timeout(4000, true, make_clear_wakelock_functor());
138  }
139  break;
140  }
141  case Engine::State::playing:
142  {
143  // We update the track metadata prior to updating the playback status.
144  // Some MPRIS clients expect this order of events.
145  time_t now;
146  time(&now);
147  char buf[sizeof("2011-10-08T07:07:09Z")];
148  strftime(buf, sizeof(buf), "%FT%TZ", gmtime(&now));
149  media::Track::MetaData metadata{std::get<1>(engine->track_meta_data().get())};
150  // Setting this with second resolution makes sure that the track_meta_data property changes
151  // and thus the track_meta_data().changed() signal gets sent out over dbus. Otherwise the
152  // Property caching mechanism would prevent this.
153  metadata.set_last_used(std::string{buf});
154  update_mpris_metadata(std::get<0>(engine->track_meta_data().get()), metadata);
155 
156  // And update our playback status.
157  parent->playback_status().set(media::Player::playing);
158  MH_INFO("Requesting power state");
159  request_power_state();
160  break;
161  }
162  case Engine::State::stopped:
163  {
164  parent->playback_status().set(media::Player::stopped);
165  if (previous_state == Engine::State::playing)
166  {
167  timeout(4000, true, make_clear_wakelock_functor());
168  }
169  break;
170  }
171  case Engine::State::paused:
172  {
173  parent->playback_status().set(media::Player::paused);
174  if (previous_state == Engine::State::playing)
175  {
176  timeout(4000, true, make_clear_wakelock_functor());
177  }
178  break;
179  }
180  default:
181  break;
182  };
183 
184  // Keep track of the previous Engine playback state:
185  previous_state = state;
186  };
187  }
188 
189  std::function<void(const media::Player::PlaybackStatus& status)> make_playback_status_change_handler()
190  {
191  return [this](const media::Player::PlaybackStatus& status)
192  {
193  MH_INFO("Emiting playback_status_changed signal: %s", status);
194  parent->emit_playback_status_changed(status);
195  };
196  }
197 
199  {
200  MH_TRACE("");
201  try
202  {
203  if (parent->is_video_source())
204  {
205  if (++display_wakelock_count == 1)
206  {
207  MH_INFO("Requesting new display wakelock.");
208  display_state_lock->request_acquire(media::power::DisplayState::on);
209  MH_INFO("Requested new display wakelock.");
210  }
211  }
212  else
213  {
214  if (++system_wakelock_count == 1)
215  {
216  MH_INFO("Requesting new system wakelock.");
217  system_state_lock->request_acquire(media::power::SystemState::active);
218  MH_INFO("Requested new system wakelock.");
219  }
220  }
221  }
222  catch(const std::exception& e)
223  {
224  MH_WARNING("Failed to request power state: %s", e.what());
225  }
226  }
227 
228  void clear_wakelock(const wakelock_clear_t &wakelock)
229  {
230  MH_TRACE("");
231  try
232  {
233  switch (wakelock)
234  {
235  case wakelock_clear_t::WAKELOCK_CLEAR_INACTIVE:
236  break;
237  case wakelock_clear_t::WAKELOCK_CLEAR_SYSTEM:
238  // Only actually clear the system wakelock once the count reaches zero
239  if (--system_wakelock_count == 0)
240  {
241  MH_INFO("Clearing system wakelock.");
242  system_state_lock->request_release(media::power::SystemState::active);
243  }
244  break;
245  case wakelock_clear_t::WAKELOCK_CLEAR_DISPLAY:
246  // Only actually clear the display wakelock once the count reaches zero
247  if (--display_wakelock_count == 0)
248  {
249  MH_INFO("Clearing display wakelock.");
250  display_state_lock->request_release(media::power::DisplayState::on);
251  }
252  break;
253  case wakelock_clear_t::WAKELOCK_CLEAR_INVALID:
254  default:
255  MH_WARNING("Can't clear invalid wakelock type");
256  }
257  }
258  catch(const std::exception& e)
259  {
260  MH_WARNING("Failed to request clear power state: %s", e.what());
261  }
262  }
263 
265  {
266  return (parent->is_video_source()) ?
267  wakelock_clear_t::WAKELOCK_CLEAR_DISPLAY : wakelock_clear_t::WAKELOCK_CLEAR_SYSTEM;
268  }
269 
271  {
272  // Clear both types of wakelocks (display and system)
273  if (system_wakelock_count.load() > 0)
274  {
275  system_wakelock_count = 1;
276  clear_wakelock(wakelock_clear_t::WAKELOCK_CLEAR_SYSTEM);
277  }
278  if (display_wakelock_count.load() > 0)
279  {
280  display_wakelock_count = 1;
281  clear_wakelock(wakelock_clear_t::WAKELOCK_CLEAR_DISPLAY);
282  }
283  }
284 
285  std::function<void()> make_clear_wakelock_functor()
286  {
287  // Since this functor will be executed on a separate detached thread
288  // the execution of the functor may surpass the lifetime of this Private
289  // object instance. By keeping a weak_ptr to the private object instance
290  // we can check if the object is dead before calling methods on it
291  std::weak_ptr<Private> weak_self{this->shared_from_this()};
292  auto wakelock_type = current_wakelock_type();
293  return [weak_self, wakelock_type] {
294  if (auto self = weak_self.lock())
295  self->clear_wakelock(wakelock_type);
296  };
297  }
298 
300  {
301  engine->reset();
302  }
303 
305  {
306  const Track::UriType uri = track_list->query_uri_for_track(id);
307  if (!uri.empty())
308  {
309  // Using a TrackList for playback, added tracks via add_track(), but open_uri hasn't
310  // been called yet to load a media resource
311  MH_INFO("Calling d->engine->open_resource_for_uri() for first track added only: %s",
312  uri);
313  MH_INFO("\twith a Track::Id: %s", id);
314  static const bool do_pipeline_reset = false;
315  engine->open_resource_for_uri(uri, do_pipeline_reset);
316  }
317  }
318 
320  {
321  const bool has_previous = track_list->has_previous()
322  or parent->Parent::loop_status() != Player::LoopStatus::none;
323  const bool has_next = track_list->has_next()
324  or parent->Parent::loop_status() != Player::LoopStatus::none;
325  const auto n_tracks = track_list->tracks()->size();
326  const bool has_tracks = (n_tracks > 0) ? true : false;
327 
328  MH_INFO("Updating MPRIS TrackList properties:");
329  MH_INFO("\tTracks: %d", n_tracks);
330  MH_INFO("\thas_previous: %d", has_previous);
331  MH_INFO("\thas_next: %d", has_next);
332 
333  // Update properties
334  parent->can_play().set(has_tracks);
335  parent->can_pause().set(has_tracks);
336  parent->can_go_previous().set(has_previous);
337  parent->can_go_next().set(has_next);
338  }
339 
341  const media::Track::MetaData& metadata)
342  {
343  std::string art_uri;
344  static const std::string file_uri_prefix{"file://"};
345  bool is_local_file = false;
346  if (not uri.empty())
347  is_local_file = (uri.substr(0, 7) == file_uri_prefix or uri.at(0) == '/');
348 
349  // If the track has a full image or preview image or is a video and it is a local file,
350  // then use the thumbnailer cache
351  if ( (( metadata.count(tags::PreviewImage::name) > 0
352  and metadata.get(tags::PreviewImage::name) == "true")
353  or ( metadata.count(tags::Image::name) > 0
354  and metadata.get(tags::Image::name) == "true")
355  or parent->is_video_source().get())
356  and is_local_file)
357  {
358  art_uri = "image://thumbnailer/" + uri;
359  }
360  // If all else fails, display a placeholder icon
361  else
362  {
363  art_uri = "file:///usr/share/icons/suru/apps/scalable/music-app-symbolic.svg";
364  }
365 
366  return art_uri;
367  }
368 
369  // Makes sure all relevant metadata fields are set to current data and
370  // will trigger the track_meta_data().changed() signal to go out over dbus
371  void update_mpris_metadata(const media::Track::UriType& uri, const media::Track::MetaData& md)
372  {
373  media::Track::MetaData metadata{md};
374  if (not metadata.is_set(media::Track::MetaData::TrackIdKey))
375  {
376  const std::string current_track = track_list->current();
377  if (not current_track.empty())
378  {
379  const std::size_t last_slash = current_track.find_last_of("/");
380  const std::string track_id = current_track.substr(last_slash + 1);
381  if (not track_id.empty())
382  metadata.set_track_id("/org/mpris/MediaPlayer2/Track/" + track_id);
383  else
384  MH_WARNING("Failed to set MPRIS track id since the id value is NULL");
385  }
386  else
387  MH_WARNING("Failed to set MPRIS track id since the id value is NULL");
388  }
389 
390  if (not metadata.is_set(media::Track::MetaData::TrackArtlUrlKey))
391  metadata.set_art_url(get_uri_for_album_artwork(uri, metadata));
392 
393  if (not metadata.is_set(media::Track::MetaData::TrackLengthKey))
394  {
395  // Duration is in nanoseconds, MPRIS spec requires microseconds
396  metadata.set_track_length(std::to_string(engine->duration().get() / 1000));
397  }
398 
399  parent->meta_data_for_current_track().set(metadata);
400  }
401 
403  {
404  if (not config.parent.player_service)
405  return false;
406 
407  media::ServiceSkeleton* skel {
408  reinterpret_cast<media::ServiceSkeleton*>(config.parent.player_service)
409  };
410  skel->pause_other_sessions(key);
411  return true;
412  }
413 
415  {
416  if (not config.parent.player_service)
417  return false;
418 
419  media::ServiceSkeleton* skel {
420  reinterpret_cast<media::ServiceSkeleton*>(config.parent.player_service)
421  };
422  skel->set_current_player(key);
423  return true;
424  }
425 
426  bool is_current_player() const
427  {
428  if (not config.parent.player_service)
429  return false;
430 
431  media::ServiceSkeleton* skel {
432  reinterpret_cast<media::ServiceSkeleton*>(config.parent.player_service)
433  };
434  return skel->is_current_player(parent->key());
435  }
436 
437  bool is_multimedia_role() const
438  {
439  return parent->audio_stream_role() == media::Player::AudioStreamRole::multimedia;
440  }
441 
443  {
444  if (not config.parent.player_service)
445  return false;
446 
447  media::ServiceSkeleton* skel {
448  reinterpret_cast<media::ServiceSkeleton*>(config.parent.player_service)
449  };
450  skel->reset_current_player();
451  return true;
452  }
453 
454  // Our link back to our parent.
456  // We just store the parameters passed on construction.
458  media::power::StateController::Lock<media::power::DisplayState>::Ptr display_state_lock;
459  media::power::StateController::Lock<media::power::SystemState>::Ptr system_state_lock;
460 
461  std::shared_ptr<Engine> engine;
462  std::shared_ptr<media::TrackListImplementation> track_list;
463  std::atomic<int> system_wakelock_count;
464  std::atomic<int> display_wakelock_count;
465  Engine::State previous_state;
466  core::Signal<> on_client_disconnected;
469  // Prevent the TrackList from auto advancing to the next track
470  std::mutex doing_go_to_track;
471  std::atomic<bool> doing_abandon;
472 };
473 
474 template<typename Parent>
476  : Parent{config.parent},
477  d{std::make_shared<Private>(this, config)}
478 {
479  // Initialize default values for Player interface properties
480  Parent::can_play().set(false);
481  Parent::can_pause().set(false);
482  Parent::can_seek().set(true);
483  Parent::can_go_previous().set(false);
484  Parent::can_go_next().set(false);
485  Parent::is_video_source().set(false);
486  Parent::is_audio_source().set(false);
487  Parent::shuffle().set(false);
488  Parent::playback_rate().set(1.f);
489  Parent::playback_status().set(Player::PlaybackStatus::null);
490  Parent::backend().set(media::AVBackend::get_backend_type());
491  Parent::loop_status().set(Player::LoopStatus::none);
492  Parent::position().set(0);
493  Parent::duration().set(0);
494  Parent::audio_stream_role().set(Player::AudioStreamRole::multimedia);
495  d->engine->audio_stream_role().set(Player::AudioStreamRole::multimedia);
496  Parent::orientation().set(Player::Orientation::rotate0);
497  Parent::lifetime().set(Player::Lifetime::normal);
498  d->engine->lifetime().set(Player::Lifetime::normal);
499 
500  // Make sure that the Position property gets updated from the Engine
501  // every time the client requests position
502  std::function<uint64_t()> position_getter = [this]()
503  {
504  return d->engine->position().get();
505  };
506  Parent::position().install(position_getter);
507 
508  d->engine->position().changed().connect([this](uint64_t position)
509  {
510  d->track_list->on_position_changed(position);
511  });
512 
513  // Make sure that the Duration property gets updated from the Engine
514  // every time the client requests duration
515  std::function<uint64_t()> duration_getter = [this]()
516  {
517  return d->engine->duration().get();
518  };
519  Parent::duration().install(duration_getter);
520 
521  std::function<bool()> video_type_getter = [this]()
522  {
523  return d->engine->is_video_source().get();
524  };
525  Parent::is_video_source().install(video_type_getter);
526 
527  std::function<bool()> audio_type_getter = [this]()
528  {
529  return d->engine->is_audio_source().get();
530  };
531  Parent::is_audio_source().install(audio_type_getter);
532 
533  std::function<bool()> can_go_next_getter = [this]()
534  {
535  // If LoopStatus == playlist, then there is always a next track
536  return d->track_list->has_next() or Parent::loop_status() != Player::LoopStatus::none;
537  };
538  Parent::can_go_next().install(can_go_next_getter);
539 
540  std::function<bool()> can_go_previous_getter = [this]()
541  {
542  // If LoopStatus == playlist, then there is always a next previous
543  return d->track_list->has_previous() or Parent::loop_status() != Player::LoopStatus::none;
544  };
545  Parent::can_go_previous().install(can_go_previous_getter);
546 
547  // When the client changes the loop status, make sure to update the TrackList
548  Parent::loop_status().changed().connect([this](media::Player::LoopStatus loop_status)
549  {
550  MH_INFO("LoopStatus: %s", loop_status);
551  d->track_list->on_loop_status_changed(loop_status);
552  });
553 
554  // When the client changes the shuffle setting, make sure to update the TrackList
555  Parent::shuffle().changed().connect([this](bool shuffle)
556  {
557  d->track_list->on_shuffle_changed(shuffle);
558  });
559 
560  // Make sure that the audio_stream_role property gets updated on the Engine side
561  // whenever the client side sets the role
562  Parent::audio_stream_role().changed().connect([this](media::Player::AudioStreamRole new_role)
563  {
564  d->engine->audio_stream_role().set(new_role);
565  });
566 
567  // When the value of the orientation Property is changed in the Engine by playbin,
568  // update the Player's cached value
569  d->engine->orientation().changed().connect([this](const Player::Orientation& o)
570  {
571  Parent::orientation().set(o);
572  });
573 
574  Parent::lifetime().changed().connect([this](media::Player::Lifetime lifetime)
575  {
576  d->engine->lifetime().set(lifetime);
577  });
578 
579  d->engine->track_meta_data().changed().connect([this, config](
580  const std::tuple<media::Track::UriType, media::Track::MetaData>& md)
581  {
582  d->update_mpris_metadata(std::get<0>(md), std::get<1>(md));
583  });
584 
585  d->engine->about_to_finish_signal().connect([this]()
586  {
587  if (d->doing_abandon)
588  return;
589 
590  // Prevent on_go_to_track from executing as it's not needed in this case. on_go_to_track
591  // (see the lambda below) is only needed when the client explicitly calls next() not during
592  // the about_to_finish condition
593  d->doing_go_to_track.lock();
594 
595  Parent::about_to_finish()();
596 
597  const media::Track::Id prev_track_id = d->track_list->current();
598  // Make sure that the TrackList keeps advancing. The logic for what gets played next,
599  // if anything at all, occurs in TrackListSkeleton::next()
600  const Track::UriType uri = d->track_list->query_uri_for_track(d->track_list->next());
601  if (prev_track_id != d->track_list->current() && !uri.empty())
602  {
603  MH_INFO("Advancing to next track on playbin: %s", uri);
604  static const bool do_pipeline_reset = false;
605  d->engine->open_resource_for_uri(uri, do_pipeline_reset);
606  }
607 
608  d->doing_go_to_track.unlock();
609  });
610 
611  d->engine->client_disconnected_signal().connect([this]()
612  {
613  // If the client disconnects, make sure both wakelock types
614  // are cleared
615  d->clear_wakelocks();
616  d->track_list->reset();
617 
618  // This is not a fatal error but merely a warning that should
619  // be logged
620  if (d->is_multimedia_role() and d->is_current_player())
621  {
622  MH_DEBUG("==== Resetting current player");
623  if (not d->reset_current_player())
624  MH_WARNING("Failed to reset current player");
625  }
626 
627  // And tell the outside world that the client has gone away
628  d->on_client_disconnected();
629  });
630 
631  d->engine->seeked_to_signal().connect([this](uint64_t value)
632  {
633  Parent::seeked_to()(value);
634  });
635 
636  d->engine->on_buffering_changed_signal().connect([this](int value)
637  {
638  Parent::buffering_changed()(value);
639  });
640 
641  d->engine->end_of_stream_signal().connect([this]()
642  {
643  Parent::end_of_stream()();
644  });
645 
646  d->engine->video_dimension_changed_signal().connect([this](const media::video::Dimensions& dimensions)
647  {
648  Parent::video_dimension_changed()(dimensions);
649  });
650 
651  d->engine->error_signal().connect([this](const Player::Error& e)
652  {
653  Parent::error()(e);
654  });
655 
656  d->track_list->on_end_of_tracklist().connect([this]()
657  {
658  if (d->engine->state() != gstreamer::Engine::State::ready
659  && d->engine->state() != gstreamer::Engine::State::stopped)
660  {
661  MH_INFO("End of tracklist reached, stopping playback");
662  const constexpr bool use_main_thread = true;
663  d->engine->stop(use_main_thread);
664  }
665  });
666 
667  d->track_list->on_go_to_track().connect([this](const media::Track::Id& id)
668  {
669  // This lambda needs to be mutually exclusive with the about_to_finish lambda above
670  const bool locked = d->doing_go_to_track.try_lock();
671  // If the try_lock fails, it means that about_to_finish lambda above has it locked and it will
672  // call d->engine->open_resource_for_uri()
673  if (!locked)
674  return;
675 
676  // Store whether we should restore the current playing state after loading the new uri
677  const bool auto_play = Parent::playback_status().get() == media::Player::playing;
678 
679  const Track::UriType uri = d->track_list->query_uri_for_track(id);
680  if (!uri.empty())
681  {
682  MH_INFO("Setting next track on playbin (on_go_to_track signal): %s", uri);
683  MH_INFO("\twith a Track::Id: %s", id);
684  static const bool do_pipeline_reset = true;
685  d->engine->open_resource_for_uri(uri, do_pipeline_reset);
686  }
687 
688  if (auto_play)
689  {
690  MH_DEBUG("Restoring playing state");
691  d->engine->play();
692  }
693 
694  d->doing_go_to_track.unlock();
695  });
696 
697  d->track_list->on_track_added().connect([this](const media::Track::Id& id)
698  {
699  MH_TRACE("** Track was added, handling in PlayerImplementation");
700  if (d->track_list->tracks()->size() == 1)
701  d->open_first_track_from_tracklist(id);
702 
703  d->update_mpris_properties();
704  });
705 
706  d->track_list->on_tracks_added().connect([this](const media::TrackList::ContainerURI& tracks)
707  {
708  MH_TRACE("** Track was added, handling in PlayerImplementation");
709  // If the two sizes are the same, that means the TrackList was previously empty and we need
710  // to open the first track in the TrackList so that is_audio_source() and is_video_source()
711  // will function correctly.
712  if (tracks.size() >= 1 and d->track_list->tracks()->size() == tracks.size())
713  d->open_first_track_from_tracklist(tracks.front());
714 
715  d->update_mpris_properties();
716  });
717 
718  d->track_list->on_track_removed().connect([this](const media::Track::Id&)
719  {
720  d->update_mpris_properties();
721  });
722 
723  d->track_list->on_track_list_reset().connect([this](void)
724  {
725  d->update_mpris_properties();
726  });
727 
728  d->track_list->on_track_changed().connect([this](const media::Track::Id&)
729  {
730  d->update_mpris_properties();
731  });
732 
733  d->track_list->on_track_list_replaced().connect(
735  {
736  d->update_mpris_properties();
737  }
738  );
739 
740  // Everything is setup, we now subscribe to death notifications.
741  std::weak_ptr<Private> wp{d};
742 
743  d->config.client_death_observer->register_for_death_notifications_with_key(config.key);
744  d->config.client_death_observer->on_client_with_key_died().connect([wp](const media::Player::PlayerKey& died)
745  {
746  if (auto sp = wp.lock())
747  {
748  if (sp->doing_abandon)
749  return;
750 
751  if (died != sp->config.key)
752  return;
753 
754  static const std::chrono::milliseconds timeout{1000};
755  media::timeout(timeout.count(), true, [wp]()
756  {
757  if (auto sp = wp.lock())
758  sp->on_client_died();
759  });
760  }
761  });
762 }
763 
764 template<typename Parent>
766 {
767  // Install null getters as these properties may be destroyed
768  // after the engine has been destroyed since they are owned by the
769  // base class.
770  std::function<uint64_t()> position_getter = [this]()
771  {
772  return static_cast<uint64_t>(0);
773  };
774  Parent::position().install(position_getter);
775 
776  std::function<uint64_t()> duration_getter = [this]()
777  {
778  return static_cast<uint64_t>(0);
779  };
780  Parent::duration().install(duration_getter);
781 
782  std::function<bool()> video_type_getter = [this]()
783  {
784  return false;
785  };
786  Parent::is_video_source().install(video_type_getter);
787 
788  std::function<bool()> audio_type_getter = [this]()
789  {
790  return false;
791  };
792  Parent::is_audio_source().install(audio_type_getter);
793 }
794 
795 template<typename Parent>
797 {
798  // No impl for now, as not needed internally.
799  return std::string{};
800 }
801 
802 template<typename Parent>
804 {
805  d->config.client_death_observer->register_for_death_notifications_with_key(d->config.key);
806 }
807 
808 template<typename Parent>
810 {
811  // Signal client disconnection due to abandonment of player
812  d->doing_abandon = true;
813  d->on_client_died();
814 }
815 
816 template<typename Parent>
817 std::shared_ptr<media::TrackList> media::PlayerImplementation<Parent>::track_list()
818 {
819  return d->track_list;
820 }
821 
822 // TODO: Convert this to be a property instead of sync call
823 template<typename Parent>
825 {
826  return d->config.key;
827 }
828 
829 template<typename Parent>
830 media::video::Sink::Ptr media::PlayerImplementation<Parent>::create_gl_texture_video_sink(std::uint32_t texture_id)
831 {
832  d->engine->create_video_sink(texture_id);
833  return media::video::Sink::Ptr{};
834 }
835 
836 template<typename Parent>
838 {
839  d->track_list->reset();
840 
841  // If empty uri, give the same meaning as QMediaPlayer::setMedia("")
842  if (uri.empty())
843  {
844  MH_DEBUG("Resetting current media");
845  return true;
846  }
847 
848  static const bool do_pipeline_reset = false;
849  const bool ret = d->engine->open_resource_for_uri(uri, do_pipeline_reset);
850  // Don't set new track as the current track to play since we're calling open_resource_for_uri above
851  static const bool make_current = false;
852  d->track_list->add_track_with_uri_at(uri, media::TrackList::after_empty_track(), make_current);
853 
854  return ret;
855 }
856 
857 template<typename Parent>
859 {
860  return d->engine->open_resource_for_uri(uri, headers);
861 }
862 
863 template<typename Parent>
865 {
866  d->track_list->next();
867 }
868 
869 template<typename Parent>
871 {
872  d->track_list->previous();
873 }
874 
875 template<typename Parent>
877 {
878  MH_TRACE("");
879  if (d->is_multimedia_role())
880  {
881  MH_DEBUG("==== Pausing all other multimedia player sessions");
882  if (not d->pause_other_players(d->config.key))
883  MH_WARNING("Failed to pause other player sessions");
884 
885  MH_DEBUG("==== Updating the current player");
886  // This player will begin playing so make sure it's the current player. If
887  // this operation fails it is not a fatal condition but should be logged
888  if (not d->update_current_player(d->config.key))
889  MH_WARNING("Failed to update current player");
890  }
891 
892  d->engine->play();
893 }
894 
895 template<typename Parent>
897 {
898  MH_TRACE("");
899  d->engine->pause();
900 }
901 
902 template<typename Parent>
904 {
905  MH_TRACE("");
906  d->engine->stop();
907 }
908 
909 template<typename Parent>
910 void media::PlayerImplementation<Parent>::seek_to(const std::chrono::microseconds& ms)
911 {
912  d->engine->seek_to(ms);
913 }
914 
915 template<typename Parent>
917 {
918  return d->on_client_disconnected;
919 }
920 
921 template<typename Parent>
923 {
924  Parent::playback_status_changed()(status);
925 }
926 
928 
929 // 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)
Definition: bus.h:33
std::shared_ptr< media::TrackListImplementation > track_list
#define MH_INFO(...)
Definition: logger.h:125
media::PlayerImplementation< Parent >::Configuration config
STL namespace.
virtual std::string uuid() const
const core::Signal & on_client_disconnected() const
std::map< std::string, std::string > HeadersType
Definition: player.h:65
std::tuple< std::vector< Track::Id >, Track::Id > ContainerTrackIdTuple
Definition: track_list.h:45
media::PlayerImplementation< Parent > * parent
#define MH_DEBUG(...)
Definition: logger.h:123
#define MH_WARNING(...)
Definition: logger.h:127
virtual video::Sink::Ptr create_gl_texture_video_sink(std::uint32_t texture_id)
static Backend get_backend_type()
Returns the type of audio/video decoding/encoding backend being used.
Definition: backend.cpp:26
void update_mpris_metadata(const media::Track::UriType &uri, const media::Track::MetaData &md)
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()
bool update_current_player(media::Player::PlayerKey key)
#define MH_TRACE(...)
Definition: logger.h:121
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:50
virtual Player::PlayerKey key() const
std::string get_uri_for_album_artwork(const media::Track::UriType &uri, const media::Track::MetaData &metadata)
bool pause_other_players(media::Player::PlayerKey key)
std::vector< Track::UriType > ContainerURI
Definition: track_list.h:44
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)