Android完美实现视频播放功能

1、前言

视频播放功能在APP开发中有的会用到这个功能,实现视频播放四种方式

(1)系统自带的视频播放器

(2)VideoView播放器(自带开始、停止、暂停等功能,进度条自带不能改变),实现起来较简单

(3)SurfaceView+MediaPlayer实现播放器(SurfaceView是视频播放功能,不带进度条等时间显示功能,需要自己按照需求开发),比VIdeoVIew实现要困难。

(4)使用第三方插件

所以在选择的时候,可以根据自己的需求是否需要自己设计进度条等功能。自己设计可以使用SurfaceView+MediaPlayer的封装实现视频播放功能,大家可以直接拿封装好的代码实现即可,也可以自己优化功能,但是自己设计的播放器总会很简陋,只能实现基本功能,如开始黑屏,点击开始反应慢等情况都会出现,那么出现了已经写好的第三方插件,哎,现在感觉开发Android真是越来越简单了,各种好用的封装遍地。

第三方插件JiaoZiVideoPlayer,GitHub地址:https://github.com/lipangit/JiaoZiVideoPlayer   有8000多个star,相当可以了。下面分别讲解一下自己封装和第三方插件的使用

 

2、MediaPlayer介绍

(1)MediaPlayer就是实现播放的进度条和控制进度等的方法

(2)MediaPlayer的相关方法及监听接口:

方法介绍状态
setDataSource设置数据源Initialized
prepare准备播放,同步Preparing —> Prepared
prepareAsync准备播放,异步Preparing —> Prepared
start开始或恢复播放Started
pause暂停Paused
stop停止Stopped
seekTo到指定时间点位置PrePared/Started
reset重置播放器Idle
setAudioStreamType设置音频流类型--
setDisplay设置播放视频的Surface--
setVolume设置声音--
getBufferPercentage获取缓冲半分比--
getCurrentPosition获取当前播放位置--
getDuration获取播放文件总时间--
内部回调接口介绍状态
OnPreparedListener准备监听Preparing ——>Prepared
OnVideoSizeChangedListener视频尺寸变化监听--
OnInfoListener指示信息和警告信息监听--
OnCompletionListener播放完成监听PlaybackCompleted
OnErrorListener播放错误监听Error
OnBufferingUpdateListener缓冲更新监听--

3、源码实例

(1) 封装的文件一共有四个:封装的class(MySurfaceView),页面Activity(TestMySurfaceView),接口(SurfaceViewListen),和xml文件

(2)Activity代码,其中继承的BaseActivity可修改为Activity

class TestMySurfaceView : BaseActivity(),View.OnClickListener,SurfaceViewListen {
    var surface : MySurfaceView ?=null
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_play_video)
        initView()
    }

    fun initView(){
        surface =  MySurfaceView(surface_view_id,this)
        surface!!.initView()
        surface!!.initPlayer()
        surface!!.setUri("http://112.253.22.157/17/z/z/y/u/zzyuasjwufnqerzvyxgkuigrkcatxr/hc.yinyuetai.com%20/D046015255134077DDB3ACA0D7E68D45.flv")
        surface_view_id!!.setOnClickListener(this)
        tv_backward!!.setOnClickListener(this)
        tv_forward!!.setOnClickListener(this)
    }
    override fun onClick(v: View) {
        when (v.id) {
            R.id.tv_backward -> surface!!.backWard()
            R.id.tv_forward -> surface!!.forWard()
            R.id.surface_view_id -> surface!!.play()
        }
    }

    override fun updateTime(time : Int) {
        tv_start_time!!.setText(TimeUtil.secToTime(time/1000))
        tv_progess!!.setProgress(time)
    }

    override fun preparedPlay(currentPosition: Int, duration: Int) {
        tv_start_time!!.setText(TimeUtil.secToTime(currentPosition/1000))
        tv_end_time!!.setText(TimeUtil.secToTime(duration/1000))
        tv_progess!!.setMax(duration)
        tv_progess!!.setProgress(currentPosition)
    }

    override fun playStatus(status: Int) {
        //当为开始状态时,需要取消结束按钮
        if(status!! == 0){
            playOrPause!!.setVisibility(View.INVISIBLE)
            playOrPause!!.setImageResource(android.R.drawable.ic_media_pause)
            //当为结束状态时
        }else{
            playOrPause!!.setVisibility(View.VISIBLE)
            playOrPause.setImageResource(android.R.drawable.ic_media_play)
        }
    }

}

(3)封装的class代码

