diff options
Diffstat (limited to 'Src/replicant/nswasabi/PlaybackBase2.cpp')
-rw-r--r-- | Src/replicant/nswasabi/PlaybackBase2.cpp | 490 |
1 files changed, 490 insertions, 0 deletions
diff --git a/Src/replicant/nswasabi/PlaybackBase2.cpp b/Src/replicant/nswasabi/PlaybackBase2.cpp new file mode 100644 index 00000000..4a924e23 --- /dev/null +++ b/Src/replicant/nswasabi/PlaybackBase2.cpp @@ -0,0 +1,490 @@ +#include "PlaybackBase2.h" + +PlaybackImpl::PlaybackImpl() +{ + playback=0; + playback_parameters=0; +} + +PlaybackImpl::~PlaybackImpl() +{ + // TODO: decide if we need playback->Release() or not + if (playback_parameters) + playback_parameters->Release(); +} + +void PlaybackImpl::Connect(PlaybackBase2 *playback, ifc_playback_parameters *playback_parameters) +{ + this->playback = playback; + // TODO: decide if we need playback->Retain() or not + + this->playback_parameters = playback_parameters; + if (playback_parameters) + playback_parameters->Retain(); + +} + +/* ---------- */ +PlaybackBase2::PlaybackBase2() +{ + out=0; + implementation=0; + filelocker=0; + paused=false; + last_position=0; + output_pointers=0; + exact_length=false; + memset(¶meters, 0, sizeof(parameters)); +} + +PlaybackBase2::~PlaybackBase2() +{ + /* out should have hopefully been close already. just in case */ + if (out) + out->Release(); + out=0; + + if (filelocker) + filelocker->Release(); + delete implementation; + free(output_pointers); + +} + +ns_error_t PlaybackBase2::Initialize(api_service *service_manager, PlaybackImpl *implementation, nx_uri_t filename, ifc_player *player) +{ + service_manager->GetService(&filelocker); + + this->implementation = implementation; + ns_error_t ret = PlaybackBase::Initialize(filename, player); + if (ret != NErr_Success) + return ret; + + implementation->Connect(this, secondary_parameters); + + this->ifc_playback::Retain(); /* the thread needs to hold a reference to this object so that it doesn't disappear out from under us */ + NXThreadCreate(&playback_thread, PlayerThreadFunction, this); + return NErr_Success; +} + +nx_thread_return_t PlaybackBase2::PlayerThreadFunction(nx_thread_parameter_t param) +{ + PlaybackBase2 *playback = (PlaybackBase2 *)param; + NXThreadCurrentSetPriority(NX_THREAD_PRIORITY_PLAYBACK); + nx_thread_return_t ret = playback->DecodeLoop(); + playback->ifc_playback::Release(); /* give up the reference that was acquired before spawning the thread */ + return ret; +} + +int PlaybackBase2::Init() +{ + if (filelocker) + filelocker->WaitForReadInterruptable(filename, this); + + ns_error_t ret = implementation->Open(filename); + if (ret != NErr_Success) + return ret; + + ifc_metadata *metadata; + if (implementation->GetMetadata(&metadata) == NErr_Success) + { + player->SetMetadata(metadata); + metadata->Release(); + } + else + player->SetMetadata(0); + + player->SetSeekable(implementation->IsSeekable()?1:0); + + double length; + ret = implementation->GetLength(&length, &exact_length); + if (ret == NErr_Success) + player->SetLength(length); + + return NErr_Success; +} + +nx_thread_return_t PlaybackBase2::DecodeLoop() +{ + player->OnLoaded(filename); + + int ret = Init(); + if (ret != NErr_Success) + { + implementation->Close(); + if (filelocker) + filelocker->UnlockFile(filename); + player->OnError(ret); + return 0; + } + + player->OnReady(); + + /* wait for Play (or Stop to abort) */ + for (;;) + { + ns_error_t ret = Wake(WAKE_PLAY|WAKE_STOP|WAKE_INTERRUPT); + if (ret == WAKE_PLAY) + { + break; + } + else if (ret == WAKE_STOP) + { + player->OnStopped(); + goto cleanup; + } + else if (ret == WAKE_INTERRUPT) + { + ns_error_t ret = Internal_Interrupt(); + if (ret != NErr_Success) + { + implementation->Close(); + player->OnError(ret); + goto cleanup; + } + } + } + + /* at this point, we know that PLAY is on */ + for (;;) + { + int ret = Check(WAKE_STOP|WAKE_PAUSE|WAKE_INTERRUPT); + if (ret == WAKE_PAUSE) + { + if (out) + out->Pause(1); + paused=true; + continue; /* continue in case there's another wake reason */ + } + else if (ret== WAKE_UNPAUSE) + { + if (out) + out->Pause(0); + paused=false; + continue; /* continue in case there's another wake reason */ + } + else if (ret == WAKE_STOP) + { + if (out) + { + out->Stop(); + out->Release(); + out=0; + } + player->OnStopped(); + goto cleanup; + } + else if (ret == WAKE_INTERRUPT) + { + ns_error_t ret = Internal_Interrupt(); + if (ret != NErr_Success) + { + implementation->Close(); + player->OnError(ret); + goto cleanup; + } + continue; + } + + Agave_Seek *seek = PlaybackBase::GetSeek(); + if (seek) + { + ns_error_t seek_error; + double new_position; + ns_error_t ret = implementation->Seek(seek, &seek_error, &new_position); + if (ret != NErr_Success) + { + player->OnError(ret); + goto cleanup; + } + if (out) + out->Flush(new_position); + player->OnSeekComplete(seek_error, new_position); + PlaybackBase::FreeSeek(seek); + } + + ret = implementation->DecodeStep(); + if (ret == NErr_EndOfFile) + { + if (out) + out->Done(); + + PlaybackBase::OnStopPlaying(); + player->OnEndOfFile(); + + ret = WaitForClose(); + if (out) + out->Release(); + out=0; + + if (ret != NErr_True) + goto cleanup; + } + else if (ret != NErr_Success) + { + if (out) + { + out->Done(); + out->Release(); + out=0; + } + if (ret != NErr_False) + player->OnError(NErr_Error); // TODO: find better error code + goto cleanup; + } + else + { + if (!exact_length) + { + double length; + ret = implementation->GetLength(&length, &exact_length); + if (ret == NErr_Success) + player->SetLength(length); + } + } + } + +cleanup: + implementation->Close(); + + if (filelocker) + filelocker->UnlockFile(filename); + return 0; +} + +ns_error_t PlaybackBase2::WaitForClose() +{ + if (!out) + { + player->OnClosed(); + return NErr_False; + } + else for (;;) + { + int ret = Wait(10, WAKE_PLAY|WAKE_KILL|WAKE_STOP); + if (ret == WAKE_KILL) + { + player->OnClosed(); + return NErr_False; + } + else if (ret == WAKE_PLAY) + { + return NErr_True; + } + else if (ret == WAKE_STOP) + { + player->OnStopped(); + return NErr_False; + } + else + { + if (out->Playing() == NErr_True) + player->SetPosition(last_position - out->Latency()); + else + { + player->SetPosition(last_position); + player->OnClosed(); + return NErr_False; + } + } + } +} + +ns_error_t PlaybackBase2::OpenOutput(const ifc_audioout::Parameters *_parameters) +{ + // if out is already set, it means that there was a change in parameters, so we'll start a new stream + if (out) + { + // check to see that the parameters actually changed + if (!memcmp(¶meters, _parameters, sizeof(parameters))) + return NErr_Success; + + out->Done(); + out=0; + } + + parameters = *_parameters; + + free(output_pointers); + output_pointers = (const uint8_t **)malloc(parameters.audio.number_of_channels * sizeof(const uint8_t *)); + if (!output_pointers) + return NErr_OutOfMemory; + + ns_error_t ret = output_service->AudioOpen(¶meters, player, secondary_parameters, &out); + if (ret != NErr_Success) + { + player->OnError(ret); + return ret; + } + + if (paused) + out->Pause(1); + else + out->Pause(0); + + + return NErr_True; +} + +int PlaybackBase2::OutputNonInterleaved(const void *decode_buffer, size_t decoded, double start_position) +{ + int ret; + size_t frames_written=0; + const uint8_t **buffer = (const uint8_t **)decode_buffer; + + for (size_t c=0;c<parameters.audio.number_of_channels;c++) + { + output_pointers[c] = buffer[c]; + } + + while (decoded) + { + size_t to_write = out->CanWrite(); + if (to_write) + { + if (decoded < to_write) + to_write = decoded; + + ret = out->Output(output_pointers, to_write); + if (ret != NErr_Success) + { + out->Release(); + out=0; + return ret; + } + + decoded -= to_write; + for (size_t c=0;c<parameters.audio.number_of_channels;c++) + { + output_pointers[c] += to_write/parameters.audio.number_of_channels; + } + frames_written += to_write/parameters.audio.number_of_channels; + player->SetPosition(start_position + (double)frames_written/parameters.audio.sample_rate - out->Latency()); + } + else + { + ns_error_t ret = OutputWait(); + if (ret != NErr_Success) + return ret; + } + } + return NErr_True; +} + +int PlaybackBase2::Output(const void *decode_buffer, size_t decoded, double start_position) +{ + int ret; + size_t frames_written=0; + const uint8_t *decode8 = (const uint8_t *)decode_buffer; + size_t buffer_position=0; + while (decoded) + { + size_t to_write = out->CanWrite(); + if (to_write) + { + if (decoded < to_write) + to_write = decoded; + + ret = out->Output(&decode8[buffer_position], to_write); + if (ret != NErr_Success) + { + out->Release(); + out=0; + return ret; + } + + decoded -= to_write; + buffer_position += to_write; + frames_written += to_write/parameters.audio.number_of_channels; + player->SetPosition(start_position + (double)frames_written/parameters.audio.sample_rate - out->Latency()); + } + else + { + ns_error_t ret = OutputWait(); + if (ret != NErr_Success) + return ret; + } + } + return NErr_True; +} + +ns_error_t PlaybackBase2::OutputWait() +{ + if (paused) + { + /* if we're paused, we need to sit and wait until we're eiter unpaused or stopped */ + for (;;) + { + int ret = Wake(WAKE_STOP|WAKE_PAUSE|WAKE_INTERRUPT); + if (ret == WAKE_STOP) + { + out->Stop(); + out->Release(); + out=0; + player->OnStopped(); + return NErr_False; + } + else if (ret == WAKE_UNPAUSE) + { + out->Pause(0); + paused=false; + break; + } + else if (ret == WAKE_PAUSE) + { + out->Pause(1); + paused=true; + } + else if (PlaybackBase::PendingSeek()) + { + return NErr_True; + } + else if (ret == WAKE_INTERRUPT) + { + ns_error_t ret = Internal_Interrupt(); + if (ret != NErr_Success) + return ret; + } + } + } + else + { + int ret = Wait(10, WAKE_STOP); + if (ret == WAKE_STOP) + { + out->Stop(); + out->Release(); + out=0; + player->OnStopped(); + return NErr_False; + } + } + return NErr_Success; +} + +ns_error_t PlaybackBase2::Internal_Interrupt() +{ + Agave_Seek resume_information; + implementation->Interrupt(&resume_information); + ns_error_t ret = filelocker->UnlockFile(filename); + if (ret != NErr_Success) + { + implementation->Close(); + return ret; + } + PlaybackBase::OnInterrupted(); + if (filelocker) + filelocker->WaitForReadInterruptable(filename, this); + ret = implementation->Resume(&resume_information); + + + if (ret != NErr_Success) + return ret; + ifc_metadata *metadata; + if (implementation->GetMetadata(&metadata) == NErr_Success) + { + player->SetMetadata(metadata); + metadata->Release(); + } + return NErr_Success; +}
\ No newline at end of file |