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(""+typeElement+">");
sb.append("<" + descriptionElement + ">").append(descriptionText).append(""+descriptionElement+">");
sb.append(""+ELEMENT_NAME+">");
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(""+sdpMidElement+">");
sb.append("<" + sdpMLineIndexElement + ">").append(sdpMLineIndexText).append(""+sdpMLineIndexElement+">");
sb.append("<" + sdpElement + ">").append(sdpText).append(""+sdpElement+">");
sb.append(""+ELEMENT_NAME+">");
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后要进行的保存等操作以后再说!