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