流媒体开发(三)视频播放

简介

和音频播放一样,iOS内置了视频播放器,提供个很多的API实现视频播放。如mediaPlayer.framework下的MPMovieplayerController、AVFounditon.framework下的AVPlayer和AVKit下的AVPlayerViewcontroller。但是MPMovieplayerController已经在ios9.0中被废弃了,用来替代的是AVPlayerViewcontroller,我们在文中的最后在对它们的使用做一些描述。同时前文中提到的AVPlayer同样可以实现视频播放功能,而且实现功能更加强大,但是实现起来相对比较麻烦。

1. AVPlayer

在iOS开发上,项目往往对视频播放的要求较高,尤其是过场动画和用户互动效果上的表现需要更加细致的操作,比如有些时候需要自定义播放器的样式,要对视频有自由的控制等。所以在iOS 4之后,我们可以使用 AVPlayer来对视频播放做更加细致的操作。AVPlayer存在于AVFoundation中,它更加接近于底层,所以灵活性也更强:
这里写图片描述

AVPlayer本身并不能显示视频,如果AVPlayer要显示必须创建一个播放器层AVPlayerLayer用于展示,播放器层继承于CALayer,有了AVPlayerLayer之添加到控制器视图的layer中即可。要使用AVPlayer首先了解一下几个常用的类:

  • AVAsset:是一个抽象类,不能直接使用,主要用于获取多媒体信息。
  • AVURLAsset:AVAsset的子类,可以根据一个URL路径创建一个包含媒体信息的AVURLAsset对象。
  • AVPlayerItem:一个媒体资源管理对象,管理者视频的一些基本信息和状态,一个AVPlayerItem对应着一个视频资源。

下面简单通过一个播放器来演示AVPlayer的使用, 在这个自定义的播放器中实现了视频播放、暂停、进度展示和视频列表功能,下面将对这些功能一一介绍。

  • 视频的播放、暂停功能:这也是视频播放最基本的功能,AVPlayer对应着两个方法play、pause来实现。但是关键问题是如何判断当前视频是否在播放,在前面的内容中无论是音频播放器还是视频播放器都有对应的状态来判断,但是AVPlayer却没有这样的状态属性,通常情况下可以通过判断播放器的播放速度来获得播放状态。如果rate为0说明是停止状态,1是则是正常播放状态。
  • 展示播放进度:无论是AVPlayer还是AVPlayerItem (AVPlayer有一个属性currentItem是AVPlayerItem类型,表示当前播放的视频对象)都无法获得这些信息。当然AVPlayerItem是有通知的,但是对于获得播放状态和加载状态有用的通知只有一个:播放完成通知AVPlayerItemDidPlayToEndTimeNotification。在播放视频时,特别是播放网络视频往往需要知道视频加载情况、缓冲情况、播放情况,这些信息可以通过KVO监控AVPlayerItem 的status、loadedTimeRanges属性来获得。当AVPlayerItem的status属性为AVPlayerStatusReadyToPlay 是说明正在播放,只有处于这个状态时才能获得视频时长等信息;当loadedTimeRanges的改变时(每缓冲一部分数据就会更新此属性)可以获得本次缓冲加载的视频范围(包含起始时间、本次加载时长),这样一来就可以实时获得缓冲情况。然后就是依靠AVPlayer的周期性回调的方法获得播放进度,这个方法会在设定的时间间隔内定时更新播放进度,通过time参数通知客户端。相信有了这些视频信息播放进度就不成问题了,事实上通过这些信息就算是平时看到的其他播放器的缓冲进度显示以及拖动播放的功能也可以顺利的实现。
  • 最后就是视频切换的功能,在前面介绍的所有播放器中每个播放器对象一次只能播放一个视频,如果要切换视频只能重新创建一个对象,但是AVPlayer却提供了- (void)replaceCurrentItemWithPlayerItem:(AVPlayerItem *)item方法用于在不同的视频之间切换,但是该方法会导致原先持有的视频对像弹出一个错误,所以该操作在iOS4之后成为一个无效操作(事实上在AVFoundation内部还有一个AVQueuePlayer专门处理播放列表切换,有兴趣的朋友可以自行研究,这里不再赘述)。

示例代码

#import "ViewController.h"
#import <AVFoundation/AVFoundation.h>

