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