iOS 利用AVPlayer创建视频播放器

1 篇文章 0 订阅
0 篇文章 0 订阅

目录

  1. 导入框架
    导入

NSString *str = @"http://vmovier.qiniudn.com/559b918dbf717.mp4";
NSString *urlStr =[str stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];//链接接口中的汉字会导致请求失败
NSURL *url=[NSURL URLWithString:urlStr];
AVPlayerItem *playerItem=[AVPlayerItem playerItemWithURL:url];//主要创建方法,创建媒体资源管理对象
AVPlayer *player = [AVPlayer playerWithPlayerItem:playerItem];//根据媒体资源管理对象创建AVPlayer对象
AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:player];//AVPlayer要显示视频,必须创建一个播放器层AVPlayerLayer用于展示

(2)视频的播放、暂停功能。
这也是最基本的功能,AVPlayer对应着两个方法play、pause来实现。
[player play];//播放
[player pause];//暂停
通常我们会需要得知当前的播放状态来进行主动地改变播放状态,问题的关键是如何判断当前视频是否在播放。很不幸的是AVPlayer没有这样的状态属性,但是很幸运的是我们可以通过判断播放器的播放速度来获得播放状态。AVPlayer类包含一个极其重要的属性:

**@property (nonatomic) float rate;**

其代码注释为:/* indicates the current rate of playback; 0.0 means “stopped”, 1.0 means “play at the natural rate of the current item” */
解释为:”表示当前的播放速度;0表示“停止”,1表示“在当前的PlayerItem下以正常速率播放”。
通过监控这个属性,可以间接获取当前播放状态,获取这个属性值对于视频播放器的功能完善具有不可代替的作用。

(3)播放进度及缓存进度
MPMoviePlayerController拥有自己的进度条,AVPlayer想要要展示播放进度就没有那么简单了。视频播放器通常是使用通知来获得播放器的当前状态,媒体加载状态等,但是无论是AVPlayer还是AVPlayerItem(AVPlayer有一个属性currentItem是AVPlayerItem类型,表示当前播放的视频对象)都无法获得这些信息。当然AVPlayerItem是有通知的,但是对于获得播放状态和加载状态有用的通知只有一个:播放完成通知AVPlayerItemDidPlayToEndTimeNotification。
在播放视频时,特别是播放网络视频往往需要知道视频加载情况、缓冲情况、播放情况,这些信息可以通过KVO监控AVPlayerItem的status、loadedTimeRanges属性来获得。当AVPlayerItem的status属性为AVPlayerStatusReadyToPlay是说明正在播放,只有处于这个状态时才能获得视频时长等信息;当loadedTimeRanges的改变时(每缓冲一部分数据就会更新此属性)可以获得本次缓冲加载的视频范围(包含起始时间、本次加载时长),这样一来就可以实时获得缓冲情况。然后就是依靠AVPlayer的

- (id)addPeriodicTimeObserverForInterval:(CMTime)interval queue:(dispatch_queue_t)queue usingBlock:(void (^)(CMTime time))block

方法获得播放进度,这个方法会在设定的时间间隔内定时更新播放进度,通过time参数通知客户端。有了这些视频信息,播放进度就不成问题了,事实上通过这些信息就算是平时看到的其他播放器的缓冲进度显示以及拖动播放的功能也可以顺利的实现。

  1. 具体实现
    具体实现的效果图:
    exp_1
    exp_2

具体代码:
(注:自知自己水平有限,代码优化处理方面做的也不是太好,但是幸好实现效果勉强可以,请各位读者勿喷就好)
(1)ViewController里的调用情况:

ViewController.h文件

#import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h>
#import "AVPlayer_S.h"

@interface ViewController : UIViewController
**//AVPlayer_S 为我自己创建的类,继承于UIView,用于承载AVPlayer**
@property (nonatomic, retain) AVPlayer_S *myVideoPlayer;
@end

ViewController.m文件

#import "ViewController.h"
@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];    
    self.view.backgroundColor = [UIColor whiteColor];

    //AVPlayer_S 为我自己创建的类,继承于UIView,用于承载AVPlayer,此处用了固定的frame,自己在使用时可以根据需要设定具体的frame
    self.myVideoPlayer = [[AVPlayer_S alloc] initWithFrame:CGRectMake(20, 60, 335, 280) videoURL:@"http://vmovier.qiniudn.com/559b918dbf717.mp4"];

    [self.view addSubview:self.myVideoPlayer];
}
- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}
@end

