美文网首页学习学习之鸿蒙&Android
Android之媒体探究(二)—— 视频

Android之媒体探究(二)—— 视频

作者: 乌托邦式的爱情 | 来源:发表于2021-08-23 16:52 被阅读0次

    在上一篇讲到了Android中关于音频的一些入门级的知识点,接下来就看看Android中关于视频的使用。需要说明的,对于android来说,其音频和视频在底层使用的是同一个东西,在上一篇关于音频的使用过程中,我们使用到的框架有两个,MediaPlayer和ExoPlayer,还不了解这两个的可以去看看我上一篇文章Android之媒体探究(一)—— 音频。而在这篇视频里面,我们同样使用的也是这两个。废话不多说,先看下效果。因为录制的视频有点长,所以录制为两段连着看。

    视频播放1.gif 视频播放2.gif

    正文

    其实实现视频播放的方式有很多种,这里我只是简单的列举了几种供大家作为入门的参考,实际在项目里面实现视频播放要复杂的多,因为要考虑很多很多的东西,比如声道、屏幕比例大小、清晰度等等,但是万变不离其宗,所有复杂的东西都是从一个一个小点开始的,所以这篇文章不会讲很深的东西而是带大家入个门。

    实现视频播放的方法

    (一)使用VideoView + MediaController
    (二)使用MediaPlayer + SurfaceView
    (三)使用MediaPlayer + SurfaceView + MediaController
    (四)使用ExoPlayer

    视频播放原理

    系统会首先确定视频的格式,然后得到视频的编码..然后对编码进行解码,得到一帧一帧的图像,最后在画布上进行迅速更新,显然需要在独立的线程中完成。

    VideoView

    VideoView是系统提供的一个用于最快速接入视频播放的View,VideoView是继承自SurfaceView的,其实真正用于播放视频的是MediaPlayer+SurfaceView,VideoView只是将这两者封装起来,便于开发者使用。

    SurfaceView

    我们知道,在android里面,系统提供了View进行绘画处理,基本上大多数时候我们使用的所有的控件都是继承自ViewGroup或者View,而又因为ViewGroup又是继承自View,所以对于大多数的控件来说,其都是继承于View。那么,在android里面有没有不是继承于View的呢?其实是有的,就是我们今天的主角——SurfaceView。我们知道View是通过刷新来重绘视图,系统通过发出VSSYNC信号来进行屏幕的重绘,刷新的时间间隔是16ms,如果我们可以在16ms以内将绘制工作完成,则没有任何问题,如果我们绘制过程逻辑很复杂,并且我们的界面更新还非常频繁,这时候就会造成界面的卡顿,影响用户体验,为此Android提供了SurfaceView来解决这一问题

    View和SurfaceView的区别

    1 . View适用于主动更新的情况,而SurfaceView则适用于被动更新的情况,比如频繁刷新界面。
    2 . View在主线程中对页面进行刷新,而SurfaceView则开启一个子线程来对页面进行刷新。
    3 . View在绘图时没有实现双缓冲机制,SurfaceView在底层机制中就实现了双缓冲机制。

    双缓冲技术

    双缓冲技术是游戏开发中的一个重要的技术。当一个动画争先显示时,程序又在改变它,前面还没有显示完,程序又请求重新绘制,这样屏幕就会不停地闪烁。而双缓冲技术是把要处理的图片在内存中处理好之后,再将其显示在屏幕上。双缓冲主要是为了解决 反复局部刷屏带来的闪烁。把要画的东西先画到一个内存区域里,然后整体的一次性画出来。

    MediaController

    一个包含媒体播放器(MediaPlayer)控件的视图。包含了一些典型的按钮,像"播放(Play)/暂停(Pause)", "倒带(Rewind)", "快进(Fast Forward)"与进度滑动器(progress slider)。它管理媒体播放器(MediaController)的状态以保持控件的同步。

    通过编程来实例化使用这个类。这个媒体控制器将创建一个具有默认设置的控件,并把它们放到一个窗口里漂浮在你的应用程序上。具体来说,这些控件会漂浮在通过setAnchorView()指定的视图上。如果这个窗口空闲3秒那么它将消失,直到用户触摸这个视图的时候重现。

    好,介绍完上面的几个重要概念,下面就直接进入编码阶段了。

    (一)使用VideoView + MediaController

    XML文件

    <androidx.constraintlayout.widget.ConstraintLayout 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"
        tools:context=".MediaControllerVideoViewActivity">
    
        <RelativeLayout
            android:id="@+id/video_view_container"
            android:layout_width="match_parent"
            android:layout_height="205dp"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    
    </androidx.constraintlayout.widget.ConstraintLayout>
    

    实现

    class MediaControllerVideoViewActivity : AppCompatActivity() {
    
        private var video_view: VideoView? = null
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_media_controller_video_view)
    
        }
    
        override fun onResume() {
            super.onResume()
            mediaControllerVideoView()
        }
    
        /**
         * 通过MediaController控制界面中的快进、快退、暂停、播放等
         * 控制器是由系统提供的,无法进行自定义
         */
        private fun mediaControllerVideoView() {
            if (video_view_container != null) {
                video_view_container.removeAllViews()
            }
            video_view = VideoView(applicationContext)
            video_view_container.addView(video_view)
            val path = "https://video-qn.51miz.com/preview/video/00/00/12/30/V-123077-95B77BE6.mp4"
            video_view!!.setVideoPath(path)
            val mediaController = MediaController(this)
            video_view!!.setMediaController(mediaController)
            video_view!!.start()
            video_view!!.requestFocus()
        }
    
       /**
        * Activity关闭的时候释放资源
        */
        override fun onDestroy() {
            super.onDestroy()
            video_view!!.stopPlayback()
            video_view_container.removeAllViews()
            video_view = null
        }
    }
    
    (二)使用MediaPlayer + SurfaceView

    XML文件

    <RelativeLayout 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"
        tools:context=".MediaVideoActivity">
    
    
        <SurfaceView
            android:id="@+id/surfaceView"
            android:layout_width="match_parent"
            android:layout_height="200dp"/>
    
        <LinearLayout
            android:id="@+id/media_type"
            android:layout_width="match_parent"
            android:layout_height="72dp"
            android:layout_alignParentBottom="true"
            android:background="@color/dialog_title_text_color"
            android:orientation="horizontal">
    
    
            <RelativeLayout
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1">
    
                <ImageView
                    android:id="@+id/last_song"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_centerInParent="true"
                    android:src="@drawable/last_song" />
            </RelativeLayout>
    
            <RelativeLayout
                android:id="@+id/media_play"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1">
    
                <ImageView
                    android:id="@+id/media_song"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_centerInParent="true"
                    android:src="@drawable/media_pause_day" />
            </RelativeLayout>
    
            <RelativeLayout
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1">
    
                <ImageView
                    android:id="@+id/next_song"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_centerInParent="true"
                    android:src="@drawable/next_song" />
            </RelativeLayout>
        </LinearLayout>
    
        <RelativeLayout
            android:id="@+id/seek_bar_layout"
            android:layout_width="wrap_content"
            android:layout_above="@+id/media_type"
            android:layout_height="50dp">
    
            <TextView
                android:id="@+id/media_book_start_time"
                android:layout_width="50dp"
                android:layout_height="wrap_content"
                android:layout_centerVertical="true"
                android:paddingLeft="15dp"
                android:textColor="@color/colorAccent"
                android:textSize="12sp" />
    
            <TextView
                android:id="@+id/media_book_total_time"
                android:layout_width="50dp"
                android:layout_height="wrap_content"
                android:layout_alignParentRight="true"
                android:layout_centerVertical="true"
                android:paddingRight="15dp"
                android:textColor="@color/colorAccent"
                android:textSize="12sp" />
    
            <SeekBar
                android:id="@+id/seek_bar"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_centerVertical="true"
                android:layout_toLeftOf="@+id/media_book_total_time"
                android:layout_toRightOf="@+id/media_book_start_time"
                android:background="@null"
                android:maxHeight="1dp"
                android:progress="0"
                android:progressDrawable="@drawable/seek_bar_bg"
                android:splitTrack="false"
                android:thumb="@drawable/seekbar_thumb" />
        </RelativeLayout>
    
    </RelativeLayout>
    

    实现

    class MediaVideoActivity : AppCompatActivity(), SurfaceHolder.Callback, MediaPlayer.OnCompletionListener, MediaPlayer.OnErrorListener, MediaPlayer.OnInfoListener, MediaPlayer.OnPreparedListener, MediaPlayer.OnSeekCompleteListener, MediaPlayer.OnVideoSizeChangedListener, SeekBar.OnSeekBarChangeListener, MediaPlayer.OnBufferingUpdateListener {
    
        private var mMediaPlayer: MediaPlayer? = null
    
        companion object {
            const val UPDATE_TIME = 0x0001
        }
    
        private val mHandler: Handler = object : Handler() {
            override fun handleMessage(msg: Message) {
                when (msg.what) {
                    UPDATE_TIME -> {
                        updateTime()
                        sendEmptyMessageDelayed(UPDATE_TIME, 500)
                    }
                }
            }
        }
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_media_video)
    
        }
    
        override fun onResume() {
            super.onResume()
            mediaPlayerSurfaceView()
        }
    
    
        /**
         * MediaPlayer+SurfaceView+自定义控制器
         * 所有的界面控制器由自己定义
         */
        private fun mediaPlayerSurfaceView() {
            val path = "https://video-qn.51miz.com/preview/video/00/00/12/37/V-123783-D16F674D.mp4"
            // 设置SurfaceView
            surfaceView.setZOrderOnTop(false)
            surfaceView.holder.addCallback(this)
            // 设置MediaPlayer
            mMediaPlayer = MediaPlayer()
            mMediaPlayer?.setOnCompletionListener(this)
            mMediaPlayer?.setOnErrorListener(this)
            mMediaPlayer?.setOnInfoListener(this)
            mMediaPlayer?.setOnPreparedListener(this)
            mMediaPlayer?.setOnSeekCompleteListener(this)
            mMediaPlayer?.setOnVideoSizeChangedListener(this)
            mMediaPlayer?.setOnBufferingUpdateListener(this)
            try {
                //使用手机本地视频
                mMediaPlayer?.setDataSource(path)
            } catch (e: Exception) {
                e.printStackTrace()
            }
            seek_bar.setOnSeekBarChangeListener(this)
            media_play.setOnClickListener {
                play()
            }
        }
    
        /**
         * Surface被创建
         */
        override fun surfaceCreated(holder: SurfaceHolder) {
            mMediaPlayer?.setDisplay(holder)
            mMediaPlayer?.prepareAsync()
        }
    
        /**
         * Surface发生改变
         */
        override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
    
        }
    
        /**
         * Surface销毁
         */
        override fun surfaceDestroyed(holder: SurfaceHolder) {
    
        }
    
        /**
         * MediaPlayer装载完毕
         */
        override fun onPrepared(mp: MediaPlayer?) {
            media_book_start_time.text = TimeUtil.getTime(mp!!.currentPosition / 1000)
            media_book_total_time.text = TimeUtil.getTime(mp.duration / 1000)
            seek_bar.max = mp.duration
            seek_bar.progress = mp.currentPosition
        }
    
        /**
         * 播放结束
         */
        override fun onCompletion(mp: MediaPlayer?) {
            mHandler.removeMessages(UPDATE_TIME)
            seek_bar.progress = 0
            media_book_start_time.text = TimeUtil.getTime(0)
            media_song.setImageResource(R.drawable.media_pause_day)
    
        }
    
        override fun onError(mp: MediaPlayer?, what: Int, extra: Int): Boolean {
            return false
        }
    
        override fun onInfo(mp: MediaPlayer?, what: Int, extra: Int): Boolean {
            return false
        }
    
    
        override fun onSeekComplete(mp: MediaPlayer?) {
    
        }
    
        override fun onVideoSizeChanged(mp: MediaPlayer?, width: Int, height: Int) {
    
        }
    
        private fun play() {
            if (mMediaPlayer == null) {
                return
            }
            if (mMediaPlayer!!.isPlaying) {
                mMediaPlayer!!.pause()
                mHandler.removeMessages(UPDATE_TIME)
                media_song.setImageResource(R.drawable.media_pause_day)
            } else {
                mMediaPlayer!!.start()
                mHandler.sendEmptyMessageDelayed(UPDATE_TIME, 500)
                media_song.setImageResource(R.drawable.media_play_day)
            }
        }
    
        /**
         * 更新播放时间
         */
        private fun updateTime() {
            media_book_start_time.text = TimeUtil.getTime(mMediaPlayer!!.currentPosition / 1000)
            seek_bar.progress = mMediaPlayer!!.currentPosition
        }
    
        override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
            if (mMediaPlayer != null && fromUser) {
                mMediaPlayer!!.seekTo(progress)
                media_book_start_time.text = TimeUtil.getTime(mMediaPlayer!!.currentPosition / 1000)
            }
        }
    
        override fun onStartTrackingTouch(seekBar: SeekBar?) {
    
        }
    
        override fun onStopTrackingTouch(seekBar: SeekBar?) {
            if (!mMediaPlayer!!.isPlaying) {
                play()
            }
        }
    
        /**
         * 获取缓冲的信息
         */
        override fun onBufferingUpdate(mp: MediaPlayer?, percent: Int) {
            Log.i("aaaaaaaaaaaaaaaaaaaaa", "当前缓冲$percent")
        }
    
        override fun onDestroy() {
            super.onDestroy()
            mHandler.removeMessages(UPDATE_TIME)
            mMediaPlayer!!.stop()
            mMediaPlayer!!.release()
            mMediaPlayer = null
        }
    }
    
    
    (三)使用MediaPlayer + SurfaceView + MediaController

    XML文件

    <RelativeLayout 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:id="@+id/root_all"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/white"
        tools:context=".MediaPlayerControllerSurfaceViewActivity">
    
        <SurfaceView
            android:id="@+id/surfaceView"
            android:layout_width="match_parent"
            android:layout_height="200dp" />
    </RelativeLayout>
    

    实现

    class MediaPlayerControllerSurfaceViewActivity : AppCompatActivity(), SurfaceHolder.Callback, MediaPlayer.OnBufferingUpdateListener, MediaController.MediaPlayerControl {
    
        private var mMediaPlayer: MediaPlayer? = null
        private var mMediaController: MediaController? = null
        private var bufferPercentage = 0
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_media_player_controller_surface_view)
            init()
        }
    
        private fun init() {
            mMediaPlayer = MediaPlayer()
            mMediaController = MediaController(this)
            mMediaController?.setAnchorView(root_all)
            surfaceView.setZOrderOnTop(false)
            surfaceView.holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS)
            surfaceView.holder.addCallback(this)
        }
    
        override fun onResume() {
            super.onResume()
            try {
                val path = "https://video-qn.51miz.com/preview/video/00/00/12/37/V-123783-D16F674D.mp4"
                mMediaPlayer?.setDataSource(path)
                mMediaPlayer?.setOnBufferingUpdateListener(this)
                mMediaController?.setMediaPlayer(this)
                mMediaController?.isEnabled = true
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
    
        override fun onPause() {
            super.onPause()
            if (mMediaPlayer!!.isPlaying) {
                mMediaPlayer!!.stop()
            }
        }
    
        override fun onDestroy() {
            super.onDestroy()
            if (mMediaPlayer != null) {
                mMediaPlayer!!.release()
                mMediaPlayer = null
            }
        }
    
        override fun onTouchEvent(event: MotionEvent?): Boolean {
            mMediaController?.show()
            return super.onTouchEvent(event)
        }
    
        override fun surfaceCreated(holder: SurfaceHolder) {
            mMediaPlayer?.setDisplay(holder)
            mMediaPlayer?.prepareAsync()
        }
    
        override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
        }
    
        override fun surfaceDestroyed(holder: SurfaceHolder) {
        }
    
        override fun onBufferingUpdate(mp: MediaPlayer?, percent: Int) {
            bufferPercentage = percent
        }
    
        override fun start() {
            if (mMediaPlayer != null) {
                mMediaPlayer?.start()
            }
        }
    
        override fun pause() {
            if (mMediaPlayer != null) {
                mMediaPlayer?.pause()
            }
        }
    
        override fun getDuration() = mMediaPlayer!!.duration
    
    
        override fun getCurrentPosition() = mMediaPlayer!!.currentPosition
    
    
        override fun seekTo(pos: Int) {
            mMediaPlayer!!.seekTo(pos)
        }
    
        override fun isPlaying(): Boolean {
            if (mMediaPlayer!!.isPlaying) {
                return true
            }
            return false
        }
    
        override fun getBufferPercentage(): Int {
            return bufferPercentage
        }
    
        override fun canPause(): Boolean {
            return true
        }
    
        override fun canSeekBackward(): Boolean {
            return true
        }
    
        override fun canSeekForward(): Boolean {
            return true
        }
    
        override fun getAudioSessionId(): Int {
            return 0
        }
    }
    
    (四)使用ExoPlayer

    XML文件

    <RelativeLayout 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"
        tools:context=".ExoPlayerVideoActivity">
    
        <com.google.android.exoplayer2.ui.PlayerView
            android:id="@+id/exo_play_show"
            android:layout_width="match_parent"
            android:layout_height="205dp"/>
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="40dp"
            android:layout_alignParentBottom="true"
            android:orientation="horizontal">
    
            <Button
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:onClick="startExoPlayer"
                android:text="播放" />
    
            <Button
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:onClick="pauseExoPlayer"
                android:text="暂停" />
    
            <Button
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:onClick="stopExoPlayer"
                android:text="结束" />
    
    
    
        </LinearLayout>
    
    </RelativeLayout>
    

    实现

    class ExoPlayerVideoActivity : AppCompatActivity(), Player.EventListener {
    
        private var mSimpleExoPlayer: ExoPlayer? = null
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_exo_player_video)
            initExoPlayer()
        }
    
        private fun initExoPlayer() {
            // 创建一个播放器
            mSimpleExoPlayer = ExoPlayerFactory.newSimpleInstance(this)
            // 将播放器附着在一个View上
            exo_play_show.player = mSimpleExoPlayer
            // 准备播放器资源
            val dataSourceFactory = DefaultDataSourceFactory(this, Util.getUserAgent(this, application.packageName))
            val videoSource = ExtractorMediaSource.Factory(dataSourceFactory).createMediaSource(Uri.parse("https://video-qn.51miz.com/preview/video/00/00/12/37/V-123783-D16F674D.mp4"))
            mSimpleExoPlayer!!.prepare(videoSource)
        }
    
        fun startExoPlayer(view: View) {
            mSimpleExoPlayer!!.playWhenReady = true
        }
    
        fun pauseExoPlayer(view: View) {
            mSimpleExoPlayer!!.playWhenReady = false
        }
    
        fun stopExoPlayer(view: View) {
            mSimpleExoPlayer!!.stop()
        }
    
        override fun onDestroy() {
            super.onDestroy()
            mSimpleExoPlayer!!.stop()
            mSimpleExoPlayer!!.release()
            mSimpleExoPlayer = null
        }
    }
    
    

    最后别忘记在清单文件里面添加权限

        <uses-permission android:name="android.permission.TYPE_APPLICATION_OVERLAY" />
        <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
        <uses-permission android:name="android.permission.INTERNET" />
    

    相关文章

      网友评论

        本文标题:Android之媒体探究(二)—— 视频

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