1、首先,音频播放的实现,我这里使用的是AVPlayer
。
AVAudioPlayer
只能播放本地资源。当然还有别的播放方法这里就不列举了。
以下代码实现的是如下图所示的效果,点击图标可以暂停或者继续播放:
需要的属性:
//
@property (nonatomic, strong) AVPlayer *player;
@property (nonatomic, strong) UIImageView *playerView;
@property (nonatomic, strong) UILabel *timeL;
@property (nonatomic, strong) UIImageView *animationV;//加载动画
初始化属性
//
- (AVPlayer *)player
{
if (_player == nil) {
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@%@",kVedioUrl,_playItemInfo[@"ypwj"]]];
AVAsset *avset = [AVAsset assetWithURL:url] ;
AVPlayerItem *item = [AVPlayerItem playerItemWithAsset:avset];
_item = item;
// 创建AVPlayer
_player = [AVPlayer playerWithPlayerItem:_item];
// 添加AVPlayerLayer
AVPlayerLayer *layer = [AVPlayerLayer playerLayerWithPlayer:self.player];
layer.frame = CGRectMake(self.view.bounds.size.width - 55, self.view.bounds.size.height - 100, 50, 50);
[self.view.layer addSublayer:layer];
//监听是否可播放
[_item addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
//监听缓存状态,可以添加加载动画
[_item addObserver:self forKeyPath:@"playbackBufferEmpty" options:NSKeyValueObservingOptionNew context:nil];
//监听是否播放完成
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playToTheEnd) name:AVPlayerItemDidPlayToEndTimeNotification object:_item];
//监听播放状态,播放还是暂停
[_player addObserver:self forKeyPath:@"timeControlStatus" options:NSKeyValueObservingOptionNew context:nil];
}
return _player;
}
- (UIImageView *)playerView{
if (_playerView == nil) {
_playerView = [[UIImageView alloc] init];
_playerView.image = [UIImage imageNamed:@"L0"];
//播放动画
NSMutableArray *imgArr = [NSMutableArray array];
for (int i = 0; i<4; i ++) {
UIImage *image = [UIImage imageNamed:[NSString stringWithFormat:@"L%d",i]];
[imgArr addObject:image];
}
_playerView.animationImages = imgArr;
_playerView.animationDuration = 2.0;
_playerView.animationRepeatCount = 0;
_playerView.userInteractionEnabled = YES;
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(playOrPause)];
[_playerView addGestureRecognizer:tap];
}
return _playerView;
}
- (UIImageView *)animationV{
//旋转动画
if (_animationV == nil) {
_animationV = [[UIImageView alloc] init];
_animationV.image = [UIImage imageNamed:@"loadcc"];
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
//默认是顺时针效果,若将fromValue和toValue的值互换,则为逆时针效果
animation.fromValue = [NSNumber numberWithFloat:0.f];
animation.toValue = [NSNumber numberWithFloat: M_PI *2];
animation.duration = 2;
animation.autoreverses = NO;
animation.fillMode = kCAFillModeForwards;
animation.repeatCount = MAXFLOAT; //如果这里想设置成一直自旋转,可以设置为MAXFLOAT,否则设置具体的数值则代表执行多少次
[_animationV.layer addAnimation:animation forKey:nil];
}
return _animationV;
}
播放音频
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@%@",kVedioUrl,_mp3Url]];
AVAsset *avset = [AVAsset assetWithURL:url] ;
CMTime audioDuration = avset.duration; //获取音频时长
_countTime = CMTimeGetSeconds(audioDuration);
AVPlayerItem *item = [AVPlayerItem playerItemWithAsset:avset];
[self.player replaceCurrentItemWithPlayerItem:item];//替换当前播放的音频
[self.player play];
注:如果需要获取音频的时长等信息,必须使用AVAsset
,不需要的话,可以直接使用[AVPlayerItem playerItemWithURL:url]
就可以了
显示音频所剩的播放时间
//
__weak typeof(self) weakSelf = self;
[self.player addPeriodicTimeObserverForInterval:CMTimeMake(1, 1) queue:nil usingBlock:^(CMTime time) {
AVPlayerItem *item = weakSelf.item;
//已播放时长
NSInteger currentTime = item.currentTime.value/item.currentTime.timescale;
//音频总时长
NSInteger allTime = CMTimeGetSeconds(weakSelf.player.currentItem.duration);
weakSelf.timeL.text = [weakSelf showPlayerTime:allTime - currentTime];
}];
监听回调
//
#pragma 监听播放状态回调
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionaryid> *)change context:(void *)context{
if ([object isKindOfClass:[AVPlayerItem class]]) {
if ([keyPath isEqualToString:@"status"]) {
switch (_item.status) {
case AVPlayerItemStatusReadyToPlay:
//推荐将视频播放在这里
break;
case AVPlayerItemStatusUnknown:
NSLog(@"AVPlayerItemStatusUnknown");
break;
case AVPlayerItemStatusFailed:
NSLog(@"AVPlayerItemStatusFailed");
break;
default:
break;
}
}else if ([keyPath isEqualToString:@"playbackBufferEmpty"]){
if (_item.playbackBufferEmpty) {
_animationV.hidden = NO;
}
}
}
if ([object isKindOfClass:[AVPlayer class]]) {
if ([keyPath isEqualToString:@"timeControlStatus"]){
switch (_player.timeControlStatus) {
case AVPlayerTimeControlStatusPlaying:
_isPlaying = YES;
_animationV.hidden = YES;
break;
case AVPlayerTimeControlStatusPaused:
_isPlaying = NO;
_animationV.hidden = YES;
[_playerView stopAnimating];
_playerView.image = [UIImage imageNamed:@"L0"];
break;
case AVPlayerTimeControlStatusWaitingToPlayAtSpecifiedRate:
_animationV.hidden = NO;
default:
break;
}
}
}
}
时间显示
//
- (NSString *)showPlayerTime:(NSInteger)countTime{
NSInteger minute = countTime / 60;
NSInteger second = countTime % 60;
NSString *str = @"";
if (minute < 10) {
if (second < 10) {
str = [NSString stringWithFormat:@" 0%ld:0%ld",minute,second];
}else{
str = [NSString stringWithFormat:@" 0%ld:%ld",minute,second];
}
}else{
if (second < 10) {
str = [NSString stringWithFormat:@" %ld:0%ld",minute,second];
}else{
str = [NSString stringWithFormat:@" %ld:%ld",minute,second];
}
}
return str;
}
注意:有的说是NSTimer 计时器计时应该写在子线程中,但是写在子线程中发现倒计时与音频播放不同步,出现倒计时已经结束,但是音频还没播放完,所以这里我就都写在了主线程。(有见解的伙伴欢迎提点哦)
(2)然后,就是要实现后台播放以及返回其他页面,音频继续播放的过程
首先开启,允许后台运行模式
然后在APPDelegate 中:
- (void)applicationWillResignActive:(UIApplication *)application {
//开启后台处理多媒体事件
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
AVAudioSession *session=[AVAudioSession sharedInstance];
[session setActive:YES error:nil];
//后台播放
[session setCategory:AVAudioSessionCategoryPlayback error:nil];
//这样做,可以在按home键进入后台后 ,播放一段时间,几分钟吧。但是不能持续播放,若需要持续播放,还需要申请后台任务id,具体做法是:
_bgTaskId=[AppDelegate backgroundPlayerID:_bgTaskId];
//其中的_bgTaskId是后台任务UIBackgroundTaskIdentifier _bgTaskId;
}
+(UIBackgroundTaskIdentifier)backgroundPlayerID:(UIBackgroundTaskIdentifier)backTaskId
{
//设置并激活音频会话类别
AVAudioSession *session=[AVAudioSession sharedInstance];
[session setCategory:AVAudioSessionCategoryPlayback error:nil];
[session setActive:YES error:nil];
//允许应用程序接收远程控制
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
//设置后台任务ID
UIBackgroundTaskIdentifier newTaskId=UIBackgroundTaskInvalid;
newTaskId=[[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:nil];
if(newTaskId!=UIBackgroundTaskInvalid&&backTaskId!=UIBackgroundTaskInvalid)
{
[[UIApplication sharedApplication] endBackgroundTask:backTaskId];
}
return newTaskId;
}
But:我们会发现当实现当只实现以上的applicationWillResignActive
方法,或者只开启图中的background modes ,做到这两个中的一个,就可以实现后台播放。。。
进入后台播放可以了,但是当我们返回到其他的页面,再次进入到这个页面时,会发现,音频又叠加了一个音频,而不是我们想要的当前音频正常的继续播放。所以…我们想到了,对,揍死它——单例。
将当前控制器设置成单例,这样每次进入这个页面,音频可以毫无影响的继续播放啦~
static MyController *instance;
+(id)shareInstance{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if(instance == nil)
instance = [[MyController alloc] init];
});
return instance;
}
注:以上只是部分主要代码,并非完整代码。
附:
参考文章:https://www.jianshu.com/p/ab300ea6e90c