mirror of
https://github.com/clangen/musikcube.git
synced 2025-03-12 07:13:23 +00:00
- 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:
parent
14aa38518a
commit
05aca20bbd
@ -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) {
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -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;
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user