苹果提供了AVFAudio与AVFoundation作为音频与视频源生框架,能够满足绝大部分音视频的需求,其中AVFoundation框架中的AVPlayer控件,甚至可以作为直播播放器,.mp4,.avi,.m3u8格式都能顺利解码,音频框架除了常见的.mp3,,苹果的.caf也能解码。
音视频播放都分为本地文件和远程文件,下面会详细介绍:
代码参考:https://github.com/xinsun001/AVandAudio
一:视频播放
1:使用AVPlayer
视频播放器,使用AVPlayer播放器来解码,通过AVPlayerLayer来绘制到想要展示的控件上面,用来展示视频的窗口可以自己随便定义
NSString *audioPath = [[NSBundle mainBundle] pathForResource:@"chenyifaer" ofType:@"mp4"];
NSURL *url = [NSURL fileURLWithPath:audioPath];
self.playerItem = [AVPlayerItem playerItemWithURL:url];
self.player = [AVPlayer playerWithPlayerItem:self.playerItem];
// 初始化播放器的Layer
self.playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.player];
// layer的frame
self.playerLayer.frame = self.xsVideoView.videoBgView.bounds;
// layer的填充属性 和UIImageView的填充属性类似
// AVLayerVideoGravityResizeAspect 等比例拉伸,会留白
// AVLayerVideoGravityResizeAspectFill // 等比例拉伸,会裁剪
// AVLayerVideoGravityResize // 保持原有大小拉伸
self.playerLayer.videoGravity = AVLayerVideoGravityResizeAspect;
// 把Layer加到底部View上
[self.xsVideoView.videoBgView.layer addSublayer:self.playerLayer];
视频播放往往涉及到全屏的问题,这就会造成视频展示的窗口会有UI的变化,UI每变换一次,addSublayer就需要重新执行一次,如果是用的相对布局的方式,则需要[self.viewlayoutIfNeeded]之后,待控件有了对应的frame值,再去执行addSublayer。如果addSublayer加载的不对,就会出现只有声音,画面黑屏的情况。
2:两个重要的监听属性
AVPlayer中存在两个非常重要的监听属性,由这两个监听才完善视频的缓冲,播放,进度条和快进等功能。分别是播放器的状态变化:NSKeyValueObservingOptionNew 和视频缓冲状态:NSKeyValueObservingOptionNew
// 监听播放器状态变化
[self.playerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
// 监听缓冲
[self.playerItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];
在之后的
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
方法中,通过keyPath的来区分监听。
播放器的状态变化是一个枚举AVPlayerItemStatus,用[change[NSKeyValueChangeNewKey] integerValue]来读取,
AVPlayerItemStatusReadyToPlay:表示视频缓冲完成,已经准备好进行播放了,这个可以直接使用[self.player play]播放视频
AVPlayerItemStatusFailed:表示视频解码错误,或者缓冲失败,推流或者网速都会对此产生影响
AVPlayerItemStatusUnknown:未知故障
缓存变化的监听,则能够提供视频的时长,和缓冲进度的数据
//缓冲进度
- (NSTimeInterval)availableDuration {
NSArray *loadedTimeRanges = [self.playerItem loadedTimeRanges];
CMTimeRange timeRange = [loadedTimeRanges.firstObject CMTimeRangeValue];// 获取缓冲区域
float startSeconds = CMTimeGetSeconds(timeRange.start); // 开始的点
float durationSeconds = CMTimeGetSeconds(timeRange.duration); // 已缓存的时间点
NSTimeInterval result = startSeconds + durationSeconds;// 计算缓冲总进度
return result;
}
//视频总长度
CMTime duration = self.playerItem.duration;
CGFloat durationTime = CMTimeGetSeconds(duration);
3:视频暂停和重复播放
AVPlayer中只有播放暂停,没有停止的指令,
播放:[self.player play]
暂停:[self.player pause]
视频播放完毕的通知AVPlayerItemDidPlayToEndTimeNotification,可以在改通知内把进度条归零,来实现重复播放视频的方法
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playToEnd:) name:AVPlayerItemDidPlayToEndTimeNotification object:nil];
-(void)playToEnd:(NSNotification *)nottification{
//进度条归零
[self.playerItem seekToTime:kCMTimeZero completionHandler:^(BOOL finished) {
}];
}
如果想停止,则需要直接移除掉AVPlayer的所有相关内容,并且销毁监听属性和通知。特别是在退出页面是,播放器是不会随着页面的退出而停止媒体流的播放,必须要先销毁掉AVPlayer,再退出页面
[self.playerItem removeObserver:self forKeyPath:@"status"];
[self.playerItem removeObserver:self forKeyPath:@"loadedTimeRanges"];
[[NSNotificationCenter defaultCenter]removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:[self.player currentItem]];
// 加上这句话,避免反复进入视频播放时,后面导致视频不加载的情况,
[self.player replaceCurrentItemWithPlayerItem:nil];
if (self.player) {
[self.player pause];
self.playerItem = nil;
self.player = nil;
}
播放远程视频和本地视频没有什么不同,只要把URL换成远程连接就行
NSString *playString = @"https://cloud.video.taobao.com/play/u/1723456468/p/2/e/6/t/1/244497031401.mp4?appKey=38829";
NSURL *url = [NSURL URLWithString:playString];
二:播放本地音频
1:使用AVAudioPlayer
本地音频用AVPlayer同样可以实现播放的效果,但是AVPlayer需要使用完手动销毁,无法停止,相对来说AVAudioPlayer就是比较完美的音频播放器,并且可以设置音量大小,循环次数
NSString *playAudio = [[NSBundle mainBundle] pathForResource:@"Ice.mp3" ofType:nil];
NSURL *url = [NSURL URLWithString:playAudio];
self.musicplayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil];
self.musicplayer.volume = 0.8; //音量
self.musicplayer.numberOfLoops = 0; //循环次数,-1表示无限循环,0不循环
self.musicplayer.delegate = self;
[self.musicplayer prepareToPlay]; //预加载
// self.musicplayer.currentTime = 18; //从18秒开始播放
[self.musicplayer play];
currentTime要结合play属性一起使用,实现快进或者指定位置跳跃播放的效果。AVAudioPlayer有三个基本属性:
开始播放:[self.musicplayer play]
暂停播放:[self.musicplayer pause]
停止播放:[self.musicplayer stop]
AVAudioPlayer在声明的时候必须是强引用属性,否则播放音频文件的时候会没有声音发出,也不能再局部声明AVAudioPlayer。如果是单个页面使用的AVAudioPlayer,退出页面时不需要可以去销毁播放器。
AVAudioPlayer在预加载之后就可以读取到本地音频的长度self.musicplayer.duration。
2:两个代理
要在文件中声明AVAudioPlayerDelegate,可以使用以下两个代理
//音频播放完毕
-(void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag{
}
//播放出错
-(void)audioPlayerDecodeErrorDidOccur:(AVAudioPlayer *)player error:(NSError *)error{
}
经过测试,AVAudioPlayer是无法播放远程音频的,所以远程音频还是得用AVPlayer,只是不想要加载画面,所以layer属性不再需要。但是要保留两个重要的监听和播放完成的通知
NSURL *url = [NSURL URLWithString:@"http://downsc.chinaz.net/Files/DownLoad/sound1/201906/11582.mp3"];
self.playerItem = [AVPlayerItem playerItemWithURL:url];
self.avplayer = [AVPlayer playerWithPlayerItem:self.playerItem];
// 监听播放器状态变化
[self.playerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
// 监听缓存大小
[self.playerItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playToEnd:) name:AVPlayerItemDidPlayToEndTimeNotification object:nil];