Переглянути джерело

Add new version (Android)

Babosa 2 місяців тому
батько
коміт
1b31372fc2
100 змінених файлів з 8487 додано та 0 видалено
  1. 17 0
      Android/.gitignore
  2. 3 0
      Android/.idea/.gitignore
  3. 1 0
      Android/.idea/.name
  4. 149 0
      Android/.idea/codeStyles/Project.xml
  5. 5 0
      Android/.idea/codeStyles/codeStyleConfig.xml
  6. 6 0
      Android/.idea/compiler.xml
  7. 22 0
      Android/.idea/gradle.xml
  8. 6 0
      Android/.idea/vcs.xml
  9. 1 0
      Android/.sdkmanrc
  10. 21 0
      Android/README.en.md
  11. 16 0
      Android/README.md
  12. 1 0
      Android/app/.gitignore
  13. 86 0
      Android/app/build.gradle
  14. BIN
      Android/app/libs/libuvccamera-release.aar
  15. 21 0
      Android/app/proguard-rules.pro
  16. 60 0
      Android/app/src/main/AndroidManifest.xml
  17. BIN
      Android/app/src/main/assets/zk/SIMYOU.ttf
  18. 195 0
      Android/app/src/main/java/com/easygbs/easygbd/StretchVideoView.kt
  19. 62 0
      Android/app/src/main/java/com/easygbs/easygbd/activity/BaseActivity.kt
  20. 0 0
      Android/app/src/main/java/com/easygbs/easygbd/activity/MainActivity.kt
  21. 122 0
      Android/app/src/main/java/com/easygbs/easygbd/activity/RecordListActivity.kt
  22. 1 0
      Android/app/src/main/java/com/easygbs/easygbd/adapter/AudiochannelAdapter.java
  23. 1 0
      Android/app/src/main/java/com/easygbs/easygbd/adapter/AudiocodeAdapter.java
  24. 1 0
      Android/app/src/main/java/com/easygbs/easygbd/adapter/AudiocoderateAdapter.java
  25. 14 0
      Android/app/src/main/java/com/easygbs/easygbd/adapter/BindingAdapters.java
  26. 1 0
      Android/app/src/main/java/com/easygbs/easygbd/adapter/CameraAdapter.java
  27. 0 0
      Android/app/src/main/java/com/easygbs/easygbd/adapter/ChannelAdapter.java
  28. 1 0
      Android/app/src/main/java/com/easygbs/easygbd/adapter/FramerateAdapter.java
  29. 1 0
      Android/app/src/main/java/com/easygbs/easygbd/adapter/LocationfrequencyAdapter.java
  30. 1 0
      Android/app/src/main/java/com/easygbs/easygbd/adapter/LoglevelAdapter.java
  31. 45 0
      Android/app/src/main/java/com/easygbs/easygbd/adapter/Mp4Adapter.kt
  32. 1 0
      Android/app/src/main/java/com/easygbs/easygbd/adapter/ResolutionAdapter.java
  33. 1 0
      Android/app/src/main/java/com/easygbs/easygbd/adapter/SamplingrateAdapter.java
  34. 1 0
      Android/app/src/main/java/com/easygbs/easygbd/adapter/TranproAdapter.java
  35. 1 0
      Android/app/src/main/java/com/easygbs/easygbd/adapter/VerAdapter.java
  36. 1 0
      Android/app/src/main/java/com/easygbs/easygbd/adapter/VideoCodeAdapter.java
  37. 1 0
      Android/app/src/main/java/com/easygbs/easygbd/adapter/VideocoderateAdapter.java
  38. 1 0
      Android/app/src/main/java/com/easygbs/easygbd/application/App.java
  39. 22 0
      Android/app/src/main/java/com/easygbs/easygbd/bean/AudiochannelBean.java
  40. 22 0
      Android/app/src/main/java/com/easygbs/easygbd/bean/AudiocodeBean.java
  41. 22 0
      Android/app/src/main/java/com/easygbs/easygbd/bean/AudiocoderateBean.java
  42. 22 0
      Android/app/src/main/java/com/easygbs/easygbd/bean/CameraBean.java
  43. 22 0
      Android/app/src/main/java/com/easygbs/easygbd/bean/FramerateBean.java
  44. 22 0
      Android/app/src/main/java/com/easygbs/easygbd/bean/LocationfrequencyBean.java
  45. 22 0
      Android/app/src/main/java/com/easygbs/easygbd/bean/LoglevelBean.java
  46. 22 0
      Android/app/src/main/java/com/easygbs/easygbd/bean/ResolutionBean.java
  47. 22 0
      Android/app/src/main/java/com/easygbs/easygbd/bean/SamplingrateBean.java
  48. 22 0
      Android/app/src/main/java/com/easygbs/easygbd/bean/TranproBean.java
  49. 22 0
      Android/app/src/main/java/com/easygbs/easygbd/bean/VerBean.java
  50. 22 0
      Android/app/src/main/java/com/easygbs/easygbd/bean/VideoCodeBean.java
  51. 22 0
      Android/app/src/main/java/com/easygbs/easygbd/bean/VideocoderateBean.java
  52. 32 0
      Android/app/src/main/java/com/easygbs/easygbd/common/Constant.java
  53. 0 0
      Android/app/src/main/java/com/easygbs/easygbd/dao/ChanOp.java
  54. 13 0
      Android/app/src/main/java/com/easygbs/easygbd/dao/DCon.java
  55. 95 0
      Android/app/src/main/java/com/easygbs/easygbd/dao/DOpe.java
  56. 5 0
      Android/app/src/main/java/com/easygbs/easygbd/dao/TCon.java
  57. 59 0
      Android/app/src/main/java/com/easygbs/easygbd/dao/bean/Chan.java
  58. 26 0
      Android/app/src/main/java/com/easygbs/easygbd/dao/ve/UVO.java
  59. 141 0
      Android/app/src/main/java/com/easygbs/easygbd/dialog/ChanDialog.java
  60. 94 0
      Android/app/src/main/java/com/easygbs/easygbd/fragment/AboutFragment.java
  61. 568 0
      Android/app/src/main/java/com/easygbs/easygbd/fragment/BasicSettingsFragment.java
  62. 490 0
      Android/app/src/main/java/com/easygbs/easygbd/fragment/ChannelSettingsFragment.java
  63. 257 0
      Android/app/src/main/java/com/easygbs/easygbd/fragment/RecordFragment.java
  64. 175 0
      Android/app/src/main/java/com/easygbs/easygbd/fragment/StreamingSettingsFragment.java
  65. 9 0
      Android/app/src/main/java/com/easygbs/easygbd/logger/LogLevel.java
  66. 203 0
      Android/app/src/main/java/com/easygbs/easygbd/logger/Logger.java
  67. 107 0
      Android/app/src/main/java/com/easygbs/easygbd/push/G711Code.java
  68. 1252 0
      Android/app/src/main/java/com/easygbs/easygbd/push/MediaStream.java
  69. 42 0
      Android/app/src/main/java/com/easygbs/easygbd/push/PushCallback.java
  70. 174 0
      Android/app/src/main/java/com/easygbs/easygbd/service/BackgroundCameraService.java
  71. 192 0
      Android/app/src/main/java/com/easygbs/easygbd/service/UVCCameraService.java
  72. 49 0
      Android/app/src/main/java/com/easygbs/easygbd/util/PeProxy.java
  73. 78 0
      Android/app/src/main/java/com/easygbs/easygbd/util/PeUtil.java
  74. 54 0
      Android/app/src/main/java/com/easygbs/easygbd/util/SPHelper.java
  75. 19 0
      Android/app/src/main/java/com/easygbs/easygbd/util/ScrUtil.java
  76. 0 0
      Android/app/src/main/java/com/easygbs/easygbd/util/SipUtil.java
  77. 50 0
      Android/app/src/main/java/com/easygbs/easygbd/viewadapter/CommonAdapter.java
  78. 11 0
      Android/app/src/main/java/com/easygbs/easygbd/viewadapter/ItemViewDelegate.java
  79. 113 0
      Android/app/src/main/java/com/easygbs/easygbd/viewadapter/ItemViewDelegateManager.java
  80. 126 0
      Android/app/src/main/java/com/easygbs/easygbd/viewadapter/MultiItemTypeAdapter.java
  81. 276 0
      Android/app/src/main/java/com/easygbs/easygbd/viewadapter/ViewHolder.java
  82. 10 0
      Android/app/src/main/java/com/easygbs/easygbd/viewmodel/BaseViewModel.kt
  83. 88 0
      Android/app/src/main/java/com/easygbs/easygbd/viewmodel/activity/MainViewModel.kt
  84. 26 0
      Android/app/src/main/java/com/easygbs/easygbd/viewmodel/activity/RecordListViewModel.kt
  85. 47 0
      Android/app/src/main/java/com/easygbs/easygbd/viewmodel/fragment/AboutViewModel.java
  86. 552 0
      Android/app/src/main/java/com/easygbs/easygbd/viewmodel/fragment/BasicSettingsViewModel.java
  87. 76 0
      Android/app/src/main/java/com/easygbs/easygbd/viewmodel/fragment/ChannelSettingsViewModel.java
  88. 83 0
      Android/app/src/main/java/com/easygbs/easygbd/viewmodel/fragment/RecordViewModel.java
  89. 1645 0
      Android/app/src/main/java/com/easygbs/easygbd/viewmodel/fragment/StreamingSettingsViewModel.java
  90. 5 0
      Android/app/src/main/res/anim/slide_in_up.xml
  91. 5 0
      Android/app/src/main/res/anim/slide_out_down.xml
  92. 7 0
      Android/app/src/main/res/drawable/bg_btn_grey_round_border.xml
  93. 7 0
      Android/app/src/main/res/drawable/bg_btn_white_round_border.xml
  94. 9 0
      Android/app/src/main/res/drawable/bg_gradient_d9f4eb.xml
  95. 5 0
      Android/app/src/main/res/drawable/bg_rounded_53c3a3.xml
  96. 10 0
      Android/app/src/main/res/drawable/bg_rounded_add.xml
  97. 10 0
      Android/app/src/main/res/drawable/bg_rounded_channel.xml
  98. 13 0
      Android/app/src/main/res/drawable/bg_rounded_channel_btn.xml
  99. 16 0
      Android/app/src/main/res/drawable/bg_rounded_channel_btn_submit.xml
  100. 10 0
      Android/app/src/main/res/drawable/bg_rounded_f8f8f8.xml

+ 17 - 0
Android/.gitignore

@@ -0,0 +1,17 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/misc.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+/app/release
+easygbs/build

+ 3 - 0
Android/.idea/.gitignore

@@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
+/workspace.xml

+ 1 - 0
Android/.idea/.name

@@ -0,0 +1 @@
+output-metadata.json

+ 149 - 0
Android/.idea/codeStyles/Project.xml

@@ -0,0 +1,149 @@
+<component name="ProjectCodeStyleConfiguration">
+  <code_scheme name="Project" version="173">
+    <option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" />
+    <option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" />
+    <option name="PACKAGES_TO_USE_IMPORT_ON_DEMAND">
+      <value />
+    </option>
+    <option name="IMPORT_LAYOUT_TABLE">
+      <value>
+        <package name="android" withSubpackages="true" static="false" />
+        <emptyLine />
+        <package name="com" withSubpackages="true" static="false" />
+        <emptyLine />
+        <package name="junit" withSubpackages="true" static="false" />
+        <emptyLine />
+        <package name="net" withSubpackages="true" static="false" />
+        <emptyLine />
+        <package name="org" withSubpackages="true" static="false" />
+        <emptyLine />
+        <package name="java" withSubpackages="true" static="false" />
+        <emptyLine />
+        <package name="javax" withSubpackages="true" static="false" />
+        <emptyLine />
+        <package name="" withSubpackages="true" static="false" />
+        <emptyLine />
+        <package name="" withSubpackages="true" static="true" />
+        <emptyLine />
+      </value>
+    </option>
+    <JetCodeStyleSettings>
+      <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
+    </JetCodeStyleSettings>
+    <codeStyleSettings language="XML">
+      <indentOptions>
+        <option name="CONTINUATION_INDENT_SIZE" value="4" />
+      </indentOptions>
+      <arrangement>
+        <rules>
+          <section>
+            <rule>
+              <match>
+                <AND>
+                  <NAME>xmlns:android</NAME>
+                  <XML_ATTRIBUTE />
+                  <XML_NAMESPACE>^$</XML_NAMESPACE>
+                </AND>
+              </match>
+            </rule>
+          </section>
+          <section>
+            <rule>
+              <match>
+                <AND>
+                  <NAME>xmlns:.*</NAME>
+                  <XML_ATTRIBUTE />
+                  <XML_NAMESPACE>^$</XML_NAMESPACE>
+                </AND>
+              </match>
+              <order>BY_NAME</order>
+            </rule>
+          </section>
+          <section>
+            <rule>
+              <match>
+                <AND>
+                  <NAME>.*:id</NAME>
+                  <XML_ATTRIBUTE />
+                  <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
+                </AND>
+              </match>
+            </rule>
+          </section>
+          <section>
+            <rule>
+              <match>
+                <AND>
+                  <NAME>.*:name</NAME>
+                  <XML_ATTRIBUTE />
+                  <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
+                </AND>
+              </match>
+            </rule>
+          </section>
+          <section>
+            <rule>
+              <match>
+                <AND>
+                  <NAME>name</NAME>
+                  <XML_ATTRIBUTE />
+                  <XML_NAMESPACE>^$</XML_NAMESPACE>
+                </AND>
+              </match>
+            </rule>
+          </section>
+          <section>
+            <rule>
+              <match>
+                <AND>
+                  <NAME>style</NAME>
+                  <XML_ATTRIBUTE />
+                  <XML_NAMESPACE>^$</XML_NAMESPACE>
+                </AND>
+              </match>
+            </rule>
+          </section>
+          <section>
+            <rule>
+              <match>
+                <AND>
+                  <NAME>.*</NAME>
+                  <XML_ATTRIBUTE />
+                  <XML_NAMESPACE>^$</XML_NAMESPACE>
+                </AND>
+              </match>
+              <order>BY_NAME</order>
+            </rule>
+          </section>
+          <section>
+            <rule>
+              <match>
+                <AND>
+                  <NAME>.*</NAME>
+                  <XML_ATTRIBUTE />
+                  <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
+                </AND>
+              </match>
+              <order>ANDROID_ATTRIBUTE_ORDER</order>
+            </rule>
+          </section>
+          <section>
+            <rule>
+              <match>
+                <AND>
+                  <NAME>.*</NAME>
+                  <XML_ATTRIBUTE />
+                  <XML_NAMESPACE>.*</XML_NAMESPACE>
+                </AND>
+              </match>
+              <order>BY_NAME</order>
+            </rule>
+          </section>
+        </rules>
+      </arrangement>
+    </codeStyleSettings>
+    <codeStyleSettings language="kotlin">
+      <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
+    </codeStyleSettings>
+  </code_scheme>
+</component>

+ 5 - 0
Android/.idea/codeStyles/codeStyleConfig.xml

@@ -0,0 +1,5 @@
+<component name="ProjectCodeStyleConfiguration">
+  <state>
+    <option name="USE_PER_PROJECT_SETTINGS" value="true" />
+  </state>
+</component>

+ 6 - 0
Android/.idea/compiler.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="CompilerConfiguration">
+    <bytecodeTargetLevel target="11" />
+  </component>
+</project>

+ 22 - 0
Android/.idea/gradle.xml

@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="GradleMigrationSettings" migrationVersion="1" />
+  <component name="GradleSettings">
+    <option name="linkedExternalProjectsSettings">
+      <GradleProjectSettings>
+        <option name="testRunner" value="GRADLE" />
+        <option name="distributionType" value="DEFAULT_WRAPPED" />
+        <option name="externalProjectPath" value="$PROJECT_DIR$" />
+        <option name="gradleHome" value="$USER_HOME$/Tools/android-sdk-macosx/gradle/gradle-2.3" />
+        <option name="modules">
+          <set>
+            <option value="$PROJECT_DIR$" />
+            <option value="$PROJECT_DIR$/app" />
+            <option value="$PROJECT_DIR$/easygbs" />
+            <option value="$PROJECT_DIR$/qr" />
+          </set>
+        </option>
+      </GradleProjectSettings>
+    </option>
+  </component>
+</project>

+ 6 - 0
Android/.idea/vcs.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="$PROJECT_DIR$" vcs="Git" />
+  </component>
+</project>

+ 1 - 0
Android/.sdkmanrc

@@ -0,0 +1 @@
+java=11.0.25-zulu

+ 21 - 0
Android/README.en.md

@@ -0,0 +1,21 @@
+EasyGBD
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ 16 - 0
Android/README.md

@@ -0,0 +1,16 @@
+EasyGBD
+ 
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ 1 - 0
Android/app/.gitignore

@@ -0,0 +1 @@
+/build

+ 86 - 0
Android/app/build.gradle

@@ -0,0 +1,86 @@
+plugins {
+    id 'com.android.application'
+    id 'org.jetbrains.kotlin.android'
+    id 'kotlin-android'
+//    id 'kotlin-android-extensions'
+    id 'kotlin-kapt'
+}
+
+android {
+    compileSdk 32
+
+    dataBinding {
+        enabled = true
+    }
+
+    viewBinding {
+        enabled = true
+    }
+
+    defaultConfig {
+        applicationId "com.easygbs.easygbd"
+        minSdk 19
+        targetSdk 32
+        versionCode 1
+        versionName "1.0.0"
+        multiDexEnabled true
+
+        ndk {
+            abiFilters 'arm64-v8a','armeabi-v7a'
+        }
+
+        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+    }
+
+    buildTypes {
+        debug {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+        }
+
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+        }
+    }
+
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+    kotlinOptions {
+        jvmTarget = '1.8'
+    }
+}
+
+dependencies {
+    implementation fileTree(dir: 'libs', include: ['*.jar'])
+    implementation 'androidx.core:core-ktx:1.7.0'
+    implementation 'androidx.appcompat:appcompat:1.3.0'
+    implementation 'com.google.android.material:material:1.4.0'
+    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
+    testImplementation 'junit:junit:4.13.2'
+    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
+    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
+    implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.4.32"
+    implementation "androidx.navigation:navigation-fragment:2.3.0"
+    implementation "androidx.navigation:navigation-ui:2.3.0"
+    implementation "androidx.navigation:navigation-fragment-ktx:2.3.0"
+    implementation "androidx.navigation:navigation-ui-ktx:2.3.0"
+    implementation "androidx.navigation:navigation-dynamic-features-fragment:2.3.0"
+    kapt 'androidx.room:room-compiler:2.2.5'
+    implementation   'androidx.room:room-runtime:2.2.4'
+    implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
+    api 'pub.devrel:easypermissions:3.0.0'
+    implementation 'com.geyifeng.immersionbar:immersionbar:3.2.2'
+    api project(path: ':easygbs')
+    implementation project(':qr')
+    api files("libs/libuvccamera-release.aar")
+    implementation 'org.greenrobot:eventbus:3.0.0'
+    implementation 'android.arch.lifecycle:extensions:1.0.0'
+    implementation 'android.arch.lifecycle:reactivestreams:1.0.0'
+    implementation 'android.arch.lifecycle:extensions:1.0.0'
+    implementation 'android.arch.lifecycle:reactivestreams:1.0.0'
+    annotationProcessor "android.arch.lifecycle:compiler:1.0.0"
+    annotationProcessor "android.arch.lifecycle:compiler:1.0.0"
+}

BIN
Android/app/libs/libuvccamera-release.aar


+ 21 - 0
Android/app/proguard-rules.pro

@@ -0,0 +1,21 @@
+# 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

+ 60 - 0
Android/app/src/main/AndroidManifest.xml

@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+package="com.easygbs.easygbd"
+>
+
+    <application
+        android:name="com.easygbs.easygbd.application.App"
+        android:allowBackup="true"
+        android:icon="@mipmap/icon"
+        android:label="@string/app_name"
+        android:roundIcon="@mipmap/ic_launcher_round"
+        android:supportsRtl="true"
+        android:theme="@style/AppTheme">
+        <activity
+            android:name=".activity.MainActivity"
+            android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation|mcc|mnc|density"
+            android:exported="true"
+            android:launchMode="singleTask"
+            android:screenOrientation="portrait">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+        <activity
+            android:name=".activity.RecordListActivity"
+            android:label="录像文件"
+            />
+
+        <service
+            android:name="com.easygbs.easygbd.service.BackgroundCameraService"
+            android:enabled="true"
+            />
+
+        <service
+            android:name="com.easygbs.easygbd.service.UVCCameraService"
+            android:enabled="true"
+            />
+    </application>
+
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
+    <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.MODIFY_AUDIO_SETTINGS" />
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+    <uses-permission android:name="android.permission.SYSTEM_OVERLAY_WINDOW" />
+    <uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
+    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
+    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+    <uses-permission android:name="android.permission.READ_LOGS" />
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+    <uses-feature android:name="android.hardware.camera" />
+</manifest>

BIN
Android/app/src/main/assets/zk/SIMYOU.ttf


+ 195 - 0
Android/app/src/main/java/com/easygbs/easygbd/StretchVideoView.kt

@@ -0,0 +1,195 @@
+package com.easygbs.easygbd
+
+import android.content.Context
+import android.graphics.Color
+import android.graphics.drawable.ColorDrawable
+import android.graphics.drawable.LayerDrawable
+import android.os.Build
+import android.os.Handler
+import android.os.Looper
+import android.util.AttributeSet
+import android.util.Log
+import android.view.Gravity
+import android.view.View
+import android.widget.*
+import androidx.annotation.RequiresApi
+import androidx.core.content.ContextCompat
+
+@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
+class StretchVideoView(context: Context, attrs: AttributeSet? = null) : VideoView(context, attrs) {
+
+    private lateinit var seekBar: SeekBar
+    private lateinit var playPauseButton: ImageView
+    private lateinit var remainingTimeText: TextView // 添加 TextView 显示剩余时间
+    private lateinit var buttonContainer: LinearLayout
+    private val progressHandler = Handler(Looper.getMainLooper())
+
+    private var mPlaying = false;
+
+    init {
+        // 延迟初始化,确保父布局加载完成
+        post {
+            // 外部容器:用于设置半透明背景
+            val outerContainer = FrameLayout(context).apply {
+                setBackgroundColor(Color.parseColor("#99000000")) // 半透明背景
+            }
+
+            // 父布局是 RelativeLayout
+            val parentLayout = parent as? RelativeLayout
+            if (parentLayout != null) {
+                // 初始化播放/暂停按钮,使用 ImageView 显示图片
+                playPauseButton = ImageView(context).apply {
+                    id = View.generateViewId() // 生成唯一ID
+                    setImageResource(R.mipmap.playing) // 设置图片资源,替换为你的图标
+                    setOnClickListener {
+                        if (isPlaying) {
+                            pause()
+                            setImageResource(R.mipmap.pause)
+                            progressHandler.removeCallbacksAndMessages(null) // 停止更新
+                        } else {
+                            start()
+                            setImageResource(R.mipmap.playing)
+                            startProgressHandler() // 启动进度条更新
+                        }
+                    }
+                }
+
+                // 初始化进度条
+                seekBar = SeekBar(context).apply {
+                    max = 0 // 初始值为 0,后续会动态设置
+                    id = View.generateViewId()
+                    layoutParams = LinearLayout.LayoutParams(
+                        0,
+                        LinearLayout.LayoutParams.WRAP_CONTENT,
+                        1f
+                    ) // 设置宽度为 0,权重为 1,使其填满父布局
+                    setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
+                        override fun onProgressChanged(
+                            seekBar: SeekBar?,
+                            progress: Int,
+                            fromUser: Boolean
+                        ) {
+                            if (fromUser) {
+                                seekTo(progress)
+                            }
+                        }
+
+                        override fun onStartTrackingTouch(seekBar: SeekBar?) {
+                            mPlaying = isPlaying
+                            pause() // 用户拖动时暂停播放
+                        }
+
+                        override fun onStopTrackingTouch(seekBar: SeekBar?) {
+                            if (mPlaying) start()
+                            updateProgress()
+                            startProgressHandler() // 启动进度条更新
+                        }
+                    })
+                }
+
+                // 设置进度条未加载部分颜色为白色
+                val layerDrawable = seekBar.progressDrawable as LayerDrawable
+                // 设置进度条未加载部分的颜色
+                val backgroundDrawable = layerDrawable.getDrawable(0)
+                backgroundDrawable.setTint(Color.WHITE)
+
+
+                // 初始化剩余时间显示的 TextView
+                remainingTimeText = TextView(context).apply {
+                    id = View.generateViewId()
+                    setTextColor(Color.WHITE) // 设置文本颜色为白色
+                    textSize = 16f // 设置文本大小
+                    text = formatTime(duration) // 初始显示剩余时间
+                }
+
+
+                // 初始化 LinearLayout 容器
+                buttonContainer = LinearLayout(context).apply {
+                    orientation = LinearLayout.HORIZONTAL // 设置为水平布局
+                    gravity = Gravity.CENTER_VERTICAL // 内容居中
+                    id = View.generateViewId() // 生成唯一ID
+                    setPadding(50, 32, 50, 32) // 内边距
+                    addView(playPauseButton) // 将图片按钮添加到容器中
+                    addView(seekBar) // 将进度条添加到容器中
+                    addView(remainingTimeText) // 将剩余时间文本添加到容器中
+                }
+
+
+                // 将 LinearLayout 添加到外部容器
+                outerContainer.addView(buttonContainer)
+
+                // 设置外部容器的布局参数,将其定位到底部
+                val layoutParams = RelativeLayout.LayoutParams(
+                    RelativeLayout.LayoutParams.MATCH_PARENT,
+                    RelativeLayout.LayoutParams.WRAP_CONTENT
+                ).apply {
+                    addRule(RelativeLayout.ALIGN_PARENT_BOTTOM) // 底部对齐
+                    bottomMargin = 0 // 距离底部的间距(可根据需求调整)
+                }
+
+                // 将外部容器添加到父布局
+                parentLayout.addView(outerContainer, layoutParams)
+
+                setListener()
+            }
+        }
+    }
+
+    private fun setListener() {
+        // 设置视频播放完成的监听器
+        setOnCompletionListener {
+            // 播放完成后,更新按钮图片为播放图标
+            playPauseButton.setImageResource(R.mipmap.pause)
+            seekBar.progress = 0 // 重置进度条
+            remainingTimeText.text = formatTime(duration) // 重置剩余时间文本
+            progressHandler.removeCallbacksAndMessages(null) // 停止更新任务
+        }
+
+        setOnPreparedListener {
+            start()
+            playPauseButton.setImageResource(R.mipmap.playing) // 更新为暂停按钮
+            seekBar.max = duration // 设置 SeekBar 最大值为视频总时长
+            Log.d("StretchVideoView", "duration: $duration")
+            remainingTimeText.text = formatTime(duration) // 初始化显示剩余时间
+            startProgressHandler() // 启动进度条更新
+        }
+    }
+
+    fun updateProgress() {
+        val progress = (currentPosition.toFloat() / duration * 100).toInt()
+        seekBar.progress = currentPosition // 更新进度条
+        remainingTimeText.text = formatTime(duration - currentPosition) // 初始化显示剩余时间
+        Log.d(
+            "StretchVideoView",
+            "Current: $currentPosition, Duration: $duration, Progress: $progress"
+        )
+    }
+
+    // 更新进度条
+    private fun startProgressHandler() {
+        progressHandler.postDelayed(object : Runnable {
+            override fun run() {
+                updateProgress();
+                progressHandler.postDelayed(this, 1000) // 每 500 毫秒更新一次
+            }
+        }, 200)
+    }
+
+    // 格式化时间为分钟:秒的形式
+    private fun formatTime(timeInMillis: Int): String {
+        val seconds = timeInMillis / 1000
+        val minutes = seconds / 60
+        val remainingSeconds = seconds % 60
+        return String.format("%02d:%02d", minutes, remainingSeconds)
+    }
+
+    // 重写 onMeasure,强制拉伸 VideoView
+    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+        val width = MeasureSpec.getSize(widthMeasureSpec)
+        val height = MeasureSpec.getSize(heightMeasureSpec)
+        setMeasuredDimension(width, height)
+    }
+}
+
+
+

+ 62 - 0
Android/app/src/main/java/com/easygbs/easygbd/activity/BaseActivity.kt

@@ -0,0 +1,62 @@
+package com.easygbs.easygbd.activity
+import android.annotation.SuppressLint
+import android.os.Bundle
+import androidx.fragment.app.FragmentActivity
+import org.greenrobot.eventbus.EventBus
+import pub.devrel.easypermissions.EasyPermissions
+import pub.devrel.easypermissions.EasyPermissions.PermissionCallbacks
+
+abstract class BaseActivity: FragmentActivity(), PermissionCallbacks {
+    var BaseActivityTAG: String = BaseActivity::class.java.getSimpleName()
+
+    @SuppressLint("MissingSuperCall")
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        init()
+    }
+
+    abstract fun init()
+
+    override fun onDestroy() {
+        super.onDestroy()
+
+    }
+
+    override fun onRequestPermissionsResult(
+        requestCode: Int,
+        permissions: Array<String?>,
+        grantResults: IntArray
+    ) {
+        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
+        EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this)
+    }
+
+    override fun onPermissionsGranted(requestCode: Int,perms: List<String?>) {
+        Pergran(requestCode)
+    }
+
+    override fun onPermissionsDenied(requestCode: Int, perms: List<String>) {
+        for (i in perms.indices) {
+            var d: Boolean = EasyPermissions.somePermissionDenied(this,perms.get(i))
+            if (d) {
+                val deniedPermission = perms[i]
+                Perteden(deniedPermission)
+            }
+        }
+
+        var sp:Boolean=EasyPermissions.somePermissionPermanentlyDenied(this,perms)
+        if (sp) {
+            for (j in perms.indices) {
+                val per = perms[j]
+                Perpeden(per)
+            }
+        }
+    }
+
+    open fun Pergran(requestCode: Int) {}
+
+    open fun Perteden(deniedPermission: String) {}
+
+    open fun Perpeden(deniedPermission: String) {}
+
+}

Різницю між файлами не показано, бо вона завелика
+ 0 - 0
Android/app/src/main/java/com/easygbs/easygbd/activity/MainActivity.kt


+ 122 - 0
Android/app/src/main/java/com/easygbs/easygbd/activity/RecordListActivity.kt

@@ -0,0 +1,122 @@
+package com.easygbs.easygbd.activity;
+
+import Mp4Adapter
+import android.app.Dialog
+import android.content.Context
+import android.net.Uri
+import android.os.Build
+import android.util.DisplayMetrics
+import android.util.Log
+import android.view.View
+import android.view.ViewGroup
+import android.widget.*
+import androidx.annotation.RequiresApi
+import androidx.databinding.DataBindingUtil
+import androidx.recyclerview.widget.GridLayoutManager
+import com.easygbs.easygbd.R
+import com.easygbs.easygbd.StretchVideoView
+import com.easygbs.easygbd.application.App
+import com.easygbs.easygbd.databinding.ActivityRecordListBinding
+import com.easygbs.easygbd.util.ScrUtil
+import com.easygbs.easygbd.viewmodel.activity.RecordListViewModel
+import com.gyf.immersionbar.ImmersionBar
+import java.io.File
+
+
+class RecordListActivity : BaseActivity() {
+    var TAG: String = RecordListActivity::class.java.getSimpleName()
+
+    lateinit var mRecordListViewModel: RecordListViewModel
+    var mActivityRecordListBinding: ActivityRecordListBinding? = null
+
+    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
+    override fun init() {
+        mActivityRecordListBinding =
+            DataBindingUtil.setContentView(this@RecordListActivity, R.layout.activity_record_list)
+
+        mRecordListViewModel = RecordListViewModel(App.getInstance())
+        mRecordListViewModel.setRecordListActivity(this@RecordListActivity)
+
+        mActivityRecordListBinding!!.mRecordListViewModel = mRecordListViewModel
+
+        ImmersionBar.with(this).statusBarDarkFont(true).init()
+
+        val layoutParams =
+            mActivityRecordListBinding!!.root.layoutParams as LinearLayout.LayoutParams
+        layoutParams.topMargin = ScrUtil.getStatusBarHeight(this@RecordListActivity)
+        mActivityRecordListBinding!!.root.layoutParams = layoutParams
+
+        loadRecordFiles()
+
+    }
+
+    private fun loadRecordFiles() {
+        // 获取 MP4 文件列表
+        val mp4Files = getMp4FilesInFilesDir()
+        // 设置适配器
+        mActivityRecordListBinding!!.rcvRecording.layoutManager =
+            GridLayoutManager(this@RecordListActivity, 3)
+
+        var mMp4Adapter = Mp4Adapter(this, mp4Files) { file ->
+            // 点击播放视频
+            showVideoDialog(file)
+        }
+        mActivityRecordListBinding!!.rcvRecording.adapter = mMp4Adapter
+    }
+
+    private fun getMp4FilesInFilesDir(): List<File> {
+        // 获取应用内部 files 目录
+        val filesDir = File(getExternalFilesDir(null), "easygbd")
+        // 过滤出扩展名为 .mp4 的文件
+        return filesDir.listFiles { _, name ->
+            name.endsWith(".mp4", ignoreCase = true)
+        }?.toList() ?: emptyList()
+    }
+
+    private fun showVideoDialog(file: File) {
+        // 创建无边距的 Dialog
+        val dialog = Dialog(this, R.style.FullWidthDialog)
+        dialog.setContentView(R.layout.dialog_video_player)
+        dialog.setCancelable(false)
+
+        // 获取屏幕宽度
+        val displayMetrics = DisplayMetrics()
+        windowManager.defaultDisplay.getMetrics(displayMetrics)
+        val screenWidth = displayMetrics.widthPixels
+
+        // 设置 Dialog 的宽高
+        val window = dialog.window
+        window?.setLayout(
+            ViewGroup.LayoutParams.MATCH_PARENT, // 宽度占满屏幕
+            dpToPx(this, 280)                   // 高度:200dp
+        )
+
+        // 获取 VideoView
+        val videoView: StretchVideoView = dialog.findViewById(R.id.videoView)
+
+        // 设置 VideoView 播放路径
+        videoView.setVideoURI(Uri.fromFile(file))
+        videoView.setOnPreparedListener {
+            videoView.start() // 视频准备好后自动播放
+        }
+
+        val closeRecordBtn: ImageView = dialog.findViewById(R.id.closeRecordBtn)
+
+        closeRecordBtn.setOnClickListener(View.OnClickListener {
+            dialog.cancel()
+        })
+
+        // 显示 Dialog
+        dialog.show()
+    }
+
+    private fun dpToPx(context: Context, dp: Int): Int {
+        val density = context.resources.displayMetrics.density
+        return (dp * density).toInt()
+    }
+
+    override fun finish() {
+        super.finish()
+    }
+
+}

+ 1 - 0
Android/app/src/main/java/com/easygbs/easygbd/adapter/AudiochannelAdapter.java

@@ -0,0 +1 @@
+package com.easygbs.easygbd.adapter;
import android.content.Context;
import android.widget.TextView;
import com.easygbs.easygbd.R;
import com.easygbs.easygbd.activity.MainActivity;
import com.easygbs.easygbd.bean.AudiochannelBean;
import com.easygbs.easygbd.viewadapter.CommonAdapter;
import com.easygbs.easygbd.viewadapter.ViewHolder;

import java.util.List;

public class AudiochannelAdapter extends CommonAdapter<AudiochannelBean> {
    public String TAG= AudiochannelAdapter.class.getSimpleName();
    private Context context;
    private MainActivity mMainActivity;
    public List<AudiochannelBean> AudiochannelBeanList;

    public AudiochannelAdapter(Context context, MainActivity mMainActivity, int layoutId, List<AudiochannelBean> AudiochannelBeanList){
        super(context, layoutId, AudiochannelBeanList);
        this.context = context;
        this.mMainActivity=mMainActivity;
        this.AudiochannelBeanList=AudiochannelBeanList;
    }

    @Override
    protected void convert(ViewHolder holder, AudiochannelBean mAudiochannelBean, int position) {
        TextView tvName = holder.getView(R.id.tvname);
        String name=mAudiochannelBean.getName();
        tvName.setText(name);

        int isst=mAudiochannelBean.getIsst();
        if(isst==0){
            tvName.setTextColor(context.getResources().getColor(R.color.color_4d4d4d));
        }else if(isst==1){
            tvName.setTextColor(context.getResources().getColor(R.color.color_01c9a7));
        }
    }
}

+ 1 - 0
Android/app/src/main/java/com/easygbs/easygbd/adapter/AudiocodeAdapter.java

@@ -0,0 +1 @@
+package com.easygbs.easygbd.adapter;

import android.content.Context;
import android.widget.TextView;

import com.easygbs.easygbd.R;
import com.easygbs.easygbd.activity.MainActivity;
import com.easygbs.easygbd.bean.AudiocodeBean;
import com.easygbs.easygbd.viewadapter.CommonAdapter;
import com.easygbs.easygbd.viewadapter.ViewHolder;

import java.util.List;

public class AudiocodeAdapter extends CommonAdapter<AudiocodeBean> {
    public String TAG= AudiocodeAdapter.class.getSimpleName();
    private Context context;
    private MainActivity mMainActivity;
    public List<AudiocodeBean> AudiocodeBeanList;

    public AudiocodeAdapter(Context context, MainActivity mMainActivity, int layoutId, List<AudiocodeBean> AudiocodeBeanList) {
        super(context, layoutId, AudiocodeBeanList);
        this.context = context;
        this.mMainActivity=mMainActivity;
        this.AudiocodeBeanList=AudiocodeBeanList;
    }

    @Override
    protected void convert(ViewHolder holder, AudiocodeBean mAudiocodeBean, int position) {
        TextView tvName = holder.getView(R.id.tvname);
        String name=mAudiocodeBean.getName();
        tvName.setText(name);

        int isst=mAudiocodeBean.getIsst();
        if(isst==0){
            tvName.setTextColor(context.getResources().getColor(R.color.color_4d4d4d));
        }else if(isst==1){
            tvName.setTextColor(context.getResources().getColor(R.color.color_01c9a7));
        }
    }
}

+ 1 - 0
Android/app/src/main/java/com/easygbs/easygbd/adapter/AudiocoderateAdapter.java

@@ -0,0 +1 @@
+package com.easygbs.easygbd.adapter;
import android.content.Context;
import android.widget.TextView;
import com.easygbs.easygbd.R;
import com.easygbs.easygbd.activity.MainActivity;
import com.easygbs.easygbd.bean.AudiocoderateBean;
import com.easygbs.easygbd.viewadapter.CommonAdapter;
import com.easygbs.easygbd.viewadapter.ViewHolder;
import java.util.List;

public class AudiocoderateAdapter extends CommonAdapter<AudiocoderateBean> {
    public String TAG= AudiocoderateAdapter.class.getSimpleName();
    private Context context;
    private MainActivity mMainActivity;
    public List<AudiocoderateBean> AudiocoderateBeanList;

