iOS 教你使用MP官份、AVPlayer只厘、AVPlayerVC構(gòu)建一個完整的視頻播放器

前言

標題必須要浮夸!要感覺像是一個大新聞舅巷。長者如是說羔味。
其實是前幾天去面試的時候,被要求說必須做過視頻播放相關(guān)項目钠右。有點鬧心之余赋元,就花了點時間在家寫了一個簡單播放器,基本實現(xiàn)了主流播放器的大致功能。之前項目沒有需求用到過視頻播放搁凸,所以寫的時候難免會遇到一些坑媚值,花了一些時間解決。

蘋果在視頻播放方面提供了多個框架供我們選擇使用护糖。分別為:

  • 基于mediaPlayer類庫的MPMediaPlayerController(iOS9后遭到廢棄褥芒,被AVPlayerViewController所替代)
  • 基于AVFounditon類庫的AVPlayer
  • 基于AVKit類庫的AVPlayerViewController(iOS8后才可使用)

正文

AVPlayer與MPMediaPlayerController比較:

  • AVplayer有更多的靈活性,當然嫡良,也需要你去自定義構(gòu)建UI锰扶。還有一大優(yōu)勢,例如其擴展的AVQueuePlayer皆刺,可以實現(xiàn)視頻無縫隊列播放少辣、多視頻同時播放、視頻轉(zhuǎn)換羡蛾、編解碼等功能漓帅。
  • MPMediaPlayerController實際上是基于AVPlayer的簡單UI封裝,對于一般的播放要求痴怨,幾行代碼就可實現(xiàn)忙干,省心省事。
    因為MPMediaPlayerController是對AVPlayer進行的單例封裝浪藻,所以不能進行多視頻播放捐迫。
播放器Demo(全屏)已實現(xiàn)功能點:
  • push到播放器頁面,橫屏顯示爱葵。
  • 單機隱藏or顯示上方標題欄與下方操作欄施戴。
  • 呼出右側(cè)設(shè)置欄。
  • 視頻播放操作與進度條設(shè)置萌丈。
  • 在屏幕上左右拖動赞哗,進行視頻快進與快退。
  • 在屏幕左側(cè)上下拖動辆雾,進行亮度調(diào)整肪笋。
  • 在屏幕右側(cè)上下拖動,進行音量調(diào)整度迂。
想到但是暫未實現(xiàn)的功能點:(大多為優(yōu)化或與業(yè)務(wù)相關(guān))
  • 屏幕或進度條拖動快進操作時藤乙,添加提示框進行快進時間的實時提示。
  • 用戶無操作兩三秒之后自動隱藏上下View惭墓。
  • 視頻清晰度調(diào)整按鈕坛梁。(更換視頻源)
  • 操作加鎖按鈕。(加鎖后未進行解鎖操作之前不可進行操作)
  • 彈幕相關(guān)腊凶。
  • 用戶允許橫屏狀態(tài)下划咐,橫屏豎屏自動進行頁面切換與動畫效果等毅人。
  • 網(wǎng)絡(luò)視頻的緩存、下載等尖殃。
  • 軟硬解碼模式切換等。

筆者Demo選擇使用了AVPlayer進行視頻播放器的構(gòu)建划煮。由于UI的代碼實現(xiàn)送丰,加上略蛋疼的邏輯代碼,播放器頁面的代碼量達到400多行弛秋,之后有時間的話會再進行優(yōu)化器躏。這里只貼出部分代碼,想要查看或借鑒完整Demo蟹略,可以到本人github去下載登失。

使用AVPlayer構(gòu)建播放器

1.導入頭文件

#import <AVFoundation/AVFoundation.h>

2.其實沒什么可說的,很簡單挖炬,先初始化AVPlayer揽浙,然后添加到AVPlayerLayer,最后將其添加到視圖的layer層意敛。

#pragma mark - Demo中此視圖的屬性

