Stop audio in iOS when backgrounded. - Fixes a crash when a sound is still playing and Artboard is de-allocated. Easy to cause with long sounds as sound resources would get nuked and the engine would keep trying to decode from a dead buffer. - Stops audio when going in background, resumes when coming to foreground. The way I did the backgrounding is on the RiveRendererView as I couldn't find a better general spot to do this but maybe @mjtalbot has an idea. This does not stop long sounds (like songs) when exiting a view, we can address that separately... Diffs= 4a9947630 Stop audio in iOS when backgrounded. (#7055) Co-authored-by: Luigi Rosso <luigi-rosso@users.noreply.github.com>
diff --git a/.rive_head b/.rive_head index 8d2c3c4..35589d3 100644 --- a/.rive_head +++ b/.rive_head
@@ -1 +1 @@ -cb2ea5b2d31eb6888a96cb25524392087bdf47c7 +4a9947630d3848ff70813cf5e2d8718adb9a1b90
diff --git a/include/rive/assets/audio_asset.hpp b/include/rive/assets/audio_asset.hpp index 4e00ce5..c4f042f 100644 --- a/include/rive/assets/audio_asset.hpp +++ b/include/rive/assets/audio_asset.hpp
@@ -2,6 +2,7 @@ #define _RIVE_AUDIO_ASSET_HPP_ #include "rive/generated/assets/audio_asset_base.hpp" #include "rive/audio/audio_source.hpp" +#include "rive/audio/audio_engine.hpp" namespace rive { @@ -20,6 +21,7 @@ rcp<AudioSource> audioSource() { return m_audioSource; } void audioSource(rcp<AudioSource> source) { m_audioSource = source; } + void stop(rcp<AudioEngine> engine); private: rcp<AudioSource> m_audioSource;
diff --git a/include/rive/audio/audio_engine.hpp b/include/rive/audio/audio_engine.hpp index a49f669..8bdf33c 100644 --- a/include/rive/audio/audio_engine.hpp +++ b/include/rive/audio/audio_engine.hpp
@@ -19,6 +19,7 @@ class AudioSound; class AudioSource; class LevelsNode; +class Artboard; class AudioEngine : public RefCnt<AudioEngine> { friend class AudioSound; @@ -42,9 +43,10 @@ rcp<AudioSound> play(rcp<AudioSource> source, uint64_t startTime, uint64_t endTime, - uint64_t soundStartTime); + uint64_t soundStartTime, + Artboard* artboard = nullptr); - static rcp<AudioEngine> RuntimeEngine(); + static rcp<AudioEngine> RuntimeEngine(bool makeWhenNecessary = true); #ifdef EXTERNAL_RIVE_AUDIO_ENGINE bool readAudioFrames(float* frames, uint64_t numFrames, uint64_t* framesRead = nullptr); @@ -57,6 +59,13 @@ float level(uint32_t channel); #endif + void start(); + void stop(); + void stop(Artboard* artboard); + +#ifdef TESTING + size_t playingSoundCount(); +#endif private: AudioEngine(ma_engine* engine); ma_device* m_device; @@ -64,6 +73,7 @@ std::mutex m_mutex; void soundCompleted(rcp<AudioSound> sound); + void unlinkSound(rcp<AudioSound> sound); std::vector<rcp<AudioSound>> m_completedSounds; rcp<AudioSound> m_playingSoundsHead;
diff --git a/include/rive/audio/audio_sound.hpp b/include/rive/audio/audio_sound.hpp index 783183c..5d34ca4 100644 --- a/include/rive/audio/audio_sound.hpp +++ b/include/rive/audio/audio_sound.hpp
@@ -4,10 +4,12 @@ #include "miniaudio.h" #include "rive/refcnt.hpp" +#include "rive/audio/audio_source.hpp" namespace rive { class AudioEngine; +class Artboard; class AudioSound : public RefCnt<AudioSound> { friend class AudioEngine; @@ -21,7 +23,7 @@ bool completed() const; private: - AudioSound(AudioEngine* engine); + AudioSound(AudioEngine* engine, rcp<AudioSource> source, Artboard* artboard); ma_decoder* decoder() { return &m_decoder; } ma_audio_buffer* buffer() { return &m_buffer; } ma_sound* sound() { return &m_sound; } @@ -30,12 +32,14 @@ ma_decoder m_decoder; ma_audio_buffer m_buffer; ma_sound m_sound; + rcp<AudioSource> m_source; // This is storage used by the AudioEngine. bool m_isDisposed; rcp<AudioSound> m_nextPlaying; rcp<AudioSound> m_prevPlaying; AudioEngine* m_engine; + Artboard* m_artboard; }; } // namespace rive
diff --git a/src/artboard.cpp b/src/artboard.cpp index 6021a07..074e0cd 100644 --- a/src/artboard.cpp +++ b/src/artboard.cpp
@@ -18,6 +18,7 @@ #include "rive/shapes/shape.hpp" #include "rive/text/text_value_run.hpp" #include "rive/event.hpp" +#include "rive/assets/audio_asset.hpp" #include <unordered_map> @@ -25,6 +26,18 @@ Artboard::~Artboard() { +#ifdef WITH_RIVE_AUDIO +#ifdef EXTERNAL_RIVE_AUDIO_ENGINE + auto audioEngine = m_audioEngine; +#else + auto audioEngine = AudioEngine::RuntimeEngine(false); +#endif + if (audioEngine) + { + audioEngine->stop(this); + } +#endif + for (auto object : m_Objects) { // First object is artboard
diff --git a/src/audio/audio_engine.cpp b/src/audio/audio_engine.cpp index c63bb69..076733d 100644 --- a/src/audio/audio_engine.cpp +++ b/src/audio/audio_engine.cpp
@@ -29,11 +29,8 @@ engine->soundCompleted(ref_rcp(audioSound)); } -void AudioEngine::soundCompleted(rcp<AudioSound> sound) +void AudioEngine::unlinkSound(rcp<AudioSound> sound) { - std::unique_lock<std::mutex> lock(m_mutex); - m_completedSounds.push_back(sound); - auto next = sound->m_nextPlaying; auto prev = sound->m_prevPlaying; if (next != nullptr) @@ -54,6 +51,13 @@ sound->m_prevPlaying = nullptr; } +void AudioEngine::soundCompleted(rcp<AudioSound> sound) +{ + std::unique_lock<std::mutex> lock(m_mutex); + m_completedSounds.push_back(sound); + unlinkSound(sound); +} + #ifdef WITH_RIVE_AUDIO_TOOLS namespace rive { @@ -153,6 +157,9 @@ } #endif +void AudioEngine::start() { ma_engine_start(m_engine); } +void AudioEngine::stop() { ma_engine_stop(m_engine); } + rcp<AudioEngine> AudioEngine::Make(uint32_t numChannels, uint32_t sampleRate) { ma_engine_config engineConfig = ma_engine_config_init(); @@ -185,7 +192,8 @@ rcp<AudioSound> AudioEngine::play(rcp<AudioSource> source, uint64_t startTime, uint64_t endTime, - uint64_t soundStartTime) + uint64_t soundStartTime, + Artboard* artboard) { std::unique_lock<std::mutex> lock(m_mutex); // We have to dispose completed sounds out of the completed callback. So we @@ -196,7 +204,7 @@ } m_completedSounds.clear(); - rcp<AudioSound> audioSound = rcp<AudioSound>(new AudioSound(this)); + rcp<AudioSound> audioSound = rcp<AudioSound>(new AudioSound(this, source, artboard)); if (source->isBuffered()) { rive::Span<float> samples = source->bufferedSamples(); @@ -279,6 +287,39 @@ return audioSound; } +#ifdef TESTING +size_t AudioEngine::playingSoundCount() +{ + std::unique_lock<std::mutex> lock(m_mutex); + size_t count = 0; + auto sound = m_playingSoundsHead; + while (sound != nullptr) + { + count++; + sound = sound->m_nextPlaying; + } + + return count; +} +#endif + +void AudioEngine::stop(Artboard* artboard) +{ + std::unique_lock<std::mutex> lock(m_mutex); + auto sound = m_playingSoundsHead; + while (sound != nullptr) + { + auto next = sound->m_nextPlaying; + if (sound->m_artboard == artboard) + { + sound->stop(); + m_completedSounds.push_back(sound); + unlinkSound(sound); + } + sound = next; + } +} + AudioEngine::~AudioEngine() { auto sound = m_playingSoundsHead; @@ -316,10 +357,18 @@ return (uint64_t)ma_engine_get_time_in_pcm_frames(m_engine); } -rcp<AudioEngine> AudioEngine::RuntimeEngine() +static rcp<AudioEngine> m_runtimeAudioEngine; +rcp<AudioEngine> AudioEngine::RuntimeEngine(bool makeWhenNecessary) { - static rcp<AudioEngine> engine = AudioEngine::Make(defaultNumChannels, defaultSampleRate); - return engine; + if (!makeWhenNecessary) + { + return m_runtimeAudioEngine; + } + else if (m_runtimeAudioEngine == nullptr) + { + m_runtimeAudioEngine = AudioEngine::Make(defaultNumChannels, defaultSampleRate); + } + return m_runtimeAudioEngine; } #ifdef EXTERNAL_RIVE_AUDIO_ENGINE
diff --git a/src/audio/audio_sound.cpp b/src/audio/audio_sound.cpp index e355aad..6bc227d 100644 --- a/src/audio/audio_sound.cpp +++ b/src/audio/audio_sound.cpp
@@ -6,8 +6,14 @@ using namespace rive; -AudioSound::AudioSound(AudioEngine* engine) : - m_decoder({}), m_buffer({}), m_sound({}), m_isDisposed(false), m_engine(engine) +AudioSound::AudioSound(AudioEngine* engine, rcp<AudioSource> source, Artboard* artboard) : + m_decoder({}), + m_buffer({}), + m_sound({}), + m_source(std::move(source)), + m_isDisposed(false), + m_engine(engine), + m_artboard(artboard) {} void AudioSound::dispose()
diff --git a/src/audio_event.cpp b/src/audio_event.cpp index 9a9e043..53c9812 100644 --- a/src/audio_event.cpp +++ b/src/audio_event.cpp
@@ -32,7 +32,7 @@ #endif AudioEngine::RuntimeEngine(); - auto sound = engine->play(audioSource, engine->timeInFrames(), 0, 0); + auto sound = engine->play(audioSource, engine->timeInFrames(), 0, 0, artboard()); if (volume != 1.0f) {
diff --git a/test/audio_test.cpp b/test/audio_test.cpp index 06ffa30..c96e4e0 100644 --- a/test/audio_test.cpp +++ b/test/audio_test.cpp
@@ -147,4 +147,42 @@ } } +TEST_CASE("audio sounds from different artboards stop accordingly", "[audio]") +{ + rcp<AudioEngine> engine = AudioEngine::Make(2, 44100); + + auto file = ReadRiveFile("../../test/assets/sound.riv"); + auto artboard = file->artboardDefault(); + artboard->audioEngine(engine); + auto artboard2 = file->artboardDefault(); + artboard2->audioEngine(engine); + + REQUIRE(artboard != nullptr); + + auto audioEvents = artboard->find<AudioEvent>(); + REQUIRE(audioEvents.size() == 1); + + auto audioEvent = audioEvents[0]; + REQUIRE(audioEvent->asset() != nullptr); + REQUIRE(audioEvent->asset()->hasAudioSource()); + + audioEvent->play(); + audioEvent->play(); + REQUIRE(engine->playingSoundCount() == 2); + auto audioEvent2 = artboard2->find<AudioEvent>()[0]; + audioEvent2->play(); + REQUIRE(engine->playingSoundCount() == 3); + audioEvent->play(); + REQUIRE(engine->playingSoundCount() == 4); + + // The three playing sounds owned by the first artboard should now stop. + artboard = nullptr; + + REQUIRE(engine->playingSoundCount() == 1); + + // The last one belonging to artboard2 should now stop too. + artboard2 = nullptr; + REQUIRE(engine->playingSoundCount() == 0); +} + // TODO check if sound->stop calls completed callback!!! \ No newline at end of file