    public AudiocoderateAdapter(Context context, MainActivity mMainActivity, int layoutId, List<AudiocoderateBean> AudiocoderateBeanList){
        super(context, layoutId, AudiocoderateBeanList);
        this.context = context;
        this.mMainActivity=mMainActivity;
        this.AudiocoderateBeanList=AudiocoderateBeanList;
    }

    @Override
    protected void convert(ViewHolder holder, AudiocoderateBean mAudiocoderateBean, int position) {
        TextView tvName = holder.getView(R.id.tvname);
        String name=mAudiocoderateBean.getName();
        tvName.setText(name);

        int isst=mAudiocoderateBean.getIsst();
        if(isst==0){
            tvName.setTextColor(context.getResources().getColor(R.color.color_4d4d4d));
        }else if(isst==1){
            tvName.setTextColor(context.getResources().getColor(R.color.color_01c9a7));
        }
    }
}

+ 14 - 0
Android/app/src/main/java/com/easygbs/easygbd/adapter/BindingAdapters.java

@@ -0,0 +1,14 @@
+package com.easygbs.easygbd.adapter;
+
+import android.text.TextWatcher;
+import android.widget.EditText;
+import androidx.databinding.BindingAdapter;
+
+public class BindingAdapters {
+    @BindingAdapter("textWatcher")
+    public static void setTextWatcher(EditText editText, TextWatcher textWatcher) {
+        if (editText != null) {
+            editText.addTextChangedListener(textWatcher);
+        }
+    }
+}

+ 1 - 0
Android/app/src/main/java/com/easygbs/easygbd/adapter/CameraAdapter.java

@@ -0,0 +1 @@
+package com.easygbs.easygbd.adapter;
import android.content.Context;
import android.widget.TextView;
import com.easygbs.easygbd.R;
import com.easygbs.easygbd.activity.MainActivity;
import com.easygbs.easygbd.bean.CameraBean;
import com.easygbs.easygbd.viewadapter.CommonAdapter;
import com.easygbs.easygbd.viewadapter.ViewHolder;
import java.util.List;

public class CameraAdapter extends CommonAdapter<CameraBean> {
    public String TAG= CameraAdapter.class.getSimpleName();
    private Context context;
    private MainActivity mMainActivity;
    public List<CameraBean> CameraBeanList;

    public CameraAdapter(Context context, MainActivity mMainActivity, int layoutId, List<CameraBean> CameraBeanList) {
        super(context, layoutId, CameraBeanList);
        this.context = context;
        this.mMainActivity=mMainActivity;
        this.CameraBeanList=CameraBeanList;
    }

    @Override
    protected void convert(ViewHolder holder, CameraBean mCameraBean, int position) {
        TextView tvName = holder.getView(R.id.tvname);
        String name=mCameraBean.getName();
        tvName.setText(name);

        int isst=mCameraBean.getIsst();
        if(isst==0){
            tvName.setTextColor(context.getResources().getColor(R.color.color_4d4d4d));
        }else if(isst==1){
            tvName.setTextColor(context.getResources().getColor(R.color.color_01c9a7));
        }
    }
}

Різницю між файлами не показано, бо вона завелика
+ 0 - 0
Android/app/src/main/java/com/easygbs/easygbd/adapter/ChannelAdapter.java


+ 1 - 0
Android/app/src/main/java/com/easygbs/easygbd/adapter/FramerateAdapter.java

@@ -0,0 +1 @@
+package com.easygbs.easygbd.adapter;
import android.content.Context;
import android.widget.TextView;
import com.easygbs.easygbd.R;
import com.easygbs.easygbd.activity.MainActivity;
import com.easygbs.easygbd.bean.FramerateBean;
import com.easygbs.easygbd.viewadapter.CommonAdapter;
import com.easygbs.easygbd.viewadapter.ViewHolder;
import java.util.List;

public class FramerateAdapter extends CommonAdapter<FramerateBean> {
    public String TAG= FramerateAdapter.class.getSimpleName();
    private Context context;
    private MainActivity mMainActivity;
    public List<FramerateBean> FramerateBeanList;

    public FramerateAdapter(Context context, MainActivity mMainActivity, int layoutId, List<FramerateBean> FramerateBeanList) {
        super(context, layoutId, FramerateBeanList);
        this.context = context;
        this.mMainActivity=mMainActivity;
        this.FramerateBeanList=FramerateBeanList;
    }

    @Override
    protected void convert(ViewHolder holder, FramerateBean mFramerateBean, int position) {
        TextView tvName = holder.getView(R.id.tvname);
        String name=mFramerateBean.getName();
        tvName.setText(name);

        int isst=mFramerateBean.getIsst();
        if(isst==0){
            tvName.setTextColor(context.getResources().getColor(R.color.color_4d4d4d));
        }else if(isst==1){
            tvName.setTextColor(context.getResources().getColor(R.color.color_01c9a7));
        }
    }
}

+ 1 - 0
Android/app/src/main/java/com/easygbs/easygbd/adapter/LocationfrequencyAdapter.java

@@ -0,0 +1 @@
+package com.easygbs.easygbd.adapter;
import android.content.Context;
import android.widget.TextView;
import com.easygbs.easygbd.R;
import com.easygbs.easygbd.activity.MainActivity;
import com.easygbs.easygbd.bean.LocationfrequencyBean;
import com.easygbs.easygbd.viewadapter.CommonAdapter;
import com.easygbs.easygbd.viewadapter.ViewHolder;
import java.util.List;

public class LocationfrequencyAdapter extends CommonAdapter<LocationfrequencyBean> {
    public String TAG= LocationfrequencyAdapter.class.getSimpleName();
    private Context context;
    private MainActivity mMainActivity;
    public List<LocationfrequencyBean> LocationfrequencyBeanList;

    public LocationfrequencyAdapter(Context context, MainActivity mMainActivity, int layoutId, List<LocationfrequencyBean> LocationfrequencyBeanList){
        super(context, layoutId, LocationfrequencyBeanList);
        this.context = context;
        this.mMainActivity=mMainActivity;
        this.LocationfrequencyBeanList=LocationfrequencyBeanList;
    }

    @Override
    protected void convert(ViewHolder holder, LocationfrequencyBean mLocationfrequencyBean, int position) {
        TextView tvName = holder.getView(R.id.tvname);
        String name=mLocationfrequencyBean.getName();
        tvName.setText(name);

        int isst=mLocationfrequencyBean.getIsst();
        if(isst==0){
            tvName.setTextColor(context.getResources().getColor(R.color.color_4d4d4d));
        }else if(isst==1){
            tvName.setTextColor(context.getResources().getColor(R.color.color_01c9a7));
        }
    }
}

+ 1 - 0
Android/app/src/main/java/com/easygbs/easygbd/adapter/LoglevelAdapter.java

@@ -0,0 +1 @@
+package com.easygbs.easygbd.adapter;
import android.content.Context;
import android.widget.TextView;
import com.easygbs.easygbd.R;
import com.easygbs.easygbd.activity.MainActivity;
import com.easygbs.easygbd.bean.LoglevelBean;
import com.easygbs.easygbd.viewadapter.CommonAdapter;
import com.easygbs.easygbd.viewadapter.ViewHolder;
import java.util.List;

public class LoglevelAdapter extends CommonAdapter<LoglevelBean> {
    public String TAG= LoglevelAdapter.class.getSimpleName();
    private Context context;
    private MainActivity mMainActivity;
    public List<LoglevelBean> LoglevelBeanList;

    public LoglevelAdapter(Context context, MainActivity mMainActivity, int layoutId, List<LoglevelBean> LoglevelBeanList) {
        super(context, layoutId,LoglevelBeanList);
        this.context = context;
        this.mMainActivity=mMainActivity;
        this.LoglevelBeanList=LoglevelBeanList;
    }

    @Override
    protected void convert(ViewHolder holder, LoglevelBean mLoglevelBean, int position) {
        TextView tvname = holder.getView(R.id.tvname);
        String name=mLoglevelBean.getName();
        tvname.setText(name);

        int isst=mLoglevelBean.getIsst();
        if(isst==0){
            tvname.setTextColor(context.getResources().getColor(R.color.color_4d4d4d));
        }else if(isst==1){
            tvname.setTextColor(context.getResources().getColor(R.color.color_01c9a7));
        }
    }
}

+ 45 - 0
Android/app/src/main/java/com/easygbs/easygbd/adapter/Mp4Adapter.kt

@@ -0,0 +1,45 @@
+import android.content.Context
+import android.net.Uri
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.recyclerview.widget.RecyclerView
+import com.easygbs.easygbd.R
+import java.io.File
+
+class Mp4Adapter(
+    private val context: Context,
+    private val mp4Files: List<File>,
+    private val onItemClick: (File) -> Unit
+) : RecyclerView.Adapter<Mp4Adapter.Mp4ViewHolder>() {
+
+    class Mp4ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
+        val thumbnail: ImageView = view.findViewById(R.id.thumbnail)
+//        val fileName: TextView = view.findViewById(R.id.fileName)
+    }
+
+    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Mp4ViewHolder {
+        val view = LayoutInflater.from(context).inflate(R.layout.item_mp4, parent, false)
+        return Mp4ViewHolder(view)
+    }
+
+    override fun onBindViewHolder(holder: Mp4ViewHolder, position: Int) {
+        val file = mp4Files[position]
+//        holder.fileName.text = file.name
+
+        // 使用 ThumbnailUtils 生成缩略图
+        val thumbnailBitmap = android.media.ThumbnailUtils.createVideoThumbnail(
+            file.absolutePath,
+            android.provider.MediaStore.Video.Thumbnails.MINI_KIND
+        )
+        holder.thumbnail.setImageBitmap(thumbnailBitmap)
+
+        holder.itemView.setOnClickListener {
+            onItemClick(file) // 点击事件回调
+        }
+    }
+
+    override fun getItemCount(): Int = mp4Files.size
+}

+ 1 - 0
Android/app/src/main/java/com/easygbs/easygbd/adapter/ResolutionAdapter.java

@@ -0,0 +1 @@
+package com.easygbs.easygbd.adapter;
import android.content.Context;
import android.widget.TextView;
import com.easygbs.easygbd.R;
import com.easygbs.easygbd.activity.MainActivity;
import com.easygbs.easygbd.bean.ResolutionBean;
import com.easygbs.easygbd.viewadapter.CommonAdapter;
import com.easygbs.easygbd.viewadapter.ViewHolder;

import java.util.List;

public class ResolutionAdapter extends CommonAdapter<ResolutionBean> {
    public String TAG= ResolutionAdapter.class.getSimpleName();
    private Context context;
    private MainActivity mMainActivity;
    public List<ResolutionBean> ResolutionBeanList;

    public ResolutionAdapter(Context context, MainActivity mMainActivity, int layoutId, List<ResolutionBean> ResolutionBeanList) {
        super(context, layoutId, ResolutionBeanList);
        this.context = context;
        this.mMainActivity=mMainActivity;
        this.ResolutionBeanList=ResolutionBeanList;
    }

    @Override
    protected void convert(ViewHolder holder, ResolutionBean mResolutionBean, int position) {
        TextView tvName = holder.getView(R.id.tvname);
        String name=mResolutionBean.getName();
        tvName.setText(name);

        int isst=mResolutionBean.getIsst();
        if(isst==0){
            tvName.setTextColor(context.getResources().getColor(R.color.color_4d4d4d));
        }else if(isst==1){
            tvName.setTextColor(context.getResources().getColor(R.color.color_01c9a7));
        }
    }
}

+ 1 - 0
Android/app/src/main/java/com/easygbs/easygbd/adapter/SamplingrateAdapter.java

@@ -0,0 +1 @@
+package com.easygbs.easygbd.adapter;
import android.content.Context;
import android.widget.TextView;
import com.easygbs.easygbd.R;
import com.easygbs.easygbd.activity.MainActivity;
import com.easygbs.easygbd.bean.SamplingrateBean;
import com.easygbs.easygbd.viewadapter.CommonAdapter;
import com.easygbs.easygbd.viewadapter.ViewHolder;
import java.util.List;

public class SamplingrateAdapter extends CommonAdapter<SamplingrateBean> {
    public String TAG= SamplingrateAdapter.class.getSimpleName();
    private Context context;
    private MainActivity mMainActivity;
    public List<SamplingrateBean> SamplingrateBeanList;

    public SamplingrateAdapter(Context context, MainActivity mMainActivity, int layoutId, List<SamplingrateBean> SamplingrateBeanList){
        super(context,layoutId,SamplingrateBeanList);
        this.context = context;
        this.mMainActivity=mMainActivity;
        this.SamplingrateBeanList=SamplingrateBeanList;
    }

    @Override
    protected void convert(ViewHolder holder, SamplingrateBean mSamplingrateBean, int position) {
        TextView tvName = holder.getView(R.id.tvname);
        String name=mSamplingrateBean.getName();
        tvName.setText(name);

        int isst=mSamplingrateBean.getIsst();
        if(isst==0){
            tvName.setTextColor(context.getResources().getColor(R.color.color_4d4d4d));
        }else if(isst==1){
            tvName.setTextColor(context.getResources().getColor(R.color.color_01c9a7));
        }
    }
}

+ 1 - 0
Android/app/src/main/java/com/easygbs/easygbd/adapter/TranproAdapter.java

@@ -0,0 +1 @@
+package com.easygbs.easygbd.adapter;
import android.content.Context;
import android.widget.TextView;
import com.easygbs.easygbd.R;
import com.easygbs.easygbd.activity.MainActivity;
import com.easygbs.easygbd.bean.TranproBean;
import com.easygbs.easygbd.viewadapter.CommonAdapter;
import com.easygbs.easygbd.viewadapter.ViewHolder;
import java.util.List;

public class TranproAdapter extends CommonAdapter<TranproBean> {
    public String TAG= TranproAdapter.class.getSimpleName();
    private Context context;
    private MainActivity mSetActivity;
    public List<TranproBean> SelectCameraResolutionBeanList;

    public TranproAdapter(Context context, MainActivity mSetActivity, int layoutId, List<TranproBean> SelectCameraResolutionBeanList) {
        super(context, layoutId, SelectCameraResolutionBeanList);
        this.context = context;
        this.mSetActivity=mSetActivity;
        this.SelectCameraResolutionBeanList=SelectCameraResolutionBeanList;
    }

    @Override
    protected void convert(ViewHolder holder, TranproBean mTranproBean, int position) {
        TextView tvName = holder.getView(R.id.tvname);
        String name=mTranproBean.getName();
        tvName.setText(name);

        int isst=mTranproBean.getIsst();
        if(isst==0){
            tvName.setTextColor(context.getResources().getColor(R.color.color_4d4d4d));
        }else if(isst==1){
            tvName.setTextColor(context.getResources().getColor(R.color.color_01c9a7));
        }
    }
}

+ 1 - 0
Android/app/src/main/java/com/easygbs/easygbd/adapter/VerAdapter.java

@@ -0,0 +1 @@
+package com.easygbs.easygbd.adapter;
import android.content.Context;
import android.widget.TextView;
import com.easygbs.easygbd.R;
import com.easygbs.easygbd.activity.MainActivity;
import com.easygbs.easygbd.bean.VerBean;
import com.easygbs.easygbd.viewadapter.CommonAdapter;
import com.easygbs.easygbd.viewadapter.ViewHolder;
import java.util.List;

public class VerAdapter extends CommonAdapter<VerBean> {
    public String TAG= VerAdapter.class.getSimpleName();
    private Context context;
    private MainActivity mMainActivity;
    public List<VerBean> SelectCameraResolutionBeanList;

    public VerAdapter(Context context, MainActivity mMainActivity, int layoutId, List<VerBean> SelectCameraResolutionBeanList) {
        super(context, layoutId, SelectCameraResolutionBeanList);
        this.context = context;
        this.mMainActivity=mMainActivity;
        this.SelectCameraResolutionBeanList=SelectCameraResolutionBeanList;
    }

    @Override
    protected void convert(ViewHolder holder, VerBean mSelectCameraResolutionBean, int position) {
        TextView tvName = holder.getView(R.id.tvname);
        String name=mSelectCameraResolutionBean.getName();
        tvName.setText(name);

        int isst=mSelectCameraResolutionBean.getIsst();
        if(isst==0){
            tvName.setTextColor(context.getResources().getColor(R.color.color_4d4d4d));
        }else if(isst==1){
            tvName.setTextColor(context.getResources().getColor(R.color.color_01c9a7));
        }
    }
}

+ 1 - 0
Android/app/src/main/java/com/easygbs/easygbd/adapter/VideoCodeAdapter.java

@@ -0,0 +1 @@
+package com.easygbs.easygbd.adapter;
import android.content.Context;
import android.widget.TextView;
import com.easygbs.easygbd.R;
import com.easygbs.easygbd.activity.MainActivity;
import com.easygbs.easygbd.bean.VideoCodeBean;
import com.easygbs.easygbd.viewadapter.CommonAdapter;
import com.easygbs.easygbd.viewadapter.ViewHolder;
import java.util.List;

public class VideoCodeAdapter extends CommonAdapter<VideoCodeBean> {
    public String TAG= VideoCodeAdapter.class.getSimpleName();
    private Context context;
    private MainActivity mMainActivity;
    public List<VideoCodeBean> VideoCodeBeanList;

    public VideoCodeAdapter(Context context, MainActivity mMainActivity, int layoutId, List<VideoCodeBean> VideoCodeBeanList) {
        super(context, layoutId, VideoCodeBeanList);
        this.context = context;
        this.mMainActivity=mMainActivity;
        this.VideoCodeBeanList=VideoCodeBeanList;
    }

    @Override
    protected void convert(ViewHolder holder, VideoCodeBean mVideoCodeBean, int position) {
        TextView tvName = holder.getView(R.id.tvname);
        String name=mVideoCodeBean.getName();
        tvName.setText(name);

        int isst=mVideoCodeBean.getIsst();
        if(isst==0){
            tvName.setTextColor(context.getResources().getColor(R.color.color_4d4d4d));
        }else if(isst==1){
            tvName.setTextColor(context.getResources().getColor(R.color.color_01c9a7));
        }
    }
}

+ 1 - 0
Android/app/src/main/java/com/easygbs/easygbd/adapter/VideocoderateAdapter.java

@@ -0,0 +1 @@
+package com.easygbs.easygbd.adapter;
import android.content.Context;
import android.widget.TextView;
import com.easygbs.easygbd.R;
import com.easygbs.easygbd.activity.MainActivity;
import com.easygbs.easygbd.bean.VideocoderateBean;
import com.easygbs.easygbd.viewadapter.CommonAdapter;
import com.easygbs.easygbd.viewadapter.ViewHolder;
import java.util.List;

public class VideocoderateAdapter extends CommonAdapter<VideocoderateBean> {
    public String TAG= VideocoderateAdapter.class.getSimpleName();
    private Context context;
    private MainActivity mMainActivity;
    public List<VideocoderateBean> VideocoderateBeanList;

    public VideocoderateAdapter(Context context, MainActivity mMainActivity, int layoutId, List<VideocoderateBean> VideocoderateBeanList) {
        super(context, layoutId, VideocoderateBeanList);
        this.context = context;
        this.mMainActivity=mMainActivity;
        this.VideocoderateBeanList=VideocoderateBeanList;
    }

    @Override
    protected void convert(ViewHolder holder, VideocoderateBean mVideocoderateBean, int position) {
        TextView tvName = holder.getView(R.id.tvname);
        String name=mVideocoderateBean.getName();
        tvName.setText(name);

        int isst=mVideocoderateBean.getIsst();
        if(isst==0){
            tvName.setTextColor(context.getResources().getColor(R.color.color_4d4d4d));
        }else if(isst==1){
            tvName.setTextColor(context.getResources().getColor(R.color.color_01c9a7));
        }
    }
}

+ 1 - 0
Android/app/src/main/java/com/easygbs/easygbd/application/App.java

@@ -0,0 +1 @@
+package com.easygbs.easygbd.application;
import android.app.Application;
import android.content.Context;
import android.content.res.AssetManager;
import android.util.Log;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;

public class App extends Application{
	private final static String TAG= App.class.getSimpleName();
	public static final String CHANNEL_CAMERA = "camera";

	public static App instance;

	public void onCreate() {
		super.onCreate();
		instance=this;

		File youyuan = getFileStreamPath("SIMYOU.ttf");
		if (!youyuan.exists()) {
			AssetManager mAssetManager = getAssets();

			try{
				InputStream is = mAssetManager.open("zk/SIMYOU.ttf");
				FileOutputStream os = openFileOutput("SIMYOU.ttf",MODE_PRIVATE);
				byte[] buffer = new byte[1024];
				int len;

				while((len = is.read(buffer)) != -1){
					os.write(buffer,0,len);
				}

				os.close();
				is.close();
			}catch(Exception e){
				Log.e(TAG,"Exception  "+e.toString());
			}
		}
	}

	public static App getInstance() {
		return instance;
	}

} 

+ 22 - 0
Android/app/src/main/java/com/easygbs/easygbd/bean/AudiochannelBean.java

@@ -0,0 +1,22 @@
+package com.easygbs.easygbd.bean;
+
+public class AudiochannelBean {
+    private String name;
+    private int isst;
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public int getIsst() {
+        return isst;
+    }
+
+    public void setIsst(int isst) {
+        this.isst = isst;
+    }
+}

+ 22 - 0
Android/app/src/main/java/com/easygbs/easygbd/bean/AudiocodeBean.java

@@ -0,0 +1,22 @@
+package com.easygbs.easygbd.bean;
+
+public class AudiocodeBean {
+    private String name;
+    private int isst;
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public int getIsst() {
+        return isst;
+    }
+
+    public void setIsst(int isst) {
+        this.isst = isst;
+    }
+}

+ 22 - 0
Android/app/src/main/java/com/easygbs/easygbd/bean/AudiocoderateBean.java

@@ -0,0 +1,22 @@
+package com.easygbs.easygbd.bean;
+
+public class AudiocoderateBean {
+    private String name;
+    private int isst;
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public int getIsst() {
+        return isst;
+    }
+
+    public void setIsst(int isst) {
+        this.isst = isst;
+    }
+}

+ 22 - 0
Android/app/src/main/java/com/easygbs/easygbd/bean/CameraBean.java

@@ -0,0 +1,22 @@
+package com.easygbs.easygbd.bean;
+
+public class CameraBean {
+    private String name;
+    private int isst;
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public int getIsst() {
+        return isst;
+    }
+
+    public void setIsst(int isst) {
+        this.isst = isst;
+    }
+}

+ 22 - 0
Android/app/src/main/java/com/easygbs/easygbd/bean/FramerateBean.java

@@ -0,0 +1,22 @@
+package com.easygbs.easygbd.bean;
+
+public class FramerateBean {
+    private String name;
+    private int isst;
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public int getIsst() {
+        return isst;
+    }
+
+    public void setIsst(int isst) {
+        this.isst = isst;
+    }
+}

+ 22 - 0
Android/app/src/main/java/com/easygbs/easygbd/bean/LocationfrequencyBean.java

@@ -0,0 +1,22 @@
+package com.easygbs.easygbd.bean;
+
+public class LocationfrequencyBean {
+    private String name;
+    private int isst;
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public int getIsst() {
+        return isst;
+    }
+
+    public void setIsst(int isst) {
+        this.isst = isst;
+    }
+}

+ 22 - 0
Android/app/src/main/java/com/easygbs/easygbd/bean/LoglevelBean.java

@@ -0,0 +1,22 @@
+package com.easygbs.easygbd.bean;
+
+public class LoglevelBean {
+    private String name;
+    private int isst;
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public int getIsst() {
+        return isst;
+    }
+
+    public void setIsst(int isst) {
+        this.isst = isst;
+    }
+}

+ 22 - 0
Android/app/src/main/java/com/easygbs/easygbd/bean/ResolutionBean.java

@@ -0,0 +1,22 @@
+package com.easygbs.easygbd.bean;
+
+public class ResolutionBean {
+    private String name;
+    private int isst;
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public int getIsst() {
+        return isst;
+    }
+
+    public void setIsst(int isst) {
+        this.isst = isst;
+    }
+}

+ 22 - 0
Android/app/src/main/java/com/easygbs/easygbd/bean/SamplingrateBean.java

@@ -0,0 +1,22 @@
+package com.easygbs.easygbd.bean;
+
+public class SamplingrateBean {
+    private String name;
+    private int isst;
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public int getIsst() {
+        return isst;
+    }
+
+    public void setIsst(int isst) {
+        this.isst = isst;
+    }
+}

+ 22 - 0
Android/app/src/main/java/com/easygbs/easygbd/bean/TranproBean.java

@@ -0,0 +1,22 @@
+package com.easygbs.easygbd.bean;
+
+public class TranproBean {
+    private String name;
+    private int isst;
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public int getIsst() {
+        return isst;
+    }
+
+    public void setIsst(int isst) {
+        this.isst = isst;
+    }
+}

+ 22 - 0
Android/app/src/main/java/com/easygbs/easygbd/bean/VerBean.java

@@ -0,0 +1,22 @@
+package com.easygbs.easygbd.bean;
+
+public class VerBean {
+    private String name;
+    private int isst;
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public int getIsst() {
+        return isst;
+    }
+
+    public void setIsst(int isst) {
+        this.isst = isst;
+    }
+}

+ 22 - 0
Android/app/src/main/java/com/easygbs/easygbd/bean/VideoCodeBean.java

@@ -0,0 +1,22 @@
+package com.easygbs.easygbd.bean;
+
+public class VideoCodeBean {
+    private String name;
+    private int isst;
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public int getIsst() {
+        return isst;
+    }
+
+    public void setIsst(int isst) {
+        this.isst = isst;
+    }
+}

+ 22 - 0
Android/app/src/main/java/com/easygbs/easygbd/bean/VideocoderateBean.java

@@ -0,0 +1,22 @@
+package com.easygbs.easygbd.bean;
+
+public class VideocoderateBean {
+    private String name;
+    private int isst;
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public int getIsst() {
+        return isst;
+    }
+
+    public void setIsst(int isst) {
+        this.isst = isst;
+    }
+}

+ 32 - 0
Android/app/src/main/java/com/easygbs/easygbd/common/Constant.java

@@ -0,0 +1,32 @@
+package com.easygbs.easygbd.common;
+
+import android.util.Log;
+
+import java.util.List;
+
+public final class Constant {
+    public static String DIR = "easygbd";
+    public static String Root = "";
+    public static String ROOT = "root";
+    public static String de = "";
+    public static String DE = "de";
+    public static String LOGSTATUS = "logstatus";
+    public static String LOGLEV = "loglev";
+
+    public static final int TOINIT = 1;
+    public static final int DATA = 2;
+    public static final int REQUESTPER = 3;
+    public static final int OPENCAMERA = 4;
+    public static final int LOG = 5;
+    public static final int STARTPRINTLOG = 6;
+    public final static int QRCODE = 7;
+    public static final int CHANGEDATA = 8;
+    public final static int STARTORSTOPSTREAM = 9;
+    public final static int CHANGE_DATA_RESTART = 10;
+    public final static int START_OR_STOP_RECORD = 11;
+    public final static int RECORD_BACK = 12;
+
+    public static int ChanM = 1000;
+    public static int ChanID = 1;
+    public static int ChanF = 0;
+}

Різницю між файлами не показано, бо вона завелика
+ 0 - 0
Android/app/src/main/java/com/easygbs/easygbd/dao/ChanOp.java


+ 13 - 0
Android/app/src/main/java/com/easygbs/easygbd/dao/DCon.java

@@ -0,0 +1,13 @@
+package com.easygbs.easygbd.dao;
+
+public final class DCon {
+    public static final int Chanqueryct=1001;
+    public static final int Chanjudinsert=1002;
+    public static final int Chaninsert=1003;
+    public static final int Chanqueryrow=1004;
+    public static final int Chanqueryeffect=1005;
+    public static final int Chanupdatedependuid=1006;
+    public static final int Chanquerymaxuid=1007;
+    public static final int ChanCount=1008;
+    public static final int ChanDelete=1009;
+}

+ 95 - 0
Android/app/src/main/java/com/easygbs/easygbd/dao/DOpe.java

@@ -0,0 +1,95 @@
+package com.easygbs.easygbd.dao;
+
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.util.Log;
+
+import com.easygbs.easygbd.dao.bean.Chan;
+import com.easygbs.easygbd.dao.ve.UVO;
+
+import java.io.File;
+
+public class DOpe extends SQLiteOpenHelper {
+    private static final String TAG = DOpe.class.getSimpleName();
+    private static DOpe instance = null;
+    private final SQLiteDatabase mSQLiteDatabase;
+    public static int VERSION = 1;
+    public ChanOp mChanOp;
+
+    private DOpe(Context mContext, String dir) {
+        super(mContext, getName(dir), null, VERSION);
+
+        mSQLiteDatabase = DOpe.this.getWritableDatabase();
+        mChanOp = ChanOp.Instance(DOpe.this, mSQLiteDatabase);
+    }
+
+    public static DOpe Instance(Context mContext, String dir) {
+        if (instance == null) {
+            instance = new DOpe(mContext, dir);
+        }
+
+        return instance;
+    }
+
+    private static String getName(String dir) {
+        String dn;
+        String ph = dir + File.separator + "de";
+        File db = new File(ph);
+        if (!db.exists()) {
+            db.mkdirs();
+        }
+
+        dn = ph + File.separator + "e.db";
+        return dn;
+    }
+
+    @Override
+    public void onCreate(SQLiteDatabase mSQLiteDatabase) {
+        UVO.Instance().up(mSQLiteDatabase);
+    }
+
+    @Override
+    public void onUpgrade(SQLiteDatabase mSQLiteDatabase, int arg1, int arg2) {
+
+    }
+
+    public Object OperaDatabase(int value, Object... obj) {
+        Object result = null;
+        try {
+            switch (value) {
+                case DCon.Chanqueryct:
+                    result = mChanOp.Chanqueryct();
+                    break;
+                case DCon.Chanjudinsert:
+                    result = mChanOp.Chanjudinsert((Chan) obj[0]);
+                    break;
+                case DCon.Chaninsert:
+                    result = mChanOp.MametInsert((Chan) obj[0]);
+                    break;
+                case DCon.Chanqueryrow:
+                    result = mChanOp.Chanqueryrow((int) obj[0]);
+                    break;
+                case DCon.Chanqueryeffect:
+                    result = mChanOp.Chanqueryeffect();
+                    break;
+                case DCon.Chanquerymaxuid:
+                    result = mChanOp.Chanquerymaxuid();
+                    break;
+                case DCon.Chanupdatedependuid:
+                    result = mChanOp.Chanupdatedependuid((Chan) obj[0]);
+                    break;
+                case DCon.ChanCount:
+                    result = mChanOp.getCount();
+                    break;
+                case DCon.ChanDelete:
+                    result = mChanOp.ChanDelete((Chan) obj[0]);
+                    break;
+            }
+        } catch (Exception e) {
+            Log.e(TAG, "OperaDatabase Exception  " + e);
+        }
+
+        return result;
+    }
+}

+ 5 - 0
Android/app/src/main/java/com/easygbs/easygbd/dao/TCon.java

@@ -0,0 +1,5 @@
+package com.easygbs.easygbd.dao;
+
+public final class TCon {
+    public static final String chan="CREATE TABLE chan (ec INTEGER PRIMARY KEY AUTOINCREMENT,uid INTEGER,cid TEXT,na TEXT,sta INTEGER)";
+}

+ 59 - 0
Android/app/src/main/java/com/easygbs/easygbd/dao/bean/Chan.java

@@ -0,0 +1,59 @@
+package com.easygbs.easygbd.dao.bean;
+
+public class Chan{
+	private int ec;
+	private int uid;
+	private String cid;
+	private String na;
+	//0:无效  1:有效
+	private int sta;
+	private int selected;
+
+	public int getEc() {
+		return ec;
+	}
+
+	public void setEc(int ec) {
+		this.ec = ec;
+	}
+
+	public int getUid() {
+		return uid;
+	}
+
+	public void setUid(int uid) {
+		this.uid = uid;
+	}
+
+	public String getCid() {
+		return cid;
+	}
+
+	public void setCid(String cid) {
+		this.cid = cid;
+	}
+
+	public String getNa() {
+		return na;
+	}
+
+	public void setNa(String na) {
+		this.na = na;
+	}
+
+	public int getSta() {
+		return sta;
+	}
+
+	public void setSta(int sta) {
+		this.sta = sta;
+	}
+
+	public int getSelected() {
+		return selected;
+	}
+
+	public void setSelected(int selected) {
+		this.selected = selected;
+	}
+}

+ 26 - 0
Android/app/src/main/java/com/easygbs/easygbd/dao/ve/UVO.java

@@ -0,0 +1,26 @@
+package com.easygbs.easygbd.dao.ve;
+import android.database.sqlite.SQLiteDatabase;
+
+import com.easygbs.easygbd.dao.TCon;
+
+public class UVO {
+    private static final String TAG = UVO.class.getSimpleName();
+    private static UVO instance = null;
+    private SQLiteDatabase mSQLiteDatabase;
+
+    public static UVO Instance(){
+        if(instance == null){
+            instance = new UVO();
+        }
+        return instance;
+    }
+
+    public UVO(){
+
+    }
+
+    public void up(SQLiteDatabase mSQLiteDatabase){
+        this.mSQLiteDatabase = mSQLiteDatabase;
+        mSQLiteDatabase.execSQL(TCon.chan);
+    }
+}

+ 141 - 0
Android/app/src/main/java/com/easygbs/easygbd/dialog/ChanDialog.java

@@ -0,0 +1,141 @@
+package com.easygbs.easygbd.dialog;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.easygbs.easygbd.R;
+import com.easygbs.easygbd.activity.MainActivity;
+import com.easygbs.easygbd.dao.bean.Chan;
+import com.easygbs.easygbd.fragment.ChannelSettingsFragment;
+
+public class ChanDialog extends Dialog {
+    public String TAG = ChanDialog.class.getSimpleName();
+    public Context mContext;
+    public MainActivity mMainActivity;
+    public ChannelSettingsFragment mChannelSettingsFragment;
+    public Chan mChan;
+    public View mContextView;
+    public LayoutInflater mInflater;
+
+    public EditText etchanid;
+
+    public EditText etchanna;
+
+    public Button btnsu;
+
+    public TextView btnca;
+
+    public TextView btnde;
+
+    public static final int DISMISS = 1;
+
+    public Handler mHandler = new Handler() {
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case DISMISS:
+                    ChanDialog.this.dismiss();
+                    break;
+            }
+        }
+    };
+
+    public ChanDialog(Context mContext, MainActivity mMainActivity, ChannelSettingsFragment mChannelSettingsFragment, Chan mChan) {
+        this(mContext, R.style.dialog_style);
+        this.mContext = mContext;
+        this.mMainActivity = mMainActivity;
+        this.mChannelSettingsFragment = mChannelSettingsFragment;
+        this.mChan = mChan;
+    }
+
+    public ChanDialog(Context context, int theme) {
+        super(context, theme);
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        ChanDialog.this.requestWindowFeature(Window.FEATURE_NO_TITLE);
+        init(mContext);
+        Window window = ChanDialog.this.getWindow();
+        WindowManager.LayoutParams lp = window.getAttributes();
+
+        lp.width = (int) (mMainActivity.getWidth() - mMainActivity.getResources().getDimension(R.dimen.dp_40));
+        lp.height = (int) (mMainActivity.getResources().getDimension(R.dimen.dp_220));
+        lp.alpha = (float) 1.0;
+        window.setAttributes(lp);
+        window.setGravity(Gravity.CENTER);
+    }
+
+    private void init(Context context) {
+        this.mContext = context;
+        mInflater = LayoutInflater.from(mContext);
+        mContextView = mInflater.inflate(R.layout.dialog_chan, null, false);
+
+        setContentView(mContextView);
+
+        etchanid = mContextView.findViewById(R.id.etchanid);
+
+        etchanna = mContextView.findViewById(R.id.etchanna);
+
+        btnsu = mContextView.findViewById(R.id.btnsu);
+        btnsu.setOnClickListener(new Button.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+
+                String id = etchanid.getText().toString().trim();
+
+                if (id.length() != 20) {
+                    Toast.makeText(context, "通道ID长度必须20位!!!", Toast.LENGTH_SHORT).show();
+                    return;
+                }
+                String na = etchanna.getText().toString().trim();
+                mChan.setCid(id);
+                mChan.setNa(na);
+                mChannelSettingsFragment.modify(mChan);
+
+                mHandler.sendEmptyMessage(DISMISS);
+            }
+        });
+
+        btnca = mContextView.findViewById(R.id.btnca);
+        btnca.setOnClickListener(new Button.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                mChannelSettingsFragment.cancel();
+
+                mHandler.sendEmptyMessage(DISMISS);
+            }
+        });
+
+        btnde = mContextView.findViewById(R.id.btnde);
+        btnde.setOnClickListener(new Button.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+
+                mChannelSettingsFragment.delete(mChan);
+
+                mHandler.sendEmptyMessage(DISMISS);
+            }
+        });
+
+        etchanid.setText(mChan.getCid());
+
+        etchanna.setText(mChan.getNa());
+
+        ChanDialog.this.setCancelable(false);
+    }
+}

+ 94 - 0
Android/app/src/main/java/com/easygbs/easygbd/fragment/AboutFragment.java

@@ -0,0 +1,94 @@
+package com.easygbs.easygbd.fragment;
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+
+import androidx.fragment.app.Fragment;
+import com.easygbs.easygbd.R;
+import com.easygbs.easygbd.activity.MainActivity;
+import com.easygbs.easygbd.databinding.FragmentAboutBinding;
+import com.easygbs.easygbd.util.ScrUtil;
+import com.easygbs.easygbd.viewmodel.fragment.AboutViewModel;
+
+public class AboutFragment extends Fragment {
+	public String TAG= AboutFragment.class.getSimpleName();
+	public MainActivity mMainActivity;
+	public FragmentAboutBinding mFragmentAboutBinding;
+	public AboutViewModel mAboutViewModel;
+
+	@Override
+	public void onAttach(Activity activity) {
+		super.onAttach(activity);
+		mMainActivity= (MainActivity) activity;
+	}
+
+	@Override
+	public void onCreate(Bundle savedInstanceState) {
+		super.onCreate(savedInstanceState);
+	}
+
+	@Override
+	public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {
+		mFragmentAboutBinding = FragmentAboutBinding.inflate(inflater);
+
+		mAboutViewModel = new AboutViewModel(mMainActivity,AboutFragment.this);
+		mFragmentAboutBinding.setViewModel(mAboutViewModel);
+
+		View mView=mFragmentAboutBinding.getRoot();
+		return mView;
+	}
+
+	@Override
+	public void onViewCreated(View view, Bundle savedInstanceState) {
+		super.onViewCreated(view, savedInstanceState);
+	}
+
+	@Override
+	public void onActivityCreated(Bundle savedInstanceState) {
+		super.onActivityCreated(savedInstanceState);
+	}
+
+	@Override
+	public void onStart() {
+		super.onStart();
+	}
+
+	@Override
+	public void onResume() {
+		super.onResume();
+	}
+
+	@Override
+	public void onPause() {
+		super.onPause();
+	}
+
+	@Override
+	public void onStop() {
+		super.onStop();
+	}
+
+	@Override
+	public void onDestroyView() {
+		super.onDestroyView();
+	}
+
+	@Override
+	public void onDestroy() {
+		super.onDestroy();
+	}
+
+	@Override
+	public void onDetach() {
+		super.onDetach();
+	}
+
+	public void show(){
+		LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) mFragmentAboutBinding.llroot.getLayoutParams();
+		layoutParams.topMargin = ScrUtil.getStatusBarHeight(mMainActivity);
+		mFragmentAboutBinding.llroot.setLayoutParams(layoutParams);
+	}
+}

