Full Code of jpsim/JPSVolumeButtonHandler for AI

master f0606143c3d9 cached
7 files
15.6 KB
3.7k tokens
1 requests
Download .txt
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 <Foundation/Foundation.h>
#import <AVFoundation/AVFoundation.h>

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 <MediaPlayer/MediaPlayer.h>

// 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.
Download .txt
gitextract_m331ra9_/

├── .gitignore
├── JPSVolumeButtonHandler/
│   ├── JPSVolumeButtonHandler.h
│   └── JPSVolumeButtonHandler.m
├── JPSVolumeButtonHandler.podspec
├── LICENSE.txt
├── Package.swift
└── README.md
Condensed preview — 7 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (17K chars).
[
  {
    "path": ".gitignore",
    "chars": 212,
    "preview": ".DS_Store\n*/build/*\n*.pbxuser\n!default.pbxuser\n*.mode1v3\n!default.mode1v3\n*.mode2v3\n!default.mode2v3\n*.perspectivev3\n!de"
  },
  {
    "path": "JPSVolumeButtonHandler/JPSVolumeButtonHandler.h",
    "chars": 1240,
    "preview": "//\n//  JPSVolumeButtonHandler.h\n//  JPSImagePickerController\n//\n//  Created by JP Simard on 1/31/2014.\n//  Copyright (c)"
  },
  {
    "path": "JPSVolumeButtonHandler/JPSVolumeButtonHandler.m",
    "chars": 9450,
    "preview": "//\n//  JPSVolumeButtonHandler.m\n//  JPSImagePickerController\n//\n//  Created by JP Simard on 1/31/2014.\n//  Copyright (c)"
  },
  {
    "path": "JPSVolumeButtonHandler.podspec",
    "chars": 1037,
    "preview": "Pod::Spec.new do |s|\n  s.name     = 'JPSVolumeButtonHandler'\n  s.version  = '1.0.5'\n  s.platform = :ios, \"7.0\"\n  s.licen"
  },
  {
    "path": "LICENSE.txt",
    "chars": 1068,
    "preview": "Copyright 2014 JP Simard - http://jpsim.com\n\nPermission is hereby granted, free of charge, to any person obtaining\na cop"
  },
  {
    "path": "Package.swift",
    "chars": 918,
    "preview": "// swift-tools-version:5.4\n// The swift-tools-version declares the minimum version of Swift required to build this packa"
  },
  {
    "path": "README.md",
    "chars": 2051,
    "preview": "# JPSVolumeButtonHandler\n\n`JPSVolumeButtonHandler` provides an easy block interface to hardware volume buttons on iOS de"
  }
]

About this extraction

This page contains the full source code of the jpsim/JPSVolumeButtonHandler GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 7 files (15.6 KB), approximately 3.7k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!