Andoid鬼點子-近期專案總結(3)-ijkplayer視訊播放

我是綠色大米呀發表於2017-10-18

圖1
圖1

視訊播放是一個很常見的功能,根據功能需求的不同,有不同的實現方式。
如果只是類似預覽的功能,可以直接調取系統的視訊播放功能:

 Intent intent = new Intent();
 intent.setAction(Intent.ACTION_VIEW);
 intent.setDataAndType(Uri.fromFile(new File(path)), "video/mp4");
 activity.startActivity(intent);複製程式碼

這樣做的話,就會跳出App,好處就是用起來簡單,壞處就是離開的應用,如果有其他需求的話則無法實現。

最近的專案中用到的視訊播放,有一些特殊的功能,比如不允許使用者快進,但是可以退回,使用者看過的部分可以快進。要記錄播放進度,再次進入時要恢復進度。可以設定斷點,斷點暫停後使用者需要手動點選繼續播放。綜上,上面的做法就不能用了,只能自己寫一個播放器了。

之前用過Vitamio,整體的使用感覺還是比較順利,文件示例都比較全。也沒有什麼大bug。但是商用收費!如果你對Vitamio感興趣可以看這裡

這次就用了ijkplayer。ijkplayer的文件和示例都沒有Vitamio那麼多,我是在示例上修修改改的。它是可以支援的線上播放和本地播放的。

它們都是基於FFmpeg的,你也可以直接幹FFmpeg。

按照ijkplayer的github一步步整合進來,還是比較順利的。就是這樣:

圖2
圖2

官方示例

上面例子最好down下來,跑一下。

我用到了一個VideoView來播放視訊,它是一個FrameLayout。
我是在這裡扒的,這裡程式碼還用到了其他的呼叫,一併copy過來,最後是這樣的。

圖3
圖3

其他的程式碼你也可以在示例中找到。我把上面的程式碼放到了自己的專案中。

然後在佈局中放入你寫(拷)的IjkVideoView。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:background="@color/black">

    <com.greendami.video.widget.media.IjkVideoView
        android:id="@+id/video"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"></com.greendami.video.widget.media.IjkVideoView>

</LinearLayout>複製程式碼

如果你只是想簡單的播放視訊,對介面沒有什麼要求的話,可以使用ijkplayer提供的MediaController,直接就有進度條,暫停,播放等功能。
video.setMediaController(AndroidMediaController(this)),不需要的話就傳個null進去就行。

只要這樣就行:

package com.greendami.actvity.worknotes

import android.content.pm.ActivityInfo
import android.content.res.Configuration
import android.net.Uri
import android.os.Environment
import android.view.WindowManager
import android.widget.FrameLayout
import android.widget.LinearLayout
import com.allrun.dangjianshisanshi.R
import com.allrun.dangjianshisanshi.actvity.BaseActivity
import com.allrun.dangjianshisanshi.video.widget.media.AndroidMediaController
import com.allrun.dangjianshisanshi.widget.LoadingDialog
import kotlinx.android.synthetic.main.activity_worknote_videoplayer.*
import org.jetbrains.anko.toast
import tv.danmaku.ijk.media.player.IjkMediaPlayer

/**
 * Created by greendami on 2017/8/30.
 */
class WorkNoteVideoPlayerActivity : BaseActivity() {

    private val SIZE_DEFAULT = 0
    private val SIZE_4_3 = 1
    private val SIZE_16_9 = 2
    private val currentSize = SIZE_DEFAULT

    private var screenWidth = 0
    private var screenHeight = 0


    ////http://www.modrails.com/videos/passenger_nginx.mov
    var uri = Uri.parse(Environment.getExternalStorageDirectory().path + "/test.mp4")
    var path = ""

    override fun setContentView() {
        setContentView(R.layout.activity_worknote_videoplayer)
    }

    override fun initView() {
        IjkMediaPlayer.loadLibrariesOnce(null)
        IjkMediaPlayer.native_profileBegin("libijkplayer.so")
        LoadingDialog.showDialog(this)
        initEvent()
    }

    private fun initEvent() {

        back.setOnClickListener { finish() }
        video.setOnCompletionListener {
            toast("播放完畢")
            finish()
        }

        video.setOnPreparedListener {
            LoadingDialog.dismissDialog()
            video.start()
            setVideoLayoutSize()
        }


    }


    private fun setVideoLayoutSize() {
        initScreenInfo()
        var width = video.width
        var height = video.height
        if (video.getmVideoWidth() / video.getmVideoHeight() > width / height) {
            height = width * video.getmVideoHeight() / video.getmVideoWidth()
        } else {
            width = height * video.getmVideoWidth() / video.getmVideoHeight()
        }


        if (width > 0 && height > 0) {
            val lp = video.getmRenderView().view.layoutParams as FrameLayout.LayoutParams
            lp.width = width
            lp.height = height
            video.getmRenderView().view.layoutParams = lp
        }
    }


    override fun bindData() {
        video.setVideoPath(path)
        video.setMediaController(AndroidMediaController(this))
    }

    override fun loadData() {
        path = intent.extras["path"].toString()
    }

    private fun setScreenRate(newConfig: Configuration) {
        var width = 0
        var height = 0
        if (newConfig.orientation === Configuration.ORIENTATION_LANDSCAPE) {//切換為橫屏
            when (currentSize) {
                SIZE_DEFAULT -> {
                    width = video.getmVideoWidth()
                    height = video.getmVideoHeight()
                }
                SIZE_4_3 -> {
                    width = screenHeight / 3 * 4
                    height = screenHeight
                }
                SIZE_16_9 -> {
                    width = screenHeight / 9 * 16
                    height = screenHeight
                }
            }
        } else { //豎屏

            when (currentSize) {
                SIZE_DEFAULT -> {
                    width = video.getmVideoWidth()
                    height = video.getmVideoHeight()
                }
                SIZE_4_3 -> {
                    width = screenWidth
                    height = screenWidth * 3 / 4
                }
                SIZE_16_9 -> {
                    width = screenWidth
                    height = screenWidth * 9 / 16
                }
            }
        }
        if (width > 0 && height > 0) {
            val lp = video.getmRenderView().view.layoutParams as FrameLayout.LayoutParams
            lp.width = width
            lp.height = height
            video.getmRenderView().view.layoutParams = lp
        }

    }

