AVPlayer:
AVPlayer存在于AVFoundation中,其实它是一个视频播放器,但是用它来播放音乐是没问题的,本地音频和流媒体播放,但处理音频不够灵活;但是它对视屏有很高自由度的控制,而且能够自定义视屏播放界面。.iOS9后,AVFoundation框架还做了几点修改,如果需要切换视频播放的时间,或需要控制视频从头播放调用seekToDate方法,需要保持视频的播放rate大于0才能修改,还有canUseNetworkResourcesForLiveStreamingWhilePaused这个属性,在iOS9前默认为YES,之后默认为NO。在iOS9后,AVPlayer的replaceCurrentItemWithPlayerItem方法在切换视频时底层会调用信号量等待然后导致当前线程卡顿,如果在UITableViewCell中切换视频播放使用这个方法,会导致当前线程冻结几秒钟。解决方法是在每次需要切换视频时,需重新创建AVPlayer和AVPlayerItem。
利用AVPlayer播放本地音乐代码如下:
- (void)initMusic3{
NSString* musicPath = [[NSBundle mainBundle]pathForResource:@"chirp" ofType:@"mp3"];
//构建URL
NSURL *url = [NSURL fileURLWithPath:musicPath];
AVPlayerItem* songItem = [[AVPlayerItem alloc]initWithURL:url];
_avplayer = [[AVPlayer alloc]initWithPlayerItem:songItem];
[_avplayer play];
}
- (void)dealloc{
_avplayer = nil;
}
使用AVPlayer的方法开启下载服务
1.AVURLAsset *urlAsset = [[AVURLAsset alloc]initWithURL:url options:nil];
2.AVPlayerItem *item = [AVPlayerItem playerItemWithAsset:urlAsset];
3.[self.avPlayer replaceCurrentItemWithPlayerItem:item];
4.[self addObserverToPlayerItem:item];
但由于AVPlayer是没有提供方法给我们直接获取它下载下来的数据,所以我们只能在视频下载完之后自己去寻找缓存视频数据的办法,AVFoundation框架中有一种从多媒体信息类AVAsset中提取视频数据的类AVMutableComposition和AVAssetExportSession。
其中AVMutableComposition的作用是能够从现有的asset实例中创建出一个新的AVComposition(它也是AVAsset的字类),使用者能够从别的asset中提取他们的音频轨道或视频轨道,并且把它们添加到新建的Composition中。
AVAssetExportSession的作用是把现有的自己创建的asset输出到本地文件中。
为什么需要把原先的AVAsset(AVURLAsset)实现的数据提取出来后拼接成另一个AVAsset(AVComposition)的数据后输出呢,由于通过网络url下载下来的视频没有保存视频的原始数据(或者苹果没有暴露接口给我们获取),下载后播放的avasset不能使用AVAssetExportSession输出到本地文件,要曲线地把下载下来的视频通过重构成另外一个AVAsset实例才能输出。代码例子如下:
NSString *documentDirectory = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
NSString *myPathDocument = [documentDirectory stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.mp4",[_source.videoUrl MD5]]];
NSURL *fileUrl = [NSURL fileURLWithPath:myPathDocument];
if (asset != nil) {
AVMutableComposition *mixComposition = [[AVMutableComposition alloc]init];
AVMutableCompositionTrack *firstTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
[firstTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, asset.duration) ofTrack:[[asset tracksWithMediaType:AVMediaTypeVideo]objectAtIndex:0] atTime:kCMTimeZero error:nil];
AVMutableCompositionTrack *audioTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
[audioTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, asset.duration) ofTrack:[[asset tracksWithMediaType:AVMediaTypeAudio]objectAtIndex:0] atTime:kCMTimeZero error:nil];
AVAssetExportSession *exporter = [[AVAssetExportSession alloc]initWithAsset:mixComposition presetName:AVAssetExportPresetHighestQuality];
exporter.outputURL = fileUrl;
if (exporter.supportedFileTypes) {
exporter.outputFileType = [exporter.supportedFileTypes objectAtIndex:0] ;
exporter.shouldOptimizeForNetworkUse = YES;
[exporter exportAsynchronouslyWithCompletionHandler:^{
}];
}
}
利用AVPlayer-在线播放
- (void)initMusic3{
NSURL* url = [NSURL URLWithString:@"http://qy.bbzx.wuxi.cn/MyImages/2010-3/b478f0bb-b906-435c-9150-d691fde227f1.mp3"];
AVPlayerItem* songItem = [[AVPlayerItem alloc]initWithURL:url];
_avplayer = [[AVPlayer alloc]initWithPlayerItem:songItem];
[_avplayer play];
}
- (void)dealloc{
_avplayer = nil;
}
利用AVPlayer-在线播放--并且获取播放器的播放进度
- (void)initMusic3{
NSURL* url = [NSURL URLWithString:@"http://qy.bbzx.wuxi.cn/MyImages/2010-3/b478f0bb-b906-435c-9150-d691fde227f1.mp3"];
AVPlayerItem* songItem = [[AVPlayerItem alloc]initWithURL:url];//AVPlayerItem:和媒体资源存在对应关系,管理媒体资源的信息和状态
_avplayer = [[AVQueuePlayer alloc]init];
[_avplayer replaceCurrentItemWithPlayerItem:songItem];//此方法也可以实现歌曲切换-上一首或者下一首--自行控制下一首的item,将其替换成当前播放的item
[songItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];//播放完成之后需要移除观察者
//添加通知----也要记得移除通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playerItemAction:) name:AVPlayerItemDidPlayToEndTimeNotification object:_playerItem];
//添加观察者-----使用addPeriodicTimeObserverForInterval:queue:usingBlock:来监听播放器的播放进度
/*
(1)方法传入一个CMTime结构体,每到一定时间都会回调一次,包括开始和结束播放
(2)如果block里面的操作耗时太长,下次不一定会收到回调,所以尽量减少block的操作耗时
(3)方法会返回一个观察者对象,当播放完毕时需要移除这个观察者
*/
timeObserve = [_avplayer addPeriodicTimeObserverForInterval:CMTimeMake(1.0, 1.0) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
float current = CMTimeGetSeconds(time);
float total = CMTimeGetSeconds(songItem.duration);
NSLog(@"current-%f total-%f",current,total);
}];
[_avplayer play];
}
//歌曲播放完后处理事件
- (void)playerItemAction:(AVPlayerItem *)item {
[songItem removeObserver:self forKeyPath:@" loadedTimeRanges"];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void *)context {
AVPlayerItem * songItem = object;
if ([keyPath isEqualToString:@"loadedTimeRanges"]) {
NSArray * array = songItem.loadedTimeRanges;
CMTimeRange timeRange = [array.firstObject CMTimeRangeValue]; //本次缓冲的时间范围
NSTimeInterval totalBuffer = CMTimeGetSeconds(timeRange.start) + CMTimeGetSeconds(timeRange.duration); //缓冲总长度
NSLog(@"共缓冲%.2f",totalBuffer);
}
}
- (void)dealloc{
if (timeObserve) {
[_avplayer removeTimeObserver:timeObserve];
timeObserve = nil;
}
[[NSNotificationCenter defaultCenter] removeObserver:self];
_avplayer = nil;
}
注意:如果_avplayer为AVPlayer的对象会造成一定的内存泄漏,所以此处用AVQueuePlayer
注意:切换上一首和下一首的另一种方法:使用AVPlayer的子类AVQueuePlayer来播放多个item.调用advanceToNextItem来播放下一首音乐 NSArray* items = @[item1,item2];
AVQueuePlayer* queuePlayer = [[AVQueuePlayer alloc]initWithItems:items];
注意:
AVPlayer的对象要设置为全局的,否则会播放不成功。
AVPlayer先缓冲,等缓冲完了,再播放。
AVPlayer视频播放
AVPlayer本身并不能显示视屏,他也不像MPMoviePlayerController有一个view属性。AVPlayer如果要显示视屏,需要创建一个播放器层AVPlayerLayer用于展示,这个播放器层继承于CALayer,创建AVPlayerLayer之后添加到控制器视图的layer即可。他的常用的类如下:
AVAsset:主要用于获取多媒体信息,是一个抽象类,不能直接使用。
AVURLAsset:AVAsset的子类,可以根据一个URL路径创建一个包含媒体信息的AVURLAsset对象。
AVPlayerItem:一个媒体资源管理对象,管理者视频的一些基本信息和状态,一个AVPlayerItem对应着一个视频资源。
- (void)initTV{
self.automaticallyAdjustsScrollViewInsets = NO;
NSString* str = @"http://mp3.wy520.com/%E5%AE%89%E5%92%8C%E6%A1%A5.mp4";//这是在线视屏,如果是本地视屏的话,只需把响应的路径改为本地视屏路径。
NSURL* url = [NSURL URLWithString: str];
AVPlayerItem* playItem = [AVPlayerItem playerItemWithURL:url];
self.avplayerTV = [[AVPlayer alloc]initWithPlayerItem:playItem];
[playItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];//播放完成之后需要移除观察者---也可以不添加
//创建播放器层
AVPlayerLayer* playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.avplayerTV];
playerLayer.frame = CGRectMake(0, 64, [UIScreen mainScreen].bounds.size.width, 80);
[self.view.layer addSublayer:playerLayer];
[self.avplayerTV seekToTime:CMTimeMakeWithSeconds(0, 1000)];//设置播放位置 1000位帧率
[self.avplayerTV play];
}
- (void)dealloc{
self.avplayerTV = nil;
}
注意:AVPlayer的replaceCurrentItemWithPlayerItem方法正常是会引用住参数AVPlayerItem的,但在某些情况下导致视频播放失败,它会马上释放对这个对象的持有,假如你对AVPlayerItem的实例对象添加了监听,但是自己没有对item的计数进行管理,不知道什么时候释放这个监听,则会导致程序崩溃。