/*
* (C) Copyright 2014 Kurento (http://kurento.org/)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.kurento.basicroom;
import java.io.IOException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.annotation.PreDestroy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject;
/**
* @author Ivan Gracia (izanmail@gmail.com)
* @author Micael Gallego (micael.gallego@gmail.com)
* @since 1.0.0
*/
public class RoomHandler extends TextWebSocketHandler {
private static final String USER = "user";
private static final Logger log = LoggerFactory.getLogger(RoomHandler.class);
private static final Gson gson = new GsonBuilder().create();
private static final String HANDLER_THREAD_NAME = "handler";
private static final ExecutorService executor = Executors.newFixedThreadPool(10);
@Autowired
private RoomManager roomManager;
@PreDestroy
public void close() {
executor.shutdown();
}
@Override
public void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
final JsonObject jsonMessage = gson.fromJson(message.getPayload(), JsonObject.class);
// FIXME: Hack to ignore real userName sent by browser
// if (jsonMessage.get("id").getAsString().equals("joinRoom")) {
// jsonMessage.add("name", new JsonPrimitive(UUID.randomUUID()
// .toString()));
// }
final RoomParticipant user = (RoomParticipant) session.getAttributes().get(USER);
if (user != null) {
log.debug("Incoming message from user '{}': {}", user.getName(), jsonMessage);
} else {
log.debug("Incoming message from new user: {}", jsonMessage);
}
switch (jsonMessage.get("id").getAsString()) {
case "receiveVideoFrom":
executor.submit(new Runnable() {
@Override
public void run() {
updateThreadName("rv:" + user.getName());
receiveVideoFrom(user, jsonMessage);
updateThreadName(HANDLER_THREAD_NAME);
}
});
break;
case "joinRoom":
joinRoom(jsonMessage, session);
break;
case "leaveRoom":
leaveRoom(user);
break;
default:
break;
}
updateThreadName(HANDLER_THREAD_NAME);
}
private void receiveVideoFrom(final RoomParticipant user, final JsonObject jsonMessage) {
final String senderName = jsonMessage.get("sender").getAsString();
final String sdpOffer = jsonMessage.get("sdpOffer").getAsString();
Room room = user.getRoom();
final RoomParticipant sender = room.getParticipant(senderName);
if (sender != null) {
user.receiveVideoFrom(sender, sdpOffer);
} else {
log.warn("PARTICIPANT {}: Requesting send video for user {} in room {} but it is not found",
user.getName(), senderName, user.getRoom().getName());
}
}
private void joinRoom(JsonObject jsonMessage, final WebSocketSession session)
throws IOException, InterruptedException, ExecutionException {
final String roomName = jsonMessage.get("room").getAsString();
final String userName = jsonMessage.get("name").getAsString();
updateThreadName(userName);
log.debug("PARTICIPANT {}: trying to join room {}", userName, roomName);
final Room room = roomManager.getRoom(roomName);
if (!room.isClosed()) {
room.execute(new Runnable() {
@Override
public void run() {
updateThreadName("r>" + userName);
final RoomParticipant user = room.join(userName, session);
session.getAttributes().put(USER, user);
updateThreadName("r>" + HANDLER_THREAD_NAME);
}
});
} else {
log.warn("Trying to join from room {} but it is closed", room.getName());
}
}
private void leaveRoom(final RoomParticipant user)
throws IOException, InterruptedException, ExecutionException {
final Room room = user.getRoom();
final String threadName = Thread.currentThread().getName();
if (!room.isClosed()) {
room.execute(new Runnable() {
@Override
public void run() {
updateThreadName("room>" + threadName);
room.leave(user);
if (room.getParticipants().isEmpty()) {
roomManager.removeRoom(room);
}
updateThreadName("room>" + HANDLER_THREAD_NAME);
}
});
} else {
log.warn("Trying to leave from room {} but it is closed", room.getName());
}
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
RoomParticipant user = (RoomParticipant) session.getAttributes().get(USER);
if (user != null) {
updateThreadName(user.getName() + "|wsclosed");
leaveRoom(user);
updateThreadName(HANDLER_THREAD_NAME);
}
}
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
RoomParticipant user = (RoomParticipant) session.getAttributes().get(USER);
if (user != null && !user.isClosed()) {
log.warn("Transport error", exception);
}
}
private void updateThreadName(final String name) {
Thread.currentThread().setName("user:" + name);
}
}