|
@@ -0,0 +1,878 @@
|
|
|
+package com.wdkl.ncs.janus.client;
|
|
|
+
|
|
|
+import android.util.Log;
|
|
|
+
|
|
|
+import com.wdkl.ncs.janus.rtc.WebRTCEngine;
|
|
|
+import com.wdkl.ncs.janus.util.EnumType;
|
|
|
+
|
|
|
+import org.json.JSONException;
|
|
|
+import org.json.JSONObject;
|
|
|
+import org.webrtc.IceCandidate;
|
|
|
+import org.webrtc.SessionDescription;
|
|
|
+
|
|
|
+import java.math.BigInteger;
|
|
|
+import java.util.Random;
|
|
|
+import java.util.Timer;
|
|
|
+import java.util.TimerTask;
|
|
|
+import java.util.concurrent.ConcurrentHashMap;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 参考1:https://blog.csdn.net/Java_lilin/article/details/104007291
|
|
|
+ * 参考2:https://github.com/benwtrent/janus-gateway-android
|
|
|
+ * 参考3:https://zhuanlan.zhihu.com/p/149324861?utm_source=wechat_session
|
|
|
+ */
|
|
|
+public class JanusClient implements WebSocketChannel.WebSocketCallback {
|
|
|
+ private static final String TAG = "JanusClient";
|
|
|
+ private ConcurrentHashMap<BigInteger, PluginHandle> attachedPlugins = new ConcurrentHashMap<>();
|
|
|
+ private ConcurrentHashMap<String, Transaction> transactions = new ConcurrentHashMap<>();
|
|
|
+ private BigInteger sessionId = null;
|
|
|
+ private BigInteger currentHandleId = null;
|
|
|
+ private JanusCallback janusCallback;
|
|
|
+
|
|
|
+ private volatile boolean isKeepAliveRunning;
|
|
|
+ private Thread keepAliveThread;
|
|
|
+
|
|
|
+ private String janusUrl;
|
|
|
+ private WebSocketChannel webSocketChannel;
|
|
|
+
|
|
|
+ private EnumType.CallState callState;
|
|
|
+ private EnumType.CallEndReason endReason;
|
|
|
+ private EnumType.RefuseType refuseType;
|
|
|
+
|
|
|
+ private BigInteger roomId;
|
|
|
+ private BigInteger userId;
|
|
|
+ public static String remoteUsername = "";
|
|
|
+
|
|
|
+ private TimerTask timerTask;
|
|
|
+ private Timer timer;
|
|
|
+
|
|
|
+ public static final int ERROR_CREATE_ROOM = 0x01;
|
|
|
+ public static final int ERROR_ON_MESSAGE = 0x02;
|
|
|
+
|
|
|
+ public JanusClient(String janusUrl, BigInteger userId) {
|
|
|
+ this.janusUrl = janusUrl;
|
|
|
+ this.userId = userId;
|
|
|
+ webSocketChannel = new WebSocketChannel();
|
|
|
+ webSocketChannel.setWebSocketCallback(this);
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ public WebSocketChannel getWebSocketChannel() {
|
|
|
+ return webSocketChannel;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setWebSocketChannel(WebSocketChannel webSocketChannel) {
|
|
|
+ this.webSocketChannel = webSocketChannel;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setJanusCallback(JanusCallback janusCallback) {
|
|
|
+ this.janusCallback = janusCallback;
|
|
|
+ }
|
|
|
+
|
|
|
+ public BigInteger getSessionId() {
|
|
|
+ return sessionId;
|
|
|
+ }
|
|
|
+
|
|
|
+ public BigInteger getCurrentHandleId() {
|
|
|
+ return currentHandleId;
|
|
|
+ }
|
|
|
+
|
|
|
+ public EnumType.CallState getCallState() {
|
|
|
+ return callState;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setCallState(EnumType.CallState callState) {
|
|
|
+ this.callState = callState;
|
|
|
+ }
|
|
|
+
|
|
|
+ public EnumType.CallEndReason getEndReason() {
|
|
|
+ return endReason;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setEndReason(EnumType.CallEndReason endReason) {
|
|
|
+ this.endReason = endReason;
|
|
|
+ }
|
|
|
+
|
|
|
+ public EnumType.RefuseType getRefuseType() {
|
|
|
+ return refuseType;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setRefuseType(EnumType.RefuseType refuseType) {
|
|
|
+ this.refuseType = refuseType;
|
|
|
+ }
|
|
|
+
|
|
|
+ public BigInteger getRoomId() {
|
|
|
+ return roomId;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setRoomId(BigInteger roomId) {
|
|
|
+ this.roomId = roomId;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void connect() {
|
|
|
+ webSocketChannel.connect(janusUrl);
|
|
|
+ }
|
|
|
+
|
|
|
+ public void disConnect() {
|
|
|
+ stopKeepAliveTimer();
|
|
|
+ Log.i(TAG, "close ws");
|
|
|
+
|
|
|
+ if (webSocketChannel != null) {
|
|
|
+ try {
|
|
|
+ Log.i(TAG,"close rtc");
|
|
|
+ webSocketChannel.close();
|
|
|
+ WebRTCEngine.getInstance().release();
|
|
|
+ }catch (Exception e){
|
|
|
+ // e.printStackTrace();
|
|
|
+ Log.e(TAG, e.getMessage());
|
|
|
+ }
|
|
|
+
|
|
|
+ webSocketChannel = null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 创建session,并得到反馈
|
|
|
+ * 发出
|
|
|
+ * {
|
|
|
+ * "janus" : "create",
|
|
|
+ * "transaction" : "<random alphanumeric string>"
|
|
|
+ * }
|
|
|
+ * 成功后的反馈
|
|
|
+ * {
|
|
|
+ * "janus" : "success",
|
|
|
+ * "transaction" : "<same as the request>",
|
|
|
+ * "data" : {
|
|
|
+ * "id" : <unique integer session ID>
|
|
|
+ * }
|
|
|
+ * }
|
|
|
+ * 错误时的反馈
|
|
|
+ * error: a JSON object containing two fields:
|
|
|
+ * code: a numeric error code, as defined in apierror.h;
|
|
|
+ * reason: a verbose string describing the cause of the failure.
|
|
|
+ * <p>
|
|
|
+ * {
|
|
|
+ * "janus" : "error",
|
|
|
+ * "transaction" : "<same as the request>",
|
|
|
+ * "error" : {
|
|
|
+ * "code" : 458
|
|
|
+ * "reason" : "Could not find session 12345678"
|
|
|
+ * }
|
|
|
+ * }
|
|
|
+ * <p>
|
|
|
+ * Once you've created a session, a new endpoint you can use is created in the server. Specifically, the new endpoint is constructed by concatenating the server root and the session identifier you've been returned (e.g., /janus/12345678).
|
|
|
+ * <p>
|
|
|
+ * This endpoint can be used in two different ways:
|
|
|
+ * <p>
|
|
|
+ * using a parameter-less GET request to the endpoint, you'll issue a long-poll request to be notified about events and incoming messages from this session;
|
|
|
+ * using a POST request to send JSON messages, you'll interact with the session itself.
|
|
|
+ */
|
|
|
+ private void createSession() {
|
|
|
+ //先声明回调
|
|
|
+ String tid = randomString(12);
|
|
|
+ transactions.put(tid, new Transaction(tid) {
|
|
|
+ @Override
|
|
|
+ public void onSuccess(JSONObject msg) throws Exception {
|
|
|
+ JSONObject data = msg.getJSONObject("data");
|
|
|
+ sessionId = new BigInteger(data.getString("id"));
|
|
|
+ startKeepAliveTimer();
|
|
|
+ if (janusCallback != null) {
|
|
|
+ janusCallback.onCreateSession(sessionId);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ //再发送命令
|
|
|
+ try {
|
|
|
+ JSONObject obj = new JSONObject();
|
|
|
+ obj.put("janus", "create");
|
|
|
+ obj.put("transaction", tid);
|
|
|
+ sendMessage(obj.toString());
|
|
|
+ } catch (Exception e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 释放session
|
|
|
+ */
|
|
|
+ public void destroySession() {
|
|
|
+ String tid = randomString(12);
|
|
|
+ transactions.put(tid, new Transaction(tid) {
|
|
|
+ @Override
|
|
|
+ public void onSuccess(JSONObject msg) throws Exception {
|
|
|
+ stopKeepAliveTimer();
|
|
|
+ if (janusCallback != null) {
|
|
|
+ janusCallback.onDestroySession(sessionId);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ try {
|
|
|
+ JSONObject obj = new JSONObject();
|
|
|
+ obj.put("janus", "destroy");
|
|
|
+ obj.put("transaction", tid);
|
|
|
+ obj.put("session_id", sessionId);
|
|
|
+ sendMessage(obj.toString());
|
|
|
+ } catch (Exception e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Interacting with the session。建立session后,与session进行交互,得到handle id
|
|
|
+ *
|
|
|
+ * @param pluginName
|
|
|
+ */
|
|
|
+ public void attachPlugin(String pluginName) {
|
|
|
+ String tid = randomString(12);
|
|
|
+ transactions.put(tid, new Transaction(tid) {
|
|
|
+ @Override
|
|
|
+ public void onSuccess(JSONObject msg) throws Exception {
|
|
|
+ JSONObject data = msg.getJSONObject("data");
|
|
|
+ BigInteger handleId = new BigInteger(data.getString("id"));
|
|
|
+ currentHandleId = handleId;
|
|
|
+ if (janusCallback != null) {
|
|
|
+ janusCallback.onAttached(handleId);
|
|
|
+ }
|
|
|
+ PluginHandle handle = new PluginHandle(handleId);
|
|
|
+ attachedPlugins.put(handleId, handle);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ try {
|
|
|
+ JSONObject obj = new JSONObject();
|
|
|
+ obj.put("janus", "attach");
|
|
|
+ obj.put("transaction", tid);
|
|
|
+ obj.put("plugin", pluginName);
|
|
|
+ obj.put("session_id", sessionId);
|
|
|
+ sendMessage(obj.toString());
|
|
|
+ } catch (Exception e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * attach 到发布者的 handler 上,准备接收视频流
|
|
|
+ * 每个发布者都要 attach 一遍,然后协商 sdp, SFU
|
|
|
+ *
|
|
|
+ * @param feedId
|
|
|
+ */
|
|
|
+ public void subscribeAttach(BigInteger feedId) {
|
|
|
+ String tid = randomString(12);
|
|
|
+ transactions.put(tid, new Transaction(tid, feedId) {
|
|
|
+ @Override
|
|
|
+ public void onSuccess(JSONObject msg, BigInteger feedId) throws Exception {
|
|
|
+ JSONObject data = msg.getJSONObject("data");
|
|
|
+ //其它发布者的 handleId
|
|
|
+ BigInteger handleId = new BigInteger(data.getString("id"));
|
|
|
+ if (janusCallback != null) {
|
|
|
+ janusCallback.onSubscribeAttached(handleId, feedId);
|
|
|
+ }
|
|
|
+ PluginHandle handle = new PluginHandle(handleId);
|
|
|
+ attachedPlugins.put(handleId, handle);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ try {
|
|
|
+ JSONObject obj = new JSONObject();
|
|
|
+ obj.put("janus", "attach");
|
|
|
+ obj.put("transaction", tid);
|
|
|
+ obj.put("plugin", "janus.plugin.videoroom");
|
|
|
+ obj.put("session_id", sessionId);
|
|
|
+ sendMessage(obj.toString());
|
|
|
+ } catch (Exception e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 给 plugin 发送消息
|
|
|
+ *
|
|
|
+ * @param handleId
|
|
|
+ * @param sdp webrtc session description
|
|
|
+ */
|
|
|
+ public void createOffer(BigInteger handleId, SessionDescription sdp, Boolean videoOn) {
|
|
|
+ JSONObject message = new JSONObject();
|
|
|
+ try {
|
|
|
+ JSONObject publish = new JSONObject();
|
|
|
+ publish.putOpt("audio", true);
|
|
|
+ publish.putOpt("video", videoOn);
|
|
|
+
|
|
|
+ JSONObject jsep = new JSONObject();
|
|
|
+ jsep.putOpt("type", sdp.type);
|
|
|
+ jsep.putOpt("sdp", sdp.description);
|
|
|
+
|
|
|
+ message.putOpt("janus", "message");
|
|
|
+ message.putOpt("body", publish);
|
|
|
+ message.putOpt("jsep", jsep);
|
|
|
+ message.putOpt("transaction", randomString(12));
|
|
|
+ message.putOpt("session_id", sessionId);
|
|
|
+ message.putOpt("handle_id", handleId);
|
|
|
+ } catch (JSONException e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ }
|
|
|
+
|
|
|
+ sendMessage(message.toString());
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 开始订阅
|
|
|
+ *
|
|
|
+ * @param subscriptionHandleId
|
|
|
+ * @param sdp
|
|
|
+ */
|
|
|
+ public void subscriptionStart(BigInteger subscriptionHandleId, SessionDescription sdp) {
|
|
|
+ JSONObject message = new JSONObject();
|
|
|
+ try {
|
|
|
+ JSONObject body = new JSONObject();
|
|
|
+ body.putOpt("request", "start");
|
|
|
+ body.putOpt("room", roomId);
|
|
|
+
|
|
|
+ message.putOpt("janus", "message");
|
|
|
+ message.putOpt("body", body);
|
|
|
+ message.putOpt("transaction", randomString(12));
|
|
|
+ message.putOpt("session_id", sessionId);
|
|
|
+ message.putOpt("handle_id", subscriptionHandleId);
|
|
|
+
|
|
|
+ if (sdp != null) {
|
|
|
+ JSONObject jsep = new JSONObject();
|
|
|
+ jsep.putOpt("type", sdp.type);
|
|
|
+ jsep.putOpt("sdp", sdp.description);
|
|
|
+ message.putOpt("jsep", jsep);
|
|
|
+ }
|
|
|
+ } catch (JSONException e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ }
|
|
|
+
|
|
|
+ sendMessage(message.toString());
|
|
|
+ }
|
|
|
+
|
|
|
+ public void publish(BigInteger handleId, SessionDescription sdp, Boolean videoOn) {
|
|
|
+ JSONObject message = new JSONObject();
|
|
|
+ try {
|
|
|
+ JSONObject publish = new JSONObject();
|
|
|
+ //你也可以用configure请求代替publish。两者的功能在发布上是等效的,但从语义的角度来看,publish是发布时要发送的正确消息。configure请求也可以用于更新发布者会话的某些属性,在这种情况下,就不能用publish请求了
|
|
|
+ publish.putOpt("request", "publish");
|
|
|
+ publish.putOpt("audio", true);
|
|
|
+ publish.putOpt("video", videoOn);
|
|
|
+
|
|
|
+ JSONObject jsep = new JSONObject();
|
|
|
+ jsep.putOpt("type", sdp.type);
|
|
|
+ jsep.putOpt("sdp", sdp.description);
|
|
|
+
|
|
|
+ message.putOpt("janus", "message");
|
|
|
+ message.putOpt("body", publish);
|
|
|
+ message.putOpt("jsep", jsep);
|
|
|
+ message.putOpt("transaction", randomString(12));
|
|
|
+ message.putOpt("session_id", sessionId);
|
|
|
+ message.putOpt("handle_id", handleId);
|
|
|
+ } catch (JSONException e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ }
|
|
|
+
|
|
|
+ sendMessage(message.toString());
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 订阅
|
|
|
+ *
|
|
|
+ * @param feedId 要订阅的ID
|
|
|
+ */
|
|
|
+ public void subscribe(BigInteger subscriptionHandleId, BigInteger feedId) {
|
|
|
+ JSONObject message = new JSONObject();
|
|
|
+ JSONObject body = new JSONObject();
|
|
|
+ try {
|
|
|
+ body.putOpt("ptype", "subscriber");
|
|
|
+ body.putOpt("request", "join");
|
|
|
+ body.putOpt("room", roomId);
|
|
|
+ body.putOpt("feed", feedId);
|
|
|
+
|
|
|
+ message.put("body", body);
|
|
|
+ message.putOpt("janus", "message");
|
|
|
+ message.putOpt("transaction", randomString(12));
|
|
|
+ message.putOpt("session_id", sessionId);
|
|
|
+ message.putOpt("handle_id", subscriptionHandleId);
|
|
|
+ } catch (JSONException e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ }
|
|
|
+
|
|
|
+ sendMessage(message.toString());
|
|
|
+ }
|
|
|
+
|
|
|
+ public void trickleCandidate(BigInteger handleId, IceCandidate iceCandidate) {
|
|
|
+ JSONObject candidate = new JSONObject();
|
|
|
+ JSONObject message = new JSONObject();
|
|
|
+ try {
|
|
|
+ candidate.putOpt("candidate", iceCandidate.sdp);
|
|
|
+ candidate.putOpt("sdpMid", iceCandidate.sdpMid);
|
|
|
+ candidate.putOpt("sdpMLineIndex", iceCandidate.sdpMLineIndex);
|
|
|
+
|
|
|
+ message.putOpt("janus", "trickle");
|
|
|
+ message.putOpt("candidate", candidate);
|
|
|
+ message.putOpt("transaction", randomString(12));
|
|
|
+ message.putOpt("session_id", sessionId);
|
|
|
+ message.putOpt("handle_id", handleId);
|
|
|
+ } catch (JSONException e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ }
|
|
|
+ sendMessage(message.toString());
|
|
|
+ }
|
|
|
+
|
|
|
+ public void trickleCandidateComplete(BigInteger handleId) {
|
|
|
+ JSONObject candidate = new JSONObject();
|
|
|
+ JSONObject message = new JSONObject();
|
|
|
+ try {
|
|
|
+ candidate.putOpt("completed", true);
|
|
|
+
|
|
|
+ message.putOpt("janus", "trickle");
|
|
|
+ message.putOpt("candidate", candidate);
|
|
|
+ message.putOpt("transaction", randomString(12));
|
|
|
+ message.putOpt("session_id", sessionId);
|
|
|
+ message.putOpt("handle_id", handleId);
|
|
|
+ } catch (JSONException e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ }
|
|
|
+ sendMessage(message.toString());
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 创建房间
|
|
|
+ *
|
|
|
+ * @param handleId
|
|
|
+ * @param newRoomId {
|
|
|
+ * "request" : "create",
|
|
|
+ * "room" : <unique numeric ID, optional, chosen by plugin if missing>,
|
|
|
+ * "permanent" : <true|false, whether the room should be saved in the config file, default=false>,
|
|
|
+ * "description" : "<pretty name of the room, optional>",
|
|
|
+ * "secret" : "<password required to edit/destroy the room, optional>",
|
|
|
+ * "pin" : "<password required to join the room, optional>",
|
|
|
+ * "is_private" : <true|false, whether the room should appear in a list request>,
|
|
|
+ * "allowed" : [ array of string tokens users can use to join this room, optional],
|
|
|
+ * ...
|
|
|
+ * }
|
|
|
+ * <p>
|
|
|
+ * 反馈
|
|
|
+ * <p>
|
|
|
+ * {
|
|
|
+ * "janus": "success",
|
|
|
+ * "session_id": 6421975129252707,
|
|
|
+ * "transaction": "m2D5tESZ3fwb",
|
|
|
+ * "sender": 537399609431468,
|
|
|
+ * "plugindata": {
|
|
|
+ * "plugin": "janus.plugin.videoroom",
|
|
|
+ * "data": {
|
|
|
+ * "videoroom": "created",
|
|
|
+ * "room": 12345678,
|
|
|
+ * "permanent": false
|
|
|
+ * }
|
|
|
+ * }
|
|
|
+ * }
|
|
|
+ * <p>
|
|
|
+ * {
|
|
|
+ * "janus": "success",
|
|
|
+ * "session_id": 1359598870403518,
|
|
|
+ * "transaction": "4mC6b30kX0sr",
|
|
|
+ * "sender": 2692286927487266,
|
|
|
+ * "plugindata": {
|
|
|
+ * "plugin": "janus.plugin.videoroom",
|
|
|
+ * "data": {
|
|
|
+ * "videoroom": "event",
|
|
|
+ * "error_code": 427,
|
|
|
+ * "error": "Room 12345678 already exists"
|
|
|
+ * }
|
|
|
+ * }
|
|
|
+ * }
|
|
|
+ */
|
|
|
+ public void createRoom(BigInteger handleId, BigInteger newRoomId) {
|
|
|
+ roomId = newRoomId;
|
|
|
+
|
|
|
+ String tid = randomString(12);
|
|
|
+ transactions.put(tid, new Transaction(tid) {
|
|
|
+ @Override
|
|
|
+ public void onSuccess(JSONObject msg) throws Exception {
|
|
|
+ JSONObject data = msg.getJSONObject("plugindata").getJSONObject("data");
|
|
|
+ if (data.getString("videoroom").equals("event")) {
|
|
|
+ if (data.has("error")) {
|
|
|
+ if (data.getInt("error_code") == 427) {
|
|
|
+ //拨打
|
|
|
+ if (EnumType.CallState.Outgoing == callState) {
|
|
|
+ destroyRoom(handleId, new DestroyRoomCallback() {
|
|
|
+ @Override
|
|
|
+ public void onSuccess() {
|
|
|
+ createRoom(handleId, newRoomId);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void onFailed() {
|
|
|
+ janusCallback.onError(ERROR_CREATE_ROOM, "创建房间失败");
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ //接收
|
|
|
+ else if (EnumType.CallState.Incoming == callState) {
|
|
|
+ janusCallback.onCreateRoom(handleId);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ janusCallback.onError(ERROR_CREATE_ROOM, "创建房间失败");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else if (data.getString("videoroom").equals("created")) {
|
|
|
+ janusCallback.onCreateRoom(handleId);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void onError() {
|
|
|
+ janusCallback.onError(ERROR_CREATE_ROOM, "创建房间失败");
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ JSONObject message = new JSONObject();
|
|
|
+ JSONObject body = new JSONObject();
|
|
|
+ try {
|
|
|
+ body.putOpt("request", "create");
|
|
|
+ body.putOpt("room", newRoomId);
|
|
|
+ message.put("body", body);
|
|
|
+
|
|
|
+ message.putOpt("janus", "message");
|
|
|
+ message.putOpt("transaction", tid);
|
|
|
+ message.putOpt("session_id", sessionId);
|
|
|
+ message.putOpt("handle_id", handleId);
|
|
|
+ } catch (JSONException e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ }
|
|
|
+
|
|
|
+ sendMessage(message.toString());
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @param handleId "videoroom" : "destroyed",
|
|
|
+ */
|
|
|
+ public void destroyRoom(BigInteger handleId, DestroyRoomCallback destroyRoomCallback) {
|
|
|
+ String tid = randomString(12);
|
|
|
+ transactions.put(tid, new Transaction(tid) {
|
|
|
+ @Override
|
|
|
+ public void onSuccess(JSONObject msg) throws Exception {
|
|
|
+ JSONObject data = msg.getJSONObject("plugindata").getJSONObject("data");
|
|
|
+ if (destroyRoomCallback != null) {
|
|
|
+ if (data.getString("videoroom").equals("destroyed")) {
|
|
|
+ destroyRoomCallback.onSuccess();
|
|
|
+ } else {
|
|
|
+ destroyRoomCallback.onFailed();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void onError() {
|
|
|
+ if (destroyRoomCallback != null) {
|
|
|
+ destroyRoomCallback.onFailed();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ JSONObject message = new JSONObject();
|
|
|
+ JSONObject body = new JSONObject();
|
|
|
+ try {
|
|
|
+ body.putOpt("request", "destroy");
|
|
|
+ body.putOpt("room", roomId);
|
|
|
+ message.put("body", body);
|
|
|
+
|
|
|
+ message.putOpt("janus", "message");
|
|
|
+ message.putOpt("transaction", tid);
|
|
|
+ message.putOpt("session_id", sessionId);
|
|
|
+ message.putOpt("handle_id", handleId);
|
|
|
+ } catch (JSONException e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ }
|
|
|
+
|
|
|
+ sendMessage(message.toString());
|
|
|
+ }
|
|
|
+
|
|
|
+ //给插件,发送消息,得到ack和event两个消息
|
|
|
+ public void joinRoom(BigInteger handleId, String displayName) {
|
|
|
+ JSONObject message = new JSONObject();
|
|
|
+ JSONObject body = new JSONObject();
|
|
|
+ try {
|
|
|
+ body.putOpt("display", displayName);
|
|
|
+ body.putOpt("ptype", "publisher");
|
|
|
+ body.putOpt("request", "join");
|
|
|
+ body.putOpt("id", userId); //发布者id,我司系统 sip_id
|
|
|
+ body.putOpt("room", roomId);
|
|
|
+ message.put("body", body);
|
|
|
+
|
|
|
+ message.putOpt("janus", "message");
|
|
|
+ message.putOpt("transaction", randomString(12));
|
|
|
+ message.putOpt("session_id", sessionId);
|
|
|
+ message.putOpt("handle_id", handleId);
|
|
|
+ } catch (JSONException e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ }
|
|
|
+
|
|
|
+ sendMessage(message.toString());
|
|
|
+ }
|
|
|
+
|
|
|
+ public void leaveRoom() {
|
|
|
+ JSONObject message = new JSONObject();
|
|
|
+ JSONObject body = new JSONObject();
|
|
|
+ try {
|
|
|
+ body.putOpt("request", "leave");
|
|
|
+ message.put("body", body);
|
|
|
+
|
|
|
+ message.putOpt("janus", "message");
|
|
|
+ message.putOpt("transaction", randomString(12));
|
|
|
+ message.putOpt("session_id", sessionId);
|
|
|
+ message.putOpt("handle_id", currentHandleId);
|
|
|
+ } catch (JSONException e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ }
|
|
|
+
|
|
|
+ sendMessage(message.toString());
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ private synchronized void sendMessage(String message){
|
|
|
+ if(webSocketChannel!=null){
|
|
|
+ webSocketChannel.sendMessage(message);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void onOpen() {
|
|
|
+ if (sessionId == null) {
|
|
|
+ createSession();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void onMessage(String message) {
|
|
|
+ //Log.d(TAG, "收到消息》》》" + message);
|
|
|
+ try {
|
|
|
+ JSONObject obj = new JSONObject(message);
|
|
|
+ JanusMessageType type = JanusMessageType.fromString(obj.getString("janus"));
|
|
|
+ String transaction = null;
|
|
|
+ BigInteger sender = null;
|
|
|
+ if (obj.has("transaction")) {
|
|
|
+ transaction = obj.getString("transaction");
|
|
|
+ }
|
|
|
+ //是插件 handleId
|
|
|
+ if (obj.has("sender")) {
|
|
|
+ sender = new BigInteger(obj.getString("sender"));
|
|
|
+ }
|
|
|
+ PluginHandle handle = null;
|
|
|
+ if (sender != null) {
|
|
|
+ handle = attachedPlugins.get(sender);
|
|
|
+ }
|
|
|
+ switch (type) {
|
|
|
+ case keepalive:
|
|
|
+ break;
|
|
|
+ case ack:
|
|
|
+ break;
|
|
|
+ case success:
|
|
|
+ if (transaction != null) {
|
|
|
+ Transaction cb = transactions.get(transaction);
|
|
|
+ if (cb != null) {
|
|
|
+ try {
|
|
|
+ if (cb.getFeedId() != null) {
|
|
|
+ cb.onSuccess(obj, cb.getFeedId());
|
|
|
+ } else {
|
|
|
+ cb.onSuccess(obj);
|
|
|
+ }
|
|
|
+ transactions.remove(transaction);
|
|
|
+ } catch (Exception e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case error: {
|
|
|
+ if (transaction != null) {
|
|
|
+ Transaction cb = transactions.get(transaction);
|
|
|
+ if (cb != null) {
|
|
|
+ cb.onError();
|
|
|
+ transactions.remove(transaction);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ case hangup: {
|
|
|
+
|
|
|
+ if (handle != null) {
|
|
|
+
|
|
|
+ if (janusCallback != null) {
|
|
|
+
|
|
|
+ janusCallback.onHangup(handle.getHandleId());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ case detached: {
|
|
|
+ if (handle != null) {
|
|
|
+ if (janusCallback != null) {
|
|
|
+ janusCallback.onDetached(handle.getHandleId());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ case event: { //不进行 transaction 处理
|
|
|
+ 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");
|
|
|
+ }
|
|
|
+ if (janusCallback != null) {
|
|
|
+ janusCallback.onMessage(sender, handle.getHandleId(), data, jsep);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ case trickle:
|
|
|
+ if (handle != null) {
|
|
|
+ if (obj.has("candidate")) {
|
|
|
+ JSONObject candidate = obj.getJSONObject("candidate");
|
|
|
+ if (janusCallback != null) {
|
|
|
+ janusCallback.onIceCandidate(handle.getHandleId(), candidate);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case destroy:
|
|
|
+ if (janusCallback != null) {
|
|
|
+ janusCallback.onDestroySession(sessionId);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case slowlink:
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ } catch (JSONException ex) {
|
|
|
+ if (janusCallback != null) {
|
|
|
+ janusCallback.onError(ERROR_ON_MESSAGE, ex.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * The long-poll request has a 30 seconds timeout. If it has no event to report, a simple keep-alive message will be triggered:
|
|
|
+ */
|
|
|
+ private void startKeepAliveTimer() {
|
|
|
+ isKeepAliveRunning = true;
|
|
|
+ if(timer!=null){
|
|
|
+ timer.purge();
|
|
|
+ }
|
|
|
+ if(timerTask!=null){
|
|
|
+ timerTask.cancel();
|
|
|
+ }
|
|
|
+ timer = new Timer();
|
|
|
+
|
|
|
+ timerTask=new TimerTask() {
|
|
|
+ @Override
|
|
|
+ public void run() {
|
|
|
+ if (webSocketChannel != null && webSocketChannel.isConnected()) {
|
|
|
+
|
|
|
+ JSONObject obj = new JSONObject();
|
|
|
+ try {
|
|
|
+ obj.put("janus", "keepalive");
|
|
|
+ obj.put("session_id", sessionId);
|
|
|
+ obj.put("transaction", randomString(12));
|
|
|
+ sendMessage(obj.toString());
|
|
|
+ } catch (JSONException e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ }
|
|
|
+
|
|
|
+ } else {
|
|
|
+ Log.e(TAG, "keepAlive failed websocket is null or not connected");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ };
|
|
|
+ timer.schedule(timerTask,25000,25000);
|
|
|
+
|
|
|
+// keepAliveThread = new Thread(new Runnable() {
|
|
|
+// @Override
|
|
|
+// public void run() {
|
|
|
+// while (isKeepAliveRunning && !Thread.interrupted()) {
|
|
|
+// synchronized (this) {
|
|
|
+// try {
|
|
|
+// Thread.sleep(25 * 1000);
|
|
|
+// } catch (InterruptedException ex) {
|
|
|
+// ex.printStackTrace();
|
|
|
+// }
|
|
|
+//
|
|
|
+// }
|
|
|
+// }
|
|
|
+// Log.d(TAG, "keepAlive thread stopped");
|
|
|
+// }
|
|
|
+// }, "KeepAlive");
|
|
|
+// keepAliveThread.start();
|
|
|
+ }
|
|
|
+
|
|
|
+ private void stopKeepAliveTimer() {
|
|
|
+ isKeepAliveRunning = false;
|
|
|
+// if (keepAliveThread != null) {
|
|
|
+// keepAliveThread.interrupt();
|
|
|
+// }
|
|
|
+ if(timer!=null){
|
|
|
+ timer.purge();
|
|
|
+ }
|
|
|
+ if(timerTask!=null){
|
|
|
+ timerTask.cancel();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void onClosed() {
|
|
|
+ stopKeepAliveTimer();
|
|
|
+ }
|
|
|
+
|
|
|
+ public String randomString(int length) {
|
|
|
+ String str = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
|
+ Random random = new Random();
|
|
|
+ StringBuilder sb = new StringBuilder(length);
|
|
|
+ for (int i = 0; i < length; i++) {
|
|
|
+ sb.append(str.charAt(random.nextInt(str.length())));
|
|
|
+ }
|
|
|
+ return sb.toString();
|
|
|
+ }
|
|
|
+
|
|
|
+ public interface DestroyRoomCallback {
|
|
|
+ void onSuccess();
|
|
|
+
|
|
|
+ void onFailed();
|
|
|
+ }
|
|
|
+
|
|
|
+ public interface JanusCallback {
|
|
|
+ void onCreateSession(BigInteger sessionId);
|
|
|
+
|
|
|
+ void onCreateRoom(BigInteger handleId);
|
|
|
+
|
|
|
+ void onAttached(BigInteger handleId);
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 订阅回调
|
|
|
+ *
|
|
|
+ * @param subscribeHandleId 订阅HandlerId
|
|
|
+ * @param feedId 订阅 feedId
|
|
|
+ */
|
|
|
+ void onSubscribeAttached(BigInteger subscribeHandleId, BigInteger feedId);
|
|
|
+
|
|
|
+ void onDetached(BigInteger handleId);
|
|
|
+
|
|
|
+ void onHangup(BigInteger handleId);
|
|
|
+
|
|
|
+ void onMessage(BigInteger sender, BigInteger handleId, JSONObject msg, JSONObject jsep);
|
|
|
+
|
|
|
+ void onIceCandidate(BigInteger handleId, JSONObject candidate);
|
|
|
+
|
|
|
+ void onDestroySession(BigInteger sessionId);
|
|
|
+
|
|
|
+ void onError(int errorCode, String error);
|
|
|
+ }
|
|
|
+}
|