項目概述
- 以下項目是基於AVPlayer的實際運用,實現音頻播放、橫豎屏視頻切換播放、類似抖音的豎屏全屏播放效果。
項目地址:AVPlayerAudioVideo
如果文章和項目對你有幫助,還請給個Star⭐️,你的Star⭐️是我持續輸出的動力,謝謝啦😘
1.音頻播放器效果:

2.豎屏和橫屏的切換效果:

3.類似抖音豎屏全屏的效果:

豎屏全屏用UICollectionView實現,只創建了三個UICollectionViewCell視圖實例。無論有多少視頻需要播放,都是復用這三個UICollectionViewCell視圖實例,有效控制內存大小,避免內存加載過大、內存爆滿的情況。
UICollectionViewCell復用時有一個難點,就是記錄視頻當前已播放的位置,一開始用CMTime來保存發現不行,然后用CMTimeValue和CMTimeScale分別記錄也是存在各種問題,后來使用AVPlayerItem來保存已播放位置才徹底解決。
遇到的問題
- 播放時,揚聲器沒有聲音,插上耳機才有聲音。
原因是app揚聲器默認跟隨系統聲音模式,而手機調了靜音模式,因此揚聲器跟隨靜音模式,沒有聲音。
解決方式:設置Category,讓揚聲器不跟隨系統聲音模式。
//必須設置,否則揚聲器默認跟隨系統聲音模式
//AVAudioSessionCategoryPlayAndRecord模式能播放能錄音,該模式下聲音默認出口是聽筒(戴耳機才有聲音),切換到揚聲器通過以下方式
AVAudioSession *session = [AVAudioSession sharedInstance];
[session setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:AVAudioSessionCategoryOptionDefaultToSpeaker error:nil];
- 當反復快速移動滑塊時,滑塊會出現跳躍的現象。
這是由於移動滑塊時,會調用seekToTime:
,該方法用於搜索並播放指定視頻幀,執行時需要一點時間,不會立馬搜索並播放到指定視頻幀,此時addPeriodicTimeObserverForInterval:queue:usingBlock:
回調會設置滑塊的位置,出現手指已讓滑塊移動到某一位置,突然有一瞬間滑塊回到之前的位置,然后立馬又回到手指停留的位置。
解決方式:用seekToTime:toleranceBefore:toleranceAfter:completionHandler:
代替seekToTime:
,搜索並播放到指定視頻幀會有completionHandler的回調,獲得該回調后再設置滑塊的位置。具體處理細節詳見項目。
什么是AVPlayer
- AVPlayer存在於AVFoundation框架中,它是一個視頻播放器,用來播放視頻,但也可以用來播放音樂,播放音樂時不需要實現界面。換句話說,只要掌握了視頻播放,音頻播放自然就掌握了。
- AVPlayerItem:和媒體資源存在對應關系,管理媒體資源的信息和狀態。它的初始化需要URL或AVAsset。
- AVPlayer:播放器,控制資源的播放和暫停,AVPlayerItem是它的屬性,它的初始化需要URL或AVPlayerItem。
+ (instancetype)playerWithURL:(NSURL *)URL;
+ (instancetype)playerWithPlayerItem:(nullable AVPlayerItem *)item;
- AVPlayerLayer:播放器圖層,用於展示視頻內容,AVPlayer是它的屬性,它的初始化需要AVPlayer。如果是播放音頻,則不需要創建AVPlayerLayer。
+ (AVPlayerLayer *)playerLayerWithPlayer:(nullable AVPlayer *)player
- AVPlayerItem、AVPlayer、AVPlayerLayer三者關系,做個類比:
AVPlayerItem是光盤,AVPlayer是dvd影碟機,AVPlayerLayer是電視機屏幕。
視頻播放功能實現
1.通過網絡鏈接播放視頻資源
//url有中文時需要URL編碼
NSURL *url = [NSURL URLWithString:[self.str stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]];
AVPlayerItem *playerItem = [AVPlayerItem playerItemWithURL:url];
AVPlayer *player = [AVPlayer playerWithPlayerItem:playerItem];
AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:player];
playerLayer.frame = CGRectMake(0, 50, self.view.frame.size.width, 200);
[self.view.layer addSublayer:playerLayer];
2.常用操作
- 播放和暫停
[player play];
[player pause];
- 替換播放資源
[player replaceCurrentItemWithPlayerItem:videoItem];
3.監聽播放進度
- 使用
addPeriodicTimeObserverForInterval:queue:usingBlock:
監聽播放器的進度,常用於指示播放進度,獲取播放時長等信息。
1)Interval參數表示回調的間隔時間,block是每到一個間隔時間執行一次。
例如Interval傳CMTimeMake(1,10),1表示當前有1幀,10表示每秒10幀,1/10=0.1,即player在播放中時每0.1秒執行一次block,包括開始播放、暫停播放也會回調。
2)方法返回一個觀察者對象,當不再播放時,要移除該觀察者。
添加觀察者
self.timeObserve = [self.player addPeriodicTimeObserverForInterval:CMTimeMake(1, 10) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
float current = CMTimeGetSeconds(time);
float total = CMTimeGetSeconds(weakSelf.player.currentItem.duration);
weakSelf.playTime = [NSString stringWithFormat:@"%.f",current];
weakSelf.playDuration = [NSString stringWithFormat:@"%.2f",total];
if (weakSelf.slider.isTracking == NO) {
weakSelf.slider.value = current / total;
}
}];
移除觀察者
if (self.timeObserve) {
[self.player removeTimeObserver:self.timeObserve];
}
4.移動滑塊播放指定時刻的視頻幀
- 使用
seekToTime:
或seekToTime:completionHandler:
或seekToTime:toleranceBefore:toleranceAfter:completionHandler:
播放指定時刻的視頻內容。
精確搜索某一時刻的視頻幀可能會導致額外的解碼延遲,seekToTime:
默認不是精確搜索,而是有一個小范圍的誤差。
seekToTime:toleranceBefore:toleranceAfter:completionHandler:
的搜索的范圍是[time-toleranceBefore, time+toleranceAfter],當toleranceBefore和toleranceAfter設置為kCMTimePositiveInfinity時,執行效果等同於seekToTime:completionHandler:
[self.player seekToTime:goalTime toleranceBefore:kCMTimePositiveInfinity toleranceAfter:kCMTimePositiveInfinity completionHandler:^(BOOL finished) {
}];