視訊播放是一個很常見的功能,根據功能需求的不同,有不同的實現方式。
如果只是類似預覽的功能,可以直接調取系統的視訊播放功能:
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一步步整合進來,還是比較順利的。就是這樣:
上面例子最好down下來,跑一下。
我用到了一個VideoView來播放視訊,它是一個FrameLayout。
我是在這裡扒的,這裡程式碼還用到了其他的呼叫,一併copy過來,最後是這樣的。
其他的程式碼你也可以在示例中找到。我把上面的程式碼放到了自己的專案中。
然後在佈局中放入你寫(拷)的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()複製程式碼