1.加入TextureView支援
2.將取得畫面寬高的計算方式改成不使用baseView.post
This commit is contained in:
parent
02a26445be
commit
8449706b64
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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"
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user