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