+ 568 - 0
Android/app/src/main/java/com/easygbs/easygbd/fragment/BasicSettingsFragment.java

@@ -0,0 +1,568 @@
+package com.easygbs.easygbd.fragment;
+
+import android.Manifest;
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.ActivityInfo;
+import android.graphics.SurfaceTexture;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.Surface;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Toast;
+
+import androidx.fragment.app.Fragment;
+
+import com.easygbs.easygbd.R;
+import com.easygbs.easygbd.common.Constant;
+import com.easygbs.easygbd.databinding.FragmentBasicsettingsBinding;
+import com.easygbs.easygbd.activity.MainActivity;
+import com.easygbs.easygbd.logger.LogLevel;
+import com.easygbs.easygbd.logger.Logger;
+import com.easygbs.easygbd.util.PeUtil;
+import com.easygbs.easygbd.push.MediaStream;
+import com.easygbs.easygbd.util.SPHelper;
+
+import org.easydarwin.util.SPUtil;
+
+import com.easygbs.easygbd.viewmodel.fragment.BasicSettingsViewModel;
+import com.easygbs.easygbd.service.*;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.List;
+
+
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+
+public class BasicSettingsFragment extends Fragment {
+    private String TAG = BasicSettingsFragment.class.getSimpleName();
+    private MainActivity mMainActivity;
+    public FragmentBasicsettingsBinding mFragmentBasicsettingsBinding;
+    private BasicSettingsViewModel mBasicSettingsViewModel;
+
+    private int width = 1920;
+    private int height = 1080;
+
+    private BackgroundCameraService mService;
+    private ServiceConnection conn;
+
+    private printThread mprintThread = null;
+
+    public static boolean running = true;
+
+    public List<String> logList = new ArrayList<String>();
+
+    public Intent intent;
+
+    public Intent intent1;
+
+    public Handler mHandler = new Handler() {
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case Constant.OPENCAMERA:
+                    initCamera();
+                    break;
+                case Constant.LOG:
+                    if (logList.size() >= 6) {
+                        logList.remove(0);
+                    }
+
+                    logList.add(msg.obj.toString());
+
+                    String str = "";
+                    for (int i = 0; i < logList.size(); i++) {
+                        str += logList.get(i) + "\n\n";
+                    }
+
+                    mFragmentBasicsettingsBinding.tvlog.setText(str);
+                    break;
+                case Constant.STARTPRINTLOG:
+                    SPHelper mSPHelper = mMainActivity.getSPHelper();
+                    int logstatus = (int) mSPHelper.get(Constant.LOGSTATUS, 1);
+                    if (logstatus == 1) {
+                        running = true;
+                        String loglev = (String) mSPHelper.get(Constant.LOGLEV, "DEBUG");
+                        start(loglev);
+                    }
+                    break;
+                case Constant.STARTORSTOPSTREAM:
+                    startOrStopStream();
+                    break;
+            }
+        }
+    };
+
+    @Override
+    public void onAttach(Activity activity) {
+        super.onAttach(activity);
+        mMainActivity = (MainActivity) activity;
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+        mFragmentBasicsettingsBinding = FragmentBasicsettingsBinding.inflate(inflater);
+
+        mBasicSettingsViewModel = new BasicSettingsViewModel(mMainActivity, BasicSettingsFragment.this);
+        mFragmentBasicsettingsBinding.setViewModel(mBasicSettingsViewModel);
+
+        init();
+
+        View mView = mFragmentBasicsettingsBinding.getRoot();
+        return mView;
+    }
+
+    @Override
+    public void onViewCreated(View view, Bundle savedInstanceState) {
+        super.onViewCreated(view, savedInstanceState);
+    }
+
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+    }
+
+    @Override
+    public void onStop() {
+        super.onStop();
+    }
+
+    @Override
+    public void onDestroyView() {
+        super.onDestroyView();
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        stop();
+    }
+
+    @Override
+    public void onDetach() {
+        super.onDetach();
+    }
+
+    public void init() {
+        mFragmentBasicsettingsBinding.lllogall.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                SPHelper mSPHelper = mMainActivity.getSPHelper();
+                int logstatus = (int) mSPHelper.get(Constant.LOGSTATUS, 1);
+                if (logstatus == 0) {
+                    mSPHelper.put(Constant.LOGSTATUS, 1);
+
+                    mFragmentBasicsettingsBinding.lllog.setBackgroundResource(R.mipmap.ic_selected);
+
+                    mFragmentBasicsettingsBinding.ivlog.setVisibility(View.VISIBLE);
+
+                    Logger.getInstance().startLogging();
+
+
+                } else {
+                    mSPHelper.put(Constant.LOGSTATUS, 0);
+
+                    mFragmentBasicsettingsBinding.lllog.setBackgroundResource(R.mipmap.ic_notselected);
+
+                    Logger.getInstance().stopLogging();
+
+                }
+            }
+        });
+
+        boolean hasPermission1 = PeUtil.ishasPer(mMainActivity, Manifest.permission.CAMERA);
+        if (hasPermission1) {
+            boolean hasPermission2 = PeUtil.ishasPer(mMainActivity, Manifest.permission.RECORD_AUDIO);
+            if (hasPermission2) {
+                initCamera();
+            }
+        }
+
+
+        SPHelper mSPHelper = mMainActivity.getSPHelper();
+
+        int logstatus = (int) mSPHelper.get(Constant.LOGSTATUS, 1);
+
+        if (logstatus == 1) {
+            String loglev = (String) mSPHelper.get(Constant.LOGLEV, LogLevel.DEBUG);
+
+            // 设置全局 Logger 的 logTextView 和 logScrollView
+            Logger.getInstance().setLogTextView(mFragmentBasicsettingsBinding.tvlog, mFragmentBasicsettingsBinding.svLog);
+
+            // 设置日志等级
+            Logger.getInstance().setLogLevel(LogLevel.DEBUG);
+
+            // 记录不同等级的日志
+            Logger.getInstance().debug("-----begin-----");
+        }
+
+        //浮动按钮
+        mFragmentBasicsettingsBinding.fab.setOnTouchListener(new View.OnTouchListener() {
+            private float dX, dY;
+            private long startTime = 0;
+            private static final int CLICK_THRESHOLD = 20;  // 增大阈值
+            private static final int CLICK_TIME_THRESHOLD = 300; // 最大点击时间
+
+            @Override
+            public boolean onTouch(View view, MotionEvent event) {
+                switch (event.getAction()) {
+                    case MotionEvent.ACTION_DOWN:
+                        // 记录按下时的坐标和时间
+                        dX = event.getRawX() - view.getX();
+                        dY = event.getRawY() - view.getY();
+                        startTime = System.currentTimeMillis(); // 记录按下时间
+                        return true;
+
+                    case MotionEvent.ACTION_MOVE:
+                        // 计算新的坐标
+                        float newX = event.getRawX() - dX;
+                        float newY = event.getRawY() - dY;
+                        view.animate().x(newX).y(newY).setDuration(0).start();
+                        return true;
+
+                    case MotionEvent.ACTION_UP:
+                        // 获取按下到松开时的坐标差距
+                        float deltaX = Math.abs(event.getRawX() - (view.getX() + dX));
+                        float deltaY = Math.abs(event.getRawY() - (view.getY() + dY));  // 使用View当前位置加上偏移量
+
+                        // 获取按下到释放的时间差
+                        long upTime = System.currentTimeMillis();
+                        long time = upTime - startTime;
+
+                        // 打印调试信息
+//                        Log.d("TouchEvent", "deltaX: " + deltaX + ", deltaY: " + deltaY + ", time: " + time);
+
+                        // 判断是否为点击事件
+                        if (deltaX < CLICK_THRESHOLD && deltaY < CLICK_THRESHOLD && time < CLICK_TIME_THRESHOLD) {
+                            // 点击事件
+//                            Log.d("TouchEvent", "Detected click");
+                            view.performClick();  // 手动触发点击事件
+                        }
+                        return true;
+
+                    default:
+                        return false;
+                }
+            }
+        });
+    }
+
+    public void start(String lev) {
+        if (mprintThread == null) {
+            int pid = android.os.Process.myPid();
+            mprintThread = new printThread(String.valueOf(pid), lev, new printThread.info() {
+                @Override
+                public void msg(String value) {
+                    super.msg(value);
+                    Message msg = new Message();
+                    msg.what = Constant.LOG;
+                    msg.obj = value;
+                    mHandler.sendMessage(msg);
+                }
+            });
+            mprintThread.start();
+        }
+    }
+
+    public void stop() {
+        mFragmentBasicsettingsBinding.tvlog.setText("");
+        if (mprintThread != null) {
+            mprintThread.stopt();
+            mprintThread = null;
+        }
+    }
+
+    public void initCamera() {
+        try {
+            boolean hasPermission1 = PeUtil.ishasPer(mMainActivity, Manifest.permission.CAMERA);
+            if (hasPermission1) {
+                boolean hasPermission2 = PeUtil.ishasPer(mMainActivity, Manifest.permission.RECORD_AUDIO);
+                if (hasPermission2) {
+                    intent = new Intent(mMainActivity, BackgroundCameraService.class);
+                    mMainActivity.startService(intent);
+
+                    intent1 = new Intent(mMainActivity, UVCCameraService.class);
+                    mMainActivity.startService(intent1);
+
+                    conn = new ServiceConnection() {
+                        @Override
+                        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
+                            mService = ((BackgroundCameraService.LocalBinder) iBinder).getService();
+
+                            if (mFragmentBasicsettingsBinding.tvcamera.isAvailable()) {
+                                goonWithAvailableTexture(mFragmentBasicsettingsBinding.tvcamera.getSurfaceTexture());
+                            }
+                        }
+
+                        @Override
+                        public void onServiceDisconnected(ComponentName componentName) {
+
+                        }
+                    };
+
+                    mMainActivity.bindService(new Intent(mMainActivity, BackgroundCameraService.class), conn, 0);
+
+                }
+            }
+        } catch (Exception e) {
+            Log.e(TAG, "initCamera  Exception  " + e.toString());
+        }
+    }
+
+    public void destroyService() {
+        mMainActivity.stopService(intent);
+        mMainActivity.stopService(intent1);
+        mMainActivity.unbindService(conn);
+    }
+
+    private void goonWithAvailableTexture(SurfaceTexture surface) {
+        final File easyPusher = new File(recordPath());
+        easyPusher.mkdirs();
+
+        MediaStream ms = mService.getMediaStream();
+
+        if (ms != null) {
+            ms.stopPreview();
+            mService.inActivePreview();
+            ms.setSurfaceTexture(surface);
+            ms.startPreview();
+
+            mMainActivity.setMMediaStream(ms);
+
+            if (ms.getDisplayRotationDegree() != getDisplayRotationDegree()) {
+                int orientation = mMainActivity.getRequestedOrientation();
+
+                if (orientation == SCREEN_ORIENTATION_UNSPECIFIED || orientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) {
+                    mMainActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
+                } else {
+                    mMainActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+                }
+            }
+        } else {
+            boolean enableVideo = SPUtil.getEnableVideo(mMainActivity);
+
+            mMainActivity.setMMediaStream(new MediaStream(mMainActivity.getApplicationContext(), surface, enableVideo));
+            String path = easyPusher.getPath();
+
+            mMainActivity.getMMediaStream().setRecordPath(path);
+
+            startCamera();
+
+            mService.setMediaStream(mMainActivity.getMMediaStream());
+        }
+    }
+
+    public void startCamera() {
+        int cameraid = SPUtil.getCameraid(mMainActivity);
+        mMainActivity.getMMediaStream().setCameraId(cameraid);
+
+        int videoresolution = SPUtil.getVideoresolution(mMainActivity);
+        if (videoresolution == 0) {
+            width = 1920;
+            height = 1080;
+        } else if (videoresolution == 1) {
+            width = 1280;
+            height = 960;
+        } else if (videoresolution == 2) {
+            width = 1280;
+            height = 720;
+        }
+        mMainActivity.getMMediaStream().updateResolution(width, height);
+        mMainActivity.getMMediaStream().setDisplayRotationDegree(getDisplayRotationDegree());
+        mMainActivity.getMMediaStream().createCamera();
+        mMainActivity.getMMediaStream().startPreview();
+    }
+
+    public String recordPath() {
+        return mMainActivity.getExternalFilesDir(null).getAbsolutePath() + "/" + Constant.DIR;
+    }
+
+    private int getDisplayRotationDegree() {
+        int rotation = mMainActivity.getWindowManager().getDefaultDisplay().getRotation();
+        int degrees = 0;
+
+        switch (rotation) {
+            case Surface.ROTATION_0:
+                degrees = 0;
+                break; // Natural orientation
+            case Surface.ROTATION_90:
+                degrees = 90;
+                break; // Landscape left
+            case Surface.ROTATION_180:
+                degrees = 180;
+                break;// Upside down
+            case Surface.ROTATION_270:
+                degrees = 270;
+                break;// Landscape right
+        }
+
+        return degrees;
+    }
+
+    public void save() {
+        try {
+            String port = mFragmentBasicsettingsBinding.etport.getText().toString();
+            SPUtil.setServerport(mMainActivity, Integer.parseInt(port));
+
+            String serveraddr = mFragmentBasicsettingsBinding.etsipserveraddr.getText().toString();
+            SPUtil.setServerip(mMainActivity, serveraddr);
+
+            String serverid = mFragmentBasicsettingsBinding.etsipserverid.getText().toString();
+            SPUtil.setServerid(mMainActivity, serverid);
+
+            String serverdomain = mFragmentBasicsettingsBinding.etsipserverdomain.getText().toString();
+            SPUtil.setServerdomain(mMainActivity, serverdomain);
+
+            String sipname = mFragmentBasicsettingsBinding.tvsipname.getText().toString();
+            SPUtil.setDeviceid(mMainActivity, sipname);
+
+            String devicename = mFragmentBasicsettingsBinding.etdevicename.getText().toString();
+            SPUtil.setDevicename(mMainActivity, devicename);
+
+            String sippassword = mFragmentBasicsettingsBinding.etsippassword.getText().toString();
+            SPUtil.setPassword(mMainActivity, sippassword);
+
+            String registervalidtime = mFragmentBasicsettingsBinding.etregistervalidtime.getText().toString();
+            SPUtil.setRegexpires(mMainActivity, Integer.parseInt(registervalidtime));
+
+            String heartbeatcycle = mFragmentBasicsettingsBinding.tvheartbeatcycle.getText().toString();
+            SPUtil.setHeartbeatinterval(mMainActivity, Integer.parseInt(heartbeatcycle));
+
+            String heartbeatcount = mFragmentBasicsettingsBinding.etheartbeatcount.getText().toString();
+            SPUtil.setHeartbeatcount(mMainActivity, Integer.parseInt(heartbeatcount));
+        } catch (Exception e) {
+            Log.e(TAG, "save  Exception  " + e.toString());
+        }
+    }
+
+    public void startOrStopStream() {
+        try {
+            if (mMainActivity.getMMediaStream() != null) {
+                if (!mMainActivity.getMMediaStream().isStreaming()) {
+                    mMainActivity.getMMediaStream().startStream();
+                } else {
+                    mMainActivity.getMMediaStream().stopStream();
+                    Logger.getInstance().debug("注销");
+                }
+            }
+        } catch (Exception e) {
+            Log.e(TAG, "startOrStopStream  Exception  " + e.toString());
+        }
+    }
+
+    static class printThread extends Thread {
+        public String TAG = printThread.class.getSimpleName();
+        private BufferedReader mBufferedReader = null;
+        private Object lock = new Object();
+        private String cmd = null;
+        private Process process;
+        private String mPID;
+        private String lev;
+        private info minfo;
+
+        public printThread(String pid, String lev, info minfo) {
+            mPID = pid;
+            this.lev = lev;
+            this.minfo = minfo;
+
+            if (lev.equals("DEBUG")) {
+                cmd = "logcat *:d | grep \"(" + mPID + ")\"";
+            } else if (lev.equals("INFO")) {
+                cmd = "logcat *:i | grep \"(" + mPID + ")\"";
+            } else if (lev.equals("WARNING")) {
+                cmd = "logcat *:w | grep \"(" + mPID + ")\"";
+            } else if (lev.equals("ERROR")) {
+                cmd = "logcat *:e | grep \"(" + mPID + ")\"";
+            }
+        }
+
+        public void stopt() {
+            synchronized (lock) {
+                lock.notify();
+                running = false;
+            }
+        }
+
+        @Override
+        public void run() {
+            try {
+                super.run();
+                process = Runtime.getRuntime().exec(cmd);
+                InputStream is = process.getInputStream();
+                InputStreamReader isr = new InputStreamReader(is);
+                mBufferedReader = new BufferedReader(isr, 1024);
+                String line = null;
+
+                synchronized (lock) {
+                    while (running && ((line = mBufferedReader.readLine()) != null)) {
+                        if (!running) {
+                            break;
+                        }
+
+                        if (line.length() == 0) {
+                            continue;
+                        }
+
+                        minfo.msg(line);
+
+                        lock.wait(500);
+                    }
+                }
+            } catch (Exception e) {
+                Log.e(TAG, "run  Exception  " + e.toString());
+            } finally {
+                try {
+                    if (process != null) {
+                        process.destroy();
+                        process = null;
+                    }
+
+                    if (mBufferedReader != null) {
+                        mBufferedReader.close();
+                        mBufferedReader = null;
+                    }
+                } catch (Exception e) {
+                    Log.e(TAG, "run  finally  Exception  " + e.toString());
+                }
+            }
+        }
+
+        static class info {
+            public void msg(String value) {
+
+            }
+        }
+    }
+}

+ 490 - 0
Android/app/src/main/java/com/easygbs/easygbd/fragment/ChannelSettingsFragment.java

@@ -0,0 +1,490 @@
+package com.easygbs.easygbd.fragment;
+
+import android.Manifest;
+import android.app.Activity;
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.text.TextUtils;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.Display;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.PopupMenu;
+import android.widget.PopupWindow;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.fragment.app.Fragment;
+import androidx.recyclerview.widget.GridLayoutManager;
+import androidx.recyclerview.widget.OrientationHelper;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.easygbs.easygbd.R;
+import com.easygbs.easygbd.common.Constant;
+import com.easygbs.easygbd.dao.DCon;
+import com.easygbs.easygbd.dao.DOpe;
+import com.easygbs.easygbd.dao.bean.Chan;
+import com.easygbs.easygbd.dialog.ChanDialog;
+import com.easygbs.easygbd.util.PeUtil;
+import com.easygbs.easygbd.util.ScrUtil;
+import com.easygbs.easygbd.viewadapter.MultiItemTypeAdapter;
+import com.easygbs.easygbd.activity.MainActivity;
+import com.easygbs.easygbd.adapter.ChannelAdapter;
+import com.easygbs.easygbd.databinding.FragmentChannelsettingsBinding;
+import com.easygbs.easygbd.viewmodel.fragment.ChannelSettingsViewModel;
+
+import android.app.Dialog;
+
+import java.io.File;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+public class ChannelSettingsFragment extends Fragment {
+    private String TAG = ChannelSettingsFragment.class.getSimpleName();
+    private MainActivity mMainActivity;
+    private FragmentChannelsettingsBinding mFragmentChannelsettingsBinding;
+    private ChannelSettingsViewModel mChannelSettingsViewModel;
+
+    public Chan modifyChan;
+    private View operationView;
+
+    public List<Chan> ChanList;
+    public ChannelAdapter mChannelAdapter;
+
+    public Handler mHandler = new Handler() {
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+
+            }
+        }
+    };
+
+    @Override
+    public void onAttach(Activity activity) {
+        super.onAttach(activity);
+        mMainActivity = (MainActivity) activity;
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+        mFragmentChannelsettingsBinding = FragmentChannelsettingsBinding.inflate(inflater);
+
+        mChannelSettingsViewModel = new ChannelSettingsViewModel(mMainActivity, ChannelSettingsFragment.this);
+        mFragmentChannelsettingsBinding.setViewModel(mChannelSettingsViewModel);
+
+        init();
+
+        View mView = mFragmentChannelsettingsBinding.getRoot();
+        return mView;
+    }
+
+    @Override
+    public void onViewCreated(View view, Bundle savedInstanceState) {
+        super.onViewCreated(view, savedInstanceState);
+    }
+
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+    }
+
+    @Override
+    public void onStop() {
+        super.onStop();
+    }
+
+    @Override
+    public void onDestroyView() {
+        super.onDestroyView();
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+    }
+
+    @Override
+    public void onDetach() {
+        super.onDetach();
+    }
+
+    public void init() {
+        mFragmentChannelsettingsBinding.rvlist.setLayoutManager(
+                new GridLayoutManager(mMainActivity, 1, RecyclerView.VERTICAL, false));
+
+
+        mFragmentChannelsettingsBinding.llroot.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                clearOperationView();
+            }
+        });
+
+        ChanList = new ArrayList<Chan>();
+        mChannelAdapter = new ChannelAdapter(mMainActivity, mMainActivity, R.layout.adapter_channel, ChanList);
+        mFragmentChannelsettingsBinding.rvlist.setAdapter(mChannelAdapter);
+        mFragmentChannelsettingsBinding.rvlist.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+
+            }
+        });
+
+        mChannelAdapter.setOnItemClickListener(new ChannelAdapter.OnItemClickListener() {
+            @Override
+            public void onItemClick(View view, int position, Chan chan, String type) {
+
+                ChanDialog mChanDialog = new ChanDialog(mMainActivity, mMainActivity, ChannelSettingsFragment.this, ChanList.get(position));
+                mChanDialog.show();
+            }
+        });
+
+        mChannelAdapter.setOnItemClickListener(new MultiItemTypeAdapter.OnItemClickListener() {
+            @Override
+            public void onItemClick(View view, RecyclerView.ViewHolder holder, int position) {
+                for (int i = 0; i < ChanList.size(); i++) {
+                    Chan mChan = ChanList.get(i);
+                    if (mChan.getUid() == ChanList.get(position).getUid()) {
+                        mChan.setSelected(1);
+                    } else {
+                        mChan.setSelected(0);
+                    }
+                }
+
+
+//                mChannelAdapter.notifyDataSetChanged();
+
+
+                ChanDialog mChanDialog = new ChanDialog(mMainActivity, mMainActivity, ChannelSettingsFragment.this, ChanList.get(position));
+                mChanDialog.show();
+            }
+
+            @Override
+            public boolean onItemLongClick(View view, RecyclerView.ViewHolder holder, int position) {
+
+                return false;
+            }
+        });
+
+        int height = getScreenHeight(mMainActivity);
+        int StatusBarHeight = ScrUtil.getStatusBarHeight(mMainActivity);
+        int navigationheight = getNavBarHeight(mMainActivity);
+        int diff = height - StatusBarHeight - mMainActivity.getHeight() - navigationheight;
+
+        if (diff == 0) {
+            int getheight = height - StatusBarHeight - (int) mMainActivity.getResources().getDimension(R.dimen.dp_220) - navigationheight;
+            LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mFragmentChannelsettingsBinding.lllist.getLayoutParams();
+            lp.height = getheight;
+            mFragmentChannelsettingsBinding.lllist.setLayoutParams(lp);
+        } else if (diff < 0) {
+            int getheight = height - StatusBarHeight - (int) mMainActivity.getResources().getDimension(R.dimen.dp_220) - navigationheight / 2;
+            LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mFragmentChannelsettingsBinding.lllist.getLayoutParams();
+            lp.height = getheight;
+            mFragmentChannelsettingsBinding.lllist.setLayoutParams(lp);
+        }
+
+        showChannels();
+    }
+
+
+    private void showMenuDialog(View anchorView) {
+        // 加载菜单布局
+        View popupView = LayoutInflater.from(mMainActivity).inflate(R.layout.modify_channel_dialog, null);
+
+        // 创建 PopupWindow
+        PopupWindow popupWindow = new PopupWindow(
+                popupView,
+                ViewGroup.LayoutParams.WRAP_CONTENT,
+                ViewGroup.LayoutParams.WRAP_CONTENT,
+                true);
+
+        // 设置 PopupWindow 的背景(必要,否则点击外部无法关闭)
+        popupWindow.setBackgroundDrawable(mMainActivity.getResources().getDrawable(android.R.drawable.dialog_holo_light_frame));
+
+        // 显示 PopupWindow
+
+        popupWindow.showAsDropDown(anchorView, -mMainActivity.dpToPx(10), 0);
+
+
+        popupView.findViewById(R.id.cut).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                ClipboardManager clipboard = (ClipboardManager) mMainActivity.getSystemService(Context.CLIPBOARD_SERVICE);
+                // 创建一个 ClipData 对象,包含你要复制的文本
+                EditText editText = (EditText) operationView;
+                ClipData clip = ClipData.newPlainText("label", editText.getText().toString());
+                // 将文本复制到剪切板
+                clipboard.setPrimaryClip(clip);
+                editText.setText("");
+                editText.requestFocus();
+                // 获取输入法管理器
+                InputMethodManager imm = (InputMethodManager) mMainActivity.getSystemService(Context.INPUT_METHOD_SERVICE);
+                // 显示键盘
+                if (imm != null) {
+                    imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
+                }
+                popupWindow.dismiss();
+            }
+        });
+
+        popupView.findViewById(R.id.copy).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                ClipboardManager clipboard = (ClipboardManager) mMainActivity.getSystemService(Context.CLIPBOARD_SERVICE);
+                // 创建一个 ClipData 对象,包含你要复制的文本
+                EditText editText = (EditText) operationView;
+                ClipData clip = ClipData.newPlainText("label", editText.getText().toString());
+                // 将文本复制到剪切板
+                clipboard.setPrimaryClip(clip);
+                editText.requestFocus();
+                // 获取输入法管理器
+                InputMethodManager imm = (InputMethodManager) mMainActivity.getSystemService(Context.INPUT_METHOD_SERVICE);
+                // 显示键盘
+                if (imm != null) {
+                    imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
+                }
+                popupWindow.dismiss();
+            }
+        });
+
+        popupView.findViewById(R.id.paste).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                ClipboardManager clipboard = (ClipboardManager) mMainActivity.getSystemService(Context.CLIPBOARD_SERVICE);
+                // 创建一个 ClipData 对象,包含你要复制的文本
+                if (clipboard != null && clipboard.hasPrimaryClip()) {
+                    // 获取剪切板中的文本
+                    CharSequence clipText = clipboard.getPrimaryClip().getItemAt(0).getText();
+                    if (!TextUtils.isEmpty(clipText)) {
+                        EditText editText = (EditText) operationView;
+                        editText.setText(clipText); // 粘贴到 EditText 中
+                    }
+                }
+                popupWindow.dismiss();
+            }
+        });
+
+        popupView.findViewById(R.id.modify).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+
+                EditText editText = (EditText) operationView;
+                editText.requestFocus();
+
+                mMainActivity.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
+
+                // 显示软键盘
+                InputMethodManager imm = (InputMethodManager) mMainActivity.getSystemService(Context.INPUT_METHOD_SERVICE);
+                if (imm != null) {
+                    imm.showSoftInput(editText, InputMethodManager.SHOW_IMPLICIT);
+                }
+                // 移动光标到文本末尾
+                editText.setSelection(editText.getText().length());
+                popupWindow.dismiss();
+            }
+        });
+
+
+        popupWindow.setOnDismissListener(new PopupWindow.OnDismissListener() {
+            @Override
+            public void onDismiss() {
+
+            }
+        });
+    }
+
+    private void clearOperationView() {
+        if (operationView == null) return;
+        operationView.setBackgroundColor(Color.parseColor("#FFFFFF"));
+        operationView.setFocusable(false);
+        operationView.setFocusableInTouchMode(false);
+
+        operationView = null;
+        modifyChan = null;
+
+
+    }
+
+    private void setAdapterListener() {
+        mChannelAdapter.setOnItemClickListener(new ChannelAdapter.OnItemClickListener() {
+            @Override
+            public void onItemClick(View view, int position, Chan chan, String type) {
+                mFragmentChannelsettingsBinding.llroot.clearFocus();
+                EditText editText = (EditText) view;
+                if (operationView != null) clearOperationView();
+
+                view.setBackgroundColor(Color.parseColor("#D9F4EB"));
+                view.setFocusable(true);
+                view.setFocusableInTouchMode(true);
+                editText.setSelection(0, editText.getText().length());
+                modifyChan = chan;
+                operationView = view;
+                showMenuDialog(view);
+
+                editText.setOnFocusChangeListener(new View.OnFocusChangeListener() {
+                    @Override
+                    public void onFocusChange(View view, boolean hasFocus) {
+                        if (modifyChan == null) return;
+                        // 当 EditText 失去焦点时执行
+                        if (!hasFocus) {
+                            if (Objects.equals(type, "channel_id")) {
+                                modifyChan.setCid(((EditText) view).getText().toString().trim());
+                            } else if (Objects.equals(type, "channel_name")) {
+                                modifyChan.setNa(((EditText) view).getText().toString().trim());
+                            }
+                            modify(modifyChan);
+                        }
+                    }
+                });
+            }
+        });
+    }
+
+
+    public static int getNavBarHeight(Context c) {
+        Resources resources = c.getResources();
+        int identifier = resources.getIdentifier("navigation_bar_height", "dimen", "android");
+        return resources.getDimensionPixelOffset(identifier);
+    }
+
+    public static int getScreenHeight(Context context) {
+        int dpi = 0;
+        Display display = ((Activity) context).getWindowManager()
+                .getDefaultDisplay();
+        DisplayMetrics dm = new DisplayMetrics();
+        @SuppressWarnings("rawtypes")
+        Class c;
+        try {
+            c = Class.forName("android.view.Display");
+            @SuppressWarnings("unchecked") Method method = c.getMethod("getRealMetrics", DisplayMetrics.class);
+            method.invoke(display, dm);
+            dpi = dm.heightPixels;
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return dpi;
+    }
+
+    public void showChannels() {
+        ChanList.clear();
+
+        boolean per = PeUtil.ishasPer(mMainActivity, Manifest.permission.WRITE_EXTERNAL_STORAGE);
+        if (per) {
+            if (mMainActivity.getMDOpe() == null) {
+                DOpe mDOpe = DOpe.Instance(mMainActivity, mMainActivity.getExternalFilesDir(null).getAbsolutePath() + File.separator + Constant.DIR);
+                List<Chan> ChanListLocal = (List<Chan>) mDOpe.OperaDatabase(DCon.Chanqueryeffect, "");
+                if (ChanListLocal.size() >= 1) {
+                    ChanList.addAll(ChanListLocal);
+                }
+            } else {
+                List<Chan> ChanListLocal = (List<Chan>) mMainActivity.getMDOpe().OperaDatabase(DCon.Chanqueryeffect, "");
+                if (ChanListLocal.size() >= 1) {
+                    ChanList.addAll(ChanListLocal);
+                }
+            }
+        }
+
+        mChannelAdapter.notifyDataSetChanged();
+    }
+
+    public void cancel() {
+        mChannelAdapter.notifyDataSetChanged();
+    }
+
+    public void delete() {
+        if (operationView != null) {
+            operationView.setBackgroundColor(Color.parseColor("#FFFFFF"));
+            operationView = null;
+        }
+
+        if (modifyChan != null) {
+            cancel();
+            for (int i = 0; i < ChanList.size(); i++) {
+                Chan mmChan = ChanList.get(i);
+                if (mmChan.getUid() == modifyChan.getUid()) {
+                    mmChan.setSta(0);
+                    ChanList.remove(i);
+                    mMainActivity.getMDOpe().OperaDatabase(DCon.Chanupdatedependuid, mmChan);
+                    break;
+                }
+            }
+            modifyChan = null;
+            mChannelAdapter.notifyDataSetChanged();
+        }
+
+    }
+
+    public void delete(Chan mChan) {
+
+        if (ChanList.size() == 1) {
+            Toast.makeText(mMainActivity, "通道数最少一路", Toast.LENGTH_SHORT).show();
+            return;
+        }
+
+        cancel();
+
+        for (int i = 0; i < ChanList.size(); i++) {
+            Chan mmChan = ChanList.get(i);
+            if (mmChan.getUid() == mChan.getUid()) {
+                mmChan.setSta(0);
+                ChanList.remove(i);
+
+                mMainActivity.getMDOpe().OperaDatabase(DCon.Chanupdatedependuid, mmChan);
+                break;
+            }
+        }
+
+        mChannelAdapter.notifyDataSetChanged();
+    }
+
+    public void modify(Chan mChan) {
+        cancel();
+
+        for (int i = 0; i < ChanList.size(); i++) {
+            Chan mmChan = ChanList.get(i);
+            if (mmChan.getUid() == mChan.getUid()) {
+                mmChan.setCid(mChan.getCid());
+                mmChan.setNa(mChan.getNa());
+                mMainActivity.getMDOpe().OperaDatabase(DCon.Chanupdatedependuid, mmChan);
+                break;
+            }
+        }
+
+        mChannelAdapter.notifyDataSetChanged();
+    }
+}

+ 257 - 0
Android/app/src/main/java/com/easygbs/easygbd/fragment/RecordFragment.java

@@ -0,0 +1,257 @@
+package com.easygbs.easygbd.fragment;
+
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+
+import android.Manifest;
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.ActivityInfo;
+import android.graphics.SurfaceTexture;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.Surface;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+
+import androidx.fragment.app.Fragment;
+
+import com.easygbs.easygbd.activity.MainActivity;
+import com.easygbs.easygbd.common.Constant;
+import com.easygbs.easygbd.databinding.FragmentRecordBinding;
+import com.easygbs.easygbd.push.MediaStream;
+import com.easygbs.easygbd.service.BackgroundCameraService;
+import com.easygbs.easygbd.service.UVCCameraService;
+import com.easygbs.easygbd.util.PeUtil;
+
+import org.easydarwin.util.SPUtil;
+
+import com.easygbs.easygbd.util.ScrUtil;
+import com.easygbs.easygbd.viewmodel.fragment.RecordViewModel;
+
+import java.io.File;
+
+public class RecordFragment extends Fragment {
+    public String TAG = RecordFragment.class.getSimpleName();
+    public MainActivity mMainActivity;
+    public FragmentRecordBinding mFragmentRecordBinding;
+    public RecordViewModel mRecordViewModel;
+
+    private BackgroundCameraService mService;
+    private ServiceConnection conn;
+
+    public Intent intent;
+
+    public Intent intent1;
+
+    private int width = 1920;
+    private int height = 1080;
+
+    @Override
+    public void onAttach(Activity activity) {
+        super.onAttach(activity);
+        mMainActivity = (MainActivity) activity;
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+        mFragmentRecordBinding = FragmentRecordBinding.inflate(inflater);
+        mRecordViewModel = new RecordViewModel(mMainActivity, RecordFragment.this);
+        mFragmentRecordBinding.setViewModel(mRecordViewModel);
+
+        View mView = mFragmentRecordBinding.getRoot();
+        return mView;
+    }
+
+    @Override
+    public void onViewCreated(View view, Bundle savedInstanceState) {
+        super.onViewCreated(view, savedInstanceState);
+    }
+
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+    }
+
+    @Override
+    public void onStop() {
+        super.onStop();
+    }
+
+    @Override
+    public void onDestroyView() {
+        super.onDestroyView();
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+    }
+
+    @Override
+    public void onDetach() {
+        super.onDetach();
+    }
+
+    public void show() {
+        LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) mFragmentRecordBinding.llroot.getLayoutParams();
+        // 设置宽度和高度为 MATCH_PARENT
+        layoutParams.topMargin = ScrUtil.getStatusBarHeight(mMainActivity);
+        mFragmentRecordBinding.llroot.setLayoutParams(layoutParams);
+        init();
+    }
+
+    public void init() {
+        initCamera();
+    }
+
+    public void initCamera() {
+        try {
+            boolean hasPermission1 = PeUtil.ishasPer(mMainActivity, Manifest.permission.CAMERA);
+            if (hasPermission1) {
+                boolean hasPermission2 = PeUtil.ishasPer(mMainActivity, Manifest.permission.RECORD_AUDIO);
+                if (hasPermission2) {
+                    intent = new Intent(mMainActivity, BackgroundCameraService.class);
+                    mMainActivity.startService(intent);
+
+                    intent1 = new Intent(mMainActivity, UVCCameraService.class);
+                    mMainActivity.startService(intent1);
+
+                    conn = new ServiceConnection() {
+                        @Override
+                        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
+                            mService = ((BackgroundCameraService.LocalBinder) iBinder).getService();
+
+                            if (mFragmentRecordBinding.tvcamera.isAvailable()) {
+                                goonWithAvailableTexture(mFragmentRecordBinding.tvcamera.getSurfaceTexture());
+                            }
+                        }
+
+                        @Override
+                        public void onServiceDisconnected(ComponentName componentName) {
+
+                        }
+                    };
+
+                    mMainActivity.bindService(new Intent(mMainActivity, BackgroundCameraService.class), conn, 0);
+                }
+            }
+        } catch (Exception e) {
+            Log.e(TAG, "initCamera  Exception  " + e.toString());
+        }
+    }
+
+    private void goonWithAvailableTexture(SurfaceTexture surface) {
+        final File easyPusher = new File(recordPath());
+        easyPusher.mkdirs();
+
+        MediaStream ms = mService.getMediaStream();
+
+        if (ms != null) {
+            ms.stopPreview();
+            mService.inActivePreview();
+            ms.setSurfaceTexture(surface);
+            ms.startPreview();
+
+            mMainActivity.setMMediaStream(ms);
+
+            if (ms.getDisplayRotationDegree() != getDisplayRotationDegree()) {
+                int orientation = mMainActivity.getRequestedOrientation();
+
+                if (orientation == SCREEN_ORIENTATION_UNSPECIFIED || orientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) {
+                    mMainActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
+                } else {
+                    mMainActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+                }
+            }
+        } else {
+            boolean enableVideo = SPUtil.getEnableVideo(mMainActivity);
+
+            mMainActivity.setMMediaStream(new MediaStream(mMainActivity.getApplicationContext(), surface, enableVideo));
+            String path = easyPusher.getPath();
+
+            mMainActivity.getMMediaStream().setRecordPath(path);
+
+            startCamera();
+
+            mService.setMediaStream(mMainActivity.getMMediaStream());
+        }
+    }
+
+    public void startCamera() {
+        int cameraid = SPUtil.getCameraid(mMainActivity);
+        mMainActivity.getMMediaStream().setCameraId(cameraid);
+        int videoresolution = SPUtil.getVideoresolution(mMainActivity);
+        if (videoresolution == 0) {
+            width = 1920;
+            height = 1080;
+        } else if (videoresolution == 1) {
+            width = 1280;
+            height = 720;
+        } else if (videoresolution == 2) {
+            width = 640;
+            height = 480;
+        }
+        mMainActivity.getMMediaStream().updateResolution(width, height);
+        mMainActivity.getMMediaStream().setDisplayRotationDegree(getDisplayRotationDegree());
+        mMainActivity.getMMediaStream().createCamera();
+        mMainActivity.getMMediaStream().startPreview();
+    }
+
+    public String recordPath() {
+        return mMainActivity.getExternalFilesDir(null).getAbsolutePath() + "/" + Constant.DIR;
+    }
+
+    private int getDisplayRotationDegree() {
+        int rotation = mMainActivity.getWindowManager().getDefaultDisplay().getRotation();
+        int degrees = 0;
+
+        switch (rotation) {
+            case Surface.ROTATION_0:
+                degrees = 0;
+                break; // Natural orientation
+            case Surface.ROTATION_90:
+                degrees = 90;
+                break; // Landscape left
+            case Surface.ROTATION_180:
+                degrees = 180;
+                break;// Upside down
+            case Surface.ROTATION_270:
+                degrees = 270;
+                break;// Landscape right
+        }
+
+        return degrees;
+    }
+
+    public void destroyService() {
+        if (mMainActivity == null) return;
+        if (intent != null) mMainActivity.stopService(intent);
+        if (intent1 != null) mMainActivity.stopService(intent1);
+        if (conn != null) mMainActivity.unbindService(conn);
+    }
+}