#define TopViewHeight 55
#define BottomViewHeight 72
#define mainWidth [UIScreen mainScreen].bounds.size.width
#define mainHeight [UIScreen mainScreen].bounds.size.height

//上層建筑
@property (nonatomic,strong)UIView *topView;
@property (nonatomic,strong)UIButton *backBtn;
@property (nonatomic,strong)UILabel *titleLabel;
@property (nonatomic,strong)UIButton *settingsBtn;

//經(jīng)濟基礎(chǔ)
@property (nonatomic,strong)UIView *bottomView;
@property (nonatomic,strong)UIButton *playBtn;
@property (nonatomic,strong)UILabel *textLabel;
@property (nonatomic,assign)BOOL isPlay;
@property (nonatomic,strong)UISlider *movieProgressSlider;//進度條
@property (nonatomic,assign)CGFloat ProgressBeginToMove;
@property (nonatomic,assign)CGFloat totalMovieDuration;//視頻總時間

//核心軀干
@property (nonatomic,strong)AVPlayer *player;

//神之右手
@property (nonatomic,strong)UIView *settingsView;
@property (nonatomic,strong)UIView *rightView;
@property (nonatomic,strong)UIButton *setTestBtn;

//touch evens
@property (nonatomic,assign)BOOL isShowView;
@property (nonatomic,assign)BOOL isSettingsViewShow;
@property (nonatomic,assign)BOOL isSlideOrClick;

@property (nonatomic,strong)UISlider *volumeViewSlider;
@property (nonatomic,assign)float systemVolume;//系統(tǒng)音量值
@property (nonatomic,assign)float systemBrightness;//系統(tǒng)亮度
@property (nonatomic,assign)CGPoint startPoint;//起始位置坐標

@property (nonatomic,assign)BOOL isTouchBeganLeft;//起始位置方向
@property (nonatomic,copy)NSString *isSlideDirection;//滑動方向
@property (nonatomic,assign)float startProgress;//起始進度條

#pragma mark - 播放器軀干
- (void)createAvPlayer{
    //設(shè)置靜音狀態(tài)也可播放聲音
    AVAudioSession *audioSession = [AVAudioSession sharedInstance];
    [audioSession setCategory:AVAudioSessionCategoryPlayback error:nil];
    
    CGRect playerFrame = CGRectMake(0, 0, self.view.layer.bounds.size.height, self.view.layer.bounds.size.width);
    
    AVURLAsset *asset = [AVURLAsset assetWithURL: _url];
    Float64 duration = CMTimeGetSeconds(asset.duration);
    //獲取視頻總時長
    _totalMovieDuration = duration;
    
    AVPlayerItem *playerItem = [AVPlayerItem playerItemWithAsset: asset];

    _player = [[AVPlayer alloc]initWithPlayerItem:playerItem];
    
    AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:_player];
    playerLayer.frame = playerFrame;
    playerLayer.videoGravity = AVLayerVideoGravityResizeAspect;
    [self.view.layer addSublayer:playerLayer];
    //需要設(shè)置自動播放的直接play即可
    //[_player play];
}
屏幕單擊手勢與視頻快進

屏幕單擊
1.符合條件的情況下(手指按下后離開屏幕馅巷,并且沒有拖動)通過BOOL值判斷,隱藏或顯示上下View

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
    CGPoint point = [[touches anyObject] locationInView:self.view];
     if (_isShowView) {
        //上下View為顯示狀態(tài),此時點擊上下View直接return
        if ((point.y>CGRectGetMinY(self.topView.frame)&&point.y< CGRectGetMaxY(self.topView.frame))||(point.y<CGRectGetMaxY(self.bottomView.frame)&&point.y>CGRectGetMinY(self.bottomView.frame))) {
            return
         }
        _isShowView = NO;
        [UIView animateWithDuration:0.5 animations:^{
            _topView.alpha = 0;
            _bottomView.alpha = 0;
        }];
    }else{
        _isShowView = YES;
        [UIView animateWithDuration:0.5 animations:^{
            _topView.alpha = 1;
            _bottomView.alpha = 1;
        }];
    }
}

