ZFPlayer 3.0解析


https://www.jianshu.com/p/90e55deb4d51

2018.06.05 17:57* 字數 676 閱讀 16542評論 63

詳細介紹一下ZFPlayer 3.0的用法,如果你有什么問題或者建議可聯系我。在3.0之前版本使用ZFPlayer,是不是在煩惱播放器SDK自定義、控制層自定義等問題。作者公司多個項目分別使用不同播放器SDK以及每個項目控制層都不一樣,但是為了統一管理、統一調用,我特意寫了這個播放器殼子。播放器SDK只要遵守ZFPlayerMediaPlayback協議,控制層只要遵守ZFPlayerMediaControl協議,可以實現自定義播放器和控制層。

目前支持的功能如下:

  • 普通模式的播放,類似於騰訊視頻、愛奇藝等APP;
  • 列表普通模式的播放,包括手動點擊播放、滑動到屏幕中間自動播放,wifi網絡智能播放等等;
  • 列表的亮暗模式播放,類似於微博、UC瀏覽器視頻列表等APP;
  • 列表視頻滑出屏幕后停止播放、滑出屏幕后小窗播放;
  • 優雅的全屏,支持橫屏和豎屏全屏模式;

播放器的主要類為ZFPlayerController,具體API請看下邊這張圖吧,后邊我也會詳細介紹。在之前版本收到好多開發朋友的Issues建議也好bug也好,ZFPlayer也是致力於解決這些問題和滿足各位的建議。

 
ZFPlayer.png

ZFPlayerController(播放器的主要類)

初始化方式:

/// 普通播放的初始化

+ (instancetype)playerWithPlayerManager:(id<ZFPlayerMediaPlayback>)playerManager containerView:(UIView *)containerView;

 

/// 普通播放的初始化

- (instancetype)initWithPlayerManager:(id<ZFPlayerMediaPlayback>)playerManager containerView:(UIView *)containerView;

 

/// 列表播放的初始化

+ (instancetype)playerWithScrollView:(UIScrollView *)scrollView playerManager:(id<ZFPlayerMediaPlayback>)playerManager containerViewTag:(NSInteger)containerViewTag;

 

/// 列表播放的初始化

- (instancetype)initWithScrollView:(UIScrollView *)scrollView playerManager:(id<ZFPlayerMediaPlayback>)playerManager containerViewTag:(NSInteger)containerViewTag;

 

屬性

/// 初始化時傳遞的容器視圖,用來顯示播放器view,和播放器view同等大小

@property (nonatomic, readonly) UIView *containerView;

 

/// 初始化時傳遞的播放器manager,必須遵守`ZFPlayerMediaPlayback`協議

@property (nonatomic, readonly) id<ZFPlayerMediaPlayback> currentPlayerManager;

 

/// 此屬性是設置顯示的控制層,自定義UIView遵守`ZFPlayerMediaControl`協議,實現相關協議就可以滿足自定義控制層的目的。

@property (nonatomic, strong) UIView<ZFPlayerMediaControl> *controlView;

 

ZFPlayerController (ZFPlayerTimeControl)

/// 當前播放視頻的時間,支持KVO

@property (nonatomic, readonly) NSTimeInterval currentTime;

 

/// 當前播放視頻總時間,支持KVO

@property (nonatomic, readonly) NSTimeInterval totalTime;

 

/// 當前播放視頻緩沖時間,支持KVO

@property (nonatomic, readonly) NSTimeInterval bufferTime;

 

/// 根據播放的時間和總時間,計算出當前播放的進度,取值范圍0...1

@property (nonatomic, readonly) float progress;

 

/// 根據播放緩沖時間和總時間,計算出當前緩沖的進度,取值范圍0...1

@property (nonatomic, readonly) float bufferProgress;

 

/// 調節播放進度

- (void)seekToTime:(NSTimeInterval)time completionHandler:(void (^ __nullable)(BOOL finished))completionHandler;

 

ZFPlayerController (ZFPlayerPlaybackControl)

/// 0...1.0,調節系統的聲音,要是調節播放器聲音可以使用播放器管理類設置

@property (nonatomic) float volume;

 

/// 系統靜音,要是調節播放器靜音可以使用播放器管理類設置

@property (nonatomic, getter=isMuted) BOOL muted;

 

// 0...1.0, 系統屏幕亮度

@property (nonatomic) float brightness;

 

/// 移動網絡下自動播放, default is NO.

@property (nonatomic, getter=isWWANAutoPlay) BOOL WWANAutoPlay;

 

/// 當前播放的下標,只適用於設置了`assetURLs`

@property (nonatomic) NSInteger currentPlayIndex;

 

/// 當退到后台后是否暫停播放,前提是支持后台播放器模式,default is YES.

@property (nonatomic) BOOL pauseWhenAppResignActive;

 

/// 播放完的回調

@property (nonatomic, copy, nullable) void(^playerDidToEnd)(id asset);

 

/// 播放下一個,只適用於設置了`assetURLs`

- (void)playTheNext;

 

/// 播放上一個,只適用於設置了`assetURLs`

- (void)playThePrevious;

 

/// 播放某一個,只適用於設置了`assetURLs`

- (void)playTheIndex:(NSInteger)index;

 

/// 停止播放,並且把播放器view和相關通知移除

- (void)stop;

 

/// 切換當前的PlayerManager,適用場景:播放某一個視頻時候使用特定的播放器管理類

- (void)replaceCurrentPlayerManager:(id<ZFPlayerMediaPlayback>)manager;

 

ZFPlayerController (ZFPlayerOrientationRotation)

/// 屏幕旋轉管理類

@property (nonatomic, readonly) ZFOrientationObserver *orientationObserver;

 

/// 是否是全屏狀態,當ZFFullScreenMode == ZFFullScreenModeLandscape,當currentOrientation是LandscapeLeft或者LandscapeRight,這個值是YES

/// 當ZFFullScreenMode == ZFFullScreenModePortrait,當視頻全屏后,這個值是YES

@property (nonatomic, readonly) BOOL isFullScreen;

 

/// 鎖定當前的屏幕方向,目的是禁止設備自動旋轉

@property (nonatomic, getter=isLockedScreen) BOOL lockedScreen;

 

/// 隱藏系統的狀態欄

@property (nonatomic, getter=isStatusBarHidden) BOOL statusBarHidden;

 

/// 播放器view當前方向

@property (nonatomic, readonly) UIInterfaceOrientation currentOrientation;

 

/// 當即將全屏時候會調用

@property (nonatomic, copy, nullable) void(^orientationWillChange)(ZFPlayerController *player, BOOL isFullScreen);

 

/// 當已經全屏時候會調用

@property (nonatomic, copy, nullable) void(^orientationDidChanged)(ZFPlayerController *player, BOOL isFullScreen);

 

/// 添加設備方向的監聽