+ 175 - 0
Android/app/src/main/java/com/easygbs/easygbd/fragment/StreamingSettingsFragment.java

@@ -0,0 +1,175 @@
+package com.easygbs.easygbd.fragment;
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import androidx.fragment.app.Fragment;
+import com.easygbs.easygbd.R;
+import com.easygbs.easygbd.activity.MainActivity;
+import com.easygbs.easygbd.databinding.FragmentStreamingsettingsBinding;
+import org.easydarwin.util.SPUtil;
+import com.easygbs.easygbd.viewmodel.fragment.StreamingSettingsViewModel;
+
+public class StreamingSettingsFragment extends Fragment {
+	public String TAG= StreamingSettingsFragment.class.getSimpleName();
+	public MainActivity mMainActivity;
+	public FragmentStreamingsettingsBinding mFragmentStreamingsettingsBinding;
+	public StreamingSettingsViewModel mStreamingSettingsViewModel;
+	public int isenvideo=0;
+	public int isenaudio=0;
+	public int isenlocreport=0;
+
+	public Handler mHandler=new Handler() {
+		public void handleMessage(Message msg) {
+			switch (msg.what) {
+
+			}
+		}
+	};
+
+	@Override
+	public void onAttach(Activity activity) {
+		super.onAttach(activity);
+		mMainActivity= (MainActivity) activity;
+	}
+
+	@Override
+	public void onCreate(Bundle savedInstanceState) {
+		super.onCreate(savedInstanceState);
+	}
+
+	@Override
+	public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {
+		mFragmentStreamingsettingsBinding = FragmentStreamingsettingsBinding.inflate(inflater);
+
+		mStreamingSettingsViewModel = new StreamingSettingsViewModel(mMainActivity,StreamingSettingsFragment.this);
+		mFragmentStreamingsettingsBinding.setViewModel(mStreamingSettingsViewModel);
+
+		init();
+
+		View mView=mFragmentStreamingsettingsBinding.getRoot();
+		return mView;
+	}
+
+	@Override
+	public void onViewCreated(View view, Bundle savedInstanceState) {
+		super.onViewCreated(view, savedInstanceState);
+	}
+
+	@Override
+	public void onActivityCreated(Bundle savedInstanceState) {
+		super.onActivityCreated(savedInstanceState);
+	}
+
+	@Override
+	public void onStart() {
+		super.onStart();
+	}
+
+	@Override
+	public void onResume() {
+		super.onResume();
+	}
+
+	@Override
+	public void onPause() {
+		super.onPause();
+	}
+
+	@Override
+	public void onStop() {
+		super.onStop();
+	}
+
+	@Override
+	public void onDestroyView() {
+		super.onDestroyView();
+	}
+
+	@Override
+	public void onDestroy() {
+		super.onDestroy();
+	}
+
+	@Override
+	public void onDetach() {
+		super.onDetach();
+	}
+
+	public void init(){
+		isenvideo = SPUtil.getIsenvideo(mMainActivity);
+//		mFragmentStreamingsettingsBinding.llvideosetall.setOnClickListener(new View.OnClickListener(){
+//			@Override
+//			public void onClick(View v) {
+//				if(isenvideo==0){
+//					isenvideo=1;
+//					mFragmentStreamingsettingsBinding.llvideoset.setBackgroundResource(R.mipmap.ic_selected);
+//					mFragmentStreamingsettingsBinding.ivvideoset.setVisibility(View.VISIBLE);
+//
+//					mFragmentStreamingsettingsBinding.llcamera.setEnabled(true);
+//					mFragmentStreamingsettingsBinding.llvideocode.setEnabled(true);
+//					mFragmentStreamingsettingsBinding.llresolution.setEnabled(true);
+//					mFragmentStreamingsettingsBinding.llframerate.setEnabled(true);
+//					mFragmentStreamingsettingsBinding.llvideocoderate.setEnabled(true);
+//				}else{
+//					isenvideo=0;
+//					mFragmentStreamingsettingsBinding.llvideoset.setBackgroundResource(R.mipmap.ic_notselected);
+//					mFragmentStreamingsettingsBinding.ivvideoset.setVisibility(View.INVISIBLE);
+//
+//					mFragmentStreamingsettingsBinding.llcamera.setEnabled(false);
+//					mFragmentStreamingsettingsBinding.llvideocode.setEnabled(false);
+//					mFragmentStreamingsettingsBinding.llresolution.setEnabled(false);
+//					mFragmentStreamingsettingsBinding.llframerate.setEnabled(false);
+//					mFragmentStreamingsettingsBinding.llvideocoderate.setEnabled(false);
+//				}
+//			}
+//		});
+
+		mFragmentStreamingsettingsBinding.llaudiosetall.setOnClickListener(new View.OnClickListener(){
+			@Override
+			public void onClick(View v) {
+				if(isenaudio==0) {
+					isenaudio=1;
+					mFragmentStreamingsettingsBinding.llaudioset.setBackgroundResource(R.mipmap.ic_selected);
+					mFragmentStreamingsettingsBinding.ivaudioset.setVisibility(View.VISIBLE);
+
+					mFragmentStreamingsettingsBinding.llaudiocode.setEnabled(true);
+					mFragmentStreamingsettingsBinding.llsamplingrate.setEnabled(true);
+					mFragmentStreamingsettingsBinding.llaudiochannel.setEnabled(true);
+					mFragmentStreamingsettingsBinding.llaudiocoderate.setEnabled(true);
+				}else{
+					isenaudio=0;
+					mFragmentStreamingsettingsBinding.llaudioset.setBackgroundResource(R.mipmap.ic_notselected);
+					mFragmentStreamingsettingsBinding.ivaudioset.setVisibility(View.INVISIBLE);
+
+					mFragmentStreamingsettingsBinding.llaudiocode.setEnabled(false);
+					mFragmentStreamingsettingsBinding.llsamplingrate.setEnabled(false);
+					mFragmentStreamingsettingsBinding.llaudiochannel.setEnabled(false);
+					mFragmentStreamingsettingsBinding.llaudiocoderate.setEnabled(false);
+				}
+			}
+		});
+
+		mFragmentStreamingsettingsBinding.lllocreportsetall.setOnClickListener(new View.OnClickListener(){
+			@Override
+			public void onClick(View v) {
+				if(isenlocreport==0) {
+					isenlocreport=1;
+					mFragmentStreamingsettingsBinding.lllocreportset.setBackgroundResource(R.mipmap.ic_selected);
+					mFragmentStreamingsettingsBinding.ivlocreportset.setVisibility(View.VISIBLE);
+
+					mFragmentStreamingsettingsBinding.lllocationfrequency.setEnabled(true);
+				}else{
+					isenlocreport=0;
+					mFragmentStreamingsettingsBinding.lllocreportset.setBackgroundResource(R.mipmap.ic_notselected);
+					mFragmentStreamingsettingsBinding.ivlocreportset.setVisibility(View.INVISIBLE);
+
+					mFragmentStreamingsettingsBinding.lllocationfrequency.setEnabled(false);
+				}
+			}
+		});
+	}
+}

+ 9 - 0
Android/app/src/main/java/com/easygbs/easygbd/logger/LogLevel.java

@@ -0,0 +1,9 @@
+package com.easygbs.easygbd.logger;
+
+// 定义日志等级
+public enum LogLevel {
+    DEBUG,
+    INFO,
+    WARN,
+    ERROR
+}

+ 203 - 0
Android/app/src/main/java/com/easygbs/easygbd/logger/Logger.java

@@ -0,0 +1,203 @@
+package com.easygbs.easygbd.logger;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+import android.widget.ScrollView;
+import android.widget.TextView;
+
+import com.easygbs.easygbd.logger.LogLevel;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.Queue;
+import java.util.LinkedList;
+
+public class Logger {
+
+    private static Logger instance;
+    private TextView logTextView;
+    private ScrollView logScrollView; // 用于滚动的 ScrollView
+    private Queue<String> logQueue; // 用于存储日志
+    private Handler uiHandler; // 更新 UI 的 Handler
+    private Handler loggingHandler; // 处理日志的后台线程 Handler
+
+    // 日志等级,默认是 DEBUG
+    private LogLevel currentLogLevel = LogLevel.DEBUG;
+
+    // 最大日志条数(例如,最多显示 500 行日志)
+    private static final int MAX_LOG_COUNT = 500;
+    // 最大字符长度(例如,最多 10000 个字符)
+    private static final int MAX_TEXT_LENGTH = 10000;
+
+    // 是否允许日志记录的标志
+    private boolean isLogging = true;
+
+    // 单例模式获取 Logger 实例
+    public static synchronized Logger getInstance() {
+        if (instance == null) {
+            instance = new Logger();
+        }
+        return instance;
+    }
+
+    private Logger() {
+        this.logQueue = new LinkedList<>();
+        this.uiHandler = new Handler(Looper.getMainLooper());
+    }
+
+    // 设置 logTextView 和 logScrollView,供 Activity 或 Fragment 调用
+    public void setLogTextView(TextView logTextView, ScrollView logScrollView) {
+        this.logTextView = logTextView;
+        this.logScrollView = logScrollView;
+    }
+
+    // 设置日志等级
+    public void setLogLevel(LogLevel logLevel) {
+        this.currentLogLevel = logLevel;
+    }
+
+    // 记录 DEBUG 日志
+    public void debug(String message) {
+        if (isLogging && currentLogLevel.ordinal() <= LogLevel.DEBUG.ordinal()) {
+            log(message, LogLevel.DEBUG);
+        }
+    }
+
+    // 记录 INFO 日志
+    public void info(String message) {
+        if (isLogging && currentLogLevel.ordinal() <= LogLevel.INFO.ordinal()) {
+            log(message, LogLevel.INFO);
+        }
+    }
+
+    // 记录 WARN 日志
+    public void warn(String message) {
+        if (isLogging && currentLogLevel.ordinal() <= LogLevel.WARN.ordinal()) {
+            log(message, LogLevel.WARN);
+        }
+    }
+
+    // 记录 ERROR 日志
+    public void error(String message) {
+        if (isLogging && currentLogLevel.ordinal() <= LogLevel.ERROR.ordinal()) {
+            log(message, LogLevel.ERROR);
+        }
+    }
+
+    // 处理日志输出
+    private void log(String message, LogLevel logLevel) {
+        // 只在当前日志等级大于或等于设定等级时才记录日志
+        if (logLevel.ordinal() < currentLogLevel.ordinal()) {
+            return; // 如果日志等级低于当前设定等级,跳过此日志
+        }
+
+        String formattedMessage = formatLogMessage(message, logLevel);
+
+        // 如果日志队列已满,则删除最早的日志
+        if (logQueue.size() >= MAX_LOG_COUNT) {
+            logQueue.poll(); // 移除队列中的第一个元素
+        }
+
+        // 将符合条件的日志消息添加到队列中
+        logQueue.offer(formattedMessage);
+
+        // 在后台线程中处理日志
+        processLogs();
+    }
+
+    // 格式化日志消息,包含日志等级和时间戳
+    private String formatLogMessage(String message, LogLevel logLevel) {
+        // 获取当前线程的 ID
+        int threadId = android.os.Process.myPid();
+
+        // 获取当前时间戳
+        long timestampMillis = System.currentTimeMillis();
+
+        // 使用 SimpleDateFormat 格式化时间戳
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());
+        String formattedTimestamp = sdf.format(new Date(timestampMillis));
+
+        // 格式化日志消息,加入日志等级信息
+        return String.format("[%s] [%d] %s: %s", formattedTimestamp, threadId, logLevel, message);
+    }
+
+    // 逐行更新日志
+    private void processLogs() {
+        if (!isLogging) return; // 如果日志已停止,则不再处理日志
+
+        // 每次更新一条日志,延迟 200 毫秒逐条更新
+        uiHandler.postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                if (!logQueue.isEmpty()) {
+                    String logMessage = logQueue.poll();
+                    LogLevel logLevel = getLogLevelFromMessage(logMessage); // 获取日志等级
+                    updateLogOnScreen(logMessage, logLevel);
+                    // 延迟继续更新,直到队列为空
+                    processLogs();
+                }
+            }
+        }, 200); // 每次更新的间隔时间为 200 毫秒
+    }
+
+    // 假设你有方法可以从日志消息中解析出日志等级
+    private LogLevel getLogLevelFromMessage(String logMessage) {
+        if (logMessage.contains("DEBUG")) {
+            return LogLevel.DEBUG;
+        } else if (logMessage.contains("INFO")) {
+            return LogLevel.INFO;
+        } else if (logMessage.contains("WARN")) {
+            return LogLevel.WARN;
+        } else if (logMessage.contains("ERROR")) {
+            return LogLevel.ERROR;
+        }
+        return LogLevel.DEBUG; // 默认等级
+    }
+
+    // 更新 logTextView 中的内容,并滚动到最后一行
+    private void updateLogOnScreen(String logMessage, LogLevel logLevel) {
+        // 在更新 UI 之前,先进行日志等级的检查
+        if (logLevel.ordinal() < currentLogLevel.ordinal()) {
+            return; // 如果日志等级低于当前设定的日志等级,跳过此日志
+        }
+
+        if (logTextView != null) {
+            // 使用 append() 来追加内容而不是替换
+            logTextView.append("\n" + logMessage);
+
+            // 确保 TextView 的内容不会超过最大长度
+            if (logTextView.getText().length() > MAX_TEXT_LENGTH) {
+                // 清除部分旧日志,确保不超过最大字符数
+                String currentText = logTextView.getText().toString();
+                int excessLength = currentText.length() - MAX_TEXT_LENGTH;
+                logTextView.setText(currentText.substring(excessLength)); // 删除最早的日志
+            }
+
+            // 滚动到最后一行
+            if (logScrollView != null) {
+                logScrollView.post(() -> logScrollView.fullScroll(ScrollView.FOCUS_DOWN));
+            }
+        }
+    }
+
+    // 清空日志内容
+    public void clearLogText() {
+        if (logTextView != null) logTextView.setText("");
+    }
+
+    // 启动日志记录
+    public void startLogging() {
+        isLogging = true;
+    }
+
+    // 停止日志记录
+    public void stopLogging() {
+        isLogging = false;
+        logQueue.clear();
+        if (logTextView != null) {
+            logTextView.setText("");
+        }
+    }
+}

+ 107 - 0
Android/app/src/main/java/com/easygbs/easygbd/push/G711Code.java

@@ -0,0 +1,107 @@
+package com.easygbs.easygbd.push;
+
+/**
+ * 核心转换
+ * Created by Magic.XL
+ */
+
+public class G711Code {
+
+    private final static int SIGN_BIT = 0x80;
+    private final static int QUANT_MASK = 0xf;
+    private final static int SEG_SHIFT = 4;
+    private final static int SEG_MASK = 0x70;
+
+    static short[] seg_end = {0xFF, 0x1FF, 0x3FF, 0x7FF, 0xFFF, 0x1FFF, 0x3FFF, 0x7FFF};
+
+    static short search(short val, short[] table, short size) {
+        for (short i = 0; i < size; i++) {
+            if (val <= table[i]) {
+                return i;
+            }
+        }
+
+        return size;
+    }
+
+    static byte linear2alaw(short pcm_val) {
+        short mask;
+        short seg;
+        char aval;
+        if (pcm_val >= 0) {
+            mask = 0xD5;
+        } else {
+            mask = 0x55;
+            pcm_val = (short) (-pcm_val - 1);
+            if (pcm_val < 0) {
+                pcm_val = 32767;
+            }
+        }
+
+        /* Convert the scaled magnitude to segment number. */
+        seg = search(pcm_val, seg_end, (short) 8);
+
+        /* Combine the sign, segment, and quantization bits. */
+
+        if (seg >= 8)       /* out of range, return maximum value. */
+            return (byte) (0x7F ^ mask);
+        else {
+            aval = (char) (seg << SEG_SHIFT);
+            if (seg < 2)
+                aval |= (pcm_val >> 4) & QUANT_MASK;
+            else
+                aval |= (pcm_val >> (seg + 3)) & QUANT_MASK;
+            return (byte) (aval ^ mask);
+        }
+    }
+
+
+    static short alaw2linear(byte a_val) {
+        short t;
+        short seg;
+
+        a_val ^= 0x55;
+
+        t = (short) ((a_val & QUANT_MASK) << 4);
+        seg = (short) ((a_val & SEG_MASK) >> SEG_SHIFT);
+        switch (seg) {
+            case 0:
+                t += 8;
+                break;
+            case 1:
+                t += 0x108;
+                break;
+            default:
+                t += 0x108;
+                t <<= seg - 1;
+        }
+        return (a_val & SIGN_BIT) != 0 ? t : (short) -t;
+    }
+
+    /**
+     * pcm 转 G711 a率
+     *
+     * @param pcm
+     * @param code
+     * @param size
+     */
+    public static void G711aEncoder(short[] pcm, byte[] code, int size) {
+        for (int i = 0; i < size; i++) {
+            code[i] = linear2alaw(pcm[i]);
+        }
+    }
+
+    /**
+     * G.711 转 PCM
+     *
+     * @param pcm
+     * @param code
+     * @param size
+     */
+    public static void G711aDecoder(short[] pcm, byte[] code, int size) {
+        for (int i = 0; i < size; i++) {
+            pcm[i] = alaw2linear(code[i]);
+        }
+    }
+
+}

+ 1252 - 0
Android/app/src/main/java/com/easygbs/easygbd/push/MediaStream.java

