AVPlayer
是一個強大的視頻播放器,可以播放多種格式的視頻厨埋,缺點是沒有控制界面邪媳,需要自己去實現(xiàn)。
效果圖
先看下它的結(jié)構(gòu)
首先初始化播放器揽咕,設(shè)置播放URL悲酷。
self.avPlayerView = [[XYAVPlayerView alloc] initWithFrame:CGRectMake(0, 0, ScreenWidth, VIDEO_HEIGHT)];
self.avPlayerView.videoUrl = m3u;
[self.view addSubview:self.avPlayerView];
初始化方法套菜,添加一個視頻控制器亲善。
- (instancetype)initWithFrame:(CGRect)frame
{
if ([super initWithFrame:frame]) {
self.backgroundColor = [UIColor blackColor];
[self addSubview:self.playControlView];
}
return self;
}
設(shè)置視頻URL
- (void)setVideoUrl:(NSString *)videoUrl
{
_videoUrl = videoUrl.copy;
[self createAVPlayer];
}
初始化AVPlayer
,給_playControlView
引用AVPlayer
逗柴,方便進行控制蛹头, [_playControlView addObserver];
和[_playControlView playerTimerAction];
會在后面說明。
- (void)createAVPlayer
{
NSURL *url = [NSURL URLWithString:self.videoUrl];
if (!url) {
return;
}
/**
* AVPlayer
*/
_avPlayerItem = [AVPlayerItem playerItemWithURL:url];
_avPlayer = [AVPlayer playerWithPlayerItem:_avPlayerItem];
_avPlayerLayer = [AVPlayerLayer playerLayerWithPlayer:_avPlayer];
_avPlayerLayer.frame = self.bounds;
_avPlayerLayer.videoGravity = AVLayerVideoGravityResizeAspect;//
[self.layer addSublayer:_avPlayerLayer];
[_avPlayer play];
self.autoresizesSubviews = YES; //子視圖Size自適應(yīng)
_playControlView.avPlayer = self.avPlayer;
_playControlView.avPlayerItem = self.avPlayerItem;
_playControlView.avPlayerLayer = self.avPlayerLayer;
[_playControlView addObserver];
[_playControlView playerTimerAction];
[self bringSubviewToFront:self.playControlView];
}
到這里只是創(chuàng)建了一個View,上面加載了一個AVPlayer渣蜗,一個視頻控制器視圖屠尊。
視頻控制器代碼:
我用的xib
創(chuàng)建的View,所以初始化方法是awakeFromNib
- (void)awakeFromNib
{
[super awakeFromNib];
[self configureVolume];
UITapGestureRecognizer * screenTap = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(screenTap)];
[self addGestureRecognizer:screenTap];
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(panGestureRecognizerAction:)];
pan.delegate = self;
[_adjustView addGestureRecognizer:pan];
[self.slider setThumbImage:[UIImage imageNamed:@"verify_code_button"] forState:UIControlStateNormal];
// slider開始滑動事件
[_slider addTarget:self action:@selector(progressSliderTouchBegan:) forControlEvents:UIControlEventTouchDown];
// slider滑動中事件
[_slider addTarget:self action:@selector(progressSliderValueChanged:) forControlEvents:UIControlEventValueChanged];
// slider結(jié)束滑動事件
[_slider addTarget:self action:@selector(progressSliderTouchEnded:) forControlEvents:UIControlEventTouchUpInside | UIControlEventTouchCancel | UIControlEventTouchUpOutside];
// slider 添加點擊手勢
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(sliderTap:)];
[_slider addGestureRecognizer:tap];
[self hiddenViews];
}
/**
* 獲取系統(tǒng)音量
*/
- (void)configureVolume {
MPVolumeView *volumeView = [[MPVolumeView alloc] init];
_volumeSlider = nil;
for (UIView *view in [volumeView subviews]){
if ([view.class.description isEqualToString:@"MPVolumeSlider"]){
_volumeSlider = (UISlider *)view;
break;
}
}
// 使用這個category的應(yīng)用不會隨著手機靜音鍵打開而靜音耕拷,可在手機靜音下播放聲音
NSError *setCategoryError = nil;
BOOL success = [[AVAudioSession sharedInstance]
setCategory: AVAudioSessionCategoryPlayback
error: &setCategoryError];
if (!success) { /* handle the error in setCategoryError */ }
}
第一個手勢screenTap
讼昆,是控制下方控制條的顯示與隱藏的
- (void)screenTap {
self.controlStripView.hidden = !self.controlStripView.hidden;
}
第二個手勢則是用來控制快進、快退骚烧、音量浸赫、亮度調(diào)節(jié)的
- (void)panGestureRecognizerAction: (UIPanGestureRecognizer *)sender {
//根據(jù)在view上Pan的位置,確定是調(diào)音量還是亮度
CGPoint locationPoint = [sender locationInView:self];
CGPoint veloctyPoint = [sender velocityInView:self];
switch (sender.state) {
case UIGestureRecognizerStateBegan:{
// 使用絕對值來判斷移動的方向
CGFloat x = fabs(veloctyPoint.x);
CGFloat y = fabs(veloctyPoint.y);
if (x > y) { //水平移動
[self showViews];
[self playerPause];
self.pandirection = PanDirectionHorizontalMoved;
}else { //垂直移動
self.pandirection = PanDirectionVerticalMoved;
}
break;
}
case UIGestureRecognizerStateChanged:{
switch (_pandirection) {
case PanDirectionHorizontalMoved:{
[self horizontalMoved:veloctyPoint.x];
break;
}
case PanDirectionVerticalMoved:{
if (locationPoint.x > self.bounds.size.width / 2) {//音量調(diào)節(jié)-右側(cè)
[self verticalMovedForVolume:veloctyPoint.y];
}else {//亮度調(diào)節(jié)-左側(cè)
[self verticalMovedForBrightness:veloctyPoint.y];
}
break;
}
}
break;
}
case UIGestureRecognizerStateEnded:{
switch (_pandirection) {
case PanDirectionHorizontalMoved:{
[self hiddenViews];
[self playerPlay];
break;
}
case PanDirectionVerticalMoved:{
break;
}
}
break;
}
default:break;
}
}
對于slider
的方法赃绊,在開始手勢的時候暫停視頻播放既峡,顯示時間label
- (void)progressSliderTouchBegan: (UISlider *)sender {
self.changeTimeLabel.hidden = NO;
[self playerPause];
NSLog(@" --- began touch");
}
在開始手勢的時候seek到slider
對應(yīng)時間的時間點,然后開始視頻播放碧查,隱藏時間label
- (void)progressSliderTouchEnded: (UISlider *)sender {
CMTime durationTime = self.avPlayerItem.duration;
NSTimeInterval currentTime = CMTimeGetSeconds(durationTime) * sender.value;
[self seekWithTime:currentTime];
self.changeTimeLabel.hidden = YES;
[self playerPlay];
NSLog(@" ---- end touch");
}
在 slider
滑動的時候运敢,只是改變label
上顯示的時間
- (void)progressSliderValueChanged: (UISlider *)sender {
CGFloat currentTime = sender.value * CMTimeGetSeconds(self.avPlayerItem.duration);
NSString *tempCurrentTime = [self timeFormatterForServiceWithTimeStamp:currentTime];
//當前播放時間
self.currentTimeLabel.text = tempCurrentTime;
//屏幕中間時間
self.changeTimeLabel.text = tempCurrentTime;
CGFloat sliderProgress = sender.value / sender.maximumValue;
if (self.progressView.progress < sliderProgress) {
self.progressView.progress = sender.value / sender.maximumValue;
}
NSLog(@" --- event touch %f",sender.value);
}
slider
的單擊方法則是直接seek到對應(yīng)時間點,AVPlayer
會自動處理的忠售。
/**
* Slider Tap
*/
- (void)sliderTap: (UITapGestureRecognizer *)sender {
if ([sender.view isKindOfClass:[UISlider class]]) {
UISlider *slider = (UISlider *)sender.view;
CGPoint point = [sender locationInView:slider];
CGFloat length = slider.frame.size.width;
CGFloat tempValue = point.x / length;
NSTimeInterval currentTime = CMTimeGetSeconds(self.avPlayerItem.duration) * tempValue;
CGFloat progress = currentTime/CMTimeGetSeconds(self.avPlayerItem.duration);
if (progress > slider.value) {
self.progressView.progress = progress;
}
[self seekWithTime:currentTime];
}
}
前面用的 addObserver
方法传惠,則是給播放器添加觀察者,用來檢測播放器狀態(tài)稻扬,還有APP的狀態(tài)涉枫。
- (void)addObserver
{
//監(jiān)控狀態(tài)屬性,注意AVPlayer也有一個status屬性腐螟,通過監(jiān)控它的status也可以獲得播放狀態(tài)
[self.avPlayerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
//監(jiān)控網(wǎng)絡(luò)加載情況屬性
[self.avPlayerItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];
/**
* 進入后臺 暫停播放
*
*/
[kNotificationCenter addObserver:self selector:@selector(applicationDidEnterBackground_Notification) name:ApplicationDidEnterBackground_Notification object:nil];
/**
* 進入活躍狀態(tài) 繼續(xù)播放
*
*/
[kNotificationCenter addObserver:self selector:@selector(applicationDidBecomeActive_Notification) name:ApplicationDidBecomeActive_Notification object:nil];
[kNotificationCenter addObserver:self selector:@selector(playerReset) name:AVPlayerItemDidPlayToEndTimeNotification object:nil];
}
/** * 通過KVO監(jiān)控播放器狀態(tài) *
* @param keyPath 監(jiān)控屬性
* @param object 監(jiān)視器
* @param change 狀態(tài)改變
* @param context 上下文 */
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
{
AVPlayerItem *playerItem=object;
if ([keyPath isEqualToString:@"status"]) {
AVPlayerStatus status= [[change objectForKey:@"new"] intValue];
if(status==AVPlayerStatusReadyToPlay){
//總時長
self.allTimeLabel.text = [self timeFormatterForServiceWithTimeStamp:CMTimeGetSeconds(playerItem.duration)];
self.currentTimeLabel.text = @"00:00:00";
NSLog(@"正在播放...愿汰,視頻總長度:%.2f",CMTimeGetSeconds(playerItem.duration));
}
}
else if([keyPath isEqualToString:@"loadedTimeRanges"])
{
NSArray *array=playerItem.loadedTimeRanges;
CMTimeRange timeRange = [array.firstObject CMTimeRangeValue];//本次緩沖時間范圍
float startSeconds = CMTimeGetSeconds(timeRange.start);
float durationSeconds = CMTimeGetSeconds(timeRange.duration);
NSTimeInterval totalBuffer = startSeconds + durationSeconds;//緩沖總長度
NSLog(@"共緩沖:%.2f",totalBuffer);
double ableScale = totalBuffer / CMTimeGetSeconds(playerItem.duration);
if (ableScale <= 1) {
self.progressView.progress = ableScale;
}
}
}
playerTimerAction
方法則是制定每秒進行一次返回,返回當前播放進度乐纸。
- (void)playerTimerAction
{
__weak XYAVControlView * weakSelf = self;
[self.avPlayer addPeriodicTimeObserverForInterval:CMTimeMake(1.0, 1.0) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
float current=CMTimeGetSeconds(time);
if (current) {
//當前播放進度
weakSelf.currentTimeLabel.text = [weakSelf timeFormatterForServiceWithTimeStamp:CMTimeGetSeconds(weakSelf.avPlayerItem.currentTime)];
//滑塊進度
double totalTempTime = CMTimeGetSeconds(weakSelf.avPlayerItem.duration);
double scale = CMTimeGetSeconds(weakSelf.avPlayerItem.currentTime) / totalTempTime;
weakSelf.slider.value = scale;
}
}];
}
最后在 ViewController
里面監(jiān)控屏幕方向的變化衬廷,來處理全屏效果
[kNotificationCenter addObserver:self
selector:@selector(onDeviceOrientationChange)
name:UIDeviceOrientationDidChangeNotification
object:nil];
/**
* 屏幕方向發(fā)生變化會調(diào)用這里
*/
- (void)onDeviceOrientationChange
{
UIDeviceOrientation orientation = [UIDevice currentDevice].orientation;
UIInterfaceOrientation interfaceOrientation = (UIInterfaceOrientation)orientation;
switch (interfaceOrientation) {
case UIInterfaceOrientationPortraitUpsideDown:
case UIInterfaceOrientationPortrait:{
self.view.frame = (CGRect){0,0,ScreenWidth,ScreenHeight};
self.avPlayerView.frame = CGRectMake(0, 0,ScreenWidth,VIDEO_HEIGHT);
self.avPlayerView.playControlView.isFullScreen = NO;
break;
}
case UIInterfaceOrientationLandscapeLeft:
case UIInterfaceOrientationLandscapeRight:{
self.view.frame = (CGRect){0,0,ScreenWidth,ScreenHeight};
self.avPlayerView.frame = self.view.bounds;
self.avPlayerView.playControlView.isFullScreen = YES;
break;
}
default:
break;
}
}
到這里一個簡單的視頻播放器就做完了。