流媒體開發(三)視頻播放

簡介

和音頻播放一樣,iOS內置了視頻播放器,提供個很多的API實現視頻播放。如mediaPlayer.framework下的MPMovieplayerController、AVFounditon.framework下的AVPlayer和AVKit下的AVPlayerViewcontroller。但是MPMovieplayerController已經在ios9.0中被廢棄了,用來替代的是AVPlayerViewcontroller,我們在文中的最後在對它們的使用做一些描述。同時前文中提到的AVPlayer同樣可以實現視頻播放功能,而且實現功能更加強大,但是實現起來相對比較麻煩。

1. AVPlayer

在iOS開發上,項目往往對視頻播放的要求較高,尤其是過場動畫和用戶互動效果上的表現需要更加細緻的操作,比如有些時候需要自定義播放器的樣式,要對視頻有自由的控制等。所以在iOS 4之後,我們可以使用 AVPlayer來對視頻播放做更加細緻的操作。AVPlayer存在於AVFoundation中,它更加接近於底層,所以靈活性也更強:
這裏寫圖片描述

AVPlayer本身並不能顯示視頻,如果AVPlayer要顯示必須創建一個播放器層AVPlayerLayer用於展示,播放器層繼承於CALayer,有了AVPlayerLayer之添加到控制器視圖的layer中即可。要使用AVPlayer首先了解一下幾個常用的類:

  • AVAsset:是一個抽象類,不能直接使用,主要用於獲取多媒體信息。
  • AVURLAsset:AVAsset的子類,可以根據一個URL路徑創建一個包含媒體信息的AVURLAsset對象。
  • AVPlayerItem:一個媒體資源管理對象,管理者視頻的一些基本信息和狀態,一個AVPlayerItem對應着一個視頻資源。

下面簡單通過一個播放器來演示AVPlayer的使用, 在這個自定義的播放器中實現了視頻播放、暫停、進度展示和視頻列表功能,下面將對這些功能一一介紹。

  • 視頻的播放、暫停功能:這也是視頻播放最基本的功能,AVPlayer對應着兩個方法play、pause來實現。但是關鍵問題是如何判斷當前視頻是否在播放,在前面的內容中無論是音頻播放器還是視頻播放器都有對應的狀態來判斷,但是AVPlayer卻沒有這樣的狀態屬性,通常情況下可以通過判斷播放器的播放速度來獲得播放狀態。如果rate爲0說明是停止狀態,1是則是正常播放狀態。
  • 展示播放進度:無論是AVPlayer還是AVPlayerItem (AVPlayer有一個屬性currentItem是AVPlayerItem類型,表示當前播放的視頻對象)都無法獲得這些信息。當然AVPlayerItem是有通知的,但是對於獲得播放狀態和加載狀態有用的通知只有一個:播放完成通知AVPlayerItemDidPlayToEndTimeNotification。在播放視頻時,特別是播放網絡視頻往往需要知道視頻加載情況、緩衝情況、播放情況,這些信息可以通過KVO監控AVPlayerItem 的status、loadedTimeRanges屬性來獲得。當AVPlayerItem的status屬性爲AVPlayerStatusReadyToPlay 是說明正在播放,只有處於這個狀態時才能獲得視頻時長等信息;當loadedTimeRanges的改變時(每緩衝一部分數據就會更新此屬性)可以獲得本次緩衝加載的視頻範圍(包含起始時間、本次加載時長),這樣一來就可以實時獲得緩衝情況。然後就是依靠AVPlayer的週期性回調的方法獲得播放進度,這個方法會在設定的時間間隔內定時更新播放進度,通過time參數通知客戶端。相信有了這些視頻信息播放進度就不成問題了,事實上通過這些信息就算是平時看到的其他播放器的緩衝進度顯示以及拖動播放的功能也可以順利的實現。
  • 最後就是視頻切換的功能,在前面介紹的所有播放器中每個播放器對象一次只能播放一個視頻,如果要切換視頻只能重新創建一個對象,但是AVPlayer卻提供了- (void)replaceCurrentItemWithPlayerItem:(AVPlayerItem *)item方法用於在不同的視頻之間切換,但是該方法會導致原先持有的視頻對像彈出一個錯誤,所以該操作在iOS4之後成爲一個無效操作(事實上在AVFoundation內部還有一個AVQueuePlayer專門處理播放列表切換,有興趣的朋友可以自行研究,這裏不再贅述)。

