Music Hub ..
A session-wide music playback service
track_list_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 */
18
19#include <algorithm>
20#include <random>
21#include <sstream>
22#include <stdio.h>
23#include <stdlib.h>
24#include <tuple>
25#include <unistd.h>
26
27#include <dbus/dbus.h>
28
30
31#include "engine.h"
32
34
35namespace dbus = core::dbus;
36namespace media = core::ubuntu::media;
37
39{
40 typedef std::map<Track::Id, std::tuple<Track::UriType, Track::MetaData>> MetaDataCache;
41
42 dbus::Object::Ptr object;
45 std::shared_ptr<media::Engine::MetaDataExtractor> extractor;
46 // Used for caching the original tracklist order to be used to restore the order
47 // to the live TrackList after shuffle is turned off
49 bool shuffle;
50
52 {
53 if (meta_data_cache.count(id) == 0)
54 {
55 // FIXME: This code seems to conflict badly when called multiple times in a row: causes segfaults
56#if 0
57 try {
58 meta_data_cache[id] = std::make_tuple(
59 uri,
60 extractor->meta_data_for_track_with_uri(uri));
61 } catch (const std::runtime_error &e) {
62 std::cerr << "Failed to retrieve metadata for track '" << uri << "' (" << e.what() << ")" << std::endl;
63 }
64#else
65 meta_data_cache[id] = std::make_tuple(
66 uri,
68#endif
69 } else
70 {
71 std::get<0>(meta_data_cache[id]) = uri;
72 }
73 }
74
75 media::TrackList::Container::iterator get_shuffled_insert_it()
76 {
77 media::TrackList::Container::iterator random_it = shuffled_tracks.begin();
78 if (random_it == shuffled_tracks.end())
79 return random_it;
80
81 // This is slightly biased, but not much, as RAND_MAX >= 32767, which is
82 // much more than the average number of tracks.
83 // Note that for N tracks we have N + 1 possible insertion positions.
84 std::advance(random_it, rand() % (shuffled_tracks.size() + 1));
85 return random_it;
86 }
87};
88
90 const dbus::Bus::Ptr& bus,
91 const dbus::Object::Ptr& object,
92 const std::shared_ptr<media::Engine::MetaDataExtractor>& extractor,
93 const media::apparmor::ubuntu::RequestContextResolver::Ptr& request_context_resolver,
94 const media::apparmor::ubuntu::RequestAuthenticator::Ptr& request_authenticator)
95 : media::TrackListSkeleton(bus, object, request_context_resolver, request_authenticator),
96 d(new Private{object, 0, Private::MetaDataCache{},
97 extractor, media::TrackList::Container{}, false})
98{
99 can_edit_tracks().set(true);
100}
101
103{
104}
105
107{
108 const auto it = d->meta_data_cache.find(id);
109
110 if (it == d->meta_data_cache.end())
111 return Track::UriType{};
112
113 return std::get<0>(it->second);
114}
115
117{
118 const auto it = d->meta_data_cache.find(id);
119
120 if (it == d->meta_data_cache.end())
121 return Track::MetaData{};
122
123 return std::get<1>(it->second);
124}
125
127 const media::Track::UriType& uri,
128 const media::Track::Id& position,
129 bool make_current)
130{
131 MH_TRACE("");
132
133 std::stringstream ss;
134 ss << d->object->path().as_string() << "/" << d->track_counter++;
135 Track::Id id{ss.str()};
136
137 MH_DEBUG("Adding Track::Id: %s", id);
138 MH_DEBUG("\tURI: %s", uri);
139
140 const auto current = get_current_track();
141
142 auto result = tracks().update([this, id, position, make_current](TrackList::Container& container)
143 {
144 auto it = std::find(container.begin(), container.end(), position);
145 container.insert(it, id);
146
147 return true;
148 });
149
150 if (result)
151 {
152 d->updateCachedTrackMetadata(id, uri);
153
154 if (d->shuffle)
155 d->shuffled_tracks.insert(d->get_shuffled_insert_it(), id);
156
157 if (make_current)
158 {
160 go_to(id);
161 } else {
163 }
164
165 MH_DEBUG("Signaling that we just added track id: %s", id);
166 // Signal to the client that a track was added to the TrackList
167 on_track_added()(id);
168
169 // Signal to the client that the current track has changed for the first
170 // track added to the TrackList
171 if (tracks().get().size() == 1)
172 on_track_changed()(id);
173 }
174}
175
177{
178 MH_TRACE("");
179
180 const auto current = get_current_track();
181
182 Track::Id current_id;
183 ContainerURI tmp;
184 for (const auto uri : uris)
185 {
186 // TODO: Refactor this code to use a smaller common function shared with add_track_with_uri_at()
187 std::stringstream ss;
188 ss << d->object->path().as_string() << "/" << d->track_counter++;
189 Track::Id id{ss.str()};
190 MH_DEBUG("Adding Track::Id: %s", id);
191 MH_DEBUG("\tURI: %s", uri);
192
193 tmp.push_back(id);
194
195 Track::Id insert_position = position;
196
197 auto it = std::find(tracks().get().begin(), tracks().get().end(), insert_position);
198 const auto result = tracks().update([this, id, position, it, &insert_position](TrackList::Container& container)
199 {
200 container.insert(it, id);
201 // Make sure the next insert position is after the current insert position
202 // Update the Track::Id after which to insert the next one from uris
203 insert_position = id;
204
205 return true;
206 });
207
208 if (result)
209 {
210 d->updateCachedTrackMetadata(id, uri);
211
212 if (d->shuffle)
213 d->shuffled_tracks.insert(d->get_shuffled_insert_it(), id);
214
215 // Signal to the client that the current track has changed for the first track added to the TrackList
216 if (tracks().get().size() == 1)
217 current_id = id;
218 }
219 }
220
222
223 MH_DEBUG("Signaling that we just added %d tracks to the TrackList", tmp.size());
224 on_tracks_added()(tmp);
225
226 if (!current_id.empty())
227 on_track_changed()(current_id);
228}
229
231 const media::Track::Id& to)
232{
233 MH_TRACE("");
234
235 MH_DEBUG("-----------------------------------------------------");
236 if (id.empty() or to.empty())
237 {
238 MH_ERROR("Can't move track since 'id' or 'to' are empty");
239 return false;
240 }
241
242 if (id == to)
243 {
244 MH_ERROR("Can't move track to it's same position");
245 return false;
246 }
247
248 if (tracks().get().size() == 1)
249 {
250 MH_ERROR("Can't move track since TrackList contains only one track");
251 return false;
252 }
253
254 bool ret = false;
255 const media::Track::Id current_id = *current_iterator();
256 MH_DEBUG("current_track id: %s", current_id);
257 // Get an iterator that points to the track that is the insertion point
258 auto insert_point_it = std::find(tracks().get().begin(), tracks().get().end(), to);
259 if (insert_point_it != tracks().get().end())
260 {
261 const auto result = tracks().update([this, id, to, current_id, &insert_point_it]
262 (TrackList::Container& container)
263 {
264 // Get an iterator that points to the track to move within the TrackList
265 auto to_move_it = std::find(tracks().get().begin(), tracks().get().end(), id);
266 if (to_move_it != tracks().get().end())
267 {
268 container.erase(to_move_it);
269 }
270 else
271 {
272 throw media::TrackList::Errors::FailedToFindMoveTrackDest
273 ("Failed to find destination track " + to);
274 }
275
276 // Insert id at the location just before insert_point_it
277 container.insert(insert_point_it, id);
278
279 const auto new_current_track_it = std::find(tracks().get().begin(), tracks().get().end(), current_id);
280 if (new_current_track_it != tracks().get().end())
281 {
282 const bool r = update_current_iterator(new_current_track_it);
283 if (!r)
284 {
285 throw media::TrackList::Errors::FailedToMoveTrack();
286 }
287 MH_DEBUG("*** Updated current_iterator, id: %s", *current_iterator());
288 }
289 else
290 {
291 MH_ERROR("Can't update current_iterator - failed to find track after move");
292 throw media::TrackList::Errors::FailedToMoveTrack();
293 }
294
295 return true;
296 });
297
298 if (result)
299 {
300 MH_DEBUG("TrackList after move");
301 for(const auto track : tracks().get())
302 {
303 MH_DEBUG("%s", track);
304 }
305 const media::TrackList::TrackIdTuple ids = std::make_tuple(id, to);
306 // Signal to the client that track 'id' was moved within the TrackList
307 on_track_moved()(ids);
308 ret = true;
309 }
310 }
311 else
312 {
313 throw media::TrackList::Errors::FailedToFindMoveTrackSource
314 ("Failed to find source track " + id);
315 }
316
317 MH_DEBUG("-----------------------------------------------------");
318
319 return ret;
320}
321
323{
324 const auto result = tracks().update([id](TrackList::Container& container)
325 {
326 container.erase(std::find(container.begin(), container.end(), id));
327 return true;
328 });
329
331
332 if (result)
333 {
334 d->meta_data_cache.erase(id);
335
336 if (d->shuffle)
337 d->shuffled_tracks.erase(find(d->shuffled_tracks.begin(),
338 d->shuffled_tracks.end(), id));
339
340 on_track_removed()(id);
341
342 // Make sure playback stops if all tracks were removed
343 if (tracks().get().empty())
345 }
346}
347
349{
350 MH_TRACE("");
351 // Signal the Player instance to go to a specific track for playback
352 on_go_to_track()(track);
353 on_track_changed()(track);
354}
355
357{
358 d->shuffle = shuffle;
359
360 if (shuffle) {
361 d->shuffled_tracks = tracks().get();
362 random_shuffle(d->shuffled_tracks.begin(), d->shuffled_tracks.end());
363 }
364}
365
367{
368 return d->shuffle;
369}
370
372{
373 return d->shuffled_tracks;
374}
375
377{
378 MH_TRACE("");
379
380 // Make sure playback stops
382 // And make sure there is no "current" track
384
385 tracks().update([this](TrackList::Container& container)
386 {
387 container.clear();
389
390 d->track_counter = 0;
391 d->shuffled_tracks.clear();
392
393 return true;
394 });
395}
const media::TrackList::Container & shuffled_tracks()
TrackListImplementation(const core::dbus::Bus::Ptr &bus, const core::dbus::Object::Ptr &object, const std::shared_ptr< Engine::MetaDataExtractor > &extractor, const core::ubuntu::media::apparmor::ubuntu::RequestContextResolver::Ptr &request_context_resolver, const core::ubuntu::media::apparmor::ubuntu::RequestAuthenticator::Ptr &request_authenticator)
void add_tracks_with_uri_at(const ContainerURI &uris, const Track::Id &position)
bool move_track(const Track::Id &id, const Track::Id &to)
Track::UriType query_uri_for_track(const Track::Id &id)
void go_to(const Track::Id &track)
void add_track_with_uri_at(const Track::UriType &uri, const Track::Id &position, bool make_current)
Track::MetaData query_meta_data_for_track(const Track::Id &id)
const core::Signal< TrackIdTuple > & on_track_moved() const
const core::Signal< Track::Id > & on_track_added() const
const core::Signal< Track::Id > & on_go_to_track() const
const core::Signal< Track::Id > & on_track_changed() const
const core::Signal< void > & on_track_list_reset() const
media::Track::Id get_current_track(void)
const core::Property< bool > & can_edit_tracks() const
const core::Signal< void > & on_end_of_tracklist() const
const TrackList::ConstIterator & current_iterator()
bool update_current_iterator(const TrackList::ConstIterator &it)
const core::Signal< ContainerURI > & on_tracks_added() const
const core::Property< Container > & tracks() const
const core::Signal< Track::Id > & on_track_removed() const
void set_current_track(const media::Track::Id &id)
std::tuple< Track::Id, Track::Id > TrackIdTuple
Definition: track_list.h:46
std::vector< Track::Id > Container
Definition: track_list.h:43
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_ERROR(...)
Definition: logger.h:128
#define MH_DEBUG(...)
Definition: logger.h:123
media::TrackList::Container::iterator get_shuffled_insert_it()
void updateCachedTrackMetadata(const media::Track::Id &id, const media::Track::UriType &uri)
std::shared_ptr< media::Engine::MetaDataExtractor > extractor
std::map< Track::Id, std::tuple< Track::UriType, Track::MetaData > > MetaDataCache