最近在做一个webrtc相关的项目,有接触到Janus,参考了github上的一个android demo:https://github.com/benwtrent/janus-gateway-android
demo中有一个module叫janusagent,通过websoket的方式与janus服务器进行通信,接下来简单讲一下代码的逻辑。
Module Janusagent
首先说一下与janusagent进行连接的顺序:
- createSession
- 建立websocket连接
- 创建transaction, 将request发给服务器
- 根据消息类型做不同的处理
Interface
IJCallback : void onError() 回调父接口
-
IJAttachPluginCallback extends IJCallback :
void attachPluginSuccess()
插件连接成功会回调此方法
-
IJGatewayCallback extends IJCallback : 顾名思义,这个回调主要跟网络有关系,穿透服务器也在这里处理
void onSuccess(); void onDestroy(); String getServerUri(); List
getIceServers(); Boolean getIpv6Support(); Integer getMaxPollEvents();
-
IJPluginCallback extends IJCallback 跟plugin的交互有关,实现接口的类是重点需要修改的部分
void onSuccess(JPluginHandle handle); void onMessage(JSONObject msg, JSONObject jsep); void onLocalStream(MediaStream stream); void onRemoteStream(MediaStream stream); void onDataOpen(Object data); void onData(Object data); void onCleanUp(); void onDetached(); JPluginPackage getPlugin();
-
IJPluginSendMessageCallback extends IJCallback 发送消息后的回调
void onSuccessSync(JSONObject obj); void onSuccessAsync(); JSONObject getMessage();
-
IJPluginWebRTCCallback extends IJCallback WebRTC相关回调
void onSuccess(JSONObject object); JSONObject getJsep(); JMediaConstraints getMedia(); Boolean getTrickle();
-
IJSessionCreationCallback extends IJCallback Session建立的回调
void onSessionCreate(JSONObject obj);
-
IJTransactionCallback 事务回调
void onReportSuccess(JSONObject obj); TransactionType getTransactionType();
-
IJMessenger 发送消息相关的接口
void connect(); void disconnect(); void sendMessage(String msg); void sendMessage(String msg, BigInteger sessionId); void sendMessage(String msg, BigInteger sessionId, BigInteger handleId); void onReceiveMessage(String msg); JMessengerType getMessengerType();
-
IJMessageObserver 顾名思义
void onReceive(JSONObject obj); void onClose(); void onOpen(); void onError(Exception e);
Class
JMessengerType(enum) 消息接口类型(websocket/restful)
-
JCreateSessionTransaction implements IJTransactionCallback
创建Session的事务
在onReportSuccess回调方法中调用到Jserver中的onSessionCreate。
Jserver中的onSessionCreate方法:开启keep_alive线程,回调IJGatewayCallback的onSuccess(在具体的业务类中实现该接口)
-
JAttachPluginTransaction implements IJTransactionCallback
顾名思义,连接Plugin的事务
JMessengerFactory 根据uri前缀返回JWebSocketMessenger
JMessageType(enum)消息的类型
JMediaConstraints 对媒体的一些限制
TransactionType(enum)事务的类型(“janus”后面的字段)
JPluginHandle 与插件连接之后,这个类接管大部分信息处理,包括所有的webrtc的连接建立过程
JPluginPackage
-
JPluginWebRtcCallback implements IJPluginWebRTCCallback
有关webrtc的一些处理,jsep,与handler有关
-
JPSendMessageCallback implements IJPluginSendMessageCallback
持有JSONObject message
-
JSendPluginMsgTransaction implements IJTransactionCallback
发送插件消息的事务,在onReportSuccess回调方法中回调IJPluginSendMessageCallback的相应方法
-
JServer implements Runnable, IJMessageObserver, IJSessionCreationCallback, IJAttachPluginCallback
处理各消息的主要类,实现多个接口。
JTransactionCallbackFactory 事务工厂
-
JWebRtcTransaction implements IJTransactionCallback
webrtc事务
-
JWebSocketMessenger implements IJMessenger
websocket连接中的重要角色,充当送信者
源码流程解析
启动app,进入后点击audio,接下来从源码来看完整的过程:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_audio_room);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
//在这里调用MyInit().run()
new MyInit().run();
//...
}
看一下MyInit类,这是一个内部类,实现了Runnable接口
private class MyInit implements Runnable {
public void run() {
init();
}
//在init()方法中,对audioRoomTest进行初始化
private void init() {
try {
EGLContext con = VideoRendererGui.getEGLContext();
audioRoomTest = new AudioRoomTest();
audioRoomTest.initializeMediaContext(AudioRoomActivity.this, true, true, true, con);
audioRoomTest.Start();
} catch (Exception ex) {
Log.e("computician.janusclient", ex.getMessage());
}
}
}
进入AudioRoomTest类中看看相关方法
//构造方法中初始化JServer对象janusServer,传入JanusGlobalCallbacks(处理ICE服务器相关)
public AudioRoomTest() {
janusServer = new JServer(new JanusGlobalCallbacks());
}
继续往下看JServer的构造函数
public JServer(IJGatewayCallback gatewayCallback) {
gatewayObserver = gatewayCallback;
java.lang.System.setProperty("java.net.preferIPv6Addresses", "false");
java.lang.System.setProperty("java.net.preferIPv4Stack", "true");
serverUri = gatewayObserver.getServerUri();
iceServers = gatewayObserver.getIceServers();
ipv6Support = gatewayObserver.getIpv6Support();
maxPollEvents = gatewayObserver.getMaxPollEvents();
connected = false;
sessionId = new BigInteger("-1");//未连接,sessionId缺省
serverConnection = JMessengerFactory.createMessager(serverUri, this);//websocket的messenger
}
可看到,在构造函数中,通过传入的JanusGlobalCallbacks拿到了一些配置,并初始化了一些对象。
因此可知通过初始化audioRoomTest,实际上对JServer进行了初始配置。
接下来是initializeMediaContext方法
public boolean initializeMediaContext(Context context, boolean audio, boolean video, boolean videoHwAcceleration, EGLContext eglContext){
return janusServer.initializeMediaContext(context, audio, video, videoHwAcceleration, eglContext);
}
//直接看一下JServer类中的同名方法
public boolean initializeMediaContext(Context context, boolean audio, boolean video, boolean videoHwAcceleration, EGLContext eglContext) {
//这是一个native方法,应该也是对android的媒体进行一些初始化
if (!PeerConnectionFactory.initializeAndroidGlobals(context, audio, video, videoHwAcceleration, eglContext))
return false;
//初始化后赋值为true
peerConnectionFactoryInitialized = true;
return true;
}
这一步应该主要是对手机端的媒体设备进行一些初始化。
最后,调用Start()方法,正式开始网络通信
public void Start() {
janusServer.Connect();
}
//JServer中又转交给了JWebSocketMessenger
public void Connect() {
serverConnection.connect();
}
JWebSocketMessenger中的connect()方法较长,实际上就是用AsyncHttpClient库来实现了websocket连接。
我这里省略了大量代码。
@Override
public void connect() {
AsyncHttpClient.getDefaultInstance().websocket(uri, "janus-protocol", new AsyncHttpClient.WebSocketConnectCallback() {
@Override
public void onCompleted(Exception ex, WebSocket webSocket) {
if (ex != null) {
handler.onError(ex);
}
//...
//前后代码忽略,这里是收到消息的回调,后面再看
client.setStringCallback(new WebSocket.StringCallback() {
@Override
public void onStringAvailable(String s) {
onMessage(s);
}
});
//...
//上面的code可以先忽略,这里的重点是这一行
handler.onOpen();
}
});
}
这里的handler的类型是IJMessageObserver,而只有JServer实现了该接口(之前在JServer的构造函数中也正是传入了this)。因此,又绕了一圈回到了JServer类
@Override
public void onOpen() {
createSession();
}
//这一步正式开始createSession
private void createSession() {
try {
JSONObject obj = new JSONObject();
obj.put("janus", JMessageType.create);
IJTransactionCallback cb = JTransactionCallbackFactory.createNewTransactionCallback(this, TransactionType.create);
String transaction = putNewTransaction(cb);//此方法会生成随机随机字符串,并与cb一起放入HashMap中存储
obj.put("transaction", transaction);
//通过JWebSocketMessenger发送消息
serverConnection.sendMessage(obj.toString());
} catch (JSONException ex) {
onError(ex.getMessage());
}
}
这里给出request的结构
{
"janus" : "create",
"transaction" : ""
}
JWebSocketMessenger中又进行了一层封装,直接交给了Websocket对象进行消息发送
@Override
public void sendMessage(String msg) {
Log.d("JANUSCLIENT", "Sent: \n\t" + msg);
client.send(msg);
}
到这里createSession的客户端发送告一段落。
当收到服务器端应答之后,会进入在JWebSocketMessenger中的connect()方法设置过client的回调。
client.setStringCallback(new WebSocket.StringCallback() {
@Override
public void onStringAvailable(String s) {
onMessage(s);
}
});
//一步步往下看
private void onMessage(String message) {
Log.d("JANUSCLIENT", "Recv: \n\t" + message);
onReceiveMessage(message);
}
@Override
public void onReceiveMessage(String msg) {
try {
//将收到的消息转交给JServer
JSONObject obj = new JSONObject(msg);
handler.onReceive(obj);
} catch (Exception ex) {
handler.onError(ex);
}
}
接下来看JServer对消息会有哪些处理。
这段代码也较长,会根据收到的message的不同进行不同处理,首先给出各type的含义
- keepalive,心跳消息。
- ack,确认消息。也就是说之前客户端发送了一个信令给服务端,服务端收到之后给客户端回了一个
ack
,确认服务端已经收到该消息了。- success,消息处理成功。该消息与 ack 消息是类似的,当服务器完成了客户端的命令后会返回该消息。
- trickle,收集
候选者
用的消息。里边存放着 candidate,janus.js收到该消息后,需要将Candidate解析出来。- webrtcup,表示一个peer上线了,此时要找到以应的业务插件(plugin)做业务处理。
- hangup,用户挂断,找到对应的plugin,进行挂断操作。
- detached,某个插件要求与Janus Core之间断开连接。
- media,开始或停信媒体流。
- slowlink,限流?
- error,错误消息
- event,插件发磅的事件消息。
- timeout,超时。
这一步会收到的消息示例
{
"janus": "success",
"transaction": "USukLEbyw9vy",
"data": {
"id": 5097149942368993
}
}
接下来再看下JServer源码这一部分
@Override
public void onReceive(JSONObject obj) {
try {
JMessageType type = JMessageType.fromString(obj.getString("janus"));
String transaction = null;
BigInteger sender = null;
if (obj.has("transaction")) {
//拿到transaction
transaction = obj.getString("transaction");
}
if (obj.has("sender")) {
sender = new BigInteger(obj.getString("sender"));
}
JPluginHandle handle = null;
if (sender != null) {
synchronized (attachedPluginLock) {
handle = attachedPlugins.get(sender);
}
}
//根据type的不同做不同处理
switch (type) {
case keepalive:
break;
case ack:
case success:
case error: {
//ack/success/error都会走此分支,从HashMap中移除键值对
//success 拿到transaction对应的IJTransactionCallback,并回调onReportSuccess方法
if (transaction != null) {
IJTransactionCallback cb = null;
synchronized (transactionsLock) {
cb = transactions.get(transaction);
if (cb != null)
transactions.remove(transaction);
}
if (cb != null) {
cb.onReportSuccess(obj);
transactions.remove(transaction);
}
}
break;
}
case hangup: {
//挂断处理,转交给JPluginHandle
if(handle != null) {
handle.hangUp();
}
break;
}
case detached: {
if (handle != null) {
handle.onDetached();
handle.detach();
}
break;
}
//之后的plugin消息一般走这个分支
case event: {
if (handle != null) {
JSONObject plugin_data = null;
if (obj.has("plugindata"))
plugin_data = obj.getJSONObject("plugindata");
if (plugin_data != null) {
JSONObject data = null;
JSONObject jsep = null;
if (plugin_data.has("data"))
data = plugin_data.getJSONObject("data");
if (obj.has("jsep"))
jsep = obj.getJSONObject("jsep");
handle.onMessage(data, jsep);
}
}
}
}
} catch (JSONException ex) {
gatewayObserver.onError(ex.getMessage());
}
}
可以看到,在这个方法中,除了ack/success/error的分支会转交给对应的IJTransactionCallback,之后建立连接后都转交给JPluginHandle进行处理。
接下来看下此处对应的IJTransactionCallback的实现JCreateSessionTransaction
@Override
public void onReportSuccess(JSONObject obj) {
try {
JMessageType type = JMessageType.fromString(obj.getString("janus"));
if (type != JMessageType.success) {
callback.onError(obj.getJSONObject("error").getString("reason"));
} else {
//这里的callback是JServer
callback.onSessionCreate((obj));
}
} catch (JSONException ex) {
callback.onError(ex.getMessage());
}
}
JServer的onSessionCreate方法
@Override
public void onSessionCreate(JSONObject obj) {
try {
//拿到sessionID
sessionId = new BigInteger(obj.getJSONObject("data").getString("id"));
//开启新线程发送keep_alive消息(如果60s内未收到消息服务器就会断开
keep_alive = new Thread(this, "KeepAlive");
keep_alive.start();
//连接建立
connected = true;
//TODO do we want to keep track of multiple sessions and servers?
//回调客户端方法
gatewayObserver.onSuccess();
} catch (JSONException ex) {
gatewayObserver.onError(ex.getMessage());
}
}
客户端转交给JServer
public void onSuccess() {
janusServer.Attach(new JanusPublisherPluginCallbacks());
}
JServer的Attach方法
public void Attach(IJPluginCallback callbacks) {
if (!peerConnectionFactoryInitialized) {
callbacks.onError("Peerconnection factory is not initialized, please initialize via initializeMediaContext so that peerconnections can be made by the plugins");
return;
}
//AsyncAttach是一个AsyncTask,开启了后台线程,在doInBackground方法中处理耗时操作
new AsyncAttach().execute(callbacks);
}
AsyncAttach中的具体实现
private class AsyncAttach extends AsyncTask {
@Override
protected Void doInBackground(IJPluginCallback... cbs) {
IJPluginCallback cb = cbs[0];
try {
//和之前发送消息类似的操作
JSONObject obj = new JSONObject();
obj.put("janus", JMessageType.attach);
obj.put("plugin", cb.getPlugin());
if (serverConnection.getMessengerType() == JMessengerType.websocket) {
obj.put("session_id", sessionId);
}
IJTransactionCallback tcb = JTransactionCallbackFactory.createNewTransactionCallback(JServer.this, TransactionType.attach, cb.getPlugin(), cb);
String transaction = putNewTransaction(tcb);
obj.put("transaction", transaction);
serverConnection.sendMessage(obj.toString(), sessionId);
} catch (JSONException ex) {
onError(ex.getMessage());
}
return null;
}
}
对应request示例
{"janus":"attach",
"plugin":"janus.plugin.audiobridge",
"session_id":5097149942368993,
"transaction":"w9QEHOwzqOCU"}
之后的发送都走的相同的流程,不再赘述。
收到服务器应答之后
对应message示例
{
"janus": "success",
"session_id": 5097149942368993,
"transaction": "w9QEHOwzqOCU",
"data": {
"id": 3946752844339959
}
}
由于这一步主要是Attach Plugin,收到消息后,对应JAttachPluginTransaction中的回调,最终依然转交给JServer
@Override
public void attachPluginSuccess(JSONObject obj, JPluginPackage plugin, IJPluginCallback callback) {
try {
//为Plugin绑定对应的JPluginHandle
BigInteger handle = new BigInteger(obj.getJSONObject("data").getString("id"));
JPluginHandle pluginHandle = new JPluginHandle(this, plugin, handle, callback);
synchronized (attachedPluginLock) {
attachedPlugins.put(handle, pluginHandle);
}
//callback即客户端的JanusPublisherPluginCallbacks
callback.onSuccess(pluginHandle);
} catch (JSONException ex) {
//or do we want to use the pluginCallbacks.error(ex.getMessage());
gatewayObserver.onError(ex.getMessage());
}
}
在JanusPublisherPluginCallbacks的onSuccess方法中
@Override
public void onSuccess(JPluginHandle pluginHandle) {
handle = pluginHandle;
registerUsername();
}
registerUsername方法实际就是加入房间,接下来都交给JPluginHandle来进行处理
private void registerUsername() {
if(handle != null) {
JSONObject obj = new JSONObject();
JSONObject msg = new JSONObject();
try
{
obj.put(REQUEST, "join");
obj.put("room", roomid);
obj.put("muted", false);
msg.put(MESSAGE, obj);
}
catch(Exception ex)
{
}
handle.sendMessage(new JPSendMessageCallback(msg));
}
}
JPluginHandle继续转交给JServer
public void sendMessage(TransactionType type, BigInteger handle, IJPluginSendMessageCallback callbacks, JPluginPackage plugin) {
JSONObject msg = callbacks.getMessage();
if (msg != null) {
try {
JSONObject newMessage = new JSONObject();
newMessage.put("janus", JMessageType.message.toString());
if (serverConnection.getMessengerType() == JMessengerType.websocket) {
newMessage.put("session_id", sessionId);
newMessage.put("handle_id", handle);
}
IJTransactionCallback cb = JTransactionCallbackFactory.createNewTransactionCallback(this, TransactionType.plugin_handle_message, plugin, callbacks);
String transaction = putNewTransaction(cb);
newMessage.put("transaction", transaction);
if (msg.has("message")) {
newMessage.put("body", msg.getJSONObject("message"));
}
if (msg.has("jsep")) {
newMessage.put("jsep", msg.getJSONObject("jsep"));
}
serverConnection.sendMessage(newMessage.toString(), sessionId, handle);
} catch (JSONException ex) {
callbacks.onError(ex.getMessage());
}
}
}
request示例
{"janus":"message",
"session_id":5097149942368993,
"handle_id":3946752844339959,
"transaction":"hgJiofqaZKzS",
"body":{"request":"join","room":1234,"muted":false}}
接收message示例
{
"janus": "event",
"session_id": 5097149942368993,
"transaction": "hgJiofqaZKzS",
"sender": 3946752844339959,
"plugindata": {
"plugin": "janus.plugin.audiobridge",
"data": {
"audiobridge": "joined",
"room": 1234,
"id": 5264697718312922,
"participants": []
}
}
}
看一下event分支
case event: {
if (handle != null) {
JSONObject plugin_data = null;
if (obj.has("plugindata"))
plugin_data = obj.getJSONObject("plugindata");
if (plugin_data != null) {
JSONObject data = null;
JSONObject jsep = null;
if (plugin_data.has("data"))
data = plugin_data.getJSONObject("data");
if (obj.has("jsep"))
jsep = obj.getJSONObject("jsep");
handle.onMessage(data, jsep);
}
}
}
注:JSEP(JavaScript Session Establishment Protocol,JavaScript会话建立协议),是一个信令控制协议。
//JPluginHandle中的callback为之前传入的客户端的JanusPublisherPluginCallbacks
public void onMessage(JSONObject msg, JSONObject jsep) {
callbacks.onMessage(msg, jsep);
}
JanusPublisherPluginCallbacks类的onMessage方法
@Override
public void onMessage(JSONObject msg, JSONObject jsepLocal) {
try
{
String event = msg.getString("audiobridge");
if(event.equals("joined")) {
//由于这一段都参考的VideoRoomTest,有一些冗余代码未删除,实际只会调用到这一行
publishOwnFeed();
} else if(event.equals("destroyed")) {
} else if(event.equals("event")) {
if(msg.has(PUBLISHERS)){
//...
} else if(msg.has("leaving")) {
} else if(msg.has("unpublished")) {
} else {
//todo error
}
}
if(jsepLocal != null) {
handle.handleRemoteJsep(new JPluginWebRtcCallback(null, jsepLocal, false));
}
}
catch (Exception ex)
{
}
}
这一步实际上是join房间后再configure,正式开始通话
private void publishOwnFeed() {
if(handle != null) {
handle.createOffer(new IJPluginWebRTCCallback() {
@Override
public void onSuccess(JSONObject obj) {
try
{
//webrtc createOffer成功后发送configure
JSONObject msg = new JSONObject();
JSONObject body = new JSONObject();
body.put(REQUEST, "configure");
body.put("muted", false);
body.put("display", "android");
body.put("record", false);
msg.put(MESSAGE, body);
msg.put("jsep", obj);
handle.sendMessage(new JPSendMessageCallback(msg));
}catch (Exception ex) {
}
}
@Override
public JSONObject getJsep() {
return null;
}
//此处设置只发送音频
@Override
public JMediaConstraints getMedia() {
JMediaConstraints cons = new JMediaConstraints();
cons.setRecvAudio(true);
cons.setRecvVideo(false);
cons.setSendAudio(true);
cons.setSendVideo(false);
return cons;
}
@Override
public Boolean getTrickle() {
return true;
}
@Override
public void onError(String error) {
}
});
}
}
接下来看下webrtc部分,首先是JPluginHandle
public void createOffer(IJPluginWebRTCCallback webrtcCallbacks) {
new AsyncPrepareWebRtc().execute(webrtcCallbacks);
}
AsyncPrepareWebRtc也是一个AsyncTask
private class AsyncPrepareWebRtc extends AsyncTask {
@Override
protected Void doInBackground(IJPluginWebRTCCallback... params) {
IJPluginWebRTCCallback cb = params[0];
//准备webrtc
prepareWebRtc(cb);
return null;
}
}
private void prepareWebRtc(IJPluginWebRTCCallback callbacks) {
//第一次调用时pc为null,进入else分支
if (pc != null) {
if (callbacks.getJsep() == null) {
//为null
createSdpInternal(callbacks, true);
} else {
try {
JSONObject jsep = callbacks.getJsep();
String sdpString = jsep.getString("sdp");
SessionDescription.Type type = SessionDescription.Type.fromCanonicalForm(jsep.getString("type"));
SessionDescription sdp = new SessionDescription(type, sdpString);
pc.setRemoteDescription(new WebRtcObserver(callbacks), sdp);
} catch (JSONException ex) {
ex.printStackTrace();
}
}
} else {
//根据客户端那边的MediaConstraints进行配置
trickle = callbacks.getTrickle() != null ? callbacks.getTrickle() : false;
AudioTrack audioTrack = null;
VideoTrack videoTrack = null;
MediaStream stream = null;
if (callbacks.getMedia().getSendAudio()) {
AudioSource source = sessionFactory.createAudioSource(new MediaConstraints());
audioTrack = sessionFactory.createAudioTrack(AUDIO_TRACK_ID, source);
}
//不发送video 直接跳过
if (callbacks.getMedia().getSendVideo()) {
VideoCapturerAndroid capturer = null;
switch (callbacks.getMedia().getCamera()) {
case back:
capturer = VideoCapturerAndroid.create(VideoCapturerAndroid.getNameOfBackFacingDevice());
break;
case front:
capturer = VideoCapturerAndroid.create(VideoCapturerAndroid.getNameOfFrontFacingDevice());
break;
}
MediaConstraints constraints = new MediaConstraints();
JMediaConstraints.JVideo videoConstraints = callbacks.getMedia().getVideo();
VideoSource source = sessionFactory.createVideoSource(capturer, constraints);
videoTrack = sessionFactory.createVideoTrack(VIDEO_TRACK_ID, source);
}
//创建媒体流
if (audioTrack != null || videoTrack != null) {
stream = sessionFactory.createLocalMediaStream(LOCAL_MEDIA_ID);
if (audioTrack != null)
stream.addTrack(audioTrack);
if (videoTrack != null)
stream.addTrack(videoTrack);
}
localStream = stream;
if (stream != null)
//这一步主要是加本地流(视频通话),语音通话同样不需要
onLocalStream(stream);
//streamsDone方法中初始化PeerConnection
streamsDone(callbacks);
}
}
private void streamsDone(IJPluginWebRTCCallback webRTCCallbacks) {
MediaConstraints pc_cons = new MediaConstraints();
//一些约束配置
pc_cons.optional.add(new MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true"));
if (webRTCCallbacks.getMedia().getRecvAudio()) {
pc_cons.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveAudio", "true"));
}
if (webRTCCallbacks.getMedia().getRecvVideo()) {
pc_cons.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveVideo", "true"));
}
pc = sessionFactory.createPeerConnection(server.iceServers, pc_cons, new WebRtcObserver(webRTCCallbacks));
if (localStream != null)
//加入本地流
pc.addStream(localStream);
if (webRTCCallbacks.getJsep() == null) {
// 媒体协商
createSdpInternal(webRTCCallbacks, true);
} else {
try {
JSONObject obj = webRTCCallbacks.getJsep();
String sdp = obj.getString("sdp");
SessionDescription.Type type = SessionDescription.Type.fromCanonicalForm(obj.getString("type"));
SessionDescription sessionDescription = new SessionDescription(type, sdp);
pc.setRemoteDescription(new WebRtcObserver(webRTCCallbacks), sessionDescription);
} catch (Exception ex) {
webRTCCallbacks.onError(ex.getMessage());
}
}
}
createSdpInternal方法创建sdp(webrtc中媒体协商采用了SDP协议)
注:SDP(Session Description Protocol)是一种通用的会话描述协议,主要用来描述多媒体会话,用途包括会话声明、会话邀请、会话初始化等。
WebRTC主要在连接建立阶段用到SDP,连接双方通过信令服务交换会话信息,包括音视频编解码器(codec)、主机候选地址、网络传输协议等。
private void createSdpInternal(IJPluginWebRTCCallback callbacks, Boolean isOffer) {
MediaConstraints pc_cons = new MediaConstraints();
pc_cons.optional.add(new MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true"));
if (callbacks.getMedia().getRecvAudio()) {
pc_cons.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveAudio", "true"));
}
//这里不接收video
if (callbacks.getMedia().getRecvVideo()) {
Log.d("VIDEO_ROOM", "Receiving video");
}
if (isOffer) {
//这里是协商阶段,调用本地方法进行协商
pc.createOffer(new WebRtcObserver(callbacks), pc_cons);
} else {
pc.createAnswer(new WebRtcObserver(callbacks), pc_cons);
}
}
SDP创建成功后,会回调IJPluginHandle的内部类WebRtcObserver的方法
@Override
public void onCreateSuccess(SessionDescription sdp) {
Log.d("JANUSCLIENT", "Create success");
onLocalSdp(sdp, webRtcCallback);
}
private void onLocalSdp(SessionDescription sdp, IJPluginWebRTCCallback callbacks) {
if (pc == null) {
return;
}
if (localSdp == null) {
localSdp = sdp;
pc.setLocalDescription(new WebRtcObserver(callbacks), sdp);
}
if (!iceDone && !trickle) {
return;
}
if (sdpSent) {
return;
}
try {
sdpSent = true;
JSONObject obj = new JSONObject();
obj.put("sdp", localSdp.description);
obj.put("type", localSdp.type.canonicalForm());
//最终回调onSuccess方法回到客户端
callbacks.onSuccess(obj);
} catch (JSONException ex) {
callbacks.onError(ex.getMessage());
}
}
最终通过websocket发送configure消息,建立了webrtc连接。