Pārlūkot izejas kodu

commit Android version

EasyGBD free
Babosa 1 gadu atpakaļ
revīzija
d4985119cd
100 mainītis faili ar 4913 papildinājumiem un 0 dzēšanām
  1. 11 0
      Android/.gitignore
  2. 1 0
      Android/app/.gitignore
  3. 86 0
      Android/app/build.gradle
  4. BIN
      Android/app/libs/gson-2.1.jar
  5. BIN
      Android/app/libs/libuvccamera-release.aar
  6. 17 0
      Android/app/proguard-rules.pro
  7. 19 0
      Android/app/src/androidTest/java/org/easydarwin/device/ApplicationTest.java
  8. 81 0
      Android/app/src/androidTest/java/org/easydarwin/device/SplashActivityTest.java
  9. 67 0
      Android/app/src/main/AndroidManifest.xml
  10. BIN
      Android/app/src/main/assets/zk/SIMYOU.ttf
  11. BIN
      Android/app/src/main/ic_launcher-web.png
  12. 110 0
      Android/app/src/main/java/com/easygbs/device/AboutActivity.java
  13. 174 0
      Android/app/src/main/java/com/easygbs/device/BackgroundCameraService.java
  14. 65 0
      Android/app/src/main/java/com/easygbs/device/EasyApplication.java
  15. 250 0
      Android/app/src/main/java/com/easygbs/device/MediaFilesActivity.java
  16. 277 0
      Android/app/src/main/java/com/easygbs/device/SettingActivity.java
  17. 48 0
      Android/app/src/main/java/com/easygbs/device/SplashActivity.java
  18. 694 0
      Android/app/src/main/java/com/easygbs/device/StreamActivity.java
  19. 209 0
      Android/app/src/main/java/com/easygbs/device/UVCCameraService.java
  20. 891 0
      Android/app/src/main/java/com/easygbs/device/push/MediaStream.java
  21. 30 0
      Android/app/src/main/java/com/easygbs/device/push/PushCallback.java
  22. 75 0
      Android/app/src/main/java/com/easygbs/device/util/DataUtil.java
  23. 91 0
      Android/app/src/main/java/com/easygbs/device/util/LocalIPUtil.java
  24. 131 0
      Android/app/src/main/java/com/easygbs/device/util/SPUtil.java
  25. 32 0
      Android/app/src/main/java/com/easygbs/device/views/SquareImageView.java
  26. 9 0
      Android/app/src/main/res/anim/slide_bottom_in.xml
  27. 9 0
      Android/app/src/main/res/anim/slide_left_out.xml
  28. 9 0
      Android/app/src/main/res/anim/slide_right_in.xml
  29. 9 0
      Android/app/src/main/res/anim/slide_top_out.xml
  30. BIN
      Android/app/src/main/res/drawable-hdpi/float_button.png
  31. BIN
      Android/app/src/main/res/drawable-hdpi/ic_action_push_error.png
  32. BIN
      Android/app/src/main/res/drawable-hdpi/ic_stat_camera.png
  33. BIN
      Android/app/src/main/res/drawable-mdpi/ic_action_push_error.png
  34. BIN
      Android/app/src/main/res/drawable-mdpi/ic_stat_camera.png
  35. BIN
      Android/app/src/main/res/drawable-xhdpi/android_player_pro.png
  36. BIN
      Android/app/src/main/res/drawable-xhdpi/android_rtmp.png
  37. BIN
      Android/app/src/main/res/drawable-xhdpi/com_back.png
  38. BIN
      Android/app/src/main/res/drawable-xhdpi/green.png
  39. BIN
      Android/app/src/main/res/drawable-xhdpi/ic_action_push_error.png
  40. BIN
      Android/app/src/main/res/drawable-xhdpi/ic_action_switch_camera.png
  41. BIN
      Android/app/src/main/res/drawable-xhdpi/ic_action_switch_oritation.png
  42. BIN
      Android/app/src/main/res/drawable-xhdpi/ic_stat_camera.png
  43. BIN
      Android/app/src/main/res/drawable-xhdpi/ios_player_pro.png
  44. BIN
      Android/app/src/main/res/drawable-xhdpi/ios_rtmp.png
  45. BIN
      Android/app/src/main/res/drawable-xhdpi/new_player.png
  46. BIN
      Android/app/src/main/res/drawable-xhdpi/push_screen.png
  47. BIN
      Android/app/src/main/res/drawable-xhdpi/push_screen_click.png
  48. BIN
      Android/app/src/main/res/drawable-xhdpi/qr_scan_btn.png
  49. BIN
      Android/app/src/main/res/drawable-xhdpi/record.png
  50. BIN
      Android/app/src/main/res/drawable-xhdpi/record_pressed.png
  51. BIN
      Android/app/src/main/res/drawable-xhdpi/red.png
  52. BIN
      Android/app/src/main/res/drawable-xhdpi/settings.png
  53. BIN
      Android/app/src/main/res/drawable-xhdpi/start_push.png
  54. BIN
      Android/app/src/main/res/drawable-xhdpi/start_push_pressed.png
  55. BIN
      Android/app/src/main/res/drawable-xhdpi/yellow.png
  56. BIN
      Android/app/src/main/res/drawable-xxhdpi/ic_action_push_error.png
  57. BIN
      Android/app/src/main/res/drawable-xxhdpi/ic_stat_camera.png
  58. BIN
      Android/app/src/main/res/drawable-xxhdpi/new_player.png
  59. 74 0
      Android/app/src/main/res/drawable/ic_launcher_background.xml
  60. 15 0
      Android/app/src/main/res/drawable/recording_marker_interval_shape.xml
  61. 15 0
      Android/app/src/main/res/drawable/recording_marker_shape.xml
  62. 8 0
      Android/app/src/main/res/drawable/setting_url_shape.xml
  63. 219 0
      Android/app/src/main/res/layout/activity_about.xml
  64. 38 0
      Android/app/src/main/res/layout/activity_media_files.xml
  65. 544 0
      Android/app/src/main/res/layout/activity_setting.xml
  66. 181 0
      Android/app/src/main/res/layout/activity_stream.xml
  67. 30 0
      Android/app/src/main/res/layout/float_btn.xml
  68. 7 0
      Android/app/src/main/res/layout/fragment_media_file.xml
  69. 32 0
      Android/app/src/main/res/layout/image_picker_item.xml
  70. 34 0
      Android/app/src/main/res/layout/splash_activity.xml
  71. 10 0
      Android/app/src/main/res/layout/spn_item.xml
  72. 5 0
      Android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
  73. 5 0
      Android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
  74. BIN
      Android/app/src/main/res/mipmap-hdpi/ic_launcher.png
  75. BIN
      Android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
  76. BIN
      Android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
  77. BIN
      Android/app/src/main/res/mipmap-mdpi/ic_launcher.png
  78. BIN
      Android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
  79. BIN
      Android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
  80. BIN
      Android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
  81. BIN
      Android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
  82. BIN
      Android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
  83. BIN
      Android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
  84. BIN
      Android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
  85. BIN
      Android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
  86. BIN
      Android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
  87. BIN
      Android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
  88. BIN
      Android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
  89. 6 0
      Android/app/src/main/res/values-w820dp/dimens.xml
  90. 7 0
      Android/app/src/main/res/values/colors.xml
  91. 7 0
      Android/app/src/main/res/values/dimens.xml
  92. 4 0
      Android/app/src/main/res/values/ids.xml
  93. 9 0
      Android/app/src/main/res/values/strings.xml
  94. 18 0
      Android/app/src/main/res/values/styles.xml
  95. 4 0
      Android/app/src/main/res/values/update_strings.xml
  96. 22 0
      Android/app/src/test/java/org/easydarwin/device/ExampleUnitTest.java
  97. 46 0
      Android/build.gradle
  98. BIN
      Android/gradle/wrapper/gradle-wrapper.jar
  99. 6 0
      Android/gradle/wrapper/gradle-wrapper.properties
  100. 172 0
      Android/gradlew

+ 11 - 0
Android/.gitignore

@@ -0,0 +1,11 @@
+
+\.gradle/
+
+\.idea/
+
+build/
+
+*.iml
+
+gradle.properties
+local.properties

+ 1 - 0
Android/app/.gitignore

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

+ 86 - 0
Android/app/build.gradle

@@ -0,0 +1,86 @@
+apply plugin: 'com.android.application'
+
+android {
+    compileSdkVersion 26
+    buildToolsVersion '28.0.3'
+
+    defaultConfig {
+        applicationId "com.easygbs.device"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 10211129
+        versionName '1.0.21.1129'
+        testInstrumentationRunner 'android.support.test.runner.AndroidJUnitRunner'
+        flavorDimensions "versionCode"
+
+        ndk {
+            //设置支持的SO库架构
+            abiFilters 'armeabi', 'x86', 'armeabi-v7a', 'x86_64'
+        }
+    }
+
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+
+    dataBinding {
+        enabled = true
+    }
+
+    android.applicationVariants.all { variant ->
+        variant.outputs.all {
+            outputFileName = "EasyGBD-Android-" + variant.versionCode + "-" + variant.versionName + ".apk"
+        }
+    }
+
+    compileOptions {
+        sourceCompatibility = '1.8'
+        targetCompatibility = '1.8'
+    }
+}
+
+repositories {
+    flatDir {
+        dirs 'libs'
+    }
+    mavenCentral()
+    google()
+}
+
+dependencies {
+    implementation fileTree(include: ['*.jar'], dir: 'libs')
+
+    testImplementation 'junit:junit:4.12'
+
+    implementation 'com.android.support:appcompat-v7:26.1.0'
+    implementation 'com.android.support:support-v4:26.1.0'
+    implementation 'com.android.support:preference-v7:26.1.0'
+    implementation 'com.android.support:design:26.1.0'
+    implementation 'com.squareup.okhttp3:okhttp:3.4.1'
+    implementation 'com.android.support.constraint:constraint-layout:1.1.2'
+    implementation 'com.github.bumptech.glide:glide:3.7.0'
+    implementation 'com.android.support:cardview-v7:26.1.0'
+    implementation 'com.github.chrisbanes:PhotoView:1.3.0'
+    implementation 'com.squareup:otto:1.3.8'
+
+    androidTestImplementation 'com.android.support.test.espresso:espresso-core:2.2.2', {
+        exclude group: 'com.android.support', module: 'support-annotations'
+    }
+
+    implementation project(':library')
+
+    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"
+
+    implementation(name: 'libuvccamera-release', ext: 'aar') {
+        exclude module: 'support-v4'
+        exclude module: 'appcompat-v7'
+    }
+}

BIN
Android/app/libs/gson-2.1.jar


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


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

@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in F:\01Tools\Android\Android_SDK/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# 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 *;
+#}

+ 19 - 0
Android/app/src/androidTest/java/org/easydarwin/device/ApplicationTest.java

@@ -0,0 +1,19 @@
+/*
+	Copyright (c) 2013-2016 EasyDarwin.ORG.  All rights reserved.
+	Github: https://github.com/EasyDarwin
+	WEChat: EasyDarwin
+	Website: http://www.easydarwin.org
+*/
+package org.easydarwin.device;
+
+import android.app.Application;
+import android.test.ApplicationTestCase;
+
+/**
+ * <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
+ */
+public class ApplicationTest extends ApplicationTestCase<Application> {
+    public ApplicationTest() {
+        super(Application.class);
+    }
+}

+ 81 - 0
Android/app/src/androidTest/java/org/easydarwin/device/SplashActivityTest.java

@@ -0,0 +1,81 @@
+package org.easydarwin.device;
+
+
+import android.support.test.espresso.ViewInteraction;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.LargeTest;
+
+import com.easygbs.device.SplashActivity;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.Espresso.pressBack;
+import static android.support.test.espresso.action.ViewActions.click;
+import static android.support.test.espresso.action.ViewActions.scrollTo;
+import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+import static android.support.test.espresso.matcher.ViewMatchers.withParent;
+import static android.support.test.espresso.matcher.ViewMatchers.withText;
+import static org.hamcrest.Matchers.allOf;
+
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class SplashActivityTest {
+
+    @Rule
+    public ActivityTestRule<SplashActivity> mActivityTestRule = new ActivityTestRule<>(SplashActivity.class);
+
+    @Test
+    public void splashActivityTest() {
+        // Added a sleep statement to match the app's execution delay.
+        // The recommended way to handle such scenarios is to use Espresso idling resources:
+        // https://google.github.io/android-testing-support-library/docs/espresso/idling-resource/index.html
+
+
+        ViewInteraction appCompatButton = onView(
+                allOf(withId(R.id.btn_switch), withText("推送"), isDisplayed()));
+        appCompatButton.perform(click());
+
+        ViewInteraction appCompatButton2 = onView(
+                allOf(withId(R.id.btn_setting), withText("设置"), isDisplayed()));
+        appCompatButton2.perform(click());
+
+        pressBack();
+
+        ViewInteraction appCompatCheckBox = onView(
+                allOf(withId(R.id.only_push_audio), withText("仅推送音频")));
+        appCompatCheckBox.perform(scrollTo(), click());
+
+        ViewInteraction appCompatButton3 = onView(
+                allOf(withId(R.id.btn_save), withText("保存")));
+        appCompatButton3.perform(scrollTo(), click());
+
+        ViewInteraction appCompatButton4 = onView(
+                allOf(withId(R.id.btn_setting), withText("设置"), isDisplayed()));
+        appCompatButton4.perform(click());
+
+        pressBack();
+
+        ViewInteraction appCompatCheckBox2 = onView(
+                allOf(withId(R.id.only_push_audio), withText("仅推送音频")));
+        appCompatCheckBox2.perform(scrollTo(), click());
+
+        ViewInteraction appCompatButton5 = onView(
+                allOf(withId(R.id.btn_save), withText("保存")));
+        appCompatButton5.perform(scrollTo(), click());
+
+        pressBack();
+
+        ViewInteraction appCompatButton6 = onView(
+                allOf(withId(android.R.id.button2), withText("取消"),
+                        withParent(allOf(withId(R.id.buttonPanel),
+                                withParent(withId(R.id.parentPanel)))),
+                        isDisplayed()));
+        appCompatButton6.perform(click());
+
+    }
+
+}

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

@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.easygbs.device">
+
+    <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-feature android:name="android.hardware.camera" />
+
+    <application
+        android:name=".EasyApplication"
+        android:allowBackup="true"
+        android:icon="@mipmap/ic_launcher_foreground"
+        android:label="@string/app_name"
+        android:supportsRtl="true"
+        android:theme="@style/AppTheme">
+
+        <activity
+            android:name=".SplashActivity"
+            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=".StreamActivity"
+            android:launchMode="singleInstance"
+            android:screenOrientation="portrait"></activity>
+
+        <activity android:name=".AboutActivity" />
+
+        <activity
+            android:name=".SettingActivity"
+            android:screenOrientation="portrait" />
+
+        <activity
+            android:name=".MediaFilesActivity"
+            android:label="文件夹" />
+
+        <service
+            android:name=".BackgroundCameraService"
+            android:enabled="true" />
+
+        <service
+            android:name=".UVCCameraService"
+            android:enabled="true" />
+
+    </application>
+
+</manifest>

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


BIN
Android/app/src/main/ic_launcher-web.png


+ 110 - 0
Android/app/src/main/java/com/easygbs/device/AboutActivity.java

