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(); | ||||
|     } | ||||
| } | ||||