iOS通過AVPlayer打造自己的視頻播放器
AVPlayer
AVPlayer是用于管理媒體資產的播放和定時控制器對象它提供了控制播放器的有運輸行為的接口,如它可以在媒體的時限內播放,暫停,和改變播放的速度,并有定位各個動態點的能力。可以使用AVPlayer來播放本地和遠程的視頻媒體文件,如QuickTime影片和MP3音頻文件,以及視聽媒體使用HTTP流媒體直播服務。
一個普通播放器的組成
概述
注意
AVPlayer旨在用于在一段內時間播放單個媒體資產。播放器實例可以重復使用其播放額外的媒體資產[replaceCurrentItem(with:)]的方法,但它管理僅單個媒體資產的播放一次。該框架還提供了的一個子類AVPlayer,叫做AVQueuePlayer
你可以用它來創建和媒體資產的隊列管理進行順序播放。
使用AVPlayer需導入AVFoundation框架。
#import <AVFoundation/AVFoundation.h>
創建AVPlayer需是全局對象,否則在運行時無法顯示視頻圖像。
<p>@property (nonatomic,strong) AVPlayer *player;</p>AVPlayer播放器的創建
首先創建資產AVURLAsset
self.asset=[[AVURLAsset alloc]initWithURL:_url options:nil];
使用AVURLAsset然后將asset對象導入到AVPlayerItem中
self.item=[AVPlayerItem playerItemWithAsset:self.assert];
再將item對象添加到AVPlayer中
self.player=[[AVPlayer alloc]initWithPlayerItem:self.item];
比直接使用AVPlayer初始化方法播放URL如
self.player=[[AVPlayer alloc]initWithURL:url];
的好處是,self.asset可以記錄緩存大小,而直接使用AVPlayer初始化URL不利于多個控制器更好的銜接緩存大小。
當我們在使用 今日頭條 或者 UC頭條 的時候,會發現點擊cell上的視頻播放一段時間后,再點擊cell上的評論會跳到另外一個控制器,但是視頻播放的位置和緩存的進度跟第一級控制器cell上位置一模一樣,看起來就像是2個控制器共用一個視頻播放器,這種無縫切換的效果用戶體驗很好,做法其實只需公用一個AVURLAsset就可以做到。
下面我將介紹下我最近封裝AVPlayer的視頻播放器SBPlayer組成及思路
SBPlayer github源碼---> github
SBPlayer是基于AVPlayer封裝的輕量級播放器,可以播放本地網絡視頻,易于定制,適合初學者學習打造屬于自己的視頻播放器
播放器集成于UIView 的SBPlayer.h文件開發的接口
#import "SBView.h"
import "SBPlayerLoading.h"
import <AVFoundation/AVFoundation.h>
import "SBPlayerControl.h"
import "SBPlayerPlayPausedView.h"
/*
設置視頻播放填充模式
/
typedef NS_ENUM(NSInteger,SBPlayerContentMode) {
SBPlayerContentModeResizeFit,//尺寸適合
SBPlayerContentModeResizeFitFill,//填充視圖
SBPlayerContentModeResize,//默認
};
typedef NS_ENUM(NSInteger,SBPlayerState) {
SBPlayerStateFailed, // 播放失敗
SBPlayerStateBuffering, // 緩沖中
SBPlayerStatePlaying, // 播放中
SBPlayerStateStopped, //停止播放
};
@interface SBPlayer : SBView
//當視頻沒有播放為0,播放后是1
@property (nonatomic,assign) NSInteger isNormal;
//加載的image;
@property (nonatomic,strong) UIImageView imageViewLogin;
//視頻填充模式
@property (nonatomic,assign) SBPlayerContentMode contentMode;
//播放狀態
@property (nonatomic,assign) SBPlayerState state;
//加載視圖
@property (nonatomic,strong) SBPlayerLoading loadingView;
//是否正在播放
@property (nonatomic,assign,readonly) BOOL isPlaying;
//暫停時的插圖
@property (nonatomic,strong) SBPlayerPlayPausedView playPausedView;
//urlAsset
@property (nonatomic,strong) AVURLAsset assert;
//當前時間
@property (nonatomic,assign) CMTime currentTime;
//播放器控制視圖
@property (nonatomic,strong) SBPlayerControl *playerControl;
//初始化
- (instancetype)initWithUrl:(NSURL *)url;
- (instancetype)initWithURLAsset:(AVURLAsset )asset;
//設置標題
-(void)setTitle:(NSString )title;
//跳到某個播放時間段
-(void)seekToTime:(CMTime)time;
//播放
-(void)play;
//暫停
-(void)pause;
//停止
-(void)stop;
//移除監聽,notification,dealloc
-(void)remove;
//顯示或者隱藏暫停按鍵
-(void)hideOrShowPauseView;</code></pre>
SBPlayer.m文件中的擴展方法
@interface SBPlayer ()<SBPlayerControlSliderDelegate,SBPlayerPlayPausedViewDelegate>
{
NSURL *_url;
NSTimer *_timer;
}
@property (nonatomic,strong) AVPlayerLayer *playerLayer;
@property (nonatomic,strong) AVPlayer *player;
@property (nonatomic,strong) AVPlayerItem *item;
//總時長
@property (nonatomic,assign) CGFloat totalDuration;
//轉換后的時間
@property (nonatomic,copy) NSString *totalTime;
//當前播放位置
@property (nonatomic,assign) CMTime currenTime;
//監聽播放值
@property (nonatomic,strong) id playbackTimerObserver;
//全屏控制器
@property (nonatomic,strong) UIViewController *fullVC;
//全屏播放器
@property (nonatomic,strong) SBPlayer *fullScreenPlayer;
@end
播放器的初始化
//配置播放器
-(void)configPlayer{
self.backgroundColor=[UIColor blackColor];
self.item=[AVPlayerItem playerItemWithAsset:self.assert];
self.player=[[AVPlayer alloc]init];
[self.player replaceCurrentItemWithPlayerItem:self.item];
self.player.usesExternalPlaybackWhileExternalScreenIsActive=YES;
self.playerLayer=[[AVPlayerLayer alloc]init];
self.playerLayer.backgroundColor=[UIColor blackColor].CGColor;
self.playerLayer.player=self.player;
self.playerLayer.frame=self.bounds;
[self.playerLayer displayIfNeeded];
[self.layer insertSublayer:self.playerLayer atIndex:0];
self.playerLayer.videoGravity=AVLayerVideoGravityResizeAspect;
}
由于是使用AutoLayout布局,而不是直接設置Frame,所以需要在layoutSubviews中初始化AVPlayerLayer大小,否則在AVPlayerLayer播放區域顯示不了自定義的控件,比如播放、暫停、進度條等等。
-(void)layoutSubviews{
[super layoutSubviews];
self.playerLayer.frame=self.bounds;
}
視頻播放需大量使用KVO和NSNotificationCenter
-(void)addKVO{
//監聽狀態屬性
[self.item addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
//監聽網絡加載情況屬性
[self.item addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];
//監聽播放的區域緩存是否為空
[self.item addObserver:self forKeyPath:@"playbackBufferEmpty" options:NSKeyValueObservingOptionNew context:nil];
//緩存可以播放的時候調用
[self.item addObserver:self forKeyPath:@"playbackLikelyToKeepUp" options:NSKeyValueObservingOptionNew context:nil];
//監聽暫停或者播放中
[self.player addObserver:self forKeyPath:@"rate" options:NSKeyValueObservingOptionNew context:nil];
[self.player addObserver:self forKeyPath:@"timeControlStatus" options:NSKeyValueObservingOptionNew context:nil];
[self.playerControl addObserver:self forKeyPath:@"scalling" options:NSKeyValueObservingOptionNew context:nil];
[self.playPausedView addObserver:self forKeyPath:@"backBtnTouched" options:NSKeyValueObservingOptionNew context:nil];
}
-(void)addNotification{
//監聽當視頻播放結束時
[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(SBPlayerItemDidPlayToEndTimeNotification:) name:AVPlayerItemDidPlayToEndTimeNotification object:[self.player currentItem]];
//監聽當視頻開始或快進或者慢進或者跳過某段播放
[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(SBPlayerItemTimeJumpedNotification:) name:AVPlayerItemTimeJumpedNotification object:[self.player currentItem]];
//監聽播放失敗時
[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(SBPlayerItemFailedToPlayToEndTimeNotification:) name:AVPlayerItemFailedToPlayToEndTimeNotification object:[self.player currentItem]];
[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(SBPlayerItemPlaybackStalledNotification:) name:AVPlayerItemPlaybackStalledNotification object:[self.player currentItem]];
[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(SBPlayerItemNewAccessLogEntryNotification:) name:AVPlayerItemNewAccessLogEntryNotification object:[self.player currentItem]];
[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(SBPlayerItemNewErrorLogEntryNotification:) name:AVPlayerItemNewErrorLogEntryNotification object:[self.player currentItem]];
[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(SBPlayerItemFailedToPlayToEndTimeErrorKey:) name:AVPlayerItemFailedToPlayToEndTimeErrorKey object:[self.player currentItem]];
[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(deviceOrientationDidChange:) name:UIDeviceOrientationDidChangeNotification object:nil];
}
進度條的實現

添加自定義控制器:有播放、暫停、進度條、全屏功能

#pragma mark - addPlayerControl
//添加播放控制器
-(void)addPlayerControl{
self.playerControl=[[SBPlayerControl alloc]init];
self.playerControl.minValue=0.0f;
self.playerControl.delegate=self;
//設置播放控制器的背景顏色
self.playerControl.backgroundColor=[UIColor colorWithRed:0.20 green:0.20 blue:0.20 alpha:0.5];
NSLog(@"self.totalDuration:%f",self.totalDuration);
[self addSubview:self.playerControl];
[self.playerControl mas_makeConstraints:^(MASConstraintMaker *make) {
make.bottom.mas_equalTo(self.mas_bottom).priorityHigh();
make.left.mas_equalTo(self.mas_left);
make.right.mas_equalTo(self.mas_right);
make.height.mas_equalTo(@(controlHeight));
}];
[self setNeedsLayout];
[self layoutIfNeeded];
self.playerControl.hidden=YES;
}</code></pre>
在自定義進度條的時候,如果使用UISlider定制進度條,雖然能實時顯示視頻播放的進度,卻無法顯示視頻緩沖的進度。當自己高高興興,辛辛苦苦用UISlider寫好了進度條后突然想到還要加緩沖進度條,這就坑爹了。還好,天無絕人之路,只需在UISlider視圖底層再加一層UIProgressView, 設置UISider setMaximumTrackTintColor 為透明色,改變UIProgressView的值便可以成功加入緩沖進度條。最好是通過UIView自定義進度條。我在寫進度條的時候正好是用UISilder做的,可實現緩沖進度哦,大家有興趣看下源碼,幫忙加star。
加載動畫的實現
SBPlayer加載動畫是通過旋轉圖片實現,使用簡單
#import "SBPlayerLoading.h"
#import <Masonry.h>
//間隔時間
#define duration 1.0f
//加載圖片名
#define kLoadingImageName @"Source.bundle/collection_loading"
@interface SBPlayerLoading (){
NSTimer _timer;//定時器
}
@property (nonatomic,strong) UIImageView loadingImage;//加載時的圖片
@end
@implementation SBPlayerLoading
//初始化
(instancetype)init
{
self = [super init];
if (self) {
self.loadingImage=[[UIImageView alloc]initWithImage:[UIImage imageNamed:kLoadingImageName]];
self.loadingImage.contentMode=UIViewContentModeScaleAspectFill;
[self addSubview:self.loadingImage];
[self addConstraintWithView:self.loadingImage];
}
return self;
}
//添加約束
-(void)addConstraintWithView:(UIImageView )imageView{
[imageView mas_makeConstraints:^(MASConstraintMaker make) {
make.centerX.centerY.mas_equalTo(self);
make.size.mas_equalTo(CGSizeMake(30, 30));
}];
[self setNeedsLayout];
[self layoutIfNeeded];
}
//顯示
-(void)show{
if ([_timer isValid]) {
[_timer invalidate];
_timer=nil;
}
self.hidden=NO;
_timer=[NSTimer timerWithTimeInterval:duration/2 target:self selector:@selector(rotationImage) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop]addTimer:_timer forMode:NSDefaultRunLoopMode];
}
//旋轉圖片
-(void)rotationImage{
[UIView animateWithDuration:duration animations:^{
self.loadingImage.transform=CGAffineTransformRotate(self.loadingImage.transform, M_PI);
}];
}
//隱藏
-(void)hide{
if ([_timer isValid]) {
[_timer invalidate];
_timer=nil;
}
self.hidden=YES;
}
@end</code></pre>
全屏的實現
SBPlayer的全屏是通過Present出一個新的UIViewController,通過 NSNotificationCenter 監聽 UIDeviceOrientationDidChangeNotification 來處理橫屏和豎屏下的播放器。
由于SBPlayer是繼承UIView,UIView是不可以推出新的控制器的,所以通過keyWindow獲取當前控制器。這樣就不用管是在UITableViewController還是UIViewController
//獲取當前屏幕顯示的viewcontroller
(UIViewController )getCurrentVC
{
UIViewController result = nil;
UIWindow * window = [[UIApplication sharedApplication] keyWindow];
if (window.windowLevel != UIWindowLevelNormal)
{
NSArray *windows = [[UIApplication sharedApplication] windows];
for(UIWindow * tmpWin in windows)
{
if (tmpWin.windowLevel == UIWindowLevelNormal)
{
window = tmpWin;
break;
}
}
}
UIView *frontView = [[window subviews] objectAtIndex:0];
id nextResponder = [frontView nextResponder];
if ([nextResponder isKindOfClass:[UIViewController class]])
result = nextResponder;
else
result = window.rootViewController;
return result;
}</code></pre>
點擊全屏按鈕或者旋轉手機的時候,自動判斷旋轉方向,當橫屏時present新的控制器,豎屏時銷毀
- (void)deviceOrientationDidChange: (NSNotification *)notification
{
UIInterfaceOrientation _interfaceOrientation=[[UIApplication sharedApplication]statusBarOrientation];
switch (_interfaceOrientation) {
case UIInterfaceOrientationLandscapeLeft:
case UIInterfaceOrientationLandscapeRight:
{
self.fullVC=[self pushToFullScreen];
[self.player pause];
[self.fullScreenPlayer seekToTime:self.item.currentTime];
[[self getCurrentVC] presentViewController:self.fullVC animated:YES completion:nil];
}
break;
case UIInterfaceOrientationPortraitUpsideDown:
case UIInterfaceOrientationPortrait:
{
if (self.fullVC) {
if (self.fullScreenPlayer.isPlaying) {
[self.player play];
[self.playPausedView hide];
}else{
[self pause];
_isNormal=1;
[self hideOrShowPauseView];
}
if (self.fullScreenPlayer.item.currentTime.value/self.fullScreenPlayer.item.currentTime.timescale>0) {
[self.player seekToTime:self.fullScreenPlayer.currentTime];
}
[self.fullScreenPlayer remove];
self.fullScreenPlayer=nil;
[self.fullVC dismissViewControllerAnimated:YES completion:nil];
}
}
break;
case UIInterfaceOrientationUnknown:
NSLog(@"UIInterfaceOrientationLandscapePortial");
break;
}
}
視頻前添加標題和彈幕
前面重要的步驟處理完后,在視頻上添加標題彈幕只需在AVPlayerLayer所在UIView上添加相關控件,就可以實現這些簡單功能。
來自:http://www.jianshu.com/p/ffe1bd598bf2