@@ -0,0 +1,110 @@
+package com.easygbs.device;
+
+import android.databinding.DataBindingUtil;
+import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.Toolbar;
+import android.text.SpannableString;
+import android.text.Spanned;
+import android.text.method.LinkMovementMethod;
+import android.text.style.ForegroundColorSpan;
+import android.text.style.URLSpan;
+import android.view.Menu;
+import android.view.MenuItem;
+
+import com.easygbs.device.databinding.ActivityAboutBinding;
+
+/**
+ * 关于我们
+ * */
+public class AboutActivity extends AppCompatActivity implements Toolbar.OnMenuItemClickListener {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        ActivityAboutBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_about);
+
+        setSupportActionBar(binding.mainToolbar);
+        getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+        binding.mainToolbar.setOnMenuItemClickListener(this);
+        // 左边的小箭头(注意需要在setSupportActionBar(toolbar)之后才有效果)
+        binding.mainToolbar.setNavigationIcon(R.drawable.com_back);
+
+        binding.version.setText("EasyRTMP Android 推流器");
+        binding.version.append("(");
+
+        SpannableString spannableString;
+        if (EasyApplication.activeDays >= 9999) {
+            spannableString = new SpannableString("激活码永久有效");
+            spannableString.setSpan(new ForegroundColorSpan(getResources().getColor(R.color.colorGREEN)),
+                    0,
+                    spannableString.length(),
+                    Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+        } else if (EasyApplication.activeDays > 0) {
+            spannableString = new SpannableString(String.format("激活码还剩%d天可用", EasyApplication.activeDays));
+            spannableString.setSpan(new ForegroundColorSpan(getResources().getColor(R.color.colorYELLOW)),
+                    0,
+                    spannableString.length(),
+                    Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+        } else {
+            spannableString = new SpannableString(String.format("激活码已过期(%d)", EasyApplication.activeDays));
+            spannableString.setSpan(new ForegroundColorSpan(getResources().getColor(R.color.colorRED)),
+                    0,
+                    spannableString.length(),
+                    Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+        }
+
+        binding.version.append(spannableString);
+        binding.version.append(")");
+
+        binding.serverTitle.setText("EasyDSS RTMP流媒体服务器:\n");
+        binding.serverTitle.setMovementMethod(LinkMovementMethod.getInstance());
+
+        spannableString = new SpannableString("http://www.easydss.com");
+        //设置下划线文字
+        spannableString.setSpan(new URLSpan("http://www.easydss.com"), 0, spannableString.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+
+        //设置文字的前景色
+        spannableString.setSpan(new ForegroundColorSpan(getResources().getColor(R.color.colorTheme)),
+                0,
+                spannableString.length(),
+                Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+
+        binding.serverTitle.append(spannableString);
+
+        binding.playerTitle.setText("EasyPlayerPro全功能播放器:\n");
+        binding.playerTitle.setMovementMethod(LinkMovementMethod.getInstance());
+        spannableString = new SpannableString("https://github.com/EasyDSS/EasyPlayerPro");
+        //设置下划线文字
+        spannableString.setSpan(new URLSpan("https://github.com/EasyDSS/EasyPlayerPro"), 0, spannableString.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+
+        //设置文字的前景色
+        spannableString.setSpan(new ForegroundColorSpan(getResources().getColor(R.color.colorTheme)),
+                0,
+                spannableString.length(),
+                Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+
+        binding.playerTitle.append(spannableString);
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        return true;
+    }
+
+    @Override
+    public boolean onMenuItemClick(MenuItem menuItem) {
+        return false;
+    }
+
+    // 返回的功能
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        if (item.getItemId() == android.R.id.home) {
+            finish();
+            return true;
+        }
+
+        return super.onOptionsItemSelected(item);
+    }
+}

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

@@ -0,0 +1,174 @@
+package com.easygbs.device;
+
+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.support.v4.app.NotificationCompat;
+import android.view.Gravity;
+import android.view.TextureView;
+import android.view.WindowManager;
+
+import com.easygbs.device.push.MediaStream;
+
+public class BackgroundCameraService extends Service implements TextureView.SurfaceTextureListener {
+    private static final int NOTIFICATION_ID = 1;
+
+    /**
+     * 表示后台是否正在渲染
+     */
+    private TextureView mOutComeVideoView;
+    private WindowManager mWindowManager;
+    private MediaStream mMediaStream;
+
+    // Binder given to clients
+    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, StreamActivity.class);
+        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
+        Notification notification = new NotificationCompat.Builder(this, EasyApplication.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;
+    }
+}

+ 65 - 0
Android/app/src/main/java/com/easygbs/device/EasyApplication.java

@@ -0,0 +1,65 @@
+package com.easygbs.device;
+
+import android.app.Application;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.content.res.AssetManager;
+import android.os.Build;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class EasyApplication extends Application {
+
+    public static final String CHANNEL_CAMERA = "camera";
+
+    private static EasyApplication mApplication;
+    public static int activeDays = 9999;
+
+    public static EasyApplication getEasyApplication() {
+        return mApplication;
+    }
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+
+        mApplication = this;
+
+        File youyuan = getFileStreamPath("SIMYOU.ttf");
+        if (!youyuan.exists()) {
+            AssetManager am = getAssets();
+
+            try {
+                InputStream is = am.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 (IOException e) {
+                e.printStackTrace();
+            }
+        }
+
+        // Create the NotificationChannel, but only on API 26+ because
+        // the NotificationChannel class is new and not in the support library
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+            CharSequence name = getString(R.string.camera);
+
+            int importance = NotificationManager.IMPORTANCE_HIGH;
+            NotificationChannel channel = new NotificationChannel(CHANNEL_CAMERA, name, importance);
+            // Register the channel with the system; you can't change the importance
+            // or other notification behaviors after this
+            NotificationManager notificationManager = getSystemService(NotificationManager.class);
+            notificationManager.createNotificationChannel(channel);
+        }
+    }
+}

+ 250 - 0
Android/app/src/main/java/com/easygbs/device/MediaFilesActivity.java

@@ -0,0 +1,250 @@
+package com.easygbs.device;
+
+import android.content.ActivityNotFoundException;
+import android.content.Intent;
+import android.databinding.DataBindingUtil;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentPagerAdapter;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.view.ActionMode;
+import android.support.v7.widget.GridLayoutManager;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.Toolbar;
+import android.text.TextUtils;
+import android.util.SparseArray;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.ImageView;
+import android.widget.Toast;
+
+import com.bumptech.glide.Glide;
+
+import com.easygbs.device.databinding.ActivityMediaFilesBinding;
+import com.easygbs.device.databinding.FragmentMediaFileBinding;
+import com.easygbs.device.databinding.ImagePickerItemBinding;
+import com.easygbs.device.util.DataUtil;
+
+import java.io.File;
+import java.io.FilenameFilter;
+
+/**
+ * 录像 / 抓拍
+ * */
+public class MediaFilesActivity extends AppCompatActivity implements Toolbar.OnMenuItemClickListener {
+
+    private ActivityMediaFilesBinding mDataBinding;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mDataBinding = DataBindingUtil.setContentView(this, R.layout.activity_media_files);
+
+        setSupportActionBar(mDataBinding.mainToolbar);
+        getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+        mDataBinding.mainToolbar.setOnMenuItemClickListener(this);
+        // 左边的小箭头(注意需要在setSupportActionBar(toolbar)之后才有效果)
+        mDataBinding.mainToolbar.setNavigationIcon(R.drawable.com_back);
+
+        mDataBinding.viewPager.setAdapter(new FragmentPagerAdapter(getSupportFragmentManager()) {
+            @Override
+            public int getCount() {
+                return 1;
+            }
+
+            public Fragment getItem(int position) {
+                Bundle args = new Bundle();
+                args.putBoolean(LocalFileFragment.KEY_IS_RECORD, position == 0);
+                return Fragment.instantiate(MediaFilesActivity.this, LocalFileFragment.class.getName(), args);
+            }
+
+            @Override
+            public CharSequence getPageTitle(int position) {
+                return "";
+            }
+        });
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        return true;
+    }
+
+    @Override
+    public boolean onMenuItemClick(MenuItem menuItem) {
+        return false;
+    }
+
+    // 返回的功能
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        if (item.getItemId() == android.R.id.home) {
+            finish();
+            return true;
+        }
+
+        return super.onOptionsItemSelected(item);
+    }
+
+    public static class LocalFileFragment extends Fragment implements CompoundButton.OnCheckedChangeListener, View.OnClickListener {
+        public static final String KEY_IS_RECORD = "key_last_selection";
+
+        private boolean mShowMp4File;
+        private ActionMode mActionMode;
+        private FragmentMediaFileBinding mBinding;
+
+        SparseArray<Boolean> mImageChecked;
+
+        private String mSuffix;
+        File mRoot = null;
+        File[] mSubFiles;
+        int mImgHeight;
+
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            setHasOptionsMenu(false);
+
+            mImageChecked = new SparseArray<>();
+
+            mShowMp4File = getArguments().getBoolean(KEY_IS_RECORD);
+            mSuffix = mShowMp4File ? ".mp4" : ".jpg";
+            File easyPusher = new File(DataUtil.recordPath());
+            easyPusher.mkdir();
+            mRoot = easyPusher;
+
+            File[] subFiles = mRoot.listFiles(new FilenameFilter() {
+                @Override
+                public boolean accept(File dir, String filename) {
+                    return filename.endsWith(mSuffix);
+                }
+            });
+
+            if (subFiles == null)
+                subFiles = new File[0];
+
+            mSubFiles = subFiles;
+            mImgHeight = (int) (getResources().getDisplayMetrics().density * 100 + 0.5f);
+        }
+
+        @Override
+        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+            mBinding = DataBindingUtil.inflate(inflater, R.layout.fragment_media_file, container, false);
+            return mBinding.getRoot();
+        }
+
+        @Override
+        public void onActivityCreated(@Nullable Bundle savedInstanceState) {
+            super.onActivityCreated(savedInstanceState);
+            GridLayoutManager layoutManager = new GridLayoutManager(getContext(), 3);
+            layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
+
+            mBinding.recycler.setLayoutManager(layoutManager);
+            mBinding.recycler.setAdapter(new RecyclerView.Adapter() {
+                @Override
+                public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+                    ImagePickerItemBinding binding = DataBindingUtil.inflate(LayoutInflater.from(getContext()), R.layout.image_picker_item, parent, false);
+                    return new ImageItemHolder(binding);
+                }
+
+                @Override
+                public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
+                    ImageItemHolder holder = (ImageItemHolder) viewHolder;
+                    holder.mCheckBox.setOnCheckedChangeListener(null);
+                    holder.mCheckBox.setChecked(mImageChecked.get(position, false));
+                    holder.mCheckBox.setOnCheckedChangeListener(LocalFileFragment.this);
+                    holder.mCheckBox.setTag(R.id.click_tag, holder);
+                    holder.mImage.setTag(R.id.click_tag, holder);
+
+                    if (mShowMp4File) {
+                        holder.mPlayImage.setVisibility(View.VISIBLE);
+                    } else {
+                        holder.mPlayImage.setVisibility(View.GONE);
+                    }
+
+                    Glide.with(getContext())
+                            .load(mSubFiles[position])
+                            .into(holder.mImage);
+                }
+
+                @Override
+                public int getItemCount() {
+                    return mSubFiles.length;
+                }
+            });
+        }
+
+        @Override
+        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+            ImageItemHolder holder = (ImageItemHolder) buttonView.getTag(R.id.click_tag);
+            int position = holder.getAdapterPosition();
+
+            if (mActionMode != null) {
+                mActionMode.invalidate();
+            }
+        }
+
+        @Override
+        public void onClick(View v) {
+            ImageItemHolder holder = (ImageItemHolder) v.getTag(R.id.click_tag);
+
+            if (holder.getAdapterPosition() == RecyclerView.NO_POSITION) {
+                return;
+            }
+
+            final String path = mSubFiles[holder.getAdapterPosition()].getPath();
+
+            if (TextUtils.isEmpty(path)) {
+                Toast.makeText(getContext(), "文件不存在", Toast.LENGTH_SHORT).show();
+                return;
+            }
+
+            if (path.endsWith(".mp4")) {
+                try {
+                    File f = new File(path);
+                    Uri uri = Uri.fromFile(f);
+
+                    Intent intent = new Intent(Intent.ACTION_VIEW);
+                    intent.setDataAndType(uri, "video/*");
+                    startActivity(intent);
+                } catch (ActivityNotFoundException e) {
+                    e.printStackTrace();
+                }
+//            } else if (path.endsWith(".jpg")) {
+//                try {
+//                    Intent intent = new Intent();
+//                    intent.setAction(Intent.ACTION_VIEW);
+//
+//                    Uri fileUri = FileProvider.getUriForFile(getContext(), getString(R.string.org_easydarwin_update_authorities), mSubFiles[holder.getAdapterPosition()]);
+//                    intent.setDataAndType(fileUri,"image/*");
+//                    startActivity(intent);
+//                } catch (ActivityNotFoundException e) {
+//                    e.printStackTrace();
+//                }
+            }
+        }
+
+        class ImageItemHolder extends RecyclerView.ViewHolder {
+            public final CheckBox mCheckBox;
+            public final ImageView mImage;
+            public final ImageView mPlayImage;
+
+            public ImageItemHolder(ImagePickerItemBinding binding) {
+                super(binding.getRoot());
+
+                mCheckBox = binding.imageCheckbox;
+                mImage = binding.imageIcon;
+                mPlayImage = binding.imagePlay;
+                mImage.setOnClickListener(LocalFileFragment.this);
+            }
+        }
+    }
+}

+ 277 - 0
Android/app/src/main/java/com/easygbs/device/SettingActivity.java

