├── app
├── .gitignore
├── src
│ └── main
│ │ ├── res
│ │ ├── values
│ │ │ ├── dimens.xml
│ │ │ ├── strings.xml
│ │ │ └── colors.xml
│ │ ├── mipmap-hdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ └── layout
│ │ │ └── activity_main.xml
│ │ ├── java
│ │ └── com
│ │ │ └── dingsoft
│ │ │ └── webrtc
│ │ │ └── webrtcroom
│ │ │ ├── webrtcmodule
│ │ │ ├── RtcListener.java
│ │ │ ├── TrustAllCerts.java
│ │ │ ├── PeerConnectionParameters.java
│ │ │ ├── Peer.java
│ │ │ └── WebRtcClient.java
│ │ │ └── activity
│ │ │ └── MainActivity.java
│ │ └── AndroidManifest.xml
├── proguard-rules.pro
└── build.gradle
├── settings.gradle
├── 流程说明.asta
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .gitignore
├── .idea
├── vcs.xml
├── modules.xml
├── runConfigurations.xml
├── gradle.xml
└── misc.xml
├── gradle.properties
├── 接口说明.txt
├── README.md
├── gradlew.bat
└── gradlew
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------
/流程说明.asta:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qdgx/WebRtcRoomAndroid/HEAD/流程说明.asta
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 | 16dp
3 |
4 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qdgx/WebRtcRoomAndroid/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qdgx/WebRtcRoomAndroid/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qdgx/WebRtcRoomAndroid/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qdgx/WebRtcRoomAndroid/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qdgx/WebRtcRoomAndroid/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qdgx/WebRtcRoomAndroid/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qdgx/WebRtcRoomAndroid/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qdgx/WebRtcRoomAndroid/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qdgx/WebRtcRoomAndroid/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qdgx/WebRtcRoomAndroid/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qdgx/WebRtcRoomAndroid/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/workspace.xml
5 | /.idea/libraries
6 | /.idea
7 | .DS_Store
8 | /build
9 | /captures
10 | .externalNativeBuild
11 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | WebRtcRoom
3 | MainActivity
4 |
5 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Aug 22 10:36:15 CST 2018
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 | #000000
7 |
8 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/java/com/dingsoft/webrtc/webrtcroom/webrtcmodule/RtcListener.java:
--------------------------------------------------------------------------------
1 | package com.dingsoft.webrtc.webrtcroom.webrtcmodule;
2 |
3 | import org.webrtc.VideoTrack;
4 |
5 | /**
6 | * UI页面事件监听
7 | * Created by chengshaobo on 2018/10/26.
8 | */
9 |
10 | public interface RtcListener {
11 |
12 | //远程音视频流加入 Peer通道
13 | void onAddRemoteStream(String peerId,VideoTrack videoTrack);
14 |
15 | //远程音视频流移除 Peer通道销毁
16 | void onRemoveRemoteStream(String peerId);
17 | }
18 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/app/src/main/java/com/dingsoft/webrtc/webrtcroom/webrtcmodule/TrustAllCerts.java:
--------------------------------------------------------------------------------
1 | package com.dingsoft.webrtc.webrtcroom.webrtcmodule;
2 |
3 | import java.security.cert.X509Certificate;
4 |
5 | import javax.net.ssl.X509TrustManager;
6 |
7 | /**
8 | * SSL全部信任
9 | * Created by chengshaobo on 2018/10/25.
10 | */
11 |
12 |
13 | public class TrustAllCerts implements X509TrustManager {
14 | @Override
15 | public void checkClientTrusted(X509Certificate[] chain, String authType) {}
16 |
17 | @Override
18 | public void checkServerTrusted(X509Certificate[] chain, String authType) {}
19 |
20 | @Override
21 | public X509Certificate[] getAcceptedIssuers() {return new X509Certificate[0];}
22 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 27
5 | defaultConfig {
6 | applicationId "com.dingsoft.webrtc.webrtcroom"
7 | minSdkVersion 21
8 | targetSdkVersion 25
9 | versionCode 1
10 | versionName "1.0"
11 | multiDexEnabled true
12 | }
13 | compileOptions {//使用JAVA8语法解析
14 | sourceCompatibility JavaVersion.VERSION_1_8
15 | targetCompatibility JavaVersion.VERSION_1_8
16 | }
17 | buildTypes {
18 | release {
19 | minifyEnabled false
20 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
21 | }
22 | }
23 | }
24 |
25 | dependencies {
26 | compile fileTree(dir: 'libs', include: ['*.jar'])
27 | implementation 'com.android.support:appcompat-v7:27.1.1'
28 | //动态权限申请
29 | implementation 'com.yanzhenjie:permission:2.0.0-rc12'
30 | compile 'io.socket:socket.io-client:1.0.0'
31 | compile 'org.webrtc:google-webrtc:1.0.25331'
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/接口说明.txt:
--------------------------------------------------------------------------------
1 | 通过Socket.Io进行数据交互 Json格式
2 |
3 | --------------------Client To Server--------------------
4 | 1:事件名:createAndJoinRoom 客户端通知服务器创建并加入room中,若room已存在则直接加入 {room}
5 | room:房间名称,字符串
6 |
7 | 2:事件名:offer 发送offer消息 {from,to,room,sdp}
8 | from: 发送者socket连接标识,字符串
9 | to:接收者socket连接标识,字符串
10 | room:房间名称,字符串
11 | sdp:发送者设备sdp描述,字符串
12 |
13 | 3:事件名:answer 发送answer消息 {from,to,room,sdp}
14 | from: 发送者socket连接标识,字符串
15 | to:接收者socket连接标识,字符串
16 | room:房间名称,字符串
17 | sdp:发送者设备sdp描述,字符串
18 |
19 | 5:事件名:candidate 发送candidate消息 {from,to,room,candidate{sdpMid,sdpMLineIndex,sdp}}
20 | from: 发送者socket连接标识,字符串
21 | to:接收者socket连接标识,字符串
22 | room:房间名称,字符串
23 | candidate:发送者设备candidate描述,Json类型
24 | sdpMid:描述协议id,字符串
25 | sdpMLineIndex:描述协议的行索引,字符串
26 | sdp:sdp描述协议,字符串
27 |
28 | 6:事件名:exit 发送exit消息 {from,room}
29 | from: 发送者socket连接标识,字符串
30 | room:房间名称,字符串
31 |
32 | --------------------Server To Client--------------------
33 | 1:事件名:created 服务器通知客户端信令连接成功 {id,room,peers[{id}]}
34 | id: 当前socket连接标识,字符串
35 | room:房间名称,字符串
36 | peers:Json数组,房间其他客户端socket连接标识集合
37 | id:房间其他socket连接标识
38 |
39 | 2:事件名:joined 服务器通知客户端当前房间有新连接加入 {id,room}
40 | id: 新socket连接标识,字符串
41 | room:房间名称,字符串
42 |
43 | 3:事件名:offer 服务器转发offer消息 {from,to,room,sdp}
44 | from: 发送者socket连接标识,字符串
45 | to:接收者socket连接标识,字符串
46 | room:房间名称,字符串
47 | sdp:发送者设备sdp描述,字符串
48 |
49 | 4:事件名:answer 服务器转发answer消息 {from,to,room,sdp}
50 | from: 发送者socket连接标识,字符串
51 | to:接收者socket连接标识,字符串
52 | room:房间名称,字符串
53 | sdp:发送者设备sdp描述,字符串
54 |
55 | 5:事件名:candidate 服务器转发candidate消息 {from,to,room,candidate{sdpMid,sdpMLineIndex,sdp}}
56 | from: 发送者socket连接标识,字符串
57 | to:接收者socket连接标识,字符串
58 | room:房间名称,字符串
59 | candidate:发送者设备candidate描述,Json类型
60 | sdpMid:描述协议id,字符串
61 | sdpMLineIndex:描述协议的行索引,字符串
62 | sdp:sdp描述协议,字符串
63 |
64 | 6:事件名:exit 服务器转发exit消息 {from,room}
65 | from: 发送者socket连接标识,字符串
66 | room:房间名称,字符串
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | 1.8
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # WebRtcRoomAndroid
2 | WebRtcRoom for Android,使用google发布最新webRtc库编译(org.webrtc:google-webrtc:1.0.25331),信令服务器使用 Socket.IO(io.socket:socket.io-client:1.0.0)。
3 |
4 | Android,iOS,Html,Server均做了实现,若有需要可分别查看:
5 |
6 | WebRtcRoomHtml: https://github.com/qdgx/WebRtcRoomHtml
7 |
8 | WebRtcRoomServer: https://github.com/qdgx/WebRtcRoomServer
9 |
10 | WebRtcRoomIOS: https://github.com/qdgx/WebRtcRoomIOS
11 |
12 | # 接口说明
13 |
14 | 通过Socket.Io进行数据交互,Json格式
15 |
16 | ----------------------------------------Client To Server----------------------------------------
17 |
18 | 1:事件名:createAndJoinRoom 客户端通知服务器创建并加入room中,若room已存在则直接加入 {room}
19 |
20 | room:房间名称,字符串
21 |
22 | 2:事件名:offer 发送offer消息 {from,to,room,sdp}
23 |
24 | from: 发送者socket连接标识,字符串
25 | to:接收者socket连接标识,字符串
26 | room:房间名称,字符串
27 | sdp:发送者设备sdp描述,字符串
28 |
29 | 3:事件名:answer 发送answer消息 {from,to,room,sdp}
30 |
31 | from: 发送者socket连接标识,字符串
32 | to:接收者socket连接标识,字符串
33 | room:房间名称,字符串
34 | sdp:发送者设备sdp描述,字符串
35 |
36 | 5:事件名:candidate 发送candidate消息 {from,to,room,candidate{sdpMid,sdpMLineIndex,sdp}}
37 |
38 | from: 发送者socket连接标识,字符串
39 | to:接收者socket连接标识,字符串
40 | room:房间名称,字符串
41 | candidate:发送者设备candidate描述,Json类型
42 | sdpMid:描述协议id,字符串
43 | sdpMLineIndex:描述协议的行索引,字符串
44 | sdp:sdp描述协议,字符串
45 |
46 | 6:事件名:exit 发送exit消息 {from,room}
47 |
48 | from: 发送者socket连接标识,字符串
49 | room:房间名称,字符串
50 |
51 | ----------------------------------------Server To Client----------------------------------------
52 |
53 | 1:事件名:created 服务器通知客户端信令连接成功 {id,room,peers[{id}]}
54 |
55 | id: 当前socket连接标识,字符串
56 | room:房间名称,字符串
57 | peers:Json数组,房间其他客户端socket连接标识集合
58 | id:房间其他socket连接标识
59 |
60 | 2:事件名:joined 服务器通知客户端当前房间有新连接加入 {id,room}
61 |
62 | id: 新socket连接标识,字符串
63 | room:房间名称,字符串
64 |
65 | 3:事件名:offer 服务器转发offer消息 {from,to,room,sdp}
66 |
67 | from: 发送者socket连接标识,字符串
68 | to:接收者socket连接标识,字符串
69 | room:房间名称,字符串
70 | sdp:发送者设备sdp描述,字符串
71 |
72 | 4:事件名:answer 服务器转发answer消息 {from,to,room,sdp}
73 |
74 | from: 发送者socket连接标识,字符串
75 | to:接收者socket连接标识,字符串
76 | room:房间名称,字符串
77 | sdp:发送者设备sdp描述,字符串
78 |
79 | 5:事件名:candidate 服务器转发candidate消息 {from,to,room,candidate{sdpMid,sdpMLineIndex,sdp}}
80 |
81 | from: 发送者socket连接标识,字符串
82 | to:接收者socket连接标识,字符串
83 | room:房间名称,字符串
84 | candidate:发送者设备candidate描述,Json类型
85 | sdpMid:描述协议id,字符串
86 | sdpMLineIndex:描述协议的行索引,字符串
87 | sdp:sdp描述协议,字符串
88 |
89 | 6:事件名:exit 服务器转发exit消息 {from,room}
90 |
91 | from: 发送者socket连接标识,字符串
92 | room:房间名称,字符串
93 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
10 |
11 |
12 |
17 |
18 |
23 |
24 |
30 |
31 |
32 |
38 |
39 |
40 |
46 |
47 |
52 |
53 |
54 |
55 |
56 |
62 |
63 |
64 |
70 |
71 |
72 |
73 |
74 |
--------------------------------------------------------------------------------
/app/src/main/java/com/dingsoft/webrtc/webrtcroom/webrtcmodule/PeerConnectionParameters.java:
--------------------------------------------------------------------------------
1 | package com.dingsoft.webrtc.webrtcroom.webrtcmodule;
2 |
3 | /**
4 | * PeerConnection连接参数
5 | * Created by chengshaobo on 2018/10/24.
6 | */
7 |
8 | public class PeerConnectionParameters {
9 | public final boolean videoCallEnabled;
10 | public final boolean loopback;
11 | public final boolean tracing;
12 | public final int videoWidth;
13 | public final int videoHeight;
14 | public final int videoFps;
15 | public final int videoMaxBitrate;
16 | public final String videoCodec;
17 | public final boolean videoCodecHwAcceleration;
18 | public final boolean videoFlexfecEnabled;
19 | public final int audioStartBitrate;
20 | public final String audioCodec;
21 | public final boolean noAudioProcessing;
22 | public final boolean aecDump;
23 | public final boolean saveInputAudioToFile;
24 | public final boolean useOpenSLES;
25 | public final boolean disableBuiltInAEC;
26 | public final boolean disableBuiltInAGC;
27 | public final boolean disableBuiltInNS;
28 | public final boolean disableWebRtcAGCAndHPF;
29 | public final boolean enableRtcEventLog;
30 | public final boolean useLegacyAudioDevice;
31 |
32 | public PeerConnectionParameters(boolean videoCallEnabled, boolean loopback, boolean tracing,
33 | int videoWidth, int videoHeight, int videoFps, int videoMaxBitrate, String videoCodec,
34 | boolean videoCodecHwAcceleration, boolean videoFlexfecEnabled, int audioStartBitrate,
35 | String audioCodec, boolean noAudioProcessing, boolean aecDump, boolean saveInputAudioToFile,
36 | boolean useOpenSLES, boolean disableBuiltInAEC, boolean disableBuiltInAGC,
37 | boolean disableBuiltInNS, boolean disableWebRtcAGCAndHPF, boolean enableRtcEventLog,
38 | boolean useLegacyAudioDevice) {
39 | this.videoCallEnabled = videoCallEnabled;
40 | this.loopback = loopback;
41 | this.tracing = tracing;
42 | this.videoWidth = videoWidth;
43 | this.videoHeight = videoHeight;
44 | this.videoFps = videoFps;
45 | this.videoMaxBitrate = videoMaxBitrate;
46 | this.videoCodec = videoCodec;
47 | this.videoFlexfecEnabled = videoFlexfecEnabled;
48 | this.videoCodecHwAcceleration = videoCodecHwAcceleration;
49 | this.audioStartBitrate = audioStartBitrate;
50 | this.audioCodec = audioCodec;
51 | this.noAudioProcessing = noAudioProcessing;
52 | this.aecDump = aecDump;
53 | this.saveInputAudioToFile = saveInputAudioToFile;
54 | this.useOpenSLES = useOpenSLES;
55 | this.disableBuiltInAEC = disableBuiltInAEC;
56 | this.disableBuiltInAGC = disableBuiltInAGC;
57 | this.disableBuiltInNS = disableBuiltInNS;
58 | this.disableWebRtcAGCAndHPF = disableWebRtcAGCAndHPF;
59 | this.enableRtcEventLog = enableRtcEventLog;
60 | this.useLegacyAudioDevice = useLegacyAudioDevice;
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/app/src/main/java/com/dingsoft/webrtc/webrtcroom/webrtcmodule/Peer.java:
--------------------------------------------------------------------------------
1 | package com.dingsoft.webrtc.webrtcroom.webrtcmodule;
2 |
3 | import android.util.Log;
4 | import org.json.JSONException;
5 | import org.json.JSONObject;
6 | import org.webrtc.DataChannel;
7 | import org.webrtc.IceCandidate;
8 | import org.webrtc.MediaStream;
9 | import org.webrtc.MediaStreamTrack;
10 | import org.webrtc.PeerConnection;
11 | import org.webrtc.PeerConnectionFactory;
12 | import org.webrtc.RtpReceiver;
13 | import org.webrtc.RtpTransceiver;
14 | import org.webrtc.SdpObserver;
15 | import org.webrtc.SessionDescription;
16 | import org.webrtc.VideoSink;
17 | import org.webrtc.VideoTrack;
18 |
19 | /**
20 | * PeerConnection通道封装,包括PeerConnection创建及状态回调
21 | * Created by chengshaobo on 2018/10/23.
22 | */
23 |
24 | public class Peer implements SdpObserver, PeerConnection.Observer {
25 | //PeerConnection对象
26 | private PeerConnection pc;
27 | //PeerConnection标识
28 | private String id;
29 | //webRtClient对象
30 | private WebRtcClient webRtcClient;
31 |
32 | //日志Tag
33 | private final static String TAG = Peer.class.getCanonicalName();
34 |
35 | //构造函数
36 | public Peer(String id,
37 | PeerConnectionFactory factory,
38 | PeerConnection.RTCConfiguration rtcConfig,
39 | WebRtcClient webRtcClient) {
40 | Log.d(TAG,"new Peer: " + id );
41 | this.pc = factory.createPeerConnection(rtcConfig,this);
42 | this.id = id;
43 | this.webRtcClient = webRtcClient;
44 | }
45 |
46 | public PeerConnection getPc() {
47 | return pc;
48 | }
49 |
50 | public void setPc(PeerConnection pc) {
51 | this.pc = pc;
52 | }
53 |
54 | public String getId() {
55 | return id;
56 | }
57 |
58 | public void setId(String id) {
59 | this.id = id;
60 | }
61 |
62 | /**SdpObserver是来回调sdp是否创建(offer,answer)成功,是否设置描述成功(local,remote)的接口**/
63 |
64 | //Create{Offer,Answer}成功回调
65 | @Override
66 | public void onCreateSuccess(SessionDescription sdp) {
67 | String type = sdp.type.canonicalForm();
68 | Log.d(TAG,"onCreateSuccess " + type);
69 | //设置本地LocalDescription
70 | pc.setLocalDescription(Peer.this, sdp);
71 | //构建信令数据
72 | try {
73 | JSONObject message = new JSONObject();
74 | message.put("from",webRtcClient.getSocketId());
75 | message.put("to",id);
76 | message.put("room",webRtcClient.getRoomId());
77 | message.put("sdp",sdp.description);
78 | //向信令服务器发送信令
79 | webRtcClient.sendMessage(type,message);
80 | }catch (JSONException e){
81 | e.printStackTrace();
82 | }
83 | }
84 |
85 | //Set{Local,Remote}Description()成功回调
86 | @Override
87 | public void onSetSuccess() {
88 |
89 | }
90 |
91 | //Create{Offer,Answer}失败回调
92 | @Override
93 | public void onCreateFailure(String s) {
94 |
95 | }
96 |
97 | //Set{Local,Remote}Description()失败回调
98 | @Override
99 | public void onSetFailure(String s) {
100 |
101 | }
102 |
103 | /**SdpObserver是来回调sdp是否创建(offer,answer)成功,是否设置描述成功(local,remote)的接口**/
104 | //信令状态改变时候触发
105 | @Override
106 | public void onSignalingChange(PeerConnection.SignalingState signalingState) {
107 |
108 | }
109 |
110 | //IceConnectionState连接状态改变时候触发
111 | @Override
112 | public void onIceConnectionChange(PeerConnection.IceConnectionState iceConnectionState) {
113 | Log.d(TAG,"onIceConnectionChange " + iceConnectionState);
114 | if (iceConnectionState == PeerConnection.IceConnectionState.DISCONNECTED){
115 | /** ice连接中断处理 **/
116 | }
117 | }
118 |
119 | //IceConnectionState连接接收状态改变
120 | @Override
121 | public void onIceConnectionReceivingChange(boolean b) {
122 |
123 | }
124 |
125 | //IceConnectionState网络信息获取状态改变
126 | @Override
127 | public void onIceGatheringChange(PeerConnection.IceGatheringState iceGatheringState) {
128 |
129 | }
130 |
131 | //新ice地址被找到触发
132 | @Override
133 | public void onIceCandidate(IceCandidate iceCandidate) {
134 | Log.d(TAG,"onIceCandidate "+iceCandidate.sdpMid);
135 | try {
136 | //构建信令数据
137 | JSONObject message = new JSONObject();
138 | message.put("from",webRtcClient.getSocketId());
139 | message.put("to",id);
140 | message.put("room",webRtcClient.getRoomId());
141 | //candidate参数
142 | JSONObject candidate = new JSONObject();
143 | candidate.put("sdpMid",iceCandidate.sdpMid);
144 | candidate.put("sdpMLineIndex",iceCandidate.sdpMLineIndex);
145 | candidate.put("sdp",iceCandidate.sdp);
146 | message.put("candidate",candidate);
147 | //向信令服务器发送信令
148 | webRtcClient.sendMessage("candidate",message);
149 | } catch (JSONException e) {
150 | e.printStackTrace();
151 | }
152 |
153 | }
154 | //ice地址被移除掉触发
155 | @Override
156 | public void onIceCandidatesRemoved(IceCandidate[] iceCandidates) {
157 |
158 | }
159 |
160 | //Peer连接远端音视频数据到达时触发 注:用onTrack回调代替
161 | @Override
162 | public void onAddStream(MediaStream mediaStream) {
163 | Log.d(TAG,"onAddStream "+ mediaStream.getId());
164 | }
165 |
166 | //Peer连接远端音视频数据移除时触发
167 | @Override
168 | public void onRemoveStream(MediaStream mediaStream) {
169 | Log.d(TAG,"onRemoveStream "+ mediaStream.getId());
170 | //移除Peer连接 & 通知监听远端音视频数据到达
171 | }
172 |
173 | //Peer连接远端开启数据传输通道时触发
174 | @Override
175 | public void onDataChannel(DataChannel dataChannel) {
176 |
177 | }
178 |
179 | //通道交互协议需要重新协商时触发
180 | @Override
181 | public void onRenegotiationNeeded() {
182 |
183 | }
184 |
185 | //Triggered when a new track is signaled by the remote peer, as a result of setRemoteDescription.
186 | @Override
187 | public void onAddTrack(RtpReceiver rtpReceiver, MediaStream[] mediaStreams) {
188 |
189 | }
190 |
191 | @Override
192 | public void onTrack(RtpTransceiver transceiver) {
193 | MediaStreamTrack track = transceiver.getReceiver().track();
194 | Log.d(TAG,"onTrack "+ track.id());
195 | if (track instanceof VideoTrack) {
196 | webRtcClient.getRtcListener().onAddRemoteStream(id,(VideoTrack)track);
197 | }
198 | }
199 | }
200 |
--------------------------------------------------------------------------------
/app/src/main/java/com/dingsoft/webrtc/webrtcroom/activity/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.dingsoft.webrtc.webrtcroom.activity;
2 |
3 | import android.app.Activity;
4 | import android.graphics.Point;
5 | import android.graphics.drawable.ColorDrawable;
6 | import android.os.Bundle;
7 | import android.view.KeyEvent;
8 | import android.view.View;
9 | import android.widget.Button;
10 | import android.widget.EditText;
11 | import android.widget.LinearLayout;
12 | import android.widget.Toast;
13 |
14 | import com.dingsoft.webrtc.webrtcroom.R;
15 | import com.dingsoft.webrtc.webrtcroom.webrtcmodule.PeerConnectionParameters;
16 | import com.dingsoft.webrtc.webrtcroom.webrtcmodule.RtcListener;
17 | import com.dingsoft.webrtc.webrtcroom.webrtcmodule.WebRtcClient;
18 | import com.yanzhenjie.permission.Action;
19 | import com.yanzhenjie.permission.AndPermission;
20 | import com.yanzhenjie.permission.Permission;
21 | import org.webrtc.EglBase;
22 | import org.webrtc.RendererCommon;
23 | import org.webrtc.SurfaceViewRenderer;
24 | import org.webrtc.VideoTrack;
25 |
26 | import java.util.HashMap;
27 | import java.util.List;
28 |
29 | public class MainActivity extends Activity implements RtcListener,View.OnClickListener{
30 | //控件
31 | private EditText roomName;
32 | private Button openCamera;
33 | private Button switchCamera;
34 | private Button createRoom;
35 | private Button exitRoom;
36 | private SurfaceViewRenderer localSurfaceViewRenderer;
37 | private LinearLayout remoteVideoLl;
38 | private HashMap remoteViews;
39 | //EglBase
40 | private EglBase rootEglBase;
41 | //WebRtcClient
42 | private WebRtcClient webRtcClient;
43 | //PeerConnectionParameters
44 | private PeerConnectionParameters peerConnectionParameters;
45 | //host地址
46 | //private String socketHost = "http://172.16.70.226:8081";
47 | private String socketHost = "https://172.16.70.226:8443";
48 |
49 | //记录用户首次点击返回键的时间
50 | private long firstTime = 0;
51 | //摄像头是否开启
52 | private boolean isCameraOpen = false;
53 |
54 | @Override
55 | protected void onCreate(Bundle savedInstanceState) {
56 | super.onCreate(savedInstanceState);
57 | setContentView(R.layout.activity_main);
58 | roomName = findViewById(R.id.room);
59 | openCamera = findViewById(R.id.openCamera);
60 | openCamera.setOnClickListener(this);
61 | switchCamera = findViewById(R.id.switchCamera);
62 | switchCamera.setOnClickListener(this);
63 | createRoom = findViewById(R.id.create);
64 | createRoom.setOnClickListener(this);
65 | exitRoom = findViewById(R.id.exit);
66 | exitRoom.setOnClickListener(this);
67 | localSurfaceViewRenderer = findViewById(R.id.localVideo);
68 | remoteVideoLl = findViewById(R.id.remoteVideoLl);
69 | remoteViews = new HashMap<>();
70 | //创建WebRtcClient
71 | createWebRtcClient();
72 | }
73 |
74 | @Override
75 | protected void onResume() {
76 | super.onResume();
77 | //某些机型锁屏点亮后需要重新开启摄像头
78 | if (isCameraOpen){
79 | webRtcClient.startCamera(localSurfaceViewRenderer,WebRtcClient.FONT_FACTING);
80 | }
81 | }
82 |
83 | @Override
84 | protected void onDestroy() {
85 | super.onDestroy();
86 | //数据销毁
87 | localSurfaceViewRenderer.release();
88 | localSurfaceViewRenderer = null;
89 | }
90 |
91 | @Override
92 | public boolean onKeyUp(int keyCode, KeyEvent event) {
93 | switch (keyCode){
94 | case KeyEvent.KEYCODE_BACK:
95 | long secondTime=System.currentTimeMillis();
96 | if(secondTime-firstTime>2000){
97 | Toast.makeText(MainActivity.this,"再按一次退出程序",Toast.LENGTH_SHORT).show();
98 | firstTime = secondTime;
99 | return true;
100 | }else{
101 | System.exit(0);
102 | }
103 | break;
104 | }
105 | return super.onKeyUp(keyCode, event);
106 | }
107 |
108 | @Override
109 | public void onClick(View v) {
110 | switch (v.getId()) {
111 | case R.id.openCamera:
112 | //开启_关闭摄像头
113 | if(isCameraOpen){
114 | //关闭
115 | webRtcClient.closeCamera();
116 | //数据
117 | localSurfaceViewRenderer.clearImage();
118 | localSurfaceViewRenderer.setBackground(new ColorDrawable(getResources().getColor(R.color.colorBlack)));
119 | //localSurfaceViewRenderer.setForeground(new ColorDrawable(R.color.colorBlack));
120 | localSurfaceViewRenderer.release();
121 | isCameraOpen = false;
122 | openCamera.setText("开启摄像头");
123 | }else{
124 | //开启
125 | openCamera();
126 | }
127 | break;
128 | case R.id.switchCamera:
129 | //切换摄像头
130 | switchCamera();
131 | break;
132 | case R.id.create:
133 | //创建并加入聊天室
134 | String roomId = roomName.getText().toString();
135 | if(isCameraOpen){
136 | webRtcClient.createAndJoinRoom(roomId);
137 | createRoom.setEnabled(false);
138 | }else{
139 | Toast.makeText(this,"请先开启摄像头",Toast.LENGTH_SHORT).show();
140 | }
141 | break;
142 | case R.id.exit:
143 | //退出聊天室
144 | webRtcClient.exitRoom();
145 | createRoom.setEnabled(true);
146 | break;
147 | default:
148 | break;
149 | }
150 | }
151 |
152 | //创建配置参数
153 | private void createPeerConnectionParameters(){
154 | //获取webRtc 音视频配置参数
155 | Point displaySize = new Point();
156 | this.getWindowManager().getDefaultDisplay().getSize(displaySize);
157 | displaySize.set(480,320);
158 | peerConnectionParameters = new PeerConnectionParameters(true, false,
159 | false, displaySize.x, displaySize.y, 30,
160 | 0, "VP8",
161 | true,false,0,"OPUS",
162 | false,false,false,false,false,false,
163 | false,false,false,false);
164 | }
165 |
166 | //创建webRtcClient
167 | private void createWebRtcClient(){
168 | //配置参数
169 | createPeerConnectionParameters();
170 | //创建视频渲染器
171 | rootEglBase = EglBase.create();
172 | //WebRtcClient对象
173 | webRtcClient = new WebRtcClient(getApplicationContext(),
174 | rootEglBase,
175 | peerConnectionParameters,
176 | MainActivity.this,
177 | socketHost);
178 | }
179 |
180 | //本地摄像头创建
181 | private void openCamera(){
182 | if(AndPermission.hasPermissions(this,Permission.Group.CAMERA)){
183 | startCamera();
184 | }else{
185 | AndPermission.with(this)
186 | .runtime()
187 | .permission(Permission.Group.CAMERA)
188 | .onGranted(new Action>() {
189 | @Override
190 | public void onAction(List data) {
191 | //申请权限成功
192 | startCamera();
193 | }
194 | })
195 | .onDenied(new Action>() {
196 | @Override
197 | public void onAction(List data) {
198 | //当用户没有允许该权限时,回调该方法
199 | Toast.makeText(MainActivity.this, "没有获取照相机权限,该功能无法使用", Toast.LENGTH_SHORT).show();
200 | }
201 | }).start();
202 | }
203 | }
204 |
205 | //开启摄像头
206 | private void startCamera(){
207 | //初始化渲染源
208 | localSurfaceViewRenderer.init(rootEglBase.getEglBaseContext(), null);
209 | //填充模式
210 | localSurfaceViewRenderer.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FIT);
211 | localSurfaceViewRenderer.setZOrderMediaOverlay(true);
212 | localSurfaceViewRenderer.setEnableHardwareScaler(false);
213 | localSurfaceViewRenderer.setMirror(true);
214 | localSurfaceViewRenderer.setBackground(null);
215 | //启动摄像头
216 | webRtcClient.startCamera(localSurfaceViewRenderer,WebRtcClient.FONT_FACTING);
217 | //状态设置
218 | isCameraOpen = true;
219 | openCamera.setText("关闭摄像头");
220 | }
221 |
222 | //切换摄像头
223 | private void switchCamera(){
224 | if (webRtcClient != null){
225 | webRtcClient.switchCamera();
226 | }
227 | }
228 |
229 | //清空远端摄像头
230 | public void clearRemoteCamera(){
231 | remoteVideoLl.removeAllViews();
232 | }
233 |
234 | /* RtcListener 数据回调 */
235 | @Override
236 | public void onAddRemoteStream(String peerId,VideoTrack videoTrack) {
237 | this.runOnUiThread(new Runnable() {
238 | @Override
239 | public void run() {
240 | ////UI线程执行
241 | //构建远端view
242 | SurfaceViewRenderer remoteView = new SurfaceViewRenderer(MainActivity.this);
243 | //初始化渲染源
244 | remoteView.init(rootEglBase.getEglBaseContext(), null);
245 | //填充模式
246 | remoteView.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FIT);
247 | remoteView.setZOrderMediaOverlay(true);
248 | remoteView.setEnableHardwareScaler(false);
249 | remoteView.setMirror(true);
250 | //控件布局
251 | LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(360,360);
252 | layoutParams.topMargin = 20;
253 | remoteVideoLl.addView(remoteView,layoutParams);
254 | //添加至hashmap中
255 | remoteViews.put(peerId,remoteView);
256 | //添加数据
257 | //VideoTrack videoTrack = mediaStream.videoTracks.get(0);
258 | videoTrack.addSink(remoteView);
259 | }
260 | });
261 | }
262 |
263 | @Override
264 | public void onRemoveRemoteStream(String peerId) {
265 | this.runOnUiThread(new Runnable() {
266 | @Override
267 | public void run() {
268 | ////UI线程执行
269 | //移除远端view
270 | SurfaceViewRenderer remoteView = (SurfaceViewRenderer)remoteViews.get(peerId);
271 | if (remoteView != null){
272 | remoteVideoLl.removeView(remoteView);
273 | remoteViews.remove(peerId);
274 | //数据销毁
275 | remoteView.release();
276 | remoteView = null;
277 | }
278 | }
279 | });
280 | }
281 | }
282 |
--------------------------------------------------------------------------------
/app/src/main/java/com/dingsoft/webrtc/webrtcroom/webrtcmodule/WebRtcClient.java:
--------------------------------------------------------------------------------
1 | package com.dingsoft.webrtc.webrtcroom.webrtcmodule;
2 |
3 | import android.content.Context;
4 | import android.opengl.EGLContext;
5 | import android.util.Log;
6 |
7 | import com.dingsoft.webrtc.webrtcroom.activity.MainActivity;
8 |
9 | import org.json.JSONArray;
10 | import org.json.JSONException;
11 | import org.json.JSONObject;
12 | import org.webrtc.AudioTrack;
13 | import org.webrtc.Camera1Enumerator;
14 | import org.webrtc.CameraVideoCapturer;
15 | import org.webrtc.DefaultVideoDecoderFactory;
16 | import org.webrtc.DefaultVideoEncoderFactory;
17 | import org.webrtc.EglBase;
18 | import org.webrtc.IceCandidate;
19 | import org.webrtc.MediaConstraints;
20 | import org.webrtc.MediaStream;
21 | import org.webrtc.PeerConnection;
22 | import org.webrtc.PeerConnectionFactory;
23 | import org.webrtc.SessionDescription;
24 | import org.webrtc.SoftwareVideoDecoderFactory;
25 | import org.webrtc.SoftwareVideoEncoderFactory;
26 | import org.webrtc.SurfaceTextureHelper;
27 | import org.webrtc.VideoDecoderFactory;
28 | import org.webrtc.VideoEncoderFactory;
29 | import org.webrtc.VideoSink;
30 | import org.webrtc.VideoSource;
31 | import org.webrtc.VideoTrack;
32 | import org.webrtc.audio.AudioDeviceModule;
33 | import org.webrtc.audio.JavaAudioDeviceModule;
34 | import org.webrtc.audio.LegacyAudioDeviceModule;
35 | import org.webrtc.voiceengine.WebRtcAudioManager;
36 | import org.webrtc.voiceengine.WebRtcAudioRecord;
37 | import org.webrtc.voiceengine.WebRtcAudioTrack;
38 | import org.webrtc.voiceengine.WebRtcAudioUtils;
39 |
40 | import java.net.URISyntaxException;
41 | import java.security.SecureRandom;
42 | import java.util.HashMap;
43 | import java.util.LinkedList;
44 |
45 | import javax.net.ssl.HostnameVerifier;
46 | import javax.net.ssl.SSLContext;
47 | import javax.net.ssl.SSLSession;
48 | import javax.net.ssl.SSLSocketFactory;
49 | import javax.net.ssl.TrustManager;
50 |
51 | import io.socket.client.IO;
52 | import io.socket.client.Socket;
53 | import io.socket.emitter.Emitter;
54 | import okhttp3.OkHttpClient;
55 |
56 | /**
57 | * WebRtcClient类 封装PeerConnectionFactory工厂类及Socket.IO信令服务器
58 | * Created by chengshaobo on 2018/10/24.
59 | */
60 |
61 | public class WebRtcClient {
62 | //Log Tag
63 | private final static String TAG = WebRtcClient.class.getCanonicalName();
64 | //PeerConnectionFactory工厂类
65 | private PeerConnectionFactory factory;
66 | //Peer集合
67 | private HashMap peers = new HashMap<>();
68 | //IceServer集合 用于构建PeerConnection
69 | private LinkedList iceServers = new LinkedList<>();
70 | //PeerConnectFactory构建参数
71 | private PeerConnectionParameters pcParams;
72 | //PeerConnect构建参数
73 | PeerConnection.RTCConfiguration rtcConfig;
74 | //PeerConnect 音频约束
75 | private MediaConstraints audioConstraints;
76 | //PeerConnect sdp约束
77 | private MediaConstraints sdpMediaConstraints;
78 | //本地Video视频资源
79 | private VideoSource localVideoSource;
80 | //视频Track
81 | private VideoTrack localVideoTrack;
82 | //音频Track
83 | private AudioTrack localAudioTrack;
84 | //本地摄像头视频捕获
85 | private CameraVideoCapturer cameraVideoCapturer;
86 | //页面context
87 | private Context appContext;
88 | //WebRtc EglContext环境
89 | private EglBase eglBase;
90 | //Activity回调接口
91 | private RtcListener rtcListener;
92 | //socket.io信令交互
93 | private Socket client;
94 | //信令服务器地址
95 | private String host;
96 | //本地socket id
97 | private String socketId;
98 | //room id
99 | private String roomId;
100 |
101 | ////webRtc定义常量////
102 | private static final String AUDIO_ECHO_CANCELLATION_CONSTRAINT = "googEchoCancellation";
103 | private static final String AUDIO_AUTO_GAIN_CONTROL_CONSTRAINT = "googAutoGainControl";
104 | private static final String AUDIO_HIGH_PASS_FILTER_CONSTRAINT = "googHighpassFilter";
105 | private static final String AUDIO_NOISE_SUPPRESSION_CONSTRAINT = "googNoiseSuppression";
106 | private static final String VIDEO_FLEXFEC_FIELDTRIAL =
107 | "WebRTC-FlexFEC-03-Advertised/Enabled/WebRTC-FlexFEC-03/Enabled/";
108 | private static final String VIDEO_VP8_INTEL_HW_ENCODER_FIELDTRIAL = "WebRTC-IntelVP8/Enabled/";
109 | private static final String DISABLE_WEBRTC_AGC_FIELDTRIAL =
110 | "WebRTC-Audio-MinimizeResamplingOnMobile/Enabled/";
111 | public static final int FONT_FACTING = 0 ;
112 | public static final int BACK_FACING = 1 ;
113 | //构造函数
114 | public WebRtcClient(Context appContext,
115 | EglBase eglBase,
116 | PeerConnectionParameters peerConnectionParameters,
117 | RtcListener listener,
118 | String host) {
119 | //参数设置
120 | this.appContext = appContext;
121 | this.eglBase = eglBase;
122 | this.pcParams = peerConnectionParameters;
123 | this.rtcListener = listener;
124 | this.host = host;
125 | //PeerConnectionFactory工厂类构建
126 | createPeerConnectionFactoryInternal();
127 | //创建iceservers
128 | createIceServers();
129 | //创建RTCConfiguration参数
130 | createRtcConfig();
131 | //创建Pc及Sdp约束
132 | createMediaConstraintsInternal();
133 | //Socket.IO信令服务器构建
134 | createSocket();
135 | }
136 |
137 | public RtcListener getRtcListener() {
138 | return rtcListener;
139 | }
140 |
141 | public String getSocketId() {
142 | return socketId;
143 | }
144 |
145 | public String getRoomId() {
146 | return roomId;
147 | }
148 |
149 | //创建IceServers参数
150 | private void createIceServers() {
151 | iceServers.add(PeerConnection.IceServer.builder("stun:stun.xten.com").createIceServer());
152 | }
153 |
154 | //创建RTCConfiguration参数
155 | private void createRtcConfig() {
156 | rtcConfig =
157 | new PeerConnection.RTCConfiguration(iceServers);
158 | // TCP candidates are only useful when connecting to a server that supports
159 | // ICE-TCP.
160 | rtcConfig.tcpCandidatePolicy = PeerConnection.TcpCandidatePolicy.DISABLED;
161 | rtcConfig.bundlePolicy = PeerConnection.BundlePolicy.MAXBUNDLE;
162 | rtcConfig.rtcpMuxPolicy = PeerConnection.RtcpMuxPolicy.REQUIRE;
163 | rtcConfig.continualGatheringPolicy = PeerConnection.ContinualGatheringPolicy.GATHER_CONTINUALLY;
164 | // Use ECDSA encryption.
165 | rtcConfig.keyType = PeerConnection.KeyType.ECDSA;
166 | // Enable DTLS for normal calls and disable for loopback calls.
167 | rtcConfig.enableDtlsSrtp = !pcParams.loopback;
168 | rtcConfig.sdpSemantics = PeerConnection.SdpSemantics.UNIFIED_PLAN;
169 | }
170 |
171 |
172 | //创建PeerConnection工厂类
173 | private void createPeerConnectionFactoryInternal() {
174 | //创建webRtc连接工厂类
175 | final VideoEncoderFactory encoderFactory;
176 | final VideoDecoderFactory decoderFactory;
177 | final boolean enableH264HighProfile =
178 | "H264 High".equals(pcParams.videoCodec);
179 | //音频模式
180 | final AudioDeviceModule adm = pcParams.useLegacyAudioDevice
181 | ? createLegacyAudioDevice()
182 | : createJavaAudioDevice();
183 | //编解码模式【硬件加速,软编码】
184 | if (pcParams.videoCodecHwAcceleration) {
185 | encoderFactory = new DefaultVideoEncoderFactory(
186 | eglBase.getEglBaseContext(), true /* enableIntelVp8Encoder */, enableH264HighProfile);
187 | decoderFactory = new DefaultVideoDecoderFactory(eglBase.getEglBaseContext());
188 | } else {
189 | encoderFactory = new SoftwareVideoEncoderFactory();
190 | decoderFactory = new SoftwareVideoDecoderFactory();
191 | }
192 | //PeerConnectionFactory.initialize
193 | String fieldTrials = "";
194 | if (pcParams.videoFlexfecEnabled) {
195 | fieldTrials += VIDEO_FLEXFEC_FIELDTRIAL;
196 | }
197 | fieldTrials += VIDEO_VP8_INTEL_HW_ENCODER_FIELDTRIAL;
198 | if (pcParams.disableWebRtcAGCAndHPF) {
199 | fieldTrials += DISABLE_WEBRTC_AGC_FIELDTRIAL;
200 | }
201 | //PeerConnectionFactory.initialize
202 | PeerConnectionFactory.initialize(
203 | PeerConnectionFactory.InitializationOptions.builder(appContext)
204 | .setFieldTrials(fieldTrials)
205 | .setEnableInternalTracer(true)
206 | .createInitializationOptions());
207 | //构建PeerConnectionFactory
208 | PeerConnectionFactory.Options options = new PeerConnectionFactory.Options();
209 | factory = PeerConnectionFactory.builder()
210 | .setOptions(options)
211 | .setAudioDeviceModule(adm)
212 | .setVideoEncoderFactory(encoderFactory)
213 | .setVideoDecoderFactory(decoderFactory)
214 | .createPeerConnectionFactory();
215 | }
216 |
217 | //创建信令服务器及监听
218 | private void createSocket() {
219 | //socket模式连接信令服务器
220 | try {
221 | //普通连接
222 | //client = IO.socket(host);
223 |
224 | //SSL加密连接
225 | OkHttpClient okHttpClient = new OkHttpClient.Builder()
226 | .hostnameVerifier(new HostnameVerifier() {
227 | @Override
228 | public boolean verify(String hostname, SSLSession session) {
229 | return true;
230 | }
231 | })
232 | .sslSocketFactory(getSSLSocketFactory(), new TrustAllCerts())
233 | .build();
234 | IO.setDefaultOkHttpWebSocketFactory(okHttpClient);
235 | IO.setDefaultOkHttpCallFactory(okHttpClient);
236 | IO.Options opts = new IO.Options();
237 | opts.callFactory = okHttpClient;
238 | opts.webSocketFactory = okHttpClient;
239 | client = IO.socket(host, opts);
240 |
241 | ////设置消息监听
242 | //created [id,room,peers]
243 | client.on("created", createdListener);
244 | //joined [id,room]
245 | client.on("joined", joinedListener);
246 | //offer [from,to,room,sdp]
247 | client.on("offer", offerListener);
248 | //answer [from,to,room,sdp]
249 | client.on("answer", answerListener);
250 | //candidate [from,to,room,candidate[sdpMid,sdpMLineIndex,sdp]]
251 | client.on("candidate", candidateListener);
252 | //exit [from,room]
253 | client.on("exit", exitListener);
254 | //开始连接
255 | client.connect();
256 | } catch (URISyntaxException e) {
257 | e.printStackTrace();
258 | }
259 | }
260 |
261 | /** UI操作相关 */
262 | //创建并加入
263 | public void createAndJoinRoom(String roomId){
264 | //构建信令数据并发送
265 | try {
266 | JSONObject message = new JSONObject();
267 | message.put("room",roomId);
268 | //向信令服务器发送信令
269 | sendMessage("createAndJoinRoom",message);
270 | }catch (JSONException e){
271 | e.printStackTrace();
272 | }
273 | }
274 |
275 | //退出room
276 | public void exitRoom(){
277 | //信令服务器发送 exit [from room]
278 | try {
279 | JSONObject message = new JSONObject();
280 | message.put("from",socketId);
281 | message.put("room",roomId);
282 | //向信令服务器发送信令
283 | sendMessage("exit",message);
284 | }catch (JSONException e){
285 | e.printStackTrace();
286 | }
287 | //循环遍历 peer关闭
288 | for(Peer pc: peers.values())
289 | {
290 | pc.getPc().close();
291 | }
292 | //数据重置
293 | socketId = "";
294 | roomId = "";
295 | peers.clear();
296 | //通知UI清空远端摄像头
297 | ((MainActivity)rtcListener).clearRemoteCamera();
298 | }
299 |
300 | /** WebRtc相关 */
301 | //构建webRtc连接并返回
302 | private Peer getOrCreateRtcConnect(String socketId) {
303 | Peer pc = peers.get(socketId);
304 | if (pc == null) {
305 | //构建RTCPeerConnection PeerConnection相关回调进入Peer中
306 | pc = new Peer(socketId,factory,rtcConfig,WebRtcClient.this);
307 | //设置本地数据流
308 | pc.getPc().addTrack(localVideoTrack);
309 | //保存peer连接
310 | peers.put(socketId,pc);
311 | }
312 | return pc;
313 | }
314 |
315 | //启动设备视频并关联本地video
316 | public void startCamera(VideoSink localRender,int type){
317 | if(pcParams.videoCallEnabled){
318 | //创建VideoCapturer
319 | if (cameraVideoCapturer == null){
320 | String cameraname = "";
321 | Camera1Enumerator camera1Enumerator = new Camera1Enumerator();
322 | String[] deviceNames = camera1Enumerator.getDeviceNames();
323 | if (type == FONT_FACTING){
324 | //前置摄像头
325 | for (String deviceName : deviceNames){
326 | if (camera1Enumerator.isFrontFacing(deviceName)){
327 | cameraname = deviceName;
328 | }
329 | }
330 | }else {
331 | //后置摄像头
332 | for (String deviceName : deviceNames){
333 | if (camera1Enumerator.isBackFacing(deviceName)){
334 | cameraname = deviceName;
335 | }
336 | }
337 | }
338 | cameraVideoCapturer = camera1Enumerator.createCapturer(cameraname,null);
339 | SurfaceTextureHelper surfaceTextureHelper =
340 | SurfaceTextureHelper.create("CaptureThread", eglBase.getEglBaseContext());
341 | localVideoSource = factory.createVideoSource(false);
342 | cameraVideoCapturer.initialize(surfaceTextureHelper, appContext, localVideoSource.getCapturerObserver());
343 | cameraVideoCapturer.startCapture(pcParams.videoWidth,pcParams.videoHeight,pcParams.videoFps);
344 | localVideoTrack = factory.createVideoTrack("ARDAMSv0", localVideoSource);
345 | localVideoTrack.setEnabled(true);
346 | localVideoTrack.addSink(localRender);
347 | }else{
348 | cameraVideoCapturer.startCapture(pcParams.videoWidth,pcParams.videoHeight,pcParams.videoFps);
349 | }
350 | }
351 | }
352 |
353 | //切换摄像头
354 | public void switchCamera(){
355 | if(cameraVideoCapturer != null){
356 | cameraVideoCapturer.switchCamera(null);
357 | }
358 | }
359 |
360 | //关闭摄像头
361 | public void closeCamera(){
362 | if(cameraVideoCapturer != null){
363 | try {
364 | cameraVideoCapturer.stopCapture();
365 | } catch (InterruptedException e) {
366 | e.printStackTrace();
367 | }
368 | }
369 | }
370 |
371 | /** 信令服务器处理相关 **/
372 | //created [id,room,peers]
373 | private Emitter.Listener createdListener = new Emitter.Listener() {
374 | @Override
375 | public void call(Object... args) {
376 | JSONObject data = (JSONObject) args[0];
377 | Log.d(TAG, "created:" + data);
378 | try {
379 | //设置socket id
380 | socketId = data.getString("id");
381 | //设置room id
382 | roomId = data.getString("room");
383 | //获取peer数据
384 | JSONArray peers = data.getJSONArray("peers");
385 | //根据回应peers 循环创建WebRtcPeerConnection,创建成功后发送offer消息 [from,to,room,sdp]
386 | for (int i = 0; i < peers.length(); i++) {
387 | JSONObject otherPeer = peers.getJSONObject(i);
388 | String otherSocketId = otherPeer.getString("id");
389 | //创建WebRtcPeerConnection
390 | Peer pc = getOrCreateRtcConnect(otherSocketId);
391 | //设置offer
392 | pc.getPc().createOffer(pc,sdpMediaConstraints);
393 | }
394 | } catch (JSONException e) {
395 | e.printStackTrace();
396 | }
397 | }
398 | };
399 |
400 | //joined [id,room]
401 | private Emitter.Listener joinedListener = new Emitter.Listener() {
402 | @Override
403 | public void call(Object... args) {
404 | JSONObject data = (JSONObject) args[0];
405 | Log.d(TAG, "joined:" + data);
406 | try {
407 | //获取新加入socketId
408 | String fromId = data.getString("id");
409 | //构建pcconnection
410 | getOrCreateRtcConnect(fromId);
411 | } catch (JSONException e) {
412 | e.printStackTrace();
413 | }
414 | }
415 | };
416 |
417 | //offer [from,to,room,sdp]
418 | private Emitter.Listener offerListener = new Emitter.Listener() {
419 | @Override
420 | public void call(Object... args) {
421 | JSONObject data = (JSONObject) args[0];
422 | Log.d(TAG, "offer:" + data);
423 | try {
424 | //获取id
425 | String fromId = data.getString("from");
426 | //获取peer
427 | Peer pc = getOrCreateRtcConnect(fromId);
428 | //构建RTCSessionDescription参数
429 | SessionDescription sdp = new SessionDescription(
430 | SessionDescription.Type.fromCanonicalForm("offer"),
431 | data.getString("sdp")
432 | );
433 | //设置远端setRemoteDescription
434 | pc.getPc().setRemoteDescription(pc,sdp);
435 | //设置answer
436 | pc.getPc().createAnswer(pc,sdpMediaConstraints);
437 | } catch (JSONException e) {
438 | e.printStackTrace();
439 | }
440 | }
441 | };
442 |
443 | //answer [from,to,room,sdp]
444 | private Emitter.Listener answerListener = new Emitter.Listener() {
445 | @Override
446 | public void call(Object... args) {
447 | JSONObject data = (JSONObject) args[0];
448 | Log.d(TAG, "answer:" + data);
449 | try {
450 | //获取id
451 | String fromId = data.getString("from");
452 | //获取peer
453 | Peer pc = getOrCreateRtcConnect(fromId);
454 | //构建RTCSessionDescription参数
455 | SessionDescription sdp = new SessionDescription(
456 | SessionDescription.Type.fromCanonicalForm("answer"),
457 | data.getString("sdp")
458 | );
459 | //设置远端setRemoteDescription
460 | pc.getPc().setRemoteDescription(pc,sdp);
461 | } catch (JSONException e) {
462 | e.printStackTrace();
463 | }
464 | }
465 | };
466 |
467 | //candidate [from,to,room,candidate[sdpMid,sdpMLineIndex,sdp]]
468 | private Emitter.Listener candidateListener = new Emitter.Listener() {
469 | @Override
470 | public void call(Object... args) {
471 | JSONObject data = (JSONObject) args[0];
472 | Log.d(TAG, "candidate:" + data);
473 | try {
474 | //获取id
475 | String fromId = data.getString("from");
476 | //获取peer
477 | Peer pc = getOrCreateRtcConnect(fromId);
478 | //获取candidate
479 | JSONObject candidate = data.getJSONObject("candidate");
480 | IceCandidate iceCandidate = new IceCandidate(
481 | candidate.getString("sdpMid"), //描述协议id
482 | candidate.getInt("sdpMLineIndex"),//描述协议的行索引
483 | candidate.getString("sdp")//描述协议
484 | );
485 |
486 | //添加远端设备路由描述
487 | pc.getPc().addIceCandidate(iceCandidate);
488 | } catch (JSONException e) {
489 | e.printStackTrace();
490 | }
491 | }
492 | };
493 |
494 | //exit [from,room]
495 | private Emitter.Listener exitListener = new Emitter.Listener() {
496 | @Override
497 | public void call(Object... args) {
498 | JSONObject data = (JSONObject) args[0];
499 | Log.d(TAG, "exit:" + data);
500 | try {
501 | //获取id
502 | String fromId = data.getString("from");
503 | //判断是否为当前连接
504 | Peer pc = peers.get(fromId);
505 | if (pc != null){
506 | //peer关闭
507 | getOrCreateRtcConnect(fromId).getPc().close();
508 | //删除peer对象
509 | peers.remove(fromId);
510 | //通知UI界面移除video
511 | rtcListener.onRemoveRemoteStream(fromId);
512 | }
513 | } catch (JSONException e) {
514 | e.printStackTrace();
515 | }
516 | }
517 | };
518 |
519 | /** 信令服务器发送消息 **/
520 | public void sendMessage(String event,JSONObject message){
521 | client.emit(event, message);
522 | }
523 |
524 | /** WebRtc 音视频相关辅助函数**/
525 | //创建Media及Sdp约束
526 | private void createMediaConstraintsInternal() {
527 | // 音频约束
528 | audioConstraints = new MediaConstraints();
529 | // added for audio performance measurements
530 | if (pcParams.noAudioProcessing) {
531 | Log.d(TAG, "Disabling audio processing");
532 | audioConstraints.mandatory.add(
533 | new MediaConstraints.KeyValuePair(AUDIO_ECHO_CANCELLATION_CONSTRAINT, "false"));
534 | audioConstraints.mandatory.add(
535 | new MediaConstraints.KeyValuePair(AUDIO_AUTO_GAIN_CONTROL_CONSTRAINT, "false"));
536 | audioConstraints.mandatory.add(
537 | new MediaConstraints.KeyValuePair(AUDIO_HIGH_PASS_FILTER_CONSTRAINT, "false"));
538 | audioConstraints.mandatory.add(
539 | new MediaConstraints.KeyValuePair(AUDIO_NOISE_SUPPRESSION_CONSTRAINT, "false"));
540 | }
541 |
542 | //SDP约束 createOffer createAnswer
543 | sdpMediaConstraints = new MediaConstraints();
544 | sdpMediaConstraints.mandatory.add(
545 | new MediaConstraints.KeyValuePair("OfferToReceiveAudio", "true"));
546 | sdpMediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair(
547 | "OfferToReceiveVideo", "true" ));
548 | sdpMediaConstraints.optional.add(new MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true"));
549 | }
550 |
551 | //创建PeerConnection
552 | private void createPeerConnectionInternal() {
553 |
554 | }
555 |
556 |
557 | //创建音频模式LegacyAudioDevice
558 | private AudioDeviceModule createLegacyAudioDevice() {
559 | // Enable/disable OpenSL ES playback.
560 | if (!pcParams.useOpenSLES) {
561 | Log.d(TAG, "Disable OpenSL ES audio even if device supports it");
562 | WebRtcAudioManager.setBlacklistDeviceForOpenSLESUsage(true /* enable */);
563 | } else {
564 | Log.d(TAG, "Allow OpenSL ES audio if device supports it");
565 | WebRtcAudioManager.setBlacklistDeviceForOpenSLESUsage(false);
566 | }
567 |
568 | if (pcParams.disableBuiltInAEC) {
569 | Log.d(TAG, "Disable built-in AEC even if device supports it");
570 | WebRtcAudioUtils.setWebRtcBasedAcousticEchoCanceler(true);
571 | } else {
572 | Log.d(TAG, "Enable built-in AEC if device supports it");
573 | WebRtcAudioUtils.setWebRtcBasedAcousticEchoCanceler(false);
574 | }
575 |
576 | if (pcParams.disableBuiltInNS) {
577 | Log.d(TAG, "Disable built-in NS even if device supports it");
578 | WebRtcAudioUtils.setWebRtcBasedNoiseSuppressor(true);
579 | } else {
580 | Log.d(TAG, "Enable built-in NS if device supports it");
581 | WebRtcAudioUtils.setWebRtcBasedNoiseSuppressor(false);
582 | }
583 |
584 | //WebRtcAudioRecord.setOnAudioSamplesReady(saveRecordedAudioToFile);
585 |
586 | // Set audio record error callbacks.
587 | WebRtcAudioRecord.setErrorCallback(new WebRtcAudioRecord.WebRtcAudioRecordErrorCallback() {
588 | @Override
589 | public void onWebRtcAudioRecordInitError(String errorMessage) {
590 | Log.e(TAG, "onWebRtcAudioRecordInitError: " + errorMessage);
591 | }
592 |
593 | @Override
594 | public void onWebRtcAudioRecordStartError(
595 | WebRtcAudioRecord.AudioRecordStartErrorCode errorCode, String errorMessage) {
596 | Log.e(TAG, "onWebRtcAudioRecordStartError: " + errorCode + ". " + errorMessage);
597 | }
598 |
599 | @Override
600 | public void onWebRtcAudioRecordError(String errorMessage) {
601 | Log.e(TAG, "onWebRtcAudioRecordError: " + errorMessage);
602 | }
603 | });
604 |
605 | WebRtcAudioTrack.setErrorCallback(new WebRtcAudioTrack.ErrorCallback() {
606 | @Override
607 | public void onWebRtcAudioTrackInitError(String errorMessage) {
608 | Log.e(TAG, "onWebRtcAudioTrackInitError: " + errorMessage);
609 | }
610 |
611 | @Override
612 | public void onWebRtcAudioTrackStartError(
613 | WebRtcAudioTrack.AudioTrackStartErrorCode errorCode, String errorMessage) {
614 | Log.e(TAG, "onWebRtcAudioTrackStartError: " + errorCode + ". " + errorMessage);
615 | }
616 |
617 | @Override
618 | public void onWebRtcAudioTrackError(String errorMessage) {
619 | Log.e(TAG, "onWebRtcAudioTrackError: " + errorMessage);
620 | }
621 | });
622 |
623 | return new LegacyAudioDeviceModule();
624 | }
625 |
626 | //创建音频模式JavaAudioDevice
627 | private AudioDeviceModule createJavaAudioDevice() {
628 | // Enable/disable OpenSL ES playback.
629 | if (!pcParams.useOpenSLES) {
630 | Log.w(TAG, "External OpenSLES ADM not implemented yet.");
631 | // TODO(magjed): Add support for external OpenSLES ADM.
632 | }
633 |
634 | // Set audio record error callbacks.
635 | JavaAudioDeviceModule.AudioRecordErrorCallback audioRecordErrorCallback = new JavaAudioDeviceModule.AudioRecordErrorCallback() {
636 | @Override
637 | public void onWebRtcAudioRecordInitError(String errorMessage) {
638 | Log.e(TAG, "onWebRtcAudioRecordInitError: " + errorMessage);
639 | }
640 |
641 | @Override
642 | public void onWebRtcAudioRecordStartError(
643 | JavaAudioDeviceModule.AudioRecordStartErrorCode errorCode, String errorMessage) {
644 | Log.e(TAG, "onWebRtcAudioRecordStartError: " + errorCode + ". " + errorMessage);
645 | }
646 |
647 | @Override
648 | public void onWebRtcAudioRecordError(String errorMessage) {
649 | Log.e(TAG, "onWebRtcAudioRecordError: " + errorMessage);
650 | }
651 | };
652 |
653 | JavaAudioDeviceModule.AudioTrackErrorCallback audioTrackErrorCallback = new JavaAudioDeviceModule.AudioTrackErrorCallback() {
654 | @Override
655 | public void onWebRtcAudioTrackInitError(String errorMessage) {
656 | Log.e(TAG, "onWebRtcAudioTrackInitError: " + errorMessage);
657 | }
658 |
659 | @Override
660 | public void onWebRtcAudioTrackStartError(
661 | JavaAudioDeviceModule.AudioTrackStartErrorCode errorCode, String errorMessage) {
662 | Log.e(TAG, "onWebRtcAudioTrackStartError: " + errorCode + ". " + errorMessage);
663 | }
664 |
665 | @Override
666 | public void onWebRtcAudioTrackError(String errorMessage) {
667 | Log.e(TAG, "onWebRtcAudioTrackError: " + errorMessage);
668 | }
669 | };
670 |
671 | return JavaAudioDeviceModule.builder(appContext)
672 | //.setSamplesReadyCallback(saveRecordedAudioToFile)
673 | .setUseHardwareAcousticEchoCanceler(!pcParams.disableBuiltInAEC)
674 | .setUseHardwareNoiseSuppressor(!pcParams.disableBuiltInNS)
675 | .setAudioRecordErrorCallback(audioRecordErrorCallback)
676 | .setAudioTrackErrorCallback(audioTrackErrorCallback)
677 | .createAudioDeviceModule();
678 | }
679 |
680 | //返回SSLSocketFactory 用于ssl连接
681 | private SSLSocketFactory getSSLSocketFactory() {
682 | SSLSocketFactory ssfFactory = null;
683 |
684 | try {
685 | SSLContext sc = SSLContext.getInstance("TLS");
686 | sc.init(null, new TrustManager[]{new TrustAllCerts()}, new SecureRandom());
687 |
688 | ssfFactory = sc.getSocketFactory();
689 | } catch (Exception e) {
690 | e.printStackTrace();
691 | }
692 | return ssfFactory;
693 | }
694 | }
695 |
--------------------------------------------------------------------------------