Merge branch 'master' of github.com:bigbluebutton/bigbluebutton into record-and-playback-feature
This commit is contained in:
commit
d6c8b30e19
@ -19,14 +19,12 @@
|
||||
**/
|
||||
package org.bigbluebutton.voiceconf.red5;
|
||||
|
||||
import java.text.MessageFormat;
|
||||
import java.util.List;
|
||||
import org.slf4j.Logger;
|
||||
import org.bigbluebutton.voiceconf.sip.PeerNotFoundException;
|
||||
import org.bigbluebutton.voiceconf.sip.SipPeerManager;
|
||||
import org.red5.logging.Red5LoggerFactory;
|
||||
import org.red5.server.adapter.MultiThreadedApplicationAdapter;
|
||||
import org.red5.server.api.IClient;
|
||||
import org.red5.server.api.IConnection;
|
||||
import org.red5.server.api.IScope;
|
||||
import org.red5.server.api.Red5;
|
||||
@ -72,22 +70,43 @@ public class Application extends MultiThreadedApplicationAdapter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean appJoin(IClient client, IScope scope) {
|
||||
log.debug("VoiceConferenceApplication roomJoin[" + client.getId() + "]");
|
||||
clientConnManager.createClient(client.getId(), (IServiceCapableConnection) Red5.getConnectionLocal());
|
||||
public boolean appConnect(IConnection conn, Object[] params) {
|
||||
String userid = ((String) params[0]).toString();
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appLeave(IClient client, IScope scope) {
|
||||
log.debug("VoiceConferenceApplication roomLeave[" + client.getId() + "]");
|
||||
clientConnManager.removeClient(client.getId());
|
||||
log.debug( "Red5SIP Client closing client {}", client.getId());
|
||||
public void appDisconnect(IConnection conn) {
|
||||
String clientId = Red5.getConnectionLocal().getClient().getId();
|
||||
String userid = getUserId();
|
||||
String username = getUsername();
|
||||
|
||||
String remoteHost = Red5.getConnectionLocal().getRemoteAddress();
|
||||
int remotePort = Red5.getConnectionLocal().getRemotePort();
|
||||
log.info("[clientid={}] disconnnected from {}.", clientId, remoteHost + ":" + remotePort);
|
||||
log.debug("{} [clientid={}] is leaving the voice conf app. Removing from ConnectionManager.", username + "[uid=" + userid + "]", clientId);
|
||||
|
||||
clientConnManager.removeClient(clientId);
|
||||
|
||||
String peerId = (String) Red5.getConnectionLocal().getAttribute("VOICE_CONF_PEER");
|
||||
if (peerId != null) {
|
||||
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) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
@ -97,13 +116,16 @@ public class Application extends MultiThreadedApplicationAdapter {
|
||||
|
||||
@Override
|
||||
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());
|
||||
IConnection conn = Red5.getConnectionLocal();
|
||||
String peerId = (String) conn.getAttribute("VOICE_CONF_PEER");
|
||||
if (peerId != null) {
|
||||
super.streamPublishStart(stream);
|
||||
String clientId = conn.getClient().getId();
|
||||
sipPeerManager.startTalkStream(peerId, clientId, stream, conn.getScope());
|
||||
// recordStream(stream);
|
||||
}
|
||||
@ -128,12 +150,15 @@ public class Application extends MultiThreadedApplicationAdapter {
|
||||
|
||||
@Override
|
||||
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();
|
||||
String peerId = (String) conn.getAttribute("VOICE_CONF_PEER");
|
||||
if (peerId != null) {
|
||||
super.streamPublishStart(stream);
|
||||
String clientId = conn.getClient().getId();
|
||||
sipPeerManager.stopTalkStream(peerId, clientId, stream, conn.getScope());
|
||||
super.streamBroadcastClose(stream);
|
||||
}
|
||||
@ -179,4 +204,16 @@ public class Application extends MultiThreadedApplicationAdapter {
|
||||
public void setClientConnectionManager(ClientConnectionManager ccm) {
|
||||
clientConnManager = ccm;
|
||||
}
|
||||
|
||||
private String getUserId() {
|
||||
String userid = (String) Red5.getConnectionLocal().getAttribute("USERID");
|
||||
if ((userid == null) || ("".equals(userid))) userid = "unknown-userid";
|
||||
return userid;
|
||||
}
|
||||
|
||||
private String getUsername() {
|
||||
String username = (String) Red5.getConnectionLocal().getAttribute("USERNAME");
|
||||
if ((username == null) || ("".equals(username))) username = "UNKNOWN-CALLER";
|
||||
return username;
|
||||
}
|
||||
}
|
||||
|
@ -28,10 +28,14 @@ private static Logger log = Red5LoggerFactory.getLogger(ClientConnection.class,
|
||||
|
||||
private final IServiceCapableConnection connection;
|
||||
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.connId = connId;
|
||||
this.userid = userid;
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getConnId() {
|
||||
@ -39,17 +43,17 @@ private static Logger log = Red5LoggerFactory.getLogger(ClientConnection.class,
|
||||
}
|
||||
|
||||
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});
|
||||
}
|
||||
|
||||
public void onJoinConferenceFail() {
|
||||
log.debug("onOutgoingCallFailed");
|
||||
log.debug("Notify client that {} [{}] failed to join the conference.", username, userid);
|
||||
connection.invoke("failedToJoinVoiceConferenceCallback", new Object[] {"onUaCallFailed"});
|
||||
}
|
||||
|
||||
public void onLeaveConference() {
|
||||
log.debug("onCallClosed");
|
||||
log.debug("Notify client that {} [{}] left the conference.", username, userid);
|
||||
connection.invoke("disconnectedFromJoinVoiceConferenceCallback", new Object[] {"onUaCallClosed"});
|
||||
}
|
||||
}
|
||||
|
@ -31,14 +31,18 @@ public class ClientConnectionManager {
|
||||
|
||||
private Map<String, ClientConnection> clients = new ConcurrentHashMap<String, ClientConnection>();
|
||||
|
||||
public void createClient(String id, IServiceCapableConnection connection) {
|
||||
ClientConnection cc = new ClientConnection(id, connection);
|
||||
public void createClient(String id, String userid, String username, IServiceCapableConnection connection) {
|
||||
ClientConnection cc = new ClientConnection(id, userid, username, connection);
|
||||
clients.put(id, cc);
|
||||
}
|
||||
|
||||
public void removeClient(String 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) {
|
||||
@ -46,7 +50,7 @@ public class ClientConnectionManager {
|
||||
if (cc != null) {
|
||||
cc.onJoinConferenceSuccess(usertalkStream, userListenStream, codec);
|
||||
} 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) {
|
||||
cc.onJoinConferenceFail();
|
||||
} 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) {
|
||||
cc.onLeaveConference();
|
||||
} else {
|
||||
log.warn("Can't find connection {}", clientId);
|
||||
log.warn("Can't find client {} to inform user that she has left the conference.", clientId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -35,7 +35,11 @@ public class Service {
|
||||
private MessageFormat callExtensionPattern = new MessageFormat("{0}");
|
||||
|
||||
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 });
|
||||
try {
|
||||
sipPeerManager.call(peerId, getClientId(), callerName, extension);
|
||||
@ -48,7 +52,10 @@ public class Service {
|
||||
}
|
||||
|
||||
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 {
|
||||
sipPeerManager.hangup(peerId, getClientId());
|
||||
return true;
|
||||
@ -70,4 +77,16 @@ public class Service {
|
||||
public void setSipPeerManager(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;
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,9 @@ import java.util.Set;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
|
||||
import org.red5.logging.Red5LoggerFactory;
|
||||
import org.red5.server.api.IConnection;
|
||||
import org.red5.server.api.IScope;
|
||||
import org.red5.server.api.Red5;
|
||||
import org.red5.server.api.event.IEvent;
|
||||
import org.red5.server.api.stream.IBroadcastStream;
|
||||
import org.red5.server.api.stream.IStreamCodecInfo;
|
||||
@ -140,11 +142,11 @@ public class AudioBroadcastStream implements IBroadcastStream, IProvider, IPipeC
|
||||
}
|
||||
|
||||
public void start() {
|
||||
log.trace("start()");
|
||||
log.debug("Starting AudioBroadcastStream()");
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
log.trace("stop");
|
||||
log.debug("Stopping AudioBroadcastStream");
|
||||
}
|
||||
|
||||
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) {
|
||||
// log.trace("dispatchEvent(event:{})", event);
|
||||
if (event instanceof IRTMPEvent) {
|
||||
IRTMPEvent rtmpEvent = (IRTMPEvent) event;
|
||||
if (livePipe != null) {
|
||||
RTMPMessage msg = new RTMPMessage();
|
||||
|
||||
msg.setBody(rtmpEvent);
|
||||
|
||||
if (creationTime == null)
|
||||
creationTime = (long)rtmpEvent.getTimestamp();
|
||||
|
||||
try {
|
||||
|
||||
// log.debug("dispatchEvent(event:)" + event);
|
||||
livePipe.pushMessage(msg);
|
||||
|
||||
|
@ -21,8 +21,10 @@ package org.bigbluebutton.voiceconf.red5.media;
|
||||
|
||||
public class AudioByteData {
|
||||
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];
|
||||
System.arraycopy(data, 0, this.data, 0, data.length);
|
||||
}
|
||||
@ -30,4 +32,8 @@ public class AudioByteData {
|
||||
public byte[] getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public boolean status() {
|
||||
return poison;
|
||||
}
|
||||
}
|
||||
|
@ -81,8 +81,11 @@ public class CallStream implements StreamObserver {
|
||||
}
|
||||
|
||||
public void startTalkStream(IBroadcastStream broadcastStream, IScope scope) throws StreamException {
|
||||
log.debug("Starting userListenSteam");
|
||||
userListenStream.start();
|
||||
log.debug("userTalkStream setup");
|
||||
userTalkStream.start(broadcastStream, scope);
|
||||
log.debug("userTalkStream Started");
|
||||
}
|
||||
|
||||
public void stopTalkStream(IBroadcastStream broadcastStream, IScope scope) {
|
||||
@ -90,6 +93,7 @@ public class CallStream implements StreamObserver {
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
log.debug("Stopping call stream");
|
||||
userListenStream.stop();
|
||||
}
|
||||
|
||||
|
@ -19,12 +19,12 @@
|
||||
**/
|
||||
package org.bigbluebutton.voiceconf.red5.media;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PipedInputStream;
|
||||
import java.io.PipedOutputStream;
|
||||
import java.net.DatagramSocket;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
import org.apache.mina.core.buffer.IoBuffer;
|
||||
import org.bigbluebutton.voiceconf.red5.media.transcoder.FlashToSipTranscoder;
|
||||
import org.bigbluebutton.voiceconf.red5.media.transcoder.TranscodedAudioDataListener;
|
||||
@ -41,7 +41,9 @@ import org.slf4j.Logger;
|
||||
public class FlashToSipAudioStream {
|
||||
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 Runnable audioDataProcessor;
|
||||
private volatile boolean processAudioData = false;
|
||||
@ -58,6 +60,13 @@ public class FlashToSipAudioStream {
|
||||
this.srcSocket = srcSocket;
|
||||
this.connInfo = connInfo;
|
||||
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 {
|
||||
@ -76,10 +85,9 @@ public class FlashToSipAudioStream {
|
||||
|
||||
if (packet instanceof AudioData) {
|
||||
byte[] data = SerializeUtils.ByteBufferToByteArray(buf);
|
||||
AudioByteData abd = new AudioByteData(data);
|
||||
try {
|
||||
audioDataQ.put(abd);
|
||||
} catch (InterruptedException e) {
|
||||
streamFromFlash.write(data, 1, data.length-1);
|
||||
} catch (IOException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
@ -101,11 +109,23 @@ public class FlashToSipAudioStream {
|
||||
}
|
||||
|
||||
private void processAudioData() {
|
||||
int len = 64;
|
||||
byte[] nellyAudio = new byte[len];
|
||||
int remaining = len;
|
||||
int offset = 0;
|
||||
TranscodedAudioListener transcodedAudioListener = new TranscodedAudioListener();
|
||||
while (processAudioData) {
|
||||
try {
|
||||
AudioByteData abd = audioDataQ.take();
|
||||
transcoder.transcode(abd, 1, abd.getData().length-1, new TranscodedAudioListener());
|
||||
} catch (InterruptedException e) {
|
||||
int bytesRead = streamToSip.read(nellyAudio, offset, remaining);
|
||||
remaining -= bytesRead;
|
||||
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
|
||||
e.printStackTrace();
|
||||
}
|
||||
@ -114,7 +134,12 @@ public class FlashToSipAudioStream {
|
||||
|
||||
public void stop(IBroadcastStream broadcastStream, IScope scope) {
|
||||
broadcastStream.removeStreamListener(mInputListener);
|
||||
if (broadcastStream != null) {
|
||||
broadcastStream.stop();
|
||||
broadcastStream.close();
|
||||
}
|
||||
processAudioData = false;
|
||||
srcSocket.close();
|
||||
}
|
||||
|
||||
public String getStreamName() {
|
||||
@ -124,7 +149,7 @@ public class FlashToSipAudioStream {
|
||||
private class TranscodedAudioListener implements TranscodedAudioDataListener {
|
||||
@Override
|
||||
public void handleTranscodedAudioData(byte[] audioData, long timestamp) {
|
||||
if (audioData != null) {
|
||||
if (audioData != null && processAudioData) {
|
||||
rtpSender.sendAudio(audioData, transcoder.getCodecId(), timestamp);
|
||||
} else {
|
||||
log.warn("Transcodec audio is null. Discarding.");
|
||||
|
@ -84,14 +84,11 @@ public class RtpStreamReceiver {
|
||||
public void receiveRtpPackets() {
|
||||
int packetReceivedCounter = 0;
|
||||
int internalBufferLength = payloadLength + RTP_HEADER_SIZE;
|
||||
byte[] internalBuffer;
|
||||
RtpPacket rtpPacket;
|
||||
byte[] internalBuffer = new byte[internalBufferLength];
|
||||
RtpPacket rtpPacket = new RtpPacket(internalBuffer, internalBufferLength);;
|
||||
|
||||
while (receivePackets) {
|
||||
try {
|
||||
internalBuffer = new byte[internalBufferLength];
|
||||
rtpPacket = new RtpPacket(internalBuffer, internalBufferLength);
|
||||
|
||||
rtpSocket.receive(rtpPacket);
|
||||
packetReceivedCounter++;
|
||||
// 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)) {
|
||||
// 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() + "]");
|
||||
processRtpPacket(rtpPacket);
|
||||
lastSequenceNumber = rtpPacket.getSeqNum();
|
||||
lastPacketTimestamp = rtpPacket.getTimestamp();
|
||||
processRtpPacket(internalBuffer, RTP_HEADER_SIZE, payloadLength);
|
||||
} else {
|
||||
if (log.isDebugEnabled())
|
||||
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) {
|
||||
long now = System.currentTimeMillis();
|
||||
if (now - lastPacketReceived > 100) {
|
||||
if (now - lastPacketReceived > 200) {
|
||||
if (log.isDebugEnabled())
|
||||
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() + "]");
|
||||
@ -217,11 +216,8 @@ public class RtpStreamReceiver {
|
||||
return false;
|
||||
}
|
||||
|
||||
private void processRtpPacket(RtpPacket rtpPacket) {
|
||||
lastSequenceNumber = rtpPacket.getSeqNum();
|
||||
lastPacketTimestamp = rtpPacket.getTimestamp();
|
||||
AudioByteData audioData = new AudioByteData(rtpPacket.getPayload());
|
||||
if (listener != null) listener.onAudioDataReceived(audioData);
|
||||
private void processRtpPacket(byte[] rtpAudio, int offset, int len) {
|
||||
if (listener != null) listener.onAudioDataReceived(rtpAudio, offset, len);
|
||||
else log.debug("No listener for incoming audio packet");
|
||||
}
|
||||
}
|
||||
|
@ -22,5 +22,5 @@ package org.bigbluebutton.voiceconf.red5.media;
|
||||
public interface RtpStreamReceiverListener {
|
||||
|
||||
void onStoppedReceiving();
|
||||
void onAudioDataReceived(AudioByteData audioData);
|
||||
void onAudioDataReceived(byte[] audioData, int offset, int len);
|
||||
}
|
||||
|
@ -41,6 +41,7 @@ public class RtpStreamSender {
|
||||
private final DatagramSocket srcSocket;
|
||||
private final SipConnectInfo connInfo;
|
||||
private boolean marked = false;
|
||||
private long startTimestamp;
|
||||
|
||||
public RtpStreamSender(DatagramSocket srcSocket, SipConnectInfo connInfo) {
|
||||
this.srcSocket = srcSocket;
|
||||
@ -66,6 +67,7 @@ public class RtpStreamSender {
|
||||
if (!marked) {
|
||||
rtpPacket.setMarker(true);
|
||||
marked = true;
|
||||
startTimestamp = System.currentTimeMillis();
|
||||
}
|
||||
rtpPacket.setPadding(false);
|
||||
rtpPacket.setExtension(false);
|
||||
|
@ -19,11 +19,12 @@
|
||||
**/
|
||||
package org.bigbluebutton.voiceconf.red5.media;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PipedInputStream;
|
||||
import java.io.PipedOutputStream;
|
||||
import java.net.DatagramSocket;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import org.apache.mina.core.buffer.IoBuffer;
|
||||
import org.bigbluebutton.voiceconf.red5.media.transcoder.SipToFlashTranscoder;
|
||||
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.net.rtmp.event.AudioData;
|
||||
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.IBroadcastScope;
|
||||
import org.red5.server.stream.IProviderService;
|
||||
@ -40,7 +42,9 @@ import org.slf4j.Logger;
|
||||
public class SipToFlashAudioStream implements TranscodedAudioDataListener, RtpStreamReceiverListener {
|
||||
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 Runnable audioDataProcessor;
|
||||
private volatile boolean processAudioData = false;
|
||||
@ -51,9 +55,9 @@ public class SipToFlashAudioStream implements TranscodedAudioDataListener, RtpSt
|
||||
private RtpStreamReceiver rtpStreamReceiver;
|
||||
private StreamObserver observer;
|
||||
private SipToFlashTranscoder transcoder;
|
||||
|
||||
private long startTimestamp = 0;
|
||||
private boolean sentMetadata = false;
|
||||
private IoBuffer mBuffer;
|
||||
private AudioData audioData;
|
||||
|
||||
private final byte[] fakeMetadata = new byte[] {
|
||||
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);
|
||||
listenStreamName = "speaker_" + System.currentTimeMillis();
|
||||
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() {
|
||||
@ -85,14 +100,25 @@ public class SipToFlashAudioStream implements TranscodedAudioDataListener, RtpSt
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
log.debug("Stopping stream for {}", listenStreamName);
|
||||
processAudioData = false;
|
||||
rtpStreamReceiver.stop();
|
||||
log.debug("Stopped RTP Stream Receiver for {}", listenStreamName);
|
||||
if (audioBroadcastStream != null) {
|
||||
audioBroadcastStream.stop();
|
||||
log.debug("Stopped audioBroadcastStream for {}", listenStreamName);
|
||||
audioBroadcastStream.close();
|
||||
log.debug("stopping and closing stream {}", listenStreamName);
|
||||
log.debug("Closed audioBroadcastStream for {}", listenStreamName);
|
||||
} else
|
||||
log.debug("audioBroadcastStream is null, couldn't stop");
|
||||
log.debug("Stream(s) stopped");
|
||||
}
|
||||
|
||||
public void start() {
|
||||
|
||||
}
|
||||
|
||||
private void startNow() {
|
||||
log.debug("started publishing stream in " + scope.getName());
|
||||
audioBroadcastStream = new AudioBroadcastStream(listenStreamName);
|
||||
audioBroadcastStream.setPublishedName(listenStreamName);
|
||||
@ -111,7 +137,6 @@ public class SipToFlashAudioStream implements TranscodedAudioDataListener, RtpSt
|
||||
|
||||
audioBroadcastStream.start();
|
||||
processAudioData = true;
|
||||
startTimestamp = System.currentTimeMillis();
|
||||
|
||||
audioDataProcessor = new Runnable() {
|
||||
public void run() {
|
||||
@ -124,11 +149,23 @@ public class SipToFlashAudioStream implements TranscodedAudioDataListener, RtpSt
|
||||
}
|
||||
|
||||
private void processAudioData() {
|
||||
int len = 160;
|
||||
byte[] pcmAudio = new byte[len];
|
||||
int remaining = len;
|
||||
int offset = 0;
|
||||
|
||||
while (processAudioData) {
|
||||
try {
|
||||
AudioByteData abd = audioDataQ.take();
|
||||
transcoder.transcode(abd, this);
|
||||
} catch (InterruptedException e) {
|
||||
int bytesRead = streamToFlash.read(pcmAudio, offset, remaining);
|
||||
remaining -= bytesRead;
|
||||
if (remaining == 0) {
|
||||
remaining = len;
|
||||
offset = 0;
|
||||
transcoder.transcode(pcmAudio, this);
|
||||
} else {
|
||||
offset += bytesRead;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
@ -141,10 +178,10 @@ public class SipToFlashAudioStream implements TranscodedAudioDataListener, RtpSt
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAudioDataReceived(AudioByteData audioData) {
|
||||
public void onAudioDataReceived(byte[] audioData, int offset, int len) {
|
||||
try {
|
||||
audioDataQ.put(audioData);
|
||||
} catch (InterruptedException e) {
|
||||
streamFromSip.write(audioData, offset, len);
|
||||
} catch (IOException e) {
|
||||
// TODO Auto-generated catch block
|
||||
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
|
||||
* but for Red5 0.91, doesn't yet. (ralam Sept 24, 2010).
|
||||
*/
|
||||
IoBuffer mBuffer = IoBuffer.allocate(1024);
|
||||
mBuffer.setAutoExpand(true);
|
||||
|
||||
mBuffer.clear();
|
||||
mBuffer.put(fakeMetadata);
|
||||
mBuffer.flip();
|
||||
|
||||
Notify notifyData = new Notify(mBuffer);
|
||||
notifyData.setTimestamp((int)timestamp );
|
||||
notifyData.setTimestamp((int)timestamp);
|
||||
notifyData.setSourceType(Constants.SOURCE_TYPE_LIVE);
|
||||
audioBroadcastStream.dispatchEvent(notifyData);
|
||||
notifyData.release();
|
||||
sentMetadata = true;
|
||||
@ -182,23 +217,20 @@ public class SipToFlashAudioStream implements TranscodedAudioDataListener, RtpSt
|
||||
}
|
||||
|
||||
private void pushAudio(byte[] audio, long timestamp) {
|
||||
|
||||
sendFakeMetadata(timestamp);
|
||||
|
||||
IoBuffer buffer = IoBuffer.allocate(1024);
|
||||
buffer.setAutoExpand(true);
|
||||
|
||||
buffer.clear();
|
||||
|
||||
buffer.put((byte) transcoder.getCodecId());
|
||||
byte[] copy = new byte[audio.length];
|
||||
System.arraycopy(audio, 0, copy, 0, audio.length );
|
||||
|
||||
buffer.put(copy);
|
||||
buffer.flip();
|
||||
|
||||
AudioData audioData = new AudioData(buffer);
|
||||
audioData.setTimestamp((int)timestamp );
|
||||
mBuffer.clear();
|
||||
mBuffer.put((byte) transcoder.getCodecId());
|
||||
mBuffer.put(audio);
|
||||
mBuffer.flip();
|
||||
audioData.setSourceType(Constants.SOURCE_TYPE_LIVE);
|
||||
/*
|
||||
* Use timestamp increments passed in by codecs (i.e. 32 for nelly). This will force
|
||||
* Flash Player to playback audio at proper timestamp. If we calculate timestamp using
|
||||
* System.currentTimeMillis() - startTimestamp, the audio has tendency to drift and
|
||||
* introduce delay. (ralam dec 14, 2010)
|
||||
*/
|
||||
audioData.setTimestamp((int)(timestamp));
|
||||
audioData.setData(mBuffer);
|
||||
audioBroadcastStream.dispatchEvent(audioData);
|
||||
audioData.release();
|
||||
}
|
||||
|
@ -40,6 +40,8 @@ public class RtpSocket {
|
||||
/** Remote port */
|
||||
int r_port;
|
||||
|
||||
private final byte[] payload = new byte[10];
|
||||
|
||||
/** Creates a new RTP socket (only receiver) */
|
||||
public RtpSocket(DatagramSocket datagram_socket) {
|
||||
socket=datagram_socket;
|
||||
@ -59,19 +61,24 @@ public class RtpSocket {
|
||||
return socket;
|
||||
}
|
||||
|
||||
private final DatagramPacket rxDatagram = new DatagramPacket(payload, payload.length);
|
||||
|
||||
/** Receives a RTP packet from this socket */
|
||||
public void receive(RtpPacket rtpp) throws IOException {
|
||||
DatagramPacket datagram = new DatagramPacket(rtpp.getPacket(), rtpp.getLength());
|
||||
socket.receive(datagram);
|
||||
rtpp.setPacketLength(datagram.getLength());
|
||||
rxDatagram.setData(rtpp.getPacket());
|
||||
socket.receive(rxDatagram);
|
||||
rtpp.setPacketLength(rxDatagram.getLength());
|
||||
}
|
||||
|
||||
private final DatagramPacket txDatagram = new DatagramPacket(payload, payload.length);
|
||||
|
||||
/** Sends a RTP packet from this socket */
|
||||
public void send(RtpPacket rtpp) throws IOException {
|
||||
DatagramPacket datagram = new DatagramPacket(rtpp.getPacket(), rtpp.getLength());
|
||||
datagram.setAddress(r_addr);
|
||||
datagram.setPort(r_port);
|
||||
socket.send(datagram);
|
||||
txDatagram.setData(rtpp.getPacket());
|
||||
txDatagram.setAddress(r_addr);
|
||||
txDatagram.setPort(r_port);
|
||||
if (!socket.isClosed())
|
||||
socket.send(txDatagram);
|
||||
}
|
||||
|
||||
/** Closes this socket */
|
||||
|
@ -19,10 +19,8 @@
|
||||
**/
|
||||
package org.bigbluebutton.voiceconf.red5.media.transcoder;
|
||||
|
||||
import org.bigbluebutton.voiceconf.red5.media.AudioByteData;
|
||||
|
||||
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 getCodecId();
|
||||
|
@ -19,13 +19,12 @@
|
||||
**/
|
||||
package org.bigbluebutton.voiceconf.red5.media.transcoder;
|
||||
|
||||
import java.nio.FloatBuffer;
|
||||
import java.util.Random;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.bigbluebutton.voiceconf.red5.media.AudioByteData;
|
||||
import org.red5.logging.Red5LoggerFactory;
|
||||
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.DecoderMap;
|
||||
|
||||
@ -38,28 +37,48 @@ import org.red5.app.sip.codecs.asao.DecoderMap;
|
||||
public class NellyFlashToSipTranscoderImp implements FlashToSipTranscoder {
|
||||
protected static Logger log = Red5LoggerFactory.getLogger( NellyFlashToSipTranscoderImp.class, "sip" );
|
||||
|
||||
private static final int NELLYMOSER_DECODED_PACKET_SIZE = 256;
|
||||
private static final int NELLYMOSER_ENCODED_PACKET_SIZE = 64;
|
||||
private static final int NELLY_TO_L16_AUDIO_SIZE = 256;
|
||||
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 DecoderMap decoderMap;
|
||||
private float[] tempBuffer; // Temporary buffer with received PCM audio from FlashPlayer.
|
||||
private int tempBufferRemaining = 0; // Floats remaining on temporary buffer.
|
||||
private float[] encodingBuffer; // Encoding buffer used to encode to final codec format;
|
||||
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 float[] tempL16Buffer = new float[NELLY_TO_L16_AUDIO_SIZE];
|
||||
private float[] tempUlawBuffer = new float[ULAW_AUDIO_LENGTH];
|
||||
private byte[] ulawEncodedBuffer = new byte[ULAW_AUDIO_LENGTH];
|
||||
|
||||
private long timestamp = 0;
|
||||
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) {
|
||||
this.sipCodec = sipCodec;
|
||||
decoder = new Decoder();
|
||||
decoderMap = null;
|
||||
Random rgen = new Random();
|
||||
timestamp = rgen.nextInt(1000);
|
||||
viewBuffer = l16Audio.asReadOnlyBuffer();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -73,91 +92,65 @@ public class NellyFlashToSipTranscoderImp implements FlashToSipTranscoder {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transcode(AudioByteData audioData, int startOffset, int length, TranscodedAudioDataListener listener) {
|
||||
byte[] codedBuffer = new byte[length];
|
||||
System.arraycopy(audioData.getData(), startOffset, codedBuffer, 0, length);
|
||||
byte[] transcodedAudioData = new byte[sipCodec.getOutgoingEncodedFrameSize()];
|
||||
|
||||
asao_buffer_processed = false;
|
||||
|
||||
if (!hasInitilializedBuffers) {
|
||||
tempBuffer = new float[NELLYMOSER_DECODED_PACKET_SIZE];
|
||||
encodingBuffer = new float[sipCodec.getOutgoingDecodedFrameSize()];
|
||||
hasInitilializedBuffers = true;
|
||||
public void transcode(byte[] audioData, int startOffset, int length, TranscodedAudioDataListener listener) {
|
||||
if (audioData.length != NELLY_AUDIO_LENGTH) {
|
||||
log.warn("Receiving bad nelly audio. Expecting {}, got {}.", NELLY_AUDIO_LENGTH, audioData.length);
|
||||
return;
|
||||
}
|
||||
|
||||
if (length > 0) {
|
||||
do {
|
||||
int encodedBytes = fillRtpPacketBuffer(codedBuffer, transcodedAudioData);
|
||||
if (encodedBytes == 0) {
|
||||
break;
|
||||
}
|
||||
// Convert the Nelly audio to L16.
|
||||
decoderMap = decoder.decode(decoderMap, audioData, 0, tempL16Buffer, 0);
|
||||
|
||||
if (encodingOffset == sipCodec.getOutgoingDecodedFrameSize()) {
|
||||
encodingOffset = 0;
|
||||
listener.handleTranscodedAudioData(transcodedAudioData, timestamp += TS_INCREMENT);
|
||||
}
|
||||
} while (!asao_buffer_processed);
|
||||
}
|
||||
}
|
||||
// Store the L16 audio into the buffer
|
||||
l16Audio.put(tempL16Buffer);
|
||||
|
||||
private int fillRtpPacketBuffer(byte[] audioData, byte[] transcodedAudioData) {
|
||||
int copyingSize = 0;
|
||||
int finalCopySize = 0;
|
||||
byte[] codedBuffer = new byte[sipCodec.getOutgoingEncodedFrameSize()];
|
||||
// Read 160-float worth of audio
|
||||
viewBuffer.get(tempUlawBuffer);
|
||||
|
||||
if ((tempBufferRemaining + encodingOffset) >= sipCodec.getOutgoingDecodedFrameSize()) {
|
||||
copyingSize = encodingBuffer.length - encodingOffset;
|
||||
// Convert the L16 audio to Ulaw
|
||||
int encodedBytes = sipCodec.pcmToCodec(tempUlawBuffer, ulawEncodedBuffer);
|
||||
|
||||
System.arraycopy(tempBuffer, tempBuffer.length-tempBufferRemaining, encodingBuffer, encodingOffset, copyingSize);
|
||||
// Send it to the server
|
||||
listener.handleTranscodedAudioData(ulawEncodedBuffer, timestamp += TS_INCREMENT);
|
||||
|
||||
encodingOffset = sipCodec.getOutgoingDecodedFrameSize();
|
||||
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);
|
||||
if (l16Audio.position() == l16Audio.capacity()) {
|
||||
/**
|
||||
* This means we already processed 5 Nelly packets and sent 5 Ulaw packets.
|
||||
* However, we have 3 extra Ulaw packets.
|
||||
* Fire them off to the server. We don't want to discard them as it will
|
||||
* result in choppy audio.
|
||||
*/
|
||||
|
||||
// Get the 6th packet and send
|
||||
viewBuffer.get(tempUlawBuffer);
|
||||
encodedBytes = sipCodec.pcmToCodec(tempUlawBuffer, ulawEncodedBuffer);
|
||||
if (encodedBytes == sipCodec.getOutgoingEncodedFrameSize()) {
|
||||
System.arraycopy(codedBuffer, 0, transcodedAudioData, 0, codedBuffer.length);
|
||||
listener.handleTranscodedAudioData(ulawEncodedBuffer, timestamp += TS_INCREMENT);
|
||||
} else {
|
||||
log.error("Failure encoding buffer." );
|
||||
}
|
||||
|
||||
// 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." );
|
||||
}
|
||||
|
||||
return finalCopySize;
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,87 +19,114 @@
|
||||
**/
|
||||
package org.bigbluebutton.voiceconf.red5.media.transcoder;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PipedInputStream;
|
||||
import java.io.PipedOutputStream;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
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.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.asao.ByteStream;
|
||||
import org.red5.app.sip.codecs.asao.CodecImpl;
|
||||
|
||||
public class NellySipToFlashTranscoderImp implements SipToFlashTranscoder {
|
||||
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;
|
||||
|
||||
/**
|
||||
* 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 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 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) {
|
||||
this.audioCodec = audioCodec;
|
||||
encoderMap = new float[64];
|
||||
tempBuffer = new float[NELLYMOSER_DECODED_PACKET_SIZE];
|
||||
|
||||
Random rgen = new Random();
|
||||
timestamp = rgen.nextInt(1000);
|
||||
}
|
||||
|
||||
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() +"]");
|
||||
}
|
||||
viewBuffer = l16Audio.asReadOnlyBuffer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transcode(AudioByteData audioData, TranscodedAudioDataListener listener) {
|
||||
transcodePcmToNellymoser(audioData.getData(), listener);
|
||||
public void transcode(byte[] audioData, TranscodedAudioDataListener 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
|
||||
public int getIncomingEncodedFrameSize() {
|
||||
@ -111,3 +138,4 @@ public class NellySipToFlashTranscoderImp implements SipToFlashTranscoder {
|
||||
return NELLYMOSER_CODEC_ID;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -19,10 +19,8 @@
|
||||
**/
|
||||
package org.bigbluebutton.voiceconf.red5.media.transcoder;
|
||||
|
||||
import org.bigbluebutton.voiceconf.red5.media.AudioByteData;
|
||||
|
||||
public interface SipToFlashTranscoder {
|
||||
void transcode(AudioByteData audioData, TranscodedAudioDataListener listener);
|
||||
void transcode(byte[] audioData, TranscodedAudioDataListener listener);
|
||||
int getCodecId();
|
||||
int getIncomingEncodedFrameSize();
|
||||
}
|
||||
|
@ -20,8 +20,6 @@
|
||||
package org.bigbluebutton.voiceconf.red5.media.transcoder;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
import org.bigbluebutton.voiceconf.red5.media.AudioByteData;
|
||||
import org.red5.app.sip.codecs.Codec;
|
||||
import org.red5.logging.Red5LoggerFactory;
|
||||
import org.slf4j.Logger;
|
||||
@ -39,9 +37,9 @@ public class SpeexFlashToSipTranscoderImp implements FlashToSipTranscoder {
|
||||
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];
|
||||
System.arraycopy(audioData.getData(), startOffset, transcodedAudio, 0, length);
|
||||
System.arraycopy(audioData, startOffset, transcodedAudio, 0, length);
|
||||
listener.handleTranscodedAudioData(transcodedAudio, timestamp += TS_INCREMENT);
|
||||
}
|
||||
|
||||
|
@ -20,8 +20,6 @@
|
||||
package org.bigbluebutton.voiceconf.red5.media.transcoder;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
import org.bigbluebutton.voiceconf.red5.media.AudioByteData;
|
||||
import org.red5.app.sip.codecs.Codec;
|
||||
import org.red5.logging.Red5LoggerFactory;
|
||||
import org.slf4j.Logger;
|
||||
@ -41,8 +39,8 @@ public class SpeexSipToFlashTranscoderImp implements SipToFlashTranscoder {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transcode(AudioByteData audioData, TranscodedAudioDataListener listener) {
|
||||
byte[] codedBuffer = audioData.getData();
|
||||
public void transcode(byte[] audioData, TranscodedAudioDataListener listener) {
|
||||
byte[] codedBuffer = audioData;
|
||||
listener.handleTranscodedAudioData(codedBuffer, timestamp += TS_INCREMENT);
|
||||
}
|
||||
|
||||
|
@ -50,4 +50,8 @@ public class AudioConferenceProvider {
|
||||
public int getStartAudioPort() {
|
||||
return startAudioPort;
|
||||
}
|
||||
|
||||
public int getStopAudioPort() {
|
||||
return stopAudioPort;
|
||||
}
|
||||
}
|
||||
|
@ -37,7 +37,9 @@ import org.red5.logging.Red5LoggerFactory;
|
||||
import org.red5.server.api.IScope;
|
||||
import org.red5.server.api.stream.IBroadcastStream;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.InetAddress;
|
||||
import java.net.SocketException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Vector;
|
||||
|
||||
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) {
|
||||
log.debug("call {}", destination);
|
||||
log.debug("{} making a call to {}", callerName, destination);
|
||||
try {
|
||||
localSocket = getLocalAudioSocket();
|
||||
userProfile.audioPort = localSocket.getLocalPort();
|
||||
} catch (Exception e) {
|
||||
log.debug("{} failed to allocate local port for call to {}. Notifying client that call failed.", callerName, destination);
|
||||
notifyListenersOnOutgoingCallFailed();
|
||||
return;
|
||||
}
|
||||
@ -138,18 +141,22 @@ public class CallAgent extends CallListenerAdapter implements CallStreamObserver
|
||||
private DatagramSocket getLocalAudioSocket() throws Exception {
|
||||
DatagramSocket socket = null;
|
||||
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 {
|
||||
socket = new DatagramSocket(portProvider.getFreeAudioPort());
|
||||
socket = new DatagramSocket(freePort);
|
||||
failedToGetSocket = false;
|
||||
log.info("Successfully setup local audio port {}. {}", freePort, failedPorts);
|
||||
break;
|
||||
} catch (SocketException e) {
|
||||
log.error("Failed to setup local audio socket.");
|
||||
failedPorts.append(freePort + ", ");
|
||||
}
|
||||
}
|
||||
|
||||
if (failedToGetSocket) {
|
||||
log.warn("Failed to setup local audio port {}.", failedPorts);
|
||||
throw new Exception("Exception while initializing CallStream");
|
||||
}
|
||||
|
||||
@ -169,7 +176,8 @@ public class CallAgent extends CallListenerAdapter implements CallStreamObserver
|
||||
int localAudioPort = SessionDescriptorUtil.getLocalAudioPort(localSdp);
|
||||
|
||||
SipConnectInfo connInfo = new SipConnectInfo(localSocket, remoteMediaAddress, remoteAudioPort);
|
||||
|
||||
try {
|
||||
localSocket.connect(InetAddress.getByName(remoteMediaAddress), remoteAudioPort);
|
||||
log.debug("[localAudioPort=" + localAudioPort + ",remoteAudioPort=" + remoteAudioPort + "]");
|
||||
|
||||
if (userProfile.audio && localAudioPort != 0 && remoteAudioPort != 0) {
|
||||
@ -185,6 +193,9 @@ public class CallAgent extends CallListenerAdapter implements CallStreamObserver
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (UnknownHostException e1) {
|
||||
log.error(StackTraceUtil.getStackTrace(e1));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -204,11 +215,12 @@ public class CallAgent extends CallListenerAdapter implements CallStreamObserver
|
||||
}
|
||||
|
||||
private void closeVoiceStreams() {
|
||||
log.debug("closeMediaApplication" );
|
||||
|
||||
log.debug("Shutting down the voice streams.");
|
||||
if (callStream != null) {
|
||||
callStream.stop();
|
||||
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() {
|
||||
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 */
|
||||
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;
|
||||
|
||||
log.debug("CLOSE.");
|
||||
|
||||
closeVoiceStreams();
|
||||
|
||||
notifyListenersOfOnCallClosed();
|
||||
callState = CallState.UA_IDLE;
|
||||
|
||||
|
@ -63,7 +63,7 @@ public class BigBlueButtonApplication extends MultiThreadedApplicationAdapter {
|
||||
|
||||
@Override
|
||||
public boolean roomStart(IScope room) {
|
||||
log.debug("{} - roomStart ", APP);
|
||||
log.debug("Starting room [{}].", room.getName());
|
||||
assert participantsApplication != null;
|
||||
participantsApplication.createRoom(room.getName());
|
||||
return super.roomStart(room);
|
||||
@ -71,21 +71,28 @@ public class BigBlueButtonApplication extends MultiThreadedApplicationAdapter {
|
||||
|
||||
@Override
|
||||
public void roomStop(IScope room) {
|
||||
log.debug("{} - roomStop", APP);
|
||||
log.debug("Stopping room [{}]", room.getName());
|
||||
super.roomStop(room);
|
||||
assert participantsApplication != null;
|
||||
participantsApplication.destroyRoom(room.getName());
|
||||
BigBlueButtonSession bbbSession = getBbbSession();
|
||||
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;
|
||||
recorderApplication.destroyRecordSession(bbbSession.getSessionName());
|
||||
log.debug("{} - roomStop - destroyed RecordSession {}", APP, bbbSession.getSessionName());
|
||||
|
||||
log.debug("Stopped room [{}]", room.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
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 role = ((String) params[1]).toString();
|
||||
@ -116,47 +123,44 @@ public class BigBlueButtonApplication extends MultiThreadedApplicationAdapter {
|
||||
|
||||
String debugInfo = "userid=" + userid + ",username=" + username + ",role=" + role + ",conference=" + conference + "," +
|
||||
"session=" + sessionName + ",voiceConf=" + voiceBridge + ",room=" + room + ",externsUserid=" + externUserID;
|
||||
log.debug("roomConnect - [{}]", debugInfo);
|
||||
|
||||
log.info("User Joined [{}, {}]", username, room);
|
||||
log.debug("User [{}] connected to room [{}]", debugInfo, room);
|
||||
super.roomConnect(connection, params);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
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);
|
||||
log.info("User Left [{}, {}]", bbbSession.getUsername(), bbbSession.getRoom());
|
||||
log.info("User [{}] disconnected from room [{}]", bbbSession.getUsername(), bbbSession.getRoom());
|
||||
super.roomDisconnect(conn);
|
||||
}
|
||||
|
||||
public String getMyUserId() {
|
||||
log.debug("Getting userid for connection.");
|
||||
BigBlueButtonSession bbbSession = (BigBlueButtonSession) Red5.getConnectionLocal().getAttribute(Constants.SESSION);
|
||||
assert bbbSession != null;
|
||||
return bbbSession.getUserid()+"";
|
||||
}
|
||||
|
||||
public void setParticipantsApplication(ParticipantsApplication a) {
|
||||
log.debug("Setting participants application");
|
||||
participantsApplication = a;
|
||||
}
|
||||
|
||||
public void setRecorderApplication(RecorderApplication a) {
|
||||
log.debug("Setting recorder application");
|
||||
recorderApplication = a;
|
||||
}
|
||||
|
||||
public void setApplicationListeners(Set<IApplication> listeners) {
|
||||
log.debug("Setting application listeners");
|
||||
int count = 0;
|
||||
Iterator<IApplication> iter = listeners.iterator();
|
||||
while (iter.hasNext()) {
|
||||
log.debug("Setting application listeners {}", count);
|
||||
super.addListener((IApplication) iter.next());
|
||||
count++;
|
||||
}
|
||||
log.debug("Finished Setting application listeners");
|
||||
}
|
||||
|
||||
public void setVersion(String v) {
|
||||
@ -172,7 +176,7 @@ public class BigBlueButtonApplication extends MultiThreadedApplicationAdapter {
|
||||
@Override
|
||||
public void onApplicationEvent(ApplicationEvent event) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
@ -49,3 +49,4 @@
|
||||
</buildCSSFiles>
|
||||
</actionScriptProperties>
|
||||
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<flexProperties enableServiceManager="false" flexServerFeatures="0" flexServerType="0" toolCompile="true" useServerFlexSDK="false" version="1"/>
|
||||
|
||||
|
||||
|
3
bigbluebutton-client/.gitignore
vendored
3
bigbluebutton-client/.gitignore
vendored
@ -1,3 +1,6 @@
|
||||
.settings/org.eclipse.core.resources.prefs
|
||||
.actionScriptProperties
|
||||
.flexProperties
|
||||
linker-report.xml
|
||||
bin
|
||||
client
|
||||
|
4
bigbluebutton-client/.settings/org.eclipse.core.resources.prefs
Normal file → Executable file
4
bigbluebutton-client/.settings/org.eclipse.core.resources.prefs
Normal file → Executable 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
|
||||
encoding//locale/el_GR/bbbResources.properties=UTF-8
|
||||
encoding/<project>=utf-8
|
||||
encoding/<project>=UTF-8
|
||||
|
@ -36,6 +36,7 @@
|
||||
|
||||
<module name="PhoneModule" url="PhoneModule.swf?v=VERSION"
|
||||
uri="rtmp://HOST/sip"
|
||||
autoJoin="false"
|
||||
dependsOn="ViewersModule"
|
||||
/>
|
||||
|
||||
|
BIN
bigbluebutton-client/src/branding/css/assets/img/BBBLogo.png
Executable file
BIN
bigbluebutton-client/src/branding/css/assets/img/BBBLogo.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 19 KiB |
@ -8,6 +8,13 @@ Application
|
||||
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
|
||||
{
|
||||
|
4
bigbluebutton-client/src/org/bigbluebutton/main/views/BrandingLogo.mxml
Executable file
4
bigbluebutton-client/src/org/bigbluebutton/main/views/BrandingLogo.mxml
Executable 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>
|
@ -238,7 +238,9 @@
|
||||
<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: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>
|
||||
|
||||
<mx:ControlBar width="100%" height="20" paddingTop="0">
|
||||
<mx:Label text="{ResourceUtil.getInstance().getString('bbb.mainshell.copyrightLabel2',[appVersion])}" id="copyrightLabel2"/>
|
||||
<mx:Spacer width="20"/>
|
||||
|
@ -47,6 +47,7 @@ package org.bigbluebutton.modules.deskshare.services
|
||||
|
||||
private var width:Number;
|
||||
private var height:Number;
|
||||
private var uri:String;
|
||||
|
||||
public function DeskshareService()
|
||||
{
|
||||
@ -60,6 +61,7 @@ package org.bigbluebutton.modules.deskshare.services
|
||||
}
|
||||
|
||||
public function connect(uri:String):void {
|
||||
this.uri = uri;
|
||||
LogUtil.debug("Deskshare Service connecting to " + uri);
|
||||
conn = new Connection();
|
||||
conn.addEventListener(Connection.SUCCESS, connectionSuccessHandler);
|
||||
@ -93,9 +95,9 @@ package org.bigbluebutton.modules.deskshare.services
|
||||
}
|
||||
|
||||
private function connectionSuccessHandler(e:ConnectionEvent):void{
|
||||
LogUtil.debug("Successully connection to " + module.uri);
|
||||
LogUtil.debug("Successully connection to " + uri);
|
||||
nc = conn.getConnection();
|
||||
deskSO = SharedObject.getRemote("deskSO", module.uri, false);
|
||||
deskSO = SharedObject.getRemote("deskSO", uri, false);
|
||||
deskSO.client = this;
|
||||
deskSO.connect(nc);
|
||||
|
||||
@ -107,11 +109,11 @@ package org.bigbluebutton.modules.deskshare.services
|
||||
}
|
||||
|
||||
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{
|
||||
LogUtil.error("connection rejected " + module.uri + " with message " + e.toString());
|
||||
LogUtil.error("connection rejected " + uri + " with message " + e.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -26,7 +26,6 @@
|
||||
title="{windowTitle}"
|
||||
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.REGISTER_LISTENERS}" method="registerListeners" />
|
||||
<mate:Listener type="{ListenersEvent.SET_LOCAL_MODERATOR_STATUS}" method="{setModerator}" />
|
||||
@ -128,16 +127,16 @@
|
||||
dispatchEvent(unmuteCommand);
|
||||
}
|
||||
|
||||
private function firstListenerJoined(e:ListenersEvent):void{
|
||||
|
||||
}
|
||||
|
||||
private function roomMuteStateChange(e:ListenersEvent):void{
|
||||
setMuteState(e.mute_state);
|
||||
}
|
||||
|
||||
private function registerListeners(e:ListenersEvent):void{
|
||||
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{
|
||||
@ -145,14 +144,6 @@
|
||||
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{
|
||||
var item:ListenerItem = e.itemRenderer as ListenerItem;
|
||||
item.onRollOver();
|
||||
|
@ -60,7 +60,7 @@ package org.bigbluebutton.modules.phone.managers {
|
||||
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;
|
||||
isConnected = true;
|
||||
|
||||
@ -68,16 +68,16 @@ package org.bigbluebutton.modules.phone.managers {
|
||||
this.username = username;
|
||||
this.room = room;
|
||||
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 = new NetConnection();
|
||||
netConnection.client = this;
|
||||
netConnection.addEventListener( NetStatusEvent.NET_STATUS , netStatus );
|
||||
netConnection.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler);
|
||||
netConnection.connect(uri);
|
||||
netConnection.connect(uri, externUID, username);
|
||||
}
|
||||
|
||||
public function disconnect():void {
|
||||
|
@ -19,8 +19,6 @@
|
||||
|
||||
package org.bigbluebutton.modules.phone.managers
|
||||
{
|
||||
import flash.events.IEventDispatcher;
|
||||
|
||||
import org.bigbluebutton.common.LogUtil;
|
||||
import org.bigbluebutton.modules.phone.events.CallConnectedEvent;
|
||||
import org.bigbluebutton.modules.phone.events.JoinVoiceConferenceEvent;
|
||||
@ -40,6 +38,8 @@ package org.bigbluebutton.modules.phone.managers
|
||||
public function setModuleAttributes(attributes:Object):void {
|
||||
this.attributes = attributes;
|
||||
LogUtil.debug("Attributes Set... webvoiceconf:" + attributes.webvoiceconf);
|
||||
|
||||
if (attributes.autoJoin == "true") joinVoice(true);
|
||||
}
|
||||
|
||||
private function setupMic(useMic:Boolean):void {
|
||||
@ -54,28 +54,36 @@ package org.bigbluebutton.modules.phone.managers
|
||||
}
|
||||
|
||||
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() ) );
|
||||
connectionManager.connect(uid, attributes.username, attributes.room, attributes.uri);
|
||||
connectionManager.connect(uid, attributes.externUserID, attributes.username, attributes.room, attributes.uri);
|
||||
}
|
||||
|
||||
public function dialConference():void {
|
||||
LogUtil.debug("Dialing...." + attributes.webvoiceconf);
|
||||
LogUtil.debug("Dialing...." + attributes.webvoiceconf + "...." + attributes.externUserID);
|
||||
connectionManager.doCall(attributes.webvoiceconf);
|
||||
}
|
||||
|
||||
public function callConnected(event:CallConnectedEvent):void {
|
||||
LogUtil.debug("Call connected...");
|
||||
setupConnection();
|
||||
LogUtil.debug("callConnected: Connection Setup");
|
||||
streamManager.callConnected(event.playStreamName, event.publishStreamName, event.codec);
|
||||
LogUtil.debug("callConnected::onCall set");
|
||||
onCall = true;
|
||||
}
|
||||
|
||||
public function hangup():void {
|
||||
LogUtil.debug("PhoneManager hangup");
|
||||
if (onCall) {
|
||||
LogUtil.debug("PM OnCall");
|
||||
streamManager.stopStreams();
|
||||
connectionManager.doHangUp();
|
||||
LogUtil.debug("PM hangup::doHangUp");
|
||||
onCall = false;
|
||||
}
|
||||
}
|
||||
|
@ -115,7 +115,9 @@ package org.bigbluebutton.modules.phone.managers
|
||||
|
||||
public function mute():void {
|
||||
if(!muted) {
|
||||
|
||||
if(outgoingStream != null) {
|
||||
LogUtil.debug("***** Muting the mic.");
|
||||
outgoingStream.close();
|
||||
outgoingStream = null;
|
||||
muted = true;
|
||||
@ -125,6 +127,7 @@ package org.bigbluebutton.modules.phone.managers
|
||||
|
||||
public function unmute():void {
|
||||
if (muted) {
|
||||
LogUtil.debug("***** UNMuting the mic.");
|
||||
outgoingStream = new NetStream(connection);
|
||||
outgoingStream.addEventListener(NetStatusEvent.NET_STATUS, netStatus);
|
||||
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 {
|
||||
LogUtil.debug("SM callConnected");
|
||||
isCallConnected = true;
|
||||
audioCodec = codec;
|
||||
setupIncomingStream();
|
||||
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();
|
||||
LogUtil.debug("SM callConnected: After setupPlayStatusHandler");
|
||||
play(playStreamName);
|
||||
LogUtil.debug("SM callConnected: After play");
|
||||
publish(publishStreamName);
|
||||
LogUtil.debug("SM callConnected: Published Stream");
|
||||
}
|
||||
|
||||
private function play(playStreamName:String):void {
|
||||
@ -155,14 +167,25 @@ package org.bigbluebutton.modules.phone.managers
|
||||
|
||||
private function publish(publishStreamName:String):void {
|
||||
LogUtil.debug("Publishing stream " + publishStreamName);
|
||||
if (mic != null)
|
||||
outgoingStream.publish(publishStreamName, "live");
|
||||
else
|
||||
LogUtil.debug("SM publish: No Microphone to publish");
|
||||
}
|
||||
|
||||
private function setupIncomingStream():void {
|
||||
incomingStream = new NetStream(connection);
|
||||
incomingStream.addEventListener(NetStatusEvent.NET_STATUS, netStatus);
|
||||
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 {
|
||||
@ -178,19 +201,29 @@ package org.bigbluebutton.modules.phone.managers
|
||||
custom_obj.onPlayStatus = playStatus;
|
||||
custom_obj.onMetadata = onMetadata;
|
||||
incomingStream.client = custom_obj;
|
||||
if (mic != null)
|
||||
outgoingStream.client = custom_obj;
|
||||
}
|
||||
|
||||
public function stopStreams():void {
|
||||
LogUtil.debug("Stopping Stream(s)");
|
||||
if(incomingStream != null) {
|
||||
LogUtil.debug("--Stopping Incoming Stream");
|
||||
incomingStream.play(false);
|
||||
} else {
|
||||
LogUtil.debug("--Incoming Stream Null");
|
||||
}
|
||||
|
||||
if(outgoingStream != null) {
|
||||
LogUtil.debug("--Stopping Outgoing Stream");
|
||||
outgoingStream.attachAudio(null);
|
||||
outgoingStream.close();
|
||||
} else {
|
||||
LogUtil.debug("--Outgoing Stream Null");
|
||||
}
|
||||
|
||||
isCallConnected = false;
|
||||
LogUtil.debug("Stopped Stream(s)");
|
||||
}
|
||||
|
||||
private function netStatus (evt:NetStatusEvent ):void {
|
||||
|
@ -36,24 +36,72 @@
|
||||
# 2010-09-15 FFD Updates for 0.71-dev
|
||||
# 2010-10-16 FFD Updates for 0.71-beta
|
||||
# 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
|
||||
|
||||
#
|
||||
# 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
|
||||
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
|
||||
VOICE_CONFERENCE="bbb-voice-freeswitch.xml"
|
||||
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}')
|
||||
|
||||
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
|
||||
@ -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
|
||||
# $2 -- loctation of value
|
||||
# $3 -- value to check
|
||||
@ -80,65 +129,16 @@ check_file() {
|
||||
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 the the BigBlueButton VM?
|
||||
#
|
||||
if [ -f /home/firstuser/.profile ]; then
|
||||
echo $(cat /home/firstuser/.profile | grep BigBlueButton)
|
||||
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() {
|
||||
if [ ! $HEADER ]; then
|
||||
@ -166,10 +166,10 @@ need_root() {
|
||||
}
|
||||
|
||||
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
|
||||
echo "$0 [options]"
|
||||
echo " bbb-conf [options]"
|
||||
echo
|
||||
echo "Configuration:"
|
||||
echo " --version Display BigBlueButton version (packages)"
|
||||
@ -184,7 +184,7 @@ usage() {
|
||||
echo " --watch Scan the log files for error messages every 2 seconds"
|
||||
echo " --salt View the URL and security salt for the server"
|
||||
echo
|
||||
echo "Administration":
|
||||
echo "Administration:"
|
||||
echo " --restart Restart BigBueButton"
|
||||
echo " --stop Stop BigBueButton"
|
||||
echo " --start Start BigBueButton"
|
||||
@ -247,7 +247,7 @@ uncomment () {
|
||||
|
||||
stop_bigbluebutton () {
|
||||
/etc/init.d/red5 stop
|
||||
/etc/init.d/${TOMCAT} stop
|
||||
/etc/init.d/${SERVLET_CONTAINER} stop
|
||||
/etc/init.d/nginx stop
|
||||
|
||||
if [ -a /opt/freeswitch/run/freeswitch.pid ]; then
|
||||
@ -259,7 +259,10 @@ stop_bigbluebutton () {
|
||||
fi
|
||||
|
||||
/etc/init.d/activemq stop
|
||||
|
||||
if [ -f /etc/init.d/bbb-openoffice-headless ]; then
|
||||
/etc/init.d/bbb-openoffice-headless stop
|
||||
fi
|
||||
}
|
||||
|
||||
start_bigbluebutton () {
|
||||
@ -301,13 +304,15 @@ start_bigbluebutton () {
|
||||
done
|
||||
fi
|
||||
|
||||
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/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): "
|
||||
|
||||
@ -321,8 +326,22 @@ start_bigbluebutton () {
|
||||
done
|
||||
fi
|
||||
|
||||
if ! wget http://$NGINX_IP/bigbluebutton/api -O - --quiet | grep -q SUCCESS; then
|
||||
echo "Startup unsuccessful"
|
||||
if [ $PLATFORM == "ubuntu-jetty-intalio" ]; then
|
||||
#
|
||||
# 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
|
||||
fi
|
||||
|
||||
@ -334,7 +353,7 @@ display_bigbluebutton_status () {
|
||||
/etc/init.d/activemq status
|
||||
/etc/init.d/nginx status
|
||||
/etc/init.d/red5 status
|
||||
/etc/init.d/${TOMCAT} status
|
||||
/etc/init.d/${SERVLET_CONTAINER} status
|
||||
}
|
||||
|
||||
if [ $# -eq 0 ]; then
|
||||
@ -473,10 +492,10 @@ while [ $# -gt 0 ]; do
|
||||
if [ "$1" = "--salt" -o "$1" = "-salt" -o "$1" = "--setsalt" ]; then
|
||||
SALT="${2}"
|
||||
if [ -z "$SALT" ]; then
|
||||
IP=$(cat /var/lib/${TOMCAT}/webapps/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`;
|
||||
BBB_WEB=$(cat ${SERVLET_DIR}/bigbluebutton/WEB-INF/classes/bigbluebutton.properties | sed -n '/^bigbluebutton.web.serverURL/{s/.*\///;p}')
|
||||
SALT=$(cat ${SERVLET_DIR}/bigbluebutton/WEB-INF/classes/bigbluebutton.properties | grep securitySalt | cut -d= -f2);
|
||||
echo
|
||||
echo " URL: http://$IP/bigbluebutton/"
|
||||
echo " URL: http://$BBB_WEB/bigbluebutton/"
|
||||
echo " Salt: $SALT"
|
||||
echo
|
||||
exit 0
|
||||
@ -644,12 +663,12 @@ if [ $SETUPDEV ]; then
|
||||
exit 1
|
||||
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
|
||||
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"
|
||||
cp /var/lib/${TOMCAT}/webapps/bigbluebutton/demo/bbb_api_conf.jsp ${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 ${SERVLET_DIR}/bigbluebutton/demo/bbb_api_conf.jsp ${BBBWEBHOME}/web-app/demo
|
||||
|
||||
echo "# Enabling $USER to write to /var/bigbluebutton to upload slides"
|
||||
sudo chmod -R ugo+rwx /var/bigbluebutton
|
||||
@ -665,7 +684,7 @@ if [ $SETUPDEV ]; then
|
||||
echo "
|
||||
# Done. To run your local build of bbb-web:
|
||||
|
||||
sudo /etc/init.d/${TOMCAT} stop
|
||||
sudo /etc/init.d/${SERVLET_CONTAINER} stop
|
||||
cd ${BBBWEBHOME}
|
||||
ant
|
||||
"
|
||||
@ -713,7 +732,7 @@ if [ $SETUPDEV ]; then
|
||||
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
|
||||
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
|
||||
|
||||
cd $BBBCLIENTHOME
|
||||
@ -868,22 +887,24 @@ check_configuration() {
|
||||
#
|
||||
# Check if BigBlueButto is defined in Nginx
|
||||
#
|
||||
if [ $PLATFORM != "ubuntu-jetty-intalio" ]; then
|
||||
if [ ! -L /etc/nginx/sites-enabled/bigbluebutton ]; then
|
||||
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
|
||||
|
||||
|
||||
#
|
||||
# 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_DEMO=$(cat /var/lib/${TOMCAT}/webapps/bigbluebutton/demo/bbb_api_conf.jsp | tr -d '\r' | sed -n '/salt =/{s/.* = "//;s/".*//g;p}')
|
||||
SALT_PROPERTIES=$(cat ${SERVLET_DIR}/bigbluebutton/WEB-INF/classes/bigbluebutton.properties | tr -d '\r' | sed -n '/securitySalt/{s/.*=//;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
|
||||
echo "# Salt mismatch: "
|
||||
echo "# /var/lib/${TOMCAT}/webapps/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/WEB-INF/classes/bigbluebutton.properties=$SALT_PROPERTIES"
|
||||
echo "# ${SERVLET_DIR}/bigbluebutton/demo/bbb_api_conf.jsp=$SALT_DEMO"
|
||||
fi
|
||||
|
||||
|
||||
@ -891,7 +912,7 @@ check_configuration() {
|
||||
# Look for properties with no values set
|
||||
#
|
||||
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"
|
||||
|
||||
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
|
||||
#
|
||||
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
|
||||
echo "# pdf2swf, jpeg2swf and png2swf are not installed in $VARFolder"
|
||||
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
|
||||
echo "# ImageMagick's convert is not installed in $VARFolder"
|
||||
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
|
||||
echo "# Ghostscript is not installd in $VARFolder"
|
||||
fi
|
||||
@ -1012,14 +1033,14 @@ check_state() {
|
||||
|
||||
if ! netstat -ant | grep '8080' > /dev/null; then
|
||||
print_header
|
||||
NOT_RUNNING_APPS="${NOT_RUNNING_APPS} ${TOMCAT} or grails"
|
||||
NOT_RUNNING_APPS="${NOT_RUNNING_APPS} ${SERVLET_CONTAINER} or grails"
|
||||
else
|
||||
if ps aux | ps -aef | grep -v grep | grep grails | grep run-app > /dev/null; then
|
||||
print_header
|
||||
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
|
||||
RUNNING_APPS="${RUNNING_APPS} ${TOMCAT}"
|
||||
RUNNING_APPS="${RUNNING_APPS} ${SERVLET_CONTAINER}"
|
||||
fi
|
||||
fi
|
||||
|
||||
@ -1120,10 +1141,10 @@ check_state() {
|
||||
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
|
||||
echo "# empty directory: $TOMCAT6_LOGS contains no logs"
|
||||
if [ -z "$(ls -A $SERVLET_LOGS)" ]; then
|
||||
echo "# empty directory: $SERVLET_LOGS contains no logs"
|
||||
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
|
||||
# a firewall
|
||||
#
|
||||
NGINX_IP=$(cat /etc/nginx/sites-available/bigbluebutton | sed -n '/server_name/{s/.*name[ ]*//;s/;//;p}')
|
||||
check_no_value server_name /etc/nginx/sites-available/bigbluebutton $NGINX_IP
|
||||
if ! wget http://$NGINX_IP/bigbluebutton/api -O - --quiet | grep -q SUCCESS; then
|
||||
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 $BBB_WEB
|
||||
if ! wget http://$BBB_WEB/bigbluebutton/api -O - --quiet | grep -q SUCCESS; then
|
||||
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 "# If you are setting up BigBlueButton behind a firewall, see the FAQ"
|
||||
echo "# for steps to setup BigBlueButton behind a firewall."
|
||||
@ -1192,7 +1213,7 @@ check_state() {
|
||||
fi
|
||||
|
||||
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
|
||||
echo
|
||||
echo "# The IP address ($SIP_SERVER_HOST) set for sip.server.host in"
|
||||
@ -1252,19 +1273,19 @@ if [ $CHECK ]; then
|
||||
|
||||
echo
|
||||
echo "/var/www/bigbluebutton/client/conf/config.xml"
|
||||
IP=$(cat /var/www/bigbluebutton/client/conf/config.xml | sed -n '/porttest /{s/.*host="//;s/".*//;p}')
|
||||
echo " Port test (tunnel): $IP"
|
||||
PORT_IP=$(cat /var/www/bigbluebutton/client/conf/config.xml | sed -n '/porttest /{s/.*host="//;s/".*//;p}')
|
||||
echo " Port test (tunnel): $PORT_IP"
|
||||
|
||||
IP=$(cat /var/www/bigbluebutton/client/conf/config.xml | sed -n '/uri.*video/{s/.*rtmp:\/\///;s/\/.*//;p}')
|
||||
echo " Red5: $IP"
|
||||
RED5_IP=$(cat /var/www/bigbluebutton/client/conf/config.xml | sed -n '/uri.*video/{s/.*rtmp:\/\///;s/\/.*//;p}')
|
||||
echo " Red5: $RED5_IP"
|
||||
|
||||
# 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
|
||||
echo "/etc/nginx/sites-available/bigbluebutton"
|
||||
IP=$(cat /etc/nginx/sites-available/bigbluebutton | sed -n '/server_name/{s/.*name[ ]*//;s/;//;p}')
|
||||
echo " server name: $IP"
|
||||
NGINX_IP=$(cat /etc/nginx/sites-available/bigbluebutton | sed -n '/server_name/{s/.*name[ ]*//;s/;//;p}')
|
||||
echo " server name: $NGINX_IP"
|
||||
|
||||
PORT=$(cat /etc/nginx/sites-available/bigbluebutton | sed -n '/listen/{s/.*listen[ ]*//;s/;//;p}')
|
||||
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}')
|
||||
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 "/var/lib/${TOMCAT}/webapps/bigbluebutton/WEB-INF/classes/bigbluebutton.properties (bbb-web)"
|
||||
echo " bbb-web host: $IP"
|
||||
echo "${SERVLET_DIR}/bigbluebutton/WEB-INF/classes/bigbluebutton.properties (bbb-web)"
|
||||
echo " bbb-web host: $BBB_WEB_IP"
|
||||
|
||||
if [ -f /var/lib/${TOMCAT}/webapps/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')
|
||||
if [ -f ${SERVLET_DIR}/bigbluebutton/demo/bbb_api_conf.jsp ]; then
|
||||
API_IP=$(cat ${SERVLET_DIR}/bigbluebutton/demo/bbb_api_conf.jsp | sed -n '/String BigBlueButtonURL/{s/.*http:\/\///;s/\/.*//;p}' | tr -d '\015')
|
||||
echo
|
||||
echo "/var/lib/${TOMCAT}/webapps/bigbluebutton/demo/bbb_api_conf.jsp (API demos)"
|
||||
echo " bbb-web-api host: $IP"
|
||||
echo "${SERVLET_DIR}/bigbluebutton/demo/bbb_api_conf.jsp (API demos)"
|
||||
echo " bbb-web-api host: $API_IP"
|
||||
fi
|
||||
|
||||
if [ $VOICE_CONFERENCE == "bbb-voice-freeswitch.xml" ]; then
|
||||
@ -1321,7 +1342,7 @@ if [ $ZIP ]; then
|
||||
touch /tmp/empty
|
||||
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 $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/nginx/error.log > /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
|
||||
|
||||
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
|
||||
echo " -- Exceptions found in $TOMCAT6_LOGS/ -- "
|
||||
echo " -- Exceptions found in $SERVLET_LOGS/ -- "
|
||||
cat /tmp/t
|
||||
echo
|
||||
fi
|
||||
@ -1483,16 +1504,16 @@ if [ -n "$HOST" ]; then
|
||||
#
|
||||
# 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" \
|
||||
/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
|
||||
# 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
|
||||
|
||||
# cat /var/lib/${TOMCAT}/webapps/bigbluebutton/WEB-INF/classes/bigbluebutton.properties
|
||||
# cat ${SERVLET_DIR}/bigbluebutton/WEB-INF/classes/bigbluebutton.properties
|
||||
|
||||
#
|
||||
# Update nginx
|
||||
@ -1511,10 +1532,10 @@ if [ -n "$HOST" ]; then
|
||||
# Update api demos
|
||||
#
|
||||
|
||||
if [ -f /var/lib/${TOMCAT}/webapps/bigbluebutton/demo/bbb_api_conf.jsp ]; then
|
||||
echo "Assigning $HOST for api demos in /var/lib/${TOMCAT}/webapps/bigbluebutton/demo/bbb_api_conf.jsp"
|
||||
if [ -f ${SERVLET_DIR}/bigbluebutton/demo/bbb_api_conf.jsp ]; then
|
||||
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" \
|
||||
/var/lib/${TOMCAT}/webapps/bigbluebutton/demo/bbb_api_conf.jsp
|
||||
${SERVLET_DIR}/bigbluebutton/demo/bbb_api_conf.jsp
|
||||
fi
|
||||
|
||||
#
|
||||
@ -1758,8 +1779,8 @@ if [ $CLEAN ]; then
|
||||
rm -rf $RED5_DIR/log/*
|
||||
fi
|
||||
|
||||
if [ $TOMCAT6_LOGS ]; then
|
||||
rm -rf $TOMCAT6_LOGS/*
|
||||
if [ $SERVLET_LOGS ]; then
|
||||
rm -rf $SERVLET_LOGS/*
|
||||
fi
|
||||
|
||||
rm -rf /var/log/nginx/*
|
||||
|
@ -22,6 +22,9 @@
|
||||
package org.bigbluebutton.deskshare.client;
|
||||
|
||||
import javax.swing.JApplet;
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JOptionPane;
|
||||
|
||||
import java.awt.Image;
|
||||
|
||||
public class DeskShareApplet extends JApplet implements ClientListener {
|
||||
@ -92,7 +95,20 @@ public class DeskShareApplet extends JApplet implements ClientListener {
|
||||
}
|
||||
|
||||
public void onClientStop(ExitCode reason) {
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -57,6 +57,32 @@ public class DeskshareClient {
|
||||
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() {
|
||||
System.out.println(NAME + "Stop");
|
||||
screenSharer.stop();
|
||||
|
@ -64,6 +64,35 @@ public class DeskshareSystemTray {
|
||||
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() {
|
||||
if (tray != null && trayIcon != null) {
|
||||
tray.remove(trayIcon);
|
||||
|
@ -41,6 +41,32 @@ public class FullScreenSharer implements ScreenSharer {
|
||||
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() {
|
||||
sharer.stopSharing();
|
||||
}
|
||||
|
@ -50,6 +50,34 @@ public class ScreenRegionSharer implements ScreenSharer {
|
||||
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() {
|
||||
frame.setVisible(false);
|
||||
sharer.stopSharing();
|
||||
|
@ -23,6 +23,7 @@ package org.bigbluebutton.deskshare.client;
|
||||
|
||||
public interface ScreenSharer {
|
||||
void start();
|
||||
void disconnected(); // 2010.11.19 _PTS_272_
|
||||
void stop();
|
||||
void addClientListener(ClientListener l);
|
||||
}
|
||||
|
@ -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() {
|
||||
System.out.println(NAME + "Stopping");
|
||||
System.out.println(NAME + "Removing icon from system tray.");
|
||||
|
@ -19,6 +19,7 @@
|
||||
*
|
||||
* ===License Header===
|
||||
*/
|
||||
|
||||
package org.bigbluebutton.deskshare.client.frame;
|
||||
|
||||
import java.awt.BasicStroke;
|
||||
@ -30,6 +31,7 @@ import java.awt.Frame;
|
||||
import java.awt.GradientPaint;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.GraphicsConfiguration;
|
||||
import java.awt.GraphicsDevice;
|
||||
import java.awt.GraphicsEnvironment;
|
||||
import java.awt.Insets;
|
||||
@ -116,6 +118,104 @@ class WindowlessFrame implements Serializable {
|
||||
private final BarFrame mLeftBorder;
|
||||
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 static final long serialVersionUID = 1L;
|
||||
|
||||
@ -149,8 +249,51 @@ class WindowlessFrame implements Serializable {
|
||||
|
||||
@Override
|
||||
public void mouseDragged(MouseEvent e) {
|
||||
final int changeInX = e.getLocationOnScreen().x - mActionOffset.x - mTopLeft.x;
|
||||
final int changeInY = e.getLocationOnScreen().y - mActionOffset.y - mTopLeft.y;
|
||||
int changeInX = e.getLocationOnScreen().x - mActionOffset.x - mTopLeft.x;
|
||||
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()) {
|
||||
WindowlessFrame.this.setLocation(changeInX + mTopLeft.x, changeInY + mTopLeft.y);
|
||||
}
|
||||
@ -175,7 +318,7 @@ class WindowlessFrame implements Serializable {
|
||||
|
||||
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);
|
||||
|
||||
@ -185,15 +328,28 @@ class WindowlessFrame implements Serializable {
|
||||
|
||||
@Override
|
||||
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;
|
||||
|
||||
Toolkit tk = Toolkit.getDefaultToolkit();
|
||||
Dimension d = tk.getScreenSize();
|
||||
|
||||
if (mResizing.get()) {
|
||||
int newH = mOriginalSize.height;
|
||||
int newW = mOriginalSize.width;
|
||||
if (mCorner == Corner.SOUTHEAST) {
|
||||
newH += changeInY;
|
||||
if (Corner.SOUTHEAST == mCorner) {
|
||||
|
||||
if (e.getLocationOnScreen().x < mTopLeft.x+5) {
|
||||
newW = 5;
|
||||
} else {
|
||||
newW += changeInX;
|
||||
} else if (mCorner == Corner.NORTHEAST) {
|
||||
}
|
||||
if (e.getLocationOnScreen().y < mTopLeft.y+5) {
|
||||
newH = 5;
|
||||
} else {
|
||||
newH += changeInY;
|
||||
}
|
||||
} /*else if (mCorner == Corner.NORTHEAST) {
|
||||
mTopLeft.y = mTopLeft.y + changeInY;
|
||||
newH = mOverallSize.height + -changeInY;
|
||||
newW += changeInX;
|
||||
@ -206,8 +362,33 @@ class WindowlessFrame implements Serializable {
|
||||
newH += changeInY;
|
||||
mTopLeft.x = mTopLeft.x + changeInX;
|
||||
newW = mOverallSize.width + -changeInX;
|
||||
}
|
||||
}*/
|
||||
//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);
|
||||
e.consume();
|
||||
}
|
||||
@ -235,13 +416,14 @@ class WindowlessFrame implements Serializable {
|
||||
private Corner nearCorner(Point mouse) {
|
||||
if (isNearBottomRightCorner(mouse)) {
|
||||
return Corner.SOUTHEAST;
|
||||
} else if (isNearTopRightCorner(mouse)) {
|
||||
} /* else if (isNearTopRightCorner(mouse)) {
|
||||
return Corner.NORTHEAST;
|
||||
} else if (isNearTopLeftCorner(mouse)) {
|
||||
return Corner.NORTHWEST;
|
||||
} else if (isNearBottomLeftCorner(mouse)) {
|
||||
return Corner.SOUTHWEST;
|
||||
}
|
||||
*/
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -251,7 +433,7 @@ class WindowlessFrame implements Serializable {
|
||||
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 yToTopRight = Math.abs(mTopLeft.y - mouse.y);
|
||||
return xToTopRight < CORNER_SIZE && yToTopRight < CORNER_SIZE;
|
||||
@ -268,17 +450,22 @@ class WindowlessFrame implements Serializable {
|
||||
int yToTopLeft = Math.abs(mTopLeft.y - mouse.y);
|
||||
return xToTopLeft < CORNER_SIZE && yToTopLeft < CORNER_SIZE;
|
||||
}
|
||||
*/
|
||||
|
||||
@Override
|
||||
public void mouseMoved(MouseEvent e) {
|
||||
final Point mouse = e.getLocationOnScreen();
|
||||
|
||||
/*
|
||||
if (isNearTopLeftCorner(mouse)) {
|
||||
e.getComponent().setCursor(Cursor.getPredefinedCursor(Cursor.NW_RESIZE_CURSOR));
|
||||
} else if (isNearBottomLeftCorner(mouse)) {
|
||||
e.getComponent().setCursor(Cursor.getPredefinedCursor(Cursor.SW_RESIZE_CURSOR));
|
||||
} else if (isNearTopRightCorner(mouse)) {
|
||||
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));
|
||||
} else {
|
||||
e.getComponent().setCursor(Cursor.getDefaultCursor());
|
||||
|
Loading…
Reference in New Issue
Block a user