zoukankan      html  css  js  c++  java
  • 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的功能。

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

     

  • 相关阅读:
    4.19Java.util.Arrays类
    4.19Java数组的拷贝
    Inverse matrix of 4x4 matrix
    自言自语
    病了两天
    当年3ds max盗版光碟上的广告
    头晕的厉害
    复习了一下STL容器的知识
    一个简单的能处理MIPMAP的类
    空间变换代码,相当简洁优美
  • 原文地址:https://www.cnblogs.com/machao/p/5669613.html
Copyright © 2011-2022 走看看