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"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="CompilerConfiguration"> <component name="CompilerConfiguration">
<bytecodeTargetLevel target="16" /> <bytecodeTargetLevel target="17" />
</component> </component>
</project> </project>

View File

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

View File

@ -1,4 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="DesignSurface"> <component name="DesignSurface">
<option name="filePathToZoomLevelMap"> <option name="filePathToZoomLevelMap">
@ -16,5 +15,5 @@
</option> </option>
</component> </component>
<component name="ExternalStorageConfigurationManager" enabled="true" /> <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> </project>

View File

@ -1,5 +1,12 @@
package com.ray650128.gstreamer_demo_app package com.ray650128.gstreamer_demo_app
object Constants { 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 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() set(value) = sharedPreferences.edit().putBoolean(IS_FIRST_OPEN_KEY, value).apply()
var lastSplitMode: Int 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() set(value) = sharedPreferences.edit().putInt(LAST_SPLIT_MODE, value).apply()
} }

View File

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

View File

@ -19,7 +19,7 @@ class MainViewModel: ViewModel() {
stream1 = "/media/video1", stream1 = "/media/video1",
stream2 = "/media/video2", stream2 = "/media/video2",
), ),
Device( /*Device(
deviceName = "192.168.0.73", deviceName = "192.168.0.73",
ip = "192.168.0.73", ip = "192.168.0.73",
rtspPort = "554", rtspPort = "554",
@ -27,8 +27,8 @@ class MainViewModel: ViewModel() {
password = "hs22601576", password = "hs22601576",
stream1 = "/media/video1", stream1 = "/media/video1",
stream2 = "/media/video2", stream2 = "/media/video2",
), ),*/
Device( /*Device(
deviceName = "192.168.0.79", deviceName = "192.168.0.79",
ip = "192.168.0.79", ip = "192.168.0.79",
rtspPort = "554", rtspPort = "554",
@ -36,7 +36,7 @@ class MainViewModel: ViewModel() {
password = "1q2w3e4r!", password = "1q2w3e4r!",
stream1 = "/media/video1", stream1 = "/media/video1",
stream2 = "/media/video2", stream2 = "/media/video2",
), ),*/
Device( Device(
deviceName = "192.168.0.88", deviceName = "192.168.0.88",
ip = "211.23.78.226", ip = "211.23.78.226",
@ -64,7 +64,7 @@ class MainViewModel: ViewModel() {
stream1 = "/v01", stream1 = "/v01",
stream2 = "/v02", stream2 = "/v02",
), ),
Device( /*Device(
deviceName = "192.168.0.76", deviceName = "192.168.0.76",
ip = "211.23.78.226", ip = "211.23.78.226",
rtspPort = "8576", rtspPort = "8576",
@ -72,7 +72,7 @@ class MainViewModel: ViewModel() {
password = "123456", password = "123456",
stream1 = "/profile1", stream1 = "/profile1",
stream2 = "/profile2", stream2 = "/profile2",
), ),*/
Device( Device(
deviceName = "192.168.0.82", deviceName = "192.168.0.82",
ip = "192.168.0.82", ip = "192.168.0.82",
@ -91,7 +91,7 @@ class MainViewModel: ViewModel() {
stream1 = "/profile1", stream1 = "/profile1",
stream2 = "/profile2", stream2 = "/profile2",
), ),
Device( /*Device(
deviceName = "192.168.0.95", deviceName = "192.168.0.95",
ip = "192.168.0.95", ip = "192.168.0.95",
rtspPort = "554", rtspPort = "554",
@ -99,7 +99,7 @@ class MainViewModel: ViewModel() {
password = "123456", password = "123456",
stream1 = "/profile1", stream1 = "/profile1",
stream2 = "/profile2", stream2 = "/profile2",
) )*/
) )
} }
@ -137,11 +137,4 @@ class MainViewModel: ViewModel() {
} }
return tmpData 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() { class SplitViewFragment : Fragment() {
val viewModel: SplitViewModel by activityViewModels()
private var mPageNum: Int = 0 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 streamType = VideoView.SUB_STREAM
private var isClickable = true private var isClickable = true
@ -98,143 +96,146 @@ class SplitViewFragment : Fragment() {
baseView.rowCount = maxRow baseView.rowCount = maxRow
baseView.columnCount = maxCol 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 { baseView.post {
for (col in 0 until maxCol) { // 根據分割數量決定子項目的寬度
for (row in 0 until maxRow) { val cellWidth: Int
val videoView = VideoView(requireContext()) val cellHeight: Int
val layoutParam = GridLayout.LayoutParams().apply { when (splitMode) {
topMargin = 0.dp Constants.SPLIT_MODE_SINGLE -> {
bottomMargin = 0.dp cellWidth = (baseView.width / maxRow)
marginEnd = 0.dp cellHeight = (baseView.height / maxCol)
marginStart = 0.dp }
else -> {
// 調整間距 cellWidth = (baseView.width / maxRow) - maxRow.dp
when (splitMode) { cellHeight = (baseView.height / maxCol) - maxCol.dp
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)
} }
} }
videoViews.forEach { videoView ->
setAllUrl() 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
}
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")
}
}
}
} }
} }
} }
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() { private fun setAllUrl() {
for (index in data.indices) { for (index in data.indices) {
videoViews[index].setData(data[index]) videoViews[index].setData(data[index])
videoViews[index].setTextVisible( 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) { 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) { for (index in data.indices) {
if (!videoViews[index].isReady) continue if (!videoViews[index].isReady) continue
videoViews[index].resetRetryCount() videoViews[index].resetRetryCount()
videoViews[index].play() videoViews[index].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) { for (index in data.indices) {
videoViews[index].stopRetryCount() videoViews[index].stopRetryCount()
if (!videoViews[index].isPlaying || videoViews[index].isLoading) continue if (!videoViews[index].isPlaying || videoViews[index].isLoading) continue
videoViews[index].pause() videoViews[index].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) { for (index in data.indices) {
//videoViews[index].destroy() //videoViews[index].destroy()
videoViews[index].destroySurface() videoViews[index].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
@ -77,7 +78,8 @@ class VideoView : ConstraintLayout, GstCallback {
//videoView.holder.addCallback(this) //videoView.holder.addCallback(this)
gstLibrary = GstLibrary(context) gstLibrary = GstLibrary(context)
gstLibrary.setSurfaceHolder(videoView.holder) //gstLibrary.setSurfaceHolder(videoView.holder)
gstLibrary.setTextureView(videoView)
gstLibrary.setOnStatusChangeListener(this) gstLibrary.setOnStatusChangeListener(this)
// View 預設狀態 // View 預設狀態

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

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

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,11 +148,33 @@ 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
this.surface = null
//pause() //pause()
//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 {
private val TAG = GstLibrary::class.java.simpleName private val TAG = GstLibrary::class.java.simpleName