2.右側(cè)View顯示的狀態(tài)下草姻,點擊屏幕左半空白區(qū)域钓猬,隱藏右側(cè)View

if (_isSettingsViewShow) {
    if (point.x>CGRectGetMinX(_rightView.frame)&&point.x< CGRectGetMaxX(_rightView.frame)) {
        return;
    }
    _settingsView.alpha = 0;
    _isSettingsViewShow = NO;
}

拖動快進
1.計算后得出拖動方向為橫向拖動。

CGPoint location = [[touches anyObject] locationInView:self.view];
CGFloat changeY = location.y - _startPoint.y;
CGFloat changeX = location.x - _startPoint.x;
if(fabs(changeX) > fabs(changeY)){
    _isSlideDirection = @"橫向";//設(shè)置為橫向
}else if(fabs(changeY)>fabs(changeX)){
    _isSlideDirection = @"縱向";//設(shè)置為縱向
}else{
    _isSlideOrClick = NO;
    NSLog(@"不在五行中撩独。");
}

2.根據(jù)手指按下與離開屏幕后敞曹,橫向位移的坐標值,對視頻播放進度進行刷新综膀。

    if (_isSlideOrClick) {
        _isSlideDirection = @"";
         _isSlideOrClick = NO;
            
        CGFloat changeY = point.y - _startPoint.y;
        CGFloat changeX = point.x - _startPoint.x;
        //如果位置改變 刷新進度條
        if(fabs(changeX) > fabs(changeY)){
            [self scrubberIsScrolling];
        }
        return;
    }
//拖動進度條
-(void)scrubberIsScrolling{
    //計算出拖動的當前秒數(shù)(總長*當前百分比)
    NSInteger dragedSeconds = floorf(_totalMovieDuration * _movieProgressSlider.value);
    CMTime newCMTime = CMTimeMake(dragedSeconds, 1);
    
    [_player seekToTime:newCMTime completionHandler:^(BOOL finished) {
        [_player play];
        [_playBtn setTitle:@"暫停" forState:UIControlStateNormal];
    }];
}

MPMediaPlayerController與AVPlayerViewController的使用介紹

MPMediaPlayerController與AVPlayerViewController澳迫,兩者都是基于AVPlayer的簡單UI封裝,如果只是需要簡單的視頻播放功能僧须,可以使用這兩個類快速的構(gòu)建視頻播放器纲刀。

MPMediaPlayerController
1.導入頭文件

#import <MediaPlayer/MediaPlayer.h>

2.初始化mp,幾行代碼既可以實現(xiàn)担平。

@property (nonatomic,strong)MPMoviePlayerController *mp;

NSURL *url1 = [[NSBundle mainBundle]URLForResource:@"chenyifaer" withExtension:@"mp4"];

_mp = [[MPMoviePlayerController alloc] initWithContentURL:url1];
_mp.controlStyle = MPMovieControlStyleNone;
_mp.view.frame = CGRectMake(0, 0, self.view.layer.bounds.size.height, self.view.layer.bounds.size.width);
[self.view addSubview:_mp.view];
[_mp play];

controlStyle屬性有三個值:

  • MPMovieControlStyleNone, //無控制
  • MPMovieControlStyleEmbedded, //有全屏按鈕與控制
  • MPMovieControlStyleFullscreen, // 默認全屏示绊,有退出和控制

當然還有一些其他屬性,有需要可以自行進行設(shè)置暂论。

AVPlayerViewController
1.導入框架與頭文件

#import <AVKit/AVKit.h>

2.初始化AVPlayerViewController,創(chuàng)建一個AVPlayer添加上面褐。然后將其添加到視圖上,再將View添加到self.View上取胎,然后play即可

