#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;cCanWrite(); 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;cSetPosition(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; }