class MySurfaceView(val surfaceId : SurfaceView,var listen : SurfaceViewListen ) : SurfaceHolder.Callback, SeekBar.OnSeekBarChangeListener,
        MediaPlayer.OnPreparedListener,
        MediaPlayer.OnCompletionListener,
        MediaPlayer.OnErrorListener,
        MediaPlayer.OnInfoListener,
        MediaPlayer.OnSeekCompleteListener,
        MediaPlayer.OnVideoSizeChangedListener {
    val UPDATE_TIME = 0
    val HIDE_CONTROL = 1
    private var isShow = false
    var path: String? = null
    private var mPlayer: MediaPlayer? = null
    private var surfaceHolder: SurfaceHolder? = null
    private var possion = 0

    //初始化
    fun initView() {
        surfaceHolder = surfaceId!!.holder
        mPlayer =MediaPlayer ()
        surfaceHolder!!.setKeepScreenOn(true)
    }

    //给视频播放框架surfaceView加载网络地址
    fun setUri(uri : String){
        path=uri
    }
    fun initPlayer()
    {
        mPlayer!!.setOnCompletionListener(this)
        mPlayer!!.setOnErrorListener(this)
        mPlayer!!.setOnInfoListener(this)
        mPlayer!!.setOnPreparedListener(this)
        mPlayer!!.setOnSeekCompleteListener(this)
        mPlayer!!.setOnVideoSizeChangedListener(this)
    }

    override fun surfaceCreated(holder: SurfaceHolder) {
        mPlayer!!.setDisplay(holder)
        mPlayer!!.prepare()
        play()
        mPlayer!!.seekTo(possion)
    }

    override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
        //showToastShort("___"+format.toString())
    }

    override fun surfaceDestroyed(holder: SurfaceHolder) {
       // showToastShort("___1")
    }
    //当开始时对视频进行的准备工作
    override fun onPrepared(mp: MediaPlayer) {
        listen!!.preparedPlay(mp.currentPosition,mp.duration)
    }
    //播放完成执行的方法
    override fun onCompletion(mp: MediaPlayer) {
        //showToastShort("完成")
    }

    override fun onError(mp: MediaPlayer, what: Int, extra: Int): Boolean {
        return false
    }

    override fun onInfo(mp: MediaPlayer, what: Int, extra: Int): Boolean {
        return false
    }

    //视频的播放与暂停
    @Throws(IllegalArgumentException::class, SecurityException::class, IllegalStateException::class, IOException::class)
    fun play() {
        if (mPlayer!!.isPlaying()) {
            possion = mPlayer!!.getCurrentPosition()
            mPlayer!!.pause()
            mHandler.removeMessages(UPDATE_TIME)
            mHandler.removeMessages(HIDE_CONTROL)
            listen!!.playStatus(1)
        } else {
            if(possion==0){
                mPlayer!!.setDataSource(path)
                // 把视频输出到SurfaceView上
                mPlayer!!.setDisplay(surfaceHolder)
                //不要用prepareAsync,用了也报错,ERROR(-38,0)
                mPlayer!!.prepare()
            }
          //  mPlayer!!.seekTo(possion)
            mPlayer!!.start()
            mHandler.sendEmptyMessageDelayed(UPDATE_TIME, 500)
            mHandler.sendEmptyMessageDelayed(HIDE_CONTROL, 50000)
            listen!!.playStatus(0)
        }
    }

    override fun onSeekComplete(mp: MediaPlayer) {
        // showToastShort("进度条")
    }

    override fun onVideoSizeChanged(mp: MediaPlayer, width: Int, height: Int) {

    }

    /**
     * 隐藏进度条
     */
    private fun hideControl() {
       // isShow = false
        //mHandler.removeMessages(UPDATE_TIME)
        //control_ll!!.animate().setDuration(300).translationY(control_ll!!.getHeight().toFloat())
    }

    /**
     * 显示进度条
     */
    private fun showControl() {
        if (isShow) {
            play()
        }
        isShow = true
        mHandler.removeMessages(HIDE_CONTROL)
        mHandler.sendEmptyMessage(UPDATE_TIME)
        mHandler.sendEmptyMessageDelayed(HIDE_CONTROL, 50000)
       // control_ll!!.animate().setDuration(300).translationY(0.toFloat())
    }

    /**
     * 设置快进10秒方法
     */
    fun forWard() {
        if (mPlayer!! != null) {
            val position = mPlayer!!.getCurrentPosition()
            mPlayer!!.seekTo(position + 10000)
        }
    }

    /**
     * 设置快退10秒的方法
     */
    fun backWard() {
        if (mPlayer!! != null) {
            var position = mPlayer!!.getCurrentPosition()
            if (position > 10000) {
                position -= 10000
            } else {
                position = 0
            }
            mPlayer!!.seekTo(position)
        }
    }
    //拖动进度条改变时调用
    override fun onProgressChanged(seekBar: SeekBar, progress: Int, b: Boolean) {
        if (mPlayer!! != null && b) {
            mPlayer!!.seekTo(progress)
        }
    }
    //进度条开始时调用
    override fun onStartTrackingTouch(seekBar: SeekBar) {
        //showToastShort("移动进度条开始")
    }

    //进度条结束时调用
    override fun onStopTrackingTouch(seekBar: SeekBar) {
        // showToastShort("移动进度条结束")
    }
    //设置定时器,定时更新播放的时间和进度条功能
    val mHandler = object : Handler() {
        override fun handleMessage(msg: Message) {
            when (msg.what) {
                UPDATE_TIME -> {
                    listen!!.updateTime(mPlayer!!.getCurrentPosition())
                    this.sendEmptyMessageDelayed(UPDATE_TIME, 500)
                }
                HIDE_CONTROL -> hideControl()
            }
        }
    }
}

