1.加入TextureView支援

2.將取得畫面寬高的計算方式改成不使用baseView.post
This commit is contained in:
Raymond Yang 2023-05-22 09:43:53 +08:00
parent 02a26445be
commit 8449706b64
5 changed files with 179 additions and 65 deletions

View File

@ -0,0 +1,94 @@
package com.ray650128.gstreamer_demo_app
import android.content.Context
import android.os.Build
import android.util.DisplayMetrics
import android.view.WindowManager
/**
* 螢幕參數工具類別
* @author Raymond Yang
*/
class DisplayUtils(private var context: Context) {
private val TAG = DisplayUtils::class.java.simpleName
/**
* 取得螢幕寬度
* @return 螢幕寬度值
*/
fun getScreenWidth(): Int {
val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val windowMetrics = windowManager.currentWindowMetrics
windowMetrics.bounds.width()
} else {
val metric = DisplayMetrics()
@Suppress("DEPRECATION")
windowManager.defaultDisplay.getMetrics(metric)
metric.widthPixels
}
}
/**
* 取得螢幕高度
* @return 螢幕高度值
*/
fun getScreenHeight(): Int {
val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val windowMetrics = windowManager.currentWindowMetrics
windowMetrics.bounds.height()
} else {
val metric = DisplayMetrics()
@Suppress("DEPRECATION")
windowManager.defaultDisplay.getMetrics(metric)
metric.heightPixels
}
}
/**
* 取得狀態列高度
* @return 狀態列高度值
*/
fun getStatusBarHeight(): Int {
var result = 0
val resourceId: Int = context.resources.getIdentifier(
"status_bar_height",
"dimen",
"android"
)
if (resourceId > 0) {
result = context.resources.getDimensionPixelSize(resourceId)
}
return result
}
/**
* 取得導航列高度
* @return 導航列高度值
*/
fun getNavigationBarHeight(): Int {
val resources = context.resources
val resourceId: Int = resources.getIdentifier("navigation_bar_height", "dimen", "android")
return if (resourceId > 0) {
resources.getDimensionPixelSize(resourceId)
} else 0
}
/**
* 取得視窗範圍高度
* @return 視窗範圍高度值
*/
fun getWindowHeight(): Int {
val screen = getScreenHeight()
val statusBar = getStatusBarHeight()
val navBar = getNavigationBarHeight()
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
screen - statusBar - navBar
} else {
screen
}
}
}

View File