NSURL *url1 = [[NSBundle mainBundle]URLForResource:@"chenyifaer" withExtension:@"mp4"];

AVPlayer * player = [AVPlayer playerWithURL:url1];
AVPlayerViewController *playerController = [[AVPlayerViewController alloc]init];
playerController.player = player;
    
[self addChildViewController:playerController];
[self.view addSubview:playerController.view];
playerController.view.frame = CGRectMake(0, 0, self.view.layer.bounds.size.height, self.view.layer.bounds.size.width);
[player play];

同樣幾行代碼展哭,即可實現(xiàn)湃窍。

音量調(diào)整

1.導入頭文件

#import <MediaPlayer/MediaPlayer.h>

2.借助MPVolumeView類來獲取到其音量進度條,進而進行音量獲取與控制

@property (nonatomic,strong)UISlider *movieProgressSlider;//進度條

MPVolumeView *volumeView = [[MPVolumeView alloc] init];
_volumeViewSlider = nil;
for (UIView *view in [volumeView subviews]){
    if ([view.class.description isEqualToString:@"MPVolumeSlider"]){
        _volumeViewSlider = (UISlider *)view;
        break;
}

3.觸摸屏幕時匪傍,記錄手指按下的位置您市、獲取按下時系統(tǒng)的音量(實現(xiàn)touchesBegan方法)

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
        _startProgress = _movieProgressSlider.value;
}

4.手指在規(guī)定行為下(手指按下位置為視圖右半?yún)^(qū),且縱向滑動)持續(xù)滑動時役衡,動態(tài)改變系統(tǒng)音量(實現(xiàn)touchesMoved方法)

//手指持續(xù)滑動茵休,此方法會持續(xù)調(diào)用
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
    CGPoint location = [[touches anyObject] locationInView:self.view];
    int index = location.y - _startPoint.y;
    if(index>0){
        [_volumeViewSlider setValue:_systemVolume - (abs(index)/10 * 0.05) animated:YES];
        [_volumeViewSlider sendActionsForControlEvents:UIControlEventTouchUpInside];
    }else{
       [_volumeViewSlider setValue:_systemVolume + (abs(index)/10 * 0.05) animated:YES];
        [_volumeViewSlider sendActionsForControlEvents:UIControlEventTouchUpInside];
    }
}
亮度調(diào)整

1.觸摸屏幕時,記錄手指按下的位置手蝎、按下時屏幕的亮度(實現(xiàn)touchesBegan方法)
2.手指在規(guī)定行為下(手指按下位置為視圖左半?yún)^(qū)榕莺,且縱向滑動)持續(xù)滑動時,不斷動態(tài)處理(實現(xiàn)touchesMoved方法)
3.改變屏幕亮度:[UIScreen mainScreen].brightness = X (0~1);

//手指持續(xù)滑動棵介,此方法會持續(xù)調(diào)用
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
    CGPoint location = [[touches anyObject] locationInView:self.view];
    int index = location.y - _startPoint.y;
    if(index>0){
        [UIScreen mainScreen].brightness = _systemBrightness - abs(index)/10 * 0.01;
    }else{
        _movieProgressSlider.value = _startProgress - abs(index)/10 * 0.008;
    }
}
屏幕旋轉(zhuǎn)

1.設(shè)置應(yīng)用支持橫屏(默認支持)钉鸯。
2.在根視圖中設(shè)置默認豎屏(Nav、TabBar邮辽、VC基類)

- (BOOL)shouldAutorotate{
    return NO;
}
- (UIInterfaceOrientationMask)supportedInterfaceOrientations{
    return UIInterfaceOrientationMaskPortrait;
}

3.在需要橫屏的VC中重寫下列方法即可

//允許橫屏旋轉(zhuǎn)
- (BOOL)shouldAutorotate{
    return YES;
}

