博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
ZFPlayer 源码解读
阅读量:6210 次
发布时间:2019-06-21

本文共 13641 字,大约阅读时间需要 45 分钟。

源码下载地址:

之前自己实现过一个模仿百思不得姐的demo 

由于有朋友推荐,看了下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写的 

在这里也正好总结一些我对写一个类似这样播放器的看法。

作者是把整个功能使用UIView来实现的,而且额外提供了一些功能,可以让用户处理点击事件或者设置点击后的行为。

如果是我,我会把整个功能封装成一个NSObject(在一本书上学到的),把所有的功能封装进这个对象中去,就像这样

很简单,之暴露出来一个初始化方法,和一个实际播放的view

使用起来大概是这么使用

内部的实现是这样

1 #import "THPlayerController.h"  2 #import "THThumbnail.h"  3 #import 
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 ()
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
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; // 1113 AVMediaSelectionGroup *group =114 [self.asset mediaSelectionGroupForMediaCharacteristic:mc]; // 2115 if (group) {116 NSMutableArray *subtitles = [NSMutableArray array]; // 3117 for (AVMediaSelectionOption *option in group.options) {118 [subtitles addObject:option.displayName];119 }120 [self.transport setSubtitles:subtitles]; // 4121 } 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]; // 1130 BOOL selected = NO;131 for (AVMediaSelectionOption *option in group.options) {132 if ([option.displayName isEqualToString:subtitle]) {133 [self.playerItem selectMediaOption:option // 2134 inMediaSelectionGroup:group];135 selected = YES;136 }137 }138 if (!selected) {139 [self.playerItem selectMediaOption:nil // 3140 inMediaSelectionGroup:group];141 }142 }143 144 145 #pragma mark - Time Observers146 147 - (void)addPlayerItemTimeObserver {148 149 // Create 0.5 second refresh interval - REFRESH_INTERVAL == 0.5150 CMTime interval =151 CMTimeMakeWithSeconds(REFRESH_INTERVAL, NSEC_PER_SEC); // 1152 153 // Main dispatch queue154 dispatch_queue_t queue = dispatch_get_main_queue(); // 2155 156 // Create callback block for time observer157 __weak THPlayerController *weakSelf = self; // 3158 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]; // 4162 };163 164 // Add observer and store pointer for future use165 self.timeObserver = // 5166 [self.player addPeriodicTimeObserverForInterval:interval167 queue:queue168 usingBlock:callback];169 }170 171 - (void)addItemEndObserverForPlayerItem {172 173 NSString *name = AVPlayerItemDidPlayToEndTimeNotification;174 175 NSOperationQueue *queue = [NSOperationQueue mainQueue];176 177 __weak THPlayerController *weakSelf = self; // 1178 void (^callback)(NSNotification *note) = ^(NSNotification *notification) {179 [weakSelf.player seekToTime:kCMTimeZero // 2180 completionHandler:^(BOOL finished) {181 [weakSelf.transport playbackComplete]; // 3182 }];183 };184 185 self.itemEndObserver = // 4186 [[NSNotificationCenter defaultCenter] addObserverForName:name187 object:self.playerItem188 queue:queue189 usingBlock:callback];190 }191 192 #pragma mark - THTransportDelegate Methods193 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 { // 1213 self.lastPlaybackRate = self.player.rate;214 [self.player pause];215 [self.player removeTimeObserver:self.timeObserver];216 }217 218 - (void)scrubbedToTime:(NSTimeInterval)time { // 2219 [self.playerItem cancelPendingSeeks];220 [self.player seekToTime:CMTimeMakeWithSeconds(time, NSEC_PER_SEC) toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero];221 }222 223 - (void)scrubbingDidEnd { // 3224 [self addPlayerItemTimeObserver];225 if (self.lastPlaybackRate > 0.0f) {226 [self.player play];227 }228 }229 230 231 #pragma mark - Thumbnail Generation232 233 - (void)generateThumbnails {234 235 self.imageGenerator = // 1236 [AVAssetImageGenerator assetImageGeneratorWithAsset:self.asset];237 238 // Generate the @2x equivalent239 self.imageGenerator.maximumSize = CGSizeMake(200.0f, 0.0f); // 2240 241 CMTime duration = self.asset.duration;242 243 NSMutableArray *times = [NSMutableArray array]; // 3244 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; // 4253 __block NSMutableArray *images = [NSMutableArray array];254 255 AVAssetImageGeneratorCompletionHandler handler; // 5256 257 handler = ^(CMTime requestedTime,258 CGImageRef imageRef,259 CMTime actualTime,260 AVAssetImageGeneratorResult result,261 NSError *error) {262 263 if (result == AVAssetImageGeneratorSucceeded) { // 6264 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) { // 7274 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 // 8283 completionHandler:handler];284 285 286 }287 288 289 #pragma mark - Housekeeping290 291 - (UIView *)view {292 return self.playerView;293 }294 295 - (void)dealloc {296 if (self.itemEndObserver) { // 5297 NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];298 [nc removeObserver:self.itemEndObserver299 name:AVPlayerItemDidPlayToEndTimeNotification300 object:self.player.currentItem];301 self.itemEndObserver = nil;302 }303 }304 305 @end

本类只提供 AVFoundation中的关于视频的一些播放暂停等等的控制功能,

界面需要另外一个view来展示,

控制单元也就是界面 跟 播放控制器 之间的通信同过一个协议来实现

这样需要在控制界面添加功能 都是通过协议来通信的,即实现了功能,也保持了很好的独立性。

这样用户完全可以自定义一套界面 ,依然能够使用AVFoundation的功能。

好了 ,本片文章就到此为止了。由于个人能力有限,如有错误之处,请帮忙给与指出,不胜感谢啊 。

 

转载地址:http://rpzja.baihongyu.com/

你可能感兴趣的文章
.NET Core 2.1的重大缺陷延长了.NET Core 2.0的寿命
查看>>
我的第一次移动端页面制作 — 总结与思考
查看>>
Android 自定义 TabLayout
查看>>
js根据参数名获取url上的参数值
查看>>
emacs 进阶:了解命令
查看>>
App架构设计经验谈:接口的设计
查看>>
magento中的面包屑(breadcrumb)
查看>>
每周 Swift 社区问答 2016-01-13
查看>>
利用python进行识别相似图片(二)
查看>>
使用xmake编译工程
查看>>
Add Two Numbers
查看>>
Python数据结构——二叉搜索树的实现(上)
查看>>
Coding iPad 客户端开源——一个程序员的独白
查看>>
JS工厂模式
查看>>
Qt笔记:QDate、QTextCodec、QFileDialog以及Q_OBJECT
查看>>
【wordpress优化】压缩和使用静态缓存
查看>>
Fescar - RM 全局事务提交回滚流程
查看>>
新手上云
查看>>
程序员吐槽:看了上家写的一行注释,给气死了,可是又不敢删 ...
查看>>
工业物联网技术
查看>>