源码下载地址: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

使用起来大概是这么使用

内部的实现是这样

 #import "THPlayerController.h"
#import "THThumbnail.h"
#import <AVFoundation/AVFoundation.h>
#import "THTransport.h"
#import "THPlayerView.h"
#import "AVAsset+THAdditions.h"
#import "UIAlertView+THAdditions.h"
#import "THNotifications.h" // AVPlayerItem's status property
#define STATUS_KEYPATH @"status" // Refresh interval for timed observations of AVPlayer
#define REFRESH_INTERVAL 0.5f // Define this constant for the key-value observation context.
static const NSString *PlayerItemStatusContext; @interface THPlayerController () <THTransportDelegate> @property (strong, nonatomic) AVAsset *asset;
@property (strong, nonatomic) AVPlayerItem *playerItem;
@property (strong, nonatomic) AVPlayer *player;
@property (strong, nonatomic) THPlayerView *playerView; @property (weak, nonatomic) id <THTransport> transport; @property (strong, nonatomic) id timeObserver;
@property (strong, nonatomic) id itemEndObserver;
@property (assign, nonatomic) float lastPlaybackRate; @property (strong, nonatomic) AVAssetImageGenerator *imageGenerator; @end @implementation THPlayerController #pragma mark - Setup - (id)initWithURL:(NSURL *)assetURL {
self = [super init];
if (self) {
_asset = [AVAsset assetWithURL:assetURL]; //
[self prepareToPlay];
}
return self;
} - (void)prepareToPlay {
NSArray *keys = @[
@"tracks",
@"duration",
@"commonMetadata",
@"availableMediaCharacteristicsWithMediaSelectionOptions"
];
self.playerItem = [AVPlayerItem playerItemWithAsset:self.asset //
automaticallyLoadedAssetKeys:keys]; [self.playerItem addObserver:self //
forKeyPath:STATUS_KEYPATH
options:
context:&PlayerItemStatusContext]; self.player = [AVPlayer playerWithPlayerItem:self.playerItem]; // self.playerView = [[THPlayerView alloc] initWithPlayer:self.player]; //
self.transport = self.playerView.transport;
self.transport.delegate = self;
} - (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context { if (context == &PlayerItemStatusContext) { dispatch_async(dispatch_get_main_queue(), ^{ // [self.playerItem removeObserver:self forKeyPath:STATUS_KEYPATH]; if (self.playerItem.status == AVPlayerItemStatusReadyToPlay) { // Set up time observers. //
[self addPlayerItemTimeObserver];
[self addItemEndObserverForPlayerItem]; CMTime duration = self.playerItem.duration; // Synchronize the time display //
[self.transport setCurrentTime:CMTimeGetSeconds(kCMTimeZero)
duration:CMTimeGetSeconds(duration)]; // Set the video title.
[self.transport setTitle:self.asset.title]; // [self.player play]; // [self loadMediaOptions];
[self generateThumbnails]; } else {
[UIAlertView showAlertWithTitle:@"Error"
message:@"Failed to load video"];
}
});
}
} - (void)loadMediaOptions {
NSString *mc = AVMediaCharacteristicLegible; //
AVMediaSelectionGroup *group =
[self.asset mediaSelectionGroupForMediaCharacteristic:mc]; //
if (group) {
NSMutableArray *subtitles = [NSMutableArray array]; //
for (AVMediaSelectionOption *option in group.options) {
[subtitles addObject:option.displayName];
}
[self.transport setSubtitles:subtitles]; //
} else {
[self.transport setSubtitles:nil];
}
} - (void)subtitleSelected:(NSString *)subtitle {
NSString *mc = AVMediaCharacteristicLegible;
AVMediaSelectionGroup *group =
[self.asset mediaSelectionGroupForMediaCharacteristic:mc]; //
BOOL selected = NO;
for (AVMediaSelectionOption *option in group.options) {
if ([option.displayName isEqualToString:subtitle]) {
[self.playerItem selectMediaOption:option //
inMediaSelectionGroup:group];
selected = YES;
}
}
if (!selected) {
[self.playerItem selectMediaOption:nil //
inMediaSelectionGroup:group];
}
} #pragma mark - Time Observers - (void)addPlayerItemTimeObserver { // Create 0.5 second refresh interval - REFRESH_INTERVAL == 0.5
CMTime interval =
CMTimeMakeWithSeconds(REFRESH_INTERVAL, NSEC_PER_SEC); // 1 // Main dispatch queue
dispatch_queue_t queue = dispatch_get_main_queue(); // 2 // Create callback block for time observer
__weak THPlayerController *weakSelf = self; //
void (^callback)(CMTime time) = ^(CMTime time) {
NSTimeInterval currentTime = CMTimeGetSeconds(time);
NSTimeInterval duration = CMTimeGetSeconds(weakSelf.playerItem.duration);
[weakSelf.transport setCurrentTime:currentTime duration:duration]; //
}; // Add observer and store pointer for future use
self.timeObserver = //
[self.player addPeriodicTimeObserverForInterval:interval
queue:queue
usingBlock:callback];
} - (void)addItemEndObserverForPlayerItem { NSString *name = AVPlayerItemDidPlayToEndTimeNotification; NSOperationQueue *queue = [NSOperationQueue mainQueue]; __weak THPlayerController *weakSelf = self; //
void (^callback)(NSNotification *note) = ^(NSNotification *notification) {
[weakSelf.player seekToTime:kCMTimeZero //
completionHandler:^(BOOL finished) {
[weakSelf.transport playbackComplete]; //
}];
}; self.itemEndObserver = //
[[NSNotificationCenter defaultCenter] addObserverForName:name
object:self.playerItem
queue:queue
usingBlock:callback];
} #pragma mark - THTransportDelegate Methods - (void)play {
[self.player play];
} - (void)pause {
self.lastPlaybackRate = self.player.rate;
[self.player pause];
} - (void)stop {
[self.player setRate:0.0f];
[self.transport playbackComplete];
} - (void)jumpedToTime:(NSTimeInterval)time {
[self.player seekToTime:CMTimeMakeWithSeconds(time, NSEC_PER_SEC)];
} - (void)scrubbingDidStart { //
self.lastPlaybackRate = self.player.rate;
[self.player pause];
[self.player removeTimeObserver:self.timeObserver];
} - (void)scrubbedToTime:(NSTimeInterval)time { //
[self.playerItem cancelPendingSeeks];
[self.player seekToTime:CMTimeMakeWithSeconds(time, NSEC_PER_SEC) toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero];
} - (void)scrubbingDidEnd { //
[self addPlayerItemTimeObserver];
if (self.lastPlaybackRate > 0.0f) {
[self.player play];
}
} #pragma mark - Thumbnail Generation - (void)generateThumbnails { self.imageGenerator = //
[AVAssetImageGenerator assetImageGeneratorWithAsset:self.asset]; // Generate the @2x equivalent
self.imageGenerator.maximumSize = CGSizeMake(200.0f, 0.0f); // CMTime duration = self.asset.duration; NSMutableArray *times = [NSMutableArray array]; //
CMTimeValue increment = duration.value / ;
CMTimeValue currentValue = 2.0 * duration.timescale;
while (currentValue <= duration.value) {
CMTime time = CMTimeMake(currentValue, duration.timescale);
[times addObject:[NSValue valueWithCMTime:time]];
currentValue += increment;
} __block NSUInteger imageCount = times.count; //
__block NSMutableArray *images = [NSMutableArray array]; AVAssetImageGeneratorCompletionHandler handler; // handler = ^(CMTime requestedTime,
CGImageRef imageRef,
CMTime actualTime,
AVAssetImageGeneratorResult result,
NSError *error) { if (result == AVAssetImageGeneratorSucceeded) { //
UIImage *image = [UIImage imageWithCGImage:imageRef];
id thumbnail =
[THThumbnail thumbnailWithImage:image time:actualTime];
[images addObject:thumbnail];
} else {
NSLog(@"Error: %@", [error localizedDescription]);
} // If the decremented image count is at 0, we're all done.
if (--imageCount == ) { //
dispatch_async(dispatch_get_main_queue(), ^{
NSString *name = THThumbnailsGeneratedNotification;
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc postNotificationName:name object:images];
});
}
}; [self.imageGenerator generateCGImagesAsynchronouslyForTimes:times //
completionHandler:handler]; } #pragma mark - Housekeeping - (UIView *)view {
return self.playerView;
} - (void)dealloc {
if (self.itemEndObserver) { //
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc removeObserver:self.itemEndObserver
name:AVPlayerItemDidPlayToEndTimeNotification
object:self.player.currentItem];
self.itemEndObserver = nil;
}
} @end

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

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

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

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

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

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

ZFPlayer 源码解读的更多相关文章

  1. SDWebImage源码解读之SDWebImageDownloaderOperation

    第七篇 前言 本篇文章主要讲解下载操作的相关知识,SDWebImageDownloaderOperation的主要任务是把一张图片从服务器下载到内存中.下载数据并不难,如何对下载这一系列的任务进行设计 ...

  2. SDWebImage源码解读 之 NSData+ImageContentType

    第一篇 前言 从今天开始,我将开启一段源码解读的旅途了.在这里先暂时不透露具体解读的源码到底是哪些?因为也可能随着解读的进行会更改计划.但能够肯定的是,这一系列之中肯定会有Swift版本的代码. 说说 ...

  3. SDWebImage源码解读 之 UIImage+GIF

    第二篇 前言 本篇是和GIF相关的一个UIImage的分类.主要提供了三个方法: + (UIImage *)sd_animatedGIFNamed:(NSString *)name ----- 根据名 ...

  4. SDWebImage源码解读 之 SDWebImageCompat

    第三篇 前言 本篇主要解读SDWebImage的配置文件.正如compat的定义,该配置文件主要是兼容Apple的其他设备.也许我们真实的开发平台只有一个,但考虑各个平台的兼容性,对于框架有着很重要的 ...

  5. SDWebImage源码解读_之SDWebImageDecoder

    第四篇 前言 首先,我们要弄明白一个问题? 为什么要对UIImage进行解码呢?难道不能直接使用吗? 其实不解码也是可以使用的,假如说我们通过imageNamed:来加载image,系统默认会在主线程 ...

  6. SDWebImage源码解读之SDWebImageCache(上)

    第五篇 前言 本篇主要讲解图片缓存类的知识,虽然只涉及了图片方面的缓存的设计,但思想同样适用于别的方面的设计.在架构上来说,缓存算是存储设计的一部分.我们把各种不同的存储内容按照功能进行切割后,图片缓 ...

  7. SDWebImage源码解读之SDWebImageCache(下)

    第六篇 前言 我们在SDWebImageCache(上)中了解了这个缓存类大概的功能是什么?那么接下来就要看看这些功能是如何实现的? 再次强调,不管是图片的缓存还是其他各种不同形式的缓存,在原理上都极 ...

  8. AFNetworking 3.0 源码解读 总结(干货)(下)

    承接上一篇AFNetworking 3.0 源码解读 总结(干货)(上) 21.网络服务类型NSURLRequestNetworkServiceType 示例代码: typedef NS_ENUM(N ...

  9. AFNetworking 3.0 源码解读 总结(干货)(上)

    养成记笔记的习惯,对于一个软件工程师来说,我觉得很重要.记得在知乎上看到过一个问题,说是人类最大的缺点是什么?我个人觉得记忆算是一个缺点.它就像时间一样,会自己消散. 前言 终于写完了 AFNetwo ...

随机推荐

  1. NodeJs之child_process

    一.child_process child_process是NodeJs的重要模块.帮助我们创建多进程任务,更好的利用了计算机的多核性能. 当然也支持线程间的通信. 二.child_process的几 ...

  2. word-wrap ,word-break 和white-space 的联系

    在工作中我遇到一个问题,其实功能也不复杂,就是上面有个textarea标签 ,里面输入内容,下面有个显示效果 ,有个条件就是 上面输入的什么格式(比如换行等等),下面显示的也是 什么格式.如下图: 这 ...

  3. HTML 事件(四) 模拟事件操作

    本篇主要介绍HTML DOM中事件的模拟操作. 其他事件文章 1. HTML 事件(一) 事件的介绍 2. HTML 事件(二) 事件的注册与注销 3. HTML 事件(三) 事件流与事件委托 4.  ...

  4. android 使用Tabhost 发生could not create tab content because could not find view with id 错误

    使用Tabhost的时候经常报:could not create tab content because could not find view with id 错误. 总结一下发生错误的原因,一般的 ...

  5. 前端常用的WindowsCMD命令

    前面的话   在网上找了一些关于命令提示符CMD的资料,但是很多资料都是把所有的功能罗列出来,大部分都不会用到.所以,自己把常用的CMD命令总结如下,方便查阅 操作类 help 列出所有支持的指令及说 ...

  6. 套用JQuery EasyUI列表显示数据、分页、查询

    声明,本博客从csdn搬到cnblogs博客园了,以前的csdn不再更新,朋友们可以到这儿来找我的文章,更多的文章会发表,谢谢关注! 有时候闲的无聊,看到extjs那么肥大,真想把自己的项目改了,最近 ...

  7. 初识npm

    一.npm简介: npm全称为Node Package Manager,是一个基于Node.js的包管理器,也是整个Node.js社区最流行.支持的第三方模块最多的包管理器. npm的初衷:JavaS ...

  8. MySQL常用命令

    数据库登陆命令: mysql -uroot -p 2.提示输入密码: 3.登陆成功: 4.数据库修改相关命令: 修改数据库的编码格式: 语法格式为:ALTER {DATABASE|SCHEMA}  [ ...

  9. SQL 约束

    先用设计器创建约束.再用代码创建约束.数据库约束是为了保证数据的完整性(正确性)而实现的一套机制见文件Employee.sql非空约束(选择复选框)主键约束(PK) primary key const ...

  10. Linux环境变量设置

    修改环境变量PATH 最近为root添加一个环境变量发现sudo su进去没有变化所以总结了一下所有设置环境变量的方法: 查看PATH:echo $PATH 直接在命令行修改,就可以使用,但是只有在当 ...