Xmpp作为WebRTC信令的音视频通话01-SessionDescription和IceCandidate的交换

xmpp作为信令主要用于客户端之间进行sdp和ice候选的交换,基于smack4.1.4。

1、自定义消息格式

1.1、SessionDescription消息格式

根据org.webrtc.SessionDescription的内容,自定义需要发送的SessionDescription消息格式

package org.webrtc;

public SessionDescription(SessionDescription.Type type, String description) {
    this.type = type;
    this.description = description;
}

自定义的SessionDescription消息格式:

public class SDPExtensionElement implements ExtensionElement {
    public static final String NAME_SPACE = "com.webrtc.sdp";
    public static final String ELEMENT_NAME = "sdp";

    //代表SessionDescription.Type.type
    private String typeElement = "type";
    private String typeText = "";

    //代表 SessionDescription.description
    private String descriptionElement = "description";
    private String descriptionText = "";

    public void setDescriptionText(String descriptionText) {
        this.descriptionText = descriptionText;
    }

    public void setTypeText(String typeText) {
        this.typeText = typeText;
    }

    @Override
    public String getNamespace() {
        return NAME_SPACE;
    }

    @Override
    public String getElementName() {
        return ELEMENT_NAME;
    }

    @Override
    public CharSequence toXML() {
        StringBuilder sb = new StringBuilder();

        sb.append("<").append(ELEMENT_NAME).append(" xmlns=\"").append(NAME_SPACE).append("\">");
        sb.append("<" + typeElement + ">").append(typeText).append("");
        sb.append("<" + descriptionElement + ">").append(descriptionText).append("");
        sb.append("");

        return sb.toString();
    }
}

1.2、IceCandidate消息格式

根据org.webrtc.IceCandidate的内容,自定义需要发送的IceCandidate消息格式

package org.webrtc;

public IceCandidate(String sdpMid, int sdpMLineIndex, String sdp) {
      this.sdpMid = sdpMid;
      this.sdpMLineIndex = sdpMLineIndex;
      this.sdp = sdp;
      this.serverUrl = "";
}

自定义的IceCandidate消息格式:

public class IceCandidateExtensionElement implements ExtensionElement {
    public static final String NAME_SPACE = "com.webrtc.ice_candidate";
    public static final String ELEMENT_NAME = "ice_candidate";

    //代表IceCandidate.sdpMid;
    private String sdpMidElement = "sdpMid";
    private String sdpMidText = "";

    //代表IceCandidate.sdpMLineIndex;
    private String sdpMLineIndexElement = "sdpMLineIndex";
    private int sdpMLineIndexText = 0;

    //代表IceCandidate.sdp;
    private String sdpElement = "sdp";
    private String sdpText = "";


    public void setSdpMidText(String sdpMidText) {
        this.sdpMidText = sdpMidText;
    }


    public void setSdpMLineIndexText(int sdpMLineIndexText) {
        this.sdpMLineIndexText = sdpMLineIndexText;
    }

    public void setSdpText(String sdpText) {
        this.sdpText = sdpText;
    }

    @Override
    public String getNamespace() {
        return NAME_SPACE;
    }

    @Override
    public String getElementName() {
        return ELEMENT_NAME;
    }

    @Override
    public CharSequence toXML() {
        StringBuilder sb = new StringBuilder();

        sb.append("<").append(ELEMENT_NAME).append(" xmlns=\"").append(NAME_SPACE).append("\">");
        sb.append("<" + sdpMidElement + ">").append(sdpMidText).append("");
        sb.append("<" + sdpMLineIndexElement + ">").append(sdpMLineIndexText).append("");
        sb.append("<" + sdpElement + ">").append(sdpText).append("");
        sb.append("");

        return sb.toString();
    }
}

2、消息发送

2.1、消息发送公共方法

首先在XmppUtils工具类中写一个发送message的静态方法

/**
 *发送Message消息
 * @param mXMPPConnection    与服务器连接类
 * @param message    org.jivesoftware.smack.packet.Message,消息实体
 * @param touser     接收方
 */
public static void sendMessage(XMPPTCPConnection mXMPPConnection, Message message, String touser){
    ChatManager chatmanager = ChatManager.getInstanceFor(mXMPPConnection);
    Chat chat = chatmanager.createChat(touser,null);
    if (chat != null) {
        try {
            chat.sendMessage(message);
        } catch (SmackException.NotConnectedException e) {
            e.printStackTrace();
        }
        Log.e(TAG, "发送成功 === " + message.getBody());
    }
}

2.2、发送SessionDescription消息

/**
 * 发送SessionDescription消息
 * @param remotePeerName     接收方,对于呼叫方来说就是接听方,对于接听方来说就是呼叫方
 * @param sdp     客户端创建的用于offer或answer的SessionDescription
 */
