Experimental changes to support automatically enqueuing of previously

playing track. Required minor surgery to the ITransport interface, both
transport types, and PlaybackService.
This commit is contained in:
casey langen 2018-02-17 16:57:53 -08:00
parent a22c346293
commit 3dd4bbbad7
14 changed files with 153 additions and 61 deletions

View File

@ -69,34 +69,43 @@ PlaybackState CrossfadeTransport::GetPlaybackState() {
return this->state;
}
void CrossfadeTransport::PrepareNextTrack(const std::string& trackUrl, Gain gain) {
void CrossfadeTransport::PrepareNextTrack(const std::string& uri, Gain gain) {
Lock lock(this->stateMutex);
this->next.Reset(trackUrl, this, gain);
this->next.Reset(uri, this, gain, false);
}
void CrossfadeTransport::Start(const std::string& url, Gain gain) {
void CrossfadeTransport::Start(const std::string& uri, Gain gain, StartMode mode) {
{
Lock lock(this->stateMutex);
musik::debug::info(TAG, "trying to play " + url);
musik::debug::info(TAG, "trying to play " + uri);
bool immediate = mode == StartMode::Immediate;
/* in many cases (e.g. the user is skipping through tracks,
the requested track may already be queued up. use it, if it is */
if (this->next.player && this->next.player->GetUrl() == url) {
if (this->next.player && this->next.player->GetUrl() == uri) {
this->active.Reset();
this->next.TransferTo(this->active);
this->active.Start(this->volume);
if (immediate) {
this->active.Start(this->volume);
}
}
else {
this->active.Reset(url, this, gain);
this->active.Reset(uri, this, gain, immediate);
this->next.Stop();
}
}
this->RaiseStreamEvent(StreamScheduled, this->active.player);
}
std::string CrossfadeTransport::Uri() {
auto player = this->active.player;
return player ? player->GetUrl() : "";
}
void CrossfadeTransport::ReloadOutput() {
this->Stop();
}
@ -141,7 +150,7 @@ bool CrossfadeTransport::Resume() {
{
Lock lock(this->stateMutex);
this->crossfader.Resume();
this->active.Resume();
this->active.Resume(this->volume);
}
if (this->active.player) {
@ -262,12 +271,19 @@ void CrossfadeTransport::OnPlayerPrepared(Player* player) {
if (player == active.player) {
active.canFade = canFade;
active.Start(this->volume);
if (active.startImmediate) {
active.Start(this->volume);
}
}
else if (player == next.player) {
next.canFade = canFade;
}
}
if (player == this->active.player) {
this->RaiseStreamEvent(StreamPrepared, player);
this->SetPlaybackState(PlaybackPrepared);
}
}
void CrossfadeTransport::OnPlayerStarted(Player* player) {
@ -368,7 +384,8 @@ CrossfadeTransport::PlayerContext::PlayerContext(
: transport(transport)
, crossfader(crossfader)
, player(nullptr)
, canFade(false) {
, canFade(false)
, startImmediate(false) {
}
void CrossfadeTransport::PlayerContext::StopIf(Player* player) {
@ -378,17 +395,19 @@ void CrossfadeTransport::PlayerContext::StopIf(Player* player) {
}
void CrossfadeTransport::PlayerContext::Reset() {
this->Reset("", nullptr, Gain());
this->Reset("", nullptr, Gain(), false);
}
void CrossfadeTransport::PlayerContext::Reset(
const std::string& url,
Player::EventListener* listener,
Gain gain)
Gain gain,
bool startImmediate)
{
this->startImmediate = false;
if (this->player && this->output) {
this->player->Detach(&this->transport);
if (this->started && this->canFade) {
crossfader.Cancel(
this->player,
@ -407,6 +426,7 @@ void CrossfadeTransport::PlayerContext::Reset(
}
}
this->startImmediate = startImmediate;
this->canFade = this->started = false;
this->output = url.size() ? outputs::SelectedOutput() : nullptr;
this->player = url.size() ? Player::Create(url, this->output, Player::Drain, listener, gain) : nullptr;
@ -462,12 +482,17 @@ void CrossfadeTransport::PlayerContext::Pause() {
}
}
void CrossfadeTransport::PlayerContext::Resume() {
if (this->output) {
this->output->Resume();
void CrossfadeTransport::PlayerContext::Resume(double transportVolume) {
if (!this->started) {
this->Start(transportVolume);
}
else {
if (this->output) {
this->output->Resume();
if (this->player) {
this->player->Play();
if (this->player) {
this->player->Play();
}
}
}
}

View File

@ -60,8 +60,10 @@ namespace musik { namespace core { namespace audio {
void StopImmediately();
virtual void Start(const std::string& trackUrl, Gain gain);
virtual void PrepareNextTrack(const std::string& trackUrl, Gain gain);
virtual void Start(const std::string& uri, Gain gain, StartMode mode);
virtual void PrepareNextTrack(const std::string& uri, Gain gain);
virtual std::string Uri();
virtual void Stop();
virtual bool Pause();
@ -100,7 +102,8 @@ namespace musik { namespace core { namespace audio {
void Reset(
const std::string& url,
Player::EventListener* listener,
Gain gain);
Gain gain,
bool startImmediate);
void TransferTo(PlayerContext& context);
@ -108,10 +111,11 @@ namespace musik { namespace core { namespace audio {
void Stop();
void StopIf(Player* player);
void Pause();
void Resume();
void Resume(double transportVolume);
void SetVolume(double volume);
bool IsEmpty();
bool startImmediate;
bool started;
bool canFade;
Output output;

View File

@ -80,15 +80,15 @@ PlaybackState GaplessTransport::GetPlaybackState() {
return this->state;
}
void GaplessTransport::PrepareNextTrack(const std::string& trackUrl, Gain gain) {
void GaplessTransport::PrepareNextTrack(const std::string& uri, Gain gain) {
bool startNext = false;
{
LockT lock(this->stateMutex);
RESET_NEXT_PLAYER(this);
if (trackUrl.size()) {
this->nextPlayer = Player::Create(trackUrl, this->output, Player::NoDrain, this, gain);
if (uri.size()) {
this->nextPlayer = Player::Create(uri, this->output, Player::NoDrain, this, gain);
startNext = this->nextCanStart;
}
}
@ -98,16 +98,16 @@ void GaplessTransport::PrepareNextTrack(const std::string& trackUrl, Gain gain)
}
}
void GaplessTransport::Start(const std::string& url, Gain gain) {
musik::debug::info(TAG, "we were asked to start the track at " + url);
void GaplessTransport::Start(const std::string& uri, Gain gain, StartMode mode) {
musik::debug::info(TAG, "we were asked to start the track at " + uri);
Player* newPlayer = Player::Create(url, this->output, Player::NoDrain, this, gain);
Player* newPlayer = Player::Create(uri, this->output, Player::NoDrain, this, gain);
musik::debug::info(TAG, "Player created successfully");
this->StartWithPlayer(newPlayer);
this->StartWithPlayer(newPlayer, mode);
}
void GaplessTransport::StartWithPlayer(Player* newPlayer) {
void GaplessTransport::StartWithPlayer(Player* newPlayer, StartMode mode) {
if (newPlayer) {
bool playingNext = false;
@ -134,8 +134,10 @@ void GaplessTransport::StartWithPlayer(Player* newPlayer) {
this->StopInternal(true, !playingNext, newPlayer);
this->SetNextCanStart(false);
this->output->Resume();
newPlayer->Play();
musik::debug::info(TAG, "play()");
if (mode == StartMode::Immediate) {
newPlayer->Play();
}
this->RaiseStreamEvent(StreamScheduled, newPlayer);
}
@ -151,6 +153,11 @@ void GaplessTransport::Stop() {
this->StopInternal(false, true);
}
std::string GaplessTransport::Uri() {
auto player = this->activePlayer;
return player ? player->GetUrl() : "";
}
void GaplessTransport::StopInternal(
bool suppressStopEvent,
bool stopOutput,
@ -286,6 +293,13 @@ void GaplessTransport::SetNextCanStart(bool nextCanStart) {
this->nextCanStart = nextCanStart;
}
void GaplessTransport::OnPlayerPrepared(Player* player) {
if (player == this->activePlayer) {
this->RaiseStreamEvent(StreamPrepared, player);
this->SetPlaybackState(PlaybackPrepared);
}
}
void GaplessTransport::OnPlayerStarted(Player* player) {
this->RaiseStreamEvent(StreamPlaying, player);
this->SetPlaybackState(PlaybackPlaying);

View File

@ -53,8 +53,10 @@ namespace musik { namespace core { namespace audio {
GaplessTransport();
virtual ~GaplessTransport();
virtual void Start(const std::string& trackUrl, Gain gain);
virtual void PrepareNextTrack(const std::string& trackUrl, Gain gain);
virtual void Start(const std::string& uri, Gain gain, StartMode mode);
virtual void PrepareNextTrack(const std::string& uri, Gain gain);
virtual std::string Uri();
virtual void Stop();
virtual bool Pause();
@ -78,7 +80,7 @@ namespace musik { namespace core { namespace audio {
private:
using LockT = std::unique_lock<std::recursive_mutex>;
void StartWithPlayer(Player* player);
void StartWithPlayer(Player* player, StartMode mode = StartMode::Immediate);
void StopInternal(
bool suppressStopEvent,
@ -91,6 +93,7 @@ namespace musik { namespace core { namespace audio {
void SetPlaybackState(int state);
virtual void OnPlayerStarted(Player* player);
virtual void OnPlayerPrepared(Player* player);
virtual void OnPlayerAlmostEnded(Player* player);
virtual void OnPlayerFinished(Player* player);
virtual void OnPlayerError(Player* player);

View File

@ -45,6 +45,10 @@ namespace musik { namespace core { namespace audio {
public:
using Gain = Player::Gain;
enum class StartMode : int {
Immediate = 0, Wait = 1
};
sigslot::signal2<int, std::string> StreamEvent;
sigslot::signal1<int> PlaybackEvent;
sigslot::signal0<> VolumeChanged;
@ -52,8 +56,10 @@ namespace musik { namespace core { namespace audio {
virtual ~ITransport() { }
virtual void Start(const std::string& trackUrl, Gain gain) = 0;
virtual void PrepareNextTrack(const std::string& trackUrl, Gain gain) = 0;
virtual void Start(const std::string& uri, Gain gain, StartMode mode) = 0;
virtual void PrepareNextTrack(const std::string& uri, Gain gain) = 0;
virtual std::string Uri() = 0;
virtual void Stop() = 0;
virtual bool Pause() = 0;

View File

@ -103,12 +103,16 @@ MasterTransport::Type MasterTransport::GetType() {
return this->type;
}
void MasterTransport::PrepareNextTrack(const std::string& trackUrl, Gain gain) {
this->transport->PrepareNextTrack(trackUrl, gain);
void MasterTransport::PrepareNextTrack(const std::string& uri, Gain gain) {
this->transport->PrepareNextTrack(uri, gain);
}
void MasterTransport::Start(const std::string& trackUrl, Gain gain) {
this->transport->Start(trackUrl, gain);
void MasterTransport::Start(const std::string& uri, Gain gain, StartMode type) {
this->transport->Start(uri, gain, type);
}
std::string MasterTransport::Uri() {
return this->transport->Uri();
}
void MasterTransport::Stop() {

View File

@ -49,8 +49,10 @@ namespace musik { namespace core { namespace audio {
MasterTransport();
virtual ~MasterTransport();
virtual void Start(const std::string& trackUrl, Gain gain);
virtual void PrepareNextTrack(const std::string& trackUrl, Gain gain);
virtual void Start(const std::string& uri, Gain gain, StartMode mode);
virtual void PrepareNextTrack(const std::string& uri, Gain gain);
virtual std::string Uri();
virtual void Stop();
virtual bool Pause();

View File

@ -328,6 +328,16 @@ void PlaybackService::ProcessMessage(IMessage &message) {
if (eventType == PlaybackStopped) {
this->OnTrackChanged(NO_POSITION, TrackPtr());
}
else if (eventType == PlaybackPrepared) {
/* notify track change as soon as we're prepared. if we wait until
we start playing, it may be a while until the UI knows to redraw! */
if (this->UriAtIndex(this->index) == transport.Uri()) {
auto track = this->playlist.Get(this->index);
if (track) {
this->OnTrackChanged(this->index, track);
}
}
}
for (auto it = remotes.begin(); it != remotes.end(); it++) {
(*it)->OnPlaybackStateChanged((PlaybackState) eventType);
@ -664,21 +674,27 @@ void PlaybackService::CopyFrom(const musik::core::sdk::ITrackList* source) {
}
}
void PlaybackService::Play(size_t index) {
void PlaybackService::PlayAt(size_t index, ITransport::StartMode mode) {
index = std::min(this->Count(), index);
std::string uri = this->UriAtIndex(index);
auto gain = this->GainAtIndex(index);
if (uri.size()) {
transport.Start(
this->UriAtIndex(index),
this->GainAtIndex(index));
transport.Start(uri, gain, mode);
this->nextIndex = NO_POSITION;
this->index = index;
}
}
void PlaybackService::Play(size_t index) {
this->PlayAt(index, ITransport::StartMode::Immediate);
}
void PlaybackService::Prepare(size_t index) {
this->PlayAt(index, ITransport::StartMode::Wait);
}
size_t PlaybackService::GetIndex() {
return this->index;
}
@ -694,7 +710,7 @@ void PlaybackService::PauseOrResume() {
this->Play(0);
}
}
else if (state == PlaybackPaused) {
else if (state == PlaybackPaused || state == PlaybackPrepared) {
transport.Resume();
}
else if (state == PlaybackPlaying) {

View File

@ -115,6 +115,7 @@ namespace musik { namespace core { namespace audio {
concrete data types with known optimizations */
musik::core::audio::ITransport& GetTransport() { return this->transport; }
void Play(const musik::core::TrackList& tracks, size_t index);
void Prepare(size_t index);
void CopyTo(musik::core::TrackList& target);
void CopyFrom(const musik::core::TrackList& source);
musik::core::TrackPtr GetTrackAtIndex(size_t index);
@ -181,6 +182,8 @@ namespace musik { namespace core { namespace audio {
void InitRemotes();
void ResetRemotes();
void PlayAt(size_t index, ITransport::StartMode mode);
std::string UriAtIndex(size_t index);
musik::core::audio::ITransport::Gain GainAtIndex(size_t index);

View File

@ -40,17 +40,19 @@ namespace musik {
namespace core {
namespace sdk {
enum PlaybackState {
PlaybackStopped,
PlaybackPaused,
PlaybackPlaying
PlaybackStopped = 1,
PlaybackPaused = 2,
PlaybackPrepared = 3,
PlaybackPlaying = 4,
};
enum StreamEventType {
StreamScheduled = 1,
StreamPlaying = 2,
StreamAlmostDone = 3,
StreamFinished = 4,
StreamStopped = 5,
StreamPrepared = 2,
StreamPlaying = 3,
StreamAlmostDone = 4,
StreamFinished = 5,
StreamStopped = 6,
StreamError = -1
};

View File

@ -45,7 +45,7 @@ namespace musik {
namespace playback {
void PauseOrResume(ITransport& transport) {
int state = transport.GetPlaybackState();
if (state == PlaybackPaused) {
if (state == PlaybackPaused || state == PlaybackPrepared) {
transport.Resume();
}
else if (state == PlaybackPlaying) {

View File

@ -298,7 +298,7 @@ bool ConsoleLayout::ProcessCommand(const std::string& cmd) {
bool ConsoleLayout::PlayFile(const std::vector<std::string>& args) {
if (args.size() > 0) {
std::string filename = boost::algorithm::join(args, " ");
transport.Start(filename, ITransport::Gain());
transport.Start(filename, ITransport::Gain(), ITransport::StartMode::Immediate);
return true;
}

View File

@ -95,10 +95,22 @@ void NowPlayingLayout::LoadLastSession() {
PersistedPlayQueueQuery::Restore(this->library, this->playback));
this->library->Enqueue(query, ILibrary::QuerySynchronous);
int index = this->prefs->GetInt(keys::LastPlayQueueIndex, -1);
if (index >= 0) {
this->playback.Prepare(index);
}
}
void NowPlayingLayout::SaveSession() {
if (this->prefs->GetBool(keys::SaveSessionOnExit, false)) {
if (playback.GetPlaybackState() != sdk::PlaybackStopped) {
this->prefs->SetInt(keys::LastPlayQueueIndex, (int) playback.GetIndex());
}
else {
this->prefs->SetInt(keys::LastPlayQueueIndex, -1);
}
auto query = std::shared_ptr<PersistedPlayQueueQuery>(
PersistedPlayQueueQuery::Save(this->library, this->playback));

View File

@ -484,8 +484,9 @@ void TransportWindow::Update(TimeMode timeMode) {
}
WINDOW *c = this->GetContent();
bool paused = (transport.GetPlaybackState() == PlaybackPaused);
bool stopped = (transport.GetPlaybackState() == PlaybackStopped);
auto state = transport.GetPlaybackState();
bool paused = (state == PlaybackPrepared || state == PlaybackPaused);
bool stopped = (state == PlaybackStopped);
bool muted = transport.IsMuted();
bool replayGainEnabled = (this->replayGainMode != ReplayGainMode::Disabled);