前言
- 公司APP使用七牛的播放器,各种崩溃,各种卡顿问题,一怒之下抽时间,看看有没苹果自带框架比较好解决播放问题,又不想引入其他库增加APP大小。发现AVPlayer,确实效率不错,基本满足业务需求,和同事确认了APP某部分功能播放文件只使用了mp4,m3u8,flv (按使用量先后排序)。AVPlayer不支持flv,所以mp4,m3u8使用AVPlayer,其他使用七牛播放器SDK.
注意
- 由于项目使用较多约束,但是AVPlayer默认渲染不能根据约束大小变化,所以需要重载UIView的一个方法,强制改变layerClass,作为AVPlayer的渲染层,以便动态适配约束。
.h 文件
#import <UIKit/UIKit.h>
@interface TTVAVPlayerView : UIView
@end
.m 文件
#import "TTVAVPlayerView.h"
#import <AVFoundation/AVFoundation.h>
@implementation TTVAVPlayerView
+ (Class)layerClass {
return [AVPlayerLayer class];
}
@end
AVPlayer
使用库
#import <AVFoundation/AVFoundation.h>
#import<MediaPlayer/MediaPlayer.h>
#import<CoreMedia/CoreMedia.h>
初始化
- (NSDictionary*)setupPlayer:(NSString*)url{
//防盗链headers
NSMutableDictionary * headers = [NSMutableDictionary dictionary];
[headers setObject:@"http://*.itouchtv.cn" forKey:@"Referer"];
NSURL*liveURL = [NSURL URLWithString:url];
AVAsset*liveAsset = [AVURLAsset URLAssetWithURL:liveURL options:@{@"AVURLAssetHTTPHeaderFieldsKey" : headers} ];
AVPlayerItem* playerItem = [AVPlayerItem playerItemWithAsset:liveAsset];
if (iOS9_OR_LATER) {
playerItem.canUseNetworkResourcesForLiveStreamingWhilePaused = true;
}
if (iOS10_OR_LATER) {
playerItem.preferredForwardBufferDuration = kPreferredForwardBufferDuration;
}
//播放器
AVPlayer*player = [AVPlayer playerWithPlayerItem:playerItem];
//渲染对象
TTVAVPlayerView* avPlayerView = [[TTVAVPlayerView alloc ]init];
AVPlayerLayer* playerLayer = (AVPlayerLayer *)avPlayerView.layer;
[playerLayer setPlayer:player];
return @{@"AVAsset":player,@"TTVAVPlayerView":avPlayerView};
}
状态监听
static NSString* const kStatusKeyName = @"status";
static NSString* const kLoadedTimeRangesKeyName = @"loadedTimeRanges";
static NSString* const kPlaybackBufferEmptyKeyName = @"playbackBufferEmpty";
static NSString* const kPlaybackLikelyToKeepUpKeyName = @"playbackLikelyToKeepUp";
- (void)addKVO:(AVPlayerItem*)playerItem{
if (playerItem == nil) return;
[playerItem addObserver:self forKeyPath:kStatusKeyName options:NSKeyValueObservingOptionNew context:nil];
[playerItem addObserver:self forKeyPath:kLoadedTimeRangesKeyName options:NSKeyValueObservingOptionNew context:nil];
[playerItem addObserver:self forKeyPath:kPlaybackBufferEmptyKeyName options:NSKeyValueObservingOptionNew context:nil];
[playerItem addObserver:self forKeyPath:kPlaybackLikelyToKeepUpKeyName options:NSKeyValueObservingOptionNew context:nil];
}
- (void)delKVO:(AVPlayerItem*)playerItem{
@try {
[playerItem removeObserver:self forKeyPath:kStatusKeyName context:nil];
[playerItem removeObserver:self forKeyPath:kLoadedTimeRangesKeyName context:nil];
[playerItem removeObserver:self forKeyPath:kPlaybackBufferEmptyKeyName context:nil];
[playerItem removeObserver:self forKeyPath:kPlaybackLikelyToKeepUpKeyName context:nil];
}
@catch (NSException *exception) {
DLog(@"多次删除了");
}
}
//监听获得消息
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
{
{
AVPlayerItem *playerItem = (AVPlayerItem *)object;
if ([keyPath isEqualToString:@"status"]) {
if ([playerItem status] == AVPlayerStatusReadyToPlay) {
self.playerStatus = TTVPlayerStatusReady;
//status 点进去看 有三种状态
CGFloat duration = playerItem.duration.value / playerItem.duration.timescale; //视频总时间
DLog(@"准备好播放了,总时间:%.2f", duration);//还可以获得播放的进度,这里可以给播放进度条赋值了
} else if ([playerItem status] == AVPlayerStatusFailed || [playerItem status] == AVPlayerStatusUnknown) {
[_player pause];
}
} else if ([keyPath isEqualToString:@"loadedTimeRanges"]) { //监听播放器的下载进度
NSArray *loadedTimeRanges = [playerItem loadedTimeRanges];
CMTimeRange timeRange = [loadedTimeRanges.firstObject CMTimeRangeValue];// 获取缓冲区域
float startSeconds = CMTimeGetSeconds(timeRange.start);
float durationSeconds = CMTimeGetSeconds(timeRange.duration);
NSTimeInterval timeInterval = startSeconds + durationSeconds;// 计算缓冲总进度
CMTime duration = playerItem.duration;
CGFloat totalDuration = CMTimeGetSeconds(duration);
CGFloat bufferdDuration = round(timeInterval);
DLog(@"下载进度:%.2f 当前下载时长:%.2f 总时长:%.2f", timeInterval / totalDuration,self.bufferedTime,totalDuration);
} else if ([keyPath isEqualToString:@"playbackBufferEmpty"]) { //监听播放器在缓冲数据的状态
DLog(@"缓冲不足暂停了");
} else if ([keyPath isEqualToString:@"playbackLikelyToKeepUp"]) {
DLog(@"缓冲达到可播放程度了");
//由于 AVPlayer 缓存不足就会自动暂停,所以缓存充足了需要手动播放,才能继续播放
[_player play];
}
}
}
- (void)addNotification{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[[NSNotificationCenter defaultCenter] removeObserver:self.player];
@weakify(self)
[[NSNotificationCenter defaultCenter]
addObserverForName:AVPlayerItemDidPlayToEndTimeNotification
object:nil queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification * _Nonnull note) {
@strongify(self)
DLog(@"播放完成");
}];
[[NSNotificationCenter defaultCenter]
addObserverForName:AVPlayerItemFailedToPlayToEndTimeNotification
object:nil queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification * _Nonnull note) {
@strongify(self)
DLog(@"播放出错");
}];
}
@property (nonatomic, strong) AVPlayer *player;
- (BOOL)playing {
if (self.playerItem.isPlaybackLikelyToKeepUp && self.player.rate == 1) {
return true;
}
return false;
}
- (NSUInteger)currentPlayingTime {
CMTime time = self.player.currentTime;
return CMTimeGetSeconds(time);
}
- (NSUInteger)totalPlayingTime {
CMTime time = self.player.currentItem.duration;
Float64 seconds = CMTimeGetSeconds(time);
return seconds;
}
- (void)seekTo:(NSUInteger)sec{
CMTime time = CMTimeMakeWithSeconds(sec, 1);
[self.player seekToTime:time];
}
- (void)play{
[self.player play];
}
- (void)resume{
[self seekTo:self.currentPlayingTime];
[self.player play];
}
- (void)pause{
[self.player pause];
}
- (void)stop{
[self.player pause];
self.player.muted = true;
self.player.rate = 0.0;
}