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

View File

@ -83,6 +83,7 @@ namespace musik { namespace core { namespace audio {
virtual void ReloadOutput();
virtual musik::core::sdk::PlaybackState GetPlaybackState();
virtual musik::core::sdk::StreamState GetStreamState();
private:
using Lock = std::unique_lock<std::recursive_mutex>;
@ -129,13 +130,14 @@ namespace musik { namespace core { namespace audio {
void OnCrossfaderEmptied();
virtual void OnPlayerPrepared(Player* player);
virtual void OnPlayerBuffered(Player* player);
virtual void OnPlayerStarted(Player* player);
virtual void OnPlayerFinished(Player* player);
virtual void OnPlayerError(Player* player);
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;
Crossfader crossfader;
PlayerContext active;

View File

@ -57,11 +57,12 @@ static std::string TAG = "GaplessTransport";
instance->activePlayer->Detach(instance); \
instance->activePlayer->Destroy(); \
instance->activePlayer = nullptr; \
instance->activePlayerState = StreamError; \
}
GaplessTransport::GaplessTransport()
: volume(1.0)
, state(PlaybackStopped)
, playbackState(PlaybackStopped)
, activePlayer(nullptr)
, nextPlayer(nullptr)
, nextCanStart(false)
@ -77,7 +78,12 @@ GaplessTransport::~GaplessTransport() {
PlaybackState GaplessTransport::GetPlaybackState() {
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) {
@ -121,6 +127,10 @@ void GaplessTransport::StartWithPlayer(Player* newPlayer, StartMode mode) {
this->nextPlayer = nullptr;
this->activePlayer = newPlayer;
if (newPlayer) {
this->RaiseStreamEvent(newPlayer->GetStreamState(), newPlayer);
}
/* first argument suppresses the "Stop" event from getting triggered,
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
@ -135,8 +145,6 @@ void GaplessTransport::StartWithPlayer(Player* newPlayer, StartMode mode) {
newPlayer->Play();
}
}
this->RaiseStreamEvent(StreamScheduled, newPlayer);
}
}
@ -237,7 +245,7 @@ void GaplessTransport::SetPosition(double seconds) {
LockT lock(this->stateMutex);
if (this->activePlayer) {
if (this->state != PlaybackPlaying) {
if (this->playbackState != PlaybackPlaying) {
this->SetPlaybackState(PlaybackPlaying);
}
this->activePlayer->SetPosition(seconds);
@ -289,9 +297,9 @@ void GaplessTransport::SetNextCanStart(bool nextCanStart) {
this->nextCanStart = nextCanStart;
}
void GaplessTransport::OnPlayerPrepared(Player* player) {
void GaplessTransport::OnPlayerBuffered(Player* player) {
if (player == this->activePlayer) {
this->RaiseStreamEvent(StreamPrepared, player);
this->RaiseStreamEvent(StreamBuffered, player);
this->SetPlaybackState(PlaybackPrepared);
}
}
@ -372,8 +380,8 @@ void GaplessTransport::SetPlaybackState(int state) {
{
LockT lock(this->stateMutex);
changed = (this->state != state);
this->state = (PlaybackState) state;
changed = (this->playbackState != state);
this->playbackState = (PlaybackState) state;
}
if (changed) {
@ -382,5 +390,11 @@ void GaplessTransport::SetPlaybackState(int state) {
}
void GaplessTransport::RaiseStreamEvent(int type, Player* player) {
{
LockT lock(this->stateMutex);
if (player == activePlayer) {
this->activePlayerState = (StreamState) type;
}
}
this->StreamEvent(type, player->GetUrl());
}

View File

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

View File

@ -79,6 +79,7 @@ namespace musik { namespace core { namespace audio {
virtual void ReloadOutput() = 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();
}
StreamState MasterTransport::GetStreamState() {
return this->transport->GetStreamState();
}
void MasterTransport::OnStreamEvent(int type, std::string url) {
this->StreamEvent(type, url);
}

View File

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

View File

@ -305,15 +305,13 @@ void PlaybackService::ProcessMessage(IMessage &message) {
}
else if (type == MESSAGE_STREAM_EVENT) {
StreamMessage* streamMessage = static_cast<StreamMessage*>(&message);
int64_t eventType = streamMessage->GetEventType();
StreamState eventType = (StreamState) streamMessage->GetEventType();
if (eventType == StreamPlaying) {
TrackPtr track;
{
std::unique_lock<std::recursive_mutex> lock(this->playlistMutex);
if (this->nextIndex != NO_POSITION) {
/* 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
@ -343,6 +341,16 @@ void PlaybackService::ProcessMessage(IMessage &message) {
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) {
int64_t eventType = message.UserData1();
@ -365,7 +373,7 @@ void PlaybackService::ProcessMessage(IMessage &message) {
(*it)->OnPlaybackStateChanged((PlaybackState) eventType);
}
this->PlaybackEvent((PlaybackState) eventType);
this->PlaybackStateChanged((PlaybackState) eventType);
}
else if (type == MESSAGE_PREPARE_NEXT_TRACK) {
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
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::signal1<double> TimeChanged;

View File

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

View File

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

View File

@ -1019,7 +1019,7 @@ class mcsdk_audio_player_callback_proxy: public Player::EventListener {
public:
std::set<mcsdk_audio_player_callbacks*> callbacks;
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);
for (auto c : callbacks) {
if (c->on_prepared) {

View File

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

View File

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

View File

@ -56,17 +56,18 @@ namespace musik {
static const int RequeryTrackList = First + 5;
static const int RequeryCategoryList = First + 6;
static const int RefreshTransport = First + 7;
static const int RefreshLogs = First + 8;
static const int UpdateCheckFinished = First + 9;
static const int JumpToConsole = First + 10;
static const int JumpToLibrary = First + 11;
static const int JumpToSettings = First + 12;
static const int JumpToLyrics = First + 13;
static const int JumpToHotkeys = First + 14;
static const int JumpToPlayQueue = First + 15;
static const int SetLastFmState = First + 16;
static const int UpdateEqualizer = First + 17;
static const int DebugLog = First + 18;
static const int TransportBuffering = First + 8;
static const int RefreshLogs = First + 9;
static const int UpdateCheckFinished = First + 10;
static const int JumpToConsole = First + 11;
static const int JumpToLibrary = First + 12;
static const int JumpToSettings = First + 13;
static const int JumpToLyrics = First + 14;
static const int JumpToHotkeys = First + 15;
static const int JumpToPlayQueue = First + 16;
static const int SetLastFmState = First + 17;
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. */
static struct StringCache {
std::string PLAYING_FORMAT;
std::string PLAYING;
std::string BUFFERING;
std::string STOPPED;
std::string EMPTY_SONG;
std::string EMPTY_ALBUM;
@ -146,6 +148,8 @@ static struct StringCache {
void Initialize() {
PLAYING_FORMAT = _TSTR("transport_playing_format");
PLAYING = _TSTR("transport_playing_format_playing");
BUFFERING = _TSTR("transport_playing_format_buffering");
STOPPED = _TSTR("transport_stopped");
EMPTY_SONG = _TSTR("transport_empty_song");
EMPTY_ALBUM = _TSTR("transport_empty_album");
@ -248,6 +252,7 @@ utf8 characters and ellipsizing */
static size_t writePlayingFormat(
WINDOW *w,
TransportDisplayCache& displayCache,
bool buffering,
size_t width)
{
TokenList tokens;
@ -255,6 +260,7 @@ static size_t writePlayingFormat(
Color dim = Color::TextDisabled;
Color gb = Color::TextActive;
Color warn = Color::TextWarning;
size_t remaining = width;
auto it = tokens.begin();
@ -263,19 +269,30 @@ static size_t writePlayingFormat(
Color attr = dim;
std::string value;
size_t cols;
size_t cols = 0;
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") {
attr = gb;
value = displayCache.title;
cols = displayCache.titleCols;
}
else if (token->value == "$album") {
attr = gb;
value = displayCache.album;
cols = displayCache.albumCols;
}
else if (token->value == "$artist") {
attr = gb;
value = displayCache.artist;
cols = displayCache.artistCols;
}
@ -349,6 +366,7 @@ TransportWindow::TransportWindow(
this->playback.Shuffled.connect(this, &TransportWindow::OnPlaybackShuffled);
this->playback.VolumeChanged.connect(this, &TransportWindow::OnTransportVolumeChanged);
this->playback.TimeChanged.connect(this, &TransportWindow::OnTransportTimeChanged);
this->playback.StreamStateChanged.connect(this, &TransportWindow::OnPlaybackStreamStateChanged);
this->paused = false;
this->lastTime = DEFAULT_TIME;
this->shufflePos.y = 0;
@ -478,7 +496,7 @@ void TransportWindow::OnFocusChanged(bool focused) {
}
void TransportWindow::ProcessMessage(IMessage &message) {
int type = message.Type();
const int type = message.Type();
if (type == message::RefreshTransport) {
this->Update((TimeMode) message.UserData1());
@ -487,6 +505,10 @@ void TransportWindow::ProcessMessage(IMessage &message) {
DEBOUNCE_REFRESH(TimeSmooth, REFRESH_INTERVAL_MS)
}
}
else if (type == message::TransportBuffering) {
this->buffering = true;
this->Update();
}
}
void TransportWindow::OnPlaybackServiceTrackChanged(size_t index, TrackPtr track) {
@ -496,6 +518,17 @@ void TransportWindow::OnPlaybackServiceTrackChanged(size_t index, TrackPtr track
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() {
DEBOUNCE_REFRESH(TimeSync, 0);
}
@ -587,7 +620,7 @@ void TransportWindow::Update(TimeMode timeMode) {
}
else {
displayCache->Update(transport, this->currentTrack);
writePlayingFormat(c, *this->displayCache, cx - shuffleWidth);
writePlayingFormat(c, *this->displayCache, this->buffering, cx - shuffleWidth);
}
/* draw the "shuffle" label */

View File

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

View File

@ -193,7 +193,9 @@
"shortcuts_tracks": "tracks",
"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_empty_song": "[song]",
"transport_empty_album": "[album]",