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);