diff --git a/Advanced-Audio-Android/.gitignore b/Advanced-Audio-Android/.gitignore
new file mode 100644
index 0000000..603b140
--- /dev/null
+++ b/Advanced-Audio-Android/.gitignore
@@ -0,0 +1,14 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
diff --git a/Advanced-Audio-Android/.idea/codeStyles/Project.xml b/Advanced-Audio-Android/.idea/codeStyles/Project.xml
new file mode 100644
index 0000000..681f41a
--- /dev/null
+++ b/Advanced-Audio-Android/.idea/codeStyles/Project.xml
@@ -0,0 +1,116 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ xmlns:android
+
+ ^$
+
+
+
+
+
+
+
+
+ xmlns:.*
+
+ ^$
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*:id
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ .*:name
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ name
+
+ ^$
+
+
+
+
+
+
+
+
+ style
+
+ ^$
+
+
+
+
+
+
+
+
+ .*
+
+ ^$
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*
+
+ http://schemas.android.com/apk/res/android
+
+
+ ANDROID_ATTRIBUTE_ORDER
+
+
+
+
+
+
+ .*
+
+ .*
+
+
+ BY_NAME
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Advanced-Audio-Android/.idea/gradle.xml b/Advanced-Audio-Android/.idea/gradle.xml
new file mode 100644
index 0000000..d291b3d
--- /dev/null
+++ b/Advanced-Audio-Android/.idea/gradle.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Advanced-Audio-Android/.idea/misc.xml b/Advanced-Audio-Android/.idea/misc.xml
new file mode 100644
index 0000000..be0cc41
--- /dev/null
+++ b/Advanced-Audio-Android/.idea/misc.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Advanced-Audio-Android/.idea/runConfigurations.xml b/Advanced-Audio-Android/.idea/runConfigurations.xml
new file mode 100644
index 0000000..7f68460
--- /dev/null
+++ b/Advanced-Audio-Android/.idea/runConfigurations.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Advanced-Audio-Android/.idea/vcs.xml b/Advanced-Audio-Android/.idea/vcs.xml
new file mode 100644
index 0000000..6c0b863
--- /dev/null
+++ b/Advanced-Audio-Android/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Advanced-Audio-Android/build-template/build-android.yml b/Advanced-Audio-Android/build-template/build-android.yml
new file mode 100644
index 0000000..e92da54
--- /dev/null
+++ b/Advanced-Audio-Android/build-template/build-android.yml
@@ -0,0 +1,34 @@
+parameters:
+ project: ''
+ module: ''
+ name: ''
+
+jobs:
+
+- job: ${{ parameters.name }}_Build
+ displayName: ${{ parameters.mame }}
+ pool:
+ vmImage: 'macos-latest'
+ steps:
+ - script: cd ${{ parameters.project }} && ls && python ci.env.py
+ env:
+ AGORA_APP_ID: $(agora.appId)
+
+ - task: Gradle@2
+ inputs:
+ workingDirectory: ${{ parameters.project }}/${{ parameters.module }}
+ gradleWrapperFile: ${{ parameters.project }}/gradlew
+ gradleOptions: '-Xmx3072m'
+ publishJUnitResults: false
+ testResultsFiles: '**/TEST-*.xml'
+ tasks: 'assembleDebug'
+
+ - task: CopyFiles@2
+ inputs:
+ Contents: '**/*.apk'
+ TargetFolder: '$(Build.ArtifactStagingDirectory)'
+
+ - task: PublishBuildArtifacts@1
+ inputs:
+ PathtoPublish: '$(Build.ArtifactStagingDirectory)'
+ ArtifactName: ${{ parameters.name }}
\ No newline at end of file
diff --git a/Advanced-Audio-Android/build.gradle b/Advanced-Audio-Android/build.gradle
new file mode 100644
index 0000000..a5bb815
--- /dev/null
+++ b/Advanced-Audio-Android/build.gradle
@@ -0,0 +1,27 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+ repositories {
+ google()
+ jcenter()
+
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:3.5.3'
+
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ google()
+ jcenter()
+
+ }
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
diff --git a/Advanced-Audio-Android/ci.env.py b/Advanced-Audio-Android/ci.env.py
new file mode 100644
index 0000000..8c1a2dd
--- /dev/null
+++ b/Advanced-Audio-Android/ci.env.py
@@ -0,0 +1,22 @@
+#!/usr/bin/python
+# -*- coding: UTF-8 -*-
+import re
+import os
+
+def main():
+ appId = ""
+ if "AGORA_APP_ID" in os.environ:
+ appId = os.environ["AGORA_APP_ID"]
+ token = ""
+
+ f = open("./lib-component/src/main/res/values/strings_config.xml", 'r+')
+ content = f.read()
+ contentNew = re.sub(r'<#YOUR APP ID#>', appId, content)
+ contentNew = re.sub(r'<#YOUR ACCESS TOKEN#>', token, contentNew)
+ f.seek(0)
+ f.write(contentNew)
+ f.truncate()
+
+
+if __name__ == "__main__":
+ main()
\ No newline at end of file
diff --git a/Advanced-Audio-Android/gradle.properties b/Advanced-Audio-Android/gradle.properties
new file mode 100644
index 0000000..199d16e
--- /dev/null
+++ b/Advanced-Audio-Android/gradle.properties
@@ -0,0 +1,20 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx1536m
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app's APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Automatically convert third-party libraries to use AndroidX
+android.enableJetifier=true
+
diff --git a/Advanced-Audio-Android/gradle/wrapper/gradle-wrapper.jar b/Advanced-Audio-Android/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..f6b961f
Binary files /dev/null and b/Advanced-Audio-Android/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/Advanced-Audio-Android/gradle/wrapper/gradle-wrapper.properties b/Advanced-Audio-Android/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..9ffcaba
--- /dev/null
+++ b/Advanced-Audio-Android/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Fri Dec 13 15:32:56 CST 2019
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
diff --git a/Advanced-Audio-Android/gradlew b/Advanced-Audio-Android/gradlew
new file mode 100644
index 0000000..cccdd3d
--- /dev/null
+++ b/Advanced-Audio-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" "$@"
diff --git a/Advanced-Audio-Android/gradlew.bat b/Advanced-Audio-Android/gradlew.bat
new file mode 100644
index 0000000..f955316
--- /dev/null
+++ b/Advanced-Audio-Android/gradlew.bat
@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/Advanced-Audio-Android/lib-component/build.gradle b/Advanced-Audio-Android/lib-component/build.gradle
new file mode 100644
index 0000000..18018f0
--- /dev/null
+++ b/Advanced-Audio-Android/lib-component/build.gradle
@@ -0,0 +1,28 @@
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion 29
+ buildToolsVersion "29.0.2"
+ defaultConfig {
+ minSdkVersion 16
+ targetSdkVersion 29
+ versionCode 1
+ versionName "1.0"
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+}
+
+dependencies {
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
+ implementation 'androidx.appcompat:appcompat:1.1.0'
+ api 'io.agora.rtc:voice-sdk:2.9.2'
+ testImplementation 'junit:junit:4.12'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.0'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
+}
diff --git a/Advanced-Audio-Android/lib-component/proguard-rules.pro b/Advanced-Audio-Android/lib-component/proguard-rules.pro
new file mode 100644
index 0000000..f1b4245
--- /dev/null
+++ b/Advanced-Audio-Android/lib-component/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/Advanced-Audio-Android/lib-component/src/androidTest/java/io/agora/advancedaudio/ExampleInstrumentedTest.java b/Advanced-Audio-Android/lib-component/src/androidTest/java/io/agora/advancedaudio/ExampleInstrumentedTest.java
new file mode 100644
index 0000000..04022a4
--- /dev/null
+++ b/Advanced-Audio-Android/lib-component/src/androidTest/java/io/agora/advancedaudio/ExampleInstrumentedTest.java
@@ -0,0 +1,27 @@
+package io.agora.advancedaudio;
+
+import android.content.Context;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * @see Testing documentation
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+ @Test
+ public void useAppContext() {
+ // Context of the app under test.
+ Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+
+ assertEquals("io.agora.advancedaudio", appContext.getPackageName());
+ }
+}
diff --git a/Advanced-Audio-Android/lib-component/src/main/AndroidManifest.xml b/Advanced-Audio-Android/lib-component/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..1b5d0b4
--- /dev/null
+++ b/Advanced-Audio-Android/lib-component/src/main/AndroidManifest.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Advanced-Audio-Android/lib-component/src/main/java/io/agora/advancedaudio/AgoraApplication.java b/Advanced-Audio-Android/lib-component/src/main/java/io/agora/advancedaudio/AgoraApplication.java
new file mode 100644
index 0000000..d77bb74
--- /dev/null
+++ b/Advanced-Audio-Android/lib-component/src/main/java/io/agora/advancedaudio/AgoraApplication.java
@@ -0,0 +1,38 @@
+package io.agora.advancedaudio;
+
+import android.app.Application;
+
+import io.agora.advancedaudio.component.R;
+import io.agora.advancedaudio.component.rtc.AgoraEventHandler;
+import io.agora.advancedaudio.component.rtc.EventHandler;
+import io.agora.advancedaudio.component.utils.FileUtil;
+import io.agora.rtc.RtcEngine;
+
+public class AgoraApplication extends Application {
+ private RtcEngine mRtcEngine;
+ private AgoraEventHandler mHandler = new AgoraEventHandler();
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ try {
+ mRtcEngine = RtcEngine.create(getApplicationContext(), getString(R.string.agora_app_id), mHandler);
+ mRtcEngine.setChannelProfile(io.agora.rtc.Constants.CHANNEL_PROFILE_LIVE_BROADCASTING);
+ mRtcEngine.setLogFile(FileUtil.initializeLogFile(this));
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ public RtcEngine rtcEngine() { return mRtcEngine; }
+
+ public void registerEventHandler(EventHandler handler) { mHandler.addHandler(handler); }
+
+ public void removeEventHandler(EventHandler handler) { mHandler.removeHandler(handler); }
+
+ @Override
+ public void onTerminate() {
+ super.onTerminate();
+ RtcEngine.destroy();
+ }
+}
diff --git a/Advanced-Audio-Android/lib-component/src/main/java/io/agora/advancedaudio/Constants.java b/Advanced-Audio-Android/lib-component/src/main/java/io/agora/advancedaudio/Constants.java
new file mode 100644
index 0000000..5a3f95b
--- /dev/null
+++ b/Advanced-Audio-Android/lib-component/src/main/java/io/agora/advancedaudio/Constants.java
@@ -0,0 +1,5 @@
+package io.agora.advancedaudio;
+
+public class Constants {
+ public static final String KEY_CHANNEL_NAME = "channel-name";
+}
diff --git a/Advanced-Audio-Android/lib-component/src/main/java/io/agora/advancedaudio/component/activities/BaseActivity.java b/Advanced-Audio-Android/lib-component/src/main/java/io/agora/advancedaudio/component/activities/BaseActivity.java
new file mode 100644
index 0000000..37c0e16
--- /dev/null
+++ b/Advanced-Audio-Android/lib-component/src/main/java/io/agora/advancedaudio/component/activities/BaseActivity.java
@@ -0,0 +1,127 @@
+package io.agora.advancedaudio.component.activities;
+
+import android.os.Bundle;
+import android.util.DisplayMetrics;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.view.Window;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+import io.agora.advancedaudio.AgoraApplication;
+import io.agora.advancedaudio.component.annotations.DisplayActivity;
+import io.agora.advancedaudio.component.rtc.EventHandler;
+import io.agora.advancedaudio.component.utils.WindowUtil;
+import io.agora.rtc.IRtcEngineEventHandler;
+import io.agora.rtc.RtcEngine;
+
+@DisplayActivity(
+SubClasses = {
+ "io.agora.advancedaudio.customrecorder.CustomRecorderActivity"
+})
+public abstract class BaseActivity extends AppCompatActivity implements EventHandler {
+ protected DisplayMetrics mDisplayMetrics = new DisplayMetrics();
+ protected int mStatusBarHeight;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ WindowUtil.hideWindowStatusBar(getWindow());
+ setGlobalLayoutListener();
+ getDisplayMetrics();
+ initStatusBarHeight();
+ }
+
+ private void setGlobalLayoutListener() {
+ final View layout = findViewById(Window.ID_ANDROID_CONTENT);
+ ViewTreeObserver observer = layout.getViewTreeObserver();
+ observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ layout.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+ onGlobalLayoutCompleted();
+ }
+ });
+ }
+
+ /**
+ * Give a chance to obtain view layout attributes when the
+ * content view layout process is completed.
+ * Some layout attributes will be available here but not
+ * in onCreate(), like measured width/height.
+ * This callback will be called ONLY ONCE before the whole
+ * window content is ready to be displayed for first time.
+ */
+ protected void onGlobalLayoutCompleted() {
+
+ }
+
+ private void getDisplayMetrics() {
+ getWindowManager().getDefaultDisplay().getMetrics(mDisplayMetrics);
+ }
+
+ private void initStatusBarHeight() {
+ mStatusBarHeight = WindowUtil.getSystemStatusBarHeight(this);
+ }
+
+ protected AgoraApplication application() {
+ return (AgoraApplication) getApplication();
+ }
+
+ protected RtcEngine rtcEngine() {
+ return application().rtcEngine();
+ }
+
+ protected void registerRtcEventHandler(EventHandler handler) {
+ application().registerEventHandler(handler);
+ }
+
+ protected void removeRtcEventHandler(EventHandler handler) {
+ application().removeEventHandler(handler);
+ }
+
+ @Override
+ public void onLeaveChannel(IRtcEngineEventHandler.RtcStats stats) {
+
+ }
+
+ @Override
+ public void onJoinChannelSuccess(String channel, int uid, int elapsed) {
+
+ }
+
+ @Override
+ public void onUserOffline(int uid, int reason) {
+
+ }
+
+ @Override
+ public void onUserJoined(int uid, int elapsed) {
+
+ }
+
+ @Override
+ public void onLastmileQuality(int quality) {
+
+ }
+
+ @Override
+ public void onLastmileProbeResult(IRtcEngineEventHandler.LastmileProbeResult result) {
+
+ }
+
+ @Override
+ public void onRtcStats(IRtcEngineEventHandler.RtcStats stats) {
+
+ }
+
+ @Override
+ public void onNetworkQuality(int uid, int txQuality, int rxQuality) {
+
+ }
+
+ @Override
+ public void onRemoteAudioStats(IRtcEngineEventHandler.RemoteAudioStats stats) {
+
+ }
+}
diff --git a/Advanced-Audio-Android/lib-component/src/main/java/io/agora/advancedaudio/component/activities/MainActivity.java b/Advanced-Audio-Android/lib-component/src/main/java/io/agora/advancedaudio/component/activities/MainActivity.java
new file mode 100644
index 0000000..361dd3b
--- /dev/null
+++ b/Advanced-Audio-Android/lib-component/src/main/java/io/agora/advancedaudio/component/activities/MainActivity.java
@@ -0,0 +1,302 @@
+package io.agora.advancedaudio.component.activities;
+
+import android.Manifest;
+import android.animation.Animator;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.text.Editable;
+import android.text.TextUtils;
+import android.text.TextWatcher;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.core.app.ActivityCompat;
+import androidx.core.content.ContextCompat;
+
+import java.lang.annotation.Annotation;
+
+import io.agora.advancedaudio.Constants;
+import io.agora.advancedaudio.component.R;
+import io.agora.advancedaudio.component.annotations.DisplayActivity;
+
+public class MainActivity extends BaseActivity {
+ private static final String TAG = MainActivity.class.getSimpleName();
+ private static final int MIN_INPUT_METHOD_HEIGHT = 200;
+ private static final int ANIM_DURATION = 200;
+
+ // Permission request code of any integer value
+ private static final int PERMISSION_REQ_CODE = 1 << 4;
+
+ private String[] PERMISSIONS = {
+ Manifest.permission.RECORD_AUDIO,
+ Manifest.permission.WRITE_EXTERNAL_STORAGE
+ };
+
+ private Rect mVisibleRect = new Rect();
+ private int mLastVisibleHeight = 0;
+ private RelativeLayout mBodyLayout;
+ private int mBodyDefaultMarginTop;
+ private EditText mTopicEdit;
+ private TextView mStartBtn;
+ private ImageView mLogo;
+
+ private Animator.AnimatorListener mLogoAnimListener = new Animator.AnimatorListener() {
+ @Override
+ public void onAnimationStart(Animator animator) {
+ // Do nothing
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ mLogo.setVisibility(View.VISIBLE);
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animator) {
+ mLogo.setVisibility(View.VISIBLE);
+ }
+
+ @Override
+ public void onAnimationRepeat(Animator animator) {
+ // Do nothing
+ }
+ };
+
+ private TextWatcher mTextWatcher = new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
+ // Do nothing
+ }
+
+ @Override
+ public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
+ // Do nothing
+ }
+
+ @Override
+ public void afterTextChanged(Editable editable) {
+ mStartBtn.setEnabled(!TextUtils.isEmpty(editable));
+ }
+ };
+
+ private ViewTreeObserver.OnGlobalLayoutListener mLayoutObserverListener =
+ new ViewTreeObserver.OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ checkInputMethodWindowState();
+ }
+ };
+
+ private void checkInputMethodWindowState() {
+ getWindow().getDecorView().getRootView().getWindowVisibleDisplayFrame(mVisibleRect);
+ int visibleHeight = mVisibleRect.bottom - mVisibleRect.top;
+ if (visibleHeight == mLastVisibleHeight) return;
+
+ boolean inputShown = mDisplayMetrics.heightPixels - visibleHeight > MIN_INPUT_METHOD_HEIGHT;
+ mLastVisibleHeight = visibleHeight;
+
+ // Log.i(TAG, "onGlobalLayout:" + inputShown +
+ // "|" + getWindow().getDecorView().getRootView().getViewTreeObserver());
+
+ // There is no official way to determine whether the
+ // input method dialog has already shown.
+ // This is a workaround, and if the visible content
+ // height is significantly less than the screen height,
+ // we should know that the input method dialog takes
+ // up some screen space.
+ if (inputShown) {
+ if (mLogo.getVisibility() == View.VISIBLE) {
+ mBodyLayout.animate().translationYBy(-mLogo.getMeasuredHeight())
+ .setDuration(ANIM_DURATION).setListener(null).start();
+ mLogo.setVisibility(View.INVISIBLE);
+ }
+ } else if (mLogo.getVisibility() != View.VISIBLE) {
+ mBodyLayout.animate().translationYBy(mLogo.getMeasuredHeight())
+ .setDuration(ANIM_DURATION).setListener(mLogoAnimListener).start();
+ }
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+ initUI();
+ }
+
+ private void initUI() {
+ mBodyLayout = findViewById(R.id.middle_layout);
+ mLogo = findViewById(R.id.main_logo);
+
+ mTopicEdit = findViewById(R.id.topic_edit);
+ mTopicEdit.addTextChangedListener(mTextWatcher);
+
+ mStartBtn = findViewById(R.id.start_broadcast_button);
+ if (TextUtils.isEmpty(mTopicEdit.getText())) mStartBtn.setEnabled(false);
+ }
+
+ @Override
+ protected void onGlobalLayoutCompleted() {
+ adjustViewPositions();
+ }
+
+ private void adjustViewPositions() {
+ RelativeLayout.LayoutParams param;
+
+ // Logo is 0.48 times the screen width
+ param = (RelativeLayout.LayoutParams) mLogo.getLayoutParams();
+ int size = (int) (mDisplayMetrics.widthPixels * 0.48);
+ param.width = size;
+ param.height = size;
+ mLogo.setLayoutParams(param);
+
+ // Bottom margin of the main body should be two times it's top margin.
+ param = (RelativeLayout.LayoutParams) mBodyLayout.getLayoutParams();
+ param.topMargin = (mDisplayMetrics.heightPixels -
+ mBodyLayout.getMeasuredHeight() - mStatusBarHeight) / 3;
+ mBodyLayout.setLayoutParams(param);
+ mBodyDefaultMarginTop = param.topMargin;
+
+ // The width of the start button is roughly 0.72
+ // times the width of the screen
+ mStartBtn = findViewById(R.id.start_broadcast_button);
+ param = (RelativeLayout.LayoutParams) mStartBtn.getLayoutParams();
+ param.width = (int) (mDisplayMetrics.widthPixels * 0.72);
+ mStartBtn.setLayoutParams(param);
+ }
+
+ public void onStartBroadcastClicked(View view) {
+ checkPermission();
+ }
+
+ private void checkPermission() {
+ boolean granted = true;
+ for (String per : PERMISSIONS) {
+ if (!permissionGranted(per)) {
+ granted = false;
+ break;
+ }
+ }
+
+ if (granted) {
+ resetLayoutAndForward();
+ } else {
+ requestPermissions();
+ }
+ }
+
+ private boolean permissionGranted(String permission) {
+ return ContextCompat.checkSelfPermission(
+ this, permission) == PackageManager.PERMISSION_GRANTED;
+ }
+
+ private void requestPermissions() {
+ ActivityCompat.requestPermissions(this, PERMISSIONS, PERMISSION_REQ_CODE);
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) {
+ if (requestCode == PERMISSION_REQ_CODE) {
+ boolean granted = true;
+ for (int result : grantResults) {
+ granted = (result == PackageManager.PERMISSION_GRANTED);
+ if (!granted) break;
+ }
+
+ if (granted) {
+ resetLayoutAndForward();
+ } else {
+ toastNeedPermissions();
+ }
+ }
+ }
+
+ private void resetLayoutAndForward() {
+ closeImeDialogIfNeeded();
+ gotoActivity();
+ }
+
+ private void closeImeDialogIfNeeded() {
+ InputMethodManager manager = (InputMethodManager)
+ getSystemService(Context.INPUT_METHOD_SERVICE);
+ manager.hideSoftInputFromWindow(mTopicEdit.getWindowToken(),
+ InputMethodManager.HIDE_NOT_ALWAYS);
+ }
+
+ public void gotoActivity() {
+ Annotation[] annotations = BaseActivity.class.getDeclaredAnnotations();
+ Class> targetActivity = null;
+ for (Annotation annotation : annotations) {
+ if (annotation instanceof DisplayActivity) {
+ boolean found;
+ String[] targets = ((DisplayActivity) annotation).SubClasses();
+ for (String className : targets) {
+ try {
+ targetActivity = Class.forName(className);
+ found = true;
+ } catch (ClassNotFoundException e) {
+ found = false;
+ }
+
+ if (targetActivity != null && found) {
+ break;
+ }
+ }
+ }
+ }
+
+ if (targetActivity != null) {
+ Intent intent = new Intent(this, targetActivity);
+ intent.putExtra(Constants.KEY_CHANNEL_NAME,
+ mTopicEdit.getText().toString());
+ startActivity(intent);
+ }
+ }
+
+ private void toastNeedPermissions() {
+ Toast.makeText(this, R.string.need_necessary_permissions, Toast.LENGTH_LONG).show();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ resetUI();
+ registerLayoutObserverForSoftKeyboard();
+ }
+
+ private void resetUI() {
+ resetLogo();
+ closeImeDialogIfNeeded();
+ }
+
+ private void resetLogo() {
+ mLogo.setVisibility(View.VISIBLE);
+ mBodyLayout.setY(mBodyDefaultMarginTop);
+ }
+
+ private void registerLayoutObserverForSoftKeyboard() {
+ View view = getWindow().getDecorView().getRootView();
+ ViewTreeObserver observer = view.getViewTreeObserver();
+ observer.addOnGlobalLayoutListener(mLayoutObserverListener);
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ removeLayoutObserverForSoftKeyboard();
+ }
+
+ private void removeLayoutObserverForSoftKeyboard() {
+ View view = getWindow().getDecorView().getRootView();
+ view.getViewTreeObserver().removeOnGlobalLayoutListener(mLayoutObserverListener);
+ }
+}
diff --git a/Advanced-Audio-Android/lib-component/src/main/java/io/agora/advancedaudio/component/annotations/DisplayActivity.java b/Advanced-Audio-Android/lib-component/src/main/java/io/agora/advancedaudio/component/annotations/DisplayActivity.java
new file mode 100644
index 0000000..f54eae3
--- /dev/null
+++ b/Advanced-Audio-Android/lib-component/src/main/java/io/agora/advancedaudio/component/annotations/DisplayActivity.java
@@ -0,0 +1,9 @@
+package io.agora.advancedaudio.component.annotations;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Retention(RetentionPolicy.RUNTIME)
+public @interface DisplayActivity {
+ String[] SubClasses();
+}
diff --git a/Advanced-Audio-Android/lib-component/src/main/java/io/agora/advancedaudio/component/rtc/AgoraEventHandler.java b/Advanced-Audio-Android/lib-component/src/main/java/io/agora/advancedaudio/component/rtc/AgoraEventHandler.java
new file mode 100644
index 0000000..6bb78eb
--- /dev/null
+++ b/Advanced-Audio-Android/lib-component/src/main/java/io/agora/advancedaudio/component/rtc/AgoraEventHandler.java
@@ -0,0 +1,80 @@
+package io.agora.advancedaudio.component.rtc;
+
+import java.util.ArrayList;
+
+import io.agora.rtc.IRtcEngineEventHandler;
+
+public class AgoraEventHandler extends IRtcEngineEventHandler {
+ private ArrayList mHandler = new ArrayList<>();
+
+ public void addHandler(EventHandler handler) {
+ mHandler.add(handler);
+ }
+
+ public void removeHandler(EventHandler handler) {
+ mHandler.remove(handler);
+ }
+
+ @Override
+ public void onJoinChannelSuccess(String channel, int uid, int elapsed) {
+ for (EventHandler handler : mHandler) {
+ handler.onJoinChannelSuccess(channel, uid, elapsed);
+ }
+ }
+
+ @Override
+ public void onLeaveChannel(RtcStats stats) {
+ for (EventHandler handler : mHandler) {
+ handler.onLeaveChannel(stats);
+ }
+ }
+
+ @Override
+ public void onUserJoined(int uid, int elapsed) {
+ for (EventHandler handler : mHandler) {
+ handler.onUserJoined(uid, elapsed);
+ }
+ }
+
+ @Override
+ public void onUserOffline(int uid, int reason) {
+ for (EventHandler handler : mHandler) {
+ handler.onUserOffline(uid, reason);
+ }
+ }
+
+ @Override
+ public void onRtcStats(IRtcEngineEventHandler.RtcStats stats) {
+ for (EventHandler handler : mHandler) {
+ handler.onRtcStats(stats);
+ }
+ }
+
+ @Override
+ public void onNetworkQuality(int uid, int txQuality, int rxQuality) {
+ for (EventHandler handler : mHandler) {
+ handler.onNetworkQuality(uid, txQuality, rxQuality);
+ }
+ }
+
+ @Override
+ public void onRemoteAudioStats(IRtcEngineEventHandler.RemoteAudioStats stats) {
+ for (EventHandler handler : mHandler) {
+ handler.onRemoteAudioStats(stats);
+ }
+ }
+
+ @Override
+ public void onLastmileQuality(int quality) {
+ for (EventHandler handler : mHandler) {
+ handler.onLastmileQuality(quality);
+ }
+ }
+
+ @Override
+ public void onLastmileProbeResult(IRtcEngineEventHandler.LastmileProbeResult result) {
+ for (EventHandler handler : mHandler) {
+ handler.onLastmileProbeResult(result);
+ }
+ }
+}
diff --git a/Advanced-Audio-Android/lib-component/src/main/java/io/agora/advancedaudio/component/rtc/EventHandler.java b/Advanced-Audio-Android/lib-component/src/main/java/io/agora/advancedaudio/component/rtc/EventHandler.java
new file mode 100644
index 0000000..8a856de
--- /dev/null
+++ b/Advanced-Audio-Android/lib-component/src/main/java/io/agora/advancedaudio/component/rtc/EventHandler.java
@@ -0,0 +1,23 @@
+package io.agora.advancedaudio.component.rtc;
+
+import io.agora.rtc.IRtcEngineEventHandler;
+
+public interface EventHandler {
+ void onLeaveChannel(IRtcEngineEventHandler.RtcStats stats);
+
+ void onJoinChannelSuccess(String channel, int uid, int elapsed);
+
+ void onUserOffline(int uid, int reason);
+
+ void onUserJoined(int uid, int elapsed);
+
+ void onLastmileQuality(int quality);
+
+ void onLastmileProbeResult(IRtcEngineEventHandler.LastmileProbeResult result);
+
+ void onRtcStats(IRtcEngineEventHandler.RtcStats stats);
+
+ void onNetworkQuality(int uid, int txQuality, int rxQuality);
+
+ void onRemoteAudioStats(IRtcEngineEventHandler.RemoteAudioStats stats);
+}
diff --git a/Advanced-Audio-Android/lib-component/src/main/java/io/agora/advancedaudio/component/utils/FileUtil.java b/Advanced-Audio-Android/lib-component/src/main/java/io/agora/advancedaudio/component/utils/FileUtil.java
new file mode 100644
index 0000000..1e820bb
--- /dev/null
+++ b/Advanced-Audio-Android/lib-component/src/main/java/io/agora/advancedaudio/component/utils/FileUtil.java
@@ -0,0 +1,34 @@
+package io.agora.advancedaudio.component.utils;
+
+import android.content.Context;
+import android.os.Build;
+import android.os.Environment;
+
+import java.io.File;
+
+public class FileUtil {
+ private static final String LOG_FOLDER_NAME = "log";
+ private static final String LOG_FILE_NAME = "agora-rtc.log";
+
+ /**
+ * Initialize the log folder
+ * @param context Context to find the accessible file folder
+ * @return the absolute path of the log file
+ */
+ public static String initializeLogFile(Context context) {
+ File folder;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ folder = new File(context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS), LOG_FOLDER_NAME);
+ } else {
+ String path = Environment.getExternalStorageDirectory()
+ .getAbsolutePath() + File.separator +
+ context.getPackageName() + File.separator +
+ LOG_FOLDER_NAME;
+ folder = new File(path);
+ if (!folder.exists() && !folder.mkdir()) folder = null;
+ }
+
+ if (folder != null && !folder.exists() && !folder.mkdir()) return "";
+ else return new File(folder, LOG_FILE_NAME).getAbsolutePath();
+ }
+}
diff --git a/Advanced-Audio-Android/lib-component/src/main/java/io/agora/advancedaudio/component/utils/WindowUtil.java b/Advanced-Audio-Android/lib-component/src/main/java/io/agora/advancedaudio/component/utils/WindowUtil.java
new file mode 100644
index 0000000..b5e4f63
--- /dev/null
+++ b/Advanced-Audio-Android/lib-component/src/main/java/io/agora/advancedaudio/component/utils/WindowUtil.java
@@ -0,0 +1,28 @@
+package io.agora.advancedaudio.component.utils;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.os.Build;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+
+public class WindowUtil {
+ public static void hideWindowStatusBar(Window window) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+ window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
+ window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
+ window.setStatusBarColor(Color.TRANSPARENT);
+ } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+ }
+ }
+
+ public static int getSystemStatusBarHeight(Context context) {
+ int id = context.getResources().getIdentifier(
+ "status_bar_height", "dimen", "android");
+ return id > 0 ? context.getResources().getDimensionPixelSize(id) : id;
+ }
+}
diff --git a/Advanced-Audio-Android/lib-component/src/main/res/color/start_broadcast_text_color.xml b/Advanced-Audio-Android/lib-component/src/main/res/color/start_broadcast_text_color.xml
new file mode 100644
index 0000000..956e309
--- /dev/null
+++ b/Advanced-Audio-Android/lib-component/src/main/res/color/start_broadcast_text_color.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/Advanced-Audio-Android/lib-component/src/main/res/drawable-xxhdpi/live_room_bg_logo.png b/Advanced-Audio-Android/lib-component/src/main/res/drawable-xxhdpi/live_room_bg_logo.png
new file mode 100644
index 0000000..ca3defc
Binary files /dev/null and b/Advanced-Audio-Android/lib-component/src/main/res/drawable-xxhdpi/live_room_bg_logo.png differ
diff --git a/Advanced-Audio-Android/lib-component/src/main/res/drawable-xxhdpi/main_logo.png b/Advanced-Audio-Android/lib-component/src/main/res/drawable-xxhdpi/main_logo.png
new file mode 100644
index 0000000..3c2d267
Binary files /dev/null and b/Advanced-Audio-Android/lib-component/src/main/res/drawable-xxhdpi/main_logo.png differ
diff --git a/Advanced-Audio-Android/lib-component/src/main/res/drawable/ic_launcher_background.xml b/Advanced-Audio-Android/lib-component/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..0d025f9
--- /dev/null
+++ b/Advanced-Audio-Android/lib-component/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Advanced-Audio-Android/lib-component/src/main/res/drawable/main_background.xml b/Advanced-Audio-Android/lib-component/src/main/res/drawable/main_background.xml
new file mode 100644
index 0000000..68f88bf
--- /dev/null
+++ b/Advanced-Audio-Android/lib-component/src/main/res/drawable/main_background.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/Advanced-Audio-Android/lib-component/src/main/res/drawable/start_broadcast_bg.xml b/Advanced-Audio-Android/lib-component/src/main/res/drawable/start_broadcast_bg.xml
new file mode 100644
index 0000000..51d749b
--- /dev/null
+++ b/Advanced-Audio-Android/lib-component/src/main/res/drawable/start_broadcast_bg.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/Advanced-Audio-Android/lib-component/src/main/res/drawable/start_broadcast_bg_clicked.xml b/Advanced-Audio-Android/lib-component/src/main/res/drawable/start_broadcast_bg_clicked.xml
new file mode 100644
index 0000000..4a02ac5
--- /dev/null
+++ b/Advanced-Audio-Android/lib-component/src/main/res/drawable/start_broadcast_bg_clicked.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/Advanced-Audio-Android/lib-component/src/main/res/drawable/start_broadcast_bg_normal.xml b/Advanced-Audio-Android/lib-component/src/main/res/drawable/start_broadcast_bg_normal.xml
new file mode 100644
index 0000000..0c24ade
--- /dev/null
+++ b/Advanced-Audio-Android/lib-component/src/main/res/drawable/start_broadcast_bg_normal.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/Advanced-Audio-Android/lib-component/src/main/res/layout/activity_main.xml b/Advanced-Audio-Android/lib-component/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..d16c12b
--- /dev/null
+++ b/Advanced-Audio-Android/lib-component/src/main/res/layout/activity_main.xml
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/Advanced-Audio-Android/lib-component/src/main/res/mipmap-xxhdpi/ic_launcher.png b/Advanced-Audio-Android/lib-component/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..b0907ca
Binary files /dev/null and b/Advanced-Audio-Android/lib-component/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/Advanced-Audio-Android/lib-component/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/Advanced-Audio-Android/lib-component/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..d8ae031
Binary files /dev/null and b/Advanced-Audio-Android/lib-component/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/Advanced-Audio-Android/lib-component/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/Advanced-Audio-Android/lib-component/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..2c18de9
Binary files /dev/null and b/Advanced-Audio-Android/lib-component/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/Advanced-Audio-Android/lib-component/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/Advanced-Audio-Android/lib-component/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..beed3cd
Binary files /dev/null and b/Advanced-Audio-Android/lib-component/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/Advanced-Audio-Android/lib-component/src/main/res/values-zh/strings.xml b/Advanced-Audio-Android/lib-component/src/main/res/values-zh/strings.xml
new file mode 100644
index 0000000..dc5bf79
--- /dev/null
+++ b/Advanced-Audio-Android/lib-component/src/main/res/values-zh/strings.xml
@@ -0,0 +1,9 @@
+
+ Advanced-Audio-Android
+
+
+ 开始直播
+ 输入一个话题
+ Powered by agora.io
+ 缺少必要权限
+
diff --git a/Advanced-Audio-Android/lib-component/src/main/res/values/colors.xml b/Advanced-Audio-Android/lib-component/src/main/res/values/colors.xml
new file mode 100644
index 0000000..8afc89d
--- /dev/null
+++ b/Advanced-Audio-Android/lib-component/src/main/res/values/colors.xml
@@ -0,0 +1,21 @@
+
+
+ #008577
+ #00574B
+ #D81B60
+
+ #DEDEDE
+ #CCCCCC
+ #989898
+ #666666
+ #333333
+
+ #007AFF
+ #44A2FC
+ #0ECFFF
+ #9CD9FF
+
+ #CC262626
+ #66000000
+ #22000000
+
diff --git a/Advanced-Audio-Android/lib-component/src/main/res/values/dimens.xml b/Advanced-Audio-Android/lib-component/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..240bf8b
--- /dev/null
+++ b/Advanced-Audio-Android/lib-component/src/main/res/values/dimens.xml
@@ -0,0 +1,27 @@
+
+
+
+ 16dp
+ 16dp
+
+ 240dp
+ 224dp
+
+ 12sp
+ 14sp
+ 16sp
+ 18sp
+
+
+ 12dp
+ 18dp
+ 36dp
+ 158dp
+ 48dp
+ 42dp
+ 16dp
+ 48dp
+ 25dp
+ 45dp
+ 48dp
+
diff --git a/Advanced-Audio-Android/lib-component/src/main/res/values/strings.xml b/Advanced-Audio-Android/lib-component/src/main/res/values/strings.xml
new file mode 100644
index 0000000..b682cbf
--- /dev/null
+++ b/Advanced-Audio-Android/lib-component/src/main/res/values/strings.xml
@@ -0,0 +1,12 @@
+
+ Advanced-Audio-Android
+
+
+ Start Live Broadcast
+ Pick a topic to chat
+ Powered by agora.io
+ Necessary permissions acquired
+
+
+ abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%&()+,-:;<=.>?@[]^_`{|}~
+
diff --git a/Advanced-Audio-Android/lib-component/src/main/res/values/strings_config.xml b/Advanced-Audio-Android/lib-component/src/main/res/values/strings_config.xml
new file mode 100644
index 0000000..9b04438
--- /dev/null
+++ b/Advanced-Audio-Android/lib-component/src/main/res/values/strings_config.xml
@@ -0,0 +1,10 @@
+
+
+
+
+ <#YOUR APP ID#>
+
+
+
+ <#YOUR ACCESS TOKEN#>
+
diff --git a/Advanced-Audio-Android/lib-component/src/main/res/values/styles.xml b/Advanced-Audio-Android/lib-component/src/main/res/values/styles.xml
new file mode 100644
index 0000000..28aab42
--- /dev/null
+++ b/Advanced-Audio-Android/lib-component/src/main/res/values/styles.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
diff --git a/Advanced-Audio-Android/lib-component/src/test/java/io/agora/advancedaudio/ExampleUnitTest.java b/Advanced-Audio-Android/lib-component/src/test/java/io/agora/advancedaudio/ExampleUnitTest.java
new file mode 100644
index 0000000..307c9fb
--- /dev/null
+++ b/Advanced-Audio-Android/lib-component/src/test/java/io/agora/advancedaudio/ExampleUnitTest.java
@@ -0,0 +1,17 @@
+package io.agora.advancedaudio;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see Testing documentation
+ */
+public class ExampleUnitTest {
+ @Test
+ public void addition_isCorrect() {
+ assertEquals(4, 2 + 2);
+ }
+}
\ No newline at end of file
diff --git a/Advanced-Audio-Android/sample-custom-recorder/.gitignore b/Advanced-Audio-Android/sample-custom-recorder/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/Advanced-Audio-Android/sample-custom-recorder/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/Advanced-Audio-Android/sample-custom-recorder/build.gradle b/Advanced-Audio-Android/sample-custom-recorder/build.gradle
new file mode 100644
index 0000000..37fcfea
--- /dev/null
+++ b/Advanced-Audio-Android/sample-custom-recorder/build.gradle
@@ -0,0 +1,35 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 29
+ buildToolsVersion "29.0.2"
+
+
+ defaultConfig {
+ applicationId "io.agora.advancedaudio.customrecorder"
+ minSdkVersion 16
+ targetSdkVersion 29
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+
+}
+
+dependencies {
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
+
+ implementation 'androidx.appcompat:appcompat:1.1.0'
+ testImplementation 'junit:junit:4.12'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.1'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
+ implementation project(path: ':lib-component')
+}
diff --git a/Advanced-Audio-Android/sample-custom-recorder/proguard-rules.pro b/Advanced-Audio-Android/sample-custom-recorder/proguard-rules.pro
new file mode 100644
index 0000000..f1b4245
--- /dev/null
+++ b/Advanced-Audio-Android/sample-custom-recorder/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/Advanced-Audio-Android/sample-custom-recorder/src/androidTest/java/io/agora/advancedaudio/ExampleInstrumentedTest.java b/Advanced-Audio-Android/sample-custom-recorder/src/androidTest/java/io/agora/advancedaudio/ExampleInstrumentedTest.java
new file mode 100644
index 0000000..04022a4
--- /dev/null
+++ b/Advanced-Audio-Android/sample-custom-recorder/src/androidTest/java/io/agora/advancedaudio/ExampleInstrumentedTest.java
@@ -0,0 +1,27 @@
+package io.agora.advancedaudio;
+
+import android.content.Context;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * @see Testing documentation
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+ @Test
+ public void useAppContext() {
+ // Context of the app under test.
+ Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+
+ assertEquals("io.agora.advancedaudio", appContext.getPackageName());
+ }
+}
diff --git a/Advanced-Audio-Android/sample-custom-recorder/src/main/AndroidManifest.xml b/Advanced-Audio-Android/sample-custom-recorder/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..7688cea
--- /dev/null
+++ b/Advanced-Audio-Android/sample-custom-recorder/src/main/AndroidManifest.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/Advanced-Audio-Android/sample-custom-recorder/src/main/java/io/agora/advancedaudio/customrecorder/CustomRecorder.java b/Advanced-Audio-Android/sample-custom-recorder/src/main/java/io/agora/advancedaudio/customrecorder/CustomRecorder.java
new file mode 100644
index 0000000..658636c
--- /dev/null
+++ b/Advanced-Audio-Android/sample-custom-recorder/src/main/java/io/agora/advancedaudio/customrecorder/CustomRecorder.java
@@ -0,0 +1,45 @@
+package io.agora.advancedaudio.customrecorder;
+
+import android.media.AudioRecord;
+import android.media.MediaRecorder;
+
+class CustomRecorder {
+ private AudioRecord mAudioRecord;
+ private CustomRecorderConfig mConfig;
+
+ CustomRecorder() {
+ mConfig = CustomRecorderConfig.getDefaultConfig();
+ mAudioRecord = new AudioRecord(
+ MediaRecorder.AudioSource.MIC,
+ mConfig.getSampleRate(),
+ mConfig.getChannelCount(),
+ mConfig.getAudioFormat(),
+ mConfig.getBufferSize());
+ }
+
+ void start() {
+ if (mAudioRecord != null) {
+ try {
+ mAudioRecord.startRecording();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ void stop() {
+ if (mAudioRecord != null) {
+ mAudioRecord.stop();
+ mAudioRecord.release();
+ mAudioRecord = null;
+ }
+ }
+
+ CustomRecorderConfig getConfig() {
+ return mConfig;
+ }
+
+ int read(byte[] buffer, int offset, int size) {
+ return mAudioRecord.read(buffer, offset, size);
+ }
+}
diff --git a/Advanced-Audio-Android/sample-custom-recorder/src/main/java/io/agora/advancedaudio/customrecorder/CustomRecorderActivity.java b/Advanced-Audio-Android/sample-custom-recorder/src/main/java/io/agora/advancedaudio/customrecorder/CustomRecorderActivity.java
new file mode 100644
index 0000000..2e1e918
--- /dev/null
+++ b/Advanced-Audio-Android/sample-custom-recorder/src/main/java/io/agora/advancedaudio/customrecorder/CustomRecorderActivity.java
@@ -0,0 +1,90 @@
+package io.agora.advancedaudio.customrecorder;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+
+import io.agora.advancedaudio.Constants;
+import io.agora.advancedaudio.R;
+import io.agora.advancedaudio.component.activities.BaseActivity;
+
+public class CustomRecorderActivity extends BaseActivity {
+ private static final String TAG = CustomRecorderActivity.class.getSimpleName();
+
+ private Intent mServiceIntent;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ initUI();
+ registerRtcEventHandler(this);
+ joinChannel();
+ }
+
+ private void initUI() {
+ setContentView(R.layout.activity_audio_room);
+ findViewById(R.id.live_btn_mute_audio).setActivated(true);
+ }
+
+ private void joinChannel() {
+ String channelName = getIntent().
+ getStringExtra(Constants.KEY_CHANNEL_NAME);
+ rtcEngine().setClientRole(io.agora.rtc.Constants.CLIENT_ROLE_BROADCASTER);
+
+ // Notify the Rtc Engine that we want to use external
+ // audio sources, instead of creating a recorder inside
+ // the engine.
+ // This must be called before joining a channel. So
+ // there must be a way to obtain the recording
+ // parameters globally.
+ // Here we take the default recording configuration
+ // as an example. Developers should implement the
+ // mechanism of their own.
+ CustomRecorderConfig config =
+ CustomRecorderConfig.getDefaultConfig();
+ rtcEngine().setExternalAudioSource(true,
+ config.getSampleRate(),
+ config.getChannelCount());
+ rtcEngine().joinChannel(null, channelName, null, 0);
+ }
+
+ private void leaveChannel() {
+ rtcEngine().leaveChannel();
+ }
+
+ @Override
+ public void onJoinChannelSuccess(String channel, int uid, int elapsed) {
+ Log.i(TAG, "onJoinChannelSuccess " + (uid & 0xFFFFFFFFL));
+ startRecordService();
+ }
+
+ private void startRecordService() {
+ mServiceIntent = new Intent(this, CustomRecorderService.class);
+ startService(mServiceIntent);
+ }
+
+ private void stopRecordService() {
+ if (mServiceIntent != null) {
+ stopService(mServiceIntent);
+ }
+ }
+
+ @Override
+ public void finish() {
+ stopRecordService();
+ removeRtcEventHandler(this);
+ leaveChannel();
+ super.finish();
+ }
+
+ public void onLeaveClicked(View view) {
+ finish();
+ }
+
+ public void onMuteAudioClicked(View view) {
+ boolean activated = view.isActivated();
+ rtcEngine().muteLocalAudioStream(activated);
+ view.setActivated(!activated);
+ }
+}
diff --git a/Advanced-Audio-Android/sample-custom-recorder/src/main/java/io/agora/advancedaudio/customrecorder/CustomRecorderConfig.java b/Advanced-Audio-Android/sample-custom-recorder/src/main/java/io/agora/advancedaudio/customrecorder/CustomRecorderConfig.java
new file mode 100644
index 0000000..4d2701d
--- /dev/null
+++ b/Advanced-Audio-Android/sample-custom-recorder/src/main/java/io/agora/advancedaudio/customrecorder/CustomRecorderConfig.java
@@ -0,0 +1,87 @@
+package io.agora.advancedaudio.customrecorder;
+
+import android.media.AudioFormat;
+import android.media.AudioRecord;
+
+class CustomRecorderConfig {
+ private static final int DEFAULT_SAMPLE_RATE = 16000;
+
+ private int mSampleRate;
+ private int mChannel;
+ private int mAudioFormat;
+ private int mBufferSize;
+
+ private static CustomRecorderConfig sDefaultConfig;
+
+ int getSampleRate() {
+ return mSampleRate;
+ }
+
+ void setSampleRate(int mSampleRate) {
+ this.mSampleRate = mSampleRate;
+ }
+
+ int getChannelCount() {
+ switch (mChannel) {
+ case AudioFormat.CHANNEL_IN_MONO:
+ return 1;
+ case AudioFormat.CHANNEL_IN_STEREO:
+ return 2;
+ default:
+ return 0;
+ }
+ }
+
+ /**
+ * Set audio channel count
+ * @param channel One of AudioFormat.CHANNEL_IN_MONO
+ * or AudioFormat.CHANNEL_IN_STEREO
+ */
+ void setChannel(int channel) {
+ mChannel = channel;
+ }
+
+ int getChannelType() {
+ return mChannel;
+ }
+
+ int getAudioFormat() {
+ return mAudioFormat;
+ }
+
+ void setAudioFormat(int mAudioFormat) {
+ this.mAudioFormat = mAudioFormat;
+ }
+
+ int getBufferSize() {
+ return mBufferSize;
+ }
+
+ void setBufferSize(int mBufferSize) {
+ this.mBufferSize = mBufferSize;
+ }
+
+ /**
+ * Get a default audio recording configuration with:
+ * 1) Sample rate: 16KHz
+ * 2) Channel count: mono (1)
+ * 3) Audio format: AudioFormat.ENCODING_PCM_16BIT
+ * 4) Buffer size: twice the minimum buffer size
+ * @return
+ */
+ static CustomRecorderConfig getDefaultConfig() {
+ if (sDefaultConfig == null) {
+ sDefaultConfig = new CustomRecorderConfig();
+ sDefaultConfig.setSampleRate(DEFAULT_SAMPLE_RATE);
+ sDefaultConfig.setChannel(AudioFormat.CHANNEL_IN_MONO);
+ sDefaultConfig.setAudioFormat(AudioFormat.ENCODING_PCM_16BIT);
+ int bufSize = AudioRecord.getMinBufferSize(
+ sDefaultConfig.getSampleRate(),
+ sDefaultConfig.getChannelType(),
+ sDefaultConfig.getAudioFormat());
+ sDefaultConfig.setBufferSize(bufSize * 2);
+ }
+
+ return sDefaultConfig;
+ }
+}
diff --git a/Advanced-Audio-Android/sample-custom-recorder/src/main/java/io/agora/advancedaudio/customrecorder/CustomRecorderService.java b/Advanced-Audio-Android/sample-custom-recorder/src/main/java/io/agora/advancedaudio/customrecorder/CustomRecorderService.java
new file mode 100644
index 0000000..e8eeb45
--- /dev/null
+++ b/Advanced-Audio-Android/sample-custom-recorder/src/main/java/io/agora/advancedaudio/customrecorder/CustomRecorderService.java
@@ -0,0 +1,138 @@
+package io.agora.advancedaudio.customrecorder;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.Intent;
+import android.media.AudioRecord;
+import android.os.Build;
+import android.os.IBinder;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+import androidx.core.app.NotificationCompat;
+
+import io.agora.advancedaudio.AgoraApplication;
+import io.agora.rtc.RtcEngine;
+
+public class CustomRecorderService extends Service {
+ private static final String TAG = CustomRecorderService.class.getSimpleName();
+
+ private RecordThread mThread;
+ private volatile boolean mStopped;
+
+ public AgoraApplication application() {
+ return (AgoraApplication) getApplication();
+ }
+
+ public RtcEngine rtcEngine() {
+ return application().rtcEngine();
+ }
+
+ @Nullable
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ startForeground();
+ startRecording();
+ return Service.START_STICKY;
+ }
+
+ private void startForeground() {
+ createNotificationChannel();
+ Intent notificationIntent = new Intent(this, CustomRecorderActivity.class);
+ PendingIntent pendingIntent = PendingIntent.getActivity(this,
+ 0, notificationIntent, 0);
+
+ Notification notification = new NotificationCompat.Builder(this, TAG)
+ .setContentTitle(TAG)
+ .setContentIntent(pendingIntent)
+ .build();
+
+ startForeground(1, notification);
+ }
+
+ private void createNotificationChannel() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ NotificationChannel serviceChannel = new NotificationChannel(
+ TAG, TAG, NotificationManager.IMPORTANCE_DEFAULT
+ );
+
+ NotificationManager manager = getSystemService(NotificationManager.class);
+ manager.createNotificationChannel(serviceChannel);
+ }
+ }
+
+ private void startRecording() {
+ mThread = new RecordThread();
+ mThread.start();
+ }
+
+ private void stopRecording() {
+ mStopped = true;
+ }
+
+ @Override
+ public void onDestroy() {
+ stopRecording();
+ super.onDestroy();
+ }
+
+ private class RecordThread extends Thread {
+ private CustomRecorder mRecorder;
+ private byte[] mBuffer;
+
+ RecordThread() {
+ mRecorder = new CustomRecorder();
+ CustomRecorderConfig config = mRecorder.getConfig();
+ mBuffer = new byte[config.getBufferSize()];
+ }
+
+ @Override
+ public void run() {
+ mRecorder.start();
+ while (!mStopped) {
+ int result = mRecorder.read(mBuffer, 0, mBuffer.length);
+ if (result >= 0) {
+ rtcEngine().pushExternalAudioFrame(
+ mBuffer, System.currentTimeMillis());
+ } else {
+ logRecordError(result);
+ }
+ }
+ release();
+ }
+
+ private void logRecordError(int error) {
+ String message = "";
+ switch (error) {
+ case AudioRecord.ERROR:
+ message = "generic operation failure";
+ break;
+ case AudioRecord.ERROR_BAD_VALUE:
+ message = "failure due to the use of an invalid value";
+ break;
+ case AudioRecord.ERROR_DEAD_OBJECT:
+ message = "object is no longer valid and needs to be recreated";
+ break;
+ case AudioRecord.ERROR_INVALID_OPERATION:
+ message = "failure due to the improper use of method";
+ break;
+ }
+ Log.e(TAG, message);
+ }
+
+ private void release() {
+ if (mRecorder != null) {
+ mRecorder.stop();
+ mBuffer = null;
+ }
+ }
+ }
+}
diff --git a/Advanced-Audio-Android/sample-custom-recorder/src/main/res/drawable-xxhdpi/btn_audio_disabled.png b/Advanced-Audio-Android/sample-custom-recorder/src/main/res/drawable-xxhdpi/btn_audio_disabled.png
new file mode 100644
index 0000000..49dda96
Binary files /dev/null and b/Advanced-Audio-Android/sample-custom-recorder/src/main/res/drawable-xxhdpi/btn_audio_disabled.png differ
diff --git a/Advanced-Audio-Android/sample-custom-recorder/src/main/res/drawable-xxhdpi/btn_audio_enabled.png b/Advanced-Audio-Android/sample-custom-recorder/src/main/res/drawable-xxhdpi/btn_audio_enabled.png
new file mode 100644
index 0000000..0e3dae8
Binary files /dev/null and b/Advanced-Audio-Android/sample-custom-recorder/src/main/res/drawable-xxhdpi/btn_audio_enabled.png differ
diff --git a/Advanced-Audio-Android/sample-custom-recorder/src/main/res/drawable-xxhdpi/btn_leave.png b/Advanced-Audio-Android/sample-custom-recorder/src/main/res/drawable-xxhdpi/btn_leave.png
new file mode 100644
index 0000000..c8f42c3
Binary files /dev/null and b/Advanced-Audio-Android/sample-custom-recorder/src/main/res/drawable-xxhdpi/btn_leave.png differ
diff --git a/Advanced-Audio-Android/sample-custom-recorder/src/main/res/drawable-xxhdpi/live_room_bg_logo.png b/Advanced-Audio-Android/sample-custom-recorder/src/main/res/drawable-xxhdpi/live_room_bg_logo.png
new file mode 100644
index 0000000..ca3defc
Binary files /dev/null and b/Advanced-Audio-Android/sample-custom-recorder/src/main/res/drawable-xxhdpi/live_room_bg_logo.png differ
diff --git a/Advanced-Audio-Android/sample-custom-recorder/src/main/res/drawable/btn_mute_audio.xml b/Advanced-Audio-Android/sample-custom-recorder/src/main/res/drawable/btn_mute_audio.xml
new file mode 100644
index 0000000..b5a2b3c
--- /dev/null
+++ b/Advanced-Audio-Android/sample-custom-recorder/src/main/res/drawable/btn_mute_audio.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/Advanced-Audio-Android/sample-custom-recorder/src/main/res/drawable/ic_launcher_background.xml b/Advanced-Audio-Android/sample-custom-recorder/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..0d025f9
--- /dev/null
+++ b/Advanced-Audio-Android/sample-custom-recorder/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Advanced-Audio-Android/sample-custom-recorder/src/main/res/drawable/live_room_bg.xml b/Advanced-Audio-Android/sample-custom-recorder/src/main/res/drawable/live_room_bg.xml
new file mode 100644
index 0000000..c736ef8
--- /dev/null
+++ b/Advanced-Audio-Android/sample-custom-recorder/src/main/res/drawable/live_room_bg.xml
@@ -0,0 +1,13 @@
+
+
+ -
+
+
+
+
+ -
+
+
+
diff --git a/Advanced-Audio-Android/sample-custom-recorder/src/main/res/drawable/rounded_bg_full_transparent.xml b/Advanced-Audio-Android/sample-custom-recorder/src/main/res/drawable/rounded_bg_full_transparent.xml
new file mode 100644
index 0000000..8e47806
--- /dev/null
+++ b/Advanced-Audio-Android/sample-custom-recorder/src/main/res/drawable/rounded_bg_full_transparent.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/Advanced-Audio-Android/sample-custom-recorder/src/main/res/drawable/rounded_bg_half_transparent.xml b/Advanced-Audio-Android/sample-custom-recorder/src/main/res/drawable/rounded_bg_half_transparent.xml
new file mode 100644
index 0000000..8e47806
--- /dev/null
+++ b/Advanced-Audio-Android/sample-custom-recorder/src/main/res/drawable/rounded_bg_half_transparent.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/Advanced-Audio-Android/sample-custom-recorder/src/main/res/layout/activity_audio_room.xml b/Advanced-Audio-Android/sample-custom-recorder/src/main/res/layout/activity_audio_room.xml
new file mode 100644
index 0000000..31c2d73
--- /dev/null
+++ b/Advanced-Audio-Android/sample-custom-recorder/src/main/res/layout/activity_audio_room.xml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
diff --git a/Advanced-Audio-Android/sample-custom-recorder/src/main/res/mipmap-xxhdpi/ic_launcher.png b/Advanced-Audio-Android/sample-custom-recorder/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..b0907ca
Binary files /dev/null and b/Advanced-Audio-Android/sample-custom-recorder/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/Advanced-Audio-Android/sample-custom-recorder/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/Advanced-Audio-Android/sample-custom-recorder/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..d8ae031
Binary files /dev/null and b/Advanced-Audio-Android/sample-custom-recorder/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/Advanced-Audio-Android/sample-custom-recorder/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/Advanced-Audio-Android/sample-custom-recorder/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..2c18de9
Binary files /dev/null and b/Advanced-Audio-Android/sample-custom-recorder/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/Advanced-Audio-Android/sample-custom-recorder/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/Advanced-Audio-Android/sample-custom-recorder/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..beed3cd
Binary files /dev/null and b/Advanced-Audio-Android/sample-custom-recorder/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/Advanced-Audio-Android/sample-custom-recorder/src/main/res/values-zh/strings.xml b/Advanced-Audio-Android/sample-custom-recorder/src/main/res/values-zh/strings.xml
new file mode 100644
index 0000000..432cedb
--- /dev/null
+++ b/Advanced-Audio-Android/sample-custom-recorder/src/main/res/values-zh/strings.xml
@@ -0,0 +1,3 @@
+
+ Custom-Device
+
diff --git a/Advanced-Audio-Android/sample-custom-recorder/src/main/res/values/colors.xml b/Advanced-Audio-Android/sample-custom-recorder/src/main/res/values/colors.xml
new file mode 100644
index 0000000..69b2233
--- /dev/null
+++ b/Advanced-Audio-Android/sample-custom-recorder/src/main/res/values/colors.xml
@@ -0,0 +1,6 @@
+
+
+ #008577
+ #00574B
+ #D81B60
+
diff --git a/Advanced-Audio-Android/sample-custom-recorder/src/main/res/values/dimens.xml b/Advanced-Audio-Android/sample-custom-recorder/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..62c1e0a
--- /dev/null
+++ b/Advanced-Audio-Android/sample-custom-recorder/src/main/res/values/dimens.xml
@@ -0,0 +1,21 @@
+
+
+ 76dp
+ 36dp
+ 15dp
+ 22dp
+ 186dp
+ 48dp
+ 44dp
+ 2dp
+ 6dp
+ 36dp
+ 36dp
+ 24dp
+ 8dp
+ 44dp
+ 18dp
+ 12dp
+ 68dp
+ 180dp
+
diff --git a/Advanced-Audio-Android/sample-custom-recorder/src/main/res/values/strings.xml b/Advanced-Audio-Android/sample-custom-recorder/src/main/res/values/strings.xml
new file mode 100644
index 0000000..403c3ba
--- /dev/null
+++ b/Advanced-Audio-Android/sample-custom-recorder/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ 自定义音频源
+
diff --git a/Advanced-Audio-Android/sample-custom-recorder/src/main/res/values/styles.xml b/Advanced-Audio-Android/sample-custom-recorder/src/main/res/values/styles.xml
new file mode 100644
index 0000000..28aab42
--- /dev/null
+++ b/Advanced-Audio-Android/sample-custom-recorder/src/main/res/values/styles.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
diff --git a/Advanced-Audio-Android/sample-custom-recorder/src/test/java/io/agora/advancedaudio/ExampleUnitTest.java b/Advanced-Audio-Android/sample-custom-recorder/src/test/java/io/agora/advancedaudio/ExampleUnitTest.java
new file mode 100644
index 0000000..307c9fb
--- /dev/null
+++ b/Advanced-Audio-Android/sample-custom-recorder/src/test/java/io/agora/advancedaudio/ExampleUnitTest.java
@@ -0,0 +1,17 @@
+package io.agora.advancedaudio;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see Testing documentation
+ */
+public class ExampleUnitTest {
+ @Test
+ public void addition_isCorrect() {
+ assertEquals(4, 2 + 2);
+ }
+}
\ No newline at end of file
diff --git a/Advanced-Audio-Android/settings.gradle b/Advanced-Audio-Android/settings.gradle
new file mode 100644
index 0000000..08f95ae
--- /dev/null
+++ b/Advanced-Audio-Android/settings.gradle
@@ -0,0 +1,2 @@
+include ':lib-component', ':sample-custom-recorder'
+rootProject.name='Advanced-Audio-Android'
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
new file mode 100644
index 0000000..3d66931
--- /dev/null
+++ b/azure-pipelines.yml
@@ -0,0 +1,17 @@
+trigger:
+- master
+- dev/*
+
+pool:
+ vmImage: 'macos-latest'
+
+variables:
+- group: AgoraKeys
+
+jobs:
+
+- template: Advanced-Audio-Android/build-template/build-android.yml
+ parameters:
+ project: 'Advanced-Audio-Android'
+ module: 'sample-custom-recorder'
+ name: 'CustomRecorder'
\ No newline at end of file