iOS 音量键的监听的方案

在iOS上,如何监听用户的音量键行为

需求

用户点击音量键的两个需求:

  • 音量达到最大时,用户继续点击音量增加键,支持进一步放大音量;
  • 音量达到最小时,用户继续点击音量减小键,支持让音量达到无声;

拆解

  1. 监听
  2. 增强

监听,直接祭出最佳方案

监听 iOS 系统私有通知: @“SystemVolumeDidChange”
以 iOS 15.0 为界,iOS 15.0 前后的通知详情 userInfo 有区别:

/// > iOS 15.0 
userInfo = {
    AudioCategory = PhoneCall;
    Reason = ExplicitVolumeChange;
    SequenceNumber = 1498;
    Volume = "0.125";
}
/// < iOS 15.0 
userInfo = {
  	AudioCategory = PhoneCall;
  	AudioVolume = 1;
  	AudioVolumeChangeReason = ExplicitVolumeChange;
  	UserVolumeAboveEUVolumeLimit = 0;
}

通知详情

这个通知不只在音量变化时会触发,还会在音量相关类别变化时收到此通知, userInfo 中包含“AudioCategory”、“Reason” 、“SequenceNumber” 、“Volume” 四个字段:

AudioCategory

这个字段指示当前音量所处的类别,主要已知包含几种情况:

  • AudioCategory = "Audio/Video"
    主要是指媒体音量,即 AVAudioSessionMode 切换为 AVAudioSessionModeDefault 、AVAudioSessionModeMoviePlayback、 AVAudioSessionModeSpokenAudio 、AVAudioSessionModeVideoRecording 这四个之一时。
  • AudioCategory = PhoneCall
    主要是指通话音量,即 AVAudioSessionMode 切换为 AVAudioSessionModeGameChat 、AVAudioSessionModeVideoChat 、AVAudioSessionModeVoiceChat 、AVAudioSessionModeVoicePrompt 这四个之一时。例如我们在使用 PSTN 通话时,调整音量,收到的通知中 AudioCategory = PhoneCall。
  • AudioCategory = Ringtone
    主要是指 PSTN 的来电响铃,即收到一个 PSTN 来电时,如果此时系统是播放铃音的提醒方式,则收到通知中 AudioCategory = Ringtone。
  • AudioCategory = Alarm
    主要是指闹铃的铃音响起
  • AudioCategory = VoiceCommand
    主要是指 siri 的语音播报
  • AudioCategory = FindMyPhone
    主要是指点击控制面板中调整找回设备的方式时

Reason

这个字段指示收到当前这个通知的原因,主要包含

  • Reason = CategoryChange
    主要是指 “AudioCategory”字段发生变化的时候,此时实际并没有音量的调整
  • Reason = RouteChange
    主要是指当前的输出发生变化的时候,例如麦克风播放改为听筒播放。此时实际并没有音量的调整
  • Reason = ExplicitVolumeChange
    主要是指实际的音量调整,例如我们点按物理按键的音量加减键,或者控制面板中调节滑动音量槽。

SequenceNumber

这个字段指示当前通知的序号,由于通知的重复和无序, 要注意特殊情况:

  • 有些情况下会收到连续几个通知,内容相同,但是序号递增
  • 物理按键或控制面板调整音量时,会收到两个序号和内容都相同的通知
  • 有些情况下会 1、2、1、2 这样顺序的通知

Volume

这个字段指示当前上下文环境下的音量值。如果是音量调整后的通知,则只是调整后最新的音量值。

回到需求本身,当我们判断到音量达到最大值 1 时,还能收到 Reason = ExplicitVolumeChange,并且 Volume = 1 的通知,就说明识别到了用户还需要进一步放大音量的场景;当我们判断到通话音量达到最小值(通话音量最小值 0.0625,媒体音量最小值 0),还能收到 Reason = ExplicitVolumeChange,并且 Volume = 0.0625 的通知,说明识别到了用户需要完全无声的场景 。

代码

第一步,监听通知

[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(systemVolumeDidChange:)
                                             name:@"SystemVolumeDidChange"
                                           object:nil];

第二步,响应通知的方法

- (void)systemVolumeDidChange:(NSNotification *)notification {
    NSDictionary *userInfo = [notification userInfo];
    NSString *reason = [userInfo objectForKey:@"Reason"];
    float volume = [[userInfo objectForKey:@"Volume"] floatValue];
    NSString *audioCategory = [userInfo objectForKey:@"AudioCategory"];

    if ([reason isEqualToString:@"ExplicitVolumeChange"]) {
        if ([audioCategory isEqualToString:@"Audio/Video"]) {
            if (volume >= 1.0) {
                [self amplifyVolume];
            } else if (volume <= 0.0625) {
                [self muteVolume];
            }
        }
    }
}

第三步,具体实现

放大音量

- (void)amplifyVolume {
    // 使用应用内部的声音增强功能来进一步放大音量
    // 1. 获取当前正在播放的音频会话
    AVAudioSession *audioSession = [AVAudioSession sharedInstance];
  
    // 2. 使用某种算法增加音频数据的振幅(这里只是一个示例)
    // 例如:audioData = audioData * amplificationFactor;
  
    // 3. 重新播放增强后的音频
    // 假设你有一个名为 'audioPlayer' 的 AVAudioPlayer 对象
    // [audioPlayer play];
}

音量调整为无声

- (void)muteVolume {
    // 使用代码将音量设置为静音
    // 假设你有一个名为 'audioPlayer' 的 AVAudioPlayer 对象
    audioPlayer.volume = 0.0;
}

彩蛋

  • iOS 端可用的打断通知 AVAudioSessionInterruptionNotification 对打断原因没有详细信息, 而这个音量变化通知中的 AudioCategory 字段可以为音频打断提供更多参考信息
  • 音量键的监听可以很方便用于调试,不需要在视图上添加 UI 组件,只需通过监听并实现响应方法,就可以通过物理按键调试不同场景的切换。