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的功能。
好了 ,本片文章就到此为止了。由于个人能力有限,如有错误之处,请帮忙给与指出,不胜感谢啊 。