@@ -0,0 +1,277 @@
+/*
+	Copyright (c) 2013-2016 EasyDarwin.ORG.  All rights reserved.
+	Github: https://github.com/EasyDarwin
+	WEChat: EasyDarwin
+	Website: http://www.easydarwin.org
+*/
+
+package com.easygbs.device;
+
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.databinding.DataBindingUtil;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.support.v7.app.AlertDialog;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.Toolbar;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.RadioButton;
+import android.widget.RadioGroup;
+import android.widget.SeekBar;
+import android.widget.TextView;
+
+import com.easygbs.device.databinding.ActivitySettingBinding;
+import com.easygbs.device.util.DataUtil;
+import com.easygbs.device.util.SPUtil;
+
+import org.easydarwin.util.SIP;
+
+/**
+ * 设置页
+ */
+public class SettingActivity extends AppCompatActivity implements Toolbar.OnMenuItemClickListener {
+
+    public static final int REQUEST_OVERLAY_PERMISSION = 1004;  // 悬浮框
+
+    private ActivitySettingBinding binding;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        binding = DataBindingUtil.setContentView(this, R.layout.activity_setting);
+
+        setSupportActionBar(binding.mainToolbar);
+        getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+        binding.mainToolbar.setOnMenuItemClickListener(this);
+        // 左边的小箭头(注意需要在setSupportActionBar(toolbar)之后才有效果)
+        binding.mainToolbar.setNavigationIcon(R.drawable.com_back);
+
+        SIP sip = DataUtil.getSIP();
+        binding.sipServerIp.setText(sip.getServerIp());
+        binding.sipServerPort.setText(String.valueOf(sip.getServerPort()));
+        binding.localSipPort.setText(String.valueOf(sip.getLocalSipPort()));
+        binding.sipServerId.setText(sip.getServerId());
+        binding.sipServerDomain.setText(sip.getServerDomain());
+        binding.sipDeviceId.setText(sip.getDeviceId());
+        binding.sipPassword.setText(sip.getPassword());
+        binding.sipRegExpires.setText(String.valueOf(sip.getRegExpires()));
+        binding.sipHeartbeatInterval.setText(String.valueOf(sip.getHeartbeatInterval()));
+        binding.sipHeartbeatCount.setText(String.valueOf(sip.getHeartbeatCount()));
+
+        if (sip.getProtocol() == SIP.ProtocolEnum.UDP.getValue()) {
+            binding.sipProtocolUdp.setChecked(true);
+        } else {
+            binding.sipProtocolTcp.setChecked(true);
+        }
+
+//        // 使能摄像头后台采集
+//        CheckBox backgroundPushing = (CheckBox) findViewById(R.id.enable_background_camera_pushing);
+//        backgroundPushing.setChecked(SPUtil.getEnableBackgroundCamera(this));
+//        backgroundPushing.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+//            @Override
+//            public void onCheckedChanged(final CompoundButton buttonView, boolean isChecked) {
+//                if (isChecked) {
+//                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+//                        if (Settings.canDrawOverlays(SettingActivity.this)) {
+//                            SPUtil.setEnableBackgroundCamera(SettingActivity.this, true);
+//                        } else {
+//                            new AlertDialog
+//                                    .Builder(SettingActivity.this)
+//                                    .setTitle("后台上传视频")
+//                                    .setMessage("后台上传视频需要APP出现在顶部.是否确定?")
+//                                    .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+//                                        @Override
+//                                        public void onClick(DialogInterface dialogInterface, int i) {
+//                                            // 在Android 6.0后,Android需要动态获取权限,若没有权限,提示获取.
+//                                            final Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + BuildConfig.APPLICATION_ID));
+//                                            startActivityForResult(intent, REQUEST_OVERLAY_PERMISSION);
+//                                        }
+//                                    }).setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
+//                                        @Override
+//                                        public void onClick(DialogInterface dialogInterface, int i) {
+//                                            SPUtil.setEnableBackgroundCamera(SettingActivity.this, false);
+//                                            buttonView.toggle();
+//                                        }
+//                                    })
+//                                    .setCancelable(false)
+//                                    .show();
+//                        }
+//                    } else {
+//                        SPUtil.setEnableBackgroundCamera(SettingActivity.this, true);
+//                    }
+//                } else {
+//                    SPUtil.setEnableBackgroundCamera(SettingActivity.this, false);
+//                }
+//            }
+//        });
+
+        // 是否使用软编码
+        CheckBox x264enc = findViewById(R.id.use_x264_encode);
+        x264enc.setChecked(SPUtil.getswCodec(this));
+        x264enc.setOnCheckedChangeListener(
+                (buttonView, isChecked) -> SPUtil.setswCodec(this, isChecked)
+        );
+
+//        // 使能H.265编码
+//        CheckBox enable_hevc_cb = findViewById(R.id.enable_hevc);
+//        enable_hevc_cb.setChecked(SPUtil.getHevcCodec(this));
+//        enable_hevc_cb.setOnCheckedChangeListener(
+//                (buttonView, isChecked) -> SPUtil.setHevcCodec(this, isChecked)
+//        );
+
+        // 使能H.265编码
+        CheckBox enable_aac_cb = findViewById(R.id.enable_aac);
+        enable_aac_cb.setChecked(SPUtil.getAACCodec(this));
+        enable_aac_cb.setOnCheckedChangeListener(
+                (buttonView, isChecked) -> SPUtil.setAACCodec(this, isChecked)
+        );
+
+        // 叠加水印
+        CheckBox enable_video_overlay = findViewById(R.id.enable_video_overlay);
+        enable_video_overlay.setChecked(SPUtil.getEnableVideoOverlay(this));
+        enable_video_overlay.setOnCheckedChangeListener(
+                (buttonView, isChecked) -> SPUtil.setEnableVideoOverlay(this, isChecked)
+        );
+
+        // 推送内容
+        RadioGroup push_content = findViewById(R.id.push_content);
+
+        boolean videoEnable = SPUtil.getEnableVideo(this);
+        if (videoEnable) {
+            boolean audioEnable = SPUtil.getEnableAudio(this);
+
+            if (audioEnable) {
+                RadioButton push_av = findViewById(R.id.push_av);
+                push_av.setChecked(true);
+            } else {
+                RadioButton push_v = findViewById(R.id.push_v);
+                push_v.setChecked(true);
+            }
+        } else {
+            RadioButton push_a = findViewById(R.id.push_a);
+            push_a.setChecked(true);
+        }
+
+        push_content.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
+            @Override
+            public void onCheckedChanged(RadioGroup group, int checkedId) {
+                if (checkedId == R.id.push_av) {
+                    SPUtil.setEnableVideo(SettingActivity.this, true);
+                    SPUtil.setEnableAudio(SettingActivity.this, true);
+                } else if (checkedId == R.id.push_a) {
+                    SPUtil.setEnableVideo(SettingActivity.this, false);
+                    SPUtil.setEnableAudio(SettingActivity.this, true);
+                } else if (checkedId == R.id.push_v) {
+                    SPUtil.setEnableVideo(SettingActivity.this, true);
+                    SPUtil.setEnableAudio(SettingActivity.this, false);
+                }
+            }
+        });
+
+        SeekBar sb = findViewById(R.id.bitrate_seekbar);
+        final TextView bitrateValue = findViewById(R.id.bitrate_value);
+
+        int bitrate_added_kbps = SPUtil.getBitrateKbps(this);
+        int kbps = 72000 + bitrate_added_kbps;
+        bitrateValue.setText(kbps / 1000 + "kbps");
+
+        sb.setMax(5000000);
+        sb.setProgress(bitrate_added_kbps);
+        sb.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
+            @Override
+            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+                int kbps = 72000 + progress;
+                bitrateValue.setText(kbps / 1000 + "kbps");
+            }
+
+            @Override
+            public void onStartTrackingTouch(SeekBar seekBar) {
+
+            }
+
+            @Override
+            public void onStopTrackingTouch(SeekBar seekBar) {
+                SPUtil.setBitrateKbps(SettingActivity.this, seekBar.getProgress());
+            }
+        });
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+
+        SIP sip = new SIP();
+        sip.setServerIp(binding.sipServerIp.getText().toString());
+        sip.setServerId(binding.sipServerId.getText().toString());
+        sip.setServerDomain(binding.sipServerDomain.getText().toString());
+        sip.setDeviceId(binding.sipDeviceId.getText().toString());
+        sip.setPassword(binding.sipPassword.getText().toString());
+        sip.setServerPort(Integer.parseInt(binding.sipServerPort.getText().toString()));
+        sip.setLocalSipPort(Integer.parseInt(binding.localSipPort.getText().toString()));
+        sip.setRegExpires(Integer.parseInt(binding.sipRegExpires.getText().toString()));
+        sip.setHeartbeatInterval(Integer.parseInt(binding.sipHeartbeatInterval.getText().toString()));
+        sip.setHeartbeatCount(Integer.parseInt(binding.sipHeartbeatCount.getText().toString()));
+
+        if (binding.sipProtocolUdp.isChecked()) {
+            sip.setProtocol(SIP.ProtocolEnum.UDP.getValue());
+        } else {
+            sip.setProtocol(SIP.ProtocolEnum.TCP.getValue());
+        }
+
+        DataUtil.setSIP(sip);
+    }
+
+    /**
+     * 本地录像
+     */
+    public void onOpenLocalRecord(View view) {
+        Intent intent = new Intent(this, MediaFilesActivity.class);
+        startActivityForResult(intent, 0);
+        overridePendingTransition(R.anim.slide_right_in, R.anim.slide_left_out);
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+
+//        if (requestCode == REQUEST_OVERLAY_PERMISSION) {
+//            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+//                boolean canDraw = Settings.canDrawOverlays(this);
+//                SPUtil.setEnableBackgroundCamera(SettingActivity.this, canDraw);
+//
+//                if (!canDraw) {
+//                    CheckBox backgroundPushing = (CheckBox) findViewById(R.id.enable_background_camera_pushing);
+//                    backgroundPushing.setChecked(false);
+//                }
+//            }
+//        }
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        return true;
+    }
+
+    @Override
+    public boolean onMenuItemClick(MenuItem menuItem) {
+        return false;
+    }
+
+    // 返回的功能
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        if (item.getItemId() == android.R.id.home) {
+            finish();
+            return true;
+        }
+
+        return super.onOptionsItemSelected(item);
+    }
+}

+ 48 - 0
Android/app/src/main/java/com/easygbs/device/SplashActivity.java

@@ -0,0 +1,48 @@
+/*
+	Copyright (c) 2013-2016 EasyDarwin.ORG.  All rights reserved.
+	Github: https://github.com/EasyDarwin
+	WEChat: EasyDarwin
+	Website: http://www.easydarwin.org
+*/
+package com.easygbs.device;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.view.WindowManager;
+import android.widget.TextView;
+
+/**
+ * 启动页
+ * */
+public class SplashActivity extends Activity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.splash_activity);
+
+        getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); //隐藏状态栏
+
+        new Handler().postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                startActivity(new Intent(SplashActivity.this, StreamActivity.class));
+                SplashActivity.this.finish();
+            }
+        }, 2000);
+
+        String versionName;
+
+        try {
+            versionName = getPackageManager().getPackageInfo(getPackageName(), 0).versionName;
+        } catch (PackageManager.NameNotFoundException e) {
+            versionName = getResources().getString(R.string.version);
+        }
+
+        TextView txtVersion = (TextView) findViewById(R.id.txt_version);
+        txtVersion.setText(String.format("v%s", versionName));
+    }
+}

+ 694 - 0
Android/app/src/main/java/com/easygbs/device/StreamActivity.java