@@ -0,0 +1,1252 @@
+package com.easygbs.easygbd.push;
+
+import org.easydarwin.util.RecordStatusListener;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.ImageFormat;
+import android.graphics.SurfaceTexture;
+import android.hardware.Camera;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.AudioTrack;
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecList;
+import android.media.MediaFormat;
+import android.os.Build;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Message;
+import android.os.Process;
+import android.util.Log;
+
+import com.easygbs.easygbd.common.Constant;
+import com.easygbs.easygbd.util.SPHelper;
+
+import org.easydarwin.util.SPUtil;
+
+import com.easygbs.easygbd.util.SipUtil;
+import com.easygbs.Device;
+import com.serenegiant.usb.IFrameCallback;
+import com.serenegiant.usb.UVCCamera;
+
+import org.easydarwin.bus.StartRecord;
+import org.easydarwin.bus.StopRecord;
+import org.easydarwin.bus.SupportResolution;
+import org.easydarwin.common.EasyGBDConstant;
+import org.easydarwin.encode.AudioStream;
+import org.easydarwin.encode.ClippableVideoConsumer;
+import org.easydarwin.encode.HWConsumer;
+import org.easydarwin.encode.SWConsumer;
+import org.easydarwin.encode.VideoConsumer;
+import org.easydarwin.muxer.EasyMuxer;
+import org.easydarwin.muxer.RecordVideoConsumer;
+import org.easydarwin.push.Pusher;
+import org.easydarwin.sw.JNIUtil;
+import org.easydarwin.util.Util;
+import org.greenrobot.eventbus.EventBus;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.lang.ref.WeakReference;
+import java.nio.ByteBuffer;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.PriorityQueue;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.ReentrantLock;
+
+import static android.media.AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
+import static android.media.MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedPlanar;
+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;
+
+import com.easygbs.easygbd.service.*;
+
+public class MediaStream {
+    private static final String TAG = MediaStream.class.getSimpleName();
+    private static final int SWITCH_CAMERA = 11;
+
+    private final boolean enableVideo;
+    private boolean mSWCodec, mHevc;    // mSWCodec是否软编码, mHevc是否H265
+
+    private String recordPath;          // 录像地址
+    boolean isPushStream = false;       // 是否要推送数据
+    private int displayRotationDegree;  // 旋转角度
+
+    private Context context;
+    WeakReference<SurfaceTexture> mSurfaceHolderRef;
+
+    private VideoConsumer mVC, mRecordVC;
+    private AudioStream audioStream;
+    private EasyMuxer mEasyMuxer;
+    private Pusher mEasyPusher;
+
+    private final HandlerThread mCameraThread;
+    private final Handler mCameraHandler;
+
+    private TheRecordStatusListener mTheRecordStatusListener = null;
+
+    private UVCCamera uvcCamera;
+
+    BlockingQueue<byte[]> cache = new ArrayBlockingQueue<byte[]>(100);
+
+    public SimpleDateFormat ymdhm = new SimpleDateFormat("yyyyMMddHHmmss", Locale.CHINA);
+
+    /*
+     0:Camera.CameraInfo.CAMERA_FACING_BACK
+     1:Camera.CameraInfo.CAMERA_FACING_FRONT
+     CAMERA_FACING_BACK_UVC
+    */
+    int mCameraId = Camera.CameraInfo.CAMERA_FACING_BACK;
+    public static final int CAMERA_FACING_BACK_UVC = 2;
+    public static final int CAMERA_FACING_BACK_LOOP = -1;
+
+    private int frameWidth;
+    private int frameHeight;
+
+    int defaultWidth = 1920;
+    int defaultHeight = 1080;
+
+    private int mTargetCameraId;
+
+    //通道ID
+    private int channelid = 0;
+
+    //录像时间
+    private long durationMillis = 5 * 60 * 1000; //五分钟
+
+    Camera mCamera;
+    private Camera.CameraInfo camInfo;
+    private Camera.Parameters parameters;
+    private byte[] i420_buffer;
+
+    public MediaStream(Context context, SurfaceTexture texture, boolean enableVideo) {
+        this.context = context;
+        audioStream = AudioStream.getInstance(context);
+        mSurfaceHolderRef = new WeakReference(texture);
+
+        mTheRecordStatusListener = new TheRecordStatusListener();
+
+        mCameraThread = new HandlerThread("CAMERA") {
+            public void run() {
+                try {
+                    super.run();
+                } catch (Throwable e) {
+                    Log.e(TAG, "HandlerThread  Throwable   " + e.toString());
+
+                    Intent intent = new Intent(context, BackgroundCameraService.class);
+                    context.stopService(intent);
+                } finally {
+                    Log.i(TAG, "HandlerThread  finally");
+                    stopStream();
+                    stopPreview();
+                    destroyCamera();
+                }
+            }
+        };
+
+        mCameraThread.start();
+
+        mCameraHandler = new Handler(mCameraThread.getLooper()) {
+            @Override
+            public void handleMessage(Message msg) {
+                super.handleMessage(msg);
+
+                if (msg.what == SWITCH_CAMERA) {
+                    switchCameraTask.run();
+                }
+            }
+        };
+
+        this.enableVideo = enableVideo;
+    }
+
+    public void createCamera() {
+        if (Thread.currentThread() != mCameraThread) {
+            mCameraHandler.post(() -> {
+                Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
+                createCamera();
+            });
+            return;
+        }
+
+        mSWCodec = SPUtil.getswCodec(context);
+        mHevc = SPUtil.getHevcCodec(context);
+
+        mEasyPusher = new Device(context);
+        ((Device) mEasyPusher).setCallback(new Device.OnInitPusherCallback() {
+
+            @Override
+            public void onCallback(int channel, int code, String name) {
+                Log.i(TAG, "onCallback  channel  " + channel + "  code  " + code + "  name  " + name);
+                EventBus.getDefault().post(new PushCallback(channelid, code, name));
+            }
+
+            @Override
+            public void onSourceCallBack(Device.FrameInfo frameInfo) {
+                try {
+                    mQueue.put(frameInfo);
+                } catch (InterruptedException e) {
+                    e.printStackTrace();
+                }
+
+//                Log.i(TAG, String.format("audio queue size :%d", mQueue.size()));
+            }
+
+        });
+
+        if (!enableVideo) {
+            return;
+        }
+
+        if (mCameraId == CAMERA_FACING_BACK_UVC) {
+            createUvcCamera();
+        } else {
+            createNativeCamera();
+        }
+    }
+
+    private void createNativeCamera() {
+        try {
+            mCamera = Camera.open(mCameraId);
+            mCamera.setErrorCallback((i, camera) -> {
+                throw new IllegalStateException("Camera Error:" + i);
+            });
+
+            parameters = mCamera.getParameters();
+
+            if (Util.getSupportResolution(context).size() == 0) {
+                StringBuilder stringBuilder = new StringBuilder();
+
+                List<Camera.Size> supportedPreviewSizes = parameters.getSupportedPreviewSizes();
+
+                for (Camera.Size str : supportedPreviewSizes) {
+                    stringBuilder.append(str.width + "x" + str.height).append(";");
+                }
+
+                Util.saveSupportResolution(context, stringBuilder.toString());
+            }
+
+            EventBus.getDefault().post(new SupportResolution());
+
+            camInfo = new Camera.CameraInfo();
+            Camera.getCameraInfo(mCameraId, camInfo);
+            int cameraRotationOffset = camInfo.orientation;
+
+            if (mCameraId == Camera.CameraInfo.CAMERA_FACING_FRONT)
+                cameraRotationOffset += 180;
+
+            int rotate = (360 + cameraRotationOffset - displayRotationDegree) % 360;
+            parameters.setRotation(rotate);
+            ArrayList<CodecInfo> infos = listEncoders(mHevc ? MediaFormat.MIMETYPE_VIDEO_HEVC : MediaFormat.MIMETYPE_VIDEO_AVC);
+
+            if (!infos.isEmpty()) {
+                CodecInfo ci = infos.get(0);
+                info.mName = ci.mName;
+                info.mColorFormat = ci.mColorFormat;
+            } else {
+                mSWCodec = true;
+            }
+
+            int videoresolution = SPUtil.getVideoresolution(context);
+            if (videoresolution == 0) {
+                defaultWidth = 1920;
+                defaultHeight = 1080;
+            } else if (videoresolution == 1) {
+                defaultWidth = 1280;
+                defaultHeight = 960;
+            } else if (videoresolution == 2) {
+                defaultWidth = 1280;
+                defaultHeight = 720;
+            }
+
+            boolean isfind = false;
+            List<Camera.Size> sizes = parameters.getSupportedPreviewSizes();
+            for (int i = 0; i < sizes.size(); i++) {
+                Camera.Size mSize = sizes.get(i);
+                if ((mSize.width == defaultWidth) && (mSize.height == defaultHeight)) {
+                    isfind = true;
+                    break;
+                }
+            }
+
+            if (!isfind) {
+                defaultWidth = sizes.get(0).width;
+                defaultHeight = sizes.get(0).height;
+            }
+
+            parameters.setPreviewSize(defaultWidth, defaultHeight);
+
+            int[] ints = determineMaximumSupportedFramerate(parameters);
+            parameters.setPreviewFpsRange(ints[0], ints[1]);
+
+            List<String> supportedFocusModes = parameters.getSupportedFocusModes();
+
+            if (supportedFocusModes == null)
+                supportedFocusModes = new ArrayList<>();
+
+            if (supportedFocusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
+                parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
+            } else if (supportedFocusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)) {
+                parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
+            }
+
+            mCamera.setParameters(parameters);
+
+            int displayRotation;
+            displayRotation = (cameraRotationOffset - displayRotationDegree + 360) % 360;
+            mCamera.setDisplayOrientation(displayRotation);
+        } catch (Exception e) {
+            Log.e(TAG, "createNativeCamera  Exception  " + e.toString());
+
+            destroyCamera();
+        }
+    }
+
+    private void createUvcCamera() {
+        frameWidth = defaultWidth;
+        frameHeight = defaultHeight;
+
+        uvcCamera = UVCCameraService.liveData.getValue();
+        if (uvcCamera != null) {
+            uvcCamera.setPreviewSize(frameWidth,
+                    frameHeight,
+                    1,
+                    30,
+                    UVCCamera.PIXEL_FORMAT_YUV420SP, 1.0f);
+        }
+
+        if (uvcCamera == null) {
+            mCameraId = Camera.CameraInfo.CAMERA_FACING_BACK;
+            createNativeCamera();
+        }
+    }
+
+    public synchronized void destroyCamera() {
+        if (Thread.currentThread() != mCameraThread) {
+            mCameraHandler.post(() -> destroyCamera());
+            return;
+        }
+
+        if (uvcCamera != null) {
+            uvcCamera.destroy();
+            uvcCamera = null;
+        }
+
+        if (mCamera != null) {
+            mCamera.stopPreview();
+
+            try {
+                mCamera.release();
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+
+            mCamera = null;
+        }
+
+        if (mEasyMuxer != null) {
+            mEasyMuxer.release();
+            mEasyMuxer = null;
+        }
+    }
+
+    //回收线程
+    public void release() {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
+            mCameraThread.quitSafely();
+        } else {
+            if (!mCameraHandler.post(() -> mCameraThread.quit())) {
+                mCameraThread.quit();
+            }
+        }
+
+        try {
+            mCameraThread.join();
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+    }
+
+    public synchronized void startPreview() {
+        if (Thread.currentThread() != mCameraThread) {
+            mCameraHandler.post(() -> startPreview());
+            return;
+        }
+
+        if (uvcCamera != null) {
+//            Log.i(TAG, "uvcCamera != null");
+            startUvcPreview();
+        } else if (mCamera != null) {
+//            Log.i(TAG, "mCamera != null");
+            startCameraPreview();
+        }
+
+        SPHelper mSPHelper = SPHelper.instance(context);
+        Constant.Root = (String) mSPHelper.get(Constant.ROOT, "");
+        if (Constant.Root.equals("")) {
+            Constant.Root = context.getExternalFilesDir(null).getAbsolutePath() + File.separator + Constant.DIR;
+            mSPHelper.put(Constant.ROOT, Constant.Root);
+
+            File rootdir = new File(Constant.Root);
+            if (!rootdir.exists()) {
+                rootdir.mkdirs();
+            }
+        }
+        mHevc = SPUtil.getHevcCodec(context);
+        if (mSWCodec) {
+            SWConsumer sw = new SWConsumer(context, mEasyPusher, SPUtil.getBitrateKbps(context), channelid, Constant.Root);
+            mVC = new ClippableVideoConsumer(context, sw, frameWidth, frameHeight, SPUtil.getEnableVideoOverlay(context));
+        } else {
+            HWConsumer hw = new HWConsumer(context,
+                    mHevc ? MediaFormat.MIMETYPE_VIDEO_HEVC : MediaFormat.MIMETYPE_VIDEO_AVC,
+                    mEasyPusher,
+                    SPUtil.getBitrateKbps(context),
+                    info.mName,
+                    info.mColorFormat,
+                    channelid, Constant.Root);
+            mVC = new ClippableVideoConsumer(context, hw, frameWidth, frameHeight, SPUtil.getEnableVideoOverlay(context));
+        }
+
+        if (uvcCamera != null || mCamera != null) {
+            mVC.onVideoStart(frameWidth, frameHeight);
+        }
+
+        audioStream.setEnableAudio(SPUtil.getEnableAudio(context));
+        audioStream.addPusher(mEasyPusher);
+    }
+
+    private void startUvcPreview() {
+        SurfaceTexture holder = mSurfaceHolderRef.get();
+        if (holder != null) {
+            uvcCamera.setPreviewTexture(holder);
+        }
+
+        try {
+            uvcCamera.setFrameCallback(uvcFrameCallback, UVCCamera.PIXEL_FORMAT_YUV420SP/*UVCCamera.PIXEL_FORMAT_NV21*/);
+            uvcCamera.startPreview();
+        } catch (Throwable e) {
+            e.printStackTrace();
+        }
+    }
+
+    private void startCameraPreview() {
+        int previewFormat = parameters.getPreviewFormat();
+
+        Camera.Size previewSize = parameters.getPreviewSize();
+        int size = previewSize.width * previewSize.height * ImageFormat.getBitsPerPixel(previewFormat) / 8;
+
+        defaultWidth = previewSize.width;
+        defaultHeight = previewSize.height;
+
+        mCamera.addCallbackBuffer(new byte[size]);
+        mCamera.addCallbackBuffer(new byte[size]);
+        mCamera.setPreviewCallbackWithBuffer(previewCallback);
+
+
+        try {
+            // TextureView的
+            SurfaceTexture holder = mSurfaceHolderRef.get();
+
+            if (holder != null) {
+                mCamera.setPreviewTexture(holder);
+//                Log.i(TAG, "setPreviewTexture");
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+
+        mCamera.startPreview();
+
+        boolean frameRotate;
+        int result;
+
+        if (camInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
+            result = (camInfo.orientation + displayRotationDegree) % 360;
+        } else {  // back-facing
+            result = (camInfo.orientation - displayRotationDegree + 360) % 360;
+        }
+
+        frameRotate = result % 180 != 0;
+
+        frameWidth = frameRotate ? defaultHeight : defaultWidth;
+        frameHeight = frameRotate ? defaultWidth : defaultHeight;
+    }
+
+    public synchronized void stopPreview() {
+        if (Thread.currentThread() != mCameraThread) {
+            mCameraHandler.post(() -> stopPreview());
+            return;
+        }
+
+        if (uvcCamera != null) {
+            uvcCamera.stopPreview();
+        }
+
+        //关闭摄像头
+        if (mCamera != null) {
+            mCamera.stopPreview();
+            mCamera.setPreviewCallbackWithBuffer(null);
+        }
+
+        //关闭音频采集和音频编码器
+        if (audioStream != null) {
+            audioStream.removePusher(mEasyPusher);
+            audioStream.setMuxer(null);
+        }
+
+        //关闭视频编码器
+        if (mVC != null) {
+            mVC.onVideoStop();
+        }
+
+        //关闭录像的编码器
+        if (mRecordVC != null) {
+            mRecordVC.onVideoStop();
+        }
+
+        //关闭音视频合成器
+        if (mEasyMuxer != null) {
+            mEasyMuxer.release();
+            mEasyMuxer = null;
+        }
+    }
+
+    //开始推流
+    public void startStream() throws IOException {
+        try {
+            mEasyPusher.initPush(SipUtil.getInstance(context).getSIP());
+            isPushStream = true;
+
+            mQueue.clear();
+            startAudio(); //启动对讲
+        } catch (Exception ex) {
+            Log.e(TAG, "startStream  Exception  " + ex.toString());
+        }
+    }
+
+    //停止推流
+    public void stopStream() {
+        Log.i(TAG, "stopStream");
+        if (mEasyPusher != null) {
+            mEasyPusher.stop();
+        }
+
+        isPushStream = false;
+
+        if (mAudioThread != null) {
+            mAudioThread.interrupt();
+            try {
+                mAudioThread.join();
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+
+            mAudioThread = null;
+        }
+    }
+
+    //开始录像
+    public synchronized void startRecord() {
+        if (Thread.currentThread() != mCameraThread) {
+            mCameraHandler.post(() -> startRecord());
+            return;
+        }
+
+        if (mCamera == null) {
+            return;
+        }
+
+        String mFilePath = new File(recordPath, ymdhm.format(new Date())).toString();
+
+        mEasyMuxer = new EasyMuxer(recordPath, mFilePath, durationMillis, mTheRecordStatusListener);
+
+        mRecordVC = new RecordVideoConsumer(
+                context, mHevc ? MediaFormat.MIMETYPE_VIDEO_HEVC : MediaFormat.MIMETYPE_VIDEO_AVC,
+                mEasyMuxer,
+                SPUtil.getEnableVideoOverlay(context),
+                SPUtil.getBitrateKbps(context),
+                info.mName, info.mColorFormat,
+                channelid,
+                Constant.Root);
+        mRecordVC.onVideoStart(frameWidth, frameHeight);
+        if (audioStream != null) {
+            audioStream.setMuxer(mEasyMuxer);
+        }
+    }
+
+    public synchronized void stopRecord() {
+        if (Thread.currentThread() != mCameraThread) {
+            mCameraHandler.post(() -> stopRecord());
+            return;
+        }
+
+        if (mRecordVC == null || audioStream == null) {
+
+        } else {
+            audioStream.setMuxer(null);
+            mRecordVC.onVideoStop();
+            mRecordVC = null;
+        }
+
+        if (mEasyMuxer != null)
+            mEasyMuxer.release();
+
+        mEasyMuxer = null;
+    }
+
+    public void updateResolution(final int w, final int h) {
+        if (mCamera == null)
+            return;
+
+        stopPreview();
+        destroyCamera();
+
+        mCameraHandler.post(() -> {
+            defaultWidth = w;
+            defaultHeight = h;
+        });
+
+        createCamera();
+        startPreview();
+    }
+
+    public void upDateAllowAudio() {
+        if (audioStream != null) audioStream.upDateAllowAudio();
+    }
+
+    /**
+     * 切换前后摄像头
+     * CAMERA_FACING_BACK_LOOP                 循环切换摄像头
+     * Camera.CameraInfo.CAMERA_FACING_BACK    后置摄像头
+     * Camera.CameraInfo.CAMERA_FACING_FRONT   前置摄像头
+     * CAMERA_FACING_BACK_UVC                  UVC摄像头
+     */
+    public void switchCamera(int cameraId) {
+        this.mTargetCameraId = cameraId;
+
+        if (mCameraHandler.hasMessages(SWITCH_CAMERA)) {
+            return;
+        } else {
+            mCameraHandler.sendEmptyMessage(SWITCH_CAMERA);
+        }
+    }
+
+    public void switchCamera() {
+        switchCamera(CAMERA_FACING_BACK_LOOP);
+    }
+
+    private Runnable switchCameraTask = new Runnable() {
+        @Override
+        public void run() {
+            if (!enableVideo)
+                return;
+
+            try {
+                if (mTargetCameraId != CAMERA_FACING_BACK_LOOP && mCameraId == mTargetCameraId) {
+                    if (uvcCamera != null || mCamera != null) {
+                        return;
+                    }
+                }
+
+                if (mTargetCameraId == CAMERA_FACING_BACK_LOOP) {
+                    if (mCameraId == Camera.CameraInfo.CAMERA_FACING_BACK) {
+                        mCameraId = Camera.CameraInfo.CAMERA_FACING_FRONT;
+                    } else if (mCameraId == Camera.CameraInfo.CAMERA_FACING_FRONT) {
+                        mCameraId = CAMERA_FACING_BACK_UVC;// 尝试切换到外置摄像头
+                    } else {
+                        mCameraId = Camera.CameraInfo.CAMERA_FACING_BACK;
+                    }
+                } else {
+                    mCameraId = mTargetCameraId;
+                }
+
+                stopPreview();
+                destroyCamera();
+                createCamera();
+                startPreview();
+            } catch (Exception e) {
+                e.printStackTrace();
+            } finally {
+
+            }
+        }
+    };
+
+    Camera.PreviewCallback previewCallback = (data, camera) -> {
+        if (data == null)
+            return;
+
+        int result;
+        if (camInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
+            result = (camInfo.orientation + displayRotationDegree) % 360;
+        } else {
+            result = (camInfo.orientation - displayRotationDegree + 360) % 360;
+        }
+
+        if (i420_buffer == null || i420_buffer.length != data.length) {
+            i420_buffer = new byte[data.length];
+        }
+
+        JNIUtil.ConvertToI420(data, i420_buffer, defaultWidth, defaultHeight, 0, 0, defaultWidth, defaultHeight, result % 360, 2);
+        System.arraycopy(i420_buffer, 0, data, 0, data.length);
+
+        if (mRecordVC != null) {
+            mRecordVC.onVideo(i420_buffer, 0);
+        }
+
+        mVC.onVideo(data, 0);
+        mCamera.addCallbackBuffer(data);
+    };
+
+    final IFrameCallback uvcFrameCallback = new IFrameCallback() {
+        @Override
+        public void onFrame(ByteBuffer frame) {
+            if (uvcCamera == null)
+                return;
+
+            Thread.currentThread().setName("UVCCamera");
+            frame.clear();
+
+            byte[] data = cache.poll();
+            if (data == null) {
+                data = new byte[frame.capacity()];
+            }
+
+            frame.get(data);
+
+//            bufferQueue.offer(data);
+//            mCameraHandler.post(dequeueRunnable);
+
+            onPreviewFrame2(data, uvcCamera);
+        }
+    };
+
+    public void onPreviewFrame2(byte[] data, Object camera) {
+        if (data == null)
+            return;
+
+        if (i420_buffer == null || i420_buffer.length != data.length) {
+            i420_buffer = new byte[data.length];
+        }
+
+        JNIUtil.ConvertToI420(data, i420_buffer,
+                defaultWidth, defaultHeight,
+                0, 0,
+                defaultWidth, defaultHeight,
+                0, 2);
+        System.arraycopy(i420_buffer, 0, data, 0, data.length);
+
+        if (mRecordVC != null) {
+            mRecordVC.onVideo(i420_buffer, 0);
+        }
+
+        mVC.onVideo(data, 0);
+    }
+
+    /* ============================== CodecInfo ============================== */
+
+    public static CodecInfo info = new CodecInfo();
+
+    public static class CodecInfo {
+        public String mName;
+        public int mColorFormat;
+    }
+
+    public static ArrayList<CodecInfo> listEncoders(String mime) {
+        // 可能有多个编码库,都获取一下
+        ArrayList<CodecInfo> codecInfoList = new ArrayList<>();
+        int numCodecs = MediaCodecList.getCodecCount();
+
+        // int colorFormat = 0;
+        // String name = null;
+        for (int i1 = 0; i1 < numCodecs; i1++) {
+            MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i1);
+
+            if (!codecInfo.isEncoder()) {
+                continue;
+            }
+
+            if (codecMatch(mime, codecInfo)) {
+                String name = codecInfo.getName();
+                int colorFormat = getColorFormat(codecInfo, mime);
+
+                if (colorFormat != 0) {
+                    CodecInfo ci = new CodecInfo();
+                    ci.mName = name;
+                    ci.mColorFormat = colorFormat;
+                    codecInfoList.add(ci);
+                }
+            }
+        }
+
+        return codecInfoList;
+    }
+
+    /* ============================== private method ============================== */
+
+    private static boolean codecMatch(String mimeType, MediaCodecInfo codecInfo) {
+        String[] types = codecInfo.getSupportedTypes();
+
+        for (String type : types) {
+            if (type.equalsIgnoreCase(mimeType)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    private static int getColorFormat(MediaCodecInfo codecInfo, String mimeType) {
+        // 在ByteBuffer模式下,视频缓冲区根据其颜色格式进行布局。
+        MediaCodecInfo.CodecCapabilities capabilities = codecInfo.getCapabilitiesForType(mimeType);
+        int[] cf = new int[capabilities.colorFormats.length];
+        System.arraycopy(capabilities.colorFormats, 0, cf, 0, cf.length);
+        List<Integer> sets = new ArrayList<>();
+
+        for (int i = 0; i < cf.length; i++) {
+            sets.add(cf[i]);
+        }
+
+        if (sets.contains(COLOR_FormatYUV420SemiPlanar)) {
+            return COLOR_FormatYUV420SemiPlanar;
+        } else if (sets.contains(COLOR_FormatYUV420Planar)) {
+            return COLOR_FormatYUV420Planar;
+        } else if (sets.contains(COLOR_FormatYUV420PackedPlanar)) {
+            return COLOR_FormatYUV420PackedPlanar;
+        } else if (sets.contains(COLOR_TI_FormatYUV420PackedSemiPlanar)) {
+            return COLOR_TI_FormatYUV420PackedSemiPlanar;
+        }
+
+        return 0;
+    }
+
+    private static int[] determineMaximumSupportedFramerate(Camera.Parameters parameters) {
+        int[] maxFps = new int[]{0, 0};
+        List<int[]> supportedFpsRanges = parameters.getSupportedPreviewFpsRange();
+
+        for (Iterator<int[]> it = supportedFpsRanges.iterator(); it.hasNext(); ) {
+            int[] interval = it.next();
+
+            if (interval[1] > maxFps[1] || (interval[0] > maxFps[0] && interval[1] == maxFps[1])) {
+                maxFps = interval;
+            }
+        }
+
+        return maxFps;
+    }
+
+    public void setRecordPath(String recordPath) {
+        this.recordPath = recordPath;
+    }
+
+    public boolean isRecording() {
+        return mEasyMuxer != null;
+    }
+
+    public void setSurfaceTexture(SurfaceTexture texture) {
+        mSurfaceHolderRef = new WeakReference<SurfaceTexture>(texture);
+    }
+
+    public boolean isStreaming() {
+        return isPushStream;
+    }
+
+    public Camera getCamera() {
+        return mCamera;
+    }
+
+    public int getDisplayRotationDegree() {
+        return displayRotationDegree;
+    }
+
+    public void setDisplayRotationDegree(int degree) {
+        displayRotationDegree = degree;
+    }
+
+    /**
+     * 旋转YUV格式数据
+     *
+     * @param src    YUV数据
+     * @param format 0,420P;1,420SP
+     * @param width  宽度
+     * @param height 高度
+     * @param degree 旋转度数
+     */
+    private static void yuvRotate(byte[] src, int format, int width, int height, int degree) {
+        int offset = 0;
+        if (format == 0) {
+            JNIUtil.rotateMatrix(src, offset, width, height, degree);
+            offset += (width * height);
+            JNIUtil.rotateMatrix(src, offset, width / 2, height / 2, degree);
+            offset += width * height / 4;
+            JNIUtil.rotateMatrix(src, offset, width / 2, height / 2, degree);
+        } else if (format == 1) {
+            JNIUtil.rotateMatrix(src, offset, width, height, degree);
+            offset += width * height;
+            JNIUtil.rotateShortMatrix(src, offset, width / 2, height / 2, degree);
+        }
+    }
+
+    class TheRecordStatusListener extends RecordStatusListener {
+        @Override
+        public void msg(int action) {
+            switch (action) {
+                case EasyGBDConstant.STARTRECORD:
+                    EventBus.getDefault().post(new StartRecord());
+                    break;
+                case EasyGBDConstant.STOPRECORD:
+                    EventBus.getDefault().post(new StopRecord());
+                    break;
+            }
+        }
+    }
+
+    public void setCameraId(int id) {
+        mCameraId = id;
+    }
+
+    public int getCameraId() {
+        return mCameraId;
+    }
+
+    public void setLoLa(int channel, double longitude, double latitude) {
+        mEasyPusher.setLoLa(channel, longitude, latitude);
+    }
+
+    //    /* 音频编码 */
+    public static final int EASY_SDK_AUDIO_CODEC_AAC = 0x15002;     /* AAC */
+    public static final int EASY_SDK_AUDIO_CODEC_G711U = 0x10006;   /* G711 ulaw */
+    public static final int EASY_SDK_AUDIO_CODEC_G711A = 0x10007;   /* G711 alaw */
+
+    private FrameInfoQueue mQueue = new FrameInfoQueue();
+
+
+    private static class FrameInfoQueue extends PriorityQueue<Device.FrameInfo> {
+        public static final int CAPACITY = 500;
+        public static final int INITIAL_CAPACITY = 300;
+
+        public FrameInfoQueue() {
+            super(INITIAL_CAPACITY, new Comparator<Device.FrameInfo>() {
+                @Override
+                public int compare(Device.FrameInfo frameInfo, Device.FrameInfo t1) {
+                    return (int) (frameInfo.stamp - t1.stamp);
+                }
+            });
+        }
+
+        final ReentrantLock lock = new ReentrantLock();
+        final Condition notFull = lock.newCondition();
+        final Condition notVideo = lock.newCondition();
+        final Condition notAudio = lock.newCondition();
+
+        @Override
+        public int size() {
+            lock.lock();
+            try {
+                return super.size();
+            } finally {
+                lock.unlock();
+            }
+        }
+
+        @Override
+        public void clear() {
+            lock.lock();
+            try {
+                int size = super.size();
+                super.clear();
+                int k = size;
+
+                for (; k > 0 && lock.hasWaiters(notFull); k--) {
+                    notFull.signal();
+                }
+            } finally {
+                lock.unlock();
+            }
+        }
+
+        public void put(Device.FrameInfo x) throws InterruptedException {
+            lock.lockInterruptibly();
+
+            try {
+                int size;
+                while ((size = super.size()) == CAPACITY) {
+                    Log.i(TAG, "queue full:" + CAPACITY);
+                    notFull.await();
+                }
+
+                offer(x);
+
+                // 这里是乱序的。并非只有空的queue才丢到首位。因此不能做限制 if (size == 0)
+                {
+                    if (x.audio) {
+                        notAudio.signal();
+                    } else {
+                        notVideo.signal();
+                    }
+                }
+            } finally {
+                lock.unlock();
+            }
+        }
+
+        public Device.FrameInfo takeVideoFrame() throws InterruptedException {
+            lock.lockInterruptibly();
+
+            try {
+                while (true) {
+                    Device.FrameInfo x = peek();
+
+                    if (x == null) {
+                        notVideo.await();
+                    } else {
+                        if (!x.audio) {
+                            remove();
+                            notFull.signal();
+                            notAudio.signal();
+                            return x;
+                        } else {
+                            notVideo.await();
+                        }
+                    }
+                }
+            } finally {
+                lock.unlock();
+            }
+        }
+
+        public Device.FrameInfo takeVideoFrame(long ms) throws InterruptedException {
+            lock.lockInterruptibly();
+
+            try {
+                while (true) {
+                    Device.FrameInfo x = peek();
+
+                    if (x == null) {
+                        if (!notVideo.await(ms, TimeUnit.MILLISECONDS)) return null;
+                    } else {
+                        if (!x.audio) {
+                            remove();
+                            notFull.signal();
+                            notAudio.signal();
+                            return x;
+                        } else {
+                            notVideo.await();
+                        }
+                    }
+                }
+            } finally {
+                lock.unlock();
+            }
+        }
+
+        public Device.FrameInfo takeAudioFrame() throws InterruptedException {
+            lock.lockInterruptibly();
+
+            try {
+                while (true) {
+                    Device.FrameInfo x = peek();
+                    if (x == null) {
+                        notAudio.await();
+                    } else {
+                        if (x.audio) {
+                            remove();
+                            notFull.signal();
+                            notVideo.signal();
+                            return x;
+                        } else {
+                            notAudio.await();
+                        }
+                    }
+                }
+            } finally {
+                lock.unlock();
+            }
+        }
+    }
+
+
+    private volatile Thread mAudioThread;
+    private AudioTrack mAudioTrack;
+
+    private Device.MediaInfo mMediaInfo = new Device.MediaInfo(8000, 1, 16);
+
+
+    private void startAudio() {
+        mAudioThread = new Thread("AUDIO_CONSUMER") {
+            @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
+            @Override
+            public void run() {
+                Process.setThreadPriority(Process.THREAD_PRIORITY_AUDIO);
+                Device.FrameInfo frameInfo = null;
+
+                final AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+
+                AudioManager.OnAudioFocusChangeListener l = new AudioManager.OnAudioFocusChangeListener() {
+                    @Override
+                    public void onAudioFocusChange(int focusChange) {
+                        if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
+                            AudioTrack audioTrack = mAudioTrack;
+                            if (audioTrack != null) {
+                                audioTrack.setStereoVolume(1.0f, 1.0f);
+                                if (audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PAUSED) {
+                                    audioTrack.flush();
+                                    audioTrack.play();
+                                }
+                            }
+                        } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
+                            AudioTrack audioTrack = mAudioTrack;
+                            if (audioTrack != null) {
+                                if (audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) {
+                                    audioTrack.pause();
+                                }
+                            }
+                        } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) {
+                            AudioTrack audioTrack = mAudioTrack;
+                            if (audioTrack != null) {
+                                audioTrack.setStereoVolume(0.5f, 0.5f);
+                            }
+                        }
+                    }
+                };
+
+                try {
+                    int requestCode = am.requestAudioFocus(l, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
+                    if (requestCode != AUDIOFOCUS_REQUEST_GRANTED) {
+                        return;
+                    }
+
+//                    do {
+//                        frameInfo = mQueue.takeAudioFrame();
+//
+//                        if (mMediaInfo != null)
+//                            break;
+//                    } while (true);
+
+//                    final Thread t = Thread.currentThread();
+
+                    if (mAudioTrack == null) {
+//                        int sampleRateInHz = (int) (mMediaInfo.sample * 1.001);
+                        int sampleRateInHz = mMediaInfo.sample;
+                        int channelConfig = mMediaInfo.channel == 1 ? AudioFormat.CHANNEL_OUT_MONO : AudioFormat.CHANNEL_OUT_STEREO;
+                        int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
+
+//                        int bfSize = AudioTrack.getMinBufferSize(mMediaInfo.sample, channelConfig, audioFormat) * 8;
+                        int bfSize = AudioTrack.getMinBufferSize(mMediaInfo.sample, channelConfig, audioFormat);
+
+                        Log.d("Magic", String.format("Magic- bfSize: %d", bfSize));
+
+
+                        mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRateInHz, channelConfig, audioFormat, bfSize, AudioTrack.MODE_STREAM);
+                    }
+
+                    mAudioTrack.play();
+
+                    Log.i(TAG, String.format("POST VIDEO_DISPLAYED IN AUDIO THREAD!!!"));
+
+                    // 半秒钟的数据缓存
+//                    byte[] mBufferReuse = new byte[16000];
+
+                    int[] outLen = new int[1];
+                    while (mAudioThread != null) {
+
+                        if (frameInfo == null) {
+                            frameInfo = mQueue.takeAudioFrame();
+                        }
+
+                        short[] mBufferReuse = new short[frameInfo.buffer.length];
+
+                        outLen[0] = mBufferReuse.length;
+
+
+                        /////////////////// 第一种解码方式:///////////////////
+//                        int nRet = AudioCodec.decode(handle, frameInfo.buffer,
+//                                0, frameInfo.length, mBufferReuse, outLen);
+//                        if (nRet == 0) {
+//                            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+//                                mAudioTrack.write(mBufferReuse, 0, outLen[0], AudioTrack.WRITE_NON_BLOCKING);
+//                            } else {
+//                                mAudioTrack.write(mBufferReuse, 0, outLen[0]);
+//                            }
+//                        }
+
+                        /////////////////// 第二种解码方式:///////////////////
+
+
+                        G711Code.G711aDecoder(mBufferReuse, frameInfo.buffer, frameInfo.length);
+
+
+                        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+                        for (short s : mBufferReuse) {
+                            byteArrayOutputStream.write(s & 0xFF);       // Write the lower byte (8 bits)
+                            byteArrayOutputStream.write((s >> 8) & 0xFF); // Write the upper byte (8 bits)
+                        }
+                        byte[] byteBuffer = byteArrayOutputStream.toByteArray();
+
+                        mAudioTrack.write(byteBuffer, 0, byteBuffer.length);
+
+                        saveToFile(byteBuffer, "talk.pcm");
+
+                        frameInfo = null;
+                    }
+                } catch (Exception ex) {
+                    ex.printStackTrace();
+                } finally {
+                    am.abandonAudioFocus(l);
+
+                    AudioTrack track = mAudioTrack;
+                    if (track != null) {
+                        synchronized (track) {
+                            mAudioTrack = null;
+                            track.release();
+                        }
+                    }
+                }
+            }
+        };
+
+        mAudioThread.start();
+    }
+
+
+    private void saveToFile(byte[] buffer, String fileName) {
+        try {
+            File file = new File(context.getExternalFilesDir(null), fileName);
+
+            if (!file.exists()) {
+                try {
+                    boolean created = file.createNewFile();
+                    if (!created) {
+                        Log.e("FileOutput", "Failed to create file.");
+                        return;
+                    }
+                } catch (IOException e) {
+                    Log.e("FileOutput", "Error creating file: " + e.getMessage());
+                    return;
+                }
+            }
+            FileOutputStream fos = new FileOutputStream(file, true);
+            fos.write(buffer, 0, buffer.length);
+            fos.close();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+}

+ 42 - 0
Android/app/src/main/java/com/easygbs/easygbd/push/PushCallback.java

@@ -0,0 +1,42 @@
+package com.easygbs.easygbd.push;
+
+public class PushCallback {
+    private int channelid;
+    private int code;
+    private String name;
+
+    public PushCallback(int code,String name){
+        this.code = code;
+        this.name = name;
+    }
+
+    public PushCallback(int channelid,int code,String name){
+        this.channelid = channelid;
+        this.code = code;
+        this.name = name;
+    }
+
+    public int getChannelid() {
+        return channelid;
+    }
+
+    public void setChannelid(int channelid) {
+        this.channelid = channelid;
+    }
+
+    public int getCode() {
+        return code;
+    }
+
+    public void setCode(int code) {
+        this.code = code;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+}

+ 174 - 0
Android/app/src/main/java/com/easygbs/easygbd/service/BackgroundCameraService.java

@@ -0,0 +1,174 @@
+package com.easygbs.easygbd.service;
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.PixelFormat;
+import android.graphics.SurfaceTexture;
+import android.os.Binder;
+import android.os.Build;
+import android.os.IBinder;
+import android.provider.Settings;
+import android.view.Gravity;
+import android.view.TextureView;
+import android.view.WindowManager;
+import com.easygbs.easygbd.application.App;
+import androidx.core.app.NotificationCompat;
+import com.easygbs.easygbd.R;
+import com.easygbs.easygbd.push.*;
+import com.easygbs.easygbd.activity.*;
+
+public class BackgroundCameraService extends Service implements TextureView.SurfaceTextureListener {
+    private static final int NOTIFICATION_ID = 1;
+
+    /**
+     * 表示后台是否正在渲染
+     */
+    private TextureView mOutComeVideoView;
+    private WindowManager mWindowManager;
+    private MediaStream mMediaStream;
+
+    private final IBinder mBinder = new LocalBinder();
+    private SurfaceTexture mTexture;
+    private boolean mPenddingStartPreview;
+
+    @Override
+    public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
+        mTexture = surface;
+
+        if (mPenddingStartPreview) {
+            mMediaStream.setSurfaceTexture(mTexture);
+            mMediaStream.startPreview();
+            backGroundNotificate();
+        }
+    }
+
+    @Override
+    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
+
+    }
+
+    @Override
+    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
+        mTexture = null;
+        return true;
+    }
+
+    @Override
+    public void onSurfaceTextureUpdated(SurfaceTexture surface) {
+
+    }
+
+    public void activePreview() {
+        mMediaStream.stopPreview();
+        mPenddingStartPreview = true;
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            if (Settings.canDrawOverlays(this)) {
+                WindowManager.LayoutParams param = new WindowManager.LayoutParams();
+                param.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
+
+                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+                    param.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+                }
+
+                param.format = PixelFormat.TRANSLUCENT;
+                param.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+                param.alpha = 1.0f;
+                param.gravity = Gravity.LEFT | Gravity.TOP;
+                param.width = 1;
+                param.height = 1;
+
+                mWindowManager.addView(mOutComeVideoView, param);
+            }
+        } else {
+            WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(1, 1,
+                    WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY,
+                    WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH,
+                    PixelFormat.TRANSLUCENT);
+            layoutParams.gravity = Gravity.LEFT | Gravity.TOP;
+            mWindowManager.addView(mOutComeVideoView, layoutParams);
+        }
+    }
+
+    private void backGroundNotificate() {
+        Intent notificationIntent = new Intent(this,MainActivity.class);
+        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
+       Notification notification = new NotificationCompat.Builder(this, App.CHANNEL_CAMERA)
+                        .setContentTitle(getString(R.string.app_name))
+                        .setContentText(getString(R.string.video_uploading_in_background))
+                        .setSmallIcon(R.drawable.ic_stat_camera)
+                        .setContentIntent(pendingIntent)
+                        .build();
+
+        startForeground(NOTIFICATION_ID, notification);
+    }
+
+     public void inActivePreview() {
+                                                                    if (mOutComeVideoView != null) {
+            if (mOutComeVideoView.getParent() != null) {
+                mWindowManager.removeView(mOutComeVideoView);
+            }
+        }
+
+        stopForeground(true);
+    }
+
+    /**
+     * Class used for the client Binder.  Because we know this service always
+     * runs in the same process as its clients, we don't need to deal with IPC.
+     */
+    public class LocalBinder extends Binder {
+        public BackgroundCameraService getService() {
+            return BackgroundCameraService.this;
+        }
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mBinder;
+    }
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+
+        // Create new SurfaceView, set its size to 1x1, move it to the top left
+        // corner and set this service as a callback
+        mWindowManager = (WindowManager) this.getSystemService(Context.WINDOW_SERVICE);
+
+        mOutComeVideoView = new TextureView(this);
+        mOutComeVideoView.setSurfaceTextureListener(this);
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        if (intent == null) {
+            return START_NOT_STICKY;
+        }
+
+        return super.onStartCommand(intent, flags, startId);
+    }
+
+    @Override
+    public void onDestroy() {
+        stopForeground(true);
+
+        if (mOutComeVideoView != null) {
+            if (mOutComeVideoView.getParent() != null) {
+                mWindowManager.removeView(mOutComeVideoView);
+            }
+        }
+
+        super.onDestroy();
+    }
+
+    public MediaStream getMediaStream() {
+        return mMediaStream;
+    }
+
+    public void setMediaStream(MediaStream ms) {
+        mMediaStream = ms;
+    }
+}

+ 192 - 0
Android/app/src/main/java/com/easygbs/easygbd/service/UVCCameraService.java

@@ -0,0 +1,192 @@
+package com.easygbs.easygbd.service;
+import android.app.Service;
+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 androidx.lifecycle.LiveData;
+import com.easygbs.easygbd.BuildConfig;
+import com.easygbs.easygbd.R;
+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 java.nio.ByteBuffer;
+
+public class UVCCameraService extends Service {
+    private static final String TAG = UVCCameraService.class.getSimpleName();
+
+    private USBMonitor mUSBMonitor;
+    private UVCCamera mUVCCamera;
+
+    private SparseArray<UVCCamera> cameras = new SparseArray<>();
+
+    MyBinder binder = new MyBinder();
+
+    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();
+        }
+    }
+
+    public class MyBinder extends Binder {
+        public UVCCameraService getService() {
+            return UVCCameraService.this;
+        }
+    }
+
+    @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) {
+                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 + ")");
+                        }
+                    });
+
+                    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:");
+
+                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);
+                }
+            }
+
+            @Override
+            public void onCancel(UsbDevice usbDevice) {
+                releaseCamera();
+            }
+
+            @Override
+            public void onDettach(final UsbDevice device) {
+                Log.v(TAG, "onDettach:");
+                releaseCamera();
+            }
+        });
+
+        mUSBMonitor.setDeviceFilter(DeviceFilter.getDeviceFilters(this, R.xml.device_filter));
+        //mUSBMonitor.register();
+    }
+
+    @Override
+    public void onDestroy() {
+        releaseCamera();
+
+        if (mUSBMonitor != null) {
+            mUSBMonitor.unregister();
+        }
+
+        super.onDestroy();
+    }
+}

+ 49 - 0
Android/app/src/main/java/com/easygbs/easygbd/util/PeProxy.java

@@ -0,0 +1,49 @@
+package com.easygbs.easygbd.util;
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.util.Log;
+import androidx.annotation.NonNull;
+import androidx.fragment.app.FragmentManager;
+import pub.devrel.easypermissions.helper.BaseSupportPermissionsHelper;
+
+public class PeProxy<T> extends BaseSupportPermissionsHelper {
+    public String TAG= PeProxy.class.getSimpleName();
+
+    private BaseSupportPermissionsHelper<T> permissionsHelper;
+
+    @SuppressLint("RestrictedApi")
+    public PeProxy(BaseSupportPermissionsHelper<T> permissionHelper) {
+        super(permissionHelper.getHost());
+        this.permissionsHelper = permissionHelper;
+    }
+
+    @SuppressLint("RestrictedApi")
+    @Override
+    public FragmentManager getSupportFragmentManager() {
+        return permissionsHelper.getSupportFragmentManager();
+    }
+
+    @SuppressLint("RestrictedApi")
+    @Override
+    public void directRequestPermissions(int requestCode, @NonNull String... perms) {
+        permissionsHelper.directRequestPermissions(requestCode,perms);
+    }
+
+    @SuppressLint("RestrictedApi")
+    @Override
+    public boolean shouldShowRequestPermissionRationale(@NonNull String perm) {
+        return permissionsHelper.shouldShowRequestPermissionRationale(perm);
+    }
+
+    @SuppressLint("RestrictedApi")
+    @Override
+    public Context getContext() {
+        return permissionsHelper.getContext();
+    }
+
+    @SuppressLint("RestrictedApi")
+    @Override
+    public void showRequestPermissionRationale(@NonNull String rationale, @NonNull String positiveButton, @NonNull String negativeButton, int theme, int requestCode, @NonNull String... perms) {
+
+    }
+}

+ 78 - 0
Android/app/src/main/java/com/easygbs/easygbd/util/PeUtil.java

@@ -0,0 +1,78 @@
+package com.easygbs.easygbd.util;
+import android.Manifest;
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.content.Context;
+import android.util.Log;
+import androidx.appcompat.app.AppCompatActivity;
+import java.lang.reflect.Field;
+import pub.devrel.easypermissions.EasyPermissions;
+import pub.devrel.easypermissions.PermissionRequest;
+import pub.devrel.easypermissions.helper.BaseSupportPermissionsHelper;
+
+public class PeUtil {
+    private static String TAG= PeUtil.class.getSimpleName();
+    public static final int STORAGE=1;
+    public static final int CAMERA=2;
+    public static final int RECORDAUDIO=3;
+    public static final int INTERNET=4;
+    public static final int LOC=5;
+
+    public static boolean ishasPer(Context context,String permission){
+        return EasyPermissions.hasPermissions(context, permission);
+    }
+
+    public static void requestPer(Activity activity,int code){
+        PermissionRequest request=null;
+        switch(code){
+            case STORAGE:
+                 request = new PermissionRequest.Builder(activity, code, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE)
+                    .setRationale("启用存储权限")
+                    .setPositiveButtonText("确定")
+                    .setNegativeButtonText("取消")
+                    .build();
+            break;
+            case CAMERA:
+                 request = new PermissionRequest.Builder(activity, code, Manifest.permission.CAMERA)
+                        .setRationale("启用相机权限")
+                        .setPositiveButtonText("确定")
+                        .setNegativeButtonText("取消")
+                        .build();
+                break;
+            case RECORDAUDIO:
+                request = new PermissionRequest.Builder(activity, code, Manifest.permission.RECORD_AUDIO)
+                        .setRationale("启用录音权限")
+                        .setPositiveButtonText("确定")
+                        .setNegativeButtonText("取消")
+                        .build();
+                break;
+            case INTERNET:
+                request = new PermissionRequest.Builder(activity, code, Manifest.permission.RECORD_AUDIO)
+                        .setRationale("启用网络权限")
+                        .setPositiveButtonText("确定")
+                        .setNegativeButtonText("取消")
+                        .build();
+                break;
+            case LOC:
+                request = new PermissionRequest.Builder(activity, code, Manifest.permission.ACCESS_FINE_LOCATION,Manifest.permission.ACCESS_COARSE_LOCATION)
+                        .setRationale("启用位置权限")
+                        .setPositiveButtonText("确定")
+                        .setNegativeButtonText("取消")
+                        .build();
+                break;
+        }
+
+        try{
+            Class clazz = request.getClass();
+            Field field = clazz.getDeclaredField("mHelper");
+            field.setAccessible(true);
+            @SuppressLint("RestrictedApi")
+            BaseSupportPermissionsHelper<AppCompatActivity> permissionHelper = (BaseSupportPermissionsHelper<AppCompatActivity>) field.get(request);
+            field.set(request, new PeProxy<AppCompatActivity>(permissionHelper));
+        }catch(Exception e){
+            Log.e(TAG,"requestPer  Exception  "+e.toString());
+        }
+
+        EasyPermissions.requestPermissions(request);
+    }
+}

+ 54 - 0
Android/app/src/main/java/com/easygbs/easygbd/util/SPHelper.java

@@ -0,0 +1,54 @@
+package com.easygbs.easygbd.util;
+import android.app.Activity;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.util.Log;
+
+public class SPHelper {
+    private static String TAG= SPHelper.class.getSimpleName();
+    private static SPHelper _instance = null;
+
+    private SharedPreferences mSharedPreferences;
+    private SharedPreferences.Editor mEditor;
+
+    public static SPHelper instance(Context mContext) {
+        if (_instance == null) {
+            _instance = new SPHelper(mContext);
+        }
+        return _instance;
+    }
+
+    public SPHelper(Context context) {
+        try {
+            mSharedPreferences = context.getSharedPreferences("data",Activity.MODE_PRIVATE);
+            mEditor = mSharedPreferences.edit();
+        } catch (Exception e) {
+            Log.e(TAG,"SPHelper  Exception  "+e.toString());
+        }
+    }
+
+    public boolean put(String key, Object object) {
+        if (object instanceof String) {
+            mEditor.putString(key, (String) object);
+        } else if (object instanceof Integer) {
+            mEditor.putInt(key, (Integer) object);
+        } else if (object instanceof Boolean) {
+            mEditor.putBoolean(key, (Boolean) object);
+        } else {
+            mEditor.putString(key, object.toString());
+        }
+        return mEditor.commit();
+    }
+
+    public Object get(String key, Object defaultObject) {
+        if (defaultObject instanceof String) {
+            return mSharedPreferences.getString(key, (String) defaultObject);
+        } else if (defaultObject instanceof Integer) {
+            return mSharedPreferences.getInt(key, (Integer) defaultObject);
+        } else if (defaultObject instanceof Boolean) {
+            return mSharedPreferences.getBoolean(key, (Boolean) defaultObject);
+        } else {
+            return mSharedPreferences.getString(key, null);
+        }
+    }
+}

+ 19 - 0
Android/app/src/main/java/com/easygbs/easygbd/util/ScrUtil.java

@@ -0,0 +1,19 @@
+package com.easygbs.easygbd.util;
+import android.app.Activity;
+import android.graphics.Rect;
+import android.view.Window;
+
+public class ScrUtil {
+
+    public static int getStatusBarHeight(Activity activity){
+        int barHeight = activity.getResources().getIdentifier("status_bar_height","dimen","android");
+        if(barHeight > 0){
+            return activity.getResources().getDimensionPixelSize(barHeight);
+        }else{
+            Rect rectangle= new Rect();
+            Window window = activity.getWindow();
+            window.getDecorView().getWindowVisibleDisplayFrame(rectangle);
+            return rectangle.top;
+        }
+    }
+}

Різницю між файлами не показано, бо вона завелика
+ 0 - 0
Android/app/src/main/java/com/easygbs/easygbd/util/SipUtil.java


+ 50 - 0
Android/app/src/main/java/com/easygbs/easygbd/viewadapter/CommonAdapter.java

@@ -0,0 +1,50 @@
+package com.easygbs.easygbd.viewadapter;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+
+
+import java.util.List;
+
+
+public abstract class CommonAdapter<T> extends com.easygbs.easygbd.viewadapter.MultiItemTypeAdapter<T>
+{
+    protected Context mContext;
+    protected int mLayoutId;
+    protected List<T> mDatas;
+    protected LayoutInflater mInflater;
+
+    public CommonAdapter(final Context context, final int layoutId, List<T> datas)
+    {
+        super(context, datas);
+        mContext = context;
+        mInflater = LayoutInflater.from(context);
+        mLayoutId = layoutId;
+        mDatas = datas;
+
+        addItemViewDelegate(new com.easygbs.easygbd.viewadapter.ItemViewDelegate<T>()
+        {
+            @Override
+            public int getItemViewLayoutId()
+            {
+                return layoutId;
+            }
+
+            @Override
+            public boolean isForViewType( T item, int position)
+            {
+                return true;
+            }
+
+            @Override
+            public void convert(ViewHolder holder, T t, int position)
+            {
+                CommonAdapter.this.convert(holder, t, position);
+            }
+        });
+    }
+
+    protected abstract void convert(ViewHolder holder, T t, int position);
+
+
+}

+ 11 - 0
Android/app/src/main/java/com/easygbs/easygbd/viewadapter/ItemViewDelegate.java

@@ -0,0 +1,11 @@
+package com.easygbs.easygbd.viewadapter;
+public interface ItemViewDelegate<T>
+{
+
+    int getItemViewLayoutId();
+
+    boolean isForViewType(T item, int position);
+
+    void convert(ViewHolder holder, T t, int position);
+
+}

+ 113 - 0
Android/app/src/main/java/com/easygbs/easygbd/viewadapter/ItemViewDelegateManager.java

@@ -0,0 +1,113 @@
+package com.easygbs.easygbd.viewadapter;
+
+
+import androidx.collection.SparseArrayCompat;
+
+public class ItemViewDelegateManager<T>
+{
+    SparseArrayCompat<ItemViewDelegate<T>> delegates = new SparseArrayCompat();
+
+    public int getItemViewDelegateCount()
+    {
+        return delegates.size();
+    }
+
+    public ItemViewDelegateManager<T> addDelegate(ItemViewDelegate<T> delegate)
+    {
+        int viewType = delegates.size();
+        if (delegate != null)
+        {
+            delegates.put(viewType, delegate);
+            viewType++;
+        }
+        return this;
+    }
+
+    public ItemViewDelegateManager<T> addDelegate(int viewType, ItemViewDelegate<T> delegate)
+    {
+        if (delegates.get(viewType) != null)
+        {
+            throw new IllegalArgumentException(
+                    "An ItemViewDelegate is already registered for the viewType = "
+                            + viewType
+                            + ". Already registered ItemViewDelegate is "
+                            + delegates.get(viewType));
+        }
+        delegates.put(viewType, delegate);
+        return this;
+    }
+
+    public ItemViewDelegateManager<T> removeDelegate(ItemViewDelegate<T> delegate)
+    {
+        if (delegate == null)
+        {
+            throw new NullPointerException("ItemViewDelegate is null");
+        }
+        int indexToRemove = delegates.indexOfValue(delegate);
+
+        if (indexToRemove >= 0)
+        {
+            delegates.removeAt(indexToRemove);
+        }
+        return this;
+    }
+
+    public ItemViewDelegateManager<T> removeDelegate(int itemType)
+    {
+        int indexToRemove = delegates.indexOfKey(itemType);
+
+        if (indexToRemove >= 0)
+        {
+            delegates.removeAt(indexToRemove);
+        }
+        return this;
+    }
+
+    public int getItemViewType(T item, int position)
+    {
+        int delegatesCount = delegates.size();
+        for (int i = delegatesCount - 1; i >= 0; i--)
+        {
+            ItemViewDelegate<T> delegate = delegates.valueAt(i);
+            if (delegate.isForViewType( item, position))
+            {
+                return delegates.keyAt(i);
+            }
+        }
+        throw new IllegalArgumentException(
+                "No ItemViewDelegate added that matches position=" + position + " in data source");
+    }
+
+    public void convert(ViewHolder holder, T item, int position)
+    {
+        int delegatesCount = delegates.size();
+        for (int i = 0; i < delegatesCount; i++)
+        {
+            ItemViewDelegate<T> delegate = delegates.valueAt(i);
+
+            if (delegate.isForViewType( item, position))
+            {
+                delegate.convert(holder, item, position);
+                return;
+            }
+        }
+        throw new IllegalArgumentException(
+                "No ItemViewDelegateManager added that matches position=" + position + " in data source");
+    }
+
+
+    public ItemViewDelegate getItemViewDelegate(int viewType)
+    {
+        return delegates.get(viewType);
+    }
+
+    public int getItemViewLayoutId(int viewType)
+    {
+        return getItemViewDelegate(viewType).getItemViewLayoutId();
+    }
+
+    public int getItemViewType(ItemViewDelegate itemViewDelegate)
+    {
+        return delegates.indexOfValue(itemViewDelegate);
+    }
+}

+ 126 - 0
Android/app/src/main/java/com/easygbs/easygbd/viewadapter/MultiItemTypeAdapter.java

