概述
【iOS】视频播放之AVPlayer
iOS平台使用播放视频,可用的选项一般有这四个,他们各自的作用和功能如下:
使用环境 | 优点 | 缺点 | |
---|---|---|---|
AVPlayerViewController | AVKit | 简单易用 | 不可定制 |
MPMoviePlayerController | MediaPlayer | 简单易用 | 不可定制 |
IJKPlayer | IJKMediaFrameWork | 定制度高,可支持流媒体播放 | 使用复杂 |
AVPlayer | AVFoundation | 可定制度高,功能强大 | 不支持流媒体 |
由此可以看出,如果我们不做直播功能AVPlayer就是一个最优的选择。
另外AVPlayer是一个可以播放任何格式的全功能影音播放器
支持视频格式: MP4,MOV,M4V,3GP,AVI等。
支持音频格式:MP3,AAC,WAV,AMR,M4A等。
详见AVPlayer支持的视频格式
如何使用
AVPlayer存在于AVFoundation框架,我们使用时需要导入:
#import <AVFoundation/AVFoundation.h>
几个播放相关的参数
在创建一个播放器之前我们需要先了解一些播放器相关的类
AVPlayer:控制播放器的播放,暂停,播放速度
AVURLAsset : AVAsset 的一个子类,使用 URL 进行实例化,实例化对象包换 URL 对应视频资源的所有信息。
AVPlayerItem:管理资源对象,提供播放数据源
AVPlayerLayer:负责显示视频,如果没有添加该类,只有声音没有画面
我们这片文章就围绕这几个参数展开,光说这些你可能还有点不明白,那我们就围绕一个最简单的播放器做起,一点点扩展功能,在具体讲解这几个参数的作用。
最简单的播放器
根据上面描述,我们知道AVPlayer是播放的必要条件,所以我们可以构建的简单播放器就是:
NSURL*url=[NSURLfileURLWithPath:@"视频地址"];
AVPlayerItem*playerItem=[AVPlayerItemplayerItemWithURL:url];
self.player=[AVPlayer playerWithPlayerItem:playerItem];
[self.player addObserver:self forKeyPath:@"status"options:NSKeyValueObservingOptionNew context:nil];
self.player.actionAtItemEnd=AVPlayerActionAtItemEndNone;
self.playerLayer=[AVPlayerLayer playerLayerWithPlayer:self.player];
self.playerLayer.videoGravity=AVLayerVideoGravityResizeAspect;
self.playerLayer.frame=self.view.bounds;
[self.view.layer addSublayer:self.playerLayer];
[self.player play];
为完成这些,我们需要对AVPlayerItem和AVPlayerLayer进一步了解一下。
一、AVPlayer的控制
前面讲过该类是控制视频播放行为的,他的使用比较简单。
播放视频:
[self.player play];
暂停视频:
[self.player pause];
更改速度:
self.player.rate =1.5;//注意更改播放速度要在视频开始播放之后才会生效
还有一下其他的控制,我们可以调转到系统API进行查看
二、AVPlayerItem的控制
AVPlayerItem作为资源管理对象,它控制着视频从创建到销毁的诸多状态。
1. 播放状态 status
typedef NS_ENUM(NSInteger,AVPlayerItemStatus) {
AVPlayerItemStatusUnknown,//未知
AVPlayerItemStatusReadyToPlay,//准备播放
AVPlayerItemStatusFailed//播放失败
};
我们使用KVO监测playItem.status,可以获取播放状态的变化
[self.playerItem addObserver:selfforKeyPath:@"status"options:NSKeyValueObservingOptionNewcontext:nil];
在监听回调中:
- (void)observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void*)context{
if([object isKindOfClass:[AVPlayerItemclass]]) {
if([keyPath isEqualToString:@"status"]) {
switch(_playerItem.status) {
caseAVPlayerItemStatusReadyToPlay://推荐将视频播放放这里 [self play];
break;
caseAVPlayerItemStatusUnknown:
NSLog(@"AVPlayerItemStatusUnknown");
break;
caseAVPlayerItemStatusFailed:
NSLog(@"AVPlayerItemStatusFailed");
break;
default:break;
}
}}
虽然设置完播放配置我们可以直接调用[self.player play];进行播放,但是更稳妥的方法是在回调收到AVPlayerItemStatusReadyToPlay时进行播放
2. 视频的时间信息
在AVPlayer中时间的表示有一个专门的结构体CMTime
typedef struct{
CMTimeValuevalue;// 帧数
CMTimeScaletimescale;// 帧率(影片每秒有几帧)
CMTimeFlagsflags;
CMTimeEpochepoch;
}CMTime;
CMTime是以分数的形式表示时间,value表示分子,timescale表示分母,flags是位掩码,表示时间的指定状态。
获取当前播放时间,可以用value/timescale的方式:
floatcurrentTime =self.playItem.currentTime.value/item.currentTime.timescale;
还有一种利用系统提供的方法,我们用它获取视频总时间:
floattotalTime =CMTimeGetSeconds(item.duration);
如果我们想要添加一个计时的标签不断更新当前的播放进度,有一个系统的方法:
- (id)addPeriodicTimeObserverForInterval:(CMTime)interval queue:(nullabledispatch_queue_t)queue usingBlock:(void(^)(CMTimetime))block;
方法名如其意, “添加周期时间观察者” ,参数1 interal 为CMTime 类型的,参数2 queue为串行队列,如果传入NULL就是默认主线程,参数3 为CMTime 的block类型。
简而言之就是,每隔一段时间后执行 block。
比如:我们把interval设置成CMTimeMake(1, 10),在block里面刷新label,就是一秒钟刷新10次。
正常观察播放进度一秒钟一次就行了,所以可以这么写:
[self.player addPeriodicTimeObserverForInterval:CMTimeMake(1,1) queue:nilusingBlock:^(CMTimetime) {
AVPlayerItem*item = WeakSelf.playerItem;
NSIntegercurrentTime = item.currentTime.value/item.currentTime.timescale;
NSLog(@"当前播放时间:%ld",currentTime);
}];
3. loadedTimeRange 缓存时间
获取视频的缓存情况我们需要监听playerItem的loadedTimeRanges属性
[self.playerItem addObserver:selfforKeyPath:@"loadedTimeRanges"options:NSKeyValueObservingOptionNewcontext:nil];
在KVO的回调里:
if([keyPath isEqualToString:@"loadedTimeRanges"]){
NSArray*array = _playerItem.loadedTimeRanges;
//本次缓冲时间范围
CMTimeRangetimeRange = [array.firstObject CMTimeRangeValue];
floatstartSeconds =CMTimeGetSeconds(timeRange.start);
floatdurationSeconds =CMTimeGetSeconds(timeRange.duration);
NSTimeIntervaltotalBuffer = startSeconds + durationSeconds;//缓冲总长度
NSLog(@"当前缓冲时间:%f",totalBuffer);
}
4. playbackBufferEmpty
监听playbackBufferEmpty我们可以获取当缓存不够,视频加载不出来的情况:
[self.playerItem addObserver:selfforKeyPath:@"playbackBufferEmpty"options:NSKeyValueObservingOptionNewcontext:nil];
在KVO回调里:
if([keyPath isEqualToString:@"playbackBufferEmpty"]) {
//some code show loading
}
5. playbackLikelyToKeepUp
playbackLikelyToKeepUp和playbackBufferEmpty是一对,用于监听缓存足够播放的状态
[self.playerItem addObserver:selfforKeyPath:@"playbackLikelyToKeepUp"options:NSKeyValueObservingOptionNewcontext:nil];
if([keyPath isEqualToString:@"playbackLikelyToKeepUp"]) {
//由于 AVPlayer 缓存不足就会自动暂停,所以缓存充足了需要手动播放,才能继续播放 [_player play];
}
AVURLAsset
播放视频只需一个url就能进行这样太不安全了,别人可以轻易的抓包盗链,为此我们需要为视频链接做一个请求头的认证,这个功能可以借助AVURLAsset完成。
AVPlayerItem除了可以用URL初始化,还可以用AVAsset初始化,而AVAsset不能直接使用,我们看下AVURLAsset的一个初始化方法:
/*!
@param URL
An instance of NSURL that references a media resource.
@param options
An instance of NSDictionary that contains keys for specifying options for the initialization of the AVURLAsset. See AVURLAssetPreferPreciseDurationAndTimingKey and AVURLAssetReferenceRestrictionsKey above.
*/+ (instancetype)URLAssetWithURL:(NSURL*)URL options:(nullableNSDictionary *)options;
AVURLAssetPreferPreciseDurationAndTimingKey.这个key对应的value是一个布尔值, 用来表明资源是否需要为时长的精确展示,以及随机时间内容的读取进行提前准备。
除了这个苹果官方介绍的功能外,他还可以设置请求头,这个算是隐藏功能了,因为苹果并没有明说这个功能,我是费了很大劲找到的。
NSMutableDictionary* headers = [NSMutableDictionary dictionary];
[headers setObject:@"yourHeader"forKey:@"User-Agent"];
self.urlAsset = [AVURLAsset URLAssetWithURL:self.videoURL options:@{@"AVURLAssetHTTPHeaderFieldsKey": headers}];// 初始化playerItem
self.playerItem = [AVPlayerItem playerItemWithAsset:self.urlAsset];
6.系统音量相关
/**
* 获取系统音量
*/
- (void)configureVolume
{
MPVolumeView *volumeView = [[MPVolumeView alloc] init];
_volumeViewSlider = nil;
for (UIView *view in [volumeView subviews]){
if ([view.class.description isEqualToString:@"MPVolumeSlider"]){
_volumeViewSlider = (UISlider *)view;
break;
}
}
// 使用这个category的应用不会随着手机静音键打开而静音,可在手机静音下播放声音
NSError *setCategoryError = nil;
BOOL success = [[AVAudioSession sharedInstance]
setCategory: AVAudioSessionCategoryPlayback
error: &setCategoryError];
if (!success) { /* handle the error in setCategoryError */ }
// 监听耳机插入和拔掉通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(audioRouteChangeListenerCallback:) name:AVAudioSessionRouteChangeNotification object:nil];
}
/**
* 耳机插入、拔出事件
*/
- (void)audioRouteChangeListenerCallback:(NSNotification*)notification
{
NSDictionary *interuptionDict = notification.userInfo;
NSInteger routeChangeReason = [[interuptionDict valueForKey:AVAudioSessionRouteChangeReasonKey] integerValue];
switch (routeChangeReason) {
case AVAudioSessionRouteChangeReasonNewDeviceAvailable:
// 耳机插入
break;
case AVAudioSessionRouteChangeReasonOldDeviceUnavailable:
{
// 耳机拔掉
// 拔掉耳机继续播放
[self play];
}
break;
case AVAudioSessionRouteChangeReasonCategoryChange:
// called at start - also when other audio wants to play
NSLog(@"AVAudioSessionRouteChangeReasonCategoryChange");
break;
}
}
7.其它App播放声音打断
如果用户当时在后台听音乐,如QQ音乐,或者喜马拉雅这些App,这个时候播放视频后,其会被我们打断,当我们不再播放视频的时候,自然需要继续这些后台声音的播放。
首先,我们需要先向设备注册激活声音打断
AudioSessionSetActive(YES);
当然我们也可以通过
[AVAudioSession sharedInstance].otherAudioPlaying;
这个方法来判断还有没有其它业务的声音在播放。 当我们播放完视频后,需要恢复其它业务或App的声音,这时我们可以在退到后台的事件中调用如下方法:
- (void)applicationDidEnterBackground:(UIApplication *)application {
NSError *error =nil;
AVAudioSession *session = [AVAudioSession sharedInstance];
// [session setCategory:AVAudioSessionCategoryPlayback error:nil];
BOOL isSuccess = [session setActive:NO withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation error:&error];
if (!isSuccess) {
NSLog(@"__%@",error);
}else{
NSLog(@"成功了");
}
}
播放相关通知
1、声音类:
//声音被打断的通知(电话打来)
AVAudioSessionInterruptionNotification
//耳机插入和拔出的通知
AVAudioSessionRouteChangeNotification
根据userInfo判断具体状态
2、播放类
//播放完成
AVPlayerItemDidPlayToEndTimeNotification
//播放失败
AVPlayerItemFailedToPlayToEndTimeNotification
//异常中断
AVPlayerItemPlaybackStalledNotification
对于播放完成的通知:
[[NSNotificationCenterdefaultCenter] addObserver:selfselector:@selector(playerMovieFinish:) name:AVPlayerItemDidPlayToEndTimeNotificationobject:[self.player currentItem]];
3、系统状态
//进入后台
UIApplicationWillResignActiveNotification
//返回前台
UIApplicationDidBecomeActiveNotification
最后
以上就是和谐猫咪为你收集整理的【iOS】视频播放之AVPlayer的全部内容,希望文章能够帮你解决【iOS】视频播放之AVPlayer所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复