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>()),
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  // Otherwise we'll try and see if we can look up the album art online through
361  // the dash's artwork proxy
362  else if (metadata.is_set(xesam::Album::name) or metadata.is_set(xesam::Artist::name))
363  {
364  if (metadata.is_set(xesam::Album::name) and metadata.is_set(xesam::Artist::name))
365  {
366  art_uri = "image://albumart/artist=" + metadata.encode(xesam::Artist::name)
367  + "&album=" + metadata.encode(xesam::Album::name);
368  }
369  else
370  {
371  art_uri = "image://albumart/";
372  if (metadata.is_set(xesam::Artist::name))
373  art_uri += "artist=" + metadata.encode(xesam::Artist::name);
374  else if (metadata.is_set(xesam::Album::name))
375  art_uri += "album=" + metadata.encode(xesam::Album::name);
376  }
377  }
378  // If all else fails, display a placeholder icon
379  else
380  {
381  art_uri = "file:///usr/lib/arm-linux-gnueabihf/unity-scopes/mediascanner-music/album_missing.svg";
382  }
383 
384  return art_uri;
385  }
386 
387  // Makes sure all relevant metadata fields are set to current data and
388  // will trigger the track_meta_data().changed() signal to go out over dbus
389  void update_mpris_metadata(const media::Track::UriType& uri, const media::Track::MetaData& md)
390  {
391  media::Track::MetaData metadata{md};
392  if (not metadata.is_set(media::Track::MetaData::TrackIdKey))
393  {
394  const std::string current_track = track_list->current();
395  if (not current_track.empty())
396  {
397  const std::size_t last_slash = current_track.find_last_of("/");
398  const std::string track_id = current_track.substr(last_slash + 1);
399  if (not track_id.empty())
400  metadata.set_track_id("/org/mpris/MediaPlayer2/Track/" + track_id);
401  else
402  MH_WARNING("Failed to set MPRIS track id since the id value is NULL");
403  }
404  else
405  MH_WARNING("Failed to set MPRIS track id since the id value is NULL");
406  }
407 
408  if (not metadata.is_set(media::Track::MetaData::TrackArtlUrlKey))
409  metadata.set_art_url(get_uri_for_album_artwork(uri, metadata));
410 
411  if (not metadata.is_set(media::Track::MetaData::TrackLengthKey))
412  {
413  // Duration is in nanoseconds, MPRIS spec requires microseconds
414  metadata.set_track_length(std::to_string(engine->duration().get() / 1000));
415  }
416 
417  parent->meta_data_for_current_track().set(metadata);
418  }
419 
421  {
422  if (not config.parent.player_service)
423  return false;
424 
425  media::ServiceSkeleton* skel {
426  reinterpret_cast<media::ServiceSkeleton*>(config.parent.player_service)
427  };
428  skel->pause_other_sessions(key);
429  return true;
430  }
431 
433  {
434  if (not config.parent.player_service)
435  return false;
436 
437  media::ServiceSkeleton* skel {
438  reinterpret_cast<media::ServiceSkeleton*>(config.parent.player_service)
439  };
440  skel->set_current_player(key);
441  return true;
442  }
443 
444  bool is_current_player() const
445  {
446  if (not config.parent.player_service)
447  return false;
448 
449  media::ServiceSkeleton* skel {
450  reinterpret_cast<media::ServiceSkeleton*>(config.parent.player_service)
451  };
452  return skel->is_current_player(parent->key());
453  }
454 
455  bool is_multimedia_role() const
456  {
457  return parent->audio_stream_role() == media::Player::AudioStreamRole::multimedia;
458  }
459 
461  {
462  if (not config.parent.player_service)
463  return false;
464 
465  media::ServiceSkeleton* skel {
466  reinterpret_cast<media::ServiceSkeleton*>(config.parent.player_service)
467  };
468  skel->reset_current_player();
469  return true;
470  }
471 
472  // Our link back to our parent.
474  // We just store the parameters passed on construction.
476  media::power::StateController::Lock<media::power::DisplayState>::Ptr display_state_lock;
477  media::power::StateController::Lock<media::power::SystemState>::Ptr system_state_lock;
478 
479  std::shared_ptr<Engine> engine;
480  std::shared_ptr<media::TrackListImplementation> track_list;
481  std::atomic<int> system_wakelock_count;
482  std::atomic<int> display_wakelock_count;
483  Engine::State previous_state;
484  core::Signal<> on_client_disconnected;
487  // Prevent the TrackList from auto advancing to the next track
488  std::mutex doing_go_to_track;
489  std::atomic<bool> doing_abandon;
490 };
491 
492 template<typename Parent>
494  : Parent{config.parent},
495  d{std::make_shared<Private>(this, config)}
496 {
497  // Initialize default values for Player interface properties
498  Parent::can_play().set(false);
499  Parent::can_pause().set(false);
500  Parent::can_seek().set(true);
501  Parent::can_go_previous().set(false);
502  Parent::can_go_next().set(false);
503  Parent::is_video_source().set(false);
504  Parent::is_audio_source().set(false);
505  Parent::shuffle().set(false);
506  Parent::playback_rate().set(1.f);
507  Parent::playback_status().set(Player::PlaybackStatus::null);
508  Parent::backend().set(media::AVBackend::get_backend_type());
509  Parent::loop_status().set(Player::LoopStatus::none);
510  Parent::position().set(0);
511  Parent::duration().set(0);
512  Parent::audio_stream_role().set(Player::AudioStreamRole::multimedia);
513  d->engine->audio_stream_role().set(Player::AudioStreamRole::multimedia);
514  Parent::orientation().set(Player::Orientation::rotate0);
515  Parent::lifetime().set(Player::Lifetime::normal);
516  d->engine->lifetime().set(Player::Lifetime::normal);
517 
518  // Make sure that the Position property gets updated from the Engine
519  // every time the client requests position
520  std::function<uint64_t()> position_getter = [this]()
521  {
522  return d->engine->position().get();
523  };
524  Parent::position().install(position_getter);
525 
526  d->engine->position().changed().connect([this](uint64_t position)
527  {
528  d->track_list->on_position_changed(position);
529  });
530 
531  // Make sure that the Duration property gets updated from the Engine
532  // every time the client requests duration
533  std::function<uint64_t()> duration_getter = [this]()
534  {
535  return d->engine->duration().get();
536  };
537  Parent::duration().install(duration_getter);
538 
539  std::function<bool()> video_type_getter = [this]()
540  {
541  return d->engine->is_video_source().get();
542  };
543  Parent::is_video_source().install(video_type_getter);
544 
545  std::function<bool()> audio_type_getter = [this]()
546  {
547  return d->engine->is_audio_source().get();
548  };
549  Parent::is_audio_source().install(audio_type_getter);
550 
551  std::function<bool()> can_go_next_getter = [this]()
552  {
553  // If LoopStatus == playlist, then there is always a next track
554  return d->track_list->has_next() or Parent::loop_status() != Player::LoopStatus::none;
555  };
556  Parent::can_go_next().install(can_go_next_getter);
557 
558  std::function<bool()> can_go_previous_getter = [this]()
559  {
560  // If LoopStatus == playlist, then there is always a next previous
561  return d->track_list->has_previous() or Parent::loop_status() != Player::LoopStatus::none;
562  };
563  Parent::can_go_previous().install(can_go_previous_getter);
564 
565  // When the client changes the loop status, make sure to update the TrackList
566  Parent::loop_status().changed().connect([this](media::Player::LoopStatus loop_status)
567  {
568  MH_INFO("LoopStatus: %s", loop_status);
569  d->track_list->on_loop_status_changed(loop_status);
570  });
571 
572  // When the client changes the shuffle setting, make sure to update the TrackList
573  Parent::shuffle().changed().connect([this](bool shuffle)
574  {
575  d->track_list->on_shuffle_changed(shuffle);
576  });
577 
578  // Make sure that the audio_stream_role property gets updated on the Engine side
579  // whenever the client side sets the role
580  Parent::audio_stream_role().changed().connect([this](media::Player::AudioStreamRole new_role)
581  {
582  d->engine->audio_stream_role().set(new_role);
583  });
584 
585  // When the value of the orientation Property is changed in the Engine by playbin,
586  // update the Player's cached value
587  d->engine->orientation().changed().connect([this](const Player::Orientation& o)
588  {
589  Parent::orientation().set(o);
590  });
591 
592  Parent::lifetime().changed().connect([this](media::Player::Lifetime lifetime)
593  {
594  d->engine->lifetime().set(lifetime);
595  });
596 
597  d->engine->track_meta_data().changed().connect([this, config](
598  const std::tuple<media::Track::UriType, media::Track::MetaData>& md)
599  {
600  d->update_mpris_metadata(std::get<0>(md), std::get<1>(md));
601  });
602 
603  d->engine->about_to_finish_signal().connect([this]()
604  {
605  if (d->doing_abandon)
606  return;
607 
608  // Prevent on_go_to_track from executing as it's not needed in this case. on_go_to_track
609  // (see the lambda below) is only needed when the client explicitly calls next() not during
610  // the about_to_finish condition
611  d->doing_go_to_track.lock();
612 
613  Parent::about_to_finish()();
614 
615  const media::Track::Id prev_track_id = d->track_list->current();
616  // Make sure that the TrackList keeps advancing. The logic for what gets played next,
617  // if anything at all, occurs in TrackListSkeleton::next()
618  const Track::UriType uri = d->track_list->query_uri_for_track(d->track_list->next());
619  if (prev_track_id != d->track_list->current() && !uri.empty())
620  {
621  MH_INFO("Advancing to next track on playbin: %s", uri);
622  static const bool do_pipeline_reset = false;
623  d->engine->open_resource_for_uri(uri, do_pipeline_reset);
624  }
625 
626  d->doing_go_to_track.unlock();
627  });
628 
629  d->engine->client_disconnected_signal().connect([this]()
630  {
631  // If the client disconnects, make sure both wakelock types
632  // are cleared
633  d->clear_wakelocks();
634  d->track_list->reset();
635 
636  // This is not a fatal error but merely a warning that should
637  // be logged
638  if (d->is_multimedia_role() and d->is_current_player())
639  {
640  MH_DEBUG("==== Resetting current player");
641  if (not d->reset_current_player())
642  MH_WARNING("Failed to reset current player");
643  }
644 
645  // And tell the outside world that the client has gone away
646  d->on_client_disconnected();
647  });
648 
649  d->engine->seeked_to_signal().connect([this](uint64_t value)
650  {
651  Parent::seeked_to()(value);
652  });
653 
654  d->engine->on_buffering_changed_signal().connect([this](int value)
655  {
656  Parent::buffering_changed()(value);
657  });
658 
659  d->engine->end_of_stream_signal().connect([this]()
660  {
661  Parent::end_of_stream()();
662  });
663 
664  d->engine->video_dimension_changed_signal().connect([this](const media::video::Dimensions& dimensions)
665  {
666  Parent::video_dimension_changed()(dimensions);
667  });
668 
669  d->engine->error_signal().connect([this](const Player::Error& e)
670  {
671  Parent::error()(e);
672  });
673 
674  d->track_list->on_end_of_tracklist().connect([this]()
675  {
676  if (d->engine->state() != gstreamer::Engine::State::ready
677  && d->engine->state() != gstreamer::Engine::State::stopped)
678  {
679  MH_INFO("End of tracklist reached, stopping playback");
680  const constexpr bool use_main_thread = true;
681  d->engine->stop(use_main_thread);
682  }
683  });
684 
685  d->track_list->on_go_to_track().connect([this](const media::Track::Id& id)
686  {
687  // This lambda needs to be mutually exclusive with the about_to_finish lambda above
688  const bool locked = d->doing_go_to_track.try_lock();
689  // If the try_lock fails, it means that about_to_finish lambda above has it locked and it will
690  // call d->engine->open_resource_for_uri()
691  if (!locked)
692  return;
693 
694  // Store whether we should restore the current playing state after loading the new uri
695  const bool auto_play = Parent::playback_status().get() == media::Player::playing;
696 
697  const Track::UriType uri = d->track_list->query_uri_for_track(id);
698  if (!uri.empty())
699  {
700  MH_INFO("Setting next track on playbin (on_go_to_track signal): %s", uri);
701  MH_INFO("\twith a Track::Id: %s", id);
702  static const bool do_pipeline_reset = true;
703  d->engine->open_resource_for_uri(uri, do_pipeline_reset);
704  }
705 
706  if (auto_play)
707  {
708  MH_DEBUG("Restoring playing state");
709  d->engine->play();
710  }
711 
712  d->doing_go_to_track.unlock();
713  });
714 
715  d->track_list->on_track_added().connect([this](const media::Track::Id& id)
716  {
717  MH_TRACE("** Track was added, handling in PlayerImplementation");
718  if (d->track_list->tracks()->size() == 1)
719  d->open_first_track_from_tracklist(id);
720 
721  d->update_mpris_properties();
722  });
723 
724  d->track_list->on_tracks_added().connect([this](const media::TrackList::ContainerURI& tracks)
725  {
726  MH_TRACE("** Track was added, handling in PlayerImplementation");
727  // If the two sizes are the same, that means the TrackList was previously empty and we need
728  // to open the first track in the TrackList so that is_audio_source() and is_video_source()
729  // will function correctly.
730  if (tracks.size() >= 1 and d->track_list->tracks()->size() == tracks.size())
731  d->open_first_track_from_tracklist(tracks.front());
732 
733  d->update_mpris_properties();
734  });
735 
736  d->track_list->on_track_removed().connect([this](const media::Track::Id&)
737  {
738  d->update_mpris_properties();
739  });
740 
741  d->track_list->on_track_list_reset().connect([this](void)
742  {
743  d->update_mpris_properties();
744  });
745 
746  d->track_list->on_track_changed().connect([this](const media::Track::Id&)
747  {
748  d->update_mpris_properties();
749  });
750 
751  d->track_list->on_track_list_replaced().connect(
753  {
754  d->update_mpris_properties();
755  }
756  );
757 
758  // Everything is setup, we now subscribe to death notifications.
759  std::weak_ptr<Private> wp{d};
760 
761  d->config.client_death_observer->register_for_death_notifications_with_key(config.key);
762  d->config.client_death_observer->on_client_with_key_died().connect([wp](const media::Player::PlayerKey& died)
763  {
764  if (auto sp = wp.lock())
765  {
766  if (sp->doing_abandon)
767  return;
768 
769  if (died != sp->config.key)
770  return;
771 
772  static const std::chrono::milliseconds timeout{1000};
773  media::timeout(timeout.count(), true, [wp]()
774  {
775  if (auto sp = wp.lock())
776  sp->on_client_died();
777  });
778  }
779  });
780 }
781 
782 template<typename Parent>
784 {
785  // Install null getters as these properties may be destroyed
786  // after the engine has been destroyed since they are owned by the
787  // base class.
788  std::function<uint64_t()> position_getter = [this]()
789  {
790  return static_cast<uint64_t>(0);
791  };
792  Parent::position().install(position_getter);
793 
794  std::function<uint64_t()> duration_getter = [this]()
795  {
796  return static_cast<uint64_t>(0);
797  };
798  Parent::duration().install(duration_getter);
799 
800  std::function<bool()> video_type_getter = [this]()
801  {
802  return false;
803  };
804  Parent::is_video_source().install(video_type_getter);
805 
806  std::function<bool()> audio_type_getter = [this]()
807  {
808  return false;
809  };
810  Parent::is_audio_source().install(audio_type_getter);
811 }
812 
813 template<typename Parent>
815 {
816  // No impl for now, as not needed internally.
817  return std::string{};
818 }
819 
820 template<typename Parent>
822 {
823  d->config.client_death_observer->register_for_death_notifications_with_key(d->config.key);
824 }
825 
826 template<typename Parent>
828 {
829  // Signal client disconnection due to abandonment of player
830  d->doing_abandon = true;
831  d->on_client_died();
832 }
833 
834 template<typename Parent>
835 std::shared_ptr<media::TrackList> media::PlayerImplementation<Parent>::track_list()
836 {
837  return d->track_list;
838 }
839 
840 // TODO: Convert this to be a property instead of sync call
841 template<typename Parent>
843 {
844  return d->config.key;
845 }
846 
847 template<typename Parent>
848 media::video::Sink::Ptr media::PlayerImplementation<Parent>::create_gl_texture_video_sink(std::uint32_t texture_id)
849 {
850  d->engine->create_video_sink(texture_id);
851  return media::video::Sink::Ptr{};
852 }
853 
854 template<typename Parent>
856 {
857  d->track_list->reset();
858 
859  // If empty uri, give the same meaning as QMediaPlayer::setMedia("")
860  if (uri.empty())
861  {
862  MH_DEBUG("Resetting current media");
863  return true;
864  }
865 
866  static const bool do_pipeline_reset = false;
867  const bool ret = d->engine->open_resource_for_uri(uri, do_pipeline_reset);
868  // Don't set new track as the current track to play since we're calling open_resource_for_uri above
869  static const bool make_current = false;
870  d->track_list->add_track_with_uri_at(uri, media::TrackList::after_empty_track(), make_current);
871 
872  return ret;
873 }
874 
875 template<typename Parent>
877 {
878  return d->engine->open_resource_for_uri(uri, headers);
879 }
880 
881 template<typename Parent>
883 {
884  d->track_list->next();
885 }
886 
887 template<typename Parent>
889 {
890  d->track_list->previous();
891 }
892 
893 template<typename Parent>
895 {
896  MH_TRACE("");
897  if (d->is_multimedia_role())
898  {
899  MH_DEBUG("==== Pausing all other multimedia player sessions");
900  if (not d->pause_other_players(d->config.key))
901  MH_WARNING("Failed to pause other player sessions");
902 
903  MH_DEBUG("==== Updating the current player");
904  // This player will begin playing so make sure it's the current player. If
905  // this operation fails it is not a fatal condition but should be logged
906  if (not d->update_current_player(d->config.key))
907  MH_WARNING("Failed to update current player");
908  }
909 
910  d->engine->play();
911 }
912 
913 template<typename Parent>
915 {
916  MH_TRACE("");
917  d->engine->pause();
918 }
919 
920 template<typename Parent>
922 {
923  MH_TRACE("");
924  d->engine->stop();
925 }
926 
927 template<typename Parent>
928 void media::PlayerImplementation<Parent>::seek_to(const std::chrono::microseconds& ms)
929 {
930  d->engine->seek_to(ms);
931 }
932 
933 template<typename Parent>
935 {
936  return d->on_client_disconnected;
937 }
938 
939 template<typename Parent>
941 {
942  Parent::playback_status_changed()(status);
943 }
944 
946 
947 // 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
#define MH_INFO(...)
Definition: logger.h:125
media::PlayerImplementation< Parent >::Configuration config
STL namespace.
std::map< std::string, std::string > HeadersType
Definition: player.h:64
wakelock_clear_t current_wakelock_type() const
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
virtual std::string uuid() const
#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)
const core::Signal & on_client_disconnected() const
#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
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)