private void sendSdp(final String remotePeerName, final SessionDescription sdp) {
    Message message = new Message();
    SDPExtensionElement sdpExtensionElement = new SDPExtensionElement();
    sdpExtensionElement.setTypeText(sdp.type.canonicalForm());
    sdpExtensionElement.setDescriptionText(sdp.description);
    message.addExtension(sdpExtensionElement);
    XmppUtils.sendMessage(MyApplication.xmpptcpConnection,message,remotePeerName);
}

2.3、发送IceCandidate消息

/**
 * 发送IceCandidate 
 * @param remotePeerName   接收方,对于呼叫方来说就是接听方,对于接听方来说就是呼
 * @param candidate    IceCandidate
 */
private void sendIceCandidate(String remotePeerName, IceCandidate candidate) {
    Message message = new Message();
    IceCandidateExtensionElement iceCandidateExtensionElement = new IceCandidateExtensionElement();
    iceCandidateExtensionElement.setSdpMidText(candidate.sdpMid);
    iceCandidateExtensionElement.setSdpMLineIndexText(candidate.sdpMLineIndex);
    iceCandidateExtensionElement.setSdpText(candidate.sdp);
    message.addExtension(iceCandidateExtensionElement);
    XmppUtils.sendMessage(MyApplication.xmpptcpConnection,message,remotePeerName);
}

3、消息接收

在MsfService中添加Message监听器

ChatManager chatManager = ChatManager.getInstanceFor(mXmpptcpConnection);
chatManager.addChatListener(new ChatManagerListener() {
    @Override
    public void chatCreated(Chat chat, boolean b) {
        chat.addMessageListener(new MsgListener(MsfService.this,mNotificationManager));
    }
});

Message监听器

public class MsgListener implements ChatMessageListener {
    private static final String TAG = "MsgListener";
    private MsfService context;

    public MsgListener(MsfService context){
        this.context = context;
    }
    