@@ -0,0 +1,126 @@
+package com.easygbs.easygbd.viewadapter;
+
+import android.content.Context;
+import android.view.View;
+import android.view.ViewGroup;
+import androidx.recyclerview.widget.RecyclerView;
+
+import java.util.List;
+
+public class MultiItemTypeAdapter<T> extends RecyclerView.Adapter<ViewHolder>  {
+    protected Context mContext;
+    protected List<T> mDatas;
+
+    protected ItemViewDelegateManager mItemViewDelegateManager;
+    protected OnItemClickListener mOnItemClickListener;
+
+
+    public MultiItemTypeAdapter(Context context, List<T> datas) {
+        mContext = context;
+        mDatas = datas;
+        mItemViewDelegateManager = new ItemViewDelegateManager();
+    }
+
+    @Override
+    public int getItemViewType(int position) {
+        if (!useItemViewDelegateManager()) return super.getItemViewType(position);
+        return mItemViewDelegateManager.getItemViewType(mDatas.get(position), position);
+    }
+
+
+    @Override
+    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+        ItemViewDelegate itemViewDelegate = mItemViewDelegateManager.getItemViewDelegate(viewType);
+        int layoutId = itemViewDelegate.getItemViewLayoutId();
+        ViewHolder holder = com.easygbs.easygbd.viewadapter.ViewHolder.createViewHolder(mContext, parent, layoutId);
+        onViewHolderCreated(holder, holder.getConvertView());
+        setListener(parent, holder, viewType);
+        return holder;
+    }
+
+    public void onViewHolderCreated(ViewHolder holder, View itemView) {
+
+    }
+
+    public void convert(ViewHolder holder, T t) {
+        mItemViewDelegateManager.convert(holder, t, holder.getAdapterPosition());
+    }
+
+    protected boolean isEnabled(int viewType) {
+        return true;
+    }
+
+
+    protected void setListener(final ViewGroup parent, final ViewHolder viewHolder, int viewType) {
+        if (!isEnabled(viewType)) return;
+        viewHolder.getConvertView().setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                if (mOnItemClickListener != null) {
+                    int position = viewHolder.getAdapterPosition();
+                    mOnItemClickListener.onItemClick(v, viewHolder, position);
+                }
+            }
+        });
+
+        viewHolder.getConvertView().setOnLongClickListener(new View.OnLongClickListener() {
+            @Override
+            public boolean onLongClick(View v) {
+                if (mOnItemClickListener != null) {
+                    int position = viewHolder.getAdapterPosition();
+                    return mOnItemClickListener.onItemLongClick(v, viewHolder, position);
+                }
+                return false;
+            }
+        });
+    }
+
+    @Override
+    public void onBindViewHolder(ViewHolder holder, int position) {
+        convert(holder, mDatas.get(position));
+    }
+
+    @Override
+    public int getItemCount() {
+        int itemCount = mDatas.size();
+        return itemCount;
+    }
+
+
+    public List<T> getDatas() {
+        return mDatas;
+    }
+
+    public MultiItemTypeAdapter addItemViewDelegate(ItemViewDelegate<T> itemViewDelegate) {
+        mItemViewDelegateManager.addDelegate(itemViewDelegate);
+        return this;
+    }
+
+    public MultiItemTypeAdapter addItemViewDelegate(int viewType, ItemViewDelegate<T> itemViewDelegate) {
+        mItemViewDelegateManager.addDelegate(viewType, itemViewDelegate);
+        return this;
+    }
+
+    protected boolean useItemViewDelegateManager() {
+        return mItemViewDelegateManager.getItemViewDelegateCount() > 0;
+    }
+
+    public interface OnItemClickListener {
+        void onItemClick(View view, RecyclerView.ViewHolder holder, int position);
+
+        boolean onItemLongClick(View view, RecyclerView.ViewHolder holder, int position);
+    }
+
+    public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
+        this.mOnItemClickListener = onItemClickListener;
+    }
+
+    public void clearAll() {
+        mDatas.clear();
+    }
+
+    public void add(List<T> beans) {
+        mDatas.addAll(beans);
+    }
+
+}

+ 276 - 0
Android/app/src/main/java/com/easygbs/easygbd/viewadapter/ViewHolder.java

@@ -0,0 +1,276 @@
+package com.easygbs.easygbd.viewadapter;
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Paint;
+import android.graphics.Typeface;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.text.util.Linkify;
+import android.util.SparseArray;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.AlphaAnimation;
+import android.widget.Checkable;
+import android.widget.ImageView;
+import android.widget.ProgressBar;
+import android.widget.RatingBar;
+import android.widget.TextView;
+import androidx.recyclerview.widget.RecyclerView;
+
+public class ViewHolder extends RecyclerView.ViewHolder {
+    private SparseArray<View> mViews;
+    private View mConvertView;
+    private Context mContext;
+
+    public ViewHolder(Context context, View itemView) {
+        super(itemView);
+        mContext = context;
+        mConvertView = itemView;
+        mViews = new SparseArray<View>();
+    }
+
+
+    public static ViewHolder createViewHolder(Context context, View itemView) {
+        ViewHolder holder = new ViewHolder(context, itemView);
+        return holder;
+    }
+
+    public static ViewHolder createViewHolder(Context context,
+                                              ViewGroup parent, int layoutId) {
+        View itemView = LayoutInflater.from(context).inflate(layoutId, parent,
+                false);
+        ViewHolder holder = new ViewHolder(context, itemView);
+        return holder;
+    }
+
+    /**
+     * 通过viewId获取控件
+     *
+     * @param viewId
+     * @return
+     */
+    public <T extends View> T getView(int viewId) {
+        View view = mViews.get(viewId);
+        if (view == null) {
+            view = mConvertView.findViewById(viewId);
+            mViews.put(viewId, view);
+        }
+        return (T) view;
+    }
+
+    public View getConvertView() {
+        return mConvertView;
+    }
+
+
+    /****以下为辅助方法*****/
+
+    /**
+     * 设置TextView的值
+     *
+     * @param viewId
+     * @param text
+     * @return
+     */
+    public ViewHolder setText(int viewId, String text) {
+        TextView tv = getView(viewId);
+        tv.setText(text);
+        return this;
+    }
+
+    public ViewHolder setCompoundDrawables(int viewId, Drawable left, Drawable top, Drawable right, Drawable bottom) {
+        TextView tv = getView(viewId);
+        tv.setCompoundDrawables(left, top, right, bottom);
+        return this;
+    }
+
+    public ViewHolder setImageResource(int viewId, int resId) {
+        ImageView view = getView(viewId);
+        view.setImageResource(resId);
+        return this;
+    }
+
+    public ViewHolder setImageBitmap(int viewId, Bitmap bitmap) {
+        ImageView view = getView(viewId);
+        view.setImageBitmap(bitmap);
+        return this;
+    }
+
+    public ViewHolder setImageDrawable(int viewId, Drawable drawable) {
+        ImageView view = getView(viewId);
+        view.setImageDrawable(drawable);
+        return this;
+    }
+
+    public ViewHolder setImageUrl(int viewId, String url, boolean isDefault, int placeHolderRes, int errorRes) {
+        ImageView view = getView(viewId);
+//        GlideUtil.setImageUrl(mContext, view, url, placeHolderRes, errorRes);
+        return this;
+    }
+
+    public ViewHolder setBackgroundColor(int viewId, int color) {
+        View view = getView(viewId);
+        view.setBackgroundColor(color);
+        return this;
+    }
+
+    public ViewHolder setBackgroundRes(int viewId, int backgroundRes) {
+        View view = getView(viewId);
+        view.setBackgroundResource(backgroundRes);
+        return this;
+    }
+
+    public ViewHolder setTextColor(int viewId, int textColor) {
+        TextView view = getView(viewId);
+        view.setTextColor(textColor);
+        return this;
+    }
+
+    public ViewHolder setTextColorRes(int viewId, int textColorRes) {
+        TextView view = getView(viewId);
+        view.setTextColor(mContext.getResources().getColor(textColorRes));
+        return this;
+    }
+
+    public ViewHolder setTextSize(int viewId, float textsize) {
+        TextView view = getView(viewId);
+        view.setTextSize(dip2px(mContext,textsize));
+        return this;
+    }
+    public static int dip2px(Context context, float dipValue) {
+        final float scale = context.getResources().getDisplayMetrics().density;
+        return (int) (dipValue * scale + 0.5f);
+    }
+
+    @SuppressLint("NewApi")
+    public ViewHolder setAlpha(int viewId, float value) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+            getView(viewId).setAlpha(value);
+        } else {
+            // Pre-honeycomb hack to set Alpha value
+            AlphaAnimation alpha = new AlphaAnimation(value, value);
+            alpha.setDuration(0);
+            alpha.setFillAfter(true);
+            getView(viewId).startAnimation(alpha);
+        }
+        return this;
+    }
+
+    public ViewHolder setVisible(int viewId, boolean visible) {
+        View view = getView(viewId);
+        view.setVisibility(visible ? View.VISIBLE : View.GONE);
+        return this;
+    }
+
+    public ViewHolder setHide(int viewId, boolean visible) {
+        View view = getView(viewId);
+        view.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
+        return this;
+    }
+
+    public ViewHolder setInVisible(int viewId, boolean visible) {
+        View view = getView(viewId);
+        view.setVisibility(visible ? View.INVISIBLE : View.GONE);
+        return this;
+    }
+
+    public ViewHolder setClickable(int viewId, boolean isClickable) {
+        View view = getView(viewId);
+        view.setClickable(isClickable);
+        return this;
+    }
+
+    public ViewHolder linkify(int viewId) {
+        TextView view = getView(viewId);
+        Linkify.addLinks(view, Linkify.ALL);
+        return this;
+    }
+
+
+    public ViewHolder setTypeface(Typeface typeface, int... viewIds) {
+        for (int viewId : viewIds) {
+            TextView view = getView(viewId);
+            view.setTypeface(typeface);
+            view.setPaintFlags(view.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG);
+        }
+        return this;
+    }
+
+    public ViewHolder setProgress(int viewId, int progress) {
+        ProgressBar view = getView(viewId);
+        view.setProgress(progress);
+        return this;
+    }
+
+    public ViewHolder setProgress(int viewId, int progress, int max) {
+        ProgressBar view = getView(viewId);
+        view.setMax(max);
+        view.setProgress(progress);
+        return this;
+    }
+
+    public ViewHolder setMax(int viewId, int max) {
+        ProgressBar view = getView(viewId);
+        view.setMax(max);
+        return this;
+    }
+
+    public ViewHolder setRating(int viewId, float rating) {
+        RatingBar view = getView(viewId);
+        view.setRating(rating);
+        return this;
+    }
+
+    public ViewHolder setRating(int viewId, float rating, int max) {
+        RatingBar view = getView(viewId);
+        view.setMax(max);
+        view.setRating(rating);
+        return this;
+    }
+
+    public ViewHolder setTag(int viewId, Object tag) {
+        View view = getView(viewId);
+        view.setTag(tag);
+        return this;
+    }
+
+    public ViewHolder setTag(int viewId, int key, Object tag) {
+        View view = getView(viewId);
+        view.setTag(key, tag);
+        return this;
+    }
+
+    public ViewHolder setChecked(int viewId, boolean checked) {
+        Checkable view = (Checkable) getView(viewId);
+        view.setChecked(checked);
+        return this;
+    }
+
+    /**
+     * 关于事件的
+     */
+    public ViewHolder setOnClickListener(int viewId,
+                                         View.OnClickListener listener) {
+        View view = getView(viewId);
+        view.setOnClickListener(listener);
+        return this;
+    }
+
+    public ViewHolder setOnTouchListener(int viewId,
+                                         View.OnTouchListener listener) {
+        View view = getView(viewId);
+        view.setOnTouchListener(listener);
+        return this;
+    }
+
+    public ViewHolder setOnLongClickListener(int viewId,
+                                             View.OnLongClickListener listener) {
+        View view = getView(viewId);
+        view.setOnLongClickListener(listener);
+        return this;
+    }
+
+
+}

+ 10 - 0
Android/app/src/main/java/com/easygbs/easygbd/viewmodel/BaseViewModel.kt

@@ -0,0 +1,10 @@
+package com.easygbs.easygbd.viewmodel;
+import android.app.Application
+import android.util.Log
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.MutableLiveData
+
+open class BaseViewModel(application:Application): AndroidViewModel( application) {
+    var BaseViewModelTAG = BaseViewModel::class.java.getSimpleName()
+
+}

+ 88 - 0
Android/app/src/main/java/com/easygbs/easygbd/viewmodel/activity/MainViewModel.kt

@@ -0,0 +1,88 @@
+package com.easygbs.easygbd.viewmodel.activity
+
+import android.app.Application
+import android.util.Log
+import android.view.View
+import android.widget.ImageView
+import androidx.databinding.ObservableField
+import androidx.lifecycle.MutableLiveData
+import com.easygbs.easygbd.R
+import com.easygbs.easygbd.activity.MainActivity
+import com.easygbs.easygbd.push.MediaStream
+import com.easygbs.easygbd.viewmodel.BaseViewModel
+
+class MainViewModel(application: Application) : BaseViewModel(application) {
+    var TAG: String = MainViewModel::class.java.getSimpleName()
+    var mMainActivity: MainActivity? = null
+    val selectTopItem: MutableLiveData<Int> = MutableLiveData<Int>(0)
+    public val selectBottomItem: MutableLiveData<Int> = MutableLiveData<Int>(-1)
+    var isshowbottomObservableField = ObservableField<Boolean>(true)
+
+    fun setMainActivity(mMainActivity: MainActivity) {
+        this.mMainActivity = mMainActivity
+    }
+
+    fun topfirst() {
+        selectTopItem.value = 0
+    }
+
+    fun topfirst(v: View) {
+        selectTopItem.value = 0
+    }
+
+    fun topsecond(v: View) {
+        selectTopItem.value = 1
+    }
+
+    fun topthird(v: View) {
+        selectTopItem.value = 2
+    }
+
+    fun bf() {
+        try {
+            selectBottomItem.value = 0
+            mMainActivity!!.mBasicSettingsFragment!!.save()
+            mMainActivity!!.mBasicSettingsFragment!!.startOrStopStream()
+        } catch (e: Exception) {
+            Log.e(TAG, "bf  Exception  " + e.toString());
+        }
+    }
+
+
+    fun bf(v: View) {
+        try {
+            selectBottomItem.value = 0
+            mMainActivity!!.mBasicSettingsFragment!!.save()
+            mMainActivity!!.mBasicSettingsFragment!!.startOrStopStream()
+        } catch (e: Exception) {
+            Log.e(TAG, "bf  Exception  " + e.toString());
+        }
+    }
+
+    private var lastClickTime: Long = 0
+
+    fun bs(v: View) {
+        val currentTime = System.currentTimeMillis()
+        if (currentTime - lastClickTime < 1000) {
+            return
+        }
+        mMainActivity!!.mMediaStream!!.stopStream();
+        mMainActivity!!.mActivityMainlandBinding!!.ivbottomfirst!!.setBackgroundResource(R.mipmap.ic_unreg)
+        mMainActivity!!.mActivityMainlandBinding!!.tvbottomfirst!!.setTextColor(
+            mMainActivity!!.resources.getColor(
+                R.color.color_808080
+            )
+        )
+        lastClickTime = currentTime  // 更新最后一次点击时间
+
+        try {
+            selectBottomItem.value = 1
+        } catch (e: Exception) {
+            Log.e(TAG, "bs Exception " + e.toString())
+        }
+    }
+
+    fun bt(v: View) {
+        selectBottomItem.value = 2
+    }
+}

+ 26 - 0
Android/app/src/main/java/com/easygbs/easygbd/viewmodel/activity/RecordListViewModel.kt

@@ -0,0 +1,26 @@
+package com.easygbs.easygbd.viewmodel.activity
+
+import android.app.Application
+import android.content.Intent
+import android.view.View
+import com.easygbs.easygbd.activity.RecordListActivity
+import com.easygbs.easygbd.common.Constant
+import com.easygbs.easygbd.viewmodel.BaseViewModel
+
+
+class RecordListViewModel(application: Application) : BaseViewModel(application) {
+    var TAG: String = RecordListViewModel::class.java.getSimpleName()
+    var mRecordListActivity: RecordListActivity? = null
+
+    fun setRecordListActivity(mMainActivity: RecordListActivity) {
+        this.mRecordListActivity = mMainActivity
+    }
+
+    fun back(view: View) {
+        val resultIntent = Intent()
+        resultIntent.putExtra("key", "value") // 将数据放入 Intent
+        mRecordListActivity?.setResult(Constant.RECORD_BACK, resultIntent) // 返回数据,并指示操作成功
+        mRecordListActivity?.finish()
+    }
+
+}

+ 47 - 0
Android/app/src/main/java/com/easygbs/easygbd/viewmodel/fragment/AboutViewModel.java

@@ -0,0 +1,47 @@
+package com.easygbs.easygbd.viewmodel.fragment;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.util.Log;
+import android.view.View;
+import android.widget.Toast;
+
+import androidx.databinding.BaseObservable;
+
+import com.easygbs.easygbd.R;
+import com.easygbs.easygbd.activity.MainActivity;
+import com.easygbs.easygbd.dao.DCon;
+import com.easygbs.easygbd.dao.bean.Chan;
+import com.easygbs.easygbd.fragment.AboutFragment;
+import com.easygbs.easygbd.fragment.ChannelSettingsFragment;
+
+public class AboutViewModel extends BaseObservable {
+    private static final String TAG = AboutViewModel.class.getSimpleName();
+    public MainActivity mMainActivity = null;
+    public AboutFragment mAboutFragment;
+
+    public AboutViewModel(MainActivity mMainActivity, AboutFragment mAboutFragment) {
+        this.mMainActivity = mMainActivity;
+        this.mAboutFragment=mAboutFragment;
+
+        mAboutFragment.mFragmentAboutBinding.tvver.setText("Version  "+getversionName());
+    }
+
+    public void back(View view) {
+        mMainActivity.about();
+    }
+
+    public String getversionName(){
+        String ve="";
+        try{
+            PackageManager mPackageManager = null;
+            PackageInfo mPackageInfo = null;
+            mPackageManager=mMainActivity.getPackageManager();
+            mPackageInfo = mPackageManager.getPackageInfo(mMainActivity.getPackageName(), PackageManager.GET_CONFIGURATIONS);
+            ve = mPackageInfo.versionName;
+        }catch(Exception e){
+            Log.e(TAG,"getversionName  Exception  "+ e);
+        }
+        return ve;
+    }
+}
+

+ 552 - 0
Android/app/src/main/java/com/easygbs/easygbd/viewmodel/fragment/BasicSettingsViewModel.java

@@ -0,0 +1,552 @@
+package com.easygbs.easygbd.viewmodel.fragment;
+
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.PopupWindow;
+import android.widget.Toast;
+
+import androidx.databinding.BaseObservable;
+import androidx.databinding.ObservableField;
+import androidx.lifecycle.ViewModel;
+import androidx.recyclerview.widget.GridLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.easygbs.easygbd.R;
+import com.easygbs.easygbd.activity.MainActivity;
+import com.easygbs.easygbd.adapter.LoglevelAdapter;
+import com.easygbs.easygbd.adapter.TranproAdapter;
+import com.easygbs.easygbd.adapter.VerAdapter;
+import com.easygbs.easygbd.bean.LoglevelBean;
+import com.easygbs.easygbd.bean.TranproBean;
+import com.easygbs.easygbd.bean.VerBean;
+import com.easygbs.easygbd.common.Constant;
+import com.easygbs.easygbd.fragment.BasicSettingsFragment;
+import com.easygbs.easygbd.logger.LogLevel;
+import com.easygbs.easygbd.logger.Logger;
+import com.easygbs.easygbd.push.MediaStream;
+import com.easygbs.easygbd.util.SPHelper;
+import com.easygbs.easygbd.viewadapter.MultiItemTypeAdapter;
+
+import org.easydarwin.util.SPUtil;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class BasicSettingsViewModel extends ViewModel {
+    private static final String TAG = BasicSettingsViewModel.class.getSimpleName();
+    public MainActivity mMainActivity = null;
+    public BasicSettingsFragment mBasicSettingsFragment = null;
+
+    public LayoutInflater mLayoutInflater = null;
+
+    public View mViewVer = null;
+
+    public RecyclerView rvver;
+
+    public List<VerBean> VerBeanList = null;
+
+    public VerAdapter mVerAdapter = null;
+
+    public PopupWindow mPopupVer = null;
+
+    public View mViewTranpro = null;
+
+    public RecyclerView rvtranpro;
+
+    public List<TranproBean> TranproBeanList = null;
+
+    public TranproAdapter mTranproAdapter = null;
+
+    public PopupWindow mPopupTranpro = null;
+
+    public View mViewLoglevel = null;
+
+    public RecyclerView rvloglevel;
+
+    public List<LoglevelBean> LoglevelBeanList = null;
+
+    public LoglevelAdapter mLoglevelAdapter = null;
+
+    public PopupWindow mPopupLoglevel = null;
+
+    public ObservableField<String> sipserveraddrObservableField = new ObservableField<>();
+
+    public SPHelper mSPHelper;
+
+    public BasicSettingsViewModel(MainActivity mMainActivity, BasicSettingsFragment mBasicSettingsFragment) {
+        this.mMainActivity = mMainActivity;
+        this.mBasicSettingsFragment = mBasicSettingsFragment;
+        mLayoutInflater = LayoutInflater.from(mMainActivity);
+
+        int getver = SPUtil.getVer(mMainActivity);
+        mBasicSettingsFragment.mFragmentBasicsettingsBinding.tvver.setText("" + getver);
+
+        mBasicSettingsFragment.mFragmentBasicsettingsBinding.etport.setText("" + SPUtil.getServerport(mMainActivity));
+
+        mBasicSettingsFragment.mFragmentBasicsettingsBinding.etsipserveraddr.setText(SPUtil.getServerip(mMainActivity));
+        mBasicSettingsFragment.mFragmentBasicsettingsBinding.etsipserverid.setText(SPUtil.getServerid(mMainActivity));
+
+        mBasicSettingsFragment.mFragmentBasicsettingsBinding.etsipserverdomain.setText(SPUtil.getServerdomain(mMainActivity));
+
+        String sipUserName = SPUtil.getDeviceid(mMainActivity);
+
+        if (sipUserName.isEmpty()) {
+            String pDeviceId = "34020000001320";
+            int n = (int) (Math.random() * 999999) + 1;
+            sipUserName = pDeviceId + String.format("%06d", n);
+            SPUtil.setDeviceid(mMainActivity, sipUserName);
+        }
+
+        mBasicSettingsFragment.mFragmentBasicsettingsBinding.tvsipname.setText(sipUserName);
+
+        String deviceName = SPUtil.getDevicename(mMainActivity);
+        if (deviceName.isEmpty()) {
+            deviceName = "EasyGBD-" + sipUserName.substring(sipUserName.length() - 6);
+            SPUtil.setDevicename(mMainActivity, deviceName);
+        }
+
+        mBasicSettingsFragment.mFragmentBasicsettingsBinding.etdevicename.setText(deviceName);
+
+        mBasicSettingsFragment.mFragmentBasicsettingsBinding.etsippassword.setText(SPUtil.getPassword(mMainActivity));
+        mBasicSettingsFragment.mFragmentBasicsettingsBinding.etregistervalidtime.setText("" + SPUtil.getRegexpires(mMainActivity));
+        mBasicSettingsFragment.mFragmentBasicsettingsBinding.tvheartbeatcycle.setText("" + SPUtil.getHeartbeatinterval(mMainActivity));
+        mBasicSettingsFragment.mFragmentBasicsettingsBinding.etheartbeatcount.setText("" + SPUtil.getHeartbeatcount(mMainActivity));
+
+        mSPHelper = mMainActivity.getSPHelper();
+        int logstatus = (int) mSPHelper.get(Constant.LOGSTATUS, 1);
+        if (logstatus == 0) {
+            mBasicSettingsFragment.mFragmentBasicsettingsBinding.lllog.setBackgroundResource(R.mipmap.ic_notselected);
+
+            mBasicSettingsFragment.mFragmentBasicsettingsBinding.ivlog.setVisibility(View.INVISIBLE);
+        } else {
+            mBasicSettingsFragment.mFragmentBasicsettingsBinding.lllog.setBackgroundResource(R.mipmap.ic_selected);
+
+            mBasicSettingsFragment.mFragmentBasicsettingsBinding.ivlog.setVisibility(View.VISIBLE);
+        }
+
+        int pro = SPUtil.getProtocol(mMainActivity);
+        if (pro == 0) {
+            mBasicSettingsFragment.mFragmentBasicsettingsBinding.tvtranpro.setText("UDP");
+        } else {
+            mBasicSettingsFragment.mFragmentBasicsettingsBinding.tvtranpro.setText("TCP");
+        }
+
+        mSPHelper = mMainActivity.getSPHelper();
+        String loglev = (String) mSPHelper.get(Constant.LOGLEV, "DEBUG");
+        mBasicSettingsFragment.mFragmentBasicsettingsBinding.tvloglevel.setText(loglev);
+        if (loglev.equals("DEBUG")) {
+            Logger.getInstance().setLogLevel(LogLevel.DEBUG);
+        } else if (loglev.equals("INFO")) {
+            Logger.getInstance().setLogLevel(LogLevel.INFO);
+        } else if (loglev.equals("WARNING")) {
+            Logger.getInstance().setLogLevel(LogLevel.WARN);
+        } else if (loglev.equals("ERROR")) {
+            Logger.getInstance().setLogLevel(LogLevel.ERROR);
+        }
+        Logger.getInstance().clearLogText();
+    }
+
+    public void openQR(View view) {
+        MediaStream mMediaStream = mMainActivity.getMMediaStream();
+        if (mMediaStream != null) {
+            if (mMediaStream.isStreaming()) {
+                Toast.makeText(mMainActivity, "正在推流", Toast.LENGTH_SHORT).show();
+                return;
+            } else {
+                mMediaStream.stopPreview();
+                mMediaStream.destroyCamera();
+                mBasicSettingsFragment.destroyService();
+            }
+        }
+
+        Toast.makeText(mMainActivity, "扫一扫", Toast.LENGTH_SHORT).show();
+        mMainActivity.toSc();
+    }
+
+    public void ver(View view) {
+        view.post(new Runnable() {
+            @Override
+            public void run() {
+
+                // 获取视图的宽度和高度
+                int viewWidth = view.getWidth();
+                if (mViewVer == null) {
+                    mViewVer = mLayoutInflater.inflate(R.layout.po_ver, null);
+
+                    rvver = mViewVer.findViewById(R.id.rvver);
+
+                    rvver.setLayoutManager(new GridLayoutManager(mMainActivity, 1, RecyclerView.VERTICAL, false));
+
+                    VerBeanList = new ArrayList<VerBean>();
+                    mVerAdapter = new VerAdapter(mMainActivity, mMainActivity, R.layout.adapter_ver, VerBeanList);
+                    rvver.setAdapter(mVerAdapter);
+
+                    mVerAdapter.setOnItemClickListener(new MultiItemTypeAdapter.OnItemClickListener() {
+                        @Override
+                        public void onItemClick(View view, RecyclerView.ViewHolder holder, int position) {
+                            String name = VerBeanList.get(position).getName();
+
+                            SPUtil.setVer(mMainActivity, Integer.parseInt(name));
+
+                            mBasicSettingsFragment.mFragmentBasicsettingsBinding.tvver.setText(name);
+
+                            for (int i = 0; i < VerBeanList.size(); i++) {
+                                VerBean mVerBean = VerBeanList.get(i);
+                                mVerBean.setIsst(0);
+                            }
+
+                            for (int i = 0; i < VerBeanList.size(); i++) {
+                                VerBean mVerBean = VerBeanList.get(i);
+                                if (mVerBean.getName().equals(name)) {
+                                    mVerBean.setIsst(1);
+                                    break;
+                                }
+                            }
+
+                            mVerAdapter.notifyDataSetChanged();
+
+
+                            hidePopVer();
+                        }
+
+                        @Override
+                        public boolean onItemLongClick(View view, RecyclerView.ViewHolder holder, int position) {
+                            return false;
+                        }
+                    });
+
+
+                    int getver = SPUtil.getVer(mMainActivity);
+
+                    String[] vers = mMainActivity.getResources().getStringArray(R.array.verarr);
+                    for (int i = 0; i < vers.length; i++) {
+                        VerBean mVerBean = new VerBean();
+                        mVerBean.setName(vers[i]);
+                        if (getver == Integer.parseInt(vers[i])) {
+                            mVerBean.setIsst(1);
+                        } else {
+                            mVerBean.setIsst(0);
+                        }
+
+                        VerBeanList.add(mVerBean);
+                    }
+
+                    mBasicSettingsFragment.mFragmentBasicsettingsBinding.tvver.setText("" + getver);
+
+                    mVerAdapter.notifyDataSetChanged();
+                }
+
+                // 动态计算 PopupWindow 高度
+                mViewVer.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
+                int height = mViewVer.getMeasuredHeight(); // 获取测量后的高度
+
+                mPopupVer = new PopupWindow(mViewVer, viewWidth, height);
+
+                mPopupVer.setOutsideTouchable(true);
+
+                mPopupVer.setOnDismissListener(new PopupWindow.OnDismissListener() {
+                    @Override
+                    public void onDismiss() {
+
+                    }
+                });
+
+                int yoff = (int) mMainActivity.getResources().getDimension(R.dimen.dp_4);
+
+                mPopupVer.showAsDropDown(mBasicSettingsFragment.mFragmentBasicsettingsBinding.tvver, 0, yoff);
+            }
+        });
+    }
+
+    public void hidePopVer() {
+        if (mPopupVer != null) {
+            boolean isShowing = mPopupVer.isShowing();
+            if (isShowing) {
+                mPopupVer.dismiss();
+            }
+        }
+    }
+
+    public void tranpro(View view) {
+        view.post(new Runnable() {
+            @Override
+            public void run() {
+                // 获取视图的宽度和高度
+                int viewWidth = view.getWidth();
+                if (mViewTranpro == null) {
+                    mViewTranpro = mLayoutInflater.inflate(R.layout.po_ver, null);
+
+                    rvtranpro = mViewTranpro.findViewById(R.id.rvver);
+
+                    rvtranpro.setLayoutManager(new GridLayoutManager(mMainActivity, 1, RecyclerView.VERTICAL, false));
+
+                    TranproBeanList = new ArrayList<TranproBean>();
+                    mTranproAdapter = new TranproAdapter(mMainActivity, mMainActivity, R.layout.adapter_ver, TranproBeanList);
+                    rvtranpro.setAdapter(mTranproAdapter);
+
+                    mTranproAdapter.setOnItemClickListener(new MultiItemTypeAdapter.OnItemClickListener() {
+                        @Override
+                        public void onItemClick(View view, RecyclerView.ViewHolder holder, int position) {
+                            String name = TranproBeanList.get(position).getName();
+
+                            int pro;
+                            if (name.equals("UDP")) {
+                                pro = 0;
+                            } else {
+                                pro = 1;
+                            }
+                            SPUtil.setProtocol(mMainActivity, pro);
+
+                            mBasicSettingsFragment.mFragmentBasicsettingsBinding.tvtranpro.setText(name);
+
+                            for (int i = 0; i < TranproBeanList.size(); i++) {
+                                TranproBean mTranproBean = TranproBeanList.get(i);
+                                mTranproBean.setIsst(0);
+                            }
+
+                            for (int i = 0; i < TranproBeanList.size(); i++) {
+                                TranproBean mTranproBean = TranproBeanList.get(i);
+                                if (mTranproBean.getName().equals(name)) {
+                                    mTranproBean.setIsst(1);
+                                    break;
+                                }
+                            }
+
+                            mTranproAdapter.notifyDataSetChanged();
+
+                            hidePopTranpro();
+                        }
+
+                        @Override
+                        public boolean onItemLongClick(View view, RecyclerView.ViewHolder holder, int position) {
+                            return false;
+                        }
+                    });
+
+                    int pro = SPUtil.getProtocol(mMainActivity);
+                    String str = "";
+                    if (pro == 0) {
+                        str = "UDP";
+                    } else {
+                        str = "TCP";
+                    }
+
+                    mBasicSettingsFragment.mFragmentBasicsettingsBinding.tvtranpro.setText(str);
+
+                    String[] tranpros = mMainActivity.getResources().getStringArray(R.array.tranproarr);
+                    for (int i = 0; i < tranpros.length; i++) {
+                        TranproBean mTranproBean = new TranproBean();
+                        mTranproBean.setName(tranpros[i]);
+                        if (str.equals(tranpros[i])) {
+                            mTranproBean.setIsst(1);
+                        } else {
+                            mTranproBean.setIsst(0);
+                        }
+
+                        TranproBeanList.add(mTranproBean);
+                    }
+
+                    mTranproAdapter.notifyDataSetChanged();
+                }
+
+                // 动态计算 PopupWindow 高度
+                mViewTranpro.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
+                int height = mViewTranpro.getMeasuredHeight(); // 获取测量后的高度
+
+                mPopupTranpro = new PopupWindow(mViewTranpro, viewWidth, height);
+
+                mPopupTranpro.setOutsideTouchable(true);
+
+                mPopupTranpro.setOnDismissListener(new PopupWindow.OnDismissListener() {
+                    @Override
+                    public void onDismiss() {
+
+                    }
+                });
+
+                int y0ff = (int) mMainActivity.getResources().getDimension(R.dimen.dp_4);
+
+                mPopupTranpro.showAsDropDown(mBasicSettingsFragment.mFragmentBasicsettingsBinding.tvtranpro, 0, y0ff);
+            }
+        });
+    }
+
+    public void hidePopTranpro() {
+        if (mPopupTranpro != null) {
+            boolean isShowing = mPopupTranpro.isShowing();
+            if (isShowing) {
+                mPopupTranpro.dismiss();
+            }
+        }
+    }
+
+    public void loglevel(View view) {
+        view.post(new Runnable() {
+            @Override
+            public void run() {
+                // 获取视图的宽度和高度
+                int viewWidth = view.getWidth();
+
+                if (mViewLoglevel == null) {
+
+                    mViewLoglevel = mLayoutInflater.inflate(R.layout.po_ver, null);
+
+                    rvloglevel = mViewLoglevel.findViewById(R.id.rvver);
+
+                    rvloglevel.setLayoutManager(new GridLayoutManager(mMainActivity, 1, RecyclerView.VERTICAL, false));
+
+                    LoglevelBeanList = new ArrayList<LoglevelBean>();
+                    mLoglevelAdapter = new LoglevelAdapter(mMainActivity, mMainActivity, R.layout.adapter_ver, LoglevelBeanList);
+                    rvloglevel.setAdapter(mLoglevelAdapter);
+
+                    mLoglevelAdapter.setOnItemClickListener(new MultiItemTypeAdapter.OnItemClickListener() {
+                        @Override
+                        public void onItemClick(View view, RecyclerView.ViewHolder holder, int position) {
+                            String name = LoglevelBeanList.get(position).getName();
+
+                            String loglev = (String) mSPHelper.get(Constant.LOGLEV, "DEBUG");
+
+
+                            if (loglev.equals(name)) {
+                                Log.i(TAG, "一样  log");
+                                hidePopLoglevel();
+                                return;
+                            }
+
+
+                            mSPHelper.put(Constant.LOGLEV, name);
+
+                            mBasicSettingsFragment.mFragmentBasicsettingsBinding.tvloglevel.setText(name);
+
+                            for (int i = 0; i < LoglevelBeanList.size(); i++) {
+                                LoglevelBean mLoglevelBean = LoglevelBeanList.get(i);
+                                mLoglevelBean.setIsst(0);
+                            }
+
+                            for (int i = 0; i < LoglevelBeanList.size(); i++) {
+                                LoglevelBean mLoglevelBean = LoglevelBeanList.get(i);
+                                if (mLoglevelBean.getName().equals(name)) {
+                                    mLoglevelBean.setIsst(1);
+                                    break;
+                                }
+                            }
+                            mLoglevelAdapter.notifyDataSetChanged();
+
+                            Log.i(TAG, "一样  log" + name + "," + (name.equals("DEBUG")));
+                            if (name.equals("DEBUG")) {
+                                Logger.getInstance().setLogLevel(LogLevel.DEBUG);
+                            } else if (name.equals("INFO")) {
+                                Logger.getInstance().setLogLevel(LogLevel.INFO);
+                            } else if (name.equals("WARNING")) {
+                                Logger.getInstance().setLogLevel(LogLevel.WARN);
+                            } else if (name.equals("ERROR")) {
+                                Logger.getInstance().setLogLevel(LogLevel.ERROR);
+                            }
+                            Logger.getInstance().clearLogText();
+                            hidePopLoglevel();
+                        }
+
+                        @Override
+                        public boolean onItemLongClick(View view, RecyclerView.ViewHolder holder, int position) {
+                            return false;
+                        }
+                    });
+
+                    SPHelper mSPHelper = mMainActivity.getSPHelper();
+                    String loglev = (String) mSPHelper.get(Constant.LOGLEV, "DEBUG");
+                    mBasicSettingsFragment.mFragmentBasicsettingsBinding.tvloglevel.setText(loglev);
+
+                    String[] loglevels = mMainActivity.getResources().getStringArray(R.array.loglevelarr);
+                    for (int i = 0; i < loglevels.length; i++) {
+                        LoglevelBean mLoglevelBean = new LoglevelBean();
+                        mLoglevelBean.setName(loglevels[i]);
+                        if (loglev.equals(loglevels[i])) {
+                            mLoglevelBean.setIsst(1);
+                        } else {
+                            mLoglevelBean.setIsst(0);
+                        }
+
+                        LoglevelBeanList.add(mLoglevelBean);
+                    }
+
+                    mLoglevelAdapter.notifyDataSetChanged();
+                }
+
+                // 动态计算 PopupWindow 高度
+                mViewLoglevel.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
+                int height = mViewLoglevel.getMeasuredHeight(); // 获取测量后的高度
+
+                mPopupLoglevel = new PopupWindow(mViewLoglevel, viewWidth, height);
+
+                mPopupLoglevel.setOutsideTouchable(true);
+
+                mPopupLoglevel.setOnDismissListener(new PopupWindow.OnDismissListener() {
+                    @Override
+                    public void onDismiss() {
+
+                    }
+                });
+
+                int yoff = (int) mMainActivity.getResources().getDimension(R.dimen.dp_4);
+
+                mPopupLoglevel.showAsDropDown(mBasicSettingsFragment.mFragmentBasicsettingsBinding.tvloglevel, 0, yoff);
+
+            }
+        });
+
+
+    }
+
+    public void hidePopLoglevel() {
+        if (mPopupLoglevel != null) {
+            boolean isShowing = mPopupLoglevel.isShowing();
+            if (isShowing) {
+                mPopupLoglevel.dismiss();
+            }
+        }
+    }
+
+    public void save(String text) {
+        mBasicSettingsFragment.save();
+    }
+
+    // 创建一个 TextWatcher,用于监听 EditText 的文本变化
+    public TextWatcher textWatcher = new TextWatcher() {
+        @Override
+        public void beforeTextChanged(CharSequence charSequence, int start, int count, int after) {
+            // 文本变化前的逻辑
+        }
+
+        @Override
+        public void onTextChanged(CharSequence charSequence, int start, int before, int count) {
+            // 文本变化中的逻辑
+
+        }
+
+        @Override
+        public void afterTextChanged(Editable editable) {
+            save(editable.toString());  // 调用 save 方法
+        }
+    };
+
+    public void startOrStopRecording(View view) {
+        MediaStream mMediaStream = mMainActivity.getMMediaStream();
+        boolean isRecording = mMediaStream.isRecording();
+        Toast.makeText(mMainActivity, String.format("%s录像", !isRecording ? "开始" : "结束"), Toast.LENGTH_SHORT).show();
+        ImageView imageView = (ImageView) view;
+        imageView.setImageResource(!isRecording ? R.mipmap.recording : R.mipmap.record_default);  // 替换为新的图片资源
+
+        if (mMediaStream != null) {
+            if (isRecording) {
+                mMediaStream.stopRecord();
+            } else {
+                mMediaStream.startRecord();
+            }
+        }
+    }
+}
+

