https://www.jianshu.com/p/90e55deb4d51
詳細介紹一下ZFPlayer 3.0的用法,如果你有什么問題或者建議可聯系我。在3.0之前版本使用ZFPlayer,是不是在煩惱播放器SDK自定義、控制層自定義等問題。作者公司多個項目分別使用不同播放器SDK以及每個項目控制層都不一樣,但是為了統一管理、統一調用,我特意寫了這個播放器殼子。播放器SDK只要遵守ZFPlayerMediaPlayback
協議,控制層只要遵守ZFPlayerMediaControl
協議,可以實現自定義播放器和控制層。
目前支持的功能如下:
- 普通模式的播放,類似於騰訊視頻、愛奇藝等APP;
- 列表普通模式的播放,包括手動點擊播放、滑動到屏幕中間自動播放,wifi網絡智能播放等等;
- 列表的亮暗模式播放,類似於微博、UC瀏覽器視頻列表等APP;
- 列表視頻滑出屏幕后停止播放、滑出屏幕后小窗播放;
- 優雅的全屏,支持橫屏和豎屏全屏模式;
播放器的主要類為ZFPlayerController
,具體API請看下邊這張圖吧,后邊我也會詳細介紹。在之前版本收到好多開發朋友的Issues建議也好bug也好,ZFPlayer也是致力於解決這些問題和滿足各位的建議。

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
- Weibo: @任子豐
- Email: zifeng1300@gmail.com
- QQ Group: 213375947(付費群,提供技術支持)
打賞作者
你的支持就是我的動力,請作者喝杯下午茶吧!
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];
}
4、seekToTime指定從某一秒開始播放
可以使用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中為啥能轉屏,而集成到自己項目中不能轉屏,我可以明確的告訴你,是你們項目的橫屏給禁止掉了,你可以看一下這里是否打鈎啦:
設備方向
有人又會問了,我們想實現這么個需求,只有在播放器頁面支持橫屏,其他頁面不支持橫屏。好了,那下邊我來告訴怎么實現,首先上圖中的橫屏必須勾選,其次在你需要轉屏的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協議,完全可以實現自定義播放器和控制層。
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
- Weibo: @任子豐
- Email: zifeng1300@gmail.com
- QQ群: (付費群)
隨筆 - 204 文章 - 2 評論 - 70
源碼下載地址:https://github.com/renzifeng/ZFPlayer
之前自己實現過一個模仿百思不得姐的demo https://github.com/agelessman/FFmpegAndKxmovieDemo
由於有朋友推薦,看了下ZFPlayer,覺得功能和封裝都寫的很好,就把源碼看了一遍,現在看源碼已經養成了一個習慣,就是把自己在源碼中不太熟悉的地方記錄下來,還有就是盡量捕捉作者的思路。
打開demo,先看主控制器
主要的方法有兩個:
// 哪些頁面支持自動轉屏
- (BOOL)shouldAutorotate
// viewcontroller支持哪些轉屏方向
- (UIInterfaceOrientationMask)supportedInterfaceOrientations
這兩個方法也沒什么好說的,只是在我們寫app的時候,一般都是默認開啟app支持旋轉的,然后用代碼實現支持哪些界面能夠旋轉。
這里作者使用了這樣的代碼
// 調用ZFPlayerSingleton單例記錄播放狀態是否鎖定屏幕方向
return !ZFPlayerShared.isLockScreen;
不看后邊的代碼,應該能夠推斷出整個播放器采用的是單例模式的設計,有且只有一個,這樣就避免了反復創建的消耗。但不是說創建了就一直存在,完全可以在需要銷毀的時候進行銷毀。
接下來看這四個文件
不難看出,ZFSessionModel應該就是與下載的文件相關信息的一個模型,在這個模型中我們能夠得到跟下載的文件相關的我們需要的所有信息
支持NSCoding 協議,說明這個類會被歸檔和解檔,也就是說對本類或進行本地存儲操作
從編碼的屬性看,並沒有編碼所有的屬性,只編碼了必要的信息。
我們用一張圖表來看本類的所有信息
接下來我們說說下載管理器的問題
其實編程跟我們日常生活中的生活規律特別的像,比如,我需要一個下載管理器來管理我整個工程的下載任務,如果我的下載任務很重,很多,那么我就應該多弄幾個管理器,各管各自的業務,最后向一個總的管理boss負責。這種思想很重要,我們完全可以在寫代碼之前想象出一個大概的職責列表,每一項職責都是一個屬性或者方法。
這樣的想法很奇妙,不如我們就按照現在的思路,想象一下,現實生活中,作為一個數據倉庫的管理員都需要干什么呢?
大家可以對比一下這個日常生活中的做事習慣跟變成是不是很像
再和
文件對比下,看看是不是差不多,可能我們在寫接口文件的時候並不能一開始寫的很周到,但是在實現功能的過程中,會慢慢的想到需要添加哪些東西,除非很必要,應該暴露的東西越少越好。
由於作者的注釋非常的詳細,對所有的方法就不一一解釋了,有點基礎的都能看懂,
這個是更加安全的單例寫法,不要只寫最下邊的那個方法。
在下載管理者的實現中 通過
NSURLSessionDataDelegate
處理了下載過程和下載完成后的邏輯,這個就不解釋了,所有的下載代碼都差不多是這樣的,需要指出的是斷點下載的實現,是下邊的代碼,在配置下載器的時候傳入一個范圍就可以了
好了現在重點來看看播放器的部分。
這個demo作者是沒有加入邊播邊下載功能的,但是加了加載進度的緩存顯示效果,這個效果主要是通過監聽
loadedTimeRanges 實現的,
由於代碼比較長,也都是一些業務邏輯上的問題,再次就一個個的進行說明了,作者也注釋的清晰,
通過這個方法可以直接用在tableview類型的播放器中,這個還是比較方便的,看來作者也是想讓別人用起來方便。
該demo提供的邏輯和功能還是很完善的,因為前段時間也自學了AVFoundation方面的知識,所以對這個還是很感興趣的。
AVFoundation 提供了一系列很強大的功能
有興趣的朋友可以下載這些demo看看,使用swift寫的 http://code.cocoachina.com/u/373290
在這里也正好總結一些我對寫一個類似這樣播放器的看法。
作者是把整個功能使用UIView來實現的,而且額外提供了一些功能,可以讓用戶處理點擊事件或者設置點擊后的行為。
如果是我,我會把整個功能封裝成一個NSObject(在一本書上學到的),把所有的功能封裝進這個對象中去,就像這樣
很簡單,之暴露出來一個初始化方法,和一個實際播放的view
使用起來大概是這么使用
內部的實現是這樣
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
本類只提供 AVFoundation中的關於視頻的一些播放暫停等等的控制功能,
界面需要另外一個view來展示,
控制單元也就是界面 跟 播放控制器 之間的通信同過一個協議來實現
這樣需要在控制界面添加功能 都是通過協議來通信的,即實現了功能,也保持了很好的獨立性。
這樣用戶完全可以自定義一套界面 ,依然能夠使用AVFoundation的功能。
好了 ,本片文章就到此為止了。由於個人能力有限,如有錯誤之處,請幫忙給與指出,不勝感謝啊 。