/* PlayManager.cpp */
/* Copyright (C) 2011-2020 Michael Lugmair (Lucio Carreras)
*
* This file is part of sayonara player
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include "PlayManagerImpl.h"
#include "Components/Tagging/ChangeNotifier.h"
#include "Interfaces/Notification/NotificationHandler.h"
#include "Utils/Algorithm.h"
#include "Utils/MetaData/MetaData.h"
#include "Utils/MetaData/MetaDataList.h"
#include "Utils/Settings/Settings.h"
#include "Utils/Logger/Logger.h"
#include "Utils/FileUtils.h"
#include
#include
constexpr const auto InvalidTimeStamp {-1};
template
class RingBuffer
{
private:
size_t mCurrentIndex;
size_t mItemCount;
std::array mData;
public:
RingBuffer()
{
clear();
}
void clear()
{
mCurrentIndex = 0;
mItemCount = 0;
}
void insert(const T& item)
{
mData[mCurrentIndex] = item;
mCurrentIndex = (mCurrentIndex + 1) % MAX_ITEM_COUNT;
mItemCount = std::min(MAX_ITEM_COUNT, mItemCount + 1);
}
bool contains(const T& item) const
{
auto it = std::find(mData.begin(), mData.end(), item);
return (it != mData.end());
}
int count() const
{
return int(mItemCount);
}
bool isEmpty() const
{
return (count() == 0);
}
};
struct PlayManagerImpl::Private
{
MetaData currentTrack;
RingBuffer ringBuffer;
int currentTrackIndex;
MilliSeconds positionMs;
MilliSeconds initialPositionMs;
MilliSeconds trackPlaytimeMs;
PlayState playstate;
Private() :
currentTrackIndex(-1),
positionMs(0),
initialPositionMs(InvalidTimeStamp),
trackPlaytimeMs(0),
playstate(PlayState::FirstStartup)
{
const auto loadPlaylist = (GetSetting(Set::PL_LoadSavedPlaylists) ||
GetSetting(Set::PL_LoadTemporaryPlaylists));
const auto loadLastTrack = GetSetting(Set::PL_LoadLastTrack);
const auto rememberLastTime = GetSetting(Set::PL_RememberTime);
if(loadPlaylist && loadLastTrack)
{
initialPositionMs = rememberLastTime ?
(GetSetting(Set::Engine_CurTrackPos_s) * 1000) :
0;
}
}
void reset()
{
currentTrack = MetaData();
ringBuffer.clear();
currentTrackIndex = -1;
positionMs = 0;
trackPlaytimeMs = 0;
initialPositionMs = InvalidTimeStamp;
playstate = PlayState::Stopped;
}
};
PlayManagerImpl::PlayManagerImpl(QObject* parent) :
PlayManager(parent)
{
m = Pimpl::make();
auto* mdcn = Tagging::ChangeNotifier::instance();
connect(mdcn, &Tagging::ChangeNotifier::sigMetadataChanged, this, &PlayManagerImpl::trackMetadataChanged);
connect(mdcn, &Tagging::ChangeNotifier::sigMetadataDeleted, this, &PlayManagerImpl::tracksDeleted);
}
PlayManagerImpl::~PlayManagerImpl() = default;
PlayState PlayManagerImpl::playstate() const
{
return m->playstate;
}
MilliSeconds PlayManagerImpl::currentPositionMs() const
{
return m->positionMs;
}
MilliSeconds PlayManagerImpl::currentTrackPlaytimeMs() const
{
return m->trackPlaytimeMs;
}
MilliSeconds PlayManagerImpl::initialPositionMs() const
{
return m->initialPositionMs;
}
MilliSeconds PlayManagerImpl::durationMs() const
{
return m->currentTrack.durationMs();
}
Bitrate PlayManagerImpl::bitrate() const
{
return m->currentTrack.bitrate();
}
const MetaData& PlayManagerImpl::currentTrack() const
{
return m->currentTrack;
}
int PlayManagerImpl::volume() const
{
return GetSetting(Set::Engine_Vol);
}
bool PlayManagerImpl::isMuted() const
{
return GetSetting(Set::Engine_Mute);
}
void PlayManagerImpl::play()
{
m->playstate = PlayState::Playing;
emit sigPlaystateChanged(m->playstate);
}
void PlayManagerImpl::wakeUp()
{
emit sigWakeup();
}
void PlayManagerImpl::playPause()
{
if(m->playstate == PlayState::Playing)
{
pause();
}
else if(m->playstate == PlayState::Stopped)
{
wakeUp();
}
else
{
play();
}
}
void PlayManagerImpl::pause()
{
m->playstate = PlayState::Paused;
emit sigPlaystateChanged(m->playstate);
}
void PlayManagerImpl::previous()
{
emit sigPrevious();
}
void PlayManagerImpl::next()
{
emit sigNext();
}
void PlayManagerImpl::stop()
{
m->reset();
emit sigPlaystateChanged(m->playstate);
}
void PlayManagerImpl::record(bool b)
{
emit sigRecording(b && GetSetting(SetNoDB::MP3enc_found));
}
void PlayManagerImpl::seekRelative(double percent)
{
emit sigSeekedRelative(percent);
}
void PlayManagerImpl::seekRelativeMs(MilliSeconds ms)
{
emit sigSeekedRelativeMs(ms);
}
void PlayManagerImpl::seekAbsoluteMs(MilliSeconds ms)
{
emit sigSeekedAbsoluteMs(ms);
}
void PlayManagerImpl::setCurrentPositionMs(MilliSeconds ms)
{
const auto differenceMs = (ms - m->positionMs);
if(differenceMs > 0 && differenceMs < 1000)
{
m->trackPlaytimeMs += differenceMs;
}
m->positionMs = ms;
SetSetting(Set::Engine_CurTrackPos_s, int(m->positionMs / 1000));
emit sigPositionChangedMs(ms);
}
void PlayManagerImpl::changeCurrentTrack(const MetaData& track, int trackIdx)
{
const auto isFirstStart = (m->playstate == PlayState::FirstStartup);
m->currentTrack = track;
m->positionMs = 0;
m->trackPlaytimeMs = 0;
m->currentTrackIndex = trackIdx;
m->ringBuffer.clear();
// initial position is outdated now and never needed again
if(m->initialPositionMs >= 0)
{
const auto oldIndex = GetSetting(Set::PL_LastTrack);
if(oldIndex != m->currentTrackIndex)
{
m->initialPositionMs = InvalidTimeStamp;
}
}
// play or stop
if(m->currentTrackIndex >= 0)
{
emit sigCurrentTrackChanged(m->currentTrack);
emit sigTrackIndexChanged(m->currentTrackIndex);
if(!isFirstStart)
{
play();
if((track.radioMode() != RadioMode::Off) &&
GetSetting(Set::Engine_SR_Active) &&
GetSetting(Set::Engine_SR_AutoRecord))
{
record(true);
}
}
}
else
{
spLog(Log::Info, this) << "Playlist finished";
emit sigPlaylistFinished();
stop();
}
if(!isFirstStart)
{
// save last track
const auto currentIndex = (track.databaseId() == 0) ? m->currentTrackIndex : -1;
SetSetting(Set::PL_LastTrack, currentIndex);
}
// show notification
if(GetSetting(Set::Notification_Show))
{
if(m->currentTrackIndex > -1 && !m->currentTrack.filepath().isEmpty())
{
NotificationHandler::instance()->notify(m->currentTrack);
}
}
}
void PlayManagerImpl::changeCurrentMetadata(const MetaData& md)
{
if(m->currentTrack.radioMode() == RadioMode::Podcast)
{
if(!m->currentTrack.title().isEmpty() &&
!m->currentTrack.artist().isEmpty() &&
!m->currentTrack.album().isEmpty())
{
return;
}
}
auto lastTrack = std::move(m->currentTrack);
m->currentTrack = md;
const auto trackInfoString = md.title() + md.artist() + md.album();
const auto hasData = m->ringBuffer.contains(trackInfoString);
if(!hasData)
{
if(GetSetting(Set::Notification_Show))
{
NotificationHandler::instance()->notify(m->currentTrack);
}
// only insert www tracks into the buffer
if(m->ringBuffer.count() > 0 && Util::File::isWWW(md.filepath()))
{
lastTrack.setAlbum("");
lastTrack.setDisabled(true);
lastTrack.setFilepath("");
const auto time = QTime::currentTime();
lastTrack.setDurationMs((time.hour() * 60 + time.minute()) * 1000);
emit sigStreamFinished(lastTrack);
m->trackPlaytimeMs = 0;
}
}
emit sigCurrentMetadataChanged();
}
void PlayManagerImpl::setTrackReady()
{
if(m->initialPositionMs == InvalidTimeStamp)
{
return;
}
spLog(Log::Debug, this) << "Track ready, Start at " << m->initialPositionMs / 1000 << "ms";
if(m->initialPositionMs != 0)
{
this->seekAbsoluteMs(m->initialPositionMs);
}
m->initialPositionMs = InvalidTimeStamp;
GetSetting(Set::PL_StartPlaying)
? play()
: pause();
}
void PlayManagerImpl::setTrackFinished()
{
next();
}
void PlayManagerImpl::buffering(int progress)
{
emit sigBuffering(progress);
}
void PlayManagerImpl::volumeUp()
{
setVolume(GetSetting(Set::Engine_Vol) + 5);
}
void PlayManagerImpl::volumeDown()
{
setVolume(GetSetting(Set::Engine_Vol) - 5);
}
void PlayManagerImpl::setVolume(int vol)
{
vol = std::min(vol, 100);
vol = std::max(vol, 0);
SetSetting(Set::Engine_Vol, vol);
emit sigVolumeChanged(vol);
}
void PlayManagerImpl::setMute(bool b)
{
SetSetting(Set::Engine_Mute, b);
emit sigMuteChanged(b);
}
void PlayManagerImpl::toggleMute()
{
setMute(!GetSetting(Set::Engine_Mute));
}
void PlayManagerImpl::error(const QString& message)
{
emit sigError(message);
}
void PlayManagerImpl::changeDuration(MilliSeconds ms)
{
m->currentTrack.setDurationMs(ms);
emit sigDurationChangedMs();
}
void PlayManagerImpl::changeBitrate(Bitrate br)
{
m->currentTrack.setBitrate(br);
emit sigBitrateChanged();
}
void PlayManagerImpl::shutdown()
{
if(m->playstate == PlayState::Stopped)
{
SetSetting(Set::PL_LastTrack, -1);
SetSetting(Set::Engine_CurTrackPos_s, 0);
}
else
{
SetSetting(Set::Engine_CurTrackPos_s, int(m->positionMs / 1000));
}
}
void PlayManagerImpl::trackMetadataChanged()
{
auto* mdcn = static_cast(sender());
const auto& changedMetadata = mdcn->changedMetadata();
for(const auto& trackPair : changedMetadata)
{
const auto isSamePath = m->currentTrack.isEqual(trackPair.first);
if(isSamePath)
{
this->changeCurrentMetadata(trackPair.second);
return;
}
}
}
void PlayManagerImpl::tracksDeleted()
{
auto* mdcn = static_cast(sender());
const auto& deletedTracks = mdcn->deletedMetadata();
if(deletedTracks.contains(m->currentTrack))
{
stop();
}
}