源碼下載地址: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的功能。
好了 ,本片文章就到此為止了。由於個人能力有限,如有錯誤之處,請幫忙給與指出,不勝感謝啊 。