AVPlayer_S.h文件

 #import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h>
#import "MBProgressHUD.h"//为视频添加缓冲时候的菊花旋转,此第三方可以再Github上下载,直接搜索MBProgressHUD即可下载使用,假如你不想用可以把与之相关的全部删除
//附加MBProgressHUD的下载地址:https://github.com/jdg/MBProgressHUD

@interface AVPlayer_S : UIView<MBProgressHUDDelegate>

@property (nonatomic, retain) AVPlayer *player;//AVPlayer对象
@property (nonatomic, retain) AVPlayerLayer *playerLayer;//视频播放器的layer层视图,若不明白为何创建,请看目录2.AVPlayer类介绍

@property (nonatomic, retain) NSString *video_url;//接收视频连接
@property (nonatomic, assign) CMTime totalTime;//记录视频总时长
@property (nonatomic, assign) BOOL hidenBar;//视频UI控件是否显示的标识
@property (nonatomic, assign) BOOL didPlay;//缓冲结束立即播放的标识
@property (nonatomic, assign) BOOL isFullScreen;//全屏状态标识
@property (nonatomic, assign) CGRect originalFrame;//记录播放器原始frame

@property (nonatomic, retain) UIImageView *backIamge;//背景图
@property (nonatomic, retain) UILabel *didTimeLabel;//显示已完成的播放时长
@property (nonatomic, retain) UILabel *totalTimeLabel;//显示视频的总播放时长
@property (nonatomic, retain) UIButton *playButton;//播放暂停按钮
@property (nonatomic, retain) UIProgressView *playProgress;//缓冲条
@property (nonatomic, retain) UISlider *playSlider;//播放进度条
@property (nonatomic, retain) UIButton *fullScreenButton;
@property (nonatomic, retain) UIView *barView;//承载上述控件的UIView
@property (nonatomic, retain) UIView *superView;//记录下全屏前的父视图,便于退出全屏后视频处在正确的位置

- (instancetype)initWithFrame:(CGRect)frame videoURL:(NSString *)url;//自定义初始化方法,只需要给定frame和视频连接即可返回一个具有该frame的承载视频播放器的UIView
- (void)removeNotification;//移除通知
- (void)removeObserverFromPlayerItem:(AVPlayerItem *)playerItem;//移除playerItem的观察者

@end

AVPlayer_S.m文件

#import "AVPlayer_S.h"
@implementation AVPlayer_S

//自定义初始化方法
- (instancetype)initWithFrame:(CGRect)frame videoURL:(NSString *)url
{
    self = [super initWithFrame:frame];
    if (self) {
        self.didPlay = NO;
        self.originalFrame = frame;
        self.hidenBar = NO;
        self.isFullScreen = NO;
        self.video_url = url;
        self.backgroundColor = [UIColor blackColor];
        [self creatView:frame];//创建UI视图
        [self createPlayer:self.originalFrame];//创建layer层,用于播放视频
        [MBProgressHUD showHUDAddedTo:self animated:YES];//添加小菊花,若不想用就删除与之相关的东西
    }

    return self;
}

- (void)createPlayer:(CGRect)frame {

    NSString *urlStr =[self.video_url stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    NSURL *url=[NSURL URLWithString:urlStr];
    //开辟线程执行AVPlayerItem的创建,若不开辟线程则会占用主线程,造成视图加载完成后暂时无法对页面进行操作的卡顿现象
    dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
    dispatch_async(globalQueue, ^{
    //创建AVPlayerItem
         AVPlayerItem *playerItem=[AVPlayerItem playerItemWithURL:url];
        dispatch_async(dispatch_get_main_queue(), ^{
            if (playerItem) {
                if (!_player) {
                    _player = [AVPlayer playerWithPlayerItem:playerItem];
                    if (_player) {
                    //保证AVPlayer对象存在,然后再为其添加进度观察,以便获取当前播放进度
                        [self addProgressObserver];
                        //给AVPlayerItem添加监控(最重要的监控)
                        [self addObserverToPlayerItem:playerItem];
                    }
                }
            } else {
                UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"提示" message:@"无法连接到视频资源" delegate:self cancelButtonTitle:@"取消" otherButtonTitles:@"确定", nil];
                [alert show];
                [self removeFromSuperview];
            }

        });
    });
    //创建播放层(layer层)
    self.playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.player];
    self.playerLayer.frame = CGRectMake(0, 0, frame.size.width, frame.size.height);
    [self.layer addSublayer:self.playerLayer];
}


