/*
0 * RED5 Open Source Flash Server - http://code.google.com/p/red5/
*
* Copyright 2006-2012 by respective authors (see below). All rights reserved.
*
* 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.red5.server.net.mrtmp;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolCodecFactory;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filter.logging.LoggingFilter;
import org.red5.server.api.IConnection;
import org.red5.server.net.rtmp.IRTMPHandler;
import org.red5.server.net.rtmp.RTMPConnection;
import org.red5.server.net.rtmp.RTMPOriginConnection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author Steven Gong (steven.gong@gmail.com)
*/
public class OriginMRTMPHandler extends IoHandlerAdapter {
private Logger log = LoggerFactory.getLogger(OriginMRTMPHandler.class);
private IMRTMPOriginManager mrtmpManager;
private ProtocolCodecFactory codecFactory;
private IRTMPHandler handler;
private Map<Integer, RTMPOriginConnection> dynConnMap =
new HashMap<Integer, RTMPOriginConnection>();
private Map<StaticConnId, RTMPOriginConnection> statConnMap =
new HashMap<StaticConnId, RTMPOriginConnection>();
private ReadWriteLock lock = new ReentrantReadWriteLock();
public void setMrtmpManager(IMRTMPOriginManager mrtmpManager) {
this.mrtmpManager = mrtmpManager;
}
public void setHandler(IRTMPHandler handler) {
this.handler = handler;
}
public void setCodecFactory(ProtocolCodecFactory codecFactory) {
this.codecFactory = codecFactory;
}
@Override
public void messageReceived(IoSession session, Object message) throws Exception {
MRTMPPacket packet = (MRTMPPacket) message;
MRTMPPacket.Header header = packet.getHeader();
MRTMPPacket.Body body = packet.getBody();
if (log.isDebugEnabled()) {
log.debug(packet.toString());
}
int clientId = header.getClientId();
int sessionId = getSessionId(session);
MRTMPOriginConnection mrtmpConn = (MRTMPOriginConnection) session.getAttribute(MRTMPOriginConnection.ORIGIN_CONNECTION_KEY);
RTMPOriginConnection conn = null;
switch (packet.getHeader().getType()) {
case MRTMPPacket.CONNECT:
lock.writeLock().lock();
try {
if (header.isDynamic()) {
if (!dynConnMap.containsKey(clientId)) {
conn = new RTMPOriginConnection(
IConnection.POLLING,
header.getClientId()
);
conn.setMrtmpManager(mrtmpManager);
conn.setHandler(this);
dynConnMap.put(clientId, conn);
} else {
log.warn("Open an already existing RTMPT origin connection!");
}
} else {
StaticConnId connId = new StaticConnId();
connId.clientId = header.getClientId();
connId.sessionId = sessionId;
if (!statConnMap.containsKey(connId)) {
conn = new RTMPOriginConnection(
IConnection.PERSISTENT,
header.getClientId(),
sessionId
);
conn.setMrtmpManager(mrtmpManager);
conn.setHandler(this);
statConnMap.put(connId, conn);
} else {
log.warn("Open an already existing RTMP origin connection!");
}
}
} finally {
lock.writeLock().unlock();
}
break;
case MRTMPPacket.CLOSE:
case MRTMPPacket.RTMP:
lock.readLock().lock();
try {
if (header.isDynamic()) {
conn = dynConnMap.get(clientId);
} else {
StaticConnId connId = new StaticConnId();
connId.clientId = header.getClientId();
connId.sessionId = sessionId;
conn = statConnMap.get(connId);
}
} finally {
lock.readLock().unlock();
}
if (conn != null) {
if (packet.getHeader().getType() == MRTMPPacket.CLOSE) {
closeConnection(conn);
conn = null;
} else {
MRTMPPacket.RTMPBody rtmpBody = (MRTMPPacket.RTMPBody) body;
handler.messageReceived((RTMPConnection)conn, packet);
}
} else {
log.warn("Handle on a non-existent origin connection!");
}
break;
default:
log.warn("Unknown mrtmp packet received!");
break;
}
if (conn != null) {
mrtmpManager.associate(conn, mrtmpConn);
}
}
@Override
public void messageSent(IoSession session, Object message) throws Exception {
MRTMPPacket packet = (MRTMPPacket) message;
if (packet.getHeader().getType() != MRTMPPacket.RTMP) {
return;
}
MRTMPPacket.Header header = packet.getHeader();
//MRTMPPacket.Body body = packet.getBody();
int clientId = header.getClientId();
int sessionId = getSessionId(session);
@SuppressWarnings("unused")
RTMPOriginConnection conn = null;
lock.readLock().lock();
try {
if (header.isDynamic()) {
conn = dynConnMap.get(clientId);
} else {
StaticConnId connId = new StaticConnId();
connId.clientId = header.getClientId();
connId.sessionId = sessionId;
conn = statConnMap.get(connId);
}
} finally {
lock.readLock().unlock();
}
//if (conn != null) {
//MRTMPPacket.RTMPBody rtmpBody = (MRTMPPacket.RTMPBody) body;
//final int channelId = rtmpBody.getRtmpPacket().getHeader().getChannelId();
//final IClientStream stream = conn.getStreamByChannelId(channelId);
// XXX we'd better use new event model for notification
//if (stream != null && (stream instanceof PlaylistSubscriberStream)) {
// ((PlaylistSubscriberStream) stream).written(rtmpBody.getRtmpPacket().getMessage());
//}
//} else {
//log.warn("Handle on a non-existent origin connection!");
//}
}
@Override
public void sessionClosed(IoSession session) throws Exception {
MRTMPOriginConnection conn = (MRTMPOriginConnection) session.getAttribute(MRTMPOriginConnection.ORIGIN_CONNECTION_KEY);
// TODO we need to handle the case when all MRTMP connection is broken.
mrtmpManager.unregisterConnection(conn);
conn.close();
log.debug("Closed MRTMP Origin Connection " + conn);
}
@Override
public void sessionCreated(IoSession session) throws Exception {
MRTMPOriginConnection conn = new MRTMPOriginConnection();
conn.setIoSession(session);
mrtmpManager.registerConnection(conn);
session.setAttribute(MRTMPOriginConnection.ORIGIN_CONNECTION_KEY, conn);
session.getFilterChain().addFirst("protocolFilter",
new ProtocolCodecFilter(this.codecFactory));
if (log.isDebugEnabled()) {
session.getFilterChain().addLast("logger", new LoggingFilter());
}
log.debug("Created MRTMP Origin Connection {}", conn);
}
public void closeConnection(RTMPOriginConnection conn) {
boolean dynamic = !conn.getType().equals(IConnection.PERSISTENT);
lock.writeLock().lock();
try {
if (dynamic) {
if (dynConnMap.containsKey(conn.getId())) {
dynConnMap.remove(conn.getId());
conn.realClose();
} else {
log.warn("Close a non-existent origin connection!");
}
} else {
StaticConnId connId = new StaticConnId();
connId.clientId = conn.getId();
connId.sessionId = conn.getIoSessionId();
if (statConnMap.containsKey(connId)) {
statConnMap.remove(connId);
conn.realClose();
} else {
log.warn("Close a non-existent origin connection!");
}
}
} finally {
lock.writeLock().unlock();
}
mrtmpManager.dissociate(conn);
}
protected int getSessionId(IoSession session) {
MRTMPOriginConnection mrtmpConn = (MRTMPOriginConnection) session.getAttribute(MRTMPOriginConnection.ORIGIN_CONNECTION_KEY);
if (mrtmpConn != null) {
return mrtmpConn.hashCode();
}
return 0;
}
private static class StaticConnId {
public int sessionId;
public int clientId;
@Override
public int hashCode() {
final int PRIME = 31;
int result = 1;
result = PRIME * result + clientId;
result = PRIME * result + sessionId;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final StaticConnId other = (StaticConnId) obj;
if (clientId != other.clientId)
return false;
if (sessionId != other.sessionId)
return false;
return true;
}
}
}