iOS4 Multitasking: Developer’s Hell
I finally got around to installing iOS4, so I decided I’d go ahead and investigate what it takes to update the Fresh app (streaming radio) to support multitasking. Currently, my conclusion is that implementing ANY of the multitasking APIs is 110% hell unless you’re starting from scratch.
First of all, your application has to play nice while it’s sitting in the background. This means that you’re basically required to implement a whole lot of “if backgrounded” checks, unless you want to soak up the battery. This involves stopping unnecessary timers, updating the UI when the application comes back to the foreground (rather than per a specific event), and saving non-critical alerts for when the user pops back into the application. This basically requires a crapton of refactoring…and we all love how that introduces bugs. If you have any sort of complicated app, you’ll be having to manage state saving so that fast app switching works nicely. Oh, and not to mention that iOS never guarantees that your application stays backgrounded. It can be killed depending on resource usage, available memory or even just idle time. Every task (other than a few specific exceptions) is time limited, so you are constantly having to check and request more time for your task.
Now, this particular application is meant to take advantage of the background audio multitasking API. Now, as an aside, Apple does not provide any “simple” API to stream audio. What they provide is a primitive and generic audio buffer/queue that suits both local files and network streams. Basically, this means that if you want to stream audio over the network, you have to implement the protocol, manually send the requests to get data (via HTTP for example), then feed that data into a buffer (where you have to handle the size of incoming data vs. the size of the buffer), THEN…play the audio from the buffer or handle any errors that occurred along the way. Now, in any half decent modern API, surely, you’d have a perfected version of the algorithm where you could simply plug in a URL and instruct a magical black box to play it. But alas no, each developer is to implement the over complicated, primitive audio queue… just for a streaming radio application. Now combine that with the following multitasking API, and you’re in developer hell.
One of the official pages on backgrounding is nearly impossible to find on the Apple site, so here it is: http://developer.apple.com/iphone/library/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/BackgroundExecution/BackgroundExecution.html Now, if you look closely, you’ll see that most sections are detailed nicely….except background audio, fantastic. So, taking a quick look at that section reveals that if you simply “flag” your application as one that streams audio, you’re good to go. Hooray right? Well, sort of, not really, no.
Hurdle 1: Hooking into iPod controls
Surprisingly, this is the EASIEST part of implementing background audio. It seems to “just work” as long as you have fairly simple start/stop/pause methods for your radio streamer. One gotcha is that state changes can now take place from both your play/stop button and the iPod controls… so, basically you should be using some sort of event system for state changes and to reupdate the UI whenever your app hits the foregound again.
Hurdle 2: Interruptions
Interruptions have existed since OS2. They cause your music to stop, usually when someone rings. However, now your application has to act more like the iPod application…except you can’t! Unlike the iPod application, which automatically restarts after you finish your call, the only way your application can automatically restart it’s stream is if the user brings it back to the foreground. Not only that, I’ve found that interruption events are very inconsistent. Sometimes they fire simultaneously with the interruption, sometimes they won’t fire until you bring the application to the foreground. On the times that they don’t fire with the interruption, I find that some sort of crazy auto deallocation of the streamer occurs which I can only fix with a terrible workaround. I am yet to find the actual cause of this.
Hurdle 3: iPhone Simulator
It sucks. Testing background audio with it is impossible! Even in the GM build, there is a bug that mutes all sound from your application when it is backgrounded. This makes it rather difficult to tell whether or not your application IS ACTUALLY SITTING IN THE BACKGROUND.
Hurdle 4: You’re free to do whatever you want…except when you need do something
So, as explained before, if you flag your application for streaming audio, your application can go ahead and play audio and do what it needs to do without any particular time limit or suspended state. Cool! Sort of like a free pass to do whatever the hell I want, right? Wrong. There’s a HUGE exception here: “if your application stops playing audio while in the background, your application is suspended.” Wow, that’s fantastic. So let me get this right…if my STREAMING AUDIO APPLICATION happens to need to BUFFER (i.e. STOP PLAYING AUDIO) while on a dodgy cellular connection, my application is essentially…fucked. Yes, that’s right. If you have a little bit of a network hiccup, and need to buffer, the user will need to jump back into the application to restart the audio stream! Not even the iPod controls (which are meant for external use) allow you restart the stream. I haven’t yet found a fix for this yet, but I’m assuming I’m going to have to go back to that mindfuck of a primitive audio queue to fill it with some sort of dummy audio while it buffers, so that iOS doesn’t fucking suspend my application in the middle of playing music. Doesn’t this found like fun?
Hurdle 5:Backwards Compatibility
Finally, as always, you need to aim for backwards compatibility. Apple claims to have completely culled OS2.x, but let’s face it, there are still 1st Generation iPod Touch users that DIDN’T pay the iOS3/4 upgrade tax, that want to use apps. You have to, at a minimum, ensure your application works on OS3 devices, which means making sure that some method calls, that are iOS4 specific, actually exist. Then using the simulator to test………. /facepalm.
FML.
about 1 month ago
Hi Grant,
Thanks for your post. I thought I was the only one lost in Apple’s documentation on multi-tasking and background audio (especially streaming).
However, in my experience hooking into iPod controls has been hell. I can’t get the controls to respond.
I’m streaming audio using Matt Gallagher’s AudioStreamer.
Have you been using [MPMusicPlayerController applicationMusicPlayer] or [MPMusicPlayerController iPodMusicPlayer] ?
Do you register for notifications using [musicPlayer beginGeneratingPlaybackNotifications] ?
Also, have you been able to replace the ipod icon (along the ipod controls) with your own application icon ?
Your help will be greatly appreciated.
PS: I see you like wine and cheese. I’m from France. You should visit Paris and taste our food
about 1 month ago
Hi Max!
You can check out my github fork of AudioStreamer here: http://github.com/DigitalDJ/AudioStreamer
I haven’t been working on it for a while…but I hopefully intend to the fix the bugs I talk about above in the next week or so.
iPhoneStreamingPlayerViewController.m:
In viewDidAppear:
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
Then implement remoteControlReceivedWithEvent
- (void)remoteControlReceivedWithEvent:(UIEvent *)event {
switch (event.subtype) {
case UIEventSubtypeRemoteControlTogglePlayPause:
if ([streamer isPlaying])
[streamer pause];
else {
[self createStreamer];
[streamer start];
}
break;
case UIEventSubtypeRemoteControlPlay:
[streamer start];
break;
case UIEventSubtypeRemoteControlPause:
[streamer pause];
break;
case UIEventSubtypeRemoteControlStop:
[streamer stop];
break;
default:
break;
}
}
Hope that helps!
This makes the App icon show instead of the iPod icon…and it works for the iPod multitask widget and the lockscreen….but I can’t seem to make it switch to a “Stop” icon…only pause.
about 1 month ago
Thank you so much.
I’m still having problems with my AudioStreamer because I’ve customized a few things. But I’m sure I will soon work.
I’ve tested your AudioStreamer sample from github and it just simply works. Awesome.
Thanks for sharing.
about 1 month ago
It doesn’t just work,yet :p. At the moment the app will crash if you interrupt it with a phone call or the iPod app. Also, if the app is backgrouded and buffeting it will not start playing again until the app becomes active. I think you need to move buffeting to a background task. If you fix any of these… Let me know
Cheers!
about 1 month ago
Hey!
Am doing the exact same thing. And you’ve nailed the sentiment! And my application requires me to have two AudioQueues at a time!
about 1 month ago
I am using your fork of AudioStreamer from github to play funky music in my radionula radio app and it seems to work great, pure divinity!
But hey, I come from Slovenia and I have only one 2nd gen iPod to test it
so I wish from my heart You get those bugs smashed soon and if You want or need I can send You some of my cannabis if You like smoking it.
Enjoy!
about 3 weeks ago
Here is a nice solution for starting to play audio after the app is suspended (After the current playing audio is stopped and the next audio stream to start).
http://stackoverflow.com/questions/3161635/entering-background-on-ios4-to-play-audio/3161899#3161899
about 17 hours ago
“the only way your application can automatically restart it’s stream is if the user brings it back to the foreground” <- I think this is wrong as you can surely resume your audio by listening to the notification (AVAudioSessionInterruptionFlags_ShouldResume)
about 14 hours ago
That won’t work if the application is suspended since the application will never get the event! You need to create a background task for buffering operations. Which is EXACTLY why streaming audio is an absolute pain in the ass.
My follow up is here: http://digitaldj.net/2010/08/25/ios4-background-audio-revisited/