+ 76 - 0
Android/app/src/main/java/com/easygbs/easygbd/viewmodel/fragment/ChannelSettingsViewModel.java

@@ -0,0 +1,76 @@
+package com.easygbs.easygbd.viewmodel.fragment;
+
+import android.util.Log;
+import android.view.View;
+import android.widget.Toast;
+
+import androidx.databinding.BaseObservable;
+import androidx.databinding.ObservableField;
+
+import com.easygbs.easygbd.activity.MainActivity;
+import com.easygbs.easygbd.dao.DCon;
+import com.easygbs.easygbd.dao.bean.Chan;
+import com.easygbs.easygbd.fragment.ChannelSettingsFragment;
+import com.easygbs.easygbd.push.MediaStream;
+import org.easydarwin.util.SPUtil;
+
+import java.util.Random;
+
+public class ChannelSettingsViewModel extends BaseObservable {
+    private static final String TAG = ChannelSettingsViewModel.class.getSimpleName();
+    public MainActivity mMainActivity = null;
+    public ChannelSettingsFragment mChannelSettingsFragment = null;
+    private static final String pCid = "34020000001320";
+
+    public ChannelSettingsViewModel(MainActivity mMainActivity, ChannelSettingsFragment mChannelSettingsFragment) {
+        this.mMainActivity = mMainActivity;
+        this.mChannelSettingsFragment = mChannelSettingsFragment;
+    }
+
+    public void add(View view) {
+        try {
+            Chan mChan = (Chan) mMainActivity.getMDOpe().OperaDatabase(DCon.Chanquerymaxuid);
+
+            if (mChan == null) {
+                Chan mmChan = new Chan();
+                mmChan.setUid(1);
+                mmChan.setCid("34020000001320000001");
+                mmChan.setNa("channel1");
+                mmChan.setSta(1);
+                mMainActivity.ChanIn(mmChan);
+            } else {
+                long size = (long) mMainActivity.getMDOpe().OperaDatabase(DCon.ChanCount);
+                if (size >= 8) {
+                    Toast.makeText(mMainActivity, "最大通道数仅支持 8 通道", Toast.LENGTH_SHORT).show();
+                    return;
+                }
+                Chan mmChan = new Chan();
+                int uid = mChan.getEc() + 1;
+                mmChan.setUid(uid);
+                mmChan.setCid(pCid + String.format("%06d", uid));
+//                int n = (int) (Math.random() * 999999) + 1;
+//                mmChan.setCid(pCid + String.format("%06d", n));
+                mmChan.setNa("channel" + uid);
+                mmChan.setSta(1);
+                mMainActivity.ChanIn(mmChan);
+            }
+
+            mChannelSettingsFragment.showChannels();
+        } catch (Exception e) {
+            Log.e(TAG, "add  Exception  " + e.toString());
+        }
+    }
+
+    public void save(View view) {
+        mMainActivity.goTopBaseSetting();
+    }
+
+    public void deleteChan(View view) {
+        mChannelSettingsFragment.delete();
+    }
+}
+
+
+
+
+

+ 83 - 0
Android/app/src/main/java/com/easygbs/easygbd/viewmodel/fragment/RecordViewModel.java

@@ -0,0 +1,83 @@
+package com.easygbs.easygbd.viewmodel.fragment;
+
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.util.Log;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.Toast;
+
+import androidx.databinding.BaseObservable;
+
+import com.easygbs.easygbd.R;
+import com.easygbs.easygbd.activity.MainActivity;
+import com.easygbs.easygbd.activity.RecordListActivity;
+import com.easygbs.easygbd.fragment.RecordFragment;
+import com.easygbs.easygbd.push.MediaStream;
+
+public class RecordViewModel extends BaseObservable {
+    private static final String TAG = RecordViewModel.class.getSimpleName();
+    public MainActivity mMainActivity = null;
+    public RecordFragment mRecordFragment;
+    public Boolean isRecording = false;
+
+    public RecordViewModel(MainActivity mMainActivity, RecordFragment fragment) {
+        this.mMainActivity = mMainActivity;
+        this.mRecordFragment = fragment;
+    }
+
+    public void back(View view) {
+        if (mMainActivity.getMMediaStream() != null) {
+            mMainActivity.getMMediaStream().stopPreview();
+            mMainActivity.getMMediaStream().destroyCamera();
+        }
+
+        mRecordFragment.destroyService();
+
+    }
+
+    public void openRecordPath(View view) {
+        mMainActivity.openRecordPath();
+    }
+
+    public void switchCamera(View view) {
+        MediaStream mMediaStream = mMainActivity.getMMediaStream();
+        if (mMediaStream != null) {
+            boolean isRecording = mMediaStream.isRecording();
+            if (isRecording) {
+                Toast.makeText(mMainActivity, "正在录像", Toast.LENGTH_SHORT).show();
+                return;
+            }
+        }
+
+        Toast.makeText(mMainActivity, "切换摄像头", Toast.LENGTH_SHORT).show();
+        int id = mMainActivity.getMMediaStream().getCameraId();
+        int swid = 0;
+        if (id == 0) {
+            swid = 1;
+        } else {
+            swid = 0;
+        }
+
+        mMainActivity.getMMediaStream().switchCamera(swid);
+    }
+
+    public void startOrStopRecording(View view) {
+        isRecording = !isRecording;
+        Toast.makeText(mMainActivity, String.format("%s录像", isRecording ? "开始" : "结束"), Toast.LENGTH_SHORT).show();
+        ImageView imageView = (ImageView) view;
+        imageView.setImageResource(isRecording ? R.mipmap.recording : R.mipmap.record_default);  // 替换为新的图片资源
+
+        MediaStream mMediaStream = mMainActivity.getMMediaStream();
+        if (mMediaStream != null) {
+            boolean isRecording = mMediaStream.isRecording();
+            if (isRecording) {
+                mMediaStream.stopRecord();
+            } else {
+                mMediaStream.startRecord();
+            }
+        }
+    }
+}
+

+ 1645 - 0
Android/app/src/main/java/com/easygbs/easygbd/viewmodel/fragment/StreamingSettingsViewModel.java

