Merge lp:~alfonsosanchezbeato/media-hub/bg-playlist into lp:media-hub

Proposed by Alfonso Sanchez-Beato
Status: Merged
Approved by: Alfonso Sanchez-Beato
Approved revision: 154
Merged at revision: 154
Proposed branch: lp:~alfonsosanchezbeato/media-hub/bg-playlist
Merge into: lp:media-hub
Diff against target: 1225 lines (+452/-213)
14 files modified
include/core/media/track.h (+1/-0)
include/core/media/track_list.h (+8/-1)
src/core/media/CMakeLists.txt (+1/-1)
src/core/media/gstreamer/engine.cpp (+7/-6)
src/core/media/mpris/track_list.h (+17/-6)
src/core/media/null_track_list.h (+0/-114)
src/core/media/player_implementation.cpp (+43/-17)
src/core/media/service_skeleton.cpp (+18/-15)
src/core/media/track.cpp (+7/-1)
src/core/media/track_list_implementation.cpp (+34/-2)
src/core/media/track_list_skeleton.cpp (+204/-32)
src/core/media/track_list_skeleton.h (+12/-3)
src/core/media/track_list_stub.cpp (+98/-15)
src/core/media/track_list_stub.h (+2/-0)
To merge this branch: bzr merge lp:~alfonsosanchezbeato/media-hub/bg-playlist
Reviewer Review Type Date Requested Status
Alfonso Sanchez-Beato Approve
Review via email: mp+270011@code.launchpad.net

Commit message

[ Jim Hodapp ]
* Support for background playlists

Description of the change

[ Jim Hodapp ]
* Support for background playlists

