ZFPlayer 源码解读

article/2025/8/28 3:13:24

源码下载地址: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的功能。

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

 

转载于:https://www.cnblogs.com/machao/p/5669613.html


http://chatgpt.dhexx.cn/article/Vhn9FHhF.shtml

相关文章

iOS开发-ZFPlayer的简单使用 播放单个网络视频

iOS开发-ZFPlayer的简单使用 播放单个网络视频 前言开发准备代码注意 前言 关于ZFPlayer播放单个网络视频案例&#xff0c;它的网络列表视频案例在gitHub上面很多。 开发准备 podfile导入 pod ZFPlayer, ~> 3.3.3 #, ~> 3.2.17 # 视频播放 pod ZFPlayer/ControlView p…

关于ZFPlayer集成播放 rtmp 视频流的总结

最近公司项目需要能够播放 rtmp 格式的直播流地址的播放器,安卓那边是买了人家的三方库,EasyPlayer,但是不想再买一个 ios 的了所以需要自己去找免费的合适的播放器去集成,终于在 github 上找到一个合适的了,那就是 强大的IJKPlayer 播放器;但在集成的时候出现了一些问题,朋友推…

ZFPlayer 播放器调用addPlayerViewToSmallFloatView开小窗播放样式错误以及无法关闭处理

ZFPLayer 是一款强大开源的视频播放器&#xff0c;在结合列表播放滑动超出当前视频范围调用 addPlayerViewToSmallFloatView开启小窗播放(类似画中画&#xff0c;但没有其强大和友好的交互)布局错误并且右上角的关闭按钮也没法关闭&#xff0c;错乱参考图如下&#xff1a; 1、样…

ios html zfplayer,【iOS】ZFPlayer源码解读中

前言 本篇继ZFPlayer源码解读基础之上&#xff0c;主要解析说明控制层与播放器&#xff0c;因为在上篇文章至现在并未提及丝毫关于这两个类业务的实现。 首先说下这两个类各自的职责。 控制层&#xff1a;主要负责响应与用户之间的交互&#xff0c;如手势控制的播放&#xff0c…

ZFPlayer iOS16 系统横屏全屏问题处理

iOS16 以后 通过原始横屏的方法已经不好使了&#xff0c;需要在此基础上添加 setNeedsUpdateOfSupportedInterfaceOrientations 方法的调用&#xff0c;具体API 描述如下&#xff1a; 回到主题&#xff0c;在使用ZFPlayer 视频库全屏播放时iOS16系统下的处理参考如下&#xff…

iOS使用ZFPlayer 实现视频播放

