
io.antmedia.websocket.WebSocketCommunityHandler Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ant-media-server Show documentation
Show all versions of ant-media-server Show documentation
Ant Media Server supports RTMP, RTSP, MP4, HLS, WebRTC, Adaptive Streaming, etc.
package io.antmedia.websocket;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.webrtc.IceCandidate;
import org.webrtc.SessionDescription;
import org.webrtc.SessionDescription.Type;
import io.antmedia.AntMediaApplicationAdapter;
import io.antmedia.AppSettings;
import io.antmedia.StreamIdValidator;
import io.antmedia.datastore.db.types.Broadcast;
import io.antmedia.muxer.IAntMediaStreamHandler;
import io.antmedia.settings.ServerSettings;
import io.antmedia.webrtc.adaptor.RTMPAdaptor;
import jakarta.websocket.Session;
public class WebSocketCommunityHandler {
public static final String WEBRTC_VERTX_BEAN_NAME = "webRTCVertx";
private static Logger logger = LoggerFactory.getLogger(WebSocketCommunityHandler.class);
protected AppSettings appSettings;
private ApplicationContext appContext;
protected Session session;
private String appName;
private AntMediaApplicationAdapter appAdaptor;
protected String userAgent = "N/A";
public WebSocketCommunityHandler(ApplicationContext appContext, Session session) {
this.appContext = appContext;
this.session = session;
appSettings = (AppSettings) getAppContext().getBean(AppSettings.BEAN_NAME);
appAdaptor = ((AntMediaApplicationAdapter)appContext.getBean(AntMediaApplicationAdapter.BEAN_NAME));
appName = appAdaptor.getScope().getName();
}
public void onClose(Session session) {
RTMPAdaptor connectionContext = (RTMPAdaptor) session.getUserProperties().get(session.getId());
if (connectionContext != null) {
connectionContext.stop();
}
}
public void onError(Session session, Throwable throwable) {
//not used for now
}
public void onMessage(Session session, String message) {
//json parser is not thread-safe
JSONParser jsonParser = new JSONParser();
try {
if (message == null) {
logger.error("Received message null for session id: {}" , session.getId());
return;
}
JSONObject jsonObject = (JSONObject) jsonParser.parse(message);
String cmd = (String) jsonObject.get(WebSocketConstants.COMMAND);
if (cmd == null) {
logger.error("Received message does not contain any command for session id: {}" , session.getId());
return;
}
final String streamId = (String) jsonObject.get(WebSocketConstants.STREAM_ID);
if ((streamId == null || streamId.isEmpty())
&& !cmd.equals(WebSocketConstants.PING_COMMAND))
{
sendNoStreamIdSpecifiedError(session);
return;
}
if(!StreamIdValidator.isStreamIdValid(streamId)) {
sendInvalidStreamNameError(streamId, session);
return;
}
if (cmd.equals(WebSocketConstants.PUBLISH_COMMAND))
{
Broadcast broadcast = appAdaptor.getDataStore().get(streamId);
if (broadcast != null) {
String status = broadcast.getStatus();
if (status.endsWith(IAntMediaStreamHandler.BROADCAST_STATUS_BROADCASTING)
||
status.endsWith(IAntMediaStreamHandler.BROADCAST_STATUS_PREPARING))
{
logger.error("Sending stream id in use error for stream:{} session:{}", streamId, session.getId());
sendStreamIdInUse(streamId, session);
return;
}
}
//Get if enableVideo is true or false
boolean enableVideo = jsonObject.containsKey(WebSocketConstants.VIDEO) ? (boolean) jsonObject.get(WebSocketConstants.VIDEO) : true;
//audio is by default true
//get scope and use its name
startRTMPAdaptor(session, streamId, enableVideo);
}
else if (cmd.equals(WebSocketConstants.TAKE_CONFIGURATION_COMMAND))
{
RTMPAdaptor connectionContext = (RTMPAdaptor) session.getUserProperties().get(session.getId());
String typeString = (String)jsonObject.get(WebSocketConstants.TYPE);
String sdpDescription = (String)jsonObject.get(WebSocketConstants.SDP);
setRemoteDescription(connectionContext, typeString, sdpDescription, streamId);
}
else if (cmd.equals(WebSocketConstants.TAKE_CANDIDATE_COMMAND)) {
RTMPAdaptor connectionContext = (RTMPAdaptor) session.getUserProperties().get(session.getId());
String sdpMid = (String) jsonObject.get(WebSocketConstants.CANDIDATE_ID);
String sdp = (String) jsonObject.get(WebSocketConstants.CANDIDATE_SDP);
long sdpMLineIndex = (long)jsonObject.get(WebSocketConstants.CANDIDATE_LABEL);
addICECandidate(streamId, connectionContext, ((sdpMid != null) ? sdpMid : "0"), sdp, sdpMLineIndex);
}
else if (cmd.equals(WebSocketConstants.STOP_COMMAND)) {
RTMPAdaptor connectionContext = (RTMPAdaptor) session.getUserProperties().get(session.getId());
if (connectionContext != null) {
connectionContext.stop();
}
else {
logger.warn("Connection context is null for stop. Wrong message order for stream: {}", streamId);
}
}
else if (cmd.equals(WebSocketConstants.PING_COMMAND)) {
sendPongMessage(session);
}
else if (cmd.equals(WebSocketConstants.GET_STREAM_INFO_COMMAND) || cmd.equals(WebSocketConstants.PLAY_COMMAND))
{
sendNotFoundJSON(streamId, session);
}
}
catch (Exception e) {
logger.error(ExceptionUtils.getStackTrace(e));
}
}
private void startRTMPAdaptor(Session session, final String streamId, boolean enableVideo) {
int rtmpPort = appAdaptor.getServerSettings().getRtmpPort();
//get scope and use its name
String outputURL = "rtmp://127.0.0.1" + ":" + rtmpPort +"/"+ appName +"/" + streamId;
RTMPAdaptor connectionContext = getNewRTMPAdaptor(outputURL, appSettings.getHeightRtmpForwarding());
session.getUserProperties().put(session.getId(), connectionContext);
connectionContext.setSession(session);
connectionContext.setStreamId(streamId);
connectionContext.setPortRange(appSettings.getWebRTCPortRangeMin(), appSettings.getWebRTCPortRangeMax());
connectionContext.setStunServerUri(appSettings.getStunServerURI(), appSettings.getTurnServerUsername(), appSettings.getTurnServerCredential());
connectionContext.setTcpCandidatesEnabled(appSettings.isWebRTCTcpCandidatesEnabled());
connectionContext.setEnableVideo(enableVideo);
connectionContext.start();
}
public RTMPAdaptor getNewRTMPAdaptor(String outputURL, int height) {
return new RTMPAdaptor(outputURL, this, height, "flv");
}
public void addICECandidate(final String streamId, RTMPAdaptor connectionContext, String sdpMid, String sdp,
long sdpMLineIndex) {
if (connectionContext != null) {
IceCandidate iceCandidate = new IceCandidate(sdpMid, (int)sdpMLineIndex, sdp);
connectionContext.addIceCandidate(iceCandidate);
}
else {
logger.warn("Connection context is null for take candidate. Wrong message order for stream: {}", streamId);
}
}
private void setRemoteDescription(RTMPAdaptor connectionContext, String typeString, String sdpDescription, String streamId) {
if (connectionContext != null) {
SessionDescription.Type type;
if ("offer".equals(typeString)) {
type = Type.OFFER;
logger.info("received sdp type is offer {}", streamId);
}
else {
type = Type.ANSWER;
logger.info("received sdp type is answer {}", streamId);
}
SessionDescription sdp = new SessionDescription(type, sdpDescription);
connectionContext.setRemoteDescription(sdp);
}
else {
logger.warn("Connection context is null. Wrong message order for stream: {}", streamId);
}
}
@SuppressWarnings("unchecked")
public void sendSDPConfiguration(String description, String type, String streamId, Session session, Map midSidMap, String linkedSessionForSignaling, String subscriberId) {
sendMessage(getSDPConfigurationJSON (description, type, streamId, midSidMap, linkedSessionForSignaling, subscriberId), session);
}
@SuppressWarnings("unchecked")
public void sendPublishStartedMessage(String streamId, Session session, String roomName, String subscriberId) {
JSONObject jsonObj = new JSONObject();
jsonObj.put(WebSocketConstants.COMMAND, WebSocketConstants.NOTIFICATION_COMMAND);
jsonObj.put(WebSocketConstants.DEFINITION, WebSocketConstants.PUBLISH_STARTED);
jsonObj.put(WebSocketConstants.STREAM_ID, streamId);
if(roomName != null) {
jsonObj.put(WebSocketConstants.ATTR_ROOM_NAME, roomName); //keep it for compatibility
jsonObj.put(WebSocketConstants.ROOM, roomName);
}
jsonObj.put(WebSocketConstants.SUBSCRIBER_ID, subscriberId);
sendMessage(jsonObj, session);
}
public void sendStreamIdInUse(String streamId, Session session) {
JSONObject jsonResponse = new JSONObject();
jsonResponse.put(WebSocketConstants.COMMAND, WebSocketConstants.ERROR_COMMAND);
jsonResponse.put(WebSocketConstants.DEFINITION, WebSocketConstants.STREAM_ID_IN_USE);
jsonResponse.put(WebSocketConstants.STREAM_ID, streamId);
sendMessage(jsonResponse, session);
}
@SuppressWarnings("unchecked")
public void sendPongMessage(Session session) {
JSONObject jsonResponseObject = new JSONObject();
jsonResponseObject.put(WebSocketConstants.COMMAND, WebSocketConstants.PONG_COMMAND);
sendMessage(jsonResponseObject, session);
}
@SuppressWarnings("unchecked")
public void sendPublishFinishedMessage(String streamId, Session session, String subscriberId) {
JSONObject jsonObject = new JSONObject();
jsonObject.put(WebSocketConstants.COMMAND, WebSocketConstants.NOTIFICATION_COMMAND);
jsonObject.put(WebSocketConstants.DEFINITION, WebSocketConstants.PUBLISH_FINISHED);
jsonObject.put(WebSocketConstants.STREAM_ID, streamId);
jsonObject.put(WebSocketConstants.SUBSCRIBER_ID, subscriberId);
sendMessage(jsonObject, session);
}
@SuppressWarnings("unchecked")
public void sendStartMessage(String streamId, Session session, String subscriberId)
{
JSONObject jsonObject = new JSONObject();
jsonObject.put(WebSocketConstants.COMMAND, WebSocketConstants.START_COMMAND);
jsonObject.put(WebSocketConstants.STREAM_ID, streamId);
jsonObject.put(WebSocketConstants.SUBSCRIBER_ID, subscriberId);
sendMessage(jsonObject, session);
}
@SuppressWarnings("unchecked")
public final void sendNoStreamIdSpecifiedError(Session session) {
JSONObject jsonResponse = new JSONObject();
jsonResponse.put(WebSocketConstants.COMMAND, WebSocketConstants.ERROR_COMMAND);
jsonResponse.put(WebSocketConstants.DEFINITION, WebSocketConstants.NO_STREAM_ID_SPECIFIED);
sendMessage(jsonResponse, session);
}
@SuppressWarnings("unchecked")
public void sendTakeCandidateMessage(long sdpMLineIndex, String sdpMid, String sdp, String streamId, Session session, String linkedSessionForSignaling, String subscriberId)
{
sendMessage(getTakeCandidateJSON(sdpMLineIndex, sdpMid, sdp, streamId, linkedSessionForSignaling, subscriberId), session);
}
@SuppressWarnings("unchecked")
public void sendMessage(JSONObject message, final Session session) {
synchronized (this) {
if (session.isOpen()) {
try {
session.getBasicRemote().sendText(message.toJSONString());
}
catch (Exception e) {
//capture all exceptions because some unexpected events may happen it causes some internal errors
String exceptioMessage = e.getMessage();
//ignore following messages
if (exceptioMessage == null || !exceptioMessage.contains("WebSocket session has been closed"))
{
logger.error(ExceptionUtils.getStackTrace(e));
}
}
}
}
}
@SuppressWarnings("unchecked")
public void sendRoomNotActiveInformation(String roomId) {
JSONObject jsonResponse = new JSONObject();
jsonResponse.put(WebSocketConstants.COMMAND, WebSocketConstants.ERROR_COMMAND);
jsonResponse.put(WebSocketConstants.DEFINITION, WebSocketConstants.ROOM_NOT_ACTIVE);
jsonResponse.put(WebSocketConstants.ROOM, roomId);
sendMessage(jsonResponse, session);
}
/**
*
* @param streamIdNameMap this is the map that keys are stream ids and values are stream names
* @param roomId is the id of the room
* @param subscriberId
*/
public void sendRoomInformation(Map streamIdNameMap , String roomId)
{
JSONObject jsObject = new JSONObject();
JSONArray jsonStreamIdArray = new JSONArray();
JSONArray jsonStreamListArray = new JSONArray();
prepareStreamListJSON(streamIdNameMap, jsonStreamIdArray, jsonStreamListArray, new HashMap());
jsObject.put(WebSocketConstants.COMMAND, WebSocketConstants.ROOM_INFORMATION_NOTIFICATION);
jsObject.put(WebSocketConstants.STREAMS_IN_ROOM, jsonStreamIdArray);
//This field is deprecated. Use STREAM_LIST_IN_ROOM
jsObject.put(WebSocketConstants.STREAM_LIST_IN_ROOM, jsonStreamListArray);
jsObject.put(WebSocketConstants.ATTR_ROOM_NAME, roomId);
jsObject.put(WebSocketConstants.ROOM, roomId);
sendMessage(jsObject, session);
}
private void prepareStreamListJSON(Map streamIdNameMap, JSONArray jsonStreamIdArray,
JSONArray jsonStreamListArray, HashMap streamMetaDataMap) {
if(streamIdNameMap != null) {
for (Map.Entry e : streamIdNameMap.entrySet()) {
jsonStreamIdArray.add(e.getKey());
JSONObject jsStreamObject = new JSONObject();
jsStreamObject.put(WebSocketConstants.STREAM_ID, e.getKey());
jsStreamObject.put(WebSocketConstants.STREAM_NAME, e.getValue());
jsStreamObject.put(WebSocketConstants.META_DATA, streamMetaDataMap.get(e.getKey()));
jsonStreamListArray.add(jsStreamObject);
}
}
}
public void sendJoinedRoomMessage(String room, String newStreamId, Map streamIdNameMap, HashMap streamMetaDataMap ) {
JSONObject jsonResponse = new JSONObject();
JSONArray jsonStreamIdArray = new JSONArray();
JSONArray jsonStreamListArray = new JSONArray();
prepareStreamListJSON(streamIdNameMap, jsonStreamIdArray, jsonStreamListArray, streamMetaDataMap);
jsonResponse.put(WebSocketConstants.COMMAND, WebSocketConstants.NOTIFICATION_COMMAND);
jsonResponse.put(WebSocketConstants.DEFINITION, WebSocketConstants.JOINED_THE_ROOM);
jsonResponse.put(WebSocketConstants.STREAM_ID, newStreamId);
//This field is deprecated. Use STREAM_LIST_IN_ROOM
jsonResponse.put(WebSocketConstants.STREAMS_IN_ROOM, jsonStreamIdArray);
jsonResponse.put(WebSocketConstants.STREAM_LIST_IN_ROOM, jsonStreamListArray);
jsonResponse.put(WebSocketConstants.ATTR_ROOM_NAME, room);
jsonResponse.put(WebSocketConstants.ROOM, room);
jsonResponse.put(WebSocketConstants.MAX_TRACK_COUNT, appSettings.getMaxVideoTrackCount());
sendMessage(jsonResponse, session);
}
public static JSONObject getTakeCandidateJSON(long sdpMLineIndex, String sdpMid, String sdp, String streamId, String linkedSessionForSignaling, String subscriberId) {
JSONObject jsonObject = new JSONObject();
jsonObject.put(WebSocketConstants.COMMAND, WebSocketConstants.TAKE_CANDIDATE_COMMAND);
jsonObject.put(WebSocketConstants.CANDIDATE_LABEL, sdpMLineIndex);
jsonObject.put(WebSocketConstants.CANDIDATE_ID, sdpMid);
jsonObject.put(WebSocketConstants.CANDIDATE_SDP, sdp);
jsonObject.put(WebSocketConstants.STREAM_ID, streamId);
jsonObject.put(WebSocketConstants.LINK_SESSION, linkedSessionForSignaling);
jsonObject.put(WebSocketConstants.SUBSCRIBER_ID, subscriberId);
return jsonObject;
}
public static JSONObject getSDPConfigurationJSON(String description, String type, String streamId, Map midSidMap, String linkedSessionForSignaling, String subscriberId) {
JSONObject jsonResponseObject = new JSONObject();
jsonResponseObject.put(WebSocketConstants.COMMAND, WebSocketConstants.TAKE_CONFIGURATION_COMMAND);
jsonResponseObject.put(WebSocketConstants.SDP, description);
jsonResponseObject.put(WebSocketConstants.TYPE, type);
jsonResponseObject.put(WebSocketConstants.STREAM_ID, streamId);
jsonResponseObject.put(WebSocketConstants.LINK_SESSION, linkedSessionForSignaling);
jsonResponseObject.put(WebSocketConstants.SUBSCRIBER_ID, subscriberId);
if(midSidMap != null) {
JSONObject jsonIdMappingObject = new JSONObject();
for (Entry entry : midSidMap.entrySet()) {
jsonIdMappingObject.put(entry.getKey(), entry.getValue());
}
jsonResponseObject.put(WebSocketConstants.ID_MAPPING, jsonIdMappingObject);
}
return jsonResponseObject;
}
@SuppressWarnings("unchecked")
public void sendInvalidStreamNameError(String streamId, Session session) {
JSONObject jsonResponse = new JSONObject();
jsonResponse.put(WebSocketConstants.COMMAND, WebSocketConstants.ERROR_COMMAND);
jsonResponse.put(WebSocketConstants.DEFINITION, WebSocketConstants.INVALID_STREAM_NAME);
jsonResponse.put(WebSocketConstants.STREAM_ID, streamId);
sendMessage(jsonResponse, session);
}
public ApplicationContext getAppContext() {
return appContext;
}
public void setAppContext(ApplicationContext appContext) {
this.appContext = appContext;
}
public void setAppAdaptor(AntMediaApplicationAdapter appAdaptor) {
this.appAdaptor = appAdaptor;
}
public void sendRemoteDescriptionSetFailure(Session session, String streamId, String subscriberId) {
JSONObject jsonObject = new JSONObject();
jsonObject.put(WebSocketConstants.COMMAND, WebSocketConstants.ERROR_COMMAND);
jsonObject.put(WebSocketConstants.DEFINITION, WebSocketConstants.NOT_SET_REMOTE_DESCRIPTION);
jsonObject.put(WebSocketConstants.STREAM_ID, streamId);
jsonObject.put(WebSocketConstants.SUBSCRIBER_ID, subscriberId);
sendMessage(jsonObject, session);
}
public void sendLocalDescriptionSetFailure(Session session, String streamId, String subscriberId) {
JSONObject jsonObject = new JSONObject();
jsonObject.put(WebSocketConstants.COMMAND, WebSocketConstants.ERROR_COMMAND);
jsonObject.put(WebSocketConstants.DEFINITION, WebSocketConstants.NOT_SET_LOCAL_DESCRIPTION);
jsonObject.put(WebSocketConstants.STREAM_ID, streamId);
jsonObject.put(WebSocketConstants.SUBSCRIBER_ID, subscriberId);
sendMessage(jsonObject, session);
}
@SuppressWarnings("unchecked")
public void sendNotFoundJSON(String streamId, Session session) {
JSONObject jsonResponse = new JSONObject();
jsonResponse.put(WebSocketConstants.COMMAND, WebSocketConstants.ERROR_COMMAND);
jsonResponse.put(WebSocketConstants.ERROR_CODE, "404");
jsonResponse.put(WebSocketConstants.DEFINITION, WebSocketConstants.NO_STREAM_EXIST);
jsonResponse.put(WebSocketConstants.STREAM_ID, streamId);
sendMessage(jsonResponse, session);
}
public void sendServerError(String streamId, Session session) {
JSONObject jsonResponse = new JSONObject();
jsonResponse.put(WebSocketConstants.COMMAND, WebSocketConstants.ERROR_COMMAND);
jsonResponse.put(WebSocketConstants.DEFINITION, WebSocketConstants.SERVER_ERROR_CHECK_LOGS);
jsonResponse.put(WebSocketConstants.STREAM_ID, streamId);
sendMessage(jsonResponse, session);
}
public void setSession(Session session) {
this.session = session;
}
public String getUserAgent() {
return userAgent;
}
public void setUserAgent(String userAgent) {
this.userAgent = userAgent;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy