[
  {
    "path": ".gitignore",
    "content": ".DS_Store\n*/build/*\n*.pbxuser\n!default.pbxuser\n*.mode1v3\n!default.mode1v3\n*.mode2v3\n!default.mode2v3\n*.perspectivev3\n!default.perspectivev3\nxcuserdata\nprofile\n*.moved-aside\nDerivedData\n.idea/\n*.hmap\n*.xccheckout\n"
  },
  {
    "path": "JPSVolumeButtonHandler/JPSVolumeButtonHandler.h",
    "content": "//\n//  JPSVolumeButtonHandler.h\n//  JPSImagePickerController\n//\n//  Created by JP Simard on 1/31/2014.\n//  Copyright (c) 2014 JP Simard. All rights reserved.\n//\n\n#import <Foundation/Foundation.h>\n#import <AVFoundation/AVFoundation.h>\n\ntypedef void (^JPSVolumeButtonBlock)(void);\n\n@interface JPSVolumeButtonHandler : NSObject\n\n// A block to run when the volume up button is pressed\n@property (nonatomic, copy) JPSVolumeButtonBlock upBlock;\n\n// A block to run when the volume down button is pressed\n@property (nonatomic, copy) JPSVolumeButtonBlock downBlock;\n\n// A shared audio session category\n@property (nonatomic, strong) NSString * sessionCategory;\n\n@property (nonatomic, assign) AVAudioSessionCategoryOptions sessionOptions;\n\n- (void)startHandler:(BOOL)disableSystemVolumeHandler;\n- (void)stopHandler;\n\n// A Function to set exactJumpsOnly.  When set to YES, only volume jumps of .0625 call the code blocks.\n// If it doesn't match, the code blocks are not called and setInitialVolume is called\n- (void)useExactJumpsOnly:(BOOL)enabled;\n\n// Returns a button handler with the specified up/down volume button blocks\n+ (instancetype)volumeButtonHandlerWithUpBlock:(JPSVolumeButtonBlock)upBlock downBlock:(JPSVolumeButtonBlock)downBlock;\n\n@end\n"
  },
  {
    "path": "JPSVolumeButtonHandler/JPSVolumeButtonHandler.m",
    "content": "//\n//  JPSVolumeButtonHandler.m\n//  JPSImagePickerController\n//\n//  Created by JP Simard on 1/31/2014.\n//  Copyright (c) 2014 JP Simard. All rights reserved.\n//\n\n#import \"JPSVolumeButtonHandler.h\"\n#import <MediaPlayer/MediaPlayer.h>\n\n// Comment/uncomment out NSLog to enable/disable logging\n#define JPSLog(fmt, ...) //NSLog(fmt, __VA_ARGS__)\n\nstatic NSString *const sessionVolumeKeyPath = @\"outputVolume\";\nstatic void *sessionContext                 = &sessionContext;\nstatic CGFloat maxVolume                    = 0.99999f;\nstatic CGFloat minVolume                    = 0.00001f;\n\n@interface JPSVolumeButtonHandler ()\n\n@property (nonatomic, assign) CGFloat          initialVolume;\n@property (nonatomic, strong) AVAudioSession * session;\n@property (nonatomic, strong) MPVolumeView   * volumeView;\n@property (nonatomic, assign) BOOL             appIsActive;\n@property (nonatomic, assign) BOOL             isStarted;\n@property (nonatomic, assign) BOOL             disableSystemVolumeHandler;\n@property (nonatomic, assign) BOOL             isAdjustingInitialVolume;\n@property (nonatomic, assign) BOOL             exactJumpsOnly;\n\n@end\n\n@implementation JPSVolumeButtonHandler\n\n#pragma mark - Init\n\n- (id)init {\n    self = [super init];\n    \n    if (self) {\n        _appIsActive = YES;\n        _sessionCategory = AVAudioSessionCategoryPlayback;\n        _sessionOptions = AVAudioSessionCategoryOptionMixWithOthers;\n\n        _volumeView = [[MPVolumeView alloc] initWithFrame:CGRectMake(MAXFLOAT, MAXFLOAT, 0, 0)];\n\n        [[UIApplication sharedApplication].windows.firstObject addSubview:_volumeView];\n        \n        _volumeView.hidden = YES;\n\n        _exactJumpsOnly = NO;\n    }\n    return self;\n}\n\n- (void)dealloc {\n    [self stopHandler];\n    \n    MPVolumeView *volumeView = self.volumeView;\n    dispatch_async(dispatch_get_main_queue(), ^{\n        [volumeView removeFromSuperview];\n    });\n}\n\n- (void)startHandler:(BOOL)disableSystemVolumeHandler {\n    [self setupSession];\n    self.volumeView.hidden = NO; // Start visible to prevent changes made during setup from showing default volume\n    self.disableSystemVolumeHandler = disableSystemVolumeHandler;\n\n    // There is a delay between setting the volume view before the system actually disables the HUD\n    [self performSelector:@selector(setupSession) withObject:nil afterDelay:1];\n}\n\n- (void)stopHandler {\n    if (!self.isStarted) {\n        // Prevent stop process when already stop\n        return;\n    }\n    \n    self.isStarted = NO;\n    \n    self.volumeView.hidden = YES;\n    // https://github.com/jpsim/JPSVolumeButtonHandler/issues/11\n    // http://nshipster.com/key-value-observing/#safe-unsubscribe-with-@try-/-@catch\n    @try {\n        [self.session removeObserver:self forKeyPath:sessionVolumeKeyPath];\n    }\n    @catch (NSException * __unused exception) {\n    }\n    [[NSNotificationCenter defaultCenter] removeObserver:self];\n}\n\n- (void)setupSession {\n    if (self.isStarted){\n        // Prevent setup twice\n        return;\n    }\n    \n    self.isStarted = YES;\n\n    NSError *error = nil;\n    self.session = [AVAudioSession sharedInstance];\n    // this must be done before calling setCategory or else the initial volume is reset\n    [self setInitialVolume];\n    [self.session setCategory:_sessionCategory\n                  withOptions:_sessionOptions\n                        error:&error];\n    if (error) {\n        NSLog(@\"%@\", error);\n        return;\n    }\n    [self.session setActive:YES error:&error];\n    if (error) {\n        NSLog(@\"%@\", error);\n        return;\n    }\n\n    // Observe outputVolume\n    [self.session addObserver:self\n                   forKeyPath:sessionVolumeKeyPath\n                      options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew)\n                      context:sessionContext];\n\n    // Audio session is interrupted when you send the app to the background,\n    // and needs to be set to active again when it goes to app goes back to the foreground\n    [[NSNotificationCenter defaultCenter] addObserver:self\n                                             selector:@selector(audioSessionInterrupted:)\n                                                 name:AVAudioSessionInterruptionNotification\n                                               object:nil];\n\n    [[NSNotificationCenter defaultCenter] addObserver:self\n                                             selector:@selector(applicationDidChangeActive:)\n                                                 name:UIApplicationWillResignActiveNotification\n                                               object:nil];\n    [[NSNotificationCenter defaultCenter] addObserver:self\n                                             selector:@selector(applicationDidChangeActive:)\n                                                 name:UIApplicationDidBecomeActiveNotification\n                                               object:nil];\n\n    self.volumeView.hidden = !self.disableSystemVolumeHandler;\n}\n\n- (void) useExactJumpsOnly:(BOOL)enabled{\n    _exactJumpsOnly = enabled;\n}\n\n- (void)audioSessionInterrupted:(NSNotification*)notification {\n    NSDictionary *interuptionDict = notification.userInfo;\n    NSInteger interuptionType = [[interuptionDict valueForKey:AVAudioSessionInterruptionTypeKey] integerValue];\n    switch (interuptionType) {\n        case AVAudioSessionInterruptionTypeBegan:\n            JPSLog(@\"Audio Session Interruption case started.\", nil);\n            break;\n        case AVAudioSessionInterruptionTypeEnded:\n        {\n            JPSLog(@\"Audio Session Interruption case ended.\", nil);\n            NSError *error = nil;\n            [self.session setActive:YES error:&error];\n            if (error) {\n                NSLog(@\"%@\", error);\n            }\n            break;\n        }\n        default:\n            JPSLog(@\"Audio Session Interruption Notification case default.\", nil);\n            break;\n    }\n}\n\n- (void)setInitialVolume {\n    self.initialVolume = self.session.outputVolume;\n    if (self.initialVolume > maxVolume) {\n        self.initialVolume = maxVolume;\n        self.isAdjustingInitialVolume = YES;\n        [self setSystemVolume:self.initialVolume];\n    } else if (self.initialVolume < minVolume) {\n        self.initialVolume = minVolume;\n        self.isAdjustingInitialVolume = YES;\n        [self setSystemVolume:self.initialVolume];\n    }\n}\n\n- (void)applicationDidChangeActive:(NSNotification *)notification {\n    self.appIsActive = [notification.name isEqualToString:UIApplicationDidBecomeActiveNotification];\n    if (self.appIsActive && self.isStarted) {\n        [self setInitialVolume];\n    }\n}\n\n#pragma mark - Convenience\n\n+ (instancetype)volumeButtonHandlerWithUpBlock:(JPSVolumeButtonBlock)upBlock downBlock:(JPSVolumeButtonBlock)downBlock {\n    JPSVolumeButtonHandler *instance = [[JPSVolumeButtonHandler alloc] init];\n    if (instance) {\n        instance.upBlock = upBlock;\n        instance.downBlock = downBlock;\n    }\n    return instance;\n}\n\n#pragma mark - KVO\n\n- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {\n    if (context == sessionContext) {\n        if (!self.appIsActive) {\n            // Probably control center, skip blocks\n            return;\n        }\n        \n        CGFloat newVolume = [change[NSKeyValueChangeNewKey] floatValue];\n        CGFloat oldVolume = [change[NSKeyValueChangeOldKey] floatValue];\n\n        if (self.disableSystemVolumeHandler && newVolume == self.initialVolume) {\n            // Resetting volume, skip blocks\n            return;\n        } else if (self.isAdjustingInitialVolume) {\n            if (newVolume == maxVolume || newVolume == minVolume) {\n                // Sometimes when setting initial volume during setup the callback is triggered incorrectly\n                return;\n            }\n            self.isAdjustingInitialVolume = NO;\n        }\n\n        CGFloat difference = fabs(newVolume-oldVolume);\n\n        JPSLog(@\"Old Vol:%f New Vol:%f Difference = %f\", (double)oldVolume, (double)newVolume, (double) difference);\n\n        if (_exactJumpsOnly && difference < .062 && (newVolume == 1. || newVolume == 0)) {\n            JPSLog(@\"Using a non-standard Jump of %f (%f-%f) which is less than the .0625 because a press of the volume button resulted in hitting min or max volume\", difference, oldVolume, newVolume);\n        } else if (_exactJumpsOnly && (difference > .063 || difference < .062)) {\n            JPSLog(@\"Ignoring non-standard Jump of %f (%f-%f), which is not the .0625 a press of the actually volume button would have resulted in.\", difference, oldVolume, newVolume);\n            [self setInitialVolume];\n            return;\n        }\n        \n        if (newVolume > oldVolume) {\n            if (self.upBlock) self.upBlock();\n        } else {\n            if (self.downBlock) self.downBlock();\n        }\n\n        if (!self.disableSystemVolumeHandler) {\n            // Don't reset volume if default handling is enabled\n            return;\n        }\n\n        // Reset volume\n        [self setSystemVolume:self.initialVolume];\n    } else {\n        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];\n    }\n}\n\n#pragma mark - System Volume\n\n- (void)setSystemVolume:(CGFloat)volume {\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wdeprecated-declarations\"\n    [[MPMusicPlayerController applicationMusicPlayer] setVolume:(float)volume];\n#pragma clang diagnostic pop\n}\n\n@end\n"
  },
  {
    "path": "JPSVolumeButtonHandler.podspec",
    "content": "Pod::Spec.new do |s|\n  s.name     = 'JPSVolumeButtonHandler'\n  s.version  = '1.0.5'\n  s.platform = :ios, \"7.0\"\n  s.license  = 'MIT'\n  s.summary  = 'JPSVolumeButtonHandler provides an easy block interface to hardware volume buttons on iOS devices. Perfect for camera apps!'\n  s.homepage = 'https://github.com/jpsim/JPSVolumeButtonHandler'\n  s.author   = { 'JP Simard' => 'jp@jpsim.com' }\n  s.source   = { :git => 'https://github.com/jpsim/JPSVolumeButtonHandler.git', :tag => s.version.to_s }\n\n  s.description = 'JPSVolumeButtonHandler provides an easy block interface to hardware volume buttons on iOS devices. Perfect for camera apps! Features:\\n* Run blocks whenever a hardware volume button is pressed\\n* Volume button presses don\\'t affect system audio\\n* Hide the HUD typically displayed on volume button presses\\n* Works even when the system audio level is at its maximum or minimum, even when muted'\n\n  s.source_files = 'JPSVolumeButtonHandler/*.{h,m}'\n  s.framework    = 'MediaPlayer', 'AVFoundation'\n  s.requires_arc = true\nend\n"
  },
  {
    "path": "LICENSE.txt",
    "content": "Copyright 2014 JP Simard - http://jpsim.com\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n\"Software\"), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\nLIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "Package.swift",
    "content": "// swift-tools-version:5.4\n// The swift-tools-version declares the minimum version of Swift required to build this package.\n\nimport PackageDescription\n\nlet package = Package(\n    name: \"JPSVolumeButtonHandler\",\n    platforms: [.iOS(.v11)],\n    products: [\n        // Products define the executables and libraries a package produces, and make them visible to other packages.\n        .library(\n            name: \"JPSVolumeButtonHandler\",\n            targets: [\"JPSVolumeButtonHandler\"]\n        ),\n    ],\n    dependencies: [],\n    targets: [\n        // Targets are the basic building blocks of a package. A target can define a module or a test suite.\n        // Targets can depend on other targets in this package, and on products in packages this package depends on.\n        .target(\n            name: \"JPSVolumeButtonHandler\",\n            dependencies: [],\n            path: \"JPSVolumeButtonHandler/\"\n        )\n    ]\n)\n"
  },
  {
    "path": "README.md",
    "content": "# JPSVolumeButtonHandler\n\n`JPSVolumeButtonHandler` provides an easy block interface to hardware volume buttons on iOS devices. Perfect for camera apps! Used in [`JPSImagePickerController`](https://github.com/jpsim/JPSImagePickerController).\n\nFeatures:\n\n* Run blocks whenever a hardware volume button is pressed\n* Volume button presses don't affect system audio\n* Hide the HUD typically displayed on volume button presses\n* Works even when the system audio level is at its maximum or minimum, even when muted\n\n## Installation\n\n### Swift Package Manager (SPM)\n\nAdd: `https://github.com/jpsim/JPSVolumeButtonHandler.git` (`master` branch) to your \"Package Dependencies\" in XCode.\n\nOr add: `.package(url: \"https://github.com/jpsim/JPSVolumeButtonHandler.git\", branch: \"master\")` to your swift package file.\n\n### From CocoaPods\n\nAdd `pod 'JPSVolumeButtonHandler'` to your Podfile.\n\n### Manually\n\nDrag the `JPSVolumeButtonHandler` folder into your project and link the MediaPlayer and AVFoundation frameworks to your project.\n\n## Usage\n\nSet your blocks to be run when the volume buttons are pressed:\n\n```objective-c\nself.volumeButtonHandler = [JPSVolumeButtonHandler volumeButtonHandlerWithUpBlock:^{\n\t// Volume Up Button Pressed\n} downBlock:^{\n\t// Volume Down Button Pressed\n}];\n```\n\nTo enable/disable the handler:\n\n```objective-c\n// Start\n[self.volumeButtonHandler startHandler:YES]; \n// Stop\n[self.volumeButtonHandler stopHandler];\n```\n\nTo change audio session category (by default `AVAudioSessionCategoryPlayAndRecord`):\n\n```objective-c\n// Set category\nself.volumeButtonHandler.sessionCategory = AVAudioSessionCategoryAmbient; \n```\n\nTo change the audio session category options (by default `AVAudioSessionCategoryOptionMixWithOthers`):\n\n```objective-c\nself.volumeButtonHandler.sessionOptions = AVAudioSessionCategoryOptionAllowBluetooth|AVAudioSessionCategoryOptionMixWithOthers;\n```\n\nNote that not all options are compatible with all category options. See `AVAudioSession` documentation for details.\n\n## License\n\nThis project is under the MIT license.\n"
  }
]