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 }