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