desc:移除部分未使用代码
@ -1 +0,0 @@
|
|||||||
/build
|
|
@ -1,62 +0,0 @@
|
|||||||
apply from: "${rootProject.rootDir}/buildCommon/commonLibConfig.gradle"
|
|
||||||
project.ext.setAppDefaultConfig project
|
|
||||||
|
|
||||||
android {
|
|
||||||
namespace 'com.yinuo.safetywatcher'
|
|
||||||
|
|
||||||
defaultConfig {
|
|
||||||
applicationId "com.yinuo.safetywatcher"
|
|
||||||
vectorDrawables {
|
|
||||||
useSupportLibrary true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
buildFeatures {
|
|
||||||
compose true
|
|
||||||
}
|
|
||||||
composeOptions {
|
|
||||||
kotlinCompilerExtensionVersion '1.3.2'
|
|
||||||
}
|
|
||||||
packagingOptions {
|
|
||||||
resources {
|
|
||||||
excludes += '/META-INF/{AL2.0,LGPL2.1}'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
implementation 'androidx.core:core-ktx:1.8.0'
|
|
||||||
implementation 'androidx.appcompat:appcompat:1.4.1'
|
|
||||||
|
|
||||||
var lifeCycle_version = '2.5.1'
|
|
||||||
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifeCycle_version"
|
|
||||||
implementation "androidx.lifecycle:lifecycle-viewmodel-compose:$lifeCycle_version"
|
|
||||||
|
|
||||||
implementation platform('androidx.compose:compose-bom:2022.10.00')
|
|
||||||
implementation 'androidx.activity:activity-compose:1.5.1'
|
|
||||||
implementation 'androidx.compose.ui:ui'
|
|
||||||
implementation 'androidx.compose.ui:ui-graphics'
|
|
||||||
implementation 'androidx.compose.ui:ui-tooling-preview'
|
|
||||||
implementation 'androidx.compose.material3:material3'
|
|
||||||
implementation 'androidx.navigation:navigation-runtime-ktx:2.5.2'
|
|
||||||
implementation "androidx.navigation:navigation-compose:2.4.0-rc01"
|
|
||||||
implementation 'io.reactivex.rxjava2:rxjava:2.1.6'
|
|
||||||
implementation 'io.reactivex.rxjava2:rxandroid:2.0.1'
|
|
||||||
|
|
||||||
var accompanist_version = '0.30.1'
|
|
||||||
implementation "com.google.accompanist:accompanist-permissions:$accompanist_version"
|
|
||||||
|
|
||||||
//添加excel
|
|
||||||
implementation rootProject.ext.dependencies.jxl
|
|
||||||
|
|
||||||
implementation project(path: ':library-push')
|
|
||||||
implementation project(path: ':library-ijkplayer')
|
|
||||||
|
|
||||||
// documentfile 访问U盘
|
|
||||||
implementation "androidx.documentfile:documentfile:1.0.1"
|
|
||||||
|
|
||||||
implementation(name: 'libuvccamera-release', ext: 'aar') {
|
|
||||||
exclude module: 'support-v4'
|
|
||||||
exclude module: 'appcompat-v7'
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
# Add project specific ProGuard rules here.
|
|
||||||
# You can control the set of applied configuration files using the
|
|
||||||
# proguardFiles setting in build.gradle.
|
|
||||||
#
|
|
||||||
# For more details, see
|
|
||||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
|
||||||
|
|
||||||
# If your project uses WebView with JS, uncomment the following
|
|
||||||
# and specify the fully qualified class name to the JavaScript interface
|
|
||||||
# class:
|
|
||||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
|
||||||
# public *;
|
|
||||||
#}
|
|
||||||
|
|
||||||
# Uncomment this to preserve the line number information for
|
|
||||||
# debugging stack traces.
|
|
||||||
#-keepattributes SourceFile,LineNumberTable
|
|
||||||
|
|
||||||
# If you keep the line number information, uncomment this to
|
|
||||||
# hide the original source file name.
|
|
||||||
#-renamesourcefileattribute SourceFile
|
|
@ -1,38 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
|
|
||||||
<uses-feature
|
|
||||||
android:name="android.hardware.usb.host"
|
|
||||||
android:required="true" />
|
|
||||||
<uses-feature
|
|
||||||
android:name="android.hardware.camera"
|
|
||||||
android:required="false" />
|
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
|
||||||
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
|
|
||||||
<uses-permission android:name="android.permission.CAMERA" />
|
|
||||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
|
||||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
|
|
||||||
|
|
||||||
<application
|
|
||||||
android:name=".App"
|
|
||||||
android:allowBackup="true"
|
|
||||||
android:icon="@mipmap/ic_launcher"
|
|
||||||
android:label="@string/app_name"
|
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
|
||||||
android:supportsRtl="true"
|
|
||||||
android:theme="@style/Theme.Easypusher">
|
|
||||||
<activity
|
|
||||||
android:name=".MainActivity"
|
|
||||||
android:exported="true"
|
|
||||||
android:label="@string/app_name"
|
|
||||||
android:theme="@style/Theme.Easypusher">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.MAIN" />
|
|
||||||
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
|
||||||
</intent-filter>
|
|
||||||
</activity>
|
|
||||||
</application>
|
|
||||||
|
|
||||||
</manifest>
|
|
@ -1,13 +0,0 @@
|
|||||||
package com.yinuo.safetywatcher
|
|
||||||
|
|
||||||
import android.app.Application
|
|
||||||
import com.lztek.toolkit.Lztek
|
|
||||||
import com.yinuo.safetywatcher.utils.LztekUtil
|
|
||||||
|
|
||||||
class App : Application() {
|
|
||||||
|
|
||||||
override fun onCreate() {
|
|
||||||
super.onCreate()
|
|
||||||
LztekUtil.setObject(Lztek.create(this))
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,68 +0,0 @@
|
|||||||
package com.yinuo.safetywatcher
|
|
||||||
|
|
||||||
import android.content.Intent
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.activity.ComponentActivity
|
|
||||||
import androidx.activity.OnBackPressedCallback
|
|
||||||
import androidx.activity.compose.setContent
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.Surface
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
|
||||||
import androidx.navigation.compose.rememberNavController
|
|
||||||
import com.yinuo.safetywatcher.navi.NavigationUtil
|
|
||||||
import com.yinuo.safetywatcher.navi.NavigationView
|
|
||||||
import com.yinuo.safetywatcher.ui.SplashView
|
|
||||||
import com.yinuo.safetywatcher.ui.theme.EasypusherTheme
|
|
||||||
import org.easydarwin.push.MediaStream
|
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
|
||||||
var exitTime = 0L
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
startMediaService()
|
|
||||||
setContent {
|
|
||||||
EasypusherTheme {
|
|
||||||
// A surface container using the 'background' color from the theme
|
|
||||||
Surface(
|
|
||||||
modifier = Modifier.fillMaxSize(),
|
|
||||||
color = MaterialTheme.colorScheme.background
|
|
||||||
) {
|
|
||||||
val viewModel: MainViewModel = viewModel()
|
|
||||||
if (viewModel.isSplash) {
|
|
||||||
SplashView { viewModel.isSplash = false }
|
|
||||||
} else {
|
|
||||||
NavigationUtil.navHostController = rememberNavController()
|
|
||||||
NavigationView()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
addBackListener()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun startMediaService() {
|
|
||||||
// 启动服务...
|
|
||||||
val intent = Intent(this, MediaStream::class.java)
|
|
||||||
startService(intent)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun addBackListener() {
|
|
||||||
onBackPressedDispatcher.addCallback(object : OnBackPressedCallback(true) {
|
|
||||||
override fun handleOnBackPressed() {
|
|
||||||
//是主页
|
|
||||||
if (System.currentTimeMillis() - exitTime > 2000) {
|
|
||||||
Toast.makeText(this@MainActivity, "再点一次退出!", Toast.LENGTH_SHORT)
|
|
||||||
.show()
|
|
||||||
exitTime = System.currentTimeMillis()
|
|
||||||
} else {
|
|
||||||
finish()
|
|
||||||
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
package com.yinuo.safetywatcher
|
|
||||||
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.lifecycle.ViewModel
|
|
||||||
|
|
||||||
class MainViewModel: ViewModel() {
|
|
||||||
var isSplash by mutableStateOf(true)
|
|
||||||
}
|
|
@ -1,11 +0,0 @@
|
|||||||
package com.yinuo.safetywatcher.navi
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @Description: todo
|
|
||||||
* @CreateDate: 2022/1/5 9:42
|
|
||||||
*/
|
|
||||||
sealed class ModelPath(val route: String) {
|
|
||||||
object Home : ModelPath("home")
|
|
||||||
object Setting : ModelPath("setting")
|
|
||||||
object Cloud : ModelPath("cloud")
|
|
||||||
}
|
|
@ -1,35 +0,0 @@
|
|||||||
package com.yinuo.safetywatcher.navi
|
|
||||||
|
|
||||||
import androidx.compose.animation.core.tween
|
|
||||||
import androidx.compose.animation.fadeIn
|
|
||||||
import androidx.compose.animation.fadeOut
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.navigation.compose.NavHost
|
|
||||||
import androidx.navigation.compose.composable
|
|
||||||
import com.yinuo.safetywatcher.ui.cloud.CloudView
|
|
||||||
import com.yinuo.safetywatcher.ui.home.HomeView
|
|
||||||
import com.yinuo.safetywatcher.ui.setting.SettingView
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @Description: todo
|
|
||||||
* @CreateDate: 2022/2/22 19:31
|
|
||||||
*/
|
|
||||||
@Composable
|
|
||||||
fun NavigationView() {
|
|
||||||
NavHost(navController = NavigationUtil.navHostController,
|
|
||||||
startDestination = ModelPath.Home.route) {
|
|
||||||
composable(ModelPath.Home.route) {
|
|
||||||
HomeView()
|
|
||||||
}
|
|
||||||
composable(ModelPath.Setting.route) {
|
|
||||||
SettingView(Modifier.fillMaxSize())
|
|
||||||
}
|
|
||||||
composable(ModelPath.Cloud.route){
|
|
||||||
CloudView()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,53 +0,0 @@
|
|||||||
package com.yinuo.safetywatcher.navi
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.os.Bundle
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateListOf
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.core.net.toUri
|
|
||||||
import androidx.navigation.*
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @CreateDate: 2021/8/27 10:06
|
|
||||||
*/
|
|
||||||
object NavigationUtil {
|
|
||||||
|
|
||||||
@SuppressLint("StaticFieldLeak")
|
|
||||||
lateinit var navHostController: NavHostController
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 跳转到某个页面
|
|
||||||
*/
|
|
||||||
fun to(screenName: ModelPath) {
|
|
||||||
navHostController.navigate(screenName.route)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 跳转到某个页面带参数
|
|
||||||
*/
|
|
||||||
fun toBundle(screenName: ModelPath, bundle: Bundle) {
|
|
||||||
navHostController.navigate(screenName.route, bundle)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun NavController.navigate(
|
|
||||||
route: String,
|
|
||||||
args: Bundle,
|
|
||||||
navOptions: NavOptions? = null,
|
|
||||||
navigatorExtras: Navigator.Extras? = null
|
|
||||||
) {
|
|
||||||
val routeLink = NavDeepLinkRequest.Builder.Companion.fromUri(
|
|
||||||
NavDestination.Companion.createRoute(route).toUri())
|
|
||||||
.build()
|
|
||||||
|
|
||||||
val deepLinkMatch = graph.matchDeepLink(routeLink)
|
|
||||||
if (deepLinkMatch != null) {
|
|
||||||
val destination = deepLinkMatch.destination
|
|
||||||
val id = destination.id
|
|
||||||
navigate(id, args, navOptions, navigatorExtras)
|
|
||||||
} else {
|
|
||||||
navigate(route, navOptions, navigatorExtras)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,55 +0,0 @@
|
|||||||
package com.yinuo.safetywatcher.ui
|
|
||||||
|
|
||||||
import androidx.compose.animation.animateColorAsState
|
|
||||||
import androidx.compose.animation.core.tween
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.*
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @Description: todo
|
|
||||||
* @Author: yshh
|
|
||||||
* @CreateDate: 2022/2/22 14:19
|
|
||||||
*/
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun SplashView(startMain: () -> Unit) {
|
|
||||||
var enabled by remember { mutableStateOf(false) }
|
|
||||||
val bgColor: Color by animateColorAsState(
|
|
||||||
if (enabled) MaterialTheme.colorScheme.primary
|
|
||||||
else MaterialTheme.colorScheme.primary.copy(alpha = 0.3f),
|
|
||||||
animationSpec = tween(durationMillis = 2000)
|
|
||||||
)
|
|
||||||
val textColor: Color by animateColorAsState(
|
|
||||||
if (enabled) Color.White
|
|
||||||
else Color.White.copy(alpha = 0.3f),
|
|
||||||
animationSpec = tween(durationMillis = 2000)
|
|
||||||
)
|
|
||||||
Box(
|
|
||||||
Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.background(Color.White)
|
|
||||||
) {
|
|
||||||
Box(
|
|
||||||
Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.background(bgColor),
|
|
||||||
contentAlignment = Alignment.Center
|
|
||||||
) {
|
|
||||||
Text(text = "Safety Watcher", color = textColor)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
LaunchedEffect(Unit) {
|
|
||||||
enabled = true
|
|
||||||
delay(2000)
|
|
||||||
startMain.invoke()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,32 +0,0 @@
|
|||||||
package com.yinuo.safetywatcher.ui.cloud
|
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.material3.Button
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun CloudView() {
|
|
||||||
|
|
||||||
// rememberLauncherForActivityResult(contract = ActivityResultContracts.OpenDocumentTree(), onResult ={} )
|
|
||||||
|
|
||||||
Column(
|
|
||||||
modifier = Modifier.fillMaxSize(),
|
|
||||||
verticalArrangement = Arrangement.Center,
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
|
||||||
) {
|
|
||||||
|
|
||||||
val context = LocalContext.current;
|
|
||||||
val viewModel: CloudViewModel = viewModel()
|
|
||||||
|
|
||||||
Button(onClick = { viewModel.exportExcel(context) }) {
|
|
||||||
Text(text = "导出数据")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,28 +0,0 @@
|
|||||||
package com.yinuo.safetywatcher.ui.cloud
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.lifecycle.ViewModel
|
|
||||||
import com.yinuo.safetywatcher.xls.SimpleCellValue
|
|
||||||
import com.yinuo.safetywatcher.xls.utils.ExcelUtils
|
|
||||||
|
|
||||||
class CloudViewModel : ViewModel() {
|
|
||||||
|
|
||||||
fun exportExcel(context: Context){
|
|
||||||
val allData: MutableList<List<SimpleCellValue>> = ArrayList()
|
|
||||||
val row1: MutableList<SimpleCellValue> = ArrayList()
|
|
||||||
row1.add(SimpleCellValue("5.22"))
|
|
||||||
row1.add(SimpleCellValue("1"))
|
|
||||||
row1.add(SimpleCellValue("2"))
|
|
||||||
row1.add(SimpleCellValue("3"))
|
|
||||||
val row2: MutableList<SimpleCellValue> = ArrayList()
|
|
||||||
row2.add(SimpleCellValue("5.23"))
|
|
||||||
row2.add(SimpleCellValue("4"))
|
|
||||||
row2.add(SimpleCellValue("5"))
|
|
||||||
row2.add(SimpleCellValue("6"))
|
|
||||||
|
|
||||||
allData.add(row1)
|
|
||||||
allData.add(row2)
|
|
||||||
|
|
||||||
ExcelUtils.writeStringListToExcel(allData, context)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,64 +0,0 @@
|
|||||||
package com.yinuo.safetywatcher.ui.home
|
|
||||||
|
|
||||||
import android.view.TextureView
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.foundation.layout.width
|
|
||||||
import androidx.compose.material3.Button
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.compose.ui.viewinterop.AndroidView
|
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
|
||||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
|
||||||
import com.yinuo.safetywatcher.navi.ModelPath
|
|
||||||
import com.yinuo.safetywatcher.navi.NavigationUtil
|
|
||||||
|
|
||||||
@OptIn(ExperimentalPermissionsApi::class)
|
|
||||||
@Composable
|
|
||||||
fun HomeView() {
|
|
||||||
// 权限
|
|
||||||
// val permissionsState = rememberMultiplePermissionsState(
|
|
||||||
// permissions = listOf(
|
|
||||||
// android.Manifest.permission.MANAGE_EXTERNAL_STORAGE,
|
|
||||||
// android.Manifest.permission.CAMERA,
|
|
||||||
// )
|
|
||||||
// )
|
|
||||||
// if (permissionsState.allPermissionsGranted) {
|
|
||||||
val viewModel: HomeViewModel = viewModel()
|
|
||||||
val context = LocalContext.current
|
|
||||||
Row(modifier = Modifier.fillMaxSize()) {
|
|
||||||
AndroidView(factory = {
|
|
||||||
TextureView(it)
|
|
||||||
}, modifier = Modifier
|
|
||||||
.fillMaxHeight()
|
|
||||||
.width(200.dp), update = {
|
|
||||||
viewModel.setTextureView(it, context);
|
|
||||||
})
|
|
||||||
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.width(150.dp)
|
|
||||||
.fillMaxHeight()
|
|
||||||
.background(Color.Red)
|
|
||||||
) {
|
|
||||||
Button(onClick = { NavigationUtil.to(ModelPath.Setting) }) {
|
|
||||||
Text(text = "设置")
|
|
||||||
}
|
|
||||||
Button(onClick = { NavigationUtil.to(ModelPath.Cloud) }) {
|
|
||||||
Text(text = "云平台")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// } else {
|
|
||||||
// LaunchedEffect(Unit) {
|
|
||||||
// permissionsState.launchMultiplePermissionRequest()
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
|
@ -1,61 +0,0 @@
|
|||||||
package com.yinuo.safetywatcher.ui.home
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.content.Context
|
|
||||||
import android.graphics.SurfaceTexture
|
|
||||||
import android.util.Log
|
|
||||||
import android.view.TextureView
|
|
||||||
import androidx.activity.ComponentActivity
|
|
||||||
import androidx.lifecycle.LifecycleOwner
|
|
||||||
import androidx.lifecycle.ViewModel
|
|
||||||
import com.yinuo.safetywatcher.utils.RxHelper
|
|
||||||
import com.yinuo.safetywatcher.utils.SurfaceTextureListenerWrapper
|
|
||||||
import io.reactivex.Single
|
|
||||||
import org.easydarwin.push.MediaStream
|
|
||||||
|
|
||||||
class HomeViewModel : ViewModel() {
|
|
||||||
@SuppressLint("StaticFieldLeak")
|
|
||||||
private var mediaStream: MediaStream? = null
|
|
||||||
|
|
||||||
private fun getMediaStream(context: Context, owner: LifecycleOwner): Single<MediaStream?>? {
|
|
||||||
val single: Single<MediaStream?> =
|
|
||||||
RxHelper.single(MediaStream.getBindedMediaStream(context, owner), mediaStream)
|
|
||||||
return if (mediaStream == null) {
|
|
||||||
single.doOnSuccess { ms -> mediaStream = ms }
|
|
||||||
} else {
|
|
||||||
single
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("CheckResult")
|
|
||||||
fun setTextureView(it: TextureView, context: Context) {
|
|
||||||
if (context is ComponentActivity) {
|
|
||||||
val activity: ComponentActivity = context
|
|
||||||
getMediaStream(context, activity)?.subscribe(
|
|
||||||
{ ms ->
|
|
||||||
if (it.isAvailable) {
|
|
||||||
ms?.setSurfaceTexture(it.surfaceTexture)
|
|
||||||
} else {
|
|
||||||
it.surfaceTextureListener = object : SurfaceTextureListenerWrapper() {
|
|
||||||
override fun onSurfaceTextureAvailable(
|
|
||||||
surfaceTexture: SurfaceTexture,
|
|
||||||
i: Int,
|
|
||||||
i1: Int
|
|
||||||
) {
|
|
||||||
ms?.setSurfaceTexture(surfaceTexture)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onSurfaceTextureDestroyed(surfaceTexture: SurfaceTexture): Boolean {
|
|
||||||
ms?.setSurfaceTexture(null)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Log.w("HomeViewModel", "创建服务出错!" + it.message)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
package com.yinuo.safetywatcher.ui.setting
|
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun SettingView(modifier: Modifier = Modifier) {
|
|
||||||
Box(modifier = modifier){
|
|
||||||
Text(text = "这是设置页面")
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,11 +0,0 @@
|
|||||||
package com.yinuo.safetywatcher.ui.theme
|
|
||||||
|
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
|
|
||||||
val Purple80 = Color(0xFFD0BCFF)
|
|
||||||
val PurpleGrey80 = Color(0xFFCCC2DC)
|
|
||||||
val Pink80 = Color(0xFFEFB8C8)
|
|
||||||
|
|
||||||
val Purple40 = Color(0xFF6650a4)
|
|
||||||
val PurpleGrey40 = Color(0xFF625b71)
|
|
||||||
val Pink40 = Color(0xFF7D5260)
|
|
@ -1,24 +0,0 @@
|
|||||||
package com.yinuo.safetywatcher.ui.theme
|
|
||||||
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.lightColorScheme
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
|
|
||||||
|
|
||||||
private val LightColorScheme = lightColorScheme(
|
|
||||||
primary = Purple40,
|
|
||||||
secondary = PurpleGrey40,
|
|
||||||
tertiary = Pink40
|
|
||||||
)
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun EasypusherTheme(
|
|
||||||
content: @Composable () -> Unit
|
|
||||||
) {
|
|
||||||
val colorScheme = LightColorScheme
|
|
||||||
MaterialTheme(
|
|
||||||
colorScheme = colorScheme,
|
|
||||||
typography = Typography,
|
|
||||||
content = content
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,34 +0,0 @@
|
|||||||
package com.yinuo.safetywatcher.ui.theme
|
|
||||||
|
|
||||||
import androidx.compose.material3.Typography
|
|
||||||
import androidx.compose.ui.text.TextStyle
|
|
||||||
import androidx.compose.ui.text.font.FontFamily
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
|
||||||
import androidx.compose.ui.unit.sp
|
|
||||||
|
|
||||||
// Set of Material typography styles to start with
|
|
||||||
val Typography = Typography(
|
|
||||||
bodyLarge = TextStyle(
|
|
||||||
fontFamily = FontFamily.Default,
|
|
||||||
fontWeight = FontWeight.Normal,
|
|
||||||
fontSize = 16.sp,
|
|
||||||
lineHeight = 24.sp,
|
|
||||||
letterSpacing = 0.5.sp
|
|
||||||
)
|
|
||||||
/* Other default text styles to override
|
|
||||||
titleLarge = TextStyle(
|
|
||||||
fontFamily = FontFamily.Default,
|
|
||||||
fontWeight = FontWeight.Normal,
|
|
||||||
fontSize = 22.sp,
|
|
||||||
lineHeight = 28.sp,
|
|
||||||
letterSpacing = 0.sp
|
|
||||||
),
|
|
||||||
labelSmall = TextStyle(
|
|
||||||
fontFamily = FontFamily.Default,
|
|
||||||
fontWeight = FontWeight.Medium,
|
|
||||||
fontSize = 11.sp,
|
|
||||||
lineHeight = 16.sp,
|
|
||||||
letterSpacing = 0.5.sp
|
|
||||||
)
|
|
||||||
*/
|
|
||||||
)
|
|
@ -1,16 +0,0 @@
|
|||||||
package com.yinuo.safetywatcher.utils
|
|
||||||
|
|
||||||
import com.lztek.toolkit.Lztek
|
|
||||||
|
|
||||||
object LztekUtil {
|
|
||||||
|
|
||||||
private var mLztek: Lztek? = null;
|
|
||||||
|
|
||||||
fun setObject(value: Lztek) {
|
|
||||||
mLztek = value
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getLztek(): Lztek {
|
|
||||||
return mLztek!!
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,47 +0,0 @@
|
|||||||
package com.yinuo.safetywatcher.utils;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
import org.easydarwin.util.AbstractSubscriber;
|
|
||||||
import org.reactivestreams.Publisher;
|
|
||||||
|
|
||||||
import io.reactivex.Single;
|
|
||||||
import io.reactivex.subjects.PublishSubject;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by apple on 2017/12/22.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class RxHelper {
|
|
||||||
static boolean IGNORE_ERROR = false;
|
|
||||||
|
|
||||||
public static <T> Single<T> single(@NonNull Publisher<T> t, @Nullable T defaultValueIfNotNull){
|
|
||||||
if (defaultValueIfNotNull != null) return Single.just(defaultValueIfNotNull);
|
|
||||||
final PublishSubject sub = PublishSubject.create();
|
|
||||||
t.subscribe(new AbstractSubscriber<T>() {
|
|
||||||
@Override
|
|
||||||
public void onNext(T t) {
|
|
||||||
super.onNext(t);
|
|
||||||
sub.onNext(t);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(Throwable t) {
|
|
||||||
if (IGNORE_ERROR) {
|
|
||||||
super.onError(t);
|
|
||||||
sub.onComplete();
|
|
||||||
}else {
|
|
||||||
sub.onError(t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onComplete() {
|
|
||||||
super.onComplete();
|
|
||||||
sub.onComplete();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return sub.firstOrError();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,27 +0,0 @@
|
|||||||
package com.yinuo.safetywatcher.utils;
|
|
||||||
|
|
||||||
import android.graphics.SurfaceTexture;
|
|
||||||
import android.view.TextureView;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by apple on 2017/9/11.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public abstract class SurfaceTextureListenerWrapper implements TextureView.SurfaceTextureListener{
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int i, int i1) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +0,0 @@
|
|||||||
package com.yinuo.safetywatcher.xls
|
|
||||||
|
|
||||||
interface ICellValue {
|
|
||||||
fun getValue(): String
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
package com.yinuo.safetywatcher.xls
|
|
||||||
|
|
||||||
data class SimpleCellValue(val content: String) : ICellValue {
|
|
||||||
override fun getValue(): String {
|
|
||||||
return content
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,138 +0,0 @@
|
|||||||
package com.yinuo.safetywatcher.xls.utils
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.util.Log
|
|
||||||
import com.yinuo.safetywatcher.xls.ICellValue
|
|
||||||
import com.yinuo.safetywatcher.R
|
|
||||||
import jxl.Workbook
|
|
||||||
import jxl.WorkbookSettings
|
|
||||||
import jxl.write.Label
|
|
||||||
import jxl.write.WritableCellFormat
|
|
||||||
import jxl.write.WritableFont
|
|
||||||
import jxl.write.WritableWorkbook
|
|
||||||
import jxl.write.WriteException
|
|
||||||
import java.io.File
|
|
||||||
import java.io.FileInputStream
|
|
||||||
import java.io.InputStream
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* excel表格工具
|
|
||||||
*/
|
|
||||||
object ExcelUtils {
|
|
||||||
private const val TAG: String = "ExcelUtils"
|
|
||||||
private const val UTF8_ENCODING = "UTF-8"
|
|
||||||
private var arial12format: WritableCellFormat? = null
|
|
||||||
|
|
||||||
private fun format() {
|
|
||||||
try {
|
|
||||||
val writableFont = WritableFont(WritableFont.ARIAL, 12)
|
|
||||||
arial12format = WritableCellFormat(writableFont)
|
|
||||||
arial12format?.setBorder(jxl.format.Border.ALL, jxl.format.BorderLineStyle.THIN)
|
|
||||||
} catch (exception: WriteException) {
|
|
||||||
Log.e(TAG, "format() e==" + exception.message)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun makeDir(filePath: File) {
|
|
||||||
if (!filePath.parentFile.exists()) {
|
|
||||||
makeDir(filePath.parentFile)
|
|
||||||
}
|
|
||||||
filePath.mkdir()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun deleteByPath(filePath: String) {
|
|
||||||
val file = File(filePath)
|
|
||||||
if (file.exists()) {
|
|
||||||
if (file.isFile || file.isDirectory) {
|
|
||||||
file.delete()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun deleteExistFile(excelFilePath: String) {
|
|
||||||
val file = File(excelFilePath)
|
|
||||||
if (file.exists()) {
|
|
||||||
val files: Array<File> = file.listFiles()
|
|
||||||
for (i in files.indices) {
|
|
||||||
deleteByPath(files[i].absolutePath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun initExcel(
|
|
||||||
allColumNames: Array<String>,
|
|
||||||
filePath: String,
|
|
||||||
sheetName: String
|
|
||||||
) {
|
|
||||||
format()
|
|
||||||
|
|
||||||
var workbook: WritableWorkbook? = null
|
|
||||||
try {
|
|
||||||
val file = File(filePath)
|
|
||||||
if (!file.exists()) {
|
|
||||||
file.createNewFile()
|
|
||||||
}
|
|
||||||
workbook = Workbook.createWorkbook(file)
|
|
||||||
val sheet = workbook.createSheet(sheetName, 0)
|
|
||||||
|
|
||||||
allColumNames.forEachIndexed { index, s ->
|
|
||||||
sheet.addCell(Label(index, 0, s, arial12format))
|
|
||||||
}
|
|
||||||
workbook.write()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(TAG, "initExcel e==" + e.message)
|
|
||||||
} finally {
|
|
||||||
try {
|
|
||||||
workbook?.close()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(TAG, "initExcel e==" + e.message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun writeStringListToExcel(
|
|
||||||
allRowsData: List<List<ICellValue>>,
|
|
||||||
context: Context
|
|
||||||
): Boolean {
|
|
||||||
val fileName = PathUtils.getNowTimeFormat(PathUtils.DATE_TO_STRING_LONG_PATTERN) + ".xls"
|
|
||||||
PathUtils.EXCEL_EXPORT_NAME = fileName
|
|
||||||
val filePath =
|
|
||||||
PathUtils.getUDiskPath() + PathUtils.EXCEL_EXPORT_PATH + PathUtils.EXCEL_EXPORT_NAME
|
|
||||||
deleteExistFile(PathUtils.getUDiskPath() + PathUtils.EXCEL_EXPORT_PATH)
|
|
||||||
makeDir(File(PathUtils.getUDiskPath() + PathUtils.EXCEL_EXPORT_PATH))
|
|
||||||
|
|
||||||
val columns = context.resources.getStringArray(R.array.excel_column)
|
|
||||||
initExcel(columns, filePath, PathUtils.SHEET_NAME) //需要写入权限
|
|
||||||
|
|
||||||
if (PathUtils.isListEmpty(allRowsData) || context == null)
|
|
||||||
return false
|
|
||||||
|
|
||||||
var writebook: WritableWorkbook? = null
|
|
||||||
var inputStream: InputStream? = null
|
|
||||||
try {
|
|
||||||
val setEncode = WorkbookSettings()
|
|
||||||
setEncode.encoding = UTF8_ENCODING
|
|
||||||
inputStream = FileInputStream(File(filePath))
|
|
||||||
val workbook = Workbook.getWorkbook(inputStream)
|
|
||||||
val file = File(filePath)
|
|
||||||
writebook = Workbook.createWorkbook(file, workbook)
|
|
||||||
val sheet = writebook.getSheet(0)
|
|
||||||
for ((row, item) in allRowsData.withIndex()) {
|
|
||||||
item.forEachIndexed { index, cellValue ->
|
|
||||||
sheet.addCell(Label(index, row + 1, cellValue.getValue(), arial12format))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
writebook.write()
|
|
||||||
Log.i(TAG, "Excelel 写入成功")
|
|
||||||
return true
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(TAG, "writeStringListToExcel() e==" + e.message)
|
|
||||||
} finally {
|
|
||||||
writebook?.close()
|
|
||||||
inputStream?.close()
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,52 +0,0 @@
|
|||||||
package com.yinuo.safetywatcher.xls.utils
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.os.Environment
|
|
||||||
import com.yinuo.safetywatcher.utils.LztekUtil
|
|
||||||
import java.text.SimpleDateFormat
|
|
||||||
import java.util.Date
|
|
||||||
import java.util.Locale
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 基础工具类
|
|
||||||
*/
|
|
||||||
object PathUtils {
|
|
||||||
private const val TAG: String = "BaseUtils"
|
|
||||||
const val SHEET_NAME = "表格1"
|
|
||||||
const val EXCEL_EXPORT_PATH = "/ExportExcel/"
|
|
||||||
lateinit var EXCEL_EXPORT_NAME: String
|
|
||||||
const val DATE_TO_STRING_LONG_PATTERN: String = "yyyy_MM_dd_HH_mm_ss"
|
|
||||||
|
|
||||||
fun <T> isListEmpty(list: List<T>?): Boolean {
|
|
||||||
return list == null || list.isEmpty()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取应用中文件存储
|
|
||||||
*/
|
|
||||||
fun getExternalStoragePath(context: Context): String? {
|
|
||||||
return context.getExternalFilesDir(null)?.path
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取U盘存储
|
|
||||||
*/
|
|
||||||
fun getUDiskPath(): String {
|
|
||||||
return LztekUtil.getLztek().usbStoragePath
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getExternalStorageDirectory(context: Context): String? {
|
|
||||||
return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).path
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 外部存储目录
|
|
||||||
*/
|
|
||||||
fun getExternalStorageDirectory(): String? {
|
|
||||||
return Environment.getExternalStorageDirectory().absolutePath
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getNowTimeFormat(dateToStringLongPattern: String): String {
|
|
||||||
return SimpleDateFormat(dateToStringLongPattern, Locale.ROOT).format(Date());
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,30 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:aapt="http://schemas.android.com/aapt"
|
|
||||||
android:width="108dp"
|
|
||||||
android:height="108dp"
|
|
||||||
android:viewportWidth="108"
|
|
||||||
android:viewportHeight="108">
|
|
||||||
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
|
|
||||||
<aapt:attr name="android:fillColor">
|
|
||||||
<gradient
|
|
||||||
android:endX="85.84757"
|
|
||||||
android:endY="92.4963"
|
|
||||||
android:startX="42.9492"
|
|
||||||
android:startY="49.59793"
|
|
||||||
android:type="linear">
|
|
||||||
<item
|
|
||||||
android:color="#44000000"
|
|
||||||
android:offset="0.0" />
|
|
||||||
<item
|
|
||||||
android:color="#00000000"
|
|
||||||
android:offset="1.0" />
|
|
||||||
</gradient>
|
|
||||||
</aapt:attr>
|
|
||||||
</path>
|
|
||||||
<path
|
|
||||||
android:fillColor="#FFFFFF"
|
|
||||||
android:fillType="nonZero"
|
|
||||||
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
|
|
||||||
android:strokeWidth="1"
|
|
||||||
android:strokeColor="#00000000" />
|
|
||||||
</vector>
|
|
@ -1,170 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="108dp"
|
|
||||||
android:height="108dp"
|
|
||||||
android:viewportWidth="108"
|
|
||||||
android:viewportHeight="108">
|
|
||||||
<path
|
|
||||||
android:fillColor="#3DDC84"
|
|
||||||
android:pathData="M0,0h108v108h-108z" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M9,0L9,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,0L19,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M29,0L29,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M39,0L39,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M49,0L49,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M59,0L59,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M69,0L69,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M79,0L79,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M89,0L89,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M99,0L99,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,9L108,9"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,19L108,19"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,29L108,29"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,39L108,39"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,49L108,49"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,59L108,59"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,69L108,69"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,79L108,79"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,89L108,89"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,99L108,99"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,29L89,29"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,39L89,39"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,49L89,49"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,59L89,59"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,69L89,69"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,79L89,79"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M29,19L29,89"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M39,19L39,89"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M49,19L49,89"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M59,19L59,89"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M69,19L69,89"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M79,19L79,89"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
</vector>
|
|
@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<background android:drawable="@drawable/ic_launcher_background" />
|
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
|
||||||
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
|
||||||
</adaptive-icon>
|
|
@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<background android:drawable="@drawable/ic_launcher_background" />
|
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
|
||||||
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
|
||||||
</adaptive-icon>
|
|
Before Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 982 B |
Before Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 5.8 KiB |
Before Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 7.6 KiB |
@ -1,10 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<color name="purple_200">#FFBB86FC</color>
|
|
||||||
<color name="purple_500">#FF6200EE</color>
|
|
||||||
<color name="purple_700">#FF3700B3</color>
|
|
||||||
<color name="teal_200">#FF03DAC5</color>
|
|
||||||
<color name="teal_700">#FF018786</color>
|
|
||||||
<color name="black">#FF000000</color>
|
|
||||||
<color name="white">#FFFFFFFF</color>
|
|
||||||
</resources>
|
|
@ -1,11 +0,0 @@
|
|||||||
<resources>
|
|
||||||
<string name="app_name">app-compose</string>
|
|
||||||
|
|
||||||
|
|
||||||
<string-array name="excel_column">
|
|
||||||
<item>时间</item>
|
|
||||||
<item>属性</item>
|
|
||||||
<item>值</item>
|
|
||||||
<item>单位</item>
|
|
||||||
</string-array>
|
|
||||||
</resources>
|
|
@ -1,5 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
|
|
||||||
<style name="Theme.Easypusher" parent="android:Theme.Material.Light.NoActionBar" />
|
|
||||||
</resources>
|
|
@ -1,279 +0,0 @@
|
|||||||
package com.yinuo.safetywatcher;
|
|
||||||
|
|
||||||
import android.Manifest;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.graphics.SurfaceTexture;
|
|
||||||
import android.media.projection.MediaProjectionManager;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.preference.PreferenceManager;
|
|
||||||
import android.view.TextureView;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.CheckBox;
|
|
||||||
import android.widget.CompoundButton;
|
|
||||||
import android.widget.TextView;
|
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
import androidx.core.app.ActivityCompat;
|
|
||||||
import androidx.lifecycle.Observer;
|
|
||||||
|
|
||||||
import com.yinuo.safetywatcher.xls.SimpleCellValue;
|
|
||||||
import com.yinuo.safetywatcher.xls.utils.ExcelUtils;
|
|
||||||
|
|
||||||
import org.easydarwin.push.MediaStream;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import io.reactivex.Single;
|
|
||||||
import io.reactivex.functions.Consumer;
|
|
||||||
|
|
||||||
public class MainActivity extends AppCompatActivity {
|
|
||||||
|
|
||||||
private static final int REQUEST_CAMERA_PERMISSION = 1000;
|
|
||||||
private static final int REQUEST_MEDIA_PROJECTION = 1001;
|
|
||||||
public static final String HOST = "192.168.51.189";
|
|
||||||
private MediaStream mediaStream;
|
|
||||||
|
|
||||||
private Single<MediaStream> getMediaStream() {
|
|
||||||
Single<MediaStream> single = RxHelper.single(MediaStream.getBindedMediaStream(this, this), mediaStream);
|
|
||||||
if (mediaStream == null) {
|
|
||||||
return single.doOnSuccess(new Consumer<MediaStream>() {
|
|
||||||
@Override
|
|
||||||
public void accept(MediaStream ms) throws Exception {
|
|
||||||
mediaStream = ms;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
return single;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
setContentView(R.layout.activity_main);
|
|
||||||
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE}, 0);
|
|
||||||
|
|
||||||
CheckBox hevc_enable = findViewById(R.id.enable_265);
|
|
||||||
hevc_enable.setChecked(PreferenceManager.getDefaultSharedPreferences(this).getBoolean("try_265_encode", false));
|
|
||||||
hevc_enable.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
|
||||||
@Override
|
|
||||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
|
||||||
PreferenceManager.getDefaultSharedPreferences(MainActivity.this).edit().putBoolean("try_265_encode", isChecked).apply();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 启动服务...
|
|
||||||
Intent intent = new Intent(this, MediaStream.class);
|
|
||||||
startService(intent);
|
|
||||||
|
|
||||||
getMediaStream().subscribe(new Consumer<MediaStream>() {
|
|
||||||
@Override
|
|
||||||
public void accept(final MediaStream ms) throws Exception {
|
|
||||||
ms.observeCameraPreviewResolution(MainActivity.this, new Observer<int[]>() {
|
|
||||||
@Override
|
|
||||||
public void onChanged(@Nullable int[] size) {
|
|
||||||
Toast.makeText(MainActivity.this, "当前摄像头分辨率为:" + size[0] + "*" + size[1], Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
final TextView pushingStateText = findViewById(R.id.pushing_state);
|
|
||||||
final TextView pushingBtn = findViewById(R.id.pushing);
|
|
||||||
ms.observePushingState(MainActivity.this, new Observer<MediaStream.PushingState>() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onChanged(@Nullable MediaStream.PushingState pushingState) {
|
|
||||||
if (pushingState.screenPushing) {
|
|
||||||
pushingStateText.setText("屏幕推送");
|
|
||||||
|
|
||||||
// 更改屏幕推送按钮状态.
|
|
||||||
|
|
||||||
TextView tview = findViewById(R.id.pushing_desktop);
|
|
||||||
if (ms.isScreenPushing()) {
|
|
||||||
tview.setText("取消推送");
|
|
||||||
} else {
|
|
||||||
tview.setText("推送屏幕");
|
|
||||||
}
|
|
||||||
findViewById(R.id.pushing_desktop).setEnabled(true);
|
|
||||||
} else {
|
|
||||||
pushingStateText.setText("推送");
|
|
||||||
if (ms.isCameraPushing()) {
|
|
||||||
pushingBtn.setText("停止");
|
|
||||||
} else {
|
|
||||||
pushingBtn.setText("推送");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pushingStateText.append(":\t" + pushingState.msg);
|
|
||||||
if (pushingState.state > 0) {
|
|
||||||
pushingStateText.append(pushingState.url);
|
|
||||||
pushingStateText.append("\n");
|
|
||||||
if ("avc".equals(pushingState.videoCodec)) {
|
|
||||||
pushingStateText.append("视频编码方式:" + "H264硬编码");
|
|
||||||
} else if ("hevc".equals(pushingState.videoCodec)) {
|
|
||||||
pushingStateText.append("视频编码方式:" + "H265硬编码");
|
|
||||||
} else if ("x264".equals(pushingState.videoCodec)) {
|
|
||||||
pushingStateText.append("视频编码方式:" + "x264");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
TextureView textureView = findViewById(R.id.texture_view);
|
|
||||||
if (textureView.isAvailable()) {
|
|
||||||
ms.setSurfaceTexture(textureView.getSurfaceTexture());
|
|
||||||
} else {
|
|
||||||
textureView.setSurfaceTextureListener(new SurfaceTextureListenerWrapper() {
|
|
||||||
@Override
|
|
||||||
public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int i, int i1) {
|
|
||||||
ms.setSurfaceTexture(surfaceTexture);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
|
|
||||||
ms.setSurfaceTexture(null);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ActivityCompat.checkSelfPermission(MainActivity.this, android.Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED ||
|
|
||||||
ActivityCompat.checkSelfPermission(MainActivity.this, android.Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
|
|
||||||
ActivityCompat.requestPermissions(MainActivity.this, new String[]{android.Manifest.permission.CAMERA, android.Manifest.permission.RECORD_AUDIO}, REQUEST_CAMERA_PERMISSION);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, new Consumer<Throwable>() {
|
|
||||||
@Override
|
|
||||||
public void accept(Throwable throwable) throws Exception {
|
|
||||||
Toast.makeText(MainActivity.this, "创建服务出错!", Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onPushing(View view) {
|
|
||||||
getMediaStream().subscribe(new Consumer<MediaStream>() {
|
|
||||||
@Override
|
|
||||||
public void accept(MediaStream mediaStream) throws Exception {
|
|
||||||
MediaStream.PushingState state = mediaStream.getPushingState();
|
|
||||||
if (state != null && state.state > 0) { // 终止推送和预览
|
|
||||||
mediaStream.stopStream();
|
|
||||||
mediaStream.closeCameraPreview();
|
|
||||||
} else { // 启动预览和推送.
|
|
||||||
mediaStream.openCameraPreview();
|
|
||||||
String id = PreferenceManager.getDefaultSharedPreferences(MainActivity.this).getString("caemra-id", null);
|
|
||||||
if (id == null) {
|
|
||||||
double v = Math.random() * 1000;
|
|
||||||
id = "c_" + (int) v;
|
|
||||||
PreferenceManager.getDefaultSharedPreferences(MainActivity.this).edit().putString("caemra-id", id).apply();
|
|
||||||
}
|
|
||||||
mediaStream.startStream(HOST, "554", id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onRequestPermissionsResult(int requestCode,
|
|
||||||
String permissions[], int[] grantResults) {
|
|
||||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
|
||||||
switch (requestCode) {
|
|
||||||
case REQUEST_CAMERA_PERMISSION: {
|
|
||||||
if (grantResults.length > 1
|
|
||||||
&& grantResults[0] == PackageManager.PERMISSION_GRANTED && grantResults[1] == PackageManager.PERMISSION_GRANTED) {
|
|
||||||
getMediaStream().subscribe(new Consumer<MediaStream>() {
|
|
||||||
@Override
|
|
||||||
public void accept(MediaStream mediaStream) throws Exception {
|
|
||||||
mediaStream.notifyPermissionGranted();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 推送屏幕.
|
|
||||||
public void onPushScreen(final View view) {
|
|
||||||
getMediaStream().subscribe(new Consumer<MediaStream>() {
|
|
||||||
@Override
|
|
||||||
public void accept(MediaStream mediaStream) {
|
|
||||||
if (mediaStream.isScreenPushing()) { // 正在推送,那取消推送。
|
|
||||||
// 取消推送。
|
|
||||||
mediaStream.stopPushScreen();
|
|
||||||
} else { // 没在推送,那启动推送。
|
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { // lollipop 以前版本不支持。
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
MediaProjectionManager mMpMngr = (MediaProjectionManager) getApplicationContext().getSystemService(MEDIA_PROJECTION_SERVICE);
|
|
||||||
startActivityForResult(mMpMngr.createScreenCaptureIntent(), REQUEST_MEDIA_PROJECTION);
|
|
||||||
// 防止点多次.
|
|
||||||
view.setEnabled(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onActivityResult(int requestCode, final int resultCode, final Intent data) {
|
|
||||||
super.onActivityResult(requestCode, resultCode, data);
|
|
||||||
if (requestCode == REQUEST_MEDIA_PROJECTION) {
|
|
||||||
getMediaStream().subscribe(new Consumer<MediaStream>() {
|
|
||||||
@Override
|
|
||||||
public void accept(MediaStream mediaStream) {
|
|
||||||
mediaStream.pushScreen(resultCode, data, HOST, "554", "screen111");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onSwitchCamera(View view) {
|
|
||||||
getMediaStream().subscribe(new Consumer<MediaStream>() {
|
|
||||||
@Override
|
|
||||||
public void accept(MediaStream mediaStream) throws Exception {
|
|
||||||
mediaStream.switchCamera();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onUVCCamera(View view) {
|
|
||||||
Intent intent = new Intent(this, UVCActivity.class);
|
|
||||||
|
|
||||||
|
|
||||||
// Intent intent = new Intent(this, ProVideoActivity.class);
|
|
||||||
// String path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/mvtest.mp4";
|
|
||||||
// intent.putExtra("videoPath", path);
|
|
||||||
startActivity(intent);
|
|
||||||
|
|
||||||
// List<List<SimpleCellValue>> allData = new ArrayList<>();
|
|
||||||
// List<SimpleCellValue> row1 = new ArrayList<>();
|
|
||||||
// row1.add(new SimpleCellValue("5.22"));
|
|
||||||
// row1.add(new SimpleCellValue("1"));
|
|
||||||
// row1.add(new SimpleCellValue("2"));
|
|
||||||
// row1.add(new SimpleCellValue("3"));
|
|
||||||
// List<SimpleCellValue> row2 = new ArrayList<>();
|
|
||||||
// row2.add(new SimpleCellValue("5.23"));
|
|
||||||
// row2.add(new SimpleCellValue("4"));
|
|
||||||
// row2.add(new SimpleCellValue("5"));
|
|
||||||
// row2.add(new SimpleCellValue("6"));
|
|
||||||
//
|
|
||||||
// allData.add(row1);
|
|
||||||
// allData.add(row2);
|
|
||||||
//
|
|
||||||
// ExcelUtils.INSTANCE.writeStringListToExcel(allData, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnSaveRecord(View view) {
|
|
||||||
if (mediaStream.isRecording()) {
|
|
||||||
mediaStream.stopRecord();
|
|
||||||
} else {
|
|
||||||
mediaStream.startRecord(this.getCacheDir().getAbsolutePath(), 10 * 1000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,47 +0,0 @@
|
|||||||
package com.yinuo.safetywatcher;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
import org.easydarwin.util.AbstractSubscriber;
|
|
||||||
import org.reactivestreams.Publisher;
|
|
||||||
|
|
||||||
import io.reactivex.Single;
|
|
||||||
import io.reactivex.subjects.PublishSubject;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by apple on 2017/12/22.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class RxHelper {
|
|
||||||
static boolean IGNORE_ERROR = false;
|
|
||||||
|
|
||||||
public static <T> Single<T> single(@NonNull Publisher<T> t, @Nullable T defaultValueIfNotNull){
|
|
||||||
if (defaultValueIfNotNull != null) return Single.just(defaultValueIfNotNull);
|
|
||||||
final PublishSubject sub = PublishSubject.create();
|
|
||||||
t.subscribe(new AbstractSubscriber<T>() {
|
|
||||||
@Override
|
|
||||||
public void onNext(T t) {
|
|
||||||
super.onNext(t);
|
|
||||||
sub.onNext(t);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(Throwable t) {
|
|
||||||
if (IGNORE_ERROR) {
|
|
||||||
super.onError(t);
|
|
||||||
sub.onComplete();
|
|
||||||
}else {
|
|
||||||
sub.onError(t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onComplete() {
|
|
||||||
super.onComplete();
|
|
||||||
sub.onComplete();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return sub.firstOrError();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,27 +0,0 @@
|
|||||||
package com.yinuo.safetywatcher;
|
|
||||||
|
|
||||||
import android.graphics.SurfaceTexture;
|
|
||||||
import android.view.TextureView;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by apple on 2017/9/11.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public abstract class SurfaceTextureListenerWrapper implements TextureView.SurfaceTextureListener{
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int i, int i1) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,208 +0,0 @@
|
|||||||
package com.yinuo.safetywatcher;
|
|
||||||
|
|
||||||
import androidx.lifecycle.Observer;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.graphics.SurfaceTexture;
|
|
||||||
import android.os.Environment;
|
|
||||||
import android.preference.PreferenceManager;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.core.app.ActivityCompat;
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.TextureView;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.TextView;
|
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import org.easydarwin.push.MediaStream;
|
|
||||||
|
|
||||||
import io.reactivex.Single;
|
|
||||||
import io.reactivex.functions.Consumer;
|
|
||||||
|
|
||||||
public class UVCActivity extends AppCompatActivity {
|
|
||||||
private MediaStream mediaStream;
|
|
||||||
|
|
||||||
private static final int REQUEST_CAMERA_PERMISSION = 1000;
|
|
||||||
private Single<MediaStream> getMediaStream() {
|
|
||||||
Single<MediaStream> single = RxHelper.single(MediaStream.getBindedMediaStream(this, this), mediaStream);
|
|
||||||
if (mediaStream == null) {
|
|
||||||
return single.doOnSuccess(new Consumer<MediaStream>() {
|
|
||||||
@Override
|
|
||||||
public void accept(MediaStream ms) throws Exception {
|
|
||||||
mediaStream = ms;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
return single;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
setContentView(R.layout.activity_uvc);
|
|
||||||
|
|
||||||
// 启动服务...
|
|
||||||
Intent intent = new Intent(this, MediaStream.class);
|
|
||||||
startService(intent);
|
|
||||||
|
|
||||||
getMediaStream().subscribe(new Consumer<MediaStream>() {
|
|
||||||
@Override
|
|
||||||
public void accept(final MediaStream ms) throws Exception {
|
|
||||||
|
|
||||||
final TextView pushingStateText = findViewById(R.id.pushing_state);
|
|
||||||
final TextView pushingBtn = findViewById(R.id.pushing);
|
|
||||||
ms.observePushingState(UVCActivity.this, new Observer<MediaStream.PushingState>() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onChanged(@Nullable MediaStream.PushingState pushingState) {
|
|
||||||
if (pushingState.screenPushing) {
|
|
||||||
pushingStateText.setText("屏幕推送");
|
|
||||||
} else {
|
|
||||||
pushingStateText.setText("推送");
|
|
||||||
|
|
||||||
if (pushingState.state > 0) {
|
|
||||||
pushingBtn.setText("停止");
|
|
||||||
} else {
|
|
||||||
pushingBtn.setText("推送");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
pushingStateText.append(":\t" + pushingState.msg);
|
|
||||||
if (pushingState.state > 0) {
|
|
||||||
pushingStateText.append(pushingState.url);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
TextureView textureView = findViewById(R.id.texture_view);
|
|
||||||
textureView.setSurfaceTextureListener(new SurfaceTextureListenerWrapper() {
|
|
||||||
@Override
|
|
||||||
public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int i, int i1) {
|
|
||||||
ms.setSurfaceTexture(surfaceTexture);
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
|
|
||||||
ms.setSurfaceTexture(null);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
if (ActivityCompat.checkSelfPermission(UVCActivity.this, android.Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED ||
|
|
||||||
ActivityCompat.checkSelfPermission(UVCActivity.this, android.Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
|
|
||||||
ActivityCompat.requestPermissions(UVCActivity.this, new String[]{android.Manifest.permission.CAMERA, android.Manifest.permission.RECORD_AUDIO}, REQUEST_CAMERA_PERMISSION);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, new Consumer<Throwable>() {
|
|
||||||
@Override
|
|
||||||
public void accept(Throwable throwable) throws Exception {
|
|
||||||
Toast.makeText(UVCActivity.this, "创建服务出错!", Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// 权限获取到了.
|
|
||||||
@Override
|
|
||||||
public void onRequestPermissionsResult(int requestCode,
|
|
||||||
String permissions[], int[] grantResults) {
|
|
||||||
switch (requestCode) {
|
|
||||||
case REQUEST_CAMERA_PERMISSION: {
|
|
||||||
if (grantResults.length > 1
|
|
||||||
&& grantResults[0] == PackageManager.PERMISSION_GRANTED && grantResults[1] == PackageManager.PERMISSION_GRANTED) {
|
|
||||||
getMediaStream().subscribe(new Consumer<MediaStream>() {
|
|
||||||
@Override
|
|
||||||
public void accept(MediaStream mediaStream) throws Exception {
|
|
||||||
mediaStream.notifyPermissionGranted();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// 没有获取到权限,退出....
|
|
||||||
Intent intent = new Intent(this, MediaStream.class);
|
|
||||||
stopService(intent);
|
|
||||||
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onPush(View view) {
|
|
||||||
|
|
||||||
// 异步获取到MediaStream对象.
|
|
||||||
getMediaStream().subscribe(new Consumer<MediaStream>() {
|
|
||||||
@Override
|
|
||||||
public void accept(final MediaStream mediaStream) throws Exception {
|
|
||||||
// 判断当前的推送状态.
|
|
||||||
MediaStream.PushingState state = mediaStream.getPushingState();
|
|
||||||
if (state != null && state.state > 0) { // 当前正在推送,那终止推送和预览
|
|
||||||
mediaStream.stopStream();
|
|
||||||
mediaStream.closeCameraPreview();
|
|
||||||
}else{
|
|
||||||
// switch 0表示后置,1表示前置,2表示UVC摄像头
|
|
||||||
RxHelper.single(mediaStream.switchCamera(2), null).subscribe(new Consumer<Object>() {
|
|
||||||
@Override
|
|
||||||
public void accept(Object o) throws Exception {
|
|
||||||
String id = PreferenceManager.getDefaultSharedPreferences(UVCActivity.this).getString("uvc-id", null);
|
|
||||||
if (id == null) {
|
|
||||||
double v = Math.random() * 1000;
|
|
||||||
id = "uvc_" + (int) v;
|
|
||||||
PreferenceManager.getDefaultSharedPreferences(UVCActivity.this).edit().putString("uvc-id", id).apply();
|
|
||||||
}
|
|
||||||
mediaStream.startStream("cloud.easydarwin.org", "554", id);
|
|
||||||
}
|
|
||||||
}, new Consumer<Throwable>() {
|
|
||||||
@Override
|
|
||||||
public void accept(final Throwable t) throws Exception {
|
|
||||||
t.printStackTrace();
|
|
||||||
runOnUiThread(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
Toast.makeText(UVCActivity.this, "UVC摄像头启动失败.." + t.getMessage(), Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onRecord(View view) { // 开始或结束录像.
|
|
||||||
final TextView txt = (TextView) view;
|
|
||||||
getMediaStream().subscribe(new Consumer<MediaStream>() {
|
|
||||||
@Override
|
|
||||||
public void accept(MediaStream mediaStream) throws Exception {
|
|
||||||
if (mediaStream.isRecording()){ // 如果正在录像,那停止.
|
|
||||||
mediaStream.stopRecord();
|
|
||||||
txt.setText("录像");
|
|
||||||
}else { // 没在录像,开始录像...
|
|
||||||
// 表示最大录像时长为30秒,30秒后如果没有停止,会生成一个新文件.依次类推...
|
|
||||||
// 文件格式为test_uvc_0.mp4,test_uvc_1.mp4,test_uvc_2.mp4,test_uvc_3.mp4
|
|
||||||
String path = getExternalFilesDir(Environment.DIRECTORY_MOVIES) + "/test_uvc.mp4";
|
|
||||||
mediaStream.startRecord(path, 30000);
|
|
||||||
|
|
||||||
final TextView pushingStateText = findViewById(R.id.pushing_state);
|
|
||||||
pushingStateText.append("\n录像地址:" + path);
|
|
||||||
txt.setText("停止");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onQuit(View view) { // 退出
|
|
||||||
finish();
|
|
||||||
|
|
||||||
// 终止服务...
|
|
||||||
Intent intent = new Intent(this, MediaStream.class);
|
|
||||||
stopService(intent);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onBackground(View view) { // 后台
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,38 +1,4 @@
|
|||||||
<?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="org.easydarwin.easypusher">
|
package="org.easydarwin.easypusher">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
|
||||||
<uses-permission android:name="android.permission.CAMERA" />
|
|
||||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
|
||||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
|
|
||||||
|
|
||||||
<uses-feature android:name="android.hardware.camera" />
|
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
|
||||||
<uses-feature
|
|
||||||
android:name="android.hardware.usb.host"
|
|
||||||
android:required="true" />
|
|
||||||
<uses-feature
|
|
||||||
android:glEsVersion="0x00020000"
|
|
||||||
android:required="true" />
|
|
||||||
|
|
||||||
<application>
|
|
||||||
<service
|
|
||||||
android:name="org.easydarwin.push.PushScreenService"
|
|
||||||
android:enabled="true" />
|
|
||||||
|
|
||||||
<service
|
|
||||||
android:name="org.easydarwin.push.UVCCameraService"
|
|
||||||
android:enabled="true" />
|
|
||||||
|
|
||||||
|
|
||||||
<service
|
|
||||||
android:name="org.easydarwin.push.MediaStream"
|
|
||||||
android:enabled="true" />
|
|
||||||
|
|
||||||
</application>
|
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
@ -1,263 +0,0 @@
|
|||||||
package org.easydarwin.push;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.media.MediaCodec;
|
|
||||||
import android.media.MediaFormat;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.preference.PreferenceManager;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import org.easydarwin.easypusher.BuildConfig;
|
|
||||||
import org.easydarwin.muxer.EasyMuxer;
|
|
||||||
import org.easydarwin.sw.JNIUtil;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
|
|
||||||
import static android.media.MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar;
|
|
||||||
import static android.media.MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar;
|
|
||||||
import static android.media.MediaCodecInfo.CodecCapabilities.COLOR_TI_FormatYUV420PackedSemiPlanar;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by apple on 2017/5/13.
|
|
||||||
*/
|
|
||||||
public class HWConsumer extends Thread implements VideoConsumer {
|
|
||||||
private static final String TAG = "Pusher";
|
|
||||||
private final MediaStream.CodecInfo info;
|
|
||||||
public EasyMuxer mMuxer;
|
|
||||||
private final Context mContext;
|
|
||||||
private final Pusher mPusher;
|
|
||||||
private int mHeight;
|
|
||||||
private int mWidth;
|
|
||||||
private MediaCodec mMediaCodec;
|
|
||||||
private ByteBuffer[] inputBuffers;
|
|
||||||
private ByteBuffer[] outputBuffers;
|
|
||||||
private volatile boolean mVideoStarted;
|
|
||||||
private MediaFormat newFormat;
|
|
||||||
|
|
||||||
public HWConsumer(Context context, Pusher pusher, MediaStream.CodecInfo info) {
|
|
||||||
mContext = context;
|
|
||||||
mPusher = pusher;
|
|
||||||
this.info = info;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onVideoStart(int width, int height) throws IOException {
|
|
||||||
newFormat = null;
|
|
||||||
this.mWidth = width;
|
|
||||||
this.mHeight = height;
|
|
||||||
startMediaCodec();
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP + 1) {
|
|
||||||
inputBuffers = outputBuffers = null;
|
|
||||||
} else {
|
|
||||||
inputBuffers = mMediaCodec.getInputBuffers();
|
|
||||||
outputBuffers = mMediaCodec.getOutputBuffers();
|
|
||||||
}
|
|
||||||
start();
|
|
||||||
mVideoStarted = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
final int millisPerframe = 1000 / 20;
|
|
||||||
long lastPush = 0;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int onVideo(byte[] data, int format) {
|
|
||||||
if (!mVideoStarted) return 0;
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (lastPush == 0) {
|
|
||||||
lastPush = System.currentTimeMillis();
|
|
||||||
}
|
|
||||||
long time = System.currentTimeMillis() - lastPush;
|
|
||||||
if (time >= 0) {
|
|
||||||
time = millisPerframe - time;
|
|
||||||
if (time > 0) Thread.sleep(time / 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (info.mColorFormat == COLOR_FormatYUV420SemiPlanar) {
|
|
||||||
JNIUtil.yuvConvert(data, mWidth, mHeight, 6);
|
|
||||||
} else if (info.mColorFormat == COLOR_TI_FormatYUV420PackedSemiPlanar) {
|
|
||||||
JNIUtil.yuvConvert(data, mWidth, mHeight, 6);
|
|
||||||
} else if (info.mColorFormat == COLOR_FormatYUV420Planar) {
|
|
||||||
JNIUtil.yuvConvert(data, mWidth, mHeight, 5);
|
|
||||||
} else {
|
|
||||||
JNIUtil.yuvConvert(data, mWidth, mHeight, 5);
|
|
||||||
}
|
|
||||||
int bufferIndex = mMediaCodec.dequeueInputBuffer(0);
|
|
||||||
if (bufferIndex >= 0) {
|
|
||||||
ByteBuffer buffer = null;
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
|
||||||
buffer = mMediaCodec.getInputBuffer(bufferIndex);
|
|
||||||
} else {
|
|
||||||
buffer = inputBuffers[bufferIndex];
|
|
||||||
}
|
|
||||||
buffer.clear();
|
|
||||||
buffer.put(data);
|
|
||||||
buffer.clear();
|
|
||||||
mMediaCodec.queueInputBuffer(bufferIndex, 0, data.length, System.nanoTime() / 1000, MediaCodec.BUFFER_FLAG_KEY_FRAME);
|
|
||||||
}
|
|
||||||
if (time > 0) Thread.sleep(time / 2);
|
|
||||||
lastPush = System.currentTimeMillis();
|
|
||||||
} catch (InterruptedException ex) {
|
|
||||||
ex.printStackTrace();
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
|
|
||||||
int outputBufferIndex = 0;
|
|
||||||
byte[] mPpsSps = new byte[0];
|
|
||||||
byte[] h264 = new byte[mWidth * mHeight];
|
|
||||||
do {
|
|
||||||
outputBufferIndex = mMediaCodec.dequeueOutputBuffer(bufferInfo, 10000);
|
|
||||||
if (outputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
|
|
||||||
// no output available yet
|
|
||||||
} else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
|
|
||||||
// not expected for an encoder
|
|
||||||
outputBuffers = mMediaCodec.getOutputBuffers();
|
|
||||||
} else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
|
|
||||||
synchronized (HWConsumer.this) {
|
|
||||||
newFormat = mMediaCodec.getOutputFormat();
|
|
||||||
EasyMuxer muxer = mMuxer;
|
|
||||||
if (muxer != null) {
|
|
||||||
// should happen before receiving buffers, and should only happen once
|
|
||||||
|
|
||||||
muxer.addTrack(newFormat, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (outputBufferIndex < 0) {
|
|
||||||
// let's ignore it
|
|
||||||
} else {
|
|
||||||
ByteBuffer outputBuffer;
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
|
||||||
outputBuffer = mMediaCodec.getOutputBuffer(outputBufferIndex);
|
|
||||||
} else {
|
|
||||||
outputBuffer = outputBuffers[outputBufferIndex];
|
|
||||||
}
|
|
||||||
outputBuffer.position(bufferInfo.offset);
|
|
||||||
outputBuffer.limit(bufferInfo.offset + bufferInfo.size);
|
|
||||||
EasyMuxer muxer = mMuxer;
|
|
||||||
if (muxer != null) {
|
|
||||||
muxer.pumpStream(outputBuffer, bufferInfo, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean sync = false;
|
|
||||||
if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {// sps
|
|
||||||
sync = (bufferInfo.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) != 0;
|
|
||||||
if (!sync) {
|
|
||||||
byte[] temp = new byte[bufferInfo.size];
|
|
||||||
outputBuffer.get(temp);
|
|
||||||
mPpsSps = temp;
|
|
||||||
mMediaCodec.releaseOutputBuffer(outputBufferIndex, false);
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
mPpsSps = new byte[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sync |= (bufferInfo.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) != 0;
|
|
||||||
int len = mPpsSps.length + bufferInfo.size;
|
|
||||||
if (len > h264.length) {
|
|
||||||
h264 = new byte[len];
|
|
||||||
}
|
|
||||||
if (sync) {
|
|
||||||
System.arraycopy(mPpsSps, 0, h264, 0, mPpsSps.length);
|
|
||||||
outputBuffer.get(h264, mPpsSps.length, bufferInfo.size);
|
|
||||||
mPusher.push(h264, 0, mPpsSps.length + bufferInfo.size, bufferInfo.presentationTimeUs / 1000, 1);
|
|
||||||
if (BuildConfig.DEBUG)
|
|
||||||
Log.i(TAG, String.format("push i video stamp:%d", bufferInfo.presentationTimeUs / 1000));
|
|
||||||
} else {
|
|
||||||
outputBuffer.get(h264, 0, bufferInfo.size);
|
|
||||||
mPusher.push(h264, 0, bufferInfo.size, bufferInfo.presentationTimeUs / 1000, 1);
|
|
||||||
if (BuildConfig.DEBUG)
|
|
||||||
Log.i(TAG, String.format("push video stamp:%d", bufferInfo.presentationTimeUs / 1000));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
mMediaCodec.releaseOutputBuffer(outputBufferIndex, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
while (mVideoStarted);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onVideoStop() {
|
|
||||||
do {
|
|
||||||
newFormat = null;
|
|
||||||
mVideoStarted = false;
|
|
||||||
try {
|
|
||||||
join();
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
} while (isAlive());
|
|
||||||
if (mMediaCodec != null) {
|
|
||||||
stopMediaCodec();
|
|
||||||
mMediaCodec = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void setMuxer(EasyMuxer muxer) {
|
|
||||||
if (muxer != null) {
|
|
||||||
if (newFormat != null)
|
|
||||||
muxer.addTrack(newFormat, true);
|
|
||||||
}
|
|
||||||
mMuxer = muxer;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 初始化编码器
|
|
||||||
*/
|
|
||||||
private void startMediaCodec() throws IOException {
|
|
||||||
/*
|
|
||||||
SD (Low quality) SD (High quality) HD 720p
|
|
||||||
1 HD 1080p
|
|
||||||
1
|
|
||||||
Video resolution 320 x 240 px 720 x 480 px 1280 x 720 px 1920 x 1080 px
|
|
||||||
Video frame rate 20 fps 30 fps 30 fps 30 fps
|
|
||||||
Video bitrate 384 Kbps 2 Mbps 4 Mbps 10 Mbps
|
|
||||||
*/
|
|
||||||
int framerate = 20;
|
|
||||||
// if (width == 640 || height == 640) {
|
|
||||||
// bitrate = 2000000;
|
|
||||||
// } else if (width == 1280 || height == 1280) {
|
|
||||||
// bitrate = 4000000;
|
|
||||||
// } else {
|
|
||||||
// bitrate = 2 * width * height;
|
|
||||||
// }
|
|
||||||
|
|
||||||
int bitrate = (int) (mWidth * mHeight * 20 * 2 * 0.05f);
|
|
||||||
if (mWidth >= 1920 || mHeight >= 1920) bitrate *= 0.3;
|
|
||||||
else if (mWidth >= 1280 || mHeight >= 1280) bitrate *= 0.4;
|
|
||||||
else if (mWidth >= 720 || mHeight >= 720) bitrate *= 0.6;
|
|
||||||
mMediaCodec = MediaCodec.createByCodecName(info.mName);
|
|
||||||
MediaFormat mediaFormat = MediaFormat.createVideoFormat(info.mime, mWidth, mHeight);
|
|
||||||
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
|
|
||||||
mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, framerate);
|
|
||||||
mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, info.mColorFormat);
|
|
||||||
mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
|
|
||||||
mMediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
|
|
||||||
mMediaCodec.start();
|
|
||||||
|
|
||||||
Bundle params = new Bundle();
|
|
||||||
params.putInt(MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME, 0);
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
|
||||||
mMediaCodec.setParameters(params);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 停止编码并释放编码资源占用
|
|
||||||
*/
|
|
||||||
private void stopMediaCodec() {
|
|
||||||
mMediaCodec.stop();
|
|
||||||
mMediaCodec.release();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,371 +0,0 @@
|
|||||||
package org.easydarwin.push;
|
|
||||||
|
|
||||||
import android.annotation.TargetApi;
|
|
||||||
import android.app.Application;
|
|
||||||
import android.app.Notification;
|
|
||||||
import android.app.PendingIntent;
|
|
||||||
import android.app.Service;
|
|
||||||
import android.content.BroadcastReceiver;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.IntentFilter;
|
|
||||||
import android.hardware.display.DisplayManager;
|
|
||||||
import android.hardware.display.VirtualDisplay;
|
|
||||||
import android.media.MediaCodec;
|
|
||||||
import android.media.MediaCodecInfo;
|
|
||||||
import android.media.MediaFormat;
|
|
||||||
import android.media.projection.MediaProjection;
|
|
||||||
import android.media.projection.MediaProjectionManager;
|
|
||||||
import android.os.Binder;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Environment;
|
|
||||||
import android.os.IBinder;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.annotation.RequiresApi;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.util.DisplayMetrics;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.view.Surface;
|
|
||||||
import android.view.WindowManager;
|
|
||||||
|
|
||||||
import org.easydarwin.easypusher.BuildConfig;
|
|
||||||
import org.easydarwin.easypusher.R;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
|
|
||||||
import static android.app.PendingIntent.FLAG_CANCEL_CURRENT;
|
|
||||||
|
|
||||||
|
|
||||||
public class PushScreenService extends Service {
|
|
||||||
|
|
||||||
private static final String TAG = "RService";
|
|
||||||
public static final String ACTION_CLOSE_PUSHING_SCREEN = "ACTION_CLOSE_PUSHING_SCREEN";
|
|
||||||
private String mVideoPath;
|
|
||||||
private MediaProjectionManager mMpmngr;
|
|
||||||
private MediaProjection mMpj;
|
|
||||||
private VirtualDisplay mVirtualDisplay;
|
|
||||||
private int windowWidth;
|
|
||||||
private int windowHeight;
|
|
||||||
private int screenDensity;
|
|
||||||
|
|
||||||
private Surface mSurface;
|
|
||||||
private MediaCodec mMediaCodec;
|
|
||||||
|
|
||||||
private WindowManager wm;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
MediaStream.CodecInfo info = new MediaStream.CodecInfo();
|
|
||||||
private MediaCodec.BufferInfo mBufferInfo = new MediaCodec.BufferInfo();
|
|
||||||
|
|
||||||
private Thread mPushThread;
|
|
||||||
private byte[] mPpsSps;
|
|
||||||
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
|
|
||||||
@Override
|
|
||||||
public void onReceive(Context context, Intent intent) {
|
|
||||||
Application app = (Application) context.getApplicationContext();
|
|
||||||
MediaStream.stopPushScreen(app);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
private final Pusher mEasyPusher = new EasyPusher();
|
|
||||||
private String ip;
|
|
||||||
private String port;
|
|
||||||
private String id;
|
|
||||||
private MediaStream.PushingScreenLiveData liveData;
|
|
||||||
|
|
||||||
|
|
||||||
public class MyBinder extends Binder
|
|
||||||
{
|
|
||||||
public PushScreenService getService(){
|
|
||||||
return PushScreenService.this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MyBinder binder = new MyBinder();
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public IBinder onBind(Intent intent) {
|
|
||||||
return binder;
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
|
||||||
@Override
|
|
||||||
public void onCreate() {
|
|
||||||
super.onCreate();
|
|
||||||
mMpmngr = (MediaProjectionManager) getApplicationContext().getSystemService(MEDIA_PROJECTION_SERVICE);
|
|
||||||
createEnvironment();
|
|
||||||
|
|
||||||
registerReceiver(mReceiver,new IntentFilter(ACTION_CLOSE_PUSHING_SCREEN));
|
|
||||||
}
|
|
||||||
|
|
||||||
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
|
|
||||||
private void configureMedia() throws IOException {
|
|
||||||
MediaStream.initEncoder(this, info);
|
|
||||||
if (TextUtils.isEmpty(info.mName) && info.mColorFormat == 0){
|
|
||||||
throw new IOException("media codec init error");
|
|
||||||
}
|
|
||||||
MediaFormat mediaFormat = MediaFormat.createVideoFormat(info.mime, windowWidth, windowHeight);
|
|
||||||
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 1200000);
|
|
||||||
mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 25);
|
|
||||||
mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
|
|
||||||
mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
|
|
||||||
mediaFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
|
|
||||||
mediaFormat.setInteger(MediaFormat.KEY_CAPTURE_RATE, 25);
|
|
||||||
mediaFormat.setInteger(MediaFormat.KEY_REPEAT_PREVIOUS_FRAME_AFTER, 1000000);
|
|
||||||
mMediaCodec = MediaCodec.createByCodecName(info.mName);
|
|
||||||
mMediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
|
|
||||||
mSurface = mMediaCodec.createInputSurface();
|
|
||||||
mMediaCodec.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void createEnvironment() {
|
|
||||||
mVideoPath = Environment.getExternalStorageDirectory().getPath() + "/";
|
|
||||||
wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
|
|
||||||
windowWidth = wm.getDefaultDisplay().getWidth();
|
|
||||||
windowHeight = wm.getDefaultDisplay().getHeight();
|
|
||||||
DisplayMetrics displayMetrics = new DisplayMetrics();
|
|
||||||
wm.getDefaultDisplay().getMetrics(displayMetrics);
|
|
||||||
screenDensity = displayMetrics.densityDpi;
|
|
||||||
|
|
||||||
while (windowWidth > 480){
|
|
||||||
windowWidth /= 2;
|
|
||||||
windowHeight /=2;
|
|
||||||
}
|
|
||||||
|
|
||||||
windowWidth /= 16;
|
|
||||||
windowWidth *= 16;
|
|
||||||
|
|
||||||
|
|
||||||
windowHeight /= 16;
|
|
||||||
windowHeight *= 16;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void startPush() {
|
|
||||||
// liveData.postValue(new MediaStream.PushingState(0, "未开始", true));
|
|
||||||
mPushThread = new Thread(){
|
|
||||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
|
|
||||||
startForeground(111, new Notification.Builder(PushScreenService.this).setContentTitle(getString(R.string.screen_pushing))
|
|
||||||
.setSmallIcon(R.drawable.ic_pusher_screen_pushing)
|
|
||||||
.addAction(new Notification.Action(R.drawable.ic_close_pushing_screen, "关闭",
|
|
||||||
PendingIntent.getBroadcast(getApplicationContext(), 10000, new Intent(ACTION_CLOSE_PUSHING_SCREEN), FLAG_CANCEL_CURRENT))).build());
|
|
||||||
|
|
||||||
final String url = String.format("rtsp://%s:%s/%s.sdp", ip, port, id);
|
|
||||||
InitCallback _callback = new InitCallback() {
|
|
||||||
@Override
|
|
||||||
public void onCallback(int code) {
|
|
||||||
String msg = "";
|
|
||||||
switch (code) {
|
|
||||||
case EasyPusher.OnInitPusherCallback.CODE.EASY_ACTIVATE_INVALID_KEY:
|
|
||||||
msg = ("无效Key");
|
|
||||||
break;
|
|
||||||
case EasyPusher.OnInitPusherCallback.CODE.EASY_ACTIVATE_SUCCESS:
|
|
||||||
msg = ("未开始");
|
|
||||||
break;
|
|
||||||
case EasyPusher.OnInitPusherCallback.CODE.EASY_PUSH_STATE_CONNECTING:
|
|
||||||
msg = ("连接中");
|
|
||||||
break;
|
|
||||||
case EasyPusher.OnInitPusherCallback.CODE.EASY_PUSH_STATE_CONNECTED:
|
|
||||||
msg = ("连接成功");
|
|
||||||
break;
|
|
||||||
case EasyPusher.OnInitPusherCallback.CODE.EASY_PUSH_STATE_CONNECT_FAILED:
|
|
||||||
msg = ("连接失败");
|
|
||||||
break;
|
|
||||||
case EasyPusher.OnInitPusherCallback.CODE.EASY_PUSH_STATE_CONNECT_ABORT:
|
|
||||||
msg = ("连接异常中断");
|
|
||||||
break;
|
|
||||||
case EasyPusher.OnInitPusherCallback.CODE.EASY_PUSH_STATE_PUSHING:
|
|
||||||
msg = ("推流中");
|
|
||||||
break;
|
|
||||||
case EasyPusher.OnInitPusherCallback.CODE.EASY_PUSH_STATE_DISCONNECTED:
|
|
||||||
msg = ("断开连接");
|
|
||||||
break;
|
|
||||||
case EasyPusher.OnInitPusherCallback.CODE.EASY_ACTIVATE_PLATFORM_ERR:
|
|
||||||
msg = ("平台不匹配");
|
|
||||||
break;
|
|
||||||
case EasyPusher.OnInitPusherCallback.CODE.EASY_ACTIVATE_COMPANY_ID_LEN_ERR:
|
|
||||||
msg = ("授权使用商不匹配");
|
|
||||||
break;
|
|
||||||
case EasyPusher.OnInitPusherCallback.CODE.EASY_ACTIVATE_PROCESS_NAME_LEN_ERR:
|
|
||||||
msg = ("进程名称长度不匹配");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
liveData.postValue(new MediaStream.PushingState(url, code, msg, true));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// startStream(ip, port, id, _callback);
|
|
||||||
mEasyPusher.initPush( getApplicationContext(), _callback);
|
|
||||||
MediaStream.PushingState.sCodec = (info.hevcEncode ? "hevc":"avc");
|
|
||||||
mEasyPusher.setMediaInfo(info.hevcEncode ? Pusher.Codec.EASY_SDK_VIDEO_CODEC_H265:Pusher.Codec.EASY_SDK_VIDEO_CODEC_H264, 25, Pusher.Codec.EASY_SDK_AUDIO_CODEC_AAC, 1, 8000, 16);
|
|
||||||
mEasyPusher.start(ip, port, String.format("%s.sdp", id), Pusher.TransType.EASY_RTP_OVER_TCP);
|
|
||||||
try {
|
|
||||||
byte[] h264 = new byte[102400];
|
|
||||||
while (mPushThread != null) {
|
|
||||||
int index = mMediaCodec.dequeueOutputBuffer(mBufferInfo, 10000);
|
|
||||||
Log.d(TAG, "dequeue output buffer index=" + index);
|
|
||||||
|
|
||||||
if (index == MediaCodec.INFO_TRY_AGAIN_LATER) {//请求超时
|
|
||||||
try {
|
|
||||||
// wait 10ms
|
|
||||||
Thread.sleep(10);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
}
|
|
||||||
} else if (index >= 0) {//有效输出
|
|
||||||
ByteBuffer outputBuffer = mMediaCodec.getOutputBuffer(index);
|
|
||||||
outputBuffer.position(mBufferInfo.offset);
|
|
||||||
outputBuffer.limit(mBufferInfo.offset + mBufferInfo.size);
|
|
||||||
|
|
||||||
|
|
||||||
boolean sync = false;
|
|
||||||
if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {// sps
|
|
||||||
sync = (mBufferInfo.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) != 0;
|
|
||||||
if (!sync) {
|
|
||||||
byte[] temp = new byte[mBufferInfo.size];
|
|
||||||
outputBuffer.get(temp);
|
|
||||||
mPpsSps = temp;
|
|
||||||
mMediaCodec.releaseOutputBuffer(index, false);
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
mPpsSps = new byte[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sync |= (mBufferInfo.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) != 0;
|
|
||||||
int len = mPpsSps.length + mBufferInfo.size;
|
|
||||||
if (len > h264.length) {
|
|
||||||
h264 = new byte[len];
|
|
||||||
}
|
|
||||||
if (sync) {
|
|
||||||
System.arraycopy(mPpsSps, 0, h264, 0, mPpsSps.length);
|
|
||||||
outputBuffer.get(h264, mPpsSps.length, mBufferInfo.size);
|
|
||||||
mEasyPusher.push(h264, 0, mPpsSps.length + mBufferInfo.size, mBufferInfo.presentationTimeUs / 1000, 1);
|
|
||||||
if (BuildConfig.DEBUG)
|
|
||||||
Log.i(TAG, String.format("push i video stamp:%d", mBufferInfo.presentationTimeUs / 1000));
|
|
||||||
} else {
|
|
||||||
outputBuffer.get(h264, 0, mBufferInfo.size);
|
|
||||||
mEasyPusher.push(h264, 0, mBufferInfo.size, mBufferInfo.presentationTimeUs / 1000, 1);
|
|
||||||
if (BuildConfig.DEBUG)
|
|
||||||
Log.i(TAG, String.format("push video stamp:%d", mBufferInfo.presentationTimeUs / 1000));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
mMediaCodec.releaseOutputBuffer(index, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
stopForeground(true);
|
|
||||||
}finally {
|
|
||||||
mEasyPusher.stop();
|
|
||||||
liveData.postValue(new MediaStream.PushingState("", 0, "未开始", true));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
mPushThread.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
|
||||||
private void stopPush(){
|
|
||||||
Thread t = mPushThread;
|
|
||||||
if (t != null){
|
|
||||||
mPushThread = null;
|
|
||||||
try {
|
|
||||||
t.join();
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
|
||||||
void startVirtualDisplay(int resultCode, Intent resultData, String ip, String port, String id, final MediaStream.PushingScreenLiveData liveData) {
|
|
||||||
|
|
||||||
try {
|
|
||||||
configureMedia();
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
liveData.postValue(new MediaStream.PushingState("",-1, "编码器初始化错误", true));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mMpj == null) {
|
|
||||||
mMpj = mMpmngr.getMediaProjection(resultCode, resultData);
|
|
||||||
}
|
|
||||||
if (mMpj == null) {
|
|
||||||
liveData.postValue(new MediaStream.PushingState("",-1, "未知错误", true));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
mVirtualDisplay = mMpj.createVirtualDisplay("record_screen", windowWidth, windowHeight, screenDensity,
|
|
||||||
DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR|DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC|DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION, mSurface, null, null);
|
|
||||||
|
|
||||||
|
|
||||||
this.ip = ip;
|
|
||||||
this.port = port;
|
|
||||||
this.id = id;
|
|
||||||
this.liveData = liveData;
|
|
||||||
|
|
||||||
startPush();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
|
||||||
private void encodeToVideoTrack(int index) {
|
|
||||||
ByteBuffer encodedData = mMediaCodec.getOutputBuffer(index);
|
|
||||||
|
|
||||||
if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {//是编码需要的特定数据,不是媒体数据
|
|
||||||
// The codec config data was pulled out and fed to the muxer when we got
|
|
||||||
// the INFO_OUTPUT_FORMAT_CHANGED status.
|
|
||||||
// Ignore it.
|
|
||||||
Log.d(TAG, "ignoring BUFFER_FLAG_CODEC_CONFIG");
|
|
||||||
mBufferInfo.size = 0;
|
|
||||||
}
|
|
||||||
if (mBufferInfo.size == 0) {
|
|
||||||
Log.d(TAG, "info.size == 0, drop it.");
|
|
||||||
encodedData = null;
|
|
||||||
} else {
|
|
||||||
Log.d(TAG, "got buffer, info: size=" + mBufferInfo.size
|
|
||||||
+ ", presentationTimeUs=" + mBufferInfo.presentationTimeUs
|
|
||||||
+ ", offset=" + mBufferInfo.offset);
|
|
||||||
}
|
|
||||||
if (encodedData != null) {
|
|
||||||
encodedData.position(mBufferInfo.offset);
|
|
||||||
encodedData.limit(mBufferInfo.offset + mBufferInfo.size);
|
|
||||||
// mMuxer.writeSampleData(mVideoTrackIndex, encodedData, mBufferInfo);//写入
|
|
||||||
Log.i(TAG, "sent " + mBufferInfo.size + " bytes to muxer...");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@TargetApi(Build.VERSION_CODES.KITKAT)
|
|
||||||
private void release() {
|
|
||||||
|
|
||||||
Log.i(TAG, " release() ");
|
|
||||||
if (mMediaCodec != null) {
|
|
||||||
mMediaCodec.stop();
|
|
||||||
mMediaCodec.release();
|
|
||||||
mMediaCodec = null;
|
|
||||||
}
|
|
||||||
if (mSurface != null){
|
|
||||||
mSurface.release();
|
|
||||||
}
|
|
||||||
if (mVirtualDisplay != null) {
|
|
||||||
mVirtualDisplay.release();
|
|
||||||
mVirtualDisplay = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
|
||||||
@Override
|
|
||||||
public void onDestroy() {
|
|
||||||
super.onDestroy();
|
|
||||||
stopPush();
|
|
||||||
release();
|
|
||||||
if (mMpj != null) {
|
|
||||||
mMpj.stop();
|
|
||||||
}
|
|
||||||
unregisterReceiver(mReceiver);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,200 +0,0 @@
|
|||||||
package org.easydarwin.push;
|
|
||||||
|
|
||||||
import android.app.Service;
|
|
||||||
import androidx.lifecycle.LiveData;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.hardware.usb.UsbDevice;
|
|
||||||
import android.os.Binder;
|
|
||||||
import android.os.IBinder;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.util.SparseArray;
|
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import com.serenegiant.usb.DeviceFilter;
|
|
||||||
import com.serenegiant.usb.IButtonCallback;
|
|
||||||
import com.serenegiant.usb.IStatusCallback;
|
|
||||||
import com.serenegiant.usb.USBMonitor;
|
|
||||||
import com.serenegiant.usb.UVCCamera;
|
|
||||||
|
|
||||||
import org.easydarwin.easypusher.BuildConfig;
|
|
||||||
import org.easydarwin.easypusher.R;
|
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
|
|
||||||
public class UVCCameraService extends Service {
|
|
||||||
|
|
||||||
|
|
||||||
public static class UVCCameraLivaData extends LiveData<UVCCamera>{
|
|
||||||
@Override
|
|
||||||
protected void postValue(UVCCamera value) {
|
|
||||||
super.postValue(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final UVCCameraLivaData liveData = new UVCCameraLivaData();
|
|
||||||
public static class MyUVCCamera extends UVCCamera {
|
|
||||||
|
|
||||||
boolean prev = false;
|
|
||||||
@Override
|
|
||||||
public synchronized void startPreview() {
|
|
||||||
if (prev ) return;
|
|
||||||
super.startPreview();
|
|
||||||
prev = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void stopPreview() {
|
|
||||||
if (!prev )return;
|
|
||||||
super.stopPreview();
|
|
||||||
prev = false;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void destroy() {
|
|
||||||
prev = false;
|
|
||||||
super.destroy();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private static final String TAG = "OutterCamera";
|
|
||||||
private USBMonitor mUSBMonitor;
|
|
||||||
private UVCCamera mUVCCamera;
|
|
||||||
|
|
||||||
private SparseArray<UVCCamera> cameras = new SparseArray<>();
|
|
||||||
|
|
||||||
public class MyBinder extends Binder {
|
|
||||||
|
|
||||||
public UVCCameraService getService() {
|
|
||||||
return UVCCameraService.this;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
MyBinder binder = new MyBinder();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public IBinder onBind(Intent intent) {
|
|
||||||
return binder;
|
|
||||||
}
|
|
||||||
|
|
||||||
public UVCCamera getCamera() {
|
|
||||||
return mUVCCamera;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void releaseCamera() {
|
|
||||||
if (mUVCCamera != null) {
|
|
||||||
try {
|
|
||||||
mUVCCamera.close();
|
|
||||||
mUVCCamera.destroy();
|
|
||||||
mUVCCamera = null;
|
|
||||||
} catch (final Exception e) {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate() {
|
|
||||||
super.onCreate();
|
|
||||||
|
|
||||||
mUSBMonitor = new USBMonitor(this, new USBMonitor.OnDeviceConnectListener() {
|
|
||||||
@Override
|
|
||||||
public void onAttach(final UsbDevice device) {
|
|
||||||
Log.v(TAG, "onAttach:" + device);
|
|
||||||
mUSBMonitor.requestPermission(device);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onConnect(final UsbDevice device, final USBMonitor.UsbControlBlock ctrlBlock, final boolean createNew) {
|
|
||||||
releaseCamera();
|
|
||||||
if (BuildConfig.DEBUG) Log.v(TAG, "onConnect:");
|
|
||||||
try {
|
|
||||||
final UVCCamera camera = new MyUVCCamera();
|
|
||||||
camera.open(ctrlBlock);
|
|
||||||
camera.setStatusCallback(new IStatusCallback() {
|
|
||||||
@Override
|
|
||||||
public void onStatus(final int statusClass, final int event, final int selector,
|
|
||||||
final int statusAttribute, final ByteBuffer data) {
|
|
||||||
Log.i(TAG, "onStatus(statusClass=" + statusClass
|
|
||||||
+ "; " +
|
|
||||||
"event=" + event + "; " +
|
|
||||||
"selector=" + selector + "; " +
|
|
||||||
"statusAttribute=" + statusAttribute + "; " +
|
|
||||||
"data=...)");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
camera.setButtonCallback(new IButtonCallback() {
|
|
||||||
@Override
|
|
||||||
public void onButton(final int button, final int state) {
|
|
||||||
Log.i(TAG, "onButton(button=" + button + "; " + "state=" + state + ")");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// camera.setPreviewTexture(camera.getSurfaceTexture());
|
|
||||||
mUVCCamera = camera;
|
|
||||||
liveData.postValue(camera);
|
|
||||||
|
|
||||||
Toast.makeText(UVCCameraService.this, "UVCCamera connected!", Toast.LENGTH_SHORT).show();
|
|
||||||
if (device != null)
|
|
||||||
cameras.append(device.getDeviceId(), camera);
|
|
||||||
}catch (Exception ex){
|
|
||||||
ex.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDisconnect(final UsbDevice device, final USBMonitor.UsbControlBlock ctrlBlock) {
|
|
||||||
Log.v(TAG, "onDisconnect:");
|
|
||||||
// Toast.makeText(MainActivity.this, R.string.usb_camera_disconnected, Toast.LENGTH_SHORT).show();
|
|
||||||
|
|
||||||
// releaseCamera();
|
|
||||||
|
|
||||||
if (device != null) {
|
|
||||||
UVCCamera camera = cameras.get(device.getDeviceId());
|
|
||||||
if (mUVCCamera == camera) {
|
|
||||||
mUVCCamera = null;
|
|
||||||
Toast.makeText(UVCCameraService.this, "UVCCamera disconnected!", Toast.LENGTH_SHORT).show();
|
|
||||||
liveData.postValue(null);
|
|
||||||
}
|
|
||||||
cameras.remove(device.getDeviceId());
|
|
||||||
}else {
|
|
||||||
Toast.makeText(UVCCameraService.this, "UVCCamera disconnected!", Toast.LENGTH_SHORT).show();
|
|
||||||
mUVCCamera = null;
|
|
||||||
liveData.postValue(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
// if (mUSBMonitor != null) {
|
|
||||||
// mUSBMonitor.destroy();
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// mUSBMonitor = new USBMonitor(OutterCameraService.this, this);
|
|
||||||
// mUSBMonitor.setDeviceFilter(DeviceFilter.getDeviceFilters(OutterCameraService.this, R.xml.device_filter));
|
|
||||||
// mUSBMonitor.register();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCancel(UsbDevice usbDevice) {
|
|
||||||
releaseCamera();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDettach(final UsbDevice device) {
|
|
||||||
Log.v(TAG, "onDettach:");
|
|
||||||
releaseCamera();
|
|
||||||
// AppContext.getInstance().bus.post(new UVCCameraDisconnect());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
mUSBMonitor.setDeviceFilter(DeviceFilter.getDeviceFilters(this, R.xml.device_filter));
|
|
||||||
mUSBMonitor.register();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDestroy() {
|
|
||||||
releaseCamera();
|
|
||||||
if (mUSBMonitor != null) {
|
|
||||||
mUSBMonitor.unregister();
|
|
||||||
}
|
|
||||||
super.onDestroy();
|
|
||||||
}
|
|
||||||
}
|
|