To post a comment you must log in.
Revision history for this message
Alfonso Sanchez-Beato (alfonsosanchezbeato) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'include/core/media/track.h'
2--- include/core/media/track.h 2014-02-12 16:02:48 +0000
3+++ include/core/media/track.h 2015-09-03 07:33:12 +0000
4@@ -107,6 +107,7 @@
5 bool operator==(const Track&) const;
6
7 virtual const Id& id() const;
8+ virtual const UriType& uri() const;
9 /*
10 class MetaData
11 {
12
13=== modified file 'include/core/media/track_list.h'
14--- include/core/media/track_list.h 2015-04-29 21:18:20 +0000
15+++ include/core/media/track_list.h 2015-09-03 07:33:12 +0000
16@@ -25,8 +25,9 @@
17
18 #include <functional>
19 #include <iosfwd>
20+#include <memory>
21+#include <string>
22 #include <vector>
23-#include <memory>
24
25 namespace core
26 {
27@@ -61,6 +62,9 @@
28 /** Gets all the metadata available for a given Track. */
29 virtual Track::MetaData query_meta_data_for_track(const Track::Id& id) = 0;
30
31+ /** Gets the URI for a given Track. */
32+ virtual Track::UriType query_uri_for_track(const Track::Id& id) = 0;
33+
34 /** Adds a URI in the TrackList. */
35 virtual void add_track_with_uri_at(const Track::UriType& uri, const Track::Id& position, bool make_current) = 0;
36
37@@ -108,6 +112,9 @@
38 /** Used to notify the Player of when the client requested that the Player should immediately play a new track. */
39 virtual const core::Signal<std::pair<Track::Id, bool>>& on_go_to_track() const = 0;
40
41+ /** Used to notify the Player of when the end of the tracklist has been reached. */
42+ virtual const core::Signal<void>& on_end_of_tracklist() const = 0;
43+
44 protected:
45 TrackList();
46 };
47
48=== modified file 'src/core/media/CMakeLists.txt'
49--- src/core/media/CMakeLists.txt 2015-02-24 15:22:19 +0000
50+++ src/core/media/CMakeLists.txt 2015-09-03 07:33:12 +0000
51@@ -88,7 +88,7 @@
52
53 client_death_observer.cpp
54 hashed_keyed_player_store.cpp
55- hybris_client_death_observer.cpp
56+ hybris_client_death_observer.cpp
57 cover_art_resolver.cpp
58 engine.cpp
59
60
61=== modified file 'src/core/media/gstreamer/engine.cpp'
62--- src/core/media/gstreamer/engine.cpp 2015-06-01 16:30:59 +0000
63+++ src/core/media/gstreamer/engine.cpp 2015-09-03 07:33:12 +0000
64@@ -374,7 +374,6 @@
65
66 gstreamer::Engine::Engine() : d(new Private{})
67 {
68- cout << "Creating a new Engine instance in " << __PRETTY_FUNCTION__ << endl;
69 d->state = media::Engine::State::no_media;
70 }
71
72@@ -413,7 +412,7 @@
73
74 bool gstreamer::Engine::play()
75 {
76- auto result = d->playbin.set_state_and_wait(GST_STATE_PLAYING);
77+ const auto result = d->playbin.set_state_and_wait(GST_STATE_PLAYING);
78
79 if (result)
80 {
81@@ -430,10 +429,12 @@
82 {
83 // No need to wait, and we can immediately return.
84 if (d->state == media::Engine::State::stopped)
85+ {
86+ std::cerr << "Current player state is already stopped - no need to change state to stopped" << std::endl;
87 return true;
88-
89- auto result = d->playbin.set_state_and_wait(GST_STATE_NULL);
90-
91+ }
92+
93+ const auto result = d->playbin.set_state_and_wait(GST_STATE_NULL);
94 if (result)
95 {
96 d->state = media::Engine::State::stopped;
97@@ -446,7 +447,7 @@
98
99 bool gstreamer::Engine::pause()
100 {
101- auto result = d->playbin.set_state_and_wait(GST_STATE_PAUSED);
102+ const auto result = d->playbin.set_state_and_wait(GST_STATE_PAUSED);
103
104 if (result)
105 {
106
107=== modified file 'src/core/media/mpris/track_list.h'
108--- src/core/media/mpris/track_list.h 2015-04-14 17:02:09 +0000
109+++ src/core/media/mpris/track_list.h 2015-09-03 07:33:12 +0000
110@@ -49,9 +49,11 @@
111 }
112
113 DBUS_CPP_METHOD_DEF(GetTracksMetadata, TrackList)
114+ DBUS_CPP_METHOD_DEF(GetTracksUri, TrackList)
115 DBUS_CPP_METHOD_DEF(AddTrack, TrackList)
116 DBUS_CPP_METHOD_DEF(RemoveTrack, TrackList)
117 DBUS_CPP_METHOD_DEF(GoTo, TrackList)
118+ DBUS_CPP_METHOD_DEF(Reset, TrackList)
119
120 struct Signals
121 {
122@@ -80,6 +82,13 @@
123
124 DBUS_CPP_SIGNAL_DEF
125 (
126+ TrackChanged,
127+ TrackList,
128+ core::ubuntu::media::Track::Id
129+ )
130+
131+ DBUS_CPP_SIGNAL_DEF
132+ (
133 TrackMetadataChanged,
134 TrackList,
135 BOOST_IDENTITY_TYPE((std::tuple<std::map<std::string, dbus::types::Variant>, dbus::types::ObjectPath>))
136@@ -118,15 +127,16 @@
137 : configuration(configuration),
138 properties
139 {
140- configuration.object->get_property<Properties::Tracks>(),
141- configuration.object->get_property<Properties::CanEditTracks>(),
142+ configuration.object->template get_property<Properties::Tracks>(),
143+ configuration.object->template get_property<Properties::CanEditTracks>(),
144 },
145 signals
146 {
147- configuration.object->get_signal<Signals::TrackListReplaced>(),
148- configuration.object->get_signal<Signals::TrackAdded>(),
149- configuration.object->get_signal<Signals::TrackRemoved>(),
150- configuration.object->get_signal<Signals::TrackMetadataChanged>(),
151+ configuration.object->template get_signal<Signals::TrackListReplaced>(),
152+ configuration.object->template get_signal<Signals::TrackAdded>(),
153+ configuration.object->template get_signal<Signals::TrackRemoved>(),
154+ configuration.object->template get_signal<Signals::TrackChanged>(),
155+ configuration.object->template get_signal<Signals::TrackMetadataChanged>(),
156 configuration.object->template get_signal<core::dbus::interfaces::Properties::Signals::PropertiesChanged>()
157 }
158 {
159@@ -169,6 +179,7 @@
160 core::dbus::Signal<Signals::TrackListReplaced, Signals::TrackListReplaced::ArgumentType>::Ptr tracklist_replaced;
161 core::dbus::Signal<Signals::TrackAdded, Signals::TrackAdded::ArgumentType>::Ptr track_added;
162 core::dbus::Signal<Signals::TrackRemoved, Signals::TrackRemoved::ArgumentType>::Ptr track_removed;
163+ core::dbus::Signal<Signals::TrackChanged, Signals::TrackChanged::ArgumentType>::Ptr track_changed;
164 core::dbus::Signal<Signals::TrackMetadataChanged, Signals::TrackMetadataChanged::ArgumentType>::Ptr track_metadata_changed;
165
166 dbus::Signal <core::dbus::interfaces::Properties::Signals::PropertiesChanged,
167
168=== removed file 'src/core/media/null_track_list.h'
169--- src/core/media/null_track_list.h 2014-12-15 21:02:14 +0000
170+++ src/core/media/null_track_list.h 1970-01-01 00:00:00 +0000
171@@ -1,114 +0,0 @@
172-/*
173- *
174- * This program is free software: you can redistribute it and/or modify it
175- * under the terms of the GNU Lesser General Public License version 3,
176- * as published by the Free Software Foundation.
177- *
178- * This program is distributed in the hope that it will be useful,
179- * but WITHOUT ANY WARRANTY; without even the implied warranty of
180- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
181- * GNU Lesser General Public License for more details.
182- *
183- * You should have received a copy of the GNU Lesser General Public License
184- * along with this program. If not, see <http://www.gnu.org/licenses/>.
185- *
186- * Authored by: Thomas Voß <thomas.voss@canonical.com>
187- */
188-
189-#ifndef CORE_MEDIA_NULL_TRACK_LIST_H_
190-#define CORE_MEDIA_NULL_TRACK_LIST_H_
191-
192-#include <core/media/track.h>
193-#include <core/media/track_list.h>
194-
195-namespace core
196-{
197-namespace ubuntu
198-{
199-namespace media
200-{
201-// A helper type to replace the playlist implementation below.
202-// Please note that this type is only a temporary manner. Ideally,
203-// the actual implementation should be injected as a dependency from the
204-// outside.
205-struct NullTrackList : public media::TrackList
206-{
207- NullTrackList() = default;
208-
209- bool has_next()
210- {
211- return false;
212- }
213-
214- media::Track::Id next()
215- {
216- return media::Track::Id{};
217- }
218-
219- media::Track::UriType query_uri_for_track(const media::Track::Id&)
220- {
221- return media::Track::UriType{};
222- }
223-
224- const core::Property<bool>& can_edit_tracks() const override
225- {
226- return props_and_sigs.can_edit_tracks;
227- }
228-
229- const core::Property<Container>& tracks() const override
230- {
231- return props_and_sigs.tracks;
232- }
233-
234- virtual media::Track::MetaData query_meta_data_for_track(const media::Track::Id&) override
235- {
236- return media::Track::MetaData{};
237- }
238-
239- void add_track_with_uri_at(const media::Track::UriType&, const media::Track::Id&, bool) override
240- {
241- }
242-
243- void remove_track(const media::Track::Id&) override
244- {
245- }
246-
247- void go_to(const media::Track::Id&) override
248- {
249- }
250-
251- const core::Signal<void>& on_track_list_replaced() const override
252- {
253- return props_and_sigs.on_track_list_replaced;
254- }
255-
256- const core::Signal<media::Track::Id>& on_track_added() const override
257- {
258- return props_and_sigs.on_track_added;
259- }
260-
261- const core::Signal<media::Track::Id>& on_track_removed() const override
262- {
263- return props_and_sigs.on_track_removed;
264- }
265-
266- const core::Signal<media::Track::Id>& on_track_changed() const override
267- {
268- return props_and_sigs.on_track_changed;
269- }
270-
271- struct
272- {
273- core::Property<bool> can_edit_tracks;
274- core::Property<TrackList::Container> tracks;
275- core::Signal<void> on_track_list_replaced;
276- core::Signal<media::Track::Id> on_track_added;
277- core::Signal<media::Track::Id> on_track_removed;
278- core::Signal<media::Track::Id> on_track_changed;
279- } props_and_sigs;
280-};
281-}
282-}
283-}
284-
285-#endif // CORE_MEDIA_NULL_TRACK_LIST_H_
286
287=== modified file 'src/core/media/player_implementation.cpp'
288--- src/core/media/player_implementation.cpp 2015-06-01 16:31:50 +0000
289+++ src/core/media/player_implementation.cpp 2015-09-03 07:33:12 +0000
290@@ -287,6 +287,20 @@
291 engine->reset();
292 }
293
294+ void open_first_track_from_tracklist(const media::Track::Id& id)
295+ {
296+ const Track::UriType uri = track_list->query_uri_for_track(id);
297+ if (!uri.empty())
298+ {
299+ // Using a TrackList for playback, added tracks via add_track(), but open_uri hasn't been called yet
300+ // to load a media resource
301+ std::cout << "Calling d->engine->open_resource_for_uri() for first track added only: " << uri << std::endl;
302+ std::cout << "\twith a Track::Id: " << id << std::endl;
303+ static const bool do_pipeline_reset = false;
304+ engine->open_resource_for_uri(uri, do_pipeline_reset);
305+ }
306+ }
307+
308 // Our link back to our parent.
309 media::PlayerImplementation<Parent>* parent;
310 // We just store the parameters passed on construction.
311@@ -375,6 +389,7 @@
312 // When the client changes the loop status, make sure to update the TrackList
313 Parent::loop_status().changed().connect([this](media::Player::LoopStatus loop_status)
314 {
315+ std::cout << "LoopStatus: " << loop_status << std::endl;
316 d->track_list->on_loop_status_changed(loop_status);
317 });
318
319@@ -408,18 +423,20 @@
320 if (d->doing_abandon)
321 return;
322
323+ // Prevent on_go_to_track from executing as it's not needed in this case. on_go_to_track
324+ // (see the lambda below) is only needed when the client explicitly calls next() not during
325+ // the about_to_finish condition
326+ d->doing_go_to_track.lock();
327+
328 Parent::about_to_finish()();
329
330- // This lambda needs to be mutually exclusive with the on_go_to_track lambda below
331- d->doing_go_to_track.lock();
332-
333 const media::Track::Id prev_track_id = d->track_list->current();
334 // Make sure that the TrackList keeps advancing. The logic for what gets played next,
335 // if anything at all, occurs in TrackListSkeleton::next()
336 const Track::UriType uri = d->track_list->query_uri_for_track(d->track_list->next());
337 if (prev_track_id != d->track_list->current() && !uri.empty())
338 {
339- std::cout << "Setting next track on playbin: " << uri << std::endl;
340+ std::cout << "Advancing to next track on playbin: " << uri << std::endl;
341 static const bool do_pipeline_reset = false;
342 d->engine->open_resource_for_uri(uri, do_pipeline_reset);
343 }
344@@ -457,12 +474,25 @@
345 Parent::error()(e);
346 });
347
348+ d->track_list->on_end_of_tracklist().connect([this]()
349+ {
350+ if (d->engine->state() != gstreamer::Engine::State::ready
351+ && d->engine->state() != gstreamer::Engine::State::stopped)
352+ {
353+ std::cout << "End of tracklist reached, stopping playback" << std::endl;
354+ d->engine->stop();
355+ }
356+ });
357+
358+
359 d->track_list->on_go_to_track().connect([this](std::pair<const media::Track::Id, bool> p)
360 {
361- // This prevents the TrackList from auto advancing in other areas such as the about_to_finish signal
362- // handler.
363 // This lambda needs to be mutually exclusive with the about_to_finish lambda above
364- d->doing_go_to_track.lock();
365+ const bool locked = d->doing_go_to_track.try_lock();
366+ // If the try_lock fails, it means that about_to_finish lambda above has it locked and it will
367+ // call d->engine->open_resource_for_uri()
368+ if (!locked)
369+ return;
370
371 const media::Track::Id id = p.first;
372 const bool toggle_player_state = p.second;
373@@ -483,7 +513,13 @@
374 d->engine->play();
375
376 d->doing_go_to_track.unlock();
377+ });
378
379+ d->track_list->on_track_added().connect([this](const media::Track::Id& id)
380+ {
381+ std::cout << "** Track was added, handling in PlayerImplementation" << std::endl;
382+ if (d->track_list->tracks()->size() == 1)
383+ d->open_first_track_from_tracklist(id);
384 });
385
386 // Everything is setup, we now subscribe to death notifications.
387@@ -614,16 +650,6 @@
388 template<typename Parent>
389 void media::PlayerImplementation<Parent>::play()
390 {
391- if (d->track_list != nullptr && d->track_list->tracks()->size() > 0 && d->engine->state() == media::Engine::State::no_media)
392- {
393- // Using a TrackList for playback, added tracks via add_track(), but open_uri hasn't been called yet
394- // to load a media resource
395- std::cout << "No media loaded yet, calling open_uri on first track in track_list" << std::endl;
396- static const bool do_pipeline_reset = true;
397- d->engine->open_resource_for_uri(d->track_list->query_uri_for_track(d->track_list->current()), do_pipeline_reset);
398- std::cout << *d->track_list << endl;
399- }
400-
401 d->engine->play();
402 }
403
404
405=== modified file 'src/core/media/service_skeleton.cpp'
406--- src/core/media/service_skeleton.cpp 2015-04-27 21:25:03 +0000
407+++ src/core/media/service_skeleton.cpp 2015-09-03 07:33:12 +0000
408@@ -139,7 +139,7 @@
409 fprintf(stderr, "%s():%d -- app_name='%s', attached\n", __func__, __LINE__, context.str().c_str());
410 player_owner_map.insert(std::make_pair(key, std::make_tuple(context.str(), true, msg->sender())));
411 });
412-
413+
414 auto reply = dbus::Message::make_method_return(msg);
415 reply->writer() << std::make_tuple(op, uuid);
416
417@@ -161,20 +161,24 @@
418 std::string uuid;
419 msg->reader() >> uuid;
420
421- auto key = uuid_player_map.at(uuid);
422+ // Make sure we don't try to do a lookup if the map is empty
423+ if (!uuid_player_map.empty())
424+ {
425+ const auto key = uuid_player_map.at(uuid);
426
427- if (player_owner_map.count(key) != 0) {
428- auto info = player_owner_map.at(key);
429- // Check if session is attached(1) and that the detachment
430- // request comes from the same peer(2) that created the session.
431- if (std::get<1>(info) && (std::get<2>(info) == msg->sender())) { // Player is attached
432- std::get<1>(info) = false; // Detached
433- std::get<2>(info).clear(); // Clear registered sender/peer
434- auto player = configuration.player_store->player_for_key(key);
435- player->lifetime().set(media::Player::Lifetime::resumable);
436+ if (player_owner_map.count(key) != 0) {
437+ auto info = player_owner_map.at(key);
438+ // Check if session is attached(1) and that the detachment
439+ // request comes from the same peer(2) that created the session.
440+ if (std::get<1>(info) && (std::get<2>(info) == msg->sender())) { // Player is attached
441+ std::get<1>(info) = false; // Detached
442+ std::get<2>(info).clear(); // Clear registered sender/peer
443+ auto player = configuration.player_store->player_for_key(key);
444+ player->lifetime().set(media::Player::Lifetime::resumable);
445+ }
446 }
447 }
448-
449+
450 auto reply = dbus::Message::make_method_return(msg);
451 impl->access_bus()->send(reply);
452
453@@ -256,7 +260,6 @@
454
455 void handle_destroy_session(const core::dbus::Message::Ptr& msg)
456 {
457-
458 try
459 {
460 std::string uuid;
461@@ -445,9 +448,9 @@
462 // We keep a list of keys and their respective owners and states.
463 // value: (owner context, attached state, attached dbus name)
464 std::map<media::Player::PlayerKey, std::tuple<std::string, bool, std::string>> player_owner_map;
465-
466+
467 boost::uuids::random_generator gen;
468-
469+
470 // We expose the entire service as an MPRIS player.
471 struct Exported
472 {
473
474=== modified file 'src/core/media/track.cpp'
475--- src/core/media/track.cpp 2014-02-12 15:53:57 +0000
476+++ src/core/media/track.cpp 2015-09-03 07:33:12 +0000
477@@ -25,9 +25,10 @@
478 struct media::Track::Private
479 {
480 media::Track::Id id;
481+ media::Track::UriType uri;
482 };
483
484-media::Track::Track(const media::Track::Id& id) : d(new Private{id})
485+media::Track::Track(const media::Track::Id& id) : d(new Private{id, std::string{}})
486 {
487 }
488
489@@ -39,3 +40,8 @@
490 {
491 return d->id;
492 }
493+
494+const media::Track::UriType& media::Track::uri() const
495+{
496+ return d->uri;
497+}
498
499=== modified file 'src/core/media/track_list_implementation.cpp'
500--- src/core/media/track_list_implementation.cpp 2015-05-22 21:19:28 +0000
501+++ src/core/media/track_list_implementation.cpp 2015-09-03 07:33:12 +0000
502@@ -59,7 +59,7 @@
503
504 media::Track::UriType media::TrackListImplementation::query_uri_for_track(const media::Track::Id& id)
505 {
506- auto it = d->meta_data_cache.find(id);
507+ const auto it = d->meta_data_cache.find(id);
508
509 if (it == d->meta_data_cache.end())
510 return Track::UriType{};
511@@ -69,7 +69,7 @@
512
513 media::Track::MetaData media::TrackListImplementation::query_meta_data_for_track(const media::Track::Id& id)
514 {
515- auto it = d->meta_data_cache.find(id);
516+ const auto it = d->meta_data_cache.find(id);
517
518 if (it == d->meta_data_cache.end())
519 return Track::MetaData{};
520@@ -87,10 +87,14 @@
521 std::stringstream ss; ss << d->object->path().as_string() << "/" << d->track_counter++;
522 Track::Id id{ss.str()};
523
524+ std::cout << "Adding Track::Id: " << id << std::endl;
525+ std::cout << "\tURI: " << uri << std::endl;
526+
527 auto result = tracks().update([this, id, position, make_current](TrackList::Container& container)
528 {
529 auto it = std::find(container.begin(), container.end(), position);
530 container.insert(it, id);
531+ std::cout << "container.size(): " << container.size() << std::endl;
532
533 return true;
534 });
535@@ -99,6 +103,8 @@
536 {
537 if (d->meta_data_cache.count(id) == 0)
538 {
539+ // FIXME: This code seems to conflict badly when called multiple times in a row: causes segfaults
540+#if 0
541 try {
542 d->meta_data_cache[id] = std::make_tuple(
543 uri,
544@@ -106,6 +112,11 @@
545 } catch (const std::runtime_error &e) {
546 std::cerr << "Failed to retrieve metadata for track '" << uri << "' (" << e.what() << ")" << std::endl;
547 }
548+#else
549+ d->meta_data_cache[id] = std::make_tuple(
550+ uri,
551+ core::ubuntu::media::Track::MetaData{});
552+#endif
553 } else
554 {
555 std::get<0>(d->meta_data_cache[id]) = uri;
556@@ -120,8 +131,14 @@
557 go_to(id, toggle_player_state);
558 }
559
560+ // Signal to the client that the current track has changed for the first track added to the TrackList
561+ if (tracks().get().size() == 1)
562+ on_track_changed()(id);
563+
564+ std::cout << "Signaling that we just added track id: " << id << std::endl;
565 // Signal to the client that a track was added to the TrackList
566 on_track_added()(id);
567+ std::cout << "Signaled that we just added track id: " << id << std::endl;
568 }
569 }
570
571@@ -133,6 +150,8 @@
572 return true;
573 });
574
575+ reset_current_iterator_if_needed();
576+
577 if (result)
578 {
579 d->meta_data_cache.erase(id);
580@@ -155,8 +174,12 @@
581 {
582 std::cout << __PRETTY_FUNCTION__ << std::endl;
583
584+ if (tracks().get().empty())
585+ return;
586+
587 auto result = tracks().update([this](TrackList::Container& container)
588 {
589+ // Save off the original TrackList ordering
590 d->original_tracklist.assign(container.begin(), container.end());
591 std::random_shuffle(container.begin(), container.end());
592 return true;
593@@ -173,8 +196,12 @@
594 {
595 std::cout << __PRETTY_FUNCTION__ << std::endl;
596
597+ if (tracks().get().empty() or d->original_tracklist.empty())
598+ return;
599+
600 auto result = tracks().update([this](TrackList::Container& container)
601 {
602+ // Restore the original TrackList ordering
603 container.assign(d->original_tracklist.begin(), d->original_tracklist.end());
604 return true;
605 });
606@@ -190,6 +217,9 @@
607 {
608 std::cout << __PRETTY_FUNCTION__ << std::endl;
609
610+ // Make sure playback stops
611+ on_end_of_tracklist()();
612+
613 auto result = tracks().update([this](TrackList::Container& container)
614 {
615 container.clear();
616@@ -198,5 +228,7 @@
617 return true;
618 });
619
620+ media::TrackListSkeleton::reset();
621+
622 (void) result;
623 }
624
625=== modified file 'src/core/media/track_list_skeleton.cpp'
626--- src/core/media/track_list_skeleton.cpp 2015-04-29 21:18:20 +0000
627+++ src/core/media/track_list_skeleton.cpp 2015-09-03 07:33:12 +0000
628@@ -60,6 +60,7 @@
629 {
630 skeleton.signals.track_added,
631 skeleton.signals.track_removed,
632+ skeleton.signals.track_changed,
633 skeleton.signals.tracklist_replaced
634 }
635 {
636@@ -70,15 +71,28 @@
637 media::Track::Id track;
638 msg->reader() >> track;
639
640- auto meta_data = impl->query_meta_data_for_track(track);
641+ const auto meta_data = impl->query_meta_data_for_track(track);
642
643- auto reply = dbus::Message::make_method_return(msg);
644+ const auto reply = dbus::Message::make_method_return(msg);
645 reply->writer() << *meta_data;
646 bus->send(reply);
647 }
648
649+ void handle_get_tracks_uri(const core::dbus::Message::Ptr& msg)
650+ {
651+ media::Track::Id track;
652+ msg->reader() >> track;
653+
654+ const auto uri = impl->query_uri_for_track(track);
655+
656+ const auto reply = dbus::Message::make_method_return(msg);
657+ reply->writer() << uri;
658+ bus->send(reply);
659+ }
660+
661 void handle_add_track_with_uri_at(const core::dbus::Message::Ptr& msg)
662 {
663+ std::cout << "*** " << __PRETTY_FUNCTION__ << std::endl;
664 request_context_resolver->resolve_context_for_dbus_name_async(msg->sender(), [this, msg](const media::apparmor::ubuntu::Context& context)
665 {
666 Track::UriType uri; media::Track::Id after; bool make_current;
667@@ -123,6 +137,14 @@
668 bus->send(reply);
669 }
670
671+ void handle_reset(const core::dbus::Message::Ptr& msg)
672+ {
673+ impl->reset();
674+
675+ auto reply = dbus::Message::make_method_return(msg);
676+ bus->send(reply);
677+ }
678+
679 media::TrackListSkeleton* impl;
680 dbus::Bus::Ptr bus;
681 dbus::Object::Ptr object;
682@@ -138,10 +160,12 @@
683 {
684 typedef core::dbus::Signal<mpris::TrackList::Signals::TrackAdded, mpris::TrackList::Signals::TrackAdded::ArgumentType> DBusTrackAddedSignal;
685 typedef core::dbus::Signal<mpris::TrackList::Signals::TrackRemoved, mpris::TrackList::Signals::TrackRemoved::ArgumentType> DBusTrackRemovedSignal;
686+ typedef core::dbus::Signal<mpris::TrackList::Signals::TrackChanged, mpris::TrackList::Signals::TrackChanged::ArgumentType> DBusTrackChangedSignal;
687 typedef core::dbus::Signal<mpris::TrackList::Signals::TrackListReplaced, mpris::TrackList::Signals::TrackListReplaced::ArgumentType> DBusTrackListReplacedSignal;
688
689 Signals(const std::shared_ptr<DBusTrackAddedSignal>& remote_track_added,
690 const std::shared_ptr<DBusTrackRemovedSignal>& remote_track_removed,
691+ const std::shared_ptr<DBusTrackChangedSignal>& remote_track_changed,
692 const std::shared_ptr<DBusTrackListReplacedSignal>& remote_track_list_replaced)
693 {
694 // Connect all of the MPRIS interface signals to be emitted over dbus
695@@ -155,6 +179,11 @@
696 remote_track_removed->emit(id);
697 });
698
699+ on_track_changed.connect([remote_track_changed](const media::Track::Id &id)
700+ {
701+ remote_track_changed->emit(id);
702+ });
703+
704 on_track_list_replaced.connect([remote_track_list_replaced](const media::TrackList::ContainerTrackIdTuple &tltuple)
705 {
706 remote_track_list_replaced->emit(tltuple);
707@@ -163,9 +192,10 @@
708
709 core::Signal<Track::Id> on_track_added;
710 core::Signal<Track::Id> on_track_removed;
711+ core::Signal<Track::Id> on_track_changed;
712 core::Signal<TrackList::ContainerTrackIdTuple> on_track_list_replaced;
713- core::Signal<Track::Id> on_track_changed;
714 core::Signal<std::pair<Track::Id, bool>> on_go_to_track;
715+ core::Signal<void> on_end_of_tracklist;
716 } signals;
717 };
718
719@@ -179,6 +209,11 @@
720 std::ref(d),
721 std::placeholders::_1));
722
723+ d->object->install_method_handler<mpris::TrackList::GetTracksUri>(
724+ std::bind(&Private::handle_get_tracks_uri,
725+ std::ref(d),
726+ std::placeholders::_1));
727+
728 d->object->install_method_handler<mpris::TrackList::AddTrack>(
729 std::bind(&Private::handle_add_track_with_uri_at,
730 std::ref(d),
731@@ -193,74 +228,184 @@
732 std::bind(&Private::handle_go_to,
733 std::ref(d),
734 std::placeholders::_1));
735+
736+ d->object->install_method_handler<mpris::TrackList::Reset>(
737+ std::bind(&Private::handle_reset,
738+ std::ref(d),
739+ std::placeholders::_1));
740 }
741
742 media::TrackListSkeleton::~TrackListSkeleton()
743 {
744 }
745
746-bool media::TrackListSkeleton::has_next() const
747+bool media::TrackListSkeleton::has_next()
748 {
749- const auto next_track = std::next(d->current_track);
750- std::cout << "has_next track? " << (next_track != tracks().get().end() ? "yes" : "no") << std::endl;
751- return next_track != tracks().get().end();
752+ if (tracks().get().empty())
753+ return false;
754+
755+ const auto next_track = std::next(current_iterator());
756+ return !is_last_track(next_track);
757 }
758
759-bool media::TrackListSkeleton::has_previous() const
760+bool media::TrackListSkeleton::has_previous()
761 {
762+ if (tracks().get().empty())
763+ return false;
764+
765 // If we are looping over the entire list, then there is always a previous track
766 if (d->loop_status == media::Player::LoopStatus::playlist)
767 return true;
768
769- std::cout << "has_previous track? " << (d->current_track != tracks().get().begin() ? "yes" : "no") << std::endl;
770- return d->current_track != tracks().get().begin();
771+ return d->current_track != std::begin(tracks().get());
772+}
773+
774+bool media::TrackListSkeleton::is_first_track(const ConstIterator &it)
775+{
776+ return it == std::begin(tracks().get());
777+}
778+
779+bool media::TrackListSkeleton::is_last_track(const TrackList::ConstIterator &it)
780+{
781+ return it == std::end(tracks().get());
782 }
783
784 media::Track::Id media::TrackListSkeleton::next()
785 {
786 std::cout << __PRETTY_FUNCTION__ << std::endl;
787 if (tracks().get().empty())
788- return *(d->current_track);
789+ return *(d->empty_iterator);
790+
791+ const auto next_track = std::next(current_iterator());
792+ bool do_go_to_next_track = false;
793+
794+ // End of the track reached so loop around to the beginning of the track
795+ if (d->loop_status == media::Player::LoopStatus::track)
796+ {
797+ std::cout << "Looping on the current track since LoopStatus is set to track" << std::endl;
798+ do_go_to_next_track = true;
799+ }
800+ // End of the tracklist reached so loop around to the beginning of the tracklist
801+ else if (d->loop_status == media::Player::LoopStatus::playlist && not has_next())
802+ {
803+ std::cout << "Looping on the tracklist since LoopStatus is set to playlist" << std::endl;
804+ d->current_track = tracks().get().begin();
805+ do_go_to_next_track = true;
806+ }
807+ else
808+ {
809+ // Next track is not the last track
810+ if (not is_last_track(next_track))
811+ {
812+ std::cout << "Advancing to next track: " << *(next_track) << std::endl;
813+ d->current_track = next_track;
814+ do_go_to_next_track = true;
815+ }
816+ // At the end of the tracklist and not set to loop, so we stop advancing the tracklist
817+ else
818+ {
819+ std::cout << "End of tracklist reached, not advancing to next since LoopStatus is set to none" << std::endl;
820+ on_end_of_tracklist()();
821+ }
822+ }
823+
824+ if (do_go_to_next_track)
825+ {
826+ on_track_changed()(*(current_iterator()));
827+ // Don't automatically call stop() and play() in player_implementation.cpp on_go_to_track()
828+ // since this breaks video playback when using open_uri() (stop() and play() are unwanted in
829+ // this scenario since the qtubuntu-media will handle this automatically)
830+ const bool toggle_player_state = false;
831+ const media::Track::Id id = *(current_iterator());
832+ const std::pair<const media::Track::Id, bool> p = std::make_pair(id, toggle_player_state);
833+ // Signal the PlayerImplementation to play the next track
834+ on_go_to_track()(p);
835+ }
836+
837+ return *(current_iterator());
838+}
839+
840+media::Track::Id media::TrackListSkeleton::previous()
841+{
842+ std::cout << __PRETTY_FUNCTION__ << std::endl;
843+ if (tracks().get().empty())
844+ return *(d->empty_iterator);
845+
846+ bool do_go_to_previous_track = false;
847
848 // Loop on the current track forever
849 if (d->loop_status == media::Player::LoopStatus::track)
850 {
851 std::cout << "Looping on the current track..." << std::endl;
852- return *(d->current_track);
853+ do_go_to_previous_track = true;
854 }
855 // Loop over the whole playlist and repeat
856- else if (d->loop_status == media::Player::LoopStatus::playlist && !has_next())
857+ else if (d->loop_status == media::Player::LoopStatus::playlist && is_first_track(current_iterator()))
858 {
859 std::cout << "Looping on the entire TrackList..." << std::endl;
860- d->current_track = tracks().get().begin();
861- return *(d->current_track);
862- }
863- else if (has_next())
864- {
865- // Keep returning the next track until the last track is reached
866- d->current_track = std::next(d->current_track);
867- std::cout << *this << std::endl;
868- }
869-
870- return *(d->current_track);
871-}
872-
873-media::Track::Id media::TrackListSkeleton::previous()
874-{
875- // TODO: Add logic to calculate the previous track
876- return *(d->current_track);
877+ d->current_track = std::prev(tracks().get().end());
878+ do_go_to_previous_track = true;
879+ }
880+ else
881+ {
882+ // Current track is not the first track
883+ if (not is_first_track(current_iterator()))
884+ {
885+ // Keep returning the previous track until the first track is reached
886+ d->current_track = std::prev(current_iterator());
887+ do_go_to_previous_track = true;
888+ }
889+ // At the beginning of the tracklist and not set to loop, so we stop advancing the tracklist
890+ else
891+ {
892+ std::cout << "Beginning of tracklist reached, not advancing to previous since LoopStatus is set to none" << std::endl;
893+ on_end_of_tracklist()();
894+ }
895+ }
896+
897+ if (do_go_to_previous_track)
898+ {
899+ on_track_changed()(*(current_iterator()));
900+ // Don't automatically call stop() and play() in player_implementation.cpp on_go_to_track()
901+ // since this breaks video playback when using open_uri() (stop() and play() are unwanted in
902+ // this scenario since the qtubuntu-media will handle this automatically)
903+ const bool toggle_player_state = false;
904+ const media::Track::Id id = *(current_iterator());
905+ const std::pair<const media::Track::Id, bool> p = std::make_pair(id, toggle_player_state);
906+ on_go_to_track()(p);
907+ }
908+
909+ return *(current_iterator());
910 }
911
912 const media::Track::Id& media::TrackListSkeleton::current()
913 {
914+ return *(current_iterator());
915+}
916+
917+const media::TrackList::ConstIterator& media::TrackListSkeleton::current_iterator()
918+{
919 // Prevent the TrackList from sitting at the end which will cause
920 // a segfault when calling current()
921 if (tracks().get().size() && (d->current_track == d->empty_iterator))
922+ {
923+ std::cout << "Wrapping d->current_track back to begin()" << std::endl;
924 d->current_track = d->skeleton.properties.tracks->get().begin();
925+ }
926 else if (tracks().get().empty())
927+ {
928 std::cerr << "TrackList is empty therefore there is no valid current track" << std::endl;
929-
930- return *(d->current_track);
931+ }
932+
933+ return d->current_track;
934+}
935+
936+void media::TrackListSkeleton::reset_current_iterator_if_needed()
937+{
938+ // If all tracks got removed then we need to keep a sane current
939+ // iterator for further use.
940+ if (tracks().get().empty())
941+ d->current_track = d->empty_iterator;
942 }
943
944 const core::Property<bool>& media::TrackListSkeleton::can_edit_tracks() const
945@@ -293,7 +438,18 @@
946 if (shuffle)
947 shuffle_tracks();
948 else
949+ {
950+ // Save the current Track::Id of what's currently playing to restore after unshuffle
951+ const media::Track::Id current_id = *(current_iterator());
952+
953 unshuffle_tracks();
954+
955+ // Since we use assign() in unshuffle_tracks, which invalidates existing iterators, we need
956+ // to make sure that current is pointing to the right place
957+ auto it = std::find(tracks().get().begin(), tracks().get().end(), current_id);
958+ if (it != tracks().get().end())
959+ d->current_track = it;
960+ }
961 }
962
963 const core::Property<media::TrackList::Container>& media::TrackListSkeleton::tracks() const
964@@ -328,6 +484,11 @@
965 return d->signals.on_go_to_track;
966 }
967
968+const core::Signal<void>& media::TrackListSkeleton::on_end_of_tracklist() const
969+{
970+ return d->signals.on_end_of_tracklist;
971+}
972+
973 core::Signal<media::TrackList::ContainerTrackIdTuple>& media::TrackListSkeleton::on_track_list_replaced()
974 {
975 return d->signals.on_track_list_replaced;
976@@ -353,6 +514,17 @@
977 return d->signals.on_go_to_track;
978 }
979
980+core::Signal<void>& media::TrackListSkeleton::on_end_of_tracklist()
981+{
982+ return d->signals.on_end_of_tracklist;
983+}
984+
985+void media::TrackListSkeleton::reset()
986+{
987+ std::cout << __PRETTY_FUNCTION__ << std::endl;
988+ d->current_track = d->empty_iterator;
989+}
990+
991 // operator<< pretty prints the given TrackList to the given output stream.
992 inline std::ostream& media::operator<<(std::ostream& out, const media::TrackList& tracklist)
993 {
994
995=== modified file 'src/core/media/track_list_skeleton.h'
996--- src/core/media/track_list_skeleton.h 2015-04-29 21:18:20 +0000
997+++ src/core/media/track_list_skeleton.h 2015-09-03 07:33:12 +0000
998@@ -41,8 +41,8 @@
999 const core::ubuntu::media::apparmor::ubuntu::RequestAuthenticator::Ptr& request_authenticator);
1000 ~TrackListSkeleton();
1001
1002- bool has_next() const;
1003- bool has_previous() const;
1004+ bool has_next();
1005+ bool has_previous();
1006 Track::Id next();
1007 Track::Id previous();
1008 const Track::Id& current();
1009@@ -52,10 +52,13 @@
1010
1011 const core::Signal<ContainerTrackIdTuple>& on_track_list_replaced() const;
1012 const core::Signal<Track::Id>& on_track_added() const;
1013+ core::Signal<Track::Id>& on_track_added();
1014 const core::Signal<Track::Id>& on_track_removed() const;
1015 const core::Signal<Track::Id>& on_track_changed() const;
1016 const core::Signal<std::pair<Track::Id, bool>>& on_go_to_track() const;
1017 core::Signal<std::pair<Track::Id, bool>>& on_go_to_track();
1018+ const core::Signal<void>& on_end_of_tracklist() const;
1019+ core::Signal<void>& on_end_of_tracklist();
1020 core::Signal<Track::Id>& on_track_removed();
1021
1022 core::Property<Container>& tracks();
1023@@ -67,12 +70,18 @@
1024 void on_shuffle_changed(bool shuffle);
1025
1026 protected:
1027+ inline bool is_first_track(const ConstIterator &it);
1028+ inline bool is_last_track(const ConstIterator &it);
1029+ inline const TrackList::ConstIterator& current_iterator();
1030+ void reset_current_iterator_if_needed();
1031+
1032 core::Property<bool>& can_edit_tracks();
1033
1034 core::Signal<ContainerTrackIdTuple>& on_track_list_replaced();
1035- core::Signal<Track::Id>& on_track_added();
1036 core::Signal<Track::Id>& on_track_changed();
1037
1038+ void reset();
1039+
1040 private:
1041 struct Private;
1042 std::unique_ptr<Private> d;
1043
1044=== modified file 'src/core/media/track_list_stub.cpp'
1045--- src/core/media/track_list_stub.cpp 2015-04-29 21:18:20 +0000
1046+++ src/core/media/track_list_stub.cpp 2015-09-03 07:33:12 +0000
1047@@ -48,7 +48,14 @@
1048 parent(parent),
1049 object(object),
1050 can_edit_tracks(object->get_property<mpris::TrackList::Properties::CanEditTracks>()),
1051- tracks(object->get_property<mpris::TrackList::Properties::Tracks>())
1052+ tracks(object->get_property<mpris::TrackList::Properties::Tracks>()),
1053+ signals
1054+ {
1055+ object->get_signal<mpris::TrackList::Signals::TrackAdded>(),
1056+ object->get_signal<mpris::TrackList::Signals::TrackRemoved>(),
1057+ object->get_signal<mpris::TrackList::Signals::TrackListReplaced>(),
1058+ object->get_signal<mpris::TrackList::Signals::TrackChanged>()
1059+ }
1060 {
1061 }
1062
1063@@ -59,11 +66,70 @@
1064 std::shared_ptr<core::dbus::Property<mpris::TrackList::Properties::CanEditTracks>> can_edit_tracks;
1065 std::shared_ptr<core::dbus::Property<mpris::TrackList::Properties::Tracks>> tracks;
1066
1067- core::Signal<media::TrackList::ContainerTrackIdTuple> on_track_list_replaced;
1068- core::Signal<Track::Id> on_track_added;
1069- core::Signal<Track::Id> on_track_removed;
1070- core::Signal<Track::Id> on_track_changed;
1071- core::Signal<std::pair<Track::Id, bool>> on_go_to_track;
1072+
1073+ struct Signals
1074+ {
1075+ typedef core::dbus::Signal<mpris::TrackList::Signals::TrackAdded, mpris::TrackList::Signals::TrackAdded::ArgumentType> DBusTrackAddedSignal;
1076+ typedef core::dbus::Signal<mpris::TrackList::Signals::TrackRemoved, mpris::TrackList::Signals::TrackRemoved::ArgumentType> DBusTrackRemovedSignal;
1077+ typedef core::dbus::Signal<mpris::TrackList::Signals::TrackListReplaced, mpris::TrackList::Signals::TrackListReplaced::ArgumentType> DBusTrackListReplacedSignal;
1078+ typedef core::dbus::Signal<mpris::TrackList::Signals::TrackChanged, mpris::TrackList::Signals::TrackChanged::ArgumentType> DBusTrackChangedSignal;
1079+
1080+ Signals(const std::shared_ptr<DBusTrackAddedSignal>& track_added,
1081+ const std::shared_ptr<DBusTrackRemovedSignal>& track_removed,
1082+ const std::shared_ptr<DBusTrackListReplacedSignal>& track_list_replaced,
1083+ const std::shared_ptr<DBusTrackChangedSignal>& track_changed)
1084+ : on_track_added(),
1085+ on_track_removed(),
1086+ on_track_list_replaced(),
1087+ on_track_changed(),
1088+ dbus
1089+ {
1090+ track_added,
1091+ track_removed,
1092+ track_list_replaced,
1093+ track_changed,
1094+ }
1095+ {
1096+ dbus.on_track_added->connect([this](const Track::Id& id)
1097+ {
1098+ std::cout << "OnTrackAdded signal arrived via the bus." << std::endl;
1099+ on_track_added(id);
1100+ });
1101+
1102+ dbus.on_track_removed->connect([this](const Track::Id& id)
1103+ {
1104+ std::cout << "OnTrackRemoved signal arrived via the bus." << std::endl;
1105+ on_track_removed(id);
1106+ });
1107+
1108+ dbus.on_track_list_replaced->connect([this](const media::TrackList::ContainerTrackIdTuple& list)
1109+ {
1110+ std::cout << "OnTrackListRemoved signal arrived via the bus." << std::endl;
1111+ on_track_list_replaced(list);
1112+ });
1113+
1114+ dbus.on_track_changed->connect([this](const Track::Id& id)
1115+ {
1116+ std::cout << "OnTrackChanged signal arrived via the bus." << std::endl;
1117+ on_track_changed(id);
1118+ });
1119+ }
1120+
1121+ core::Signal<Track::Id> on_track_added;
1122+ core::Signal<Track::Id> on_track_removed;
1123+ core::Signal<media::TrackList::ContainerTrackIdTuple> on_track_list_replaced;
1124+ core::Signal<Track::Id> on_track_changed;
1125+ core::Signal<std::pair<Track::Id, bool>> on_go_to_track;
1126+ core::Signal<void> on_end_of_tracklist;
1127+
1128+ struct DBus
1129+ {
1130+ std::shared_ptr<DBusTrackAddedSignal> on_track_added;
1131+ std::shared_ptr<DBusTrackRemovedSignal> on_track_removed;
1132+ std::shared_ptr<DBusTrackListReplacedSignal> on_track_list_replaced;
1133+ std::shared_ptr<DBusTrackChangedSignal> on_track_changed;
1134+ } dbus;
1135+ } signals;
1136 };
1137
1138 media::TrackListStub::TrackListStub(
1139@@ -104,6 +170,18 @@
1140 return md;
1141 }
1142
1143+media::Track::UriType media::TrackListStub::query_uri_for_track(const media::Track::Id& id)
1144+{
1145+ auto op = d->object->invoke_method_synchronously<
1146+ mpris::TrackList::GetTracksUri,
1147+ std::string>(id);
1148+
1149+ if (op.is_error())
1150+ throw std::runtime_error("Problem querying track for uri: " + op.error());
1151+
1152+ return op.value();
1153+}
1154+
1155 void media::TrackListStub::add_track_with_uri_at(
1156 const media::Track::UriType& uri,
1157 const media::Track::Id& id,
1158@@ -161,33 +239,38 @@
1159
1160 void media::TrackListStub::reset()
1161 {
1162- std::cerr << "reset() does nothing from the client side" << std::endl;
1163+ auto op = d->object->invoke_method_synchronously<mpris::TrackList::Reset, void>();
1164+
1165+ if (op.is_error())
1166+ throw std::runtime_error("Problem resetting tracklist: " + op.error());
1167 }
1168
1169 const core::Signal<media::TrackList::ContainerTrackIdTuple>& media::TrackListStub::on_track_list_replaced() const
1170 {
1171- std::cout << "Signal on_track_list_replaced arrived via the bus" << std::endl;
1172- return d->on_track_list_replaced;
1173+ return d->signals.on_track_list_replaced;
1174 }
1175
1176 const core::Signal<media::Track::Id>& media::TrackListStub::on_track_added() const
1177 {
1178- std::cout << "Signal on_track_added arrived via the bus" << std::endl;
1179- return d->on_track_added;
1180+ return d->signals.on_track_added;
1181 }
1182
1183 const core::Signal<media::Track::Id>& media::TrackListStub::on_track_removed() const
1184 {
1185- std::cout << "Signal on_track_removed arrived via the bus" << std::endl;
1186- return d->on_track_removed;
1187+ return d->signals.on_track_removed;
1188 }
1189
1190 const core::Signal<media::Track::Id>& media::TrackListStub::on_track_changed() const
1191 {
1192- return d->on_track_changed;
1193+ return d->signals.on_track_changed;
1194 }
1195
1196 const core::Signal<std::pair<media::Track::Id, bool>>& media::TrackListStub::on_go_to_track() const
1197 {
1198- return d->on_go_to_track;
1199+ return d->signals.on_go_to_track;
1200+}
1201+
1202+const core::Signal<void>& media::TrackListStub::on_end_of_tracklist() const
1203+{
1204+ return d->signals.on_end_of_tracklist;
1205 }
1206
1207=== modified file 'src/core/media/track_list_stub.h'
1208--- src/core/media/track_list_stub.h 2015-04-29 21:18:20 +0000
1209+++ src/core/media/track_list_stub.h 2015-09-03 07:33:12 +0000
1210@@ -45,6 +45,7 @@
1211 const core::Property<Container>& tracks() const;
1212
1213 Track::MetaData query_meta_data_for_track(const Track::Id& id);
1214+ Track::UriType query_uri_for_track(const Track::Id& id);
1215
1216 void add_track_with_uri_at(const Track::UriType& uri, const Track::Id& position, bool make_current);
1217 void remove_track(const Track::Id& id);
1218@@ -64,6 +65,7 @@
1219 const core::Signal<Track::Id>& on_track_removed() const;
1220 const core::Signal<Track::Id>& on_track_changed() const;
1221 const core::Signal<std::pair<Track::Id, bool>>& on_go_to_track() const;
1222+ const core::Signal<void>& on_end_of_tracklist() const;
1223
1224 private:
1225 struct Private;

Subscribers

People subscribed via source and target branches