59

I'm writing a very simple application that plays a sound when pressing a button. Since that button does not make a lot of sense when the device is set to silence I want to disable it when the device's audio volume is zero. (And subsequently reenable it when the volume is cranked up again.)

I am seeking a working (and AppStore safe) way to detect the current volume setting and get a notification/callback when the volume level changes. I do not want to alter the volume setting.

All this is implemented in my ViewController where said button is used. I've tested this with an iPhone 4 running iOS 4.0.1 and 4.0.2 as well as an iPhone 3G running 4.0.1. Built with iOS SDK 4.0.2 with llvm 1.5. (Using gcc or llvm-gcc doesn't improve anything.) There are no issues during build implementing either way, neither errors nor warnings. Static analyzer is happy as well.

Here is what I've tried so far, all without any success.

Following Apple's audio services documentation I should register an AudioSessionAddPropertyListener for kAudioSessionProperty_CurrentHardwareOutputVolume which should work like this:

// Registering for Volume Change notifications
AudioSessionInitialize(NULL, NULL, NULL, NULL);
returnvalue = AudioSessionAddPropertyListener (

kAudioSessionProperty_CurrentHardwareOutputVolume ,
      audioVolumeChangeListenerCallback,
      self
);

returnvalue is 0, which means that registering the callback worked.

Sadly, I never get a callback to my function audioVolumeChangeListenerCallback when I press the volume buttons on my device, the headset clicker or flip the ringer-silent switch.

When using the exact same code for registering for kAudioSessionProperty_AudioRouteChange (which is used as an analogous sample project in WWDC videos, Developer documentation and on numerous sites on the interwebs) I actually do get a callback when changing the audio route (by plugging in/out a headset or docking the device).

A user named Doug opened a thread titled iPhone volume changed event for volume already max where he claimed that he is sucessfully using this way (unless the volume would not actually change because it is already set to maximum). Still, it doesn't work for me.

Another way I have tried is to register at NSNotificationCenter like this.

// sharedAVSystemController 
AudioSessionInitialize(NULL, NULL, NULL, NULL);
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter addObserver:self
                                         selector:@selector(volumeChanged:) 
                                             name:@"AVSystemController_SystemVolumeDidChangeNotification" 
                                           object:nil];

This should notify my method volumeChanged of any SystemVolume changes but it doesn't actually do so.

Since common belief tells me that if one is working too hard to achieve something with Cocoa one is doing something fundamentally wrong I'm expecting to miss something here. It's hard to believe that there is no simple way to get the current volume level, yet I haven't been able to find one using Apple's documentation, sample code, Google, Apple Developer Forums or by watching WWDC 2010 videos.

4

10 回答 10

69

您是否有可能为 volumeChanged: 方法做错了签名?这对我有用,倾倒在我的 appdelegate 中:

- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    [[NSNotificationCenter defaultCenter]
     addObserver:self
     selector:@selector(volumeChanged:)
     name:@"AVSystemController_SystemVolumeDidChangeNotification"
     object:nil];
}

- (void)volumeChanged:(NSNotification *)notification
{
    float volume =
    [[[notification userInfo]
      objectForKey:@"AVSystemController_AudioVolumeNotificationParameter"]
     floatValue];

    // Do stuff with volume
}

每次按下按钮时都会触发我的 volumeChanged: 方法,即使音量没有因此而改变(因为它已经处于最大/最小)。

于 2011-06-24T18:25:05.110 回答
56

此处某些答案使用的AudioSessionAPI 自 iOS 7 起已弃用。它已被替换为AVAudioSession,它公开outputVolume了系统范围输出音量的属性。正如文档中所指出的,当音量发生变化时,可以使用 KVO 接收通知来观察这一点:

0.0 到 1.0 范围内的值,其中 0.0 表示最小音量,1.0 表示最大音量。

系统范围的输出音量只能由用户直接设置;要在您的应用程序中提供音量控制,请使用 MPVolumeView 类。

您可以使用键值观察来观察此属性值的变化。

您需要确保应用程序的音频会话处于活动状态才能使其正常工作:

let audioSession = AVAudioSession.sharedInstance()
do {
    try audioSession.setActive(true)
    startObservingVolumeChanges()
} catch {
    print(“Failed to activate audio session")
}

因此,如果您只需要查询当前系统卷:

let volume = audioSession.outputVolume

或者我们可以收到这样的更改通知:

private struct Observation {
    static let VolumeKey = "outputVolume"
    static var Context = 0

}

func startObservingVolumeChanges() {
    audioSession.addObserver(self, forKeyPath: Observation.VolumeKey, options: [.Initial, .New], context: &Observation.Context)
}

override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
    if context == &Observation.Context {
        if keyPath == Observation.VolumeKey, let volume = (change?[NSKeyValueChangeNewKey] as? NSNumber)?.floatValue {
            // `volume` contains the new system output volume...
            print("Volume: \(volume)")
        }
    } else {
        super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
    }
}

不要忘记在被释放之前停止观察:

func stopObservingVolumeChanges() {
    audioSession.removeObserver(self, forKeyPath: Observation.VolumeKey, context: &Observation.Context)
}
于 2015-03-20T16:03:08.290 回答
6
-(float) getVolumeLevel
{
    MPVolumeView *slide = [MPVolumeView new];
    UISlider *volumeViewSlider;

    for (UIView *view in [slide subviews]){
        if ([[[view class] description] isEqualToString:@"MPVolumeSlider"]) {
            volumeViewSlider = (UISlider *) view;
        }
    }

    float val = [volumeViewSlider value];
    [slide release];

    return val;
}

