ZFPlayer 源码解读

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