Kotlin实现SurfaceView+MediaPlayer封装自定义视频播放功能

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/f552126367/article/details/84063898

1、前言

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

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

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

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

所以在选择的时候,可以根据自己的需求是否需要自己设计进度条等功能。而本篇文章讲的是SurfaceView+MediaPlayer的封装实现视频播放功能,大家可以直接拿封装好的代码实现即可,也可以自己优化功能,实现起来更加简单。

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>

猜你喜欢

转载自blog.csdn.net/f552126367/article/details/84063898