iOS視頻播放的基本方法

主要內(nèi)容:

  1. iOS播放器概述
  2. MPMoviePlayerController
  3. MPMoviePlayerViewController
  4. AVPlayer
  5. AVPlayerViewController

一、iOS播放器概述

Apple我們提供了多種方法來實現(xiàn)視頻播放,包括以下:

  1. MPMoviePlayerController
  2. MPMoviePlayerViewController
  3. AVPlayer
  4. AVPlayerViewController

注意:MPMoviePlayerControllerMPMoviePlayerViewControlleriOS9.0之后被棄用;

雖說如此,這里還是將所有的用法進行總結(jié)以便對比,下面的圖示簡單概括它們之間的區(qū)別:

iOS播放視頻.png

溫馨提示:代碼更直觀,首先附上本文Demo

二、MPMoviePlayerController

1.播放視頻

MPMoviewPlayerController繼承于NSObject,使用它播放視頻需要將其自帶的視頻View添加到視圖控制器的View上才能顯示視頻。使用步驟如下:

步驟1:引用MediaPlayer框架,聲明視圖控制器屬性PlayerController

#import <mediaplayer mediaplayer.h>

@property(nonatomic,strong)MPMoviePlayerController *playerController;

步驟2:獲取視頻路徑,創(chuàng)建播放器

//本地視頻路徑
NSString* localFilePath=[[NSBundle mainBundle]pathForResource:@"不能說的秘密" ofType:@"mp4"];
NSURL *localVideoUrl = [NSURL fileURLWithPath:localFilePath];
//網(wǎng)絡視頻路徑
NSString *webVideoPath = @"http://api.junqingguanchashi.net/yunpan/bd/c.php?vid=/junqing/1115.mp4";
NSURL *webVideoUrl = [NSURL URLWithString:webVideoPath];
self.playerController =[[MPMoviePlayerController alloc]initWithContentURL:webVideoUrl];

步驟3:設置Frame將播放器View添加到視圖控制器View

self.playerController.view.frame = CGRectMake(0, 10, kDeviceWidth, 300);
[self.view addSubview: self.playerController.view];

步驟4:設置播放器屬性

//設置控制面板風格:無,嵌入,全屏,默認
self.playerController.controlStyle = MPMovieControlStyleDefault;
//設置是否自動播放(默認為YES)
self.playerController.shouldAutoplay = NO;
//設置播放器顯示模式,類似于圖片的處理,設置Fill有可能造成部分區(qū)域被裁剪
self.playerController.scalingMode = MPMovieScalingModeAspectFit;
//設置重復模式
self.playerController.repeatMode = MPMovieRepeatModeOne;

步驟5:播放視頻

//播放前的準備,會中斷當前正在活躍的音頻會話
[ self.playerController  prepareToPlay];
//播放視頻,設置了自動播放之后可以不調(diào)用此方法
//[ self.playerController  play];

步驟6:在退出界面的時候,關(guān)閉播放器,移除通知

