diff --git a/cmake/copy_resources.cmake b/cmake/copy_resources.cmake index e0046ff..600da5f 100644 --- a/cmake/copy_resources.cmake +++ b/cmake/copy_resources.cmake @@ -45,14 +45,13 @@ function(copy_resources target plugin_path) # Windows: Copy DLLs for plugin formats message(STATUS "Copying Windows runtime DLLs for ${target}") - # Set DLL paths - set(GPAC_DLL "${CMAKE_SOURCE_DIR}/third_party/gpac/lib/Windows/${CMAKE_BUILD_TYPE}/libgpac.dll") - set(CRYPTOMD_DLL "${CMAKE_SOURCE_DIR}/third_party/gpac/lib/Windows/${CMAKE_BUILD_TYPE}/libcryptoMD.dll") - set(LIBSSLMD_DLL "${CMAKE_SOURCE_DIR}/third_party/gpac/lib/Windows/${CMAKE_BUILD_TYPE}/libsslMD.dll") - set(IAMF_TOOLS_DLL "${CMAKE_SOURCE_DIR}/third_party/iamftools/lib/Windows/${CMAKE_BUILD_TYPE}/iamf_tools.dll") - set(OPENSVC_DECODER "${CMAKE_SOURCE_DIR}/third_party/OpenSVC/lib/Windows/${CMAKE_BUILD_TYPE}/OpenSVCDecoder.dll") - string(TOLOWER "${CMAKE_CFG_INTDIR}" ZMQ_CFG) - set(ZMQ_DLL "${CMAKE_BINARY_DIR}/_deps/zeromq-build/bin/${ZMQ_CFG}/libzmq-v143-mt$<$:-gd>-4_3_6.dll") + # Set DLL paths using generator expressions for multi-config generators + set(GPAC_DLL "${CMAKE_SOURCE_DIR}/third_party/gpac/lib/Windows/$/libgpac.dll") + set(CRYPTOMD_DLL "${CMAKE_SOURCE_DIR}/third_party/gpac/lib/Windows/$/libcryptoMD.dll") + set(LIBSSLMD_DLL "${CMAKE_SOURCE_DIR}/third_party/gpac/lib/Windows/$/libsslMD.dll") + set(IAMF_TOOLS_DLL "${CMAKE_SOURCE_DIR}/third_party/iamftools/lib/Windows/$/iamf_tools.dll") + set(OPENSVC_DECODER "${CMAKE_SOURCE_DIR}/third_party/OpenSVC/lib/Windows/$/OpenSVCDecoder.dll") + set(ZMQ_DLL "${CMAKE_BINARY_DIR}/_deps/zeromq-build/bin/$/libzmq-v143-mt$<$:-gd>-4_3_6.dll") if("${target}" MATCHES ".*_VST3$") set(PLUGIN_BINARY_DIR "${plugin_path}/Contents/x86_64-win") diff --git a/common/components/src/AudioFilePlayer.cpp b/common/components/src/AudioFilePlayer.cpp index 7cb93e3..2158387 100644 --- a/common/components/src/AudioFilePlayer.cpp +++ b/common/components/src/AudioFilePlayer.cpp @@ -232,6 +232,7 @@ void AudioFilePlayer::resized() { } void AudioFilePlayer::update() { + std::lock_guard lock(pbeMutex_); if (playbackEngine_) { const IAMFFileReader::StreamData kData = playbackEngine_->getStreamData(); const float kDuration_s = @@ -265,9 +266,18 @@ void AudioFilePlayer::valueTreePropertyChanged( triggerAsyncUpdate(); } else if (property == FilePlayback::kPlaybackFile) { attemptCreatePlaybackEngine(); - } else if (property == FileExport::kExportCompleted && - fer_.get().getExportCompleted()) { - attemptCreatePlaybackEngine(); + } else if (property == FileExport::kExportCompleted) { + // When this property is false a new export is starting, so we want to + // destroy the player and wait until export is complete. + // When this property is true we want to attempt to create the playback + // engine again. + if (fer_.get().getExportCompleted()) { + auto safeThis = juce::Component::SafePointer(this); + juce::MessageManager::callAsync( + [safeThis]() { safeThis->attemptCreatePlaybackEngine(); }); + } else { + cancelCreatePlaybackEngine(); + } } } @@ -293,61 +303,85 @@ void AudioFilePlayer::updateComponentVisibility() { if (spinner_) spinner_->setVisible(kBuffering); } +void AudioFilePlayer::cancelCreatePlaybackEngine() { + isBeingDestroyed_ = true; + if (playbackEngineLoaderThread_.joinable()) { + playbackEngineLoaderThread_.join(); + } + // Wait for async callback to complete + std::unique_lock lock(pbeMutex_); + pbeCv_.wait(lock, [this] { return !isLoadingPlaybackEngine_; }); + isBeingDestroyed_ = false; +} + void AudioFilePlayer::attemptCreatePlaybackEngine() { - auto playbackState = fer_.get(); - const std::filesystem::path kFileToLoad( - playbackState.getExportFile().toStdString()); + cancelCreatePlaybackEngine(); + // If the file doesn't exist or it's a new file, we set the player to a + // stopped state + auto fe = fer_.get(); + const std::filesystem::path kFileToLoad(fe.getExportFile().toStdString()); if (kFileToLoad.empty() || kFileToLoad.extension() != ".iamf" || !std::filesystem::exists(kFileToLoad)) { + auto playbackState = fpbr_.get(); + playbackState.setPlayState(FilePlayback::kStop); + fpbr_.update(playbackState); return; } + + auto fpb = fpbr_.get(); + fpb.setPlayState(FilePlayback::kBuffering); + fpbr_.update(fpb); + createPlaybackEngine(kFileToLoad); } void AudioFilePlayer::createPlaybackEngine( const std::filesystem::path iamfPath) { - // Join any existing thread before starting a new one - isBeingDestroyed_ = true; - if (playbackEngineLoaderThread_.joinable()) { - playbackEngineLoaderThread_.join(); - } - isBeingDestroyed_ = false; - - auto playbackState = fpbr_.get(); - playbackState.setPlayState(FilePlayback::kBuffering); - fpbr_.update(playbackState); - const juce::String kDevice = fpbr_.get().getPlaybackDevice(); - - if (playbackEngine_) { - playbackEngine_->stop(); - } - playbackEngineLoaderThread_ = std::thread([this, iamfPath, kDevice]() { + isLoadingPlaybackEngine_ = true; + IAMFPlaybackDevice::Result res = IAMFPlaybackDevice::create( iamfPath, kDevice, isBeingDestroyed_, fpbr_, deviceManager_); auto safeThis = juce::Component::SafePointer(this); + + if (isBeingDestroyed_) { + isLoadingPlaybackEngine_ = false; + return; + } juce::MessageManager::callAsync( [safeThis, device = res.device.release(), error = res.error]() { - if (safeThis) { + if (safeThis && !safeThis->isBeingDestroyed_) { safeThis->onPlaybackEngineCreated(IAMFPlaybackDevice::Result{ std::unique_ptr(device), error}); + } else { + delete device; + } + + // Always reset the loading flag and notify waiters + if (safeThis) { + std::lock_guard lock(safeThis->pbeMutex_); + safeThis->isLoadingPlaybackEngine_ = false; + safeThis->pbeCv_.notify_all(); } }); }); } void AudioFilePlayer::onPlaybackEngineCreated(IAMFPlaybackDevice::Result res) { + std::lock_guard lock(pbeMutex_); if (!res.device && res.error == IAMFPlaybackDevice::Error::kInvalidIAMFFile) { // Failed to create playback engine - reset state to disabled playbackEngine_ = nullptr; auto fpb = fpbr_.get(); fpb.setPlayState(FilePlayback::kDisabled); fpbr_.update(fpb); - } else if (res.error == IAMFPlaybackDevice::kEarlyAbortRequested) { + } else if (res.error == IAMFPlaybackDevice::kEarlyAbortRequested || + isBeingDestroyed_) { // Do nothing - destruction was requested + playbackEngine_ = nullptr; } else { playbackEngine_ = std::move(res.device); // Update play state from buffering to ready diff --git a/common/components/src/AudioFilePlayer.h b/common/components/src/AudioFilePlayer.h index 70008e1..0208a4f 100644 --- a/common/components/src/AudioFilePlayer.h +++ b/common/components/src/AudioFilePlayer.h @@ -18,6 +18,7 @@ #include #include +#include #include #include #include @@ -52,8 +53,9 @@ class AudioFilePlayer : public juce::Component, class Spinner; void updateComponentVisibility(); - void createPlaybackEngine(const std::filesystem::path iamfPath); + void cancelCreatePlaybackEngine(); void attemptCreatePlaybackEngine(); + void createPlaybackEngine(const std::filesystem::path iamfPath); void onPlaybackEngineCreated(IAMFPlaybackDevice::Result res); // Components @@ -71,5 +73,8 @@ class AudioFilePlayer : public juce::Component, juce::AudioDeviceManager deviceManager_; std::unique_ptr playbackEngine_; std::thread playbackEngineLoaderThread_; + std::mutex pbeMutex_; + std::condition_variable pbeCv_; std::atomic_bool isBeingDestroyed_ = false; + std::atomic_bool isLoadingPlaybackEngine_ = false; }; \ No newline at end of file diff --git a/common/data_structures/src/FileExport.cpp b/common/data_structures/src/FileExport.cpp index 43e14d8..bd8d810 100644 --- a/common/data_structures/src/FileExport.cpp +++ b/common/data_structures/src/FileExport.cpp @@ -37,7 +37,7 @@ FileExport::FileExport() lpcm_sample_size_(24), sample_tally_(0), profile_(FileProfile::BASE), - exportCompleted_(false) {} + exportCompleted_(true) {} FileExport::FileExport(int startTime, int endTime, juce::String exportFile, juce::String exportFolder, diff --git a/rendererplugin/src/screens/FileExportScreen.cpp b/rendererplugin/src/screens/FileExportScreen.cpp index c238dc1..77b463b 100644 --- a/rendererplugin/src/screens/FileExportScreen.cpp +++ b/rendererplugin/src/screens/FileExportScreen.cpp @@ -485,7 +485,6 @@ FileExportScreen::FileExportScreen(MainEditor& editor, return; } config.setManualExport(!config.getManualExport()); - repository_->update(config); if (config.getManualExport()) { startTimer_.setEnabled(false); endTimer_.setEnabled(false); @@ -501,7 +500,7 @@ FileExportScreen::FileExportScreen(MainEditor& editor, exportVideoFolder_.setEnabled(false); browseVideoButton_.setEnabled(false); browseVideoSourceButton_.setEnabled(false); - + config.setExportCompleted(false); } else { startTimer_.setEnabled(true); endTimer_.setEnabled(true); @@ -517,15 +516,9 @@ FileExportScreen::FileExportScreen(MainEditor& editor, exportVideoFolder_.setEnabled(true); browseVideoButton_.setEnabled(true); browseVideoSourceButton_.setEnabled(true); - - if (!config.getExportCompleted()) { - // Normally this is handled by the file export processor - // But in manual button cases where the processor is destroyed we need - // to flag it here - config.setExportCompleted(true); - repository_->update(config); - } + config.setExportCompleted(true); } + repository_->update(config); repaint(); }; LOG_ANALYTICS(RendererProcessor::instanceId_, "FileExportScreen initiated.");