@interface ViewController ()
{
    NSArray *_playList;
    AVPlayer *_avPlayer;// 视频播放器
}
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    NSLog(@"%@",[NSHomeDirectory() stringByAppendingString:@"/Documents"]);

    {// UI设置
        self.volume.value = _avPlayer.volume;
        self.progress.value = 0;
        self.progressView.progress = 0;
        [self.play setTitle:@"pause" forState:UIControlStateNormal];
    }

#pragma mark - 视频播放
    // 1.加载网络视频网址
    NSString *str1 = @"http://vfx.mtime.cn/Video/2015/07/04/mp4/150704102229172451_480.mp4";
    // 1.获取本地视频文件路径
    NSString *str2 = [[NSBundle mainBundle] pathForResource:@"海妖宝藏" ofType:@"mp4"];
    NSURL *url1 = [NSURL URLWithString:str1];
    NSURL *url2 = [NSURL fileURLWithPath:str2];
    _playList = @[url1,url2];

    // 2.初始化AVPlayer视频播放器
    AVPlayerItem *item = [[AVPlayerItem alloc] initWithURL:_playList[0]];
    _avPlayer = [[AVPlayer alloc] initWithPlayerItem:item];

    // 3.初始化播放图层
    AVPlayerLayer *layer = [AVPlayerLayer playerLayerWithPlayer:_avPlayer];//AVPlayer播放视频需要生成一个Layer图层完成播放
    //宽高比例设置为 9:16
    layer.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.width *9/16.f);
    layer.videoGravity = AVLayerVideoGravityResizeAspect;//视频的填充模式
    //添加播放图层到界面
    [self.view.layer addSublayer:layer];

    {// 常用属性设置

        _avPlayer.rate = 1;// 播放速度
        _avPlayer.volume = 0.8f;// 播放音量
        _avPlayer.muted = NO;// 静音

        // 播放完成的操作
        _avPlayer.actionAtItemEnd = AVPlayerActionAtItemEndPause;
        /* AVPlayerActionAtItemEnd
         AVPlayerActionAtItemEndAdvance = 0,// 不要使用
         AVPlayerActionAtItemEndPause = 1,
         AVPlayerActionAtItemEndNone    = 2,
         */
    }
    {// 常用方法
        // 指定播放时间
        [_avPlayer seekToTime:CMTimeMakeWithSeconds(20, 24)];// 从指定时间的前一秒开始播放
    }

    // 3.播放音频
    [_avPlayer play];

#pragma mark - 注册通知及KVO
    // 注册通知 AVPlayerItemDidPlayToEndTimeNotification:音频播放完成
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playDidEnd) name:AVPlayerItemDidPlayToEndTimeNotification object:nil];

    //监控状态属性,注意AVPlayer也有一个status属性,通过监控它的status也可以获得播放状态
    [_avPlayer.currentItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
    // 监控网络加载情况属性
    [_avPlayer.currentItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];

#pragma mark - 播放进度周期回调
    // 实时监听视频文件的播放进度
    __block ViewController *blockSelf = self;
    __block AVPlayer *blockPlayer = _avPlayer;
    [_avPlayer addPeriodicTimeObserverForInterval:CMTimeMake(1, 1) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {

        // 周期性回调该block的代码块

        /* CMTime : value/timescale = seconds.
         time指的就是時間(不是秒),
         而時間要換算成秒就要看第二個參數timeScale了.
         timeScale指的是1秒需要由幾個frame構成(可以視為fps,帧数),
         因此真正要表達的時間就會是 time / timeScale 才會是秒.
         */

        // 统计时长
        CMTime duration = blockPlayer.currentItem.duration;
        CGFloat durationSec = CMTimeGetSeconds(duration);
        NSLog(@"总时长:%f",durationSec);
        // 统计当前播放时长
        CMTime current = blockPlayer.currentItem.currentTime;
        CGFloat currentSec = CMTimeGetSeconds(current);
        NSLog(@"当前时间:%f",currentSec);

        // 刷新播放进度
        [blockSelf.progress setValue:currentSec/durationSec animated:YES];

        //计算缓存时间
        NSTimeInterval timeInterval = [blockSelf availabelDuration];
        NSLog(@"time interval :%f",timeInterval);
        // 刷新下载进度
        [blockSelf.progressView setProgress:timeInterval/durationSec animated:YES];

    }];

    // 限定时间监听,可以设置具体的回调时间
//    NSValue *value = [NSValue valueWithCMTime:CMTimeMake(10, 2)];
    NSValue *value = [NSValue valueWithCMTime:CMTimeMakeWithSeconds(5, 24)];
    [_avPlayer addBoundaryTimeObserverForTimes:@[value] queue:dispatch_get_main_queue() usingBlock:^{
        NSLog(@"第0秒。。。。。");
    }];

}

