Merge branch 'antobinary-merge-webrtc-ds'

This commit is contained in:
Richard Alam 2016-05-20 20:28:43 +00:00
commit 7125f4534d
146 changed files with 12027 additions and 622 deletions

View File

@ -24,6 +24,8 @@ resolvers ++= Seq(
"blindside-repos" at "http://blindside.googlecode.com/svn/repository/"
)
resolvers += Resolver.sonatypeRepo("releases")
publishTo := Some(Resolver.file("file", new File(Path.userHome.absolutePath+"/dev/repo/maven-repo/releases" )) )
// We want to have our jar files in lib_managed dir.
@ -38,20 +40,20 @@ testOptions in Test += Tests.Argument(TestFrameworks.ScalaTest, "-h", "target/sc
libraryDependencies ++= {
val akkaVersion = "2.3.11"
Seq(
"com.typesafe.akka" %% "akka-actor" % akkaVersion,
"com.typesafe.akka" %% "akka-testkit" % akkaVersion % "test",
"com.typesafe.akka" %% "akka-slf4j" % akkaVersion,
"ch.qos.logback" % "logback-classic" % "1.0.13" % "runtime",
"org.pegdown" % "pegdown" % "1.4.0",
"junit" % "junit" % "4.11",
"com.etaty.rediscala" %% "rediscala" % "1.4.0",
"commons-codec" % "commons-codec" % "1.8",
"joda-time" % "joda-time" % "2.3",
"com.google.code.gson" % "gson" % "1.7.1",
"redis.clients" % "jedis" % "2.7.2",
"org.apache.commons" % "commons-lang3" % "3.2",
"org.bigbluebutton" % "bbb-common-message" % "0.0.17-SNAPSHOT"
)}
"com.typesafe.akka" %% "akka-actor" % akkaVersion,
"com.typesafe.akka" %% "akka-testkit" % akkaVersion % "test",
"com.typesafe.akka" %% "akka-slf4j" % akkaVersion,
"ch.qos.logback" % "logback-classic" % "1.0.13" % "runtime",
"org.pegdown" % "pegdown" % "1.4.0",
"junit" % "junit" % "4.11",
"com.etaty.rediscala" %% "rediscala" % "1.4.0",
"commons-codec" % "commons-codec" % "1.8",
"joda-time" % "joda-time" % "2.3",
"com.google.code.gson" % "gson" % "1.7.1",
"redis.clients" % "jedis" % "2.7.2",
"org.apache.commons" % "commons-lang3" % "3.2",
"org.bigbluebutton" % "bbb-common-message" % "0.0.17"
)}
seq(Revolver.settings: _*)

View File

@ -110,5 +110,12 @@ public interface IBigBlueButtonInGW {
void undoWhiteboard(String meetingID, String requesterID, String whiteboardId);
void enableWhiteboard(String meetingID, String requesterID, Boolean enable);
void isWhiteboardEnabled(String meetingID, String requesterID, String replyTo);
// DeskShare
void deskShareStarted(String conferenceName, String callerId, String callerIdName);
void deskShareStopped(String conferenceName, String callerId, String callerIdName);
void deskShareRTMPBroadcastStarted(String conferenceName, String streamname, int videoWidth, int videoHeight, String timestamp);
void deskShareRTMPBroadcastStopped(String conferenceName, String streamname, int videoWidth, int videoHeight, String timestamp);
void deskShareGetInfoRequest(String meetingId, String requesterId, String replyTo);
}

View File

@ -0,0 +1,59 @@
package org.bigbluebutton.core.pubsub.receivers;
import com.google.gson.JsonParser;
import com.google.gson.JsonObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.bigbluebutton.common.messages.DeskShareStartedEventMessage;
import org.bigbluebutton.common.messages.DeskShareStoppedEventMessage;
import org.bigbluebutton.common.messages.DeskShareRTMPBroadcastStartedEventMessage;
import org.bigbluebutton.common.messages.DeskShareRTMPBroadcastStoppedEventMessage;
import org.bigbluebutton.common.messages.DeskShareGetInfoRequestMessage;
import org.bigbluebutton.common.messages.MessagingConstants;
import org.bigbluebutton.core.api.IBigBlueButtonInGW;
public class DeskShareMessageReceiver implements MessageHandler {
private IBigBlueButtonInGW bbbGW;
private static final Logger log = LoggerFactory.getLogger(DeskShareMessageReceiver.class);
public DeskShareMessageReceiver(IBigBlueButtonInGW bbbGW) {
this.bbbGW = bbbGW;
}
@Override
public void handleMessage(String pattern, String channel, String message) {
if (channel.equalsIgnoreCase(MessagingConstants.FROM_VOICE_CONF_SYSTEM_CHAN)) {
JsonParser parser = new JsonParser();
JsonObject obj = (JsonObject) parser.parse(message);
if (obj.has("header") && obj.has("payload")) {
JsonObject header = (JsonObject) obj.get("header");
if (header.has("name")) {
String messageName = header.get("name").getAsString();
if (DeskShareStartedEventMessage.DESKSHARE_STARTED_MESSAGE.equals(messageName)) {
DeskShareStartedEventMessage msg = DeskShareStartedEventMessage.fromJson(message);
log.info("^^^^^^^DESKSHARE STARTED^^^^^^");
bbbGW.deskShareStarted(msg.conferenceName, msg.callerId, msg.callerIdName);
} else if (DeskShareStoppedEventMessage.DESK_SHARE_STOPPED_MESSAGE.equals(messageName)) {
DeskShareStoppedEventMessage msg = DeskShareStoppedEventMessage.fromJson(message);
log.info("^^^^^^^DESKSHARE STOPPED^^^^^^");
bbbGW.deskShareStopped(msg.conferenceName, msg.callerId, msg.callerIdName);
} else if (DeskShareRTMPBroadcastStartedEventMessage.DESKSHARE_RTMP_BROADCAST_STARTED_MESSAGE.equals(messageName)) {
log.info("^^^^^^^DESKSHARE_RTMP_BROADCAST_STARTED_MESSAGE^^^^^^");
DeskShareRTMPBroadcastStartedEventMessage msg = DeskShareRTMPBroadcastStartedEventMessage.fromJson(message);
bbbGW.deskShareRTMPBroadcastStarted(msg.conferenceName, msg.streamname, msg.vw, msg.vh, msg.timestamp);
} else if (DeskShareRTMPBroadcastStoppedEventMessage.DESKSHARE_RTMP_BROADCAST_STOPPED_MESSAGE.equals(messageName)) {
log.info("^^^^^^^DESKSHARE_RTMP_BROADCAST_STOPPED_MESSAGE^^^^^^");
DeskShareRTMPBroadcastStoppedEventMessage msg = DeskShareRTMPBroadcastStoppedEventMessage.fromJson(message);
bbbGW.deskShareRTMPBroadcastStopped(msg.conferenceName, msg.streamname, msg.vw, msg.vh, msg.timestamp);
} else if (DeskShareGetInfoRequestMessage.GET_DESKTOP_SHARE_GET_INFO_REQUEST.equals(messageName)) {
log.info("^^^^^^^GET_DESKTOP_SHARE_INFO_REQUEST^^^^^^");
DeskShareGetInfoRequestMessage msg = DeskShareGetInfoRequestMessage.fromJson(message);
bbbGW.deskShareGetInfoRequest(msg.meetingId, msg.requesterId, msg.replyTo);
}
}
}
}
}
}

View File

@ -45,10 +45,13 @@ public class RedisMessageReceiver {
WhiteboardMessageReceiver whiteboardRx = new WhiteboardMessageReceiver(bbbGW);
receivers.add(whiteboardRx);
DeskShareMessageReceiver deskShareRx = new DeskShareMessageReceiver(bbbGW);
receivers.add(deskShareRx);
PollingMessageReceiver pollRx = new PollingMessageReceiver(bbbGW);
receivers.add(pollRx);
MeetingMessageReceiver meetingRx = new MeetingMessageReceiver(bbbGW);
receivers.add(meetingRx);
}

View File

@ -0,0 +1,28 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2015 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.core.recorders.events;
import org.bigbluebutton.core.service.recorder.RecordEvent;
public abstract class AbstractDeskShareRecordEvent extends RecordEvent {
public AbstractDeskShareRecordEvent() {
setModule("DESKSHARE");
}
}

View File

@ -0,0 +1,26 @@
package org.bigbluebutton.core.recorders.events;
public class DeskShareNotifyViewersRTMPRecordEvent extends
AbstractDeskShareRecordEvent {
public DeskShareNotifyViewersRTMPRecordEvent() {
super();
setEvent("DeskShareNotifyViewersRTMP");
}
public void setStreamPath(String streamPath) {
eventMap.put("streamPath", streamPath);
}
public void setBroadcasting(Boolean broadcasting) {
eventMap.put("broadcasting", broadcasting.toString());
}
public void setVideoWidth(int videoWidth) {
eventMap.put("videoWidth", Integer.toString(videoWidth));
}
public void setVideoHeight(int videoHeight) {
eventMap.put("videoHeight", Integer.toString(videoHeight));
}
}

View File

@ -0,0 +1,14 @@
package org.bigbluebutton.core.recorders.events;
public class DeskShareStartRTMPRecordEvent extends
AbstractDeskShareRecordEvent {
public DeskShareStartRTMPRecordEvent() {
super();
setEvent("DeskShareStartRTMP");
}
public void setStreamPath(String streamPath) {
eventMap.put("streamPath", streamPath);
}
}

View File

@ -0,0 +1,14 @@
package org.bigbluebutton.core.recorders.events;
public class DeskShareStopRTMPRecordEvent extends
AbstractDeskShareRecordEvent {
public DeskShareStopRTMPRecordEvent() {
super();
setEvent("DeskShareStopRTMP");
}
public void setStreamPath(String streamPath) {
eventMap.put("streamPath", streamPath);
}
}

View File

@ -1,13 +0,0 @@
package org.bigbluebutton.core.service.chat;
public class ChatKeyUtil {
public static final String CHAT_TYPE = "chatType";
public static final String FROM_USERID = "fromUserID";
public static final String FROM_USERNAME = "fromUsername";
public static final String FROM_COLOR = "fromColor";
public static final String FROM_TIME = "fromTime";
public static final String FROM_TZ_OFFSET = "fromTimezoneOffset";
public static final String TO_USERID = "toUserID";
public static final String TO_USERNAME = "toUsername";
public static final String MESSAGE = "message";
}

View File

@ -1,6 +0,0 @@
package org.bigbluebutton.core.service.voice;
public class VoiceKeyUtil {
public static final String MUTE = "mute";
public static final String USERID = "userId";
}

View File

@ -33,4 +33,9 @@ redis {
password=""
# recording keys should expire in 14 days
keyExpiry=1209600
}
}
red5 {
deskshareip="192.168.0.109"
deskshareapp="video-broadcast"
}

View File

@ -29,11 +29,10 @@ object Boot extends App with SystemConfiguration {
val recorderApp = new RecorderApplication(redisDispatcher)
recorderApp.start()
val bbbInGW = new BigBlueButtonInGW(system, recorderApp, msgSender)
val bbbInGW = new BigBlueButtonInGW(system, recorderApp, msgSender, red5DeskShareIP, red5DeskShareApp)
val redisMsgReceiver = new RedisMessageReceiver(bbbInGW)
val redisSubscriberActor = system.actorOf(AppsRedisSubscriberActor.props(redisMsgReceiver), "redis-subscriber")
val keepAliveRedisPublisher = new KeepAliveRedisPublisher(system, redisPublisher)
}
}

View File

@ -11,4 +11,6 @@ trait SystemConfiguration {
lazy val redisPort = Try(config.getInt("redis.port")).getOrElse(6379)
lazy val redisPassword = Try(config.getString("redis.password")).getOrElse("")
lazy val keysExpiresInSec = Try(config.getInt("redis.keyExpiry")).getOrElse(14 * 86400) // 14 days
lazy val red5DeskShareIP = Try(config.getString("red5.deskshareip")).getOrElse("127.0.0.1")
lazy val red5DeskShareApp = Try(config.getString("red5.deskshareapp")).getOrElse("")
}

View File

@ -47,12 +47,20 @@ class BigBlueButtonActor(val system: ActorSystem, recorderApp: RecorderApplicati
case msg: UserMutedInVoiceConfMessage => handleUserMutedInVoiceConfMessage(msg)
case msg: UserTalkingInVoiceConfMessage => handleUserTalkingInVoiceConfMessage(msg)
case msg: VoiceConfRecordingStartedMessage => handleVoiceConfRecordingStartedMessage(msg)
case msg: DeskShareStartedRequest => handleDeskShareStartedRequest(msg)
case msg: DeskShareStoppedRequest => handleDeskShareStoppedRequest(msg)
case msg: DeskShareRTMPBroadcastStartedRequest => handleDeskShareRTMPBroadcastStartedRequest(msg)
case msg: DeskShareRTMPBroadcastStoppedRequest => handleDeskShareRTMPBroadcastStoppedRequest(msg)
case msg: DeskShareGetDeskShareInfoRequest => handleDeskShareGetDeskShareInfoRequest(msg)
case msg: InMessage => handleMeetingMessage(msg)
case _ => // do nothing
}
private def findMeetingWithVoiceConfId(voiceConfId: String): Option[RunningMeeting] = {
meetings.values.find(m => m.mProps.voiceBridge == voiceConfId)
meetings.values.find(m => {
println("+++ compare " + m.mProps.voiceBridge + " with our " + voiceConfId)
m.mProps.voiceBridge == voiceConfId
})
}
private def handleUserJoinedVoiceConfMessage(msg: UserJoinedVoiceConfMessage) {
@ -230,9 +238,61 @@ class BigBlueButtonActor(val system: ActorSystem, recorderApp: RecorderApplicati
//send lock settings
self ! (new GetLockSettings(id, "nodeJSapp"))
//send desktop sharing info
self ! (new DeskShareGetDeskShareInfoRequest(id, "nodeJSapp", "nodeJSapp"))
}
outGW.send(new GetAllMeetingsReply(resultArray))
}
private def handleDeskShareStartedRequest(msg: DeskShareStartedRequest) {
log.info("handleDeskShareStartedRequest: msg.conferenceName=" + msg.conferenceName)
findMeetingWithVoiceConfId(msg.conferenceName) foreach { m =>
{
// println(msg.conferenceName + " (in for each) handleDeskShareStartedRequest BBBActor ")
m.actorRef ! msg
}
}
}
private def handleDeskShareStoppedRequest(msg: DeskShareStoppedRequest) {
log.info("handleDeskShareStoppedRequest msg.conferenceName=" + msg.conferenceName)
findMeetingWithVoiceConfId(msg.conferenceName) foreach { m =>
{
// println(msg.conferenceName + " (in for each) handleDeskShareStoppedRequest BBBActor ")
m.actorRef ! msg
}
}
}
private def handleDeskShareRTMPBroadcastStartedRequest(msg: DeskShareRTMPBroadcastStartedRequest) {
log.info("handleDeskShareRTMPBroadcastStartedRequest msg.conferenceName=" + msg.conferenceName)
findMeetingWithVoiceConfId(msg.conferenceName) foreach { m =>
{
// println(msg.conferenceName + " (in for each) handleDeskShareRTMPBroadcastStartedRequest BBBActor ")
m.actorRef ! msg
}
}
}
private def handleDeskShareRTMPBroadcastStoppedRequest(msg: DeskShareRTMPBroadcastStoppedRequest) {
log.info("handleDeskShareRTMPBroadcastStoppedRequest msg.conferenceName=" + msg.conferenceName)
findMeetingWithVoiceConfId(msg.conferenceName) foreach { m =>
{
// println(msg.conferenceName + " (in for each) handleDeskShareRTMPBroadcastStoppedRequest BBBActor ")
m.actorRef ! msg
}
}
}
private def handleDeskShareGetDeskShareInfoRequest(msg: DeskShareGetDeskShareInfoRequest): Unit = {
val m = meetings.values.find(m => {
m.mProps.meetingID == msg.conferenceName
})
m foreach { mActor => mActor.actorRef ! msg }
}
}

View File

@ -18,7 +18,9 @@ import org.bigbluebutton.common.messages.IBigBlueButtonMessage
import org.bigbluebutton.common.messages.StartCustomPollRequestMessage
import org.bigbluebutton.common.messages.PubSubPingMessage
class BigBlueButtonInGW(val system: ActorSystem, recorderApp: RecorderApplication, messageSender: MessageSender) extends IBigBlueButtonInGW {
class BigBlueButtonInGW(val system: ActorSystem, recorderApp: RecorderApplication, messageSender: MessageSender,
val red5DeskShareIP: String, val red5DeskShareApp: String) extends IBigBlueButtonInGW {
val log = system.log
val bbbActor = system.actorOf(BigBlueButtonActor.props(system, recorderApp, messageSender), "bigbluebutton-actor")
@ -41,7 +43,7 @@ class BigBlueButtonInGW(val system: ActorSystem, recorderApp: RecorderApplicatio
val mProps = new MeetingProperties(meetingID, externalMeetingID, meetingName, record,
voiceBridge, duration, autoStartRecording, allowStartStopRecording,
moderatorPass, viewerPass, createTime, createDate)
moderatorPass, viewerPass, createTime, createDate, red5DeskShareIP, red5DeskShareApp)
bbbActor ! new CreateMeeting(meetingID, mProps)
}
@ -443,6 +445,31 @@ class BigBlueButtonInGW(val system: ActorSystem, recorderApp: RecorderApplicatio
bbbActor ! new VoiceConfRecordingStartedMessage(voiceConfId, recordingFile, recording, timestamp)
}
/**
* *******************************************************************
* Message Interface for DeskShare
* *****************************************************************
*/
def deskShareStarted(conferenceName: String, callerId: String, callerIdName: String) {
bbbActor ! new DeskShareStartedRequest(conferenceName, callerId, callerIdName)
}
def deskShareStopped(conferenceName: String, callerId: String, callerIdName: String) {
bbbActor ! new DeskShareStoppedRequest(conferenceName, callerId, callerIdName)
}
def deskShareRTMPBroadcastStarted(conferenceName: String, streamname: String, videoWidth: Int, videoHeight: Int, timestamp: String) {
bbbActor ! new DeskShareRTMPBroadcastStartedRequest(conferenceName, streamname, videoWidth, videoHeight, timestamp)
}
def deskShareRTMPBroadcastStopped(conferenceName: String, streamname: String, videoWidth: Int, videoHeight: Int, timestamp: String) {
bbbActor ! new DeskShareRTMPBroadcastStoppedRequest(conferenceName, streamname, videoWidth, videoHeight, timestamp)
}
def deskShareGetInfoRequest(meetingId: String, requesterId: String, replyTo: String): Unit = {
bbbActor ! new DeskShareGetDeskShareInfoRequest(meetingId, requesterId, replyTo)
}
// Polling
def votePoll(meetingId: String, userId: String, pollId: String, questionId: Integer, answerId: Integer) {
bbbActor ! new RespondToPollRequest(meetingId, userId, pollId, questionId, answerId)

View File

@ -56,6 +56,16 @@ class MeetingActor(val mProps: MeetingProperties, val outGW: OutMessageGateway)
handleUserTalkingInVoiceConfMessage(msg)
case msg: VoiceConfRecordingStartedMessage =>
handleVoiceConfRecordingStartedMessage(msg)
case msg: DeskShareRTMPBroadcastStartedRequest =>
handleDeskShareRTMPBroadcastStartedRequest(msg)
case msg: DeskShareRTMPBroadcastStoppedRequest =>
handleDeskShareRTMPBroadcastStoppedRequest(msg)
case msg: DeskShareStartedRequest =>
handleDeskShareStartedRequest(msg)
case msg: DeskShareStoppedRequest =>
handleDeskShareStoppedRequest(msg)
case msg: DeskShareGetDeskShareInfoRequest =>
handleDeskShareGetDeskShareInfoRequest(msg)
case msg: UserJoining =>
handleUserJoin(msg)
case msg: UserLeaving =>
@ -221,11 +231,12 @@ class MeetingActor(val mProps: MeetingProperties, val outGW: OutMessageGateway)
}
def handleMonitorNumberOfWebUsers() {
// println("BACK TIMER")
// println("BACK TIMER")
if (usersModel.numWebUsers == 0 && meetingModel.lastWebUserLeftOn > 0) {
if (timeNowInMinutes - meetingModel.lastWebUserLeftOn > 2) {
log.info("Empty meeting. Ejecting all users from voice. meetingId={}", mProps.meetingID)
outGW.send(new EjectAllVoiceUsers(mProps.meetingID, mProps.recorded, mProps.voiceBridge))
}
}
@ -292,4 +303,74 @@ class MeetingActor(val mProps: MeetingProperties, val outGW: OutMessageGateway)
meetingModel.permissionsEqual(other)
}
}
// Broadcast video stream,
private def handleDeskShareStartedRequest(msg: DeskShareStartedRequest) {
log.info("handleDeskShareStartedRequest: dsStarted=" + meetingModel.getDeskShareStarted())
if (!meetingModel.getDeskShareStarted()) {
val timestamp = System.currentTimeMillis().toString()
val streamPath = "rtmp://" + mProps.red5DeskShareIP + "/" + mProps.red5DeskShareApp +
"/" + mProps.meetingID + "/" + mProps.meetingID + "-" + timestamp
log.info("handleDeskShareStartedRequest: streamPath=" + streamPath)
// Tell FreeSwitch to broadcast to RTMP
outGW.send(new DeskShareStartRTMPBroadcast(msg.conferenceName, streamPath))
meetingModel.setDeskShareStarted(true)
}
}
private def handleDeskShareStoppedRequest(msg: DeskShareStoppedRequest) {
log.info("handleDeskShareStoppedRequest: dsStarted=" + meetingModel.getDeskShareStarted())
// Tell FreeSwitch to stop broadcasting to RTMP
outGW.send(new DeskShareStopRTMPBroadcast(msg.conferenceName, meetingModel.getRTMPBroadcastingUrl()))
meetingModel.setDeskShareStarted(false)
}
private def handleDeskShareRTMPBroadcastStartedRequest(msg: DeskShareRTMPBroadcastStartedRequest) {
log.info("handleDeskShareRTMPBroadcastStartedRequest: isBroadcastingRTMP=" + meetingModel.isBroadcastingRTMP())
// only valid if not broadcasting yet
if (!meetingModel.isBroadcastingRTMP()) {
meetingModel.setRTMPBroadcastingUrl(msg.streamname)
meetingModel.broadcastingRTMPStarted()
meetingModel.setDesktopShareVideoWidth(msg.videoWidth)
meetingModel.setDesktopShareVideoHeight(msg.videoHeight)
log.info("START broadcast ALLOWED when isBroadcastingRTMP=false")
// Notify viewers in the meeting that there's an rtmp stream to view
outGW.send(new DeskShareNotifyViewersRTMP(mProps.meetingID, msg.streamname, msg.videoWidth, msg.videoHeight, true))
} else {
log.info("START broadcast NOT ALLOWED when isBroadcastingRTMP=true")
}
}
private def handleDeskShareRTMPBroadcastStoppedRequest(msg: DeskShareRTMPBroadcastStoppedRequest) {
log.info("handleDeskShareRTMPBroadcastStoppedRequest: isBroadcastingRTMP=" + meetingModel.isBroadcastingRTMP())
// only valid if currently broadcasting
if (meetingModel.isBroadcastingRTMP()) {
log.info("STOP broadcast ALLOWED when isBroadcastingRTMP=true")
meetingModel.broadcastingRTMPStopped()
// notify viewers that RTMP broadcast stopped
outGW.send(new DeskShareNotifyViewersRTMP(mProps.meetingID, meetingModel.getRTMPBroadcastingUrl(),
msg.videoWidth, msg.videoHeight, false))
} else {
log.info("STOP broadcast NOT ALLOWED when isBroadcastingRTMP=false")
}
}
private def handleDeskShareGetDeskShareInfoRequest(msg: DeskShareGetDeskShareInfoRequest): Unit = {
log.info("handleDeskShareGetDeskShareInfoRequest: " + msg.conferenceName + "isBroadcasting=" + meetingModel.isBroadcastingRTMP())
if (meetingModel.isBroadcastingRTMP()) {
// if the meeting has an ongoing WebRTC Deskshare session, send a notification
outGW.send(new DeskShareNotifyASingleViewer(mProps.meetingID, msg.requesterID, meetingModel.getRTMPBroadcastingUrl(),
meetingModel.getDesktopShareVideoWidth(), meetingModel.getDesktopShareVideoHeight(), true))
}
}
}

View File

@ -6,13 +6,14 @@ import java.util.concurrent.TimeUnit
case object StopMeetingActor
case class MeetingProperties(meetingID: String, externalMeetingID: String, meetingName: String, recorded: Boolean,
voiceBridge: String, duration: Long, autoStartRecording: Boolean, allowStartStopRecording: Boolean,
moderatorPass: String, viewerPass: String, createTime: Long, createDate: String)
moderatorPass: String, viewerPass: String, createTime: Long, createDate: String, red5DeskShareIP: String, red5DeskShareApp: String)
class MeetingModel {
private var audioSettingsInited = false
private var permissionsInited = false
private var permissions = new Permissions()
private var recording = false;
private var broadcastingRTMP = false
private var muted = false;
private var meetingEnded = false
private var meetingMuted = false
@ -22,9 +23,46 @@ class MeetingModel {
private var lastWebUserLeftOnTimestamp: Long = 0
private var voiceRecordingFilename: String = ""
private var rtmpBroadcastingUrl: String = ""
private var deskShareStarted = false
private var desktopShareVideoWidth = 0
private var desktopShareVideoHeight = 0
val startedOn = timeNowInMinutes;
def resetDesktopSharingParams() = {
broadcastingRTMP = false
deskShareStarted = false
rtmpBroadcastingUrl = ""
desktopShareVideoWidth = 0
desktopShareVideoHeight = 0
}
def getDeskShareStarted(): Boolean = {
return deskShareStarted
}
def setDeskShareStarted(b: Boolean) {
deskShareStarted = b
println("---deskshare status changed to:" + b)
}
def setDesktopShareVideoWidth(videoWidth: Int) {
desktopShareVideoWidth = videoWidth
}
def setDesktopShareVideoHeight(videoHeight: Int) {
desktopShareVideoHeight = videoHeight
}
def getDesktopShareVideoWidth(): Int = {
desktopShareVideoWidth
}
def getDesktopShareVideoHeight(): Int = {
desktopShareVideoHeight
}
def muteMeeting() {
meetingMuted = true
}
@ -49,6 +87,18 @@ class MeetingModel {
recording
}
def broadcastingRTMPStarted() {
broadcastingRTMP = true
}
def isBroadcastingRTMP(): Boolean = {
broadcastingRTMP
}
def broadcastingRTMPStopped() {
broadcastingRTMP = false
}
def lastWebUserLeft() {
lastWebUserLeftOnTimestamp = timeNowInMinutes
}
@ -69,6 +119,15 @@ class MeetingModel {
voiceRecordingFilename
}
def setRTMPBroadcastingUrl(path: String) {
println("---RTMP broadcastUrl changed to:" + path)
rtmpBroadcastingUrl = path
}
def getRTMPBroadcastingUrl(): String = {
rtmpBroadcastingUrl
}
def permisionsInitialized(): Boolean = {
permissionsInited
}

View File

@ -11,6 +11,7 @@ import org.bigbluebutton.common.messages.StartRecordingVoiceConfRequestMessage
import org.bigbluebutton.common.messages.StopRecordingVoiceConfRequestMessage
import org.bigbluebutton.core.pubsub.senders.MeetingMessageToJsonConverter
import org.bigbluebutton.core.pubsub.senders.PesentationMessageToJsonConverter
import org.bigbluebutton.core.pubsub.senders.DeskShareMessageToJsonConverter
import org.bigbluebutton.common.messages.GetPresentationInfoReplyMessage
import org.bigbluebutton.common.messages.PresentationRemovedMessage
import org.bigbluebutton.core.apps.Page
@ -114,6 +115,11 @@ class MessageSenderActor(val meetingId: String, val service: MessageSender)
case msg: UndoWhiteboardEvent => handleUndoWhiteboardEvent(msg)
case msg: WhiteboardEnabledEvent => handleWhiteboardEnabledEvent(msg)
case msg: IsWhiteboardEnabledReply => handleIsWhiteboardEnabledReply(msg)
case msg: DeskShareStartRTMPBroadcast => handleDeskShareStartRTMPBroadcast(msg)
case msg: DeskShareStopRTMPBroadcast => handleDeskShareStopRTMPBroadcast(msg)
case msg: DeskShareNotifyViewersRTMP => handleDeskShareNotifyViewersRTMP(msg)
case msg: DeskShareNotifyASingleViewer => handleDeskShareNotifyASingleViewer(msg)
case msg: DeskShareHangUp => handleDeskShareHangUp(msg)
case _ => // do nothing
}
@ -122,6 +128,31 @@ class MessageSenderActor(val meetingId: String, val service: MessageSender)
service.send(MessagingConstants.FROM_USERS_CHANNEL, m.toJson)
}
private def handleDeskShareHangUp(msg: DeskShareHangUp) {
val json = DeskShareMessageToJsonConverter.getDeskShareHangUpToJson(msg)
service.send(MessagingConstants.TO_VOICE_CONF_SYSTEM_CHAN, json)
}
private def handleDeskShareStopRTMPBroadcast(msg: DeskShareStopRTMPBroadcast) {
val json = DeskShareMessageToJsonConverter.getDeskShareStopRTMPBroadcastToJson(msg)
service.send(MessagingConstants.TO_VOICE_CONF_SYSTEM_CHAN, json)
}
private def handleDeskShareNotifyViewersRTMP(msg: DeskShareNotifyViewersRTMP) {
val json = DeskShareMessageToJsonConverter.getDeskShareNotifyViewersRTMPToJson(msg)
service.send(MessagingConstants.FROM_DESK_SHARE_CHANNEL, json)
}
def handleDeskShareNotifyASingleViewer(msg: DeskShareNotifyASingleViewer) {
val json = DeskShareMessageToJsonConverter.getDeskShareNotifyASingleViewerToJson(msg)
service.send(MessagingConstants.FROM_DESK_SHARE_CHANNEL, json)
}
private def handleDeskShareStartRTMPBroadcast(msg: DeskShareStartRTMPBroadcast) {
val json = DeskShareMessageToJsonConverter.getDeskShareStartRTMPBroadcastToJson(msg)
service.send(MessagingConstants.TO_VOICE_CONF_SYSTEM_CHAN, json)
}
private def handleGetChatHistoryReply(msg: GetChatHistoryReply) {
val json = ChatMessageToJsonConverter.getChatHistoryReplyToJson(msg)
service.send(MessagingConstants.FROM_CHAT_CHANNEL, json)

View File

@ -29,7 +29,11 @@ import org.bigbluebutton.core.recorders.events.ParticipantEndAndKickAllRecordEve
import org.bigbluebutton.core.recorders.events.UndoShapeWhiteboardRecordEvent
import org.bigbluebutton.core.recorders.events.ClearPageWhiteboardRecordEvent
import org.bigbluebutton.core.recorders.events.AddShapeWhiteboardRecordEvent
import org.bigbluebutton.core.service.whiteboard.WhiteboardKeyUtil
import org.bigbluebutton.core.recorders.events.DeskShareStartRTMPRecordEvent
import org.bigbluebutton.core.recorders.events.DeskShareStopRTMPRecordEvent
import org.bigbluebutton.core.recorders.events.DeskShareNotifyViewersRTMPRecordEvent
// import org.bigbluebutton.core.service.whiteboard.WhiteboardKeyUtil
import org.bigbluebutton.common.messages.WhiteboardKeyUtil
import org.bigbluebutton.core.recorders.events.ModifyTextWhiteboardRecordEvent
import scala.collection.immutable.StringOps
@ -67,6 +71,9 @@ class RecorderActor(val meetingId: String, val recorder: RecorderApplication)
case msg: SendWhiteboardAnnotationEvent => handleSendWhiteboardAnnotationEvent(msg)
case msg: ClearWhiteboardEvent => handleClearWhiteboardEvent(msg)
case msg: UndoWhiteboardEvent => handleUndoWhiteboardEvent(msg)
case msg: DeskShareStartRTMPBroadcast => handleDeskShareStartRTMPBroadcast(msg)
case msg: DeskShareStopRTMPBroadcast => handleDeskShareStopRTMPBroadcast(msg)
case msg: DeskShareNotifyViewersRTMP => handleDeskShareNotifyViewersRTMP(msg)
case _ => // do nothing
}
@ -434,4 +441,34 @@ class RecorderActor(val meetingId: String, val recorder: RecorderApplication)
}
}
private def handleDeskShareStartRTMPBroadcast(msg: DeskShareStartRTMPBroadcast) {
val event = new DeskShareStartRTMPRecordEvent()
event.setMeetingId(msg.conferenceName)
event.setStreamPath(msg.streamPath)
event.setTimestamp(TimestampGenerator.generateTimestamp)
log.info("handleDeskShareStartRTMPBroadcast " + msg.conferenceName)
recorder.record(msg.conferenceName, event)
}
private def handleDeskShareStopRTMPBroadcast(msg: DeskShareStopRTMPBroadcast) {
val event = new DeskShareStopRTMPRecordEvent()
event.setMeetingId(msg.conferenceName)
event.setStreamPath(msg.streamPath)
event.setTimestamp(TimestampGenerator.generateTimestamp)
log.info("handleDeskShareStopRTMPBroadcast " + msg.conferenceName)
recorder.record(msg.conferenceName, event)
}
private def handleDeskShareNotifyViewersRTMP(msg: DeskShareNotifyViewersRTMP) {
val event = new DeskShareNotifyViewersRTMPRecordEvent()
event.setMeetingId(msg.meetingID)
event.setStreamPath(msg.streamPath)
event.setBroadcasting(msg.broadcasting)
event.setTimestamp(TimestampGenerator.generateTimestamp)
log.info("handleDeskShareNotifyViewersRTMP " + msg.meetingID)
recorder.record(msg.meetingID, event)
}
}

View File

@ -115,3 +115,11 @@ case class UndoWhiteboardRequest(meetingID: String, requesterID: String, whitebo
case class EnableWhiteboardRequest(meetingID: String, requesterID: String, enable: Boolean) extends InMessage
case class IsWhiteboardEnabledRequest(meetingID: String, requesterID: String, replyTo: String) extends InMessage
case class GetAllMeetingsRequest(meetingID: String /** Not used. Just to satisfy trait **/ ) extends InMessage
// DeskShare
case class DeskShareStartedRequest(conferenceName: String, callerId: String, callerIdName: String)
case class DeskShareStoppedRequest(conferenceName: String, callerId: String, callerIdName: String)
case class DeskShareRTMPBroadcastStartedRequest(conferenceName: String, streamname: String, videoWidth: Int, videoHeight: Int, timestamp: String)
case class DeskShareRTMPBroadcastStoppedRequest(conferenceName: String, streamname: String, videoWidth: Int, videoHeight: Int, timestamp: String)
case class DeskShareGetDeskShareInfoRequest(conferenceName: String, requesterID: String, replyTo: String)

View File

@ -130,6 +130,13 @@ case class WhiteboardEnabledEvent(meetingID: String, recorded: Boolean, requeste
case class IsWhiteboardEnabledReply(meetingID: String, recorded: Boolean, requesterID: String, enabled: Boolean, replyTo: String) extends IOutMessage
case class GetAllMeetingsReply(meetings: Array[MeetingInfo]) extends IOutMessage
// DeskShare
case class DeskShareStartRTMPBroadcast(conferenceName: String, streamPath: String) extends IOutMessage
case class DeskShareStopRTMPBroadcast(conferenceName: String, streamPath: String) extends IOutMessage
case class DeskShareNotifyViewersRTMP(meetingID: String, streamPath: String, videoWidth: Int, videoHeight: Int, broadcasting: Boolean) extends IOutMessage
case class DeskShareNotifyASingleViewer(meetingID: String, userID: String, streamPath: String, videoWidth: Int, videoHeight: Int, broadcasting: Boolean) extends IOutMessage
case class DeskShareHangUp(meetingID: String, fsConferenceName: String) extends IOutMessage
// Value Objects
case class MeetingVO(id: String, recorded: Boolean)

View File

@ -132,4 +132,9 @@ case class MeetingPasswords(moderatorPass: String, viewerPass: String)
case class MeetingDuration(duration: Int = 0, createdTime: Long = 0,
startTime: Long = 0, endTime: Long = 0)
case class MeetingInfo(meetingID: String, meetingName: String, recorded: Boolean, voiceBridge: String, duration: Long)
case class MeetingInfo(
meetingID: String,
meetingName: String,
recorded: Boolean,
voiceBridge: String,
duration: Long)

View File

@ -4,7 +4,8 @@ import org.bigbluebutton.core.api._
import org.bigbluebutton.core.MeetingActor
import scala.collection.mutable.HashMap
import scala.collection.mutable.ArrayBuffer
import org.bigbluebutton.core.service.whiteboard.WhiteboardKeyUtil
import org.bigbluebutton.common.messages.WhiteboardKeyUtil
// import org.bigbluebutton.core.service.whiteboard.WhiteboardKeyUtil
import com.google.gson.Gson
import java.util.ArrayList
import org.bigbluebutton.core.OutMessageGateway

View File

@ -246,6 +246,18 @@ trait UsersApp {
+ ". Making user=[" + mod.userID + "] presenter.")
assignNewPresenter(mod.userID, mod.name, mod.userID)
}
if (meetingModel.isBroadcastingRTMP()) {
// The presenter left during desktop sharing. Stop desktop sharing on FreeSWITCH
outGW.send(new DeskShareHangUp(mProps.meetingID, mProps.voiceBridge))
// notify other clients to close their deskshare view
outGW.send(new DeskShareNotifyViewersRTMP(mProps.meetingID, meetingModel.getRTMPBroadcastingUrl(),
meetingModel.getDesktopShareVideoWidth(), meetingModel.getDesktopShareVideoHeight(), false))
// reset meeting info
meetingModel.resetDesktopSharingParams()
}
}
}
@ -399,9 +411,9 @@ trait UsersApp {
* and is reconnecting. Make the user as joined only in the voice conference. If we get a
* user left voice conference message, then we will remove the user from the users list.
*/
switchUserToPhoneUser((new UserJoinedVoiceConfMessage(mProps.voiceBridge,
switchUserToPhoneUser(new UserJoinedVoiceConfMessage(mProps.voiceBridge,
vu.userId, u.userID, u.externUserID, vu.callerName,
vu.callerNum, vu.muted, vu.talking, vu.avatarURL, u.listenOnly)));
vu.callerNum, vu.muted, vu.talking, vu.avatarURL, u.listenOnly));
}
}

View File

@ -2,7 +2,7 @@ package org.bigbluebutton.core.apps
import org.bigbluebutton.core.api._
import org.bigbluebutton.core.MeetingActor
import org.bigbluebutton.core.service.whiteboard.WhiteboardKeyUtil
import org.bigbluebutton.common.messages.WhiteboardKeyUtil
import org.bigbluebutton.core.OutMessageGateway
case class Whiteboard(id: String, shapes: Seq[AnnotationVO])
@ -90,4 +90,4 @@ trait WhiteboardApp {
val enabled = wbModel.isWhiteboardEnabled()
outGW.send(new IsWhiteboardEnabledReply(mProps.meetingID, mProps.recorded, msg.requesterID, enabled, msg.replyTo))
}
}
}

View File

@ -9,7 +9,7 @@ import scala.collection.JavaConversions._
import java.util.ArrayList
import org.bigbluebutton.common.messages.MessagingConstants
import org.bigbluebutton.core.messaging.Util
import org.bigbluebutton.core.service.chat.ChatKeyUtil
import org.bigbluebutton.common.messages.ChatKeyUtil
object ChatMessageToJsonConverter {

View File

@ -0,0 +1,39 @@
package org.bigbluebutton.core.pubsub.senders
import org.bigbluebutton.core.api._
import org.bigbluebutton.common.messages.DeskShareStartRTMPBroadcastEventMessage
import org.bigbluebutton.common.messages.DeskShareStopRTMPBroadcastEventMessage
import org.bigbluebutton.common.messages.DeskShareNotifyViewersRTMPEventMessage
import org.bigbluebutton.common.messages.DeskShareNotifyASingleViewerEventMessage
import org.bigbluebutton.common.messages.DeskShareHangUpEventMessage
object DeskShareMessageToJsonConverter {
def getDeskShareHangUpToJson(msg: DeskShareHangUp): String = {
val newMsg = new DeskShareHangUpEventMessage(msg.meetingID, msg.fsConferenceName, TimestampGenerator.getCurrentTime.toString())
newMsg.toJson()
}
def getDeskShareNotifyASingleViewerToJson(msg: DeskShareNotifyASingleViewer): String = {
val newMsg = new DeskShareNotifyASingleViewerEventMessage(msg.meetingID, msg.userID,
msg.streamPath, msg.broadcasting, msg.videoWidth, msg.videoHeight, TimestampGenerator.getCurrentTime.toString())
newMsg.toJson()
}
def getDeskShareStartRTMPBroadcastToJson(msg: DeskShareStartRTMPBroadcast): String = {
val newMsg = new DeskShareStartRTMPBroadcastEventMessage(msg.conferenceName, msg.streamPath,
TimestampGenerator.getCurrentTime.toString())
newMsg.toJson()
}
def getDeskShareStopRTMPBroadcastToJson(msg: DeskShareStopRTMPBroadcast): String = {
val newMsg = new DeskShareStopRTMPBroadcastEventMessage(msg.conferenceName, msg.streamPath,
TimestampGenerator.getCurrentTime.toString())
newMsg.toJson()
}
def getDeskShareNotifyViewersRTMPToJson(msg: DeskShareNotifyViewersRTMP): String = {
val newMsg = new DeskShareNotifyViewersRTMPEventMessage(msg.meetingID, msg.streamPath,
msg.broadcasting, msg.videoWidth, msg.videoHeight, TimestampGenerator.getCurrentTime.toString())
newMsg.toJson()
}
}

View File

@ -24,6 +24,8 @@ resolvers ++= Seq(
"blindside-repos" at "http://blindside.googlecode.com/svn/repository/"
)
resolvers += Resolver.sonatypeRepo("releases")
publishTo := Some(Resolver.file("file", new File(Path.userHome.absolutePath+"/dev/repo/maven-repo/releases" )) )
// We want to have our jar files in lib_managed dir.
@ -38,21 +40,21 @@ testOptions in Test += Tests.Argument(TestFrameworks.ScalaTest, "-h", "target/sc
libraryDependencies ++= {
val akkaVersion = "2.3.11"
Seq(
"com.typesafe.akka" %% "akka-actor" % akkaVersion,
"com.typesafe.akka" %% "akka-testkit" % akkaVersion % "test",
"com.typesafe.akka" %% "akka-slf4j" % akkaVersion,
"ch.qos.logback" % "logback-classic" % "1.0.3",
"org.pegdown" % "pegdown" % "1.4.0",
"junit" % "junit" % "4.11",
"com.etaty.rediscala" %% "rediscala" % "1.4.0",
"commons-codec" % "commons-codec" % "1.8",
"joda-time" % "joda-time" % "2.3",
"com.google.code.gson" % "gson" % "1.7.1",
"redis.clients" % "jedis" % "2.1.0",
"org.apache.commons" % "commons-lang3" % "3.2",
"org.bigbluebutton" % "bbb-common-message" % "0.0.17-SNAPSHOT",
"org.bigbluebutton" % "bbb-fsesl-client" % "0.0.3"
)}
"com.typesafe.akka" %% "akka-actor" % akkaVersion,
"com.typesafe.akka" %% "akka-testkit" % akkaVersion % "test",
"com.typesafe.akka" %% "akka-slf4j" % akkaVersion,
"ch.qos.logback" % "logback-classic" % "1.0.3",
"org.pegdown" % "pegdown" % "1.4.0",
"junit" % "junit" % "4.11",
"com.etaty.rediscala" %% "rediscala" % "1.4.0",
"commons-codec" % "commons-codec" % "1.8",
"joda-time" % "joda-time" % "2.3",
"com.google.code.gson" % "gson" % "1.7.1",
"redis.clients" % "jedis" % "2.1.0",
"org.apache.commons" % "commons-lang3" % "3.2",
"org.bigbluebutton" % "bbb-common-message" % "0.0.17",
"org.bigbluebutton" % "bbb-fsesl-client" % "0.0.4"
)}
seq(Revolver.settings: _*)

View File

@ -0,0 +1,73 @@
<!-- http://wiki.freeswitch.org/wiki/Mod_conference -->
<!-- None of these paths are real if you want any of these options you need to really set them up -->
<configuration name="conference.conf" description="Audio Conference">
<!-- Advertise certain presence on startup . -->
<advertise>
<room name="3001@$${domain}" status="FreeSWITCH"/>
</advertise>
<!-- These are the default keys that map when you do not specify a caller control group -->
<!-- Note: none and default are reserved names for group names. Disabled if dist-dtmf member flag is set. -->
<caller-controls>
<group name="default">
<control action="mute" digits="0"/>
<control action="deaf mute" digits="*"/>
<control action="energy up" digits="9"/>
<control action="energy equ" digits="8"/>
<control action="energy dn" digits="7"/>
<control action="vol talk up" digits="3"/>
<control action="vol talk zero" digits="2"/>
<control action="vol talk dn" digits="1"/>
<control action="vol listen up" digits="6"/>
<control action="vol listen zero" digits="5"/>
<control action="vol listen dn" digits="4"/>
<control action="hangup" digits="#"/>
</group>
</caller-controls>
<!-- Profiles are collections of settings you can reference by name. -->
<profiles>
<!-- profile used for WebRTC Desktop Sharing -->
<profile name="video-mcu-stereo">
<param name="domain" value="$${domain}"/>
<param name="rate" value="48000"/>
<param name="channels" value="2"/>
<param name="interval" value="20"/>
<param name="energy-level" value="200"/>
<!-- <param name="tts-engine" value="flite"/> -->
<!-- <param name="tts-voice" value="kal16"/> -->
<!--remove audio for when user is alone since we hit this case every time-->
<!--with -DESKSHARE conference. It has a single user only by default-->
<!--<param name="muted-sound" value="conference/conf-muted.wav"/>-->
<!--<param name="unmuted-sound" value="conference/conf-unmuted.wav"/>-->
<!-- <param name="alone-sound" value="conference/conf-alone.wav"/> -->
<!-- <param name="moh-sound" value="local_stream://stereo"/> -->
<!--<param name="enter-sound" value="tone_stream://%(200,0,500,600,700)"/>-->
<!--<param name="exit-sound" value="tone_stream://%(500,0,300,200,100,50,25)"/>-->
<!--<param name="kicked-sound" value="conference/conf-kicked.wav"/>-->
<!--<param name="locked-sound" value="conference/conf-locked.wav"/>-->
<!--<param name="is-locked-sound" value="conference/conf-is-locked.wav"/>-->
<!--<param name="is-unlocked-sound" value="conference/conf-is-unlocked.wav"/>-->
<!--<param name="pin-sound" value="conference/conf-pin.wav"/>-->
<!--<param name="bad-pin-sound" value="conference/conf-bad-pin.wav"/>-->
<param name="caller-id-name" value="$${outbound_caller_name}"/>
<param name="caller-id-number" value="$${outbound_caller_id}"/>
<param name="comfort-noise" value="false"/>
<param name="conference-flags" value="video-floor-only|video-required-for-canvas|rfc-4579|livearray-sync|minimize-video-encoding"/>
<param name="video-mode" value="mux"/> <!-- other values for video-mode are transcode or passthrough -->
<param name="video-layout-name" value="1x1"/> <!-- 1x1 since we only have 1 video stream -->
<param name="video-layout-name" value="group:grid"/>
<param name="video-canvas-size" value="1920x1080"/>
<param name="video-canvas-bgcolor" value="#333333"/>
<param name="video-layout-bgcolor" value="#000000"/>
<param name="video-codec-bandwidth" value="1mb"/>
<param name="video-fps" value="15"/>
</profile>
</profiles>
</configuration>

View File

@ -0,0 +1,30 @@
<configuration name="verto.conf" description="HTML5 Verto Endpoint">
<settings>
<param name="debug" value="10"/>
</settings>
<profiles>
<profile name="mine">
<param name="bind-local" value="0.0.0.0:8081"/>
<param name="bind-local" value="0.0.0.0:8082" secure="true"/>
<param name="force-register-domain" value="$${domain}"/>
<param name="secure-combined" value="$${certs_dir}/wss.pem"/>
<param name="secure-chain" value="$${certs_dir}/wss.pem"/>
<param name="userauth" value="true"/>
<!-- setting this to true will allow anyone to register even with no account so use with care -->
<param name="blind-reg" value="false"/>
<param name="mcast-ip" value="224.1.1.1"/>
<param name="mcast-port" value="1337"/>
<param name="rtp-ip" value="$${local_ip_v4}"/>
<!-- <param name="ext-rtp-ip" value=""/> -->
<param name="local-network" value="localnet.auto"/>
<param name="outbound-codec-string" value="opus,vp8"/>
<param name="inbound-codec-string" value="opus,vp8"/>
<param name="apply-candidate-acl" value="wan.auto"/>
<param name="timer-name" value="soft"/>
</profile>
</profiles>
</configuration>

View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
NOTICE:
This context is usually accessed via authenticated callers on the sip profile on port 5060
or transfered callers from the public context which arrived via the sip profile on port 5080.
Authenticated users will use the user_context variable on the user to determine what context
they can access. You can also add a user in the directory with the cidr= attribute acl.conf.xml
will build the domains ACL using this value.
-->
<!-- http://wiki.freeswitch.org/wiki/Dialplan_XML -->
<include>
<context name="default">
<extension name="public_extensions">
<condition field="destination_number" expression="^\d{5}$">
<action application="log" data="INFO AAAAAA transferring to $1 XML public!!"/>
<action application="transfer" data="${destination_number} XML public"/>
</condition>
</extension>
<extension name="public_extensions">
<condition field="destination_number" expression="^(\d{5})(-screen)$">
<action application="log" data="INFO BB $1 $2 BBBB transferring to $1 XML public!!"/>
<action application="transfer" data="$1 XML public"/>
</condition>
</extension>
<!-- other extensions -->
</context>
</include>

View File

@ -0,0 +1,32 @@
<!--
NOTICE:
This context is usually accessed via the external sip profile listening on port 5080.
It is recommended to have separate inbound and outbound contexts. Not only for security
but clearing up why you would need to do such a thing. You don't want outside un-authenticated
callers hitting your default context which allows dialing calls thru your providers and results
in Toll Fraud.
-->
<!-- http://wiki.freeswitch.org/wiki/Dialplan_XML -->
<include>
<context name="public">
<!-- other extensions -->
<extension name="public_extensions">
<condition field="destination_number" expression="^\d{5}$">
<action application="log" data="INFO ************ redirecting ${destination_number} to ${destination_number}-DESKSHARE@video-mcu-stereo ***********" />
<action application="answer"/>
<action application="conference" data="${destination_number}-DESKSHARE@video-mcu-stereo"/>
</condition>
</extension>
<!--
You can place files in the public directory to get included.
-->
<X-PRE-PROCESS cmd="include" data="public/*.xml"/>
</context>
</include>

View File

@ -7,6 +7,9 @@ import org.bigbluebutton.common.messages.GetUsersFromVoiceConfRequestMessage;
import org.bigbluebutton.common.messages.MuteUserInVoiceConfRequestMessage;
import org.bigbluebutton.common.messages.StartRecordingVoiceConfRequestMessage;
import org.bigbluebutton.common.messages.StopRecordingVoiceConfRequestMessage;
import org.bigbluebutton.common.messages.DeskShareStartRTMPBroadcastEventMessage;
import org.bigbluebutton.common.messages.DeskShareStopRTMPBroadcastEventMessage;
import org.bigbluebutton.common.messages.DeskShareHangUpEventMessage;
import org.bigbluebutton.freeswitch.voice.freeswitch.FreeswitchApplication;
import com.google.gson.JsonObject;
@ -53,12 +56,39 @@ public class RedisMessageReceiver {
case StopRecordingVoiceConfRequestMessage.STOP_RECORD_VOICE_CONF_REQUEST:
processStopRecordingVoiceConfRequestMessage(message);
break;
case DeskShareStartRTMPBroadcastEventMessage.DESKSHARE_START_RTMP_BROADCAST_MESSAGE:
System.out.println("RedisMessageReceiver got DESKSHARE_START_RTMP_BROADCAST_MESSAGE");
processDeskShareStartRTMPBroadcastEventMessage(message);
break;
case DeskShareStopRTMPBroadcastEventMessage.DESKSHARE_STOP_RTMP_BROADCAST_MESSAGE:
System.out.println("RedisMessageReceiver got DESKSHARE_STOP_RTMP_BROADCAST_MESSAGE");
processDeskShareStopRTMPBroadcastEventMessage(message);
break;
case DeskShareHangUpEventMessage.DESKSHARE_HANG_UP_MESSAGE:
System.out.println("RedisMessageReceiver got DESKSHARE_HANG_UP_MESSAGE");
processDeskShareHangUpEventMessage(message);
break;
}
}
}
}
}
private void processDeskShareStartRTMPBroadcastEventMessage(String json) {
DeskShareStartRTMPBroadcastEventMessage msg = DeskShareStartRTMPBroadcastEventMessage.fromJson(json);
fsApp.deskShareBroadcastRTMP(msg.conferenceName, msg.streamUrl, msg.timestamp, true);
}
private void processDeskShareStopRTMPBroadcastEventMessage(String json) {
DeskShareStopRTMPBroadcastEventMessage msg = DeskShareStopRTMPBroadcastEventMessage.fromJson(json);
fsApp.deskShareBroadcastRTMP(msg.conferenceName, msg.streamUrl, msg.timestamp, false);
}
private void processDeskShareHangUpEventMessage(String json) {
DeskShareHangUpEventMessage msg = DeskShareHangUpEventMessage.fromJson(json);
fsApp.deskShareHangUp(msg.conferenceName, msg.fsConferenceName, msg.timestamp);
}
private void processEjectAllVoiceUsersRequestMessage(String json) {
EjectAllUsersFromVoiceConfRequestMessage msg = EjectAllUsersFromVoiceConfRequestMessage.fromJson(json);
fsApp.ejectAll(msg.voiceConfId);

View File

@ -24,7 +24,10 @@ import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import org.bigbluebutton.freeswitch.voice.events.DeskShareStartedEvent;
import org.bigbluebutton.freeswitch.voice.events.DeskShareEndedEvent;
import org.bigbluebutton.freeswitch.voice.events.ConferenceEventListener;
import org.bigbluebutton.freeswitch.voice.events.DeskShareRTMPBroadcastEvent;
import org.bigbluebutton.freeswitch.voice.events.VoiceConferenceEvent;
import org.bigbluebutton.freeswitch.voice.events.VoiceStartRecordingEvent;
import org.bigbluebutton.freeswitch.voice.events.VoiceUserJoinedEvent;
@ -78,13 +81,33 @@ public class FreeswitchConferenceEventListener implements ConferenceEventListene
VoiceStartRecordingEvent evt = (VoiceStartRecordingEvent) event;
System.out.println("************** FreeswitchConferenceEventListener VoiceStartRecordingEvent recording=[" + evt.startRecord() + "]");
vcs.voiceConfRecordingStarted(evt.getRoom(), evt.getRecordingFilename(), evt.startRecord(), evt.getTimestamp());
}
} else if (event instanceof DeskShareStartedEvent) {
DeskShareStartedEvent evt = (DeskShareStartedEvent) event;
System.out.println("************** FreeswitchConferenceEventListener DeskShareStartedEvent");
vcs.deskShareStarted(evt.getRoom(), evt.getCallerIdNum(), evt.getCallerIdName());
} else if (event instanceof DeskShareEndedEvent) {
DeskShareEndedEvent evt = (DeskShareEndedEvent) event;
System.out.println("************** FreeswitchConferenceEventListener DeskShareEndedEvent");
vcs.deskShareEnded(evt.getRoom(), evt.getCallerIdNum(), evt.getCallerIdName());
} else if (event instanceof DeskShareRTMPBroadcastEvent) {
if (((DeskShareRTMPBroadcastEvent) event).getBroadcast()) {
DeskShareRTMPBroadcastEvent evt = (DeskShareRTMPBroadcastEvent) event;
System.out.println("************** FreeswitchConferenceEventListener DeskShareRTMPBroadcastStartedEvent");
vcs.deskShareRTMPBroadcastStarted(evt.getRoom(), evt.getBroadcastingStreamUrl(),
evt.getVideoWidth(), evt.getVideoHeight(), evt.getTimestamp());
} else {
DeskShareRTMPBroadcastEvent evt = (DeskShareRTMPBroadcastEvent) event;
System.out.println("************** FreeswitchConferenceEventListener DeskShareRTMPBroadcastStoppedEvent");
vcs.deskShareRTMPBroadcastStopped(evt.getRoom(), evt.getBroadcastingStreamUrl(),
evt.getVideoWidth(), evt.getVideoHeight(), evt.getTimestamp());
}
}
}
};
runExec.execute(task);
}
public void start() {
sendMessages = true;
Runnable sender = new Runnable() {
@ -97,7 +120,7 @@ public class FreeswitchConferenceEventListener implements ConferenceEventListene
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
};
@ -111,5 +134,5 @@ public class FreeswitchConferenceEventListener implements ConferenceEventListene
public void handleConferenceEvent(VoiceConferenceEvent event) {
queueMessage(event);
}
}

View File

@ -8,5 +8,9 @@ public interface IVoiceConferenceService {
void userLockedInVoiceConf(String voiceConfId, String voiceUserId, Boolean locked);
void userMutedInVoiceConf(String voiceConfId, String voiceUserId, Boolean muted);
void userTalkingInVoiceConf(String voiceConfId, String voiceUserId, Boolean talking);
void deskShareStarted(String voiceConfId, String callerIdNum, String callerIdName);
void deskShareEnded(String voiceConfId, String callerIdNum, String callerIdName);
void deskShareRTMPBroadcastStarted(String room, String streamname, Integer videoWidth, Integer videoHeight, String timestamp);
void deskShareRTMPBroadcastStopped(String room, String streamname, Integer videoWidth, Integer videoHeight, String timestamp);
}

View File

@ -0,0 +1,39 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2015 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.freeswitch.voice.events;
public class DeskShareEndedEvent extends VoiceConferenceEvent {
private final String callerIdNum;
private final String callerIdName;
public DeskShareEndedEvent(String room, String callerIdNum, String callerIdName) {
super(room);
this.callerIdName = callerIdName;
this.callerIdNum = callerIdNum;
}
public String getCallerIdNum() {
return callerIdNum;
}
public String getCallerIdName() {
return callerIdName;
}
}

View File

@ -0,0 +1,68 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2015 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.freeswitch.voice.events;
public class DeskShareRTMPBroadcastEvent extends VoiceConferenceEvent {
private String timestamp;
private boolean broadcast;
private String streamUrl;
private Integer vw;
private Integer vh;
private final String DESKSHARE_SUFFIX = "-DESKSHARE";
public DeskShareRTMPBroadcastEvent(String room, boolean broadcast) {
super(room);
this.broadcast = broadcast;
}
public void setTimestamp(String timestamp) {
this.timestamp = timestamp;
}
public void setBroadcastingStreamUrl(String streamUrl) {
this.streamUrl = streamUrl;
}
public void setVideoWidth(Integer vw) {this.vw = vw;}
public void setVideoHeight(Integer vh) {this.vh = vh;}
public Integer getVideoHeight() {return vh;}
public Integer getVideoWidth() {return vw;}
public String getTimestamp() {
return timestamp;
}
public String getBroadcastingStreamUrl()
{
if (streamUrl.endsWith(DESKSHARE_SUFFIX)) {
streamUrl = streamUrl.replace(DESKSHARE_SUFFIX, "");
}
return streamUrl;
}
public boolean getBroadcast() {
return broadcast;
}
}

View File

@ -0,0 +1,39 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2015 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.freeswitch.voice.events;
public class DeskShareStartedEvent extends VoiceConferenceEvent {
private final String callerIdNum;
private final String callerIdName;
public DeskShareStartedEvent(String room, String callerIdNum, String callerIdName) {
super(room);
this.callerIdName = callerIdName;
this.callerIdNum = callerIdNum;
}
public String getCallerIdNum() {
return callerIdNum;
}
public String getCallerIdName() {
return callerIdName;
}
}

View File

@ -24,12 +24,7 @@ import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.bigbluebutton.freeswitch.voice.events.ConferenceEventListener;
import org.bigbluebutton.freeswitch.voice.freeswitch.actions.BroadcastConferenceCommand;
import org.bigbluebutton.freeswitch.voice.freeswitch.actions.EjectAllUsersCommand;
import org.bigbluebutton.freeswitch.voice.freeswitch.actions.EjectUserCommand;
import org.bigbluebutton.freeswitch.voice.freeswitch.actions.MuteUserCommand;
import org.bigbluebutton.freeswitch.voice.freeswitch.actions.GetAllUsersCommand;
import org.bigbluebutton.freeswitch.voice.freeswitch.actions.RecordConferenceCommand;
import org.bigbluebutton.freeswitch.voice.freeswitch.actions.*;
import org.freeswitch.esl.client.inbound.Client;
import org.freeswitch.esl.client.inbound.InboundConnectionFailure;
import org.freeswitch.esl.client.manager.ManagerConnection;
@ -143,5 +138,23 @@ public class ConnectionManager {
EslMessage response = c.sendSyncApiCommand(rcc.getCommand(), rcc.getCommandArgs());
rcc.handleResponse(response, conferenceEventListener);
}
}
}
public void broadcastRTMP(DeskShareBroadcastRTMPCommand rtmp) {
Client c = manager.getESLClient();
if (c.canSend()) {
System.out.println("ConnectionManager: send to FS: broadcastRTMP " + rtmp.getCommandArgs());
EslMessage response = c.sendSyncApiCommand(rtmp.getCommand(), rtmp.getCommandArgs());
rtmp.handleResponse(response, conferenceEventListener);
}
}
public void hangUp(DeskShareHangUpCommand huCmd) {
Client c = manager.getESLClient();
if (c.canSend()) {
System.out.println("ConnectionManager: send to FS: hangUp " + huCmd.getCommandArgs());
EslMessage response = c.sendSyncApiCommand(huCmd.getCommand(), huCmd.getCommandArgs());
huCmd.handleResponse(response, conferenceEventListener);
}
}
}

View File

@ -7,6 +7,10 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.bigbluebutton.freeswitch.voice.events.ConferenceEventListener;
import org.bigbluebutton.freeswitch.voice.events.DeskShareEndedEvent;
import org.bigbluebutton.freeswitch.voice.events.DeskShareStartedEvent;
import org.bigbluebutton.freeswitch.voice.events.DeskShareRTMPBroadcastEvent;
import org.bigbluebutton.freeswitch.voice.events.VoiceConferenceEvent;
import org.bigbluebutton.freeswitch.voice.events.VoiceStartRecordingEvent;
import org.bigbluebutton.freeswitch.voice.events.VoiceUserJoinedEvent;
import org.bigbluebutton.freeswitch.voice.events.VoiceUserLeftEvent;
@ -22,11 +26,15 @@ public class ESLEventListener implements IEslEventListener {
private static final String STOP_TALKING_EVENT = "stop-talking";
private static final String START_RECORDING_EVENT = "start-recording";
private static final String STOP_RECORDING_EVENT = "stop-recording";
private static final String DESKSHARE_CONFERENCE_NAME_SUFFIX = "-DESKSHARE";
private static final String DESKSHARE_CALLER_NAME_SUFFIX = " (Screen)";
private static final String DESKSHARE_CALLER_ID_SUFFIX = " (screen)";
private final ConferenceEventListener conferenceEventListener;
public ESLEventListener(ConferenceEventListener conferenceEventListener) {
this.conferenceEventListener = conferenceEventListener;
this.conferenceEventListener = conferenceEventListener;
}
@Override
@ -50,7 +58,7 @@ public class ESLEventListener implements IEslEventListener {
@Override
public void conferenceEventJoin(String uniqueId, String confName, int confSize, EslEvent event) {
Integer memberId = this.getMemberIdFromEvent(event);
Map<String, String> headers = event.getEventHeaders();
String callerId = this.getCallerIdFromEvent(event);
@ -59,29 +67,52 @@ public class ESLEventListener implements IEslEventListener {
boolean speaking = headers.get("Talking").equals("true") ? true : false;
String voiceUserId = callerIdName;
System.out.println("User joined voice conference, user=[" + callerIdName + "], conf=[" + confName + "]");
Matcher gapMatcher = GLOBAL_AUDION_PATTERN.matcher(callerIdName);
if (gapMatcher.matches()) {
System.out.println("Ignoring GLOBAL AUDIO USER [" + callerIdName + "]");
return;
System.out.println("Ignoring GLOBAL AUDIO USER [" + callerIdName + "]");
return;
}
Matcher matcher = CALLERNAME_PATTERN.matcher(callerIdName);
if (matcher.matches()) {
voiceUserId = matcher.group(1).trim();
callerIdName = matcher.group(2).trim();
}
VoiceUserJoinedEvent pj = new VoiceUserJoinedEvent(voiceUserId, memberId.toString(), confName, callerId, callerIdName, muted, speaking, null);
// Deskstop sharing conferences have their name in the form ddddd-DESKSHARE
// Deskstop sharing conferences have the user with the desktop video displayed in this way:
// username (Screen) and usernum (screen)
if (confName.endsWith(DESKSHARE_CONFERENCE_NAME_SUFFIX) &&
callerId.endsWith(DESKSHARE_CALLER_ID_SUFFIX) &&
callerIdName.endsWith(DESKSHARE_CALLER_NAME_SUFFIX)) {
DeskShareStartedEvent dsStart = new DeskShareStartedEvent(confName, callerId, callerIdName);
conferenceEventListener.handleConferenceEvent(dsStart);
}
Matcher matcher = CALLERNAME_PATTERN.matcher(callerIdName);
if (matcher.matches()) {
voiceUserId = matcher.group(1).trim();
callerIdName = matcher.group(2).trim();
}
VoiceUserJoinedEvent pj = new VoiceUserJoinedEvent(voiceUserId, memberId.toString(), confName, callerId, callerIdName, muted, speaking, "");
conferenceEventListener.handleConferenceEvent(pj);
}
@Override
public void conferenceEventLeave(String uniqueId, String confName, int confSize, EslEvent event) {
public void conferenceEventLeave(String uniqueId, String confName, int confSize, EslEvent event) {
Integer memberId = this.getMemberIdFromEvent(event);
System.out.println("User left voice conference, user=[" + memberId.toString() + "], conf=[" + confName + "]");
String callerId = this.getCallerIdFromEvent(event);
String callerIdName = this.getCallerIdNameFromEvent(event);
// Deskstop sharing conferences have their name in the form ddddd-DESKSHARE
// Deskstop sharing conferences have the user with the desktop video displayed in this way:
// username (Screen) and usernum (screen)
if (confName.endsWith(DESKSHARE_CONFERENCE_NAME_SUFFIX) &&
callerId.endsWith(DESKSHARE_CALLER_ID_SUFFIX) &&
callerIdName.endsWith(DESKSHARE_CALLER_NAME_SUFFIX)) {
DeskShareEndedEvent dsEnd = new DeskShareEndedEvent(confName, callerId, callerIdName);
conferenceEventListener.handleConferenceEvent(dsEnd);
}
VoiceUserLeftEvent pl = new VoiceUserLeftEvent(memberId.toString(), confName);
conferenceEventListener.handleConferenceEvent(pl);
}
@ -117,12 +148,12 @@ public class ESLEventListener implements IEslEventListener {
if (action.equals(START_TALKING_EVENT)) {
pt = new VoiceUserTalkingEvent(memberId.toString(), confName, true);
conferenceEventListener.handleConferenceEvent(pt);
conferenceEventListener.handleConferenceEvent(pt);
} else if (action.equals(STOP_TALKING_EVENT)) {
pt = new VoiceUserTalkingEvent(memberId.toString(), confName, false);
conferenceEventListener.handleConferenceEvent(pt);
conferenceEventListener.handleConferenceEvent(pt);
} else {
System.out.println("Unknown conference Action [" + action + "]");
System.out.println("Unknown conference Action [" + action + "]");
}
}
@ -133,52 +164,80 @@ public class ESLEventListener implements IEslEventListener {
@Override
public void conferenceEventThreadRun(String uniqueId, String confName, int confSize, EslEvent event) {
}
//@Override
public void conferenceEventRecord(String uniqueId, String confName, int confSize, EslEvent event) {
String action = event.getEventHeaders().get("Action");
if(action == null) {
String action = event.getEventHeaders().get("Action");
if(action == null) {
return;
}
System.out.println("Handling conferenceEventRecord " + action);
if (action.equals(START_RECORDING_EVENT)) {
VoiceStartRecordingEvent sre = new VoiceStartRecordingEvent(confName, true);
sre.setRecordingFilename(getRecordFilenameFromEvent(event));
sre.setTimestamp(genTimestamp().toString());
System.out.println("Voice conference recording started. file=[" + getRecordFilenameFromEvent(event) + "], conf=[" + confName + "]");
conferenceEventListener.handleConferenceEvent(sre);
} else if (action.equals(STOP_RECORDING_EVENT)) {
VoiceStartRecordingEvent srev = new VoiceStartRecordingEvent(confName, false);
srev.setRecordingFilename(getRecordFilenameFromEvent(event));
srev.setTimestamp(genTimestamp().toString());
System.out.println("Voice conference recording stopped. file=[" + getRecordFilenameFromEvent(event) + "], conf=[" + confName + "]");
conferenceEventListener.handleConferenceEvent(srev);
} else {
System.out.println("Processing UNKNOWN conference Action " + action + "]");
}
if (action.equals(START_RECORDING_EVENT)) {
if (confName.endsWith(DESKSHARE_CONFERENCE_NAME_SUFFIX)){
if (isRTMPStream(event)) {
DeskShareRTMPBroadcastEvent rtmp = new DeskShareRTMPBroadcastEvent(confName, true);
rtmp.setBroadcastingStreamUrl(getStreamUrl(event));
rtmp.setVideoHeight(Integer.parseInt(getBroadcastParameter(event, "vh")));
rtmp.setVideoWidth(Integer.parseInt(getBroadcastParameter(event, "vw")));
rtmp.setTimestamp(genTimestamp().toString());
System.out.println("DeskShare conference broadcast started. url=["
+ getStreamUrl(event) + "], conf=[" + confName + "]");
conferenceEventListener.handleConferenceEvent(rtmp);
}
} else {
VoiceStartRecordingEvent sre = new VoiceStartRecordingEvent(confName, true);
sre.setRecordingFilename(getRecordFilenameFromEvent(event));
sre.setTimestamp(genTimestamp().toString());
System.out.println("Voice conference recording started. file=["
+ getRecordFilenameFromEvent(event) + "], conf=[" + confName + "]");
conferenceEventListener.handleConferenceEvent(sre);
}
} else if (action.equals(STOP_RECORDING_EVENT)) {
if (confName.endsWith(DESKSHARE_CONFERENCE_NAME_SUFFIX)){
if (isRTMPStream(event)) {
DeskShareRTMPBroadcastEvent rtmp = new DeskShareRTMPBroadcastEvent(confName, false);
rtmp.setBroadcastingStreamUrl(getStreamUrl(event));
rtmp.setVideoHeight(Integer.parseInt(getBroadcastParameter(event, "vh")));
rtmp.setVideoWidth(Integer.parseInt(getBroadcastParameter(event, "vw")));
rtmp.setTimestamp(genTimestamp().toString());
System.out.println("DeskShare conference broadcast stopped. url=["
+ getStreamUrl(event) + "], conf=[" + confName + "]");
conferenceEventListener.handleConferenceEvent(rtmp);
}
} else {
VoiceStartRecordingEvent sre = new VoiceStartRecordingEvent(confName, false);
sre.setRecordingFilename(getRecordFilenameFromEvent(event));
sre.setTimestamp(genTimestamp().toString());
System.out.println("Voice conference recording stopped. file=["
+ getRecordFilenameFromEvent(event) + "], conf=[" + confName + "]");
conferenceEventListener.handleConferenceEvent(sre);
}
}
else {
System.out.println("Processing UNKNOWN conference Action " + action + "]");
}
}
private Long genTimestamp() {
return TimeUnit.NANOSECONDS.toMillis(System.nanoTime());
return TimeUnit.NANOSECONDS.toMillis(System.nanoTime());
}
@Override
public void eventReceived(EslEvent event) {
System.out.println("ESL Event Listener received event=[" + event.getEventName() + "]");
@Override
public void eventReceived(EslEvent event) {
System.out.println("ESL Event Listener received event=[" + event.getEventName() + "]");
// if (event.getEventName().equals(FreeswitchHeartbeatMonitor.EVENT_HEARTBEAT)) {
//// setChanged();
// notifyObservers(event);
// return;
// }
}
}
private Integer getMemberIdFromEvent(EslEvent e) {
return new Integer(e.getEventHeaders().get("Member-ID"));
@ -191,8 +250,52 @@ public class ESLEventListener implements IEslEventListener {
private String getCallerIdNameFromEvent(EslEvent e) {
return e.getEventHeaders().get("Caller-Caller-ID-Name");
}
private String getRecordFilenameFromEvent(EslEvent e) {
return e.getEventHeaders().get("Path");
return e.getEventHeaders().get("Path");
}
// Distinguish between recording to a file:
// /path/to/a/file.mp4
// and broadcasting a stream:
// {channels=2,samplerate=48000,vw=1920,vh=1080,fps=15.00}rtmp://192.168.0.109/live/abc/dev-test
private Boolean isRTMPStream(EslEvent e) {
String path = e.getEventHeaders().get("Path");
if (path.contains("rtmp") && path.contains("channels")
&& path.contains("samplerate") && path.contains("vw")
&& path.contains("vh") && path.contains("fps")) {
return true;
} else {
return false;
}
}
// returns a String so that we can parse to an int or double depending on the param
private String getBroadcastParameter(EslEvent e, String param) {
String path = e.getEventHeaders().get("Path");
if (isRTMPStream(e)) {
String temp = path.substring(path.indexOf("{") + 1, path.indexOf("}"));
String[] arr = temp.split(",");
for (int i = 0; i < 5; i++) {
if (arr[i].startsWith(param)) {
return arr[i].substring(arr[i].indexOf('=') + 1);
}
}
return "0";
} else {
return "0";
}
}
// Obtain the rtmp url from the event (if any):
private String getStreamUrl(EslEvent e) {
String path = e.getEventHeaders().get("Path");
if (isRTMPStream(e)){
return path.substring(path.lastIndexOf("}") + 1);
} else {
return "";
}
}
}

View File

@ -1,7 +1,7 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
* Copyright (c) 2015 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
@ -24,77 +24,81 @@ import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import org.bigbluebutton.freeswitch.voice.freeswitch.actions.BroadcastConferenceCommand;
import org.bigbluebutton.freeswitch.voice.freeswitch.actions.EjectAllUsersCommand;
import org.bigbluebutton.freeswitch.voice.freeswitch.actions.EjectUserCommand;
import org.bigbluebutton.freeswitch.voice.freeswitch.actions.FreeswitchCommand;
import org.bigbluebutton.freeswitch.voice.freeswitch.actions.MuteUserCommand;
import org.bigbluebutton.freeswitch.voice.freeswitch.actions.GetAllUsersCommand;
import org.bigbluebutton.freeswitch.voice.freeswitch.actions.RecordConferenceCommand;
import org.bigbluebutton.freeswitch.voice.freeswitch.actions.*;
public class FreeswitchApplication {
private static final int SENDERTHREADS = 1;
private static final Executor msgSenderExec = Executors.newFixedThreadPool(SENDERTHREADS);
private static final Executor runExec = Executors.newFixedThreadPool(SENDERTHREADS);
private BlockingQueue<FreeswitchCommand> messages = new LinkedBlockingQueue<FreeswitchCommand>();
private final ConnectionManager manager;
private final String USER = "0"; /* not used for now */
private volatile boolean sendMessages = false;
public FreeswitchApplication(ConnectionManager manager) {
this.manager = manager;
}
private void queueMessage(FreeswitchCommand command) {
try {
messages.offer(command, 5, TimeUnit.SECONDS);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void getAllUsers(String voiceConfId) {
GetAllUsersCommand prc = new GetAllUsersCommand(voiceConfId, USER);
queueMessage(prc);
}
public void muteUser(String voiceConfId, String voiceUserId, Boolean mute) {
MuteUserCommand mpc = new MuteUserCommand(voiceConfId, voiceUserId, mute, USER);
queueMessage(mpc);
}
public void eject(String voiceConfId, String voiceUserId) {
EjectUserCommand mpc = new EjectUserCommand(voiceConfId, voiceUserId, USER);
queueMessage(mpc);
}
public void ejectAll(String voiceConfId) {
EjectAllUsersCommand mpc = new EjectAllUsersCommand(voiceConfId, USER);
queueMessage(mpc);
}
private Long genTimestamp() {
return TimeUnit.NANOSECONDS.toMillis(System.nanoTime());
}
public void startRecording(String voiceConfId, String meetingid){
String RECORD_DIR = "/var/freeswitch/meetings";
String voicePath = RECORD_DIR + File.separatorChar + meetingid + "-" + genTimestamp() + ".wav";
RecordConferenceCommand rcc = new RecordConferenceCommand(voiceConfId, USER, true, voicePath);
queueMessage(rcc);
}
public void stopRecording(String voiceConfId, String meetingid, String voicePath){
RecordConferenceCommand rcc = new RecordConferenceCommand(voiceConfId, USER, false, voicePath);
queueMessage(rcc);
}
private void queueMessage(FreeswitchCommand command) {
try {
messages.offer(command, 5, TimeUnit.SECONDS);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void getAllUsers(String voiceConfId) {
GetAllUsersCommand prc = new GetAllUsersCommand(voiceConfId, USER);
queueMessage(prc);
}
public void muteUser(String voiceConfId, String voiceUserId, Boolean mute) {
MuteUserCommand mpc = new MuteUserCommand(voiceConfId, voiceUserId, mute, USER);
queueMessage(mpc);
}
public void eject(String voiceConfId, String voiceUserId) {
EjectUserCommand mpc = new EjectUserCommand(voiceConfId, voiceUserId, USER);
queueMessage(mpc);
}
public void ejectAll(String voiceConfId) {
EjectAllUsersCommand mpc = new EjectAllUsersCommand(voiceConfId, USER);
queueMessage(mpc);
}
private Long genTimestamp() {
return TimeUnit.NANOSECONDS.toMillis(System.nanoTime());
}
public void startRecording(String voiceConfId, String meetingid){
String RECORD_DIR = "/var/freeswitch/meetings";
String voicePath = RECORD_DIR + File.separatorChar + meetingid + "-" + genTimestamp() + ".wav";
RecordConferenceCommand rcc = new RecordConferenceCommand(voiceConfId, USER, true, voicePath);
queueMessage(rcc);
}
public void stopRecording(String voiceConfId, String meetingid, String voicePath){
RecordConferenceCommand rcc = new RecordConferenceCommand(voiceConfId, USER, false, voicePath);
queueMessage(rcc);
}
public void deskShareBroadcastRTMP(String voiceConfId, String streamUrl, String timestamp, Boolean broadcast){
DeskShareBroadcastRTMPCommand rtmp = new DeskShareBroadcastRTMPCommand(voiceConfId, USER, streamUrl, timestamp, broadcast);
queueMessage(rtmp);
}
public void deskShareHangUp(String voiceConfId, String fsConferenceName, String timestamp){
DeskShareHangUpCommand huCmd = new DeskShareHangUpCommand(voiceConfId, fsConferenceName, USER, timestamp);
queueMessage(huCmd);
}
private void sendMessageToFreeswitch(final FreeswitchCommand command) {
Runnable task = new Runnable() {
public void run() {
@ -105,7 +109,6 @@ public class FreeswitchApplication {
} else if (command instanceof MuteUserCommand) {
MuteUserCommand cmd = (MuteUserCommand) command;
System.out.println("Sending MuteParticipantCommand for conference = [" + cmd.getRoom() + "]");
System.out.println("Sending MuteParticipantCommand for conference = [" + cmd.getRoom() + "]");
manager.mute(cmd);
} else if (command instanceof EjectUserCommand) {
EjectUserCommand cmd = (EjectUserCommand) command;
@ -117,15 +120,20 @@ public class FreeswitchApplication {
manager.ejectAll(cmd);
} else if (command instanceof RecordConferenceCommand) {
manager.record((RecordConferenceCommand) command);
} else if (command instanceof DeskShareBroadcastRTMPCommand) {
manager.broadcastRTMP((DeskShareBroadcastRTMPCommand)command);
} else if (command instanceof DeskShareHangUpCommand) {
DeskShareHangUpCommand cmd = (DeskShareHangUpCommand) command;
manager.hangUp(cmd);
} else if (command instanceof BroadcastConferenceCommand) {
manager.broadcast((BroadcastConferenceCommand) command);
}
}
}
};
runExec.execute(task);
runExec.execute(task);
}
public void start() {
sendMessages = true;
Runnable sender = new Runnable() {
@ -134,18 +142,17 @@ public class FreeswitchApplication {
FreeswitchCommand message;
try {
message = messages.take();
sendMessageToFreeswitch(message);
sendMessageToFreeswitch(message);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
};
msgSenderExec.execute(sender);
msgSenderExec.execute(sender);
}
public void stop() {
sendMessages = false;
}

View File

@ -0,0 +1,58 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2015 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.freeswitch.voice.freeswitch.actions;
import org.bigbluebutton.freeswitch.voice.events.ConferenceEventListener;
import org.freeswitch.esl.client.transport.message.EslMessage;
public class DeskShareBroadcastRTMPCommand extends FreeswitchCommand {
private String broadcastPath;
private boolean broadcast;
private String timestamp;
private final String DESKSHARE_SUFFIX = "-DESKSHARE";
public DeskShareBroadcastRTMPCommand(String room, String requesterId, String broadcastPath, String timestamp, boolean broadcast){
super(room, requesterId);
this.broadcastPath = broadcastPath;
this.broadcast = broadcast;
this.timestamp = timestamp;
}
@Override
public String getCommandArgs() {
String action = "norecord";
if (broadcast) {
action = "record";
}
String room = getRoom();
if (!room.endsWith(DESKSHARE_SUFFIX)) {
room = room + DESKSHARE_SUFFIX;
}
return SPACE + room + SPACE + action + SPACE + broadcastPath;
}
public void handleResponse(EslMessage response, ConferenceEventListener eventListener) {
//Test for Known Conference
System.out.println("\nDeskShareBroadcastRTMPCommand\n");
}
}

View File

@ -0,0 +1,35 @@
package org.bigbluebutton.freeswitch.voice.freeswitch.actions;
import org.bigbluebutton.freeswitch.voice.events.ConferenceEventListener;
import org.freeswitch.esl.client.transport.message.EslMessage;
/**
* Created by anton on 07/01/16.
*/
public class DeskShareHangUpCommand extends FreeswitchCommand {
private String timestamp;
private String fsConferenceName;
private final String DESKSHARE_SUFFIX = "-DESKSHARE";
public DeskShareHangUpCommand(String room, String fsConferenceName, String requesterId, String timestamp){
super(room, requesterId);
this.timestamp = timestamp;
this.fsConferenceName = fsConferenceName;
}
@Override
public String getCommandArgs() {
String action = "kick all";
if(!fsConferenceName.endsWith(DESKSHARE_SUFFIX)) {
fsConferenceName = fsConferenceName + DESKSHARE_SUFFIX;
}
return SPACE + fsConferenceName + SPACE + action;
}
public void handleResponse(EslMessage response, ConferenceEventListener eventListener) {
System.out.println("\nDeskShareHangUpCommand\n");
}
}

View File

@ -0,0 +1,50 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2015 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.freeswitch.voice.freeswitch.actions;
import org.bigbluebutton.freeswitch.voice.events.ConferenceEventListener;
import org.freeswitch.esl.client.transport.message.EslMessage;
public class DeskShareRecordCommand extends FreeswitchCommand {
private String recordPath;
private boolean record;
public DeskShareRecordCommand(String room, String requesterId, boolean record, String recordPath){
super(room, requesterId);
this.recordPath = recordPath;
this.record = record;
}
@Override
public String getCommandArgs() {
String action = "norecord";
if (record)
action = "record";
System.out.println("\n\n\n\n\n DESKSHARE RECORD " + record + "\n\n\n\n");
return SPACE + getRoom() + SPACE + action + SPACE + recordPath;
}
public void handleResponse(EslMessage response, ConferenceEventListener eventListener) {
//Test for Known Conference
System.out.println("\n\n\n\n\nLALALALLALA\n\n\n\n");
}
}

View File

@ -2,11 +2,20 @@ package org.bigbluebutton.freeswitch
import org.bigbluebutton.freeswitch.voice.IVoiceConferenceService
import org.bigbluebutton.endpoint.redis.RedisPublisher
import org.bigbluebutton.common.messages._
import org.bigbluebutton.common.messages.VoiceConfRecordingStartedMessage
import org.bigbluebutton.common.messages.UserJoinedVoiceConfMessage
import org.bigbluebutton.common.messages.UserLeftVoiceConfMessage
import org.bigbluebutton.common.messages.UserMutedInVoiceConfMessage
import org.bigbluebutton.common.messages.UserTalkingInVoiceConfMessage
import org.bigbluebutton.common.messages.DeskShareStartedEventMessage
import org.bigbluebutton.common.messages.DeskShareStoppedEventMessage
import org.bigbluebutton.common.messages.DeskShareRTMPBroadcastStartedEventMessage
import org.bigbluebutton.common.messages.DeskShareRTMPBroadcastStoppedEventMessage
class VoiceConferenceService(sender: RedisPublisher) extends IVoiceConferenceService {
val FROM_VOICE_CONF_SYSTEM_CHAN = "bigbluebutton:from-voice-conf:system";
private final val DESKSHARE_CONFERENCE_NAME_SUFFIX = "-DESKSHARE"
def voiceConfRecordingStarted(voiceConfId: String, recordStream: String, recording: java.lang.Boolean, timestamp: String) {
val msg = new VoiceConfRecordingStartedMessage(voiceConfId, recordStream, recording, timestamp)
@ -41,4 +50,33 @@ class VoiceConferenceService(sender: RedisPublisher) extends IVoiceConferenceSer
val msg = new UserTalkingInVoiceConfMessage(voiceConfId, voiceUserId, talking)
sender.publish(FROM_VOICE_CONF_SYSTEM_CHAN, msg.toJson())
}
def deskShareStarted(voiceConfId: String, callerIdNum: String, callerIdName: String) {
val trimmedVoiceConfId = voiceConfId.replace(DESKSHARE_CONFERENCE_NAME_SUFFIX, "")
println("******** FreeswitchConferenceService send deskShareStarted to BBB " + trimmedVoiceConfId)
val msg = new DeskShareStartedEventMessage(trimmedVoiceConfId, callerIdNum, callerIdName)
sender.publish(FROM_VOICE_CONF_SYSTEM_CHAN, msg.toJson())
}
def deskShareEnded(voiceConfId: String, callerIdNum: String, callerIdName: String) {
val trimmedVoiceConfId = voiceConfId.replace(DESKSHARE_CONFERENCE_NAME_SUFFIX, "")
println("******** FreeswitchConferenceService send deskShareStopped to BBB " + trimmedVoiceConfId)
val msg = new DeskShareStoppedEventMessage(trimmedVoiceConfId, callerIdNum, callerIdName)
sender.publish(FROM_VOICE_CONF_SYSTEM_CHAN, msg.toJson())
}
def deskShareRTMPBroadcastStarted(voiceConfId: String, streamname: String, vw: java.lang.Integer, vh: java.lang.Integer, timestamp: String) {
val trimmedVoiceConfId = voiceConfId.replace(DESKSHARE_CONFERENCE_NAME_SUFFIX, "")
println("******** FreeswitchConferenceService send deskShareRTMPBroadcastStarted to BBB " + trimmedVoiceConfId)
val msg = new DeskShareRTMPBroadcastStartedEventMessage(trimmedVoiceConfId, streamname, vw, vh, timestamp)
sender.publish(FROM_VOICE_CONF_SYSTEM_CHAN, msg.toJson())
}
def deskShareRTMPBroadcastStopped(voiceConfId: String, streamname: String, vw: java.lang.Integer, vh: java.lang.Integer, timestamp: String) {
val trimmedVoiceConfId = voiceConfId.replace(DESKSHARE_CONFERENCE_NAME_SUFFIX, "")
println("******** FreeswitchConferenceService send deskShareRTMPBroadcastStopped to BBB " + trimmedVoiceConfId)
val msg = new DeskShareRTMPBroadcastStoppedEventMessage(trimmedVoiceConfId, streamname, vw, vh, timestamp)
sender.publish(FROM_VOICE_CONF_SYSTEM_CHAN, msg.toJson())
}
}

View File

@ -1,10 +1,9 @@
name := "bbb-common-message"
organization := "org.bigbluebutton"
version := "0.0.17-SNAPSHOT"
version := "0.0.17"
// We want to have our jar files in lib_managed dir.
// This way we'll have the right path when we import
@ -48,19 +47,19 @@ autoScalaLibrary := false
* publish to the local maven repo using "sbt publish"
*/
// Uncomment this to publish to local maven repo while commenting out the nexus repo
publishTo := Some(Resolver.file("file", new File(Path.userHome.absolutePath+"/.m2/repository")))
//publishTo := Some(Resolver.file("file", new File(Path.userHome.absolutePath+"/.m2/repository")))
// Comment this out when publishing to local maven repo using SNAPSHOT version.
// To push to sonatype "sbt publishSigned"
// publishTo := {
// val nexus = "https://oss.sonatype.org/"
// if (isSnapshot.value)
// Some("snapshots" at nexus + "content/repositories/snapshots")
// else
// Some("releases" at nexus + "service/local/staging/deploy/maven2")
// }
publishTo := {
val nexus = "https://oss.sonatype.org/"
if (isSnapshot.value)
Some("snapshots" at nexus + "content/repositories/snapshots")
else
Some("releases" at nexus + "service/local/staging/deploy/maven2")
}
// Enables publishing to maven repo

View File

@ -32,7 +32,7 @@ public class FromJsonDecoder {
StartCustomPollRequestMessage msg = gson.fromJson(message, StartCustomPollRequestMessage.class);
return msg;
} else {
System.out.println("Unknown message name=[" + messageName + "]");
// System.out.println("Unknown message name=[" + messageName + "]");
return null;
}
}

View File

@ -126,4 +126,10 @@ public class Constants {
public static final String PERM_LOCK_ON_JOIN_CONFIG = "lockOnJoinConfigurable";
public static final String ENABLED = "enabled";
public static final String AVATAR_URL = "avatarURL";
public static final String STUNS = "stuns";
public static final String TURNS = "turns";
public static final String USERNAME = "username";
public static final String URL = "url";
public static final String TTL = "ttl";
public static final String PASSWORD = "password";
}

View File

@ -0,0 +1,61 @@
package org.bigbluebutton.common.messages;
import java.util.HashMap;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
/**
* Created by anton on 21/12/15.
*/
public class DeskShareGetInfoRequestMessage {
public static final String GET_DESKTOP_SHARE_GET_INFO_REQUEST = "desktop_share_get_info_request";
public static final String VERSION = "0.0.1";
public final String meetingId;
public final String requesterId;
public final String replyTo;
public DeskShareGetInfoRequestMessage(String meetingId, String requesterId, String replyTo) {
this.meetingId = meetingId;
this.requesterId = requesterId;
this.replyTo = replyTo;
}
public String toJson() {
HashMap<String, Object> payload = new HashMap<String, Object>();
payload.put(Constants.MEETING_ID, meetingId);
payload.put(Constants.REQUESTER_ID, requesterId);
payload.put(Constants.REPLY_TO, replyTo);
java.util.HashMap<String, Object> header = MessageBuilder.buildHeader(GET_DESKTOP_SHARE_GET_INFO_REQUEST, VERSION, null);
return MessageBuilder.buildJson(header, payload);
}
public static DeskShareGetInfoRequestMessage fromJson(String message) {
JsonParser parser = new JsonParser();
JsonObject obj = (JsonObject) parser.parse(message);
if (obj.has("header") && obj.has("payload")) {
JsonObject header = (JsonObject) obj.get("header");
JsonObject payload = (JsonObject) obj.get("payload");
if (header.has("name")) {
String messageName = header.get("name").getAsString();
if (GET_DESKTOP_SHARE_GET_INFO_REQUEST.equals(messageName)) {
if (payload.has(Constants.MEETING_ID)
&& payload.has(Constants.REPLY_TO)
&& payload.has(Constants.REQUESTER_ID)) {
String id = payload.get(Constants.MEETING_ID).getAsString();
String requesterId = payload.get(Constants.REQUESTER_ID).getAsString();
String replyTo = payload.get(Constants.REPLY_TO).getAsString();
return new DeskShareGetInfoRequestMessage(id, requesterId, replyTo);
}
}
}
}
return null;
}
}

View File

@ -0,0 +1,68 @@
package org.bigbluebutton.common.messages;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import java.util.HashMap;
/**
* Created by anton on 07/01/16.
*/
// akka-bbb-apps to akka-bbb-fsesl
public class DeskShareHangUpEventMessage {
public static final String DESKSHARE_HANG_UP_MESSAGE = "deskshare_hang_up_message";
public static final String VERSION = "0.0.1";
public static final String CONFERENCE_NAME = "conference_name";
public static final String FS_CONFERENCE_NAME = "fs_conference_name";
public static final String TIMESTAMP = "timestamp";
public final String conferenceName;
public final String timestamp;
public final String fsConferenceName;
public DeskShareHangUpEventMessage(String conferenceName, String fsConferenceName, String timestamp) {
this.conferenceName = conferenceName;
this.fsConferenceName = fsConferenceName;
this.timestamp = timestamp;
}
public String toJson() {
HashMap<String, Object> payload = new HashMap<String, Object>();
payload.put(CONFERENCE_NAME, conferenceName);
payload.put(FS_CONFERENCE_NAME, fsConferenceName);
payload.put(TIMESTAMP, timestamp);
java.util.HashMap<String, Object> header = MessageBuilder.buildHeader(DESKSHARE_HANG_UP_MESSAGE, VERSION, null);
return MessageBuilder.buildJson(header, payload);
}
public static DeskShareHangUpEventMessage fromJson(String message) {
JsonParser parser = new JsonParser();
JsonObject obj = (JsonObject) parser.parse(message);
if (obj.has("header") && obj.has("payload")) {
JsonObject header = (JsonObject) obj.get("header");
JsonObject payload = (JsonObject) obj.get("payload");
if (header.has("name")) {
String messageName = header.get("name").getAsString();
if (DESKSHARE_HANG_UP_MESSAGE.equals(messageName)) {
if (payload.has(CONFERENCE_NAME)
&& payload.has(FS_CONFERENCE_NAME)
&& payload.has(TIMESTAMP)) {
String conferenceName = payload.get(CONFERENCE_NAME).getAsString();
String fsConferenceName = payload.get(FS_CONFERENCE_NAME).getAsString();
String timestamp = payload.get(TIMESTAMP).getAsString();
return new DeskShareHangUpEventMessage(conferenceName, fsConferenceName, timestamp);
}
}
}
}
return null;
}
}

View File

@ -0,0 +1,93 @@
package org.bigbluebutton.common.messages;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import java.util.HashMap;
//Message from bbb-akka-apps to bigbluebutton-apps (in red5)
public class DeskShareNotifyASingleViewerEventMessage {
public static final String DESK_SHARE_NOTIFY_A_SINGLE_VIEWER = "desk_share_notify_a_single_viewer";
public static final String VERSION = "0.0.1";
public static final String MEETING_ID = "meeting_id";
public static final String USER_ID = "userid";
public static final String STREAM_PATH = "stream_path";
public static final String BROADCASTING = "broadcasting";
public static final String TIMESTAMP = "timestamp";
public static final String VIDEO_WIDTH = "vw";
public static final String VIDEO_HEIGHT = "vh";
public final String meetingId;
public final String userId;
public final String streamPath;
public final boolean broadcasting;
public final String timestamp;
public final int vw;
public final int vh;
public DeskShareNotifyASingleViewerEventMessage(String meetingId, String userId, String streamPath,
boolean broadcasting, int vw, int vh, String timestamp) {
this.meetingId = meetingId;
this.userId=userId;
this.streamPath = streamPath;
this.broadcasting = broadcasting;
this.timestamp = timestamp;
this.vw = vw;
this.vh = vh;
}
public String toJson() {
HashMap<String, Object> payload = new HashMap<String, Object>();
payload.put(MEETING_ID, meetingId);
payload.put(USER_ID, userId);
payload.put(STREAM_PATH, streamPath);
payload.put(BROADCASTING, broadcasting);
payload.put(TIMESTAMP, timestamp);
payload.put(VIDEO_HEIGHT, vh);
payload.put(VIDEO_WIDTH, vw);
java.util.HashMap<String, Object> header = MessageBuilder.buildHeader(DESK_SHARE_NOTIFY_A_SINGLE_VIEWER, VERSION, null);
return MessageBuilder.buildJson(header, payload);
}
public static DeskShareNotifyASingleViewerEventMessage fromJson(String message) {
JsonParser parser = new JsonParser();
JsonObject obj = (JsonObject) parser.parse(message);
if (obj.has("header") && obj.has("payload")) {
JsonObject header = (JsonObject) obj.get("header");
JsonObject payload = (JsonObject) obj.get("payload");
if (header.has("name")) {
String messageName = header.get("name").getAsString();
if (DESK_SHARE_NOTIFY_A_SINGLE_VIEWER.equals(messageName)) {
if (payload.has(MEETING_ID)
&& payload.has(USER_ID)
&& payload.has(BROADCASTING)
&& payload.has(TIMESTAMP)
&& payload.has(VIDEO_HEIGHT)
&& payload.has(VIDEO_WIDTH)
&& payload.has(STREAM_PATH)) {
String meetingId = payload.get(MEETING_ID).getAsString();
String userId = payload.get(USER_ID).getAsString();
String streamPath = payload.get(STREAM_PATH).getAsString();
boolean broadcasting = payload.get(BROADCASTING).getAsBoolean();
String timestamp = payload.get(TIMESTAMP).getAsString();
int vh = payload.get(VIDEO_HEIGHT).getAsInt();
int vw = payload.get(VIDEO_WIDTH).getAsInt();
return new DeskShareNotifyASingleViewerEventMessage(meetingId, userId, streamPath,
broadcasting, vw, vh, timestamp);
}
}
}
}
return null;
}
}

View File

@ -0,0 +1,83 @@
package org.bigbluebutton.common.messages;
import java.util.HashMap;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
//Message from bbb-akka-apps to bbb-apps-red5
public class DeskShareNotifyViewersRTMPEventMessage {
public static final String DESK_SHARE_NOTIFY_VIEWERS_RTMP = "desk_share_notify_viewers_rtmp";
public static final String VERSION = "0.0.1";
public static final String MEETING_ID = "meeting_id";
public static final String STREAM_PATH = "stream_path";
public static final String BROADCASTING = "broadcasting";
public static final String VIDEO_WIDTH = "vw";
public static final String VIDEO_HEIGHT = "vh";
public static final String TIMESTAMP = "timestamp";
public final String meetingId;
public final String streamPath;
public final boolean broadcasting;
public final String timestamp;
public final int vw;
public final int vh;
public DeskShareNotifyViewersRTMPEventMessage(String meetingId, String streamPath,
boolean broadcasting, int vw, int vh, String timestamp) {
this.meetingId = meetingId;
this.streamPath = streamPath;
this.broadcasting = broadcasting;
this.timestamp = timestamp;
this.vw = vw;
this.vh = vh;
}
public String toJson() {
HashMap<String, Object> payload = new HashMap<String, Object>();
payload.put(MEETING_ID, meetingId);
payload.put(STREAM_PATH, streamPath);
payload.put(BROADCASTING, broadcasting);
payload.put(TIMESTAMP, timestamp);
payload.put(VIDEO_HEIGHT, vh);
payload.put(VIDEO_WIDTH, vw);
java.util.HashMap<String, Object> header = MessageBuilder.buildHeader(DESK_SHARE_NOTIFY_VIEWERS_RTMP, VERSION, null);
return MessageBuilder.buildJson(header, payload);
}
public static DeskShareNotifyViewersRTMPEventMessage fromJson(String message) {
JsonParser parser = new JsonParser();
JsonObject obj = (JsonObject) parser.parse(message);
if (obj.has("header") && obj.has("payload")) {
JsonObject header = (JsonObject) obj.get("header");
JsonObject payload = (JsonObject) obj.get("payload");
if (header.has("name")) {
String messageName = header.get("name").getAsString();
if (DESK_SHARE_NOTIFY_VIEWERS_RTMP.equals(messageName)) {
if (payload.has(MEETING_ID)
&& payload.has(BROADCASTING)
&& payload.has(TIMESTAMP)
&& payload.has(VIDEO_HEIGHT)
&& payload.has(VIDEO_WIDTH)
&& payload.has(STREAM_PATH)) {
String meetingId = payload.get(MEETING_ID).getAsString();
String streamPath = payload.get(STREAM_PATH).getAsString();
boolean broadcasting = payload.get(BROADCASTING).getAsBoolean();
String timestamp = payload.get(TIMESTAMP).getAsString();
int vh = payload.get(VIDEO_HEIGHT).getAsInt();
int vw = payload.get(VIDEO_WIDTH).getAsInt();
return new DeskShareNotifyViewersRTMPEventMessage(meetingId, streamPath, broadcasting, vw, vh, timestamp);
}
}
}
}
return null;
}
}

View File

@ -0,0 +1,75 @@
package org.bigbluebutton.common.messages;
import java.util.HashMap;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
public class DeskShareRTMPBroadcastStartedEventMessage {
public static final String DESKSHARE_RTMP_BROADCAST_STARTED_MESSAGE = "deskshare_rtmp_broadcast_started_message";
public static final String VERSION = "0.0.1";
public static final String CONFERENCE_NAME = "conference_name";
public static final String STREAMNAME = "streamname";
public static final String TIMESTAMP = "timestamp";
public static final String VIDEO_WIDTH = "vw";
public static final String VIDEO_HEIGHT = "vh";
public final String conferenceName;
public final String streamname;
public final String timestamp;
public final int vw;
public final int vh;
public DeskShareRTMPBroadcastStartedEventMessage(String conferenceName, String streamname, int vw, int vh, String timestamp) {
this.conferenceName = conferenceName;
this.streamname = streamname;
this.timestamp = timestamp;
this.vw = vw;
this.vh = vh;
}
public String toJson() {
HashMap<String, Object> payload = new HashMap<String, Object>();
payload.put(CONFERENCE_NAME, conferenceName);
payload.put(STREAMNAME, streamname);
payload.put(TIMESTAMP, timestamp);
payload.put(VIDEO_HEIGHT, vh);
payload.put(VIDEO_WIDTH, vw);
java.util.HashMap<String, Object> header = MessageBuilder.buildHeader(DESKSHARE_RTMP_BROADCAST_STARTED_MESSAGE, VERSION, null);
return MessageBuilder.buildJson(header, payload);
}
public static DeskShareRTMPBroadcastStartedEventMessage fromJson(String message) {
JsonParser parser = new JsonParser();
JsonObject obj = (JsonObject) parser.parse(message);
if (obj.has("header") && obj.has("payload")) {
JsonObject header = (JsonObject) obj.get("header");
JsonObject payload = (JsonObject) obj.get("payload");
if (header.has("name")) {
String messageName = header.get("name").getAsString();
if (DESKSHARE_RTMP_BROADCAST_STARTED_MESSAGE.equals(messageName)) {
if (payload.has(CONFERENCE_NAME)
&& payload.has(TIMESTAMP)
&& payload.has(VIDEO_HEIGHT)
&& payload.has(VIDEO_WIDTH)
&& payload.has(STREAMNAME)) {
String conferenceName = payload.get(CONFERENCE_NAME).getAsString();
String streamname = payload.get(STREAMNAME).getAsString();
String timestamp = payload.get(TIMESTAMP).getAsString();
int vh = payload.get(VIDEO_HEIGHT).getAsInt();
int vw = payload.get(VIDEO_WIDTH).getAsInt();
return new DeskShareRTMPBroadcastStartedEventMessage(conferenceName, streamname, vw, vh, timestamp);
}
}
}
}
return null;
}
}

View File

@ -0,0 +1,76 @@
package org.bigbluebutton.common.messages;
import java.util.HashMap;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
public class DeskShareRTMPBroadcastStoppedEventMessage {
public static final String DESKSHARE_RTMP_BROADCAST_STOPPED_MESSAGE = "deskshare_rtmp_broadcast_stopped_message";
public static final String VERSION = "0.0.1";
public static final String CONFERENCE_NAME = "conference_name";
public static final String STREAMNAME = "streamname";
public static final String TIMESTAMP = "timestamp";
public static final String VIDEO_WIDTH = "vw";
public static final String VIDEO_HEIGHT = "vh";
public final String conferenceName;
public final String streamname;
public final String timestamp;
public final int vw;
public final int vh;
public DeskShareRTMPBroadcastStoppedEventMessage(String conferenceName, String streamname, int vw, int vh, String timestamp) {
this.conferenceName = conferenceName;
this.streamname = streamname;
this.timestamp = timestamp;
this.vw = vw;
this.vh = vh;
}
public String toJson() {
HashMap<String, Object> payload = new HashMap<String, Object>();
payload.put(CONFERENCE_NAME, conferenceName);
payload.put(STREAMNAME, streamname);
payload.put(TIMESTAMP, timestamp);
payload.put(VIDEO_HEIGHT, vh);
payload.put(VIDEO_WIDTH, vw);
java.util.HashMap<String, Object> header = MessageBuilder.buildHeader(DESKSHARE_RTMP_BROADCAST_STOPPED_MESSAGE, VERSION, null);
return MessageBuilder.buildJson(header, payload);
}
public static DeskShareRTMPBroadcastStoppedEventMessage fromJson(String message) {
JsonParser parser = new JsonParser();
JsonObject obj = (JsonObject) parser.parse(message);
if (obj.has("header") && obj.has("payload")) {
JsonObject header = (JsonObject) obj.get("header");
JsonObject payload = (JsonObject) obj.get("payload");
if (header.has("name")) {
String messageName = header.get("name").getAsString();
if (DESKSHARE_RTMP_BROADCAST_STOPPED_MESSAGE.equals(messageName)) {
if (payload.has(CONFERENCE_NAME)
&& payload.has(TIMESTAMP)
&& payload.has(VIDEO_HEIGHT)
&& payload.has(VIDEO_WIDTH)
&& payload.has(STREAMNAME)) {
String conferenceName = payload.get(CONFERENCE_NAME).getAsString();
String streamname = payload.get(STREAMNAME).getAsString();
String timestamp = payload.get(TIMESTAMP).getAsString();
int vh = payload.get(VIDEO_HEIGHT).getAsInt();
int vw = payload.get(VIDEO_WIDTH).getAsInt();
return new DeskShareRTMPBroadcastStoppedEventMessage(conferenceName, streamname, vw, vh, timestamp);
}
}
}
}
return null;
}
}

View File

@ -0,0 +1,63 @@
package org.bigbluebutton.common.messages;
import java.util.HashMap;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
public class DeskShareStartRTMPBroadcastEventMessage {
public static final String DESKSHARE_START_RTMP_BROADCAST_MESSAGE = "deskshare_start_rtmp_broadcast_message";
public static final String VERSION = "0.0.1";
public static final String CONFERENCE_NAME = "conference_name";
public static final String STREAMURL = "stream_url";
public static final String TIMESTAMP = "timestamp";
public final String conferenceName;
public final String streamUrl;
public final String timestamp;
public DeskShareStartRTMPBroadcastEventMessage(String conferenceName, String streamUrl, String timestamp) {
this.conferenceName = conferenceName;
this.streamUrl = streamUrl;
this.timestamp = timestamp;
}
public String toJson() {
HashMap<String, Object> payload = new HashMap<String, Object>();
payload.put(CONFERENCE_NAME, conferenceName);
payload.put(STREAMURL, streamUrl);
payload.put(TIMESTAMP, timestamp);
java.util.HashMap<String, Object> header = MessageBuilder.buildHeader(DESKSHARE_START_RTMP_BROADCAST_MESSAGE, VERSION, null);
return MessageBuilder.buildJson(header, payload);
}
public static DeskShareStartRTMPBroadcastEventMessage fromJson(String message) {
JsonParser parser = new JsonParser();
JsonObject obj = (JsonObject) parser.parse(message);
if (obj.has("header") && obj.has("payload")) {
JsonObject header = (JsonObject) obj.get("header");
JsonObject payload = (JsonObject) obj.get("payload");
if (header.has("name")) {
String messageName = header.get("name").getAsString();
if (DESKSHARE_START_RTMP_BROADCAST_MESSAGE.equals(messageName)) {
if (payload.has(CONFERENCE_NAME)
&& payload.has(TIMESTAMP)
&& payload.has(STREAMURL)) {
String conferenceName = payload.get(CONFERENCE_NAME).getAsString();
String streamUrl = payload.get(STREAMURL).getAsString();
String timestamp = payload.get(TIMESTAMP).getAsString();
return new DeskShareStartRTMPBroadcastEventMessage(conferenceName, streamUrl, timestamp);
}
}
}
}
return null;
}
}

View File

@ -0,0 +1,63 @@
package org.bigbluebutton.common.messages;
import java.util.HashMap;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
//Message from FreeSwitch to bbb-akka-apps
public class DeskShareStartedEventMessage {
public static final String DESKSHARE_STARTED_MESSAGE = "deskshare_start_message";
public static final String VERSION = "0.0.1";
public static final String CONFERENCE_NAME = "conference_name";
public static final String CALLER_ID = "caller_id";
public static final String CALLER_ID_NAME = "caller_id_name";
public final String conferenceName;
public final String callerId;
public final String callerIdName;
public DeskShareStartedEventMessage(String conferenceName, String callerId, String callerIdName) {
this.conferenceName = conferenceName;
this.callerId = callerId;
this.callerIdName = callerIdName;
}
public String toJson() {
HashMap<String, Object> payload = new HashMap<String, Object>();
payload.put(CONFERENCE_NAME, conferenceName);
payload.put(CALLER_ID_NAME, callerIdName);
payload.put(CALLER_ID, callerId);
java.util.HashMap<String, Object> header = MessageBuilder.buildHeader(DESKSHARE_STARTED_MESSAGE, VERSION, null);
return MessageBuilder.buildJson(header, payload);
}
public static DeskShareStartedEventMessage fromJson(String message) {
JsonParser parser = new JsonParser();
JsonObject obj = (JsonObject) parser.parse(message);
if (obj.has("header") && obj.has("payload")) {
JsonObject header = (JsonObject) obj.get("header");
JsonObject payload = (JsonObject) obj.get("payload");
if (header.has("name")) {
String messageName = header.get("name").getAsString();
if (DESKSHARE_STARTED_MESSAGE.equals(messageName)) {
if (payload.has(CONFERENCE_NAME)
&& payload.has(CALLER_ID)
&& payload.has(CALLER_ID_NAME)) {
String conferenceName = payload.get(CONFERENCE_NAME).getAsString();
String callerId = payload.get(CALLER_ID_NAME).getAsString();
String callerIdName = payload.get(CALLER_ID_NAME).getAsString();
return new DeskShareStartedEventMessage(conferenceName, callerId, callerIdName);
}
}
}
}
return null;
}
}

View File

@ -0,0 +1,63 @@
package org.bigbluebutton.common.messages;
import java.util.HashMap;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
public class DeskShareStopRTMPBroadcastEventMessage {
public static final String DESKSHARE_STOP_RTMP_BROADCAST_MESSAGE = "deskshare_stop_rtmp_broadcast_message";
public static final String VERSION = "0.0.1";
public static final String CONFERENCE_NAME = "conference_name";
public static final String STREAMURL = "stream_url";
public static final String TIMESTAMP = "timestamp";
public final String conferenceName;
public final String streamUrl;
public final String timestamp;
public DeskShareStopRTMPBroadcastEventMessage(String conferenceName, String streamUrl, String timestamp) {
this.conferenceName = conferenceName;
this.streamUrl = streamUrl;
this.timestamp = timestamp;
}
public String toJson() {
HashMap<String, Object> payload = new HashMap<String, Object>();
payload.put(CONFERENCE_NAME, conferenceName);
payload.put(STREAMURL, streamUrl);
payload.put(TIMESTAMP, timestamp);
java.util.HashMap<String, Object> header = MessageBuilder.buildHeader(DESKSHARE_STOP_RTMP_BROADCAST_MESSAGE, VERSION, null);
return MessageBuilder.buildJson(header, payload);
}
public static DeskShareStopRTMPBroadcastEventMessage fromJson(String message) {
JsonParser parser = new JsonParser();
JsonObject obj = (JsonObject) parser.parse(message);
if (obj.has("header") && obj.has("payload")) {
JsonObject header = (JsonObject) obj.get("header");
JsonObject payload = (JsonObject) obj.get("payload");
if (header.has("name")) {
String messageName = header.get("name").getAsString();
if (DESKSHARE_STOP_RTMP_BROADCAST_MESSAGE.equals(messageName)) {
if (payload.has(CONFERENCE_NAME)
&& payload.has(TIMESTAMP)
&& payload.has(STREAMURL)) {
String conferenceName = payload.get(CONFERENCE_NAME).getAsString();
String streamUrl = payload.get(STREAMURL).getAsString();
String timestamp = payload.get(TIMESTAMP).getAsString();
return new DeskShareStopRTMPBroadcastEventMessage(conferenceName, streamUrl, timestamp);
}
}
}
}
return null;
}
}

View File

@ -0,0 +1,63 @@
package org.bigbluebutton.common.messages;
import java.util.HashMap;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
//Message from FreeSwitch to bbb-akka-apps
public class DeskShareStoppedEventMessage {
public static final String DESK_SHARE_STOPPED_MESSAGE = "desk_share_stopped_message";
public static final String VERSION = "0.0.1";
public static final String CONFERENCE_NAME = "conference_name";
public static final String CALLER_ID = "caller_id";
public static final String CALLER_ID_NAME = "caller_id_name";
public final String conferenceName;
public final String callerId;
public final String callerIdName;
public DeskShareStoppedEventMessage(String conferenceName, String callerId, String callerIdName) {
this.conferenceName = conferenceName;
this.callerId = callerId;
this.callerIdName = callerIdName;
}
public String toJson() {
HashMap<String, Object> payload = new HashMap<String, Object>();
payload.put(CONFERENCE_NAME, conferenceName);
payload.put(CALLER_ID_NAME, callerIdName);
payload.put(CALLER_ID, callerId);
java.util.HashMap<String, Object> header = MessageBuilder.buildHeader(DESK_SHARE_STOPPED_MESSAGE, VERSION, null);
return MessageBuilder.buildJson(header, payload);
}
public static DeskShareStoppedEventMessage fromJson(String message) {
JsonParser parser = new JsonParser();
JsonObject obj = (JsonObject) parser.parse(message);
if (obj.has("header") && obj.has("payload")) {
JsonObject header = (JsonObject) obj.get("header");
JsonObject payload = (JsonObject) obj.get("payload");
if (header.has("name")) {
String messageName = header.get("name").getAsString();
if (DESK_SHARE_STOPPED_MESSAGE.equals(messageName)) {
if (payload.has(CONFERENCE_NAME)
&& payload.has(CALLER_ID)
&& payload.has(CALLER_ID_NAME)) {
String conferenceName = payload.get(CONFERENCE_NAME).getAsString();
String callerId = payload.get(CALLER_ID_NAME).getAsString();
String callerIdName = payload.get(CALLER_ID_NAME).getAsString();
return new DeskShareStoppedEventMessage(conferenceName, callerId, callerIdName);
}
}
}
}
return null;
}
}

View File

@ -29,6 +29,7 @@ public class MessagingConstants {
public static final String FROM_USERS_CHANNEL = FROM_BBB_APPS_CHANNEL + ":users";
public static final String FROM_CHAT_CHANNEL = FROM_BBB_APPS_CHANNEL + ":chat";
public static final String FROM_WHITEBOARD_CHANNEL = FROM_BBB_APPS_CHANNEL + ":whiteboard";
public static final String FROM_DESK_SHARE_CHANNEL = FROM_BBB_APPS_CHANNEL + ":deskshare";
public static final String TO_BBB_APPS_CHANNEL = "bigbluebutton:to-bbb-apps";
public static final String TO_BBB_APPS_PATTERN = TO_BBB_APPS_CHANNEL + ":*";
@ -42,7 +43,9 @@ public class MessagingConstants {
public static final String TO_WHITEBOARD_CHANNEL = TO_BBB_APPS_CHANNEL + ":whiteboard";
public static final String BBB_APPS_KEEP_ALIVE_CHANNEL = "bigbluebutton:from-bbb-apps:keepalive";
public static final String TO_BBB_HTML5_CHANNEL = "bigbluebutton:to-bbb-html5";
public static final String TO_VOICE_CONF_CHANNEL = "bigbluebutton:to-voice-conf";
public static final String TO_VOICE_CONF_PATTERN = TO_VOICE_CONF_CHANNEL + ":*";
public static final String TO_VOICE_CONF_SYSTEM_CHAN = TO_VOICE_CONF_CHANNEL + ":system";

View File

@ -0,0 +1,74 @@
package org.bigbluebutton.common.messages;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
public class SendStunTurnInfoReplyMessage {
public static final String SEND_STUN_TURN_INFO_REPLY_MESSAGE = "send_stun_turn_info_reply_message";
public static final String VERSION = "0.0.1";
public final String meetingId;
public final String requesterId;
public final ArrayList<String> stuns;
public final ArrayList<Map<String, Object>> turns;
public SendStunTurnInfoReplyMessage(String meetingId, String requesterId, ArrayList<String> stuns,
ArrayList<Map<String, Object>> turns) {
this.meetingId = meetingId;
this.requesterId = requesterId;
this.stuns = stuns;
this.turns = turns;
}
public String toJson() {
HashMap<String, Object> payload = new HashMap<String, Object>();
payload.put(Constants.MEETING_ID, meetingId);
payload.put(Constants.REQUESTER_ID, requesterId);
payload.put(Constants.STUNS, stuns);
payload.put(Constants.TURNS, turns);
java.util.HashMap<String, Object> header = MessageBuilder.buildHeader(SEND_STUN_TURN_INFO_REPLY_MESSAGE, VERSION, null);
return MessageBuilder.buildJson(header, payload);
}
public static SendStunTurnInfoReplyMessage fromJson(String message) {
JsonParser parser = new JsonParser();
JsonObject obj = (JsonObject) parser.parse(message);
if (obj.has("header") && obj.has("payload")) {
JsonObject header = (JsonObject) obj.get("header");
JsonObject payload = (JsonObject) obj.get("payload");
if (header.has("name")) {
String messageName = header.get("name").getAsString();
if (SEND_STUN_TURN_INFO_REPLY_MESSAGE.equals(messageName)) {
if (payload.has(Constants.MEETING_ID)
&& payload.has(Constants.STUNS)
&& payload.has(Constants.TURNS)
&& payload.has(Constants.REQUESTER_ID)) {
String meetingId = payload.get(Constants.MEETING_ID).getAsString();
String requesterId = payload.get(Constants.REQUESTER_ID).getAsString();
Util util = new Util();
JsonArray stunsArray = (JsonArray) payload.get(Constants.STUNS);
ArrayList<String> stunsArrayList = util.extractStuns(stunsArray);
JsonArray turnsArray = (JsonArray) payload.get(Constants.TURNS);
ArrayList<Map<String, Object>> turnsArrayList = util.extractTurns(turnsArray);
return new SendStunTurnInfoReplyMessage(meetingId, requesterId, stunsArrayList, turnsArrayList);
}
}
}
}
return null;
}
}

View File

@ -0,0 +1,58 @@
package org.bigbluebutton.common.messages;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import java.util.HashMap;
/**
* Created by anton on 05/02/16.
*/
public class SendStunTurnInfoRequestMessage {
public static final String SEND_STUN_TURN_INFO_REQUEST_MESSAGE = "send_stun_turn_info_request_message";
public static final String VERSION = "0.0.1";
public final String meetingId;
public final String requesterId;
public SendStunTurnInfoRequestMessage(String meetingId, String requesterId) {
this.meetingId = meetingId;
this.requesterId = requesterId;
}
public String toJson() {
HashMap<String, Object> payload = new HashMap<String, Object>();
payload.put(Constants.MEETING_ID, meetingId);
payload.put(Constants.REQUESTER_ID, requesterId);
java.util.HashMap<String, Object> header = MessageBuilder.buildHeader(SEND_STUN_TURN_INFO_REQUEST_MESSAGE, VERSION, null);
return MessageBuilder.buildJson(header, payload);
}
public static SendStunTurnInfoRequestMessage fromJson(String message) {
JsonParser parser = new JsonParser();
JsonObject obj = (JsonObject) parser.parse(message);
if (obj.has("header") && obj.has("payload")) {
JsonObject header = (JsonObject) obj.get("header");
JsonObject payload = (JsonObject) obj.get("payload");
if (header.has("name")) {
String messageName = header.get("name").getAsString();
if (SEND_STUN_TURN_INFO_REQUEST_MESSAGE.equals(messageName)) {
if (payload.has(Constants.MEETING_ID)
&& payload.has(Constants.REQUESTER_ID)) {
String meetingId = payload.get(Constants.MEETING_ID).getAsString();
String requesterId = payload.get(Constants.REQUESTER_ID).getAsString();
return new SendStunTurnInfoRequestMessage(meetingId, requesterId);
}
}
}
}
return null;
}
}

View File

@ -119,6 +119,52 @@ public class Util {
}
public ArrayList<String> extractStuns(JsonArray stunsArray) {
ArrayList<String> collection = new ArrayList<String>();
Iterator<JsonElement> stunIter = stunsArray.iterator();
while (stunIter.hasNext()) {
JsonElement aStun = stunIter.next();
if (aStun != null) {
collection.add(aStun.getAsString());
}
}
return collection;
}
public ArrayList<Map<String, Object>> extractTurns(JsonArray turnsArray) {
ArrayList<Map<String, Object>> collection = new ArrayList<Map<String, Object>>();
Iterator<JsonElement> turnIter = turnsArray.iterator();
while (turnIter.hasNext()){
JsonElement aTurn = turnIter.next();
Map<String, Object> turnMap = extractATurn((JsonObject)aTurn);
if (turnMap != null) {
collection.add(turnMap);
}
}
return collection;
}
private Map<String,Object> extractATurn(JsonObject aTurn) {
if (aTurn.has(Constants.USERNAME)
&& aTurn.has(Constants.TTL)
&& aTurn.has(Constants.URL)
&& aTurn.has(Constants.PASSWORD)) {
Map<String, Object> turnMap = new HashMap<String, Object>();
turnMap.put(Constants.USERNAME, aTurn.get(Constants.USERNAME).getAsString());
turnMap.put(Constants.URL, aTurn.get(Constants.URL).getAsString());
turnMap.put(Constants.PASSWORD, aTurn.get(Constants.PASSWORD).getAsString());
turnMap.put(Constants.TTL, aTurn.get(Constants.TTL).getAsInt());
return turnMap;
}
return null;
}
public ArrayList<Map<String, Object>> extractChatHistory(JsonArray history) {
ArrayList<Map<String, Object>> collection = new ArrayList<Map<String, Object>>();
Iterator<JsonElement> historyIter = history.iterator();
@ -163,7 +209,7 @@ public class Util {
public ArrayList<Map<String, Object>> extractUsers(JsonArray users) {
ArrayList<Map<String, Object>> collection = new ArrayList<Map<String, Object>>();
Iterator<JsonElement> usersIter = users.iterator();
while (usersIter.hasNext()){
JsonElement user = usersIter.next();
@ -172,39 +218,39 @@ public class Util {
collection.add(userMap);
}
}
return collection;
}
public ArrayList<String> extractWebcamStreams(JsonArray webcamStreams) {
ArrayList<String> collection = new ArrayList<String>();
Iterator<JsonElement> webcamStreamsIter = webcamStreams.iterator();
while (webcamStreamsIter.hasNext()){
JsonElement stream = webcamStreamsIter.next();
collection.add(stream.getAsString());
}
return collection;
}
public ArrayList<String> extractUserids(JsonArray users) {
ArrayList<String> collection = new ArrayList<String>();
Iterator<JsonElement> usersIter = users.iterator();
while (usersIter.hasNext()){
JsonElement user = usersIter.next();
collection.add(user.getAsString());
}
return collection;
}
}
public Map<String, Object> extractAnnotation(JsonObject annotationElement) {
//NON-TEXT SHAPE
if (annotationElement.has(Constants.ID)
@ -300,11 +346,11 @@ public class Util {
}
return null;
}
public Map<String, Object> extractCurrentPresenter(JsonObject vu) {
if (vu.has(Constants.USER_ID) && vu.has(Constants.NAME)
&& vu.has(Constants.ASSIGNED_BY)){
Map<String, Object> vuMap = new HashMap<String, Object>();
String presenterUserId = vu.get(Constants.USER_ID).getAsString();
String presenterName = vu.get(Constants.NAME).getAsString();
@ -313,41 +359,41 @@ public class Util {
vuMap.put("userId", presenterUserId);
vuMap.put("name", presenterName);
vuMap.put("assignedBy", assignedBy);
return vuMap;
}
return null;
}
public ArrayList<Map<String, Object>> extractPresentationPages(JsonArray pagesArray) {
ArrayList<Map<String, Object>> pages = new ArrayList<Map<String, Object>>();
Iterator<JsonElement> pagesIter = pagesArray.iterator();
while (pagesIter.hasNext()){
JsonObject pageObj = (JsonObject)pagesIter.next();
if (pageObj.has("id") && pageObj.has("num")
if (pageObj.has("id") && pageObj.has("num")
&& pageObj.has("thumb_uri") && pageObj.has("swf_uri")
&& pageObj.has("txt_uri") && pageObj.has("svg_uri")
&& pageObj.has("current") && pageObj.has("x_offset")
&& pageObj.has("y_offset") && pageObj.has("width_ratio")
&& pageObj.has("height_ratio")) {
Map<String, Object> page = new HashMap<String, Object>();
String pageId = pageObj.get("id").getAsString();
Integer pageNum = pageObj.get("num").getAsInt();
String pageThumbUri = pageObj.get("thumb_uri").getAsString();
String pageSwfUri = pageObj.get("swf_uri").getAsString();
String pageTxtUri = pageObj.get("txt_uri").getAsString();
String pageSvgUri = pageObj.get("svg_uri").getAsString();
Boolean currentPage = pageObj.get("current").getAsBoolean();
Double xOffset = pageObj.get("x_offset").getAsDouble();
Double yOffset = pageObj.get("y_offset").getAsDouble();
Double widthRatio = pageObj.get("width_ratio").getAsDouble();
Double heightRatio = pageObj.get("height_ratio").getAsDouble();
page.put("id", pageId);
page.put("num", pageNum);
page.put("thumbUri", pageThumbUri);
@ -359,7 +405,7 @@ public class Util {
page.put("yOffset", yOffset);
page.put("widthRatio", widthRatio);
page.put("heightRatio", heightRatio);
pages.add(page);
}
}
@ -368,7 +414,7 @@ public class Util {
public ArrayList<Map<String, Object>> extractPresentations(JsonArray presArray) {
ArrayList<Map<String, Object>> presentations = new ArrayList<Map<String, Object>>();
Iterator<JsonElement> presentationsIter = presArray.iterator();
while (presentationsIter.hasNext()){
JsonObject presObj = (JsonObject)presentationsIter.next();
@ -378,7 +424,7 @@ public class Util {
}
public Map<String, Object> extractPresentation(JsonObject presObj) {
if (presObj.has(Constants.ID) && presObj.has(Constants.NAME)
if (presObj.has(Constants.ID) && presObj.has(Constants.NAME)
&& presObj.has(Constants.CURRENT) && presObj.has(Constants.PAGES)) {
Map<String, Object> pres = new HashMap<String, Object>();
@ -393,7 +439,7 @@ public class Util {
JsonArray pagesJsonArray = presObj.get(Constants.PAGES).getAsJsonArray();
ArrayList<Map<String, Object>> pages = extractPresentationPages(pagesJsonArray);
// store the pages in the presentation
// store the pages in the presentation
pres.put(Constants.PAGES, pages);
// add this presentation into our presentations list
@ -417,20 +463,20 @@ public class Util {
}
return collection;
}
public Map<String, Object> extractPollResultAnnotation(JsonObject annotationElement) {
if (annotationElement.has("result") && annotationElement.has("whiteboardId")
&& annotationElement.has("points")) {
Map<String, Object> finalAnnotation = new HashMap<String, Object>();
String whiteboardId = annotationElement.get("whiteboardId").getAsString();
Integer numRespondents = annotationElement.get(NUM_RESPONDENTS).getAsInt();
Integer numResponders = annotationElement.get(NUM_RESPONDERS).getAsInt();
String resultJson = annotationElement.get("result").getAsString();
JsonParser parser = new JsonParser();
JsonArray resultJsonArray = parser.parse(resultJson).getAsJsonArray();
ArrayList<Map<String, Object>> collection = new ArrayList<Map<String, Object>>();
Iterator<JsonElement> resultIter = resultJsonArray.iterator();
@ -443,10 +489,10 @@ public class Util {
vote.put("id", vid);
vote.put("num_votes", vvotes);
vote.put("key", vkey);
collection.add(vote);
}
JsonArray pointsJsonArray = annotationElement.get("points").getAsJsonArray();
ArrayList<Float> pointsArray = new ArrayList<Float>();
Iterator<JsonElement> pointIter = pointsJsonArray.iterator();
@ -457,13 +503,13 @@ public class Util {
pointsArray.add(pf);
}
}
finalAnnotation.put("whiteboardId", whiteboardId);
finalAnnotation.put(NUM_RESPONDENTS, numRespondents);
finalAnnotation.put(NUM_RESPONDERS, numResponders);
finalAnnotation.put("result", collection);
finalAnnotation.put("points", pointsArray);
return finalAnnotation;
}
return null;
@ -488,13 +534,13 @@ public class Util {
JsonElement shape = annotationElement.get("shape");
Map<String, Object> shapesMap;
if (type.equals("poll_result")) {
shapesMap = extractPollResultAnnotation((JsonObject)shape);
} else {
shapesMap = extractAnnotation((JsonObject)shape);
}
if (shapesMap != null) {
finalAnnotation.put("shapes", shapesMap);
}
@ -582,30 +628,30 @@ public class Util {
if (answer.has(Constants.ID) && answer.has(KEY)) {
String id = answer.get(Constants.ID).getAsString();
String key = answer.get(KEY).getAsString();
answerMap.put(Constants.ID, id);
answerMap.put(KEY, key);
answerMap.put(KEY, key);
}
return answerMap;
}
public static final String ANSWERS = "answers";
public static final String KEY = "key";
public static final String NUM_VOTES = "num_votes";
public static final String NUM_RESPONDERS = "num_responders";
public static final String NUM_RESPONDENTS = "num_respondents";
public Map<String, Object> decodeSimplePoll(JsonObject poll) {
Map<String, Object> pollMap = new HashMap<String, Object>();
if (poll.has(Constants.ID) && poll.has(ANSWERS)) {
String id = poll.get(Constants.ID).getAsString();
String id = poll.get(Constants.ID).getAsString();
JsonArray answers = poll.get(ANSWERS).getAsJsonArray();
ArrayList<Map<String, Object>> collection = new ArrayList<Map<String, Object>>();
Iterator<JsonElement> answersIter = answers.iterator();
while (answersIter.hasNext()){
JsonElement qElem = answersIter.next();
@ -616,12 +662,12 @@ public class Util {
collection.add(answerMap);
}
}
pollMap.put(Constants.ID, id);
pollMap.put(ANSWERS, collection);
pollMap.put(ANSWERS, collection);
}
return pollMap;
}
@ -631,27 +677,26 @@ public class Util {
String id = answer.get(Constants.ID).getAsString();
String key = answer.get(KEY).getAsString();
Integer numVotes = answer.get(NUM_VOTES).getAsInt();
answerMap.put(Constants.ID, id);
answerMap.put(KEY, key);
answerMap.put(KEY, key);
answerMap.put(NUM_VOTES, numVotes);
}
return answerMap;
}
public Map<String, Object> decodeSimplePollResult(JsonObject poll) {
Map<String, Object> pollMap = new HashMap<String, Object>();
if (poll.has(Constants.ID) && poll.has(ANSWERS)) {
String id = poll.get(Constants.ID).getAsString();
String id = poll.get(Constants.ID).getAsString();
Integer numRespondents = poll.get(NUM_RESPONDENTS).getAsInt();
Integer numResponders = poll.get(NUM_RESPONDERS).getAsInt();
JsonArray answers = poll.get(ANSWERS).getAsJsonArray();
ArrayList<Map<String, Object>> collection = new ArrayList<Map<String, Object>>();
Iterator<JsonElement> answersIter = answers.iterator();
while (answersIter.hasNext()){
JsonElement qElem = answersIter.next();
@ -662,16 +707,14 @@ public class Util {
collection.add(answerMap);
}
}
pollMap.put(Constants.ID, id);
pollMap.put(NUM_RESPONDENTS, numRespondents);
pollMap.put(NUM_RESPONDERS, numResponders);
pollMap.put(ANSWERS, collection);
pollMap.put(ANSWERS, collection);
}
return pollMap;
}
}
}

View File

@ -1,17 +1,14 @@
package org.bigbluebutton.core.service.whiteboard;
package org.bigbluebutton.common.messages;
public class WhiteboardKeyUtil {
public static final String TEXT_TYPE = "text";
public static final String PENCIL_TYPE = "pencil";
public static final String RECTANGLE_TYPE = "rectangle";
public static final String ELLIPSE_TYPE = "ellipse";
public static final String TRIANGLE_TYPE = "triangle";
public static final String LINE_TYPE = "line";
public static final String POLL_RESULT_TYPE = "poll_result";
public static final String TEXT_CREATED_STATUS = "textCreated";
public static final String DRAW_START_STATUS = "DRAW_START";
public static final String DRAW_END_STATUS = "DRAW_END";
public static final String POLL_RESULT_TYPE = "poll_result";
}

View File

@ -6,7 +6,7 @@ description := "BigBlueButton custom FS-ESL client built on top of FS-ESL Java l
organization := "org.bigbluebutton"
version := "0.0.3"
version := "0.0.4"
// We want to have our jar files in lib_managed dir.
// This way we'll have the right path when we import
@ -44,18 +44,15 @@ crossPaths := false
// This forbids including Scala related libraries into the dependency
autoScalaLibrary := false
//publishTo := Some(Resolver.file("file", new File(Path.userHome.absolutePath+"/.m2/repository")))
publishTo := Some(Resolver.file("file", new File(Path.userHome.absolutePath+"/.m2/repository")))
publishTo := Some(Resolver.file("file", new File(Path.userHome.absolutePath+"/dev/repo/maven-repo/releases" )) )
publishTo := {
val nexus = "https://oss.sonatype.org/"
if (isSnapshot.value)
Some("snapshots" at nexus + "content/repositories/snapshots")
else
Some("releases" at nexus + "service/local/staging/deploy/maven2")
}
//publishTo := {
// val nexus = "https://oss.sonatype.org/"
// if (isSnapshot.value)
// Some("snapshots" at nexus + "content/repositories/snapshots")
// else
// Some("releases" at nexus + "service/local/staging/deploy/maven2")
//}
// Enables publishing to maven repo
@ -80,4 +77,4 @@ pomExtra := (
licenses := Seq("Apache License, Version 2.0" -> url("http://opensource.org/licenses/Apache-2.0"))
homepage := Some(url("http://www.bigbluebutton.org"))
homepage := Some(url("http://www.bigbluebutton.org"))

View File

@ -475,26 +475,21 @@ public class Client
System.out.println("##### Client member_add_file_data");
listener.conferenceEventPlayFile(uniqueId, confName, confSize, event);
return;
//API has changed between freeswitch 1.4 and 1.6
} else if (eventFunc.equals("conf_api_sub_transfer") || eventFunc.equals("conference_api_sub_transfer")) {
//Member transfered to another conf...
listener.conferenceEventTransfer(uniqueId, confName, confSize, event);
return;
//API has changed between freeswitch 1.4 and 1.6
} else if (eventFunc.equals("conference_add_member") || eventFunc.equals("conference_member_add")) {
System.out.println("##### Client conference_add_member");
listener.conferenceEventJoin(uniqueId, confName, confSize, event);
return;
//API has changed between freeswitch 1.4 and 1.6
} else if (eventFunc.equals("conference_del_member") || eventFunc.equals("conference_member_del")) {
System.out.println("##### Client conference_del_member");
listener.conferenceEventLeave(uniqueId, confName, confSize, event);
return;
//API has changed between freeswitch 1.4 and 1.6
} else if (eventFunc.equals("conf_api_sub_mute") || eventFunc.equals("conference_api_sub_mute")) {
listener.conferenceEventMute(uniqueId, confName, confSize, event);
return;
//API has changed between freeswitch 1.4 and 1.6
} else if (eventFunc.equals("conf_api_sub_unmute") || eventFunc.equals("conference_api_sub_unmute")) {
listener.conferenceEventUnMute(uniqueId, confName, confSize, event);
return;

View File

@ -179,7 +179,11 @@ public class VideoApplication extends MultiThreadedApplicationAdapter {
String meetingId = conn.getScope().getName();
String streamId = stream.getPublishedName();
publisher.userSharedWebcamMessage(meetingId, userId, streamId);
//publisher.userSharedWebcamMessage(meetingId, userId, streamId);
log.info("^^^^^^^^^^^publisher.userSharedWebcamMessage(meetingId, userId, streamId);");
VideoStreamListener listener = new VideoStreamListener(conn.getScope(), stream, recordVideoStream, userId, packetTimeout);
listener.setEventRecordingService(recordingService);
stream.addStreamListener(listener);
@ -212,7 +216,10 @@ public class VideoApplication extends MultiThreadedApplicationAdapter {
String meetingId = conn.getScope().getName();
String streamId = stream.getPublishedName();
publisher.userUnshareWebcamRequestMessage(meetingId, userId, streamId);
//publisher.userUnshareWebcamRequestMessage(meetingId, userId, streamId);
log.info("^^^^^^^^^^^publisher.userUnshareWebcamRequestMessage(meetingId, userId, streamId);");
IStreamListener listener = streamListeners.remove(scopeName + "-" + stream.getPublishedName());
if (listener != null) {

View File

@ -101,10 +101,10 @@ dependencies {
compile "redis.clients:jedis:2.7.2"
compile 'org.apache.commons:commons-pool2:2.3'
compile 'com.google.code.gson:gson:1.7.1'
providedCompile 'org.apache.commons:commons-lang3:3.2'
compile 'com.google.code.gson:gson:1.7.1'
providedCompile 'org.apache.commons:commons-lang3:3.2'
compile 'org.bigbluebutton:bbb-common-message:0.0.17-SNAPSHOT'
compile 'org.bigbluebutton:bbb-common-message:0.0.17'
}
test {

View File

@ -0,0 +1,85 @@
package org.bigbluebutton.red5.client;
import java.util.HashMap;
import java.util.Map;
import org.bigbluebutton.common.messages.ChatKeyUtil;
import org.bigbluebutton.common.messages.DeskShareNotifyViewersRTMPEventMessage;
import org.bigbluebutton.common.messages.DeskShareNotifyASingleViewerEventMessage;
import org.bigbluebutton.red5.client.messaging.BroadcastClientMessage;
import org.bigbluebutton.red5.client.messaging.DirectClientMessage;
import org.bigbluebutton.red5.client.messaging.ConnectionInvokerService;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
public class DeskShareMessageSender {
private ConnectionInvokerService service;
public DeskShareMessageSender(ConnectionInvokerService service) {
this.service = service;
}
public void handleDeskShareMessage(String message) {
JsonParser parser = new JsonParser();
JsonObject obj = (JsonObject) parser.parse(message);
if (obj.has("header") && obj.has("payload")) {
JsonObject header = (JsonObject) obj.get("header");
if (header.has("name")) {
String messageName = header.get("name").getAsString();
switch (messageName) {
case DeskShareNotifyViewersRTMPEventMessage.DESK_SHARE_NOTIFY_VIEWERS_RTMP:
DeskShareNotifyViewersRTMPEventMessage rtmp = DeskShareNotifyViewersRTMPEventMessage.fromJson(message);
if (rtmp != null) {
processDeskShareNotifyViewersRTMPEventMessage(rtmp);
}
break;
case DeskShareNotifyASingleViewerEventMessage.DESK_SHARE_NOTIFY_A_SINGLE_VIEWER:
DeskShareNotifyASingleViewerEventMessage singleViewerMsg = DeskShareNotifyASingleViewerEventMessage.fromJson(message);
if (singleViewerMsg != null) {
processDeskShareNotifyASingleViewerEventMessage(singleViewerMsg);
}
}
}
}
}
private void processDeskShareNotifyViewersRTMPEventMessage(DeskShareNotifyViewersRTMPEventMessage msg) {
Map<String, Object> messageInfo = new HashMap<String, Object>();
// split the string streamPath if there are params in the format:
// {channels=2,samplerate=48000,vw=1920,vh=1080,fps=5.00}rtmp://192.168.23.3/video-broadcast/.../..."
String fullPathString = msg.streamPath;
String delims = "[,{}]+";
String[] arr = fullPathString.split(delims);
String rtmpStreamPath = arr[arr.length -1];
messageInfo.put("rtmpUrl", rtmpStreamPath);
messageInfo.put("broadcasting", msg.broadcasting);
messageInfo.put("width", msg.vw);
messageInfo.put("height", msg.vh);
BroadcastClientMessage m = new BroadcastClientMessage(msg.meetingId, "DeskShareRTMPBroadcastNotification", messageInfo);
service.sendMessage(m);
}
private void processDeskShareNotifyASingleViewerEventMessage(DeskShareNotifyASingleViewerEventMessage msg) {
Map<String, Object> messageInfo = new HashMap<String, Object>();
messageInfo.put("rtmpUrl", msg.streamPath);
messageInfo.put("broadcasting", msg.broadcasting);
messageInfo.put("width", msg.vw);
messageInfo.put("height", msg.vh);
String toUserId = msg.userId;
DirectClientMessage receiver = new DirectClientMessage(msg.meetingId, toUserId,
"DeskShareRTMPBroadcastNotification", messageInfo);
service.sendMessage(receiver);
}
}

View File

@ -234,6 +234,10 @@ public class MessagePublisher {
sender.send(MessagingConstants.TO_CHAT_CHANNEL, msg.toJson());
}
public void requestDeskShareInfo(String meetingID, String requesterID, String replyTo) {
DeskShareGetInfoRequestMessage msg = new DeskShareGetInfoRequestMessage(meetingID, requesterID, replyTo);
sender.send(MessagingConstants.FROM_VOICE_CONF_SYSTEM_CHAN, msg.toJson());
}
public void sendWhiteboardAnnotation(String meetingID, String requesterID, Map<String, Object> annotation) {
SendWhiteboardAnnotationRequestMessage msg = new SendWhiteboardAnnotationRequestMessage(meetingID, requesterID, annotation);
sender.send(MessagingConstants.TO_WHITEBOARD_CHANNEL, msg.toJson());

View File

@ -7,6 +7,7 @@ import org.bigbluebutton.red5.client.PresentationClientMessageSender;
import org.bigbluebutton.red5.client.UserClientMessageSender;
import org.bigbluebutton.red5.client.ChatClientMessageSender;
import org.bigbluebutton.red5.client.WhiteboardClientMessageSender;
import org.bigbluebutton.red5.client.DeskShareMessageSender;
import org.bigbluebutton.red5.client.messaging.ConnectionInvokerService;
import org.bigbluebutton.red5.monitoring.BbbAppsIsKeepAliveHandler;
import org.red5.logging.Red5LoggerFactory;
@ -19,8 +20,9 @@ public class RedisPubSubMessageHandler implements MessageHandler {
private UserClientMessageSender userMessageSender;
private MeetingClientMessageSender meetingMessageSender;
private ChatClientMessageSender chatMessageSender;
private PresentationClientMessageSender presentationMessageSender;
private PresentationClientMessageSender presentationMessageSender;
private WhiteboardClientMessageSender whiteboardMessageSender;
private DeskShareMessageSender deskShareMessageSender;
private BbbAppsIsKeepAliveHandler bbbAppsIsKeepAliveHandler;
private PollingClientMessageSender pollingMessageSender;
@ -31,6 +33,7 @@ public class RedisPubSubMessageHandler implements MessageHandler {
chatMessageSender = new ChatClientMessageSender(service);
presentationMessageSender = new PresentationClientMessageSender(service);
whiteboardMessageSender = new WhiteboardClientMessageSender(service);
deskShareMessageSender = new DeskShareMessageSender(service);
pollingMessageSender = new PollingClientMessageSender(service);
}
@ -40,6 +43,7 @@ public class RedisPubSubMessageHandler implements MessageHandler {
@Override
public void handleMessage(String pattern, String channel, String message) {
// System.out.println("in red5 getting message: " + channel + " " + message);
if (channel.equalsIgnoreCase(MessagingConstants.FROM_CHAT_CHANNEL)) {
chatMessageSender.handleChatMessage(message);
} else if (channel.equalsIgnoreCase(MessagingConstants.FROM_PRESENTATION_CHANNEL)) {
@ -53,9 +57,11 @@ public class RedisPubSubMessageHandler implements MessageHandler {
whiteboardMessageSender.handleWhiteboardMessage(message);
} else if (channel.equalsIgnoreCase(MessagingConstants.FROM_SYSTEM_CHANNEL)) {
bbbAppsIsKeepAliveHandler.handleKeepAliveMessage(message);
} else if (channel.equalsIgnoreCase(MessagingConstants.FROM_DESK_SHARE_CHANNEL)) {
deskShareMessageSender.handleDeskShareMessage(message);
} else if (channel.equalsIgnoreCase(MessagingConstants.FROM_POLLING_CHANNEL)) {
pollingMessageSender.handlePollMessage(message);
}
}
}

View File

@ -0,0 +1,53 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2015 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.red5.service;
import java.util.HashMap;
import java.util.Map;
import org.bigbluebutton.red5.BigBlueButtonSession;
import org.bigbluebutton.red5.Constants;
import org.bigbluebutton.red5.pubsub.MessagePublisher;
import org.red5.logging.Red5LoggerFactory;
import org.red5.server.api.Red5;
import org.slf4j.Logger;
public class DesktopSharingService {
private static Logger log = Red5LoggerFactory.getLogger( DesktopSharingService.class, "bigbluebutton" );
private MessagePublisher red5BBBInGw;
private BigBlueButtonSession getBbbSession() {
return (BigBlueButtonSession) Red5.getConnectionLocal().getAttribute(Constants.SESSION);
}
public void setRed5Publisher(MessagePublisher inGW) {
red5BBBInGw = inGW;
}
public void requestDeskShareInfo() {
String meetingID = Red5.getConnectionLocal().getScope().getName();
String requesterID = getBbbSession().getInternalUserID();
// Just hardcode as we don't really need it for flash client.
String replyTo = meetingID + "/" + requesterID;
System.out.println("\nDESKTOP SHARING SERVICE _ on the way to bbb-core\n");
red5BBBInGw.requestDeskShareInfo(meetingID, requesterID, replyTo);
}
}

View File

@ -1,16 +0,0 @@
package org.bigbluebutton.red5.service;
public class WhiteboardKeyUtil {
public static final String TEXT_TYPE = "text";
public static final String PENCIL_TYPE = "pencil";
public static final String RECTANGLE_TYPE = "rectangle";
public static final String ELLIPSE_TYPE = "ellipse";
public static final String TRIANGLE_TYPE = "triangle";
public static final String LINE_TYPE = "line";
public static final String TEXT_CREATED_STATUS = "textCreated";
public static final String DRAW_START_STATUS = "DRAW_START";
public static final String DRAW_END_STATUS = "DRAW_END";
}

View File

@ -56,26 +56,30 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
<property name="red5Publisher"> <ref bean="red5Publisher"/> </property>
</bean>
<bean id="connInvokerService" class="org.bigbluebutton.red5.client.messaging.ConnectionInvokerService"
<bean id="connInvokerService" class="org.bigbluebutton.red5.client.messaging.ConnectionInvokerService"
init-method="start" destroy-method="stop"/>
<bean id="layout.service" class="org.bigbluebutton.red5.service.LayoutService">
<property name="red5Publisher"> <ref bean="red5Publisher"/></property>
</bean>
</bean>
<bean id="desktopSharing.service" class="org.bigbluebutton.red5.service.DesktopSharingService">
<property name="red5Publisher"><ref bean="red5Publisher"/></property>
</bean>
<bean id="lock.service" class="org.bigbluebutton.red5.service.LockService">
<property name="red5Publisher"><ref bean="red5Publisher"/></property>
</bean>
<bean id="chat.service" class="org.bigbluebutton.red5.service.ChatService">
<property name="red5Publisher"> <ref bean="red5Publisher"/></property>
<property name="maxMessageLength" value="1024"/>
</bean>
<bean id="participants.service" class="org.bigbluebutton.red5.service.ParticipantsService">
<property name="red5Publisher"> <ref bean="red5Publisher"/> </property>
</bean>
<bean id="presentationApplication" class="org.bigbluebutton.red5.service.PresentationApplication">
<property name="red5Publisher"><ref bean="red5Publisher"/></property>
</bean>

View File

@ -11,10 +11,10 @@
<property name="themeFile" value="BBBDefault.css"/>
<property name="blackTheme" value="BBBBlack.css"/>
<property name="LOCALE" value="en_US" />
<property name="RESOURCES_DIR" value="${BASE_DIR}/resources" />
<property name="PROD_RESOURCES_DIR" value="${RESOURCES_DIR}/prod" />
<property name="RESOURCES_DIR" value="${BASE_DIR}/resources" />
<property name="PROD_RESOURCES_DIR" value="${RESOURCES_DIR}/prod" />
<property name="SRC_DIR" value="${BASE_DIR}/src" />
<property name="OUTPUT_DIR" value="${BASE_DIR}/client" />
<taskdef resource="flexTasks.tasks" classpath="${FLEX_HOME}/ant/lib/flexTasks.jar" />
@ -37,31 +37,31 @@
<property name="POLLING" value="PollingModule" />
<property name="LAYOUT" value="LayoutModule" />
<property name="USERS" value="UsersModule" />
<xmlproperty file="${SRC_DIR}/conf/locales.xml" collapseAttributes="true"/>
<target name="init-ant-contrib">
<property name="ant-contrib.jar" location="${BASE_DIR}/build/lib/ant-contrib-0.6.jar"/>
<taskdef resource="net/sf/antcontrib/antcontrib.properties" classpath="${ant-contrib.jar}"/>
</target>
<target name="locales" depends="init-ant-contrib">
<echo message="Checking if locale output dir exists"/>
<available file="${OUTPUT_DIR}/locale" type="file" property="locale.dir.present"/>
<if>
<equals arg1="${locale.dir.present}" arg2="true"/>
<then>
<echo message="Locale output dir exists. Deleting contents of ${OUTPUT_DIR}/locale"/>
<if>
<equals arg1="${locale.dir.present}" arg2="true"/>
<then>
<echo message="Locale output dir exists. Deleting contents of ${OUTPUT_DIR}/locale"/>
<delete>
<fileset dir="${OUTPUT_DIR}/locale">
<include name="**/*"/>
</fileset>
</delete>
</then>
<else>
<echo message="Locale output dir does not exists. Creating ${OUTPUT_DIR}/locale"/>
</then>
<else>
<echo message="Locale output dir does not exists. Creating ${OUTPUT_DIR}/locale"/>
<mkdir dir="${OUTPUT_DIR}/locale"/>
</else>
</else>
</if>
<echo message="Determining supported locales."/>
@ -74,38 +74,38 @@
</path>
</foreach>
</target>
<target name="branding" depends="init-ant-contrib">
<sequential>
<mxmlc file="${BASE_DIR}/branding/default/style/css/${themeFile}"
output="${OUTPUT_DIR}/branding/css/${themeFile}.swf"
debug="${DEBUG}"
mxml.compatibility-version="3.0.0"
swf-version="13"
<mxmlc file="${BASE_DIR}/branding/default/style/css/${themeFile}"
output="${OUTPUT_DIR}/branding/css/${themeFile}.swf"
debug="${DEBUG}"
mxml.compatibility-version="3.0.0"
swf-version="13"
optimize="true">
</mxmlc>
</sequential>
</target>
<target name="branding-black" depends="init-ant-contrib">
<sequential>
<mxmlc file="${BASE_DIR}/branding/default/style/css/${blackTheme}"
output="${OUTPUT_DIR}/branding/css/${blackTheme}.swf"
debug="${DEBUG}"
mxml.compatibility-version="3.0.0"
swf-version="13"
<mxmlc file="${BASE_DIR}/branding/default/style/css/${blackTheme}"
output="${OUTPUT_DIR}/branding/css/${blackTheme}.swf"
debug="${DEBUG}"
mxml.compatibility-version="3.0.0"
swf-version="13"
optimize="true">
</mxmlc>
</sequential>
</target>
<target name="build-bbb-main-test" description="Compile BigBlueButton Main Test">
<build-main src="${SRC_DIR}" target="${BBB_MAIN_TEST}" />
</target>
<target name="build-locale">
<echo message="Locale dir is ${supportedlocale}. Extract locale name." />
<basename property="locale.name" file="${supportedlocale}"/>
<basename property="locale.name" file="${supportedlocale}"/>
<echo message="Locale name is ${locale.name}"/>
<sequential>
@ -115,33 +115,33 @@
<equals arg1="${locale.name}" arg2="locale"/>
<then>
<echo message="Somehow, the dirset for locales is passing the parent (${locale.name}) dir."/>
<echo message="We don't want it, so we need to skip it."/>
<echo message="We don't want it, so we need to skip it."/>
</then>
<else>
<if>
<equals arg1="${locale.dir.present}" arg2="true"/>
<then>
<else>
<if>
<equals arg1="${locale.dir.present}" arg2="true"/>
<then>
<echo message="We already have a copy of the framework locale. No need to copy ${LOCALE_DIR}/${locale.name}"/>
</then>
<else>
<echo message="No copy of the framework locale. Copying ${LOCALE_DIR}/${locale.name}"/>
</then>
<else>
<echo message="No copy of the framework locale. Copying ${LOCALE_DIR}/${locale.name}"/>
<exec vmlauncher="true" executable="copylocale">
<arg value="en_US"/>
<arg value="${locale.name}"/>
</exec>
</exec>
</else>
</if>
</if>
<echo message="Compiling locale ${locale.name}"/>
<compileLocale locale="${locale.name}" />
</else>
</if>
<compileLocale locale="${locale.name}" />
</else>
</if>
</sequential>
</target>
<target name="localize">
<compileLocale locale="${LOCALE}" />
<compileLocale locale="${LOCALE}" />
</target>
<macrodef name="compileLocale" description="Compiles the Resource package for the given locale">
<attribute name="locale" default="en_US"/>
<sequential>
@ -149,13 +149,13 @@
<echo message="**********************************************"/>
<echo message="* Did you check bundles.txt and made *"/>
<echo message="* all resources listed here? *"/>
<echo message="**********************************************"/>
<echo message="**********************************************"/>
<!-- Invoke MXMLC -->
<mxmlc output="${OUTPUT_DIR}/locale/@{locale}_resources.swf">
<locale>@{locale}</locale>
<target-player>11</target-player>
<source-path path-element="locale/{locale}"/>
<!--
Look into bundles.txt to find out what resources to include here.
http://forums.adobe.com/thread/758619
@ -163,47 +163,47 @@
-->
<include-resource-bundles>SharedResources</include-resource-bundles>
<include-resource-bundles>bbbResources</include-resource-bundles>
<include-resource-bundles>collections</include-resource-bundles>
<include-resource-bundles>containers</include-resource-bundles>
<include-resource-bundles>controls</include-resource-bundles>
<include-resource-bundles>core</include-resource-bundles>
<include-resource-bundles>effects</include-resource-bundles>
<include-resource-bundles>logging</include-resource-bundles>
<include-resource-bundles>messaging</include-resource-bundles>
<include-resource-bundles>modules</include-resource-bundles>
<include-resource-bundles>rpc</include-resource-bundles>
<include-resource-bundles>skins</include-resource-bundles>
<include-resource-bundles>collections</include-resource-bundles>
<include-resource-bundles>containers</include-resource-bundles>
<include-resource-bundles>controls</include-resource-bundles>
<include-resource-bundles>core</include-resource-bundles>
<include-resource-bundles>effects</include-resource-bundles>
<include-resource-bundles>logging</include-resource-bundles>
<include-resource-bundles>messaging</include-resource-bundles>
<include-resource-bundles>modules</include-resource-bundles>
<include-resource-bundles>rpc</include-resource-bundles>
<include-resource-bundles>skins</include-resource-bundles>
<include-resource-bundles>styles</include-resource-bundles>
<source-path path-element="${FLEX_HOME}/frameworks"/>
</mxmlc>
</sequential>
</macrodef>
<target name="build-bbb-main" description="Compile BigBlueButton Main">
<build-main src="${SRC_DIR}" target="${BBB_MAIN}" />
<echo message="Copying common assets for BBB Main" />
<copy todir="${OUTPUT_DIR}/org/bigbluebutton/common/assets/images" >
<fileset dir="${BASE_DIR}/src/org/bigbluebutton/common/assets/images/" />
</copy>
</copy>
</target>
<target name="build-broadcast" description="Compile Broadcast Module" >
<build-module src="${SRC_DIR}" target="${BROADCAST}" />
</target>
<target name="build-chat" description="Compile Chat Module">
<build-module src="${SRC_DIR}" target="${CHAT}" />
</target>
<target name="build-notes" description="Compile Notes Module">
<build-module src="${SRC_DIR}" target="${NOTES}" />
</target>
<target name="build-polling" description="Compile Polling Module">
<build-module src="${SRC_DIR}" target="${POLLING}" />
</target>
<target name="build-present" description="Compile Present Module">
<build-module src="${SRC_DIR}" target="${PRESENT}" />
</target>
@ -212,12 +212,12 @@
<echo message="Compiling deskshare standalone without optimization." />
<build-module-no-link src="${SRC_DIR}" target="${DESKSHARE_SA}" />
</target>
<target name="build-deskshare-no-linker" description="Compile Deskshare Module without the linker">
<echo message="Compiling deskshare without optimization." />
<build-module-no-link src="${SRC_DIR}" target="${DESKSHARE}" />
<build-module-no-link src="${SRC_DIR}" target="${DESKSHARE}" />
</target>
<target name="build-webcam-preview-standalone" description="Compile Webcam Preview Standalone Application">
<echo message="Compiling webcam preview standalone without optimization." />
<build-module-no-link src="${SRC_DIR}" target="${CAM_PREVIEW_SA}" />
@ -237,28 +237,28 @@
<echo message="Compiling Camera Check Application." />
<build-module-no-link src="${SRC_DIR}" target="${CAMERA_CHECK}" />
</target>
<target name="build-conn-check" description="Compile Connection Check Application">
<echo message="Compiling Connection Check Application." />
<build-module-no-link src="${SRC_DIR}" target="${CONNECTION_CHECK}" />
</target>
<target name="build-deskshare" description="Compile Deskshare Module">
<build-module src="${SRC_DIR}" target="${DESKSHARE}" />
<echo message="Copying deskshare applet for Deskshare Module" />
<copy file="${PROD_RESOURCES_DIR}/bbb-deskshare-applet-unsigned-0.9.0.jar" todir="${OUTPUT_DIR}"/>
<copy file="${PROD_RESOURCES_DIR}/bbb-deskshare-applet-0.9.0.jar" todir="${OUTPUT_DIR}"/>
<copy file="${PROD_RESOURCES_DIR}/bbb-deskshare-applet-unsigned-0.9.0.jar" todir="${OUTPUT_DIR}"/>
<copy file="${PROD_RESOURCES_DIR}/bbb-deskshare-applet-0.9.0.jar" todir="${OUTPUT_DIR}"/>
</target>
<target name="build-phone" description="Compile Phone Module">
<build-module src="${SRC_DIR}" target="${PHONE}" />
<echo message="Copying assets for Phone Module" />
<copy todir="${OUTPUT_DIR}/org/bigbluebutton/modules/phone/views/assets/images/" >
<fileset dir="${BASE_DIR}/src/org/bigbluebutton/modules/phone/views/assets/images/" />
</copy>
</copy>
</target>
<target name="build-video" description="Compile Video Module">
<build-module src="${SRC_DIR}" target="${VIDEO}" />
</target>
@ -266,27 +266,27 @@
<target name="build-whiteboard" description="Compile Whiteboard Module">
<build-module src="${SRC_DIR}" target="${WHITEBOARD}" />
</target>
<target name="build-layout" description="Compile Layout Module">
<build-module src="${SRC_DIR}" target="${LAYOUT}" />
</target>
<target name="build-users" description="Compile Users Module">
<build-module src="${SRC_DIR}" target="${USERS}" />
</target>
<!-- just a grouping of modules to compile -->
<target name="build-main-chat-present"
<target name="build-main-chat-present"
depends="build-bbb-main, build-chat, build-present, build-layout, build-broadcast, build-users"
description="Compile main, chat, present modules">
</target>
<!-- just a grouping of modules to compile -->
<target name="build-deskshare-phone-video-whiteboard-dyn"
<target name="build-deskshare-phone-video-whiteboard-dyn"
depends="build-deskshare, build-phone, build-video, build-whiteboard, build-notes, build-polling"
description="Compile deskshare, phone, video, whiteboard modules">
</target>
<macrodef name="build-main">
<attribute name="target" description="Module to compile" />
<attribute name="flex" default="${FLEX_HOME}" description="Location of the Flex install." />
@ -297,7 +297,7 @@
<target-player>11</target-player>
<load-config filename="@{flex}/frameworks/flex-config.xml" />
<source-path path-element="@{flex}/frameworks" />
<!--
Dump out resources to find out what resources to include building the locales.
http://forums.adobe.com/thread/758619
@ -305,7 +305,7 @@
-->
<resource-bundle-list>bundles.txt</resource-bundle-list>
<static-link-runtime-shared-libraries>${STATIC_RSL}</static-link-runtime-shared-libraries>
<compiler.library-path dir="@{flex}/frameworks" append="true">
<include name="libs" />
<include name="../bundles/{locale}" />
@ -325,7 +325,7 @@
<echo message="**********************************************"/>
</sequential>
</macrodef>
<macrodef name="build-module-no-link">
<attribute name="target" description="Module to compile" />
<attribute name="flex" default="${FLEX_HOME}" description="Location of the Flex install." />
@ -352,7 +352,7 @@
</mxmlc>
</sequential>
</macrodef>
<macrodef name="build-module">
<attribute name="target" description="Module to compile" />
<attribute name="flex" default="${FLEX_HOME}" description="Location of the Flex install." />
@ -381,12 +381,12 @@
</macrodef>
<target name="compile-deskshare-standalone" depends="build-deskshare-standalone"
description="Compiling standalone desktop sharing">
description="Compiling standalone desktop sharing">
<echo message="Deskshare standalone built without optimization." />
</target>
<target name="compile-bbb" depends="build-main-chat-present, build-deskshare-phone-video-whiteboard-dyn, copy-resource-files"
description="Compiling the BBB without copying config.xml">
description="Compiling the BBB without copying config.xml">
</target>
<target name="copy-config-if-needed">
@ -396,7 +396,7 @@
<copy file="/var/www/bigbluebutton/client/conf/config.xml" todir="${BASE_DIR}/src/conf" />
</else>
</if>
</target>
</target>
<target name="copy-join-mock-if-needed">
<if>
@ -405,23 +405,23 @@
<copy file="${RESOURCES_DIR}/dev/join-mock.xml" todir="${BASE_DIR}/src/conf" />
</else>
</if>
</target>
</target>
<target name="copy-resource-files" >
<copy todir="${OUTPUT_DIR}/swfobject/" >
<fileset dir="${PROD_RESOURCES_DIR}/swfobject" />
</copy>
</copy>
<copy todir="${OUTPUT_DIR}/lib/" >
<fileset dir="${PROD_RESOURCES_DIR}/lib"/>
</copy>
</copy>
<copy file="${PROD_RESOURCES_DIR}/BigBlueButtonTest.html" todir="${OUTPUT_DIR}" overwrite="true"/>
<copy file="${PROD_RESOURCES_DIR}/BigBlueButton.html" todir="${OUTPUT_DIR}" overwrite="true"/>
<copy file="${PROD_RESOURCES_DIR}/DeskshareStandalone.html" todir="${OUTPUT_DIR}" overwrite="true"/>
<copy file="${PROD_RESOURCES_DIR}/bbb.gif" todir="${OUTPUT_DIR}" overwrite="true"/>
<copy file="${PROD_RESOURCES_DIR}/get_flash_player.gif" todir="${OUTPUT_DIR}" overwrite="true"/>
<copy file="${PROD_RESOURCES_DIR}/get_flash_player.gif" todir="${OUTPUT_DIR}" overwrite="true"/>
<copy file="${PROD_RESOURCES_DIR}/bbb.gif" todir="${OUTPUT_DIR}" overwrite="true"/>
<copy file="${PROD_RESOURCES_DIR}/avatar.png" todir="${OUTPUT_DIR}" overwrite="true"/>
<copy file="${PROD_RESOURCES_DIR}/locales.xml" todir="${OUTPUT_DIR}/conf" overwrite="true"/>
<copy file="${PROD_RESOURCES_DIR}/expressInstall.swf" todir="${OUTPUT_DIR}" overwrite="true"/>
<copy file="${PROD_RESOURCES_DIR}/locales.xml" todir="${OUTPUT_DIR}/conf" overwrite="true"/>
<copy file="${PROD_RESOURCES_DIR}/expressInstall.swf" todir="${OUTPUT_DIR}" overwrite="true"/>
<copy file="${PROD_RESOURCES_DIR}/example-info-data.xml" todir="${OUTPUT_DIR}/conf" overwrite="true"/>
<copy file="${PROD_RESOURCES_DIR}/layout.xml" todir="${OUTPUT_DIR}/conf" overwrite="true"/>
<copy file="${PROD_RESOURCES_DIR}/profiles.xml" todir="${OUTPUT_DIR}/conf" overwrite="true"/>
@ -429,17 +429,17 @@
<equals arg1="${BUILD_ENV}" arg2="DEV"/>
<then>
<echo message="Copying config.xml for development environment"/>
<copy file="${BASE_DIR}/src/conf/config.xml" todir="${OUTPUT_DIR}/conf" />
<copy file="${BASE_DIR}/src/conf/config.xml" todir="${OUTPUT_DIR}/conf" />
<!-- echo message="Copying layout.xml for development environment"/>
<copy file="${BASE_DIR}/src/conf/layout.xml" todir="${OUTPUT_DIR}/conf" /-->
<copy file="${BASE_DIR}/src/conf/layout.xml" todir="${OUTPUT_DIR}/conf" /-->
</then>
<else>
<echo message="Need to copy config.xml.template for production environment"/>
<copy file="${RESOURCES_DIR}/config.xml.template" todir="${OUTPUT_DIR}/conf" overwrite="true"/>
<copy file="${RESOURCES_DIR}/config.xml.template" todir="${OUTPUT_DIR}/conf" overwrite="true"/>
</else>
</if>
</target>
<target name="generate-html-wrapper">
<html-wrapper
@ -464,9 +464,9 @@
<mkdir dir="${BASE_DIR}/asdoc" />
<!-- asdoc task not natively supported for ant flexTasks.jar for flex3. It is supported for flex 4, so it should be enabled here
when bbb-client is moved to Flex 4 -->
<!--<asdoc output="${BASE_DIR}/asdoc"
external-library-path="{BASE_DIR}/libs"
lenient="true"
<!--<asdoc output="${BASE_DIR}/asdoc"
external-library-path="{BASE_DIR}/libs"
lenient="true"
failonerror="true"
source-path="${SRC_DIR}"
doc-sources="${SRC_DIR}"
@ -504,18 +504,16 @@
</if>
</target>
<!-- NOTE: compile-deskshare-standalone MUST come first before compile-bbb as we need the deskshare-standalone
to be compiled withouth being optimized by using the linker -->
<target name="clean-build-bbb" depends="clean, init-ant-contrib, generate-html-wrapper, compile-deskshare-standalone,
build-webcam-preview-standalone, build-webcam-view-standalone, compile-bbb"
description="Build BBB client skipping compiling of locales"/>
<target name="clean-build-bbb" depends="clean, init-ant-contrib, generate-html-wrapper, compile-deskshare-standalone,
build-webcam-preview-standalone, build-webcam-view-standalone, compile-bbb"
description="Build BBB client skipping compiling of locales"/>
<target name="clean-build-all" depends="clean, init-ant-contrib, generate-html-wrapper, compile-deskshare-standalone, build-mic-check,
build-cam-check, build-conn-check, build-webcam-preview-standalone, build-webcam-view-standalone,
compile-bbb, branding, branding-black"
build-cam-check, build-conn-check, build-webcam-preview-standalone, build-webcam-view-standalone,
compile-bbb, branding, branding-black"
description="Build BBB client including locales"/>
<target name="modules" depends="init-ant-contrib, generate-html-wrapper, compile-deskshare-standalone,
<target name="modules" depends="init-ant-contrib, generate-html-wrapper, compile-deskshare-standalone,
build-webcam-preview-standalone, build-webcam-view-standalone, compile-bbb"
description="Build BBB client without locales"/>
description="Build BBB client without locales"/>
<target name="cleanandmake" depends="clean-build-all" description="Build BBB client including locales"/>
<target name="build-poll" depends="init-ant-contrib, build-polling" description="Build only the polling module." />

View File

@ -41,7 +41,6 @@
baseTabIndex="301"
/>
<module name="DeskShareModule"
url="http://HOST/client/DeskShareModule.swf?v=VERSION"
uri="rtmp://HOST/deskShare"
@ -51,10 +50,16 @@
autoStart="false"
autoFullScreen="false"
baseTabIndex="201"
useWebRTCIfAvailable="false"
chromeExtensionKey=""
vertoPort="PORT"
hostName="HOST.NAME"
login="LOGIN"
password="PASSWORD"
/>
<module name="PhoneModule" url="http://HOST/client/PhoneModule.swf?v=VERSION"
uri="rtmp://HOST/sip"
<module name="PhoneModule" url="http://HOST/client/PhoneModule.swf?v=VERSION"
uri="rtmp://HOST/sip"
autoJoin="true"
listenOnlyMode="true"
presenterShareOnly="false"

View File

@ -14,10 +14,28 @@
swfobject.registerObject("WebcamPreviewStandalone", "11", "expressInstall.swf");
swfobject.registerObject("WebcamViewStandalone", "11", "expressInstall.swf");
</script>
<script src="http://192.168.0.249/client/lib/jquery-1.5.1.min.js" language="javascript"></script>
<!--<script src="http://192.168.0.249/client/lib/jquery-1.5.1.min.js" language="javascript"></script>-->
<script src="http://192.168.0.249/client/lib/bigbluebutton.js" language="javascript"></script>
<script src="http://192.168.0.249/client/lib/bbb_localization.js" language="javascript"></script>
<script src="http://192.168.0.249/client/lib/bbb_blinker.js" language="javascript"></script>
<script src="http://192.168.0.249/client/lib/jquery.mobile.min.js" language="javascript"></script>
<script src="http://192.168.0.249/client/lib/jquery.json-2.4.min.js" language="javascript"></script>
<!-- <script src="http://192.168.0.249/client/lib/getScreenId.js" language="javascript"></script> -->
<script src="http://192.168.0.249/client/lib/jquery.cookie.js" language="javascript"></script>
<script src="http://192.168.0.249/client/lib/jquery.dataTables.min.js" language="javascript"></script>
<script src="lib/getScreenId.js" language="javascript"></script>
<script src="lib/jquery.FSRTC.js" language="javascript"></script>
<script src="lib/jquery.jsonrpcclient.js" language="javascript"></script>
<script src="lib/jquery.verto.js" language="javascript"></script>
<script src="lib/Screen-Capturing.js" language="javascript"></script>
<script src="lib/verto_extension.js" language="javascript"></script>
<script src="lib/verto_extension_share.js" language="javascript"></script>
<!-- <script src="http://192.168.0.249/client/lib/verto_extension.js" language="javascript"></script> -->
<!-- <script src="http://192.168.0.249/client/lib/verto_extension_share.js" language="javascript"></script> -->
<script src="http://192.168.0.249/client/lib/bbb_deskshare.js" language="javascript"></script>
<script type="text/javascript" src="http://192.168.0.249/client/lib/bbb_api_bridge.js"></script>
<script type="text/javascript" src="http://192.168.0.249/client/lib/bbb_api_cam_preview.js"></script>

View File

@ -17,12 +17,12 @@
width: 1px !important;
overflow: hidden;
}
#deployJavaPlugin {
display : none;
}
</style>
<script type="text/javascript" src="swfobject/swfobject.js"></script>
<script src="lib/deployJava.js?v=VERSION" language="javascript"></script>
<script type="text/javascript">
@ -63,7 +63,7 @@
attributes.align = "middle";
attributes.tabIndex = 0;
swfobject.embedSWF("BigBlueButton.swf?v=VERSION", "altFlash", "100%", "100%", "11.0.0", "expressInstall.swf", flashvars, params, attributes, embedCallback);
function embedCallback(e) {
// Work around pixel alignment bug with Chrome 21 on Mac.
// See: http://code.google.com/p/bigbluebutton/issues/detail?id=1294
@ -86,17 +86,32 @@
});
}
</script>
<script src="lib/jquery-1.5.1.min.js?v=VERSION" language="javascript"></script>
<!--<script src="lib/jquery-1.5.1.min.js?v=VERSION" language="javascript"></script>-->
<script src="lib/jquery-2.1.1.min.js" language="javascript"></script>
<script src="lib/bbblogger.js?v=VERSION" language="javascript"></script>
<script src="lib/bigbluebutton.js?v=VERSION" language="javascript"></script>
<script src="lib/bbb_localization.js?v=VERSION" language="javascript"></script>
<script src="lib/bbb_blinker.js?v=VERSION" language="javascript"></script>
<!--<script src="lib/jquery.mobile.min.js" language="javascript"></script>-->
<script src="lib/jquery.json-2.4.min.js" language="javascript"></script>
<script src="lib/jquery.cookie.js" language="javascript"></script>
<!--<script src="lib/jquery.dataTables.min.js" language="javascript"></script>-->
<script src="lib/getScreenId.js" language="javascript"></script>
<script src="lib/jquery.FSRTC.js" language="javascript"></script>
<script src="lib/jquery.jsonrpcclient.js" language="javascript"></script>
<script src="lib/jquery.verto.js" language="javascript"></script>
<script src="lib/Screen-Capturing.js" language="javascript"></script>
<script src="lib/verto_extension.js" language="javascript"></script>
<script src="lib/verto_extension_share.js" language="javascript"></script>
<script src="lib/bbb_deskshare.js?v=VERSION" language="javascript"></script>
<script src="lib/bbb_api_bridge.js?v=VERSION" language="javascript"></script>
<script src="lib/sip.js?v=VERSION" language="javascript"></script>
<script src="lib/sip.js?v=VERSION" language="javascript"></script>
<script src="lib/bbb_webrtc_bridge_sip.js?v=VERSION" language="javascript"></script>
<script src="lib/weburl_regex.js?v=VERSION" language="javascript"></script>
<script src="lib/jsnlog.min.js?v=VERSION" language="javascript"></script>
<script src="lib/weburl_regex.js?v=VERSION" language="javascript"></script>
<script src="lib/jsnlog.min.js?v=VERSION" language="javascript"></script>
<script>
window.chatLinkClicked = function(url) {
window.open(url, '_blank');
@ -153,6 +168,9 @@
<body>
<div>
<audio id="remote-media" autoplay="autoplay"></audio>
<video id="localVertoVideo" autoplay="autoplay" style="display: none;">
<p>Your browser doesn't support HTML5 video.</p>
</video>
</div>
<div id="accessibile-progress" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" class="visually-hidden">0 %</div>
<button id="enterFlash" type="button" class="visually-hidden" onclick="startFlashFocus();">Set focus to client</button>

View File

@ -12,10 +12,25 @@
<script type="text/javascript">
swfobject.registerObject("BigBlueButton", "10.3.0", "expressInstall.swf");
</script>
<script src="lib/jquery-1.5.1.min.js" language="javascript"></script>
<script src="lib/jquery-2.1.1.min.js" language="javascript"></script>
<!--<script src="lib/jquery-1.5.1.min.js" language="javascript"></script>-->
<script src="lib/bigbluebutton.js" language="javascript"></script>
<script src="lib/bbb_localization.js" language="javascript"></script>
<script src="lib/bbb_blinker.js" language="javascript"></script>
<script src="lib/jquery.mobile.min.js" language="javascript"></script>
<script src="lib/jquery.json-2.4.min.js" language="javascript"></script>
<script src="lib/jquery.cookie.js" language="javascript"></script>
<script src="lib/jquery.dataTables.min.js" language="javascript"></script>
<script src="lib/getScreenId.js" language="javascript"></script>
<script src="lib/jquery.FSRTC.js" language="javascript"></script>
<script src="lib/jquery.jsonrpcclient.js" language="javascript"></script>
<script src="lib/jquery.verto.js" language="javascript"></script>
<script src="lib/Screen-Capturing.js" language="javascript"></script>
<script src="lib/verto_extension.js" language="javascript"></script>
<script src="lib/verto_extension_share.js" language="javascript"></script>
<script src="lib/bbb_deskshare.js" language="javascript"></script>
</head>
<body>

View File

@ -0,0 +1,159 @@
// Last time updated at January 07, 2016
// By Daniel Perrone (perroned)
// Latest file can be found here: https://cdn.webrtc-experiment.com/Screen-Capturing.js
// Muaz Khan - www.MuazKhan.com
// MIT License - www.WebRTC-Experiment.com/licence
// Documentation - https://github.com/muaz-khan/Chrome-Extensions/tree/master/Screen-Capturing.js
// Demo - https://www.webrtc-experiment.com/Screen-Capturing/
// ___________________
// Screen-Capturing.js
// Source code: https://github.com/muaz-khan/Chrome-Extensions/tree/master/desktopCapture
// Google AppStore installation path: https://chrome.google.com/webstore/detail/screen-capturing/ajhifddimkapgcifgcodmmfdlknahffk
// This JavaScript file is aimed to explain steps needed to integrate above chrome extension
// in your own webpages
// Usage:
// getScreenConstraints(function(screen_constraints) {
// navigator.webkitGetUserMedia({ video: screen_constraints }, onSuccess, onFailure );
// });
// First Step: Download the extension, modify "manifest.json" and publish to Google AppStore
// https://github.com/muaz-khan/Chrome-Extensions/tree/master/desktopCapture#how-to-publish-yourself
// Second Step: Listen for postMessage handler
// postMessage is used to exchange "sourceId" between chrome extension and you webpage.
// though, there are tons other options as well, e.g. XHR-signaling, websockets, etc.
window.addEventListener('message', function(event) {
if (event.origin != window.location.origin) {
return;
}
onMessageCallback(event.data);
});
// and the function that handles received messages
function onMessageCallback(data) {
// "cancel" button is clicked
if (data == 'PermissionDeniedError') {
chromeMediaSource = 'PermissionDeniedError';
if (screenCallback) return screenCallback('PermissionDeniedError');
else throw new Error('PermissionDeniedError');
}
// extension notified his presence
if (data == 'rtcmulticonnection-extension-loaded') {
chromeMediaSource = 'desktop';
}
// extension shared temp sourceId
if (data.sourceId && screenCallback) {
screenCallback(sourceId = data.sourceId);
}
}
// global variables
var chromeMediaSource = 'screen';
var sourceId;
var screenCallback;
// this method can be used to check if chrome extension is installed & enabled.
function isChromeExtensionAvailable(callback) {
if (!callback) return;
if (chromeMediaSource == 'desktop') return callback(true);
// ask extension if it is available
window.postMessage('are-you-there', '*');
setTimeout(function() {
if (chromeMediaSource == 'screen') {
callback(false);
} else callback(true);
}, 2000);
}
// this function can be used to get "source-id" from the extension
function getSourceId(callback) {
if (!callback) throw '"callback" parameter is mandatory.';
if(sourceId) return callback(sourceId);
screenCallback = callback;
window.postMessage('get-sourceId', '*');
}
var isFirefox = typeof window.InstallTrigger !== 'undefined';
var isOpera = !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0;
var isChrome = !!window.chrome && !isOpera;
function getChromeExtensionStatus(extensionid, callback) {
callback = normalizeCallback(callback);
if (isFirefox) return callback('not-chrome');
if (chromeMediaSource == 'desktop') return callback('installed-enabled');
if (arguments.length != 2) {
callback = extensionid;
extensionid = 'ajhifddimkapgcifgcodmmfdlknahffk'; // Muaz Khan's Screen Capturing
}
var image = document.createElement('img');
image.src = 'chrome-extension://' + extensionid + '/icon.png';
image.onload = function() {
chromeMediaSource = 'screen';
window.postMessage('are-you-there', '*');
setTimeout(function() {
callback('installed-enabled');
}, 2000);
};
image.onerror = function() {
callback('not-installed');
};
}
// this function explains how to use above methods/objects
function getScreenConstraints(callback) {
var firefoxScreenConstraints = {
mozMediaSource: 'window',
mediaSource: 'window'
};
if(isFirefox) return callback(null, firefoxScreenConstraints);
// this statement defines getUserMedia constraints
// that will be used to capture content of screen
var screen_constraints = {
mandatory: {
chromeMediaSource: chromeMediaSource,
maxWidth: screen.width > 1920 ? screen.width : 1920,
maxHeight: screen.height > 1080 ? screen.height : 1080
},
optional: []
};
// this statement verifies chrome extension availability
// if installed and available then it will invoke extension API
// otherwise it will fallback to command-line based screen capturing API
sourceId = null;
if (chromeMediaSource == 'desktop') {
getSourceId(function() {
screen_constraints.mandatory.chromeMediaSourceId = sourceId;
callback(sourceId == 'PermissionDeniedError' ? sourceId : null, screen_constraints);
});
return;
}
// this statement sets gets 'sourceId" and sets "chromeMediaSourceId"
if (chromeMediaSource == 'desktop') {
screen_constraints.mandatory.chromeMediaSourceId = sourceId;
}
// now invoking native getUserMedia API
callback(null, screen_constraints);
}

View File

@ -0,0 +1,103 @@
// Last time updated at Sep 07, 2014, 08:32:23
// Latest file can be found here: https://cdn.webrtc-experiment.com/getScreenId.js
// Muaz Khan - www.MuazKhan.com
// MIT License - www.WebRTC-Experiment.com/licence
// Documentation - https://github.com/muaz-khan/WebRTC-Experiment/tree/master/getScreenId.js
// ______________
// getScreenId.js
/*
getScreenId(function (error, sourceId, screen_constraints) {
// error == null || 'permission-denied' || 'not-installed' || 'installed-disabled' || 'not-chrome'
// sourceId == null || 'string' || 'firefox'
if(sourceId == 'firefox') {
navigator.mozGetUserMedia(screen_constraints, onSuccess, onFailure);
}
else navigator.webkitGetUserMedia(screen_constraints, onSuccess, onFailure);
});
*/
(function() {
window.getScreenId = function(callback) {
// for Firefox:
// sourceId == 'firefox'
// screen_constraints = {...}
if (!!navigator.mozGetUserMedia) {
callback(null, 'firefox', {
video: {
mozMediaSource: 'window',
mediaSource: 'window'
}
});
return;
}
postMessage();
window.addEventListener('message', onIFrameCallback);
function onIFrameCallback(event) {
if (!event.data) return;
if (event.data.chromeMediaSourceId) {
if (event.data.chromeMediaSourceId === 'PermissionDeniedError') {
callback('permission-denied');
} else callback(null, event.data.chromeMediaSourceId, getScreenConstraints(null, event.data.chromeMediaSourceId));
}
if (event.data.chromeExtensionStatus) {
callback(event.data.chromeExtensionStatus, null, getScreenConstraints(event.data.chromeExtensionStatus));
}
// this event listener is no more needed
window.removeEventListener('message', onIFrameCallback);
}
};
function getScreenConstraints(error, sourceId) {
var screen_constraints = {
audio: false,
video: {
mandatory: {
chromeMediaSource: error ? 'screen' : 'desktop',
maxWidth: window.screen.width > 1920 ? window.screen.width : 1920,
maxHeight: window.screen.height > 1080 ? window.screen.height : 1080
},
optional: []
}
};
console.log("____in getScreenConstraints and error=" + error + " and sourceId=" + sourceId +
" and chromeMediaSource = " + screen_constraints.video.mandatory.chromeMediaSource);
if (sourceId) {
screen_constraints.video.mandatory.chromeMediaSourceId = sourceId;
}
return screen_constraints;
}
function postMessage() {
console.log("___in postMessage and iframe isLoaded=" + iframe.isLoaded);
if (!iframe.isLoaded) {
setTimeout(postMessage, 100);
return;
}
iframe.contentWindow.postMessage({
captureSourceId: true
}, '*');
}
var iframe = document.createElement('iframe');
iframe.onload = function() {
iframe.isLoaded = true;
};
iframe.src = 'https://www.webrtc-experiment.com/getSourceId/';
iframe.style.display = 'none';
(document.body || document.documentElement).appendChild(iframe);
})();

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,90 @@
/*!
* jQuery Cookie Plugin v1.3.1
* https://github.com/carhartl/jquery-cookie
*
* Copyright 2013 Klaus Hartl
* Released under the MIT license
*/
(function ($, document, undefined) {
var pluses = /\+/g;
function raw(s) {
return s;
}
function decoded(s) {
return unRfc2068(decodeURIComponent(s.replace(pluses, ' ')));
}
function unRfc2068(value) {
if (value.indexOf('"') === 0) {
// This is a quoted cookie as according to RFC2068, unescape
value = value.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, '\\');
}
return value;
}
function fromJSON(value) {
return config.json ? JSON.parse(value) : value;
}
var config = $.cookie = function (key, value, options) {
// write
if (value !== undefined) {
options = $.extend({}, config.defaults, options);
if (value === null) {
options.expires = -1;
}
if (typeof options.expires === 'number') {
var days = options.expires, t = options.expires = new Date();
t.setDate(t.getDate() + days);
}
value = config.json ? JSON.stringify(value) : String(value);
return (document.cookie = [
encodeURIComponent(key), '=', config.raw ? value : encodeURIComponent(value),
options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE
options.path ? '; path=' + options.path : '',
options.domain ? '; domain=' + options.domain : '',
options.secure ? '; secure' : ''
].join(''));
}
// read
var decode = config.raw ? raw : decoded;
var cookies = document.cookie.split('; ');
var result = key ? null : {};
for (var i = 0, l = cookies.length; i < l; i++) {
var parts = cookies[i].split('=');
var name = decode(parts.shift());
var cookie = decode(parts.join('='));
if (key && key === name) {
result = fromJSON(cookie);
break;
}
if (!key) {
result[name] = fromJSON(cookie);
}
}
return result;
};
config.defaults = {};
$.removeCookie = function (key, options) {
if ($.cookie(key) !== null) {
$.cookie(key, null, options);
return true;
}
return false;
};
})(jQuery, document);

View File

@ -0,0 +1,155 @@
/*! DataTables 1.10.1
* ©2008-2014 SpryMedia Ltd - datatables.net/license
*/
(function(za,O,l){var N=function(h){function T(a){var b,c,d={};h.each(a,function(e){if((b=e.match(/^([^A-Z]+?)([A-Z])/))&&-1!=="a aa ai ao as b fn i m o s ".indexOf(b[1]+" "))c=e.replace(b[0],b[2].toLowerCase()),d[c]=e,"o"===b[1]&&T(a[e])});a._hungarianMap=d}function G(a,b,c){a._hungarianMap||T(a);var d;h.each(b,function(e){d=a._hungarianMap[e];if(d!==l&&(c||b[d]===l))"o"===d.charAt(0)?(b[d]||(b[d]={}),h.extend(!0,b[d],b[e]),G(a[d],b[d],c)):b[d]=b[e]})}function N(a){var b=p.defaults.oLanguage,c=a.sZeroRecords;
!a.sEmptyTable&&(c&&"No data available in table"===b.sEmptyTable)&&D(a,a,"sZeroRecords","sEmptyTable");!a.sLoadingRecords&&(c&&"Loading..."===b.sLoadingRecords)&&D(a,a,"sZeroRecords","sLoadingRecords");a.sInfoThousands&&(a.sThousands=a.sInfoThousands);(a=a.sDecimal)&&cb(a)}function db(a){w(a,"ordering","bSort");w(a,"orderMulti","bSortMulti");w(a,"orderClasses","bSortClasses");w(a,"orderCellsTop","bSortCellsTop");w(a,"order","aaSorting");w(a,"orderFixed","aaSortingFixed");w(a,"paging","bPaginate");
w(a,"pagingType","sPaginationType");w(a,"pageLength","iDisplayLength");w(a,"searching","bFilter");if(a=a.aoSearchCols)for(var b=0,c=a.length;b<c;b++)a[b]&&G(p.models.oSearch,a[b])}function eb(a){w(a,"orderable","bSortable");w(a,"orderData","aDataSort");w(a,"orderSequence","asSorting");w(a,"orderDataType","sortDataType")}function fb(a){var a=a.oBrowser,b=h("<div/>").css({position:"absolute",top:0,left:0,height:1,width:1,overflow:"hidden"}).append(h("<div/>").css({position:"absolute",top:1,left:1,width:100,
overflow:"scroll"}).append(h('<div class="test"/>').css({width:"100%",height:10}))).appendTo("body"),c=b.find(".test");a.bScrollOversize=100===c[0].offsetWidth;a.bScrollbarLeft=1!==c.offset().left;b.remove()}function gb(a,b,c,d,e,f){var g,j=!1;c!==l&&(g=c,j=!0);for(;d!==e;)a.hasOwnProperty(d)&&(g=j?b(g,a[d],d,a):a[d],j=!0,d+=f);return g}function Aa(a,b){var c=p.defaults.column,d=a.aoColumns.length,c=h.extend({},p.models.oColumn,c,{nTh:b?b:O.createElement("th"),sTitle:c.sTitle?c.sTitle:b?b.innerHTML:
"",aDataSort:c.aDataSort?c.aDataSort:[d],mData:c.mData?c.mData:d,idx:d});a.aoColumns.push(c);c=a.aoPreSearchCols;c[d]=h.extend({},p.models.oSearch,c[d]);fa(a,d,null)}function fa(a,b,c){var b=a.aoColumns[b],d=a.oClasses,e=h(b.nTh);if(!b.sWidthOrig){b.sWidthOrig=e.attr("width")||null;var f=(e.attr("style")||"").match(/width:\s*(\d+[pxem%]+)/);f&&(b.sWidthOrig=f[1])}c!==l&&null!==c&&(eb(c),G(p.defaults.column,c),c.mDataProp!==l&&!c.mData&&(c.mData=c.mDataProp),c.sType&&(b._sManualType=c.sType),c.className&&
!c.sClass&&(c.sClass=c.className),h.extend(b,c),D(b,c,"sWidth","sWidthOrig"),"number"===typeof c.iDataSort&&(b.aDataSort=[c.iDataSort]),D(b,c,"aDataSort"));var g=b.mData,j=U(g),i=b.mRender?U(b.mRender):null,c=function(a){return"string"===typeof a&&-1!==a.indexOf("@")};b._bAttrSrc=h.isPlainObject(g)&&(c(g.sort)||c(g.type)||c(g.filter));b.fnGetData=function(a,b,c){var d=j(a,b,l,c);return i&&b?i(d,b,a,c):d};b.fnSetData=function(a,b,c){return Ba(g)(a,b,c)};a.oFeatures.bSort||(b.bSortable=!1,e.addClass(d.sSortableNone));
a=-1!==h.inArray("asc",b.asSorting);c=-1!==h.inArray("desc",b.asSorting);!b.bSortable||!a&&!c?(b.sSortingClass=d.sSortableNone,b.sSortingClassJUI=""):a&&!c?(b.sSortingClass=d.sSortableAsc,b.sSortingClassJUI=d.sSortJUIAscAllowed):!a&&c?(b.sSortingClass=d.sSortableDesc,b.sSortingClassJUI=d.sSortJUIDescAllowed):(b.sSortingClass=d.sSortable,b.sSortingClassJUI=d.sSortJUI)}function V(a){if(!1!==a.oFeatures.bAutoWidth){var b=a.aoColumns;Ca(a);for(var c=0,d=b.length;c<d;c++)b[c].nTh.style.width=b[c].sWidth}b=
a.oScroll;(""!==b.sY||""!==b.sX)&&W(a);u(a,null,"column-sizing",[a])}function ga(a,b){var c=X(a,"bVisible");return"number"===typeof c[b]?c[b]:null}function Y(a,b){var c=X(a,"bVisible"),c=h.inArray(b,c);return-1!==c?c:null}function Z(a){return X(a,"bVisible").length}function X(a,b){var c=[];h.map(a.aoColumns,function(a,e){a[b]&&c.push(e)});return c}function Da(a){var b=a.aoColumns,c=a.aoData,d=p.ext.type.detect,e,f,g,j,i,h,m,n,k;e=0;for(f=b.length;e<f;e++)if(m=b[e],k=[],!m.sType&&m._sManualType)m.sType=
m._sManualType;else if(!m.sType){g=0;for(j=d.length;g<j;g++){i=0;for(h=c.length;i<h&&!(k[i]===l&&(k[i]=A(a,i,e,"type")),n=d[g](k[i],a),!n||"html"===n);i++);if(n){m.sType=n;break}}m.sType||(m.sType="string")}}function hb(a,b,c,d){var e,f,g,j,i,o,m=a.aoColumns;if(b)for(e=b.length-1;0<=e;e--){o=b[e];var n=o.targets!==l?o.targets:o.aTargets;h.isArray(n)||(n=[n]);f=0;for(g=n.length;f<g;f++)if("number"===typeof n[f]&&0<=n[f]){for(;m.length<=n[f];)Aa(a);d(n[f],o)}else if("number"===typeof n[f]&&0>n[f])d(m.length+
n[f],o);else if("string"===typeof n[f]){j=0;for(i=m.length;j<i;j++)("_all"==n[f]||h(m[j].nTh).hasClass(n[f]))&&d(j,o)}}if(c){e=0;for(a=c.length;e<a;e++)d(e,c[e])}}function I(a,b,c,d){var e=a.aoData.length,f=h.extend(!0,{},p.models.oRow,{src:c?"dom":"data"});f._aData=b;a.aoData.push(f);for(var b=a.aoColumns,f=0,g=b.length;f<g;f++)c&&Ea(a,e,f,A(a,e,f)),b[f].sType=null;a.aiDisplayMaster.push(e);(c||!a.oFeatures.bDeferRender)&&Fa(a,e,c,d);return e}function ha(a,b){var c;b instanceof h||(b=h(b));return b.map(function(b,
e){c=ia(a,e);return I(a,c.data,e,c.cells)})}function A(a,b,c,d){var e=a.iDraw,f=a.aoColumns[c],g=a.aoData[b]._aData,j=f.sDefaultContent,c=f.fnGetData(g,d,{settings:a,row:b,col:c});if(c===l)return a.iDrawError!=e&&null===j&&(P(a,0,"Requested unknown parameter "+("function"==typeof f.mData?"{function}":"'"+f.mData+"'")+" for row "+b,4),a.iDrawError=e),j;if((c===g||null===c)&&null!==j)c=j;else if("function"===typeof c)return c.call(g);return null===c&&"display"==d?"":c}function Ea(a,b,c,d){a.aoColumns[c].fnSetData(a.aoData[b]._aData,
d,{settings:a,row:b,col:c})}function Ga(a){return h.map(a.match(/(\\.|[^\.])+/g),function(a){return a.replace(/\\./g,".")})}function U(a){if(h.isPlainObject(a)){var b={};h.each(a,function(a,c){c&&(b[a]=U(c))});return function(a,c,f,g){var j=b[c]||b._;return j!==l?j(a,c,f,g):a}}if(null===a)return function(a){return a};if("function"===typeof a)return function(b,c,f,g){return a(b,c,f,g)};if("string"===typeof a&&(-1!==a.indexOf(".")||-1!==a.indexOf("[")||-1!==a.indexOf("("))){var c=function(a,b,f){var g,
j;if(""!==f){j=Ga(f);for(var i=0,h=j.length;i<h;i++){f=j[i].match($);g=j[i].match(Q);if(f){j[i]=j[i].replace($,"");""!==j[i]&&(a=a[j[i]]);g=[];j.splice(0,i+1);j=j.join(".");i=0;for(h=a.length;i<h;i++)g.push(c(a[i],b,j));a=f[0].substring(1,f[0].length-1);a=""===a?g:g.join(a);break}else if(g){j[i]=j[i].replace(Q,"");a=a[j[i]]();continue}if(null===a||a[j[i]]===l)return l;a=a[j[i]]}}return a};return function(b,e){return c(b,e,a)}}return function(b){return b[a]}}function Ba(a){if(h.isPlainObject(a))return Ba(a._);
if(null===a)return function(){};if("function"===typeof a)return function(b,d,e){a(b,"set",d,e)};if("string"===typeof a&&(-1!==a.indexOf(".")||-1!==a.indexOf("[")||-1!==a.indexOf("("))){var b=function(a,d,e){var e=Ga(e),f;f=e[e.length-1];for(var g,j,i=0,h=e.length-1;i<h;i++){g=e[i].match($);j=e[i].match(Q);if(g){e[i]=e[i].replace($,"");a[e[i]]=[];f=e.slice();f.splice(0,i+1);g=f.join(".");j=0;for(h=d.length;j<h;j++)f={},b(f,d[j],g),a[e[i]].push(f);return}j&&(e[i]=e[i].replace(Q,""),a=a[e[i]](d));if(null===
a[e[i]]||a[e[i]]===l)a[e[i]]={};a=a[e[i]]}if(f.match(Q))a[f.replace(Q,"")](d);else a[f.replace($,"")]=d};return function(c,d){return b(c,d,a)}}return function(b,d){b[a]=d}}function Ha(a){return C(a.aoData,"_aData")}function ja(a){a.aoData.length=0;a.aiDisplayMaster.length=0;a.aiDisplay.length=0}function ka(a,b,c){for(var d=-1,e=0,f=a.length;e<f;e++)a[e]==b?d=e:a[e]>b&&a[e]--; -1!=d&&c===l&&a.splice(d,1)}function la(a,b,c,d){var e=a.aoData[b],f;if("dom"===c||(!c||"auto"===c)&&"dom"===e.src)e._aData=
ia(a,e).data;else{var g=e.anCells,j;if(g){c=0;for(f=g.length;c<f;c++){for(j=g[c];j.childNodes.length;)j.removeChild(j.firstChild);g[c].innerHTML=A(a,b,c,"display")}}}e._aSortData=null;e._aFilterData=null;a=a.aoColumns;if(d!==l)a[d].sType=null;else{c=0;for(f=a.length;c<f;c++)a[c].sType=null}Ia(e)}function ia(a,b){var c=[],d=[],e=b.firstChild,f,g,j,i=0,o,m=a.aoColumns,n=function(a,b,c){"string"===typeof a&&(b=a.indexOf("@"),-1!==b&&(a=a.substring(b+1),j["@"+a]=c.getAttribute(a)))},k=function(a){g=m[i];
o=h.trim(a.innerHTML);g&&g._bAttrSrc?(j={display:o},n(g.mData.sort,j,a),n(g.mData.type,j,a),n(g.mData.filter,j,a),c.push(j)):c.push(o);i++};if(e)for(;e;){f=e.nodeName.toUpperCase();if("TD"==f||"TH"==f)k(e),d.push(e);e=e.nextSibling}else{d=b.anCells;e=0;for(f=d.length;e<f;e++)k(d[e])}return{data:c,cells:d}}function Fa(a,b,c,d){var e=a.aoData[b],f=e._aData,g=[],j,i,h,m,n;if(null===e.nTr){j=c||O.createElement("tr");e.nTr=j;e.anCells=g;j._DT_RowIndex=b;Ia(e);m=0;for(n=a.aoColumns.length;m<n;m++){h=a.aoColumns[m];
i=c?d[m]:O.createElement(h.sCellType);g.push(i);if(!c||h.mRender||h.mData!==m)i.innerHTML=A(a,b,m,"display");h.sClass&&(i.className+=" "+h.sClass);h.bVisible&&!c?j.appendChild(i):!h.bVisible&&c&&i.parentNode.removeChild(i);h.fnCreatedCell&&h.fnCreatedCell.call(a.oInstance,i,A(a,b,m),f,b,m)}u(a,"aoRowCreatedCallback",null,[j,f,b])}e.nTr.setAttribute("role","row")}function Ia(a){var b=a.nTr,c=a._aData;if(b){c.DT_RowId&&(b.id=c.DT_RowId);if(c.DT_RowClass){var d=c.DT_RowClass.split(" ");a.__rowc=a.__rowc?
Ja(a.__rowc.concat(d)):d;h(b).removeClass(a.__rowc.join(" ")).addClass(c.DT_RowClass)}c.DT_RowData&&h(b).data(c.DT_RowData)}}function ib(a){var b,c,d,e,f,g=a.nTHead,j=a.nTFoot,i=0===h("th, td",g).length,o=a.oClasses,m=a.aoColumns;i&&(e=h("<tr/>").appendTo(g));b=0;for(c=m.length;b<c;b++)f=m[b],d=h(f.nTh).addClass(f.sClass),i&&d.appendTo(e),a.oFeatures.bSort&&(d.addClass(f.sSortingClass),!1!==f.bSortable&&(d.attr("tabindex",a.iTabIndex).attr("aria-controls",a.sTableId),Ka(a,f.nTh,b))),f.sTitle!=d.html()&&
d.html(f.sTitle),La(a,"header")(a,d,f,o);i&&aa(a.aoHeader,g);h(g).find(">tr").attr("role","row");h(g).find(">tr>th, >tr>td").addClass(o.sHeaderTH);h(j).find(">tr>th, >tr>td").addClass(o.sFooterTH);if(null!==j){a=a.aoFooter[0];b=0;for(c=a.length;b<c;b++)f=m[b],f.nTf=a[b].cell,f.sClass&&h(f.nTf).addClass(f.sClass)}}function ba(a,b,c){var d,e,f,g=[],j=[],i=a.aoColumns.length,o;if(b){c===l&&(c=!1);d=0;for(e=b.length;d<e;d++){g[d]=b[d].slice();g[d].nTr=b[d].nTr;for(f=i-1;0<=f;f--)!a.aoColumns[f].bVisible&&
!c&&g[d].splice(f,1);j.push([])}d=0;for(e=g.length;d<e;d++){if(a=g[d].nTr)for(;f=a.firstChild;)a.removeChild(f);f=0;for(b=g[d].length;f<b;f++)if(o=i=1,j[d][f]===l){a.appendChild(g[d][f].cell);for(j[d][f]=1;g[d+i]!==l&&g[d][f].cell==g[d+i][f].cell;)j[d+i][f]=1,i++;for(;g[d][f+o]!==l&&g[d][f].cell==g[d][f+o].cell;){for(c=0;c<i;c++)j[d+c][f+o]=1;o++}h(g[d][f].cell).attr("rowspan",i).attr("colspan",o)}}}}function K(a){var b=u(a,"aoPreDrawCallback","preDraw",[a]);if(-1!==h.inArray(!1,b))B(a,!1);else{var b=
[],c=0,d=a.asStripeClasses,e=d.length,f=a.oLanguage,g=a.iInitDisplayStart,j="ssp"==z(a),i=a.aiDisplay;a.bDrawing=!0;g!==l&&-1!==g&&(a._iDisplayStart=j?g:g>=a.fnRecordsDisplay()?0:g,a.iInitDisplayStart=-1);var g=a._iDisplayStart,o=a.fnDisplayEnd();if(a.bDeferLoading)a.bDeferLoading=!1,a.iDraw++,B(a,!1);else if(j){if(!a.bDestroying&&!jb(a))return}else a.iDraw++;if(0!==i.length){f=j?a.aoData.length:o;for(j=j?0:g;j<f;j++){var m=i[j],n=a.aoData[m];null===n.nTr&&Fa(a,m);m=n.nTr;if(0!==e){var k=d[c%e];n._sRowStripe!=
k&&(h(m).removeClass(n._sRowStripe).addClass(k),n._sRowStripe=k)}u(a,"aoRowCallback",null,[m,n._aData,c,j]);b.push(m);c++}}else c=f.sZeroRecords,1==a.iDraw&&"ajax"==z(a)?c=f.sLoadingRecords:f.sEmptyTable&&0===a.fnRecordsTotal()&&(c=f.sEmptyTable),b[0]=h("<tr/>",{"class":e?d[0]:""}).append(h("<td />",{valign:"top",colSpan:Z(a),"class":a.oClasses.sRowEmpty}).html(c))[0];u(a,"aoHeaderCallback","header",[h(a.nTHead).children("tr")[0],Ha(a),g,o,i]);u(a,"aoFooterCallback","footer",[h(a.nTFoot).children("tr")[0],
Ha(a),g,o,i]);d=h(a.nTBody);d.children().detach();d.append(h(b));u(a,"aoDrawCallback","draw",[a]);a.bSorted=!1;a.bFiltered=!1;a.bDrawing=!1}}function L(a,b){var c=a.oFeatures,d=c.bFilter;c.bSort&&kb(a);d?ca(a,a.oPreviousSearch):a.aiDisplay=a.aiDisplayMaster.slice();!0!==b&&(a._iDisplayStart=0);a._drawHold=b;K(a);a._drawHold=!1}function lb(a){var b=a.oClasses,c=h(a.nTable),c=h("<div/>").insertBefore(c),d=a.oFeatures,e=h("<div/>",{id:a.sTableId+"_wrapper","class":b.sWrapper+(a.nTFoot?"":" "+b.sNoFooter)});
a.nHolding=c[0];a.nTableWrapper=e[0];a.nTableReinsertBefore=a.nTable.nextSibling;for(var f=a.sDom.split(""),g,j,i,o,m,n,k=0;k<f.length;k++){g=null;j=f[k];if("<"==j){i=h("<div/>")[0];o=f[k+1];if("'"==o||'"'==o){m="";for(n=2;f[k+n]!=o;)m+=f[k+n],n++;"H"==m?m=b.sJUIHeader:"F"==m&&(m=b.sJUIFooter);-1!=m.indexOf(".")?(o=m.split("."),i.id=o[0].substr(1,o[0].length-1),i.className=o[1]):"#"==m.charAt(0)?i.id=m.substr(1,m.length-1):i.className=m;k+=n}e.append(i);e=h(i)}else if(">"==j)e=e.parent();else if("l"==
j&&d.bPaginate&&d.bLengthChange)g=mb(a);else if("f"==j&&d.bFilter)g=nb(a);else if("r"==j&&d.bProcessing)g=ob(a);else if("t"==j)g=pb(a);else if("i"==j&&d.bInfo)g=qb(a);else if("p"==j&&d.bPaginate)g=rb(a);else if(0!==p.ext.feature.length){i=p.ext.feature;n=0;for(o=i.length;n<o;n++)if(j==i[n].cFeature){g=i[n].fnInit(a);break}}g&&(i=a.aanFeatures,i[j]||(i[j]=[]),i[j].push(g),e.append(g))}c.replaceWith(e)}function aa(a,b){var c=h(b).children("tr"),d,e,f,g,j,i,o,m,n,k;a.splice(0,a.length);f=0;for(i=c.length;f<
i;f++)a.push([]);f=0;for(i=c.length;f<i;f++){d=c[f];for(e=d.firstChild;e;){if("TD"==e.nodeName.toUpperCase()||"TH"==e.nodeName.toUpperCase()){m=1*e.getAttribute("colspan");n=1*e.getAttribute("rowspan");m=!m||0===m||1===m?1:m;n=!n||0===n||1===n?1:n;g=0;for(j=a[f];j[g];)g++;o=g;k=1===m?!0:!1;for(j=0;j<m;j++)for(g=0;g<n;g++)a[f+g][o+j]={cell:e,unique:k},a[f+g].nTr=d}e=e.nextSibling}}}function ma(a,b,c){var d=[];c||(c=a.aoHeader,b&&(c=[],aa(c,b)));for(var b=0,e=c.length;b<e;b++)for(var f=0,g=c[b].length;f<
g;f++)if(c[b][f].unique&&(!d[f]||!a.bSortCellsTop))d[f]=c[b][f].cell;return d}function na(a,b,c){u(a,"aoServerParams","serverParams",[b]);if(b&&h.isArray(b)){var d={},e=/(.*?)\[\]$/;h.each(b,function(a,b){var c=b.name.match(e);c?(c=c[0],d[c]||(d[c]=[]),d[c].push(b.value)):d[b.name]=b.value});b=d}var f,g=a.ajax,j=a.oInstance;if(h.isPlainObject(g)&&g.data){f=g.data;var i=h.isFunction(f)?f(b):f,b=h.isFunction(f)&&i?i:h.extend(!0,b,i);delete g.data}i={data:b,success:function(b){var d=b.error||b.sError;
d&&a.oApi._fnLog(a,0,d);a.json=b;u(a,null,"xhr",[a,b]);c(b)},dataType:"json",cache:!1,type:a.sServerMethod,error:function(b,c){var d=a.oApi._fnLog;"parsererror"==c?d(a,0,"Invalid JSON response",1):4===b.readyState&&d(a,0,"Ajax error",7);B(a,!1)}};a.oAjaxData=b;u(a,null,"preXhr",[a,b]);a.fnServerData?a.fnServerData.call(j,a.sAjaxSource,h.map(b,function(a,b){return{name:b,value:a}}),c,a):a.sAjaxSource||"string"===typeof g?a.jqXHR=h.ajax(h.extend(i,{url:g||a.sAjaxSource})):h.isFunction(g)?a.jqXHR=g.call(j,
b,c,a):(a.jqXHR=h.ajax(h.extend(i,g)),g.data=f)}function jb(a){return a.bAjaxDataGet?(a.iDraw++,B(a,!0),na(a,sb(a),function(b){tb(a,b)}),!1):!0}function sb(a){var b=a.aoColumns,c=b.length,d=a.oFeatures,e=a.oPreviousSearch,f=a.aoPreSearchCols,g,j=[],i,o,m,n=R(a);g=a._iDisplayStart;i=!1!==d.bPaginate?a._iDisplayLength:-1;var k=function(a,b){j.push({name:a,value:b})};k("sEcho",a.iDraw);k("iColumns",c);k("sColumns",C(b,"sName").join(","));k("iDisplayStart",g);k("iDisplayLength",i);var l={draw:a.iDraw,
columns:[],order:[],start:g,length:i,search:{value:e.sSearch,regex:e.bRegex}};for(g=0;g<c;g++)o=b[g],m=f[g],i="function"==typeof o.mData?"function":o.mData,l.columns.push({data:i,name:o.sName,searchable:o.bSearchable,orderable:o.bSortable,search:{value:m.sSearch,regex:m.bRegex}}),k("mDataProp_"+g,i),d.bFilter&&(k("sSearch_"+g,m.sSearch),k("bRegex_"+g,m.bRegex),k("bSearchable_"+g,o.bSearchable)),d.bSort&&k("bSortable_"+g,o.bSortable);d.bFilter&&(k("sSearch",e.sSearch),k("bRegex",e.bRegex));d.bSort&&
(h.each(n,function(a,b){l.order.push({column:b.col,dir:b.dir});k("iSortCol_"+a,b.col);k("sSortDir_"+a,b.dir)}),k("iSortingCols",n.length));b=p.ext.legacy.ajax;return null===b?a.sAjaxSource?j:l:b?j:l}function tb(a,b){var c=b.sEcho!==l?b.sEcho:b.draw,d=b.iTotalRecords!==l?b.iTotalRecords:b.recordsTotal,e=b.iTotalDisplayRecords!==l?b.iTotalDisplayRecords:b.recordsFiltered;if(c){if(1*c<a.iDraw)return;a.iDraw=1*c}ja(a);a._iRecordsTotal=parseInt(d,10);a._iRecordsDisplay=parseInt(e,10);c=oa(a,b);d=0;for(e=
c.length;d<e;d++)I(a,c[d]);a.aiDisplay=a.aiDisplayMaster.slice();a.bAjaxDataGet=!1;K(a);a._bInitComplete||pa(a,b);a.bAjaxDataGet=!0;B(a,!1)}function oa(a,b){var c=h.isPlainObject(a.ajax)&&a.ajax.dataSrc!==l?a.ajax.dataSrc:a.sAjaxDataProp;return"data"===c?b.aaData||b[c]:""!==c?U(c)(b):b}function nb(a){var b=a.oClasses,c=a.sTableId,d=a.oLanguage,e=a.oPreviousSearch,f=a.aanFeatures,g='<input type="search" class="'+b.sFilterInput+'"/>',j=d.sSearch,j=j.match(/_INPUT_/)?j.replace("_INPUT_",g):j+g,b=h("<div/>",
{id:!f.f?c+"_filter":null,"class":b.sFilter}).append(h("<label/>").append(j)),f=function(){var b=!this.value?"":this.value;b!=e.sSearch&&(ca(a,{sSearch:b,bRegex:e.bRegex,bSmart:e.bSmart,bCaseInsensitive:e.bCaseInsensitive}),a._iDisplayStart=0,K(a))},i=h("input",b).val(e.sSearch).attr("placeholder",d.sSearchPlaceholder).bind("keyup.DT search.DT input.DT paste.DT cut.DT","ssp"===z(a)?Ma(f,400):f).bind("keypress.DT",function(a){if(13==a.keyCode)return!1}).attr("aria-controls",c);h(a.nTable).on("search.dt.DT",
function(b,c){if(a===c)try{i[0]!==O.activeElement&&i.val(e.sSearch)}catch(d){}});return b[0]}function ca(a,b,c){var d=a.oPreviousSearch,e=a.aoPreSearchCols,f=function(a){d.sSearch=a.sSearch;d.bRegex=a.bRegex;d.bSmart=a.bSmart;d.bCaseInsensitive=a.bCaseInsensitive};Da(a);if("ssp"!=z(a)){ub(a,b.sSearch,c,b.bEscapeRegex!==l?!b.bEscapeRegex:b.bRegex,b.bSmart,b.bCaseInsensitive);f(b);for(b=0;b<e.length;b++)vb(a,e[b].sSearch,b,e[b].bEscapeRegex!==l?!e[b].bEscapeRegex:e[b].bRegex,e[b].bSmart,e[b].bCaseInsensitive);
wb(a)}else f(b);a.bFiltered=!0;u(a,null,"search",[a])}function wb(a){for(var b=p.ext.search,c=a.aiDisplay,d,e,f=0,g=b.length;f<g;f++){for(var j=[],i=0,h=c.length;i<h;i++)e=c[i],d=a.aoData[e],b[f](a,d._aFilterData,e,d._aData,i)&&j.push(e);c.length=0;c.push.apply(c,j)}}function vb(a,b,c,d,e,f){if(""!==b)for(var g=a.aiDisplay,d=Na(b,d,e,f),e=g.length-1;0<=e;e--)b=a.aoData[g[e]]._aFilterData[c],d.test(b)||g.splice(e,1)}function ub(a,b,c,d,e,f){var d=Na(b,d,e,f),e=a.oPreviousSearch.sSearch,f=a.aiDisplayMaster,
g;0!==p.ext.search.length&&(c=!0);g=xb(a);if(0>=b.length)a.aiDisplay=f.slice();else{if(g||c||e.length>b.length||0!==b.indexOf(e)||a.bSorted)a.aiDisplay=f.slice();b=a.aiDisplay;for(c=b.length-1;0<=c;c--)d.test(a.aoData[b[c]]._sFilterRow)||b.splice(c,1)}}function Na(a,b,c,d){a=b?a:Oa(a);c&&(a="^(?=.*?"+h.map(a.match(/"[^"]+"|[^ ]+/g)||"",function(a){return'"'===a.charAt(0)?a.match(/^"(.*)"$/)[1]:a}).join(")(?=.*?")+").*$");return RegExp(a,d?"i":"")}function Oa(a){return a.replace(Vb,"\\$1")}function xb(a){var b=
a.aoColumns,c,d,e,f,g,j,i,h,m=p.ext.type.search;c=!1;d=0;for(f=a.aoData.length;d<f;d++)if(h=a.aoData[d],!h._aFilterData){j=[];e=0;for(g=b.length;e<g;e++)if(c=b[e],c.bSearchable?(i=A(a,d,e,"filter"),i=m[c.sType]?m[c.sType](i):null!==i?i:""):i="",i)i.indexOf&&-1!==i.indexOf("&")&&(qa.innerHTML=i,i=Wb?qa.textContent:qa.innerText),i.replace&&(i=i.replace(/[\r\n]/g,"")),j.push(i);h._aFilterData=j;h._sFilterRow=j.join(" ");c=!0}return c}function yb(a){return{search:a.sSearch,smart:a.bSmart,regex:a.bRegex,
caseInsensitive:a.bCaseInsensitive}}function zb(a){return{sSearch:a.search,bSmart:a.smart,bRegex:a.regex,bCaseInsensitive:a.caseInsensitive}}function qb(a){var b=a.sTableId,c=a.aanFeatures.i,d=h("<div/>",{"class":a.oClasses.sInfo,id:!c?b+"_info":null});c||(a.aoDrawCallback.push({fn:Ab,sName:"information"}),d.attr("role","status").attr("aria-live","polite"),h(a.nTable).attr("aria-describedby",b+"_info"));return d[0]}function Ab(a){var b=a.aanFeatures.i;if(0!==b.length){var c=a.oLanguage,d=a._iDisplayStart+
1,e=a.fnDisplayEnd(),f=a.fnRecordsTotal(),g=a.fnRecordsDisplay(),j=g?c.sInfo:c.sInfoEmpty;g!==f&&(j+=" "+c.sInfoFiltered);j+=c.sInfoPostFix;j=Bb(a,j);c=c.fnInfoCallback;null!==c&&(j=c.call(a.oInstance,a,d,e,f,g,j));h(b).html(j)}}function Bb(a,b){var c=a.fnFormatNumber,d=a._iDisplayStart+1,e=a._iDisplayLength,f=a.fnRecordsDisplay(),g=-1===e;return b.replace(/_START_/g,c.call(a,d)).replace(/_END_/g,c.call(a,a.fnDisplayEnd())).replace(/_MAX_/g,c.call(a,a.fnRecordsTotal())).replace(/_TOTAL_/g,c.call(a,
f)).replace(/_PAGE_/g,c.call(a,g?1:Math.ceil(d/e))).replace(/_PAGES_/g,c.call(a,g?1:Math.ceil(f/e)))}function ra(a){var b,c,d=a.iInitDisplayStart,e=a.aoColumns,f;c=a.oFeatures;if(a.bInitialised){lb(a);ib(a);ba(a,a.aoHeader);ba(a,a.aoFooter);B(a,!0);c.bAutoWidth&&Ca(a);b=0;for(c=e.length;b<c;b++)f=e[b],f.sWidth&&(f.nTh.style.width=s(f.sWidth));L(a);e=z(a);"ssp"!=e&&("ajax"==e?na(a,[],function(c){var f=oa(a,c);for(b=0;b<f.length;b++)I(a,f[b]);a.iInitDisplayStart=d;L(a);B(a,!1);pa(a,c)},a):(B(a,!1),
pa(a)))}else setTimeout(function(){ra(a)},200)}function pa(a,b){a._bInitComplete=!0;b&&V(a);u(a,"aoInitComplete","init",[a,b])}function Pa(a,b){var c=parseInt(b,10);a._iDisplayLength=c;Qa(a);u(a,null,"length",[a,c])}function mb(a){for(var b=a.oClasses,c=a.sTableId,d=a.aLengthMenu,e=h.isArray(d[0]),f=e?d[0]:d,d=e?d[1]:d,e=h("<select/>",{name:c+"_length","aria-controls":c,"class":b.sLengthSelect}),g=0,j=f.length;g<j;g++)e[0][g]=new Option(d[g],f[g]);var i=h("<div><label/></div>").addClass(b.sLength);
a.aanFeatures.l||(i[0].id=c+"_length");i.children().append(a.oLanguage.sLengthMenu.replace("_MENU_",e[0].outerHTML));h("select",i).val(a._iDisplayLength).bind("change.DT",function(){Pa(a,h(this).val());K(a)});h(a.nTable).bind("length.dt.DT",function(b,c,d){a===c&&h("select",i).val(d)});return i[0]}function rb(a){var b=a.sPaginationType,c=p.ext.pager[b],d="function"===typeof c,e=function(a){K(a)},b=h("<div/>").addClass(a.oClasses.sPaging+b)[0],f=a.aanFeatures;d||c.fnInit(a,b,e);f.p||(b.id=a.sTableId+
"_paginate",a.aoDrawCallback.push({fn:function(a){if(d){var b=a._iDisplayStart,i=a._iDisplayLength,h=a.fnRecordsDisplay(),m=-1===i,b=m?0:Math.ceil(b/i),i=m?1:Math.ceil(h/i),h=c(b,i),n,m=0;for(n=f.p.length;m<n;m++)La(a,"pageButton")(a,f.p[m],m,h,b,i)}else c.fnUpdate(a,e)},sName:"pagination"}));return b}function Ra(a,b,c){var d=a._iDisplayStart,e=a._iDisplayLength,f=a.fnRecordsDisplay();0===f||-1===e?d=0:"number"===typeof b?(d=b*e,d>f&&(d=0)):"first"==b?d=0:"previous"==b?(d=0<=e?d-e:0,0>d&&(d=0)):"next"==
b?d+e<f&&(d+=e):"last"==b?d=Math.floor((f-1)/e)*e:P(a,0,"Unknown paging action: "+b,5);b=a._iDisplayStart!==d;a._iDisplayStart=d;b&&(u(a,null,"page",[a]),c&&K(a));return b}function ob(a){return h("<div/>",{id:!a.aanFeatures.r?a.sTableId+"_processing":null,"class":a.oClasses.sProcessing}).html(a.oLanguage.sProcessing).insertBefore(a.nTable)[0]}function B(a,b){a.oFeatures.bProcessing&&h(a.aanFeatures.r).css("display",b?"block":"none");u(a,null,"processing",[a,b])}function pb(a){var b=h(a.nTable);b.attr("role",
"grid");var c=a.oScroll;if(""===c.sX&&""===c.sY)return a.nTable;var d=c.sX,e=c.sY,f=a.oClasses,g=b.children("caption"),j=g.length?g[0]._captionSide:null,i=h(b[0].cloneNode(!1)),o=h(b[0].cloneNode(!1)),m=b.children("tfoot");c.sX&&"100%"===b.attr("width")&&b.removeAttr("width");m.length||(m=null);c=h("<div/>",{"class":f.sScrollWrapper}).append(h("<div/>",{"class":f.sScrollHead}).css({overflow:"hidden",position:"relative",border:0,width:d?!d?null:s(d):"100%"}).append(h("<div/>",{"class":f.sScrollHeadInner}).css({"box-sizing":"content-box",
width:c.sXInner||"100%"}).append(i.removeAttr("id").css("margin-left",0).append(b.children("thead")))).append("top"===j?g:null)).append(h("<div/>",{"class":f.sScrollBody}).css({overflow:"auto",height:!e?null:s(e),width:!d?null:s(d)}).append(b));m&&c.append(h("<div/>",{"class":f.sScrollFoot}).css({overflow:"hidden",border:0,width:d?!d?null:s(d):"100%"}).append(h("<div/>",{"class":f.sScrollFootInner}).append(o.removeAttr("id").css("margin-left",0).append(b.children("tfoot")))).append("bottom"===j?g:
null));var b=c.children(),n=b[0],f=b[1],k=m?b[2]:null;d&&h(f).scroll(function(){var a=this.scrollLeft;n.scrollLeft=a;m&&(k.scrollLeft=a)});a.nScrollHead=n;a.nScrollBody=f;a.nScrollFoot=k;a.aoDrawCallback.push({fn:W,sName:"scrolling"});return c[0]}function W(a){var b=a.oScroll,c=b.sX,d=b.sXInner,e=b.sY,f=b.iBarWidth,g=h(a.nScrollHead),j=g[0].style,i=g.children("div"),o=i[0].style,m=i.children("table"),i=a.nScrollBody,n=h(i),k=i.style,l=h(a.nScrollFoot).children("div"),p=l.children("table"),r=h(a.nTHead),
q=h(a.nTable),da=q[0],M=da.style,J=a.nTFoot?h(a.nTFoot):null,u=a.oBrowser,v=u.bScrollOversize,y,t,x,w,z,A=[],B=[],C=[],D,E=function(a){a=a.style;a.paddingTop="0";a.paddingBottom="0";a.borderTopWidth="0";a.borderBottomWidth="0";a.height=0};q.children("thead, tfoot").remove();z=r.clone().prependTo(q);y=r.find("tr");x=z.find("tr");z.find("th, td").removeAttr("tabindex");J&&(w=J.clone().prependTo(q),t=J.find("tr"),w=w.find("tr"));c||(k.width="100%",g[0].style.width="100%");h.each(ma(a,z),function(b,c){D=
ga(a,b);c.style.width=a.aoColumns[D].sWidth});J&&F(function(a){a.style.width=""},w);b.bCollapse&&""!==e&&(k.height=n[0].offsetHeight+r[0].offsetHeight+"px");g=q.outerWidth();if(""===c){if(M.width="100%",v&&(q.find("tbody").height()>i.offsetHeight||"scroll"==n.css("overflow-y")))M.width=s(q.outerWidth()-f)}else""!==d?M.width=s(d):g==n.width()&&n.height()<q.height()?(M.width=s(g-f),q.outerWidth()>g-f&&(M.width=s(g))):M.width=s(g);g=q.outerWidth();F(E,x);F(function(a){C.push(a.innerHTML);A.push(s(h(a).css("width")))},
x);F(function(a,b){a.style.width=A[b]},y);h(x).height(0);J&&(F(E,w),F(function(a){B.push(s(h(a).css("width")))},w),F(function(a,b){a.style.width=B[b]},t),h(w).height(0));F(function(a,b){a.innerHTML='<div class="dataTables_sizing" style="height:0;overflow:hidden;">'+C[b]+"</div>";a.style.width=A[b]},x);J&&F(function(a,b){a.innerHTML="";a.style.width=B[b]},w);if(q.outerWidth()<g){t=i.scrollHeight>i.offsetHeight||"scroll"==n.css("overflow-y")?g+f:g;if(v&&(i.scrollHeight>i.offsetHeight||"scroll"==n.css("overflow-y")))M.width=
s(t-f);(""===c||""!==d)&&P(a,1,"Possible column misalignment",6)}else t="100%";k.width=s(t);j.width=s(t);J&&(a.nScrollFoot.style.width=s(t));!e&&v&&(k.height=s(da.offsetHeight+f));e&&b.bCollapse&&(k.height=s(e),b=c&&da.offsetWidth>i.offsetWidth?f:0,da.offsetHeight<i.offsetHeight&&(k.height=s(da.offsetHeight+b)));b=q.outerWidth();m[0].style.width=s(b);o.width=s(b);m=q.height()>i.clientHeight||"scroll"==n.css("overflow-y");u="padding"+(u.bScrollbarLeft?"Left":"Right");o[u]=m?f+"px":"0px";J&&(p[0].style.width=
s(b),l[0].style.width=s(b),l[0].style[u]=m?f+"px":"0px");n.scroll();if((a.bSorted||a.bFiltered)&&!a._drawHold)i.scrollTop=0}function F(a,b,c){for(var d=0,e=0,f=b.length,g,j;e<f;){g=b[e].firstChild;for(j=c?c[e].firstChild:null;g;)1===g.nodeType&&(c?a(g,j,d):a(g,d),d++),g=g.nextSibling,j=c?j.nextSibling:null;e++}}function Ca(a){var b=a.nTable,c=a.aoColumns,d=a.oScroll,e=d.sY,f=d.sX,g=d.sXInner,j=c.length,d=X(a,"bVisible"),i=h("th",a.nTHead),o=b.getAttribute("width"),m=b.parentNode,n=!1,k,l;for(k=0;k<
d.length;k++)l=c[d[k]],null!==l.sWidth&&(l.sWidth=Cb(l.sWidthOrig,m),n=!0);if(!n&&!f&&!e&&j==Z(a)&&j==i.length)for(k=0;k<j;k++)c[k].sWidth=s(i.eq(k).width());else{j=h(b).clone().empty().css("visibility","hidden").removeAttr("id").append(h(a.nTHead).clone(!1)).append(h(a.nTFoot).clone(!1)).append(h("<tbody><tr/></tbody>"));j.find("tfoot th, tfoot td").css("width","");var p=j.find("tbody tr"),i=ma(a,j.find("thead")[0]);for(k=0;k<d.length;k++)l=c[d[k]],i[k].style.width=null!==l.sWidthOrig&&""!==l.sWidthOrig?
s(l.sWidthOrig):"";if(a.aoData.length)for(k=0;k<d.length;k++)n=d[k],l=c[n],h(Db(a,n)).clone(!1).append(l.sContentPadding).appendTo(p);j.appendTo(m);f&&g?j.width(g):f?(j.css("width","auto"),j.width()<m.offsetWidth&&j.width(m.offsetWidth)):e?j.width(m.offsetWidth):o&&j.width(o);Eb(a,j[0]);if(f){for(k=g=0;k<d.length;k++)l=c[d[k]],e=h(i[k]).outerWidth(),g+=null===l.sWidthOrig?e:parseInt(l.sWidth,10)+e-h(i[k]).width();j.width(s(g));b.style.width=s(g)}for(k=0;k<d.length;k++)if(l=c[d[k]],e=h(i[k]).width())l.sWidth=
s(e);b.style.width=s(j.css("width"));j.remove()}o&&(b.style.width=s(o));if((o||f)&&!a._reszEvt)h(za).bind("resize.DT-"+a.sInstance,Ma(function(){V(a)})),a._reszEvt=!0}function Ma(a,b){var c=b||200,d,e;return function(){var b=this,g=+new Date,j=arguments;d&&g<d+c?(clearTimeout(e),e=setTimeout(function(){d=l;a.apply(b,j)},c)):d?(d=g,a.apply(b,j)):d=g}}function Cb(a,b){if(!a)return 0;var c=h("<div/>").css("width",s(a)).appendTo(b||O.body),d=c[0].offsetWidth;c.remove();return d}function Eb(a,b){var c=
a.oScroll;if(c.sX||c.sY)c=!c.sX?c.iBarWidth:0,b.style.width=s(h(b).outerWidth()-c)}function Db(a,b){var c=Fb(a,b);if(0>c)return null;var d=a.aoData[c];return!d.nTr?h("<td/>").html(A(a,c,b,"display"))[0]:d.anCells[b]}function Fb(a,b){for(var c,d=-1,e=-1,f=0,g=a.aoData.length;f<g;f++)c=A(a,f,b,"display")+"",c=c.replace(Xb,""),c.length>d&&(d=c.length,e=f);return e}function s(a){return null===a?"0px":"number"==typeof a?0>a?"0px":a+"px":a.match(/\d$/)?a+"px":a}function Gb(){if(!p.__scrollbarWidth){var a=
h("<p/>").css({width:"100%",height:200,padding:0})[0],b=h("<div/>").css({position:"absolute",top:0,left:0,width:200,height:150,padding:0,overflow:"hidden",visibility:"hidden"}).append(a).appendTo("body"),c=a.offsetWidth;b.css("overflow","scroll");a=a.offsetWidth;c===a&&(a=b[0].clientWidth);b.remove();p.__scrollbarWidth=c-a}return p.__scrollbarWidth}function R(a){var b,c,d=[],e=a.aoColumns,f,g,j,i;b=a.aaSortingFixed;c=h.isPlainObject(b);var o=[];f=function(a){a.length&&!h.isArray(a[0])?o.push(a):o.push.apply(o,
a)};h.isArray(b)&&f(b);c&&b.pre&&f(b.pre);f(a.aaSorting);c&&b.post&&f(b.post);for(a=0;a<o.length;a++){i=o[a][0];f=e[i].aDataSort;b=0;for(c=f.length;b<c;b++)g=f[b],j=e[g].sType||"string",d.push({src:i,col:g,dir:o[a][1],index:o[a][2],type:j,formatter:p.ext.type.order[j+"-pre"]})}return d}function kb(a){var b,c,d=[],e=p.ext.type.order,f=a.aoData,g=0,j,i=a.aiDisplayMaster,h;Da(a);h=R(a);b=0;for(c=h.length;b<c;b++)j=h[b],j.formatter&&g++,Hb(a,j.col);if("ssp"!=z(a)&&0!==h.length){b=0;for(c=i.length;b<c;b++)d[i[b]]=
b;g===h.length?i.sort(function(a,b){var c,e,g,j,i=h.length,l=f[a]._aSortData,p=f[b]._aSortData;for(g=0;g<i;g++)if(j=h[g],c=l[j.col],e=p[j.col],c=c<e?-1:c>e?1:0,0!==c)return"asc"===j.dir?c:-c;c=d[a];e=d[b];return c<e?-1:c>e?1:0}):i.sort(function(a,b){var c,g,j,i,l=h.length,p=f[a]._aSortData,r=f[b]._aSortData;for(j=0;j<l;j++)if(i=h[j],c=p[i.col],g=r[i.col],i=e[i.type+"-"+i.dir]||e["string-"+i.dir],c=i(c,g),0!==c)return c;c=d[a];g=d[b];return c<g?-1:c>g?1:0})}a.bSorted=!0}function Ib(a){for(var b,c,
d=a.aoColumns,e=R(a),a=a.oLanguage.oAria,f=0,g=d.length;f<g;f++){c=d[f];var j=c.asSorting;b=c.sTitle.replace(/<.*?>/g,"");var i=c.nTh;i.removeAttribute("aria-sort");c.bSortable&&(0<e.length&&e[0].col==f?(i.setAttribute("aria-sort","asc"==e[0].dir?"ascending":"descending"),c=j[e[0].index+1]||j[0]):c=j[0],b+="asc"===c?a.sSortAscending:a.sSortDescending);i.setAttribute("aria-label",b)}}function Sa(a,b,c,d){var e=a.aaSorting,f=a.aoColumns[b].asSorting,g=function(a){var b=a._idx;b===l&&(b=h.inArray(a[1],
f));return b+1>=f.length?0:b+1};"number"===typeof e[0]&&(e=a.aaSorting=[e]);c&&a.oFeatures.bSortMulti?(c=h.inArray(b,C(e,"0")),-1!==c?(b=g(e[c]),e[c][1]=f[b],e[c]._idx=b):(e.push([b,f[0],0]),e[e.length-1]._idx=0)):e.length&&e[0][0]==b?(b=g(e[0]),e.length=1,e[0][1]=f[b],e[0]._idx=b):(e.length=0,e.push([b,f[0]]),e[0]._idx=0);L(a);"function"==typeof d&&d(a)}function Ka(a,b,c,d){var e=a.aoColumns[c];Ta(b,{},function(b){!1!==e.bSortable&&(a.oFeatures.bProcessing?(B(a,!0),setTimeout(function(){Sa(a,c,b.shiftKey,
d);"ssp"!==z(a)&&B(a,!1)},0)):Sa(a,c,b.shiftKey,d))})}function sa(a){var b=a.aLastSort,c=a.oClasses.sSortColumn,d=R(a),e=a.oFeatures,f,g;if(e.bSort&&e.bSortClasses){e=0;for(f=b.length;e<f;e++)g=b[e].src,h(C(a.aoData,"anCells",g)).removeClass(c+(2>e?e+1:3));e=0;for(f=d.length;e<f;e++)g=d[e].src,h(C(a.aoData,"anCells",g)).addClass(c+(2>e?e+1:3))}a.aLastSort=d}function Hb(a,b){var c=a.aoColumns[b],d=p.ext.order[c.sSortDataType],e;d&&(e=d.call(a.oInstance,a,b,Y(a,b)));for(var f,g=p.ext.type.order[c.sType+
"-pre"],j=0,i=a.aoData.length;j<i;j++)if(c=a.aoData[j],c._aSortData||(c._aSortData=[]),!c._aSortData[b]||d)f=d?e[j]:A(a,j,b,"sort"),c._aSortData[b]=g?g(f):f}function ta(a){if(a.oFeatures.bStateSave&&!a.bDestroying){var b={time:+new Date,start:a._iDisplayStart,length:a._iDisplayLength,order:h.extend(!0,[],a.aaSorting),search:yb(a.oPreviousSearch),columns:h.map(a.aoColumns,function(b,d){return{visible:b.bVisible,search:yb(a.aoPreSearchCols[d])}})};u(a,"aoStateSaveParams","stateSaveParams",[a,b]);a.oSavedState=
b;a.fnStateSaveCallback.call(a.oInstance,a,b)}}function Jb(a){var b,c,d=a.aoColumns;if(a.oFeatures.bStateSave){var e=a.fnStateLoadCallback.call(a.oInstance,a);if(e&&e.time&&(b=u(a,"aoStateLoadParams","stateLoadParams",[a,e]),-1===h.inArray(!1,b)&&(b=a.iStateDuration,!(0<b&&e.time<+new Date-1E3*b)&&d.length===e.columns.length))){a.oLoadedState=h.extend(!0,{},e);a._iDisplayStart=e.start;a.iInitDisplayStart=e.start;a._iDisplayLength=e.length;a.aaSorting=[];h.each(e.order,function(b,c){a.aaSorting.push(c[0]>=
d.length?[0,c[1]]:c)});h.extend(a.oPreviousSearch,zb(e.search));b=0;for(c=e.columns.length;b<c;b++){var f=e.columns[b];d[b].bVisible=f.visible;h.extend(a.aoPreSearchCols[b],zb(f.search))}u(a,"aoStateLoaded","stateLoaded",[a,e])}}}function ua(a){var b=p.settings,a=h.inArray(a,C(b,"nTable"));return-1!==a?b[a]:null}function P(a,b,c,d){c="DataTables warning: "+(null!==a?"table id="+a.sTableId+" - ":"")+c;d&&(c+=". For more information about this error, please see http://datatables.net/tn/"+d);if(b)za.console&&
console.log&&console.log(c);else if(a=p.ext,"alert"==(a.sErrMode||a.errMode))alert(c);else throw Error(c);}function D(a,b,c,d){h.isArray(c)?h.each(c,function(c,d){h.isArray(d)?D(a,b,d[0],d[1]):D(a,b,d)}):(d===l&&(d=c),b[c]!==l&&(a[d]=b[c]))}function Kb(a,b,c){var d,e;for(e in b)b.hasOwnProperty(e)&&(d=b[e],h.isPlainObject(d)?(h.isPlainObject(a[e])||(a[e]={}),h.extend(!0,a[e],d)):a[e]=c&&"data"!==e&&"aaData"!==e&&h.isArray(d)?d.slice():d);return a}function Ta(a,b,c){h(a).bind("click.DT",b,function(b){a.blur();
c(b)}).bind("keypress.DT",b,function(a){13===a.which&&(a.preventDefault(),c(a))}).bind("selectstart.DT",function(){return!1})}function x(a,b,c,d){c&&a[b].push({fn:c,sName:d})}function u(a,b,c,d){var e=[];b&&(e=h.map(a[b].slice().reverse(),function(b){return b.fn.apply(a.oInstance,d)}));null!==c&&h(a.nTable).trigger(c+".dt",d);return e}function Qa(a){var b=a._iDisplayStart,c=a.fnDisplayEnd(),d=a._iDisplayLength;c===a.fnRecordsDisplay()&&(b=c-d);if(-1===d||0>b)b=0;a._iDisplayStart=b}function La(a,b){var c=
a.renderer,d=p.ext.renderer[b];return h.isPlainObject(c)&&c[b]?d[c[b]]||d._:"string"===typeof c?d[c]||d._:d._}function z(a){return a.oFeatures.bServerSide?"ssp":a.ajax||a.sAjaxSource?"ajax":"dom"}function Ua(a,b){var c=[],c=Lb.numbers_length,d=Math.floor(c/2);b<=c?c=S(0,b):a<=d?(c=S(0,c-2),c.push("ellipsis"),c.push(b-1)):(a>=b-1-d?c=S(b-(c-2),b):(c=S(a-1,a+2),c.push("ellipsis"),c.push(b-1)),c.splice(0,0,"ellipsis"),c.splice(0,0,0));c.DT_el="span";return c}function cb(a){h.each({num:function(b){return va(b,
a)},"num-fmt":function(b){return va(b,a,Va)},"html-num":function(b){return va(b,a,wa)},"html-num-fmt":function(b){return va(b,a,wa,Va)}},function(b,c){t.type.order[b+a+"-pre"]=c})}function Mb(a){return function(){var b=[ua(this[p.ext.iApiIndex])].concat(Array.prototype.slice.call(arguments));return p.ext.internal[a].apply(this,b)}}var p,t,q,r,v,Wa={},Nb=/[\r\n]/g,wa=/<.*?>/g,Yb=/^[\w\+\-]/,Zb=/[\w\+\-]$/,Vb=RegExp("(\\/|\\.|\\*|\\+|\\?|\\||\\(|\\)|\\[|\\]|\\{|\\}|\\\\|\\$|\\^|\\-)","g"),Va=/[',$\u00a3\u20ac\u00a5%\u2009\u202F]/g,
H=function(a){return!a||!0===a||"-"===a?!0:!1},Ob=function(a){var b=parseInt(a,10);return!isNaN(b)&&isFinite(a)?b:null},Pb=function(a,b){Wa[b]||(Wa[b]=RegExp(Oa(b),"g"));return"string"===typeof a?a.replace(/\./g,"").replace(Wa[b],"."):a},Xa=function(a,b,c){var d="string"===typeof a;b&&d&&(a=Pb(a,b));c&&d&&(a=a.replace(Va,""));return H(a)||!isNaN(parseFloat(a))&&isFinite(a)},Qb=function(a,b,c){return H(a)?!0:!(H(a)||"string"===typeof a)?null:Xa(a.replace(wa,""),b,c)?!0:null},C=function(a,b,c){var d=
[],e=0,f=a.length;if(c!==l)for(;e<f;e++)a[e]&&a[e][b]&&d.push(a[e][b][c]);else for(;e<f;e++)a[e]&&d.push(a[e][b]);return d},xa=function(a,b,c,d){var e=[],f=0,g=b.length;if(d!==l)for(;f<g;f++)e.push(a[b[f]][c][d]);else for(;f<g;f++)e.push(a[b[f]][c]);return e},S=function(a,b){var c=[],d;b===l?(b=0,d=a):(d=b,b=a);for(var e=b;e<d;e++)c.push(e);return c},Ja=function(a){var b=[],c,d,e=a.length,f,g=0;d=0;a:for(;d<e;d++){c=a[d];for(f=0;f<g;f++)if(b[f]===c)continue a;b.push(c);g++}return b},w=function(a,
b,c){a[b]!==l&&(a[c]=a[b])},$=/\[.*?\]$/,Q=/\(\)$/,qa=h("<div>")[0],Wb=qa.textContent!==l,Xb=/<.*?>/g;p=function(a){this.$=function(a,b){return this.api(!0).$(a,b)};this._=function(a,b){return this.api(!0).rows(a,b).data()};this.api=function(a){return a?new q(ua(this[t.iApiIndex])):new q(this)};this.fnAddData=function(a,b){var c=this.api(!0),d=h.isArray(a)&&(h.isArray(a[0])||h.isPlainObject(a[0]))?c.rows.add(a):c.row.add(a);(b===l||b)&&c.draw();return d.flatten().toArray()};this.fnAdjustColumnSizing=
function(a){var b=this.api(!0).columns.adjust(),c=b.settings()[0],d=c.oScroll;a===l||a?b.draw(!1):(""!==d.sX||""!==d.sY)&&W(c)};this.fnClearTable=function(a){var b=this.api(!0).clear();(a===l||a)&&b.draw()};this.fnClose=function(a){this.api(!0).row(a).child.hide()};this.fnDeleteRow=function(a,b,c){var d=this.api(!0),a=d.rows(a),e=a.settings()[0],h=e.aoData[a[0][0]];a.remove();b&&b.call(this,e,h);(c===l||c)&&d.draw();return h};this.fnDestroy=function(a){this.api(!0).destroy(a)};this.fnDraw=function(a){this.api(!0).draw(!a)};
this.fnFilter=function(a,b,c,d,e,h){e=this.api(!0);null===b||b===l?e.search(a,c,d,h):e.column(b).search(a,c,d,h);e.draw()};this.fnGetData=function(a,b){var c=this.api(!0);if(a!==l){var d=a.nodeName?a.nodeName.toLowerCase():"";return b!==l||"td"==d||"th"==d?c.cell(a,b).data():c.row(a).data()||null}return c.data().toArray()};this.fnGetNodes=function(a){var b=this.api(!0);return a!==l?b.row(a).node():b.rows().nodes().flatten().toArray()};this.fnGetPosition=function(a){var b=this.api(!0),c=a.nodeName.toUpperCase();
return"TR"==c?b.row(a).index():"TD"==c||"TH"==c?(a=b.cell(a).index(),[a.row,a.columnVisible,a.column]):null};this.fnIsOpen=function(a){return this.api(!0).row(a).child.isShown()};this.fnOpen=function(a,b,c){return this.api(!0).row(a).child(b,c).show().child()[0]};this.fnPageChange=function(a,b){var c=this.api(!0).page(a);(b===l||b)&&c.draw(!1)};this.fnSetColumnVis=function(a,b,c){a=this.api(!0).column(a).visible(b);(c===l||c)&&a.columns.adjust().draw()};this.fnSettings=function(){return ua(this[t.iApiIndex])};
this.fnSort=function(a){this.api(!0).order(a).draw()};this.fnSortListener=function(a,b,c){this.api(!0).order.listener(a,b,c)};this.fnUpdate=function(a,b,c,d,e){var h=this.api(!0);c===l||null===c?h.row(b).data(a):h.cell(b,c).data(a);(e===l||e)&&h.columns.adjust();(d===l||d)&&h.draw();return 0};this.fnVersionCheck=t.fnVersionCheck;var b=this,c=a===l,d=this.length;c&&(a={});this.oApi=this.internal=t.internal;for(var e in p.ext.internal)e&&(this[e]=Mb(e));this.each(function(){var e={},g=1<d?Kb(e,a,!0):
a,j=0,i,o=this.getAttribute("id"),e=!1,m=p.defaults;if("table"!=this.nodeName.toLowerCase())P(null,0,"Non-table node initialisation ("+this.nodeName+")",2);else{db(m);eb(m.column);G(m,m,!0);G(m.column,m.column,!0);G(m,g);var n=p.settings,j=0;for(i=n.length;j<i;j++){if(n[j].nTable==this){i=g.bRetrieve!==l?g.bRetrieve:m.bRetrieve;if(c||i)return n[j].oInstance;if(g.bDestroy!==l?g.bDestroy:m.bDestroy){n[j].oInstance.fnDestroy();break}else{P(n[j],0,"Cannot reinitialise DataTable",3);return}}if(n[j].sTableId==
this.id){n.splice(j,1);break}}if(null===o||""===o)this.id=o="DataTables_Table_"+p.ext._unique++;var k=h.extend(!0,{},p.models.oSettings,{nTable:this,oApi:b.internal,oInit:g,sDestroyWidth:h(this)[0].style.width,sInstance:o,sTableId:o});n.push(k);k.oInstance=1===b.length?b:h(this).dataTable();db(g);g.oLanguage&&N(g.oLanguage);g.aLengthMenu&&!g.iDisplayLength&&(g.iDisplayLength=h.isArray(g.aLengthMenu[0])?g.aLengthMenu[0][0]:g.aLengthMenu[0]);g=Kb(h.extend(!0,{},m),g);D(k.oFeatures,g,"bPaginate bLengthChange bFilter bSort bSortMulti bInfo bProcessing bAutoWidth bSortClasses bServerSide bDeferRender".split(" "));
D(k,g,["asStripeClasses","ajax","fnServerData","fnFormatNumber","sServerMethod","aaSorting","aaSortingFixed","aLengthMenu","sPaginationType","sAjaxSource","sAjaxDataProp","iStateDuration","sDom","bSortCellsTop","iTabIndex","fnStateLoadCallback","fnStateSaveCallback","renderer",["iCookieDuration","iStateDuration"],["oSearch","oPreviousSearch"],["aoSearchCols","aoPreSearchCols"],["iDisplayLength","_iDisplayLength"],["bJQueryUI","bJUI"]]);D(k.oScroll,g,[["sScrollX","sX"],["sScrollXInner","sXInner"],
["sScrollY","sY"],["bScrollCollapse","bCollapse"]]);D(k.oLanguage,g,"fnInfoCallback");x(k,"aoDrawCallback",g.fnDrawCallback,"user");x(k,"aoServerParams",g.fnServerParams,"user");x(k,"aoStateSaveParams",g.fnStateSaveParams,"user");x(k,"aoStateLoadParams",g.fnStateLoadParams,"user");x(k,"aoStateLoaded",g.fnStateLoaded,"user");x(k,"aoRowCallback",g.fnRowCallback,"user");x(k,"aoRowCreatedCallback",g.fnCreatedRow,"user");x(k,"aoHeaderCallback",g.fnHeaderCallback,"user");x(k,"aoFooterCallback",g.fnFooterCallback,
"user");x(k,"aoInitComplete",g.fnInitComplete,"user");x(k,"aoPreDrawCallback",g.fnPreDrawCallback,"user");o=k.oClasses;g.bJQueryUI?(h.extend(o,p.ext.oJUIClasses,g.oClasses),g.sDom===m.sDom&&"lfrtip"===m.sDom&&(k.sDom='<"H"lfr>t<"F"ip>'),k.renderer)?h.isPlainObject(k.renderer)&&!k.renderer.header&&(k.renderer.header="jqueryui"):k.renderer="jqueryui":h.extend(o,p.ext.classes,g.oClasses);h(this).addClass(o.sTable);if(""!==k.oScroll.sX||""!==k.oScroll.sY)k.oScroll.iBarWidth=Gb();!0===k.oScroll.sX&&(k.oScroll.sX=
"100%");k.iInitDisplayStart===l&&(k.iInitDisplayStart=g.iDisplayStart,k._iDisplayStart=g.iDisplayStart);null!==g.iDeferLoading&&(k.bDeferLoading=!0,j=h.isArray(g.iDeferLoading),k._iRecordsDisplay=j?g.iDeferLoading[0]:g.iDeferLoading,k._iRecordsTotal=j?g.iDeferLoading[1]:g.iDeferLoading);""!==g.oLanguage.sUrl?(k.oLanguage.sUrl=g.oLanguage.sUrl,h.getJSON(k.oLanguage.sUrl,null,function(a){N(a);G(m.oLanguage,a);h.extend(true,k.oLanguage,g.oLanguage,a);ra(k)}),e=!0):h.extend(!0,k.oLanguage,g.oLanguage);
null===g.asStripeClasses&&(k.asStripeClasses=[o.sStripeOdd,o.sStripeEven]);var j=k.asStripeClasses,r=h("tbody tr:eq(0)",this);-1!==h.inArray(!0,h.map(j,function(a){return r.hasClass(a)}))&&(h("tbody tr",this).removeClass(j.join(" ")),k.asDestroyStripes=j.slice());var n=[],q,j=this.getElementsByTagName("thead");0!==j.length&&(aa(k.aoHeader,j[0]),n=ma(k));if(null===g.aoColumns){q=[];j=0;for(i=n.length;j<i;j++)q.push(null)}else q=g.aoColumns;j=0;for(i=q.length;j<i;j++)Aa(k,n?n[j]:null);hb(k,g.aoColumnDefs,
q,function(a,b){fa(k,a,b)});if(r.length){var s=function(a,b){return a.getAttribute("data-"+b)?b:null};h.each(ia(k,r[0]).cells,function(a,b){var c=k.aoColumns[a];if(c.mData===a){var d=s(b,"sort")||s(b,"order"),e=s(b,"filter")||s(b,"search");if(d!==null||e!==null){c.mData={_:a+".display",sort:d!==null?a+".@data-"+d:l,type:d!==null?a+".@data-"+d:l,filter:e!==null?a+".@data-"+e:l};fa(k,a)}}})}var t=k.oFeatures;g.bStateSave&&(t.bStateSave=!0,Jb(k,g),x(k,"aoDrawCallback",ta,"state_save"));if(g.aaSorting===
l){n=k.aaSorting;j=0;for(i=n.length;j<i;j++)n[j][1]=k.aoColumns[j].asSorting[0]}sa(k);t.bSort&&x(k,"aoDrawCallback",function(){if(k.bSorted){var a=R(k),b={};h.each(a,function(a,c){b[c.src]=c.dir});u(k,null,"order",[k,a,b]);Ib(k)}});x(k,"aoDrawCallback",function(){(k.bSorted||z(k)==="ssp"||t.bDeferRender)&&sa(k)},"sc");fb(k);j=h(this).children("caption").each(function(){this._captionSide=h(this).css("caption-side")});i=h(this).children("thead");0===i.length&&(i=h("<thead/>").appendTo(this));k.nTHead=
i[0];i=h(this).children("tbody");0===i.length&&(i=h("<tbody/>").appendTo(this));k.nTBody=i[0];i=h(this).children("tfoot");if(0===i.length&&0<j.length&&(""!==k.oScroll.sX||""!==k.oScroll.sY))i=h("<tfoot/>").appendTo(this);0===i.length||0===i.children().length?h(this).addClass(o.sNoFooter):0<i.length&&(k.nTFoot=i[0],aa(k.aoFooter,k.nTFoot));if(g.aaData)for(j=0;j<g.aaData.length;j++)I(k,g.aaData[j]);else(k.bDeferLoading||"dom"==z(k))&&ha(k,h(k.nTBody).children("tr"));k.aiDisplay=k.aiDisplayMaster.slice();
k.bInitialised=!0;!1===e&&ra(k)}});b=null;return this};var Rb=[],y=Array.prototype,$b=function(a){var b,c,d=p.settings,e=h.map(d,function(a){return a.nTable});if(a){if(a.nTable&&a.oApi)return[a];if(a.nodeName&&"table"===a.nodeName.toLowerCase())return b=h.inArray(a,e),-1!==b?[d[b]]:null;if(a&&"function"===typeof a.settings)return a.settings().toArray();"string"===typeof a?c=h(a):a instanceof h&&(c=a)}else return[];if(c)return c.map(function(){b=h.inArray(this,e);return-1!==b?d[b]:null}).toArray()};
q=function(a,b){if(!this instanceof q)throw"DT API must be constructed as a new object";var c=[],d=function(a){(a=$b(a))&&c.push.apply(c,a)};if(h.isArray(a))for(var e=0,f=a.length;e<f;e++)d(a[e]);else d(a);this.context=Ja(c);b&&this.push.apply(this,b.toArray?b.toArray():b);this.selector={rows:null,cols:null,opts:null};q.extend(this,this,Rb)};p.Api=q;q.prototype={concat:y.concat,context:[],each:function(a){for(var b=0,c=this.length;b<c;b++)a.call(this,this[b],b,this);return this},eq:function(a){var b=
this.context;return b.length>a?new q(b[a],this[a]):null},filter:function(a){var b=[];if(y.filter)b=y.filter.call(this,a,this);else for(var c=0,d=this.length;c<d;c++)a.call(this,this[c],c,this)&&b.push(this[c]);return new q(this.context,b)},flatten:function(){var a=[];return new q(this.context,a.concat.apply(a,this.toArray()))},join:y.join,indexOf:y.indexOf||function(a,b){for(var c=b||0,d=this.length;c<d;c++)if(this[c]===a)return c;return-1},iterator:function(a,b,c){var d=[],e,f,g,h,i,o=this.context,
m,n,k=this.selector;"string"===typeof a&&(c=b,b=a,a=!1);f=0;for(g=o.length;f<g;f++)if("table"===b)e=c(o[f],f),e!==l&&d.push(e);else if("columns"===b||"rows"===b)e=c(o[f],this[f],f),e!==l&&d.push(e);else if("column"===b||"column-rows"===b||"row"===b||"cell"===b){n=this[f];"column-rows"===b&&(m=Ya(o[f],k.opts));h=0;for(i=n.length;h<i;h++)e=n[h],e="cell"===b?c(o[f],e.row,e.column,f,h):c(o[f],e,f,h,m),e!==l&&d.push(e)}return d.length?(a=new q(o,a?d.concat.apply([],d):d),b=a.selector,b.rows=k.rows,b.cols=
k.cols,b.opts=k.opts,a):this},lastIndexOf:y.lastIndexOf||function(a,b){return this.indexOf.apply(this.toArray.reverse(),arguments)},length:0,map:function(a){var b=[];if(y.map)b=y.map.call(this,a,this);else for(var c=0,d=this.length;c<d;c++)b.push(a.call(this,this[c],c));return new q(this.context,b)},pluck:function(a){return this.map(function(b){return b[a]})},pop:y.pop,push:y.push,reduce:y.reduce||function(a,b){return gb(this,a,b,0,this.length,1)},reduceRight:y.reduceRight||function(a,b){return gb(this,
a,b,this.length-1,-1,-1)},reverse:y.reverse,selector:null,shift:y.shift,sort:y.sort,splice:y.splice,toArray:function(){return y.slice.call(this)},to$:function(){return h(this)},toJQuery:function(){return h(this)},unique:function(){return new q(this.context,Ja(this))},unshift:y.unshift};q.extend=function(a,b,c){if(b&&(b instanceof q||b.__dt_wrapper)){var d,e,f,g=function(a,b,c){return function(){var d=b.apply(a,arguments);q.extend(d,d,c.methodExt);return d}};d=0;for(e=c.length;d<e;d++)f=c[d],b[f.name]=
"function"===typeof f.val?g(a,f.val,f):h.isPlainObject(f.val)?{}:f.val,b[f.name].__dt_wrapper=!0,q.extend(a,b[f.name],f.propExt)}};q.register=r=function(a,b){if(h.isArray(a))for(var c=0,d=a.length;c<d;c++)q.register(a[c],b);else for(var e=a.split("."),f=Rb,g,j,c=0,d=e.length;c<d;c++){g=(j=-1!==e[c].indexOf("()"))?e[c].replace("()",""):e[c];var i;a:{i=0;for(var o=f.length;i<o;i++)if(f[i].name===g){i=f[i];break a}i=null}i||(i={name:g,val:{},methodExt:[],propExt:[]},f.push(i));c===d-1?i.val=b:f=j?i.methodExt:
i.propExt}};q.registerPlural=v=function(a,b,c){q.register(a,c);q.register(b,function(){var a=c.apply(this,arguments);return a===this?this:a instanceof q?a.length?h.isArray(a[0])?new q(a.context,a[0]):a[0]:l:a})};r("tables()",function(a){var b;if(a){b=q;var c=this.context;if("number"===typeof a)a=[c[a]];else var d=h.map(c,function(a){return a.nTable}),a=h(d).filter(a).map(function(){var a=h.inArray(this,d);return c[a]}).toArray();b=new b(a)}else b=this;return b});r("table()",function(a){var a=this.tables(a),
b=a.context;return b.length?new q(b[0]):a});v("tables().nodes()","table().node()",function(){return this.iterator("table",function(a){return a.nTable})});v("tables().body()","table().body()",function(){return this.iterator("table",function(a){return a.nTBody})});v("tables().header()","table().header()",function(){return this.iterator("table",function(a){return a.nTHead})});v("tables().footer()","table().footer()",function(){return this.iterator("table",function(a){return a.nTFoot})});v("tables().containers()",
"table().container()",function(){return this.iterator("table",function(a){return a.nTableWrapper})});r("draw()",function(a){return this.iterator("table",function(b){L(b,!1===a)})});r("page()",function(a){return a===l?this.page.info().page:this.iterator("table",function(b){Ra(b,a)})});r("page.info()",function(){if(0===this.context.length)return l;var a=this.context[0],b=a._iDisplayStart,c=a._iDisplayLength,d=a.fnRecordsDisplay(),e=-1===c;return{page:e?0:Math.floor(b/c),pages:e?1:Math.ceil(d/c),start:b,
end:a.fnDisplayEnd(),length:c,recordsTotal:a.fnRecordsTotal(),recordsDisplay:d}});r("page.len()",function(a){return a===l?0!==this.context.length?this.context[0]._iDisplayLength:l:this.iterator("table",function(b){Pa(b,a)})});var Sb=function(a,b,c){"ssp"==z(a)?L(a,b):(B(a,!0),na(a,[],function(c){ja(a);for(var c=oa(a,c),d=0,g=c.length;d<g;d++)I(a,c[d]);L(a,b);B(a,!1)}));if(c){var d=new q(a);d.one("draw",function(){c(d.ajax.json())})}};r("ajax.json()",function(){var a=this.context;if(0<a.length)return a[0].json});
r("ajax.params()",function(){var a=this.context;if(0<a.length)return a[0].oAjaxData});r("ajax.reload()",function(a,b){return this.iterator("table",function(c){Sb(c,!1===b,a)})});r("ajax.url()",function(a){var b=this.context;if(a===l){if(0===b.length)return l;b=b[0];return b.ajax?h.isPlainObject(b.ajax)?b.ajax.url:b.ajax:b.sAjaxSource}return this.iterator("table",function(b){h.isPlainObject(b.ajax)?b.ajax.url=a:b.ajax=a})});r("ajax.url().load()",function(a,b){return this.iterator("table",function(c){Sb(c,
!1===b,a)})});var Za=function(a,b){var c=[],d,e,f,g,j,i;if(!a||"string"===typeof a||a.length===l)a=[a];f=0;for(g=a.length;f<g;f++){e=a[f]&&a[f].split?a[f].split(","):[a[f]];j=0;for(i=e.length;j<i;j++)(d=b("string"===typeof e[j]?h.trim(e[j]):e[j]))&&d.length&&c.push.apply(c,d)}return c},$a=function(a){a||(a={});a.filter&&!a.search&&(a.search=a.filter);return{search:a.search||"none",order:a.order||"current",page:a.page||"all"}},ab=function(a){for(var b=0,c=a.length;b<c;b++)if(0<a[b].length)return a[0]=
a[b],a.length=1,a.context=[a.context[b]],a;a.length=0;return a},Ya=function(a,b){var c,d,e,f=[],g=a.aiDisplay;c=a.aiDisplayMaster;var j=b.search;d=b.order;e=b.page;if("ssp"==z(a))return"removed"===j?[]:S(0,c.length);if("current"==e){c=a._iDisplayStart;for(d=a.fnDisplayEnd();c<d;c++)f.push(g[c])}else if("current"==d||"applied"==d)f="none"==j?c.slice():"applied"==j?g.slice():h.map(c,function(a){return-1===h.inArray(a,g)?a:null});else if("index"==d||"original"==d){c=0;for(d=a.aoData.length;c<d;c++)"none"==
j?f.push(c):(e=h.inArray(c,g),(-1===e&&"removed"==j||0<=e&&"applied"==j)&&f.push(c))}return f};r("rows()",function(a,b){a===l?a="":h.isPlainObject(a)&&(b=a,a="");var b=$a(b),c=this.iterator("table",function(c){var e=b;return Za(a,function(a){var b=Ob(a);if(b!==null&&!e)return[b];var j=Ya(c,e);if(b!==null&&h.inArray(b,j)!==-1)return[b];if(!a)return j;for(var b=[],i=0,o=j.length;i<o;i++)b.push(c.aoData[j[i]].nTr);return a.nodeName&&h.inArray(a,b)!==-1?[a._DT_RowIndex]:h(b).filter(a).map(function(){return this._DT_RowIndex}).toArray()})});
c.selector.rows=a;c.selector.opts=b;return c});r("rows().nodes()",function(){return this.iterator("row",function(a,b){return a.aoData[b].nTr||l})});r("rows().data()",function(){return this.iterator(!0,"rows",function(a,b){return xa(a.aoData,b,"_aData")})});v("rows().cache()","row().cache()",function(a){return this.iterator("row",function(b,c){var d=b.aoData[c];return"search"===a?d._aFilterData:d._aSortData})});v("rows().invalidate()","row().invalidate()",function(a){return this.iterator("row",function(b,
c){la(b,c,a)})});v("rows().indexes()","row().index()",function(){return this.iterator("row",function(a,b){return b})});v("rows().remove()","row().remove()",function(){var a=this;return this.iterator("row",function(b,c,d){var e=b.aoData;e.splice(c,1);for(var f=0,g=e.length;f<g;f++)null!==e[f].nTr&&(e[f].nTr._DT_RowIndex=f);h.inArray(c,b.aiDisplay);ka(b.aiDisplayMaster,c);ka(b.aiDisplay,c);ka(a[d],c,!1);Qa(b)})});r("rows.add()",function(a){var b=this.iterator("table",function(b){var c,f,g,h=[];f=0;
for(g=a.length;f<g;f++)c=a[f],c.nodeName&&"TR"===c.nodeName.toUpperCase()?h.push(ha(b,c)[0]):h.push(I(b,c));return h}),c=this.rows(-1);c.pop();c.push.apply(c,b.toArray());return c});r("row()",function(a,b){return ab(this.rows(a,b))});r("row().data()",function(a){var b=this.context;if(a===l)return b.length&&this.length?b[0].aoData[this[0]]._aData:l;b[0].aoData[this[0]]._aData=a;la(b[0],this[0],"data");return this});r("row().node()",function(){var a=this.context;return a.length&&this.length?a[0].aoData[this[0]].nTr||
null:null});r("row.add()",function(a){a instanceof h&&a.length&&(a=a[0]);var b=this.iterator("table",function(b){return a.nodeName&&"TR"===a.nodeName.toUpperCase()?ha(b,a)[0]:I(b,a)});return this.row(b[0])});var bb=function(a){var b=a.context;b.length&&a.length&&(a=b[0].aoData[a[0]],a._details&&(a._details.remove(),a._detailsShow=l,a._details=l))},Tb=function(a,b){var c=a.context;if(c.length&&a.length){var d=c[0].aoData[a[0]];if(d._details){(d._detailsShow=b)?d._details.insertAfter(d.nTr):d._details.detach();
var e=c[0],f=new q(e),g=e.aoData;f.off("draw.dt.DT_details column-visibility.dt.DT_details destroy.dt.DT_details");0<C(g,"_details").length&&(f.on("draw.dt.DT_details",function(a,b){e===b&&f.rows({page:"current"}).eq(0).each(function(a){a=g[a];a._detailsShow&&a._details.insertAfter(a.nTr)})}),f.on("column-visibility.dt.DT_details",function(a,b){if(e===b)for(var c,d=Z(b),f=0,h=g.length;f<h;f++)c=g[f],c._details&&c._details.children("td[colspan]").attr("colspan",d)}),f.on("destroy.dt.DT_details",function(a,
b){if(e===b)for(var c=0,d=g.length;c<d;c++)g[c]._details&&bb(g[c])}))}}};r("row().child()",function(a,b){var c=this.context;if(a===l)return c.length&&this.length?c[0].aoData[this[0]]._details:l;if(!0===a)this.child.show();else if(!1===a)bb(this);else if(c.length&&this.length){var d=c[0],c=c[0].aoData[this[0]],e=[],f=function(a,b){if(a.nodeName&&"tr"===a.nodeName.toLowerCase())e.push(a);else{var c=h("<tr><td/></tr>").addClass(b);h("td",c).addClass(b).html(a)[0].colSpan=Z(d);e.push(c[0])}};if(h.isArray(a)||
a instanceof h)for(var g=0,j=a.length;g<j;g++)f(a[g],b);else f(a,b);c._details&&c._details.remove();c._details=h(e);c._detailsShow&&c._details.insertAfter(c.nTr)}return this});r(["row().child.show()","row().child().show()"],function(){Tb(this,!0);return this});r(["row().child.hide()","row().child().hide()"],function(){Tb(this,!1);return this});r(["row().child.remove()","row().child().remove()"],function(){bb(this);return this});r("row().child.isShown()",function(){var a=this.context;return a.length&&
this.length?a[0].aoData[this[0]]._detailsShow||!1:!1});var ac=/^(.*):(name|visIdx|visible)$/;r("columns()",function(a,b){a===l?a="":h.isPlainObject(a)&&(b=a,a="");var b=$a(b),c=this.iterator("table",function(b){var c=a,f=b.aoColumns,g=C(f,"sName"),j=C(f,"nTh");return Za(c,function(a){var c=Ob(a);if(a==="")return S(f.length);if(c!==null)return[c>=0?c:f.length+c];var e=typeof a==="string"?a.match(ac):"";if(e)switch(e[2]){case "visIdx":case "visible":a=parseInt(e[1],10);if(a<0){c=h.map(f,function(a,
b){return a.bVisible?b:null});return[c[c.length+a]]}return[ga(b,a)];case "name":return h.map(g,function(a,b){return a===e[1]?b:null})}else return h(j).filter(a).map(function(){return h.inArray(this,j)}).toArray()})});c.selector.cols=a;c.selector.opts=b;return c});v("columns().header()","column().header()",function(){return this.iterator("column",function(a,b){return a.aoColumns[b].nTh})});v("columns().footer()","column().footer()",function(){return this.iterator("column",function(a,b){return a.aoColumns[b].nTf})});
v("columns().data()","column().data()",function(){return this.iterator("column-rows",function(a,b,c,d,e){for(var c=[],d=0,f=e.length;d<f;d++)c.push(A(a,e[d],b,""));return c})});v("columns().cache()","column().cache()",function(a){return this.iterator("column-rows",function(b,c,d,e,f){return xa(b.aoData,f,"search"===a?"_aFilterData":"_aSortData",c)})});v("columns().nodes()","column().nodes()",function(){return this.iterator("column-rows",function(a,b,c,d,e){return xa(a.aoData,e,"anCells",b)})});v("columns().visible()",
"column().visible()",function(a){return this.iterator("column",function(b,c){var d;if(a===l)d=b.aoColumns[c].bVisible;else{var e=b.aoColumns;d=e[c];var f=b.aoData,g,j,i;if(a===l)d=d.bVisible;else{if(d.bVisible!==a){if(a){var o=h.inArray(!0,C(e,"bVisible"),c+1);g=0;for(j=f.length;g<j;g++)i=f[g].nTr,e=f[g].anCells,i&&i.insertBefore(e[c],e[o]||null)}else h(C(b.aoData,"anCells",c)).detach();d.bVisible=a;ba(b,b.aoHeader);ba(b,b.aoFooter);V(b);(b.oScroll.sX||b.oScroll.sY)&&W(b);u(b,null,"column-visibility",
[b,c,a]);ta(b)}d=void 0}}return d})});v("columns().indexes()","column().index()",function(a){return this.iterator("column",function(b,c){return"visible"===a?Y(b,c):c})});r("columns.adjust()",function(){return this.iterator("table",function(a){V(a)})});r("column.index()",function(a,b){if(0!==this.context.length){var c=this.context[0];if("fromVisible"===a||"toData"===a)return ga(c,b);if("fromData"===a||"toVisible"===a)return Y(c,b)}});r("column()",function(a,b){return ab(this.columns(a,b))});r("cells()",
function(a,b,c){h.isPlainObject(a)&&(typeof a.row!==l?(c=b,b=null):(c=a,a=null));h.isPlainObject(b)&&(c=b,b=null);if(null===b||b===l)return this.iterator("table",function(b){var d=a,e=$a(c),f=b.aoData,g=Ya(b,e),e=xa(f,g,"anCells"),i=h([].concat.apply([],e)),j,m=b.aoColumns.length,o,p,r,q;return Za(d,function(a){if(a===null||a===l){o=[];p=0;for(r=g.length;p<r;p++){j=g[p];for(q=0;q<m;q++)o.push({row:j,column:q})}return o}return h.isPlainObject(a)?[a]:i.filter(a).map(function(a,b){j=b.parentNode._DT_RowIndex;
return{row:j,column:h.inArray(b,f[j].anCells)}}).toArray()})});var d=this.columns(b,c),e=this.rows(a,c),f,g,j,i,o,m=this.iterator("table",function(a,b){f=[];g=0;for(j=e[b].length;g<j;g++){i=0;for(o=d[b].length;i<o;i++)f.push({row:e[b][g],column:d[b][i]})}return f});h.extend(m.selector,{cols:b,rows:a,opts:c});return m});v("cells().nodes()","cell().node()",function(){return this.iterator("cell",function(a,b,c){return a.aoData[b].anCells[c]})});r("cells().data()",function(){return this.iterator("cell",
function(a,b,c){return A(a,b,c)})});v("cells().cache()","cell().cache()",function(a){a="search"===a?"_aFilterData":"_aSortData";return this.iterator("cell",function(b,c,d){return b.aoData[c][a][d]})});v("cells().indexes()","cell().index()",function(){return this.iterator("cell",function(a,b,c){return{row:b,column:c,columnVisible:Y(a,c)}})});r(["cells().invalidate()","cell().invalidate()"],function(a){var b=this.selector;this.rows(b.rows,b.opts).invalidate(a);return this});r("cell()",function(a,b,
c){return ab(this.cells(a,b,c))});r("cell().data()",function(a){var b=this.context,c=this[0];if(a===l)return b.length&&c.length?A(b[0],c[0].row,c[0].column):l;Ea(b[0],c[0].row,c[0].column,a);la(b[0],c[0].row,"data",c[0].column);return this});r("order()",function(a,b){var c=this.context;if(a===l)return 0!==c.length?c[0].aaSorting:l;"number"===typeof a?a=[[a,b]]:h.isArray(a[0])||(a=Array.prototype.slice.call(arguments));return this.iterator("table",function(b){b.aaSorting=a.slice()})});r("order.listener()",
function(a,b,c){return this.iterator("table",function(d){Ka(d,a,b,c)})});r(["columns().order()","column().order()"],function(a){var b=this;return this.iterator("table",function(c,d){var e=[];h.each(b[d],function(b,c){e.push([c,a])});c.aaSorting=e})});r("search()",function(a,b,c,d){var e=this.context;return a===l?0!==e.length?e[0].oPreviousSearch.sSearch:l:this.iterator("table",function(e){e.oFeatures.bFilter&&ca(e,h.extend({},e.oPreviousSearch,{sSearch:a+"",bRegex:null===b?!1:b,bSmart:null===c?!0:
c,bCaseInsensitive:null===d?!0:d}),1)})});r(["columns().search()","column().search()"],function(a,b,c,d){return this.iterator("column",function(e,f){var g=e.aoPreSearchCols;if(a===l)return g[f].sSearch;e.oFeatures.bFilter&&(h.extend(g[f],{sSearch:a+"",bRegex:null===b?!1:b,bSmart:null===c?!0:c,bCaseInsensitive:null===d?!0:d}),ca(e,e.oPreviousSearch,1))})});r("state()",function(){return this.context.length?this.context[0].oSavedState:null});r("state.clear()",function(){return this.iterator("table",
function(a){a.fnStateSaveCallback.call(a.oInstance,a,{})})});r("state.loaded()",function(){return this.context.length?this.context[0].oLoadedState:null});r("state.save()",function(){return this.iterator("table",function(a){ta(a)})});p.versionCheck=p.fnVersionCheck=function(a){for(var b=p.version.split("."),a=a.split("."),c,d,e=0,f=a.length;e<f;e++)if(c=parseInt(b[e],10)||0,d=parseInt(a[e],10)||0,c!==d)return c>d;return!0};p.isDataTable=p.fnIsDataTable=function(a){var b=h(a).get(0),c=!1;h.each(p.settings,
function(a,e){if(e.nTable===b||e.nScrollHead===b||e.nScrollFoot===b)c=!0});return c};p.tables=p.fnTables=function(a){return jQuery.map(p.settings,function(b){if(!a||a&&h(b.nTable).is(":visible"))return b.nTable})};p.camelToHungarian=G;r("$()",function(a,b){var c=this.rows(b).nodes(),c=h(c);return h([].concat(c.filter(a).toArray(),c.find(a).toArray()))});h.each(["on","one","off"],function(a,b){r(b+"()",function(){var a=Array.prototype.slice.call(arguments);a[0].match(/\.dt\b/)||(a[0]+=".dt");var d=
h(this.tables().nodes());d[b].apply(d,a);return this})});r("clear()",function(){return this.iterator("table",function(a){ja(a)})});r("settings()",function(){return new q(this.context,this.context)});r("data()",function(){return this.iterator("table",function(a){return C(a.aoData,"_aData")}).flatten()});r("destroy()",function(a){a=a||!1;return this.iterator("table",function(b){var c=b.nTableWrapper.parentNode,d=b.oClasses,e=b.nTable,f=b.nTBody,g=b.nTHead,j=b.nTFoot,i=h(e),f=h(f),l=h(b.nTableWrapper),
m=h.map(b.aoData,function(a){return a.nTr}),n;b.bDestroying=!0;u(b,"aoDestroyCallback","destroy",[b]);a||(new q(b)).columns().visible(!0);l.unbind(".DT").find(":not(tbody *)").unbind(".DT");h(za).unbind(".DT-"+b.sInstance);e!=g.parentNode&&(i.children("thead").detach(),i.append(g));j&&e!=j.parentNode&&(i.children("tfoot").detach(),i.append(j));i.detach();l.detach();b.aaSorting=[];b.aaSortingFixed=[];sa(b);h(m).removeClass(b.asStripeClasses.join(" "));h("th, td",g).removeClass(d.sSortable+" "+d.sSortableAsc+
" "+d.sSortableDesc+" "+d.sSortableNone);b.bJUI&&(h("th span."+d.sSortIcon+", td span."+d.sSortIcon,g).detach(),h("th, td",g).each(function(){var a=h("div."+d.sSortJUIWrapper,this);h(this).append(a.contents());a.detach()}));!a&&c&&c.insertBefore(e,b.nTableReinsertBefore);f.children().detach();f.append(m);i.css("width",b.sDestroyWidth).removeClass(d.sTable);(n=b.asDestroyStripes.length)&&f.children().each(function(a){h(this).addClass(b.asDestroyStripes[a%n])});c=h.inArray(b,p.settings);-1!==c&&p.settings.splice(c,
1)})});p.version="1.10.1";p.settings=[];p.models={};p.models.oSearch={bCaseInsensitive:!0,sSearch:"",bRegex:!1,bSmart:!0};p.models.oRow={nTr:null,anCells:null,_aData:[],_aSortData:null,_aFilterData:null,_sFilterRow:null,_sRowStripe:"",src:null};p.models.oColumn={idx:null,aDataSort:null,asSorting:null,bSearchable:null,bSortable:null,bVisible:null,_sManualType:null,_bAttrSrc:!1,fnCreatedCell:null,fnGetData:null,fnSetData:null,mData:null,mRender:null,nTh:null,nTf:null,sClass:null,sContentPadding:null,
sDefaultContent:null,sName:null,sSortDataType:"std",sSortingClass:null,sSortingClassJUI:null,sTitle:null,sType:null,sWidth:null,sWidthOrig:null};p.defaults={aaData:null,aaSorting:[[0,"asc"]],aaSortingFixed:[],ajax:null,aLengthMenu:[10,25,50,100],aoColumns:null,aoColumnDefs:null,aoSearchCols:[],asStripeClasses:null,bAutoWidth:!0,bDeferRender:!1,bDestroy:!1,bFilter:!0,bInfo:!0,bJQueryUI:!1,bLengthChange:!0,bPaginate:!0,bProcessing:!1,bRetrieve:!1,bScrollCollapse:!1,bServerSide:!1,bSort:!0,bSortMulti:!0,
bSortCellsTop:!1,bSortClasses:!0,bStateSave:!1,fnCreatedRow:null,fnDrawCallback:null,fnFooterCallback:null,fnFormatNumber:function(a){return a.toString().replace(/\B(?=(\d{3})+(?!\d))/g,this.oLanguage.sThousands)},fnHeaderCallback:null,fnInfoCallback:null,fnInitComplete:null,fnPreDrawCallback:null,fnRowCallback:null,fnServerData:null,fnServerParams:null,fnStateLoadCallback:function(a){try{return JSON.parse((-1===a.iStateDuration?sessionStorage:localStorage).getItem("DataTables_"+a.sInstance+"_"+location.pathname))}catch(b){}},
fnStateLoadParams:null,fnStateLoaded:null,fnStateSaveCallback:function(a,b){try{(-1===a.iStateDuration?sessionStorage:localStorage).setItem("DataTables_"+a.sInstance+"_"+location.pathname,JSON.stringify(b))}catch(c){}},fnStateSaveParams:null,iStateDuration:7200,iDeferLoading:null,iDisplayLength:10,iDisplayStart:0,iTabIndex:0,oClasses:{},oLanguage:{oAria:{sSortAscending:": activate to sort column ascending",sSortDescending:": activate to sort column descending"},oPaginate:{sFirst:"First",sLast:"Last",
sNext:"Next",sPrevious:"Previous"},sEmptyTable:"No data available in table",sInfo:"Showing _START_ to _END_ of _TOTAL_ entries",sInfoEmpty:"Showing 0 to 0 of 0 entries",sInfoFiltered:"(filtered from _MAX_ total entries)",sInfoPostFix:"",sDecimal:"",sThousands:",",sLengthMenu:"Show _MENU_ entries",sLoadingRecords:"Loading...",sProcessing:"Processing...",sSearch:"Search:",sSearchPlaceholder:"",sUrl:"",sZeroRecords:"No matching records found"},oSearch:h.extend({},p.models.oSearch),sAjaxDataProp:"data",
sAjaxSource:null,sDom:"lfrtip",sPaginationType:"simple_numbers",sScrollX:"",sScrollXInner:"",sScrollY:"",sServerMethod:"GET",renderer:null};T(p.defaults);p.defaults.column={aDataSort:null,iDataSort:-1,asSorting:["asc","desc"],bSearchable:!0,bSortable:!0,bVisible:!0,fnCreatedCell:null,mData:null,mRender:null,sCellType:"td",sClass:"",sContentPadding:"",sDefaultContent:null,sName:"",sSortDataType:"std",sTitle:null,sType:null,sWidth:null};T(p.defaults.column);p.models.oSettings={oFeatures:{bAutoWidth:null,
bDeferRender:null,bFilter:null,bInfo:null,bLengthChange:null,bPaginate:null,bProcessing:null,bServerSide:null,bSort:null,bSortMulti:null,bSortClasses:null,bStateSave:null},oScroll:{bCollapse:null,iBarWidth:0,sX:null,sXInner:null,sY:null},oLanguage:{fnInfoCallback:null},oBrowser:{bScrollOversize:!1,bScrollbarLeft:!1},ajax:null,aanFeatures:[],aoData:[],aiDisplay:[],aiDisplayMaster:[],aoColumns:[],aoHeader:[],aoFooter:[],oPreviousSearch:{},aoPreSearchCols:[],aaSorting:null,aaSortingFixed:[],asStripeClasses:null,
asDestroyStripes:[],sDestroyWidth:0,aoRowCallback:[],aoHeaderCallback:[],aoFooterCallback:[],aoDrawCallback:[],aoRowCreatedCallback:[],aoPreDrawCallback:[],aoInitComplete:[],aoStateSaveParams:[],aoStateLoadParams:[],aoStateLoaded:[],sTableId:"",nTable:null,nTHead:null,nTFoot:null,nTBody:null,nTableWrapper:null,bDeferLoading:!1,bInitialised:!1,aoOpenRows:[],sDom:null,sPaginationType:"two_button",iStateDuration:0,aoStateSave:[],aoStateLoad:[],oSavedState:null,oLoadedState:null,sAjaxSource:null,sAjaxDataProp:null,
bAjaxDataGet:!0,jqXHR:null,json:l,oAjaxData:l,fnServerData:null,aoServerParams:[],sServerMethod:null,fnFormatNumber:null,aLengthMenu:null,iDraw:0,bDrawing:!1,iDrawError:-1,_iDisplayLength:10,_iDisplayStart:0,_iRecordsTotal:0,_iRecordsDisplay:0,bJUI:null,oClasses:{},bFiltered:!1,bSorted:!1,bSortCellsTop:null,oInit:null,aoDestroyCallback:[],fnRecordsTotal:function(){return"ssp"==z(this)?1*this._iRecordsTotal:this.aiDisplayMaster.length},fnRecordsDisplay:function(){return"ssp"==z(this)?1*this._iRecordsDisplay:
this.aiDisplay.length},fnDisplayEnd:function(){var a=this._iDisplayLength,b=this._iDisplayStart,c=b+a,d=this.aiDisplay.length,e=this.oFeatures,f=e.bPaginate;return e.bServerSide?!1===f||-1===a?b+d:Math.min(b+a,this._iRecordsDisplay):!f||c>d||-1===a?d:c},oInstance:null,sInstance:null,iTabIndex:0,nScrollHead:null,nScrollFoot:null,aLastSort:[],oPlugins:{}};p.ext=t={classes:{},errMode:"alert",feature:[],search:[],internal:{},legacy:{ajax:null},pager:{},renderer:{pageButton:{},header:{}},order:{},type:{detect:[],
search:{},order:{}},_unique:0,fnVersionCheck:p.fnVersionCheck,iApiIndex:0,oJUIClasses:{},sVersion:p.version};h.extend(t,{afnFiltering:t.search,aTypes:t.type.detect,ofnSearch:t.type.search,oSort:t.type.order,afnSortData:t.order,aoFeatures:t.feature,oApi:t.internal,oStdClasses:t.classes,oPagination:t.pager});h.extend(p.ext.classes,{sTable:"dataTable",sNoFooter:"no-footer",sPageButton:"paginate_button",sPageButtonActive:"current",sPageButtonDisabled:"disabled",sStripeOdd:"odd",sStripeEven:"even",sRowEmpty:"dataTables_empty",
sWrapper:"dataTables_wrapper",sFilter:"dataTables_filter",sInfo:"dataTables_info",sPaging:"dataTables_paginate paging_",sLength:"dataTables_length",sProcessing:"dataTables_processing",sSortAsc:"sorting_asc",sSortDesc:"sorting_desc",sSortable:"sorting",sSortableAsc:"sorting_asc_disabled",sSortableDesc:"sorting_desc_disabled",sSortableNone:"sorting_disabled",sSortColumn:"sorting_",sFilterInput:"",sLengthSelect:"",sScrollWrapper:"dataTables_scroll",sScrollHead:"dataTables_scrollHead",sScrollHeadInner:"dataTables_scrollHeadInner",
sScrollBody:"dataTables_scrollBody",sScrollFoot:"dataTables_scrollFoot",sScrollFootInner:"dataTables_scrollFootInner",sHeaderTH:"",sFooterTH:"",sSortJUIAsc:"",sSortJUIDesc:"",sSortJUI:"",sSortJUIAscAllowed:"",sSortJUIDescAllowed:"",sSortJUIWrapper:"",sSortIcon:"",sJUIHeader:"",sJUIFooter:""});var ya="",ya="",E=ya+"ui-state-default",ea=ya+"css_right ui-icon ui-icon-",Ub=ya+"fg-toolbar ui-toolbar ui-widget-header ui-helper-clearfix";h.extend(p.ext.oJUIClasses,p.ext.classes,{sPageButton:"fg-button ui-button "+
E,sPageButtonActive:"ui-state-disabled",sPageButtonDisabled:"ui-state-disabled",sPaging:"dataTables_paginate fg-buttonset ui-buttonset fg-buttonset-multi ui-buttonset-multi paging_",sSortAsc:E+" sorting_asc",sSortDesc:E+" sorting_desc",sSortable:E+" sorting",sSortableAsc:E+" sorting_asc_disabled",sSortableDesc:E+" sorting_desc_disabled",sSortableNone:E+" sorting_disabled",sSortJUIAsc:ea+"triangle-1-n",sSortJUIDesc:ea+"triangle-1-s",sSortJUI:ea+"carat-2-n-s",sSortJUIAscAllowed:ea+"carat-1-n",sSortJUIDescAllowed:ea+
"carat-1-s",sSortJUIWrapper:"DataTables_sort_wrapper",sSortIcon:"DataTables_sort_icon",sScrollHead:"dataTables_scrollHead "+E,sScrollFoot:"dataTables_scrollFoot "+E,sHeaderTH:E,sFooterTH:E,sJUIHeader:Ub+" ui-corner-tl ui-corner-tr",sJUIFooter:Ub+" ui-corner-bl ui-corner-br"});var Lb=p.ext.pager;h.extend(Lb,{simple:function(){return["previous","next"]},full:function(){return["first","previous","next","last"]},simple_numbers:function(a,b){return["previous",Ua(a,b),"next"]},full_numbers:function(a,b){return["first",
"previous",Ua(a,b),"next","last"]},_numbers:Ua,numbers_length:7});h.extend(!0,p.ext.renderer,{pageButton:{_:function(a,b,c,d,e,f){var g=a.oClasses,j=a.oLanguage.oPaginate,i,l,m=0,n=function(b,d){var k,p,r,q,s=function(b){Ra(a,b.data.action,true)};k=0;for(p=d.length;k<p;k++){q=d[k];if(h.isArray(q)){r=h("<"+(q.DT_el||"div")+"/>").appendTo(b);n(r,q)}else{l=i="";switch(q){case "ellipsis":b.append("<span>&hellip;</span>");break;case "first":i=j.sFirst;l=q+(e>0?"":" "+g.sPageButtonDisabled);break;case "previous":i=
j.sPrevious;l=q+(e>0?"":" "+g.sPageButtonDisabled);break;case "next":i=j.sNext;l=q+(e<f-1?"":" "+g.sPageButtonDisabled);break;case "last":i=j.sLast;l=q+(e<f-1?"":" "+g.sPageButtonDisabled);break;default:i=q+1;l=e===q?g.sPageButtonActive:""}if(i){r=h("<a>",{"class":g.sPageButton+" "+l,"aria-controls":a.sTableId,"data-dt-idx":m,tabindex:a.iTabIndex,id:c===0&&typeof q==="string"?a.sTableId+"_"+q:null}).html(i).appendTo(b);Ta(r,{action:q},s);m++}}}};try{var k=h(O.activeElement).data("dt-idx");n(h(b).empty(),
d);k!==null&&h(b).find("[data-dt-idx="+k+"]").focus()}catch(p){}}}});var va=function(a,b,c,d){if(!a||"-"===a)return-Infinity;b&&(a=Pb(a,b));a.replace&&(c&&(a=a.replace(c,"")),d&&(a=a.replace(d,"")));return 1*a};h.extend(t.type.order,{"date-pre":function(a){return Date.parse(a)||0},"html-pre":function(a){return H(a)?"":a.replace?a.replace(/<.*?>/g,"").toLowerCase():a+""},"string-pre":function(a){return H(a)?"":"string"===typeof a?a.toLowerCase():!a.toString?"":a.toString()},"string-asc":function(a,
b){return a<b?-1:a>b?1:0},"string-desc":function(a,b){return a<b?1:a>b?-1:0}});cb("");h.extend(p.ext.type.detect,[function(a,b){var c=b.oLanguage.sDecimal;return Xa(a,c)?"num"+c:null},function(a){if(a&&(!Yb.test(a)||!Zb.test(a)))return null;var b=Date.parse(a);return null!==b&&!isNaN(b)||H(a)?"date":null},function(a,b){var c=b.oLanguage.sDecimal;return Xa(a,c,!0)?"num-fmt"+c:null},function(a,b){var c=b.oLanguage.sDecimal;return Qb(a,c)?"html-num"+c:null},function(a,b){var c=b.oLanguage.sDecimal;return Qb(a,
c,!0)?"html-num-fmt"+c:null},function(a){return H(a)||"string"===typeof a&&-1!==a.indexOf("<")?"html":null}]);h.extend(p.ext.type.search,{html:function(a){return H(a)?a:"string"===typeof a?a.replace(Nb," ").replace(wa,""):""},string:function(a){return H(a)?a:"string"===typeof a?a.replace(Nb," "):a}});h.extend(!0,p.ext.renderer,{header:{_:function(a,b,c,d){h(a.nTable).on("order.dt.DT",function(e,f,g,h){if(a===f){e=c.idx;b.removeClass(c.sSortingClass+" "+d.sSortAsc+" "+d.sSortDesc).addClass(h[e]=="asc"?
d.sSortAsc:h[e]=="desc"?d.sSortDesc:c.sSortingClass)}})},jqueryui:function(a,b,c,d){var e=c.idx;h("<div/>").addClass(d.sSortJUIWrapper).append(b.contents()).append(h("<span/>").addClass(d.sSortIcon+" "+c.sSortingClassJUI)).appendTo(b);h(a.nTable).on("order.dt.DT",function(f,g,h,i){if(a===g){b.removeClass(d.sSortAsc+" "+d.sSortDesc).addClass(i[e]=="asc"?d.sSortAsc:i[e]=="desc"?d.sSortDesc:c.sSortingClass);b.find("span."+d.sSortIcon).removeClass(d.sSortJUIAsc+" "+d.sSortJUIDesc+" "+d.sSortJUI+" "+d.sSortJUIAscAllowed+
" "+d.sSortJUIDescAllowed).addClass(i[e]=="asc"?d.sSortJUIAsc:i[e]=="desc"?d.sSortJUIDesc:c.sSortingClassJUI)}})}}});p.render={number:function(a,b,c,d){return{display:function(e){var f=0>e?"-":"",e=Math.abs(parseFloat(e)),g=parseInt(e,10),e=c?b+(e-g).toFixed(c).substring(2):"";return f+(d||"")+g.toString().replace(/\B(?=(\d{3})+(?!\d))/g,a)+e}}}};h.extend(p.ext.internal,{_fnExternApiFunc:Mb,_fnBuildAjax:na,_fnAjaxUpdate:jb,_fnAjaxParameters:sb,_fnAjaxUpdateDraw:tb,_fnAjaxDataSrc:oa,_fnAddColumn:Aa,
_fnColumnOptions:fa,_fnAdjustColumnSizing:V,_fnVisibleToColumnIndex:ga,_fnColumnIndexToVisible:Y,_fnVisbleColumns:Z,_fnGetColumns:X,_fnColumnTypes:Da,_fnApplyColumnDefs:hb,_fnHungarianMap:T,_fnCamelToHungarian:G,_fnLanguageCompat:N,_fnBrowserDetect:fb,_fnAddData:I,_fnAddTr:ha,_fnNodeToDataIndex:function(a,b){return b._DT_RowIndex!==l?b._DT_RowIndex:null},_fnNodeToColumnIndex:function(a,b,c){return h.inArray(c,a.aoData[b].anCells)},_fnGetCellData:A,_fnSetCellData:Ea,_fnSplitObjNotation:Ga,_fnGetObjectDataFn:U,
_fnSetObjectDataFn:Ba,_fnGetDataMaster:Ha,_fnClearTable:ja,_fnDeleteIndex:ka,_fnInvalidateRow:la,_fnGetRowElements:ia,_fnCreateTr:Fa,_fnBuildHead:ib,_fnDrawHead:ba,_fnDraw:K,_fnReDraw:L,_fnAddOptionsHtml:lb,_fnDetectHeader:aa,_fnGetUniqueThs:ma,_fnFeatureHtmlFilter:nb,_fnFilterComplete:ca,_fnFilterCustom:wb,_fnFilterColumn:vb,_fnFilter:ub,_fnFilterCreateSearch:Na,_fnEscapeRegex:Oa,_fnFilterData:xb,_fnFeatureHtmlInfo:qb,_fnUpdateInfo:Ab,_fnInfoMacros:Bb,_fnInitialise:ra,_fnInitComplete:pa,_fnLengthChange:Pa,
_fnFeatureHtmlLength:mb,_fnFeatureHtmlPaginate:rb,_fnPageChange:Ra,_fnFeatureHtmlProcessing:ob,_fnProcessingDisplay:B,_fnFeatureHtmlTable:pb,_fnScrollDraw:W,_fnApplyToChildren:F,_fnCalculateColumnWidths:Ca,_fnThrottle:Ma,_fnConvertToWidth:Cb,_fnScrollingWidthAdjust:Eb,_fnGetWidestNode:Db,_fnGetMaxLenString:Fb,_fnStringToCss:s,_fnScrollBarWidth:Gb,_fnSortFlatten:R,_fnSort:kb,_fnSortAria:Ib,_fnSortListener:Sa,_fnSortAttachListener:Ka,_fnSortingClasses:sa,_fnSortData:Hb,_fnSaveState:ta,_fnLoadState:Jb,
_fnSettingsFromNode:ua,_fnLog:P,_fnMap:D,_fnBindAction:Ta,_fnCallbackReg:x,_fnCallbackFire:u,_fnLengthOverflow:Qa,_fnRenderer:La,_fnDataSource:z,_fnRowAttributes:Ia,_fnCalculateEnd:function(){}});h.fn.dataTable=p;h.fn.dataTableSettings=p.settings;h.fn.dataTableExt=p.ext;h.fn.DataTable=function(a){return h(this).dataTable(a).api()};h.each(p,function(a,b){h.fn.DataTable[a]=b});return h.fn.dataTable};"function"===typeof define&&define.amd?define("datatables",["jquery"],N):"object"===typeof exports?N(require("jquery")):
jQuery&&!jQuery.fn.dataTable&&N(jQuery)})(window,document);

View File

@ -0,0 +1,23 @@
/*! jQuery JSON plugin 2.4.0 | code.google.com/p/jquery-json */
(function($){'use strict';var escape=/["\\\x00-\x1f\x7f-\x9f]/g,meta={'\b':'\\b','\t':'\\t','\n':'\\n','\f':'\\f','\r':'\\r','"':'\\"','\\':'\\\\'},hasOwn=Object.prototype.hasOwnProperty;$.toJSON=typeof JSON==='object'&&JSON.stringify?JSON.stringify:function(o){if(o===null){return'null';}
var pairs,k,name,val,type=$.type(o);if(type==='undefined'){return undefined;}
if(type==='number'||type==='boolean'){return String(o);}
if(type==='string'){return $.quoteString(o);}
if(typeof o.toJSON==='function'){return $.toJSON(o.toJSON());}
if(type==='date'){var month=o.getUTCMonth()+1,day=o.getUTCDate(),year=o.getUTCFullYear(),hours=o.getUTCHours(),minutes=o.getUTCMinutes(),seconds=o.getUTCSeconds(),milli=o.getUTCMilliseconds();if(month<10){month='0'+month;}
if(day<10){day='0'+day;}
if(hours<10){hours='0'+hours;}
if(minutes<10){minutes='0'+minutes;}
if(seconds<10){seconds='0'+seconds;}
if(milli<100){milli='0'+milli;}
if(milli<10){milli='0'+milli;}
return'"'+year+'-'+month+'-'+day+'T'+
hours+':'+minutes+':'+seconds+'.'+milli+'Z"';}
pairs=[];if($.isArray(o)){for(k=0;k<o.length;k++){pairs.push($.toJSON(o[k])||'null');}
return'['+pairs.join(',')+']';}
if(typeof o==='object'){for(k in o){if(hasOwn.call(o,k)){type=typeof k;if(type==='number'){name='"'+k+'"';}else if(type==='string'){name=$.quoteString(k);}else{continue;}
type=typeof o[k];if(type!=='function'&&type!=='undefined'){val=$.toJSON(o[k]);pairs.push(name+':'+val);}}}
return'{'+pairs.join(',')+'}';}};$.evalJSON=typeof JSON==='object'&&JSON.parse?JSON.parse:function(str){return eval('('+str+')');};$.secureEvalJSON=typeof JSON==='object'&&JSON.parse?JSON.parse:function(str){var filtered=str.replace(/\\["\\\/bfnrtu]/g,'@').replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,']').replace(/(?:^|:|,)(?:\s*\[)+/g,'');if(/^[\],:{}\s]*$/.test(filtered)){return eval('('+str+')');}
throw new SyntaxError('Error parsing JSON, source is not valid.');};$.quoteString=function(str){if(str.match(escape)){return'"'+str.replace(escape,function(a){var c=meta[a];if(typeof c==='string'){return c;}
c=a.charCodeAt();return'\\u00'+Math.floor(c/16).toString(16)+(c%16).toString(16);})+'"';}
return'"'+str+'"';};}(jQuery));

View File

@ -0,0 +1,716 @@
/*
* Verto HTML5/Javascript Telephony Signaling and Control Protocol Stack for FreeSWITCH
* Copyright (C) 2005-2014, Anthony Minessale II <anthm@freeswitch.org>
*
* Version: MPL 1.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is jquery.jsonrpclient.js modified for Verto HTML5/Javascript Telephony Signaling and Control Protocol Stack for FreeSWITCH
*
* The Initial Developer of the Original Code is
* Textalk AB http://textalk.se/
* Portions created by the Initial Developer are Copyright (C)
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Anthony Minessale II <anthm@freeswitch.org>
*
* jquery.jsonrpclient.js - JSON RPC client code
*
*/
/**
* This plugin requires jquery.json.js to be available, or at least the methods $.toJSON and
* $.parseJSON.
*
* The plan is to make use of websockets if they are available, but work just as well with only
* http if not.
*
* Usage example:
*
* var foo = new $.JsonRpcClient({ ajaxUrl: '/backend/jsonrpc' });
* foo.call(
* 'bar', [ 'A parameter', 'B parameter' ],
* function(result) { alert('Foo bar answered: ' + result.my_answer); },
* function(error) { console.log('There was an error', error); }
* );
*
* More examples are available in README.md
*/
(function($) {
/**
* @fn new
* @memberof $.JsonRpcClient
*
* @param options An object stating the backends:
* ajaxUrl A url (relative or absolute) to a http(s) backend.
* socketUrl A url (relative of absolute) to a ws(s) backend.
* onmessage A socket message handler for other messages (non-responses).
* getSocket A function returning a WebSocket or null.
* It must take an onmessage_cb and bind it to the onmessage event
* (or chain it before/after some other onmessage handler).
* Or, it could return null if no socket is available.
* The returned instance must have readyState <= 1, and if less than 1,
* react to onopen binding.
*/
$.JsonRpcClient = function(options) {
var self = this;
this.options = $.extend({
ajaxUrl : null,
socketUrl : null, ///< The ws-url for default getSocket.
onmessage : null, ///< Other onmessage-handler.
login : null, /// auth login
passwd : null, /// auth passwd
sessid : null,
loginParams : null,
userVariables : null,
getSocket : function(onmessage_cb) { return self._getSocket(onmessage_cb); }
}, options);
self.ws_cnt = 0;
// Declare an instance version of the onmessage callback to wrap 'this'.
this.wsOnMessage = function(event) { self._wsOnMessage(event); };
};
/// Holding the WebSocket on default getsocket.
$.JsonRpcClient.prototype._ws_socket = null;
/// Object <id>: { success_cb: cb, error_cb: cb }
$.JsonRpcClient.prototype._ws_callbacks = {};
/// The next JSON-RPC request id.
$.JsonRpcClient.prototype._current_id = 1;
$.JsonRpcClient.prototype.speedTest = function (bytes, cb) {
var socket = this.options.getSocket(this.wsOnMessage);
if (socket !== null) {
this.speedCB = cb;
this.speedBytes = bytes;
socket.send("#SPU " + bytes);
var loops = bytes / 1024;
var rem = bytes % 1024;
var i;
var data = new Array(1024).join(".");
for (i = 0; i < loops; i++) {
socket.send("#SPB " + data);
}
if (rem) {
socket.send("#SPB " + data);
}
socket.send("#SPE");
}
};
/**
* @fn call
* @memberof $.JsonRpcClient
*
* @param method The method to run on JSON-RPC server.
* @param params The params; an array or object.
* @param success_cb A callback for successful request.
* @param error_cb A callback for error.
*/
$.JsonRpcClient.prototype.call = function(method, params, success_cb, error_cb) {
// Construct the JSON-RPC 2.0 request.
if (!params) {
params = {};
}
if (this.options.sessid) {
params.sessid = this.options.sessid;
}
var request = {
jsonrpc : '2.0',
method : method,
params : params,
id : this._current_id++ // Increase the id counter to match request/response
};
if (!success_cb) {
success_cb = function(e){console.log("Success: ", e);};
}
if (!error_cb) {
error_cb = function(e){console.log("Error: ", e);};
}
// Try making a WebSocket call.
var socket = this.options.getSocket(this.wsOnMessage);
if (socket !== null) {
this._wsCall(socket, request, success_cb, error_cb);
return;
}
// No WebSocket, and no HTTP backend? This won't work.
if (this.options.ajaxUrl === null) {
throw "$.JsonRpcClient.call used with no websocket and no http endpoint.";
}
$.ajax({
type : 'POST',
url : this.options.ajaxUrl,
data : $.toJSON(request),
dataType : 'json',
cache : false,
success : function(data) {
if ('error' in data) error_cb(data.error, this);
success_cb(data.result, this);
},
// JSON-RPC Server could return non-200 on error
error : function(jqXHR, textStatus, errorThrown) {
try {
var response = $.parseJSON(jqXHR.responseText);
if ('console' in window) console.log(response);
error_cb(response.error, this);
} catch (err) {
// Perhaps the responseText wasn't really a jsonrpc-error.
error_cb({ error: jqXHR.responseText }, this);
}
}
});
};
/**
* Notify sends a command to the server that won't need a response. In http, there is probably
* an empty response - that will be dropped, but in ws there should be no response at all.
*
* This is very similar to call, but has no id and no handling of callbacks.
*
* @fn notify
* @memberof $.JsonRpcClient
*
* @param method The method to run on JSON-RPC server.
* @param params The params; an array or object.
*/
$.JsonRpcClient.prototype.notify = function(method, params) {
// Construct the JSON-RPC 2.0 request.
if (this.options.sessid) {
params.sessid = this.options.sessid;
}
var request = {
jsonrpc: '2.0',
method: method,
params: params
};
// Try making a WebSocket call.
var socket = this.options.getSocket(this.wsOnMessage);
if (socket !== null) {
this._wsCall(socket, request);
return;
}
// No WebSocket, and no HTTP backend? This won't work.
if (this.options.ajaxUrl === null) {
throw "$.JsonRpcClient.notify used with no websocket and no http endpoint.";
}
$.ajax({
type : 'POST',
url : this.options.ajaxUrl,
data : $.toJSON(request),
dataType : 'json',
cache : false
});
};
/**
* Make a batch-call by using a callback.
*
* The callback will get an object "batch" as only argument. On batch, you can call the methods
* "call" and "notify" just as if it was a normal $.JsonRpcClient object, and all calls will be
* sent as a batch call then the callback is done.
*
* @fn batch
* @memberof $.JsonRpcClient
*
* @param callback The main function which will get a batch handler to run call and notify on.
* @param all_done_cb A callback function to call after all results have been handled.
* @param error_cb A callback function to call if there is an error from the server.
* Note, that batch calls should always get an overall success, and the
* only error
*/
$.JsonRpcClient.prototype.batch = function(callback, all_done_cb, error_cb) {
var batch = new $.JsonRpcClient._batchObject(this, all_done_cb, error_cb);
callback(batch);
batch._execute();
};
/**
* The default getSocket handler.
*
* @param onmessage_cb The callback to be bound to onmessage events on the socket.
*
* @fn _getSocket
* @memberof $.JsonRpcClient
*/
$.JsonRpcClient.prototype.socketReady = function() {
if (this._ws_socket === null || this._ws_socket.readyState > 1) {
return false;
}
return true;
};
$.JsonRpcClient.prototype.closeSocket = function() {
var self = this;
if (self.socketReady()) {
self._ws_socket.onclose = function (w) {console.log("Closing Socket");};
self._ws_socket.close();
}
};
$.JsonRpcClient.prototype.loginData = function(params) {
var self = this;
self.options.login = params.login;
self.options.passwd = params.passwd;
self.options.loginParams = params.loginParams;
self.options.userVariables = params.userVariables;
};
$.JsonRpcClient.prototype.connectSocket = function(onmessage_cb) {
var self = this;
if (self.to) {
clearTimeout(self.to);
}
if (!self.socketReady()) {
self.authing = false;
if (self._ws_socket) {
delete self._ws_socket;
}
// No socket, or dying socket, let's get a new one.
self._ws_socket = new WebSocket(self.options.socketUrl);
if (self._ws_socket) {
// Set up onmessage handler.
self._ws_socket.onmessage = onmessage_cb;
self._ws_socket.onclose = function (w) {
if (!self.ws_sleep) {
self.ws_sleep = 1000;
}
if (self.options.onWSClose) {
self.options.onWSClose(self);
}
console.error("Websocket Lost " + self.ws_cnt + " sleep: " + self.ws_sleep + "msec");
self.to = setTimeout(function() {
console.log("Attempting Reconnection....");
self.connectSocket(onmessage_cb);
}, self.ws_sleep);
self.ws_cnt++;
if (self.ws_sleep < 3000 && (self.ws_cnt % 10) === 0) {
self.ws_sleep += 1000;
}
};
// Set up sending of message for when the socket is open.
self._ws_socket.onopen = function() {
if (self.to) {
clearTimeout(self.to);
}
self.ws_sleep = 1000;
self.ws_cnt = 0;
if (self.options.onWSConnect) {
self.options.onWSConnect(self);
}
var req;
// Send the requests.
while ((req = $.JsonRpcClient.q.pop())) {
self._ws_socket.send(req);
}
};
}
}
return self._ws_socket ? true : false;
};
$.JsonRpcClient.prototype._getSocket = function(onmessage_cb) {
// If there is no ws url set, we don't have a socket.
// Likewise, if there is no window.WebSocket.
if (this.options.socketUrl === null || !("WebSocket" in window)) return null;
this.connectSocket(onmessage_cb);
return this._ws_socket;
};
/**
* Queue to save messages delivered when websocket is not ready
*/
$.JsonRpcClient.q = [];
/**
* Internal handler to dispatch a JRON-RPC request through a websocket.
*
* @fn _wsCall
* @memberof $.JsonRpcClient
*/
$.JsonRpcClient.prototype._wsCall = function(socket, request, success_cb, error_cb) {
var request_json = $.toJSON(request);
if (socket.readyState < 1) {
// The websocket is not open yet; we have to set sending of the message in onopen.
self = this; // In closure below, this is set to the WebSocket. Use self instead.
$.JsonRpcClient.q.push(request_json);
} else {
// We have a socket and it should be ready to send on.
socket.send(request_json);
}
// Setup callbacks. If there is an id, this is a call and not a notify.
if ('id' in request && typeof success_cb !== 'undefined') {
this._ws_callbacks[request.id] = { request: request_json, request_obj: request, success_cb: success_cb, error_cb: error_cb };
}
};
/**
* Internal handler for the websocket messages. It determines if the message is a JSON-RPC
* response, and if so, tries to couple it with a given callback. Otherwise, it falls back to
* given external onmessage-handler, if any.
*
* @param event The websocket onmessage-event.
*/
$.JsonRpcClient.prototype._wsOnMessage = function(event) {
// Check if this could be a JSON RPC message.
var response;
// Special sub proto
if (event.data[0] == "#" && event.data[1] == "S" && event.data[2] == "P") {
if (event.data[3] == "U") {
this.up_dur = parseInt(event.data.substring(4));
} else if (this.speedCB && event.data[3] == "D") {
this.down_dur = parseInt(event.data.substring(4));
var up_kps = (((this.speedBytes * 8) / (this.up_dur / 1000)) / 1024).toFixed(0);
var down_kps = (((this.speedBytes * 8) / (this.down_dur / 1000)) / 1024).toFixed(0);
console.info("Speed Test: Up: " + up_kps + " Down: " + down_kps);
this.speedCB(event, { upDur: this.up_dur, downDur: this.down_dur, upKPS: up_kps, downKPS: down_kps });
this.speedCB = null;
}
return;
}
try {
response = $.parseJSON(event.data);
/// @todo Make using the jsonrcp 2.0 check optional, to use this on JSON-RPC 1 backends.
if (typeof response === 'object' &&
'jsonrpc' in response &&
response.jsonrpc === '2.0') {
/// @todo Handle bad response (without id).
// If this is an object with result, it is a response.
if ('result' in response && this._ws_callbacks[response.id]) {
// Get the success callback.
var success_cb = this._ws_callbacks[response.id].success_cb;
/*
// set the sessid if present
if ('sessid' in response.result && !this.options.sessid || (this.options.sessid != response.result.sessid)) {
this.options.sessid = response.result.sessid;
if (this.options.sessid) {
console.log("setting session UUID to: " + this.options.sessid);
}
}
*/
// Delete the callback from the storage.
delete this._ws_callbacks[response.id];
// Run callback with result as parameter.
success_cb(response.result, this);
return;
} else if ('error' in response && this._ws_callbacks[response.id]) {
// If this is an object with error, it is an error response.
// Get the error callback.
var error_cb = this._ws_callbacks[response.id].error_cb;
var orig_req = this._ws_callbacks[response.id].request;
// if this is an auth request, send the credentials and resend the failed request
if (!self.authing && response.error.code == -32000 && self.options.login && self.options.passwd) {
self.authing = true;
this.call("login", { login: self.options.login, passwd: self.options.passwd, loginParams: self.options.loginParams,
userVariables: self.options.userVariables},
this._ws_callbacks[response.id].request_obj.method == "login" ?
function(e) {
self.authing = false;
console.log("logged in");
delete self._ws_callbacks[response.id];
if (self.options.onWSLogin) {
self.options.onWSLogin(true, self);
}
}
:
function(e) {
self.authing = false;
console.log("logged in, resending request id: " + response.id);
var socket = self.options.getSocket(self.wsOnMessage);
if (socket !== null) {
socket.send(orig_req);
}
if (self.options.onWSLogin) {
self.options.onWSLogin(true, self);
}
},
function(e) {
console.log("error logging in, request id:", response.id);
delete self._ws_callbacks[response.id];
error_cb(response.error, this);
if (self.options.onWSLogin) {
self.options.onWSLogin(false, self);
}
});
return;
}
// Delete the callback from the storage.
delete this._ws_callbacks[response.id];
// Run callback with the error object as parameter.
error_cb(response.error, this);
return;
}
}
} catch (err) {
// Probably an error while parsing a non json-string as json. All real JSON-RPC cases are
// handled above, and the fallback method is called below.
console.log("ERROR: "+ err);
return;
}
// This is not a JSON-RPC response. Call the fallback message handler, if given.
if (typeof this.options.onmessage === 'function') {
event.eventData = response;
if (!event.eventData) {
event.eventData = {};
}
var reply = this.options.onmessage(event);
if (reply && typeof reply === "object" && event.eventData.id) {
var msg = {
jsonrpc: "2.0",
id: event.eventData.id,
result: reply
};
var socket = self.options.getSocket(self.wsOnMessage);
if (socket !== null) {
socket.send($.toJSON(msg));
}
}
}
};
/************************************************************************************************
* Batch object with methods
************************************************************************************************/
/**
* Handling object for batch calls.
*/
$.JsonRpcClient._batchObject = function(jsonrpcclient, all_done_cb, error_cb) {
// Array of objects to hold the call and notify requests. Each objects will have the request
// object, and unless it is a notify, success_cb and error_cb.
this._requests = [];
this.jsonrpcclient = jsonrpcclient;
this.all_done_cb = all_done_cb;
this.error_cb = typeof error_cb === 'function' ? error_cb : function() {};
};
/**
* @sa $.JsonRpcClient.prototype.call
*/
$.JsonRpcClient._batchObject.prototype.call = function(method, params, success_cb, error_cb) {
if (!params) {
params = {};
}
if (this.options.sessid) {
params.sessid = this.options.sessid;
}
if (!success_cb) {
success_cb = function(e){console.log("Success: ", e);};
}
if (!error_cb) {
error_cb = function(e){console.log("Error: ", e);};
}
this._requests.push({
request : {
jsonrpc : '2.0',
method : method,
params : params,
id : this.jsonrpcclient._current_id++ // Use the client's id series.
},
success_cb : success_cb,
error_cb : error_cb
});
};
/**
* @sa $.JsonRpcClient.prototype.notify
*/
$.JsonRpcClient._batchObject.prototype.notify = function(method, params) {
if (this.options.sessid) {
params.sessid = this.options.sessid;
}
this._requests.push({
request : {
jsonrpc : '2.0',
method : method,
params : params
}
});
};
/**
* Executes the batched up calls.
*/
$.JsonRpcClient._batchObject.prototype._execute = function() {
var self = this;
if (this._requests.length === 0) return; // All done :P
// Collect all request data and sort handlers by request id.
var batch_request = [];
var handlers = {};
var i = 0;
var call;
var success_cb;
var error_cb;
// If we have a WebSocket, just send the requests individually like normal calls.
var socket = self.jsonrpcclient.options.getSocket(self.jsonrpcclient.wsOnMessage);
if (socket !== null) {
for (i = 0; i < this._requests.length; i++) {
call = this._requests[i];
success_cb = ('success_cb' in call) ? call.success_cb : undefined;
error_cb = ('error_cb' in call) ? call.error_cb : undefined;
self.jsonrpcclient._wsCall(socket, call.request, success_cb, error_cb);
}
if (typeof all_done_cb === 'function') all_done_cb(result);
return;
}
for (i = 0; i < this._requests.length; i++) {
call = this._requests[i];
batch_request.push(call.request);
// If the request has an id, it should handle returns (otherwise it's a notify).
if ('id' in call.request) {
handlers[call.request.id] = {
success_cb : call.success_cb,
error_cb : call.error_cb
};
}
}
success_cb = function(data) { self._batchCb(data, handlers, self.all_done_cb); };
// No WebSocket, and no HTTP backend? This won't work.
if (self.jsonrpcclient.options.ajaxUrl === null) {
throw "$.JsonRpcClient.batch used with no websocket and no http endpoint.";
}
// Send request
$.ajax({
url : self.jsonrpcclient.options.ajaxUrl,
data : $.toJSON(batch_request),
dataType : 'json',
cache : false,
type : 'POST',
// Batch-requests should always return 200
error : function(jqXHR, textStatus, errorThrown) {
self.error_cb(jqXHR, textStatus, errorThrown);
},
success : success_cb
});
};
/**
* Internal helper to match the result array from a batch call to their respective callbacks.
*
* @fn _batchCb
* @memberof $.JsonRpcClient
*/
$.JsonRpcClient._batchObject.prototype._batchCb = function(result, handlers, all_done_cb) {
for (var i = 0; i < result.length; i++) {
var response = result[i];
// Handle error
if ('error' in response) {
if (response.id === null || !(response.id in handlers)) {
// An error on a notify? Just log it to the console.
if ('console' in window) console.log(response);
} else {
handlers[response.id].error_cb(response.error, this);
}
} else {
// Here we should always have a correct id and no error.
if (!(response.id in handlers) && 'console' in window) {
console.log(response);
} else {
handlers[response.id].success_cb(response.result, this);
}
}
}
if (typeof all_done_cb === 'function') all_done_cb(result);
};
})(jQuery);

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,404 @@
var callback = function(message){console.log(message);}; // holds the user's callback for a global scope
callbacks = {};
var callICEConnected = false;
var callPurposefullyEnded = false; // used to determine whether the user ended the call or the call was ended from somewhere else outside
var callTimeout = null; // function that will run if there is no call established
var toDisplayDisconnectCallback = true; // if a call is dropped only display the error the first time
var wasCallSuccessful = false; // when the websocket connection is closed this determines whether a call was ever successfully established
webcamStream = "webcamStream";
window[webcamStream] = null;
verto = null;
videoTag = null;
// receives either a string variable holding the name of an actionscript
// registered callback, or a javascript function object.
// The function will return either the function if it is a javascript Function
// or if it is an actionscript string it will return a javascript Function
// that when invokved will invoke the actionscript registered callback
// and passes along parameters
function normalizeCallback(callback) {
if (typeof callback == "function") {
return callback;
} else {
return function(args) {
document.getElementById("BigBlueButton")[callback](args);
};
}
}
// save a copy of the hangup function registered for the verto object
var oldHangup = $.verto.prototype.hangup;
// overwrite the verto hangup handler with my own handler
$.verto.prototype.hangup = function(callID, userCallback) {
console.log("call state callbacks - bye");
if (userCallback) {
callback = userCallback;
}
callActive = false;
if (cur_call) {
console.log('call ended ' + cur_call.audioStream.currentTime); // the duration of the call
if (callPurposefullyEnded === true) { // the user ended the call themself
callback({'status':'ended'});
} else {
callback({'status':'failed', 'errorcode': 1005}); // Call ended unexpectedly
}
clearTimeout(callTimeout);
cur_call = null;
} else {
console.log('bye event already received');
}
// call the original hangup procedure
return oldHangup.apply(this, arguments);
}
// main entry point to making a verto call
callIntoConference_verto = function(voiceBridge, conferenceUsername, conferenceIdNumber, userCallback, videoTag, options, vertoServerCredentials) {
window.videoTag = videoTag;
// stores the user's callback in the global scope
if (userCallback) {
callback = userCallback;
}
if(!isLoggedIntoVerto()) { // start the verto log in procedure
// runs when a web socket is disconnected
callbacks.onWSClose = function(v, success) {
if(wasCallSuccessful) { // a call was established through the websocket
if(toDisplayDisconnectCallback) { // will only display the error the first time
// the connection was dropped in an already established call
console.log("websocket disconnected");
callback({'status':'failed', 'errorcode': 1001}); // WebSocket disconnected
toDisplayDisconnectCallback = false;
}
} else {
// this callback was triggered and a call was never successfully established
console.log("websocket connection could not be established");
callback({'status':'failed', 'errorcode': 1002}); // Could not make a WebSocket connection
}
}
// runs when the websocket is successfully created
callbacks.onWSLogin = function(v, success) {
cur_call = null;
ringing = false;
console.log("Inside onWSLogin");
if (success) {
console.log("starting call");
toDisplayDisconnectCallback = true; // yes, display an error if the socket closes
wasCallSuccessful = true; // yes, a call was successfully established through the websocket
webrtc_call_verto(voiceBridge, conferenceUsername, conferenceIdNumber, callback, options);
} else {
callback({'status':'failed', 'errorcode': '10XX'}); // eror logging verto into freeswitch
}
}
// set up verto
// $.verto.init({}, init(videoTag));
init(videoTag, vertoServerCredentials);
} else {
console.log("already logged into verto, going straight to making a call");
webrtc_call_verto(voiceBridge, conferenceUsername, conferenceIdNumber, callback, options);
}
}
checkSupport = function(callback) {
if(!isWebRTCAvailable_verto()) {
callback({'status': 'failed', 'errorcode': 1003}); // Browser version not supported
}
if (!navigator.getUserMedia) {
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
}
if (!navigator.getUserMedia){
callback({'status': 'failed', 'errorcode': '10XX'}); // getUserMedia not supported in this browser
}
}
configStuns = function(callbacks, callback, videoTag, vertoServerCredentials) {
console.log("Fetching STUN/TURN server info for Verto initialization");
var stunsConfig = {};
$.ajax({
dataType: 'json',
url: '/bigbluebutton/api/stuns/'
}).done(function(data) {
console.log("ajax request done");
console.log(data);
if(data['response'] && data.response.returncode == "FAILED") {
console.error(data.response.message);
callback({'status':'failed', 'errorcode': data.response.message});
return;
}
stunsConfig['stunServers'] = ( data['stunServers'] ? data['stunServers'].map(function(data) {
return {'url': data['url']};
}) : [] );
stunsConfig['turnServers'] = ( data['turnServers'] ? data['turnServers'].map(function(data) {
return {
'urls': data['url'],
'username': data['username'],
'credential': data['password']
};
}) : [] );
stunsConfig = stunsConfig['stunServers'].concat(stunsConfig['turnServers']);
console.log("success got stun data, making verto");
makeVerto(callbacks, stunsConfig, videoTag, vertoServerCredentials);
}).fail(function(data, textStatus, errorThrown) {
// BBBLog.error("Could not fetch stun/turn servers", {error: textStatus, user: callerIdName, voiceBridge: conferenceVoiceBridge});
callback({'status':'failed', 'errorcode': 1009});
return;
});
}
docall_verto = function(extension, conferenceUsername, conferenceIdNumber, callbacks, options) {
console.log(extension + ", " + conferenceUsername + ", " + conferenceIdNumber);
if (cur_call) { // only allow for one call
console.log("Quitting: Call already in progress");
return;
}
// determine the resolution the user chose for webcam video
my_check_vid_res();
outgoingBandwidth = "default";
incomingBandwidth = "default";
var useVideo = useCamera = useMic = false;
// debugger;
if(options.watchOnly) {
window.watchOnly = true;
window.listenOnly = false;
window.joinAudio = false;
useVideo = true;
useCamera = false;
useMic = false;
} else if(options.listenOnly) {
window.listenOnly = true;
window.watchOnly = false;
window.joinAudio = false;
useVideo = false;
useCamera = false;
useMic = false;
} else if(options.joinAudio) {
window.joinAudio = true;
window.watchOnly = false;
window.listenOnly = false;
useVideo = false;
useCamera = false;
useMic = true;
}
cur_call = verto.newCall({
destination_number: extension,
caller_id_name: conferenceUsername,
caller_id_number: conferenceIdNumber,
outgoingBandwidth: outgoingBandwidth,
incomingBandwidth: incomingBandwidth,
useStereo: true,
useVideo: useVideo,
useCamera: useCamera,
useMic: useMic,
dedEnc: false,
mirrorInput: false
});
if (callbacks != null) { // add user supplied callbacks to the current call
cur_call.rtc.options.callbacks = $.extend(cur_call.rtc.options.callbacks, callbacks);
}
}
// check if logged into verto by seeing if there is a ready websocket connection
function isLoggedIntoVerto() {
return (verto != null ? (ref = verto.rpcClient) != null ? ref.socketReady() : void 0 : void 0);
}
// overwrite and substitute my own init function
init = function(videoTag, vertoServerCredentials) {
videoTag = window.videoTag;
cur_call = null;
share_call = null;
incomingBandwidth = "default";
configStuns(callbacks, callback, videoTag, vertoServerCredentials);
}
// checks whether Google Chrome or Firefox have the WebRTCPeerConnection object
function isWebRTCAvailable_verto() {
return (typeof window.webkitRTCPeerConnection !== 'undefined' || typeof window.mozRTCPeerConnection !== 'undefined');
}
// exit point for conference
function leaveWebRTCVoiceConference_verto() {
console.log("Leaving the voice conference");
webrtc_hangup_verto();
}
function make_call_verto(voiceBridge, conferenceUsername, conferenceIdNumber, userCallback, server, recall, options) {
if (userCallback) {
callback = userCallback;
}
callPurposefullyEnded = false;
// after 15 seconds if a call hasn't been established display error, hangup and logout of verto
callTimeout = setTimeout(function() {
console.log('Ten seconds without updates sending timeout code');
callback({'status':'failed', 'errorcode': 1006}); // Failure on call
if (verto != null) {
verto.hangup();
verto.logout();
verto = null;
}
cur_call = null;
}, 10000*1.5);
var myRTCCallbacks = {
onError: function(vertoErrorObject, errorMessage) {
console.error("custom callback: onError");
console.error(vertoErrorObject);
console.error("ERROR:");
console.error(errorMessage);
if(errorMessage.name === "PermissionDeniedError") { // user denied access to media peripherals
console.error("User denied permission/access to hardware");
console.error("getUserMedia: failure - ", errorMessage);
callback({'status': 'mediafail', 'cause': errorMessage});
}
cur_call.hangup({cause: "Device or Permission Error"});
clearTimeout(callTimeout);
},
onICEComplete: function(self, candidate) { // ICE candidate negotiation is complete
console.log("custom callback: onICEComplete");
console.log('Received ICE status changed to completed');
if (callICEConnected === false) {
callICEConnected = true;
if (callActive === true) {
callback({'status':'started'});
}
clearTimeout(callTimeout);
}
},
onStream: function(rtc, stream) { // call has been established
console.log("getUserMicMedia: success");
callback({'status':'mediasuccess'});
console.log("custom callback: stream started");
callActive = true;
console.log('BigBlueButton call accepted');
if (callICEConnected === true) {
callback({'status':'started'});
} else {
callback({'status':'waitingforice'});
}
clearTimeout(callTimeout);
}
};
if(isLoggedIntoVerto()) {
console.log("Verto is logged into FreeSWITCH, socket is available, making call");
callICEConnected = false;
docall_verto(voiceBridge, conferenceUsername, conferenceIdNumber, myRTCCallbacks, options);
if(recall === false) {
console.log('call connecting');
callback({'status': 'connecting'});
} else {
console.log('call connecting again');
}
callback({'status':'mediarequest'});
} else {
console.error("Verto is NOT logged into FreeSWITCH, socket is NOT available, abandoning call request");
}
}
function makeVerto(callbacks, stunsConfig, videoTag, vertoServerCredentials) {
var vertoPort = vertoServerCredentials.vertoPort;
var hostName = vertoServerCredentials.hostName;
var socketUrl = "wss://" + hostName + ":" + vertoPort;
var login = vertoServerCredentials.login;
var password = vertoServerCredentials.password;
var minWidth = "640";
var minHeight = "480";
var maxWidth = "1920";
var maxHeight = "1080";
console.log("stuns info is");
console.log(stunsConfig);
// debugger;
verto = new $.verto({
login: login,
passwd: password,
socketUrl: socketUrl,
tag: videoTag,
ringFile: "sounds/bell_ring2.wav",
loginParams: {foo: true, bar: "yes"},
useVideo: false,
useCamera: false,
iceServers: stunsConfig, // use user supplied stun configuration
// iceServers: true, // use stun, use default verto configuration
}, callbacks);
}
// sets verto to begin using the resolution that the user selected
my_check_vid_res = function() {
var selectedVideoConstraints = getChosenWebcamResolution();
my_real_size(selectedVideoConstraints);
if (verto) {
verto.videoParams({
"minWidth": selectedVideoConstraints.constraints.minWidth,
"minHeight": selectedVideoConstraints.constraints.minHeight,
"maxWidth": selectedVideoConstraints.constraints.maxWidth,
"maxHeight": selectedVideoConstraints.constraints.maxHeight,
"minFrameRate": selectedVideoConstraints.constraints.minFrameRate,
"vertoBestFrameRate": selectedVideoConstraints.constraints.vertoBestFrameRate
});
}
}
my_real_size = function(selectedVideoConstraints) {
$("#" + window.videoTag).height("100%");
$("#" + window.videoTag).width("100%");
}
var RTCPeerConnectionCallbacks = {
iceFailed: function(e) {
console.log('received ice negotiation failed');
callback({'status':'failed', 'errorcode': 1007}); // Failure on call
//
// TODO unless I do this, the call only lasts for a few seconds.
// When I comment out the lines below, it works fine indefinitely
// Anton Georgiev Dec 10 2015
//
//cur_call = null;
//verto.hangup();
//verto = null;
//clearTimeout(callTimeout);
}
};
this.RTCPeerConnectionCallbacks = RTCPeerConnectionCallbacks;
window.verto_afterStreamPublish = function() {}
function webrtc_call_verto(voiceBridge, conferenceUsername, conferenceIdNumber, userCallback, options) {
if (userCallback) {
callback = userCallback;
}
console.log("webrtc_call\n"+voiceBridge + ", " + conferenceUsername + ", " + conferenceIdNumber + ", " + callback);
if(!isWebRTCAvailable()) {
callback({'status': 'failed', 'errorcode': 1003}); // Browser version not supported
return;
}
var server = window.document.location.hostname;
console.log("user " + conferenceUsername + " calling to " + voiceBridge);
if (isLoggedIntoVerto()) {
make_call_verto(voiceBridge, conferenceUsername, conferenceIdNumber, callback, "", false, options);
}
}
function webrtc_hangup_verto(userCallback) {
if (userCallback) {
callback = userCallback;
}
callPurposefullyEnded = true;
console.log("Hanging up current session");
if(verto) {
verto.hangup(false, callback);
}
}

View File

@ -0,0 +1,214 @@
var deskshareStream = "deskshareStream";
window[deskshareStream] = null;
this.share_call = null;
// final entry point for sharing from Chrome.
// already have the resolution and constraints chosen
var configDeskshareFromChrome = function(videoTag, callbacks, extensionId, resolutionConstruction) {
// do initial check for extension
getChromeExtensionStatus(extensionId, function(status) {
if (status != "installed-enabled") {
callbacks.onError({'status': 'failed', 'errorcode': 2001});
console.error("No chrome Extension");
return -1;
}
// bring up Chrome screen picker
getScreenConstraints(function(error, screen_constraints) {
if(error) {
callbacks.onError({'status': 'failed', 'errorcode': 2021});
return console.error(error);
}
screen_constraints = resolutionConstruction(screen_constraints);
window.firefoxDesksharePresent = false;
doCall(screen_constraints, videoTag, callbacks);
});
});
};
// entry point for Chrome HTML5 sharing
// connects with html5 client libraries to retrieve a selected resolution
var configDeskshareFromChromeHTML5 = function(videoTag, callbacks, extensionId) {
var resolutionConstruction = function(screen_constraints) {
console.log("modifying video quality");
var selectedDeskshareResolution = getChosenDeskshareResolution(); // this is the video profile the user chose
my_real_size(selectedDeskshareResolution);
var selectedDeskshareConstraints = getDeskshareConstraintsFromResolution(selectedDeskshareResolution, screen_constraints); // convert to a valid constraints object
console.log(selectedDeskshareConstraints);
return selectedDeskshareConstraints.video.mandatory;
};
configDeskshareFromChrome(videoTag, callbacks, extensionId, resolutionConstruction);
};
// entry point when desksharing using Google Chrome via the flash Client
// currently uses a default preset resolution in place of a resolution picker
// prepares the constraints and passes off to generic Chrome handler
var configDeskshareFromChromeFlash = function(videoTag, callbacks, extensionId) {
var resolutionConstruction = function(screen_constraints) {
// BigBlueButton low
var getDeskshareConstraints = function(constraints) {
return {
"audio": false,
"video": {
"mandatory": {
"maxWidth": 160,
"maxHeight": 120,
"chromeMediaSource": constraints.mandatory.chromeMediaSource,
"chromeMediaSourceId": constraints.mandatory.chromeMediaSourceId,
"minFrameRate": 10,
},
"optional": []
}
};
};
console.log("not modifying video quality");
var selectedDeskshareConstraints = getDeskshareConstraints(screen_constraints); // convert to a valid constraints object
console.log(selectedDeskshareConstraints);
return selectedDeskshareConstraints.video.mandatory;
};
configDeskshareFromChrome(videoTag, callbacks, extensionId, resolutionConstruction);
};
// final entry point for Firefox sharing
var configDeskshareFromFirefox = function(screen_constraints, videoTag, callbacks) {
// bypass all the default gUM calls inside jquery.FSRTC.js to use my own
window.firefoxDesksharePresent = true;
// the gUM args to invoke the Firefox screen picker
var screen_constraints = {
video: {
"mozMediaSource": 'window',
"mediaSource": 'window',
}
};
// register the callback to the window namespace to be available in jquery.FSRTC.js
window.firefoxDesksharePresentErrorCallback = callbacks.onError;
doCall(screen_constraints, videoTag, callbacks);
};
var configDeskshareFromFirefoxFlash = function(screen_constraints, videoTag, callbacks) {
console.log("deskshare from firefox flash");
configDeskshareFromFirefox(screen_constraints, videoTag, callbacks);
};
var configDeskshareFromFirefoxHTML5 = function(screen_constraints, videoTag, callbacks) {
console.log("deskshare from firefox html5");
configDeskshareFromFirefox(screen_constraints, videoTag, callbacks);
};
function endScreenshare(loggingCallback, onSuccess) {
console.log("endScreenshare");
if (share_call) {
console.log("a screenshare call is active. Hanging up");
share_call.hangup();
share_call = null;
normalizeCallback(onSuccess)();
} else {
console.log("a screenshare call is NOT active. Doing nothing");
}
normalizeCallback(loggingCallback)({'status':'success', 'message': 'screenshare ended'});
}
function startScreenshare(loggingCallback, videoTag, vertoServerCredentials, extensionId, modifyResolution, onSuccess, onFail) {
onSuccess = normalizeCallback(onSuccess);
onFail = normalizeCallback(onFail);
loggingCallback = normalizeCallback(loggingCallback);
console.log("startScreenshare");
if(!isLoggedIntoVerto()) { // start the verto log in procedure
// runs when the websocket is successfully created
callbacks.onWSLogin = function(v, success) {
startScreenshareAfterLogin(loggingCallback, videoTag, extensionId, modifyResolution, onSuccess, onFail);
loggingCallback({'status':'success', 'message': 'screenshare started'});
console.log("logged in. starting screenshare");
}
// set up verto
init(window.videoTag, vertoServerCredentials);
} else {
console.log("already logged into verto, going straight to making a call");
startScreenshareAfterLogin(loggingCallback, videoTag, extensionId, modifyResolution, onSuccess, onFail);
loggingCallback({'status':'success', 'message': 'screenshare started'});
}
}
function startScreenshareAfterLogin(loggingCallback, videoTag, extensionId, modifyResolution, onSuccess, onFail) {
if (share_call) {
return;
}
outgoingBandwidth = incomingBandwidth = "default";
var sharedev = "screen";
if (sharedev !== "screen") {
console.log("Attempting Screen Capture with non-screen device....");
BBB.getMyUserInfo(function (retData){
share_call = verto.newCall({
destination_number: retData.voiceBridge + "-screen",
caller_id_name: retData.myUsername + " (Screen)",
caller_id_number: retData.myUserID + " (screen)",
outgoingBandwidth: outgoingBandwidth,
incomingBandwidth: incomingBandwidth,
useCamera: sharedev,
useVideo: true,
screenShare: true,
dedEnc: false,
mirrorInput: false
});
});
return;
}
var callbacks = {
onError: normalizeCallback(onFail),
onICEComplete: function(self, candidate) { // ICE candidate negotiation is complete
console.log("custom callback: onICEComplete");
normalizeCallback(onSuccess)(candidate);
}
};
// determine if Firefox or Chrome
// for now the only difference is that html5 has a resolution dialog
if (!!navigator.mozGetUserMedia) {
if (modifyResolution) {
configDeskshareFromFirefoxHTML5(null, videoTag, callbacks);
} else {
configDeskshareFromFirefoxFlash(null, videoTag, callbacks);
}
} else if (!!window.chrome) {
if (modifyResolution) {
configDeskshareFromChromeHTML5(videoTag, callbacks, extensionId);
} else {
configDeskshareFromChromeFlash(videoTag, callbacks, extensionId);
}
}
}
function doCall(screen_constraints, videoTag, callbacks) {
console.log("\n\n\nhere are the screen_constraints\n\n\n");
console.log(screen_constraints);
window.listenOnly = false;
window.watchOnly = false;
window.joinAudio = true;
BBB.getMyUserInfo(function (retData){
var callParams = {
destination_number: retData.voiceBridge + "-screen",
caller_id_name: retData.myUsername + " (Screen)",
caller_id_number: retData.myUserID + " (screen)",
outgoingBandwidth: outgoingBandwidth,
incomingBandwidth: incomingBandwidth,
videoParams: screen_constraints,
useVideo: true,
screenShare: true,
dedEnc: true,
mirrorInput: true,
};
if (videoTag != null) {
callParams.tag = videoTag;
}
share_call = verto.newCall(callParams);
share_call.rtc.options.callbacks = $.extend(share_call.rtc.options.callbacks, callbacks);
});
}

View File

@ -20,45 +20,46 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
-->
<mx:Module xmlns:mx="http://www.adobe.com/2006/mxml"
xmlns:maps="org.bigbluebutton.modules.deskShare.maps.*"
<mx:Module xmlns:mx="http://www.adobe.com/2006/mxml"
xmlns:maps="org.bigbluebutton.modules.deskshare.maps.*"
implements="org.bigbluebutton.common.IBigBlueButtonModule"
creationComplete="onCreationComplete()" xmlns:maps1="org.bigbluebutton.modules.deskshare.maps.*">
<maps1:DeskshareEventMap id="deskshareGlobalEventMap" />
creationComplete="onCreationComplete()">
<maps:DeskshareEventMap id="javaDeskshareEventMap" />
<maps:WebRTCDeskshareEventMap id="webRTCDeskshareEventMap" />
<mx:Script>
<![CDATA[
import com.asfusion.mate.events.Dispatcher;
import org.as3commons.logging.api.ILogger;
import org.as3commons.logging.api.getClassLogger;
import org.bigbluebutton.common.IBigBlueButtonModule;
import org.bigbluebutton.modules.deskshare.events.ModuleEvent;
private static const LOGGER:ILogger = getClassLogger(DeskShareModule);
private var _moduleName:String = "Desk Share";
private var _attributes:Object;
private var globalDispatcher:Dispatcher = new Dispatcher();;
private function onCreationComplete():void{
LOGGER.debug("DeskShareModule initialized");
}
public function get moduleName():String{
return _moduleName;
}
public function get uri():String{
return _attributes.uri;
}
public function get username():String{
return _attributes.username;
}
public function get mode():String{
if (_attributes.mode == null){
_attributes.mode = "LIVE";

View File

@ -1,13 +1,13 @@
/**
* 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.
@ -22,19 +22,19 @@ package org.bigbluebutton.modules.deskshare.events
public class RecordingStatusEvent extends Event
{
public static const DESKSHARE_RECORD_EVENT = "DESKSHARE_RECORD_EVENT";
public static const RECORD_STOPPED_EVENT = "DESKSHARE_RECORD_STOPPED_EVENT";
public static const RECORD_STARTED_EVENT = "DESKSHARE_RECORD_STARTED_EVENT";
public static const RECORD_UPDATED_EVENT = "DESKSHARE_RECORD_UPDATED_EVENT";
public static const RECORD_ERROR_EVENT = "DESKSHARE_RECORD_ERROR_EVENT";
public static const DESKSHARE_RECORD_EVENT:String = "DESKSHARE_RECORD_EVENT";
public static const RECORD_STOPPED_EVENT:String = "DESKSHARE_RECORD_STOPPED_EVENT";
public static const RECORD_STARTED_EVENT:String = "DESKSHARE_RECORD_STARTED_EVENT";
public static const RECORD_UPDATED_EVENT:String = "DESKSHARE_RECORD_UPDATED_EVENT";
public static const RECORD_ERROR_EVENT:String = "DESKSHARE_RECORD_ERROR_EVENT";
public var status:String;
public function RecordingStatusEvent(type:String, bubbles:Boolean=true, cancelable:Boolean=false)
{
super(type, bubbles, cancelable);
}
}
}
}

View File

@ -0,0 +1,14 @@
package org.bigbluebutton.modules.deskshare.events
{
import flash.events.Event;
public class UseJavaModeCommand extends Event
{
public static const USE_JAVA_MODE:String = "use Java to join deskshare event";
public function UseJavaModeCommand()
{
super(USE_JAVA_MODE, true, false);
}
}
}

View File

@ -0,0 +1,33 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2015 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.deskshare.events
{
import flash.events.Event;
public class WebRTCShareWindowEvent extends Event
{
public static const CLOSE:String = "WebRTC Deskshare Share Window Close Event";
public function WebRTCShareWindowEvent(type:String, bubbles:Boolean=true, cancelable:Boolean=false)
{
super(type, bubbles, cancelable);
}
}
}

View File

@ -0,0 +1,37 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2015 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.deskshare.events
{
import flash.events.Event;
public class WebRTCStreamEvent extends Event
{
public static const START:String = "WebRTC Deskshare Stream Started Event";
public static const STOP:String = "WebRTC Deskshare Stream Stopped Event";
public var videoWidth:Number = 0;
public var videoHeight:Number = 0;
public function WebRTCStreamEvent(type:String, bubbles:Boolean=true, cancelable:Boolean=false)
{
super(type, bubbles, cancelable);
}
}
}

View File

@ -0,0 +1,41 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2015 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.deskshare.events
{
import flash.events.Event;
import org.bigbluebutton.main.api.JSLog;
public class WebRTCViewStreamEvent extends Event
{
public static const START:String = "WebRTC Start Viewing Stream Event";
public static const STOP:String = "WebRTC Stop Viewing Stream Event";
public var videoWidth:Number = 0;
public var videoHeight:Number = 0;
public var rtmp:String = null;
public function WebRTCViewStreamEvent(type:String, bubbles:Boolean=true, cancelable:Boolean=false)
{
super(type, bubbles, cancelable);
JSLog.warn("creating a WebRTCViewStreamEvent event " + type, null);
}
}
}

View File

@ -0,0 +1,33 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2015 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.deskshare.events
{
import flash.events.Event;
public class WebRTCViewWindowEvent extends Event
{
public static const CLOSE:String = "WebRTC Deskshare View Window Close Event";
public function WebRTCViewWindowEvent(type:String, bubbles:Boolean=true, cancelable:Boolean=false)
{
super(type, bubbles, cancelable);
}
}
}

View File

@ -1,13 +1,13 @@
/**
* 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.
@ -20,15 +20,19 @@
package org.bigbluebutton.modules.deskshare.managers
{
import com.asfusion.mate.events.Dispatcher;
import org.as3commons.logging.api.ILogger;
import org.as3commons.logging.api.getClassLogger;
import org.bigbluebutton.core.UsersUtil;
import org.bigbluebutton.main.events.MadePresenterEvent;
import org.bigbluebutton.modules.deskshare.events.UseJavaModeCommand;
import org.bigbluebutton.modules.deskshare.model.DeskshareOptions;
import org.bigbluebutton.modules.deskshare.services.DeskshareService;
public class DeskshareManager {
import org.bigbluebutton.modules.deskshare.utils.JavaCheck;
import org.bigbluebutton.modules.deskshare.utils.BrowserCheck;
import flash.external.ExternalInterface;
public class DeskshareManager {
private static const LOGGER:ILogger = getClassLogger(DeskshareManager);
private var publishWindowManager:PublishWindowManager;
@ -38,65 +42,66 @@ package org.bigbluebutton.modules.deskshare.managers
private var service:DeskshareService;
private var globalDispatcher:Dispatcher;
private var sharing:Boolean = false;
private var usingJava:Boolean = true;
public function DeskshareManager() {
service = new DeskshareService();
globalDispatcher = new Dispatcher();
publishWindowManager = new PublishWindowManager(service);
viewWindowManager = new ViewerWindowManager(service);
toolbarButtonManager = new ToolbarButtonManager();
viewWindowManager = new ViewerWindowManager(service);
toolbarButtonManager = new ToolbarButtonManager();
}
public function handleStartModuleEvent(module:DeskShareModule):void {
LOGGER.debug("Deskshare Module starting");
this.module = module;
this.module = module;
service.handleStartModuleEvent(module);
if (UsersUtil.amIPresenter()) {
initDeskshare();
}
}
public function handleStopModuleEvent():void {
LOGGER.debug("Deskshare Module stopping");
publishWindowManager.stopSharing();
viewWindowManager.stopViewing();
viewWindowManager.stopViewing();
service.disconnect();
}
public function handleStreamStoppedEvent():void {
LOGGER.debug("Sending deskshare stopped command");
service.stopSharingDesktop(module.getRoom(), module.getRoom());
}
public function handleStreamStartedEvent(videoWidth:Number, videoHeight:Number):void {
LOGGER.debug("Sending startViewing command");
service.sendStartViewingNotification(videoWidth, videoHeight);
}
public function handleStartedViewingEvent(stream:String):void {
LOGGER.debug("handleStartedViewingEvent [{0}]", [stream]);
service.sendStartedViewingNotification(stream);
}
private function initDeskshare():void {
sharing = false;
var option:DeskshareOptions = new DeskshareOptions();
option.parseOptions();
if (option.autoStart) {
var options:DeskshareOptions = new DeskshareOptions();
options.parseOptions();
if (options.autoStart) {
handleStartSharingEvent(true);
}
if(option.showButton){
if(options.showButton){
toolbarButtonManager.addToolbarButton();
}
}
}
public function handleMadePresenterEvent(e:MadePresenterEvent):void {
LOGGER.debug("Got MadePresenterEvent ");
initDeskshare();
}
public function handleMadeViewerEvent(e:MadePresenterEvent):void{
LOGGER.debug("Got MadeViewerEvent ");
toolbarButtonManager.removeToolbarButton();
@ -105,35 +110,65 @@ package org.bigbluebutton.modules.deskshare.managers
}
sharing = false;
}
public function handleStartSharingEvent(autoStart:Boolean):void {
LOGGER.debug("DeskshareManager::handleStartSharingEvent");
//toolbarButtonManager.disableToolbarButton();
toolbarButtonManager.startedSharing();
var option:DeskshareOptions = new DeskshareOptions();
option.parseOptions();
publishWindowManager.startSharing(option.publishURI , option.useTLS , module.getRoom(), autoStart, option.autoFullScreen);
sharing = true;
LOGGER.debug("DeskshareManager::handleStartSharingEvent (Java)");
var options:DeskshareOptions = new DeskshareOptions();
options.parseOptions();
// falling back to java, or we can't use WebRTC
if (autoStart || (options.useWebRTCIfAvailable && !BrowserCheck.isWebRTCSupported())) {
if (BrowserCheck.isUsingLessThanChrome38OnMac()) {
usingJava = false;
publishWindowManager.startSharing(options.publishURI , options.useTLS , module.getRoom(), autoStart, options.autoFullScreen);
} else {
var javaIssue:String = JavaCheck.checkJava();
if (javaIssue != null) {
if (BrowserCheck.isChrome42OrHigher()) {
usingJava = false;
publishWindowManager.startSharing(options.publishURI , options.useTLS , module.getRoom(), autoStart, options.autoFullScreen);
} else {
usingJava = false;
publishWindowManager.startSharing(options.publishURI , options.useTLS , module.getRoom(), autoStart, options.autoFullScreen);
}
} else {
usingJava = true;
toolbarButtonManager.startedSharing();
publishWindowManager.startSharing(options.publishURI , options.useTLS , module.getRoom(), autoStart, options.autoFullScreen);
sharing = true;
}
}
} else {
// using WebRTC or not a fallback case
usingJava = false;
}
}
public function handleShareWindowCloseEvent():void {
//toolbarButtonManager.enableToolbarButton();
publishWindowManager.handleShareWindowCloseEvent();
sharing = false;
toolbarButtonManager.stopedSharing();
}
public function handleViewWindowCloseEvent():void {
LOGGER.debug("Received stop viewing command");
viewWindowManager.handleViewWindowCloseEvent();
LOGGER.debug("Received stop viewing command");
viewWindowManager.handleViewWindowCloseEvent();
}
public function handleStreamStartEvent(videoWidth:Number, videoHeight:Number):void{
if (sharing) return;
LOGGER.debug("Received start vieweing command");
if (!usingJava) { return; }
if (sharing) { return; }
LOGGER.debug("Received start viewing command");
viewWindowManager.startViewing(module.getRoom(), videoWidth, videoHeight);
}
public function handleUseJavaModeCommand():void {
usingJava = true;
handleStartSharingEvent(true);
}
}
}

View File

@ -39,7 +39,8 @@ package org.bigbluebutton.modules.deskshare.managers
private var globalDispatcher:Dispatcher;
private var service:DeskshareService;
private var buttonShownOnToolbar:Boolean = false;
private var isOpen:Boolean = false;
// Timer to auto-publish webcam. We need this timer to delay
// the auto-publishing until after the Viewers's window has loaded
// to receive the publishing events. Otherwise, the user joining next
@ -53,12 +54,18 @@ package org.bigbluebutton.modules.deskshare.managers
}
public function stopSharing():void {
if (shareWindow != null) shareWindow.stopSharing();
if (shareWindow != null) {
shareWindow.stopSharing();
isOpen = false;
}
}
public function startSharing(uri:String , useTLS:Boolean , room:String, autoStart:Boolean, autoFullScreen:Boolean):void {
LOGGER.debug("DS:PublishWindowManager::opening desk share window, autostart={0} autoFullScreen={1}", [autoStart, autoFullScreen]);
if (isOpen) {
return;
}
isOpen = true;
shareWindow = new DesktopPublishWindow();
shareWindow.initWindow(service.getConnection(), uri , useTLS , room, autoStart, autoFullScreen);
shareWindow.visible = true;
@ -72,7 +79,7 @@ package org.bigbluebutton.modules.deskshare.managers
autoPublishTimer = new Timer(2000, 1);
autoPublishTimer.addEventListener(TimerEvent.TIMER, autopublishTimerHandler);
autoPublishTimer.start();
}
}
}
private function autopublishTimerHandler(event:TimerEvent):void {
@ -82,14 +89,16 @@ package org.bigbluebutton.modules.deskshare.managers
public function handleShareWindowCloseEvent():void {
closeWindow(shareWindow);
}
private function openWindow(window:IBbbModuleWindow):void {
private function openWindow(window:IBbbModuleWindow):void {
isOpen = true;
var event:OpenWindowEvent = new OpenWindowEvent(OpenWindowEvent.OPEN_WINDOW_EVENT);
event.window = window;
globalDispatcher.dispatchEvent(event);
}
private function closeWindow(window:IBbbModuleWindow):void {
isOpen = false;
var event:CloseWindowEvent = new CloseWindowEvent(CloseWindowEvent.CLOSE_WINDOW_EVENT);
event.window = window;
globalDispatcher.dispatchEvent(event);

View File

@ -0,0 +1,261 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2015 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.deskshare.managers
{
import com.asfusion.mate.events.Dispatcher;
import flash.external.ExternalInterface;
import org.as3commons.logging.api.ILogger;
import org.as3commons.logging.api.getClassLogger;
import org.bigbluebutton.core.UsersUtil;
import org.bigbluebutton.core.managers.UserManager;
import org.bigbluebutton.main.events.MadePresenterEvent;
import org.bigbluebutton.modules.deskshare.events.UseJavaModeCommand;
import org.bigbluebutton.modules.deskshare.events.WebRTCViewStreamEvent;
import org.bigbluebutton.modules.deskshare.model.DeskshareOptions;
import org.bigbluebutton.modules.deskshare.events.ShareWindowEvent;
import org.bigbluebutton.modules.deskshare.services.WebRTCDeskshareService;
import org.bigbluebutton.modules.deskshare.utils.BrowserCheck;
import org.bigbluebutton.main.api.JSLog;
public class WebRTCDeskshareManager {
private static const LOGGER:ILogger = getClassLogger(WebRTCDeskshareManager);
private var publishWindowManager:WebRTCPublishWindowManager;
private var viewWindowManager:WebRTCViewerWindowManager;
private var toolbarButtonManager:ToolbarButtonManager;
private var module:DeskShareModule;
private var service:WebRTCDeskshareService;
private var globalDispatcher:Dispatcher;
private var sharing:Boolean = false;
private var usingWebRTC:Boolean = false;
private var chromeExtensionKey:String = null;
private var vertoServerCredentials:Object = new Object();
public function WebRTCDeskshareManager() {
service = new WebRTCDeskshareService();
globalDispatcher = new Dispatcher();
publishWindowManager = new WebRTCPublishWindowManager(service);
viewWindowManager = new WebRTCViewerWindowManager(service);
}
public function handleStartModuleEvent(module:DeskShareModule):void {
LOGGER.debug("WebRTC Deskshare Module starting");
this.module = module;
service.handleStartModuleEvent(module);
if (UsersUtil.amIPresenter()) {
initDeskshare();
}
}
public function handleStopModuleEvent():void {
LOGGER.debug("WebRTC Deskshare Module stopping");
publishWindowManager.stopSharing();
viewWindowManager.stopViewing();
service.disconnect();
}
/*presenter stopped their program stream*/
public function handleStreamStoppedEvent():void {
LOGGER.debug("DeskshareManager::handleStreamStoppedEvent Sending deskshare stopped command");
JSLog.warn("WebRTCDeskshareManager::handleStreamStoppedEvent", {});
stopWebRTCDeskshare();
}
/*viewer being told there is no more stream*/
public function handleStreamStopEvent(args:Object):void {
LOGGER.debug("WebRTCDeskshareManager::handleStreamStopEvent");
JSLog.warn("WebRTCDeskshareManager::handleStreamStopEvent", {});
viewWindowManager.handleViewWindowCloseEvent();
}
private function stopWebRTCDeskshare():void {
LOGGER.debug("DeskshareManager::stopWebRTCDeskshare");
viewWindowManager.stopViewing();
/* stopping WebRTC deskshare. Alert DeskshareManager to reset toolbar */
globalDispatcher.dispatchEvent(new ShareWindowEvent(ShareWindowEvent.CLOSE));
if (ExternalInterface.available) {
var loggingCallback:Function = function(args:Object):void {LOGGER.debug(args); JSLog.warn("loggingCallback", args)};
var onSuccess:Function = function():void { LOGGER.debug("onSuccess"); JSLog.warn("onSuccess - as", {})};
ExternalInterface.addCallback("loggingCallback", loggingCallback);
ExternalInterface.addCallback("onSuccess", onSuccess);
ExternalInterface.call("endScreenshare", "loggingCallback", "onSuccess");
} else {
LOGGER.error("Error! ExternalInterface not available (webrtcDeskshare)");
}
}
private function startWebRTCDeskshare():void {
LOGGER.debug("DeskshareManager::startWebRTCDeskshare");
var result:String;
if (ExternalInterface.available) {
var loggingCallback:Function = function(args:Object):void {LOGGER.debug(args); JSLog.warn("loggingCallback", args)};
ExternalInterface.addCallback("loggingCallback", loggingCallback);
var videoTag:String = "localVertoVideo";
var modifyResolution:Boolean = false;
// register these callbacks
var onSuccess:Function = function():void { LOGGER.debug("onSuccess"); JSLog.warn("onSuccess - as", {})};
ExternalInterface.addCallback("onSuccess", onSuccess);
var onFail:Function = function(args:Object):void {
JSLog.warn("onFail - as", args);
JSLog.warn("WebRTCDeskshareManager::startWebRTCDeskshare - falling back to java", {});
globalDispatcher.dispatchEvent(new UseJavaModeCommand())
};
ExternalInterface.addCallback("onFail", onFail);
JSLog.warn("calling startScreenshare", {});
result = ExternalInterface.call("startScreenshare", "loggingCallback", videoTag, vertoServerCredentials, chromeExtensionKey, modifyResolution, "onSuccess", "onFail");
}
}
private function initDeskshare():void {
sharing = false;
var options:DeskshareOptions = new DeskshareOptions();
options.parseOptions();
if (options.chromeExtensionKey) {
chromeExtensionKey = options.chromeExtensionKey;
}
if (options.vertoPort) {
vertoServerCredentials.vertoPort = options.vertoPort;
}
if (options.hostName) {
vertoServerCredentials.hostName = options.hostName;
}
if (options.login) {
vertoServerCredentials.login = options.login;
}
if (options.password) {
vertoServerCredentials.password = options.password;
}
if (options.autoStart) {
handleStartSharingEvent(true);
}
}
public function handleMadePresenterEvent(e:MadePresenterEvent):void {
LOGGER.debug("Got MadePresenterEvent ");
initDeskshare();
}
public function handleMadeViewerEvent(e:MadePresenterEvent):void{
LOGGER.debug("Got MadeViewerEvent ");
if (sharing) {
publishWindowManager.stopSharing();
stopWebRTCDeskshare();
}
sharing = false;
}
private function canIUseVertoOnThisBrowser(onFailure:Function, onSuccess:Function):void {
LOGGER.debug("DeskshareManager::canIUseVertoOnThisBrowser");
var options:DeskshareOptions = new DeskshareOptions();
options.parseOptions();
if (options.useWebRTCIfAvailable && BrowserCheck.isWebRTCSupported()) {
JSLog.warn("WebRTCDeskshareManager::handleStartSharingEvent WebRTC Supported", {});
if (BrowserCheck.isFirefox()) {
onSuccess("Firefox, lets try");
} else {
if (chromeExtensionKey != null) {
/*toolbarButtonManager.startedSharing();*/
JSLog.warn("WebRTCDeskshareManager::handleStartSharingEvent chrome extension key exists - ", chromeExtensionKey);
if (ExternalInterface.available) {
var success:Function = function(status:String):void {
ExternalInterface.addCallback("callback", null);
JSLog.warn("WebRTCDeskshareManager::handleStartSharingEvent inside onSuccess", {});
if (status == "installed-enabled") {
JSLog.warn("Chrome Extension exists", {});
onSuccess("worked");
} else {
onFailure("No Chrome Extension");
}
};
ExternalInterface.addCallback("callback", success);
ExternalInterface.call("getChromeExtensionStatus", chromeExtensionKey, "callback");
}
} else {
onFailure("No chromeExtensionKey in config.xml");
return;
}
}
} else {
onFailure("Web browser doesn't support WebRTC");
return;
}
}
/*handle start sharing event*/
public function handleStartSharingEvent(autoStart:Boolean):void {
LOGGER.debug("WebRTCDeskshareManager::handleStartSharingEvent");
JSLog.warn("WebRTCDeskshareManager::handleStartSharingEvent", {});
var onFailure:Function = function(message:String):void {
JSLog.warn(message, {});
usingWebRTC = false;
// send out event to fallback to Java
JSLog.warn("WebRTCDeskshareManager::handleStartSharingEvent - falling back to java", {});
globalDispatcher.dispatchEvent(new UseJavaModeCommand());
return;
};
var onSuccess:Function = function(message:String):void {
JSLog.warn(message, {});
usingWebRTC = true;
startWebRTCDeskshare();
};
canIUseVertoOnThisBrowser(onFailure, onSuccess);
}
public function handleShareWindowCloseEvent():void {
publishWindowManager.handleShareWindowCloseEvent();
sharing = false;
stopWebRTCDeskshare();
}
public function handleViewWindowCloseEvent():void {
LOGGER.debug("Received stop viewing command");
JSLog.warn("WebRTCDeskshareManager::handleViewWindowCloseEvent", {});
viewWindowManager.handleViewWindowCloseEvent();
}
public function handleStreamStartEvent(e:WebRTCViewStreamEvent):void{
// if (!usingWebRTC) { return; } //TODO this was causing issues
if (sharing) return; //TODO must uncomment this for the non-webrtc desktop share
var isPresenter:Boolean = UserManager.getInstance().getConference().amIPresenter;
LOGGER.debug("Received start viewing command when isPresenter==[{0}]",[isPresenter]);
if(isPresenter) {
publishWindowManager.startViewing(e.rtmp, e.videoWidth, e.videoHeight);
} else {
viewWindowManager.startViewing(e.rtmp, e.videoWidth, e.videoHeight);
}
sharing = true; //TODO must uncomment this for the non-webrtc desktop share
}
public function handleUseJavaModeCommand():void {
usingWebRTC = false;
}
}
}

Some files were not shown because too many files have changed in this diff Show More