Compare commits

...

2 Commits

Author SHA1 Message Date
Raymond Yang
7357085fdf 加入TextureView支援 2023-05-19 15:37:47 +08:00
Raymond Yang
02a26445be 同步HiSharpDX的程式碼 2023-05-19 14:02:47 +08:00
12 changed files with 174 additions and 140 deletions

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="16" />
<bytecodeTargetLevel target="17" />
</component>
</project>

View File

@ -7,7 +7,7 @@
<option name="testRunner" value="GRADLE" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="semeru-16" />
<option name="gradleJvm" value="jbr-17" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />

View File

@ -1,4 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DesignSurface">
<option name="filePathToZoomLevelMap">
@ -16,5 +15,5 @@
</option>
</component>
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_16_PREVIEW" project-jdk-name="11" project-jdk-type="JavaSDK" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK" />
</project>

View File

@ -1,5 +1,12 @@
package com.ray650128.gstreamer_demo_app
object Constants {
//region Split mode
const val SPLIT_MODE_SIXTEEN = 16
const val SPLIT_MODE_NINE = 9
const val SPLIT_MODE_FOUR = 4
const val SPLIT_MODE_SINGLE = 1
//endregion
const val CONF_DELAY_BASE_MILLIS = 1000L
}

View File

@ -19,6 +19,6 @@ object PreferenceUtil {
set(value) = sharedPreferences.edit().putBoolean(IS_FIRST_OPEN_KEY, value).apply()
var lastSplitMode: Int
get() = sharedPreferences.getInt(LAST_SPLIT_MODE, MainViewModel.PAGE_MODE_ONE)
get() = sharedPreferences.getInt(LAST_SPLIT_MODE, Constants.SPLIT_MODE_SINGLE)
set(value) = sharedPreferences.edit().putInt(LAST_SPLIT_MODE, value).apply()
}

View File