#pragma mark - UIActions
- (IBAction)volume:(UISlider *)sender {

    _avPlayer.volume = sender.value;
}

- (IBAction)progress:(UISlider *)sender {

    // 总时长
    float duration = _avPlayer.currentItem.duration.value/_avPlayer.currentItem.duration.timescale;
    float currentTime = duration * sender.value;
    [_avPlayer seekToTime:CMTimeMake(currentTime, 1)];

}

static bool playing = YES;
- (IBAction)playOrPause:(UIButton *)sender {

    if (playing) {
        [_avPlayer pause]; //暂停播放
        playing = NO;
        [sender setTitle:@"play" forState:UIControlStateNormal];
    }else {

        [_avPlayer play]; //暂停播放
        playing = YES;
        [sender setTitle:@"pause" forState:UIControlStateNormal];
    }
}

#pragma mark 音频文件缓冲
- (NSTimeInterval)availabelDuration {

    NSArray *loadedTimeRanges = [[_avPlayer currentItem] loadedTimeRanges];
    CMTimeRange timeRange = [loadedTimeRanges.firstObject CMTimeRangeValue];// 获取缓存区域
    /* typedef struct
      {
         CMTime start;
         CMTime duration;
      } CMTimeRange;
     */
    float startSeconds = CMTimeGetSeconds(timeRange.start);
    float durationSeconds = CMTimeGetSeconds(timeRange.duration);

    // 计算缓存事件
    NSTimeInterval result = startSeconds + durationSeconds;

    return result;
}

#pragma mark - 通知及KVO
// 播放结束后执行该方法
- (void)playDidEnd {

    // 播放结束是,播放进度为0
    NSLog(@"play finished  %f",_avPlayer.rate);

    // 播放下一首
    AVPlayerItem *currentItem = [[AVPlayerItem alloc] initWithURL:_playList[1]];
    [_avPlayer replaceCurrentItemWithPlayerItem:currentItem];
    [_avPlayer play];

}