文章目录 ZFPlayer 视频播放使用1 创建 ZFAVPlayerManager 对象2创建containerView, 也就是视频视图的父视图3 创建 controllView4 创建 ZFPlayerController播放视频判断视频的横竖 ZFPlayer 视频播放使用 1 创建 ZFAVPlayerManager 对象 ZFAVPlayerManager *manager [[ZFAVP…

配置分离式LANP源代码编译实例

一、php编译 配置apache-2.4.9以fpm方式的php-5.4.26&#xff08;php服务器IP为172.16.3.30&#xff09; 1、编译php #tar xf php-5.4.26.tar.bz2 #cd php-5.4.26 # ./configure --prefix/usr/local/php --with-mysql/usr/l…

Centos使用Docker搭建Lanp环境

前言 LAMP 指的Linux(操作系统)、ApacheHTTP服务器&#xff0c;MySQL(有时也指MariaDB&#xff0c;数据库软件) 和 PHP(有时也是指Perl或Python)的第一个字母&#xff0c;集成环境方便&#xff0c;一般用来建立web应用平台 Docker 是一个开源的应用容器引擎&#xff0c;在doc…

LANP+KEEPALIVED集群(一)

lanpkeepalived集群 1、nginx模块与工作原理 (1)结构上分 基础模块&#xff1a;HTTP Access模块、HTTP FastCGI模块、HTTP Proxy模块和HTTP Rewrite 核心模块&#xff1a;HTTP模块、EVENT模块和MAIL模块 第三方模块&#xff1a;HTTP Upstream Request Hase模块、Notice模块和HT…

LANP+KEEPALIVED集群(二)

LANPKEEPALIVED集群&#xff08;二&#xff09; #基于不同域名 server { listen 80; server_name nginx.postfix.local; charset utf-8; access_log logs/domain.log main; location / { root html/domain; index domain.html; } error_page 500 502 503 504 /5…

LWIP网络协议基础

1、LWIP有3种编程接口&#xff1a;RAW&#xff08;裸机跑&#xff0c;不带操作系统&#xff09;、NETCONN和SOCKET(要带操作系统) 例程使用的是ucos小型操作系统&#xff0c;ucosii任务数限制了最大只能有255个任务&#xff08;其中0—空闲任务和254、255—系统任务不能用&…

LAN IP,WAN IP和Global IP

LAN IP&#xff0c;WAN IP和Global IP是与网络通信相关的三种IP地址&#xff0c;下面是它们的解释&#xff1a; LAN IP 局域网IP地址&#xff08;LAN IP&#xff09;&#xff0c;又称私有IP地址&#xff0c;是指对于一个私有网络环境内的主机所使用的IP地址。这些IP地址属于私…

一文读懂ssh,tomcat,LANP,LNMP,ftp,dns等常见的环境配置(运维工程师必看)

文章目录 一.网络服务的概述1.网络服务是什么2.网络服务有哪些&#xff08;进行简单的梳理&#xff09;3.网络服务学习建议 二.网络服务基础1.CentOS6与7的区别2.常见网络端口以及/etc/service文件3.网关和路由,主机名&#xff08;路由选择&#xff0c;网关&#xff0c;NAT解释…

ubuntu 上搭建lanp环境

2019独角兽企业重金招聘Python工程师标准>>> 1.安装tasksel sudo apt-get install tasksel 使用tasksel 时只需 sudo tasksel 2.安装lamp sudo tasksel instal lamp-server 打开浏览器输入127.0.0.1 可以看到apache首页 切换到/var/www/html(默认目录) 目录下新…

LANP环境编译设置

1.下载nginx-1.10.2 wget -O nginx-1.10.2.tar.gz http://nginx.org/download/nginx-1.10.2.tar.gz 2.安装nginx-1.10.2.tar.gz [rootxuegod64 ~]# yum in stall -y gcc gcc-c autoconf automake zlib zlib-devel openssl openssl-devel pcre-devel //zlib&#xff1a;给Ngin…

LANP平台搭建

【需要理解--lamp调用过程】 apache(libphp5.so) -> index.php(mysql.so)-> mysql ####################################################### 【配置yum】 mount /dev/cdrom /media vim /etc/yum.repos.d/yum.repo [base] namebase baseurlfile:///media/Server gpgch…

CentOS 7.4 YUM 搭建LANP环境+WordPress

CentOS YUM 搭建LANP环境Wordpress LAMP是什么呢&#xff1f; 其实就是一系列服务的简称 LAMP&#xff1a;LLinux&#xff0c;AApache&#xff0c;MMariadb/MySQL&#xff0c;PPHP LNMP&#xff1a;LLinux&#xff0c;NNginx&#xff0c;MMariadb/MySQL&#xff0c;PPHP LA…

lanp+nginx实现动静分离

因为apache处理动态页面能力比较高&#xff0c;nginx处理静态页面能力比较高&#xff0c;所以做动静分离来提高页面的访问速度。 系统环境&#xff1a; CentOS Linux release 7.9.2009 (Core) 脚本编译安装httpd-2.4.53 测试httpd服务 执行脚本安装mysql 验证mysql 执行脚本…

LANP架构搭建

安装Apache 解压apache安装包&#xff08;httpd-2.4.17.tar.gz&#xff09;到 /usr/src/目录下面 tar -zxvf /root/httpd-2.4.17.tar.gz -C /usr/src/ 安装httpd所需要的依赖包 yum -y install zlib* openssl* apr* pcre-devel openssl* 进入httpd目录&#xff0c;安装httpd所需…

LANP环境搭建(yum安装)

LAMP环境搭建 LAMP是指一组通常一起使用来运行动态网站或者服务器的自由软件名称首字母缩写, 在很多的生产条件下&#xff0c;都需要LAMP环境来实现。今天小白就教大家如何搭建一个LAMP环境。 环境需求 一台win10的主机&#xff08;192.168.150.110&#xff09;&#xff0c;…