mirror of
https://github.com/clangen/musikcube.git
synced 2025-02-06 03:39:50 +00:00
Gapless playback seems to be working with waveout! Not perfect for all
MP3 files, but that's to be expected. TODO: CoreAudio changes to allow gapless playback.
This commit is contained in:
parent
6ec0a8042b
commit
a8f8425f91
@ -183,10 +183,10 @@ bool Mpg123Decoder::Open(IDataStream *fileStream){
|
||||
this->fileStream = fileStream;
|
||||
|
||||
if (mpg123_open_feed(this->decoder) == MPG123_OK) {
|
||||
mpg123_param(
|
||||
int result = mpg123_param(
|
||||
this->decoder,
|
||||
MPG123_ADD_FLAGS,
|
||||
MPG123_FUZZY | MPG123_SEEKBUFFER,
|
||||
MPG123_FUZZY | MPG123_SEEKBUFFER | MPG123_GAPLESS | MPG123_QUIET,
|
||||
0);
|
||||
|
||||
mpg123_set_filesize(this->decoder, this->fileStream->Length());
|
||||
|
@ -1,34 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include <core/sdk/IDecoder.h>
|
||||
#include <core/sdk/IDataStream.h>
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <core/sdk/IDecoder.h>
|
||||
#include <core/sdk/IDataStream.h>
|
||||
|
||||
#ifdef _MSC_VER /* ehh */
|
||||
typedef long ssize_t;
|
||||
#endif
|
||||
|
||||
#include <mpg123.h>
|
||||
|
||||
class Mpg123Decoder : public musik::core::audio::IDecoder {
|
||||
public:
|
||||
Mpg123Decoder();
|
||||
virtual ~Mpg123Decoder();
|
||||
|
||||
virtual bool Open(musik::core::io::IDataStream *dataStream);
|
||||
virtual double SetPosition(double seconds);
|
||||
virtual bool GetBuffer(musik::core::audio::IBuffer *buffer);
|
||||
virtual void Destroy();
|
||||
|
||||
private:
|
||||
bool Feed();
|
||||
|
||||
private:
|
||||
musik::core::io::IDataStream *fileStream;
|
||||
mpg123_handle *decoder;
|
||||
|
||||
unsigned long cachedLength;
|
||||
long sampleRate;
|
||||
int channels;
|
||||
int sampleSizeBytes;
|
||||
int lastMpg123Status;
|
||||
};
|
||||
#endif
|
||||
|
||||
#include <mpg123.h>
|
||||
|
||||
class Mpg123Decoder : public musik::core::audio::IDecoder {
|
||||
public:
|
||||
Mpg123Decoder();
|
||||
virtual ~Mpg123Decoder();
|
||||
|
||||
virtual bool Open(musik::core::io::IDataStream *dataStream);
|
||||
virtual double SetPosition(double seconds);
|
||||
virtual bool GetBuffer(musik::core::audio::IBuffer *buffer);
|
||||
virtual void Destroy();
|
||||
|
||||
private:
|
||||
bool Feed();
|
||||
|
||||
private:
|
||||
musik::core::io::IDataStream *fileStream;
|
||||
mpg123_handle *decoder;
|
||||
|
||||
unsigned long cachedLength;
|
||||
long sampleRate;
|
||||
int channels;
|
||||
int sampleSizeBytes;
|
||||
int lastMpg123Status;
|
||||
};
|
||||
|
@ -104,10 +104,12 @@
|
||||
<ClCompile Include="stdafx.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="include\fmt123.h" />
|
||||
<ClInclude Include="include\mpg123.h" />
|
||||
<ClInclude Include="include\out123.h" />
|
||||
<ClInclude Include="Mpg123Decoder.h" />
|
||||
<ClInclude Include="Mpg123DecoderFactory.h" />
|
||||
<ClInclude Include="mpg123\src\config.h" />
|
||||
<ClInclude Include="mpg123\src\libmpg123\mpg123.h" />
|
||||
<ClInclude Include="resource.h" />
|
||||
<ClInclude Include="stdafx.h" />
|
||||
</ItemGroup>
|
||||
|
@ -28,7 +28,6 @@
|
||||
<Filter>plugin</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="mpg123\src\config.h" />
|
||||
<ClInclude Include="mpg123\src\libmpg123\mpg123.h" />
|
||||
<ClInclude Include="resource.h" />
|
||||
<ClInclude Include="Mpg123DecoderFactory.h">
|
||||
<Filter>plugin</Filter>
|
||||
@ -36,5 +35,8 @@
|
||||
<ClInclude Include="Mpg123Decoder.h">
|
||||
<Filter>plugin</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="include\mpg123.h" />
|
||||
<ClInclude Include="include\out123.h" />
|
||||
<ClInclude Include="include\fmt123.h" />
|
||||
</ItemGroup>
|
||||
</Project>
|
@ -34,7 +34,7 @@
|
||||
#include "WaveOut.h"
|
||||
|
||||
#define MAX_VOLUME 0xFFFF
|
||||
#define MAX_BUFFERS 8
|
||||
#define MAX_BUFFERS_PER_OUTPUT 8
|
||||
|
||||
static void notifyBufferProcessed(WaveOutBuffer *buffer) {
|
||||
/* let the provider know the output device is done with the buffer; the
|
||||
@ -49,6 +49,7 @@ WaveOut::WaveOut()
|
||||
, currentVolume(1.0)
|
||||
, threadId(0)
|
||||
, threadHandle(NULL)
|
||||
, playing(false)
|
||||
{
|
||||
}
|
||||
|
||||
@ -75,20 +76,25 @@ void WaveOut::Destroy() {
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
boost::recursive_mutex::scoped_lock lock1(this->bufferQueueMutex);
|
||||
this->ClearBufferQueue();
|
||||
}
|
||||
this->ClearBufferQueue();
|
||||
|
||||
delete this;
|
||||
}
|
||||
|
||||
void WaveOut::Pause() {
|
||||
boost::recursive_mutex::scoped_lock lock2(this->outputDeviceMutex);
|
||||
waveOutPause(this->waveHandle);
|
||||
this->playing = false;
|
||||
}
|
||||
|
||||
void WaveOut::Resume() {
|
||||
waveOutRestart(this->waveHandle);
|
||||
boost::recursive_mutex::scoped_lock lock2(this->outputDeviceMutex);
|
||||
|
||||
if (!this->playing) {
|
||||
waveOutRestart(this->waveHandle);
|
||||
}
|
||||
|
||||
this->playing = true;
|
||||
}
|
||||
|
||||
void WaveOut::SetVolume(double volume) {
|
||||
@ -102,51 +108,56 @@ void WaveOut::SetVolume(double volume) {
|
||||
}
|
||||
|
||||
void WaveOut::Stop() {
|
||||
boost::recursive_mutex::scoped_lock lock2(this->outputDeviceMutex);
|
||||
|
||||
{
|
||||
boost::recursive_mutex::scoped_lock lock2(this->outputDeviceMutex);
|
||||
|
||||
if (this->waveHandle != NULL) {
|
||||
waveOutReset(this->waveHandle);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
boost::recursive_mutex::scoped_lock lock1(this->bufferQueueMutex);
|
||||
this->ClearBufferQueue();
|
||||
if (this->waveHandle != NULL) {
|
||||
waveOutReset(this->waveHandle);
|
||||
}
|
||||
}
|
||||
|
||||
void WaveOut::ClearBufferQueue() {
|
||||
boost::recursive_mutex::scoped_lock lock(this->bufferQueueMutex);
|
||||
std::list<WaveOutBufferPtr> remove;
|
||||
|
||||
{
|
||||
boost::recursive_mutex::scoped_lock lock(this->bufferQueueMutex);
|
||||
std::swap(this->queuedBuffers, remove);
|
||||
}
|
||||
|
||||
/* notify and free any pending buffers, the Player in the core
|
||||
will be waiting for all pending buffers to be processed. */
|
||||
if (this->queuedBuffers.size() > 0) {
|
||||
BufferList::iterator it = this->queuedBuffers.begin();
|
||||
for (; it != this->queuedBuffers.end(); it++) {
|
||||
if (remove.size() > 0) {
|
||||
BufferList::iterator it = remove.begin();
|
||||
for (; it != remove.end(); it++) {
|
||||
notifyBufferProcessed((*it).get());
|
||||
}
|
||||
|
||||
this->queuedBuffers.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void WaveOut::OnBufferWrittenToOutput(WaveOutBuffer *buffer) {
|
||||
boost::recursive_mutex::scoped_lock lock(this->bufferQueueMutex);
|
||||
|
||||
/* removed the buffer. it should be at the front of the queue. */
|
||||
BufferList::iterator it = this->queuedBuffers.begin();
|
||||
for( ; it != this->queuedBuffers.end(); it++) {
|
||||
if (it->get() == buffer) {
|
||||
notifyBufferProcessed(buffer);
|
||||
this->queuedBuffers.erase(it);
|
||||
return;
|
||||
WaveOutBufferPtr erased;
|
||||
|
||||
{
|
||||
boost::recursive_mutex::scoped_lock lock(this->bufferQueueMutex);
|
||||
|
||||
/* removed the buffer. it should be at the front of the queue. */
|
||||
BufferList::iterator it = this->queuedBuffers.begin();
|
||||
for (; it != this->queuedBuffers.end(); it++) {
|
||||
if (it->get() == buffer) {
|
||||
erased = *it;
|
||||
this->queuedBuffers.erase(it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (erased) {
|
||||
notifyBufferProcessed(erased.get());
|
||||
}
|
||||
}
|
||||
|
||||
void WaveOut::StartWaveOutThread() {
|
||||
this->StopWaveOutThread();
|
||||
|
||||
this->threadHandle = CreateThread(
|
||||
NULL,
|
||||
0,
|
||||
@ -168,11 +179,9 @@ void WaveOut::StopWaveOutThread() {
|
||||
bool WaveOut::Play(IBuffer *buffer, IBufferProvider *provider) {
|
||||
boost::recursive_mutex::scoped_lock lock(this->bufferQueueMutex);
|
||||
|
||||
size_t bufferCount = bufferCount = this->queuedBuffers.size();
|
||||
|
||||
/* if we have a different format, return false and wait for the pending
|
||||
buffers to be written to the output device. */
|
||||
if (bufferCount > 0) {
|
||||
if (!this->queuedBuffers.empty()) {
|
||||
bool formatChanged =
|
||||
this->currentChannels != buffer->Channels() ||
|
||||
this->currentSampleRate != buffer->SampleRate();
|
||||
@ -182,7 +191,24 @@ bool WaveOut::Play(IBuffer *buffer, IBufferProvider *provider) {
|
||||
}
|
||||
}
|
||||
|
||||
if (MAX_BUFFERS > bufferCount) {
|
||||
size_t buffersForOutput = 0;
|
||||
auto it = this->queuedBuffers.begin();
|
||||
while (it != this->queuedBuffers.end()) {
|
||||
if ((*it)->GetBufferProvider() == provider) {
|
||||
++buffersForOutput;
|
||||
}
|
||||
++it;
|
||||
}
|
||||
|
||||
if (MAX_BUFFERS_PER_OUTPUT > buffersForOutput) {
|
||||
{
|
||||
boost::recursive_mutex::scoped_lock lock2(this->outputDeviceMutex);
|
||||
|
||||
if (!this->playing) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* ensure the output device itself (the WAVEOUT) is configured correctly
|
||||
for the new buffer */
|
||||
this->SetFormat(buffer);
|
||||
@ -211,7 +237,6 @@ void WaveOut::SetFormat(IBuffer *buffer) {
|
||||
this->currentSampleRate = buffer->SampleRate();
|
||||
|
||||
this->Stop();
|
||||
this->StopWaveOutThread();
|
||||
this->StartWaveOutThread();
|
||||
|
||||
/* reset, and configure speaker output */
|
||||
|
@ -65,6 +65,7 @@ class WaveOut : public IOutput {
|
||||
void SetFormat(IBuffer *buffer);
|
||||
void StartWaveOutThread();
|
||||
void StopWaveOutThread();
|
||||
void ResetWaveOut();
|
||||
void ClearBufferQueue();
|
||||
|
||||
protected:
|
||||
@ -86,6 +87,7 @@ class WaveOut : public IOutput {
|
||||
int currentChannels;
|
||||
long currentSampleRate;
|
||||
double currentVolume;
|
||||
bool playing;
|
||||
|
||||
/* a queue of buffers we've recieved from the core Player, and have enqueued
|
||||
to the output device. we need to notify the IBufferProvider when they have finished
|
||||
|
@ -1,164 +1,168 @@
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright <20> 2007, Daniel <20>nnerby
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistributions in binary form must reproduce the above copyright
|
||||
// notice, this list of conditions and the following disclaimer in the
|
||||
// documentation and/or other materials provided with the distribution.
|
||||
//
|
||||
// * Neither the name of the author nor the names of other contributors may
|
||||
// be used to endorse or promote products derived from this software
|
||||
// without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
// POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "pch.hpp"
|
||||
|
||||
#include <core/audio/Buffer.h>
|
||||
|
||||
#define DEBUG 0
|
||||
|
||||
#if DEBUG > 0
|
||||
#include <iostream>
|
||||
#endif
|
||||
|
||||
using namespace musik::core::audio;
|
||||
|
||||
Buffer::Buffer(void)
|
||||
:buffer(NULL)
|
||||
,sampleSize(0)
|
||||
,internalBufferSize(0)
|
||||
,sampleRate(44100)
|
||||
,channels(2) {
|
||||
}
|
||||
|
||||
Buffer::~Buffer() {
|
||||
delete this->buffer;
|
||||
}
|
||||
|
||||
BufferPtr Buffer::Create() {
|
||||
return BufferPtr(new Buffer());
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright <20> 2007, Daniel <20>nnerby
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistributions in binary form must reproduce the above copyright
|
||||
// notice, this list of conditions and the following disclaimer in the
|
||||
// documentation and/or other materials provided with the distribution.
|
||||
//
|
||||
// * Neither the name of the author nor the names of other contributors may
|
||||
// be used to endorse or promote products derived from this software
|
||||
// without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
// POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "pch.hpp"
|
||||
|
||||
#include <core/audio/Buffer.h>
|
||||
|
||||
#define DEBUG 0
|
||||
|
||||
#if DEBUG > 0
|
||||
#include <iostream>
|
||||
#endif
|
||||
|
||||
using namespace musik::core::audio;
|
||||
|
||||
Buffer::Buffer(void)
|
||||
:buffer(NULL)
|
||||
,sampleSize(0)
|
||||
,internalBufferSize(0)
|
||||
,sampleRate(44100)
|
||||
,channels(2) {
|
||||
}
|
||||
|
||||
Buffer::~Buffer() {
|
||||
delete this->buffer;
|
||||
}
|
||||
|
||||
BufferPtr Buffer::Create() {
|
||||
return BufferPtr(new Buffer());
|
||||
}
|
||||
|
||||
long Buffer::SampleRate() const { /* hertz */
|
||||
return this->sampleRate;
|
||||
}
|
||||
|
||||
void Buffer::SetSampleRate(long sampleRate) { /* hertz */
|
||||
return this->sampleRate;
|
||||
}
|
||||
|
||||
void Buffer::SetSampleRate(long sampleRate) { /* hertz */
|
||||
this->sampleRate = sampleRate;
|
||||
}
|
||||
|
||||
int Buffer::Channels() const {
|
||||
return this->channels;
|
||||
}
|
||||
|
||||
void Buffer::SetChannels(int channels) {
|
||||
this->channels = channels;
|
||||
this->ResizeBuffer();
|
||||
}
|
||||
|
||||
float* Buffer::BufferPointer() const {
|
||||
return this->buffer;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int Buffer::Channels() const {
|
||||
return this->channels;
|
||||
}
|
||||
|
||||
void Buffer::SetChannels(int channels) {
|
||||
this->channels = channels;
|
||||
this->ResizeBuffer();
|
||||
}
|
||||
|
||||
float* Buffer::BufferPointer() const {
|
||||
return this->buffer;
|
||||
}
|
||||
|
||||
long Buffer::Samples() const { /* pre-multiplied by channel count */
|
||||
return this->sampleSize;
|
||||
}
|
||||
|
||||
void Buffer::SetSamples(long samples) {
|
||||
this->sampleSize = samples;
|
||||
this->ResizeBuffer();
|
||||
}
|
||||
|
||||
void Buffer::CopyFormat(BufferPtr fromBuffer) {
|
||||
this->sampleSize = fromBuffer->Samples();
|
||||
this->channels = fromBuffer->Channels();
|
||||
this->sampleRate = fromBuffer->SampleRate();
|
||||
this->ResizeBuffer();
|
||||
}
|
||||
|
||||
void Buffer::ResizeBuffer() {
|
||||
long requiredBufferSize = this->sampleSize * this->channels;
|
||||
if (requiredBufferSize > this->internalBufferSize) {
|
||||
if (this->buffer) {
|
||||
delete this->buffer;
|
||||
this->buffer = NULL;
|
||||
}
|
||||
|
||||
this->buffer = new float[requiredBufferSize];
|
||||
this->internalBufferSize = requiredBufferSize;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Buffer::SetSamples(long samples) {
|
||||
this->sampleSize = samples;
|
||||
this->ResizeBuffer();
|
||||
}
|
||||
|
||||
void Buffer::CopyFormat(BufferPtr fromBuffer) {
|
||||
this->sampleSize = fromBuffer->Samples();
|
||||
this->channels = fromBuffer->Channels();
|
||||
this->sampleRate = fromBuffer->SampleRate();
|
||||
this->ResizeBuffer();
|
||||
}
|
||||
|
||||
void Buffer::ResizeBuffer() {
|
||||
long requiredBufferSize = this->sampleSize * this->channels;
|
||||
if (requiredBufferSize > this->internalBufferSize) {
|
||||
if (this->buffer) {
|
||||
delete this->buffer;
|
||||
this->buffer = NULL;
|
||||
}
|
||||
|
||||
this->buffer = new float[requiredBufferSize];
|
||||
this->internalBufferSize = requiredBufferSize;
|
||||
}
|
||||
}
|
||||
|
||||
/* logical bytes; backing store may be be larger */
|
||||
long Buffer::Bytes() const {
|
||||
return sizeof(float) * this->sampleSize * this->channels;
|
||||
}
|
||||
|
||||
double Buffer::Position() const { /* position in track. ???? */
|
||||
return sizeof(float) * this->sampleSize * this->channels;
|
||||
}
|
||||
|
||||
double Buffer::Position() const {
|
||||
return this->position;
|
||||
}
|
||||
|
||||
bool Buffer::Append(BufferPtr appendBuffer) {
|
||||
if (this->SampleRate() == appendBuffer->SampleRate() &&
|
||||
this->Channels() == appendBuffer->Channels())
|
||||
{
|
||||
/* number of floats (not bytes) in buffer */
|
||||
long newBufferSize = (this->Samples() + appendBuffer->Samples()) * this->channels;
|
||||
|
||||
if (newBufferSize > this->internalBufferSize) { /* resize, then copy, if too small */
|
||||
float *newBuffer = new float[newBufferSize];
|
||||
|
||||
CopyFloat(newBuffer, this->buffer, this->sampleSize * this->channels);
|
||||
|
||||
float *dst = &newBuffer[this->sampleSize * this->channels];
|
||||
float *src = appendBuffer->BufferPointer();
|
||||
long count = appendBuffer->Samples() * this->channels;
|
||||
|
||||
CopyFloat(dst, src, count);
|
||||
|
||||
delete this->buffer;
|
||||
|
||||
#if DEBUG > 0
|
||||
std::cerr << "resized with realloc old: " << this->internalBufferSize << " new: " << newBufferSize << "\n";
|
||||
#endif
|
||||
|
||||
this->buffer = newBuffer;
|
||||
this->internalBufferSize = newBufferSize;
|
||||
}
|
||||
else { /* append, no resize required */
|
||||
float *dst = &this->buffer[this->sampleSize * this->channels];
|
||||
float *src = appendBuffer->BufferPointer();
|
||||
long count = appendBuffer->Samples() * this->channels;
|
||||
CopyFloat(dst, src, count);
|
||||
|
||||
#if DEBUG > 0
|
||||
std::cerr << "appended " << count << " floats to existing buffer, logical bytes=" << newBufferSize << "\n";
|
||||
#endif
|
||||
}
|
||||
|
||||
this->sampleSize = newBufferSize / this->channels;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void Buffer::SetPosition(double position) {
|
||||
this->position = position;
|
||||
}
|
||||
|
||||
bool Buffer::Append(BufferPtr appendBuffer) {
|
||||
if (this->SampleRate() == appendBuffer->SampleRate() &&
|
||||
this->Channels() == appendBuffer->Channels())
|
||||
{
|
||||
/* number of floats (not bytes) in buffer */
|
||||
long newBufferSize = (this->Samples() + appendBuffer->Samples()) * this->channels;
|
||||
|
||||
if (newBufferSize > this->internalBufferSize) { /* resize, then copy, if too small */
|
||||
float *newBuffer = new float[newBufferSize];
|
||||
|
||||
CopyFloat(newBuffer, this->buffer, this->sampleSize * this->channels);
|
||||
|
||||
float *dst = &newBuffer[this->sampleSize * this->channels];
|
||||
float *src = appendBuffer->BufferPointer();
|
||||
long count = appendBuffer->Samples() * this->channels;
|
||||
|
||||
CopyFloat(dst, src, count);
|
||||
|
||||
delete this->buffer;
|
||||
|
||||
#if DEBUG > 0
|
||||
std::cerr << "resized with realloc old: " << this->internalBufferSize << " new: " << newBufferSize << "\n";
|
||||
#endif
|
||||
|
||||
this->buffer = newBuffer;
|
||||
this->internalBufferSize = newBufferSize;
|
||||
}
|
||||
else { /* append, no resize required */
|
||||
float *dst = &this->buffer[this->sampleSize * this->channels];
|
||||
float *src = appendBuffer->BufferPointer();
|
||||
long count = appendBuffer->Samples() * this->channels;
|
||||
CopyFloat(dst, src, count);
|
||||
|
||||
#if DEBUG > 0
|
||||
std::cerr << "appended " << count << " floats to existing buffer, logical bytes=" << newBufferSize << "\n";
|
||||
#endif
|
||||
}
|
||||
|
||||
this->sampleSize = newBufferSize / this->channels;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
@ -1,81 +1,78 @@
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright <20> 2007, Daniel <20>nnerby
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistributions in binary form must reproduce the above copyright
|
||||
// notice, this list of conditions and the following disclaimer in the
|
||||
// documentation and/or other materials provided with the distribution.
|
||||
//
|
||||
// * Neither the name of the author nor the names of other contributors may
|
||||
// be used to endorse or promote products derived from this software
|
||||
// without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
// POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
#pragma once
|
||||
|
||||
#include <core/config.h>
|
||||
#include <boost/shared_ptr.hpp>
|
||||
#include <core/sdk/IBuffer.h>
|
||||
|
||||
namespace musik { namespace core { namespace audio {
|
||||
|
||||
class Buffer;
|
||||
class Stream;
|
||||
typedef std::shared_ptr<Buffer> BufferPtr;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright <20> 2007, Daniel <20>nnerby
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistributions in binary form must reproduce the above copyright
|
||||
// notice, this list of conditions and the following disclaimer in the
|
||||
// documentation and/or other materials provided with the distribution.
|
||||
//
|
||||
// * Neither the name of the author nor the names of other contributors may
|
||||
// be used to endorse or promote products derived from this software
|
||||
// without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
// POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
#pragma once
|
||||
|
||||
#include <core/config.h>
|
||||
#include <boost/shared_ptr.hpp>
|
||||
#include <core/sdk/IBuffer.h>
|
||||
|
||||
namespace musik { namespace core { namespace audio {
|
||||
|
||||
class Buffer;
|
||||
class Stream;
|
||||
typedef std::shared_ptr<Buffer> BufferPtr;
|
||||
|
||||
class Buffer : public IBuffer {
|
||||
private:
|
||||
Buffer();
|
||||
|
||||
public:
|
||||
static BufferPtr Create();
|
||||
~Buffer();
|
||||
|
||||
virtual long SampleRate() const;
|
||||
virtual void SetSampleRate(long sampleRate);
|
||||
virtual int Channels() const;
|
||||
virtual void SetChannels(int channels);
|
||||
virtual float* BufferPointer() const;
|
||||
virtual long Samples() const;
|
||||
virtual void SetSamples(long samples);
|
||||
virtual long Bytes() const;
|
||||
virtual double Position() const;
|
||||
|
||||
bool Append(BufferPtr appendBuffer);
|
||||
void CopyFormat(BufferPtr fromBuffer);
|
||||
|
||||
private:
|
||||
void ResizeBuffer();
|
||||
|
||||
private:
|
||||
float *buffer;
|
||||
long sampleSize;
|
||||
long internalBufferSize;
|
||||
long sampleRate;
|
||||
int channels;
|
||||
|
||||
protected:
|
||||
friend class Stream;
|
||||
double position;
|
||||
};
|
||||
|
||||
} } }
|
||||
private:
|
||||
Buffer();
|
||||
|
||||
public:
|
||||
static BufferPtr Create();
|
||||
~Buffer();
|
||||
|
||||
virtual long SampleRate() const;
|
||||
virtual void SetSampleRate(long sampleRate);
|
||||
virtual int Channels() const;
|
||||
virtual void SetChannels(int channels);
|
||||
virtual float* BufferPointer() const;
|
||||
virtual long Samples() const;
|
||||
virtual void SetSamples(long samples);
|
||||
virtual long Bytes() const;
|
||||
virtual double Position() const;
|
||||
void SetPosition(double position);
|
||||
|
||||
bool Append(BufferPtr appendBuffer);
|
||||
void CopyFormat(BufferPtr fromBuffer);
|
||||
|
||||
private:
|
||||
void ResizeBuffer();
|
||||
|
||||
float *buffer;
|
||||
long sampleSize;
|
||||
long internalBufferSize;
|
||||
long sampleRate;
|
||||
int channels;
|
||||
double position;
|
||||
};
|
||||
|
||||
} } }
|
||||
|
@ -46,11 +46,11 @@ using std::max;
|
||||
|
||||
static std::string TAG = "Player";
|
||||
|
||||
PlayerPtr Player::Create(const std::string &url, double volume, OutputPtr output) {
|
||||
return PlayerPtr(new Player(url, volume, output));
|
||||
PlayerPtr Player::Create(const std::string &url, OutputPtr output) {
|
||||
return PlayerPtr(new Player(url, output));
|
||||
}
|
||||
|
||||
Player::OutputPtr Player::CreateDefaultOutput() {
|
||||
OutputPtr Player::CreateDefaultOutput() {
|
||||
/* if no output is specified, find all output plugins, and select the first one. */
|
||||
typedef std::vector<OutputPtr> OutputVector;
|
||||
|
||||
@ -62,32 +62,36 @@ Player::OutputPtr Player::CreateDefaultOutput() {
|
||||
return outputs.front();
|
||||
}
|
||||
|
||||
return Player::OutputPtr();
|
||||
return OutputPtr();
|
||||
}
|
||||
|
||||
|
||||
Player::Player(const std::string &url, double volume, OutputPtr output)
|
||||
: volume(volume)
|
||||
, state(Player::Precache)
|
||||
Player::Player(const std::string &url, OutputPtr output)
|
||||
: state(Player::Precache)
|
||||
, url(url)
|
||||
, currentPosition(0)
|
||||
, output(output)
|
||||
, setPosition(-1) {
|
||||
musik::debug::info(TAG, "new instance created");
|
||||
|
||||
/* we allow callers to specify an output device; but if they don't,
|
||||
we will create and manage our own. */
|
||||
this->output = output ? output : Player::CreateDefaultOutput();
|
||||
if (!this->output) {
|
||||
throw std::runtime_error("output cannot be null!");
|
||||
}
|
||||
|
||||
/* each player instance is driven by a background thread. start it. */
|
||||
this->thread.reset(new boost::thread(boost::bind(&Player::ThreadLoop,this)));
|
||||
}
|
||||
|
||||
Player::~Player() {
|
||||
this->Stop();
|
||||
|
||||
if (this->thread) {
|
||||
this->thread->join();
|
||||
{
|
||||
boost::mutex::scoped_lock lock(this->mutex);
|
||||
this->state = Player::Quit;
|
||||
this->prebufferQueue.clear();
|
||||
this->writeToOutputCondition.notify_all();
|
||||
}
|
||||
|
||||
this->thread->join();
|
||||
}
|
||||
|
||||
void Player::Play() {
|
||||
@ -97,28 +101,9 @@ void Player::Play() {
|
||||
}
|
||||
|
||||
void Player::Stop() {
|
||||
{
|
||||
boost::mutex::scoped_lock lock(this->mutex);
|
||||
this->state = Player::Quit;
|
||||
this->prebufferQueue.clear();
|
||||
this->writeToOutputCondition.notify_all();
|
||||
}
|
||||
|
||||
if (this->output) {
|
||||
this->output->Stop();
|
||||
}
|
||||
}
|
||||
|
||||
void Player::Pause() {
|
||||
if (this->output) {
|
||||
this->output->Pause();
|
||||
}
|
||||
}
|
||||
|
||||
void Player::Resume() {
|
||||
if (this->output) {
|
||||
this->output->Resume();
|
||||
}
|
||||
boost::mutex::scoped_lock lock(this->mutex);
|
||||
this->state = Player::Quit;
|
||||
this->writeToOutputCondition.notify_all();
|
||||
}
|
||||
|
||||
double Player::Position() {
|
||||
@ -131,21 +116,6 @@ void Player::SetPosition(double seconds) {
|
||||
this->setPosition = std::max(0.0, seconds);
|
||||
}
|
||||
|
||||
double Player::Volume() {
|
||||
boost::mutex::scoped_lock lock(this->mutex);
|
||||
return this->volume;
|
||||
}
|
||||
|
||||
void Player::SetVolume(double volume) {
|
||||
boost::mutex::scoped_lock lock(this->mutex);
|
||||
|
||||
this->volume = volume;
|
||||
|
||||
if (this->output) {
|
||||
this->output->SetVolume(this->volume);
|
||||
}
|
||||
}
|
||||
|
||||
int Player::State() {
|
||||
boost::mutex::scoped_lock lock(this->mutex);
|
||||
return this->state;
|
||||
@ -155,42 +125,35 @@ void Player::ThreadLoop() {
|
||||
/* create and open the stream */
|
||||
this->stream = Stream::Create();
|
||||
if (this->stream->OpenStream(this->url)) {
|
||||
/* ensure the volume is set properly */
|
||||
{
|
||||
boost::mutex::scoped_lock lock(this->mutex);
|
||||
this->output->SetVolume(this->volume);
|
||||
}
|
||||
|
||||
/* precache until buffers are full */
|
||||
bool keepPrecaching = true;
|
||||
while (this->State() == Precache && keepPrecaching) {
|
||||
keepPrecaching = this->PreBuffer();
|
||||
boost::thread::yield();
|
||||
}
|
||||
|
||||
/* wait until we enter the Playing or Quit state; we may still
|
||||
be in the Precache state. */
|
||||
{
|
||||
while (this->state == Precache) {
|
||||
boost::mutex::scoped_lock lock(this->mutex);
|
||||
while (this->state == Precache) {
|
||||
this->writeToOutputCondition.wait(lock);
|
||||
}
|
||||
this->writeToOutputCondition.wait(lock);
|
||||
}
|
||||
|
||||
this->PlaybackStarted(this);
|
||||
|
||||
/* we're ready to go.... */
|
||||
bool finished = false;
|
||||
BufferPtr buffer;
|
||||
|
||||
bool startedPlaying = false;
|
||||
|
||||
while (!finished && !this->Exited()) {
|
||||
/* see if we've been asked to seek since the last sample was
|
||||
played. if we have, clear our output buffer and seek the
|
||||
stream. */
|
||||
if (this->setPosition != -1) {
|
||||
this->output->Stop();
|
||||
this->output->Stop(); /* flush all buffers */
|
||||
this->output->Resume(); /* start it back up */
|
||||
|
||||
while (this->lockedBuffers.size() > 0) {
|
||||
boost::mutex::scoped_lock lock(this->mutex);
|
||||
writeToOutputCondition.wait(this->mutex);
|
||||
}
|
||||
|
||||
@ -224,6 +187,11 @@ void Player::ThreadLoop() {
|
||||
the output device. */
|
||||
if (buffer) {
|
||||
if (this->output->Play(buffer.get(), this)) {
|
||||
if (!startedPlaying) {
|
||||
startedPlaying = true;
|
||||
this->PlaybackStarted(this);
|
||||
}
|
||||
|
||||
/* success! the buffer was accepted by the output.*/
|
||||
boost::mutex::scoped_lock lock(this->mutex);
|
||||
|
||||
@ -263,37 +231,18 @@ void Player::ThreadLoop() {
|
||||
this->PlaybackError(this);
|
||||
}
|
||||
|
||||
bool stopped = false;
|
||||
this->state = Player::Quit;
|
||||
|
||||
/* wait until all remaining buffers have been written, set final state... */
|
||||
{
|
||||
boost::mutex::scoped_lock lock(this->mutex);
|
||||
|
||||
/* if the state is "Quit" that means the user terminated the stream, so no
|
||||
more buffers will be played / recycled from the output device. kill them now.*/
|
||||
if (this->state == Player::Quit) {
|
||||
this->lockedBuffers.clear();
|
||||
while (this->lockedBuffers.size() > 0) {
|
||||
writeToOutputCondition.wait(this->mutex);
|
||||
}
|
||||
/* otherwise wait for their completion */
|
||||
else {
|
||||
while (this->lockedBuffers.size() > 0) {
|
||||
writeToOutputCondition.wait(this->mutex);
|
||||
}
|
||||
}
|
||||
|
||||
stopped = (this->state == Player::Quit);
|
||||
this->state = Player::Quit;
|
||||
}
|
||||
|
||||
if (stopped) {
|
||||
this->PlaybackStopped(this);
|
||||
}
|
||||
else {
|
||||
this->PlaybackFinished(this);
|
||||
}
|
||||
|
||||
this->output.reset();
|
||||
this->stream.reset();
|
||||
this->PlaybackFinished(this);
|
||||
}
|
||||
|
||||
void Player::ReleaseAllBuffers() {
|
||||
|
@ -49,21 +49,20 @@ namespace musik { namespace core { namespace audio {
|
||||
class Player;
|
||||
typedef std::shared_ptr<Player> PlayerPtr;
|
||||
|
||||
class Output;
|
||||
typedef std::shared_ptr<IOutput> OutputPtr;
|
||||
|
||||
class Player : public IBufferProvider {
|
||||
public:
|
||||
typedef std::shared_ptr<IOutput> OutputPtr;
|
||||
|
||||
static OutputPtr CreateDefaultOutput();
|
||||
|
||||
static PlayerPtr Create(
|
||||
const std::string &url,
|
||||
double volume = 1.0f,
|
||||
OutputPtr output = OutputPtr());
|
||||
OutputPtr output);
|
||||
|
||||
Player(
|
||||
const std::string &url,
|
||||
double volume = 1.0f,
|
||||
OutputPtr output = OutputPtr());
|
||||
OutputPtr output);
|
||||
|
||||
~Player();
|
||||
|
||||
@ -71,25 +70,18 @@ namespace musik { namespace core { namespace audio {
|
||||
|
||||
void Play();
|
||||
void Stop();
|
||||
void Pause();
|
||||
void Resume();
|
||||
|
||||
double Position();
|
||||
void SetPosition(double seconds);
|
||||
|
||||
double Volume();
|
||||
void SetVolume(double volume);
|
||||
|
||||
std::string GetUrl() const { return this->url; }
|
||||
|
||||
bool Exited();
|
||||
|
||||
public:
|
||||
typedef sigslot::signal1<Player*> PlayerEvent;
|
||||
PlayerEvent PlaybackStarted;
|
||||
PlayerEvent PlaybackAlmostEnded;
|
||||
PlayerEvent PlaybackFinished;
|
||||
PlayerEvent PlaybackStopped;
|
||||
PlayerEvent PlaybackError;
|
||||
|
||||
private:
|
||||
|
@ -145,7 +145,9 @@ BufferPtr Stream::GetNextBufferFromDecoder() {
|
||||
this->decoderSamplePosition += buffer->Samples();
|
||||
|
||||
/* calculate the position (seconds) in the buffer */
|
||||
buffer->position = ((double) this->decoderSamplePosition) / ((double) this->decoderSampleRate);
|
||||
buffer->SetPosition(
|
||||
((double) this->decoderSamplePosition) /
|
||||
((double) this->decoderSampleRate));
|
||||
|
||||
return buffer;
|
||||
}
|
||||
@ -175,7 +177,7 @@ BufferPtr Stream::GetNextProcessedOutputBuffer() {
|
||||
|
||||
for (Dsps::iterator dsp = this->dsps.begin(); dsp != this->dsps.end(); ++dsp) {
|
||||
oldBuffer->CopyFormat(currentBuffer);
|
||||
oldBuffer->position = currentBuffer->position;
|
||||
currentBuffer->SetPosition(oldBuffer->Position());
|
||||
|
||||
if ((*dsp)->Process(currentBuffer.get(), oldBuffer.get())) {
|
||||
currentBuffer.swap(oldBuffer);
|
||||
|
@ -53,14 +53,6 @@ static std::string TAG = "Transport";
|
||||
thread.detach(); \
|
||||
}
|
||||
|
||||
static void pausePlayer(Player* p) {
|
||||
p->Pause();
|
||||
}
|
||||
|
||||
static void resumePlayer(Player* p) {
|
||||
p->Resume();
|
||||
}
|
||||
|
||||
static void stopPlayer(Player* p) {
|
||||
p->Stop();
|
||||
}
|
||||
@ -72,7 +64,8 @@ static void deletePlayer(Player* p) {
|
||||
Transport::Transport()
|
||||
: volume(1.0)
|
||||
, state(PlaybackStopped)
|
||||
, nextPlayer(NULL) {
|
||||
, nextPlayer(NULL)
|
||||
, nextCanStart(false) {
|
||||
this->output = Player::CreateDefaultOutput();
|
||||
}
|
||||
|
||||
@ -88,14 +81,10 @@ void Transport::PrepareNextTrack(const std::string& trackUrl) {
|
||||
bool startNext = false;
|
||||
{
|
||||
boost::recursive_mutex::scoped_lock lock(this->stateMutex);
|
||||
this->nextPlayer = new Player(trackUrl, this->volume, this->output);
|
||||
startNext = this->state == PlaybackStopped;
|
||||
this->nextPlayer = new Player(trackUrl, this->output);
|
||||
startNext = this->nextCanStart;
|
||||
}
|
||||
|
||||
/* we raise an event when the current stream has almost finished, which
|
||||
allows the calling app to prepare a new track. by the time this preparation
|
||||
is complete, the track may have ended. if we're in the stopped state, start
|
||||
playback of this new track immediately. */
|
||||
if (startNext) {
|
||||
this->StartWithPlayer(this->nextPlayer);
|
||||
}
|
||||
@ -104,7 +93,7 @@ void Transport::PrepareNextTrack(const std::string& trackUrl) {
|
||||
void Transport::Start(const std::string& url) {
|
||||
musik::debug::info(TAG, "we were asked to start the track at " + url);
|
||||
|
||||
Player* newPlayer = new Player(url, this->volume, this->output);
|
||||
Player* newPlayer = new Player(url, this->output);
|
||||
musik::debug::info(TAG, "Player created successfully");
|
||||
|
||||
this->StartWithPlayer(newPlayer);
|
||||
@ -115,23 +104,25 @@ void Transport::StartWithPlayer(Player* newPlayer) {
|
||||
{
|
||||
boost::recursive_mutex::scoped_lock lock(this->stateMutex);
|
||||
|
||||
bool playingNext = (newPlayer == nextPlayer);
|
||||
|
||||
if (newPlayer != nextPlayer) {
|
||||
delete nextPlayer;
|
||||
}
|
||||
|
||||
this->nextPlayer = NULL;
|
||||
this->Stop(true); /* true = suppress stopped event */
|
||||
this->Stop(playingNext); /* true = suppress stopped event */
|
||||
this->SetNextCanStart(false);
|
||||
|
||||
newPlayer->PlaybackStarted.connect(this, &Transport::OnPlaybackStarted);
|
||||
newPlayer->PlaybackAlmostEnded.connect(this, &Transport::OnPlaybackAlmostEnded);
|
||||
newPlayer->PlaybackFinished.connect(this, &Transport::OnPlaybackFinished);
|
||||
newPlayer->PlaybackStopped.connect(this, &Transport::OnPlaybackStopped);
|
||||
newPlayer->PlaybackError.connect(this, &Transport::OnPlaybackError);
|
||||
|
||||
musik::debug::info(TAG, "play()");
|
||||
|
||||
this->active.push_front(newPlayer);
|
||||
newPlayer->SetVolume(this->volume);
|
||||
this->output->Resume();
|
||||
newPlayer->Play();
|
||||
}
|
||||
|
||||
@ -143,7 +134,7 @@ void Transport::Stop() {
|
||||
this->Stop(false);
|
||||
}
|
||||
|
||||
void Transport::Stop(bool suppressEvent) {
|
||||
void Transport::Stop(bool playingNext) {
|
||||
musik::debug::info(TAG, "stop");
|
||||
|
||||
std::list<Player*> toDelete;
|
||||
@ -156,15 +147,20 @@ void Transport::Stop(bool suppressEvent) {
|
||||
|
||||
/* delete these in the background to avoid deadlock in some cases
|
||||
where this method is implicitly triggered via Player callback. however,
|
||||
it's perfectly safe (and actually required) to stop them immediately to
|
||||
ensure all pending buffers have been flushed. */
|
||||
we should stop them immediately so they stop producing audio. */
|
||||
std::for_each(toDelete.begin(), toDelete.end(), stopPlayer);
|
||||
DEFER(&Transport::DeletePlayers, toDelete);
|
||||
|
||||
/* stopping the transport will stop any buffers that are currently in
|
||||
flight. this makes the sound end immediately. */
|
||||
if (!playingNext) {
|
||||
this->output->Stop();
|
||||
}
|
||||
|
||||
/* if we know we're starting another track immediately, suppress
|
||||
the stop event. this functionality is not available to the public
|
||||
interface, it's an internal optimization */
|
||||
if (!suppressEvent) {
|
||||
if (!playingNext) {
|
||||
this->SetPlaybackState(PlaybackStopped);
|
||||
}
|
||||
}
|
||||
@ -174,9 +170,10 @@ bool Transport::Pause() {
|
||||
|
||||
size_t count = 0;
|
||||
|
||||
this->output->Pause();
|
||||
|
||||
{
|
||||
boost::recursive_mutex::scoped_lock lock(this->stateMutex);
|
||||
std::for_each(this->active.begin(), this->active.end(), pausePlayer);
|
||||
count = this->active.size();
|
||||
}
|
||||
|
||||
@ -191,12 +188,19 @@ bool Transport::Pause() {
|
||||
bool Transport::Resume() {
|
||||
musik::debug::info(TAG, "resume");
|
||||
|
||||
this->output->Resume();
|
||||
|
||||
size_t count = 0;
|
||||
|
||||
{
|
||||
boost::recursive_mutex::scoped_lock lock(this->stateMutex);
|
||||
std::for_each(this->active.begin(), this->active.end(), resumePlayer);
|
||||
count = this->active.size();
|
||||
|
||||
auto it = this->active.begin();
|
||||
while (it != this->active.end()) {
|
||||
(*it)->Play();
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
if (count) {
|
||||
@ -241,25 +245,12 @@ void Transport::SetVolume(double volume) {
|
||||
this->VolumeChanged();
|
||||
}
|
||||
|
||||
musik::debug::info(TAG, boost::str(
|
||||
boost::format("set volume %d%%") % round(volume * 100)));
|
||||
std::string output = boost::str(
|
||||
boost::format("set volume %d%%") % round(volume * 100));
|
||||
|
||||
{
|
||||
boost::recursive_mutex::scoped_lock lock(this->stateMutex);
|
||||
musik::debug::info(TAG, output);
|
||||
|
||||
if (!this->active.empty()) {
|
||||
this->active.front()->SetVolume(volume);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Transport::OnPlaybackStarted(Player* player) {
|
||||
this->RaiseStreamEvent(Transport::StreamPlaying, player);
|
||||
this->SetPlaybackState(Transport::PlaybackPlaying);
|
||||
}
|
||||
|
||||
void Transport::OnPlaybackAlmostEnded(Player* player) {
|
||||
this->RaiseStreamEvent(Transport::StreamAlmostDone, player);
|
||||
this->output->SetVolume(this->volume);
|
||||
}
|
||||
|
||||
void Transport::RemoveActive(Player* player) {
|
||||
@ -287,7 +278,33 @@ void Transport::DeletePlayers(std::list<Player*> players) {
|
||||
std::for_each(players.begin(), players.end(), deletePlayer);
|
||||
}
|
||||
|
||||
void Transport::SetNextCanStart(bool nextCanStart) {
|
||||
boost::recursive_mutex::scoped_lock lock(this->stateMutex);
|
||||
this->nextCanStart = nextCanStart;
|
||||
}
|
||||
|
||||
void Transport::OnPlaybackStarted(Player* player) {
|
||||
this->RaiseStreamEvent(Transport::StreamPlaying, player);
|
||||
this->SetPlaybackState(Transport::PlaybackPlaying);
|
||||
}
|
||||
|
||||
void Transport::OnPlaybackAlmostEnded(Player* player) {
|
||||
this->SetNextCanStart(true);
|
||||
this->RaiseStreamEvent(Transport::StreamAlmostDone, player);
|
||||
|
||||
{
|
||||
boost::recursive_mutex::scoped_lock lock(this->stateMutex);
|
||||
|
||||
/* if another component configured a next player while we were playing,
|
||||
go ahead and get it started now. */
|
||||
if (this->nextPlayer) {
|
||||
this->StartWithPlayer(this->nextPlayer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Transport::OnPlaybackFinished(Player* player) {
|
||||
this->SetNextCanStart(true);
|
||||
this->RaiseStreamEvent(Transport::StreamFinished, player);
|
||||
|
||||
bool stopped = false;
|
||||
@ -320,23 +337,8 @@ void Transport::OnPlaybackFinished(Player* player) {
|
||||
DEFER(&Transport::RemoveActive, player);
|
||||
}
|
||||
|
||||
void Transport::OnPlaybackStopped (Player* player) {
|
||||
this->RaiseStreamEvent(Transport::StreamStopped, player);
|
||||
|
||||
bool stopped = false;
|
||||
{
|
||||
boost::recursive_mutex::scoped_lock lock(this->stateMutex);
|
||||
stopped = !this->active.size();
|
||||
}
|
||||
|
||||
if (stopped) {
|
||||
this->SetPlaybackState(Transport::PlaybackStopped);
|
||||
}
|
||||
|
||||
DEFER(&Transport::RemoveActive, player);
|
||||
}
|
||||
|
||||
void Transport::OnPlaybackError(Player* player) {
|
||||
this->SetNextCanStart(true);
|
||||
this->RaiseStreamEvent(Transport::StreamError, player);
|
||||
this->SetPlaybackState(Transport::PlaybackStopped);
|
||||
DEFER(&Transport::RemoveActive, player);
|
||||
|
@ -84,9 +84,10 @@ namespace musik { namespace core { namespace audio {
|
||||
|
||||
private:
|
||||
void StartWithPlayer(Player* player);
|
||||
void Stop(bool suppressEvent);
|
||||
void Stop(bool stopOutput);
|
||||
void RemoveActive(Player* player);
|
||||
void DeletePlayers(std::list<Player*> players);
|
||||
void SetNextCanStart(bool nextCanStart);
|
||||
|
||||
void RaiseStreamEvent(int type, Player* player);
|
||||
void SetPlaybackState(int state);
|
||||
@ -94,16 +95,15 @@ namespace musik { namespace core { namespace audio {
|
||||
void OnPlaybackStarted(Player* player);
|
||||
void OnPlaybackAlmostEnded(Player* player);
|
||||
void OnPlaybackFinished(Player* player);
|
||||
void OnPlaybackStopped(Player* player);
|
||||
void OnPlaybackError(Player* player);
|
||||
|
||||
private:
|
||||
double volume;
|
||||
PlaybackState state;
|
||||
|
||||
bool nextCanStart;
|
||||
boost::recursive_mutex stateMutex;
|
||||
Player* nextPlayer;
|
||||
std::shared_ptr<IOutput> output;
|
||||
musik::core::audio::OutputPtr output;
|
||||
std::list<Player*> active;
|
||||
|
||||
};
|
||||
|
@ -175,7 +175,7 @@ static inline std::string readKeyPress(int64 ch) {
|
||||
}
|
||||
|
||||
static inline int64 now() {
|
||||
return duration_cast<milliseconds>(
|
||||
return duration_cast<milliseconds>(
|
||||
system_clock::now().time_since_epoch()).count();
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user