@@ -0,0 +1,694 @@
+/*
+	Copyright (c) 2013-2016 EasyDarwin.ORG.  All rights reserved.
+	Github: https://github.com/EasyDarwin
+	WEChat: EasyDarwin
+	Website: http://www.easydarwin.org
+*/
+
+package com.easygbs.device;
+
+import android.Manifest;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.graphics.SurfaceTexture;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.support.v4.app.ActivityCompat;
+import android.support.v7.app.AppCompatActivity;
+import android.view.Surface;
+import android.view.TextureView;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.ImageView;
+import android.widget.Spinner;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.easygbs.device.push.MediaStream;
+import com.easygbs.device.push.PushCallback;
+import com.easygbs.device.util.DataUtil;
+import com.easygbs.device.util.SPUtil;
+import com.squareup.otto.Subscribe;
+
+import org.easydarwin.bus.StartRecord;
+import org.easydarwin.bus.StopRecord;
+import org.easydarwin.bus.StreamStat;
+import org.easydarwin.bus.SupportResolution;
+
+import org.easydarwin.util.BUSUtil;
+import org.easydarwin.util.Util;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
+/**
+ * 预览+推流等主页
+ */
+public class StreamActivity extends AppCompatActivity implements View.OnClickListener, TextureView.SurfaceTextureListener {
+    public static final int EXTERNAL_STORAGE_REQ_CODE = 10;
+    public static final int REQUEST_CAMERA_PERMISSION = 1003;
+    public static final int REQUEST_STORAGE_PERMISSION = 1004;
+
+    private ImageView startPush;
+    // 默认分辨率
+    int width = 1280, height = 720;
+
+    ImageView btnSwitchCemera;
+    Spinner spnResolution;
+    TextView txtStatus, streamStat;
+    TextView textRecordTick;
+
+    List<String> listResolution = new ArrayList<>();
+
+    MediaStream mMediaStream;
+
+    private BackgroundCameraService mService;
+    private ServiceConnection conn;
+
+    private boolean mNeedGrantedPermission;
+
+    private static final String STATE = "state";
+    private static final int MSG_STATE = 1;
+
+    public static long mRecordingBegin;
+    public static boolean mRecording;
+
+    private long mExitTime;//声明一个long类型变量:用于存放上一点击“返回键”的时刻
+
+    // 录像时的线程
+    private Runnable mRecordTickRunnable = new Runnable() {
+        @Override
+        public void run() {
+            long duration = System.currentTimeMillis() - mRecordingBegin;
+            duration /= 1000;
+
+            textRecordTick.setText(String.format("%02d:%02d", duration / 60, (duration) % 60));
+
+            if (duration % 2 == 0) {
+                textRecordTick.setCompoundDrawablesWithIntrinsicBounds(R.drawable.recording_marker_shape, 0, 0, 0);
+            } else {
+                textRecordTick.setCompoundDrawablesWithIntrinsicBounds(R.drawable.recording_marker_interval_shape, 0, 0, 0);
+            }
+
+            textRecordTick.removeCallbacks(this);
+            textRecordTick.postDelayed(this, 1000);
+        }
+    };
+
+    Handler handler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_STATE:
+                    String state = msg.getData().getString("state");
+                    txtStatus.setText(state);
+                    break;
+            }
+        }
+    };
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        // 全屏
+        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
+
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_stream);
+        startPush = findViewById(R.id.streaming_activity_push);
+
+        BUSUtil.BUS.register(this);
+
+        notifyAboutColorChange();
+
+        // 动态获取camera和audio权限
+        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PERMISSION_GRANTED
+                || ActivityCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PERMISSION_GRANTED) {
+            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO}, REQUEST_CAMERA_PERMISSION);
+            mNeedGrantedPermission = true;
+            return;
+        } else {
+            // resume
+        }
+
+        int permission = ActivityCompat.checkSelfPermission(this,
+                Manifest.permission.WRITE_EXTERNAL_STORAGE);
+
+        if (permission != PackageManager.PERMISSION_GRANTED) {
+            // 请求权限
+            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
+                    EXTERNAL_STORAGE_REQ_CODE);
+        }
+    }
+
+    @Override
+    protected void onPause() {
+        if (!mNeedGrantedPermission) {
+            unbindService(conn);
+            handler.removeCallbacksAndMessages(null);
+        }
+
+        boolean isStreaming = mMediaStream != null && mMediaStream.isStreaming();
+
+        if (mMediaStream != null) {
+            mMediaStream.stopPreview();
+
+//            if (isStreaming && SPUtil.getEnableBackgroundCamera(this)) {
+//                mService.activePreview();
+//            } else {
+                mMediaStream.stopStream();
+                mMediaStream.release();
+                mMediaStream = null;
+
+                stopService(new Intent(this, BackgroundCameraService.class));
+                stopService(new Intent(this, UVCCameraService.class));
+//            }
+        }
+
+        super.onPause();
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+
+        if (!mNeedGrantedPermission) {
+            goonWithPermissionGranted();
+        }
+    }
+
+    @Override
+    protected void onDestroy() {
+        BUSUtil.BUS.unregister(this);
+        super.onDestroy();
+    }
+
+    /*
+     * android6.0权限,onRequestPermissionsResult回调
+     * */
+    @Override
+    public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
+        switch (requestCode) {
+            case REQUEST_CAMERA_PERMISSION: {
+                if (grantResults.length > 1
+                        && grantResults[0] == PackageManager.PERMISSION_GRANTED
+                        && grantResults[1] == PackageManager.PERMISSION_GRANTED) {
+                    mNeedGrantedPermission = false;
+                    goonWithPermissionGranted();
+                } else {
+                    finish();
+                }
+
+                break;
+            }
+        }
+    }
+
+    private void notifyAboutColorChange() {
+        ImageView iv = findViewById(R.id.toolbar_about);
+
+        if (EasyApplication.activeDays >= 9999) {
+            iv.setImageResource(R.drawable.green);
+        } else if (EasyApplication.activeDays > 0) {
+            iv.setImageResource(R.drawable.yellow);
+        } else {
+            iv.setImageResource(R.drawable.red);
+        }
+    }
+
+    private void goonWithPermissionGranted() {
+        spnResolution = findViewById(R.id.spn_resolution);
+        streamStat = findViewById(R.id.stream_stat);
+        txtStatus = findViewById(R.id.txt_stream_status);
+        btnSwitchCemera = findViewById(R.id.btn_switchCamera);
+        textRecordTick = findViewById(R.id.tv_start_record);
+        final TextureView surfaceView = findViewById(R.id.sv_surfaceview);
+
+        streamStat.setText(null);
+        btnSwitchCemera.setOnClickListener(this);
+        surfaceView.setSurfaceTextureListener(this);
+        surfaceView.setOnClickListener(this);
+        startPush.setImageResource(R.drawable.start_push);
+
+        // create background service for background use.
+        Intent intent = new Intent(this, BackgroundCameraService.class);
+        startService(intent);
+
+        Intent intent1 = new Intent(this, UVCCameraService.class);
+        startService(intent1);
+
+        conn = new ServiceConnection() {
+            @Override
+            public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
+                mService = ((BackgroundCameraService.LocalBinder) iBinder).getService();
+
+                if (surfaceView.isAvailable()) {
+                    goonWithAvailableTexture(surfaceView.getSurfaceTexture());
+                }
+            }
+
+            @Override
+            public void onServiceDisconnected(ComponentName componentName) {
+
+            }
+        };
+
+        bindService(new Intent(this, BackgroundCameraService.class), conn, 0);
+
+        if (mRecording) {
+            textRecordTick.setVisibility(View.VISIBLE);
+            textRecordTick.removeCallbacks(mRecordTickRunnable);
+            textRecordTick.post(mRecordTickRunnable);
+        } else {
+            textRecordTick.setVisibility(View.INVISIBLE);
+            textRecordTick.removeCallbacks(mRecordTickRunnable);
+        }
+    }
+
+    /*
+     * 初始化MediaStream
+     * */
+    private void goonWithAvailableTexture(SurfaceTexture surface) {
+        final File easyPusher = new File(DataUtil.recordPath());
+        easyPusher.mkdir();
+
+        MediaStream ms = mService.getMediaStream();
+
+        if (ms != null) { // switch from background to front
+            ms.stopPreview();
+            mService.inActivePreview();
+            ms.setSurfaceTexture(surface);
+            ms.startPreview();
+
+            mMediaStream = ms;
+
+//            if (ms.isStreaming()) {
+//                sendMessage("推流中");
+//                startPush.setImageResource(R.drawable.start_push_pressed);
+//            }
+
+            if (ms.getDisplayRotationDegree() != getDisplayRotationDegree()) {
+                int orientation = getRequestedOrientation();
+
+                if (orientation == SCREEN_ORIENTATION_UNSPECIFIED || orientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) {
+                    setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
+                } else {
+                    setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+                }
+            }
+        } else {
+            boolean enableVideo = SPUtil.getEnableVideo(this);
+
+            ms = new MediaStream(getApplicationContext(), surface, enableVideo);
+            ms.setRecordPath(easyPusher.getPath());
+            mMediaStream = ms;
+
+            startCamera();
+            sendMessage("");
+
+            mService.setMediaStream(ms);
+        }
+    }
+
+    private void startCamera() {
+        mMediaStream.updateResolution(width, height);
+        mMediaStream.setDisplayRotationDegree(getDisplayRotationDegree());
+        mMediaStream.createCamera();
+        mMediaStream.startPreview();
+
+//        if (mMediaStream.isStreaming()) {
+//            sendMessage("推流中");
+//        }
+    }
+
+    // 屏幕的角度
+    private int getDisplayRotationDegree() {
+        int rotation = 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;
+    }
+
+    /*
+     * 初始化下拉控件的列表(显示分辨率)
+     * */
+    private void initSpinner() {
+        ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, R.layout.spn_item, listResolution);
+        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+        spnResolution.setAdapter(adapter);
+
+        int position = listResolution.indexOf(String.format("%dx%d", width, height));
+        spnResolution.setSelection(position, false);
+
+        spnResolution.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+            @Override
+            public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+                if (mMediaStream != null && mMediaStream.isStreaming()) {
+                    int pos = listResolution.indexOf(String.format("%dx%d", width, height));
+
+                    if (pos == position)
+                        return;
+
+                    spnResolution.setSelection(pos, false);
+
+                    Toast.makeText(StreamActivity.this, "正在推送中,无法切换分辨率", Toast.LENGTH_SHORT).show();
+                    return;
+                }
+
+                String r = listResolution.get(position);
+                String[] splitR = r.split("x");
+
+                int wh = Integer.parseInt(splitR[0]);
+                int ht = Integer.parseInt(splitR[1]);
+
+                if (width != wh || height != ht) {
+                    width = wh;
+                    height = ht;
+
+                    if (mMediaStream != null) {
+                        mMediaStream.updateResolution(width, height);
+                    }
+                }
+            }
+
+            @Override
+            public void onNothingSelected(AdapterView<?> parent) {
+
+            }
+        });
+    }
+
+    /*
+     * 开始录像的通知
+     * */
+    @Subscribe
+    public void onStartRecord(StartRecord sr) {
+        // 开始录像的通知,记下当前时间
+        mRecording = true;
+        mRecordingBegin = System.currentTimeMillis();
+
+        runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                textRecordTick.setVisibility(View.VISIBLE);
+                textRecordTick.removeCallbacks(mRecordTickRunnable);
+                textRecordTick.post(mRecordTickRunnable);
+
+                ImageView ib = findViewById(R.id.streaming_activity_record);
+                ib.setImageResource(R.drawable.record_pressed);
+            }
+        });
+    }
+
+    /*
+     * 得知停止录像
+     * */
+    @Subscribe
+    public void onStopRecord(StopRecord sr) {
+        // 停止录像的通知,更新状态
+        mRecording = false;
+        mRecordingBegin = 0;
+
+        runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                textRecordTick.setVisibility(View.INVISIBLE);
+                textRecordTick.removeCallbacks(mRecordTickRunnable);
+
+                ImageView ib = findViewById(R.id.streaming_activity_record);
+                ib.setImageResource(R.drawable.record);
+            }
+        });
+    }
+
+    /*
+     * 开始推流,获取fps、bps
+     * */
+    @Subscribe
+    public void onStreamStat(final StreamStat stat) {
+        streamStat.post(() ->
+                streamStat.setText(getString(R.string.stream_stat,
+                        stat.framePerSecond,
+                        stat.bytesPerSecond * 8 / 1024))
+        );
+    }
+
+    /*
+     * 获取可以支持的分辨率
+     * */
+    @Subscribe
+    public void onSupportResolution(SupportResolution res) {
+        runOnUiThread(() -> {
+            listResolution = Util.getSupportResolution(getApplicationContext());
+            boolean supportdefault = listResolution.contains(String.format("%dx%d", width, height));
+
+            if (!supportdefault) {
+                String r = listResolution.get(0);
+                String[] splitR = r.split("x");
+
+                width = Integer.parseInt(splitR[0]);
+                height = Integer.parseInt(splitR[1]);
+            }
+
+            initSpinner();
+        });
+    }
+
+    /*
+     * 显示推流的状态
+     * */
+    private void sendMessage(String message) {
+        Message msg = Message.obtain();
+        msg.what = MSG_STATE;
+        Bundle bundle = new Bundle();
+        bundle.putString(STATE, message);
+        msg.setData(bundle);
+
+        handler.sendMessage(msg);
+    }
+
+    /* ========================= 点击事件 ========================= */
+
+    /**
+     * Take care of popping the fragment back stack or finishing the activity
+     * as appropriate.
+     */
+    @Override
+    public void onBackPressed() {
+//        boolean isStreaming = mMediaStream != null && mMediaStream.isStreaming();
+//
+//        if (isStreaming && SPUtil.getEnableBackgroundCamera(this)) {
+//            new AlertDialog.Builder(this).setTitle("是否允许后台上传?")
+//                    .setMessage("您设置了使能摄像头后台采集,是否继续在后台采集并上传视频?如果是,记得直播结束后,再回来这里关闭直播。")
+//                    .setNeutralButton("后台采集", (dialogInterface, i) -> {
+//                        StreamActivity.super.onBackPressed();
+//                    })
+//                    .setPositiveButton("退出程序", (dialogInterface, i) -> {
+//                        mMediaStream.stopStream();
+//                        StreamActivity.super.onBackPressed();
+//                        Toast.makeText(StreamActivity.this, "程序已退出。", Toast.LENGTH_SHORT).show();
+//                    })
+//                    .setNegativeButton(android.R.string.cancel, null)
+//                    .show();
+//            return;
+//        } else {
+//            super.onBackPressed();
+//        }
+//
+//        boolean isStreaming = mMediaStream != null && mMediaStream.isStreaming();
+//
+//        if (isStreaming && SPUtil.getEnableBackgroundCamera(this)) {
+//            new AlertDialog.Builder(this).setTitle("是否允许后台上传?")
+//                    .setMessage("您设置了使能摄像头后台采集,是否继续在后台采集并上传视频?如果是,记得直播结束后,再回来这里关闭直播。")
+//                    .setNeutralButton("后台采集", (dialogInterface, i) -> {
+//                        StreamActivity.super.onBackPressed();
+//                    })
+//                    .setPositiveButton("退出程序", (dialogInterface, i) -> {
+//                        mMediaStream.stopStream();
+//                        StreamActivity.super.onBackPressed();
+//                        Toast.makeText(StreamActivity.this, "程序已退出。", Toast.LENGTH_SHORT).show();
+//                    })
+//                    .setNegativeButton(android.R.string.cancel, null)
+//                    .show();
+//            return;
+//        }
+
+        //与上次点击返回键时刻作差
+        if ((System.currentTimeMillis() - mExitTime) > 2000) {
+            //大于2000ms则认为是误操作,使用Toast进行提示
+            Toast.makeText(this, "再按一次退出程序", Toast.LENGTH_SHORT).show();
+            //并记录下本次点击“返回键”的时刻,以便下次进行判断
+            mExitTime = System.currentTimeMillis();
+        } else {
+            super.onBackPressed();
+        }
+    }
+
+    @Override
+    public void onClick(View v) {
+        switch (v.getId()) {
+            case R.id.sv_surfaceview:
+                try {
+                    mMediaStream.getCamera().autoFocus(null);
+                } catch (Exception e) {
+
+                }
+                break;
+            case R.id.btn_switchCamera:
+                if (!mMediaStream.isStreaming()) {
+                    mMediaStream.switchCamera();
+                }
+                break;
+        }
+    }
+
+    /*
+     * 录像
+     * */
+    public void onRecord(View view) {
+        if (ActivityCompat.checkSelfPermission(this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE) != PERMISSION_GRANTED) {
+            ActivityCompat.requestPermissions(this, new String[]{android.Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_STORAGE_PERMISSION);
+            return;
+        }
+
+        ImageView ib = findViewById(R.id.streaming_activity_record);
+
+        if (mMediaStream != null) {
+            if (mMediaStream.isRecording()) {
+                mMediaStream.stopRecord();
+                ib.setImageResource(R.drawable.record_pressed);
+            } else {
+                mMediaStream.startRecord();
+                ib.setImageResource(R.drawable.record);
+            }
+        }
+    }
+
+    /*
+     * 切换分辨率
+     * */
+    public void onClickResolution(View view) {
+        findViewById(R.id.spn_resolution).performClick();
+    }
+
+    /*
+     * 切换屏幕方向
+     * */
+    public void onSwitchOrientation(View view) {
+        if (mMediaStream != null) {
+            if (mMediaStream.isStreaming()) {
+                Toast.makeText(this, "正在推送中,无法更改屏幕方向", Toast.LENGTH_SHORT).show();
+                return;
+            }
+        }
+
+        int orientation = getRequestedOrientation();
+
+        if (orientation == SCREEN_ORIENTATION_UNSPECIFIED || orientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) {
+            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
+        } else {
+            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+        }
+
+//        if (mMediaStream != null)
+//            mMediaStream.setDisplayRotationDegree(getDisplayRotationDegree());
+    }
+
+    /*
+     * 推流or停止
+     * */
+    public void onStartOrStopPush(View view) {
+        ImageView ib = findViewById(R.id.streaming_activity_push);
+
+        if (mMediaStream != null && !mMediaStream.isStreaming()) {
+            try {
+                mMediaStream.startStream();
+
+                ib.setImageResource(R.drawable.start_push_pressed);
+            } catch (IOException e) {
+                e.printStackTrace();
+                sendMessage("激活失败,无效Key");
+            }
+        } else {
+            mMediaStream.stopStream();
+            ib.setImageResource(R.drawable.start_push);
+            sendMessage("离线");
+        }
+    }
+
+    /*
+     * 关于我们
+     * */
+    public void onAbout(View view) {
+        Intent intent = new Intent(this, AboutActivity.class);
+        startActivityForResult(intent, 0);
+        overridePendingTransition(R.anim.slide_right_in, R.anim.slide_left_out);
+    }
+
+    /*
+     * 设置
+     * */
+    public void onSetting(View view) {
+        Intent intent = new Intent(this, SettingActivity.class);
+        startActivityForResult(intent, 0);
+        overridePendingTransition(R.anim.slide_right_in, R.anim.slide_left_out);
+    }
+
+    /* ========================= TextureView.SurfaceTextureListener ========================= */
+
+    @Override
+    public void onSurfaceTextureAvailable(final SurfaceTexture surface, int width, int height) {
+        if (mService != null) {
+            goonWithAvailableTexture(surface);
+        }
+    }
+
+    @Override
+    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
+
+    }
+
+    @Override
+    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
+        return true;
+    }
+
+    @Override
+    public void onSurfaceTextureUpdated(SurfaceTexture surface) {
+
+    }
+
+    /**
+     * 状态的回调
+     * */
+    @Subscribe
+    public void onPushCallback(final PushCallback cb) {
+        sendMessage(cb.getName());
+    }
+}

+ 209 - 0
Android/app/src/main/java/com/easygbs/device/UVCCameraService.java

@@ -0,0 +1,209 @@
+package com.easygbs.device;
+
+import android.app.Service;
+import android.arch.lifecycle.BuildConfig;
+import android.arch.lifecycle.LiveData;
+import android.content.Intent;
+import android.hardware.usb.UsbDevice;
+import android.os.Binder;
+import android.os.IBinder;
+import android.util.Log;
+import android.util.SparseArray;
+import android.widget.Toast;
+
+import com.serenegiant.usb.DeviceFilter;
+import com.serenegiant.usb.IButtonCallback;
+import com.serenegiant.usb.IStatusCallback;
+import com.serenegiant.usb.USBMonitor;
+import com.serenegiant.usb.UVCCamera;
+
+import java.nio.ByteBuffer;
+
+public class UVCCameraService extends Service {
+
+    public static class UVCCameraLivaData extends LiveData<UVCCamera> {
+        @Override
+        protected void postValue(UVCCamera value) {
+            super.postValue(value);
+        }
+    }
+
+    public static final UVCCameraLivaData liveData = new UVCCameraLivaData();
+
+    public static class MyUVCCamera extends UVCCamera {
+        boolean prev = false;
+
+        @Override
+        public synchronized void startPreview() {
+            if (prev)
+                return;
+
+            super.startPreview();
+            prev = true;
+        }
+
+        @Override
+        public synchronized void stopPreview() {
+            if (!prev)
+                return;
+
+            super.stopPreview();
+            prev = false;
+        }
+
+        @Override
+        public synchronized void destroy() {
+            prev = false;
+            super.destroy();
+        }
+    }
+
+    private static final String TAG = UVCCameraService.class.getSimpleName();
+
+    private USBMonitor mUSBMonitor;
+    private UVCCamera mUVCCamera;
+
+    private SparseArray<UVCCamera> cameras = new SparseArray<>();
+
+    public class MyBinder extends Binder {
+        public UVCCameraService getService() {
+            return UVCCameraService.this;
+        }
+    }
+
+    MyBinder binder = new MyBinder();
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return binder;
+    }
+
+    public UVCCamera getCamera() {
+        return mUVCCamera;
+    }
+
+    private void releaseCamera() {
+        if (mUVCCamera != null) {
+            try {
+                mUVCCamera.close();
+                mUVCCamera.destroy();
+                mUVCCamera = null;
+            } catch (final Exception e) {
+                //
+            }
+        }
+    }
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+
+        mUSBMonitor = new USBMonitor(this, new USBMonitor.OnDeviceConnectListener() {
+            @Override
+            public void onAttach(final UsbDevice device) {
+                Log.v(TAG, "onAttach:" + device);
+                mUSBMonitor.requestPermission(device);
+            }
+
+            @Override
+            public void onConnect(final UsbDevice device, final USBMonitor.UsbControlBlock ctrlBlock, final boolean createNew) {
+                releaseCamera();
+
+                if (BuildConfig.DEBUG)
+                    Log.v(TAG, "onConnect:");
+
+                try {
+                    final UVCCamera camera = new MyUVCCamera();
+                    camera.open(ctrlBlock);
+                    camera.setStatusCallback(new IStatusCallback() {
+                        @Override
+                        public void onStatus(final int statusClass, final int event, final int selector, final int statusAttribute, final ByteBuffer data) {
+                            Log.i(TAG, "onStatus(statusClass=" + statusClass
+                                    + "; " +
+                                    "event=" + event + "; " +
+                                    "selector=" + selector + "; " +
+                                    "statusAttribute=" + statusAttribute + "; " +
+                                    "data=...)");
+                        }
+                    });
+
+                    camera.setButtonCallback(new IButtonCallback() {
+                        @Override
+                        public void onButton(final int button, final int state) {
+                            Log.i(TAG, "onButton(button=" + button + "; " + "state=" + state + ")");
+                        }
+                    });
+
+//					camera.setPreviewTexture(camera.getSurfaceTexture());
+                    mUVCCamera = camera;
+                    liveData.postValue(camera);
+
+                    Toast.makeText(UVCCameraService.this, "UVCCamera connected!", Toast.LENGTH_SHORT).show();
+
+                    if (device != null)
+                        cameras.append(device.getDeviceId(), camera);
+                } catch (Exception ex) {
+                    ex.printStackTrace();
+                }
+            }
+
+            @Override
+            public void onDisconnect(final UsbDevice device, final USBMonitor.UsbControlBlock ctrlBlock) {
+                Log.v(TAG, "onDisconnect:");
+//                Toast.makeText(MainActivity.this, R.string.usb_camera_disconnected, Toast.LENGTH_SHORT).show();
+
+//                releaseCamera();
+
+                if (device != null) {
+                    UVCCamera camera = cameras.get(device.getDeviceId());
+
+                    if (mUVCCamera == camera) {
+                        mUVCCamera = null;
+                        Toast.makeText(UVCCameraService.this, "UVCCamera disconnected!", Toast.LENGTH_SHORT).show();
+                        liveData.postValue(null);
+                    }
+
+                    cameras.remove(device.getDeviceId());
+                } else {
+                    Toast.makeText(UVCCameraService.this, "UVCCamera disconnected!", Toast.LENGTH_SHORT).show();
+                    mUVCCamera = null;
+                    liveData.postValue(null);
+                }
+
+//                if (mUSBMonitor != null) {
+//                    mUSBMonitor.destroy();
+//                }
+//
+//                mUSBMonitor = new USBMonitor(OutterCameraService.this, this);
+//                mUSBMonitor.setDeviceFilter(DeviceFilter.getDeviceFilters(OutterCameraService.this, R.xml.device_filter));
+//                mUSBMonitor.register();
+            }
+
+            @Override
+            public void onCancel(UsbDevice usbDevice) {
+                releaseCamera();
+            }
+
+            @Override
+            public void onDettach(final UsbDevice device) {
+                Log.v(TAG, "onDettach:");
+                releaseCamera();
+//                AppContext.getInstance().bus.post(new UVCCameraDisconnect());
+            }
+        });
+
+        mUSBMonitor.setDeviceFilter(DeviceFilter.getDeviceFilters(this, R.xml.device_filter));
+        mUSBMonitor.register();
+    }
+
+    @Override
+    public void onDestroy() {
+        releaseCamera();
+
+        if (mUSBMonitor != null) {
+            mUSBMonitor.unregister();
+        }
+
+        super.onDestroy();
+    }
+}

