Xmpp作為WebRTC信令的音視頻通話01-SessionDescription和IceCandidate的交換

xmpp作為信令主要用于客戶端之間進行sdp和ice候選的交換赛惩,基于smack4.1.4隘世。

1均芽、自定義消息格式

1.1云头、SessionDescription消息格式

根據(jù)org.webrtc.SessionDescription的內(nèi)容,自定義需要發(fā)送的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("</"+typeElement+">");
        sb.append("<" + descriptionElement + ">").append(descriptionText).append("</"+descriptionElement+">");
        sb.append("</"+ELEMENT_NAME+">");

        return sb.toString();
    }
}

1.2量愧、IceCandidate消息格式

根據(jù)org.webrtc.IceCandidate的內(nèi)容钾菊,自定義需要發(fā)送的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("</"+sdpMidElement+">");
        sb.append("<" + sdpMLineIndexElement + ">").append(sdpMLineIndexText).append("</"+sdpMLineIndexElement+">");
        sb.append("<" + sdpElement + ">").append(sdpText).append("</"+sdpElement+">");
        sb.append("</"+ELEMENT_NAME+">");

        return sb.toString();
    }
}

2、消息發(fā)送

2.1偎肃、消息發(fā)送公共方法

首先在XmppUtils工具類中寫一個發(fā)送message的靜態(tài)方法

/**
 *發(fā)送Message消息
 * @param mXMPPConnection    與服務(wù)器連接類
 * @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, "發(fā)送成功 === " + message.getBody());
    }
}

2.2、發(fā)送SessionDescription消息

/**
 * 發(fā)送SessionDescription消息
 * @param remotePeerName     接收方累颂,對于呼叫方來說就是接聽方滞详,對于接聽方來說就是呼叫方
 * @param sdp     客戶端創(chuàng)建的用于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、發(fā)送IceCandidate消息

/**
 * 發(fā)送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監(jiān)聽器

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監(jiān)聽器

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")) {//接聽方 接收到 呼叫方 發(fā)的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保存發(fā)送方的sdp
                    CallUtils.getInst().setSdpMap(remotePeerName,sessionDescription);
                    //設(shè)為接聽方
                    CallUtils.getInst().setCallOutModel(false);
                    //peerConnectionClient保存發(fā)送方的sdp朱监,此處調(diào)用PeerConnection.setRemoteDescription
                    CallUtils.getInst().onRemoteDescription(remotePeerName,sessionDescription);
                } else if (type.equals("answer")){//呼叫方 接收到 接聽方 發(fā)的answer sdp
                    Log.d(TAG,"=============type ===answer============");
                    if (CallUtils.getInst().isCallOutModel()) {
                        SessionDescription sessionDescription = new SessionDescription(SessionDescription.Type.fromCanonicalForm(type),description);
                        //設(shè)為呼叫方
                        CallUtils.getInst().setCallOutModel(true);
                        //peerConnectionClient保存接聽方的sdp,此處調(diào)用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就保存起來岸啡,此處調(diào)用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、實現(xiàn)PeerConnectionEvents

客戶端之間發(fā)送和接收SessionDescription 赫编、IceCandidate已經(jīng)實現(xiàn)了巡蘸,那么什么情況下用他們呢奋隶?

首先,客戶端通過createOffer或者createAnswer方法創(chuàng)建sdp后悦荒,會向?qū)Ψ桨l(fā)送sdp唯欣,這時調(diào)用private void sendSdp(final String remotePeerName, final SessionDescription sdp);

其次搬味,客戶端收集到IceCandidate后境氢,會向?qū)Ψ桨l(fā)送IceCandidate,這時調(diào)用private void sendIceCandidate(String remotePeerName, IceCandidate candidate)碰纬;

這些先不管产还,先實現(xiàn)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接口實現(xiàn)

PeerConnectionEvents events = new PeerConnectionEvents() {
    @Override
    public void onLocalDescription(final SessionDescription sdp) {//當本地sdp創(chuàng)建成功并保存到本地后調(diào)用
        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);//呼叫方發(fā)送用于offer的sdp
                } else {
                    if (isHangUp()) return;
                    sendSdp(remotePeerName,sdp);//接聽方發(fā)送用于answer的sdp
                }
            }
        });
    }

    @Override
    public void onIceCandidate(final IceCandidate candidate) {//當本地收集到IceCandidate時調(diào)用
        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發(fā)送給接收端
            }
        });
    }

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

    @Override
    public void onIceConnected() {//當呼叫方接收到接聽方的sdp和IceCandidate后調(diào)用
        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);
            }
        });
    }
};

總結(jié)

??至此客戶端之間已經(jīng)可以進行SessionDescription和IceCandidate的交換,雙方客戶端接收到對方的SessionDescription和IceCandidate后要進行的保存等操作以后再說她按!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末牛隅,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子酌泰,更是在濱河造成了極大的恐慌媒佣,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件陵刹,死亡現(xiàn)場離奇詭異默伍,居然都是意外死亡,警方通過查閱死者的電腦和手機衰琐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進店門也糊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人羡宙,你說我怎么就攤上這事狸剃。” “怎么了狗热?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵钞馁,是天一觀的道長。 經(jīng)常有香客問我匿刮,道長僧凰,這世上最難降的妖魔是什么熟丸? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任训措,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘隙弛。我一直安慰自己架馋,他們只是感情好狞山,可當我...
    茶點故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布全闷。 她就那樣靜靜地躺著,像睡著了一般萍启。 火紅的嫁衣襯著肌膚如雪总珠。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天勘纯,我揣著相機與錄音局服,去河邊找鬼。 笑死驳遵,一個胖子當著我的面吹牛淫奔,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播堤结,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼唆迁,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了竞穷?” 一聲冷哼從身側(cè)響起唐责,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎瘾带,沒想到半個月后鼠哥,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡看政,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年朴恳,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片允蚣。...
    茶點故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡菜皂,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出厉萝,到底是詐尸還是另有隱情恍飘,我是刑警寧澤,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布谴垫,位于F島的核電站章母,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏翩剪。R本人自食惡果不足惜乳怎,卻給世界環(huán)境...
    茶點故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望前弯。 院中可真熱鬧蚪缀,春花似錦秫逝、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至金蜀,卻和暖如春刷后,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背渊抄。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工尝胆, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人护桦。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓含衔,卻偏偏與公主長得像,于是被迫代替她去往敵國和親二庵。 傳聞我的和親對象是個殘疾皇子贪染,可洞房花燭夜當晚...
    茶點故事閱讀 44,700評論 2 354