Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best way to play silence using AVAudioPlayer on iOS

I found myself in a situation where I need to simulate audio playback to trick OS controls and MPNowPlayingInfoCenter into thinking that an audio is being played. This is because I am building a player that plays multiple audio tracks, with pauses in-between creating one, continuous "audio" track. I have already everything setup inside the app itself, and the lock screen controls are working correctly but the only problem I am facing is while the actual audio stops and a pause is being "played", the lock screen info center stops the timer, and it only continues with showing correct time and overall state once another audio track starts playing.

Here is the example of my audio track built from audio files and pause items:

let items: [AudioItem] = [
    .audio("part-1.mp3"),
    .pause(duration: 5), // value of type: TimeInterval
    .audio("part-2.mp3"),
    .pause(duration: 3),
    ... // the list goes on
]

then in my custom player, once AVAudioPlayer finishes its job with current item, I get the next one from the array and play either a .pause with a scheduled Timer or another .audio with AVAudioPlayer.

extension Player: AVAudioPlayerDelegate {
    func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
        playNextItem()
    }
}

And here lies the problem, once the AVAudioPlayer stops, the Now Playing info center automatically stops too, even tho I keep feeding it fresh nowPlayingInfo. Then when it hits another .audio item, it resumes correctly and shows current time, etc.

And here lies the question

how do I trick the MPNowPlayingInfoCenter into thinking that audio is being played while I "play" my .pause item?

I realise that it may still not be clear, what I am trying to achieve but I am happy to share more insight if needed. Thanks!

Some solutions I am currently thinking about:

A. Keeping 1s long empty audio track that would play on loop for as long as the pause is needed to play.

B. Creating programatically empty audio track with appropriate lenght and playing it instead of using Timer for keeping track of pause duration/progress and relying completely on AVAudioPlayer for both .audio and .pause items. Not sure this is possible though.

C. Maybe there is a way to tell the MPNowPlayingInfoCenter that the audio keeps playing without the need of using AVAudioPlayer but some API I am not familiar with?

like image 235
Witek Bobrowski Avatar asked Oct 14 '22 23:10

Witek Bobrowski


1 Answers

AVAudioPlayer is probably the wrong tool here. You want AVAudioPlayerNode, which is slightly lower-level. Create an AVAudioEngine, and attach an AVAudioPlayerNode. You can then call scheduleFile(_:at:completionHandler:) to play the audio at the times you want.

Much of the Apple documentation on AVAudioEngine appears broken right this moment, but the links hopefully will be available again shortly in the links for Audio Engine Building Blocks. (If it stays down and you have trouble finding docs, leave a comment and I'll hunt down the WWDC videos and other tutorials on using AVAudioEngine. It's not particularly difficult for simple problems.)

If you know in advance how you want to compose these items (and it looks like you may), see also AVMutableComposition, which lets you glue together assets very efficiently, including adding empty segments of silence. See Media Composition and Editing for the various tools in that space.

like image 191
Rob Napier Avatar answered Nov 15 '22 08:11

Rob Napier