+ 891 - 0
Android/app/src/main/java/com/easygbs/device/push/MediaStream.java

@@ -0,0 +1,891 @@
+package com.easygbs.device.push;
+
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.ImageFormat;
+import android.graphics.SurfaceTexture;
+import android.hardware.Camera;
+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.Device;
+import com.easygbs.device.BackgroundCameraService;
+import com.easygbs.device.util.DataUtil;
+import com.easygbs.device.util.SPUtil;
+import com.serenegiant.usb.IFrameCallback;
+import com.serenegiant.usb.UVCCamera;
+
+import org.easydarwin.bus.SupportResolution;
+
+import com.easygbs.device.UVCCameraService;
+
+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.BUSUtil;
+import org.easydarwin.util.Util;
+
+import java.io.File;
+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.Date;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+
+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;
+
+/**
+ * 摄像头实时数据采集,并调用相关编码器
+ * */
+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 mMuxer;
+    private Pusher mEasyPusher;
+
+    private final HandlerThread mCameraThread;
+    private final Handler mCameraHandler;
+
+    /**
+     * 初始化MediaStream
+     * */
+    public MediaStream(Context context, SurfaceTexture texture, boolean enableVideo) {
+        this.context = context;
+        audioStream = AudioStream.getInstance(context);
+        mSurfaceHolderRef = new WeakReference(texture);
+
+        mCameraThread = new HandlerThread("CAMERA") {
+            public void run() {
+                try {
+                    super.run();
+                } catch (Throwable e) {
+                    e.printStackTrace();
+
+                    Intent intent = new Intent(context, BackgroundCameraService.class);
+                    context.stopService(intent);
+                } 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 code, String name) {
+                BUSUtil.BUS.post(new PushCallback(code, name));
+            }
+        });
+
+        if (!enableVideo) {
+            return;
+        }
+
+        if (mCameraId == CAMERA_FACING_BACK_UVC) {
+            createUvcCamera();
+        } else {
+            createNativeCamera();
+        }
+    }
+
+    private void createNativeCamera() {
+        try {
+            mCamera = Camera.open(mCameraId);// 初始化创建Camera实例对象
+            mCamera.setErrorCallback((i, camera) -> {
+                throw new IllegalStateException("Camera Error:" + i);
+            });
+            Log.i(TAG, "open Camera");
+
+            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());
+            }
+
+            BUSUtil.BUS.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); // 设置Camera预览方向
+//            parameters.setRecordingHint(true);
+
+            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;
+            }
+
+//            List<Camera.Size> sizes = parameters.getSupportedPreviewSizes();
+            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);
+            }
+
+//            int maxExposureCompensation = parameters.getMaxExposureCompensation();
+//            parameters.setExposureCompensation(3);
+//
+//            if(parameters.isAutoExposureLockSupported()) {
+//                parameters.setAutoExposureLock(false);
+//            }
+
+//            parameters.setWhiteBalance(Camera.Parameters.WHITE_BALANCE_AUTO);
+//            parameters.setFlashMode(Camera.Parameters.FLASH_MODE_AUTO);
+//            parameters.setSceneMode(Camera.Parameters.SCENE_MODE_AUTO);
+//            parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
+//            mCamera.setFaceDetectionListener(new );
+
+//            if (parameters.isAutoWhiteBalanceLockSupported()){
+//                parameters.setAutoExposureLock(false);
+//            }
+
+            mCamera.setParameters(parameters);
+            Log.i(TAG, "setParameters");
+
+            int displayRotation;
+            displayRotation = (cameraRotationOffset - displayRotationDegree + 360) % 360;
+            mCamera.setDisplayOrientation(displayRotation);
+
+            Log.i(TAG, "setDisplayOrientation");
+        } catch (Exception e) {
+            StringWriter sw = new StringWriter();
+            PrintWriter pw = new PrintWriter(sw);
+            e.printStackTrace(pw);
+
+//            String stack = sw.toString();
+            destroyCamera();
+            e.printStackTrace();
+        }
+    }
+
+    private void createUvcCamera() {
+//        frameWidth = frameRotate ? height : width;
+//        frameHeight = frameRotate ? width : height;
+
+        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();
+        }
+    }
+
+    /// 销毁Camera
+    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();
+            }
+
+            Log.i(TAG, "release Camera");
+
+            mCamera = null;
+        }
+
+        if (mMuxer != null) {
+            mMuxer.release();
+            mMuxer = 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) {
+            startUvcPreview();
+        } else if (mCamera != null) {
+            startCameraPreview();
+        }
+
+        if (mSWCodec) {
+            SWConsumer sw = new SWConsumer(context, mEasyPusher, SPUtil.getBitrateKbps(context));
+            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);
+            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);
+
+        Log.i(TAG, "setPreviewCallbackWithBuffer");
+
+        try {
+            // TextureView的
+            SurfaceTexture holder = mSurfaceHolderRef.get();
+
+            // SurfaceView传入上面创建的Camera对象
+            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();
+        }
+
+//        mCameraHandler.removeCallbacks(dequeueRunnable);
+
+        // 关闭摄像头
+        if (mCamera != null) {
+            mCamera.stopPreview();
+            mCamera.setPreviewCallbackWithBuffer(null);
+            Log.i(TAG, "StopPreview");
+        }
+
+        // 关闭音频采集和音频编码器
+        if (audioStream != null) {
+            audioStream.removePusher(mEasyPusher);
+            audioStream.setMuxer(null);
+            Log.i(TAG, "Stop AudioStream");
+        }
+
+        // 关闭视频编码器
+        if (mVC != null) {
+            mVC.onVideoStop();
+            Log.i(TAG, "Stop VC");
+        }
+
+        // 关闭录像的编码器
+        if (mRecordVC != null) {
+            mRecordVC.onVideoStop();
+        }
+
+        // 关闭音视频合成器
+        if (mMuxer != null) {
+            mMuxer.release();
+            mMuxer = null;
+        }
+    }
+
+    /// 开始推流
+    public void startStream() throws IOException {
+        try {
+            mEasyPusher.initPush(DataUtil.getSIP());
+
+            isPushStream = true;
+        } catch (Exception ex) {
+            ex.printStackTrace();
+            throw new IOException(ex.getMessage());
+        }
+    }
+
+    /// 停止推流
+    public void stopStream() {
+        if (mEasyPusher != null) {
+            mEasyPusher.stop();
+        }
+
+        isPushStream = false;
+    }
+
+    /// 开始录像
+    public synchronized void startRecord() {
+        if (Thread.currentThread() != mCameraThread) {
+            mCameraHandler.post(() -> startRecord());
+            return;
+        }
+
+        if (mCamera == null) {
+            return;
+        }
+
+        // 默认录像时间300000毫秒
+        mMuxer = new EasyMuxer(new File(recordPath, new SimpleDateFormat("yyyy_MM_dd_HH_mm_ss").format(new Date())).toString(), 300000);
+
+        mRecordVC = new RecordVideoConsumer(context,
+                mHevc ? MediaFormat.MIMETYPE_VIDEO_HEVC : MediaFormat.MIMETYPE_VIDEO_AVC,
+                mMuxer,
+                SPUtil.getEnableVideoOverlay(context),
+                SPUtil.getBitrateKbps(context),
+                info.mName,
+                info.mColorFormat);
+        mRecordVC.onVideoStart(frameWidth, frameHeight);
+
+        if (audioStream != null) {
+            audioStream.setMuxer(mMuxer);
+        }
+    }
+
+    /// 停止录像
+    public synchronized void stopRecord() {
+        if (Thread.currentThread() != mCameraThread) {
+            mCameraHandler.post(() -> stopRecord());
+            return;
+        }
+
+        if (mRecordVC == null || audioStream == null) {
+//            nothing
+        } else {
+            audioStream.setMuxer(null);
+            mRecordVC.onVideoStop();
+            mRecordVC = null;
+        }
+
+        if (mMuxer != null)
+            mMuxer.release();
+
+        mMuxer = null;
+    }
+
+    /// 更新分辨率
+    public void updateResolution(final int w, final int h) {
+        if (mCamera == null)
+            return;
+
+        stopPreview();
+        destroyCamera();
+
+        mCameraHandler.post(() -> {
+            defaultWidth = w;
+            defaultHeight = h;
+        });
+
+        createCamera();
+        startPreview();
+    }
+
+    /* ============================== Camera ============================== */
+
+    /*
+     * 默认后置摄像头
+     *   Camera.CameraInfo.CAMERA_FACING_BACK
+     *   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 = 1280, defaultHeight = 720;
+    private int mTargetCameraId;
+
+    /**
+     * 切换前后摄像头
+     *  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 {
+
+            }
+        }
+    };
+
+    /* ============================== Native Camera ============================== */
+
+    Camera mCamera;
+    private Camera.CameraInfo camInfo;
+    private Camera.Parameters parameters;
+    private byte[] i420_buffer;
+
+    // 摄像头预览的视频流数据
+    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 {  // back-facing
+            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);
+    };
+
+    /* ============================== UVC Camera ============================== */
+
+    private UVCCamera uvcCamera;
+
+    BlockingQueue<byte[]> cache = new ArrayBlockingQueue<byte[]>(100);
+//    BlockingQueue<byte[]>
+//    = new ArrayBlockingQueue<byte[]>(10);
+//    final Runnable dequeueRunnable = new Runnable() {
+//        @Override
+//        public void run() {
+//            try {
+//                byte[] data = bufferQueue.poll(10, TimeUnit.MICROSECONDS);
+//
+//                if (data != null) {
+//                    onPreviewFrame2(data, uvcCamera);
+//                    cache.offer(data);
+//                }
+//
+//                if (uvcCamera == null)
+//                    return;
+//
+//                mCameraHandler.post(this);
+//            } catch (InterruptedException ex) {
+//                ex.printStackTrace();
+//            }
+//        }
+//    };
+
+    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;
+    }
+
+    /* ============================== get/set ============================== */
+
+    public void setRecordPath(String recordPath) {
+        this.recordPath = recordPath;
+    }
+
+    public boolean isRecording() {
+        return mMuxer != 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);
+        }
+    }
+}

+ 30 - 0
Android/app/src/main/java/com/easygbs/device/push/PushCallback.java

@@ -0,0 +1,30 @@
+package com.easygbs.device.push;
+
+/**
+ * 推流状态的回调
+ */
+public class PushCallback {
+    private int code;
+    private String name;
+
+    public PushCallback(int code, String name) {
+        this.code = code;
+        this.name = name;
+    }
+
+    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;
+    }
+}

+ 75 - 0
Android/app/src/main/java/com/easygbs/device/util/DataUtil.java

@@ -0,0 +1,75 @@
+package com.easygbs.device.util;
+
+import android.content.SharedPreferences;
+import android.os.Environment;
+import android.preference.PreferenceManager;
+
+import com.easygbs.device.EasyApplication;
+
+import org.easydarwin.util.SIP;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class DataUtil {
+
+    public static void setSIP(SIP sip) {
+        PreferenceManager.getDefaultSharedPreferences(EasyApplication.getEasyApplication())
+                .edit()
+                .putString("serverIp", sip.getServerIp())
+                .putInt("serverPort", sip.getServerPort())
+                .putInt("localSipPort", sip.getLocalSipPort())
+                .putString("serverId", sip.getServerId())
+                .putString("serverDomain", sip.getServerDomain())
+                .putString("deviceId", sip.getDeviceId())
+                .putString("password", sip.getPassword())
+                .putInt("protocol", sip.getProtocol())
+                .putInt("regExpires", sip.getRegExpires())
+                .putInt("heartbeatInterval", sip.getHeartbeatInterval())
+                .putInt("heartbeatCount", sip.getHeartbeatCount())
+                .apply();
+    }
+
+    public static SIP getSIP() {
+        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(EasyApplication.getEasyApplication());
+
+        SIP sip = new SIP();
+        sip.setServerIp(sp.getString("serverIp", "demo.easygbs.com"));
+        sip.setServerPort(sp.getInt("serverPort", 15060));
+        sip.setLocalSipPort(sp.getInt("localSipPort", 15060));
+        sip.setServerId(sp.getString("serverId", "34020000002000000001"));
+        sip.setServerDomain(sp.getString("serverDomain", "3402000000"));
+        sip.setDeviceId(sp.getString("deviceId", "34020000001110005555"));
+        sip.setPassword(sp.getString("password", "12345678"));
+        sip.setProtocol(sp.getInt("protocol", SIP.ProtocolEnum.UDP.getValue()));
+        sip.setRegExpires(sp.getInt("regExpires", 3600));
+        sip.setHeartbeatInterval(sp.getInt("heartbeatInterval", 30));
+        sip.setHeartbeatCount(sp.getInt("heartbeatCount", 3));
+
+        // TODO
+        List<SIP.GB28181_CHANNEL_INFO_T> list = new ArrayList<>();
+        for (int i = 0; i < 1; i++) {
+            SIP.GB28181_CHANNEL_INFO_T item = new SIP.GB28181_CHANNEL_INFO_T();
+            item.setName("EasyIPC");
+            item.setManufacturer("TSINGSEE");
+            item.setModel("EasyGBD");
+            item.setParentId(sp.getString("deviceId", "34020000001110005555"));
+            item.setIndexCode(sp.getString("indexCode", "34020000001310005554"));
+            item.setOwner("Owner");
+            item.setCivilCode("CivilCode");
+            item.setAddress("Address");
+            // TODO 经纬度自己填写
+            item.setLongitude(116.397128);
+            item.setLatitude(39.916527);
+
+            list.add(item);
+        }
+        sip.setList(list);
+
+        return sip;
+    }
+
+    public static String recordPath() {
+        return Environment.getExternalStorageDirectory() +"/EasyGBS";
+    }
+}

+ 91 - 0
Android/app/src/main/java/com/easygbs/device/util/LocalIPUtil.java

@@ -0,0 +1,91 @@
+package com.easygbs.device.util;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.util.Log;
+
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.util.ArrayList;
+import java.util.Collections;
+
+public class LocalIPUtil {
+
+    public static String getLocalIp(Context context) {
+        ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+        NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
+
+        if (activeNetworkInfo == null) {
+            return null;
+        }
+
+        String ip;
+        if (activeNetworkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
+            // wifi
+            ip = getWifiIp(context);
+        } else {
+            // 不是wifi
+            ip = get4gIp();
+        }
+
+        Log.i("LocalIPUtil", ip);
+        return ip;
+    }
+
+    /*
+    * 移动网络下获得本地IP地址
+    * */
+    private static String get4gIp() {
+        try {
+            String ipv4;
+            ArrayList<NetworkInterface> nilist = Collections.list(NetworkInterface.getNetworkInterfaces());
+
+            for (NetworkInterface ni: nilist) {
+                ArrayList<InetAddress> ialist = Collections.list(ni.getInetAddresses());
+
+                for (InetAddress address: ialist) {
+                    if (!address.isLoopbackAddress() && !address.isLinkLocalAddress()) {
+                        ipv4=address.getHostAddress();
+                        return ipv4;
+                    }
+                }
+            }
+        } catch (SocketException ex) {
+            Log.e("localip", ex.toString());
+        }
+
+        return null;
+    }
+
+    /*
+    * wifi下获得本地IP地址
+    * */
+    private static String getWifiIp(Context context) {
+        //获取wifi服务
+
+        WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+
+        //判断wifi是否开启
+        if (!wifiManager.isWifiEnabled()) {
+            wifiManager.setWifiEnabled(true);
+        }
+
+        WifiInfo wifiInfo = wifiManager.getConnectionInfo();
+        int ipAddress = wifiInfo.getIpAddress();
+        String ip = intToIp(ipAddress);
+
+        return ip;
+    }
+
+    /* 获取Wifi ip 地址 */
+    private static String intToIp(int i) {
+        return (i & 0xFF) + "." +
+                ((i >> 8) & 0xFF) + "." +
+                ((i >> 16) & 0xFF) + "." +
+                (i >> 24 & 0xFF);
+    }
+}

