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 
290  // Our link back to our parent.
292  // We just store the parameters passed on construction.
294  media::power::StateController::Lock<media::power::DisplayState>::Ptr display_state_lock;
295  media::power::StateController::Lock<media::power::SystemState>::Ptr system_state_lock;
296 
297  std::shared_ptr<Engine> engine;
298  std::shared_ptr<media::TrackListImplementation> track_list;
299  std::atomic<int> system_wakelock_count;
300  std::atomic<int> display_wakelock_count;
301  Engine::State previous_state;
302  core::Signal<> on_client_disconnected;
305  // Prevent the TrackList from auto advancing to the next track
306  std::mutex doing_go_to_track;
307  std::atomic<bool> doing_abandon;
308 };
309 
310 template<typename Parent>
312  : Parent{config.parent},
313  d{std::make_shared<Private>(this, config)}
314 {
315  // Initialize default values for Player interface properties
316  Parent::can_play().set(true);
317  Parent::can_pause().set(true);
318  Parent::can_seek().set(true);
319  Parent::can_go_previous().set(true);
320  Parent::can_go_next().set(true);
321  Parent::is_video_source().set(false);
322  Parent::is_audio_source().set(false);
323  Parent::shuffle().set(false);
324  Parent::playback_rate().set(1.f);
325  Parent::playback_status().set(Player::PlaybackStatus::null);
326  Parent::loop_status().set(Player::LoopStatus::none);
327  Parent::position().set(0);
328  Parent::duration().set(0);
329  Parent::audio_stream_role().set(Player::AudioStreamRole::multimedia);
330  d->engine->audio_stream_role().set(Player::AudioStreamRole::multimedia);
331  Parent::orientation().set(Player::Orientation::rotate0);
332  Parent::lifetime().set(Player::Lifetime::normal);
333  d->engine->lifetime().set(Player::Lifetime::normal);
334 
335  // Make sure that the Position property gets updated from the Engine
336  // every time the client requests position
337  std::function<uint64_t()> position_getter = [this]()
338  {
339  return d->engine->position().get();
340  };
341  Parent::position().install(position_getter);
342 
343  // Make sure that the Duration property gets updated from the Engine
344  // every time the client requests duration
345  std::function<uint64_t()> duration_getter = [this]()
346  {
347  return d->engine->duration().get();
348  };
349  Parent::duration().install(duration_getter);
350 
351  std::function<bool()> video_type_getter = [this]()
352  {
353  return d->engine->is_video_source().get();
354  };
355  Parent::is_video_source().install(video_type_getter);
356 
357  std::function<bool()> audio_type_getter = [this]()
358  {
359  return d->engine->is_audio_source().get();
360  };
361  Parent::is_audio_source().install(audio_type_getter);
362 
363  // When the client changes the loop status, make sure to update the TrackList
364  Parent::loop_status().changed().connect([this](media::Player::LoopStatus loop_status)
365  {
366  d->track_list->on_loop_status_changed(loop_status);
367  });
368 
369  // When the client changes the shuffle setting, make sure to update the TrackList
370  Parent::shuffle().changed().connect([this](bool shuffle)
371  {
372  d->track_list->on_shuffle_changed(shuffle);
373  });
374 
375  // Make sure that the audio_stream_role property gets updated on the Engine side
376  // whenever the client side sets the role
377  Parent::audio_stream_role().changed().connect([this](media::Player::AudioStreamRole new_role)
378  {
379  d->engine->audio_stream_role().set(new_role);
380  });
381 
382  // When the value of the orientation Property is changed in the Engine by playbin,
383  // update the Player's cached value
384  d->engine->orientation().changed().connect([this](const Player::Orientation& o)
385  {
386  Parent::orientation().set(o);
387  });
388 
389  Parent::lifetime().changed().connect([this](media::Player::Lifetime lifetime)
390  {
391  d->engine->lifetime().set(lifetime);
392  });
393 
394  d->engine->about_to_finish_signal().connect([this]()
395  {
396  if (d->doing_abandon)
397  return;
398 
399  Parent::about_to_finish()();
400 
401  // This lambda needs to be mutually exclusive with the on_go_to_track lambda below
402  d->doing_go_to_track.lock();
403 
404  const media::Track::Id prev_track_id = d->track_list->current();
405  // Make sure that the TrackList keeps advancing. The logic for what gets played next,
406  // if anything at all, occurs in TrackListSkeleton::next()
407  const Track::UriType uri = d->track_list->query_uri_for_track(d->track_list->next());
408  if (prev_track_id != d->track_list->current() && !uri.empty())
409  {
410  std::cout << "Setting next track on playbin: " << uri << std::endl;
411  static const bool do_pipeline_reset = false;
412  d->engine->open_resource_for_uri(uri, do_pipeline_reset);
413  }
414 
415  d->doing_go_to_track.unlock();
416  });
417 
418  d->engine->client_disconnected_signal().connect([this]()
419  {
420  // If the client disconnects, make sure both wakelock types
421  // are cleared
422  d->clear_wakelocks();
423  d->track_list->reset();
424  // And tell the outside world that the client has gone away
425  d->on_client_disconnected();
426  });
427 
428  d->engine->seeked_to_signal().connect([this](uint64_t value)
429  {
430  Parent::seeked_to()(value);
431  });
432 
433  d->engine->end_of_stream_signal().connect([this]()
434  {
435  Parent::end_of_stream()();
436  });
437 
438  d->engine->video_dimension_changed_signal().connect([this](const media::video::Dimensions& dimensions)
439  {
440  Parent::video_dimension_changed()(dimensions);
441  });
442 
443  d->engine->error_signal().connect([this](const Player::Error& e)
444  {
445  Parent::error()(e);
446  });
447 
448  d->track_list->on_go_to_track().connect([this](std::pair<const media::Track::Id, bool> p)
449  {
450  // This prevents the TrackList from auto advancing in other areas such as the about_to_finish signal
451  // handler.
452  // This lambda needs to be mutually exclusive with the about_to_finish lambda above
453  d->doing_go_to_track.lock();
454 
455  const media::Track::Id id = p.first;
456  const bool toggle_player_state = p.second;
457 
458  if (toggle_player_state)
459  d->engine->stop();
460 
461  const Track::UriType uri = d->track_list->query_uri_for_track(id);
462  if (!uri.empty())
463  {
464  std::cout << "Setting next track on playbin (on_go_to_track signal): " << uri << std::endl;
465  std::cout << "\twith a Track::Id: " << id << std::endl;
466  static const bool do_pipeline_reset = true;
467  d->engine->open_resource_for_uri(uri, do_pipeline_reset);
468  }
469 
470  if (toggle_player_state)
471  d->engine->play();
472 
473  d->doing_go_to_track.unlock();
474 
475  });
476 
477  // Everything is setup, we now subscribe to death notifications.
478  std::weak_ptr<Private> wp{d};
479 
480  d->config.client_death_observer->register_for_death_notifications_with_key(config.key);
481  d->config.client_death_observer->on_client_with_key_died().connect([wp](const media::Player::PlayerKey& died)
482  {
483  if (auto sp = wp.lock())
484  {
485  if (sp->doing_abandon)
486  return;
487 
488  if (died != sp->config.key)
489  return;
490 
491  static const std::chrono::milliseconds timeout{1000};
492  media::timeout(timeout.count(), true, [wp]()
493  {
494  if (auto sp = wp.lock())
495  sp->on_client_died();
496  });
497  }
498  });
499 }
500 
501 template<typename Parent>
503 {
504  // Install null getters as these properties may be destroyed
505  // after the engine has been destroyed since they are owned by the
506  // base class.
507  std::function<uint64_t()> position_getter = [this]()
508  {
509  return static_cast<uint64_t>(0);
510  };
511  Parent::position().install(position_getter);
512 
513  std::function<uint64_t()> duration_getter = [this]()
514  {
515  return static_cast<uint64_t>(0);
516  };
517  Parent::duration().install(duration_getter);
518 
519  std::function<bool()> video_type_getter = [this]()
520  {
521  return false;
522  };
523  Parent::is_video_source().install(video_type_getter);
524 
525  std::function<bool()> audio_type_getter = [this]()
526  {
527  return false;
528  };
529  Parent::is_audio_source().install(audio_type_getter);
530 }
531 
532 template<typename Parent>
534 {
535  // No impl for now, as not needed internally.
536  return std::string{};
537 }
538 
539 template<typename Parent>
541 {
542  d->config.client_death_observer->register_for_death_notifications_with_key(d->config.key);
543 }
544 
545 template<typename Parent>
547 {
548  // Signal client disconnection due to abandonment of player
549  d->doing_abandon = true;
550  d->on_client_died();
551 }
552 
553 template<typename Parent>
554 std::shared_ptr<media::TrackList> media::PlayerImplementation<Parent>::track_list()
555 {
556  return d->track_list;
557 }
558 
559 // TODO: Convert this to be a property instead of sync call
560 template<typename Parent>
562 {
563  return d->config.key;
564 }
565 
566 template<typename Parent>
567 media::video::Sink::Ptr media::PlayerImplementation<Parent>::create_gl_texture_video_sink(std::uint32_t texture_id)
568 {
569  d->engine->create_video_sink(texture_id);
570  return media::video::Sink::Ptr{};
571 }
572 
573 template<typename Parent>
575 {
576  d->track_list->reset();
577  // Set new track as the current track to play
578  d->track_list->add_track_with_uri_at(uri, media::TrackList::after_empty_track(), true);
579  return true;
580 }
581 
582 template<typename Parent>
584 {
585  return d->engine->open_resource_for_uri(uri, headers);
586 }
587 
588 template<typename Parent>
590 {
591 }
592 
593 template<typename Parent>
595 {
596 }
597 
598 template<typename Parent>
600 {
601  if (d->track_list != nullptr && d->track_list->tracks()->size() > 0 && d->engine->state() == media::Engine::State::no_media)
602  {
603  // Using a TrackList for playback, added tracks via add_track(), but open_uri hasn't been called yet
604  // to load a media resource
605  std::cout << "No media loaded yet, calling open_uri on first track in track_list" << std::endl;
606  static const bool do_pipeline_reset = true;
607  d->engine->open_resource_for_uri(d->track_list->query_uri_for_track(d->track_list->current()), do_pipeline_reset);
608  std::cout << *d->track_list << endl;
609  }
610 
611  d->engine->play();
612 }
613 
614 template<typename Parent>
616 {
617  d->engine->pause();
618 }
619 
620 template<typename Parent>
622 {
623  std::cout << __PRETTY_FUNCTION__ << std::endl;
624  d->engine->stop();
625 }
626 
627 template<typename Parent>
628 void media::PlayerImplementation<Parent>::seek_to(const std::chrono::microseconds& ms)
629 {
630  d->engine->seek_to(ms);
631 }
632 
633 template<typename Parent>
635 {
636  return d->on_client_disconnected;
637 }
638 
639 template<typename Parent>
641 {
642  Parent::playback_status_changed()(status);
643 }
644 
646 
647 // 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
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)