@ -57,27 +57,27 @@ class MainActivity : AppCompatActivity() {
private fun initContentView() = binding.apply {
//region Content area
button.setOnClickListener {
if (splitMode == MainViewModel.PAGE_MODE_ONE) return@setOnClickListener
viewModel.setSplitMode(MainViewModel.PAGE_MODE_ONE)
if (splitMode == Constants.SPLIT_MODE_SINGLE) return@setOnClickListener
viewModel.setSplitMode(Constants.SPLIT_MODE_SINGLE)
Log.e(TAG, "+++ split style: 1")
}
button2.setOnClickListener {
if (splitMode == MainViewModel.PAGE_MODE_FOUR) return@setOnClickListener
viewModel.setSplitMode(MainViewModel.PAGE_MODE_FOUR)
if (splitMode == Constants.SPLIT_MODE_FOUR) return@setOnClickListener
viewModel.setSplitMode(Constants.SPLIT_MODE_FOUR)
Log.e(TAG, "+++ split style: 4")
}
button3.setOnClickListener {
if (splitMode == MainViewModel.PAGE_MODE_NINE) return@setOnClickListener
viewModel.setSplitMode(MainViewModel.PAGE_MODE_NINE)
if (splitMode == Constants.SPLIT_MODE_NINE) return@setOnClickListener
viewModel.setSplitMode(Constants.SPLIT_MODE_NINE)
Log.e(TAG, "+++ split style: 9")
}
button4.setOnClickListener {
if (splitMode == MainViewModel.PAGE_MODE_SIXTEEN) return@setOnClickListener
viewModel.setSplitMode(MainViewModel.PAGE_MODE_SIXTEEN)
Log.e(TAG, "+++ split style: 9")
if (splitMode == Constants.SPLIT_MODE_SIXTEEN) return@setOnClickListener
viewModel.setSplitMode(Constants.SPLIT_MODE_SIXTEEN)
Log.e(TAG, "+++ split style: 16")
}
//endregion
}

View File

@ -19,7 +19,7 @@ class MainViewModel: ViewModel() {
stream1 = "/media/video1",
stream2 = "/media/video2",
),
Device(
/*Device(
deviceName = "192.168.0.73",
ip = "192.168.0.73",
rtspPort = "554",
@ -27,8 +27,8 @@ class MainViewModel: ViewModel() {
password = "hs22601576",
stream1 = "/media/video1",
stream2 = "/media/video2",
),
Device(
),*/
/*Device(
deviceName = "192.168.0.79",
ip = "192.168.0.79",
rtspPort = "554",
@ -36,7 +36,7 @@ class MainViewModel: ViewModel() {
password = "1q2w3e4r!",
stream1 = "/media/video1",
stream2 = "/media/video2",
),
),*/
Device(
deviceName = "192.168.0.88",
ip = "211.23.78.226",
@ -64,7 +64,7 @@ class MainViewModel: ViewModel() {
stream1 = "/v01",
stream2 = "/v02",
),
Device(
/*Device(
deviceName = "192.168.0.76",
ip = "211.23.78.226",
rtspPort = "8576",
@ -72,7 +72,7 @@ class MainViewModel: ViewModel() {
password = "123456",
stream1 = "/profile1",
stream2 = "/profile2",
),
),*/
Device(
deviceName = "192.168.0.82",
ip = "192.168.0.82",
@ -91,7 +91,7 @@ class MainViewModel: ViewModel() {
stream1 = "/profile1",
stream2 = "/profile2",
),
Device(
/*Device(
deviceName = "192.168.0.95",
ip = "192.168.0.95",
rtspPort = "554",
@ -99,7 +99,7 @@ class MainViewModel: ViewModel() {
password = "123456",
stream1 = "/profile1",
stream2 = "/profile2",
)
)*/
)
}
@ -137,11 +137,4 @@ class MainViewModel: ViewModel() {
}
return tmpData
}
companion object {
const val PAGE_MODE_ONE = 1
const val PAGE_MODE_FOUR = 4
const val PAGE_MODE_NINE = 9
const val PAGE_MODE_SIXTEEN = 16
}
}

View File

@ -22,10 +22,8 @@ import kotlin.math.sqrt
class SplitViewFragment : Fragment() {
val viewModel: SplitViewModel by activityViewModels()
private var mPageNum: Int = 0
private var splitMode = MainViewModel.PAGE_MODE_ONE
private var splitMode = Constants.SPLIT_MODE_SINGLE
private var streamType = VideoView.SUB_STREAM
private var isClickable = true
@ -98,143 +96,146 @@ class SplitViewFragment : Fragment() {
baseView.rowCount = maxRow
baseView.columnCount = maxCol
Log.e("${TAG}_$mPageNum", "baseView.rowCount: ${baseView.rowCount}, baseView.columnCount: ${baseView.columnCount}")
for (col in 0 until maxCol) {
for (row in 0 until maxRow) {
val videoView = VideoView(requireContext())
val layoutParam = GridLayout.LayoutParams().apply {
topMargin = 0.dp
bottomMargin = 0.dp
marginEnd = 0.dp
marginStart = 0.dp
// 調整間距
when (splitMode) {
Constants.SPLIT_MODE_FOUR -> {
when (col) {
0 -> bottomMargin = 2.dp
1 -> topMargin = 2.dp
}
when (row) {
0 -> marginEnd = 2.dp
1 -> marginStart = 2.dp
}
}
Constants.SPLIT_MODE_NINE -> {
if (col == 1) {
topMargin = 4.dp
bottomMargin = 4.dp
}
if (row == 1) {
marginEnd = 4.dp
marginStart = 4.dp
}
}
Constants.SPLIT_MODE_SIXTEEN -> {
if (col == 1) {
topMargin = 4.dp
bottomMargin = 2.dp
}
if (col == 2) {
topMargin = 2.dp
bottomMargin = 4.dp
}
if (row == 1) {
marginStart = 4.dp
marginEnd = 2.dp
}
if (row == 2) {
marginStart = 2.dp
marginEnd = 4.dp
}
}
}
}
baseView.addView(videoView, layoutParam)
videoViews.add(videoView)
}
}
baseView.post {
for (col in 0 until maxCol) {
for (row in 0 until maxRow) {
val videoView = VideoView(requireContext())
val layoutParam = GridLayout.LayoutParams().apply {
topMargin = 0.dp
bottomMargin = 0.dp
marginEnd = 0.dp
marginStart = 0.dp
// 調整間距
when (splitMode) {
MainViewModel.PAGE_MODE_ONE -> {
width = (baseView.width / maxRow)
height = (baseView.height / maxCol)
}
MainViewModel.PAGE_MODE_FOUR -> {
when (col) {
0 -> bottomMargin = 2.dp
1 -> topMargin = 2.dp
}
when (row) {
0 -> marginEnd = 2.dp
1 -> marginStart = 2.dp
}
width = (baseView.width / maxRow) - maxRow.dp
height = (baseView.height / maxCol) - maxCol.dp
}
MainViewModel.PAGE_MODE_NINE -> {
if (col == 1) {
topMargin = 4.dp
bottomMargin = 4.dp
}
if (row == 1) {
marginEnd = 4.dp
marginStart = 4.dp
}
width = (baseView.width / maxRow) - maxRow.dp
height = (baseView.height / maxCol) - maxCol.dp
}
MainViewModel.PAGE_MODE_SIXTEEN -> {
if (col == 1) {
topMargin = 4.dp
bottomMargin = 2.dp
}
if (col == 2) {
topMargin = 2.dp
bottomMargin = 4.dp
}
if (row == 1) {
marginStart = 4.dp
marginEnd = 2.dp
}
if (row == 2) {
marginStart = 2.dp
marginEnd = 4.dp
}
width = (baseView.width / maxRow) - maxRow.dp
height = (baseView.height / maxCol) - maxCol.dp
}
}
}
baseView.addView(videoView, layoutParam)
videoViews.add(videoView)
// 根據分割數量決定子項目的寬度
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
}
}
setAllUrl()
if (isClickable) {
for (position in videoViews.indices) {
videoViews[position].setOnClickListener {
if (position >= data.size) return@setOnClickListener
if (!videoViews[position].isPlaying) {
return@setOnClickListener
}
MainScope().launch {
stopAll()
delay(splitMode * Constants.CONF_DELAY_BASE_MILLIS)
val item = data[position]
val bundle = Bundle().apply {
//putInt(MonitoringActivity.BUNDLE_DEVICE_ID, item.id)
//putInt(MonitoringActivity.BUNDLE_CHANNEL_ID, item.channelId)
putParcelable(MonitoringActivity.BUNDLE_DEVICE, item)
}
val intent = Intent(requireContext(), MonitoringActivity::class.java)
intent.putExtras(bundle)
startActivity(intent)
//gotoActivity(MonitoringActivity::class.java, bundle)*/
//Log.d("${TAG}_$mPageNum", "check: $item")
}
}
}
videoViews.forEach { videoView ->
videoView.layoutParams?.width = cellWidth
videoView.layoutParams?.height = cellHeight
}
}
}
if (isClickable) {
for (position in videoViews.indices) {
videoViews[position].setOnClickListener {
if (position >= data.size) return@setOnClickListener
if (!videoViews[position].isPlaying) {
return@setOnClickListener
}
stopAll()
val item = data[position]
val bundle = Bundle().apply {
//putInt(MonitoringActivity.BUNDLE_DEVICE_ID, item.id)
//putInt(MonitoringActivity.BUNDLE_CHANNEL_ID, item.channelId)
putParcelable(MonitoringActivity.BUNDLE_DEVICE, item)
}
val intent = Intent(requireContext(), MonitoringActivity::class.java)
intent.putExtras(bundle)
startActivity(intent)
//gotoActivity(MonitoringActivity::class.java, bundle)*/
//Log.d("${TAG}_$mPageNum", "check: $item")
}
}
}
setAllUrl()
}
private fun setAllUrl() {
for (index in data.indices) {
videoViews[index].setData(data[index])
videoViews[index].setTextVisible(
(splitMode != MainViewModel.PAGE_MODE_NINE && splitMode != MainViewModel.PAGE_MODE_SIXTEEN)
(splitMode != Constants.SPLIT_MODE_NINE && splitMode != Constants.SPLIT_MODE_SIXTEEN)
)
}
}
fun playAll() = MainScope().launch(Dispatchers.Main) {
if (videoViews.isEmpty()) return@launch
delay(splitMode * Constants.CONF_DELAY_BASE_MILLIS)
//delay(splitMode * Constants.CONF_DELAY_BASE_MILLIS)
for (index in data.indices) {
if (!videoViews[index].isReady) continue
videoViews[index].resetRetryCount()
videoViews[index].play()
//delay(300)
delay(300)
}
}.start()
fun stopAll() = MainScope().launch(Dispatchers.Main) {
if (videoViews.isEmpty()) return@launch
fun stopAll() {
if (videoViews.isEmpty()) return
for (index in data.indices) {
videoViews[index].stopRetryCount()
if (!videoViews[index].isPlaying || videoViews[index].isLoading) continue
videoViews[index].pause()
//delay(300)
}
}.start()
}
fun destroyAll() = MainScope().launch(Dispatchers.Main) {
if (videoViews.isEmpty()) return@launch
fun destroyAll() {
if (videoViews.isEmpty()) return
for (index in data.indices) {
//videoViews[index].destroy()
videoViews[index].destroySurface()
}
}.start()
}
companion object {
private val TAG = SplitViewFragment::class.java.simpleName

View File

@ -9,6 +9,7 @@ import android.util.Log
import android.view.LayoutInflater
import android.view.SurfaceHolder
import android.view.SurfaceView
import android.view.TextureView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.isVisible
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
@ -77,7 +78,8 @@ class VideoView : ConstraintLayout, GstCallback {
//videoView.holder.addCallback(this)
gstLibrary = GstLibrary(context)
gstLibrary.setSurfaceHolder(videoView.holder)
//gstLibrary.setSurfaceHolder(videoView.holder)
gstLibrary.setTextureView(videoView)
gstLibrary.setOnStatusChangeListener(this)
// View 預設狀態

View File

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

View File

@ -250,7 +250,7 @@ static void *app_function(void *userdata) {
g_main_context_push_thread_default(data->context);
/* Build pipeline */
data->pipeline = gst_parse_launch("playbin", &error);
data->pipeline = gst_parse_launch("playbin3", &error);
if (error) {
gchar *message = g_strdup_printf("Unable to build pipeline: %s", error->message);
g_clear_error(&error);

View File

@ -1,13 +1,16 @@
package com.hisharp.gstreamer_player
import android.content.Context
import android.graphics.SurfaceTexture
import android.util.Log
import android.view.Surface
import android.view.SurfaceHolder
import android.view.TextureView
import org.freedesktop.gstreamer.GStreamer
import java.io.Closeable
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 var gstCallback: GstCallback? = null
@ -15,6 +18,8 @@ class GstLibrary(context: Context) : Closeable, SurfaceHolder.Callback {
private var tag = ""
private var isInit = false
private var surface: Surface? = null
private external fun nativeInit() // Initialize native code, build pipeline, etc
private external fun nativeFinalize() // Destroy pipeline and shutdown native code
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)
}
fun setTextureView(textureView: TextureView) {
textureView.surfaceTextureListener = this
}
// Called from native code. This sets the content of the TextView from the UI thread.
private fun setMessage(message: String) {
if (gstCallback == null) return
@ -128,7 +137,8 @@ class GstLibrary(context: Context) : Closeable, SurfaceHolder.Callback {
override fun surfaceCreated(holder: SurfaceHolder) {
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) {
@ -138,11 +148,33 @@ class GstLibrary(context: Context) : Closeable, SurfaceHolder.Callback {
override fun surfaceDestroyed(holder: SurfaceHolder) {
Log.d("$TAG+$tag", "Surface destroyed")
isInit = false
this.isInit = false
this.surface = null
//pause()
//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 {
private val TAG = GstLibrary::class.java.simpleName