+ 131 - 0
Android/app/src/main/java/com/easygbs/device/util/SPUtil.java

@@ -0,0 +1,131 @@
+package com.easygbs.device.util;
+
+import android.content.Context;
+import android.preference.PreferenceManager;
+
+/**
+ * SharedPreferences存储工具
+ * */
+public class SPUtil {
+
+    /* ============================ 使用软编码 ============================ */
+    private static final String KEY_SW_CODEC = "key-sw-codec";
+
+    public static boolean getswCodec(Context context) {
+        return PreferenceManager.getDefaultSharedPreferences(context)
+                .getBoolean(KEY_SW_CODEC, false);
+    }
+
+    public static void setswCodec(Context context, boolean isChecked) {
+        PreferenceManager.getDefaultSharedPreferences(context)
+                .edit()
+                .putBoolean(KEY_SW_CODEC, isChecked)
+                .apply();
+    }
+
+    /* ============================ 使能H.265编码 ============================ */
+    private static final String KEY_HEVC_CODEC = "key-hevc-codec";
+
+    public static boolean getHevcCodec(Context context) {
+        return PreferenceManager.getDefaultSharedPreferences(context)
+                .getBoolean(KEY_HEVC_CODEC, false);
+    }
+
+    public static void setHevcCodec(Context context, boolean isChecked) {
+        PreferenceManager.getDefaultSharedPreferences(context)
+                .edit()
+                .putBoolean(KEY_HEVC_CODEC, isChecked)
+                .apply();
+    }
+
+    /* ============================ 使能AAC编码 ============================ */
+    private static final String KEY_AAC_CODEC = "key-aac-codec";
+
+    public static boolean getAACCodec(Context context) {
+        return PreferenceManager.getDefaultSharedPreferences(context)
+                .getBoolean(KEY_AAC_CODEC, false);
+    }
+
+    public static void setAACCodec(Context context, boolean isChecked) {
+        PreferenceManager.getDefaultSharedPreferences(context)
+                .edit()
+                .putBoolean(KEY_AAC_CODEC, isChecked)
+                .apply();
+    }
+
+    /* ============================ 叠加水印 ============================ */
+    private static final String KEY_ENABLE_VIDEO_OVERLAY = "key_enable_video_overlay";
+
+    public static boolean getEnableVideoOverlay(Context context) {
+        return PreferenceManager.getDefaultSharedPreferences(context)
+                .getBoolean(KEY_ENABLE_VIDEO_OVERLAY, false);
+    }
+
+    public static void setEnableVideoOverlay(Context context, boolean isChecked) {
+        PreferenceManager.getDefaultSharedPreferences(context)
+                .edit()
+                .putBoolean(KEY_ENABLE_VIDEO_OVERLAY, isChecked)
+                .apply();
+    }
+
+    /* ============================ 码率 ============================ */
+    private static final String KEY_BITRATE_ADDED_KBPS = "bitrate_added_kbps";
+
+    public static int getBitrateKbps(Context context) {
+        return PreferenceManager.getDefaultSharedPreferences(context)
+                .getInt(KEY_BITRATE_ADDED_KBPS, 300000);
+    }
+
+    public static void setBitrateKbps(Context context, int value) {
+         PreferenceManager.getDefaultSharedPreferences(context)
+                 .edit()
+                 .putInt(KEY_BITRATE_ADDED_KBPS, value)
+                 .apply();
+    }
+
+//    /* ============================ 使能摄像头后台采集 ============================ */
+//    private static final String KEY_ENABLE_BACKGROUND_CAMERA = "key_enable_background_camera";
+//
+//    public static boolean getEnableBackgroundCamera(Context context) {
+//        return PreferenceManager.getDefaultSharedPreferences(context)
+//                .getBoolean(KEY_ENABLE_BACKGROUND_CAMERA, false);
+//    }
+//
+//    public static void setEnableBackgroundCamera(Context context, boolean value) {
+//        PreferenceManager.getDefaultSharedPreferences(context)
+//                .edit()
+//                .putBoolean(KEY_ENABLE_BACKGROUND_CAMERA, value)
+//                .apply();
+//    }
+
+    /* ============================ 推送视频 ============================ */
+    private static final String KEY_ENABLE_VIDEO = "key-enable-video";
+
+    public static boolean getEnableVideo(Context context) {
+        return PreferenceManager.getDefaultSharedPreferences(context)
+                .getBoolean(KEY_ENABLE_VIDEO, true);
+    }
+
+    public static void setEnableVideo(Context context, boolean value) {
+        PreferenceManager.getDefaultSharedPreferences(context)
+                .edit()
+                .putBoolean(KEY_ENABLE_VIDEO, value)
+                .apply();
+    }
+
+    /* ============================ 推送音频 ============================ */
+
+    private static final String KEY_ENABLE_AUDIO = "key-enable-audio";
+
+    public static boolean getEnableAudio(Context context) {
+        return PreferenceManager.getDefaultSharedPreferences(context)
+                .getBoolean(KEY_ENABLE_AUDIO, true);
+    }
+
+    public static void setEnableAudio(Context context, boolean value) {
+        PreferenceManager.getDefaultSharedPreferences(context)
+                .edit()
+                .putBoolean(KEY_ENABLE_AUDIO, value)
+                .apply();
+    }
+}

+ 32 - 0
Android/app/src/main/java/com/easygbs/device/views/SquareImageView.java

@@ -0,0 +1,32 @@
+package com.easygbs.device.views;
+
+import android.content.Context;
+import android.os.Build;
+import android.support.v7.widget.AppCompatImageView;
+import android.util.AttributeSet;
+
+/**
+ * Created by John on 2014/11/11.
+ */
+public class SquareImageView extends AppCompatImageView {
+    public SquareImageView(Context context) {
+        super(context);
+    }
+
+    public SquareImageView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public SquareImageView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+            setMeasuredDimension(widthMeasureSpec, widthMeasureSpec);
+        } else {
+            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        }
+    }
+}

+ 9 - 0
Android/app/src/main/res/anim/slide_bottom_in.xml

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<set xmlns:android="http://schemas.android.com/apk/res/android" >
+
+    <translate
+        android:duration="300"
+        android:fromYDelta="100.0%p"
+        android:toYDelta="0" />
+
+</set>

+ 9 - 0
Android/app/src/main/res/anim/slide_left_out.xml

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<set xmlns:android="http://schemas.android.com/apk/res/android" >
+
+    <translate
+        android:duration="300"
+        android:fromXDelta="0"
+        android:toXDelta="100.0%p" />
+
+</set>

+ 9 - 0
Android/app/src/main/res/anim/slide_right_in.xml

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<set xmlns:android="http://schemas.android.com/apk/res/android" >
+
+    <translate
+        android:duration="300"
+        android:fromXDelta="100.0%p"
+        android:toXDelta="0" />
+
+</set>

+ 9 - 0
Android/app/src/main/res/anim/slide_top_out.xml

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<set xmlns:android="http://schemas.android.com/apk/res/android" >
+
+    <translate
+        android:duration="300"
+        android:fromYDelta="0"
+        android:toYDelta="100.0%p" />
+
+</set>

BIN
Android/app/src/main/res/drawable-hdpi/float_button.png


BIN
Android/app/src/main/res/drawable-hdpi/ic_action_push_error.png


BIN
Android/app/src/main/res/drawable-hdpi/ic_stat_camera.png


BIN
Android/app/src/main/res/drawable-mdpi/ic_action_push_error.png


BIN
Android/app/src/main/res/drawable-mdpi/ic_stat_camera.png


BIN
Android/app/src/main/res/drawable-xhdpi/android_player_pro.png


BIN
Android/app/src/main/res/drawable-xhdpi/android_rtmp.png


BIN
Android/app/src/main/res/drawable-xhdpi/com_back.png


BIN
Android/app/src/main/res/drawable-xhdpi/green.png


BIN
Android/app/src/main/res/drawable-xhdpi/ic_action_push_error.png


BIN
Android/app/src/main/res/drawable-xhdpi/ic_action_switch_camera.png


BIN
Android/app/src/main/res/drawable-xhdpi/ic_action_switch_oritation.png


BIN
Android/app/src/main/res/drawable-xhdpi/ic_stat_camera.png


BIN
Android/app/src/main/res/drawable-xhdpi/ios_player_pro.png


BIN
Android/app/src/main/res/drawable-xhdpi/ios_rtmp.png


BIN
Android/app/src/main/res/drawable-xhdpi/new_player.png


BIN
Android/app/src/main/res/drawable-xhdpi/push_screen.png


BIN
Android/app/src/main/res/drawable-xhdpi/push_screen_click.png


BIN
Android/app/src/main/res/drawable-xhdpi/qr_scan_btn.png


BIN
Android/app/src/main/res/drawable-xhdpi/record.png


BIN
Android/app/src/main/res/drawable-xhdpi/record_pressed.png


BIN
Android/app/src/main/res/drawable-xhdpi/red.png


BIN
Android/app/src/main/res/drawable-xhdpi/settings.png


BIN
Android/app/src/main/res/drawable-xhdpi/start_push.png


BIN
Android/app/src/main/res/drawable-xhdpi/start_push_pressed.png


BIN
Android/app/src/main/res/drawable-xhdpi/yellow.png


BIN
Android/app/src/main/res/drawable-xxhdpi/ic_action_push_error.png


BIN
Android/app/src/main/res/drawable-xxhdpi/ic_stat_camera.png


BIN
Android/app/src/main/res/drawable-xxhdpi/new_player.png


+ 74 - 0
Android/app/src/main/res/drawable/ic_launcher_background.xml

@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector
+    android:height="108dp"
+    android:width="108dp"
+    android:viewportHeight="108"
+    android:viewportWidth="108"
+    xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="#008577"
+          android:pathData="M0,0h108v108h-108z"/>
+    <path android:fillColor="#00000000" android:pathData="M9,0L9,108"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M19,0L19,108"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M29,0L29,108"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M39,0L39,108"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M49,0L49,108"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M59,0L59,108"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M69,0L69,108"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M79,0L79,108"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M89,0L89,108"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M99,0L99,108"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M0,9L108,9"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M0,19L108,19"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M0,29L108,29"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M0,39L108,39"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M0,49L108,49"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M0,59L108,59"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M0,69L108,69"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M0,79L108,79"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M0,89L108,89"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M0,99L108,99"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M19,29L89,29"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M19,39L89,39"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M19,49L89,49"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M19,59L89,59"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M19,69L89,69"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M19,79L89,79"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M29,19L29,89"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M39,19L39,89"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M49,19L49,89"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M59,19L59,89"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M69,19L69,89"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+    <path android:fillColor="#00000000" android:pathData="M79,19L79,89"
+          android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+</vector>

+ 15 - 0
Android/app/src/main/res/drawable/recording_marker_interval_shape.xml

@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
+
+    <size
+        android:width="5dp"
+        android:height="5dp" >
+    </size>
+
+    <stroke android:color="#00000000">
+    </stroke>
+
+    <solid android:color="#00000000">
+    </solid>
+
+</shape>

+ 15 - 0
Android/app/src/main/res/drawable/recording_marker_shape.xml

@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
+
+    <size
+        android:width="5dp"
+        android:height="5dp" >
+    </size>
+
+    <stroke android:color="#ff0000">
+    </stroke>
+
+    <solid android:color="#ff0000">
+    </solid>
+
+</shape>

+ 8 - 0
Android/app/src/main/res/drawable/setting_url_shape.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
+
+    <corners android:radius="5dp"/>
+
+    <stroke android:width="2dp" android:color="@color/colorTheme"/>
+
+</shape>

+ 219 - 0
Android/app/src/main/res/layout/activity_about.xml

@@ -0,0 +1,219 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<layout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools">
+
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical">
+
+        <android.support.v7.widget.Toolbar
+            android:id="@+id/main_toolbar"
+            android:layout_width="match_parent"
+            android:layout_height="44dp"
+            android:background="@color/colorTheme">
+
+            <TextView
+                android:id="@+id/main_title_tv"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginRight="50dp"
+                android:gravity="center"
+                android:singleLine="true"
+                android:text="关于"
+                android:textColor="@android:color/white"
+                android:textSize="18sp" />
+
+        </android.support.v7.widget.Toolbar>
+
+        <TextView
+            android:id="@+id/textView3"
+            android:layout_width="match_parent"
+            android:layout_height="48dp"
+            android:layout_alignParentBottom="true"
+            android:background="@color/colorTheme"
+            android:gravity="center"
+            android:text="Copyright @ Open.TSINGSEE.com"
+            android:textColor="#FFFFFF" />
+
+        <ScrollView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_above="@id/textView3"
+            android:layout_below="@id/main_toolbar">
+
+            <android.support.constraint.ConstraintLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content">
+
+                <TextView
+                    android:id="@+id/version"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_margin="20dp"
+                    android:text="version:"
+                    android:textSize="18sp"
+                    app:layout_constraintLeft_toLeftOf="parent"
+                    app:layout_constraintRight_toRightOf="parent"
+                    app:layout_constraintTop_toTopOf="parent" />
+
+                <TextView
+                    android:id="@+id/rtmp_title2"
+                    android:layout_width="0dp"
+                    android:layout_height="wrap_content"
+                    android:layout_margin="20dp"
+                    android:text="配套方案推荐"
+                    android:textColor="@color/colorTheme"
+                    app:layout_constraintLeft_toLeftOf="parent"
+                    app:layout_constraintRight_toRightOf="parent"
+                    app:layout_constraintTop_toBottomOf="@+id/version" />
+
+                <TextView
+                    android:id="@+id/rtmp_title"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:text="EasyRTMP推流组件:"
+                    android:textAlignment="center"
+                    app:layout_constraintLeft_toLeftOf="parent"
+                    app:layout_constraintRight_toRightOf="parent"
+                    app:layout_constraintTop_toBottomOf="@+id/rtmp_title2"
+                    app:layout_goneMarginTop="50dp" />
+
+                <ImageView
+                    android:id="@+id/imageView"
+                    android:layout_width="156dp"
+                    android:layout_height="156dp"
+                    android:layout_marginLeft="16dp"
+                    android:layout_marginTop="8dp"
+                    android:layout_marginRight="16dp"
+                    android:src="@drawable/android_rtmp"
+                    app:layout_constraintLeft_toLeftOf="parent"
+                    app:layout_constraintRight_toLeftOf="@+id/guideline"
+                    app:layout_constraintTop_toBottomOf="@+id/rtmp_title" />
+
+                <TextView
+                    android:id="@+id/android_rtmp_title"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginStart="16dp"
+                    android:layout_marginLeft="16dp"
+                    android:layout_marginTop="8dp"
+                    android:layout_marginEnd="16dp"
+                    android:text="Android"
+                    app:layout_constraintLeft_toLeftOf="parent"
+                    app:layout_constraintRight_toLeftOf="@+id/guideline"
+                    app:layout_constraintTop_toBottomOf="@+id/imageView" />
+
+                <android.support.constraint.Guideline
+                    android:id="@+id/guideline"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:orientation="vertical"
+                    app:layout_constraintGuide_percent="0.5" />
+
+
+                <ImageView
+                    android:id="@+id/imageView2"
+                    android:layout_width="156dp"
+                    android:layout_height="156dp"
+                    android:layout_marginLeft="16dp"
+                    android:layout_marginTop="8dp"
+                    android:layout_marginRight="16dp"
+                    android:src="@drawable/ios_rtmp"
+                    app:layout_constraintLeft_toRightOf="@+id/guideline"
+                    app:layout_constraintRight_toRightOf="parent"
+                    app:layout_constraintTop_toBottomOf="@+id/rtmp_title" />
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginStart="16dp"
+                    android:layout_marginLeft="16dp"
+                    android:layout_marginTop="8dp"
+                    android:layout_marginEnd="16dp"
+                    android:text="iOS"
+                    app:layout_constraintLeft_toRightOf="@+id/guideline"
+                    app:layout_constraintRight_toRightOf="parent"
+                    app:layout_constraintTop_toBottomOf="@+id/imageView2" />
+
+                <TextView
+                    android:id="@+id/server_title"
+                    android:layout_width="0dp"
+                    android:layout_height="wrap_content"
+                    android:layout_marginLeft="16dp"
+                    android:layout_marginTop="8dp"
+                    android:layout_marginRight="16dp"
+                    android:text="EasyDSS RTMP流媒体服务器:\nhttp://www.easydss.com"
+                    app:layout_constraintLeft_toLeftOf="parent"
+                    app:layout_constraintRight_toRightOf="parent"
+                    app:layout_constraintTop_toBottomOf="@+id/android_rtmp_title" />
+
+                <TextView
+                    android:id="@+id/player_title"
+                    android:layout_width="0dp"
+                    android:layout_height="wrap_content"
+                    android:layout_marginLeft="16dp"
+                    android:layout_marginTop="8dp"
+                    android:layout_marginRight="16dp"
+                    android:text="EasyPlayerPro全功能播放器:\nhttps://github.com/EasyDSS/EasyPlayerPro"
+                    app:layout_constraintLeft_toLeftOf="parent"
+                    app:layout_constraintRight_toRightOf="parent"
+                    app:layout_constraintTop_toBottomOf="@+id/server_title" />
+
+                <ImageView
+                    android:id="@+id/imageView_player_pro_android"
+                    android:layout_width="156dp"
+                    android:layout_height="156dp"
+                    android:layout_marginLeft="16dp"
+                    android:layout_marginTop="8dp"
+                    android:layout_marginRight="16dp"
+                    android:src="@drawable/android_player_pro"
+                    app:layout_constraintLeft_toLeftOf="parent"
+                    app:layout_constraintRight_toLeftOf="@+id/guideline"
+                    app:layout_constraintTop_toBottomOf="@+id/player_title" />
+
+                <TextView
+                    android:id="@+id/android_player_pro_title"
+                    android:layout_width="wrap_content"
+                    android:layout_height="40dp"
+                    android:layout_marginStart="16dp"
+                    android:layout_marginLeft="16dp"
+                    android:layout_marginEnd="16dp"
+                    android:gravity="center"
+                    android:text="Android"
+                    app:layout_constraintLeft_toLeftOf="parent"
+                    app:layout_constraintRight_toLeftOf="@+id/guideline"
+                    app:layout_constraintTop_toBottomOf="@+id/imageView_player_pro_android" />
+
+                <ImageView
+                    android:id="@+id/imageView_player_pro_ios"
+                    android:layout_width="156dp"
+                    android:layout_height="156dp"
+                    android:layout_marginLeft="16dp"
+                    android:layout_marginTop="8dp"
+                    android:layout_marginRight="16dp"
+                    android:src="@drawable/ios_player_pro"
+                    app:layout_constraintLeft_toRightOf="@+id/guideline"
+                    app:layout_constraintRight_toRightOf="parent"
+                    app:layout_constraintTop_toBottomOf="@+id/player_title" />
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="40dp"
+                    android:layout_marginStart="16dp"
+                    android:layout_marginLeft="16dp"
+                    android:layout_marginEnd="16dp"
+                    android:gravity="center"
+                    android:text="iOS"
+                    app:layout_constraintLeft_toRightOf="@+id/guideline"
+                    app:layout_constraintRight_toRightOf="parent"
+                    app:layout_constraintTop_toBottomOf="@+id/imageView_player_pro_ios" />
+
+
+            </android.support.constraint.ConstraintLayout>
+        </ScrollView>
+
+    </RelativeLayout>
+</layout>

