1 //////////////////////////////////////////////////////////////////////////////
2 //
3 // Copyright (c) 2004-2021 musikcube team
4 //
5 // All rights reserved.
6 //
7 // Redistribution and use in source and binary forms, with or without
8 // modification, are permitted provided that the following conditions are met:
9 //
10 //    * Redistributions of source code must retain the above copyright notice,
11 //      this list of conditions and the following disclaimer.
12 //
13 //    * Redistributions in binary form must reproduce the above copyright
14 //      notice, this list of conditions and the following disclaimer in the
15 //      documentation and/or other materials provided with the distribution.
16 //
17 //    * Neither the name of the author nor the names of other contributors may
18 //      be used to endorse or promote products derived from this software
19 //      without specific prior written permission.
20 //
21 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22 // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
25 // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26 // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27 // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
28 // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
29 // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30 // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31 // POSSIBILITY OF SUCH DAMAGE.
32 //
33 //////////////////////////////////////////////////////////////////////////////
34 
35 #include <pch.hpp>
36 
37 #include "PlaybackService.h"
38 
39 #include <musikcore/audio/MasterTransport.h>
40 #include <musikcore/library/LocalLibraryConstants.h>
41 #include <musikcore/library/track/Track.h>
42 #include <musikcore/library/query/MarkTrackPlayedQuery.h>
43 #include <musikcore/plugin/PluginFactory.h>
44 #include <musikcore/runtime/MessageQueue.h>
45 #include <musikcore/runtime/Message.h>
46 #include <musikcore/support/PreferenceKeys.h>
47 #include <musikcore/support/Playback.h>
48 #include <musikcore/support/LastFm.h>
49 
50 using namespace musik::core::library;
51 using namespace musik::core;
52 using namespace musik::core::prefs;
53 using namespace musik::core::sdk;
54 using namespace musik::core::runtime;
55 using namespace musik::core::audio;
56 using namespace musik::core::library::query;
57 
58 using musik::core::TrackPtr;
59 using musik::core::ILibraryPtr;
60 using musik::core::audio::ITransport;
61 using Editor = PlaybackService::Editor;
62 
63 #define NO_POSITION (size_t) -1
64 #define START_OVER (size_t) -2
65 
66 #define PREVIOUS_GRACE_PERIOD 2.0f
67 
68 #define MESSAGE_STREAM_EVENT 1000
69 #define MESSAGE_PLAYBACK_EVENT 1001
70 #define MESSAGE_PREPARE_NEXT_TRACK 1002
71 #define MESSAGE_VOLUME_CHANGED 1003
72 #define MESSAGE_TIME_CHANGED 1004
73 #define MESSAGE_MODE_CHANGED 1005
74 #define MESSAGE_SHUFFLED 1006
75 #define MESSAGE_NOTIFY_EDITED 1007
76 #define MESSAGE_NOTIFY_RESET 1008
77 #define MESSAGE_SEEK 1009
78 #define MESSAGE_RELOAD_OUTPUT 1010
79 #define MESSAGE_LOAD_PLAYBACK_CONTEXT 1011
80 #define MESSAGE_MARK_TRACK_PLAYED 1012
81 
82 class StreamMessage: public Message {
83     public:
StreamMessage(IMessageTarget * target,int eventType,const std::string & uri)84         StreamMessage(IMessageTarget* target, int eventType, const std::string& uri)
85         : Message(target, MESSAGE_STREAM_EVENT, eventType, 0) {
86             this->uri = uri;
87         }
88 
GetUri()89         std::string GetUri() {
90             return this->uri;
91         }
92 
GetEventType()93         int GetEventType() {
94             return static_cast<int>(this->UserData1());
95         }
96 
97     private:
98         std::string uri;
99 };
100 
101 #define POST(instance, type, user1, user2) \
102     this->messageQueue.Post( \
103         musik::core::runtime::Message::Create(instance, type, user1, user2));
104 
105 #define POST_DELAYED(instance, type, user1, user2, afterMs) \
106     this->messageQueue.Post( \
107         musik::core::runtime::Message::Create(instance, type, user1, user2), afterMs);
108 
109 #define POST_STREAM_MESSAGE(instance, eventType, uri) \
110     this->messageQueue.Post( \
111         musik::core::runtime::IMessagePtr(new StreamMessage(instance, eventType, uri)));
112 
loadPreferences(ITransport & transport,IPlaybackService & playback,Preferences & prefs)113 static inline void loadPreferences(ITransport& transport, IPlaybackService& playback, Preferences& prefs) {
114     double volume = prefs.GetDouble(keys::Volume, 1.0f);
115     volume = std::max(0.0f, std::min(1.0f, (float)volume));
116     transport.SetVolume(volume);
117 
118     auto repeatMode = static_cast<RepeatMode>(
119         prefs.GetInt(keys::RepeatMode, static_cast<int>(RepeatMode::None)));
120 
121     repeatMode = (repeatMode > RepeatMode::List || repeatMode < RepeatMode::None) ? RepeatMode::None : repeatMode;
122 
123     playback.SetRepeatMode(repeatMode);
124 
125     const auto timeChangeMode = static_cast<TimeChangeMode>(
126         prefs.GetInt(keys::TimeChangeMode, static_cast<int>(TimeChangeMode::Scrub)));
127 
128     playback.SetTimeChangeMode(timeChangeMode);
129 }
130 
savePreferences(IPlaybackService & playback,Preferences & prefs)131 static inline void savePreferences(IPlaybackService& playback, Preferences& prefs) {
132     prefs.SetDouble(keys::Volume, playback.GetVolume());
133     prefs.SetInt(keys::RepeatMode, static_cast<int>(playback.GetRepeatMode()));
134     prefs.SetInt(keys::TimeChangeMode, static_cast<int>(playback.GetTimeChangeMode()));
135 }
136 
137 /* internally PlaybackService leverages a message queue for synchronization;
138 tracks are a special in that they are heavy-weight so aggressively exjected
139 from caches... sometimes we may have to query for them. if they take more than
140 the specified timeout we consider it a failure and stop playback. */
141 #define TRACK_TIMEOUT_MS() (size_t) this->appPrefs->GetInt(keys::PlaybackTrackQueryTimeoutMs, 5000)
142 
PlaybackService(IMessageQueue & messageQueue,ILibraryPtr library,std::shared_ptr<ITransport> transport)143 PlaybackService::PlaybackService(
144     IMessageQueue& messageQueue,
145     ILibraryPtr library,
146     std::shared_ptr<ITransport> transport)
147 : library(library)
148 , transport(transport)
149 , playlist(library)
150 , unshuffled(library)
151 , repeatMode(RepeatMode::None)
152 , messageQueue(messageQueue)
153 , timeChangeMode(TimeChangeMode::Seek)
154 , seekPosition(-1.0f)
155 , index(NO_POSITION)
156 , nextIndex(NO_POSITION)
157 , playbackPrefs(Preferences::ForComponent(components::Playback))
158 , appPrefs(Preferences::ForComponent(components::Settings)) {
159     transport->StreamEvent.connect(this, &PlaybackService::OnStreamEvent);
160     transport->PlaybackEvent.connect(this, &PlaybackService::OnPlaybackEvent);
161     transport->VolumeChanged.connect(this, &PlaybackService::OnVolumeChanged);
162     transport->TimeChanged.connect(this, &PlaybackService::OnTimeChanged);
163     library->Indexer()->Finished.connect(this, &PlaybackService::OnIndexerFinished);
164     messageQueue.Register(this);
165     messageQueue.Post(Message::Create(this, MESSAGE_LOAD_PLAYBACK_CONTEXT));
166 }
167 
PlaybackService(IMessageQueue & messageQueue,ILibraryPtr library)168 PlaybackService::PlaybackService(
169     IMessageQueue& messageQueue,
170     ILibraryPtr library)
171 : PlaybackService(messageQueue, library, std::make_shared<MasterTransport>()) {
172     messageQueue.Register(this);
173 }
174 
~PlaybackService()175 PlaybackService::~PlaybackService() {
176     playback::SavePlaybackContext(library, *this);
177     this->messageQueue.Unregister(this);
178     savePreferences(*this, *playbackPrefs);
179     this->Stop();
180     this->ResetRemotes();
181 }
182 
InitRemotes()183 void PlaybackService::InitRemotes() {
184     typedef PluginFactory::ReleaseDeleter<IPlaybackRemote> Deleter;
185 
186     this->remotes = PluginFactory::Instance()
187         .QueryInterface<IPlaybackRemote, Deleter>("GetPlaybackRemote");
188 
189     for (auto it : remotes) {
190         it->SetPlaybackService(this);
191     }
192 }
193 
ResetRemotes()194 void PlaybackService::ResetRemotes() {
195     for (auto it : remotes) {
196         it->SetPlaybackService(nullptr);
197     }
198 }
199 
PrepareNextTrack()200 void PlaybackService::PrepareNextTrack() {
201     std::unique_lock<std::recursive_mutex> lock(this->playlistMutex);
202 
203     if (this->Count() > 0) {
204         /* repeat track, just keep playing the same thing over and over */
205         if (this->repeatMode == RepeatMode::Track) {
206             this->nextIndex = this->index;
207             this->transport->PrepareNextTrack(
208                 this->UriAtIndex(this->index),
209                 this->GainAtIndex(this->index));
210         }
211         else {
212             /* annoying and confusing special case -- the user edited the
213             playlist and deleted the currently playing item. let's start
214             again from the top... */
215             if (this->index == START_OVER) {
216                 if (this->playlist.Count() > 0) {
217                     this->index = NO_POSITION;
218                     this->nextIndex = 0;
219                     this->transport->PrepareNextTrack(
220                         this->UriAtIndex(nextIndex),
221                         this->GainAtIndex(nextIndex));
222                 }
223             }
224             /* normal case, just move forward */
225             else if (this->playlist.Count() > this->index + 1) {
226                 if (this->nextIndex != this->index + 1) {
227                     this->nextIndex = this->index + 1;
228                     this->transport->PrepareNextTrack(
229                         this->UriAtIndex(nextIndex),
230                         this->GainAtIndex(nextIndex));
231                 }
232             }
233             /* repeat list case, wrap around to the beginning if necessary */
234             else if (this->repeatMode == RepeatMode::List) {
235                 if (this->nextIndex != 0) {
236                     this->nextIndex = 0;
237                     this->transport->PrepareNextTrack(
238                         this->UriAtIndex(nextIndex),
239                         this->GainAtIndex(nextIndex));
240                 }
241             }
242             else {
243                 /* nothing to prepare if we get here. */
244                 this->transport->PrepareNextTrack("", ITransport::Gain());
245             }
246         }
247     }
248 }
249 
SetRepeatMode(RepeatMode mode)250 void PlaybackService::SetRepeatMode(RepeatMode mode) {
251     if (this->repeatMode != mode) {
252         this->repeatMode = mode;
253         POST(this, MESSAGE_PREPARE_NEXT_TRACK, NO_POSITION, 0);
254         POST(this, MESSAGE_MODE_CHANGED, 0, 0);
255     }
256 }
257 
GetTimeChangeMode()258 musik::core::sdk::TimeChangeMode PlaybackService::GetTimeChangeMode() noexcept {
259     return this->timeChangeMode;
260 }
261 
SetTimeChangeMode(TimeChangeMode mode)262 void PlaybackService::SetTimeChangeMode(TimeChangeMode mode) noexcept {
263     this->timeChangeMode = mode;
264 }
265 
ToggleShuffle()266 void PlaybackService::ToggleShuffle() {
267     std::unique_lock<std::recursive_mutex> lock(this->playlistMutex);
268 
269     /* remember the ID of the playing track -- we're going to need to look
270     it up after the shuffle */
271     int64_t id = -1;
272     if (this->index < this->playlist.Count()) {
273         id = this->playlist.GetId(this->index);
274     }
275 
276     this->playlist.ClearCache();
277     this->unshuffled.ClearCache();
278     bool shuffled = false;
279 
280     if (this->unshuffled.Count() > 0) { /* shuffled -> unshuffled */
281         this->playlist.Clear();
282         this->playlist.Swap(this->unshuffled);
283     }
284     else { /* unshuffled -> shuffle */
285         this->unshuffled.CopyFrom(this->playlist);
286         this->playlist.Shuffle();
287         shuffled = true;
288     }
289 
290     /* find the new playback index and prefetch the next track */
291     if (id != -1) {
292         const int index = this->playlist.IndexOf(id);
293         if (index != -1) {
294             this->index = index;
295             POST(this, MESSAGE_PREPARE_NEXT_TRACK, NO_POSITION, 0);
296         }
297     }
298 
299     POST(this, MESSAGE_SHUFFLED, shuffled ? 1 : 0, 0);
300     POST(this, MESSAGE_NOTIFY_EDITED, 0, 0);
301 }
302 
ProcessMessage(IMessage & message)303 void PlaybackService::ProcessMessage(IMessage &message) {
304     const int type = message.Type();
305     if (type == MESSAGE_LOAD_PLAYBACK_CONTEXT) {
306         loadPreferences(*this->transport, *this, *playbackPrefs);
307         playback::LoadPlaybackContext(library, *this);
308         this->InitRemotes();
309     }
310     else if (type == MESSAGE_MARK_TRACK_PLAYED) {
311         this->MarkTrackAsPlayed(message.UserData1()); /* UserData1 is a trackId */
312     }
313     else if (type == MESSAGE_STREAM_EVENT) {
314         StreamMessage* streamMessage = dynamic_cast<StreamMessage*>(&message);
315         const StreamState eventType = static_cast<StreamState>(streamMessage->GetEventType());
316 
317         if (eventType == StreamState::Buffering || eventType == StreamState::Buffered || eventType == StreamState::Playing) {
318             TrackPtr track;
319 
320             {
321                 std::unique_lock<std::recursive_mutex> lock(this->playlistMutex);
322                 if (this->nextIndex != NO_POSITION) {
323                     /* in most cases when we get here it means that the next track is
324                     starting, so we want to update our internal index. however, because
325                     things are asynchronous, this may not always be the case, especially if
326                     the tracks are very short, or the user is advancing through songs very
327                     quickly. compare the track URIs before we update internal state. */
328                     if (this->nextIndex >= this->Count()) {
329                         this->nextIndex = NO_POSITION;
330                         this->transport->PrepareNextTrack("", ITransport::Gain());
331                         return;
332                     }
333 
334                     if (this->UriAtIndex(this->nextIndex) == streamMessage->GetUri()) {
335                         this->index = this->nextIndex;
336                         this->nextIndex = NO_POSITION;
337                     }
338                 }
339 
340                 if (this->index != NO_POSITION) {
341                     track = TrackAtIndexWithTimeout(this->index);
342                 }
343             }
344 
345             if (track) {
346                 this->OnTrackChanged(this->index, track);
347             }
348             else {
349                 this->Stop();
350                 return;
351             }
352 
353             if (eventType == StreamState::Playing) {
354                 this->PrepareNextTrack();
355             }
356         }
357 
358         bool raiseStreamEvent = false;
359         {
360             std::unique_lock<std::recursive_mutex> lock(this->playlistMutex);
361             raiseStreamEvent = this->UriAtIndex(this->index) == streamMessage->GetUri();
362         }
363 
364         if (raiseStreamEvent) {
365             this->StreamStateChanged(eventType);
366         }
367     }
368     else if (type == MESSAGE_PLAYBACK_EVENT) {
369         const PlaybackState eventType = static_cast<PlaybackState>(message.UserData1());
370 
371         if (eventType == PlaybackState::Stopped) {
372             this->OnTrackChanged(NO_POSITION, TrackPtr());
373         }
374         else if (eventType == PlaybackState::Prepared) {
375             /* notify track change as soon as we're prepared. if we wait until
376             we start playing, it may be a while until the UI knows to redraw! */
377             if (this->UriAtIndex(this->index) == transport->Uri()) {
378                 auto track = TrackAtIndexWithTimeout(this->index);
379                 if (track) {
380                     this->OnTrackChanged(this->index, track);
381                 }
382                 else {
383                     this->Stop();
384                 }
385             }
386         }
387 
388         for (auto it : remotes) {
389             it->OnPlaybackStateChanged(static_cast<PlaybackState>(eventType));
390         }
391 
392         this->PlaybackStateChanged(static_cast<PlaybackState>(eventType));
393     }
394     else if (type == MESSAGE_PREPARE_NEXT_TRACK) {
395         if (transport->GetPlaybackState() != PlaybackState::Stopped) {
396             const size_t updatedIndex = static_cast<size_t>(message.UserData1());
397 
398             if (updatedIndex != NO_POSITION) {
399                 this->index = updatedIndex;
400                 this->nextIndex = NO_POSITION; /* force recalc */
401             }
402 
403             this->PrepareNextTrack();
404         }
405     }
406     else if (type == MESSAGE_VOLUME_CHANGED) {
407         const double volume = transport->Volume();
408         for (auto it : remotes) {
409             it->OnVolumeChanged(volume);
410         }
411         this->VolumeChanged();
412     }
413     else if (type == MESSAGE_MODE_CHANGED) {
414         this->NotifyRemotesModeChanged();
415         this->ModeChanged();
416     }
417     else if (type == MESSAGE_SHUFFLED) {
418         this->NotifyRemotesModeChanged();
419         this->Shuffled(!!message.UserData1());
420     }
421     else if (type == MESSAGE_TIME_CHANGED) {
422         this->TimeChanged(transport->Position());
423         const double volume = transport->Volume();
424         for (auto remote : this->remotes) {
425             remote->OnPlaybackTimeChanged(transport->Position());
426         }
427     }
428     else if (type == MESSAGE_NOTIFY_EDITED ||
429              type == MESSAGE_NOTIFY_RESET)
430     {
431         for (auto it : remotes) {
432             it->OnPlayQueueChanged();
433         }
434 
435         this->QueueEdited();
436     }
437     else if (type == MESSAGE_SEEK) {
438         if (this->seekPosition != -1.0f) {
439             this->transport->SetPosition(this->seekPosition + 0.5f);
440             this->seekPosition = -1.0f;
441         }
442     }
443     else if (type == MESSAGE_RELOAD_OUTPUT) {
444         const auto state = this->GetPlaybackState();
445         const auto index = this->GetIndex();
446         const double time = this->GetPosition();
447 
448         /* we generally have a MasterTransport available, but apps may
449         choose to implement their own transport and use it instead, so
450         do this nasty check first... */
451         MasterTransport* masterTransport =
452             dynamic_cast<MasterTransport*>(this->transport.get());
453 
454         if (masterTransport) {
455             const TransportType transportType = static_cast<TransportType>(
456                 playbackPrefs->GetInt(
457                     keys::Transport,
458                     static_cast<int>(TransportType::Gapless)));
459 
460             if (masterTransport->GetType() != transportType) {
461                 masterTransport->SwitchTo(transportType);
462             }
463         }
464 
465         if (state != PlaybackState::Stopped) {
466             this->Stop();
467             this->transport->ReloadOutput();
468 
469             if (index != NO_POSITION) {
470                 const auto startMode =
471                     (state != PlaybackState::Paused && state != PlaybackState::Prepared)
472                         ? ITransport::StartMode::Immediate
473                         : ITransport::StartMode::Wait;
474 
475                 this->PlayAt(index, startMode);
476                 if (time > 0.0f) {
477                     this->transport->SetPosition(time);
478                 }
479 
480                 if (startMode == ITransport::StartMode::Wait) {
481                     this->transport->Pause();
482                 }
483             }
484         }
485         else {
486             this->transport->ReloadOutput();
487         }
488     }
489 }
490 
NotifyRemotesModeChanged()491 void PlaybackService::NotifyRemotesModeChanged() {
492     const RepeatMode mode = this->repeatMode;
493     const bool shuffled = this->IsShuffled();
494     for (auto it : remotes) {
495         it->OnModeChanged(repeatMode, shuffled);
496     }
497 }
498 
OnTrackChanged(size_t pos,TrackPtr track)499 void PlaybackService::OnTrackChanged(size_t pos, TrackPtr track) {
500     this->playingTrack = track;
501     this->TrackChanged(this->index, track);
502     this->messageQueue.Remove(this, MESSAGE_MARK_TRACK_PLAYED);
503 
504     if (track && this->transport->GetStreamState() == StreamState::Playing) {
505         /* TODO: maybe consider folding Scrobble() the `MarkTrackAsPlayed` logic?
506         needs a bit more thought */
507         lastfm::Scrobble(track);
508 
509         /* we consider a track to be played if (1) it enters the playing state and
510         it's less than 10 seconds long, or (2) it enters the playing state, and
511         remains playing for > 10 seconds */
512         const double duration = this->transport->GetDuration();
513         if (duration > 0 && duration < 10.0) {
514             this->MarkTrackAsPlayed(track->GetId());
515         }
516         else {
517             POST_DELAYED(this, MESSAGE_MARK_TRACK_PLAYED, track->GetId(), 0, 10000LL);
518         }
519     }
520 
521     for (auto it : remotes) {
522         it->OnTrackChanged(track.get());
523     }
524 }
525 
MarkTrackAsPlayed(int64_t trackId)526 void PlaybackService::MarkTrackAsPlayed(int64_t trackId) {
527     this->library->Enqueue(std::make_shared<MarkTrackPlayedQuery>(trackId));
528 }
529 
Next()530 bool PlaybackService::Next() {
531     if (transport->GetPlaybackState() == PlaybackState::Stopped) {
532         return false;
533     }
534 
535     std::unique_lock<std::recursive_mutex> lock(this->playlistMutex);
536 
537     if (this->playlist.Count() > index + 1) {
538         this->Play(index + 1);
539         return true;
540     }
541     else if (this->repeatMode == RepeatMode::List) {
542         this->Play(0); /* wrap around */
543         return true;
544     }
545 
546     return false;
547 }
548 
Previous()549 bool PlaybackService::Previous() {
550     if (transport->GetPlaybackState() == PlaybackState::Stopped) {
551         return false;
552     }
553 
554     std::unique_lock<std::recursive_mutex> lock(this->playlistMutex);
555 
556     if (transport->Position() > PREVIOUS_GRACE_PERIOD) {
557         this->Play(index);
558         return true;
559     }
560 
561     if (index > 0) {
562         this->Play(index - 1);
563         return true;
564     }
565     else if (this->repeatMode == RepeatMode::List) {
566         this->Play(this->Count() - 1); /* wrap around */
567         return true;
568     }
569 
570     return false;
571 }
572 
IsShuffled()573 bool PlaybackService::IsShuffled() {
574     std::unique_lock<std::recursive_mutex> lock(this->playlistMutex);
575     return this->unshuffled.Count() > 0;
576 }
577 
Count()578 size_t PlaybackService::Count() {
579     std::unique_lock<std::recursive_mutex> lock(this->playlistMutex);
580     return this->playlist.Count();
581 }
582 
ToggleRepeatMode()583 void PlaybackService::ToggleRepeatMode() {
584     const RepeatMode mode = GetRepeatMode();
585     switch (mode) {
586         case RepeatMode::None: SetRepeatMode(RepeatMode::List); break;
587         case RepeatMode::List: SetRepeatMode(RepeatMode::Track); break;
588         default: SetRepeatMode(RepeatMode::None); break;
589     }
590 }
591 
GetPlaybackState()592 PlaybackState PlaybackService::GetPlaybackState() {
593     return transport->GetPlaybackState();
594 }
595 
HotSwap(const TrackList & tracks,size_t index)596 bool PlaybackService::HotSwap(const TrackList& tracks, size_t index) {
597     if (&tracks == &playlist) {
598         return true;
599     }
600 
601     if (!tracks.Count()) {
602         return false;
603     }
604 
605     bool found = false;
606     auto playingTrack = this->GetPlaying();
607     if (playingTrack && tracks.Count() > index) {
608         const auto supplantId = tracks.GetId(index);
609         const auto playingId = playingTrack->GetId();
610 
611         /* look at the index hint, see if we can find a matching track without
612         iteration. */
613         if (supplantId == playingId) {
614             found = true;
615         }
616         /* otherwise search the input */
617         else {
618             for (size_t i = 0; i < tracks.Count(); i++) {
619                 if (tracks.GetId(i) == playingId) {
620                     index = i;
621                     found = true;
622                 }
623             }
624         }
625     }
626 
627     {
628         std::unique_lock<std::recursive_mutex> lock(this->playlistMutex);
629         TrackList temp(this->library);
630         temp.CopyFrom(tracks);
631         this->playlist.Swap(temp);
632         this->unshuffled.Clear();
633         this->index = found ? index : NO_POSITION;
634         this->nextIndex = NO_POSITION;
635     }
636 
637     if (found) {
638         POST(this, MESSAGE_PREPARE_NEXT_TRACK, this->index, 0);
639     }
640 
641     POST(this, MESSAGE_NOTIFY_EDITED, NO_POSITION, 0);
642 
643     return true;
644 }
645 
Play(const TrackList & tracks,size_t index)646 void PlaybackService::Play(const TrackList& tracks, size_t index) {
647     {
648         std::unique_lock<std::recursive_mutex> lock(this->playlistMutex);
649 
650         if (&tracks != &playlist) {
651             TrackList temp(this->library);
652             temp.CopyFrom(tracks);
653             this->playlist.Swap(temp);
654             this->unshuffled.Clear();
655         }
656     }
657 
658     if (index <= tracks.Count()) {
659         this->Play(index);
660     }
661 
662     POST(this, MESSAGE_NOTIFY_RESET, 0, 0);
663 }
664 
Play(const musik::core::sdk::ITrackList * source,size_t index)665 void PlaybackService::Play(const musik::core::sdk::ITrackList* source, size_t index) {
666     if (source) {
667         /* see if we have a TrackList -- if we do we can optimize the copy */
668         const TrackList* sourceTrackList = dynamic_cast<const TrackList*>(source);
669 
670         if (sourceTrackList) {
671             this->Play(*sourceTrackList, index);
672             return;
673         }
674 
675         /* otherwise use slower impl to be compatible with SDK */
676         {
677             std::unique_lock<std::recursive_mutex> lock(this->playlistMutex);
678             if (source != &playlist) {
679                 this->CopyFrom(source);
680                 this->unshuffled.Clear();
681             }
682         }
683 
684         if (index <= source->Count()) {
685             this->Play(index);
686         }
687 
688         POST(this, MESSAGE_NOTIFY_RESET, 0, 0);
689     }
690 }
691 
ReloadOutput()692 void PlaybackService::ReloadOutput() {
693     messageQueue.Debounce(Message::Create(this, MESSAGE_RELOAD_OUTPUT), 500);
694 }
695 
CopyTo(TrackList & target)696 void PlaybackService::CopyTo(TrackList& target) {
697     std::unique_lock<std::recursive_mutex> lock(this->playlistMutex);
698     target.CopyFrom(this->playlist);
699 }
700 
CopyFrom(const TrackList & source)701 void PlaybackService::CopyFrom(const TrackList& source) {
702     std::unique_lock<std::recursive_mutex> lock(this->playlistMutex);
703 
704     this->playlist.CopyFrom(source);
705     this->index = NO_POSITION;
706     this->nextIndex = NO_POSITION;
707 
708     if (this->playingTrack) {
709         this->index = playlist.IndexOf(this->playingTrack->GetId());
710         POST(this, MESSAGE_PREPARE_NEXT_TRACK, this->index, 0);
711     }
712 
713     POST(this, MESSAGE_NOTIFY_EDITED, NO_POSITION, 0);
714 }
715 
CopyFrom(const musik::core::sdk::ITrackList * source)716 void PlaybackService::CopyFrom(const musik::core::sdk::ITrackList* source) {
717     if (source) {
718         /* see if we have a TrackList -- if we do we can optimize the copy */
719         const TrackList* sourceTrackList = dynamic_cast<const TrackList*>(source);
720 
721         if (sourceTrackList) {
722             this->CopyFrom(*sourceTrackList);
723             return;
724         }
725 
726         /* otherwise we gotta do it one at a time */
727         std::unique_lock<std::recursive_mutex> lock(this->playlistMutex);
728 
729         this->playlist.Clear();
730         for (size_t i = 0; i < source->Count(); i++) {
731             this->playlist.Add(source->GetId(i));
732         }
733 
734         this->index = NO_POSITION;
735         this->nextIndex = NO_POSITION;
736 
737         if (this->playingTrack) {
738             this->index = playlist.IndexOf(this->playingTrack->GetId());
739             POST(this, MESSAGE_PREPARE_NEXT_TRACK, NO_POSITION, 0);
740         }
741 
742         POST(this, MESSAGE_NOTIFY_EDITED, NO_POSITION, 0);
743     }
744 }
745 
PlayAt(size_t index,ITransport::StartMode mode)746 void PlaybackService::PlayAt(size_t index, ITransport::StartMode mode) {
747     index = std::min(this->Count(), index);
748 
749     std::string uri = this->UriAtIndex(index);
750     const auto gain = this->GainAtIndex(index);
751 
752     if (uri.size()) {
753         transport->Start(uri, gain, mode);
754         this->nextIndex = NO_POSITION;
755         this->index = index;
756     }
757 }
758 
Play(size_t index)759 void PlaybackService::Play(size_t index) {
760     this->PlayAt(index, ITransport::StartMode::Immediate);
761 }
762 
Prepare(size_t index,double position)763 void PlaybackService::Prepare(size_t index, double position) {
764     this->PlayAt(index, ITransport::StartMode::Wait);
765     if (position > 0.0) {
766         this->transport->SetPosition(position);
767     }
768 }
769 
GetIndex()770 size_t PlaybackService::GetIndex() noexcept {
771     return this->index;
772 }
773 
GetVolume()774 double PlaybackService::GetVolume() {
775     return transport->Volume();
776 }
777 
PauseOrResume()778 void PlaybackService::PauseOrResume() {
779     const auto state = transport->GetPlaybackState();
780     if (state == PlaybackState::Stopped) {
781         if (this->Count()) {
782             this->Play(0);
783         }
784     }
785     else if (state == PlaybackState::Paused || state == PlaybackState::Prepared) {
786         transport->Resume();
787     }
788     else if (state == PlaybackState::Playing) {
789         transport->Pause();
790     }
791 }
792 
IsMuted()793 bool PlaybackService::IsMuted() {
794     return transport->IsMuted();
795 }
796 
ToggleMute()797 void PlaybackService::ToggleMute() {
798     transport->SetMuted(!transport->IsMuted());
799 }
800 
SetVolume(double vol)801 void PlaybackService::SetVolume(double vol) {
802     transport->SetVolume(vol);
803 }
804 
GetPosition()805 double PlaybackService::GetPosition() {
806     if (this->timeChangeMode == TimeChangeMode::Seek && this->seekPosition != -1.0f) {
807         return this->seekPosition;
808     }
809 
810     return transport->Position();
811 }
812 
SetPosition(double seconds)813 void PlaybackService::SetPosition(double seconds) {
814     if (this->timeChangeMode == TimeChangeMode::Seek) {
815         seconds = std::max(seconds, (double) 0.0);
816         this->seekPosition = seconds;
817         this->TimeChanged(seconds);
818         messageQueue.Debounce(Message::Create(this, MESSAGE_SEEK), 500);
819     }
820     else { /* TimeChangeMode::Scrub */
821         transport->SetPosition(seconds);
822     }
823 }
824 
GetDuration()825 double PlaybackService::GetDuration() {
826     TrackPtr track;
827 
828     const double duration = this->transport->GetDuration();
829 
830     if (duration > 0) {
831         return duration;
832     }
833 
834     {
835         std::unique_lock<std::recursive_mutex> lock(this->playlistMutex);
836         const size_t index = this->index;
837         if (index < this->playlist.Count()) {
838             track = TrackAtIndexWithTimeout(index);
839         }
840     }
841 
842     if (track) {
843         std::stod(track->GetString(constants::Track::DURATION));
844     }
845 
846     return 0.0f;
847 }
848 
GetTrack(size_t index)849 ITrack* PlaybackService::GetTrack(size_t index) {
850     if (this->library->GetConnectionState() != ILibrary::ConnectionState::Connected) {
851         return nullptr;
852     }
853 
854     std::unique_lock<std::recursive_mutex> lock(this->playlistMutex);
855 
856     const size_t count = this->playlist.Count();
857 
858     if (count && index < this->playlist.Count()) {
859         auto track = this->playlist.GetWithTimeout(index, TRACK_TIMEOUT_MS() * 10);
860         if (track) {
861             return track->GetSdkValue();
862         }
863     }
864 
865     return nullptr;
866 }
867 
GetPlayingTrack()868 ITrack* PlaybackService::GetPlayingTrack() {
869     std::unique_lock<std::recursive_mutex> lock(this->playlistMutex);
870 
871     if (this->playingTrack) {
872         return this->playingTrack->GetSdkValue();
873     }
874 
875     return nullptr;
876 }
877 
GetPlaying()878 TrackPtr PlaybackService::GetPlaying() {
879     std::unique_lock<std::recursive_mutex> lock(this->playlistMutex);
880     return this->playingTrack;
881 }
882 
TrackAtIndexWithTimeout(size_t index)883 TrackPtr PlaybackService::TrackAtIndexWithTimeout(size_t index) {
884     if (this->library->GetConnectionState() != ILibrary::ConnectionState::Connected) {
885         return TrackPtr();
886     }
887 
888     std::unique_lock<std::recursive_mutex> lock(this->playlistMutex);
889 
890     if (index >= this->playlist.Count()) {
891         return TrackPtr();
892     }
893 
894     return this->playlist.GetWithTimeout(index, TRACK_TIMEOUT_MS());
895 }
896 
Edit()897 Editor PlaybackService::Edit() {
898     return Editor(
899         *this,
900         this->playlist,
901         this->messageQueue,
902         this->playlistMutex);
903 }
904 
EditPlaylist()905 ITrackListEditor* PlaybackService::EditPlaylist() {
906     /* the internal implementation of this class has a stubbed
907     Release() method to avoid programmer error. if the SDK is
908     requesting an Editor we need to actually release resources. */
909     class SdkTrackListEditor : public PlaybackService::Editor {
910         public:
911             SdkTrackListEditor(
912                 PlaybackService& playback,
913                 TrackList& tracks,
914                 Queue& queue,
915                 Mutex& mutex)
916             : PlaybackService::Editor(playback, tracks, queue, mutex) {
917             }
918 
919             virtual ~SdkTrackListEditor() {
920             }
921 
922             virtual void Release() noexcept {
923                 delete this;
924             }
925     };
926 
927     return new SdkTrackListEditor(
928         *this,
929         this->playlist,
930         this->messageQueue,
931         this->playlistMutex);
932 }
933 
OnStreamEvent(StreamState eventType,std::string uri)934 void PlaybackService::OnStreamEvent(StreamState eventType, std::string uri) {
935     POST_STREAM_MESSAGE(this, (int64_t) eventType, uri);
936 }
937 
OnPlaybackEvent(PlaybackState eventType)938 void PlaybackService::OnPlaybackEvent(PlaybackState eventType) {
939     POST(this, MESSAGE_PLAYBACK_EVENT, (int64_t) eventType, 0);
940 }
941 
OnVolumeChanged()942 void PlaybackService::OnVolumeChanged() {
943     POST(this, MESSAGE_VOLUME_CHANGED, 0, 0);
944 }
945 
OnTimeChanged(double time)946 void PlaybackService::OnTimeChanged(double time) {
947     POST(this, MESSAGE_TIME_CHANGED, 0, 0);
948 }
949 
OnIndexerFinished(int trackCount)950 void PlaybackService::OnIndexerFinished(int trackCount) {
951     std::unique_lock<std::recursive_mutex> lock(this->playlistMutex);
952     this->playlist.ClearCache();
953     this->unshuffled.ClearCache();
954 }
955 
956 /* our Editor interface. we proxy all of the ITrackListEditor methods so we
957 can track and maintain our currently playing index as tracks move around
958 in the backing store. lots of annoying book keeping. we have to do it this
959 way because it's possible to have the same item in the playlist multiple
960 times -- otherwise we could just cache the ID and then look it up when we
961 have finished all operations. */
962 
Editor(PlaybackService & playback,TrackList & tracks,Queue & queue,Mutex & mutex)963 PlaybackService::Editor::Editor(
964     PlaybackService& playback,
965     TrackList& tracks,
966     Queue& queue,
967     Mutex& mutex)
968 : playback(playback)
969 , queue(queue)
970 , lock(mutex)
971 , edited(false) {
972     this->tracks = IEditor(new musik::core::TrackListEditor(tracks));
973     this->playIndex = playback.GetIndex();
974     this->nextTrackInvalidated = false;
975 }
976 
Editor(Editor && other)977 PlaybackService::Editor::Editor(Editor&& other)
978 : playback(other.playback)
979 , tracks(other.tracks)
980 , queue(other.queue)
981 , playIndex(other.playIndex)
982 , edited(false) {
983     std::swap(this->lock, other.lock);
984 
985     /* we may never actually edit the playing track, but instead, edit the
986     track that's up next. if that happens, we need to reload the next track.
987     we COULD do this unconditionally, but it requires I/O, so it's best to
988     avoid if possible. */
989     this->nextTrackInvalidated = other.nextTrackInvalidated;
990 }
991 
~Editor()992 PlaybackService::Editor::~Editor() {
993     if (this->edited) {
994         /* we've been tracking the playback index through edit operations. let's
995         update it here. */
996 
997         /* make sure the play index we're requesting is in bounds */
998         if (this->playIndex != this->playback.GetIndex() || this->nextTrackInvalidated) {
999             if (this->playback.Count() > 0 && this->playIndex != NO_POSITION) {
1000                 this->playIndex = std::min(this->playback.Count() - 1, this->playIndex);
1001             }
1002 
1003             this->queue.Post(Message::Create(
1004                 &this->playback, MESSAGE_PREPARE_NEXT_TRACK, this->playIndex, 0));
1005         }
1006 
1007         this->playback.messageQueue.Post(Message::Create(
1008             &this->playback, MESSAGE_NOTIFY_EDITED, 0, 0));
1009     }
1010 
1011     /* implicitly unlocks the mutex when this block exists */
1012 }
1013 
Insert(int64_t id,size_t index)1014 bool PlaybackService::Editor::Insert(int64_t id, size_t index) {
1015     if ((this->edited = this->tracks->Insert(id, index))) {
1016         if (index == this->playIndex) {
1017             ++this->playIndex;
1018         }
1019 
1020         if (index == this->playIndex + 1) {
1021             this->nextTrackInvalidated = true;
1022         }
1023 
1024         return true;
1025     }
1026     return false;
1027 }
1028 
Swap(size_t index1,size_t index2)1029 bool PlaybackService::Editor::Swap(size_t index1, size_t index2) {
1030     if ((this->edited = this->tracks->Swap(index1, index2))) {
1031         if (index1 == this->playIndex) {
1032             this->playIndex = index2;
1033             this->nextTrackInvalidated = true;
1034         }
1035         else if (index2 == this->playIndex) {
1036             this->playIndex = index1;
1037             this->nextTrackInvalidated = true;
1038         }
1039 
1040         return true;
1041     }
1042     return false;
1043 }
1044 
Move(size_t from,size_t to)1045 bool PlaybackService::Editor::Move(size_t from, size_t to) {
1046     if ((this->edited = this->tracks->Move(from, to))) {
1047         if (from == this->playIndex) {
1048             this->playIndex = to;
1049         }
1050         else if (to == this->playIndex) {
1051             this->playIndex += (from > to) ? 1 : -1;
1052         }
1053 
1054         if (to == this->playIndex + 1) {
1055             this->nextTrackInvalidated = true;
1056         }
1057 
1058         return true;
1059     }
1060     return false;
1061 }
1062 
Delete(size_t index)1063 bool PlaybackService::Editor::Delete(size_t index) {
1064     if ((this->edited = this->tracks->Delete(index))) {
1065         if (this->playback.Count() == 0) {
1066             this->playIndex = NO_POSITION;
1067         }
1068         if (index == this->playIndex) {
1069             this->playIndex = START_OVER;
1070         }
1071         else if (index == this->playIndex + 1) {
1072             this->nextTrackInvalidated = true;
1073         }
1074         else if (index < this->playIndex) {
1075             --this->playIndex;
1076         }
1077         return true;
1078     }
1079     return false;
1080 }
1081 
Add(const int64_t id)1082 void PlaybackService::Editor::Add(const int64_t id) {
1083     this->tracks->Add(id);
1084 
1085     if (this->playback.Count() - 1 == this->playIndex + 1) {
1086         this->nextTrackInvalidated = true;
1087     }
1088 
1089     this->edited = true;
1090 }
1091 
Clear()1092 void PlaybackService::Editor::Clear() {
1093     playback.playlist.Clear();
1094     playback.unshuffled.Clear();
1095     this->playIndex = -1;
1096     this->nextTrackInvalidated = true;
1097     this->edited = true;
1098 }
1099 
Shuffle()1100 void PlaybackService::Editor::Shuffle() {
1101     /* inefficient -- we can do it faster with a bit more logic. if
1102     this ever becomes a problem we can speed it up. */
1103     if (playback.IsShuffled()) {
1104         playback.ToggleShuffle(); /* off */
1105     }
1106 
1107     playback.ToggleShuffle(); /* on */
1108     this->playIndex = playback.GetIndex();
1109     this->nextTrackInvalidated = true;
1110     this->edited = true;
1111 }
1112 
Release()1113 void PlaybackService::Editor::Release() noexcept {
1114     /* nothing! */
1115 }
1116 
UriAtIndex(size_t index)1117 std::string PlaybackService::UriAtIndex(size_t index) {
1118     if (index < this->playlist.Count()) {
1119         auto track = TrackAtIndexWithTimeout(index);
1120         if (track) {
1121             return this->library->GetResourceLocator().GetTrackUri(track.get());
1122         }
1123     }
1124     return "";
1125 }
1126 
Clone()1127 ITrackList* PlaybackService::Clone() {
1128     std::unique_lock<std::recursive_mutex> lock(this->playlistMutex);
1129     std::shared_ptr<TrackList> to = std::make_shared<TrackList>(this->library);
1130     this->playlist.CopyTo(*to.get());
1131     return to->GetSdkValue();
1132 }
1133 
GainAtIndex(size_t index)1134 ITransport::Gain PlaybackService::GainAtIndex(size_t index) {
1135     using Mode = ReplayGainMode;
1136 
1137     ITransport::Gain result;
1138 
1139     const float preampDb = static_cast<float>(
1140         playbackPrefs->GetDouble(keys::PreampDecibels.c_str(), 0.0f));
1141 
1142     result.preamp = powf(10.0f, (preampDb / 20.0f));
1143     result.peakValid = false;
1144 
1145     const Mode mode = static_cast<Mode>(playbackPrefs->GetInt(
1146         keys::ReplayGainMode.c_str(), (int) Mode::Disabled));
1147 
1148     if (mode != Mode::Disabled && index < this->playlist.Count()) {
1149         auto track = TrackAtIndexWithTimeout(index);
1150         if (track) {
1151             const auto rg = track->GetReplayGain();
1152             const float gain = (mode == Mode::Album) ? rg.albumGain : rg.trackGain;
1153             const float peak = (mode == Mode::Album) ? rg.albumPeak : rg.trackPeak;
1154             if (gain != 1.0f) {
1155                 /* http://wiki.hydrogenaud.io/index.php?title=ReplayGain_2.0_specification#Reduced_gain */
1156                 result.gain = powf(10.0f, (gain / 20.0f));
1157                 result.peak = (1.0f / peak);
1158                 result.peakValid = true;
1159             }
1160         }
1161     }
1162 
1163     return result;
1164 }