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
23#include "service_skeleton.h"
24#include "util/timeout.h"
25
26#include <unistd.h>
27#include <ctime>
28
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
44namespace media = core::ubuntu::media;
45namespace dbus = core::dbus;
46
47using namespace std;
48
49template<typename Parent>
51 public std::enable_shared_from_this<Private>
52{
54 {
55 WAKELOCK_CLEAR_INACTIVE,
56 WAKELOCK_CLEAR_DISPLAY,
57 WAKELOCK_CLEAR_SYSTEM,
58 WAKELOCK_CLEAR_INVALID
59 };
60
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
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
408 reinterpret_cast<media::ServiceSkeleton*>(config.parent.player_service)
409 };
411 return true;
412 }
413
415 {
416 if (not config.parent.player_service)
417 return false;
418
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
432 reinterpret_cast<media::ServiceSkeleton*>(config.parent.player_service)
433 };
434 return skel->is_current_player(parent->key());
435 }
436
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
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;
469 // Prevent the TrackList from auto advancing to the next track
471 std::atomic<bool> doing_abandon;
472};
473
474template<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
764template<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
795template<typename Parent>
797{
798 // No impl for now, as not needed internally.
799 return std::string{};
800}
801
802template<typename Parent>
804{
805 d->config.client_death_observer->register_for_death_notifications_with_key(d->config.key);
806}
807
808template<typename Parent>
810{
811 // Signal client disconnection due to abandonment of player
812 d->doing_abandon = true;
813 d->on_client_died();
814}
815
816template<typename Parent>
817std::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
823template<typename Parent>
825{
826 return d->config.key;
827}
828
829template<typename Parent>
830media::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
836template<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
857template<typename Parent>
859{
860 return d->engine->open_resource_for_uri(uri, headers);
861}
862
863template<typename Parent>
865{
866 d->track_list->next();
867}
868
869template<typename Parent>
871{
872 d->track_list->previous();
873}
874
875template<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
895template<typename Parent>
897{
898 MH_TRACE("");
899 d->engine->pause();
900}
901
902template<typename Parent>
904{
905 MH_TRACE("");
906 d->engine->stop();
907}
908
909template<typename Parent>
910void media::PlayerImplementation<Parent>::seek_to(const std::chrono::microseconds& ms)
911{
912 d->engine->seek_to(ms);
913}
914
915template<typename Parent>
917{
918 return d->on_client_disconnected;
919}
920
921template<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.
virtual video::Sink::Ptr create_gl_texture_video_sink(std::uint32_t texture_id)
virtual std::shared_ptr< TrackList > track_list()
virtual void seek_to(const std::chrono::microseconds &offset)
virtual Player::PlayerKey key() const
virtual std::string uuid() const
PlayerImplementation(const Configuration &configuration)
const core::Signal & on_client_disconnected() const
virtual bool open_uri(const Track::UriType &uri)
void emit_playback_status_changed(const Player::PlaybackStatus &status)
std::map< std::string, std::string > HeadersType
Definition: player.h:65
void set_current_player(Player::PlayerKey key)
bool is_current_player(Player::PlayerKey key) const
void pause_other_sessions(Player::PlayerKey key)
std::tuple< std::vector< Track::Id >, Track::Id > ContainerTrackIdTuple
Definition: track_list.h:45
static const Track::Id & after_empty_track()
Definition: track_list.cpp:50
std::vector< Track::UriType > ContainerURI
Definition: track_list.h:44
std::string UriType
Definition: track.h:40
#define MH_TRACE(...)
Definition: logger.h:121
#define MH_INFO(...)
Definition: logger.h:125
#define MH_WARNING(...)
Definition: logger.h:127
#define MH_DEBUG(...)
Definition: logger.h:123
std::tuple< Height, Width > Dimensions
Height and Width of a video.
Definition: dimensions.h:139
Definition: bus.h:34
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)
media::PlayerImplementation< Parent >::Configuration config
std::function< void(const media::Player::PlaybackStatus &status)> make_playback_status_change_handler()
std::string get_uri_for_album_artwork(const media::Track::UriType &uri, const media::Track::MetaData &metadata)
Private(PlayerImplementation *parent, const media::PlayerImplementation< Parent >::Configuration &config)
media::power::StateController::Lock< media::power::SystemState >::Ptr system_state_lock
void open_first_track_from_tracklist(const media::Track::Id &id)
media::PlayerImplementation< Parent > * parent
bool update_current_player(media::Player::PlayerKey key)
void clear_wakelock(const wakelock_clear_t &wakelock)
std::shared_ptr< media::TrackListImplementation > track_list
bool pause_other_players(media::Player::PlayerKey key)
media::power::StateController::Lock< media::power::DisplayState >::Ptr display_state_lock
std::function< void()> make_clear_wakelock_functor()
std::function< void(const Engine::State &state)> make_state_change_handler()