WebRTC学习之ProjectRTC & AndroidRTC运行

前言

第一次写博客,有点小鸡冻~

之前领导要求研究一下webrtc,在Android和web间的使用。在网上找了demo,准备边看边学,并在此记录下来。新手上路,有不对的地方恳请各位大佬指教!

项目地址

ProjectRTC & AndroidRTC是webRTC的一个开源实现,利用webRTC实现了Android端与web端、web端与web端之间的RTC通话功能。

AndroidRTC:https://github.com/pchab/AndroidRTC
ProjectRTC: https://github.com/pchab/ProjectRTC

项目运行

ProjectRTC

1.ProjectRTC运行在node服务器上,所以首先要安装node环境(之前已经装过node,且网上教程非常多,这里不记录了。npm貌似在安装node的时候会一起装上)。

2.打开terminal、打开ProjectRTC所在文件夹

1
cd /Users/xxx/Desktop/ProjectRTC-master

3.安装模块(只有第一次需要安装,之后直接跳过这步)

1
npm install

4.开启服务器

1
npm start

5.测试是否成功开启

在Chrome中打开下面的网址

1
2
3
1.http://localhost:3000/
2.http://127.0.0.1:3000/
3.http://10.53.60.175:3000/(这里是本机IP地址,需要修改为自己的)

结果出现了这样的画面

看来并没有正常运行,右键选择检查->选择console

6.可以看到是angular.min.js文件出现问题,通过检索,发现 views/index.ejs 第12行有https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js,猜测是无法连接Google导致,然后在网上找到1.3.15版本的angular.min.js文件保存到本地,放入/public/javascripts/,然后修改index.ejs第12行的src为

1
src="javascripts/angular.min.js"

7.重新打开localhost:3000->点击start->浏览器申请摄像头权限-> 出现自己的大脸->点击stop出现链接(感觉好像是个bug?)->打开新的浏览器窗口并输入复制的网址->看到两张自己的大脸

8.慢着!在第7步的时候,如果打开的不是 localhost:3000,而是IP:3000,会很尴尬地发现自己的大脸并没有出现,而是会

(不知道别人会不会,反正我这样了)

Browser does not appear to be WebRTC-capable

毕竟用手机连的话还是得用IP地址,所以这个问题不能不解决。

网上看到webrtc必须用https,猜想或许是这个问题。

于是使用OpenSSL自签名生成服务器的加密证书 (免费,如果有自己的域名,也可以去申请SSL证书并下载,这里只为把服务器配置为https访问方式,并不真的验证,因此随便用啥都行,网上有很多OpenSSL生成证书的教程,后面有时间再补充进来)

得到服务器端的证书文件server.crt和server.key,新建一个文件夹ssl并将证书文件放入,然后将文件夹放入项目根目录下

在app.js中加入SSL相关的代码,下面是修改后的完整代码

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
/**
 * Module dependencies.
 */
var express = require('express')
,   path = require('path')
,   streams = require('./app/streams.js')();

var favicon = require('serve-favicon')
,   logger = require('morgan')
,   methodOverride = require('method-override')
,   bodyParser = require('body-parser')
,   errorHandler = require('errorhandler');

var https = require("https");  //1.
var fs = require("fs");  //2.
var app = express();

// all environments
app.set('port', process.env.PORT || 3000);
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
app.use(favicon(__dirname + '/public/images/favicon.ico'));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(methodOverride());
app.use(express.static(path.join(__dirname, 'public')));
const httpsOption = {                         //3.
  key : fs.readFileSync("ssl/server.key"),
  cert: fs.readFileSync("ssl/server.crt")
}

// development only
if ('development' == app.get('env')) {
  app.use(errorHandler());
}

// routing
require('./app/routes.js')(app, streams);

// var server = app.listen(app.get('port'), function(){           4.//
//   console.log('Express server listening on port ' + app.get('port'));
// });
var Httpsserver = https.createServer(httpsOption, app).listen(app.get('port'), function(){    //5.
  console.log('Express server listening on port ' + app.get('port'));
});

// var io = require('socket.io').listen(server);   //6.
var io = require('socket.io').listen(Httpsserver);   //7.
/**
 * Socket.io event handling
 */
require('./app/socketHandler.js')(io, streams);

重启服务器:

1
npm restart

Chrome中打开https://IP地址:3000(注意是https),成功!

此时用localhost和127.0.0.1打开也是用https(提示您的连接不是私密连接,选择高级->继续前往),成功!

AndroidRTC

1.res/values/strings.xml 中修改host为自己的服务器IP

2.java/fr/pchab/androidrtc/RtcActivity.java 中第65行修改http为https

3.build.gradle(Module:webrtc-client) 中修改socket.io的版本

