Repository: AnyRTC/RTMPCHybridEngine-Android Branch: github Commit: b4403f3d2851 Files: 100 Total size: 271.6 KB Directory structure: gitextract_zej5ynzh/ ├── .gitignore ├── README.md ├── app/ │ ├── .gitignore │ ├── build.gradle │ ├── gradle/ │ │ └── wrapper/ │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── proguard-rules.pro │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── org/ │ │ └── ar/ │ │ └── rtmpc/ │ │ └── ApplicationTest.java │ ├── main/ │ │ ├── AndroidManifest.xml │ │ ├── java/ │ │ │ └── org/ │ │ │ └── ar/ │ │ │ ├── ARApplication.java │ │ │ ├── BaseActivity.java │ │ │ ├── DeveloperInfo.java │ │ │ ├── SplashActivity.java │ │ │ ├── adapter/ │ │ │ │ ├── AudioLineAdapter.java │ │ │ │ ├── LiveLineAdapter.java │ │ │ │ ├── LiveListAdapter.java │ │ │ │ ├── LiveMessageAdapter.java │ │ │ │ └── LogAdapter.java │ │ │ ├── guest/ │ │ │ │ ├── AudioGuestActivity.java │ │ │ │ ├── GuestActivity.java │ │ │ │ └── LiveListActivity.java │ │ │ ├── hoster/ │ │ │ │ ├── AudioHosterActivity.java │ │ │ │ ├── HosterActivity.java │ │ │ │ └── LineFragment.java │ │ │ ├── model/ │ │ │ │ ├── LineBean.java │ │ │ │ ├── LiveBean.java │ │ │ │ └── MessageBean.java │ │ │ ├── utils/ │ │ │ │ ├── ARUtils.java │ │ │ │ ├── DisplayUtils.java │ │ │ │ ├── MD5.java │ │ │ │ ├── NameUtils.java │ │ │ │ ├── PermissionsCheckUtil.java │ │ │ │ ├── ScreenUtils.java │ │ │ │ └── ToastUtil.java │ │ │ └── widgets/ │ │ │ ├── ARVideoView.java │ │ │ ├── AppBaseDialogFragment.java │ │ │ ├── BaseDialog.java │ │ │ ├── CustomDialog.java │ │ │ └── KeyboardDialogFragment.java │ │ └── res/ │ │ ├── anim/ │ │ │ ├── push_bottom_in.xml │ │ │ └── push_bottom_out.xml │ │ ├── color/ │ │ │ └── select_text_host_input.xml │ │ ├── drawable/ │ │ │ ├── bg_host_head.xml │ │ │ ├── bg_list_item.xml │ │ │ ├── bg_white.xml │ │ │ ├── default_input_bg.xml │ │ │ ├── line_anim.xml │ │ │ ├── list_item_bg.xml │ │ │ ├── selector_apply.xml │ │ │ ├── shape_creat_btn_bg.xml │ │ │ ├── shape_edittext_bg.xml │ │ │ ├── shape_home_green_btn.xml │ │ │ ├── shape_meet_id.xml │ │ │ ├── shape_message.xml │ │ │ ├── shape_popuwindow.xml │ │ │ ├── shape_room_apply_audio_line.xml │ │ │ ├── shape_room_apply_line.xml │ │ │ ├── shape_room_hang_up_line.xml │ │ │ ├── shape_room_member.xml │ │ │ ├── shape_room_message.xml │ │ │ └── shape_room_name.xml │ │ ├── drawable-xxhdpi/ │ │ │ ├── selector_video_manager.xml │ │ │ ├── shape_back_btn.xml │ │ │ ├── shape_index_button.xml │ │ │ └── shape_index_solid_button.xml │ │ ├── layout/ │ │ │ ├── activity_audio_guest.xml │ │ │ ├── activity_audio_hoster.xml │ │ │ ├── activity_guest.xml │ │ │ ├── activity_hoster.xml │ │ │ ├── activity_live_list.xml │ │ │ ├── dialog_base.xml │ │ │ ├── dialogfragment_keyboard.xml │ │ │ ├── empty_act_data.xml │ │ │ ├── empty_no_line_data.xml │ │ │ ├── fragment_line.xml │ │ │ ├── item_audio_line.xml │ │ │ ├── item_line.xml │ │ │ ├── item_line_list.xml │ │ │ ├── item_live.xml │ │ │ ├── item_live_chat.xml │ │ │ ├── item_member.xml │ │ │ ├── item_more_futures.xml │ │ │ └── layout_arvideo.xml │ │ ├── values/ │ │ │ ├── attrs.xml │ │ │ ├── colors.xml │ │ │ ├── dimens.xml │ │ │ ├── ids.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ └── values-w820dp/ │ │ └── dimens.xml │ └── test/ │ └── java/ │ └── org/ │ └── ar/ │ └── rtmpc/ │ └── ExampleUnitTest.java ├── build.gradle ├── gradle/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat └── settings.gradle ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ *.iml .gradle /local.properties /.idea/workspace.xml /.idea/libraries .DS_Store /build /captures # *.iml.gradle/ local.properties/ .idea .DS_Store /build /captures ### Android template # Built application files *.apk *.ap_ # Files for the ART/Dalvik VM *.dex # Java class files *.class # Generated files bin/ gen/ out/ # Gradle files .gradle/ build/ # Local configuration file (sdk path, etc) local.properties # Proguard folder generated by Eclipse proguard/ # Log Files *.log # Android Studio Navigation editor temp files .navigation/ # Android Studio captures folder captures/ # Intellij .idea/ workspace.xml # Keystore files *.jks ================================================ FILE: README.md ================================================ # 重要提醒 anyRTC 对该版本已经不再维护。[前往新版本](https://github.com/anyRTC/ArAndroidSDK). **新版本功能如下:** - 频道管理 - 音频管理 - 视频管理 - 音频文件播放及混音 - 音效文件播放管理 - CDN推流 - 本地推流CDN组件 - 本地播放器组件 - 跨频道流媒体转发 - 直播导入在线媒体流 - 视频双流模式 - 音频自采集自渲染 - 视频自采集自渲染 - 耳返功能 - 。。。 **公司网址: [www.anyrtc.io](https://www.anyrtc.io)** ### anyRTC-RTMPC-Android SDK for Android ### 简介 基于RTMP和RTC混合引擎的在线视频连麦互动直播 Android 直播(网络自适应码率RTMP publisher)、点播播放器(播放器经过专业优化,可实现秒开RTMP Player)、基于RTMP和RTC混合引擎的视频连麦互动(最多支持4人同时互动) ### 优势 - 商业级开源代码,高效稳定 超小内存占有率,移动直播针对性极致优化,代码冗余率极低 - iOS,Web,PC全平台适配,硬件编解码可保证99%的可用性 - 接口极简,推流:2个 拉流:2个 - 底层库C++核心库代码风格采用:Google code style - 极简内核,无需再去深扒复杂的FFMpeg代码 - OpenH264软件编码,FFMpeg软件解码,FAAC/FAAD软件编解码,适配不同系统的硬件编解码统统包含 - 支持SRS、Nginx-RTMP等标准RTMP服务;同时支持各大CDN厂商的接入 ### app体验 ##### [点击下载](http://download.anyrtc.io/xuye) ### SDK集成 # > 方式一[ ![Download](https://api.bintray.com/packages/dyncanyrtc/ar_dev/rtmpc/images/download.svg) ](https://bintray.com/dyncanyrtc/ar_dev/rtmpc/_latestVersion) 添加Jcenter仓库 Gradle依赖: ``` dependencies {  compile 'org.ar:rtmpc_hybrid:3.1.1' } ``` 或者 Maven ``` org.ar rtmpc_hybrid 3.1.1 pom ``` ##### 编译环境 AndroidStudio ##### 运行环境 Android API 16+ 真机运行 ### 如何使用 ##### 注册开发者信息 >如果您还未注册anyRTC开发者账号,请登录[anyRTC官网](http://www.anyrtc.io)注册及获取更多的帮助。 ##### 替换开发者账号 在[anyRTC官网](http://www.anyrtc.io)获取了应用ID,应用Token后,替换DEMO中 **DeveloperInfo**类中的信息即可。推拉流地址需用自己的 ### 操作步骤 1. 演示需要两部以及两部以上的手机,装上该demo. 2. 一部手机创建直播间,另外两部手机在主页,下拉刷新当前直播列表,点击列表进入直播间。 3. 游客端点击链接按钮,进行连麦。 ### 完整文档 SDK集成,API介绍,详见官方完整文档:[点击查看](https://docs.anyrtc.io/v1/RTMPC/android.html) ### iOS 版 互动连麦 [AR-RTMPC-iOS](https://github.com/AnyRTC/anyRTC-RTMPC-iOS) ### 支持的系统平台 **Android** 4.0及以上 ### 支持的CPU架构 **Android** arm64-v8a armeabi armeabi-v7a ### 注意事项 1. RTMPC SDK所有回调均在子线程中,所以在回调中操作UI等,应切换主线程。 2. 注意安卓6.0+动态权限处理。 3. 常见错误代码请参考[错误码查询](https://www.anyrtc.io/resoure) ### 技术支持 - anyRTC官方网址:[https://www.anyrtc.io](https://www.anyrtc.io/resoure) - QQ技术咨询群:554714720 - 联系电话:021-65650071-816 - Email:hi@dync.cc ### 关于直播 本公司有一整套完整直播解决方案。本公司开发者平台www.anyrtc.io。除了基于RTMP协议的直播系统外,我公司还有基于WebRTC的时时交互直播系统、P2P呼叫系统、会议系统等。快捷集成SDK,便可让你的应用拥有时时通话功能。欢迎您的来电~ ### License - RTMPCEngine is available under the MIT license. See the LICENSE file for more info. ================================================ FILE: app/.gitignore ================================================ /build ================================================ FILE: app/build.gradle ================================================ apply plugin: 'com.android.application' android { compileSdkVersion 28 buildToolsVersion '27.0.3' defaultConfig { applicationId "org.ar.rtmpc" minSdkVersion 16 targetSdkVersion 28 versionCode 1 versionName "3.0.0" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } repositories { flatDir { dirs 'libs' } } } dependencies { compile fileTree(include: ['*.jar'], dir: 'libs') compile 'com.android.support:appcompat-v7:28.0.0' compile 'com.android.support:recyclerview-v7:28.0.0' compile 'com.android.support:cardview-v7:28.0.0' compile 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.22' compile 'com.gyf.barlibrary:barlibrary:2.3.0' compile 'com.yanzhenjie.permission:support:2.0.1' compile 'com.jaredrummler:material-spinner:1.1.0' compile 'com.android.support:design:28.0.0' compile 'com.orhanobut:logger:1.15' compile 'com.yanzhenjie.nohttp:okhttp:1.1.11' compile 'org.ar:rtmpc_hybrid:3.1.0' compile 'com.loopj.android:android-async-http:1.4.9' } ================================================ FILE: app/gradle/wrapper/gradle-wrapper.properties ================================================ #Fri Mar 15 15:28:49 CST 2019 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip ================================================ FILE: app/gradlew ================================================ #!/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" "$@" ================================================ FILE: app/gradlew.bat ================================================ @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 ================================================ FILE: app/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # By default, the flags in this file are appended to flags specified # in D:\Android\android-sdk-windows/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 *; #} -keepattributes InnerClasses -dontoptimize -optimizationpasses 7 -dontusemixedcaseclassnames -dontskipnonpubliclibraryclasses -dontpreverify -verbose -optimizations !code/simplification/arithmetic,!field/*,!class/merging/* -keep public class * extends android.app.Activity -keep public class * extends android.app.Application -keep public class * extends android.app.Service -keep public class * extends android.content.BroadcastReceiver -keep public class * extends android.content.ContentProvider -keep public class * extends android.app.backup.BackupAgentHelper -keep public class * extends android.preference.Preference -keep public class com.android.vending.licensing.ILicensingService -keepclasseswithmembernames class * { native ; } -keepclasseswithmembers class * { public (android.content.Context, android.util.AttributeSet); } -keepclasseswithmembers class * { public (android.content.Context, android.util.AttributeSet, int); } -keepclassmembers class * extends android.app.Activity { public void *(android.view.View); } -keep public class * extends android.view.View { public (android.content.Context); public (android.content.Context, android.util.AttributeSet); public (android.content.Context, android.util.AttributeSet, int); public void set*(...); } -keepclassmembers enum * { public static **[] values(); public static ** valueOf(java.lang.String); } -keep class * implements android.os.Parcelable { public static final android.os.Parcelable$Creator *; } -keepnames class * implements java.io.Serializable -keepclassmembers class * implements java.io.Serializable { static final long serialVersionUID; private static final java.io.ObjectStreamField[] serialPersistentFields; !static !transient ; private void writeObject(java.io.ObjectOutputStream); private void readObject(java.io.ObjectInputStream); java.lang.Object writeReplace(); java.lang.Object readResolve(); } -keepattributes *Annotation* -keepattributes Exceptions,InnerClasses,Signature -keepattributes SourceFile,LineNumberTable -keep class **.R$* { *; } -dontwarn android.support.v4.** -keep class android.support.v4.** { *; } -keep interface android.support.v4.** { *; } -keep public class * extends android.support.v4.** -keep public class * extends android.app.Fragment -keep class org.anyrtc.model.** { *; } #anyrtc -dontwarn org.anyrtc.rtmpc_hybrid.** -keep class org.anyrtc.rtmpc_hybrid.**{*;} -dontwarn org.webrtc.** -keep class org.webrtc.**{*;} -keep class com.gyf.barlibrary.* {*;} #Andprermission -keepclassmembers class ** { @com.yanzhenjie.permission.PermissionYes ; } -keepclassmembers class ** { @com.yanzhenjie.permission.PermissionNo ; } #BaseAdapter -keep class com.chad.library.adapter.** { *; } ================================================ FILE: app/src/androidTest/java/org/ar/rtmpc/ApplicationTest.java ================================================ package org.ar.rtmpc; import android.app.Application; import android.test.ApplicationTestCase; /** * Testing Fundamentals */ public class ApplicationTest extends ApplicationTestCase { public ApplicationTest() { super(Application.class); } } ================================================ FILE: app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: app/src/main/java/org/ar/ARApplication.java ================================================ package org.ar; import android.app.Application; import com.yanzhenjie.nohttp.InitializationConfig; import com.yanzhenjie.nohttp.NoHttp; import org.ar.utils.NameUtils; import org.ar.rtmpc_hybrid.ARRtmpcEngine; /** * Created by Skyline on 2016/8/3. */ public class ARApplication extends Application { public static ARApplication mARApplication; private static String NickName=""; public static String LIVE_ID=(int)((Math.random()*9+1)*100000)+"";//直播间ID @Override public void onCreate() { super.onCreate(); mARApplication =this; NickName= NameUtils.getNickName(); ARRtmpcEngine.Inst().initEngine(getApplicationContext(), DeveloperInfo.APPID, DeveloperInfo.APPTOKEN); InitializationConfig config = InitializationConfig.newBuilder(this) .connectionTimeout(15*1000) .readTimeout(15*1000) .retry(1).build(); NoHttp.initialize(config); } public static Application App(){ return mARApplication; } public static String getNickName(){ return NickName; } } ================================================ FILE: app/src/main/java/org/ar/BaseActivity.java ================================================ package org.ar; import android.app.ProgressDialog; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import com.gyf.barlibrary.ImmersionBar; /** * Created by Skyline on 2016/5/24. */ public abstract class BaseActivity extends AppCompatActivity { protected ImmersionBar mImmersionBar; ProgressDialog pd; @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); this.setContentView(this.getLayoutId()); mImmersionBar = ImmersionBar.with(this); mImmersionBar.statusBarDarkFont(true,0.2f).init(); this.initView(savedInstanceState); } private void ProgressDialog(Context ctx) { pd = new ProgressDialog(ctx); pd.setProgressStyle(ProgressDialog.STYLE_SPINNER); pd.setCancelable(true); pd.setCanceledOnTouchOutside(false); pd.show(); } public void showProgressDialog() { if (pd == null) { ProgressDialog(this); } pd.setMessage("正在加载..."); pd.show(); } public void hiddenProgressDialog() { if (pd != null && pd.isShowing()) { pd.cancel(); } } @Override protected void onDestroy() { super.onDestroy(); if (mImmersionBar != null) mImmersionBar.destroy(); } @Override public void setContentView(int layoutResID) { super.setContentView(layoutResID); } public void startAnimActivity(Class cls) { startActivity(new Intent(this, cls)); } public void finishAnimActivity() { finish(); } public void startAnimActivity(Class cls, Bundle bundle) { Intent intent = new Intent(this, cls); intent.putExtras(bundle); startActivity(intent); } public abstract int getLayoutId(); public abstract void initView(Bundle savedInstanceState); } ================================================ FILE: app/src/main/java/org/ar/DeveloperInfo.java ================================================ package org.ar; public class DeveloperInfo { public final static String APPID = ""; public final static String APPTOKEN = ""; public final static String PULL_URL = ""; public final static String PUSH_URL= ""; } ================================================ FILE: app/src/main/java/org/ar/SplashActivity.java ================================================ package org.ar; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import org.ar.guest.LiveListActivity; public class SplashActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); startActivity(new Intent(this,LiveListActivity.class)); finish(); } } ================================================ FILE: app/src/main/java/org/ar/adapter/AudioLineAdapter.java ================================================ package org.ar.adapter; import android.view.View; import android.widget.TextView; import com.chad.library.adapter.base.BaseQuickAdapter; import com.chad.library.adapter.base.BaseViewHolder; import org.ar.rtmpc.R; import org.ar.model.LineBean; /** * Created by liuxiaozhong on 2017-09-25. */ public class AudioLineAdapter extends BaseQuickAdapter { boolean isHost; public AudioLineAdapter(boolean isHost) { super(R.layout.item_audio_line); this.isHost=isHost; } @Override protected void convert(BaseViewHolder helper, LineBean item) { TextView hangup=helper.getView(R.id.tv_hangup); helper.setText(R.id.tv_name,item.name); helper.addOnClickListener(R.id.tv_hangup); if (isHost){ hangup.setVisibility(View.VISIBLE); }else { if (item.isSelf){ hangup.setVisibility(View.VISIBLE); }else { hangup.setVisibility(View.INVISIBLE); } } } } ================================================ FILE: app/src/main/java/org/ar/adapter/LiveLineAdapter.java ================================================ package org.ar.adapter; import android.widget.TextView; import com.chad.library.adapter.base.BaseQuickAdapter; import com.chad.library.adapter.base.BaseViewHolder; import org.ar.rtmpc.R; import org.ar.model.LineBean; /** * Created by liuxiaozhong on 2017/9/24. */ public class LiveLineAdapter extends BaseQuickAdapter { public LiveLineAdapter() { super(R.layout.item_line); } @Override protected void convert(BaseViewHolder helper, LineBean item) { TextView tv_name=helper.getView(R.id.tv_name); tv_name.setText(item.name+"申请连麦"); helper.addOnClickListener(R.id.tv_agree); helper.addOnClickListener(R.id.tv_refuse); } } ================================================ FILE: app/src/main/java/org/ar/adapter/LiveListAdapter.java ================================================ package org.ar.adapter; import android.graphics.drawable.Drawable; import android.widget.TextView; import com.chad.library.adapter.base.BaseQuickAdapter; import com.chad.library.adapter.base.BaseViewHolder; import org.ar.rtmpc.R; import org.ar.model.LiveBean; /** * Created by liuxiaozhong on 2017-09-14. */ public class LiveListAdapter extends BaseQuickAdapter { public LiveListAdapter() { super(R.layout.item_live); } @Override protected void convert(BaseViewHolder helper, LiveBean item) { helper.setText(R.id.tv_name,item.getmLiveTopic()); helper.setText(R.id.tv_num,item.getmMemberNum()+""); TextView tvLiveType = helper.getView(R.id.tv_live_type); Drawable imgVideo = helper.itemView.getContext().getResources().getDrawable( R.drawable.img_video); Drawable imgAdudio = helper.itemView.getContext().getResources().getDrawable( R.drawable.img_audio); if(item.isAudioLive==1) { tvLiveType.setCompoundDrawablesWithIntrinsicBounds(imgAdudio, null, null, null); tvLiveType.setText("音频直播"); } else { tvLiveType.setCompoundDrawablesWithIntrinsicBounds(imgVideo, null, null, null); tvLiveType.setText("视频"); } } } ================================================ FILE: app/src/main/java/org/ar/adapter/LiveMessageAdapter.java ================================================ package org.ar.adapter; import android.graphics.Color; import com.chad.library.adapter.base.BaseQuickAdapter; import com.chad.library.adapter.base.BaseViewHolder; import org.ar.model.MessageBean; /** * Created by liuxiaozhong on 2017-09-22. */ public class LiveMessageAdapter extends BaseQuickAdapter { public LiveMessageAdapter() { super(android.R.layout.simple_list_item_1); } @Override protected void convert(BaseViewHolder helper, MessageBean item) { helper.setTextColor(android.R.id.text1, item.type==1 ? Color.parseColor("#ffffff") : Color.parseColor("#666666")); helper.setText(android.R.id.text1, item.name+":"+item.content); } } ================================================ FILE: app/src/main/java/org/ar/adapter/LogAdapter.java ================================================ package org.ar.adapter; import android.graphics.Color; import android.widget.TextView; import com.chad.library.adapter.base.BaseQuickAdapter; import com.chad.library.adapter.base.BaseViewHolder; /** * Created by liuxiaozhong on 2019/3/12. */ public class LogAdapter extends BaseQuickAdapter { public LogAdapter() { super(android.R.layout.simple_list_item_1); } @Override protected void convert(BaseViewHolder helper, String item) { TextView textView=helper.getView(android.R.id.text1); textView.setTextColor(Color.parseColor("#666666")); helper.setText(android.R.id.text1,item); } } ================================================ FILE: app/src/main/java/org/ar/guest/AudioGuestActivity.java ================================================ package org.ar.guest; import android.content.DialogInterface; import android.graphics.drawable.AnimationDrawable; import android.media.AudioManager; import android.os.Bundle; import android.support.v4.app.DialogFragment; import android.support.v7.app.AlertDialog; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.util.Log; import android.view.KeyEvent; import android.view.View; import android.view.WindowManager; import android.widget.ImageView; import android.widget.RelativeLayout; import android.widget.TextView; import android.widget.Toast; import com.chad.library.adapter.base.BaseQuickAdapter; import org.ar.BaseActivity; import org.ar.ARApplication; import org.ar.adapter.AudioLineAdapter; import org.ar.adapter.LiveMessageAdapter; import org.ar.adapter.LogAdapter; import org.ar.common.utils.ARAudioManager; import org.ar.model.LineBean; import org.ar.model.MessageBean; import org.ar.utils.ARUtils; import org.ar.utils.ToastUtil; import org.ar.widgets.KeyboardDialogFragment; import org.ar.common.enums.ARVideoCommon; import org.ar.rtmpc_hybrid.ARRtmpcEngine; import org.ar.rtmpc_hybrid.ARRtmpcGuestEvent; import org.ar.rtmpc_hybrid.ARRtmpcGuestKit; import org.json.JSONException; import org.json.JSONObject; import java.util.Set; /** * 音频游客界面 */ public class AudioGuestActivity extends BaseActivity implements BaseQuickAdapter.OnItemChildClickListener { TextView tvTitle; RecyclerView rvMsgList,rvLog; TextView tvApplyLine; View viewSpace; TextView tvMemberNum; RecyclerView rvLineList; TextView tvRtmpOk; TextView tvRtmpStatus; TextView tvRtcOk; TextView tvHostName; ImageView ivLineAnim; RelativeLayout rl_log_layout; private ARRtmpcGuestKit mGuestKit; private ARAudioManager mRtmpAudioManager = null; private LiveMessageAdapter mAdapter; private LogAdapter logAdapter; private boolean isApplyLine = false;//是否在连麦、申请连麦 private boolean isLinling = false; private AnimationDrawable hostAnimation; private AudioLineAdapter audioLineAdapter; private String liveId = ""; private String userID="guest"+(int)((Math.random()*9+1)*100000)+""; @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK) { if (isApplyLine) { ShowExitDialog(); return false; } } return super.onKeyDown(keyCode, event); } @Override protected void onDestroy() { super.onDestroy(); /** * 销毁rtmp播放器 */ if (mGuestKit != null) { mGuestKit.clean(); mGuestKit = null; } if (mRtmpAudioManager != null) { mRtmpAudioManager.stop(); mRtmpAudioManager = null; } } @Override public int getLayoutId() { return org.ar.rtmpc.R.layout.activity_audio_guest; } @Override public void initView(Bundle savedInstanceState) { getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); //保持屏幕常亮 viewSpace = findViewById(org.ar.rtmpc.R.id.view_space); mImmersionBar.titleBar(viewSpace).init(); tvTitle = findViewById(org.ar.rtmpc.R.id.tv_title); rl_log_layout=findViewById(org.ar.rtmpc.R.id.rl_log_layout); rvLog=findViewById(org.ar.rtmpc.R.id.rv_log); rvMsgList = findViewById(org.ar.rtmpc.R.id.rv_msg_list); tvApplyLine = findViewById(org.ar.rtmpc.R.id.tv_apply_line); tvMemberNum = findViewById(org.ar.rtmpc.R.id.tv_member_num); rvLineList = findViewById(org.ar.rtmpc.R.id.rv_line_list); tvRtmpOk = findViewById(org.ar.rtmpc.R.id.tv_rtmp_ok); tvRtmpStatus = findViewById(org.ar.rtmpc.R.id.tv_rtmp_status); tvRtcOk = findViewById(org.ar.rtmpc.R.id.tv_rtc_ok); tvHostName = findViewById(org.ar.rtmpc.R.id.tv_host_name); ivLineAnim = findViewById(org.ar.rtmpc.R.id.iv_line_anim); hostAnimation = (AnimationDrawable) ivLineAnim.getBackground(); rvMsgList.setLayoutManager(new LinearLayoutManager(this)); mAdapter = new LiveMessageAdapter(); audioLineAdapter = new AudioLineAdapter(false); audioLineAdapter.setOnItemChildClickListener(this); LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this); linearLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL); rvLineList.setLayoutManager(linearLayoutManager); rvLineList.setAdapter(audioLineAdapter); rvLineList.setItemAnimator(null); rvMsgList.setAdapter(mAdapter); logAdapter = new LogAdapter(); rvLog.setLayoutManager(new LinearLayoutManager(this)); logAdapter.bindToRecyclerView(rvLog); String pullUrl = getIntent().getStringExtra("pullURL"); String hostName=getIntent().getStringExtra("hostName"); tvHostName.setText(hostName); liveId = getIntent().getStringExtra("liveId"); tvTitle.setText("房间ID:" + liveId); mRtmpAudioManager = ARAudioManager.create(this); mRtmpAudioManager.start(new ARAudioManager.AudioManagerEvents() { @Override public void onAudioDeviceChanged(ARAudioManager.AudioDevice audioDevice, Set set) { } }); ARRtmpcEngine.Inst().getGuestOption().setMediaType(ARVideoCommon.ARMediaType.Audio); mGuestKit = new ARRtmpcGuestKit(mGuestListener); mGuestKit.startRtmpPlay(pullUrl, 0); mGuestKit.joinRTCLine("", liveId, userID, getUserData()); } public String getUserData() { JSONObject user = new JSONObject(); try { user.put("isHost", 0); user.put("userId", userID); user.put("nickName", ARApplication.getNickName()); user.put("headUrl", "www.baidu.com"); } catch (JSONException e) { e.printStackTrace(); } return user.toString(); } private void onAudioManagerChangedState() { // TODO(henrika): disable video if // AppRTCAudioManager.AudioDevice.EARPIECE // is active. setVolumeControlStream(AudioManager.STREAM_VOICE_CALL); } private void ShowExitDialog() { AlertDialog.Builder build = new AlertDialog.Builder(this); build.setTitle(org.ar.rtmpc.R.string.str_exit); build.setMessage(org.ar.rtmpc.R.string.str_line_hangup); build.setPositiveButton(org.ar.rtmpc.R.string.str_ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // TODO Auto-generated method stub mGuestKit.hangupRTCLine(); isApplyLine = false; isLinling = false; finishAnimActivity(); } }); build.setNegativeButton(org.ar.rtmpc.R.string.str_cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // TODO Auto-generated method stub } }); build.show(); } /** * 更新列表 * * @param chatMessageBean */ private void addChatMessageList(MessageBean chatMessageBean) { // 150 条 修改; if (chatMessageBean == null) { return; } if (mAdapter.getData().size() < 150) { mAdapter.addData(chatMessageBean); } else { mAdapter.remove(0); mAdapter.addData(chatMessageBean); } rvMsgList.smoothScrollToPosition(mAdapter.getData().size() - 1); } public void printLog(String log){ Log.d("RTMPC", log); logAdapter.addData(log); } /** * 观看直播回调信息接口 */ private ARRtmpcGuestEvent mGuestListener = new ARRtmpcGuestEvent() { /** * rtmp 连接成功 视频即将播放;视频播放前的操作可以在此接口中进行操作 */ @Override public void onRtmpPlayerOk() { AudioGuestActivity.this.runOnUiThread(new Runnable() { @Override public void run() { printLog("回调:onRtmpPlayerOk"); if (tvRtmpOk != null) { tvRtmpOk.setText("Rtmp连接成功"); } } }); } /** * rtmp 开始播放 视频开始播放 */ @Override public void onRtmpPlayerStart() { AudioGuestActivity.this.runOnUiThread(new Runnable() { @Override public void run() { printLog("回调:onRtmpPlayerStart"); } }); } /** * rtmp 当前播放状态 * @param cacheTime 当前缓存时间 * @param curBitrate 当前播放器码流 */ @Override public void onRtmpPlayerStatus(final int cacheTime, final int curBitrate) { AudioGuestActivity.this.runOnUiThread(new Runnable() { @Override public void run() { printLog("回调:onRtmpPlayerStatus cacheTime:" + cacheTime + " curBitrate:" + curBitrate); if (tvRtmpStatus != null) { tvRtmpStatus.setText("当前缓存时间:" + cacheTime + " ms" + "\n当前码流:" + curBitrate / 10024 / 8 + "kb/s"); } } }); } /** * rtmp 播放缓冲区时长 * @param nPercent 缓冲时间 */ @Override public void onRtmpPlayerLoading(final int nPercent) { AudioGuestActivity.this.runOnUiThread(new Runnable() { @Override public void run() { printLog("回调:onRtmpPlayerCache nPercent:" + nPercent); } }); } /** * rtmp 播放器关闭 * @param nCode */ @Override public void onRtmpPlayerClosed(final int nCode) { AudioGuestActivity.this.runOnUiThread(new Runnable() { @Override public void run() { printLog("回调:onRtmpPlayerClosed nCode:" + nCode); } }); } /** * 游客RTC 状态回调 * @param nCode 回调响应码:0:正常;101:主播未开启直播; */ @Override public void onRTCJoinLineResult(final int nCode, String s) { AudioGuestActivity.this.runOnUiThread(new Runnable() { @Override public void run() { printLog("回调:onRTCJoinLineResult nCode:" + nCode); if (nCode == 0) { if (tvRtcOk != null) { tvRtcOk.setText(org.ar.rtmpc.R.string.str_rtc_connect_success); } } else if (nCode == 101) { Toast.makeText(AudioGuestActivity.this, org.ar.rtmpc.R.string.str_hoster_not_live, Toast.LENGTH_LONG).show(); if (tvRtcOk != null) { tvRtcOk.setText(org.ar.rtmpc.R.string.str_rtc_connect_success); } } else { if (tvRtcOk != null) { tvRtcOk.setText(ARUtils.getErrString(nCode)); } } } }); } /** * 游客申请连线结果 * @param nCode 0:申请连线成功;-1:主播拒绝连线 */ @Override public void onRTCApplyLineResult(final int nCode) { AudioGuestActivity.this.runOnUiThread(new Runnable() { @Override public void run() { printLog("回调:onRTCApplyLineResult nCode:" + nCode); if (nCode == 0) { isApplyLine = true; tvApplyLine.setText("挂断"); audioLineAdapter.addData(0, new LineBean("self", "自己", true)); isLinling = true; tvApplyLine.setBackgroundResource(org.ar.rtmpc.R.drawable.shape_room_hang_up_line); } else if (nCode == 601) { Toast.makeText(AudioGuestActivity.this, org.ar.rtmpc.R.string.str_hoster_refused, Toast.LENGTH_LONG).show(); isApplyLine = false; isLinling = false; tvApplyLine.setText("连麦"); tvApplyLine.setBackgroundResource(org.ar.rtmpc.R.drawable.shape_room_apply_line); } } }); } /** * 挂断连线回调 */ @Override public void onRTCHangupLine() { //主播连线断开 AudioGuestActivity.this.runOnUiThread(new Runnable() { @Override public void run() { printLog("回调:onRTCHangupLine "); mGuestKit.hangupRTCLine(); audioLineAdapter.remove(0); tvApplyLine.setText(org.ar.rtmpc.R.string.str_connect_hoster); tvApplyLine.setBackgroundResource(org.ar.rtmpc.R.drawable.shape_room_apply_line); isApplyLine = false; isLinling = false; } }); } @Override public void onRTCOpenRemoteVideoRender(String s, String s1, String s2, String s3) { } @Override public void onRTCCloseRemoteVideoRender(String s, String s1, String s2) { } @Override public void onRTCOpenRemoteAudioLine(final String strLivePeerId, final String strUserId, final String strUserData) { AudioGuestActivity.this.runOnUiThread(new Runnable() { @Override public void run() { printLog("回调:onRTCOpenAudioLine strLivePeerId:" + strLivePeerId + "strUserId:" + strUserId + " strUserData:" + strUserData); try { JSONObject jsonObject = new JSONObject(strUserData); if (strLivePeerId.equals("RTMPC_Line_Hoster")) { // audioLineAdapter.addData(new LineBean(strLivePeerId, jsonObject.getString("nickName"), true)); } else { audioLineAdapter.addData(new LineBean(strLivePeerId, jsonObject.getString("nickName"), false)); } } catch (JSONException e) { e.printStackTrace(); } } }); } @Override public void onRTCCloseRemoteAudioLine(final String strLivePeerId, final String strUserId) { AudioGuestActivity.this.runOnUiThread(new Runnable() { @Override public void run() { printLog("回调:onRTCCloseAudioLine strLivePeerId:" + strLivePeerId + "strUserId:" + strUserId); int index = 9; for (int i = 0; i < audioLineAdapter.getData().size(); i++) { if (audioLineAdapter.getItem(i).peerId.equals(strLivePeerId)) { index = i; } } if (index != 9 && index <= audioLineAdapter.getData().size()) { audioLineAdapter.remove(index); } } }); } @Override public void onRTCLocalAudioActive(int i) { AudioGuestActivity.this.runOnUiThread(new Runnable() { @Override public void run() { Log.d("RTMPC", "onRTCLocalAudioActive"); } }); } @Override public void onRTCHosterAudioActive(int i) { AudioGuestActivity.this.runOnUiThread(new Runnable() { @Override public void run() { Log.d("RTMPC", "onRTCHosterAudioActive"); } }); } @Override public void onRTCRemoteAudioActive(final String strLivePeerId, final String strUserId, final int nTime) { AudioGuestActivity.this.runOnUiThread(new Runnable() { @Override public void run() { printLog("回调:onRTCAudioActive strLivePeerId:" + strLivePeerId + "strUserId:" + strUserId + " nTime:" + nTime); if (strLivePeerId.equals("RTMPC_Hoster")) {//主播 ivLineAnim.setVisibility(View.VISIBLE); hostAnimation.start(); } else { for (int i = 0; i < audioLineAdapter.getData().size(); i++) { if (strLivePeerId.equals(audioLineAdapter.getData().get(i).peerId)) { audioLineAdapter.getItem(i).setStartAnim(true); audioLineAdapter.notifyItemChanged(i); } } } } }); } @Override public void onRTCRemoteAVStatus(final String s, boolean b, boolean b1) { AudioGuestActivity.this.runOnUiThread(new Runnable() { @Override public void run() { printLog("回调:onRTCRemoteAVStatus peerID:" + s); } }); } /** * 主播已离开回调 * @param nCode */ @Override public void onRTCLineLeave(final int nCode, String s) { //主播关闭直播 AudioGuestActivity.this.runOnUiThread(new Runnable() { @Override public void run() { printLog("回调:onRTCLineLeave nCode:" + nCode); if (mGuestKit != null) { mGuestKit.stopRtmpPlay(); } finishAnimActivity(); ToastUtil.show("主播已离开"); } }); } /** * 消息回调 * @param strCustomID 消息的发送者id * @param strCustomName 消息的发送者昵称 * @param strCustomHeader 消息的发送者头像url * @param strMessage 消息内容 */ @Override public void onRTCUserMessage(final int nType, final String strCustomID, final String strCustomName, final String strCustomHeader, final String strMessage) { AudioGuestActivity.this.runOnUiThread(new Runnable() { @Override public void run() { printLog("回调:onRTCUserMessage nType:" + nType + "strCustomID:" + strCustomID + "strCustomName:" + strCustomName + "strCustomHeader:" + strCustomHeader + "strMessage:" + strMessage); addChatMessageList(new MessageBean(MessageBean.AUDIO, strCustomName, strMessage)); } }); } /** * 观看直播的总人数回调 * @param totalMembers 观看直播的总人数 */ @Override public void onRTCMemberNotify(final String strServerId, final String strRoomId, final int totalMembers) { AudioGuestActivity.this.runOnUiThread(new Runnable() { @Override public void run() { printLog("回调:onRTCMemberNotify strServerId:" + strServerId + "strRoomId:" + strRoomId + "totalMembers:" + totalMembers); tvMemberNum.setText("在线观看人数" + totalMembers + ""); } }); } }; public void onClick(View view) { switch (view.getId()) { case org.ar.rtmpc.R.id.btn_close: if (isLinling) { ShowExitDialog(); } else { finishAnimActivity(); } break; case org.ar.rtmpc.R.id.iv_message: showChatLayout(); break; case org.ar.rtmpc.R.id.tv_apply_line: if (isApplyLine) { if (mGuestKit != null) { mGuestKit.hangupRTCLine(); if (isLinling){ audioLineAdapter.remove(0); } tvApplyLine.setText("连麦"); tvApplyLine.setBackgroundResource(org.ar.rtmpc.R.drawable.shape_room_apply_line); isApplyLine = false; isLinling = false; } } else { if (mGuestKit != null) { mGuestKit.applyRTCLine(); tvApplyLine.setText("挂断"); tvApplyLine.setBackgroundResource(org.ar.rtmpc.R.drawable.shape_room_hang_up_line); isApplyLine = true; isLinling = false; } } break; case org.ar.rtmpc.R.id.btn_log: rl_log_layout.setVisibility(View.VISIBLE); break; case org.ar.rtmpc.R.id.ibtn_close_log: rl_log_layout.setVisibility(View.GONE); break; } } private void showChatLayout() { KeyboardDialogFragment keyboardDialogFragment = new KeyboardDialogFragment(); keyboardDialogFragment.show(getSupportFragmentManager(), "KeyboardDialogFragment"); keyboardDialogFragment.setEdittextListener(new KeyboardDialogFragment.EdittextListener() { @Override public void setTextStr(String text) { addChatMessageList(new MessageBean(MessageBean.AUDIO, ARApplication.getNickName(), text)); mGuestKit.sendMessage(0, ARApplication.getNickName(), "", text); } @Override public void dismiss(DialogFragment dialogFragment) { } }); } @Override public void onItemChildClick(BaseQuickAdapter adapter, View view, int position) { switch (view.getId()) { case org.ar.rtmpc.R.id.tv_hangup: if (mGuestKit != null) { mGuestKit.hangupRTCLine(); tvApplyLine.setBackgroundResource(org.ar.rtmpc.R.drawable.shape_room_apply_line); tvApplyLine.setText("连麦"); audioLineAdapter.remove(0); isApplyLine = false; isLinling = false; } break; } } } ================================================ FILE: app/src/main/java/org/ar/guest/GuestActivity.java ================================================ package org.ar.guest; import android.annotation.SuppressLint; import android.content.DialogInterface; import android.media.AudioManager; import android.os.Bundle; import android.support.v4.app.DialogFragment; import android.support.v7.app.AlertDialog; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.util.Log; import android.view.Gravity; import android.view.KeyEvent; import android.view.View; import android.view.WindowManager; import android.widget.ImageButton; import android.widget.LinearLayout; import android.widget.RelativeLayout; import android.widget.TextView; import android.widget.Toast; import org.ar.BaseActivity; import org.ar.ARApplication; import org.ar.adapter.LiveMessageAdapter; import org.ar.adapter.LogAdapter; import org.ar.common.utils.ARAudioManager; import org.ar.model.MessageBean; import org.ar.rtmpc.R; import org.ar.utils.ARUtils; import org.ar.utils.ToastUtil; import org.ar.widgets.ARVideoView; import org.ar.widgets.KeyboardDialogFragment; import org.ar.common.enums.ARVideoCommon; import org.ar.rtmpc_hybrid.ARRtmpcEngine; import org.ar.rtmpc_hybrid.ARRtmpcGuestEvent; import org.ar.rtmpc_hybrid.ARRtmpcGuestKit; import org.json.JSONException; import org.json.JSONObject; import org.webrtc.VideoRenderer; import java.util.Set; /** * 视频游客页面 */ public class GuestActivity extends BaseActivity { RelativeLayout rlRtmpcVideos,rl_log_layout; TextView tvTitle; TextView tvRtmpOk; TextView tvRtmpStatus; TextView tvRtcOk; RecyclerView rvMsgList,rvLog; TextView tvApplyLine; View viewSpace; TextView tvMemberNum; ImageButton ibtnCamera; private ARRtmpcGuestKit mGuestKit; private ARVideoView mVideoView; private ARAudioManager mRtmpAudioManager = null; private LiveMessageAdapter mAdapter; private LogAdapter logAdapter; private boolean isApplyLine = false;//是否申请连麦 private boolean isLining = false;//是否正在连麦 private String liveId = ""; private String userId="guest"+(int)((Math.random()*9+1)*100000)+""; @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK) { if (isApplyLine) { ShowExitDialog(); return false; } } return super.onKeyDown(keyCode, event); } @Override protected void onDestroy() { super.onDestroy(); if (mRtmpAudioManager != null) { mRtmpAudioManager.stop(); mRtmpAudioManager = null; } /** * 销毁rtmp播放器 */ if (mGuestKit != null) { mGuestKit.clean(); mVideoView.removeLocalVideoRender(); mGuestKit = null; } } @Override public int getLayoutId() { return org.ar.rtmpc.R.layout.activity_guest; } @Override protected void onCreate(Bundle savedInstanceState) { getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); //保持屏幕常亮 super.onCreate(savedInstanceState); } @Override public void initView(Bundle savedInstanceState) { viewSpace=findViewById(org.ar.rtmpc.R.id.view_space); mImmersionBar.titleBar(viewSpace).init(); ibtnCamera=findViewById(org.ar.rtmpc.R.id.btn_camare); rlRtmpcVideos=findViewById(org.ar.rtmpc.R.id.rl_rtmpc_videos); rl_log_layout=findViewById(org.ar.rtmpc.R.id.rl_log_layout); rvLog=findViewById(org.ar.rtmpc.R.id.rv_log); tvTitle=findViewById(org.ar.rtmpc.R.id.tv_title); tvRtmpOk=findViewById(org.ar.rtmpc.R.id.tv_rtmp_ok); tvRtmpStatus=findViewById(org.ar.rtmpc.R.id.tv_rtmp_status); tvRtcOk=findViewById(org.ar.rtmpc.R.id.tv_rtc_ok); rvMsgList=findViewById(org.ar.rtmpc.R.id.rv_msg_list); tvApplyLine=findViewById(org.ar.rtmpc.R.id.tv_apply_line); tvMemberNum=findViewById(org.ar.rtmpc.R.id.tv_member_num); logAdapter = new LogAdapter(); rvLog.setLayoutManager(new LinearLayoutManager(this)); logAdapter.bindToRecyclerView(rvLog); rvMsgList.setLayoutManager(new LinearLayoutManager(this)); mAdapter = new LiveMessageAdapter(); rvMsgList.setAdapter(mAdapter); ARRtmpcEngine.Inst().getGuestOption().setDefaultFrontCamera(true); ARRtmpcEngine.Inst().getGuestOption().setMediaType(ARVideoCommon.ARMediaType.Video); String pullUrl = getIntent().getStringExtra("pullURL"); liveId = getIntent().getStringExtra("liveId"); tvTitle.setText("房间ID:" +liveId); mVideoView = new ARVideoView( rlRtmpcVideos, ARRtmpcEngine.Inst().egl(), this,false, false); mVideoView.setVideoViewLayout(false, Gravity.RIGHT, LinearLayout.VERTICAL); mVideoView.setVideoLayoutOnclickEvent(new ARVideoView.VideoLayoutOnclickEvent() { @Override public void onCloseVideoRender(View view, String strPeerId) { /** * 挂断连线 */ mGuestKit.hangupRTCLine(); mVideoView.removeRemoteRender("LocalCameraRender"); tvApplyLine.setText(org.ar.rtmpc.R.string.str_connect_hoster); tvApplyLine.setBackgroundResource(org.ar.rtmpc.R.drawable.shape_room_apply_line); isApplyLine = false; isLining=false; } }); mRtmpAudioManager = ARAudioManager.create(this); mRtmpAudioManager.start(new ARAudioManager.AudioManagerEvents() { @Override public void onAudioDeviceChanged(ARAudioManager.AudioDevice audioDevice, Set set) { } }); ARRtmpcEngine.Inst().getGuestOption().setMediaType(ARVideoCommon.ARMediaType.Video); mGuestKit = new ARRtmpcGuestKit(mGuestListener); mGuestKit.setAudioActiveCheck(true); VideoRenderer render = mVideoView.openLocalVideoRender(); mGuestKit.startRtmpPlay(pullUrl, render.GetRenderPointer()); mGuestKit.joinRTCLine("", liveId, userId, getUserData()); } public String getUserData() { JSONObject user = new JSONObject(); try { user.put("isHost", 0); user.put("userId", userId); user.put("nickName", ARApplication.getNickName()); user.put("headUrl", "www.baidu.com"); } catch (JSONException e) { e.printStackTrace(); } return user.toString(); } private void onAudioManagerChangedState() { // TODO(henrika): disable video if // AppRTCAudioManager.AudioDevice.EARPIECE // is active. setVolumeControlStream(AudioManager.STREAM_VOICE_CALL); } private void ShowExitDialog() { AlertDialog.Builder build = new AlertDialog.Builder(this); build.setTitle(org.ar.rtmpc.R.string.str_exit); build.setMessage(org.ar.rtmpc.R.string.str_line_hangup); build.setPositiveButton(org.ar.rtmpc.R.string.str_ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // TODO Auto-generated method stub mGuestKit.hangupRTCLine(); mVideoView.removeRemoteRender("LocalCameraRender"); finishAnimActivity(); } }); build.setNegativeButton(org.ar.rtmpc.R.string.str_cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // TODO Auto-generated method stub } }); build.show(); } /** * 更新列表 * * @param chatMessageBean */ private void addChatMessageList(MessageBean chatMessageBean) { // 150 条 修改; if (chatMessageBean == null) { return; } if (mAdapter.getData().size() < 150) { mAdapter.addData(chatMessageBean); } else { mAdapter.remove(0); mAdapter.addData(chatMessageBean); } rvMsgList.smoothScrollToPosition(mAdapter.getData().size() - 1); } public void printLog(String log){ Log.d("RTMPC", log); logAdapter.addData(log); } /** * 观看直播回调信息接口 */ private ARRtmpcGuestEvent mGuestListener = new ARRtmpcGuestEvent() { /** * rtmp 连接成功 视频即将播放;视频播放前的操作可以在此接口中进行操作 */ @Override public void onRtmpPlayerOk() { GuestActivity.this.runOnUiThread(new Runnable() { @Override public void run() { printLog("回调:onRtmpPlayerOk"); if (tvRtmpOk != null) { tvRtmpOk.setText("Rtmp连接成功"); } } }); } /** * rtmp 开始播放 视频开始播放 */ @Override public void onRtmpPlayerStart() { GuestActivity.this.runOnUiThread(new Runnable() { @Override public void run() { printLog("回调:onRtmpPlayerStart"); } }); } /** * rtmp 当前播放状态 * @param cacheTime 当前缓存时间 * @param curBitrate 当前播放器码流 */ @Override public void onRtmpPlayerStatus(final int cacheTime, final int curBitrate) { GuestActivity.this.runOnUiThread(new Runnable() { @Override public void run() { printLog("回调:onRtmpPlayerStatus cacheTime:" + cacheTime + " curBitrate:" + curBitrate); if (tvRtmpStatus != null) { tvRtmpStatus.setText("当前缓存时间:" + cacheTime + " ms" + "\n当前码流:" + curBitrate / 10024 / 8 + "kb/s"); } } }); } /** * rtmp 播放缓冲区时长 * @param nPercent 缓冲时间 */ @Override public void onRtmpPlayerLoading(final int nPercent) { GuestActivity.this.runOnUiThread(new Runnable() { @Override public void run() { printLog("回调:onRtmpPlayerCache nPercent:" + nPercent); } }); } /** * rtmp 播放器关闭 * @param nCode */ @Override public void onRtmpPlayerClosed(final int nCode) { GuestActivity.this.runOnUiThread(new Runnable() { @Override public void run() { printLog("回调:onRtmpPlayerClosed nCode:" + nCode); } }); } /** * 游客RTC 状态回调 * @param nCode 回调响应码:0:正常;101:主播未开启直播; */ @Override public void onRTCJoinLineResult(final int nCode, String s) { GuestActivity.this.runOnUiThread(new Runnable() { @Override public void run() { printLog("回调:onRTCJoinLineResult nCode:" + nCode); if (tvRtcOk != null) { if (nCode == 0) { tvRtcOk.setText(org.ar.rtmpc.R.string.str_rtc_connect_success); } else if (nCode == 101) { Toast.makeText(GuestActivity.this, org.ar.rtmpc.R.string.str_hoster_not_live, Toast.LENGTH_LONG).show(); tvRtcOk.setText(org.ar.rtmpc.R.string.str_rtc_connect_success); } else { tvRtcOk.setText(ARUtils.getErrString(nCode)); } } } }); } /** * 游客申请连线回调 */ @Override public void onRTCApplyLineResult(final int nCode) { GuestActivity.this.runOnUiThread(new Runnable() { @SuppressLint("MissingPermission") @Override public void run() { printLog("回调:onRTCApplyLineResult nCode:" + nCode); if (nCode == 0) { ibtnCamera.setVisibility(View.VISIBLE); isApplyLine = true; isLining = true; tvApplyLine.setText("挂断"); tvApplyLine.setBackgroundResource(org.ar.rtmpc.R.drawable.shape_room_hang_up_line); VideoRenderer render = mVideoView.openRemoteVideoRender("LocalCameraRender"); mGuestKit.setLocalVideoCapturer(render.GetRenderPointer()); } else if (nCode == 601) { Toast.makeText(GuestActivity.this, org.ar.rtmpc.R.string.str_hoster_refused, Toast.LENGTH_LONG).show(); isApplyLine = false; ibtnCamera.setVisibility(View.GONE); tvApplyLine.setText("连麦"); tvApplyLine.setBackgroundResource(org.ar.rtmpc.R.drawable.shape_room_apply_line); } } }); } /** * 挂断连线回调 */ @Override public void onRTCHangupLine() { //主播连线断开 GuestActivity.this.runOnUiThread(new Runnable() { @Override public void run() { printLog("回调:onRTCHangupLine "); mGuestKit.hangupRTCLine(); ibtnCamera.setVisibility(View.GONE); mVideoView.removeRemoteRender("LocalCameraRender"); tvApplyLine.setText(org.ar.rtmpc.R.string.str_connect_hoster); tvApplyLine.setBackgroundResource(org.ar.rtmpc.R.drawable.shape_room_apply_line); isApplyLine = false; isLining = false; } }); } @Override public void onRTCOpenRemoteVideoRender(final String strLivePeerId, final String strPublishId, final String strUserId, final String strUserData) { GuestActivity.this.runOnUiThread(new Runnable() { @Override public void run() { printLog("回调:onRTCOpenVideoRenderLeave strLivePeerId:" + strLivePeerId + "strUserId:" + strUserId + " strUserData:" + strUserData); final VideoRenderer render = mVideoView.openRemoteVideoRender(strLivePeerId); mGuestKit.setRTCRemoteVideoRender(strPublishId, render.GetRenderPointer()); } }); } @Override public void onRTCCloseRemoteVideoRender(final String strLivePeerId, final String strPublishId, final String strUserId) { GuestActivity.this.runOnUiThread(new Runnable() { @Override public void run() { Log.d("RTMPC", "onRTCCloseVideoRender strLivePeerId:" + strLivePeerId + "strUserId:" + strUserId); if (mGuestKit != null && mVideoView != null ) { mGuestKit.setRTCRemoteVideoRender(strPublishId, 0); mVideoView.removeRemoteRender(strLivePeerId); } } }); } @Override public void onRTCOpenRemoteAudioLine(String s, String s1, String s2) { } @Override public void onRTCCloseRemoteAudioLine(String s, String s1) { } @Override public void onRTCLocalAudioActive(int i) { GuestActivity.this.runOnUiThread(new Runnable() { @Override public void run() { Log.d("RTMPC", "onRTCLocalAudioActive "); } }); } @Override public void onRTCHosterAudioActive(int i) { GuestActivity.this.runOnUiThread(new Runnable() { @Override public void run() { Log.d("RTMPC", "onRTCHosterAudioActive "); } }); } @Override public void onRTCRemoteAudioActive(final String s, String s1, int i) { GuestActivity.this.runOnUiThread(new Runnable() { @Override public void run() { printLog("回调:onRTCRemoteAudioActive peerID:" + s); } }); } @Override public void onRTCRemoteAVStatus(final String s, boolean b, boolean b1) { GuestActivity.this.runOnUiThread(new Runnable() { @Override public void run() { printLog("回调:onRTCRemoteAVStatus peerID:" + s); } }); } /** * 主播已离开回调 */ @Override public void onRTCLineLeave(final int nCode, String s) { //主播关闭直播 GuestActivity.this.runOnUiThread(new Runnable() { @Override public void run() { printLog("回调:onRTCLineLeave nCode:" + nCode); if (mGuestKit != null) { mGuestKit.stopRtmpPlay(); } if (nCode == 0) { ToastUtil.show("主播已离开"); } else if (nCode == 100) { ToastUtil.show("网络已断开"); } finishAnimActivity(); } }); } /** * 连线接通后回调 * @param strLivePeerId */ /** * 连线关闭后图像回调 * @param strLivePeerId */ /** * 消息回调 * @param strCustomID 消息的发送者id * @param strCustomName 消息的发送者昵称 * @param strCustomHeader 消息的发送者头像url * @param strMessage 消息内容 */ @Override public void onRTCUserMessage(final int nType, final String strCustomID, final String strCustomName, final String strCustomHeader, final String strMessage) { GuestActivity.this.runOnUiThread(new Runnable() { @Override public void run() { printLog("回调:onRTCUserMessage nType:" + nType + "strCustomID:" + strCustomID + "strCustomName:" + strCustomName + "strCustomHeader:" + strCustomHeader + "strMessage:" + strMessage); addChatMessageList(new MessageBean(MessageBean.VIDEO, strCustomName, strMessage)); } }); } /** * 观看直播的总人数回调 * @param totalMembers 观看直播的总人数 */ @Override public void onRTCMemberNotify(final String strServerId, final String strRoomId, final int totalMembers) { GuestActivity.this.runOnUiThread(new Runnable() { @Override public void run() { printLog("回调:onRTCMemberNotify strServerId:" + strServerId + "strRoomId:" + strRoomId + "totalMembers:" + totalMembers); if (tvMemberNum != null) { tvMemberNum.setText("在线人数" + totalMembers + ""); } } }); } }; public void onClick(View view) { switch (view.getId()) { case org.ar.rtmpc.R.id.btn_close: if (isLining) { ShowExitDialog(); } else { finishAnimActivity(); } break; case org.ar.rtmpc.R.id.iv_message: showChatLayout(); break; case org.ar.rtmpc.R.id.tv_apply_line: if (isApplyLine) { if (mGuestKit != null) { mGuestKit.hangupRTCLine(); tvApplyLine.setText("连麦"); ibtnCamera.setVisibility(View.GONE); tvApplyLine.setBackgroundResource(org.ar.rtmpc.R.drawable.shape_room_apply_line); mVideoView.removeRemoteRender("LocalCameraRender"); isApplyLine = false; } } else { if (mGuestKit != null) { mGuestKit.applyRTCLine(); tvApplyLine.setText("挂断"); tvApplyLine.setBackgroundResource(org.ar.rtmpc.R.drawable.shape_room_hang_up_line); isApplyLine = true; } } break; case org.ar.rtmpc.R.id.btn_log: rl_log_layout.setVisibility(View.VISIBLE); break; case org.ar.rtmpc.R.id.ibtn_close_log: rl_log_layout.setVisibility(View.GONE); break; case R.id.btn_camare: mGuestKit.switchCamera(); break; } } private void showChatLayout() { KeyboardDialogFragment keyboardDialogFragment = new KeyboardDialogFragment(); keyboardDialogFragment.show(getSupportFragmentManager(), "KeyboardDialogFragment"); keyboardDialogFragment.setEdittextListener(new KeyboardDialogFragment.EdittextListener() { @Override public void setTextStr(String text) { addChatMessageList(new MessageBean(MessageBean.VIDEO, ARApplication.getNickName(), text)); mGuestKit.sendMessage(0, ARApplication.getNickName(), "", text); } @Override public void dismiss(DialogFragment dialogFragment) { } }); } } ================================================ FILE: app/src/main/java/org/ar/guest/LiveListActivity.java ================================================ package org.ar.guest; import android.content.Intent; import android.os.Bundle; import android.support.v4.widget.SwipeRefreshLayout; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.text.TextUtils; import android.util.Log; import android.view.KeyEvent; import android.view.View; import android.widget.Button; import android.widget.TextView; import com.chad.library.adapter.base.BaseQuickAdapter; import com.yanzhenjie.nohttp.NoHttp; import com.yanzhenjie.nohttp.RequestMethod; import com.yanzhenjie.nohttp.rest.Response; import com.yanzhenjie.nohttp.rest.SimpleResponseListener; import com.yanzhenjie.nohttp.rest.StringRequest; import com.yanzhenjie.permission.AndPermission; import com.yanzhenjie.permission.runtime.Permission; import org.ar.BaseActivity; import org.ar.ARApplication; import org.ar.adapter.LiveListAdapter; import org.ar.hoster.AudioHosterActivity; import org.ar.hoster.HosterActivity; import org.ar.rtmpc.BuildConfig; import org.ar.model.LiveBean; import org.ar.DeveloperInfo; import org.ar.utils.MD5; import org.ar.utils.PermissionsCheckUtil; import org.ar.utils.ToastUtil; import org.ar.rtmpc_hybrid.ARRtmpcHttpKit; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.util.ArrayList; import java.util.List; public class LiveListActivity extends BaseActivity implements SwipeRefreshLayout.OnRefreshListener, BaseQuickAdapter.OnItemClickListener, View.OnClickListener { RecyclerView rvList; SwipeRefreshLayout swipeRefresh; LiveListAdapter mAdapter; private List live_list = new ArrayList<>(); Button btn_video, btn_audio; TextView tvVersion; @Override public int getLayoutId() { return org.ar.rtmpc.R.layout.activity_live_list; } @Override public void initView(Bundle savedInstanceState) { tvVersion=findViewById(org.ar.rtmpc.R.id.tv_version); tvVersion.setText("v "+ BuildConfig.VERSION_NAME); btn_video = findViewById(org.ar.rtmpc.R.id.btn_video); btn_audio = findViewById(org.ar.rtmpc.R.id.btn_audio); btn_video.setOnClickListener(this); btn_audio.setOnClickListener(this); rvList = findViewById(org.ar.rtmpc.R.id.rv_list); swipeRefresh = findViewById(org.ar.rtmpc.R.id.swipe_refresh); mAdapter = new LiveListAdapter(); mAdapter.setOnItemClickListener(this); mAdapter.setEmptyView(getEmptyView()); swipeRefresh.setOnRefreshListener(this); rvList.setLayoutManager(new LinearLayoutManager(this,LinearLayoutManager.HORIZONTAL,false)); rvList.setAdapter(mAdapter); AndPermission.with(this).runtime().permission(Permission.RECORD_AUDIO,Permission.CAMERA).start(); } @Override protected void onResume() { super.onResume(); getLiveList(); } public View getEmptyView(){ View view=View.inflate(this, org.ar.rtmpc.R.layout.empty_act_data,null); TextView tvReGet= (TextView) view.findViewById(org.ar.rtmpc.R.id.tv_reget); tvReGet.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { getLiveList(); } }); return view; } @Override public void onRefresh() { getLiveList(); } @Override public void onItemClick(BaseQuickAdapter adapter, View view, final int position) { if (AndPermission.hasPermissions(LiveListActivity.this,Permission.CAMERA,Permission.RECORD_AUDIO)){ Intent intent = new Intent(LiveListActivity.this, mAdapter.getItem(position).getIsAudioLive()==1 ? AudioGuestActivity.class : GuestActivity.class); intent.putExtra("pullURL", mAdapter.getItem(position).getmRtmpPullUrl()); intent.putExtra("liveId",mAdapter.getItem(position).getmAnyrtcId()); intent.putExtra("hostName",mAdapter.getItem(position).getmHostName()); startActivity(intent); }else { PermissionsCheckUtil.showMissingPermissionDialog(LiveListActivity.this, "请先开启录音和相机权限"); } } private void getLiveList() { ARRtmpcHttpKit.getAuthLivingList(this, new ARRtmpcHttpKit.RTMPCHttpCallback() { @Override public void OnRTMPCHttpOK(String s) { if (swipeRefresh != null) { swipeRefresh.setRefreshing(false); } if (TextUtils.isEmpty(s)) { return; } try { live_list.clear(); JSONObject jsonObject = new JSONObject(s); if (jsonObject.has("LiveList")) { JSONArray liveList = jsonObject.getJSONArray("LiveList"); JSONArray member = jsonObject.getJSONArray("LiveMembers"); for (int i = 0; i < liveList.length(); i++) { JSONObject itemJson = new JSONObject(liveList.getString(i)); LiveBean bean = new LiveBean(); bean.setmRtmpPullUrl(itemJson.getString("rtmpUrl")); bean.setmHlsUrl(itemJson.getString("hlsUrl")); bean.setmLiveTopic(itemJson.getString("liveTopic")); bean.setIsAudioLive(itemJson.getInt("isAudioLive")); bean.setmAnyrtcId(itemJson.getString("anyrtcId")); bean.setmHostName(itemJson.getString("hosterName")); if (i <= member.length()) { bean.setmMemberNum(member.get(i).toString()); } live_list.add(bean); } mAdapter.setNewData(live_list); } } catch (JSONException e) { e.printStackTrace(); } } @Override public void OnRTMPCHttpFailed(int i) { ToastUtil.show("获取列表失败"); if (swipeRefresh != null) { swipeRefresh.setRefreshing(false); } } }); } @Override public void onClick(View view) { switch (view.getId()) { case org.ar.rtmpc.R.id.btn_audio: if (AndPermission.hasPermissions(LiveListActivity.this,Permission.CAMERA,Permission.RECORD_AUDIO)){ Intent intent = new Intent(LiveListActivity.this, AudioHosterActivity.class); intent.putExtra("pushURL", DeveloperInfo.PUSH_URL); intent.putExtra("pullURL", DeveloperInfo.PULL_URL); startActivity(intent); }else { PermissionsCheckUtil.showMissingPermissionDialog(LiveListActivity.this, "请先开启录音和相机权限"); } break; case org.ar.rtmpc.R.id.btn_video: if (AndPermission.hasPermissions(LiveListActivity.this,Permission.CAMERA,Permission.RECORD_AUDIO)){ Intent intent = new Intent(LiveListActivity.this, HosterActivity.class); intent.putExtra("pushURL", DeveloperInfo.PUSH_URL); intent.putExtra("pullURL", DeveloperInfo.PULL_URL); startActivity(intent); }else { PermissionsCheckUtil.showMissingPermissionDialog(LiveListActivity.this, "请先开启录音和相机权限"); } break; } } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK) { System.exit(0); finishAnimActivity(); return true; } return super.onKeyDown(keyCode, event); } } ================================================ FILE: app/src/main/java/org/ar/hoster/AudioHosterActivity.java ================================================ package org.ar.hoster; import android.content.DialogInterface; import android.media.AudioManager; import android.os.Bundle; import android.support.v4.app.DialogFragment; import android.support.v7.app.AlertDialog; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.util.Log; import android.view.Gravity; import android.view.KeyEvent; import android.view.View; import android.view.WindowManager; import android.widget.ImageButton; import android.widget.RelativeLayout; import android.widget.TextView; import com.chad.library.adapter.base.BaseQuickAdapter; import org.ar.BaseActivity; import org.ar.ARApplication; import org.ar.adapter.AudioLineAdapter; import org.ar.adapter.LiveMessageAdapter; import org.ar.adapter.LogAdapter; import org.ar.common.utils.ARAudioManager; import org.ar.rtmpc.R; import org.ar.model.LineBean; import org.ar.model.MessageBean; import org.ar.utils.ARUtils; import org.ar.utils.DisplayUtils; import org.ar.utils.ToastUtil; import org.ar.widgets.CustomDialog; import org.ar.widgets.KeyboardDialogFragment; import org.ar.common.enums.ARVideoCommon; import org.ar.rtmpc_hybrid.ARRtmpcEngine; import org.ar.rtmpc_hybrid.ARRtmpcHosterEvent; import org.ar.rtmpc_hybrid.ARRtmpcHosterKit; import org.json.JSONException; import org.json.JSONObject; import java.util.ArrayList; import java.util.List; import java.util.Set; /** * 音频主播页面 */ public class AudioHosterActivity extends BaseActivity implements BaseQuickAdapter.OnItemChildClickListener { TextView tvTitle, tvRtmpOk, tvRtmpStatus, tvRtcOk, tvMemberNum, tv_host_name; RecyclerView rvMsgList; View viewSpace; ImageButton tvLineList; RecyclerView rvLineList,rvLog; RelativeLayout rl_log_layout; private LogAdapter logAdapter; private AudioLineAdapter audioLineAdapter; private ARRtmpcHosterKit mHosterKit; private ARAudioManager mRtmpAudioManager = null; private LiveMessageAdapter mAdapter; private String nickname; private CustomDialog line_dialog; private LineFragment lineFragment; private boolean isShowLineList = false; HosterActivity.LineListener lineListener; private String pushURL = "",pullURL="", liveId = ARApplication.LIVE_ID,userId="host"+(int)((Math.random()*9+1)*100000)+""; private List applyLineList=new ArrayList<>();//申请连麦的人 这个用于判断小红点显示隐藏 @Override protected void onResume() { super.onResume(); } @Override protected void onPause() { super.onPause(); } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK) { ShowExitDialog(); } return false; } @Override protected void onDestroy() { super.onDestroy(); if (mHosterKit != null) { mHosterKit.clean(); mHosterKit = null; } // Close RTMPAudioManager if (mRtmpAudioManager != null) { mRtmpAudioManager.stop(); mRtmpAudioManager = null; } } @Override public int getLayoutId() { return R.layout.activity_audio_hoster; } @Override public void initView(Bundle savedInstanceState) { getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); tvTitle = findViewById(R.id.tv_title); rl_log_layout=findViewById(R.id.rl_log_layout); rvLog=findViewById(R.id.rv_log); tvRtmpOk = findViewById(R.id.tv_rtmp_ok); tvRtmpStatus = findViewById(R.id.tv_rtmp_status); tvRtcOk = findViewById(R.id.tv_rtc_ok); rvMsgList = findViewById(R.id.rv_msg_list); viewSpace = findViewById(R.id.view_space); mImmersionBar.titleBar(viewSpace).init(); tvMemberNum = findViewById(R.id.tv_member_num); tvLineList = findViewById(R.id.tv_line_list); rvLineList = findViewById(R.id.rv_line_list); tv_host_name = findViewById(R.id.tv_host_name); initLineFragment(); logAdapter = new LogAdapter(); rvLog.setLayoutManager(new LinearLayoutManager(this)); logAdapter.bindToRecyclerView(rvLog); mAdapter = new LiveMessageAdapter(); audioLineAdapter = new AudioLineAdapter(true); audioLineAdapter.setOnItemChildClickListener(this); rvLineList.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)); rvLineList.setAdapter(audioLineAdapter); rvMsgList.setLayoutManager(new LinearLayoutManager(this)); rvMsgList.setAdapter(mAdapter); pushURL = getIntent().getStringExtra("pushURL"); pullURL=getIntent().getStringExtra("pullURL"); tvTitle.setText("房间ID:" + liveId); nickname = ARApplication.getNickName(); tv_host_name.setText(nickname); mRtmpAudioManager = ARAudioManager.create(this); mRtmpAudioManager.start(new ARAudioManager.AudioManagerEvents() { @Override public void onAudioDeviceChanged(ARAudioManager.AudioDevice audioDevice, Set set) { } }); ARRtmpcEngine.Inst().getHosterOption().setMediaType(ARVideoCommon.ARMediaType.Audio); mHosterKit = new ARRtmpcHosterKit(mHosterListener); mHosterKit.startPushRtmpStream(pushURL); mHosterKit.createRTCLine("", liveId, "host", getUserData(), getLiveInfo(pullURL, pullURL)); } public String getLiveInfo(String pullUrl, String hlsUrl) { JSONObject liveInfo = new JSONObject(); try { liveInfo.put("hosterId", userId); liveInfo.put("rtmpUrl", pullUrl); liveInfo.put("hlsUrl", hlsUrl); liveInfo.put("liveTopic", liveId); liveInfo.put("anyrtcId", liveId); liveInfo.put("isAudioLive", 1); liveInfo.put("hosterName", nickname); } catch (JSONException e) { e.printStackTrace(); } return liveInfo.toString(); } public String getUserData() { JSONObject user = new JSONObject(); try { user.put("isHost", 1); user.put("userId", userId); user.put("nickName", ARApplication.getNickName()); user.put("headUrl", "www.baidu.com"); } catch (JSONException e) { e.printStackTrace(); } return user.toString(); } private void onAudioManagerChangedState() { // TODO(henrika): disable video if // AppRTCAudioManager.AudioDevice.EARPIECE // is active. setVolumeControlStream(AudioManager.STREAM_VOICE_CALL); } /** * 更细列表 * * @param chatMessageBean */ private void addChatMessageList(MessageBean chatMessageBean) { // 150 条 修改; if (chatMessageBean == null) { return; } if (mAdapter.getData().size() < 150) { mAdapter.addData(chatMessageBean); } else { mAdapter.remove(0); mAdapter.addData(chatMessageBean); } rvMsgList.smoothScrollToPosition(mAdapter.getData().size() - 1); } private void showChatLayout() { KeyboardDialogFragment keyboardDialogFragment = new KeyboardDialogFragment(); keyboardDialogFragment.show(getSupportFragmentManager(), "KeyboardDialogFragment"); keyboardDialogFragment.setEdittextListener(new KeyboardDialogFragment.EdittextListener() { @Override public void setTextStr(String text) { addChatMessageList(new MessageBean(MessageBean.AUDIO, nickname, text)); mHosterKit.sendMessage(0, nickname, "", text); } @Override public void dismiss(DialogFragment dialogFragment) { } }); } private void ShowExitDialog() { AlertDialog.Builder build = new AlertDialog.Builder(this); build.setTitle(R.string.str_exit); build.setMessage(R.string.str_live_stop); build.setPositiveButton(R.string.str_ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // TODO Auto-generated method stub finishAnimActivity(); } }); build.setNegativeButton(R.string.str_cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // TODO Auto-generated method stub } }); build.show(); } public void printLog(String log){ Log.d("RTMPC", log); logAdapter.addData(log); } /** * 主播回调信息接口 */ private ARRtmpcHosterEvent mHosterListener = new ARRtmpcHosterEvent() { /** * rtmp连接成功 */ @Override public void onRtmpStreamOk() { AudioHosterActivity.this.runOnUiThread(new Runnable() { @Override public void run() { printLog("回调:onRtmpStreamOk"); if (tvRtmpOk != null) { tvRtmpOk.setText("RTMP连接成功"); } } }); } /** * rtmp 重连次数 * @param times 重连次数 */ @Override public void onRtmpStreamReconnecting(final int times) { AudioHosterActivity.this.runOnUiThread(new Runnable() { @Override public void run() { printLog("回调:onRtmpStreamReconnecting times:" + times); } }); } /** * rtmp 推流状态 * @param delayMs 推流延时 * @param netBand 推流码流 */ @Override public void onRtmpStreamStatus(final int delayMs, final int netBand) { AudioHosterActivity.this.runOnUiThread(new Runnable() { @Override public void run() { printLog("回调:onRtmpStreamStatus delayMs:" + delayMs + "netBand:" + netBand); if (tvRtmpStatus != null) { tvRtmpStatus.setText(String.format(getString(R.string.str_rtmp_status), delayMs + "ms", netBand / 1024 / 8 + "kb/s")); } } }); } /** * rtmp推流失败回调 * @param code */ @Override public void onRtmpStreamFailed(final int code) { AudioHosterActivity.this.runOnUiThread(new Runnable() { @Override public void run() { printLog("回调:onRtmpStreamFailed code:" + code); if (tvRtmpStatus != null) { tvRtmpStatus.setText("推流失败"); } } }); } /** * rtmp 推流关闭回调 */ @Override public void onRtmpStreamClosed() { Log.d("RTMPC", "onRtmpStreamClosed "); AudioHosterActivity.this.runOnUiThread(new Runnable() { @Override public void run() { printLog("回调:onRtmpStreamClosed "); if (tvRtmpStatus != null) { tvRtmpStatus.setText("RTMP流关闭"); } } }); } /** * RTC 连接回调 * @param code 0: 连接成功 */ @Override public void onRTCCreateLineResult(final int code, String s) { AudioHosterActivity.this.runOnUiThread(new Runnable() { @Override public void run() { printLog("回调: onRTCCreateLineResult code:" + code); if (code == 0) { if (tvRtcOk != null) { tvRtcOk.setText(R.string.str_rtc_connect_success); } } else { if (tvRtcOk != null) { tvRtcOk.setText(ARUtils.getErrString(code)); } } } }); } /** * 游客有申请连线回调 * * @param strLivePeerID * @param strCustomID * @param strUserData */ @Override public void onRTCApplyToLine(final String strLivePeerID, final String strCustomID, final String strUserData) { AudioHosterActivity.this.runOnUiThread(new Runnable() { @Override public void run() { printLog("回调:onRTCApplyToLine strLivePeerID:" + strLivePeerID + " strCustomID:" + strCustomID + " strUserData:" + strUserData); try { JSONObject jsonObject = new JSONObject(strUserData); if (line_dialog != null && lineListener != null && mHosterKit != null) { lineListener.AddAudioGuest(new LineBean(strLivePeerID, jsonObject.getString("nickName"), false), mHosterKit); tvLineList.setSelected(true); } applyLineList.add(strLivePeerID); } catch (JSONException e) { e.printStackTrace(); } } }); } /** * 游客挂断连线回调 * @param strLivePeerID */ @Override public void onRTCCancelLine(final int nCode, final String strLivePeerID) { AudioHosterActivity.this.runOnUiThread(new Runnable() { @Override public void run() { printLog("回调:onRTCCancelLine strLivePeerID:" + strLivePeerID + "nCode:" + nCode); if (nCode == 602) { ToastUtil.show("连麦人数已满"); } if (nCode == 0) { if (line_dialog != null && lineListener != null) { lineListener.RemoveGuest(strLivePeerID); } if (applyLineList.contains(strLivePeerID)) { applyLineList.remove(strLivePeerID); } if (applyLineList.size()==0){//小红点 tvLineList.setSelected(false); } } } }); } @Override public void onRTCOpenRemoteVideoRender(String s, String s1, String s2, String s3) { } @Override public void onRTCCloseRemoteVideoRender(String s, String s1, String s2) { } @Override public void onRTCOpenRemoteAudioLine(final String strLivePeerId, final String strUserId, final String strUserData) { AudioHosterActivity.this.runOnUiThread(new Runnable() { @Override public void run() { printLog("回调: onRTCOpenRemoteAudioLine strLivePeerID:" + strLivePeerId + " strUserId:" + strUserId + " strUserData:" + strUserData); try { JSONObject jsonObject = new JSONObject(strUserData); audioLineAdapter.addData(new LineBean(strLivePeerId, jsonObject.getString("nickName"), false)); } catch (JSONException e) { e.printStackTrace(); } } }); } @Override public void onRTCCloseRemoteAudioLine(final String strLivePeerId, final String strUserId) { AudioHosterActivity.this.runOnUiThread(new Runnable() { @Override public void run() { printLog("回调: onRTCCloseRemoteAudioLine strLivePeerID:" + strLivePeerId + " strUserId:" + strUserId); int index = 9; for (int i = 0; i < audioLineAdapter.getData().size(); i++) { if (audioLineAdapter.getItem(i).peerId.equals(strLivePeerId)) { index = i; } } if (index != 9 && index <= audioLineAdapter.getData().size()) { audioLineAdapter.remove(index); } } }); } @Override public void onRTCLocalAudioActive(final int leave) { AudioHosterActivity.this.runOnUiThread(new Runnable() { @Override public void run() { Log.d("RTMPC", "onRTLocalAudioActive leave:" + leave); } }); } @Override public void onRTCRemoteAudioActive(final String strLivePeerId, final String strUserId, final int nTime) { AudioHosterActivity.this.runOnUiThread(new Runnable() { @Override public void run() { Log.d("RTMPC", "onRTCAudioActive strLivePeerID:" + strLivePeerId + " strUserId:" + strUserId + " nTime:" + nTime); if (strLivePeerId.equals("RTMPC_Hoster")) {//主播 } else { // for (int i = 0; i < audioLineAdapter.getData().size(); i++) { // if (strLivePeerId.equals(audioLineAdapter.getData().get(i).peerId)) { // audioLineAdapter.getItem(i).setStartAnim(true); // audioLineAdapter.notifyItemChanged(i); // } // } } } }); } @Override public void onRTCRemoteAVStatus(final String s, boolean b, boolean b1) { AudioHosterActivity.this.runOnUiThread(new Runnable() { @Override public void run() { printLog("回调:onRTCRemoteAVStatus peerID:"+s); } }); } /** * RTC 连接关闭回调 * @param code 207:请去AnyRTC官网申请账号,如有疑问请联系客服! * @param strReason */ /** * RTC 连接关闭回调 * @param code 207:请去AnyRTC官网申请账号,如有疑问请联系客服! */ @Override public void onRTCLineClosed(final int code, String s) { AudioHosterActivity.this.runOnUiThread(new Runnable() { @Override public void run() { printLog("回调:onRTCLineClosedLine code:" + code); } }); } /** * 消息回调 * @param strCustomID 消息的发送者id * @param strCustomName 消息的发送者昵称 * @param strCustomHeader 消息的发送者头像url * @param strMessage 消息内容 */ @Override public void onRTCUserMessage(final int nType, final String strCustomID, final String strCustomName, final String strCustomHeader, final String strMessage) { AudioHosterActivity.this.runOnUiThread(new Runnable() { @Override public void run() { printLog("回调:onRTCUserMessage nType:" + nType + " strUserId:" + strCustomID + " strCustomName:" + strCustomName + " strCustomHeader:" + strCustomHeader + " strMessage:" + strMessage); addChatMessageList(new MessageBean(MessageBean.AUDIO, strCustomName, strMessage)); } }); } /** * 观看直播的总人数回调 * @param totalMembers 观看直播的总人数 */ @Override public void onRTCMemberNotify(final String strServerId, final String strRoomId, final int totalMembers) { AudioHosterActivity.this.runOnUiThread(new Runnable() { @Override public void run() { printLog("回调:onRTCMemberNotify strServerId:" + strServerId + "strRoomId:" + strRoomId + "totalMembers:" + totalMembers); tvMemberNum.setText("在线观看人数" + totalMembers + ""); } }); } }; public void onClick(View view) { switch (view.getId()) { case R.id.btn_close: ShowExitDialog(); break; case R.id.iv_message: showChatLayout(); break; case R.id.tv_line_list: if (isShowLineList) { if (line_dialog != null) { line_dialog.hide(); isShowLineList = false; } } else { if (line_dialog != null) { line_dialog.show(); tvLineList.setSelected(false); isShowLineList = true; } } break; case R.id.btn_log: rl_log_layout.setVisibility(View.VISIBLE); break; case R.id.ibtn_close_log: rl_log_layout.setVisibility(View.GONE); break; } } private void initLineFragment() { CustomDialog.Builder builder = new CustomDialog.Builder(this); builder.setContentView(R.layout.item_line_list) .setAnimId(R.style.AnimBottom) .setGravity(Gravity.BOTTOM) .setLayoutParams(WindowManager.LayoutParams.MATCH_PARENT, DisplayUtils.getScreenHeightPixels(this) / 3) .setBackgroundDrawable(true) .build(); line_dialog = builder.show(new CustomDialog.Builder.onInitListener() { @Override public void init(CustomDialog view) { if (lineFragment == null) { lineFragment = new LineFragment(); } } }); line_dialog.hide(); line_dialog.setOnDismissListener(new DialogInterface.OnDismissListener() { @Override public void onDismiss(DialogInterface dialog) { isShowLineList = false; } }); } public void SetLineListener(HosterActivity.LineListener mLineListener) { this.lineListener = mLineListener; } @Override public void onItemChildClick(BaseQuickAdapter adapter, View view, int position) { if (mHosterKit != null) { mHosterKit.hangupRTCLine(audioLineAdapter.getItem(position).peerId); audioLineAdapter.remove(position); } } } ================================================ FILE: app/src/main/java/org/ar/hoster/HosterActivity.java ================================================ package org.ar.hoster; import android.content.DialogInterface; import android.media.AudioManager; import android.os.Bundle; import android.support.v4.app.DialogFragment; import android.support.v7.app.AlertDialog; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.util.Log; import android.view.Gravity; import android.view.KeyEvent; import android.view.View; import android.view.WindowManager; import android.widget.ImageButton; import android.widget.LinearLayout; import android.widget.RelativeLayout; import android.widget.TextView; import android.widget.Toast; import org.ar.BaseActivity; import org.ar.ARApplication; import org.ar.adapter.LiveMessageAdapter; import org.ar.adapter.LogAdapter; import org.ar.common.utils.ARAudioManager; import org.ar.rtmpc.R; import org.ar.model.LineBean; import org.ar.model.MessageBean; import org.ar.utils.ARUtils; import org.ar.utils.DisplayUtils; import org.ar.utils.ToastUtil; import org.ar.widgets.ARVideoView; import org.ar.widgets.CustomDialog; import org.ar.widgets.KeyboardDialogFragment; import org.ar.common.enums.ARVideoCommon; import org.ar.rtmpc_hybrid.ARRtmpcEngine; import org.ar.rtmpc_hybrid.ARRtmpcHosterEvent; import org.ar.rtmpc_hybrid.ARRtmpcHosterKit; import org.json.JSONException; import org.json.JSONObject; import org.webrtc.VideoRenderer; import java.net.URLDecoder; import java.util.ArrayList; import java.util.List; import java.util.Set; /** * 视频主播页面 */ public class HosterActivity extends BaseActivity { RelativeLayout rlRtmpcVideos,rl_log_layout; TextView tvTitle,tvRtmpOk,tvRtmpStatus,tvRtcOk,tvMemberNum; RecyclerView rvMsgList,rvLog; View viewSpace; ImageButton tvLineList; private ARRtmpcHosterKit mHosterKit; private ARVideoView mVideoView; private ARAudioManager mRtmpAudioManager; private LiveMessageAdapter mAdapter; private LogAdapter logAdapter; private CustomDialog line_dialog; private LineFragment lineFragment; private boolean isShowLineList = false; private LineListener lineListener; private String pushURL = "",pullURL=""; private String liveId= ARApplication.LIVE_ID; private String nickname= ARApplication.getNickName(); private String userId="host"+(int)((Math.random()*9+1)*100000)+""; private List applyLineList=new ArrayList<>();//申请连麦的人 这个用于判断小红点显示隐藏 @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK) { ShowExitDialog(); } return false; } @Override protected void onDestroy() { super.onDestroy(); if (mHosterKit != null) { mVideoView.removeLocalVideoRender(); mHosterKit.clean(); } // Close RTMPAudioManager if (mRtmpAudioManager != null) { mRtmpAudioManager.stop(); mRtmpAudioManager = null; } } @Override public int getLayoutId() { return R.layout.activity_hoster; } @Override public void initView(Bundle savedInstanceState) { //设置屏幕常亮 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); rlRtmpcVideos=findViewById(R.id.rl_rtmpc_videos); rl_log_layout=findViewById(R.id.rl_log_layout); rvLog=findViewById(R.id.rv_log); tvTitle = findViewById(R.id.tv_title); tvRtmpOk = findViewById(R.id.tv_rtmp_ok); tvRtmpStatus = findViewById(R.id.tv_rtmp_status); tvRtcOk = findViewById(R.id.tv_rtc_ok); rvMsgList = findViewById(R.id.rv_msg_list); viewSpace=findViewById(R.id.view_space); mImmersionBar.titleBar(viewSpace).init(); tvMemberNum=findViewById(R.id.tv_member_num); tvLineList=findViewById(R.id.tv_line_list); pushURL = getIntent().getStringExtra("pushURL"); pullURL=getIntent().getStringExtra("pullURL"); initLineFragment(); mAdapter = new LiveMessageAdapter(); rvMsgList.setLayoutManager(new LinearLayoutManager(this)); rvMsgList.setAdapter(mAdapter); logAdapter = new LogAdapter(); rvLog.setLayoutManager(new LinearLayoutManager(this)); logAdapter.bindToRecyclerView(rvLog); //设置视频质量 参数对应清晰度,可查看API文档 ARRtmpcEngine.Inst().getHosterOption().setVideoProfile(ARVideoCommon.ARVideoProfile.ARVideoProfile480x640); tvTitle.setText("房间ID:" + liveId); ARRtmpcEngine.Inst().getHosterOption().setVideoOrientation(ARVideoCommon.ARVideoOrientation.Portrait); //音频管理对象 当靠近听筒时将会减小音量 mRtmpAudioManager = ARAudioManager.create(this); mRtmpAudioManager.start(new ARAudioManager.AudioManagerEvents() { @Override public void onAudioDeviceChanged(ARAudioManager.AudioDevice audioDevice, Set set) { } }); ARRtmpcEngine.Inst().getHosterOption().setMediaType(ARVideoCommon.ARMediaType.Video); //实例化主播对象 mHosterKit = new ARRtmpcHosterKit(mHosterListener); mHosterKit.setAudioActiveCheck(true); //实例化连麦窗口对象 mVideoView = new ARVideoView(rlRtmpcVideos, ARRtmpcEngine.Inst().egl(), this,false,true); mVideoView.setVideoViewLayout(false,Gravity.RIGHT,LinearLayout.VERTICAL); mVideoView.setVideoLayoutOnclickEvent(mBtnVideoCloseEvent); //设置本地视频采集 VideoRenderer render = mVideoView.openLocalVideoRender(); mHosterKit.setLocalVideoCapturer(render.GetRenderPointer()); //开始推流 mHosterKit.startPushRtmpStream(pushURL); //创建RTC连接,必须放在开始推流之后 mHosterKit.createRTCLine("", liveId, userId, getUserData(), getLiveInfo(pullURL,pullURL)); //设置音频连麦直播,默认视频 //=====================================视频数据相关=============================== // /** // * 设置是否采用ARCamera,默认使用ARCamera, 如果设置为false,必须调用setByteBufferFrameCaptured才能本地显示 // * @param usedARCamera true:使用ARCamera,false:不使用ARCamera采集的数据 // */ // mHosterKit.setUsedARCamera(true); // /** // * 设置本地显示的视频数据 // * @param data 相机采集数据 // * @param width 宽 // * @param height 高 // * @param rotation 旋转角度 // * @param timeStamp 时间戳 // */ // mHosterKit.setByteBufferFrameCaptured(); //设置ARCamera视频回调数据 // mHosterKit.setARCameraCaptureObserver(new VideoCapturer.ARCameraCapturerObserver() { // @Override // public void onByteBufferFrameCaptured(byte[] data, int width, int height, int rotation, long timeStamp) { // } // }); //=====================================视频数据相关=============================== } public String getLiveInfo(String pullUrl,String hlsUrl) { JSONObject liveInfo = new JSONObject(); try { liveInfo.put("hosterId", userId); liveInfo.put("rtmpUrl", pullUrl); liveInfo.put("hlsUrl", hlsUrl); liveInfo.put("liveTopic", ARApplication.LIVE_ID); liveInfo.put("anyrtcId", ARApplication.LIVE_ID); liveInfo.put("isAudioLive", 0); liveInfo.put("hosterName", ARApplication.getNickName()); } catch (JSONException e) { e.printStackTrace(); } return liveInfo.toString(); } public String getUserData() { JSONObject user = new JSONObject(); try { user.put("isHost", 1); user.put("userId", userId); user.put("nickName", ARApplication.getNickName()); user.put("headUrl", "www.baidu.com"); } catch (JSONException e) { e.printStackTrace(); } return user.toString(); } private void onAudioManagerChangedState() { // TODO(henrika): disable video if // AppRTCAudioManager.AudioDevice.EARPIECE // is active. setVolumeControlStream(AudioManager.STREAM_VOICE_CALL); } /** * 连线时小图标的关闭按钮 */ private ARVideoView.VideoLayoutOnclickEvent mBtnVideoCloseEvent = new ARVideoView.VideoLayoutOnclickEvent() { @Override public void onCloseVideoRender(View view, String strPeerId) { mHosterKit.hangupRTCLine(strPeerId); } }; /** * 更细列表 * * @param chatMessageBean */ private void addChatMessageList(MessageBean chatMessageBean) { // 150 条 修改; if (chatMessageBean == null) { return; } if (mAdapter.getData().size() < 150) { mAdapter.addData(chatMessageBean); } else { mAdapter.remove(0); mAdapter.addData(chatMessageBean); } rvMsgList.smoothScrollToPosition(mAdapter.getData().size() - 1); } private void showChatLayout() { KeyboardDialogFragment keyboardDialogFragment = new KeyboardDialogFragment(); keyboardDialogFragment.show(getSupportFragmentManager(), "KeyboardDialogFragment"); keyboardDialogFragment.setEdittextListener(new KeyboardDialogFragment.EdittextListener() { @Override public void setTextStr(String text) { addChatMessageList(new MessageBean(MessageBean.VIDEO, nickname, text)); mHosterKit.sendMessage(0, nickname, "", text); } @Override public void dismiss(DialogFragment dialogFragment) { } }); } private void ShowExitDialog() { AlertDialog.Builder build = new AlertDialog.Builder(this); build.setTitle(R.string.str_exit); build.setMessage(R.string.str_live_stop); build.setPositiveButton(R.string.str_ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // TODO Auto-generated method stub if (mHosterKit != null) { mHosterKit.stopRtmpStream(); } finishAnimActivity(); } }); build.setNegativeButton(R.string.str_cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // TODO Auto-generated method stub } }); build.show(); } public void printLog(String log){ Log.d("RTMPC", log); logAdapter.addData(log); } /** * 主播回调信息接口 */ private ARRtmpcHosterEvent mHosterListener = new ARRtmpcHosterEvent() { /** * rtmp连接成功 */ @Override public void onRtmpStreamOk() { HosterActivity.this.runOnUiThread(new Runnable() { @Override public void run() { printLog("回调:onRtmpStreamOk"); if (tvRtmpOk != null) { tvRtmpOk.setText("Rtmp连接成功"); } } }); } /** * rtmp 重连次数 * @param times 重连次数 */ @Override public void onRtmpStreamReconnecting(final int times) { HosterActivity.this.runOnUiThread(new Runnable() { @Override public void run() { printLog("回调:onRtmpStreamReconnecting times:" + times); if (tvRtmpStatus != null) { tvRtmpStatus.setText(String.format(getString(R.string.str_reconnect_times), times)); } } }); } /** * rtmp 推流状态 * @param delayMs 推流延时 * @param netBand 推流码流 */ @Override public void onRtmpStreamStatus(final int delayMs, final int netBand) { HosterActivity.this.runOnUiThread(new Runnable() { @Override public void run() { printLog("回调:onRtmpStreamStatus delayMs:" + delayMs + "netBand:" + netBand); if (tvRtmpStatus != null) { tvRtmpStatus.setText(String.format(getString(R.string.str_rtmp_status), delayMs + "ms", netBand / 1024 / 8 + "kb/s")); } } }); } /** * rtmp推流失败回调 * @param code */ @Override public void onRtmpStreamFailed(final int code) { HosterActivity.this.runOnUiThread(new Runnable() { @Override public void run() { printLog("回调:onRtmpStreamFailed code:" + code); if (tvRtcOk != null) { tvRtcOk.setText(R.string.str_rtmp_connect_failed); } } }); } /** * rtmp 推流关闭回调 */ @Override public void onRtmpStreamClosed() { HosterActivity.this.runOnUiThread(new Runnable() { @Override public void run() { printLog("回调:onRtmpStreamClosed "); finish(); } }); } /** * RTC 连接回调 * @param code 0: 连接成功 */ @Override public void onRTCCreateLineResult(final int code, String s) { HosterActivity.this.runOnUiThread(new Runnable() { @Override public void run() { printLog("回调: onRTCCreateLineResult code:" + code); if (tvRtcOk != null) { if (code == 0) { tvRtcOk.setText(R.string.str_rtc_connect_success); } else { tvRtcOk.setText(ARUtils.getErrString(code)); } } } }); } /** * 游客有申请连线回调 * * @param strLivePeerID * @param strCustomID * @param strUserData */ @Override public void onRTCApplyToLine(final String strLivePeerID, final String strCustomID, final String strUserData) { HosterActivity.this.runOnUiThread(new Runnable() { @Override public void run() { printLog("回调:onRTCApplyToLine strLivePeerID:" + strLivePeerID + " strCustomID:" + strCustomID + " strUserData:" + strUserData); try { String userdata = URLDecoder.decode(strUserData); JSONObject jsonObject = new JSONObject(userdata); if (line_dialog != null && lineListener != null && mHosterKit != null && tvLineList != null) { lineListener.AddGuest(new LineBean(strLivePeerID, jsonObject.getString("nickName"), false), mHosterKit); tvLineList.setSelected(true); } applyLineList.add(strLivePeerID); } catch (JSONException e) { e.printStackTrace(); } } }); } /** * 游客挂断连线回调 * @param strLivePeerID */ @Override public void onRTCCancelLine(final int nCode, final String strLivePeerID) { HosterActivity.this.runOnUiThread(new Runnable() { @Override public void run() { printLog("回调:onRTCCancelLine strLivePeerID:" + strLivePeerID + "nCode:" + nCode); if (nCode == 0) { if (line_dialog != null && lineListener != null) { lineListener.RemoveGuest(strLivePeerID); } if (applyLineList.contains(strLivePeerID)) { applyLineList.remove(strLivePeerID); } if (applyLineList.size()==0){//小红点 tvLineList.setSelected(false); } } if (nCode == 602) { ToastUtil.show("连麦人数已满"); } } }); } @Override public void onRTCOpenRemoteVideoRender(final String strLivePeerId, final String strPublishId, final String strUserId, final String strUserData) { HosterActivity.this.runOnUiThread(new Runnable() { @Override public void run() { printLog("回调:onRTCOpenVideoRender strPublishId:" + strPublishId + " strUserId:" + strUserId + " strUserData:" + strUserData); final VideoRenderer render = mVideoView.openRemoteVideoRender(strLivePeerId); if (null != render) { mHosterKit.setRTCRemoteVideoRender(strPublishId, render.GetRenderPointer()); } } }); } @Override public void onRTCCloseRemoteVideoRender(final String strLivePeerId, final String strPublishId, final String strUserId) { HosterActivity.this.runOnUiThread(new Runnable() { @Override public void run() { printLog("回调:onRTCCloseVideoRender strPublishId:" + strPublishId + " strUserId:" + strUserId); mHosterKit.setRTCRemoteVideoRender(strPublishId, 0); mVideoView.removeRemoteRender(strLivePeerId); if (line_dialog != null && lineListener != null) { lineListener.RemoveGuest(strLivePeerId); } } }); } @Override public void onRTCOpenRemoteAudioLine(String s, String s1, String s2) { } @Override public void onRTCCloseRemoteAudioLine(String s, String s1) { HosterActivity.this.runOnUiThread(new Runnable() { @Override public void run() { } }); } @Override public void onRTCLocalAudioActive(int i) { HosterActivity.this.runOnUiThread(new Runnable() { @Override public void run() { Log.d("RTMPC", "onRTLocalAudioActive "); } }); } @Override public void onRTCRemoteAudioActive(String s, String s1, int i) { HosterActivity.this.runOnUiThread(new Runnable() { @Override public void run() { Log.d("RTMPC", "onRTCRemoteAudioActive "); } }); } @Override public void onRTCRemoteAVStatus(final String s, boolean b, boolean b1) { HosterActivity.this.runOnUiThread(new Runnable() { @Override public void run() { printLog("回调:onRTCRemoteAVStatus peerID:"+s); } }); } /** * RTC 连接关闭回调 * @param code 207:请去AnyRTC官网申请账号,如有疑问请联系客服! */ @Override public void onRTCLineClosed(final int code, String s) { HosterActivity.this.runOnUiThread(new Runnable() { @Override public void run() { Log.d("RTMPC", "onRTCLineClosedLine code:" + code); if (code == 207) { Toast.makeText(HosterActivity.this, getString(R.string.str_apply_anyrtc_account), Toast.LENGTH_LONG).show(); finish(); } } }); } /** * 连线接通时的视频图像回调; */ /** * 连线关闭时的视频图像回调; */ /** * 消息回调 * @param strCustomID 消息的发送者id * @param strCustomName 消息的发送者昵称 * @param strCustomHeader 消息的发送者头像url * @param strMessage 消息内容 */ @Override public void onRTCUserMessage(final int nType, final String strCustomID, final String strCustomName, final String strCustomHeader, final String strMessage) { HosterActivity.this.runOnUiThread(new Runnable() { @Override public void run() { printLog("回调:onRTCUserMessage nType:" + nType + " strUserId:" + strCustomID + " strCustomName:" + strCustomName + " strCustomHeader:" + strCustomHeader + " strMessage:" + strMessage); addChatMessageList(new MessageBean(MessageBean.VIDEO, strCustomName, strMessage)); } }); } /** * 观看直播的总人数回调 * @param totalMembers 观看直播的总人数 */ @Override public void onRTCMemberNotify(final String strServerId, final String strRoomId, final int totalMembers) { HosterActivity.this.runOnUiThread(new Runnable() { @Override public void run() { printLog("回调:onRTCMemberNotify strServerId:" + strServerId + "strRoomId:" + strRoomId + "totalMembers:" + totalMembers); tvMemberNum.setText("在线人数:" + totalMembers + ""); } }); } }; public void onClick(View view) { switch (view.getId()) { case R.id.btn_camare: if (mHosterKit == null) { return; } mHosterKit.switchCamera(); break; case R.id.btn_close: ShowExitDialog(); break; case R.id.iv_message: showChatLayout(); break; case R.id.tv_line_list: if (isShowLineList) { if (line_dialog != null) { line_dialog.hide(); isShowLineList = false; } } else { if (line_dialog != null) { line_dialog.show(); tvLineList.setSelected(false); isShowLineList = true; } } break; case R.id.btn_log: rl_log_layout.setVisibility(View.VISIBLE); break; case R.id.ibtn_close_log: rl_log_layout.setVisibility(View.GONE); break; } } private void initLineFragment() { CustomDialog.Builder builder = new CustomDialog.Builder(this); builder.setContentView(R.layout.item_line_list) .setAnimId(R.style.AnimBottom) .setGravity(Gravity.BOTTOM) .setLayoutParams(WindowManager.LayoutParams.MATCH_PARENT, DisplayUtils.getScreenHeightPixels(this) / 3) .setBackgroundDrawable(true) .build(); line_dialog = builder.show(new CustomDialog.Builder.onInitListener() { @Override public void init(CustomDialog view) { if (lineFragment == null) { lineFragment = new LineFragment(); } } }); line_dialog.hide(); line_dialog.setOnDismissListener(new DialogInterface.OnDismissListener() { @Override public void onDismiss(DialogInterface dialog) { isShowLineList = false; } }); } public interface LineListener { void AddAudioGuest(LineBean lineBean, ARRtmpcHosterKit hosterKit);//添加音频游客的申请到列表 void AddGuest(LineBean lineBean, ARRtmpcHosterKit hosterKit);//添加游客的申请到列表 void RemoveGuest(String peerid);//从列表移除游客 } public void SetLineListener(LineListener mLineListener) { this.lineListener = mLineListener; } public void closeLineDialog() { if (line_dialog != null) { line_dialog.hide(); } } } ================================================ FILE: app/src/main/java/org/ar/hoster/LineFragment.java ================================================ package org.ar.hoster; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.support.v4.widget.SwipeRefreshLayout; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import com.chad.library.adapter.base.BaseQuickAdapter; import org.ar.adapter.LiveLineAdapter; import org.ar.rtmpc.R; import org.ar.model.LineBean; import org.ar.rtmpc_hybrid.ARRtmpcHosterKit; /** * Created by liuxiaozhong on 2017/9/24. */ public class LineFragment extends Fragment implements HosterActivity.LineListener,BaseQuickAdapter.OnItemChildClickListener{ private RecyclerView lineList; private LiveLineAdapter mAadapter; private ARRtmpcHosterKit rtmpcHosterKit; private ARRtmpcHosterKit audioHosterKit; private HosterActivity activity; private AudioHosterActivity audioHosterActivity; private SwipeRefreshLayout swipe_refresh; private boolean isAudioLive=false; @Nullable @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_line, container, false); swipe_refresh= (SwipeRefreshLayout) view.findViewById(R.id.swipe_refresh); swipe_refresh.setEnabled(false); lineList= (RecyclerView) view.findViewById(R.id.rv_list); lineList.setLayoutManager(new LinearLayoutManager(getActivity())); mAadapter=new LiveLineAdapter(); mAadapter.setEmptyView(R.layout.empty_no_line_data, (ViewGroup) lineList.getParent()); mAadapter.setOnItemChildClickListener(this); lineList.setAdapter(mAadapter); if (getActivity() instanceof HosterActivity){ isAudioLive=false; activity= (HosterActivity) getActivity(); activity.SetLineListener(this); }else { isAudioLive=true; audioHosterActivity= (AudioHosterActivity) getActivity(); audioHosterActivity.SetLineListener(this); } return view; } @Override public void AddAudioGuest(LineBean lineBean, ARRtmpcHosterKit hosterKit) { isAudioLive=true; this.audioHosterKit=hosterKit; mAadapter.addData(lineBean); } @Override public void AddGuest(LineBean lineBean, ARRtmpcHosterKit hosterKit) { isAudioLive=false; this.rtmpcHosterKit=hosterKit; mAadapter.addData(lineBean); } @Override public void RemoveGuest(String peerid) { int index=9; for (int i=0;i */ public class ScreenUtils { private ScreenUtils() { /* cannot be instantiated */ throw new UnsupportedOperationException("cannot be instantiated"); } /** * dip to px * * @param dip * @param context * @return */ public static float dip2Dimension(float dip, Context context) { DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics(); return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip, displayMetrics); } public static int dpToP(Resources paramResources, int paramInt) { return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, paramInt, paramResources.getDisplayMetrics()); } /** * @param context * @param complexUnit {@link TypedValue#COMPLEX_UNIT_DIP} * {@link TypedValue#COMPLEX_UNIT_SP} * @return */ public static float toDimension(float dip, Context context, int complexUnit) { DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics(); return TypedValue.applyDimension(complexUnit, dip, displayMetrics); } /** * Get screen width * * @param context * @return */ public static int getScreenWidth(Context context) { WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); DisplayMetrics outMetrics = new DisplayMetrics(); wm.getDefaultDisplay().getMetrics(outMetrics); return outMetrics.widthPixels; } /** * Get screen height * * @param context * @return */ public static int getScreenHeight(Context context) { WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); DisplayMetrics outMetrics = new DisplayMetrics(); wm.getDefaultDisplay().getMetrics(outMetrics); return outMetrics.heightPixels; } /** * 判断是否为平板 * * @return */ public static boolean isPad(Context context) { DisplayMetrics dm = context.getResources().getDisplayMetrics(); int width = dm.widthPixels; int height = dm.heightPixels; double x = Math.pow(width, 2); double y = Math.pow(height, 2); double diagonal = Math.sqrt(x + y); int dens = dm.densityDpi; double screenInches = diagonal / (double) dens; // 大于6尺寸则为Pad if (screenInches >= 6.0) { return true; } return false; } /** * 获得状态栏的高度 * * @param context * @return */ public static int getStatusHeight(Context context) { int statusHeight = -1; try { Class clazz = Class.forName("com.android.internal.R$dimen"); Object object = clazz.newInstance(); int height = Integer.parseInt(clazz.getField("status_bar_height").get(object).toString()); statusHeight = context.getResources().getDimensionPixelSize(height); } catch (Exception e) { e.printStackTrace(); } return statusHeight; } /** * 获取当前屏幕截图,包含状态栏 * * @param activity * @return */ public static Bitmap snapShotWithStatusBar(Activity activity) { View view = activity.getWindow().getDecorView(); view.setDrawingCacheEnabled(true); view.buildDrawingCache(); Bitmap bmp = view.getDrawingCache(); int width = getScreenWidth(activity); int height = getScreenHeight(activity); Bitmap bp = null; bp = Bitmap.createBitmap(bmp, 0, 0, width, height); view.destroyDrawingCache(); return bp; } /** * 获取当前屏幕截图,不包含状态栏 * * @param activity * @return */ public static Bitmap snapShotWithoutStatusBar(Activity activity) { View view = activity.getWindow().getDecorView(); view.setDrawingCacheEnabled(true); view.buildDrawingCache(); Bitmap bmp = view.getDrawingCache(); Rect frame = new Rect(); activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(frame); int statusBarHeight = frame.top; int width = getScreenWidth(activity); int height = getScreenHeight(activity); Bitmap bp = null; bp = Bitmap.createBitmap(bmp, 0, statusBarHeight, width, height - statusBarHeight); view.destroyDrawingCache(); return bp; } public static final int getHeightInPx(Context context) { final int height = context.getResources().getDisplayMetrics().heightPixels; return height; } public static final int getWidthInPx(Context context) { final int width = context.getResources().getDisplayMetrics().widthPixels; return width; } public static final int getHeightInDp(Context context) { final float height = context.getResources().getDisplayMetrics().heightPixels; int heightInDp = px2dip(context, height); return heightInDp; } public static final int getWidthInDp(Context context) { final float height = context.getResources().getDisplayMetrics().heightPixels; int widthInDp = px2dip(context, height); return widthInDp; } public static int dip2px(Context context, float dpValue) { final float scale = context.getResources().getDisplayMetrics().density; return (int) (dpValue * scale + 0.5f); } public static int px2dip(Context context, float pxValue) { final float scale = context.getResources().getDisplayMetrics().density; return (int) (pxValue / scale + 0.5f); } public static int px2sp(Context context, float pxValue) { final float scale = context.getResources().getDisplayMetrics().density; return (int) (pxValue / scale + 0.5f); } public static int sp2px(Context context, float spValue) { final float scale = context.getResources().getDisplayMetrics().density; return (int) (spValue * scale + 0.5f); } } ================================================ FILE: app/src/main/java/org/ar/utils/ToastUtil.java ================================================ package org.ar.utils; import android.content.Context; import android.text.TextUtils; import android.view.Gravity; import android.widget.Toast; import org.ar.ARApplication; /** * Created by Skyline on 2016/5/24. */ public class ToastUtil { private static Context context = ARApplication.App(); private static Toast mToast; public static void show(int resId) { show(context.getResources().getText(resId), Toast.LENGTH_SHORT); } public static void show(int resId, int duration) { show(context.getResources().getText(resId), duration); } public static void show(CharSequence text) { show(text, Toast.LENGTH_SHORT); } public static void show(CharSequence text, int duration) { text = TextUtils.isEmpty(text == null ? "" : text.toString()) ? "" : text; if (mToast == null) { mToast = Toast.makeText(context, text, duration); mToast.setGravity(Gravity.CENTER, 0, 0); } else { mToast.setText(text); } mToast.show(); } public static void show(int resId, Object... args) { show(String.format(context.getResources().getString(resId), args), Toast.LENGTH_SHORT); } public static void show(String format, Object... args) { show(String.format(format, args), Toast.LENGTH_SHORT); } public static void show(int resId, int duration, Object... args) { show(String.format(context.getResources().getString(resId), args), duration); } public static void show(String format, int duration, Object... args) { show(String.format(format, args), duration); } public void cancelToast() { if (mToast != null) { mToast.cancel(); } } } ================================================ FILE: app/src/main/java/org/ar/widgets/ARVideoView.java ================================================ package org.ar.widgets; import android.content.Context; import android.content.res.Configuration; import android.graphics.Bitmap; import android.util.Log; import android.view.Gravity; import android.view.MotionEvent; import android.view.View; import android.widget.FrameLayout; import android.widget.ImageButton; import android.widget.LinearLayout; import android.widget.RelativeLayout; import org.ar.common.utils.ScreenUtils; import org.ar.rtmpc.R; import org.webrtc.EglBase; import org.webrtc.EglRenderer; import org.webrtc.PercentFrameLayout; import org.webrtc.RendererCommon; import org.webrtc.SurfaceViewRenderer; import org.webrtc.VideoRenderer; import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import static android.view.View.VISIBLE; /** * Created by liuxiaozhong on 2019/1/11. */ public class ARVideoView implements View.OnTouchListener{ public RelativeLayout rlVideoGroup;//所有视频的容器布局 private EglBase eglBase;//底层视频渲染相关对象 private Context mContext;//上下文对象 private VideoView LocalVideoRender;//本地视频显示对象 private LinkedHashMap mRemoteRenderList;//远程视频集合 private static int mScreenWidth;//屏幕宽 private static int mScreenHeight;//屏幕高 private boolean isSameSize=false;//是否是平均大小模式 private boolean is169 = false;//比例是否是16:9 private int direction = Gravity.CENTER;//1大几小的时候 小像位置 private int orientation = LinearLayout.HORIZONTAL;//1大几小的时候 小像横向或纵向排列 private static int SUB_WIDTH = 0; private static int SUB_HEIGHT = 0; private boolean isHost ;//是否是主播 VideoLayoutOnclickEvent videoLayoutOnclickEvent; public interface VideoLayoutOnclickEvent { void onCloseVideoRender(View view, String strPeerId); } public void setVideoLayoutOnclickEvent(VideoLayoutOnclickEvent videoLayoutOnclickEvent) { this.videoLayoutOnclickEvent = videoLayoutOnclickEvent; } public ARVideoView(RelativeLayout rlVideoGroup, EglBase eglBase, Context context, boolean isSameSize, boolean isHost) { this.rlVideoGroup = rlVideoGroup; this.eglBase = eglBase; this.mContext = context; this.isSameSize=isSameSize; this.isHost=isHost; mRemoteRenderList = new LinkedHashMap<>(); mScreenWidth = ScreenUtils.getScreenWidth(mContext); mScreenHeight = ScreenUtils.getScreenHeight(mContext) - ScreenUtils.getStatusHeight(mContext); } public void setVideoSwitchEnable(boolean enable) { if (!isSameSize) { rlVideoGroup.setOnTouchListener(this); } } @Override public boolean onTouch(View v, MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_DOWN) { int startX = (int) event.getX(); int startY = (int) event.getY(); if (LocalVideoRender.Hited(startX, startY)) { return true; } else { Iterator> iter = mRemoteRenderList.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = iter.next(); String peerId = entry.getKey(); VideoView render = entry.getValue(); if (render.Hited(startX, startY)) { return true; } } } } else if (event.getAction() == MotionEvent.ACTION_UP) { int startX = (int) event.getX(); int startY = (int) event.getY(); if (LocalVideoRender.Hited(startX, startY)) { SwitchViewToFullscreen(LocalVideoRender, GetFullScreen()); return true; } else { Iterator> iter = mRemoteRenderList.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = iter.next(); String peerId = entry.getKey(); VideoView render = entry.getValue(); if (render.Hited(startX, startY)) { SwitchViewToFullscreen(render, GetFullScreen()); return true; } } } } return false; } /** * 一个VideoView对象 就是一个视频渲染对象 里面的方法 UI 可以根据需求自定义 */ protected static class VideoView { public String videoId; //视频ID 保持唯一 public int index; //视频的下标 public int x; //装载视频的容器的起始X轴位置 最大100 最左边为0 public int y; //装载视频的容器的起始Y轴位置 最大100 最上边为0 public int w; //装载视频的容器的宽 最大100 public int h; //装载视频的容器的高 最大100 public PercentFrameLayout mLayout = null;//自定义宽高为百分比的布局控件 public SurfaceViewRenderer surfaceViewRenderer = null; //显示视频的SurfaceView private FrameLayout flLoading; //视频显示前的Loading public VideoRenderer videoRenderer = null; //底层视频渲染对象 public ImageButton btnHangUp; public VideoView(String videoId, Context ctx, EglBase eglBase, int index, int x, int y, int w, int h) { this.videoId = videoId; this.index = index; this.x = x; this.y = y; this.w = w; this.h = h; mLayout = new PercentFrameLayout(ctx); mLayout.setLayoutParams(new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT)); View view = View.inflate(ctx, R.layout.layout_arvideo, null);//这个View可完全自定义 需要显示名字或者其他图标可以在里面加 flLoading = (FrameLayout) view.findViewById(R.id.fl_video_loading); btnHangUp=view.findViewById(R.id.ibtn_hang_up); surfaceViewRenderer = (SurfaceViewRenderer) view.findViewById(R.id.sv_video_render); surfaceViewRenderer.init(eglBase.getEglBaseContext(), null); surfaceViewRenderer.setLayoutParams(new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT)); mLayout.addView(view);//将SurfaceView添加到自定义宽高为百分比的布局控件中 } /** * 该视频对象是否全屏显示 * * @return true false */ public Boolean isFullScreen() { return w == 100 || h == 100; } /** * 是否点击了该视频对象 * * @param px * @param py * @return */ public Boolean Hited(int px, int py) { if (!isFullScreen()) { int left = x * mScreenWidth / 100; int top = y * mScreenHeight / 100; int right = (x + w) * mScreenWidth / 100; int bottom = (y + h) * mScreenHeight / 100; if ((px >= left && px <= right) && (py >= top && py <= bottom)) { return true; } } return false; } public void close() { mLayout.removeView(surfaceViewRenderer); surfaceViewRenderer.release(); surfaceViewRenderer = null; videoRenderer = null; } } /** * 仅用于1大几小 * 1个大像和几个小像的时候设置 * @param is169 比例是否是16:9 true 16:9 false 4:3 * @param direction 显示位置 左边 中间 右边 * @param orientation 排列方式 垂直 横向 */ public void setVideoViewLayout(boolean is169, int direction, int orientation) { this.is169 = is169; this.direction = direction; this.orientation = orientation; if (!isSameSize) { changeSizeWhenRotate(false); } } /** * 仅用于1大几小 * 旋转屏幕时改变尺寸 * isFirst 是否是第一次 是的话 是不需要更新视频View的 */ public void changeSizeWhenRotate(boolean isFirst) { if (is169) { if (mContext.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {//横屏 SUB_WIDTH = (int) (((mScreenWidth / 5f) * 1.777777f) / (mScreenHeight / 100f)); SUB_HEIGHT=(int) ((mScreenWidth / 5f) / (mScreenWidth / 100f)); } else { SUB_HEIGHT = (int) (((mScreenWidth / 5f) * 1.777777f) / (mScreenHeight / 100f)); SUB_WIDTH = (int) ((mScreenWidth / 5f) / (mScreenWidth / 100f)); } } else { if (mContext.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {//横屏 SUB_WIDTH = (int) (((mScreenWidth / 4f) * 1.33333f) / (mScreenHeight / 100f)); SUB_HEIGHT=(int) ((mScreenWidth / 4f) / (mScreenWidth / 100f)); } else { SUB_HEIGHT = (int) (((mScreenWidth / 3f) /1.33333f) / (mScreenHeight / 100f)); SUB_WIDTH = (int) ((mScreenWidth / 3f) / (mScreenWidth / 100f)); } } if (!isFirst){ updateVideoView1Big(); } } /** * 获取视频窗口的个数 * * @return */ public int getVideoRenderSize() { int size = mRemoteRenderList.size(); if (LocalVideoRender != null) { size += 1; } return size; } /** * 打开本地摄像头渲染对象 * * @return */ public VideoRenderer openLocalVideoRender() { int size = getVideoRenderSize(); if (size == 0) { LocalVideoRender = new VideoView("localRender", rlVideoGroup.getContext(), eglBase, 0, 0, 0, 100, 100); LocalVideoRender.surfaceViewRenderer.setZOrderMediaOverlay(false); } else { LocalVideoRender = new VideoView("localRender", rlVideoGroup.getContext(), eglBase, size, 0, 0, 100, 100); LocalVideoRender.surfaceViewRenderer.setZOrderMediaOverlay(false); } rlVideoGroup.addView(LocalVideoRender.mLayout, -1); LocalVideoRender.mLayout.setPosition( LocalVideoRender.x, LocalVideoRender.y, LocalVideoRender.w, LocalVideoRender.h); LocalVideoRender.surfaceViewRenderer.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FILL); LocalVideoRender.flLoading.setVisibility(VISIBLE); LocalVideoRender.surfaceViewRenderer.addFrameListener(new EglRenderer.FrameListener() { @Override public void onFrame(Bitmap frame) { Log.d("surfaceView", frame.toString()); LocalVideoRender.surfaceViewRenderer.post(new Runnable() { @Override public void run() { LocalVideoRender.flLoading.setVisibility(View.GONE); } }); } }, 1f); if (isSameSize){ updateVideoViewSameSize(); }else { updateVideoView1Big(); } LocalVideoRender.videoRenderer = new VideoRenderer(LocalVideoRender.surfaceViewRenderer); return LocalVideoRender.videoRenderer; } /** * 移除本地视频渲染对象 */ public void removeLocalVideoRender() { if (LocalVideoRender != null) { LocalVideoRender.close(); LocalVideoRender.videoRenderer = null; rlVideoGroup.removeView(LocalVideoRender.mLayout); LocalVideoRender = null; if (isSameSize){ updateVideoViewSameSize(); }else { updateVideoView1Big(); } } } /** * 打开远程视频渲染对象 * * @param videoId 视频ID * @return */ public VideoRenderer openRemoteVideoRender(final String videoId) { VideoView remoteVideoRender = mRemoteRenderList.get(videoId); if (remoteVideoRender == null) { int size = getVideoRenderSize(); if (size == 0) { remoteVideoRender = new VideoView(videoId, rlVideoGroup.getContext(), eglBase, 0, 0, 0, 100, 100); } else { remoteVideoRender = new VideoView(videoId, rlVideoGroup.getContext(), eglBase, size, 0, 0, 0, 0); remoteVideoRender.surfaceViewRenderer.setZOrderMediaOverlay(true); } rlVideoGroup.addView(remoteVideoRender.mLayout, -1); remoteVideoRender.mLayout.setPosition( remoteVideoRender.x, remoteVideoRender.y, remoteVideoRender.w, remoteVideoRender.h); remoteVideoRender.surfaceViewRenderer.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FILL); remoteVideoRender.flLoading.setVisibility(VISIBLE); final VideoView finalRemoteVideoRender = remoteVideoRender; remoteVideoRender.surfaceViewRenderer.addFrameListener(new EglRenderer.FrameListener() { @Override public void onFrame(Bitmap frame) { finalRemoteVideoRender.surfaceViewRenderer.post(new Runnable() { @Override public void run() { finalRemoteVideoRender.flLoading.setVisibility(View.GONE); } }); } }, 1f); remoteVideoRender.videoRenderer = new VideoRenderer(remoteVideoRender.surfaceViewRenderer); mRemoteRenderList.put(videoId, remoteVideoRender); if (isSameSize){ updateVideoViewSameSize(); }else { updateVideoView1Big(); } if (isHost || (!isHost && videoId.equals("LocalCameraRender"))) { remoteVideoRender.btnHangUp.setVisibility(View.VISIBLE); remoteVideoRender.btnHangUp.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (null != videoLayoutOnclickEvent) { videoLayoutOnclickEvent.onCloseVideoRender(v, videoId); } } }); } } return remoteVideoRender.videoRenderer; } /** * 移除远程像 * * @param videoId */ public void removeRemoteRender(String videoId) { VideoView remoteVideoRender = mRemoteRenderList.get(videoId); if (remoteVideoRender != null) { remoteVideoRender.close(); rlVideoGroup.removeView(remoteVideoRender.mLayout); mRemoteRenderList.remove(videoId); sortVideoRenderIndex(); if (isSameSize){ updateVideoViewSameSize(); }else { updateVideoView1Big(); } } } public void sortVideoRenderIndex() { List> list = new ArrayList>(mRemoteRenderList.entrySet()); for (int i = 0; i < list.size(); i++) { list.get(i).getValue().index = i + 1; } } //第一种 1个大 多个小 小像从中间位置开始 最多5个 /** * 1个大像 5个小像示例 * 小像横排/竖排排列 * 小像从左边 中间 右边开始排列 */ private void updateVideoView1Big() { int size = mRemoteRenderList.size(); if (size == 0) { if (LocalVideoRender != null) { LocalVideoRender.x = 0; LocalVideoRender.y = 0; LocalVideoRender.w = 100; LocalVideoRender.h = 100; LocalVideoRender.mLayout.setPosition(0, 0, 100, 100); LocalVideoRender.surfaceViewRenderer.requestLayout(); } } else { int startX = 0; int startY = 100-SUB_HEIGHT-2; if (orientation== LinearLayout.HORIZONTAL){ if (direction == Gravity.CENTER) { startX = (100 - (SUB_WIDTH * size)) / 2;//小像起始位置 } else if (direction == Gravity.LEFT) { startX = 0; } else if (direction == Gravity.RIGHT) { startX = 100 - SUB_WIDTH; } else { startX = (100 - (SUB_WIDTH * size)) / 2; } }else { if (direction == Gravity.CENTER) { startX = (100 - SUB_WIDTH) / 2;//小像起始位置 } else if (direction == Gravity.LEFT) { startX = 0; } else if (direction == Gravity.RIGHT) { startX = 100 - SUB_WIDTH-5; } else { startX = (100 - SUB_WIDTH) / 2; } } if (LocalVideoRender != null) { LocalVideoRender.x = 0; LocalVideoRender.y = 0; LocalVideoRender.w = 100; LocalVideoRender.h = 100; LocalVideoRender.mLayout.setPosition(0, 0, 100, 100); LocalVideoRender.surfaceViewRenderer.requestLayout(); } Iterator> iter = mRemoteRenderList.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = iter.next(); VideoView render = entry.getValue(); if (orientation == LinearLayout.HORIZONTAL) { if (direction == Gravity.CENTER) { render.x = startX + (render.index - 1) * SUB_WIDTH; } else if (direction == Gravity.LEFT) { render.x = startX + (render.index - 1) * SUB_WIDTH; } else if (direction == Gravity.RIGHT) { render.x = startX - (render.index - 1) * SUB_WIDTH; } else { render.x = startX + (render.index - 1) * SUB_WIDTH; } render.y = startY; } else { render.x=startX; render.y = startY - (render.index - 1) * SUB_HEIGHT; } render.w = SUB_WIDTH; render.h = SUB_HEIGHT; render.mLayout.setPosition(render.x, render.y, render.w, render.h); render.surfaceViewRenderer.requestLayout(); } } } /** * 适合横屏 * 平均大小模式示例 * 1个全屏 2个上下或左右个1 3个品字形状 4个田字形状 5个上2下3 6个上3下3 */ public void updateVideoViewSameSize() { int HEIGHT, WIDTH; //平均大小模式 int size = mRemoteRenderList.size(); if (size == 0) { LocalVideoRender.mLayout.setPosition(0, 0, 100, 100); LocalVideoRender.surfaceViewRenderer.requestLayout(); } else if (size == 1) { if (!is169) { HEIGHT = (int) (((mScreenWidth / 2f) / 1.33333f) / (mScreenHeight / 100)); WIDTH = (int) ((mScreenWidth / 2f) / (mScreenWidth / 100)); } else { HEIGHT = (int) (((mScreenWidth / 2f) / 1.77777f) / (mScreenHeight / 100)); WIDTH = (int) ((mScreenWidth / 2f) / (mScreenWidth / 100)); } int Y = (100 - HEIGHT) / 2; Iterator> iter = mRemoteRenderList.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = iter.next(); VideoView render = entry.getValue(); LocalVideoRender.mLayout.setPosition(0, Y, WIDTH, HEIGHT); LocalVideoRender.surfaceViewRenderer.requestLayout(); if (render.index == 1) { render.mLayout.setPosition(WIDTH, Y, WIDTH, HEIGHT); render.surfaceViewRenderer.requestLayout(); } } } else if (size == 2) { if (!is169) { WIDTH = (int) (((mScreenHeight / 2f) * 1.33333f) / (mScreenWidth / 100)); HEIGHT = (int) ((mScreenHeight / 2f) / (mScreenHeight / 100)); } else { WIDTH = (int) (((mScreenHeight / 2f) * 1.77777f) / (mScreenWidth / 100)); HEIGHT = (int) ((mScreenHeight / 2f) / (mScreenHeight / 100)); } int X = 0; int Y = 0; // int WIDTH = 100 / 2; // int HEIGHT = 50; Iterator> iter = mRemoteRenderList.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = iter.next(); VideoView render = entry.getValue(); LocalVideoRender.mLayout.setPosition((100 - WIDTH) / 2, Y, WIDTH, HEIGHT); LocalVideoRender.surfaceViewRenderer.requestLayout(); if (render.index == 1) { render.mLayout.setPosition((100 - 2 * WIDTH) / 2, Y + HEIGHT, WIDTH, HEIGHT); render.surfaceViewRenderer.requestLayout(); } else if (render.index == 2) { render.mLayout.setPosition((100 - 2 * WIDTH) / 2 + WIDTH, Y + HEIGHT, WIDTH, HEIGHT); render.surfaceViewRenderer.requestLayout(); } } } else if (size == 3) { if (!is169) { WIDTH = (int) (((mScreenHeight / 2f) * 1.33333f) / (mScreenWidth / 100)); HEIGHT = (int) ((mScreenHeight / 2f) / (mScreenHeight / 100)); } else { WIDTH = (int) (((mScreenHeight / 2f) * 1.77777f) / (mScreenWidth / 100)); HEIGHT = (int) ((mScreenHeight / 2f) / (mScreenHeight / 100)); } int X = 0; int Y = 0; // int WIDTH = 50; // int HEIGHT = 50; Iterator> iter = mRemoteRenderList.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = iter.next(); VideoView render = entry.getValue(); LocalVideoRender.mLayout.setPosition((100 - WIDTH * 2) / 2, Y, WIDTH, HEIGHT); LocalVideoRender.surfaceViewRenderer.requestLayout(); if (render.index == 1) { render.mLayout.setPosition((100 - 2 * WIDTH) / 2 + WIDTH, Y, WIDTH, HEIGHT); render.surfaceViewRenderer.requestLayout(); } else if (render.index == 2) { render.mLayout.setPosition((100 - 2 * WIDTH) / 2, Y + HEIGHT, WIDTH, HEIGHT); render.surfaceViewRenderer.requestLayout(); } else if (render.index == 3) { render.mLayout.setPosition((100 - 2 * WIDTH) / 2 + WIDTH, Y + HEIGHT, WIDTH, HEIGHT); render.surfaceViewRenderer.requestLayout(); } } } else if (size == 4) { if (!is169) { WIDTH = (int) (((mScreenHeight / 2f) * 1.33333f) / (mScreenWidth / 100)); HEIGHT = (int) ((mScreenHeight / 2f) / (mScreenHeight / 100)); } else { HEIGHT = (int) (((mScreenWidth / 3f) / 1.77777f) / (mScreenHeight / 100)); WIDTH = (int) ((mScreenWidth / 3f) / (mScreenWidth / 100)); } int X = (100 - WIDTH * 3) / 2; int Y = (100-HEIGHT*2)/2; Iterator> iter = mRemoteRenderList.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = iter.next(); VideoView render = entry.getValue(); LocalVideoRender.mLayout.setPosition((100 - WIDTH * 2) / 2, Y, WIDTH, HEIGHT); LocalVideoRender.surfaceViewRenderer.requestLayout(); if (render.index == 1) { render.mLayout.setPosition((100 - WIDTH * 2) / 2 + WIDTH, Y, WIDTH, HEIGHT); render.surfaceViewRenderer.requestLayout(); } else { if (render.index % 3 == 0) { render.mLayout.setPosition(X, Y + HEIGHT, WIDTH, HEIGHT); render.surfaceViewRenderer.requestLayout(); } else { render.mLayout.setPosition(X + (render.index % 3 * WIDTH), Y + HEIGHT, WIDTH, HEIGHT); render.surfaceViewRenderer.requestLayout(); } } } } else { if (!is169) { WIDTH = (int) (((mScreenHeight / 2f) * 1.33333f) / (mScreenWidth / 100)); HEIGHT = (int) ((mScreenHeight / 2f) / (mScreenHeight / 100)); } else { HEIGHT = (int) (((mScreenWidth / 3f) / 1.77777f) / (mScreenHeight / 100)); WIDTH = (int) ((mScreenWidth / 3f) / (mScreenWidth / 100)); } int X = (100 - WIDTH * 3) / 2; int Y = (100-HEIGHT*2)/2; // int WIDTH = 100 / 3; // int HEIGHT = 30; Iterator> iter = mRemoteRenderList.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = iter.next(); VideoView render = entry.getValue(); LocalVideoRender.mLayout.setPosition(X, Y, WIDTH, HEIGHT); LocalVideoRender.surfaceViewRenderer.requestLayout(); if (render.index == 1) { render.mLayout.setPosition(X + WIDTH, Y, WIDTH, HEIGHT); render.surfaceViewRenderer.requestLayout(); } else if (render.index == 2) { render.mLayout.setPosition(X + (WIDTH * 2), Y, WIDTH, HEIGHT); render.surfaceViewRenderer.requestLayout(); } else if (render.index == 3) { render.mLayout.setPosition(X, HEIGHT+Y, WIDTH, HEIGHT); render.surfaceViewRenderer.requestLayout(); } else if (render.index == 4) { render.mLayout.setPosition(X + WIDTH, HEIGHT+Y, WIDTH, HEIGHT); render.surfaceViewRenderer.requestLayout(); } else if (render.index == 5) { render.mLayout.setPosition(X + (WIDTH * 2), HEIGHT+Y, WIDTH, HEIGHT); render.surfaceViewRenderer.requestLayout(); } } } } private void SwitchViewToFullscreen(VideoView view1, VideoView fullscrnView) { if (view1==null||fullscrnView==null){ return; } int index, x, y, w, h; index = view1.index; x = view1.x; y = view1.y; w = view1.w; h = view1.h; view1.index = fullscrnView.index; view1.x = fullscrnView.x; view1.y = fullscrnView.y; view1.w = fullscrnView.w; view1.h = fullscrnView.h; fullscrnView.index = index; fullscrnView.x = x; fullscrnView.y = y; fullscrnView.w = w; fullscrnView.h = h; fullscrnView.mLayout.setPosition(fullscrnView.x, fullscrnView.y, fullscrnView.w, fullscrnView.h); view1.mLayout.setPosition(view1.x, view1.y, view1.w, view1.h); updateVideoLayout(view1, fullscrnView); } /** * 视频切换后更新视频的布局 * * @param view1 * @param view2 */ private void updateVideoLayout(VideoView view1, VideoView view2) { if (view1.isFullScreen()) { view1.surfaceViewRenderer.setZOrderMediaOverlay(false); view2.surfaceViewRenderer.setZOrderMediaOverlay(true); view1.mLayout.requestLayout(); view2.mLayout.requestLayout(); rlVideoGroup.removeView(view1.mLayout); rlVideoGroup.removeView(view2.mLayout); rlVideoGroup.addView(view1.mLayout, -1); rlVideoGroup.addView(view2.mLayout, 0); } else if (view2.isFullScreen()) { view1.surfaceViewRenderer.setZOrderMediaOverlay(true); view2.surfaceViewRenderer.setZOrderMediaOverlay(false); view2.mLayout.requestLayout(); view1.mLayout.requestLayout(); rlVideoGroup.removeView(view1.mLayout); rlVideoGroup.removeView(view2.mLayout); rlVideoGroup.addView(view1.mLayout, 0); rlVideoGroup.addView(view2.mLayout, -1); } else { view1.mLayout.requestLayout(); view2.mLayout.requestLayout(); rlVideoGroup.removeView(view1.mLayout); rlVideoGroup.removeView(view2.mLayout); rlVideoGroup.addView(view1.mLayout, 0); rlVideoGroup.addView(view2.mLayout, 0); } } /** * 获取全屏的界面 * * @return */ private VideoView GetFullScreen() { if (LocalVideoRender.isFullScreen()) { return LocalVideoRender; } Iterator> iter = mRemoteRenderList.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = iter.next(); String peerId = entry.getKey(); VideoView render = entry.getValue(); if (render.isFullScreen()) return render; } return null; } } ================================================ FILE: app/src/main/java/org/ar/widgets/AppBaseDialogFragment.java ================================================ package org.ar.widgets; import android.os.Bundle; import android.support.annotation.LayoutRes; import android.support.annotation.Nullable; import android.support.v4.app.DialogFragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; /** * Created by KathLine on 2016/12/30. */ public abstract class AppBaseDialogFragment extends DialogFragment { private View view; public AppBaseDialogFragment() { // Required empty public constructor } @Nullable @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // getDialog().requestWindowFeature(Window.FEATURE_NO_TITLE);//必须放在setContextView之前调用, 去掉Dialog中的蓝线 // view = LayoutInflater.from(getActivity()).inflate(getContentViewID(), container, false); view = inflater.inflate(getContentViewID(), container); setLayout(); initData(view); return view; } protected void setLayout() { } @LayoutRes protected abstract int getContentViewID(); protected abstract void initData(View view); } ================================================ FILE: app/src/main/java/org/ar/widgets/BaseDialog.java ================================================ package org.ar.widgets; import android.app.Dialog; import android.content.Context; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; /** * Created by KathLine on 2016/11/8. */ public abstract class BaseDialog extends Dialog { public BaseDialog(Context context) { super(context); } protected abstract void onTouchOutside(); @Override public boolean onTouchEvent(MotionEvent event) { /* 触摸外部弹窗 */ if (isOutOfBounds(getContext(), event)) { onTouchOutside(); } return super.onTouchEvent(event); } /** * 判断当前用户触摸是否超出了Dialog的显示区域 * * @param context * @param event * @return */ private boolean isOutOfBounds(Context context, MotionEvent event) { final int x = (int) event.getX(); final int y = (int) event.getY(); final int slop = ViewConfiguration.get(context).getScaledWindowTouchSlop(); final View decorView = getWindow().getDecorView(); return (x < -slop) || (y < -slop) || (x > (decorView.getWidth() + slop)) || (y > (decorView.getHeight() + slop)); } } ================================================ FILE: app/src/main/java/org/ar/widgets/CustomDialog.java ================================================ package org.ar.widgets; import android.content.Context; import android.graphics.drawable.ColorDrawable; import android.os.Bundle; import android.support.annotation.LayoutRes; import android.view.Gravity; import android.view.Window; import android.view.WindowManager; import org.ar.rtmpc.R; /** * Created by KathLine on 2016/8/2. */ public class CustomDialog extends BaseDialog { protected Builder builder; protected int layoutId; protected int gravity; protected int animId; protected boolean backgroundDrawableable; protected float dimAmount; protected boolean cancelable; protected boolean existDialogLined; public boolean isFullScreen; protected int width; protected int height; protected CustomDialog(Context context, Builder builder) { super(context); this.builder = builder; layoutId = builder.layoutId; gravity = builder.gravity; animId = builder.animId; backgroundDrawableable = builder.backgroundDrawableable; dimAmount = builder.dimAmount; cancelable = builder.cancelable; existDialogLined = builder.existDialogLined; isFullScreen = builder.isFullScreen; width = builder.width; height = builder.height; } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (existDialogLined) { requestWindowFeature(Window.FEATURE_NO_TITLE); } setContentView(layoutId); Window window = getWindow(); window.setGravity(gravity); window.setWindowAnimations(animId); window.getDecorView().setPadding(0, 0, 0, 0); WindowManager.LayoutParams lp = window.getAttributes(); if (width != 0 && height != 0) { lp.width = width; lp.height = height; } if (isFullScreen) { window.setFlags( WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);//设置全屏,即没有系统状态栏 } if (backgroundDrawableable) { window.setBackgroundDrawable(new ColorDrawable(0)); } if (dimAmount < 0f || dimAmount > 1f) { throw new RuntimeException("透明度必须在0~1之间"); }else { lp.dimAmount = dimAmount; } window.setAttributes(lp); setCancelable(cancelable); if (cancelable) { setCanceledOnTouchOutside(true); } } @Override protected void onTouchOutside() { if (onTouchOutsideListener != null){ onTouchOutsideListener.touchOutSide(); } } public interface onTouchOutsideListener{ void touchOutSide(); } public onTouchOutsideListener onTouchOutsideListener; public void setOnTouchOutsideListener(onTouchOutsideListener listener){ onTouchOutsideListener = listener; } public Builder getBuilder() { return builder; } public CustomDialog show(Builder.onInitListener listener) { this.show(); if (listener != null) { listener.init(this); } return this; } public static class Builder { public Context context; public int layoutId; public int gravity; public int animId; public boolean backgroundDrawableable; public float dimAmount; public boolean cancelable; public boolean existDialogLined; public boolean isFullScreen; public int width; public int height; public Builder(Context context) { this.context = context; layoutId = R.layout.dialog_base; gravity = Gravity.CENTER; animId = R.style.default_dialog_style; backgroundDrawableable = false; dimAmount = 0.5f; cancelable = true; existDialogLined = true; width = 0; height = 0; } public Builder setContentView(@LayoutRes int layoutId) { this.layoutId = layoutId; return this; } /** * 必须使用Gravity的静态常量,默认在中间弹出 * * @param gravity 详见{@link Gravity} * @return * @see Gravity */ public Builder setGravity(int gravity) { this.gravity = gravity; return this; } /** * 设置Dialog弹出和Dialog退出的动画 * * @param animId * @return */ public Builder setAnimId(int animId) { this.animId = animId; return this; } /** * Creates a new set of layout parameters with the specified width * and height. * * @param width the width, either set WindowManager.LayoutParams.WRAP_CONTENT or * WindowManager.LayoutParams.FILL_PARENT (replaced by WindowManager.LayoutParams.MATCH_PARENT in * API Level 8), or a fixed size in pixels * @param height the height, either set WindowManager.LayoutParams.WRAP_CONTENT or * WindowManager.LayoutParams.FILL_PARENT (replaced by WindowManager.LayoutParams.MATCH_PARENT in * API Level 8), or a fixed size in pixels * @return */ public Builder setLayoutParams(int width, int height) { this.width = width; this.height = height; return this; } /** * 是否给Dialog的背景设置透明,默认false * * @param backgroundDrawableable * @return */ public Builder setBackgroundDrawable(boolean backgroundDrawableable) { this.backgroundDrawableable = backgroundDrawableable; return this; } /** * 设置Dialog之外的背景透明度,0~1之间,默认值 0.5f,半透明 * * @param dimAmount * @return */ public Builder setDimAmount(float dimAmount) { this.dimAmount = dimAmount; return this; } /** * 设置Dialog是否可以关闭在Dialog之外的区域,默认true * * @param cancelable * @return */ public Builder setCancelable(boolean cancelable) { this.cancelable = cancelable; return this; } /** * 如果存在Holo主题下Dialog有蓝色线(含有标题栏)可以尝试调用该方法,默认不存在 * * @param existDialogLined * @return */ public Builder setExistDialogLined(boolean existDialogLined) { this.existDialogLined = existDialogLined; return this; } /** * 是否设置全屏模式,指的是去除系统状态栏,默认不去除 * * @param isFullScreen * @return */ public Builder setFullScreen(boolean isFullScreen) { this.isFullScreen = isFullScreen; return this; } public interface onInitListener { /** * 绑定控件 * * @param customDialog */ void init(CustomDialog customDialog); } public CustomDialog build() { return new CustomDialog(context, this); } /** * 如果对话框仅仅起提示作用,可以传入null * * @param listener * @return */ public CustomDialog show(onInitListener listener) { CustomDialog dialog = build(); dialog.show(); if (listener != null) { listener.init(dialog); } return dialog; } } } ================================================ FILE: app/src/main/java/org/ar/widgets/KeyboardDialogFragment.java ================================================ package org.ar.widgets; import android.app.Activity; import android.content.Context; import android.content.DialogInterface; import android.content.res.Configuration; import android.graphics.drawable.ColorDrawable; import android.support.v4.app.DialogFragment; import android.text.TextUtils; import android.view.View; import android.view.ViewGroup; import android.view.Window; import android.view.WindowManager; import android.view.inputmethod.InputMethodManager; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; import org.ar.rtmpc.R; import java.util.Timer; import java.util.TimerTask; /** * Created by KathLine on 2016/12/30. */ public class KeyboardDialogFragment extends AppBaseDialogFragment { TextView close; EditText editSendMessage; Button btnSend; private static InputMethodManager imm; public KeyboardDialogFragment() { // Required empty public constructor } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); } public interface EdittextListener { void setTextStr(String text); void dismiss(DialogFragment dialogFragment); } private EdittextListener edittextListener; public void setEdittextListener(EdittextListener listener) { edittextListener = listener; } @Override protected void setLayout() { Window window = getDialog().getWindow(); window.getDecorView().setPadding(0, 0, 0, 0); window.setBackgroundDrawable(new ColorDrawable(0));//背景透明 WindowManager.LayoutParams lp = window.getAttributes(); lp.width = ViewGroup.LayoutParams.MATCH_PARENT; lp.height =ViewGroup.LayoutParams.MATCH_PARENT; lp.dimAmount = 0; window.setAttributes(lp); } @Override protected int getContentViewID() { return R.layout.dialogfragment_keyboard; } @Override protected void initData(View view) { close=view.findViewById(R.id.close); close.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { if (edittextListener != null) { hideKeyboard(); dismiss(); } } }); editSendMessage=view.findViewById(R.id.edit_send_message); btnSend=view.findViewById(R.id.btn_send); btnSend.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { String text = editSendMessage.getText().toString(); if (!TextUtils.isEmpty(text)) { if (edittextListener != null) { edittextListener.setTextStr(text); editSendMessage.setText(""); hideKeyboard(); dismiss(); } } } }); Timer timer = new Timer(); timer.schedule(new TimerTask() { public void run() { showKeyboard(editSendMessage.getContext(), editSendMessage); } }, 150); } private void hideKeyboard() { try { InputMethodManager imm = (InputMethodManager) editSendMessage.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); if (imm!=null&&getDialog()!=null) { imm.hideSoftInputFromWindow(getDialog().getCurrentFocus().getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS); } }catch (Exception e){ } } @Override public void onDismiss(DialogInterface dialog) { super.onDismiss(dialog); if (edittextListener != null) { edittextListener.dismiss(this); } } public void hideKeyboard(Context context, View view) { try { view.requestFocus(); imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); if (imm!=null) { imm.hideSoftInputFromWindow(view.getApplicationWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS); } }catch (Exception e){ } } public void hideKeyboard(Activity activity) { try { imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE); if (imm!=null) { imm.hideSoftInputFromWindow(activity.getCurrentFocus().getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS); } }catch (Exception e){ } } public void showKeyboard(Context context, View view) { try { imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); // imm.showSoftInput(view, 0); if (imm!=null) { imm.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT); } }catch (Exception e){ } } } ================================================ FILE: app/src/main/res/anim/push_bottom_in.xml ================================================ ================================================ FILE: app/src/main/res/anim/push_bottom_out.xml ================================================ ================================================ FILE: app/src/main/res/color/select_text_host_input.xml ================================================ ================================================ FILE: app/src/main/res/drawable/bg_host_head.xml ================================================ ================================================ FILE: app/src/main/res/drawable/bg_list_item.xml ================================================ ================================================ FILE: app/src/main/res/drawable/bg_white.xml ================================================ ================================================ FILE: app/src/main/res/drawable/default_input_bg.xml ================================================ ================================================ FILE: app/src/main/res/drawable/line_anim.xml ================================================ ================================================ FILE: app/src/main/res/drawable/list_item_bg.xml ================================================ ================================================ FILE: app/src/main/res/drawable/selector_apply.xml ================================================ ================================================ FILE: app/src/main/res/drawable/shape_creat_btn_bg.xml ================================================ ================================================ FILE: app/src/main/res/drawable/shape_edittext_bg.xml ================================================ ================================================ FILE: app/src/main/res/drawable/shape_home_green_btn.xml ================================================ ================================================ FILE: app/src/main/res/drawable/shape_meet_id.xml ================================================ ================================================ FILE: app/src/main/res/drawable/shape_message.xml ================================================ ================================================ FILE: app/src/main/res/drawable/shape_popuwindow.xml ================================================ ================================================ FILE: app/src/main/res/drawable/shape_room_apply_audio_line.xml ================================================ ================================================ FILE: app/src/main/res/drawable/shape_room_apply_line.xml ================================================ ================================================ FILE: app/src/main/res/drawable/shape_room_hang_up_line.xml ================================================ ================================================ FILE: app/src/main/res/drawable/shape_room_member.xml ================================================ ================================================ FILE: app/src/main/res/drawable/shape_room_message.xml ================================================ ================================================ FILE: app/src/main/res/drawable/shape_room_name.xml ================================================ ================================================ FILE: app/src/main/res/drawable-xxhdpi/selector_video_manager.xml ================================================ ================================================ FILE: app/src/main/res/drawable-xxhdpi/shape_back_btn.xml ================================================ ================================================ FILE: app/src/main/res/drawable-xxhdpi/shape_index_button.xml ================================================ ================================================ FILE: app/src/main/res/drawable-xxhdpi/shape_index_solid_button.xml ================================================ ================================================ FILE: app/src/main/res/layout/activity_audio_guest.xml ================================================ ================================================ FILE: app/src/main/res/layout/activity_audio_hoster.xml ================================================ ================================================ FILE: app/src/main/res/layout/activity_guest.xml ================================================ ================================================ FILE: app/src/main/res/layout/activity_hoster.xml ================================================ ================================================ FILE: app/src/main/res/layout/activity_live_list.xml ================================================