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:
casey langen 2016-12-23 01:33:22 -08:00
parent 4b53439d68
commit a6372b3c92
36 changed files with 287 additions and 96 deletions

View File

@ -200,6 +200,10 @@ bool CddaDataStream::SetPosition(PositionType position) {
return true;
}
bool CddaDataStream::Seekable() {
return true;
}
PositionType CddaDataStream::Position() {
return (PositionType) this->position;
}

View File

@ -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();

View File

@ -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);

View File

@ -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;
};

View File

@ -141,6 +141,10 @@ void DirectSoundOut::SetVolume(double volume) {
}
}
double DirectSoundOut::GetVolume() {
return this->volume;
}
void DirectSoundOut::Stop() {
Lock lock(this->stateMutex);
this->ResetBuffers();

View File

@ -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();

View File

@ -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);

View File

@ -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;

View File

@ -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;

View File

@ -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;
};

View File

@ -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);
}

View File

@ -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;
};

View File

@ -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
};

View File

@ -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

View File

@ -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;

View File

@ -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;
};

View File

@ -104,6 +104,10 @@ void WasapiOut::SetVolume(double volume) {
}
}
double WasapiOut::GetVolume() {
return this->volume;
}
void WasapiOut::Stop() {
Lock lock(this->stateMutex);

View File

@ -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();

View File

@ -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);

View File

@ -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; }

View File

@ -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;

View File

@ -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;

View File

@ -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;
};

View File

@ -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;

View File

@ -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);
}
}
}

View File

@ -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;

View File

@ -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);

View File

@ -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:

View File

@ -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" />

View File

@ -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>

View File

@ -61,6 +61,10 @@ LocalFileStream::~LocalFileStream() {
}
}
bool LocalFileStream::Seekable() {
return true;
}
bool LocalFileStream::Open(const char *filename, unsigned int options) {
try {
this->uri = filename;

View File

@ -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();

View File

@ -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;

View File

@ -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;
};

View File

@ -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;

View File

@ -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);