- 通信API
通信的过程是在封装好的jar包里面的,KurentoRoomAPI->(继承)KurentoAPI->(实现)JsonRpcWebSocketClient.WebSocketConnectionEvents。
public interface WebSocketConnectionEvents {
public void onOpen(ServerHandshake handshakedata);
public void onRequest(JsonRpcRequest request);
public void onResponse(JsonRpcResponse response);
public void onNotification(JsonRpcNotification notification);
public void onClose(int code, String reason, boolean remote);
public void onError(Exception e);
}
- 连接Url
KurentoAPI.connectWebSocket
public void connectWebSocket() {
try {
if(isWebSocketConnected()){
return;
}
URI uri = new URI(wsUri);
client = new JsonRpcWebSocketClient(uri, this,executor);
if (webSocketClientFactory != null) {
client.setWebSocketFactory(webSocketClientFactory);
}
executor.execute(new Runnable() {
public void run() {
client.connect();
}
});
} catch (Exception exc){
Log.e(LOG_TAG, "connectWebSocket", exc);
}
}
2)发送消息
KurentoAPI.send
protected void send(String method, HashMap<String, Object> namedParameters, int id){
try {
final JsonRpcRequest request = new JsonRpcRequest();
request.setMethod(method);
if(namedParameters!=null) {
request.setNamedParams(namedParameters);
}
if(id>=0) {
request.setId(id);
}
executor.execute(new Runnable() {
public void run() {
if(isWebSocketConnected()) {
client.sendRequest(request);
}
}
});
} catch (Exception exc){
Log.e(LOG_TAG, "send: "+method, exc);
}
}
KurentoRoomAPI中实现了 具体发送业务消息的代码
如加入聊天室:
public void sendJoinRoom(String userId, String roomId, boolean dataChannelsEnabled, int id){
HashMap<String, Object> namedParameters = new HashMap<>();
namedParameters.put("user", userId);
namedParameters.put("room", roomId);
namedParameters.put("dataChannels", dataChannelsEnabled);
send("joinRoom", namedParameters, id);
}
而在项目中自己写的类VideoClientAPI又继承自KurentoRoomAPI并实现获取WebRTC监控视频的功能。
服务端还是原来的基于原本的js-kurento-player的服务端,把需要的摄像头的rtsp地址用videourl传给服务端,接入原本的webrtc的过程。
VideoClientAPI.startPlay
public void startPlay(String videourl, String sdpOffer, int id) {
HashMap<String, Object> namedParameters = new HashMap<>();
namedParameters.put("videourl", videourl);
namedParameters.put("sdpOffer", sdpOffer);
sendWithBuffer("start", namedParameters, id);
}
具体的webrtc的过程暂时不讨论。
另外一个自定义的消息是获取视频图片
public void getImage(int id) {
HashMap<String, Object> namedParameters = new HashMap<>();
sendWithBuffer("getImage", namedParameters, id);
}
3)接受消息
javascript的websocket建立连接后获取到服务端消息后会进入到onMessage事件方法中,在里面根据不同的消息做不同的处理。
javascript里面的websocket对象,对应于前面KurentoAPI.client对象(JsonRpcWebSocketClient类)
相对应的也有onMessage
@Override
public void onMessage(final String message) {
executor.execute(new Runnable() {
@Override
public void run() {
if (connectionState == WebSocketConnectionState.CONNECTED) {
try {
JSONRPC2Message msg = JSONRPC2Message.parse(message);
if (msg instanceof JSONRPC2Request) {
JsonRpcRequest request = new JsonRpcRequest();
request.setId(((JSONRPC2Request) msg).getID());
request.setMethod(((JSONRPC2Request) msg).getMethod());
request.setNamedParams(((JSONRPC2Request) msg).getNamedParams());
request.setPositionalParams(((JSONRPC2Request) msg).getPositionalParams());
events.onRequest(request);
} else if (msg instanceof JSONRPC2Notification) {
JsonRpcNotification notification = new JsonRpcNotification();
notification.setMethod(((JSONRPC2Notification) msg).getMethod());
notification.setNamedParams(((JSONRPC2Notification) msg).getNamedParams());
notification.setPositionalParams(((JSONRPC2Notification) msg).getPositionalParams());
events.onNotification(notification);
} else if (msg instanceof JSONRPC2Response) {
JsonRpcResponse notification = new JsonRpcResponse(message);
events.onResponse(notification);
}
} catch (JSONRPC2ParseException e) {
// TODO: Handle exception
}
}
}
});
}
这里接收到一个json字符串并解析,根据json解析后的类型不同,后续调用onRequest,onResponse或者onNotification,就是JsonRpcWebSocketClient.WebSocketConnectionEvents接口中定义的那几个方法,在KurentoRoomAPI中实现了具体的代码。
@Override
public void onResponse(JsonRpcResponse response) {
if(response.isSuccessful()){
JSONObject jsonObject = (JSONObject)response.getResult();
RoomResponse roomResponse = new RoomResponse(response.getId().toString(), jsonObject);
synchronized (listeners) {
for (RoomListener rl : listeners) {
rl.onRoomResponse(roomResponse);
}
}
} else {
RoomError roomError = new RoomError(response.getError());
synchronized (listeners) {
for (RoomListener rl : listeners) {
rl.onRoomError(roomError);
}
}
}
}
/**
* Callback method that relays the RoomNotification to the RoomListener interface.
*/
@Override
public void onNotification(JsonRpcNotification notification) {
RoomNotification roomNotification = new RoomNotification(notification);
synchronized (listeners) {
for (RoomListener rl : listeners) {
rl.onRoomNotification(roomNotification);
}
}
}
这里onResponse封装了一下,把信息保存到RoomResponse里面,并通过RoomListener接口的onRoomResponse将消息发给前端代码。
onNotifaction也封装了一下,保持到RoomNotifacation里面,并通过RoomListener接口的onRoomNotification将消息发给前端代码。
前端业务逻辑代码需要实现RoomListener。
public interface RoomListener {
public void onRoomResponse(RoomResponse response);
public void onRoomError(RoomError error);
public void onRoomNotification(RoomNotification notification);
public void onRoomConnected();
public void onRoomDisconnected();
}
具体前端代码:
public class VideoPlayer implements NBMWebRTCPeer.Observer, RoomListener {
具体的接收消息并处理的代码就在VideoPlayer的onRoomNotifacation里面了。
@Override
public void onRoomNotification(RoomNotification notification) {
//MyLog.i("RoomListener", "onRoomNotification:" + notification);
String method = notification.getMethod();
switch (method) {
case "startResponse":
startResponse(notification);
break;
case "error":
// if (state == I_AM_STARTING) {
// setState(I_CAN_START);
// }
// onError('Error message from server: ' + parsedMessage.message);
break;
case "playEnd":
//playEnd();
break;
case "videoInfo":
//showVideoData(parsedMessage);
break;
case "iceCandidate":
addIceCandidate(notification);
break;
case "seek":
//console.log (parsedMessage.message);
break;
case "position":
//document.getElementById("videoPosition").value = parsedMessage.position;
break;
case "imageInfo":
getImageInfo(notification);
break;
default:
// if (state == I_AM_STARTING) {
// setState(I_CAN_START);
// }
// onError('Unrecognized message', parsedMessage);
break;
}
}
VideoPlayer也是封装了视频通信的一个类,具体到界面相关的交互还要到Activity里面。
public class VideoClientActivity
extends AppCompatActivity
{
protected VideoPlayer videoPlayer=new VideoPlayer(this);
在Activiy里面调用videoPlayer里面的方法,发送消息,如:
findViewById(R.id.btnGenerate).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
VideoDebugActivity activity = VideoDebugActivity.this;
videoPlayer.startPlay(cbSTUN.isChecked(), cbUseBuffer.isChecked(), true, cbLocalMedia.isChecked());
}
});
处理消息:
videoPlayer.addRtcListener(new RtcListener() {
@Override
public void addIceCandidate(RoomNotification notification) {
if (isShowLocalCandidate == false) {
showRemoteCandidates();
}
}
@Override
public void onIceCandidate(IceCandidate iceCandidate, NBMPeerConnection connection) {
if (isShowLocalCandidate)
showLocalCandidates();
}
@Override
public void onIceGatheringDone() {
Logger.w("OnIceGatheringDone");
//todo:显示offer和localCandidate
}
@Override
public void getImageInfo(String base64) {
//final Bitmap bitmap = base64ToBitmap(base64);
//todo:显示图片
// runOnUiThread(new Runnable() {
// @Override
// public void run() {
// videoImage.setImageBitmap(bitmap);
// }
// });
}
});
这里向videoPlayer添加RtcListener,从刚刚的onRoomNotification将消息触发到Activity进行相应处理。
这里用到java的接口的匿名实现的概念,对应于c#的事件(+=相应函数)。
这里的RtcListener是自定义的接口,用于做事件响应的过程。
public interface RtcListener {
void addIceCandidate(RoomNotification notification) ;
void onIceCandidate(IceCandidate iceCandidate, NBMPeerConnection connection);
void onIceGatheringDone();
void getImageInfo(String base64) ;
}
需要相应其他的服务端消息时,这样也要相应的增加接口。
具体调用路径:VideoPlayer的onRoomNotification->getImageInfo->notifyGetImageInfo->Activity中定位的RtcListener匿名实现类