- (void)dealloc{
    //當前視圖控制器pop之后并不會關(guān)閉播放,需要手動關(guān)閉
    [self.playerController stop];
    self.playerController = nil;
    //移除播放器相關(guān)的通知
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

2.視頻播放相關(guān)的通知

MPMoviePlayerController有關(guān)視頻播放的很多狀態(tài)控制都是通過通知完成的,尤其是播放在線視頻的時候,我們不僅監(jiān)控視頻加載是否成功,也會監(jiān)控是視頻緩存進度等。演示具體代碼如下:

步驟1:添加觀察者

//關(guān)于通知的使用(還有很多通知可以監(jiān)聽,可查看SDK)
NSNotificationCenter *notificaionCenter = [NSNotificationCenter defaultCenter];
//監(jiān)聽播放器狀態(tài)的變化
[notificaionCenter addObserver:self
                          selector:@selector(playerStateChanged:)
                              name:MPMoviePlayerPlaybackStateDidChangeNotification
                            object:nil];
//監(jiān)聽播放完成
[notificaionCenter addObserver:self
                          selector:@selector(playerFinished) name:MPMoviePlayerPlaybackDidFinishNotification
                            object:nil];
//監(jiān)聽切換到全屏
[notificaionCenter addObserver:self
                          selector:@selector(palyerChangeFullScreen) name:MPMoviePlayerDidEnterFullscreenNotification
                            object:nil];
//監(jiān)聽截屏操作完成
[notificaionCenter addObserver:self
                          selector:@selector(playerCaptureFinished:) name:MPMoviePlayerThumbnailImageRequestDidFinishNotification
                            object:nil];

步驟2:添加監(jiān)聽通知的響應方法

//播放狀態(tài)變化,注意播放完成時的狀態(tài)是暫停
- (void)playerStateChanged:(NSNotification *)notificaion{
    switch (self.playerController.playbackState) {
        case MPMoviePlaybackStateStopped:{
            NSLog(@"播放停止");
            break;
        }
        case MPMoviePlaybackStatePlaying:{
            NSLog(@"播放器正在播放");
            break;
        }
        case MPMoviePlaybackStatePaused:{
            NSLog(@"播放器暫停");
            break;
        }
        case MPMoviePlaybackStateInterrupted:{
            NSLog(@"播放器中斷");
            break;
        }
        case MPMoviePlaybackStateSeekingForward:{
            NSLog(@"播放器快進");
            break;
        }
        case MPMoviePlaybackStateSeekingBackward:{
            NSLog(@"播放器快退");
            break;
        }
        default:
            break;
    }
}

//視頻播放結(jié)束
- (void)playerFinished{
    NSLog(@"playerFinished:播放結(jié)束");
}

//播放器切換到了全屏
- (void)palyerChangeFullScreen{
    NSLog(@"palyerChangeFullScreen:播放器進入全屏");
}

//播放器截屏結(jié)束
- (void)playerCaptureFinished:(NSNotification *)notification{
    //獲取并顯示截圖
    UIImage *image=notification.userInfo[MPMoviePlayerThumbnailImageKey];
    self.captureImgView.image = image;
}

3.實現(xiàn)截屏

步驟1:添加一個按鈕,觸發(fā)截屏

_captureBtn = [[UIButton alloc] initWithFrame:CGRectMake(30, CGRectGetMaxY(self.playerController.view.frame) + 30, kDeviceWidth - 30 * 2, 50)];
_captureBtn.backgroundColor = [UIColor purpleColor];
[_captureBtn setTitle:@"截圖當前屏幕" forState: UIControlStateNormal];
[_captureBtn addTarget:self action:@selector(captureCurrentScreenImg) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:_captureBtn];
//添加一個ImgView 顯示截屏后的圖片
_captureImgView = [[UIImageView alloc] initWithFrame:CGRectMake((kDeviceWidth - 150)/2, CGRectGetMaxY(_captureBtn.frame) + 20, 150, 150)];
 _captureImgView.contentMode = UIViewContentModeScaleAspectFit;
_captureImgView.backgroundColor = [UIColor grayColor];
[self.view addSubview:_captureImgView];

步驟2:截取當前屏幕

- (void)captureCurrentScreenImg{
    [self.playerController requestThumbnailImagesAtTimes:@[@(self.playerController.currentPlaybackTime)] timeOption:MPMovieTimeOptionNearestKeyFrame];
}

步驟3:監(jiān)聽通知:播放器截屏結(jié)束,得到圖片并顯示截圖

- (void)playerCaptureFinished:(NSNotification *)notification{
    UIImage *image=notification.userInfo[MPMoviePlayerThumbnailImageKey];
    self.captureImgView.image = image;
}

三、MPMoviePlayerViewController

MPMovicePlayerViewController

  1. 只能全屏幕播放視頻,它是一個包含了MPMoviePlayerController類型屬性的特殊視圖控制器,因此通過模態(tài)視圖彈出的方式顯示視頻;
  2. 我們可以通過其MPMoviePlayerController屬性設置很多播放器的屬性,具體用法就和MPMoviePlayerController相同;

使用該播放視頻的具體代碼示例如下:

//第一步:獲取視頻路徑
//本地視頻
NSString* localFilePath=[[NSBundle mainBundle]pathForResource:@"不能說的秘密" ofType:@"mp4"];
NSURL *localVideoUrl = [NSURL fileURLWithPath:localFilePath];
//在線視頻
//NSString *webVideoPath = @"http://api.junqingguanchashi.net/yunpan/bd/c.php?vid=/junqing/1115.mp4";
//NSURL *webVideoUrl = [NSURL URLWithString:webVideoPath];

//第二步:創(chuàng)建視頻播放器
MPMoviePlayerViewController *playerViewController = [[MPMoviePlayerViewController alloc] initWithContentURL:localVideoUrl];

//第三步:設置播放器屬性
//通過moviePlayer屬性設置播放器屬性(與MPMoviePlayerController類似)
playerViewController.moviePlayer.scalingMode = MPMovieScalingModeFill;

//第四步:跳轉(zhuǎn)視頻播放界面
[self presentViewController:playerViewController animated:YES completion:nil];

四、AVPlayer

AVPlayer相比上述兩種方式,播放視頻功能更加強大,使用也十分靈活,因為它更加接近底層。但是AVPlayer本身是不能直接顯示視頻的,必須創(chuàng)建一個播放層AVPlayerLayer并將其添加到其他的視圖Layer上才能顯示。

1. 使用AVPlayer需要了解的常用類

  • AVAsset:一個用于獲取多媒體信息的抽象類,但不能直接使用;
  • AVURLAsset:AVAsset的子類,可以根據(jù)一個URL路徑創(chuàng)建一個包含媒體信息的AVURLAsset對象;
  • AVPlayerItem:一個媒體資源管理對象,用于管理視頻的基本信息和狀態(tài),一個AVPlayerItem對應一個視頻資源;
  • AVPlayer:負責視頻播放、暫停、時間控制等操作;
  • AVPlayerLayer:負責顯示視頻的圖層,如果不設置此屬性,視頻就只有聲音沒有圖像;

2. AVPlayer的使用步驟

步驟1:引用AVFoundation框架,添加播放器屬性

#import <AVFoundation/AVFoundation.h>
@property (nonatomic,strong)AVPlayer *player;//播放器對象
@property (nonatomic,strong)AVPlayerItem *currentPlayerItem;

步驟2:獲取播放地址URL

//本地視頻路徑
NSString* localFilePath=[[NSBundle mainBundle]pathForResource:@"不能說的秘密" ofType:@"mp4"];
NSURL *localVideoUrl = [NSURL fileURLWithPath:localFilePath];

//網(wǎng)絡視頻路徑
NSString *webVideoPath = @"http://api.junqingguanchashi.net/yunpan/bd/c.php?vid=/junqing/1129.mp4";
NSURL *webVideoUrl = [NSURL URLWithString:webVideoPath];

步驟3:創(chuàng)建播放器(四種方法)

//如果使用URL創(chuàng)建的方式會默認為AVPlayer創(chuàng)建一個AVPlayerItem
//self.player = [AVPlayer playerWithURL:localVideoUrl];
//self.player = [[AVPlayer alloc] initWithURL:localVideoUrl];
//self.player = [AVPlayer playerWithPlayerItem:playerItem];
AVPlayerItem *playerItem = [[AVPlayerItem alloc] initWithURL:webVideoUrl];
self.currentPlayerItem = playerItem;
self.player = [[AVPlayer alloc] initWithPlayerItem:playerItem];

步驟4:創(chuàng)建顯示視頻的AVPlayerLayer,設置視頻顯示屬性,并添加視頻圖層

//contentView是一個普通View,用于放置視頻視圖
/*
  AVLayerVideoGravityResizeAspectFill等比例鋪滿,寬或高有可能出屏幕
  AVLayerVideoGravityResizeAspect 等比例  默認
  AVLayerVideoGravityResize 完全適應寬高
*/
AVPlayerLayer *avLayer = [AVPlayerLayer playerLayerWithPlayer:self.player];
avLayer.videoGravity = AVLayerVideoGravityResizeAspect;
avLayer.frame = _containerView.bounds;
[_containerView.layer addSublayer:avLayer];

步驟5:執(zhí)行play方法,開始播放

//本地視頻可以直接播放
//網(wǎng)絡視頻需要監(jiān)測AVPlayerItem的status屬性為AVPlayerStatusReadyToPlay時方法才會生效
[self.player play];

3. 添加屬性觀察

一個AVPlayerItem對象對應著一個視頻,我們需要通過AVPlayerItem來獲取視頻屬性。

但是AVPlayerItem必須是在視頻資源加載到可以播放的時候才能使用,這是受限于網(wǎng)絡的原因。為了解決這一問題,我們需要使用KVO監(jiān)測AVPlayerItemstatus屬性,當其AVPlayerItemStatusReadyToPlay的時候我們才能獲取視頻相關(guān)屬性;相關(guān)的代碼示例如下:

步驟1:注冊觀察者,監(jiān)測播放器屬性

//觀察Status屬性,可以在加載成功之后得到視頻的長度
[self.player.currentItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
//觀察loadedTimeRanges,可以獲取緩存進度,實現(xiàn)緩沖進度條
[self.player.currentItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];

步驟2:添加屬性觀察

- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context {
    AVPlayerItem *playerItem = (AVPlayerItem *)object;
    if ([keyPath isEqualToString:@"status"]) {
        //獲取playerItem的status屬性最新的狀態(tài)
        AVPlayerStatus status = [[change objectForKey:@"new"] intValue];
        switch (status) {
            case AVPlayerStatusReadyToPlay:{
                //獲取視頻長度
                CMTime duration = playerItem.duration; 
                //更新顯示:視頻總時長(自定義方法顯示時間的格式)
                self.totalNeedPlayTimeLabel.text = [self formatTimeWithTimeInterVal:CMTimeGetSeconds(duration)];
                //開啟滑塊的滑動功能
                self.sliderView.enabled = YES;
                //關(guān)閉加載Loading提示
                [self showaAtivityInDicatorView:NO];
                //開始播放視頻
                [self.player play];
                break;
            }
            case AVPlayerStatusFailed:{//視頻加載失敗,點擊重新加載
                [self showaAtivityInDicatorView:NO];//關(guān)閉Loading視圖
                self.playerInfoButton.hidden = NO; //顯示錯誤提示按鈕,點擊后重新加載視頻
                [self.playerInfoButton setTitle:@"資源加載失敗,點擊繼續(xù)嘗試加載" forState: UIControlStateNormal];
                break;
            }
            case AVPlayerStatusUnknown:{
                NSLog(@"加載遇到未知問題:AVPlayerStatusUnknown");
                break;
            }
            default:
                break;
        }
    } else if ([keyPath isEqualToString:@"loadedTimeRanges"]) {
        //獲取視頻緩沖進度數(shù)組,這些緩沖的數(shù)組可能不是連續(xù)的
        NSArray *loadedTimeRanges = playerItem.loadedTimeRanges;
        //獲取最新的緩沖區(qū)間
        CMTimeRange timeRange = [loadedTimeRanges.firstObject CMTimeRangeValue];
        //緩沖區(qū)間的開始的時間
        NSTimeInterval loadStartSeconds = CMTimeGetSeconds(timeRange.start);
        //緩沖區(qū)間的時長
        NSTimeInterval loadDurationSeconds = CMTimeGetSeconds(timeRange.duration);
        //當前視頻緩沖時間總長度
        NSTimeInterval currentLoadTotalTime = loadStartSeconds + loadDurationSeconds;
        //NSLog(@"開始緩沖:%f,緩沖時長:%f,總時間:%f", loadStartSeconds, loadDurationSeconds, currentLoadTotalTime);
        //更新顯示:當前緩沖總時長
        _currentLoadTimeLabel.text = [self formatTimeWithTimeInterVal:currentLoadTotalTime];
        //更新顯示:視頻的總時長
        _totalNeedLoadTimeLabel.text = [self formatTimeWithTimeInterVal:CMTimeGetSeconds(self.player.currentItem.duration)];
        //更新顯示:緩沖進度條的值
        _progressView.progress = currentLoadTotalTime/CMTimeGetSeconds(self.player.currentItem.duration);
    }
}

轉(zhuǎn)換時間格式的方法:

- (NSString *)formatTimeWithTimeInterVal:(NSTimeInterval)timeInterVal{
    int minute = 0, hour = 0, secend = timeInterVal;
    minute = (secend % 3600)/60;
    hour = secend / 3600;
    secend = secend % 60;
    return [NSString stringWithFormat:@"%02d:%02d:%02d", hour, minute, secend];
}

4. 獲取當前播放時間與總時間

在此之前我們需要首先了解一個數(shù)據(jù)類型,也就是上述操作中的CMTime

AVPlayer的使用中我們會經(jīng)常用到CMTime,其實它是一個如下的結(jié)構(gòu)體:

typedef struct{
    CMTimeValue    value;      // 幀數(shù)
    CMTimeScale    timescale;  // 幀率(影片每秒有幾幀)
    CMTimeFlags    flags;
    CMTimeEpoch    epoch;
} CMTi

在上面的操作中我們看到AVPlayerItemDuration屬性就是一個CMTime類型的數(shù)據(jù)。所以獲取視頻的總時長(秒)需要duration.value/duration.timeScale。當然系統(tǒng)也為我們提供了CMTimeGetSeconds函數(shù)更加方便計算:

總時長: duration.value == CMTimeGetSeconds(duration) 。

在快進視頻到某一個位置的時候我們也需要創(chuàng)建CMTime作為參數(shù),那么CMTime的創(chuàng)建方法有兩種:

//方法1:
CMTimeMakeWithSeconds(Flout64 seconds, int32_t scale)

//方法2:
CMTimeMake(int64_t value, int32_t scale)
//注:兩者的區(qū)別在于方法一的第一個參數(shù)可以是float

至于獲取視頻的總時間在上述代碼中已有體現(xiàn),是在檢測播放狀態(tài)變?yōu)?code>AVPlayerStatusReadyToPlay的時候獲取的

//視頻總時長,在AVPlayerItem狀態(tài)為AVPlayerStatusReadyToPlay時獲取
CMTime duration = self.player.currentItem.duration;
CGFloat totalTime = CMTimeGetSeconds(duration);

//當前AVPlayer的播放時長
CMTime cmTime = self.player.currentTime;
CGFloat currentTime  = CMTimeGetSeconds(cmTime);

5. 播放進度與狀態(tài)的刷新

實時更新當前播放時間,這時候我們不必使用定時器,因為AVPlayer已經(jīng)提供了方法:

addPeriodicTimeObserverForInterval: queue: usingBlock`

當播放進度改變的時候方法中的回調(diào)會被執(zhí)行,我們可以在這里做刷新時間的操作,代碼示例如下:

__weak __typeof(self) weakSelf = self;
self.timeObserver = [self.player addPeriodicTimeObserverForInterval:CMTimeMake(1, 1) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
     //當前播放的時間
     NSTimeInterval currentTime = CMTimeGetSeconds(time);
     //視頻的總時間
     NSTimeInterval totalTime = CMTimeGetSeconds(weakSelf.player.currentItem.duration);
     //設置滑塊的當前進度
     weakSelf.sliderView.value = currentTime/totalTime;
     //設置顯示的時間:以00:00:00的格式
     weakSelf.currentTimeLabel.text = [weakSelf formatTimeWithTimeInterVal:currentTime];
    }];

//移除時,調(diào)用removeTimeObserver
// [self.player removeTimeObserver:self.timeObserver];

注意:使用addPeriodicTimeObserverForInterval必須持有返回對象,且在不需要播放器的時候移除此對象; 否則將會導致undefined behavior,這一點可以從文檔是這樣說明的:

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.
Releasing the observer object without a call to -removeTimeObserver: will result in undefined behavior

6. 滑塊拖拽修改視頻播放進度

//UISlider的響應方法:拖動滑塊,改變播放進度
- (IBAction)sliderViewChange:(id)sender {
    if(self.player.status == AVPlayerStatusReadyToPlay){
        NSTimeInterval playTime = self.sliderView.value * CMTimeGetSeconds(self.player.currentItem.duration);
        CMTime seekTime = CMTimeMake(playTime, 1);
        [self.player seekToTime:seekTime completionHandler:^(BOOL finished) {
        }];
    }
}

五、AVPlayerViewController

AVPlayerViewController

  1. 這是iOS8新增視頻框架AVKit中的播放器類,適合開發(fā)播放界面要求不是很高的應用;
  2. 相比AVPlayer的使用更加方便,而原理上其實還是其中包含了一個AVPlayer對象;
  3. iOS9棄用前兩種播放器類后,AVPlayerViewController更加推薦常用;

AVPlayerViewController有以下兩種播放視頻的方式:

1. 直接彈出模態(tài)視圖控制器播放

//步驟1:獲取視頻路徑
NSString *webVideoPath = @"http://api.junqingguanchashi.net/yunpan/bd/c.php?vid=/junqing/1213.mp4";
NSURL *webVideoUrl = [NSURL URLWithString:webVideoPath];

//步驟2:創(chuàng)建AVPlayer
AVPlayer *avPlayer = [[AVPlayer alloc] initWithURL:webVideoUrl];

//步驟3:使用AVPlayer創(chuàng)建AVPlayerViewController,并跳轉(zhuǎn)播放界面
AVPlayerViewController *avPlayerVC =[[AVPlayerViewController alloc] init];
avPlayerVC.player = avPlayer;
[self presentViewController:avPlayerVC animated:YES completion:nil];

2.添加AVPlayerViewController的View到父視圖上播放

使用這種方式播放的優(yōu)點在于:可以指定播放界面的原始尺寸大小;

但是需要注意: AVPlayerViewController必須被當前視圖控制器所持有,以防止被當做局部變量被釋放。為了滿足這一條件,有兩種方式:

  1. 可以將AVPlayerViewController作為屬性;
  2. 也可以使用addChildViewController方法將其作為當前視圖控制器的子視圖控制器;

相關(guān)的示例代碼如下:

//步驟1:獲取視頻路徑
NSString *webVideoPath = @"http://api.junqingguanchashi.net/yunpan/bd/c.php?vid=/junqing/1213.mp4";
NSURL *webVideoUrl = [NSURL URLWithString:webVideoPath];

//步驟2:創(chuàng)建AVPlayer
AVPlayer *avPlayer = [[AVPlayer alloc] initWithURL:webVideoUrl];

//步驟3:使用AVPlayer創(chuàng)建AVPlayerViewController,并跳轉(zhuǎn)播放界面
AVPlayerViewController *avPlayerVC =[[AVPlayerViewController alloc] init];
avPlayerVC.player = avPlayer;

//步驟4:設置播放器視圖大小
avPlayerVC.view.frame = CGRectMake(25, 0, 320, 300);
//特別注意:AVPlayerViewController不能作為局部變量被釋放,否則無法播放成功
//解決1.AVPlayerViewController作為屬性
//解決2:使用addChildViewController,AVPlayerViewController作為子視圖控制器
[self addChildViewController:avPlayerVC];
[self.view addSubview:avPlayerVC.view];

最后總結(jié):

以上就是iOS視頻播放的基本方法,但這里也僅限一些基礎(chǔ)的播放需求。若要實現(xiàn)更為復雜的播放功能,仍然有很多東西需要我們繼續(xù)深入研究,加油!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者。

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