mirror of
https://github.com/clangen/musikcube.git
synced 2025-03-14 04:18:36 +00:00
A bunch of changes, some of which are still untested:
1. Added IDecoder::GetDuration() methods. Implemented it for all decoders. 2. Modified NomadDecoder to properly resolve duration for mp3 files that do not have valid Xing headers (borrowed TagLib code) 3. Added IStream::GetDuration(), IPlayer::GetDuration(), and ITransport::GetDuration() 4. Stubbed out a CrossfaderTransport (untested, unimplemented) 5. Added the concept of "MixPoints" to Player, so clients can be notified when playback reaches certain points. (untested) 6. Started fleshing out a Crossfader implementation that uses a background thread and a MessageQueue (untested) 7. Modified TransportWindow to pull the decoder's duration, then fallback to the library duration if unavailable. 8. Added IDataStream::Seekable(), implemented in LocalFileStream.
This commit is contained in:
parent
4b53439d68
commit
a6372b3c92
@ -200,6 +200,10 @@ bool CddaDataStream::SetPosition(PositionType position) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CddaDataStream::Seekable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
PositionType CddaDataStream::Position() {
|
||||
return (PositionType) this->position;
|
||||
}
|
||||
|
@ -52,6 +52,7 @@ class CddaDataStream : public IDataStream {
|
||||
virtual PositionType Position();
|
||||
virtual bool Eof();
|
||||
virtual long Length();
|
||||
virtual bool Seekable();
|
||||
virtual const char* Type();
|
||||
virtual const char* Uri();
|
||||
|
||||
|
@ -40,7 +40,8 @@
|
||||
#define BYTES_PER_RAW_SAMPLE 2
|
||||
|
||||
CddaDecoder::CddaDecoder() {
|
||||
this->data = NULL;
|
||||
this->duration = -1.0f;
|
||||
this->data = nullptr;
|
||||
this->buffer = new BYTE[CDDA_BUFFER_SIZE];
|
||||
}
|
||||
|
||||
@ -54,6 +55,7 @@ void CddaDecoder::Destroy() {
|
||||
|
||||
bool CddaDecoder::Open(IDataStream* data) {
|
||||
this->data = (CddaDataStream *) data;
|
||||
this->duration = (double)(data->Length() / sizeof(int)) / 44100.0f;
|
||||
return (data != NULL);
|
||||
}
|
||||
|
||||
@ -67,6 +69,10 @@ double CddaDecoder::SetPosition(double seconds) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
double CddaDecoder::GetDuration() {
|
||||
return this->duration;
|
||||
}
|
||||
|
||||
bool CddaDecoder::GetBuffer(IBuffer *buffer) {
|
||||
int channels = data->GetChannelCount();
|
||||
buffer->SetSamples(CDDA_BUFFER_SIZE / BYTES_PER_RAW_SAMPLE);
|
||||
|
@ -53,9 +53,11 @@ public:
|
||||
bool Open(IDataStream* data);
|
||||
void Destroy();
|
||||
double SetPosition(double seconds);
|
||||
double GetDuration();
|
||||
bool GetBuffer(IBuffer *buffer);
|
||||
|
||||
private:
|
||||
CddaDataStream* data;
|
||||
double duration;
|
||||
BYTE* buffer;
|
||||
};
|
||||
|
@ -141,6 +141,10 @@ void DirectSoundOut::SetVolume(double volume) {
|
||||
}
|
||||
}
|
||||
|
||||
double DirectSoundOut::GetVolume() {
|
||||
return this->volume;
|
||||
}
|
||||
|
||||
void DirectSoundOut::Stop() {
|
||||
Lock lock(this->stateMutex);
|
||||
this->ResetBuffers();
|
||||
|
@ -62,6 +62,7 @@ class DirectSoundOut : public IOutput {
|
||||
virtual void Pause();
|
||||
virtual void Resume();
|
||||
virtual void SetVolume(double volume);
|
||||
virtual double GetVolume();
|
||||
virtual void Stop();
|
||||
virtual bool Play(IBuffer *buffer, IBufferProvider *provider);
|
||||
virtual double Latency();
|
||||
|
@ -54,7 +54,8 @@ FlacDecoder::FlacDecoder()
|
||||
, channels(0)
|
||||
, sampleRate(0)
|
||||
, bitsPerSample(0)
|
||||
, totalSamples(0) {
|
||||
, totalSamples(0)
|
||||
, duration(-1.0f) {
|
||||
this->decoder = FLAC__stream_decoder_new();
|
||||
}
|
||||
|
||||
@ -177,6 +178,7 @@ void FlacDecoder::FlacMetadata(
|
||||
fdec->sampleRate = metadata->data.stream_info.sample_rate;
|
||||
fdec->channels = metadata->data.stream_info.channels;
|
||||
fdec->bitsPerSample = metadata->data.stream_info.bits_per_sample;
|
||||
fdec->duration = (double)fdec->totalSamples / fdec->sampleRate;
|
||||
}
|
||||
}
|
||||
|
||||
@ -227,6 +229,10 @@ double FlacDecoder::SetPosition(double seconds) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
double FlacDecoder::GetDuration() {
|
||||
return this->duration;
|
||||
}
|
||||
|
||||
bool FlacDecoder::GetBuffer(IBuffer *buffer) {
|
||||
buffer->SetSampleRate(this->sampleRate);
|
||||
buffer->SetChannels(this->channels);
|
||||
|
@ -50,6 +50,7 @@ class FlacDecoder : public musik::core::sdk::IDecoder {
|
||||
virtual void Destroy();
|
||||
virtual double SetPosition(double seconds);
|
||||
virtual bool GetBuffer(IBuffer *buffer);
|
||||
virtual double GetDuration();
|
||||
virtual bool Open(musik::core::sdk::IDataStream *stream);
|
||||
|
||||
private:
|
||||
@ -102,6 +103,7 @@ class FlacDecoder : public musik::core::sdk::IDecoder {
|
||||
long sampleRate;
|
||||
uint64 totalSamples;
|
||||
int bitsPerSample;
|
||||
double duration;
|
||||
|
||||
float *outputBuffer;
|
||||
unsigned long outputBufferSize;
|
||||
|
@ -77,9 +77,10 @@ static int FindAacTrack(mp4ff_t *infile) {
|
||||
}
|
||||
|
||||
M4aDecoder::M4aDecoder() {
|
||||
this->decoder = NULL;
|
||||
this->decoderFile = NULL;
|
||||
this->decoder = nullptr;
|
||||
this->decoderFile = nullptr;
|
||||
memset(&decoderCallbacks, 0, sizeof(this->decoderCallbacks));
|
||||
this->duration = -1.0f;
|
||||
}
|
||||
|
||||
M4aDecoder::~M4aDecoder() {
|
||||
@ -113,6 +114,16 @@ bool M4aDecoder::Open(musik::core::sdk::IDataStream *stream) {
|
||||
mp4ff_get_decoder_config(
|
||||
decoderFile, audioTrackId, &buffer, &bufferSize);
|
||||
|
||||
const float scale =
|
||||
(float) mp4ff_time_scale(decoderFile, audioTrackId);
|
||||
|
||||
const float duration =
|
||||
(float) mp4ff_get_track_duration(decoderFile, audioTrackId);
|
||||
|
||||
if (scale > 0 && duration > 0) {
|
||||
this->duration = duration / scale;
|
||||
}
|
||||
|
||||
if (buffer) {
|
||||
if (NeAACDecInit2(
|
||||
decoder,
|
||||
@ -135,8 +146,7 @@ bool M4aDecoder::Open(musik::core::sdk::IDataStream *stream) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void M4aDecoder::Destroy(void)
|
||||
{
|
||||
void M4aDecoder::Destroy() {
|
||||
mp4ff_close(decoderFile);
|
||||
|
||||
if (decoder) {
|
||||
@ -160,6 +170,10 @@ double M4aDecoder::SetPosition(double seconds) {
|
||||
return seconds;
|
||||
}
|
||||
|
||||
double M4aDecoder::GetDuration() {
|
||||
return this->duration;
|
||||
}
|
||||
|
||||
bool M4aDecoder::GetBuffer(IBuffer* target) {
|
||||
if (this->decoderSampleId < 0) {
|
||||
return false;
|
||||
|
@ -46,6 +46,7 @@ class M4aDecoder : public musik::core::sdk::IDecoder {
|
||||
virtual void Destroy();
|
||||
virtual double SetPosition(double seconds);
|
||||
virtual bool GetBuffer(musik::core::sdk::IBuffer *buffer);
|
||||
virtual double GetDuration();
|
||||
virtual bool Open(musik::core::sdk::IDataStream *stream);
|
||||
|
||||
private:
|
||||
@ -57,4 +58,5 @@ class M4aDecoder : public musik::core::sdk::IDecoder {
|
||||
unsigned long sampleRate;
|
||||
unsigned char channelCount;
|
||||
long decoderSampleId;
|
||||
double duration;
|
||||
};
|
||||
|
@ -33,10 +33,13 @@
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "NomadDecoder.h"
|
||||
#include <vector>
|
||||
|
||||
using namespace musik::core::sdk;
|
||||
|
||||
#define DEFAULT_READ_SAMPLE_SIZE 2304 * 4
|
||||
#define SAMPLES_PER_FRAME_LAYER_1 1152
|
||||
#define SAMPLES_PER_FRAME_LAYER_2 576
|
||||
|
||||
static ssize_t nomadRead(void *datasource, void *buffer, size_t count) {
|
||||
IDataStream *stream = static_cast<IDataStream*>(datasource);
|
||||
@ -65,6 +68,7 @@ static int nomadClose(void *datasource) {
|
||||
}
|
||||
|
||||
NomadDecoder::NomadDecoder() {
|
||||
this->duration = -1.0f;
|
||||
this->nomadContext = nullptr;
|
||||
this->callbacks.read = &nomadRead;
|
||||
this->callbacks.lseek = &nomadSeek;
|
||||
@ -84,6 +88,10 @@ double NomadDecoder::SetPosition(double seconds) {
|
||||
return !nomad_time_seek(this->nomadContext, seconds) ? seconds : -1;
|
||||
}
|
||||
|
||||
double NomadDecoder::GetDuration() {
|
||||
return this->duration;
|
||||
}
|
||||
|
||||
bool NomadDecoder::GetBuffer(IBuffer *buffer) {
|
||||
buffer->SetSamples(DEFAULT_READ_SAMPLE_SIZE);
|
||||
|
||||
@ -99,6 +107,65 @@ bool NomadDecoder::GetBuffer(IBuffer *buffer) {
|
||||
}
|
||||
|
||||
bool NomadDecoder::Open(IDataStream *stream) {
|
||||
int result = nomad_open_callbacks(&this->nomadContext, stream, &this->callbacks);
|
||||
int tagLengthBytes = 0;
|
||||
|
||||
if (stream->Seekable()) {
|
||||
tagLengthBytes = this->GetId3v2HeaderLength(stream);
|
||||
stream->SetPosition(0);
|
||||
}
|
||||
|
||||
const int result = nomad_open_callbacks(&this->nomadContext, stream, &this->callbacks);
|
||||
|
||||
if (!result) {
|
||||
/* nomad's time estimation when there is no xing header seems wrong in many
|
||||
cases -- so we recalculate it here based on taglib's algorithm, which
|
||||
(empirically) seems much more accurate */
|
||||
if (!nomad_xing(this->nomadContext) && tagLengthBytes > 0) {
|
||||
const int bitrate = nomad_info(this->nomadContext)->avg_bitrate / 1000;
|
||||
const int streamLength = stream->Length() - tagLengthBytes;
|
||||
this->duration = (streamLength * 8.0 / bitrate) / 1000;
|
||||
}
|
||||
else {
|
||||
this->duration = nomad_info(this->nomadContext)->duration;
|
||||
}
|
||||
}
|
||||
|
||||
return result ? false : true;
|
||||
}
|
||||
|
||||
/* adapted from http://stackoverflow.com/a/3520427 */
|
||||
|
||||
#pragma pack(1)
|
||||
struct Mp3Header {
|
||||
char tag[3];
|
||||
unsigned char maj_ver;
|
||||
unsigned char min_ver;
|
||||
unsigned char flags;
|
||||
unsigned int size;
|
||||
};
|
||||
#pragma pack()
|
||||
|
||||
static int HEADER_LENGTH = sizeof(Mp3Header);
|
||||
|
||||
size_t NomadDecoder::GetId3v2HeaderLength(musik::core::sdk::IDataStream *stream) {
|
||||
Mp3Header hdr = { 0 };
|
||||
if (stream->Read(&hdr, HEADER_LENGTH) != HEADER_LENGTH) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (memcmp(hdr.tag, "ID3", 3) != 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// value has 4 bytes (8-bits each)
|
||||
// - discard most significant bit from each byte
|
||||
// - reverse byte order
|
||||
// - concatenate the 4 * 7-bit nibbles into a 24-bit size.
|
||||
unsigned char *parts = reinterpret_cast<unsigned char *>(&hdr.size);
|
||||
|
||||
return
|
||||
(parts[3] & 0x7F) |
|
||||
((parts[2] & 0x7F) << 7) |
|
||||
((parts[1] & 0x7F) << 14) |
|
||||
((parts[0] & 0x7F) << 21);
|
||||
}
|
@ -49,9 +49,13 @@ class NomadDecoder : public musik::core::sdk::IDecoder {
|
||||
virtual bool Open(musik::core::sdk::IDataStream *dataStream);
|
||||
virtual double SetPosition(double seconds);
|
||||
virtual bool GetBuffer(musik::core::sdk::IBuffer *buffer);
|
||||
virtual double GetDuration();
|
||||
virtual void Destroy();
|
||||
|
||||
private:
|
||||
size_t GetId3v2HeaderLength(musik::core::sdk::IDataStream *stream);
|
||||
|
||||
double duration;
|
||||
nomad_callbacks callbacks;
|
||||
nomad *nomadContext;
|
||||
};
|
||||
|
@ -30,7 +30,6 @@ enum {
|
||||
ID3_ENCODING_UTF_16 = 0x01,
|
||||
ID3_ENCODING_UTF_16_BE = 0x02,
|
||||
ID3_ENCODING_UTF_8 = 0x03,
|
||||
|
||||
ID3_ENCODING_MAX = 0x03
|
||||
};
|
||||
|
||||
|
@ -21,53 +21,6 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* flags for id3_read_tags */
|
||||
#define ID3_V1 (1 << 0)
|
||||
#define ID3_V2 (1 << 1)
|
||||
|
||||
enum id3_key {
|
||||
ID3_ARTIST,
|
||||
ID3_ALBUM,
|
||||
ID3_TITLE,
|
||||
ID3_DATE,
|
||||
ID3_ORIGINALDATE,
|
||||
ID3_GENRE,
|
||||
ID3_DISC,
|
||||
ID3_TRACK,
|
||||
ID3_ALBUMARTIST,
|
||||
ID3_ARTISTSORT,
|
||||
ID3_ALBUMARTISTSORT,
|
||||
ID3_ALBUMSORT,
|
||||
ID3_COMPILATION,
|
||||
ID3_RG_TRACK_GAIN,
|
||||
ID3_RG_TRACK_PEAK,
|
||||
ID3_RG_ALBUM_GAIN,
|
||||
ID3_RG_ALBUM_PEAK,
|
||||
ID3_COMPOSER,
|
||||
ID3_CONDUCTOR,
|
||||
ID3_LYRICIST,
|
||||
ID3_REMIXER,
|
||||
ID3_LABEL,
|
||||
ID3_PUBLISHER,
|
||||
ID3_SUBTITLE,
|
||||
ID3_COMMENT,
|
||||
ID3_MUSICBRAINZ_TRACKID,
|
||||
ID3_MEDIA,
|
||||
ID3_BPM,
|
||||
|
||||
NUM_ID3_KEYS
|
||||
};
|
||||
|
||||
struct id3tag {
|
||||
char v1[128];
|
||||
char *v2[NUM_ID3_KEYS];
|
||||
|
||||
unsigned int has_v1 : 1;
|
||||
unsigned int has_v2 : 1;
|
||||
};
|
||||
|
||||
extern const char * const id3_key_names[NUM_ID3_KEYS];
|
||||
|
||||
int id3_tag_size(const char *buf, int buf_size);
|
||||
|
||||
#endif
|
||||
|
@ -38,6 +38,7 @@
|
||||
#define OGG_MAX_SAMPLES 1024
|
||||
|
||||
OggDecoder::OggDecoder() {
|
||||
this->duration = -1.0f;
|
||||
this->oggCallbacks.read_func = &OggRead;
|
||||
this->oggCallbacks.seek_func = &OggSeek;
|
||||
this->oggCallbacks.tell_func = &OggTell;
|
||||
@ -101,6 +102,8 @@ bool OggDecoder::Open(musik::core::sdk::IDataStream *fileStream) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this->duration = ov_time_total(&this->oggFile, -1);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -119,7 +122,9 @@ double OggDecoder::SetPosition(double second) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
#include <iostream>
|
||||
double OggDecoder::GetDuration() {
|
||||
return this->duration;
|
||||
}
|
||||
|
||||
bool OggDecoder::GetBuffer(IBuffer *buffer) {
|
||||
int bitstream;
|
||||
|
@ -40,28 +40,26 @@
|
||||
|
||||
using namespace musik::core::sdk;
|
||||
|
||||
class OggDecoder : public IDecoder
|
||||
{
|
||||
class OggDecoder : public IDecoder {
|
||||
public:
|
||||
OggDecoder();
|
||||
virtual ~OggDecoder();
|
||||
|
||||
public:
|
||||
OggDecoder();
|
||||
~OggDecoder();
|
||||
virtual void Destroy();
|
||||
virtual double SetPosition(double second);
|
||||
virtual bool GetBuffer(IBuffer *buffer);
|
||||
virtual double GetDuration();
|
||||
virtual bool Open(musik::core::sdk::IDataStream *fileStream);
|
||||
|
||||
public:
|
||||
virtual void Destroy();
|
||||
virtual double SetPosition(double second);
|
||||
virtual bool GetBuffer(IBuffer *buffer);
|
||||
virtual bool Open(musik::core::sdk::IDataStream *fileStream);
|
||||
/* libvorbis callbacks */
|
||||
static size_t OggRead(void *buffer, size_t nofParts, size_t partSize, void *datasource);
|
||||
static int OggSeek(void *datasource, ogg_int64_t offset, int whence);
|
||||
static long OggTell(void *datasource);
|
||||
static int OggClose(void *datasource);
|
||||
|
||||
public:
|
||||
/* libvorbis callbacks */
|
||||
static size_t OggRead(void *buffer, size_t nofParts, size_t partSize, void *datasource);
|
||||
static int OggSeek(void *datasource, ogg_int64_t offset, int whence);
|
||||
static long OggTell(void *datasource);
|
||||
static int OggClose(void *datasource);
|
||||
|
||||
protected:
|
||||
musik::core::sdk::IDataStream *fileStream;
|
||||
OggVorbis_File oggFile;
|
||||
ov_callbacks oggCallbacks;
|
||||
private:
|
||||
musik::core::sdk::IDataStream *fileStream;
|
||||
OggVorbis_File oggFile;
|
||||
ov_callbacks oggCallbacks;
|
||||
double duration;
|
||||
};
|
||||
|
@ -104,6 +104,10 @@ void WasapiOut::SetVolume(double volume) {
|
||||
}
|
||||
}
|
||||
|
||||
double WasapiOut::GetVolume() {
|
||||
return this->volume;
|
||||
}
|
||||
|
||||
void WasapiOut::Stop() {
|
||||
Lock lock(this->stateMutex);
|
||||
|
||||
|
@ -62,6 +62,7 @@ class WasapiOut : public IOutput {
|
||||
virtual void Pause();
|
||||
virtual void Resume();
|
||||
virtual void SetVolume(double volume);
|
||||
virtual double GetVolume();
|
||||
virtual void Stop();
|
||||
virtual bool Play(IBuffer *buffer, IBufferProvider *provider);
|
||||
virtual double Latency();
|
||||
|
@ -115,6 +115,10 @@ void WaveOut::SetVolume(double volume) {
|
||||
this->currentVolume = volume;
|
||||
}
|
||||
|
||||
double WaveOut::GetVolume() {
|
||||
return this->currentVolume;
|
||||
}
|
||||
|
||||
void WaveOut::Stop() {
|
||||
LockT lock(this->outputDeviceMutex);
|
||||
|
||||
|
@ -59,6 +59,7 @@ class WaveOut : public IOutput {
|
||||
virtual void Pause();
|
||||
virtual void Resume();
|
||||
virtual void SetVolume(double volume);
|
||||
virtual double GetVolume();
|
||||
virtual void Stop();
|
||||
virtual bool Play(IBuffer *buffer, IBufferProvider *provider);
|
||||
virtual double Latency() { return 0.0; }
|
||||
|
@ -44,7 +44,7 @@
|
||||
using namespace musik::core::audio;
|
||||
using namespace musik::core::sdk;
|
||||
|
||||
static std::string TAG = "Transport";
|
||||
static std::string TAG = "GaplessTransport";
|
||||
|
||||
#define RESET_NEXT_PLAYER(instance) \
|
||||
if (instance->nextPlayer) { \
|
||||
@ -160,8 +160,6 @@ void GaplessTransport::StopInternal(
|
||||
otherwise, we let them finish naturally; RemoveActive() will take
|
||||
care of disposing of them */
|
||||
if (stopOutput) {
|
||||
std::list<Player*> toDelete;
|
||||
|
||||
{
|
||||
LockT lock(this->stateMutex);
|
||||
|
||||
@ -224,7 +222,7 @@ double GaplessTransport::Position() {
|
||||
LockT lock(this->stateMutex);
|
||||
|
||||
if (this->activePlayer) {
|
||||
return this->activePlayer->Position();
|
||||
return this->activePlayer->GetPosition();
|
||||
}
|
||||
|
||||
return 0;
|
||||
@ -244,6 +242,11 @@ void GaplessTransport::SetPosition(double seconds) {
|
||||
}
|
||||
}
|
||||
|
||||
double GaplessTransport::GetDuration() {
|
||||
LockT lock(this->stateMutex);
|
||||
return this->activePlayer ? this->activePlayer->GetDuration() : -1.0f;
|
||||
}
|
||||
|
||||
bool GaplessTransport::IsMuted() {
|
||||
return this->muted;
|
||||
}
|
||||
@ -347,6 +350,10 @@ void GaplessTransport::OnPlayerDestroying(Player *player) {
|
||||
}
|
||||
}
|
||||
|
||||
void GaplessTransport::OnPlayerMixPoint(Player* player, int id, double time) {
|
||||
/* we don't have any mixpoints. */
|
||||
}
|
||||
|
||||
void GaplessTransport::SetPlaybackState(int state) {
|
||||
bool changed = false;
|
||||
|
||||
|
@ -47,7 +47,11 @@
|
||||
|
||||
namespace musik { namespace core { namespace audio {
|
||||
|
||||
class GaplessTransport : public ITransport, private Player::PlayerEventListener, public sigslot::has_slots<> {
|
||||
class GaplessTransport :
|
||||
public ITransport,
|
||||
private Player::PlayerEventListener,
|
||||
public sigslot::has_slots<>
|
||||
{
|
||||
public:
|
||||
GaplessTransport();
|
||||
virtual ~GaplessTransport();
|
||||
@ -68,6 +72,8 @@ namespace musik { namespace core { namespace audio {
|
||||
virtual bool IsMuted();
|
||||
virtual void SetMuted(bool muted);
|
||||
|
||||
virtual double GetDuration();
|
||||
|
||||
virtual void ReloadOutput();
|
||||
|
||||
virtual musik::core::sdk::PlaybackState GetPlaybackState();
|
||||
@ -92,6 +98,7 @@ namespace musik { namespace core { namespace audio {
|
||||
virtual void OnPlayerFinished(Player* player);
|
||||
virtual void OnPlayerError(Player* player);
|
||||
virtual void OnPlayerDestroying(Player* player);
|
||||
virtual void OnPlayerMixPoint(Player* player, int id, double time);
|
||||
|
||||
musik::core::sdk::PlaybackState state;
|
||||
std::recursive_mutex stateMutex;
|
||||
|
@ -55,6 +55,7 @@ namespace musik { namespace core { namespace audio {
|
||||
virtual BufferPtr GetNextProcessedOutputBuffer() = 0;
|
||||
virtual void OnBufferProcessedByPlayer(BufferPtr buffer) = 0;
|
||||
virtual double SetPosition(double seconds) = 0;
|
||||
virtual double GetDuration() = 0;
|
||||
virtual bool OpenStream(std::string uri) = 0;
|
||||
};
|
||||
|
||||
|
@ -62,6 +62,8 @@ namespace musik { namespace core { namespace audio {
|
||||
virtual double Volume() = 0;
|
||||
virtual void SetVolume(double volume) = 0;
|
||||
|
||||
virtual double GetDuration() = 0;
|
||||
|
||||
virtual bool IsMuted() = 0;
|
||||
virtual void SetMuted(bool muted) = 0;
|
||||
|
||||
|
@ -158,14 +158,29 @@ void Player::Destroy() {
|
||||
}
|
||||
}
|
||||
|
||||
double Player::Position() {
|
||||
double Player::GetPosition() {
|
||||
std::unique_lock<std::mutex> lock(this->positionMutex);
|
||||
return std::max(0.0, round(this->currentPosition - this->output->Latency()));
|
||||
}
|
||||
|
||||
double Player::GetDuration() {
|
||||
return this->stream ? this->stream->GetDuration() : -1.0f;
|
||||
}
|
||||
|
||||
void Player::SetPosition(double seconds) {
|
||||
std::unique_lock<std::mutex> lock(this->positionMutex);
|
||||
std::unique_lock<std::mutex> positionLock(this->positionMutex);
|
||||
this->setPosition = std::max(0.0, seconds);
|
||||
|
||||
/* reset our mix points on seek! that way we'll notify again if necessary */
|
||||
std::unique_lock<std::mutex> queueLock(this->queueMutex);
|
||||
this->pendingMixPoints.splice(
|
||||
this->pendingMixPoints.begin(),
|
||||
this->processedMixPoints);
|
||||
}
|
||||
|
||||
void Player::AddMixPoint(int id, double time) {
|
||||
std::unique_lock<std::mutex> queueLock(this->queueMutex);
|
||||
this->pendingMixPoints.push_back(std::make_shared<MixPoint>(id, time));
|
||||
}
|
||||
|
||||
int Player::State() {
|
||||
@ -431,6 +446,8 @@ void Player::OnBufferProcessed(IBuffer *buffer) {
|
||||
vis::PcmVisualizer()->Write(buffer);
|
||||
}
|
||||
|
||||
MixPointList mixPointsHit;
|
||||
|
||||
/* free the buffer */
|
||||
|
||||
{
|
||||
@ -461,6 +478,23 @@ void Player::OnBufferProcessed(IBuffer *buffer) {
|
||||
this->currentPosition = this->lockedBuffers.front()->Position();
|
||||
}
|
||||
|
||||
/* did we hit any pending mixpoints? if so add them to our set and
|
||||
move them to the processed set. we'll notify once out of the
|
||||
critical section. */
|
||||
if (this->pendingMixPoints.size() > 0) {
|
||||
double adjustedPosition = this->GetPosition();
|
||||
|
||||
auto it = this->pendingMixPoints.begin();
|
||||
while (it != this->pendingMixPoints.end()) {
|
||||
if (adjustedPosition >= (*it)->time) {
|
||||
mixPointsHit.push_back(*it);
|
||||
this->processedMixPoints.push_back(*it);
|
||||
it = this->pendingMixPoints.erase(it);
|
||||
}
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
/* if the output device's internal buffers are full, it will stop
|
||||
accepting new samples. now that a buffer has been processed, we can
|
||||
try to enqueue another sample. the thread loop blocks on this condition */
|
||||
@ -477,13 +511,19 @@ void Player::OnBufferProcessed(IBuffer *buffer) {
|
||||
}
|
||||
}
|
||||
|
||||
/* we notify our listeners that we've started playing only after the first
|
||||
buffer has been consumed. this is because sometimes we precache buffers
|
||||
and send them to the output before they are actually processed by the
|
||||
output device */
|
||||
if (started) {
|
||||
if (!this->Exited() && this->listener) {
|
||||
|
||||
if (!this->Exited() && this->listener) {
|
||||
/* we notify our listeners that we've started playing only after the first
|
||||
buffer has been consumed. this is because sometimes we precache buffers
|
||||
and send them to the output before they are actually processed by the
|
||||
output device */
|
||||
if (started) {
|
||||
this->listener->OnPlayerStarted(this);
|
||||
}
|
||||
|
||||
auto it = mixPointsHit.begin();
|
||||
while (it != mixPointsHit.end()) {
|
||||
this->listener->OnPlayerMixPoint(this, (*it)->id, (*it)->time);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -57,6 +57,7 @@ namespace musik { namespace core { namespace audio {
|
||||
virtual void OnPlayerFinished(Player *player) = 0;
|
||||
virtual void OnPlayerError(Player *player) = 0;
|
||||
virtual void OnPlayerDestroying(Player *player) = 0;
|
||||
virtual void OnPlayerMixPoint(Player *player, int id, double time) = 0;
|
||||
};
|
||||
|
||||
static Player* Create(
|
||||
@ -69,8 +70,11 @@ namespace musik { namespace core { namespace audio {
|
||||
void Play();
|
||||
void Destroy();
|
||||
|
||||
double Position();
|
||||
double GetPosition();
|
||||
void SetPosition(double seconds);
|
||||
double GetDuration();
|
||||
|
||||
void AddMixPoint(int id, double time);
|
||||
|
||||
std::string GetUrl() const { return this->url; }
|
||||
|
||||
@ -82,6 +86,19 @@ namespace musik { namespace core { namespace audio {
|
||||
void ReleaseAllBuffers();
|
||||
|
||||
private:
|
||||
struct MixPoint {
|
||||
MixPoint(int id, double time) {
|
||||
this->id = id;
|
||||
this->time = time;
|
||||
}
|
||||
|
||||
int id;
|
||||
double time;
|
||||
};
|
||||
|
||||
using MixPointPtr = std::shared_ptr<MixPoint>;
|
||||
using MixPointList = std::list<MixPointPtr>;
|
||||
|
||||
friend void playerThreadLoop(Player* player);
|
||||
|
||||
Player(
|
||||
@ -107,6 +124,9 @@ namespace musik { namespace core { namespace audio {
|
||||
BufferList lockedBuffers;
|
||||
PlayerEventListener* listener;
|
||||
|
||||
MixPointList pendingMixPoints;
|
||||
MixPointList processedMixPoints;
|
||||
|
||||
std::string url;
|
||||
|
||||
BufferList prebufferQueue;
|
||||
|
@ -99,6 +99,10 @@ double Stream::SetPosition(double requestedSeconds) {
|
||||
return actualSeconds;
|
||||
}
|
||||
|
||||
double Stream::GetDuration() {
|
||||
return this->decoder ? this->decoder->GetDuration() : -1.0f;
|
||||
}
|
||||
|
||||
bool Stream::OpenStream(std::string uri) {
|
||||
musik::debug::info(TAG, "opening " + uri);
|
||||
|
||||
|
@ -68,6 +68,7 @@ namespace musik { namespace core { namespace audio {
|
||||
virtual BufferPtr GetNextProcessedOutputBuffer();
|
||||
virtual void OnBufferProcessedByPlayer(BufferPtr buffer);
|
||||
virtual double SetPosition(double seconds);
|
||||
virtual double GetDuration();
|
||||
virtual bool OpenStream(std::string uri);
|
||||
|
||||
private:
|
||||
|
@ -84,6 +84,8 @@
|
||||
</ClCompile>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="audio\Crossfader.cpp" />
|
||||
<ClCompile Include="audio\CrossfadeTransport.cpp" />
|
||||
<ClCompile Include="audio\GaplessTransport.cpp" />
|
||||
<ClCompile Include="audio\Outputs.cpp" />
|
||||
<ClCompile Include="audio\Streams.cpp" />
|
||||
@ -118,6 +120,8 @@
|
||||
<ClCompile Include="support\Preferences.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="audio\Crossfader.h" />
|
||||
<ClInclude Include="audio\CrossfadeTransport.h" />
|
||||
<ClInclude Include="audio\GaplessTransport.h" />
|
||||
<ClInclude Include="audio\IStream.h" />
|
||||
<ClInclude Include="audio\ITransport.h" />
|
||||
|
@ -127,6 +127,12 @@
|
||||
<ClCompile Include="runtime\MessageQueue.cpp">
|
||||
<Filter>src\runtime</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="audio\CrossfadeTransport.cpp">
|
||||
<Filter>src\audio</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="audio\Crossfader.cpp">
|
||||
<Filter>src\audio</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="pch.hpp">
|
||||
@ -309,5 +315,11 @@
|
||||
<ClInclude Include="runtime\IMessageTarget.h">
|
||||
<Filter>src\runtime</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="audio\CrossfadeTransport.h">
|
||||
<Filter>src\audio</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="audio\Crossfader.h">
|
||||
<Filter>src\audio</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
</Project>
|
@ -61,6 +61,10 @@ LocalFileStream::~LocalFileStream() {
|
||||
}
|
||||
}
|
||||
|
||||
bool LocalFileStream::Seekable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LocalFileStream::Open(const char *filename, unsigned int options) {
|
||||
try {
|
||||
this->uri = filename;
|
||||
|
@ -54,6 +54,7 @@ namespace musik { namespace core { namespace io {
|
||||
virtual PositionType Position();
|
||||
virtual bool Eof();
|
||||
virtual long Length();
|
||||
virtual bool Seekable();
|
||||
virtual const char* Type();
|
||||
virtual const char* Uri();
|
||||
|
||||
|
@ -48,6 +48,7 @@ namespace musik { namespace core { namespace sdk {
|
||||
virtual PositionType Read(void *buffer, PositionType readBytes) = 0;
|
||||
virtual bool SetPosition(PositionType position) = 0;
|
||||
virtual PositionType Position() = 0;
|
||||
virtual bool Seekable() = 0;
|
||||
virtual bool Eof() = 0;
|
||||
virtual long Length() = 0;
|
||||
virtual const char* Type() = 0;
|
||||
|
@ -45,6 +45,7 @@ namespace musik { namespace core { namespace sdk {
|
||||
virtual void Destroy() = 0;
|
||||
virtual double SetPosition(double seconds) = 0;
|
||||
virtual bool GetBuffer(IBuffer *buffer) = 0;
|
||||
virtual double GetDuration() = 0;
|
||||
virtual bool Open(IDataStream *stream) = 0;
|
||||
};
|
||||
|
||||
|
@ -47,6 +47,7 @@ namespace musik { namespace core { namespace sdk {
|
||||
virtual void Pause() = 0;
|
||||
virtual void Resume() = 0;
|
||||
virtual void SetVolume(double volume) = 0;
|
||||
virtual double GetVolume() = 0;
|
||||
virtual void Stop() = 0;
|
||||
virtual bool Play(IBuffer *buffer, IBufferProvider *provider) = 0;
|
||||
virtual double Latency() = 0;
|
||||
|
@ -379,7 +379,7 @@ void TransportWindow::Update(TimeMode timeMode) {
|
||||
|
||||
/* playing SONG TITLE from ALBUM NAME */
|
||||
|
||||
std::string duration = "0";
|
||||
int secondsTotal = 0;
|
||||
|
||||
if (stopped) {
|
||||
ON(c, disabled);
|
||||
@ -387,19 +387,28 @@ void TransportWindow::Update(TimeMode timeMode) {
|
||||
OFF(c, disabled);
|
||||
}
|
||||
else {
|
||||
secondsTotal = transport.GetDuration();
|
||||
|
||||
std::string title, album, artist;
|
||||
|
||||
if (this->currentTrack) {
|
||||
title = this->currentTrack->GetValue(constants::Track::TITLE);
|
||||
album = this->currentTrack->GetValue(constants::Track::ALBUM);
|
||||
artist = this->currentTrack->GetValue(constants::Track::ARTIST);
|
||||
duration = this->currentTrack->GetValue(constants::Track::DURATION);
|
||||
|
||||
if (secondsTotal <= 0) {
|
||||
std::string duration =
|
||||
this->currentTrack->GetValue(constants::Track::DURATION);
|
||||
|
||||
if (duration.size()) {
|
||||
secondsTotal = boost::lexical_cast<int>(duration);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
title = title.size() ? title : "[song]";
|
||||
album = album.size() ? album : "[album]";
|
||||
artist = artist.size() ? artist : "[artist]";
|
||||
duration = duration.size() ? duration : "0";
|
||||
|
||||
writePlayingFormat(
|
||||
c,
|
||||
@ -496,8 +505,6 @@ void TransportWindow::Update(TimeMode timeMode) {
|
||||
secondsCurrent = (int) round(this->lastTime);
|
||||
}
|
||||
|
||||
int secondsTotal = boost::lexical_cast<int>(duration);
|
||||
|
||||
const std::string currentTime = duration::Duration(std::min(secondsCurrent, secondsTotal));
|
||||
const std::string totalTime = duration::Duration(secondsTotal);
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user