feat: 增加websocket
This commit is contained in:
6
pom.xml
6
pom.xml
@@ -51,6 +51,12 @@
|
||||
<artifactId>lombok</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
<version>2.10.1</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
53
src/main/java/com/ttstd/signaling/bean/IceCandidate.java
Normal file
53
src/main/java/com/ttstd/signaling/bean/IceCandidate.java
Normal file
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.*;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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<String, Object> payload; // 其他负载数据
|
||||
}
|
||||
@@ -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<String, Session> clients = new ConcurrentHashMap<>();
|
||||
private static final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
private static final Map<String, SessionDescription> serviceSessionDescriptionMap = new ConcurrentHashMap<>();
|
||||
private static final Map<String, IceCandidate> serviceIceCandidateMap = new ConcurrentHashMap<>();
|
||||
|
||||
private static final Map<String, SessionDescription> clientSessionDescriptionMap = new ConcurrentHashMap<>();
|
||||
private static final Map<String, IceCandidate> 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);
|
||||
|
||||
Reference in New Issue
Block a user