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.
11 #include "CodecFactory.h"
12 #include "ServiceBroker.h"
14 #include "cores/AudioEngine/Interfaces/AE.h"
15 #include "cores/AudioEngine/Interfaces/AEStream.h"
16 #include "cores/AudioEngine/Utils/AEStreamData.h"
17 #include "cores/AudioEngine/Utils/AEUtil.h"
18 #include "cores/DataCacheCore.h"
19 #include "cores/VideoPlayer/Process/ProcessInfo.h"
20 #include "messaging/ApplicationMessenger.h"
21 #include "music/tags/MusicInfoTag.h"
22 #include "settings/AdvancedSettings.h"
23 #include "settings/Settings.h"
24 #include "settings/SettingsComponent.h"
25 #include "threads/SystemClock.h"
26 #include "utils/JobManager.h"
27 #include "utils/log.h"
28 #include "video/Bookmark.h"
32 using namespace std::chrono_literals
;
34 #define TIME_TO_CACHE_NEXT_FILE 5000 /* 5 seconds before end of song, start caching the next song */
35 #define FAST_XFADE_TIME 80 /* 80 milliseconds */
36 #define MAX_SKIP_XFADE_TIME 2000 /* max 2 seconds crossfade on track skip */
38 // PAP: Psycho-acoustic Audio Player
39 // Supporting all open audio codec standards.
40 // First one being nullsoft's nsv audio decoder format
42 PAPlayer::PAPlayer(IPlayerCallback
& callback
) :
45 m_signalSpeedChange(false),
50 m_defaultCrossfadeMS (0),
51 m_upcomingCrossfadeMS(0),
52 m_audioCallback(NULL
),
54 m_newForcedPlayerTime(-1),
55 m_newForcedTotalTime (-1)
57 memset(&m_playerGUIData
, 0, sizeof(m_playerGUIData
));
58 m_processInfo
.reset(CProcessInfo::CreateInstance());
59 m_processInfo
->SetDataCache(&CServiceBroker::GetDataCacheCore());
67 void PAPlayer::SoftStart(bool wait
/* = false */)
69 std::unique_lock
<CCriticalSection
> lock(m_streamsLock
);
70 for(StreamList::iterator itt
= m_streams
.begin(); itt
!= m_streams
.end(); ++itt
)
72 StreamInfo
* si
= *itt
;
73 if (si
->m_fadeOutTriggered
)
76 si
->m_stream
->Resume();
77 si
->m_stream
->FadeVolume(0.0f
, 1.0f
, FAST_XFADE_TIME
);
82 /* wait for them to fade in */
84 CThread::Sleep(std::chrono::milliseconds(FAST_XFADE_TIME
));
87 /* be sure they have faded in */
91 for(StreamList::iterator itt
= m_streams
.begin(); itt
!= m_streams
.end(); ++itt
)
93 StreamInfo
* si
= *itt
;
94 if (si
->m_stream
->IsFading())
107 void PAPlayer::SoftStop(bool wait
/* = false */, bool close
/* = true */)
109 /* fade all the streams out fast for a nice soft stop */
110 std::unique_lock
<CCriticalSection
> lock(m_streamsLock
);
111 for(StreamList::iterator itt
= m_streams
.begin(); itt
!= m_streams
.end(); ++itt
)
113 StreamInfo
* si
= *itt
;
115 si
->m_stream
->FadeVolume(1.0f
, 0.0f
, FAST_XFADE_TIME
);
119 si
->m_prepareTriggered
= true;
120 si
->m_playNextTriggered
= true;
121 si
->m_fadeOutTriggered
= true;
125 /* if we are going to wait for them to finish fading */
128 // fail safe timer, do not wait longer than 1000ms
129 XbmcThreads::EndTime
<> timer(1000ms
);
131 /* wait for them to fade out */
133 CThread::Sleep(std::chrono::milliseconds(FAST_XFADE_TIME
));
136 /* be sure they have faded out */
137 while(wait
&& !CServiceBroker::GetActiveAE()->IsSuspended() && !timer
.IsTimePast())
140 for(StreamList::iterator itt
= m_streams
.begin(); itt
!= m_streams
.end(); ++itt
)
142 StreamInfo
* si
= *itt
;
143 if (si
->m_stream
&& si
->m_stream
->IsFading())
154 /* if we are not closing the streams, pause them */
157 for(StreamList::iterator itt
= m_streams
.begin(); itt
!= m_streams
.end(); ++itt
)
159 StreamInfo
* si
= *itt
;
160 si
->m_stream
->Pause();
166 void PAPlayer::CloseAllStreams(bool fade
/* = true */)
170 std::unique_lock
<CCriticalSection
> lock(m_streamsLock
);
171 while (!m_streams
.empty())
173 StreamInfo
* si
= m_streams
.front();
174 m_streams
.pop_front();
179 si
->m_stream
.reset();
182 si
->m_decoder
.Destroy();
186 while (!m_finishing
.empty())
188 StreamInfo
* si
= m_finishing
.front();
189 m_finishing
.pop_front();
194 si
->m_stream
.reset();
197 si
->m_decoder
.Destroy();
200 m_currentStream
= nullptr;
204 SoftStop(false, true);
205 std::unique_lock
<CCriticalSection
> lock(m_streamsLock
);
206 m_currentStream
= NULL
;
210 bool PAPlayer::OpenFile(const CFileItem
& file
, const CPlayerOptions
&options
)
212 m_defaultCrossfadeMS
= CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_MUSICPLAYER_CROSSFADE
) * 1000;
213 m_fullScreen
= options
.fullscreen
;
215 if (m_streams
.size() > 1 || !m_defaultCrossfadeMS
|| m_isPaused
)
217 CloseAllStreams(!m_isPaused
);
219 m_isPaused
= false; // Make sure to reset the pause state
223 std::unique_lock
<CCriticalSection
> lock(m_streamsLock
);
226 CServiceBroker::GetJobManager()->Submit([=]() { QueueNextFileEx(file
, false); }, this,
227 CJob::PRIORITY_NORMAL
);
229 std::unique_lock
<CCriticalSection
> lock(m_streamsLock
);
230 if (m_streams
.size() == 2)
232 //do a short crossfade on trackskip, set to max 2 seconds for these prev/next transitions
233 m_upcomingCrossfadeMS
= std::min(m_defaultCrossfadeMS
, (unsigned int)MAX_SKIP_XFADE_TIME
);
235 //start transition to next track
236 StreamInfo
* si
= m_streams
.front();
237 si
->m_playNextAtFrame
= si
->m_framesSent
; //start next track at current frame
238 si
->m_prepareTriggered
= true; //next track is ready to go
245 /* trigger playback start */
249 // OnPlayBackStarted to be made only once. Callback processing may be slower than player process
250 // so clear signal flag first otherwise async stream processing could also make callback
251 m_signalStarted
= false;
252 m_callback
.OnPlayBackStarted(file
);
257 void PAPlayer::UpdateCrossfadeTime(const CFileItem
& file
)
259 // we explicitly disable crossfading for audio cds
261 m_upcomingCrossfadeMS
= 0;
263 m_upcomingCrossfadeMS
= m_defaultCrossfadeMS
= CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_MUSICPLAYER_CROSSFADE
) * 1000;
265 if (m_upcomingCrossfadeMS
)
267 if (!m_currentStream
||
268 (file
.HasMusicInfoTag() && !CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_MUSICPLAYER_CROSSFADEALBUMTRACKS
) &&
269 m_currentStream
->m_fileItem
.HasMusicInfoTag() &&
270 (m_currentStream
->m_fileItem
.GetMusicInfoTag()->GetAlbum() != "") &&
271 (m_currentStream
->m_fileItem
.GetMusicInfoTag()->GetAlbum() == file
.GetMusicInfoTag()->GetAlbum()) &&
272 (m_currentStream
->m_fileItem
.GetMusicInfoTag()->GetDiscNumber() == file
.GetMusicInfoTag()->GetDiscNumber()) &&
273 (m_currentStream
->m_fileItem
.GetMusicInfoTag()->GetTrackNumber() == file
.GetMusicInfoTag()->GetTrackNumber() - 1)))
275 //do not crossfade when playing consecutive albumtracks
276 m_upcomingCrossfadeMS
= 0;
281 bool PAPlayer::QueueNextFile(const CFileItem
&file
)
284 std::unique_lock
<CCriticalSection
> lock(m_streamsLock
);
287 CServiceBroker::GetJobManager()->Submit([this, file
]() { QueueNextFileEx(file
, true); }, this,
288 CJob::PRIORITY_NORMAL
);
293 bool PAPlayer::QueueNextFileEx(const CFileItem
&file
, bool fadeIn
)
297 // check if we advance a track of a CUE sheet
298 // if this is the case we don't need to open a new stream
299 std::string newURL
= file
.GetDynURL().GetFileName();
300 std::string oldURL
= m_currentStream
->m_fileItem
.GetDynURL().GetFileName();
301 if (newURL
.compare(oldURL
) == 0 && file
.GetStartOffset() &&
302 file
.GetStartOffset() == m_currentStream
->m_fileItem
.GetEndOffset() && m_currentStream
&&
303 m_currentStream
->m_prepareTriggered
)
305 m_currentStream
->m_nextFileItem
.reset(new CFileItem(file
));
306 m_upcomingCrossfadeMS
= 0;
309 m_currentStream
->m_nextFileItem
.reset();
312 StreamInfo
*si
= new StreamInfo();
313 si
->m_fileItem
= file
;
315 // Start stream at zero offset
316 si
->m_startOffset
= 0;
317 //File item start offset defines where in song to resume
318 double starttime
= CUtil::ConvertMilliSecsToSecs(si
->m_fileItem
.GetStartOffset());
320 // Music from cuesheet => "item_start" and offset match
321 // Start offset defines where this song starts in file of multiple songs
322 if (si
->m_fileItem
.HasProperty("item_start") &&
323 (si
->m_fileItem
.GetProperty("item_start").asInteger() == si
->m_fileItem
.GetStartOffset()))
325 // Start stream at offset from cuesheet
326 si
->m_startOffset
= si
->m_fileItem
.GetStartOffset();
327 starttime
= 0; // No resume point
330 if (!si
->m_decoder
.Create(file
, si
->m_startOffset
))
332 CLog::Log(LOGWARNING
, "PAPlayer::QueueNextFileEx - Failed to create the decoder");
335 AdvancePlaylistOnError(si
->m_fileItem
);
336 m_callback
.OnQueueNextItem();
342 /* decode until there is data-available */
343 si
->m_decoder
.Start();
344 while (si
->m_decoder
.GetDataSize(true) == 0)
346 int status
= si
->m_decoder
.GetStatus();
347 if (status
== STATUS_ENDED
||
348 status
== STATUS_NO_FILE
||
349 si
->m_decoder
.ReadSamples(PACKET_SIZE
) == RET_ERROR
)
351 CLog::Log(LOGINFO
, "PAPlayer::QueueNextFileEx - Error reading samples");
353 si
->m_decoder
.Destroy();
355 AdvancePlaylistOnError(si
->m_fileItem
);
356 m_callback
.OnQueueNextItem();
361 /* yield our time so that the main PAP thread doesn't stall */
365 // set m_upcomingCrossfadeMS depending on type of file and user settings
366 UpdateCrossfadeTime(si
->m_fileItem
);
368 /* init the streaminfo struct */
369 si
->m_audioFormat
= si
->m_decoder
.GetFormat();
370 // si->m_startOffset already initialized
371 si
->m_endOffset
= file
.GetEndOffset();
372 si
->m_bytesPerSample
= CAEUtil::DataFormatToBits(si
->m_audioFormat
.m_dataFormat
) >> 3;
373 si
->m_bytesPerFrame
= si
->m_bytesPerSample
* si
->m_audioFormat
.m_channelLayout
.Count();
374 si
->m_started
= false;
375 si
->m_finishing
= false;
376 si
->m_framesSent
= 0;
377 si
->m_seekNextAtFrame
= 0;
378 si
->m_seekFrame
= -1;
380 si
->m_volume
= (fadeIn
&& m_upcomingCrossfadeMS
) ? 0.0f
: 1.0f
;
381 si
->m_fadeOutTriggered
= false;
382 si
->m_isSlaved
= false;
384 si
->m_decoderTotal
= si
->m_decoder
.TotalTime();
385 int64_t streamTotalTime
= si
->m_decoderTotal
;
387 streamTotalTime
= si
->m_endOffset
- si
->m_startOffset
;
389 // Seek to a resume point
390 if (si
->m_fileItem
.HasProperty("StartPercent") &&
391 (si
->m_fileItem
.GetProperty("StartPercent").asDouble() > 0) &&
392 (si
->m_fileItem
.GetProperty("StartPercent").asDouble() <= 100))
395 si
->m_audioFormat
.m_sampleRate
*
396 CUtil::ConvertMilliSecsToSecs(static_cast<int>(+(static_cast<double>(
397 streamTotalTime
* (si
->m_fileItem
.GetProperty("StartPercent").asDouble() / 100.0)))));
399 else if (starttime
> 0)
400 si
->m_seekFrame
= si
->m_audioFormat
.m_sampleRate
* starttime
;
401 else if (si
->m_fileItem
.HasProperty("audiobook_bookmark"))
403 si
->m_audioFormat
.m_sampleRate
*
404 CUtil::ConvertMilliSecsToSecs(si
->m_fileItem
.GetProperty("audiobook_bookmark").asInteger());
406 si
->m_prepareNextAtFrame
= 0;
407 // cd drives don't really like it to be crossfaded or prepared
410 if (streamTotalTime
>= TIME_TO_CACHE_NEXT_FILE
+ m_defaultCrossfadeMS
)
411 si
->m_prepareNextAtFrame
= (int)((streamTotalTime
- TIME_TO_CACHE_NEXT_FILE
- m_defaultCrossfadeMS
) * si
->m_audioFormat
.m_sampleRate
/ 1000.0f
);
414 if (m_currentStream
&& ((m_currentStream
->m_audioFormat
.m_dataFormat
== AE_FMT_RAW
) || (si
->m_audioFormat
.m_dataFormat
== AE_FMT_RAW
)))
416 m_currentStream
->m_prepareTriggered
= false;
417 m_currentStream
->m_waitOnDrain
= true;
418 m_currentStream
->m_prepareNextAtFrame
= 0;
419 si
->m_decoder
.Destroy();
424 si
->m_prepareTriggered
= false;
425 si
->m_playNextAtFrame
= 0;
426 si
->m_playNextTriggered
= false;
427 si
->m_waitOnDrain
= false;
429 if (!PrepareStream(si
))
431 CLog::Log(LOGINFO
, "PAPlayer::QueueNextFileEx - Error preparing stream");
433 si
->m_decoder
.Destroy();
435 AdvancePlaylistOnError(si
->m_fileItem
);
436 m_callback
.OnQueueNextItem();
441 /* add the stream to the list */
442 std::unique_lock
<CCriticalSection
> lock(m_streamsLock
);
443 m_streams
.push_back(si
);
444 //update the current stream to start playing the next track at the correct frame.
445 UpdateStreamInfoPlayNextAtFrame(m_currentStream
, m_upcomingCrossfadeMS
);
450 void PAPlayer::UpdateStreamInfoPlayNextAtFrame(StreamInfo
*si
, unsigned int crossFadingTime
)
452 // if no crossfading or cue sheet, wait for eof
453 if (si
&& (crossFadingTime
|| si
->m_endOffset
))
455 int64_t streamTotalTime
= si
->m_decoder
.TotalTime();
457 streamTotalTime
= si
->m_endOffset
- si
->m_startOffset
;
458 if (streamTotalTime
< crossFadingTime
)
459 si
->m_playNextAtFrame
= (int)((streamTotalTime
/ 2) * si
->m_audioFormat
.m_sampleRate
/ 1000.0f
);
461 si
->m_playNextAtFrame
= (int)((streamTotalTime
- crossFadingTime
) * si
->m_audioFormat
.m_sampleRate
/ 1000.0f
);
465 inline bool PAPlayer::PrepareStream(StreamInfo
*si
)
467 /* if we have a stream we are already prepared */
471 /* get a paused stream */
472 AEAudioFormat format
= si
->m_audioFormat
;
473 si
->m_stream
= CServiceBroker::GetActiveAE()->MakeStream(
480 CLog::Log(LOGDEBUG
, "PAPlayer::PrepareStream - Failed to get IAEStream");
484 si
->m_stream
->SetVolume(si
->m_volume
);
486 float gain
= si
->m_decoder
.GetReplayGain(peak
);
487 if (peak
* gain
<= 1.0f
)
488 // No clipping protection needed
489 si
->m_stream
->SetReplayGain(gain
);
490 else if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_MUSICPLAYER_REPLAYGAINAVOIDCLIPPING
))
491 // Normalise volume reducing replaygain to avoid needing clipping protection, plays file at lower level
492 si
->m_stream
->SetReplayGain(1.0f
/ fabs(peak
));
494 // Clipping protection (when enabled in AE) by audio limiting, applied just where needed
495 si
->m_stream
->SetAmplification(gain
);
497 /* if its not the first stream and crossfade is not enabled */
498 if (m_currentStream
&& m_currentStream
!= si
&& !m_upcomingCrossfadeMS
)
500 /* slave the stream for gapless */
501 si
->m_isSlaved
= true;
502 m_currentStream
->m_stream
->RegisterSlave(si
->m_stream
.get());
505 /* fill the stream's buffer */
506 while(si
->m_stream
->IsBuffering())
508 int status
= si
->m_decoder
.GetStatus();
509 if (status
== STATUS_ENDED
||
510 status
== STATUS_NO_FILE
||
511 si
->m_decoder
.ReadSamples(PACKET_SIZE
) == RET_ERROR
)
513 CLog::Log(LOGINFO
, "PAPlayer::PrepareStream - Stream Finished");
520 /* yield our time so that the main PAP thread doesn't stall */
524 CLog::Log(LOGINFO
, "PAPlayer::PrepareStream - Ready");
529 bool PAPlayer::CloseFile(bool reopen
)
532 CServiceBroker::GetActiveAE()->KeepConfiguration(3000);
535 SoftStop(true, true);
536 CloseAllStreams(false);
538 /* wait for the thread to terminate */
539 StopThread(true);//true - wait for end of thread
541 // wait for any pending jobs to complete
543 std::unique_lock
<CCriticalSection
> lock(m_streamsLock
);
544 while (m_jobCounter
> 0)
547 m_jobEvent
.Wait(100ms
);
555 void PAPlayer::Process()
557 if (!m_startEvent
.Wait(100ms
))
559 CLog::Log(LOGDEBUG
, "PAPlayer::Process - Failed to receive start event");
563 CLog::Log(LOGDEBUG
, "PAPlayer::Process - Playback started");
564 while(m_isPlaying
&& !m_bStop
)
566 /* this needs to happen outside of any locks to prevent deadlocks */
567 if (m_signalSpeedChange
)
569 m_callback
.OnPlayBackSpeedChanged(m_playbackSpeed
);
570 m_signalSpeedChange
= false;
573 double freeBufferTime
= 0.0;
574 ProcessStreams(freeBufferTime
);
576 // if none of our streams wants at least 10ms of data, we sleep
577 if (freeBufferTime
< 0.01)
579 CThread::Sleep(10ms
);
582 if (m_newForcedPlayerTime
!= -1)
584 if (SetTimeInternal(m_newForcedPlayerTime
))
586 m_newForcedPlayerTime
= -1;
590 if (m_newForcedTotalTime
!= -1)
592 if (SetTotalTimeInternal(m_newForcedTotalTime
))
594 m_newForcedTotalTime
= -1;
598 GetTimeInternal(); //update for GUI
603 inline void PAPlayer::ProcessStreams(double &freeBufferTime
)
605 std::unique_lock
<CCriticalSection
> sharedLock(m_streamsLock
);
606 if (m_isFinished
&& m_streams
.empty() && m_finishing
.empty())
609 freeBufferTime
= 1.0;
613 /* destroy any drained streams */
614 for (auto itt
= m_finishing
.begin(); itt
!= m_finishing
.end();)
616 StreamInfo
* si
= *itt
;
617 if (si
->m_stream
->IsDrained())
619 itt
= m_finishing
.erase(itt
);
622 CLog::Log(LOGDEBUG
, "PAPlayer::ProcessStreams - Stream Freed");
629 std::unique_lock
<CCriticalSection
> lock(m_streamsLock
);
631 for(StreamList::iterator itt
= m_streams
.begin(); itt
!= m_streams
.end(); ++itt
)
633 StreamInfo
* si
= *itt
;
634 if (!m_currentStream
&& !si
->m_started
)
636 m_currentStream
= si
;
637 UpdateGUIData(si
); //update for GUI
639 /* if the stream is finishing */
640 if ((si
->m_playNextTriggered
&& si
->m_stream
&& !si
->m_stream
->IsFading()) || !ProcessStream(si
, freeBufferTime
))
642 if (!si
->m_prepareTriggered
)
644 if (si
->m_waitOnDrain
)
646 si
->m_stream
->Drain(true);
647 si
->m_waitOnDrain
= false;
649 si
->m_prepareTriggered
= true;
650 m_callback
.OnQueueNextItem();
653 /* remove the stream */
654 itt
= m_streams
.erase(itt
);
655 /* if its the current stream */
656 if (si
== m_currentStream
)
658 /* if it was the last stream */
659 if (itt
== m_streams
.end())
661 /* if it didn't trigger the next queue item */
662 if (!si
->m_prepareTriggered
)
664 if (si
->m_waitOnDrain
)
666 si
->m_stream
->Drain(true);
667 si
->m_waitOnDrain
= false;
669 m_callback
.OnQueueNextItem();
670 si
->m_prepareTriggered
= true;
672 m_currentStream
= NULL
;
676 m_currentStream
= *itt
;
677 UpdateGUIData(*itt
); //update for GUI
681 /* unregister the audio callback */
682 si
->m_stream
->UnRegisterAudioCallback();
683 si
->m_decoder
.Destroy();
684 si
->m_stream
->Drain(false);
685 m_finishing
.push_back(si
);
692 // is it time to prepare the next stream?
693 if (si
->m_prepareNextAtFrame
> 0 && !si
->m_prepareTriggered
&& si
->m_framesSent
>= si
->m_prepareNextAtFrame
)
695 si
->m_prepareTriggered
= true;
696 m_callback
.OnQueueNextItem();
699 // it is time to start playing the next stream?
700 if (si
->m_playNextAtFrame
> 0 && !si
->m_playNextTriggered
&& !si
->m_nextFileItem
&& si
->m_framesSent
>= si
->m_playNextAtFrame
)
702 if (!si
->m_prepareTriggered
)
704 si
->m_prepareTriggered
= true;
705 m_callback
.OnQueueNextItem();
710 if (m_upcomingCrossfadeMS
)
712 si
->m_stream
->FadeVolume(1.0f
, 0.0f
, m_upcomingCrossfadeMS
);
713 si
->m_fadeOutTriggered
= true;
715 m_currentStream
= NULL
;
717 /* unregister the audio callback */
718 si
->m_stream
->UnRegisterAudioCallback();
721 si
->m_playNextTriggered
= true;
726 inline bool PAPlayer::ProcessStream(StreamInfo
*si
, double &freeBufferTime
)
728 /* if playback needs to start on this stream, do it */
729 if (si
== m_currentStream
&& !si
->m_started
)
731 si
->m_started
= true;
732 si
->m_stream
->RegisterAudioCallback(m_audioCallback
);
734 si
->m_stream
->Resume();
735 si
->m_stream
->FadeVolume(0.0f
, 1.0f
, m_upcomingCrossfadeMS
);
737 m_callback
.OnPlayBackStarted(si
->m_fileItem
);
738 m_signalStarted
= true;
741 CServiceBroker::GetAppMessenger()->PostMsg(TMSG_SWITCHTOFULLSCREEN
);
742 m_fullScreen
= false;
744 m_callback
.OnAVStarted(si
->m_fileItem
);
747 /* if we have not started yet and the stream has been primed */
748 unsigned int space
= si
->m_stream
->GetSpace();
749 if (!si
->m_started
&& !space
)
752 if (!m_playbackSpeed
)
755 /* see if it is time yet to FF/RW or a direct seek */
756 if (!si
->m_playNextTriggered
&& ((m_playbackSpeed
!= 1 && si
->m_framesSent
>= si
->m_seekNextAtFrame
) || si
->m_seekFrame
> -1))
758 int64_t time
= (int64_t)0;
759 /* if its a direct seek */
760 if (si
->m_seekFrame
> -1)
762 time
= (int64_t)((float)si
->m_seekFrame
/ (float)si
->m_audioFormat
.m_sampleRate
* 1000.0f
);
763 si
->m_framesSent
= (int)(si
->m_seekFrame
- ((float)si
->m_startOffset
* (float)si
->m_audioFormat
.m_sampleRate
) / 1000.0f
);
764 si
->m_seekFrame
= -1;
765 m_playerGUIData
.m_time
= time
; //update for GUI
766 si
->m_seekNextAtFrame
= 0;
767 CDataCacheCore::GetInstance().SetPlayTimes(0, time
, 0, m_playerGUIData
.m_totalTime
);
772 si
->m_framesSent
+= si
->m_audioFormat
.m_sampleRate
* (m_playbackSpeed
- 1);
773 si
->m_seekNextAtFrame
= si
->m_framesSent
+ si
->m_audioFormat
.m_sampleRate
/ 2;
774 time
= (int64_t)(((float)si
->m_framesSent
/ (float)si
->m_audioFormat
.m_sampleRate
* 1000.0f
) + (float)si
->m_startOffset
);
777 /* if we are seeking back before the start of the track start normal playback */
778 if (time
< si
->m_startOffset
|| si
->m_framesSent
< 0)
780 time
= si
->m_startOffset
;
781 si
->m_framesSent
= 0;
782 si
->m_seekNextAtFrame
= 0;
786 si
->m_decoder
.Seek(time
);
789 int status
= si
->m_decoder
.GetStatus();
790 if (status
== STATUS_ENDED
||
791 status
== STATUS_NO_FILE
||
792 si
->m_decoder
.ReadSamples(PACKET_SIZE
) == RET_ERROR
||
793 ((si
->m_endOffset
) && (si
->m_framesSent
/ si
->m_audioFormat
.m_sampleRate
>= (si
->m_endOffset
- si
->m_startOffset
) / 1000)))
795 if (si
== m_currentStream
&& si
->m_nextFileItem
)
799 // update current stream with info of next track
800 si
->m_startOffset
= si
->m_nextFileItem
->GetStartOffset();
801 if (si
->m_nextFileItem
->GetEndOffset())
802 si
->m_endOffset
= si
->m_nextFileItem
->GetEndOffset();
805 si
->m_framesSent
= 0;
807 si
->m_fileItem
= *si
->m_nextFileItem
;
808 si
->m_nextFileItem
.reset();
810 int64_t streamTotalTime
= si
->m_decoder
.TotalTime() - si
->m_startOffset
;
812 streamTotalTime
= si
->m_endOffset
- si
->m_startOffset
;
814 // calculate time when to prepare next stream
815 si
->m_prepareNextAtFrame
= 0;
816 if (streamTotalTime
>= TIME_TO_CACHE_NEXT_FILE
+ m_defaultCrossfadeMS
)
817 si
->m_prepareNextAtFrame
= (int)((streamTotalTime
- TIME_TO_CACHE_NEXT_FILE
- m_defaultCrossfadeMS
) * si
->m_audioFormat
.m_sampleRate
/ 1000.0f
);
819 si
->m_prepareTriggered
= false;
820 si
->m_playNextAtFrame
= 0;
821 si
->m_playNextTriggered
= false;
822 si
->m_seekNextAtFrame
= 0;
824 //update the current stream to start playing the next track at the correct frame.
825 UpdateStreamInfoPlayNextAtFrame(m_currentStream
, m_upcomingCrossfadeMS
);
829 m_callback
.OnPlayBackStarted(si
->m_fileItem
);
830 m_signalStarted
= true;
831 m_callback
.OnAVStarted(si
->m_fileItem
);
835 CLog::Log(LOGINFO
, "PAPlayer::ProcessStream - Stream Finished");
843 /* update free buffer time if we are running */
846 if (si
->m_stream
->IsBuffering())
847 freeBufferTime
= 1.0;
851 if (si
->m_audioFormat
.m_dataFormat
!= AE_FMT_RAW
)
852 free_space
= (double)(si
->m_stream
->GetSpace() / si
->m_bytesPerSample
) / si
->m_audioFormat
.m_sampleRate
;
854 free_space
= (double)si
->m_stream
->GetSpace() *
855 si
->m_audioFormat
.m_streamInfo
.GetDuration(true) / 1000;
857 freeBufferTime
= std::max(freeBufferTime
, free_space
);
864 bool PAPlayer::QueueData(StreamInfo
*si
)
866 unsigned int space
= si
->m_stream
->GetSpace();
868 if (si
->m_audioFormat
.m_dataFormat
!= AE_FMT_RAW
)
870 unsigned int samples
= std::min(si
->m_decoder
.GetDataSize(false), space
/ si
->m_bytesPerSample
);
874 // we want complete frames
875 samples
-= samples
% si
->m_audioFormat
.m_channelLayout
.Count();
877 uint8_t* data
= (uint8_t*)si
->m_decoder
.GetData(samples
);
880 CLog::Log(LOGERROR
, "PAPlayer::QueueData - Failed to get data from the decoder");
884 unsigned int frames
= samples
/si
->m_audioFormat
.m_channelLayout
.Count();
885 unsigned int added
= si
->m_stream
->AddData(&data
, 0, frames
, nullptr);
886 si
->m_framesSent
+= added
;
894 uint8_t *data
= si
->m_decoder
.GetRawData(size
);
897 int added
= si
->m_stream
->AddData(&data
, 0, size
, nullptr);
900 CLog::Log(LOGERROR
, "PAPlayer::QueueData - unknown error");
903 si
->m_framesSent
+= si
->m_audioFormat
.m_streamInfo
.GetDuration(true) / 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
)
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()
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
);