@@ -0,0 +1,1645 @@
+package com.easygbs.easygbd.viewmodel.fragment;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.PopupWindow;
+import android.widget.Toast;
+
+import androidx.databinding.BaseObservable;
+import androidx.databinding.ObservableField;
+import androidx.recyclerview.widget.GridLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.easygbs.easygbd.R;
+import com.easygbs.easygbd.activity.MainActivity;
+import com.easygbs.easygbd.adapter.AudiochannelAdapter;
+import com.easygbs.easygbd.adapter.AudiocodeAdapter;
+import com.easygbs.easygbd.adapter.AudiocoderateAdapter;
+import com.easygbs.easygbd.adapter.CameraAdapter;
+import com.easygbs.easygbd.adapter.FramerateAdapter;
+import com.easygbs.easygbd.adapter.LocationfrequencyAdapter;
+import com.easygbs.easygbd.adapter.ResolutionAdapter;
+import com.easygbs.easygbd.adapter.SamplingrateAdapter;
+import com.easygbs.easygbd.adapter.VideoCodeAdapter;
+import com.easygbs.easygbd.adapter.VideocoderateAdapter;
+import com.easygbs.easygbd.bean.AudiochannelBean;
+import com.easygbs.easygbd.bean.AudiocodeBean;
+import com.easygbs.easygbd.bean.AudiocoderateBean;
+import com.easygbs.easygbd.bean.CameraBean;
+import com.easygbs.easygbd.bean.FramerateBean;
+import com.easygbs.easygbd.bean.LocationfrequencyBean;
+import com.easygbs.easygbd.bean.ResolutionBean;
+import com.easygbs.easygbd.bean.SamplingrateBean;
+import com.easygbs.easygbd.bean.VideoCodeBean;
+import com.easygbs.easygbd.bean.VideocoderateBean;
+import com.easygbs.easygbd.common.Constant;
+import com.easygbs.easygbd.fragment.StreamingSettingsFragment;
+import com.easygbs.easygbd.push.MediaStream;
+import com.easygbs.easygbd.viewadapter.MultiItemTypeAdapter;
+
+import org.easydarwin.util.SPUtil;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class StreamingSettingsViewModel extends BaseObservable {
+    private static final String TAG = StreamingSettingsViewModel.class.getSimpleName();
+    public MainActivity mMainActivity = null;
+    public StreamingSettingsFragment mStreamingSettingsFragment = null;
+
+    public LayoutInflater mLayoutInflater = null;
+
+    public View mViewCamera = null;
+
+    public RecyclerView rvcamera;
+
+    public List<CameraBean> CameraBeanList = null;
+
+    public CameraAdapter mCameraAdapter = null;
+
+    public PopupWindow mPopupCamera = null;
+
+    public View mViewVideoCode = null;
+
+    public RecyclerView rvvideocode;
+
+    public List<VideoCodeBean> VideoCodeBeanList = null;
+
+    public VideoCodeAdapter mVideoCodeAdapter = null;
+
+    public PopupWindow mPopupVideoCode = null;
+
+    public View mViewResolution = null;
+
+    public RecyclerView rvresolution;
+
+    public List<ResolutionBean> ResolutionBeanList = null;
+
+    public ResolutionAdapter mResolutionAdapter = null;
+
+    public PopupWindow mPopupResolution = null;
+
+    public View mViewFramerate = null;
+
+    public RecyclerView rvframerate;
+
+    public List<FramerateBean> FramerateBeanList = null;
+
+    public FramerateAdapter mFramerateAdapter = null;
+
+    public PopupWindow mPopupFramerate = null;
+
+    public View mViewvideocoderate = null;
+
+    public RecyclerView rvvideocoderate;
+
+    public List<VideocoderateBean> VideocoderateBeanList = null;
+
+    public VideocoderateAdapter mVideocoderateAdapter = null;
+
+    public PopupWindow mPopupvideocoderate = null;
+
+    public View mViewaudiocode = null;
+
+    public RecyclerView rvaudiocode;
+
+    public List<AudiocodeBean> AudiocodeBeanList = null;
+
+    public AudiocodeAdapter mAudiocodeAdapter = null;
+
+    public PopupWindow mPopupaudiocode = null;
+
+    public View mViewsamplingrate = null;
+
+    public RecyclerView rvsamplingrate;
+
+    public List<SamplingrateBean> SamplingrateBeanList = null;
+
+    public SamplingrateAdapter mSamplingrateAdapter = null;
+
+    public PopupWindow mPopupsamplingrate = null;
+
+    public View mViewaudiochannel = null;
+
+    public RecyclerView rvaudiochannel;
+
+    public List<AudiochannelBean> AudiochannelBeanList = null;
+
+    public AudiochannelAdapter mAudiochannelAdapter = null;
+
+    public PopupWindow mPopupaudiochannel = null;
+
+    public View mViewaudiocoderate = null;
+
+    public RecyclerView rvaudiocoderate;
+
+    public List<AudiocoderateBean> AudiocoderateBeanList = null;
+
+    public AudiocoderateAdapter mAudiocoderateAdapter = null;
+
+    public PopupWindow mPopupaudiocoderate = null;
+
+    public View mViewlocationfrequency = null;
+
+    public RecyclerView rvlocationfrequency;
+
+    public List<LocationfrequencyBean> LocationfrequencyBeanList = null;
+
+    public LocationfrequencyAdapter mLocationfrequencyAdapter = null;
+
+    public PopupWindow mPopuplocationfrequency = null;
+
+    public ObservableField<String> audiochannelObservableField = new ObservableField<>();
+
+    public int selectcameraid = 0;
+
+    public boolean selectvideocode = true;
+
+    public int selectvideoresolution = 0;
+
+    public int selectframerate = 25;
+
+    public int selectvideocoderate = 1024;
+
+    public int selectaudiocode = 0;
+
+    public int selectsamplingrate = 8000;
+
+    public int selectaudiochannel = 0;
+
+    public int selectaudiocoderate = 16;
+
+    public int selectlocationfreq = 1;
+
+    public StreamingSettingsViewModel(MainActivity mMainActivity, StreamingSettingsFragment mStreamingSettingsFragment) {
+        this.mMainActivity = mMainActivity;
+        this.mStreamingSettingsFragment = mStreamingSettingsFragment;
+        mLayoutInflater = LayoutInflater.from(mMainActivity);
+
+        init();
+    }
+
+    public void init() {
+        int isenvideo = SPUtil.getIsenvideo(mMainActivity);
+        if (isenvideo == 0) {
+            mStreamingSettingsFragment.mFragmentStreamingsettingsBinding.llvideoset.setBackgroundResource(R.mipmap.ic_notselected);
+            mStreamingSettingsFragment.mFragmentStreamingsettingsBinding.ivvideoset.setVisibility(View.INVISIBLE);
+
+            mStreamingSettingsFragment.mFragmentStreamingsettingsBinding.llcamera.setEnabled(false);
+            mStreamingSettingsFragment.mFragmentStreamingsettingsBinding.llvideocode.setEnabled(false);
+            mStreamingSettingsFragment.mFragmentStreamingsettingsBinding.llresolution.setEnabled(false);
+            mStreamingSettingsFragment.mFragmentStreamingsettingsBinding.llframerate.setEnabled(false);
+            mStreamingSettingsFragment.mFragmentStreamingsettingsBinding.llvideocoderate.setEnabled(false);
+        } else {
+            mStreamingSettingsFragment.mFragmentStreamingsettingsBinding.llvideoset.setBackgroundResource(R.mipmap.ic_selected);
+            mStreamingSettingsFragment.mFragmentStreamingsettingsBinding.ivvideoset.setVisibility(View.VISIBLE);
+
+            mStreamingSettingsFragment.mFragmentStreamingsettingsBinding.llcamera.setEnabled(true);
+            mStreamingSettingsFragment.mFragmentStreamingsettingsBinding.llvideocode.setEnabled(true);
+            mStreamingSettingsFragment.mFragmentStreamingsettingsBinding.llresolution.setEnabled(true);
+            mStreamingSettingsFragment.mFragmentStreamingsettingsBinding.llframerate.setEnabled(true);
+            mStreamingSettingsFragment.mFragmentStreamingsettingsBinding.llvideocoderate.setEnabled(true);
+        }
+
+        int cameraid = SPUtil.getCameraid(mMainActivity);
+        if (cameraid == 0) {
+            mStreamingSettingsFragment.mFragmentStreamingsettingsBinding.tvcamera.setText("后置相机");
+        } else {
+            mStreamingSettingsFragment.mFragmentStreamingsettingsBinding.tvcamera.setText("前置相机");
+        }
+
+        if (mViewCamera != null) {
+            selectcameraid = cameraid;
+            String str = "";
+            if (cameraid == 0) {
+                str = "后置相机";
+            } else {
+                str = "前置相机";
+            }
+
+            for (int i = 0; i < CameraBeanList.size(); i++) {
+                CameraBean mCameraBean = CameraBeanList.get(i);
+                mCameraBean.setIsst(0);
+            }
+
+            for (int i = 0; i < CameraBeanList.size(); i++) {
+                CameraBean mCameraBean = CameraBeanList.get(i);
+                if (str.equals(mCameraBean.getName())) {
+                    mCameraBean.setIsst(1);
+                    break;
+                }
+            }
+
+            mCameraAdapter.notifyDataSetChanged();
+        }
+
+        boolean mHevc = SPUtil.getHevcCodec(mMainActivity);
+        Log.i(TAG, "mHevc  " + mHevc);
+        String type = "";
+        if (mHevc) {
+            type = "H265";
+        } else {
+            type = "H264";
+        }
+        mStreamingSettingsFragment.mFragmentStreamingsettingsBinding.tvvideocode.setText(type);
+
+        if (mViewVideoCode != null) {
+            selectvideocode = mHevc;
+
+            for (int i = 0; i < VideoCodeBeanList.size(); i++) {
+                VideoCodeBean mVideoCodeBean = VideoCodeBeanList.get(i);
+                mVideoCodeBean.setIsst(0);
+            }
+
+            for (int i = 0; i < VideoCodeBeanList.size(); i++) {
+                VideoCodeBean mVideoCodeBean = VideoCodeBeanList.get(i);
+                if (mHevc) {
+                    if (mVideoCodeBean.getName().equals("H265")) {
+                        mVideoCodeBean.setIsst(1);
+                    } else {
+                        mVideoCodeBean.setIsst(0);
+                    }
+                } else {
+                    if (mVideoCodeBean.getName().equals("H264")) {
+                        mVideoCodeBean.setIsst(1);
+                    } else {
+                        mVideoCodeBean.setIsst(0);
+                    }
+                }
+            }
+
+            mVideoCodeAdapter.notifyDataSetChanged();
+        }
+
+        int videoresolution = SPUtil.getVideoresolution(mMainActivity);
+        String videoresolutionname = "";
+        if (videoresolution == 0) {
+            videoresolutionname = "1920*1080";
+            mStreamingSettingsFragment.mFragmentStreamingsettingsBinding.tvresolution.setText("1920*1080");
+        } else if (videoresolution == 1) {
+            videoresolutionname = "1280*960";
+            mStreamingSettingsFragment.mFragmentStreamingsettingsBinding.tvresolution.setText("1280*960");
+        } else if (videoresolution == 2) {
+            videoresolutionname = "1280*720";
+            mStreamingSettingsFragment.mFragmentStreamingsettingsBinding.tvresolution.setText("1280*720");
+        }
+
+        if (mViewResolution != null) {
+            selectvideoresolution = videoresolution;
+
+            for (int i = 0; i < ResolutionBeanList.size(); i++) {
+                ResolutionBean mResolutionBean = ResolutionBeanList.get(i);
+                mResolutionBean.setIsst(0);
+            }
+
+            for (int i = 0; i < ResolutionBeanList.size(); i++) {
+                ResolutionBean mResolutionBean = ResolutionBeanList.get(i);
+                if (mResolutionBean.getName().equals(videoresolutionname)) {
+                    mResolutionBean.setIsst(1);
+                    break;
+                }
+            }
+
+            mResolutionAdapter.notifyDataSetChanged();
+        }
+
+        int framerate = SPUtil.getFramerate(mMainActivity);
+        mStreamingSettingsFragment.mFragmentStreamingsettingsBinding.tvframerate.setText("" + framerate);
+        if (mViewFramerate != null) {
+            selectframerate = framerate;
+
+            for (int i = 0; i < FramerateBeanList.size(); i++) {
+                FramerateBean mFramerateBean = FramerateBeanList.get(i);
+                mFramerateBean.setIsst(0);
+            }
+
+            for (int i = 0; i < FramerateBeanList.size(); i++) {
+                FramerateBean mFramerateBean = FramerateBeanList.get(i);
+                if (mFramerateBean.getName().equals("" + framerate)) {
+                    mFramerateBean.setIsst(1);
+                    break;
+                }
+            }
+
+            mFramerateAdapter.notifyDataSetChanged();
+        }
+
+        int videocoderate = SPUtil.getBitrateKbps(mMainActivity);
+        mStreamingSettingsFragment.mFragmentStreamingsettingsBinding.tvvideocoderate.setText("" + videocoderate);
+        if (mViewvideocoderate != null) {
+            selectvideocoderate = videocoderate;
+
+            for (int i = 0; i < VideocoderateBeanList.size(); i++) {
+                VideocoderateBean mVideocoderateBean = VideocoderateBeanList.get(i);
+                mVideocoderateBean.setIsst(0);
+            }
+
+            for (int i = 0; i < VideocoderateBeanList.size(); i++) {
+                VideocoderateBean mVideocoderateBean = VideocoderateBeanList.get(i);
+                if (mVideocoderateBean.getName().equals("" + videocoderate)) {
+                    mVideocoderateBean.setIsst(1);
+                    break;
+                }
+            }
+
+            mVideocoderateAdapter.notifyDataSetChanged();
+
+        }
+
+        mStreamingSettingsFragment.isenaudio = SPUtil.getIsenaudio(mMainActivity);
+        if (mStreamingSettingsFragment.isenaudio == 0) {
+            mStreamingSettingsFragment.mFragmentStreamingsettingsBinding.llaudioset.setBackgroundResource(R.mipmap.ic_notselected);
+            mStreamingSettingsFragment.mFragmentStreamingsettingsBinding.ivaudioset.setVisibility(View.INVISIBLE);
+
+            mStreamingSettingsFragment.mFragmentStreamingsettingsBinding.llaudiocode.setEnabled(false);
+            mStreamingSettingsFragment.mFragmentStreamingsettingsBinding.llsamplingrate.setEnabled(false);
+            mStreamingSettingsFragment.mFragmentStreamingsettingsBinding.llaudiochannel.setEnabled(false);
+            mStreamingSettingsFragment.mFragmentStreamingsettingsBinding.llaudiocoderate.setEnabled(false);
+        } else {
+            mStreamingSettingsFragment.mFragmentStreamingsettingsBinding.llaudioset.setBackgroundResource(R.mipmap.ic_selected);
+            mStreamingSettingsFragment.mFragmentStreamingsettingsBinding.ivaudioset.setVisibility(View.VISIBLE);
+
+            mStreamingSettingsFragment.mFragmentStreamingsettingsBinding.llaudiocode.setEnabled(true);
+            mStreamingSettingsFragment.mFragmentStreamingsettingsBinding.llsamplingrate.setEnabled(true);
+            mStreamingSettingsFragment.mFragmentStreamingsettingsBinding.llaudiochannel.setEnabled(true);
+            mStreamingSettingsFragment.mFragmentStreamingsettingsBinding.llaudiocoderate.setEnabled(true);
+        }
+
+        int AACCodec = SPUtil.getAACCodec(mMainActivity);
+        Log.i(TAG, "AACCodec  " + AACCodec);
+        String AACCodecstr = "";
+        if (AACCodec == 0) {
+            AACCodecstr = "G711A";
+        } else if (AACCodec == 1) {
+            AACCodecstr = "G711U";
+        } else if (AACCodec == 2) {
+            AACCodecstr = "AAC";
+        }
+
+        mStreamingSettingsFragment.mFragmentStreamingsettingsBinding.tvaudiocode.setText(AACCodecstr);
+
+        if (mViewaudiocode != null) {
+            selectaudiocode = AACCodec;
+
+            for (int i = 0; i < AudiocodeBeanList.size(); i++) {
+                AudiocodeBean mAudiocodeBean = AudiocodeBeanList.get(i);
+                mAudiocodeBean.setIsst(0);
+            }
+
+            for (int i = 0; i < AudiocodeBeanList.size(); i++) {
+                AudiocodeBean mAudiocodeBean = AudiocodeBeanList.get(i);
+                if (mAudiocodeBean.getName().equals(AACCodecstr)) {
+                    mAudiocodeBean.setIsst(1);
+                    break;
+                }
+            }
+
+            mAudiocodeAdapter.notifyDataSetChanged();
+        }
+
+        int samplingrate = SPUtil.getSamplingrate(mMainActivity);
+        Log.i(TAG, "samplingrate  " + samplingrate);
+        mStreamingSettingsFragment.mFragmentStreamingsettingsBinding.tvsamplingrate.setText("" + samplingrate);
+        if (mViewsamplingrate != null) {
+            selectsamplingrate = samplingrate;
+
+            for (int i = 0; i < SamplingrateBeanList.size(); i++) {
+                SamplingrateBean mSamplingrateBean = SamplingrateBeanList.get(i);
+                mSamplingrateBean.setIsst(0);
+            }
+
+            for (int i = 0; i < SamplingrateBeanList.size(); i++) {
+                SamplingrateBean mSamplingrateBean = SamplingrateBeanList.get(i);
+                if (mSamplingrateBean.getName().equals("" + samplingrate)) {
+                    mSamplingrateBean.setIsst(1);
+                    break;
+                }
+            }
+
+            mSamplingrateAdapter.notifyDataSetChanged();
+        }
+
+        int audiochannel = SPUtil.getAudiochannel(mMainActivity);
+        Log.i(TAG, "audiochannel  " + audiochannel);
+        String auch = "";
+        if (audiochannel == 0) {
+            auch = "单声道";
+        } else {
+            auch = "立体声道";
+        }
+        //audiochannelObservableField.set(auch);
+
+        mStreamingSettingsFragment.mFragmentStreamingsettingsBinding.tvaudiochannel.setText(auch);
+
+        if (mViewaudiochannel != null) {
+            selectaudiochannel = audiochannel;
+
+            for (int i = 0; i < AudiochannelBeanList.size(); i++) {
+                AudiochannelBean mAudiochannelBean = AudiochannelBeanList.get(i);
+                mAudiochannelBean.setIsst(0);
+            }
+
+            for (int i = 0; i < AudiochannelBeanList.size(); i++) {
+                AudiochannelBean mAudiochannelBean = AudiochannelBeanList.get(i);
+                if (mAudiochannelBean.getName().equals(auch)) {
+                    mAudiochannelBean.setIsst(1);
+                    break;
+                }
+            }
+
+            mAudiochannelAdapter.notifyDataSetChanged();
+        }
+
+        int audiocoderate = SPUtil.getAudiocoderate(mMainActivity);
+        Log.i(TAG, "audiocoderate  " + audiocoderate);
+        mStreamingSettingsFragment.mFragmentStreamingsettingsBinding.tvaudiocoderate.setText("" + audiocoderate);
+
+        if (mViewaudiocoderate != null) {
+            selectaudiocoderate = audiocoderate;
+
+            for (int i = 0; i < AudiocoderateBeanList.size(); i++) {
+                AudiocoderateBean mAudiocoderateBean = AudiocoderateBeanList.get(i);
+                mAudiocoderateBean.setIsst(0);
+            }
+
+            for (int i = 0; i < AudiocoderateBeanList.size(); i++) {
+                AudiocoderateBean mAudiocoderateBean = AudiocoderateBeanList.get(i);
+                if (mAudiocoderateBean.getName().equals("" + audiocoderate)) {
+                    mAudiocoderateBean.setIsst(1);
+                    break;
+                }
+            }
+
+            mAudiocoderateAdapter.notifyDataSetChanged();
+        }
+
+        mStreamingSettingsFragment.isenlocreport = SPUtil.getIsenlocreport(mMainActivity);
+        if (mStreamingSettingsFragment.isenlocreport == 0) {
+            mStreamingSettingsFragment.mFragmentStreamingsettingsBinding.lllocreportset.setBackgroundResource(R.mipmap.ic_notselected);
+            mStreamingSettingsFragment.mFragmentStreamingsettingsBinding.ivlocreportset.setVisibility(View.INVISIBLE);
+
+            mStreamingSettingsFragment.mFragmentStreamingsettingsBinding.lllocationfrequency.setEnabled(false);
+        } else {
+            mStreamingSettingsFragment.mFragmentStreamingsettingsBinding.lllocreportset.setBackgroundResource(R.mipmap.ic_selected);
+            mStreamingSettingsFragment.mFragmentStreamingsettingsBinding.ivlocreportset.setVisibility(View.VISIBLE);
+
+            mStreamingSettingsFragment.mFragmentStreamingsettingsBinding.lllocationfrequency.setEnabled(true);
+        }
+
+        int locationfreq = SPUtil.getLocationfreq(mMainActivity);
+        mStreamingSettingsFragment.mFragmentStreamingsettingsBinding.tvlocationfrequency.setText("" + locationfreq + "秒");
+
+        if (mViewlocationfrequency != null) {
+            selectlocationfreq = locationfreq;
+
+            for (int i = 0; i < LocationfrequencyBeanList.size(); i++) {
+                LocationfrequencyBean mLocationfrequencyBean = LocationfrequencyBeanList.get(i);
+                mLocationfrequencyBean.setIsst(0);
+            }
+
+            for (int i = 0; i < LocationfrequencyBeanList.size(); i++) {
+                LocationfrequencyBean mLocationfrequencyBean = LocationfrequencyBeanList.get(i);
+                String getname = mLocationfrequencyBean.getName();
+                if (Integer.parseInt(getname.substring(0, getname.length() - 1)) == locationfreq) {
+                    mLocationfrequencyBean.setIsst(1);
+                    break;
+                }
+            }
+
+            mLocationfrequencyAdapter.notifyDataSetChanged();
+        }
+    }
+
+    public void camera(View view) {
+        view.post(new Runnable() {
+            @Override
+            public void run() {
+                if (mViewCamera == null) {
+                    mViewCamera = mLayoutInflater.inflate(R.layout.po_camera, null);
+
+                    rvcamera = mViewCamera.findViewById(R.id.rvcamera);
+
+                    rvcamera.setLayoutManager(new GridLayoutManager(mMainActivity, 1, RecyclerView.VERTICAL, false));
+
+                    CameraBeanList = new ArrayList<CameraBean>();
+                    mCameraAdapter = new CameraAdapter(mMainActivity, mMainActivity, R.layout.adapter_camera, CameraBeanList);
+                    rvcamera.setAdapter(mCameraAdapter);
+
+                    mCameraAdapter.setOnItemClickListener(new MultiItemTypeAdapter.OnItemClickListener() {
+                        @Override
+                        public void onItemClick(View view, RecyclerView.ViewHolder holder, int position) {
+                            String name = CameraBeanList.get(position).getName();
+
+                            selectcameraid = position;
+
+                            mStreamingSettingsFragment.mFragmentStreamingsettingsBinding.tvcamera.setText(name);
+
+                            for (int i = 0; i < CameraBeanList.size(); i++) {
+                                CameraBean mCameraBean = CameraBeanList.get(i);
+                                mCameraBean.setIsst(0);
+                            }
+
+                            for (int i = 0; i < CameraBeanList.size(); i++) {
+                                CameraBean mCameraBean = CameraBeanList.get(i);
+                                if (mCameraBean.getName().equals(name)) {
+                                    mCameraBean.setIsst(1);
+                                    break;
+                                }
+                            }
+
+                            mCameraAdapter.notifyDataSetChanged();
+
+                            hidePopCamera();
+                        }
+
+                        @Override
+                        public boolean onItemLongClick(View view, RecyclerView.ViewHolder holder, int position) {
+                            return false;
+                        }
+                    });
+
+
+                    int cameraid = SPUtil.getCameraid(mMainActivity);
+
+                    selectcameraid = cameraid;
+                    String str = "";
+                    if (cameraid == 0) {
+                        str = "后置相机";
+                    } else {
+                        str = "前置相机";
+                    }
+
+                    String[] cameras = mMainActivity.getResources().getStringArray(R.array.cameraarr);
+                    for (int i = 0; i < cameras.length; i++) {
+                        CameraBean mCameraBean = new CameraBean();
+                        mCameraBean.setName(cameras[i]);
+                        if (str.equals(cameras[i])) {
+                            mCameraBean.setIsst(1);
+                        } else {
+                            mCameraBean.setIsst(0);
+                        }
+
+                        CameraBeanList.add(mCameraBean);
+                    }
+
+                    mCameraAdapter.notifyDataSetChanged();
+                }
+
+                // 动态计算 PopupWindow 高度
+                int viewWidth = view.getWidth();
+                mViewCamera.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
+                int height = mViewCamera.getMeasuredHeight(); // 获取测量后的高度
+                mPopupCamera = new PopupWindow(mViewCamera, viewWidth, height + 2);
+
+                mPopupCamera.setOutsideTouchable(true);
+
+                mPopupCamera.setOnDismissListener(new PopupWindow.OnDismissListener() {
+                    @Override
+                    public void onDismiss() {
+
+                    }
+                });
+
+                int yoff = (int) mMainActivity.getResources().getDimension(R.dimen.dp_4);
+
+                mPopupCamera.showAsDropDown(mStreamingSettingsFragment.mFragmentStreamingsettingsBinding.tvcamera, 0, yoff);
+
+            }
+        });
+    }
+
+    public void hidePopCamera() {
+        if (mPopupCamera != null) {
+            boolean isShowing = mPopupCamera.isShowing();
+            if (isShowing) {
+                mPopupCamera.dismiss();
+            }
+        }
+    }
+
+    public void videocode(View view) {
+        view.post(new Runnable() {
+            @Override
+            public void run() {
+
+                if (mViewVideoCode == null) {
+                    mViewVideoCode = mLayoutInflater.inflate(R.layout.po_videocode, null);
+
+                    rvvideocode = mViewVideoCode.findViewById(R.id.rvvideocode);
+
+                    rvvideocode.setLayoutManager(new GridLayoutManager(mMainActivity, 1, RecyclerView.VERTICAL, false));
+
+                    VideoCodeBeanList = new ArrayList<VideoCodeBean>();
+                    mVideoCodeAdapter = new VideoCodeAdapter(mMainActivity, mMainActivity, R.layout.adapter_videocode, VideoCodeBeanList);
+                    rvvideocode.setAdapter(mVideoCodeAdapter);
+
+                    mVideoCodeAdapter.setOnItemClickListener(new MultiItemTypeAdapter.OnItemClickListener() {
+                        @Override
+                        public void onItemClick(View view, RecyclerView.ViewHolder holder, int position) {
+                            String name = VideoCodeBeanList.get(position).getName();
+                            if (name.equals("H264")) {
+                                selectvideocode = false;
+                            } else if (name.equals("H265")) {
+                                selectvideocode = true;
+                            }
+
+                            mStreamingSettingsFragment.mFragmentStreamingsettingsBinding.tvvideocode.setText(name);
+
+                            for (int i = 0; i < VideoCodeBeanList.size(); i++) {
+                                VideoCodeBean mVideoCodeBean = VideoCodeBeanList.get(i);
+                                mVideoCodeBean.setIsst(0);
+                            }
+
+                            for (int i = 0; i < VideoCodeBeanList.size(); i++) {
+                                VideoCodeBean mVideoCodeBean = VideoCodeBeanList.get(i);
+                                if (mVideoCodeBean.getName().equals(name)) {
+                                    mVideoCodeBean.setIsst(1);
+                                    break;
+                                }
+                            }
+
+                            mVideoCodeAdapter.notifyDataSetChanged();
+
+                            hidePopVideoCode();
+                        }
+
+                        @Override
+                        public boolean onItemLongClick(View view, RecyclerView.ViewHolder holder, int position) {
+                            return false;
+                        }
+                    });
+
+                    boolean mHevc = SPUtil.getHevcCodec(mMainActivity);
+
+                    Log.i(TAG, "videocode  mHevc  " + mHevc);
+
+                    selectvideocode = mHevc;
+
+                    String type = "";
+                    String[] videocodes = mMainActivity.getResources().getStringArray(R.array.videocodearr);
+                    for (int i = 0; i < videocodes.length; i++) {
+                        VideoCodeBean mVideoCodeBean = new VideoCodeBean();
+                        mVideoCodeBean.setName(videocodes[i]);
+                        if (mHevc) {
+                            if (videocodes[i].equals("H265")) {
+                                mVideoCodeBean.setIsst(1);
+                                type = videocodes[i];
+                            } else {
+                                mVideoCodeBean.setIsst(0);
+                            }
+                        } else {
+                            if (videocodes[i].equals("H264")) {
+                                mVideoCodeBean.setIsst(1);
+                                type = videocodes[i];
+                            } else {
+                                mVideoCodeBean.setIsst(0);
+                            }
+                        }
+
+                        VideoCodeBeanList.add(mVideoCodeBean);
+                    }
+
+                    mStreamingSettingsFragment.mFragmentStreamingsettingsBinding.tvvideocode.setText(type);
+
+                    mVideoCodeAdapter.notifyDataSetChanged();
+                }
+
+                // 动态计算 PopupWindow 高度
+                int viewWidth = view.getWidth();
+                mViewVideoCode.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
+                int height = mViewVideoCode.getMeasuredHeight(); // 获取测量后的高度
+                mPopupVideoCode = new PopupWindow(mViewVideoCode, viewWidth, height + 2);
+
+                mPopupVideoCode.setOutsideTouchable(true);
+
+                mPopupVideoCode.setOnDismissListener(new PopupWindow.OnDismissListener() {
+                    @Override
+                    public void onDismiss() {
+
+                    }
+                });
+                int yoff = (int) mMainActivity.getResources().getDimension(R.dimen.dp_4);
+
+                mPopupVideoCode.showAsDropDown(mStreamingSettingsFragment.mFragmentStreamingsettingsBinding.tvvideocode, 0, yoff);
+
+            }
+        });
+    }
+
+    public void hidePopVideoCode() {
+        if (mPopupVideoCode != null) {
+            boolean isShowing = mPopupVideoCode.isShowing();
+            if (isShowing) {
+                mPopupVideoCode.dismiss();
+            }
+        }
+    }
+
+    public void resolution(View view) {
+        view.post(new Runnable() {
+            @Override
+            public void run() {
+
+                if (mViewResolution == null) {
+                    mViewResolution = mLayoutInflater.inflate(R.layout.po_resolution, null);
+
+                    rvresolution = mViewResolution.findViewById(R.id.rvresolution);
+
+                    rvresolution.setLayoutManager(new GridLayoutManager(mMainActivity, 1, RecyclerView.VERTICAL, false));
+
+                    ResolutionBeanList = new ArrayList<ResolutionBean>();
+                    mResolutionAdapter = new ResolutionAdapter(mMainActivity, mMainActivity, R.layout.adapter_resolution, ResolutionBeanList);
+                    rvresolution.setAdapter(mResolutionAdapter);
+
+                    mResolutionAdapter.setOnItemClickListener(new MultiItemTypeAdapter.OnItemClickListener() {
+                        @Override
+                        public void onItemClick(View view, RecyclerView.ViewHolder holder, int position) {
+                            String name = ResolutionBeanList.get(position).getName();
+
+                            int videoresolution = 0;
+                            if (name.equals("1920*1080")) {
+                                videoresolution = 0;
+                            } else if (name.equals("1280*960")) {
+                                videoresolution = 1;
+                            } else if (name.equals("1280*720")) {
+                                videoresolution = 2;
+                            }
+                            selectvideoresolution = videoresolution;
+
+                            mStreamingSettingsFragment.mFragmentStreamingsettingsBinding.tvresolution.setText(name);
+
+                            for (int i = 0; i < ResolutionBeanList.size(); i++) {
+                                ResolutionBean mResolutionBean = ResolutionBeanList.get(i);
+                                mResolutionBean.setIsst(0);
+                            }
+
+                            for (int i = 0; i < ResolutionBeanList.size(); i++) {
+                                ResolutionBean mResolutionBean = ResolutionBeanList.get(i);
+                                if (mResolutionBean.getName().equals(name)) {
+                                    mResolutionBean.setIsst(1);
+                                    break;
+                                }
+                            }
+
+                            mResolutionAdapter.notifyDataSetChanged();
+
+                            hidePopResolution();
+                        }
+
+                        @Override
+                        public boolean onItemLongClick(View view, RecyclerView.ViewHolder holder, int position) {
+                            return false;
+                        }
+                    });
+
+                    int videoresolution = SPUtil.getVideoresolution(mMainActivity);
+                    selectvideoresolution = videoresolution;
+                    String str = "";
+                    if (videoresolution == 0) {
+                        str = "1920*1080";
+                    } else if (videoresolution == 1) {
+                        str = "1280*720";
+                    } else if (videoresolution == 2) {
+                        str = "640*480";
+                    }
+
+                    String[] resolutions = mMainActivity.getResources().getStringArray(R.array.resolutionarr);
+                    for (int i = 0; i < resolutions.length; i++) {
+                        ResolutionBean mResolutionBean = new ResolutionBean();
+                        mResolutionBean.setName(resolutions[i]);
+                        if (str.equals(resolutions[i])) {
+                            mResolutionBean.setIsst(1);
+                        } else {
+                            mResolutionBean.setIsst(0);
+                        }
+
+                        ResolutionBeanList.add(mResolutionBean);
+                    }
+
+                    mResolutionAdapter.notifyDataSetChanged();
+                }
+
+                int viewWidth = view.getWidth();
+                mViewResolution.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
+                int height = mViewResolution.getMeasuredHeight(); // 获取测量后的高度
+                mPopupResolution = new PopupWindow(mViewResolution, viewWidth, height + 2);
+                mPopupResolution.setOutsideTouchable(true);
+
+                mPopupResolution.setOnDismissListener(new PopupWindow.OnDismissListener() {
+                    @Override
+                    public void onDismiss() {
+
+                    }
+                });
+
+
+                int yoff = (int) mMainActivity.getResources().getDimension(R.dimen.dp_4);
+
+                mPopupResolution.showAsDropDown(mStreamingSettingsFragment.mFragmentStreamingsettingsBinding.tvresolution, 0, yoff);
+
+            }
+        });
+    }
+
+    public void hidePopResolution() {
+        if (mPopupResolution != null) {
+            boolean isShowing = mPopupResolution.isShowing();
+            if (isShowing) {
+                mPopupResolution.dismiss();
+            }
+        }
+    }
+
+    public void framerate(View view) {
+        view.post(new Runnable() {
+            @Override
+            public void run() {
+
+                if (mViewFramerate == null) {
+                    mViewFramerate = mLayoutInflater.inflate(R.layout.po_framerate, null);
+
+                    rvframerate = mViewFramerate.findViewById(R.id.rvframerate);
+
+                    rvframerate.setLayoutManager(new GridLayoutManager(mMainActivity, 1, RecyclerView.VERTICAL, false));
+
+                    FramerateBeanList = new ArrayList<FramerateBean>();
+                    mFramerateAdapter = new FramerateAdapter(mMainActivity, mMainActivity, R.layout.adapter_framerate, FramerateBeanList);
+                    rvframerate.setAdapter(mFramerateAdapter);
+
+                    mFramerateAdapter.setOnItemClickListener(new MultiItemTypeAdapter.OnItemClickListener() {
+                        @Override
+                        public void onItemClick(View view, RecyclerView.ViewHolder holder, int position) {
+                            String name = FramerateBeanList.get(position).getName();
+                            selectframerate = Integer.parseInt(name);
+
+                            mStreamingSettingsFragment.mFragmentStreamingsettingsBinding.tvframerate.setText(name);
+
+                            for (int i = 0; i < FramerateBeanList.size(); i++) {
+                                FramerateBean mFramerateBean = FramerateBeanList.get(i);
+                                mFramerateBean.setIsst(0);
+                            }
+
+                            for (int i = 0; i < FramerateBeanList.size(); i++) {
+                                FramerateBean mFramerateBean = FramerateBeanList.get(i);
+                                if (mFramerateBean.getName().equals(name)) {
+                                    mFramerateBean.setIsst(1);
+                                    break;
+                                }
+                            }
+
+                            mFramerateAdapter.notifyDataSetChanged();
+
+                            hidePopFramerate();
+                        }
+
+                        @Override
+                        public boolean onItemLongClick(View view, RecyclerView.ViewHolder holder, int position) {
+                            return false;
+                        }
+                    });
+
+                    int framerate = SPUtil.getFramerate(mMainActivity);
+                    selectframerate = framerate;
+
+                    mStreamingSettingsFragment.mFragmentStreamingsettingsBinding.tvframerate.setText("" + framerate);
+
+                    String[] framerates = mMainActivity.getResources().getStringArray(R.array.frameratearr);
+                    for (int i = 0; i < framerates.length; i++) {
+                        FramerateBean mFramerateBean = new FramerateBean();
+                        mFramerateBean.setName(framerates[i]);
+                        if (framerate == Integer.parseInt(framerates[i])) {
+                            mFramerateBean.setIsst(1);
+                        } else {
+                            mFramerateBean.setIsst(0);
+                        }
+
+                        FramerateBeanList.add(mFramerateBean);
+                    }
+
+                    mFramerateAdapter.notifyDataSetChanged();
+                }
+
+                int viewWidth = view.getWidth();
+                mViewFramerate.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
+                int height = mViewFramerate.getMeasuredHeight(); // 获取测量后的高度
+                mPopupFramerate = new PopupWindow(mViewFramerate, viewWidth, height - 1);
+                mPopupFramerate.setOutsideTouchable(true);
+
+                mPopupFramerate.setOnDismissListener(new PopupWindow.OnDismissListener() {
+                    @Override
+                    public void onDismiss() {
+
+                    }
+                });
+
+                int yoff = (int) mMainActivity.getResources().getDimension(R.dimen.dp_4);
+
+                mPopupFramerate.showAsDropDown(mStreamingSettingsFragment.mFragmentStreamingsettingsBinding.tvframerate, 0, yoff);
+
+            }
+        });
+    }
+
+    public void hidePopFramerate() {
+        if (mPopupFramerate != null) {
+            boolean isShowing = mPopupFramerate.isShowing();
+            if (isShowing) {
+                mPopupFramerate.dismiss();
+            }
+        }
+    }
+
+    public void videocoderate(View view) {
+        view.post(new Runnable() {
+            @Override
+            public void run() {
+
+                if (mViewvideocoderate == null) {
+                    mViewvideocoderate = mLayoutInflater.inflate(R.layout.po_videocoderate, null);
+
+                    rvvideocoderate = mViewvideocoderate.findViewById(R.id.rvvideocoderate);
+
+                    rvvideocoderate.setLayoutManager(new GridLayoutManager(mMainActivity, 1, RecyclerView.VERTICAL, false));
+
+                    VideocoderateBeanList = new ArrayList<VideocoderateBean>();
+                    mVideocoderateAdapter = new VideocoderateAdapter(mMainActivity, mMainActivity, R.layout.adapter_videocoderate, VideocoderateBeanList);
+                    rvvideocoderate.setAdapter(mVideocoderateAdapter);
+
+                    mVideocoderateAdapter.setOnItemClickListener(new MultiItemTypeAdapter.OnItemClickListener() {
+                        @Override
+                        public void onItemClick(View view, RecyclerView.ViewHolder holder, int position) {
+                            String name = VideocoderateBeanList.get(position).getName();
+                            selectvideocoderate = Integer.parseInt(name);
+
+                            mStreamingSettingsFragment.mFragmentStreamingsettingsBinding.tvvideocoderate.setText(name);
+
+                            for (int i = 0; i < VideocoderateBeanList.size(); i++) {
+                                VideocoderateBean mVideocoderateBean = VideocoderateBeanList.get(i);
+                                mVideocoderateBean.setIsst(0);
+                            }
+
+                            for (int i = 0; i < VideocoderateBeanList.size(); i++) {
+                                VideocoderateBean mVideocoderateBean = VideocoderateBeanList.get(i);
+                                if (mVideocoderateBean.getName().equals(name)) {
+                                    mVideocoderateBean.setIsst(1);
+                                    break;
+                                }
+                            }
+
+                            mVideocoderateAdapter.notifyDataSetChanged();
+
+                            hidePopvideocoderate();
+                        }
+
+                        @Override
+                        public boolean onItemLongClick(View view, RecyclerView.ViewHolder holder, int position) {
+                            return false;
+                        }
+                    });
+
+                    int videocoderate = SPUtil.getBitrateKbps(mMainActivity);
+                    selectvideocoderate = videocoderate;
+
+                    mStreamingSettingsFragment.mFragmentStreamingsettingsBinding.tvvideocoderate.setText("" + videocoderate);
+
+                    String[] videocoderates = mMainActivity.getResources().getStringArray(R.array.videocoderatearr);
+                    for (int i = 0; i < videocoderates.length; i++) {
+                        VideocoderateBean mVideocoderateBean = new VideocoderateBean();
+                        mVideocoderateBean.setName(videocoderates[i]);
+                        if (videocoderate == Integer.parseInt(videocoderates[i])) {
+                            mVideocoderateBean.setIsst(1);
+                        } else {
+                            mVideocoderateBean.setIsst(0);
+                        }
+
+                        VideocoderateBeanList.add(mVideocoderateBean);
+                    }
+
+                    mVideocoderateAdapter.notifyDataSetChanged();
+                }
+
+                int viewWidth = view.getWidth();
+                mViewvideocoderate.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
+                int height = mViewvideocoderate.getMeasuredHeight(); // 获取测量后的高度
+                mPopupvideocoderate = new PopupWindow(mViewvideocoderate, viewWidth, height - 1);
+
+                mPopupvideocoderate.setOutsideTouchable(true);
+
+                mPopupvideocoderate.setOnDismissListener(new PopupWindow.OnDismissListener() {
+                    @Override
+                    public void onDismiss() {
+
+                    }
+                });
+
+                int yoff = (int) mMainActivity.getResources().getDimension(R.dimen.dp_4);
+                mPopupvideocoderate.showAsDropDown(mStreamingSettingsFragment.mFragmentStreamingsettingsBinding.tvvideocoderate, 0, yoff);
+            }
+        });
+    }
+
+    public void hidePopvideocoderate() {
+        if (mPopupvideocoderate != null) {
+            boolean isShowing = mPopupvideocoderate.isShowing();
+            if (isShowing) {
+                mPopupvideocoderate.dismiss();
+            }
+        }
+    }
+
+    public void audiocode(View view) {
+        view.post(new Runnable() {
+            @Override
+            public void run() {
+
+                if (mViewaudiocode == null) {
+                    mViewaudiocode = mLayoutInflater.inflate(R.layout.po_audiocode, null);
+
+                    rvaudiocode = mViewaudiocode.findViewById(R.id.rvaudiocode);
+
+                    rvaudiocode.setLayoutManager(new GridLayoutManager(mMainActivity, 1, RecyclerView.VERTICAL, false));
+
+                    AudiocodeBeanList = new ArrayList<AudiocodeBean>();
+                    mAudiocodeAdapter = new AudiocodeAdapter(mMainActivity, mMainActivity, R.layout.adapter_audiocode, AudiocodeBeanList);
+                    rvaudiocode.setAdapter(mAudiocodeAdapter);
+
+                    mAudiocodeAdapter.setOnItemClickListener(new MultiItemTypeAdapter.OnItemClickListener() {
+                        @Override
+                        public void onItemClick(View view, RecyclerView.ViewHolder holder, int position) {
+                            String name = AudiocodeBeanList.get(position).getName();
+
+                            int value = 0;
+                            if (name.equals("G711A")) {
+                                value = 0;
+                            } else if (name.equals("G711U")) {
+                                value = 1;
+                            } else if (name.equals("AAC")) {
+                                value = 2;
+                            }
+
+                            selectaudiocode = value;
+
+                            mStreamingSettingsFragment.mFragmentStreamingsettingsBinding.tvaudiocode.setText(name);
+
+                            for (int i = 0; i < AudiocodeBeanList.size(); i++) {
+                                AudiocodeBean mAudiocodeBean = AudiocodeBeanList.get(i);
+                                mAudiocodeBean.setIsst(0);
+                            }
+
+                            for (int i = 0; i < AudiocodeBeanList.size(); i++) {
+                                AudiocodeBean mAudiocodeBean = AudiocodeBeanList.get(i);
+                                if (mAudiocodeBean.getName().equals(name)) {
+                                    mAudiocodeBean.setIsst(1);
+                                    break;
+                                }
+                            }
+
+                            mAudiocodeAdapter.notifyDataSetChanged();
+
+                            hidePopaudiocode();
+                        }
+
+                        @Override
+                        public boolean onItemLongClick(View view, RecyclerView.ViewHolder holder, int position) {
+                            return false;
+                        }
+                    });
+
+                    int AACCodec = SPUtil.getAACCodec(mMainActivity);
+                    selectaudiocode = AACCodec;
+                    String str = "";
+                    if (AACCodec == 0) {
+                        str = "G711A";
+                    } else if (AACCodec == 1) {
+                        str = "G711U";
+                    } else if (AACCodec == 2) {
+                        str = "AAC";
+                    }
+
+                    mStreamingSettingsFragment.mFragmentStreamingsettingsBinding.tvaudiocode.setText(str);
+
+                    String[] audiocodes = mMainActivity.getResources().getStringArray(R.array.audiocodearr);
+                    for (int i = 0; i < audiocodes.length; i++) {
+                        AudiocodeBean mAudiocodeBean = new AudiocodeBean();
+                        mAudiocodeBean.setName(audiocodes[i]);
+                        if (str.equals(audiocodes[i])) {
+                            mAudiocodeBean.setIsst(1);
+                        } else {
+                            mAudiocodeBean.setIsst(0);
+                        }
+
+                        AudiocodeBeanList.add(mAudiocodeBean);
+                    }
+
+                    mAudiocodeAdapter.notifyDataSetChanged();
+                }
+
+                int viewWidth = view.getWidth();
+                mViewaudiocode.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
+                int height = mViewaudiocode.getMeasuredHeight(); // 获取测量后的高度
+                mPopupaudiocode = new PopupWindow(mViewaudiocode, viewWidth, height - 1);
+
+                mPopupaudiocode.setOutsideTouchable(true);
+
+                mPopupaudiocode.setOnDismissListener(new PopupWindow.OnDismissListener() {
+                    @Override
+                    public void onDismiss() {
+
+                    }
+                });
+
+                int yoff = (int) mMainActivity.getResources().getDimension(R.dimen.dp_4);
+
+                mPopupaudiocode.showAsDropDown(mStreamingSettingsFragment.mFragmentStreamingsettingsBinding.tvaudiocode, 0, yoff);
+            }
+        });
+    }
+
+    public void hidePopaudiocode() {
+        if (mPopupaudiocode != null) {
+            boolean isShowing = mPopupaudiocode.isShowing();
+            if (isShowing) {
+                mPopupaudiocode.dismiss();
+            }
+        }
+    }
+
+    public void samplingrate(View view) {
+        if (mViewsamplingrate == null) {
+            mViewsamplingrate = mLayoutInflater.inflate(R.layout.po_samplingrate, null);
+
+            rvsamplingrate = mViewsamplingrate.findViewById(R.id.rvsamplingrate);
+
+            rvsamplingrate.setLayoutManager(new GridLayoutManager(mMainActivity, 1, RecyclerView.VERTICAL, false));
+
+            SamplingrateBeanList = new ArrayList<SamplingrateBean>();
+            mSamplingrateAdapter = new SamplingrateAdapter(mMainActivity, mMainActivity, R.layout.adapter_samplingrate, SamplingrateBeanList);
+            rvsamplingrate.setAdapter(mSamplingrateAdapter);
+
+            mSamplingrateAdapter.setOnItemClickListener(new MultiItemTypeAdapter.OnItemClickListener() {
+                @Override
+                public void onItemClick(View view, RecyclerView.ViewHolder holder, int position) {
+                    String name = SamplingrateBeanList.get(position).getName();
+                    selectsamplingrate = Integer.parseInt(name);
+
+                    mStreamingSettingsFragment.mFragmentStreamingsettingsBinding.tvsamplingrate.setText(name);
+
+                    for (int i = 0; i < SamplingrateBeanList.size(); i++) {
+                        SamplingrateBean mSamplingrateBean = SamplingrateBeanList.get(i);
+                        mSamplingrateBean.setIsst(0);
+                    }
+
+                    for (int i = 0; i < SamplingrateBeanList.size(); i++) {
+                        SamplingrateBean mSamplingrateBean = SamplingrateBeanList.get(i);
+                        if (mSamplingrateBean.getName().equals(name)) {
+                            mSamplingrateBean.setIsst(1);
+                            break;
+                        }
+                    }
+
+                    mSamplingrateAdapter.notifyDataSetChanged();
+
+                    hidePopsamplingrate();
+                }
+
+                @Override
+                public boolean onItemLongClick(View view, RecyclerView.ViewHolder holder, int position) {
+                    return false;
+                }
+            });
+
+            int samplingrate = SPUtil.getSamplingrate(mMainActivity);
+            selectsamplingrate = samplingrate;
+            mStreamingSettingsFragment.mFragmentStreamingsettingsBinding.tvsamplingrate.setText("" + samplingrate);
+
+            String[] samplingrates = mMainActivity.getResources().getStringArray(R.array.samplingratearr);
+            for (int i = 0; i < samplingrates.length; i++) {
+                SamplingrateBean mSamplingrateBean = new SamplingrateBean();
+                mSamplingrateBean.setName(samplingrates[i]);
+                if (samplingrate == Integer.parseInt(samplingrates[i])) {
+                    mSamplingrateBean.setIsst(1);
+                } else {
+                    mSamplingrateBean.setIsst(0);
+                }
+
+                SamplingrateBeanList.add(mSamplingrateBean);
+            }
+
+            mSamplingrateAdapter.notifyDataSetChanged();
+        }
+
+        int viewWidth = view.getWidth();
+        mViewsamplingrate.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
+        int height = mViewsamplingrate.getMeasuredHeight(); // 获取测量后的高度
+        mPopupsamplingrate = new PopupWindow(mViewsamplingrate, viewWidth, height - 1);
+
+        mPopupsamplingrate.setOutsideTouchable(true);
+
+        mPopupsamplingrate.setOnDismissListener(new PopupWindow.OnDismissListener() {
+            @Override
+            public void onDismiss() {
+
+            }
+        });
+
+        int yoff = (int) mMainActivity.getResources().getDimension(R.dimen.dp_4);
+
+        mPopupsamplingrate.showAsDropDown(mStreamingSettingsFragment.mFragmentStreamingsettingsBinding.tvsamplingrate, 0, yoff);
+    }
+
+    public void hidePopsamplingrate() {
+        if (mPopupsamplingrate != null) {
+            boolean isShowing = mPopupsamplingrate.isShowing();
+            if (isShowing) {
+                mPopupsamplingrate.dismiss();
+            }
+        }
+    }
+
+    public void audiochannel(View view) {
+        view.post(new Runnable() {
+            @Override
+            public void run() {
+
+                if (mViewaudiochannel == null) {
+
+                    mViewaudiochannel = mLayoutInflater.inflate(R.layout.po_audiochannel, null);
+
+                    rvaudiochannel = mViewaudiochannel.findViewById(R.id.rvaudiochannel);
+
+                    rvaudiochannel.setLayoutManager(new GridLayoutManager(mMainActivity, 1, RecyclerView.VERTICAL, false));
+
+                    AudiochannelBeanList = new ArrayList<AudiochannelBean>();
+                    mAudiochannelAdapter = new AudiochannelAdapter(mMainActivity, mMainActivity, R.layout.adapter_audiochannel, AudiochannelBeanList);
+                    rvaudiochannel.setAdapter(mAudiochannelAdapter);
+
+                    mAudiochannelAdapter.setOnItemClickListener(new MultiItemTypeAdapter.OnItemClickListener() {
+                        @Override
+                        public void onItemClick(View view, RecyclerView.ViewHolder holder, int position) {
+                            String name = AudiochannelBeanList.get(position).getName();
+
+                            int value;
+                            if (name.equals("单声道")) {
+                                value = 0;
+                            } else {
+                                value = 1;
+                            }
+
+                            selectaudiochannel = value;
+
+                            mStreamingSettingsFragment.mFragmentStreamingsettingsBinding.tvaudiochannel.setText(name);
+
+                            for (int i = 0; i < AudiochannelBeanList.size(); i++) {
+                                AudiochannelBean mAudiochannelBean = AudiochannelBeanList.get(i);
+                                mAudiochannelBean.setIsst(0);
+                            }
+
+                            for (int i = 0; i < AudiochannelBeanList.size(); i++) {
+                                AudiochannelBean mAudiochannelBean = AudiochannelBeanList.get(i);
+                                if (mAudiochannelBean.getName().equals(name)) {
+                                    mAudiochannelBean.setIsst(1);
+                                    break;
+                                }
+                            }
+
+                            mAudiochannelAdapter.notifyDataSetChanged();
+
+                            hidePopaudiochannel();
+                        }
+
+                        @Override
+                        public boolean onItemLongClick(View view, RecyclerView.ViewHolder holder, int position) {
+                            return false;
+                        }
+                    });
+
+                    int audiochannel = SPUtil.getAudiochannel(mMainActivity);
+                    selectaudiochannel = audiochannel;
+                    String str = "";
+                    if (audiochannel == 0) {
+                        str = "单声道";
+                    } else {
+                        str = "立体声道";
+                    }
+
+                    String[] audiochannels = mMainActivity.getResources().getStringArray(R.array.audiochannelarr);
+                    for (int i = 0; i < audiochannels.length; i++) {
+                        AudiochannelBean mAudiochannelBean = new AudiochannelBean();
+                        mAudiochannelBean.setName(audiochannels[i]);
+                        if (str.equals(audiochannels[i])) {
+                            mAudiochannelBean.setIsst(1);
+                        } else {
+                            mAudiochannelBean.setIsst(0);
+                        }
+
+                        AudiochannelBeanList.add(mAudiochannelBean);
+                    }
+
+                    mAudiochannelAdapter.notifyDataSetChanged();
+                }
+
+                int viewWidth = view.getWidth();
+                mViewaudiochannel.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
+                int height = mViewaudiochannel.getMeasuredHeight(); // 获取测量后的高度
+                mPopupaudiochannel = new PopupWindow(mViewaudiochannel, viewWidth, height - 1);
+
+                mPopupaudiochannel.setOutsideTouchable(true);
+
+                mPopupaudiochannel.setOnDismissListener(new PopupWindow.OnDismissListener() {
+                    @Override
+                    public void onDismiss() {
+
+                    }
+                });
+
+                int yoff = (int) mMainActivity.getResources().getDimension(R.dimen.dp_4);
+
+                mPopupaudiochannel.showAsDropDown(mStreamingSettingsFragment.mFragmentStreamingsettingsBinding.tvaudiochannel, 0, yoff);
+            }
+        });
+    }
+
+    public void hidePopaudiochannel() {
+        if (mPopupaudiochannel != null) {
+            boolean isShowing = mPopupaudiochannel.isShowing();
+            if (isShowing) {
+                mPopupaudiochannel.dismiss();
+            }
+        }
+    }
+
+    public void audiocoderate(View view) {
+        view.post(new Runnable() {
+            @Override
+            public void run() {
+                if (mViewaudiocoderate == null) {
+                    mViewaudiocoderate = mLayoutInflater.inflate(R.layout.po_audiocoderate, null);
+
+                    rvaudiocoderate = mViewaudiocoderate.findViewById(R.id.rvaudiocoderate);
+
+                    rvaudiocoderate.setLayoutManager(new GridLayoutManager(mMainActivity, 1, RecyclerView.VERTICAL, false));
+
+                    AudiocoderateBeanList = new ArrayList<AudiocoderateBean>();
+                    mAudiocoderateAdapter = new AudiocoderateAdapter(mMainActivity, mMainActivity, R.layout.adapter_audiocoderate, AudiocoderateBeanList);
+                    rvaudiocoderate.setAdapter(mAudiocoderateAdapter);
+
+                    mAudiocoderateAdapter.setOnItemClickListener(new MultiItemTypeAdapter.OnItemClickListener() {
+                        @Override
+                        public void onItemClick(View view, RecyclerView.ViewHolder holder, int position) {
+                            String name = AudiocoderateBeanList.get(position).getName();
+                            selectaudiocoderate = Integer.parseInt(name);
+
+                            mStreamingSettingsFragment.mFragmentStreamingsettingsBinding.tvaudiocoderate.setText(name);
+
+                            for (int i = 0; i < AudiocoderateBeanList.size(); i++) {
+                                AudiocoderateBean mAudiocoderateBean = AudiocoderateBeanList.get(i);
+                                mAudiocoderateBean.setIsst(0);
+                            }
+
+                            for (int i = 0; i < AudiocoderateBeanList.size(); i++) {
+                                AudiocoderateBean mAudiocoderateBean = AudiocoderateBeanList.get(i);
+                                if (mAudiocoderateBean.getName().equals(name)) {
+                                    mAudiocoderateBean.setIsst(1);
+                                    break;
+                                }
+                            }
+
+                            mAudiocoderateAdapter.notifyDataSetChanged();
+
+                            hidePopaudiocoderate();
+                        }
+
+                        @Override
+                        public boolean onItemLongClick(View view, RecyclerView.ViewHolder holder, int position) {
+                            return false;
+                        }
+                    });
+
+                    int audiocoderate = SPUtil.getAudiocoderate(mMainActivity);
+                    selectaudiocoderate = audiocoderate;
+                    String[] audiocoderates = mMainActivity.getResources().getStringArray(R.array.audiocoderatearr);
+                    for (int i = 0; i < audiocoderates.length; i++) {
+                        AudiocoderateBean mAudiocoderateBean = new AudiocoderateBean();
+                        mAudiocoderateBean.setName(audiocoderates[i]);
+                        if (audiocoderate == Integer.parseInt(audiocoderates[i])) {
+                            mAudiocoderateBean.setIsst(1);
+                        } else {
+                            mAudiocoderateBean.setIsst(0);
+                        }
+
+                        AudiocoderateBeanList.add(mAudiocoderateBean);
+                    }
+
+                    mAudiocoderateAdapter.notifyDataSetChanged();
+                }
+                int viewWidth = view.getWidth();
+                mViewaudiocoderate.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
+                int height = mViewaudiocoderate.getMeasuredHeight(); // 获取测量后的高度
+                mPopupaudiocoderate = new PopupWindow(mViewaudiocoderate, viewWidth, height - 1);
+
+                mPopupaudiocoderate.setOutsideTouchable(true);
+
+                mPopupaudiocoderate.setOnDismissListener(new PopupWindow.OnDismissListener() {
+                    @Override
+                    public void onDismiss() {
+
+                    }
+                });
+
+                int yoff = (int) mMainActivity.getResources().getDimension(R.dimen.dp_4);
+
+                mPopupaudiocoderate.showAsDropDown(mStreamingSettingsFragment.mFragmentStreamingsettingsBinding.tvaudiocoderate, 0, yoff);
+            }
+        });
+    }
+
+    public void hidePopaudiocoderate() {
+        if (mPopupaudiocoderate != null) {
+            boolean isShowing = mPopupaudiocoderate.isShowing();
+            if (isShowing) {
+                mPopupaudiocoderate.dismiss();
+            }
+        }
+    }
+
+    public void locationfrequency(View view) {
+        view.post(new Runnable() {
+            @Override
+            public void run() {
+
+                if (mViewlocationfrequency == null) {
+                    mViewlocationfrequency = mLayoutInflater.inflate(R.layout.po_locationfrequency, null);
+
+                    rvlocationfrequency = mViewlocationfrequency.findViewById(R.id.rvlocationfrequency);
+
+                    rvlocationfrequency.setLayoutManager(new GridLayoutManager(mMainActivity, 1, RecyclerView.VERTICAL, false));
+
+                    LocationfrequencyBeanList = new ArrayList<LocationfrequencyBean>();
+                    mLocationfrequencyAdapter = new LocationfrequencyAdapter(mMainActivity, mMainActivity, R.layout.adapter_locationfrequency, LocationfrequencyBeanList);
+                    rvlocationfrequency.setAdapter(mLocationfrequencyAdapter);
+
+                    mLocationfrequencyAdapter.setOnItemClickListener(new MultiItemTypeAdapter.OnItemClickListener() {
+                        @Override
+                        public void onItemClick(View view, RecyclerView.ViewHolder holder, int position) {
+                            String name = LocationfrequencyBeanList.get(position).getName();
+                            selectlocationfreq = Integer.parseInt(name.substring(0, name.length() - 1));
+
+                            mStreamingSettingsFragment.mFragmentStreamingsettingsBinding.tvlocationfrequency.setText(name);
+
+                            for (int i = 0; i < LocationfrequencyBeanList.size(); i++) {
+                                LocationfrequencyBean mLocationfrequencyBean = LocationfrequencyBeanList.get(i);
+                                mLocationfrequencyBean.setIsst(0);
+                            }
+
+                            for (int i = 0; i < LocationfrequencyBeanList.size(); i++) {
+                                LocationfrequencyBean mLocationfrequencyBean = LocationfrequencyBeanList.get(i);
+                                if (mLocationfrequencyBean.getName().equals(name)) {
+                                    mLocationfrequencyBean.setIsst(1);
+                                    break;
+                                }
+                            }
+
+                            mLocationfrequencyAdapter.notifyDataSetChanged();
+
+                            hidePoplocationfrequency();
+                        }
+
+                        @Override
+                        public boolean onItemLongClick(View view, RecyclerView.ViewHolder holder, int position) {
+                            return false;
+                        }
+                    });
+
+                    int locationfreq = SPUtil.getLocationfreq(mMainActivity);
+                    selectlocationfreq = locationfreq;
+
+                    String[] locationfrequencys = mMainActivity.getResources().getStringArray(R.array.locationfrequencyarr);
+                    for (int i = 0; i < locationfrequencys.length; i++) {
+                        LocationfrequencyBean mLocationfrequencyBean = new LocationfrequencyBean();
+                        mLocationfrequencyBean.setName(locationfrequencys[i]);
+                        if (locationfreq == Integer.parseInt(locationfrequencys[i].substring(0, locationfrequencys[i].length() - 1))) {
+                            mLocationfrequencyBean.setIsst(1);
+                        } else {
+                            mLocationfrequencyBean.setIsst(0);
+                        }
+
+                        LocationfrequencyBeanList.add(mLocationfrequencyBean);
+                    }
+
+                    mLocationfrequencyAdapter.notifyDataSetChanged();
+                }
+
+                int viewWidth = view.getWidth();
+                mViewlocationfrequency.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
+                int height = mViewlocationfrequency.getMeasuredHeight(); // 获取测量后的高度
+                mPopuplocationfrequency = new PopupWindow(mViewlocationfrequency, viewWidth, height - 1);
+
+                mPopuplocationfrequency.setOutsideTouchable(true);
+
+                mPopuplocationfrequency.setOnDismissListener(new PopupWindow.OnDismissListener() {
+                    @Override
+                    public void onDismiss() {
+
+                    }
+                });
+
+                int yoff = (int) mMainActivity.getResources().getDimension(R.dimen.dp_4);
+
+                mPopuplocationfrequency.showAsDropDown(mStreamingSettingsFragment.mFragmentStreamingsettingsBinding.tvlocationfrequency, 0, yoff);
+
+            }
+        });
+    }
+
+    public void hidePoplocationfrequency() {
+        if (mPopuplocationfrequency != null) {
+            boolean isShowing = mPopuplocationfrequency.isShowing();
+            if (isShowing) {
+                mPopuplocationfrequency.dismiss();
+            }
+        }
+    }
+
+    public void save(View view) {
+        try {
+
+            MediaStream mMediaStream = mMainActivity.getMMediaStream();
+
+            Boolean change = SPUtil.getCameraid(mMainActivity) != selectcameraid;
+
+            saveData();
+
+            Boolean isStreaming = mMediaStream.isStreaming();
+
+            if (!isStreaming) {
+                mMainActivity.getMHandler().sendEmptyMessageDelayed(Constant.CHANGEDATA, 500);
+            } else {
+                AlertDialog.Builder builder = new AlertDialog.Builder(mMainActivity);
+                builder.setTitle("提示"); // 设置标题
+                builder.setMessage("正在推流中是否重新注册?"); // 设置提示内容
+
+                builder.setPositiveButton("确定", new DialogInterface.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialog, int which) {
+                        // 确定操作
+                        Toast.makeText(mMainActivity.getApplicationContext(), "保存并重新注册", Toast.LENGTH_SHORT).show();
+
+                        mMainActivity.stopLoc();  //位置监听
+
+                        mMainActivity.getMHandler().sendEmptyMessageDelayed(Constant.CHANGE_DATA_RESTART, 500);
+
+                        dialog.dismiss(); // 关闭对话框
+                    }
+                });
+
+                // 设置“取消”按钮及其点击事件
+                builder.setNegativeButton("取消", new DialogInterface.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialog, int which) {
+                        // 取消操作
+                        mMainActivity.getMHandler().sendEmptyMessageDelayed(Constant.CHANGEDATA, 500);
+                        Toast.makeText(mMainActivity.getApplicationContext(), "保存成功", Toast.LENGTH_SHORT).show();
+                        dialog.dismiss(); // 关闭对话框
+                    }
+                });
+
+                // 创建并显示对话框
+                AlertDialog dialog = builder.create();
+                dialog.show();
+            }
+
+            if (change) {
+//                mMediaStream.switchCamera(selectcameraid);
+            }
+
+
+        } catch (Exception e) {
+            Log.e(TAG, "save  Exception  " + e.toString());
+        }
+    }
+
+
+    public void saveData() {
+
+
+        SPUtil.setIsenvideo(mMainActivity, mStreamingSettingsFragment.isenvideo);
+        SPUtil.setCameraid(mMainActivity, selectcameraid);
+        SPUtil.setHevcCodec(mMainActivity, selectvideocode);
+        SPUtil.setVideoresolution(mMainActivity, selectvideoresolution);
+        SPUtil.setFramerate(mMainActivity, selectframerate);
+        SPUtil.setBitrateKbps(mMainActivity, selectvideocoderate);
+        SPUtil.setIsenaudio(mMainActivity, mStreamingSettingsFragment.isenaudio);
+        SPUtil.setAACCodec(mMainActivity, selectaudiocode);
+        SPUtil.setSamplingrate(mMainActivity, selectsamplingrate);
+        SPUtil.setAudiochannel(mMainActivity, selectaudiochannel);
+        SPUtil.setAudiocoderate(mMainActivity, selectaudiocoderate);
+        SPUtil.setIsenlocreport(mMainActivity, mStreamingSettingsFragment.isenlocreport);
+        SPUtil.setLocationfreq(mMainActivity, selectlocationfreq);
+    }
+
+
+}

+ 5 - 0
Android/app/src/main/res/anim/slide_in_up.xml

@@ -0,0 +1,5 @@
+<translate xmlns:android="http://schemas.android.com/apk/res/android"
+    android:duration="300"
+    android:fromYDelta="100%"
+    android:interpolator="@android:anim/accelerate_decelerate_interpolator"
+    android:toYDelta="0%" />

+ 5 - 0
Android/app/src/main/res/anim/slide_out_down.xml

@@ -0,0 +1,5 @@
+<translate xmlns:android="http://schemas.android.com/apk/res/android"
+    android:duration="300"
+    android:fromYDelta="0%"
+    android:interpolator="@android:anim/accelerate_decelerate_interpolator"
+    android:toYDelta="100%" />

+ 7 - 0
Android/app/src/main/res/drawable/bg_btn_grey_round_border.xml

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <corners android:radius="@dimen/dp_5" />
+    <stroke
+        android:width="@dimen/dp_2"
+        android:color="@color/color_666666" />
+</shape>

+ 7 - 0
Android/app/src/main/res/drawable/bg_btn_white_round_border.xml

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <corners android:radius="@dimen/dp_5" />
+    <stroke
+        android:width="@dimen/dp_1"
+        android:color="#dcdee2" />
+</shape>

+ 9 - 0
Android/app/src/main/res/drawable/bg_gradient_d9f4eb.xml

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape android:shape="rectangle" xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <gradient
+    android:startColor="@color/white"
+    android:endColor="@color/color_d9f4eb"
+    android:angle="90"
+    />
+</shape>

+ 5 - 0
Android/app/src/main/res/drawable/bg_rounded_53c3a3.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <solid android:color="@color/color_53c3a3" />
+    <corners android:radius="@dimen/dp_22" />
+</shape>

+ 10 - 0
Android/app/src/main/res/drawable/bg_rounded_add.xml

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <solid android:color="#ffffffff" />
+    <corners
+        android:bottomLeftRadius="22dp"
+        android:bottomRightRadius="22dp"
+        android:topLeftRadius="22dp"
+        android:topRightRadius="22dp" />
+</shape>

+ 10 - 0
Android/app/src/main/res/drawable/bg_rounded_channel.xml

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <solid android:color="#ffffffff" />
+    <corners
+        android:bottomLeftRadius="6dp"
+        android:bottomRightRadius="6dp"
+        android:topLeftRadius="6dp"
+        android:topRightRadius="6dp" />
+</shape>

+ 13 - 0
Android/app/src/main/res/drawable/bg_rounded_channel_btn.xml

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <solid android:color="#fff6f8fb" />
+    <corners
+        android:bottomLeftRadius="20dp"
+        android:bottomRightRadius="20dp"
+        android:topLeftRadius="20dp"
+        android:topRightRadius="20dp" />
+    <stroke
+        android:width="1dp"
+        android:color="#E9ECEF" />
+</shape>

+ 16 - 0
Android/app/src/main/res/drawable/bg_rounded_channel_btn_submit.xml

@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <solid android:color="#fff6f8fb" />
+    <gradient
+        android:angle="90"
+        android:endColor="#ff07e2ba"
+        android:startColor="#ff00c4a3"
+        android:type="linear"
+        android:useLevel="true" />
+    <corners
+        android:bottomLeftRadius="20dp"
+        android:bottomRightRadius="20dp"
+        android:topLeftRadius="20dp"
+        android:topRightRadius="20dp" />
+</shape>

+ 10 - 0
Android/app/src/main/res/drawable/bg_rounded_f8f8f8.xml

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    >
+<solid android:color="@color/color_f8f8f8"/>
+
+<stroke android:width="@dimen/dp_5"
+    android:color="@color/color_f8f8f8"/>
+
+<corners android:radius="@dimen/dp_5"/>
+</shape>

Деякі файли не було показано, через те що забагато файлів було змінено