/**
 *  通过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"]) {
        AVPlayerStatus status= [[change objectForKey:@"new"] intValue];
        if(status==AVPlayerStatusReadyToPlay){
            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);
        //
    }
}
@end

2. AVPlayerViewController

上面我们通过AVPlayer实现了视频播放,发现实现起来非常的复杂,如果项目只是需要简单的实现播放影片的功能,我们再使用AVPlayer来实现就有些多余了。在iOS8以前,我们主要使用MeidaPlayer框架中的MPMoviePlayerController类和MPMoviePlayerViewController类来实现视频的简单播放。在iOS8后,iOS开发框架中引入了一个新的视频框架AVKit,其中提供了视频开发类AVPlayerViewController用于在应用中嵌入播放视频的控件。在iOS8中,这两个框架中的视频播放功能并无太大差异,基本都可以满足开发者的需求。同时AVKit框架可以帮助我们实现画中画任务,所谓画中画,即是用户可以将当前播放的视频缩小放在屏幕上同时进行其他应用程序的使用。iOS9系统后,iPad Air正式开始支持多任务与画中画的分屏功能,这个革命性的功能将极大的方便用户的使用。于此同时,在iOS9中,MPMoviePlayerController与MPMoviePlayerViewController类也被完全弃用,所以开发者建议使用AVPlayerViewController,可以十分方便的实现视频播放的功能并在一些型号的iPad上集成画中画的功能。
AVPlayerViewController的使用非常简单,提供给开发者使用的属性和方法很少,下面是AVPlayerViewController的一些属性和方法:

属性说明
@property(nonatomic)BOOL showsPlaybackControls;是否显示视频播放控制控件
@property(nonatomic,copy)NSString *videoGravity;设置视频播放界面的尺寸缩放选项 AVLayerVideoGravityResizeAspect:不进行比例缩放,以宽高中长的一边充满为基准。AVLayerVideoGravityResizeAspectFill:不进行比例缩放,以宽高中短的一边充满为基准。 AVLayerVideoGravityResize进行缩放充满屏幕
@property(nonatomic,readonly,getter=isReadyForDisplay)BOOL readyForDisplay;获取是否已经准备好开始播放
@property(nonatomic,readonly)CGRect videoBounds;获取视频播放界面的尺寸
@property(nonatomic,readonly,nullable)UIView *contentOverlayView;视频播放器的视图 自定义的控件可以添加在其上
@property(nonatomic,weak,nullable) id delegate画中画代理 iOS9后可用
@property (nonatomic) BOOL allowsPictureInPicturePlayback NS_AVAILABLE_IOS(9_0);是否支持画中画 iOS9后可用 默认支持
代理方法说明
-(void)playerViewControllerWillStartPictureInPicture:(AVPlayerViewController*)playerViewController将要开始画中画时调用的方法
-(void)playerViewControllerDidStartPictureIn Picture:(AVPlayerViewController *)playerViewController已经开始画中画时调用的方法
-(void)playerViewController:(AVPlayerView Controller )playerViewController failedToStart PictureInPictureWithError:(NSError )error开始画中画失败调用的方法
-(void)playerViewControllerWillStopPictureIn Picture:(AVPlayerViewController *) playerViewController将要停止画中画时调用的方法
-(void)playerViewControllerDidStopPictureIn Picture:(AVPlayerViewController *) playerViewController已经停止画中画时调用的方法
-(BOOL)playerViewControllerShould AutomaticallyDismissAtPictureInPictureStart:(AVPlayerViewController *)playerViewController是否在开始画中画时自动将当前的播放界面dismiss掉 返回YES则自动dismiss 返回NO则不会自动dismiss
-(void)playerViewController:(AVPlayerViewController *)playerViewController restoreUserInterf用户点击还原按钮 从画中画模式还原回app内嵌模式时调用的方法

注意:

AVPlayerViewController本身只是一个视图控制器,并不具备播放视频的功能,它有一个player属性用来承载视频的播放者,事实上,使用AVPlayerViewController来实现视频播放还是通过AVPlayer来完成视频的播放,只不过AVPlayerViewController帮助我们完成了很多视频操作方面的功能。下面,我们来完成一个Demo来通过AVPlayerViewController实现视频播放。

示例代码:

#import <AVFoundation/AVFoundation.h>
#import <AVKit/AVKit.h>

@interface ViewController ()
{
    AVPlayerViewController *_playerController;
    AVPlayer *_player;
}
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    // 1.初始化AVPlayer
    NSString *path = [[NSBundle mainBundle] pathForResource:@"捉妖记" ofType:@"mp4"];
    _player = [AVPlayer playerWithURL:[NSURL fileURLWithPath:path]];

    _playerController = [[AVPlayerViewController alloc] init];
    _playerController.player = _player;
    _playerController.delegate = self;// 设置代理

    // 在当前界面播放视频,设置播放视图的大小
    // 注意视图尺寸设置全屏与小屏播放效果及操作效果都不同
    _playerController.view.frame = CGRectMake(0, 0, 414, 414);
    [self.view addSubview:_playerController.view];

    _playerController.videoGravity = AVLayerVideoGravityResizeAspect;// 设置播放视图拉伸属性
    /*
     AVLayerVideoGravityResize:宽高基准
     AVLayerVideoGravityResizeAspect:宽度基准,
     AVLayerVideoGravityResizeAspectFill:高度基准
     */

    _playerController.view.translatesAutoresizingMaskIntoConstraints = YES;    //AVPlayerViewController 内部可能是用约束写的,这句可以禁用自动约束,消除报错  注意:设置为NO,视频播放视图尺寸会出缩小

    _playerController.showsPlaybackControls = YES;//设置是否显示视频控制台,实现对视频的控制
    _playerController.allowsPictureInPicturePlayback = YES;//画中画,iPad可用,在这里没有效果

    // 添加自定义控件,控件不影响原生控件的操作和显示
    UIView *subView = [[UIView alloc] initWithFrame:CGRectMake(314, 314, 50, 100)];
    subView.backgroundColor = [UIColor redColor];
    [_playerController.contentOverlayView addSubview:subView];

    [_player play];
}

// 在iPad Air中,AVPlayerViewController作为独立视图控制器完成视频播放可以实现画中画的设置
- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    // 推出AVPlayerViewController完成视频播放
    [self presentViewController:_playerController animated:YES completion:nil];
}