//支持左右旋轉(zhuǎn)
- (UIInterfaceOrientationMask)supportedInterfaceOrientations{
    return UIInterfaceOrientationMaskLandscapeRight|UIInterfaceOrientationMaskLandscapeLeft;
}

//默認為右旋轉(zhuǎn)
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation{
    return UIInterfaceOrientationLandscapeRight;
}

源碼

點此下載:github源碼
作者其他文章推薦:iOS 使用CIDetector掃描相冊二維碼唠雕、原生掃描

結(jié)語

怎么樣,實現(xiàn)一個簡單的視頻播放器是不是很簡單吨述。當然及塘,要做一個好的視頻播放器,需要更多的優(yōu)化處理锐极、UI處理笙僚、業(yè)務(wù)功能擴展、動畫效果....
筆者Demo中關(guān)于timer與進度條的互動之間不甚完美灵再,還有些小問題肋层。
總之,初次嘗試寫翎迁,水平有限栋猖,如有錯誤,還望指正汪榔。

參考:
1.http://www.techotopia.com/index.php/iOS8AVPlayerAndPlayerViewController
2.http://stackoverflow.com/questions/8146942/avplayer-and-mpmovieplayercontroller-differences
3.http://www.reibang.com/p/e64fe3c7f9ab
4.http://www.th7.cn/Program/IOS/201504/439086.shtml
其他小問題多在stackoverflow查詢解決蒲拉。


PS:最后吐槽一下。因為寫的稍微有點多痴腌,想做個快速電梯樹進行頁內(nèi)跳轉(zhuǎn)雌团,結(jié)果發(fā)現(xiàn)簡書竟然不支持<span id="jump">正文</span>這樣使用錨點進行頁內(nèi)跳轉(zhuǎn)的語法。簡直坑爹士聪。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末锦援,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子剥悟,更是在濱河造成了極大的恐慌灵寺,老刑警劉巖曼库,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異略板,居然都是意外死亡毁枯,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進店門叮称,熙熙樓的掌柜王于貴愁眉苦臉地迎上來后众,“玉大人,你說我怎么就攤上這事颅拦。” “怎么了教藻?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵距帅,是天一觀的道長。 經(jīng)常有香客問我括堤,道長碌秸,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任悄窃,我火速辦了婚禮讥电,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘轧抗。我一直安慰自己恩敌,他們只是感情好,可當我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布横媚。 她就那樣靜靜地躺著纠炮,像睡著了一般。 火紅的嫁衣襯著肌膚如雪灯蝴。 梳的紋絲不亂的頭發(fā)上恢口,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天,我揣著相機與錄音穷躁,去河邊找鬼耕肩。 笑死,一個胖子當著我的面吹牛问潭,可吹牛的內(nèi)容都是我干的猿诸。 我是一名探鬼主播,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼狡忙,長吁一口氣:“原來是場噩夢啊……” “哼两芳!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起去枷,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤怖辆,失蹤者是張志新(化名)和其女友劉穎是复,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體竖螃,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡淑廊,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了特咆。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片季惩。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖腻格,靈堂內(nèi)的尸體忽然破棺而出画拾,到底是詐尸還是另有隱情,我是刑警寧澤菜职,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布青抛,位于F島的核電站,受9級特大地震影響酬核,放射性物質(zhì)發(fā)生泄漏蜜另。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一嫡意、第九天 我趴在偏房一處隱蔽的房頂上張望举瑰。 院中可真熱鬧,春花似錦蔬螟、人聲如沸此迅。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽邮屁。三九已至,卻和暖如春菠齿,著一層夾襖步出監(jiān)牢的瞬間佑吝,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工绳匀, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留芋忿,地道東北人。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓疾棵,卻偏偏與公主長得像戈钢,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子是尔,可洞房花燭夜當晚...
    茶點故事閱讀 44,592評論 2 353

推薦閱讀更多精彩內(nèi)容