janus-gateway-android的web相关模块源码流程

最近在做一个webrtc相关的项目,有接触到Janus,参考了github上的一个android demo:https://github.com/benwtrent/janus-gateway-android

demo中有一个module叫janusagent,通过websoket的方式与janus服务器进行通信,接下来简单讲一下代码的逻辑。

Module Janusagent

首先说一下与janusagent进行连接的顺序:

  1. createSession
  2. 建立websocket连接
  3. 创建transaction, 将request发给服务器
  4. 根据消息类型做不同的处理

Interface

  • IJCallback : void onError() 回调父接口

  • IJAttachPluginCallback extends IJCallback :

    void attachPluginSuccess()
    

    插件连接成功会回调此方法

  • IJGatewayCallback extends IJCallback : 顾名思义,这个回调主要跟网络有关系,穿透服务器也在这里处理

     void onSuccess(); 
     void onDestroy();
     String getServerUri();
     List<PeerConnection.IceServer> 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" : "<random alphanumeric string>"
}

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<IJPluginCallback, Void, Void> {

    @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<IJPluginWebRTCCallback, Void, Void> {
    @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连接。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,670评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,928评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,926评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,238评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,112评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,138评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,545评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,232评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,496评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,596评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,369评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,226评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,600评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,906评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,185评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,516评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,721评论 2 335