#pragma mark - AVPlayerViewControllerDelegate
- (void)playerViewControllerWillStartPictureInPicture:(AVPlayerViewController *)playerViewController {
    NSLog(@"1%s", __FUNCTION__);
}

- (void)playerViewControllerDidStartPictureInPicture:(AVPlayerViewController *)playerViewController {
    NSLog(@"2%s", __FUNCTION__);
}

- (void)playerViewController:(AVPlayerViewController *)playerViewController failedToStartPictureInPictureWithError:(NSError *)error {
    NSLog(@"3%s", __FUNCTION__);
}

- (void)playerViewControllerWillStopPictureInPicture:(AVPlayerViewController *)playerViewController {
    NSLog(@"4%s", __FUNCTION__);
}

- (void)playerViewControllerDidStopPictureInPicture:(AVPlayerViewController *)playerViewController {
    NSLog(@"5%s", __FUNCTION__);
}

- (BOOL)playerViewControllerShouldAutomaticallyDismissAtPictureInPictureStart:(AVPlayerViewController *)playerViewController {
    NSLog(@"6%s", __FUNCTION__);
    return true;
}

- (void)playerViewController:(AVPlayerViewController *)playerViewController restoreUserInterfaceForPictureInPictureStopWithCompletionHandler:(void (^)(BOOL))completionHandler {
    NSLog(@"7%s", __FUNCTION__);
}

@end

注意:如果开发设备为iPad Air,我们通过模态视图的方式推出AVPlayerViewController,来显示播放视图,可以实现画中画的播放功能。如下图所示:
这里写图片描述

这里写图片描述

这里写图片描述

    到目前为止无论是AVPlayerViewController还是AVPlayer来播放视频都相当强大,但是它也存在着一些不可回避的问题,那就是支持的视频编码格式很有限:H.264、MPEG-4,扩展名(压缩格式):.mp4、.mov、.m4v、.m2v、.3gp、.3g2等。但是无论是AVPlayerViewController还是AVPlayer它们都支持绝大多数音频编码,所以大家如果纯粹是为了播放视频的话也可以考虑使用这两个播放器。那么如何支持更多视频编码格式呢?目前来说主要还是依靠第三方框架,在iOS上常用的视频编码、解码框架有:VLC、ffmpeg, 具体使用方式今天就不再做详细介绍。

3. 使用AVFoundation生成缩略图

在实际应用中,有时候要求我们实现获取视频文件的截图功能,我们可以通过使用AVFundation框架中的AVAssetImageGenerator来获取视频缩略图。使用AVAssetImageGenerator获取缩略图大致分为三个步骤:

  1. 创建AVURLAsset对象(此类主要用于获取媒体信息,包括视频、声音等)。
  2. 根据AVURLAsset创建AVAssetImageGenerator对象。
  3. 使用AVAssetImageGenerator的copyCGImageAtTime::方法获得指定时间点的截图。

示例代码:

#import "ViewController.h"
#import <AVFoundation/AVFoundation.h>

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    // 1.创建AVURLAsset对象
    NSString *path = [[NSBundle mainBundle] pathForResource:@"捉妖记" ofType:@"mp4"];
    AVURLAsset *set = [AVURLAsset assetWithURL:[NSURL fileURLWithPath:path]];

    // 2.根据AVURLAsset创建AVAssetImageGenerator对象
    AVAssetImageGenerator *generator = [AVAssetImageGenerator assetImageGeneratorWithAsset:set];

    // 3.获得指定时间点的截图。
    CMTime time = CMTimeMakeWithSeconds(49, 10);//CMTime是表示电影时间信息的结构体,第一个参数表示是视频第几秒,第二个参数表示每秒帧数.(如果要获得某一秒的第几帧可以使用CMTimeMake方法)
    CMTime actualTime;
    /*截图时间
     * time:缩略图创建时间
     * actualTime:缩略图实际生成的时间
     */

    CGImageRef cgImage = [generator copyCGImageAtTime:time actualTime:&actualTime error:nil];
    CMTimeShow(actualTime);// 显示截图的实际生成时间

    UIImage *image = [UIImage imageWithCGImage:cgImage];

    // 4.保存图片到相册
