2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
13 #include "ServiceBroker.h"
16 #include "cores/AudioEngine/Interfaces/AE.h"
17 #include "cores/AudioEngine/Interfaces/AEStream.h"
18 #include "cores/AudioEngine/Utils/AEStreamData.h"
19 #include "cores/AudioEngine/Utils/AEUtil.h"
20 #include "cores/DataCacheCore.h"
21 #include "cores/VideoPlayer/Process/ProcessInfo.h"
22 #include "messaging/ApplicationMessenger.h"
23 #include "music/MusicFileItemClassify.h"
24 #include "music/tags/MusicInfoTag.h"
25 #include "settings/AdvancedSettings.h"
26 #include "settings/Settings.h"
27 #include "settings/SettingsComponent.h"
28 #include "threads/SystemClock.h"
29 #include "utils/JobManager.h"
30 #include "utils/log.h"
31 #include "video/Bookmark.h"
37 using namespace std::chrono_literals
;
39 #define TIME_TO_CACHE_NEXT_FILE 5000 /* 5 seconds before end of song, start caching the next song */
40 #define FAST_XFADE_TIME 80 /* 80 milliseconds */
41 #define MAX_SKIP_XFADE_TIME 2000 /* max 2 seconds crossfade on track skip */
43 // PAP: Psycho-acoustic Audio Player
44 // Supporting all open audio codec standards.
45 // First one being nullsoft's nsv audio decoder format
47 PAPlayer::PAPlayer(IPlayerCallback
& callback
)
48 : IPlayer(callback
), CThread("PAPlayer"), m_playbackSpeed(1), m_audioCallback(NULL
)
50 memset(&m_playerGUIData
, 0, sizeof(m_playerGUIData
));
51 m_processInfo
.reset(CProcessInfo::CreateInstance());
52 m_processInfo
->SetDataCache(&CServiceBroker::GetDataCacheCore());
60 void PAPlayer::SoftStart(bool wait
/* = false */)
62 std::unique_lock
<CCriticalSection
> lock(m_streamsLock
);
63 for(StreamList::iterator itt
= m_streams
.begin(); itt
!= m_streams
.end(); ++itt
)
65 StreamInfo
* si
= *itt
;
66 if (si
->m_fadeOutTriggered
)
69 si
->m_stream
->Resume();
70 si
->m_stream
->FadeVolume(0.0f
, 1.0f
, FAST_XFADE_TIME
);
75 /* wait for them to fade in */
77 CThread::Sleep(std::chrono::milliseconds(FAST_XFADE_TIME
));
80 /* be sure they have faded in */
84 for(StreamList::iterator itt
= m_streams
.begin(); itt
!= m_streams
.end(); ++itt
)
86 StreamInfo
* si
= *itt
;
87 if (si
->m_stream
->IsFading())
100 void PAPlayer::SoftStop(bool wait
/* = false */, bool close
/* = true */)
102 /* fade all the streams out fast for a nice soft stop */
103 std::unique_lock
<CCriticalSection
> lock(m_streamsLock
);
104 for(StreamList::iterator itt
= m_streams
.begin(); itt
!= m_streams
.end(); ++itt
)
106 StreamInfo
* si
= *itt
;
108 si
->m_stream
->FadeVolume(1.0f
, 0.0f
, FAST_XFADE_TIME
);
112 si
->m_prepareTriggered
= true;
113 si
->m_playNextTriggered
= true;
114 si
->m_fadeOutTriggered
= true;
118 /* if we are going to wait for them to finish fading */
121 // fail safe timer, do not wait longer than 1000ms
122 XbmcThreads::EndTime
<> timer(1000ms
);
124 /* wait for them to fade out */
126 CThread::Sleep(std::chrono::milliseconds(FAST_XFADE_TIME
));
129 /* be sure they have faded out */
130 while(wait
&& !CServiceBroker::GetActiveAE()->IsSuspended() && !timer
.IsTimePast())
133 for(StreamList::iterator itt
= m_streams
.begin(); itt
!= m_streams
.end(); ++itt
)
135 StreamInfo
* si
= *itt
;
136 if (si
->m_stream
&& si
->m_stream
->IsFading())
147 /* if we are not closing the streams, pause them */
150 for(StreamList::iterator itt
= m_streams
.begin(); itt
!= m_streams
.end(); ++itt
)
152 StreamInfo
* si
= *itt
;
153 si
->m_stream
->Pause();
159 void PAPlayer::CloseAllStreams(bool fade
/* = true */)
163 std::unique_lock
<CCriticalSection
> lock(m_streamsLock
);
164 while (!m_streams
.empty())
166 StreamInfo
* si
= m_streams
.front();
167 m_streams
.pop_front();
172 si
->m_stream
.reset();
175 si
->m_decoder
.Destroy();
179 while (!m_finishing
.empty())
181 StreamInfo
* si
= m_finishing
.front();
182 m_finishing
.pop_front();
187 si
->m_stream
.reset();
190 si
->m_decoder
.Destroy();
193 m_currentStream
= nullptr;
197 SoftStop(false, true);
198 std::unique_lock
<CCriticalSection
> lock(m_streamsLock
);
199 m_currentStream
= NULL
;
203 bool PAPlayer::OpenFile(const CFileItem
& file
, const CPlayerOptions
&options
)
205 m_defaultCrossfadeMS
= CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_MUSICPLAYER_CROSSFADE
) * 1000;
206 m_fullScreen
= options
.fullscreen
;
208 if (m_streams
.size() > 1 || !m_defaultCrossfadeMS
|| m_isPaused
)
210 CloseAllStreams(!m_isPaused
);
212 m_isPaused
= false; // Make sure to reset the pause state
216 std::unique_lock
<CCriticalSection
> lock(m_streamsLock
);
219 CServiceBroker::GetJobManager()->Submit([=, this]() { QueueNextFileEx(file
, false); }, this,
220 CJob::PRIORITY_NORMAL
);
222 std::unique_lock
<CCriticalSection
> lock(m_streamsLock
);
223 if (m_streams
.size() == 2)
225 //do a short crossfade on trackskip, set to max 2 seconds for these prev/next transitions
226 m_upcomingCrossfadeMS
= std::min(m_defaultCrossfadeMS
, (unsigned int)MAX_SKIP_XFADE_TIME
);
228 //start transition to next track
229 StreamInfo
* si
= m_streams
.front();
230 si
->m_playNextAtFrame
= si
->m_framesSent
; //start next track at current frame
231 si
->m_prepareTriggered
= true; //next track is ready to go
238 /* trigger playback start */
242 // OnPlayBackStarted to be made only once. Callback processing may be slower than player process
243 // so clear signal flag first otherwise async stream processing could also make callback
244 m_signalStarted
= false;
245 m_callback
.OnPlayBackStarted(file
);
250 void PAPlayer::UpdateCrossfadeTime(const CFileItem
& file
)
252 // we explicitly disable crossfading for audio cds
253 if (MUSIC::IsCDDA(file
))
254 m_upcomingCrossfadeMS
= 0;
256 m_upcomingCrossfadeMS
= m_defaultCrossfadeMS
= CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_MUSICPLAYER_CROSSFADE
) * 1000;
258 if (m_upcomingCrossfadeMS
)
260 if (!m_currentStream
|| (file
.HasMusicInfoTag() &&
261 !CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
262 CSettings::SETTING_MUSICPLAYER_CROSSFADEALBUMTRACKS
) &&
263 m_currentStream
->m_fileItem
->HasMusicInfoTag() &&
264 (m_currentStream
->m_fileItem
->GetMusicInfoTag()->GetAlbum() != "") &&
265 (m_currentStream
->m_fileItem
->GetMusicInfoTag()->GetAlbum() ==
266 file
.GetMusicInfoTag()->GetAlbum()) &&
267 (m_currentStream
->m_fileItem
->GetMusicInfoTag()->GetDiscNumber() ==
268 file
.GetMusicInfoTag()->GetDiscNumber()) &&
269 (m_currentStream
->m_fileItem
->GetMusicInfoTag()->GetTrackNumber() ==
270 file
.GetMusicInfoTag()->GetTrackNumber() - 1)))
272 //do not crossfade when playing consecutive albumtracks
273 m_upcomingCrossfadeMS
= 0;
278 bool PAPlayer::QueueNextFile(const CFileItem
&file
)
281 std::unique_lock
<CCriticalSection
> lock(m_streamsLock
);
284 CServiceBroker::GetJobManager()->Submit([this, file
]() { QueueNextFileEx(file
, true); }, this,
285 CJob::PRIORITY_NORMAL
);
290 bool PAPlayer::QueueNextFileEx(const CFileItem
&file
, bool fadeIn
)
294 // check if we advance a track of a CUE sheet
295 // if this is the case we don't need to open a new stream
296 std::string newURL
= file
.GetDynURL().GetFileName();
297 std::string oldURL
= m_currentStream
->m_fileItem
->GetDynURL().GetFileName();
298 if (newURL
.compare(oldURL
) == 0 && file
.GetStartOffset() &&
299 file
.GetStartOffset() == m_currentStream
->m_fileItem
->GetEndOffset() && m_currentStream
&&
300 m_currentStream
->m_prepareTriggered
)
302 m_currentStream
->m_nextFileItem
= std::make_unique
<CFileItem
>(file
);
303 m_upcomingCrossfadeMS
= 0;
306 m_currentStream
->m_nextFileItem
.reset();
309 StreamInfo
*si
= new StreamInfo();
310 si
->m_fileItem
= std::make_unique
<CFileItem
>(file
);
312 // Start stream at zero offset
313 si
->m_startOffset
= 0;
314 //File item start offset defines where in song to resume
315 double starttime
= CUtil::ConvertMilliSecsToSecs(si
->m_fileItem
->GetStartOffset());
317 // Music from cuesheet => "item_start" and offset match
318 // Start offset defines where this song starts in file of multiple songs
319 if (si
->m_fileItem
->HasProperty("item_start") &&
320 (si
->m_fileItem
->GetProperty("item_start").asInteger() == si
->m_fileItem
->GetStartOffset()))
322 // Start stream at offset from cuesheet
323 si
->m_startOffset
= si
->m_fileItem
->GetStartOffset();
324 starttime
= 0; // No resume point
327 if (!si
->m_decoder
.Create(file
, si
->m_startOffset
))
329 CLog::Log(LOGWARNING
, "PAPlayer::QueueNextFileEx - Failed to create the decoder");
332 AdvancePlaylistOnError(*si
->m_fileItem
);
333 m_callback
.OnQueueNextItem();
339 /* decode until there is data-available */
340 si
->m_decoder
.Start();
341 while (si
->m_decoder
.GetDataSize(true) == 0)
343 int status
= si
->m_decoder
.GetStatus();
344 if (status
== STATUS_ENDED
||
345 status
== STATUS_NO_FILE
||
346 si
->m_decoder
.ReadSamples(PACKET_SIZE
) == RET_ERROR
)
348 CLog::Log(LOGINFO
, "PAPlayer::QueueNextFileEx - Error reading samples");
350 si
->m_decoder
.Destroy();
352 AdvancePlaylistOnError(*si
->m_fileItem
);
353 m_callback
.OnQueueNextItem();
358 /* yield our time so that the main PAP thread doesn't stall */
362 // set m_upcomingCrossfadeMS depending on type of file and user settings
363 UpdateCrossfadeTime(*si
->m_fileItem
);
365 /* init the streaminfo struct */
366 si
->m_audioFormat
= si
->m_decoder
.GetFormat();
367 // si->m_startOffset already initialized
368 si
->m_endOffset
= file
.GetEndOffset();
369 si
->m_bytesPerSample
= CAEUtil::DataFormatToBits(si
->m_audioFormat
.m_dataFormat
) >> 3;
370 si
->m_bytesPerFrame
= si
->m_bytesPerSample
* si
->m_audioFormat
.m_channelLayout
.Count();
371 si
->m_started
= false;
372 si
->m_finishing
= false;
373 si
->m_framesSent
= 0;
374 si
->m_seekNextAtFrame
= 0;
375 si
->m_seekFrame
= -1;
377 si
->m_volume
= (fadeIn
&& m_upcomingCrossfadeMS
) ? 0.0f
: 1.0f
;
378 si
->m_fadeOutTriggered
= false;
379 si
->m_isSlaved
= false;
381 si
->m_decoderTotal
= si
->m_decoder
.TotalTime();
382 int64_t streamTotalTime
= si
->m_decoderTotal
;
384 streamTotalTime
= si
->m_endOffset
- si
->m_startOffset
;
386 // Seek to a resume point
387 if (si
->m_fileItem
->HasProperty("StartPercent") &&
388 (si
->m_fileItem
->GetProperty("StartPercent").asDouble() > 0) &&
389 (si
->m_fileItem
->GetProperty("StartPercent").asDouble() <= 100))
392 si
->m_audioFormat
.m_sampleRate
*
393 CUtil::ConvertMilliSecsToSecs(static_cast<int>(+(static_cast<double>(
394 streamTotalTime
* (si
->m_fileItem
->GetProperty("StartPercent").asDouble() / 100.0)))));
396 else if (starttime
> 0)
397 si
->m_seekFrame
= si
->m_audioFormat
.m_sampleRate
* starttime
;
398 else if (si
->m_fileItem
->HasProperty("audiobook_bookmark"))
399 si
->m_seekFrame
= si
->m_audioFormat
.m_sampleRate
*
400 CUtil::ConvertMilliSecsToSecs(
401 si
->m_fileItem
->GetProperty("audiobook_bookmark").asInteger());
403 si
->m_prepareNextAtFrame
= 0;
404 // cd drives don't really like it to be crossfaded or prepared
405 if (!MUSIC::IsCDDA(file
))
407 if (streamTotalTime
>= TIME_TO_CACHE_NEXT_FILE
+ m_defaultCrossfadeMS
)
408 si
->m_prepareNextAtFrame
= (int)((streamTotalTime
- TIME_TO_CACHE_NEXT_FILE
- m_defaultCrossfadeMS
) * si
->m_audioFormat
.m_sampleRate
/ 1000.0f
);
411 if (m_currentStream
&& ((m_currentStream
->m_audioFormat
.m_dataFormat
== AE_FMT_RAW
) || (si
->m_audioFormat
.m_dataFormat
== AE_FMT_RAW
)))
413 m_currentStream
->m_prepareTriggered
= false;
414 m_currentStream
->m_waitOnDrain
= true;
415 m_currentStream
->m_prepareNextAtFrame
= 0;
416 si
->m_decoder
.Destroy();
421 si
->m_prepareTriggered
= false;
422 si
->m_playNextAtFrame
= 0;
423 si
->m_playNextTriggered
= false;
424 si
->m_waitOnDrain
= false;
426 if (!PrepareStream(si
))
428 CLog::Log(LOGINFO
, "PAPlayer::QueueNextFileEx - Error preparing stream");
430 si
->m_decoder
.Destroy();
432 AdvancePlaylistOnError(*si
->m_fileItem
);
433 m_callback
.OnQueueNextItem();
438 /* add the stream to the list */
439 std::unique_lock
<CCriticalSection
> lock(m_streamsLock
);
440 m_streams
.push_back(si
);
441 //update the current stream to start playing the next track at the correct frame.
442 UpdateStreamInfoPlayNextAtFrame(m_currentStream
, m_upcomingCrossfadeMS
);
447 void PAPlayer::UpdateStreamInfoPlayNextAtFrame(StreamInfo
*si
, unsigned int crossFadingTime
)
449 // if no crossfading or cue sheet, wait for eof
450 if (si
&& (crossFadingTime
|| si
->m_endOffset
))
452 int64_t streamTotalTime
= si
->m_decoder
.TotalTime();
454 streamTotalTime
= si
->m_endOffset
- si
->m_startOffset
;
455 if (streamTotalTime
< crossFadingTime
)
456 si
->m_playNextAtFrame
= (int)((streamTotalTime
/ 2) * si
->m_audioFormat
.m_sampleRate
/ 1000.0f
);
458 si
->m_playNextAtFrame
= (int)((streamTotalTime
- crossFadingTime
) * si
->m_audioFormat
.m_sampleRate
/ 1000.0f
);
462 inline bool PAPlayer::PrepareStream(StreamInfo
*si
)
464 /* if we have a stream we are already prepared */
468 /* get a paused stream */
469 AEAudioFormat format
= si
->m_audioFormat
;
470 si
->m_stream
= CServiceBroker::GetActiveAE()->MakeStream(
477 CLog::Log(LOGDEBUG
, "PAPlayer::PrepareStream - Failed to get IAEStream");
481 si
->m_stream
->SetVolume(si
->m_volume
);
483 float gain
= si
->m_decoder
.GetReplayGain(peak
);
484 if (peak
* gain
<= 1.0f
)
485 // No clipping protection needed
486 si
->m_stream
->SetReplayGain(gain
);
487 else if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_MUSICPLAYER_REPLAYGAINAVOIDCLIPPING
))
488 // Normalise volume reducing replaygain to avoid needing clipping protection, plays file at lower level
489 si
->m_stream
->SetReplayGain(1.0f
/ fabs(peak
));
491 // Clipping protection (when enabled in AE) by audio limiting, applied just where needed
492 si
->m_stream
->SetAmplification(gain
);
494 /* if its not the first stream and crossfade is not enabled */
495 if (m_currentStream
&& m_currentStream
!= si
&& !m_upcomingCrossfadeMS
)
497 /* slave the stream for gapless */
498 si
->m_isSlaved
= true;
499 m_currentStream
->m_stream
->RegisterSlave(si
->m_stream
.get());
502 /* fill the stream's buffer */
503 while(si
->m_stream
->IsBuffering())
505 int status
= si
->m_decoder
.GetStatus();
506 if (status
== STATUS_ENDED
||
507 status
== STATUS_NO_FILE
||
508 si
->m_decoder
.ReadSamples(PACKET_SIZE
) == RET_ERROR
)
510 CLog::Log(LOGINFO
, "PAPlayer::PrepareStream - Stream Finished");
517 /* yield our time so that the main PAP thread doesn't stall */
521 CLog::Log(LOGINFO
, "PAPlayer::PrepareStream - Ready");
526 bool PAPlayer::CloseFile(bool reopen
)
529 CServiceBroker::GetActiveAE()->KeepConfiguration(3000);
532 SoftStop(true, true);
533 CloseAllStreams(false);
535 /* wait for the thread to terminate */
536 StopThread(true);//true - wait for end of thread
538 // wait for any pending jobs to complete
540 std::unique_lock
<CCriticalSection
> lock(m_streamsLock
);
541 while (m_jobCounter
> 0)
544 m_jobEvent
.Wait(100ms
);
548 CServiceBroker::GetDataCacheCore().Reset();
552 void PAPlayer::Process()
554 if (!m_startEvent
.Wait(100ms
))
556 CLog::Log(LOGDEBUG
, "PAPlayer::Process - Failed to receive start event");
560 CLog::Log(LOGDEBUG
, "PAPlayer::Process - Playback started");
561 while(m_isPlaying
&& !m_bStop
)
563 /* this needs to happen outside of any locks to prevent deadlocks */
564 if (m_signalSpeedChange
)
566 m_callback
.OnPlayBackSpeedChanged(m_playbackSpeed
);
567 m_signalSpeedChange
= false;
570 double freeBufferTime
= 0.0;
571 ProcessStreams(freeBufferTime
);
573 // if none of our streams wants at least 10ms of data, we sleep
574 if (freeBufferTime
< 0.01)
576 CThread::Sleep(10ms
);
579 if (m_newForcedPlayerTime
!= -1)
581 if (SetTimeInternal(m_newForcedPlayerTime
))
583 m_newForcedPlayerTime
= -1;
587 if (m_newForcedTotalTime
!= -1)
589 if (SetTotalTimeInternal(m_newForcedTotalTime
))
591 m_newForcedTotalTime
= -1;
595 GetTimeInternal(); //update for GUI
600 inline void PAPlayer::ProcessStreams(double &freeBufferTime
)
602 std::unique_lock
<CCriticalSection
> sharedLock(m_streamsLock
);
603 if (m_isFinished
&& m_streams
.empty() && m_finishing
.empty())
606 freeBufferTime
= 1.0;
610 /* destroy any drained streams */
611 for (auto itt
= m_finishing
.begin(); itt
!= m_finishing
.end();)
613 StreamInfo
* si
= *itt
;
614 if (si
->m_stream
->IsDrained())
616 itt
= m_finishing
.erase(itt
);
619 CLog::Log(LOGDEBUG
, "PAPlayer::ProcessStreams - Stream Freed");
626 std::unique_lock
<CCriticalSection
> lock(m_streamsLock
);
628 for(StreamList::iterator itt
= m_streams
.begin(); itt
!= m_streams
.end(); ++itt
)
630 StreamInfo
* si
= *itt
;
631 if (!m_currentStream
&& !si
->m_started
)
633 m_currentStream
= si
;
634 UpdateGUIData(si
); //update for GUI
636 /* if the stream is finishing */
637 if ((si
->m_playNextTriggered
&& si
->m_stream
&& !si
->m_stream
->IsFading()) || !ProcessStream(si
, freeBufferTime
))
639 if (!si
->m_prepareTriggered
)
641 if (si
->m_waitOnDrain
)
643 si
->m_stream
->Drain(true);
644 si
->m_waitOnDrain
= false;
646 si
->m_prepareTriggered
= true;
647 m_callback
.OnQueueNextItem();
650 /* remove the stream */
651 itt
= m_streams
.erase(itt
);
652 /* if its the current stream */
653 if (si
== m_currentStream
)
655 /* if it was the last stream */
656 if (itt
== m_streams
.end())
658 /* if it didn't trigger the next queue item */
659 if (!si
->m_prepareTriggered
)
661 if (si
->m_waitOnDrain
)
663 si
->m_stream
->Drain(true);
664 si
->m_waitOnDrain
= false;
666 m_callback
.OnQueueNextItem();
667 si
->m_prepareTriggered
= true;
669 m_currentStream
= NULL
;
673 m_currentStream
= *itt
;
674 UpdateGUIData(*itt
); //update for GUI
678 /* unregister the audio callback */
679 si
->m_stream
->UnRegisterAudioCallback();
680 si
->m_decoder
.Destroy();
681 si
->m_stream
->Drain(false);
682 m_finishing
.push_back(si
);
689 // is it time to prepare the next stream?
690 if (si
->m_prepareNextAtFrame
> 0 && !si
->m_prepareTriggered
&& si
->m_framesSent
>= si
->m_prepareNextAtFrame
)
692 si
->m_prepareTriggered
= true;
693 m_callback
.OnQueueNextItem();
696 // it is time to start playing the next stream?
697 if (si
->m_playNextAtFrame
> 0 && !si
->m_playNextTriggered
&& !si
->m_nextFileItem
&& si
->m_framesSent
>= si
->m_playNextAtFrame
)
699 if (!si
->m_prepareTriggered
)
701 si
->m_prepareTriggered
= true;
702 m_callback
.OnQueueNextItem();
707 if (m_upcomingCrossfadeMS
)
709 si
->m_stream
->FadeVolume(1.0f
, 0.0f
, m_upcomingCrossfadeMS
);
710 si
->m_fadeOutTriggered
= true;
712 m_currentStream
= NULL
;
714 /* unregister the audio callback */
715 si
->m_stream
->UnRegisterAudioCallback();
718 si
->m_playNextTriggered
= true;
723 inline bool PAPlayer::ProcessStream(StreamInfo
*si
, double &freeBufferTime
)
725 /* if playback needs to start on this stream, do it */
726 if (si
== m_currentStream
&& !si
->m_started
)
728 si
->m_started
= true;
729 si
->m_stream
->RegisterAudioCallback(m_audioCallback
);
731 si
->m_stream
->Resume();
732 si
->m_stream
->FadeVolume(0.0f
, 1.0f
, m_upcomingCrossfadeMS
);
734 m_callback
.OnPlayBackStarted(*si
->m_fileItem
);
735 m_signalStarted
= true;
738 CServiceBroker::GetAppMessenger()->PostMsg(TMSG_SWITCHTOFULLSCREEN
);
739 m_fullScreen
= false;
741 m_callback
.OnAVStarted(*si
->m_fileItem
);
744 /* if we have not started yet and the stream has been primed */
745 unsigned int space
= si
->m_stream
->GetSpace();
746 if (!si
->m_started
&& !space
)
749 if (!m_playbackSpeed
)
752 /* see if it is time yet to FF/RW or a direct seek */
753 if (!si
->m_playNextTriggered
&& ((m_playbackSpeed
!= 1 && si
->m_framesSent
>= si
->m_seekNextAtFrame
) || si
->m_seekFrame
> -1))
755 int64_t time
= (int64_t)0;
756 /* if its a direct seek */
757 if (si
->m_seekFrame
> -1)
759 time
= (int64_t)((float)si
->m_seekFrame
/ (float)si
->m_audioFormat
.m_sampleRate
* 1000.0f
);
760 si
->m_framesSent
= (int)(si
->m_seekFrame
- ((float)si
->m_startOffset
* (float)si
->m_audioFormat
.m_sampleRate
) / 1000.0f
);
761 si
->m_seekFrame
= -1;
762 m_playerGUIData
.m_time
= time
; //update for GUI
763 si
->m_seekNextAtFrame
= 0;
764 CDataCacheCore::GetInstance().SetPlayTimes(0, time
, 0, m_playerGUIData
.m_totalTime
);
769 si
->m_framesSent
+= si
->m_audioFormat
.m_sampleRate
* (m_playbackSpeed
- 1);
770 si
->m_seekNextAtFrame
= si
->m_framesSent
+ si
->m_audioFormat
.m_sampleRate
/ 2;
771 time
= (int64_t)(((float)si
->m_framesSent
/ (float)si
->m_audioFormat
.m_sampleRate
* 1000.0f
) + (float)si
->m_startOffset
);
774 /* if we are seeking back before the start of the track start normal playback */
775 if (time
< si
->m_startOffset
|| si
->m_framesSent
< 0)
777 time
= si
->m_startOffset
;
778 si
->m_framesSent
= 0;
779 si
->m_seekNextAtFrame
= 0;
783 si
->m_decoder
.Seek(time
);
786 int status
= si
->m_decoder
.GetStatus();
787 if (status
== STATUS_ENDED
||
788 status
== STATUS_NO_FILE
||
789 si
->m_decoder
.ReadSamples(PACKET_SIZE
) == RET_ERROR
||
790 ((si
->m_endOffset
) && (si
->m_framesSent
/ si
->m_audioFormat
.m_sampleRate
>= (si
->m_endOffset
- si
->m_startOffset
) / 1000)))
792 if (si
== m_currentStream
&& si
->m_nextFileItem
)
796 // update current stream with info of next track
797 si
->m_startOffset
= si
->m_nextFileItem
->GetStartOffset();
798 if (si
->m_nextFileItem
->GetEndOffset())
799 si
->m_endOffset
= si
->m_nextFileItem
->GetEndOffset();
802 si
->m_framesSent
= 0;
804 *si
->m_fileItem
= *si
->m_nextFileItem
;
805 si
->m_nextFileItem
.reset();
807 int64_t streamTotalTime
= si
->m_decoder
.TotalTime() - si
->m_startOffset
;
809 streamTotalTime
= si
->m_endOffset
- si
->m_startOffset
;
811 // calculate time when to prepare next stream
812 si
->m_prepareNextAtFrame
= 0;
813 if (streamTotalTime
>= TIME_TO_CACHE_NEXT_FILE
+ m_defaultCrossfadeMS
)
814 si
->m_prepareNextAtFrame
= (int)((streamTotalTime
- TIME_TO_CACHE_NEXT_FILE
- m_defaultCrossfadeMS
) * si
->m_audioFormat
.m_sampleRate
/ 1000.0f
);
816 si
->m_prepareTriggered
= false;
817 si
->m_playNextAtFrame
= 0;
818 si
->m_playNextTriggered
= false;
819 si
->m_seekNextAtFrame
= 0;
821 //update the current stream to start playing the next track at the correct frame.
822 UpdateStreamInfoPlayNextAtFrame(m_currentStream
, m_upcomingCrossfadeMS
);
826 m_callback
.OnPlayBackStarted(*si
->m_fileItem
);
827 m_signalStarted
= true;
828 m_callback
.OnAVStarted(*si
->m_fileItem
);
832 CLog::Log(LOGINFO
, "PAPlayer::ProcessStream - Stream Finished");
840 /* update free buffer time if we are running */
843 if (si
->m_stream
->IsBuffering())
844 freeBufferTime
= 1.0;
848 if (si
->m_audioFormat
.m_dataFormat
!= AE_FMT_RAW
)
849 free_space
= (double)(si
->m_stream
->GetSpace() / si
->m_bytesPerSample
) / si
->m_audioFormat
.m_sampleRate
;
852 free_space
= static_cast<double>(si
->m_stream
->GetSpace()) *
853 si
->m_audioFormat
.m_streamInfo
.GetDuration() / 1000;
856 freeBufferTime
= std::max(freeBufferTime
, free_space
);
863 bool PAPlayer::QueueData(StreamInfo
*si
)
865 unsigned int space
= si
->m_stream
->GetSpace();
867 if (si
->m_audioFormat
.m_dataFormat
!= AE_FMT_RAW
)
869 unsigned int samples
= std::min(si
->m_decoder
.GetDataSize(false), space
/ si
->m_bytesPerSample
);
873 // we want complete frames
874 samples
-= samples
% si
->m_audioFormat
.m_channelLayout
.Count();
876 uint8_t* data
= (uint8_t*)si
->m_decoder
.GetData(samples
);
879 CLog::Log(LOGERROR
, "PAPlayer::QueueData - Failed to get data from the decoder");
883 unsigned int frames
= samples
/si
->m_audioFormat
.m_channelLayout
.Count();
884 unsigned int added
= si
->m_stream
->AddData(&data
, 0, frames
, nullptr);
885 si
->m_framesSent
+= added
;
893 uint8_t *data
= si
->m_decoder
.GetRawData(size
);
896 int added
= si
->m_stream
->AddData(&data
, 0, size
, nullptr);
899 CLog::Log(LOGERROR
, "PAPlayer::QueueData - unknown error");
903 si
->m_framesSent
+= si
->m_audioFormat
.m_streamInfo
.GetDuration() / 1000 *
904 si
->m_audioFormat
.m_streamInfo
.m_sampleRate
;
908 const ICodec
* codec
= si
->m_decoder
.GetCodec();
909 m_playerGUIData
.m_cacheLevel
= codec
? codec
->GetCacheLevel() : 0; //update for GUI
914 void PAPlayer::OnExit()
916 //@todo signal OnPlayBackError if there was an error on last stream
917 if (m_isFinished
&& !m_bStop
)
918 m_callback
.OnPlayBackEnded();
920 m_callback
.OnPlayBackStopped();
923 void PAPlayer::OnNothingToQueueNotify()
928 bool PAPlayer::IsPlaying() const
933 void PAPlayer::Pause()
945 void PAPlayer::SetVolume(float volume
)
950 void PAPlayer::SetDynamicRangeCompression(long drc
)
955 void PAPlayer::SetSpeed(float speed
)
957 m_playbackSpeed
= static_cast<int>(speed
);
958 CDataCacheCore::GetInstance().SetSpeed(1.0, speed
);
959 if (m_playbackSpeed
!= 0 && m_isPaused
)
963 m_callback
.OnPlayBackResumed();
965 else if (m_playbackSpeed
== 0 && !m_isPaused
)
968 SoftStop(true, false);
969 m_callback
.OnPlayBackPaused();
971 m_signalSpeedChange
= true;
974 int64_t PAPlayer::GetTimeInternal()
976 std::unique_lock
<CCriticalSection
> lock(m_streamsLock
);
977 if (!m_currentStream
)
980 double time
= ((double)m_currentStream
->m_framesSent
/ (double)m_currentStream
->m_audioFormat
.m_sampleRate
);
981 if (m_currentStream
->m_stream
)
982 time
-= m_currentStream
->m_stream
->GetDelay();
983 time
= time
* 1000.0;
985 m_playerGUIData
.m_time
= (int64_t)time
; //update for GUI
986 CDataCacheCore::GetInstance().SetPlayTimes(0, time
, 0, m_playerGUIData
.m_totalTime
);
988 return (int64_t)time
;
991 bool PAPlayer::SetTotalTimeInternal(int64_t time
)
993 std::unique_lock
<CCriticalSection
> lock(m_streamsLock
);
994 if (!m_currentStream
)
999 m_currentStream
->m_decoder
.SetTotalTime(time
);
1000 UpdateGUIData(m_currentStream
);
1005 bool PAPlayer::SetTimeInternal(int64_t time
)
1007 std::unique_lock
<CCriticalSection
> lock(m_streamsLock
);
1008 if (!m_currentStream
)
1011 m_currentStream
->m_framesSent
= time
/ 1000 * m_currentStream
->m_audioFormat
.m_sampleRate
;
1013 if (m_currentStream
->m_stream
)
1014 m_currentStream
->m_framesSent
+= m_currentStream
->m_stream
->GetDelay() * m_currentStream
->m_audioFormat
.m_sampleRate
;
1019 void PAPlayer::SetTime(int64_t time
)
1021 m_newForcedPlayerTime
= time
;
1024 int64_t PAPlayer::GetTotalTime64()
1026 std::unique_lock
<CCriticalSection
> lock(m_streamsLock
);
1027 if (!m_currentStream
)
1030 int64_t total
= m_currentStream
->m_decoder
.TotalTime();
1031 if (m_currentStream
->m_endOffset
)
1032 total
= m_currentStream
->m_endOffset
;
1033 total
-= m_currentStream
->m_startOffset
;
1037 void PAPlayer::SetTotalTime(int64_t time
)
1039 m_newForcedTotalTime
= time
;
1042 int PAPlayer::GetCacheLevel() const
1044 return m_playerGUIData
.m_cacheLevel
;
1047 void PAPlayer::GetAudioStreamInfo(int index
, AudioStreamInfo
& info
) const
1049 info
.bitrate
= m_playerGUIData
.m_audioBitrate
;
1050 info
.channels
= m_playerGUIData
.m_channelCount
;
1051 info
.codecName
= m_playerGUIData
.m_codec
;
1052 info
.samplerate
= m_playerGUIData
.m_sampleRate
;
1053 info
.bitspersample
= m_playerGUIData
.m_bitsPerSample
;
1056 bool PAPlayer::CanSeek() const
1058 return m_playerGUIData
.m_canSeek
;
1061 void PAPlayer::Seek(bool bPlus
, bool bLargeStep
, bool bChapterOverride
)
1063 if (!CanSeek()) return;
1066 const std::shared_ptr
<CAdvancedSettings
> advancedSettings
= CServiceBroker::GetSettingsComponent()->GetAdvancedSettings();
1067 if (advancedSettings
->m_musicUseTimeSeeking
&& m_playerGUIData
.m_totalTime
> 2 * advancedSettings
->m_musicTimeSeekForwardBig
)
1070 seek
= bPlus
? advancedSettings
->m_musicTimeSeekForwardBig
: advancedSettings
->m_musicTimeSeekBackwardBig
;
1072 seek
= bPlus
? advancedSettings
->m_musicTimeSeekForward
: advancedSettings
->m_musicTimeSeekBackward
;
1074 seek
+= m_playerGUIData
.m_time
;
1080 percent
= bPlus
? static_cast<float>(advancedSettings
->m_musicPercentSeekForwardBig
) : static_cast<float>(advancedSettings
->m_musicPercentSeekBackwardBig
);
1082 percent
= bPlus
? static_cast<float>(advancedSettings
->m_musicPercentSeekForward
) : static_cast<float>(advancedSettings
->m_musicPercentSeekBackward
);
1083 seek
= static_cast<long long>(GetTotalTime64() * (GetPercentage() + percent
) / 100);
1089 void PAPlayer::SeekTime(int64_t iTime
/*=0*/)
1091 if (!CanSeek()) return;
1093 std::unique_lock
<CCriticalSection
> lock(m_streamsLock
);
1094 if (!m_currentStream
)
1097 int64_t seekOffset
= iTime
- GetTimeInternal();
1099 if (m_playbackSpeed
!= 1)
1102 m_currentStream
->m_seekFrame
= (int)((float)m_currentStream
->m_audioFormat
.m_sampleRate
* ((float)iTime
+ (float)m_currentStream
->m_startOffset
) / 1000.0f
);
1103 m_callback
.OnPlayBackSeek(iTime
, seekOffset
);
1106 void PAPlayer::SeekPercentage(float fPercent
/*=0*/)
1108 if (fPercent
< 0.0f
) fPercent
= 0.0f
;
1109 if (fPercent
> 100.0f
) fPercent
= 100.0f
;
1110 SeekTime((int64_t)(fPercent
* 0.01f
* (float)GetTotalTime64()));
1113 float PAPlayer::GetPercentage()
1115 if (m_playerGUIData
.m_totalTime
> 0)
1116 return m_playerGUIData
.m_time
* 100.0f
/ m_playerGUIData
.m_totalTime
;
1121 void PAPlayer::UpdateGUIData(StreamInfo
*si
)
1123 /* Store data need by external threads in member
1124 * structure to prevent locking conflicts when
1125 * data required by GUI and main application
1127 std::unique_lock
<CCriticalSection
> lock(m_streamsLock
);
1129 m_playerGUIData
.m_sampleRate
= si
->m_audioFormat
.m_sampleRate
;
1130 m_playerGUIData
.m_channelCount
= si
->m_audioFormat
.m_channelLayout
.Count();
1131 m_playerGUIData
.m_canSeek
= si
->m_decoder
.CanSeek();
1133 const ICodec
* codec
= si
->m_decoder
.GetCodec();
1135 m_playerGUIData
.m_audioBitrate
= codec
? codec
->m_bitRate
: 0;
1136 strncpy(m_playerGUIData
.m_codec
,codec
? codec
->m_CodecName
.c_str() : "",20);
1137 m_playerGUIData
.m_cacheLevel
= codec
? codec
->GetCacheLevel() : 0;
1138 m_playerGUIData
.m_bitsPerSample
= (codec
&& codec
->m_bitsPerCodedSample
) ? codec
->m_bitsPerCodedSample
: si
->m_bytesPerSample
<< 3;
1140 int64_t total
= si
->m_decoder
.TotalTime();
1141 if (si
->m_endOffset
)
1142 total
= m_currentStream
->m_endOffset
;
1143 total
-= m_currentStream
->m_startOffset
;
1144 m_playerGUIData
.m_totalTime
= total
;
1146 CServiceBroker::GetDataCacheCore().SignalAudioInfoChange();
1149 void PAPlayer::OnJobComplete(unsigned int jobID
, bool success
, CJob
*job
)
1151 std::unique_lock
<CCriticalSection
> lock(m_streamsLock
);
1156 void PAPlayer::CloseFileCB(StreamInfo
&si
)
1158 IPlayerCallback
*cb
= &m_callback
;
1159 CFileItem
fileItem(*si
.m_fileItem
);
1161 double total
= si
.m_decoderTotal
;
1163 total
= si
.m_endOffset
;
1164 total
-= si
.m_startOffset
;
1165 bookmark
.totalTimeInSeconds
= total
/ 1000;
1166 bookmark
.timeInSeconds
= (static_cast<double>(si
.m_framesSent
) /
1167 static_cast<double>(si
.m_audioFormat
.m_sampleRate
));
1168 bookmark
.timeInSeconds
-= si
.m_stream
->GetDelay();
1169 bookmark
.player
= m_name
;
1170 bookmark
.playerState
= GetPlayerState();
1171 CServiceBroker::GetJobManager()->Submit([=]() { cb
->OnPlayerCloseFile(fileItem
, bookmark
); },
1172 CJob::PRIORITY_NORMAL
);
1175 void PAPlayer::AdvancePlaylistOnError(CFileItem
&fileItem
)
1177 if (m_signalStarted
)
1178 m_callback
.OnPlayBackStarted(fileItem
);
1179 m_signalStarted
= true;
1180 m_callback
.OnAVStarted(fileItem
);