- (void)addDeviceOrientationObserver;

 

/// 移除設備方向的監聽

- (void)removeDeviceOrientationObserver;

 

/// 當 ZFFullScreenMode == ZFFullScreenModeLandscape使用此API設置全屏切換

- (void)enterLandscapeFullScreen:(UIInterfaceOrientation)orientation animated:(BOOL)animated;

 

/// 當 ZFFullScreenMode == ZFFullScreenModePortrait使用此API設置全屏切換

- (void)enterPortraitFullScreen:(BOOL)fullScreen animated:(BOOL)animated;

 

/// 內部根據ZFFullScreenMode的值來設置全屏切換

- (void)enterFullScreen:(BOOL)fullScreen animated:(BOOL)animated;

 

ZFPlayerController (ZFPlayerViewGesture)

/// 手勢的管理類

@property (nonatomic, readonly) ZFPlayerGestureControl *gestureControl;

 

/// 禁用哪些手勢,默認支持單擊、雙擊、滑動、縮放手勢

@property (nonatomic, assign) ZFPlayerDisableGestureTypes disableGestureTypes;

 

ZFPlayerController (ZFPlayerScrollView)

/// 初始化時候設置的scrollView

@property (nonatomic, readonly, nullable) UIScrollView *scrollView;

 

/// 列表播放時候是否自動播放,default is YES.

@property (nonatomic) BOOL shouldAutoPlay;

 

/// 列表播放的時小屏的懸浮窗,如果滑出屏幕后小窗播放,需要設置次view的默認frame,次view支持在屏幕可見范圍內隨意拖動。

@property (nonatomic, readonly, nullable) ZFFloatView *smallFloatView;

 

/// 當前播放的indexPath 

@property (nonatomic, nullable) NSIndexPath *playingIndexPath;

 

/// 初始化時候設置的containerViewTag,根據此tag在cell上找到播放器view顯示的位置

@property (nonatomic) NSInteger containerViewTag;

 

/// 滑出屏幕后是否停止播放,如果設置為NO,滑出屏幕后則會小窗播放,defalut is YES.

@property (nonatomic) BOOL stopWhileNotVisible;

 

/// 小屏懸浮窗是否正在顯示

@property (nonatomic, readonly) BOOL isSmallFloatViewShow;

 

/// 如果列表播放時候有一個區或者普通播放時候,可以使用此API,此時 `playTheNext` `playThePrevious` `playTheIndex:`有效。

@property (nonatomic, copy, nullable) NSArray <NSURL *>*assetURLs;

 

/// 如果列表播放時候有多個區,使用此API

@property (nonatomic, copy, nullable) NSArray <NSArray <NSURL *>*>*sectionAssetURLs;

 

/// 在cell上停止當前正在播放的視頻

- (void)stopCurrentPlayingCell;

 

//// 設置播放某indexPath,是否把當前播放indexPath滑動到UICollectionViewScrollPositionTop位置

- (void)playTheIndexPath:(NSIndexPath *)indexPath scrollToTop:(BOOL)scrollToTop;

 

ZFPlayerMediaPlayback—播放器SDK遵守的協議

 

枚舉類型:

 

///  播放狀態:未知、播放中、暫停、失敗、停止

typedef NS_ENUM(NSUInteger, ZFPlayerPlaybackState) {

    ZFPlayerPlayStateUnknown = 0, 

    ZFPlayerPlayStatePlaying,

    ZFPlayerPlayStatePaused,

    ZFPlayerPlayStatePlayFailed,

    ZFPlayerPlayStatePlayStopped

};

 

///  加載狀態:未知、就緒、可以播放、自動播放、播放暫停

typedef NS_OPTIONS(NSUInteger, ZFPlayerLoadState) {

    ZFPlayerLoadStateUnknown        = 0,

    ZFPlayerLoadStatePrepare        = 1 << 0,

    ZFPlayerLoadStatePlayable       = 1 << 1,

    ZFPlayerLoadStatePlaythroughOK  = 1 << 2, 

    ZFPlayerLoadStateStalled        = 1 << 3,

};

 

///  播放畫面拉伸模式:無拉伸、等比例拉伸不裁剪、部分內容裁剪按比例填充、非等比例填滿

typedef NS_ENUM(NSInteger, ZFPlayerScalingMode) {

     ZFPlayerScalingModeNone,         

     ZFPlayerScalingModeAspectFit,  

     ZFPlayerScalingModeAspectFill,     

     ZFPlayerScalingModeFill     

     };

 

 

協議屬性:

 

///  播放器視圖繼承於ZFPlayerView,處理一些手勢沖突

@property (nonatomic) ZFPlayerView *view;

 

///  0...1.0,播放器音量,不影響設備的音量大小

@property (nonatomic) float volume;

 

///  播放器是否靜音,不影響設備靜音

@property (nonatomic, getter=isMuted) BOOL muted;

 

///  0.5...2,播放速率,正常速率為 1

@property (nonatomic) float rate;

 

///  當前播放時間

@property (nonatomic, readonly) NSTimeInterval currentTime;

 

///  播放總時間

@property (nonatomic, readonly) NSTimeInterval totalTime;

 

///  緩沖時間

@property (nonatomic, readonly) NSTimeInterval bufferTime;

 

///  視頻播放定位時間

@property (nonatomic) NSTimeInterval seekTime;

 

///  視頻是否正在播放中

@property (nonatomic, readonly) BOOL isPlaying;

 

///  視頻播放視圖的填充模式,默認不做任何拉伸

@property (nonatomic) ZFPlayerScalingMode scalingMode;

 

///  檢查視頻播放是否准備就緒,返回YES,調用play方法直接播放視頻;返回NO,調用play方法內部自動調用prepareToPlay方法進行視頻播放准備工作

@property (nonatomic, readonly) BOOL isPreparedToPlay;

 

///  媒體播放資源URL

@property (nonatomic) NSURL *assetURL;

 

///  視頻的尺寸

@property (nonatomic, readonly) CGSize presentationSize;

 

///  視頻播放狀態

@property (nonatomic, readonly) ZFPlayerPlaybackState playState;

 

///  視頻的加載狀態

@property (nonatomic, readonly) ZFPlayerLoadState loadState;

 

///  准備播放

@property (nonatomic, copy, nullable) void(^playerPrepareToPlay)(id<ZFPlayerMediaPlayback> asset, NSURL *assetURL);

 

///  播放進度改變

@property (nonatomic, copy, nullable) void(^playerPlayTimeChanged)(id<ZFPlayerMediaPlayback> asset, NSTimeInterval currentTime, NSTimeInterval duration);

 

///  視頻緩沖進度改變

@property (nonatomic, copy, nullable) void(^playerBufferTimeChanged)(id<ZFPlayerMediaPlayback> asset, NSTimeInterval bufferTime);

 

///  視頻播放狀態改變