    private fun fullChangeScreen() {
        requestedOrientation = if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) {// 切換為豎屏
            ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
        } else {
            ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
        }
    }

    override fun onConfigurationChanged(newConfig: Configuration) {
        super.onConfigurationChanged(newConfig)
        //重新獲取螢幕寬高
        initScreenInfo()
        if (newConfig.orientation === Configuration.ORIENTATION_LANDSCAPE) {//切換為橫屏

            //去掉通知欄
            //獲得 WindowManager.LayoutParams 屬性物件
            val lp2 = window.attributes
            //直接對它flags變數操作   LayoutParams.FLAG_FULLSCREEN 表示設定全屏
            lp2.flags = lp2.flags or WindowManager.LayoutParams.FLAG_FULLSCREEN
            //設定屬性
            window.attributes = lp2
            //意思大致就是  允許視窗擴充套件到螢幕之外
            window.addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS)


            val lp = video.layoutParams as LinearLayout.LayoutParams
            lp.height = screenHeight
            lp.width = screenWidth
            video.layoutParams = lp
        } else {

            //恢復通知欄
            //獲得 WindowManager.LayoutParams 屬性物件
            val lp2 = window.attributes
            //LayoutParams.FLAG_FULLSCREEN 強制螢幕狀態條欄彈出
            lp2.flags = lp2.flags and WindowManager.LayoutParams.FLAG_FULLSCREEN.inv()
            //設定屬性
            window.attributes = lp2
            //不允許視窗擴充套件到螢幕之外  clear掉了
            window.clearFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS)


            val lp = video.layoutParams as LinearLayout.LayoutParams
            when (currentSize) {
                SIZE_DEFAULT -> {
                    lp.height = video.getmVideoHeight() * screenWidth / video.getmVideoWidth()
                }
                SIZE_4_3 -> {
                    lp.height = screenWidth * 3 / 4
                }
                SIZE_16_9 -> {
                    lp.height = screenWidth * 9 / 16
                }
            }

            lp.width = screenWidth
            video.layoutParams = lp
        }
        setScreenRate(newConfig)
    }

    private fun initScreenInfo() {
        val wm = this.windowManager

        screenWidth = wm.defaultDisplay.width
        screenHeight = wm.defaultDisplay.height
    }


    override fun onDestroy() {
        video.release(true)
        LoadingDialog.dismissDialog()
        super.onDestroy()
    }
}複製程式碼

如果不把BaseActivity放上來,可能看起來費勁,這個是我隨便寫的,請不要在意。

abstract class BaseActivity : LifecycleActivity() {
    lateinit var modelHolder: ModelHolder
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        modelHolder = ViewModelProviders.of(this).get(ModelHolder::class.java)
        setContentView()
        loadData()
        initView()
        bindData()
    }

    abstract fun setContentView()

    /**
     * 請求資料
     */
    abstract fun loadData()

    abstract fun initView()

    /**
    * 把資料和控制元件繫結
    */
    abstract fun bindData()

}複製程式碼

大體的步驟是:

//載入庫檔案
IjkMediaPlayer.loadLibrariesOnce(null)
IjkMediaPlayer.native_profileBegin("libijkplayer.so")

//設定檔案路徑可以是網路地址或者本地檔案路徑
video.setVideoPath(path)
//使用預設的控制介面,進度條快進等等
video.setMediaController(AndroidMediaController(this))

//繫結載入完成監聽器,載入完了就播放
video.setOnPreparedListener {
        LoadingDialog.dismissDialog()
        video.start()
        //這裡是設定視訊的尺寸,不是必須
        setVideoLayoutSize()
    }
//到此就完成了,如果你需要重力感應,全屏切換,請往下看

//如果按鈕切換橫豎屏,呼叫這個方法
fullChangeScreen()
//這裡是橫豎屏切換事件的回撥
override fun onConfigurationChanged(newConfig: Configuration)

//這裡是螢幕方向改變後重新計算視訊尺寸,我是預設不改變視訊長寬比的前提下鋪滿螢幕
//video.getmVideoWidth()是視訊的尺寸,是我自己加的方法,只是返回了tmVideoWidth
//video.getmRenderView().view.layoutParams = lp這句是真正設定視訊尺寸
private fun setScreenRate(newConfig: Configuration) {
        initScreenInfo()
        var width = screenWidth
        var height = screenHeight
        if (video.getmVideoWidth() / video.getmVideoHeight() > width / height) {
            height = width * video.getmVideoHeight() / video.getmVideoWidth()
        } else {
            width = height * video.getmVideoWidth() / video.getmVideoHeight()
        }
        if (width > 0 && height > 0) {
            val lp = video.getmRenderView().view.layoutParams as FrameLayout.LayoutParams
            lp.width = width
            lp.height = height
            video.getmRenderView().view.layoutParams = lp
        }

    }複製程式碼

如果你需要控制視訊,下面的API你可能會用到:

//進度控制
video.seekTo(progress)
//視訊播放完畢回撥
video.setOnCompletionListener {toast("播放完畢")}
//視訊長度
video.duration
//暫停
video.pause()
//繼續播放
if (!video.isPlaying) video.start()複製程式碼

相關文章