#pragma mark - 通知
//添加通知

- (void)addNotification {
    //添加 播放结束 通知
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playbackFinished:) name:AVPlayerItemDidPlayToEndTimeNotification object:self.player.currentItem];
}

//移除所有通知
- (void)removeNotification {
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

// 播放完成通知执行的方法:移除播放视图及小菊花假如想执行此操作可以解除注释

- (void)playbackFinished:(NSNotification *)notification {
/*
    [MBProgressHUD hideHUDForView:self animated:YES];
    [self removeFromSuperview];
*/
}

#pragma mark - 监控
// 给播放器添加进度更新

- (void)addProgressObserver {

    UISlider *slider = self.playSlider;//获取当前滑动条
    __block AVPlayer_S *av_s = self;

    //这里设置每秒执行一次
    [self.player addPeriodicTimeObserverForInterval:CMTimeMake(1.0, 10.0) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {

        float current = CMTimeGetSeconds(time);//获取当前的播放时间

        if (current) {
            [slider setValue:current animated:YES];//实时设置滑动条当前值
            int currentTime = (int)current;
            int m = currentTime / 60;
            int s = currentTime % 60;
            NSString *strM = nil;
            NSString *strS = nil;
            if (m < 10) {
                strM = [NSString stringWithFormat:@"0%d", m];
            } else {
                strM = [NSString stringWithFormat:@"%d", m];
            }
            if (s < 10) {
                strS = [NSString stringWithFormat:@"0%d", s];
            } else {
                strS = [NSString stringWithFormat:@"%d", s];
            }
            //设置UILabel视图显示当前播放时长
            av_s.didTimeLabel.text = [NSString stringWithFormat:@"%@:%@", strM, strS];
        }

    }];
}
// 给AVPlayerItem添加监控

- (void)addObserverToPlayerItem:(AVPlayerItem *)playerItem {
    //监控状态属性,注意AVPlayer也有一个status属性,通过监控它的status也可以获得播放状态
    [playerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
    //监控网络加载情况属性
    [playerItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];

}

//通知移除方法
- (void)removeObserverFromPlayerItem:(AVPlayerItem *)playerItem {
    [playerItem removeObserver:self forKeyPath:@"status"];
    [playerItem removeObserver:self forKeyPath:@"loadedTimeRanges"];
}

/*
 *  通过KVO监控播放器状态
 *  @param keyPath 监控属性
 *  @param object  监视器
 *  @param change  状态改变
 *  @param context 上下文
 */
 //通知所执行的方法
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {

    AVPlayerItem *playerItem = object;//获取监控对象
    if ([keyPath isEqualToString:@"status"]) {
        //获取status值
        AVPlayerStatus status = [[change objectForKey:@"new"] intValue];
        //暂停状态
        if (status == AVPlayerStatusReadyToPlay) {
            self.totalTime = playerItem.duration;//获取视频总时长
            [self customVideoSlider:playerItem.duration];//设置播放进度最大值
            int totalTime = (int)CMTimeGetSeconds(playerItem.duration);
            int m = totalTime / 60;
            int s = totalTime % 60;
            NSString *strM = nil;
            NSString *strS = nil;
            if (m < 10) {
                strM = [NSString stringWithFormat:@"0%d", m];
            } else {
                strM = [NSString stringWithFormat:@"%d", m];
            }
            if (s < 10) {
                strS = [NSString stringWithFormat:@"0%d", s];
            } else {
                strS = [NSString stringWithFormat:@"%d", s];
            }
            //设置视频时长的UILabel视图显示值
            self.totalTimeLabel.text = [NSString stringWithFormat:@"%@:%@", strM, strS];
        }
    } else if ([keyPath isEqualToString:@"loadedTimeRanges"]) {
    //播放状态
        [self addNotification];//添加播放完成通知
        NSArray *array = playerItem.loadedTimeRanges;
        CMTimeRange timeRange = [array.firstObject CMTimeRangeValue];//本次缓冲时间范围
        float startSeconds = CMTimeGetSeconds(timeRange.start);
        float durationSeconds = CMTimeGetSeconds(timeRange.duration);
        NSTimeInterval totalBuffer = startSeconds + durationSeconds;//缓冲总长度
        //设置缓存条
        [self.playProgress setProgress:totalBuffer/CMTimeGetSeconds(self.totalTime) animated:YES];
        float currentPlayTime =  CMTimeGetSeconds([self.player currentTime]);//获取当前播放时长
        //设定当缓冲总时长超过播放时长3秒时开始播放视频
        if (totalBuffer - currentPlayTime > 3.0) {
            if (!self.didPlay) {
                [self createPlayer:self.frame];
                [self.player play];
                [MBProgressHUD hideHUDForView:self animated:YES];
                [self bringSubviewToFront:self.barView];
                [self.playButton setImage:[UIImage imageNamed:@"pause_64"] forState:UIControlStateNormal];
                self.didPlay = YES;
            }
        } else {
            [self.player pause];
            [MBProgressHUD showHUDAddedTo:self animated:YES];
            self.didPlay = NO;
            [self.playButton setImage:[UIImage imageNamed:@"play_64"] forState:UIControlStateNormal];
        }

    }
}
//播放视图的创建
- (void)creatView:(CGRect)frame {

    self.backIamge = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, frame.size.width, frame.size.height)];
    self.backIamge.image = [UIImage imageNamed:@"back_Image.png"];

    self.barView = [[UIView alloc] initWithFrame:CGRectMake(0, frame.size.height-40, frame.size.width, 40)];
    self.barView.backgroundColor = [UIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:0.5];

    self.playProgress = [[UIProgressView alloc] initWithFrame:CGRectMake(50, 15, self.barView.frame.size.width-100, 10)];
    [self.playProgress setProgressTintColor:[UIColor orangeColor]];

    self.playSlider = [[UISlider alloc] initWithFrame:CGRectMake(47, 11.2, self.playProgress.frame.size.width+10, 10)];
    [self.playSlider setThumbImage:[UIImage imageNamed:@"ball_16"] forState:UIControlStateNormal];

    self.didTimeLabel = [[UILabel alloc] initWithFrame:CGRectMake(50, 25, 50, 10)];
    self.didTimeLabel.font = [UIFont systemFontOfSize:14];
    self.totalTimeLabel = [[UILabel alloc] initWithFrame:CGRectMake(self.barView.frame.size.width-100, 25, 50, 10)];
    self.totalTimeLabel.font = [UIFont systemFontOfSize:14];
    //=================
    UIGraphicsBeginImageContextWithOptions((CGSize){ 2, 2 }, NO, 0.0f);
    UIImage *transparentImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    [self.playSlider setMinimumTrackTintColor:[UIColor blueColor]];
    [self.playSlider setMaximumTrackImage:transparentImage forState:UIControlStateNormal];
    //=================
    [self.playSlider addTarget:self action:@selector(sliderAction:) forControlEvents:UIControlEventValueChanged];

    self.playButton = [UIButton buttonWithType:UIButtonTypeCustom];
    self.playButton.frame = CGRectMake(5, 5, 30, 30);
    [self.playButton addTarget:self action:@selector(playButtonAction:) forControlEvents:UIControlEventTouchUpInside];
    [self.playButton setBackgroundColor:[UIColor clearColor]];
    [self.playButton setImage:[UIImage imageNamed:@"play_64.png"] forState:UIControlStateNormal];

    self.fullScreenButton = [UIButton buttonWithType:UIButtonTypeCustom];
    self.fullScreenButton.frame = CGRectMake(self.barView.frame.size.width - 40, 5, 30, 30);
    [self.fullScreenButton addTarget:self action:@selector(fullScreenButtonAction:) forControlEvents:UIControlEventTouchUpInside];
    [self.fullScreenButton setBackgroundColor:[UIColor clearColor]];
    [self.fullScreenButton setImage:[UIImage imageNamed:@"fullScreen_64.png"] forState:UIControlStateNormal];

    [self.barView addSubview:self.playButton];
    [self.barView addSubview:self.playProgress];
    [self.barView addSubview:self.playSlider];
    [self.barView addSubview:self.didTimeLabel];
    [self.barView addSubview:self.totalTimeLabel];
    [self.barView addSubview:self.fullScreenButton];
    [self addSubview:self.barView];

    UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapAction:)];
    [self addGestureRecognizer:tap];
}

