No trouble at all. Yeah, coroutines are of limited use, but sort of nice for keeping some kinds of code more understandable I guess.
I switched to a non-coroutine approach, but for anyone else who reads this and still wants to use coroutines, I figured out a workaround for this problem. (Also, maybe Corona’s developers could adapt this workaround to easily fix the problem “under the hood” without a massive overhaul of Corona?)
The workaround works by guaranteeing that all callbacks happen in the main thread. It achieves this by detecting when a callback is being registered in a coroutine, and ‘postponing’ the call for the main thread to handle later. This is done via a timer, whose callbacks, unlike audio callbacks, seem to work when registered in a coroutine. (At least in the simulator.)
function runInMainThread ( func, packedArgs )  
 local thread = coroutine.running()  
 if thread then  
 timer.performWithDelay( 1,   
 function ( event )  
 local r  
 -- We're in the main thread now.  
 r = { func(unpack(packedArgs,1,table.maxn(packedArgs))) }  
 -- Let the coroutine know the result of the function call:  
 coroutine.resume ( thread, r )  
 end )  
 return unpack ( coroutine.yield() ) -- Wait for function to be called.  
 else  
 -- This is the main thread, so call normally.  
 return func ( unpack ( packedArgs, 1, table.maxn(packedArgs) ) )   
 end  
end  
So a “coroutine-safe” version of audio.play might look like:
function cosafe\_audioplay( h, params )  
 if coroutine.running() and params and params.onComplete then  
 runInMainThread ( audio.play, { h, params } )  
 else  
 audio.play( h, params )  
 end  
end  
And a blocking version of audio.play (for calling within a coroutine) could be written like this:
function blocking\_audioplay( h, params )  
 -- Only allow when called in a coroutine.  
 local thread = coroutine.running()  
 if thread then  
 -- Save the caller's onComplete and replace with our own.  
 if not params then params = {} end  
 orig\_onComplete = params.onComplete  
 -- Our new onComplete will be called in the main thread.  
 params.onComplete = function ( event )   
 if orig\_onComplete then  
 orig\_onComplete ( event )  
 end  
 coroutine.resume( thread )  
 end   
 cosafe\_audioplay ( h, params )  
 coroutine.yield()  
 end  
end  
And then code like this works as expected:
local h1 = audio.loadSound( "sound1.mp3" )  
local h2 = audio.loadSound( "sound2.mp3" )  
local h3 = audio.loadSound( "sound3.mp3" )  
  
function cofunction()  
 -- These sounds will play back to back now.  
 blocking\_audioplay( h1 )  
 blocking\_audioplay( h2, { onComplete=function() print("sound 2 done.") end } )  
 blocking\_audioplay( h3 )  
end  
  
local thread = coroutine.create( cofunction )  
coroutine.resume( thread )  
Cheers,
bc [import]uid: 131050 topic_id: 35305 reply_id: 140487[/import]