Compare commits

..

32 Commits

Author SHA1 Message Date
Raymond Yang e3c958c632 加入初始化旗標 2023-03-07 11:55:54 +08:00
Raymond Yang 48db07290c 調整onResume 2023-03-06 17:08:20 +08:00
Raymond Yang 1b80ae3349 更新Gradle Plugin 2023-03-06 10:11:18 +08:00
Raymond Yang ed8f2a7c0f 使用暴力方式解決閃退問題:加大ViewPager可以容許的page數量 2023-03-06 09:31:20 +08:00
Raymond Yang d8d351831c 加上exitProcess 2023-02-10 10:36:51 +08:00
Raymond Yang 77df0eb8a5 修正GstLibrary的LOG TAG名稱 2023-02-10 09:23:00 +08:00
Raymond Yang 0dca3ce9e8 C lib加上tag區分 2023-02-09 16:16:49 +08:00
Raymond Yang 363bfdbfbd 調整delay時間算法 2023-02-09 11:27:55 +08:00
Raymond Yang dc1da2449b 當MainActivity退出時,呼叫GStreamer的finalize 2023-02-08 14:41:33 +08:00
Raymond Yang 53763bfa34 將延遲參數常數化 2023-02-08 10:28:40 +08:00
Raymond Yang 4cf5ae5094 補上VideoViewAdapter 2023-02-07 17:01:44 +08:00
Raymond Yang ced178513b 將滑動控制加上播放/暫停 2023-02-07 17:01:07 +08:00
Raymond Yang 88954a6599 GStreamer更新到1.22.0 2023-02-07 16:02:56 +08:00
Raymond Yang df105dd518 將stopRetryCount函數加上removeCallbacks,停止重連功能 2023-02-07 13:45:41 +08:00
Raymond Yang 2f4e3659f9 更新Kotlin plugin 2023-02-06 14:03:39 +08:00
Raymond Yang 2e8d8a6381 將分割設定值存入Shared Preference內 2023-02-06 10:55:45 +08:00
Raymond Yang 87f731168f 如果目前的分割模式是點選的類型,則不刷新分割模式 2023-02-03 17:46:27 +08:00
Raymond Yang c7d0785cef 將延遲時間改成500毫秒,再繼續觀察 2023-02-03 17:40:22 +08:00
Raymond Yang a37e79d841 加上註解 2023-02-03 12:24:43 +08:00
Raymond Yang 4e09a449ca 將Device修改成HiSharpDX的資料結構 2023-02-03 12:07:06 +08:00
Raymond Yang 85218c443f 將跳轉加上延遲 2023-02-02 17:13:20 +08:00
Raymond Yang b4346f142b 將跳轉加上coroutine 2023-02-02 12:20:35 +08:00
Raymond Yang 9fc8673715 將停止播放函數改成手動呼叫,測試看看是否還會容易閃退 2023-02-02 10:20:00 +08:00
Raymond Yang de152da749 在surface create時,呼叫gstLibrary.setSurfaceHolder(holder) 2023-02-01 14:48:37 +08:00
Raymond Yang db673d3553 修正log內容 2023-02-01 13:35:46 +08:00
Raymond Yang 792ebc70c2 將VideoView的retryCount封裝 2023-02-01 10:38:22 +08:00
Raymond Yang 90344d14b0 VideoView加上預設狀態 2023-02-01 09:13:01 +08:00
Raymond Yang c3f8366475 加入點擊 2023-01-31 11:56:05 +08:00
Raymond Yang 15475923cb 整理專案結構 2023-01-31 09:49:26 +08:00
Raymond Yang a29f0e7bbf 加入重連機制 2023-01-30 16:16:20 +08:00
Raymond Yang ac15111ab3 加入重連機制 2023-01-30 16:15:34 +08:00
Raymond Yang cdd2c3775d 狀態加上緩衝中 2023-01-30 11:56:20 +08:00
24 changed files with 640 additions and 205 deletions
+1
View File
@@ -48,6 +48,7 @@ dependencies {
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:+'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
+9 -4
View File
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.ray650128.gstreamer_demo_app">
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:name=".MyApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
@@ -10,14 +10,19 @@
android:supportsRtl="true"
android:theme="@style/Theme.Gstreamer">
<activity
android:name="com.ray650128.gstreamer_demo_app.MainActivity"
android:exported="true">
android:name="com.ray650128.gstreamer_demo_app.ui.mainScreen.MainActivity"
android:exported="true"
android:screenOrientation="nosensor">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".ui.monitoringScreen.MonitoringActivity"
android:exported="false"
android:screenOrientation="nosensor" />
</application>
</manifest>
@@ -0,0 +1,5 @@
package com.ray650128.gstreamer_demo_app
object Constants {
const val CONF_DELAY_BASE_MILLIS = 1000L
}
@@ -1,97 +0,0 @@
package com.ray650128.gstreamer_demo_app
import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.ray650128.gstreamer_demo_app.model.Device
class MainViewModel: ViewModel() {
private val uriList: List<Device> by lazy {
listOf(
Device(
name = "192.168.0.73",
rtspUrl = "rtsp://admin:hs22601576@@192.168.0.73:554/media/video2"
),
Device(
name = "192.168.0.77",
rtspUrl = "rtsp://admin:admin@192.168.0.77:554/media/video2"
),
Device(
name = "192.168.0.79",
rtspUrl = "rtsp://admin:1q2w3e4r!@192.168.0.79:554/media/video2"
),
Device(
name = "192.168.0.88",
rtspUrl = "rtsp://admin:1q2w3e4r~@211.23.78.226:8588/media/video2"
),
Device(
name = "192.168.0.74",
rtspUrl = "rtsp://admin:admin@211.23.78.226:8574/v02"
),
Device(
name = "192.168.0.75",
rtspUrl = "rtsp://admin:admin@211.23.78.226:8575/v02"
),
Device(
name = "192.168.0.76",
rtspUrl = "rtsp://admin:123456@211.23.78.226:8576/profile2"
),
Device(
name = "192.168.0.82",
rtspUrl = "rtsp://admin:123456@192.168.0.82:554/profile2"
),
Device(
name = "192.168.0.84",
rtspUrl = "rtsp://admin:123456@192.168.0.84:554/profile2"
),
Device(
name = "192.168.0.95",
rtspUrl = "rtsp://admin:123456@192.168.0.95:554/profile2"
)
)
}
private var splitModeInt: Int = PAGE_MODE_ONE
val splitMode: MutableLiveData<Int> by lazy { MutableLiveData<Int>() }
val cameraList: MediatorLiveData<List<List<Device>>> by lazy {
MediatorLiveData<List<List<Device>>>().apply {
addSource(splitMode) {
postValue(updateCameraList(uriList))
}
}
}
init {
setSplitMode(PAGE_MODE_ONE)
}
fun setSplitMode(mode: Int) {
splitModeInt = mode
splitMode.postValue(splitModeInt)
}
private fun updateCameraList(dbData: List<Device>?): List<List<Device>>? {
if (dbData.isNullOrEmpty()) return null
val tmpData = ArrayList<List<Device>>()
for (index in uriList.indices step (splitModeInt)) {
if (index == uriList.size) break
val tmpSubData = ArrayList<Device>()
for (subIndex in 0 until (splitModeInt)) {
val dataIndex = index + subIndex
if (dataIndex !in uriList.indices) break
tmpSubData.add(uriList[dataIndex])
}
tmpData.add(tmpSubData)
}
return tmpData
}
companion object {
const val PAGE_MODE_ONE = 1
const val PAGE_MODE_FOUR = 4
const val PAGE_MODE_NINE = 9
}
}
@@ -0,0 +1,49 @@
package com.ray650128.gstreamer_demo_app
import android.app.Activity
import android.app.Application
import android.content.Context
import android.os.Bundle
import android.util.Log
import kotlin.system.exitProcess
class MyApplication : Application(), Application.ActivityLifecycleCallbacks {
override fun onCreate() {
super.onCreate()
instance = this
registerActivityLifecycleCallbacks(this)
}
private var currentActivity: Activity? = null
override fun onActivityCreated(p0: Activity, p1: Bundle?) {}
override fun onActivityStarted(p0: Activity) {}
override fun onActivityResumed(p0: Activity) {
currentActivity = p0
}
override fun onActivityPaused(p0: Activity) {}
override fun onActivityStopped(p0: Activity) {}
override fun onActivitySaveInstanceState(p0: Activity, p1: Bundle) {}
override fun onActivityDestroyed(p0: Activity) {
if (currentActivity?.equals(p0) == true) {
Log.e("MyApplication", "EXIT...")
onTerminate()
exitProcess(0)
}
}
companion object {
var instance: Application? = null
fun getAppContext(): Context {
return instance!!.applicationContext
}
}
}
@@ -0,0 +1,24 @@
package com.ray650128.gstreamer_demo_app
import android.content.Context
import com.ray650128.gstreamer_demo_app.ui.mainScreen.MainViewModel
/**
* Shared Preferences 工具類別
* @author Raymond Yang
*/
object PreferenceUtil {
private const val MAIN_KEY = "GST_DEMO_APP"
private const val IS_FIRST_OPEN_KEY = "IS_FIRST_OPEN_KEY"
private const val LAST_SPLIT_MODE = "LAST_SPLIT_MODE"
private val sharedPreferences = MyApplication.getAppContext().getSharedPreferences(MAIN_KEY, Context.MODE_PRIVATE)
var isFirstOpen: Boolean
get() = sharedPreferences.getBoolean(IS_FIRST_OPEN_KEY, true)
set(value) = sharedPreferences.edit().putBoolean(IS_FIRST_OPEN_KEY, value).apply()
var lastSplitMode: Int
get() = sharedPreferences.getInt(LAST_SPLIT_MODE, MainViewModel.PAGE_MODE_ONE)
set(value) = sharedPreferences.edit().putInt(LAST_SPLIT_MODE, value).apply()
}
@@ -0,0 +1,55 @@
package com.ray650128.gstreamer_demo_app.extensions
import android.util.Log
import com.ray650128.gstreamer_demo_app.model.Device
/**
* IpCam 擴充函式-取得完整串流路徑
* @param streamPathNum 串流路徑編號
* @author Raymond Yang
*/
fun Device.getStreamPath(streamPathNum: Int): String? {
val stringBuilder = getPath(this) ?: return null
// 加入串流路徑
when (streamPathNum) {
1 -> if (this.stream1.isNotEmpty()) {
if (this.stream1.first() != '/') {
stringBuilder.append("/")
}
stringBuilder.append(this.stream1)
}
2 -> if (this.stream2.isNotEmpty()) {
if (this.stream2.first() != '/') {
stringBuilder.append("/")
}
stringBuilder.append(this.stream2)
}
else -> return null
}
Log.d("+++URL", stringBuilder.toString())
return stringBuilder.toString()
}
private fun getPath(ipCam: Device): StringBuilder? {
if (ipCam.ip.isEmpty()) return null
val stringBuilder = StringBuilder()
// 加入 URL schema
stringBuilder.append("rtsp://")
// 加入帳號密碼
if (ipCam.account.isNotEmpty()) {
if (ipCam.password.isEmpty()) {
stringBuilder.append("${ipCam.account}@")
} else {
stringBuilder.append("${ipCam.account}:${ipCam.password}@")
}
}
// 加入 IP 及 port
stringBuilder.append(ipCam.ip)
if (ipCam.rtspPort.isNotEmpty()) {
stringBuilder.append(":${ipCam.rtspPort}")
}
return stringBuilder
}
@@ -5,7 +5,13 @@ import kotlinx.parcelize.Parcelize
@Parcelize
data class Device(
val name: String = "",
val rtspUrl: String = "",
var ip: String = "",
var rtspPort: String = "",
val deviceName: String = "",
var account: String = "",
var password: String = "",
var stream1: String = "",
var stream2: String = "",
//val rtspUrl: String = "",
var isPlaying: Boolean = false
): Parcelable
@@ -1,19 +1,20 @@
package com.ray650128.gstreamer_demo_app
package com.ray650128.gstreamer_demo_app.ui.mainScreen
import android.content.Context
import android.graphics.Color
import android.os.Bundle
import android.util.Log
import android.view.WindowManager
import android.widget.Toast
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.viewpager2.widget.ViewPager2
import com.ray650128.gstreamer_demo_app.Constants
import com.ray650128.gstreamer_demo_app.R
import com.ray650128.gstreamer_demo_app.databinding.ActivityMainBinding
import com.ray650128.gstreamer_demo_app.model.Device
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.freedesktop.gstreamer.GStreamer
class MainActivity : AppCompatActivity() {
@@ -21,9 +22,12 @@ class MainActivity : AppCompatActivity() {
private val viewModel: MainViewModel by viewModels()
private val splitViewModel: SplitViewModel by viewModels()
private val mContext: Context by lazy { this }
private var splitMode = 1
private var oldSplitMode = 1
private lateinit var splitVideoViewAdapter: VideoViewAdapter
@@ -43,32 +47,70 @@ class MainActivity : AppCompatActivity() {
initObservers()
}
override fun onBackPressed() {
super.onBackPressed()
finish()
}
override fun onDestroy() {
for (i in 0 until splitVideoViewAdapter.itemCount) {
splitVideoViewAdapter.destroy(i)
}
super.onDestroy()
}
private fun initContentView() = binding.apply {
//region Content area
splitVideoViewAdapter = VideoViewAdapter(supportFragmentManager, lifecycle)
viewPager.apply {
adapter = splitVideoViewAdapter
offscreenPageLimit = 1
offscreenPageLimit = 100
setPageTransformer(null)
registerOnPageChangeCallback(object: ViewPager2.OnPageChangeCallback() {
private var oldPage = 0
override fun onPageScrollStateChanged(state: Int) {
super.onPageScrollStateChanged(state)
/*if (state == ViewPager2.SCROLL_STATE_DRAGGING) {
oldPage = currentPage
splitVideoViewAdapter.stop(currentPage)
}
if (state == ViewPager2.SCROLL_STATE_IDLE && currentPage == oldPage) {
splitVideoViewAdapter.play(currentPage)
}*/
//Log.d("Split", "oldPage: $oldPage, currentPage: $currentPage")
}
override fun onPageScrolled(
position: Int,
positionOffset: Float,
positionOffsetPixels: Int
) {
super.onPageScrolled(position, positionOffset, positionOffsetPixels)
//Log.d("Split", "onPageScrolled: $currentPage")
}
override fun onPageSelected(position: Int) {
super.onPageSelected(position)
currentPage = position
splitViewModel.activePage.postValue(position)
//Log.d("Split", "currentPage: $currentPage")
}
})
}
button.setOnClickListener {
if (splitMode == MainViewModel.PAGE_MODE_ONE) return@setOnClickListener
viewModel.setSplitMode(MainViewModel.PAGE_MODE_ONE)
Log.e(TAG, "+++ split style: 1")
}
button2.setOnClickListener {
if (splitMode == MainViewModel.PAGE_MODE_FOUR) return@setOnClickListener
viewModel.setSplitMode(MainViewModel.PAGE_MODE_FOUR)
Log.e(TAG, "+++ split style: 4")
}
button3.setOnClickListener {
if (splitMode == MainViewModel.PAGE_MODE_NINE) return@setOnClickListener
viewModel.setSplitMode(MainViewModel.PAGE_MODE_NINE)
Log.e(TAG, "+++ split style: 9")
}
@@ -90,6 +132,12 @@ class MainActivity : AppCompatActivity() {
private fun reloadVideoViews(list: List<List<Device>>?) = MainScope().launch {
binding.viewPager.setCurrentItem(0, false)
for (i in 0 until splitVideoViewAdapter.itemCount) {
splitVideoViewAdapter.stop(i)
}
//delay(oldSplitMode * Constants.CONF_DELAY_BASE_MILLIS)
delay((oldSplitMode * 100) + Constants.CONF_DELAY_BASE_MILLIS)
oldSplitMode = splitMode
splitVideoViewAdapter.clear()
// 如果群組內沒有裝置,則顯示底圖
if (list.isNullOrEmpty()) {
@@ -104,6 +152,8 @@ class MainActivity : AppCompatActivity() {
)
splitVideoViewAdapter.add(i, splitFragment)
}
currentPage = 0
//binding.viewPager.currentItem = 0
}
}
@@ -0,0 +1,146 @@
package com.ray650128.gstreamer_demo_app.ui.mainScreen
import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.ray650128.gstreamer_demo_app.PreferenceUtil
import com.ray650128.gstreamer_demo_app.model.Device
class MainViewModel: ViewModel() {
private val uriList: List<Device> by lazy {
listOf(
Device(
deviceName = "192.168.0.77",
ip = "192.168.0.77",
rtspPort = "554",
account = "admin",
password = "admin",
stream1 = "/media/video1",
stream2 = "/media/video2",
),
Device(
deviceName = "192.168.0.73",
ip = "192.168.0.73",
rtspPort = "554",
account = "admin",
password = "hs22601576",
stream1 = "/media/video1",
stream2 = "/media/video2",
),
Device(
deviceName = "192.168.0.79",
ip = "192.168.0.79",
rtspPort = "554",
account = "admin",
password = "1q2w3e4r!",
stream1 = "/media/video1",
stream2 = "/media/video2",
),
Device(
deviceName = "192.168.0.88",
ip = "211.23.78.226",
rtspPort = "8588",
account = "admin",
password = "1q2w3e4r~",
stream1 = "/media/video1",
stream2 = "/media/video2",
),
Device(
deviceName = "192.168.0.74",
ip = "211.23.78.226",
rtspPort = "8574",
account = "admin",
password = "admin",
stream1 = "/v01",
stream2 = "/v02",
),
Device(
deviceName = "192.168.0.75",
ip = "211.23.78.226",
rtspPort = "8575",
account = "admin",
password = "admin",
stream1 = "/v01",
stream2 = "/v02",
),
Device(
deviceName = "192.168.0.76",
ip = "211.23.78.226",
rtspPort = "8576",
account = "admin",
password = "123456",
stream1 = "/profile1",
stream2 = "/profile2",
),
Device(
deviceName = "192.168.0.82",
ip = "192.168.0.82",
rtspPort = "554",
account = "admin",
password = "123456",
stream1 = "/profile1",
stream2 = "/profile2",
),
Device(
deviceName = "192.168.0.84",
ip = "192.168.0.84",
rtspPort = "554",
account = "admin",
password = "123456",
stream1 = "/profile1",
stream2 = "/profile2",
),
Device(
deviceName = "192.168.0.95",
ip = "192.168.0.95",
rtspPort = "554",
account = "admin",
password = "123456",
stream1 = "/profile1",
stream2 = "/profile2",
)
)
}
val splitMode: MutableLiveData<Int> by lazy { MutableLiveData<Int>() }
val cameraList: MediatorLiveData<List<List<Device>>> by lazy {
MediatorLiveData<List<List<Device>>>().apply {
addSource(splitMode) {
postValue(updateCameraList(uriList))
}
}
}
init {
splitMode.postValue(PreferenceUtil.lastSplitMode)
}
fun setSplitMode(mode: Int) {
PreferenceUtil.lastSplitMode = mode
splitMode.postValue(PreferenceUtil.lastSplitMode)
}
private fun updateCameraList(dbData: List<Device>?): List<List<Device>>? {
if (dbData.isNullOrEmpty()) return null
val tmpData = ArrayList<List<Device>>()
for (index in uriList.indices step (PreferenceUtil.lastSplitMode)) {
if (index == uriList.size) break
val tmpSubData = ArrayList<Device>()
for (subIndex in 0 until (PreferenceUtil.lastSplitMode)) {
val dataIndex = index + subIndex
if (dataIndex !in uriList.indices) break
tmpSubData.add(uriList[dataIndex])
}
tmpData.add(tmpSubData)
}
return tmpData
}
companion object {
const val PAGE_MODE_ONE = 1
const val PAGE_MODE_FOUR = 4
const val PAGE_MODE_NINE = 9
}
}
@@ -1,14 +1,19 @@
package com.ray650128.gstreamer_demo_app
package com.ray650128.gstreamer_demo_app.ui.mainScreen
import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
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.databinding.FragmentSplitViewBinding
import com.ray650128.gstreamer_demo_app.dp
import com.ray650128.gstreamer_demo_app.model.Device
import com.ray650128.gstreamer_demo_app.ui.monitoringScreen.MonitoringActivity
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.delay
@@ -17,6 +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 streamType = VideoView.SUB_STREAM
@@ -49,11 +56,23 @@ 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() {
super.onPause()
stopAll()
super.onPause()
Log.d("${TAG}_$mPageNum", "onPause()")
}
@@ -63,6 +82,13 @@ class SplitViewFragment : Fragment() {
Log.d("${TAG}_$mPageNum", "onResume()")
}
override fun onDestroy() {
super.onDestroy()
//destroyAll()
stopAll()
Log.d("${TAG}_$mPageNum", "onDestroy()")
}
private fun initView() {
// 生成 VideoView 分割畫面
binding.apply {
@@ -122,31 +148,40 @@ class SplitViewFragment : Fragment() {
}
setAllUrl()
}
}
if (isClickable) {
for (position in videoViews.indices) {
videoViews[position].setOnClickListener {
if (position >= data.size) return@setOnClickListener
/*if (!videoViews[position].isPlaying) {
if (!videoViews[position].isPlaying) {
if (!videoViews[position].isLoading) {
videoViews[position].resetRetryCount()
videoViews[position].play()
}
return@setSafeOnClickListener
return@setOnClickListener
}
MainScope().launch {
stopAll()
delay(splitMode * Constants.CONF_DELAY_BASE_MILLIS)
//delay((splitMode * 100) + 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)
//putInt(MonitoringActivity.BUNDLE_DEVICE_ID, item.id)
//putInt(MonitoringActivity.BUNDLE_CHANNEL_ID, item.channelId)
putParcelable(MonitoringActivity.BUNDLE_DEVICE, item)
}
gotoActivity(MonitoringActivity::class.java, bundle)*/
val intent = Intent(requireContext(), MonitoringActivity::class.java)
intent.putExtras(bundle)
startActivity(intent)
//gotoActivity(MonitoringActivity::class.java, bundle)*/
//Log.d("${TAG}_$mPageNum", "check: $item")
}
}
}
}
}
}
}
private fun setAllUrl() {
for (index in data.indices) {
@@ -157,18 +192,32 @@ class SplitViewFragment : Fragment() {
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)
}
//delay(300)
}
}.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].stop()
delay(300)
//delay(300)
}
}.start()
fun destroyAll() /*= MainScope().launch(Dispatchers.Main)*/ {
if (videoViews.isEmpty()) return
for (index in data.indices) {
videoViews[index].destroy()
//delay(100)
}
}//.start()
companion object {
private val TAG = SplitViewFragment::class.java.simpleName
@@ -0,0 +1,10 @@
package com.ray650128.gstreamer_demo_app.ui.mainScreen
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
class SplitViewModel : ViewModel() {
val activePage: MutableLiveData<Int> by lazy {
MutableLiveData()
}
}
@@ -1,4 +1,4 @@
package com.ray650128.gstreamer_demo_app
package com.ray650128.gstreamer_demo_app.ui.mainScreen
import android.content.Context
import android.os.Handler
@@ -15,9 +15,10 @@ import com.hisharp.gstreamer_player.GstCallback
import com.hisharp.gstreamer_player.GstLibrary
import com.hisharp.gstreamer_player.GstStatus
import com.ray650128.gstreamer_demo_app.databinding.ItemVideoViewBinding
import com.ray650128.gstreamer_demo_app.extensions.getStreamPath
import com.ray650128.gstreamer_demo_app.model.Device
class VideoView : ConstraintLayout, SurfaceHolder.Callback, GstCallback {
class VideoView : ConstraintLayout, GstCallback {
constructor(context: Context) : super(context) {
initView(context)
@@ -35,6 +36,8 @@ class VideoView : ConstraintLayout, SurfaceHolder.Callback, GstCallback {
private var data: Device? = null
var isReady: Boolean = false
var isLoading: Boolean = false
set(value) {
view.pbLoading.isVisible = value
@@ -47,14 +50,18 @@ class VideoView : ConstraintLayout, SurfaceHolder.Callback, GstCallback {
field = value
}
private var retryCount = 0
private var retryRunnable = object : Runnable {
override fun run() {
play()
//Log.e("${TAG}_$tag", "Retry count: $retryCount")
}
}
private val videoView: SurfaceView by lazy { view.videoView }
private lateinit var gstLibrary: GstLibrary
private var streamType = MAIN_STREAM
private var retryCount = 0
private val mHandler: MyHandler by lazy {
MyHandler(Looper.getMainLooper())
}
@@ -64,23 +71,32 @@ class VideoView : ConstraintLayout, SurfaceHolder.Callback, GstCallback {
view = ItemVideoViewBinding.inflate(layoutInflater, this, true)
view.baseView.clipToOutline = true
videoView.holder.addCallback(this)
//videoView.holder.addCallback(this)
gstLibrary = GstLibrary(context)
gstLibrary.setSurfaceHolder(videoView.holder)
gstLibrary.setOnStatusChangeListener(this)
// View 預設狀態
view.textDeviceName.isVisible = false
isPlaying = false
isLoading = false
}
fun setData(device: Device?) {
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.name
view.textDeviceName.text = device.name
this.tag = device.deviceName
view.textDeviceName.text = device.deviceName
view.textDeviceName.isVisible = true
gstLibrary.setRtspUrl(this.data?.rtspUrl)
Log.d("${TAG}_$tag", "Set device to: $device")
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) {
@@ -88,50 +104,49 @@ class VideoView : ConstraintLayout, SurfaceHolder.Callback, GstCallback {
}
fun play() {
if (data == null) return
videoView.postInvalidate()
gstLibrary.play()
retryCount = 0
}
fun stop() {
if (data == null) return
isPlaying = false
if (this::gstLibrary.isInitialized) {
gstLibrary.stop()
}
retryCount = 999
}
override fun surfaceCreated(holder: SurfaceHolder) {
Log.d("${TAG}_$tag", "Surface created: " + holder.surface)
}
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
Log.d("${TAG}_$tag", "Surface changed to format: $format, width: $width, height: $height")
fun destroy() {
if (this::gstLibrary.isInitialized) {
gstLibrary.setSurfaceHolder(holder)
gstLibrary.close()
}
}
override fun surfaceDestroyed(p0: SurfaceHolder) {
Log.d("${TAG}_$tag", "Surface destroyed")
// TODO: 如果呼叫 releaseSurface(),會閃退
/*if (this::gstLibrary.isInitialized) {
gstLibrary.releaseSurface()
}*/
fun resetRetryCount() {
retryCount = 0
}
fun stopRetryCount() {
retryCount = RETRY_OFF
mHandler.removeCallbacks(retryRunnable)
}
override fun onStatus(gstStatus: GstStatus?) { // onStatus 不是在主執行緒,因此透過 Handler 發訊息到主執行緒去執行
when (gstStatus) {
GstStatus.READY -> isReady = true
GstStatus.PLAYING -> mHandler.sendMessage(Message().apply { what = MSG_PLAY })
GstStatus.PAUSE -> mHandler.sendMessage(Message().apply { what = MSG_PAUSE })
GstStatus.ERROR_WHEN_OPENING -> mHandler.sendMessage(Message().apply { what = MSG_PAUSE })
//GstStatus.ERROR_WHEN_OPENING -> mHandler.sendMessage(Message().apply { what = MSG_PAUSE })
GstStatus.BUFFERING -> mHandler.sendMessage(Message().apply { what = MSG_BUFFERING })
GstStatus.ERROR_WHEN_OPENING -> mHandler.sendMessage(Message().apply { what = MSG_ERROR })
else -> {}
}
Log.e("${TAG}_$tag", "onStatus: $gstStatus")
//Log.e("${TAG}_$tag", "onStatus: $gstStatus")
}
override fun onMessage(message: String?) {
Log.e("${TAG}_$tag", "onMessage: $message")
//Log.e("${TAG}_$tag", "onMessage: $message")
}
inner class MyHandler(looper: Looper): Handler(looper) {
@@ -144,6 +159,20 @@ class VideoView : ConstraintLayout, SurfaceHolder.Callback, GstCallback {
MSG_PLAY -> {
isLoading = false
isPlaying = true
resetRetryCount()
}
MSG_BUFFERING -> {
isLoading = true
isPlaying = true
}
MSG_ERROR -> {
if (retryCount != RETRY_OFF && retryCount in 0 until 5) {
mHandler.post(retryRunnable)
retryCount++
} else {
stopRetryCount()
Log.e("${TAG}_$tag", "Retry count = 5, stopped retry...")
}
}
}
}
@@ -157,6 +186,9 @@ class VideoView : ConstraintLayout, SurfaceHolder.Callback, GstCallback {
private const val MSG_PAUSE = 1
private const val MSG_PLAY = 2
private const val MSG_BUFFERING = 3
private const val MSG_ERROR = 4
private const val RETRY_OFF = 999
}
}
@@ -1,4 +1,4 @@
package com.ray650128.gstreamer_demo_app
package com.ray650128.gstreamer_demo_app.ui.mainScreen
import android.annotation.SuppressLint
import androidx.fragment.app.FragmentManager
@@ -49,13 +49,21 @@ class VideoViewAdapter(
fragment.stopAll()
}
/*fun play(index: Int) {
fun play(index: Int) {
play(fragments[index])
}
private fun play(fragment: SplitViewFragment) {
fragment.playAll()
}*/
}
fun destroy(index: Int) {
destroy(fragments[index])
}
private fun destroy(fragment: SplitViewFragment) {
fragment.destroyAll()
}
override fun getItemId(position: Int): Long {
return fragments[position].hashCode().toLong()
@@ -0,0 +1,28 @@
package com.ray650128.gstreamer_demo_app.ui.monitoringScreen
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.WindowManager
import com.ray650128.gstreamer_demo_app.databinding.ActivityMainBinding
import com.ray650128.gstreamer_demo_app.databinding.ActivityMonitoringBinding
class MonitoringActivity : AppCompatActivity() {
private lateinit var binding: ActivityMonitoringBinding
override fun onCreate(savedInstanceState: Bundle?) {
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
super.onCreate(savedInstanceState)
binding = ActivityMonitoringBinding.inflate(layoutInflater)
setContentView(binding.root)
}
companion object {
const val BUNDLE_DEVICE_ID = "BUNDLE_DEVICE_ID"
const val BUNDLE_CHANNEL_ID = "BUNDLE_CHANNEL_ID"
const val BUNDLE_DEVICE = "BUNDLE_DEVICE"
}
}
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.monitoringScreen.MonitoringActivity">
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -9,4 +9,4 @@
app:columnCount="3"
app:rowCount="3"
app:useDefaultMargins="false"
tools:context=".SplitViewFragment" />
tools:context=".ui.mainScreen.SplitViewFragment" />
+3 -3
View File
@@ -7,9 +7,9 @@ buildscript {
}
plugins {
id 'com.android.application' version '7.3.1' apply false
id 'com.android.library' version '7.3.1' apply false
id 'org.jetbrains.kotlin.android' version '1.7.10' apply false
id 'com.android.application' version '7.4.2' apply false
id 'com.android.library' version '7.4.2' 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
}
+1 -1
View File
@@ -1,6 +1,6 @@
#Thu Mar 24 14:41:01 CST 2022
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
-2
View File
@@ -8,8 +8,6 @@ android {
defaultConfig {
minSdkVersion 15
targetSdkVersion 33
versionCode 1
versionName "1.0"
externalNativeBuild {
ndkBuild {
+43 -28
View File
@@ -35,6 +35,7 @@ typedef struct _CustomData {
GstState state; /* Current pipeline state */
GstState target_state; /* Desired pipeline state, to be set once buffering is complete */
gboolean is_live; /* Live streams do not use buffering */
gchar * tag;
} CustomData;
/* playbin2 flags */
@@ -94,7 +95,7 @@ static JNIEnv *get_jni_env(void) {
/* Change the content of the UI's TextView */
static void set_ui_message(const gchar *message, CustomData *data) {
JNIEnv *env = get_jni_env();
GST_DEBUG ("Setting message to: %s", message);
//GST_DEBUG ("Setting message to: %s", message);
jstring jmessage = (*env)->NewStringUTF(env, message);
(*env)->CallVoidMethod(env, data->app, set_message_method_id, jmessage);
if ((*env)->ExceptionCheck(env)) {
@@ -125,8 +126,9 @@ static void error_cb(GstBus *bus, GstMessage *msg, CustomData *data) {
/* Called when the End Of the Stream is reached. Just move to the beginning of the media and pause. */
static void eos_cb(GstBus *bus, GstMessage *msg, CustomData *data) {
data->target_state = GST_STATE_PAUSED;
data->is_live |= (gst_element_set_state(data->pipeline,
GST_STATE_PAUSED) == GST_STATE_CHANGE_NO_PREROLL);
GstStateChangeReturn status = gst_element_set_state(data->pipeline,GST_STATE_PAUSED);
data->is_live |= (status == GST_STATE_CHANGE_NO_PREROLL);
GST_DEBUG ("eos_cb, %s", data->is_live ? "true" : "false");
}
/* Called when buffering messages are received. We inform the UI about the current buffering level and
@@ -196,8 +198,7 @@ static void state_changed_cb(GstBus *bus, GstMessage *msg, CustomData *data) {
/* Only pay attention to messages coming from the pipeline, not its children */
if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data->pipeline)) {
data->state = new_state;
gchar *message = g_strdup_printf("State changed to %s",
gst_element_state_get_name(new_state));
gchar *message = g_strdup_printf("State changed to %s", gst_element_state_get_name(new_state));
set_ui_message(message, data);
g_free(message);
@@ -217,9 +218,7 @@ static void state_changed_cb(GstBus *bus, GstMessage *msg, CustomData *data) {
static void check_initialization_complete(CustomData *data) {
JNIEnv *env = get_jni_env();
if (!data->initialized && data->native_window && data->main_loop) {
GST_DEBUG
("Initialization complete, notifying application. native_window:%p main_loop:%p",
data->native_window, data->main_loop);
GST_DEBUG("Initialization complete, notifying application. native_window:%p main_loop:%p", data->native_window, data->main_loop);
/* The main loop is running and we received a native window, inform the sink about it */
gst_video_overlay_set_window_handle(GST_VIDEO_OVERLAY (data->pipeline),
@@ -311,8 +310,7 @@ static void *app_function(void *userdata) {
static void gst_native_init(JNIEnv *env, jobject thiz) {
CustomData *data = g_new0 (CustomData, 1);
SET_CUSTOM_DATA (env, thiz, custom_data_field_id, data);
GST_DEBUG_CATEGORY_INIT (debug_category, "player", 0,
"Android tutorial 5");
GST_DEBUG_CATEGORY_INIT (debug_category, "player", 0, "HI SHARP DX RTSP Player");
gst_debug_set_threshold_for_name("player", GST_LEVEL_DEBUG);
GST_DEBUG ("Created CustomData at %p", data);
data->app = (*env)->NewGlobalRef(env, thiz);
@@ -325,16 +323,16 @@ static void gst_native_finalize(JNIEnv *env, jobject thiz) {
CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
if (!data)
return;
GST_DEBUG ("Quitting main loop...");
GST_DEBUG ("[%s]Quitting main loop...", data->tag);
g_main_loop_quit(data->main_loop);
GST_DEBUG ("Waiting for thread to finish...");
GST_DEBUG ("[%s]Waiting for thread to finish...", data->tag);
pthread_join(gst_app_thread, NULL);
GST_DEBUG ("Deleting GlobalRef for app object at %p", data->app);
GST_DEBUG ("[%s]Deleting GlobalRef for app object at %p", data->tag, data->app);
(*env)->DeleteGlobalRef(env, data->app);
GST_DEBUG ("Freeing CustomData at %p", data);
GST_DEBUG ("[%s]Freeing CustomData at %p", data->tag, data);
g_free(data);
SET_CUSTOM_DATA (env, thiz, custom_data_field_id, NULL);
GST_DEBUG ("Done finalizing");
GST_DEBUG ("[%s]Done finalizing", data->tag);
}
/* Set playbin2's URI */
@@ -349,7 +347,21 @@ void gst_native_set_uri(JNIEnv *env, jobject thiz, jstring uri) {
g_object_set(data->pipeline, "uri", char_uri, NULL);
(*env)->ReleaseStringUTFChars(env, uri, char_uri);
g_object_set(data->pipeline, "latency", 250, NULL);
data->is_live = (gst_element_set_state(data->pipeline, data->target_state) == GST_STATE_CHANGE_NO_PREROLL);
GstStateChangeReturn status = gst_element_set_state(data->pipeline, data->target_state);
data->is_live = (status == GST_STATE_CHANGE_NO_PREROLL);
GST_DEBUG ("[%s]gst_native_set_uri, %s", data->tag, data->is_live ? "true" : "false");
}
/* Set name */
void gst_native_set_tag(JNIEnv *env, jobject thiz, jstring tag) {
CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
if (!data || !data->pipeline)
return;
const gchar *char_tag = (*env)->GetStringUTFChars(env, tag, NULL);
GST_DEBUG ("Setting TAG to %s", char_tag);
data->tag = g_strdup (char_tag);
(*env)->ReleaseStringUTFChars(env, tag, char_tag);
}
/* Set pipeline to PLAYING state */
@@ -357,9 +369,12 @@ static void gst_native_play(JNIEnv *env, jobject thiz) {
CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
if (!data)
return;
GST_DEBUG ("Setting state to PLAYING");
//GST_DEBUG ("Setting state to PLAYING");
GstStateChangeReturn status = gst_element_set_state(data->pipeline, GST_STATE_PLAYING);
data->is_live = (status == GST_STATE_CHANGE_ASYNC);
data->target_state = GST_STATE_PLAYING;
data->is_live = (gst_element_set_state(data->pipeline, GST_STATE_PLAYING) == GST_STATE_CHANGE_NO_PREROLL);
GST_DEBUG ("[%s]Setting state to PLAYING, %s", data->tag, data->is_live ? "true" : "false");
}
/* Set pipeline to PAUSED state */
@@ -367,9 +382,11 @@ static void gst_native_pause(JNIEnv *env, jobject thiz) {
CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
if (!data)
return;
GST_DEBUG ("Setting state to PAUSED");
//GST_DEBUG ("Setting state to PAUSED");
GstStateChangeReturn status = gst_element_set_state(data->pipeline, GST_STATE_PAUSED);
data->is_live = (status == GST_STATE_CHANGE_ASYNC);
data->target_state = GST_STATE_PAUSED;
data->is_live = (gst_element_set_state(data->pipeline, GST_STATE_PAUSED) == GST_STATE_CHANGE_NO_PREROLL);
GST_DEBUG ("[%s]Setting state to PAUSED, %s", data->tag, data->is_live ? "true" : "false");
}
/* Static class initializer: retrieve method and field IDs */
@@ -396,20 +413,18 @@ static void gst_native_surface_init(JNIEnv *env, jobject thiz, jobject surface)
if (!data)
return;
ANativeWindow *new_native_window = ANativeWindow_fromSurface(env, surface);
GST_DEBUG ("Received surface %p (native window %p)", surface,
new_native_window);
GST_DEBUG ("[%s]Received surface %p (native window %p)", data->tag, surface, new_native_window);
if (data->native_window) {
ANativeWindow_release(data->native_window);
if (data->native_window == new_native_window) {
GST_DEBUG ("New native window is the same as the previous one %p",
data->native_window);
GST_DEBUG ("[%s]New native window is the same as the previous one %p", data->tag, data->native_window);
if (data->pipeline) {
gst_video_overlay_expose(GST_VIDEO_OVERLAY (data->pipeline));
}
return;
} else {
GST_DEBUG ("Released previous native window %p", data->native_window);
GST_DEBUG ("[%s]Released previous native window %p", data->tag, data->native_window);
data->initialized = FALSE;
}
}
@@ -422,7 +437,7 @@ static void gst_native_surface_finalize(JNIEnv *env, jobject thiz) {
CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
if (!data)
return;
GST_DEBUG ("Releasing Native Window %p", data->native_window);
GST_DEBUG ("[%s]Releasing Native Window %p", data->tag, data->native_window);
if (data->pipeline) {
gst_video_overlay_set_window_handle(GST_VIDEO_OVERLAY (data->pipeline),
@@ -440,6 +455,7 @@ static JNINativeMethod native_methods[] = {
{"nativeInit", "()V", (void *) gst_native_init},
{"nativeFinalize", "()V", (void *) gst_native_finalize},
{"nativeSetUri", "(Ljava/lang/String;)V", (void *) gst_native_set_uri},
{"nativeSetTag", "(Ljava/lang/String;)V", (void *) gst_native_set_tag},
{"nativePlay", "()V", (void *) gst_native_play},
{"nativePause", "()V", (void *) gst_native_pause},
{"nativeSurfaceInit", "(Ljava/lang/Object;)V",
@@ -455,8 +471,7 @@ jint JNI_OnLoad(JavaVM *vm, void *reserved) {
java_vm = vm;
if ((*vm)->GetEnv(vm, (void **) &env, JNI_VERSION_1_4) != JNI_OK) {
__android_log_print(ANDROID_LOG_ERROR, "player",
"Could not retrieve JNIEnv");
__android_log_print(ANDROID_LOG_ERROR, "player", "Could not retrieve JNIEnv");
return 0;
}
jclass klass = (*env)->FindClass(env, "com/hisharp/gstreamer_player/GstLibrary");
@@ -13,7 +13,9 @@ import org.freedesktop.gstreamer.GStreamer;
import java.io.Closeable;
import java.io.IOException;
public class GstLibrary implements Closeable {
public class GstLibrary implements Closeable, SurfaceHolder.Callback {
private static final String TAG = GstLibrary.class.getSimpleName();
final Context mAppContext;
@@ -21,6 +23,10 @@ public class GstLibrary implements Closeable {
private String rtspUrl;
private String tag = "";
private Boolean isInit = false;
public GstLibrary(Context context) {
this.mAppContext = context.getApplicationContext();
@@ -38,6 +44,7 @@ public class GstLibrary implements Closeable {
private native void nativeInit(); // Initialize native code, build pipeline, etc
private native void nativeFinalize(); // Destroy pipeline and shutdown native code
private native void nativeSetUri(String uri); // Set the URI of the media to play
private native void nativeSetTag(String tag); // Set the URI of the media to play
private native void nativePlay(); // Set pipeline to PLAYING
private native void nativePause(); // Set pipeline to PAUSED
private static native boolean nativeClassInit(); // Initialize native class: cache Method IDs for callbacks
@@ -48,10 +55,12 @@ public class GstLibrary implements Closeable {
//private final String defaultMediaUri = "rtsp://admin:admin@192.168.0.77:554/media/video2";
public void play() {
if (!isInit) return;
nativePlay();
}
public void stop() {
if (!isInit) return;
nativePause();
}
@@ -59,7 +68,13 @@ public class GstLibrary implements Closeable {
this.rtspUrl = rtspUrl;
}
public void setTag(String tag) {
this.tag = tag;
nativeSetTag(tag);
}
public void releaseSurface() {
isInit = false;
nativeSurfaceFinalize();
}
@@ -68,7 +83,7 @@ public class GstLibrary implements Closeable {
}
public void setSurfaceHolder(SurfaceHolder holder) {
nativeSurfaceInit(holder.getSurface());
holder.addCallback(this);
}
// Called from native code. This sets the content of the TextView from the UI thread.
@@ -76,7 +91,7 @@ public class GstLibrary implements Closeable {
if (gstCallback == null) return;
if (message.contains("State changed to PAUSED")) {
gstCallback.onStatus(GstStatus.PAUSE);
} else if (message.contains("State changed to PLAYING")) {
} else if (message.contains("State changed to PLAYING") || message.contains("Buffering complete")) {
gstCallback.onStatus(GstStatus.PLAYING);
} else if (message.contains("Could not open resource for reading and writing")) {
gstCallback.onStatus(GstStatus.ERROR_WHEN_OPENING);
@@ -84,6 +99,8 @@ public class GstLibrary implements Closeable {
gstCallback.onStatus(GstStatus.ERROR);
} else if (message.contains("Unhandled error")) {
gstCallback.onStatus(GstStatus.ERROR);
} else if (message.contains("Buffering")) {
gstCallback.onStatus(GstStatus.BUFFERING);
}
gstCallback.onMessage(message);
}
@@ -91,7 +108,7 @@ public class GstLibrary implements Closeable {
// Called from native code. Native code calls this once it has created its pipeline and
// the main loop is running, so it is ready to accept commands.
private void onGStreamerInitialized () {
Log.i ("GStreamer", "GStreamer initialized:");
Log.i (TAG + "+" + tag, "GStreamer initialized:");
if (gstCallback != null) {
gstCallback.onStatus(GstStatus.READY);
@@ -101,12 +118,14 @@ public class GstLibrary implements Closeable {
if (rtspUrl != null) {
nativeSetUri(rtspUrl);
}
isInit = true;
}
// Called from native code when the size of the media changes or is first detected.
// Inform the video surface about the new size and recalculate the layout.
private void onMediaSizeChanged (int width, int height) {
Log.i ("GStreamer", "Media size changed to " + width + "x" + height);
Log.i (TAG + "+" + tag, String.format("Media size changed to %d x %d", width, height));
/*mainHandler.post(() -> {
surfaceView.media_width = width;
surfaceView.media_height = height;
@@ -122,10 +141,32 @@ public class GstLibrary implements Closeable {
@Override
public void close() throws IOException {
isInit = false;
try {
nativeFinalize();
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
Log.d(TAG + "+" + tag, "Surface created: " + holder.getSurface());
Log.i (TAG + "+" + tag, "GStreamer surfaceCreated");
isInit = true;
nativeSurfaceInit(holder.getSurface());
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
Log.d(TAG + "+" + tag, String.format("Surface changed, format: %d, width: %d, height: %d", format, width, height));
/*setSurfaceHolder(holder);*/
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
Log.d(TAG + "+" + tag, "Surface destroyed");
releaseSurface();
isInit = false;
}
}
@@ -5,5 +5,6 @@ public enum GstStatus {
PAUSE,
PLAYING,
ERROR,
ERROR_WHEN_OPENING
ERROR_WHEN_OPENING,
BUFFERING
}