(4)回调UI的接口文档

interface SurfaceViewListen {
    /**
     * 更新播放时间
     */
    fun updateTime(time : Int)
    //播放成功后返回到UI界面的进度条
    fun preparedPlay(currentPosition : Int,duration : Int)
    //播放状态,开始或者暂停功能,0:代表开始;1:代表暂停
    fun playStatus(status : Int)
}

(5)xml文件(可完全按照自己的要求进行设计)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <RelativeLayout
        android:id="@+id/root_rl"
        android:layout_width="match_parent"
        android:layout_height="400dp"
        android:background="#000000">

        <SurfaceView
            android:id="@+id/surface_view_id"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
        <ImageView
            android:id="@+id/playOrPause"
            android:layout_width="60dp"
            android:layout_height="60dp"
            android:layout_centerInParent="true"
            android:src="@android:drawable/ic_media_play"/>
        <LinearLayout
            android:id="@+id/control_ll"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:background="#005500"
            android:orientation="vertical">

            <RelativeLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginLeft="10dp"
                android:layout_marginRight="10dp"
                android:orientation="horizontal"
                android:paddingBottom="5dp">

                <TextView
                    android:id="@+id/tv_start_time"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_alignParentLeft="true"
                    android:layout_marginLeft="30dp"
                    android:text="00.00"
                    android:textColor="#ffffff"/>
                <TextView
                    android:id="@+id/tv_separate_time"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_toRightOf="@+id/tv_start_time"
                    android:layout_marginLeft="1dp"
                    android:text="/"
                    android:textColor="#ffffff"/>
                <TextView
                    android:id="@+id/tv_end_time"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_toRightOf="@+id/tv_separate_time"
                    android:layout_marginLeft="1dp"
                    android:text="00.00"
                    android:textColor="#ffffff"/>
                <ImageView
                    android:id="@+id/tv_backward"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_below="@+id/tv_start_time"
                    android:layout_alignParentLeft="true"
                    android:layout_marginLeft="1dp"
                    android:src="@android:drawable/ic_media_rew"/>

                <SeekBar
                    android:id="@+id/tv_progess"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_toRightOf="@+id/tv_backward"
                    android:layout_toLeftOf="@+id/tv_forward"
                    android:layout_below="@+id/tv_start_time"/>

                <ImageView
                    android:id="@+id/tv_forward"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_below="@+id/tv_start_time"
                    android:layout_alignParentRight="true"
                    android:layout_marginRight="1dp"
                    android:src="@android:drawable/ic_media_ff"/>

            </RelativeLayout>

        </LinearLayout>
    </RelativeLayout>
</LinearLayout>

四、第三方插件JiaoZiVideoPlayer的使用 

1、首先在build.xml中加载包

implementation 'cn.jzvd:jiaozivideoplayer:6.4.1'

2、编写xml,只需要一个属性就OK

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:orientation="vertical"
    android:layout_height="match_parent">
    <!--饺子第三方插件-->
    <cn.jzvd.JzvdStd
        android:id="@+id/jiaozi_videoplayer"
        android:layout_width="match_parent"
        android:layout_height="200dp"/>
</LinearLayout>

3、在activity中的使用,大功告成

private void initData(){
        JzvdStd jiaozi_videoplayer=findViewById(R.id.jiaozi_videoplayer);
        jiaozi_videoplayer.setUp("http://112.253.22.157/17/z/z/y/u/zzyuasjwufnqerzvyxgkuigrkcatxr/hc.yinyuetai.com%20/D046015255134077DDB3ACA0D7E68D45.flv",
                "视频播放",Jzvd.SCREEN_WINDOW_NORMAL);
//        jiaozi_videoplayer.thumbImageView.setImageResource(R.drawable.default_picture);
    }

    @Override
    public void onBackPressed() {
        if (Jzvd.backPress()) {
            return;
        }
        super.onBackPressed();
    }
    @Override
    protected void onPause() {
        super.onPause();
        Jzvd.releaseAllVideos();
    }

五、总结

其实我自己封装的视频播放功能是我在没发现第三方插件之前封装的,后来对于封装不太满意,才发现了JiaoZiVideoPlayer,一下子啊感觉自己封装的真的很low,建议大家还是用第三方插件封装的吧,功能强大不说,使用起来也特别方便。

 

  • 2
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值