Compare commits
5 Commits
0021b30842
..
Test
| Author | SHA1 | Date | |
|---|---|---|---|
| 4a8fb55fc8 | |||
| fdb88fc47a | |||
| ba12fd3856 | |||
| 8449706b64 | |||
| 02a26445be |
Generated
+1
-1
@@ -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>
|
||||
Generated
+1
-1
@@ -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$" />
|
||||
|
||||
Generated
+1
-2
@@ -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>
|
||||
+9
-9
@@ -6,6 +6,7 @@ plugins {
|
||||
}
|
||||
|
||||
android {
|
||||
namespace 'com.ray650128.gstreamer_demo_app'
|
||||
ndkVersion "21.3.6528147"
|
||||
compileSdk 33
|
||||
|
||||
@@ -26,29 +27,28 @@ android {
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
sourceCompatibility JavaVersion.VERSION_17
|
||||
targetCompatibility JavaVersion.VERSION_17
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = '1.8'
|
||||
jvmTarget = '17'
|
||||
}
|
||||
lint {
|
||||
abortOnError false
|
||||
checkReleaseBuilds false
|
||||
}
|
||||
viewBinding.enabled = true
|
||||
namespace 'com.ray650128.gstreamer_demo_app'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
implementation 'androidx.core:core-ktx:1.9.0'
|
||||
implementation 'androidx.core:core-ktx:1.10.1'
|
||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||
implementation 'com.google.android.material:material:1.8.0'
|
||||
implementation 'com.google.android.material:material:1.9.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||
implementation 'androidx.gridlayout:gridlayout:1.0.0'
|
||||
implementation 'androidx.core:core-ktx:1.9.0'
|
||||
implementation 'androidx.core:core-ktx:1.10.1'
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
|
||||
@@ -56,6 +56,6 @@ dependencies {
|
||||
implementation project(path: ':gstreamer_player')
|
||||
|
||||
// Android Jetpack lib
|
||||
implementation("androidx.fragment:fragment-ktx:1.5.5")
|
||||
implementation("androidx.activity:activity-ktx:1.6.1")
|
||||
implementation("androidx.fragment:fragment-ktx:1.5.7")
|
||||
implementation("androidx.activity:activity-ktx:1.7.1")
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -94,8 +94,7 @@ class MainActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun reloadVideoViews(list: List<List<Device>>?) = MainScope().launch {
|
||||
val oldListCount = videoPageList.size
|
||||
private fun reloadVideoViews(list: List<List<Device>>?) {
|
||||
for (videoPage in videoPageList) {
|
||||
supportFragmentManager.commit {
|
||||
remove(videoPage)
|
||||
@@ -104,9 +103,6 @@ class MainActivity : AppCompatActivity() {
|
||||
binding.viewPager.removeAllViews()
|
||||
videoPageList.clear()
|
||||
|
||||
if (oldListCount > 0) {
|
||||
delay(500L)
|
||||
}
|
||||
// 如果群組內沒有裝置,則顯示底圖
|
||||
if (list.isNullOrEmpty()) {
|
||||
binding.viewPager.setBackgroundResource(R.drawable.bg_not_in_playing)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
+52
-68
@@ -6,10 +6,12 @@ import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.gridlayout.widget.GridLayout
|
||||
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.dp
|
||||
import com.ray650128.gstreamer_demo_app.model.Device
|
||||
@@ -22,10 +24,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
|
||||
|
||||
@@ -56,18 +56,6 @@ class SplitViewFragment : Fragment() {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
initView()
|
||||
|
||||
/*viewModel.activePage.observe(viewLifecycleOwner) {
|
||||
if (it == null) return@observe
|
||||
if (it == this.mPageNum) {
|
||||
MainScope().launch {
|
||||
//delay(1000)
|
||||
//playAll()
|
||||
}
|
||||
} else {
|
||||
//stopAll()
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
@@ -90,6 +78,7 @@ class SplitViewFragment : Fragment() {
|
||||
}
|
||||
|
||||
private fun initView() {
|
||||
val displayUtil = DisplayUtils(requireContext())
|
||||
// 生成 VideoView 分割畫面
|
||||
binding.apply {
|
||||
val maxRow = sqrt(splitMode.toFloat()).toInt()
|
||||
@@ -98,9 +87,20 @@ class SplitViewFragment : Fragment() {
|
||||
|
||||
baseView.rowCount = maxRow
|
||||
baseView.columnCount = maxCol
|
||||
Log.e("${TAG}_$mPageNum", "baseView.rowCount: ${baseView.rowCount}, baseView.columnCount: ${baseView.columnCount}")
|
||||
|
||||
baseView.post {
|
||||
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 (row in 0 until maxRow) {
|
||||
val videoView = VideoView(requireContext())
|
||||
@@ -110,13 +110,12 @@ class SplitViewFragment : Fragment() {
|
||||
marginEnd = 0.dp
|
||||
marginStart = 0.dp
|
||||
|
||||
width = cellWidth
|
||||
height = cellHeight
|
||||
|
||||
// 調整間距
|
||||
when (splitMode) {
|
||||
MainViewModel.PAGE_MODE_ONE -> {
|
||||
width = (baseView.width / maxRow)
|
||||
height = (baseView.height / maxCol)
|
||||
}
|
||||
MainViewModel.PAGE_MODE_FOUR -> {
|
||||
Constants.SPLIT_MODE_FOUR -> {
|
||||
when (col) {
|
||||
0 -> bottomMargin = 2.dp
|
||||
1 -> topMargin = 2.dp
|
||||
@@ -125,10 +124,8 @@ class SplitViewFragment : Fragment() {
|
||||
0 -> marginEnd = 2.dp
|
||||
1 -> marginStart = 2.dp
|
||||
}
|
||||
width = (baseView.width / maxRow) - maxRow.dp
|
||||
height = (baseView.height / maxCol) - maxCol.dp
|
||||
}
|
||||
MainViewModel.PAGE_MODE_NINE -> {
|
||||
Constants.SPLIT_MODE_NINE -> {
|
||||
if (col == 1) {
|
||||
topMargin = 4.dp
|
||||
bottomMargin = 4.dp
|
||||
@@ -137,10 +134,8 @@ class SplitViewFragment : Fragment() {
|
||||
marginEnd = 4.dp
|
||||
marginStart = 4.dp
|
||||
}
|
||||
width = (baseView.width / maxRow) - maxRow.dp
|
||||
height = (baseView.height / maxCol) - maxCol.dp
|
||||
}
|
||||
MainViewModel.PAGE_MODE_SIXTEEN -> {
|
||||
Constants.SPLIT_MODE_SIXTEEN -> {
|
||||
if (col == 1) {
|
||||
topMargin = 4.dp
|
||||
bottomMargin = 2.dp
|
||||
@@ -157,8 +152,6 @@ class SplitViewFragment : Fragment() {
|
||||
marginStart = 2.dp
|
||||
marginEnd = 4.dp
|
||||
}
|
||||
width = (baseView.width / maxRow) - maxRow.dp
|
||||
height = (baseView.height / maxCol) - maxCol.dp
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -166,20 +159,16 @@ class SplitViewFragment : Fragment() {
|
||||
videoViews.add(videoView)
|
||||
}
|
||||
}
|
||||
|
||||
setAllUrl()
|
||||
}
|
||||
|
||||
if (isClickable) {
|
||||
for (position in videoViews.indices) {
|
||||
videoViews[position].setOnClickListener {
|
||||
if (position >= data.size) return@setOnClickListener
|
||||
if (!videoViews[position].isPlaying) {
|
||||
videoViews.forEach { videoView ->
|
||||
videoView.setOnClickListener {
|
||||
if (!videoView.isPlaying) {
|
||||
return@setOnClickListener
|
||||
}
|
||||
MainScope().launch {
|
||||
stopAll()
|
||||
delay(splitMode * Constants.CONF_DELAY_BASE_MILLIS)
|
||||
val item = data[position]
|
||||
val item = videoView.data
|
||||
val bundle = Bundle().apply {
|
||||
//putInt(MonitoringActivity.BUNDLE_DEVICE_ID, item.id)
|
||||
//putInt(MonitoringActivity.BUNDLE_CHANNEL_ID, item.channelId)
|
||||
@@ -193,48 +182,43 @@ class SplitViewFragment : Fragment() {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setAllUrl()
|
||||
}
|
||||
|
||||
private fun setAllUrl() {
|
||||
for (index in data.indices) {
|
||||
videoViews[index].setData(data[index])
|
||||
videoViews[index].data = 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)
|
||||
for (index in data.indices) {
|
||||
if (!videoViews[index].isReady) continue
|
||||
videoViews[index].resetRetryCount()
|
||||
videoViews[index].play()
|
||||
//delay(300)
|
||||
fun playAll() {
|
||||
videoViews.forEach { videoView ->
|
||||
if (videoView.isReady) {
|
||||
videoView.resetRetryCount()
|
||||
videoView.play()
|
||||
}
|
||||
}
|
||||
}
|
||||
}.start()
|
||||
|
||||
fun stopAll() = MainScope().launch(Dispatchers.Main) {
|
||||
if (videoViews.isEmpty()) return@launch
|
||||
for (index in data.indices) {
|
||||
videoViews[index].stopRetryCount()
|
||||
if (!videoViews[index].isPlaying || videoViews[index].isLoading) continue
|
||||
videoViews[index].pause()
|
||||
//delay(300)
|
||||
fun stopAll() {
|
||||
videoViews.forEach { videoView ->
|
||||
videoView.stopRetryCount()
|
||||
if (videoView.isPlaying || !videoView.isLoading) {
|
||||
videoView.pause()
|
||||
}
|
||||
}
|
||||
}
|
||||
}.start()
|
||||
|
||||
fun destroyAll() = MainScope().launch(Dispatchers.Main) {
|
||||
if (videoViews.isEmpty()) return@launch
|
||||
for (index in data.indices) {
|
||||
//videoViews[index].destroy()
|
||||
videoViews[index].destroySurface()
|
||||
fun destroyAll() {
|
||||
videoViews.forEach { videoView ->
|
||||
//videoView.destroy()
|
||||
videoView.destroySurface()
|
||||
}
|
||||
}
|
||||
}.start()
|
||||
|
||||
companion object {
|
||||
private val TAG = SplitViewFragment::class.java.simpleName
|
||||
|
||||
@@ -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
|
||||
@@ -34,7 +35,25 @@ class VideoView : ConstraintLayout, GstCallback {
|
||||
|
||||
private lateinit var view: ItemVideoViewBinding
|
||||
|
||||
private var data: Device? = null
|
||||
var streamType: Int = MAIN_STREAM
|
||||
|
||||
var data: Device? = null
|
||||
set(value) {
|
||||
field = value
|
||||
if (field == null) {
|
||||
view.textDeviceName.isVisible = false
|
||||
isPlaying = false
|
||||
isLoading = false
|
||||
return
|
||||
}
|
||||
this.tag = field?.deviceName
|
||||
view.textDeviceName.text = field?.deviceName
|
||||
view.textDeviceName.isVisible = true
|
||||
val rtspUrl = this.data?.getStreamPath(streamType) ?: return // 如果 null 就不指派給 Gstreamer 了
|
||||
gstLibrary.setTag(this.data!!.deviceName)
|
||||
gstLibrary.setRtspUrl(rtspUrl)
|
||||
Log.d("${TAG}_$tag", "Set device to: $field, rtspUrl = $rtspUrl")
|
||||
}
|
||||
|
||||
var isReady: Boolean = false
|
||||
|
||||
@@ -62,7 +81,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
|
||||
|
||||
@@ -75,11 +94,13 @@ class VideoView : ConstraintLayout, GstCallback {
|
||||
view = ItemVideoViewBinding.inflate(layoutInflater, this, true)
|
||||
view.baseView.clipToOutline = true
|
||||
|
||||
//videoView.holder.addCallback(this)
|
||||
gstLibrary = GstLibrary(context)
|
||||
gstLibrary.setSurfaceHolder(videoView.holder)
|
||||
gstLibrary.setTextureView(videoView)
|
||||
gstLibrary.setOnStatusChangeListener(this)
|
||||
|
||||
//videoView.holder.addCallback(this)
|
||||
//gstLibrary.setSurfaceHolder(videoView.holder)
|
||||
|
||||
// View 預設狀態
|
||||
view.textDeviceName.isVisible = false
|
||||
isPlaying = false
|
||||
@@ -92,23 +113,6 @@ class VideoView : ConstraintLayout, GstCallback {
|
||||
}
|
||||
}
|
||||
|
||||
fun setData(device: Device?, streamType: Int = SUB_STREAM) {
|
||||
if (device == null) {
|
||||
view.textDeviceName.isVisible = false
|
||||
isPlaying = false
|
||||
isLoading = false
|
||||
return
|
||||
}
|
||||
this.data = device
|
||||
this.tag = device.deviceName
|
||||
view.textDeviceName.text = device.deviceName
|
||||
view.textDeviceName.isVisible = true
|
||||
val rtspUrl = this.data?.getStreamPath(streamType) ?: return // 如果 null 就不指派給 Gstreamer 了
|
||||
gstLibrary.setTag(this.data!!.deviceName)
|
||||
gstLibrary.setRtspUrl(rtspUrl)
|
||||
Log.d("${TAG}_$tag", "Set device to: $device, rtspUrl = $rtspUrl")
|
||||
}
|
||||
|
||||
fun setTextVisible(isVisible: Boolean) {
|
||||
view.textDeviceName.isVisible = isVisible
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
+2
-2
@@ -7,8 +7,8 @@ buildscript {
|
||||
}
|
||||
|
||||
plugins {
|
||||
id 'com.android.application' version '7.4.2' apply false
|
||||
id 'com.android.library' version '7.4.2' apply false
|
||||
id 'com.android.application' version '8.0.1' apply false
|
||||
id 'com.android.library' version '8.0.1' apply false
|
||||
id 'org.jetbrains.kotlin.android' version '1.8.10' apply false
|
||||
id 'org.jetbrains.kotlin.plugin.parcelize' version '1.7.0' apply false
|
||||
}
|
||||
|
||||
@@ -19,3 +19,6 @@ android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
# Kotlin code style for this project: "official" or "obsolete":
|
||||
kotlin.code.style=official
|
||||
android.defaults.buildfeatures.buildconfig=true
|
||||
android.nonTransitiveRClass=false
|
||||
android.nonFinalResIds=false
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
#Thu Mar 24 14:41:01 CST 2022
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
|
||||
distributionPath=wrapper/dists
|
||||
zipStorePath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
@@ -2,9 +2,10 @@ apply plugin: 'com.android.library'
|
||||
apply plugin: 'org.jetbrains.kotlin.android'
|
||||
|
||||
android {
|
||||
namespace 'com.hisharp.gstreamer_player'
|
||||
ndkVersion "21.3.6528147"
|
||||
|
||||
compileSdkVersion 33
|
||||
compileSdk 33
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 15
|
||||
@@ -58,6 +59,17 @@ android {
|
||||
path 'jni/Android.mk'
|
||||
}
|
||||
}
|
||||
buildFeatures {
|
||||
renderScript true
|
||||
aidl true
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_17
|
||||
targetCompatibility JavaVersion.VERSION_17
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = '17'
|
||||
}
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
@@ -69,7 +81,7 @@ afterEvaluate {
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation 'androidx.core:core-ktx:1.9.0'
|
||||
implementation 'androidx.core:core-ktx:1.10.1'
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
//implementation 'androidx.appcompat:appcompat:1.6.0'
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,19 +137,41 @@ 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) {
|
||||
Log.d("$TAG+$tag", String.format("Surface changed, format: %d, width: %d, height: %d", format, width, height))
|
||||
//Log.d("$TAG+$tag", String.format("Surface changed, format: %d, width: %d, height: %d", format, width, height))
|
||||
/*setSurfaceHolder(holder);*/
|
||||
}
|
||||
|
||||
override fun surfaceDestroyed(holder: SurfaceHolder) {
|
||||
Log.d("$TAG+$tag", "Surface destroyed")
|
||||
isInit = false
|
||||
//pause()
|
||||
//releaseSurface()
|
||||
//Log.d("$TAG+$tag", "Surface destroyed")
|
||||
this.isInit = false
|
||||
this.surface = null
|
||||
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 {
|
||||
|
||||
Reference in New Issue
Block a user