项目需求,封装了一个AVPlayer
视频播放需要AVPlayer,AVPlayerLayer和AVPlayerItem,AVPlayer负责控制视频播放暂停等controller负责的事情,AVPlayerLayer只负责显示这种view需要处理的事情,而AVPlayerItem则是提供播放源。
新建一个继承NSObject的类
//单例类
+(instancetype)shareManager
{
static dispatch_once_t once;
dispatch_once(&once, ^{
_videoManager = [[VideoManager alloc] init];
});
return _videoManager;
}
这里单例只能设置本类单例,之前我有把avplayer也设置单例,后来发现在切换播放源的时候回卡住,只有声音画面不动。
avplayer在设置播放源的时候进行初始化
//添加播放源
-(void)replacePlayitemWithURL:(NSString *)url
{
self.playerItem = [AVPlayerItem playerItemWithURL:URL(url)];
_player = [AVPlayer playerWithPlayerItem:self.playerItem];
self.playStatus = PlayerStatusUnknown;
//添加通知
[self addObserverItems];
}
播放源在刚添加上的时候并不会立刻播放,在可以播放的时候值会改变,所以需要为avplayeritem添加kvo
[self.playerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
并在值发生改变的时候发送通知
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
//playItem的监听
if ([object class] == [AVPlayerItem class]) {
//播放状态的监听
if ([keyPath isEqualToString:@"status"]) {
//判断是否可以播放
if ([change[@"new"] intValue] == AVPlayerStatusReadyToPlay) {
self.canPlay = YES;
//block通知
if (self.readyToPlay) {
self.readyToPlay();
}
}
}
}
//改变状态
[self changeStatus];
}
这里定义了一个枚举类型,用来判断当前item播放状态
typedef NS_ENUM(NSInteger, PlayerStatus) {
PlayerStatusUnknown, ///<初始化
PlayerStatusPlaying, ///<播放中
PlayerStatusPause, ///<暂停
PlayerStatusEnd, ///<播放完成
PlayerStatusError, ///<播放错误
PlayerStatusBuffering ///<缓冲
};
//改变状态
-(void)changeStatus{
if (_player.error != nil || self.playerItem.error != nil) {
self.playStatus = PlayerStatusError;
return;
}
if IOS(10.0) {
switch (_player.timeControlStatus){
case AVPlayerTimeControlStatusPaused:
self.playStatus = PlayerStatusPause;
break;
case AVPlayerTimeControlStatusWaitingToPlayAtSpecifiedRate:
self.playStatus = PlayerStatusBuffering;
break;
case AVPlayerTimeControlStatusPlaying:
self.playStatus = PlayerStatusPlaying;
break;
}
} else {
if (_player.rate != 0) {
if (self.playerItem.isPlaybackLikelyToKeepUp) {
self.playStatus = PlayerStatusPlaying;
} else {
self.playStatus = PlayerStatusBuffering;
}
} else {
self.playStatus = PlayerStatusPlaying;
}
}
}
avplyer有自己提供的监听进度的方法
//监听播放
self.timeObserver = [_player addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(0.01, NSEC_PER_SEC) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
//回调
CGFloat second = CMTimeGetSeconds(time);
if (self.videoProgress) {
self.videoProgress(second);
}
}];
返回的值是CMTime类型,CMTime是个结构体,value表示视频一共多少帧,timeScale表示每秒钟播放几帧,所以时间(秒)=value/timeScale,也可以用CMTimeGetSeconds(time);
跳转到指定时间的方法是seekToTime,传递的值也是CMTime类型
//跳转到指定时间
-(void)jumpToTime:(NSString *)time
{
//清除上次的seek
[self.playerItem cancelPendingSeeks];
//有可能会造成屏幕卡顿,所以先清除上次
//先获取到时间
CGFloat seconds = [time translateStringToSeconds];
//创建CMTime
CMTime t = CMTimeMakeWithSeconds(seconds, self.playerItem.duration.timescale);
[_player seekToTime:t completionHandler:^(BOOL finished) {
//处理跳转完之后的操作,如播放
}];
}
视频播放完成后需要置空并且销毁监听
//停止
-(void)stop
{
self.playerItem = nil;
self.playStatus = PlayerStatusUnknown;
[_player replaceCurrentItemWithPlayerItem:nil];
_player = nil;
[self removeObserverItems];
}
avplaylayer有个属性一定要设置,
设置填充整个屏幕。
最开始用的模拟器是iphone6,没毛病,但是换成x测适配就发现问题了,只有6的屏幕大小有视频,最后发现是视频大小就是375、667。设置这个属性,解决一切花里胡哨
_playerLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;