Merge branch 'multivideo' into multivideo-1404

Conflicts:
	bbb-video/build.gradle
	bbb-video/src/main/java/org/bigbluebutton/app/video/VideoApplication.java
	bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/maps/VideoEventMapDelegate.as
	bigbluebutton-web/grails-app/conf/bigbluebutton.properties
This commit is contained in:
Mateus Dalepiane 2014-06-26 16:21:25 -03:00
commit 52502488ec
18 changed files with 799 additions and 57 deletions

View File

@ -76,6 +76,7 @@ dependencies {
// Red5
providedCompile 'org/red5:red5:1.0.2@jar'
compile 'org.red5:red5-client:1.0.2-RC2@jar'
providedCompile 'org.red5:red5-io:1.0.3@jar'
// Logging
@ -101,6 +102,9 @@ dependencies {
//redis
compile 'redis.clients:jedis:2.0.0'
providedCompile 'commons-pool:commons-pool:1.5.6'
// Needed for StringUtils
providedCompile 'org.apache.commons:commons-lang3:3.1@jar'
}
test {

View File

@ -0,0 +1,55 @@
/*
* RED5 Open Source Flash Server - http://code.google.com/p/red5/
*
* Copyright 2006-2012 by respective authors (see below). All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.bigbluebutton.app.video;
import org.red5.client.net.rtmp.RTMPClient;
import org.red5.server.service.PendingCall;
import org.red5.server.net.rtmp.event.Ping;
/**
* Custom RTMP Client
*
* This client behaves like the flash player plugin when requesting to play a stream
*
* @author Mateus Dalepiane (mdalepiane@gmail.com)
*/
public class CustomRTMPClient extends RTMPClient {
public void play(int streamId, String name) {
System.out.println("play stream "+ streamId + ", name: " + name);
if (conn != null) {
// get the channel
int channel = getChannelForStreamId(streamId);
// send our requested buffer size
ping(Ping.CLIENT_BUFFER, streamId, 2000);
// send our request for a/v
PendingCall receiveAudioCall = new PendingCall("receiveAudio");
conn.invoke(receiveAudioCall, channel);
PendingCall receiveVideoCall = new PendingCall("receiveVideo");
conn.invoke(receiveVideoCall, channel);
// call play
Object[] params = new Object[1];
params[0] = name;
PendingCall pendingCall = new PendingCall("play", params);
conn.invoke(pendingCall, channel);
} else {
System.out.println("Connection was null ?");
}
}
}

View File

@ -0,0 +1,350 @@
/*
* RED5 Open Source Flash Server - http://code.google.com/p/red5/
*
* Copyright 2006-2012 by respective authors (see below). All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.bigbluebutton.app.video;
import java.io.IOException;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import org.red5.client.net.rtmp.ClientExceptionHandler;
import org.red5.client.net.rtmp.INetStreamEventHandler;
import org.red5.client.net.rtmp.RTMPClient;
import org.red5.io.utils.ObjectMap;
import org.red5.proxy.StreamingProxy;
import org.red5.server.api.event.IEvent;
import org.red5.server.api.event.IEventDispatcher;
import org.red5.server.api.service.IPendingServiceCall;
import org.red5.server.api.service.IPendingServiceCallback;
import org.red5.server.net.rtmp.event.IRTMPEvent;
import org.red5.server.net.rtmp.event.Notify;
import org.red5.server.net.rtmp.status.StatusCodes;
import org.red5.server.stream.message.RTMPMessage;
/**
* Relay a stream from one location to another via RTMP.
*
* @author Paul Gregoire (mondain@gmail.com)
*/
public class CustomStreamRelay {
// our consumer
private CustomRTMPClient client;
// our publisher
private StreamingProxy proxy;
// task timer
private Timer timer;
private String sourceHost;
private String destHost;
private String sourceApp;
private String destApp;
private int sourcePort;
private int destPort;
private String sourceStreamName;
private String destStreamName;
private String publishMode;
Map<String, Object> defParams;
private boolean isDisconnecting;
/**
* Creates a stream client to consume a stream from an end point and a proxy to relay the stream
* to another end point.
*
* @param args application arguments
*/
public void setSourceHost(String sourceHost) {
this.sourceHost = sourceHost;
}
public void setSourcePort(int sourcePort) {
this.sourcePort = sourcePort;
}
public void setDestinationHost(String destHost) {
this.destHost = destHost;
}
public void setDestinationPort(int destPort) {
this.destPort = destPort;
}
public void setSourceApp(String sourceApp) {
this.sourceApp = sourceApp;
}
public void setDestinationApp(String destApp) {
this.destApp = destApp;
}
public void setSourceStreamName(String sourceStreamName) {
this.sourceStreamName = sourceStreamName;
}
public void setDestinationStreamName(String destStreamName) {
this.destStreamName = destStreamName;
}
public void setPublishMode(String publishMode) {
this.publishMode = publishMode;
}
public void initRelay(String... args) {
if (args == null || args.length < 7) {
System.out
.println("Not enough args supplied. Usage: <source uri> <source app> <source stream name> <destination uri> <destination app> <destination stream name> <publish mode>");
}
else {
sourceHost = args[0];
destHost = args[3];
sourceApp = args[1];
destApp = args[4];
sourcePort = 1935;
destPort = 1935;
sourceStreamName = args[2];
destStreamName = args[5];
publishMode = args[6]; //live, record, or append
int colonIdx = sourceHost.indexOf(':');
if (colonIdx > 0) {
sourcePort = Integer.valueOf(sourceHost.substring(colonIdx + 1));
sourceHost = sourceHost.substring(0, colonIdx);
System.out.printf("Source host: %s port: %d\n", sourceHost, sourcePort);
}
colonIdx = destHost.indexOf(':');
if (colonIdx > 0) {
destPort = Integer.valueOf(destHost.substring(colonIdx + 1));
destHost = destHost.substring(0, colonIdx);
System.out.printf("Destination host: %s port: %d\n", destHost, destPort);
}
}
}
public void stopRelay() {
isDisconnecting = true;
client.disconnect();
proxy.stop();
}
public void startRelay() {
isDisconnecting = false;
// create a timer
timer = new Timer();
// create our publisher
proxy = new StreamingProxy();
proxy.setHost(destHost);
proxy.setPort(destPort);
proxy.setApp(destApp);
proxy.init();
proxy.setConnectionClosedHandler(new Runnable() {
public void run() {
System.out.println("Publish connection has been closed, source will be disconnected");
client.disconnect();
}
});
proxy.setExceptionHandler(new ClientExceptionHandler() {
@Override
public void handleException(Throwable throwable) {
throwable.printStackTrace();
proxy.stop();
}
});
proxy.start(destStreamName, publishMode, new Object[] {});
// wait for the publish state
// Change to use signal or something more cleaner
do {
try {
Thread.sleep(100L);
} catch (InterruptedException e) {
e.printStackTrace();
}
} while (!proxy.isPublished());
System.out.println("Publishing...");
// create the consumer
client = new CustomRTMPClient();
client.setStreamEventDispatcher(new StreamEventDispatcher());
client.setStreamEventHandler(new INetStreamEventHandler() {
public void onStreamEvent(Notify notify) {
System.out.printf("AQUIonStreamEvent: %s\n", notify);
ObjectMap<?, ?> map = (ObjectMap<?, ?>) notify.getCall().getArguments()[0];
String code = (String) map.get("code");
System.out.printf("<:%s\n", code);
if (StatusCodes.NS_PLAY_STREAMNOTFOUND.equals(code)) {
System.out.println("Requested stream was not found");
isDisconnecting = true;
client.disconnect();
} else if (StatusCodes.NS_PLAY_UNPUBLISHNOTIFY.equals(code) || StatusCodes.NS_PLAY_COMPLETE.equals(code)) {
System.out.println("Source has stopped publishing or play is complete");
isDisconnecting = true;
client.disconnect();
}
}
});
client.setConnectionClosedHandler(new Runnable() {
public void run() {
System.out.println("Source connection has been closed");
//System.exit(2);
if(isDisconnecting) {
System.out.println("Proxy will be stopped");
client.disconnect();
proxy.stop();
} else {
System.out.println("Reconnecting client...");
client.connect(sourceHost, sourcePort, defParams, new ClientConnectCallback());
}
}
});
client.setExceptionHandler(new ClientExceptionHandler() {
@Override
public void handleException(Throwable throwable) {
throwable.printStackTrace();
//System.exit(1);
client.disconnect();
proxy.stop();
}
});
// connect the consumer
defParams = client.makeDefaultConnectionParams(sourceHost, sourcePort, sourceApp);
// add pageurl and swfurl
defParams.put("pageUrl", "");
defParams.put("swfUrl", "app:/Red5-StreamRelay.swf");
// indicate for the handshake to generate swf verification data
client.setSwfVerification(true);
// connect the client
System.out.println("startRelay:: ProxyRelay status is running: " + proxy.isRunning());
client.connect(sourceHost, sourcePort, defParams, new ClientConnectCallback());
}
private final class ClientConnectCallback implements IPendingServiceCallback{
public void resultReceived(IPendingServiceCall call) {
System.out.println("connectCallback");
ObjectMap<?, ?> map = (ObjectMap<?, ?>) call.getResult();
String code = (String) map.get("code");
if ("NetConnection.Connect.Rejected".equals(code)) {
System.out.printf("Rejected: %s\n", map.get("description"));
client.disconnect();
proxy.stop();
} else if ("NetConnection.Connect.Success".equals(code)) {
// 1. Wait for onBWDone
timer.schedule(new BandwidthStatusTask(), 2000L);
} else {
System.out.printf("Unhandled response code: %s\n", code);
}
}
}
/**
* Dispatches consumer events.
*/
private final class StreamEventDispatcher implements IEventDispatcher {
public void dispatchEvent(IEvent event) {
System.out.println("ClientStream.dispachEvent()" + event.toString());
System.out.println("dispatchEvent:: ProxyRelay status is running: " + proxy.isRunning());
try {
proxy.pushMessage(null, RTMPMessage.build((IRTMPEvent) event));
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* Handles result from subscribe call.
*/
private final class SubscribeStreamCallBack implements IPendingServiceCallback {
public void resultReceived(IPendingServiceCall call) {
System.out.println("SubscirbeStreamCallBack::resultReceived: " + call);
}
}
/**
* Creates a "stream" via playback, this is the source stream.
*/
private final class CreateStreamCallback implements IPendingServiceCallback {
public void resultReceived(IPendingServiceCall call) {
System.out.println("CreateStreamCallBack::resultReceived: " + call);
int streamId = (Integer) call.getResult();
System.out.println("stream id: " + streamId);
// send our buffer size request
if (sourceStreamName.endsWith(".flv") || sourceStreamName.endsWith(".f4v") || sourceStreamName.endsWith(".mp4")) {
System.out.println("THIS IS WRONG");
System.out.println("play stream name " + sourceStreamName + " start 0 lenght -1");
client.play(streamId, sourceStreamName, 0, -1);
} else {
System.out.println("play stream name " + sourceStreamName);
client.play(streamId, sourceStreamName);
}
}
}
/**
* Continues to check for onBWDone
*/
private final class BandwidthStatusTask extends TimerTask {
@Override
public void run() {
// check for onBWDone
System.out.println("Bandwidth check done: " + client.isBandwidthCheckDone());
// cancel this task
this.cancel();
// create a task to wait for subscribed
timer.schedule(new PlayStatusTask(), 1000L);
// 2. send FCSubscribe
client.subscribe(new SubscribeStreamCallBack(), new Object[] { sourceStreamName });
}
}
private final class PlayStatusTask extends TimerTask {
@Override
public void run() {
// checking subscribed
System.out.println("Subscribed: " + client.isSubscribed());
// cancel this task
this.cancel();
// 3. create stream
client.createStream(new CreateStreamCallback());
}
}
}

View File

@ -21,17 +21,25 @@ package org.bigbluebutton.app.video;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.ConcurrentHashMap;
import java.util.Timer;
import java.util.TimerTask;
import org.red5.logging.Red5LoggerFactory;
import org.red5.server.adapter.MultiThreadedApplicationAdapter;
import org.red5.server.api.IConnection;
import org.red5.server.api.Red5;
import org.red5.server.api.scope.IScope;
import org.red5.server.api.scope.IBasicScope;
import org.red5.server.api.scope.IBroadcastScope;
import org.red5.server.api.scope.ScopeType;
import org.red5.server.api.stream.IBroadcastStream;
import org.red5.server.api.stream.IPlayItem;
import org.red5.server.api.stream.IServerStream;
import org.red5.server.api.stream.IStreamListener;
import org.red5.server.api.stream.ISubscriberStream;
import org.red5.server.stream.ClientBroadcastStream;
import org.slf4j.Logger;
import org.apache.commons.lang3.StringUtils;
public class VideoApplication extends MultiThreadedApplicationAdapter {
private static Logger log = Red5LoggerFactory.getLogger(VideoApplication.class, "video");
@ -42,6 +50,16 @@ public class VideoApplication extends MultiThreadedApplicationAdapter {
private boolean recordVideoStream = false;
private EventRecordingService recordingService;
private final Map<String, IStreamListener> streamListeners = new HashMap<String, IStreamListener>();
private Map<String, CustomStreamRelay> remoteStreams = new ConcurrentHashMap<String, CustomStreamRelay>();
private Map<String, Integer> listenersOnRemoteStream = new ConcurrentHashMap<String, Integer>();
// Proxy disconnection timer
private Timer timer;
// Proxy disconnection timeout
// TODO: This timeout should be configurable
private long timeout = 60000L;
@Override
public boolean appStart(IScope app) {
@ -49,6 +67,7 @@ public class VideoApplication extends MultiThreadedApplicationAdapter {
log.info("oflaDemo appStart");
System.out.println("oflaDemo appStart");
appScope = app;
timer = new Timer();
return true;
}
@ -72,13 +91,24 @@ public class VideoApplication extends MultiThreadedApplicationAdapter {
super.streamPublishStart(stream);
}
public IBroadcastScope getBroadcastScope(IScope scope, String name) {
IBasicScope basicScope = scope.getBasicScope(ScopeType.BROADCAST, name);
if (!(basicScope instanceof IBroadcastScope)) {
return null;
} else {
return (IBroadcastScope) basicScope;
}
}
@Override
public void streamBroadcastStart(IBroadcastStream stream) {
IConnection conn = Red5.getConnectionLocal();
super.streamBroadcastStart(stream);
log.info("streamBroadcastStart " + stream.getPublishedName() + " " + System.currentTimeMillis() + " " + conn.getScope().getName());
if (recordVideoStream) {
if (recordVideoStream && stream.getPublishedName().contains("/") == false) {
recordStream(stream);
VideoStreamListener listener = new VideoStreamListener();
listener.setEventRecordingService(recordingService);
@ -142,5 +172,96 @@ public class VideoApplication extends MultiThreadedApplicationAdapter {
public void setEventRecordingService(EventRecordingService s) {
recordingService = s;
}
@Override
public void streamPlayItemPlay(ISubscriberStream stream, IPlayItem item, boolean isLive) {
// log w3c connect event
String streamName = item.getName();
//rtmp://SV1/video/conferencia
//SV2/SV3/SV4/streamName
if(streamName.contains("/")) {
synchronized(remoteStreams) {
if(remoteStreams.containsKey(streamName) == false) {
String[] parts = streamName.split("/");
String sourceServer = parts[0];
String sourceStreamName = StringUtils.join(parts, '/', 1, parts.length);
String destinationServer = Red5.getConnectionLocal().getHost();
String destinationStreamName = streamName;
String app = "video/"+Red5.getConnectionLocal().getScope().getName();
System.out.println("streamPlayItemPlay:: streamName [" + streamName + "]");
System.out.println("streamPlayItemPlay:: sourceServer [" + sourceServer + "]");
System.out.println("streamPlayItemPlay:: sourceStreamName [" + sourceStreamName + "]");
System.out.println("streamPlayItemPlay:: destinationServer [" + destinationServer + "]");
System.out.println("streamPlayItemPlay:: destinationStreamName [" + destinationStreamName + "]");
System.out.println("streamPlayItemPlay:: app [" + app + "]");
CustomStreamRelay remoteRelay = new CustomStreamRelay();
remoteRelay.initRelay(new String[]{sourceServer, app, sourceStreamName, destinationServer, app, destinationStreamName, "live"});
remoteRelay.startRelay();
remoteStreams.put(destinationStreamName, remoteRelay);
listenersOnRemoteStream.put(streamName, 1);
}
else {
Integer numberOfListeners = listenersOnRemoteStream.get(streamName) + 1;
listenersOnRemoteStream.put(streamName,numberOfListeners);
}
}
}
log.info("W3C x-category:stream x-event:play c-ip:{} x-sname:{} x-name:{}", new Object[] { Red5.getConnectionLocal().getRemoteAddress(), stream.getName(), item.getName() });
}
@Override
public void streamSubscriberClose(ISubscriberStream stream) {
synchronized(remoteStreams) {
super.streamSubscriberClose(stream);
String streamName = stream.getBroadcastStreamPublishName();
log.trace("Subscriber close for stream [{}]", streamName);
if(streamName.contains("/")) {
if(remoteStreams.containsKey(streamName)) {
Integer numberOfListeners = listenersOnRemoteStream.get(streamName);
if(numberOfListeners != null) {
numberOfListeners = numberOfListeners - 1;
listenersOnRemoteStream.put(streamName, numberOfListeners);
log.trace("Stream [{}] has {} subscribers left", streamName, numberOfListeners);
if(numberOfListeners < 1) {
log.info("Starting timeout to close proxy for stream: {}", streamName);
timer.schedule(new DisconnectProxyTask(streamName), timeout);
}
}
}
}
}
}
private final class DisconnectProxyTask extends TimerTask {
// Stream name that should be disconnected
private String streamName;
public DisconnectProxyTask(String streamName) {
this.streamName = streamName;
}
@Override
public void run() {
// Cancel this task
this.cancel();
// Check if someone reconnected
synchronized(remoteStreams) {
Integer numberOfListeners = listenersOnRemoteStream.get(streamName);
log.trace("Stream [{}] has {} subscribers", streamName, numberOfListeners);
if(numberOfListeners != null) {
if(numberOfListeners < 1) {
// No one else is connected to this stream, close relay
log.info("Stopping relay for stream [{}]", streamName);
listenersOnRemoteStream.remove(streamName);
CustomStreamRelay remoteRelay = remoteStreams.remove(streamName);
remoteRelay.stopRelay();
}
}
}
}
}
}

View File

@ -63,7 +63,7 @@ public class VideoStreamListener implements IStreamListener {
return;
}
if (packet instanceof VideoData) {
/*if (packet instanceof VideoData) {
if (! firstPacketReceived) {
firstPacketReceived = true;
IConnection conn = Red5.getConnectionLocal();
@ -76,7 +76,7 @@ public class VideoStreamListener implements IStreamListener {
recordingService.record(conn.getScope().getName(), event);
}
}
} */
}
public void setEventRecordingService(EventRecordingService s) {

View File

@ -31,7 +31,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
import org.bigbluebutton.modules.videoconf.events.CloseAllWindowsEvent;
import org.bigbluebutton.modules.videoconf.events.ShareCameraRequestEvent;
import org.bigbluebutton.modules.videoconf.events.VideoModuleStartEvent;
import org.bigbluebutton.modules.videoconf.events.VideoModuleStopEvent;
import org.bigbluebutton.modules.videoconf.events.VideoModuleStopEvent;
private var _moduleName:String = "Videoconf Module";
private var _attributes:Object;
@ -46,6 +46,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
public function get uri():String {
return _attributes.uri + "/" + _attributes.room;
//return "rtmp://143.54.10.63/videoproxy" + "/" + _attributes.room;
}
public function get username():String {

View File

@ -29,7 +29,8 @@ package org.bigbluebutton.modules.videoconf.business
import flash.net.NetConnection;
import flash.net.NetStream;
import flash.system.Capabilities;
import flash.utils.Dictionary;
import mx.collections.ArrayCollection;
import org.bigbluebutton.common.LogUtil;
@ -40,16 +41,28 @@ package org.bigbluebutton.modules.videoconf.business
import org.bigbluebutton.modules.videoconf.events.ConnectedEvent;
import org.bigbluebutton.modules.videoconf.events.StartBroadcastEvent;
import org.bigbluebutton.modules.videoconf.model.VideoConfOptions;
import org.bigbluebutton.modules.videoconf.events.PlayConnectionReady;
public class VideoProxy
{
public var videoOptions:VideoConfOptions;
// NetConnection used for stream publishing
private var nc:NetConnection;
// NetStream used for stream publishing
private var ns:NetStream;
private var _url:String;
// Dictionary<url,NetConnection> used for stream playing
private var playConnectionDict:Dictionary;
// Dictionary<url,int> used to keep track of how many streams use a URL
private var playConnectionCountDict:Dictionary;
// Dictionary<userID,streamNamePrefix> used for stream playing
private var streamNamePrefixDict:Dictionary;
// Dictionary<userID,url>
private var userUrlDict:Dictionary;
private function parseOptions():void {
videoOptions = new VideoConfOptions();
videoOptions.parseOptions();
@ -66,11 +79,16 @@ package org.bigbluebutton.modules.videoconf.business
nc.addEventListener(IOErrorEvent.IO_ERROR, onIOError);
nc.addEventListener(NetStatusEvent.NET_STATUS, onNetStatus);
nc.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSecurityError);
playConnectionDict = new Dictionary();
playConnectionCountDict = new Dictionary();
streamNamePrefixDict = new Dictionary();
userUrlDict = new Dictionary();
}
public function connect():void {
nc.connect(_url);
nc.connect(_url);
playConnectionDict[_url] = nc;
playConnectionCountDict[_url] = 0;
}
private function onAsyncError(event:AsyncErrorEvent):void{
@ -83,7 +101,7 @@ package org.bigbluebutton.modules.videoconf.business
var dispatcher:Dispatcher = new Dispatcher();
dispatcher.dispatchEvent(new ConnectedEvent(ConnectedEvent.VIDEO_CONNECTED));
}
private function onNetStatus(event:NetStatusEvent):void{
switch(event.info.code){
case "NetConnection.Connect.Success":
@ -95,14 +113,118 @@ package org.bigbluebutton.modules.videoconf.business
break;
}
}
private function onSecurityError(event:NetStatusEvent):void{
}
public function get connection():NetConnection{
public function get publishConnection():NetConnection{
return this.nc;
}
private function onPlayNetStatus(event:NetStatusEvent):void {
switch(event.info.code){
case "NetConnection.Connect.Success":
var dispatcher:Dispatcher = new Dispatcher();
dispatcher.dispatchEvent(new PlayConnectionReady(PlayConnectionReady.PLAY_CONNECTION_READY));
break;
default:
LogUtil.debug("[" + event.info.code + "] for a play connection");
break;
}
}
public function createPlayConnectionFor(userID:String):void {
LogUtil.debug("VideoProxy::createPlayConnectionFor:: Creating connection for stream from [" + userID + "]");
// TODO: Ask LB for path to current user
var connectionPath:String = "10.0.3.203/10.0.3.254/10.0.3.79";
var serverIp:String = connectionPath.split("/")[0];
var ipRegex:RegExp = /([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/;
var newUrl:String = _url.replace(ipRegex, serverIp);
// Store URL for this user
userUrlDict[userID] = newUrl;
var streamPrefix:String;
if(connectionPath != serverIp) // More than one server -> has prefix
streamPrefix = connectionPath.replace(serverIp + "/", "") + "/";
else
streamPrefix = "";
// Set current user streamPrefix to use the current path
streamNamePrefixDict[userID] = streamPrefix;
// If connection with this URL does not exist
if(!playConnectionDict[newUrl]){
// Create new NetConnection and store it
var connection:NetConnection = new NetConnection();
connection.client = this;
connection.addEventListener(AsyncErrorEvent.ASYNC_ERROR, onAsyncError);
connection.addEventListener(IOErrorEvent.IO_ERROR, onIOError);
connection.addEventListener(NetStatusEvent.NET_STATUS, onPlayNetStatus);
connection.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSecurityError);
connection.connect(newUrl);
// TODO change to trace
LogUtil.debug("VideoProxy::createPlayConnectionFor:: Creating NetConnection for [" + newUrl + "]");
playConnectionDict[newUrl] = connection;
playConnectionCountDict[newUrl] = 0;
}
else {
if(playConnectionDict[newUrl].connected) {
// Connection is ready, send event
var dispatcher:Dispatcher = new Dispatcher();
dispatcher.dispatchEvent(new PlayConnectionReady(PlayConnectionReady.PLAY_CONNECTION_READY));
}
// TODO change to trace
LogUtil.debug("VideoProxy::createPlayConnectionFor:: Found NetConnection for [" + newUrl + "]");
}
}
public function playConnectionIsReadyFor(userID:String):Boolean {
var userUrl:String = userUrlDict[userID];
if(playConnectionDict[userUrl].connected)
return true;
return false;
}
public function getPlayConnectionFor(userID:String):NetConnection {
var userUrl:String = userUrlDict[userID];
playConnectionCountDict[userUrl] = playConnectionCountDict[userUrl] + 1;
// TODO: change to trace
LogUtil.debug("VideoProxy:: getPlayConnection:: URL: [" + userUrl + "], count: [" + playConnectionCountDict[userUrl] + "]");
return playConnectionDict[userUrl];
}
public function getStreamNamePrefixFor(userID:String):String{
// If does not exist
if(!streamNamePrefixDict[userID]){
// TODO: change LogUtil.debug(); to trace();
LogUtil.debug("VideoProxy:: getStreamNamePrefixFor:: streamPrefix not found. NetConnection might not exist for stream from [" + userID + "]");
return "";
}
else{
return streamNamePrefixDict[userID];
}
}
public function closePlayConnectionFor(userID:String):void {
var userUrl:String = userUrlDict[userID];
// Do not close publish connection, no matter what
if(playConnectionDict[userUrl] == nc)
return;
if(userUrl != null) {
var count:int = playConnectionCountDict[userUrl] - 1;
// TODO: change to trace
LogUtil.debug("VideoProxy:: closePlayConnectionFor:: userID: [" + userID + "], URL: [" + userUrl + "], new streamCount: [" + count + "]");
playConnectionCountDict[userUrl] = count;
if(count <= 0) {
// No one else is using this NetConnection
var connection:NetConnection = playConnectionDict[userUrl];
if(connection != null) connection.close();
delete playConnectionDict[userUrl];
delete playConnectionCountDict[userUrl];
}
}
}
public function startPublishing(e:StartBroadcastEvent):void{
ns.addEventListener( NetStatusEvent.NET_STATUS, onNetStatus );
ns.addEventListener( IOErrorEvent.IO_ERROR, onIOError );
@ -164,7 +286,7 @@ package org.bigbluebutton.modules.videoconf.business
}
public function stopBroadcasting():void{
trace("Closing netstream for webcam publishing");
LogUtil.debug("Closing netstream for webcam publishing");
if (ns != null) {
ns.attachCamera(null);
@ -175,9 +297,20 @@ package org.bigbluebutton.modules.videoconf.business
}
public function disconnect():void {
trace("VideoProxy:: disconnecting from Video application");
LogUtil.debug("VideoProxy:: disconnecting from Video application");
stopBroadcasting();
// Close publish NetConnection
if (nc != null) nc.close();
// Close play NetConnections
for (var k:Object in playConnectionDict) {
var connection:NetConnection = playConnectionDict[k];
connection.close();
}
// Reset dictionaries
playConnectionDict = new Dictionary();
playConnectionCountDict = new Dictionary();
streamNamePrefixDict = new Dictionary();
userUrlDict = new Dictionary();
}
public function onBWCheck(... rest):Number {
@ -189,7 +322,7 @@ package org.bigbluebutton.modules.videoconf.business
if (rest.length > 0) p_bw = rest[0];
// your application should do something here
// when the bandwidth check is complete
trace("bandwidth = " + p_bw + " Kbps.");
LogUtil.debug("bandwidth = " + p_bw + " Kbps.");
}

View File

@ -221,7 +221,7 @@ package org.bigbluebutton.modules.videoconf.business
}
override public function close(event:MouseEvent = null):void{
trace("VideoWIndowItf close window event");
LogUtil.debug("VideoWIndowItf close window event");
var e:CloseWindowEvent = new CloseWindowEvent();
e.window = this;

View File

@ -0,0 +1,32 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.modules.videoconf.events
{
import flash.events.Event;
public class PlayConnectionReady extends Event
{
public static const PLAY_CONNECTION_READY:String = "a netconnetion is ready";
public function PlayConnectionReady(type:String, bubbles:Boolean=true, cancelable:Boolean=false)
{
super(type, bubbles, cancelable);
}
}
}

View File

@ -38,6 +38,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
import org.bigbluebutton.modules.videoconf.events.VideoModuleStartEvent;
import org.bigbluebutton.modules.videoconf.events.VideoModuleStopEvent;
import org.bigbluebutton.modules.users.events.ViewCameraEvent;
import org.bigbluebutton.modules.videoconf.events.PlayConnectionReady;
]]>
</mx:Script>
@ -115,6 +116,9 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
<MethodInvoker generator="{VideoEventMapDelegate}" method="handleCamSettingsClosedEvent" arguments="{event}"/>
</EventHandlers>
<EventHandlers type="{PlayConnectionReady.PLAY_CONNECTION_READY}">
<MethodInvoker generator="{VideoEventMapDelegate}" method="handlePlayConnectionReady" />
</EventHandlers>
<!-- ~~~~~~~~~~~~~~~~~~ INJECTORS ~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
</EventMap>

View File

@ -55,6 +55,8 @@ package org.bigbluebutton.modules.videoconf.maps
import org.bigbluebutton.modules.videoconf.views.ToolbarButton;
import org.bigbluebutton.modules.videoconf.views.VideoWindow;
import org.flexunit.runner.manipulation.filters.IncludeAllFilter;
import org.bigbluebutton.modules.videoconf.events.PlayConnectionReady;
public class VideoEventMapDelegate
{
@ -72,6 +74,9 @@ package org.bigbluebutton.modules.videoconf.maps
private var _isPublishing:Boolean = false;
private var _isPreviewWebcamOpen:Boolean = false;
private var _isWaitingActivation:Boolean = false;
// Store userID of windows waiting for a NetConnection
private var pendingVideoWindowsList:Object = new Object();
public function VideoEventMapDelegate(dispatcher:IEventDispatcher)
{
@ -93,7 +98,7 @@ package org.bigbluebutton.modules.videoconf.maps
if (!_ready) return;
trace("VideoEventMapDelegate:: [" + me + "] Viewing [" + userID + " stream [" + stream + "]");
if (! UserManager.getInstance().getConference().amIThisUser(userID)) {
openViewWindowFor(userID);
initPlayConnectionFor(userID);
}
}
@ -213,7 +218,7 @@ package org.bigbluebutton.modules.videoconf.maps
closeWindow(userID);
}
trace("VideoEventMapDelegate:: [" + me + "] openWebcamWindowFor:: View user's = [" + userID + "] webcam.");
openViewWindowFor(userID);
initPlayConnectionFor(userID);
} else {
if (UsersUtil.isMe(userID) && options.autoStart) {
trace("VideoEventMapDelegate:: [" + me + "] openWebcamWindowFor:: It's ME and AutoStart. Start publishing.");
@ -279,6 +284,7 @@ package org.bigbluebutton.modules.videoconf.maps
if (win != null) {
trace("VideoEventMapDelegate:: [" + me + "] closeWindow:: Closing [" + win.getWindowType() + "] for [" + userID + "] [" + UsersUtil.getUserName(userID) + "]");
win.close();
proxy.closePlayConnectionFor(userID);
var cwe:CloseWindowEvent = new CloseWindowEvent();
cwe.window = win;
_dispatcher.dispatchEvent(cwe);
@ -286,6 +292,25 @@ package org.bigbluebutton.modules.videoconf.maps
trace("VideoEventMapDelegate:: [" + me + "] closeWindow:: Not Closing. No window for [" + userID + "] [" + UsersUtil.getUserName(userID) + "]");
}
}
private function initPlayConnectionFor(userID:String):void {
//TODO: Change to trace
LogUtil.debug("VideoEventMapDelegate:: initPlayConnectionFor : [" + userID + "]");
// Store the userID
pendingVideoWindowsList[userID] = true;
// Request the connection
proxy.createPlayConnectionFor(userID);
}
public function handlePlayConnectionReady():void {
// Iterate through all pending windows
for(var userID:String in pendingVideoWindowsList) {
if(proxy.playConnectionIsReadyFor(userID)) {
delete pendingVideoWindowsList[userID];
openViewWindowFor(userID);
}
}
}
private function openViewWindowFor(userID:String):void {
trace("VideoEventMapDelegate:: [" + me + "] openViewWindowFor:: Opening VIEW window for [" + userID + "] [" + UsersUtil.getUserName(userID) + "]");
@ -299,7 +324,8 @@ package org.bigbluebutton.modules.videoconf.maps
closeWindow(userID);
var bbbUser:BBBUser = UsersUtil.getUser(userID);
window.startVideo(proxy.connection, bbbUser.streamName);
var streamName:String = proxy.getStreamNamePrefixFor(userID) + bbbUser.streamName;
window.startVideo(proxy.getPlayConnectionFor(userID), streamName);
webcamWindows.addWindow(window);
openWindow(window);
@ -325,7 +351,7 @@ package org.bigbluebutton.modules.videoconf.maps
}
public function startPublishing(e:StartBroadcastEvent):void{
LogUtil.debug("VideoEventMapDelegate:: [" + me + "] startPublishing:: Publishing stream to: " + proxy.connection.uri + "/" + e.stream);
LogUtil.debug("VideoEventMapDelegate:: [" + me + "] startPublishing:: Publishing stream to: " + proxy.publishConnection.uri + "/" + e.stream);
streamName = e.stream;
proxy.startPublishing(e);
@ -487,4 +513,4 @@ package org.bigbluebutton.modules.videoconf.maps
}
}
}
}
}

View File

@ -22,6 +22,7 @@ package org.bigbluebutton.modules.videoconf.maps
import mx.collections.ArrayCollection;
import org.bigbluebutton.common.LogUtil;
import org.bigbluebutton.core.UsersUtil;
import org.bigbluebutton.modules.videoconf.business.VideoWindowItf;
import org.bigbluebutton.modules.videoconf.views.AvatarWindow;
@ -36,13 +37,13 @@ package org.bigbluebutton.modules.videoconf.maps
public function addWindow(window:VideoWindowItf):void {
webcamWindows.addItem(window);
trace("[" + me + "] addWindow:: userID = [" + window.userID + "] numWindows = [" + webcamWindows.length + "]");
LogUtil.debug("[" + me + "] addWindow:: userID = [" + window.userID + "] numWindows = [" + webcamWindows.length + "]");
}
public function removeWindow(userID:String):VideoWindowItf {
for (var i:int = 0; i < webcamWindows.length; i++) {
var win:VideoWindowItf = webcamWindows.getItemAt(i) as VideoWindowItf;
trace("[" + me + "] removeWindow:: [" + win.userID + " == " + userID + "] equal = [" + (win.userID == userID) + "]");
LogUtil.debug("[" + me + "] removeWindow:: [" + win.userID + " == " + userID + "] equal = [" + (win.userID == userID) + "]");
if (win.userID == userID) {
return webcamWindows.removeItemAt(i) as VideoWindowItf;
}
@ -52,10 +53,10 @@ package org.bigbluebutton.modules.videoconf.maps
}
public function hasWindow(userID:String):Boolean {
trace("[" + me + "] hasWindow:: user [" + userID + "] numWindows = [" + webcamWindows.length + "]");
LogUtil.debug("[" + me + "] hasWindow:: user [" + userID + "] numWindows = [" + webcamWindows.length + "]");
for (var i:int = 0; i < webcamWindows.length; i++) {
var win:VideoWindowItf = webcamWindows.getItemAt(i) as VideoWindowItf;
trace("[" + me + "] hasWindow:: [" + win.userID + " == " + userID + "] equal = [" + (win.userID == userID) + "]");
LogUtil.debug("[" + me + "] hasWindow:: [" + win.userID + " == " + userID + "] equal = [" + (win.userID == userID) + "]");
if (win.userID == userID) {
return true;
}
@ -67,11 +68,11 @@ package org.bigbluebutton.modules.videoconf.maps
public function getWindow(userID:String):VideoWindowItf {
for (var i:int = 0; i < webcamWindows.length; i++) {
var win:VideoWindowItf = webcamWindows.getItemAt(i) as VideoWindowItf;
trace("[" + me + "] getWindow:: [" + win.userID + " == " + userID + "] equal = [" + (win.userID == userID) + "]");
LogUtil.debug("[" + me + "] getWindow:: [" + win.userID + " == " + userID + "] equal = [" + (win.userID == userID) + "]");
if (win.userID == userID) return win;
}
return null;
}
}
}
}

View File

@ -190,12 +190,12 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
}
private function handleMadePresenterEvent(event:MadePresenterEvent):void {
trace("******** Avatar: HandleMadePresenter event *********");
LogUtil.debug("******** Avatar: HandleMadePresenter event *********");
updateControlButtons();
}
private function handleSwitchedPresenterEvent(event:SwitchedPresenterEvent):void {
trace("******** Avatar: handleSwitchedPresenterEvent event *********");
LogUtil.debug("******** Avatar: handleSwitchedPresenterEvent event *********");
updateControlButtons();
}
@ -241,7 +241,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
}
private function closeThisWindow():void {
trace("* Closing avatar window for user [" + userID + "] *");
LogUtil.debug("* Closing avatar window for user [" + userID + "] *");
}

View File

@ -127,13 +127,13 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
private function displayEjectButton(controlsForPresenter:Boolean):void {
var userOption:UsersOptions = new UsersOptions();
if (! userOption.allowKickUser) {
trace("Kicking user not allowed");
LogUtil.debug("Kicking user not allowed");
// User kicking not enabled. Just return;
ejectUserBtn.visible = false;
return;
}
trace("Kicking user allowed [" + userOption.allowKickUser + "]");
LogUtil.debug("Kicking user allowed [" + userOption.allowKickUser + "]");
/**
* Display button if:

View File

@ -167,12 +167,12 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
}
private function handleMadePresenterEvent(event:MadePresenterEvent):void {
trace("******** PublishWindow: HandleMadePresenter event *********");
LogUtil.debug("******** PublishWindow: HandleMadePresenter event *********");
updateControlButtons();
}
private function handleSwitchedPresenterEvent(event:SwitchedPresenterEvent):void {
trace("******** PublishWindow: handleSwitchedPresenterEvent event *********");
LogUtil.debug("******** PublishWindow: handleSwitchedPresenterEvent event *********");
updateControlButtons();
}
@ -236,7 +236,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
_camera.setQuality(videoOptions.camQualityBandwidth, videoOptions.camQualityPicture);
if (_camera.width != camWidth || _camera.height != camHeight) {
trace("Resolution " + camWidth + "x" + camHeight + " is not supported, using " + _camera.width + "x" + _camera.height + " instead");
LogUtil.debug("Resolution " + camWidth + "x" + camHeight + " is not supported, using " + _camera.width + "x" + _camera.height + " instead");
setResolution(_camera.width, _camera.height);
}
@ -262,12 +262,12 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
private function onActivityEvent(e:ActivityEvent):void {
if (_waitingForActivation && e.activating) {
trace("Cam activity event: waitingForActivation = [" + _waitingForActivation + "] activating = [" + e.activating + "]");
LogUtil.debug("Cam activity event: waitingForActivation = [" + _waitingForActivation + "] activating = [" + e.activating + "]");
_activationTimer.stop();
showWarning('bbb.video.publish.hint.videoPreview', false, "0xFFFF00");
_waitingForActivation = false;
trace("Starting auto-publisher timer.");
LogUtil.debug("Starting auto-publisher timer.");
autoPublishTimer = new Timer(3000, 1);
autoPublishTimer.addEventListener(TimerEvent.TIMER, autopublishTimerHandler);
autoPublishTimer.start();
@ -333,23 +333,23 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
private var _isClosing:Boolean = false;
override public function close(event:MouseEvent=null):void{
trace("PublishWindow:: closing");
LogUtil.debug("PublishWindow:: closing");
if (!_isClosing) {
_isClosing = true;
stopPublishing();
trace("Dispatching ClosePublishWindowEvent event");
LogUtil.debug("Dispatching ClosePublishWindowEvent event");
var gDispatcher:Dispatcher = new Dispatcher();
gDispatcher.dispatchEvent(new ClosePublishWindowEvent());
}
}
private function stopCamera():void {
trace("PublishWindow:: stopping camera");
LogUtil.debug("PublishWindow:: stopping camera");
_camera = null;
if (_video != null) {
trace("PublishWindow:: removing video from display");
LogUtil.debug("PublishWindow:: removing video from display");
_videoHolder.removeChild(_video);
_video.attachCamera(null);
@ -360,7 +360,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
}
private function stopPublishing():void{
trace("PublishWindow::stopPublishing");
LogUtil.debug("PublishWindow::stopPublishing");
stopCamera();
var e:StopBroadcastEvent = new StopBroadcastEvent()
e.stream = streamName;
@ -431,7 +431,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
}
private function closeWindow(e:CloseAllWindowsEvent):void{
trace("PublishWindow::closeWindow");
LogUtil.debug("PublishWindow::closeWindow");
stopCamera();
}

View File

@ -39,6 +39,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
<![CDATA[
import com.asfusion.mate.events.Dispatcher;
import org.bigbluebutton.common.LogUtil;
import org.bigbluebutton.common.Images;
import org.bigbluebutton.core.events.LockControlEvent;
import org.bigbluebutton.core.managers.UserManager;
@ -114,14 +115,14 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
private function openPublishWindow():void{
if(_currentState == ON_STATE) {
trace("Close window");
LogUtil.debug("Close window");
_currentState = OFF_STATE;
//this.toolTip = ResourceUtil.getInstance().getString('bbb.toolbar.video.toolTip.start');
camIcon = images.webcam;
this.selected = false;
dispatchEvent(new ClosePublishWindowEvent());
} else {
trace("Share camera");
LogUtil.debug("Share camera");
_currentState = ON_STATE;
//this.toolTip = ResourceUtil.getInstance().getString('bbb.toolbar.video.toolTip.stop');
camIcon = images.webcamOn;

View File

@ -63,10 +63,13 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
import org.bigbluebutton.modules.videoconf.business.TalkingButtonOverlay;
import org.bigbluebutton.modules.videoconf.events.CloseAllWindowsEvent;
import org.bigbluebutton.modules.videoconf.model.VideoConfOptions;
import flash.net.NetConnection;
private var ns:NetStream;
private var globalDispatcher:Dispatcher;
[Bindable]
public var videoOptions:VideoConfOptions = new VideoConfOptions();
@ -74,12 +77,19 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
[Bindable] public var glowColor:String = "";
[Bindable] public var glowBlurSize:Number = 0;
public var connection2:NetConnection;
override public function getWindowType():String {
return windowType;
}
private function onCreationComplete():void{
//connection2.connect("rtmp://143.54.10.63/video/conferencia");
this.glowColor = videoOptions.glowColor;
this.glowBlurSize = videoOptions.glowBlurSize;
@ -123,12 +133,12 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
}
private function handleMadePresenterEvent(event:MadePresenterEvent):void {
trace("******** VideoWindow: HandleMadePresenter event *********");
LogUtil.debug("******** VideoWindow: HandleMadePresenter event *********");
updateControlButtons();
}
private function handleSwitchedPresenterEvent(event:SwitchedPresenterEvent):void {
trace("******** VideoWindow: handleSwitchedPresenterEvent event *********");
LogUtil.debug("******** VideoWindow: handleSwitchedPresenterEvent event *********");
updateControlButtons();
}
@ -174,6 +184,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
public function startVideo(connection:NetConnection, stream:String):void{
ns = new NetStream(connection);
ns.addEventListener( NetStatusEvent.NET_STATUS, onNetStatus );
ns.addEventListener(AsyncErrorEvent.ASYNC_ERROR, onAsyncError);
@ -194,7 +205,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
if (videoOptions.smoothVideo) {
trace("Smoothing video.")
LogUtil.debug("Smoothing video.")
_video.smoothing = true;
}
@ -202,15 +213,15 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
var filter:ConvolutionFilter = new flash.filters.ConvolutionFilter();
filter.matrixX = 3;
filter.matrixY = 3;
trace("Applying convolution filter =[" + videoOptions.convolutionFilter + "]");
LogUtil.debug("Applying convolution filter =[" + videoOptions.convolutionFilter + "]");
filter.matrix = videoOptions.convolutionFilter;
filter.bias = videoOptions.filterBias;
filter.divisor = videoOptions.filterDivisor;
_video.filters = [filter];
}
ns.play(stream);
this.streamName = stream;
this.width = _video.width + paddingHorizontal;
this.height = _video.height + paddingVertical;
@ -225,7 +236,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
}
public function onMetaData(info:Object):void{
trace("metadata: width=" + info.width + " height=" + info.height);
LogUtil.debug("metadata: width=" + info.width + " height=" + info.height);
_video.width = info.width;
_video.height = info.height;
setAspectRatio(info.width, info.height);
@ -233,6 +244,8 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
}
private function onNetStatus(e:NetStatusEvent):void{
LogUtil.debug(e.info.code);
switch(e.info.code){
case "NetStream.Publish.Start":
LogUtil.debug("NetStream.Publish.Start for broadcast stream " + streamName);
@ -253,6 +266,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
case "NetStream.Play.NoSupportedTrackFound":
LogUtil.debug("The MP4 doesn't contain any supported tracks");
break;
}
}

View File

@ -77,7 +77,7 @@ defaultDialAccessNumber=613-555-1234
# conference. This is only used for the old scheduling which will be
# removed in the future. Use the API to create a conference.
defaultWelcomeMessage=<br>Welcome to <b>%%CONFNAME%%</b>!<br><br>For help on using BigBlueButton see these (short) <a href="event:http://www.bigbluebutton.org/content/videos"><u>tutorial videos</u></a>.<br><br>To join the audio bridge click the headset icon (upper-left hand corner). Use a headset to avoid causing background noise for others.<br>
defaultWelcomeMessageFooter=This server is running a <a href="https://code.google.com/p/bigbluebutton/wiki/081Overview" target="_blank"><u>BigBlueButton 0.9.0-dev</u></a>.
defaultWelcomeMessageFooter=This server is running <a href="https://code.google.com/p/bigbluebutton/wiki/081Overview" target="_blank"><u>BigBlueButton 0.81-RC5</u></a>.
# Default maximum number of users a meeting can have.
# Doesn't get enforced yet but is the default value when the create