@property (nonatomic, copy, nullable) void(^playerPlayStatChanged)(id<ZFPlayerMediaPlayback> asset, ZFPlayerPlaybackState playState);

 

///  視頻加載狀態改變

@property (nonatomic, copy, nullable) void(^playerLoadStatChanged)(id<ZFPlayerMediaPlayback> asset, ZFPlayerLoadState loadState);

 

///  視頻播放已經結束

@property (nonatomic, copy, nullable) void(^playerDidToEnd)(id<ZFPlayerMediaPlayback> asset);

 

 

 

協議方法:

 

///  視頻播放准備,中斷除non-mixible之外的任何音頻會話

- (void)prepareToPlay;

 

///  重新進行視頻播放准備

- (void)reloadPlayer;

 

///  視頻播放

- (void)play;

 

///  視頻暫停

- (void)pause;

 

///  視頻重新播放

- (void)replay;

 

///  視頻播放停止

- (void)stop;

 

///  視頻播放當前時間的畫面截圖

- (UIImage *)thumbnailImageAtCurrentTime;

 

///  替換當前媒體資源地址

- (void)replaceCurrentAssetURL:(NSURL *)assetURL;

 

///  調節播放進度

- (void)seekToTime:(NSTimeInterval)time completionHandler:(void (^ __nullable)(BOOL finished))completionHandler;

 

ZFPlayerMediaControl—控制層遵守的協議

 

視頻狀態相關

 

///  視頻播放准備就緒

- (void)videoPlayer:(ZFPlayerController *)videoPlayer prepareToPlay:(NSURL *)assetURL;

 

///  視頻播放狀態改變

- (void)videoPlayer:(ZFPlayerController *)videoPlayer playStateChanged:(ZFPlayerPlaybackState)state;

 

///  視頻加載狀態改變

- (void)videoPlayer:(ZFPlayerController *)videoPlayer loadStateChanged:(ZFPlayerLoadState)state;

 

 

播放進度

 

///  視頻播放時間進度

- (void)videoPlayer:(ZFPlayerController *)videoPlayer

        currentTime:(NSTimeInterval)currentTime

          totalTime:(NSTimeInterval)totalTime;

 

///  視頻緩沖進度

- (void)videoPlayer:(ZFPlayerController *)videoPlayer

         bufferTime:(NSTimeInterval)bufferTime;

 

///  視頻定位播放時間

- (void)videoPlayer:(ZFPlayerController *)videoPlayer

       draggingTime:(NSTimeInterval)seekTime

          totalTime:(NSTimeInterval)totalTime;

 

///  視頻播放結束

- (void)videoPlayerPlayEnd:(ZFPlayerController *)videoPlayer;

 

 

 

鎖屏

 

/// 設置播放器鎖屏時的協議方法

- (void)lockedVideoPlayer:(ZFPlayerController *)videoPlayer lockedScreen:(BOOL)locked;

 

 

 

屏幕旋轉

 

///  播放器全屏模式即將改變

- (void)videoPlayer:(ZFPlayerController *)videoPlayer orientationWillChange:(ZFOrientationObserver *)observer;

 

///  播放器全屏模式已經改變

- (void)videoPlayer:(ZFPlayerController *)videoPlayer orientationDidChanged:(ZFOrientationObserver *)observer;

 

///  當前網絡狀態發生變化

- (void)videoPlayer:(ZFPlayerController *)videoPlayer reachabilityChanged:(ZFReachabilityStatus)status;

 

 

 

手勢方法

 

///  相關手勢設置

- (BOOL)gestureTriggerCondition:(ZFPlayerGestureControl *)gestureControl

                    gestureType:(ZFPlayerGestureType)gestureType

              gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer

                          touch:(UITouch *)touch;

 

///  單擊

- (void)gestureSingleTapped:(ZFPlayerGestureControl *)gestureControl;

 

///  雙擊

- (void)gestureDoubleTapped:(ZFPlayerGestureControl *)gestureControl;

 

///  開始拖拽

- (void)gestureBeganPan:(ZFPlayerGestureControl *)gestureControl

           panDirection:(ZFPanDirection)direction

            panLocation:(ZFPanLocation)location;

 

///  拖拽中

- (void)gestureChangedPan:(ZFPlayerGestureControl *)gestureControl

             panDirection:(ZFPanDirection)direction

              panLocation:(ZFPanLocation)location

             withVelocity:(CGPoint)velocity;

 

///  拖拽結束

- (void)gestureEndedPan:(ZFPlayerGestureControl *)gestureControl

           panDirection:(ZFPanDirection)direction

            panLocation:(ZFPanLocation)location;

 

///  捏合手勢變化

- (void)gesturePinched:(ZFPlayerGestureControl *)gestureControl

        scale:(float)scale;

        

 

 

scrollView上的播放器視圖方法

 

 

/// scrollView中的播放器視圖已經出現(包括tableView或collectionView)

- (void)playerDidAppearInScrollView:(ZFPlayerController *)videoPlayer;

 

///  scrollView中的播放器視圖即將消失(包括tableView或collectionView)

- (void)playerWillDisappearInScrollView:(ZFPlayerController *)videoPlayer;

 

///  scrollView中的播放器視圖消失過半(包括tableView或collectionView)

- (void)playerDisappearHalfInScrollView:(ZFPlayerController *)videoPlayer;

 

/// scrollView中的播放器視圖已經消失(包括tableView或collectionView)

- (void)playerDidDisappearInScrollView:(ZFPlayerController *)videoPlayer;

 

 

UIScrollView+ZFPlayer (處理列表播放相關邏輯)

 
列表

列表播放區域如上圖所示,主要代碼在 scrollViewScrolling方法中,
主要思想就是把播放器View的frame轉到列表的父視圖上,topSpacing和bottomSpacing分別是,播放器上邊線、下邊線與列表父視圖上邊線、下邊線的距離。根據不同的滾動方向來比較topSpacing和bottomSpacing值,來判斷當前播放的view是否在可視區域,具體看代碼吧,注釋寫的很詳細。

代碼傳送門:https://github.com/renzifeng/ZFPlayer
未完待續...

Author

打賞作者

你的支持就是我的動力,請作者喝杯下午茶吧!

 

 

2016.08.10 17:59* 字數 1731 閱讀 44235評論 189喜歡 231贊賞 1

引言

本文主要針對ZFPlayer的功能實現來剖析,以及總結一下大家遇到的問題和解決方案

首先ZFPlayer現在擁有的功能:

  • 支持橫、豎屏切換,在全屏播放模式下還可以鎖定屏幕方向
  • 支持本地視頻、網絡視頻播放
  • 支持在TableviewCell播放視頻
  • 左側1/2位置上下滑動調節屏幕亮度(模擬器調不了亮度,請在真機調試)
  • 右側1/2位置上下滑動調節音量(模擬器調不了音量,請在真機調試)
  • 左右滑動調節播放進度
  • 全屏狀態下拖動slider控制進度,顯示視頻的預覽圖
  • 斷點下載功能
  • 切換視頻分辨率

