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"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
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>
|
@ -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();
|
||||
}
|
||||
}
|