Skip to content

Commit

Permalink
Stop audio in iOS when backgrounded.
Browse files Browse the repository at this point in the history
- 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 <[email protected]>
  • Loading branch information
luigi-rosso and luigi-rosso committed Apr 16, 2024
1 parent 70dfed9 commit ae49ac7
Show file tree
Hide file tree
Showing 9 changed files with 138 additions and 16 deletions.
2 changes: 1 addition & 1 deletion .rive_head
Original file line number Diff line number Diff line change
@@ -1 +1 @@
cb2ea5b2d31eb6888a96cb25524392087bdf47c7
4a9947630d3848ff70813cf5e2d8718adb9a1b90
2 changes: 2 additions & 0 deletions include/rive/assets/audio_asset.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -20,6 +21,7 @@ class AudioAsset : public AudioAssetBase

rcp<AudioSource> audioSource() { return m_audioSource; }
void audioSource(rcp<AudioSource> source) { m_audioSource = source; }
void stop(rcp<AudioEngine> engine);

private:
rcp<AudioSource> m_audioSource;
Expand Down
14 changes: 12 additions & 2 deletions include/rive/audio/audio_engine.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ namespace rive
class AudioSound;
class AudioSource;
class LevelsNode;
class Artboard;
class AudioEngine : public RefCnt<AudioEngine>
{
friend class AudioSound;
Expand All @@ -42,9 +43,10 @@ class AudioEngine : public RefCnt<AudioEngine>
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);
Expand All @@ -57,13 +59,21 @@ class AudioEngine : public RefCnt<AudioEngine>
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;
ma_engine* m_engine;
std::mutex m_mutex;

void soundCompleted(rcp<AudioSound> sound);
void unlinkSound(rcp<AudioSound> sound);

std::vector<rcp<AudioSound>> m_completedSounds;
rcp<AudioSound> m_playingSoundsHead;
Expand Down
6 changes: 5 additions & 1 deletion include/rive/audio/audio_sound.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -21,7 +23,7 @@ class AudioSound : public RefCnt<AudioSound>
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; }
Expand All @@ -30,12 +32,14 @@ class AudioSound : public RefCnt<AudioSound>
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

Expand Down
13 changes: 13 additions & 0 deletions src/artboard.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,26 @@
#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>

using namespace rive;

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
Expand Down
67 changes: 58 additions & 9 deletions src/audio/audio_engine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,8 @@ void AudioEngine::SoundCompleted(void* pUserData, ma_sound* pSound)
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)
Expand All @@ -54,6 +51,13 @@ void AudioEngine::soundCompleted(rcp<AudioSound> sound)
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
{
Expand Down Expand Up @@ -153,6 +157,9 @@ float AudioEngine::level(uint32_t channel)
}
#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();
Expand Down Expand Up @@ -185,7 +192,8 @@ AudioEngine::AudioEngine(ma_engine* engine) :
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
Expand All @@ -196,7 +204,7 @@ rcp<AudioSound> AudioEngine::play(rcp<AudioSource> source,
}
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();
Expand Down Expand Up @@ -279,6 +287,39 @@ rcp<AudioSound> AudioEngine::play(rcp<AudioSource> source,
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;
Expand Down Expand Up @@ -316,10 +357,18 @@ uint64_t AudioEngine::timeInFrames()
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
Expand Down
10 changes: 8 additions & 2 deletions src/audio/audio_sound.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
2 changes: 1 addition & 1 deletion src/audio_event.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ void AudioEvent::play()
#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)
{
Expand Down
38 changes: 38 additions & 0 deletions test/audio_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -147,4 +147,42 @@ TEST_CASE("many audio sounds can outlive engine", "[audio]")
}
}

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!!!

0 comments on commit ae49ac7

Please sign in to comment.