    @Override
    public void processMessage(Chat chat,Message message) {
        try {

            String remotePeerName = (message.getFrom()).split("/")[0];
            Log.d(TAG,"remotePeerName === " + remotePeerName);
            if (message.hasExtension(SDPExtensionElement.ELEMENT_NAME,SDPExtensionElement.NAME_SPACE)) {
            
                DefaultExtensionElement defaultExtensionElement =
                        message.getExtension(SDPExtensionElement.ELEMENT_NAME, SDPExtensionElement.NAME_SPACE);

                String type = defaultExtensionElement.getValue("type");
                String description = defaultExtensionElement.getValue("description");

                
                if (type.equals("offer")) {//接听方 接收到 呼叫方 发的offer sdp
                Log.d(TAG,"=============type ===offer============");
                    if (CallUtils.getInst().isHangUp()) {
                        CallUtils.getInst().setHangUp(false);
                    }
                    SessionDescription sessionDescription = new SessionDescription(SessionDescription.Type.fromCanonicalForm(type),description);
                    //Map保存发送方的sdp
                    CallUtils.getInst().setSdpMap(remotePeerName,sessionDescription);
                    //设为接听方
                    CallUtils.getInst().setCallOutModel(false);
                    //peerConnectionClient保存发送方的sdp,此处调用PeerConnection.setRemoteDescription
                    CallUtils.getInst().onRemoteDescription(remotePeerName,sessionDescription);
                } else if (type.equals("answer")){//呼叫方 接收到 接听方 发的answer sdp
                    Log.d(TAG,"=============type ===answer============");
                    if (CallUtils.getInst().isCallOutModel()) {
                        SessionDescription sessionDescription = new SessionDescription(SessionDescription.Type.fromCanonicalForm(type),description);
                        //设为呼叫方
                        CallUtils.getInst().setCallOutModel(true);
                        //peerConnectionClient保存接听方的sdp,此处调用PeerConnection.setRemoteDescription
                        CallUtils.getInst().onRemoteDescription(remotePeerName,sessionDescription);
                    }
                }
            } else if (message.hasExtension(IceCandidateExtensionElement.ELEMENT_NAME,IceCandidateExtensionElement.NAME_SPACE)) {
            
                DefaultExtensionElement defaultExtensionElement =
                        message.getExtension(IceCandidateExtensionElement.ELEMENT_NAME, IceCandidateExtensionElement.NAME_SPACE);
                        
                String sdpMid = defaultExtensionElement.getValue("sdpMid");
                int sdpMLineIndex = Integer.parseInt(defaultExtensionElement.getValue("sdpMLineIndex"));
                String sdp = defaultExtensionElement.getValue("sdp");

                IceCandidate iceCandidate = new IceCandidate(sdpMid,sdpMLineIndex,sdp);
                //接收到IceCandidate就保存起来,此处调用PeerConnection.addIceCandidate()方法
                CallUtils.getInst().onRemoteIceCandidate(remotePeerName,iceCandidate);
            } else if (message.hasExtension(VideoCallExtensionElement.ELEMENT_NAME,VideoCallExtensionElement.NAME_SPACE)) {
                DefaultExtensionElement defaultExtensionElement = message.getExtension(VideoCallExtensionElement.ELEMENT_NAME,
                        VideoCallExtensionElement.NAME_SPACE);
                String type = defaultExtensionElement.getValue("type");
                switch (type) {
                    case "video-end"://挂断命令
                         
                        break;
                        default:
                            break;
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

4、实现PeerConnectionEvents

客户端之间发送和接收SessionDescription 、IceCandidate已经实现了,那么什么情况下用他们呢?

首先,客户端通过createOffer或者createAnswer方法创建sdp后,会向对方发送sdp,这时调用private void sendSdp(final String remotePeerName, final SessionDescription sdp);

其次,客户端收集到IceCandidate后,会向对方发送IceCandidate,这时调用private void sendIceCandidate(String remotePeerName, IceCandidate candidate);

这些先不管,先实现PeerConnectionEvents接口。

4、1 PeerConnectionEvents接口

public interface PeerConnectionEvents {
    /**
     * Callback fired once local SDP is created and set.
     */
    void onLocalDescription(final SessionDescription sdp);

    /**
     * Callback fired once local Ice candidate is generated.
     */
    void onIceCandidate(final IceCandidate candidate);

    /**
     * Callback fired once local ICE candidates are removed.
     */
    void onIceCandidatesRemoved(final IceCandidate[] candidates);

    /**
     * Callback fired once connection is established (IceConnectionState is
     * CONNECTED).
     */
    void onIceConnected();

    /**
     * Callback fired once connection is closed (IceConnectionState is
     * DISCONNECTED).
     */
    void onIceDisconnected();

    /**
     * Callback fired once peer connection is closed.
     */
    void onPeerConnectionClosed();

    /**
     * Callback fired once peer connection statistics is ready.
     */
    void onPeerConnectionStatsReady(final StatsReport[] reports);

    /**
     * Callback fired once peer connection error happened.
     */
    void onPeerConnectionError(final String description);
}

4、2 PeerConnectionEvents接口实现

PeerConnectionEvents events = new PeerConnectionEvents() {
    @Override
    public void onLocalDescription(final SessionDescription sdp) {//当本地sdp创建成功并保存到本地后调用
        if (isHangUp()) return;

        final long delta = System.currentTimeMillis() - callStartedTimeMs;

        context.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                Log.d(TAG, "Sending " + sdp.type + ", delay=" + delta + "ms");
                if (isHangUp()) return;

                if (isCallOut) {
                    if (isHangUp()) {
                        Log.e(TAG,"挂断002");
                    }
                    localSdp = sdp;
                    sendSdp(remotePeerName,sdp);//呼叫方发送用于offer的sdp
                } else {
                    if (isHangUp()) return;
                    sendSdp(remotePeerName,sdp);//接听方发送用于answer的sdp
                }
            }
        });
    }

    @Override
    public void onIceCandidate(final IceCandidate candidate) {//当本地收集到IceCandidate时调用
        if (isHangUp()) return;
        context.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                if (localCandidateList == null) {
                    localCandidateList = new ArrayList<>();
                }
                localCandidateList.add(candidate);//将本地的IceCandidate保存起来
                sendIceCandidate(remotePeerName,candidate);//将本地的IceCandidate发送给接收端
            }
        });
    }

    @Override
    public void onIceCandidatesRemoved(IceCandidate[] candidates) {
        Log.d(TAG,"onIceCandidatesRemoved");
    }

    @Override
    public void onIceConnected() {//当呼叫方接收到接听方的sdp和IceCandidate后调用
        if (isHangUp()) return;
        final long delta = System.currentTimeMillis() - callStartedTimeMs;
        context.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                Log.e(TAG, "ICE connected, delay=" + delta + "ms");
                localCandidateList = null;
                localSdp = null;
                App.getInst().getVideoCallListener().onConnect(remotePeerName);//将对方的视频渲染到本地
                setConnected(true);
            }
        });
    }

    @Override
    public void onIceDisconnected() {
        if (isHangUp()) return;
        context.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                Log.d(TAG,"onIceDisconnected");
            }
        });
    }

    @Override
    public void onPeerConnectionClosed() {
        if (isHangUp()) return;
        Log.d(TAG,"onPeerConnectionClosed");
    }

    @Override
    public void onPeerConnectionStatsReady(StatsReport[] reports) {
        if (isHangUp()) return;
        Log.d(TAG,"onPeerConnectionStatsReady");
    }

    @Override
    public void onPeerConnectionError(final String description) {
        if (isHangUp()) return;
        context.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                Log.d(TAG,"onPeerConnectionError :" + description);
            }
        });
    }
};

总结

  至此客户端之间已经可以进行SessionDescription和IceCandidate的交换,双方客户端接收到对方的SessionDescription和IceCandidate后要进行的保存等操作以后再说!

你可能感兴趣的:(Xmpp作为WebRTC信令的音视频通话01-SessionDescription和IceCandidate的交换)