Skip to content

2018.01

Minya edited this page Jan 14, 2018 · 4 revisions

为什么音频播放器突然没声音了呢?

做音频的同学可能都会遇到播放的音频突然没声音的情况,遇到这种情况后,一般控制台会抛出下面的错误:

[AVAudioSession setActive:withOptions:error:]: Deactivating an audio session that has running I/O. All I/O should be stopped or paused prior to deactivating the audio session.

遇到这个错误,会导致音频不能正常播放的情况。出现这种情况的主要原因当你设置

[[AVAudioSession sharedInstance] setActive:NO withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation error:nil];

时还有某些操作占用了 AVAudioSession 权限,必须暂停或停止对 AVAudioSession 的使用。比如使用 AVAudioPlayer 播放某一音频时,此时音频正在播放,直接设置 AVAudioSessionactiveNO,就会报上面提到的错误。而正确的做法是先暂停播放,再设置 AVAudioSessionactiveNO。其正确的做法像下面代码所示,这样的好处是,当遇到设置失败后可以第一时间知道出错的时间点。

NSError *error;
BOOL isSuccess = [[AVAudioSession sharedInstance] setActive:active withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation error:&error];
if (isSuccess) {
   NSLog(@"恭喜你成功设置");
} else {
   NSLog(@"设置失败");
}

当然如果应用中有多个地方使用 AVAudioSession,建议项目中统一处理 AVAudioSessionactive,这样避免出现错误,一旦出现错误,调试起来就非常费劲。

iOS 中音量控制解惑

iOS 中音量中其实也有好多小窍门,这个小集帮你解惑。iOS 中主要有2个地方可以控制音量,一个是系统音量,用户主动按音量键,调整音量,这种方式会显示系统音量提示框;另一个是播放器的音量,比如通过 AVAudioPlayer 调整音量,这种不会显示系统提示音量框。

如何调节音量时不显示系统音量提示框

主要原理就是获取系统音量 View,并把它让用户不可见。但注意一点,你不能把 MPVolumeViewhidden 属性设置为 YES,这样导致的结果是用户调整音量时任然会显示系统音量提示框,如下代码所示。

_volumeView = [[MPVolumeView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
_volumeView.backgroundColor = [UIColor yellowColor];

// 如果设置了 Hidden 为 YES,那么修改音量时会弹出系统音量框
_volumeView.hidden = NO;
_volumeView.alpha = 0.01;
for (UIView *view in [_volumeView subviews]){
if ([view.class.description isEqualToString:@"MPVolumeSlider"]){
    self.volumeSlider = (UISlider*)view;
    break;
   }
}
[self.view addSubview:_volumeView];

获取系统音量

方法一:通过 self.volumeSlider 获取

如果想获取系统音量,可以通过第一种方式,self.volumeSlider.value 来获取,但是你发现第一次为 0,这很纠结,这样导致的结果就是获取的系统音量不准确。这是因为初始 MPVolumeView 时,volumeSlider.value 还没有赋值,如下图所示:

可以发现,音量是后来通过 [MPVolumeController updateVolumeValue] 来更新的。所以我们可以通过监听 self.volumeSlide 值改变时的事件,达到获取系统音量的目的。

[self.volumeSlider addTarget:self action:@selector(sliderValueDidChange:) forControlEvents:UIControlEventValueChanged];

方法二:通过 AVAudioSession 获取

[[AVAudioSession sharedInstance] outputVolume];

这种方法直接了当。

自定义音量控件

如果想自定义音量控件,可以监听音量的变化,并且通过第一种方法隐藏系统音量提示框。通过监听通知,达到监听音量变化的效果。

监听音量变化

监听音量变化,通过监听通知

AVSystemController_SystemVolumeDidChangeNotification

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

最终结果 AVSystemController_AudioVolumeNotificationParameter 表示音量的值,这里需要注意的是 "AVSystemController_AudioVolumeChangeReasonNotificationParameter" = ExplicitVolumeChange; 这个值,它会由于不同的场景,有不同的值。ExplicitVolumeChange 是用户点击音量按钮,CategoryChange 是用户按 home 键调起 SiriRouteChange 这个时路线修改(不太清楚,什么情况下触发的)。

AVSystemController_SystemVolumeDidChangeNotification; object = <AVSystemController: 0x1c4001dc0>; userInfo = {
    "AVSystemController_AudioCategoryNotificationParameter" = "Audio/Video";
    "AVSystemController_AudioVolumeChangeReasonNotificationParameter" = ExplicitVolumeChange;
    "AVSystemController_AudioVolumeNotificationParameter" = "0.5625";
    "AVSystemController_UserVolumeAboveEUVolumeLimitNotificationParameter" = 0;
}}

注意点

如果通过代码修改了 self.volumeSlidevalue,那么会显示出系统音量框,如果你发现某个页面突然蹦出一个系统音量框,原因大多数是你修改了这个值。

动态修改应用程序的icon

作者: 南峰子_老驴

偶然看到 Price Tag 有个替换应用图标的功能,如图,研究了一下。

这个功能是在 iOS 10.3 后新增的,主要的 API 如下所示:

@interface UIApplication (UIAlternateApplicationIcons)
// If false, alternate icons are not supported for the current process.
@property (readonly, nonatomic) BOOL supportsAlternateIcons NS_EXTENSION_UNAVAILABLE("Extensions may not have alternate icons") API_AVAILABLE(ios(10.3), tvos(10.2));

// Pass `nil` to use the primary application icon. The completion handler will be invoked asynchronously on an arbitrary background queue; be sure to dispatch back to the main queue before doing any further UI work.
- (void)setAlternateIconName:(nullable NSString *)alternateIconName completionHandler:(nullable void (^)(NSError *_Nullable error))completionHandler NS_EXTENSION_UNAVAILABLE("Extensions may not have alternate icons") API_AVAILABLE(ios(10.3), tvos(10.2));

// If `nil`, the primary application icon is being used.
@property (nullable, readonly, nonatomic) NSString *alternateIconName NS_EXTENSION_UNAVAILABLE("Extensions may not have alternate icons") API_AVAILABLE(ios(10.3), tvos(10.2));
@end

只读属性 supportsAlternateIcons 用于判断系统是否允许修改 App 图标,只有在允许的情况下才能修改。-setAlternateIconName:completionHandler: 用于执行修改操作,如果 iconName 设置为 nil,则恢复为主图标,使用方式如下代码所示:

- (IBAction)changeIcon:(UIButton *)sender {
    if ([[UIApplication sharedApplication] supportsAlternateIcons]) {
        
        NSString *iconName = nil;
        if (sender.tag == 1) {
            iconName = @"rocket";
        } else if (sender.tag == 2) {
            iconName = @"pin";
        }
        
        [[UIApplication sharedApplication] setAlternateIconName:iconName completionHandler:^(NSError * _Nullable error) {
            
        }];
    }
}

除了调用 API 外,最主要的还需要在 info.plist 中配置 CFBundleIcons 项,这是一个字典,可包含 CFBundlePrimaryIconCFBundleAlternateIconsUINewsstandIcon 三个键。

CFBundlePrimaryIcon 为主图标,即 Assets.xcassetsAppIcon 的信息,一般置空。CFBundleAlternateIcons 即用于设置替换图标,具体的配置项描述可以参考官方文档 ,通常的配置如图所示。

这里需要注意的是,替换图标应该放在工程的某个目录下,而不放在 Assets.xcassets 中,如图所示。

iOS 关于音频播放调研

作者: Lefe_x

由于最近做音频方面的工作,就调研了一下关于音频播放的一些知识,中间也走过不少弯路,希望这篇小集能对关注我们的同学一点启示,少走一些弯路。最后提供一份我看过的资料。这里关于音频播放简单做一个总结。iOS 中音频播放有以下 5 种方式(如果你有更多的方式告诉我,非常感激),它们的使用场景各不同。

[1] 播放小于 30s 的音频: AudioServicesPlaySystemSound 可以播放小于等于 30s 的音频,主要用于播放一些提示音,你可以利用 AudioServicesPlaySystemSoundWithCompletion 的值播放完成的 callback。它有以下特点:

  • 使用系统音量,不能修改播放音量;
  • 立刻开始播放,不能暂停;
  • 不支持快进播放,也不可以循环播放;
  • 同一时刻只能播放一个音频;
  • 只能通过手机播放音频,不能通过其它设备输出,比如不能通过车载播放。

查看更多的系统声音ID

[2] AVAudioPlayer 播放本地的音频,或者已加载到内存中的音频流,主要用于播放本地的一些音频文件。注意它不能播放网络音频。它有以下特点:

  • 可以从任意位置播放,可快进,快退;
  • 可以循环播放;
  • 可以同时播放多个音频;
  • 可以控制播放速率;

[3] AVPlayer 可以播放本地和网络音频,也可以播放视频,它支持流媒体播放,也就是说我们可以用它来做边下别播的使用场景。

[4] AVQueuePlayerAVPlayer 的子类,它含有一个队列,主要用来播放一个音视频队列。

[5] Audio Queue 主要用来播放音频,录音,它比较底层,会有更多的控制权,如果 APP 主要功能是基于音频播放,推荐使用这个。

总的来说,如果普通的本地音频播放,可以选择 AVAudioPlayer ,这个不需要了解更多的音频知识,就可以达到一个基本的播放;如果想做流媒体播放,建议使用 AVPlayer + Local Server 的方式,类似于唱吧目前开源的方式。当然也可以选择 Audio Queue,不过这个难度比较高,需要对音频播放有一个整体的了解,推荐使用三方库 FreeStream,不过需要一些 C++ 的知识,因为使用过程中有一些坑需要填,这样不得不阅读源码。最后推荐一些不错的文章。

官方 Audio Queue

官方 AudioSession

@pp锅的码农生活 博客

@cy_zju 博客

iOS中NSArray/NSSet的一些巧妙用法

作者: Vong_HUST

最近用到很多操作集合类型的方法,这里总结分享一下,也欢迎大家一起补充。

  • 假设我们已经有一个 NSArray<Model *> 类型的数组,但是我们想把这个数组中的 Model 的某个属性取出组成一个新的数组,一般情况下可能是直接去遍历,但是 NSArray/NSSet 有一个更便捷的方法 valueForKey:,可以快速取出对应属性组成的数组。但是有个问题就是这个方法的效率比 for 循环低,数据量不大的时候使用还是没有问题的。如下面两张图:

  • 要取两个数组的交集的时候,可以先将 NSArray 转换成 NSMutableSet,再通过取二者交集即可。但是需要注意一点的是数组中的元素最好复写一下 isEqual:hash 方法,保证取交集后的结果是正确的。

  • 要将数组内元素排序或者过滤等操作,可以结合 NSSortDescriptorNSPredicate 使用,可以避免掉大量冗余的 for 循环之类的代码。关于 NSPredicate 的用法可以参考 NSHipsterRealmCheetsheet

  • 关于图中 valueForKey: 的参数为什么不直接用 @"name" 而是用 NSStringFromSelector(@selector(name)),是因为后者会有代码提示可以避免硬编码带来的错误,同时后续该 key 换名字了之后,会有对应的警告。这个也是从 AFNetworking 中学到的。如图所示:

对清除图片缓存的思考

作者: 高老师很忙

众所周知,使用 +[UIImage imageNamed:] 方法加载图片是会进图片缓存的,清除缓存是系统触发,并没有为我们提供API;使用 +[UIImage imageWithContentsOfFile:] 方法加载图片是不会进入图片缓存的。如果想要有图片缓存机制,并且能手动清除图片缓存,我们可以这样做:

+[UIImage imageWithContentsOfFile:] 方向下手: 我们可以自己维护一套图片缓存,Swizzle +[UIImage imageWithContentsOfFile:] 方法加入缓存机制。加载图片后,加入到 NSCache 缓存,再次取该图片时,优先取 NSCache 内的缓存,如果缓存内没有再去真正加载。NSCacheMemory Warning 的时候会自动清除缓存,我们也可以使用 -[NSCache removeAllObjects] 手动清除缓存。当然,你也可以不使用 Swizzle ,写一个 Manager 也是可以的,我只是提供一种思路。

+[UIImage imageNamed:] 方向下手: 在 Memory Warning 或进入后台时,系统会自动帮我们清除使用 +[UIImage imageNamed:] 的图片缓存。我们也可以通过模拟发送 UIApplicationDidReceiveMemoryWarningNotificationUIApplicationDidEnterBackgroundNotification 来清除图片缓存,风险可以根据实际情况来评估。

还可以从私有API来下手,+[UIImage imageNamed:] 系统底层是通过 UIAssetManager 来管理图片缓存的,如下两图所示,我们可以模拟调用 _clearCachedResources 方法来实现清除缓存。

如果有其他思路的,欢迎提出!

小集

Clone this wiki locally