feat: 增加websocket
This commit is contained in:
6
pom.xml
6
pom.xml
@@ -51,6 +51,12 @@
|
|||||||
<artifactId>lombok</artifactId>
|
<artifactId>lombok</artifactId>
|
||||||
<optional>true</optional>
|
<optional>true</optional>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.code.gson</groupId>
|
||||||
|
<artifactId>gson</artifactId>
|
||||||
|
<version>2.10.1</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<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;
|
package com.ttstd.signaling.controller;
|
||||||
|
|
||||||
import com.ttstd.signaling.model.SignalingMessage;
|
|
||||||
import jakarta.servlet.http.HttpSession;
|
import jakarta.servlet.http.HttpSession;
|
||||||
import org.springframework.web.bind.annotation.*;
|
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;
|
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.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.ttstd.signaling.config.HttpSessionConfigurator;
|
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||||
import com.ttstd.signaling.model.SignalMessage;
|
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
|
||||||
import com.ttstd.signaling.model.SignalingMessage;
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
import jakarta.servlet.http.HttpSession;
|
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.*;
|
||||||
|
import jakarta.websocket.server.PathParam;
|
||||||
import jakarta.websocket.server.ServerEndpoint;
|
import jakarta.websocket.server.ServerEndpoint;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.util.HashMap;
|
||||||
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.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
@@ -29,10 +28,17 @@ public class WebRTCSignalingServer {
|
|||||||
|
|
||||||
Logger logger = LoggerFactory.getLogger(WebRTCSignalingServer.class);
|
Logger logger = LoggerFactory.getLogger(WebRTCSignalingServer.class);
|
||||||
|
|
||||||
|
private Gson gson = new Gson();
|
||||||
// 用于存储在线用户的 Session
|
// 用于存储在线用户的 Session
|
||||||
private static final Map<String, Session> clients = new ConcurrentHashMap<>();
|
private static final Map<String, Session> clients = new ConcurrentHashMap<>();
|
||||||
private static final ObjectMapper objectMapper = new ObjectMapper();
|
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
|
@OnOpen
|
||||||
public void onOpen(Session session, @PathParam("userId") String userId) {
|
public void onOpen(Session session, @PathParam("userId") String userId) {
|
||||||
clients.put(userId, session);
|
clients.put(userId, session);
|
||||||
@@ -41,24 +47,141 @@ public class WebRTCSignalingServer {
|
|||||||
|
|
||||||
@OnMessage
|
@OnMessage
|
||||||
public void onMessage(String message, Session session, @PathParam("userId") String userId) {
|
public void onMessage(String message, Session session, @PathParam("userId") String userId) {
|
||||||
try {
|
logger.info("onMessage: message = " + message);
|
||||||
// 解析收到的 JSON 消息
|
|
||||||
SignalMessage signal = objectMapper.readValue(message, SignalMessage.class);
|
|
||||||
String toUser = signal.getToUser();
|
|
||||||
Session targetSession = clients.get(toUser);
|
|
||||||
|
|
||||||
if (targetSession != null && targetSession.isOpen()) {
|
try {
|
||||||
// 转发信令(SDP, ICE, 拒绝/接受)
|
JsonNode jsonNode = objectMapper.readTree(message);
|
||||||
targetSession.getAsyncRemote().sendText(message);
|
int msgType = jsonNode.get("msg_type").asInt();
|
||||||
logger.info("onMessage: 信令类型 [" + signal.getType() + "] 从 " + userId + " 发往 " + toUser);
|
logger.info("onMessage: msgType = " + msgType);
|
||||||
} else {
|
String content = jsonNode.get("content").toString();
|
||||||
sendErrorMessage(session, "目标用户不在线: " + toUser);
|
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) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
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
|
@OnClose
|
||||||
public void onClose(@PathParam("userId") String userId) {
|
public void onClose(@PathParam("userId") String userId) {
|
||||||
clients.remove(userId);
|
clients.remove(userId);
|
||||||
|
|||||||
Reference in New Issue
Block a user