这应该可以让您获得当前的音量。1 为最大音量,0 为无音量。注意:无需显示 UI 元素即可使用。另请注意,当前音量级别与耳机或扬声器相关(意思是,两个音量级别不同,这可以让您了解当前正在使用的设备。这不能回答您关于接收音量何时更改通知的问题。

于 2012-02-04T03:41:01.057 回答
4

你用 AudioSessionSetActive 开始音频会话了吗

于 2010-09-06T13:05:33.633 回答
2

使用 AVAudioSession 添加到 Stuart 的答案以说明 Swift 3 中的一些更改。我希望代码能够清楚地说明每个组件的去向。

override func viewWillAppear(_ animated: Bool) {
    listenVolumeButton()
}

func listenVolumeButton(){
   let audioSession = AVAudioSession.sharedInstance()
   do{
       try audioSession.setActive(true)
       let vol = audioSession.outputVolume
       print(vol.description) //gets initial volume
     }
   catch{
       print("Error info: \(error)")
   }
   audioSession.addObserver(self, forKeyPath: "outputVolume", options: 
   NSKeyValueObservingOptions.new, context: nil)
}

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    if keyPath == "outputVolume"{
        let volume = (change?[NSKeyValueChangeKey.newKey] as 
        NSNumber)?.floatValue
        print("volume " + volume!.description)
    }
}

 override func viewWillDisappear(_ animated: Bool) {
     audioSession.removeObserver(self, forKeyPath: "outputVolume")
 }
于 2017-03-26T18:16:58.217 回答
2

斯图尔特优秀答案的 Swift 3 版本:

let audioSession = AVAudioSession.sharedInstance()

do {
    try audioSession.setActive(true)
    startObservingVolumeChanges()
} 
catch {
    print("Failed to activate audio session")
}

let volume = audioSession.outputVolume

private struct Observation {
    static let VolumeKey = "outputVolume"
    static var Context = 0
}

func startObservingVolumeChanges() {
    audioSession.addObserver(self, forKeyPath: Observation.VolumeKey, options: [.Initial, .New], context: &Observation.Context)
}

override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
    if context == &Observation.Context {
        if keyPath == Observation.VolumeKey, let volume = (change?[NSKeyValueChangeNewKey] as? NSNumber)?.floatValue {
            // `volume` contains the new system output volume...
            print("Volume: \(volume)")
        }
    } else {
        super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
    }
}
于 2017-08-15T12:39:14.030 回答
2

斯威夫特 5 / iOS 13

在我的测试中,我发现与系统卷交互的最可靠方法是使用 anMPVolumeView作为每个操作的中介。您已经需要在视图层次结构中的某个位置拥有此视图,以使系统隐藏音量更改 HUD。

在 setup 期间,可能在内部viewDidLoad(),创建您的MPVolumeView(如果您不想实际使用系统提供的控件,则在屏幕外):

let systemVolumeView = MPVolumeView(frame: CGRect(x: -CGFloat.greatestFiniteMagnitude, y: 0, width: 0, height: 0))
myContainerView.addSubview(systemVolumeView)
self.systemVolumeSlider = systemVolumeView.subviews.first(where:{ $0 is UISlider }) as? UISlider

获取设置音量:

var volumeLevel:Float {
    get {
        return self.systemVolumeSlider.value
    }
    set {
        self.systemVolumeSlider.value = newValue
    }
}

观察音量的变化(包括硬件按钮):

self.systemVolumeSlider.addTarget(self, action: #selector(volumeDidChange), for: .valueChanged)

@objc func volumeDidChange() {
    // Handle volume change
}
于 2020-07-11T21:17:24.970 回答
1

我认为这取决于其他实现。例如,如果您使用滑块来控制音量,您可以进行检查操作UIControlEventValueChanged,如果您获得 0 值,您可以将按钮设置为隐藏或禁用。

就像是:

[MusicsliderCtl addTarget:self action:@selector(checkZeroVolume:)forControlEvents:UIControlEventValueChanged];

其中 voidcheckZeroVolume可以比较实际交易量,因为它是在任何交易量变化后触发的。

于 2010-09-06T13:23:33.410 回答
1

斯威夫特 4

func startObservingVolumeChanges() {
    avAudioSession.addObserver(self, forKeyPath: Observation.VolumeKey, options: [.initial, .new], context: &Observation.Context)
}

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    if context == &Observation.Context {
        if keyPath == Observation.VolumeKey, let volume = (change?[NSKeyValueChangeKey.newKey] as? NSNumber)?.floatValue {
            print("\(logClassName): Volume: \(volume)")
        }
    } else {
        super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
    }
}

func stopObservingVolumeChanges() {
    avAudioSession.removeObserver(self, forKeyPath: Observation.VolumeKey, context: &Observation.Context)
}

然后你打电话

var avAudioSession = AVAudioSession.sharedInstance()
try? avAudioSession.setActive(true)
startObservingVolumeChanges()
于 2018-01-19T14:11:02.333 回答
0

进入设置->声音并检查“使用按钮更改”。如果它关闭,则按下音量按钮时系统音量不会改变。也许这就是你没有收到通知的原因。

于 2014-04-17T06:43:03.637 回答