Propagate buffering status from Player -> Transport -> PlaybackService

-> UI and render it in TransportWindow.
This commit is contained in:
casey langen 2020-10-13 21:37:51 -07:00
parent b21b21dab6
commit c644862959
18 changed files with 161 additions and 62 deletions

View File

@ -50,7 +50,7 @@ static std::string TAG = "CrossfadeTransport";
CrossfadeTransport::CrossfadeTransport() CrossfadeTransport::CrossfadeTransport()
: volume(1.0) : volume(1.0)
, state(PlaybackStopped) , playbackState(PlaybackStopped)
, muted(false) , muted(false)
, crossfader(*this) , crossfader(*this)
, active(*this, crossfader) , active(*this, crossfader)
@ -66,7 +66,12 @@ CrossfadeTransport::~CrossfadeTransport() {
PlaybackState CrossfadeTransport::GetPlaybackState() { PlaybackState CrossfadeTransport::GetPlaybackState() {
Lock lock(this->stateMutex); Lock lock(this->stateMutex);
return this->state; return this->playbackState;
}
StreamState CrossfadeTransport::GetStreamState() {
Lock lock(this->stateMutex);
return this->activePlayerState;
} }
void CrossfadeTransport::PrepareNextTrack(const std::string& uri, Gain gain) { void CrossfadeTransport::PrepareNextTrack(const std::string& uri, Gain gain) {
@ -88,6 +93,12 @@ void CrossfadeTransport::Start(const std::string& uri, Gain gain, StartMode mode
this->active.Reset(); this->active.Reset();
this->next.TransferTo(this->active); this->next.TransferTo(this->active);
if (this->active.player) {
this->RaiseStreamEvent(
this->active.player->GetStreamState(),
this->active.player);
}
if (immediate) { if (immediate) {
this->active.Start(this->volume); this->active.Start(this->volume);
} }
@ -98,7 +109,7 @@ void CrossfadeTransport::Start(const std::string& uri, Gain gain, StartMode mode
} }
} }
this->RaiseStreamEvent(StreamScheduled, this->active.player); this->RaiseStreamEvent(StreamBuffering, this->active.player);
} }
std::string CrossfadeTransport::Uri() { std::string CrossfadeTransport::Uri() {
@ -176,7 +187,7 @@ void CrossfadeTransport::SetPosition(double seconds) {
Lock lock(this->stateMutex); Lock lock(this->stateMutex);
if (this->active.player) { if (this->active.player) {
if (this->state != PlaybackPlaying) { if (this->playbackState != PlaybackPlaying) {
this->SetPlaybackState(PlaybackPlaying); this->SetPlaybackState(PlaybackPlaying);
} }
this->active.player->SetPosition(seconds); this->active.player->SetPosition(seconds);
@ -243,7 +254,7 @@ void CrossfadeTransport::SetVolume(double volume) {
} }
} }
void CrossfadeTransport::OnPlayerPrepared(Player* player) { void CrossfadeTransport::OnPlayerBuffered(Player* player) {
{ {
Lock lock(this->stateMutex); Lock lock(this->stateMutex);
@ -280,7 +291,7 @@ void CrossfadeTransport::OnPlayerPrepared(Player* player) {
} }
if (player == this->active.player) { if (player == this->active.player) {
this->RaiseStreamEvent(StreamPrepared, player); this->RaiseStreamEvent(StreamBuffered, player);
this->SetPlaybackState(PlaybackPrepared); this->SetPlaybackState(PlaybackPrepared);
} }
} }
@ -364,8 +375,8 @@ void CrossfadeTransport::SetPlaybackState(int state) {
{ {
Lock lock(this->stateMutex); Lock lock(this->stateMutex);
changed = (this->state != state); changed = (this->playbackState != state);
this->state = (PlaybackState) state; this->playbackState = (PlaybackState) state;
} }
if (changed) { if (changed) {
@ -374,6 +385,13 @@ void CrossfadeTransport::SetPlaybackState(int state) {
} }
void CrossfadeTransport::RaiseStreamEvent(int type, Player* player) { void CrossfadeTransport::RaiseStreamEvent(int type, Player* player) {
{
Lock lock(this->stateMutex);
if (player == active.player) {
this->activePlayerState = (StreamState) type;
}
}
this->StreamEvent(type, player->GetUrl()); this->StreamEvent(type, player->GetUrl());
} }

View File

@ -83,6 +83,7 @@ namespace musik { namespace core { namespace audio {
virtual void ReloadOutput(); virtual void ReloadOutput();
virtual musik::core::sdk::PlaybackState GetPlaybackState(); virtual musik::core::sdk::PlaybackState GetPlaybackState();
virtual musik::core::sdk::StreamState GetStreamState();
private: private:
using Lock = std::unique_lock<std::recursive_mutex>; using Lock = std::unique_lock<std::recursive_mutex>;
@ -129,13 +130,14 @@ namespace musik { namespace core { namespace audio {
void OnCrossfaderEmptied(); void OnCrossfaderEmptied();
virtual void OnPlayerPrepared(Player* player); virtual void OnPlayerBuffered(Player* player);
virtual void OnPlayerStarted(Player* player); virtual void OnPlayerStarted(Player* player);
virtual void OnPlayerFinished(Player* player); virtual void OnPlayerFinished(Player* player);
virtual void OnPlayerError(Player* player); virtual void OnPlayerError(Player* player);
virtual void OnPlayerMixPoint(Player* player, int id, double time); virtual void OnPlayerMixPoint(Player* player, int id, double time);
musik::core::sdk::PlaybackState state; musik::core::sdk::PlaybackState playbackState;
musik::core::sdk::StreamState activePlayerState;
std::recursive_mutex stateMutex; std::recursive_mutex stateMutex;
Crossfader crossfader; Crossfader crossfader;
PlayerContext active; PlayerContext active;

View File

@ -57,11 +57,12 @@ static std::string TAG = "GaplessTransport";
instance->activePlayer->Detach(instance); \ instance->activePlayer->Detach(instance); \
instance->activePlayer->Destroy(); \ instance->activePlayer->Destroy(); \
instance->activePlayer = nullptr; \ instance->activePlayer = nullptr; \
instance->activePlayerState = StreamError; \
} }
GaplessTransport::GaplessTransport() GaplessTransport::GaplessTransport()
: volume(1.0) : volume(1.0)
, state(PlaybackStopped) , playbackState(PlaybackStopped)
, activePlayer(nullptr) , activePlayer(nullptr)
, nextPlayer(nullptr) , nextPlayer(nullptr)
, nextCanStart(false) , nextCanStart(false)
@ -77,7 +78,12 @@ GaplessTransport::~GaplessTransport() {
PlaybackState GaplessTransport::GetPlaybackState() { PlaybackState GaplessTransport::GetPlaybackState() {
LockT lock(this->stateMutex); LockT lock(this->stateMutex);
return this->state; return this->playbackState;
}
StreamState GaplessTransport::GetStreamState() {
LockT lock(this->stateMutex);
return this->activePlayerState;
} }
void GaplessTransport::PrepareNextTrack(const std::string& uri, Gain gain) { void GaplessTransport::PrepareNextTrack(const std::string& uri, Gain gain) {
@ -121,6 +127,10 @@ void GaplessTransport::StartWithPlayer(Player* newPlayer, StartMode mode) {
this->nextPlayer = nullptr; this->nextPlayer = nullptr;
this->activePlayer = newPlayer; this->activePlayer = newPlayer;
if (newPlayer) {
this->RaiseStreamEvent(newPlayer->GetStreamState(), newPlayer);
}
/* first argument suppresses the "Stop" event from getting triggered, /* first argument suppresses the "Stop" event from getting triggered,
the second param is used for gapless playback -- we won't stop the output the second param is used for gapless playback -- we won't stop the output
and will allow pending buffers to finish if we're not automatically and will allow pending buffers to finish if we're not automatically
@ -135,8 +145,6 @@ void GaplessTransport::StartWithPlayer(Player* newPlayer, StartMode mode) {
newPlayer->Play(); newPlayer->Play();
} }
} }
this->RaiseStreamEvent(StreamScheduled, newPlayer);
} }
} }
@ -237,7 +245,7 @@ void GaplessTransport::SetPosition(double seconds) {
LockT lock(this->stateMutex); LockT lock(this->stateMutex);
if (this->activePlayer) { if (this->activePlayer) {
if (this->state != PlaybackPlaying) { if (this->playbackState != PlaybackPlaying) {
this->SetPlaybackState(PlaybackPlaying); this->SetPlaybackState(PlaybackPlaying);
} }
this->activePlayer->SetPosition(seconds); this->activePlayer->SetPosition(seconds);
@ -289,9 +297,9 @@ void GaplessTransport::SetNextCanStart(bool nextCanStart) {
this->nextCanStart = nextCanStart; this->nextCanStart = nextCanStart;
} }
void GaplessTransport::OnPlayerPrepared(Player* player) { void GaplessTransport::OnPlayerBuffered(Player* player) {
if (player == this->activePlayer) { if (player == this->activePlayer) {
this->RaiseStreamEvent(StreamPrepared, player); this->RaiseStreamEvent(StreamBuffered, player);
this->SetPlaybackState(PlaybackPrepared); this->SetPlaybackState(PlaybackPrepared);
} }
} }
@ -372,8 +380,8 @@ void GaplessTransport::SetPlaybackState(int state) {
{ {
LockT lock(this->stateMutex); LockT lock(this->stateMutex);
changed = (this->state != state); changed = (this->playbackState != state);
this->state = (PlaybackState) state; this->playbackState = (PlaybackState) state;
} }
if (changed) { if (changed) {
@ -382,5 +390,11 @@ void GaplessTransport::SetPlaybackState(int state) {
} }
void GaplessTransport::RaiseStreamEvent(int type, Player* player) { void GaplessTransport::RaiseStreamEvent(int type, Player* player) {
{
LockT lock(this->stateMutex);
if (player == activePlayer) {
this->activePlayerState = (StreamState) type;
}
}
this->StreamEvent(type, player->GetUrl()); this->StreamEvent(type, player->GetUrl());
} }

View File

@ -76,6 +76,7 @@ namespace musik { namespace core { namespace audio {
virtual void ReloadOutput(); virtual void ReloadOutput();
virtual musik::core::sdk::PlaybackState GetPlaybackState(); virtual musik::core::sdk::PlaybackState GetPlaybackState();
virtual musik::core::sdk::StreamState GetStreamState();
private: private:
using LockT = std::unique_lock<std::recursive_mutex>; using LockT = std::unique_lock<std::recursive_mutex>;
@ -93,13 +94,14 @@ namespace musik { namespace core { namespace audio {
void SetPlaybackState(int state); void SetPlaybackState(int state);
virtual void OnPlayerStarted(Player* player); virtual void OnPlayerStarted(Player* player);
virtual void OnPlayerPrepared(Player* player); virtual void OnPlayerBuffered(Player* player);
virtual void OnPlayerAlmostEnded(Player* player); virtual void OnPlayerAlmostEnded(Player* player);
virtual void OnPlayerFinished(Player* player); virtual void OnPlayerFinished(Player* player);
virtual void OnPlayerError(Player* player); virtual void OnPlayerError(Player* player);
virtual void OnPlayerDestroying(Player* player); virtual void OnPlayerDestroying(Player* player);
musik::core::sdk::PlaybackState state; musik::core::sdk::PlaybackState playbackState;
musik::core::sdk::StreamState activePlayerState;
std::recursive_mutex stateMutex; std::recursive_mutex stateMutex;
std::shared_ptr<musik::core::sdk::IOutput> output; std::shared_ptr<musik::core::sdk::IOutput> output;
Player* activePlayer; Player* activePlayer;

View File

@ -79,6 +79,7 @@ namespace musik { namespace core { namespace audio {
virtual void ReloadOutput() = 0; virtual void ReloadOutput() = 0;
virtual musik::core::sdk::PlaybackState GetPlaybackState() = 0; virtual musik::core::sdk::PlaybackState GetPlaybackState() = 0;
virtual musik::core::sdk::StreamState GetStreamState() = 0;
}; };
} } } } } }

View File

@ -163,6 +163,10 @@ PlaybackState MasterTransport::GetPlaybackState() {
return this->transport->GetPlaybackState(); return this->transport->GetPlaybackState();
} }
StreamState MasterTransport::GetStreamState() {
return this->transport->GetStreamState();
}
void MasterTransport::OnStreamEvent(int type, std::string url) { void MasterTransport::OnStreamEvent(int type, std::string url) {
this->StreamEvent(type, url); this->StreamEvent(type, url);
} }

View File

@ -72,6 +72,7 @@ namespace musik { namespace core { namespace audio {
virtual void ReloadOutput(); virtual void ReloadOutput();
virtual musik::core::sdk::PlaybackState GetPlaybackState(); virtual musik::core::sdk::PlaybackState GetPlaybackState();
virtual musik::core::sdk::StreamState GetStreamState();
void SwitchTo(Type type); void SwitchTo(Type type);
Type GetType(); Type GetType();

View File

@ -305,15 +305,13 @@ void PlaybackService::ProcessMessage(IMessage &message) {
} }
else if (type == MESSAGE_STREAM_EVENT) { else if (type == MESSAGE_STREAM_EVENT) {
StreamMessage* streamMessage = static_cast<StreamMessage*>(&message); StreamMessage* streamMessage = static_cast<StreamMessage*>(&message);
StreamState eventType = (StreamState) streamMessage->GetEventType();
int64_t eventType = streamMessage->GetEventType();
if (eventType == StreamPlaying) { if (eventType == StreamPlaying) {
TrackPtr track; TrackPtr track;
{ {
std::unique_lock<std::recursive_mutex> lock(this->playlistMutex); std::unique_lock<std::recursive_mutex> lock(this->playlistMutex);
if (this->nextIndex != NO_POSITION) { if (this->nextIndex != NO_POSITION) {
/* in most cases when we get here it means that the next track is /* in most cases when we get here it means that the next track is
starting, so we want to update our internal index. however, because starting, so we want to update our internal index. however, because
@ -343,6 +341,16 @@ void PlaybackService::ProcessMessage(IMessage &message) {
this->PrepareNextTrack(); this->PrepareNextTrack();
} }
bool raiseStreamEvent = false;
{
std::unique_lock<std::recursive_mutex> lock(this->playlistMutex);
raiseStreamEvent = this->UriAtIndex(this->index) == streamMessage->GetUri();
}
if (raiseStreamEvent) {
this->StreamStateChanged(eventType);
}
} }
else if (type == MESSAGE_PLAYBACK_EVENT) { else if (type == MESSAGE_PLAYBACK_EVENT) {
int64_t eventType = message.UserData1(); int64_t eventType = message.UserData1();
@ -365,7 +373,7 @@ void PlaybackService::ProcessMessage(IMessage &message) {
(*it)->OnPlaybackStateChanged((PlaybackState) eventType); (*it)->OnPlaybackStateChanged((PlaybackState) eventType);
} }
this->PlaybackEvent((PlaybackState) eventType); this->PlaybackStateChanged((PlaybackState) eventType);
} }
else if (type == MESSAGE_PREPARE_NEXT_TRACK) { else if (type == MESSAGE_PREPARE_NEXT_TRACK) {
if (transport->GetPlaybackState() != PlaybackStopped) { if (transport->GetPlaybackState() != PlaybackStopped) {

View File

@ -63,7 +63,8 @@ namespace musik { namespace core { namespace audio {
/* copied from Transport, but will be automatically called on the /* copied from Transport, but will be automatically called on the
specified MessageQueue's thread! */ specified MessageQueue's thread! */
sigslot::signal1<int> PlaybackEvent; sigslot::signal1<musik::core::sdk::PlaybackState> PlaybackStateChanged;
sigslot::signal1<musik::core::sdk::StreamState> StreamStateChanged;
sigslot::signal0<> VolumeChanged; sigslot::signal0<> VolumeChanged;
sigslot::signal1<double> TimeChanged; sigslot::signal1<double> TimeChanged;

View File

@ -123,7 +123,8 @@ Player::Player(
DestroyMode destroyMode, DestroyMode destroyMode,
EventListener *listener, EventListener *listener,
Gain gain) Gain gain)
: state(Player::Idle) : internalState(Player::Idle)
, streamState(StreamBuffering)
, stream(Stream::Create()) , stream(Stream::Create())
, url(url) , url(url)
, currentPosition(0) , currentPosition(0)
@ -159,8 +160,8 @@ Player::~Player() {
void Player::Play() { void Player::Play() {
std::unique_lock<std::mutex> lock(this->queueMutex); std::unique_lock<std::mutex> lock(this->queueMutex);
if (this->state != Player::Quit) { if (this->internalState != Player::Quit) {
this->state = Player::Playing; this->internalState = Player::Playing;
this->writeToOutputCondition.notify_all(); this->writeToOutputCondition.notify_all();
} }
} }
@ -173,11 +174,11 @@ void Player::Destroy() {
std::unique_lock<std::mutex> lock(this->queueMutex); std::unique_lock<std::mutex> lock(this->queueMutex);
if (this->state == Player::Quit && !this->thread) { if (this->internalState == Player::Quit && !this->thread) {
return; /* already terminated (or terminating) */ return; /* already terminated (or terminating) */
} }
this->state = Player::Quit; this->internalState = Player::Quit;
this->writeToOutputCondition.notify_all(); this->writeToOutputCondition.notify_all();
this->thread->detach(); this->thread->detach();
delete this->thread; delete this->thread;
@ -258,7 +259,7 @@ void Player::AddMixPoint(int id, double time) {
int Player::State() { int Player::State() {
std::unique_lock<std::mutex> lock(this->queueMutex); std::unique_lock<std::mutex> lock(this->queueMutex);
return this->state; return this->internalState;
} }
bool Player::HasCapability(Capability c) { bool Player::HasCapability(Capability c) {
@ -297,13 +298,14 @@ void musik::core::audio::playerThreadLoop(Player* player) {
if (player->stream->OpenStream(player->url)) { if (player->stream->OpenStream(player->url)) {
for (Listener* l : player->Listeners()) { for (Listener* l : player->Listeners()) {
l->OnPlayerPrepared(player); player->streamState = StreamBuffered;
l->OnPlayerBuffered(player);
} }
/* wait until we enter the Playing or Quit state */ /* wait until we enter the Playing or Quit state */
{ {
std::unique_lock<std::mutex> lock(player->queueMutex); std::unique_lock<std::mutex> lock(player->queueMutex);
while (player->state == Player::Idle) { while (player->internalState == Player::Idle) {
player->writeToOutputCondition.wait(lock); player->writeToOutputCondition.wait(lock);
} }
} }
@ -419,6 +421,7 @@ void musik::core::audio::playerThreadLoop(Player* player) {
it wasn't stopped by the user. raise the "almost ended" flag. */ it wasn't stopped by the user. raise the "almost ended" flag. */
if (!player->Exited()) { if (!player->Exited()) {
for (Listener* l : player->Listeners()) { for (Listener* l : player->Listeners()) {
player->streamState = StreamAlmostDone;
l->OnPlayerAlmostEnded(player); l->OnPlayerAlmostEnded(player);
} }
} }
@ -428,6 +431,7 @@ void musik::core::audio::playerThreadLoop(Player* player) {
else { else {
if (!player->Exited()) { if (!player->Exited()) {
for (Listener* l : player->Listeners()) { for (Listener* l : player->Listeners()) {
player->streamState = StreamError;
l->OnPlayerError(player); l->OnPlayerError(player);
} }
} }
@ -454,13 +458,15 @@ void musik::core::audio::playerThreadLoop(Player* player) {
if (!player->Exited()) { if (!player->Exited()) {
for (Listener* l : player->Listeners()) { for (Listener* l : player->Listeners()) {
player->streamState = StreamFinished;
l->OnPlayerFinished(player); l->OnPlayerFinished(player);
} }
} }
player->state = Player::Quit; player->internalState = Player::Quit;
for (Listener* l : player->Listeners()) { for (Listener* l : player->Listeners()) {
player->streamState = StreamStopped;
l->OnPlayerDestroying(player); l->OnPlayerDestroying(player);
} }
@ -471,7 +477,7 @@ void musik::core::audio::playerThreadLoop(Player* player) {
bool Player::Exited() { bool Player::Exited() {
std::unique_lock<std::mutex> lock(this->queueMutex); std::unique_lock<std::mutex> lock(this->queueMutex);
return (this->state == Player::Quit); return (this->internalState == Player::Quit);
} }
static inline void initHammingWindow() { static inline void initHammingWindow() {
@ -587,6 +593,7 @@ void Player::OnBufferProcessed(IBuffer *buffer) {
} }
if (!this->notifiedStarted) { if (!this->notifiedStarted) {
this->streamState = StreamPlaying;
this->notifiedStarted = true; this->notifiedStarted = true;
started = true; started = true;
} }

View File

@ -67,7 +67,7 @@ namespace musik { namespace core { namespace audio {
}; };
struct EventListener { struct EventListener {
virtual void OnPlayerPrepared(Player *player) { } virtual void OnPlayerBuffered(Player *player) { }
virtual void OnPlayerStarted(Player *player) { } virtual void OnPlayerStarted(Player *player) { }
virtual void OnPlayerAlmostEnded(Player *player) { } virtual void OnPlayerAlmostEnded(Player *player) { }
virtual void OnPlayerFinished(Player *player) { } virtual void OnPlayerFinished(Player *player) { }
@ -102,6 +102,8 @@ namespace musik { namespace core { namespace audio {
std::string GetUrl() const { return this->url; } std::string GetUrl() const { return this->url; }
musik::core::sdk::StreamState GetStreamState() { return this->streamState; }
private: private:
friend void playerThreadLoop(Player* player); friend void playerThreadLoop(Player* player);
@ -135,7 +137,7 @@ namespace musik { namespace core { namespace audio {
Idle = 0, Idle = 0,
Playing = 1, Playing = 1,
Quit = 2 Quit = 2
} States; } InternalState;
bool Exited(); bool Exited();
int State(); int State();
@ -162,7 +164,8 @@ namespace musik { namespace core { namespace audio {
double nextMixPoint; double nextMixPoint;
std::atomic<double> currentPosition; std::atomic<double> currentPosition;
std::atomic<double> seekToPosition; std::atomic<double> seekToPosition;
int state; std::atomic<musik::core::sdk::StreamState> streamState;
std::atomic<int> internalState;
bool notifiedStarted; bool notifiedStarted;
float* spectrum; float* spectrum;
DestroyMode destroyMode; DestroyMode destroyMode;

View File

@ -1019,7 +1019,7 @@ class mcsdk_audio_player_callback_proxy: public Player::EventListener {
public: public:
std::set<mcsdk_audio_player_callbacks*> callbacks; std::set<mcsdk_audio_player_callbacks*> callbacks;
mcsdk_player_context_internal* context; mcsdk_player_context_internal* context;
virtual void OnPlayerPrepared(Player *player) { virtual void OnPlayerBuffered(Player *player) {
std::unique_lock<std::mutex> lock(this->context->event_mutex); std::unique_lock<std::mutex> lock(this->context->event_mutex);
for (auto c : callbacks) { for (auto c : callbacks) {
if (c->on_prepared) { if (c->on_prepared) {

View File

@ -47,9 +47,9 @@ namespace musik {
PlaybackPlaying = 4, PlaybackPlaying = 4,
}; };
enum StreamEventType { enum StreamState {
StreamScheduled = 1, StreamBuffering = 1,
StreamPrepared = 2, StreamBuffered = 2,
StreamPlaying = 3, StreamPlaying = 3,
StreamAlmostDone = 4, StreamAlmostDone = 4,
StreamFinished = 5, StreamFinished = 5,

View File

@ -51,8 +51,8 @@ using namespace musik::core::sdk;
using namespace musik::cube; using namespace musik::cube;
using namespace cursespp; using namespace cursespp;
static inline size_t longestStringLength(const std::vector<std::string>&& keys) { static inline int longestStringLength(const std::vector<std::string>&& keys) {
size_t max = 0; int max = 0;
for (auto& str: keys) { for (auto& str: keys) {
size_t len = u8cols(_TSTR(str)); size_t len = u8cols(_TSTR(str));
max = len > max ? len : max; max = len > max ? len : max;
@ -71,7 +71,7 @@ RemoteLibrarySettingsLayout::~RemoteLibrarySettingsLayout() {
} }
void RemoteLibrarySettingsLayout::OnLayout() { void RemoteLibrarySettingsLayout::OnLayout() {
size_t labelWidth = longestStringLength({ const int labelWidth = longestStringLength({
"settings_library_type_remote_hostname", "settings_library_type_remote_hostname",
"settings_library_type_remote_wss_port", "settings_library_type_remote_wss_port",
"settings_library_type_remote_http_port", "settings_library_type_remote_http_port",

View File

@ -56,17 +56,18 @@ namespace musik {
static const int RequeryTrackList = First + 5; static const int RequeryTrackList = First + 5;
static const int RequeryCategoryList = First + 6; static const int RequeryCategoryList = First + 6;
static const int RefreshTransport = First + 7; static const int RefreshTransport = First + 7;
static const int RefreshLogs = First + 8; static const int TransportBuffering = First + 8;
static const int UpdateCheckFinished = First + 9; static const int RefreshLogs = First + 9;
static const int JumpToConsole = First + 10; static const int UpdateCheckFinished = First + 10;
static const int JumpToLibrary = First + 11; static const int JumpToConsole = First + 11;
static const int JumpToSettings = First + 12; static const int JumpToLibrary = First + 12;
static const int JumpToLyrics = First + 13; static const int JumpToSettings = First + 13;
static const int JumpToHotkeys = First + 14; static const int JumpToLyrics = First + 14;
static const int JumpToPlayQueue = First + 15; static const int JumpToHotkeys = First + 15;
static const int SetLastFmState = First + 16; static const int JumpToPlayQueue = First + 16;
static const int UpdateEqualizer = First + 17; static const int SetLastFmState = First + 17;
static const int DebugLog = First + 18; static const int UpdateEqualizer = First + 18;
static const int DebugLog = First + 19;
} }
} }
} }

View File

@ -133,6 +133,8 @@ void tokenize(const std::string& format, TokenList& tokens) {
/* a cache of localized, pre-formatted strings we use every second. */ /* a cache of localized, pre-formatted strings we use every second. */
static struct StringCache { static struct StringCache {
std::string PLAYING_FORMAT; std::string PLAYING_FORMAT;
std::string PLAYING;
std::string BUFFERING;
std::string STOPPED; std::string STOPPED;
std::string EMPTY_SONG; std::string EMPTY_SONG;
std::string EMPTY_ALBUM; std::string EMPTY_ALBUM;
@ -146,6 +148,8 @@ static struct StringCache {
void Initialize() { void Initialize() {
PLAYING_FORMAT = _TSTR("transport_playing_format"); PLAYING_FORMAT = _TSTR("transport_playing_format");
PLAYING = _TSTR("transport_playing_format_playing");
BUFFERING = _TSTR("transport_playing_format_buffering");
STOPPED = _TSTR("transport_stopped"); STOPPED = _TSTR("transport_stopped");
EMPTY_SONG = _TSTR("transport_empty_song"); EMPTY_SONG = _TSTR("transport_empty_song");
EMPTY_ALBUM = _TSTR("transport_empty_album"); EMPTY_ALBUM = _TSTR("transport_empty_album");
@ -248,6 +252,7 @@ utf8 characters and ellipsizing */
static size_t writePlayingFormat( static size_t writePlayingFormat(
WINDOW *w, WINDOW *w,
TransportDisplayCache& displayCache, TransportDisplayCache& displayCache,
bool buffering,
size_t width) size_t width)
{ {
TokenList tokens; TokenList tokens;
@ -255,6 +260,7 @@ static size_t writePlayingFormat(
Color dim = Color::TextDisabled; Color dim = Color::TextDisabled;
Color gb = Color::TextActive; Color gb = Color::TextActive;
Color warn = Color::TextWarning;
size_t remaining = width; size_t remaining = width;
auto it = tokens.begin(); auto it = tokens.begin();
@ -263,19 +269,30 @@ static size_t writePlayingFormat(
Color attr = dim; Color attr = dim;
std::string value; std::string value;
size_t cols; size_t cols = 0;
if (token->type == Token::Placeholder) { if (token->type == Token::Placeholder) {
attr = gb; if (token->value == "$state") {
if (buffering) {
attr = warn;
value = Strings.BUFFERING;
}
else {
value = Strings.PLAYING;
}
}
if (token->value == "$title") { if (token->value == "$title") {
attr = gb;
value = displayCache.title; value = displayCache.title;
cols = displayCache.titleCols; cols = displayCache.titleCols;
} }
else if (token->value == "$album") { else if (token->value == "$album") {
attr = gb;
value = displayCache.album; value = displayCache.album;
cols = displayCache.albumCols; cols = displayCache.albumCols;
} }
else if (token->value == "$artist") { else if (token->value == "$artist") {
attr = gb;
value = displayCache.artist; value = displayCache.artist;
cols = displayCache.artistCols; cols = displayCache.artistCols;
} }
@ -349,6 +366,7 @@ TransportWindow::TransportWindow(
this->playback.Shuffled.connect(this, &TransportWindow::OnPlaybackShuffled); this->playback.Shuffled.connect(this, &TransportWindow::OnPlaybackShuffled);
this->playback.VolumeChanged.connect(this, &TransportWindow::OnTransportVolumeChanged); this->playback.VolumeChanged.connect(this, &TransportWindow::OnTransportVolumeChanged);
this->playback.TimeChanged.connect(this, &TransportWindow::OnTransportTimeChanged); this->playback.TimeChanged.connect(this, &TransportWindow::OnTransportTimeChanged);
this->playback.StreamStateChanged.connect(this, &TransportWindow::OnPlaybackStreamStateChanged);
this->paused = false; this->paused = false;
this->lastTime = DEFAULT_TIME; this->lastTime = DEFAULT_TIME;
this->shufflePos.y = 0; this->shufflePos.y = 0;
@ -478,7 +496,7 @@ void TransportWindow::OnFocusChanged(bool focused) {
} }
void TransportWindow::ProcessMessage(IMessage &message) { void TransportWindow::ProcessMessage(IMessage &message) {
int type = message.Type(); const int type = message.Type();
if (type == message::RefreshTransport) { if (type == message::RefreshTransport) {
this->Update((TimeMode) message.UserData1()); this->Update((TimeMode) message.UserData1());
@ -487,6 +505,10 @@ void TransportWindow::ProcessMessage(IMessage &message) {
DEBOUNCE_REFRESH(TimeSmooth, REFRESH_INTERVAL_MS) DEBOUNCE_REFRESH(TimeSmooth, REFRESH_INTERVAL_MS)
} }
} }
else if (type == message::TransportBuffering) {
this->buffering = true;
this->Update();
}
} }
void TransportWindow::OnPlaybackServiceTrackChanged(size_t index, TrackPtr track) { void TransportWindow::OnPlaybackServiceTrackChanged(size_t index, TrackPtr track) {
@ -496,6 +518,17 @@ void TransportWindow::OnPlaybackServiceTrackChanged(size_t index, TrackPtr track
DEBOUNCE_REFRESH(TimeSync, 0); DEBOUNCE_REFRESH(TimeSync, 0);
} }
void TransportWindow::OnPlaybackStreamStateChanged(StreamState state) {
if (state == StreamBuffering) {
this->Debounce(message::TransportBuffering, 0, 0, 250);
}
else {
this->Remove(message::TransportBuffering);
this->buffering = false;
this->Update();
}
}
void TransportWindow::OnPlaybackModeChanged() { void TransportWindow::OnPlaybackModeChanged() {
DEBOUNCE_REFRESH(TimeSync, 0); DEBOUNCE_REFRESH(TimeSync, 0);
} }
@ -587,7 +620,7 @@ void TransportWindow::Update(TimeMode timeMode) {
} }
else { else {
displayCache->Update(transport, this->currentTrack); displayCache->Update(transport, this->currentTrack);
writePlayingFormat(c, *this->displayCache, cx - shuffleWidth); writePlayingFormat(c, *this->displayCache, this->buffering, cx - shuffleWidth);
} }
/* draw the "shuffle" label */ /* draw the "shuffle" label */

View File

@ -116,6 +116,7 @@ namespace musik {
void OnPlaybackServiceTrackChanged(size_t index, musik::core::TrackPtr track); void OnPlaybackServiceTrackChanged(size_t index, musik::core::TrackPtr track);
void OnPlaybackModeChanged(); void OnPlaybackModeChanged();
void OnPlaybackStreamStateChanged(musik::core::sdk::StreamState);
void OnTransportVolumeChanged(); void OnTransportVolumeChanged();
void OnTransportTimeChanged(double time); void OnTransportTimeChanged(double time);
void OnPlaybackShuffled(bool shuffled); void OnPlaybackShuffled(bool shuffled);
@ -131,6 +132,7 @@ namespace musik {
musik::core::TrackPtr currentTrack; musik::core::TrackPtr currentTrack;
FocusTarget focus, lastFocus; FocusTarget focus, lastFocus;
std::unique_ptr<TransportDisplayCache> displayCache; std::unique_ptr<TransportDisplayCache> displayCache;
bool buffering{ false };
double lastTime; double lastTime;
}; };
} }

View File

@ -193,7 +193,9 @@
"shortcuts_tracks": "tracks", "shortcuts_tracks": "tracks",
"shortcuts_play_queue": "play queue", "shortcuts_play_queue": "play queue",
"transport_playing_format": "playing $title by $artist from $album", "transport_playing_format": "$state $title by $artist from $album",
"transport_playing_format_playing": "playing",
"transport_playing_format_buffering": "buffering",
"transport_stopped": "playback is stopped", "transport_stopped": "playback is stopped",
"transport_empty_song": "[song]", "transport_empty_song": "[song]",
"transport_empty_album": "[album]", "transport_empty_album": "[album]",