+ 38 - 0
Android/app/src/main/res/layout/activity_media_files.xml

@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    tools:context=".MediaFilesActivity">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical">
+
+        <android.support.v7.widget.Toolbar
+            android:id="@+id/main_toolbar"
+            android:layout_width="match_parent"
+            android:layout_height="44dp"
+            android:background="@color/colorTheme">
+
+            <TextView
+                android:id="@+id/main_title_tv"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:textColor="@android:color/white"
+                android:text="录像"
+                android:gravity="center"
+                android:singleLine="true"
+                android:layout_marginRight="50dp"
+                android:textSize="18sp"/>
+
+        </android.support.v7.widget.Toolbar>
+
+        <android.support.v4.view.ViewPager
+            android:id="@+id/view_pager"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent">
+        </android.support.v4.view.ViewPager>
+
+    </LinearLayout>
+
+</layout>

+ 544 - 0
Android/app/src/main/res/layout/activity_setting.xml

@@ -0,0 +1,544 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical">
+
+        <android.support.v7.widget.Toolbar
+            android:id="@+id/main_toolbar"
+            android:layout_width="match_parent"
+            android:layout_height="44dp"
+            android:background="@color/colorTheme">
+
+            <TextView
+                android:id="@+id/main_title_tv"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginRight="50dp"
+                android:gravity="center"
+                android:singleLine="true"
+                android:text="设置"
+                android:textColor="@android:color/white"
+                android:textSize="18sp" />
+
+        </android.support.v7.widget.Toolbar>
+
+        <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+            xmlns:tools="http://schemas.android.com/tools"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent">
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:focusable="true"
+                android:focusableInTouchMode="true"
+                android:orientation="vertical"
+                android:padding="20dp"
+                tools:context="org.easydarwin.device.org.easygbs.device.SettingActivity">
+
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:background="@drawable/setting_url_shape"
+                    android:orientation="vertical"
+                    android:padding="5dp">
+
+                    <LinearLayout
+                        android:layout_width="match_parent"
+                        android:layout_height="36dp"
+                        android:gravity="center"
+                        android:orientation="horizontal">
+
+                        <TextView
+                            android:layout_width="wrap_content"
+                            android:layout_height="match_parent"
+                            android:gravity="center"
+                            android:paddingLeft="5dp"
+                            android:paddingRight="5dp"
+                            android:text="SIP服务器地址:" />
+
+                        <EditText
+                            android:id="@+id/sip_server_ip"
+                            android:layout_width="0dp"
+                            android:layout_height="match_parent"
+                            android:layout_gravity="center_vertical"
+                            android:layout_weight="1"
+                            android:text=""
+                            android:textColor="#4c4c4c"
+                            android:textSize="12sp" />
+
+                    </LinearLayout>
+
+                    <LinearLayout
+                        android:layout_width="match_parent"
+                        android:layout_height="36dp"
+                        android:gravity="center"
+                        android:orientation="horizontal">
+
+                        <TextView
+                            android:layout_width="wrap_content"
+                            android:layout_height="match_parent"
+                            android:gravity="center"
+                            android:paddingLeft="5dp"
+                            android:paddingRight="5dp"
+                            android:text="SIP服务器端口:" />
+
+                        <EditText
+                            android:id="@+id/sip_server_port"
+                            android:layout_width="0dp"
+                            android:layout_height="match_parent"
+                            android:layout_gravity="center_vertical"
+                            android:layout_weight="1"
+                            android:inputType="number"
+                            android:text=""
+                            android:textColor="#4c4c4c"
+                            android:textSize="12sp" />
+
+                    </LinearLayout>
+
+                    <LinearLayout
+                        android:layout_width="match_parent"
+                        android:layout_height="36dp"
+                        android:gravity="center"
+                        android:orientation="horizontal">
+
+                        <TextView
+                            android:layout_width="wrap_content"
+                            android:layout_height="match_parent"
+                            android:gravity="center"
+                            android:paddingLeft="5dp"
+                            android:paddingRight="5dp"
+                            android:text="本地SIP端口:" />
+
+                        <EditText
+                            android:id="@+id/local_sip_port"
+                            android:layout_width="0dp"
+                            android:layout_height="match_parent"
+                            android:layout_gravity="center_vertical"
+                            android:layout_weight="1"
+                            android:inputType="number"
+                            android:text=""
+                            android:textColor="#4c4c4c"
+                            android:textSize="12sp" />
+
+                    </LinearLayout>
+
+                    <LinearLayout
+                        android:layout_width="match_parent"
+                        android:layout_height="36dp"
+                        android:gravity="center"
+                        android:orientation="horizontal">
+
+                        <TextView
+                            android:layout_width="wrap_content"
+                            android:layout_height="match_parent"
+                            android:gravity="center"
+                            android:paddingLeft="5dp"
+                            android:paddingRight="5dp"
+                            android:text="SIP服务器ID:" />
+
+                        <EditText
+                            android:id="@+id/sip_server_id"
+                            android:layout_width="0dp"
+                            android:layout_height="match_parent"
+                            android:layout_gravity="center_vertical"
+                            android:layout_weight="1"
+                            android:text=""
+                            android:textColor="#4c4c4c"
+                            android:textSize="12sp" />
+
+                    </LinearLayout>
+
+                    <LinearLayout
+                        android:layout_width="match_parent"
+                        android:layout_height="36dp"
+                        android:gravity="center"
+                        android:orientation="horizontal">
+
+                        <TextView
+                            android:layout_width="wrap_content"
+                            android:layout_height="match_parent"
+                            android:gravity="center"
+                            android:paddingLeft="5dp"
+                            android:paddingRight="5dp"
+                            android:text="SIP服务器域:" />
+
+                        <EditText
+                            android:id="@+id/sip_server_domain"
+                            android:layout_width="0dp"
+                            android:layout_height="match_parent"
+                            android:layout_gravity="center_vertical"
+                            android:layout_weight="1"
+                            android:text=""
+                            android:textColor="#4c4c4c"
+                            android:textSize="12sp" />
+
+                    </LinearLayout>
+
+                    <LinearLayout
+                        android:layout_width="match_parent"
+                        android:layout_height="36dp"
+                        android:gravity="center"
+                        android:orientation="horizontal">
+
+                        <TextView
+                            android:layout_width="wrap_content"
+                            android:layout_height="match_parent"
+                            android:gravity="center"
+                            android:paddingLeft="5dp"
+                            android:paddingRight="5dp"
+                            android:text="SIP用户名:" />
+
+                        <EditText
+                            android:id="@+id/sip_device_id"
+                            android:layout_width="0dp"
+                            android:layout_height="match_parent"
+                            android:layout_gravity="center_vertical"
+                            android:layout_weight="1"
+                            android:text=""
+                            android:textColor="#4c4c4c"
+                            android:textSize="12sp" />
+
+                    </LinearLayout>
+
+                    <LinearLayout
+                        android:layout_width="match_parent"
+                        android:layout_height="36dp"
+                        android:gravity="center"
+                        android:orientation="horizontal">
+
+                        <TextView
+                            android:layout_width="wrap_content"
+                            android:layout_height="match_parent"
+                            android:gravity="center"
+                            android:paddingLeft="5dp"
+                            android:paddingRight="5dp"
+                            android:text="SIP用户认证密码:" />
+
+                        <EditText
+                            android:id="@+id/sip_password"
+                            android:layout_width="0dp"
+                            android:layout_height="match_parent"
+                            android:layout_gravity="center_vertical"
+                            android:layout_weight="1"
+                            android:text=""
+                            android:textColor="#4c4c4c"
+                            android:textSize="12sp" />
+
+                    </LinearLayout>
+
+                    <LinearLayout
+                        android:layout_width="match_parent"
+                        android:layout_height="36dp"
+                        android:gravity="center"
+                        android:orientation="horizontal"
+                        android:visibility="gone">
+
+                        <TextView
+                            android:layout_width="wrap_content"
+                            android:layout_height="match_parent"
+                            android:gravity="center"
+                            android:paddingLeft="5dp"
+                            android:paddingRight="5dp"
+                            android:text="传输协议:" />
+
+                        <RadioGroup
+                            android:id="@+id/sip_protocol"
+                            android:layout_width="match_parent"
+                            android:layout_height="wrap_content"
+                            android:orientation="horizontal"
+                            android:paddingLeft="@dimen/activity_horizontal_margin"
+                            android:tooltipText="推送内容">
+
+                            <RadioButton
+                                android:id="@+id/sip_protocol_udp"
+                                android:layout_width="wrap_content"
+                                android:layout_height="wrap_content"
+                                android:checked="true"
+                                android:text="UDP" />
+
+                            <RadioButton
+                                android:id="@+id/sip_protocol_tcp"
+                                android:layout_width="wrap_content"
+                                android:layout_height="wrap_content"
+                                android:checked="false"
+                                android:text="TCP" />
+
+                        </RadioGroup>
+
+                    </LinearLayout>
+
+                    <LinearLayout
+                        android:layout_width="match_parent"
+                        android:layout_height="36dp"
+                        android:gravity="center"
+                        android:orientation="horizontal">
+
+                        <TextView
+                            android:layout_width="wrap_content"
+                            android:layout_height="match_parent"
+                            android:gravity="center"
+                            android:paddingLeft="5dp"
+                            android:paddingRight="5dp"
+                            android:text="注册有效期:" />
+
+                        <EditText
+                            android:id="@+id/sip_reg_expires"
+                            android:layout_width="0dp"
+                            android:layout_height="match_parent"
+                            android:layout_gravity="center_vertical"
+                            android:layout_weight="1"
+                            android:inputType="number"
+                            android:text=""
+                            android:textColor="#4c4c4c"
+                            android:textSize="12sp" />
+
+                        <TextView
+                            android:layout_width="wrap_content"
+                            android:layout_height="match_parent"
+                            android:gravity="center"
+                            android:paddingLeft="5dp"
+                            android:paddingRight="5dp"
+                            android:text="秒" />
+
+                    </LinearLayout>
+
+                    <LinearLayout
+                        android:layout_width="match_parent"
+                        android:layout_height="36dp"
+                        android:gravity="center"
+                        android:orientation="horizontal">
+
+                        <TextView
+                            android:layout_width="wrap_content"
+                            android:layout_height="match_parent"
+                            android:gravity="center"
+                            android:paddingLeft="5dp"
+                            android:paddingRight="5dp"
+                            android:text="心跳周期:" />
+
+                        <EditText
+                            android:id="@+id/sip_heartbeat_interval"
+                            android:layout_width="0dp"
+                            android:layout_height="match_parent"
+                            android:layout_gravity="center_vertical"
+                            android:layout_weight="1"
+                            android:inputType="number"
+                            android:text=""
+                            android:textColor="#4c4c4c"
+                            android:textSize="12sp" />
+
+                        <TextView
+                            android:layout_width="wrap_content"
+                            android:layout_height="match_parent"
+                            android:gravity="center"
+                            android:paddingLeft="5dp"
+                            android:paddingRight="5dp"
+                            android:text="秒" />
+                    </LinearLayout>
+
+                    <LinearLayout
+                        android:layout_width="match_parent"
+                        android:layout_height="36dp"
+                        android:gravity="center"
+                        android:orientation="horizontal">
+
+                        <TextView
+                            android:layout_width="wrap_content"
+                            android:layout_height="match_parent"
+                            android:gravity="center"
+                            android:paddingLeft="5dp"
+                            android:paddingRight="5dp"
+                            android:text="最大心跳超时次数:" />
+
+                        <EditText
+                            android:id="@+id/sip_heartbeat_count"
+                            android:layout_width="0dp"
+                            android:layout_height="match_parent"
+                            android:layout_gravity="center_vertical"
+                            android:layout_weight="1"
+                            android:inputType="number"
+                            android:text=""
+                            android:textColor="#4c4c4c"
+                            android:textSize="12sp" />
+
+                    </LinearLayout>
+
+                </LinearLayout>
+
+                <!--<CheckBox-->
+                <!--android:id="@+id/enable_background_camera_pushing"-->
+                <!--android:layout_width="match_parent"-->
+                <!--android:layout_height="wrap_content"-->
+                <!--android:layout_marginTop="8dp"-->
+                <!--android:checked="false"-->
+                <!--android:text="使能摄像头后台采集" />-->
+
+                <!--<View-->
+                <!--android:layout_width="match_parent"-->
+                <!--android:layout_height="1dp"-->
+                <!--android:layout_marginVertical="5dp"-->
+                <!--android:background="?android:attr/listDivider" />-->
+
+                <!--<TextView-->
+                <!--android:layout_width="match_parent"-->
+                <!--android:layout_height="wrap_content"-->
+                <!--android:paddingLeft="5dp"-->
+                <!--android:paddingRight="5dp"-->
+                <!--android:text="EasyGBS默认使用H.264格式来编码视频。如果需要使用H.265,请勾选下面的单选框。\n注:H.265可能在某些设备上不支持,这种情况下会自动切换为H.264格式" />-->
+
+                <!--<CheckBox-->
+                <!--android:id="@+id/enable_hevc"-->
+                <!--android:layout_width="match_parent"-->
+                <!--android:layout_height="wrap_content"-->
+                <!--android:layout_marginBottom="8dp"-->
+                <!--android:checked="false"-->
+                <!--android:text="使能H.265编码" />-->
+
+                <!--<View-->
+                <!--android:layout_width="match_parent"-->
+                <!--android:layout_height="1dp"-->
+                <!--android:layout_marginVertical="5dp"-->
+                <!--android:background="?android:attr/listDivider" />-->
+
+                <TextView
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_marginTop="20dp"
+                    android:paddingLeft="5dp"
+                    android:paddingRight="5dp"
+                    android:text="EasyGBS默认使用G.711编码推送音频。如果需要测试AAC编码,请勾选下面的单选框。" />
+
+                <CheckBox
+                    android:id="@+id/enable_aac"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_marginBottom="8dp"
+                    android:checked="false"
+                    android:text="AAC编码" />
+
+                <View
+                    android:layout_width="match_parent"
+                    android:layout_height="1dp"
+                    android:layout_marginVertical="5dp"
+                    android:background="?android:attr/listDivider" />
+
+                <TextView
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:paddingLeft="5dp"
+                    android:paddingRight="5dp"
+                    android:text="EasyGBS默认使用硬编码推送视频。如果需要测试软编码,请勾选下面的单选框。\n注:软编码情况下,仅支持H.264编码格式" />
+
+                <CheckBox
+                    android:id="@+id/use_x264_encode"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:checked="false"
+                    android:text="使用软编码" />
+
+                <View
+                    android:layout_width="match_parent"
+                    android:layout_height="1dp"
+                    android:layout_marginVertical="5dp"
+                    android:background="?android:attr/listDivider" />
+
+                <CheckBox
+                    android:id="@+id/enable_video_overlay"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:checked="false"
+                    android:text="叠加水印" />
+
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_marginTop="8dp"
+                    android:paddingLeft="5dp"
+                    android:paddingRight="5dp">
+
+                    <TextView
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:text="码率:" />
+
+                    <TextView
+                        android:id="@+id/bitrate_value"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:text="1023Kbps" />
+
+                </LinearLayout>
+
+                <android.support.v7.widget.AppCompatSeekBar
+                    android:id="@+id/bitrate_seekbar"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_marginTop="8dp"
+                    android:max="100" />
+
+                <TextView
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:paddingLeft="5dp"
+                    android:paddingRight="5dp"
+                    android:text="硬编码码率可能设置值与实际值不完全准" />
+
+
+                <TextView
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_marginTop="@dimen/activity_vertical_margin"
+                    android:paddingLeft="5dp"
+                    android:paddingRight="5dp"
+                    android:text="推送内容:" />
+
+                <RadioGroup
+                    android:id="@+id/push_content"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:orientation="vertical"
+                    android:paddingLeft="@dimen/activity_horizontal_margin"
+                    android:tooltipText="推送内容">
+
+                    <RadioButton
+                        android:id="@+id/push_av"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:checked="true"
+                        android:text="推送音视频" />
+
+                    <RadioButton
+                        android:id="@+id/push_v"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:checked="false"
+                        android:text="仅推送视频" />
+
+                    <RadioButton
+                        android:id="@+id/push_a"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:checked="false"
+                        android:text="仅推送音频" />
+                </RadioGroup>
+
+                <Button
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_marginTop="15dp"
+                    android:background="@color/colorTheme"
+                    android:onClick="onOpenLocalRecord"
+                    android:text="打开录像文件夹"
+                    android:textColor="#ffffff" />
+
+            </LinearLayout>
+        </ScrollView>
+
+    </LinearLayout>
+</layout>

