/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.voiceconf.red5;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.slf4j.Logger;
import org.bigbluebutton.voiceconf.sip.PeerNotFoundException;
import org.bigbluebutton.voiceconf.sip.SipPeerManager;
import org.red5.logging.Red5LoggerFactory;
import org.red5.server.adapter.MultiThreadedApplicationAdapter;
import org.red5.server.api.IConnection;
import org.red5.server.api.Red5;
import org.red5.server.api.scope.IScope;
import org.red5.server.api.service.IServiceCapableConnection;
import org.red5.server.api.stream.IBroadcastStream;
import org.red5.server.stream.ClientBroadcastStream;
import com.google.gson.Gson;
public class Application extends MultiThreadedApplicationAdapter {
private static Logger log = Red5LoggerFactory.getLogger(Application.class, "sip");
private SipPeerManager sipPeerManager;
private ClientConnectionManager clientConnManager;
private String sipServerHost = "localhost";
private String sipClientRtpIp = "";
private int sipPort = 5070;
private int startAudioPort = 3000;
private int stopAudioPort = 3029;
private String password = "secret";
private String username;
private CallStreamFactory callStreamFactory;
@Override
public boolean appStart(IScope scope) {
log.debug("VoiceConferenceApplication appStart[" + scope.getName() + "]");
super.setScope(scope);
callStreamFactory = new CallStreamFactory();
callStreamFactory.setScope(scope);
sipPeerManager.setCallStreamFactory(callStreamFactory);
sipPeerManager.setClientConnectionManager(clientConnManager);
sipPeerManager.createSipPeer("default", sipClientRtpIp, sipServerHost, sipPort, startAudioPort, stopAudioPort);
try {
sipPeerManager.register("default", username, password);
} catch (PeerNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return super.appStart(scope);
}
@Override
public void appStop(IScope scope) {
log.debug("VoiceConferenceApplication appStop[" + scope.getName() + "]");
sipPeerManager.destroyAllSessions();
super.appStop(scope);
}
@Override
public boolean appConnect(IConnection conn, Object[] params) {
String meetingId = ((String) params[0]).toString();
String userId = ((String) params[1]).toString();
String username = ((String) params[2]).toString();
String clientId = Red5.getConnectionLocal().getClient().getId();
String remoteHost = Red5.getConnectionLocal().getRemoteAddress();
int remotePort = Red5.getConnectionLocal().getRemotePort();
if ((userId == null) || ("".equals(userId))) userId = "unknown-userid";
if ((username == null) || ("".equals(username))) username = "UNKNOWN-CALLER";
Red5.getConnectionLocal().setAttribute("MEETING_ID", meetingId);
Red5.getConnectionLocal().setAttribute("USERID", userId);
Red5.getConnectionLocal().setAttribute("USERNAME", username);
log.info("{} [clientid={}] has connected to the voice conf app.", username + "[uid=" + userId + "]", clientId);
log.info("[clientid={}] connected from {}.", clientId, remoteHost + ":" + remotePort);
String connType = getConnectionType(Red5.getConnectionLocal().getType());
String userFullname = username;
String connId = Red5.getConnectionLocal().getSessionId();
Map<String, Object> logData = new HashMap<String, Object>();
logData.put("meetingId", meetingId);
logData.put("connType", connType);
logData.put("connId", connId);
logData.put("userId", userId);
logData.put("username", userFullname);
logData.put("event", "user_joining_bbb_voice");
logData.put("description", "User joining BBB Voice.");
Gson gson = new Gson();
String logStr = gson.toJson(logData);
log.info("User joining bbb-voice: data={}", logStr);
clientConnManager.createClient(clientId, userId, username, (IServiceCapableConnection) Red5.getConnectionLocal());
return super.appConnect(conn, params);
}
private String getConnectionType(String connType) {
if ("persistent".equals(connType.toLowerCase())) {
return "RTMP";
} else if("polling".equals(connType.toLowerCase())) {
return "RTMPT";
} else {
return connType.toUpperCase();
}
}
@Override
public void appDisconnect(IConnection conn) {
String clientId = Red5.getConnectionLocal().getClient().getId();
String userId = getUserId();
String username = getUsername();
String remoteHost = Red5.getConnectionLocal().getRemoteAddress();
int remotePort = Red5.getConnectionLocal().getRemotePort();
log.info("[clientid={}] disconnnected from {}.", clientId, remoteHost + ":" + remotePort);
log.debug("{} [clientid={}] is leaving the voice conf app. Removing from ConnectionManager.", username + "[uid=" + userId + "]", clientId);
String connType = getConnectionType(Red5.getConnectionLocal().getType());
String userFullname = username;
String connId = Red5.getConnectionLocal().getSessionId();
Map<String, Object> logData = new HashMap<String, Object>();
logData.put("meetingId", getMeetingId());
logData.put("connType", connType);
logData.put("connId", connId);
logData.put("userId", userId);
logData.put("username", userFullname);
logData.put("event", "user_leaving_bbb_voice");
logData.put("description", "User leaving BBB Voice.");
Gson gson = new Gson();
String logStr = gson.toJson(logData);
log.info("User leaving bbb-voice: data={}", logStr);
clientConnManager.removeClient(clientId);
String peerId = (String) Red5.getConnectionLocal().getAttribute("VOICE_CONF_PEER");
if (peerId != null) {
try {
log.debug("Forcing hang up {} [clientid={}] in case the user is still in the conference.", username + "[uid=" + userId + "]", clientId);
sipPeerManager.hangup(peerId, clientId);
} catch (PeerNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
super.appDisconnect(conn);
}
@Override
public void streamPublishStart(IBroadcastStream stream) {
String clientId = Red5.getConnectionLocal().getClient().getId();
String userid = getUserId();
String username = getUsername();
log.debug("{} has started publishing stream [{}]", username + "[uid=" + userid + "][clientid=" + clientId + "]", stream.getPublishedName());
System.out.println("streamPublishStart: " + stream.getPublishedName());
IConnection conn = Red5.getConnectionLocal();
String peerId = (String) conn.getAttribute("VOICE_CONF_PEER");
if (peerId != null) {
super.streamPublishStart(stream);
sipPeerManager.startTalkStream(peerId, clientId, stream, conn.getScope());
// recordStream(stream);
}
}
/**
* A hook to record a sample stream. A file is written in webapps/sip/streams/
* @param stream
*/
private void recordStream(IBroadcastStream stream) {
IConnection conn = Red5.getConnectionLocal();
String streamName = stream.getPublishedName();
try {
ClientBroadcastStream cstream = (ClientBroadcastStream) this.getBroadcastStream(conn.getScope(), stream.getPublishedName() );
cstream.saveAs(streamName, false);
} catch(Exception e) {
System.out.println("ERROR while recording stream " + e.getMessage());
e.printStackTrace();
}
}
@Override
public void streamBroadcastClose(IBroadcastStream stream) {
String clientId = Red5.getConnectionLocal().getClient().getId();
String userid = getUserId();
String username = getUsername();
log.debug("{} has stopped publishing stream [{}]", username + "[uid=" + userid + "][clientid=" + clientId + "]", stream.getPublishedName());
IConnection conn = Red5.getConnectionLocal();
String peerId = (String) conn.getAttribute("VOICE_CONF_PEER");
if (peerId != null) {
sipPeerManager.stopTalkStream(peerId, clientId, stream, conn.getScope());
super.streamBroadcastClose(stream);
}
}
public Set<String> getStreams() {
IConnection conn = Red5.getConnectionLocal();
return getBroadcastStreamNames( conn.getScope() );
}
public void onPing() {
log.debug( "Red5SIP Ping" );
}
public void setSipServerHost(String h) {
sipServerHost = h.trim();
}
public void setSipClientRtpIp(String ipAddr) {
this.sipClientRtpIp = ipAddr.trim();
}
public void setUsername(String un) {
this.username = un.trim();
}
public void setPassword(String pw) {
this.password = pw.trim();
}
public void setSipPort(int sipPort) {
this.sipPort = sipPort;
}
public void setStartAudioPort(int startRTPPort) {
this.startAudioPort = startRTPPort;
}
public void setStopAudioPort(int stopRTPPort) {
this.stopAudioPort = stopRTPPort;
}
public void setSipPeerManager(SipPeerManager spm) {
sipPeerManager = spm;
}
public void setClientConnectionManager(ClientConnectionManager ccm) {
clientConnManager = ccm;
}
private String getUserId() {
String userid = (String) Red5.getConnectionLocal().getAttribute("USERID");
if ((userid == null) || ("".equals(userid))) userid = "unknown-userid";
return userid;
}
private String getMeetingId() {
String meetingId = (String) Red5.getConnectionLocal().getAttribute("MEETING_ID");
if ((meetingId == null) || ("".equals(meetingId))) meetingId = "unknown-meetingid";
return meetingId;
}
private String getUsername() {
String username = (String) Red5.getConnectionLocal().getAttribute("USERNAME");
if ((username == null) || ("".equals(username))) username = "UNKNOWN-CALLER";
return username;
}
}