ZFPlayer是對AVPlayer的封裝,有人會問它支持什么格式的視頻播放,問這個問題的可以自行搜索AVPlayer支持的格式。

跟AVPlayer聯系密切的名詞:

  • Asset:AVAsset是抽象類,不能直接使用,其子類AVURLAsset可以根據URL生成包含媒體信息的Asset對象。
  • AVPlayerItem:和媒體資源存在對應關系,管理媒體資源的信息和狀態。
  • AVPlayerLayer: CALayer的subclass,它主要用來在iOS中播放視頻內容

具體功能實現

1、通過一個網絡鏈接播放視頻

AVURLAsset *urlAsset = [AVURLAsset assetWithURL:videoURL];

// 初始化playerItem

AVPlayerItem *playerItem = [AVPlayerItem playerItemWithAsset:urlAsset];

// 也可以使用來初始化playerItem

// AVPlayerItem * playerItem = [AVPlayerItem playerItemWithURL:videoURL];

 

// 初始化Player

AVPlayer *player = [AVPlayer playerWithPlayerItem:self.playerItem];

// 初始化playerLayer

AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.player];

// 添加playerLayer到self.layer

[self.layer insertSublayer:self.playerLayer atIndex:0];

2、播放器的常用操作

  • 播放:

[player play];

需要注意的是初始化完player之后不一定會馬上開始播放,需要等待player的狀態變為ReadyToPlay才會進行播放。

  • 暫停:

[player pause];

3、播放多個items

這里我們有兩種方式可以實現,一種是由你自行控制下一首歌曲的item,將其替換到當前播放的item

[player replaceCurrentItemWithPlayerItem:playerItem];

在iOS9后,AVPlayer的replaceCurrentItemWithPlayerItem方法在切換視頻時底層會調用信號量等待然后導致當前線程卡頓,如果在UITableViewCell中切換視頻播放使用這個方法,會導致當前線程凍結幾秒鍾。遇到這個坑還真不好在系統層面對它做什么,后來找到的解決方法是在每次需要切換視頻時,需重新創建AVPlayer和AVPlayerItem。

另一種可以使用AVQueuePlayer播放多個items,AVQueuePlayer是AVPlayer的子類,可以用一個數組來初始化一個AVQueuePlayer對象。代碼如下:

NSArray *items = <#An array of player items#>;

AVQueuePlayer *queuePlayer = [[AVQueuePlayer alloc] initWithItems:items];

和AVPlayer一樣,直接調用play方法來播放,queue player順序播放隊列中的item,如果想要跳過一個item,播放下一個item,可以調用方法advanceToNextItem。

可以對隊列進行插入和刪除操作,調用方法insertItem:afterItem:, removeItem:, 和 removeAllItems。正常情況下當插入一個item之前,應該檢查是否可以插入,通過使用canInsertItem:afterItem:方法,第二個參數傳nil,代碼如下:

AVPlayerItem *anItem = <#Get a player item#>;

if ([queuePlayer canInsertItem:anItem afterItem:nil]) {

   [queuePlayer insertItem:anItem afterItem:nil];

}

4seekToTime指定從某一秒開始播放

可以使用seekToTime:定位播放頭到指定的時間,如下代碼:

CMTime fiveSecondsIn = CMTimeMake(5, 1);

[player seekToTime:fiveSecondsIn];

seekTime:不能精確定位,如果需要精確定位,可以使用seekToTime:toleranceBefore:toleranceAfter:,代碼如下:

CMTime fiveSecondsIn = CMTimeMake(5, 1);

[player seekToTime:fiveSecondsIn toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero];

當tolerance=0的時候,framework需要進行大量解碼工作,比較耗性能,所以,只有當你必須使用的時候才用這個方法,比如開發一個復雜的多媒體編輯應用,這需要精確的控制。

關於重播什么的就不用我多說了吧,點擊重播seekToTime:kCMTimeZero。還有關於下次播放的時候從上次離開的那個時間開始播放,大家都有思路啦吧,當離開當前視頻時候記錄播放到哪一秒了,下次點開直接seekToTime到那一秒開始播放就好了嘛。

5、監聽播放進度

使用addPeriodicTimeObserverForInterval:queue:usingBlock:來監聽播放器的進度

(1)方法傳入一個CMTime結構體,每到一定時間都會回調一次,包括開始和結束播放

(2)如果block里面的操作耗時太長,下次不一定會收到回調,所以盡量減少block的操作耗時

(3)方法會返回一個觀察者對象,當播放完畢時需要移除這個觀察者

添加觀察者:

id timeObserve = [player addPeriodicTimeObserverForInterval:CMTimeMake(1.0, 1.0) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {

    float current = CMTimeGetSeconds(time);

    float total = CMTimeGetSeconds(songItem.duration);

    if (current) {

        weakSelf.progress = current / total;

        weakSelf.playTime = [NSString stringWithFormat:@"%.f",current];

        weakSelf.playDuration = [NSString stringWithFormat:@"%.2f",total];       

    }

}];

移除觀察者:

if (timeObserve) {

   [player removeTimeObserver:_timeObserve];

   timeObserve = nil;

 }

6、監聽改播放器狀態

[playerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];

播放器的三種狀態,當playerItem的狀態變為AVPlayerItemStatusReadyToPlay才會進行播放。

typedef NS_ENUM(NSInteger, AVPlayerItemStatus) {

   AVPlayerItemStatusUnknown,

   AVPlayerItemStatusReadyToPlay,

   AVPlayerItemStatusFailed

};

播放完了需要移除觀察者

[playerItem removeObserver:self forKeyPath:@"status"];

7、監聽緩沖進度

[playerItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];

播放完了需要移除觀察者

[playerItem removeObserver:self forKeyPath:@"loadedTimeRanges"];

8、監聽網絡緩沖狀態

// 緩沖區空了,需要等待數據

[playerItem addObserver:self forKeyPath:@"playbackBufferEmpty" options:NSKeyValueObservingOptionNew context:nil];

// 緩沖區有足夠數據可以播放了

[playerItem addObserver:self forKeyPath:@"playbackLikelyToKeepUp" options:NSKeyValueObservingOptionNew context:nil];

播放完了需要移除觀察者

[playerItem removeObserver:self forKeyPath:@"playbackBufferEmpty"];

[playerItem removeObserver:self forKeyPath:@"playbackLikelyToKeepUp"];

9、監聽AVPlayer播放完成通知

監聽通知AVPlayerItemDidPlayToEndTimeNotification,來處理一些播放完的事情

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

10系統音量相關

/**

 *  獲取系統音量

 */

- (void)configureVolume

{

    MPVolumeView *volumeView = [[MPVolumeView alloc] init];

    _volumeViewSlider = nil;

    for (UIView *view in [volumeView subviews]){

        if ([view.class.description isEqualToString:@"MPVolumeSlider"]){

            _volumeViewSlider = (UISlider *)view;

            break;

        }

    }

    

    // 使用這個category的應用不會隨着手機靜音鍵打開而靜音,可在手機靜音下播放聲音

    NSError *setCategoryError = nil;

    BOOL success = [[AVAudioSession sharedInstance]

                    setCategory: AVAudioSessionCategoryPlayback

                    error: &setCategoryError];

    

    if (!success) { /* handle the error in setCategoryError */ }

    

    // 監聽耳機插入和拔掉通知

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(audioRouteChangeListenerCallback:) name:AVAudioSessionRouteChangeNotification object:nil];

}

 

/**

 *  耳機插入、拔出事件

 */

- (void)audioRouteChangeListenerCallback:(NSNotification*)notification

{

    NSDictionary *interuptionDict = notification.userInfo;

    

    NSInteger routeChangeReason = [[interuptionDict valueForKey:AVAudioSessionRouteChangeReasonKey] integerValue];

    

    switch (routeChangeReason) {

            

        case AVAudioSessionRouteChangeReasonNewDeviceAvailable:

            // 耳機插入

            break;

        case AVAudioSessionRouteChangeReasonOldDeviceUnavailable:

        {

            // 耳機拔掉

            // 拔掉耳機繼續播放

            [self play];

        }

 

            break;

 

        case AVAudioSessionRouteChangeReasonCategoryChange:

            // called at start - also when other audio wants to play

            NSLog(@"AVAudioSessionRouteChangeReasonCategoryChange");

            break;

    }

}

設置系統音量

// 0 ... 1.0的數值, 1.0是最大的聲音.

self.volumeViewSlider.value = ...

11、屏幕亮度相關

// 0 ... 1.0的數值, 1.0是最大的亮度.

[UIScreen mainScreen].brightness = ...

12、屏幕旋轉相關

蘋果手機除iPhone 4s(320*480)屏幕寬高比不是16:9外,其他都為16:9,所以橫豎屏可以這樣實現,這里必須使用autolayout,這里提供兩種方法實現:

  • 使用Xib或者Storyboard的話,必須把播放器view的寬高比設置成16:9,4s的話可以單獨適配加約束(使用sizeClasses)
  • 使用masonry,具體代碼如下:

[self.playerView mas_makeConstraints:^(MASConstraintMaker *make) {

        make.top.equalTo(self.view).offset(20);

        make.left.right.equalTo(self.view);

        // 注意此處,寬高比16:9優先級比1000低就行,在因為iPhone 4S寬高比不是16:9

        make.height.equalTo(self.playerView.mas_width).multipliedBy(9.0f/16.0f).with.priority(750);

    }];

關於屏幕旋轉可以這樣強制讓屏幕轉屏,有人會問了,在我demo中為啥能轉屏,而集成到自己項目中不能轉屏,我可以明確的告訴你,是你們項目的橫屏給禁止掉了,你可以看一下這里是否打鈎啦:

 

 

pastedGraphic.png

設備方向

有人又會問了,我們想實現這么個需求,只有在播放器頁面支持橫屏,其他頁面不支持橫屏。好了,那下邊我來告訴怎么實現,首先上圖中的橫屏必須勾選,其次在你需要轉屏的ViewController中來實現三個方法:

//  是否支持自動轉屏

- (BOOL)shouldAutorotate

{

    // 調用ZFPlayerSingleton單例記錄播放狀態是否鎖定屏幕方向

    return !ZFPlayerShared.isLockScreen;

}

 

// 支持哪些轉屏方向

- (UIInterfaceOrientationMask)supportedInterfaceOrientations

{

    return UIInterfaceOrientationMaskAllButUpsideDown;

}

 

// 頁面展示的時候默認屏幕方向(當前ViewController必須是通過模態ViewController(模態帶導航的無效)方式展現出來的,才會調用這個方法)

- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation

{

    return UIInterfaceOrientationPortrait;

}

ZFPlayer內部已經實現屏幕旋轉的分類(UITabBarController+ZFPlayerRotation.h UINavigationController+ZFPlayerRotation UIViewController+ZFPlayerRotation),不管你項目的rootViewController的是UINavigationController還是UITabBarController,則只需要在支持除豎屏以外的控制器實現上邊三個方法就行。

下邊來說說強制屏幕旋轉,即使用戶的手機鎖定了屏幕方法,調用這個方法照樣可以旋轉:

/**

 *  強制屏幕轉屏

 *

 *  @param orientation 屏幕方向

 */

- (void)interfaceOrientation:(UIInterfaceOrientation)orientation

{

    // arc下

    if ([[UIDevice currentDevice] respondsToSelector:@selector(setOrientation:)]) {

        SEL selector             = NSSelectorFromString(@"setOrientation:");

        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[UIDevice instanceMethodSignatureForSelector:selector]];

        [invocation setSelector:selector];

        [invocation setTarget:[UIDevice currentDevice]];

        int val                  = orientation;

        // 從2開始是因為0 1 兩個參數已經被selector和target占用

        [invocation setArgument:&val atIndex:2];

        [invocation invoke];

    }

    /*

     // 非arc下

     if ([[UIDevice currentDevice] respondsToSelector:@selector(setOrientation:)]) {

     [[UIDevice currentDevice] performSelector:@selector(setOrientation:)

     withObject:@(orientation)];

     }

     

     // 直接調用這個方法通不過apple上架審核

     [[UIDevice currentDevice] setValue:[NSNumber numberWithInteger:UIInterfaceOrientationLandscapeRight] forKey:@"orientation"];

     */

}

監聽設備旋轉通知,來處理一些UI顯示問題

 [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];

 [[NSNotificationCenter defaultCenter] addObserver:self

                                             selector:@selector(onDeviceOrientationChange)

                                                 name:UIDeviceOrientationDidChangeNotification

                                               object:nil

];

未完待續....

打賞作者

你的支持就是我的動力,請作者喝杯奶茶吧!

https://www.jianshu.com/p/5566077bb25f

 

中文說明

Before this, you used ZFPlayer, are you worried about encapsulating avplayer instead of using or modifying the source code to support other players, the control layer is not easy to customize, and so on? In order to solve these problems, I have wrote this player template, for player SDK you can conform the ZFPlayerMediaPlayback protocol, for control view you can conform the ZFPlayerMediaControl protocol, can custom the player and control view.

在3.X之前,是不是在煩惱播放器SDK自定義、控制層自定義等問題。作者公司多個項目分別使用不同播放器SDK以及每個項目控制層都不一樣,但是為了統一管理、統一調用,我特意寫了這個播放器殼子。播放器SDK只要遵守ZFPlayerMediaPlayback協議,控制層只要遵守ZFPlayerMediaControl協議,完全可以實現自定義播放器和控制層。

