Compare commits
42 Commits
62503432b0
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
| e3c958c632 | |||
| 48db07290c | |||
| 1b80ae3349 | |||
| ed8f2a7c0f | |||
| d8d351831c | |||
| 77df0eb8a5 | |||
| 0dca3ce9e8 | |||
| 363bfdbfbd | |||
| dc1da2449b | |||
| 53763bfa34 | |||
| 4cf5ae5094 | |||
| ced178513b | |||
| 88954a6599 | |||
| df105dd518 | |||
| 2f4e3659f9 | |||
| 2e8d8a6381 | |||
| 87f731168f | |||
| c7d0785cef | |||
| a37e79d841 | |||
| 4e09a449ca | |||
| 85218c443f | |||
| b4346f142b | |||
| 9fc8673715 | |||
| de152da749 | |||
| db673d3553 | |||
| 792ebc70c2 | |||
| 90344d14b0 | |||
| c3f8366475 | |||
| 15475923cb | |||
| a29f0e7bbf | |||
| ac15111ab3 | |||
| cdd2c3775d | |||
| 0a65a9615b | |||
| 642d0e7525 | |||
| eb4f643e99 | |||
| e48ebcbb02 | |||
| 8288bc57c5 | |||
| ca3744f8be | |||
| 969c91e436 | |||
| 38b39c6d1d | |||
| 2eca9c4860 | |||
| 81c48f7cf2 |
@@ -16,5 +16,5 @@
|
|||||||
</option>
|
</option>
|
||||||
</component>
|
</component>
|
||||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_16" project-jdk-name="16" project-jdk-type="JavaSDK" />
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_16_PREVIEW" project-jdk-name="11" project-jdk-type="JavaSDK" />
|
||||||
</project>
|
</project>
|
||||||
@@ -1,15 +1,18 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'com.android.application'
|
id 'com.android.application'
|
||||||
|
id 'org.jetbrains.kotlin.android'
|
||||||
|
id 'org.jetbrains.kotlin.plugin.parcelize'
|
||||||
|
id 'org.jetbrains.kotlin.kapt'
|
||||||
}
|
}
|
||||||
apply plugin: 'kotlin-android'
|
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdk 32
|
ndkVersion "21.3.6528147"
|
||||||
|
compileSdk 33
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "com.ray650128.gstreamer_demo_app"
|
applicationId "com.ray650128.gstreamer_demo_app"
|
||||||
minSdk 26
|
minSdk 26
|
||||||
targetSdk 32
|
targetSdk 33
|
||||||
versionCode 1
|
versionCode 1
|
||||||
versionName "1.0"
|
versionName "1.0"
|
||||||
|
|
||||||
@@ -26,23 +29,33 @@ android {
|
|||||||
sourceCompatibility JavaVersion.VERSION_1_8
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
targetCompatibility JavaVersion.VERSION_1_8
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
}
|
}
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = '1.8'
|
||||||
|
}
|
||||||
|
lint {
|
||||||
|
abortOnError false
|
||||||
|
checkReleaseBuilds false
|
||||||
|
}
|
||||||
viewBinding.enabled = true
|
viewBinding.enabled = true
|
||||||
|
namespace 'com.ray650128.gstreamer_demo_app'
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
||||||
implementation 'androidx.appcompat:appcompat:1.0.0'
|
implementation 'androidx.core:core-ktx:1.9.0'
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
implementation 'androidx.appcompat:appcompat:1.5.1'
|
||||||
implementation project(path: ':gstreamer_player')
|
implementation 'com.google.android.material:material:1.7.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.gridlayout:gridlayout:1.0.0'
|
||||||
implementation 'androidx.viewpager2:viewpager2:1.0.0'
|
implementation 'androidx.core:core-ktx:+'
|
||||||
testImplementation 'junit:junit:4.13.2'
|
testImplementation 'junit:junit:4.13.2'
|
||||||
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
|
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0'
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
|
||||||
implementation "androidx.core:core-ktx:+"
|
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
implementation project(path: ':gstreamer_player')
|
||||||
}
|
|
||||||
repositories {
|
// Android Jetpack lib
|
||||||
mavenCentral()
|
implementation("androidx.fragment:fragment-ktx:1.5.5")
|
||||||
|
implementation("androidx.activity:activity-ktx:1.6.1")
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
package="com.ray650128.gstreamer_demo_app">
|
|
||||||
|
|
||||||
<application
|
<application
|
||||||
|
android:name=".MyApplication"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
@@ -10,14 +10,19 @@
|
|||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/Theme.Gstreamer">
|
android:theme="@style/Theme.Gstreamer">
|
||||||
<activity
|
<activity
|
||||||
android:name="com.ray650128.gstreamer_demo_app.MainActivity"
|
android:name="com.ray650128.gstreamer_demo_app.ui.mainScreen.MainActivity"
|
||||||
android:exported="true">
|
android:exported="true"
|
||||||
|
android:screenOrientation="nosensor">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name=".ui.monitoringScreen.MonitoringActivity"
|
||||||
|
android:exported="false"
|
||||||
|
android:screenOrientation="nosensor" />
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package com.ray650128.gstreamer_demo_app
|
||||||
|
|
||||||
|
object Constants {
|
||||||
|
const val CONF_DELAY_BASE_MILLIS = 1000L
|
||||||
|
}
|
||||||
@@ -1,227 +0,0 @@
|
|||||||
package com.ray650128.gstreamer_demo_app
|
|
||||||
|
|
||||||
import android.graphics.Color
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.util.DisplayMetrics
|
|
||||||
import android.util.Log
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
import androidx.gridlayout.widget.GridLayout
|
|
||||||
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.FragmentGridVideoBinding
|
|
||||||
import kotlin.math.sqrt
|
|
||||||
|
|
||||||
|
|
||||||
class GridVideoFragment : Fragment(), GstCallback {
|
|
||||||
|
|
||||||
private var mPageNum: Int = 0
|
|
||||||
private var splitMode = 0
|
|
||||||
private var streamType = STREAM_SUB
|
|
||||||
private var isClickable = true
|
|
||||||
|
|
||||||
private var data: ArrayList<String> = ArrayList()
|
|
||||||
|
|
||||||
private lateinit var binding: FragmentGridVideoBinding
|
|
||||||
|
|
||||||
private var gstPlayers: ArrayList<GstLibrary?> = ArrayList()
|
|
||||||
|
|
||||||
private var videoViews: ArrayList<VideoView> = ArrayList()
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
|
|
||||||
if (arguments != null) {
|
|
||||||
mPageNum = requireArguments().getInt(ARG_PAGE_NUM)
|
|
||||||
splitMode = requireArguments().getInt(ARG_SPLIT_MODE)
|
|
||||||
isClickable = requireArguments().getBoolean(ARG_CLICKABLE)
|
|
||||||
streamType = requireArguments().getInt(ARG_STREAM_TYPE)
|
|
||||||
data = requireArguments().getStringArrayList(ARG_STREAM_URLS) ?: arrayListOf()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
|
||||||
binding = FragmentGridVideoBinding.inflate(inflater, container, false)
|
|
||||||
return binding.root
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
||||||
super.onViewCreated(view, savedInstanceState)
|
|
||||||
|
|
||||||
initView()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPause() {
|
|
||||||
stopAll()
|
|
||||||
super.onPause()
|
|
||||||
Log.d("${TAG}_$mPageNum", "onPause()")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onResume() {
|
|
||||||
super.onResume()
|
|
||||||
playAll()
|
|
||||||
Log.d("${TAG}_$mPageNum", "onResume()")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onStop() {
|
|
||||||
super.onStop()
|
|
||||||
Log.d("${TAG}_$mPageNum", "onStop()")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onStart() {
|
|
||||||
super.onStart()
|
|
||||||
Log.d("${TAG}_$mPageNum", "onStart()")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroyView() {
|
|
||||||
Log.d("${TAG}_$mPageNum", "onDestroyView()")
|
|
||||||
super.onDestroyView()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroy() {
|
|
||||||
for (i in gstPlayers.indices) {
|
|
||||||
gstPlayers[i]?.release()
|
|
||||||
gstPlayers[i] = null
|
|
||||||
}
|
|
||||||
Log.d("${TAG}_$mPageNum", "onDestroy()")
|
|
||||||
super.onDestroy()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun initView() {
|
|
||||||
val maxRow = sqrt(splitMode.toFloat()).toInt()
|
|
||||||
val maxCol = sqrt(splitMode.toFloat()).toInt()
|
|
||||||
|
|
||||||
val displayMetrics = DisplayMetrics()
|
|
||||||
requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics)
|
|
||||||
val screenWidth = displayMetrics.widthPixels
|
|
||||||
|
|
||||||
val cellWidth = when (splitMode) {
|
|
||||||
1 -> (screenWidth / maxRow)
|
|
||||||
else -> (screenWidth / maxRow) - maxRow.dp
|
|
||||||
}
|
|
||||||
|
|
||||||
val cellHeight = (cellWidth * (0.62)).toInt()
|
|
||||||
Log.e(TAG, "cellWidth: $cellWidth, cellHeight: $cellHeight")
|
|
||||||
|
|
||||||
// 生成 VideoView 分割畫面
|
|
||||||
binding.apply {
|
|
||||||
baseView.rowCount = maxRow
|
|
||||||
baseView.columnCount = maxCol
|
|
||||||
|
|
||||||
for (col in 0 until maxCol) {
|
|
||||||
for (row in 0 until maxRow) {
|
|
||||||
val videoView = VideoView(requireContext()).apply {
|
|
||||||
layoutParams = GridLayout.LayoutParams().apply {
|
|
||||||
height = cellHeight
|
|
||||||
width = cellWidth
|
|
||||||
|
|
||||||
topMargin = 0.dp
|
|
||||||
bottomMargin = 0.dp
|
|
||||||
marginEnd = 0.dp
|
|
||||||
marginStart = 0.dp
|
|
||||||
|
|
||||||
// 調整間距
|
|
||||||
when (splitMode) {
|
|
||||||
4 -> {
|
|
||||||
when (col) {
|
|
||||||
0 -> bottomMargin = 2.dp
|
|
||||||
1 -> topMargin = 2.dp
|
|
||||||
}
|
|
||||||
when (row) {
|
|
||||||
0 -> marginEnd = 2.dp
|
|
||||||
1 -> marginStart = 2.dp
|
|
||||||
}
|
|
||||||
}
|
|
||||||
9 -> {
|
|
||||||
if (col == 1) {
|
|
||||||
topMargin = 4.dp
|
|
||||||
bottomMargin = 4.dp
|
|
||||||
}
|
|
||||||
if (row == 1) {
|
|
||||||
marginEnd = 4.dp
|
|
||||||
marginStart = 4.dp
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
baseView.addView(videoView)
|
|
||||||
videoViews.add(videoView)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (index in videoViews.indices) {
|
|
||||||
gstPlayers.add(GstLibrary(requireContext(), data[index]))
|
|
||||||
gstPlayers[index]?.setSurfaceView(videoViews[index].videoView)
|
|
||||||
gstPlayers[index]?.setOnStatusChangeListener(this)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun playAll() {
|
|
||||||
for (index in data.indices) {
|
|
||||||
gstPlayers[index]?.apply {
|
|
||||||
play()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun stopAll() {
|
|
||||||
for (index in data.indices) {
|
|
||||||
try {
|
|
||||||
gstPlayers[index]?.stop()
|
|
||||||
} catch (e: IllegalStateException) {
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onStatus(gstInstance: GstLibrary, gstStatus: GstStatus?) {
|
|
||||||
val index = gstPlayers.indexOf(gstInstance)
|
|
||||||
when (gstStatus) {
|
|
||||||
GstStatus.PAUSE -> videoViews[index].isPlaying = false
|
|
||||||
GstStatus.PLAYING -> videoViews[index].isPlaying = true
|
|
||||||
else -> {}
|
|
||||||
}
|
|
||||||
Log.d("${TAG}_$mPageNum", "GstPlayer #$index status: $gstStatus")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onMessage(gstInstance: GstLibrary, message: String?) {
|
|
||||||
val index = gstPlayers.indexOf(gstInstance)
|
|
||||||
Log.d("${TAG}_$mPageNum", "GstPlayer #$index: $message")
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private val TAG = GridVideoFragment::class.java.simpleName
|
|
||||||
private const val ARG_PAGE_NUM = "page_number"
|
|
||||||
private const val ARG_SPLIT_MODE = "split_mode"
|
|
||||||
private const val ARG_CLICKABLE = "clickable"
|
|
||||||
private const val ARG_STREAM_TYPE = "stream_type"
|
|
||||||
private const val ARG_STREAM_URLS = "stream_urls"
|
|
||||||
|
|
||||||
const val STREAM_MAIN = 1
|
|
||||||
const val STREAM_SUB = 2
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 透過傳入的參數,生成新的 Fragment 實例
|
|
||||||
*
|
|
||||||
* @param pageNumber 該 Fragment 頁碼
|
|
||||||
* @param splitMode 畫面分割模式(9/4/1分割)
|
|
||||||
* @return 透過傳入的參數,生成新的 Fragment 實例
|
|
||||||
*/
|
|
||||||
fun newInstance(pageNumber: Int, splitMode: Int, streamUrls: ArrayList<String>, isClickable: Boolean = true, streamType: Int = STREAM_SUB): GridVideoFragment {
|
|
||||||
val fragment = GridVideoFragment()
|
|
||||||
val args = Bundle()
|
|
||||||
args.putInt(ARG_PAGE_NUM, pageNumber)
|
|
||||||
args.putInt(ARG_SPLIT_MODE, splitMode)
|
|
||||||
args.putBoolean(ARG_CLICKABLE, isClickable)
|
|
||||||
args.putInt(ARG_STREAM_TYPE, streamType)
|
|
||||||
args.putStringArrayList(ARG_STREAM_URLS, streamUrls)
|
|
||||||
fragment.arguments = args
|
|
||||||
return fragment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
package com.ray650128.gstreamer_demo_app
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.graphics.Color
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
import com.hisharp.gstreamer_player.GstCallback
|
|
||||||
import com.ray650128.gstreamer_demo_app.MainActivity
|
|
||||||
import com.hisharp.gstreamer_player.GstLibrary
|
|
||||||
import com.hisharp.gstreamer_player.GStreamerSurfaceView
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.util.Log
|
|
||||||
import android.view.View
|
|
||||||
import android.view.WindowManager
|
|
||||||
import com.hisharp.gstreamer_player.GstStatus
|
|
||||||
import com.ray650128.gstreamer_demo_app.databinding.ActivityMainBinding
|
|
||||||
import java.util.ArrayList
|
|
||||||
import java.util.function.Consumer
|
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity() {
|
|
||||||
|
|
||||||
private lateinit var binding: ActivityMainBinding
|
|
||||||
|
|
||||||
private lateinit var splitVideoViewAdapter: ViewPager2Adapter
|
|
||||||
|
|
||||||
@SuppressLint("AuthLeak")
|
|
||||||
private val defaultMediaUris = arrayListOf(
|
|
||||||
arrayListOf(
|
|
||||||
"rtsp://admin:admin@211.23.78.226:8574/v02",
|
|
||||||
"rtsp://admin:admin@211.23.78.226:8575/v02",
|
|
||||||
"rtsp://admin:admin@192.168.0.77:554/media/video2",
|
|
||||||
"rtsp://admin:123456@192.168.0.80:554/profile2",
|
|
||||||
"rtsp://admin:123456@192.168.0.83:554/profile2",
|
|
||||||
"rtsp://admin:123456@192.168.0.84:554/profile2",
|
|
||||||
"rtsp://admin:admin@192.168.0.86:554/v2",
|
|
||||||
"rtsp://admin:admin@192.168.0.89:554/v02",
|
|
||||||
"rtsp://admin:1q2w3e4r!@60.249.32.50:554/unicast/c8/s1/live",
|
|
||||||
),
|
|
||||||
arrayListOf(
|
|
||||||
"rtsp://admin:1q2w3e4r!@60.249.32.50:554/unicast/c1/s1/live",
|
|
||||||
"rtsp://admin:1q2w3e4r!@60.249.32.50:554/unicast/c2/s1/live",
|
|
||||||
"rtsp://admin:1q2w3e4r!@60.249.32.50:554/unicast/c3/s1/live",
|
|
||||||
"rtsp://admin:1q2w3e4r!@60.249.32.50:554/unicast/c4/s1/live",
|
|
||||||
"rtsp://admin:1q2w3e4r!@60.249.32.50:554/unicast/c5/s1/live",
|
|
||||||
"rtsp://admin:1q2w3e4r!@60.249.32.50:554/unicast/c6/s1/live",
|
|
||||||
"rtsp://admin:1q2w3e4r!@60.249.32.50:554/unicast/c7/s1/live",
|
|
||||||
"rtsp://admin:1q2w3e4r!@60.249.32.50:554/unicast/c8/s1/live",
|
|
||||||
"rtsp://admin:1q2w3e4r!@60.249.32.50:554/unicast/c9/s1/live"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
binding = ActivityMainBinding.inflate(layoutInflater)
|
|
||||||
setContentView(binding.root)
|
|
||||||
|
|
||||||
splitVideoViewAdapter = ViewPager2Adapter(supportFragmentManager, lifecycle)
|
|
||||||
|
|
||||||
binding.viewPager.apply {
|
|
||||||
adapter = splitVideoViewAdapter
|
|
||||||
offscreenPageLimit = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
reloadVideoViews()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun reloadVideoViews() {
|
|
||||||
splitVideoViewAdapter.clear()
|
|
||||||
|
|
||||||
for (index in defaultMediaUris.indices) {
|
|
||||||
val gridFragment = GridVideoFragment.newInstance(index, 9, defaultMediaUris[index])
|
|
||||||
splitVideoViewAdapter.add(index, gridFragment)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private val TAG = MainActivity::class.java.simpleName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
package com.ray650128.gstreamer_demo_app
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.util.AttributeSet
|
|
||||||
import android.util.Log
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
|
||||||
import androidx.core.view.isVisible
|
|
||||||
import com.hisharp.gstreamer_player.GStreamerSurfaceView
|
|
||||||
import com.ray650128.gstreamer_demo_app.databinding.ItemVideoViewBinding
|
|
||||||
|
|
||||||
class VideoView : ConstraintLayout {
|
|
||||||
|
|
||||||
constructor(context: Context) : super(context) {
|
|
||||||
initView(context)
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(context: Context, attributeSet: AttributeSet) : super(context, attributeSet) {
|
|
||||||
initView(context)
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(context: Context, attributeSet: AttributeSet, intRes: Int) : super(context, attributeSet, intRes) {
|
|
||||||
initView(context)
|
|
||||||
}
|
|
||||||
|
|
||||||
private lateinit var view: ItemVideoViewBinding
|
|
||||||
|
|
||||||
//private var data: Device? = null
|
|
||||||
|
|
||||||
var isPlaying: Boolean = false
|
|
||||||
set(value) {
|
|
||||||
view.imgPause.isVisible = !value
|
|
||||||
field = value
|
|
||||||
}
|
|
||||||
|
|
||||||
val videoView: GStreamerSurfaceView by lazy { view.videoView }
|
|
||||||
|
|
||||||
private fun initView(context: Context) {
|
|
||||||
val layoutInflater = LayoutInflater.from(context)
|
|
||||||
view = ItemVideoViewBinding.inflate(layoutInflater, this, true)
|
|
||||||
view.baseView.clipToOutline = true
|
|
||||||
}
|
|
||||||
|
|
||||||
/*fun setData(device: Device?) {
|
|
||||||
if (device == null) {
|
|
||||||
view.textDeviceName.isVisible = false
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.data = device
|
|
||||||
view.textDeviceName.text = if (device.channelId == -1) device.deviceName else device.channelName
|
|
||||||
view.textDeviceName.isVisible = true
|
|
||||||
Log.d(TAG, "Set device to: $device")
|
|
||||||
}*/
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private val TAG = VideoView::class.java.simpleName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.ray650128.gstreamer_demo_app.model
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class Device(
|
||||||
|
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
|
||||||
@@ -0,0 +1,164 @@
|
|||||||
|
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 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
|
||||||
|
|
||||||
|
class MainActivity : AppCompatActivity() {
|
||||||
|
|
||||||
|
private lateinit var binding: ActivityMainBinding
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
private var videos: List<List<Device>>? = null
|
||||||
|
|
||||||
|
private var currentPage = 0
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||||
|
setContentView(binding.root)
|
||||||
|
|
||||||
|
initContentView()
|
||||||
|
|
||||||
|
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 = 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")
|
||||||
|
}
|
||||||
|
//endregion
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initObservers() {
|
||||||
|
viewModel.splitMode.observe(this) {
|
||||||
|
splitMode = it
|
||||||
|
Log.e(TAG, "Split count: $it")
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel.cameraList.observe(this) { list ->
|
||||||
|
this.videos = list
|
||||||
|
reloadVideoViews(this.videos)
|
||||||
|
currentPage = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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()) {
|
||||||
|
binding.viewPager.setBackgroundResource(R.drawable.bg_not_in_playing)
|
||||||
|
} else {
|
||||||
|
binding.viewPager.setBackgroundColor(Color.TRANSPARENT)
|
||||||
|
for (i in list.indices) {
|
||||||
|
val splitFragment = SplitViewFragment.newInstance(
|
||||||
|
pageNumber = i,
|
||||||
|
splitMode = splitMode,
|
||||||
|
pageData = ArrayList(list[i])
|
||||||
|
)
|
||||||
|
splitVideoViewAdapter.add(i, splitFragment)
|
||||||
|
}
|
||||||
|
currentPage = 0
|
||||||
|
//binding.viewPager.currentItem = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val TAG = MainActivity::class.java.simpleName
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,255 @@
|
|||||||
|
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
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
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
|
||||||
|
private var isClickable = true
|
||||||
|
|
||||||
|
private lateinit var binding: FragmentSplitViewBinding
|
||||||
|
|
||||||
|
private var data: ArrayList<Device> = ArrayList()
|
||||||
|
|
||||||
|
private var videoViews: ArrayList<VideoView> = ArrayList()
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
if (arguments != null) {
|
||||||
|
mPageNum = requireArguments().getInt(ARG_PAGE_NUM)
|
||||||
|
splitMode = requireArguments().getInt(ARG_SPLIT_MODE)
|
||||||
|
isClickable = requireArguments().getBoolean(ARG_CLICKABLE)
|
||||||
|
streamType = requireArguments().getInt(ARG_STREAM_TYPE)
|
||||||
|
data = requireArguments().getParcelableArrayList(ARG_PAGE_DATA) ?: arrayListOf(Device())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||||
|
binding = FragmentSplitViewBinding.inflate(inflater, container, false)
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
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() {
|
||||||
|
stopAll()
|
||||||
|
super.onPause()
|
||||||
|
Log.d("${TAG}_$mPageNum", "onPause()")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
playAll()
|
||||||
|
Log.d("${TAG}_$mPageNum", "onResume()")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
super.onDestroy()
|
||||||
|
//destroyAll()
|
||||||
|
stopAll()
|
||||||
|
Log.d("${TAG}_$mPageNum", "onDestroy()")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initView() {
|
||||||
|
// 生成 VideoView 分割畫面
|
||||||
|
binding.apply {
|
||||||
|
val maxRow = sqrt(splitMode.toFloat()).toInt()
|
||||||
|
val maxCol = sqrt(splitMode.toFloat()).toInt()
|
||||||
|
//Log.d(TAG, "maxRow: $maxRow, maxCol: $maxCol")
|
||||||
|
|
||||||
|
baseView.rowCount = maxRow
|
||||||
|
baseView.columnCount = maxCol
|
||||||
|
Log.e("${TAG}_$mPageNum", "baseView.rowCount: ${baseView.rowCount}, baseView.columnCount: ${baseView.columnCount}")
|
||||||
|
|
||||||
|
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_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_ONE -> {
|
||||||
|
width = (baseView.width / maxRow)
|
||||||
|
height = (baseView.height / maxCol)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
baseView.addView(videoView, layoutParam)
|
||||||
|
videoViews.add(videoView)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setAllUrl()
|
||||||
|
|
||||||
|
if (isClickable) {
|
||||||
|
for (position in videoViews.indices) {
|
||||||
|
videoViews[position].setOnClickListener {
|
||||||
|
if (position >= data.size) return@setOnClickListener
|
||||||
|
if (!videoViews[position].isPlaying) {
|
||||||
|
if (!videoViews[position].isLoading) {
|
||||||
|
videoViews[position].resetRetryCount()
|
||||||
|
videoViews[position].play()
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setAllUrl() {
|
||||||
|
for (index in data.indices) {
|
||||||
|
videoViews[index].setData(data[index])
|
||||||
|
videoViews[index].setTextVisible((splitMode != MainViewModel.PAGE_MODE_NINE))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}.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)
|
||||||
|
}
|
||||||
|
}.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
|
||||||
|
private const val ARG_PAGE_NUM = "page_number"
|
||||||
|
private const val ARG_SPLIT_MODE = "split_mode"
|
||||||
|
private const val ARG_CLICKABLE = "clickable"
|
||||||
|
private const val ARG_PAGE_DATA = "page_data"
|
||||||
|
private const val ARG_STREAM_TYPE = "stream_type"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 透過傳入的參數,生成新的 Fragment 實例
|
||||||
|
*
|
||||||
|
* @param pageNumber 該 Fragment 頁碼
|
||||||
|
* @param splitMode 畫面分割模式(9/4/1分割)
|
||||||
|
* @return 透過傳入的參數,生成新的 Fragment 實例
|
||||||
|
*/
|
||||||
|
fun newInstance(
|
||||||
|
pageNumber: Int,
|
||||||
|
splitMode: Int,
|
||||||
|
isClickable: Boolean = true,
|
||||||
|
pageData: ArrayList<Device>,
|
||||||
|
streamType: Int = VideoView.SUB_STREAM
|
||||||
|
): SplitViewFragment {
|
||||||
|
val fragment = SplitViewFragment()
|
||||||
|
val args = Bundle()
|
||||||
|
args.putInt(ARG_PAGE_NUM, pageNumber)
|
||||||
|
args.putInt(ARG_SPLIT_MODE, splitMode)
|
||||||
|
args.putBoolean(ARG_CLICKABLE, isClickable)
|
||||||
|
args.putParcelableArrayList(ARG_PAGE_DATA, pageData)
|
||||||
|
args.putInt(ARG_STREAM_TYPE, streamType)
|
||||||
|
fragment.arguments = args
|
||||||
|
return fragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,194 @@
|
|||||||
|
package com.ray650128.gstreamer_demo_app.ui.mainScreen
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
|
import android.os.Message
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.SurfaceHolder
|
||||||
|
import android.view.SurfaceView
|
||||||
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
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, GstCallback {
|
||||||
|
|
||||||
|
constructor(context: Context) : super(context) {
|
||||||
|
initView(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(context: Context, attributeSet: AttributeSet) : super(context, attributeSet) {
|
||||||
|
initView(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(context: Context, attributeSet: AttributeSet, intRes: Int) : super(context, attributeSet, intRes) {
|
||||||
|
initView(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
private lateinit var view: ItemVideoViewBinding
|
||||||
|
|
||||||
|
private var data: Device? = null
|
||||||
|
|
||||||
|
var isReady: Boolean = false
|
||||||
|
|
||||||
|
var isLoading: Boolean = false
|
||||||
|
set(value) {
|
||||||
|
view.pbLoading.isVisible = value
|
||||||
|
field = value
|
||||||
|
}
|
||||||
|
|
||||||
|
var isPlaying: Boolean = false
|
||||||
|
set(value) {
|
||||||
|
view.imgPause.isVisible = !value
|
||||||
|
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 val mHandler: MyHandler by lazy {
|
||||||
|
MyHandler(Looper.getMainLooper())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initView(context: Context) {
|
||||||
|
val layoutInflater = LayoutInflater.from(context)
|
||||||
|
view = ItemVideoViewBinding.inflate(layoutInflater, this, true)
|
||||||
|
view.baseView.clipToOutline = true
|
||||||
|
|
||||||
|
//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?, 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
|
||||||
|
}
|
||||||
|
|
||||||
|
fun play() {
|
||||||
|
if (data == null) return
|
||||||
|
videoView.postInvalidate()
|
||||||
|
gstLibrary.play()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stop() {
|
||||||
|
if (data == null) return
|
||||||
|
isPlaying = false
|
||||||
|
if (this::gstLibrary.isInitialized) {
|
||||||
|
gstLibrary.stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun destroy() {
|
||||||
|
if (this::gstLibrary.isInitialized) {
|
||||||
|
gstLibrary.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.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")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMessage(message: String?) {
|
||||||
|
//Log.e("${TAG}_$tag", "onMessage: $message")
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class MyHandler(looper: Looper): Handler(looper) {
|
||||||
|
override fun handleMessage(msg: Message) {
|
||||||
|
when(msg.what) {
|
||||||
|
MSG_PAUSE -> {
|
||||||
|
isPlaying = false
|
||||||
|
isLoading = false
|
||||||
|
}
|
||||||
|
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...")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val TAG = VideoView::class.java.simpleName
|
||||||
|
|
||||||
|
const val MAIN_STREAM = 1
|
||||||
|
const val SUB_STREAM = 2
|
||||||
|
|
||||||
|
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,31 +1,31 @@
|
|||||||
package com.ray650128.gstreamer_demo_app
|
package com.ray650128.gstreamer_demo_app.ui.mainScreen
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
import androidx.fragment.app.FragmentManager
|
import androidx.fragment.app.FragmentManager
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||||
|
|
||||||
class ViewPager2Adapter(
|
class VideoViewAdapter(
|
||||||
fragmentManager: FragmentManager,
|
fragmentManager: FragmentManager,
|
||||||
lifecycle: Lifecycle
|
lifecycle: Lifecycle
|
||||||
) : FragmentStateAdapter(fragmentManager, lifecycle) {
|
) : FragmentStateAdapter(fragmentManager, lifecycle) {
|
||||||
|
|
||||||
private var fragments: MutableList<Fragment> = arrayListOf()
|
private var fragments: MutableList<SplitViewFragment> = arrayListOf()
|
||||||
|
|
||||||
override fun getItemCount(): Int {
|
override fun getItemCount(): Int {
|
||||||
return fragments.size
|
return fragments.size
|
||||||
}
|
}
|
||||||
override fun createFragment(position: Int): Fragment {
|
|
||||||
|
override fun createFragment(position: Int): SplitViewFragment {
|
||||||
return fragments[position]
|
return fragments[position]
|
||||||
}
|
}
|
||||||
|
|
||||||
fun add(index: Int, fragment: Fragment) {
|
fun add(index: Int, fragment: SplitViewFragment) {
|
||||||
fragments.add(index, fragment)
|
fragments.add(index, fragment)
|
||||||
notifyItemChanged(index)
|
notifyItemChanged(index)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun refreshFragment(index: Int, fragment: Fragment) {
|
fun refreshFragment(index: Int, fragment: SplitViewFragment) {
|
||||||
fragments[index] = fragment
|
fragments[index] = fragment
|
||||||
notifyItemChanged(index)
|
notifyItemChanged(index)
|
||||||
}
|
}
|
||||||
@@ -41,6 +41,30 @@ class ViewPager2Adapter(
|
|||||||
notifyDataSetChanged()
|
notifyDataSetChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun stop(index: Int) {
|
||||||
|
stop(fragments[index])
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun stop(fragment: SplitViewFragment) {
|
||||||
|
fragment.stopAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
override fun getItemId(position: Int): Long {
|
||||||
return fragments[position].hashCode().toLong()
|
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"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,5 +19,40 @@
|
|||||||
tools:layout_conversion_absoluteHeight="0dp"
|
tools:layout_conversion_absoluteHeight="0dp"
|
||||||
tools:layout_conversion_absoluteWidth="411dp" />
|
tools:layout_conversion_absoluteWidth="411dp" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/button"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:text="1"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/button2"
|
||||||
|
app:layout_constraintHorizontal_bias="0.5"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/viewPager" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/button2"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:text="4"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/button3"
|
||||||
|
app:layout_constraintHorizontal_bias="0.5"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/button"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/viewPager" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/button3"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:text="9"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="0.5"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/button2"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/viewPager" />
|
||||||
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.gridlayout.widget.GridLayout 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:id="@+id/baseView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
app:columnCount="3"
|
||||||
|
app:rowCount="3"
|
||||||
|
app:useDefaultMargins="false"
|
||||||
|
tools:context=".ui.mainScreen.SplitViewFragment" />
|
||||||
@@ -1,14 +1,13 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:id="@+id/baseView"
|
android:id="@+id/baseView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:background="@drawable/bg_video_view"
|
android:background="@drawable/bg_video_view"
|
||||||
android:outlineProvider="background">
|
android:outlineProvider="background">
|
||||||
|
|
||||||
<com.hisharp.gstreamer_player.GStreamerSurfaceView
|
<SurfaceView
|
||||||
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"
|
||||||
@@ -22,6 +21,7 @@
|
|||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:scaleType="fitXY"
|
android:scaleType="fitXY"
|
||||||
|
android:visibility="gone"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
@@ -34,16 +34,16 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="8dp"
|
android:layout_marginStart="8dp"
|
||||||
android:layout_marginBottom="4dp"
|
android:layout_marginBottom="4dp"
|
||||||
android:drawableLeft="@drawable/ic_ip_cam_name"
|
|
||||||
android:drawablePadding="6dp"
|
android:drawablePadding="6dp"
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
android:maxLines="1"
|
android:maxLines="1"
|
||||||
android:text="TextView"
|
android:text=""
|
||||||
android:textColor="@android:color/white"
|
android:textColor="@android:color/white"
|
||||||
android:textSize="16sp"
|
android:textSize="16sp"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
app:layout_constraintBottom_toBottomOf="@+id/videoView"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent" />
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:drawableStartCompat="@drawable/ic_ip_cam_name" />
|
||||||
|
|
||||||
<ProgressBar
|
<ProgressBar
|
||||||
android:id="@+id/pbLoading"
|
android:id="@+id/pbLoading"
|
||||||
@@ -51,7 +51,7 @@
|
|||||||
android:layout_width="64dp"
|
android:layout_width="64dp"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
app:layout_constraintBottom_toBottomOf="@+id/videoView"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintDimensionRatio="h,1:1"
|
app:layout_constraintDimensionRatio="h,1:1"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
|||||||
@@ -1,25 +1,16 @@
|
|||||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
ext.kotlin_version = '1.7.10'
|
|
||||||
repositories {
|
repositories {
|
||||||
jcenter()
|
mavenCentral()
|
||||||
google()
|
|
||||||
}
|
|
||||||
dependencies {
|
|
||||||
classpath 'com.android.tools.build:gradle:7.1.0'
|
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
|
||||||
|
|
||||||
// NOTE: Do not place your application dependencies here; they belong
|
|
||||||
// in the individual module build.gradle files
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
allprojects {
|
plugins {
|
||||||
repositories {
|
id 'com.android.application' version '7.4.2' apply false
|
||||||
jcenter()
|
id 'com.android.library' version '7.4.2' apply false
|
||||||
google()
|
id 'org.jetbrains.kotlin.android' version '1.8.10' apply false
|
||||||
}
|
id 'org.jetbrains.kotlin.plugin.parcelize' version '1.7.0' apply false
|
||||||
}
|
}
|
||||||
|
|
||||||
task clean(type: Delete) {
|
task clean(type: Delete) {
|
||||||
|
|||||||
@@ -1,2 +1,21 @@
|
|||||||
android.enableJetifier=true
|
# Project-wide Gradle settings.
|
||||||
|
# IDE (e.g. Android Studio) users:
|
||||||
|
# Gradle settings configured through the IDE *will override*
|
||||||
|
# any settings specified in this file.
|
||||||
|
# For more details on how to configure your build environment visit
|
||||||
|
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||||
|
# Specifies the JVM arguments used for the daemon process.
|
||||||
|
# The setting is particularly useful for tweaking memory settings.
|
||||||
|
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
||||||
|
# When configured, Gradle will run in incubating parallel mode.
|
||||||
|
# This option should only be used with decoupled projects. More details, visit
|
||||||
|
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||||
|
# org.gradle.parallel=true
|
||||||
|
# AndroidX package structure to make it clearer which packages are bundled with the
|
||||||
|
# Android operating system, and which are packaged with your app"s APK
|
||||||
|
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
|
# Automatically convert third-party libraries to use AndroidX
|
||||||
|
android.enableJetifier=true
|
||||||
|
# Kotlin code style for this project: "official" or "obsolete":
|
||||||
|
kotlin.code.style=official
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#Sat Apr 21 19:58:19 WEST 2018
|
#Thu Mar 24 14:41:01 CST 2022
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
apply plugin: 'com.android.library'
|
apply plugin: 'com.android.library'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 32
|
ndkVersion "21.3.6528147"
|
||||||
|
|
||||||
|
compileSdkVersion 33
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 15
|
minSdkVersion 15
|
||||||
targetSdkVersion 32
|
targetSdkVersion 33
|
||||||
versionCode 1
|
|
||||||
versionName "1.0"
|
|
||||||
|
|
||||||
externalNativeBuild {
|
externalNativeBuild {
|
||||||
ndkBuild {
|
ndkBuild {
|
||||||
@@ -21,7 +21,10 @@ android {
|
|||||||
if (gstRoot == null)
|
if (gstRoot == null)
|
||||||
throw new GradleException('GSTREAMER_ROOT_ANDROID must be set, or "gstAndroidRoot" must be defined in your gradle.properties in the top level directory of the unpacked universal GStreamer Android binaries')
|
throw new GradleException('GSTREAMER_ROOT_ANDROID must be set, or "gstAndroidRoot" must be defined in your gradle.properties in the top level directory of the unpacked universal GStreamer Android binaries')
|
||||||
|
|
||||||
arguments "NDK_APPLICATION_MK=jni/Application.mk", "GSTREAMER_JAVA_SRC_DIR=src", "GSTREAMER_ROOT_ANDROID=$gstRoot", "GSTREAMER_ASSETS_DIR=src/assets"
|
arguments "NDK_APPLICATION_MK=jni/Application.mk",
|
||||||
|
"GSTREAMER_JAVA_SRC_DIR=src",
|
||||||
|
"GSTREAMER_ROOT_ANDROID=$gstRoot",
|
||||||
|
"GSTREAMER_ASSETS_DIR=src/assets"
|
||||||
|
|
||||||
targets "gst_player"
|
targets "gst_player"
|
||||||
|
|
||||||
@@ -65,6 +68,6 @@ afterEvaluate {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
testImplementation 'junit:junit:4.12'
|
testImplementation 'junit:junit:4.13.2'
|
||||||
implementation 'androidx.appcompat:appcompat:1.0.0'
|
//implementation 'androidx.appcompat:appcompat:1.6.0'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ typedef struct _CustomData {
|
|||||||
GstState state; /* Current pipeline state */
|
GstState state; /* Current pipeline state */
|
||||||
GstState target_state; /* Desired pipeline state, to be set once buffering is complete */
|
GstState target_state; /* Desired pipeline state, to be set once buffering is complete */
|
||||||
gboolean is_live; /* Live streams do not use buffering */
|
gboolean is_live; /* Live streams do not use buffering */
|
||||||
|
gchar * tag;
|
||||||
} CustomData;
|
} CustomData;
|
||||||
|
|
||||||
/* playbin2 flags */
|
/* playbin2 flags */
|
||||||
@@ -94,7 +95,7 @@ static JNIEnv *get_jni_env(void) {
|
|||||||
/* Change the content of the UI's TextView */
|
/* Change the content of the UI's TextView */
|
||||||
static void set_ui_message(const gchar *message, CustomData *data) {
|
static void set_ui_message(const gchar *message, CustomData *data) {
|
||||||
JNIEnv *env = get_jni_env();
|
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);
|
jstring jmessage = (*env)->NewStringUTF(env, message);
|
||||||
(*env)->CallVoidMethod(env, data->app, set_message_method_id, jmessage);
|
(*env)->CallVoidMethod(env, data->app, set_message_method_id, jmessage);
|
||||||
if ((*env)->ExceptionCheck(env)) {
|
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. */
|
/* 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) {
|
static void eos_cb(GstBus *bus, GstMessage *msg, CustomData *data) {
|
||||||
data->target_state = GST_STATE_PAUSED;
|
data->target_state = GST_STATE_PAUSED;
|
||||||
data->is_live |= (gst_element_set_state(data->pipeline,
|
GstStateChangeReturn status = gst_element_set_state(data->pipeline,GST_STATE_PAUSED);
|
||||||
GST_STATE_PAUSED) == GST_STATE_CHANGE_NO_PREROLL);
|
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
|
/* 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 */
|
/* Only pay attention to messages coming from the pipeline, not its children */
|
||||||
if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data->pipeline)) {
|
if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data->pipeline)) {
|
||||||
data->state = new_state;
|
data->state = new_state;
|
||||||
gchar *message = g_strdup_printf("State changed to %s",
|
gchar *message = g_strdup_printf("State changed to %s", gst_element_state_get_name(new_state));
|
||||||
gst_element_state_get_name(new_state));
|
|
||||||
set_ui_message(message, data);
|
set_ui_message(message, data);
|
||||||
g_free(message);
|
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) {
|
static void check_initialization_complete(CustomData *data) {
|
||||||
JNIEnv *env = get_jni_env();
|
JNIEnv *env = get_jni_env();
|
||||||
if (!data->initialized && data->native_window && data->main_loop) {
|
if (!data->initialized && data->native_window && data->main_loop) {
|
||||||
GST_DEBUG
|
GST_DEBUG("Initialization complete, notifying application. native_window:%p main_loop:%p", data->native_window, data->main_loop);
|
||||||
("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 */
|
/* 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),
|
gst_video_overlay_set_window_handle(GST_VIDEO_OVERLAY (data->pipeline),
|
||||||
@@ -253,8 +252,7 @@ static void *app_function(void *userdata) {
|
|||||||
/* Build pipeline */
|
/* Build pipeline */
|
||||||
data->pipeline = gst_parse_launch("playbin", &error);
|
data->pipeline = gst_parse_launch("playbin", &error);
|
||||||
if (error) {
|
if (error) {
|
||||||
gchar *message =
|
gchar *message = g_strdup_printf("Unable to build pipeline: %s", error->message);
|
||||||
g_strdup_printf("Unable to build pipeline: %s", error->message);
|
|
||||||
g_clear_error(&error);
|
g_clear_error(&error);
|
||||||
set_ui_message(message, data);
|
set_ui_message(message, data);
|
||||||
g_free(message);
|
g_free(message);
|
||||||
@@ -280,12 +278,9 @@ static void *app_function(void *userdata) {
|
|||||||
g_signal_connect (G_OBJECT(bus), "message::error", (GCallback) error_cb,
|
g_signal_connect (G_OBJECT(bus), "message::error", (GCallback) error_cb,
|
||||||
data);
|
data);
|
||||||
g_signal_connect (G_OBJECT(bus), "message::eos", (GCallback) eos_cb, data);
|
g_signal_connect (G_OBJECT(bus), "message::eos", (GCallback) eos_cb, data);
|
||||||
g_signal_connect (G_OBJECT(bus), "message::state-changed",
|
g_signal_connect (G_OBJECT(bus), "message::state-changed", (GCallback) state_changed_cb, data);
|
||||||
(GCallback) state_changed_cb, data);
|
g_signal_connect (G_OBJECT(bus), "message::buffering", (GCallback) buffering_cb, data);
|
||||||
g_signal_connect (G_OBJECT(bus), "message::buffering",
|
g_signal_connect (G_OBJECT(bus), "message::clock-lost", (GCallback) clock_lost_cb, data);
|
||||||
(GCallback) buffering_cb, data);
|
|
||||||
g_signal_connect (G_OBJECT(bus), "message::clock-lost",
|
|
||||||
(GCallback) clock_lost_cb, data);
|
|
||||||
gst_object_unref(bus);
|
gst_object_unref(bus);
|
||||||
|
|
||||||
/* Create a GLib Main Loop and set it to run */
|
/* Create a GLib Main Loop and set it to run */
|
||||||
@@ -315,8 +310,7 @@ static void *app_function(void *userdata) {
|
|||||||
static void gst_native_init(JNIEnv *env, jobject thiz) {
|
static void gst_native_init(JNIEnv *env, jobject thiz) {
|
||||||
CustomData *data = g_new0 (CustomData, 1);
|
CustomData *data = g_new0 (CustomData, 1);
|
||||||
SET_CUSTOM_DATA (env, thiz, custom_data_field_id, data);
|
SET_CUSTOM_DATA (env, thiz, custom_data_field_id, data);
|
||||||
GST_DEBUG_CATEGORY_INIT (debug_category, "player", 0,
|
GST_DEBUG_CATEGORY_INIT (debug_category, "player", 0, "HI SHARP DX RTSP Player");
|
||||||
"Android tutorial 5");
|
|
||||||
gst_debug_set_threshold_for_name("player", GST_LEVEL_DEBUG);
|
gst_debug_set_threshold_for_name("player", GST_LEVEL_DEBUG);
|
||||||
GST_DEBUG ("Created CustomData at %p", data);
|
GST_DEBUG ("Created CustomData at %p", data);
|
||||||
data->app = (*env)->NewGlobalRef(env, thiz);
|
data->app = (*env)->NewGlobalRef(env, thiz);
|
||||||
@@ -329,16 +323,16 @@ static void gst_native_finalize(JNIEnv *env, jobject thiz) {
|
|||||||
CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
|
CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
|
||||||
if (!data)
|
if (!data)
|
||||||
return;
|
return;
|
||||||
GST_DEBUG ("Quitting main loop...");
|
GST_DEBUG ("[%s]Quitting main loop...", data->tag);
|
||||||
g_main_loop_quit(data->main_loop);
|
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);
|
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);
|
(*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);
|
g_free(data);
|
||||||
SET_CUSTOM_DATA (env, thiz, custom_data_field_id, NULL);
|
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 */
|
/* Set playbin2's URI */
|
||||||
@@ -352,8 +346,22 @@ void gst_native_set_uri(JNIEnv *env, jobject thiz, jstring uri) {
|
|||||||
gst_element_set_state(data->pipeline, GST_STATE_READY);
|
gst_element_set_state(data->pipeline, GST_STATE_READY);
|
||||||
g_object_set(data->pipeline, "uri", char_uri, NULL);
|
g_object_set(data->pipeline, "uri", char_uri, NULL);
|
||||||
(*env)->ReleaseStringUTFChars(env, uri, char_uri);
|
(*env)->ReleaseStringUTFChars(env, uri, char_uri);
|
||||||
data->is_live |=
|
g_object_set(data->pipeline, "latency", 250, NULL);
|
||||||
(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 */
|
/* Set pipeline to PLAYING state */
|
||||||
@@ -361,9 +369,12 @@ static void gst_native_play(JNIEnv *env, jobject thiz) {
|
|||||||
CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
|
CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
|
||||||
if (!data)
|
if (!data)
|
||||||
return;
|
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->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 */
|
/* Set pipeline to PAUSED state */
|
||||||
@@ -371,21 +382,19 @@ static void gst_native_pause(JNIEnv *env, jobject thiz) {
|
|||||||
CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
|
CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
|
||||||
if (!data)
|
if (!data)
|
||||||
return;
|
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->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 */
|
/* Static class initializer: retrieve method and field IDs */
|
||||||
static jboolean gst_native_class_init(JNIEnv *env, jclass klass) {
|
static jboolean gst_native_class_init(JNIEnv *env, jclass klass) {
|
||||||
custom_data_field_id =
|
custom_data_field_id = (*env)->GetFieldID(env, klass, "native_custom_data", "J");
|
||||||
(*env)->GetFieldID(env, klass, "native_custom_data", "J");
|
set_message_method_id = (*env)->GetMethodID(env, klass, "setMessage", "(Ljava/lang/String;)V");
|
||||||
set_message_method_id =
|
on_gstreamer_initialized_method_id = (*env)->GetMethodID(env, klass, "onGStreamerInitialized", "()V");
|
||||||
(*env)->GetMethodID(env, klass, "setMessage", "(Ljava/lang/String;)V");
|
on_media_size_changed_method_id = (*env)->GetMethodID(env, klass, "onMediaSizeChanged", "(II)V");
|
||||||
on_gstreamer_initialized_method_id =
|
|
||||||
(*env)->GetMethodID(env, klass, "onGStreamerInitialized", "()V");
|
|
||||||
on_media_size_changed_method_id =
|
|
||||||
(*env)->GetMethodID(env, klass, "onMediaSizeChanged", "(II)V");
|
|
||||||
|
|
||||||
if (!custom_data_field_id || !set_message_method_id
|
if (!custom_data_field_id || !set_message_method_id
|
||||||
|| !on_gstreamer_initialized_method_id || !on_media_size_changed_method_id) {
|
|| !on_gstreamer_initialized_method_id || !on_media_size_changed_method_id) {
|
||||||
@@ -404,21 +413,18 @@ static void gst_native_surface_init(JNIEnv *env, jobject thiz, jobject surface)
|
|||||||
if (!data)
|
if (!data)
|
||||||
return;
|
return;
|
||||||
ANativeWindow *new_native_window = ANativeWindow_fromSurface(env, surface);
|
ANativeWindow *new_native_window = ANativeWindow_fromSurface(env, surface);
|
||||||
GST_DEBUG ("Received surface %p (native window %p)", surface,
|
GST_DEBUG ("[%s]Received surface %p (native window %p)", data->tag, surface, new_native_window);
|
||||||
new_native_window);
|
|
||||||
|
|
||||||
if (data->native_window) {
|
if (data->native_window) {
|
||||||
ANativeWindow_release(data->native_window);
|
ANativeWindow_release(data->native_window);
|
||||||
if (data->native_window == new_native_window) {
|
if (data->native_window == new_native_window) {
|
||||||
GST_DEBUG ("New native window is the same as the previous one %p",
|
GST_DEBUG ("[%s]New native window is the same as the previous one %p", data->tag, data->native_window);
|
||||||
data->native_window);
|
|
||||||
if (data->pipeline) {
|
if (data->pipeline) {
|
||||||
gst_video_overlay_expose(GST_VIDEO_OVERLAY (data->pipeline));
|
gst_video_overlay_expose(GST_VIDEO_OVERLAY (data->pipeline));
|
||||||
gst_video_overlay_expose(GST_VIDEO_OVERLAY (data->pipeline));
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
} else {
|
} 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;
|
data->initialized = FALSE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -431,7 +437,7 @@ static void gst_native_surface_finalize(JNIEnv *env, jobject thiz) {
|
|||||||
CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
|
CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
|
||||||
if (!data)
|
if (!data)
|
||||||
return;
|
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) {
|
if (data->pipeline) {
|
||||||
gst_video_overlay_set_window_handle(GST_VIDEO_OVERLAY (data->pipeline),
|
gst_video_overlay_set_window_handle(GST_VIDEO_OVERLAY (data->pipeline),
|
||||||
@@ -449,6 +455,7 @@ static JNINativeMethod native_methods[] = {
|
|||||||
{"nativeInit", "()V", (void *) gst_native_init},
|
{"nativeInit", "()V", (void *) gst_native_init},
|
||||||
{"nativeFinalize", "()V", (void *) gst_native_finalize},
|
{"nativeFinalize", "()V", (void *) gst_native_finalize},
|
||||||
{"nativeSetUri", "(Ljava/lang/String;)V", (void *) gst_native_set_uri},
|
{"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},
|
{"nativePlay", "()V", (void *) gst_native_play},
|
||||||
{"nativePause", "()V", (void *) gst_native_pause},
|
{"nativePause", "()V", (void *) gst_native_pause},
|
||||||
{"nativeSurfaceInit", "(Ljava/lang/Object;)V",
|
{"nativeSurfaceInit", "(Ljava/lang/Object;)V",
|
||||||
@@ -464,8 +471,7 @@ jint JNI_OnLoad(JavaVM *vm, void *reserved) {
|
|||||||
java_vm = vm;
|
java_vm = vm;
|
||||||
|
|
||||||
if ((*vm)->GetEnv(vm, (void **) &env, JNI_VERSION_1_4) != JNI_OK) {
|
if ((*vm)->GetEnv(vm, (void **) &env, JNI_VERSION_1_4) != JNI_OK) {
|
||||||
__android_log_print(ANDROID_LOG_ERROR, "player",
|
__android_log_print(ANDROID_LOG_ERROR, "player", "Could not retrieve JNIEnv");
|
||||||
"Could not retrieve JNIEnv");
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
jclass klass = (*env)->FindClass(env, "com/hisharp/gstreamer_player/GstLibrary");
|
jclass klass = (*env)->FindClass(env, "com/hisharp/gstreamer_player/GstLibrary");
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 5.4 KiB |
@@ -1,46 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:orientation="vertical" >
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/textview_message"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginBottom="16dip"
|
|
||||||
android:gravity="center_horizontal" />
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginBottom="16dip"
|
|
||||||
android:gravity="center_horizontal"
|
|
||||||
android:orientation="horizontal" >
|
|
||||||
|
|
||||||
<ImageButton
|
|
||||||
android:id="@+id/button_play"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:contentDescription="@string/button_play"
|
|
||||||
android:src="@android:drawable/ic_media_play"
|
|
||||||
android:text="@string/button_play" />
|
|
||||||
|
|
||||||
<ImageButton
|
|
||||||
android:id="@+id/button_stop"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:contentDescription="@string/button_stop"
|
|
||||||
android:src="@android:drawable/ic_media_pause"
|
|
||||||
android:text="@string/button_stop" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<com.hisharp.gstreamer_player.GStreamerSurfaceView
|
|
||||||
android:id="@+id/surface_video"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center_vertical|center_horizontal" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
@@ -1,12 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">GStreamer tutorial 5</string>
|
|
||||||
<string name="button_play">Play</string>
|
|
||||||
<string name="button_stop">Stop</string>
|
|
||||||
<string name="button_select">Select</string>
|
|
||||||
<string name="button_cancel">Cancel</string>
|
|
||||||
<string name="filechooser_name">Select a file</string>
|
|
||||||
<string name="location">Location</string>
|
|
||||||
<string name="cant_read_folder">folder cannot be read</string>
|
|
||||||
<string name="icon">Icon</string>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package com.hisharp.gstreamer_player;
|
package com.hisharp.gstreamer_player;
|
||||||
|
|
||||||
public interface GstCallback {
|
public interface GstCallback {
|
||||||
void onStatus(GstLibrary gstInstance, GstStatus gstStatus);
|
void onStatus(GstStatus gstStatus);
|
||||||
void onMessage(GstLibrary gstInstance, String message);
|
void onMessage(String message);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,21 +10,25 @@ import android.view.SurfaceView;
|
|||||||
|
|
||||||
import org.freedesktop.gstreamer.GStreamer;
|
import org.freedesktop.gstreamer.GStreamer;
|
||||||
|
|
||||||
public class GstLibrary implements SurfaceHolder.Callback {
|
import java.io.Closeable;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class GstLibrary implements Closeable, SurfaceHolder.Callback {
|
||||||
|
|
||||||
|
private static final String TAG = GstLibrary.class.getSimpleName();
|
||||||
|
|
||||||
final Context mAppContext;
|
final Context mAppContext;
|
||||||
|
|
||||||
private GstCallback gstCallback;
|
private GstCallback gstCallback;
|
||||||
|
|
||||||
private GStreamerSurfaceView surfaceView;
|
private String rtspUrl;
|
||||||
|
|
||||||
private final Handler mainHandler = new Handler(Looper.getMainLooper());
|
private String tag = "";
|
||||||
|
|
||||||
private final String rtspUrl;
|
private Boolean isInit = false;
|
||||||
|
|
||||||
public GstLibrary(Context context, String rtspUrl) {
|
public GstLibrary(Context context) {
|
||||||
this.mAppContext = context.getApplicationContext();
|
this.mAppContext = context.getApplicationContext();
|
||||||
this.rtspUrl = rtspUrl;
|
|
||||||
|
|
||||||
// Initialize GStreamer and warn if it fails
|
// Initialize GStreamer and warn if it fails
|
||||||
try {
|
try {
|
||||||
@@ -40,8 +44,8 @@ public class GstLibrary implements SurfaceHolder.Callback {
|
|||||||
private native void nativeInit(); // Initialize native code, build pipeline, etc
|
private native void nativeInit(); // Initialize native code, build pipeline, etc
|
||||||
private native void nativeFinalize(); // Destroy pipeline and shutdown native code
|
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 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 nativePlay(); // Set pipeline to PLAYING
|
||||||
private native void nativeSetPosition(int milliseconds); // Seek to the indicated position, in milliseconds
|
|
||||||
private native void nativePause(); // Set pipeline to PAUSED
|
private native void nativePause(); // Set pipeline to PAUSED
|
||||||
private static native boolean nativeClassInit(); // Initialize native class: cache Method IDs for callbacks
|
private static native boolean nativeClassInit(); // Initialize native class: cache Method IDs for callbacks
|
||||||
private native void nativeSurfaceInit(Object surface); // A new surface is available
|
private native void nativeSurfaceInit(Object surface); // A new surface is available
|
||||||
@@ -51,67 +55,82 @@ public class GstLibrary implements SurfaceHolder.Callback {
|
|||||||
//private final String defaultMediaUri = "rtsp://admin:admin@192.168.0.77:554/media/video2";
|
//private final String defaultMediaUri = "rtsp://admin:admin@192.168.0.77:554/media/video2";
|
||||||
|
|
||||||
public void play() {
|
public void play() {
|
||||||
|
if (!isInit) return;
|
||||||
nativePlay();
|
nativePlay();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void stop() {
|
public void stop() {
|
||||||
|
if (!isInit) return;
|
||||||
nativePause();
|
nativePause();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void release() {
|
public void setRtspUrl(String rtspUrl) {
|
||||||
nativeFinalize();
|
this.rtspUrl = rtspUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTag(String tag) {
|
||||||
|
this.tag = tag;
|
||||||
|
nativeSetTag(tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void releaseSurface() {
|
||||||
|
isInit = false;
|
||||||
|
nativeSurfaceFinalize();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setOnStatusChangeListener(GstCallback callback) {
|
public void setOnStatusChangeListener(GstCallback callback) {
|
||||||
this.gstCallback = callback;
|
this.gstCallback = callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSurfaceView(GStreamerSurfaceView surfaceView) {
|
public void setSurfaceHolder(SurfaceHolder holder) {
|
||||||
this.surfaceView = surfaceView;
|
|
||||||
SurfaceHolder holder = this.surfaceView.getHolder();
|
|
||||||
holder.addCallback(this);
|
holder.addCallback(this);
|
||||||
nativeSurfaceInit(holder.getSurface());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 void setMessage(final String message) {
|
private void setMessage(final String message) {
|
||||||
if (gstCallback == null) return;
|
if (gstCallback == null) return;
|
||||||
if (message.contains("State changed to PAUSED")) {
|
if (message.contains("State changed to PAUSED")) {
|
||||||
gstCallback.onStatus(this, GstStatus.PAUSE);
|
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(this, GstStatus.PLAYING);
|
gstCallback.onStatus(GstStatus.PLAYING);
|
||||||
} else if (message.contains("Could not open resource for reading and writing")) {
|
} else if (message.contains("Could not open resource for reading and writing")) {
|
||||||
gstCallback.onStatus(this, GstStatus.ERROR);
|
gstCallback.onStatus(GstStatus.ERROR_WHEN_OPENING);
|
||||||
|
} else if (message.contains("GStreamer encountered a general supporting library error")) {
|
||||||
|
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(this, message);
|
gstCallback.onMessage(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called from native code. Native code calls this once it has created its pipeline and
|
// 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.
|
// the main loop is running, so it is ready to accept commands.
|
||||||
private void onGStreamerInitialized () {
|
private void onGStreamerInitialized () {
|
||||||
Log.i ("GStreamer", "GStreamer initialized:");
|
Log.i (TAG + "+" + tag, "GStreamer initialized:");
|
||||||
|
|
||||||
if (gstCallback != null) {
|
if (gstCallback != null) {
|
||||||
gstCallback.onStatus(this, GstStatus.READY);
|
gstCallback.onStatus(GstStatus.READY);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore previous playing state
|
// Restore previous playing state
|
||||||
|
if (rtspUrl != null) {
|
||||||
nativeSetUri(rtspUrl);
|
nativeSetUri(rtspUrl);
|
||||||
nativeSetPosition (0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called from native code
|
isInit = true;
|
||||||
private void setCurrentPosition(final int position, final int duration) {}
|
}
|
||||||
|
|
||||||
// Called from native code when the size of the media changes or is first detected.
|
// 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.
|
// Inform the video surface about the new size and recalculate the layout.
|
||||||
private void onMediaSizeChanged (int width, int height) {
|
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(() -> {
|
/*mainHandler.post(() -> {
|
||||||
surfaceView.media_width = width;
|
surfaceView.media_width = width;
|
||||||
surfaceView.media_height = height;
|
surfaceView.media_height = height;
|
||||||
surfaceView.requestLayout();
|
surfaceView.requestLayout();
|
||||||
});
|
});*/
|
||||||
}
|
}
|
||||||
|
|
||||||
static {
|
static {
|
||||||
@@ -120,19 +139,34 @@ public class GstLibrary implements SurfaceHolder.Callback {
|
|||||||
nativeClassInit();
|
nativeClassInit();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void surfaceChanged(SurfaceHolder holder, int format, int width,
|
@Override
|
||||||
int height) {
|
public void close() throws IOException {
|
||||||
Log.d("GStreamer", "Surface changed to format " + format + " width "
|
isInit = false;
|
||||||
+ width + " height " + height);
|
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());
|
nativeSurfaceInit(holder.getSurface());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void surfaceCreated(SurfaceHolder holder) {
|
@Override
|
||||||
Log.d("GStreamer", "Surface created: " + holder.getSurface());
|
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) {
|
public void surfaceDestroyed(SurfaceHolder holder) {
|
||||||
Log.d("GStreamer", "Surface destroyed");
|
Log.d(TAG + "+" + tag, "Surface destroyed");
|
||||||
nativeSurfaceFinalize();
|
releaseSurface();
|
||||||
|
isInit = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,5 +4,7 @@ public enum GstStatus {
|
|||||||
READY,
|
READY,
|
||||||
PAUSE,
|
PAUSE,
|
||||||
PLAYING,
|
PLAYING,
|
||||||
ERROR
|
ERROR,
|
||||||
|
ERROR_WHEN_OPENING,
|
||||||
|
BUFFERING
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,2 +1,19 @@
|
|||||||
|
pluginManagement {
|
||||||
|
repositories {
|
||||||
|
gradlePluginPortal()
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
maven { url 'https://jitpack.io' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dependencyResolutionManagement {
|
||||||
|
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
jcenter() // Warning: this repository is going to shut down soon
|
||||||
|
maven {url "https://jitpack.io"}
|
||||||
|
}
|
||||||
|
}
|
||||||
include ':gstreamer_player'
|
include ':gstreamer_player'
|
||||||
include ':app'
|
include ':app'
|
||||||
|
|||||||