Monday, September 8, 2008

Proper usage of the MPVolumeView class

This post is no longer relevant regarding the details on keeping the volume slider in sync with the system volume. The undocumented call is no longer needed. In fact, if you use the call to "_updateVolumeFromAVSystemController", Apple will now reject your app during the review process.

I leave this post here for historical reference, but it is no longer relevant and should NOT be used!!

When dealing with audio in an app on the iPhone and iPod Touch, it is sometimes useful to have a volume control slider displayed onscreen. Apple supplies a class called MPVolumeView which is supposed to perform this function, however the documentation on it is quite poor.

Specifically, the MPVolumeView class provides a slider, and as you drag it, it adjusts the system volume. However, what the class doesn't provide is a method to automatically have the slider move in response to someone adjusting the volume via the volume buttons on the side of the iPhone. So, you can set the volume with the slider, but then if you adjust the volume with the buttons on the side of the phone, the slider is not reflecting the proper volume. Many iPhone apps exhibit this problem and personally I find it annoying.

It turns out, the reason so many apps behave this way is because there's not an easy documented method to query the system volume (at least not one I could find!). The main method for querying the system volume is in the Celestial private framework and you aren't supposed to use private frameworks in released apps.

Fortunately, someone discovered there is a system notifications that is generated whenever the volume changes. Also, the MPVolumeView slider can respond to a message called "_updateVolumeFromAVSystemController" which will cause it to adjust it's position based on the current system volume level.

So, given the two pieces of information, one can register a notification process to automatically adjust the MPVolumeView slider whenever the volume is changed using the buttons on the side of the phone.

Here's how it can be done - check out the code at pastebin. I didn't write this code, I just happened to run into it when searching for better information about MPVolumeView. There isn't really much in the way of comments in the code, but hopefully it's easy enough to follow.

UPDATE: the pastebin code expired, so here's my own version of the code. It was based on the pastebin code. I don't include the declarations from the .h file, but it should be pretty easy to figure out that part. You'll want to put this in the viewDidLoad method for whatever view will be holding the MPVolumeView.

- (void)viewDidLoad {

// create a frame to hold the MPVolumeView
CGRect frame = volumeViewHolder.bounds; // CGRectMake(0, 5, 180, 0);
volumeView = [[[MPVolumeView alloc] initWithFrame:frame] autorelease];
[volumeView sizeToFit];
[volumeViewHolder addSubview:volumeView];

// Find the volume view slider - we'll need to reference it in volumeChanged:
for (UIView *view in [volumeView subviews]){
if ([[[view class] description] isEqualToString:@"MPVolumeSlider"]) {
volumeViewSlider = view;

[[NSNotificationCenter defaultCenter] addObserver:self

- (void) volumeChanged:(NSNotification *)notify
//NSLog(@"volume changed");

// NOTE::: Do NOT use this call. It is no longer necessary and Apple will reject
// your app if you use it!

[volumeViewSlider _updateVolumeFromAVSystemController];

NOTE: when you compile this, you will get a warning that says "'UIView' may not respond to '- updateVolumeFromAVSSystemController'" but it can be safely ignored. The compiler is complaining that it can't verify volumeViewSlider is an object that can receive that message. This is some of the magic that makes Objective-C so powerful. You can send messages to objects even though the compiler doesn't know if the object supports the message. It's also an easy way to shoot yourself in the foot if you aren't certain of what you are doing.

In this instance, we know the volumeViewSlider object does support that message since we hand picked it by recursively searching the MPVolumeView until we got to the MPVolumeSlider piece of the object.

Thanks go out to the anonymous person who posted that original snippet of pastebin code. It was exactly what I was looking for.

[Edit: I must emphasize, the above code should NOT be used!! Apple will now reject an app which uses it. There's a demo project showing how to use some other features of the MPVolumeView class here.

All content copyright © 2009  Brian Stormont, unless otherwise noted.   All rights reserved.