pastedGraphic.png

 

Requirements

  • iOS 7+
  • Xcode 8+

 

Installation

ZFPlayer is available through CocoaPods. To install it,use player template simply add the following line to your Podfile: 只有Core文件夾下代碼,使用AVPlayer、IJK、KSY都需要pod單獨模塊,或者使用自定義播放器管理類,如需定制功能請單獨聯系作者。

pod 'ZFPlayer', '~> 3.0'

Use default controlView simply add the following line to your Podfile:

pod 'ZFPlayer/ControlView', '~> 3.0'

Use AVPlayer simply add the following line to your Podfile:

pod 'ZFPlayer/AVPlayer', '~> 3.0'

如果使用AVPlayer邊下邊播可以參考使用KTVHTTPCache

Use ijkplayer simply add the following line to your Podfile:

pod 'ZFPlayer/ijkplayer', '~> 3.0'

IJKMediaFramework SDK support cocoapods

Use KSYMediaPlayer simply add the following line to your Podfile:

pod 'ZFPlayer/KSYMediaPlayer', '~> 3.0'

KSYMediaPlayer SDK support cocoapods

 

Usage introduce

 

ZFPlayerController

Main classes, two initialization methods, normal mode initialization and list style initialization (tableView, collection)

Normal style initialization

ZFPlayerController *player = [ZFPlayerController playerWithPlayerManager:playerManager containerView:containerView];

ZFPlayerController *player = [[ZFPlayerController alloc] initwithPlayerManager:playerManager containerView:containerView];

List style initialization

ZFPlayerController *player = [ZFPlayerController playerWithScrollView:tableView playerManager:playerManager containerViewTag:containerViewTag];

ZFPlayerController *player = [ZFPlayerController alloc] initWithScrollView:tableView playerManager:playerManager containerViewTag:containerViewTag];

 

ZFPlayerMediaPlayback

For the playerMnager,you must conform ZFPlayerMediaPlayback protocol,custom playermanager can supports any player SDK,such as AVPlayer,MPMoviePlayerController,ijkplayer,vlc,PLPlayerKit,KSYMediaPlayerand so on,you can reference the ZFAVPlayerManagerclass.

Class<ZFPlayerMediaPlayback> *playerManager = ...;

 

ZFPlayerMediaControl

This class is used to display the control layer, and you must conform the ZFPlayerMediaControl protocol, you can reference the ZFPlayerControlView class.

UIView<ZFPlayerMediaControl> *controlView = ...;

player.controlView = controlView;

 

Usage

 

Normal Style

/// Your custom playerManager must conform `ZFPlayerMediaPlayback` protocol.

Class<ZFPlayerMediaPlayback> *playerManager = ...;

 

/// playerController

ZFPlayerController *player = [ZFPlayerController playerWithPlayerManager:playerManager containerView:self.containerView];

player.controlView = controlView<ZFPlayerMediaControl>;

playerManager.assetURL = [NSURL URLWithString:...];

 

List style

/// Your custom playerManager must conform `ZFPlayerMediaPlayback` protocol.

Class<ZFPlayerMediaPlayback> *playerManager = ...;

 

/// playerController

ZFPlayerController *player = [ZFPlayerController playerWithScrollView:tableView playerManager:playerManager containerViewTag:tag<NSInteger>];

player.controlView = controlView<ZFPlayerMediaControl>;

self.player.assetURLs = array<NSURL *>;

Rotate the video the viewController must implement

- (BOOL)shouldAutorotate {

    return player.shouldAutorotate;

}

 

Reference

 

Author

隨筆 - 204  文章 - 2  評論 - 70

ZFPlayer 源碼解讀

 

源碼下載地址:https://github.com/renzifeng/ZFPlayer

之前自己實現過一個模仿百思不得姐的demo https://github.com/agelessman/FFmpegAndKxmovieDemo

由於有朋友推薦,看了下ZFPlayer,覺得功能和封裝都寫的很好,就把源碼看了一遍,現在看源碼已經養成了一個習慣,就是把自己在源碼中不太熟悉的地方記錄下來,還有就是盡量捕捉作者的思路。

打開demo,先看主控制器

pastedGraphic.png

主要的方法有兩個:

// 哪些頁面支持自動轉屏

- (BOOL)shouldAutorotate

 

// viewcontroller支持哪些轉屏方向

- (UIInterfaceOrientationMask)supportedInterfaceOrientations

這兩個方法也沒什么好說的,只是在我們寫app的時候,一般都是默認開啟app支持旋轉的,然后用代碼實現支持哪些界面能夠旋轉。

這里作者使用了這樣的代碼

// 調用ZFPlayerSingleton單例記錄播放狀態是否鎖定屏幕方向

        return !ZFPlayerShared.isLockScreen;

不看后邊的代碼,應該能夠推斷出整個播放器采用的是單例模式的設計,有且只有一個,這樣就避免了反復創建的消耗。但不是說創建了就一直存在,完全可以在需要銷毀的時候進行銷毀。

接下來看這四個文件

pastedGraphic_1.png

不難看出,ZFSessionModel應該就是與下載的文件相關信息的一個模型,在這個模型中我們能夠得到跟下載的文件相關的我們需要的所有信息

pastedGraphic_2.png

支持NSCoding 協議,說明這個類會被歸檔和解檔,也就是說對本類或進行本地存儲操作

pastedGraphic_3.png

從編碼的屬性看,並沒有編碼所有的屬性,只編碼了必要的信息。

我們用一張圖表來看本類的所有信息

pastedGraphic_4.png

 

接下來我們說說下載管理器的問題

其實編程跟我們日常生活中的生活規律特別的像,比如,我需要一個下載管理器來管理我整個工程的下載任務,如果我的下載任務很重,很多,那么我就應該多弄幾個管理器,各管各自的業務,最后向一個總的管理boss負責。這種思想很重要,我們完全可以在寫代碼之前想象出一個大概的職責列表,每一項職責都是一個屬性或者方法。

這樣的想法很奇妙,不如我們就按照現在的思路,想象一下,現實生活中,作為一個數據倉庫的管理員都需要干什么呢?

pastedGraphic_5.png

大家可以對比一下這個日常生活中的做事習慣跟變成是不是很像

再和

pastedGraphic_6.png

文件對比下,看看是不是差不多,可能我們在寫接口文件的時候並不能一開始寫的很周到,但是在實現功能的過程中,會慢慢的想到需要添加哪些東西,除非很必要,應該暴露的東西越少越好。

由於作者的注釋非常的詳細,對所有的方法就不一一解釋了,有點基礎的都能看懂,

pastedGraphic_7.png

這個是更加安全的單例寫法,不要只寫最下邊的那個方法。

在下載管理者的實現中 通過

NSURLSessionDataDelegate

