Merge pull request #1538 from castdrian/ios-audio-stuff

fix(ios): recover audio after route changes
This commit is contained in:
bryanthaboi
2026-08-18 17:02:10 -04:00
committed by GitHub
3 changed files with 92 additions and 25 deletions
+81 -14
View File
@@ -437,6 +437,8 @@ def patch_audio_pool_gate():
"\n"
"\tprivate:\n"
"\t\tstd::atomic<bool> poolPaused;\n"
"\t\tbool poolUpdating;\n"
"\t\tlove::thread::ConditionalRef poolCondition;\n"
"\t};\n"
)
if header_original not in header:
@@ -456,6 +458,7 @@ def patch_audio_pool_gate():
"\t: pool(pool)\n"
"\t, finish(false)\n"
"\t, poolPaused(false)\n"
"\t, poolUpdating(false)\n"
"{\n"
)
if ctor_original not in text:
@@ -464,9 +467,25 @@ def patch_audio_pool_gate():
loop_original = "\t\tpool->update();\n\t\tsleep(5);\n"
loop_replacement = (
"\t\tif (!poolPaused.load(std::memory_order_acquire))\n"
"\t\tbool updatePool = false;\n"
"\t\t{\n"
"\t\t\tthread::Lock lock(mutex);\n"
"\t\t\tif (!poolPaused.load(std::memory_order_acquire))\n"
"\t\t\t{\n"
"\t\t\t\tpoolUpdating = true;\n"
"\t\t\t\tupdatePool = true;\n"
"\t\t\t}\n"
"\t\t}\n"
"\n"
"\t\tif (updatePool)\n"
"\t\t{\n"
"\t\t\tpool->update();\n"
"\n"
"\t\t\tthread::Lock lock(mutex);\n"
"\t\t\tpoolUpdating = false;\n"
"\t\t\tpoolCondition->broadcast();\n"
"\t\t}\n"
"\n"
"\t\tsleep(5);\n"
)
if loop_original not in text:
@@ -489,7 +508,13 @@ def patch_audio_pool_gate():
"\n"
"void Audio::PoolThread::setPoolPaused(bool paused)\n"
"{\n"
"\tthread::Lock lock(mutex);\n"
"\tpoolPaused.store(paused, std::memory_order_release);\n"
"\tif (paused)\n"
"\t{\n"
"\t\twhile (poolUpdating)\n"
"\t\t\tpoolCondition->wait(mutex);\n"
"\t}\n"
"}\n"
)
if finish_original not in text:
@@ -551,9 +576,44 @@ IOS_AUDIO_EVENT_HELPER = """static void gr_pushAudioEvent(const char *name)
msg->release();
}
static bool gr_routeRecoveryPending = false;
static unsigned int gr_routeRecoveryAttempts = 0;
"""
IOS_ROUTE_CHANGE_METHOD = """
- (void)finishAudioRouteChange
{
@synchronized (self)
{
NSError *err = nil;
if (![[AVAudioSession sharedInstance] setActive:YES error:&err])
{
NSLog(@"Error reactivating AVAudioSession: %@", [err localizedDescription]);
if (++gr_routeRecoveryAttempts < 8)
{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.25 * NSEC_PER_SEC)),
dispatch_get_main_queue(), ^{
[self finishAudioRouteChange];
});
}
else
{
gr_routeRecoveryPending = false;
}
return;
}
gr_routeRecoveryPending = false;
gr_routeRecoveryAttempts = 0;
auto resumed = love::Module::getInstance<love::audio::Audio>(love::Module::M_AUDIO);
if (!resumed)
return;
resumed->resumeContext();
gr_pushAudioEvent("audioreset");
}
}
- (void)audioSessionRouteChange:(NSNotification *)note
{
@synchronized (self)
@@ -571,22 +631,24 @@ IOS_ROUTE_CHANGE_METHOD = """
return;
}
gr_pushAudioEvent("audiosuspend");
audio->pauseContext();
if (gr_routeRecoveryPending)
return;
gr_routeRecoveryPending = true;
gr_routeRecoveryAttempts = 0;
dispatch_async(dispatch_get_main_queue(), ^{
@synchronized (self)
{
NSError *err = nil;
if (![[AVAudioSession sharedInstance] setActive:YES error:&err])
NSLog(@"Error reactivating AVAudioSession: %@", [err localizedDescription]);
auto resumed = love::Module::getInstance<love::audio::Audio>(love::Module::M_AUDIO);
if (resumed)
resumed->resumeContext();
gr_pushAudioEvent("audioreset");
auto suspended = love::Module::getInstance<love::audio::Audio>(love::Module::M_AUDIO);
if (!suspended)
{
gr_routeRecoveryPending = false;
return;
}
gr_pushAudioEvent("audiosuspend");
suspended->pauseContext();
}
[self finishAudioRouteChange];
});
}
}
@@ -615,8 +677,13 @@ def patch_ios_audio_route():
listener_anchor = "@interface LoveAudioInterruptionListener : NSObject\n"
if listener_anchor not in text:
fail(f"audio listener interface not found in {IOS_MM}")
text = text.replace(listener_anchor,
IOS_AUDIO_EVENT_HELPER + listener_anchor, 1)
listener_replacement = (
IOS_AUDIO_EVENT_HELPER
+ listener_anchor
+ "- (void)finishAudioRouteChange;\n"
+ "@end\n"
)
text = text.replace(listener_anchor + "@end\n", listener_replacement, 1)
interruption_original = (
"\t\tNSNumber *type = note.userInfo[AVAudioSessionInterruptionTypeKey];\n"
+1 -1
View File
@@ -179,7 +179,7 @@ local function playMusicSync(data, header, allowLoops)
currentMusic = { source = source, engine = engine, threaded = false,
started = true, finished = false }
fillSync(MUSIC_FILL_INITIAL)
if not musicHeld then source:play() end
if not musicHeld then pcall(source.play, source) end
return source
end
+10 -10
View File
@@ -165,7 +165,7 @@ local function playPath(data, key, def, pitch, tempo, plain)
reportBadDef("sfx", key, owner(data, "sfx", key), err)
return nil
end
s:setVolume(volumeFor(key))
pcall(s.setVolume, s, volumeFor(key))
cache[key] = s
src = s
end
@@ -619,12 +619,12 @@ function Sound.playPikaCry(data, n)
-- extractPikachuCries); widenMono re-decodes to 16-bit stereo so they
-- stay off surround outputs (#626). Fresh extracts are already stereo.
s = widenMono(s, path)
s:setVolume(volumeFor(key))
pcall(s.setVolume, s, volumeFor(key))
cache[key] = s
src = s
end
src:stop()
src:play()
pcall(src.stop, src)
pcall(src.play, src)
played("cry", "PIKACHU_PCM_" .. n, "PIKACHU")
return src
end
@@ -659,12 +659,12 @@ function Sound.playCry(data, species, pikaClip)
owner(data, "cries", species), err)
return nil
end
s:setVolume(volumeFor(key))
pcall(s.setVolume, s, volumeFor(key))
cache[key] = s
src = s
end
src:stop()
src:play()
pcall(src.stop, src)
pcall(src.play, src)
played("cry", species, species)
return src
end
@@ -734,12 +734,12 @@ function Sound.startLoop(data, name)
reportBadDef("sfx", name, owner(data, "sfx", name), err or "no source")
return
end
s:setLooping(true)
s:setVolume(volumeFor(name))
pcall(s.setLooping, s, true)
pcall(s.setVolume, s, volumeFor(name))
loopCache[name] = s
src = s
end
src:play()
pcall(src.play, src)
looping[name] = src
end