- Fixed a memory leak in Mp3Decoder

- Fixed WaveOut::Stop() to only reset the output device, not close it,
  to prevent cracking while seeking and also get us a step closer to
  gapless playback.
- Fixed GlobalHotkey handling so key presses aren't propagated to the
  focused control after they're handled
- Removed old, unused state from SimpleScrollAdapter
- Fixed crash on exit when using the "X" button by registering a custom
  close handler with PDCurses.
This commit is contained in:
casey 2016-05-24 20:31:43 -07:00
parent 14aa38518a
commit 05aca20bbd
13 changed files with 61 additions and 85 deletions

View File

@ -68,6 +68,7 @@ Mp3Decoder::Mp3Decoder()
} }
Mp3Decoder::~Mp3Decoder() { Mp3Decoder::~Mp3Decoder() {
delete decoder;
} }
unsigned long Mp3Decoder::GetID3HeaderLength(unsigned char * buffer) { unsigned long Mp3Decoder::GetID3HeaderLength(unsigned char * buffer) {

View File

@ -53,10 +53,30 @@ WaveOut::WaveOut()
} }
WaveOut::~WaveOut() { WaveOut::~WaveOut() {
this->Stop();
} }
void WaveOut::Destroy() { void WaveOut::Destroy() {
{
boost::recursive_mutex::scoped_lock lock(this->outputDeviceMutex);
/* reset playback immediately. this will invalidate all pending
buffers */
if (this->waveHandle != NULL) {
waveOutReset(this->waveHandle);
}
/* stop the thread so nothing else is processed */
this->StopWaveOutThread();
/* close it down after the threadproc has finished */
if (this->waveHandle != NULL) {
waveOutClose(this->waveHandle);
this->waveHandle = NULL;
}
}
this->ClearBufferQueue();
delete this; delete this;
} }
@ -79,28 +99,25 @@ void WaveOut::SetVolume(double volume) {
} }
void WaveOut::Stop() { void WaveOut::Stop() {
boost::recursive_mutex::scoped_lock lock(this->outputDeviceMutex); {
boost::recursive_mutex::scoped_lock lock(this->outputDeviceMutex);
/* reset waveout first. if we don't do this, it seems like it'll still if (this->waveHandle != NULL) {
try to send events to the thread, and fail with a runtime exception. */ waveOutReset(this->waveHandle);
if (this->waveHandle != NULL) { }
waveOutReset(this->waveHandle);
} }
/* stop the thread so nothing else is processed */ this->ClearBufferQueue();
this->StopWaveOutThread(); }
/* dealloc the handle, we'll create a new one later if we need to... */ void WaveOut::ClearBufferQueue() {
if (this->waveHandle != NULL) { boost::recursive_mutex::scoped_lock lock(this->bufferQueueMutex);
waveOutClose(this->waveHandle);
this->waveHandle = NULL;
}
/* notify and free any pending buffers, the Player in the core /* notify and free any pending buffers, the Player in the core
will be waiting for all pending buffers to be processed. */ will be waiting for all pending buffers to be processed. */
if (this->queuedBuffers.size() > 0) { if (this->queuedBuffers.size() > 0) {
BufferList::iterator it = this->queuedBuffers.begin(); BufferList::iterator it = this->queuedBuffers.begin();
for (; it != this->queuedBuffers.end(); ++it) { for (; it != this->queuedBuffers.end(); it++) {
notifyBufferProcessed((*it).get()); notifyBufferProcessed((*it).get());
} }
@ -110,14 +127,13 @@ void WaveOut::Stop() {
void WaveOut::OnBufferWrittenToOutput(WaveOutBuffer *buffer) { void WaveOut::OnBufferWrittenToOutput(WaveOutBuffer *buffer) {
boost::recursive_mutex::scoped_lock lock(this->bufferQueueMutex); boost::recursive_mutex::scoped_lock lock(this->bufferQueueMutex);
notifyBufferProcessed(buffer);
/* removed the buffer. it should be at the front of the queue. */ /* removed the buffer. it should be at the front of the queue. */
BufferList::iterator it = this->queuedBuffers.begin(); BufferList::iterator it = this->queuedBuffers.begin();
for( ; it != this->queuedBuffers.end(); ++it) { for( ; it != this->queuedBuffers.end(); it++) {
if (it->get() == buffer) { if (it->get() == buffer) {
it = this->queuedBuffers.erase(it); notifyBufferProcessed(buffer);
this->queuedBuffers.erase(it);
return; return;
} }
} }

View File

@ -65,6 +65,7 @@ class WaveOut : public IOutput {
void SetFormat(IBuffer *buffer); void SetFormat(IBuffer *buffer);
void StartWaveOutThread(); void StartWaveOutThread();
void StopWaveOutThread(); void StopWaveOutThread();
void ClearBufferQueue();
protected: protected:
friend class WaveOutBuffer; friend class WaveOutBuffer;

View File

@ -82,7 +82,6 @@ void Transport::Start(const std::string& url) {
musik::debug::info(TAG, "we were asked to start the track at " + url); musik::debug::info(TAG, "we were asked to start the track at " + url);
Player* newPlayer = new Player(url); Player* newPlayer = new Player(url);
newPlayer->SetVolume(this->volume);
musik::debug::info(TAG, "Player created successfully"); musik::debug::info(TAG, "Player created successfully");
this->StartWithPlayer(newPlayer); this->StartWithPlayer(newPlayer);
@ -108,6 +107,7 @@ void Transport::StartWithPlayer(Player* newPlayer) {
musik::debug::info(TAG, "play()"); musik::debug::info(TAG, "play()");
this->active.push_front(newPlayer); this->active.push_front(newPlayer);
newPlayer->SetVolume(this->volume);
newPlayer->Play(); newPlayer->Play();
} }
@ -193,7 +193,8 @@ void Transport::SetPosition(double seconds) {
boost::recursive_mutex::scoped_lock lock(this->stateMutex); boost::recursive_mutex::scoped_lock lock(this->stateMutex);
if (!this->active.empty()) { if (!this->active.empty()) {
return this->active.front()->SetPosition(seconds); this->active.front()->SetPosition(seconds);
this->TimeChanged(seconds);
} }
} }

View File

@ -47,6 +47,7 @@ namespace musik { namespace core { namespace audio {
sigslot::signal2<int, std::string> StreamEvent; sigslot::signal2<int, std::string> StreamEvent;
sigslot::signal1<int> PlaybackEvent; sigslot::signal1<int> PlaybackEvent;
sigslot::signal0<> VolumeChanged; sigslot::signal0<> VolumeChanged;
sigslot::signal1<double> TimeChanged;
typedef enum { typedef enum {
PlaybackStopped, PlaybackStopped,

View File

@ -39,58 +39,13 @@ namespace musik { namespace core { namespace audio {
class IBuffer { class IBuffer {
public: public:
//////////////////////////////////////////
///\brief
///Get the samplerate of the buffer
//////////////////////////////////////////
virtual long SampleRate() const = 0; virtual long SampleRate() const = 0;
//////////////////////////////////////////
///\brief
///Set the buffers samplerate
//////////////////////////////////////////
virtual void SetSampleRate(long sampleRate) = 0; virtual void SetSampleRate(long sampleRate) = 0;
//////////////////////////////////////////
///\brief
///Get the number of channels of the buffer
//////////////////////////////////////////
virtual int Channels() const = 0; virtual int Channels() const = 0;
//////////////////////////////////////////
///\brief
///Set the number of channels of the buffer
//////////////////////////////////////////
virtual void SetChannels(int channels) = 0; virtual void SetChannels(int channels) = 0;
//////////////////////////////////////////
///\brief
///Get the pointer to the real buffer.
///
///The pointer may change when you set any of the buffers
///properties like samplerate, samples and channels
//////////////////////////////////////////
virtual float* BufferPointer() const = 0; virtual float* BufferPointer() const = 0;
//////////////////////////////////////////
///\brief
///Get the number of samples in the buffer
///
///To clairify, one sample = one sample for each channel
///and that means that one sample = sizeof(float)*channels bytes big
//////////////////////////////////////////
virtual long Samples() const = 0; virtual long Samples() const = 0;
//////////////////////////////////////////
///\brief
///Set the number of samples in the buffer
//////////////////////////////////////////
virtual void SetSamples(long samples) = 0; virtual void SetSamples(long samples) = 0;
//////////////////////////////////////////
///\brief
///How many bytes does this object take
//////////////////////////////////////////
virtual long Bytes() const = 0; virtual long Bytes() const = 0;
}; };

View File

@ -38,19 +38,12 @@
namespace musik { namespace core { namespace audio { namespace musik { namespace core { namespace audio {
//////////////////////////////////////////
///\brief
///Interface for the audio::Player to make IOuput plugins be able to make callbacks
//////////////////////////////////////////
class IBufferProvider { class IBufferProvider {
public: public:
virtual ~IBufferProvider() = 0 { } virtual ~IBufferProvider() = 0 { }
////////////////////////////////////////// /* the output calls this interface to let the provider know
///\brief it's done with the Buffer, so it can be recycled or released */
///Release used by the output to notify the player a buffer has finished
///processing.
//////////////////////////////////////////
virtual void OnBufferProcessed(IBuffer *buffer) = 0; virtual void OnBufferProcessed(IBuffer *buffer) = 0;
}; };

View File

@ -163,6 +163,7 @@ int main(int argc, char* argv[])
#ifdef __PDCURSES__ #ifdef __PDCURSES__
PDC_set_title("musikbox ♫"); PDC_set_title("musikbox ♫");
PDC_set_function_key(FUNCTION_KEY_SHUT_DOWN, 4);
#endif #endif
{ {

View File

@ -24,6 +24,7 @@ bool GlobalHotkeys::Handle(int64 ch) {
else if (state == Transport::PlaybackPlaying) { else if (state == Transport::PlaybackPlaying) {
this->transport.Pause(); this->transport.Pause();
} }
return true;
} }
if (kn == "ALT_I") { if (kn == "ALT_I") {
this->transport.SetVolume(this->transport.Volume() + 0.05); /* 5% */ this->transport.SetVolume(this->transport.Volume() + 0.05); /* 5% */
@ -35,20 +36,25 @@ bool GlobalHotkeys::Handle(int64 ch) {
} }
else if (kn == "ALT_J") { else if (kn == "ALT_J") {
this->playback.Previous(); this->playback.Previous();
return true;
} }
else if (kn == "ALT_L") { else if (kn == "ALT_L") {
this->playback.Next(); this->playback.Next();
return true;
} }
else if (kn == "ALT_U") { else if (kn == "ALT_U") {
double time = this->transport.Position(); double time = this->transport.Position();
this->transport.SetPosition(time - 10.0f); this->transport.SetPosition(time - 10.0f);
return true;
} }
else if (kn == "ALT_O") { else if (kn == "ALT_O") {
double time = this->transport.Position(); double time = this->transport.Position();
this->transport.SetPosition(time + 10.0f); this->transport.SetPosition(time + 10.0f);
return true;
} }
else if (kn == "^R") { else if (kn == "^R") {
library->Indexer()->Synchronize(true); library->Indexer()->Synchronize(true);
return true;
} }
return false; return false;

View File

@ -32,7 +32,7 @@ using namespace boost::chrono;
#define REFRESH_TRANSPORT_READOUT 1001 #define REFRESH_TRANSPORT_READOUT 1001
#define REFRESH_INTERVAL_MS 1000 #define REFRESH_INTERVAL_MS 1000
#define SCHEDULE_REFRESH(x) \ #define DEBOUNCE_REFRESH(x) \
this->RemoveMessage(REFRESH_TRANSPORT_READOUT); \ this->RemoveMessage(REFRESH_TRANSPORT_READOUT); \
this->PostMessage(REFRESH_TRANSPORT_READOUT, 0, 0, x); this->PostMessage(REFRESH_TRANSPORT_READOUT, 0, 0, x);
@ -45,6 +45,7 @@ TransportWindow::TransportWindow(LibraryPtr library, Transport& transport)
this->transport = &transport; this->transport = &transport;
this->transport->StreamEvent.connect(this, &TransportWindow::OnTransportStreamEvent); this->transport->StreamEvent.connect(this, &TransportWindow::OnTransportStreamEvent);
this->transport->VolumeChanged.connect(this, &TransportWindow::OnTransportVolumeChanged); this->transport->VolumeChanged.connect(this, &TransportWindow::OnTransportVolumeChanged);
this->transport->TimeChanged.connect(this, &TransportWindow::OnTransportTimeChanged);
this->paused = false; this->paused = false;
} }
@ -61,7 +62,7 @@ void TransportWindow::ProcessMessage(IMessage &message) {
if (type == REFRESH_TRANSPORT_READOUT) { if (type == REFRESH_TRANSPORT_READOUT) {
this->Update(); this->Update();
SCHEDULE_REFRESH(REFRESH_INTERVAL_MS) DEBOUNCE_REFRESH(REFRESH_INTERVAL_MS)
} }
} }
@ -69,18 +70,22 @@ void TransportWindow::OnTransportStreamEvent(int eventType, std::string url) {
if (eventType == Transport::StreamPlaying) { if (eventType == Transport::StreamPlaying) {
this->trackQuery.reset(new SingleTrackQuery(url)); this->trackQuery.reset(new SingleTrackQuery(url));
this->library->Enqueue(this->trackQuery); this->library->Enqueue(this->trackQuery);
SCHEDULE_REFRESH(0) DEBOUNCE_REFRESH(0);
} }
} }
void TransportWindow::OnTransportVolumeChanged() { void TransportWindow::OnTransportVolumeChanged() {
SCHEDULE_REFRESH(0) DEBOUNCE_REFRESH(0)
}
void TransportWindow::OnTransportTimeChanged(double time) {
DEBOUNCE_REFRESH(0)
} }
void TransportWindow::OnQueryCompleted(QueryPtr query) { void TransportWindow::OnQueryCompleted(QueryPtr query) {
if (query == this->trackQuery && query->GetStatus() == QueryBase::Finished) { if (query == this->trackQuery && query->GetStatus() == QueryBase::Finished) {
this->currentTrack = this->trackQuery->GetResult(); this->currentTrack = this->trackQuery->GetResult();
SCHEDULE_REFRESH(0) DEBOUNCE_REFRESH(0)
} }
} }

View File

@ -25,6 +25,7 @@ class TransportWindow : public Window, public sigslot::has_slots<> {
private: private:
void OnTransportStreamEvent(int eventType, std::string url); void OnTransportStreamEvent(int eventType, std::string url);
void OnTransportVolumeChanged(); void OnTransportVolumeChanged();
void OnTransportTimeChanged(double time);
void OnQueryCompleted(QueryPtr query); void OnQueryCompleted(QueryPtr query);
bool paused; bool paused;

View File

@ -11,10 +11,6 @@
typedef IScrollAdapter::EntryPtr EntryPtr; typedef IScrollAdapter::EntryPtr EntryPtr;
SimpleScrollAdapter::SimpleScrollAdapter() { SimpleScrollAdapter::SimpleScrollAdapter() {
/* the adapters can have a maximum size. as we remove elements from
the back, we don't want to re-index everything. instead, we'll use
this offset for future calculations when searching for items. */
this->removedOffset = 0;
this->maxEntries = MAX_ENTRY_COUNT; this->maxEntries = MAX_ENTRY_COUNT;
} }

View File

@ -20,6 +20,5 @@ class SimpleScrollAdapter : public ScrollAdapterBase {
typedef EntryList::iterator Iterator; typedef EntryList::iterator Iterator;
EntryList entries; EntryList entries;
size_t removedOffset;
size_t maxEntries; size_t maxEntries;
}; };