+ 181 - 0
Android/app/src/main/res/layout/activity_stream.xml

@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="#000000"
+    tools:context=".StreamActivity">
+
+    <TextureView
+        android:id="@+id/sv_surfaceview"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:keepScreenOn="true" />
+
+    <TextView
+        android:id="@+id/stream_stat"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_below="@id/res_container"
+        android:paddingLeft="8dp"
+        android:paddingTop="8dp"
+        android:textColor="#fff" />
+
+    <LinearLayout
+        android:id="@+id/res_container"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="8dp"
+        android:gravity="center_vertical"
+        android:orientation="horizontal">
+
+        <TextView
+            android:id="@+id/txt_res"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:onClick="onClickResolution"
+            android:paddingLeft="8dp"
+            android:paddingTop="8dp"
+            android:paddingBottom="8dp"
+            android:text="分辨率:"
+            android:textColor="#ffffff" />
+
+        <Spinner
+            android:id="@+id/spn_resolution"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignTop="@+id/txt_res"
+            android:layout_toRightOf="@+id/txt_res"
+            android:background="#00ffffff" />
+
+        <TextView
+            android:id="@+id/tv_start_record"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:drawableLeft="@drawable/recording_marker_shape"
+            android:drawablePadding="5dp"
+            android:gravity="center_vertical"
+            android:singleLine="true"
+            android:text="00:00"
+            android:textColor="#FF0000"
+            android:visibility="invisible" />
+
+        <ImageView
+            android:id="@+id/btn_switch_orientation"
+            android:layout_width="40dp"
+            android:layout_height="40dp"
+            android:layout_gravity="end|center_vertical"
+            android:layout_margin="4dp"
+            android:onClick="onSwitchOrientation"
+            android:padding="10dp"
+            android:src="@drawable/ic_action_switch_oritation"
+            android:textAlignment="@id/txt_res" />
+
+        <ImageView
+            android:id="@+id/btn_switchCamera"
+            android:layout_width="40dp"
+            android:layout_height="40dp"
+            android:layout_gravity="end|center_vertical"
+            android:layout_margin="4dp"
+            android:onClick="onClick"
+            android:padding="10dp"
+            android:src="@drawable/ic_action_switch_camera"
+            android:textAlignment="@id/txt_res" />
+
+        <ImageView
+            android:id="@+id/toolbar_about"
+            android:layout_width="40dp"
+            android:layout_height="40dp"
+            android:layout_gravity="end|center_vertical"
+            android:layout_margin="4dp"
+            android:onClick="onAbout"
+            android:padding="10dp"
+            android:src="@drawable/green"
+            android:textAlignment="@id/txt_res" />
+    </LinearLayout>
+
+    <LinearLayout
+        android:id="@+id/option_bar_container"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_alignParentBottom="true"
+        android:background="#88ffffff"
+        android:orientation="horizontal"
+        android:paddingTop="6dp"
+        android:paddingBottom="6dp">
+
+        <LinearLayout
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:gravity="center_horizontal"
+            android:onClick="onStartOrStopPush"
+            android:orientation="vertical">
+
+            <ImageView
+                android:id="@+id/streaming_activity_push"
+                android:layout_width="20dp"
+                android:layout_height="20dp"
+                android:src="@drawable/start_push" />
+
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="6dp"
+                android:text="注册"
+                android:textColor="#fff" />
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:gravity="center_horizontal"
+            android:onClick="onRecord"
+            android:orientation="vertical">
+
+            <ImageView
+                android:id="@+id/streaming_activity_record"
+                android:layout_width="20dp"
+                android:layout_height="20dp"
+                android:src="@drawable/record" />
+
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="6dp"
+                android:text="本地录像"
+                android:textColor="#fff" />
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:gravity="center_horizontal"
+            android:onClick="onSetting"
+            android:orientation="vertical">
+
+            <ImageView
+                android:layout_width="20dp"
+                android:layout_height="20dp"
+                android:src="@drawable/settings" />
+
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="6dp"
+                android:text="设置"
+                android:textColor="#fff" />
+        </LinearLayout>
+    </LinearLayout>
+
+    <TextView
+        android:id="@+id/txt_stream_status"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_above="@+id/option_bar_container"
+        android:padding="5dp"
+        android:textColor="#ff0000" />
+</RelativeLayout>

+ 30 - 0
Android/app/src/main/res/layout/float_btn.xml

@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="@dimen/float_btn_width"
+    android:layout_height="@dimen/float_btn_height">
+
+    <ImageView
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:gravity="center"
+        android:scaleType="fitXY"
+        android:src="@drawable/float_button"
+        android:text="推流中" />
+
+    <ImageView
+        android:id="@+id/action_push_error"
+        android:layout_width="25dp"
+        android:layout_height="25dp"
+        android:layout_gravity="right|bottom"
+        android:src="@drawable/ic_action_push_error"
+        android:text="推流中"
+        android:visibility="gone" />
+
+    <TextView
+        android:id="@+id/text"
+        android:layout_width="1dp"
+        android:layout_height="1dp"
+        android:text="111" />
+
+</FrameLayout>

+ 7 - 0
Android/app/src/main/res/layout/fragment_media_file.xml

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android">
+    <android.support.v7.widget.RecyclerView
+        android:layout_width="match_parent"
+        android:id="@+id/recycler"
+        android:layout_height="match_parent"/>
+</layout>

+ 32 - 0
Android/app/src/main/res/layout/image_picker_item.xml

@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <FrameLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+
+        <com.easygbs.device.views.SquareImageView
+            android:id="@+id/image_icon"
+            android:layout_width="match_parent"
+            android:layout_height="128dp"
+            android:layout_margin="1dp"
+            android:adjustViewBounds="true"
+            android:background="#666"
+            android:scaleType="centerCrop" />
+
+        <ImageView
+            android:id="@+id/image_play"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:src="@drawable/new_player"/>
+
+        <CheckBox
+            android:id="@+id/image_checkbox"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="right|top"
+            android:visibility="gone" />
+
+    </FrameLayout>
+</layout>

+ 34 - 0
Android/app/src/main/res/layout/splash_activity.xml

@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="#ffffff"
+    android:orientation="vertical" >
+
+    <ImageView
+        android:id="@+id/logo"
+        android:layout_width="100dp"
+        android:layout_height="100dp"
+        android:layout_centerInParent="true"
+        android:contentDescription="@null"
+        android:src="@mipmap/ic_launcher" />
+
+    <TextView
+        android:id="@+id/txt_version"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_below="@+id/logo"
+        android:layout_marginTop="10dp"
+        android:layout_centerHorizontal="true"
+        android:text="@string/version" />
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentBottom="true"
+        android:layout_centerHorizontal="true"
+        android:layout_marginBottom="10dp"
+        android:gravity="center"
+        android:text="Copyright @ Open.TSINGSEE.com" />
+
+</RelativeLayout>

+ 10 - 0
Android/app/src/main/res/layout/spn_item.xml

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<CheckedTextView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@android:id/text1"
+    style="?android:attr/spinnerDropDownItemStyle"
+    android:singleLine="true"
+    android:layout_width="match_parent"
+    android:textColor="@android:color/white"
+    android:layout_height="wrap_content"
+    android:ellipsize="marquee"/>

+ 5 - 0
Android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@drawable/ic_launcher_background"/>
+    <foreground android:drawable="@mipmap/ic_launcher_foreground"/>
+</adaptive-icon>

+ 5 - 0
Android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@drawable/ic_launcher_background"/>
+    <foreground android:drawable="@mipmap/ic_launcher_foreground"/>
+</adaptive-icon>

BIN
Android/app/src/main/res/mipmap-hdpi/ic_launcher.png


BIN
Android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png


BIN
Android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png


BIN
Android/app/src/main/res/mipmap-mdpi/ic_launcher.png


BIN
Android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png


BIN
Android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png


BIN
Android/app/src/main/res/mipmap-xhdpi/ic_launcher.png


BIN
Android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png


BIN
Android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png


BIN
Android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png


BIN
Android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png


BIN
Android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png


BIN
Android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png


BIN
Android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png


BIN
Android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png


+ 6 - 0
Android/app/src/main/res/values-w820dp/dimens.xml

@@ -0,0 +1,6 @@
+<resources>
+    <!-- Example customization of dimensions originally defined in res/values/dimens.xml
+         (such as screen margins) for screens with more than 820dp of available width. This
+         would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
+    <dimen name="activity_horizontal_margin">64dp</dimen>
+</resources>

+ 7 - 0
Android/app/src/main/res/values/colors.xml

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="colorTheme">#5E90FF</color>
+    <color name="colorGREEN">#2cff1c</color>
+    <color name="colorYELLOW">#eee604</color>
+    <color name="colorRED">#f64a4a</color>
+</resources>

+ 7 - 0
Android/app/src/main/res/values/dimens.xml

@@ -0,0 +1,7 @@
+<resources>
+    <!-- Default screen margins, per the Android Design guidelines. -->
+    <dimen name="activity_horizontal_margin">16dp</dimen>
+    <dimen name="activity_vertical_margin">16dp</dimen>
+    <dimen name="float_btn_height">40dp</dimen>
+    <dimen name="float_btn_width">40dp</dimen>
+</resources>

+ 4 - 0
Android/app/src/main/res/values/ids.xml

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <item name="click_tag" type="id" />
+</resources>

+ 9 - 0
Android/app/src/main/res/values/strings.xml

@@ -0,0 +1,9 @@
+<resources>
+    <string name="app_name">EasyGBD</string>
+    <string name="version">1.3</string>
+    <string name="stream_stat" formatted="false">编码帧率:%d,码率:%dkbps</string>
+    <string name="camera">摄像头</string>
+    <string name="video_uploading_in_background">后台采集视频中</string>
+
+    <string name="info_desc"></string>
+</resources>

+ 18 - 0
Android/app/src/main/res/values/styles.xml

@@ -0,0 +1,18 @@
+<resources>
+
+    <!-- Base application theme. -->
+    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
+        <!-- Customize your theme here. -->
+        <item name="windowActionBar">false</item>
+        <item name="windowNoTitle">true</item>
+        <item name="colorPrimary">@color/colorTheme</item><!--Appbar背景色-->
+        <item name="colorPrimaryDark">@color/colorTheme</item><!--状态栏颜色-->
+        <item name="colorAccent">@color/colorTheme</item><!--控制各个控件被选中时的颜色-->
+        <item name="toolbarStyle">@style/toolbar</item>
+    </style>
+
+    <style name="toolbar" parent="Base.Widget.AppCompat.Toolbar">
+        <item name="android:background">@color/colorTheme</item>
+    </style>
+
+</resources>

+ 4 - 0
Android/app/src/main/res/values/update_strings.xml

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="org_easydarwin_update_authorities">org.easydarwin.easyrtmp.fileprovider</string>
+</resources>

+ 22 - 0
Android/app/src/test/java/org/easydarwin/device/ExampleUnitTest.java

@@ -0,0 +1,22 @@
+/*
+	Copyright (c) 2013-2016 EasyDarwin.ORG.  All rights reserved.
+	Github: https://github.com/EasyDarwin
+	WEChat: EasyDarwin
+	Website: http://www.easydarwin.org
+*/
+
+package org.easydarwin.device;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * To work on unit tests, switch the Test Artifact in the Build Variants view.
+ */
+public class ExampleUnitTest {
+    @Test
+    public void addition_isCorrect() throws Exception {
+        assertEquals(4, 2 + 2);
+    }
+}

+ 46 - 0
Android/build.gradle

@@ -0,0 +1,46 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+    repositories {
+        jcenter()
+        google()
+    }
+
+    dependencies {
+        classpath 'com.android.tools.build:gradle:3.4.1'
+
+        // NOTE: Do not place your application dependencies here; they belong
+        // in the individual module build.gradle files
+    }
+}
+
+allprojects {
+    repositories {
+        google()
+        jcenter()
+        maven { url "https://jitpack.io" }
+    }
+}
+
+task clean(type: Delete) {
+    delete rootProject.buildDir
+}
+
+gradle.projectsEvaluated {
+    tasks.withType(JavaCompile) {
+        options.compilerArgs << "-Xmaxerrs" << "500" // or whatever number you want
+    }
+}
+
+//static def generateVersionCode() {
+//    def result = "git rev-list HEAD --count".execute().text.trim() //unix
+//    if(result.empty) result = "PowerShell -Command git rev-list HEAD --count".execute().text.trim() //windows
+//    if(result.empty) throw new RuntimeException("Could not generate versioncode on this platform? Cmd output: ${result.text}")
+//    return result.toInteger()
+//}
+//static def generateVersionName() {
+//    def result = "git describe".execute().text.trim() //unix
+//    if(result.empty) result = "PowerShell -Command git describe".execute().text.trim() //windows
+//    if(result.empty) throw new RuntimeException("Could not generate versioncode on this platform? Cmd output: ${result.text}")
+//    return result
+//}

BIN
Android/gradle/wrapper/gradle-wrapper.jar


+ 6 - 0
Android/gradle/wrapper/gradle-wrapper.properties

@@ -0,0 +1,6 @@
+#Mon Jan 28 11:28:21 CST 2019
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip

+ 172 - 0
Android/gradlew

@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+    echo "$*"
+}
+
+die () {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+  NONSTOP* )
+    nonstop=true
+    ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Escape application args
+save () {
+    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+    echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+  cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"

Daži faili netika attēloti, jo izmaiņu fails ir pārāk liels