How to implement a volume key shutter for iPhone?

12,787

Solution 1

I've found another way to hide the "system volume overlay" and "bypass the system volume change when the volume key pressed" by myself.

The bad part: this is an super UGLY hack.

However, the good part is: this ugly hack uses NO private APIs.

Another note is: it only works for ios5+ (anyway, for my issue, since the AVSystemController_SystemVolumeDidChangeNotification only works for ios5, so this UGLY hack just fits my issue.)

The way it work: "act as a music/movie player app and let the volume key to adjust the application-volume".

Code:

// these 4 lines of code tell the system that "this app needs to play sound/music"
AVAudioPlayer* p = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:[[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"photo-shutter.wav"]] error:NULL];
[p prepareToPlay];
[p stop];
[p release];

// these 5 lines of code tell the system that "this window has an volume view inside it, so there is no need to show a system overlay"
[[self.view viewWithTag:54870149] removeFromSuperview];
MPVolumeView* vv = [[MPVolumeView alloc] initWithFrame:CGRectMake(-100, -100, 100, 100)];
[self.view addSubview:vv];
vv.tag = 54870149;
[vv release];

(5 hours spending on discovering this super ugly method... shit... 草尼马啊!)

Another thing: if you take the above hack, you need to run the code EVERY-TIME when your app become active. So, you might need to put some code into your app delegate.

- (void)applicationDidBecomeActive:(UIApplication *)application 

Solution 2

Building on huxia's code, this works on ios5+, no need to run the code every time it becomes active, just run it once in the beginning.

// these 4 lines of code tell the system that "this app needs to play sound/music"
AVAudioPlayer* p = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:[[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"photoshutter.wav"]] error:NULL];
[p prepareToPlay];
[p stop];

//make MPVolumeView Offscreen
CGRect frame = CGRectMake(-1000, -1000, 100, 100);
MPVolumeView *volumeView = [[MPVolumeView alloc] initWithFrame:frame];
[volumeView sizeToFit];
[self.view addSubview:volumeView];

Solution 3

[[NSNotificationCenter defaultCenter]
     addObserver:self
     selector:@selector(volumeChanged:)
     name:@"AVSystemController_SystemVolumeDidChangeNotification"
     object:nil];

- (void)volumeChanged:(NSNotification *)notification{ 

    CGRect frame = CGRectMake(-1000, -1000, 100, 100);
    MPVolumeView *volumeView = [[MPVolumeView alloc] initWithFrame:frame];
    [volumeView sizeToFit];
    [self.view addSubview:volumeView];
    [volumeView release];

    [self takePhoto];   
}

Solution 4

There's currently no official way to capture the volume key pressed event. Apple's stated line is that the volume button works with the UIImagePickerController if you've allowed it to show camera controls.

Other approaches, such as listening for the notification, seem to be unsupported hacks that Apple's team are — anecdotally — sometimes turning a blind eye to. To prevent the volume HUD from appearing you can use the undocumented UIApplication methods:

- (void)setSystemVolumeHUDEnabled:(BOOL)enabled;
- (void)setSystemVolumeHUDEnabled:(BOOL)enabled forAudioCategory:(NSString *)category;

The only statement of their use I've seen is:

UIApplication *app = [UIApplication sharedApplication];
[app setSystemVolumeHUDEnabled:NO forAudioCategory:@"Ringtone"];
[app setSystemVolumeHUDEnabled:NO];

I'm unsure if or why you seemingly need to disable the HUD for a specific category and then in general, but without proper documentation that's difficult to figure out.

So: use UIImagePickerController and its camera buttons if you want to be within the rules. If you've found an app that seems to work outside of the rules then it's probably using the methods above.

Solution 5

I call this method from viewDidAppear

-(void) startTrackingVolume
{
    [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryAmbient withOptions:AVAudioSessionCategoryOptionMixWithOthers error:nil];
    [[AVAudioSession sharedInstance] setActive:YES error:nil];

    if (!self.volumeView) {
        // put it somewhere outside the bounds of parent view
        self.volumeView = [[MPVolumeView alloc] initWithFrame:CGRectMake(-100, -100, 10, 0)];
        [self.volumeView sizeToFit];
    }

    if (!self.volumeView.superview) {
        [self.view addSubview:self.volumeView];
    }
}

In viewWillDisappear in call

[[AVAudioSession sharedInstance] setActive:NO error:nil];
Share:
12,787
huxia
Author by

huxia

Updated on June 05, 2022

Comments

  • huxia
    huxia almost 2 years

    I want to implement the same behavior with the native camera of iOS5:

    • press the volume + button to take a photo

    What's the ideal way to archive it? Are there any ways to capture the volume key pressed event?

    After googling & searching around for hours, I found 1 solution: using NSNotificationCenter:

    ...
        [[NSNotificationCenter defaultCenter]
             addObserver:self
             selector:@selector(volumeChanged:)
             name:@"AVSystemController_SystemVolumeDidChangeNotification"
             object:nil];
    ...
    - (void)volumeChanged:(NSNotification *)notification{
        [self takePhoto];   
    }
    

    However, it has 2 issues:

    • There is an semi-transparent overlay of "current system volume" show up every time when pressing the volume key, this is not what I wanted.
    • For the native camera, when you press the volume key as shutter, the system volume won't change, however, by using the above method, the system volume will change.
  • huxia
    huxia over 12 years
    Thank you for your answer first. I've been looking and trying around for hours and found another way to disabled the system volume HUD. And it uses no private API. In my case, we cannot use UIImagePickerController api because the camera app we are building is so different with the native one.
  • Tommy
    Tommy over 12 years
    [[NSBundle mainBundle] pathForResource:ofType:] would probably simplify that one line a little; just to be explicit: there's presumably no special meaning to the tag? You could just as successfully keep an explicit reference to any previous MPVolumeView?
  • huxia
    huxia over 12 years
    @Tommy the reason I added the tag is just to let the code could be ran more than once(because some times the self.view will be re-created because of memory warning). And Yes, there is no special meaning to the tag.
  • Pochi
    Pochi almost 11 years
    Correct me if I'm wrong but i believe that for this to work a reference to "p" has to be kept as an instance variable. Otherwise returning from the background will reset the volume view.
  • Ryan Romanchuk
    Ryan Romanchuk over 10 years
    @kwok-pan-fung anyway to prevent this from stopping music currently playing?
  • Ryan Romanchuk
    Ryan Romanchuk over 10 years
    @huxia anyway to prevent this from stopping music that's currently playing in the background?
  • Ethan_AI
    Ethan_AI over 9 years
    Captures the button press but doesn't stop the UI overlay or the system volume change.
  • Birju
    Birju over 8 years
    @Kwok Pan Fung , thank you for your wonderful solution. I wonder that if i put this code in my application then is there any possibility that apple may reject my application ?