說明
基于AVPlayer和MVP模式封裝的一個(gè)視頻播放控制器,支持全屏押逼,暫停播放羊苟,進(jìn)度條拖動(dòng)。
AVPlayer框架介紹
AVPlay既可以用來播放音頻也可以用來播放視頻哎迄,AVPlay在播放音頻方面可以直接用來播放網(wǎng)絡(luò)上的音頻。在使用AVPlay的時(shí)候我們需要導(dǎo)入AVFoundation.framework框架隆圆,再引入頭文件#import<AVFoundation/AVFoundation.h>漱挚。
主要包括下面幾個(gè)類
1.AVPlayer:播放器類
2.AVPlayerItem:播放單元類,即一個(gè)播放源
3.AVPlayerLayer:播放界面
使用時(shí)渺氧,需要先根據(jù)NSURL生成一個(gè)播放源棱烂,[AVPlayerItem playerItemWithURL:]
,再根據(jù)這個(gè)播放源獲得一個(gè)播放器對(duì)象阶女,[AVPlayer playerWithPlayerItem:];
颊糜,此時(shí)播放器已經(jīng)準(zhǔn)備完成,但還需要根據(jù)AVPlayer生成一個(gè)AVPlayerLayer秃踩,設(shè)置frame衬鱼,再加入到superView.layer中,[AVPlayerLayer playerLayerWithPlayer:]; self.playerLayer.frame = CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.width*0.6); [self.layer addSublayer:self.playerLayer];
此時(shí)一個(gè)簡(jiǎn)單的播放器就已經(jīng)配置完成憔杨。
暫停播放
AVPlayer有一個(gè)rate屬性鸟赫,可以根據(jù)這個(gè)屬性來判斷當(dāng)前是否在播放,rate == 0.f
為暫停消别,反之視頻播放抛蚤。
AVPlayerItemStatus
可以對(duì)AVPlayerItem設(shè)置kvo,監(jiān)聽視頻源是否可播放寻狂,系統(tǒng)給了三種狀態(tài)岁经,如下:
typedef NS_ENUM(NSInteger, AVPlayerItemStatus) {
AVPlayerItemStatusUnknown,
AVPlayerItemStatusReadyToPlay,
AVPlayerItemStatusFailed
};
設(shè)置KVO監(jiān)聽:
[self.playerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
if ([keyPath isEqualToString:@"status"]) {
AVPlayerItemStatus status = [change[NSKeyValueChangeNewKey] intValue];
if (status == AVPlayerItemStatusReadyToPlay) {
isReadyToPlay = YES;
[self.player play];
}else{
//預(yù)留
isReadyToPlay = NO;
}
[self.controlView controlItemStatus:status playItem:object];
}
}
全屏操作
Demo中給出的思路是:
1.首先將當(dāng)前豎屏狀態(tài)下的播放器的view的frame保存下來,方便退出全屏?xí)r蛇券,布局缀壤;
2.然后新建一個(gè)全屏展示View的控制器樊拓,重寫該控制器的@property(nonatomic, readonly) UIInterfaceOrientation preferredInterfaceOrientationForPresentation NS_AVAILABLE_IOS(6_0) __TVOS_PROHIBITED;
,強(qiáng)制讓該控制器旋轉(zhuǎn)塘慕;
3.將當(dāng)前根控制器present到上述的全屏控制器筋夏,在completion:回調(diào)中,做個(gè)簡(jiǎn)單的動(dòng)畫過渡一下图呢,然后再將承載AVPlayerLayer的view的frame改成橫屏狀態(tài)条篷,然后再修改AVPlayerLayer的frame;
退出全屏:
1.將當(dāng)前全屏控制器dismiss蛤织;
2.再dismiss的成功回調(diào)中赴叹,設(shè)置View的frame為進(jìn)入全屏前保存的frame;
3.再將AVPlayerLayer的frame修改瞳筏。
代碼如下:
#pragma mark - 進(jìn)入全屏和退出全屏的動(dòng)畫和present處理
- (void)enterFullScreen:(BOOL)rightOrLeft{
playViewBeforeRect = _playerView.frame;
playViewBeforeCenter = _playerView.center;
TBZAVFullViewController *vc = [[TBZAVFullViewController alloc] init];
vc.type = rightOrLeft;
self.fullVC = vc;
__weak TBZAVPlayerViewController *weakSelf = self;
[self.navigationController presentViewController:vc animated:false completion:^{
[UIView animateWithDuration:0.25 animations:^{
weakSelf.playerView.frame = CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height);
} completion:^(BOOL finished) {
[weakSelf.playerView enterFull];
[weakSelf.fullVC.view addSubview:weakSelf.playerView];
[UIApplication.sharedApplication.keyWindow insertSubview:UIApplication.sharedApplication.keyWindow.rootViewController.view belowSubview:vc.view.superview];
self->isFull = YES;
}];
}];
}
- (void)exitFullScreen{
__weak TBZAVPlayerViewController *weakSelf = self;
[self.fullVC dismissViewControllerAnimated:false completion:^{
[UIView animateWithDuration:0.25 animations:^{
weakSelf.playerView.frame = self->playViewBeforeRect;
} completion:^(BOOL finished) {
[weakSelf.playerView exitFull];
[weakSelf.view addSubview:weakSelf.playerView];
self->isFull = NO;
}];
}];
}
播放進(jìn)度
主要就是需要對(duì)AVPlayer添加監(jiān)聽稚瘾,且注意需要釋放該方法返回的對(duì)象牡昆。AVPlayerItem有兩個(gè)屬性姚炕,currentTime和duration,這兩個(gè)對(duì)象都是CMTime類丢烘,可以用CMTimeGetSeconds(CMTime t);得到一個(gè)float指柱宦,秒數(shù)。也就是CMTimeGetSeconds(item.currentTime)
可以得到當(dāng)前播放到第幾秒播瞳,CMTimeGetSeconds(item.duration)
可以得到當(dāng)前視頻的總時(shí)長(zhǎng)掸刊。
/*!
@method addPeriodicTimeObserverForInterval:queue:usingBlock:
@abstract Requests invocation of a block during playback to report changing time.
@param interval
The interval of invocation of the block during normal playback, according to progress of the current time of the player.
@param queue
The serial queue onto which block should be enqueued. If you pass NULL, the main queue (obtained using dispatch_get_main_queue()) will be used. Passing a
concurrent queue to this method will result in undefined behavior.
@param block
The block to be invoked periodically.
@result
An object conforming to the NSObject protocol. You must retain this returned value as long as you want the time observer to be invoked by the player.
Pass this object to -removeTimeObserver: to cancel time observation.
@discussion The block is invoked periodically at the interval specified, interpreted according to the timeline of the current item.
The block is also invoked whenever time jumps and whenever playback starts or stops.
If the interval corresponds to a very short interval in real time, the player may invoke the block less frequently
than requested. Even so, the player will invoke the block sufficiently often for the client to update indications
of the current time appropriately in its end-user interface.
Each call to -addPeriodicTimeObserverForInterval:queue:usingBlock: should be paired with a corresponding call to -removeTimeObserver:.
Releasing the observer object without a call to -removeTimeObserver: will result in undefined behavior.
*/
- (id)addPeriodicTimeObserverForInterval:(CMTime)interval queue:(nullable dispatch_queue_t)queue usingBlock:(void (^)(CMTime time))block;
/*!
@method removeTimeObserver:
@abstract Cancels a previously registered time observer.
@param observer
An object returned by a previous call to -addPeriodicTimeObserverForInterval:queue:usingBlock: or -addBoundaryTimeObserverForTimes:queue:usingBlock:.
@discussion Upon return, the caller is guaranteed that no new time observer blocks will begin executing. Depending on the calling thread and the queue
used to add the time observer, an in-flight block may continue to execute after this method returns. You can guarantee synchronous time
observer removal by enqueuing the call to -removeTimeObserver: on that queue. Alternatively, call dispatch_sync(queue, ^{}) after
-removeTimeObserver: to wait for any in-flight blocks to finish executing.
-removeTimeObserver: should be used to explicitly cancel each time observer added using -addPeriodicTimeObserverForInterval:queue:usingBlock:
and -addBoundaryTimeObserverForTimes:queue:usingBlock:.
*/
- (void)removeTimeObserver:(id)observer;
- (id)addPeriodicTimeObserverForInterval:(CMTime)interval queue:(nullable dispatch_queue_t)queue usingBlock:(void (^)(CMTime time))block;
其實(shí)就是一個(gè)Timer,每隔1秒執(zhí)行block赢乓,可以設(shè)置常駐子線程忧侧,如果設(shè)為NULL,就是在主線程牌芋。
主要使用如下:
__weak AVPlayer *weakAVPlayer = self.player;
__weak TBZAVPlayerView *weakSelf = self;
//監(jiān)聽播放進(jìn)度蚓炬,需要再destory方法中,釋放timeObserve
self.timeObserver = [self.player addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(1, NSEC_PER_SEC) queue:NULL usingBlock:^(CMTime time) {
CGFloat progress = CMTimeGetSeconds(weakAVPlayer.currentItem.currentTime) / CMTimeGetSeconds(weakAVPlayer.currentItem.duration);
if (progress == 1.0f) {
//視頻播放完畢
if (weakSelf.delegate && [weakSelf.delegate respondsToSelector:@selector(playEnd)]) {
[weakSelf.delegate playEnd];
}
}else{
[weakSelf.controlView controlPlayItem:weakAVPlayer.currentItem];
}
}];
- (void)destroy{
if (self.player || self.playerItem || self.playerLayer) {
[self.player pause];
if (self.timeObserver) {
[self.player removeTimeObserver:self.timeObserver];
}
[self.playerItem removeObserver:self forKeyPath:@"status"];
self.playerItem = nil;
self.player = nil;
[self.playerLayer removeFromSuperlayer];
}
}
總結(jié)
1.當(dāng)視頻源切換了之后躺屁,需要將當(dāng)前視頻源添加的監(jiān)聽都remove掉肯夏,重新給新的視頻源添加監(jiān)聽;
2.全屏跟退出全屏犀暑,主要是注意AVPlayerLayer的布局驯击,不會(huì)跟著superLayer的變動(dòng)而變動(dòng),需要手動(dòng)再設(shè)置一遍耐亏;
具體可以結(jié)合Demo來看徊都。
覺得有用,請(qǐng)幫忙點(diǎn)亮紅心
Better Late Than Never!
努力是為了當(dāng)機(jī)會(huì)來臨時(shí)不會(huì)錯(cuò)失機(jī)會(huì)广辰。
共勉碟贾!