處理了下載過程和下載完成后的邏輯,這個就不解釋了,所有的下載代碼都差不多是這樣的,需要指出的是斷點下載的實現,是下邊的代碼,在配置下載器的時候傳入一個范圍就可以了

pastedGraphic_8.png

 

好了現在重點來看看播放器的部分。

這個demo作者是沒有加入邊播邊下載功能的,但是加了加載進度的緩存顯示效果,這個效果主要是通過監聽

loadedTimeRanges 實現的,

由於代碼比較長,也都是一些業務邏輯上的問題,再次就一個個的進行說明了,作者也注釋的清晰,

pastedGraphic_9.png

 

通過這個方法可以直接用在tableview類型的播放器中,這個還是比較方便的,看來作者也是想讓別人用起來方便。

該demo提供的邏輯和功能還是很完善的,因為前段時間也自學了AVFoundation方面的知識,所以對這個還是很感興趣的。

AVFoundation 提供了一系列很強大的功能 

有興趣的朋友可以下載這些demo看看,使用swift寫的 http://code.cocoachina.com/u/373290

在這里也正好總結一些我對寫一個類似這樣播放器的看法。

pastedGraphic_10.png

作者是把整個功能使用UIView來實現的,而且額外提供了一些功能,可以讓用戶處理點擊事件或者設置點擊后的行為。

如果是我,我會把整個功能封裝成一個NSObject(在一本書上學到的),把所有的功能封裝進這個對象中去,就像這樣

pastedGraphic_11.png

很簡單,之暴露出來一個初始化方法,和一個實際播放的view

使用起來大概是這么使用

pastedGraphic_12.png

內部的實現是這樣

pastedGraphic_13.png

  1 #import "THPlayerController.h"

  2 #import "THThumbnail.h"

  3 #import <AVFoundation/AVFoundation.h>

  4 #import "THTransport.h"

  5 #import "THPlayerView.h"

  6 #import "AVAsset+THAdditions.h"

  7 #import "UIAlertView+THAdditions.h"

  8 #import "THNotifications.h"

  9 

 10 // AVPlayerItem's status property

 11 #define STATUS_KEYPATH @"status"

 12 

 13 // Refresh interval for timed observations of AVPlayer

 14 #define REFRESH_INTERVAL 0.5f

 15 

 16 // Define this constant for the key-value observation context.

 17 static const NSString *PlayerItemStatusContext;

 18 

 19 

 20 @interface THPlayerController () <THTransportDelegate>

 21 

 22 @property (strong, nonatomic) AVAsset *asset;

 23 @property (strong, nonatomic) AVPlayerItem *playerItem;

 24 @property (strong, nonatomic) AVPlayer *player;

 25 @property (strong, nonatomic) THPlayerView *playerView;

 26 

 27 @property (weak, nonatomic) id <THTransport> transport;

 28 

 29 @property (strong, nonatomic) id timeObserver;

 30 @property (strong, nonatomic) id itemEndObserver;

 31 @property (assign, nonatomic) float lastPlaybackRate;

 32 

 33 @property (strong, nonatomic) AVAssetImageGenerator *imageGenerator;

 34 

 35 @end

 36 

 37 @implementation THPlayerController

 38 

 39 #pragma mark - Setup

 40 

 41 - (id)initWithURL:(NSURL *)assetURL {

 42     self = [super init];

 43     if (self) {

 44         _asset = [AVAsset assetWithURL:assetURL];                           // 1

 45         [self prepareToPlay];

 46     }

 47     return self;

 48 }

 49 

 50 - (void)prepareToPlay {

 51     NSArray *keys = @[

 52         @"tracks",

 53         @"duration",

 54         @"commonMetadata",

 55         @"availableMediaCharacteristicsWithMediaSelectionOptions"

 56     ];

 57     self.playerItem = [AVPlayerItem playerItemWithAsset:self.asset          // 2

 58                            automaticallyLoadedAssetKeys:keys];

 59 

 60     [self.playerItem addObserver:self                                       // 3

 61                       forKeyPath:STATUS_KEYPATH

 62                          options:0

 63                          context:&PlayerItemStatusContext];

 64 

 65     self.player = [AVPlayer playerWithPlayerItem:self.playerItem];          // 4

 66 

 67     self.playerView = [[THPlayerView alloc] initWithPlayer:self.player];    // 5

 68     self.transport = self.playerView.transport;

 69     self.transport.delegate = self;

 70 }

 71 

 72 - (void)observeValueForKeyPath:(NSString *)keyPath

 73                       ofObject:(id)object

 74                         change:(NSDictionary *)change

 75                        context:(void *)context {

 76     

 77     if (context == &PlayerItemStatusContext) {

 78         

 79         dispatch_async(dispatch_get_main_queue(), ^{                        // 1

 80             

 81             [self.playerItem removeObserver:self forKeyPath:STATUS_KEYPATH];

 82             

 83             if (self.playerItem.status == AVPlayerItemStatusReadyToPlay) {

 84                 

 85                 // Set up time observers.                                   // 2

 86                 [self addPlayerItemTimeObserver];

 87                 [self addItemEndObserverForPlayerItem];

 88                 

 89                 CMTime duration = self.playerItem.duration;

 90                 

 91                 // Synchronize the time display                             // 3

 92                 [self.transport setCurrentTime:CMTimeGetSeconds(kCMTimeZero)

 93                                       duration:CMTimeGetSeconds(duration)];

 94                 

 95                 // Set the video title.

 96                 [self.transport setTitle:self.asset.title];                 // 4

 97                 

 98                 [self.player play];                                         // 5

 99                 

100                 [self loadMediaOptions];

101                 [self generateThumbnails];

102                 

103             } else {

104                 [UIAlertView showAlertWithTitle:@"Error"

105                                         message:@"Failed to load video"];

106             }

107         });

108     }

109 }

110 

111 - (void)loadMediaOptions {

112     NSString *mc = AVMediaCharacteristicLegible;                            // 1

113     AVMediaSelectionGroup *group =

114         [self.asset mediaSelectionGroupForMediaCharacteristic:mc];          // 2

115     if (group) {

116         NSMutableArray *subtitles = [NSMutableArray array];                 // 3

117         for (AVMediaSelectionOption *option in group.options) {

118             [subtitles addObject:option.displayName];

119         }

120         [self.transport setSubtitles:subtitles];                            // 4

121     } else {

122         [self.transport setSubtitles:nil];

123     }

124 }

125 

126 - (void)subtitleSelected:(NSString *)subtitle {

127     NSString *mc = AVMediaCharacteristicLegible;

128     AVMediaSelectionGroup *group =

129         [self.asset mediaSelectionGroupForMediaCharacteristic:mc];          // 1

130     BOOL selected = NO;

131     for (AVMediaSelectionOption *option in group.options) {

132         if ([option.displayName isEqualToString:subtitle]) {

133             [self.playerItem selectMediaOption:option                       // 2

134                          inMediaSelectionGroup:group];

135             selected = YES;

136         }

137     }

138     if (!selected) {

139         [self.playerItem selectMediaOption:nil                              // 3

140                      inMediaSelectionGroup:group];

141     }

142 }

