Merge branch 'v2.0.x-release' of https://github.com/bigbluebutton/bigbluebutton into whiteboard-bulk-upsert

This commit is contained in:
Maxim Khlobystov 2018-04-25 14:39:48 +00:00
commit 4067f5f808
129 changed files with 3154 additions and 3334 deletions

View File

@ -1,7 +1,7 @@
package org.bigbluebutton.core.apps.users
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.models.{ Roles, Users2x }
import org.bigbluebutton.core.models.{ RegisteredUsers, Roles, Users2x }
import org.bigbluebutton.core.running.{ LiveMeeting, OutMsgRouter }
trait ChangeUserRoleCmdMsgHdlr {
@ -15,7 +15,12 @@ trait ChangeUserRoleCmdMsgHdlr {
uvo <- Users2x.changeRole(liveMeeting.users2x, msg.body.userId, msg.body.role)
} yield {
val userRole = if (uvo.role == Roles.MODERATOR_ROLE) "MODERATOR" else "VIEWER"
for {
// Update guest from waiting list
u <- RegisteredUsers.findWithUserId(uvo.intId, liveMeeting.registeredUsers)
} yield {
RegisteredUsers.updateUserRole(liveMeeting.registeredUsers, u, userRole)
}
val event = buildUserRoleChangedEvtMsg(liveMeeting.props.meetingProp.intId, msg.body.userId,
msg.body.changedBy, userRole)

View File

@ -33,14 +33,6 @@ object RegisteredUsers {
} yield user
}
def updateRegUser(uvo: UserVO, users: RegisteredUsers) {
for {
ru <- RegisteredUsers.findWithUserId(uvo.id, users)
regUser = new RegisteredUser(uvo.id, uvo.externalId, uvo.name, uvo.role, ru.authToken,
uvo.avatarURL, uvo.guest, uvo.authed, uvo.waitingForAcceptance)
} yield users.save(regUser)
}
def add(users: RegisteredUsers, user: RegisteredUser): Vector[RegisteredUser] = {
users.save(user)
}
@ -56,6 +48,13 @@ object RegisteredUsers {
u
}
def updateUserRole(users: RegisteredUsers, user: RegisteredUser,
role: String): RegisteredUser = {
val u = user.modify(_.role).setTo(role)
users.save(u)
u
}
}
class RegisteredUsers {

View File

@ -28,6 +28,7 @@ import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import java.util.Set;
import java.util.concurrent.*;
@ -102,6 +103,18 @@ public class MeetingService implements MessageListener {
public void addUserSession(String token, UserSession user) {
sessions.put(token, user);
}
public String getTokenByUserId(String internalUserId) {
String result = null;
for (Entry<String, UserSession> e : sessions.entrySet()) {
String token = e.getKey();
UserSession userSession = e.getValue();
if (userSession.internalUserId.equals(internalUserId)) {
result = token;
}
}
return result;
}
public void registerUser(String meetingID, String internalUserId,
String fullname, String role, String externUserID,
@ -782,6 +795,12 @@ public class MeetingService implements MessageListener {
User user = m.getUserById(message.userId);
if (user != null) {
user.setRole(message.role);
String sessionToken = getTokenByUserId(user.getInternalUserId());
if (sessionToken != null) {
UserSession userSession = getUserSession(sessionToken);
userSession.role = message.role;
sessions.replace(sessionToken, userSession);
}
log.debug("Setting new role in meeting " + message.meetingId + " for participant:" + user.getFullname());
return;
}

View File

@ -83,6 +83,8 @@ class ReceivedJsonMsgHdlrActor(val msgFromAkkaAppsEventBus: MsgFromAkkaAppsEvent
route[UserBroadcastCamStartedEvtMsg](envelope, jsonNode)
case UserBroadcastCamStoppedEvtMsg.NAME =>
route[UserBroadcastCamStoppedEvtMsg](envelope, jsonNode)
case UserRoleChangedEvtMsg.NAME =>
route[UserRoleChangedEvtMsg](envelope, jsonNode)
case CreateBreakoutRoomSysCmdMsg.NAME =>
route[CreateBreakoutRoomSysCmdMsg](envelope, jsonNode)

View File

@ -32,6 +32,7 @@ class OldMeetingMsgHdlrActor(val olgMsgGW: OldMessageReceivedGW)
case m: UserLeftMeetingEvtMsg => handleUserLeftMeetingEvtMsg(m)
case m: UserJoinedVoiceConfToClientEvtMsg => handleUserJoinedVoiceConfToClientEvtMsg(m)
case m: UserLeftVoiceConfToClientEvtMsg => handleUserLeftVoiceConfToClientEvtMsg(m)
case m: UserRoleChangedEvtMsg => handleUserRoleChangedEvtMsg(m)
case m: UserBroadcastCamStartedEvtMsg => handleUserBroadcastCamStartedEvtMsg(m)
case m: UserBroadcastCamStoppedEvtMsg => handleUserBroadcastCamStoppedEvtMsg(m)
case m: CreateBreakoutRoomSysCmdMsg => handleCreateBreakoutRoomSysCmdMsg(m)
@ -113,6 +114,10 @@ class OldMeetingMsgHdlrActor(val olgMsgGW: OldMessageReceivedGW)
def handleUserBroadcastCamStoppedEvtMsg(msg: UserBroadcastCamStoppedEvtMsg): Unit = {
olgMsgGW.handle(new UserUnsharedWebcam(msg.header.meetingId, msg.body.userId, msg.body.stream))
}
def handleUserRoleChangedEvtMsg(msg: UserRoleChangedEvtMsg): Unit = {
olgMsgGW.handle(new UserRoleChanged(msg.header.meetingId, msg.body.userId, msg.body.role))
}
}

View File

@ -43,21 +43,17 @@ val akkaVersion = "2.5.1"
val scalaTestV = "2.2.6"
libraryDependencies ++= {
val springVersion = "4.3.3.RELEASE"
val springVersion = "4.3.12.RELEASE"
Seq(
"com.typesafe.akka" %% "akka-actor" % akkaVersion,
"com.typesafe.akka" %% "akka-testkit" % akkaVersion % "test",
"com.typesafe.akka" %% "akka-slf4j" % akkaVersion,
"com.typesafe" % "config" % "1.3.0",
"ch.qos.logback" % "logback-classic" % "1.1.6" % "runtime",
// "org.pegdown" % "pegdown" % "1.4.0",
// "junit" % "junit" % "4.11",
// "com.etaty.rediscala" %% "rediscala" % "1.4.0",
"commons-codec" % "commons-codec" % "1.10",
"redis.clients" % "jedis" % "2.7.2",
// "org.apache.commons" % "commons-lang3" % "3.2",
"ch.qos.logback" % "logback-classic" % "1.2.3" % "runtime",
"commons-codec" % "commons-codec" % "1.11",
"redis.clients" % "jedis" % "2.7.2",
"org.apache.commons" % "commons-pool2" % "2.3",
"org.red5" % "red5-server" % "1.0.8-M13",
"org.red5" % "red5-server" % "1.0.10-M5",
"com.google.code.gson" % "gson" % "2.5",
"org.springframework" % "spring-web" % springVersion,
"org.springframework" % "spring-beans" % springVersion,

View File

@ -21,7 +21,7 @@ sudo cp ~/dev/bigbluebutton/bbb-screenshare/app/target/webapp/WEB-INF/lib/bbb-sc
~/dev/bigbluebutton/bbb-screenshare/app/target/webapp/WEB-INF/lib/gson-2.5.jar \
~/dev/bigbluebutton/bbb-screenshare/app/target/webapp/WEB-INF/lib/jedis-2.7.2.jar \
~/dev/bigbluebutton/bbb-screenshare/app/target/webapp/WEB-INF/lib/commons-pool2-2.3.jar \
~/dev/bigbluebutton/bbb-screenshare/app/target/webapp/WEB-INF/lib/spring-webmvc-4.3.3.RELEASE.jar \
~/dev/bigbluebutton/bbb-screenshare/app/target/webapp/WEB-INF/lib/spring-webmvc-4.3.12.RELEASE.jar \
~/dev/bigbluebutton/bbb-screenshare/app/target/webapp/WEB-INF/lib/rediscala_2.12-1.8.0.jar \
~/dev/bigbluebutton/bbb-screenshare/app/target/webapp/WEB-INF/lib/bbb-common-message_2.12-0.0.19-SNAPSHOT.jar \
/usr/share/red5/webapps/screenshare/WEB-INF/lib/

View File

@ -22,31 +22,31 @@ dependencies {
providedCompile 'javax.servlet:servlet-api:2.5@jar'
// Mina
providedCompile 'org.apache.mina:mina-core:2.0.15@jar'
providedCompile 'org.apache.mina:mina-integration-beans:2.0.15@jar'
providedCompile 'org.apache.mina:mina-integration-jmx:2.0.14@jar'
providedCompile 'org.apache.mina:mina-core:2.0.17@jar'
providedCompile 'org.apache.mina:mina-integration-beans:2.0.17@jar'
providedCompile 'org.apache.mina:mina-integration-jmx:2.0.17@jar'
// Spring
providedCompile 'org.springframework:spring-web:4.3.3.RELEASE@jar'
providedCompile 'org.springframework:spring-beans:4.3.3.RELEASE@jar'
providedCompile 'org.springframework:spring-context:4.3.3.RELEASE@jar'
providedCompile 'org.springframework:spring-core:4.3.3.RELEASE@jar'
providedCompile 'org.springframework:spring-web:4.3.12.RELEASE@jar'
providedCompile 'org.springframework:spring-beans:4.3.12.RELEASE@jar'
providedCompile 'org.springframework:spring-context:4.3.12.RELEASE@jar'
providedCompile 'org.springframework:spring-core:4.3.12.RELEASE@jar'
providedCompile 'org.red5:red5-server:1.0.8-M13@jar'
providedCompile 'org.red5:red5-server-common:1.0.8-M13@jar'
providedCompile 'org.red5:red5-io:1.0.8-M13@jar'
providedCompile 'org.red5:red5-server:1.0.10-M5@jar'
providedCompile 'org.red5:red5-server-common:1.0.10-M5@jar'
providedCompile 'org.red5:red5-io:1.0.10-M5@jar'
// Logging
providedCompile 'ch.qos.logback:logback-core:1.1.7@jar'
providedCompile 'ch.qos.logback:logback-classic:1.1.7@jar'
providedCompile 'org.slf4j:log4j-over-slf4j:1.7.21@jar'
providedCompile 'org.slf4j:jcl-over-slf4j:1.7.21@jar'
providedCompile 'org.slf4j:jul-to-slf4j:1.7.21@jar'
providedCompile 'org.slf4j:slf4j-api:1.7.21@jar'
providedCompile 'ch.qos.logback:logback-core:1.2.3@jar'
providedCompile 'ch.qos.logback:logback-classic:1.2.3@jar'
providedCompile 'org.slf4j:log4j-over-slf4j:1.7.25@jar'
providedCompile 'org.slf4j:jcl-over-slf4j:1.7.25@jar'
providedCompile 'org.slf4j:jul-to-slf4j:1.7.25@jar'
providedCompile 'org.slf4j:slf4j-api:1.7.25@jar'
// Needed for the JVM shutdown hook but needs to be put into red5/lib dir.
// Otherwise we get exception on aop utils class not found.
providedCompile 'org.springframework:spring-aop:4.3.3.RELEASE@jar'
providedCompile 'org.springframework:spring-aop:4.3.12.RELEASE@jar'
compile 'aopalliance:aopalliance:1.0@jar'
// Testing
@ -60,7 +60,7 @@ dependencies {
compile 'org.apache.commons:commons-pool2:2.3'
compile 'com.google.code.gson:gson:2.5'
compile 'org.apache.commons:commons-lang3:3.5'
compile 'org.apache.commons:commons-lang3:3.7'
compile 'org.bigbluebutton:bbb-common-message_2.12:0.0.19-SNAPSHOT'
}

View File

@ -23,31 +23,31 @@ dependencies {
providedCompile 'javax.servlet:servlet-api:2.5@jar'
// Mina
providedCompile 'org.apache.mina:mina-core:2.0.15@jar'
providedCompile 'org.apache.mina:mina-integration-beans:2.0.15@jar'
providedCompile 'org.apache.mina:mina-integration-jmx:2.0.14@jar'
providedCompile 'org.apache.mina:mina-core:2.0.17@jar'
providedCompile 'org.apache.mina:mina-integration-beans:2.0.17@jar'
providedCompile 'org.apache.mina:mina-integration-jmx:2.0.17@jar'
// Spring
providedCompile 'org.springframework:spring-web:4.3.3.RELEASE@jar'
providedCompile 'org.springframework:spring-beans:4.3.3.RELEASE@jar'
providedCompile 'org.springframework:spring-context:4.3.3.RELEASE@jar'
providedCompile 'org.springframework:spring-core:4.3.3.RELEASE@jar'
providedCompile 'org.springframework:spring-web:4.3.12.RELEASE@jar'
providedCompile 'org.springframework:spring-beans:4.3.12.RELEASE@jar'
providedCompile 'org.springframework:spring-context:4.3.12.RELEASE@jar'
providedCompile 'org.springframework:spring-core:4.3.12.RELEASE@jar'
providedCompile 'org.red5:red5-server:1.0.8-M13@jar'
providedCompile 'org.red5:red5-server-common:1.0.8-M13@jar'
providedCompile 'org.red5:red5-io:1.0.8-M13@jar'
providedCompile 'org.red5:red5-server:1.0.10-M5@jar'
providedCompile 'org.red5:red5-server-common:1.0.10-M5@jar'
providedCompile 'org.red5:red5-io:1.0.10-M5@jar'
// Logging
providedCompile 'ch.qos.logback:logback-core:1.1.7@jar'
providedCompile 'ch.qos.logback:logback-classic:1.1.7@jar'
providedCompile 'org.slf4j:log4j-over-slf4j:1.7.21@jar'
providedCompile 'org.slf4j:jcl-over-slf4j:1.7.21@jar'
providedCompile 'org.slf4j:jul-to-slf4j:1.7.21@jar'
providedCompile 'org.slf4j:slf4j-api:1.7.21@jar'
providedCompile 'ch.qos.logback:logback-core:1.2.3@jar'
providedCompile 'ch.qos.logback:logback-classic:1.2.3@jar'
providedCompile 'org.slf4j:log4j-over-slf4j:1.7.25@jar'
providedCompile 'org.slf4j:jcl-over-slf4j:1.7.25@jar'
providedCompile 'org.slf4j:jul-to-slf4j:1.7.25@jar'
providedCompile 'org.slf4j:slf4j-api:1.7.25@jar'
// Needed for the JVM shutdown hook but needs to be put into red5/lib dir.
// Otherwise we get exception on aop utils class not found.
providedCompile 'org.springframework:spring-aop:4.3.3.RELEASE@jar'
providedCompile 'org.springframework:spring-aop:4.3.12.RELEASE@jar'
compile 'aopalliance:aopalliance:1.0@jar'
// Testing
@ -61,7 +61,7 @@ dependencies {
compile 'org.apache.commons:commons-pool2:2.3'
compile 'com.google.code.gson:gson:2.5'
compile 'org.apache.commons:commons-lang3:3.5'
compile 'org.apache.commons:commons-lang3:3.7'
compile 'org.bigbluebutton:bbb-common-message_2.12:0.0.19-SNAPSHOT'
}

View File

@ -54,6 +54,7 @@ public class AudioBroadcastStream implements IBroadcastStream, IProvider, IPipeC
// Codec handling stuff for frame dropping
private StreamCodecInfo streamCodecInfo;
private Long creationTime;
private Long startTime;
public AudioBroadcastStream(String name) {
publishedStreamName = name;
@ -137,6 +138,8 @@ public class AudioBroadcastStream implements IBroadcastStream, IProvider, IPipeC
public void start() {
log.debug("Starting AudioBroadcastStream()");
creationTime = System.currentTimeMillis();
startTime = creationTime;
}
public void stop() {
@ -205,4 +208,8 @@ public class AudioBroadcastStream implements IBroadcastStream, IProvider, IPipeC
public long getCreationTime() {
return creationTime != null ? creationTime : 0L;
}
public long getStartTime() {
return startTime != null ? startTime : 0L;
}
}

View File

@ -37,7 +37,8 @@ public class AudioStream implements IBroadcastStream, IProvider, IPipeConnection
// Codec handling stuff for frame dropping
private StreamCodecInfo streamCodecInfo;
private Long creationTime;
private Long startTime;
public AudioStream(String name) {
publishedStreamName = name;
livePipe = null;
@ -107,6 +108,8 @@ public class AudioStream implements IBroadcastStream, IProvider, IPipeConnection
public void start() {
log.trace("start()");
creationTime = System.currentTimeMillis();
startTime = creationTime;
}
public void stop() {
@ -174,4 +177,8 @@ public class AudioStream implements IBroadcastStream, IProvider, IPipeConnection
public long getCreationTime() {
return creationTime != null ? creationTime : 0L;
}
public long getStartTime() {
return startTime != null ? startTime : 0L;
}
}

View File

@ -26,31 +26,31 @@ dependencies {
providedCompile 'javax.servlet:servlet-api:2.5@jar'
// Mina
providedCompile 'org.apache.mina:mina-core:2.0.15@jar'
providedCompile 'org.apache.mina:mina-integration-beans:2.0.15@jar'
providedCompile 'org.apache.mina:mina-integration-jmx:2.0.14@jar'
providedCompile 'org.apache.mina:mina-core:2.0.17@jar'
providedCompile 'org.apache.mina:mina-integration-beans:2.0.17@jar'
providedCompile 'org.apache.mina:mina-integration-jmx:2.0.17@jar'
// Spring
providedCompile 'org.springframework:spring-web:4.3.3.RELEASE@jar'
providedCompile 'org.springframework:spring-beans:4.3.3.RELEASE@jar'
providedCompile 'org.springframework:spring-context:4.3.3.RELEASE@jar'
providedCompile 'org.springframework:spring-core:4.3.3.RELEASE@jar'
providedCompile 'org.springframework:spring-web:4.3.12.RELEASE@jar'
providedCompile 'org.springframework:spring-beans:4.3.12.RELEASE@jar'
providedCompile 'org.springframework:spring-context:4.3.12.RELEASE@jar'
providedCompile 'org.springframework:spring-core:4.3.13.RELEASE@jar'
// Red5
providedCompile 'org.red5:red5-server:1.0.8-M13@jar'
providedCompile 'org.red5:red5-server-common:1.0.8-M13@jar'
providedCompile 'org.red5:red5-server:1.0.10-M5@jar'
providedCompile 'org.red5:red5-server-common:1.0.10-M5@jar'
// Logging
providedCompile 'ch.qos.logback:logback-core:1.1.7@jar'
providedCompile 'ch.qos.logback:logback-classic:1.1.7@jar'
providedCompile 'org.slf4j:log4j-over-slf4j:1.7.21@jar'
providedCompile 'org.slf4j:jcl-over-slf4j:1.7.21@jar'
providedCompile 'org.slf4j:jul-to-slf4j:1.7.21@jar'
providedCompile 'org.slf4j:slf4j-api:1.7.23@jar'
providedCompile 'ch.qos.logback:logback-core:1.2.3@jar'
providedCompile 'ch.qos.logback:logback-classic:1.2.3@jar'
providedCompile 'org.slf4j:log4j-over-slf4j:1.7.25@jar'
providedCompile 'org.slf4j:jcl-over-slf4j:1.7.25@jar'
providedCompile 'org.slf4j:jul-to-slf4j:1.7.25@jar'
providedCompile 'org.slf4j:slf4j-api:1.7.25@jar'
// Needed for the JVM shutdown hook but needs to be put into red5/lib dir.
// Otherwise we get exception on aop utils class not found.
providedCompile 'org.springframework:spring-aop:4.3.3.RELEASE@jar'
providedCompile 'org.springframework:spring-aop:4.3.12.RELEASE@jar'
compile 'aopalliance:aopalliance:1.0@jar'
// Testing
@ -61,7 +61,7 @@ dependencies {
compile 'org.apache.commons:commons-pool2:2.3'
compile 'com.google.code.gson:gson:2.5'
providedCompile 'org.apache.commons:commons-lang3:3.2'
providedCompile 'org.apache.commons:commons-lang3:3.7'
compile 'org.bigbluebutton:bbb-common-message_2.12:0.0.19-SNAPSHOT'
compile 'org.bigbluebutton:bbb-apps-common_2.12:0.0.1-SNAPSHOT'

View File

@ -1,6 +1,8 @@
#!/bin/bash
# deploying 'bigbluebutton-apps' to /usr/share/red5/webapps
sudo chown -R red5.red5 /usr/share/red5/webapps
gradle clean
gradle resolveDeps
gradle war deploy

View File

@ -1,6 +1,6 @@
bbb.mainshell.locale.version =
bbb.mainshell.statusProgress.connecting =
bbb.mainshell.statusProgress.loading =
bbb.mainshell.statusProgress.connecting = ከአገልጋዩ ጋር በመገናኘት ላይ
bbb.mainshell.statusProgress.loading = በመጫን ላይ
bbb.mainshell.statusProgress.cannotConnectServer =
bbb.mainshell.copyrightLabel2 =
bbb.mainshell.logBtn.toolTip =
@ -50,7 +50,7 @@ bbb.micSettings.nextButton =
bbb.micSettings.nextButton.toolTip =
bbb.micSettings.join =
bbb.micSettings.join.toolTip =
bbb.micSettings.cancel =
bbb.micSettings.cancel = ሰርዝ
bbb.micSettings.connectingtoecho =
bbb.micSettings.connectingtoecho.error =
bbb.micSettings.cancel.toolTip =
@ -97,14 +97,14 @@ bbb.webrtcWarning.connection.reestablished =
bbb.inactivityWarning.title =
bbb.inactivityWarning.message =
bbb.shuttingDown.message =
bbb.inactivityWarning.cancel =
bbb.mainToolbar.helpBtn =
bbb.mainToolbar.logoutBtn =
bbb.mainToolbar.logoutBtn.toolTip =
bbb.inactivityWarning.cancel = ሰርዝ
bbb.mainToolbar.helpBtn = እገዛ
bbb.mainToolbar.logoutBtn = ውጣ
bbb.mainToolbar.logoutBtn.toolTip = ውጣ
bbb.mainToolbar.idleLogoutBtn =
bbb.mainToolbar.langSelector =
bbb.mainToolbar.settingsBtn =
bbb.mainToolbar.settingsBtn.toolTip =
bbb.mainToolbar.langSelector = ቋንቋ ይምረጡ
bbb.mainToolbar.settingsBtn = ቅንብሮች
bbb.mainToolbar.settingsBtn.toolTip = ቅንብሮችን ክፈት
bbb.mainToolbar.shortcutBtn =
bbb.mainToolbar.shortcutBtn.toolTip =
bbb.mainToolbar.recordBtn.toolTip.start =
@ -138,7 +138,7 @@ bbb.guests.askModerator =
bbb.guests.Management =
bbb.clientstatus.title =
bbb.clientstatus.notification =
bbb.clientstatus.close =
bbb.clientstatus.close = ይዝጉ
bbb.clientstatus.tunneling.title =
bbb.clientstatus.tunneling.message =
bbb.clientstatus.browser.title =
@ -155,9 +155,9 @@ bbb.clientstatus.java.title =
bbb.clientstatus.java.notdetected =
bbb.clientstatus.java.notinstalled =
bbb.clientstatus.java.oldversion =
bbb.window.minimizeBtn.toolTip =
bbb.window.minimizeBtn.toolTip = አሳንስ
bbb.window.maximizeRestoreBtn.toolTip =
bbb.window.closeBtn.toolTip =
bbb.window.closeBtn.toolTip = ይዝጉ
bbb.videoDock.titleBar =
bbb.presentation.titleBar =
bbb.chat.titleBar =
@ -166,7 +166,7 @@ bbb.users.titleBar =
bbb.users.quickLink.label =
bbb.users.minimizeBtn.accessibilityName =
bbb.users.maximizeRestoreBtn.accessibilityName =
bbb.users.settings.buttonTooltip =
bbb.users.settings.buttonTooltip = ቅንብሮች
bbb.users.settings.audioSettings =
bbb.users.settings.webcamSettings =
bbb.users.settings.muteAll =
@ -179,16 +179,16 @@ bbb.users.roomLocked.text =
bbb.users.pushToTalk.toolTip =
bbb.users.pushToMute.toolTip =
bbb.users.muteMeBtnTxt.talk =
bbb.users.muteMeBtnTxt.mute =
bbb.users.muteMeBtnTxt.muted =
bbb.users.muteMeBtnTxt.mute = ድምጸ-ከል ያድርጉ
bbb.users.muteMeBtnTxt.muted = ድምጸ-ከል ተደርጓል
bbb.users.usersGrid.contextmenu.exportusers =
bbb.users.usersGrid.accessibilityName =
bbb.users.usersGrid.nameItemRenderer =
bbb.users.usersGrid.nameItemRenderer.youIdentifier =
bbb.users.usersGrid.statusItemRenderer =
bbb.users.usersGrid.nameItemRenderer = ስም
bbb.users.usersGrid.nameItemRenderer.youIdentifier = አንተ
bbb.users.usersGrid.statusItemRenderer = ሁኔታ
bbb.users.usersGrid.statusItemRenderer.changePresenter =
bbb.users.usersGrid.statusItemRenderer.presenter =
bbb.users.usersGrid.statusItemRenderer.moderator =
bbb.users.usersGrid.statusItemRenderer.moderator = አወያይ
bbb.users.usersGrid.statusItemRenderer.voiceOnlyUser =
bbb.users.usersGrid.statusItemRenderer.raiseHand =
bbb.users.usersGrid.statusItemRenderer.applause =
@ -204,14 +204,14 @@ bbb.users.usersGrid.statusItemRenderer.neutral =
bbb.users.usersGrid.statusItemRenderer.happy =
bbb.users.usersGrid.statusItemRenderer.sad =
bbb.users.usersGrid.statusItemRenderer.clearStatus =
bbb.users.usersGrid.statusItemRenderer.viewer =
bbb.users.usersGrid.statusItemRenderer.viewer = ተመልካች
bbb.users.usersGrid.statusItemRenderer.streamIcon.toolTip =
bbb.users.usersGrid.statusItemRenderer.presIcon.toolTip =
bbb.users.usersGrid.mediaItemRenderer =
bbb.users.usersGrid.mediaItemRenderer.talking =
bbb.users.usersGrid.mediaItemRenderer.webcam =
bbb.users.usersGrid.mediaItemRenderer.webcamBtn =
bbb.users.usersGrid.mediaItemRenderer.pushToTalk =
bbb.users.usersGrid.mediaItemRenderer.pushToTalk = ድምጸ-ከል አንሳ {0}
bbb.users.usersGrid.mediaItemRenderer.pushToMute =
bbb.users.usersGrid.mediaItemRenderer.pushToLock =
bbb.users.usersGrid.mediaItemRenderer.pushToUnlock =
@ -284,10 +284,10 @@ bbb.fileupload.selectBtn.toolTip =
bbb.fileupload.uploadBtn =
bbb.fileupload.uploadBtn.toolTip =
bbb.fileupload.deleteBtn.toolTip =
bbb.fileupload.showBtn =
bbb.fileupload.showBtn = አሳይ
bbb.fileupload.retry =
bbb.fileupload.showBtn.toolTip =
bbb.fileupload.close.tooltip =
bbb.fileupload.close.tooltip = ይዝጉ
bbb.fileupload.close.accessibilityName =
bbb.fileupload.genThumbText =
bbb.fileupload.progBarLbl =
@ -295,7 +295,7 @@ bbb.fileupload.fileFormatHint =
bbb.fileupload.letUserDownload =
bbb.fileupload.letUserDownload.tooltip =
bbb.filedownload.title =
bbb.filedownload.close.tooltip =
bbb.filedownload.close.tooltip = ይዝጉ
bbb.filedownload.close.accessibilityName =
bbb.filedownload.fileLbl =
bbb.filedownload.downloadBtn =
@ -367,7 +367,7 @@ bbb.video.publish.hint.cameraDenied =
bbb.video.publish.hint.cameraIsBeingUsed =
bbb.video.publish.hint.publishing =
bbb.video.publish.closeBtn.accessName =
bbb.video.publish.closeBtn.label =
bbb.video.publish.closeBtn.label = ሰርዝ
bbb.video.publish.titleBar =
bbb.video.streamClose.toolTip =
bbb.video.message.browserhttp =
@ -379,11 +379,11 @@ bbb.screensharePublish.restart.label =
bbb.screensharePublish.maximizeRestoreBtn.toolTip =
bbb.screensharePublish.closeBtn.toolTip =
bbb.screensharePublish.closeBtn.accessibilityName =
bbb.screensharePublish.minimizeBtn.toolTip =
bbb.screensharePublish.minimizeBtn.toolTip = አሳንስ
bbb.screensharePublish.minimizeBtn.accessibilityName =
bbb.screensharePublish.maximizeRestoreBtn.accessibilityName =
bbb.screensharePublish.commonHelpText.text =
bbb.screensharePublish.helpButton.toolTip =
bbb.screensharePublish.helpButton.toolTip = እገዛ
bbb.screensharePublish.helpButton.accessibilityName =
bbb.screensharePublish.helpText.PCIE1 =
bbb.screensharePublish.helpText.PCIE2 =
@ -422,7 +422,7 @@ bbb.screensharePublish.jwsCrashed.label =
bbb.screensharePublish.commonErrorMessage.label =
bbb.screensharePublish.tunnelingErrorMessage.one =
bbb.screensharePublish.tunnelingErrorMessage.two =
bbb.screensharePublish.cancelButton.label =
bbb.screensharePublish.cancelButton.label = ሰርዝ
bbb.screensharePublish.startButton.label =
bbb.screensharePublish.stopButton.label =
bbb.screensharePublish.stopButton.toolTip =
@ -464,7 +464,7 @@ bbb.layout.combo.custom =
bbb.layout.combo.customName =
bbb.layout.combo.remote =
bbb.layout.window.name =
bbb.layout.window.close.tooltip =
bbb.layout.window.close.tooltip = ይዝጉ
bbb.layout.window.close.accessibilityName =
bbb.layout.save.complete =
bbb.layout.save.ioerror =
@ -517,9 +517,9 @@ bbb.logout.refresh.message =
bbb.logout.refresh.label =
bbb.logout.feedback.hint =
bbb.logout.feedback.label =
bbb.settings.title =
bbb.settings.title = ቅንብሮች
bbb.settings.ok =
bbb.settings.cancel =
bbb.settings.cancel = ሰርዝ
bbb.settings.btn.toolTip =
bbb.logout.confirm.title =
bbb.logout.confirm.message =
@ -542,7 +542,7 @@ bbb.notes.saveBtn.toolTip =
bbb.sharedNotes.title =
bbb.sharedNotes.quickLink.label =
bbb.sharedNotes.createNoteWindow.label =
bbb.sharedNotes.createNoteWindow.close.tooltip =
bbb.sharedNotes.createNoteWindow.close.tooltip = ይዝጉ
bbb.sharedNotes.createNoteWindow.close.accessibilityName =
bbb.sharedNotes.typing.single =
bbb.sharedNotes.typing.double =
@ -762,7 +762,7 @@ bbb.shortcutkey.caption.takeOwnership.function =
bbb.polling.startButton.tooltip =
bbb.polling.startButton.label =
bbb.polling.publishButton.label =
bbb.polling.closeButton.label =
bbb.polling.closeButton.label = ይዝጉ
bbb.polling.customPollOption.label =
bbb.polling.pollModal.title =
bbb.polling.pollModal.hint =
@ -810,7 +810,7 @@ bbb.users.settings.roomIsMuted =
bbb.lockSettings.save =
bbb.lockSettings.save.tooltip =
bbb.lockSettings.cancel =
bbb.lockSettings.cancel = ሰርዝ
bbb.lockSettings.cancel.toolTip =
bbb.lockSettings.hint =
@ -845,7 +845,7 @@ bbb.users.breakout.notAssigned =
bbb.users.breakout.dragAndDropToolTip =
bbb.users.breakout.start =
bbb.users.breakout.invite =
bbb.users.breakout.close =
bbb.users.breakout.close = ይዝጉ
bbb.users.breakout.closeAllRooms =
bbb.users.breakout.insufficientUsers =
bbb.users.breakout.confirm =
@ -853,7 +853,7 @@ bbb.users.breakout.invited =
bbb.users.breakout.accept =
bbb.users.breakout.joinSession =
bbb.users.breakout.joinSession.accessibilityName =
bbb.users.breakout.joinSession.close.tooltip =
bbb.users.breakout.joinSession.close.tooltip = ይዝጉ
bbb.users.breakout.joinSession.close.accessibilityName =
bbb.users.breakout.youareinroom =
bbb.users.roomsGrid.room =
@ -865,7 +865,7 @@ bbb.users.roomsGrid.noUsers =
bbb.langSelector.default=
bbb.alert.cancel =
bbb.alert.cancel = ሰርዝ
bbb.alert.ok =
bbb.alert.no =
bbb.alert.yes =

View File

@ -320,6 +320,9 @@ Kurento.prototype.viewer = function () {
const self = this;
if (!this.webRtcPeer) {
const options = {
mediaConstraints: {
audio: false
},
remoteVideo: document.getElementById(this.renderTag),
onicecandidate: this.onViewerIceCandidate.bind(this),
};
@ -409,7 +412,7 @@ Kurento.normalizeCallback = function (callback) {
// this function explains how to use above methods/objects
window.getScreenConstraints = function (sendSource, callback) {
const screenConstraints = { video: {} };
const screenConstraints = { video: {}, audio: false };
// Limiting FPS to a range of 5-10 (5 ideal)
screenConstraints.video.frameRate = { ideal: 5, max: 10 };
@ -431,6 +434,17 @@ window.getScreenConstraints = function (sendSource, callback) {
// this statement sets gets 'sourceId" and sets "chromeMediaSourceId"
screenConstraints.video.chromeMediaSource = { exact: [sendSource] };
screenConstraints.video.chromeMediaSourceId = sourceId;
screenConstraints.optional = [
{ googCpuOveruseDetection: true },
{ googCpuOveruseEncodeUsage: true },
{ googCpuUnderuseThreshold: 55 },
{ googCpuOveruseThreshold: 85 },
{ googPayloadPadding: true },
{ googScreencastMinBitrate: 400 },
{ googHighStartBitrate: true },
{ googHighBitrate: true },
{ googVeryHighBitrate: true }
];
console.log('getScreenConstraints for Chrome returns => ', screenConstraints);
// now invoking native getUserMedia API
@ -494,3 +508,41 @@ window.getChromeScreenConstraints = function (callback, extensionId) {
},
);
};
// a function to check whether the browser (Chrome only) is in an isIncognito
// session. Requires 1 mandatory callback that only gets called if the browser
// session is incognito. The callback for not being incognito is optional.
// Attempts to retrieve the chrome filesystem API.
window.checkIfIncognito = function(isIncognito, isNotIncognito = function () {}) {
isIncognito = Kurento.normalizeCallback(isIncognito);
isNotIncognito = Kurento.normalizeCallback(isNotIncognito);
var fs = window.RequestFileSystem || window.webkitRequestFileSystem;
if (!fs) {
isNotIncognito();
return;
}
fs(window.TEMPORARY, 100, function(){isNotIncognito()}, function(){isIncognito()});
};
window.checkChromeExtInstalled = function (callback, chromeExtensionId) {
callback = Kurento.normalizeCallback(callback);
if (typeof chrome === "undefined" || !chrome || !chrome.runtime) {
// No API, so no extension for sure
callback(false);
return;
}
chrome.runtime.sendMessage(
chromeExtensionId,
{ getVersion: true },
function (response) {
if (!response || !response.version) {
// Communication failure - assume that no endpoint exists
callback(false);
return;
}
callback(true);
}
);
}

View File

@ -1,561 +0,0 @@
(function($){function findLine(sdpLines,prefix,substr){return findLineInRange(sdpLines,0,-1,prefix,substr);}
function findLineInRange(sdpLines,startLine,endLine,prefix,substr){var realEndLine=(endLine!=-1)?endLine:sdpLines.length;for(var i=startLine;i<realEndLine;++i){if(sdpLines[i].indexOf(prefix)===0){if(!substr||sdpLines[i].toLowerCase().indexOf(substr.toLowerCase())!==-1){return i;}}}
return null;}
function getCodecPayloadType(sdpLine){var pattern=new RegExp('a=rtpmap:(\\d+) \\w+\\/\\d+');var result=sdpLine.match(pattern);return(result&&result.length==2)?result[1]:null;}
function setDefaultCodec(mLine,payload){var elements=mLine.split(' ');var newLine=[];var index=0;for(var i=0;i<elements.length;i++){if(index===3){newLine[index++]=payload;}
if(elements[i]!==payload)newLine[index++]=elements[i];}
return newLine.join(' ');}
$.FSRTC=function(options){this.options=$.extend({useVideo:null,useStereo:false,userData:null,localVideo:null,screenShare:false,useCamera:"any",iceServers:false,videoParams:{},audioParams:{},callbacks:{onICEComplete:function(){},onICE:function(){},onOfferSDP:function(){}},},options);this.audioEnabled=true;this.videoEnabled=true;this.mediaData={SDP:null,profile:{},candidateList:[]};this.constraints={offerToReceiveAudio:this.options.useSpeak==="none"?false:true,offerToReceiveVideo:this.options.useVideo?true:false,};if(self.options.useVideo){self.options.useVideo.style.display='none';}
setCompat();checkCompat();};$.FSRTC.validRes=[];$.FSRTC.prototype.useVideo=function(obj,local){var self=this;if(obj){self.options.useVideo=obj;self.options.localVideo=local;self.constraints.offerToReceiveVideo=true;}else{self.options.useVideo=null;self.options.localVideo=null;self.constraints.offerToReceiveVideo=false;}
if(self.options.useVideo){self.options.useVideo.style.display='none';}};$.FSRTC.prototype.useStereo=function(on){var self=this;self.options.useStereo=on;};$.FSRTC.prototype.stereoHack=function(sdp){var self=this;if(!self.options.useStereo){return sdp;}
var sdpLines=sdp.split('\r\n');var opusIndex=findLine(sdpLines,'a=rtpmap','opus/48000'),opusPayload;if(!opusIndex){return sdp;}else{opusPayload=getCodecPayloadType(sdpLines[opusIndex]);}
var fmtpLineIndex=findLine(sdpLines,'a=fmtp:'+opusPayload.toString());if(fmtpLineIndex===null){sdpLines[opusIndex]=sdpLines[opusIndex]+'\r\na=fmtp:'+opusPayload.toString()+" stereo=1; sprop-stereo=1"}else{sdpLines[fmtpLineIndex]=sdpLines[fmtpLineIndex].concat('; stereo=1; sprop-stereo=1');}
sdp=sdpLines.join('\r\n');return sdp;};function setCompat(){}
function checkCompat(){return true;}
function onStreamError(self,e){console.log('There has been a problem retrieving the streams - did you allow access? Check Device Resolution',e);doCallback(self,"onError",e);}
function onStreamSuccess(self,stream){console.log("Stream Success");doCallback(self,"onStream",stream);}
function onICE(self,candidate){self.mediaData.candidate=candidate;self.mediaData.candidateList.push(self.mediaData.candidate);doCallback(self,"onICE");}
function doCallback(self,func,arg){if(func in self.options.callbacks){self.options.callbacks[func](self,arg);}}
function onICEComplete(self,candidate){console.log("ICE Complete");doCallback(self,"onICEComplete");}
function onChannelError(self,e){console.error("Channel Error",e);doCallback(self,"onError",e);}
function onICESDP(self,sdp){self.mediaData.SDP=self.stereoHack(sdp.sdp);console.log("ICE SDP");doCallback(self,"onICESDP");}
function onAnswerSDP(self,sdp){self.answer.SDP=self.stereoHack(sdp.sdp);console.log("ICE ANSWER SDP");doCallback(self,"onAnswerSDP",self.answer.SDP);}
function onMessage(self,msg){console.log("Message");doCallback(self,"onICESDP",msg);}
FSRTCattachMediaStream=function(element,stream){if(typeof element.srcObject!=='undefined'){element.srcObject=stream;}else if(typeof element.src!=='undefined'){element.src=URL.createObjectURL(stream);}else{console.error('Error attaching stream to element.');}}
function onRemoteStream(self,stream){if(self.options.useVideo){self.options.useVideo.style.display='block';}
var element=self.options.useAudio;console.log("REMOTE STREAM",stream,element);FSRTCattachMediaStream(element,stream);self.options.useAudio.play();self.remoteStream=stream;}
function onOfferSDP(self,sdp){self.mediaData.SDP=self.stereoHack(sdp.sdp);console.log("Offer SDP");doCallback(self,"onOfferSDP");}
$.FSRTC.prototype.answer=function(sdp,onSuccess,onError){this.peer.addAnswerSDP({type:"answer",sdp:sdp},onSuccess,onError);};$.FSRTC.prototype.stopPeer=function(){if(self.peer){console.log("stopping peer");self.peer.stop();}}
$.FSRTC.prototype.stop=function(){var self=this;if(self.options.useVideo){self.options.useVideo.style.display='none';self.options.useVideo['src']='';}
if(self.localStream){if(typeof self.localStream.stop=='function'){self.localStream.stop();}else{if(self.localStream.active){var tracks=self.localStream.getTracks();console.log(tracks);tracks.forEach(function(track,index){console.log(track);track.stop();})}}
self.localStream=null;}
if(self.options.localVideo){self.options.localVideo.style.display='none';self.options.localVideo['src']='';}
if(self.options.localVideoStream){if(typeof self.options.localVideoStream.stop=='function'){self.options.localVideoStream.stop();}else{if(self.options.localVideoStream.active){var tracks=self.options.localVideoStream.getTracks();console.log(tracks);tracks.forEach(function(track,index){console.log(track);track.stop();})}}}
if(self.peer){console.log("stopping peer");self.peer.stop();}};$.FSRTC.prototype.getMute=function(){var self=this;return self.audioEnabled;}
$.FSRTC.prototype.setMute=function(what){var self=this;var audioTracks=self.localStream.getAudioTracks();for(var i=0,len=audioTracks.length;i<len;i++){switch(what){case"on":audioTracks[i].enabled=true;break;case"off":audioTracks[i].enabled=false;break;case"toggle":audioTracks[i].enabled=!audioTracks[i].enabled;default:break;}
self.audioEnabled=audioTracks[i].enabled;}
return!self.audioEnabled;}
$.FSRTC.prototype.getVideoMute=function(){var self=this;return self.videoEnabled;}
$.FSRTC.prototype.setVideoMute=function(what){var self=this;var videoTracks=self.localStream.getVideoTracks();for(var i=0,len=videoTracks.length;i<len;i++){switch(what){case"on":videoTracks[i].enabled=true;break;case"off":videoTracks[i].enabled=false;break;case"toggle":videoTracks[i].enabled=!videoTracks[i].enabled;default:break;}
self.videoEnabled=videoTracks[i].enabled;}
return!self.videoEnabled;}
$.FSRTC.prototype.createAnswer=function(params){var self=this;self.type="answer";self.remoteSDP=params.sdp;console.debug("inbound sdp: ",params.sdp);function onSuccess(stream){self.localStream=stream;self.peer=FSRTCPeerConnection({type:self.type,attachStream:self.localStream,onICE:function(candidate){return onICE(self,candidate);},onICEComplete:function(){return onICEComplete(self);},onRemoteStream:function(stream){return onRemoteStream(self,stream);},onICESDP:function(sdp){return onICESDP(self,sdp);},onChannelError:function(e){return onChannelError(self,e);},constraints:self.constraints,iceServers:self.options.iceServers,offerSDP:{type:"offer",sdp:self.remoteSDP}});onStreamSuccess(self,stream);}
function onError(e){onStreamError(self,e);}
var mediaParams=getMediaParams(self);console.log("Audio constraints",mediaParams.audio);console.log("Video constraints",mediaParams.video);if(self.options.useVideo&&self.options.localVideo){getUserMedia({constraints:{audio:false,video:{},},localVideo:self.options.localVideo,onsuccess:function(e){self.options.localVideoStream=e;console.log("local video ready");},onerror:function(e){console.error("local video error!");}});}
getUserMedia({constraints:{audio:mediaParams.audio,video:mediaParams.video},video:mediaParams.useVideo,onsuccess:onSuccess,onerror:onError});};function getMediaParams(obj){var audio;if(obj.options.useMic&&obj.options.useMic==="none"){console.log("Microphone Disabled");audio=false;}else if(obj.options.videoParams&&obj.options.screenShare){console.error("SCREEN SHARE",obj.options.videoParams);audio=false;}else{audio={};if(obj.options.audioParams){audio=obj.options.audioParams;}
if(obj.options.useMic!=="any"){audio.deviceId={exact:obj.options.useMic};}}
if(obj.options.useVideo&&obj.options.localVideo){getUserMedia({constraints:{audio:false,video:obj.options.videoParams},localVideo:obj.options.localVideo,onsuccess:function(e){self.options.localVideoStream=e;console.log("local video ready");},onerror:function(e){console.error("local video error!");}});}
var video={};var bestFrameRate=obj.options.videoParams.vertoBestFrameRate;var minFrameRate=obj.options.videoParams.minFrameRate||15;delete obj.options.videoParams.vertoBestFrameRate;if(obj.options.screenShare){if(!obj.options.useCamera&&!!navigator.mozGetUserMedia){var dowin=window.confirm("Do you want to share an application window? If not you can share an entire screen.");video={width:{min:obj.options.videoParams.minWidth,max:obj.options.videoParams.maxWidth},height:{min:obj.options.videoParams.minHeight,max:obj.options.videoParams.maxHeight},mediaSource:dowin?"window":"screen"}}else{var opt=[];if(obj.options.useCamera){opt.push({sourceId:obj.options.useCamera});}
if(bestFrameRate){opt.push({minFrameRate:bestFrameRate});opt.push({maxFrameRate:bestFrameRate});}
video={mandatory:obj.options.videoParams,optional:opt};}}else{video={width:{min:obj.options.videoParams.minWidth,max:obj.options.videoParams.maxWidth},height:{min:obj.options.videoParams.minHeight,max:obj.options.videoParams.maxHeight}};var useVideo=obj.options.useVideo;if(useVideo&&obj.options.useCamera&&obj.options.useCamera!=="none"){if(obj.options.useCamera!=="any"){video.deviceId=obj.options.useCamera;}
if(bestFrameRate){video.frameRate={ideal:bestFrameRate,min:minFrameRate,max:30};}}else{console.log("Camera Disabled");video=false;useVideo=false;}}
return{audio:audio,video:video,useVideo:useVideo};}
$.FSRTC.prototype.call=function(profile){checkCompat();var self=this;var screen=false;self.type="offer";if(self.options.videoParams&&self.options.screenShare){screen=true;}
function onSuccess(stream){self.localStream=stream;if(screen){self.constraints.offerToReceiveVideo=false;self.constraints.offerToReceiveAudio=false;self.constraints.offerToSendAudio=false;}
self.peer=FSRTCPeerConnection({type:self.type,attachStream:self.localStream,onICE:function(candidate){return onICE(self,candidate);},onICEComplete:function(){return onICEComplete(self);},onRemoteStream:screen?function(stream){}:function(stream){return onRemoteStream(self,stream);},onOfferSDP:function(sdp){return onOfferSDP(self,sdp);},onICESDP:function(sdp){return onICESDP(self,sdp);},onChannelError:function(e){return onChannelError(self,e);},constraints:self.constraints,iceServers:self.options.iceServers,});onStreamSuccess(self,stream);}
function onError(e){onStreamError(self,e);}
var mediaParams=getMediaParams(self);console.log("Audio constraints",mediaParams.audio);console.log("Video constraints",mediaParams.video);if(mediaParams.audio||mediaParams.video){getUserMedia({constraints:{audio:mediaParams.audio,video:mediaParams.video},video:mediaParams.useVideo,onsuccess:onSuccess,onerror:onError});}else{onSuccess(null);}};function FSRTCPeerConnection(options){var gathering=false,done=false;var config={};var default_ice={urls:['stun:stun.l.google.com:19302']};if(options.iceServers){if(typeof(options.iceServers)==="boolean"){config.iceServers=[default_ice];}else{config.iceServers=options.iceServers;}}
var peer=new window.RTCPeerConnection(config);openOffererChannel();var x=0;function ice_handler(){done=true;gathering=null;if(options.onICEComplete){options.onICEComplete();}
if(options.type=="offer"){options.onICESDP(peer.localDescription);}else{if(!x&&options.onICESDP){options.onICESDP(peer.localDescription);}}}
peer.onicecandidate=function(event){if(done){return;}
if(!gathering){gathering=setTimeout(ice_handler,1000);}
if(event){if(event.candidate){options.onICE(event.candidate);}}else{done=true;if(gathering){clearTimeout(gathering);gathering=null;}
ice_handler();}};if(options.attachStream)peer.addStream(options.attachStream);if(options.attachStreams&&options.attachStream.length){var streams=options.attachStreams;for(var i=0;i<streams.length;i++){peer.addStream(streams[i]);}}
peer.onaddstream=function(event){var remoteMediaStream=event.stream;remoteMediaStream.oninactive=function(){if(options.onRemoteStreamEnded)options.onRemoteStreamEnded(remoteMediaStream);};if(options.onRemoteStream)options.onRemoteStream(remoteMediaStream);};function createOffer(){if(!options.onOfferSDP)return;peer.createOffer(function(sessionDescription){sessionDescription.sdp=serializeSdp(sessionDescription.sdp);peer.setLocalDescription(sessionDescription);options.onOfferSDP(sessionDescription);},onSdpError,options.constraints);}
function createAnswer(){if(options.type!="answer")return;peer.setRemoteDescription(new window.RTCSessionDescription(options.offerSDP),onSdpSuccess,onSdpError);peer.createAnswer(function(sessionDescription){sessionDescription.sdp=serializeSdp(sessionDescription.sdp);peer.setLocalDescription(sessionDescription);if(options.onAnswerSDP){options.onAnswerSDP(sessionDescription);}},onSdpError);}
if((options.onChannelMessage)||!options.onChannelMessage){createOffer();createAnswer();}
function setBandwidth(sdp){sdp=sdp.replace(/b=AS([^\r\n]+\r\n)/g,'');sdp=sdp.replace(/a=mid:data\r\n/g,'a=mid:data\r\nb=AS:1638400\r\n');return sdp;}
function getInteropSDP(sdp){var chars='ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(''),extractedChars='';function getChars(){extractedChars+=chars[parseInt(Math.random()*40)]||'';if(extractedChars.length<40)getChars();return extractedChars;}
if(options.onAnswerSDP)sdp=sdp.replace(/(a=crypto:0 AES_CM_128_HMAC_SHA1_32)(.*?)(\r\n)/g,'');var inline=getChars()+'\r\n'+(extractedChars='');sdp=sdp.indexOf('a=crypto')==-1?sdp.replace(/c=IN/g,'a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:'+inline+'c=IN'):sdp;return sdp;}
function serializeSdp(sdp){return sdp;}
var channel;function openOffererChannel(){if(!options.onChannelMessage)return;_openOffererChannel();return;}
function _openOffererChannel(){channel=peer.createDataChannel(options.channel||'RTCDataChannel',{reliable:false});setChannelEvents();}
function setChannelEvents(){channel.onmessage=function(event){if(options.onChannelMessage)options.onChannelMessage(event);};channel.onopen=function(){if(options.onChannelOpened)options.onChannelOpened(channel);};channel.onclose=function(event){if(options.onChannelClosed)options.onChannelClosed(event);console.warn('WebRTC DataChannel closed',event);};channel.onerror=function(event){if(options.onChannelError)options.onChannelError(event);console.error('WebRTC DataChannel error',event);};}
function openAnswererChannel(){peer.ondatachannel=function(event){channel=event.channel;channel.binaryType='blob';setChannelEvents();};return;}
function useless(){log('Error in fake:true');}
function onSdpSuccess(){}
function onSdpError(e){if(options.onChannelError){options.onChannelError(e);}
console.error('sdp error:',e);}
return{addAnswerSDP:function(sdp,cbSuccess,cbError){peer.setRemoteDescription(new window.RTCSessionDescription(sdp),cbSuccess?cbSuccess:onSdpSuccess,cbError?cbError:onSdpError);},addICE:function(candidate){peer.addIceCandidate(new window.RTCIceCandidate({sdpMLineIndex:candidate.sdpMLineIndex,candidate:candidate.candidate}));},peer:peer,channel:channel,sendData:function(message){if(channel){channel.send(message);}},stop:function(){peer.close();if(options.attachStream){if(typeof options.attachStream.stop=='function'){options.attachStream.stop();}else{options.attachStream.active=false;}}}};}
var video_constraints={};function getUserMedia(options){var n=navigator,media;n.getMedia=n.getUserMedia;n.getMedia(options.constraints||{audio:true,video:video_constraints},streaming,options.onerror||function(e){console.error(e);});function streaming(stream){if(options.localVideo){options.localVideo['src']=window.URL.createObjectURL(stream);options.localVideo.style.display='block';}
if(options.onsuccess){options.onsuccess(stream);}
media=stream;}
return media;}
$.FSRTC.resSupported=function(w,h){for(var i in $.FSRTC.validRes){if($.FSRTC.validRes[i][0]==w&&$.FSRTC.validRes[i][1]==h){return true;}}
return false;}
$.FSRTC.bestResSupported=function(){var w=0,h=0;for(var i in $.FSRTC.validRes){if($.FSRTC.validRes[i][0]>=w&&$.FSRTC.validRes[i][1]>=h){w=$.FSRTC.validRes[i][0];h=$.FSRTC.validRes[i][1];}}
return[w,h];}
var resList=[[160,120],[320,180],[320,240],[640,360],[640,480],[1280,720],[1920,1080]];var resI=0;var ttl=0;var checkRes=function(cam,func){if(resI>=resList.length){var res={'validRes':$.FSRTC.validRes,'bestResSupported':$.FSRTC.bestResSupported()};localStorage.setItem("res_"+cam,$.toJSON(res));if(func)return func(res);return;}
var video={}
if(cam){video.deviceId={exact:cam};}
w=resList[resI][0];h=resList[resI][1];resI++;video={width:{exact:w},height:{exact:h}};getUserMedia({constraints:{audio:ttl++==0,video:video},onsuccess:function(e){e.getTracks().forEach(function(track){track.stop();});console.info(w+"x"+h+" supported.");$.FSRTC.validRes.push([w,h]);checkRes(cam,func);},onerror:function(e){console.warn(w+"x"+h+" not supported.");checkRes(cam,func);}});}
$.FSRTC.getValidRes=function(cam,func){var used=[];var cached=localStorage.getItem("res_"+cam);if(cached){var cache=$.parseJSON(cached);if(cache){$.FSRTC.validRes=cache.validRes;console.log("CACHED RES FOR CAM "+cam,cache);}else{console.error("INVALID CACHE");}
return func?func(cache):null;}
$.FSRTC.validRes=[];resI=0;checkRes(cam,func);}
$.FSRTC.checkPerms=function(runtime,check_audio,check_video){getUserMedia({constraints:{audio:check_audio,video:check_video,},onsuccess:function(e){e.getTracks().forEach(function(track){track.stop();});console.info("media perm init complete");if(runtime){setTimeout(runtime,100,true);}},onerror:function(e){if(check_video&&check_audio){console.error("error, retesting with audio params only");return $.FSRTC.checkPerms(runtime,check_audio,false);}
console.error("media perm init error");if(runtime){runtime(false)}}});}})(jQuery);(function($){$.JsonRpcClient=function(options){var self=this;this.options=$.extend({ajaxUrl:null,socketUrl:null,onmessage:null,login:null,passwd:null,sessid:null,loginParams:null,userVariables:null,getSocket:function(onmessage_cb){return self._getSocket(onmessage_cb);}},options);self.ws_cnt=0;this.wsOnMessage=function(event){self._wsOnMessage(event);};};$.JsonRpcClient.prototype._ws_socket=null;$.JsonRpcClient.prototype._ws_callbacks={};$.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");}};$.JsonRpcClient.prototype.call=function(method,params,success_cb,error_cb){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++};if(!success_cb){success_cb=function(e){console.log("Success: ",e);};}
if(!error_cb){error_cb=function(e){console.log("Error: ",e);};}
var socket=this.options.getSocket(this.wsOnMessage);if(socket!==null){this._wsCall(socket,request,success_cb,error_cb);return;}
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);},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){error_cb({error:jqXHR.responseText},this);}}});};$.JsonRpcClient.prototype.notify=function(method,params){if(this.options.sessid){params.sessid=this.options.sessid;}
var request={jsonrpc:'2.0',method:method,params:params};var socket=this.options.getSocket(this.wsOnMessage);if(socket!==null){this._wsCall(socket,request);return;}
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});};$.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();};$.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;}
self._ws_socket=new WebSocket(self.options.socketUrl);if(self._ws_socket){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;}};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;while((req=$.JsonRpcClient.q.pop())){self._ws_socket.send(req);}};}}
return self._ws_socket?true:false;};$.JsonRpcClient.prototype.stopRetrying=function(){if(self.to)
clearTimeout(self.to);}
$.JsonRpcClient.prototype._getSocket=function(onmessage_cb){if(this.options.socketUrl===null||!("WebSocket"in window))return null;this.connectSocket(onmessage_cb);return this._ws_socket;};$.JsonRpcClient.q=[];$.JsonRpcClient.prototype._wsCall=function(socket,request,success_cb,error_cb){var request_json=$.toJSON(request);if(socket.readyState<1){self=this;$.JsonRpcClient.q.push(request_json);}else{socket.send(request_json);}
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};}};$.JsonRpcClient.prototype._wsOnMessage=function(event){var response;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);if(typeof response==='object'&&'jsonrpc'in response&&response.jsonrpc==='2.0'){if('result'in response&&this._ws_callbacks[response.id]){var success_cb=this._ws_callbacks[response.id].success_cb;delete this._ws_callbacks[response.id];success_cb(response.result,this);return;}else if('error'in response&&this._ws_callbacks[response.id]){var error_cb=this._ws_callbacks[response.id].error_cb;var orig_req=this._ws_callbacks[response.id].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 this._ws_callbacks[response.id];error_cb(response.error,this);return;}}}catch(err){console.log("ERROR: "+err);return;}
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));}}}};$.JsonRpcClient._batchObject=function(jsonrpcclient,all_done_cb,error_cb){this._requests=[];this.jsonrpcclient=jsonrpcclient;this.all_done_cb=all_done_cb;this.error_cb=typeof error_cb==='function'?error_cb:function(){};};$.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++},success_cb:success_cb,error_cb:error_cb});};$.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}});};$.JsonRpcClient._batchObject.prototype._execute=function(){var self=this;if(this._requests.length===0)return;var batch_request=[];var handlers={};var i=0;var call;var success_cb;var error_cb;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('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);};if(self.jsonrpcclient.options.ajaxUrl===null){throw"$.JsonRpcClient.batch used with no websocket and no http endpoint.";}
$.ajax({url:self.jsonrpcclient.options.ajaxUrl,data:$.toJSON(batch_request),dataType:'json',cache:false,type:'POST',error:function(jqXHR,textStatus,errorThrown){self.error_cb(jqXHR,textStatus,errorThrown);},success:success_cb});};$.JsonRpcClient._batchObject.prototype._batchCb=function(result,handlers,all_done_cb){for(var i=0;i<result.length;i++){var response=result[i];if('error'in response){if(response.id===null||!(response.id in handlers)){if('console'in window)console.log(response);}else{handlers[response.id].error_cb(response.error,this);}}else{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);(function($){var sources=[];var generateGUID=(typeof(window.crypto)!=='undefined'&&typeof(window.crypto.getRandomValues)!=='undefined')?function(){var buf=new Uint16Array(8);window.crypto.getRandomValues(buf);var S4=function(num){var ret=num.toString(16);while(ret.length<4){ret="0"+ret;}
return ret;};return(S4(buf[0])+S4(buf[1])+"-"+S4(buf[2])+"-"+S4(buf[3])+"-"+S4(buf[4])+"-"+S4(buf[5])+S4(buf[6])+S4(buf[7]));}:function(){return'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g,function(c){var r=Math.random()*16|0,v=c=='x'?r:(r&0x3|0x8);return v.toString(16);});};$.verto=function(options,callbacks){var verto=this;$.verto.saved.push(verto);verto.options=$.extend({login:null,passwd:null,socketUrl:null,tag:null,localTag:null,videoParams:{},audioParams:{},loginParams:{},deviceParams:{onResCheck:null},userVariables:{},iceServers:false,ringSleep:6000,sessid:null},options);if(verto.options.deviceParams.useCamera){$.FSRTC.getValidRes(verto.options.deviceParams.useCamera,verto.options.deviceParams.onResCheck);}
if(!verto.options.deviceParams.useMic){verto.options.deviceParams.useMic="any";}
if(!verto.options.deviceParams.useSpeak){verto.options.deviceParams.useSpeak="any";}
if(verto.options.sessid){verto.sessid=verto.options.sessid;}else{verto.sessid=localStorage.getItem("verto_session_uuid")||generateGUID();localStorage.setItem("verto_session_uuid",verto.sessid);}
verto.dialogs={};verto.callbacks=callbacks||{};verto.eventSUBS={};verto.rpcClient=new $.JsonRpcClient({login:verto.options.login,passwd:verto.options.passwd,socketUrl:verto.options.socketUrl,loginParams:verto.options.loginParams,userVariables:verto.options.userVariables,sessid:verto.sessid,onmessage:function(e){return verto.handleMessage(e.eventData);},onWSConnect:function(o){o.call('login',{});},onWSLogin:function(success){if(verto.callbacks.onWSLogin){verto.callbacks.onWSLogin(verto,success);}},onWSClose:function(success){if(verto.callbacks.onWSClose){verto.callbacks.onWSClose(verto,success);}
verto.purge();}});var tag=verto.options.tag;if(typeof(tag)==="function"){tag=tag();}
if(verto.options.ringFile&&verto.options.tag){verto.ringer=$("#"+tag);}
verto.rpcClient.call('login',{});};$.verto.prototype.deviceParams=function(obj){var verto=this;for(var i in obj){verto.options.deviceParams[i]=obj[i];}
if(obj.useCamera){$.FSRTC.getValidRes(verto.options.deviceParams.useCamera,obj?obj.onResCheck:undefined);}};$.verto.prototype.videoParams=function(obj){var verto=this;for(var i in obj){verto.options.videoParams[i]=obj[i];}};$.verto.prototype.iceServers=function(obj){var verto=this;verto.options.iceServers=obj;};$.verto.prototype.loginData=function(params){var verto=this;verto.options.login=params.login;verto.options.passwd=params.passwd;verto.rpcClient.loginData(params);};$.verto.prototype.logout=function(msg){var verto=this;verto.rpcClient.closeSocket();if(verto.callbacks.onWSClose){verto.callbacks.onWSClose(verto,false);}
verto.purge();};$.verto.prototype.login=function(msg){var verto=this;verto.logout();verto.rpcClient.call('login',{});};$.verto.prototype.message=function(msg){var verto=this;var err=0;if(!msg.to){console.error("Missing To");err++;}
if(!msg.body){console.error("Missing Body");err++;}
if(err){return false;}
verto.sendMethod("verto.info",{msg:msg});return true;};$.verto.prototype.processReply=function(method,success,e){var verto=this;var i;switch(method){case"verto.subscribe":for(i in e.unauthorizedChannels){drop_bad(verto,e.unauthorizedChannels[i]);}
for(i in e.subscribedChannels){mark_ready(verto,e.subscribedChannels[i]);}
break;case"verto.unsubscribe":break;}};$.verto.prototype.sendMethod=function(method,params){var verto=this;verto.rpcClient.call(method,params,function(e){verto.processReply(method,true,e);},function(e){verto.processReply(method,false,e);});};function do_sub(verto,channel,obj){}
function drop_bad(verto,channel){console.error("drop unauthorized channel: "+channel);delete verto.eventSUBS[channel];}
function mark_ready(verto,channel){for(var j in verto.eventSUBS[channel]){verto.eventSUBS[channel][j].ready=true;console.log("subscribed to channel: "+channel);if(verto.eventSUBS[channel][j].readyHandler){verto.eventSUBS[channel][j].readyHandler(verto,channel);}}}
var SERNO=1;function do_subscribe(verto,channel,subChannels,sparams){var params=sparams||{};var local=params.local;var obj={eventChannel:channel,userData:params.userData,handler:params.handler,ready:false,readyHandler:params.readyHandler,serno:SERNO++};var isnew=false;if(!verto.eventSUBS[channel]){verto.eventSUBS[channel]=[];subChannels.push(channel);isnew=true;}
verto.eventSUBS[channel].push(obj);if(local){obj.ready=true;obj.local=true;}
if(!isnew&&verto.eventSUBS[channel][0].ready){obj.ready=true;if(obj.readyHandler){obj.readyHandler(verto,channel);}}
return{serno:obj.serno,eventChannel:channel};}
$.verto.prototype.subscribe=function(channel,sparams){var verto=this;var r=[];var subChannels=[];var params=sparams||{};if(typeof(channel)==="string"){r.push(do_subscribe(verto,channel,subChannels,params));}else{for(var i in channel){r.push(do_subscribe(verto,channel,subChannels,params));}}
if(subChannels.length){verto.sendMethod("verto.subscribe",{eventChannel:subChannels.length==1?subChannels[0]:subChannels,subParams:params.subParams});}
return r;};$.verto.prototype.unsubscribe=function(handle){var verto=this;var i;if(!handle){for(i in verto.eventSUBS){if(verto.eventSUBS[i]){verto.unsubscribe(verto.eventSUBS[i]);}}}else{var unsubChannels={};var sendChannels=[];var channel;if(typeof(handle)=="string"){delete verto.eventSUBS[handle];unsubChannels[handle]++;}else{for(i in handle){if(typeof(handle[i])=="string"){channel=handle[i];delete verto.eventSUBS[channel];unsubChannels[channel]++;}else{var repl=[];channel=handle[i].eventChannel;for(var j in verto.eventSUBS[channel]){if(verto.eventSUBS[channel][j].serno==handle[i].serno){}else{repl.push(verto.eventSUBS[channel][j]);}}
verto.eventSUBS[channel]=repl;if(verto.eventSUBS[channel].length===0){delete verto.eventSUBS[channel];unsubChannels[channel]++;}}}}
for(var u in unsubChannels){console.log("Sending Unsubscribe for: ",u);sendChannels.push(u);}
if(sendChannels.length){verto.sendMethod("verto.unsubscribe",{eventChannel:sendChannels.length==1?sendChannels[0]:sendChannels});}}};$.verto.prototype.broadcast=function(channel,params){var verto=this;var msg={eventChannel:channel,data:{}};for(var i in params){msg.data[i]=params[i];}
verto.sendMethod("verto.broadcast",msg);};$.verto.prototype.purge=function(callID){var verto=this;var x=0;var i;for(i in verto.dialogs){if(!x){console.log("purging dialogs");}
x++;verto.dialogs[i].setState($.verto.enum.state.purge);}
for(i in verto.eventSUBS){if(verto.eventSUBS[i]){console.log("purging subscription: "+i);delete verto.eventSUBS[i];}}};$.verto.prototype.hangup=function(callID){var verto=this;if(callID){var dialog=verto.dialogs[callID];if(dialog){dialog.hangup();}}else{for(var i in verto.dialogs){verto.dialogs[i].hangup();}}};$.verto.prototype.newCall=function(args,callbacks){var verto=this;if(!verto.rpcClient.socketReady()){console.error("Not Connected...");return;}
var dialog=new $.verto.dialog($.verto.enum.direction.outbound,this,args);dialog.invite();if(callbacks){dialog.callbacks=callbacks;}
return dialog;};$.verto.prototype.handleMessage=function(data){var verto=this;if(!(data&&data.method)){console.error("Invalid Data",data);return;}
if(data.params.callID){var dialog=verto.dialogs[data.params.callID];if(data.method==="verto.attach"&&dialog){delete dialog.verto.dialogs[dialog.callID];dialog.rtc.stop();dialog=null;}
if(dialog){switch(data.method){case'verto.bye':dialog.hangup(data.params);break;case'verto.answer':dialog.handleAnswer(data.params);break;case'verto.media':dialog.handleMedia(data.params);break;case'verto.display':dialog.handleDisplay(data.params);break;case'verto.info':dialog.handleInfo(data.params);break;default:console.debug("INVALID METHOD OR NON-EXISTANT CALL REFERENCE IGNORED",dialog,data.method);break;}}else{switch(data.method){case'verto.attach':data.params.attach=true;if(data.params.sdp&&data.params.sdp.indexOf("m=video")>0){data.params.useVideo=true;}
if(data.params.sdp&&data.params.sdp.indexOf("stereo=1")>0){data.params.useStereo=true;}
dialog=new $.verto.dialog($.verto.enum.direction.inbound,verto,data.params);dialog.setState($.verto.enum.state.recovering);break;case'verto.invite':if(data.params.sdp&&data.params.sdp.indexOf("m=video")>0){data.params.wantVideo=true;}
if(data.params.sdp&&data.params.sdp.indexOf("stereo=1")>0){data.params.useStereo=true;}
dialog=new $.verto.dialog($.verto.enum.direction.inbound,verto,data.params);break;default:console.debug("INVALID METHOD OR NON-EXISTANT CALL REFERENCE IGNORED");break;}}
return{method:data.method};}else{switch(data.method){case'verto.punt':verto.purge();verto.logout();break;case'verto.event':var list=null;var key=null;if(data.params){key=data.params.eventChannel;}
if(key){list=verto.eventSUBS[key];if(!list){list=verto.eventSUBS[key.split(".")[0]];}}
if(!list&&key&&key===verto.sessid){if(verto.callbacks.onMessage){verto.callbacks.onMessage(verto,null,$.verto.enum.message.pvtEvent,data.params);}}else if(!list&&key&&verto.dialogs[key]){verto.dialogs[key].sendMessage($.verto.enum.message.pvtEvent,data.params);}else if(!list){if(!key){key="UNDEFINED";}
console.error("UNSUBBED or invalid EVENT "+key+" IGNORED");}else{for(var i in list){var sub=list[i];if(!sub||!sub.ready){console.error("invalid EVENT for "+key+" IGNORED");}else if(sub.handler){sub.handler(verto,data.params,sub.userData);}else if(verto.callbacks.onEvent){verto.callbacks.onEvent(verto,data.params,sub.userData);}else{console.log("EVENT:",data.params);}}}
break;case"verto.info":if(verto.callbacks.onMessage){verto.callbacks.onMessage(verto,null,$.verto.enum.message.info,data.params.msg);}
console.debug("MESSAGE from: "+data.params.msg.from,data.params.msg.body);break;default:console.error("INVALID METHOD OR NON-EXISTANT CALL REFERENCE IGNORED",data.method);break;}}};var del_array=function(array,name){var r=[];var len=array.length;for(var i=0;i<len;i++){if(array[i]!=name){r.push(array[i]);}}
return r;};var hashArray=function(){var vha=this;var hash={};var array=[];vha.reorder=function(a){array=a;var h=hash;hash={};var len=array.length;for(var i=0;i<len;i++){var key=array[i];if(h[key]){hash[key]=h[key];delete h[key];}}
h=undefined;};vha.clear=function(){hash=undefined;array=undefined;hash={};array=[];};vha.add=function(name,val,insertAt){var redraw=false;if(!hash[name]){if(insertAt===undefined||insertAt<0||insertAt>=array.length){array.push(name);}else{var x=0;var n=[];var len=array.length;for(var i=0;i<len;i++){if(x++==insertAt){n.push(name);}
n.push(array[i]);}
array=undefined;array=n;n=undefined;redraw=true;}}
hash[name]=val;return redraw;};vha.del=function(name){var r=false;if(hash[name]){array=del_array(array,name);delete hash[name];r=true;}else{console.error("can't del nonexistant key "+name);}
return r;};vha.get=function(name){return hash[name];};vha.order=function(){return array;};vha.hash=function(){return hash;};vha.indexOf=function(name){var len=array.length;for(var i=0;i<len;i++){if(array[i]==name){return i;}}};vha.arrayLen=function(){return array.length;};vha.asArray=function(){var r=[];var len=array.length;for(var i=0;i<len;i++){var key=array[i];r.push(hash[key]);}
return r;};vha.each=function(cb){var len=array.length;for(var i=0;i<len;i++){cb(array[i],hash[array[i]]);}};vha.dump=function(html){var str="";vha.each(function(name,val){str+="name: "+name+" val: "+JSON.stringify(val)+(html?"<br>":"\n");});return str;};};$.verto.liveArray=function(verto,context,name,config){var la=this;var lastSerno=0;var binding=null;var user_obj=config.userObj;var local=false;hashArray.call(la);la._add=la.add;la._del=la.del;la._reorder=la.reorder;la._clear=la.clear;la.context=context;la.name=name;la.user_obj=user_obj;la.verto=verto;la.broadcast=function(channel,obj){verto.broadcast(channel,obj);};la.errs=0;la.clear=function(){la._clear();lastSerno=0;if(la.onChange){la.onChange(la,{action:"clear"});}};la.checkSerno=function(serno){if(serno<0){return true;}
if(lastSerno>0&&serno!=(lastSerno+1)){if(la.onErr){la.onErr(la,{lastSerno:lastSerno,serno:serno});}
la.errs++;console.debug(la.errs);if(la.errs<3){la.bootstrap(la.user_obj);}
return false;}else{lastSerno=serno;return true;}};la.reorder=function(serno,a){if(la.checkSerno(serno)){la._reorder(a);if(la.onChange){la.onChange(la,{serno:serno,action:"reorder"});}}};la.init=function(serno,val,key,index){if(key===null||key===undefined){key=serno;}
if(la.checkSerno(serno)){if(la.onChange){la.onChange(la,{serno:serno,action:"init",index:index,key:key,data:val});}}};la.bootObj=function(serno,val){if(la.checkSerno(serno)){for(var i in val){la._add(val[i][0],val[i][1]);}
if(la.onChange){la.onChange(la,{serno:serno,action:"bootObj",data:val,redraw:true});}}};la.add=function(serno,val,key,index){if(key===null||key===undefined){key=serno;}
if(la.checkSerno(serno)){var redraw=la._add(key,val,index);if(la.onChange){la.onChange(la,{serno:serno,action:"add",index:index,key:key,data:val,redraw:redraw});}}};la.modify=function(serno,val,key,index){if(key===null||key===undefined){key=serno;}
if(la.checkSerno(serno)){la._add(key,val,index);if(la.onChange){la.onChange(la,{serno:serno,action:"modify",key:key,data:val,index:index});}}};la.del=function(serno,key,index){if(key===null||key===undefined){key=serno;}
if(la.checkSerno(serno)){if(index===null||index<0||index===undefined){index=la.indexOf(key);}
var ok=la._del(key);if(ok&&la.onChange){la.onChange(la,{serno:serno,action:"del",key:key,index:index});}}};var eventHandler=function(v,e,la){var packet=e.data;if(packet.name!=la.name){return;}
switch(packet.action){case"init":la.init(packet.wireSerno,packet.data,packet.hashKey,packet.arrIndex);break;case"bootObj":la.bootObj(packet.wireSerno,packet.data);break;case"add":la.add(packet.wireSerno,packet.data,packet.hashKey,packet.arrIndex);break;case"modify":if(!(packet.arrIndex||packet.hashKey)){console.error("Invalid Packet",packet);}else{la.modify(packet.wireSerno,packet.data,packet.hashKey,packet.arrIndex);}
break;case"del":if(!(packet.arrIndex||packet.hashKey)){console.error("Invalid Packet",packet);}else{la.del(packet.wireSerno,packet.hashKey,packet.arrIndex);}
break;case"clear":la.clear();break;case"reorder":la.reorder(packet.wireSerno,packet.order);break;default:if(la.checkSerno(packet.wireSerno)){if(la.onChange){la.onChange(la,{serno:packet.wireSerno,action:packet.action,data:packet.data});}}
break;}};if(la.context){binding=la.verto.subscribe(la.context,{handler:eventHandler,userData:la,subParams:config.subParams});}
la.destroy=function(){la._clear();la.verto.unsubscribe(binding);};la.sendCommand=function(cmd,obj){var self=la;self.broadcast(self.context,{liveArray:{command:cmd,context:self.context,name:self.name,obj:obj}});};la.bootstrap=function(obj){var self=la;la.sendCommand("bootstrap",obj);};la.changepage=function(obj){var self=la;self.clear();self.broadcast(self.context,{liveArray:{command:"changepage",context:la.context,name:la.name,obj:obj}});};la.heartbeat=function(obj){var self=la;var callback=function(){self.heartbeat.call(self,obj);};self.broadcast(self.context,{liveArray:{command:"heartbeat",context:self.context,name:self.name,obj:obj}});self.hb_pid=setTimeout(callback,30000);};la.bootstrap(la.user_obj);};$.verto.liveTable=function(verto,context,name,jq,config){var dt;var la=new $.verto.liveArray(verto,context,name,{subParams:config.subParams});var lt=this;lt.liveArray=la;lt.dataTable=dt;lt.verto=verto;lt.destroy=function(){if(dt){dt.fnDestroy();}
if(la){la.destroy();}
dt=null;la=null;};la.onErr=function(obj,args){console.error("Error: ",obj,args);};function genRow(data){if(typeof(data[4])==="string"&&data[4].indexOf("{")>-1){var tmp=$.parseJSON(data[4]);data[4]=tmp.oldStatus;data[5]=null;}
return data;}
function genArray(obj){var data=obj.asArray();for(var i in data){data[i]=genRow(data[i]);}
return data;}
la.onChange=function(obj,args){var index=0;var iserr=0;if(!dt){if(!config.aoColumns){if(args.action!="init"){return;}
config.aoColumns=[];for(var i in args.data){config.aoColumns.push({"sTitle":args.data[i]});}}
dt=jq.dataTable(config);}
if(dt&&(args.action=="del"||args.action=="modify")){index=args.index;if(index===undefined&&args.key){index=la.indexOf(args.key);}
if(index===undefined){console.error("INVALID PACKET Missing INDEX\n",args);return;}}
if(config.onChange){config.onChange(obj,args);}
try{switch(args.action){case"bootObj":if(!args.data){console.error("missing data");return;}
dt.fnClearTable();dt.fnAddData(genArray(obj));dt.fnAdjustColumnSizing();break;case"add":if(!args.data){console.error("missing data");return;}
if(args.redraw>-1){dt.fnClearTable();dt.fnAddData(genArray(obj));}else{dt.fnAddData(genRow(args.data));}
dt.fnAdjustColumnSizing();break;case"modify":if(!args.data){return;}
dt.fnUpdate(genRow(args.data),index);dt.fnAdjustColumnSizing();break;case"del":dt.fnDeleteRow(index);dt.fnAdjustColumnSizing();break;case"clear":dt.fnClearTable();break;case"reorder":dt.fnClearTable();dt.fnAddData(genArray(obj));break;case"hide":jq.hide();break;case"show":jq.show();break;}}catch(err){console.error("ERROR: "+err);iserr++;}
if(iserr){obj.errs++;if(obj.errs<3){obj.bootstrap(obj.user_obj);}}else{obj.errs=0;}};la.onChange(la,{action:"init"});};var CONFMAN_SERNO=1;$.verto.conf=function(verto,params){var conf=this;conf.params=$.extend({dialog:null,hasVid:false,laData:null,onBroadcast:null,onLaChange:null,onLaRow:null},params);conf.verto=verto;conf.serno=CONFMAN_SERNO++;createMainModeratorMethods();verto.subscribe(conf.params.laData.modChannel,{handler:function(v,e){if(conf.params.onBroadcast){conf.params.onBroadcast(verto,conf,e.data);}}});verto.subscribe(conf.params.laData.infoChannel,{handler:function(v,e){if(typeof(conf.params.infoCallback)==="function"){conf.params.infoCallback(v,e);}}});verto.subscribe(conf.params.laData.chatChannel,{handler:function(v,e){if(typeof(conf.params.chatCallback)==="function"){conf.params.chatCallback(v,e);}}});};$.verto.conf.prototype.modCommand=function(cmd,id,value){var conf=this;conf.verto.rpcClient.call("verto.broadcast",{"eventChannel":conf.params.laData.modChannel,"data":{"application":"conf-control","command":cmd,"id":id,"value":value}});};$.verto.conf.prototype.destroy=function(){var conf=this;conf.destroyed=true;conf.params.onBroadcast(conf.verto,conf,'destroy');if(conf.params.laData.modChannel){conf.verto.unsubscribe(conf.params.laData.modChannel);}
if(conf.params.laData.chatChannel){conf.verto.unsubscribe(conf.params.laData.chatChannel);}
if(conf.params.laData.infoChannel){conf.verto.unsubscribe(conf.params.laData.infoChannel);}};function createMainModeratorMethods(){$.verto.conf.prototype.listVideoLayouts=function(){this.modCommand("list-videoLayouts",null,null);};$.verto.conf.prototype.play=function(file){this.modCommand("play",null,file);};$.verto.conf.prototype.stop=function(){this.modCommand("stop",null,"all");};$.verto.conf.prototype.deaf=function(memberID){this.modCommand("deaf",parseInt(memberID));};$.verto.conf.prototype.undeaf=function(memberID){this.modCommand("undeaf",parseInt(memberID));};$.verto.conf.prototype.record=function(file){this.modCommand("recording",null,["start",file]);};$.verto.conf.prototype.stopRecord=function(){this.modCommand("recording",null,["stop","all"]);};$.verto.conf.prototype.snapshot=function(file){if(!this.params.hasVid){throw'Conference has no video';}
this.modCommand("vid-write-png",null,file);};$.verto.conf.prototype.setVideoLayout=function(layout,canvasID){if(!this.params.hasVid){throw'Conference has no video';}
if(canvasID){this.modCommand("vid-layout",null,[layout,canvasID]);}else{this.modCommand("vid-layout",null,layout);}};$.verto.conf.prototype.kick=function(memberID){this.modCommand("kick",parseInt(memberID));};$.verto.conf.prototype.muteMic=function(memberID){this.modCommand("tmute",parseInt(memberID));};$.verto.conf.prototype.muteVideo=function(memberID){if(!this.params.hasVid){throw'Conference has no video';}
this.modCommand("tvmute",parseInt(memberID));};$.verto.conf.prototype.presenter=function(memberID){if(!this.params.hasVid){throw'Conference has no video';}
this.modCommand("vid-res-id",parseInt(memberID),"presenter");};$.verto.conf.prototype.videoFloor=function(memberID){if(!this.params.hasVid){throw'Conference has no video';}
this.modCommand("vid-floor",parseInt(memberID),"force");};$.verto.conf.prototype.banner=function(memberID,text){if(!this.params.hasVid){throw'Conference has no video';}
this.modCommand("vid-banner",parseInt(memberID),escape(text));};$.verto.conf.prototype.volumeDown=function(memberID){this.modCommand("volume_out",parseInt(memberID),"down");};$.verto.conf.prototype.volumeUp=function(memberID){this.modCommand("volume_out",parseInt(memberID),"up");};$.verto.conf.prototype.gainDown=function(memberID){this.modCommand("volume_in",parseInt(memberID),"down");};$.verto.conf.prototype.gainUp=function(memberID){this.modCommand("volume_in",parseInt(memberID),"up");};$.verto.conf.prototype.transfer=function(memberID,exten){this.modCommand("transfer",parseInt(memberID),exten);};$.verto.conf.prototype.sendChat=function(message,type){var conf=this;conf.verto.rpcClient.call("verto.broadcast",{"eventChannel":conf.params.laData.chatChannel,"data":{"action":"send","message":message,"type":type}});};}
$.verto.modfuncs={};$.verto.confMan=function(verto,params){var confMan=this;confMan.params=$.extend({tableID:null,statusID:null,mainModID:null,dialog:null,hasVid:false,laData:null,onBroadcast:null,onLaChange:null,onLaRow:null},params);confMan.verto=verto;confMan.serno=CONFMAN_SERNO++;confMan.canvasCount=confMan.params.laData.canvasCount;function genMainMod(jq){var play_id="play_"+confMan.serno;var stop_id="stop_"+confMan.serno;var recording_id="recording_"+confMan.serno;var snapshot_id="snapshot_"+confMan.serno;var rec_stop_id="recording_stop"+confMan.serno;var div_id="confman_"+confMan.serno;var html="<div id='"+div_id+"'><br>"+"<button class='ctlbtn' id='"+play_id+"'>Play</button>"+"<button class='ctlbtn' id='"+stop_id+"'>Stop</button>"+"<button class='ctlbtn' id='"+recording_id+"'>Record</button>"+"<button class='ctlbtn' id='"+rec_stop_id+"'>Record Stop</button>"+
(confMan.params.hasVid?"<button class='ctlbtn' id='"+snapshot_id+"'>PNG Snapshot</button>":"")+"<br><br></div>";jq.html(html);$.verto.modfuncs.change_video_layout=function(id,canvas_id){var val=$("#"+id+" option:selected").text();if(val!=="none"){confMan.modCommand("vid-layout",null,[val,canvas_id]);}};if(confMan.params.hasVid){for(var j=0;j<confMan.canvasCount;j++){var vlayout_id="confman_vid_layout_"+j+"_"+confMan.serno;var vlselect_id="confman_vl_select_"+j+"_"+confMan.serno;var vlhtml="<div id='"+vlayout_id+"'><br>"+"<b>Video Layout Canvas "+(j+1)+"</b> <select onChange='$.verto.modfuncs.change_video_layout(\""+vlayout_id+"\", \""+(j+1)+"\")' id='"+vlselect_id+"'></select> "+"<br><br></div>";jq.append(vlhtml);}
$("#"+snapshot_id).click(function(){var file=prompt("Please enter file name","");if(file){confMan.modCommand("vid-write-png",null,file);}});}
$("#"+play_id).click(function(){var file=prompt("Please enter file name","");if(file){confMan.modCommand("play",null,file);}});$("#"+stop_id).click(function(){confMan.modCommand("stop",null,"all");});$("#"+recording_id).click(function(){var file=prompt("Please enter file name","");if(file){confMan.modCommand("recording",null,["start",file]);}});$("#"+rec_stop_id).click(function(){confMan.modCommand("recording",null,["stop","all"]);});}
function genControls(jq,rowid){var x=parseInt(rowid);var kick_id="kick_"+x;var canvas_in_next_id="canvas_in_next_"+x;var canvas_in_prev_id="canvas_in_prev_"+x;var canvas_out_next_id="canvas_out_next_"+x;var canvas_out_prev_id="canvas_out_prev_"+x;var canvas_in_set_id="canvas_in_set_"+x;var canvas_out_set_id="canvas_out_set_"+x;var layer_set_id="layer_set_"+x;var layer_next_id="layer_next_"+x;var layer_prev_id="layer_prev_"+x;var tmute_id="tmute_"+x;var tvmute_id="tvmute_"+x;var vbanner_id="vbanner_"+x;var tvpresenter_id="tvpresenter_"+x;var tvfloor_id="tvfloor_"+x;var box_id="box_"+x;var gainup_id="gain_in_up"+x;var gaindn_id="gain_in_dn"+x;var volup_id="vol_in_up"+x;var voldn_id="vol_in_dn"+x;var transfer_id="transfer"+x;var html="<div id='"+box_id+"'>";html+="<b>General Controls</b><hr noshade>";html+="<button class='ctlbtn' id='"+kick_id+"'>Kick</button>"+"<button class='ctlbtn' id='"+tmute_id+"'>Mute</button>"+"<button class='ctlbtn' id='"+gainup_id+"'>Gain -</button>"+"<button class='ctlbtn' id='"+gaindn_id+"'>Gain +</button>"+"<button class='ctlbtn' id='"+voldn_id+"'>Vol -</button>"+"<button class='ctlbtn' id='"+volup_id+"'>Vol +</button>"+"<button class='ctlbtn' id='"+transfer_id+"'>Transfer</button>";if(confMan.params.hasVid){html+="<br><br><b>Video Controls</b><hr noshade>";html+="<button class='ctlbtn' id='"+tvmute_id+"'>VMute</button>"+"<button class='ctlbtn' id='"+tvpresenter_id+"'>Presenter</button>"+"<button class='ctlbtn' id='"+tvfloor_id+"'>Vid Floor</button>"+"<button class='ctlbtn' id='"+vbanner_id+"'>Banner</button>";if(confMan.canvasCount>1){html+="<br><br><b>Canvas Controls</b><hr noshade>"+"<button class='ctlbtn' id='"+canvas_in_set_id+"'>Set Input Canvas</button>"+"<button class='ctlbtn' id='"+canvas_in_prev_id+"'>Prev Input Canvas</button>"+"<button class='ctlbtn' id='"+canvas_in_next_id+"'>Next Input Canvas</button>"+"<br>"+"<button class='ctlbtn' id='"+canvas_out_set_id+"'>Set Watching Canvas</button>"+"<button class='ctlbtn' id='"+canvas_out_prev_id+"'>Prev Watching Canvas</button>"+"<button class='ctlbtn' id='"+canvas_out_next_id+"'>Next Watching Canvas</button>";}
html+="<br>"+"<button class='ctlbtn' id='"+layer_set_id+"'>Set Layer</button>"+"<button class='ctlbtn' id='"+layer_prev_id+"'>Prev Layer</button>"+"<button class='ctlbtn' id='"+layer_next_id+"'>Next Layer</button>"+"</div>";}
jq.html(html);if(!jq.data("mouse")){$("#"+box_id).hide();}
jq.mouseover(function(e){jq.data({"mouse":true});$("#"+box_id).show();});jq.mouseout(function(e){jq.data({"mouse":false});$("#"+box_id).hide();});$("#"+transfer_id).click(function(){var xten=prompt("Enter Extension");if(xten){confMan.modCommand("transfer",x,xten);}});$("#"+kick_id).click(function(){confMan.modCommand("kick",x);});$("#"+layer_set_id).click(function(){var cid=prompt("Please enter layer ID","");if(cid){confMan.modCommand("vid-layer",x,cid);}});$("#"+layer_next_id).click(function(){confMan.modCommand("vid-layer",x,"next");});$("#"+layer_prev_id).click(function(){confMan.modCommand("vid-layer",x,"prev");});$("#"+canvas_in_set_id).click(function(){var cid=prompt("Please enter canvas ID","");if(cid){confMan.modCommand("vid-canvas",x,cid);}});$("#"+canvas_out_set_id).click(function(){var cid=prompt("Please enter canvas ID","");if(cid){confMan.modCommand("vid-watching-canvas",x,cid);}});$("#"+canvas_in_next_id).click(function(){confMan.modCommand("vid-canvas",x,"next");});$("#"+canvas_in_prev_id).click(function(){confMan.modCommand("vid-canvas",x,"prev");});$("#"+canvas_out_next_id).click(function(){confMan.modCommand("vid-watching-canvas",x,"next");});$("#"+canvas_out_prev_id).click(function(){confMan.modCommand("vid-watching-canvas",x,"prev");});$("#"+tmute_id).click(function(){confMan.modCommand("tmute",x);});if(confMan.params.hasVid){$("#"+tvmute_id).click(function(){confMan.modCommand("tvmute",x);});$("#"+tvpresenter_id).click(function(){confMan.modCommand("vid-res-id",x,"presenter");});$("#"+tvfloor_id).click(function(){confMan.modCommand("vid-floor",x,"force");});$("#"+vbanner_id).click(function(){var text=prompt("Please enter text","");if(text){confMan.modCommand("vid-banner",x,escape(text));}});}
$("#"+gainup_id).click(function(){confMan.modCommand("volume_in",x,"up");});$("#"+gaindn_id).click(function(){confMan.modCommand("volume_in",x,"down");});$("#"+volup_id).click(function(){confMan.modCommand("volume_out",x,"up");});$("#"+voldn_id).click(function(){confMan.modCommand("volume_out",x,"down");});return html;}
var atitle="";var awidth=0;verto.subscribe(confMan.params.laData.infoChannel,{handler:function(v,e){if(typeof(confMan.params.infoCallback)==="function"){confMan.params.infoCallback(v,e);}}});verto.subscribe(confMan.params.laData.chatChannel,{handler:function(v,e){if(typeof(confMan.params.chatCallback)==="function"){confMan.params.chatCallback(v,e);}}});if(confMan.params.laData.role==="moderator"){atitle="Action";awidth=600;if(confMan.params.mainModID){genMainMod($(confMan.params.mainModID));$(confMan.params.displayID).html("Moderator Controls Ready<br><br>");}else{$(confMan.params.mainModID).html("");}
verto.subscribe(confMan.params.laData.modChannel,{handler:function(v,e){if(confMan.params.onBroadcast){confMan.params.onBroadcast(verto,confMan,e.data);}
if(e.data["conf-command"]==="list-videoLayouts"){for(var j=0;j<confMan.canvasCount;j++){var vlselect_id="#confman_vl_select_"+j+"_"+confMan.serno;var vlayout_id="#confman_vid_layout_"+j+"_"+confMan.serno;var x=0;var options;$(vlselect_id).selectmenu({});$(vlselect_id).selectmenu("enable");$(vlselect_id).empty();$(vlselect_id).append(new Option("Choose a Layout","none"));if(e.data.responseData){var rdata=[];for(var i in e.data.responseData){rdata.push(e.data.responseData[i].name);}
options=rdata.sort(function(a,b){var ga=a.substring(0,6)=="group:"?true:false;var gb=b.substring(0,6)=="group:"?true:false;if((ga||gb)&&ga!=gb){return ga?-1:1;}
return((a==b)?0:((a>b)?1:-1));});for(var i in options){$(vlselect_id).append(new Option(options[i],options[i]));x++;}}
if(x){$(vlselect_id).selectmenu('refresh',true);}else{$(vlayout_id).hide();}}}else{if(!confMan.destroyed&&confMan.params.displayID){$(confMan.params.displayID).html(e.data.response+"<br><br>");if(confMan.lastTimeout){clearTimeout(confMan.lastTimeout);confMan.lastTimeout=0;}
confMan.lastTimeout=setTimeout(function(){$(confMan.params.displayID).html(confMan.destroyed?"":"Moderator Controls Ready<br><br>");},4000);}}}});if(confMan.params.hasVid){confMan.modCommand("list-videoLayouts",null,null);}}
var row_callback=null;if(confMan.params.laData.role==="moderator"){row_callback=function(nRow,aData,iDisplayIndex,iDisplayIndexFull){if(!aData[5]){var $row=$('td:eq(5)',nRow);genControls($row,aData);if(confMan.params.onLaRow){confMan.params.onLaRow(verto,confMan,$row,aData);}}};}
confMan.lt=new $.verto.liveTable(verto,confMan.params.laData.laChannel,confMan.params.laData.laName,$(confMan.params.tableID),{subParams:{callID:confMan.params.dialog?confMan.params.dialog.callID:null},"onChange":function(obj,args){$(confMan.params.statusID).text("Conference Members: "+" ("+obj.arrayLen()+" Total)");if(confMan.params.onLaChange){confMan.params.onLaChange(verto,confMan,$.verto.enum.confEvent.laChange,obj,args);}},"aaData":[],"aoColumns":[{"sTitle":"ID","sWidth":"50"},{"sTitle":"Number","sWidth":"250"},{"sTitle":"Name","sWidth":"250"},{"sTitle":"Codec","sWidth":"100"},{"sTitle":"Status","sWidth":confMan.params.hasVid?"200px":"150px"},{"sTitle":atitle,"sWidth":awidth,}],"bAutoWidth":true,"bDestroy":true,"bSort":false,"bInfo":false,"bFilter":false,"bLengthChange":false,"bPaginate":false,"iDisplayLength":1400,"oLanguage":{"sEmptyTable":"The Conference is Empty....."},"fnRowCallback":row_callback});};$.verto.confMan.prototype.modCommand=function(cmd,id,value){var confMan=this;confMan.verto.rpcClient.call("verto.broadcast",{"eventChannel":confMan.params.laData.modChannel,"data":{"application":"conf-control","command":cmd,"id":id,"value":value}});};$.verto.confMan.prototype.sendChat=function(message,type){var confMan=this;confMan.verto.rpcClient.call("verto.broadcast",{"eventChannel":confMan.params.laData.chatChannel,"data":{"action":"send","message":message,"type":type}});};$.verto.confMan.prototype.destroy=function(){var confMan=this;confMan.destroyed=true;if(confMan.lt){confMan.lt.destroy();}
if(confMan.params.laData.chatChannel){confMan.verto.unsubscribe(confMan.params.laData.chatChannel);}
if(confMan.params.laData.modChannel){confMan.verto.unsubscribe(confMan.params.laData.modChannel);}
if(confMan.params.mainModID){$(confMan.params.mainModID).html("");}};$.verto.dialog=function(direction,verto,params){var dialog=this;dialog.params=$.extend({useVideo:verto.options.useVideo,useStereo:verto.options.useStereo,screenShare:false,useCamera:false,useMic:verto.options.deviceParams.useMic,useSpeak:verto.options.deviceParams.useSpeak,tag:verto.options.tag,localTag:verto.options.localTag,login:verto.options.login,videoParams:verto.options.videoParams},params);if(!dialog.params.screenShare){dialog.params.useCamera=verto.options.deviceParams.useCamera;}
dialog.verto=verto;dialog.direction=direction;dialog.lastState=null;dialog.state=dialog.lastState=$.verto.enum.state.new;dialog.callbacks=verto.callbacks;dialog.answered=false;dialog.attach=params.attach||false;dialog.screenShare=params.screenShare||false;dialog.useCamera=dialog.params.useCamera;dialog.useMic=dialog.params.useMic;dialog.useSpeak=dialog.params.useSpeak;if(dialog.params.callID){dialog.callID=dialog.params.callID;}else{dialog.callID=dialog.params.callID=generateGUID();}
if(typeof(dialog.params.tag)==="function"){dialog.params.tag=dialog.params.tag();}
if(dialog.params.tag){dialog.audioStream=document.getElementById(dialog.params.tag);if(dialog.params.useVideo){dialog.videoStream=dialog.audioStream;}}
if(dialog.params.localTag){dialog.localVideo=document.getElementById(dialog.params.localTag);}
dialog.verto.dialogs[dialog.callID]=dialog;var RTCcallbacks={};if(dialog.direction==$.verto.enum.direction.inbound){if(dialog.params.display_direction==="outbound"){dialog.params.remote_caller_id_name=dialog.params.caller_id_name;dialog.params.remote_caller_id_number=dialog.params.caller_id_number;}else{dialog.params.remote_caller_id_name=dialog.params.callee_id_name;dialog.params.remote_caller_id_number=dialog.params.callee_id_number;}
if(!dialog.params.remote_caller_id_name){dialog.params.remote_caller_id_name="Nobody";}
if(!dialog.params.remote_caller_id_number){dialog.params.remote_caller_id_number="UNKNOWN";}
RTCcallbacks.onMessage=function(rtc,msg){console.debug(msg);};RTCcallbacks.onAnswerSDP=function(rtc,sdp){console.error("answer sdp",sdp);};}else{dialog.params.remote_caller_id_name="Outbound Call";dialog.params.remote_caller_id_number=dialog.params.destination_number;}
RTCcallbacks.onICESDP=function(rtc){console.log("RECV "+rtc.type+" SDP",rtc.mediaData.SDP);if(dialog.state==$.verto.enum.state.requesting||dialog.state==$.verto.enum.state.answering||dialog.state==$.verto.enum.state.active){location.reload();return;}
if(rtc.type=="offer"){if(dialog.state==$.verto.enum.state.active){dialog.setState($.verto.enum.state.requesting);dialog.sendMethod("verto.attach",{sdp:rtc.mediaData.SDP});}else{dialog.setState($.verto.enum.state.requesting);dialog.sendMethod("verto.invite",{sdp:rtc.mediaData.SDP});}}else{dialog.setState($.verto.enum.state.answering);dialog.sendMethod(dialog.attach?"verto.attach":"verto.answer",{sdp:dialog.rtc.mediaData.SDP});}};RTCcallbacks.onICE=function(rtc){if(rtc.type=="offer"){console.log("offer",rtc.mediaData.candidate);return;}};RTCcallbacks.onStream=function(rtc,stream){if(dialog.verto.options.permissionCallback&&typeof dialog.verto.options.permissionCallback.onGranted==='function'){dialog.verto.options.permissionCallback.onGranted(stream);}
console.log("stream started");};RTCcallbacks.onError=function(e){if(dialog.verto.options.permissionCallback&&typeof dialog.verto.options.permissionCallback.onDenied==='function'){dialog.verto.options.permissionCallback.onDenied();}
console.error("ERROR:",e);dialog.hangup({cause:"Device or Permission Error"});};dialog.rtc=new $.FSRTC({callbacks:RTCcallbacks,localVideo:dialog.screenShare?null:dialog.localVideo,useVideo:dialog.params.useVideo?dialog.videoStream:null,useAudio:dialog.audioStream,useStereo:dialog.params.useStereo,videoParams:dialog.params.videoParams,audioParams:verto.options.audioParams,iceServers:verto.options.iceServers,screenShare:dialog.screenShare,useCamera:dialog.useCamera,useMic:dialog.useMic,useSpeak:dialog.useSpeak});dialog.rtc.verto=dialog.verto;if(dialog.direction==$.verto.enum.direction.inbound){if(dialog.attach){dialog.answer();}else{dialog.ring();}}};$.verto.dialog.prototype.invite=function(){var dialog=this;dialog.rtc.call();};$.verto.dialog.prototype.sendMethod=function(method,obj){var dialog=this;obj.dialogParams={};for(var i in dialog.params){if(i=="sdp"&&method!="verto.invite"&&method!="verto.attach"){continue;}
if((obj.noDialogParams&&i!="callID")){continue;}
obj.dialogParams[i]=dialog.params[i];}
delete obj.noDialogParams;dialog.verto.rpcClient.call(method,obj,function(e){dialog.processReply(method,true,e);},function(e){dialog.processReply(method,false,e);});};function checkStateChange(oldS,newS){if(newS==$.verto.enum.state.purge||$.verto.enum.states[oldS.name][newS.name]){return true;}
return false;}
function find_name(id){for(var i in $.verto.audioOutDevices){var source=$.verto.audioOutDevices[i];if(source.id===id){return(source.label);}}
return id;}
$.verto.dialog.prototype.setAudioPlaybackDevice=function(sinkId,callback,arg){var dialog=this;var element=dialog.audioStream;if(typeof element.sinkId!=='undefined'){var devname=find_name(sinkId);console.info("Dialog: "+dialog.callID+" Setting speaker:",element,devname);element.setSinkId(sinkId).then(function(){console.log("Dialog: "+dialog.callID+' Success, audio output device attached: '+sinkId);if(callback){callback(true,devname,arg);}}).catch(function(error){var errorMessage=error;if(error.name==='SecurityError'){errorMessage="Dialog: "+dialog.callID+' You need to use HTTPS for selecting audio output '+'device: '+error;}
if(callback){callback(false,null,arg);}
console.error(errorMessage);});}else{console.warn("Dialog: "+dialog.callID+' Browser does not support output device selection.');if(callback){callback(false,null,arg);}}}
$.verto.dialog.prototype.setState=function(state){var dialog=this;if(dialog.state==$.verto.enum.state.ringing){dialog.stopRinging();}
if(dialog.state==state||!checkStateChange(dialog.state,state)){console.error("Dialog "+dialog.callID+": INVALID state change from "+dialog.state.name+" to "+state.name);dialog.hangup();return false;}
console.log("Dialog "+dialog.callID+": state change from "+dialog.state.name+" to "+state.name);dialog.lastState=dialog.state;dialog.state=state;if(!dialog.causeCode){dialog.causeCode=16;}
if(!dialog.cause){dialog.cause="NORMAL CLEARING";}
if(dialog.callbacks.onDialogState){dialog.callbacks.onDialogState(this);}
switch(dialog.state){case $.verto.enum.state.early:case $.verto.enum.state.active:var speaker=dialog.useSpeak;console.info("Using Speaker: ",speaker);if(speaker&&speaker!=="any"&&speaker!=="none"){setTimeout(function(){dialog.setAudioPlaybackDevice(speaker);},500);}
break;case $.verto.enum.state.trying:setTimeout(function(){if(dialog.state==$.verto.enum.state.trying){dialog.setState($.verto.enum.state.hangup);}},30000);break;case $.verto.enum.state.purge:dialog.setState($.verto.enum.state.destroy);break;case $.verto.enum.state.hangup:if(dialog.lastState.val>$.verto.enum.state.requesting.val&&dialog.lastState.val<$.verto.enum.state.hangup.val){dialog.sendMethod("verto.bye",{});}
dialog.setState($.verto.enum.state.destroy);break;case $.verto.enum.state.destroy:if(typeof(dialog.verto.options.tag)==="function"){$('#'+dialog.params.tag).remove();}
delete dialog.verto.dialogs[dialog.callID];if(dialog.params.screenShare){dialog.rtc.stopPeer();}else{dialog.rtc.stop();}
break;}
return true;};$.verto.dialog.prototype.processReply=function(method,success,e){var dialog=this;switch(method){case"verto.answer":case"verto.attach":if(success){dialog.setState($.verto.enum.state.active);}else{dialog.hangup();}
break;case"verto.invite":if(success){dialog.setState($.verto.enum.state.trying);}else{dialog.setState($.verto.enum.state.destroy);}
break;case"verto.bye":dialog.hangup();break;case"verto.modify":if(e.holdState){if(e.holdState=="held"){if(dialog.state!=$.verto.enum.state.held){dialog.setState($.verto.enum.state.held);}}else if(e.holdState=="active"){if(dialog.state!=$.verto.enum.state.active){dialog.setState($.verto.enum.state.active);}}}
if(success){}
break;default:break;}};$.verto.dialog.prototype.hangup=function(params){var dialog=this;if(params){if(params.causeCode){dialog.causeCode=params.causeCode;}
if(params.cause){dialog.cause=params.cause;}}
if(dialog.state.val>=$.verto.enum.state.new.val&&dialog.state.val<$.verto.enum.state.hangup.val){dialog.setState($.verto.enum.state.hangup);}else if(dialog.state.val<$.verto.enum.state.destroy){dialog.setState($.verto.enum.state.destroy);}};$.verto.dialog.prototype.stopRinging=function(){var dialog=this;if(dialog.verto.ringer){dialog.verto.ringer.stop();}};$.verto.dialog.prototype.indicateRing=function(){var dialog=this;if(dialog.verto.ringer){dialog.verto.ringer.attr("src",dialog.verto.options.ringFile)[0].play();setTimeout(function(){dialog.stopRinging();if(dialog.state==$.verto.enum.state.ringing){dialog.indicateRing();}},dialog.verto.options.ringSleep);}};$.verto.dialog.prototype.ring=function(){var dialog=this;dialog.setState($.verto.enum.state.ringing);dialog.indicateRing();};$.verto.dialog.prototype.useVideo=function(on){var dialog=this;dialog.params.useVideo=on;if(on){dialog.videoStream=dialog.audioStream;}else{dialog.videoStream=null;}
dialog.rtc.useVideo(dialog.videoStream,dialog.localVideo);};$.verto.dialog.prototype.setMute=function(what){var dialog=this;return dialog.rtc.setMute(what);};$.verto.dialog.prototype.getMute=function(){var dialog=this;return dialog.rtc.getMute();};$.verto.dialog.prototype.setVideoMute=function(what){var dialog=this;return dialog.rtc.setVideoMute(what);};$.verto.dialog.prototype.getVideoMute=function(){var dialog=this;return dialog.rtc.getVideoMute();};$.verto.dialog.prototype.useStereo=function(on){var dialog=this;dialog.params.useStereo=on;dialog.rtc.useStereo(on);};$.verto.dialog.prototype.dtmf=function(digits){var dialog=this;if(digits){dialog.sendMethod("verto.info",{dtmf:digits});}};$.verto.dialog.prototype.rtt=function(obj){var dialog=this;var pobj={};if(!obj){return false;}
pobj.code=obj.code;pobj.chars=obj.chars;if(pobj.chars||pobj.code){dialog.sendMethod("verto.info",{txt:obj,noDialogParams:true});}};$.verto.dialog.prototype.transfer=function(dest,params){var dialog=this;if(dest){dialog.sendMethod("verto.modify",{action:"transfer",destination:dest,params:params});}};$.verto.dialog.prototype.hold=function(params){var dialog=this;dialog.sendMethod("verto.modify",{action:"hold",params:params});};$.verto.dialog.prototype.unhold=function(params){var dialog=this;dialog.sendMethod("verto.modify",{action:"unhold",params:params});};$.verto.dialog.prototype.toggleHold=function(params){var dialog=this;dialog.sendMethod("verto.modify",{action:"toggleHold",params:params});};$.verto.dialog.prototype.message=function(msg){var dialog=this;var err=0;msg.from=dialog.params.login;if(!msg.to){console.error("Missing To");err++;}
if(!msg.body){console.error("Missing Body");err++;}
if(err){return false;}
dialog.sendMethod("verto.info",{msg:msg});return true;};$.verto.dialog.prototype.answer=function(params){var dialog=this;if(!dialog.answered){if(!params){params={};}
params.sdp=dialog.params.sdp;if(params){if(params.useVideo){dialog.useVideo(true);}
dialog.params.callee_id_name=params.callee_id_name;dialog.params.callee_id_number=params.callee_id_number;if(params.useCamera){dialog.useCamera=params.useCamera;}
if(params.useMic){dialog.useMic=params.useMic;}
if(params.useSpeak){dialog.useSpeak=params.useSpeak;}}
dialog.rtc.createAnswer(params);dialog.answered=true;}};$.verto.dialog.prototype.handleAnswer=function(params){var dialog=this;dialog.gotAnswer=true;if(dialog.state.val>=$.verto.enum.state.active.val){return;}
if(dialog.state.val>=$.verto.enum.state.early.val){dialog.setState($.verto.enum.state.active);}else{if(dialog.gotEarly){console.log("Dialog "+dialog.callID+" Got answer while still establishing early media, delaying...");}else{console.log("Dialog "+dialog.callID+" Answering Channel");dialog.rtc.answer(params.sdp,function(){dialog.setState($.verto.enum.state.active);},function(e){console.error(e);dialog.hangup();});console.log("Dialog "+dialog.callID+"ANSWER SDP",params.sdp);}}};$.verto.dialog.prototype.cidString=function(enc){var dialog=this;var party=dialog.params.remote_caller_id_name+(enc?" &lt;":" <")+dialog.params.remote_caller_id_number+(enc?"&gt;":">");return party;};$.verto.dialog.prototype.sendMessage=function(msg,params){var dialog=this;if(dialog.callbacks.onMessage){dialog.callbacks.onMessage(dialog.verto,dialog,msg,params);}};$.verto.dialog.prototype.handleInfo=function(params){var dialog=this;dialog.sendMessage($.verto.enum.message.info,params);};$.verto.dialog.prototype.handleDisplay=function(params){var dialog=this;if(params.display_name){dialog.params.remote_caller_id_name=params.display_name;}
if(params.display_number){dialog.params.remote_caller_id_number=params.display_number;}
dialog.sendMessage($.verto.enum.message.display,{});};$.verto.dialog.prototype.handleMedia=function(params){var dialog=this;if(dialog.state.val>=$.verto.enum.state.early.val){return;}
dialog.gotEarly=true;dialog.rtc.answer(params.sdp,function(){console.log("Dialog "+dialog.callID+"Establishing early media");dialog.setState($.verto.enum.state.early);if(dialog.gotAnswer){console.log("Dialog "+dialog.callID+"Answering Channel");dialog.setState($.verto.enum.state.active);}},function(e){console.error(e);dialog.hangup();});console.log("Dialog "+dialog.callID+"EARLY SDP",params.sdp);};$.verto.ENUM=function(s){var i=0,o={};s.split(" ").map(function(x){o[x]={name:x,val:i++};});return Object.freeze(o);};$.verto.enum={};$.verto.enum.states=Object.freeze({new:{requesting:1,recovering:1,ringing:1,destroy:1,answering:1,hangup:1},requesting:{trying:1,hangup:1,active:1},recovering:{answering:1,hangup:1},trying:{active:1,early:1,hangup:1},ringing:{answering:1,hangup:1},answering:{active:1,hangup:1},active:{answering:1,requesting:1,hangup:1,held:1},held:{hangup:1,active:1},early:{hangup:1,active:1},hangup:{destroy:1},destroy:{},purge:{destroy:1}});$.verto.enum.state=$.verto.ENUM("new requesting trying recovering ringing answering early active held hangup destroy purge");$.verto.enum.direction=$.verto.ENUM("inbound outbound");$.verto.enum.message=$.verto.ENUM("display info pvtEvent");$.verto.enum=Object.freeze($.verto.enum);$.verto.saved=[];$.verto.unloadJobs=[];$(window).bind('beforeunload',function(){for(var f in $.verto.unloadJobs){$.verto.unloadJobs[f]();}
if($.verto.haltClosure)
return $.verto.haltClosure();for(var i in $.verto.saved){var verto=$.verto.saved[i];if(verto){verto.purge();verto.logout();}}
return $.verto.warnOnUnload;});$.verto.videoDevices=[];$.verto.audioInDevices=[];$.verto.audioOutDevices=[];var checkDevices=function(runtime){console.info("enumerating devices");var aud_in=[],aud_out=[],vid=[];var has_video=0,has_audio=0;var Xstream;function gotDevices(deviceInfos){for(var i=0;i!==deviceInfos.length;++i){var deviceInfo=deviceInfos[i];var text="";console.log(deviceInfo);console.log(deviceInfo.kind+": "+deviceInfo.label+" id = "+deviceInfo.deviceId);if(deviceInfo.kind==='audioinput'){text=deviceInfo.label||'microphone '+(aud_in.length+1);aud_in.push({id:deviceInfo.deviceId,kind:"audio_in",label:text});}else if(deviceInfo.kind==='audiooutput'){text=deviceInfo.label||'speaker '+(aud_out.length+1);aud_out.push({id:deviceInfo.deviceId,kind:"audio_out",label:text});}else if(deviceInfo.kind==='videoinput'){text=deviceInfo.label||'camera '+(vid.length+1);vid.push({id:deviceInfo.deviceId,kind:"video",label:text});}else{console.log('Some other kind of source/device: ',deviceInfo);}}
$.verto.videoDevices=vid;$.verto.audioInDevices=aud_in;$.verto.audioOutDevices=aud_out;console.info("Audio IN Devices",$.verto.audioInDevices);console.info("Audio Out Devices",$.verto.audioOutDevices);console.info("Video Devices",$.verto.videoDevices);if(Xstream){Xstream.getTracks().forEach(function(track){track.stop();});}
if(runtime){runtime(true);}}
function handleError(error){console.log('device enumeration error: ',error);if(runtime)runtime(false);}
function checkTypes(devs){for(var i=0;i!==devs.length;++i){if(devs[i].kind==='audioinput'){has_audio++;}else if(devs[i].kind==='videoinput'){has_video++;}}
navigator.getUserMedia({audio:(has_audio>0?true:false),video:(has_video>0?true:false)},function(stream){Xstream=stream;navigator.mediaDevices.enumerateDevices().then(gotDevices).catch(handleError);},function(err){console.log("The following error occurred: "+err.name);});}
navigator.mediaDevices.enumerateDevices().then(checkTypes).catch(handleError);};$.verto.refreshDevices=function(runtime){checkDevices(runtime);}
$.verto.init=function(obj,runtime){if(!obj){obj={};}
if(!obj.skipPermCheck&&!obj.skipDeviceCheck){$.FSRTC.checkPerms(function(status){checkDevices(runtime);},true,true);}else if(obj.skipPermCheck&&!obj.skipDeviceCheck){checkDevices(runtime);}else if(!obj.skipPermCheck&&obj.skipDeviceCheck){$.FSRTC.checkPerms(function(status){runtime(status);},true,true);}else{runtime(null);}}
$.verto.genUUID=function(){return generateGUID();}})(jQuery);(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.adapter=f()}})(function(){var define,module,exports;return(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){'use strict';var SDPUtils={};SDPUtils.generateIdentifier=function(){return Math.random().toString(36).substr(2,10);};SDPUtils.localCName=SDPUtils.generateIdentifier();SDPUtils.splitLines=function(blob){return blob.trim().split('\n').map(function(line){return line.trim();});};SDPUtils.splitSections=function(blob){var parts=blob.split('\nm=');return parts.map(function(part,index){return(index>0?'m='+part:part).trim()+'\r\n';});};SDPUtils.matchPrefix=function(blob,prefix){return SDPUtils.splitLines(blob).filter(function(line){return line.indexOf(prefix)===0;});};SDPUtils.parseCandidate=function(line){var parts;if(line.indexOf('a=candidate:')===0){parts=line.substring(12).split(' ');}else{parts=line.substring(10).split(' ');}
var candidate={foundation:parts[0],component:parseInt(parts[1],10),protocol:parts[2].toLowerCase(),priority:parseInt(parts[3],10),ip:parts[4],port:parseInt(parts[5],10),type:parts[7]};for(var i=8;i<parts.length;i+=2){switch(parts[i]){case'raddr':candidate.relatedAddress=parts[i+1];break;case'rport':candidate.relatedPort=parseInt(parts[i+1],10);break;case'tcptype':candidate.tcpType=parts[i+1];break;default:candidate[parts[i]]=parts[i+1];break;}}
return candidate;};SDPUtils.writeCandidate=function(candidate){var sdp=[];sdp.push(candidate.foundation);sdp.push(candidate.component);sdp.push(candidate.protocol.toUpperCase());sdp.push(candidate.priority);sdp.push(candidate.ip);sdp.push(candidate.port);var type=candidate.type;sdp.push('typ');sdp.push(type);if(type!=='host'&&candidate.relatedAddress&&candidate.relatedPort){sdp.push('raddr');sdp.push(candidate.relatedAddress);sdp.push('rport');sdp.push(candidate.relatedPort);}
if(candidate.tcpType&&candidate.protocol.toLowerCase()==='tcp'){sdp.push('tcptype');sdp.push(candidate.tcpType);}
if(candidate.ufrag){sdp.push('ufrag');sdp.push(candidate.ufrag);}
return'candidate:'+sdp.join(' ');};SDPUtils.parseIceOptions=function(line){return line.substr(14).split(' ');}
SDPUtils.parseRtpMap=function(line){var parts=line.substr(9).split(' ');var parsed={payloadType:parseInt(parts.shift(),10)};parts=parts[0].split('/');parsed.name=parts[0];parsed.clockRate=parseInt(parts[1],10);parsed.numChannels=parts.length===3?parseInt(parts[2],10):1;return parsed;};SDPUtils.writeRtpMap=function(codec){var pt=codec.payloadType;if(codec.preferredPayloadType!==undefined){pt=codec.preferredPayloadType;}
return'a=rtpmap:'+pt+' '+codec.name+'/'+codec.clockRate+
(codec.numChannels!==1?'/'+codec.numChannels:'')+'\r\n';};SDPUtils.parseExtmap=function(line){var parts=line.substr(9).split(' ');return{id:parseInt(parts[0],10),direction:parts[0].indexOf('/')>0?parts[0].split('/')[1]:'sendrecv',uri:parts[1]};};SDPUtils.writeExtmap=function(headerExtension){return'a=extmap:'+(headerExtension.id||headerExtension.preferredId)+
(headerExtension.direction&&headerExtension.direction!=='sendrecv'?'/'+headerExtension.direction:'')+' '+headerExtension.uri+'\r\n';};SDPUtils.parseFmtp=function(line){var parsed={};var kv;var parts=line.substr(line.indexOf(' ')+1).split(';');for(var j=0;j<parts.length;j++){kv=parts[j].trim().split('=');parsed[kv[0].trim()]=kv[1];}
return parsed;};SDPUtils.writeFmtp=function(codec){var line='';var pt=codec.payloadType;if(codec.preferredPayloadType!==undefined){pt=codec.preferredPayloadType;}
if(codec.parameters&&Object.keys(codec.parameters).length){var params=[];Object.keys(codec.parameters).forEach(function(param){params.push(param+'='+codec.parameters[param]);});line+='a=fmtp:'+pt+' '+params.join(';')+'\r\n';}
return line;};SDPUtils.parseRtcpFb=function(line){var parts=line.substr(line.indexOf(' ')+1).split(' ');return{type:parts.shift(),parameter:parts.join(' ')};};SDPUtils.writeRtcpFb=function(codec){var lines='';var pt=codec.payloadType;if(codec.preferredPayloadType!==undefined){pt=codec.preferredPayloadType;}
if(codec.rtcpFeedback&&codec.rtcpFeedback.length){codec.rtcpFeedback.forEach(function(fb){lines+='a=rtcp-fb:'+pt+' '+fb.type+
(fb.parameter&&fb.parameter.length?' '+fb.parameter:'')+'\r\n';});}
return lines;};SDPUtils.parseSsrcMedia=function(line){var sp=line.indexOf(' ');var parts={ssrc:parseInt(line.substr(7,sp-7),10)};var colon=line.indexOf(':',sp);if(colon>-1){parts.attribute=line.substr(sp+1,colon-sp-1);parts.value=line.substr(colon+1);}else{parts.attribute=line.substr(sp+1);}
return parts;};SDPUtils.getMid=function(mediaSection){var mid=SDPUtils.matchPrefix(mediaSection,'a=mid:')[0];if(mid){return mid.substr(6);}}
SDPUtils.parseFingerprint=function(line){var parts=line.substr(14).split(' ');return{algorithm:parts[0].toLowerCase(),value:parts[1]};};SDPUtils.getDtlsParameters=function(mediaSection,sessionpart){var lines=SDPUtils.matchPrefix(mediaSection+sessionpart,'a=fingerprint:');return{role:'auto',fingerprints:lines.map(SDPUtils.parseFingerprint)};};SDPUtils.writeDtlsParameters=function(params,setupType){var sdp='a=setup:'+setupType+'\r\n';params.fingerprints.forEach(function(fp){sdp+='a=fingerprint:'+fp.algorithm+' '+fp.value+'\r\n';});return sdp;};SDPUtils.getIceParameters=function(mediaSection,sessionpart){var lines=SDPUtils.splitLines(mediaSection);lines=lines.concat(SDPUtils.splitLines(sessionpart));var iceParameters={usernameFragment:lines.filter(function(line){return line.indexOf('a=ice-ufrag:')===0;})[0].substr(12),password:lines.filter(function(line){return line.indexOf('a=ice-pwd:')===0;})[0].substr(10)};return iceParameters;};SDPUtils.writeIceParameters=function(params){return'a=ice-ufrag:'+params.usernameFragment+'\r\n'+'a=ice-pwd:'+params.password+'\r\n';};SDPUtils.parseRtpParameters=function(mediaSection){var description={codecs:[],headerExtensions:[],fecMechanisms:[],rtcp:[]};var lines=SDPUtils.splitLines(mediaSection);var mline=lines[0].split(' ');for(var i=3;i<mline.length;i++){var pt=mline[i];var rtpmapline=SDPUtils.matchPrefix(mediaSection,'a=rtpmap:'+pt+' ')[0];if(rtpmapline){var codec=SDPUtils.parseRtpMap(rtpmapline);var fmtps=SDPUtils.matchPrefix(mediaSection,'a=fmtp:'+pt+' ');codec.parameters=fmtps.length?SDPUtils.parseFmtp(fmtps[0]):{};codec.rtcpFeedback=SDPUtils.matchPrefix(mediaSection,'a=rtcp-fb:'+pt+' ').map(SDPUtils.parseRtcpFb);description.codecs.push(codec);switch(codec.name.toUpperCase()){case'RED':case'ULPFEC':description.fecMechanisms.push(codec.name.toUpperCase());break;default:break;}}}
SDPUtils.matchPrefix(mediaSection,'a=extmap:').forEach(function(line){description.headerExtensions.push(SDPUtils.parseExtmap(line));});return description;};SDPUtils.writeRtpDescription=function(kind,caps){var sdp='';sdp+='m='+kind+' ';sdp+=caps.codecs.length>0?'9':'0';sdp+=' UDP/TLS/RTP/SAVPF ';sdp+=caps.codecs.map(function(codec){if(codec.preferredPayloadType!==undefined){return codec.preferredPayloadType;}
return codec.payloadType;}).join(' ')+'\r\n';sdp+='c=IN IP4 0.0.0.0\r\n';sdp+='a=rtcp:9 IN IP4 0.0.0.0\r\n';caps.codecs.forEach(function(codec){sdp+=SDPUtils.writeRtpMap(codec);sdp+=SDPUtils.writeFmtp(codec);sdp+=SDPUtils.writeRtcpFb(codec);});var maxptime=0;caps.codecs.forEach(function(codec){if(codec.maxptime>maxptime){maxptime=codec.maxptime;}});if(maxptime>0){sdp+='a=maxptime:'+maxptime+'\r\n';}
sdp+='a=rtcp-mux\r\n';caps.headerExtensions.forEach(function(extension){sdp+=SDPUtils.writeExtmap(extension);});return sdp;};SDPUtils.parseRtpEncodingParameters=function(mediaSection){var encodingParameters=[];var description=SDPUtils.parseRtpParameters(mediaSection);var hasRed=description.fecMechanisms.indexOf('RED')!==-1;var hasUlpfec=description.fecMechanisms.indexOf('ULPFEC')!==-1;var ssrcs=SDPUtils.matchPrefix(mediaSection,'a=ssrc:').map(function(line){return SDPUtils.parseSsrcMedia(line);}).filter(function(parts){return parts.attribute==='cname';});var primarySsrc=ssrcs.length>0&&ssrcs[0].ssrc;var secondarySsrc;var flows=SDPUtils.matchPrefix(mediaSection,'a=ssrc-group:FID').map(function(line){var parts=line.split(' ');parts.shift();return parts.map(function(part){return parseInt(part,10);});});if(flows.length>0&&flows[0].length>1&&flows[0][0]===primarySsrc){secondarySsrc=flows[0][1];}
description.codecs.forEach(function(codec){if(codec.name.toUpperCase()==='RTX'&&codec.parameters.apt){var encParam={ssrc:primarySsrc,codecPayloadType:parseInt(codec.parameters.apt,10),rtx:{ssrc:secondarySsrc}};encodingParameters.push(encParam);if(hasRed){encParam=JSON.parse(JSON.stringify(encParam));encParam.fec={ssrc:secondarySsrc,mechanism:hasUlpfec?'red+ulpfec':'red'};encodingParameters.push(encParam);}}});if(encodingParameters.length===0&&primarySsrc){encodingParameters.push({ssrc:primarySsrc});}
var bandwidth=SDPUtils.matchPrefix(mediaSection,'b=');if(bandwidth.length){if(bandwidth[0].indexOf('b=TIAS:')===0){bandwidth=parseInt(bandwidth[0].substr(7),10);}else if(bandwidth[0].indexOf('b=AS:')===0){bandwidth=parseInt(bandwidth[0].substr(5),10)*1000*0.95
-(50*40*8);}else{bandwidth=undefined;}
encodingParameters.forEach(function(params){params.maxBitrate=bandwidth;});}
return encodingParameters;};SDPUtils.parseRtcpParameters=function(mediaSection){var rtcpParameters={};var cname;var remoteSsrc=SDPUtils.matchPrefix(mediaSection,'a=ssrc:').map(function(line){return SDPUtils.parseSsrcMedia(line);}).filter(function(obj){return obj.attribute==='cname';})[0];if(remoteSsrc){rtcpParameters.cname=remoteSsrc.value;rtcpParameters.ssrc=remoteSsrc.ssrc;}
var rsize=SDPUtils.matchPrefix(mediaSection,'a=rtcp-rsize');rtcpParameters.reducedSize=rsize.length>0;rtcpParameters.compound=rsize.length===0;var mux=SDPUtils.matchPrefix(mediaSection,'a=rtcp-mux');rtcpParameters.mux=mux.length>0;return rtcpParameters;};SDPUtils.parseMsid=function(mediaSection){var parts;var spec=SDPUtils.matchPrefix(mediaSection,'a=msid:');if(spec.length===1){parts=spec[0].substr(7).split(' ');return{stream:parts[0],track:parts[1]};}
var planB=SDPUtils.matchPrefix(mediaSection,'a=ssrc:').map(function(line){return SDPUtils.parseSsrcMedia(line);}).filter(function(parts){return parts.attribute==='msid';});if(planB.length>0){parts=planB[0].value.split(' ');return{stream:parts[0],track:parts[1]};}};SDPUtils.generateSessionId=function(){return Math.random().toString().substr(2,21);};SDPUtils.writeSessionBoilerplate=function(sessId){var sessionId;if(sessId){sessionId=sessId;}else{sessionId=SDPUtils.generateSessionId();}
return'v=0\r\n'+'o=thisisadapterortc '+sessionId+' 2 IN IP4 127.0.0.1\r\n'+'s=-\r\n'+'t=0 0\r\n';};SDPUtils.writeMediaSection=function(transceiver,caps,type,stream){var sdp=SDPUtils.writeRtpDescription(transceiver.kind,caps);sdp+=SDPUtils.writeIceParameters(transceiver.iceGatherer.getLocalParameters());sdp+=SDPUtils.writeDtlsParameters(transceiver.dtlsTransport.getLocalParameters(),type==='offer'?'actpass':'active');sdp+='a=mid:'+transceiver.mid+'\r\n';if(transceiver.direction){sdp+='a='+transceiver.direction+'\r\n';}else if(transceiver.rtpSender&&transceiver.rtpReceiver){sdp+='a=sendrecv\r\n';}else if(transceiver.rtpSender){sdp+='a=sendonly\r\n';}else if(transceiver.rtpReceiver){sdp+='a=recvonly\r\n';}else{sdp+='a=inactive\r\n';}
if(transceiver.rtpSender){var msid='msid:'+stream.id+' '+
transceiver.rtpSender.track.id+'\r\n';sdp+='a='+msid;sdp+='a=ssrc:'+transceiver.sendEncodingParameters[0].ssrc+' '+msid;if(transceiver.sendEncodingParameters[0].rtx){sdp+='a=ssrc:'+transceiver.sendEncodingParameters[0].rtx.ssrc+' '+msid;sdp+='a=ssrc-group:FID '+
transceiver.sendEncodingParameters[0].ssrc+' '+
transceiver.sendEncodingParameters[0].rtx.ssrc+'\r\n';}}
sdp+='a=ssrc:'+transceiver.sendEncodingParameters[0].ssrc+' cname:'+SDPUtils.localCName+'\r\n';if(transceiver.rtpSender&&transceiver.sendEncodingParameters[0].rtx){sdp+='a=ssrc:'+transceiver.sendEncodingParameters[0].rtx.ssrc+' cname:'+SDPUtils.localCName+'\r\n';}
return sdp;};SDPUtils.getDirection=function(mediaSection,sessionpart){var lines=SDPUtils.splitLines(mediaSection);for(var i=0;i<lines.length;i++){switch(lines[i]){case'a=sendrecv':case'a=sendonly':case'a=recvonly':case'a=inactive':return lines[i].substr(2);default:}}
if(sessionpart){return SDPUtils.getDirection(sessionpart);}
return'sendrecv';};SDPUtils.getKind=function(mediaSection){var lines=SDPUtils.splitLines(mediaSection);var mline=lines[0].split(' ');return mline[0].substr(2);};SDPUtils.isRejected=function(mediaSection){return mediaSection.split(' ',2)[1]==='0';};module.exports=SDPUtils;},{}],2:[function(require,module,exports){(function(global){'use strict';var adapterFactory=require('./adapter_factory.js');module.exports=adapterFactory({window:global.window});}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{})},{"./adapter_factory.js":3}],3:[function(require,module,exports){'use strict';module.exports=function(dependencies){var window=dependencies&&dependencies.window;var utils=require('./utils');var logging=utils.log;var browserDetails=utils.detectBrowser(window);var adapter={browserDetails:browserDetails,extractVersion:utils.extractVersion,disableLog:utils.disableLog,disableWarnings:utils.disableWarnings};var chromeShim=require('./chrome/chrome_shim')||null;var edgeShim=require('./edge/edge_shim')||null;var firefoxShim=require('./firefox/firefox_shim')||null;var safariShim=require('./safari/safari_shim')||null;switch(browserDetails.browser){case'chrome':if(!chromeShim||!chromeShim.shimPeerConnection){logging('Chrome shim is not included in this adapter release.');return adapter;}
logging('adapter.js shimming chrome.');adapter.browserShim=chromeShim;chromeShim.shimGetUserMedia(window);chromeShim.shimMediaStream(window);utils.shimCreateObjectURL(window);chromeShim.shimSourceObject(window);chromeShim.shimPeerConnection(window);chromeShim.shimOnTrack(window);chromeShim.shimGetSendersWithDtmf(window);break;case'firefox':if(!firefoxShim||!firefoxShim.shimPeerConnection){logging('Firefox shim is not included in this adapter release.');return adapter;}
logging('adapter.js shimming firefox.');adapter.browserShim=firefoxShim;firefoxShim.shimGetUserMedia(window);utils.shimCreateObjectURL(window);firefoxShim.shimSourceObject(window);firefoxShim.shimPeerConnection(window);firefoxShim.shimOnTrack(window);break;case'edge':if(!edgeShim||!edgeShim.shimPeerConnection){logging('MS edge shim is not included in this adapter release.');return adapter;}
logging('adapter.js shimming edge.');adapter.browserShim=edgeShim;edgeShim.shimGetUserMedia(window);utils.shimCreateObjectURL(window);edgeShim.shimPeerConnection(window);edgeShim.shimReplaceTrack(window);break;case'safari':if(!safariShim){logging('Safari shim is not included in this adapter release.');return adapter;}
logging('adapter.js shimming safari.');adapter.browserShim=safariShim;utils.shimCreateObjectURL(window);safariShim.shimRTCIceServerUrls(window);safariShim.shimCallbacksAPI(window);safariShim.shimLocalStreamsAPI(window);safariShim.shimRemoteStreamsAPI(window);safariShim.shimGetUserMedia(window);break;default:logging('Unsupported browser!');break;}
return adapter;};},{"./chrome/chrome_shim":4,"./edge/edge_shim":6,"./firefox/firefox_shim":9,"./safari/safari_shim":11,"./utils":12}],4:[function(require,module,exports){'use strict';var utils=require('../utils.js');var logging=utils.log;var chromeShim={shimMediaStream:function(window){window.MediaStream=window.MediaStream||window.webkitMediaStream;},shimOnTrack:function(window){if(typeof window==='object'&&window.RTCPeerConnection&&!('ontrack'in
window.RTCPeerConnection.prototype)){Object.defineProperty(window.RTCPeerConnection.prototype,'ontrack',{get:function(){return this._ontrack;},set:function(f){var self=this;if(this._ontrack){this.removeEventListener('track',this._ontrack);this.removeEventListener('addstream',this._ontrackpoly);}
this.addEventListener('track',this._ontrack=f);this.addEventListener('addstream',this._ontrackpoly=function(e){e.stream.addEventListener('addtrack',function(te){var receiver;if(window.RTCPeerConnection.prototype.getReceivers){receiver=self.getReceivers().find(function(r){return r.track.id===te.track.id;});}else{receiver={track:te.track};}
var event=new Event('track');event.track=te.track;event.receiver=receiver;event.streams=[e.stream];self.dispatchEvent(event);});e.stream.getTracks().forEach(function(track){var receiver;if(window.RTCPeerConnection.prototype.getReceivers){receiver=self.getReceivers().find(function(r){return r.track.id===track.id;});}else{receiver={track:track};}
var event=new Event('track');event.track=track;event.receiver=receiver;event.streams=[e.stream];this.dispatchEvent(event);}.bind(this));}.bind(this));}});}},shimGetSendersWithDtmf:function(window){if(typeof window==='object'&&window.RTCPeerConnection&&!('getSenders'in window.RTCPeerConnection.prototype)&&'createDTMFSender'in window.RTCPeerConnection.prototype){window.RTCPeerConnection.prototype.getSenders=function(){return this._senders||[];};var origAddStream=window.RTCPeerConnection.prototype.addStream;var origRemoveStream=window.RTCPeerConnection.prototype.removeStream;if(!window.RTCPeerConnection.prototype.addTrack){window.RTCPeerConnection.prototype.addTrack=function(track,stream){var pc=this;if(pc.signalingState==='closed'){throw new DOMException('The RTCPeerConnection\'s signalingState is \'closed\'.','InvalidStateError');}
var streams=[].slice.call(arguments,1);if(streams.length!==1||!streams[0].getTracks().find(function(t){return t===track;})){throw new DOMException('The adapter.js addTrack polyfill only supports a single '+' stream which is associated with the specified track.','NotSupportedError');}
pc._senders=pc._senders||[];var alreadyExists=pc._senders.find(function(t){return t.track===track;});if(alreadyExists){throw new DOMException('Track already exists.','InvalidAccessError');}
pc._streams=pc._streams||{};var oldStream=pc._streams[stream.id];if(oldStream){oldStream.addTrack(track);pc.removeStream(oldStream);pc.addStream(oldStream);}else{var newStream=new window.MediaStream([track]);pc._streams[stream.id]=newStream;pc.addStream(newStream);}
var sender={track:track,get dtmf(){if(this._dtmf===undefined){if(track.kind==='audio'){this._dtmf=pc.createDTMFSender(track);}else{this._dtmf=null;}}
return this._dtmf;}};pc._senders.push(sender);return sender;};}
window.RTCPeerConnection.prototype.addStream=function(stream){var pc=this;pc._senders=pc._senders||[];origAddStream.apply(pc,[stream]);stream.getTracks().forEach(function(track){pc._senders.push({track:track,get dtmf(){if(this._dtmf===undefined){if(track.kind==='audio'){this._dtmf=pc.createDTMFSender(track);}else{this._dtmf=null;}}
return this._dtmf;}});});};window.RTCPeerConnection.prototype.removeStream=function(stream){var pc=this;pc._senders=pc._senders||[];origRemoveStream.apply(pc,[stream]);stream.getTracks().forEach(function(track){var sender=pc._senders.find(function(s){return s.track===track;});if(sender){pc._senders.splice(pc._senders.indexOf(sender),1);}});};}else if(typeof window==='object'&&window.RTCPeerConnection&&'getSenders'in window.RTCPeerConnection.prototype&&'createDTMFSender'in window.RTCPeerConnection.prototype&&window.RTCRtpSender&&!('dtmf'in window.RTCRtpSender.prototype)){var origGetSenders=window.RTCPeerConnection.prototype.getSenders;window.RTCPeerConnection.prototype.getSenders=function(){var pc=this;var senders=origGetSenders.apply(pc,[]);senders.forEach(function(sender){sender._pc=pc;});return senders;};Object.defineProperty(window.RTCRtpSender.prototype,'dtmf',{get:function(){if(this._dtmf===undefined){if(this.track.kind==='audio'){this._dtmf=this._pc.createDTMFSender(this.track);}else{this._dtmf=null;}}
return this._dtmf;},});}},shimSourceObject:function(window){var URL=window&&window.URL;if(typeof window==='object'){if(window.HTMLMediaElement&&!('srcObject'in window.HTMLMediaElement.prototype)){Object.defineProperty(window.HTMLMediaElement.prototype,'srcObject',{get:function(){return this._srcObject;},set:function(stream){var self=this;this._srcObject=stream;if(this.src){URL.revokeObjectURL(this.src);}
if(!stream){this.src='';return undefined;}
this.src=URL.createObjectURL(stream);stream.addEventListener('addtrack',function(){if(self.src){URL.revokeObjectURL(self.src);}
self.src=URL.createObjectURL(stream);});stream.addEventListener('removetrack',function(){if(self.src){URL.revokeObjectURL(self.src);}
self.src=URL.createObjectURL(stream);});}});}}},shimPeerConnection:function(window){var browserDetails=utils.detectBrowser(window);if(!window.RTCPeerConnection){window.RTCPeerConnection=function(pcConfig,pcConstraints){logging('PeerConnection');if(pcConfig&&pcConfig.iceTransportPolicy){pcConfig.iceTransports=pcConfig.iceTransportPolicy;}
return new window.webkitRTCPeerConnection(pcConfig,pcConstraints);};window.RTCPeerConnection.prototype=window.webkitRTCPeerConnection.prototype;if(window.webkitRTCPeerConnection.generateCertificate){Object.defineProperty(window.RTCPeerConnection,'generateCertificate',{get:function(){return window.webkitRTCPeerConnection.generateCertificate;}});}}else{var OrigPeerConnection=window.RTCPeerConnection;window.RTCPeerConnection=function(pcConfig,pcConstraints){if(pcConfig&&pcConfig.iceServers){var newIceServers=[];for(var i=0;i<pcConfig.iceServers.length;i++){var server=pcConfig.iceServers[i];if(!server.hasOwnProperty('urls')&&server.hasOwnProperty('url')){console.warn('RTCIceServer.url is deprecated! Use urls instead.');server=JSON.parse(JSON.stringify(server));server.urls=server.url;newIceServers.push(server);}else{newIceServers.push(pcConfig.iceServers[i]);}}
pcConfig.iceServers=newIceServers;}
return new OrigPeerConnection(pcConfig,pcConstraints);};window.RTCPeerConnection.prototype=OrigPeerConnection.prototype;Object.defineProperty(window.RTCPeerConnection,'generateCertificate',{get:function(){return OrigPeerConnection.generateCertificate;}});}
var origGetStats=window.RTCPeerConnection.prototype.getStats;window.RTCPeerConnection.prototype.getStats=function(selector,successCallback,errorCallback){var self=this;var args=arguments;if(arguments.length>0&&typeof selector==='function'){return origGetStats.apply(this,arguments);}
if(origGetStats.length===0&&(arguments.length===0||typeof arguments[0]!=='function')){return origGetStats.apply(this,[]);}
var fixChromeStats_=function(response){var standardReport={};var reports=response.result();reports.forEach(function(report){var standardStats={id:report.id,timestamp:report.timestamp,type:{localcandidate:'local-candidate',remotecandidate:'remote-candidate'}[report.type]||report.type};report.names().forEach(function(name){standardStats[name]=report.stat(name);});standardReport[standardStats.id]=standardStats;});return standardReport;};var makeMapStats=function(stats){return new Map(Object.keys(stats).map(function(key){return[key,stats[key]];}));};if(arguments.length>=2){var successCallbackWrapper_=function(response){args[1](makeMapStats(fixChromeStats_(response)));};return origGetStats.apply(this,[successCallbackWrapper_,arguments[0]]);}
return new Promise(function(resolve,reject){origGetStats.apply(self,[function(response){resolve(makeMapStats(fixChromeStats_(response)));},reject]);}).then(successCallback,errorCallback);};if(browserDetails.version<51){['setLocalDescription','setRemoteDescription','addIceCandidate'].forEach(function(method){var nativeMethod=window.RTCPeerConnection.prototype[method];window.RTCPeerConnection.prototype[method]=function(){var args=arguments;var self=this;var promise=new Promise(function(resolve,reject){nativeMethod.apply(self,[args[0],resolve,reject]);});if(args.length<2){return promise;}
return promise.then(function(){args[1].apply(null,[]);},function(err){if(args.length>=3){args[2].apply(null,[err]);}});};});}
if(browserDetails.version<52){['createOffer','createAnswer'].forEach(function(method){var nativeMethod=window.RTCPeerConnection.prototype[method];window.RTCPeerConnection.prototype[method]=function(){var self=this;if(arguments.length<1||(arguments.length===1&&typeof arguments[0]==='object')){var opts=arguments.length===1?arguments[0]:undefined;return new Promise(function(resolve,reject){nativeMethod.apply(self,[resolve,reject,opts]);});}
return nativeMethod.apply(this,arguments);};});}
['setLocalDescription','setRemoteDescription','addIceCandidate'].forEach(function(method){var nativeMethod=window.RTCPeerConnection.prototype[method];window.RTCPeerConnection.prototype[method]=function(){arguments[0]=new((method==='addIceCandidate')?window.RTCIceCandidate:window.RTCSessionDescription)(arguments[0]);return nativeMethod.apply(this,arguments);};});var nativeAddIceCandidate=window.RTCPeerConnection.prototype.addIceCandidate;window.RTCPeerConnection.prototype.addIceCandidate=function(){if(!arguments[0]){if(arguments[1]){arguments[1].apply(null);}
return Promise.resolve();}
return nativeAddIceCandidate.apply(this,arguments);};}};module.exports={shimMediaStream:chromeShim.shimMediaStream,shimOnTrack:chromeShim.shimOnTrack,shimGetSendersWithDtmf:chromeShim.shimGetSendersWithDtmf,shimSourceObject:chromeShim.shimSourceObject,shimPeerConnection:chromeShim.shimPeerConnection,shimGetUserMedia:require('./getusermedia')};},{"../utils.js":12,"./getusermedia":5}],5:[function(require,module,exports){'use strict';var utils=require('../utils.js');var logging=utils.log;module.exports=function(window){var browserDetails=utils.detectBrowser(window);var navigator=window&&window.navigator;var constraintsToChrome_=function(c){if(typeof c!=='object'||c.mandatory||c.optional){return c;}
var cc={};Object.keys(c).forEach(function(key){if(key==='require'||key==='advanced'||key==='mediaSource'){return;}
var r=(typeof c[key]==='object')?c[key]:{ideal:c[key]};if(r.exact!==undefined&&typeof r.exact==='number'){r.min=r.max=r.exact;}
var oldname_=function(prefix,name){if(prefix){return prefix+name.charAt(0).toUpperCase()+name.slice(1);}
return(name==='deviceId')?'sourceId':name;};if(r.ideal!==undefined){cc.optional=cc.optional||[];var oc={};if(typeof r.ideal==='number'){oc[oldname_('min',key)]=r.ideal;cc.optional.push(oc);oc={};oc[oldname_('max',key)]=r.ideal;cc.optional.push(oc);}else{oc[oldname_('',key)]=r.ideal;cc.optional.push(oc);}}
if(r.exact!==undefined&&typeof r.exact!=='number'){cc.mandatory=cc.mandatory||{};cc.mandatory[oldname_('',key)]=r.exact;}else{['min','max'].forEach(function(mix){if(r[mix]!==undefined){cc.mandatory=cc.mandatory||{};cc.mandatory[oldname_(mix,key)]=r[mix];}});}});if(c.advanced){cc.optional=(cc.optional||[]).concat(c.advanced);}
return cc;};var shimConstraints_=function(constraints,func){constraints=JSON.parse(JSON.stringify(constraints));if(constraints&&typeof constraints.audio==='object'){var remap=function(obj,a,b){if(a in obj&&!(b in obj)){obj[b]=obj[a];delete obj[a];}};constraints=JSON.parse(JSON.stringify(constraints));remap(constraints.audio,'autoGainControl','googAutoGainControl');remap(constraints.audio,'noiseSuppression','googNoiseSuppression');constraints.audio=constraintsToChrome_(constraints.audio);}
if(constraints&&typeof constraints.video==='object'){var face=constraints.video.facingMode;face=face&&((typeof face==='object')?face:{ideal:face});var getSupportedFacingModeLies=browserDetails.version<61;if((face&&(face.exact==='user'||face.exact==='environment'||face.ideal==='user'||face.ideal==='environment'))&&!(navigator.mediaDevices.getSupportedConstraints&&navigator.mediaDevices.getSupportedConstraints().facingMode&&!getSupportedFacingModeLies)){delete constraints.video.facingMode;var matches;if(face.exact==='environment'||face.ideal==='environment'){matches=['back','rear'];}else if(face.exact==='user'||face.ideal==='user'){matches=['front'];}
if(matches){return navigator.mediaDevices.enumerateDevices().then(function(devices){devices=devices.filter(function(d){return d.kind==='videoinput';});var dev=devices.find(function(d){return matches.some(function(match){return d.label.toLowerCase().indexOf(match)!==-1;});});if(!dev&&devices.length&&matches.indexOf('back')!==-1){dev=devices[devices.length-1];}
if(dev){constraints.video.deviceId=face.exact?{exact:dev.deviceId}:{ideal:dev.deviceId};}
constraints.video=constraintsToChrome_(constraints.video);logging('chrome: '+JSON.stringify(constraints));return func(constraints);});}}
constraints.video=constraintsToChrome_(constraints.video);}
logging('chrome: '+JSON.stringify(constraints));return func(constraints);};var shimError_=function(e){return{name:{PermissionDeniedError:'NotAllowedError',InvalidStateError:'NotReadableError',DevicesNotFoundError:'NotFoundError',ConstraintNotSatisfiedError:'OverconstrainedError',TrackStartError:'NotReadableError',MediaDeviceFailedDueToShutdown:'NotReadableError',MediaDeviceKillSwitchOn:'NotReadableError'}[e.name]||e.name,message:e.message,constraint:e.constraintName,toString:function(){return this.name+(this.message&&': ')+this.message;}};};var getUserMedia_=function(constraints,onSuccess,onError){shimConstraints_(constraints,function(c){navigator.webkitGetUserMedia(c,onSuccess,function(e){onError(shimError_(e));});});};navigator.getUserMedia=getUserMedia_;var getUserMediaPromise_=function(constraints){return new Promise(function(resolve,reject){navigator.getUserMedia(constraints,resolve,reject);});};if(!navigator.mediaDevices){navigator.mediaDevices={getUserMedia:getUserMediaPromise_,enumerateDevices:function(){return new Promise(function(resolve){var kinds={audio:'audioinput',video:'videoinput'};return window.MediaStreamTrack.getSources(function(devices){resolve(devices.map(function(device){return{label:device.label,kind:kinds[device.kind],deviceId:device.id,groupId:''};}));});});},getSupportedConstraints:function(){return{deviceId:true,echoCancellation:true,facingMode:true,frameRate:true,height:true,width:true};}};}
if(!navigator.mediaDevices.getUserMedia){navigator.mediaDevices.getUserMedia=function(constraints){return getUserMediaPromise_(constraints);};}else{var origGetUserMedia=navigator.mediaDevices.getUserMedia.bind(navigator.mediaDevices);navigator.mediaDevices.getUserMedia=function(cs){return shimConstraints_(cs,function(c){return origGetUserMedia(c).then(function(stream){if(c.audio&&!stream.getAudioTracks().length||c.video&&!stream.getVideoTracks().length){stream.getTracks().forEach(function(track){track.stop();});throw new DOMException('','NotFoundError');}
return stream;},function(e){return Promise.reject(shimError_(e));});});};}
if(typeof navigator.mediaDevices.addEventListener==='undefined'){navigator.mediaDevices.addEventListener=function(){logging('Dummy mediaDevices.addEventListener called.');};}
if(typeof navigator.mediaDevices.removeEventListener==='undefined'){navigator.mediaDevices.removeEventListener=function(){logging('Dummy mediaDevices.removeEventListener called.');};}};},{"../utils.js":12}],6:[function(require,module,exports){'use strict';var utils=require('../utils');var shimRTCPeerConnection=require('./rtcpeerconnection_shim');module.exports={shimGetUserMedia:require('./getusermedia'),shimPeerConnection:function(window){var browserDetails=utils.detectBrowser(window);if(window.RTCIceGatherer){if(!window.RTCIceCandidate){window.RTCIceCandidate=function(args){return args;};}
if(!window.RTCSessionDescription){window.RTCSessionDescription=function(args){return args;};}
if(browserDetails.version<15025){var origMSTEnabled=Object.getOwnPropertyDescriptor(window.MediaStreamTrack.prototype,'enabled');Object.defineProperty(window.MediaStreamTrack.prototype,'enabled',{set:function(value){origMSTEnabled.set.call(this,value);var ev=new Event('enabled');ev.enabled=value;this.dispatchEvent(ev);}});}}
window.RTCPeerConnection=shimRTCPeerConnection(window,browserDetails.version);},shimReplaceTrack:function(window){if(window.RTCRtpSender&&!('replaceTrack'in window.RTCRtpSender.prototype)){window.RTCRtpSender.prototype.replaceTrack=window.RTCRtpSender.prototype.setTrack;}}};},{"../utils":12,"./getusermedia":7,"./rtcpeerconnection_shim":8}],7:[function(require,module,exports){'use strict';module.exports=function(window){var navigator=window&&window.navigator;var shimError_=function(e){return{name:{PermissionDeniedError:'NotAllowedError'}[e.name]||e.name,message:e.message,constraint:e.constraint,toString:function(){return this.name;}};};var origGetUserMedia=navigator.mediaDevices.getUserMedia.bind(navigator.mediaDevices);navigator.mediaDevices.getUserMedia=function(c){return origGetUserMedia(c).catch(function(e){return Promise.reject(shimError_(e));});};};},{}],8:[function(require,module,exports){'use strict';var SDPUtils=require('sdp');function sortTracks(tracks){var audioTracks=tracks.filter(function(track){return track.kind==='audio';});var videoTracks=tracks.filter(function(track){return track.kind==='video';});tracks=[];while(audioTracks.length||videoTracks.length){if(audioTracks.length){tracks.push(audioTracks.shift());}
if(videoTracks.length){tracks.push(videoTracks.shift());}}
return tracks;}
function filterIceServers(iceServers,edgeVersion){var hasTurn=false;iceServers=JSON.parse(JSON.stringify(iceServers));return iceServers.filter(function(server){if(server&&(server.urls||server.url)){var urls=server.urls||server.url;if(server.url&&!server.urls){console.warn('RTCIceServer.url is deprecated! Use urls instead.');}
var isString=typeof urls==='string';if(isString){urls=[urls];}
urls=urls.filter(function(url){var validTurn=url.indexOf('turn:')===0&&url.indexOf('transport=udp')!==-1&&url.indexOf('turn:[')===-1&&!hasTurn;if(validTurn){hasTurn=true;return true;}
return url.indexOf('stun:')===0&&edgeVersion>=14393;});delete server.url;server.urls=isString?urls[0]:urls;return!!urls.length;}
return false;});}
function getCommonCapabilities(localCapabilities,remoteCapabilities){var commonCapabilities={codecs:[],headerExtensions:[],fecMechanisms:[]};var findCodecByPayloadType=function(pt,codecs){pt=parseInt(pt,10);for(var i=0;i<codecs.length;i++){if(codecs[i].payloadType===pt||codecs[i].preferredPayloadType===pt){return codecs[i];}}};var rtxCapabilityMatches=function(lRtx,rRtx,lCodecs,rCodecs){var lCodec=findCodecByPayloadType(lRtx.parameters.apt,lCodecs);var rCodec=findCodecByPayloadType(rRtx.parameters.apt,rCodecs);return lCodec&&rCodec&&lCodec.name.toLowerCase()===rCodec.name.toLowerCase();};localCapabilities.codecs.forEach(function(lCodec){for(var i=0;i<remoteCapabilities.codecs.length;i++){var rCodec=remoteCapabilities.codecs[i];if(lCodec.name.toLowerCase()===rCodec.name.toLowerCase()&&lCodec.clockRate===rCodec.clockRate){if(lCodec.name.toLowerCase()==='rtx'&&lCodec.parameters&&rCodec.parameters.apt){if(!rtxCapabilityMatches(lCodec,rCodec,localCapabilities.codecs,remoteCapabilities.codecs)){continue;}}
rCodec=JSON.parse(JSON.stringify(rCodec));rCodec.numChannels=Math.min(lCodec.numChannels,rCodec.numChannels);commonCapabilities.codecs.push(rCodec);rCodec.rtcpFeedback=rCodec.rtcpFeedback.filter(function(fb){for(var j=0;j<lCodec.rtcpFeedback.length;j++){if(lCodec.rtcpFeedback[j].type===fb.type&&lCodec.rtcpFeedback[j].parameter===fb.parameter){return true;}}
return false;});break;}}});localCapabilities.headerExtensions.forEach(function(lHeaderExtension){for(var i=0;i<remoteCapabilities.headerExtensions.length;i++){var rHeaderExtension=remoteCapabilities.headerExtensions[i];if(lHeaderExtension.uri===rHeaderExtension.uri){commonCapabilities.headerExtensions.push(rHeaderExtension);break;}}});return commonCapabilities;}
function isActionAllowedInSignalingState(action,type,signalingState){return{offer:{setLocalDescription:['stable','have-local-offer'],setRemoteDescription:['stable','have-remote-offer']},answer:{setLocalDescription:['have-remote-offer','have-local-pranswer'],setRemoteDescription:['have-local-offer','have-remote-pranswer']}}[type][action].indexOf(signalingState)!==-1;}
module.exports=function(window,edgeVersion){var RTCPeerConnection=function(config){var self=this;var _eventTarget=document.createDocumentFragment();['addEventListener','removeEventListener','dispatchEvent'].forEach(function(method){self[method]=_eventTarget[method].bind(_eventTarget);});this.needNegotiation=false;this.onicecandidate=null;this.onaddstream=null;this.ontrack=null;this.onremovestream=null;this.onsignalingstatechange=null;this.oniceconnectionstatechange=null;this.onicegatheringstatechange=null;this.onnegotiationneeded=null;this.ondatachannel=null;this.canTrickleIceCandidates=null;this.localStreams=[];this.remoteStreams=[];this.getLocalStreams=function(){return self.localStreams;};this.getRemoteStreams=function(){return self.remoteStreams;};this.localDescription=new window.RTCSessionDescription({type:'',sdp:''});this.remoteDescription=new window.RTCSessionDescription({type:'',sdp:''});this.signalingState='stable';this.iceConnectionState='new';this.iceGatheringState='new';this.iceOptions={gatherPolicy:'all',iceServers:[]};if(config&&config.iceTransportPolicy){switch(config.iceTransportPolicy){case'all':case'relay':this.iceOptions.gatherPolicy=config.iceTransportPolicy;break;default:break;}}
this.usingBundle=config&&config.bundlePolicy==='max-bundle';if(config&&config.iceServers){this.iceOptions.iceServers=filterIceServers(config.iceServers,edgeVersion);}
this._config=config||{};this.transceivers=[];this._localIceCandidatesBuffer=[];this._sdpSessionId=SDPUtils.generateSessionId();};RTCPeerConnection.prototype._emitGatheringStateChange=function(){var event=new Event('icegatheringstatechange');this.dispatchEvent(event);if(this.onicegatheringstatechange!==null){this.onicegatheringstatechange(event);}};RTCPeerConnection.prototype._emitBufferedCandidates=function(){var self=this;var sections=SDPUtils.splitSections(self.localDescription.sdp);this._localIceCandidatesBuffer.forEach(function(event){var end=!event.candidate||Object.keys(event.candidate).length===0;if(end){for(var j=1;j<sections.length;j++){if(sections[j].indexOf('\r\na=end-of-candidates\r\n')===-1){sections[j]+='a=end-of-candidates\r\n';}}}else{sections[event.candidate.sdpMLineIndex+1]+='a='+event.candidate.candidate+'\r\n';}
self.localDescription.sdp=sections.join('');self.dispatchEvent(event);if(self.onicecandidate!==null){self.onicecandidate(event);}
if(!event.candidate&&self.iceGatheringState!=='complete'){var complete=self.transceivers.every(function(transceiver){return transceiver.iceGatherer&&transceiver.iceGatherer.state==='completed';});if(complete&&self.iceGatheringStateChange!=='complete'){self.iceGatheringState='complete';self._emitGatheringStateChange();}}});this._localIceCandidatesBuffer=[];};RTCPeerConnection.prototype.getConfiguration=function(){return this._config;};RTCPeerConnection.prototype._createTransceiver=function(kind){var hasBundleTransport=this.transceivers.length>0;var transceiver={track:null,iceGatherer:null,iceTransport:null,dtlsTransport:null,localCapabilities:null,remoteCapabilities:null,rtpSender:null,rtpReceiver:null,kind:kind,mid:null,sendEncodingParameters:null,recvEncodingParameters:null,stream:null,wantReceive:true};if(this.usingBundle&&hasBundleTransport){transceiver.iceTransport=this.transceivers[0].iceTransport;transceiver.dtlsTransport=this.transceivers[0].dtlsTransport;}else{var transports=this._createIceAndDtlsTransports();transceiver.iceTransport=transports.iceTransport;transceiver.dtlsTransport=transports.dtlsTransport;}
this.transceivers.push(transceiver);return transceiver;};RTCPeerConnection.prototype.addTrack=function(track,stream){var transceiver;for(var i=0;i<this.transceivers.length;i++){if(!this.transceivers[i].track&&this.transceivers[i].kind===track.kind){transceiver=this.transceivers[i];}}
if(!transceiver){transceiver=this._createTransceiver(track.kind);}
transceiver.track=track;transceiver.stream=stream;transceiver.rtpSender=new window.RTCRtpSender(track,transceiver.dtlsTransport);this._maybeFireNegotiationNeeded();return transceiver.rtpSender;};RTCPeerConnection.prototype.addStream=function(stream){var self=this;if(edgeVersion>=15025){this.localStreams.push(stream);stream.getTracks().forEach(function(track){self.addTrack(track,stream);});}else{var clonedStream=stream.clone();stream.getTracks().forEach(function(track,idx){var clonedTrack=clonedStream.getTracks()[idx];track.addEventListener('enabled',function(event){clonedTrack.enabled=event.enabled;});});clonedStream.getTracks().forEach(function(track){self.addTrack(track,clonedStream);});this.localStreams.push(clonedStream);}
this._maybeFireNegotiationNeeded();};RTCPeerConnection.prototype.removeStream=function(stream){var idx=this.localStreams.indexOf(stream);if(idx>-1){this.localStreams.splice(idx,1);this._maybeFireNegotiationNeeded();}};RTCPeerConnection.prototype.getSenders=function(){return this.transceivers.filter(function(transceiver){return!!transceiver.rtpSender;}).map(function(transceiver){return transceiver.rtpSender;});};RTCPeerConnection.prototype.getReceivers=function(){return this.transceivers.filter(function(transceiver){return!!transceiver.rtpReceiver;}).map(function(transceiver){return transceiver.rtpReceiver;});};RTCPeerConnection.prototype._createIceGatherer=function(mid,sdpMLineIndex){var self=this;var iceGatherer=new window.RTCIceGatherer(self.iceOptions);iceGatherer.onlocalcandidate=function(evt){var event=new Event('icecandidate');event.candidate={sdpMid:mid,sdpMLineIndex:sdpMLineIndex};var cand=evt.candidate;var end=!cand||Object.keys(cand).length===0;if(end){if(iceGatherer.state===undefined){iceGatherer.state='completed';}}else{cand.component=1;event.candidate.candidate=SDPUtils.writeCandidate(cand);}
var sections=SDPUtils.splitSections(self.localDescription.sdp);if(!end){sections[event.candidate.sdpMLineIndex+1]+='a='+event.candidate.candidate+'\r\n';}else{sections[event.candidate.sdpMLineIndex+1]+='a=end-of-candidates\r\n';}
self.localDescription.sdp=sections.join('');var transceivers=self._pendingOffer?self._pendingOffer:self.transceivers;var complete=transceivers.every(function(transceiver){return transceiver.iceGatherer&&transceiver.iceGatherer.state==='completed';});switch(self.iceGatheringState){case'new':if(!end){self._localIceCandidatesBuffer.push(event);}
if(end&&complete){self._localIceCandidatesBuffer.push(new Event('icecandidate'));}
break;case'gathering':self._emitBufferedCandidates();if(!end){self.dispatchEvent(event);if(self.onicecandidate!==null){self.onicecandidate(event);}}
if(complete){self.dispatchEvent(new Event('icecandidate'));if(self.onicecandidate!==null){self.onicecandidate(new Event('icecandidate'));}
self.iceGatheringState='complete';self._emitGatheringStateChange();}
break;case'complete':break;default:break;}};return iceGatherer;};RTCPeerConnection.prototype._createIceAndDtlsTransports=function(){var self=this;var iceTransport=new window.RTCIceTransport(null);iceTransport.onicestatechange=function(){self._updateConnectionState();};var dtlsTransport=new window.RTCDtlsTransport(iceTransport);dtlsTransport.ondtlsstatechange=function(){self._updateConnectionState();};dtlsTransport.onerror=function(){Object.defineProperty(dtlsTransport,'state',{value:'failed',writable:true});self._updateConnectionState();};return{iceTransport:iceTransport,dtlsTransport:dtlsTransport};};RTCPeerConnection.prototype._disposeIceAndDtlsTransports=function(sdpMLineIndex){var iceGatherer=this.transceivers[sdpMLineIndex].iceGatherer;if(iceGatherer){delete iceGatherer.onlocalcandidate;delete this.transceivers[sdpMLineIndex].iceGatherer;}
var iceTransport=this.transceivers[sdpMLineIndex].iceTransport;if(iceTransport){delete iceTransport.onicestatechange;delete this.transceivers[sdpMLineIndex].iceTransport;}
var dtlsTransport=this.transceivers[sdpMLineIndex].dtlsTransport;if(dtlsTransport){delete dtlsTransport.ondtlssttatechange;delete dtlsTransport.onerror;delete this.transceivers[sdpMLineIndex].dtlsTransport;}};RTCPeerConnection.prototype._transceive=function(transceiver,send,recv){var params=getCommonCapabilities(transceiver.localCapabilities,transceiver.remoteCapabilities);if(send&&transceiver.rtpSender){params.encodings=transceiver.sendEncodingParameters;params.rtcp={cname:SDPUtils.localCName,compound:transceiver.rtcpParameters.compound};if(transceiver.recvEncodingParameters.length){params.rtcp.ssrc=transceiver.recvEncodingParameters[0].ssrc;}
transceiver.rtpSender.send(params);}
if(recv&&transceiver.rtpReceiver){if(transceiver.kind==='video'&&transceiver.recvEncodingParameters&&edgeVersion<15019){transceiver.recvEncodingParameters.forEach(function(p){delete p.rtx;});}
params.encodings=transceiver.recvEncodingParameters;params.rtcp={cname:transceiver.rtcpParameters.cname,compound:transceiver.rtcpParameters.compound};if(transceiver.sendEncodingParameters.length){params.rtcp.ssrc=transceiver.sendEncodingParameters[0].ssrc;}
transceiver.rtpReceiver.receive(params);}};RTCPeerConnection.prototype.setLocalDescription=function(description){var self=this;if(!isActionAllowedInSignalingState('setLocalDescription',description.type,this.signalingState)){var e=new Error('Can not set local '+description.type+' in state '+this.signalingState);e.name='InvalidStateError';if(arguments.length>2&&typeof arguments[2]==='function'){window.setTimeout(arguments[2],0,e);}
return Promise.reject(e);}
var sections;var sessionpart;if(description.type==='offer'){if(this._pendingOffer){sections=SDPUtils.splitSections(description.sdp);sessionpart=sections.shift();sections.forEach(function(mediaSection,sdpMLineIndex){var caps=SDPUtils.parseRtpParameters(mediaSection);self._pendingOffer[sdpMLineIndex].localCapabilities=caps;});this.transceivers=this._pendingOffer;delete this._pendingOffer;}}else if(description.type==='answer'){sections=SDPUtils.splitSections(self.remoteDescription.sdp);sessionpart=sections.shift();var isIceLite=SDPUtils.matchPrefix(sessionpart,'a=ice-lite').length>0;sections.forEach(function(mediaSection,sdpMLineIndex){var transceiver=self.transceivers[sdpMLineIndex];var iceGatherer=transceiver.iceGatherer;var iceTransport=transceiver.iceTransport;var dtlsTransport=transceiver.dtlsTransport;var localCapabilities=transceiver.localCapabilities;var remoteCapabilities=transceiver.remoteCapabilities;var rejected=SDPUtils.isRejected(mediaSection);if(!rejected&&!transceiver.isDatachannel){var remoteIceParameters=SDPUtils.getIceParameters(mediaSection,sessionpart);var remoteDtlsParameters=SDPUtils.getDtlsParameters(mediaSection,sessionpart);if(isIceLite){remoteDtlsParameters.role='server';}
if(!self.usingBundle||sdpMLineIndex===0){iceTransport.start(iceGatherer,remoteIceParameters,isIceLite?'controlling':'controlled');dtlsTransport.start(remoteDtlsParameters);}
var params=getCommonCapabilities(localCapabilities,remoteCapabilities);self._transceive(transceiver,params.codecs.length>0,false);}});}
this.localDescription={type:description.type,sdp:description.sdp};switch(description.type){case'offer':this._updateSignalingState('have-local-offer');break;case'answer':this._updateSignalingState('stable');break;default:throw new TypeError('unsupported type "'+description.type+'"');}
var hasCallback=arguments.length>1&&typeof arguments[1]==='function';if(hasCallback){var cb=arguments[1];window.setTimeout(function(){cb();if(self.iceGatheringState==='new'){self.iceGatheringState='gathering';self._emitGatheringStateChange();}
self._emitBufferedCandidates();},0);}
var p=Promise.resolve();p.then(function(){if(!hasCallback){if(self.iceGatheringState==='new'){self.iceGatheringState='gathering';self._emitGatheringStateChange();}
window.setTimeout(self._emitBufferedCandidates.bind(self),500);}});return p;};RTCPeerConnection.prototype.setRemoteDescription=function(description){var self=this;if(!isActionAllowedInSignalingState('setRemoteDescription',description.type,this.signalingState)){var e=new Error('Can not set remote '+description.type+' in state '+this.signalingState);e.name='InvalidStateError';if(arguments.length>2&&typeof arguments[2]==='function'){window.setTimeout(arguments[2],0,e);}
return Promise.reject(e);}
var streams={};var receiverList=[];var sections=SDPUtils.splitSections(description.sdp);var sessionpart=sections.shift();var isIceLite=SDPUtils.matchPrefix(sessionpart,'a=ice-lite').length>0;var usingBundle=SDPUtils.matchPrefix(sessionpart,'a=group:BUNDLE ').length>0;this.usingBundle=usingBundle;var iceOptions=SDPUtils.matchPrefix(sessionpart,'a=ice-options:')[0];if(iceOptions){this.canTrickleIceCandidates=iceOptions.substr(14).split(' ').indexOf('trickle')>=0;}else{this.canTrickleIceCandidates=false;}
sections.forEach(function(mediaSection,sdpMLineIndex){var lines=SDPUtils.splitLines(mediaSection);var kind=SDPUtils.getKind(mediaSection);var rejected=SDPUtils.isRejected(mediaSection);var protocol=lines[0].substr(2).split(' ')[2];var direction=SDPUtils.getDirection(mediaSection,sessionpart);var remoteMsid=SDPUtils.parseMsid(mediaSection);var mid=SDPUtils.getMid(mediaSection)||SDPUtils.generateIdentifier();if(kind==='application'&&protocol==='DTLS/SCTP'){self.transceivers[sdpMLineIndex]={mid:mid,isDatachannel:true};return;}
var transceiver;var iceGatherer;var iceTransport;var dtlsTransport;var rtpReceiver;var sendEncodingParameters;var recvEncodingParameters;var localCapabilities;var track;var remoteCapabilities=SDPUtils.parseRtpParameters(mediaSection);var remoteIceParameters;var remoteDtlsParameters;if(!rejected){remoteIceParameters=SDPUtils.getIceParameters(mediaSection,sessionpart);remoteDtlsParameters=SDPUtils.getDtlsParameters(mediaSection,sessionpart);remoteDtlsParameters.role='client';}
recvEncodingParameters=SDPUtils.parseRtpEncodingParameters(mediaSection);var rtcpParameters=SDPUtils.parseRtcpParameters(mediaSection);var isComplete=SDPUtils.matchPrefix(mediaSection,'a=end-of-candidates',sessionpart).length>0;var cands=SDPUtils.matchPrefix(mediaSection,'a=candidate:').map(function(cand){return SDPUtils.parseCandidate(cand);}).filter(function(cand){return cand.component==='1'||cand.component===1;});if((description.type==='offer'||description.type==='answer')&&!rejected&&usingBundle&&sdpMLineIndex>0&&self.transceivers[sdpMLineIndex]){self._disposeIceAndDtlsTransports(sdpMLineIndex);self.transceivers[sdpMLineIndex].iceGatherer=self.transceivers[0].iceGatherer;self.transceivers[sdpMLineIndex].iceTransport=self.transceivers[0].iceTransport;self.transceivers[sdpMLineIndex].dtlsTransport=self.transceivers[0].dtlsTransport;if(self.transceivers[sdpMLineIndex].rtpSender){self.transceivers[sdpMLineIndex].rtpSender.setTransport(self.transceivers[0].dtlsTransport);}
if(self.transceivers[sdpMLineIndex].rtpReceiver){self.transceivers[sdpMLineIndex].rtpReceiver.setTransport(self.transceivers[0].dtlsTransport);}}
if(description.type==='offer'&&!rejected){transceiver=self.transceivers[sdpMLineIndex]||self._createTransceiver(kind);transceiver.mid=mid;if(!transceiver.iceGatherer){transceiver.iceGatherer=usingBundle&&sdpMLineIndex>0?self.transceivers[0].iceGatherer:self._createIceGatherer(mid,sdpMLineIndex);}
if(isComplete&&(!usingBundle||sdpMLineIndex===0)){transceiver.iceTransport.setRemoteCandidates(cands);}
localCapabilities=window.RTCRtpReceiver.getCapabilities(kind);if(edgeVersion<15019){localCapabilities.codecs=localCapabilities.codecs.filter(function(codec){return codec.name!=='rtx';});}
sendEncodingParameters=[{ssrc:(2*sdpMLineIndex+2)*1001}];if(direction==='sendrecv'||direction==='sendonly'){rtpReceiver=new window.RTCRtpReceiver(transceiver.dtlsTransport,kind);track=rtpReceiver.track;if(remoteMsid){if(!streams[remoteMsid.stream]){streams[remoteMsid.stream]=new window.MediaStream();Object.defineProperty(streams[remoteMsid.stream],'id',{get:function(){return remoteMsid.stream;}});}
Object.defineProperty(track,'id',{get:function(){return remoteMsid.track;}});streams[remoteMsid.stream].addTrack(track);receiverList.push([track,rtpReceiver,streams[remoteMsid.stream]]);}else{if(!streams.default){streams.default=new window.MediaStream();}
streams.default.addTrack(track);receiverList.push([track,rtpReceiver,streams.default]);}}
transceiver.localCapabilities=localCapabilities;transceiver.remoteCapabilities=remoteCapabilities;transceiver.rtpReceiver=rtpReceiver;transceiver.rtcpParameters=rtcpParameters;transceiver.sendEncodingParameters=sendEncodingParameters;transceiver.recvEncodingParameters=recvEncodingParameters;self._transceive(self.transceivers[sdpMLineIndex],false,direction==='sendrecv'||direction==='sendonly');}else if(description.type==='answer'&&!rejected){transceiver=self.transceivers[sdpMLineIndex];iceGatherer=transceiver.iceGatherer;iceTransport=transceiver.iceTransport;dtlsTransport=transceiver.dtlsTransport;rtpReceiver=transceiver.rtpReceiver;sendEncodingParameters=transceiver.sendEncodingParameters;localCapabilities=transceiver.localCapabilities;self.transceivers[sdpMLineIndex].recvEncodingParameters=recvEncodingParameters;self.transceivers[sdpMLineIndex].remoteCapabilities=remoteCapabilities;self.transceivers[sdpMLineIndex].rtcpParameters=rtcpParameters;if((isIceLite||isComplete)&&cands.length){iceTransport.setRemoteCandidates(cands);}
if(!usingBundle||sdpMLineIndex===0){iceTransport.start(iceGatherer,remoteIceParameters,'controlling');dtlsTransport.start(remoteDtlsParameters);}
self._transceive(transceiver,direction==='sendrecv'||direction==='recvonly',direction==='sendrecv'||direction==='sendonly');if(rtpReceiver&&(direction==='sendrecv'||direction==='sendonly')){track=rtpReceiver.track;if(remoteMsid){if(!streams[remoteMsid.stream]){streams[remoteMsid.stream]=new window.MediaStream();}
streams[remoteMsid.stream].addTrack(track);receiverList.push([track,rtpReceiver,streams[remoteMsid.stream]]);}else{if(!streams.default){streams.default=new window.MediaStream();}
streams.default.addTrack(track);receiverList.push([track,rtpReceiver,streams.default]);}}else{delete transceiver.rtpReceiver;}}});this.remoteDescription={type:description.type,sdp:description.sdp};switch(description.type){case'offer':this._updateSignalingState('have-remote-offer');break;case'answer':this._updateSignalingState('stable');break;default:throw new TypeError('unsupported type "'+description.type+'"');}
Object.keys(streams).forEach(function(sid){var stream=streams[sid];if(stream.getTracks().length){self.remoteStreams.push(stream);var event=new Event('addstream');event.stream=stream;self.dispatchEvent(event);if(self.onaddstream!==null){window.setTimeout(function(){self.onaddstream(event);},0);}
receiverList.forEach(function(item){var track=item[0];var receiver=item[1];if(stream.id!==item[2].id){return;}
var trackEvent=new Event('track');trackEvent.track=track;trackEvent.receiver=receiver;trackEvent.streams=[stream];self.dispatchEvent(trackEvent);if(self.ontrack!==null){window.setTimeout(function(){self.ontrack(trackEvent);},0);}});}});window.setTimeout(function(){if(!(self&&self.transceivers)){return;}
self.transceivers.forEach(function(transceiver){if(transceiver.iceTransport&&transceiver.iceTransport.state==='new'&&transceiver.iceTransport.getRemoteCandidates().length>0){console.warn('Timeout for addRemoteCandidate. Consider sending '+'an end-of-candidates notification');transceiver.iceTransport.addRemoteCandidate({});}});},4000);if(arguments.length>1&&typeof arguments[1]==='function'){window.setTimeout(arguments[1],0);}
return Promise.resolve();};RTCPeerConnection.prototype.close=function(){this.transceivers.forEach(function(transceiver){if(transceiver.iceTransport){transceiver.iceTransport.stop();}
if(transceiver.dtlsTransport){transceiver.dtlsTransport.stop();}
if(transceiver.rtpSender){transceiver.rtpSender.stop();}
if(transceiver.rtpReceiver){transceiver.rtpReceiver.stop();}});this._updateSignalingState('closed');};RTCPeerConnection.prototype._updateSignalingState=function(newState){this.signalingState=newState;var event=new Event('signalingstatechange');this.dispatchEvent(event);if(this.onsignalingstatechange!==null){this.onsignalingstatechange(event);}};RTCPeerConnection.prototype._maybeFireNegotiationNeeded=function(){var self=this;if(this.signalingState!=='stable'||this.needNegotiation===true){return;}
this.needNegotiation=true;window.setTimeout(function(){if(self.needNegotiation===false){return;}
self.needNegotiation=false;var event=new Event('negotiationneeded');self.dispatchEvent(event);if(self.onnegotiationneeded!==null){self.onnegotiationneeded(event);}},0);};RTCPeerConnection.prototype._updateConnectionState=function(){var self=this;var newState;var states={'new':0,closed:0,connecting:0,checking:0,connected:0,completed:0,disconnected:0,failed:0};this.transceivers.forEach(function(transceiver){states[transceiver.iceTransport.state]++;states[transceiver.dtlsTransport.state]++;});states.connected+=states.completed;newState='new';if(states.failed>0){newState='failed';}else if(states.connecting>0||states.checking>0){newState='connecting';}else if(states.disconnected>0){newState='disconnected';}else if(states.new>0){newState='new';}else if(states.connected>0||states.completed>0){newState='connected';}
if(newState!==self.iceConnectionState){self.iceConnectionState=newState;var event=new Event('iceconnectionstatechange');this.dispatchEvent(event);if(this.oniceconnectionstatechange!==null){this.oniceconnectionstatechange(event);}}};RTCPeerConnection.prototype.createOffer=function(){var self=this;if(this._pendingOffer){throw new Error('createOffer called while there is a pending offer.');}
var offerOptions;if(arguments.length===1&&typeof arguments[0]!=='function'){offerOptions=arguments[0];}else if(arguments.length===3){offerOptions=arguments[2];}
var numAudioTracks=this.transceivers.filter(function(t){return t.kind==='audio';}).length;var numVideoTracks=this.transceivers.filter(function(t){return t.kind==='video';}).length;if(offerOptions){if(offerOptions.mandatory||offerOptions.optional){throw new TypeError('Legacy mandatory/optional constraints not supported.');}
if(offerOptions.offerToReceiveAudio!==undefined){if(offerOptions.offerToReceiveAudio===true){numAudioTracks=1;}else if(offerOptions.offerToReceiveAudio===false){numAudioTracks=0;}else{numAudioTracks=offerOptions.offerToReceiveAudio;}}
if(offerOptions.offerToReceiveVideo!==undefined){if(offerOptions.offerToReceiveVideo===true){numVideoTracks=1;}else if(offerOptions.offerToReceiveVideo===false){numVideoTracks=0;}else{numVideoTracks=offerOptions.offerToReceiveVideo;}}}
this.transceivers.forEach(function(transceiver){if(transceiver.kind==='audio'){numAudioTracks--;if(numAudioTracks<0){transceiver.wantReceive=false;}}else if(transceiver.kind==='video'){numVideoTracks--;if(numVideoTracks<0){transceiver.wantReceive=false;}}});while(numAudioTracks>0||numVideoTracks>0){if(numAudioTracks>0){this._createTransceiver('audio');numAudioTracks--;}
if(numVideoTracks>0){this._createTransceiver('video');numVideoTracks--;}}
var transceivers=sortTracks(this.transceivers);var sdp=SDPUtils.writeSessionBoilerplate(this._sdpSessionId);transceivers.forEach(function(transceiver,sdpMLineIndex){var track=transceiver.track;var kind=transceiver.kind;var mid=SDPUtils.generateIdentifier();transceiver.mid=mid;if(!transceiver.iceGatherer){transceiver.iceGatherer=self.usingBundle&&sdpMLineIndex>0?transceivers[0].iceGatherer:self._createIceGatherer(mid,sdpMLineIndex);}
var localCapabilities=window.RTCRtpSender.getCapabilities(kind);if(edgeVersion<15019){localCapabilities.codecs=localCapabilities.codecs.filter(function(codec){return codec.name!=='rtx';});}
localCapabilities.codecs.forEach(function(codec){if(codec.name==='H264'&&codec.parameters['level-asymmetry-allowed']===undefined){codec.parameters['level-asymmetry-allowed']='1';}});var sendEncodingParameters=[{ssrc:(2*sdpMLineIndex+1)*1001}];if(track){if(edgeVersion>=15019&&kind==='video'){sendEncodingParameters[0].rtx={ssrc:(2*sdpMLineIndex+1)*1001+1};}}
if(transceiver.wantReceive){transceiver.rtpReceiver=new window.RTCRtpReceiver(transceiver.dtlsTransport,kind);}
transceiver.localCapabilities=localCapabilities;transceiver.sendEncodingParameters=sendEncodingParameters;});if(this._config.bundlePolicy!=='max-compat'){sdp+='a=group:BUNDLE '+transceivers.map(function(t){return t.mid;}).join(' ')+'\r\n';}
sdp+='a=ice-options:trickle\r\n';transceivers.forEach(function(transceiver,sdpMLineIndex){sdp+=SDPUtils.writeMediaSection(transceiver,transceiver.localCapabilities,'offer',transceiver.stream);sdp+='a=rtcp-rsize\r\n';});this._pendingOffer=transceivers;var desc=new window.RTCSessionDescription({type:'offer',sdp:sdp});if(arguments.length&&typeof arguments[0]==='function'){window.setTimeout(arguments[0],0,desc);}
return Promise.resolve(desc);};RTCPeerConnection.prototype.createAnswer=function(){var sdp=SDPUtils.writeSessionBoilerplate(this._sdpSessionId);if(this.usingBundle){sdp+='a=group:BUNDLE '+this.transceivers.map(function(t){return t.mid;}).join(' ')+'\r\n';}
this.transceivers.forEach(function(transceiver,sdpMLineIndex){if(transceiver.isDatachannel){sdp+='m=application 0 DTLS/SCTP 5000\r\n'+'c=IN IP4 0.0.0.0\r\n'+'a=mid:'+transceiver.mid+'\r\n';return;}
if(transceiver.stream){var localTrack;if(transceiver.kind==='audio'){localTrack=transceiver.stream.getAudioTracks()[0];}else if(transceiver.kind==='video'){localTrack=transceiver.stream.getVideoTracks()[0];}
if(localTrack){if(edgeVersion>=15019&&transceiver.kind==='video'){transceiver.sendEncodingParameters[0].rtx={ssrc:(2*sdpMLineIndex+2)*1001+1};}}}
var commonCapabilities=getCommonCapabilities(transceiver.localCapabilities,transceiver.remoteCapabilities);var hasRtx=commonCapabilities.codecs.filter(function(c){return c.name.toLowerCase()==='rtx';}).length;if(!hasRtx&&transceiver.sendEncodingParameters[0].rtx){delete transceiver.sendEncodingParameters[0].rtx;}
sdp+=SDPUtils.writeMediaSection(transceiver,commonCapabilities,'answer',transceiver.stream);if(transceiver.rtcpParameters&&transceiver.rtcpParameters.reducedSize){sdp+='a=rtcp-rsize\r\n';}});var desc=new window.RTCSessionDescription({type:'answer',sdp:sdp});if(arguments.length&&typeof arguments[0]==='function'){window.setTimeout(arguments[0],0,desc);}
return Promise.resolve(desc);};RTCPeerConnection.prototype.addIceCandidate=function(candidate){if(!candidate){for(var j=0;j<this.transceivers.length;j++){this.transceivers[j].iceTransport.addRemoteCandidate({});if(this.usingBundle){return Promise.resolve();}}}else{var mLineIndex=candidate.sdpMLineIndex;if(candidate.sdpMid){for(var i=0;i<this.transceivers.length;i++){if(this.transceivers[i].mid===candidate.sdpMid){mLineIndex=i;break;}}}
var transceiver=this.transceivers[mLineIndex];if(transceiver){var cand=Object.keys(candidate.candidate).length>0?SDPUtils.parseCandidate(candidate.candidate):{};if(cand.protocol==='tcp'&&(cand.port===0||cand.port===9)){return Promise.resolve();}
if(cand.component&&!(cand.component==='1'||cand.component===1)){return Promise.resolve();}
transceiver.iceTransport.addRemoteCandidate(cand);var sections=SDPUtils.splitSections(this.remoteDescription.sdp);sections[mLineIndex+1]+=(cand.type?candidate.candidate.trim():'a=end-of-candidates')+'\r\n';this.remoteDescription.sdp=sections.join('');}}
if(arguments.length>1&&typeof arguments[1]==='function'){window.setTimeout(arguments[1],0);}
return Promise.resolve();};RTCPeerConnection.prototype.getStats=function(){var promises=[];this.transceivers.forEach(function(transceiver){['rtpSender','rtpReceiver','iceGatherer','iceTransport','dtlsTransport'].forEach(function(method){if(transceiver[method]){promises.push(transceiver[method].getStats());}});});var cb=arguments.length>1&&typeof arguments[1]==='function'&&arguments[1];var fixStatsType=function(stat){return{inboundrtp:'inbound-rtp',outboundrtp:'outbound-rtp',candidatepair:'candidate-pair',localcandidate:'local-candidate',remotecandidate:'remote-candidate'}[stat.type]||stat.type;};return new Promise(function(resolve){var results=new Map();Promise.all(promises).then(function(res){res.forEach(function(result){Object.keys(result).forEach(function(id){result[id].type=fixStatsType(result[id]);results.set(id,result[id]);});});if(cb){window.setTimeout(cb,0,results);}
resolve(results);});});};return RTCPeerConnection;};},{"sdp":1}],9:[function(require,module,exports){'use strict';var utils=require('../utils');var firefoxShim={shimOnTrack:function(window){if(typeof window==='object'&&window.RTCPeerConnection&&!('ontrack'in
window.RTCPeerConnection.prototype)){Object.defineProperty(window.RTCPeerConnection.prototype,'ontrack',{get:function(){return this._ontrack;},set:function(f){if(this._ontrack){this.removeEventListener('track',this._ontrack);this.removeEventListener('addstream',this._ontrackpoly);}
this.addEventListener('track',this._ontrack=f);this.addEventListener('addstream',this._ontrackpoly=function(e){e.stream.getTracks().forEach(function(track){var event=new Event('track');event.track=track;event.receiver={track:track};event.streams=[e.stream];this.dispatchEvent(event);}.bind(this));}.bind(this));}});}},shimSourceObject:function(window){if(typeof window==='object'){if(window.HTMLMediaElement&&!('srcObject'in window.HTMLMediaElement.prototype)){Object.defineProperty(window.HTMLMediaElement.prototype,'srcObject',{get:function(){return this.mozSrcObject;},set:function(stream){this.mozSrcObject=stream;}});}}},shimPeerConnection:function(window){var browserDetails=utils.detectBrowser(window);if(typeof window!=='object'||!(window.RTCPeerConnection||window.mozRTCPeerConnection)){return;}
if(!window.RTCPeerConnection){window.RTCPeerConnection=function(pcConfig,pcConstraints){if(browserDetails.version<38){if(pcConfig&&pcConfig.iceServers){var newIceServers=[];for(var i=0;i<pcConfig.iceServers.length;i++){var server=pcConfig.iceServers[i];if(server.hasOwnProperty('urls')){for(var j=0;j<server.urls.length;j++){var newServer={url:server.urls[j]};if(server.urls[j].indexOf('turn')===0){newServer.username=server.username;newServer.credential=server.credential;}
newIceServers.push(newServer);}}else{newIceServers.push(pcConfig.iceServers[i]);}}
pcConfig.iceServers=newIceServers;}}
return new window.mozRTCPeerConnection(pcConfig,pcConstraints);};window.RTCPeerConnection.prototype=window.mozRTCPeerConnection.prototype;if(window.mozRTCPeerConnection.generateCertificate){Object.defineProperty(window.RTCPeerConnection,'generateCertificate',{get:function(){return window.mozRTCPeerConnection.generateCertificate;}});}
window.RTCSessionDescription=window.mozRTCSessionDescription;window.RTCIceCandidate=window.mozRTCIceCandidate;}
['setLocalDescription','setRemoteDescription','addIceCandidate'].forEach(function(method){var nativeMethod=window.RTCPeerConnection.prototype[method];window.RTCPeerConnection.prototype[method]=function(){arguments[0]=new((method==='addIceCandidate')?window.RTCIceCandidate:window.RTCSessionDescription)(arguments[0]);return nativeMethod.apply(this,arguments);};});var nativeAddIceCandidate=window.RTCPeerConnection.prototype.addIceCandidate;window.RTCPeerConnection.prototype.addIceCandidate=function(){if(!arguments[0]){if(arguments[1]){arguments[1].apply(null);}
return Promise.resolve();}
return nativeAddIceCandidate.apply(this,arguments);};var makeMapStats=function(stats){var map=new Map();Object.keys(stats).forEach(function(key){map.set(key,stats[key]);map[key]=stats[key];});return map;};var modernStatsTypes={inboundrtp:'inbound-rtp',outboundrtp:'outbound-rtp',candidatepair:'candidate-pair',localcandidate:'local-candidate',remotecandidate:'remote-candidate'};var nativeGetStats=window.RTCPeerConnection.prototype.getStats;window.RTCPeerConnection.prototype.getStats=function(selector,onSucc,onErr){return nativeGetStats.apply(this,[selector||null]).then(function(stats){if(browserDetails.version<48){stats=makeMapStats(stats);}
if(browserDetails.version<53&&!onSucc){try{stats.forEach(function(stat){stat.type=modernStatsTypes[stat.type]||stat.type;});}catch(e){if(e.name!=='TypeError'){throw e;}
stats.forEach(function(stat,i){stats.set(i,Object.assign({},stat,{type:modernStatsTypes[stat.type]||stat.type}));});}}
return stats;}).then(onSucc,onErr);};}};module.exports={shimOnTrack:firefoxShim.shimOnTrack,shimSourceObject:firefoxShim.shimSourceObject,shimPeerConnection:firefoxShim.shimPeerConnection,shimGetUserMedia:require('./getusermedia')};},{"../utils":12,"./getusermedia":10}],10:[function(require,module,exports){'use strict';var utils=require('../utils');var logging=utils.log;module.exports=function(window){var browserDetails=utils.detectBrowser(window);var navigator=window&&window.navigator;var MediaStreamTrack=window&&window.MediaStreamTrack;var shimError_=function(e){return{name:{InternalError:'NotReadableError',NotSupportedError:'TypeError',PermissionDeniedError:'NotAllowedError',SecurityError:'NotAllowedError'}[e.name]||e.name,message:{'The operation is insecure.':'The request is not allowed by the '+'user agent or the platform in the current context.'}[e.message]||e.message,constraint:e.constraint,toString:function(){return this.name+(this.message&&': ')+this.message;}};};var getUserMedia_=function(constraints,onSuccess,onError){var constraintsToFF37_=function(c){if(typeof c!=='object'||c.require){return c;}
var require=[];Object.keys(c).forEach(function(key){if(key==='require'||key==='advanced'||key==='mediaSource'){return;}
var r=c[key]=(typeof c[key]==='object')?c[key]:{ideal:c[key]};if(r.min!==undefined||r.max!==undefined||r.exact!==undefined){require.push(key);}
if(r.exact!==undefined){if(typeof r.exact==='number'){r.min=r.max=r.exact;}else{c[key]=r.exact;}
delete r.exact;}
if(r.ideal!==undefined){c.advanced=c.advanced||[];var oc={};if(typeof r.ideal==='number'){oc[key]={min:r.ideal,max:r.ideal};}else{oc[key]=r.ideal;}
c.advanced.push(oc);delete r.ideal;if(!Object.keys(r).length){delete c[key];}}});if(require.length){c.require=require;}
return c;};constraints=JSON.parse(JSON.stringify(constraints));if(browserDetails.version<38){logging('spec: '+JSON.stringify(constraints));if(constraints.audio){constraints.audio=constraintsToFF37_(constraints.audio);}
if(constraints.video){constraints.video=constraintsToFF37_(constraints.video);}
logging('ff37: '+JSON.stringify(constraints));}
return navigator.mozGetUserMedia(constraints,onSuccess,function(e){onError(shimError_(e));});};var getUserMediaPromise_=function(constraints){return new Promise(function(resolve,reject){getUserMedia_(constraints,resolve,reject);});};if(!navigator.mediaDevices){navigator.mediaDevices={getUserMedia:getUserMediaPromise_,addEventListener:function(){},removeEventListener:function(){}};}
navigator.mediaDevices.enumerateDevices=navigator.mediaDevices.enumerateDevices||function(){return new Promise(function(resolve){var infos=[{kind:'audioinput',deviceId:'default',label:'',groupId:''},{kind:'videoinput',deviceId:'default',label:'',groupId:''}];resolve(infos);});};if(browserDetails.version<41){var orgEnumerateDevices=navigator.mediaDevices.enumerateDevices.bind(navigator.mediaDevices);navigator.mediaDevices.enumerateDevices=function(){return orgEnumerateDevices().then(undefined,function(e){if(e.name==='NotFoundError'){return[];}
throw e;});};}
if(browserDetails.version<49){var origGetUserMedia=navigator.mediaDevices.getUserMedia.bind(navigator.mediaDevices);navigator.mediaDevices.getUserMedia=function(c){return origGetUserMedia(c).then(function(stream){if(c.audio&&!stream.getAudioTracks().length||c.video&&!stream.getVideoTracks().length){stream.getTracks().forEach(function(track){track.stop();});throw new DOMException('The object can not be found here.','NotFoundError');}
return stream;},function(e){return Promise.reject(shimError_(e));});};}
if(!(browserDetails.version>55&&'autoGainControl'in navigator.mediaDevices.getSupportedConstraints())){var remap=function(obj,a,b){if(a in obj&&!(b in obj)){obj[b]=obj[a];delete obj[a];}};var nativeGetUserMedia=navigator.mediaDevices.getUserMedia.bind(navigator.mediaDevices);navigator.mediaDevices.getUserMedia=function(c){if(typeof c==='object'&&typeof c.audio==='object'){c=JSON.parse(JSON.stringify(c));remap(c.audio,'autoGainControl','mozAutoGainControl');remap(c.audio,'noiseSuppression','mozNoiseSuppression');}
return nativeGetUserMedia(c);};if(MediaStreamTrack&&MediaStreamTrack.prototype.getSettings){var nativeGetSettings=MediaStreamTrack.prototype.getSettings;MediaStreamTrack.prototype.getSettings=function(){var obj=nativeGetSettings.apply(this,arguments);remap(obj,'mozAutoGainControl','autoGainControl');remap(obj,'mozNoiseSuppression','noiseSuppression');return obj;};}
if(MediaStreamTrack&&MediaStreamTrack.prototype.applyConstraints){var nativeApplyConstraints=MediaStreamTrack.prototype.applyConstraints;MediaStreamTrack.prototype.applyConstraints=function(c){if(this.kind==='audio'&&typeof c==='object'){c=JSON.parse(JSON.stringify(c));remap(c,'autoGainControl','mozAutoGainControl');remap(c,'noiseSuppression','mozNoiseSuppression');}
return nativeApplyConstraints.apply(this,[c]);};}}
navigator.getUserMedia=function(constraints,onSuccess,onError){if(browserDetails.version<44){return getUserMedia_(constraints,onSuccess,onError);}
console.warn('navigator.getUserMedia has been replaced by '+'navigator.mediaDevices.getUserMedia');navigator.mediaDevices.getUserMedia(constraints).then(onSuccess,onError);};};},{"../utils":12}],11:[function(require,module,exports){'use strict';var utils=require('../utils');var safariShim={shimLocalStreamsAPI:function(window){if(typeof window!=='object'||!window.RTCPeerConnection){return;}
if(!('getLocalStreams'in window.RTCPeerConnection.prototype)){window.RTCPeerConnection.prototype.getLocalStreams=function(){if(!this._localStreams){this._localStreams=[];}
return this._localStreams;};}
if(!('getStreamById'in window.RTCPeerConnection.prototype)){window.RTCPeerConnection.prototype.getStreamById=function(id){var result=null;if(this._localStreams){this._localStreams.forEach(function(stream){if(stream.id===id){result=stream;}});}
if(this._remoteStreams){this._remoteStreams.forEach(function(stream){if(stream.id===id){result=stream;}});}
return result;};}
if(!('addStream'in window.RTCPeerConnection.prototype)){var _addTrack=window.RTCPeerConnection.prototype.addTrack;window.RTCPeerConnection.prototype.addStream=function(stream){if(!this._localStreams){this._localStreams=[];}
if(this._localStreams.indexOf(stream)===-1){this._localStreams.push(stream);}
var self=this;stream.getTracks().forEach(function(track){_addTrack.call(self,track,stream);});};window.RTCPeerConnection.prototype.addTrack=function(track,stream){if(stream){if(!this._localStreams){this._localStreams=[stream];}else if(this._localStreams.indexOf(stream)===-1){this._localStreams.push(stream);}}
_addTrack.call(this,track,stream);};}
if(!('removeStream'in window.RTCPeerConnection.prototype)){window.RTCPeerConnection.prototype.removeStream=function(stream){if(!this._localStreams){this._localStreams=[];}
var index=this._localStreams.indexOf(stream);if(index===-1){return;}
this._localStreams.splice(index,1);var self=this;var tracks=stream.getTracks();this.getSenders().forEach(function(sender){if(tracks.indexOf(sender.track)!==-1){self.removeTrack(sender);}});};}},shimRemoteStreamsAPI:function(window){if(typeof window!=='object'||!window.RTCPeerConnection){return;}
if(!('getRemoteStreams'in window.RTCPeerConnection.prototype)){window.RTCPeerConnection.prototype.getRemoteStreams=function(){return this._remoteStreams?this._remoteStreams:[];};}
if(!('onaddstream'in window.RTCPeerConnection.prototype)){Object.defineProperty(window.RTCPeerConnection.prototype,'onaddstream',{get:function(){return this._onaddstream;},set:function(f){if(this._onaddstream){this.removeEventListener('addstream',this._onaddstream);this.removeEventListener('track',this._onaddstreampoly);}
this.addEventListener('addstream',this._onaddstream=f);this.addEventListener('track',this._onaddstreampoly=function(e){var stream=e.streams[0];if(!this._remoteStreams){this._remoteStreams=[];}
if(this._remoteStreams.indexOf(stream)>=0){return;}
this._remoteStreams.push(stream);var event=new Event('addstream');event.stream=e.streams[0];this.dispatchEvent(event);}.bind(this));}});}},shimCallbacksAPI:function(window){if(typeof window!=='object'||!window.RTCPeerConnection){return;}
var prototype=window.RTCPeerConnection.prototype;var createOffer=prototype.createOffer;var createAnswer=prototype.createAnswer;var setLocalDescription=prototype.setLocalDescription;var setRemoteDescription=prototype.setRemoteDescription;var addIceCandidate=prototype.addIceCandidate;prototype.createOffer=function(successCallback,failureCallback){var options=(arguments.length>=2)?arguments[2]:arguments[0];var promise=createOffer.apply(this,[options]);if(!failureCallback){return promise;}
promise.then(successCallback,failureCallback);return Promise.resolve();};prototype.createAnswer=function(successCallback,failureCallback){var options=(arguments.length>=2)?arguments[2]:arguments[0];var promise=createAnswer.apply(this,[options]);if(!failureCallback){return promise;}
promise.then(successCallback,failureCallback);return Promise.resolve();};var withCallback=function(description,successCallback,failureCallback){var promise=setLocalDescription.apply(this,[description]);if(!failureCallback){return promise;}
promise.then(successCallback,failureCallback);return Promise.resolve();};prototype.setLocalDescription=withCallback;withCallback=function(description,successCallback,failureCallback){var promise=setRemoteDescription.apply(this,[description]);if(!failureCallback){return promise;}
promise.then(successCallback,failureCallback);return Promise.resolve();};prototype.setRemoteDescription=withCallback;withCallback=function(candidate,successCallback,failureCallback){var promise=addIceCandidate.apply(this,[candidate]);if(!failureCallback){return promise;}
promise.then(successCallback,failureCallback);return Promise.resolve();};prototype.addIceCandidate=withCallback;},shimGetUserMedia:function(window){var navigator=window&&window.navigator;if(!navigator.getUserMedia){if(navigator.webkitGetUserMedia){navigator.getUserMedia=navigator.webkitGetUserMedia.bind(navigator);}else if(navigator.mediaDevices&&navigator.mediaDevices.getUserMedia){navigator.getUserMedia=function(constraints,cb,errcb){navigator.mediaDevices.getUserMedia(constraints).then(cb,errcb);}.bind(navigator);}}},shimRTCIceServerUrls:function(window){var OrigPeerConnection=window.RTCPeerConnection;window.RTCPeerConnection=function(pcConfig,pcConstraints){if(pcConfig&&pcConfig.iceServers){var newIceServers=[];for(var i=0;i<pcConfig.iceServers.length;i++){var server=pcConfig.iceServers[i];if(!server.hasOwnProperty('urls')&&server.hasOwnProperty('url')){utils.deprecated('RTCIceServer.url','RTCIceServer.urls');server=JSON.parse(JSON.stringify(server));server.urls=server.url;delete server.url;newIceServers.push(server);}else{newIceServers.push(pcConfig.iceServers[i]);}}
pcConfig.iceServers=newIceServers;}
return new OrigPeerConnection(pcConfig,pcConstraints);};window.RTCPeerConnection.prototype=OrigPeerConnection.prototype;Object.defineProperty(window.RTCPeerConnection,'generateCertificate',{get:function(){return OrigPeerConnection.generateCertificate;}});}};module.exports={shimCallbacksAPI:safariShim.shimCallbacksAPI,shimLocalStreamsAPI:safariShim.shimLocalStreamsAPI,shimRemoteStreamsAPI:safariShim.shimRemoteStreamsAPI,shimGetUserMedia:safariShim.shimGetUserMedia,shimRTCIceServerUrls:safariShim.shimRTCIceServerUrls};},{"../utils":12}],12:[function(require,module,exports){'use strict';var logDisabled_=true;var deprecationWarnings_=true;var utils={disableLog:function(bool){if(typeof bool!=='boolean'){return new Error('Argument type: '+typeof bool+'. Please use a boolean.');}
logDisabled_=bool;return(bool)?'adapter.js logging disabled':'adapter.js logging enabled';},disableWarnings:function(bool){if(typeof bool!=='boolean'){return new Error('Argument type: '+typeof bool+'. Please use a boolean.');}
deprecationWarnings_=!bool;return'adapter.js deprecation warnings '+(bool?'disabled':'enabled');},log:function(){if(typeof window==='object'){if(logDisabled_){return;}
if(typeof console!=='undefined'&&typeof console.log==='function'){console.log.apply(console,arguments);}}},deprecated:function(oldMethod,newMethod){if(!deprecationWarnings_){return;}
console.warn(oldMethod+' is deprecated, please use '+newMethod+' instead.');},extractVersion:function(uastring,expr,pos){var match=uastring.match(expr);return match&&match.length>=pos&&parseInt(match[pos],10);},detectBrowser:function(window){var navigator=window&&window.navigator;var result={};result.browser=null;result.version=null;if(typeof window==='undefined'||!window.navigator){result.browser='Not a browser.';return result;}
if(navigator.mozGetUserMedia){result.browser='firefox';result.version=this.extractVersion(navigator.userAgent,/Firefox\/(\d+)\./,1);}else if(navigator.webkitGetUserMedia){if(window.webkitRTCPeerConnection){result.browser='chrome';result.version=this.extractVersion(navigator.userAgent,/Chrom(e|ium)\/(\d+)\./,2);}else{if(navigator.userAgent.match(/Version\/(\d+).(\d+)/)){result.browser='safari';result.version=this.extractVersion(navigator.userAgent,/AppleWebKit\/(\d+)\./,1);}else{result.browser='Unsupported webkit-based browser '+'with GUM support but no WebRTC support.';return result;}}}else if(navigator.mediaDevices&&navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)){result.browser='edge';result.version=this.extractVersion(navigator.userAgent,/Edge\/(\d+).(\d+)$/,2);}else if(navigator.mediaDevices&&navigator.userAgent.match(/AppleWebKit\/(\d+)\./)){result.browser='safari';result.version=this.extractVersion(navigator.userAgent,/AppleWebKit\/(\d+)\./,1);}else{result.browser='Not a supported browser.';return result;}
return result;},shimCreateObjectURL:function(window){var URL=window&&window.URL;if(!(typeof window==='object'&&window.HTMLMediaElement&&'srcObject'in window.HTMLMediaElement.prototype)){return undefined;}
var nativeCreateObjectURL=URL.createObjectURL.bind(URL);var nativeRevokeObjectURL=URL.revokeObjectURL.bind(URL);var streams=new Map(),newId=0;URL.createObjectURL=function(stream){if('getTracks'in stream){var url='polyblob:'+(++newId);streams.set(url,stream);utils.deprecated('URL.createObjectURL(stream)','elem.srcObject = stream');return url;}
return nativeCreateObjectURL(stream);};URL.revokeObjectURL=function(url){nativeRevokeObjectURL(url);streams.delete(url);};var dsc=Object.getOwnPropertyDescriptor(window.HTMLMediaElement.prototype,'src');Object.defineProperty(window.HTMLMediaElement.prototype,'src',{get:function(){return dsc.get.apply(this);},set:function(url){this.srcObject=streams.get(url)||null;return dsc.set.apply(this,[url]);}});var nativeSetAttribute=window.HTMLMediaElement.prototype.setAttribute;window.HTMLMediaElement.prototype.setAttribute=function(){if(arguments.length===2&&(''+arguments[0]).toLowerCase()==='src'){this.srcObject=streams.get(arguments[1])||null;}
return nativeSetAttribute.apply(this,arguments);};}};module.exports={log:utils.log,deprecated:utils.deprecated,disableLog:utils.disableLog,disableWarnings:utils.disableWarnings,extractVersion:utils.extractVersion,shimCreateObjectURL:utils.shimCreateObjectURL,detectBrowser:utils.detectBrowser.bind(utils)};},{}]},{},[2])(2)});

View File

@ -1,706 +0,0 @@
Verto = function (
tag,
voiceBridge,
conferenceUsername,
userCallback,
onFail = null,
chromeExtension = null,
stunTurnInfo = null,
loadingCallback = null) {
voiceBridge += "-SCREENSHARE";
this.cur_call = null;
this.share_call = null;
this.vertoHandle;
this.vid_width = Math.max(window.screen.width, 1920);
this.vid_height = Math.max(window.screen.height, 1080);
this.outgoingBandwidth = "default";
this.incomingBandwidth = "default";
// this.sessid = $.verto.genUUID();
this.sessid = Math.random().toString();
this.renderTag = 'remote-media';
this.destination_number = voiceBridge;
this.caller_id_name = conferenceUsername;
this.caller_id_number = conferenceUsername;
this.vertoPort = "verto";
this.hostName = window.location.hostname;
this.socketUrl = 'wss://' + this.hostName + '/' + this.vertoPort;
this.login = "bbbuser";
this.password = "secret";
this.useVideo = false;
this.useCamera = false;
this.useMic = false;
this.callWasSuccessful = false;
this.shouldConnect = true;
this.iceServers = stunTurnInfo;
this.userCallback = userCallback;
if (loadingCallback != null) {
this.videoLoadingCallback = Verto.normalizeCallback(loadingCallback);
} else {
this.videoLoadingCallback = function() {};
}
if (chromeExtension != null) {
this.chromeExtension = chromeExtension;
}
if (onFail != null) {
this.onFail = Verto.normalizeCallback(onFail);
} else {
var _this = this;
this.onFail = function () {
_this.logError('Default error handler');
};
}
};
Verto.prototype.logger = function (obj) {
console.log(obj);
};
Verto.prototype.logError = function (obj) {
console.error(obj);
};
Verto.prototype.setRenderTag = function (tag) {
this.renderTag = tag;
};
// 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
Verto.normalizeCallback = function (callback) {
if (typeof callback == 'function') {
return callback;
} else {
return function (args) {
document.getElementById('BigBlueButton')[callback](args);
};
}
};
Verto.prototype.onWSLogin = function (v, success) {
this.cur_call = null;
if (success) {
if (!this.shouldConnect) {
return;
}
this.callWasSuccessful = true;
this.mediaCallback();
return;
} else {
// error logging verto into freeswitch
this.logError({ status: 'failed', errorcode: '10XX' });
this.callWasSuccessful = false;
this.onFail();
return;
}
};
Verto.prototype.registerCallbacks = function () {
var callbacks = {
onMessage: function (verto, dialog, msg, data) {
switch (msg) {
case $.verto.enum.message.pvtEvent:
if (data.pvtData) {
switch (data.pvtData.action) {
// This client has joined the live array for the conference.
case "conference-liveArray-join":
initLiveArray(verto, dialog, data);
break;
// This client has left the live array for the conference.
case "conference-liveArray-part":
// Some kind of client-side wrapup...
break;
}
}
break;
}
},
onDialogState: function (d) {},
onWSLogin: this.onWSLogin.bind(this),
onWSClose: function (v, success) {
cur_call = null;
if (this.callWasSuccessful) {
// the connection was dropped in an already established call
this.logError('websocket disconnected');
// WebSocket disconnected
this.logError({ status: 'failed', errorcode: 1001 });
toDisplayDisconnectCallback = false;
} else {
// this callback was triggered and a call was never successfully established
this.logError('websocket connection could not be established');
// Could not make a WebSocket connection
this.logError({ status: 'failed', errorcode: 1002 });
this.onFail();
return;
}
}.bind(this),
};
this.callbacks = callbacks;
};
var initLiveArray = function(verto, dialog, data) {
// Set up addtional configuration specific to the call.
window.vertoConf = new $.verto.conf(verto, {
dialog: dialog,
hasVid: true,
laData: data.pvtData,
// For subscribing to published chat messages.
chatCallback: function(verto, eventObj) {
var from = eventObj.data.fromDisplay || eventObj.data.from || 'Unknown';
var message = eventObj.data.message || '';
},
});
var config = {
subParams: {
callID: dialog ? dialog.callID : null
},
};
// Set up the live array, using the live array data received from FreeSWITCH.
window.liveArray = new $.verto.liveArray(window.vertoHandle, data.pvtData.laChannel, data.pvtData.laName, config);
// Subscribe to live array changes.
window.liveArray.onChange = function(liveArrayObj, args) {
console.log("Call UUID is: " + args.key);
console.log("Call data is: ", args.data);
console.log(liveArrayObj);
console.log(args);
try {
switch (args.action) {
// Initial list of existing conference users.
case "bootObj":
break;
// New user joined conference.
case "add":
break;
// User left conference.
case "del":
break;
// Existing user's state changed (mute/unmute, talking, floor, etc)
case "modify":
break;
}
} catch (err) {
console.error("ERROR: " + err);
}
};
// Called if the live array throws an error.
window.liveArray.onErr = function (obj, args) {
console.error("Error: ", obj, args);
};
};
Verto.prototype.hold = function () {
this.cur_call.toggleHold();
};
Verto.prototype.hangup = function () {
if (this.cur_call) {
// the duration of the call
if (this.cur_call.audioStream) {
this.logger('call ended ' + this.cur_call.audioStream.currentTime);
}
this.cur_call.hangup();
this.cur_call = null;
}
if (this.share_call) {
if (this.share_call.state == $.verto.enum.state.active) {
this.shouldConnect = false;
} else {
this.shouldConnect = true;
}
// the duration of the call
this.logger('call ended ' + this.share_call.audioStream.currentTime);
this.share_call.rtc.localStream.getTracks().forEach(track => track.stop());
this.share_call.hangup();
this.share_call = null;
}
// the user ended the call themself
// if (callPurposefullyEnded === true) {
if (true) {
this.logger({ status: 'ended' });
} else {
// Call ended unexpectedly
this.logError({ status: 'failed', errorcode: 1005 });
}
};
Verto.prototype.mute = function () {
this.cur_call.dtmf('0');
};
Verto.prototype.localmute = function () {
// var muted = cur_call.setMute('toggle');
// if (muted) {
// display('Talking to: ' + cur_call.cidString() + ' [LOCALLY MUTED]');
// } else {
// display('Talking to: ' + cur_call.cidString());
// }
};
Verto.prototype.localvidmute = function () {
// var muted = cur_call.setVideoMute('toggle');
// if (muted) {
// display('Talking to: ' + cur_call.cidString() + ' [VIDEO LOCALLY MUTED]');
// } else {
// display('Talking to: ' + cur_call.cidString());
// }
};
Verto.prototype.vmute = function () {
this.cur_call.dtmf('*0');
};
Verto.prototype.setWatchVideo = function (tag) {
this.mediaCallback = this.docall;
this.useVideo = true;
this.useCamera = 'none';
this.useMic = 'none';
this.create(tag);
};
Verto.prototype.setListenOnly = function (tag) {
this.mediaCallback = this.docall;
this.useVideo = false;
this.useCamera = 'none';
this.useMic = 'none';
this.create(tag);
};
Verto.prototype.setMicrophone = function (tag) {
this.mediaCallback = this.docall;
this.useVideo = false;
this.useCamera = 'none';
this.useMic = 'any';
this.create(tag);
};
Verto.prototype.setScreenShare = function (tag) {
// required for Verto to know we want to use video
// tell Verto we want to share webcam so it knows there will be a video stream
// but instead of a webcam we pass screen constraints
this.useCamera = 'any';
this.useMic = 'none';
this.mediaCallback = this.makeShare;
this.create(tag);
};
Verto.prototype.create = function (tag) {
this.setRenderTag(tag);
this.registerCallbacks();
// fetch ice information from server
if (this.iceServers == null) {
this.configStuns(this.init);
} else {
// already have it. proceed with init
this.init();
}
};
Verto.prototype.docall = function () {
if (this.cur_call) {
this.logger('Quitting: Call already in progress');
return;
}
this.shouldConnect = true;
this.cur_call = window.vertoHandle.newCall({
destination_number: this.destination_number,
caller_id_name: this.caller_id_name,
caller_id_number: this.caller_id_number,
outgoingBandwidth: this.outgoingBandwidth,
incomingBandwidth: this.incomingBandwidth,
useVideo: this.useVideo,
useStereo: true,
useCamera: this.useCamera,
useMic: this.useMic,
useSpeak: 'any',
dedEnc: true,
tag: this.renderTag,
});
this.logger(this.cur_call);
};
Verto.prototype.makeShare = function () {
if (this.share_call) {
this.logError('Quitting: Call already in progress');
return;
}
var screenInfo = null;
if (!!navigator.mozGetUserMedia) {
// no screen parameters for FF, just screenShare: true down below
screenInfo = {};
this.doShare(screenInfo);
} else if (!!window.chrome) {
var _this = this;
if (!_this.chromeExtension) {
_this.logError({
status: 'failed',
message: 'Missing Chrome Extension key',
});
_this.onFail();
return;
}
// bring up Chrome screen picker
getMyScreenConstraints(function (constraints) {
if (constraints == null || constraints == "" || constraints.streamId == null || constraints.streamId == "") {
_this.onFail();
return _this.logError(constraints);
}
screenInfo = {
chromeMediaSource: "desktop",
chromeMediaSourceId: constraints.streamId,
};
_this.logger(screenInfo);
_this.doShare(screenInfo);
}, _this.chromeExtension);
}
};
Verto.prototype.doShare = function (screenConstraints) {
this.shouldConnect = true;
screenConstraints.maxWidth = this.vid_width;
screenConstraints.maxHeight = this.vid_height;
this.share_call = window.vertoHandle.newCall({
destination_number: this.destination_number,
caller_id_name: this.caller_id_name,
caller_id_number: this.caller_id_number,
outgoingBandwidth: "default",
incomingBandwidth: "default",
videoParams: screenConstraints,
useVideo: true,
screenShare: true,
dedEnc: true,
mirrorInput: false,
tag: this.renderTag,
});
var stopSharing = function() {
console.log("stopSharing");
this.share_call.hangup();
this.share_call = null;
};
var _this = this;
// Override onStream callback in $.FSRTC instance
this.share_call.rtc.options.callbacks.onStream = function (rtc, stream) {
_this.videoLoadingCallback();
if (stream) {
var StreamTrack = stream.getVideoTracks()[0];
StreamTrack.addEventListener('ended', stopSharing.bind(_this));
}
};
};
Verto.prototype.init = function () {
this.cur_call = null;
if (!window.vertoHandle) {
window.vertoHandle = new $.verto({
useVideo: true,
login: this.login,
passwd: this.password,
socketUrl: this.socketUrl,
tag: this.renderTag,
ringFile: 'sounds/bell_ring2.wav',
sessid: this.sessid,
videoParams: {
minFrameRate: 15,
vertoBestFrameRate: 30,
},
deviceParams: {
useCamera: false,
useMic: false,
useSpeak: 'none',
},
audioParams: {
googAutoGainControl: false,
googNoiseSuppression: false,
googHighpassFilter: false,
},
iceServers: this.iceServers,
}, this.callbacks);
} else {
this.mediaCallback();
return;
}
};
Verto.prototype.configStuns = function (callback) {
this.logger('Fetching STUN/TURN server info for Verto initialization');
var _this = this;
var stunsConfig = {};
// flash client has api access. html5 user passes array.
// client provided no stuns and cannot make api calls
// use defaults in verto and try making a call
if (BBB.getSessionToken == undefined) {
// uses defaults
this.iceServers = true;
// run init callback
return callback();
}
// TODO: screensharing and audio use this exact same function. Should be
// moved to a shared library for retrieving stun/turn and just pass
// success/fail callbacks
BBB.getSessionToken(function(sessionToken) {
$.ajax({
dataType: 'json',
url: '/bigbluebutton/api/stuns/',
data: {sessionToken},
}).done(function (data) {
_this.logger('ajax request done');
_this.logger(data);
if (data.response && data.response.returncode == 'FAILED') {
_this.logError(data.response.message, { error: true });
_this.logError({ 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);
_this.logger('success got stun data, making verto');
_this.iceServers = stunsConfig;
callback.apply(_this);
}).fail(function (data, textStatus, errorThrown) {
_this.logError({ status: 'failed', errorcode: 1009 });
_this.onFail();
return;
});
});
};
// checks whether Google Chrome or Firefox have the WebRTCPeerConnection object
Verto.prototype.isWebRTCAvailable = function () {
return (typeof window.webkitRTCPeerConnection !== 'undefined' ||
typeof window.mozRTCPeerConnection !== 'undefined');
};
this.VertoManager = function () {
this.vertoAudio = null;
this.vertoVideo = null;
this.vertoScreenShare = null;
window.vertoHandle = null;
};
Verto.prototype.logout = function () {
this.exitAudio();
this.exitVideo();
this.exitScreenShare();
window.vertoHandle.logout();
};
VertoManager.prototype.exitAudio = function () {
if (this.vertoAudio != null) {
console.log('Hanging up vertoAudio');
this.vertoAudio.hangup();
}
};
VertoManager.prototype.exitVideo = function () {
if (this.vertoVideo != null) {
console.log('Hanging up vertoVideo');
this.vertoVideo.hangup();
}
};
VertoManager.prototype.exitScreenShare = function () {
if (this.vertoScreenShare != null) {
console.log('Hanging up vertoScreenShare');
this.vertoScreenShare.hangup();
}
};
VertoManager.prototype.joinListenOnly = function (tag) {
this.exitAudio();
if (this.vertoAudio == null) {
var obj = Object.create(Verto.prototype);
Verto.apply(obj, arguments);
this.vertoAudio = obj;
}
this.vertoAudio.setListenOnly(tag);
};
VertoManager.prototype.joinMicrophone = function (tag) {
this.exitAudio();
if (this.vertoAudio == null) {
var obj = Object.create(Verto.prototype);
Verto.apply(obj, arguments);
this.vertoAudio = obj;
}
this.vertoAudio.setMicrophone(tag);
};
VertoManager.prototype.joinWatchVideo = function (tag) {
this.exitVideo();
if (this.vertoVideo == null) {
var obj = Object.create(Verto.prototype);
Verto.apply(obj, arguments);
this.vertoVideo = obj;
}
this.vertoVideo.setWatchVideo(tag);
};
VertoManager.prototype.shareScreen = function (tag) {
this.exitScreenShare();
if (this.vertoScreenShare == null) {
var obj = Object.create(Verto.prototype);
Verto.apply(obj, arguments);
this.vertoScreenShare = obj;
}
this.vertoScreenShare.setScreenShare(tag);
};
window.vertoInitialize = function () {
if (window.vertoManager == null || window.vertoManager == undefined) {
window.vertoManager = new VertoManager();
}
};
window.vertoExitAudio = function () {
window.vertoInitialize();
window.vertoManager.exitAudio();
};
window.vertoExitVideo = function () {
window.vertoInitialize();
window.vertoManager.exitVideo();
};
window.vertoExitScreenShare = function () {
window.vertoInitialize();
window.vertoManager.exitScreenShare();
};
window.vertoJoinListenOnly = function () {
window.vertoInitialize();
window.vertoManager.joinListenOnly.apply(window.vertoManager, arguments);
};
window.vertoJoinMicrophone = function () {
window.vertoInitialize();
window.vertoManager.joinMicrophone.apply(window.vertoManager, arguments);
};
window.vertoWatchVideo = function () {
window.vertoInitialize();
window.vertoManager.joinWatchVideo.apply(window.vertoManager, arguments);
};
window.vertoShareScreen = function () {
window.vertoInitialize();
window.vertoManager.shareScreen.apply(window.vertoManager, arguments);
};
// a function to check whether the browser (Chrome only) is in an isIncognito
// session. Requires 1 mandatory callback that only gets called if the browser
// session is incognito. The callback for not being incognito is optional.
// Attempts to retrieve the chrome filesystem API.
window.checkIfIncognito = function(isIncognito, isNotIncognito = function () {}) {
isIncognito = Verto.normalizeCallback(isIncognito);
isNotIncognito = Verto.normalizeCallback(isNotIncognito);
var fs = window.RequestFileSystem || window.webkitRequestFileSystem;
if (!fs) {
isNotIncognito();
return;
}
fs(window.TEMPORARY, 100, function(){isNotIncognito()}, function(){isIncognito()});
};
window.checkChromeExtInstalled = function (callback, chromeExtensionId) {
callback = Verto.normalizeCallback(callback);
if (typeof chrome === "undefined" || !chrome || !chrome.runtime) {
// No API, so no extension for sure
callback(false);
return;
}
chrome.runtime.sendMessage(
chromeExtensionId,
{ getVersion: true },
function (response) {
if (!response || !response.version) {
// Communication failure - assume that no endpoint exists
callback(false);
return;
}
callback(true);
}
);
}
window.getMyScreenConstraints = function(theCallback, extensionId) {
theCallback = Verto.normalizeCallback(theCallback);
chrome.runtime.sendMessage(extensionId, {
getStream: true,
sources: [
"window",
"screen",
"tab"
]},
function(response) {
console.log(response);
theCallback(response);
});
};

View File

@ -24,8 +24,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:common="org.bigbluebutton.common.*"
verticalScrollPolicy="off" horizontalScrollPolicy="off"
dataChange="dataChangeHandler(event)"
>
dataChange="dataChangeHandler(event)">
<fx:Script>
<![CDATA[

View File

@ -22,7 +22,6 @@ package org.bigbluebutton.modules.screenshare.managers {
import org.as3commons.logging.api.ILogger;
import org.as3commons.logging.api.getClassLogger;
import org.bigbluebutton.core.BBB;
import org.bigbluebutton.core.Options;
import org.bigbluebutton.core.UsersUtil;
import org.bigbluebutton.main.events.MadePresenterEvent;

View File

@ -54,7 +54,7 @@ package org.bigbluebutton.modules.screenshare.managers
private var usingKurentoWebRTC:Boolean = false;
private var chromeExtensionKey:String = null;
private var options:ScreenshareOptions;
private var videoLoadingCallbackName:String = "videoLoadingCallback";
private var videoLoadingCallbackName:String = "videoLoadingCallback";
public function WebRTCDeskshareManager() {
LOGGER.debug("WebRTCDeskshareManager::WebRTCDeskshareManager");
@ -110,21 +110,16 @@ package org.bigbluebutton.modules.screenshare.managers
extension installation */
publishWindowManager.stopSharing();
if (ExternalInterface.available) {
if(usingKurentoWebRTC) {
ExternalInterface.call("kurentoExitScreenShare");
}
else {
ExternalInterface.call("vertoExitScreenShare");
}
if (ExternalInterface.available && usingKurentoWebRTC) {
ExternalInterface.call("kurentoExitScreenShare");
}
}
private function startWebRTCDeskshare():void {
LOGGER.debug("WebRTCDeskshareManager::startWebRTCDeskshare");
if (ExternalInterface.available) {
var videoTag:String = "localVertoVideo";
if (ExternalInterface.available && usingKurentoWebRTC) {
var videoTag:String = "localWebRTCVideo";
var onFail:Function = function(args:Object):void {
LOGGER.debug("WebRTCDeskshareManager::startWebRTCDeskshare - falling back to java");
globalDispatcher.dispatchEvent(new UseJavaModeCommand())
@ -135,38 +130,15 @@ package org.bigbluebutton.modules.screenshare.managers
var myName:String = UsersUtil.getMyUsername();
var internalMeetingID:String = UsersUtil.getInternalMeetingID();
if(usingKurentoWebRTC) {
ExternalInterface.call(
'kurentoShareScreen',
videoTag,
voiceBridge,
myName,
internalMeetingID,
"onFail",
chromeExtensionKey
);
} else {
var videoLoadingCallback:Function = function():void {
ExternalInterface.addCallback(videoLoadingCallbackName, null);
publishWindowManager.openWindow();
globalDispatcher.dispatchEvent(new WebRTCPublishWindowChangeState(WebRTCPublishWindowChangeState.DISPLAY_VIDEO_LOADING));
}
ExternalInterface.addCallback(videoLoadingCallbackName, videoLoadingCallback);
var dummyStunTurn:Object = null;
ExternalInterface.call(
'vertoShareScreen',
videoTag,
voiceBridge,
myName,
null,
"onFail",
chromeExtensionKey,
dummyStunTurn,
videoLoadingCallbackName
);
}
ExternalInterface.call(
'kurentoShareScreen',
videoTag,
voiceBridge,
myName,
internalMeetingID,
"onFail",
chromeExtensionKey);
}
}
@ -177,7 +149,6 @@ package org.bigbluebutton.modules.screenshare.managers
if (!StringUtils.isEmpty(options.chromeExtensionKey)) {
chromeExtensionKey = options.chromeExtensionKey;
}
usingWebRTC = options.tryWebRTCFirst;
usingKurentoWebRTC = options.tryKurentoWebRTC;
}
@ -200,7 +171,7 @@ package org.bigbluebutton.modules.screenshare.managers
*/
private function cannotUseWebRTC (message:String):void {
LOGGER.debug("Cannot use WebRTC Screensharing: " + message);
JSLog.warn("Cannot use WebRTC Screensharing: ", message);
JSLog.warn("Cannot use WebRTC Screensharing: ", message);
usingWebRTC = false;
usingKurentoWebRTC = false;
// send out event to fallback to Java
@ -212,7 +183,7 @@ package org.bigbluebutton.modules.screenshare.managers
*/
private function webRTCWorksButNotConfigured (message:String):void {
LOGGER.debug("WebRTC Screenshare needs to be configured clientside: " + message);
JSLog.warn("WebRTC Screenshare needs to be configured clientside: ", message);
JSLog.warn("WebRTC Screenshare needs to be configured clientside: ", message);
publishWindowManager.openWindow();
globalDispatcher.dispatchEvent(new WebRTCPublishWindowChangeState(WebRTCPublishWindowChangeState.DISPLAY_INSTALL));
}
@ -222,7 +193,7 @@ package org.bigbluebutton.modules.screenshare.managers
*/
private function webRTCWorksAndConfigured (message:String):void {
LOGGER.debug("WebRTC Screenshare works, start sharing: " + message);
JSLog.warn("WebRTC Screenshare works, start sharing: ", message);
JSLog.warn("WebRTC Screenshare works, start sharing: ", message);
usingWebRTC = true;
startWebRTCDeskshare();
}
@ -238,7 +209,7 @@ package org.bigbluebutton.modules.screenshare.managers
return;
}
WebRTCScreenshareUtility.canIUseVertoOnThisBrowser(cannotUseWebRTC, webRTCWorksButNotConfigured, webRTCWorksAndConfigured);
WebRTCScreenshareUtility.canIUseWebRTCOnThisBrowser(cannotUseWebRTC, webRTCWorksButNotConfigured, webRTCWorksAndConfigured);
}
public function handleShareWindowCloseEvent():void {

View File

@ -26,7 +26,7 @@ package org.bigbluebutton.modules.screenshare.utils
import org.as3commons.logging.api.getClassLogger;
import org.bigbluebutton.core.Options;
import org.bigbluebutton.modules.screenshare.model.ScreenshareOptions;
import org.bigbluebutton.util.browser.BrowserCheck;
import org.bigbluebutton.util.browser.BrowserCheck;
public class WebRTCScreenshareUtility {
private static const LOGGER:ILogger = getClassLogger(WebRTCScreenshareUtility);
@ -34,15 +34,15 @@ package org.bigbluebutton.modules.screenshare.utils
public static var extensionLink:String = null;
private static var options:ScreenshareOptions = null;
public static function canIUseVertoOnThisBrowser (cannotUseWebRTC:Function, webRTCWorksButNotConfigured:Function, webRTCWorksAndConfigured:Function):void {
LOGGER.debug("WebRTCScreenshareUtility::canIUseVertoOnThisBrowser");
public static function canIUseWebRTCOnThisBrowser (cannotUseWebRTC:Function, webRTCWorksButNotConfigured:Function, webRTCWorksAndConfigured:Function):void {
LOGGER.debug("WebRTCScreenshareUtility::canIUseWebRTCOnThisBrowser");
if (!ExternalInterface.available) {
cannotUseWebRTC("No ExternalInterface");
return;
}
// https is required for verto and for peripheral sharing
// https is required for webrtc and for peripheral sharing
if (!BrowserCheck.isHttps()) {
cannotUseWebRTC("WebRTC Screensharing requires an HTTPS connection");
return;
@ -72,7 +72,7 @@ package org.bigbluebutton.modules.screenshare.utils
WebRTCScreenshareUtility.extensionLink = options.chromeExtensionLink;
// if its firefox go ahead and let verto handle it
// if its firefox go ahead and let webrtc handle it
if (BrowserCheck.isFirefox()) {
webRTCWorksAndConfigured("Firefox, lets try");
return;
@ -87,7 +87,7 @@ package org.bigbluebutton.modules.screenshare.utils
return;
}
// connect to the verto code to attempt a connection with the extension
// connect to the webrtc code to attempt a connection with the extension
var onSuccess:Function = function(exists:Boolean):void {
// clear the check callback
ExternalInterface.addCallback("onSuccess", null);

View File

@ -97,13 +97,19 @@
dsOptions = Options.getOptions(ScreenshareOptions) as ScreenshareOptions;
}
private function resizeVideoCanvas() : void {
videoCanvas.width = this.width - VIDEO_WIDTH_PADDING;
videoCanvas.height = this.height - VIDEO_HEIGHT_PADDING;
}
private function onCreationComplete():void{
viewScreenshareStream();
resizeVideoCanvas();
videoHolder.addChild(video);
videoCanvas.addChildAt(videoHolder, 0);
videoHolder.percentWidth = 100;
videoHolder.percentHeight = 100;
videoHolder.addEventListener(MouseEvent.MOUSE_OVER, videoHolder_mouseOverHanlder);
videoHolder.addEventListener(MouseEvent.MOUSE_OUT, videoHolder_mouseOutHanlder);
addEventListener(MDIWindowEvent.RESIZE_END, onResizeEndEvent);
@ -232,6 +238,8 @@
* Resizes the desktop sharing video to fit to this window
*/
private function fitToWindow():void {
resizeVideoCanvas();
if (!streamAvailable)
return;
@ -243,9 +251,6 @@
if (!btnActualSize.selected) {
fitVideoToWindow();
}
videoHolder.verticalCenter = 0;
videoHolder.horizontalCenter = 0;
}
private function fitVideoToWindow():void {
@ -258,6 +263,9 @@
videoHolder.width = video.width;
videoHolder.height = video.height;
videoHolder.verticalCenter = 0;
videoHolder.horizontalCenter = 0;
videoCanvas.verticalScrollPolicy = ScrollPolicy.OFF;
videoCanvas.horizontalScrollPolicy = ScrollPolicy.OFF;
}
@ -267,6 +275,11 @@
videoHolder.width = videoWidth;
video.height = videoHeight;
videoHolder.height = videoHeight;
videoHolder.verticalCenter = undefined;
videoHolder.horizontalCenter = undefined;
videoHolder.x = 0;
videoHolder.y = 0;
}
private function videoIsSmallerThanWindow():Boolean {
@ -278,12 +291,9 @@
*/
private function fitToActualSize():void{
if (videoIsSmallerThanWindow()) {
fitWindowToVideo();
fitVideoToWindow();
} else {
video.width = videoWidth;
videoHolder.width = videoWidth;
video.height = videoHeight;
videoHolder.height = videoHeight;
fitWindowToVideo();
}
videoCanvas.verticalScrollPolicy = ScrollPolicy.AUTO;
@ -339,7 +349,7 @@
tabIndices="{[minimizeBtn, maximizeRestoreBtn, closeBtn, btnActualSize]}"/>
</fx:Declarations>
<mx:Canvas id="videoCanvas" width="100%" height="100%" />
<mx:Canvas id="videoCanvas" width="100" height="100"/>
<mx:Button id="btnActualSize"
styleName="screenShareActualizeButton"

View File

@ -25,14 +25,14 @@
xmlns:mate="http://mate.asfusion.com/"
verticalScrollPolicy="off" horizontalScrollPolicy="off"
creationComplete="onCreationComplete()" >
<fx:Declarations>
<mate:Listener type="{UsersRollEvent.USER_ROLL_OVER}" method="onRollOver" />
<mate:Listener type="{UsersRollEvent.USER_ROLL_OUT}" method="onRollOut" />
<mate:Listener type="{ChangeMyRole.CHANGE_MY_ROLE_EVENT}" method="onChangeMyRole"/>
<mate:Listener type="{BBBEvent.CHANGE_WEBCAMS_ONLY_FOR_MODERATOR}" method="onChangeWebcamsOnlyForModerator"/>
</fx:Declarations>
<fx:Script>
<![CDATA[
import flash.filters.BitmapFilterQuality;
@ -59,10 +59,14 @@
import org.bigbluebutton.modules.users.model.UsersOptions;
import org.bigbluebutton.util.i18n.ResourceUtil;
private var moderator:Boolean = false;
[Bindable]
private var rolledOver:Boolean = false;
[Bindable]
private var webcamsOnlyForModerator:Boolean;
private var moderator:Boolean = false;
private var rolledOverMute:Boolean = false;
private var rolledOverLock:Boolean = false;
@ -91,6 +95,7 @@
//rest rolledOver when the data changes because onRollOut wont be called if the row moves
if (data != null) {
updateButtons();
validateNow();
}
}
@ -118,6 +123,8 @@
}
private function onChangeWebcamsOnlyForModerator(e:BBBEvent):void {
webcamsOnlyForModerator = LiveMeeting.inst().meeting.webcamsOnlyForModerator;
if (data != null) {
updateButtons();
}
@ -187,8 +194,6 @@
var ls:LockSettingsVO = UsersUtil.getLockSettings();
var webcamsOnlyForModerator:Boolean = LiveMeeting.inst().meeting.webcamsOnlyForModerator;
if (data != null) {
settingsBtn.visible = rolledOver && !data.me && !UsersUtil.isBreakout();
@ -290,8 +295,7 @@
}
}
if (data.locked && !data.presenter && ls.isAnythingLocked()) {
if (data.locked && !data.presenter && ls.isAnythingLocked() || (!data.presenter && webcamsOnlyForModerator)) {
lockImg.source = getStyle("iconLock");
} else {
lockImg.source = null;

View File

@ -421,7 +421,7 @@ $Id: $
}
}
private function setRoomLocked(e:Event = null) {
private function setRoomLocked(e:Event = null):void {
var lockSettings:LockSettingsVO = UsersUtil.getLockSettings();
roomLocked = (lockSettings.isAnythingLocked() || LiveMeeting.inst().meeting.webcamsOnlyForModerator) && (lockSettings.getLockOnJoin() || UsersUtil.isAnyoneLocked());
}

View File

@ -1,111 +0,0 @@
(function() {
function adjustVideos(tagId, centerVideos, moreThan4VideosClass, mediaContainerClass, overlayWrapperClass, presentationAreaDataId, screenshareVideoId) {
const _minContentAspectRatio = 16 / 9.0;
function calculateOccupiedArea(canvasWidth, canvasHeight, numColumns, numRows, numChildren) {
const obj = calculateCellDimensions(canvasWidth, canvasHeight, numColumns, numRows);
obj.occupiedArea = obj.width * obj.height * numChildren;
obj.numColumns = numColumns;
obj.numRows = numRows;
obj.cellAspectRatio = _minContentAspectRatio;
return obj;
}
function calculateCellDimensions(canvasWidth, canvasHeight, numColumns, numRows) {
const obj = {
width: Math.floor(canvasWidth / numColumns),
height: Math.floor(canvasHeight / numRows),
};
if (obj.width / obj.height > _minContentAspectRatio) {
obj.width = Math.min(Math.floor(obj.height * _minContentAspectRatio), Math.floor(canvasWidth / numColumns));
} else {
obj.height = Math.min(Math.floor(obj.width / _minContentAspectRatio), Math.floor(canvasHeight / numRows));
}
return obj;
}
function findBestConfiguration(canvasWidth, canvasHeight, numChildrenInCanvas) {
let bestConfiguration = {
occupiedArea: 0,
};
for (let cols = 1; cols <= numChildrenInCanvas; cols++) {
let rows = Math.floor(numChildrenInCanvas / cols);
// That's a small HACK, different from the original algorithm
// Sometimes numChildren will be bigger than cols*rows, this means that this configuration
// can't show all the videos and shouldn't be considered. So we just increment the number of rows
// and get a configuration which shows all the videos albeit with a few missing slots in the end.
// For example: with numChildren == 8 the loop will generate cols == 3 and rows == 2
// cols * rows is 6 so we bump rows to 3 and then cols*rows is 9 which is bigger than 8
if (numChildrenInCanvas > cols * rows) {
rows += 1;
}
const currentConfiguration = calculateOccupiedArea(canvasWidth, canvasHeight, cols, rows, numChildrenInCanvas);
if (currentConfiguration.occupiedArea > bestConfiguration.occupiedArea) {
bestConfiguration = currentConfiguration;
}
}
return bestConfiguration;
}
// http://stackoverflow.com/a/3437825/414642
const e = $("." + overlayWrapperClass);
const x = e.outerWidth() - 1;
const y = e.outerHeight() - 1;
const videos = $("#" + tagId + " > div:visible");
const isPortrait = ( $(document).width() < $(document).height() );
if (isPortrait) {
// If currently displaying a presentation
if ( $("#" + presentationAreaDataId).length ) {
e.css({
"margin-top": $('#' + presentationAreaDataId).offset().top - 221,
"width": "calc(100% - " + $('#' + presentationAreaDataId).offset().left + ")"
});
} else if ( $("#" + screenshareVideoId).length ) { // Or if currently displaying a screenshare
e.css({
"margin-top": $('#' + screenshareVideoId).offset().top - 221,
"width": "calc(100% - " + $('#' + screenshareVideoId).offset().left + ")"
});
}
} else {
e.css({
"width": "100%",
"margin-top": 0
});
}
if (videos.length > 4 && !isPortrait) {
e.addClass(moreThan4VideosClass);
$("." + mediaContainerClass).css("max-width", "calc(100% - 170px)");
} else {
e.removeClass(moreThan4VideosClass);
$("." + mediaContainerClass).css("max-width", "100%");
}
const best = findBestConfiguration(x, y, videos.length);
videos.each(function (i) {
const row = Math.floor(i / best.numColumns);
const col = Math.floor(i % best.numColumns);
const top = (row > 0 && videos.length <= 4 && !isPortrait) ? 1 : 0;
const left = (col > 0 && videos.length <= 4 && !isPortrait) ? 1 : 0;
$(this).attr('style', `margin-top: ${top}px; margin-left: ${left}px; width: ${best.width}px; height: ${best.height}px;`);
});
videos.attr('width', best.width);
videos.attr('height', best.height);
}
window.adjustVideos = adjustVideos;
})();

View File

@ -320,6 +320,9 @@ Kurento.prototype.viewer = function () {
const self = this;
if (!this.webRtcPeer) {
const options = {
mediaConstraints: {
audio: false
},
remoteVideo: document.getElementById(this.renderTag),
onicecandidate: this.onViewerIceCandidate.bind(this),
};
@ -409,7 +412,7 @@ Kurento.normalizeCallback = function (callback) {
// this function explains how to use above methods/objects
window.getScreenConstraints = function (sendSource, callback) {
const screenConstraints = { video: {} };
const screenConstraints = { video: {}, audio: false };
// Limiting FPS to a range of 5-10 (5 ideal)
screenConstraints.video.frameRate = { ideal: 5, max: 10 };
@ -431,6 +434,17 @@ window.getScreenConstraints = function (sendSource, callback) {
// this statement sets gets 'sourceId" and sets "chromeMediaSourceId"
screenConstraints.video.chromeMediaSource = { exact: [sendSource] };
screenConstraints.video.chromeMediaSourceId = sourceId;
screenConstraints.optional = [
{ googCpuOveruseDetection: true },
{ googCpuOveruseEncodeUsage: true },
{ googCpuUnderuseThreshold: 55 },
{ googCpuOveruseThreshold: 85 },
{ googPayloadPadding: true },
{ googScreencastMinBitrate: 400 },
{ googHighStartBitrate: true },
{ googHighBitrate: true },
{ googVeryHighBitrate: true }
];
console.log('getScreenConstraints for Chrome returns => ', screenConstraints);
// now invoking native getUserMedia API

3
bigbluebutton-html5/client/stylesheets/bbb-icons.css Normal file → Executable file
View File

@ -268,3 +268,6 @@
.icon-bbb-warning:before {
content: "\e94c";
}
.icon-bbb-pointer:before {
content: "\e950";
}

View File

@ -1,4 +1,5 @@
import RedisPubSub from '/imports/startup/server/redis';
import { processForHTML5ServerOnly } from '/imports/api/common/server/helpers';
import handleWhiteboardCleared from './handlers/whiteboardCleared';
import handleWhiteboardUndo from './handlers/whiteboardUndo';
import handleWhiteboardSend from './handlers/whiteboardSend';
@ -7,4 +8,4 @@ import handleWhiteboardAnnotations from './handlers/whiteboardAnnotations';
RedisPubSub.on('ClearWhiteboardEvtMsg', handleWhiteboardCleared);
RedisPubSub.on('UndoWhiteboardEvtMsg', handleWhiteboardUndo);
RedisPubSub.on('SendWhiteboardAnnotationEvtMsg', handleWhiteboardSend);
RedisPubSub.on('GetWhiteboardAnnotationsRespMsg', handleWhiteboardAnnotations);
RedisPubSub.on('GetWhiteboardAnnotationsRespMsg', processForHTML5ServerOnly(handleWhiteboardAnnotations));

View File

@ -317,6 +317,7 @@ export default class SIPBridge extends BaseAudioBridge {
setDefaultInputDevice() {
const handleMediaSuccess = (mediaStream) => {
const deviceLabel = mediaStream.getAudioTracks()[0].label;
window.defaultInputStream = mediaStream.getTracks();
return navigator.mediaDevices.enumerateDevices().then((mediaDevices) => {
const device = mediaDevices.find(d => d.label === deviceLabel);
return this.changeInputDevice(device.deviceId);

View File

@ -1,9 +1,10 @@
import RedisPubSub from '/imports/startup/server/redis';
import { processForHTML5ServerOnly } from '/imports/api/common/server/helpers';
import handleCaptionHistory from './handlers/captionHistory';
import handleCaptionUpdate from './handlers/captionUpdate';
import handleCaptionOwnerUpdate from './handlers/captionOwnerUpdate';
// TODO
RedisPubSub.on('SendCaptionHistoryRespMsg', handleCaptionHistory);
RedisPubSub.on('SendCaptionHistoryRespMsg', processForHTML5ServerOnly(handleCaptionHistory));
RedisPubSub.on('EditCaptionHistoryEvtMsg', handleCaptionUpdate);
RedisPubSub.on('UpdateCaptionOwnerEvtMsg', handleCaptionOwnerUpdate);

View File

@ -1,9 +1,10 @@
import RedisPubSub from '/imports/startup/server/redis';
import { processForHTML5ServerOnly } from '/imports/api/common/server/helpers';
import handleChatMessage from './handlers/chatMessage';
import handleChatHistory from './handlers/chatHistory';
import handleChatPublicHistoryClear from './handlers/chatPublicHistoryClear';
RedisPubSub.on('GetChatHistoryRespMsg', handleChatHistory);
RedisPubSub.on('GetChatHistoryRespMsg', processForHTML5ServerOnly(handleChatHistory));
RedisPubSub.on('SendPublicMessageEvtMsg', handleChatMessage);
RedisPubSub.on('SendPrivateMessageEvtMsg', handleChatMessage);
RedisPubSub.on('SendPrivateMessageEvtMsg', processForHTML5ServerOnly(handleChatMessage));
RedisPubSub.on('ClearPublicChatHistoryEvtMsg', handleChatPublicHistoryClear);

View File

@ -1,5 +1,8 @@
import WhiteboardMultiUser from '/imports/api/whiteboard-multi-user/';
const MSG_DIRECT_TYPE = 'DIRECT';
const NODE_USER = 'nodeJSapp';
export const indexOf = [].indexOf || function (item) {
for (let i = 0, l = this.length; i < l; i += 1) {
if (i in this && this[i] === item) {
@ -10,8 +13,15 @@ export const indexOf = [].indexOf || function (item) {
return -1;
};
// used in 1.1
export const inReplyToHTML5Client = arg => arg.routing.userId === 'nodeJSapp';
export const processForHTML5ServerOnly = fn => (message, ...args) => {
const { envelope } = message;
const { routing } = envelope;
const shouldSkip = routing.msgType === MSG_DIRECT_TYPE && routing.userId !== NODE_USER;
if (shouldSkip) return () => { };
return fn(message, ...args);
};
export const getMultiUserStatus = (meetingId) => {
const data = WhiteboardMultiUser.findOne({ meetingId });

View File

@ -1,4 +1,5 @@
import RedisPubSub from '/imports/startup/server/redis';
import { processForHTML5ServerOnly } from '/imports/api/common/server/helpers';
import handleJoinVoiceUser from './handlers/joinVoiceUser';
import handleLeftVoiceUser from './handlers/leftVoiceUser';
import handleTalkingVoiceUser from './handlers/talkingVoiceUser';
@ -9,4 +10,4 @@ RedisPubSub.on('UserLeftVoiceConfToClientEvtMsg', handleLeftVoiceUser);
RedisPubSub.on('UserJoinedVoiceConfToClientEvtMsg', handleJoinVoiceUser);
RedisPubSub.on('UserTalkingVoiceEvtMsg', handleTalkingVoiceUser);
RedisPubSub.on('UserMutedVoiceEvtMsg', handleMutedVoiceUser);
RedisPubSub.on('GetVoiceUsersMeetingRespMsg', handleGetVoiceUsers);
RedisPubSub.on('GetVoiceUsersMeetingRespMsg', processForHTML5ServerOnly(handleGetVoiceUsers));

View File

@ -1,7 +1,7 @@
import RedisPubSub from '/imports/startup/server/redis';
import { processForHTML5ServerOnly } from '/imports/api/common/server/helpers';
import handleGetWhiteboardAccess from './handlers/getWhiteboardAccess';
import handleModifyWhiteboardAccess from './handlers/modifyWhiteboardAccess';
RedisPubSub.on('GetWhiteboardAccessRespMsg', handleGetWhiteboardAccess);
RedisPubSub.on('GetWhiteboardAccessRespMsg', processForHTML5ServerOnly(handleGetWhiteboardAccess));
RedisPubSub.on('SyncGetWhiteboardAccessRespMsg', handleGetWhiteboardAccess);
RedisPubSub.on('ModifyWhiteboardAccessEvtMsg', handleModifyWhiteboardAccess);
RedisPubSub.on('ModifyWhiteboardAccessEvtMsg', handleGetWhiteboardAccess);

View File

@ -1,37 +0,0 @@
import { check } from 'meteor/check';
import Logger from '/imports/startup/server/logger';
import WhiteboardMultiUser from '/imports/api/whiteboard-multi-user/';
export default function handleModifyWhiteboardAccess({ body }, meetingId) {
const { multiUser } = body;
check(multiUser, Boolean);
check(meetingId, String);
const selector = {
meetingId,
};
const modifier = {
$set: {
meetingId,
multiUser,
},
};
const cb = (err, numChanged) => {
if (err) {
return Logger.error(`Error while adding an entry to Multi-User collection: ${err}`);
}
const { insertedId } = numChanged;
if (insertedId) {
return Logger.info(`Added multiUser flag to the meetingId=${meetingId}`);
}
return Logger.info(`Upserted multiUser flag into meetingId=${meetingId}`);
};
return WhiteboardMultiUser.upsert(selector, modifier, cb);
}

View File

@ -1,6 +1,7 @@
import Auth from '/imports/ui/services/auth';
import { setCustomLogoUrl } from '/imports/ui/components/user-list/service';
import { log } from '/imports/ui/services/api';
import deviceType from '/imports/utils/deviceType';
// disconnected and trying to open a new connection
const STATUS_CONNECTING = 'connecting';
@ -26,7 +27,11 @@ export function joinRouteHandler(nextState, replace, callback) {
setCustomLogoUrl(customLogoURL);
Auth.set(meetingID, internalUserID, authToken, logoutUrl, sessionToken);
replace({ pathname: '/' });
const path = deviceType().isPhone ? '/' : '/users';
replace({ pathname: path });
callback();
});
}

View File

@ -42,9 +42,8 @@ const EmojiSelect = ({
const statuses = Object.keys(options);
const lastStatus = statuses.pop();
const statusLabel = statuses.indexOf(selected) === -1 ?
intl.formatMessage(intlMessages.statusTriggerLabel)
: intl.formatMessage({ id: `app.actionsBar.emojiMenu.${selected}Label` });
const statusLabel = intl.formatMessage(intlMessages.statusTriggerLabel);
return (
<Dropdown autoFocus>

View File

@ -21,7 +21,7 @@
}
}
.left{
.left {
position: absolute;
@include mq($small-only) {
bottom: $sm-padding-x;
@ -31,9 +31,7 @@
.centerWithActions {
@include mq($xsmall-only) {
position: absolute;
bottom: $sm-padding-x;
right: $sm-padding-x;
justify-content: flex-end;
}
}
@ -48,7 +46,7 @@
}
.emojiSelected {
span, i::before{
span, i {
color: $color-primary;
}
}

View File

@ -1,7 +1,7 @@
@import "/imports/ui/stylesheets/variables/_all";
$navbar-height: 60px; // TODO: Change to NavBar real height
$actionsbar-height: 50px; // TODO: Change to ActionsBar real height
$navbar-height: 65px; // TODO: Change to NavBar real height
$actionsbar-height: 75px; // TODO: Change to ActionsBar real height
$bars-padding: $lg-padding-x - .45rem; // -.45 so user-list and chat title is aligned with the presentation title
.main {
@ -53,9 +53,7 @@ $bars-padding: $lg-padding-x - .45rem; // -.45 so user-list and chat title is al
.content {
@extend %full-page;
order: 3;
padding: 0 .25%;
&:before,
&:after {
@ -154,15 +152,9 @@ $bars-padding: $lg-padding-x - .45rem; // -.45 so user-list and chat title is al
@extend %full-page;
flex: 1 100%;
order: 2;
flex-direction: row;
position: relative;
@include mq($small-only) {
padding-bottom: $actionsbar-height;
margin-bottom: $actionsbar-height;
}
@include mq($portrait) {
flex-direction: column;
}
@ -189,10 +181,4 @@ $bars-padding: $lg-padding-x - .45rem; // -.45 so user-list and chat title is al
padding: $bars-padding;
position: relative;
order: 3;
@include mq($small-only) {
position: absolute;
bottom: 0;
width: 100%;
}
}

View File

@ -16,6 +16,10 @@
}
}
> :first-child {
margin-left: 0;
}
> :last-child {
margin-right: 0;
}

View File

@ -5,6 +5,7 @@ import Meetings from '/imports/api/meetings';
import VoiceUsers from '/imports/api/voice-users';
const init = (messages) => {
AudioManager.setAudioMessages(messages);
if (AudioManager.initialized) return;
const meetingId = Auth.meetingID;
const userId = Auth.userID;
@ -26,7 +27,7 @@ const init = (messages) => {
microphoneLockEnforced,
};
AudioManager.init(userData, messages);
AudioManager.init(userData);
};
const isVoiceUserTalking = () =>

View File

@ -51,7 +51,7 @@
flex: 1;
display: flex;
flex-flow: column;
overflow: hidden;
width: calc(100% - 1.7rem);
}
.meta {
@ -65,26 +65,30 @@
}
}
.name {
.name, .logout {
display: flex;
min-width: 0;
font-weight: 600;
color: $color-heading;
position: relative;
> span {
@extend %text-elipsis;
padding: .5rem 0;
margin-top: -.5rem;
margin-bottom: -.5rem;
}
}
.name {
color: $color-heading;
}
.logout {
display: flex;
min-width: 0;
font-weight: 600;
text-transform: capitalize;
font-style: italic;
> span {
@extend %text-elipsis;
& > span {
text-align: right;
padding-right: .1rem;
}
}
@ -93,7 +97,6 @@
font-weight: 100;
text-transform: lowercase;
font-style: italic;
margin-left: .25rem;
font-size: 90%;
line-height: 1;
align-self: center;

View File

@ -122,6 +122,7 @@ class Dropdown extends Component {
trigger = React.cloneElement(trigger, {
ref: (ref) => { this.trigger = ref; },
dropdownIsOpen: this.state.isOpen,
dropdownToggle: this.handleToggle,
dropdownShow: this.handleShow,
dropdownHide: this.handleHide,
@ -130,6 +131,7 @@ class Dropdown extends Component {
content = React.cloneElement(content, {
ref: (ref) => { this.content = ref; },
'aria-expanded': this.state.isOpen,
dropdownIsOpen: this.state.isOpen,
dropdownToggle: this.handleToggle,
dropdownShow: this.handleShow,
dropdownHide: this.handleHide,

View File

@ -26,13 +26,15 @@ const defaultProps = {
export default class DropdownContent extends Component {
render() {
const {
placement, className, children, style,
placement, children, className,
dropdownToggle, dropdownShow, dropdownHide, dropdownIsOpen,
...restProps
} = this.props;
const { dropdownToggle, dropdownShow, dropdownHide } = this.props;
const placementName = placement.split(' ').join('-');
const boundChildren = Children.map(children, child => cloneElement(child, {
dropdownIsOpen,
dropdownToggle,
dropdownShow,
dropdownHide,
@ -40,10 +42,9 @@ export default class DropdownContent extends Component {
return (
<div
style={style}
aria-expanded={this.props['aria-expanded']}
data-test="dropdownContent"
className={cx(styles.content, styles[placementName], className)}
{...restProps}
>
<div className={styles.scrollable}>
{boundChildren}

View File

@ -9,7 +9,7 @@ import ListTitle from './title/component';
import UserActions from '../../user-list/user-list-content/user-participants/user-list-item/user-action/component';
const propTypes = {
/* We should recheck this proptype, sometimes we need to create an container and send to dropdown,
/* We should recheck this proptype, sometimes we need to create an container and send to dropdown,
but with this */
// proptype, is not possible.
children: PropTypes.arrayOf((propValue, key, componentName, location, propFullName) => {
@ -33,18 +33,16 @@ const defaultProps = {
export default class DropdownList extends Component {
constructor(props) {
super(props);
this.state = {
focusedIndex: false,
};
this.childrenRefs = [];
this.menuRefs = [];
this.handleItemKeyDown = this.handleItemKeyDown.bind(this);
this.handleItemClick = this.handleItemClick.bind(this);
}
componentWillMount() {
this.setState({
focusedIndex: 0,
});
}
componentDidMount() {
this._menu.addEventListener('keydown', event => this.handleItemKeyDown(event));
}
@ -64,7 +62,8 @@ export default class DropdownList extends Component {
handleItemKeyDown(event, callback) {
const { getDropdownMenuParent } = this.props;
let nextFocusedIndex = this.state.focusedIndex;
const { focusedIndex } = this.state;
let nextFocusedIndex = focusedIndex > 0 ? focusedIndex : 0;
const isHorizontal = this.props.horizontal;
const navigationKeys = {
previous: KEY_CODES[`ARROW_${isHorizontal ? 'LEFT' : 'UP'}`],
@ -98,11 +97,13 @@ export default class DropdownList extends Component {
}
if (navigationKeys.click.includes(event.keyCode)) {
nextFocusedIndex = false;
event.stopPropagation();
document.activeElement.firstChild.click();
}
if (navigationKeys.close.includes(event.keyCode)) {
nextFocusedIndex = false;
const { dropdownHide } = this.props;
event.stopPropagation();
@ -139,7 +140,8 @@ export default class DropdownList extends Component {
render() {
const { children, style, className } = this.props;
const boundChildren = Children.map(children,
const boundChildren = Children.map(
children,
(item) => {
if (item.type === ListSeparator) {
return item;
@ -164,7 +166,8 @@ export default class DropdownList extends Component {
this.handleItemKeyDown(event, onKeyDown);
},
});
});
},
);
const listDirection = this.props.horizontal ? styles.horizontalList : styles.verticalList;
return (

View File

@ -1,8 +1,5 @@
@import "/imports/ui/stylesheets/variables/_all";
$item-bg-focus: $color-blue-lightest;
$item-border-focus: $color-blue-lighter;
%list {
list-style: none;
font-size: $font-size-base;
@ -10,7 +7,6 @@ $item-border-focus: $color-blue-lighter;
padding: 0;
text-align: left;
color: $color-gray-dark;
padding: ($line-height-computed / 2);
display: flex;
@include mq($small-only) {
@ -30,6 +26,7 @@ $item-border-focus: $color-blue-lighter;
flex-direction: row;
@include mq($small-only) {
flex-direction: column;
padding: $line-height-computed;
}
}
@ -54,29 +51,70 @@ $item-border-focus: $color-blue-lighter;
.item {
display: flex;
flex: 1 1 100%;
padding: ($line-height-computed / 3) 0;
&:focus {
outline: none;
border-radius: $border-size;
box-shadow: 0 0 0 $border-size $item-border-focus;
background-color: $item-bg-focus;
}
@include mq($small-only) {
padding: ($line-height-computed / 1.5) 0;
justify-content: center;
}
.verticalList & {
padding: ($line-height-computed / 3) 0;
@include mq($small-only) {
padding: ($line-height-computed / 1.5) 0;
}
}
.horizontalList & {
padding: 0 ($line-height-computed / 3);
@include mq($small-only) {
padding: ($line-height-computed / 1.5) 0;
}
}
&:hover,
&:focus {
cursor: pointer;
outline: none;
background-color: $color-primary;
color: $color-white;
.verticalList & {
margin-left: -($line-height-computed / 2);
margin-right: -($line-height-computed / 2);
padding-left: ($line-height-computed / 2);
padding-right: ($line-height-computed / 2);
}
.horizontalList & {
margin-top: -($line-height-computed / 2);
margin-bottom: -($line-height-computed / 2);
padding-top: ($line-height-computed / 2);
padding-bottom: ($line-height-computed / 2);
@include mq($small-only) {
margin: 0;
padding: ($line-height-computed / 1.5) 0;
margin-left: -($line-height-computed / 2);
margin-right: -($line-height-computed / 2);
padding-left: ($line-height-computed / 2);
padding-right: ($line-height-computed / 2);
}
}
@include mq($small-only) {
border-radius: 0.2rem;
}
.itemIcon,
.itemLabel {
color: inherit;
}
}
&:focus {
box-shadow: 0 0 0 2px $color-white, 0 0 2px 4px rgba($color-primary, .4);
}
}
.itemIcon {

View File

@ -1,5 +1,6 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import { styles } from '../styles';
const propTypes = {
@ -7,17 +8,16 @@ const propTypes = {
};
export default class DropdownListTitle extends Component {
constructor(props) {
super(props);
this.labelID = _.uniqueId('labelContext-');
}
render() {
const { intl, description } = this.props;
const { className, description } = this.props;
return (
<li className={styles.title} aria-describedby={this.labelID}>
<li className={cx(styles.title, className)} aria-describedby={this.labelID}>
{this.props.children}
<div id={this.labelID} aria-label={description} />
</li>

View File

@ -10,6 +10,10 @@ $dropdown-caret-height: 8px;
.dropdown {
position: relative;
&:focus {
outline: none;
}
}
.content {

View File

@ -45,6 +45,7 @@ export default class DropdownTrigger extends Component {
delete remainingProps.dropdownToggle;
delete remainingProps.dropdownShow;
delete remainingProps.dropdownHide;
delete remainingProps.dropdownIsOpen;
const {
children,

View File

@ -1,45 +1,52 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import VideoProviderContainer from '/imports/ui/components/video-provider/container';
import PollingContainer from '/imports/ui/components/polling/container';
import { styles } from './styles';
const propTypes = {
content: PropTypes.element.isRequired,
overlay: PropTypes.element,
children: PropTypes.element.isRequired,
floatingOverlay: PropTypes.bool,
hideOverlay: PropTypes.bool,
};
const defaultProps = {
overlay: null,
floatingOverlay: false,
hideOverlay: true,
};
export default class Media extends Component {
renderContent() {
const { content } = this.props;
return content;
}
renderOverlay() {
const { overlay } = this.props;
if (overlay) {
return (
<div className={styles.overlayWrapper}>
<div className={styles.overlayRatio}>
<div className={styles.overlay}>{overlay}</div>
</div>
</div>
);
}
return false;
componentWillUpdate() {
window.dispatchEvent(new Event('resize'));
}
render() {
const {
swapLayout, floatingOverlay, hideOverlay, disableVideo,
} = this.props;
const contentClassName = cx({
[styles.content]: true,
[styles.hasOverlay]: !hideOverlay,
});
const overlayClassName = cx({
[styles.overlay]: true,
[styles.hideOverlay]: hideOverlay,
[styles.floatingOverlay]: floatingOverlay,
});
return (
<div className={styles.container}>
{this.props.children}
{this.renderContent()}
{this.renderOverlay()}
<div className={!swapLayout ? contentClassName : overlayClassName}>
{this.props.children}
</div>
<div className={!swapLayout ? overlayClassName : contentClassName}>
{ !disableVideo ? <VideoProviderContainer /> : null }
</div>
<PollingContainer />
</div>
);
}

View File

@ -3,19 +3,13 @@ import { withTracker } from 'meteor/react-meteor-data';
import Settings from '/imports/ui/services/settings';
import { defineMessages, injectIntl } from 'react-intl';
import { notify } from '/imports/ui/services/notification';
import VideoService from '/imports/ui/components/video-provider/service';
import Media from './component';
import MediaService from './service';
import MediaService, { getSwapLayout } from './service';
import PresentationAreaContainer from '../presentation/container';
import VideoProviderContainer from '../video-provider/container';
import ScreenshareContainer from '../screenshare/container';
import DefaultContent from '../presentation/default-content/component';
const defaultProps = {
overlay: null,
content: <PresentationAreaContainer />,
defaultContent: <DefaultContent />,
};
const intlMessages = defineMessages({
screenshareStarted: {
id: 'app.media.screenshare.start',
@ -28,18 +22,6 @@ const intlMessages = defineMessages({
});
class MediaContainer extends Component {
constructor(props) {
super(props);
const { overlay, content, defaultContent } = this.props;
this.state = {
overlay,
content: this.props.current_presentation ? content : defaultContent,
};
this.handleToggleLayout = this.handleToggleLayout.bind(this);
}
componentWillReceiveProps(nextProps) {
const {
isScreensharing,
@ -53,29 +35,13 @@ class MediaContainer extends Component {
notify(intl.formatMessage(intlMessages.screenshareEnded), 'info', 'desktop');
}
}
if (nextProps.current_presentation !== this.props.current_presentation) {
if (nextProps.current_presentation) {
this.setState({ content: this.props.content });
} else {
this.setState({ content: this.props.defaultContent });
}
}
}
handleToggleLayout() {
const { overlay, content } = this.state;
this.setState({ overlay: content, content: overlay });
}
render() {
return <Media {...this.props}>{this.props.children}</Media>;
return <Media {...this.props} />;
}
}
MediaContainer.defaultProps = defaultProps;
export default withTracker(() => {
const { dataSaving } = Settings;
const { viewParticipantsWebcams, viewScreenshare } = dataSaving;
@ -83,21 +49,29 @@ export default withTracker(() => {
const data = {};
data.currentPresentation = MediaService.getPresentationInfo();
data.content = <DefaultContent />;
data.children = <DefaultContent />;
if (MediaService.shouldShowWhiteboard()) {
data.content = <PresentationAreaContainer />;
data.children = <PresentationAreaContainer />;
}
if (MediaService.shouldShowScreenshare() && (viewScreenshare || MediaService.isUserPresenter())) {
data.content = <ScreenshareContainer />;
data.children = <ScreenshareContainer />;
}
if (MediaService.shouldShowOverlay() && viewParticipantsWebcams) {
data.overlay = <VideoProviderContainer />;
const usersVideo = VideoService.getAllUsersVideo();
if (MediaService.shouldShowOverlay() && usersVideo.length) {
data.floatingOverlay = usersVideo.length < 2;
data.hideOverlay = usersVideo.length === 0;
}
data.isScreensharing = MediaService.isVideoBroadcasting();
data.swapLayout = getSwapLayout();
data.disableVideo = !viewParticipantsWebcams;
if (data.swapLayout) {
data.floatingOverlay = true;
}
return data;
})(injectIntl(MediaContainer));

View File

@ -2,6 +2,9 @@ import Presentations from '/imports/api/presentations';
import { isVideoBroadcasting } from '/imports/ui/components/screenshare/service';
import Auth from '/imports/ui/services/auth';
import Users from '/imports/api/users';
import Settings from '/imports/ui/services/settings';
import VideoService from '/imports/ui/components/video-provider/service';
import PollingService from '/imports/ui/components/polling/service';
const getPresentationInfo = () => {
const currentPresentation = Presentations.findOne({
@ -10,7 +13,6 @@ const getPresentationInfo = () => {
return {
current_presentation: (currentPresentation != null),
};
};
@ -28,6 +30,32 @@ function shouldShowOverlay() {
return Meteor.settings.public.kurento.enableVideo;
}
const swapLayout = {
value: false,
tracker: new Tracker.Dependency(),
};
const toggleSwapLayout = () => {
swapLayout.value = !swapLayout.value;
swapLayout.tracker.changed();
};
export const shouldEnableSwapLayout = () => {
const { viewParticipantsWebcams } = Settings.dataSaving;
const usersVideo = VideoService.getAllUsersVideo();
const poll = PollingService.mapPolls();
return usersVideo.length > 0 // prevent swap without any webcams
&& viewParticipantsWebcams // prevent swap when dataSaving for webcams is enabled
&& !poll.pollExists; // prevent swap when there is a poll running
};
export const getSwapLayout = () => {
swapLayout.tracker.depend();
return swapLayout.value && shouldEnableSwapLayout();
};
export default {
getPresentationInfo,
shouldShowWhiteboard,
@ -35,4 +63,6 @@ export default {
shouldShowOverlay,
isUserPresenter,
isVideoBroadcasting,
toggleSwapLayout,
shouldEnableSwapLayout,
};

View File

@ -1,69 +1,60 @@
@import "../../stylesheets/variables/_all";
.container {
position: relative;
display: flex;
order: 1;
flex: 2;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column-reverse;
}
.content {
display: flex;
align-self: stretch;
flex: 3;
align-items: center;
justify-content: center;
overflow: auto;
order: 1;
}
%ratio {
width: 100%;
position: relative;
display: block;
height: 0;
padding: 0;
//padding-bottom: calc(100% * 9 / 16);
}
%ratio-item {
position: absolute;
top: 0;
bottom: 0;
left: 0;
width: 100%;
height: 100%;
border: 0;
}
.overlayWrapper {
position: fixed;
width: 100%;
max-width: 170px;
height: 96px;
right: 8px;
bottom: 8px;
@include mq($portrait) {
position: absolute;
width: 100%;
max-width: none;
height: 96px;
top: 60px;
left: 0;
margin: 0 auto;
}
@include mq($large-up) {
flex-basis: 15%;
}
}
.moreThan4Videos{
top: 63px;
right: 0;
position: fixed;
height: calc(100% - 143px);
text-align: right;
}
.overlayRatio {
@extend %ratio;
}
.overlay {
@extend %ratio-item;
flex: 1;
display: flex;
order: 1;
width: 100%;
border: 5px solid transparent;
border-top: 0 !important;
position: relative;
@include mq($medium-up) {
border: 10px solid transparent;
}
}
.hideOverlay {
display: none;
}
$overlay-width: 20vw;
$overlay-min-width: 235px;
$overlay-max-width: 20vw;
$overlay-ratio: 16 / 9;
.floatingOverlay {
@include mq($medium-up) {
z-index: 1;
position: fixed;
margin: 0;
bottom: .8rem;
right: .8rem;
width: $overlay-width;
min-width: $overlay-min-width;
max-width: $overlay-max-width;
height: calc(#{$overlay-width} / #{$overlay-ratio});
min-height: calc(#{$overlay-min-width} / #{$overlay-ratio});
max-height: calc(#{$overlay-max-width} / #{$overlay-ratio});
}
}

View File

@ -1,83 +1,69 @@
import React, { Component } from 'react';
import React from 'react';
import PropTypes from 'prop-types';
import Button from '/imports/ui/components/button/component';
import { defineMessages, injectIntl } from 'react-intl';
import injectWbResizeEvent from '/imports/ui/components/presentation/resize-wrapper/component';
import { defineMessages, injectIntl } from 'react-intl';
import Tooltip from '/imports/ui/components/tooltip/component';
import { styles } from './styles.scss';
const intlMessages = defineMessages({
pollingTitleLabel: {
id: 'app.polling.pollingTitle',
description: 'Title label for polling options',
},
pollAnswerLabel: {
id: 'app.polling.pollAnswerLabel',
},
pollAnswerDesc: {
id: 'app.polling.pollAnswerDesc',
},
});
class PollingComponent extends Component {
getStyles() {
const number = this.props.poll.answers.length + 1;
const buttonStyle =
{
width: `calc(75%/ ${number} )`,
marginLeft: `calc(25%/${number * 2})`,
marginRight: `calc(25%/${number * 2})`,
};
return buttonStyle;
}
render() {
const poll = this.props.poll;
const calculatedStyles = this.getStyles();
const { intl } = this.props;
return (
<div className={styles.pollingContainer} role="alert">
<div className={styles.pollingTitle}>
<p>
{intl.formatMessage(intlMessages.pollingTitleLabel)}
</p>
</div>
{poll.answers.map(pollAnswer =>
(<div
const Polling = ({ intl, poll, handleVote }) => (
<div className={styles.pollingContainer} role="alert">
<div className={styles.pollingTitle}>
{intl.formatMessage(intlMessages.pollingTitleLabel)}
</div>
<div className={styles.pollingAnswers}>
{poll.answers.map(pollAnswer => (
<div
key={pollAnswer.id}
className={styles.pollButtonWrapper}
>
<Tooltip
key={pollAnswer.id}
style={calculatedStyles}
className={styles.pollButtonWrapper}
title={pollAnswer.key}
>
<Tooltip
title={pollAnswer.key}
>
<Button
className={styles.pollingButton}
size="lg"
color="primary"
label={pollAnswer.key}
onClick={() => this.props.handleVote(poll.pollId, pollAnswer)}
aria-labelledby={`pollAnswerLabel${pollAnswer.key}`}
aria-describedby={`pollAnswerDesc${pollAnswer.key}`}
/>
</Tooltip>
<div
className={styles.hidden}
id={`pollAnswerLabel${pollAnswer.key}`}
>
{`Poll answer ${pollAnswer.key}`}
</div>
<div
className={styles.hidden}
id={`pollAnswerDesc${pollAnswer.key}`}
>
{`Select this option to vote for ${pollAnswer.key}`}
</div>
</div>))}
</div>
);
}
}
<Button
className={styles.pollingButton}
color="default"
size="md"
label={pollAnswer.key}
onClick={() => handleVote(poll.pollId, pollAnswer)}
aria-labelledby={`pollAnswerLabel${pollAnswer.key}`}
aria-describedby={`pollAnswerDesc${pollAnswer.key}`}
/>
</Tooltip>
<div
className={styles.hidden}
id={`pollAnswerLabel${pollAnswer.key}`}
>
{intl.formatMessage(intlMessages.pollAnswerLabel, { 0: pollAnswer.key })}
</div>
<div
className={styles.hidden}
id={`pollAnswerDesc${pollAnswer.key}`}
>
{intl.formatMessage(intlMessages.pollAnswerDesc, { 0: pollAnswer.key })}
</div>
</div>
))}
</div>
</div>
);
export default injectWbResizeEvent(injectIntl(PollingComponent));
export default injectIntl(injectWbResizeEvent(Polling));
PollingComponent.propTypes = {
Polling.propTypes = {
intl: PropTypes.shape({
formatMessage: PropTypes.func.isRequired,
}).isRequired,

View File

@ -3,30 +3,52 @@
.pollingContainer {
order: 2;
width: 100%;
margin-top: 1%;
margin-bottom: 1%;
display: flex;
flex-direction: row;
justify-content: center;
}
align-items: center;
flex-direction: column;
padding-bottom: 5px;
.pollingButton {
width: 100%;
height: 100%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
@include mq($medium-up) {
padding-bottom: 10px;
}
}
.pollingTitle {
color: $color-white;
white-space: nowrap;
padding-bottom: 10px;
}
.pollingAnswers {
display: grid;
grid-auto-flow: column;
grid-auto-columns: auto;
justify-content: center;
align-items: center;
width: 100%;
}
.pollButtonWrapper {
text-align: center;
margin-left: 5px;
overflow: hidden;
@include mq($medium-up) {
margin-left: 10px;
}
&:first-of-type {
margin-left: 0;
}
}
.pollingButton {
width: 100%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.hidden {
display: none;
}
.pollButtonWrapper {
text-align: center;
max-width: 150px;
}

View File

@ -3,7 +3,6 @@ import PropTypes from 'prop-types';
import { TransitionGroup, CSSTransition } from 'react-transition-group';
import WhiteboardOverlayContainer from '/imports/ui/components/whiteboard/whiteboard-overlay/container';
import WhiteboardToolbarContainer from '/imports/ui/components/whiteboard/whiteboard-toolbar/container';
import PollingContainer from '/imports/ui/components/polling/container';
import CursorWrapperContainer from './cursor/cursor-wrapper-container/container';
import AnnotationGroupContainer from '../whiteboard/annotation-group/container';
import PresentationToolbarContainer from './presentation-toolbar/container';
@ -155,10 +154,7 @@ export default class PresentationArea extends Component {
style={{
width: adjustedSizes.width,
height: adjustedSizes.height,
WebkitTransition: 'width 0.2s', /* Safari */
transition: 'width 0.2s',
}}
id="presentationAreaData"
>
<TransitionGroup>
<CSSTransition
@ -190,7 +186,6 @@ export default class PresentationArea extends Component {
</defs>
<g clipPath="url(#viewBox)">
<Slide
id="slideComponent"
imageUri={imageUri}
svgWidth={width}
svgHeight={height}
@ -259,6 +254,7 @@ export default class PresentationArea extends Component {
return (
<PresentationToolbarContainer
userIsPresenter={this.props.userIsPresenter}
currentSlideNum={this.props.currentSlide.num}
presentationId={this.props.currentSlide.presentationId}
/>
@ -282,7 +278,7 @@ export default class PresentationArea extends Component {
render() {
return (
<div className={styles.presentationContainer} id="presentationContainer">
<div className={styles.presentationContainer}>
<div
ref={(ref) => { this.refPresentationArea = ref; }}
className={styles.presentationArea}
@ -298,7 +294,6 @@ export default class PresentationArea extends Component {
this.renderWhiteboardToolbar()
: null }
</div>
<PollingContainer />
{this.renderPresentationToolbar()}
</div>
);

View File

@ -1,5 +1,6 @@
import React from 'react';
import { withTracker } from 'meteor/react-meteor-data';
import { getSwapLayout } from '/imports/ui/components/media/service';
import PresentationAreaService from './service';
import PresentationArea from './component';
@ -9,6 +10,6 @@ const PresentationAreaContainer = props => (
export default withTracker(() => ({
currentSlide: PresentationAreaService.getCurrentSlide(),
userIsPresenter: PresentationAreaService.isPresenter(),
multiUser: PresentationAreaService.getMultiUserStatus(),
userIsPresenter: PresentationAreaService.isPresenter() && !getSwapLayout(),
multiUser: PresentationAreaService.getMultiUserStatus() && !getSwapLayout(),
}))(PresentationAreaContainer);

View File

@ -25,11 +25,10 @@ const PresentationToolbarContainer = (props) => {
return null;
};
export default withTracker((params) => {
const data = PresentationToolbarService.getSlideData(params);
export default withTracker(({ presentationId, userIsPresenter, currentSlideNum }) => {
const data = PresentationToolbarService.getSlideData(presentationId);
const {
userIsPresenter,
numberOfSlides,
} = data;
@ -38,9 +37,9 @@ export default withTracker((params) => {
numberOfSlides,
actions: {
nextSlideHandler: () =>
PresentationToolbarService.nextSlide(params.currentSlideNum, numberOfSlides),
PresentationToolbarService.nextSlide(currentSlideNum, numberOfSlides),
previousSlideHandler: () =>
PresentationToolbarService.previousSlide(params.currentSlideNum, numberOfSlides),
PresentationToolbarService.previousSlide(currentSlideNum, numberOfSlides),
skipToSlideHandler: event =>
PresentationToolbarService.skipToSlide(event),
},

View File

@ -1,23 +1,11 @@
import AuthSingleton from '/imports/ui/services/auth';
import Users from '/imports/api/users';
import Slides from '/imports/api/slides';
import { makeCall } from '/imports/ui/services/api';
const getSlideData = (params) => {
const { presentationId } = params;
const getSlideData = (presentationId) => {
// Get userId and meetingId
const userId = AuthSingleton.userID;
const meetingId = AuthSingleton.meetingID;
// Find the user object of this specific meeting and userid
const currentUser = Users.findOne({
meetingId,
userId,
});
const userIsPresenter = currentUser ? currentUser.presenter : false;
// Get total number of slides in this presentation
const numberOfSlides = Slides.find({
meetingId,
@ -25,7 +13,6 @@ const getSlideData = (params) => {
}).fetch().length;
return {
userIsPresenter,
numberOfSlides,
};
};

View File

@ -1,30 +1,28 @@
@import "/imports/ui/components/button/styles.scss";
@import "/imports/ui/stylesheets/variables/_all";
$controls-color: #212121 !default;
$controls-background: #F0F2F6 !default;
$controls-color: $color-gray !default;
$controls-background: $color-white !default;
.presentationToolbarWrapper,
.zoomWrapper {
order: 2;
padding: $line-height-computed / 2;
display: flex;
flex-direction: row;
align-items: center;
}
.presentationToolbarWrapper {
flex: 1;
position: absolute;
bottom: .8rem;
box-shadow: 0 0 10px -2px rgba(0, 0, 0, .25);
align-self: center;
justify-content: center;
width: 100%;
margin-top: 2%;
margin-bottom: 2%;
z-index: 1;
@include mq($portrait) {
@include mq($small-only) {
margin-top: 5%;
margin-bottom: 5%;
}
@include mq("#{$landscape} and (max-height:#{upper-bound($small-range)}), #{$small-only}") {
transform: scale(.75);
transform-origin: bottom;
}
button,
@ -33,18 +31,31 @@ $controls-background: #F0F2F6 !default;
background-color: $controls-background;
color: $controls-color;
border-top: 0;
border-right: $color-gray-light $border-size solid;
border-bottom: 0;
border-left: 0;
border-radius: 0;
box-shadow: none;
height: 2.25rem;
box-shadow: none !important;
border: 0;
}
i {
font-weight: bolder;
color: $color-gray;
}
button:first-of-type {
border-top-left-radius: 5px;
border-bottom-left-radius: 5px;
}
button:last-of-type {
border-top-right-radius: 5px;
border-bottom-right-radius: 5px;
}
}
.zoomWrapper {
@ -61,11 +72,18 @@ $controls-background: #F0F2F6 !default;
.skipSlide,
.prevSlide {
border: none !important;
width: 2.8rem;
&[aria-disabled="true"] {
opacity: 1;
background-color: $color-gray-lighter;
}
}
.skipSlideSelect {
padding: 0 0.8%;
border-left: $border-size solid $color-gray-light !important;
border-left: $border-size solid $color-gray-lighter !important;
border-right: $border-size solid $color-gray-lighter !important;
}
.zoomSlider {

View File

@ -1,37 +1,33 @@
import React from 'react';
import PropTypes from 'prop-types';
const Slide = (props) => {
const { imageUri, svgWidth, svgHeight } = props;
return (
<g>
{imageUri ?
// some pdfs lose a white background color during the conversion to svg
// their background color is transparent
// that's why we have a white rectangle covering the whole slide area by default
<g>
<rect
x="1"
y="1"
width={svgWidth - 2}
height={svgHeight - 2}
fill="white"
/>
<image
x="0"
y="0"
width={svgWidth}
height={svgHeight}
xlinkHref={imageUri}
strokeWidth="0.8"
style={{ WebkitTapHighlightColor: 'transparent' }}
/>
</g>
: null}
</g>
);
};
const Slide = ({ imageUri, svgWidth, svgHeight }) => (
<g>
{imageUri ?
// some pdfs lose a white background color during the conversion to svg
// their background color is transparent
// that's why we have a white rectangle covering the whole slide area by default
<g>
<rect
x="0"
y="0"
width={svgWidth}
height={svgHeight}
fill="white"
/>
<image
x="0"
y="0"
width={svgWidth}
height={svgHeight}
xlinkHref={imageUri}
strokeWidth="0.8"
style={{ WebkitTapHighlightColor: 'transparent' }}
/>
</g>
: null}
</g>
);
Slide.propTypes = {
// Image Uri

View File

@ -25,7 +25,7 @@
flex-direction: row;
align-items: center;
justify-content: center;
height: calc(100% - 1px);
height: 100%;
width:100%;
overflow: hidden;
position: relative;
@ -34,7 +34,7 @@
.whiteboardSizeAvailable {
position: absolute;
height: 100%;
width: calc(100% - #{$toolbar-container-width});
width: 100%;
z-index: -1;
}
@ -58,6 +58,9 @@
.presentationContainer {
display: flex;
flex-direction: column;
height: 100%;
width: 100%;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}

View File

@ -11,6 +11,7 @@ export default class ScreenshareComponent extends React.Component {
}
componentWillUnmount() {
this.props.presenterScreenshareHasEnded();
this.props.unshareScreen();
}
render() {

View File

@ -217,10 +217,11 @@ class Settings extends Component {
title={intl.formatMessage(intlMessages.SettingsLabel)}
confirm={{
callback: () => {
if (location.pathname.includes('/users')) {
router.push('/');
}
this.updateSettings(this.state.current);
/* We need to use mountModal(null) here to prevent submenu state updates,
* from re-opening the modal.
*/
this.props.mountModal(null);
},
label: intl.formatMessage(intlMessages.SaveLabel),
description: intl.formatMessage(intlMessages.SaveLabelDesc),

View File

@ -191,7 +191,7 @@ class ApplicationMenu extends BaseMenu {
>
{availableLocales && availableLocales.length > 0 ? (
<select
defaultValue={this.formatLocale(this.state.settings.locale)}
defaultValue={this.state.settings.locale}
className={styles.select}
onChange={this.handleSelectChange.bind(this, 'locale', availableLocales)}
>

View File

@ -18,6 +18,14 @@ const defaultProps = {
};
class Tooltip extends Component {
static wait(show, event) {
const tooltipTarget = event.target;
const expandedEl = tooltipTarget.parentElement.querySelector('[aria-expanded="true"]');
const isTarget = expandedEl === tooltipTarget;
if (expandedEl && !isTarget) return;
show();
}
constructor(props) {
super(props);
@ -40,12 +48,11 @@ class Tooltip extends Component {
delay: this.delay,
onShow: this.onShow,
onHide: this.onHide,
wait: Tooltip.wait,
touchHold: true,
};
this.tooltip = Tippy(`#${this.tippySelectorId}`, options);
}
onShow() {
document.addEventListener('keyup', this.handleEscapeHide);
}
@ -56,7 +63,6 @@ class Tooltip extends Component {
handleEscapeHide(e) {
if (e.keyCode !== ESCAPE) return;
this.tooltip.tooltips[0].hide();
}

View File

@ -44,6 +44,8 @@
margin-left: $sm-padding-x;
position: relative;
top: $md-padding-y;
padding: .5rem 0;
margin-top: -.5rem;
}
.active {

View File

@ -18,6 +18,7 @@
transition: all 0.3s;
font-weight: 400;
color: $color-gray-dark;
padding: .5rem 0;
}
.userNameSub {
@ -34,5 +35,3 @@
font-size: 75%;
}
}

View File

@ -8,7 +8,7 @@ import Toast from '/imports/ui/components/toast/component';
import _ from 'lodash';
import VideoService from './service';
import VideoDockContainer from './video-dock/container';
import VideoList from './video-list/component';
const intlMessages = defineMessages({
iceCandidateError: {
@ -53,6 +53,7 @@ class VideoProvider extends Component {
this.cameraTimeouts = {};
this.webRtcPeers = {};
this.openWs = this.ws.open.bind(this.ws);
this.onWsOpen = this.onWsOpen.bind(this);
this.onWsClose = this.onWsClose.bind(this);
this.onWsMessage = this.onWsMessage.bind(this);
@ -65,17 +66,36 @@ class VideoProvider extends Component {
this.ws.addEventListener('open', this.onWsOpen);
this.ws.addEventListener('close', this.onWsClose);
window.addEventListener('online', this.ws.open.bind(this.ws));
window.addEventListener('online', this.openWs);
window.addEventListener('offline', this.onWsClose);
}
componentDidMount() {
document.addEventListener('joinVideo', this.shareWebcam.bind(this)); // TODO find a better way to do this
document.addEventListener('exitVideo', this.unshareWebcam.bind(this));
document.addEventListener('joinVideo', this.shareWebcam); // TODO find a better way to do this
document.addEventListener('exitVideo', this.unshareWebcam);
this.ws.addEventListener('message', this.onWsMessage);
}
shouldComponentUpdate({ users: nextUsers }, nextState) {
const { users } = this.props;
return !_.isEqual(this.state, nextState) || users.length !== nextUsers.length;
}
componentWillUpdate({ users, userId }) {
const usersSharingIds = users.map(u => u.id);
const usersConnected = Object.keys(this.webRtcPeers);
const usersToConnect = usersSharingIds.filter(id => !usersConnected.includes(id));
const usersToDisconnect = usersConnected.filter(id => !usersSharingIds.includes(id));
usersToConnect.forEach(id => this.createWebRTCPeer(id, userId === id));
usersToDisconnect.forEach(id => this.stopWebRTCPeer(id));
console.warn('[usersToConnect]', usersToConnect);
console.warn('[usersToDisconnect]', usersToDisconnect);
}
componentWillUnmount() {
document.removeEventListener('joinVideo', this.shareWebcam);
document.removeEventListener('exitVideo', this.unshareWebcam);
@ -84,21 +104,21 @@ class VideoProvider extends Component {
this.ws.removeEventListener('open', this.onWsOpen);
this.ws.removeEventListener('close', this.onWsClose);
window.removeEventListener('online', this.ws.open.bind(this.ws));
window.removeEventListener('online', this.openWs);
window.removeEventListener('offline', this.onWsClose);
// Unshare user webcam
if (this.state.sharedWebcam) {
this.unshareWebcam();
this.stop(this.props.userId);
}
Object.keys(this.webRtcPeers).forEach((id) => {
this.destroyWebRTCPeer(id);
this.stopWebRTCPeer(id);
});
// Close websocket connection to prevent multiple reconnects from happening
this.ws.close();
// Don't disonnect socket on unmount to prevent multiple reconnects
// this.ws.close();
}
onWsOpen() {
@ -115,8 +135,7 @@ class VideoProvider extends Component {
onWsClose(error) {
log('debug', '------ Websocket connection closed.');
this.unshareWebcam();
VideoService.exitedVideo();
this.stopWebRTCPeer(this.props.userId);
this.setState({ socketOpen: false });
}
@ -145,7 +164,6 @@ class VideoProvider extends Component {
case 'playStop':
this.handlePlayStop(parsedMessage);
break;
case 'iceCandidate':
@ -224,20 +242,32 @@ class VideoProvider extends Component {
}
}
destroyWebRTCPeer(id) {
const webRtcPeer = this.webRtcPeers[id];
stopWebRTCPeer(id) {
log('info', 'Stopping webcam', id);
const userId = this.props.userId
const shareWebcam = id === userId;
if (shareWebcam) {
this.unshareWebcam();
}
this.sendMessage({
type: 'video',
role: shareWebcam ? 'share' : 'viewer',
id: 'stop',
cameraId: id,
});
// Clear the shared camera fail timeout when destroying
clearTimeout(this.cameraTimeouts[id]);
this.cameraTimeouts[id] = null;
delete this.cameraTimeouts[id];
this.destroyWebRTCPeer(id);
}
destroyWebRTCPeer(id) {
const webRtcPeer = this.webRtcPeers[id];
if (webRtcPeer) {
log('info', 'Stopping WebRTC peer');
if (id == this.props.userId && this.state.sharedWebcam) {
this.setState({ sharedWebcam: false });
}
webRtcPeer.dispose();
delete this.webRtcPeers[id];
} else {
@ -245,28 +275,43 @@ class VideoProvider extends Component {
}
}
initWebRTC(id, shareWebcam, videoOptions, tag) {
createWebRTCPeer(id, shareWebcam) {
const that = this;
const { intl, meetingId } = this.props;
const videoConstraints = {
width: {
min: 320,
max: 640,
},
height: {
min: 180,
max: 480,
},
};
if (!navigator.userAgent.match(/Version\/[\d\.]+.*Safari/)) {
videoConstraints.frameRate = { min: 5, ideal: 10 };
}
const options = {
mediaConstraints: {
audio: false,
video: videoOptions,
video: videoConstraints,
},
onicecandidate: this.getOnIceCandidateCallback(id, shareWebcam),
};
let peerObj;
let WebRtcPeerObj = window.kurentoUtils.WebRtcPeer.WebRtcPeerRecvonly;
if (shareWebcam) {
peerObj = kurentoUtils.WebRtcPeer.WebRtcPeerSendonly;
options.localVideo = tag;
} else {
peerObj = kurentoUtils.WebRtcPeer.WebRtcPeerRecvonly;
options.remoteVideo = tag;
WebRtcPeerObj = window.kurentoUtils.WebRtcPeer.WebRtcPeerSendonly;
this.shareWebcam();
}
const webRtcPeer = new peerObj(options, function (error) {
WebRtcPeerObj.started = false;
that.webRtcPeers[id] = new WebRtcPeerObj(options, function (error) {
if (error) {
log('error', ' WebRTC peerObj create error');
log('error', error);
@ -276,12 +321,10 @@ class VideoProvider extends Component {
* or the platform in the current context.', but there are other
* errors that could be returned. */
that.destroyWebRTCPeer(id);
that.stopWebRTCPeer(id);
if (shareWebcam) {
that.unshareWebcam();
VideoService.exitVideo();
VideoService.exitedVideo();
}
return log('error', error);
}
@ -289,28 +332,23 @@ class VideoProvider extends Component {
this.didSDPAnswered = false;
this.iceQueue = [];
that.webRtcPeers[id] = webRtcPeer;
if (shareWebcam) {
that.sharedWebcam = webRtcPeer;
}
this.generateOffer((error, offerSdp) => {
if (error) {
log('error', ' WebRtc generate offer error');
that.destroyWebRTCPeer(id);
that.stopWebRTCPeer(id);
return log('error', error);
}
that.cameraTimeouts[id] = setTimeout(() => {
log('error', `Camera share has not suceeded in ${CAMERA_SHARE_FAILED_WAIT_TIME}`);
log('error', `Camera share has not suceeded in ${CAMERA_SHARE_FAILED_WAIT_TIME} for ${id}`);
if (that.props.userId == id) {
that.notifyError(intl.formatMessage(intlMessages.sharingError));
that.unshareWebcam();
VideoService.exitedVideo();
that.destroyWebRTCPeer(id);
} else {
that.stop(id);
that.initWebRTC(id, shareWebcam, videoOptions, tag);
that.stopWebRTCPeer(id);
that.createWebRTCPeer(id, shareWebcam);
}
}, CAMERA_SHARE_FAILED_WAIT_TIME);
@ -353,38 +391,49 @@ class VideoProvider extends Component {
};
}
stop(id) {
const userId = this.props.userId;
attachVideoStream(id, video) {
if (video.srcObject) return; // Skip if the stream is already attached
if (id === userId) {
this.sendMessage({
type: 'video',
role: id == userId ? 'share' : 'viewer',
id: 'stop',
cameraId: id,
});
const isCurrent = id === this.props.userId;
const peer = this.webRtcPeers[id];
const { peerConnection } = peer;
this.unshareWebcam();
VideoService.exitedVideo();
const attachVideoStream = () => {
const stream = isCurrent ? peer.getLocalStream() : peer.getRemoteStream();
video.pause();
video.srcObject = stream;
video.load();
};
if (peer) {
if (peer.started === true) {
attachVideoStream();
return;
}
peer.on('playStart', attachVideoStream);
}
this.destroyWebRTCPeer(id);
}
handlePlayStop(message) {
const id = message.cameraId;
log('info', 'Handle play stop <--------------------');
log('error', message);
this.stop(id);
log('info', 'Handle play stop for camera', id);
this.stopWebRTCPeer(id);
}
handlePlayStart(message) {
log('info', 'Handle play start <===================');
const id = message.cameraId;
log('info', 'Handle play start for camera', id);
const peer = this.webRtcPeers[id];
// Clear camera shared timeout when camera succesfully starts
clearTimeout(this.cameraTimeouts[message.cameraId]);
this.cameraTimeouts[message.cameraId] = null;
clearTimeout(this.cameraTimeouts[id]);
this.cameraTimeouts[id] = null;
peer.emit('playStart');
peer.started = true;
if (message.cameraId == this.props.userId) {
VideoService.joinedVideo();
@ -397,10 +446,9 @@ class VideoProvider extends Component {
if (message.cameraId == userId) {
this.unshareWebcam();
VideoService.exitedVideo();
this.notifyError(intl.formatMessage(intlMessages.sharingError));
} else {
this.stop(message.cameraId);
this.stopWebRTCPeer(message.cameraId);
}
console.error(' Handle error --------------------->');
@ -412,13 +460,14 @@ class VideoProvider extends Component {
}
shareWebcam() {
let { intl } = this.props;
log('info', 'Sharing webcam');
const { intl } = this.props;
if (this.connectedToMediaServer()) {
log('info', 'Sharing webcam');
this.setState({ sharedWebcam: true });
VideoService.joiningVideo();
} else {
log('debug', 'Error on sharing webcam');
this.notifyError(intl.formatMessage(intlMessages.sharingError));
}
}
@ -426,20 +475,18 @@ class VideoProvider extends Component {
unshareWebcam() {
log('info', 'Unsharing webcam');
this.setState({ ...this.state, sharedWebcam: false });
VideoService.sendUserUnshareWebcam(this.props.userId);
VideoService.exitedVideo();
this.setState({ sharedWebcam: false });
}
render() {
if (!this.state.socketOpen) return null;
return (
<VideoDockContainer
onStart={this.initWebRTC.bind(this)}
onStop={this.stop.bind(this)}
sharedWebcam={this.state.sharedWebcam}
onShareWebcam={this.shareWebcam.bind(this)}
socketOpen={this.state.socketOpen}
isLocked={this.props.isLocked}
<VideoList
users={this.props.users}
onMount={this.attachVideoStream.bind(this)}
/>
);
}

View File

@ -3,10 +3,11 @@ import { withTracker } from 'meteor/react-meteor-data';
import VideoProvider from './component';
import VideoService from './service';
const VideoProviderContainer = ({ children, ...props }) => <VideoProvider {...props}>{children}</VideoProvider>;
const VideoProviderContainer = ({ children, ...props }) =>
(!props.users.length ? null : <VideoProvider {...props}>{children}</VideoProvider>);
export default withTracker(() => ({
isLocked: VideoService.isLocked(),
meetingId: VideoService.meetingId(),
users: VideoService.getAllUsersVideo(),
userId: VideoService.userId(),
}))(VideoProviderContainer);

View File

@ -1,12 +1,15 @@
import { Tracker } from 'meteor/tracker';
import { makeCall } from '/imports/ui/services/api';
import Users from '/imports/api/users';
import Meetings from '/imports/api/meetings/';
import Auth from '/imports/ui/services/auth';
import Meetings from '/imports/api/meetings/';
import Users from '/imports/api/users/';
import mapUser from '/imports/ui/services/user/mapUser';
import UserListService from '/imports/ui/components/user-list/service';
class VideoService {
constructor() {
this.defineProperties({
isSharing: false,
isConnected: false,
isWaitingResponse: false,
});
@ -34,6 +37,7 @@ class VideoService {
}
joinVideo() {
this.isSharing = true;
const joinVideoEvent = new Event('joinVideo');
document.dispatchEvent(joinVideoEvent);
}
@ -53,6 +57,8 @@ class VideoService {
}
exitedVideo() {
console.warn('exitedVideo');
this.isSharing = false;
this.isWaitingResponse = false;
this.isConnected = false;
}
@ -62,21 +68,44 @@ class VideoService {
}
sendUserUnshareWebcam(stream) {
this.isWaitingResponse = true;
makeCall('userUnshareWebcam', stream);
}
getAllUsers() {
return Users.find().fetch();
// Use the same function as the user-list to share the sorting/mapping
return UserListService.getUsers();
}
getAllUsersVideo() {
const userId = this.userId();
const isLocked = this.isLocked();
const currentUser = Users.findOne({ userId });
const currentUserIsModerator = mapUser(currentUser).isModerator;
const sharedWebcam = this.isSharing;
const isSharingWebcam = user => user.isSharingWebcam || (sharedWebcam && user.isCurrent);
const isNotLocked = user => !(isLocked && user.isLocked);
const isWebcamOnlyModerator = this.webcamOnlyModerator();
const allowedSeeViewersWebcams = !isWebcamOnlyModerator || currentUserIsModerator;
const webcamOnlyModerator = (user) => {
if (allowedSeeViewersWebcams) return true;
return user.isModerator || user.isCurrent;
};
return this.getAllUsers()
.filter(isSharingWebcam)
.filter(isNotLocked)
.filter(webcamOnlyModerator);
}
webcamOnlyModerator() {
const m = Meetings.findOne({meetingId: Auth.meetingID});
const m = Meetings.findOne({ meetingId: Auth.meetingID });
return m.usersProp.webcamsOnlyForModerator;
}
isLocked() {
const m = Meetings.findOne({meetingId: Auth.meetingID});
const m = Meetings.findOne({ meetingId: Auth.meetingID });
return m.lockSettingsProp ? m.lockSettingsProp.disableCam : false;
}
@ -105,7 +134,7 @@ export default {
exitedVideo: () => videoService.exitedVideo(),
getAllUsers: () => videoService.getAllUsers(),
webcamOnlyModerator: () => videoService.webcamOnlyModerator(),
isLocked: () => videoService.isLocked(),
isLocked: () => videoService.isLocked(),
isConnected: () => videoService.isConnected,
isWaitingResponse: () => videoService.isWaitingResponse,
joinVideo: () => videoService.joinVideo(),
@ -115,4 +144,5 @@ export default {
sendUserUnshareWebcam: stream => videoService.sendUserUnshareWebcam(stream),
userId: () => videoService.userId(),
meetingId: () => videoService.meetingId(),
getAllUsersVideo: () => videoService.getAllUsersVideo(),
};

View File

@ -1,88 +1,18 @@
@import "/imports/ui/stylesheets/variables/_all";
.videoDock {
position: absolute;
width: 100%;
top: 0;
right: 0;
background-size: cover;
background-position: center;
text-align: center;
@include mq($portrait) {
height: 96px;
}
}
.videoDock video {
height: auto;
width: auto;
max-height: 100%;
max-width: 100%;
display: block;
margin: 0 auto;
}
.webcamArea {
@include mq($portrait) {
position: absolute;
margin: 0 auto;
left: 0;
right: 0;
bottom: 0;
background: #04111F;
}
}
.sharedWebcamVideo {
display: none;
vertical-align: middle;
}
.sharedWebcamVideoLocal {
display: inline-block;
}
.videoContainer {
background-color: #000;
}
.videoText {
margin-top: -22px;
padding: 0 5px;
z-index: 1;
text-align: left;
}
.userName {
color: #FFFFFF;
background: #555;
padding: 3px 8px;
border-radius: 0.75rem;
font-size: 10px;
line-height: 13px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
position: relative;
display: inline-block;
max-width: 100%;
}
.pauseButton {
float: right;
span:last-child {
display: none;
} // TODO fix it better
span:first-child {
background: #D8D8D8;
border: 1px solid #555;
i {
font-size: 11px; // TODO size should be set on button?
color: #555;
}
}
}
// .webcam {
// position: relative;
//
// &:before {
// display: block;
// content: "";
// width: 100%;
// padding-top: ($height / $width) * 100%;
// }
//
// > .content {
// position: absolute;
// top: 0;
// left: 0;
// right: 0;
// bottom: 0;
// }
// }

View File

@ -8,7 +8,7 @@ import { styles as mediaStyles } from '/imports/ui/components/media/styles';
import Toast from '/imports/ui/components/toast/component';
import _ from 'lodash';
import VideoElement from '../video-element/component';
import VideoList from '../video-list/component';
const intlMessages = defineMessages({
chromeExtensionError: {
@ -32,21 +32,12 @@ class VideoDock extends Component {
const { users, userId } = this.props;
document.addEventListener('installChromeExtension', this.installChromeExtension.bind(this));
window.addEventListener('resize', this.adjustVideos);
window.addEventListener('orientationchange', this.adjustVideos);
}
componentWillUnmount() {
window.removeEventListener('resize', this.adjustVideos);
window.removeEventListener('orientationchange', this.adjustVideos);
document.removeEventListener('installChromeExtension', this.installChromeExtension.bind(this));
}
componentDidUpdate() {
this.adjustVideos();
}
notifyError(message) {
notify(message, 'error', 'video');
}
@ -64,40 +55,18 @@ class VideoDock extends Component {
</div>);
}
// TODO
// Find a better place to put this piece of code
adjustVideos() {
setTimeout(() => {
window.adjustVideos('webcamArea', true, mediaStyles.moreThan4Videos, mediaStyles.container, mediaStyles.overlayWrapper, 'presentationAreaData', 'screenshareVideo');
}, 0);
}
render() {
if (!this.props.socketOpen) {
// TODO: return something when disconnected
return null;
}
const id = this.props.userId;
const sharedWebcam = this.props.sharedWebcam;
return (
<div className={styles.videoDock} id={this.props.sharedWebcam.toString()}>
<div id="webcamArea" className={styles.webcamArea}>
{this.props.users.map(user => (
<VideoElement
shared={id === user.id && sharedWebcam}
videoId={user.id}
key={user.id}
name={user.name}
localCamera={id === user.id}
onShareWebcam={this.props.onShareWebcam.bind(this)}
onMount={this.props.onStart.bind(this)}
onUnmount={this.props.onStop.bind(this)}
/>
))}
</div>
</div>
<VideoList
users={this.props.users}
onMount={this.props.onStart}
onUnmount={this.props.onStop}
/>
);
}
}

View File

@ -1,40 +1,11 @@
import React from 'react';
import { withTracker } from 'meteor/react-meteor-data';
import Auth from '/imports/ui/services/auth';
import Meetings from '/imports/api/meetings/';
import Users from '/imports/api/users/';
import mapUser from '/imports/ui/services/user/mapUser';
import VideoDock from './component';
import VideoService from '../service';
const VideoDockContainer = ({ children, ...props }) => <VideoDock {...props}>{children}</VideoDock>;
export default withTracker(({ sharedWebcam }) => {
const meeting = Meetings.findOne({ meetingId: Auth.meetingID });
const lockCam = meeting.lockSettingsProp ? meeting.lockSettingsProp.disableCam : false;
const userId = Auth.userID;
const currentUser = Users.findOne({ userId });
const currentUserIsModerator = mapUser(currentUser).isModerator;
const isSharingWebcam = user => user.isSharingWebcam || (sharedWebcam && user.isCurrent);
const isNotLocked = user => !(lockCam && user.isLocked);
const isWebcamOnlyModerator = VideoService.webcamOnlyModerator();
const allowedSeeViewersWebcams = !isWebcamOnlyModerator || currentUserIsModerator;
const webcamOnlyModerator = (user) => {
if (allowedSeeViewersWebcams) return true;
return user.isModerator || user.isCurrent;
};
const users = VideoService.getAllUsers()
.map(mapUser)
.filter(isSharingWebcam)
.filter(isNotLocked)
.filter(webcamOnlyModerator);
return {
users,
userId,
};
})(VideoDockContainer);
export default withTracker(() => ({
users: VideoService.getAllUsersVideo(),
userId: VideoService.userId(),
}))(VideoDockContainer);

View File

@ -1,68 +0,0 @@
import React, { Component } from 'react';
import cx from 'classnames';
import { styles } from '../styles';
class VideoElement extends Component {
constructor(props) {
super(props);
}
render() {
const tagId = this.props.localCamera ? 'shareWebcam' : `video-elem-${this.props.videoId}`;
return (
<div className={cx({
[styles.videoContainer]: true,
[styles.sharedWebcamVideo]: !this.props.shared && this.props.localCamera,
[styles.sharedWebcamVideoLocal]: this.props.shared || !this.props.localCamera })}>
<video id={tagId} muted={this.props.localCamera} autoPlay playsInline />
<div className={styles.videoText}>
<div className={styles.userName}>{this.props.name}</div>
</div>
</div>
);
}
componentDidMount() {
const { videoId, localCamera } = this.props;
const tagId = localCamera ? 'shareWebcam' : `video-elem-${videoId}`;
const tag = document.getElementById(tagId);
if (localCamera && this.props.onShareWebcam === 'function') {
this.props.onShareWebcam();
}
if (typeof this.props.onMount === 'function') {
this.props.onMount(videoId, localCamera, this.getVideoConstraints(), tag);
}
}
componentWillUnmount() {
if (typeof this.props.onUnmount === 'function') {
this.props.onUnmount(this.props.videoId);
}
}
getVideoConstraints() {
let videoConstraints = {
width: {
min: 320,
max: 640,
},
height: {
min: 240,
max: 480,
},
};
if (!navigator.userAgent.match(/Version\/[\d\.]+.*Safari/)) {
videoConstraints.frameRate = { min: 5, ideal: 10, };
}
return videoConstraints;
}
}
export default VideoElement;

View File

@ -0,0 +1,208 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { defineMessages, injectIntl } from 'react-intl';
import _ from 'lodash';
import cx from 'classnames';
import { styles } from './styles';
import VideoListItem from './video-list-item/component';
const propTypes = {
users: PropTypes.arrayOf(PropTypes.object).isRequired,
onMount: PropTypes.func.isRequired,
};
const intlMessages = defineMessages({
focusLabel: {
id: 'app.videoDock.webcamFocusLabel',
},
focusDesc: {
id: 'app.videoDock.webcamFocusDesc',
},
unfocusLabel: {
id: 'app.videoDock.webcamUnfocusLabel',
},
unfocusDesc: {
id: 'app.videoDock.webcamUnfocusDesc',
},
});
// See: https://stackoverflow.com/a/3513565
const findOptimalGrid = (canvasWidth, canvasHeight, gutter, aspectRatio, numItems, columns = 1) => {
const rows = Math.ceil(numItems / columns);
const gutterTotalWidth = (columns - 1) * gutter;
const gutterTotalHeight = (rows - 1) * gutter;
const usableWidth = canvasWidth - gutterTotalWidth;
const usableHeight = canvasHeight - gutterTotalHeight;
let cellWidth = Math.floor(usableWidth / columns);
let cellHeight = Math.ceil(cellWidth / aspectRatio);
if ((cellHeight * rows) > usableHeight) {
cellHeight = Math.floor(usableHeight / rows);
cellWidth = Math.ceil(cellHeight * aspectRatio);
}
return {
columns,
rows,
width: (cellWidth * columns) + gutterTotalWidth,
height: (cellHeight * rows) + gutterTotalHeight,
filledArea: (cellWidth * cellHeight) * numItems,
};
};
class VideoList extends Component {
constructor(props) {
super(props);
this.state = {
focusedId: false,
optimalGrid: {
cols: 1,
rows: 1,
filledArea: 0,
},
};
this.ticking = false;
this.grid = null;
this.canvas = null;
this.handleCanvasResize = _.throttle(this.handleCanvasResize.bind(this), 66);
this.setOptimalGrid = this.setOptimalGrid.bind(this);
}
componentDidMount() {
this.handleCanvasResize();
window.addEventListener('resize', this.handleCanvasResize, false);
}
componentWillUnmount() {
window.removeEventListener('resize', this.handleCanvasResize, false);
}
setOptimalGrid() {
let numItems = this.props.users.length;
if (numItems < 1 || !this.canvas || !this.grid) {
return;
}
const { focusedId } = this.state;
const aspectRatio = 16 / 9;
const { width: canvasWidth, height: canvasHeight } = this.canvas.getBoundingClientRect();
const gridGutter = parseInt(window.getComputedStyle(this.grid).getPropertyValue('grid-row-gap'), 10);
const hasFocusedItem = numItems > 2 && focusedId;
// Has a focused item so we need +3 cells
if (hasFocusedItem) {
numItems += 3;
}
const optimalGrid = _.range(1, numItems + 1).reduce((currentGrid, col) => {
const testGrid = findOptimalGrid(
canvasWidth, canvasHeight, gridGutter,
aspectRatio, numItems, col,
);
// We need a minimun of 2 rows and columns for the focused
const focusedConstraint = hasFocusedItem ? testGrid.rows > 1 && testGrid.columns > 1 : true;
const betterThanCurrent = testGrid.filledArea > currentGrid.filledArea;
return focusedConstraint && betterThanCurrent ? testGrid : currentGrid;
}, { filledArea: 0 });
this.setState({
optimalGrid,
});
}
handleVideoFocus(id) {
const { focusedId } = this.state;
this.setState({
focusedId: focusedId !== id ? id : false,
}, this.handleCanvasResize);
}
handleCanvasResize() {
if (!this.ticking) {
window.requestAnimationFrame(() => {
this.ticking = false;
this.setOptimalGrid();
});
}
this.ticking = true;
}
renderVideoList() {
const {
intl, users, onMount,
} = this.props;
const { focusedId } = this.state;
return users.map((user) => {
const isFocused = focusedId === user.id;
const isFocusedIntlKey = !isFocused ? 'focus' : 'unfocus';
const actions = [{
label: intl.formatMessage(intlMessages[`${isFocusedIntlKey}Label`]),
description: intl.formatMessage(intlMessages[`${isFocusedIntlKey}Desc`]),
onClick: () => this.handleVideoFocus(user.id),
disabled: users.length < 2,
}];
return (
<div
key={user.id}
className={cx({
[styles.videoListItem]: true,
[styles.focused]: focusedId === user.id && users.length > 2,
})}
>
<VideoListItem
user={user}
actions={actions}
onMount={(videoRef) => {
this.handleCanvasResize();
return onMount(user.id, videoRef);
}}
/>
</div>
);
});
}
render() {
const { users } = this.props;
const { optimalGrid } = this.state;
return (
<div
ref={(ref) => { this.canvas = ref; }}
className={styles.videoCanvas}
>
{!users.length ? null : (
<div
ref={(ref) => { this.grid = ref; }}
className={styles.videoList}
style={{
width: `${optimalGrid.width}px`,
height: `${optimalGrid.height}px`,
gridTemplateColumns: `repeat(${optimalGrid.columns}, 1fr)`,
gridTemplateRows: `repeat(${optimalGrid.rows}, 1fr)`,
}}
>
{this.renderVideoList()}
</div>
)}
</div>
);
}
}
VideoList.propTypes = propTypes;
export default injectIntl(VideoList);

View File

@ -0,0 +1,175 @@
@import "/imports/ui/stylesheets/variables/_all";
.videoCanvas {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
}
.videoList {
display: grid;
overflow: hidden;
grid-auto-flow: dense;
grid-gap: 5px;
@include mq($medium-up) {
grid-gap: 10px;
}
}
.videoListItem {
overflow: hidden;
display: flex;
&.focused {
grid-column: 1 / span 2;
grid-row: 1 / span 2;
}
}
.content {
position: relative;
display: flex;
width: 100%;
height: 100%;
border-radius: 5px;
&::after {
content: "";
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
border: 5px solid rgba($color-white, .25);
border-radius: 5px;
opacity: 0;
transition: opacity .1s;
pointer-events: none;
}
&.talking::after {
opacity: 1;
}
}
%media-area {
position: relative;
height: 100%;
width: 100%;
object-fit: contain;
border-radius: 5px;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.connecting {
@extend %media-area;
position: absolute;
background-color: $color-gray;
color: rgba($color-white, .25);
font-size: 2.5rem;
text-align: center;
white-space: nowrap;
&::after {
content: '';
display: inline-block;
height: 100%;
vertical-align: middle;
margin-right: -0.25em;
}
&::before {
content: "\e949"; /* ascii code for the ellipsis character */
font-family: 'bbb-icons' !important;
display: inline-block;
animation: spin 4s infinite linear;
}
}
.media {
@extend %media-area;
}
.info {
position: absolute;
display: flex;
bottom: 5px;
left: 5px;
right: 5px;
justify-content: center;
align-items: center;
height: 1.25rem;
}
.dropdown {
flex: 1;
display: flex;
outline: none !important;
@include mq($medium-up) {
> [aria-expanded] {
padding: .25rem;
}
}
}
.dropdownTrigger {
@extend %text-elipsis;
position: relative;
background-color: rgba($color-gray, .5);
color: $color-white;
font-size: 80%;
border-radius: 15px;
padding: 0 1rem 0 .5rem;
max-width: 50%;
&::after {
content: "\203a";
position: absolute;
transform: rotate(90deg);
top: 45%;
width: 0;
line-height: 0;
right: .45rem;
}
}
.dropdownList {
@include mq($medium-up) {
font-size: .86rem;
}
}
.hiddenDesktop {
display: none;
@include mq($small-only) {
display: block;
}
}
.muted {
display: inline-block;
width: 1.25rem;
height: 1.25rem;
color: $color-white;
background-color: $color-danger;
border-radius: 50%;
&::before {
font-size: 75%;
}
}

View File

@ -0,0 +1,62 @@
import React, { Component } from 'react';
import cx from 'classnames';
import Dropdown from '/imports/ui/components/dropdown/component';
import DropdownTrigger from '/imports/ui/components/dropdown/trigger/component';
import DropdownContent from '/imports/ui/components/dropdown/content/component';
import DropdownList from '/imports/ui/components/dropdown/list/component';
import DropdownListTitle from '/imports/ui/components/dropdown/list/title/component';
import DropdownListSeparator from '/imports/ui/components/dropdown/list/separator/component';
import DropdownListItem from '/imports/ui/components/dropdown/list/item/component';
import Icon from '/imports/ui/components/icon/component';
import { styles } from '../styles';
class VideoListItem extends Component {
constructor(props) {
super(props);
this.videoTag = null;
}
componentDidMount() {
this.props.onMount(this.videoTag);
}
render() {
const { user, actions } = this.props;
return (
<div className={cx({
[styles.content]: true,
[styles.talking]: user.isTalking,
})}
>
<div className={styles.connecting} />
<video
className={styles.media}
ref={(ref) => { this.videoTag = ref; }}
muted={user.isCurrent}
autoPlay
playsInline
/>
<div className={styles.info}>
<Dropdown className={styles.dropdown}>
<DropdownTrigger className={styles.dropdownTrigger}>
<span>{user.name}</span>
</DropdownTrigger>
<DropdownContent placement="top left">
<DropdownList className={styles.dropdownList}>
{[
<DropdownListTitle className={styles.hiddenDesktop} key="name">{user.name}</DropdownListTitle>,
<DropdownListSeparator className={styles.hiddenDesktop} key="sep" />,
...actions.map(action => (<DropdownListItem key={user.id} {...action} />)),
]}
</DropdownList>
</DropdownContent>
</Dropdown>
{ user.isMuted ? <Icon className={styles.muted} iconName="unmute_filled" /> : null }
</div>
</div>
);
}
}
export default VideoListItem;

View File

@ -19,6 +19,10 @@ const intlMessages = defineMessages({
id: 'app.video.videoMenuDesc',
description: 'video menu description',
},
videoMenuDisabled: {
id: 'app.video.videoMenuDisabled',
description: 'video menu label',
},
});
@ -32,6 +36,7 @@ const JoinVideoOptions = ({
intl,
isSharingVideo,
videoItems,
videoShareAllowed,
}) => {
const menuItems = videoItems
.filter(item => !item.disabled)
@ -54,8 +59,11 @@ const JoinVideoOptions = ({
>
<DropdownTrigger tabIndex={0}>
<Button
label={!videoShareAllowed ?
intl.formatMessage(intlMessages.videoMenuDisabled)
: intl.formatMessage(intlMessages.videoMenu)
}
className={styles.button}
label={intl.formatMessage(intlMessages.videoMenu)}
onClick={() => null}
hideLabel
aria-label={intl.formatMessage(intlMessages.videoMenuDesc)}
@ -63,6 +71,7 @@ const JoinVideoOptions = ({
icon={isSharingVideo ? 'video_off' : 'video'}
size="lg"
circle
disabled={!videoShareAllowed}
/>
</DropdownTrigger>
<DropdownContent placement="top" >

View File

@ -29,18 +29,19 @@ const JoinVideoOptionsContainer = (props) => {
isDisabled,
handleJoinVideo,
handleCloseVideo,
toggleSwapLayout,
swapLayoutAllowed,
baseName,
intl,
...restProps
} = props;
const videoItems = [
{
iconPath: `${baseName}/resources/images/video-menu/icon-swap.svg`,
description: intl.formatMessage(intlMessages.swapCamDesc),
label: intl.formatMessage(intlMessages.swapCam),
disabled: false,
click: () => {},
disabled: !swapLayoutAllowed,
click: toggleSwapLayout,
},
{
iconPath: `${baseName}/resources/images/video-menu/icon-webcam-off.svg`,
@ -54,15 +55,11 @@ const JoinVideoOptionsContainer = (props) => {
return <JoinVideoOptions {...{ videoItems, isSharingVideo, ...restProps }} />;
};
export default injectIntl(withTracker(({
intl,
handleJoinVideo,
handleCloseVideo,
}) => ({
export default injectIntl(withTracker(() => ({
baseName: VideoMenuService.baseName,
isSharingVideo: VideoMenuService.isSharingVideo(),
isDisabled: VideoMenuService.isDisabled(),
handleJoinVideo,
handleCloseVideo,
intl,
videoShareAllowed: VideoMenuService.videoShareAllowed(),
toggleSwapLayout: VideoMenuService.toggleSwapLayout,
swapLayoutAllowed: VideoMenuService.swapLayoutAllowed(),
}))(JoinVideoOptionsContainer));

View File

@ -2,6 +2,7 @@ import Settings from '/imports/ui/services/settings';
import mapUser from '/imports/ui/services/user/mapUser';
import Auth from '/imports/ui/services/auth';
import Users from '/imports/api/users/';
import MediaService from '/imports/ui/components/media/service';
import VideoService from '../service';
const baseName = Meteor.settings.public.app.basename;
@ -12,30 +13,31 @@ const isSharingVideo = () => {
return !!user.has_stream;
};
const videoShareAllowed = () => Settings.dataSaving.viewParticipantsWebcams;
const isDisabled = () => {
const isWaitingResponse = VideoService.isWaitingResponse();
const isConnected = VideoService.isConnected();
const videoSettings = Settings.dataSaving;
const enableShare = videoSettings.viewParticipantsWebcams;
const lockCam = VideoService.isLocked();
const user = Users.findOne({ userId: Auth.userID });
const userLocked = mapUser(user).isLocked;
const isConnecting = (!isSharingVideo && isConnected);
const isLocked = (lockCam && userLocked);
return isLocked
|| isWaitingResponse
|| isConnecting
|| !enableShare;
|| !videoShareAllowed();
};
export default {
isSharingVideo,
isDisabled,
baseName,
toggleSwapLayout: MediaService.toggleSwapLayout,
swapLayoutAllowed: MediaService.shouldEnableSwapLayout,
videoShareAllowed,
};

View File

@ -21,21 +21,20 @@
font-size: $font-size-small;
white-space: nowrap;
margin-top: .5rem;
color: $color-gray;
color: inherit;
@include mq($small-only) {
margin-top: 0;
color: #06172A;
font-size: 90%;
};
}
.item {
padding-left: 0;
display: flex;
justify-content: flex-start;
flex-direction: column;
align-items: center;
margin: auto 0.25rem;
@include mq($small-only) {
flex-direction: row;
};

View File

@ -108,7 +108,9 @@ class WhiteboardToolbar extends Component {
// no drawSettings in the sessionStorage - setting default values
} else {
// setting default drawing settings if they haven't been set previously
const { annotationSelected, thicknessSelected, colorSelected, fontSizeSelected } = this.state;
const {
annotationSelected, thicknessSelected, colorSelected, fontSizeSelected,
} = this.state;
this.props.actions.setInitialWhiteboardToolbarValues(
annotationSelected.value,
thicknessSelected.value * 2,
@ -306,9 +308,9 @@ class WhiteboardToolbar extends Component {
label={intl.formatMessage(intlMessages.toolbarTools)}
icon={this.state.annotationSelected.icon}
onItemClick={this.displaySubMenu}
objectToReturn={'annotationList'}
objectToReturn="annotationList"
onBlur={this.closeSubMenu}
className={cx(styles.toolbarButton, this.state.currentSubmenuOpen === 'annotationList' ? '' : styles.notActive)}
className={cx(styles.toolbarButton, this.state.currentSubmenuOpen === 'annotationList' ? styles.toolbarActive : null)}
>
{this.state.currentSubmenuOpen === 'annotationList' ?
<ToolbarSubmenu
@ -334,9 +336,9 @@ class WhiteboardToolbar extends Component {
label={intl.formatMessage(intlMessages.toolbarFontSize)}
customIcon={this.renderFontItemIcon()}
onItemClick={this.displaySubMenu}
objectToReturn={'fontSizeList'}
objectToReturn="fontSizeList"
onBlur={this.closeSubMenu}
className={cx(styles.toolbarButton, this.state.currentSubmenuOpen === 'fontSizeList' ? '' : styles.notActive)}
className={cx(styles.toolbarButton, this.state.currentSubmenuOpen === 'fontSizeList' ? styles.toolbarActive : null)}
>
{this.state.currentSubmenuOpen === 'fontSizeList' ?
<ToolbarSubmenu
@ -382,7 +384,7 @@ class WhiteboardToolbar extends Component {
onItemClick={this.displaySubMenu}
objectToReturn="thicknessList"
onBlur={this.closeSubMenu}
className={cx(styles.toolbarButton, this.state.currentSubmenuOpen === 'thicknessList' ? '' : styles.notActive)}
className={cx(styles.toolbarButton, this.state.currentSubmenuOpen === 'thicknessList' ? styles.toolbarActive : null)}
customIcon={this.renderThicknessItemIcon()}
>
{this.state.currentSubmenuOpen === 'thicknessList' ?
@ -417,7 +419,7 @@ class WhiteboardToolbar extends Component {
attributeType="XML"
from={this.state.prevColorSelected.value}
to={this.state.colorSelected.value}
begin={'indefinite'}
begin="indefinite"
dur={TRANSITION_DURATION}
repeatCount="0"
fill="freeze"
@ -428,7 +430,7 @@ class WhiteboardToolbar extends Component {
attributeType="XML"
from={this.state.prevThicknessSelected.value}
to={this.state.thicknessSelected.value}
begin={'indefinite'}
begin="indefinite"
dur={TRANSITION_DURATION}
repeatCount="0"
fill="freeze"
@ -450,7 +452,7 @@ class WhiteboardToolbar extends Component {
onItemClick={this.displaySubMenu}
objectToReturn="colorList"
onBlur={this.closeSubMenu}
className={cx(styles.toolbarButton, this.state.currentSubmenuOpen === 'colorList' ? '' : styles.notActive)}
className={cx(styles.toolbarButton, this.state.currentSubmenuOpen === 'colorList' ? styles.toolbarActive : null)}
customIcon={this.renderColorItemIcon()}
>
{this.state.currentSubmenuOpen === 'colorList' ?
@ -495,7 +497,7 @@ class WhiteboardToolbar extends Component {
return (
<ToolbarMenuItem
label={intl.formatMessage(intlMessages.toolbarUndoAnnotation)}
icon={'undo'}
icon="undo"
onItemClick={this.handleUndo}
className={cx(styles.toolbarButton, styles.notActive)}
/>
@ -508,7 +510,7 @@ class WhiteboardToolbar extends Component {
return (
<ToolbarMenuItem
label={intl.formatMessage(intlMessages.toolbarClearAnnotations)}
icon={'circle_close'}
icon="circle_close"
onItemClick={this.handleClearAll}
className={cx(styles.toolbarButton, styles.notActive)}
/>
@ -532,14 +534,14 @@ class WhiteboardToolbar extends Component {
const { annotationSelected } = this.state;
const { isPresenter } = this.props;
return (
<div className={styles.toolbarContainer} style={{ height: this.props.height }}>
<div className={styles.toolbarContainer}>
<div className={styles.toolbarWrapper}>
{this.renderToolItem()}
{annotationSelected.value === 'text' ?
this.renderFontItem()
:
this.renderThicknessItem()
}
this.renderFontItem()
:
this.renderThicknessItem()
}
{this.renderColorItem()}
{this.renderUndoItem()}
{this.renderClearAllItem()}
@ -580,27 +582,18 @@ WhiteboardToolbar.propTypes = {
annotations: PropTypes.arrayOf(PropTypes.object).isRequired,
// defines an array of font-sizes for the Font-size submenu of the text shape
fontSizes: PropTypes.arrayOf(
PropTypes.shape({
value: PropTypes.number.isRequired,
}).isRequired,
).isRequired,
fontSizes: PropTypes.arrayOf(PropTypes.shape({
value: PropTypes.number.isRequired,
}).isRequired).isRequired,
// defines an array of colors for the toolbar (color submenu)
colors: PropTypes.arrayOf(
PropTypes.shape({
value: PropTypes.string.isRequired,
}).isRequired,
).isRequired,
colors: PropTypes.arrayOf(PropTypes.shape({
value: PropTypes.string.isRequired,
}).isRequired).isRequired,
// defines an array of thickness values for the toolbar and their corresponding session values
thicknessRadiuses: PropTypes.arrayOf(
PropTypes.shape({
value: PropTypes.number.isRequired,
}).isRequired,
).isRequired,
// defines the physical height of the whiteboard
height: PropTypes.number.isRequired,
thicknessRadiuses: PropTypes.arrayOf(PropTypes.shape({
value: PropTypes.number.isRequired,
}).isRequired).isRequired,
intl: intlShape.isRequired,

View File

@ -1,31 +1,30 @@
@import "../../../stylesheets/variables/_all";
$toolbar-left-margin: 6px;
$toolbar-button-width: 3.5rem;
$toolbar-button-height: 3.5rem;
$toolbar-container-width: calc(#{$toolbar-button-width} + 1rem);
$toolbar-button-margin-top: 1px;
$toolbar-button-margin-bottom: 1px;
/**********************************************************************************/
/* if you want to add/remove a new button to the annotation/color/thickness lists */
/* just change the number below and the corresponding list will change its size */
/* to fit another button */
/**********************************************************************************/
$number-of-annotation-list-buttons: 7;
$number-of-thickness-list-buttons: 7;
$number-of-fontsize-list-buttons: 6;
$number-of-color-list-buttons: 12;
$number-of-vertical-main-toolbar-buttons: 5;
$toolbar-margin: .8rem;
$toolbar-box-shadow: 0 0 10px -2px rgba(0, 0, 0, .25);
$toolbar-button-width: 3rem;
$toolbar-button-height: 3rem;
$toolbar-button-font-size: 1.75rem;
$toolbar-button-border: 1px;
$toolbar-button-border-radius: 5px;
$toolbar-list-bg: darken($color-white, 10%);
$toolbar-list-color: $color-gray;
.toolbarContainer {
order: 2;
display: flex;
flex-direction: column;
justify-content: center;
height: 100%;
width: $toolbar-container-width;
margin-left: $toolbar-left-margin;
margin-right: $toolbar-margin;
position: absolute;
top: 0;
right: 0;
bottom: 0;
pointer-events: none;
@include mq("#{$landscape} and (max-height:#{upper-bound($small-range)}), #{$small-only}") {
transform: scale(.75);
transform-origin: right;
}
}
.toolbarWrapper {
@ -34,6 +33,31 @@ $number-of-vertical-main-toolbar-buttons: 5;
flex-direction: column;
align-items: center;
justify-content: center;
box-shadow: $toolbar-box-shadow;
pointer-events: all;
.buttonWrapper > .toolbarButton {
border-bottom: 1px solid $color-gray-lighter;
}
.buttonWrapper:first-child > .toolbarButton {
border-top-left-radius: $toolbar-button-border-radius;
border-top-right-radius: $toolbar-button-border-radius;
&.toolbarActive {
border-top-left-radius: 0;
}
}
.buttonWrapper:last-child > .toolbarButton {
border-bottom: 0;
border-bottom-left-radius: $toolbar-button-border-radius;
border-bottom-right-radius: $toolbar-button-border-radius;
&.toolbarActive {
border-bottom-left-radius: 0;
}
}
}
.buttonWrapper {
@ -41,8 +65,7 @@ $number-of-vertical-main-toolbar-buttons: 5;
min-width: $toolbar-button-width;
height: $toolbar-button-height;
min-height: $toolbar-button-height;
margin-top: $toolbar-button-margin-top;
margin-bottom: $toolbar-button-margin-bottom;
position: relative;
}
.toolbarButton {
@ -54,12 +77,27 @@ $number-of-vertical-main-toolbar-buttons: 5;
flex-direction: row;
align-items: center !important;
justify-content: center !important;
font-size: 2.2rem;
border-radius: 0.1rem;
font-size: $toolbar-button-font-size;
position: relative;
border-radius: 0;
box-shadow: none !important;
z-index: 1;
&:focus, &:hover {
border: 0;
}
i {
color: $color-gray;
}
&.toolbarActive {
background-color: $toolbar-list-bg;
i {
color: $toolbar-list-color;
}
}
}
.toolbarList {
@ -67,11 +105,16 @@ $number-of-vertical-main-toolbar-buttons: 5;
flex-direction: row;
align-items: center;
justify-content: center;
position: relative;
height: $toolbar-button-height;
bottom: $toolbar-button-height;
background-color: black;
opacity: 0.9;
position: absolute;
right: $toolbar-button-height;
top: 0;
box-shadow: $toolbar-box-shadow;
.buttonWrapper:first-child > .toolbarListButton {
border-top-left-radius: $toolbar-button-border-radius;
border-bottom-left-radius: $toolbar-button-border-radius;
}
}
.toolbarListButton {
@ -80,22 +123,36 @@ $number-of-vertical-main-toolbar-buttons: 5;
display: flex;
align-items: center;
justify-content: center;
font-size: 2.2rem;
background-color: $color-gray-dark;
border: 0px;
border-radius: 0px;
// border-radius: 0.1rem;
&:hover {
background-color: $color-gray-light;
}
i {
color: $color-white;
}
font-size: $toolbar-button-font-size;
background-color: $toolbar-list-bg;
border: 0 !important;
box-shadow: none;
border-radius: 0;
padding: initial;
&:hover, &:focus {
background-color: darken($toolbar-list-bg, 10%);
}
i {
color: $toolbar-list-color;
}
svg {
fill: $toolbar-list-color;
}
}
.selectedListButton {
background-color: $color-gray-light;
background-color: $toolbar-list-color !important;
i {
color: darken($toolbar-list-bg, 10%) !important;
}
svg {
fill: darken($toolbar-list-bg, 10%);
}
}
.customSvgIcon {
@ -107,34 +164,7 @@ $number-of-vertical-main-toolbar-buttons: 5;
.textThickness {
font-family: Arial, sans-serif;
font-weight: normal;
text-shadow: -1px 0 black, 0 1px black, 1px 0 black, 0 -1px black;
text-shadow: -1px 0 darken($toolbar-list-bg, 10%), 0 1px darken($toolbar-list-bg, 10%), 1px 0 darken($toolbar-list-bg, 10%), 0 -1px darken($toolbar-list-bg, 10%);
margin: auto;
color: $color-white;
}
.annotationList {
width: calc(#{$number-of-annotation-list-buttons} * #{$toolbar-button-width});
right: calc(#{$number-of-annotation-list-buttons} * #{$toolbar-button-width} + (#{$toolbar-container-width} - #{$toolbar-button-width} ) / 2 + #{$toolbar-left-margin});
}
.thicknessList {
width: calc(#{$number-of-thickness-list-buttons} * #{$toolbar-button-width});
right: calc(#{$number-of-thickness-list-buttons} * #{$toolbar-button-width} + (#{$toolbar-container-width} - #{$toolbar-button-width} ) / 2 + #{$toolbar-left-margin});
}
.fontSizeList {
width: calc(#{$number-of-fontsize-list-buttons} * #{$toolbar-button-width});
right: calc(#{$number-of-fontsize-list-buttons} * #{$toolbar-button-width} + (#{$toolbar-container-width} - #{$toolbar-button-width} ) / 2 + #{$toolbar-left-margin});
}
.colorList {
width: calc(#{$number-of-color-list-buttons} * #{$toolbar-button-width});
right: calc(#{$number-of-color-list-buttons} * #{$toolbar-button-width} + (#{$toolbar-container-width} - #{$toolbar-button-width} ) / 2 + #{$toolbar-left-margin});
}
//Undo and Clear All buttons shouldn't be active
.notActive {
&:focus {
background-color: $color-white;
}
color: $toolbar-list-color;
}

View File

@ -70,7 +70,7 @@ export default class ToolbarMenuItem extends Component {
render() {
return (
<div className={styles.buttonWrapper}>
<div className={styles.buttonWrapper} hidden={this.props.disabled}>
<Button
hideLabel
role="button"

View File

@ -7,9 +7,9 @@ import { styles } from '../styles';
import ToolbarSubmenuItem from '../toolbar-submenu-item/component';
const intlMessages = defineMessages({
toolHand: {
id: 'app.whiteboard.toolbar.tools.hand',
description: 'Tool submenu hand item',
toolPointer: {
id: 'app.whiteboard.toolbar.tools.pointer',
description: 'Tool submenu pointer item',
},
toolPencil: {
id: 'app.whiteboard.toolbar.tools.pencil',
@ -96,7 +96,7 @@ class ToolbarSubmenu extends Component {
} else if (type === 'thickness') {
return (
<svg className={styles.customSvgIcon}>
<circle cx="50%" cy="50%" r={obj.value} fill="#F3F6F9" />
<circle cx="50%" cy="50%" r={obj.value} />
</svg>
);
} else if (type === 'font-size') {
@ -197,8 +197,7 @@ class ToolbarSubmenu extends Component {
)}
key={obj.value}
/>
),
) : null}
)) : null}
</div>
);
}
@ -209,20 +208,18 @@ ToolbarSubmenu.propTypes = {
handleMouseEnter: PropTypes.func.isRequired,
handleMouseLeave: PropTypes.func.isRequired,
type: PropTypes.string.isRequired,
objectsToRender: PropTypes.arrayOf(
PropTypes.oneOfType([
PropTypes.shape({
value: PropTypes.string.isRequired,
}),
PropTypes.shape({
value: PropTypes.number.isRequired,
}),
PropTypes.shape({
value: PropTypes.string.isRequired,
icon: PropTypes.string.isRequired,
}),
]).isRequired,
).isRequired,
objectsToRender: PropTypes.arrayOf(PropTypes.oneOfType([
PropTypes.shape({
value: PropTypes.string.isRequired,
}),
PropTypes.shape({
value: PropTypes.number.isRequired,
}),
PropTypes.shape({
value: PropTypes.string.isRequired,
icon: PropTypes.string.isRequired,
}),
]).isRequired).isRequired,
objectSelected: PropTypes.oneOfType([
PropTypes.shape({
value: PropTypes.string.isRequired,

View File

@ -19,6 +19,7 @@ const CALL_STATES = {
class AudioManager {
constructor() {
this._inputDevice = {
value: 'default',
tracker: new Tracker.Dependency(),
};
@ -36,12 +37,14 @@ class AudioManager {
});
}
init(userData, messages) {
init(userData) {
this.bridge = USE_SIP ? new SIPBridge(userData) : new VertoBridge(userData);
this.userData = userData;
this.messages = messages;
this.initialized = true;
}
setAudioMessages(messages) {
this.messages = messages;
}
defineProperties(obj) {
Object.keys(obj).forEach((key) => {
@ -140,7 +143,7 @@ class AudioManager {
]))
.catch((err) => {
// If theres a iceGathering timeout we retry to join after asking device permissions
if (err === iceGatheringErr && !this.devicesInitialized) {
if (err === iceGatheringErr) {
return this.askDevicesPermissions()
.then(() => this.joinListenOnly());
}
@ -203,6 +206,12 @@ class AudioManager {
this.isConnecting = false;
this.isHangingUp = false;
if (this.inputStream) {
window.defaultInputStream.forEach(track => track.stop());
this.inputStream.getTracks().forEach(track => track.stop());
this.inputDevice = { id: 'default' };
}
if (!this.error && !this.isEchoTest) {
this.notify(this.messages.info.LEFT_AUDIO);
}
@ -273,6 +282,7 @@ class AudioManager {
.then(handleChangeInputDeviceSuccess)
.catch(handleChangeInputDeviceError);
}
return this.bridge.changeInputDevice(deviceId)
.then(handleChangeInputDeviceSuccess)
.catch(handleChangeInputDeviceError);
@ -283,17 +293,18 @@ class AudioManager {
}
set inputDevice(value) {
Object.assign(this._inputDevice, value);
this._inputDevice.value = value;
this._inputDevice.tracker.changed();
}
get inputStream() {
return this._inputDevice.stream;
this._inputDevice.tracker.depend();
return this._inputDevice.value.stream;
}
get inputDeviceId() {
this._inputDevice.tracker.depend();
return this._inputDevice.id;
return this._inputDevice.value.id;
}
set userData(value) {

View File

@ -24,7 +24,7 @@ const mapUser = (user) => {
isCurrent: user.userId === userId,
isVoiceUser: voiceUser ? voiceUser.joined : false,
isMuted: voiceUser ? voiceUser.muted : false,
isTalking: voiceUser ? voiceUser.talking : false,
isTalking: voiceUser ? voiceUser.talking && !voiceUser.muted : false,
isListenOnly: voiceUser ? voiceUser.listenOnly : false,
isSharingWebcam: user.has_stream,
isPhoneUser: user.phone_user,

View File

@ -29,9 +29,9 @@ $xxlarge-range: (120.063em);
$screen: "only screen";
$landscape: "#{$screen} and (orientation: landscape)";
$portrait: "#{$screen} and (orientation: portrait)";
$small-up: $screen;
$small-only: "#{$screen} and (max-width: #{upper-bound($small-range)})";
$xsmall-only: "#{$screen} and (min-width:#{lower-bound($xsmall-range)}) and (max-width:#{upper-bound($xsmall-range)})";
$small-up: "#{$screen} and (min-width:#{lower-bound($small-range)})";;
$small-only: "#{$screen} and (max-width: #{upper-bound($small-range)})";
$medium-up: "#{$screen} and (min-width:#{lower-bound($medium-range)})";
$medium-only: "#{$screen} and (min-width:#{lower-bound($medium-range)}) and (max-width:#{upper-bound($medium-range)})";
$large-up: "#{$screen} and (min-width:#{lower-bound($large-range)})";

View File

@ -0,0 +1,16 @@
const deviceType = () => {
// Listing of Device Viewport sizes, Updated : March 25th, 2018
// http://mediag.com/news/popular-screen-resolutions-designing-for-all/
const MAX_PHONE_SHORT_SIDE = 480;
const smallSide = window.screen.width < window.screen.height
? window.screen.width
: window.screen.height;
return {
isPhone: smallSide <= MAX_PHONE_SHORT_SIDE,
};
};
export default deviceType;

View File

@ -315,8 +315,8 @@
"value": "pencil"
},
{
"icon": "hand",
"value": "hand"
"icon": "pointer",
"value": "pointer"
}
]
}

View File

@ -315,8 +315,8 @@
"value": "pencil"
},
{
"icon": "hand",
"value": "hand"
"icon": "pointer",
"value": "pointer"
}
]
}

View File

@ -36,10 +36,12 @@
"app.userList.menu.removeUser.label": "Nutzer entfernen",
"app.userList.menu.muteUserAudio.label": "Teilnehmer stummschalten",
"app.userList.menu.unmuteUserAudio.label": "Mikrofon freigeben",
"app.userList.userAriaLabel": "Teilnehmer : {0} Rolle: {1} Person: {2} Status: {3}",
"app.userList.userAriaLabel": "{0} {1} {2} Status {3}",
"app.userList.menu.promoteUser.label": "{0} zum Moderator machen",
"app.userList.menu.demoteUser.label": "{0} zum Zuschauer zurückstufen",
"app.media.label": "Media",
"app.media.screenshare.start": "Bildschirmfreigabe wurde gestartet",
"app.media.screenshare.end": "Bildschirmfreigabe wurde gestoppt",
"app.meeting.ended": "Diese Konferenz wurde beendet",
"app.meeting.endedMessage": "Sie werden zum Startbildschirm weitergeleitet",
"app.presentation.presentationToolbar.prevSlideLabel": "Vorherige Folie",
@ -91,7 +93,7 @@
"app.navBar.settingsDropdown.leaveSessionDesc": "Konferenz verlassen",
"app.navBar.settingsDropdown.exitFullscreenDesc": "Vollbildmodus beenden",
"app.navBar.userListToggleBtnLabel": "Teilnehmerliste umschalten",
"app.navBar.toggleUserList.ariaLabel": "Nutzer / Gespräche-Umschaltung",
"app.navBar.toggleUserList.ariaLabel": "Teilnehmer und Chat Umschalter",
"app.navBar.toggleUserList.newMessages": "mit Benachrichtigung für neue Nachrichten",
"app.navBar.recording": "Diese Konferenz wird aufgezeichnet",
"app.navBar.recording.on": "Aufzeichnung läuft",
@ -103,6 +105,7 @@
"app.leaveConfirmation.dismissLabel": "Abbrechen",
"app.leaveConfirmation.dismissDesc": "Schließt den Dialog zum Verlassen der Konferenz",
"app.leaveConfirmation.endMeetingLabel": "Ja und Konferenz beenden",
"app.leaveConfirmation.endMeetingAriaLabel": "Konferenz verlassen und für alle beenden",
"app.leaveConfirmation.endMeetingDesc": "Bestätigung zum Beenden der Konferenz",
"app.about.title": "Versionsinfo",
"app.about.version": "Client Build:",
@ -176,8 +179,8 @@
"app.settings.main.save.label": "Speichern",
"app.settings.main.save.label.description": "Speichert die Einstellungen und schließt das Einstellungsmenü",
"app.settings.dataSavingTab.label": "Dateneinsparung",
"app.settings.dataSavingTab.webcam": "Webcams ausschalten",
"app.settings.dataSavingTab.screenShare": "Bildschirmfreigabe ausschalten",
"app.settings.dataSavingTab.webcam": "Webcams aktiviert",
"app.settings.dataSavingTab.screenShare": "Bildschirmfreigabe aktiviert",
"app.settings.dataSavingTab.description": "Um Datentransfervolumen zu sparen, können Sie hier einstellen, was angezeigt wird.",
"app.switch.onLabel": "AN",
"app.switch.offLabel": "AUS",
@ -244,6 +247,8 @@
"app.audioModal.closeLabel": "Schließen",
"app.audioModal.yes": "Ja",
"app.audioModal.no": "Nein",
"app.audioModal.yes.arialabel" : "Echo ist hörbar",
"app.audioModal.no.arialabel" : "Echo ist nicht hörbar",
"app.audioModal.echoTestTitle": "Dies ist ein persönlicher Echotest. Sprechen Sie ein paar Worte. Hören Sie sich selbst?",
"app.audioModal.settingsTitle": "Audioeinstellungen ändern",
"app.audioModal.helpTitle": "Es gab ein Problem mit ihren Mediengeräten (Mikrofon/Webcam)",
@ -292,6 +297,11 @@
"app.video.iceCandidateError": "Fehler beim Hinzufügen vom ice candidate",
"app.video.permissionError": "Fehler bei Freigabe der Webcam. Bitte Berechtigungen prüfen",
"app.video.sharingError": "Fehler bei Freigabe der Webcam",
"app.video.swapCam": "Wechseln",
"app.video.swapCamDesc": "Ausrichtung der Webcams wechseln",
"app.video.videoMenu": "Videomenü",
"app.video.videoMenuDisabled": "Webcam in den Einstellungen deaktiviert",
"app.video.videoMenuDesc": "Videomenü öffnen",
"app.video.chromeExtensionError": "Sie müssen Folgendes installieren:",
"app.video.chromeExtensionErrorLink": "Diese Chrome Erweiterung",
"app.meeting.endNotification.ok.label": "OK",
@ -303,8 +313,10 @@
"app.whiteboard.toolbar.tools.ellipse": "Ellipse",
"app.whiteboard.toolbar.tools.line": "Linie",
"app.whiteboard.toolbar.tools.text": "Text",
"app.whiteboard.toolbar.thickness": "Strichstärkenliste",
"app.whiteboard.toolbar.color": "Farbliste",
"app.whiteboard.toolbar.thickness": "Strichdicke",
"app.whiteboard.toolbar.thicknessDisabled": "Strichdicke ist deaktiviert",
"app.whiteboard.toolbar.color": "Farben",
"app.whiteboard.toolbar.colorDisabled": "Farben sind deaktiviert",
"app.whiteboard.toolbar.color.black": "Schwarz",
"app.whiteboard.toolbar.color.white": "Weiß",
"app.whiteboard.toolbar.color.red": "Rot",

View File

@ -78,6 +78,8 @@
"app.presentationUploder.conversion.pageCountExceeded": "Ops, the page count exceeded the limit",
"app.presentationUploder.conversion.timeout": "Ops, the conversion is taking too long",
"app.polling.pollingTitle": "Polling Options",
"app.polling.pollAnswerLabel": "Poll answer {0}",
"app.polling.pollAnswerDesc": "Select this option to vote for {0}",
"app.failedMessage": "Apologies, trouble connecting to the server.",
"app.connectingMessage": "Connecting...",
"app.waitingMessage": "Disconnected. Trying to reconnect in {0} seconds...",
@ -195,7 +197,7 @@
"app.actionsBar.actionsDropdown.stopDesktopShareDesc": "Stop sharing your screen with",
"app.actionsBar.actionsDropdown.startRecording": "Start recording",
"app.actionsBar.actionsDropdown.stopRecording": "Stop recording",
"app.actionsBar.emojiMenu.statusTriggerLabel": "Status",
"app.actionsBar.emojiMenu.statusTriggerLabel": "Set a Status",
"app.actionsBar.emojiMenu.awayLabel": "Away",
"app.actionsBar.emojiMenu.awayDesc": "Change your status to away",
"app.actionsBar.emojiMenu.raiseHandLabel": "Raise",
@ -300,12 +302,13 @@
"app.video.swapCam": "Swap",
"app.video.swapCamDesc": "swap the direction of webcams",
"app.video.videoMenu": "Video menu",
"app.video.videoMenuDisabled": "Video menu Webcam is disabled in Settings",
"app.video.videoMenuDesc": "Open video menu dropdown",
"app.video.chromeExtensionError": "You must install",
"app.video.chromeExtensionErrorLink": "this Chrome Extension",
"app.meeting.endNotification.ok.label": "OK",
"app.whiteboard.toolbar.tools": "Tools",
"app.whiteboard.toolbar.tools.hand": "Hand",
"app.whiteboard.toolbar.tools.pointer": "Pointer",
"app.whiteboard.toolbar.tools.pencil": "Pencil",
"app.whiteboard.toolbar.tools.rectangle": "Rectangle",
"app.whiteboard.toolbar.tools.triangle": "Triangle",
@ -332,5 +335,9 @@
"app.whiteboard.toolbar.clear": "Clear All Annotations",
"app.whiteboard.toolbar.multiUserOn": "Turn multi-user mode on",
"app.whiteboard.toolbar.multiUserOff": "Turn multi-user mode off",
"app.whiteboard.toolbar.fontSize": "Font Size List"
"app.whiteboard.toolbar.fontSize": "Font Size List",
"app.videoDock.webcamFocusLabel": "Focus",
"app.videoDock.webcamFocusDesc": "Focus the selected webcam",
"app.videoDock.webcamUnfocusLabel": "Unfocus",
"app.videoDock.webcamUnfocusDesc": "Unfocus the selected webcam"
}

View File

@ -32,7 +32,6 @@
"app.userList.menu.makePresenter.label": "Hacer presentador",
"app.userList.menu.muteUserAudio.label": "Silenciar usuario",
"app.userList.menu.unmuteUserAudio.label": "Activar sonido de usuario",
"app.userList.userAriaLabel": "Usuario : {0} Rol: {1} Persona: {2} Estátus: {3}",
"app.media.label": "Media",
"app.presentation.presentationToolbar.prevSlideLabel": "Diapositiva anterior",
"app.presentation.presentationToolbar.prevSlideDesc": "Cambiar presentación a diapositiva anterior",

View File

@ -1,14 +1,23 @@
{
"app.home.greeting": "Bienvenue {0}! Votre présentation commence bientôt...",
"app.chat.submitLabel": "Envoyer message",
"app.chat.errorMinMessageLength": "Le message est {0} caractère(s) trop court",
"app.chat.errorMaxMessageLength": "Le message est {0} caractère(s) trop long",
"app.chat.inputLabel": "Saisie des messages pour le chat {0}",
"app.chat.inputPlaceholder": "Message {0}",
"app.chat.titlePublic": "Discussion publique",
"app.chat.titlePrivate": "Discussion privée avec {0}",
"app.chat.partnerDisconnected": "{0} a quitté la conférence",
"app.chat.closeChatLabel": "Fermer {0}",
"app.chat.hideChatLabel": "Cacher {0}",
"app.chat.moreMessages": "Plus de messages ci-dessous",
"app.chat.dropdown.options": "Options de Discussion",
"app.chat.dropdown.clear": "Effacer",
"app.chat.dropdown.copy": "Copier",
"app.chat.dropdown.save": "Sauvegarder",
"app.chat.label": "Discussion",
"app.chat.emptyLogLabel": "Journal de discussion vide",
"app.chat.clearPublicChatMessage": "L'historique des discussions publiques a été effacé par un modérateur",
"app.userList.usersTitle": "Utilisateurs",
"app.userList.participantsTitle": "Participants",
"app.userList.messagesTitle": "Messages",
@ -16,87 +25,315 @@
"app.userList.you": "Vous",
"app.userList.locked": "Verrouillé",
"app.userList.label": "Liste d'utilisateurs",
"app.userList.toggleCompactView.label": "Basculer le mode d'affichage compact",
"app.userList.guest": "Invité",
"app.userList.menuTitleContext": "Options disponibles",
"app.userList.chatListItem.unreadSingular": "{0} nouveau message",
"app.userList.chatListItem.unreadPlural": "{0} nouveaux messages",
"app.userList.userAriaLabel": "Présentation",
"app.userList.menu.chat.label": "Discussion",
"app.userList.menu.clearStatus.label": "Effacer l'état",
"app.userList.menu.makePresenter.label": "Promouvoir comme Présentateur",
"app.userList.menu.removeUser.label": "Retirer l'Utilisateur",
"app.userList.menu.muteUserAudio.label": "Rendre Muet",
"app.userList.menu.unmuteUserAudio.label": "Autoriser à parler",
"app.userList.userAriaLabel": "{0} {1} {2} Etat {3}",
"app.userList.menu.promoteUser.label": "Promouvoir {0} modérateur",
"app.userList.menu.demoteUser.label": "Redéfinir {0} comme participant",
"app.media.label": "Média",
"app.media.screenshare.start": "Le Partage d'écran à débuté",
"app.media.screenshare.end": "Le Partage d'écran s'est terminé",
"app.meeting.ended": "Cette session s'est terminée",
"app.meeting.endedMessage": "Vous serez redirigé vers l'écran d'accueil",
"app.presentation.presentationToolbar.prevSlideLabel": "Diapositive précédente",
"app.presentation.presentationToolbar.prevSlideDesc": "Changer la présentation à la diapositive précédente",
"app.presentation.presentationToolbar.nextSlideLabel": "Diapositive suivante",
"app.presentation.presentationToolbar.nextSlideDesc": "Changer la présentation à la diapositive suivante",
"app.presentation.presentationToolbar.skipSlideLabel": "Passer la diapositive",
"app.presentation.presentationToolbar.skipSlideDesc": "Changer la présentation à une diapositive spécifique",
"app.presentation.presentationToolbar.fitWidthLabel": "Adapté à la largeur",
"app.presentation.presentationToolbar.fitWidthDesc": "Afficher toute la largeur de la diapositive",
"app.presentation.presentationToolbar.fitScreenLabel": "Ajuster à l'écran",
"app.presentation.presentationToolbar.fitScreenDesc": "Afficher toute la diapositive",
"app.presentation.presentationToolbar.zoomLabel": "Zoom",
"app.presentation.presentationToolbar.zoomDesc": "Changer le niveau de zoom de la présentation",
"app.presentation.presentationToolbar.goToSlide": "Diapositive {0}",
"app.presentationUploder.title": "Présentation",
"app.presentationUploder.message": "En tant que présentateur dans BigBlueButton, vous avez la possibilité de téléverser n'importe quel document bureautique ou fichier PDF. Nous recommandons pour les meilleurs résultats de téléverser un fichier PDF.",
"app.presentationUploder.confirmLabel": "Débuter",
"app.presentationUploder.confirmDesc": "Sauvegardez vos modifications et lancez la présentation",
"app.presentationUploder.dismissLabel": "Annuler",
"app.presentationUploder.dismissDesc": "Ferme la fenêtre d'option et supprime vos modifications",
"app.presentationUploder.dropzoneLabel": "Faites glisser les fichiers ici pour téléverser",
"app.presentationUploder.browseFilesLabel": "ou parcourez pour trouver des fichiers",
"app.presentationUploder.fileToUpload": "A téléverser...",
"app.presentationUploder.currentBadge": "En cours",
"app.presentationUploder.genericError": "Oups, quelque chose s'est mal passé",
"app.presentationUploder.upload.progress": "Téléversement ({progress}%)",
"app.presentationUploder.upload.413": "Le fichier est trop volumineux",
"app.presentationUploder.conversion.conversionProcessingSlides": "Traitement de la page {current} sur {total}",
"app.presentationUploder.conversion.genericConversionStatus": "Conversion du fichier...",
"app.presentationUploder.conversion.generatingThumbnail": "Création des vignettes...",
"app.presentationUploder.conversion.generatedSlides": "Diapositives générées...",
"app.presentationUploder.conversion.generatingSvg": "Création des images SVG...",
"app.presentationUploder.conversion.pageCountExceeded": "Oups, le nombre de pages a dépassé la limite",
"app.presentationUploder.conversion.timeout": "Oups, la conversion prend trop de temps",
"app.polling.pollingTitle": "Options du Sondage",
"app.failedMessage": "Problème de connexion au serveur, toutes nos excuses.",
"app.connectingMessage": "Connexion en cours...",
"app.waitingMessage": "Déconnecté. Tentative de reconnexion dans {0} secondes...",
"app.navBar.settingsDropdown.optionsLabel": "Options",
"app.navBar.settingsDropdown.fullscreenLabel": "Plein écran",
"app.navBar.settingsDropdown.settingsLabel": "Ouvrir les paramètres",
"app.navBar.settingsDropdown.aboutLabel": "À propos",
"app.navBar.settingsDropdown.leaveSessionLabel": "Déconnexion",
"app.navBar.settingsDropdown.exitFullscreenLabel": "Quitter le plein écran",
"app.navBar.settingsDropdown.fullscreenDesc": "Passer le menu de paramétrage en plein écran",
"app.navBar.settingsDropdown.settingsDesc": "Modifier les paramètres généraux",
"app.navBar.settingsDropdown.aboutDesc": "Afficher les informations du client",
"app.navBar.settingsDropdown.leaveSessionDesc": "Quitter la conférence",
"app.navBar.settingsDropdown.exitFullscreenDesc": "Quitter le mode plein écran",
"app.navBar.userListToggleBtnLabel": "Basculer l'affichage de la Liste des Utilisateurs",
"app.navBar.toggleUserList.ariaLabel": "Basculer les Utilisateurs et Conversation",
"app.navBar.toggleUserList.newMessages": "avec notification des nouveaux messages",
"app.navBar.recording": "Cette session est enregistrée",
"app.navBar.recording.on": "Enregistrement",
"app.navBar.recording.off": "Ne pas enregistrer",
"app.leaveConfirmation.title": "Quitter la session",
"app.leaveConfirmation.message": "Voulez-vous quitter cette conférence ?",
"app.leaveConfirmation.confirmLabel": "Quitter",
"app.leaveConfirmation.confirmDesc": "Vous déconnecte de la conférence",
"app.leaveConfirmation.dismissLabel": "Annuler",
"app.leaveConfirmation.dismissDesc": "Ferme sans confirmation",
"app.leaveConfirmation.endMeetingLabel": "Accepter et fermer la session",
"app.leaveConfirmation.endMeetingAriaLabel": "Quitter et fermer la session",
"app.leaveConfirmation.endMeetingDesc": "Clore et confirmer la fin de conférence",
"app.about.title": "À propos",
"app.about.version": "Version du client :",
"app.about.copyright": "Copyright :",
"app.about.confirmLabel": "OK",
"app.about.confirmDesc": "OK",
"app.about.dismissLabel": "Annuler",
"app.about.dismissDesc": "Fermer l'information client",
"app.actionsBar.changeStatusLabel": "Changer le statut",
"app.actionsBar.muteLabel": "Rendre silencieux",
"app.actionsBar.unmuteLabel": "Autoriser à parler",
"app.actionsBar.camOffLabel": "Caméra désactivée",
"app.actionsBar.raiseLabel": "Augmenter",
"app.actionsBar.label": "Barre d'actions",
"app.submenu.application.applicationSectionTitle": "Application",
"app.submenu.application.audioNotifyLabel": "Notifications audio pour la discussion",
"app.submenu.application.pushNotifyLabel": "Pousser les notification de la discussion",
"app.submenu.application.fontSizeControlLabel": "Taille des caractères",
"app.submenu.application.increaseFontBtnLabel": "Augmenter la Taille de la Police",
"app.submenu.application.decreaseFontBtnLabel": "Diminuer la Taille de la Police",
"app.submenu.application.languageLabel": "Langue de l'application",
"app.submenu.application.ariaLanguageLabel": "Changer la langue de l'application",
"app.submenu.application.languageOptionLabel": "Choisir la langue",
"app.submenu.application.noLocaleOptionLabel": "Pas de lieu actif",
"app.submenu.audio.micSourceLabel": "Source du Micro",
"app.submenu.audio.speakerSourceLabel": "Source des diffuseurs",
"app.submenu.audio.streamVolumeLabel": "Volume de votre flux audio",
"app.submenu.video.title": "Vidéo",
"app.submenu.video.videoSourceLabel": "Voir la source",
"app.submenu.video.videoOptionLabel": "Choisir la source de la vue",
"app.submenu.video.videoQualityLabel": "Qualité vidéo",
"app.submenu.video.qualityOptionLabel": "Choisissez la qualité vidéo",
"app.submenu.video.participantsCamLabel": "Voir les webcams des participants",
"app.submenu.closedCaptions.closedCaptionsLabel": "Sous-titres codés",
"app.submenu.closedCaptions.takeOwnershipLabel": "S'approprier",
"app.submenu.closedCaptions.languageLabel": "Langue",
"app.submenu.closedCaptions.localeOptionLabel": "Choisir la langue",
"app.submenu.closedCaptions.noLocaleOptionLabel": "Pas de lieux actifs",
"app.submenu.closedCaptions.fontFamilyLabel": "Famille de police",
"app.submenu.closedCaptions.fontFamilyOptionLabel": "Choisir la famille de Police",
"app.submenu.closedCaptions.fontSizeLabel": "Taille des caractères",
"app.submenu.closedCaptions.fontSizeOptionLabel": "Choisissez la taille des caractères",
"app.submenu.closedCaptions.backgroundColorLabel": "Couleur du fond",
"app.submenu.closedCaptions.fontColorLabel": "Couleur des caractères",
"app.submenu.participants.muteAllLabel": "Couper le son, excepté pour le présentateur",
"app.submenu.participants.lockAllLabel": "Vérouiller les participants",
"app.submenu.participants.lockItemLabel": "Participants {0}",
"app.submenu.participants.lockMicDesc": "Désactiver le micro de tous les participants verrouillés",
"app.submenu.participants.lockCamDesc": "Désactiver la webcam de tous les participants verrouillés",
"app.submenu.participants.lockPublicChatDesc": "Désactiver la discussion publique pour tous les participants verrouillés",
"app.submenu.participants.lockPrivateChatDesc": "Désactiver la discussion privée pour tous les participants verrouillés",
"app.submenu.participants.lockLayoutDesc": "Verrouiller les mise en page pour tous les participants verrouillés",
"app.submenu.participants.lockMicAriaLabel": "Verrouillage du micro",
"app.submenu.participants.lockCamAriaLabel": "Verrouillage de la webcam",
"app.submenu.participants.lockPublicChatAriaLabel": "Discussion publique verrouillée",
"app.submenu.participants.lockPrivateChatAriaLabel": "Discussion privée verrouillée",
"app.submenu.participants.lockLayoutAriaLabel": "Mise en page verrouillée",
"app.submenu.participants.lockMicLabel": "Microphone",
"app.submenu.participants.lockCamLabel": "Webcam",
"app.submenu.participants.lockPublicChatLabel": "Discussion Publique",
"app.submenu.participants.lockPrivateChatLabel": "Discussion Privée",
"app.submenu.participants.lockLayoutLabel": "Mise en page",
"app.settings.applicationTab.label": "Application",
"app.settings.audioTab.label": "Audio",
"app.settings.videoTab.label": "Vidéo",
"app.settings.closedcaptionTab.label": "Sous-titres codés",
"app.settings.usersTab.label": "Participants",
"app.settings.main.label": "Paramètres",
"app.settings.main.cancel.label": "Annuler",
"app.settings.main.cancel.label.description": "Annule les changements et ferme le menu des paramètres",
"app.settings.main.save.label": "Sauvegarder",
"app.settings.main.save.label.description": "Sauvegarde les changements et ferme le menu des paramètres",
"app.settings.dataSavingTab.label": "Enregistrement des Données",
"app.settings.dataSavingTab.webcam": "Activer les Webcams",
"app.settings.dataSavingTab.screenShare": "Activer le Partage de Bureau",
"app.settings.dataSavingTab.description": "Pour économiser votre bande passante, ajustez ce qui est actuellement affiché.",
"app.switch.onLabel": "ON",
"app.switch.offLabel": "OFF",
"app.actionsBar.actionsDropdown.actionsLabel": "Actions",
"app.actionsBar.actionsDropdown.presentationLabel": "Téléverser une présentation",
"app.actionsBar.actionsDropdown.initPollLabel": "Débuter un sondage",
"app.actionsBar.actionsDropdown.desktopShareLabel": "Partager votre écran",
"app.actionsBar.actionsDropdown.stopDesktopShareLabel": "Cesser le partage d'écran",
"app.actionsBar.actionsDropdown.presentationDesc": "Téléverser votre présentation",
"app.actionsBar.actionsDropdown.initPollDesc": "Débuter un sondage",
"app.actionsBar.actionsDropdown.desktopShareDesc": "Partager votre écran avec d'autres",
"app.actionsBar.actionsDropdown.stopDesktopShareDesc": "Cesser de partager votre écran avec",
"app.actionsBar.actionsDropdown.startRecording": "Commencer à enregistrer",
"app.actionsBar.actionsDropdown.stopRecording": "Arrêter d'enregistrer",
"app.actionsBar.emojiMenu.statusTriggerLabel": "Statut",
"app.actionsBar.emojiMenu.noneLabel": "Effacer",
"app.actionsBar.emojiMenu.awayLabel": "Eloigné",
"app.actionsBar.emojiMenu.awayDesc": "Passer votre état à éloigné",
"app.actionsBar.emojiMenu.raiseHandLabel": "Augmenter",
"app.actionsBar.emojiMenu.raiseHandDesc": "Lever la main pour poser une question",
"app.actionsBar.emojiMenu.neutralLabel": "Indécis",
"app.actionsBar.emojiMenu.neutralDesc": "Passer votre état à indécis",
"app.actionsBar.emojiMenu.confusedLabel": "Désorienté",
"app.actionsBar.emojiMenu.confusedDesc": "Passer votre état à désorienté",
"app.actionsBar.emojiMenu.sadLabel": "Triste",
"app.actionsBar.emojiMenu.sadDesc": "Passer votre état à triste",
"app.actionsBar.emojiMenu.happyLabel": "Ravi",
"app.actionsBar.emojiMenu.happyDesc": "Passer votre état à ravi",
"app.actionsBar.emojiMenu.noneLabel": "Effacer votre état",
"app.actionsBar.emojiMenu.noneDesc": "Effacer votre statut",
"app.actionsBar.emojiMenu.applauseLabel": "Approuver",
"app.actionsBar.emojiMenu.applauseDesc": "Passer votre état à applaudissements",
"app.actionsBar.emojiMenu.thumbsUpLabel": "En faveur",
"app.actionsBar.emojiMenu.thumbsUpDesc": "Passer votre statut à favorable",
"app.actionsBar.emojiMenu.thumbsDownLabel": "Défavorable",
"app.actionsBar.emojiMenu.thumbsDownDesc": "Passer votre statut à défavorable",
"app.actionsBar.currentStatusDesc": "statut actuel {0}",
"app.audioNotification.audioFailedError1001": "Erreur 1001 : WebSocket déconnecté",
"app.audioNotification.audioFailedError1002": "Erreur 1002 : Impossible d'établir une connexion WebSocket.",
"app.audioNotification.audioFailedError1003": "Erreur 1003 : Version de navigateur non supportée",
"app.audioNotification.audioFailedError1004": "Erreur 1004 : Défaut sur appel",
"app.audioNotification.audioFailedError1005": "Erreur 1005 : L'appel s'est terminé inopinément",
"app.audioNotification.audioFailedError1006": "Erreur 1006 : Délai d'appel dépassé",
"app.audioNotification.audioFailedError1007": "Erreur 1007 : La négociation ICE a échoué",
"app.audioNotification.audioFailedError1008": "Erreur 1008 : Échec du transfert",
"app.audioNotification.audioFailedError1009": "Erreur 1009 : Impossible de récupérer les informations du serveur STUN/TURN.",
"app.audioNotification.audioFailedError1010": "Erreur 1010 : Délai dépassé durant la négociation ICE",
"app.audioNotification.audioFailedError1011": "Erreur 1011 : Délai d'attente dépassé pour ICE",
"app.audioNotification.audioFailedMessage": "Votre connexion audio à échoué",
"app.audioNotification.mediaFailedMessage": "Votre connexion micro a échoué",
"app.audioNotification.closeLabel": "Fermer",
"app.breakoutJoinConfirmation.title": "Rejoindre la salle de conférence",
"app.breakoutJoinConfirmation.message": "Voulez-vous rejoindre",
"app.breakoutJoinConfirmation.confirmLabel": "Rejoindre",
"app.breakoutJoinConfirmation.confirmDesc": "Rejoignez la salle de conférence",
"app.breakoutJoinConfirmation.dismissLabel": "Annuler",
"app.breakoutJoinConfirmation.dismissDesc": "Fermer et refuser de rejoindre la salle de conférence",
"app.breakoutTimeRemainingMessage": "Temps restant pour la salle de conférence: {0}",
"app.breakoutWillCloseMessage": "Temps écoulé. La salle de conférence fermera bientôt",
"app.calculatingBreakoutTimeRemaining": "Calcul du temps restant...",
"app.audioModal.microphoneLabel": "Microphone",
"app.audioModal.listenOnlyLabel": "Écoute seule",
"app.audioModal.audioChoiceLabel": "Voulez-vous rejoindre l'audio ?",
"app.audioModal.audioChoiceDesc": "Sélectionnez comment rejoindre l'audio de cette conférence",
"app.audioModal.closeLabel": "Fermer",
"app.audioModal.yes": "Oui",
"app.audioModal.no": "Non",
"app.audioModal.yes.arialabel" : "Écho activé",
"app.audioModal.no.arialabel" : "Écho désactivé",
"app.audioModal.echoTestTitle": "Écho test privé. Dites quelques mots. Entendez-vous l'audio ?",
"app.audioModal.settingsTitle": "Modifier vos paramètres audio",
"app.audioModal.helpTitle": "Il y a un problème avec vos périphériques",
"app.audioModal.helpText": "Avez-vous donné à BigBlueButton la permission d'accéder à votre microphone ? Notez qu'une boîte de dialogue devrait apparaître lorsque vous essayez de vous joindre à l'audio, vous demandant les permissions d'accès à vos périphériques multimédia. Veuillez accepter afin de vous joindre à la conférence audio. Si tel n'est pas le cas, essayez de changer les permissions de votre micro dans les paramètres de votre navigateur.",
"app.audioModal.connecting": "Connexion en cours",
"app.audioModal.connectingEchoTest": "Connexion au test d'écho",
"app.audioManager.joinedAudio": "Vous avez rejoint la conférence audio",
"app.audioManager.joinedEcho": "Vous avez rejoint le test d'écho",
"app.audioManager.leftAudio": "Vous avez quitté la conférence audio",
"app.audioManager.genericError": "Erreur: Une erreur s'est produite, réessayez s'il vous plait.",
"app.audioManager.connectionError": "Erreur: Erreur de connexion",
"app.audioManager.requestTimeout": "Erreur: Un délai est dépassé dans la requête",
"app.audioManager.invalidTarget": "Erreur: Tentative de requête sur une destination invalide",
"app.audioManager.mediaError": "Erreur: Il y a un problème pour obtenir vos périphériques multimédias",
"app.audio.joinAudio": "Rejoindre l'Audio",
"app.audio.leaveAudio": "Quitter l'Audio",
"app.audio.enterSessionLabel": "Entrer dans la Session",
"app.audio.playSoundLabel": "Jouer un son",
"app.audio.backLabel": "Retour",
"app.audio.audioSettings.titleLabel": "Choisissez vos paramètres audio",
"app.audio.audioSettings.descriptionLabel": "Veuillez noter qu'une boîte de dialogue apparaîtra dans votre navigateur, vous demandant d'accepter le partage de votre micro.",
"app.audio.audioSettings.microphoneSourceLabel": "Source du Micro",
"app.audio.audioSettings.speakerSourceLabel": "vous avez {0} nouveau message dans {1}",
"app.audio.audioSettings.microphoneStreamLabel": "Le volume de votre flux audio",
"app.audio.audioSettings.retryLabel": "Réessayer",
"app.audio.listenOnly.backLabel": "Retour",
"app.audio.listenOnly.closeLabel": "Fermer",
"app.audio.permissionsOverlay.title": "Autoriser BigBlueButton à utiliser vos périphériques multimédias",
"app.audio.permissionsOverlay.hint": "Nous avons besoin de vous pour nous permettre d'utiliser vos périphériques Multimédias afin de vous joindre à la conférence vocale :)",
"app.error.removed": "Vous avez été retiré de la conférence",
"app.error.meeting.ended": "Vous avez été déconnecté de la conférence",
"app.dropdown.close": "Fermer",
"app.error.500": "Oups, quelque chose s'est mal passé",
"app.error.404": "Non trouvé",
"app.error.401": "Non autorisé",
"app.error.403": "Interdit",
"app.error.leaveLabel": "Connectez-vous à nouveau",
"app.guest.waiting": "En attente de l'approbation d'adhésion",
"app.toast.breakoutRoomEnded": "La salle de réunion s'est terminée. Veuillez vous joindre à l'audio.",
"app.toast.chat.singular": "vous avez {0} nouveau message dans {1}",
"app.toast.chat.plural": "vous avez {0} nouveaux messages dans {1}",
"app.notification.recordingStart": "Cette session est maintenant enregistrée",
"app.notification.recordingStop": "Cette session n'est maintenant plus enregistrée"
"app.notification.recordingStop": "Cette session n'est maintenant plus enregistrée",
"app.video.joinVideo": "Partager la Webcam",
"app.video.leaveVideo": "Ne plus partager la Webcam",
"app.video.iceCandidateError": "Erreur lors de l'ajout d'un candidat ICE",
"app.video.permissionError": "Erreur lors du partage de la webcam. Vérifiez les permissions svp.",
"app.video.sharingError": "Erreur lors du partage de la Webcam",
"app.video.swapCam": "Échanger",
"app.video.swapCamDesc": "Permuter les Webcams",
"app.video.videoMenu": "Menu Vidéo",
"app.video.videoMenuDisabled": "Le menu Vidéo de la Webcam est désactivé dans les paramètres",
"app.video.videoMenuDesc": "Ouvrir le menu déroulant de la vidéo",
"app.video.chromeExtensionError": "Vous devez installer ",
"app.video.chromeExtensionErrorLink": "cette Extension Chrome",
"app.meeting.endNotification.ok.label": "OK",
"app.whiteboard.toolbar.tools": "Outils",
"app.whiteboard.toolbar.tools.hand": "Main",
"app.whiteboard.toolbar.tools.pencil": "Crayon",
"app.whiteboard.toolbar.tools.rectangle": "Rectangle",
"app.whiteboard.toolbar.tools.triangle": "Triangle",
"app.whiteboard.toolbar.tools.ellipse": "Ellipse",
"app.whiteboard.toolbar.tools.line": "Ligne",
"app.whiteboard.toolbar.tools.text": "Texte",
"app.whiteboard.toolbar.thickness": "Épaisseur du trait",
"app.whiteboard.toolbar.thicknessDisabled": "L'épaisseur du trait est désactivée",
"app.whiteboard.toolbar.color": "Couleurs",
"app.whiteboard.toolbar.colorDisabled": "Couleurs désactivées",
"app.whiteboard.toolbar.color.black": "Noir",
"app.whiteboard.toolbar.color.white": "Blanc",
"app.whiteboard.toolbar.color.red": "Rouge",
"app.whiteboard.toolbar.color.orange": "Orange",
"app.whiteboard.toolbar.color.eletricLime": "Vert Fluo",
"app.whiteboard.toolbar.color.lime": "Vert",
"app.whiteboard.toolbar.color.cyan": "Cyan",
"app.whiteboard.toolbar.color.dodgerBlue": "Bleu foncé",
"app.whiteboard.toolbar.color.blue": "Bleu",
"app.whiteboard.toolbar.color.violet": "Violet",
"app.whiteboard.toolbar.color.magenta": "Magenta",
"app.whiteboard.toolbar.color.silver": "Argenté",
"app.whiteboard.toolbar.undo": "Annuler l'annotation",
"app.whiteboard.toolbar.clear": "Effacer toutes les annotations",
"app.whiteboard.toolbar.multiUserOn": "Activer le mode multi-utilisateur",
"app.whiteboard.toolbar.multiUserOff": "Désactiver le mode multi-utilisateur",
"app.whiteboard.toolbar.fontSize": "Liste des Tailles de Police"
}

View File

@ -0,0 +1,9 @@
{
"app.home.greeting": "Benvenuto {0}! La tua presentazione comincerà a breve...",
"app.chat.submitLabel": "Invia Messaggio",
"app.chat.errorMinMessageLength": "Il messaggio di {0} carattere(i) è troppo breve",
"app.chat.errorMaxMessageLength": "Il messaggio di {0} carattere(i) è troppo lungo",
"app.chat.inputLabel": "Messaggio di input per la chat {0}"
}

View File

@ -34,7 +34,6 @@
"app.userList.menu.makePresenter.label": "ធ្វើ​ជា​អ្នក​ធ្វើ​បទ​បង្ហាញ",
"app.userList.menu.muteUserAudio.label": "បិទសម្លេងអ្នកប្រើប្រាស់",
"app.userList.menu.unmuteUserAudio.label": "ឈប់បិទសម្លេងអ្នកប្រើប្រាស់",
"app.userList.userAriaLabel": "អ្នកប្រើ ៖ {0} តួនាទី​ ៖ {1} មនុស្ស ៖ {2} ស្ថានភាព​ ៖ {3}",
"app.userList.menu.promoteUser.label": "ដំឡើងតួនាទី {0} ទៅជាអ្នកសម្របសម្រួល",
"app.userList.menu.demoteUser.label": "ដកតួនាទី {0} ទៅជាអ្នកមើល",
"app.media.label": "មេឌៀ",

View File

@ -36,10 +36,12 @@
"app.userList.menu.removeUser.label": "Remover usuário",
"app.userList.menu.muteUserAudio.label": "Silenciar usuário",
"app.userList.menu.unmuteUserAudio.label": "Desbloquear microfone do usuário",
"app.userList.userAriaLabel": "Usuário : {0} Papel: {1} Pessoa: {2} Status: {3}",
"app.userList.userAriaLabel": "{0} {1} {2} Status {3}",
"app.userList.menu.promoteUser.label": "Transforme {0} em moderador",
"app.userList.menu.demoteUser.label": "Transforme {0} em espectador",
"app.media.label": "Mídia",
"app.media.screenshare.start": "O compartilhamento de tela foi iniciado",
"app.media.screenshare.end": "O compartilhamento de tela foi encerrado",
"app.meeting.ended": "Esta reunião terminou",
"app.meeting.endedMessage": "Você será redirecionado para a tela inicial",
"app.presentation.presentationToolbar.prevSlideLabel": "Slide anterior",
@ -91,7 +93,11 @@
"app.navBar.settingsDropdown.leaveSessionDesc": "Deixar a reunião",
"app.navBar.settingsDropdown.exitFullscreenDesc": "Sair do modo de tela cheia",
"app.navBar.userListToggleBtnLabel": "Alternar lista de usuários",
"app.navBar.toggleUserList.ariaLabel": "Alternar Participantes e Bate-papo",
"app.navBar.toggleUserList.newMessages": "com notificação para novas mensagens",
"app.navBar.recording": "Esta conferência está sendo gravada",
"app.navBar.recording.on": "A gravação está em andamento",
"app.navBar.recording.off": "Sem gravação",
"app.leaveConfirmation.title": "Deixar a reunião",
"app.leaveConfirmation.message": "Você quer sair desta reunião?",
"app.leaveConfirmation.confirmLabel": "Sair",
@ -99,6 +105,7 @@
"app.leaveConfirmation.dismissLabel": "Cancelar",
"app.leaveConfirmation.dismissDesc": "Fecha e rejeita a confirmação de saída",
"app.leaveConfirmation.endMeetingLabel": "Sim e finalizar a reunião",
"app.leaveConfirmation.endMeetingAriaLabel": "Sair e finalizar a conferência para todos",
"app.leaveConfirmation.endMeetingDesc": "Confirmação para fechar e encerrar a reunião",
"app.about.title": "Sobre",
"app.about.version": "Client Build:",
@ -171,6 +178,12 @@
"app.settings.main.cancel.label.description": "Descartar as alterações e fechar o menu de configurações",
"app.settings.main.save.label": "Salvar",
"app.settings.main.save.label.description": "Salvar as alterações e fechar o menu de configurações",
"app.settings.dataSavingTab.label": "Economia de dados",
"app.settings.dataSavingTab.webcam": "Ativar Webcams",
"app.settings.dataSavingTab.screenShare": "Ativar o compartilhamento de tela",
"app.settings.dataSavingTab.description": "Para economizar o volume de transferência de dados, ajuste o que está sendo exibido no momento.",
"app.switch.onLabel": "Ligar",
"app.switch.offLabel": "Desligar",
"app.actionsBar.actionsDropdown.actionsLabel": "Ações",
"app.actionsBar.actionsDropdown.presentationLabel": "Carregar uma apresentação",
"app.actionsBar.actionsDropdown.initPollLabel": "Iniciar uma enquete",
@ -180,6 +193,8 @@
"app.actionsBar.actionsDropdown.initPollDesc": "Iniciar uma enquete",
"app.actionsBar.actionsDropdown.desktopShareDesc": "Compartilhar sua tela com os outros",
"app.actionsBar.actionsDropdown.stopDesktopShareDesc": "Pare o compartilhamento de tela com",
"app.actionsBar.actionsDropdown.startRecording": "Iniciar a gravação",
"app.actionsBar.actionsDropdown.stopRecording": "Parar a gravação",
"app.actionsBar.emojiMenu.statusTriggerLabel": "Status",
"app.actionsBar.emojiMenu.awayLabel": "Ausente",
"app.actionsBar.emojiMenu.awayDesc": "Mudar seu status para ausente",
@ -193,7 +208,7 @@
"app.actionsBar.emojiMenu.sadDesc": "Mudar seu status para triste",
"app.actionsBar.emojiMenu.happyLabel": "Feliz",
"app.actionsBar.emojiMenu.happyDesc": "Mudar seu status para feliz",
"app.actionsBar.emojiMenu.noneLabel": "Limpar",
"app.actionsBar.emojiMenu.noneLabel": "Limpar Status",
"app.actionsBar.emojiMenu.noneDesc": "Limpar seu status",
"app.actionsBar.emojiMenu.applauseLabel": "Aplauso",
"app.actionsBar.emojiMenu.applauseDesc": "Mudar seu status para aplauso",
@ -232,14 +247,16 @@
"app.audioModal.closeLabel": "Fechar",
"app.audioModal.yes": "Sim",
"app.audioModal.no": "Não",
"app.audioModal.echoTestTitle": "Este é um teste privado de eco. Fale algumas palavras. Você consegue ouvir sua voz?",
"app.audioModal.yes.arialabel" : "Som é audível",
"app.audioModal.no.arialabel" : "Som não é audível",
"app.audioModal.echoTestTitle": "Este é um teste privado de áudio. Fale algumas palavras. Você consegue ouvir sua voz?",
"app.audioModal.settingsTitle": "Alterar as configurações de áudio",
"app.audioModal.helpTitle": "Houve um problema com seus dispositivos de mídia",
"app.audioModal.helpText": "Você autorizou o BigBlueButton a acessar seu microfone? Observe que uma caixa de diálogo deve abrir assim que você tentar participar da conferência de áudio, pedindo as permissões de acesso aos dispositivos de mídia, por favor conceda essa permissão de acesso para participar da conferência. Se isso não funcionar, tente alterar a permissão do microfone nas configurações do seu navegador.",
"app.audioModal.connecting": "Conectando",
"app.audioModal.connectingEchoTest": "Conectando ao teste de eco",
"app.audioModal.connectingEchoTest": "Conectando ao teste de áudio",
"app.audioManager.joinedAudio": "Você se juntou à conferência de áudio",
"app.audioManager.joinedEcho": "O teste de eco foi iniciado",
"app.audioManager.joinedEcho": "O teste de áudio foi iniciado",
"app.audioManager.leftAudio": "Você deixou a conferência de áudio",
"app.audioManager.genericError": "Erro: Ocorreu um erro, tente novamente",
"app.audioManager.connectionError": "Erro: Erro de conexão",
@ -280,6 +297,11 @@
"app.video.iceCandidateError": "Erro ao adicionar o candidato ICE",
"app.video.permissionError": "Erro ao compartilhar a webcam. Verifique as permissões",
"app.video.sharingError": "Erro ao compartilhar a webcam",
"app.video.swapCam": "Alterar",
"app.video.swapCamDesc": "Alterar a orientação das webcams",
"app.video.videoMenu": "Menu de vídeo",
"app.video.videoMenuDisabled": "Webcam desativada nas configurações",
"app.video.videoMenuDesc": "Abra o menu de vídeo",
"app.video.chromeExtensionError": "Você deve instalar o seguinte:",
"app.video.chromeExtensionErrorLink": "esta extensão do Chrome",
"app.meeting.endNotification.ok.label": "OK",
@ -291,8 +313,10 @@
"app.whiteboard.toolbar.tools.ellipse": "Elipse",
"app.whiteboard.toolbar.tools.line": "Linha",
"app.whiteboard.toolbar.tools.text": "Texto",
"app.whiteboard.toolbar.thickness": "Lista de espessura",
"app.whiteboard.toolbar.color": "Lista de cores",
"app.whiteboard.toolbar.thickness": "Espessura da Linha",
"app.whiteboard.toolbar.thicknessDisabled": "A espessura da Linha está Desativada",
"app.whiteboard.toolbar.color": "Cores",
"app.whiteboard.toolbar.colorDisabled": "As Cores estão Desativadas",
"app.whiteboard.toolbar.color.black": "Preto",
"app.whiteboard.toolbar.color.white": "Branco",
"app.whiteboard.toolbar.color.red": "Vermelho",

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