1
2
3
4
5
6
7
8
dependencies {
    //implementation 'com.github.nkzawa:socket.io-client:0.4.2'
    api 'io.pristine:libjingle:9127@aar'
    api ('io.socket:socket.io-client:1.0.0') {
        // excluding org.json which is provided by Android
        exclude group: 'org.json', module: 'json'
    }
}

4.webrtc-client/src/main/java/fr/pchab/webrtcclient/WebRtcClient.java 中增加SSL验证代码(这里是信任所有证书,只增加了一个过程,并没有实际去校验证书)

修改import

1
2
3
4
5
6
7
//import com.github.nkzawa.socketio.client.IO;
//import com.github.nkzawa.socketio.client.Socket;
import io.socket.client.IO;
import io.socket.client.Socket;
import io.socket.emitter.Emitter;
import okhttp3.OkHttpClient;
import java.security.NoSuchAlgorithmException;

修改webrtcclient(miTM为新增函数,WebRtcClient是在原先基础上修改过后的)

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
static class miTM implements TrustManager,X509TrustManager {
        public X509Certificate[] getAcceptedIssuers() {
            return null;
        }

        public boolean isServerTrusted(X509Certificate[] certs) {
            return true;
        }

        public boolean isClientTrusted(X509Certificate[] certs) {
            return true;
        }

        public void checkServerTrusted(X509Certificate[] certs, String authType)
                throws CertificateException {
            return;
        }

        public void checkClientTrusted(X509Certificate[] certs, String authType)
                throws CertificateException {
            return;
        }
    }

    public WebRtcClient(RtcListener listener, String host, PeerConnectionParameters params, EGLContext mEGLcontext) {
        mListener = listener;
        pcParams = params;
        PeerConnectionFactory.initializeAndroidGlobals(listener, true, true,
                params.videoCodecHwAcceleration, mEGLcontext);
        factory = new PeerConnectionFactory();
        MessageHandler messageHandler = new MessageHandler();

        try {
            TrustManager[] trustAllCerts = new TrustManager[1];
            TrustManager tm = new miTM();
            trustAllCerts[0] = tm;
            SSLContext sc = SSLContext.getInstance("SSL");
            X509TrustManager x509m = new X509TrustManager() {
                //          返回受信任的X509证书数组。
                @Override
                public X509Certificate[] getAcceptedIssuers() {
                    return new java.security.cert.X509Certificate[] {};
                }
                //          该方法检查服务器的证书,若不信任该证书同样抛出异常。通过自己实现该方法,可以使之信任我们指定的任何证书。
//          在实现该方法时,也可以简单的不做任何处理,即一个空的函数体,由于不会抛出异常,它就会信任任何证书。
                @Override
                public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                }
                //          该方法检查客户端的证书,若不信任该证书则抛出异常。由于我们不需要对客户端进行认证,
//          因此我们只需要执行默认的信任管理器的这个方法。JSSE中,默认的信任管理器类为TrustManager。
                @Override
                public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                }
            };

            try {
                sc.init(null, trustAllCerts, null);
            } catch ( KeyManagementException e ) {
                e.printStackTrace();
            }
            OkHttpClient okHttpClient = new OkHttpClient.Builder()
                    .hostnameVerifier(new HostnameVerifier() {
                        @Override
                        public boolean verify( String s, SSLSession sslSession ) {
                            return true;
                        }
                    })
                    .sslSocketFactory(sc.getSocketFactory(), x509m)
                    .build();
            IO.Options opts = new IO.Options();
            opts.callFactory = okHttpClient;
            opts.webSocketFactory = okHttpClient;

            // default settings for all sockets
            IO.setDefaultOkHttpWebSocketFactory(okHttpClient);
            IO.setDefaultOkHttpCallFactory(okHttpClient);

            // set as an option
            client = IO.socket(host,opts);
        } catch (URISyntaxException | NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        client.on("id", messageHandler.onId);
        client.on("message", messageHandler.onMessage);
        client.connect();

        iceServers.add(new PeerConnection.IceServer("stun:23.21.150.121"));
        iceServers.add(new PeerConnection.IceServer("stun:stun.l.google.com:19302"));

        pcConstraints.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveAudio", "true"));
        pcConstraints.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveVideo", "true"));
        pcConstraints.optional.add(new MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true"));
    }

5.重新编译运行。(注意服务器必须处于开启状态)

6.弹窗中出现的连接复制下来,在浏览器中打开。完成!(Android并没有获取到视频,好像是因为模拟器不支持调用摄像头,在真机上是可以正常跑的。)

总结

这个demo时间比较早,webrtc的新版本要用到https,因此需要增加ssl证书,Android也需要验证。Android端也没有错误提示,因此在整个连接过程中有一个环节出错,在App跑的时候就不会出现弹窗。