diff --git a/pom.xml b/pom.xml
index 77c4268..c1b6f71 100644
--- a/pom.xml
+++ b/pom.xml
@@ -51,6 +51,12 @@
lombok
true
+
+
+ com.google.code.gson
+ gson
+ 2.10.1
+
diff --git a/src/main/java/com/ttstd/signaling/bean/IceCandidate.java b/src/main/java/com/ttstd/signaling/bean/IceCandidate.java
new file mode 100644
index 0000000..9d38d93
--- /dev/null
+++ b/src/main/java/com/ttstd/signaling/bean/IceCandidate.java
@@ -0,0 +1,53 @@
+package com.ttstd.signaling.bean;
+
+
+import com.google.gson.Gson;
+import com.google.gson.JsonParser;
+
+import java.io.Serializable;
+
+public class IceCandidate implements Serializable {
+
+ String sdpMid;
+ int sdpMLineIndex;
+ String sdp;
+
+ public IceCandidate() {
+ }
+
+ public IceCandidate(String sdpMid, int sdpMLineIndex, String sdp) {
+ this.sdpMid = sdpMid;
+ this.sdpMLineIndex = sdpMLineIndex;
+ this.sdp = sdp;
+ }
+
+ public String getSdpMid() {
+ return sdpMid;
+ }
+
+ public void setSdpMid(String sdpMid) {
+ this.sdpMid = sdpMid;
+ }
+
+ public int getSdpMLineIndex() {
+ return sdpMLineIndex;
+ }
+
+ public void setSdpMLineIndex(int sdpMLineIndex) {
+ this.sdpMLineIndex = sdpMLineIndex;
+ }
+
+ public String getSdp() {
+ return sdp;
+ }
+
+ public void setSdp(String sdp) {
+ this.sdp = sdp;
+ }
+
+ @Override
+ public String toString() {
+ return JsonParser.parseString(new Gson().toJson(this)).getAsJsonObject().toString();
+ }
+}
+
diff --git a/src/main/java/com/ttstd/signaling/bean/SessionDescription.java b/src/main/java/com/ttstd/signaling/bean/SessionDescription.java
new file mode 100644
index 0000000..3176251
--- /dev/null
+++ b/src/main/java/com/ttstd/signaling/bean/SessionDescription.java
@@ -0,0 +1,41 @@
+package com.ttstd.signaling.bean;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonParser;
+
+import java.io.Serializable;
+
+public class SessionDescription implements Serializable {
+ String type;
+ String description;
+
+ public SessionDescription() {
+ }
+
+ public SessionDescription(String type, String description) {
+ this.type = type;
+ this.description = description;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ @Override
+ public String toString() {
+ return JsonParser.parseString(new Gson().toJson(this)).getAsJsonObject().toString();
+ }
+
+}
diff --git a/src/main/java/com/ttstd/signaling/controller/AuthController.java b/src/main/java/com/ttstd/signaling/controller/AuthController.java
index 0318d03..2a435b3 100644
--- a/src/main/java/com/ttstd/signaling/controller/AuthController.java
+++ b/src/main/java/com/ttstd/signaling/controller/AuthController.java
@@ -1,6 +1,5 @@
package com.ttstd.signaling.controller;
-import com.ttstd.signaling.model.SignalingMessage;
import jakarta.servlet.http.HttpSession;
import org.springframework.web.bind.annotation.*;
diff --git a/src/main/java/com/ttstd/signaling/model/ClientIceMessage.java b/src/main/java/com/ttstd/signaling/model/ClientIceMessage.java
new file mode 100644
index 0000000..aaf7fdd
--- /dev/null
+++ b/src/main/java/com/ttstd/signaling/model/ClientIceMessage.java
@@ -0,0 +1,13 @@
+package com.ttstd.signaling.model;
+
+import lombok.Data;
+
+@Data
+public class ClientIceMessage {
+ private String sdpMid;
+ private int sdpMLineIndex;
+ private String sdp;
+ private String fromUser;
+ private String toUser;
+ private long timestamp;
+}
diff --git a/src/main/java/com/ttstd/signaling/model/ClientOfferMessage.java b/src/main/java/com/ttstd/signaling/model/ClientOfferMessage.java
new file mode 100644
index 0000000..fdd5603
--- /dev/null
+++ b/src/main/java/com/ttstd/signaling/model/ClientOfferMessage.java
@@ -0,0 +1,12 @@
+package com.ttstd.signaling.model;
+
+import lombok.Data;
+
+@Data
+public class ClientOfferMessage {
+ private String type; // 类型: "offer", "answer", "ice", "reject", "accept"
+ private String fromUser;
+ private String toUser;
+ private long timestamp;
+
+}
diff --git a/src/main/java/com/ttstd/signaling/model/ClientSdpMessage.java b/src/main/java/com/ttstd/signaling/model/ClientSdpMessage.java
new file mode 100644
index 0000000..521c1b7
--- /dev/null
+++ b/src/main/java/com/ttstd/signaling/model/ClientSdpMessage.java
@@ -0,0 +1,14 @@
+package com.ttstd.signaling.model;
+
+import lombok.Data;
+
+@Data
+public class ClientSdpMessage {
+ private String type; // 类型: "offer", "answer", "ice", "reject", "accept"
+ private String sdp;
+
+ private String fromUser;
+ private String toUser;
+ private long timestamp;
+
+}
diff --git a/src/main/java/com/ttstd/signaling/model/ServiceIceMessage.java b/src/main/java/com/ttstd/signaling/model/ServiceIceMessage.java
new file mode 100644
index 0000000..1669092
--- /dev/null
+++ b/src/main/java/com/ttstd/signaling/model/ServiceIceMessage.java
@@ -0,0 +1,12 @@
+package com.ttstd.signaling.model;
+
+import lombok.Data;
+
+@Data
+public class ServiceIceMessage {
+ private String sdpMid;
+ private int sdpMLineIndex;
+ private String sdp;
+ private String fromUser;
+ long timestamp;
+}
\ No newline at end of file
diff --git a/src/main/java/com/ttstd/signaling/model/ServiceSdpMessage.java b/src/main/java/com/ttstd/signaling/model/ServiceSdpMessage.java
new file mode 100644
index 0000000..b063403
--- /dev/null
+++ b/src/main/java/com/ttstd/signaling/model/ServiceSdpMessage.java
@@ -0,0 +1,11 @@
+package com.ttstd.signaling.model;
+
+import lombok.Data;
+
+@Data
+public class ServiceSdpMessage {
+ private String type; // 类型: "offer", "answer", "ice", "reject", "accept"
+ private String sdp;
+ private String fromUser;
+ long timestamp;
+}
\ No newline at end of file
diff --git a/src/main/java/com/ttstd/signaling/model/SignalingMessage.java b/src/main/java/com/ttstd/signaling/model/SignalingMessage.java
deleted file mode 100644
index 22bb396..0000000
--- a/src/main/java/com/ttstd/signaling/model/SignalingMessage.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package com.ttstd.signaling.model;
-
-import lombok.Data;
-import java.util.Map;
-
-@Data
-public class SignalingMessage {
- private String type; // 消息类型: offer, answer, candidate, join, leave
- private String from; // 发送者ID
- private String to; // 接收者ID
- private String roomId; // 房间ID
- private Object sdp; // SDP描述
- private Object candidate; // ICE候选
- private Map payload; // 其他负载数据
-}
\ No newline at end of file
diff --git a/src/main/java/com/ttstd/signaling/service/WebRTCSignalingServer.java b/src/main/java/com/ttstd/signaling/service/WebRTCSignalingServer.java
index 79027b7..3ebcfb3 100644
--- a/src/main/java/com/ttstd/signaling/service/WebRTCSignalingServer.java
+++ b/src/main/java/com/ttstd/signaling/service/WebRTCSignalingServer.java
@@ -1,25 +1,24 @@
package com.ttstd.signaling.service;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
-import com.ttstd.signaling.config.HttpSessionConfigurator;
-import com.ttstd.signaling.model.SignalMessage;
-import com.ttstd.signaling.model.SignalingMessage;
-import jakarta.servlet.http.HttpSession;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.gson.Gson;
+import com.google.gson.JsonObject;
+import com.ttstd.signaling.bean.IceCandidate;
+import com.ttstd.signaling.bean.SessionDescription;
+import com.ttstd.signaling.model.*;
import jakarta.websocket.*;
+import jakarta.websocket.server.PathParam;
import jakarta.websocket.server.ServerEndpoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
-import java.io.IOException;
-import java.util.concurrent.ConcurrentHashMap;
-
-import jakarta.websocket.*;
-import jakarta.websocket.server.PathParam;
-import jakarta.websocket.server.ServerEndpoint;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import org.springframework.stereotype.Component;
-import java.io.IOException;
+import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -29,10 +28,17 @@ public class WebRTCSignalingServer {
Logger logger = LoggerFactory.getLogger(WebRTCSignalingServer.class);
+ private Gson gson = new Gson();
// 用于存储在线用户的 Session
private static final Map clients = new ConcurrentHashMap<>();
private static final ObjectMapper objectMapper = new ObjectMapper();
+ private static final Map serviceSessionDescriptionMap = new ConcurrentHashMap<>();
+ private static final Map serviceIceCandidateMap = new ConcurrentHashMap<>();
+
+ private static final Map clientSessionDescriptionMap = new ConcurrentHashMap<>();
+ private static final Map clientIceCandidateMap = new ConcurrentHashMap<>();
+
@OnOpen
public void onOpen(Session session, @PathParam("userId") String userId) {
clients.put(userId, session);
@@ -41,24 +47,141 @@ public class WebRTCSignalingServer {
@OnMessage
public void onMessage(String message, Session session, @PathParam("userId") String userId) {
- try {
- // 解析收到的 JSON 消息
- SignalMessage signal = objectMapper.readValue(message, SignalMessage.class);
- String toUser = signal.getToUser();
- Session targetSession = clients.get(toUser);
+ logger.info("onMessage: message = " + message);
- if (targetSession != null && targetSession.isOpen()) {
- // 转发信令(SDP, ICE, 拒绝/接受)
- targetSession.getAsyncRemote().sendText(message);
- logger.info("onMessage: 信令类型 [" + signal.getType() + "] 从 " + userId + " 发往 " + toUser);
- } else {
- sendErrorMessage(session, "目标用户不在线: " + toUser);
+ try {
+ JsonNode jsonNode = objectMapper.readTree(message);
+ int msgType = jsonNode.get("msg_type").asInt();
+ logger.info("onMessage: msgType = " + msgType);
+ String content = jsonNode.get("content").toString();
+ logger.info("onMessage: content = " + content);
+ switch (msgType) {
+ case 1:
+ receiverServiceSdp(content);
+ break;
+ case 2:
+ receiverServiceIce(content);
+ break;
+ case 3:
+ receiverClientOfferMessage(content);
+ break;
+ case 4:
+ receiverClientSdpMessage(content);
+ break;
+ case 5:
+ receiverClientIceMessage(content);
+ break;
+ case -99:
+ // 解析收到的 JSON 消息
+ SignalMessage signal = objectMapper.readValue(message, SignalMessage.class);
+ String toUser = signal.getToUser();
+ Session targetSession = clients.get(toUser);
+
+ if (targetSession != null && targetSession.isOpen()) {
+ // 转发信令(SDP, ICE, 拒绝/接受)
+ targetSession.getAsyncRemote().sendText(message);
+ logger.info("onMessage: 信令类型 [" + signal.getType() + "] 从 " + userId + " 发往 " + toUser);
+ } else {
+ sendErrorMessage(session, "目标用户不在线: " + toUser);
+ }
+ break;
}
+
} catch (Exception e) {
e.printStackTrace();
}
}
+ /**
+ * 接收并存储被控端sdp信息
+ * @param jsonString
+ * @throws JsonProcessingException
+ */
+ private void receiverServiceSdp(String jsonString) throws JsonProcessingException {
+ // 解析收到的 JSON 消息
+ ServiceSdpMessage serviceSdpMessage = objectMapper.readValue(jsonString, ServiceSdpMessage.class);
+ String fromUser = serviceSdpMessage.getFromUser();
+ Session targetSession = clients.get(fromUser);
+ logger.info("receiverServiceSdp: fromUser = " + fromUser);
+ logger.info("receiverServiceSdp: targetSession = " + targetSession);
+ SessionDescription sessionDescription = new SessionDescription(serviceSdpMessage.getType(), serviceSdpMessage.getSdp());
+ serviceSessionDescriptionMap.put(fromUser, sessionDescription);
+ logger.info("receiverServiceSdp: serviceSessionDescriptionMap size = " + serviceSessionDescriptionMap.size());
+ }
+
+ /**
+ * 接收并存储被控端ice信息
+ * @param jsonString
+ * @throws JsonProcessingException
+ */
+ private void receiverServiceIce(String jsonString) throws JsonProcessingException {
+ ServiceIceMessage serviceIceMessage = objectMapper.readValue(jsonString, ServiceIceMessage.class);
+ String fromUser = serviceIceMessage.getFromUser();
+ Session targetSession = clients.get(fromUser);
+ logger.info("receiverServiceIce: fromUser = " + fromUser);
+ logger.info("receiverServiceIce: targetSession = " + targetSession);
+ IceCandidate iceCandidate = new IceCandidate(serviceIceMessage.getSdpMid(), serviceIceMessage.getSdpMLineIndex(), serviceIceMessage.getSdp());
+ serviceIceCandidateMap.put(fromUser, iceCandidate);
+ logger.info("receiverServiceIce: serviceIceCandidateMap size = " + serviceIceCandidateMap.size());
+ }
+
+ /**
+ * 接收并存储控制端sdp信息
+ * @param jsonString
+ * @throws JsonProcessingException
+ */
+ private void receiverClientOfferMessage(String jsonString) throws JsonProcessingException {
+ ClientOfferMessage clientOfferMessage = objectMapper.readValue(jsonString, ClientOfferMessage.class);
+ String fromUser = clientOfferMessage.getFromUser();
+ logger.info("receiverClientOfferMessage: fromUser = " + fromUser);
+ String toUser = clientOfferMessage.getToUser();
+ logger.info("receiverClientOfferMessage: toUser = " + toUser);
+
+ logger.info("receiverClientOfferMessage: serviceSessionDescriptionMap size = " + serviceSessionDescriptionMap.size());
+ logger.info("receiverClientOfferMessage: serviceIceCandidateMap size = " + serviceIceCandidateMap.size());
+
+ Session session = clients.get(fromUser);
+
+ SessionDescription sessionDescription = serviceSessionDescriptionMap.get(toUser);
+ JsonObject sdpJsonObject = new JsonObject();
+ sdpJsonObject.addProperty("msg_type", "11");
+ sdpJsonObject.addProperty("sdp", sessionDescription.toString());
+ String sdpJsonString = gson.toJson(sdpJsonObject);
+ logger.info("receiverClientOfferMessage: sdpJsonString = " + sdpJsonString);
+ session.getAsyncRemote().sendText(gson.toJson(sdpJsonObject));
+
+ IceCandidate iceCandidate = serviceIceCandidateMap.get(toUser);
+ JsonObject iceJsonObject = new JsonObject();
+ iceJsonObject.addProperty("msg_type", "22");
+ iceJsonObject.addProperty("ice", iceCandidate.toString());
+ String iceJsonString = gson.toJson(iceJsonObject);
+ logger.info("receiverClientOfferMessage: iceJsonString = " + iceJsonString);
+
+ session.getAsyncRemote().sendText(gson.toJson(iceJsonObject));
+
+ }
+
+ /**
+ * 接收并存储控制端ice信息
+ * @param jsonString
+ * @throws JsonProcessingException
+ */
+ private void receiverClientSdpMessage(String jsonString) throws JsonProcessingException {
+ ClientSdpMessage clientSdpMessage = objectMapper.readValue(jsonString, ClientSdpMessage.class);
+ String fromUser = clientSdpMessage.getFromUser();
+ String toUser = clientSdpMessage.getToUser();
+ SessionDescription description = new SessionDescription(clientSdpMessage.getType(), clientSdpMessage.getSdp());
+ clientSessionDescriptionMap.put(fromUser, description);
+ }
+
+ private void receiverClientIceMessage(String jsonString) throws JsonProcessingException {
+ ClientIceMessage clientIceMessage = objectMapper.readValue(jsonString, ClientIceMessage.class);
+ String fromUser = clientIceMessage.getFromUser();
+ String toUser = clientIceMessage.getToUser();
+ IceCandidate iceCandidate = new IceCandidate(clientIceMessage.getSdpMid(), clientIceMessage.getSdpMLineIndex(), clientIceMessage.getSdp());
+ clientIceCandidateMap.put(fromUser, iceCandidate);
+ }
+
@OnClose
public void onClose(@PathParam("userId") String userId) {
clients.remove(userId);