From 01bdc5999cee568bdc0cf6d10963e4e4384d599c Mon Sep 17 00:00:00 2001 From: Jim Crate Date: Sat, 4 Jun 2016 23:50:16 -0400 Subject: [PATCH] Handle Media Remote Volume Mute MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add handling for media remote volume mute. This may not work properly with device hardware mute, and at the very least won’t properly handle the device hardware unmute. --- .../JPSVolumeButtonHandler.h | 9 +++ .../JPSVolumeButtonHandler.m | 68 +++++++++++++++---- 2 files changed, 64 insertions(+), 13 deletions(-) diff --git a/JPSVolumeButtonHandler/JPSVolumeButtonHandler.h b/JPSVolumeButtonHandler/JPSVolumeButtonHandler.h index 52ea2d0..20c8562 100644 --- a/JPSVolumeButtonHandler/JPSVolumeButtonHandler.h +++ b/JPSVolumeButtonHandler/JPSVolumeButtonHandler.h @@ -18,7 +18,16 @@ typedef void (^JPSVolumeButtonBlock)(); // A block to run when the volume down button is pressed @property (nonatomic, copy) JPSVolumeButtonBlock downBlock; +// A block to run when the volume mute button is pressed +@property (nonatomic, copy) JPSVolumeButtonBlock muteBlock; + // Returns a button handler with the specified up/down volume button blocks + (instancetype)volumeButtonHandlerWithUpBlock:(JPSVolumeButtonBlock)upBlock downBlock:(JPSVolumeButtonBlock)downBlock; +// Returns a button handler with the specified volume up/down/mute blocks +// volume mute handling is intended to work with a media remote, and may not function correctly with hardware mute ++ (instancetype)volumeButtonHandlerWithUpBlock:(JPSVolumeButtonBlock)upBlock + downBlock:(JPSVolumeButtonBlock)downBlock + muteBlock:(JPSVolumeButtonBlock)muteBlock; + @end diff --git a/JPSVolumeButtonHandler/JPSVolumeButtonHandler.m b/JPSVolumeButtonHandler/JPSVolumeButtonHandler.m index 2e16a3d..d055045 100644 --- a/JPSVolumeButtonHandler/JPSVolumeButtonHandler.m +++ b/JPSVolumeButtonHandler/JPSVolumeButtonHandler.m @@ -20,6 +20,7 @@ @interface JPSVolumeButtonHandler () @property (nonatomic, assign) CGFloat initialVolume; @property (nonatomic, strong) AVAudioSession * session; @property (nonatomic, strong) MPVolumeView * volumeView; +@property (nonatomic, strong) UISlider * volumeSlider; @property (nonatomic, assign) BOOL appIsActive; @end @@ -37,8 +38,12 @@ - (id)init { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidChangeActive:) name:UIApplicationWillResignActiveNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidChangeActive:) name:UIApplicationDidBecomeActiveNotification object:nil]; - - [self updateInitialVolumeWithDelay]; + + // run these operations later + [[NSOperationQueue mainQueue] addOperationWithBlock:^{ + [self setInitialVolume]; + [self setupVolumeMuteHandling]; + }]; } return self; } @@ -53,6 +58,7 @@ - (void)dealloc { } [[NSNotificationCenter defaultCenter] removeObserver:self]; [self.volumeView removeFromSuperview]; + self.volumeSlider = nil; } - (void)setupSession { @@ -68,7 +74,7 @@ - (void)setupSession { NSLog(@"%@", error); return; } - + // Observe outputVolume [self.session addObserver:self forKeyPath:sessionVolumeKeyPath @@ -109,17 +115,15 @@ - (void)audioSessionInterrupted:(NSNotification*)notification { - (void)disableVolumeHUD { self.volumeView = [[MPVolumeView alloc] initWithFrame:CGRectMake(MAXFLOAT, MAXFLOAT, 0, 0)]; [[[[UIApplication sharedApplication] windows] firstObject] addSubview:self.volumeView]; + + [self.volumeView.subviews enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { + if ([obj isKindOfClass:[UISlider class]]) { + self.volumeSlider = obj; + *stop = YES; + } + }]; } -- (void)updateInitialVolumeWithDelay { - // Wait for the volume view to be ready before setting the volume to avoid showing the HUD - double delayInSeconds = 0.1f; - dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); - dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ - [self setInitialVolume]; - }); -} - - (void)setInitialVolume { self.initialVolume = self.session.outputVolume; if (self.initialVolume > maxVolume) { @@ -131,10 +135,33 @@ - (void)setInitialVolume { } } +- (void)setupVolumeMuteHandling { + // Needs to be done after setting initial volume + if (self.volumeSlider && self.muteBlock) { + // if muted, set to initial volume so everything starts in the same state + if (self.volumeSlider.value == 0) { + self.volumeSlider.value = self.initialVolume; + } + // listen for changes + [self.volumeSlider addTarget:self action:@selector(volumeSliderChanged:) forControlEvents:UIControlEventValueChanged]; + } +} + +- (void)volumeSliderChanged:(id)sender { + if (self.volumeSlider.value == 0.0) { + // mute button hit + // run block and reset volume slider, don't want to stay in mute + if (self.muteBlock) self.muteBlock(); + self.volumeSlider.value = self.initialVolume; + } +} + - (void)applicationDidChangeActive:(NSNotification *)notification { self.appIsActive = [notification.name isEqualToString:UIApplicationDidBecomeActiveNotification]; if (self.appIsActive) { - [self updateInitialVolumeWithDelay]; + [[NSOperationQueue mainQueue] addOperationWithBlock:^{ + [self setInitialVolume]; + }]; } } @@ -149,6 +176,21 @@ + (instancetype)volumeButtonHandlerWithUpBlock:(JPSVolumeButtonBlock)upBlock dow return instance; } +// Returns a button handler with the specified volume up/down/mute blocks ++ (instancetype)volumeButtonHandlerWithUpBlock:(JPSVolumeButtonBlock)upBlock + downBlock:(JPSVolumeButtonBlock)downBlock + muteBlock:(JPSVolumeButtonBlock)muteBlock +{ + JPSVolumeButtonHandler *instance = [[JPSVolumeButtonHandler alloc] init]; + if (instance) { + instance.upBlock = upBlock; + instance.downBlock = downBlock; + instance.muteBlock = muteBlock; + } + return instance; +} + + #pragma mark - KVO - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {