在使用懒猪计划的时候,发现了开启白噪声循环播放的时候会有明显的空白期,于是就查看了音频播放的问题,顺便也对中间引用的HDCommonTools这个库完善下。
在iOS音频播放中,提供了多种接口,最常用的就是AVAudioPlayer
和AVPlayer
。
相比较而言,AVAudioPlayer
使用简单,接口多,只能播放本地音频,不支持流媒体播放,每一个audioplayer对象就是一段音频,而AVPlayer
接口较少,但是可以使用AVPlayer播放本地音频和支持流媒体播放,所以两者比较中,在HDCommonTools中播放音频的时候,选用了AVPlayer。
先说说循环播放的几个网络方案
1、AVAudioPlayer的默认接口
在如果使用AVAudioPlayer
的话,已经提供了循环播放的接口,例如
audioPlayer.numberOfLoops = 1;//设置音乐播放次数 -1为一直循环
[audioPlayer prepareToPlay];
[audioPlayer play]; //播放
2、AVPlayer的完成通知
这个方案就是通过AVPlayerItemDidPlayToEndTimeNotification
这个字段,获取到AVPlayer的播放完成通知,然后在完成通知中,去将AVPlayer的进度到kCMTimeZero
最开始
//给AVPlayerItem添加播放完成通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playbackFinished:) name:AVPlayerItemDidPlayToEndTimeNotification object:avPlayer.currentItem];
/**
* 播放完成通知
*
* @param notification 通知对象
*/
- (void)playbackFinished:(NSNotification *)notification {
NSLog(@"视频播放完成.");
// 播放完成后重复播放
// 跳到最新的时间点开始播放
[avPlayer seekToTime: kCMTimeZero];
[avPlayer play];
}
这两个方案都是将播放器的播放进度在结束的时候重置,然后再继续播放,但是这两种方案在循环的时候,都会有明显的大概1s的空白期。所以这两个虽然也可以实现循环播放的目的,但是效果并不理想。
3、通过属性观察
参考两个前者方案,空白期的产生猜测是因为结束到重新加载需要时间,所以可以考虑在即将播放完成的时候提前加载新的播放器,那么就需要知道什么时候将要结束。
最开始考虑检测播放item的时长和player的状态,在快播放完成的时候去做一个循环的方案。
[item addObserver:self forKeyPath:@"duration" options:NSKeyValueObservingOptionNew context:nil];
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
AVPlayerItem *item = object;
CGFloat duration = item.duration.value / item.duration.timescale;
[NSTimer scheduledTimerWithTimeInterval:duration - 1 repeats:YES block:^(NSTimer * _Nonnull timer) {
//重新去初始化播放
}];
}
但是这样怎么去重新播放呢,如果依旧是调用seekToTime
的话,还是会发现有空白情况,那么最好的方案就是重新创建另外一个avPlayer,然后两个去循环播放,但是这样的haul,item就需要重新创建一个新的,然后去添加观察,这个思路对,但是需要重新改良。
所以可以使用下面这个通过间隔不断的去检测播放的时长:
[avPlayer addPeriodicTimeObserverForInterval:CMTimeMake(1.0, 1.0) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {}];
解决方案
因为加载中会有空白期,所以这里在刚开始加载的时候创建两个播放器,当一个播放器播放即将完成时,去播放另外那个播放器,同时将现在这个播放器停止播放,进度重置。
//第一个
[avPlayer addPeriodicTimeObserverForInterval:CMTimeMake(1.0, 1.0) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
CGFloat duration = avPlayer.currentItem.duration.value / avPlayer.currentItem.duration.timescale; //视频总时间
CGFloat currentTime =avPlayer.currentItem.currentTime.value / avPlayer.currentItem.currentTime.timescale;//视频当前运行时间
NSLog(@"第一个准备好播放了,总时间:%f,%f",duration,currentTime);
if (currentTime >= duration - 2.0) {
if (avRepeatPlayer) {
[avRepeatPlayer play];
}
}
if (currentTime >= duration - 1.0) {
[item seekToTime:kCMTimeZero completionHandler:^(BOOL finished) {
if (finished) {
[avPlayer seekToTime:kCMTimeZero];
[avPlayer pause];
}
}];
}
}];
//新建一个重复的去提前播放
AVPlayerItem *item2 = [AVPlayerItem playerItemWithURL:musicUrl];
avRepeatPlayer = [[AVPlayer alloc] initWithPlayerItem:item2];
[avRepeatPlayer addPeriodicTimeObserverForInterval:CMTimeMake(1.0, 1.0) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
CGFloat duration = avRepeatPlayer.currentItem.duration.value / avRepeatPlayer.currentItem.duration.timescale; //视频总时间
CGFloat currentTime =avRepeatPlayer.currentItem.currentTime.value / avRepeatPlayer.currentItem.currentTime.timescale;//视频当前运行时间
NSLog(@"第二个准备好播放了,总时间:%f,%f",duration,currentTime);
if (currentTime >= duration - 2.0) {
if (avPlayer) {
[avPlayer play];
}
}
if (currentTime >= duration - 1.0) {
[item2 seekToTime:kCMTimeZero completionHandler:^(BOOL finished) {
if (finished) {
[avRepeatPlayer seekToTime:kCMTimeZero];
[avRepeatPlayer pause];
}
}];
}
}];
该功能在HDCommonTools1.3.2中已经实现,如果还是不会使用,可以现在最新代码测试下。
参考文章
版权属于:东哥笔记 - DongGe.org
本文链接:http://dongge.org/blog/829.html
自2017年12月26日起,『转载以及大段采集进行后续编辑』须注明本文标题和链接!否则禁止所有转载和采集行为!
8 条评论
把NSLog打开应该也可以看出问题
切换下一首时候还是有点问题
具体是什么问题呢?我等下测试下,之前这个循环播放加载的搞的也是烦
我给你个白噪声试一下?你把log打开。第一次或者第二次的,结束前一秒有两次重复的
如果在AVPlayer的完成通知之后再播放第二个是不会有重复播放,之所以这么做,是因为如果等结束之后再去播放的话会因为加载的过程,造成短暂空白播放问题,所以会提前1s循环第二个部分。因为怕能耗问题,所以设置的1s监测CMTimeMake(1.0, 1.0),修改成CMTimeMake(1.0, 1000.0),然后提前时间减小即可。我在github和cocoapods已经提交了2.3.1版本,你可以尝试下
我这边试过了,建议1秒监测10次,一个是性能问题。还有监测太多次过渡那里还是有点瑕疵。
如果是重音的话,不妨试试把拼接时候播放器的音量修改为0,0.2s的过渡期应该影响不大的
好的,我拿这边白噪声试一下