- (void)tapAction:(UITapGestureRecognizer *)tap {

    if (self.hidenBar) {
        self.hidenBar = NO;
        [self bringSubviewToFront:self.barView];
    } else {
        self.hidenBar = YES;
    }
    self.barView.hidden = self.hidenBar;
}

- (void)playButtonAction:(UIButton *)button
{
    NSLog(@"%f", self.player.rate);
    if(self.player.rate == 0){ //说明是暂停
        [button setImage:[UIImage imageNamed:@"pause_64"] forState:UIControlStateNormal];
        self.backIamge.hidden = YES;
        if (self.didPlay) {
            [self.player play];
        }
    }else if(self.player.rate == 1){//正在播放
        [self.player pause];
        [button setImage:[UIImage imageNamed:@"play_64"] forState:UIControlStateNormal];
    }
}

//全屏设置
- (void)fullScreenButtonAction:(UIButton *)button {

    if (self.isFullScreen) {
        self.isFullScreen = NO;
        [self.fullScreenButton setImage:[UIImage imageNamed:@"fullScreen_64.png"] forState:UIControlStateNormal];

        [UIView animateWithDuration:0.2 animations:^{

            self.transform = CGAffineTransformRotate(self.transform, -M_PI_2);

            self.frame = self.originalFrame;
            [self.playerLayer removeFromSuperlayer];
            [self createPlayer:self.originalFrame];

            self.barView.frame = CGRectMake(0, self.originalFrame.size.height-40, self.originalFrame.size.width, 40);
            [self bringSubviewToFront:self.barView];

            self.center = CGPointMake(self.originalFrame.origin.x + self.originalFrame.size.width/2.0, self.originalFrame.origin.y + self.originalFrame.size.height/2.0);

            self.backIamge.frame = CGRectMake(0, 0, self.originalFrame.size.width, self.originalFrame.size.height);
            self.playProgress.frame = CGRectMake(50, 15, self.barView.frame.size.width-100, 10);
            self.playSlider.frame = CGRectMake(45, 11, self.playProgress.frame.size.width+10, 10);
            self.playButton.frame = CGRectMake(5, 5, 30, 30);
            self.fullScreenButton.frame = CGRectMake(self.barView.frame.size.width - 40, 5, 30, 30);

            [self.superView addSubview:self];
        }];

    } else {
        self.isFullScreen = YES;
        self.superView = self.superview;

        [self.fullScreenButton setImage:[UIImage imageNamed:@"exitFullScreen_64.png"] forState:UIControlStateNormal];
        [self.window addSubview:self];
        [self.window bringSubviewToFront:self];

        [UIView animateWithDuration:0.2 animations:^{
            self.frame = CGRectMake(20, 0, [UIScreen mainScreen].bounds.size.height-20, [UIScreen mainScreen].bounds.size.width);
            [self.playerLayer removeFromSuperlayer];

            [self createPlayer:self.frame];

            self.barView.frame = CGRectMake(0, [UIScreen mainScreen].bounds.size.width-40, [UIScreen mainScreen].bounds.size.height-20, 40);
            [self bringSubviewToFront:self.barView];
            self.backIamge.frame = CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.height-20, [UIScreen mainScreen].bounds.size.width);

            self.transform = CGAffineTransformRotate(self.transform, M_PI_2);
            self.center = CGPointMake([UIScreen mainScreen].bounds.size.width/2.0, ([UIScreen mainScreen].bounds.size.height+20)/2.0);
            self.playProgress.frame = CGRectMake(50, 15, self.barView.frame.size.width-100, 10);
            self.playSlider.frame = CGRectMake(45, 11, self.playProgress.frame.size.width+10, 10);
            self.playButton.frame = CGRectMake(5, 5, 30, 30);
            self.fullScreenButton.frame = CGRectMake(self.barView.frame.size.width - 40, 5, 30, 30);
            self.totalTimeLabel.frame = CGRectMake(self.barView.frame.size.width-100, 25, 50, 10);
        }];

    }
}

//UISlider基本设置
- (void)customVideoSlider:(CMTime)duration {

    self.playSlider.maximumValue = CMTimeGetSeconds(duration);
    self.playSlider.minimumValue = 0.0;
}

//拖动进度条到任何位置播放
- (void)sliderAction:(UISlider *)slider {

    if (self.player.rate == 0) {
        [self.player seekToTime:CMTimeMake((int)slider.value*10, 10.0)];
        [self.player play];
        [self.playButton setImage:[UIImage imageNamed:@"pause_64.png"] forState:UIControlStateNormal];
    } else if(self.player.rate == 1) {
        [self.player pause];
        [self.player seekToTime:CMTimeMake((int)slider.value*10, 10.0)];
        [self.player play];
        [self.playButton setImage:[UIImage imageNamed:@"pause_64.png"] forState:UIControlStateNormal];
    }
}

@end

Demo :http://download.csdn.net/detail/yangxinca/8949003

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值