iOS视频播放的基本方法

作者: 梧雨北辰 | 来源:发表于2018-05-22 08:00 被阅读1664次

    本文总结了iOS中最常见的视频播放方法,不同的方法都各具特点,我希望能够总结它们的不同,方便在开发中选择合适的技术方案。

    Apple为我们提供了多种方法来实现视频播放,包括MPMoviePlayerController,MPMoviePlayerViewController,AVPlayer,AVPlayerViewController等。而值得注意的是,上述的MPMoviePlayerController与MPMoviePlayerViewController在iOS9.0之后被弃用。虽说如此,这还是将它们的用法总结了一下,下面我们简单来了解一下四种播放方式的区别:

    iOS播放视频.png

    温馨提示:代码更直观,首先附上本文Demo

    一、MPMoviePlayerController

    1.播放视频

    MPMoviewPlayerController继承于NSObject,使用它播放视频需要将其自带的视频View添加到视图控制器的View上才能显示视频,使用步骤如下:

    第一步:引用MediaPlayer框架,声明视图控制器属性PlayerController
    #import <mediaplayer mediaplayer.h>
    
    @property(nonatomic,strong)MPMoviePlayerController *playerController;
    
    //第二步:获取视频路径,创建播放器
    //本地视频路径
    NSString* localFilePath=[[NSBundle mainBundle]pathForResource:@"不能说的秘密" ofType:@"mp4"];
    NSURL *localVideoUrl = [NSURL fileURLWithPath:localFilePath];
    //网络视频路径
    NSString *webVideoPath = @"http://api.junqingguanchashi.net/yunpan/bd/c.php?vid=/junqing/1115.mp4";
    NSURL *webVideoUrl = [NSURL URLWithString:webVideoPath];
    self.playerController =[[MPMoviePlayerController alloc]initWithContentURL:webVideoUrl];
    
    //第三步:设置Frame将播放器View添加到视图控制器View上
    self.playerController.view.frame = CGRectMake(0, 10, kDeviceWidth, 300);
    [self.view addSubview: self.playerController.view];
    
    //第四步:设置播放器属性
    //设置控制面板风格:无,嵌入,全屏,默认
    self.playerController.controlStyle = MPMovieControlStyleDefault;
    //设置是否自动播放(默认为YES)
    self.playerController.shouldAutoplay = NO;
    //设置播放器显示模式,类似于图片的处理,设置Fill有可能造成部分区域被裁剪
    self.playerController.scalingMode = MPMovieScalingModeAspectFit;
    //设置重复模式
    self.playerController.repeatMode = MPMovieRepeatModeOne;
    
    //第五步:播放视频
    //播放前的准备,会中断当前正在活跃的音频会话
    [ self.playerController  prepareToPlay];
    //播放视频,设置了自动播放之后可以不调用此方法
    //[ self.playerController  play];
    
    //第六步:在退出界面的时候,关闭播放器,移除通知
    - (void)dealloc{
        //当前视图控制器pop之后并不会关闭播放,需要手动关闭
        [self.playerController stop];
        self.playerController = nil;
        //移除播放器相关的通知
        [[NSNotificationCenter defaultCenter] removeObserver:self];
    }
    

    2.视频播放相关的通知

    MPMoviePlayerController有关视频播放的很多状态控制都是通过通知完成的,尤其是播放在线视频的时候,我们不仅监控视频加载是否成功,也会监控是视频缓存进度等。这里演示一些常用的通知如下:

    //关于通知的使用(还有很多通知可以监听,可查看SDK)
    NSNotificationCenter *notificaionCenter = [NSNotificationCenter defaultCenter];
    //监听播放器状态的变化
    [notificaionCenter addObserver:self
                              selector:@selector(playerStateChanged:)
                                  name:MPMoviePlayerPlaybackStateDidChangeNotification
                                object:nil];
    //监听播放完成
    [notificaionCenter addObserver:self
                              selector:@selector(playerFinished) name:MPMoviePlayerPlaybackDidFinishNotification
                                object:nil];
    //监听切换到全屏
    [notificaionCenter addObserver:self
                              selector:@selector(palyerChangeFullScreen) name:MPMoviePlayerDidEnterFullscreenNotification
                                object:nil];
    //监听截屏操作完成
    [notificaionCenter addObserver:self
                              selector:@selector(playerCaptureFinished:) name:MPMoviePlayerThumbnailImageRequestDidFinishNotification
                                object:nil];
    
    #pragma mark - 监听通知的响应方法
    //播放状态变化,注意播放完成时的状态是暂停
    - (void)playerStateChanged:(NSNotification *)notificaion{
        switch (self.playerController.playbackState) {
            case MPMoviePlaybackStateStopped:{
                NSLog(@"播放停止");
                break;
            }
            case MPMoviePlaybackStatePlaying:{
                NSLog(@"播放器正在播放");
                break;
            }
            case MPMoviePlaybackStatePaused:{
                NSLog(@"播放器暂停");
                break;
            }
            case MPMoviePlaybackStateInterrupted:{
                NSLog(@"播放器中断");
                break;
            }
            case MPMoviePlaybackStateSeekingForward:{
                NSLog(@"播放器快进");
                break;
            }
            case MPMoviePlaybackStateSeekingBackward:{
                NSLog(@"播放器快退");
                break;
            }
            default:
                break;
        }
    }
    
    //视频播放结束
    - (void)playerFinished{
        NSLog(@"playerFinished:播放结束");
    }
    
    //播放器切换到了全屏
    - (void)palyerChangeFullScreen{
        NSLog(@"palyerChangeFullScreen:播放器进入全屏");
    }
    
    //播放器截屏结束
    - (void)playerCaptureFinished:(NSNotification *)notification{
        //获取并显示截图
        UIImage *image=notification.userInfo[MPMoviePlayerThumbnailImageKey];
        self.captureImgView.image = image;
    }
    

    3.实现截屏

    //添加一个按钮,点击开始截屏
    _captureBtn = [[UIButton alloc] initWithFrame:CGRectMake(30, CGRectGetMaxY(self.playerController.view.frame) + 30, kDeviceWidth - 30 * 2, 50)];
    _captureBtn.backgroundColor = [UIColor purpleColor];
    [_captureBtn setTitle:@"截图当前屏幕" forState: UIControlStateNormal];
    [_captureBtn addTarget:self action:@selector(captureCurrentScreenImg) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:_captureBtn];
    //添加一个ImgView 显示截屏后的图片
    _captureImgView = [[UIImageView alloc] initWithFrame:CGRectMake((kDeviceWidth - 150)/2, CGRectGetMaxY(_captureBtn.frame) + 20, 150, 150)];
     _captureImgView.contentMode = UIViewContentModeScaleAspectFit;
    _captureImgView.backgroundColor = [UIColor grayColor];
    [self.view addSubview:_captureImgView];
    
    //截取当前屏幕
    - (void)captureCurrentScreenImg{
        [self.playerController requestThumbnailImagesAtTimes:@[@(self.playerController.currentPlaybackTime)] timeOption:MPMovieTimeOptionNearestKeyFrame];
    }
    
    //监听通知:播放器截屏结束,得到图片并显示截图
    - (void)playerCaptureFinished:(NSNotification *)notification{
        UIImage *image=notification.userInfo[MPMoviePlayerThumbnailImageKey];
        self.captureImgView.image = image;
    }
    

    二、MPMoviePlayerViewController

    MPMovicePlayerViewControlle只能全屏幕播放视频,它是一个包含了MPMoviePlayerController类型属性的特殊视图控制器,因此它是通过模态视图弹出的方式显示视频的。理解了这个,我们就可以知道在使用MPMovicePlayerViewController的时候我们可以通过它的MPMoviePlayerController属性设置很多播放器的属性了,具体用法和MPMoviePlayerController相同,就不过多的解释了,播放视频的代码示例如下;

    //第一步:获取视频路径
    //本地视频
    NSString* localFilePath=[[NSBundle mainBundle]pathForResource:@"不能说的秘密" ofType:@"mp4"];
    NSURL *localVideoUrl = [NSURL fileURLWithPath:localFilePath];
    //在线视频
    //NSString *webVideoPath = @"http://api.junqingguanchashi.net/yunpan/bd/c.php?vid=/junqing/1115.mp4";
    //NSURL *webVideoUrl = [NSURL URLWithString:webVideoPath];
    
    //第二步:创建视频播放器
    MPMoviePlayerViewController *playerViewController = [[MPMoviePlayerViewController alloc] initWithContentURL:localVideoUrl];
    
    //第三步:设置播放器属性
    //通过moviePlayer属性设置播放器属性(与MPMoviePlayerController类似)
    playerViewController.moviePlayer.scalingMode = MPMovieScalingModeFill;
    
    //第四步:跳转视频播放界面
    [self presentViewController:playerViewController animated:YES completion:nil];
    

    三、AVPlayer

    AVPlayer相比上述两种方式,播放视频功能更加强大,使用也十分灵活,因为它更加接近底层。但是AVPlayer本身是不能直接显示视频的,必须创建一个播放层AVPlayerLayer并将其添加到其他的视图Layer上才能显示。

    1. 使用AVPlayer需要了解的常用类

    • AVAsset:一个用于获取多媒体信息的抽象类,但不能直接使用
    • AVURLAsset:AVAsset的子类,可以根据一个URL路径创建一个包含媒体信息的AVURLAsset对象
    • AVPlayerItem:一个媒体资源管理对象,用于管理视频的基本信息和状态,一个AVPlayerItem对应一个视频资源
    • AVPlayer:负责视频播放、暂停、时间控制等操作
    • AVPlayerLayer:负责显示视频的图层,如果不设置此属性,视频就只有声音没有图像

    2. AVPlayer的使用步骤

    //第一步:引用AVFoundation框架,添加播放器属性
    #import <AVFoundation/AVFoundation.h>
    @property (nonatomic,strong)AVPlayer *player;//播放器对象
    @property (nonatomic,strong)AVPlayerItem *currentPlayerItem;
    
    //第二步:获取播放地址URL
    //本地视频路径
    NSString* localFilePath=[[NSBundle mainBundle]pathForResource:@"不能说的秘密" ofType:@"mp4"];
    NSURL *localVideoUrl = [NSURL fileURLWithPath:localFilePath];
    //网络视频路径
    NSString *webVideoPath = @"http://api.junqingguanchashi.net/yunpan/bd/c.php?vid=/junqing/1129.mp4";
    NSURL *webVideoUrl = [NSURL URLWithString:webVideoPath];
    
    //第三步:创建播放器(四种方法)
    //如果使用URL创建的方式会默认为AVPlayer创建一个AVPlayerItem
    //self.player = [AVPlayer playerWithURL:localVideoUrl];
    //self.player = [[AVPlayer alloc] initWithURL:localVideoUrl];
    //self.player = [AVPlayer playerWithPlayerItem:playerItem];
    AVPlayerItem *playerItem = [[AVPlayerItem alloc] initWithURL:webVideoUrl];
    self.currentPlayerItem = playerItem;
    self.player = [[AVPlayer alloc] initWithPlayerItem:playerItem];
    
    //第四步:创建显示视频的AVPlayerLayer,设置视频显示属性,并添加视频图层
    //contentView是一个普通View,用于放置视频视图
    /*
      AVLayerVideoGravityResizeAspectFill等比例铺满,宽或高有可能出屏幕
      AVLayerVideoGravityResizeAspect 等比例  默认
      AVLayerVideoGravityResize 完全适应宽高
    */
    AVPlayerLayer *avLayer = [AVPlayerLayer playerLayerWithPlayer:self.player];
    avLayer.videoGravity = AVLayerVideoGravityResizeAspect;
    avLayer.frame = _containerView.bounds;
    [_containerView.layer addSublayer:avLayer];
    
    //第六步:执行play方法,开始播放
    //本地视频可以直接播放
    //网络视频需要监测AVPlayerItem的status属性为AVPlayerStatusReadyToPlay时方法才会生效
    [self.player play];
    

    3. 添加属性观察

    一个AVPlayerItem对象对应着一个视频,我们需要通过AVPlayerItem来获取视频属性。但是AVPlayerItem必须是在视频资源加载到可以播放的时候才能使用,这是受限于网络的原因。解决这一问题,我们需要使用KVO监测AVPlayerItem的status属性,当其为AVPlayerItemStatusReadyToPlay的时候我们才能获取视频相关属性。相关的代码示例如下:

    //1.注册观察者,监测播放器属性
    //观察Status属性,可以在加载成功之后得到视频的长度
    [self.player.currentItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
    //观察loadedTimeRanges,可以获取缓存进度,实现缓冲进度条
    [self.player.currentItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];
    
    //2.添加属性观察
    - (void)observeValueForKeyPath:(NSString *)keyPath
                          ofObject:(id)object
                            change:(NSDictionary *)change
                           context:(void *)context {
        AVPlayerItem *playerItem = (AVPlayerItem *)object;
        if ([keyPath isEqualToString:@"status"]) {
            //获取playerItem的status属性最新的状态
            AVPlayerStatus status = [[change objectForKey:@"new"] intValue];
            switch (status) {
                case AVPlayerStatusReadyToPlay:{
                    //获取视频长度
                    CMTime duration = playerItem.duration; 
                    //更新显示:视频总时长(自定义方法显示时间的格式)
                    self.totalNeedPlayTimeLabel.text = [self formatTimeWithTimeInterVal:CMTimeGetSeconds(duration)];
                    //开启滑块的滑动功能
                    self.sliderView.enabled = YES;
                    //关闭加载Loading提示
                    [self showaAtivityInDicatorView:NO];
                    //开始播放视频
                    [self.player play];
                    break;
                }
                case AVPlayerStatusFailed:{//视频加载失败,点击重新加载
                    [self showaAtivityInDicatorView:NO];//关闭Loading视图
                    self.playerInfoButton.hidden = NO; //显示错误提示按钮,点击后重新加载视频
                    [self.playerInfoButton setTitle:@"资源加载失败,点击继续尝试加载" forState: UIControlStateNormal];
                    break;
                }
                case AVPlayerStatusUnknown:{
                    NSLog(@"加载遇到未知问题:AVPlayerStatusUnknown");
                    break;
                }
                default:
                    break;
            }
        } else if ([keyPath isEqualToString:@"loadedTimeRanges"]) {
            //获取视频缓冲进度数组,这些缓冲的数组可能不是连续的
            NSArray *loadedTimeRanges = playerItem.loadedTimeRanges;
            //获取最新的缓冲区间
            CMTimeRange timeRange = [loadedTimeRanges.firstObject CMTimeRangeValue];
            //缓冲区间的开始的时间
            NSTimeInterval loadStartSeconds = CMTimeGetSeconds(timeRange.start);
            //缓冲区间的时长
            NSTimeInterval loadDurationSeconds = CMTimeGetSeconds(timeRange.duration);
            //当前视频缓冲时间总长度
            NSTimeInterval currentLoadTotalTime = loadStartSeconds + loadDurationSeconds;
            //NSLog(@"开始缓冲:%f,缓冲时长:%f,总时间:%f", loadStartSeconds, loadDurationSeconds, currentLoadTotalTime);
            //更新显示:当前缓冲总时长
            _currentLoadTimeLabel.text = [self formatTimeWithTimeInterVal:currentLoadTotalTime];
            //更新显示:视频的总时长
            _totalNeedLoadTimeLabel.text = [self formatTimeWithTimeInterVal:CMTimeGetSeconds(self.player.currentItem.duration)];
            //更新显示:缓冲进度条的值
            _progressView.progress = currentLoadTotalTime/CMTimeGetSeconds(self.player.currentItem.duration);
        }
    }
    
    //转换时间格式的方法
    - (NSString *)formatTimeWithTimeInterVal:(NSTimeInterval)timeInterVal{
        int minute = 0, hour = 0, secend = timeInterVal;
        minute = (secend % 3600)/60;
        hour = secend / 3600;
        secend = secend % 60;
        return [NSString stringWithFormat:@"%02d:%02d:%02d", hour, minute, secend];
    }
    

    4. 获取当前播放时间与总时间

    在此之前我们需要首先了解一个数据类型,也就是上述操作中的CMTime, 在AVPlayer的使用中我们会经常用到它,其实CMTime是一个结构体如下:

            typedef struct{
                CMTimeValue    value;      // 帧数
                CMTimeScale    timescale;  // 帧率(影片每秒有几帧)
                CMTimeFlags    flags;
                CMTimeEpoch    epoch;
            } CMTi
    

    在上面的操作中我们看到AVPlayerItem的Duration属性就是一个CMTime类型的数据。所以获取视频的总时长(秒)需要duration.value/duration.timeScale。当然系统也为我们提供了CMTimeGetSeconds函数更加方便计算:

    总时长: duration.value == CMTimeGetSeconds(duration) 。
    

    在快进视频到某一个位置的时候我们也需要创建CMTime作为参数,那么CMTime的创建方法有两种:

    //方法1:
    CMTimeMakeWithSeconds(Flout64 seconds, int32_t scale)
    //方法2:
    CMTimeMake(int64_t value, int32_t scale)
    //注:两者的区别在于方法一的第一个参数可以是float
    

    至于获取视频的总时间在上述代码中已有体现,是在检测播放状态变为AVPlayerStatusReadyToPlay的时候获取的

    //视频总时长,在AVPlayerItem状态为AVPlayerStatusReadyToPlay时获取
    CMTime duration = self.player.currentItem.duration;
    CGFloat totalTime = CMTimeGetSeconds(duration);
    //当前AVPlayer的播放时长
    CMTime cmTime = self.player.currentTime;
    CGFloat currentTime  = CMTimeGetSeconds(cmTime);
    

    5. 播放进度与状态的刷新

    实时更新当前播放时间,这时候我们不必使用定时器,因为AVPlayer已经提供了方法:
    addPeriodicTimeObserverForInterval: queue: usingBlock。当播放进度改变的时候方法中的回调会被执行。我们可以在这里做刷新时间的操作,代码示例如下:

    __weak __typeof(self) weakSelf = self;
    [self.player addPeriodicTimeObserverForInterval:CMTimeMake(1, 1) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
         //当前播放的时间
         NSTimeInterval currentTime = CMTimeGetSeconds(time);
         //视频的总时间
         NSTimeInterval totalTime = CMTimeGetSeconds(weakSelf.player.currentItem.duration);
         //设置滑块的当前进度
         weakSelf.sliderView.value = currentTime/totalTime;
         //设置显示的时间:以00:00:00的格式
         weakSelf.currentTimeLabel.text = [weakSelf formatTimeWithTimeInterVal:currentTime];
        }];
    

    6. 滑块拖拽修改视频播放进度

    //UISlider的响应方法:拖动滑块,改变播放进度
    - (IBAction)sliderViewChange:(id)sender {
        if(self.player.status == AVPlayerStatusReadyToPlay){
            NSTimeInterval playTime = self.sliderView.value * CMTimeGetSeconds(self.player.currentItem.duration);
            CMTime seekTime = CMTimeMake(playTime, 1);
            [self.player seekToTime:seekTime completionHandler:^(BOOL finished) {
            }];
        }
    }
    

    四、AVPlayerViewController

    AVPlayerViewController是iOS8新增视频框架AVKit中的一个播放器类。由于iOS9弃用前两种播放器类的原因,AVPlayerViewController也将变得更加常用。AVPlayerViewController适合开发播放界面要求不是很高的应用。其相比AVPlayer的使用更加方便,但是原理上还是AVPlayerViewController包含了一个AVPlayer对象。
    AVPlayerViewController有两种播放视频的方式:

    第一种:直接弹出模态视图控制器播放

    //步骤1:获取视频路径
    NSString *webVideoPath = @"http://api.junqingguanchashi.net/yunpan/bd/c.php?vid=/junqing/1213.mp4";
    NSURL *webVideoUrl = [NSURL URLWithString:webVideoPath];
    //步骤2:创建AVPlayer
    AVPlayer *avPlayer = [[AVPlayer alloc] initWithURL:webVideoUrl];
    //步骤3:使用AVPlayer创建AVPlayerViewController,并跳转播放界面
    AVPlayerViewController *avPlayerVC =[[AVPlayerViewController alloc] init];
    avPlayerVC.player = avPlayer;
    [self presentViewController:avPlayerVC animated:YES completion:nil];
    

    第二种:添加AVPlayerViewController的View到父视图上播放。

    使用这种方式播放的优点在于可以指定播放界面的原始尺寸大小,但是值得注意的是AVPlayerViewController必须被当前视图控制器所持有,以防止被当做局部变量被释放。为了满足这一条件,我们可以将AVPlayerViewController作为属性,也可以使用addChildViewController方法将其作为当前视图控制器的子视图控制器,示例代码如下:

    //步骤1:获取视频路径
    NSString *webVideoPath = @"http://api.junqingguanchashi.net/yunpan/bd/c.php?vid=/junqing/1213.mp4";
    NSURL *webVideoUrl = [NSURL URLWithString:webVideoPath];
    //步骤2:创建AVPlayer
    AVPlayer *avPlayer = [[AVPlayer alloc] initWithURL:webVideoUrl];
    //步骤3:使用AVPlayer创建AVPlayerViewController,并跳转播放界面
    AVPlayerViewController *avPlayerVC =[[AVPlayerViewController alloc] init];
    avPlayerVC.player = avPlayer;
    //步骤4:设置播放器视图大小
    avPlayerVC.view.frame = CGRectMake(25, 0, 320, 300);
    //特别注意:AVPlayerViewController不能作为局部变量被释放,否则无法播放成功
    //解决1.AVPlayerViewController作为属性
    //解决2:使用addChildViewController,AVPlayerViewController作为子视图控制器
    [self addChildViewController:avPlayerVC];
    [self.view addSubview:avPlayerVC.view];
    

    最后总结:

    以上就是iOS视频播放的基本方法,但这里也仅限一些基础的播放需求。若要实现更为复杂的播放功能,仍然有很多东西需要我们继续深入研究,加油!

    相关文章

      网友评论

      本文标题:iOS视频播放的基本方法

      本文链接:https://www.haomeiwen.com/subject/lucrgxtx.html