Merge branch 'master' of github.com:bigbluebutton/bigbluebutton into record-and-playback-feature

This commit is contained in:
Marco Calderon 2010-12-17 12:03:11 +00:00
commit d6c8b30e19
45 changed files with 1095 additions and 503 deletions

View File

@ -19,14 +19,12 @@
**/ **/
package org.bigbluebutton.voiceconf.red5; package org.bigbluebutton.voiceconf.red5;
import java.text.MessageFormat;
import java.util.List; import java.util.List;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.bigbluebutton.voiceconf.sip.PeerNotFoundException; import org.bigbluebutton.voiceconf.sip.PeerNotFoundException;
import org.bigbluebutton.voiceconf.sip.SipPeerManager; import org.bigbluebutton.voiceconf.sip.SipPeerManager;
import org.red5.logging.Red5LoggerFactory; import org.red5.logging.Red5LoggerFactory;
import org.red5.server.adapter.MultiThreadedApplicationAdapter; import org.red5.server.adapter.MultiThreadedApplicationAdapter;
import org.red5.server.api.IClient;
import org.red5.server.api.IConnection; import org.red5.server.api.IConnection;
import org.red5.server.api.IScope; import org.red5.server.api.IScope;
import org.red5.server.api.Red5; import org.red5.server.api.Red5;
@ -72,22 +70,43 @@ public class Application extends MultiThreadedApplicationAdapter {
} }
@Override @Override
public boolean appJoin(IClient client, IScope scope) { public boolean appConnect(IConnection conn, Object[] params) {
log.debug("VoiceConferenceApplication roomJoin[" + client.getId() + "]"); String userid = ((String) params[0]).toString();
clientConnManager.createClient(client.getId(), (IServiceCapableConnection) Red5.getConnectionLocal()); String username = ((String) params[1]).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("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);
clientConnManager.createClient(clientId, userid, username, (IServiceCapableConnection) Red5.getConnectionLocal());
return true; return true;
} }
@Override @Override
public void appLeave(IClient client, IScope scope) { public void appDisconnect(IConnection conn) {
log.debug("VoiceConferenceApplication roomLeave[" + client.getId() + "]"); String clientId = Red5.getConnectionLocal().getClient().getId();
clientConnManager.removeClient(client.getId()); String userid = getUserId();
log.debug( "Red5SIP Client closing client {}", client.getId()); 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);
clientConnManager.removeClient(clientId);
String peerId = (String) Red5.getConnectionLocal().getAttribute("VOICE_CONF_PEER"); String peerId = (String) Red5.getConnectionLocal().getAttribute("VOICE_CONF_PEER");
if (peerId != null) { if (peerId != null) {
try { try {
sipPeerManager.hangup(peerId, client.getId()); 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) { } catch (PeerNotFoundException e) {
// TODO Auto-generated catch block // TODO Auto-generated catch block
e.printStackTrace(); e.printStackTrace();
@ -97,13 +116,16 @@ public class Application extends MultiThreadedApplicationAdapter {
@Override @Override
public void streamPublishStart(IBroadcastStream stream) { public void streamPublishStart(IBroadcastStream stream) {
log.debug("streamPublishStart: {}; {}", stream, stream.getPublishedName()); 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()); System.out.println("streamPublishStart: " + stream.getPublishedName());
IConnection conn = Red5.getConnectionLocal(); IConnection conn = Red5.getConnectionLocal();
String peerId = (String) conn.getAttribute("VOICE_CONF_PEER"); String peerId = (String) conn.getAttribute("VOICE_CONF_PEER");
if (peerId != null) { if (peerId != null) {
super.streamPublishStart(stream); super.streamPublishStart(stream);
String clientId = conn.getClient().getId();
sipPeerManager.startTalkStream(peerId, clientId, stream, conn.getScope()); sipPeerManager.startTalkStream(peerId, clientId, stream, conn.getScope());
// recordStream(stream); // recordStream(stream);
} }
@ -128,12 +150,15 @@ public class Application extends MultiThreadedApplicationAdapter {
@Override @Override
public void streamBroadcastClose(IBroadcastStream stream) { public void streamBroadcastClose(IBroadcastStream stream) {
System.out.println("streamBroadcastClose: " + stream.getPublishedName()); 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(); IConnection conn = Red5.getConnectionLocal();
String peerId = (String) conn.getAttribute("VOICE_CONF_PEER"); String peerId = (String) conn.getAttribute("VOICE_CONF_PEER");
if (peerId != null) { if (peerId != null) {
super.streamPublishStart(stream); super.streamPublishStart(stream);
String clientId = conn.getClient().getId();
sipPeerManager.stopTalkStream(peerId, clientId, stream, conn.getScope()); sipPeerManager.stopTalkStream(peerId, clientId, stream, conn.getScope());
super.streamBroadcastClose(stream); super.streamBroadcastClose(stream);
} }
@ -179,4 +204,16 @@ public class Application extends MultiThreadedApplicationAdapter {
public void setClientConnectionManager(ClientConnectionManager ccm) { public void setClientConnectionManager(ClientConnectionManager ccm) {
clientConnManager = 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 getUsername() {
String username = (String) Red5.getConnectionLocal().getAttribute("USERNAME");
if ((username == null) || ("".equals(username))) username = "UNKNOWN-CALLER";
return username;
}
} }

View File

@ -28,10 +28,14 @@ private static Logger log = Red5LoggerFactory.getLogger(ClientConnection.class,
private final IServiceCapableConnection connection; private final IServiceCapableConnection connection;
private final String connId; private final String connId;
private final String userid;
private final String username;
public ClientConnection(String connId, IServiceCapableConnection connection) { public ClientConnection(String connId, String userid, String username, IServiceCapableConnection connection) {
this.connection = connection; this.connection = connection;
this.connId = connId; this.connId = connId;
this.userid = userid;
this.username = username;
} }
public String getConnId() { public String getConnId() {
@ -39,17 +43,17 @@ private static Logger log = Red5LoggerFactory.getLogger(ClientConnection.class,
} }
public void onJoinConferenceSuccess(String publishName, String playName, String codec) { public void onJoinConferenceSuccess(String publishName, String playName, String codec) {
log.debug( "SIP Call Connected" ); log.debug("Notify client that {} [{}] has joined the conference.", username, userid);
connection.invoke("successfullyJoinedVoiceConferenceCallback", new Object[] {publishName, playName, codec}); connection.invoke("successfullyJoinedVoiceConferenceCallback", new Object[] {publishName, playName, codec});
} }
public void onJoinConferenceFail() { public void onJoinConferenceFail() {
log.debug("onOutgoingCallFailed"); log.debug("Notify client that {} [{}] failed to join the conference.", username, userid);
connection.invoke("failedToJoinVoiceConferenceCallback", new Object[] {"onUaCallFailed"}); connection.invoke("failedToJoinVoiceConferenceCallback", new Object[] {"onUaCallFailed"});
} }
public void onLeaveConference() { public void onLeaveConference() {
log.debug("onCallClosed"); log.debug("Notify client that {} [{}] left the conference.", username, userid);
connection.invoke("disconnectedFromJoinVoiceConferenceCallback", new Object[] {"onUaCallClosed"}); connection.invoke("disconnectedFromJoinVoiceConferenceCallback", new Object[] {"onUaCallClosed"});
} }
} }

View File

@ -31,14 +31,18 @@ public class ClientConnectionManager {
private Map<String, ClientConnection> clients = new ConcurrentHashMap<String, ClientConnection>(); private Map<String, ClientConnection> clients = new ConcurrentHashMap<String, ClientConnection>();
public void createClient(String id, IServiceCapableConnection connection) { public void createClient(String id, String userid, String username, IServiceCapableConnection connection) {
ClientConnection cc = new ClientConnection(id, connection); ClientConnection cc = new ClientConnection(id, userid, username, connection);
clients.put(id, cc); clients.put(id, cc);
} }
public void removeClient(String id) { public void removeClient(String id) {
ClientConnection cc = clients.remove(id); ClientConnection cc = clients.remove(id);
if (cc == null) log.warn("Failed to remove client {}.", id); if (cc == null) {
log.warn("Failed to remove client {}.", id);
} else {
log.debug("Removed client {} from ConnectionManager.", id);
}
} }
public void joinConferenceSuccess(String clientId, String usertalkStream, String userListenStream, String codec) { public void joinConferenceSuccess(String clientId, String usertalkStream, String userListenStream, String codec) {
@ -46,7 +50,7 @@ public class ClientConnectionManager {
if (cc != null) { if (cc != null) {
cc.onJoinConferenceSuccess(usertalkStream, userListenStream, codec); cc.onJoinConferenceSuccess(usertalkStream, userListenStream, codec);
} else { } else {
log.warn("Can't find connection {}", clientId); log.warn("Can't find client {} to inform user that she has joined the conference.", clientId);
} }
} }
@ -55,7 +59,7 @@ public class ClientConnectionManager {
if (cc != null) { if (cc != null) {
cc.onJoinConferenceFail(); cc.onJoinConferenceFail();
} else { } else {
log.warn("Can't find connection {}", clientId); log.warn("Can't find client {} to inform user that she failed to join conference.", clientId);
} }
} }
@ -64,7 +68,7 @@ public class ClientConnectionManager {
if (cc != null) { if (cc != null) {
cc.onLeaveConference(); cc.onLeaveConference();
} else { } else {
log.warn("Can't find connection {}", clientId); log.warn("Can't find client {} to inform user that she has left the conference.", clientId);
} }
} }
} }

View File

@ -35,7 +35,11 @@ public class Service {
private MessageFormat callExtensionPattern = new MessageFormat("{0}"); private MessageFormat callExtensionPattern = new MessageFormat("{0}");
public Boolean call(String peerId, String callerName, String destination) { public Boolean call(String peerId, String callerName, String destination) {
log.debug("Joining voice conference " + destination); String clientId = Red5.getConnectionLocal().getClient().getId();
String userid = getUserId();
String username = getUsername();
log.debug("{} is requesting to join into the conference {}", username + "[uid=" + userid + "][clientid=" + clientId + "]", destination);
String extension = callExtensionPattern.format(new String[] { destination }); String extension = callExtensionPattern.format(new String[] { destination });
try { try {
sipPeerManager.call(peerId, getClientId(), callerName, extension); sipPeerManager.call(peerId, getClientId(), callerName, extension);
@ -48,7 +52,10 @@ public class Service {
} }
public Boolean hangup(String peerId) { public Boolean hangup(String peerId) {
log.debug("Red5SIP Hangup"); String clientId = Red5.getConnectionLocal().getClient().getId();
String userid = getUserId();
String username = getUsername();
log.debug("{} is requesting to hang up from the conference.", username + "[uid=" + userid + "][clientid=" + clientId + "]");
try { try {
sipPeerManager.hangup(peerId, getClientId()); sipPeerManager.hangup(peerId, getClientId());
return true; return true;
@ -70,4 +77,16 @@ public class Service {
public void setSipPeerManager(SipPeerManager sum) { public void setSipPeerManager(SipPeerManager sum) {
sipPeerManager = sum; sipPeerManager = sum;
} }
private String getUserId() {
String userid = (String) Red5.getConnectionLocal().getAttribute("USERID");
if ((userid == null) || ("".equals(userid))) userid = "unknown-userid";
return userid;
}
private String getUsername() {
String username = (String) Red5.getConnectionLocal().getAttribute("USERNAME");
if ((username == null) || ("".equals(username))) username = "UNKNOWN-CALLER";
return username;
}
} }

View File

@ -25,7 +25,9 @@ import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.CopyOnWriteArraySet;
import org.red5.logging.Red5LoggerFactory; import org.red5.logging.Red5LoggerFactory;
import org.red5.server.api.IConnection;
import org.red5.server.api.IScope; import org.red5.server.api.IScope;
import org.red5.server.api.Red5;
import org.red5.server.api.event.IEvent; import org.red5.server.api.event.IEvent;
import org.red5.server.api.stream.IBroadcastStream; import org.red5.server.api.stream.IBroadcastStream;
import org.red5.server.api.stream.IStreamCodecInfo; import org.red5.server.api.stream.IStreamCodecInfo;
@ -140,11 +142,11 @@ public class AudioBroadcastStream implements IBroadcastStream, IProvider, IPipeC
} }
public void start() { public void start() {
log.trace("start()"); log.debug("Starting AudioBroadcastStream()");
} }
public void stop() { public void stop() {
log.trace("stop"); log.debug("Stopping AudioBroadcastStream");
} }
public void onOOBControlMessage(IMessageComponent source, IPipe pipe, OOBControlMessage oobCtrlMsg) { public void onOOBControlMessage(IMessageComponent source, IPipe pipe, OOBControlMessage oobCtrlMsg) {
@ -183,18 +185,21 @@ public class AudioBroadcastStream implements IBroadcastStream, IProvider, IPipeC
} }
} }
private final RTMPMessage msg = new RTMPMessage();
public void dispatchEvent(IEvent event) { public void dispatchEvent(IEvent event) {
// log.trace("dispatchEvent(event:{})", event); // log.trace("dispatchEvent(event:{})", event);
if (event instanceof IRTMPEvent) { if (event instanceof IRTMPEvent) {
IRTMPEvent rtmpEvent = (IRTMPEvent) event; IRTMPEvent rtmpEvent = (IRTMPEvent) event;
if (livePipe != null) { if (livePipe != null) {
RTMPMessage msg = new RTMPMessage();
msg.setBody(rtmpEvent); msg.setBody(rtmpEvent);
if (creationTime == null) if (creationTime == null)
creationTime = (long)rtmpEvent.getTimestamp(); creationTime = (long)rtmpEvent.getTimestamp();
try { try {
// log.debug("dispatchEvent(event:)" + event); // log.debug("dispatchEvent(event:)" + event);
livePipe.pushMessage(msg); livePipe.pushMessage(msg);

View File

@ -21,8 +21,10 @@ package org.bigbluebutton.voiceconf.red5.media;
public class AudioByteData { public class AudioByteData {
private final byte[] data; private final byte[] data;
private boolean poison = false;
public AudioByteData(byte[] data) { public AudioByteData(byte[] data, boolean stop) {
poison = stop;
this.data = new byte[data.length]; this.data = new byte[data.length];
System.arraycopy(data, 0, this.data, 0, data.length); System.arraycopy(data, 0, this.data, 0, data.length);
} }
@ -30,4 +32,8 @@ public class AudioByteData {
public byte[] getData() { public byte[] getData() {
return data; return data;
} }
public boolean status() {
return poison;
}
} }

View File

@ -81,8 +81,11 @@ public class CallStream implements StreamObserver {
} }
public void startTalkStream(IBroadcastStream broadcastStream, IScope scope) throws StreamException { public void startTalkStream(IBroadcastStream broadcastStream, IScope scope) throws StreamException {
log.debug("Starting userListenSteam");
userListenStream.start(); userListenStream.start();
log.debug("userTalkStream setup");
userTalkStream.start(broadcastStream, scope); userTalkStream.start(broadcastStream, scope);
log.debug("userTalkStream Started");
} }
public void stopTalkStream(IBroadcastStream broadcastStream, IScope scope) { public void stopTalkStream(IBroadcastStream broadcastStream, IScope scope) {
@ -90,6 +93,7 @@ public class CallStream implements StreamObserver {
} }
public void stop() { public void stop() {
log.debug("Stopping call stream");
userListenStream.stop(); userListenStream.stop();
} }

View File

@ -19,12 +19,12 @@
**/ **/
package org.bigbluebutton.voiceconf.red5.media; package org.bigbluebutton.voiceconf.red5.media;
import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.net.DatagramSocket; import java.net.DatagramSocket;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import org.apache.mina.core.buffer.IoBuffer; import org.apache.mina.core.buffer.IoBuffer;
import org.bigbluebutton.voiceconf.red5.media.transcoder.FlashToSipTranscoder; import org.bigbluebutton.voiceconf.red5.media.transcoder.FlashToSipTranscoder;
import org.bigbluebutton.voiceconf.red5.media.transcoder.TranscodedAudioDataListener; import org.bigbluebutton.voiceconf.red5.media.transcoder.TranscodedAudioDataListener;
@ -41,7 +41,9 @@ import org.slf4j.Logger;
public class FlashToSipAudioStream { public class FlashToSipAudioStream {
private final static Logger log = Red5LoggerFactory.getLogger(FlashToSipAudioStream.class, "sip"); private final static Logger log = Red5LoggerFactory.getLogger(FlashToSipAudioStream.class, "sip");
private final BlockingQueue<AudioByteData> audioDataQ = new LinkedBlockingQueue<AudioByteData>(); private final PipedOutputStream streamFromFlash;
private PipedInputStream streamToSip;
private final Executor exec = Executors.newSingleThreadExecutor(); private final Executor exec = Executors.newSingleThreadExecutor();
private Runnable audioDataProcessor; private Runnable audioDataProcessor;
private volatile boolean processAudioData = false; private volatile boolean processAudioData = false;
@ -58,6 +60,13 @@ public class FlashToSipAudioStream {
this.srcSocket = srcSocket; this.srcSocket = srcSocket;
this.connInfo = connInfo; this.connInfo = connInfo;
talkStreamName = "microphone_" + System.currentTimeMillis(); talkStreamName = "microphone_" + System.currentTimeMillis();
streamFromFlash = new PipedOutputStream();
try {
streamToSip = new PipedInputStream(streamFromFlash);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} }
public void start(IBroadcastStream broadcastStream, IScope scope) throws StreamException { public void start(IBroadcastStream broadcastStream, IScope scope) throws StreamException {
@ -76,13 +85,12 @@ public class FlashToSipAudioStream {
if (packet instanceof AudioData) { if (packet instanceof AudioData) {
byte[] data = SerializeUtils.ByteBufferToByteArray(buf); byte[] data = SerializeUtils.ByteBufferToByteArray(buf);
AudioByteData abd = new AudioByteData(data); try {
try { streamFromFlash.write(data, 1, data.length-1);
audioDataQ.put(abd); } catch (IOException e) {
} catch (InterruptedException e) { // TODO Auto-generated catch block
// TODO Auto-generated catch block e.printStackTrace();
e.printStackTrace(); }
}
} }
} }
}; };
@ -101,11 +109,23 @@ public class FlashToSipAudioStream {
} }
private void processAudioData() { private void processAudioData() {
int len = 64;
byte[] nellyAudio = new byte[len];
int remaining = len;
int offset = 0;
TranscodedAudioListener transcodedAudioListener = new TranscodedAudioListener();
while (processAudioData) { while (processAudioData) {
try { try {
AudioByteData abd = audioDataQ.take(); int bytesRead = streamToSip.read(nellyAudio, offset, remaining);
transcoder.transcode(abd, 1, abd.getData().length-1, new TranscodedAudioListener()); remaining -= bytesRead;
} catch (InterruptedException e) { if (remaining == 0) {
remaining = len;
offset = 0;
transcoder.transcode(nellyAudio, 0, nellyAudio.length, transcodedAudioListener);
} else {
offset += bytesRead;
}
} catch (IOException e) {
// TODO Auto-generated catch block // TODO Auto-generated catch block
e.printStackTrace(); e.printStackTrace();
} }
@ -114,7 +134,12 @@ public class FlashToSipAudioStream {
public void stop(IBroadcastStream broadcastStream, IScope scope) { public void stop(IBroadcastStream broadcastStream, IScope scope) {
broadcastStream.removeStreamListener(mInputListener); broadcastStream.removeStreamListener(mInputListener);
processAudioData = false; if (broadcastStream != null) {
broadcastStream.stop();
broadcastStream.close();
}
processAudioData = false;
srcSocket.close();
} }
public String getStreamName() { public String getStreamName() {
@ -124,7 +149,7 @@ public class FlashToSipAudioStream {
private class TranscodedAudioListener implements TranscodedAudioDataListener { private class TranscodedAudioListener implements TranscodedAudioDataListener {
@Override @Override
public void handleTranscodedAudioData(byte[] audioData, long timestamp) { public void handleTranscodedAudioData(byte[] audioData, long timestamp) {
if (audioData != null) { if (audioData != null && processAudioData) {
rtpSender.sendAudio(audioData, transcoder.getCodecId(), timestamp); rtpSender.sendAudio(audioData, transcoder.getCodecId(), timestamp);
} else { } else {
log.warn("Transcodec audio is null. Discarding."); log.warn("Transcodec audio is null. Discarding.");

View File

@ -84,14 +84,11 @@ public class RtpStreamReceiver {
public void receiveRtpPackets() { public void receiveRtpPackets() {
int packetReceivedCounter = 0; int packetReceivedCounter = 0;
int internalBufferLength = payloadLength + RTP_HEADER_SIZE; int internalBufferLength = payloadLength + RTP_HEADER_SIZE;
byte[] internalBuffer; byte[] internalBuffer = new byte[internalBufferLength];
RtpPacket rtpPacket; RtpPacket rtpPacket = new RtpPacket(internalBuffer, internalBufferLength);;
while (receivePackets) { while (receivePackets) {
try { try {
internalBuffer = new byte[internalBufferLength];
rtpPacket = new RtpPacket(internalBuffer, internalBufferLength);
rtpSocket.receive(rtpPacket); rtpSocket.receive(rtpPacket);
packetReceivedCounter++; packetReceivedCounter++;
// log.debug("Received packet [" + rtpPacket.getRtcpPayloadType() + "," + rtpPacket.getPayloadType() + ", length=" + rtpPacket.getPayloadLength() + "] seqNum[rtpSeqNum=" + rtpPacket.getSeqNum() + ",lastSeqNum=" + lastSequenceNumber // log.debug("Received packet [" + rtpPacket.getRtcpPayloadType() + "," + rtpPacket.getPayloadType() + ", length=" + rtpPacket.getPayloadLength() + "] seqNum[rtpSeqNum=" + rtpPacket.getSeqNum() + ",lastSeqNum=" + lastSequenceNumber
@ -112,7 +109,9 @@ public class RtpStreamReceiver {
if (shouldHandlePacket(rtpPacket)) { if (shouldHandlePacket(rtpPacket)) {
// log.debug("Handling packet [" + rtpPacket.getRtcpPayloadType() + "," + rtpPacket.getPayloadType() + ", length=" + rtpPacket.getPayloadLength() + "] seqNum[rtpSeqNum=" + rtpPacket.getSeqNum() + ",lastSeqNum=" + lastSequenceNumber // log.debug("Handling packet [" + rtpPacket.getRtcpPayloadType() + "," + rtpPacket.getPayloadType() + ", length=" + rtpPacket.getPayloadLength() + "] seqNum[rtpSeqNum=" + rtpPacket.getSeqNum() + ",lastSeqNum=" + lastSequenceNumber
// + "][rtpTS=" + rtpPacket.getTimestamp() + ",lastTS=" + lastPacketTimestamp + "][port=" + rtpSocket.getDatagramSocket().getLocalPort() + "]"); // + "][rtpTS=" + rtpPacket.getTimestamp() + ",lastTS=" + lastPacketTimestamp + "][port=" + rtpSocket.getDatagramSocket().getLocalPort() + "]");
processRtpPacket(rtpPacket); lastSequenceNumber = rtpPacket.getSeqNum();
lastPacketTimestamp = rtpPacket.getTimestamp();
processRtpPacket(internalBuffer, RTP_HEADER_SIZE, payloadLength);
} else { } else {
if (log.isDebugEnabled()) if (log.isDebugEnabled())
log.debug("Corrupt packet [" + rtpPacket.getRtcpPayloadType() + "," + rtpPacket.getPayloadType() + ", length=" + rtpPacket.getPayloadLength() + "] seqNum[rtpSeqNum=" + rtpPacket.getSeqNum() + ",lastSeqNum=" + lastSequenceNumber log.debug("Corrupt packet [" + rtpPacket.getRtcpPayloadType() + "," + rtpPacket.getPayloadType() + ", length=" + rtpPacket.getPayloadLength() + "] seqNum[rtpSeqNum=" + rtpPacket.getSeqNum() + ",lastSeqNum=" + lastSequenceNumber
@ -132,7 +131,7 @@ public class RtpStreamReceiver {
private boolean shouldDropDelayedPacket(RtpPacket rtpPacket) { private boolean shouldDropDelayedPacket(RtpPacket rtpPacket) {
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
if (now - lastPacketReceived > 100) { if (now - lastPacketReceived > 200) {
if (log.isDebugEnabled()) if (log.isDebugEnabled())
log.debug("Delayed packet [" + rtpPacket.getRtcpPayloadType() + "," + rtpPacket.getPayloadType() + ", length=" + rtpPacket.getPayloadLength() + "] seqNum[rtpSeqNum=" + rtpPacket.getSeqNum() + ",lastSeqNum=" + lastSequenceNumber log.debug("Delayed packet [" + rtpPacket.getRtcpPayloadType() + "," + rtpPacket.getPayloadType() + ", length=" + rtpPacket.getPayloadLength() + "] seqNum[rtpSeqNum=" + rtpPacket.getSeqNum() + ",lastSeqNum=" + lastSequenceNumber
+ "][rtpTS=" + rtpPacket.getTimestamp() + ",lastTS=" + lastPacketTimestamp + "][port=" + rtpSocket.getDatagramSocket().getLocalPort() + "]"); + "][rtpTS=" + rtpPacket.getTimestamp() + ",lastTS=" + lastPacketTimestamp + "][port=" + rtpSocket.getDatagramSocket().getLocalPort() + "]");
@ -217,11 +216,8 @@ public class RtpStreamReceiver {
return false; return false;
} }
private void processRtpPacket(RtpPacket rtpPacket) { private void processRtpPacket(byte[] rtpAudio, int offset, int len) {
lastSequenceNumber = rtpPacket.getSeqNum(); if (listener != null) listener.onAudioDataReceived(rtpAudio, offset, len);
lastPacketTimestamp = rtpPacket.getTimestamp();
AudioByteData audioData = new AudioByteData(rtpPacket.getPayload());
if (listener != null) listener.onAudioDataReceived(audioData);
else log.debug("No listener for incoming audio packet"); else log.debug("No listener for incoming audio packet");
} }
} }

View File

@ -22,5 +22,5 @@ package org.bigbluebutton.voiceconf.red5.media;
public interface RtpStreamReceiverListener { public interface RtpStreamReceiverListener {
void onStoppedReceiving(); void onStoppedReceiving();
void onAudioDataReceived(AudioByteData audioData); void onAudioDataReceived(byte[] audioData, int offset, int len);
} }

View File

@ -41,6 +41,7 @@ public class RtpStreamSender {
private final DatagramSocket srcSocket; private final DatagramSocket srcSocket;
private final SipConnectInfo connInfo; private final SipConnectInfo connInfo;
private boolean marked = false; private boolean marked = false;
private long startTimestamp;
public RtpStreamSender(DatagramSocket srcSocket, SipConnectInfo connInfo) { public RtpStreamSender(DatagramSocket srcSocket, SipConnectInfo connInfo) {
this.srcSocket = srcSocket; this.srcSocket = srcSocket;
@ -66,6 +67,7 @@ public class RtpStreamSender {
if (!marked) { if (!marked) {
rtpPacket.setMarker(true); rtpPacket.setMarker(true);
marked = true; marked = true;
startTimestamp = System.currentTimeMillis();
} }
rtpPacket.setPadding(false); rtpPacket.setPadding(false);
rtpPacket.setExtension(false); rtpPacket.setExtension(false);

View File

@ -19,11 +19,12 @@
**/ **/
package org.bigbluebutton.voiceconf.red5.media; package org.bigbluebutton.voiceconf.red5.media;
import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.net.DatagramSocket; import java.net.DatagramSocket;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import org.apache.mina.core.buffer.IoBuffer; import org.apache.mina.core.buffer.IoBuffer;
import org.bigbluebutton.voiceconf.red5.media.transcoder.SipToFlashTranscoder; import org.bigbluebutton.voiceconf.red5.media.transcoder.SipToFlashTranscoder;
import org.bigbluebutton.voiceconf.red5.media.transcoder.TranscodedAudioDataListener; import org.bigbluebutton.voiceconf.red5.media.transcoder.TranscodedAudioDataListener;
@ -32,6 +33,7 @@ import org.red5.server.api.IContext;
import org.red5.server.api.IScope; import org.red5.server.api.IScope;
import org.red5.server.net.rtmp.event.AudioData; import org.red5.server.net.rtmp.event.AudioData;
import org.red5.server.net.rtmp.event.Notify; import org.red5.server.net.rtmp.event.Notify;
import org.red5.server.net.rtmp.message.Constants;
import org.red5.server.stream.BroadcastScope; import org.red5.server.stream.BroadcastScope;
import org.red5.server.stream.IBroadcastScope; import org.red5.server.stream.IBroadcastScope;
import org.red5.server.stream.IProviderService; import org.red5.server.stream.IProviderService;
@ -40,7 +42,9 @@ import org.slf4j.Logger;
public class SipToFlashAudioStream implements TranscodedAudioDataListener, RtpStreamReceiverListener { public class SipToFlashAudioStream implements TranscodedAudioDataListener, RtpStreamReceiverListener {
final private Logger log = Red5LoggerFactory.getLogger(SipToFlashAudioStream.class, "sip"); final private Logger log = Red5LoggerFactory.getLogger(SipToFlashAudioStream.class, "sip");
private final BlockingQueue<AudioByteData> audioDataQ = new LinkedBlockingQueue<AudioByteData>(); private final PipedOutputStream streamFromSip;
private PipedInputStream streamToFlash;
private final Executor exec = Executors.newSingleThreadExecutor(); private final Executor exec = Executors.newSingleThreadExecutor();
private Runnable audioDataProcessor; private Runnable audioDataProcessor;
private volatile boolean processAudioData = false; private volatile boolean processAudioData = false;
@ -51,9 +55,9 @@ public class SipToFlashAudioStream implements TranscodedAudioDataListener, RtpSt
private RtpStreamReceiver rtpStreamReceiver; private RtpStreamReceiver rtpStreamReceiver;
private StreamObserver observer; private StreamObserver observer;
private SipToFlashTranscoder transcoder; private SipToFlashTranscoder transcoder;
private long startTimestamp = 0;
private boolean sentMetadata = false; private boolean sentMetadata = false;
private IoBuffer mBuffer;
private AudioData audioData;
private final byte[] fakeMetadata = new byte[] { private final byte[] fakeMetadata = new byte[] {
0x02, 0x00, 0x0a, 0x6f, 0x6e, 0x4d, 0x65, 0x74, 0x61, 0x44, 0x61, 0x74, 0x61, 0x08, 0x00, 0x00, 0x02, 0x00, 0x0a, 0x6f, 0x6e, 0x4d, 0x65, 0x74, 0x61, 0x44, 0x61, 0x74, 0x61, 0x08, 0x00, 0x00,
@ -74,6 +78,17 @@ public class SipToFlashAudioStream implements TranscodedAudioDataListener, RtpSt
rtpStreamReceiver.setRtpStreamReceiverListener(this); rtpStreamReceiver.setRtpStreamReceiverListener(this);
listenStreamName = "speaker_" + System.currentTimeMillis(); listenStreamName = "speaker_" + System.currentTimeMillis();
scope.setName(listenStreamName); scope.setName(listenStreamName);
streamFromSip = new PipedOutputStream();
try {
streamToFlash = new PipedInputStream(streamFromSip);
startNow();
mBuffer = IoBuffer.allocate(1024);
mBuffer = mBuffer.setAutoExpand(true);
audioData = new AudioData();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} }
public String getStreamName() { public String getStreamName() {
@ -85,14 +100,25 @@ public class SipToFlashAudioStream implements TranscodedAudioDataListener, RtpSt
} }
public void stop() { public void stop() {
log.debug("Stopping stream for {}", listenStreamName);
processAudioData = false; processAudioData = false;
rtpStreamReceiver.stop(); rtpStreamReceiver.stop();
audioBroadcastStream.stop(); log.debug("Stopped RTP Stream Receiver for {}", listenStreamName);
audioBroadcastStream.close(); if (audioBroadcastStream != null) {
log.debug("stopping and closing stream {}", listenStreamName); audioBroadcastStream.stop();
log.debug("Stopped audioBroadcastStream for {}", listenStreamName);
audioBroadcastStream.close();
log.debug("Closed audioBroadcastStream for {}", listenStreamName);
} else
log.debug("audioBroadcastStream is null, couldn't stop");
log.debug("Stream(s) stopped");
} }
public void start() { public void start() {
}
private void startNow() {
log.debug("started publishing stream in " + scope.getName()); log.debug("started publishing stream in " + scope.getName());
audioBroadcastStream = new AudioBroadcastStream(listenStreamName); audioBroadcastStream = new AudioBroadcastStream(listenStreamName);
audioBroadcastStream.setPublishedName(listenStreamName); audioBroadcastStream.setPublishedName(listenStreamName);
@ -111,7 +137,6 @@ public class SipToFlashAudioStream implements TranscodedAudioDataListener, RtpSt
audioBroadcastStream.start(); audioBroadcastStream.start();
processAudioData = true; processAudioData = true;
startTimestamp = System.currentTimeMillis();
audioDataProcessor = new Runnable() { audioDataProcessor = new Runnable() {
public void run() { public void run() {
@ -124,11 +149,23 @@ public class SipToFlashAudioStream implements TranscodedAudioDataListener, RtpSt
} }
private void processAudioData() { private void processAudioData() {
int len = 160;
byte[] pcmAudio = new byte[len];
int remaining = len;
int offset = 0;
while (processAudioData) { while (processAudioData) {
try { try {
AudioByteData abd = audioDataQ.take(); int bytesRead = streamToFlash.read(pcmAudio, offset, remaining);
transcoder.transcode(abd, this); remaining -= bytesRead;
} catch (InterruptedException e) { if (remaining == 0) {
remaining = len;
offset = 0;
transcoder.transcode(pcmAudio, this);
} else {
offset += bytesRead;
}
} catch (IOException e) {
// TODO Auto-generated catch block // TODO Auto-generated catch block
e.printStackTrace(); e.printStackTrace();
} }
@ -141,10 +178,10 @@ public class SipToFlashAudioStream implements TranscodedAudioDataListener, RtpSt
} }
@Override @Override
public void onAudioDataReceived(AudioByteData audioData) { public void onAudioDataReceived(byte[] audioData, int offset, int len) {
try { try {
audioDataQ.put(audioData); streamFromSip.write(audioData, offset, len);
} catch (InterruptedException e) { } catch (IOException e) {
// TODO Auto-generated catch block // TODO Auto-generated catch block
e.printStackTrace(); e.printStackTrace();
} }
@ -166,15 +203,13 @@ public class SipToFlashAudioStream implements TranscodedAudioDataListener, RtpSt
* We create a fake one here to get it going. Red5 should do this automatically * We create a fake one here to get it going. Red5 should do this automatically
* but for Red5 0.91, doesn't yet. (ralam Sept 24, 2010). * but for Red5 0.91, doesn't yet. (ralam Sept 24, 2010).
*/ */
IoBuffer mBuffer = IoBuffer.allocate(1024);
mBuffer.setAutoExpand(true);
mBuffer.clear(); mBuffer.clear();
mBuffer.put(fakeMetadata); mBuffer.put(fakeMetadata);
mBuffer.flip(); mBuffer.flip();
Notify notifyData = new Notify(mBuffer); Notify notifyData = new Notify(mBuffer);
notifyData.setTimestamp((int)timestamp ); notifyData.setTimestamp((int)timestamp);
notifyData.setSourceType(Constants.SOURCE_TYPE_LIVE);
audioBroadcastStream.dispatchEvent(notifyData); audioBroadcastStream.dispatchEvent(notifyData);
notifyData.release(); notifyData.release();
sentMetadata = true; sentMetadata = true;
@ -182,23 +217,20 @@ public class SipToFlashAudioStream implements TranscodedAudioDataListener, RtpSt
} }
private void pushAudio(byte[] audio, long timestamp) { private void pushAudio(byte[] audio, long timestamp) {
sendFakeMetadata(timestamp); sendFakeMetadata(timestamp);
mBuffer.clear();
IoBuffer buffer = IoBuffer.allocate(1024); mBuffer.put((byte) transcoder.getCodecId());
buffer.setAutoExpand(true); mBuffer.put(audio);
mBuffer.flip();
buffer.clear(); audioData.setSourceType(Constants.SOURCE_TYPE_LIVE);
/*
buffer.put((byte) transcoder.getCodecId()); * Use timestamp increments passed in by codecs (i.e. 32 for nelly). This will force
byte[] copy = new byte[audio.length]; * Flash Player to playback audio at proper timestamp. If we calculate timestamp using
System.arraycopy(audio, 0, copy, 0, audio.length ); * System.currentTimeMillis() - startTimestamp, the audio has tendency to drift and
* introduce delay. (ralam dec 14, 2010)
buffer.put(copy); */
buffer.flip(); audioData.setTimestamp((int)(timestamp));
audioData.setData(mBuffer);
AudioData audioData = new AudioData(buffer);
audioData.setTimestamp((int)timestamp );
audioBroadcastStream.dispatchEvent(audioData); audioBroadcastStream.dispatchEvent(audioData);
audioData.release(); audioData.release();
} }

View File

@ -40,6 +40,8 @@ public class RtpSocket {
/** Remote port */ /** Remote port */
int r_port; int r_port;
private final byte[] payload = new byte[10];
/** Creates a new RTP socket (only receiver) */ /** Creates a new RTP socket (only receiver) */
public RtpSocket(DatagramSocket datagram_socket) { public RtpSocket(DatagramSocket datagram_socket) {
socket=datagram_socket; socket=datagram_socket;
@ -59,19 +61,24 @@ public class RtpSocket {
return socket; return socket;
} }
private final DatagramPacket rxDatagram = new DatagramPacket(payload, payload.length);
/** Receives a RTP packet from this socket */ /** Receives a RTP packet from this socket */
public void receive(RtpPacket rtpp) throws IOException { public void receive(RtpPacket rtpp) throws IOException {
DatagramPacket datagram = new DatagramPacket(rtpp.getPacket(), rtpp.getLength()); rxDatagram.setData(rtpp.getPacket());
socket.receive(datagram); socket.receive(rxDatagram);
rtpp.setPacketLength(datagram.getLength()); rtpp.setPacketLength(rxDatagram.getLength());
} }
private final DatagramPacket txDatagram = new DatagramPacket(payload, payload.length);
/** Sends a RTP packet from this socket */ /** Sends a RTP packet from this socket */
public void send(RtpPacket rtpp) throws IOException { public void send(RtpPacket rtpp) throws IOException {
DatagramPacket datagram = new DatagramPacket(rtpp.getPacket(), rtpp.getLength()); txDatagram.setData(rtpp.getPacket());
datagram.setAddress(r_addr); txDatagram.setAddress(r_addr);
datagram.setPort(r_port); txDatagram.setPort(r_port);
socket.send(datagram); if (!socket.isClosed())
socket.send(txDatagram);
} }
/** Closes this socket */ /** Closes this socket */

View File

@ -19,10 +19,8 @@
**/ **/
package org.bigbluebutton.voiceconf.red5.media.transcoder; package org.bigbluebutton.voiceconf.red5.media.transcoder;
import org.bigbluebutton.voiceconf.red5.media.AudioByteData;
public interface FlashToSipTranscoder { public interface FlashToSipTranscoder {
void transcode(AudioByteData audioData, int startOffset, int length, TranscodedAudioDataListener listener); void transcode(byte[] audioData, int startOffset, int length, TranscodedAudioDataListener listener);
int getOutgoingEncodedFrameSize(); int getOutgoingEncodedFrameSize();
int getCodecId(); int getCodecId();

View File

@ -19,13 +19,12 @@
**/ **/
package org.bigbluebutton.voiceconf.red5.media.transcoder; package org.bigbluebutton.voiceconf.red5.media.transcoder;
import java.nio.FloatBuffer;
import java.util.Random; import java.util.Random;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.bigbluebutton.voiceconf.red5.media.AudioByteData;
import org.red5.logging.Red5LoggerFactory; import org.red5.logging.Red5LoggerFactory;
import org.red5.app.sip.codecs.Codec; import org.red5.app.sip.codecs.Codec;
import org.red5.app.sip.codecs.asao.ByteStream;
import org.red5.app.sip.codecs.asao.Decoder; import org.red5.app.sip.codecs.asao.Decoder;
import org.red5.app.sip.codecs.asao.DecoderMap; import org.red5.app.sip.codecs.asao.DecoderMap;
@ -38,28 +37,48 @@ import org.red5.app.sip.codecs.asao.DecoderMap;
public class NellyFlashToSipTranscoderImp implements FlashToSipTranscoder { public class NellyFlashToSipTranscoderImp implements FlashToSipTranscoder {
protected static Logger log = Red5LoggerFactory.getLogger( NellyFlashToSipTranscoderImp.class, "sip" ); protected static Logger log = Red5LoggerFactory.getLogger( NellyFlashToSipTranscoderImp.class, "sip" );
private static final int NELLYMOSER_DECODED_PACKET_SIZE = 256; private static final int NELLY_TO_L16_AUDIO_SIZE = 256;
private static final int NELLYMOSER_ENCODED_PACKET_SIZE = 64; private static final int NELLY_AUDIO_LENGTH = 64;
private static final int ULAW_AUDIO_LENGTH = 160;
private Codec sipCodec = null; // Sip codec to be used on audio session /**
* Max buffer length when 5 Nelly/L16 packets equals 8 Ulaw packets.
*/
private static final int MAX_BUFFER_LENGTH = 1280;
/**
* Allocate a fixed buffer length so we don't have to copy elements around. We'll use the
* position, limit, mart attributes of the NIO Buffer.
*/
private final FloatBuffer l16Audio = FloatBuffer.allocate(MAX_BUFFER_LENGTH);
/**
* This is a view read-only copy of the buffer to track which byte are being transcoded from L16->Ulaw
*/
private FloatBuffer viewBuffer;
private Codec sipCodec = null;
private Decoder decoder; private Decoder decoder;
private DecoderMap decoderMap; private DecoderMap decoderMap;
private float[] tempBuffer; // Temporary buffer with received PCM audio from FlashPlayer. private float[] tempL16Buffer = new float[NELLY_TO_L16_AUDIO_SIZE];
private int tempBufferRemaining = 0; // Floats remaining on temporary buffer. private float[] tempUlawBuffer = new float[ULAW_AUDIO_LENGTH];
private float[] encodingBuffer; // Encoding buffer used to encode to final codec format; private byte[] ulawEncodedBuffer = new byte[ULAW_AUDIO_LENGTH];
private int encodingOffset = 0; // Offset of encoding buffer.
private boolean asao_buffer_processed = false; // Indicates whether the current asao buffer was processed.
private boolean hasInitilializedBuffers = false; // Indicates whether the handling buffers have already been initialized.
private long timestamp = 0; private long timestamp = 0;
private final static int TS_INCREMENT = 180; // Determined from PCAP traces. private final static int TS_INCREMENT = 180; // Determined from PCAP traces.
/**
* The transcode process works by taking a 64-byte-array Nelly audio and converting it into a 256-float-array L16 audio. From the
* 256-float-array L16 audio, we take 160-float-array and convert it to a 160-byte-array Ulaw audio. The remaining 96-float-array
* will be used in the next iteration.
* Therefore, 5 Nelly/L16 packets (5x256 = 1280) will result into 8 Ulaw packets (8x160 = 1280).
*
*/
public NellyFlashToSipTranscoderImp(Codec sipCodec) { public NellyFlashToSipTranscoderImp(Codec sipCodec) {
this.sipCodec = sipCodec; this.sipCodec = sipCodec;
decoder = new Decoder(); decoder = new Decoder();
decoderMap = null; decoderMap = null;
Random rgen = new Random(); Random rgen = new Random();
timestamp = rgen.nextInt(1000); timestamp = rgen.nextInt(1000);
viewBuffer = l16Audio.asReadOnlyBuffer();
} }
@Override @Override
@ -73,91 +92,65 @@ public class NellyFlashToSipTranscoderImp implements FlashToSipTranscoder {
} }
@Override @Override
public void transcode(AudioByteData audioData, int startOffset, int length, TranscodedAudioDataListener listener) { public void transcode(byte[] audioData, int startOffset, int length, TranscodedAudioDataListener listener) {
byte[] codedBuffer = new byte[length]; if (audioData.length != NELLY_AUDIO_LENGTH) {
System.arraycopy(audioData.getData(), startOffset, codedBuffer, 0, length); log.warn("Receiving bad nelly audio. Expecting {}, got {}.", NELLY_AUDIO_LENGTH, audioData.length);
byte[] transcodedAudioData = new byte[sipCodec.getOutgoingEncodedFrameSize()]; return;
}
asao_buffer_processed = false; // Convert the Nelly audio to L16.
decoderMap = decoder.decode(decoderMap, audioData, 0, tempL16Buffer, 0);
if (!hasInitilializedBuffers) { // Store the L16 audio into the buffer
tempBuffer = new float[NELLYMOSER_DECODED_PACKET_SIZE]; l16Audio.put(tempL16Buffer);
encodingBuffer = new float[sipCodec.getOutgoingDecodedFrameSize()];
hasInitilializedBuffers = true;
}
if (length > 0) { // Read 160-float worth of audio
do { viewBuffer.get(tempUlawBuffer);
int encodedBytes = fillRtpPacketBuffer(codedBuffer, transcodedAudioData);
if (encodedBytes == 0) {
break;
}
if (encodingOffset == sipCodec.getOutgoingDecodedFrameSize()) { // Convert the L16 audio to Ulaw
encodingOffset = 0; int encodedBytes = sipCodec.pcmToCodec(tempUlawBuffer, ulawEncodedBuffer);
listener.handleTranscodedAudioData(transcodedAudioData, timestamp += TS_INCREMENT);
}
} while (!asao_buffer_processed);
}
}
private int fillRtpPacketBuffer(byte[] audioData, byte[] transcodedAudioData) { // Send it to the server
int copyingSize = 0; listener.handleTranscodedAudioData(ulawEncodedBuffer, timestamp += TS_INCREMENT);
int finalCopySize = 0;
byte[] codedBuffer = new byte[sipCodec.getOutgoingEncodedFrameSize()];
if ((tempBufferRemaining + encodingOffset) >= sipCodec.getOutgoingDecodedFrameSize()) { if (l16Audio.position() == l16Audio.capacity()) {
copyingSize = encodingBuffer.length - encodingOffset; /**
* This means we already processed 5 Nelly packets and sent 5 Ulaw packets.
System.arraycopy(tempBuffer, tempBuffer.length-tempBufferRemaining, encodingBuffer, encodingOffset, copyingSize); * However, we have 3 extra Ulaw packets.
* Fire them off to the server. We don't want to discard them as it will
encodingOffset = sipCodec.getOutgoingDecodedFrameSize(); * result in choppy audio.
tempBufferRemaining -= copyingSize; */
finalCopySize = sipCodec.getOutgoingDecodedFrameSize();
} else {
if (tempBufferRemaining > 0) {
System.arraycopy(tempBuffer, tempBuffer.length - tempBufferRemaining, encodingBuffer, encodingOffset, tempBufferRemaining);
encodingOffset += tempBufferRemaining;
finalCopySize += tempBufferRemaining;
tempBufferRemaining = 0;
}
// Decode new asao packet.
asao_buffer_processed = true;
ByteStream audioStream = new ByteStream(audioData, 0, NELLYMOSER_ENCODED_PACKET_SIZE);
decoderMap = decoder.decode(decoderMap, audioStream.bytes, 0, tempBuffer, 0);
tempBufferRemaining = tempBuffer.length;
if (tempBuffer.length <= 0) {
log.error("Asao decoder Error." );
}
// Try to complete the encodingBuffer with necessary data.
if ((encodingOffset + tempBufferRemaining) > sipCodec.getOutgoingDecodedFrameSize()) {
copyingSize = encodingBuffer.length - encodingOffset;
} else {
copyingSize = tempBufferRemaining;
}
System.arraycopy(tempBuffer, 0, encodingBuffer, encodingOffset, copyingSize);
encodingOffset += copyingSize;
tempBufferRemaining -= copyingSize;
finalCopySize += copyingSize;
}
if (encodingOffset == encodingBuffer.length) {
int encodedBytes = sipCodec.pcmToCodec(encodingBuffer, codedBuffer);
// Get the 6th packet and send
viewBuffer.get(tempUlawBuffer);
encodedBytes = sipCodec.pcmToCodec(tempUlawBuffer, ulawEncodedBuffer);
if (encodedBytes == sipCodec.getOutgoingEncodedFrameSize()) { if (encodedBytes == sipCodec.getOutgoingEncodedFrameSize()) {
System.arraycopy(codedBuffer, 0, transcodedAudioData, 0, codedBuffer.length); listener.handleTranscodedAudioData(ulawEncodedBuffer, timestamp += TS_INCREMENT);
} else { } else {
log.error("Failure encoding buffer." ); log.error("Failure encoding buffer." );
} }
}
return finalCopySize; // Get the 7th packet and send
} viewBuffer.get(tempUlawBuffer);
encodedBytes = sipCodec.pcmToCodec(tempUlawBuffer, ulawEncodedBuffer);
if (encodedBytes == sipCodec.getOutgoingEncodedFrameSize()) {
listener.handleTranscodedAudioData(ulawEncodedBuffer, timestamp += TS_INCREMENT);
} else {
log.error("Failure encoding buffer." );
}
// Get the 8th packet and send
viewBuffer.get(tempUlawBuffer);
encodedBytes = sipCodec.pcmToCodec(tempUlawBuffer, ulawEncodedBuffer);
if (encodedBytes == sipCodec.getOutgoingEncodedFrameSize()) {
listener.handleTranscodedAudioData(ulawEncodedBuffer, timestamp += TS_INCREMENT);
} else {
log.error("Failure encoding buffer." );
}
// Reset the buffer's position back to zero and start over.
l16Audio.clear();
viewBuffer.clear();
}
}
} }

View File

@ -19,87 +19,114 @@
**/ **/
package org.bigbluebutton.voiceconf.red5.media.transcoder; package org.bigbluebutton.voiceconf.red5.media.transcoder;
import java.io.IOException; import java.nio.FloatBuffer;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.Random; import java.util.Random;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.bigbluebutton.voiceconf.red5.media.AudioByteData;
import org.bigbluebutton.voiceconf.util.StackTraceUtil;
import org.red5.logging.Red5LoggerFactory; import org.red5.logging.Red5LoggerFactory;
import org.red5.server.api.IConnection;
import org.red5.server.api.Red5;
import org.red5.server.net.rtmp.RTMPMinaConnection;
import org.red5.app.sip.codecs.Codec; import org.red5.app.sip.codecs.Codec;
import org.red5.app.sip.codecs.asao.ByteStream;
import org.red5.app.sip.codecs.asao.CodecImpl; import org.red5.app.sip.codecs.asao.CodecImpl;
public class NellySipToFlashTranscoderImp implements SipToFlashTranscoder { public class NellySipToFlashTranscoderImp implements SipToFlashTranscoder {
protected static Logger log = Red5LoggerFactory.getLogger(NellySipToFlashTranscoderImp.class, "sip"); protected static Logger log = Red5LoggerFactory.getLogger(NellySipToFlashTranscoderImp.class, "sip");
private static final int NELLYMOSER_DECODED_PACKET_SIZE = 256;
private static final int NELLYMOSER_ENCODED_PACKET_SIZE = 64;
private static final int NELLYMOSER_CODEC_ID = 82; private static final int NELLYMOSER_CODEC_ID = 82;
/**
* The length of resulting L16 audio converted from 160-byte Ulaw audio.
*/
private static final int L16_AUDIO_LENGTH = 256;
/**
* The length of Nelly audio that gets sent to Flash Player.
*/
private static final int NELLY_AUDIO_LENGTH = 64;
/**
* The length of received Ulaw audio.
*/
private static final int ULAW_AUDIO_LENGTH = 160;
/**
* The maximum size of our processing buffer. 8 Ulaw packets (8x160 = 1280) yields 5 L16/Nelly audio (5x256 = 1280).
*/
private static final int MAX_BUFFER_LENGTH = 1280;
/**
* Buffer that contain L16 transcoded audio from Ulaw.
*/
private final FloatBuffer l16Audio = FloatBuffer.allocate(MAX_BUFFER_LENGTH);
/*
* A view read-only buffer that keeps track of which part of the L16 buffer will be converted to Nelly.
*/
private FloatBuffer viewBuffer;
private final float[] tempL16Buffer = new float[ULAW_AUDIO_LENGTH];
private float[] tempNellyBuffer = new float[L16_AUDIO_LENGTH];
private final byte[] nellyBytes = new byte[NELLY_AUDIO_LENGTH];
private float[] encoderMap; private float[] encoderMap;
private Codec audioCodec = null; private Codec audioCodec = null;
private float[] tempBuffer; // Temporary buffer with PCM audio to be sent to FlashPlayer.
private int tempBufferOffset = 0;
private long timestamp = 0; private long timestamp = 0;
private final static int TS_INCREMENT = 32; // Determined from PCAP traces. private final static int TS_INCREMENT = 32; // Determined from PCAP traces.
/**
* The transcode takes a 160-byte Ulaw audio and converts it to a 160-float L16 audio. Whenever there is an
* available 256-float L16 audio, that gets converted into a 64-byte Nelly audio. Therefore, 8 Ulaw packets
* are needed to generate 5 Nelly packets.
* @param audioCodec
*/
public NellySipToFlashTranscoderImp(Codec audioCodec) { public NellySipToFlashTranscoderImp(Codec audioCodec) {
this.audioCodec = audioCodec; this.audioCodec = audioCodec;
encoderMap = new float[64]; encoderMap = new float[64];
tempBuffer = new float[NELLYMOSER_DECODED_PACKET_SIZE];
Random rgen = new Random(); Random rgen = new Random();
timestamp = rgen.nextInt(1000); timestamp = rgen.nextInt(1000);
} viewBuffer = l16Audio.asReadOnlyBuffer();
private void transcodePcmToNellymoser(byte[] codedBuffer, TranscodedAudioDataListener listener) {
float[] decodingBuffer = new float[codedBuffer.length];
int decodedBytes = audioCodec.codecToPcm(codedBuffer, decodingBuffer);
if (decodedBytes == audioCodec.getIncomingDecodedFrameSize()) {
int pcmBufferOffset = 0;
int copySize = 0;
boolean pcmBufferProcessed = false;
do {
if ((tempBuffer.length - tempBufferOffset) <= (decodingBuffer.length - pcmBufferOffset)) {
copySize = tempBuffer.length - tempBufferOffset;
} else {
copySize = decodingBuffer.length - pcmBufferOffset;
}
System.arraycopy(decodingBuffer, pcmBufferOffset, tempBuffer, tempBufferOffset, copySize);
tempBufferOffset += copySize;
pcmBufferOffset += copySize;
if (tempBufferOffset == NELLYMOSER_DECODED_PACKET_SIZE) {
ByteStream encodedStream = new ByteStream(NELLYMOSER_ENCODED_PACKET_SIZE);
encoderMap = CodecImpl.encode(encoderMap, tempBuffer, encodedStream.bytes);
tempBufferOffset = 0;
listener.handleTranscodedAudioData(encodedStream.bytes, timestamp += TS_INCREMENT);
}
if (pcmBufferOffset == decodingBuffer.length) {
pcmBufferProcessed = true;
}
} while (!pcmBufferProcessed);
} else {
log.warn("[IncomingBytes=" + codedBuffer.length + ",DecodedBytes=" + decodedBytes +", ExpectedDecodedBytes=" + audioCodec.getIncomingDecodedFrameSize() +"]");
}
} }
@Override @Override
public void transcode(AudioByteData audioData, TranscodedAudioDataListener listener) { public void transcode(byte[] audioData, TranscodedAudioDataListener listener) {
transcodePcmToNellymoser(audioData.getData(), listener); if (audioData.length != ULAW_AUDIO_LENGTH) {
} log.warn("Received corrupt audio. Got {}, expected {}.", audioData.length, ULAW_AUDIO_LENGTH);
return;
}
// Convert Ulaw to L16
int decodedBytes = audioCodec.codecToPcm(audioData, tempL16Buffer);
// Store into the buffer
l16Audio.put(tempL16Buffer);
if ((l16Audio.position() - viewBuffer.position()) >= L16_AUDIO_LENGTH) {
// We have enough L16 audio to generate a Nelly audio.
// Get some L16 audio
viewBuffer.get(tempNellyBuffer);
// Convert it into Nelly
encoderMap = CodecImpl.encode(encoderMap, tempNellyBuffer, nellyBytes);
// Having done all of that, we now see if we need to send the audio or drop it.
// We have to encode to build the encoderMap so that data from previous audio packet
// will be used for the next packet.
boolean sendPacket = true;
IConnection conn = Red5.getConnectionLocal();
if (conn instanceof RTMPMinaConnection) {
long pendingMessages = ((RTMPMinaConnection)conn).getPendingMessages();
if (pendingMessages > 25) {
// Message backed up probably due to slow connection to client (25 messages * 20ms ptime = 500ms audio)
sendPacket = false;
log.info("Dropping packet. Connection {} congested with {} pending messages (~500ms worth of audio) .", conn.getClient().getId(), pendingMessages);
}
}
if (sendPacket) listener.handleTranscodedAudioData(nellyBytes, timestamp += TS_INCREMENT);
}
if (l16Audio.position() == l16Audio.capacity()) {
// We've processed 8 Ulaw packets (5 Nelly packets), reset the buffers.
l16Audio.clear();
viewBuffer.clear();
}
}
@Override @Override
public int getIncomingEncodedFrameSize() { public int getIncomingEncodedFrameSize() {
@ -111,3 +138,4 @@ public class NellySipToFlashTranscoderImp implements SipToFlashTranscoder {
return NELLYMOSER_CODEC_ID; return NELLYMOSER_CODEC_ID;
} }
} }

View File

@ -19,10 +19,8 @@
**/ **/
package org.bigbluebutton.voiceconf.red5.media.transcoder; package org.bigbluebutton.voiceconf.red5.media.transcoder;
import org.bigbluebutton.voiceconf.red5.media.AudioByteData;
public interface SipToFlashTranscoder { public interface SipToFlashTranscoder {
void transcode(AudioByteData audioData, TranscodedAudioDataListener listener); void transcode(byte[] audioData, TranscodedAudioDataListener listener);
int getCodecId(); int getCodecId();
int getIncomingEncodedFrameSize(); int getIncomingEncodedFrameSize();
} }

View File

@ -20,8 +20,6 @@
package org.bigbluebutton.voiceconf.red5.media.transcoder; package org.bigbluebutton.voiceconf.red5.media.transcoder;
import java.util.Random; import java.util.Random;
import org.bigbluebutton.voiceconf.red5.media.AudioByteData;
import org.red5.app.sip.codecs.Codec; import org.red5.app.sip.codecs.Codec;
import org.red5.logging.Red5LoggerFactory; import org.red5.logging.Red5LoggerFactory;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -39,9 +37,9 @@ public class SpeexFlashToSipTranscoderImp implements FlashToSipTranscoder {
timestamp = rgen.nextInt(1000); timestamp = rgen.nextInt(1000);
} }
public void transcode(AudioByteData audioData, int startOffset, int length, TranscodedAudioDataListener listener) { public void transcode(byte[] audioData, int startOffset, int length, TranscodedAudioDataListener listener) {
byte[] transcodedAudio = new byte[length]; byte[] transcodedAudio = new byte[length];
System.arraycopy(audioData.getData(), startOffset, transcodedAudio, 0, length); System.arraycopy(audioData, startOffset, transcodedAudio, 0, length);
listener.handleTranscodedAudioData(transcodedAudio, timestamp += TS_INCREMENT); listener.handleTranscodedAudioData(transcodedAudio, timestamp += TS_INCREMENT);
} }

View File

@ -20,8 +20,6 @@
package org.bigbluebutton.voiceconf.red5.media.transcoder; package org.bigbluebutton.voiceconf.red5.media.transcoder;
import java.util.Random; import java.util.Random;
import org.bigbluebutton.voiceconf.red5.media.AudioByteData;
import org.red5.app.sip.codecs.Codec; import org.red5.app.sip.codecs.Codec;
import org.red5.logging.Red5LoggerFactory; import org.red5.logging.Red5LoggerFactory;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -41,8 +39,8 @@ public class SpeexSipToFlashTranscoderImp implements SipToFlashTranscoder {
} }
@Override @Override
public void transcode(AudioByteData audioData, TranscodedAudioDataListener listener) { public void transcode(byte[] audioData, TranscodedAudioDataListener listener) {
byte[] codedBuffer = audioData.getData(); byte[] codedBuffer = audioData;
listener.handleTranscodedAudioData(codedBuffer, timestamp += TS_INCREMENT); listener.handleTranscodedAudioData(codedBuffer, timestamp += TS_INCREMENT);
} }

View File

@ -50,4 +50,8 @@ public class AudioConferenceProvider {
public int getStartAudioPort() { public int getStartAudioPort() {
return startAudioPort; return startAudioPort;
} }
public int getStopAudioPort() {
return stopAudioPort;
}
} }

View File

@ -37,7 +37,9 @@ import org.red5.logging.Red5LoggerFactory;
import org.red5.server.api.IScope; import org.red5.server.api.IScope;
import org.red5.server.api.stream.IBroadcastStream; import org.red5.server.api.stream.IBroadcastStream;
import java.net.DatagramSocket; import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException; import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Vector; import java.util.Vector;
public class CallAgent extends CallListenerAdapter implements CallStreamObserver { public class CallAgent extends CallListenerAdapter implements CallStreamObserver {
@ -85,11 +87,12 @@ public class CallAgent extends CallListenerAdapter implements CallStreamObserver
} }
public void call(String callerName, String destination) { public void call(String callerName, String destination) {
log.debug("call {}", destination); log.debug("{} making a call to {}", callerName, destination);
try { try {
localSocket = getLocalAudioSocket(); localSocket = getLocalAudioSocket();
userProfile.audioPort = localSocket.getLocalPort(); userProfile.audioPort = localSocket.getLocalPort();
} catch (Exception e) { } catch (Exception e) {
log.debug("{} failed to allocate local port for call to {}. Notifying client that call failed.", callerName, destination);
notifyListenersOnOutgoingCallFailed(); notifyListenersOnOutgoingCallFailed();
return; return;
} }
@ -138,18 +141,22 @@ public class CallAgent extends CallListenerAdapter implements CallStreamObserver
private DatagramSocket getLocalAudioSocket() throws Exception { private DatagramSocket getLocalAudioSocket() throws Exception {
DatagramSocket socket = null; DatagramSocket socket = null;
boolean failedToGetSocket = true; boolean failedToGetSocket = true;
StringBuilder failedPorts = new StringBuilder("Failed ports: ");
for (int i = 0; i < 3; i++) { for (int i = portProvider.getStartAudioPort(); i <= portProvider.getStopAudioPort(); i++) {
int freePort = portProvider.getFreeAudioPort();
try { try {
socket = new DatagramSocket(portProvider.getFreeAudioPort()); socket = new DatagramSocket(freePort);
failedToGetSocket = false; failedToGetSocket = false;
log.info("Successfully setup local audio port {}. {}", freePort, failedPorts);
break; break;
} catch (SocketException e) { } catch (SocketException e) {
log.error("Failed to setup local audio socket."); failedPorts.append(freePort + ", ");
} }
} }
if (failedToGetSocket) { if (failedToGetSocket) {
log.warn("Failed to setup local audio port {}.", failedPorts);
throw new Exception("Exception while initializing CallStream"); throw new Exception("Exception while initializing CallStream");
} }
@ -169,22 +176,26 @@ public class CallAgent extends CallListenerAdapter implements CallStreamObserver
int localAudioPort = SessionDescriptorUtil.getLocalAudioPort(localSdp); int localAudioPort = SessionDescriptorUtil.getLocalAudioPort(localSdp);
SipConnectInfo connInfo = new SipConnectInfo(localSocket, remoteMediaAddress, remoteAudioPort); SipConnectInfo connInfo = new SipConnectInfo(localSocket, remoteMediaAddress, remoteAudioPort);
try {
localSocket.connect(InetAddress.getByName(remoteMediaAddress), remoteAudioPort);
log.debug("[localAudioPort=" + localAudioPort + ",remoteAudioPort=" + remoteAudioPort + "]");
log.debug("[localAudioPort=" + localAudioPort + ",remoteAudioPort=" + remoteAudioPort + "]"); if (userProfile.audio && localAudioPort != 0 && remoteAudioPort != 0) {
if ((callStream == null) && (sipCodec != null)) {
if (userProfile.audio && localAudioPort != 0 && remoteAudioPort != 0) { try {
if ((callStream == null) && (sipCodec != null)) { callStream = callStreamFactory.createCallStream(sipCodec, connInfo);
try { callStream.addCallStreamObserver(this);
callStream = callStreamFactory.createCallStream(sipCodec, connInfo); callStream.start();
callStream.addCallStreamObserver(this); notifyListenersOnCallConnected(callStream.getTalkStreamName(), callStream.getListenStreamName());
callStream.start(); } catch (Exception e) {
notifyListenersOnCallConnected(callStream.getTalkStreamName(), callStream.getListenStreamName()); log.error("Failed to create Call Stream.");
} catch (Exception e) { System.out.println(StackTraceUtil.getStackTrace(e));
log.error("Failed to create Call Stream."); }
System.out.println(StackTraceUtil.getStackTrace(e)); }
} }
} } catch (UnknownHostException e1) {
} log.error(StackTraceUtil.getStackTrace(e1));
}
} }
@ -204,11 +215,12 @@ public class CallAgent extends CallListenerAdapter implements CallStreamObserver
} }
private void closeVoiceStreams() { private void closeVoiceStreams() {
log.debug("closeMediaApplication" ); log.debug("Shutting down the voice streams.");
if (callStream != null) { if (callStream != null) {
callStream.stop(); callStream.stop();
callStream = null; callStream = null;
} else {
log.debug("Can't shutdown voice stream. callstream is NULL");
} }
} }
@ -323,19 +335,20 @@ public class CallAgent extends CallListenerAdapter implements CallStreamObserver
} }
private void cleanup() { private void cleanup() {
localSocket.close(); log.debug("Closing local audio port {}", localSocket.getLocalPort());
if (localSocket != null) {
localSocket.close();
} else {
log.debug("Trying to close un-allocated port {}", localSocket.getLocalPort());
}
} }
/** Callback function called when arriving a BYE request */ /** Callback function called when arriving a BYE request */
public void onCallClosing(Call call, Message bye) { public void onCallClosing(Call call, Message bye) {
log.debug("onCallClosing"); log.info("Received a BYE from the other end telling us to hangup.");
if (!isCurrentCall(call)) return; if (!isCurrentCall(call)) return;
log.debug("CLOSE.");
closeVoiceStreams(); closeVoiceStreams();
notifyListenersOfOnCallClosed(); notifyListenersOfOnCallClosed();
callState = CallState.UA_IDLE; callState = CallState.UA_IDLE;

View File

@ -63,7 +63,7 @@ public class BigBlueButtonApplication extends MultiThreadedApplicationAdapter {
@Override @Override
public boolean roomStart(IScope room) { public boolean roomStart(IScope room) {
log.debug("{} - roomStart ", APP); log.debug("Starting room [{}].", room.getName());
assert participantsApplication != null; assert participantsApplication != null;
participantsApplication.createRoom(room.getName()); participantsApplication.createRoom(room.getName());
return super.roomStart(room); return super.roomStart(room);
@ -71,21 +71,28 @@ public class BigBlueButtonApplication extends MultiThreadedApplicationAdapter {
@Override @Override
public void roomStop(IScope room) { public void roomStop(IScope room) {
log.debug("{} - roomStop", APP); log.debug("Stopping room [{}]", room.getName());
super.roomStop(room); super.roomStop(room);
assert participantsApplication != null; assert participantsApplication != null;
participantsApplication.destroyRoom(room.getName()); participantsApplication.destroyRoom(room.getName());
BigBlueButtonSession bbbSession = getBbbSession(); BigBlueButtonSession bbbSession = getBbbSession();
assert bbbSession != null; assert bbbSession != null;
log.debug("{} - roomStop - destroying RecordSession {}", APP, bbbSession.getSessionName());
/**
* Need to figure out if the next 2 lines should be removed. (ralam nov 25, 2010).
*/
assert recorderApplication != null; assert recorderApplication != null;
recorderApplication.destroyRecordSession(bbbSession.getSessionName()); recorderApplication.destroyRecordSession(bbbSession.getSessionName());
log.debug("{} - roomStop - destroyed RecordSession {}", APP, bbbSession.getSessionName());
log.debug("Stopped room [{}]", room.getName());
} }
@Override @Override
public boolean roomConnect(IConnection connection, Object[] params) { public boolean roomConnect(IConnection connection, Object[] params) {
log.debug("{} - roomConnect - ", APP); String remoteHost = Red5.getConnectionLocal().getRemoteAddress();
int remotePort = Red5.getConnectionLocal().getRemotePort();
String clientId = Red5.getConnectionLocal().getClient().getId();
log.info("[clientid={}] connected from {}.", clientId, remoteHost + ":" + remotePort);
String username = ((String) params[0]).toString(); String username = ((String) params[0]).toString();
String role = ((String) params[1]).toString(); String role = ((String) params[1]).toString();
@ -116,47 +123,44 @@ public class BigBlueButtonApplication extends MultiThreadedApplicationAdapter {
String debugInfo = "userid=" + userid + ",username=" + username + ",role=" + role + ",conference=" + conference + "," + String debugInfo = "userid=" + userid + ",username=" + username + ",role=" + role + ",conference=" + conference + "," +
"session=" + sessionName + ",voiceConf=" + voiceBridge + ",room=" + room + ",externsUserid=" + externUserID; "session=" + sessionName + ",voiceConf=" + voiceBridge + ",room=" + room + ",externsUserid=" + externUserID;
log.debug("roomConnect - [{}]", debugInfo); log.debug("User [{}] connected to room [{}]", debugInfo, room);
log.info("User Joined [{}, {}]", username, room);
super.roomConnect(connection, params); super.roomConnect(connection, params);
return true; return true;
} }
@Override @Override
public void roomDisconnect(IConnection conn) { public void roomDisconnect(IConnection conn) {
String remoteHost = Red5.getConnectionLocal().getRemoteAddress();
int remotePort = Red5.getConnectionLocal().getRemotePort();
String clientId = Red5.getConnectionLocal().getClient().getId();
log.info("[clientid={}] disconnnected from {}.", clientId, remoteHost + ":" + remotePort);
BigBlueButtonSession bbbSession = (BigBlueButtonSession) Red5.getConnectionLocal().getAttribute(Constants.SESSION); BigBlueButtonSession bbbSession = (BigBlueButtonSession) Red5.getConnectionLocal().getAttribute(Constants.SESSION);
log.info("User Left [{}, {}]", bbbSession.getUsername(), bbbSession.getRoom()); log.info("User [{}] disconnected from room [{}]", bbbSession.getUsername(), bbbSession.getRoom());
super.roomDisconnect(conn); super.roomDisconnect(conn);
} }
public String getMyUserId() { public String getMyUserId() {
log.debug("Getting userid for connection.");
BigBlueButtonSession bbbSession = (BigBlueButtonSession) Red5.getConnectionLocal().getAttribute(Constants.SESSION); BigBlueButtonSession bbbSession = (BigBlueButtonSession) Red5.getConnectionLocal().getAttribute(Constants.SESSION);
assert bbbSession != null; assert bbbSession != null;
return bbbSession.getUserid()+""; return bbbSession.getUserid()+"";
} }
public void setParticipantsApplication(ParticipantsApplication a) { public void setParticipantsApplication(ParticipantsApplication a) {
log.debug("Setting participants application");
participantsApplication = a; participantsApplication = a;
} }
public void setRecorderApplication(RecorderApplication a) { public void setRecorderApplication(RecorderApplication a) {
log.debug("Setting recorder application");
recorderApplication = a; recorderApplication = a;
} }
public void setApplicationListeners(Set<IApplication> listeners) { public void setApplicationListeners(Set<IApplication> listeners) {
log.debug("Setting application listeners");
int count = 0; int count = 0;
Iterator<IApplication> iter = listeners.iterator(); Iterator<IApplication> iter = listeners.iterator();
while (iter.hasNext()) { while (iter.hasNext()) {
log.debug("Setting application listeners {}", count);
super.addListener((IApplication) iter.next()); super.addListener((IApplication) iter.next());
count++; count++;
} }
log.debug("Finished Setting application listeners");
} }
public void setVersion(String v) { public void setVersion(String v) {
@ -172,7 +176,7 @@ public class BigBlueButtonApplication extends MultiThreadedApplicationAdapter {
@Override @Override
public void onApplicationEvent(ApplicationEvent event) { public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof org.springframework.context.event.ContextStoppedEvent) { if (event instanceof org.springframework.context.event.ContextStoppedEvent) {
log.info("Received shutdown event. Destroying all rooms."); log.info("Received shutdown event. Red5 is shutting down. Destroying all rooms.");
participantsApplication.destroyAllRooms(); participantsApplication.destroyAllRooms();
} }
} }

View File

@ -49,3 +49,4 @@
</buildCSSFiles> </buildCSSFiles>
</actionScriptProperties> </actionScriptProperties>

View File

@ -1,3 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<flexProperties enableServiceManager="false" flexServerFeatures="0" flexServerType="0" toolCompile="true" useServerFlexSDK="false" version="1"/> <flexProperties enableServiceManager="false" flexServerFeatures="0" flexServerType="0" toolCompile="true" useServerFlexSDK="false" version="1"/>

View File

@ -1,3 +1,6 @@
.settings/org.eclipse.core.resources.prefs
.actionScriptProperties
.flexProperties
linker-report.xml linker-report.xml
bin bin
client client

View File

@ -1,4 +1,4 @@
#Tue Feb 23 11:59:38 EST 2010 #Mon Dec 13 14:02:21 EST 2010
eclipse.preferences.version=1 eclipse.preferences.version=1
encoding//locale/el_GR/bbbResources.properties=UTF-8 encoding//locale/el_GR/bbbResources.properties=UTF-8
encoding/<project>=utf-8 encoding/<project>=UTF-8

View File

@ -36,6 +36,7 @@
<module name="PhoneModule" url="PhoneModule.swf?v=VERSION" <module name="PhoneModule" url="PhoneModule.swf?v=VERSION"
uri="rtmp://HOST/sip" uri="rtmp://HOST/sip"
autoJoin="false"
dependsOn="ViewersModule" dependsOn="ViewersModule"
/> />

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View File

@ -8,6 +8,13 @@ Application
backgroundGradientAlphas: 0.62, 0.26; backgroundGradientAlphas: 0.62, 0.26;
} }
/*The image for your logo is 200x200 pixels. No other size is currently supported so resize your logo accordingly. The logo will always appear in the lower right corner. */
BrandingLogo
{
backgroundImage: Embed(source="assets/img/BBBlogo.png");
backgroundSize: "100%";
}
MDICanvas MDICanvas
{ {

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml" width="200" height="200">
</mx:Canvas>

View File

@ -238,7 +238,9 @@
<views:MainToolbar id="toolbar" dock="true" width="100%" height="30" visible="false" verticalAlign="middle"/> <views:MainToolbar id="toolbar" dock="true" width="100%" height="30" visible="false" verticalAlign="middle"/>
<views:MainCanvas id="mdiCanvas" horizontalScrollPolicy="off" verticalScrollPolicy="off" effectsLib="{flexlib.mdi.effects.effectsLib.MDIVistaEffects}" width="100%" height="100%"> <views:MainCanvas id="mdiCanvas" horizontalScrollPolicy="off" verticalScrollPolicy="off" effectsLib="{flexlib.mdi.effects.effectsLib.MDIVistaEffects}" width="100%" height="100%">
<views:LoadingBar id="progressBar" x="{this.width/2 - progressBar.width/2}" y="{this.height/2 - progressBar.height/2}" width="{this.width/2}" /> <views:LoadingBar id="progressBar" x="{this.width/2 - progressBar.width/2}" y="{this.height/2 - progressBar.height/2}" width="{this.width/2}" />
<views:BrandingLogo x="{this.width - 300}" y="{this.height - 300}" />
</views:MainCanvas> </views:MainCanvas>
<mx:ControlBar width="100%" height="20" paddingTop="0"> <mx:ControlBar width="100%" height="20" paddingTop="0">
<mx:Label text="{ResourceUtil.getInstance().getString('bbb.mainshell.copyrightLabel2',[appVersion])}" id="copyrightLabel2"/> <mx:Label text="{ResourceUtil.getInstance().getString('bbb.mainshell.copyrightLabel2',[appVersion])}" id="copyrightLabel2"/>
<mx:Spacer width="20"/> <mx:Spacer width="20"/>

View File

@ -47,6 +47,7 @@ package org.bigbluebutton.modules.deskshare.services
private var width:Number; private var width:Number;
private var height:Number; private var height:Number;
private var uri:String;
public function DeskshareService() public function DeskshareService()
{ {
@ -60,6 +61,7 @@ package org.bigbluebutton.modules.deskshare.services
} }
public function connect(uri:String):void { public function connect(uri:String):void {
this.uri = uri;
LogUtil.debug("Deskshare Service connecting to " + uri); LogUtil.debug("Deskshare Service connecting to " + uri);
conn = new Connection(); conn = new Connection();
conn.addEventListener(Connection.SUCCESS, connectionSuccessHandler); conn.addEventListener(Connection.SUCCESS, connectionSuccessHandler);
@ -93,9 +95,9 @@ package org.bigbluebutton.modules.deskshare.services
} }
private function connectionSuccessHandler(e:ConnectionEvent):void{ private function connectionSuccessHandler(e:ConnectionEvent):void{
LogUtil.debug("Successully connection to " + module.uri); LogUtil.debug("Successully connection to " + uri);
nc = conn.getConnection(); nc = conn.getConnection();
deskSO = SharedObject.getRemote("deskSO", module.uri, false); deskSO = SharedObject.getRemote("deskSO", uri, false);
deskSO.client = this; deskSO.client = this;
deskSO.connect(nc); deskSO.connect(nc);
@ -107,11 +109,11 @@ package org.bigbluebutton.modules.deskshare.services
} }
public function connectionFailedHandler(e:ConnectionEvent):void{ public function connectionFailedHandler(e:ConnectionEvent):void{
LogUtil.error("connection failed to " + module.uri + " with message " + e.toString()); LogUtil.error("connection failed to " + uri + " with message " + e.toString());
} }
public function connectionRejectedHandler(e:ConnectionEvent):void{ public function connectionRejectedHandler(e:ConnectionEvent):void{
LogUtil.error("connection rejected " + module.uri + " with message " + e.toString()); LogUtil.error("connection rejected " + uri + " with message " + e.toString());
} }
/** /**

View File

@ -26,7 +26,6 @@
title="{windowTitle}" title="{windowTitle}"
creationComplete="onCreationComplete()" xmlns:mate="http://mate.asfusion.com/"> creationComplete="onCreationComplete()" xmlns:mate="http://mate.asfusion.com/">
<mate:Listener type="{ListenersEvent.FIRST_LISTENER_JOINED_EVENT}" method="firstListenerJoined" />
<mate:Listener type="{ListenersEvent.ROOM_MUTE_STATE}" method="roomMuteStateChange" /> <mate:Listener type="{ListenersEvent.ROOM_MUTE_STATE}" method="roomMuteStateChange" />
<mate:Listener type="{ListenersEvent.REGISTER_LISTENERS}" method="registerListeners" /> <mate:Listener type="{ListenersEvent.REGISTER_LISTENERS}" method="registerListeners" />
<mate:Listener type="{ListenersEvent.SET_LOCAL_MODERATOR_STATUS}" method="{setModerator}" /> <mate:Listener type="{ListenersEvent.SET_LOCAL_MODERATOR_STATUS}" method="{setModerator}" />
@ -128,16 +127,16 @@
dispatchEvent(unmuteCommand); dispatchEvent(unmuteCommand);
} }
private function firstListenerJoined(e:ListenersEvent):void{
}
private function roomMuteStateChange(e:ListenersEvent):void{ private function roomMuteStateChange(e:ListenersEvent):void{
setMuteState(e.mute_state); setMuteState(e.mute_state);
} }
private function registerListeners(e:ListenersEvent):void{ private function registerListeners(e:ListenersEvent):void{
this.listeners = e.listeners.listeners; this.listeners = e.listeners.listeners;
/*
* Bind into this object to display the number of listeners.
*/
BindingUtils.bindSetter(updateNumberOfListeners, listeners, "length");
} }
private function setModerator(e:ListenersEvent):void{ private function setModerator(e:ListenersEvent):void{
@ -145,14 +144,6 @@
showCloseButton = false; showCloseButton = false;
} }
override protected function resourcesChanged():void{
super.resourcesChanged();
if (listeners.length > 4)
windowTitle = ResourceUtil.getInstance().getString('bbb.listeners.title', [":", listeners.length]) ;
else
windowTitle = ResourceUtil.getInstance().getString('bbb.listeners.title', ["", ""]) ;
}
private function onItemRollOver(e:ListEvent):void{ private function onItemRollOver(e:ListEvent):void{
var item:ListenerItem = e.itemRenderer as ListenerItem; var item:ListenerItem = e.itemRenderer as ListenerItem;
item.onRollOver(); item.onRollOver();

View File

@ -60,7 +60,7 @@ package org.bigbluebutton.modules.phone.managers {
return netConnection; return netConnection;
} }
public function connect(uid:String, username:String, room:String, uri:String):void { public function connect(uid:String, externUID:String, username:String, room:String, uri:String):void {
if (isConnected) return; if (isConnected) return;
isConnected = true; isConnected = true;
@ -68,16 +68,16 @@ package org.bigbluebutton.modules.phone.managers {
this.username = username; this.username = username;
this.room = room; this.room = room;
this.uri = uri; this.uri = uri;
connectToServer(); connectToServer(externUID, username);
} }
private function connectToServer():void { private function connectToServer(externUID:String, username:String):void {
NetConnection.defaultObjectEncoding = flash.net.ObjectEncoding.AMF0; NetConnection.defaultObjectEncoding = flash.net.ObjectEncoding.AMF0;
netConnection = new NetConnection(); netConnection = new NetConnection();
netConnection.client = this; netConnection.client = this;
netConnection.addEventListener( NetStatusEvent.NET_STATUS , netStatus ); netConnection.addEventListener( NetStatusEvent.NET_STATUS , netStatus );
netConnection.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler); netConnection.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler);
netConnection.connect(uri); netConnection.connect(uri, externUID, username);
} }
public function disconnect():void { public function disconnect():void {

View File

@ -19,8 +19,6 @@
package org.bigbluebutton.modules.phone.managers package org.bigbluebutton.modules.phone.managers
{ {
import flash.events.IEventDispatcher;
import org.bigbluebutton.common.LogUtil; import org.bigbluebutton.common.LogUtil;
import org.bigbluebutton.modules.phone.events.CallConnectedEvent; import org.bigbluebutton.modules.phone.events.CallConnectedEvent;
import org.bigbluebutton.modules.phone.events.JoinVoiceConferenceEvent; import org.bigbluebutton.modules.phone.events.JoinVoiceConferenceEvent;
@ -40,6 +38,8 @@ package org.bigbluebutton.modules.phone.managers
public function setModuleAttributes(attributes:Object):void { public function setModuleAttributes(attributes:Object):void {
this.attributes = attributes; this.attributes = attributes;
LogUtil.debug("Attributes Set... webvoiceconf:" + attributes.webvoiceconf); LogUtil.debug("Attributes Set... webvoiceconf:" + attributes.webvoiceconf);
if (attributes.autoJoin == "true") joinVoice(true);
} }
private function setupMic(useMic:Boolean):void { private function setupMic(useMic:Boolean):void {
@ -54,28 +54,36 @@ package org.bigbluebutton.modules.phone.managers
} }
public function join(e:JoinVoiceConferenceEvent):void { public function join(e:JoinVoiceConferenceEvent):void {
setupMic(e.useMicrophone); joinVoice(e.useMicrophone);
}
public function joinVoice(autoJoin:Boolean):void {
setupMic(autoJoin);
var uid:String = String( Math.floor( new Date().getTime() ) ); var uid:String = String( Math.floor( new Date().getTime() ) );
connectionManager.connect(uid, attributes.username, attributes.room, attributes.uri); connectionManager.connect(uid, attributes.externUserID, attributes.username, attributes.room, attributes.uri);
} }
public function dialConference():void { public function dialConference():void {
LogUtil.debug("Dialing...." + attributes.webvoiceconf); LogUtil.debug("Dialing...." + attributes.webvoiceconf + "...." + attributes.externUserID);
connectionManager.doCall(attributes.webvoiceconf); connectionManager.doCall(attributes.webvoiceconf);
} }
public function callConnected(event:CallConnectedEvent):void { public function callConnected(event:CallConnectedEvent):void {
LogUtil.debug("Call connected..."); LogUtil.debug("Call connected...");
setupConnection(); setupConnection();
LogUtil.debug("callConnected: Connection Setup");
streamManager.callConnected(event.playStreamName, event.publishStreamName, event.codec); streamManager.callConnected(event.playStreamName, event.publishStreamName, event.codec);
LogUtil.debug("callConnected::onCall set");
onCall = true; onCall = true;
} }
public function hangup():void { public function hangup():void {
LogUtil.debug("PhoneManager hangup"); LogUtil.debug("PhoneManager hangup");
if (onCall) { if (onCall) {
LogUtil.debug("PM OnCall");
streamManager.stopStreams(); streamManager.stopStreams();
connectionManager.doHangUp(); connectionManager.doHangUp();
LogUtil.debug("PM hangup::doHangUp");
onCall = false; onCall = false;
} }
} }

View File

@ -115,7 +115,9 @@ package org.bigbluebutton.modules.phone.managers
public function mute():void { public function mute():void {
if(!muted) { if(!muted) {
if(outgoingStream != null) { if(outgoingStream != null) {
LogUtil.debug("***** Muting the mic.");
outgoingStream.close(); outgoingStream.close();
outgoingStream = null; outgoingStream = null;
muted = true; muted = true;
@ -125,6 +127,7 @@ package org.bigbluebutton.modules.phone.managers
public function unmute():void { public function unmute():void {
if (muted) { if (muted) {
LogUtil.debug("***** UNMuting the mic.");
outgoingStream = new NetStream(connection); outgoingStream = new NetStream(connection);
outgoingStream.addEventListener(NetStatusEvent.NET_STATUS, netStatus); outgoingStream.addEventListener(NetStatusEvent.NET_STATUS, netStatus);
outgoingStream.addEventListener(AsyncErrorEvent.ASYNC_ERROR, asyncErrorHandler); outgoingStream.addEventListener(AsyncErrorEvent.ASYNC_ERROR, asyncErrorHandler);
@ -140,13 +143,22 @@ package org.bigbluebutton.modules.phone.managers
} }
public function callConnected(playStreamName:String, publishStreamName:String, codec:String):void { public function callConnected(playStreamName:String, publishStreamName:String, codec:String):void {
LogUtil.debug("SM callConnected");
isCallConnected = true; isCallConnected = true;
audioCodec = codec; audioCodec = codec;
setupIncomingStream(); setupIncomingStream();
setupOutgoingStream(); LogUtil.debug("SM callConnected: Incoming Stream Setup");
if (mic != null) {
setupOutgoingStream();
LogUtil.debug("SM callConnected: Setup Outgoing Stream");
}
LogUtil.debug("SM callConnected: Setup Stream(s)");
setupPlayStatusHandler(); setupPlayStatusHandler();
LogUtil.debug("SM callConnected: After setupPlayStatusHandler");
play(playStreamName); play(playStreamName);
LogUtil.debug("SM callConnected: After play");
publish(publishStreamName); publish(publishStreamName);
LogUtil.debug("SM callConnected: Published Stream");
} }
private function play(playStreamName:String):void { private function play(playStreamName:String):void {
@ -155,14 +167,25 @@ package org.bigbluebutton.modules.phone.managers
private function publish(publishStreamName:String):void { private function publish(publishStreamName:String):void {
LogUtil.debug("Publishing stream " + publishStreamName); LogUtil.debug("Publishing stream " + publishStreamName);
outgoingStream.publish(publishStreamName, "live"); if (mic != null)
outgoingStream.publish(publishStreamName, "live");
else
LogUtil.debug("SM publish: No Microphone to publish");
} }
private function setupIncomingStream():void { private function setupIncomingStream():void {
incomingStream = new NetStream(connection); incomingStream = new NetStream(connection);
incomingStream.addEventListener(NetStatusEvent.NET_STATUS, netStatus); incomingStream.addEventListener(NetStatusEvent.NET_STATUS, netStatus);
incomingStream.addEventListener(AsyncErrorEvent.ASYNC_ERROR, asyncErrorHandler); incomingStream.addEventListener(AsyncErrorEvent.ASYNC_ERROR, asyncErrorHandler);
incomingStream.bufferTime = 0.180; /*
* Set the bufferTime to 0 (zero) for live stream as suggested in the doc.
* http://help.adobe.com/en_US/FlashPlatform/beta/reference/actionscript/3/flash/net/NetStream.html#bufferTime
* If we don't, we'll have a long audio delay when a momentary network congestion occurs. When the congestion
* disappears, a flood of audio packets will arrive at the client and Flash will buffer them all and play them.
* http://stackoverflow.com/questions/1079935/actionscript-netstream-stutters-after-buffering
* ralam (Dec 13, 2010)
*/
incomingStream.bufferTime = 0;
} }
private function setupOutgoingStream():void { private function setupOutgoingStream():void {
@ -178,19 +201,29 @@ package org.bigbluebutton.modules.phone.managers
custom_obj.onPlayStatus = playStatus; custom_obj.onPlayStatus = playStatus;
custom_obj.onMetadata = onMetadata; custom_obj.onMetadata = onMetadata;
incomingStream.client = custom_obj; incomingStream.client = custom_obj;
outgoingStream.client = custom_obj; if (mic != null)
outgoingStream.client = custom_obj;
} }
public function stopStreams():void { public function stopStreams():void {
LogUtil.debug("Stopping Stream(s)");
if(incomingStream != null) { if(incomingStream != null) {
LogUtil.debug("--Stopping Incoming Stream");
incomingStream.play(false); incomingStream.play(false);
} else {
LogUtil.debug("--Incoming Stream Null");
} }
if(outgoingStream != null) { if(outgoingStream != null) {
LogUtil.debug("--Stopping Outgoing Stream");
outgoingStream.attachAudio(null); outgoingStream.attachAudio(null);
outgoingStream.close();
} else {
LogUtil.debug("--Outgoing Stream Null");
} }
isCallConnected = false; isCallConnected = false;
LogUtil.debug("Stopped Stream(s)");
} }
private function netStatus (evt:NetStatusEvent ):void { private function netStatus (evt:NetStatusEvent ):void {

View File

@ -36,24 +36,72 @@
# 2010-09-15 FFD Updates for 0.71-dev # 2010-09-15 FFD Updates for 0.71-dev
# 2010-10-16 FFD Updates for 0.71-beta # 2010-10-16 FFD Updates for 0.71-beta
# 2010-11-06 FFD Added logic to ensure red5 shuts down # 2010-11-06 FFD Added logic to ensure red5 shuts down
# 2010-12-12 FFD Fixed bug #778
# 2010-12-12 FFD Added support for Intalio VM
#set -x #set -x
#
# Setup some global variables
#
BBB_VERSION="0.71a-dev"
if [ ! -f /usr/share/red5/webapps/bigbluebutton/WEB-INF/red5-web.xml ]; then
echo "#"
echo "# BigBlueButton does not appear to be installed ... exiting"
echo "#"
exit 1
fi
#
# Figure out if we're using Asterisk or FreeSWITCH
#
if cat /usr/share/red5/webapps/bigbluebutton/WEB-INF/red5-web.xml | grep -v '<!--' | grep -q 'bbb-voice-asterisk.xml'; then if cat /usr/share/red5/webapps/bigbluebutton/WEB-INF/red5-web.xml | grep -v '<!--' | grep -q 'bbb-voice-asterisk.xml'; then
VOICE_CONFERENCE="bbb-voice-asterisk.xml" VOICE_CONFERENCE="bbb-voice-asterisk.xml"
elif cat /usr/share/red5/webapps/bigbluebutton/WEB-INF/red5-web.xml | grep -v '<!--' | grep -q 'bbb-voice-freeswitch.xml'; then elif cat /usr/share/red5/webapps/bigbluebutton/WEB-INF/red5-web.xml | grep -v '<!--' | grep -q 'bbb-voice-freeswitch.xml'; then
VOICE_CONFERENCE="bbb-voice-freeswitch.xml" VOICE_CONFERENCE="bbb-voice-freeswitch.xml"
fi fi
#
# Determine IP so it works on multilingual installations
#
IP=$(ifconfig | grep -v '127.0.0.1' | grep -E "[0-9]*\.[0-9]*\.[0-9]*\.[0-9]*" | head -1 | cut -d: -f2 | awk '{ print $1}') IP=$(ifconfig | grep -v '127.0.0.1' | grep -E "[0-9]*\.[0-9]*\.[0-9]*\.[0-9]*" | head -1 | cut -d: -f2 | awk '{ print $1}')
GENTOO=$(uname -r | grep gentoo | cut -d- -f2);
TOMCAT="" #
BBB_VERSION="0.71" # Figure out our environemtn
#
PLATFORM=ubuntu
RED5_DIR=/usr/share/red5
ACTIVEMQ_DIR=/usr/share/activemq
SERVLET_CONTAINER=tomcat6
SERVLET_LOGS=/var/lib/tomcat6/logs
SERVLET_DIR=/var/lib/tomcat6/webapps
if [ -f /etc/lsb-release ]; then
if grep -q Ubuntu /etc/lsb-release; then
if [ -f /etc/init.d/cloud ]; then
#
# Support configuration on Intalio Cloud VM
#
PLATFORM=ubuntu-jetty-intalio
SERVLET_CONTAINER=cloud
SERVLET_LOGS=/var/www/cloud/jetty/logs
SERVLET_DIR=/var/www/cloud/webappsviacontext
fi
fi
fi
if [ -f /etc/redhat-release ]; then
#
# unsupported
#
PLATFORM=redhat
SERVLET_LOGS=/var/log/tomcat6
fi
# #
# Helper functions # Helper functions
@ -61,6 +109,7 @@ BBB_VERSION="0.71"
# #
# Check if the function has a value and, if not, print an error message
# $1 -- name of value # $1 -- name of value
# $2 -- loctation of value # $2 -- loctation of value
# $3 -- value to check # $3 -- value to check
@ -80,65 +129,16 @@ check_file() {
fi fi
} }
get_platform() {
if [ -f /etc/lsb-release ]; then
if grep -q Ubuntu /etc/lsb-release; then
echo "ubuntu"
fi
elif [ ${GENTOO} ]; then
echo "gentoo"
else
echo "redhat"
fi
}
PLATFORM=$(get_platform)
is_redhat() {
if [ "$PLATFORM" == "redhat" ]; then
echo "yes"
fi
}
is_ubuntu() {
if [ "$PLATFORM" == "ubuntu" ]; then
echo "yes"
fi
}
is_gentoo() {
if [ "$PLATFORM" == "gentoo" ]; then
echo "yes"
fi
}
is_vm() { is_vm() {
#
# Is the the BigBlueButton VM?
#
if [ -f /home/firstuser/.profile ]; then if [ -f /home/firstuser/.profile ]; then
echo $(cat /home/firstuser/.profile | grep BigBlueButton) echo $(cat /home/firstuser/.profile | grep BigBlueButton)
fi fi
} }
if [ "$(is_redhat)" ]; then
TOMCAT="tomcat6"
RED5_DIR="/usr/share/red5"
ACTIVEMQ_DIR="/usr/share/activemq"
TOMCAT6_LOGS="/var/log/${TOMCAT}"
elif [ "$(is_gentoo)" ]; then
TOMCAT="tomcat-6"
RED5_DIR="/usr/share/red5"
ACTIVEMQ_DIR="/usr/share/activemq"
TOMCAT6_LOGS="/var/lib/${TOMCAT}/logs"
else
if [ "$(is_ubuntu)" ]; then
TOMCAT="tomcat6"
RED5_DIR="/usr/share/red5"
ACTIVEMQ_DIR="/usr/share/activemq"
TOMCAT6_LOGS="/var/lib/${TOMCAT}/logs"
fi
fi
print_header() { print_header() {
if [ ! $HEADER ]; then if [ ! $HEADER ]; then
@ -166,10 +166,10 @@ need_root() {
} }
usage() { usage() {
echo "BigBlueButton Configuration Utility - Version $BBB_VERSION-dev6" echo "BigBlueButton Configuration Utility - Version $BBB_VERSION"
echo "http://code.google.com/p/bigbluebutton/wiki/BBBConf" echo "http://code.google.com/p/bigbluebutton/wiki/BBBConf"
echo echo
echo "$0 [options]" echo " bbb-conf [options]"
echo echo
echo "Configuration:" echo "Configuration:"
echo " --version Display BigBlueButton version (packages)" echo " --version Display BigBlueButton version (packages)"
@ -184,7 +184,7 @@ usage() {
echo " --watch Scan the log files for error messages every 2 seconds" echo " --watch Scan the log files for error messages every 2 seconds"
echo " --salt View the URL and security salt for the server" echo " --salt View the URL and security salt for the server"
echo echo
echo "Administration": echo "Administration:"
echo " --restart Restart BigBueButton" echo " --restart Restart BigBueButton"
echo " --stop Stop BigBueButton" echo " --stop Stop BigBueButton"
echo " --start Start BigBueButton" echo " --start Start BigBueButton"
@ -247,7 +247,7 @@ uncomment () {
stop_bigbluebutton () { stop_bigbluebutton () {
/etc/init.d/red5 stop /etc/init.d/red5 stop
/etc/init.d/${TOMCAT} stop /etc/init.d/${SERVLET_CONTAINER} stop
/etc/init.d/nginx stop /etc/init.d/nginx stop
if [ -a /opt/freeswitch/run/freeswitch.pid ]; then if [ -a /opt/freeswitch/run/freeswitch.pid ]; then
@ -259,7 +259,10 @@ stop_bigbluebutton () {
fi fi
/etc/init.d/activemq stop /etc/init.d/activemq stop
/etc/init.d/bbb-openoffice-headless stop
if [ -f /etc/init.d/bbb-openoffice-headless ]; then
/etc/init.d/bbb-openoffice-headless stop
fi
} }
start_bigbluebutton () { start_bigbluebutton () {
@ -301,13 +304,15 @@ start_bigbluebutton () {
done done
fi fi
/etc/init.d/bbb-openoffice-headless start if [ -f /etc/init.d/bbb-openoffice-headless ]; then
/etc/init.d/bbb-openoffice-headless start
fi
/etc/init.d/nginx start /etc/init.d/nginx start
/etc/init.d/red5 start /etc/init.d/red5 start
/etc/init.d/${TOMCAT} start /etc/init.d/${SERVLET_CONTAINER} start
# #
# At this point the red5 and tomcat6 applications are starting up. # At this point the red5 and servlet container applications are starting up.
# #
echo -n "Waiting for BigBlueButton to finish starting up (this may take a minute): " echo -n "Waiting for BigBlueButton to finish starting up (this may take a minute): "
@ -321,8 +326,22 @@ start_bigbluebutton () {
done done
fi fi
if ! wget http://$NGINX_IP/bigbluebutton/api -O - --quiet | grep -q SUCCESS; then if [ $PLATFORM == "ubuntu-jetty-intalio" ]; then
echo "Startup unsuccessful" #
# Wait until jetty has finished starting before checking for bbb-web
#
if ! nc -z -w 1 127.0.0.1 8443; then
while ! nc -z -w 1 127.0.0.1 8443; do
echo -n "."
sleep 5
done
fi
fi
BBB_WEB=$(cat ${SERVLET_DIR}/bigbluebutton/WEB-INF/classes/bigbluebutton.properties | sed -n '/^bigbluebutton.web.serverURL/{s/.*\///;p}')
if ! wget http://$BBB_WEB/bigbluebutton/api -O - --quiet | grep -q SUCCESS; then
echo "Startup unsuccessful: could not connect to http://$BBB_WEB/bigbluebutton/api"
exit 1 exit 1
fi fi
@ -334,7 +353,7 @@ display_bigbluebutton_status () {
/etc/init.d/activemq status /etc/init.d/activemq status
/etc/init.d/nginx status /etc/init.d/nginx status
/etc/init.d/red5 status /etc/init.d/red5 status
/etc/init.d/${TOMCAT} status /etc/init.d/${SERVLET_CONTAINER} status
} }
if [ $# -eq 0 ]; then if [ $# -eq 0 ]; then
@ -473,10 +492,10 @@ while [ $# -gt 0 ]; do
if [ "$1" = "--salt" -o "$1" = "-salt" -o "$1" = "--setsalt" ]; then if [ "$1" = "--salt" -o "$1" = "-salt" -o "$1" = "--setsalt" ]; then
SALT="${2}" SALT="${2}"
if [ -z "$SALT" ]; then if [ -z "$SALT" ]; then
IP=$(cat /var/lib/${TOMCAT}/webapps/bigbluebutton/WEB-INF/classes/bigbluebutton.properties | sed -n '/^bigbluebutton.web.serverURL/{s/.*\///;p}') BBB_WEB=$(cat ${SERVLET_DIR}/bigbluebutton/WEB-INF/classes/bigbluebutton.properties | sed -n '/^bigbluebutton.web.serverURL/{s/.*\///;p}')
SALT=`cat /var/lib/tomcat6/webapps/bigbluebutton/WEB-INF/classes/bigbluebutton.properties | grep securitySalt | cut -d= -f2`; SALT=$(cat ${SERVLET_DIR}/bigbluebutton/WEB-INF/classes/bigbluebutton.properties | grep securitySalt | cut -d= -f2);
echo echo
echo " URL: http://$IP/bigbluebutton/" echo " URL: http://$BBB_WEB/bigbluebutton/"
echo " Salt: $SALT" echo " Salt: $SALT"
echo echo
exit 0 exit 0
@ -644,12 +663,12 @@ if [ $SETUPDEV ]; then
exit 1 exit 1
fi fi
echo "# Copying the bigbluebutton.properites in /var/lib/${TOMCAT}/webapps/bigbluebutton/WEB-INF/classes/bigbluebutton.properties to ~/.grails/bigbluebutton-config.properties" echo "# Copying the bigbluebutton.properites in ${SERVLET_DIR}/bigbluebutton/WEB-INF/classes/bigbluebutton.properties to ~/.grails/bigbluebutton-config.properties"
mkdir -p ~/.grails mkdir -p ~/.grails
cp /var/lib/${TOMCAT}/webapps/bigbluebutton/WEB-INF/classes/bigbluebutton.properties ~/.grails/bigbluebutton-config.properties cp ${SERVLET_DIR}/bigbluebutton/WEB-INF/classes/bigbluebutton.properties ~/.grails/bigbluebutton-config.properties
echo "# Copying the bbb_api_conf.jsp into /var/lib/${TOMCAT}/webapps/bigbluebutton/WEB-INF/classes/bigbluebutton.properties to ${BBBWEBHOME}/web-app/demo" echo "# Copying the bbb_api_conf.jsp into ${SERVLET_DIR}/bigbluebutton/WEB-INF/classes/bigbluebutton.properties to ${BBBWEBHOME}/web-app/demo"
cp /var/lib/${TOMCAT}/webapps/bigbluebutton/demo/bbb_api_conf.jsp ${BBBWEBHOME}/web-app/demo cp ${SERVLET_DIR}/bigbluebutton/demo/bbb_api_conf.jsp ${BBBWEBHOME}/web-app/demo
echo "# Enabling $USER to write to /var/bigbluebutton to upload slides" echo "# Enabling $USER to write to /var/bigbluebutton to upload slides"
sudo chmod -R ugo+rwx /var/bigbluebutton sudo chmod -R ugo+rwx /var/bigbluebutton
@ -665,7 +684,7 @@ if [ $SETUPDEV ]; then
echo " echo "
# Done. To run your local build of bbb-web: # Done. To run your local build of bbb-web:
sudo /etc/init.d/${TOMCAT} stop sudo /etc/init.d/${SERVLET_CONTAINER} stop
cd ${BBBWEBHOME} cd ${BBBWEBHOME}
ant ant
" "
@ -713,7 +732,7 @@ if [ $SETUPDEV ]; then
echo "# Creating /var/bigbluebutton/conference-mock-default/conference-mock-default/room-mock-default" echo "# Creating /var/bigbluebutton/conference-mock-default/conference-mock-default/room-mock-default"
sudo mkdir -p /var/bigbluebutton/conference-mock-default/conference-mock-default/room-mock-default sudo mkdir -p /var/bigbluebutton/conference-mock-default/conference-mock-default/room-mock-default
echo "# chown /var/bigbluebutton/conference-mock-default to tomcat6" echo "# chown /var/bigbluebutton/conference-mock-default to tomcat6"
sudo chown -R tomcat6.tomcat6 /var/bigbluebutton/conference-mock-default sudo chown -R tomcat6:tomcat6 /var/bigbluebutton/conference-mock-default
fi fi
cd $BBBCLIENTHOME cd $BBBCLIENTHOME
@ -868,22 +887,24 @@ check_configuration() {
# #
# Check if BigBlueButto is defined in Nginx # Check if BigBlueButto is defined in Nginx
# #
if [ ! -L /etc/nginx/sites-enabled/bigbluebutton ]; then if [ $PLATFORM != "ubuntu-jetty-intalio" ]; then
echo "# Nginx: BigBlueButton appears to be disabled" if [ ! -L /etc/nginx/sites-enabled/bigbluebutton ]; then
echo " - no symbolic link in /etc/nginx/sites-enabled/bigbluebutton to /etc/nginx/sites-available/bigbluebutton " echo "# Nginx: BigBlueButton appears to be disabled"
echo " - no symbolic link in /etc/nginx/sites-enabled/bigbluebutton to /etc/nginx/sites-available/bigbluebutton "
fi
fi fi
# #
# Make sure the salt for the API matches the server # Make sure the salt for the API matches the server
# #
SALT_PROPERTIES=$(cat /var/lib/${TOMCAT}/webapps/bigbluebutton/WEB-INF/classes/bigbluebutton.properties | tr -d '\r' | sed -n '/securitySalt/{s/.*=//;p}') SALT_PROPERTIES=$(cat ${SERVLET_DIR}/bigbluebutton/WEB-INF/classes/bigbluebutton.properties | tr -d '\r' | sed -n '/securitySalt/{s/.*=//;p}')
SALT_DEMO=$(cat /var/lib/${TOMCAT}/webapps/bigbluebutton/demo/bbb_api_conf.jsp | tr -d '\r' | sed -n '/salt =/{s/.* = "//;s/".*//g;p}') SALT_DEMO=$(cat ${SERVLET_DIR}/bigbluebutton/demo/bbb_api_conf.jsp | tr -d '\r' | sed -n '/salt =/{s/.* = "//;s/".*//g;p}')
if [ "$SALT_PROPERTIES" != "$SALT_DEMO" ]; then if [ "$SALT_PROPERTIES" != "$SALT_DEMO" ]; then
echo "# Salt mismatch: " echo "# Salt mismatch: "
echo "# /var/lib/${TOMCAT}/webapps/bigbluebutton/WEB-INF/classes/bigbluebutton.properties=$SALT_PROPERTIES" echo "# ${SERVLET_DIR}/bigbluebutton/WEB-INF/classes/bigbluebutton.properties=$SALT_PROPERTIES"
echo "# /var/lib/${TOMCAT}/webapps/bigbluebutton/demo/bbb_api_conf.jsp=$SALT_DEMO" echo "# ${SERVLET_DIR}/bigbluebutton/demo/bbb_api_conf.jsp=$SALT_DEMO"
fi fi
@ -891,7 +912,7 @@ check_configuration() {
# Look for properties with no values set # Look for properties with no values set
# #
CONFIG_FILES="$RED5_DIR/webapps/bigbluebutton/WEB-INF/bigbluebutton.properties \ CONFIG_FILES="$RED5_DIR/webapps/bigbluebutton/WEB-INF/bigbluebutton.properties \
/var/lib/${TOMCAT}/webapps/bigbluebutton/WEB-INF/classes/bigbluebutton.properties \ ${SERVLET_DIR}/bigbluebutton/WEB-INF/classes/bigbluebutton.properties \
$RED5_DIR/webapps/sip/WEB-INF/bigbluebutton-sip.properties" $RED5_DIR/webapps/sip/WEB-INF/bigbluebutton-sip.properties"
for file in $CONFIG_FILES ; do for file in $CONFIG_FILES ; do
@ -908,17 +929,17 @@ $RED5_DIR/webapps/sip/WEB-INF/bigbluebutton-sip.properties"
# #
# Check that the supporting applications are installed # Check that the supporting applications are installed
# #
VARFolder=$(cat /var/lib/tomcat6/webapps/bigbluebutton/WEB-INF/classes/bigbluebutton.properties | grep swfToolsDir | cut -d= -f2) VARFolder=$(cat $SERVLET_DIR/bigbluebutton/WEB-INF/classes/bigbluebutton.properties | grep swfToolsDir | cut -d= -f2)
if [ ! -x $VARFolder/pdf2swf ] && [ ! -x $VARFolder/jpeg2swf ] && [ ! -x $VARFolder/png2swf ]; then if [ ! -x $VARFolder/pdf2swf ] && [ ! -x $VARFolder/jpeg2swf ] && [ ! -x $VARFolder/png2swf ]; then
echo "# pdf2swf, jpeg2swf and png2swf are not installed in $VARFolder" echo "# pdf2swf, jpeg2swf and png2swf are not installed in $VARFolder"
fi fi
VARFolder=$(cat /var/lib/tomcat6/webapps/bigbluebutton/WEB-INF/classes/bigbluebutton.properties | grep imageMagickDir | cut -d= -f2) VARFolder=$(cat $SERVLET_DIR/bigbluebutton/WEB-INF/classes/bigbluebutton.properties | grep imageMagickDir | cut -d= -f2)
if [ ! -x $VARFolder/convert ]; then if [ ! -x $VARFolder/convert ]; then
echo "# ImageMagick's convert is not installed in $VARFolder" echo "# ImageMagick's convert is not installed in $VARFolder"
fi fi
VARFolder=$(cat /var/lib/tomcat6/webapps/bigbluebutton/WEB-INF/classes/bigbluebutton.properties | grep ghostScriptExec | cut -d= -f2) VARFolder=$(cat $SERVLET_DIR/bigbluebutton/WEB-INF/classes/bigbluebutton.properties | grep ghostScriptExec | cut -d= -f2)
if [ ! -x $VARFolder ]; then if [ ! -x $VARFolder ]; then
echo "# Ghostscript is not installd in $VARFolder" echo "# Ghostscript is not installd in $VARFolder"
fi fi
@ -1012,14 +1033,14 @@ check_state() {
if ! netstat -ant | grep '8080' > /dev/null; then if ! netstat -ant | grep '8080' > /dev/null; then
print_header print_header
NOT_RUNNING_APPS="${NOT_RUNNING_APPS} ${TOMCAT} or grails" NOT_RUNNING_APPS="${NOT_RUNNING_APPS} ${SERVLET_CONTAINER} or grails"
else else
if ps aux | ps -aef | grep -v grep | grep grails | grep run-app > /dev/null; then if ps aux | ps -aef | grep -v grep | grep grails | grep run-app > /dev/null; then
print_header print_header
RUNNING_APPS="${RUNNING_APPS} Grails" RUNNING_APPS="${RUNNING_APPS} Grails"
echo "# ${TOMCAT}: noticed you are running grails run-app instead of tomcat" echo "# ${SERVLET_CONTAINER}: noticed you are running grails run-app instead of ${SERVLET_CONTAINER}"
else else
RUNNING_APPS="${RUNNING_APPS} ${TOMCAT}" RUNNING_APPS="${RUNNING_APPS} ${SERVLET_CONTAINER}"
fi fi
fi fi
@ -1120,10 +1141,10 @@ check_state() {
fi fi
# #
# Check that tomcat6 started properly and has created log files # Check that the servlet container has started properly and has created log files
# #
if [ -z "$(ls -A $TOMCAT6_LOGS)" ]; then if [ -z "$(ls -A $SERVLET_LOGS)" ]; then
echo "# empty directory: $TOMCAT6_LOGS contains no logs" echo "# empty directory: $SERVLET_LOGS contains no logs"
fi fi
# #
@ -1144,11 +1165,11 @@ check_state() {
# Check if the local server can access the API. This is a common problem when setting up BigBlueButton behind # Check if the local server can access the API. This is a common problem when setting up BigBlueButton behind
# a firewall # a firewall
# #
NGINX_IP=$(cat /etc/nginx/sites-available/bigbluebutton | sed -n '/server_name/{s/.*name[ ]*//;s/;//;p}') BBB_WEB=$(cat ${SERVLET_DIR}/bigbluebutton/WEB-INF/classes/bigbluebutton.properties | sed -n '/^bigbluebutton.web.serverURL/{s/.*\///;p}')
check_no_value server_name /etc/nginx/sites-available/bigbluebutton $NGINX_IP check_no_value server_name /etc/nginx/sites-available/bigbluebutton $BBB_WEB
if ! wget http://$NGINX_IP/bigbluebutton/api -O - --quiet | grep -q SUCCESS; then if ! wget http://$BBB_WEB/bigbluebutton/api -O - --quiet | grep -q SUCCESS; then
echo echo
echo "# This server could not connect to BigBlueButton through http://$NGINX_IP/" echo "# This server could not connect to BigBlueButton through http://$BBB_WEB/"
echo "#" echo "#"
echo "# If you are setting up BigBlueButton behind a firewall, see the FAQ" echo "# If you are setting up BigBlueButton behind a firewall, see the FAQ"
echo "# for steps to setup BigBlueButton behind a firewall." echo "# for steps to setup BigBlueButton behind a firewall."
@ -1192,7 +1213,7 @@ check_state() {
fi fi
SIP_SERVER_HOST=$(cat /usr/share/red5/webapps/sip/WEB-INF/bigbluebutton-sip.properties | sed -n '/sip.server.host=/{s/.*=//;s/;//;p}') SIP_SERVER_HOST=$(cat /usr/share/red5/webapps/sip/WEB-INF/bigbluebutton-sip.properties | sed -n '/sip.server.host=/{s/.*=//;s/;//;p}')
IP=$(ifconfig | grep -v '127.0.0.1' | grep -E "[0-9]*\.[0-9]*\.[0-9]*\.[0-9]*" | head -1 | cut -d: -f2 | awk '{ print $1}') #IP=$(ifconfig | grep -v '127.0.0.1' | grep -E "[0-9]*\.[0-9]*\.[0-9]*\.[0-9]*" | head -1 | cut -d: -f2 | awk '{ print $1}')
if [ $SIP_SERVER_HOST != $IP ]; then if [ $SIP_SERVER_HOST != $IP ]; then
echo echo
echo "# The IP address ($SIP_SERVER_HOST) set for sip.server.host in" echo "# The IP address ($SIP_SERVER_HOST) set for sip.server.host in"
@ -1252,19 +1273,19 @@ if [ $CHECK ]; then
echo echo
echo "/var/www/bigbluebutton/client/conf/config.xml" echo "/var/www/bigbluebutton/client/conf/config.xml"
IP=$(cat /var/www/bigbluebutton/client/conf/config.xml | sed -n '/porttest /{s/.*host="//;s/".*//;p}') PORT_IP=$(cat /var/www/bigbluebutton/client/conf/config.xml | sed -n '/porttest /{s/.*host="//;s/".*//;p}')
echo " Port test (tunnel): $IP" echo " Port test (tunnel): $PORT_IP"
IP=$(cat /var/www/bigbluebutton/client/conf/config.xml | sed -n '/uri.*video/{s/.*rtmp:\/\///;s/\/.*//;p}') RED5_IP=$(cat /var/www/bigbluebutton/client/conf/config.xml | sed -n '/uri.*video/{s/.*rtmp:\/\///;s/\/.*//;p}')
echo " Red5: $IP" echo " Red5: $RED5_IP"
# HOST=$(cat /var/www/bigbluebutton/client/conf/config.xml | sed -n '/recordingHost/{s/.*recordingHost="http:\/\///;s/"//;p}') # HOST=$(cat /var/www/bigbluebutton/client/conf/config.xml | sed -n '/recordingHost/{s/.*recordingHost="http:\/\///;s/"//;p}')
# echo " host for bbb-web interface: $HOST" # echo " host for bbb-web interface: $HOST"
echo echo
echo "/etc/nginx/sites-available/bigbluebutton" echo "/etc/nginx/sites-available/bigbluebutton"
IP=$(cat /etc/nginx/sites-available/bigbluebutton | sed -n '/server_name/{s/.*name[ ]*//;s/;//;p}') NGINX_IP=$(cat /etc/nginx/sites-available/bigbluebutton | sed -n '/server_name/{s/.*name[ ]*//;s/;//;p}')
echo " server name: $IP" echo " server name: $NGINX_IP"
PORT=$(cat /etc/nginx/sites-available/bigbluebutton | sed -n '/listen/{s/.*listen[ ]*//;s/;//;p}') PORT=$(cat /etc/nginx/sites-available/bigbluebutton | sed -n '/listen/{s/.*listen[ ]*//;s/;//;p}')
echo " port: $PORT" echo " port: $PORT"
@ -1272,16 +1293,16 @@ if [ $CHECK ]; then
BBB_CLIENT_DOC_ROOT=$(cat /etc/nginx/sites-available/bigbluebutton | grep \/client -A 1 | head -n 2 | grep root | sed -n '{s/[ ]*root[ ]*//;s/;//;p}') BBB_CLIENT_DOC_ROOT=$(cat /etc/nginx/sites-available/bigbluebutton | grep \/client -A 1 | head -n 2 | grep root | sed -n '{s/[ ]*root[ ]*//;s/;//;p}')
echo " bbb-client dir: $BBB_CLIENT_DOC_ROOT" echo " bbb-client dir: $BBB_CLIENT_DOC_ROOT"
IP=$(cat /var/lib/${TOMCAT}/webapps/bigbluebutton/WEB-INF/classes/bigbluebutton.properties | sed -n '/^bigbluebutton.web.serverURL/{s/.*\///;p}') BBB_WEB_IP=$(cat ${SERVLET_DIR}/bigbluebutton/WEB-INF/classes/bigbluebutton.properties | sed -n '/^bigbluebutton.web.serverURL/{s/.*\///;p}')
echo echo
echo "/var/lib/${TOMCAT}/webapps/bigbluebutton/WEB-INF/classes/bigbluebutton.properties (bbb-web)" echo "${SERVLET_DIR}/bigbluebutton/WEB-INF/classes/bigbluebutton.properties (bbb-web)"
echo " bbb-web host: $IP" echo " bbb-web host: $BBB_WEB_IP"
if [ -f /var/lib/${TOMCAT}/webapps/bigbluebutton/demo/bbb_api_conf.jsp ]; then if [ -f ${SERVLET_DIR}/bigbluebutton/demo/bbb_api_conf.jsp ]; then
IP=$(cat /var/lib/${TOMCAT}/webapps/bigbluebutton/demo/bbb_api_conf.jsp | sed -n '/String BigBlueButtonURL/{s/.*http:\/\///;s/\/.*//;p}' | tr -d '\015') API_IP=$(cat ${SERVLET_DIR}/bigbluebutton/demo/bbb_api_conf.jsp | sed -n '/String BigBlueButtonURL/{s/.*http:\/\///;s/\/.*//;p}' | tr -d '\015')
echo echo
echo "/var/lib/${TOMCAT}/webapps/bigbluebutton/demo/bbb_api_conf.jsp (API demos)" echo "${SERVLET_DIR}/bigbluebutton/demo/bbb_api_conf.jsp (API demos)"
echo " bbb-web-api host: $IP" echo " bbb-web-api host: $API_IP"
fi fi
if [ $VOICE_CONFERENCE == "bbb-voice-freeswitch.xml" ]; then if [ $VOICE_CONFERENCE == "bbb-voice-freeswitch.xml" ]; then
@ -1321,7 +1342,7 @@ if [ $ZIP ]; then
touch /tmp/empty touch /tmp/empty
tar cf /tmp/$LOG_FILE.tar /tmp/empty > /dev/null 2>&1 tar cf /tmp/$LOG_FILE.tar /tmp/empty > /dev/null 2>&1
tar rf /tmp/$LOG_FILE.tar $RED5_DIR/log > /dev/null 2>&1 tar rf /tmp/$LOG_FILE.tar $RED5_DIR/log > /dev/null 2>&1
tar rf /tmp/$LOG_FILE.tar $TOMCAT6_LOGS > /dev/null 2>&1 tar rf /tmp/$LOG_FILE.tar $SERVLET_LOGS > /dev/null 2>&1
tar rf /tmp/$LOG_FILE.tar /var/log/bigbluebutton/* > /dev/null 2>&1 tar rf /tmp/$LOG_FILE.tar /var/log/bigbluebutton/* > /dev/null 2>&1
tar rf /tmp/$LOG_FILE.tar /var/log/nginx/error.log > /dev/null 2>&1 tar rf /tmp/$LOG_FILE.tar /var/log/nginx/error.log > /dev/null 2>&1
tar rf /tmp/$LOG_FILE.tar /var/log/syslog > /dev/null 2>&1 tar rf /tmp/$LOG_FILE.tar /var/log/syslog > /dev/null 2>&1
@ -1388,9 +1409,9 @@ if [ $DEBUG ]; then
fi fi
rm -rf /tmp/t rm -rf /tmp/t
sudo grep Exception $TOMCAT6_LOGS/* | grep -v CacheExceptionHandlerFactory > /tmp/t sudo grep Exception $SERVLET_LOGS/* | grep -v CacheExceptionHandlerFactory > /tmp/t
if [ -s /tmp/t ]; then if [ -s /tmp/t ]; then
echo " -- Exceptions found in $TOMCAT6_LOGS/ -- " echo " -- Exceptions found in $SERVLET_LOGS/ -- "
cat /tmp/t cat /tmp/t
echo echo
fi fi
@ -1483,16 +1504,16 @@ if [ -n "$HOST" ]; then
# #
# Update configuration for BigBlueButton web app # Update configuration for BigBlueButton web app
# #
echo "Assigning $HOST for web application URL in /var/lib/${TOMCAT}/webapps/bigbluebutton/WEB-INF/classes/bigbluebutton.properties" echo "Assigning $HOST for web application URL in ${SERVLET_DIR}/bigbluebutton/WEB-INF/classes/bigbluebutton.properties"
sudo sed -i "s/bigbluebutton.web.serverURL=http:\/\/.*/bigbluebutton.web.serverURL=http:\/\/$HOST/g" \ sudo sed -i "s/bigbluebutton.web.serverURL=http:\/\/.*/bigbluebutton.web.serverURL=http:\/\/$HOST/g" \
/var/lib/${TOMCAT}/webapps/bigbluebutton/WEB-INF/classes/bigbluebutton.properties ${SERVLET_DIR}/bigbluebutton/WEB-INF/classes/bigbluebutton.properties
# 3 paramenter: the file, the variable name, the new value # 3 paramenter: the file, the variable name, the new value
# echo "Assigning $HOST for FreeSWITCH Event Socket Layer URL in /var/lib/${TOMCAT}/webapps/bigbluebutton/WEB-INF/classes/bigbluebutton.properties" # echo "Assigning $HOST for FreeSWITCH Event Socket Layer URL in ${SERVLET_DIR}/bigbluebutton/WEB-INF/classes/bigbluebutton.properties"
# change_var_ip /usr/share/red5/webapps/bigbluebutton/WEB-INF/bigbluebutton.properties esl.host $HOST # change_var_ip /usr/share/red5/webapps/bigbluebutton/WEB-INF/bigbluebutton.properties esl.host $HOST
# cat /var/lib/${TOMCAT}/webapps/bigbluebutton/WEB-INF/classes/bigbluebutton.properties # cat ${SERVLET_DIR}/bigbluebutton/WEB-INF/classes/bigbluebutton.properties
# #
# Update nginx # Update nginx
@ -1511,10 +1532,10 @@ if [ -n "$HOST" ]; then
# Update api demos # Update api demos
# #
if [ -f /var/lib/${TOMCAT}/webapps/bigbluebutton/demo/bbb_api_conf.jsp ]; then if [ -f ${SERVLET_DIR}/bigbluebutton/demo/bbb_api_conf.jsp ]; then
echo "Assigning $HOST for api demos in /var/lib/${TOMCAT}/webapps/bigbluebutton/demo/bbb_api_conf.jsp" echo "Assigning $HOST for api demos in ${SERVLET_DIR}/bigbluebutton/demo/bbb_api_conf.jsp"
sudo sed -i "s/BigBlueButtonURL = \"http:\/\/\([^\"\/]*\)\([\"\/]\)/BigBlueButtonURL = \"http:\/\/$HOST\2/g" \ sudo sed -i "s/BigBlueButtonURL = \"http:\/\/\([^\"\/]*\)\([\"\/]\)/BigBlueButtonURL = \"http:\/\/$HOST\2/g" \
/var/lib/${TOMCAT}/webapps/bigbluebutton/demo/bbb_api_conf.jsp ${SERVLET_DIR}/bigbluebutton/demo/bbb_api_conf.jsp
fi fi
# #
@ -1758,8 +1779,8 @@ if [ $CLEAN ]; then
rm -rf $RED5_DIR/log/* rm -rf $RED5_DIR/log/*
fi fi
if [ $TOMCAT6_LOGS ]; then if [ $SERVLET_LOGS ]; then
rm -rf $TOMCAT6_LOGS/* rm -rf $SERVLET_LOGS/*
fi fi
rm -rf /var/log/nginx/* rm -rf /var/log/nginx/*

View File

@ -22,6 +22,9 @@
package org.bigbluebutton.deskshare.client; package org.bigbluebutton.deskshare.client;
import javax.swing.JApplet; import javax.swing.JApplet;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import java.awt.Image; import java.awt.Image;
public class DeskShareApplet extends JApplet implements ClientListener { public class DeskShareApplet extends JApplet implements ClientListener {
@ -92,7 +95,20 @@ public class DeskShareApplet extends JApplet implements ClientListener {
} }
public void onClientStop(ExitCode reason) { public void onClientStop(ExitCode reason) {
client.stop(); // determine if client is disconnected _PTS_272_
if ( ExitCode.CONNECTION_TO_DESKSHARE_SERVER_DROPPED == reason ){
JFrame pframe = new JFrame("Desktop Sharing Disconneted");
if ( null != pframe ){
client.disconnected();
JOptionPane.showMessageDialog(pframe,
"Disconnected. Reason: Lost connection to the server." + reason ,
"Disconnected" ,JOptionPane.ERROR_MESSAGE );
}else{
System.out.println("Desktop sharing allocate memory failed.");
}
}else{
client.stop();
}
} }
} }

View File

@ -57,6 +57,32 @@ public class DeskshareClient {
screenSharer.start(); screenSharer.start();
} }
/*****************************************************************************
; disconnected
;----------------------------------------------------------------------------
; DESCRIPTION
; This routine is used to set the desktop sharing string to disconnected.
;
; RETURNS : N/A
;
; INTERFACE NOTES
;
; INPUT : N/A
;
; OUTPUT : N/A
;
; IMPLEMENTATION
;
; HISTORY
; __date__ : PTS:
; 2010.11.19 problem 272
;
******************************************************************************/
public void disconnected(){
System.out.println(NAME + "Disconneted");
screenSharer.disconnected();
} // END FUNCTION disconnected
public void stop() { public void stop() {
System.out.println(NAME + "Stop"); System.out.println(NAME + "Stop");
screenSharer.stop(); screenSharer.stop();

View File

@ -64,6 +64,35 @@ public class DeskshareSystemTray {
EventQueue.invokeLater(runner); EventQueue.invokeLater(runner);
} }
/*****************************************************************************
; disconnectIconSystemTrayMessage
;----------------------------------------------------------------------------
; DESCRIPTION
; This routine is used to change icon system tray message string
; to disconnect.
;
; RETURNS : N/A
;
; INTERFACE NOTES
;
; INPUT : N/A
;
; OUTPUT : N/A
;
; IMPLEMENTATION
;
; HISTORY
; __date__ : PTS:
; 2010.11.19 problem 272
;
******************************************************************************/
public void disconnectIconSystemTrayMessage(){
trayIcon.setToolTip("Disconnected");
trayIcon.displayMessage("Deskshare Disconnected" ,
"You're disconnected from desktop sharing",
TrayIcon.MessageType.ERROR);
} // END FUNCTION disconnectIconSystemTrayMessage
public void removeIconFromSystemTray() { public void removeIconFromSystemTray() {
if (tray != null && trayIcon != null) { if (tray != null && trayIcon != null) {
tray.remove(trayIcon); tray.remove(trayIcon);

View File

@ -41,6 +41,32 @@ public class FullScreenSharer implements ScreenSharer {
listener = l; listener = l;
} }
/*****************************************************************************
; disconnected
;----------------------------------------------------------------------------
; DESCRIPTION
; This routine is used to pop up the dialog box and change icon try
; message when client is disconnected from the server.
;
; RETURNS : N/A
;
; INTERFACE NOTES
;
; INPUT : N/A
;
; OUTPUT : N/A
;
; IMPLEMENTATION
;
; HISTORY
; __date__ : PTS:
; 2010.11.19 problem 272
;
******************************************************************************/
public void disconnected(){
sharer.disconnectSharing();
} // END FUNCTION disconnected
public void stop() { public void stop() {
sharer.stopSharing(); sharer.stopSharing();
} }

View File

@ -50,6 +50,34 @@ public class ScreenRegionSharer implements ScreenSharer {
listener = l; listener = l;
} }
/*****************************************************************************
; disconnected
;----------------------------------------------------------------------------
; DESCRIPTION
; This routine is used to pop up the dialog and change icon try message when
; client is disconnected from server.
;
; RETURNS : N/A
;
; INTERFACE NOTES
;
; INPUT : N/A
;
; OUTPUT : N/A
;
; IMPLEMENTATION
;
; HISTORY
; __date__ : PTS:
; 2010.11.19 problem 272
;
******************************************************************************/
public void disconnected(){
frame.setVisible(false);
sharer.disconnectSharing();
System.out.println(NAME + "Desktop sharing disconneted");
} // END FUNCTION disconnected
public void stop() { public void stop() {
frame.setVisible(false); frame.setVisible(false);
sharer.stopSharing(); sharer.stopSharing();

View File

@ -23,6 +23,7 @@ package org.bigbluebutton.deskshare.client;
public interface ScreenSharer { public interface ScreenSharer {
void start(); void start();
void disconnected(); // 2010.11.19 _PTS_272_
void stop(); void stop();
void addClientListener(ClientListener l); void addClientListener(ClientListener l);
} }

View File

@ -82,6 +82,36 @@ public class ScreenSharerRunner {
} }
} }
/*****************************************************************************
; disconnectSharing
;----------------------------------------------------------------------------
; DESCRIPTION
; This routine is used to stop the screen capture, change desktop
; sharing system icon tray message.
;
; RETURNS : N/A
;
; INTERFACE NOTES
;
; INPUT : N/A
;
; OUTPUT : N/A
;
; IMPLEMENTATION
;
; HISTORY
; __date__ : PTS:
; 2010.11.19 problem 272
;
******************************************************************************/
public void disconnectSharing(){
System.out.println(NAME + "Disconneted");
System.out.println(NAME + "Change system tray icon message");
tray.disconnectIconSystemTrayMessage();
captureTaker.stop();
mouseLocTaker.stop();
} // END FUNCTION disconnectSharing
public void stopSharing() { public void stopSharing() {
System.out.println(NAME + "Stopping"); System.out.println(NAME + "Stopping");
System.out.println(NAME + "Removing icon from system tray."); System.out.println(NAME + "Removing icon from system tray.");

View File

@ -19,6 +19,7 @@
* *
* ===License Header=== * ===License Header===
*/ */
package org.bigbluebutton.deskshare.client.frame; package org.bigbluebutton.deskshare.client.frame;
import java.awt.BasicStroke; import java.awt.BasicStroke;
@ -30,6 +31,7 @@ import java.awt.Frame;
import java.awt.GradientPaint; import java.awt.GradientPaint;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice; import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment; import java.awt.GraphicsEnvironment;
import java.awt.Insets; import java.awt.Insets;
@ -116,6 +118,104 @@ class WindowlessFrame implements Serializable {
private final BarFrame mLeftBorder; private final BarFrame mLeftBorder;
private ToolbarFrame mToolbarFrame; private ToolbarFrame mToolbarFrame;
private MultiScreen mScreen = new MultiScreen();
/*****************************************************************************
; Class MultiScreen
;----------------------------------------------------------------------------
; DESCRIPTION
; This class is used to detect if the system has more than one screen.
******************************************************************************/
private class MultiScreen {
private int minX=0 ; //minimum of x position
private int totalWidth=0 ; // total screen resolution
private int curWidth=0 ; // primary screen width
private GraphicsEnvironment ge ;
private GraphicsDevice[] screenDevice ;
private boolean ismultiscreen=false ;
/*****************************************************************************
; MultiScreen
;----------------------------------------------------------------------------
; DESCRIPTION
; This is the class constructor.
;
; RETURNS : N/A
;
; INTERFACE NOTES
;
; INPUT : N/A
;
; OUTPUT : N/A
;
; IMPLEMENTATION
;
; HISTORY
; __date__ : PTS:
; 2010.11.16 problem 644 and 647
;
******************************************************************************/
private MultiScreen(){
int i ;
ge = GraphicsEnvironment.getLocalGraphicsEnvironment() ;
screenDevice = ge.getScreenDevices() ;
if ( 1 < screenDevice.length ){
// this is the case for multiple devices.
// set the flag to indicate multiple devices on the system.
ismultiscreen=true ;
for ( i=0; i<screenDevice.length; i++){
GraphicsConfiguration[] gc = screenDevice[i].getConfigurations() ;
// determine the minimum x position for the main screen
if ( gc[0].getBounds().x <= minX ){
minX = gc[0].getBounds().x;
}
// determine the total screen size
if ( gc[0].getBounds().x >= 0){
totalWidth = totalWidth + gc[0].getBounds().width;
}
}
}else{
// this is the case for one screen only.
ismultiscreen = false ;
}
// set the main screen width
curWidth = screenDevice[0].getConfigurations()[0].getBounds().width ;
} // END FUNCTION MultiScreen
/*****************************************************************************
; isMultiScreen
;----------------------------------------------------------------------------
; DESCRIPTION
; This routine returns if the system is multi-screen or not.
;
; RETURNS : true/false
;
; INTERFACE NOTES
;
; INPUT : N/A
;
;
; IMPLEMENTATION
;
; HISTORY
; __date__ : PTS:
; 2010.11.16 problem 644 and 647
;
******************************************************************************/
public boolean isMultiScreen(){
return ismultiscreen ;
} // END FUNCTION isMultiScreen
} // END CLASS MultiScreen
private class ToolbarFrame extends Window implements LocationAndSizeUpdateable { private class ToolbarFrame extends Window implements LocationAndSizeUpdateable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@ -149,8 +249,51 @@ class WindowlessFrame implements Serializable {
@Override @Override
public void mouseDragged(MouseEvent e) { public void mouseDragged(MouseEvent e) {
final int changeInX = e.getLocationOnScreen().x - mActionOffset.x - mTopLeft.x; int changeInX = e.getLocationOnScreen().x - mActionOffset.x - mTopLeft.x;
final int changeInY = e.getLocationOnScreen().y - mActionOffset.y - mTopLeft.y; int changeInY = e.getLocationOnScreen().y - mActionOffset.y - mTopLeft.y;
Toolkit tk = Toolkit.getDefaultToolkit();
Dimension d = tk.getScreenSize();
// check if multiscreen
if ( false == mScreen.isMultiScreen() ){
// case one screen only
if (mTopLeft.x < 1 && changeInX < 0) {
mTopLeft.x = 0;
changeInX = 0;
}
if (mTopLeft.y < 1 && changeInY < 0) {
mTopLeft.y = 0;
changeInY = 0;
}
if (mTopLeft.x + mOverallSize.width > (d.width-6) && changeInX > 0) {
mTopLeft.x = d.width - mOverallSize.width-5;
changeInX = 0;
}
if (mTopLeft.y + mOverallSize.height > (d.height-6) && changeInY > 0) {
mTopLeft.y = d.height - mOverallSize.height-5;
changeInY = 0;
}
}else{
// case multiple screen
if (mTopLeft.x < mScreen.minX+1 && changeInX < 0) {
mTopLeft.x = mScreen.minX;
changeInX = 0;
}
if (mTopLeft.y < 1 && changeInY < 0) {
mTopLeft.y = 0;
changeInY = 0;
}
if (mTopLeft.x + mOverallSize.width > (mScreen.totalWidth-6) && changeInX > 0) {
mTopLeft.x = mScreen.totalWidth - mOverallSize.width-5;
changeInX = 0;
}
if (mTopLeft.y + mOverallSize.height > (d.height-6) && changeInY > 0) {
mTopLeft.y = d.height - mOverallSize.height-5;
changeInY = 0;
}
}
if (mMoving.get() && !e.isConsumed()) { if (mMoving.get() && !e.isConsumed()) {
WindowlessFrame.this.setLocation(changeInX + mTopLeft.x, changeInY + mTopLeft.y); WindowlessFrame.this.setLocation(changeInX + mTopLeft.x, changeInY + mTopLeft.y);
} }
@ -175,7 +318,7 @@ class WindowlessFrame implements Serializable {
private class WindowlessFrameResizingMouseListener extends MouseAdapter { private class WindowlessFrameResizingMouseListener extends MouseAdapter {
private static final int CORNER_SIZE = 15; private static final int CORNER_SIZE = 150;
private AtomicBoolean mResizing = new AtomicBoolean(false); private AtomicBoolean mResizing = new AtomicBoolean(false);
@ -185,15 +328,28 @@ class WindowlessFrame implements Serializable {
@Override @Override
public void mouseDragged(MouseEvent e) { public void mouseDragged(MouseEvent e) {
final int changeInX = e.getLocationOnScreen().x - mActionOffset.x - mTopLeft.x; int changeInX = e.getLocationOnScreen().x - mActionOffset.x - mTopLeft.x;
final int changeInY = e.getLocationOnScreen().y - mActionOffset.y - mTopLeft.y; final int changeInY = e.getLocationOnScreen().y - mActionOffset.y - mTopLeft.y;
Toolkit tk = Toolkit.getDefaultToolkit();
Dimension d = tk.getScreenSize();
if (mResizing.get()) { if (mResizing.get()) {
int newH = mOriginalSize.height; int newH = mOriginalSize.height;
int newW = mOriginalSize.width; int newW = mOriginalSize.width;
if (mCorner == Corner.SOUTHEAST) { if (Corner.SOUTHEAST == mCorner) {
newH += changeInY;
newW += changeInX; if (e.getLocationOnScreen().x < mTopLeft.x+5) {
} else if (mCorner == Corner.NORTHEAST) { newW = 5;
} else {
newW += changeInX;
}
if (e.getLocationOnScreen().y < mTopLeft.y+5) {
newH = 5;
} else {
newH += changeInY;
}
} /*else if (mCorner == Corner.NORTHEAST) {
mTopLeft.y = mTopLeft.y + changeInY; mTopLeft.y = mTopLeft.y + changeInY;
newH = mOverallSize.height + -changeInY; newH = mOverallSize.height + -changeInY;
newW += changeInX; newW += changeInX;
@ -206,8 +362,33 @@ class WindowlessFrame implements Serializable {
newH += changeInY; newH += changeInY;
mTopLeft.x = mTopLeft.x + changeInX; mTopLeft.x = mTopLeft.x + changeInX;
newW = mOverallSize.width + -changeInX; newW = mOverallSize.width + -changeInX;
} }*/
//System.out.println("orig size: " + mOriginalSize + ", newH: " + newH + ", newW: " + newW + ", X: " + changeInX + ", Y: " + changeInY); //System.out.println("orig size: " + mOriginalSize + ", newH: " + newH + ", newW: " + newW + ", X: " + changeInX + ", Y: " + changeInY);
if (newH + mTopLeft.y > d.height-5){
newH = d.height - mTopLeft.y-5;
}
// check if multiple screen _PTS_644_ _PTS_647_
if ( false == mScreen.isMultiScreen() ){
// one screen only
if (newW + mTopLeft.x > d.width-5){
newW = d.width - mTopLeft.x-5;
}
}else{
int mWidth=0 ;
if ( mTopLeft.x > mScreen.curWidth ){
mWidth = mScreen.totalWidth ;
}else{
mWidth = d.width ;
}
if (newW + mTopLeft.x > mWidth-5 && mTopLeft.x >= 0){
newW = mWidth - mTopLeft.x-5;
}else if (mTopLeft.x<0 && mTopLeft.x + newW > -5){
newW = - mTopLeft.x-5;
}
}
WindowlessFrame.this.setSize(newH, newW); WindowlessFrame.this.setSize(newH, newW);
e.consume(); e.consume();
} }
@ -235,13 +416,14 @@ class WindowlessFrame implements Serializable {
private Corner nearCorner(Point mouse) { private Corner nearCorner(Point mouse) {
if (isNearBottomRightCorner(mouse)) { if (isNearBottomRightCorner(mouse)) {
return Corner.SOUTHEAST; return Corner.SOUTHEAST;
} else if (isNearTopRightCorner(mouse)) { } /* else if (isNearTopRightCorner(mouse)) {
return Corner.NORTHEAST; return Corner.NORTHEAST;
} else if (isNearTopLeftCorner(mouse)) { } else if (isNearTopLeftCorner(mouse)) {
return Corner.NORTHWEST; return Corner.NORTHWEST;
} else if (isNearBottomLeftCorner(mouse)) { } else if (isNearBottomLeftCorner(mouse)) {
return Corner.SOUTHWEST; return Corner.SOUTHWEST;
} }
*/
return null; return null;
} }
@ -251,7 +433,7 @@ class WindowlessFrame implements Serializable {
return xToBotLeft < CORNER_SIZE && yToBotLeft < CORNER_SIZE; return xToBotLeft < CORNER_SIZE && yToBotLeft < CORNER_SIZE;
} }
private boolean isNearTopRightCorner(Point mouse) { /* private boolean isNearTopRightCorner(Point mouse) {
int xToTopRight = Math.abs(mTopLeft.x + (int) mOverallSize.getWidth() - mouse.x); int xToTopRight = Math.abs(mTopLeft.x + (int) mOverallSize.getWidth() - mouse.x);
int yToTopRight = Math.abs(mTopLeft.y - mouse.y); int yToTopRight = Math.abs(mTopLeft.y - mouse.y);
return xToTopRight < CORNER_SIZE && yToTopRight < CORNER_SIZE; return xToTopRight < CORNER_SIZE && yToTopRight < CORNER_SIZE;
@ -268,17 +450,22 @@ class WindowlessFrame implements Serializable {
int yToTopLeft = Math.abs(mTopLeft.y - mouse.y); int yToTopLeft = Math.abs(mTopLeft.y - mouse.y);
return xToTopLeft < CORNER_SIZE && yToTopLeft < CORNER_SIZE; return xToTopLeft < CORNER_SIZE && yToTopLeft < CORNER_SIZE;
} }
*/
@Override @Override
public void mouseMoved(MouseEvent e) { public void mouseMoved(MouseEvent e) {
final Point mouse = e.getLocationOnScreen(); final Point mouse = e.getLocationOnScreen();
/*
if (isNearTopLeftCorner(mouse)) { if (isNearTopLeftCorner(mouse)) {
e.getComponent().setCursor(Cursor.getPredefinedCursor(Cursor.NW_RESIZE_CURSOR)); e.getComponent().setCursor(Cursor.getPredefinedCursor(Cursor.NW_RESIZE_CURSOR));
} else if (isNearBottomLeftCorner(mouse)) { } else if (isNearBottomLeftCorner(mouse)) {
e.getComponent().setCursor(Cursor.getPredefinedCursor(Cursor.SW_RESIZE_CURSOR)); e.getComponent().setCursor(Cursor.getPredefinedCursor(Cursor.SW_RESIZE_CURSOR));
} else if (isNearTopRightCorner(mouse)) { } else if (isNearTopRightCorner(mouse)) {
e.getComponent().setCursor(Cursor.getPredefinedCursor(Cursor.NE_RESIZE_CURSOR)); e.getComponent().setCursor(Cursor.getPredefinedCursor(Cursor.NE_RESIZE_CURSOR));
} else if (isNearBottomRightCorner(mouse)) { } else
*/
if (isNearBottomRightCorner(mouse)) {
e.getComponent().setCursor(Cursor.getPredefinedCursor(Cursor.SE_RESIZE_CURSOR)); e.getComponent().setCursor(Cursor.getPredefinedCursor(Cursor.SE_RESIZE_CURSOR));
} else { } else {
e.getComponent().setCursor(Cursor.getDefaultCursor()); e.getComponent().setCursor(Cursor.getDefaultCursor());