@ -6,10 +6,12 @@ import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.gridlayout.widget.GridLayout import androidx.gridlayout.widget.GridLayout
import com.ray650128.gstreamer_demo_app.Constants import com.ray650128.gstreamer_demo_app.Constants
import com.ray650128.gstreamer_demo_app.DisplayUtils
import com.ray650128.gstreamer_demo_app.R
import com.ray650128.gstreamer_demo_app.databinding.FragmentSplitViewBinding import com.ray650128.gstreamer_demo_app.databinding.FragmentSplitViewBinding
import com.ray650128.gstreamer_demo_app.dp import com.ray650128.gstreamer_demo_app.dp
import com.ray650128.gstreamer_demo_app.model.Device import com.ray650128.gstreamer_demo_app.model.Device
@ -22,8 +24,6 @@ import kotlin.math.sqrt
class SplitViewFragment : Fragment() { class SplitViewFragment : Fragment() {
val viewModel: SplitViewModel by activityViewModels()
private var mPageNum: Int = 0 private var mPageNum: Int = 0
private var splitMode = Constants.SPLIT_MODE_SINGLE private var splitMode = Constants.SPLIT_MODE_SINGLE
private var streamType = VideoView.SUB_STREAM private var streamType = VideoView.SUB_STREAM
@ -56,18 +56,6 @@ class SplitViewFragment : Fragment() {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
initView() initView()
/*viewModel.activePage.observe(viewLifecycleOwner) {
if (it == null) return@observe
if (it == this.mPageNum) {
MainScope().launch {
//delay(1000)
//playAll()
}
} else {
//stopAll()
}
}*/
} }
override fun onPause() { override fun onPause() {
@ -90,6 +78,7 @@ class SplitViewFragment : Fragment() {
} }
private fun initView() { private fun initView() {
val displayUtil = DisplayUtils(requireContext())
// 生成 VideoView 分割畫面 // 生成 VideoView 分割畫面
binding.apply { binding.apply {
val maxRow = sqrt(splitMode.toFloat()).toInt() val maxRow = sqrt(splitMode.toFloat()).toInt()
@ -99,6 +88,19 @@ class SplitViewFragment : Fragment() {
baseView.rowCount = maxRow baseView.rowCount = maxRow
baseView.columnCount = maxCol baseView.columnCount = maxCol
val cellWidth: Int
val cellHeight: Int
when (splitMode) {
Constants.SPLIT_MODE_SINGLE -> {
cellWidth = (displayUtil.getScreenWidth() / maxRow)
cellHeight = (cellWidth * 0.5625).toInt()
}
else -> {
cellWidth = (displayUtil.getScreenWidth() / maxRow) - maxRow.dp
cellHeight = (cellWidth * 0.5625).toInt() - maxCol.dp
}
}
for (col in 0 until maxCol) { for (col in 0 until maxCol) {
for (row in 0 until maxRow) { for (row in 0 until maxRow) {
val videoView = VideoView(requireContext()) val videoView = VideoView(requireContext())
@ -108,6 +110,9 @@ class SplitViewFragment : Fragment() {
marginEnd = 0.dp marginEnd = 0.dp
marginStart = 0.dp marginStart = 0.dp
width = cellWidth
height = cellHeight
// 調整間距 // 調整間距
when (splitMode) { when (splitMode) {
Constants.SPLIT_MODE_FOUR -> { Constants.SPLIT_MODE_FOUR -> {
@ -154,35 +159,15 @@ class SplitViewFragment : Fragment() {
videoViews.add(videoView) videoViews.add(videoView)
} }
} }
baseView.post {
// 根據分割數量決定子項目的寬度
val cellWidth: Int
val cellHeight: Int
when (splitMode) {
Constants.SPLIT_MODE_SINGLE -> {
cellWidth = (baseView.width / maxRow)
cellHeight = (baseView.height / maxCol)
}
else -> {
cellWidth = (baseView.width / maxRow) - maxRow.dp
cellHeight = (baseView.height / maxCol) - maxCol.dp
}
}
videoViews.forEach { videoView ->
videoView.layoutParams?.width = cellWidth
videoView.layoutParams?.height = cellHeight
}
}
} }
if (isClickable) { if (isClickable) {
for (position in videoViews.indices) { for (position in videoViews.indices) {
videoViews[position].setOnClickListener { videoViews[position].setOnClickListener {
if (position >= data.size) return@setOnClickListener if (position >= data.size) return@setOnClickListener
if (!videoViews[position].isPlaying) { /*if (!videoViews[position].isPlaying) {
return@setOnClickListener return@setOnClickListener
} }*/
stopAll() stopAll()
val item = data[position] val item = data[position]
val bundle = Bundle().apply { val bundle = Bundle().apply {
@ -214,31 +199,32 @@ class SplitViewFragment : Fragment() {
fun playAll() = MainScope().launch(Dispatchers.Main) { fun playAll() = MainScope().launch(Dispatchers.Main) {
if (videoViews.isEmpty()) return@launch if (videoViews.isEmpty()) return@launch
delay(splitMode * Constants.CONF_DELAY_BASE_MILLIS) delay(splitMode * Constants.CONF_DELAY_BASE_MILLIS)
for (index in data.indices) { videoViews.forEach { videoView ->
if (!videoViews[index].isReady) continue if (videoView.isReady) {
videoViews[index].resetRetryCount() videoView.resetRetryCount()
videoViews[index].play() videoView.play()
//delay(300) //delay(300)
}
} }
}.start() }.start()
fun stopAll() = MainScope().launch(Dispatchers.Main) { fun stopAll() {
if (videoViews.isEmpty()) return@launch if (videoViews.isEmpty()) return
for (index in data.indices) { videoViews.forEach { videoView ->
videoViews[index].stopRetryCount() videoView.stopRetryCount()
if (!videoViews[index].isPlaying || videoViews[index].isLoading) continue if (videoView.isPlaying || !videoView.isLoading) {
videoViews[index].pause() videoView.pause()
//delay(300) }
} }
}.start() }
fun destroyAll() = MainScope().launch(Dispatchers.Main) { fun destroyAll() {
if (videoViews.isEmpty()) return@launch if (videoViews.isEmpty()) return
for (index in data.indices) { videoViews.forEach { videoView ->
//videoViews[index].destroy() //videoView.destroy()
videoViews[index].destroySurface() videoView.destroySurface()
} }
}.start() }
companion object { companion object {
private val TAG = SplitViewFragment::class.java.simpleName private val TAG = SplitViewFragment::class.java.simpleName

View File

@ -9,6 +9,7 @@ import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.SurfaceHolder import android.view.SurfaceHolder
import android.view.SurfaceView import android.view.SurfaceView
import android.view.TextureView
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.isVisible import androidx.core.view.isVisible
import com.hisharp.gstreamer_player.GstCallback import com.hisharp.gstreamer_player.GstCallback
@ -62,7 +63,7 @@ class VideoView : ConstraintLayout, GstCallback {
} }
} }
private val videoView: SurfaceView by lazy { view.videoView } private val videoView: TextureView by lazy { view.videoView }
private lateinit var gstLibrary: GstLibrary private lateinit var gstLibrary: GstLibrary
@ -75,11 +76,13 @@ class VideoView : ConstraintLayout, GstCallback {
view = ItemVideoViewBinding.inflate(layoutInflater, this, true) view = ItemVideoViewBinding.inflate(layoutInflater, this, true)
view.baseView.clipToOutline = true view.baseView.clipToOutline = true
//videoView.holder.addCallback(this)
gstLibrary = GstLibrary(context) gstLibrary = GstLibrary(context)
gstLibrary.setSurfaceHolder(videoView.holder) gstLibrary.setTextureView(videoView)
gstLibrary.setOnStatusChangeListener(this) gstLibrary.setOnStatusChangeListener(this)
//videoView.holder.addCallback(this)
//gstLibrary.setSurfaceHolder(videoView.holder)
// View 預設狀態 // View 預設狀態
view.textDeviceName.isVisible = false view.textDeviceName.isVisible = false
isPlaying = false isPlaying = false

View File

@ -7,7 +7,7 @@
android:background="@drawable/bg_video_view" android:background="@drawable/bg_video_view"
android:outlineProvider="background"> android:outlineProvider="background">
<SurfaceView <TextureView
android:id="@+id/videoView" android:id="@+id/videoView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"

View File

@ -1,13 +1,16 @@
package com.hisharp.gstreamer_player package com.hisharp.gstreamer_player
import android.content.Context import android.content.Context
import android.graphics.SurfaceTexture
import android.util.Log import android.util.Log
import android.view.Surface
import android.view.SurfaceHolder import android.view.SurfaceHolder
import android.view.TextureView
import org.freedesktop.gstreamer.GStreamer import org.freedesktop.gstreamer.GStreamer
import java.io.Closeable import java.io.Closeable
import java.io.IOException import java.io.IOException
class GstLibrary(context: Context) : Closeable, SurfaceHolder.Callback { class GstLibrary(context: Context) : Closeable, SurfaceHolder.Callback, TextureView.SurfaceTextureListener {
private val mAppContext: Context private val mAppContext: Context
private var gstCallback: GstCallback? = null private var gstCallback: GstCallback? = null
@ -15,6 +18,8 @@ class GstLibrary(context: Context) : Closeable, SurfaceHolder.Callback {
private var tag = "" private var tag = ""
private var isInit = false private var isInit = false
private var surface: Surface? = null
private external fun nativeInit() // Initialize native code, build pipeline, etc private external fun nativeInit() // Initialize native code, build pipeline, etc
private external fun nativeFinalize() // Destroy pipeline and shutdown native code private external fun nativeFinalize() // Destroy pipeline and shutdown native code
private external fun nativeSetUri(uri: String) // Set the URI of the media to play private external fun nativeSetUri(uri: String) // Set the URI of the media to play
@ -59,6 +64,10 @@ class GstLibrary(context: Context) : Closeable, SurfaceHolder.Callback {
holder.addCallback(this) holder.addCallback(this)
} }
fun setTextureView(textureView: TextureView) {
textureView.surfaceTextureListener = this
}
// Called from native code. This sets the content of the TextView from the UI thread. // Called from native code. This sets the content of the TextView from the UI thread.
private fun setMessage(message: String) { private fun setMessage(message: String) {
if (gstCallback == null) return if (gstCallback == null) return
@ -128,7 +137,8 @@ class GstLibrary(context: Context) : Closeable, SurfaceHolder.Callback {
override fun surfaceCreated(holder: SurfaceHolder) { override fun surfaceCreated(holder: SurfaceHolder) {
Log.d("$TAG+$tag", "Surface created: " + holder.surface) Log.d("$TAG+$tag", "Surface created: " + holder.surface)
nativeSurfaceInit(holder.surface) this.surface = holder.surface
this.surface?.let { nativeSurfaceInit(it) }
} }
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) { override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
@ -138,9 +148,30 @@ class GstLibrary(context: Context) : Closeable, SurfaceHolder.Callback {
override fun surfaceDestroyed(holder: SurfaceHolder) { override fun surfaceDestroyed(holder: SurfaceHolder) {
Log.d("$TAG+$tag", "Surface destroyed") Log.d("$TAG+$tag", "Surface destroyed")
isInit = false this.isInit = false
//pause() this.surface = null
//releaseSurface() releaseSurface()
}
override fun onSurfaceTextureAvailable(surface: SurfaceTexture, width: Int, height: Int) {
this.surface = Surface(surface)
this.surface?.let { nativeSurfaceInit(it) }
Log.d("$TAG+$tag", "Surface onSurfaceTextureAvailable: $surface")
}
override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture, width: Int, height: Int) {
Log.d("$TAG+$tag", "Surface onSurfaceTextureSizeChanged: $surface")
}
override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean {
Log.d("$TAG+$tag", "Surface onSurfaceTextureDestroyed: $surface")
this.isInit = false
this.surface = null
return isInit
}
override fun onSurfaceTextureUpdated(surface: SurfaceTexture) {
Log.d("$TAG+$tag", "Surface onSurfaceTextureUpdated: $surface")
} }
companion object { companion object {