//    UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil);

    UIImageView *imageView = [[UIImageView alloc] initWithFrame:self.view.bounds];
    imageView.image = image;
    [self.view addSubview:imageView];

}

@end

拓展:视频播放视图的封装

我们来综合上面学习到的内容,封装一个播放视频的视图,来方便我们日后的使用。实现了下面的具体功能:

1. 完成视频的基本播放功能,
2. 添加基本的UI控件,实行视频控制
3. 通过pan手势控制视频的播放音量和播放亮度(这里使用了两种方法,推荐使用第二种,直接调整手机的音量和手机的屏幕亮度)。

示例代码:

#import "AVPlayerView.h"
#import <AVFoundation/AVFoundation.h>
#import <MediaPlayer/MediaPlayer.h>

@implementation AVPlayerView
{
    AVPlayer *_player;// 视频播放器

    UIView *_controlView;// 控件承载视图
    UIView *_maskView;// 遮罩视图
    UISlider *_progressSlider;// 进度条
}

- (id)initWithFrame:(CGRect)frame playWithURL:(NSURL *)url {

    if (self = [super initWithFrame:frame]) {

        self.backgroundColor = [UIColor blackColor];
        self.clipsToBounds = YES;

        // 通过URL初始化视频播放器
        _player = [[AVPlayer alloc] initWithURL:url];
        // AVPlayer 需要生成一个Layer 图层,添加到view的layer
        AVPlayerLayer *layer =[AVPlayerLayer playerLayerWithPlayer:_player];
        layer.frame = self.layer.bounds;
        [self.layer addSublayer:layer];
        [_player play];// 开始播放

        // 添加视频控制视图
        [self creatControlView];

        //添加操作手势
        UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panAction:)];
        [self addGestureRecognizer:pan];

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

    }

    return self;
}

- (void)creatControlView {

    // 添加半透明遮罩
    _maskView = [[UIView alloc] initWithFrame:self.bounds];
    _maskView.backgroundColor = [UIColor blackColor];
    _maskView.alpha = 0;
    [self addSubview:_maskView];

    // 控制视图
    _controlView = [[UIView alloc] initWithFrame:CGRectMake(0,self.bounds.size.height-50 , self.bounds.size.width, 50)];
    _controlView.hidden = NO;
    _controlView.alpha = 1;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

        [UIView animateWithDuration:1 animations:^{

            _controlView.transform = CGAffineTransformMakeTranslation(0, 50);
            _controlView.alpha = 0;
        } completion:^(BOOL finished) {
            _controlView.hidden = YES;// 隐藏控制栏
        }];
    });
    [_controlView setBackgroundColor:[UIColor blackColor]];
    [self addSubview:_controlView];

    // 按钮
    UIButton *btn = [UIButton buttonWithType:UIButtonTypeSystem];
    [btn setFrame:CGRectMake(0,0, 50, 50)];
    [btn setTitle:@"▶️" forState:UIControlStateNormal];
    [btn addTarget:self action:@selector(playOrPause:) forControlEvents:UIControlEventTouchUpInside];
    [_controlView addSubview:btn];

    // 进度条,需要跟随音频文件的播放进度改变value值
    _progressSlider = [[UISlider alloc] initWithFrame:CGRectMake(70, 0, self.bounds.size.width-140, 50)];
    _progressSlider.value = 0;
    // 进度条的操作方法
    [_progressSlider addTarget:self action:@selector(sliderAct:) forControlEvents:UIControlEventValueChanged];
    [_progressSlider addTarget:self action:@selector(pause) forControlEvents:UIControlEventTouchDown];// 开始控制,暂停播放
    [_progressSlider addTarget:self action:@selector(play) forControlEvents:UIControlEventTouchUpInside];// 控制结束,播放继续
    [_controlView addSubview:_progressSlider];


    // 进度条跟着视频播放时间 自动滑动
    __weak id weakSelf = self;
    // 周期性回调,实时监听视频播放进度
    [_player addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(0.2f, 24) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {

        AVPlayerView *strongSelf = weakSelf;

        //视频第一次回调 设置slider的长度
        if (time.value/time.timescale == 0) {

            // 获取视频总时长
            CMTime duration = strongSelf->_player.currentItem.duration;
            long second = duration.value/duration.timescale;

            strongSelf->_progressSlider.maximumValue = second;

        }

        //更新slider的value
        long second = time.value/time.timescale;
        strongSelf->_progressSlider.value = second;

    }];
}

#pragma mark - UIActions
// 播放和暂停按钮
-(void)playOrPause:(UIButton *)sender{

    //AVPlayer播放或者暂停 可以调用play方法,也可以直接设置速度 rate
    if (_player.rate == 0) {// rate为0,说明当前的播放状态为暂停
        _player.rate = 1.0f;
    }else{
        _player.rate = 0.0f;
    }
}

-(void)play {

    _player.rate = 1;
}

-(void)pause{

    _player.rate = 0;
}

-(void)sliderAct:(UISlider *)slider{

    int second = slider.value;// slider最大值为视屏的全部时长,所以slider得value值即代表视频当前的播放时间
    // 跳转到某个时间点
    [_player seekToTime:CMTimeMakeWithSeconds(second, 24)];
}

//手势触发 方法
-(void)TapAction:(UIPanGestureRecognizer*)ges {

    if (_controlView.hidden == YES) {

        [UIView animateWithDuration:1 animations:^{

            _controlView.hidden = NO;
            _controlView.alpha = 1;
            _controlView.transform = CGAffineTransformIdentity;

        }];
    }else {

        [UIView animateWithDuration:1 animations:^{

            _controlView.alpha = 0;
            _controlView.transform = CGAffineTransformMakeTranslation(0, 50);
        } completion:^(BOOL finished) {
            _controlView.hidden = YES;
        }];
    }

}

-(void)panAction:(UIPanGestureRecognizer*)ges {

    // 获取手势的触摸点坐标
    CGPoint location = [ges locationInView:self];

    // 判断触摸区域:右边控制音量,左侧控制亮度
    if( location.x>self.bounds.size.width/2){

        // 音量控制
        // 获取手势在y方向的位移距离,计算音量的控制比例
        CGPoint translation = [ges translationInView:self];
        float proportion = translation.y/self.bounds.size.height;

        /*
        // 音量为负值时禁止操作,因为音量取volume属性的绝对值
        if (_player.volume - 1*proportion >= 0 && _player.volume - 1*proportion <= 1) {

            _player.volume -= 1 *proportion /10.f;// 改变播放音量
            NSLog(@"%f",_player.volume);
            // 注意:不要使用volume属性来实现媒体播放的音量滑块。为此,使用MPVolumeView,在外观和提供可定制的标准媒体播放用户期望的行为。这个属性是最有用的iOS控制AVPlayer的体积相对于其他音频输出,由最终用户没有音量控制。
        }
        */

        // 真机演示
        [self changeSystemVolumeWithProportion:proportion];// 改变系统音量

    }else{

        // 亮度控制
        // 计算亮度的控制比例
        CGPoint translation = [ges translationInView:self];
        float proportion = translation.y/self.frame.size.height;

        /*
        if (_maskView.alpha + proportion/10.f >= 0 && _maskView.alpha + proportion/10.f <= 1) {
            _maskView.alpha += proportion/10.f;
            NSLog(@"%f",_maskView.alpha);
        }
         */

        // 真机演示
        [UIScreen mainScreen].brightness -= proportion/10;// 改变屏幕亮度

    }

}

// 根据传入的控制比例值,设置系统声音大小
-(void)changeSystemVolumeWithProportion:(float)proportion{

    // 获取系统音量
    float systemVolume = _systemVolumeSlider.value;
    NSLog(@"%f",systemVolume);// 发现系统音量一直为0

    // 改变系统音量
    [self.systemVolumeSlider setValue:systemVolume - proportion/10 animated:NO];
    [self.systemVolumeSlider sendActionsForControlEvents:UIControlEventTouchUpInside];
}

- (UISlider *)systemVolumeSlider {

    if (!_systemVolumeSlider) {

        // 遍历MPVolumeView子视图,获取到MPVolumeSlider,
        MPVolumeView *volumeView = [[MPVolumeView alloc] init];
        for (UIView *view in volumeView.subviews) {

            if ([view isKindOfClass:NSClassFromString(@"MPVolumeSlider")]) {
                //我们只要能控制MPVolumeSlider类就行了,可是如果我们强制创建这个类是无法实现的,但是没关系,他的baseClass是UISlider我们可以通过这种方法实现
                _systemVolumeSlider = (UISlider *)view;

            }
        }
    }

    return _systemVolumeSlider;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值