示例代碼

#import "ViewController.h"
#import <AVFoundation/AVFoundation.h>

@interface ViewController ()
{
    NSArray *_playList;
    AVPlayer *_avPlayer;// 視頻播放器
}
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    NSLog(@"%@",[NSHomeDirectory() stringByAppendingString:@"/Documents"]);

    {// UI設置
        self.volume.value = _avPlayer.volume;
        self.progress.value = 0;
        self.progressView.progress = 0;
        [self.play setTitle:@"pause" forState:UIControlStateNormal];
    }

#pragma mark - 視頻播放
    // 1.加載網絡視頻網址
    NSString *str1 = @"http://vfx.mtime.cn/Video/2015/07/04/mp4/150704102229172451_480.mp4";
    // 1.獲取本地視頻文件路徑
    NSString *str2 = [[NSBundle mainBundle] pathForResource:@"海妖寶藏" ofType:@"mp4"];
    NSURL *url1 = [NSURL URLWithString:str1];
    NSURL *url2 = [NSURL fileURLWithPath:str2];
    _playList = @[url1,url2];

    // 2.初始化AVPlayer視頻播放器
    AVPlayerItem *item = [[AVPlayerItem alloc] initWithURL:_playList[0]];
    _avPlayer = [[AVPlayer alloc] initWithPlayerItem:item];

    // 3.初始化播放圖層
    AVPlayerLayer *layer = [AVPlayerLayer playerLayerWithPlayer:_avPlayer];//AVPlayer播放視頻需要生成一個Layer圖層完成播放
    //寬高比例設置爲 9:16
    layer.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.width *9/16.f);
    layer.videoGravity = AVLayerVideoGravityResizeAspect;//視頻的填充模式
    //添加播放圖層到界面
    [self.view.layer addSublayer:layer];

    {// 常用屬性設置

        _avPlayer.rate = 1;// 播放速度
        _avPlayer.volume = 0.8f;// 播放音量
        _avPlayer.muted = NO;// 靜音

        // 播放完成的操作
        _avPlayer.actionAtItemEnd = AVPlayerActionAtItemEndPause;
        /* AVPlayerActionAtItemEnd
         AVPlayerActionAtItemEndAdvance = 0,// 不要使用
         AVPlayerActionAtItemEndPause = 1,
         AVPlayerActionAtItemEndNone    = 2,
         */
    }
    {// 常用方法
        // 指定播放時間
        [_avPlayer seekToTime:CMTimeMakeWithSeconds(20, 24)];// 從指定時間的前一秒開始播放
    }

    // 3.播放音頻
    [_avPlayer play];

#pragma mark - 註冊通知及KVO
    // 註冊通知 AVPlayerItemDidPlayToEndTimeNotification:音頻播放完成
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playDidEnd) name:AVPlayerItemDidPlayToEndTimeNotification object:nil];

    //監控狀態屬性,注意AVPlayer也有一個status屬性,通過監控它的status也可以獲得播放狀態
    [_avPlayer.currentItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
    // 監控網絡加載情況屬性
    [_avPlayer.currentItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];

#pragma mark - 播放進度週期回調
    // 實時監聽視頻文件的播放進度
    __block ViewController *blockSelf = self;
    __block AVPlayer *blockPlayer = _avPlayer;
    [_avPlayer addPeriodicTimeObserverForInterval:CMTimeMake(1, 1) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {

        // 週期性回調該block的代碼塊

        /* CMTime : value/timescale = seconds.
         time指的就是時間(不是秒),
         而時間要換算成秒就要看第二個參數timeScale了.
         timeScale指的是1秒需要由幾個frame構成(可以視為fps,幀數),
         因此真正要表達的時間就會是 time / timeScale 才會是秒.
         */

        // 統計時長
        CMTime duration = blockPlayer.currentItem.duration;
        CGFloat durationSec = CMTimeGetSeconds(duration);
        NSLog(@"總時長:%f",durationSec);
        // 統計當前播放時長
        CMTime current = blockPlayer.currentItem.currentTime;
        CGFloat currentSec = CMTimeGetSeconds(current);
        NSLog(@"當前時間:%f",currentSec);

        // 刷新播放進度
        [blockSelf.progress setValue:currentSec/durationSec animated:YES];

        //計算緩存時間
        NSTimeInterval timeInterval = [blockSelf availabelDuration];
        NSLog(@"time interval :%f",timeInterval);
        // 刷新下載進度
        [blockSelf.progressView setProgress:timeInterval/durationSec animated:YES];

    }];

    // 限定時間監聽,可以設置具體的回調時間
//    NSValue *value = [NSValue valueWithCMTime:CMTimeMake(10, 2)];
    NSValue *value = [NSValue valueWithCMTime:CMTimeMakeWithSeconds(5, 24)];
    [_avPlayer addBoundaryTimeObserverForTimes:@[value] queue:dispatch_get_main_queue() usingBlock:^{
        NSLog(@"第0秒。。。。。");
    }];

}

#pragma mark - UIActions
- (IBAction)volume:(UISlider *)sender {

    _avPlayer.volume = sender.value;
}

- (IBAction)progress:(UISlider *)sender {

    // 總時長
    float duration = _avPlayer.currentItem.duration.value/_avPlayer.currentItem.duration.timescale;
    float currentTime = duration * sender.value;
    [_avPlayer seekToTime:CMTimeMake(currentTime, 1)];

}

static bool playing = YES;
- (IBAction)playOrPause:(UIButton *)sender {

    if (playing) {
        [_avPlayer pause]; //暫停播放
        playing = NO;
        [sender setTitle:@"play" forState:UIControlStateNormal];
    }else {

        [_avPlayer play]; //暫停播放
        playing = YES;
        [sender setTitle:@"pause" forState:UIControlStateNormal];
    }
}

#pragma mark 音頻文件緩衝
- (NSTimeInterval)availabelDuration {

    NSArray *loadedTimeRanges = [[_avPlayer currentItem] loadedTimeRanges];
    CMTimeRange timeRange = [loadedTimeRanges.firstObject CMTimeRangeValue];// 獲取緩存區域
    /* typedef struct
      {
         CMTime start;
         CMTime duration;
      } CMTimeRange;
     */
    float startSeconds = CMTimeGetSeconds(timeRange.start);
    float durationSeconds = CMTimeGetSeconds(timeRange.duration);

    // 計算緩存事件
    NSTimeInterval result = startSeconds + durationSeconds;

    return result;
}

#pragma mark - 通知及KVO
// 播放結束後執行該方法
- (void)playDidEnd {

    // 播放結束是,播放進度爲0
    NSLog(@"play finished  %f",_avPlayer.rate);

    // 播放下一首
    AVPlayerItem *currentItem = [[AVPlayerItem alloc] initWithURL:_playList[1]];
    [_avPlayer replaceCurrentItemWithPlayerItem:currentItem];
    [_avPlayer play];

}

/**
 *  通過KVO監控播放器狀態
 *
 *  @param keyPath 監控屬性
 *  @param object  監視器
 *  @param change  狀態改變
 *  @param context 上下文
 */
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
    AVPlayerItem *playerItem=object;
    if ([keyPath isEqualToString:@"status"]) {
        AVPlayerStatus status= [[change objectForKey:@"new"] intValue];
        if(status==AVPlayerStatusReadyToPlay){
            NSLog(@"正在播放...,視頻總長度:%.2f",CMTimeGetSeconds(playerItem.duration));
        }
    }else if([keyPath isEqualToString:@"loadedTimeRanges"]){
        NSArray *array=playerItem.loadedTimeRanges;
        CMTimeRange timeRange = [array.firstObject CMTimeRangeValue];//本次緩衝時間範圍
        float startSeconds = CMTimeGetSeconds(timeRange.start);
        float durationSeconds = CMTimeGetSeconds(timeRange.duration);
        NSTimeInterval totalBuffer = startSeconds + durationSeconds;//緩衝總長度
        NSLog(@"共緩衝:%.2f",totalBuffer);
        //
    }
}
@end

2. AVPlayerViewController

上面我們通過AVPlayer實現了視頻播放,發現實現起來非常的複雜,如果項目只是需要簡單的實現播放影片的功能,我們再使用AVPlayer來實現就有些多餘了。在iOS8以前,我們主要使用MeidaPlayer框架中的MPMoviePlayerController類和MPMoviePlayerViewController類來實現視頻的簡單播放。在iOS8後,iOS開發框架中引入了一個新的視頻框架AVKit,其中提供了視頻開發類AVPlayerViewController用於在應用中嵌入播放視頻的控件。在iOS8中,這兩個框架中的視頻播放功能並無太大差異,基本都可以滿足開發者的需求。同時AVKit框架可以幫助我們實現畫中畫任務,所謂畫中畫,即是用戶可以將當前播放的視頻縮小放在屏幕上同時進行其他應用程序的使用。iOS9系統後,iPad Air正式開始支持多任務與畫中畫的分屏功能,這個革命性的功能將極大的方便用戶的使用。於此同時,在iOS9中,MPMoviePlayerController與MPMoviePlayerViewController類也被完全棄用,所以開發者建議使用AVPlayerViewController,可以十分方便的實現視頻播放的功能並在一些型號的iPad上集成畫中畫的功能。
AVPlayerViewController的使用非常簡單,提供給開發者使用的屬性和方法很少,下面是AVPlayerViewController的一些屬性和方法:

屬性 說明
@property(nonatomic)BOOL showsPlaybackControls; 是否顯示視頻播放控制控件
@property(nonatomic,copy)NSString *videoGravity; 設置視頻播放界面的尺寸縮放選項 AVLayerVideoGravityResizeAspect:不進行比例縮放,以寬高中長的一邊充滿爲基準。AVLayerVideoGravityResizeAspectFill:不進行比例縮放,以寬高中短的一邊充滿爲基準。 AVLayerVideoGravityResize進行縮放充滿屏幕
@property(nonatomic,readonly,getter=isReadyForDisplay)BOOL readyForDisplay; 獲取是否已經準備好開始播放
@property(nonatomic,readonly)CGRect videoBounds; 獲取視頻播放界面的尺寸
@property(nonatomic,readonly,nullable)UIView *contentOverlayView; 視頻播放器的視圖 自定義的控件可以添加在其上
@property(nonatomic,weak,nullable) id delegate 畫中畫代理 iOS9後可用
@property (nonatomic) BOOL allowsPictureInPicturePlayback NS_AVAILABLE_IOS(9_0); 是否支持畫中畫 iOS9後可用 默認支持
代理方法 說明
-(void)playerViewControllerWillStartPictureInPicture:(AVPlayerViewController*)playerViewController 將要開始畫中畫時調用的方法
-(void)playerViewControllerDidStartPictureIn Picture:(AVPlayerViewController *)playerViewController 已經開始畫中畫時調用的方法
-(void)playerViewController:(AVPlayerView Controller )playerViewController failedToStart PictureInPictureWithError:(NSError )error 開始畫中畫失敗調用的方法
-(void)playerViewControllerWillStopPictureIn Picture:(AVPlayerViewController *) playerViewController 將要停止畫中畫時調用的方法
-(void)playerViewControllerDidStopPictureIn Picture:(AVPlayerViewController *) playerViewController 已經停止畫中畫時調用的方法
-(BOOL)playerViewControllerShould AutomaticallyDismissAtPictureInPictureStart:(AVPlayerViewController *)playerViewController 是否在開始畫中畫時自動將當前的播放界面dismiss掉 返回YES則自動dismiss 返回NO則不會自動dismiss
-(void)playerViewController:(AVPlayerViewController *)playerViewController restoreUserInterf 用戶點擊還原按鈕 從畫中畫模式還原回app內嵌模式時調用的方法

注意:

AVPlayerViewController本身只是一個視圖控制器,並不具備播放視頻的功能,它有一個player屬性用來承載視頻的播放者,事實上,使用AVPlayerViewController來實現視頻播放還是通過AVPlayer來完成視頻的播放,只不過AVPlayerViewController幫助我們完成了很多視頻操作方面的功能。下面,我們來完成一個Demo來通過AVPlayerViewController實現視頻播放。

示例代碼:

#import <AVFoundation/AVFoundation.h>
#import <AVKit/AVKit.h>

@interface ViewController ()
{
    AVPlayerViewController *_playerController;
    AVPlayer *_player;
}
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    // 1.初始化AVPlayer
    NSString *path = [[NSBundle mainBundle] pathForResource:@"捉妖記" ofType:@"mp4"];
    _player = [AVPlayer playerWithURL:[NSURL fileURLWithPath:path]];

    _playerController = [[AVPlayerViewController alloc] init];
    _playerController.player = _player;
    _playerController.delegate = self;// 設置代理

    // 在當前界面播放視頻,設置播放視圖的大小
    // 注意視圖尺寸設置全屏與小屏播放效果及操作效果都不同
    _playerController.view.frame = CGRectMake(0, 0, 414, 414);
    [self.view addSubview:_playerController.view];

    _playerController.videoGravity = AVLayerVideoGravityResizeAspect;// 設置播放視圖拉伸屬性
    /*
     AVLayerVideoGravityResize:寬高基準
     AVLayerVideoGravityResizeAspect:寬度基準,
     AVLayerVideoGravityResizeAspectFill:高度基準
     */

    _playerController.view.translatesAutoresizingMaskIntoConstraints = YES;    //AVPlayerViewController 內部可能是用約束寫的,這句可以禁用自動約束,消除報錯  注意:設置爲NO,視頻播放視圖尺寸會出縮小

    _playerController.showsPlaybackControls = YES;//設置是否顯示視頻控制檯,實現對視頻的控制
    _playerController.allowsPictureInPicturePlayback = YES;//畫中畫,iPad可用,在這裏沒有效果

    // 添加自定義控件,控件不影響原生控件的操作和顯示
    UIView *subView = [[UIView alloc] initWithFrame:CGRectMake(314, 314, 50, 100)];
    subView.backgroundColor = [UIColor redColor];
    [_playerController.contentOverlayView addSubview:subView];

    [_player play];
}

// 在iPad Air中,AVPlayerViewController作爲獨立視圖控制器完成視頻播放可以實現畫中畫的設置
- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    // 推出AVPlayerViewController完成視頻播放
    [self presentViewController:_playerController animated:YES completion:nil];
}

#pragma mark - AVPlayerViewControllerDelegate
- (void)playerViewControllerWillStartPictureInPicture:(AVPlayerViewController *)playerViewController {
    NSLog(@"1%s", __FUNCTION__);
}

- (void)playerViewControllerDidStartPictureInPicture:(AVPlayerViewController *)playerViewController {
    NSLog(@"2%s", __FUNCTION__);
}

- (void)playerViewController:(AVPlayerViewController *)playerViewController failedToStartPictureInPictureWithError:(NSError *)error {
    NSLog(@"3%s", __FUNCTION__);
}

- (void)playerViewControllerWillStopPictureInPicture:(AVPlayerViewController *)playerViewController {
    NSLog(@"4%s", __FUNCTION__);
}

- (void)playerViewControllerDidStopPictureInPicture:(AVPlayerViewController *)playerViewController {
    NSLog(@"5%s", __FUNCTION__);
}

- (BOOL)playerViewControllerShouldAutomaticallyDismissAtPictureInPictureStart:(AVPlayerViewController *)playerViewController {
    NSLog(@"6%s", __FUNCTION__);
    return true;
}

- (void)playerViewController:(AVPlayerViewController *)playerViewController restoreUserInterfaceForPictureInPictureStopWithCompletionHandler:(void (^)(BOOL))completionHandler {
    NSLog(@"7%s", __FUNCTION__);
}

@end

注意:如果開發設備爲iPad Air,我們通過模態視圖的方式推出AVPlayerViewController,來顯示播放視圖,可以實現畫中畫的播放功能。如下圖所示:
這裏寫圖片描述

這裏寫圖片描述

這裏寫圖片描述

    到目前爲止無論是AVPlayerViewController還是AVPlayer來播放視頻都相當強大,但是它也存在着一些不可迴避的問題,那就是支持的視頻編碼格式很有限:H.264、MPEG-4,擴展名(壓縮格式):.mp4、.mov、.m4v、.m2v、.3gp、.3g2等。但是無論是AVPlayerViewController還是AVPlayer它們都支持絕大多數音頻編碼,所以大家如果純粹是爲了播放視頻的話也可以考慮使用這兩個播放器。那麼如何支持更多視頻編碼格式呢?目前來說主要還是依靠第三方框架,在iOS上常用的視頻編碼、解碼框架有:VLC、ffmpeg, 具體使用方式今天就不再做詳細介紹。

3. 使用AVFoundation生成縮略圖

在實際應用中,有時候要求我們實現獲取視頻文件的截圖功能,我們可以通過使用AVFundation框架中的AVAssetImageGenerator來獲取視頻縮略圖。使用AVAssetImageGenerator獲取縮略圖大致分爲三個步驟:

  1. 創建AVURLAsset對象(此類主要用於獲取媒體信息,包括視頻、聲音等)。
  2. 根據AVURLAsset創建AVAssetImageGenerator對象。
  3. 使用AVAssetImageGenerator的copyCGImageAtTime::方法獲得指定時間點的截圖。

示例代碼:

#import "ViewController.h"
#import <AVFoundation/AVFoundation.h>

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    // 1.創建AVURLAsset對象
    NSString *path = [[NSBundle mainBundle] pathForResource:@"捉妖記" ofType:@"mp4"];
    AVURLAsset *set = [AVURLAsset assetWithURL:[NSURL fileURLWithPath:path]];

    // 2.根據AVURLAsset創建AVAssetImageGenerator對象
    AVAssetImageGenerator *generator = [AVAssetImageGenerator assetImageGeneratorWithAsset:set];

    // 3.獲得指定時間點的截圖。
    CMTime time = CMTimeMakeWithSeconds(49, 10);//CMTime是表示電影時間信息的結構體,第一個參數表示是視頻第幾秒,第二個參數表示每秒幀數.(如果要獲得某一秒的第幾幀可以使用CMTimeMake方法)
    CMTime actualTime;
    /*截圖時間
     * time:縮略圖創建時間
     * actualTime:縮略圖實際生成的時間
     */

    CGImageRef cgImage = [generator copyCGImageAtTime:time actualTime:&actualTime error:nil];
    CMTimeShow(actualTime);// 顯示截圖的實際生成時間

    UIImage *image = [UIImage imageWithCGImage:cgImage];

    // 4.保存圖片到相冊
//    UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil);

    UIImageView *imageView = [[UIImageView alloc] initWithFrame:self.view.bounds];
    imageView.image = image;
    [self.view addSubview:imageView];

}

@end

拓展:視頻播放視圖的封裝

我們來綜合上面學習到的內容,封裝一個播放視頻的視圖,來方便我們日後的使用。實現了下面的具體功能:

1. 完成視頻的基本播放功能,
2. 添加基本的UI控件,實行視頻控制
3. 通過pan手勢控制視頻的播放音量和播放亮度(這裏使用了兩種方法,推薦使用第二種,直接調整手機的音量和手機的屏幕亮度)。

示例代碼:

#import "AVPlayerView.h"
#import <AVFoundation/AVFoundation.h>
#import <MediaPlayer/MediaPlayer.h>

@implementation AVPlayerView
{
    AVPlayer *_player;// 視頻播放器

    UIView *_controlView;// 控件承載視圖
    UIView *_maskView;// 遮罩視圖
    UISlider *_progressSlider;// 進度條
}

- (id)initWithFrame:(CGRect)frame playWithURL:(NSURL *)url {

    if (self = [super initWithFrame:frame]) {

        self.backgroundColor = [UIColor blackColor];
        self.clipsToBounds = YES;

        // 通過URL初始化視頻播放器
        _player = [[AVPlayer alloc] initWithURL:url];
        // AVPlayer 需要生成一個Layer 圖層,添加到view的layer
        AVPlayerLayer *layer =[AVPlayerLayer playerLayerWithPlayer:_player];
        layer.frame = self.layer.bounds;
        [self.layer addSublayer:layer];
        [_player play];// 開始播放

        // 添加視頻控制視圖
        [self creatControlView];

        //添加操作手勢
        UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panAction:)];
        [self addGestureRecognizer:pan];

        UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(TapAction:)];
        [self addGestureRecognizer:tap];

    }

    return self;
}

- (void)creatControlView {

    // 添加半透明遮罩
    _maskView = [[UIView alloc] initWithFrame:self.bounds];
    _maskView.backgroundColor = [UIColor blackColor];
    _maskView.alpha = 0;
    [self addSubview:_maskView];

    // 控制視圖
    _controlView = [[UIView alloc] initWithFrame:CGRectMake(0,self.bounds.size.height-50 , self.bounds.size.width, 50)];
    _controlView.hidden = NO;
    _controlView.alpha = 1;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

        [UIView animateWithDuration:1 animations:^{

            _controlView.transform = CGAffineTransformMakeTranslation(0, 50);
            _controlView.alpha = 0;
        } completion:^(BOOL finished) {
            _controlView.hidden = YES;// 隱藏控制欄
        }];
    });
    [_controlView setBackgroundColor:[UIColor blackColor]];
    [self addSubview:_controlView];

    // 按鈕
    UIButton *btn = [UIButton buttonWithType:UIButtonTypeSystem];
    [btn setFrame:CGRectMake(0,0, 50, 50)];
    [btn setTitle:@"▶️" forState:UIControlStateNormal];
    [btn addTarget:self action:@selector(playOrPause:) forControlEvents:UIControlEventTouchUpInside];
    [_controlView addSubview:btn];

    // 進度條,需要跟隨音頻文件的播放進度改變value值
    _progressSlider = [[UISlider alloc] initWithFrame:CGRectMake(70, 0, self.bounds.size.width-140, 50)];
    _progressSlider.value = 0;
    // 進度條的操作方法
    [_progressSlider addTarget:self action:@selector(sliderAct:) forControlEvents:UIControlEventValueChanged];
    [_progressSlider addTarget:self action:@selector(pause) forControlEvents:UIControlEventTouchDown];// 開始控制,暫停播放
    [_progressSlider addTarget:self action:@selector(play) forControlEvents:UIControlEventTouchUpInside];// 控制結束,播放繼續
    [_controlView addSubview:_progressSlider];


    // 進度條跟着視頻播放時間 自動滑動
    __weak id weakSelf = self;
    // 週期性回調,實時監聽視頻播放進度
    [_player addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(0.2f, 24) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {

        AVPlayerView *strongSelf = weakSelf;

        //視頻第一次回調 設置slider的長度
        if (time.value/time.timescale == 0) {

            // 獲取視頻總時長
            CMTime duration = strongSelf->_player.currentItem.duration;
            long second = duration.value/duration.timescale;

            strongSelf->_progressSlider.maximumValue = second;

        }

        //更新slider的value
        long second = time.value/time.timescale;
        strongSelf->_progressSlider.value = second;

    }];
}

#pragma mark - UIActions
// 播放和暫停按鈕
-(void)playOrPause:(UIButton *)sender{

    //AVPlayer播放或者暫停 可以調用play方法,也可以直接設置速度 rate
    if (_player.rate == 0) {// rate爲0,說明當前的播放狀態爲暫停
        _player.rate = 1.0f;
    }else{
        _player.rate = 0.0f;
    }
}

-(void)play {

    _player.rate = 1;
}

-(void)pause{

    _player.rate = 0;
}

-(void)sliderAct:(UISlider *)slider{

    int second = slider.value;// slider最大值爲視屏的全部時長,所以slider得value值即代表視頻當前的播放時間
    // 跳轉到某個時間點
    [_player seekToTime:CMTimeMakeWithSeconds(second, 24)];
}

//手勢觸發 方法
-(void)TapAction:(UIPanGestureRecognizer*)ges {

    if (_controlView.hidden == YES) {

        [UIView animateWithDuration:1 animations:^{

            _controlView.hidden = NO;
            _controlView.alpha = 1;
            _controlView.transform = CGAffineTransformIdentity;

        }];
    }else {

        [UIView animateWithDuration:1 animations:^{

            _controlView.alpha = 0;
            _controlView.transform = CGAffineTransformMakeTranslation(0, 50);
        } completion:^(BOOL finished) {
            _controlView.hidden = YES;
        }];
    }

}

-(void)panAction:(UIPanGestureRecognizer*)ges {

    // 獲取手勢的觸摸點座標
    CGPoint location = [ges locationInView:self];

    // 判斷觸摸區域:右邊控制音量,左側控制亮度
    if( location.x>self.bounds.size.width/2){

        // 音量控制
        // 獲取手勢在y方向的位移距離,計算音量的控制比例
        CGPoint translation = [ges translationInView:self];
        float proportion = translation.y/self.bounds.size.height;

        /*
        // 音量爲負值時禁止操作,因爲音量取volume屬性的絕對值
        if (_player.volume - 1*proportion >= 0 && _player.volume - 1*proportion <= 1) {

            _player.volume -= 1 *proportion /10.f;// 改變播放音量
            NSLog(@"%f",_player.volume);
            // 注意:不要使用volume屬性來實現媒體播放的音量滑塊。爲此,使用MPVolumeView,在外觀和提供可定製的標準媒體播放用戶期望的行爲。這個屬性是最有用的iOS控制AVPlayer的體積相對於其他音頻輸出,由最終用戶沒有音量控制。
        }
        */

        // 真機演示
        [self changeSystemVolumeWithProportion:proportion];// 改變系統音量

    }else{

        // 亮度控制
        // 計算亮度的控制比例
        CGPoint translation = [ges translationInView:self];
        float proportion = translation.y/self.frame.size.height;

        /*
        if (_maskView.alpha + proportion/10.f >= 0 && _maskView.alpha + proportion/10.f <= 1) {
            _maskView.alpha += proportion/10.f;
            NSLog(@"%f",_maskView.alpha);
        }
         */

        // 真機演示
        [UIScreen mainScreen].brightness -= proportion/10;// 改變屏幕亮度

    }

}

// 根據傳入的控制比例值,設置系統聲音大小
-(void)changeSystemVolumeWithProportion:(float)proportion{

    // 獲取系統音量
    float systemVolume = _systemVolumeSlider.value;
    NSLog(@"%f",systemVolume);// 發現系統音量一直爲0

    // 改變系統音量
    [self.systemVolumeSlider setValue:systemVolume - proportion/10 animated:NO];
    [self.systemVolumeSlider sendActionsForControlEvents:UIControlEventTouchUpInside];
}

- (UISlider *)systemVolumeSlider {

    if (!_systemVolumeSlider) {

        // 遍歷MPVolumeView子視圖,獲取到MPVolumeSlider,
        MPVolumeView *volumeView = [[MPVolumeView alloc] init];
        for (UIView *view in volumeView.subviews) {

            if ([view isKindOfClass:NSClassFromString(@"MPVolumeSlider")]) {
                //我們只要能控制MPVolumeSlider類就行了,可是如果我們強制創建這個類是無法實現的,但是沒關係,他的baseClass是UISlider我們可以通過這種方法實現
                _systemVolumeSlider = (UISlider *)view;

            }
        }
    }

    return _systemVolumeSlider;
}

@end
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章