文章目录
在开发中,单纯使用AVPlayer类是无法显示视频的,要将视频层添加至AVPlayerLayer中,这样才能将视频显示出来,所以先在ViewController的@interface中添加以下属性
@property (nonatomic ,strong) AVPlayer *player;
@property (nonatomic ,strong) AVPlayerItem *playerItem;
@property (nonatomic ,weak) IBOutlet PlayerView *playerView;
其中playerView继承自UIView,不过重写了set和get方法,用于将player添加至playerView的AVPlayerLayer中,这样才能顺利将视频显示出来
在PlayerView.h中声明一个AVPlayer对象,由于默认的layer是CALayer,而AVPlayer只能添加至AVPlayerLayer中,所以我们改变一下layerClass,这样PlayerView的默认layer就变了,之后我们可以把在viewController中初始化的AVPlayer对象赋给AVPlayerLayer的player属性。
PlayerView.h @property (nonatomic ,strong) AVPlayer *player;PlayerView.m
+ (Class)layerClass {return [AVPlayerLayer class];
}- (AVPlayer *)player {return [(AVPlayerLayer *)[self layer] player];
}- (void)setPlayer:(AVPlayer *)player {[(AVPlayerLayer *)[self layer] setPlayer:player];
}
然后在viewDidLoad中执行初始化:
NSURL *videoUrl = [NSURL URLWithString:@"http://flv.bn.netease.com/videolib3/1707/03/bGYNX4211/SD/movie_index.m3u8"];
self.playerItem = [AVPlayerItem playerItemWithURL:videoUrl];
[self.playerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];// 监听status属性
[self.playerItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];// 监听loadedTimeRanges属性
self.player = [AVPlayer playerWithPlayerItem:self.playerItem];
[[NSNotificationCenterdefaultCenter]addObserver:selfselector:@selector(moviePlayDidEnd:) name:AVPlayerItemDidPlayToEndTimeNotificationobject:self.playerItem];
先将在线视频链接存放在videoUrl中,然后初始化playerItem,playerItem是管理资源的对象(A player item manages the presentation state of an asset with which it is associated. A player item contains player item tracks—instances of AVPlayerItemTrack—that correspond to the tracks in the asset.)
然后监听playerItem的status和loadedTimeRange属性,status有三种状态:
AVPlayerStatusUnknown,
AVPlayerStatusReadyToPlay,
AVPlayerStatusFailed
当status等于AVPlayerStatusReadyToPlay时代表视频已经可以播放了,我们就可以调用play方法播放了。
loadedTimeRange属性代表已经缓冲的进度,监听此属性可以在UI中更新缓冲进度,也是很有用的一个属性。
最后添加一个通知,用于监听视频是否已经播放完毕,然后实现KVO的方法:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {AVPlayerItem *playerItem = (AVPlayerItem *)object;if ([keyPath isEqualToString:@"status"]) {if ([playerItem status] == AVPlayerStatusReadyToPlay) {NSLog(@"AVPlayerStatusReadyToPlay");self.stateButton.enabled = YES;CMTime duration = self.playerItem.duration;// 获取视频总长度CGFloat totalSecond = playerItem.duration.value / playerItem.duration.timescale;// 转换成秒_totalTime = [self convertTime:totalSecond];// 转换成播放时间[self customVideoSlider:duration];// 自定义UISlider外观NSLog(@"movie total duration:%f",CMTimeGetSeconds(duration));[self monitoringPlayback:self.playerItem];// 监听播放状态} else if ([playerItem status] == AVPlayerStatusFailed) {NSLog(@"AVPlayerStatusFailed");}} else if ([keyPath isEqualToString:@"loadedTimeRanges"]) {NSTimeInterval timeInterval = [self availableDuration];// 计算缓冲进度NSLog(@"Time Interval:%f",timeInterval);CMTime duration = self.playerItem.duration;CGFloat totalDuration = CMTimeGetSeconds(duration);[self.videoProgress setProgress:timeInterval / totalDuration animated:YES];}
}- (NSTimeInterval)availableDuration {NSArray *loadedTimeRanges = [[self.playerView.player currentItem] loadedTimeRanges];CMTimeRange timeRange = [loadedTimeRanges.firstObject CMTimeRangeValue];// 获取缓冲区域float startSeconds = CMTimeGetSeconds(timeRange.start);float durationSeconds = CMTimeGetSeconds(timeRange.duration);NSTimeInterval result = startSeconds + durationSeconds;// 计算缓冲总进度return result;
}- (NSString *)convertTime:(CGFloat)second{NSDate *d = [NSDate dateWithTimeIntervalSince1970:second];NSDateFormatter *formatter = [[NSDateFormatter alloc] init];if (second/3600 >= 1) {[formatter setDateFormat:@"HH:mm:ss"];} else {[formatter setDateFormat:@"mm:ss"];}NSString *showtimeNew = [formatter stringFromDate:d];return showtimeNew;
}
此方法主要对status和loadedTimeRanges属性做出响应,status状态变为AVPlayerStatusReadyToPlay时,说明视频已经可以播放了,这时我们可以获取一些视频的信息,包含视频长度等,把播放按钮设备enabled,点击就可以调用play方法播放视频了。在AVPlayerStatusReadyToPlay的底部还有个monitoringPlayback方法
- (void)monitoringPlayback:(AVPlayerItem *)playerItem {self.playbackTimeObserver = [self.playerView.player addPeriodicTimeObserverForInterval:CMTimeMake(1, 1) queue:NULL usingBlock:^(CMTime time) {CGFloat currentSecond = playerItem.currentTime.value/playerItem.currentTime.timescale;// 计算当前在第几秒[self updateVideoSlider:currentSecond];NSString *timeString = [self convertTime:currentSecond];self.timeLabel.text = [NSString stringWithFormat:@"%@/%@",timeString,_totalTime];}];
}
monitoringPlayback用于监听每秒的状态,- (id)addPeriodicTimeObserverForInterval:(CMTime)interval queue:(dispatch_queue_t)queue usingBlock:(void (^)(CMTime time))block;此方法就是关键,interval参数为响应的间隔时间,这里设为每秒都响应,queue是队列,传NULL代表在主线程执行。可以更新一个UI,比如进度条的当前时间等。
作为播放器,除了播放,暂停等功能外。还有一个必不可少的功能,那就是显示当前播放进度,还有缓冲的区域,我的思路是这样,用UIProgressView显示缓冲的可播放区域,用UISlider显示当前正在播放的进度,当然这里要对UISlider做一些自定义,代码如下:
- (void)customVideoSlider:(CMTime)duration {self.videoSlider.maximumValue = CMTimeGetSeconds(duration);UIGraphicsBeginImageContextWithOptions((CGSize){ 1, 1 }, NO, 0.0f);UIImage *transparentImage = UIGraphicsGetImageFromCurrentImageContext();UIGraphicsEndImageContext();[self.videoSlider setMinimumTrackImage:transparentImage forState:UIControlStateNormal];[self.videoSlider setMaximumTrackImage:transparentImage forState:UIControlStateNormal];
}
把UISlider添加至UIProgressView上面,运行起来的效果就变成了这样:
参考:https://www.cnblogs.com/mzds/p/3711867.html
Demo