143 

144 

145 #pragma mark - Time Observers

146 

147 - (void)addPlayerItemTimeObserver {

148     

149     // Create 0.5 second refresh interval - REFRESH_INTERVAL == 0.5

150     CMTime interval =

151         CMTimeMakeWithSeconds(REFRESH_INTERVAL, NSEC_PER_SEC);              // 1

152     

153     // Main dispatch queue

154     dispatch_queue_t queue = dispatch_get_main_queue();                     // 2

155     

156     // Create callback block for time observer

157     __weak THPlayerController *weakSelf = self;                             // 3

158     void (^callback)(CMTime time) = ^(CMTime time) {

159         NSTimeInterval currentTime = CMTimeGetSeconds(time);

160         NSTimeInterval duration = CMTimeGetSeconds(weakSelf.playerItem.duration);

161         [weakSelf.transport setCurrentTime:currentTime duration:duration];  // 4

162     };

163     

164     // Add observer and store pointer for future use

165     self.timeObserver =                                                     // 5

166         [self.player addPeriodicTimeObserverForInterval:interval

167                                                   queue:queue

168                                              usingBlock:callback];

169 }

170 

171 - (void)addItemEndObserverForPlayerItem {

172 

173     NSString *name = AVPlayerItemDidPlayToEndTimeNotification;

174 

175     NSOperationQueue *queue = [NSOperationQueue mainQueue];

176 

177     __weak THPlayerController *weakSelf = self;                             // 1

178     void (^callback)(NSNotification *note) = ^(NSNotification *notification) {

179         [weakSelf.player seekToTime:kCMTimeZero                             // 2

180                   completionHandler:^(BOOL finished) {

181             [weakSelf.transport playbackComplete];                          // 3

182         }];

183     };

184 

185     self.itemEndObserver =                                                  // 4

186         [[NSNotificationCenter defaultCenter] addObserverForName:name

187                                                           object:self.playerItem

188                                                            queue:queue

189                                                       usingBlock:callback];

190 }

191 

192 #pragma mark - THTransportDelegate Methods

193 

194 - (void)play {

195     [self.player play];

196 }

197 

198 - (void)pause {

199     self.lastPlaybackRate = self.player.rate;

200     [self.player pause];

201 }

202 

203 - (void)stop {

204     [self.player setRate:0.0f];

205     [self.transport playbackComplete];

206 }

207 

208 - (void)jumpedToTime:(NSTimeInterval)time {

209     [self.player seekToTime:CMTimeMakeWithSeconds(time, NSEC_PER_SEC)];

210 }

211 

212 - (void)scrubbingDidStart {                                                 // 1

213     self.lastPlaybackRate = self.player.rate;

214     [self.player pause];

215     [self.player removeTimeObserver:self.timeObserver];

216 }

217 

218 - (void)scrubbedToTime:(NSTimeInterval)time {                               // 2

219     [self.playerItem cancelPendingSeeks];

220     [self.player seekToTime:CMTimeMakeWithSeconds(time, NSEC_PER_SEC) toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero];

221 }

222 

223 - (void)scrubbingDidEnd {                                                   // 3

224     [self addPlayerItemTimeObserver];

225     if (self.lastPlaybackRate > 0.0f) {

226         [self.player play];

227     }

228 }

229 

230 

231 #pragma mark - Thumbnail Generation

232 

233 - (void)generateThumbnails {

234     

235     self.imageGenerator =                                                   // 1

236         [AVAssetImageGenerator assetImageGeneratorWithAsset:self.asset];

237     

238     // Generate the @2x equivalent

239     self.imageGenerator.maximumSize = CGSizeMake(200.0f, 0.0f);             // 2

240 

241     CMTime duration = self.asset.duration;

242 

243     NSMutableArray *times = [NSMutableArray array];                         // 3

244     CMTimeValue increment = duration.value / 20;

245     CMTimeValue currentValue = 2.0 * duration.timescale;

246     while (currentValue <= duration.value) {

247         CMTime time = CMTimeMake(currentValue, duration.timescale);

248         [times addObject:[NSValue valueWithCMTime:time]];

249         currentValue += increment;

250     }

251 

252     __block NSUInteger imageCount = times.count;                            // 4

253     __block NSMutableArray *images = [NSMutableArray array];

254 

255     AVAssetImageGeneratorCompletionHandler handler;                         // 5

256     

257     handler = ^(CMTime requestedTime,

258                 CGImageRef imageRef,

259                 CMTime actualTime,

260                 AVAssetImageGeneratorResult result,

261                 NSError *error) {

262 

263         if (result == AVAssetImageGeneratorSucceeded) {                     // 6

264             UIImage *image = [UIImage imageWithCGImage:imageRef];

265             id thumbnail =

266                 [THThumbnail thumbnailWithImage:image time:actualTime];

267             [images addObject:thumbnail];

268         } else {

269             NSLog(@"Error: %@", [error localizedDescription]);

270         }

271 

272         // If the decremented image count is at 0, we're all done.

273         if (--imageCount == 0) {                                            // 7

274             dispatch_async(dispatch_get_main_queue(), ^{

275                 NSString *name = THThumbnailsGeneratedNotification;

276                 NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];

277                 [nc postNotificationName:name object:images];

278             });

279         }

280     };

281 

282     [self.imageGenerator generateCGImagesAsynchronouslyForTimes:times       // 8

283                                               completionHandler:handler];

284     

285     

286 }

287 

288 

289 #pragma mark - Housekeeping

290 

291 - (UIView *)view {

292     return self.playerView;

293 }

294 

295 - (void)dealloc {

296     if (self.itemEndObserver) {                                             // 5

297         NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];

298         [nc removeObserver:self.itemEndObserver

299                       name:AVPlayerItemDidPlayToEndTimeNotification

300                     object:self.player.currentItem];

301         self.itemEndObserver = nil;

302     }

303 }

304 

305 @end

pastedGraphic_13.png

本類只提供 AVFoundation中的關於視頻的一些播放暫停等等的控制功能,

界面需要另外一個view來展示,

pastedGraphic_14.png

控制單元也就是界面 跟 播放控制器 之間的通信同過一個協議來實現

pastedGraphic_15.png

這樣需要在控制界面添加功能 都是通過協議來通信的,即實現了功能,也保持了很好的獨立性。

這樣用戶完全可以自定義一套界面 ,依然能夠使用AVFoundation的功能。

好了 ,本片文章就到此為止了。由於個人能力有限,如有錯誤之處,請幫忙給與指出,不勝感謝啊 。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM