你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發基礎 >> 基於 AVPlayer 自定義播放器

基於 AVPlayer 自定義播放器

編輯:IOS開發基礎

如果我只是簡單的播放一個視頻,而不需要考慮播放器的界面。iOS9.0 之前使用 MPMoviePlayerController, 或者內部自帶一個 view 的 MPMoviePlayerViewController.  iOS9.0 之後,可以使用 AVPictureInPictureController, AVPlayerViewController, 或者 WKWebView

以上系統提供的播放器由於高度的封裝性, 使得自定義播放器變的很難。 所以,如果我需要自定義播放器樣式的時候,可以使用 AVPlayer。 AVPlayer 存在於 AVFoundtion 中,更接近於底層,也更加靈活。

326377-7603681aef1c08f9.jpg

Representing and Using Media with AVFoundation

AVFoundtion 框架中主要使用 AVAsset 類來展示媒體信息,比如: title, duration, size 等等。

  • AVAsset : 存儲媒體信息的一個抽象類,不能直接使用。

  • AVURLAsset : AVAsset 的一個子類,使用 URL 進行實例化,實例化對象包換 URL 對應視頻資源的所有信息。

  • AVPlayerItem :  有一個屬性為 asset。起到觀察和管理視頻信息的作用。 比如,asset, tracks , status, duration ,loadedTimeRange 等。

我的理解是, AVPlayItem 相當於 Model 層,包含 Media 的信息和播放狀態,並提供這些數據給視頻觀察者 比如:屬性 asset ,URL視頻的信息. loadedTimeRanges ,已緩沖進度。

AVPlayerItem 使用

1. 初始化

playerItemWithURL 或者 initWithURL:

在使用 AVPlayer 播放視頻時,提供視頻信息的是 AVPlayerItem,一個 AVPlayerItem 對應著一個URL視頻資源。

初始化一個 AVPlayItem 對象後,其屬性並不是馬上就可以使用。我們必須確保 AVPlayerItem 已經被加載好了,可以播放了,才能使用。 畢竟凡是和網絡扯上關系的都需要時間去加載。 那麼,什麼時候屬性才能正常使用呢。 官方文檔給出了解決方案:

  1. 直到 AVPlayerItem 的 status 屬性為 AVPlayerItemStatusReadyToPlay.

  2. 使用 KVO 鍵值觀察者,其屬性。

因此我們在使用的時候,使用 URL 初始化 AVPlayerItem 後,還要給它添加觀察者。

2. 添加觀察者

AVPlayreItem 的屬性需要當 status 為 ReadyToPlay 的時候才可以正常使用。

觀察status屬性

[_playerItem addObserver:self forKeyPath:@"status" options:(NSKeyValueObservingOptionNew) context:nil]; // 觀察status屬性,

觀察loadedTimeRanges

如果想做緩沖進度條,顯示當前視頻的緩存進度,則需要觀察 loadedTimeRanges.

[_playerItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil]; // 觀察緩沖進度

AVPlayer & AVPlayerLayer

AVPlayer創建方式
AVPlayer 有三種創建方式:

init,initWithURL:,initWithPlayerItem: (URL,Item遍歷構造器方法)

使用 AVPlayer 時需要注意,AVPlayer 本身並不能顯示視頻, 顯示視頻的是 AVPlayerLayer。 AVPlayerLayer 繼承自 CALayer,添加到 view.layer 上就可以使用了。

AVPlayerLayer創建方式

AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:player];
[superlayer addSublayer:playerLayer];

AVPlayerLayer 顯示視頻,AVPlayerItem 提供視頻信息, AVPlayer 管理和調控。 這是不是非常熟悉。 我覺得這裡也體現了 MVC 的思想(雖然AVPlayer繼承自NSObject), 把響應層, 顯示層, 信息層, 三層分離了。 明確了每層做的任務,使用起來就會更加得心應手。

使用 AVPlayer 的核心,在於 AVPlayer 和 AVPlayerItem, AVPlayerLayer 添加到視圖的layer 上後,就沒有什麼事兒了。 思考一下,整個播放視頻的步驟。

  1. 首先,得到視頻的URL

  2. 根據URL創建AVPlayerItem

  3. 把AVPlayerItem 提供給 AVPlayer

  4. AVPlayerLayer 顯示視頻。

  5. AVPlayer 控制視頻, 播放, 暫停, 跳轉 等等。

  6. 播放過程中獲取緩沖進度,獲取播放進度。

  7. 視頻播放完成後做些什麼,是暫停還是循環播放,還是獲取最後一幀圖像。

播放步驟

1. 布局頁面,初始化 AVPlayer 和 AVPlayerLayer

 // setAVPlayer
 self.player = [[AVPlayer alloc] init];
 _playerLayer = [AVPlayerLayer playerLayerWithPlayer:_player];
 [self.playerView.layer addSublayer:_playerLayer];

2. 根據 URL 獲取 AVPayerItem,並替換 AVPlayer 的 AVPlayerItem

在第一步,布局初始化時,AVPlayer 並沒有 AVPlayerItem,AVPlayer 提供了 - (void)replaceCurrentItemWithPlayerItem:(nullable AVPlayerItem *)item;  方法,用於切換視頻。

- (void)updatePlayerWithURL:(NSURL *)url {
    _playerItem = [AVPlayerItem playerItemWithURL:url]; // create item
    [_player  replaceCurrentItemWithPlayerItem:_playerItem]; // replaceCurrentItem
    [self addObserverAndNotification]; // 注冊觀察者,通知
}

3. KVO 獲取視頻信息, 觀察緩沖進度

觀察 AVPlayerItem 的 status 屬性,當狀態變為 AVPlayerStatusReadyToPlay 時才可以使用。

也可以觀察 loadedTimeRanges 獲取緩沖進度

注冊觀察者:

 [_playerItem addObserver:self forKeyPath:@"status" options:(NSKeyValueObservingOptionNew) context:nil]; // 觀察status屬性

執行觀察者方法:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    AVPlayerItem *item = (AVPlayerItem *)object;
    if ([keyPath isEqualToString:@"status"]) {
            AVPlayerStatus status = [[change objectForKey:@"new"] intValue]; // 獲取更改後的狀態
            if (status == AVPlayerStatusReadyToPlay) {
                CMTime duration = item.duration; // 獲取視頻長度
                // 設置視頻時間
                [self setMaxDuration:CMTimeGetSeconds(duration)];
                // 播放
                [self play];
            } else if (status == AVPlayerStatusFailed) {
                NSLog(@"AVPlayerStatusFailed");
            } else {
                NSLog(@"AVPlayerStatusUnknown");
            }

    } else if ([keyPath isEqualToString:@"loadedTimeRanges"]) {
        NSTimeInterval timeInterval = [self availableDurationRanges]; // 緩沖時間
        CGFloat totalDuration = CMTimeGetSeconds(_playerItem.duration); // 總時間
        [self.loadedProgress setProgress:timeInterval / totalDuration animated:YES]; // 更新緩沖條
    }
}

4. 播放過程中響應:播放、 暫停、 跳轉

AVPlayer 提供了 play , pause, 和 - (void)seekToTime:(CMTime)time completionHandler:(void (^)(BOOL finished))completionHandler 方法。

在看 AVPlayer 的 seekToTime 之前,先來認識一個結構體。

CMTime 是專門用於標識電影時間的結構體.

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

AVPlayerItem 的 duration 屬性就是一個 CMTime 類型的數據。 如果我們想要獲取影片的總秒數那麼就可以用 duration.value / duration.timeScale 計算出來。也可以使用 CMTimeGetSeconds 函數

CMTimeGetSeconds(CMtime time)
double seconds = CMTimeGetSeconds(item.duration);  // 相當於 duration.value / duration.timeScale

如果一個影片為60frame(幀)每秒, 當前想要跳轉到 120幀的位置,也就是兩秒的位置,那麼就可以創建一個 CMTime 類型數據。

CMTime,通常用如下兩個函數來創建.

CMTimeMake(int64_t value, int32_t scale)
CMTime time1 = CMTimeMake(120, 60);

CMTimeMakeWithSeconds(Flout64 seconds, int32_t scale)
CMTime time2 = CMTimeWithSeconds(120, 60);

CMTimeMakeWithSeconds 和CMTimeMake 區別在於,第一個函數的第一個參數可以是float,其他一樣。

拖拽方法如下:

- (IBAction)playerSliderValueChanged:(id)sender {
    _isSliding = YES;
    [self pause];    // 跳轉到拖拽秒處
    // self.playProgress.maxValue = value / timeScale
    // value = progress.value * timeScale
    // CMTimemake(value, timeScale) =  (progress.value, 1.0)
    CMTime changedTime = CMTimeMakeWithSeconds(self.playProgress.value, 1.0);
    [_playerItem seekToTime:changedTime completionHandler:^(BOOL finished) {        
    // 跳轉完成後
    }];
}

5. 觀察 AVPlayer 播放進度

AVPlayerItem 是使用 KVO 模式觀察狀態,和 緩沖進度。而 AVPlayer 給我們直接提供了 觀察播放進度更為方便的方法。

- (id)addPeriodicTimeObserverForInterval:(CMTime)interval queue:(nullable dispatch_queue_t)queue usingBlock:(void (^)(CMTime time))block;

方法名如其意, “添加周期時間觀察者” ,參數1 interal 為CMTime 類型的,參數2 為一個 返回值為空,參數為 CMTime 的block類型。

簡而言之就是,每隔一段時間後執行 block。

比如: 我把時間間隔設置為, 1/ 30 秒,然後 block 裡面更新 UI。就是一秒鐘更新 30次UI。

播放進度代碼如下:

// 觀察播放進度
- (void)monitoringPlayback:(AVPlayerItem *)item {
    __weak typeof(self)WeakSelf = self;

    // 觀察間隔, CMTime 為30分之一秒
    _playTimeObserver = [_player addPeriodicTimeObserverForInterval:CMTimeMake(1, 30.0) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
        if (_touchMode != TouchPlayerViewModeHorizontal) {
            // 獲取 item 當前播放秒
            float currentPlayTime = (double)item.currentTime.value/ item.currentTime.timescale;
            // 更新slider, 如果正在滑動則不更新
            if (_isSliding == NO) {
                [WeakSelf updateVideoSlider:currentPlayTime];
            }
        } else {
            return;
        }
    }];
}

注意: 給 palyer 添加了 timeObserver 後,不使用的時候記得移除 removeTimeObserver 否則會占用大量內存。

比如,我在dealloc裡面做了移除:

- (void)dealloc {
    [self removeObserveAndNOtification];
    [_player removeTimeObserver:_playTimeObserver]; // 移除playTimeObserver}

6. AVPlayerItem 通知

AVPlaerItem 播放完成後,系統會自動發送通知,通知的定義詳情請見 AVPlayerItem.h.

/* Note that NSNotifications posted by AVPlayerItem may be posted on a different thread from the one on which the observer was registered. */

// notifications                                                                                description
AVF_EXPORT NSString *const AVPlayerItemTimeJumpedNotification             NS_AVAILABLE(10_7, 5_0);    // the item's current time has changed discontinuously
AVF_EXPORT NSString *const AVPlayerItemDidPlayToEndTimeNotification      NS_AVAILABLE(10_7, 4_0);   // item has played to its end time
AVF_EXPORT NSString *const AVPlayerItemFailedToPlayToEndTimeNotification NS_AVAILABLE(10_7, 4_3);   // item has failed to play to its end time
AVF_EXPORT NSString *const AVPlayerItemPlaybackStalledNotification       NS_AVAILABLE(10_9, 6_0);    // media did not arrive in time to continue playback
AVF_EXPORT NSString *const AVPlayerItemNewAccessLogEntryNotification     NS_AVAILABLE(10_9, 6_0);    // a new access log entry has been added
AVF_EXPORT NSString *const AVPlayerItemNewErrorLogEntryNotification         NS_AVAILABLE(10_9, 6_0);    // a new error log entry has been added

// notification userInfo key                                                                    type
AVF_EXPORT NSString *const AVPlayerItemFailedToPlayToEndTimeErrorKey     NS_AVAILABLE(10_7, 4_3);   // NSError

因此,如果我們想要在某個狀態下,執行某些操作。監聽 AVPlayerItem 的相關通知就行了。 比如,我想要播放完成後,暫停播放。 給AVPlayerItemDidPlayToEndTimeNotification 添加觀察者。

 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playbackFinished:) name:AVPlayerItemDidPlayToEndTimeNotification object:nil];

 // 播放完成後
 - (void)playbackFinished:(NSNotification *)notification {
    NSLog(@"視頻播放完成通知");
    _playerItem = [notification object];
    [_playerItem seekToTime:kCMTimeZero]; // item 跳轉到初始
      //[_player play]; // 循環播放
}

最後

使用 AVPlayer 的時候,一定要注意 AVPlayer 、 AVPlayerLayer 和 AVPlayerItem 三者之間的關系。 AVPlayer 負責控制播放, layer 顯示播放, item 提供數據,當前播放時間, 已加載情況。 Item 中一些基本的屬性, status, duration, loadedTimeRanges, currentTime(當前播放時間)。

最重要的還是多總結,6月份寫的這個 Demo ,現在才總結,懶癌到晚期 =。 =

當然,如果我寫的文章有幸讓你看到了最後,那麼, 或許 你想要更多的功能。比如,橫豎屏旋轉,一些交互動畫等等。 我有個簡單地 demo,實現了一些小小的功能,放到了 github 上, 裡面還有很多不足,多溝通交流 = W = 。

326377-cfe8b955c6a31ed1.gif

githubDemo地址



文章轉自 Shumin_Wu的簡書
  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved