iOS 视频播放器

作者: Dev_hj | 来源:发表于2017-04-08 14:17 被阅读977次

    播放页面效果:

    play.png

    实现了控件的自定义,手势快进,但是没有添加音量的改变的操作。

    播放是使用了ijkplayer没有使用AVPlayer,了解ijkplayer的封装成framework可以去看ijkplayer封装

    进入正题

    文章只是提供了一种实现自定义的思路,有错误的地方和有更好的实现方案欢迎大家提出。
    

    视频的读取是从iTunes导入到Documents文件夹下。

    ijkplayer播放视频只需要设置

    var playerView : IJKMediaPlayback!
    
    self.playerView = IJKFFMoviePlayerController.init(contentURL: URL.init(string: self.selectedFilms[self.currentUrl].fileUrl)!, with: nil)
    
    self.view.insertSubview(self.playerView.view, at: 0)
    

    播放功能就这样的实现啦!

    定时器

    观看进度需要实时的改变,观看进度也需要改变,所以需要一个定时器

    self.timer = Timer.init(timeInterval: 1, target: self, selector: #selector(MXPlayViewController.nextSecond), userInfo: nil, repeats: true)
     //添加到主循环
    RunLoop.main.add(self.timer, forMode: RunLoopMode.defaultRunLoopMode)
    

    但是什么时候启动最好呢?启动太早和太晚可能时间会不匹配。

    player有一个状态监听,它可能出现的状态为:

    IJKMPMoviePlaybackStateStopped,        停止
    IJKMPMoviePlaybackStatePlaying,        正在播放
    IJKMPMoviePlaybackStatePaused,         暂停
    IJKMPMoviePlaybackStateInterrupted,    打断
    IJKMPMoviePlaybackStateSeekingForward, 快进
    IJKMPMoviePlaybackStateSeekingBackward 快退
    

    在开始播放的时候启动定时器是正好的:

            switch self.playerView.playbackState {
            case .stopped:
                //播放完成
                //置空
                self.timer.invalidate()
                self.timer = nil
                //设置播放完成
                self.isComplete = true
                self.isSeeking = false
                //自动播放下一个资源
                self.slideNextSource()
                break
            case .paused:
                //暂停
                //停止计时器
                self.isComplete = false
                self.isSeeking = false
                break
            case .interrupted:
                //中断,资源问题
                break
            case .seekingBackward:
                //后退
                self.isComplete = false
                self.isSeeking = true
                self.playAndSettingBottomView()
                break
            case .seekingForward:
                //快进
                self.isComplete = false
                self.isSeeking = true
                self.playAndSettingBottomView()
                break
            default:
                //playing
                self.isComplete = false
                self.isSeeking = false
                self.playAndSettingBottomView()
                //设置进度
                if self.isFirstEnter && !self.isBecomeActive{
                    //必须在加载完毕之后才能设置
                    //设定播放位置
                    //指定位置开始播放
                    if self.currentTime != 0 {
                        //从头开始,如果看到最后一秒
                        if self.currentTime >= Int.init(self.playerView.duration) {
                            self.currentTime = 0
                            self.selectedFilms[self.currentUrl].seeingTime = 0
                        }
                    }
                    self.isFirstEnter = false
                }
                break
            }
    
    

    滑动手势监听,然后根据移动的距离转换时间的秒数:

    (translation / self.playerView.frame.size.width ) * 总时间
    

    添加手势:

    //滑动手势self.playerView.view.addGestureRecognizer(UIPanGestureRecognizer.init(target: self, action: #selector(MXPlayViewController.translationToSeekTime(gesture:))))
    
     //获取位移
    let translation = gesture.translation(in: self.playerView.view)
    //前进/后退的秒数,有正负之分
    let forwardSecond = translation.x
    //转换算法
    let scale = forwardSecond / self.playerView.view.frame.size.width
                
    let destinationSecond = Double(scale) * self.playerView.duration
    //时间
    var time = self.currentTime + Int(destinationSecond)
                
    //判断时间是否过线
    if time < 0 {
       time = 0
     }else if time > Int(self.playerView.duration){
       time = Int(self.playerView.duration)
    }         
    let destinationTime = MXFilmViewModel.durationToTime(time: time)
                
    self.operationProgressLabel.text = String.init(format: "%@/%@", destinationTime,MXFilmViewModel.durationToTime(time: Int(self.playerView.duration)))
                
    

    有一个需要注意的是,滑动的时候应该停止定时器的运行,所以在手势的回调判断状态

     switch gesture.state {
            case .began:
                //快进不需要停止
                //停止定时器
                //self.timer.invalidate()
                //self.timer = nil
                //显示进度提示1
                self.operationProgressLabel.alpha = 0.6
                //当前时间
                let currentTimeString = MXFilmViewModel.durationToTime(time: self.currentTime)
                
                self.operationProgressLabel.text = String.init(format: "%@/%@", currentTimeString,currentTimeString)
                break
            case .cancelled:
                
                break
                
            case .changed:
                //改变值
                //获取位移
                let translation = gesture.translation(in: self.playerView.view)
                //前进/后退的秒数,有正负之分
                let forwardSecond = translation.x
                //转换算法
                let scale = forwardSecond / self.playerView.view.frame.size.width
                
                let destinationSecond = Double(scale) * self.playerView.duration
                //时间
                var time = self.currentTime + Int(destinationSecond)
                
                //判断时间是否过线
                if time < 0 {
                    time = 0
                }else if time > Int(self.playerView.duration){
                    time = Int(self.playerView.duration)
                }
                
                let destinationTime = MXFilmViewModel.durationToTime(time: time)
                
                self.operationProgressLabel.text = String.init(format: "%@/%@", destinationTime,MXFilmViewModel.durationToTime(time: Int(self.playerView.duration)))
                
                break
                
            case .ended:
                //停止显示快进/快退提示
                self.operationProgressLabel.alpha = 0
                
                if self.playerView.isPlaying() {
    
                    //判断是左移还是右移
                    
                    //获取位移
                    let translation = gesture.translation(in: self.playerView.view)
                    //前进/后退的秒数,有正负之分
                    let forwardSecond = translation.x
                    //转换算法
                    let scale = forwardSecond / self.playerView.view.frame.size.width
                    
                    var destinationSecond = Double(scale) * self.playerView.duration
                    //判断时间是否符合
                    if destinationSecond + self.playerView.currentPlaybackTime > self.playerView.duration {
                        destinationSecond = self.playerView.duration - self.playerView.currentPlaybackTime - 1
                    }else if destinationSecond + self.playerView.currentPlaybackTime < 0 {
                        destinationSecond = -self.playerView.currentPlaybackTime
                    }
                    //执行快进
                    self.playerView.currentPlaybackTime += destinationSecond
                    //同时设置相关属性
                    self.selectedFilms[self.currentUrl].seeingTime += Int(destinationSecond)
                    self.currentTime += Int(destinationSecond)
                    //要通知BottomView进行进度条移动,快进了多少秒
                    //改变ToalSecond和mutile约束改变倍率
                    self.bottomView.totalSecond -= Int(destinationSecond)
                    self.bottomView.multiple = CGFloat(destinationSecond)
                    //
                }
                break
            case .failed:
                
                break
                
            default:
                
                break
            }
    

    控制视图在一定时间内会隐藏,然后监听屏幕的点击事件进行隐藏或者显示。

     self.playerView.view.addGestureRecognizer(UITapGestureRecognizer.init(target: self, action: #selector(MXPlayViewController.tapInPlayView)))
    
        func tapInPlayView(){
            if self.isShowsControlTool {
                //隐藏
                self.hideAnimation()
            }else{
                //显示
                self.showsAnimation()
                //启动一个延时操作,如果没有是手动取消
                DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 3, execute: {
                    //隐藏
                    if self.isShowsControlTool {
                        //隐藏
                        self.hideAnimation()
                    }
                })
            }
            self.isShowsControlTool = !self.isShowsControlTool
        }
    

    因为有了滑动快进之后,所以下面的bottomView左右按钮点击我设置成了source切换。

    topView,bottomView分别创建两个View去控制,然后通过代理回调。

    创建一个枚举,设置可能的操作类型

    enum MXPlayBottomOperationType {
        case left
        case right
        case start
        case pause
        case seekTime
    }
    
    //前进
        func goNext(){
            //回退
            //通知代理切换位置
            if self.delegate != nil {
                self.delegate.operation(with: self, type: .right, seekTime: nil, completion: {
                    //刷新UI
                    
                })
            }
        }
        //后退
        func backFormer(){
            //回退
            //通知代理切换位置
            if self.delegate != nil {
                self.delegate.operation(with: self, type: .left, seekTime: nil, completion: {
                    //刷新UI
                    
                })
            }
        }
        //开始/暂停
        func startOrpause(){
            if self.isPlaying {
                //停止
                //设置按钮
                self.playOrPauseButton.setBackgroundImage(UIImage.init(named: "pause"), for: UIControlState.normal)
                //通知代理切换位置
                if self.delegate != nil {
                    self.delegate.operation(with: self, type: .pause, seekTime: nil, completion: {
                        //刷新UI
                        
                    })
                }
            }else{
                //开始
                //设置按钮
                self.playOrPauseButton.setBackgroundImage(UIImage.init(named: "start"), for: UIControlState.normal)
                //通知代理切换位置
                if self.delegate != nil {
                    self.delegate.operation(with: self, type: .start, seekTime: nil, completion: {
                        //刷新UI
                        
                    })
                }
            }
            
            self.isPlaying = !self.isPlaying
        }
    
    

    切换资源的时候应该保存进度到数据库

        //slide source
        func slideNextSource(){
            //只有一个电影的时候
            guard self.selectedFilms.count != 1 else {
                SVProgressHUD.showError(withStatus: "没有更多电影了")
                self.settingTimer()
                return
            }
            //先停止播放
            self.playerView.shutdown()
            //刷新并保存
            self.updateAndSave()
            //加载下一个资源
            self.currentUrl += 1
            if self.currentUrl >= self.selectedFilms.count {
                //提示信息
                SVProgressHUD.showError(withStatus: "没有更多电影了")
                
                self.currentUrl = 0
            }
            //改变属性
            self.isFirstEnter = true
            self.isNewSource = true
            //加载
            self.playMovie()
            //修改状态栏
            self.topView.titleLable.text = self.selectedFilms[self.currentUrl].name
            //当前进度什么都需要更改
            
            //播放
            if !self.playerView.isPlaying() {
                self.playerView.prepareToPlay()
            }
        }
        
        func slideFormerSource(){
            //只有一个电影的时候
            guard self.selectedFilms.count != 1 else {
                SVProgressHUD.showError(withStatus: "没有更多电影了")
                self.settingTimer()
                return
            }
            //先停止播放
            self.playerView.shutdown()
            //刷新并保存
            self.updateAndSave()
            //加载下一个资源
            self.currentUrl -= 1
            if self.currentUrl < 0{
                //提示信息
                SVProgressHUD.showError(withStatus: "没有更多电影了,重新播放")
                
                self.currentUrl = self.selectedFilms.count - 1
            }
            self.isFirstEnter = true
            self.isNewSource = true
            //加载
            self.playMovie()
            //修改状态栏
            self.topView.titleLable.text = self.selectedFilms[self.currentUrl].name
            //播放
            if !self.playerView.isPlaying() {
                self.playerView.prepareToPlay()
            }
        }
    

    保存到数据库的方法为:

        func updateAndSave(){
            //添加到数据库
            if self.isComplete {
                self.selectedFilms[self.currentUrl].seeingTime = 0
            }
            
            //就刷新
            self.selectedFilms[self.currentUrl].isSeeing = true
            operationDataBase(film: self.selectedFilms[self.currentUrl], type: .updateSeeingTime)
        }
        
    

    这就是播放的核心代码了,完整代码上传到了GitHub上,大家可以下载看看。

    完整的项目包括了播放分类,主要是区分了未播放,历史播放,
    加密的栏目,自定义转场动画,布局是使用纯代码布局的,没有使用storyboard。
    已经发布在了App Store,
    地址为(https://itunes.apple.com/us/app/mxplayer/id1211708601?l=zh&ls=1&mt=8)。
    
    new.png history.png security.png

    相关文章

      网友评论

        本文标题:iOS 视频播放器

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