diff --git a/bigbluebutton-apps/src/main/java/org/bigbluebutton/conference/BigBlueButtonService.java b/bigbluebutton-apps/src/main/java/org/bigbluebutton/conference/BigBlueButtonService.java new file mode 100755 index 0000000000000000000000000000000000000000..52baf7218b4c1f58d32016336d8f3d296c43ad8f --- /dev/null +++ b/bigbluebutton-apps/src/main/java/org/bigbluebutton/conference/BigBlueButtonService.java @@ -0,0 +1,33 @@ +package org.bigbluebutton.conference; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import org.red5.server.api.Red5; + +public class BigBlueButtonService { + private ConnectionInvokerService connInvokerService; + + public void sendMessage(HashMap<String, Object> params) { + + Map<String, Object> messageToSend = new HashMap<String, Object>(); + + for (Iterator<String> it = params.keySet().iterator(); it.hasNext();) { + String key = it.next(); + messageToSend.put(key, params.get(key)); + } + + ClientMessage m = new ClientMessage(ClientMessage.BROADCAST, getMeetingId(), (String) params.get("messageID"), messageToSend); + connInvokerService.sendMessage(m); + } + + private String getMeetingId(){ + return Red5.getConnectionLocal().getScope().getName(); + } + + public void setConnInvokerService(ConnectionInvokerService connInvokerService) { + this.connInvokerService = connInvokerService; + } + +} diff --git a/bigbluebutton-apps/src/main/java/org/bigbluebutton/webconference/voice/freeswitch/FreeswitchApplication.java b/bigbluebutton-apps/src/main/java/org/bigbluebutton/webconference/voice/freeswitch/FreeswitchApplication.java index 9c6a6149e41022427dc7701e030d142ad691ba12..b2461e344ad71f30d4f64d14f67b6dadaa2811ff 100755 --- a/bigbluebutton-apps/src/main/java/org/bigbluebutton/webconference/voice/freeswitch/FreeswitchApplication.java +++ b/bigbluebutton-apps/src/main/java/org/bigbluebutton/webconference/voice/freeswitch/FreeswitchApplication.java @@ -58,10 +58,12 @@ public class FreeswitchApplication extends Observable implements ConferenceServi private FreeswitchHeartbeatMonitor heartbeatMonitor; private boolean debug = false; + private String icecastProtocol = "shout"; private String icecastHost = "localhost"; private int icecastPort = 8000; private String icecastUsername = "source"; private String icecastPassword = "hackme"; + private String icecastStreamExtension = ".mp3"; private Boolean icecastBroadcast = false; private final Integer USER = 0; /* not used for now */ @@ -180,8 +182,8 @@ public class FreeswitchApplication extends Observable implements ConferenceServi } private void broadcastToIcecast(String room, String meetingid) { - String shoutPath = "shout://" + icecastUsername + ":" + icecastPassword + "@" + icecastHost + ":" + icecastPort - + File.separatorChar + meetingid + ".mp3"; + String shoutPath = icecastProtocol + "://" + icecastUsername + ":" + icecastPassword + "@" + icecastHost + ":" + icecastPort + + File.separatorChar + meetingid + "." + icecastStreamExtension; if (log.isDebugEnabled()) log.debug("Broadcast to {}", shoutPath); @@ -364,7 +366,11 @@ public class FreeswitchApplication extends Observable implements ConferenceServi public void setDebugNullConferenceAction(boolean enabled) { this.debug = enabled; } - + + public void setIcecastProtocol(String protocol) { + icecastProtocol = protocol; + } + public void setIcecastHost(String host) { icecastHost = host; } @@ -384,6 +390,10 @@ public class FreeswitchApplication extends Observable implements ConferenceServi public void setIcecastBroadcast(Boolean broadcast) { icecastBroadcast = broadcast; } + + public void setIcecastStreamExtension(String ext) { + icecastStreamExtension = ext; + } private Integer getMemberIdFromEvent(EslEvent e) { diff --git a/bigbluebutton-apps/src/main/webapp/WEB-INF/bbb-apps.xml b/bigbluebutton-apps/src/main/webapp/WEB-INF/bbb-apps.xml index 6f44100ea8efc5ac43021464e91885304042a2bc..9eab28be4d2fcf23e812f3a5e08feff674659028 100755 --- a/bigbluebutton-apps/src/main/webapp/WEB-INF/bbb-apps.xml +++ b/bigbluebutton-apps/src/main/webapp/WEB-INF/bbb-apps.xml @@ -12,7 +12,11 @@ <property name="messagingService" ref="messagingService"></property> <property name="conversionUpdatesMessageListener" ref="conversionUpdatesMessageListener"></property> </bean> - + + <bean id="bigbluebutton.service" class="org.bigbluebutton.conference.BigBlueButtonService"> + <property name="connInvokerService"> <ref bean="connInvokerService"/></property> + </bean> + <bean id="participantsHandler" class="org.bigbluebutton.conference.service.participants.ParticipantsHandler"> <property name="participantsApplication"> <ref local="participantsApplication"/></property> <property name="recorderApplication"> <ref local="recorderApplication"/></property> diff --git a/bigbluebutton-apps/src/main/webapp/WEB-INF/bbb-voice-freeswitch.xml b/bigbluebutton-apps/src/main/webapp/WEB-INF/bbb-voice-freeswitch.xml index 72a64f3e192bb5e593bb83d6a53ab0e3e8152dea..b4d9d7623985ad19454c484edd829ab6c6c64d42 100755 --- a/bigbluebutton-apps/src/main/webapp/WEB-INF/bbb-voice-freeswitch.xml +++ b/bigbluebutton-apps/src/main/webapp/WEB-INF/bbb-voice-freeswitch.xml @@ -14,6 +14,7 @@ <beans:bean id="freeswitchApplication" class="org.bigbluebutton.webconference.voice.freeswitch.FreeswitchApplication"> <beans:property name="managerConnection" ref="eslConnection"/> <beans:property name="DebugNullConferenceAction" value="true"/> + <beans:property name="icecastProtocol" value="${icecast.protocol}"/> <beans:property name="icecastHost" value="${icecast.host}"/> <beans:property name="icecastPort" value="${icecast.port}"/> <beans:property name="icecastUsername" value="${icecast.username}"/> diff --git a/bigbluebutton-apps/src/main/webapp/WEB-INF/bigbluebutton.properties b/bigbluebutton-apps/src/main/webapp/WEB-INF/bigbluebutton.properties index 5127e693c51804f21f42047eef4b5a0c46293d05..094ee6db58fdf4fd823869282a2365aa93fffe99 100755 --- a/bigbluebutton-apps/src/main/webapp/WEB-INF/bigbluebutton.properties +++ b/bigbluebutton-apps/src/main/webapp/WEB-INF/bigbluebutton.properties @@ -31,6 +31,8 @@ version=0.63 redis.host=127.0.0.1 redis.port=6379 +icecast.protocol=shout +icecast.streamExt=mp3 icecast.host=127.0.0.1 icecast.port=8000 icecast.username=source diff --git a/bigbluebutton-client/branding/README b/bigbluebutton-client/branding/README new file mode 100644 index 0000000000000000000000000000000000000000..aeb8e83436fbf7c301670cf05c73c190287d05d4 --- /dev/null +++ b/bigbluebutton-client/branding/README @@ -0,0 +1,3 @@ +This is the directory where you should put your custom skinning/styling. + + diff --git a/bigbluebutton-client/branding/default/README b/bigbluebutton-client/branding/default/README new file mode 100644 index 0000000000000000000000000000000000000000..83731d116814f02866d6528e7052fd5f7861c958 --- /dev/null +++ b/bigbluebutton-client/branding/default/README @@ -0,0 +1 @@ + This is the default BigBlueButton style. diff --git a/bigbluebutton-client/branding/default/build.xml b/bigbluebutton-client/branding/default/build.xml new file mode 100755 index 0000000000000000000000000000000000000000..9f2653eeea7264438df5f80714cc4faa6b6692b4 --- /dev/null +++ b/bigbluebutton-client/branding/default/build.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- BigBlueButton Branding build.xml for use by Hudson builds. --> +<project name="BigBlueButton Branding" basedir="../.." default="branding" > + <property environment="env" /> + <property name="DEBUG" value="true" /> + <property name="BUILD_ENV" value="DEV" /> + <property name="FLEX_HOME" value="${env.FLEX_HOME}" /> + <property name="themeFile" value="BBBDefault.css" /> + <property name="BASE_DIR" value="${basedir}" /> + <property name="SRC_DIR" value="${BASE_DIR}/src" /> + + <property name="OUTPUT_DIR" value="${BASE_DIR}/bin" /> + <taskdef resource="flexTasks.tasks" classpath="${FLEX_HOME}/ant/lib/flexTasks.jar" /> + + <target name="init-ant-contrib"> + <property name="ant-contrib.jar" location="${BASE_DIR}/build/lib/ant-contrib-0.6.jar"/> + <taskdef resource="net/sf/antcontrib/antcontrib.properties" classpath="${ant-contrib.jar}"/> + </target> + + <target name="branding" depends="init-ant-contrib"> + <sequential> + <mxmlc file="${BASE_DIR}/branding/default/style/css/${themeFile}" + output="${OUTPUT_DIR}/branding/css/${themeFile}.swf" + debug="${DEBUG}" + mxml.compatibility-version="3.0.0" + swf-version="13" + optimize="true"> + </mxmlc> + </sequential> + </target> +</project> diff --git a/bigbluebutton-client/branding/default/style/css/BBBDefault.css b/bigbluebutton-client/branding/default/style/css/BBBDefault.css new file mode 100755 index 0000000000000000000000000000000000000000..aca09e5462861917ddd556e7adf99c1a03672c1d --- /dev/null +++ b/bigbluebutton-client/branding/default/style/css/BBBDefault.css @@ -0,0 +1,504 @@ +Application { + backgroundColor: #ffffff; +} + +ApplicationControlBar { + highlightAlphas: 0.1, 0; + fillAlphas: 0, 0; + fillColors: #32343b, #32343b; + backgroundColor: #42444c; + backgroundAlpha: 1; + cornerRadius: 0; + dropShadowEnabled: true; + shadowDistance: 1; + shadowDirection: center; + dropShadowColor: #000000; +} + +Panel { + borderColor: #dfdfdf; + borderAlpha: 1; + shadowDistance: 1; + dropShadowColor: #666666; + titleStyleName: "mypanelTitle"; +} + +.mypanelTitle { + color: #444444; + fontFamily: Arial; + fontSize: 12; +} + +.whiteboardToolbarStyle { + backgroundColor: #CCCCCC; + cornerRadius: 5; + borderStyle: solid; + borderThickness: 1; + paddingBottom: 3; + paddingTop: 3; + paddingLeft: 3; + paddingRight: 3; +} + +Button { + textIndent: 0; + paddingLeft: 1; + paddingRight: 1; + paddingTop: 1; + paddingBottom: 1; + highlightAlphas: 0.4, 0; + fillAlphas: 1, 1, 1, 1; + fillColors: #fefeff, #e1e2e5, #ffffff, #eeeeee; + color: #5e5f63; + textRollOverColor: #5e5f63; + textSelectedColor: #5e5f63; + borderColor: #b9babc; + themeColor: haloBlue; + fontFamily: Arial; + fontSize: 12; +} + +.chatSendButtonStyle { + cornerRadius: 0; + highlightAlphas: 0, 0; + fillAlphas: 1, 1, 1, 1; + fillColors: #262626, #262626, #454545, #454545; + color: #ffffff; + textRollOverColor: #ffffff; + textSelectedColor: #262626; + borderColor: #000000; + themeColor: haloBlue; + fontFamily: Arial; + fontSize: 12; +} + +.logoutButtonStyle { + cornerRadius: 0; + highlightAlphas: 0, 0; + fillAlphas: 1, 1, 1, 1; + fillColors: #262626, #262626, #454545, #454545; + color: #ffffff; + textRollOverColor: #ffffff; + textSelectedColor: #262626; + borderColor: #000000; + themeColor: haloBlue; + fontFamily: Arial; + fontSize: 12; +} + +.helpLinkButtonStyle { + rollOverColor: #cccccc; + selectionColor: #999999; + color: #ffffff; + textRollOverColor: #504f3d; + textSelectedColor: #504f3d; +} + +DataGrid { + backgroundColor: #e1e2e5; + rollOverColor: #f3f3f3; + textRollOverColor: #5e5f63; + selectionColor: #e1e2e5; + color: #5e5f63; + textSelectedColor: #5e5f63; + dropShadowEnabled: false; + fontFamily: arial; + fontSize: 11; + headerStyleName: "mydataGridHeaderStyle"; +} + +.mydataGridHeaderStyle { + color: #5e5f63; + fontFamily: arial; + fontSize: 12; +} + + +.whiteboardUndoButtonStyle, .whiteboardCircleButtonStyle, .whiteboardClearButtonStyle, +.whiteboardTriangleButtonStyle, .whiteboardTextButtonStyle, .whiteboardRectangleButtonStyle, +.whiteboardPanZoomButtonStyle, .whiteboardLineButtonStyle, .whiteboardScribbleButtonStyle +{ + textIndent: 0; + paddingLeft: 0; + paddingRight: 0; + paddingTop: 5; + paddingBottom: 5; + highlightAlphas: 0.4, 0; + fillAlphas: 1, 1, 1, 1; + fillColors: #fefeff, #e1e2e5, #ffffff, #eeeeee; + color: #5e5f63; + textRollOverColor: #5e5f63; + textSelectedColor: #5e5f63; + borderColor: #b9babc; + themeColor: haloBlue; + fontFamily: Arial; + fontSize: 12; +} + +.whiteboardUndoButtonStyle +{ + icon: Embed('assets/images/undo.png'); +} + +.whiteboardCircleButtonStyle { + icon: Embed('assets/images/ellipse.png'); +} + +.whiteboardClearButtonStyle { + icon: Embed('assets/images/delete.png'); +} + +.whiteboardScribbleButtonStyle{ + icon: Embed('assets/images/pencil.png'); +} + +.whiteboardLineButtonStyle { + icon: Embed('assets/images/line.png'); +} + +.whiteboardPanZoomButtonStyle { + icon: Embed('assets/images/hand.png'); +} + +.whiteboardRectangleButtonStyle { + icon: Embed('assets/images/square.png'); +} + +.whiteboardTextButtonStyle { + icon: Embed('assets/images/text.png'); +} + +.whiteboardTriangleButtonStyle { + icon: Embed('assets/images/triangle.png'); +} + +.presentationUploadButtonStyle, .presentationBackButtonStyle, .presentationForwardButtonStyle, +.presentationFitToWidthButtonStyle, .presentationFitToPageButtonStyle +{ + textIndent: 0; + paddingLeft: 10; + paddingRight: 10; + paddingTop: 5; + paddingBottom: 5; + highlightAlphas: 0.4, 0; + fillAlphas: 1, 1, 1, 1; + fillColors: #fefeff, #e1e2e5, #ffffff, #eeeeee; + color: #5e5f63; + textRollOverColor: #5e5f63; + textSelectedColor: #5e5f63; + borderColor: #b9babc; + themeColor: haloBlue; + fontFamily: Arial; + fontSize: 12; +} + +.presentationUploadButtonStyle { + icon: Embed('assets/images/upload.png'); +} + +.presentationBackButtonStyle { + icon: Embed('assets/images/left-arrow.png'); +} + +.presentationForwardButtonStyle { + icon: Embed('assets/images/right-arrow.png'); +} + +.presentationFitToWidthButtonStyle { + icon: Embed('assets/images/fit-to-width.png'); +} + +.presentationFitToPageButtonStyle { + icon: Embed('assets/images/fit-to-screen.png'); +} + +.presentationZoomSliderStyle{ + labelOffset: 0; + thumbOffset: 3; + dataTipOffset: -1; + tickOffset: 2; + tickLength: 5; + tickThickness: 2; + tickColor: #6cccff; + showTrackHighlight: true; + invertThumbDirection: false; + borderColor: #b9babc; + trackColors: #aaaaaa, #aaaaaa; + themeColor: haloBlue; + fillAlphas: 1, 1, 1, 1; + fillColors: #fefeff, #e1e2e5, #ffffff, #eeeeee; + labelStyleName: "presentationZoomSliderLabelStyle"; +} + +.presentationZoomSliderLabelStyle { + color: #5e5f63; + fontFamily: Arial; +} + +.videoMuteButtonStyle, .videoUnmutedButtonStyle, .videoSwitchPresenterButtonStyle, .videoEjectUserButtonStyle, .videoPrivateChatButtonStyle { + fillAlphas: 1, 1, 1, 1; + fillColors: #fefeff, #e1e2e5, #ffffff, #eeeeee; + color: #5e5f63; + textRollOverColor: #ffffff; + textSelectedColor: #ffffff; + borderColor: #b9babc; + cornerRadius: 17; +} + +.videoMutedButtonStyle { + /* Need to duplicate info here as the styleName is programatically changed. Extracting it out doesn't have any effect. */ + fillAlphas: 1, 1, 1, 1; + fillColors: #96c7e6, #50a6d7, #ffffff, #eeeeee; + color: #ffffff; + textRollOverColor: #ffffff; + textSelectedColor: #ffffff; + borderColor: #50a6d7; + cornerRadius: 17; + icon: Embed('assets/images/webcam-muted.png'); +} + +.videoUnmutedButtonStyle { + icon: Embed('assets/images/webcam-unmuted.png'); +} + +.videoSwitchPresenterButtonStyle { + icon: Embed('assets/images/webcam-switch-presenter.png'); +} + +.videoEjectUserButtonStyle { + icon: Embed('assets/images/webcam-eject-user.png'); +} + +.videoPrivateChatButtonStyle { + icon: Embed('assets/images/webcam-private-chat.png'); +} + +MDIWindow { + +} + +.mdiWindowFocus, .presentationWindowStyleFocus, .videoDockStyleFocus +{ + headerHeight: 26; + roundedBottomCorners: true; + backgroundAlpha: 1; + backgroundColor: #b9babc; + backgroundSize: '100%'; + + borderStyle: solid; + borderColor: #b9babc; + borderAlpha: 1; + borderThickness: 1; + borderThicknessLeft: 1; + borderThicknessTop: 1; + borderThicknessBottom: 1; + borderThicknessRight: 1; + cornerRadius: 0; + dropShadowEnabled: false; + titleStyleName: "mypanelTitle"; + + cornerResizeImg: Embed(source="assets/swf/Blue.swf", symbol="Corner_Resize"); + cornerResizeWidth: 2; + cornerResizeHeight: 2; + cornerResizePaddingRight: 2; + cornerResizePaddingBottom: 2; + + controlButtonWidth: 10; + controlButtonHeight: 10; + controlButtonGap: 4; +} + +.mdiWindowNoFocus, .presentationWindowStyleNoFocus, .videoDockStyleNoFocus +{ + headerHeight: 26; + roundedBottomCorners: false; + backgroundAlpha: 0.5; + backgroundColor: #b9babc; + backgroundSize: '100%'; + + borderStyle: solid; + borderColor: #b9babc; + borderAlpha: 0.5; + borderThickness: 1; + borderThicknessLeft: 1; + borderThicknessTop: 1; + borderThicknessBottom: 1; + borderThicknessRight: 1; + cornerRadius: 0; + dropShadowEnabled: false; + titleStyleName: "mypanelTitle"; + + cornerResizeImg: Embed(source="assets/swf/Blue.swf", symbol="Corner_Resize"); + cornerResizeWidth: 2; + cornerResizeHeight: 2; + cornerResizePaddingRight: 2; + cornerResizePaddingBottom: 2; + + controlButtonWidth: 10; + controlButtonHeight: 10; + controlButtonGap: 4; +} + +.presentationSlideViewStyle { + backgroundColor: #b9babc; +} + +.presentationWindowControlsStyle { + paddingBottom: 5; + paddingTop: 3; + paddingLeft: 3; + paddingRight: 3; + bottom: 5; + cornerRadius: 0; + dropShadowEnabled: false; +} + +.videoViewStyleNoFocus +{ + borderColor: #b9babc; + borderAlpha: 0.5; + borderThicknessLeft: 5; + borderThicknessTop: 5; + borderThicknessBottom: 5; + borderThicknessRight: 5; + roundedBottomCorners: true; + cornerRadius: 5; + headerHeight: 20; + backgroundColor: #b9babc; + backgroundAlpha: 0.5; + dropShadowEnabled: false; + titleStyleName: "mypanelTitle"; +} + +.videoViewStyleFocus +{ + borderColor: #b9babc; + borderAlpha: 1; + borderThicknessLeft: 5; + borderThicknessTop: 5; + borderThicknessBottom: 5; + borderThicknessRight: 5; + roundedBottomCorners: true; + cornerRadius: 5; + headerHeight: 20; + backgroundColor: #b9babc; + backgroundAlpha: 1; + dropShadowEnabled: false; + titleStyleName: "mypanelTitle"; +} + +.videoPublishStyleNoFocus +{ + borderColor: #b9babc; + borderAlpha: 0.5; + borderThicknessLeft: 5; + borderThicknessTop: 5; + borderThicknessBottom: 5; + borderThicknessRight: 5; + roundedBottomCorners: true; + cornerRadius: 5; + headerHeight: 20; + backgroundColor: #b9babc; + backgroundAlpha: 0.5; + dropShadowEnabled: false; + titleStyleName: "mypanelTitle"; +} + +.videoPublishStyleFocus +{ + borderColor: #b9babc; + borderAlpha: 1; + borderThicknessLeft: 5; + borderThicknessTop: 5; + borderThicknessBottom: 5; + borderThicknessRight: 5; + roundedBottomCorners: true; + cornerRadius: 5; + headerHeight: 20; + backgroundColor: #b9babc; + backgroundAlpha: 1; + dropShadowEnabled: false; + titleStyleName: "mypanelTitle"; +} + +.videoAvatarStyleNoFocus +{ + borderColor: #b9babc; + borderAlpha: 0.5; + borderThicknessLeft: 5; + borderThicknessTop: 5; + borderThicknessBottom: 5; + borderThicknessRight: 5; + roundedBottomCorners: true; + cornerRadius: 5; + headerHeight: 20; + backgroundColor: #b9babc; + backgroundAlpha: 0.5; + dropShadowEnabled: false; + titleStyleName: "mypanelTitle"; +} + +.videoAvatarStyleFocus +{ + borderColor: #b9babc; + borderAlpha: 1; + borderThicknessLeft: 5; + borderThicknessTop: 5; + borderThicknessBottom: 5; + borderThicknessRight: 5; + roundedBottomCorners: true; + cornerRadius: 5; + headerHeight: 20; + backgroundColor: #b9babc; + backgroundAlpha: 1; + dropShadowEnabled: false; + titleStyleName: "mypanelTitle"; +} + +.mypanelTitle { + color: #5e5f63; + fontFamily: Arial; + fontSize: 12; +} + +.closeBtnFocus, .closeBtnNoFocus +{ + upSkin: Embed('assets/images/3_closeButton.png'); + overSkin: Embed('assets/images/3_closeButton.png'); + downSkin: Embed('assets/images/3_closeButton.png'); + disabledSkin: Embed('assets/images/3_closeButton.png'); +} + + +.increaseBtnFocus, .increaseBtnNoFocus +{ + upSkin: Embed('assets/images/3_increaseButton.png'); + overSkin: Embed('assets/images/3_increaseButton.png'); + downSkin: Embed('assets/images/3_increaseButton.png'); + disabledSkin: Embed('assets/images/3_increaseButton.png'); +} + +.decreaseBtnFocus, .decreaseBtnNoFocus +{ + upSkin: Embed('assets/images/3_decreaseButton.png'); + overSkin: Embed('assets/images/3_decreaseButton.png'); + downSkin: Embed('assets/images/3_decreaseButton.png'); + disabledSkin: Embed('assets/images/3_decreaseButton.png'); +} + +.minimizeBtnFocus +{ + upSkin: Embed('assets/images/3_minimizeButton.png'); + overSkin: Embed('assets/images/3_minimizeButton.png'); + downSkin: Embed('assets/images/3_minimizeButton.png'); + disabledSkin: Embed('assets/images/3_minimizeButton.png'); +} + + +.resizeHndlr +{ + upSkin: Embed('assets/images/resizeHandler.png'); + overSkin: Embed('assets/images/resizeHandler.png'); + downSkin: Embed('assets/images/resizeHandler.png'); + disabledSkin: Embed('assets/images/resizeHandler.png'); +} diff --git a/bigbluebutton-client/src/branding/css/assets/img/3_closeButton.png b/bigbluebutton-client/branding/default/style/css/assets/images/3_closeButton.png similarity index 100% rename from bigbluebutton-client/src/branding/css/assets/img/3_closeButton.png rename to bigbluebutton-client/branding/default/style/css/assets/images/3_closeButton.png diff --git a/bigbluebutton-client/src/branding/css/assets/img/3_decreaseButton.png b/bigbluebutton-client/branding/default/style/css/assets/images/3_decreaseButton.png similarity index 100% rename from bigbluebutton-client/src/branding/css/assets/img/3_decreaseButton.png rename to bigbluebutton-client/branding/default/style/css/assets/images/3_decreaseButton.png diff --git a/bigbluebutton-client/src/branding/css/assets/img/3_increaseButton.png b/bigbluebutton-client/branding/default/style/css/assets/images/3_increaseButton.png similarity index 100% rename from bigbluebutton-client/src/branding/css/assets/img/3_increaseButton.png rename to bigbluebutton-client/branding/default/style/css/assets/images/3_increaseButton.png diff --git a/bigbluebutton-client/src/branding/css/assets/img/3_minimizeButton.png b/bigbluebutton-client/branding/default/style/css/assets/images/3_minimizeButton.png similarity index 100% rename from bigbluebutton-client/src/branding/css/assets/img/3_minimizeButton.png rename to bigbluebutton-client/branding/default/style/css/assets/images/3_minimizeButton.png diff --git a/bigbluebutton-client/branding/default/style/css/assets/images/delete.png b/bigbluebutton-client/branding/default/style/css/assets/images/delete.png new file mode 100755 index 0000000000000000000000000000000000000000..0059eaa72ce11f79b11fb2fa0d5c3e225ca6c11a Binary files /dev/null and b/bigbluebutton-client/branding/default/style/css/assets/images/delete.png differ diff --git a/bigbluebutton-client/branding/default/style/css/assets/images/ellipse.png b/bigbluebutton-client/branding/default/style/css/assets/images/ellipse.png new file mode 100755 index 0000000000000000000000000000000000000000..3252b6aa957f272a4c0ae8ea14213464681cba7d Binary files /dev/null and b/bigbluebutton-client/branding/default/style/css/assets/images/ellipse.png differ diff --git a/bigbluebutton-client/branding/default/style/css/assets/images/fit-to-screen.png b/bigbluebutton-client/branding/default/style/css/assets/images/fit-to-screen.png new file mode 100755 index 0000000000000000000000000000000000000000..afbfdbfc06c5b284df1580abe083dd486d4c2e69 Binary files /dev/null and b/bigbluebutton-client/branding/default/style/css/assets/images/fit-to-screen.png differ diff --git a/bigbluebutton-client/branding/default/style/css/assets/images/fit-to-width.png b/bigbluebutton-client/branding/default/style/css/assets/images/fit-to-width.png new file mode 100755 index 0000000000000000000000000000000000000000..25be501f1eb1578ee1ae71f85f2335caa42152c4 Binary files /dev/null and b/bigbluebutton-client/branding/default/style/css/assets/images/fit-to-width.png differ diff --git a/bigbluebutton-client/branding/default/style/css/assets/images/hand.png b/bigbluebutton-client/branding/default/style/css/assets/images/hand.png new file mode 100755 index 0000000000000000000000000000000000000000..d923194e5ed2ccd5ac57a456540474727d6531b4 Binary files /dev/null and b/bigbluebutton-client/branding/default/style/css/assets/images/hand.png differ diff --git a/bigbluebutton-client/branding/default/style/css/assets/images/icons-license.txt b/bigbluebutton-client/branding/default/style/css/assets/images/icons-license.txt new file mode 100755 index 0000000000000000000000000000000000000000..9f25609678fefd389d8e80e4ea4b0243b685c2b4 --- /dev/null +++ b/bigbluebutton-client/branding/default/style/css/assets/images/icons-license.txt @@ -0,0 +1,38 @@ +Silk icon set 1.3 + +_________________________________________ +Mark James +http://www.famfamfam.com/lab/icons/silk/ +_________________________________________ + +This work is licensed under a +Creative Commons Attribution 2.5 License. +[ http://creativecommons.org/licenses/by/2.5/ ] + +This means you may use it for any purpose, +and make any changes you like. +All I ask is that you include a link back +to this page in your credits. + +Are you using this icon set? Send me an email +(including a link or picture if available) to +mjames@gmail.com + +Any other questions about this icon set please +contact mjames@gmail.com + +================================================== +Diagona Icons + +Copyright (C) 2007 Yusuke Kamiyamane. All rights reserved. +The icons are licensed under a Creative Commons Attribution +3.0 license. <http://creativecommons.org/licenses/by/3.0/> + +If you can't or don't want to provide a link back, please +purchase a royalty-free license. +<http://p.yusukekamiyamane.com/> + +I'm unavailable for custom icon design work. But your +suggestions are always welcome! +<mailto:p@yusukekamiyamane.com> +==================== \ No newline at end of file diff --git a/bigbluebutton-client/branding/default/style/css/assets/images/left-arrow.png b/bigbluebutton-client/branding/default/style/css/assets/images/left-arrow.png new file mode 100755 index 0000000000000000000000000000000000000000..e6abeb60d45d8a2a2b0f85b741f1af14683a4c4c Binary files /dev/null and b/bigbluebutton-client/branding/default/style/css/assets/images/left-arrow.png differ diff --git a/bigbluebutton-client/branding/default/style/css/assets/images/line.png b/bigbluebutton-client/branding/default/style/css/assets/images/line.png new file mode 100755 index 0000000000000000000000000000000000000000..175174a0ea5ebb32535ad19ccd0a86a319496bb9 Binary files /dev/null and b/bigbluebutton-client/branding/default/style/css/assets/images/line.png differ diff --git a/bigbluebutton-client/branding/default/style/css/assets/images/marker.png b/bigbluebutton-client/branding/default/style/css/assets/images/marker.png new file mode 100755 index 0000000000000000000000000000000000000000..7a026099e3685ceb87505f5b1292f48b714da566 Binary files /dev/null and b/bigbluebutton-client/branding/default/style/css/assets/images/marker.png differ diff --git a/bigbluebutton-client/branding/default/style/css/assets/images/pencil.png b/bigbluebutton-client/branding/default/style/css/assets/images/pencil.png new file mode 100755 index 0000000000000000000000000000000000000000..dd979748baebc063f532af178489732ab0c4548f Binary files /dev/null and b/bigbluebutton-client/branding/default/style/css/assets/images/pencil.png differ diff --git a/bigbluebutton-client/src/branding/css/assets/img/resizeHandler.png b/bigbluebutton-client/branding/default/style/css/assets/images/resizeHandler.png similarity index 100% rename from bigbluebutton-client/src/branding/css/assets/img/resizeHandler.png rename to bigbluebutton-client/branding/default/style/css/assets/images/resizeHandler.png diff --git a/bigbluebutton-client/branding/default/style/css/assets/images/right-arrow.png b/bigbluebutton-client/branding/default/style/css/assets/images/right-arrow.png new file mode 100755 index 0000000000000000000000000000000000000000..6eab9f047a5ecd05d65ac1dae3f226b80009347d Binary files /dev/null and b/bigbluebutton-client/branding/default/style/css/assets/images/right-arrow.png differ diff --git a/bigbluebutton-client/branding/default/style/css/assets/images/square.png b/bigbluebutton-client/branding/default/style/css/assets/images/square.png new file mode 100755 index 0000000000000000000000000000000000000000..48b5280bdb794d3d5de562af781f3affeda59ffc Binary files /dev/null and b/bigbluebutton-client/branding/default/style/css/assets/images/square.png differ diff --git a/bigbluebutton-client/branding/default/style/css/assets/images/text.png b/bigbluebutton-client/branding/default/style/css/assets/images/text.png new file mode 100755 index 0000000000000000000000000000000000000000..21858b8ca4c28e93acb3eddd665860cf214d3bcf Binary files /dev/null and b/bigbluebutton-client/branding/default/style/css/assets/images/text.png differ diff --git a/bigbluebutton-client/branding/default/style/css/assets/images/trash.png b/bigbluebutton-client/branding/default/style/css/assets/images/trash.png new file mode 100755 index 0000000000000000000000000000000000000000..2b95a3faa6ff838bff79a0c8e53a8a0c83bdb9e5 Binary files /dev/null and b/bigbluebutton-client/branding/default/style/css/assets/images/trash.png differ diff --git a/bigbluebutton-client/branding/default/style/css/assets/images/triangle.png b/bigbluebutton-client/branding/default/style/css/assets/images/triangle.png new file mode 100755 index 0000000000000000000000000000000000000000..f629ee6aebb3c600c700ef3eb54cbf59917dd70b Binary files /dev/null and b/bigbluebutton-client/branding/default/style/css/assets/images/triangle.png differ diff --git a/bigbluebutton-client/branding/default/style/css/assets/images/undo.png b/bigbluebutton-client/branding/default/style/css/assets/images/undo.png new file mode 100755 index 0000000000000000000000000000000000000000..f65e9db3edf0cd4f2ee09b2a463e8436c19894d3 Binary files /dev/null and b/bigbluebutton-client/branding/default/style/css/assets/images/undo.png differ diff --git a/bigbluebutton-client/branding/default/style/css/assets/images/upload.png b/bigbluebutton-client/branding/default/style/css/assets/images/upload.png new file mode 100755 index 0000000000000000000000000000000000000000..724a094ae6974c1fd6e451ed492a06955e538ed2 Binary files /dev/null and b/bigbluebutton-client/branding/default/style/css/assets/images/upload.png differ diff --git a/bigbluebutton-client/branding/default/style/css/assets/images/webcam-eject-user.png b/bigbluebutton-client/branding/default/style/css/assets/images/webcam-eject-user.png new file mode 100755 index 0000000000000000000000000000000000000000..561edc8d9b00d9c97c9acd82b4bf121bc3a77426 Binary files /dev/null and b/bigbluebutton-client/branding/default/style/css/assets/images/webcam-eject-user.png differ diff --git a/bigbluebutton-client/branding/default/style/css/assets/images/webcam-muted.png b/bigbluebutton-client/branding/default/style/css/assets/images/webcam-muted.png new file mode 100755 index 0000000000000000000000000000000000000000..3be5bd19cf8cf09beb6c7c7758b21b5deabab69b Binary files /dev/null and b/bigbluebutton-client/branding/default/style/css/assets/images/webcam-muted.png differ diff --git a/bigbluebutton-client/branding/default/style/css/assets/images/webcam-private-chat.png b/bigbluebutton-client/branding/default/style/css/assets/images/webcam-private-chat.png new file mode 100755 index 0000000000000000000000000000000000000000..9fdff988a3cfa2f6ef81f0a2d0de135dd199371e Binary files /dev/null and b/bigbluebutton-client/branding/default/style/css/assets/images/webcam-private-chat.png differ diff --git a/bigbluebutton-client/branding/default/style/css/assets/images/webcam-switch-presenter.png b/bigbluebutton-client/branding/default/style/css/assets/images/webcam-switch-presenter.png new file mode 100755 index 0000000000000000000000000000000000000000..e7cfff5e97381b5a88d0b96b35508e52e850960e Binary files /dev/null and b/bigbluebutton-client/branding/default/style/css/assets/images/webcam-switch-presenter.png differ diff --git a/bigbluebutton-client/branding/default/style/css/assets/images/webcam-unmuted.png b/bigbluebutton-client/branding/default/style/css/assets/images/webcam-unmuted.png new file mode 100755 index 0000000000000000000000000000000000000000..32a685732f0073772bb782662fe37e30a83d5220 Binary files /dev/null and b/bigbluebutton-client/branding/default/style/css/assets/images/webcam-unmuted.png differ diff --git a/bigbluebutton-client/src/branding/css/assets/swf/Blue.swf b/bigbluebutton-client/branding/default/style/css/assets/swf/Blue.swf similarity index 100% rename from bigbluebutton-client/src/branding/css/assets/swf/Blue.swf rename to bigbluebutton-client/branding/default/style/css/assets/swf/Blue.swf diff --git a/bigbluebutton-client/src/branding/css/assets/swf/BrushedMetal.swf b/bigbluebutton-client/branding/default/style/css/assets/swf/BrushedMetal.swf similarity index 100% rename from bigbluebutton-client/src/branding/css/assets/swf/BrushedMetal.swf rename to bigbluebutton-client/branding/default/style/css/assets/swf/BrushedMetal.swf diff --git a/bigbluebutton-client/src/branding/css/assets/swf/WindowsAssets.swf b/bigbluebutton-client/branding/default/style/css/assets/swf/WindowsAssets.swf similarity index 100% rename from bigbluebutton-client/src/branding/css/assets/swf/WindowsAssets.swf rename to bigbluebutton-client/branding/default/style/css/assets/swf/WindowsAssets.swf diff --git a/bigbluebutton-client/build.xml b/bigbluebutton-client/build.xml index 2491cdb00df46798fba1472a31e734bb015f8bdf..c24eb067206a4cf6043a75b8100ba12d1d61d2b7 100755 --- a/bigbluebutton-client/build.xml +++ b/bigbluebutton-client/build.xml @@ -3,11 +3,14 @@ <project name="BigBlueButton Client" basedir="." default="clean-build-all" > <property environment="env" /> <property name="STATIC_RSL" value="true" /> + <property name="DEBUG" value="true" /> <property name="BUILD_ENV" value="DEV" /> <property name="FLEX_HOME" value="${env.FLEX_HOME}" /> <property name="LOCALE_DIR" value="${FLEX_HOME}/frameworks/locale"/> <property name="BASE_DIR" value="${basedir}" /> - <property name="RESOURCES_DIR" value="${BASE_DIR}/resources" /> + <property name="themeFile" value="BBBDefault.css"/> + + <property name="RESOURCES_DIR" value="${BASE_DIR}/resources" /> <property name="PROD_RESOURCES_DIR" value="${RESOURCES_DIR}/prod" /> <property name="SRC_DIR" value="${BASE_DIR}/src" /> @@ -15,21 +18,20 @@ <taskdef resource="flexTasks.tasks" classpath="${FLEX_HOME}/ant/lib/flexTasks.jar" /> <!-- Declare module names here --> - <property name="BBB_MAIN_TEST" value="BigBlueButtonTest" /> <property name="BBB_MAIN" value="BigBlueButton" /> + <property name="BROADCAST" value="BroadcastModule" /> <property name="CHAT" value="ChatModule" /> <property name="VIEWERS" value="ViewersModule" /> <property name="LISTENERS" value="ListenersModule" /> <property name="PRESENT" value="PresentModule" /> <property name="DESKSHARE" value="DeskShareModule" /> <property name="DESKSHARE_SA" value="DeskshareStandalone" /> + <property name="CAM_PREVIEW_SA" value="WebcamPreviewStandalone" /> + <property name="CAM_VIEW_SA" value="WebcamViewStandalone" /> <property name="PHONE" value="PhoneModule" /> + <property name="NOTES" value="NotesModule" /> <property name="VIDEO" value="VideoconfModule" /> <property name="WHITEBOARD" value="WhiteboardModule" /> - <property name="DYN_INFO" value="DynamicInfoModule" /> - <property name="BREAKOUT" value="BreakoutModule" /> - <property name="CLASSROOM_AUDIO" value="ClassroomAudioModule" /> - <property name="SETTINGS" value="SettingsModule" /> <property name="VIDEO_DOCK" value="VideodockModule" /> <property name="LAYOUT" value="LayoutModule" /> @@ -69,9 +71,9 @@ <target name="branding" depends="init-ant-contrib"> <sequential> - <mxmlc file="${SRC_DIR}/branding/css/${themeFile}" + <mxmlc file="${BASE_DIR}/branding/default/style/css/${themeFile}" output="${OUTPUT_DIR}/branding/css/${themeFile}.swf" - debug="false" + debug="${DEBUG}" mxml.compatibility-version="3.0.0" swf-version="13" optimize="true"> @@ -162,14 +164,18 @@ <fileset dir="${BASE_DIR}/src/org/bigbluebutton/common/assets/images/" /> </copy> </target> - - <target name="build-breakout" description="Compile Breakout Module" > - <build-module src="${SRC_DIR}" target="${BREAKOUT}" /> - </target> - + + <target name="build-broadcast" description="Compile Broadcast Module" > + <build-module src="${SRC_DIR}" target="${BROADCAST}" /> + </target> + <target name="build-chat" description="Compile Chat Module"> <build-module src="${SRC_DIR}" target="${CHAT}" /> </target> + + <target name="build-notes" description="Compile Notes Module"> + <build-module src="${SRC_DIR}" target="${NOTES}" /> + </target> <target name="build-viewers" description="Compile Viewers Module"> <build-module src="${SRC_DIR}" target="${VIEWERS}" /> @@ -183,16 +189,26 @@ <build-module src="${SRC_DIR}" target="${PRESENT}" /> </target> - <target name="build-deskshare-standalone" depends="build-deskshare-no-linker" description="Compile Deskshare Standalone Module"> + <target name="build-deskshare-standalone" depends="build-deskshare-no-linker" description="Compile Deskshare Standalone Application"> <echo message="Compiling deskshare standalone without optimization." /> <build-module-no-link src="${SRC_DIR}" target="${DESKSHARE_SA}" /> </target> - - <target name="build-deskshare-no-linker" description="Compile Deskshare Module without the linker"> - <echo message="Compiling deskshare without optimization." /> - <build-module-no-link src="${SRC_DIR}" target="${DESKSHARE}" /> - </target> - + + <target name="build-deskshare-no-linker" description="Compile Deskshare Module without the linker"> + <echo message="Compiling deskshare without optimization." /> + <build-module-no-link src="${SRC_DIR}" target="${DESKSHARE}" /> + </target> + + <target name="build-webcam-preview-standalone" description="Compile Webcam Preview Standalone Application"> + <echo message="Compiling webcam preview standalone without optimization." /> + <build-module-no-link src="${SRC_DIR}" target="${CAM_PREVIEW_SA}" /> + </target> + + <target name="build-webcam-view-standalone" description="Compile Webcam View Standalone Application"> + <echo message="Compiling webcam view standalone without optimization." /> + <build-module-no-link src="${SRC_DIR}" target="${CAM_VIEW_SA}" /> + </target> + <target name="build-deskshare" description="Compile Deskshare Module"> <build-module src="${SRC_DIR}" target="${DESKSHARE}" /> <echo message="Copying deskshare applet for Deskshare Module" /> @@ -207,14 +223,6 @@ <fileset dir="${BASE_DIR}/src/org/bigbluebutton/modules/phone/views/assets/images/" /> </copy> </target> - - <target name="build-classroom-audio" description="Compile Classroom Audio Module"> - <build-module src="${SRC_DIR}" target="${CLASSROOM_AUDIO}" /> - </target> - - <target name="build-settings" description="Compile Settings Module"> - <build-module src="${SRC_DIR}" target="${SETTINGS}" /> - </target> <target name="build-video" description="Compile Video Module"> <build-module src="${SRC_DIR}" target="${VIDEO}" /> @@ -228,24 +236,20 @@ <build-module src="${SRC_DIR}" target="${WHITEBOARD}" /> </target> - <target name="build-dyn" description="Compile Dynamic Info Module"> - <build-module src="${SRC_DIR}" target="${DYN_INFO}" /> - </target> - <target name="build-layout" description="Compile Layout Module"> <build-module src="${SRC_DIR}" target="${LAYOUT}" /> </target> <!-- just a grouping of modules to compile --> <target name="build-main-chat-viewers-listeners-present" - depends="build-bbb-main, build-bbb-main-test, build-chat, build-viewers, build-listeners, build-present, build-breakout, build-layout" - description="Compile main, chat, viewers, listeners, present, breakout modules"> + depends="build-bbb-main, build-chat, build-viewers, build-listeners, build-present, build-layout, build-broadcast" + description="Compile main, chat, viewers, listeners, present modules"> </target> <!-- just a grouping of modules to compile --> <target name="build-deskshare-phone-video-whiteboard-dyn" - depends="build-deskshare, build-phone, build-video, build-videodock, build-whiteboard, build-dyn, build-classroom-audio, build-settings" - description="Compile deskshare, phone, video, whiteboard, dynamic info modules"> + depends="build-deskshare, build-phone, build-video, build-videodock, build-whiteboard, build-notes" + description="Compile deskshare, phone, video, whiteboard modules"> </target> <macrodef name="build-main"> @@ -254,7 +258,7 @@ <attribute name="app" default="."/> <attribute name="src" default="${SRC_DIR}" description="Path to the module to compile" /> <sequential> - <mxmlc file="@{src}/@{target}.mxml" output="${OUTPUT_DIR}/@{target}.swf" debug="false" mxml.compatibility-version="3.0.0" swf-version="13" optimize="true" link-report="linker-report.xml"> + <mxmlc file="@{src}/@{target}.mxml" output="${OUTPUT_DIR}/@{target}.swf" debug="${DEBUG}" mxml.compatibility-version="3.0.0" swf-version="13" optimize="true" link-report="linker-report.xml"> <target-player>11</target-player> <load-config filename="@{flex}/frameworks/flex-config.xml" /> <source-path path-element="@{flex}/frameworks" /> @@ -293,7 +297,7 @@ <attribute name="app" default="."/> <attribute name="src" default="${SRC_DIR}" description="Path to the module to compile" /> <sequential> - <mxmlc file="@{src}/@{target}.mxml" output="${OUTPUT_DIR}/@{target}.swf" debug="false" mxml.compatibility-version="3.0.0" swf-version="13"> + <mxmlc file="@{src}/@{target}.mxml" output="${OUTPUT_DIR}/@{target}.swf" debug="${DEBUG}" mxml.compatibility-version="3.0.0" swf-version="13"> <target-player>11</target-player> <load-config filename="@{flex}/frameworks/flex-config.xml" /> <source-path path-element="@{flex}/frameworks" /> @@ -320,7 +324,7 @@ <attribute name="app" default="."/> <attribute name="src" default="${SRC_DIR}" description="Path to the module to compile" /> <sequential> - <mxmlc file="@{src}/@{target}.mxml" output="${OUTPUT_DIR}/@{target}.swf" debug="false" mxml.compatibility-version="3.0.0" swf-version="13" optimize="true" load-externs="linker-report.xml"> + <mxmlc file="@{src}/@{target}.mxml" output="${OUTPUT_DIR}/@{target}.swf" debug="${DEBUG}" mxml.compatibility-version="3.0.0" swf-version="13" optimize="true" load-externs="linker-report.xml"> <target-player>11</target-player> <load-config filename="@{flex}/frameworks/flex-config.xml" /> <source-path path-element="@{flex}/frameworks" /> @@ -378,7 +382,8 @@ <copy file="${PROD_RESOURCES_DIR}/BigBlueButtonTest.html" todir="${OUTPUT_DIR}" overwrite="true"/> <copy file="${PROD_RESOURCES_DIR}/BigBlueButton.html" todir="${OUTPUT_DIR}" overwrite="true"/> <copy file="${PROD_RESOURCES_DIR}/DeskshareStandalone.html" todir="${OUTPUT_DIR}" overwrite="true"/> - <copy file="${PROD_RESOURCES_DIR}/bbb.gif" todir="${OUTPUT_DIR}" overwrite="true"/> + <copy file="${PROD_RESOURCES_DIR}/bbb.gif" todir="${OUTPUT_DIR}" overwrite="true"/> + <copy file="${PROD_RESOURCES_DIR}/avatar.png" todir="${OUTPUT_DIR}" overwrite="true"/> <copy file="${PROD_RESOURCES_DIR}/locales.xml" todir="${OUTPUT_DIR}/conf" overwrite="true"/> <copy file="${PROD_RESOURCES_DIR}/expressInstall.swf" todir="${OUTPUT_DIR}" overwrite="true"/> <copy file="${PROD_RESOURCES_DIR}/example-info-data.xml" todir="${OUTPUT_DIR}/conf" overwrite="true"/> @@ -455,13 +460,16 @@ <!-- NOTE: compile-deskshare-standalone MUST come first before compile-bbb as we need the deskshare-standalone to be compiled withouth being optimized by using the linker --> - <target name="clean-build-bbb" depends="clean, init-ant-contrib, generate-html-wrapper, compile-deskshare-standalone, compile-bbb" + <target name="clean-build-bbb" depends="clean, init-ant-contrib, generate-html-wrapper, compile-deskshare-standalone, + build-webcam-preview-standalone, build-webcam-view-standalone, compile-bbb" description="Build BBB client skipping compiling of locales"/> - <target name="clean-build-all" depends="clean, init-ant-contrib, generate-html-wrapper, compile-deskshare-standalone, compile-bbb" + <target name="clean-build-all" depends="clean, init-ant-contrib, generate-html-wrapper, compile-deskshare-standalone, + build-webcam-preview-standalone, build-webcam-view-standalone, compile-bbb, branding" description="Build BBB client including locales"/> - <target name="modules" depends="init-ant-contrib, generate-html-wrapper, compile-deskshare-standalone, compile-bbb" + <target name="modules" depends="init-ant-contrib, generate-html-wrapper, compile-deskshare-standalone, + build-webcam-preview-standalone, build-webcam-view-standalone, compile-bbb" description="Build BBB client without locales"/> <target name="cleanandmake" depends="clean-build-all" description="Build BBB client including locales"/> - <target name="build-custom" depends="init-ant-contrib, build-classroom-audio" description="Build a custom defined module only, to save time as each build takes several minutes" /> + <target name="build-custom" depends="init-ant-contrib" description="Build a custom defined module only, to save time as each build takes several minutes" /> </project> diff --git a/bigbluebutton-client/locale/en_US/bbbResources.properties b/bigbluebutton-client/locale/en_US/bbbResources.properties index 67840f912d0c5a136ec0682b8090d8fe21ab9744..851c982dffd006e55e933c9943d2bc009cc28741 100755 --- a/bigbluebutton-client/locale/en_US/bbbResources.properties +++ b/bigbluebutton-client/locale/en_US/bbbResources.properties @@ -173,6 +173,10 @@ bbb.logout.rejected = The connection to the server has been rejected bbb.logout.invalidapp = The red5 app does not exist bbb.logout.unknown = Your client has lost connection with the server bbb.logout.usercommand = You have logged out of the conference +bbb.notes.title = Notes +bbb.notes.cmpColorPicker.toolTip = Text Color +bbb.notes.saveBtn = Save +bbb.notes.saveBtn.toolTip = Save Note bbb.settings.deskshare.instructions = Click Allow on the prompt that pops up to check that desktop sharing is working properly for you bbb.settings.deskshare.start = Check Desktop Sharing bbb.settings.voice.volume = Microphone Activity @@ -217,6 +221,8 @@ bbb.accessibility.chat.plustab.fontsize = Font size bbb.accessibility.chat.chatView.publicTabNew = NEW MESSAGE IN PUBLIC CHAT bbb.accessibility.chat.chatView.privateTabNew = NEW MESSAGE IN PRIVATE CHAT WITH {0} +bbb.accessibility.notes.notesview.input = Notes input + bbb.shortcuthelp.title = Shortcut glossary bbb.shortcuthelp.dropdown.all = All shortcuts bbb.shortcuthelp.dropdown.general = General shortcuts diff --git a/bigbluebutton-client/resources/config.xml.template b/bigbluebutton-client/resources/config.xml.template index d5548397b7a6fd3c6352e38a1d490b80ce79cad7..895e2428f7c642f5b6ce5fbf093abbc4d29d9050 100755 --- a/bigbluebutton-client/resources/config.xml.template +++ b/bigbluebutton-client/resources/config.xml.template @@ -6,7 +6,7 @@ <porttest host="HOST" application="video" timeout="10000"/> <application uri="rtmp://HOST/bigbluebutton" host="http://HOST/bigbluebutton/api/enter" /> <language userSelectionEnabled="true" /> - <skinning enabled="false" url="branding/css/theme.css.swf" /> + <skinning enabled="true" url="http://HOST/client/branding/css/BBBDefault.css.swf" /> <layout showLogButton="false" showVideoLayout="false" showResetLayout="true" defaultLayout="Default" showToolbar="true" showFooter="true" showHelpButton="true" showLogoutWindow="true"/> @@ -25,13 +25,11 @@ uri="rtmp://HOST/bigbluebutton" host="http://HOST/bigbluebutton/api/enter" allowKickUser="false" - windowVisible="true" /> <module name="ListenersModule" url="http://HOST/client/ListenersModule.swf?v=VERSION" uri="rtmp://HOST/bigbluebutton" recordingHost="http://HOST" - windowVisible="true" position="bottom-left" /> @@ -74,7 +72,8 @@ filterDivisor="4" enableH264 = "true" h264Level = "2.1" - h264Profile = "main" + h264Profile = "main" + displayAvatar = "false" /> <module name="WhiteboardModule" url="http://HOST/client/WhiteboardModule.swf?v=VERSION" @@ -94,6 +93,7 @@ uri="rtmp://HOST/bigbluebutton" dependsOn="VideoconfModule, ViewersModule" autoDock="true" + showControls="true" maximizeWindow="false" position="bottom-right" width="172" @@ -107,24 +107,22 @@ layoutConfig="http://HOST/client/conf/layout.xml" enableEdit="true" /> - - <!-- new module in development: - <module name="DynamicInfoModule" url="http://HOST/client/DynamicInfoModule.swf?v=VERSION" - uri="rtmp://HOST/bigbluebutton" - host="http://HOST" - infoURL="http://HOST/client/conf/example-info-data.xml?user={userID}&role={role}&meetingID={meetingID}" - /> - --> - - <!--<module name="ExampleChatModule" url="http://HOST/client/ExampleChatModule.swf?v=56" - uri="rtmp://HOST/bigbluebutton" - host="http://HOST" - />--> - - <!--<module name="SharedNotesModule" url="http://HOST/client/SharedNotesModule.swf?v=VERSION" - uri="http://192.168.0.225/bigbluebutton" - dependsOn="ViewersModule" - />--> +<!-- + <module name="NotesModule" url="http://HOST/client/NotesModule.swf?v=VERSION" + saveURL="http://HOST" + position="top-left" + /> + + <module name="BroadcastModule" url="http://HOST/client/BroadcastModule.swf?v=VERSION" + uri="rtmp://HOST/bigbluebutton" + streamsUri="http://HOST/streams.xml" + position="top-left" + showStreams="true" + autoPlay="false" + dependsOn="ViewersModule" + /> +--> + </modules> </config> diff --git a/bigbluebutton-client/resources/prod/3rd-party.html b/bigbluebutton-client/resources/prod/3rd-party.html index 2e10d69223620f52cfd9463ecc393be96b52bba9..aff8ceb2485e25a4522c9f1e438d85a3809f81ee 100755 --- a/bigbluebutton-client/resources/prod/3rd-party.html +++ b/bigbluebutton-client/resources/prod/3rd-party.html @@ -11,6 +11,8 @@ <script type="text/javascript" src="http://192.168.0.249/client/swfobject/swfobject.js"></script> <script type="text/javascript"> swfobject.registerObject("BigBlueButton", "11", "expressInstall.swf"); + swfobject.registerObject("WebcamPreviewStandalone", "11", "expressInstall.swf"); + swfobject.registerObject("WebcamViewStandalone", "11", "expressInstall.swf"); </script> <script src="http://192.168.0.249/client/lib/jquery-1.5.1.min.js" language="javascript"></script> <script src="http://192.168.0.249/client/lib/bigbluebutton.js" language="javascript"></script> @@ -18,12 +20,21 @@ <script src="http://192.168.0.249/client/lib/bbb_blinker.js" language="javascript"></script> <script src="http://192.168.0.249/client/lib/bbb_deskshare.js" language="javascript"></script> <script type="text/javascript" src="http://192.168.0.249/client/lib/bbb_api_bridge.js"></script> + <script type="text/javascript" src="http://192.168.0.249/client/lib/bbb_api_cam_preview.js"></script> + <script type="text/javascript" src="http://192.168.0.249/client/lib/bbb_api_cam_view.js"></script> <script type="text/javascript" src="http://192.168.0.235/3rd-party.js"></script> <script> window.displayBBBClient = function() { var bbbc = document.getElementById("flashclient"); + var wcpc = document.getElementById("webcampreviewclient"); + wcpc.style.display = "none"; bbbc.style.display = "block"; } + window.displayWCClient = function() { + console.log("Displaying webcam preview client"); + var wcpc = document.getElementById("webcampreview"); + wcpc.style.display = "block"; + } window.onload = function() { registerListeners(); } @@ -31,24 +42,77 @@ </head> <body> <div id="controls"> - <button type="button" onclick="displayBBBClient()">Show Client</button> - <button type="button" onclick="BBB.shareVideoCamera()">Webcam</button> + <button type="button" onclick="registerListeners()">Listen for Events</button> + <button type="button" onclick="displayBBBClient()">Show BBB Client</button> + <button type="button" onclick="displayWCClient()">Show WC Client</button> + <button type="button" onclick="BBB.shareVideoCamera()">Share Webcam</button> + <button type="button" onclick="BBB.stopSharingCamera()">Stop Webcam</button> + <button type="button" onclick="BBB.switchPresenter('x8hxeozsqbk1')">Switch Presenter</button> <button type="button" onclick="joinVoiceConference2()">Join Voice</button> + <button type="button" onclick="leaveVoiceConference2()">Leave Voice</button> + <button type="button" onclick="getMyUserID()">Get My UserID</button> + <button type="button" onclick="getMeetingID()">Get MeetingID</button> <button type="button" onclick="getMyRoleAsynch()">Get My Role Asynch</button> <button type="button" onclick="getMyRoleSynch()">Get My Role Synch</button> <button type="button" onclick="muteMe()">Mute Me</button> <button type="button" onclick="unmuteMe()">Unmute Me</button> <button type="button" onclick="muteAll()">Mute All</button> <button type="button" onclick="unmuteAll()">Unmute All</button> - <button type="button" onclick="switchLayout('Video Chat')">Switch Video Layout</button> - <button type="button" onclick="switchLayout('Lecture')">Switch Present Layout</button> + <button type="button" onclick="switchLayout('S2SVideoChat')">Switch Video Layout</button> + <button type="button" onclick="switchLayout('S2SPresentation')">Switch Present Layout</button> <button type="button" onclick="lockLayout(true)">Lock Layout</button> <button type="button" onclick="lockLayout(false)">Unlock Layout</button> <button type="button" onclick="sendPublicChat()">Send Public Chat</button> <button type="button" onclick="sendPrivateChat()">Send Private Chat</button> + <button type="button" onclick="amIPresenterSync()">Am I Presenter Sync</button> + <button type="button" onclick="amIPresenterAsync()">Am I Presenter Async</button> </div> - <div id="flashclient" style="display: none"> - <object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="100%" height="90%" id="BigBlueButton" name="BigBlueButton" align="middle"> + <div id="webcampreview" style="background-color:#FFD700;height:240px;width:320px;float:left;"> + <object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540001" width="320" height="240" id="WebcamPreviewStandalone" name="WebcamPreviewStandalone" align="middle"> + <param name="movie" value="http://192.168.0.249/client/WebcamPreviewStandalone.swf?v=VERSION" /> + <param name="quality" value="high" /> + <param name="allowfullscreen" value="true" /> + <param name="bgcolor" value="#869ca7" /> + <param name="wmode" value="window" /> + <param name="allowScriptAccess" value="always" /> + <!--[if !IE]>--> + <object type="application/x-shockwave-flash" data="http://192.168.0.249/client/WebcamPreviewStandalone.swf?v=VERSION" width="100%" height="90%" align="middle"> + <param name="quality" value="high" /> + <param name="bgcolor" value="#869ca7" /> + <param name="allowScriptAccess" value="always" /> + <!--<![endif]--> + <a href="http://www.adobe.com/go/getflashplayer"> + <img src="http://www.adobe.com/images/shared/download_buttons/get_flash_player.gif" alt="Get Adobe Flash player" /> + </a> + <!--[if !IE]>--> + </object> + <!--<![endif]--> + </object> + </div> + <div id="webcamview" style="background-color:#FFD700;height:240px;width:320px;float:left;"> + <object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540002" width="320" height="240" id="WebcamViewStandalone" name="WebcamViewStandalone" align="middle"> + <param name="movie" value="http://192.168.0.249/client/WebcamViewStandalone.swf?v=VERSION" /> + <param name="quality" value="high" /> + <param name="allowfullscreen" value="true" /> + <param name="bgcolor" value="#869ca7" /> + <param name="wmode" value="window" /> + <param name="allowScriptAccess" value="always" /> + <!--[if !IE]>--> + <object type="application/x-shockwave-flash" data="http://192.168.0.249/client/WebcamViewStandalone.swf?v=VERSION" width="100%" height="90%" align="middle"> + <param name="quality" value="high" /> + <param name="bgcolor" value="#869ca7" /> + <param name="allowScriptAccess" value="always" /> + <!--<![endif]--> + <a href="http://www.adobe.com/go/getflashplayer"> + <img src="http://www.adobe.com/images/shared/download_buttons/get_flash_player.gif" alt="Get Adobe Flash player" /> + </a> + <!--[if !IE]>--> + </object> + <!--<![endif]--> + </object> + </div> + <div id="flashclient" style="background-color:#EEEEEE;height:900px;width:1200px;float:left;"> + <object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="50%" height="50%" id="BigBlueButton" name="BigBlueButton" align="middle"> <param name="movie" value="http://192.168.0.249/client/BigBlueButton.swf?v=VERSION" /> <param name="quality" value="high" /> <param name="allowfullscreen" value="true" /> @@ -68,7 +132,7 @@ </object> <!--<![endif]--> </object> - </div> + </div> <div id="update-display"/> <div id="notifications" aria-live="polite" role="region" aria-label="Chat Notifications"></div> </body> diff --git a/bigbluebutton-client/resources/prod/avatar.png b/bigbluebutton-client/resources/prod/avatar.png new file mode 100644 index 0000000000000000000000000000000000000000..2e9f8151f4083aece663009ae40d9271104dbb41 Binary files /dev/null and b/bigbluebutton-client/resources/prod/avatar.png differ diff --git a/bigbluebutton-client/resources/prod/layout.xml b/bigbluebutton-client/resources/prod/layout.xml index 638e1f12dd473c162468ca9f8b2a6894fe1ca594..8ea77513a2d31d31f11eb6de6c80a6e7d36f1fa3 100755 --- a/bigbluebutton-client/resources/prod/layout.xml +++ b/bigbluebutton-client/resources/prod/layout.xml @@ -1,6 +1,8 @@ <?xml version="1.0"?> <layouts> <layout name="Video Chat"> + <window name="NotesWindow" hidden="true" width="0.7" height="1" x="0" y="0" draggable="false" resizable="false"/> + <window name="BroadcastWindow" hidden="true" draggable="false" resizable="false"/> <window name="ViewersWindow" minimized="true" order="3" hidden="true" /> <window name="VideoDock" width="1" height="1" x="0" y="0" order="0"/> <window name="ChatWindow" width="0.303125" height="0.9955703211517165" x="0.3229166666666667" y="0.9656699889258029" order="4" hidden="true" /> @@ -8,6 +10,8 @@ <window name="ListenersWindow" minimized="true" hidden="true" order="2"/> </layout> <layout name="Default" default="true"> + <window name="NotesWindow" hidden="true" width="0.7" height="1" x="0" y="0" draggable="false" resizable="false"/> + <window name="BroadcastWindow" hidden="true" draggable="false" resizable="false"/> <window name="ViewersWindow" width="0.1772793053545586" height="0.33643617021276595" x="0" y="0" /> <window name="ListenersWindow" width="0.1772793053545586" height="0.33643617021276595" x="0" y="0.34308510638297873" /> <window name="PresentationWindow" width="0.5137481910274964" height="0.9946808510638298" x="0.18017366136034732" y="0" /> @@ -15,6 +19,8 @@ <window name="ChatWindow" width="0.3031837916063676" height="0.9960106382978723" x="0.6968162083936325" y="0" /> </layout> <layout name="Meeting"> + <window name="NotesWindow" hidden="true" width="0.7" height="1" x="0" y="0" draggable="false" resizable="false"/> + <window name="BroadcastWindow" hidden="true" draggable="false" resizable="false"/> <window name="VideoDock" width="0.6570188133140377" height="0.9960106382978723" x="0" y="0" /> <window name="ChatWindow" width="0.3393632416787265" height="0.5305851063829787" x="0.658465991316932" y="0" /> <window name="ListenersWindow" hidden="true" /> @@ -22,6 +28,8 @@ <window name="PresentationWindow" width="0.34008683068017365" height="0.4601063829787234" x="0.658465991316932" y="0.535904255319149" /> </layout> <layout name="Webinar"> + <window name="NotesWindow" hidden="true" width="0.7" height="1" x="0" y="0" draggable="false" resizable="false"/> + <window name="BroadcastWindow" hidden="true" draggable="false" resizable="false"/> <window name="ViewersWindow" minimized="true" /> <window name="VideoDock" width="0.2923611111111111" height="0.4640957446808511" x="0.7048611111111112" y="0.535904255319149" /> <window name="ListenersWindow" minimized="true" /> @@ -29,6 +37,8 @@ <window name="ChatWindow" width="0.2923611111111111" height="0.5305851063829787" x="0.7048611111111112" y="0" /> </layout> <layout name="Lecture assistant"> + <window name="NotesWindow" hidden="true" width="0.7" height="1" x="0" y="0" draggable="false" resizable="false"/> + <window name="BroadcastWindow" hidden="true" draggable="false" resizable="false"/> <window name="ChatWindow" width="0.4597222222222222" height="0.9958677685950413" x="0.2263888888888889" y="0" /> <window name="ListenersWindow" width="0.2222222222222222" height="0.4765840220385675" x="0" y="0.5179063360881543" /> <window name="ViewersWindow" width="0.22152777777777777" height="0.5055096418732782" x="0" y="0" /> @@ -36,6 +46,8 @@ <window name="VideoDock" width="0.30972222222222223" height="0.4256198347107438" x="0.6902777777777778" y="0.568870523415978" /> </layout> <layout name="Lecture"> + <window name="NotesWindow" hidden="true" width="0.7" height="1" x="0" y="0" draggable="false" resizable="false"/> + <window name="BroadcastWindow" hidden="true" draggable="false" resizable="false"/> <window name="ViewersWindow" hidden="true" /> <window name="VideoDock" width="0.2923611111111111" height="0.4640957446808511" x="0.7048611111111112" y="0.535904255319149" /> <window name="ListenersWindow" hidden="true" /> @@ -43,6 +55,8 @@ <window name="ChatWindow" width="0.2923611111111111" height="0.5305851063829787" x="0.7048611111111112" y="0" /> </layout> <layout name="Lecture" role="presenter"> + <window name="NotesWindow" hidden="true" width="0.7" height="1" x="0" y="0" draggable="false" resizable="false"/> + <window name="BroadcastWindow" hidden="true" draggable="false" resizable="false"/> <window name="ChatWindow" hidden="true" /> <window name="ListenersWindow" hidden="true" /> <window name="ViewersWindow" hidden="true" /> @@ -50,10 +64,48 @@ <window name="VideoDock" hidden="true" /> </layout> <layout name="Lecture" role="moderator"> + <window name="NotesWindow" hidden="true" width="0.7" height="1" x="0" y="0" draggable="false" resizable="false"/> + <window name="BroadcastWindow" hidden="true" draggable="false" resizable="false"/> <window name="ChatWindow" width="0.4597222222222222" height="0.9958677685950413" x="0.2263888888888889" y="0" /> <window name="ListenersWindow" width="0.2222222222222222" height="0.4765840220385675" x="0" y="0.5179063360881543" /> <window name="ViewersWindow" width="0.22152777777777777" height="0.5055096418732782" x="0" y="0" /> <window name="PresentationWindow" width="0.3104166666666667" height="0.5537190082644629" x="0.6895833333333333" y="0" /> <window name="VideoDock" width="0.30972222222222223" height="0.4256198347107438" x="0.6902777777777778" y="0.568870523415978" /> </layout> + <layout name="S2SPresentation"> + <window name="NotesWindow" hidden="true" width="0.7" height="1" x="0" y="0" draggable="false" resizable="false"/> + <window name="BroadcastWindow" hidden="true" draggable="false" resizable="false"/> + <window name="ChatWindow" hidden="true" draggable="false" resizable="false"/> + <window name="ListenersWindow" hidden="true" draggable="false" resizable="false"/> + <window name="ViewersWindow" hidden="true" draggable="false" resizable="false"/> + <window name="PresentationWindow" width="0.8" height="1" x="0" y="0" draggable="false" resizable="false"/> + <window name="VideoDock" hidden="true" draggable="false" resizable="false"/> + </layout> + <layout name="S2SVideoChat"> + <window name="NotesWindow" hidden="true" width="0.7" height="1" x="0" y="0" draggable="false" resizable="false"/> + <window name="BroadcastWindow" hidden="true" draggable="false" resizable="false"/> + <window name="ViewersWindow" hidden="true" draggable="false" resizable="false"/> + <window name="VideoDock" width="1" height="1" x="0" y="0" draggable="false" resizable="false"/> + <window name="ChatWindow" hidden="true" draggable="false" resizable="false"/> + <window name="PresentationWindow" hidden="true" draggable="false" resizable="false"/> + <window name="ListenersWindow" hidden="true" draggable="false" resizable="false"/> + </layout> + <layout name="Notes"> + <window name="NotesWindow" hidden="false" width="0.7" height="0.4" x="0" y="0.6" draggable="false" resizable="false"/> + <window name="BroadcastWindow" hidden="false" width="0.7" height="0.6" x="0" y="0" draggable="false" resizable="false"/> + <window name="ViewersWindow" hidden="true" draggable="false" resizable="false"/> + <window name="VideoDock" hidden="true" draggable="false" resizable="false"/> + <window name="ChatWindow" width="0.3" height="1" x="0.7" y="0" draggable="false" resizable="false"/> + <window name="PresentationWindow" hidden="true" draggable="false" resizable="false"/> + <window name="ListenersWindow" hidden="true" draggable="false" resizable="false"/> + </layout> + <layout name="Broadcast"> + <window name="NotesWindow" hidden="false" width="0.7" height="1" x="0" y="0" draggable="false" resizable="false"/> + <window name="BroadcastWindow" hidden="false" width="0.7" height="0.9" x="0" y="0" draggable="false" resizable="false"/> + <window name="ViewersWindow" hidden="true" draggable="false" resizable="false"/> + <window name="VideoDock" hidden="true" draggable="false" resizable="false"/> + <window name="ChatWindow" width="0.3" height="1" x="0.7" y="0" draggable="false" resizable="false"/> + <window name="PresentationWindow" hidden="true" draggable="false" resizable="false"/> + <window name="ListenersWindow" hidden="true" draggable="false" resizable="false"/> + </layout> </layouts> diff --git a/bigbluebutton-client/resources/prod/lib/3rd-party.js b/bigbluebutton-client/resources/prod/lib/3rd-party.js index 71f69259841a9911dfd4e2d073581b2766fc5354..0c874d4c0dfaad882cf61607f56aff6fa090d0d2 100755 --- a/bigbluebutton-client/resources/prod/lib/3rd-party.js +++ b/bigbluebutton-client/resources/prod/lib/3rd-party.js @@ -1,26 +1,67 @@ var registerListeners = function() { + console.log("Listening for events."); BBB.listen("UserLeftEvent", function(bbbEvent) { console.log("User [" + bbbEvent.userID + "] has left."); }); BBB.listen("UserJoinedEvent", function(bbbEvent) { console.log("User [" + bbbEvent.userID + ", [" + bbbEvent.userName + "] has joined."); }); - BBB.listen("UserJoinedVoiceEvent", function(bbbEvent) { - console.log("Received userHasJoinedVoiceConference event"); - }); BBB.listen("NewPublicChatEvent", function(bbbEvent) { console.log("Received NewPublicChatEvent [" + bbbEvent.message + "]"); }); BBB.listen("NewPrivateChatEvent", function(bbbEvent) { console.log("Received NewPrivateChatEvent event"); }); + BBB.listen("UserJoinedVoiceEvent", function(bbbEvent) { + console.log("User [" + bbbEvent.userID + "] had joined the voice conference."); + }); + BBB.listen("UserLeftVoiceEvent", function(bbbEvent) { + console.log("User [" + bbbEvent.userID + "has left the voice conference."); + }); + BBB.listen("UserVoiceMutedEvent", function(bbbEvent) { + console.log("User [" + bbbEvent.userID + "] is muted [" + bbbEvent.muted + "]"); + }); + BBB.listen("UserLockedVoiceEvent", function(bbbEvent) { + console.log("User [" + bbbEvent.userID + "] is locked [" + bbbEvent.locked + "]"); + }); + BBB.listen("CamStreamSharedEvent", function(bbbEvent) { + console.log("User CamStreamSharedEvent [" + bbbEvent.uri + "," + bbbEvent.streamName + "]"); + CAM_VIEW.viewWebcamStream(bbbEvent.uri, bbbEvent.streamName); + }); + BBB.listen("BroadcastingCameraStartedEvent", function(bbbEvent) { + console.log("User BroadcastingCameraStartedEvent [" + bbbEvent.camIndex + "] [" + bbbEvent.camWidth + "]"); + CAM_PREVIEW.previewCamera(bbbEvent.camIndex, bbbEvent.camWidth, bbbEvent.camHeight, bbbEvent.camKeyFrameInterval, + bbbEvent.camModeFps, bbbEvent.camQualityBandwidth, bbbEvent.camQualityPicture); + }); + BBB.listen("BroadcastingCameraStoppedEvent", function(bbbEvent) { + console.log("User BroadcastingCameraStoppedEvent ]"); + CAM_PREVIEW.stopPreviewCamera(); + }); +} + +var leaveVoiceConference2 = function () { + BBB.leaveVoiceConference(); } var joinVoiceConference2 = function () { BBB.joinVoiceConference(); } +var amIPresenterAsync = function() { + BBB.listen("AmIPresenterQueryResponse", function(bbbEvent) { + console.log("Received AmIPresenterQueryResponse event [" + bbbEvent.amIPresenter + "]"); + }); + + BBB.amIPresenter(); +} + +var amIPresenterSync = function() { + BBB.amIPresenter(function(amIPresenter) { + console.log("Am I Presenter = " + amIPresenter); + }); +} + var getMyRoleAsynch = function() { BBB.listen("GetMyRoleResponse", function(bbbEvent) { console.log("Received GetMyRoleResponse event"); @@ -35,6 +76,18 @@ var getMyRoleSynch = function() { }); } +var getMyUserID = function() { + BBB.getMyUserID(function(userID) { + console.log("My user ID = [" + userID + "]"); + }); +} + +var getMeetingID = function() { + BBB.getMeetingID(function(meetingID) { + console.log("Meeting ID = [" + meetingID + "]"); + }); +} + var muteMe = function() { BBB.muteMe(); } diff --git a/bigbluebutton-client/resources/prod/lib/bbb_api_bridge.js b/bigbluebutton-client/resources/prod/lib/bbb_api_bridge.js index 14cd569d54fc846e09b41ef85b7ef3b41914ea9e..9b7bb3557979e39a0fed21d6aad4a7cfa71d3318 100755 --- a/bigbluebutton-client/resources/prod/lib/bbb_api_bridge.js +++ b/bigbluebutton-client/resources/prod/lib/bbb_api_bridge.js @@ -12,6 +12,33 @@ return swfobject.getObjectById("BigBlueButton"); } + BBB.switchPresenter = function(newPresenterUserID) { + var swfObj = getSwfObj(); + if (swfObj) { + console.log("Request to switch presenter to [" + newPresenterUserID + "]"); + swfObj.switchPresenterRequest(newPresenterUserID); + } + } + + /** + * Query the Flash client if user is presenter. + * Params: + * callback - function if you want a callback as response. Otherwise, you need to listen + * for the response as an event. + */ + BBB.amIPresenter = function(callback) { + var swfObj = getSwfObj(); + if (swfObj) { + if (arguments.length == 0) { + swfObj.amIPresenterRequestAsync(); + } else { + if (typeof callback === 'function') { + callback(swfObj.amIPresenterRequestSync()); + } + } + } + } + /** * Query the Flash client for the user's role. * Params: @@ -31,6 +58,32 @@ } } + /** + * Get external meetingID. + */ + BBB.getMyUserID = function(callback) { + var swfObj = getSwfObj(); + if (swfObj) { + console.log("Getting my userID"); + if (typeof callback === 'function') { + callback(swfObj.getMyUserID()); + } + } + } + + /** + * Get external meetingID. + */ + BBB.getMeetingID = function(callback) { + var swfObj = getSwfObj(); + if (swfObj) { + console.log("Getting external meetingID"); + if (typeof callback === 'function') { + callback(swfObj.getExternalMeetingID()); + } + } + } + /** * Join the voice conference. */ @@ -42,16 +95,35 @@ } } + BBB.leaveVoiceConference = function() { + var swfObj = getSwfObj(); + if (swfObj) { + console.log("Leave voice"); + swfObj.leaveVoiceRequest(); + } + } + /** * Share user's webcam. */ - BBB.shareVideoCamera = function() { + BBB.shareVideoCamera = function(publishInClient) { var swfObj = getSwfObj(); if (swfObj) { - swfObj.shareVideoCamera(); + if (typeof publishInClient === 'boolean') { + swfObj.shareVideoCamera(publishInClient); + } else { + swfObj.shareVideoCamera(); + } } } + BBB.stopSharingCamera = function() { + var swfObj = getSwfObj(); + if (swfObj) { + swfObj.stopShareCameraRequest(); + } + } + BBB.muteMe = function() { var swfObj = getSwfObj(); if (swfObj) { @@ -187,16 +259,32 @@ /************************************************ * EVENT NAME CONSTANTS + * + * See https://github.com/bigbluebutton/bigbluebutton/blob/master/bigbluebutton-client/src/org/bigbluebutton/core/EventConstants.as + * ************************************************/ - var GET_MY_ROLE_REQ = 'GetMyRoleRequest'; - var SWITCH_LAYOUT_REQ = 'SwitchLayoutRequest'; - var JOIN_VOICE_REQ = 'JoinVoiceRequest'; - var MUTE_ALL_REQ = 'MuteAllRequest'; - var MUTE_ME_REQ = 'MuteMeRequest'; - var SHARE_CAM_REQ = 'ShareCameraRequest'; - - - + var GET_MY_ROLE_RESP:String = 'GetMyRoleResponse'; + var AM_I_PRESENTER_RESP:String = 'AmIPresenterQueryResponse'; + var AM_I_SHARING_CAM_RESP:String = 'AmISharingCamQueryResponse'; + var BROADCASTING_CAM_STARTED:String = 'BroadcastingCameraStartedEvent'; + var BROADCASTING_CAM_STOPPED:String = 'BroadcastingCameraStoppedEvent'; + var I_AM_SHARING_CAM:String = 'IAmSharingCamEvent'; + var CAM_STREAM_SHARED:String = 'CamStreamSharedEvent'; + var USER_JOINED:String = 'UserJoinedEvent'; + var USER_LEFT:String = 'UserLeftEvent'; + var SWITCHED_PRESENTER:String = 'SwitchedPresenterEvent'; + var NEW_PRIVATE_CHAT:String = 'NewPrivateChatEvent'; + var NEW_PUBLIC_CHAT:String = 'NewPublicChatEvent'; + var SWITCHED_LAYOUT:String = 'SwitchedLayoutEvent'; + var REMOTE_LOCKED_LAYOUT:String = 'RemoteLockedLayoutEvent'; + var REMOTE_UNLOCKED_LAYOUT:String = 'RemoteUnlockedLayoutEvent'; + var USER_JOINED_VOICE:String = 'UserJoinedVoiceEvent'; + var USER_LEFT_VOICE:String = 'UserLeftVoiceEvent'; + var USER_MUTED_VOICE:String = 'UserVoiceMutedEvent'; + var USER_TALKING:String = 'UserTalkingEvent'; + var USER_LOCKED_VOICE:String = 'UserLockedVoiceEvent'; + var START_PRIVATE_CHAT:String = "StartPrivateChatEvent"; + window.BBB = BBB; })(this); diff --git a/bigbluebutton-client/resources/prod/lib/bbb_api_cam_preview.js b/bigbluebutton-client/resources/prod/lib/bbb_api_cam_preview.js new file mode 100755 index 0000000000000000000000000000000000000000..fbff68dfaf5ddc776d4042f95980ec5915deb3bb --- /dev/null +++ b/bigbluebutton-client/resources/prod/lib/bbb_api_cam_preview.js @@ -0,0 +1,44 @@ +(function(window, undefined) { + + var CAM_PREVIEW = {}; + + /** + * Internal function to get the BBB embed object. Seems like we have to do this + * each time and can't create a var for it. + * + * To get the object, see https://code.google.com/p/swfobject/wiki/api + */ + function getSwfObj() { + return swfobject.getObjectById("WebcamPreviewStandalone"); + } + + + /** + * Preview user's webcam. + */ + CAM_PREVIEW.previewCamera = function(camIndex, camWidth, camHeight, + camKeyFrameInterval, camModeFps, + camQualityBandwidth, camQualityPicture) { + console.log("CAM_PREVIEW::previewCamera [" + camIndex + "]"); + var swfObj = getSwfObj(); + if (swfObj) { + swfObj.startPreviewCamera(camIndex, camWidth, camHeight, camKeyFrameInterval, camModeFps, + camQualityBandwidth, camQualityPicture); + } + } + + /** + * Stop previewing user's webcam. + */ + CAM_PREVIEW.stopPreviewCamera = function() { + var swfObj = getSwfObj(); + if (swfObj) { + swfObj.stopPreviewCamera(); + } + } + + console.log("CAM_PREVIEW INITIALIZED"); + + window.CAM_PREVIEW = CAM_PREVIEW; +})(this); + diff --git a/bigbluebutton-client/resources/prod/lib/bbb_api_cam_view.js b/bigbluebutton-client/resources/prod/lib/bbb_api_cam_view.js new file mode 100755 index 0000000000000000000000000000000000000000..6d950886c40db881ce4dd35eedc9259aea1b082a --- /dev/null +++ b/bigbluebutton-client/resources/prod/lib/bbb_api_cam_view.js @@ -0,0 +1,42 @@ +(function(window, undefined) { + + var CAM_VIEW = {}; + + /** + * Internal function to get the BBB embed object. Seems like we have to do this + * each time and can't create a var for it. + * + * To get the object, see https://code.google.com/p/swfobject/wiki/api + */ + function getSwfObj() { + return swfobject.getObjectById("WebcamViewStandalone"); + } + + + /** + * View user's webcam. + */ + CAM_VIEW.viewWebcamStream = function(url, streamName) { + console.log("CAM_VIEW::viewWebcamStream [" + url + "," + streamName + "]"); + + var swfObj = getSwfObj(); + if (swfObj) { + console.log("CAM_VIEW::viewWebcamStream 2 [" + url + "," + streamName + "]"); + swfObj.startViewCameraStream(url, streamName); + } + } + + /** + * Stop viewing user's webcam. + */ + CAM_VIEW.stopViewWebcamStream = function() { + var swfObj = getSwfObj(); + if (swfObj) { + swfObj.stopViewCameraStream(); + } + } + + console.log("CAM_VIEW INITIALIZED"); + window.CAM_VIEW = CAM_VIEW; +})(this); + diff --git a/bigbluebutton-client/resources/streams.xml b/bigbluebutton-client/resources/streams.xml new file mode 100755 index 0000000000000000000000000000000000000000..40142888eac86d0d747a1680d14186fa7e9d4e4c --- /dev/null +++ b/bigbluebutton-client/resources/streams.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" ?> +<streams> + <stream url="rtmp://192.168.153.128/oflaDemo" id="hobbit_vp6" name="The Hobbit"/> + <stream url="rtmp://192.168.153.128/oflaDemo" id="startrekintodarkness_vp6" name="Star Trek"/> +</streams> diff --git a/bigbluebutton-client/src/BigBlueButton.mxml b/bigbluebutton-client/src/BigBlueButton.mxml index 256721b1be52171decd04b3984985a33ff7b8ecd..ec02dad430848f8ded23378a81e91846a843d192 100755 --- a/bigbluebutton-client/src/BigBlueButton.mxml +++ b/bigbluebutton-client/src/BigBlueButton.mxml @@ -25,12 +25,11 @@ xmlns:apimap="org.bigbluebutton.main.api.maps.*" xmlns:coreMap="org.bigbluebutton.core.controllers.maps.*" xmlns:mate="http://mate.asfusion.com/" - xmlns:m="library://melomel/2010" pageTitle="BigBlueButton" layout="absolute" preinitialize="init()" preloader="org.bigbluebutton.main.model.BigBlueButtonPreloader" applicationComplete="hotkeyCapture()"> - + <mx:Script> <![CDATA[ import com.asfusion.mate.events.Dispatcher; @@ -50,7 +49,6 @@ import org.bigbluebutton.util.i18n.ResourceUtil; private var langResources:ResourceUtil = null; //ResourceUtil.getInstance(); - public var myFoo:String = "bar"; /** * Thse two lines are workaround for this. (ralam - Nov 8, 2008) @@ -63,147 +61,8 @@ /** another workaround - for this issue: https://bugs.adobe.com/jira/browse/SDK-13121*/ private var hist:HistoryManager - private function handleChatEvent(e:Event):void { - trace('Got CHAT_EVENT'); - } - - public function setFoo(f:String):void { - myFoo = f; - } - - /****************************** - * Testing - ******************************/ - private var globalDispatcher:Dispatcher = new Dispatcher(); - // Login test - private static var isLoggedIn:Boolean = false; // flag to indicate if we successfully logged in - - // Chat test - private static var chatMessageSent:String = ""; - private static var chatTestSucceeded:Boolean = false; - - // Voice test - private static var didUserJoin:Boolean = false; - - // Presentation test - private static var didPresentationConvert:Boolean = false; - - // Video test - private static var didVideoStart:Boolean = false; - - - public function handleLoginEvent(event:BBBEvent):void { - LogUtil.debug("******************* Received loggedin event"); - isLoggedIn = true; - LogUtil.debug("******************* Received loggedin event " + isLoggedIn.toString()); - } - - public function checkLogin():String { - LogUtil.debug("******************* " + isLoggedIn.toString()); - return isLoggedIn.toString(); - } - - public function checkChat():String { - if (chatTestSucceeded) - { - chatTestSucceeded = false; - return "true"; - } else { - return chatTestSucceeded.toString(); - } - } - - public function handleReceivedPublicChatMessageEvent(event:BBBEvent):void { - if (event.message.search(chatMessageSent) != -1) { - chatTestSucceeded = true; - } - } - - // Creates a random string and sends it as a chat message. - public function sendRandomChatMessage():void { - chatMessageSent = generateRandomString(15); - globalDispatcher.dispatchEvent(new BBBEvent(BBBEvent.SEND_PUBLIC_CHAT_MESSAGE_EVENT, chatMessageSent)); - } - - /** - * Voice - */ - public function joinVoiceConference():void { - globalDispatcher.dispatchEvent(new BBBEvent(BBBEvent.JOIN_VOICE_CONFERENCE)); - } - - public function checkVoice():String { - return didUserJoin.toString(); - } - - public function handleAddedListenerEvent(event:BBBEvent):void { - // if (event.message == _attributes.username) { - didUserJoin = true; - // } - } - - /** - * Presentation - */ - - public function checkPresentation():String { - if (didPresentationConvert) { - didPresentationConvert = false; - return "true"; - } else { - return "false"; - } - } - - public function handlePresentationConversion(event:Event):void { - didPresentationConvert = true; - } - - // Obtained from: http://www.zedia.net/2008/generate-random-strings-in-as2-or-as3/ - private function generateRandomString(newLength:uint = 1, userAlphabet:String = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"):String{ - var alphabet:Array = userAlphabet.split(""); - var alphabetLength:int = alphabet.length; - var randomLetters:String = ""; - for (var i:uint = 0; i < newLength; i++){ - randomLetters += alphabet[int(Math.floor(Math.random() * alphabetLength))]; - } - return randomLetters; - } - - - /** - * Video - */ - - public function startVideoConnection():String { - globalDispatcher.dispatchEvent(new BBBEvent(BBBEvent.START_VIDEO_CONNECTION)); - return "true"; - } - - public function playVideo():String { - globalDispatcher.dispatchEvent(new BBBEvent(BBBEvent.START_VIDEO_STREAM, "640x480avatar.flv")); - return "true"; - } - - public function videoHasStarted(e:Event):void { - didVideoStart = true; - } - - public function checkVideo():String { - return didVideoStart.toString(); - } - - /** - * Desktop Sharing - */ - - public function startDeskshare():String { - globalDispatcher.dispatchEvent(new BBBEvent(BBBEvent.START_DESKSHARE)); - return "true"; - } - protected function init():void { setupAPI(); EventBroadcaster.getInstance().addEventListener("configLoadedEvent", configLoadedEventHandler); @@ -226,17 +85,13 @@ private function determineHTMLURL():String { var serverName:String = "*"; - if(ExternalInterface.available) // import flash.external.ExternalInterface; - { - try - { + if(ExternalInterface.available) { + try { var htmlURL:String = String(ExternalInterface.call("window.location.href.toString")); serverName = URLUtil.getServerName(htmlURL); -// Alert.show("HTML URL [" + htmlURL + "]"); - } - catch(s:Error) - { -// Alert.show("Cannot determine HTML URL"); + trace("HTML URL [" + htmlURL + "]"); + } catch(s:Error) { + trace("Cannot determine HTML URL"); } } @@ -263,10 +118,10 @@ } public function hotkeyCapture():void{ - LogUtil.debug("Entering hotkeyCapture"); + trace("Entering hotkeyCapture"); stage.addEventListener(KeyboardEvent.KEY_DOWN, handleKeyDown); ResourceUtil.getInstance().addEventListener(Event.CHANGE, localeChanged); // Listen for locale changing - LogUtil.debug("Leaving hotkeyCapture"); + trace("Leaving hotkeyCapture"); } public function handleKeyDown(e:KeyboardEvent) :void @@ -277,7 +132,7 @@ if (keyCombos[keyPress]) { - LogUtil.debug("Chad keypress " + keyPress); + trace("Chad keypress " + keyPress); globalDispatcher.dispatchEvent(new ShortcutEvent(keyCombos[keyPress])); } } @@ -289,9 +144,4 @@ <maps:ApplicationEventMap /> <views:MainApplicationShell id="mainShell"/> - <mate:Listener type="{BBBEvent.LOGIN_EVENT}" method="handleLoginEvent"/> - <mate:Listener type="{BBBEvent.RECEIVED_PUBLIC_CHAT_MESSAGE_EVENT}" method="handleReceivedPublicChatMessageEvent"/> - <mate:Listener type="{BBBEvent.ADDED_LISTENER}" method="handleAddedListenerEvent"/> - <mate:Listener type="{BBBEvent.PRESENTATION_CONVERTED}" method="handlePresentationConversion"/> - <mate:Listener type="{BBBEvent.VIDEO_STARTED}" method="videoHasStarted"/> </mx:Application> diff --git a/bigbluebutton-client/src/BigBlueButtonTest.mxml b/bigbluebutton-client/src/BigBlueButtonTest.mxml index 2271a1cd1b30d54a5c1e8da28f1027645946c31d..4a123eaf57f7282f77c0ff82716c11f68245ffc5 100755 --- a/bigbluebutton-client/src/BigBlueButtonTest.mxml +++ b/bigbluebutton-client/src/BigBlueButtonTest.mxml @@ -206,7 +206,7 @@ <mate:Listener type="{BBBEvent.LOGIN_EVENT}" method="handleLoginEvent"/> <mate:Listener type="{BBBEvent.RECEIVED_PUBLIC_CHAT_MESSAGE_EVENT}" method="handleReceivedPublicChatMessageEvent"/> - <mate:Listener type="{BBBEvent.ADDED_LISTENER}" method="handleAddedListenerEvent"/> + <mate:Listener type="{BBBEvent.USER_VOICE_JOINED}" method="handleAddedListenerEvent"/> <mate:Listener type="{BBBEvent.PRESENTATION_CONVERTED}" method="handlePresentationConversion"/> <mate:Listener type="{BBBEvent.VIDEO_STARTED}" method="videoHasStarted"/> diff --git a/bigbluebutton-client/src/BroadcastModule.mxml b/bigbluebutton-client/src/BroadcastModule.mxml new file mode 100755 index 0000000000000000000000000000000000000000..3c0048c5bf676fb9661a6ea0f9f448d8c974ea7a --- /dev/null +++ b/bigbluebutton-client/src/BroadcastModule.mxml @@ -0,0 +1,60 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- + BigBlueButton open source conferencing system - http://www.bigbluebutton.org + + Copyright (c) 2010 BigBlueButton Inc. and by respective authors (see below). + + BigBlueButton is free software; you can redistribute it and/or modify it under the + terms of the GNU Lesser General Public License as published by the Free Software + Foundation; either version 2.1 of the License, or (at your option) any later + version. + + BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License along + with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. + + $Id: $ +--> + +<mx:Module xmlns:mx="http://www.adobe.com/2006/mxml" + xmlns:maps="org.bigbluebutton.modules.broadcast.maps.*" + xmlns:mate="http://mate.asfusion.com/" + implements="org.bigbluebutton.common.IBigBlueButtonModule" + creationComplete="onCreationComplete()"> + + <maps:BroadcastEventMap id="broadcastEventMap" /> + + <mx:Script> + <![CDATA[ + import com.asfusion.mate.events.Dispatcher; + import org.bigbluebutton.common.LogUtil; + + private var _moduleName:String = "Broadcast Module"; + private var _attributes:Object; + private var dispatcher:Dispatcher = new Dispatcher(); + + private function onCreationComplete():void { + LogUtil.debug("BrodcastModule Initialized"); + } + + public function get moduleName():String { + return _moduleName; + } + + public function start(attributes:Object):void { + LogUtil.debug("***Starting BroadcastModule"); + dispatcher.dispatchEvent(new Event("BroadcastModuleStartEvent")); + } + + public function stop():void { + LogUtil.debug("Stop Broadcast Module"); + + } + + ]]> + </mx:Script> +</mx:Module> diff --git a/bigbluebutton-client/src/DeskshareStandalone.mxml b/bigbluebutton-client/src/DeskshareStandalone.mxml index 002e1199a96f089d63a3be19cd6663bd846c4346..829c14967705685a5cc325ca6474b6327dc59f3f 100755 --- a/bigbluebutton-client/src/DeskshareStandalone.mxml +++ b/bigbluebutton-client/src/DeskshareStandalone.mxml @@ -34,7 +34,6 @@ <mx:Script> <![CDATA[ import mx.controls.Image; - import org.bigbluebutton.modules.deskshare.events.CursorEvent; import org.bigbluebutton.modules.deskshare.events.ViewStreamEvent; import mx.controls.Button; diff --git a/bigbluebutton-client/src/NotesModule.mxml b/bigbluebutton-client/src/NotesModule.mxml new file mode 100755 index 0000000000000000000000000000000000000000..a4a8eed9ca7f537b794780877a6fcd04f0ed007f --- /dev/null +++ b/bigbluebutton-client/src/NotesModule.mxml @@ -0,0 +1,59 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + BigBlueButton open source conferencing system - http://www.bigbluebutton.org/ + + Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below). + + This program is free software; you can redistribute it and/or modify it under the + terms of the GNU Lesser General Public License as published by the Free Software + Foundation; either version 2.1 of the License, or (at your option) any later + version. + + BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License along + with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. + + Author: Felipe Cecagno <felipe@mconf.org> + + $Id: $ +--> +<mx:Module xmlns:mx="http://www.adobe.com/2006/mxml" + xmlns:maps="org.bigbluebutton.modules.notes.maps.*" + implements="org.bigbluebutton.common.IBigBlueButtonModule"> + + + <maps:NotesEventMap id="notesEventMap" /> + + + <mx:Script> + <![CDATA[ + import com.asfusion.mate.events.Dispatcher; + + import org.bigbluebutton.common.LogUtil; + import org.bigbluebutton.modules.notes.events.NotesModuleStartEvent; + + private var _moduleName:String = "Layout Module"; + private var _dispatcher:Dispatcher = new Dispatcher(); + + private function onCreationComplete():void { + LogUtil.debug(_moduleName + " initialized"); + } + + public function get moduleName():String { + return _moduleName; + } + + public function start(attributes:Object):void { + _dispatcher.dispatchEvent(new NotesModuleStartEvent()); + } + + public function stop():void { + + } + + ]]> + </mx:Script> +</mx:Module> \ No newline at end of file diff --git a/bigbluebutton-client/src/VideoconfModule.mxml b/bigbluebutton-client/src/VideoconfModule.mxml index 7b1578361c6b68f81d19be48203dbc8b40ae06eb..3ea6ac8494415776cbdb7582a62d839635146490 100755 --- a/bigbluebutton-client/src/VideoconfModule.mxml +++ b/bigbluebutton-client/src/VideoconfModule.mxml @@ -3,22 +3,20 @@ xmlns:maps="org.bigbluebutton.modules.videoconf.maps.*" implements="org.bigbluebutton.common.IBigBlueButtonModule"> <mx:Script> <![CDATA[ - import org.bigbluebutton.modules.videoconf.events.ShareCameraRequestEvent; - import com.asfusion.mate.events.Dispatcher; - - import mx.controls.Alert; - - import org.bigbluebutton.common.LogUtil; - import org.bigbluebutton.common.events.CloseWindowEvent; - import org.bigbluebutton.common.events.OpenWindowEvent; - import org.bigbluebutton.modules.videoconf.business.VideoProxy; - import org.bigbluebutton.modules.videoconf.events.CloseAllWindowsEvent; + import com.asfusion.mate.events.Dispatcher; + + import org.bigbluebutton.common.LogUtil; + import org.bigbluebutton.common.events.CloseWindowEvent; + import org.bigbluebutton.common.events.OpenWindowEvent; + import org.bigbluebutton.modules.videoconf.business.VideoProxy; + import org.bigbluebutton.modules.videoconf.events.CloseAllWindowsEvent; + import org.bigbluebutton.modules.videoconf.events.ShareCameraRequestEvent; + import org.bigbluebutton.modules.videoconf.events.VideoModuleStartEvent; + import org.bigbluebutton.modules.videoconf.events.VideoModuleStopEvent; private var _moduleName:String = "Videoconf Module"; private var _attributes:Object; - private var proxy:VideoProxy; - private var mockProxy:VideoProxy; - + private function onCreationComplete():void { LogUtil.debug("VideoconfModule initialized"); } @@ -38,15 +36,7 @@ public function get username():String { return _attributes.username; } - - public function get connection():NetConnection { - return proxy.connection; - } - - public function get mockConnection():NetConnection { - return mockProxy.connection; - } - + public function get mode():String { if (_attributes.mode == null) { //_attributes.mode = "PLAYBACK" @@ -79,27 +69,16 @@ } public function start(attributes:Object):void { - LogUtil.debug("Videoconf attr: " + attributes.username); - _attributes = attributes; - proxy = new VideoProxy(uri); - - eventMap.module = this; - eventMap.proxy = proxy; - eventMap.addToolbarButton(); - - if (proxy.videoOptions.autoStart) { - var dispatcher:Dispatcher = new Dispatcher(); - dispatcher.dispatchEvent(new ShareCameraRequestEvent()); - } + trace("Starting Video Module"); + var globalDispatcher:Dispatcher = new Dispatcher(); + globalDispatcher.dispatchEvent(new VideoModuleStartEvent(VideoModuleStartEvent.START)); } public function stop():void { - eventMap.stopModule(); + var globalDispatcher:Dispatcher = new Dispatcher(); + globalDispatcher.dispatchEvent(new VideoModuleStopEvent(VideoModuleStopEvent.STOP)); } - public function mockConnect():void { - mockProxy = new VideoProxy(proxy.connection.uri.substring(0, proxy.connection.uri.indexOf("video") + 5)); - } ]]> </mx:Script> diff --git a/bigbluebutton-client/src/WebcamPreviewStandalone.mxml b/bigbluebutton-client/src/WebcamPreviewStandalone.mxml new file mode 100755 index 0000000000000000000000000000000000000000..ff5a23afd306a19ea8f7d88879a4b8f8fec8e399 --- /dev/null +++ b/bigbluebutton-client/src/WebcamPreviewStandalone.mxml @@ -0,0 +1,168 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- + BigBlueButton open source conferencing system - http://www.bigbluebutton.org + + Copyright (c) 2010 BigBlueButton Inc. and by respective authors (see below). + + BigBlueButton is free software; you can redistribute it and/or modify it under the + terms of the GNU Lesser General Public License as published by the Free Software + Foundation; either version 2.1 of the License, or (at your option) any later + version. + + BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License along + with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. + + $Id: $ +--> + +<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" + creationComplete="onCreationComplete()" + pageTitle="WebcamPreview" width="600" height="400" layout="absolute"> + <mx:Script> + <![CDATA[ + import mx.core.UIComponent; + import mx.utils.URLUtil; + + [Bindable] private var defaultWidth:Number = 320; + [Bindable] private var defaultHeight:Number = 240; + + private var _video:Video; + private var _videoHolder:UIComponent; + private var camIndex:int = 0; + private var camWidth:Number = 320; + private var camHeight:Number = 240; + private var _camera:Camera = null; + private var quality:Number = 0; + private var PADDING_HORIZONTAL:Number = 6; + private var PADDING_VERTICAL:Number = 29; + private var _minWidth:int = 160 + PADDING_HORIZONTAL; + private var _minHeight:int = 120 + PADDING_VERTICAL; + private var aspectRatio:Number = 1; + + private function onCreationComplete():void{ + _videoHolder = new UIComponent(); + _videoHolder.width = camWidth; + _videoHolder.height = camHeight; + this.addChild(_videoHolder); + + this.minWidth = _minWidth; + this.minHeight = _minHeight; + + trace('WebcamPreviewSA::onCreationComplete'); + Security.allowDomain(determineHTMLURL()); + trace("WebcamPreviewSA:: Security.allowDomain(" + determineHTMLURL() + ");"); + + initExternalInterface(); +// displayCamera("0", 30, 10, 0, 90); + } + + private function determineHTMLURL():String { + var serverName:String = "*"; + if(ExternalInterface.available) { + try { + var htmlURL:String = String(ExternalInterface.call("window.location.href.toString")); + serverName = URLUtil.getServerName(htmlURL); + trace("WebcamPreviewSA::determineHTMLURL HTML URL [" + htmlURL + "]"); + } catch(s:Error) { + trace("WebcamPreviewSA::determineHTMLURL Cannot determine HTML URL"); + } + } + + return serverName; + } + + private function initExternalInterface():void { + trace('WebcamPreviewSA::initExternalInterface'); + if (ExternalInterface.available) { + ExternalInterface.addCallback("startPreviewCamera", handleStartPreviewCameraRequest); + ExternalInterface.addCallback("stopPreviewCamera", handleStopPreviewCamera); + } + } + + private function handleStartPreviewCameraRequest(camIndex:String, camWidth:int, camHeight:int, + camKeyFrameInterval:int, camModeFps:Number, + camQualityBandwidth:int, camQualityPicture:int):void { + displayCamera(camIndex, camWidth, camHeight, camKeyFrameInterval, camModeFps, camQualityBandwidth, camQualityPicture); + } + + private function handleStopPreviewCamera():void { + stopCamera(); + } + + public function displayCamera(camIndex:String, camWidth:int, camHeight:int, camKeyFrameInterval:int, camModeFps:Number, camQualityBandwidth:int, camQualityPicture:int):void { + trace('WebcamPreviewSA::displayCamera'); + stopCamera(); + + if (Camera.names.length == 0) { + trace('WebcamPreviewSA::bbb.video.publish.hint.noCamera'); + return; + } + + _camera = Camera.getCamera(camIndex); + if (_camera == null) { + trace('WebcamPreviewSA::bbb.video.publish.hint.cantOpenCamera'); + return; + } + + setResolution(camWidth, camHeight); + + _camera.setMotionLevel(5, 1000); + + _camera.setKeyFrameInterval(camKeyFrameInterval); + _camera.setMode(camWidth, camHeight, camModeFps); + _camera.setQuality(camQualityBandwidth, camQualityPicture); + + if (_camera.width != camWidth || _camera.height != camHeight) { + trace("WebcamPreviewSA::Resolution " + camWidth + "x" + camHeight + " is not supported, using " + _camera.width + "x" + _camera.height + " instead"); + setResolution(_camera.width, _camera.height); + } + + _video = new Video; + _video.attachCamera(_camera); + + if (aspectRatio > _videoHolder.width / _videoHolder.height) { + _video.width = _videoHolder.width; + _video.height = _videoHolder.width / aspectRatio; + _video.x = 0; + _video.y = (_videoHolder.height - _video.height) / 2; + } else { + _video.width = _videoHolder.height * aspectRatio; + _video.height = _videoHolder.height; + _video.x = (_videoHolder.width - _video.width) / 2; + _video.y = 0; + } + + _videoHolder.addChild(_video); + } + + private function stopCamera():void { + _camera = null; + if (_video != null) { + _videoHolder.removeChild(_video); + _video.attachCamera(null); + _video.clear(); + _video = null; + } + } + + private function setResolution(width:int, height:int):void { + camWidth = width; + camHeight = height; + setAspectRatio(camWidth, camHeight); + } + + private function setAspectRatio(width:int, height:int):void { + aspectRatio = (width/height); + this.minHeight = Math.floor((this.minWidth - PADDING_HORIZONTAL) / aspectRatio) + PADDING_VERTICAL; + } + + + ]]> + </mx:Script> + +</mx:Application> diff --git a/bigbluebutton-client/src/WebcamViewStandalone.mxml b/bigbluebutton-client/src/WebcamViewStandalone.mxml new file mode 100755 index 0000000000000000000000000000000000000000..30cf9e97bf0f570c68c65d77d9a77f8333c80825 --- /dev/null +++ b/bigbluebutton-client/src/WebcamViewStandalone.mxml @@ -0,0 +1,249 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- +BigBlueButton open source conferencing system - http://www.bigbluebutton.org + +Copyright (c) 2010 BigBlueButton Inc. and by respective authors (see below). + +BigBlueButton is free software; you can redistribute it and/or modify it under the +terms of the GNU Lesser General Public License as published by the Free Software +Foundation; either version 2.1 of the License, or (at your option) any later +version. + +BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. + +$Id: $ +--> + +<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" + creationComplete="onCreationComplete()" + pageTitle="WebcamView" width="600" height="400" layout="absolute"> + <mx:Script> + <![CDATA[ + import flash.net.NetConnection; + + import mx.core.UIComponent; + import mx.utils.URLUtil; + + private var _video:Video; + private var _videoHolder:UIComponent; + private var ns:NetStream; + private var nc:NetConnection; + private var PADDING_HORIZONTAL:Number = 6; + private var PADDING_VERTICAL:Number = 29; + private var _minWidth:int = 160 + PADDING_HORIZONTAL; + private var _minHeight:int = 120 + PADDING_VERTICAL; + private var aspectRatio:Number = 1; + private var camWidth:Number = 320; + private var camHeight:Number = 240; + + private var _url:String; + private var _stream:String; + + private var smoothVideo:Boolean = false; + private var applyConvolutionFilter:Boolean = false; + private var convolutionFilter:Array = [-1, 0, -1, 0, 6, 0, -1, 0, -1]; + private var filterBias:Number = 0; + private var filterDivisor:Number = 4; + private var enableH264:Boolean = false; + private var h264Level:String = "2.1"; + private var h264Profile:String = "main"; + + private function onCreationComplete():void{ + _videoHolder = new UIComponent(); + _videoHolder.width = camWidth; + _videoHolder.height = camHeight; + this.addChild(_videoHolder); + + this.minWidth = _minWidth; + this.minHeight = _minHeight; + + Security.allowDomain(determineHTMLURL()); + trace("WebcamViewSA:: Security.allowDomain(" + determineHTMLURL() + ");"); + + initExternalInterface(); + } + + private function determineHTMLURL():String { + var serverName:String = "*"; + if(ExternalInterface.available) { + try { + var htmlURL:String = String(ExternalInterface.call("window.location.href.toString")); + serverName = URLUtil.getServerName(htmlURL); + trace("WebcamViewSA::determineHTMLURL HTML URL [" + htmlURL + "]"); + } catch(s:Error) { + trace("WebcamViewSA::determineHTMLURL Cannot determine HTML URL"); + } + } + + return serverName; + } + + private function initExternalInterface():void { + trace('WebcamViewSA::initExternalInterface'); + if (ExternalInterface.available) { + ExternalInterface.addCallback("startViewCameraStream", handleStartViewCameraRequest); + ExternalInterface.addCallback("stopViewCameraStream", handleStopViewCamera); + } + } + + private function connect(url:String):void { + nc = new NetConnection(); + nc.client = this; + nc.addEventListener(AsyncErrorEvent.ASYNC_ERROR, onAsyncError); + nc.addEventListener(IOErrorEvent.IO_ERROR, onIOError); + nc.addEventListener(NetStatusEvent.NET_STATUS, onNetConnectStatus); + nc.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSecurityError); + nc.connect(url); + } + + public function onBWCheck(... rest):Number { + return 0; + } + + public function onBWDone(... rest):void { + var p_bw:Number; + if (rest.length > 0) p_bw = rest[0]; + // your application should do something here + // when the bandwidth check is complete + trace("WebcamViewSA:: bandwidth = " + p_bw + " Kbps."); + } + + private function getVideoResolution(stream:String):Array { + var pattern:RegExp = new RegExp("(\\d+x\\d+)-([A-Za-z0-9]+)-\\d+", ""); + if (pattern.test(stream)) { + trace("WebcamViewSA:: The stream name is well formatted [" + stream + "]"); + trace("WebcamViewSA:: Stream resolution is [" + pattern.exec(stream)[1] + "]"); + trace("WebcamViewSA:: Userid [" + pattern.exec(stream)[2] + "]"); + return pattern.exec(stream)[1].split("x"); + } else { + trace("WebcamViewSA:: The stream name doesn't follow the pattern <width>x<height>-<userId>-<timestamp>. Using 320x240 resolution."); + return "320x240".split("x"); + } + } + + + private function onIOError(event:NetStatusEvent):void{ + } + + private function onNetConnectStatus(event:NetStatusEvent):void{ + switch(event.info.code){ + case "NetConnection.Connect.Success": + playWebcamStream(); + break; + default: + trace("WebcamViewSA:: [" + event.info.code + "] for [" + _url + "]"); + break; + } + } + + private function onSecurityError(event:NetStatusEvent):void{ + } + + private function handleStartViewCameraRequest(url:String, stream:String):void { + _url = url; + _stream = stream; + connect(url); + } + + private function handleStopViewCamera():void { + stopViewing(); + } + + private function stopViewing():void { + if (_video != null) { + trace("WebcamViewSA:: removing video from holder"); + _videoHolder.removeChild(_video); + _video.clear(); + _video = null; + } + _videoHolder.visible = false; + trace("WebcamViewSA:: closing stream"); + ns.close(); + } + + private function playWebcamStream():void { + ns = new NetStream(nc); + ns.addEventListener( NetStatusEvent.NET_STATUS, onNetStatus ); + ns.addEventListener(AsyncErrorEvent.ASYNC_ERROR, onAsyncError); + ns.client = this; + ns.bufferTime = 0; + ns.receiveVideo(true); + ns.receiveAudio(false); + + var res:Array = getVideoResolution(_stream); + if (res == null) // error + return; + _video = new Video(Number(res[0]), Number(res[1])); + _video.width = Number(res[0]); + _video.height = Number(res[1]); + trace("WebcamViewSA:: resolution [" + _video.width + "," + _video.height + "]"); + setAspectRatio(Number(res[0]), Number(res[1])); + _video.attachNetStream(ns); + + _video.smoothing = smoothVideo; + + if (applyConvolutionFilter) { + var filter:ConvolutionFilter = new flash.filters.ConvolutionFilter(); + filter.matrixX = 3; + filter.matrixY = 3; + filter.matrix = convolutionFilter; + filter.bias = filterBias; + filter.divisor = filterDivisor; + _video.filters = [filter]; + } + + trace("WebcamViewSA:: adding video to video holder"); + + _videoHolder.addChild(_video); + + _videoHolder.visible = true; + ns.play(_stream); + } + + private function onAsyncError(e:AsyncErrorEvent):void{ + trace("WebcamViewSA::asyncerror " + e.toString()); + } + + public function onMetaData(info:Object):void{ + trace("WebcamViewSA:: metadata: width=" + info.width + " height=" + info.height); + _video.width = info.width; + _video.height = info.height; + setAspectRatio(info.width, info.height); + } + + private function onNetStatus(e:NetStatusEvent):void{ + switch(e.info.code){ + case "NetStream.Publish.Start": + trace("WebcamViewSA:: NetStream.Publish.Start"); + break; + case "NetStream.Play.UnpublishNotify": + trace("WebcamViewSA:: NetStream.Play.UnpublishNotify"); + stopViewing(); + break; + case "NetStream.Play.Start": + trace("WebcamViewSA:: Netstatus: " + e.info.code); + break; + case "NetStream.Play.FileStructureInvalid": + trace("WebcamViewSA:: The MP4's file structure is invalid."); + break; + case "NetStream.Play.NoSupportedTrackFound": + trace("WebcamViewSA:: The MP4 doesn't contain any supported tracks"); + break; + } + } + + private function setAspectRatio(width:int, height:int):void { + aspectRatio = (width/height); + this.minHeight = Math.floor((this.minWidth - PADDING_HORIZONTAL) / aspectRatio) + PADDING_VERTICAL; + } + + ]]> + </mx:Script> + +</mx:Application> diff --git a/bigbluebutton-client/src/assets/images/delete.png b/bigbluebutton-client/src/assets/images/delete.png new file mode 100755 index 0000000000000000000000000000000000000000..0059eaa72ce11f79b11fb2fa0d5c3e225ca6c11a Binary files /dev/null and b/bigbluebutton-client/src/assets/images/delete.png differ diff --git a/bigbluebutton-client/src/assets/images/elipse.png b/bigbluebutton-client/src/assets/images/elipse.png new file mode 100755 index 0000000000000000000000000000000000000000..3252b6aa957f272a4c0ae8ea14213464681cba7d Binary files /dev/null and b/bigbluebutton-client/src/assets/images/elipse.png differ diff --git a/bigbluebutton-client/src/assets/images/fit-to-screen.png b/bigbluebutton-client/src/assets/images/fit-to-screen.png new file mode 100755 index 0000000000000000000000000000000000000000..0f83d20bedc8a35bced16f37b0e6cd2f6277d81f Binary files /dev/null and b/bigbluebutton-client/src/assets/images/fit-to-screen.png differ diff --git a/bigbluebutton-client/src/assets/images/fit-to-width.png b/bigbluebutton-client/src/assets/images/fit-to-width.png new file mode 100755 index 0000000000000000000000000000000000000000..12b052798435720aca6d0c94e43073e4eac5a1d4 Binary files /dev/null and b/bigbluebutton-client/src/assets/images/fit-to-width.png differ diff --git a/bigbluebutton-client/src/assets/images/hand.png b/bigbluebutton-client/src/assets/images/hand.png new file mode 100755 index 0000000000000000000000000000000000000000..d923194e5ed2ccd5ac57a456540474727d6531b4 Binary files /dev/null and b/bigbluebutton-client/src/assets/images/hand.png differ diff --git a/bigbluebutton-client/src/assets/images/left_arrow.png b/bigbluebutton-client/src/assets/images/left_arrow.png new file mode 100755 index 0000000000000000000000000000000000000000..e6abeb60d45d8a2a2b0f85b741f1af14683a4c4c Binary files /dev/null and b/bigbluebutton-client/src/assets/images/left_arrow.png differ diff --git a/bigbluebutton-client/src/assets/images/line.png b/bigbluebutton-client/src/assets/images/line.png new file mode 100755 index 0000000000000000000000000000000000000000..175174a0ea5ebb32535ad19ccd0a86a319496bb9 Binary files /dev/null and b/bigbluebutton-client/src/assets/images/line.png differ diff --git a/bigbluebutton-client/src/assets/images/pencil.png b/bigbluebutton-client/src/assets/images/pencil.png new file mode 100755 index 0000000000000000000000000000000000000000..dd979748baebc063f532af178489732ab0c4548f Binary files /dev/null and b/bigbluebutton-client/src/assets/images/pencil.png differ diff --git a/bigbluebutton-client/src/assets/images/right_arrow.png b/bigbluebutton-client/src/assets/images/right_arrow.png new file mode 100755 index 0000000000000000000000000000000000000000..6eab9f047a5ecd05d65ac1dae3f226b80009347d Binary files /dev/null and b/bigbluebutton-client/src/assets/images/right_arrow.png differ diff --git a/bigbluebutton-client/src/assets/images/square.png b/bigbluebutton-client/src/assets/images/square.png new file mode 100755 index 0000000000000000000000000000000000000000..48b5280bdb794d3d5de562af781f3affeda59ffc Binary files /dev/null and b/bigbluebutton-client/src/assets/images/square.png differ diff --git a/bigbluebutton-client/src/assets/images/text.png b/bigbluebutton-client/src/assets/images/text.png new file mode 100755 index 0000000000000000000000000000000000000000..21858b8ca4c28e93acb3eddd665860cf214d3bcf Binary files /dev/null and b/bigbluebutton-client/src/assets/images/text.png differ diff --git a/bigbluebutton-client/src/assets/images/triangle.png b/bigbluebutton-client/src/assets/images/triangle.png new file mode 100755 index 0000000000000000000000000000000000000000..f629ee6aebb3c600c700ef3eb54cbf59917dd70b Binary files /dev/null and b/bigbluebutton-client/src/assets/images/triangle.png differ diff --git a/bigbluebutton-client/src/assets/images/undo.png b/bigbluebutton-client/src/assets/images/undo.png new file mode 100755 index 0000000000000000000000000000000000000000..f65e9db3edf0cd4f2ee09b2a463e8436c19894d3 Binary files /dev/null and b/bigbluebutton-client/src/assets/images/undo.png differ diff --git a/bigbluebutton-client/src/assets/images/upload.png b/bigbluebutton-client/src/assets/images/upload.png new file mode 100755 index 0000000000000000000000000000000000000000..724a094ae6974c1fd6e451ed492a06955e538ed2 Binary files /dev/null and b/bigbluebutton-client/src/assets/images/upload.png differ diff --git a/bigbluebutton-client/src/branding/css/Blue.css b/bigbluebutton-client/src/branding/css/Blue.css deleted file mode 100755 index 86a6b807a8228e8f2608e74ca809ce21866ca8b7..0000000000000000000000000000000000000000 --- a/bigbluebutton-client/src/branding/css/Blue.css +++ /dev/null @@ -1,173 +0,0 @@ -Application -{ - backgroundColors: #000000,#3f3f3f; - backgroundImage: Embed(source="assets/img/bk3.jpg"); - backgroundSize: "100%"; - backgroundGradientColors: #B4BFCF, #78829C; - themeColor: #0094C7; - backgroundGradientAlphas: 0.62, 0.26; -} - - -MDICanvas -{ - backgroundColor: #0094C7; - backgroundAlpha: 0; -} - - -Button -{ - highlightAlphas: 1, 0.33; - fillAlphas: 1, 0.16, 0.18, 1; - fillColors: #5f89b9, #697182, #ffffff, #eeeeee; -} - - -.mdiWindowFocus -{ - headerHeight: 26; - roundedBottomCorners: true; - backgroundAlpha: 1; - backgroundColor: #FFFFFF; - backgroundImage: Embed(source="assets/swf/Blue.swf", symbol="Window_Background"); - backgroundSize: '100%'; - - borderStyle: solid; - borderColor: #ffffff; - borderAlpha: 1; - borderThickness: 2; - borderThicknessLeft: 0; - borderThicknessTop: 0; - borderThicknessBottom: 0; - borderThicknessRight: 0; - cornerRadius: 4; - dropShadowEnabled: false; - titleStyleName: "mypanelTitle"; - - cornerResizeImg: Embed(source="assets/swf/Blue.swf", symbol="Corner_Resize"); - cornerResizeWidth: 15; - cornerResizeHeight: 15; - cornerResizePaddingRight: 2; - cornerResizePaddingBottom: 2; - - controlButtonWidth: 10; - controlButtonHeight: 10; - controlButtonGap: 4; -} - -.mdiWindowNoFocus -{ - headerHeight: 26; - roundedBottomCorners: true; - backgroundAlpha: 0.5; - backgroundImage: Embed(source="assets/swf/Blue.swf", symbol="Window_Background"); - backgroundSize: '100%'; - - borderStyle: solid; - borderColor: #ffffff; - borderAlpha: 1; - borderThickness: 2; - borderThicknessLeft: 0; - borderThicknessTop: 0; - borderThicknessBottom: 0; - borderThicknessRight: 0; - cornerRadius: 4; - dropShadowEnabled: false; - titleStyleName: "mypanelTitle"; - - cornerResizeImg: Embed(source="assets/swf/Blue.swf", symbol="Corner_Resize"); - cornerResizeWidth: 15; - cornerResizeHeight: 15; - cornerResizePaddingRight: 2; - cornerResizePaddingBottom: 2; - - controlButtonWidth: 10; - controlButtonHeight: 10; - controlButtonGap: 4; -} - -.mypanelTitle -{ - fontFamily: Arial; - fontSize: 12; - fontWeight: bold; - color: #ffffff; -} - - -.closeBtnFocus -{ - upSkin: Embed('assets/img/3_closeButton.png'); - overSkin: Embed('assets/img/3_closeButton.png'); - downSkin: Embed('assets/img/3_closeButton.png'); - disabledSkin: Embed('assets/img/3_closeButton.png'); -} - -.closeBtnNoFocus -{ - upSkin: Embed('assets/img/3_closeButton.png'); - overSkin: Embed('assets/img/3_closeButton.png'); - downSkin: Embed('assets/img/3_closeButton.png'); - disabledSkin: Embed('assets/img/3_closeButton.png'); -} - - -.increaseBtnFocus -{ - upSkin: Embed('assets/img/3_increaseButton.png'); - overSkin: Embed('assets/img/3_increaseButton.png'); - downSkin: Embed('assets/img/3_increaseButton.png'); - disabledSkin: Embed('assets/img/3_increaseButton.png'); -} - -.increaseBtnNoFocus -{ - upSkin: Embed('assets/img/3_increaseButton.png'); - overSkin: Embed('assets/img/3_increaseButton.png'); - downSkin: Embed('assets/img/3_increaseButton.png'); - disabledSkin: Embed('assets/img/3_increaseButton.png'); -} - - -.decreaseBtnFocus -{ - upSkin: Embed('assets/img/3_decreaseButton.png'); - overSkin: Embed('assets/img/3_decreaseButton.png'); - downSkin: Embed('assets/img/3_decreaseButton.png'); - disabledSkin: Embed('assets/img/3_decreaseButton.png'); -} - -.decreaseBtnNoFocus -{ - upSkin: Embed('assets/img/3_decreaseButton.png'); - overSkin: Embed('assets/img/3_decreaseButton.png'); - downSkin: Embed('assets/img/3_decreaseButton.png'); - disabledSkin: Embed('assets/img/3_decreaseButton.png'); -} - - -.minimizeBtnFocus -{ - upSkin: Embed('assets/img/3_minimizeButton.png'); - overSkin: Embed('assets/img/3_minimizeButton.png'); - downSkin: Embed('assets/img/3_minimizeButton.png'); - disabledSkin: Embed('assets/img/3_minimizeButton.png'); -} - -.minimizeBtnNoFocus -{ - upSkin: Embed('assets/img/3_minimizeButton.png'); - overSkin: Embed('assets/img/3_minimizeButton.png'); - downSkin: Embed('assets/img/3_minimizeButton.png'); - disabledSkin: Embed('assets/img/3_minimizeButton.png'); -} - - -.resizeHndlr -{ - upSkin: Embed('assets/img/resizeHandler.png'); - overSkin: Embed('assets/img/resizeHandler.png'); - downSkin: Embed('assets/img/resizeHandler.png'); - disabledSkin: Embed('assets/img/resizeHandler.png'); -} \ No newline at end of file diff --git a/bigbluebutton-client/src/branding/css/BrushedMetal.css b/bigbluebutton-client/src/branding/css/BrushedMetal.css deleted file mode 100755 index 4bb394c2c4183347670970143a46d57b6d8c21c1..0000000000000000000000000000000000000000 --- a/bigbluebutton-client/src/branding/css/BrushedMetal.css +++ /dev/null @@ -1,100 +0,0 @@ -Application -{ - - backgroundImage: Embed(source="assets/img/BrushedMetalBack.jpg"); - backgroundSize: "100%"; - themeColor: #999999; - fontFamily: Arial; - paddingLeft:10; paddingRight:10; paddingTop:10; paddingBottom:10; -} - -.mdiWindowFocus -{ - borderStyle: solid; - borderColor: #FFFFFF; - borderAlpha: 0; - borderThickness: 1; - borderThicknessLeft: 5; - borderThicknessTop: 5; - borderThicknessBottom: 15; - borderThicknessRight: 5; - roundedBottomCorners: true; - cornerRadius: 5; - headerHeight: 27; - - backgroundAlpha: 1; - backgroundColor: #FFFFFF; - - backgroundImage: Embed(source="assets/swf/BrushedMetal.swf", symbol="Window_Background"); - backgroundSize: "100%"; - - dropShadowEnabled: true; - - - cornerResizeImg: Embed(source="assets/swf/BrushedMetal.swf", symbol="Corner_Resize"); - cornerResizeWidth: 12; - cornerResizeHeight: 12; - cornerResizePaddingRight: 2; - cornerResizePaddingBottom: 2; - - titleStyleName: "mypanelTitle"; - titleBackgroundSkin: Embed(source="assets/swf/BrushedMetal.swf", symbol="header"); - - controlButtonWidth: 10; - controlButtonHeight: 10; - controlButtonGap: 8; -} - -.mdiWindowNoFocus -{ - borderStyle: solid; - borderColor: #FFFFFF; - borderAlpha: 0; - borderThickness: 1; - borderThicknessLeft: 5; - borderThicknessTop: 5; - borderThicknessBottom: 15; - borderThicknessRight: 5; - roundedBottomCorners: true; - cornerRadius: 5; - headerHeight: 27; - - - - - backgroundAlpha: 1; - backgroundColor: #FFFFFF; - - backgroundImage: Embed(source="assets/swf/BrushedMetal.swf", symbol="Window_Background"); - backgroundSize: "100%"; - - dropShadowEnabled: true; - - - cornerResizeImg: Embed(source="assets/swf/BrushedMetal.swf", symbol="Corner_Resize"); - cornerResizeWidth: 12; - cornerResizeHeight: 12; - cornerResizePaddingRight: 2; - cornerResizePaddingBottom: 2; - - titleStyleName: "mypanelTitle"; - titleBackgroundSkin: Embed(source="assets/swf/BrushedMetal.swf", symbol="header"); - - controlButtonWidth: 10; - controlButtonHeight: 10; - controlButtonGap: 8; -} - - - -.mypanelTitle { - fontSize: 12; - fontWeight: bold; - color: #000000; -} - - -Button { - fillColors: #ffffff, #999999, #ffffff, #eeeeee; - themeColor: #999999; -} diff --git a/bigbluebutton-client/src/branding/css/MacOS9.css b/bigbluebutton-client/src/branding/css/MacOS9.css deleted file mode 100755 index 98a49cdf6ff985c94dab5d8906d12190932de7be..0000000000000000000000000000000000000000 --- a/bigbluebutton-client/src/branding/css/MacOS9.css +++ /dev/null @@ -1,83 +0,0 @@ -Application -{ - background-image: Embed(source="assets/img/wallpaperOS9.gif"); -} - -MDIWindow -{ - titleStyleName: "macTitle"; -} - -.macTitle -{ - fontFamily: Georgia; - fontSize: 12; - fontWeight: normal; - color: #000000; -} - -.mdiWindowFocus -{ - borderColor: #CCCCCC; - borderAlpha: 1; - borderThicknessLeft: 2; - borderThicknessTop: 0; - borderThicknessBottom: 2; - borderThicknessRight: 2; - roundedBottomCorners: false; - cornerRadius: 5; - dropShadowEnabled: false; - headerHeight: 29; - backgroundAlpha: 1; - backgroundColor: #FFFFFF; - titleBackgroundSkin: Embed(source="assets/swf/WindowsAssets.swf", symbol="macHeaderFocus"); -} - -.mdiWindowNoFocus -{ - borderColor: #CCCCCC; - borderAlpha: 1; - borderThicknessLeft: 2; - borderThicknessTop: 0; - borderThicknessBottom: 2; - borderThicknessRight: 2; - roundedBottomCorners: false; - cornerRadius: 5; - dropShadowEnabled: false; - headerHeight: 29; - backgroundAlpha: 1; - backgroundColor: #FFFFFF; - titleBackgroundSkin: Embed(source="assets/swf/WindowsAssets.swf", symbol="macHeaderNoFocus"); -} - -.mdiWindowMinimizeBtn -{ - upSkin: Embed(source="assets/swf/WindowsAssets.swf", symbol="minimizeBtnOS9"); - overSkin: Embed(source="assets/swf/WindowsAssets.swf", symbol="minimizeBtnOS9"); - downSkin: Embed(source="assets/swf/WindowsAssets.swf", symbol="minimizeBtnOS9"); - disabledSkin: Embed(source="assets/swf/WindowsAssets.swf", symbol="minimizeBtnOS9"); -} - -.mdiWindowMaximizeBtn -{ - upSkin: Embed(source="assets/swf/WindowsAssets.swf", symbol="maxRestoreBtnOS9"); - overSkin: Embed(source="assets/swf/WindowsAssets.swf", symbol="maxRestoreBtnOS9"); - downSkin: Embed(source="assets/swf/WindowsAssets.swf", symbol="maxRestoreBtnOS9"); - disabledSkin: Embed(source="assets/swf/WindowsAssets.swf", symbol="maxRestoreBtnOS9"); -} - -.mdiWindowRestoreBtn -{ - upSkin: Embed(source="assets/swf/WindowsAssets.swf", symbol="maxRestoreBtnOS9"); - overSkin: Embed(source="assets/swf/WindowsAssets.swf", symbol="maxRestoreBtnOS9"); - downSkin: Embed(source="assets/swf/WindowsAssets.swf", symbol="maxRestoreBtnOS9"); - disabledSkin: Embed(source="assets/swf/WindowsAssets.swf", symbol="maxRestoreBtnOS9"); -} - -.mdiWindowCloseBtn -{ - upSkin: Embed(source="assets/swf/WindowsAssets.swf", symbol="closeBtnOS9"); - overSkin: Embed(source="assets/swf/WindowsAssets.swf", symbol="closeBtnOS9"); - downSkin: Embed(source="assets/swf/WindowsAssets.swf", symbol="closeBtnOS9"); - disabledSkin: Embed(source="assets/swf/WindowsAssets.swf", symbol="closeBtnOS9"); -} \ No newline at end of file diff --git a/bigbluebutton-client/src/branding/css/WindowsXP_default.css b/bigbluebutton-client/src/branding/css/WindowsXP_default.css deleted file mode 100755 index f7f143a4d6b3d43f64f48feefa8144ac042c7cbf..0000000000000000000000000000000000000000 --- a/bigbluebutton-client/src/branding/css/WindowsXP_default.css +++ /dev/null @@ -1,143 +0,0 @@ -/* -Copyright (c) 2007 FlexLib Contributors. See: - http://code.google.com/p/flexlib/wiki/ProjectContributors - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -*/ -Application -{ - background-image: Embed(source="assets/img/wallpaperWin.jpg"); -} - -MDIWindow -{ - titleStyleName: "winXPTitleStyle"; - - minimizeBtnStyleNameNoFocus: "mdiWindowMinimizeBtnNoFocus"; - maximizeBtnStyleNameNoFocus: "mdiWindowMaximizeBtnNoFocus"; - restoreBtnStyleNameNoFocus: "mdiWindowRestoreBtnNoFocus"; - closeBtnStyleNameNoFocus: "mdiWindowCloseBtnNoFocus"; -} - -.winXPTitleStyle -{ - fontFamily: Verdana; - fontSize: 12; - fontWeight: normal; - color: #FFFFFF; -} - -.mdiWindowFocus -{ - borderColor: #0053E1; - borderAlpha: 1; - borderThicknessLeft: 2; - borderThicknessTop: 0; - borderThicknessBottom: 2; - borderThicknessRight: 2; - roundedBottomCorners: false; - cornerRadius: 5; - dropShadowEnabled: false; - headerHeight: 29; - backgroundAlpha: 1; - backgroundColor: #FFFFFF; - titleBackgroundSkin: Embed(source="assets/swf/WindowsAssets.swf", symbol="headerBgDefault"); -} - -.mdiWindowNoFocus -{ - borderColor: #7B97E0; - borderAlpha: 1; - borderThicknessLeft: 2; - borderThicknessTop: 0; - borderThicknessBottom: 2; - borderThicknessRight: 2; - roundedBottomCorners: false; - cornerRadius: 5; - dropShadowEnabled: false; - headerHeight: 29; - backgroundAlpha: 1; - backgroundColor: #FFFFFF; - titleBackgroundSkin: Embed(source="assets/swf/WindowsAssets.swf", symbol="headerBgDefaultNoFocus"); - -} - -.mdiWindowMinimizeBtn -{ - upSkin: Embed(source="assets/swf/WindowsAssets.swf", symbol="minimizeBtnDefault_up"); - overSkin: Embed(source="assets/swf/WindowsAssets.swf", symbol="minimizeBtnDefault_over"); - downSkin: Embed(source="assets/swf/WindowsAssets.swf", symbol="minimizeBtnDefault_down"); - disabledSkin: Embed(source="assets/swf/WindowsAssets.swf", symbol="minimizeBtnDefaultNoFocus"); -} - -.mdiWindowMinimizeBtnNoFocus -{ - upSkin: Embed(source="assets/swf/WindowsAssets.swf", symbol="minimizeBtnDefaultNoFocus"); - overSkin: Embed(source="assets/swf/WindowsAssets.swf", symbol="minimizeBtnDefaultNoFocus"); - downSkin: Embed(source="assets/swf/WindowsAssets.swf", symbol="minimizeBtnDefaultNoFocus"); - disabledSkin: Embed(source="assets/swf/WindowsAssets.swf", symbol="minimizeBtnDefaultNoFocus"); -} - -.mdiWindowMaximizeBtn -{ - upSkin: Embed(source="assets/swf/WindowsAssets.swf", symbol="maximizeBtnDefault_up"); - overSkin: Embed(source="assets/swf/WindowsAssets.swf", symbol="maximizeBtnDefault_over"); - downSkin: Embed(source="assets/swf/WindowsAssets.swf", symbol="maximizeBtnDefault_down"); - disabledSkin: Embed(source="assets/swf/WindowsAssets.swf", symbol="maximizeBtnDefaultNoFocus"); -} - -.mdiWindowMaximizeBtnNoFocus -{ - upSkin: Embed(source="assets/swf/WindowsAssets.swf", symbol="maximizeBtnDefaultNoFocus"); - overSkin: Embed(source="assets/swf/WindowsAssets.swf", symbol="maximizeBtnDefaultNoFocus"); - downSkin: Embed(source="assets/swf/WindowsAssets.swf", symbol="maximizeBtnDefaultNoFocus"); - disabledSkin: Embed(source="assets/swf/WindowsAssets.swf", symbol="maximizeBtnDefaultNoFocus"); -} - -.mdiWindowRestoreBtn -{ - upSkin: Embed(source="assets/swf/WindowsAssets.swf", symbol="restoreBtnDefault_up"); - overSkin: Embed(source="assets/swf/WindowsAssets.swf", symbol="restoreBtnDefault_over"); - downSkin: Embed(source="assets/swf/WindowsAssets.swf", symbol="restoreBtnDefault_down"); - disabledSkin: Embed(source="assets/swf/WindowsAssets.swf", symbol="restoreBtnDefaultNoFocus"); -} - -.mdiWindowRestoreBtnNoFocus -{ - upSkin: Embed(source="assets/swf/WindowsAssets.swf", symbol="restoreBtnDefaultNoFocus"); - overSkin: Embed(source="assets/swf/WindowsAssets.swf", symbol="restoreBtnDefaultNoFocus"); - downSkin: Embed(source="assets/swf/WindowsAssets.swf", symbol="restoreBtnDefaultNoFocus"); - disabledSkin: Embed(source="assets/swf/WindowsAssets.swf", symbol="restoreBtnDefaultNoFocus"); -} - -.mdiWindowCloseBtn -{ - upSkin: Embed(source="assets/swf/WindowsAssets.swf", symbol="closeBtnDefault_up"); - overSkin: Embed(source="assets/swf/WindowsAssets.swf", symbol="closeBtnDefault_over"); - downSkin: Embed(source="assets/swf/WindowsAssets.swf", symbol="closeBtnDefault_down"); - disabledSkin: Embed(source="assets/swf/WindowsAssets.swf", symbol="closeBtnDefaultNoFocus"); -} - -.mdiWindowCloseBtnNoFocus -{ - upSkin: Embed(source="assets/swf/WindowsAssets.swf", symbol="closeBtnDefaultNoFocus"); - overSkin: Embed(source="assets/swf/WindowsAssets.swf", symbol="closeBtnDefaultNoFocus"); - downSkin: Embed(source="assets/swf/WindowsAssets.swf", symbol="closeBtnDefaultNoFocus"); - disabledSkin: Embed(source="assets/swf/WindowsAssets.swf", symbol="closeBtnDefaultNoFocus"); -} \ No newline at end of file diff --git a/bigbluebutton-client/src/branding/css/assets/img/BBBLogo.png b/bigbluebutton-client/src/branding/css/assets/img/BBBLogo.png deleted file mode 100755 index 9f1fa1235f1ecd14dd0424ae1e953fe66b33e597..0000000000000000000000000000000000000000 Binary files a/bigbluebutton-client/src/branding/css/assets/img/BBBLogo.png and /dev/null differ diff --git a/bigbluebutton-client/src/branding/css/assets/img/BrushedMetalBack.jpg b/bigbluebutton-client/src/branding/css/assets/img/BrushedMetalBack.jpg deleted file mode 100755 index 2db87165999edc4b00aa99f0387bb9bcf4a5e8fa..0000000000000000000000000000000000000000 Binary files a/bigbluebutton-client/src/branding/css/assets/img/BrushedMetalBack.jpg and /dev/null differ diff --git a/bigbluebutton-client/src/branding/css/assets/img/bk3.jpg b/bigbluebutton-client/src/branding/css/assets/img/bk3.jpg deleted file mode 100755 index f6c8ab242bc4e5a7dc4b589718cce54381e15d3d..0000000000000000000000000000000000000000 Binary files a/bigbluebutton-client/src/branding/css/assets/img/bk3.jpg and /dev/null differ diff --git a/bigbluebutton-client/src/branding/css/assets/img/closeButton.png b/bigbluebutton-client/src/branding/css/assets/img/closeButton.png deleted file mode 100755 index 82da8f0486f977671dbbcc99460018662b688dfc..0000000000000000000000000000000000000000 Binary files a/bigbluebutton-client/src/branding/css/assets/img/closeButton.png and /dev/null differ diff --git a/bigbluebutton-client/src/branding/css/assets/img/cornerResize3.png b/bigbluebutton-client/src/branding/css/assets/img/cornerResize3.png deleted file mode 100755 index c0e341f43f6ccdbb3cf9b4a59c52d53c936b3a8c..0000000000000000000000000000000000000000 Binary files a/bigbluebutton-client/src/branding/css/assets/img/cornerResize3.png and /dev/null differ diff --git a/bigbluebutton-client/src/branding/css/assets/img/decreaseButton.png b/bigbluebutton-client/src/branding/css/assets/img/decreaseButton.png deleted file mode 100755 index ae7f931fc0515a2886d7bf231e3901cc118b8c06..0000000000000000000000000000000000000000 Binary files a/bigbluebutton-client/src/branding/css/assets/img/decreaseButton.png and /dev/null differ diff --git a/bigbluebutton-client/src/branding/css/assets/img/increaseButton.png b/bigbluebutton-client/src/branding/css/assets/img/increaseButton.png deleted file mode 100755 index 620353fbe0085eec7b49a7224f458fcbcbcdca0c..0000000000000000000000000000000000000000 Binary files a/bigbluebutton-client/src/branding/css/assets/img/increaseButton.png and /dev/null differ diff --git a/bigbluebutton-client/src/branding/css/assets/img/menuArrow.png b/bigbluebutton-client/src/branding/css/assets/img/menuArrow.png deleted file mode 100755 index 38af93c846d284982cc59f1cd0388ea86cabb9ef..0000000000000000000000000000000000000000 Binary files a/bigbluebutton-client/src/branding/css/assets/img/menuArrow.png and /dev/null differ diff --git a/bigbluebutton-client/src/branding/css/assets/img/minimizeButton.png b/bigbluebutton-client/src/branding/css/assets/img/minimizeButton.png deleted file mode 100755 index 0178081ebf45ef277676f155f2f1d22ffe42c4cc..0000000000000000000000000000000000000000 Binary files a/bigbluebutton-client/src/branding/css/assets/img/minimizeButton.png and /dev/null differ diff --git a/bigbluebutton-client/src/branding/css/assets/img/resizeCursor.png b/bigbluebutton-client/src/branding/css/assets/img/resizeCursor.png deleted file mode 100755 index a64e80cd9d662d55b4ba969990488d2c6c6f4f35..0000000000000000000000000000000000000000 Binary files a/bigbluebutton-client/src/branding/css/assets/img/resizeCursor.png and /dev/null differ diff --git a/bigbluebutton-client/src/branding/css/assets/img/resizeCursorH.gif b/bigbluebutton-client/src/branding/css/assets/img/resizeCursorH.gif deleted file mode 100755 index daa8facfa135fa1b74afd29d11cd9785fc4350cd..0000000000000000000000000000000000000000 Binary files a/bigbluebutton-client/src/branding/css/assets/img/resizeCursorH.gif and /dev/null differ diff --git a/bigbluebutton-client/src/branding/css/assets/img/resizeCursorTLBR.gif b/bigbluebutton-client/src/branding/css/assets/img/resizeCursorTLBR.gif deleted file mode 100755 index d0c72562c0988afc47f96bde9cda32827d1ea8b3..0000000000000000000000000000000000000000 Binary files a/bigbluebutton-client/src/branding/css/assets/img/resizeCursorTLBR.gif and /dev/null differ diff --git a/bigbluebutton-client/src/branding/css/assets/img/resizeCursorTRBL.gif b/bigbluebutton-client/src/branding/css/assets/img/resizeCursorTRBL.gif deleted file mode 100755 index 442efa5d48cc5b5cf0e0d59a6a0d5df3939c1fa6..0000000000000000000000000000000000000000 Binary files a/bigbluebutton-client/src/branding/css/assets/img/resizeCursorTRBL.gif and /dev/null differ diff --git a/bigbluebutton-client/src/branding/css/assets/img/resizeCursorV.gif b/bigbluebutton-client/src/branding/css/assets/img/resizeCursorV.gif deleted file mode 100755 index 6061a32a9a85e43b6f25db8096189fb8600791ee..0000000000000000000000000000000000000000 Binary files a/bigbluebutton-client/src/branding/css/assets/img/resizeCursorV.gif and /dev/null differ diff --git a/bigbluebutton-client/src/branding/css/assets/img/resizeCursorVH.gif b/bigbluebutton-client/src/branding/css/assets/img/resizeCursorVH.gif deleted file mode 100755 index 7da81d6f379062077edbb7a1b241bd479363a312..0000000000000000000000000000000000000000 Binary files a/bigbluebutton-client/src/branding/css/assets/img/resizeCursorVH.gif and /dev/null differ diff --git a/bigbluebutton-client/src/branding/css/assets/img/wallpaperOS9.gif b/bigbluebutton-client/src/branding/css/assets/img/wallpaperOS9.gif deleted file mode 100755 index 23183806e7472cf62193ac2cf7b1cc4eb3bd67fa..0000000000000000000000000000000000000000 Binary files a/bigbluebutton-client/src/branding/css/assets/img/wallpaperOS9.gif and /dev/null differ diff --git a/bigbluebutton-client/src/branding/css/assets/img/wallpaperWin.jpg b/bigbluebutton-client/src/branding/css/assets/img/wallpaperWin.jpg deleted file mode 100755 index 5db85738abcbf140ef3a69e6f3e2f4d697705286..0000000000000000000000000000000000000000 Binary files a/bigbluebutton-client/src/branding/css/assets/img/wallpaperWin.jpg and /dev/null differ diff --git a/bigbluebutton-client/src/branding/css/logo_basic.css b/bigbluebutton-client/src/branding/css/logo_basic.css deleted file mode 100755 index 4c9dae3d7cbb07a53f08ca50e3d39a61016d5859..0000000000000000000000000000000000000000 --- a/bigbluebutton-client/src/branding/css/logo_basic.css +++ /dev/null @@ -1,6 +0,0 @@ -/*The image for your logo is 200x200 pixels. No other size is currently supported so resize your logo accordingly. The logo will always appear in the lower right corner. */ -BrandingLogo -{ - backgroundImage: Embed(source="assets/img/BBBLogo.png"); - backgroundSize: "100%"; -} \ No newline at end of file diff --git a/bigbluebutton-client/src/branding/css/theme.css b/bigbluebutton-client/src/branding/css/theme.css deleted file mode 100755 index 143ffc19c2c78d03e9821c76b6c2098f39b86911..0000000000000000000000000000000000000000 --- a/bigbluebutton-client/src/branding/css/theme.css +++ /dev/null @@ -1,180 +0,0 @@ -Application -{ - backgroundColors: #000000,#3f3f3f; - backgroundImage: Embed(source="assets/img/bk3.jpg"); - backgroundSize: "100%"; - backgroundGradientColors: #B4BFCF, #78829C; - themeColor: #0094C7; - backgroundGradientAlphas: 0.62, 0.26; -} - -/*The image for your logo is 200x200 pixels. No other size is currently supported so resize your logo accordingly. The logo will always appear in the lower right corner. */ -BrandingLogo -{ - backgroundImage: Embed(source="assets/img/BBBLogo.png"); - backgroundSize: "100%"; -} - - -MDICanvas -{ - backgroundColor: #0094C7; - backgroundAlpha: 0; -} - - -Button -{ - highlightAlphas: 1, 0.33; - fillAlphas: 1, 0.16, 0.18, 1; - fillColors: #5f89b9, #697182, #ffffff, #eeeeee; -} - - -.mdiWindowFocus -{ - headerHeight: 26; - roundedBottomCorners: true; - backgroundAlpha: 1; - backgroundColor: #FFFFFF; - backgroundImage: Embed(source="assets/swf/Blue.swf", symbol="Window_Background"); - backgroundSize: '100%'; - - borderStyle: solid; - borderColor: #ffffff; - borderAlpha: 1; - borderThickness: 2; - borderThicknessLeft: 0; - borderThicknessTop: 0; - borderThicknessBottom: 0; - borderThicknessRight: 0; - cornerRadius: 4; - dropShadowEnabled: false; - titleStyleName: "mypanelTitle"; - - cornerResizeImg: Embed(source="assets/swf/Blue.swf", symbol="Corner_Resize"); - cornerResizeWidth: 15; - cornerResizeHeight: 15; - cornerResizePaddingRight: 2; - cornerResizePaddingBottom: 2; - - controlButtonWidth: 10; - controlButtonHeight: 10; - controlButtonGap: 4; -} - -.mdiWindowNoFocus -{ - headerHeight: 26; - roundedBottomCorners: true; - backgroundAlpha: 0.5; - backgroundImage: Embed(source="assets/swf/Blue.swf", symbol="Window_Background"); - backgroundSize: '100%'; - - borderStyle: solid; - borderColor: #ffffff; - borderAlpha: 1; - borderThickness: 2; - borderThicknessLeft: 0; - borderThicknessTop: 0; - borderThicknessBottom: 0; - borderThicknessRight: 0; - cornerRadius: 4; - dropShadowEnabled: false; - titleStyleName: "mypanelTitle"; - - cornerResizeImg: Embed(source="assets/swf/Blue.swf", symbol="Corner_Resize"); - cornerResizeWidth: 15; - cornerResizeHeight: 15; - cornerResizePaddingRight: 2; - cornerResizePaddingBottom: 2; - - controlButtonWidth: 10; - controlButtonHeight: 10; - controlButtonGap: 4; -} - -.mypanelTitle -{ - fontFamily: Arial; - fontSize: 12; - fontWeight: bold; - color: #ffffff; -} - - -.closeBtnFocus -{ - upSkin: Embed('assets/img/3_closeButton.png'); - overSkin: Embed('assets/img/3_closeButton.png'); - downSkin: Embed('assets/img/3_closeButton.png'); - disabledSkin: Embed('assets/img/3_closeButton.png'); -} - -.closeBtnNoFocus -{ - upSkin: Embed('assets/img/3_closeButton.png'); - overSkin: Embed('assets/img/3_closeButton.png'); - downSkin: Embed('assets/img/3_closeButton.png'); - disabledSkin: Embed('assets/img/3_closeButton.png'); -} - - -.increaseBtnFocus -{ - upSkin: Embed('assets/img/3_increaseButton.png'); - overSkin: Embed('assets/img/3_increaseButton.png'); - downSkin: Embed('assets/img/3_increaseButton.png'); - disabledSkin: Embed('assets/img/3_increaseButton.png'); -} - -.increaseBtnNoFocus -{ - upSkin: Embed('assets/img/3_increaseButton.png'); - overSkin: Embed('assets/img/3_increaseButton.png'); - downSkin: Embed('assets/img/3_increaseButton.png'); - disabledSkin: Embed('assets/img/3_increaseButton.png'); -} - - -.decreaseBtnFocus -{ - upSkin: Embed('assets/img/3_decreaseButton.png'); - overSkin: Embed('assets/img/3_decreaseButton.png'); - downSkin: Embed('assets/img/3_decreaseButton.png'); - disabledSkin: Embed('assets/img/3_decreaseButton.png'); -} - -.decreaseBtnNoFocus -{ - upSkin: Embed('assets/img/3_decreaseButton.png'); - overSkin: Embed('assets/img/3_decreaseButton.png'); - downSkin: Embed('assets/img/3_decreaseButton.png'); - disabledSkin: Embed('assets/img/3_decreaseButton.png'); -} - - -.minimizeBtnFocus -{ - upSkin: Embed('assets/img/3_minimizeButton.png'); - overSkin: Embed('assets/img/3_minimizeButton.png'); - downSkin: Embed('assets/img/3_minimizeButton.png'); - disabledSkin: Embed('assets/img/3_minimizeButton.png'); -} - -.minimizeBtnNoFocus -{ - upSkin: Embed('assets/img/3_minimizeButton.png'); - overSkin: Embed('assets/img/3_minimizeButton.png'); - downSkin: Embed('assets/img/3_minimizeButton.png'); - disabledSkin: Embed('assets/img/3_minimizeButton.png'); -} - - -.resizeHndlr -{ - upSkin: Embed('assets/img/resizeHandler.png'); - overSkin: Embed('assets/img/resizeHandler.png'); - downSkin: Embed('assets/img/resizeHandler.png'); - disabledSkin: Embed('assets/img/resizeHandler.png'); -} \ No newline at end of file diff --git a/bigbluebutton-client/src/org/bigbluebutton/common/Images.as b/bigbluebutton-client/src/org/bigbluebutton/common/Images.as index 03dcd2c573e7c1347b84e94d8ed18e1f434047ac..01e67207782bfa2c90f92285283c5eec459a8339 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/common/Images.as +++ b/bigbluebutton-client/src/org/bigbluebutton/common/Images.as @@ -21,6 +21,33 @@ package org.bigbluebutton.common [Bindable] public class Images { + [Embed(source="assets/images/webcam_kickuser.png")] + public var webcam_kickuser:Class; + + [Embed(source="assets/images/webcam_make_presenter.png")] + public var webcam_make_presenter:Class; + + [Embed(source="assets/images/webcam_mute.png")] + public var webcam_mute:Class; + + [Embed(source="assets/images/webcam_private_chat.png")] + public var webcam_private_chat:Class; + + [Embed(source="assets/images/webcam_unmute.png")] + public var webcam_unmute:Class; + + [Embed(source="assets/images/vdoc_bg.jpg")] + public var video_dock_bg:Class; + + [Embed(source="assets/images/bandwidth.png")] + public var bandwidth:Class; + + [Embed(source="assets/images/statistics.png")] + public var stats:Class; + + [Embed(source="assets/images/avatar.png")] + public var avatar:Class; + [Embed(source="assets/images/sign-out.png")] public var signOutIcon:Class; @@ -91,7 +118,7 @@ package org.bigbluebutton.common public var sound:Class; [Embed(source="assets/images/cancel.png")] - public var cancel_user:Class; + public var cancel:Class; [Embed(source="assets/images/user_go.png")] public var eject_user:Class; @@ -108,19 +135,19 @@ package org.bigbluebutton.common [Embed(source="assets/images/shape_square.png")] public var square_icon:Class; - [Embed(source="assets/images/arrow_undo.png")] + [Embed(source="assets/images/undo.png")] public var undo_icon:Class; - [Embed(source="assets/images/hand_icon.png")] + [Embed(source="assets/images/hand.png")] public var hand_icon:Class; - [Embed(source="assets/images/scribble_icon.png")] + [Embed(source="assets/images/marker.png")] public var scribble_icon:Class; - [Embed(source="assets/images/text_icon.png")] + [Embed(source="assets/images/text.png")] public var text_icon:Class; - [Embed(source="assets/images/circle.png")] + [Embed(source="assets/images/ellipse.png")] public var circle_icon:Class; [Embed(source="assets/images/arrow_out.png")] @@ -144,7 +171,7 @@ package org.bigbluebutton.common [Embed(source="assets/images/table.png")] public var table:Class; - [Embed(source="assets/images/cancel.png")] + [Embed(source="assets/images/trash.png")] public var delete_icon:Class; [Embed(source="assets/images/arrow_right.png")] @@ -223,7 +250,7 @@ package org.bigbluebutton.common [Embed(source="assets/images/pointer_icon_small.png")] public var select_icon:Class; - [Embed(source="assets/images/triangle_icon.png")] + [Embed(source="assets/images/triangle.png")] public var triangle_icon:Class; [Embed(source="assets/images/text_background_icon.png")] diff --git a/bigbluebutton-client/src/org/bigbluebutton/common/assets/images/avatar.png b/bigbluebutton-client/src/org/bigbluebutton/common/assets/images/avatar.png new file mode 100644 index 0000000000000000000000000000000000000000..2e9f8151f4083aece663009ae40d9271104dbb41 Binary files /dev/null and b/bigbluebutton-client/src/org/bigbluebutton/common/assets/images/avatar.png differ diff --git a/bigbluebutton-client/src/org/bigbluebutton/common/assets/images/bandwidth.png b/bigbluebutton-client/src/org/bigbluebutton/common/assets/images/bandwidth.png new file mode 100755 index 0000000000000000000000000000000000000000..adb979ce5aa24832549312d1291605e4dfcc4cae Binary files /dev/null and b/bigbluebutton-client/src/org/bigbluebutton/common/assets/images/bandwidth.png differ diff --git a/bigbluebutton-client/src/org/bigbluebutton/common/assets/images/ellipse.png b/bigbluebutton-client/src/org/bigbluebutton/common/assets/images/ellipse.png new file mode 100755 index 0000000000000000000000000000000000000000..ce5466bfe0cdf9a697052b5ecf4939b3c59b95fe Binary files /dev/null and b/bigbluebutton-client/src/org/bigbluebutton/common/assets/images/ellipse.png differ diff --git a/bigbluebutton-client/src/org/bigbluebutton/common/assets/images/fit-to-screen.png b/bigbluebutton-client/src/org/bigbluebutton/common/assets/images/fit-to-screen.png new file mode 100755 index 0000000000000000000000000000000000000000..fe28c01d240b2ba38bc005ec4fc8af97f3248d62 Binary files /dev/null and b/bigbluebutton-client/src/org/bigbluebutton/common/assets/images/fit-to-screen.png differ diff --git a/bigbluebutton-client/src/org/bigbluebutton/common/assets/images/fit-to-width.png b/bigbluebutton-client/src/org/bigbluebutton/common/assets/images/fit-to-width.png index 39d6e1dd445af304d4e922288fe22a3f0d7946d9..f991166ed66ce39733208574c3a1bc7931d812be 100755 Binary files a/bigbluebutton-client/src/org/bigbluebutton/common/assets/images/fit-to-width.png and b/bigbluebutton-client/src/org/bigbluebutton/common/assets/images/fit-to-width.png differ diff --git a/bigbluebutton-client/src/org/bigbluebutton/common/assets/images/hand.png b/bigbluebutton-client/src/org/bigbluebutton/common/assets/images/hand.png new file mode 100755 index 0000000000000000000000000000000000000000..c6bcefaea0a1b7ef4950cfefcd58d7a588b58d57 Binary files /dev/null and b/bigbluebutton-client/src/org/bigbluebutton/common/assets/images/hand.png differ diff --git a/bigbluebutton-client/src/org/bigbluebutton/common/assets/images/left_arrow.png b/bigbluebutton-client/src/org/bigbluebutton/common/assets/images/left_arrow.png new file mode 100755 index 0000000000000000000000000000000000000000..3117136c1fda27fb27a057d835deb05a0a888e23 Binary files /dev/null and b/bigbluebutton-client/src/org/bigbluebutton/common/assets/images/left_arrow.png differ diff --git a/bigbluebutton-client/src/org/bigbluebutton/common/assets/images/line.png b/bigbluebutton-client/src/org/bigbluebutton/common/assets/images/line.png old mode 100644 new mode 100755 index f5413cb7bf9600800f1c5232fc52c21a17b57daf..e0abedeaaea9868fceee6038e75524d663cf883e Binary files a/bigbluebutton-client/src/org/bigbluebutton/common/assets/images/line.png and b/bigbluebutton-client/src/org/bigbluebutton/common/assets/images/line.png differ diff --git a/bigbluebutton-client/src/org/bigbluebutton/common/assets/images/marker.png b/bigbluebutton-client/src/org/bigbluebutton/common/assets/images/marker.png new file mode 100755 index 0000000000000000000000000000000000000000..7a026099e3685ceb87505f5b1292f48b714da566 Binary files /dev/null and b/bigbluebutton-client/src/org/bigbluebutton/common/assets/images/marker.png differ diff --git a/bigbluebutton-client/src/org/bigbluebutton/common/assets/images/righ-arrow.png b/bigbluebutton-client/src/org/bigbluebutton/common/assets/images/righ-arrow.png new file mode 100755 index 0000000000000000000000000000000000000000..f00775a7eab38053a23d662ed228e66cb25f4b56 Binary files /dev/null and b/bigbluebutton-client/src/org/bigbluebutton/common/assets/images/righ-arrow.png differ diff --git a/bigbluebutton-client/src/org/bigbluebutton/common/assets/images/square.png b/bigbluebutton-client/src/org/bigbluebutton/common/assets/images/square.png new file mode 100755 index 0000000000000000000000000000000000000000..bdceb2e4744dabf7b1352e115e41e58ca92c1946 Binary files /dev/null and b/bigbluebutton-client/src/org/bigbluebutton/common/assets/images/square.png differ diff --git a/bigbluebutton-client/src/org/bigbluebutton/common/assets/images/statistics.png b/bigbluebutton-client/src/org/bigbluebutton/common/assets/images/statistics.png new file mode 100755 index 0000000000000000000000000000000000000000..d4779d9d69e71375594045eeb070e9793fb897a6 Binary files /dev/null and b/bigbluebutton-client/src/org/bigbluebutton/common/assets/images/statistics.png differ diff --git a/bigbluebutton-client/src/org/bigbluebutton/common/assets/images/text.png b/bigbluebutton-client/src/org/bigbluebutton/common/assets/images/text.png new file mode 100755 index 0000000000000000000000000000000000000000..819b5803925d8254c7ced6199719aeac3ac1735d Binary files /dev/null and b/bigbluebutton-client/src/org/bigbluebutton/common/assets/images/text.png differ diff --git a/bigbluebutton-client/src/org/bigbluebutton/common/assets/images/trash.png b/bigbluebutton-client/src/org/bigbluebutton/common/assets/images/trash.png new file mode 100755 index 0000000000000000000000000000000000000000..2b95a3faa6ff838bff79a0c8e53a8a0c83bdb9e5 Binary files /dev/null and b/bigbluebutton-client/src/org/bigbluebutton/common/assets/images/trash.png differ diff --git a/bigbluebutton-client/src/org/bigbluebutton/common/assets/images/triangle.png b/bigbluebutton-client/src/org/bigbluebutton/common/assets/images/triangle.png new file mode 100755 index 0000000000000000000000000000000000000000..0530c3deb689c473880dc3fc22ddb692d766256f Binary files /dev/null and b/bigbluebutton-client/src/org/bigbluebutton/common/assets/images/triangle.png differ diff --git a/bigbluebutton-client/src/org/bigbluebutton/common/assets/images/undo.png b/bigbluebutton-client/src/org/bigbluebutton/common/assets/images/undo.png new file mode 100755 index 0000000000000000000000000000000000000000..43a7e19fc6cdedaec8349b4fabf34a0bf47e208d Binary files /dev/null and b/bigbluebutton-client/src/org/bigbluebutton/common/assets/images/undo.png differ diff --git a/bigbluebutton-client/src/org/bigbluebutton/common/assets/images/upload.png b/bigbluebutton-client/src/org/bigbluebutton/common/assets/images/upload.png index 36086b05258973bb5b2fdebf826fd32629858bae..030cc3e82aab53ff29cbc0dc4fbe30ee067177be 100755 Binary files a/bigbluebutton-client/src/org/bigbluebutton/common/assets/images/upload.png and b/bigbluebutton-client/src/org/bigbluebutton/common/assets/images/upload.png differ diff --git a/bigbluebutton-client/src/org/bigbluebutton/common/assets/images/vdoc_bg.jpg b/bigbluebutton-client/src/org/bigbluebutton/common/assets/images/vdoc_bg.jpg new file mode 100755 index 0000000000000000000000000000000000000000..0dfda438df84be7d57567944ff3a89f1f540900a Binary files /dev/null and b/bigbluebutton-client/src/org/bigbluebutton/common/assets/images/vdoc_bg.jpg differ diff --git a/bigbluebutton-client/src/org/bigbluebutton/common/assets/images/webcam_kickuser.png b/bigbluebutton-client/src/org/bigbluebutton/common/assets/images/webcam_kickuser.png new file mode 100755 index 0000000000000000000000000000000000000000..561edc8d9b00d9c97c9acd82b4bf121bc3a77426 Binary files /dev/null and b/bigbluebutton-client/src/org/bigbluebutton/common/assets/images/webcam_kickuser.png differ diff --git a/bigbluebutton-client/src/org/bigbluebutton/common/assets/images/webcam_make_presenter.png b/bigbluebutton-client/src/org/bigbluebutton/common/assets/images/webcam_make_presenter.png new file mode 100755 index 0000000000000000000000000000000000000000..e7cfff5e97381b5a88d0b96b35508e52e850960e Binary files /dev/null and b/bigbluebutton-client/src/org/bigbluebutton/common/assets/images/webcam_make_presenter.png differ diff --git a/bigbluebutton-client/src/org/bigbluebutton/common/assets/images/webcam_mute.png b/bigbluebutton-client/src/org/bigbluebutton/common/assets/images/webcam_mute.png new file mode 100755 index 0000000000000000000000000000000000000000..3be5bd19cf8cf09beb6c7c7758b21b5deabab69b Binary files /dev/null and b/bigbluebutton-client/src/org/bigbluebutton/common/assets/images/webcam_mute.png differ diff --git a/bigbluebutton-client/src/org/bigbluebutton/common/assets/images/webcam_private_chat.png b/bigbluebutton-client/src/org/bigbluebutton/common/assets/images/webcam_private_chat.png new file mode 100755 index 0000000000000000000000000000000000000000..9fdff988a3cfa2f6ef81f0a2d0de135dd199371e Binary files /dev/null and b/bigbluebutton-client/src/org/bigbluebutton/common/assets/images/webcam_private_chat.png differ diff --git a/bigbluebutton-client/src/org/bigbluebutton/common/assets/images/webcam_unmute.png b/bigbluebutton-client/src/org/bigbluebutton/common/assets/images/webcam_unmute.png new file mode 100755 index 0000000000000000000000000000000000000000..32a685732f0073772bb782662fe37e30a83d5220 Binary files /dev/null and b/bigbluebutton-client/src/org/bigbluebutton/common/assets/images/webcam_unmute.png differ diff --git a/bigbluebutton-client/src/org/bigbluebutton/core/BBB.as b/bigbluebutton-client/src/org/bigbluebutton/core/BBB.as index f2adcc2809a3dadea1bee3a54c6ddb9e6f4c3fbe..f1c4a9bbff96fc04db5261e9ac592f77c387d4fe 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/core/BBB.as +++ b/bigbluebutton-client/src/org/bigbluebutton/core/BBB.as @@ -2,7 +2,6 @@ package org.bigbluebutton.core { import org.bigbluebutton.core.managers.ConfigManager2; import org.bigbluebutton.core.managers.ConnectionManager; - import org.bigbluebutton.core.managers.StreamManager; import org.bigbluebutton.core.managers.UserConfigManager; import org.bigbluebutton.core.managers.UserManager; import org.bigbluebutton.core.model.Session; @@ -10,7 +9,6 @@ package org.bigbluebutton.core public class BBB { private static var configManager:ConfigManager2 = null; - private static var streamManager:StreamManager = null; private static var connectionManager:ConnectionManager = null; private static var session:Session = null; private static var userConfigManager:UserConfigManager = null; @@ -34,13 +32,6 @@ package org.bigbluebutton.core return initConfigManager().config.getConfigFor(module); } - public static function initStreamManager():StreamManager { - if (streamManager == null) { - streamManager = new StreamManager(); - } - return streamManager; - } - public static function initConnectionManager():ConnectionManager { if (connectionManager == null) { connectionManager = new ConnectionManager(); diff --git a/bigbluebutton-client/src/org/bigbluebutton/core/EventConstants.as b/bigbluebutton-client/src/org/bigbluebutton/core/EventConstants.as index 583d9e72eab4e766b6427f06aeae373df2b75cbf..0b377d81c48c09eb0b2bb91a3ef1e8d1b67b24db 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/core/EventConstants.as +++ b/bigbluebutton-client/src/org/bigbluebutton/core/EventConstants.as @@ -19,10 +19,15 @@ package org.bigbluebutton.core /** Events to External JS **/ public static const GET_MY_ROLE_RESP:String = 'GetMyRoleResponse'; - + public static const AM_I_PRESENTER_RESP:String = 'AmIPresenterQueryResponse'; + public static const AM_I_SHARING_CAM_RESP:String = 'AmISharingCamQueryResponse'; + public static const BROADCASTING_CAM_STARTED:String = 'BroadcastingCameraStartedEvent'; + public static const BROADCASTING_CAM_STOPPED:String = 'BroadcastingCameraStoppedEvent'; + public static const I_AM_SHARING_CAM:String = 'IAmSharingCamEvent'; + public static const CAM_STREAM_SHARED:String = 'CamStreamSharedEvent'; public static const USER_JOINED:String = 'UserJoinedEvent'; public static const USER_LEFT:String = 'UserLeftEvent'; - public static const NEW_ROLE:String = 'NewRoleEvent'; + public static const SWITCHED_PRESENTER:String = 'SwitchedPresenterEvent'; public static const NEW_PRIVATE_CHAT:String = 'NewPrivateChatEvent'; public static const NEW_PUBLIC_CHAT:String = 'NewPublicChatEvent'; public static const SWITCHED_LAYOUT:String = 'SwitchedLayoutEvent'; @@ -30,7 +35,9 @@ package org.bigbluebutton.core public static const REMOTE_UNLOCKED_LAYOUT:String = 'RemoteUnlockedLayoutEvent'; public static const USER_JOINED_VOICE:String = 'UserJoinedVoiceEvent'; public static const USER_LEFT_VOICE:String = 'UserLeftVoiceEvent'; + public static const USER_MUTED_VOICE:String = 'UserVoiceMutedEvent'; public static const USER_TALKING:String = 'UserTalkingEvent'; + public static const USER_LOCKED_VOICE:String = 'UserLockedVoiceEvent'; public static const START_PRIVATE_CHAT:String = "StartPrivateChatEvent"; } } \ No newline at end of file diff --git a/bigbluebutton-client/src/org/bigbluebutton/core/UsersUtil.as b/bigbluebutton-client/src/org/bigbluebutton/core/UsersUtil.as index e113ead8a341b1f57b3357dc77955332ab5caa16..21f2d71c8a096f1fc8233786eb0db320b72a9257 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/core/UsersUtil.as +++ b/bigbluebutton-client/src/org/bigbluebutton/core/UsersUtil.as @@ -1,11 +1,61 @@ package org.bigbluebutton.core { + import mx.collections.ArrayCollection; + import org.bigbluebutton.common.LogUtil; import org.bigbluebutton.core.managers.UserManager; + import org.bigbluebutton.core.vo.CameraSettingsVO; import org.bigbluebutton.main.model.users.BBBUser; public class UsersUtil { + + public static function amIPublishing():CameraSettingsVO { + return UserManager.getInstance().getConference().amIPublishing(); + } + + public static function setIAmPublishing(publishing:Boolean):void { + UserManager.getInstance().getConference().setCamPublishing(publishing); + } + + public static function setCameraSettings(camSettings:CameraSettingsVO):void { + UserManager.getInstance().getConference().setCameraSettings(camSettings); + } + + public static function hasWebcamStream(userID:String):Boolean { + var u:BBBUser = getUser(userID); + if (u != null) { + return u.hasStream; + } + + return false; + } + + public static function getWebcamStream(userID:String):String { + var u:BBBUser = getUser(userID); + if (u != null) { + return u.streamName; + } + + return null; + } + + public static function getUserIDs():ArrayCollection { + return UserManager.getInstance().getConference().getUserIDs(); + } + + public static function getInternalMeetingID():String { + return UserManager.getInstance().getConference().internalMeetingID; + } + + public static function getAvatarURL():String { + return UserManager.getInstance().getConference().avatarURL; + } + + public static function getExternalMeetingID():String { + return UserManager.getInstance().getConference().externalMeetingID; + } + public static function amIModerator():Boolean { return UserManager.getInstance().getConference().amIModerator(); } diff --git a/bigbluebutton-client/src/org/bigbluebutton/core/events/AmIPresenterQueryEvent.as b/bigbluebutton-client/src/org/bigbluebutton/core/events/AmIPresenterQueryEvent.as new file mode 100755 index 0000000000000000000000000000000000000000..d65ff7b132ba197dedba21aa55a9cae572b4b117 --- /dev/null +++ b/bigbluebutton-client/src/org/bigbluebutton/core/events/AmIPresenterQueryEvent.as @@ -0,0 +1,13 @@ +package org.bigbluebutton.core.events +{ + import flash.events.Event; + + public class AmIPresenterQueryEvent extends Event + { + public static const AM_I_PRESENTER_QUERY:String = "am I presenter query request"; + + public function AmIPresenterQueryEvent(bubbles:Boolean=true, cancelable:Boolean=false) { + super(AM_I_PRESENTER_QUERY, bubbles, cancelable); + } + } +} \ No newline at end of file diff --git a/bigbluebutton-client/src/org/bigbluebutton/core/events/AmISharingWebcamQueryEvent.as b/bigbluebutton-client/src/org/bigbluebutton/core/events/AmISharingWebcamQueryEvent.as new file mode 100755 index 0000000000000000000000000000000000000000..f4a11378a2671f29954841cef98938de4e9d0334 --- /dev/null +++ b/bigbluebutton-client/src/org/bigbluebutton/core/events/AmISharingWebcamQueryEvent.as @@ -0,0 +1,14 @@ +package org.bigbluebutton.core.events +{ + import flash.events.Event; + + public class AmISharingWebcamQueryEvent extends Event + { + public static const AM_I_SHARING_CAM_QUERY:String = "am I sharing cam query event"; + + public function AmISharingWebcamQueryEvent(bubbles:Boolean=true, cancelable:Boolean=false) + { + super(AM_I_SHARING_CAM_QUERY, bubbles, cancelable); + } + } +} \ No newline at end of file diff --git a/bigbluebutton-client/src/org/bigbluebutton/core/events/ConnectAppEvent.as b/bigbluebutton-client/src/org/bigbluebutton/core/events/ConnectAppEvent.as new file mode 100755 index 0000000000000000000000000000000000000000..b2fe39f1c987e5a68b64294867808cbce8f7d62d --- /dev/null +++ b/bigbluebutton-client/src/org/bigbluebutton/core/events/ConnectAppEvent.as @@ -0,0 +1,14 @@ +package org.bigbluebutton.core.events +{ + import flash.events.Event; + + public class ConnectAppEvent extends Event + { + public static const CONNECT_VIDEO_APP:String = "connect to video app event"; + + public function ConnectAppEvent(type:String, bubbles:Boolean=true, cancelable:Boolean=false) + { + super(type, bubbles, cancelable); + } + } +} \ No newline at end of file diff --git a/bigbluebutton-client/src/org/bigbluebutton/core/events/SwitchedLayoutEvent.as b/bigbluebutton-client/src/org/bigbluebutton/core/events/SwitchedLayoutEvent.as new file mode 100755 index 0000000000000000000000000000000000000000..6fb7915f0f16fdd4d4d9a6519a20d77ca2ab9127 --- /dev/null +++ b/bigbluebutton-client/src/org/bigbluebutton/core/events/SwitchedLayoutEvent.as @@ -0,0 +1,16 @@ +package org.bigbluebutton.core.events +{ + import flash.events.Event; + + public class SwitchedLayoutEvent extends Event + { + public static const SWITCHED_LAYOUT_EVENT:String = "switched layout event"; + + public var layoutID:String; + + public function SwitchedLayoutEvent(bubbles:Boolean=true, cancelable:Boolean=false) + { + super(SWITCHED_LAYOUT_EVENT, bubbles, cancelable); + } + } +} \ No newline at end of file diff --git a/bigbluebutton-client/src/org/bigbluebutton/core/managers/StreamManager.as b/bigbluebutton-client/src/org/bigbluebutton/core/managers/StreamManager.as deleted file mode 100755 index e262210f356fd9138c0bbc73c516f9013ecd4408..0000000000000000000000000000000000000000 --- a/bigbluebutton-client/src/org/bigbluebutton/core/managers/StreamManager.as +++ /dev/null @@ -1,10 +0,0 @@ -package org.bigbluebutton.core.managers -{ - public class StreamManager - { - public function StreamManager() - { - } - - } -} \ No newline at end of file diff --git a/bigbluebutton-client/src/org/bigbluebutton/core/services/BandwidthMonitor.as b/bigbluebutton-client/src/org/bigbluebutton/core/services/BandwidthMonitor.as new file mode 100755 index 0000000000000000000000000000000000000000..c8aef8314d56b9765e63d182c9ee65fe3fe70dba --- /dev/null +++ b/bigbluebutton-client/src/org/bigbluebutton/core/services/BandwidthMonitor.as @@ -0,0 +1,145 @@ +package org.bigbluebutton.core.services +{ + import flash.events.AsyncErrorEvent; + import flash.events.NetStatusEvent; + import flash.events.TimerEvent; + import flash.net.NetConnection; + import flash.utils.Timer; + + import mx.utils.ObjectUtil; + + import org.bigbluebutton.common.LogUtil; + import org.bigbluebutton.main.model.NetworkStatsData; + + import org.red5.flash.bwcheck.ClientServerBandwidth; + import org.red5.flash.bwcheck.ServerClientBandwidth; + import org.red5.flash.bwcheck.events.BandwidthDetectEvent; + + public class BandwidthMonitor { + private var _serverURL:String = "localhost"; + private var _serverApplication:String = "video"; + private var _clientServerService:String = "checkBandwidthUp"; + private var _serverClientService:String = "checkBandwidth"; + private var nc:NetConnection; + + private var bwTestTimer:Timer; + + public function BandwidthMonitor() { + + } + + public function set serverURL(url:String):void { + _serverURL = url; + } + + public function set serverApplication(app:String):void { + _serverApplication = app; + } + + public function start():void { + connect(); + } + + private function connect():void { + nc = new NetConnection(); + nc.objectEncoding = flash.net.ObjectEncoding.AMF0; + nc.client = this; + nc.addEventListener(NetStatusEvent.NET_STATUS, onStatus); + nc.addEventListener(AsyncErrorEvent.ASYNC_ERROR, onAsyncError); + nc.connect("rtmp://" + _serverURL + "/" + _serverApplication); + } + + private function onAsyncError(event:AsyncErrorEvent):void + { + LogUtil.debug(event.error.toString()); + } + + private function onStatus(event:NetStatusEvent):void + { + switch (event.info.code) + { + case "NetConnection.Connect.Success": + LogUtil.debug("Starting to monitor bandwidth between client and server"); + monitor(); + break; + default: + LogUtil.debug("Cannot establish the connection to measure bandwidth"); + break; + } + } + + private function monitor():void { + LogUtil.debug("Starting to monitor bandwidth"); + bwTestTimer = new Timer(30000); + bwTestTimer.addEventListener(TimerEvent.TIMER, rtmptRetryTimerHandler); + bwTestTimer.start(); + } + + private function rtmptRetryTimerHandler(event:TimerEvent):void { + LogUtil.debug("Starting to detect bandwidth from server to client"); + ServerClient(); + } + + public function ClientServer():void + { + var clientServer:ClientServerBandwidth = new ClientServerBandwidth(); + //connect(); + clientServer.connection = nc; + clientServer.service = _clientServerService; + clientServer.addEventListener(BandwidthDetectEvent.DETECT_COMPLETE,onClientServerComplete); + clientServer.addEventListener(BandwidthDetectEvent.DETECT_STATUS,onClientServerStatus); + clientServer.addEventListener(BandwidthDetectEvent.DETECT_FAILED,onDetectFailed); + clientServer.start(); + } + + public function ServerClient():void + { + var serverClient:ServerClientBandwidth = new ServerClientBandwidth(); + //connect(); + serverClient.connection = nc; + serverClient.service = _serverClientService; + serverClient.addEventListener(BandwidthDetectEvent.DETECT_COMPLETE,onServerClientComplete); + serverClient.addEventListener(BandwidthDetectEvent.DETECT_STATUS,onServerClientStatus); + serverClient.addEventListener(BandwidthDetectEvent.DETECT_FAILED,onDetectFailed); + serverClient.start(); + } + + public function onDetectFailed(event:BandwidthDetectEvent):void + { + LogUtil.debug("Detection failed with error: " + event.info.application + " " + event.info.description); + } + + public function onClientServerComplete(event:BandwidthDetectEvent):void + { +// LogUtil.debug("Client-slient bandwidth detect complete"); + +// LogUtil.debug(ObjectUtil.toString(event.info)); + NetworkStatsData.getInstance().setUploadMeasuredBW(event.info); + } + + public function onClientServerStatus(event:BandwidthDetectEvent):void + { + if (event.info) { +// LogUtil.debug("\n count: "+event.info.count+ " sent: "+event.info.sent+" timePassed: "+event.info.timePassed+" latency: "+event.info.latency+" overhead: "+event.info.overhead+" packet interval: " + event.info.pakInterval + " cumLatency: " + event.info.cumLatency); + } + } + + public function onServerClientComplete(event:BandwidthDetectEvent):void + { +// LogUtil.debug("Server-client bandwidth detect complete"); + +// LogUtil.debug(ObjectUtil.toString(event.info)); + NetworkStatsData.getInstance().setDownloadMeasuredBW(event.info); + +// LogUtil.debug("Detecting Client Server Bandwidth"); + ClientServer(); + } + + public function onServerClientStatus(event:BandwidthDetectEvent):void + { + if (event.info) { +// LogUtil.debug("\n count: "+event.info.count+ " sent: "+event.info.sent+" timePassed: "+event.info.timePassed+" latency: "+event.info.latency+" cumLatency: " + event.info.cumLatency); + } + } + } +} \ No newline at end of file diff --git a/bigbluebutton-client/src/org/bigbluebutton/core/services/StreamMonitor.as b/bigbluebutton-client/src/org/bigbluebutton/core/services/StreamMonitor.as new file mode 100755 index 0000000000000000000000000000000000000000..b9d198643874a68de4105c73dc39ab05072918f7 --- /dev/null +++ b/bigbluebutton-client/src/org/bigbluebutton/core/services/StreamMonitor.as @@ -0,0 +1,212 @@ +package org.bigbluebutton.core.services +{ + import com.asfusion.mate.events.Dispatcher; + + import flash.events.NetDataEvent; + import flash.events.NetMonitorEvent; + import flash.events.NetStatusEvent; + import flash.events.StatusEvent; + import flash.events.TimerEvent; + import flash.net.NetMonitor; + import flash.net.NetStream; + import flash.utils.Dictionary; + import flash.utils.Timer; + import flash.utils.getDefinitionByName; + import flash.utils.getQualifiedClassName; + + import org.bigbluebutton.common.LogUtil; + import org.bigbluebutton.main.events.NetworkStatsEvent; + import org.bigbluebutton.main.model.NetworkStatsData; + + public class StreamMonitor + { + /** + * https://github.com/ritzalam/red5-bw-check + * http://help.adobe.com/en_US/as3/dev/WS901d38e593cd1bac-1201e73713000d1f624-8000.html + * http://www.adobe.com/devnet/video/articles/media-measurement-flash.html + * http://help.adobe.com/en_US/air/reference/html/flash/net/NetMonitor.html + * http://help.adobe.com/en_US/flashmediaserver/devguide/WSae44d1d92c7021ff-1f5381712889cd7b56-8000.html#WSae44d1d92c7021ff-1f5381712889cd7b56-7ff7 + * http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/net/NetStreamInfo.html + * http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/net/NetStream.html + * http://help.adobe.com/en_US/FlashMediaServer/3.5_Deving/WS5b3ccc516d4fbf351e63e3d11a0773d56e-7ffa.html + * https://groups.google.com/d/topic/red5interest/PiUDeH6jZBQ/discussion + * http://flowplayer.electroteque.org/bwcheck + * http://code.google.com/p/flowplayer-plugins/ + * http://osflash.org/pipermail/red5_osflash.org/2009-January/028906.html + */ + + private var _netmon:NetMonitor; + private var _heartbeat:Timer = new Timer( 2000 ); + private var _globalDispatcher:Dispatcher = new Dispatcher(); + private var _totalBytesCounter:Dictionary = new Dictionary(); + + public function StreamMonitor():void { + //Create NetMonitor object + _netmon = new NetMonitor(); + _netmon.addEventListener( NetMonitorEvent.NET_STREAM_CREATE, newNetStream ); + + //Start the heartbeat timer + _heartbeat.addEventListener( TimerEvent.TIMER, onHeartbeat ); + _heartbeat.start(); + } + + //On new NetStream + private function newNetStream(event:NetMonitorEvent):void + { + log("New Netstream object: " + event); + var stream:NetStream = event.netStream; + log("Stream info: " + stream.info); + stream.addEventListener(NetDataEvent.MEDIA_TYPE_DATA, onStreamData); + stream.addEventListener(NetStatusEvent.NET_STATUS, onNetStatus); + stream.addEventListener(StatusEvent.STATUS, onStatus); + } + + //On data events from a NetStream object + private function onStreamData(event:NetDataEvent):void { + var netStream:NetStream = event.target as NetStream; +// log("Data event from " + netStream.info.uri + " at " + event.timestamp); + log("Data event: " + event); + switch(event.info.handler) { + case "onMetaData": + //handle metadata; + break; + case "onXMPData": + //handle XMP; + break; + case "onPlayStatus": + //handle NetStream.Play.Complete + break; + case "onImageData": + //handle image + break; + case "onTextData": + //handle text + break; + default: + //handle other events + break; + } + } + + //On status events from a NetStream object + private function onNetStatus(event:NetStatusEvent):void { +// log("Status event from " + event.target.info.uri + " at " + event.target.time); + log("NetStatus event code: " + event.info.code); + log("NetStatus event details: " + event.info.details); + log("Stream info: " + event.target.info); + } + + private function onStatus(event:StatusEvent):void { + log("Status event code: " + event.code); + log("Status event: " + event.target); + log("Stream info: " + event.currentTarget); + } + + private function isRemoteStream(stream:NetStream):Boolean { + return (stream != null && stream.info != null && stream.info.resourceName != null); + } + + //On heartbeat timer + private function onHeartbeat(event:TimerEvent):void { + var streams:Vector.<NetStream> = _netmon.listStreams(); + + var download:Dictionary = new Dictionary(); + var upload:Dictionary = new Dictionary(); + + for (var i:int = 0; i < streams.length; i++) { + if (streams[i] == null || streams[i].info == null) { + // stream info is null, returning + continue; + } + +// log("Heartbeat on " + streams[i].info); + + var remote:Boolean = isRemoteStream(streams[i]); + var ref:Dictionary = (remote? download: upload); + + if (streams[i].info.uri == null) { + log("Stream URI is null, returning"); + continue; + } + var uri:String = streams[i].info.uri.toLowerCase(); + var pattern1:RegExp = /(?P<protocol>.+):\/\/(?P<server>.+)\/(?P<app>.+)\/(?P<meeting>.+)/; + var pattern2:RegExp = /(?P<protocol>.+):\/\/(?P<server>.+)\/(?P<app>.+)/; + var result:Array; + var protocol:String = "", server:String = "", app:String = "", meeting:String = ""; + if (uri.match(pattern1)) { + result = pattern1.exec(uri); + protocol = result.protocol; + server = result.server; + app = result.app; + meeting = result.meeting; + } else if (uri.match(pattern2)) { + result = pattern2.exec(uri); + protocol = result.protocol; + server = result.server; + app = result.app; + } else { + log("***** NO MATCH *****"); + } + + var props:XMLList = flash.utils.describeType(streams[i].info)..accessor; + + for each (var s:XML in props) { + //log(s + ": " + streams[i].info[s]); + if (s.@type == "Number") { + var property:String = s.@name; + var num:Number = 0; + if (ref.hasOwnProperty(property)) + num = ref[property] as Number; + num += (streams[i].info[property] as Number); + ref[property] = num; + } + } + var streamName:String = app + "/" + (remote? streams[i].info.resourceName: "local"); + var streamsName:String = (ref.hasOwnProperty("streams")? ref["streams"] + ";":"") + streamName; + ref["streams"] = streamsName; + + var totalReg:Object = new Object; + totalReg.streamName = streamName; + totalReg.remote = remote; + totalReg.byteCount = streams[i].info["byteCount"]; + if (_totalBytesCounter.hasOwnProperty(streamName) && _totalBytesCounter[streamName].byteCount > totalReg.byteCount) { + var curTime:Number = new Date().getTime(); + var newStreamName:String = streamName + "_" + curTime; + _totalBytesCounter[streamName].streamName = newStreamName; + _totalBytesCounter[newStreamName] = _totalBytesCounter[streamName]; + delete _totalBytesCounter[streamName]; + } + _totalBytesCounter[streamName] = totalReg; + } + + download["byteCount"] = upload["byteCount"] = 0; + for each (var value:Object in _totalBytesCounter) { + if (value.remote) + download["byteCount"] += value.byteCount; + else + upload["byteCount"] += value.byteCount; + //log(value.streamName + ": " + value.byteCount); + } + +// var netstatsEvent:NetworkStatsEvent = new NetworkStatsEvent(); +// netstatsEvent.downloadStats = download; +// netstatsEvent.uploadStats = upload; +// _globalDispatcher.dispatchEvent(netstatsEvent); + + NetworkStatsData.getInstance().updateConsumedBW(download["currentBytesPerSecond"], + upload["currentBytesPerSecond"], + download["byteCount"], + upload["byteCount"]); + } + + static public function printDictionary(dict:Dictionary):void { + for (var key:Object in dict) { + LogUtil.debug(key + ": " + dict[key]); + } + } + + private function log(s:String):void { + LogUtil.debug("[StreamMonitor] " + s); + } + } +} diff --git a/bigbluebutton-client/src/org/bigbluebutton/core/vo/CameraSettingsVO.as b/bigbluebutton-client/src/org/bigbluebutton/core/vo/CameraSettingsVO.as new file mode 100755 index 0000000000000000000000000000000000000000..0afa3d1a73a038b1fe10161850c873736f6ab4fc --- /dev/null +++ b/bigbluebutton-client/src/org/bigbluebutton/core/vo/CameraSettingsVO.as @@ -0,0 +1,11 @@ +package org.bigbluebutton.core.vo +{ + public class CameraSettingsVO + { + public var camIndex:int; + public var camWidth:int; + public var camHeight:int; + public var isPublishing:Boolean = false; + + } +} \ No newline at end of file diff --git a/bigbluebutton-client/src/org/bigbluebutton/main/api/ExternalApiCallbacks.as b/bigbluebutton-client/src/org/bigbluebutton/main/api/ExternalApiCallbacks.as index 5437bb449fc7e5bcaa4bcba033f1e9d3518b5e17..3c48c2662fbd56239615e3ad91810a362e1a4050 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/main/api/ExternalApiCallbacks.as +++ b/bigbluebutton-client/src/org/bigbluebutton/main/api/ExternalApiCallbacks.as @@ -9,18 +9,22 @@ package org.bigbluebutton.main.api import org.bigbluebutton.common.LogUtil; import org.bigbluebutton.core.EventConstants; import org.bigbluebutton.core.UsersUtil; + import org.bigbluebutton.core.events.AmIPresenterQueryEvent; + import org.bigbluebutton.core.events.AmISharingWebcamQueryEvent; import org.bigbluebutton.core.events.CoreEvent; import org.bigbluebutton.core.managers.UserManager; + import org.bigbluebutton.core.vo.CameraSettingsVO; import org.bigbluebutton.main.events.BBBEvent; + import org.bigbluebutton.main.model.users.events.RoleChangeEvent; import org.bigbluebutton.modules.listeners.events.ListenersCommand; + import org.bigbluebutton.modules.videoconf.events.ClosePublishWindowEvent; import org.bigbluebutton.modules.videoconf.events.ShareCameraRequestEvent; public class ExternalApiCallbacks { private var _dispatcher:Dispatcher; - public function ExternalApiCallbacks() - { + public function ExternalApiCallbacks() { _dispatcher = new Dispatcher(); init(); @@ -28,21 +32,77 @@ package org.bigbluebutton.main.api private function init():void { if (ExternalInterface.available) { + ExternalInterface.addCallback("switchPresenterRequest", handleSwitchPresenterRequest); + ExternalInterface.addCallback("getMyUserID", handleGetMyUserID); + ExternalInterface.addCallback("getExternalMeetingID", handleGetExternalMeetingID); ExternalInterface.addCallback("joinVoiceRequest", handleJoinVoiceRequest); + ExternalInterface.addCallback("leaveVoiceRequest", handleLeaveVoiceRequest); ExternalInterface.addCallback("getMyRoleRequestSync", handleGetMyRoleRequestSync); ExternalInterface.addCallback("getMyRoleRequestAsync", handleGetMyRoleRequestAsynch); + ExternalInterface.addCallback("amIPresenterRequestSync", handleAmIPresenterRequestSync); + ExternalInterface.addCallback("amIPresenterRequestAsync", handleAmIPresenterRequestAsync); + ExternalInterface.addCallback("amISharingCameraRequestSync", handleAmISharingCameraRequestSync); + ExternalInterface.addCallback("amISharingCameraRequestAsync", handleAmISharingCameraRequestAsync); ExternalInterface.addCallback("muteMeRequest", handleMuteMeRequest); ExternalInterface.addCallback("unmuteMeRequest", handleUnmuteMeRequest); ExternalInterface.addCallback("muteAllUsersRequest", handleMuteAllUsersRequest); ExternalInterface.addCallback("unmuteAllUsersRequest", handleUnmuteAllUsersRequest); ExternalInterface.addCallback("shareVideoCamera", onShareVideoCamera); + ExternalInterface.addCallback("stopShareCameraRequest", handleStopShareCameraRequest); ExternalInterface.addCallback("switchLayout", handleSwitchLayoutRequest); ExternalInterface.addCallback("sendPublicChatRequest", handleSendPublicChatRequest); ExternalInterface.addCallback("sendPrivateChatRequest", handleSendPrivateChatRequest); ExternalInterface.addCallback("lockLayout", handleSendLockLayoutRequest); } } - + + + private function handleAmISharingCameraRequestSync():Object { + var obj:Object = new Object(); + var camSettings:CameraSettingsVO = UsersUtil.amIPublishing(); + obj.isPublishing = camSettings.isPublishing; + obj.camIndex = camSettings.camIndex; + obj.camWidth = camSettings.camWidth; + obj.camHeight = camSettings.camHeight; + + return obj; + } + + private function handleAmISharingCameraRequestAsync():void { + _dispatcher.dispatchEvent(new AmISharingWebcamQueryEvent()); + } + + + private function handleAmIPresenterRequestSync():Boolean { + return UsersUtil.amIPresenter(); + } + + private function handleAmIPresenterRequestAsync():void { + _dispatcher.dispatchEvent(new AmIPresenterQueryEvent()); + } + + private function handleStopShareCameraRequest():void { + _dispatcher.dispatchEvent(new ClosePublishWindowEvent()); + } + + private function handleSwitchPresenterRequest(userID:String):void { + var intUserID:String = UsersUtil.externalUserIDToInternalUserID(userID); + trace("Switching presenter to [" + intUserID + "] [" + UsersUtil.getUserName(intUserID) + "]"); + + var e:RoleChangeEvent = new RoleChangeEvent(RoleChangeEvent.ASSIGN_PRESENTER); + e.userid = intUserID; + e.username = UsersUtil.getUserName(intUserID); + _dispatcher.dispatchEvent(e); + } + + private function handleGetMyUserID():String { + return UsersUtil.internalUserIDToExternalUserID(UsersUtil.getMyUserID()); + } + + private function handleGetExternalMeetingID():String { + return UserManager.getInstance().getConference().externalMeetingID; + } + private function handleSendLockLayoutRequest(lock:Boolean):void { if (lock) { var lockEvent:CoreEvent = new CoreEvent(EventConstants.LOCK_LAYOUT_REQ); @@ -65,7 +125,7 @@ package org.bigbluebutton.main.api * */ private function handleSendPublicChatRequest(fontColor:String, localeLang:String, message:String):void { - LogUtil.debug("handleSendPublicChatRequest"); + trace("handleSendPublicChatRequest"); var chatEvent:CoreEvent = new CoreEvent(EventConstants.SEND_PUBLIC_CHAT_REQ); var payload:Object = new Object(); payload.fromColor = fontColor; @@ -157,18 +217,29 @@ package org.bigbluebutton.main.api } private function handleGetMyRoleRequestAsynch():void { - LogUtil.debug("handleGetMyRoleRequestAsynch"); + trace("handleGetMyRoleRequestAsynch"); _dispatcher.dispatchEvent(new CoreEvent(EventConstants.GET_MY_ROLE_REQ)); } private function handleJoinVoiceRequest():void { - LogUtil.debug("handleJoinVoiceRequest"); - _dispatcher.dispatchEvent(new BBBEvent(BBBEvent.JOIN_VOICE_CONFERENCE)); + trace("handleJoinVoiceRequest"); + var joinEvent:BBBEvent = new BBBEvent("JOIN_VOICE_CONFERENCE_EVENT"); + joinEvent.payload['useMicrophone'] = true; + _dispatcher.dispatchEvent(joinEvent); } - private function onShareVideoCamera():void { - LogUtil.debug("Sharing webcam"); - _dispatcher.dispatchEvent(new ShareCameraRequestEvent()); + private function handleLeaveVoiceRequest():void { + var leaveEvent:BBBEvent = new BBBEvent("LEAVE_VOICE_CONFERENCE_EVENT"); + leaveEvent.payload["userRequested"] = true; + _dispatcher.dispatchEvent(leaveEvent); + } + + private function onShareVideoCamera(publishInClient:Boolean=true):void { + trace("Sharing webcam: publishInClient = [" + publishInClient + "]"); + var event:ShareCameraRequestEvent = new ShareCameraRequestEvent(); + event.publishInClient = publishInClient; + + _dispatcher.dispatchEvent(event); } } diff --git a/bigbluebutton-client/src/org/bigbluebutton/main/api/ExternalApiCalls.as b/bigbluebutton-client/src/org/bigbluebutton/main/api/ExternalApiCalls.as index c7f4b2c02ab59a29612bf41910b2651bce38c98a..b65229f8ae00245e7e187becc984e68bdff8b083 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/main/api/ExternalApiCalls.as +++ b/bigbluebutton-client/src/org/bigbluebutton/main/api/ExternalApiCalls.as @@ -5,16 +5,94 @@ package org.bigbluebutton.main.api import org.bigbluebutton.common.LogUtil; import org.bigbluebutton.core.EventConstants; import org.bigbluebutton.core.UsersUtil; + import org.bigbluebutton.core.events.AmIPresenterQueryEvent; + import org.bigbluebutton.core.events.AmISharingWebcamQueryEvent; import org.bigbluebutton.core.events.CoreEvent; + import org.bigbluebutton.core.events.SwitchedLayoutEvent; import org.bigbluebutton.core.managers.UserManager; - import org.bigbluebutton.main.events.ParticipantJoinEvent; + import org.bigbluebutton.core.vo.CameraSettingsVO; + import org.bigbluebutton.main.events.BBBEvent; + import org.bigbluebutton.main.events.UserJoinedEvent; + import org.bigbluebutton.main.events.UserLeftEvent; import org.bigbluebutton.main.model.users.BBBUser; + import org.bigbluebutton.main.model.users.events.BroadcastStartedEvent; + import org.bigbluebutton.main.model.users.events.BroadcastStoppedEvent; + import org.bigbluebutton.main.model.users.events.StreamStartedEvent; + import org.bigbluebutton.modules.videoconf.model.VideoConfOptions; - public class ExternalApiCalls - { + + public class ExternalApiCalls { + + public function handleSwitchedLayoutEvent(event:SwitchedLayoutEvent):void { + var payload:Object = new Object(); + payload.eventName = EventConstants.SWITCHED_LAYOUT; + payload.layoutID = event.layoutID; + + broadcastEvent(payload); + } + + public function handleStreamStartedEvent(event:StreamStartedEvent):void { + var vidConf:VideoConfOptions = new VideoConfOptions(); + + var payload:Object = new Object(); + payload.eventName = EventConstants.CAM_STREAM_SHARED; + payload.userID = UsersUtil.internalUserIDToExternalUserID(event.userID); + payload.uri = vidConf.uri + "/" + UsersUtil.getInternalMeetingID(); + payload.streamName = event.stream; + + broadcastEvent(payload); + } + + public function handleBroadcastStoppedEvent(event:BroadcastStoppedEvent):void { + var payload:Object = new Object(); + payload.eventName = EventConstants.BROADCASTING_CAM_STOPPED; + broadcastEvent(payload); + } + + public function handleBroadcastStartedEvent(event:BroadcastStartedEvent):void { + var vidConf:VideoConfOptions = new VideoConfOptions(); + + var payload:Object = new Object(); + payload.eventName = EventConstants.BROADCASTING_CAM_STARTED; + payload.isPresenter = UsersUtil.amIPresenter(); + payload.streamName = event.stream; + payload.isPublishing = event.camSettings.isPublishing; + payload.camIndex = event.camSettings.camIndex; + payload.camWidth = event.camSettings.camWidth; + payload.camHeight = event.camSettings.camHeight; + payload.camKeyFrameInterval = vidConf.camKeyFrameInterval; + payload.camModeFps = vidConf.camModeFps; + payload.camQualityBandwidth = vidConf.camQualityBandwidth; + payload.camQualityPicture = vidConf.camQualityPicture; + + broadcastEvent(payload); + } + + public function handleAmISharingCamQueryEvent(event:AmISharingWebcamQueryEvent):void { + var camSettings:CameraSettingsVO = UsersUtil.amIPublishing(); + + var payload:Object = new Object(); + payload.eventName = EventConstants.AM_I_SHARING_CAM_RESP; + payload.isPublishing = camSettings.isPublishing; + payload.camIndex = camSettings.camIndex; + payload.camWidth = camSettings.camWidth; + payload.camHeight = camSettings.camHeight; + + broadcastEvent(payload); + } + + + public function handleAmIPresenterQueryEvent(event:AmIPresenterQueryEvent):void { + var payload:Object = new Object(); + payload.eventName = EventConstants.AM_I_PRESENTER_RESP; + payload.amIPresenter = UsersUtil.amIPresenter(); + broadcastEvent(payload); + } + public function handleSwitchToNewRoleEvent(event:CoreEvent):void { + trace("Got NEW ROLE EVENT role = [" + event.message.role + "]"); var payload:Object = new Object(); - payload.eventName = EventConstants.NEW_ROLE; + payload.eventName = EventConstants.SWITCHED_PRESENTER; payload.role = event.message.role; broadcastEvent(payload); } @@ -32,20 +110,47 @@ package org.bigbluebutton.main.api payload.myRole = event.message.myRole; broadcastEvent(payload); } - - public function handleUserJoinedVoiceEvent():void { + + public function handleUserJoinedVoiceEvent(event:BBBEvent):void { var payload:Object = new Object(); payload.eventName = EventConstants.USER_JOINED_VOICE; + payload.userID = UsersUtil.internalUserIDToExternalUserID(event.payload.userID); + + trace("Notifying external API that [" + UsersUtil.getUserName(event.payload.userID) + "] has joined the voice conference."); + broadcastEvent(payload); } - public function handleSwitchedLayoutEvent(layoutID:String):void { + public function handleUserVoiceMutedEvent(event:BBBEvent):void { var payload:Object = new Object(); - payload.eventName = EventConstants.SWITCHED_LAYOUT; - payload.layoutID = layoutID; + payload.eventName = EventConstants.USER_MUTED_VOICE; + payload.userID = UsersUtil.internalUserIDToExternalUserID(event.payload.userID); + payload.muted = event.payload.muted; + + trace("Notifying external API that [" + UsersUtil.getUserName(event.payload.userID) + "] is now muted=[" + payload.muted + "]"); broadcastEvent(payload); } - + + public function handleUserVoiceLockedEvent(event:BBBEvent):void { + var payload:Object = new Object(); + payload.eventName = EventConstants.USER_LOCKED_VOICE; + payload.userID = UsersUtil.internalUserIDToExternalUserID(event.payload.userID); + payload.locked = event.payload.locked; + + trace("Notifying external API that [" + UsersUtil.getUserName(event.payload.userID) + "] is now locked=[" + payload.locked + "]"); + broadcastEvent(payload); + } + + public function handleUserVoiceLeftEvent(event:BBBEvent):void { + var payload:Object = new Object(); + payload.eventName = EventConstants.USER_LEFT_VOICE; + payload.userID = UsersUtil.internalUserIDToExternalUserID(event.payload.userID); + + trace("Notifying external API that [" + UsersUtil.getUserName(event.payload.userID) + "] has left the voice conference."); + + broadcastEvent(payload); + } + public function handleNewPublicChatEvent(event:CoreEvent):void { var payload:Object = new Object(); payload.eventName = EventConstants.NEW_PUBLIC_CHAT; @@ -84,7 +189,7 @@ package org.bigbluebutton.main.api broadcastEvent(payload); } - public function handleParticipantJoinEvent(event:ParticipantJoinEvent):void { + public function handleUserJoinedEvent(event:UserJoinedEvent):void { var payload:Object = new Object(); var user:BBBUser = UserManager.getInstance().getConference().getUser(event.userID); @@ -93,17 +198,27 @@ package org.bigbluebutton.main.api return; } - if (event.join) { - payload.eventName = EventConstants.USER_JOINED; - payload.userID = user.userID; - payload.userName = user.name; - } else { - payload.eventName = EventConstants.USER_LEFT; - payload.userID = user.userID; - } + payload.eventName = EventConstants.USER_JOINED; + payload.userID = user.userID; + payload.userName = user.name; broadcastEvent(payload); } + + public function handleUserLeftEvent(event:UserLeftEvent):void { + var payload:Object = new Object(); + var user:BBBUser = UserManager.getInstance().getConference().getUser(event.userID); + + if (user == null) { + LogUtil.warn("[ExternalApiCall:handleParticipantJoinEvent] Cannot find user with ID [" + event.userID + "]"); + return; + } + + payload.eventName = EventConstants.USER_LEFT; + payload.userID = user.userID; + + broadcastEvent(payload); + } private function broadcastEvent(message:Object):void { if (ExternalInterface.available) { diff --git a/bigbluebutton-client/src/org/bigbluebutton/main/api/maps/ExternalApiEventMap.mxml b/bigbluebutton-client/src/org/bigbluebutton/main/api/maps/ExternalApiEventMap.mxml index a9fd45fafdc4bdf7fcac9a8260995a688f405c2c..e97632ccdb199de9f2dee3724e9f9e44dece839d 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/main/api/maps/ExternalApiEventMap.mxml +++ b/bigbluebutton-client/src/org/bigbluebutton/main/api/maps/ExternalApiEventMap.mxml @@ -23,29 +23,82 @@ <EventMap xmlns="http://mate.asfusion.com/" xmlns:mx="http://www.adobe.com/2006/mxml"> <mx:Script> <![CDATA[ - import org.bigbluebutton.core.EventConstants; - import org.bigbluebutton.main.api.ExternalApiCalls; - import org.bigbluebutton.main.events.BBBEvent; - import org.bigbluebutton.main.events.ParticipantJoinEvent; - import org.bigbluebutton.main.events.MadePresenterEvent; + import org.bigbluebutton.core.EventConstants; + import org.bigbluebutton.core.events.AmIPresenterQueryEvent; + import org.bigbluebutton.core.events.AmISharingWebcamQueryEvent; + import org.bigbluebutton.core.events.SwitchedLayoutEvent; + import org.bigbluebutton.main.api.ExternalApiCalls; + import org.bigbluebutton.main.events.BBBEvent; + import org.bigbluebutton.main.events.MadePresenterEvent; + import org.bigbluebutton.main.events.UserJoinedEvent; + import org.bigbluebutton.main.events.UserLeftEvent; + import org.bigbluebutton.main.model.users.events.BroadcastStartedEvent; + import org.bigbluebutton.main.model.users.events.BroadcastStoppedEvent; + import org.bigbluebutton.main.model.users.events.StreamStartedEvent; ]]> </mx:Script> <!-- Listen for events we are interested to forward to the external container.. --> - <EventHandlers type="{BBBEvent.ADDED_LISTENER}" > - <MethodInvoker generator="{ExternalApiCalls}" method="handleUserJoinedVoiceEvent"/> + + + <EventHandlers type="{StreamStartedEvent.STREAM_STARTED}" > + <MethodInvoker generator="{ExternalApiCalls}" method="handleStreamStartedEvent" arguments="{event}"/> + </EventHandlers> + + <EventHandlers type="{BroadcastStartedEvent.BROADCAST_STARTED_EVENT}" > + <MethodInvoker generator="{ExternalApiCalls}" method="handleBroadcastStartedEvent" arguments="{event}"/> + </EventHandlers> + + <EventHandlers type="{BroadcastStoppedEvent.BROADCAST_STOPPED}" > + <MethodInvoker generator="{ExternalApiCalls}" method="handleBroadcastStoppedEvent" arguments="{event}"/> + </EventHandlers> + + <EventHandlers type="{AmIPresenterQueryEvent.AM_I_PRESENTER_QUERY}" > + <MethodInvoker generator="{ExternalApiCalls}" method="handleAmIPresenterQueryEvent" arguments="{event}"/> + </EventHandlers> + + <EventHandlers type="{AmISharingWebcamQueryEvent.AM_I_SHARING_CAM_QUERY}" > + <MethodInvoker generator="{ExternalApiCalls}" method="handleAmISharingCamQueryEvent" arguments="{event}"/> + </EventHandlers> + + <EventHandlers type="{BBBEvent.USER_VOICE_MUTED}" > + <MethodInvoker generator="{ExternalApiCalls}" method="handleUserVoiceMutedEvent" arguments="{event}"/> + </EventHandlers> + + <EventHandlers type="{BBBEvent.USER_VOICE_LOCKED}" > + <MethodInvoker generator="{ExternalApiCalls}" method="handleUserVoiceLockedEvent" arguments="{event}"/> + </EventHandlers> + + <EventHandlers type="{BBBEvent.USER_VOICE_LEFT}" > + <MethodInvoker generator="{ExternalApiCalls}" method="handleUserVoiceLeftEvent" arguments="{event}"/> + </EventHandlers> + + <EventHandlers type="{BBBEvent.USER_VOICE_JOINED}" > + <MethodInvoker generator="{ExternalApiCalls}" method="handleUserJoinedVoiceEvent" arguments="{event}"/> </EventHandlers> <EventHandlers type="{EventConstants.GET_MY_ROLE_RESP}" > <MethodInvoker generator="{ExternalApiCalls}" method="handleGetMyRoleResponse" arguments="{event}"/> </EventHandlers> + + <EventHandlers type="{SwitchedLayoutEvent.SWITCHED_LAYOUT_EVENT}" > + <MethodInvoker generator="{ExternalApiCalls}" method="handleSwitchedLayoutEvent" arguments="{event}"/> + </EventHandlers> + + <EventHandlers type="{AmIPresenterQueryEvent.AM_I_PRESENTER_QUERY}" > + <MethodInvoker generator="{ExternalApiCalls}" method="handleAmIPresenterQueryEvent" arguments="{event}"/> + </EventHandlers> - <EventHandlers type="{ParticipantJoinEvent.PARTICIPANT_JOINED_EVENT}" > - <MethodInvoker generator="{ExternalApiCalls}" method="handleParticipantJoinEvent" arguments="{event}"/> + <EventHandlers type="{UserJoinedEvent.JOINED}" > + <MethodInvoker generator="{ExternalApiCalls}" method="handleUserJoinedEvent" arguments="{event}"/> </EventHandlers> - <EventHandlers type="{EventConstants.NEW_ROLE}" > + <EventHandlers type="{UserLeftEvent.LEFT}" > + <MethodInvoker generator="{ExternalApiCalls}" method="handleUserLeftEvent" arguments="{event}"/> + </EventHandlers> + + <EventHandlers type="{EventConstants.SWITCHED_PRESENTER}" > <MethodInvoker generator="{ExternalApiCalls}" method="handleSwitchToNewRoleEvent" arguments="{event}"/> </EventHandlers> diff --git a/bigbluebutton-client/src/org/bigbluebutton/main/events/BBBEvent.as b/bigbluebutton-client/src/org/bigbluebutton/main/events/BBBEvent.as index 1f172fee1237e62a304e108796707e47c4d98ae7..a2ed7c0f6114ea389de68d7c36a50b9734e2f2d6 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/main/events/BBBEvent.as +++ b/bigbluebutton-client/src/org/bigbluebutton/main/events/BBBEvent.as @@ -27,14 +27,20 @@ package org.bigbluebutton.main.events { public static const RECEIVED_PUBLIC_CHAT_MESSAGE_EVENT:String = 'RECEIVED_PUBLIC_CHAT_MESSAGE_EVENT'; public static const SEND_PUBLIC_CHAT_MESSAGE_EVENT:String = 'SEND_PUBLIC_CHAT_MESSAGE_EVENT'; public static const JOIN_VOICE_CONFERENCE:String = 'BBB_JOIN_VOICE_CONFERENCE'; - public static const ADDED_LISTENER:String = 'BBB_ADDED_LISTENER'; public static const PRESENTATION_CONVERTED:String = 'BBB_PRESENTATION_CONVERTED'; public static const START_VIDEO_CONNECTION:String = 'BBB_START_VIDEO_CONNECTION'; public static const START_VIDEO_STREAM:String = 'BBB_START_VIDEO_STREAM'; public static const VIDEO_STARTED:String = 'BBB_VIDEO_STARTED'; public static const START_DESKSHARE:String = 'BBB_START_DESKSHARE'; public static const DESKSHARE_STARTED:String = 'BBB_DESKSHARE_STARTED'; - + public static const USER_VOICE_JOINED:String = 'user voice joined event'; + public static const USER_VOICE_MUTED:String = "user voice muted event"; + public static const USER_VOICE_LOCKED:String = "user voice locked event"; + public static const USER_VOICE_LEFT:String = "user voice left event"; + public static const USER_VOICE_TALKING:String = "user voice talking event"; + public static const CAMERA_SETTING:String = "camera settings event"; + public static const OPEN_WEBCAM_PREVIEW:String = "open webcam preview event"; + public var message:String; public var payload:Object = new Object(); diff --git a/bigbluebutton-client/src/org/bigbluebutton/main/events/NetworkStatsEvent.as b/bigbluebutton-client/src/org/bigbluebutton/main/events/NetworkStatsEvent.as new file mode 100755 index 0000000000000000000000000000000000000000..8250908dc643f3f7f4e8e17355ca44a02204ade8 --- /dev/null +++ b/bigbluebutton-client/src/org/bigbluebutton/main/events/NetworkStatsEvent.as @@ -0,0 +1,19 @@ +package org.bigbluebutton.main.events +{ + import flash.events.Event; + import flash.utils.Dictionary; + + public class NetworkStatsEvent extends Event + { + public static const NETWORK_STATS_EVENTS:String = "NETWORK_STATS_EVENTS"; + + public var downloadStats:Dictionary; + public var uploadStats:Dictionary; + + public function NetworkStatsEvent(bubbles:Boolean=true, cancelable:Boolean=false) + { + super(NETWORK_STATS_EVENTS, bubbles, cancelable); + } + + } +} \ No newline at end of file diff --git a/bigbluebutton-client/src/org/bigbluebutton/main/events/StoppedViewingWebcamEvent.as b/bigbluebutton-client/src/org/bigbluebutton/main/events/StoppedViewingWebcamEvent.as new file mode 100755 index 0000000000000000000000000000000000000000..966140a2558e3cacea52df8b2ffc41b59a477ac2 --- /dev/null +++ b/bigbluebutton-client/src/org/bigbluebutton/main/events/StoppedViewingWebcamEvent.as @@ -0,0 +1,17 @@ +package org.bigbluebutton.main.events +{ + import flash.events.Event; + + public class StoppedViewingWebcamEvent extends Event + { + public static const STOPPED_VIEWING_WEBCAM:String = "stopped viewing webcam event"; + + // The userID of the webcam being viewed. + public var webcamUserID:String; + + public function StoppedViewingWebcamEvent(bubbles:Boolean=true, cancelable:Boolean=false) + { + super(STOPPED_VIEWING_WEBCAM, bubbles, cancelable); + } + } +} \ No newline at end of file diff --git a/bigbluebutton-client/src/org/bigbluebutton/main/events/ParticipantJoinEvent.as b/bigbluebutton-client/src/org/bigbluebutton/main/events/UserJoinedEvent.as similarity index 77% rename from bigbluebutton-client/src/org/bigbluebutton/main/events/ParticipantJoinEvent.as rename to bigbluebutton-client/src/org/bigbluebutton/main/events/UserJoinedEvent.as index b8c56448f8ea86b341013fbf3a23184ad852864b..bc32fcf55fdf3b28f13598a4f1eb4e1d00f2ad8d 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/main/events/ParticipantJoinEvent.as +++ b/bigbluebutton-client/src/org/bigbluebutton/main/events/UserJoinedEvent.as @@ -22,14 +22,13 @@ package org.bigbluebutton.main.events import org.bigbluebutton.main.model.User; - public class ParticipantJoinEvent extends Event + public class UserJoinedEvent extends Event { - public static const PARTICIPANT_JOINED_EVENT:String = 'PARTICIPANT_JOINED_EVENT'; + public static const JOINED:String = 'user has joined event'; public var userID:String; - public var join:Boolean = false; - public function ParticipantJoinEvent(type:String, bubbles:Boolean=true, cancelable:Boolean=false) + public function UserJoinedEvent(type:String, bubbles:Boolean=true, cancelable:Boolean=false) { super(type, bubbles, cancelable); } diff --git a/bigbluebutton-client/src/org/bigbluebutton/main/events/UserLeftEvent.as b/bigbluebutton-client/src/org/bigbluebutton/main/events/UserLeftEvent.as new file mode 100755 index 0000000000000000000000000000000000000000..f924233dae4cf5f2dae5548fe71b5036c25ec1d3 --- /dev/null +++ b/bigbluebutton-client/src/org/bigbluebutton/main/events/UserLeftEvent.as @@ -0,0 +1,37 @@ +/** +* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/ +* +* Copyright (c) 2010 BigBlueButton Inc. and by respective authors (see below). +* +* This program is free software; you can redistribute it and/or modify it under the +* terms of the GNU Lesser General Public License as published by the Free Software +* Foundation; either version 2.1 of the License, or (at your option) any later +* version. +* +* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY +* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License along +* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. +* +*/ +package org.bigbluebutton.main.events +{ + import flash.events.Event; + + import org.bigbluebutton.main.model.User; + + public class UserLeftEvent extends Event + { + public static const LEFT:String = 'user has left event'; + + public var userID:String; + + public function UserLeftEvent(type:String, bubbles:Boolean=true, cancelable:Boolean=false) + { + super(type, bubbles, cancelable); + } + + } +} \ No newline at end of file diff --git a/bigbluebutton-client/src/org/bigbluebutton/main/maps/ApplicationEventMap.mxml b/bigbluebutton-client/src/org/bigbluebutton/main/maps/ApplicationEventMap.mxml index 803f8080c88d938cc17e22a7ffdef3249ab33b29..8bbf1005df798692d6d811c20a3d2a4e53b0be6f 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/main/maps/ApplicationEventMap.mxml +++ b/bigbluebutton-client/src/org/bigbluebutton/main/maps/ApplicationEventMap.mxml @@ -31,6 +31,7 @@ <ObjectBuilder generator="{ModulesProxy}" cache="global" /> <ObjectBuilder generator="{UserService}" cache="global" /> <ObjectBuilder generator="{ConfigManager}" cache="global" /> + <ObjectBuilder generator="{StreamMonitor}" cache="global" /> </EventHandlers> <EventHandlers type="{PortTestEvent.TEST_RTMP}" > @@ -104,17 +105,17 @@ <EventHandlers type="{ModuleLoadEvent.LAYOUT_MODULE_STARTED}"> <MethodInvoker generator="{ModulesProxy}" method="startAllModules" /> </EventHandlers> - + <mx:Script> <![CDATA[ import mx.events.FlexEvent; import org.bigbluebutton.core.managers.ConfigManager; import org.bigbluebutton.core.services.SkinningService; + import org.bigbluebutton.core.services.StreamMonitor; import org.bigbluebutton.main.events.ConfigEvent; import org.bigbluebutton.main.events.LogoutEvent; import org.bigbluebutton.main.events.ModuleLoadEvent; - import org.bigbluebutton.main.events.ParticipantJoinEvent; import org.bigbluebutton.main.events.PortTestEvent; import org.bigbluebutton.main.events.SuccessfulLoginEvent; import org.bigbluebutton.main.events.UserServicesEvent; diff --git a/bigbluebutton-client/src/org/bigbluebutton/main/model/ConferenceParameters.as b/bigbluebutton-client/src/org/bigbluebutton/main/model/ConferenceParameters.as index be412ebde72874f4681940253bf123fa33ecb32d..505ebedc3e7673716ce813444bec411a30536114 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/main/model/ConferenceParameters.as +++ b/bigbluebutton-client/src/org/bigbluebutton/main/model/ConferenceParameters.as @@ -27,6 +27,10 @@ package org.bigbluebutton.main.model */ public class ConferenceParameters { + public var meetingName:String; + + public var externMeetingID:String; + /** * The name of the conference */ diff --git a/bigbluebutton-client/src/org/bigbluebutton/main/model/NetworkStatsData.as b/bigbluebutton-client/src/org/bigbluebutton/main/model/NetworkStatsData.as new file mode 100644 index 0000000000000000000000000000000000000000..be046d2e07a8c55bdd5edd827ff68668f75f0c3e --- /dev/null +++ b/bigbluebutton-client/src/org/bigbluebutton/main/model/NetworkStatsData.as @@ -0,0 +1,113 @@ +package org.bigbluebutton.main.model +{ + + import com.asfusion.mate.events.Dispatcher; + + import org.bigbluebutton.common.LogUtil; + import org.bigbluebutton.main.events.NetworkStatsEvent; + + import flash.events.EventDispatcher; + + public class NetworkStatsData extends EventDispatcher + { + private static var _instance:NetworkStatsData = null; + private var _currentConsumedDownBW:Number = 0; // Kb + private var _currentConsumedUpBW:Number = 0; // Kb + private var _totalConsumedDownBW:Number = 0; // MB + private var _totalConsumedUpBW:Number = 0; // MB + private var _measuredDownBW:int = 0; // Mb + private var _measuredDownLatency:int = 0; // ms + private var _measuredUpBW:int = 0; // Mb + private var _measuredUpLatency:int = 0; // ms + + /** + * This class is a singleton. Please initialize it using the getInstance() method. + */ + public function NetworkStatsData(enforcer:SingletonEnforcer) { + if (enforcer == null) { + throw new Error("There can only be one instance of this class"); + } + initialize(); + } + + private function initialize():void { + } + + /** + * Return the single instance of this class + */ + public static function getInstance():NetworkStatsData { + if (_instance == null){ + _instance = new NetworkStatsData(new SingletonEnforcer()); + } + return _instance; + } + + // all the numbers are in bytes + public function updateConsumedBW(down:Number, up:Number, downTotal:Number, upTotal:Number):void { + _currentConsumedDownBW = (down * 8)/1024; + _currentConsumedUpBW = (up * 8)/1024; + _totalConsumedDownBW = downTotal / 1048576; + _totalConsumedUpBW = upTotal / 1048576; + } + + /* + 12/8/2012 17:24:38.293 [DEBUG] (Array)#0 + [deltaDown] 2455.704 + [deltaTime] 170 + [kbitDown] 14445 + [latency] 10 + */ + public function setDownloadMeasuredBW(info:Object):void { + _measuredDownBW = info["kbitDown"] / 1000; + _measuredDownLatency = info["latency"]; + } + + /* + 12/8/2012 17:24:39.556 [DEBUG] (Object)#0 + deltaTime = 1 + deltaUp = 10516 + kbitUp = 10516 + KBytes = 1283 + latency = 11 + */ + public function setUploadMeasuredBW(info:Object):void { + _measuredUpBW = info.kbitUp / 1000; + _measuredUpLatency = info.latency; + } + + public function get currentConsumedDownBW():Number { + return _currentConsumedDownBW; + } + + public function get currentConsumedUpBW():Number { + return _currentConsumedUpBW; + } + + public function get totalConsumedDownBW():Number { + return _totalConsumedDownBW; + } + + public function get totalConsumedUpBW():Number { + return _totalConsumedUpBW; + } + + public function get measuredDownBW():int { + return _measuredDownBW; + } + + public function get measuredDownLatency():int { + return _measuredDownLatency; + } + + public function get measuredUpBW():int { + return _measuredUpBW; + } + + public function get measuredUpLatency():int { + return _measuredUpLatency; + } + } +} + +class SingletonEnforcer{} \ No newline at end of file diff --git a/bigbluebutton-client/src/org/bigbluebutton/main/model/users/Conference.as b/bigbluebutton-client/src/org/bigbluebutton/main/model/users/Conference.as index bf46784346a9e73fa92d8d74b728d4e9e7bb178b..b96d515b102e5102b32a4c295eb3a1842ba5fd98 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/main/model/users/Conference.as +++ b/bigbluebutton-client/src/org/bigbluebutton/main/model/users/Conference.as @@ -22,21 +22,30 @@ package org.bigbluebutton.main.model.users { import org.bigbluebutton.common.LogUtil; import org.bigbluebutton.common.Role; import org.bigbluebutton.core.BBB; + import org.bigbluebutton.core.vo.CameraSettingsVO; public class Conference { -// private var _myUserid:Number; + public var meetingName:String; + public var externalMeetingID:String; + public var internalMeetingID:String; + public var avatarURL:String; + + private var _myCamSettings:CameraSettingsVO; + [Bindable] private var me:BBBUser = null; [Bindable] public var users:ArrayCollection = null; - + + private var defaultLayout:String; + public function Conference():void { me = new BBBUser(); users = new ArrayCollection(); } public function addUser(newuser:BBBUser):void { - LogUtil.debug("Adding new user [" + newuser.userID + "]"); + trace("Adding new user [" + newuser.userID + "]"); if (! hasUser(newuser.userID)) { - LogUtil.debug("Am I this new user [" + newuser.userID + ", " + me.userID + "]"); + trace("Am I this new user [" + newuser.userID + ", " + me.userID + "]"); if (newuser.userID == me.userID) { newuser.me = true; } @@ -46,6 +55,26 @@ package org.bigbluebutton.main.model.users { } } + public function setCamPublishing(publishing:Boolean):void { + _myCamSettings.isPublishing = publishing; + } + + public function setCameraSettings(camSettings:CameraSettingsVO):void { + _myCamSettings = camSettings; + } + + public function amIPublishing():CameraSettingsVO { + return _myCamSettings; + } + + public function setDefaultLayout(defaultLayout:String):void { + this.defaultLayout = defaultLayout; + } + + public function getDefaultLayout():String { + return defaultLayout; + } + public function hasUser(userID:String):Boolean { var p:Object = getUserIndex(userID); if (p != null) { @@ -128,7 +157,7 @@ package org.bigbluebutton.main.model.users { public function removeUser(userID:String):void { var p:Object = getUserIndex(userID); if (p != null) { - LogUtil.debug("removing user[" + p.participant.name + "," + p.participant.userID + "]"); + trace("removing user[" + p.participant.name + "," + p.participant.userID + "]"); users.removeItemAt(p.index); sort(); } @@ -281,6 +310,15 @@ package org.bigbluebutton.main.model.users { sort(); } + + public function getUserIDs():ArrayCollection { + var uids:ArrayCollection = new ArrayCollection(); + for (var i:int = 0; i < users.length; i++) { + var u:BBBUser = users.getItemAt(i) as BBBUser; + uids.addItem(u.userID); + } + return uids; + } /** * Sorts the users by name diff --git a/bigbluebutton-client/src/org/bigbluebutton/main/model/users/JoinService.as b/bigbluebutton-client/src/org/bigbluebutton/main/model/users/JoinService.as index 25c582b2e974a00c99598e92c861e4c23d34a7c0..f28b97621858ad25c95ede0f21fb1aa89c9038bd 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/main/model/users/JoinService.as +++ b/bigbluebutton-client/src/org/bigbluebutton/main/model/users/JoinService.as @@ -67,11 +67,12 @@ package org.bigbluebutton.main.model.users } else if (returncode == 'SUCCESS') { LogUtil.debug("Join SUCESS = " + xml); - var user:Object = {username:xml.fullname, conference:xml.conference, conferenceName:xml.confname, + var user:Object = {username:xml.fullname, conference:xml.conference, conferenceName:xml.confname, externMeetingID:xml.externMeetingID, meetingID:xml.meetingID, externUserID:xml.externUserID, internalUserId:xml.internalUserID, role:xml.role, room:xml.room, authToken:xml.room, record:xml.record, webvoiceconf:xml.webvoiceconf, - voicebridge:xml.voicebridge, mode:xml.mode, welcome:xml.welcome, logoutUrl:xml.logoutUrl}; + voicebridge:xml.voicebridge, mode:xml.mode, welcome:xml.welcome, logoutUrl:xml.logoutUrl, + defaultLayout:xml.defaultLayout, avatarURL:xml.avatarURL}; if (_resultListener != null) _resultListener(true, user); } diff --git a/bigbluebutton-client/src/org/bigbluebutton/main/model/users/NetConnectionDelegate.as b/bigbluebutton-client/src/org/bigbluebutton/main/model/users/NetConnectionDelegate.as index deebb5977ab035a5a995aed223a6b9122659e91c..f234754490fb4c34313b6f891623b6cd54aad5e6 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/main/model/users/NetConnectionDelegate.as +++ b/bigbluebutton-client/src/org/bigbluebutton/main/model/users/NetConnectionDelegate.as @@ -18,16 +18,13 @@ */ package org.bigbluebutton.main.model.users { - import com.asfusion.mate.events.Dispatcher; - + import com.asfusion.mate.events.Dispatcher; import flash.events.*; import flash.net.NetConnection; import flash.net.Responder; import flash.utils.Timer; - - import mx.controls.Alert; - import org.bigbluebutton.common.LogUtil; + import org.bigbluebutton.core.services.BandwidthMonitor; import org.bigbluebutton.main.model.ConferenceParameters; import org.bigbluebutton.main.model.users.events.ConnectionFailedEvent; import org.bigbluebutton.main.model.users.events.UsersConnectionEvent; @@ -68,51 +65,45 @@ package org.bigbluebutton.main.model.users _netConnection.addEventListener( IOErrorEvent.IO_ERROR, netIOError ); } - public function setUri(uri:String):void { - _applicationURI = uri; - } + public function setUri(uri:String):void { + _applicationURI = uri; + } public function get connection():NetConnection { return _netConnection; } - public function addMessageListener(listener:IMessageListener):void - { - _messageListeners.push(listener); - } + public function addMessageListener(listener:IMessageListener):void { + _messageListeners.push(listener); + } - public function removeMessageListener(listener:IMessageListener):void - { - for (var ob:int=0; ob<_messageListeners.length; ob++) - { - if (_messageListeners[ob]==listener) - { - _messageListeners.splice (ob,1); - break; - } - } + public function removeMessageListener(listener:IMessageListener):void { + for (var ob:int=0; ob<_messageListeners.length; ob++) { + if (_messageListeners[ob] == listener) { + _messageListeners.splice (ob,1); + break; } + } + } - private function notifyListeners(messageName:String, message:Object):void - { - if (messageName != null && messageName != "") { - for (var notify:String in _messageListeners) - { - _messageListeners[notify].onMessage(messageName, message); - } - } else { - LogUtil.debug("Message name is undefined"); - } - } + private function notifyListeners(messageName:String, message:Object):void { + if (messageName != null && messageName != "") { + for (var notify:String in _messageListeners) { + _messageListeners[notify].onMessage(messageName, message); + } + } else { + LogUtil.debug("Message name is undefined"); + } + } - public function onMessageFromServer(messageName:String, result:Object):void { -// LogUtil.debug("Got message from server [" + messageName + "]"); - notifyListeners(messageName, result); - } + public function onMessageFromServer(messageName:String, result:Object):void { + trace("Got message from server [" + messageName + "]"); + notifyListeners(messageName, result); + } public function sendMessage(service:String, onSuccess:Function, onFailure:Function, message:Object=null):void { -// LogUtil.debug("SENDING [" + service + "]"); + trace("SENDING [" + service + "]"); var responder:Responder = new Responder( function(result:Object):void { // On successful result onSuccess("Successfully sent [" + service + "]."); @@ -130,7 +121,6 @@ package org.bigbluebutton.main.model.users } else { _netConnection.call(service, responder, message); } - } /** @@ -142,8 +132,7 @@ package org.bigbluebutton.main.model.users * mode: LIVE/PLAYBACK - Live:when used to collaborate, Playback:when being used to playback a recorded conference. * room: Need the room number when playing back a recorded conference. When LIVE, the room is taken from the URI. */ - public function connect(params:ConferenceParameters, tunnel:Boolean = false):void - { + public function connect(params:ConferenceParameters, tunnel:Boolean = false):void { _conferenceParameters = params; tried_tunneling = tunnel; @@ -157,10 +146,9 @@ package org.bigbluebutton.main.model.users _conferenceParameters.room, _conferenceParameters.voicebridge, _conferenceParameters.record, _conferenceParameters.externUserID, _conferenceParameters.internalUserID); - } catch( e : ArgumentError ) { + } catch(e:ArgumentError) { // Invalid parameters. - switch ( e.errorID ) - { + switch (e.errorID) { case 2004 : LogUtil.debug("Error! Invalid server location: " + uri); break; @@ -171,8 +159,7 @@ package org.bigbluebutton.main.model.users } } - public function disconnect(logoutOnUserCommand:Boolean) : void - { + public function disconnect(logoutOnUserCommand:Boolean):void { this.logoutOnUserCommand = logoutOnUserCommand; _netConnection.close(); } @@ -182,22 +169,33 @@ package org.bigbluebutton.main.model.users _netConnection.close(); } - protected function netStatus( event : NetStatusEvent ) : void - { + protected function netStatus(event:NetStatusEvent):void { handleResult( event ); } + private var _bwMon:BandwidthMonitor = new BandwidthMonitor(); + + private function startMonitoringBandwidth():void { + trace("Start monitoring bandwidth."); + var pattern:RegExp = /(?P<protocol>.+):\/\/(?P<server>.+)\/(?P<app>.+)/; + var result:Array = pattern.exec(_applicationURI); + _bwMon.serverURL = result.server; + _bwMon.serverApplication = "video"; + _bwMon.start(); + } + private var autoReconnectTimer:Timer = new Timer(1000, 1); - public function handleResult( event : Object ) : void { + public function handleResult(event:Object):void { var info : Object = event.info; var statusCode : String = info.code; - switch ( statusCode ) - { + switch (statusCode) { case "NetConnection.Connect.Success": LogUtil.debug(NAME + ":Connection to viewers application succeeded."); - + + startMonitoringBandwidth(); + _netConnection.call( "getMyUserId",// Remote function name new Responder( @@ -270,7 +268,6 @@ package org.bigbluebutton.main.model.users private function autoReconnectTimerHandler(event:TimerEvent):void { LogUtil.debug(NAME + "autoReconnectTimerHandler: " + event); - Alert.show("Attempting to reconnect"); connect(_conferenceParameters, tried_tunneling); } @@ -351,6 +348,7 @@ package org.bigbluebutton.main.model.users public function onBWCheck(... rest):Number { return 0; } + public function onBWDone(... rest):void { var p_bw:Number; if (rest.length > 0) p_bw = rest[0]; diff --git a/bigbluebutton-client/src/org/bigbluebutton/main/model/users/UserService.as b/bigbluebutton-client/src/org/bigbluebutton/main/model/users/UserService.as index 3aae5123d50f8792fa5f28e27033b81ba9934c2c..e19fd8598e9c6fe68f9f3269e8327065eac56731 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/main/model/users/UserService.as +++ b/bigbluebutton-client/src/org/bigbluebutton/main/model/users/UserService.as @@ -66,8 +66,16 @@ package org.bigbluebutton.main.model.users UserManager.getInstance().getConference().setMyRole(result.role); UserManager.getInstance().getConference().setMyRoom(result.room); UserManager.getInstance().getConference().setMyAuthToken(result.authToken); - + UserManager.getInstance().getConference().setDefaultLayout(result.defaultLayout); + + UserManager.getInstance().getConference().externalMeetingID = result.externMeetingID; + UserManager.getInstance().getConference().meetingName = result.confereceName; + UserManager.getInstance().getConference().internalMeetingID = result.room; + UserManager.getInstance().getConference().avatarURL = result.avatarURL; + _conferenceParameters = new ConferenceParameters(); + _conferenceParameters.meetingName = result.confereceName; + _conferenceParameters.externMeetingID = result.externMeetingID; _conferenceParameters.conference = result.conference; _conferenceParameters.username = result.username; _conferenceParameters.role = result.role; diff --git a/bigbluebutton-client/src/org/bigbluebutton/main/model/users/UsersSOService.as b/bigbluebutton-client/src/org/bigbluebutton/main/model/users/UsersSOService.as index 8cd249b5541cc6c02360c7d9ae313bc89598ad4b..fc2c3edbd0f6c51d23b81876503ecf18abc6765c 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/main/model/users/UsersSOService.as +++ b/bigbluebutton-client/src/org/bigbluebutton/main/model/users/UsersSOService.as @@ -35,8 +35,9 @@ package org.bigbluebutton.main.model.users { import org.bigbluebutton.main.events.BBBEvent; import org.bigbluebutton.main.events.LogoutEvent; import org.bigbluebutton.main.events.MadePresenterEvent; - import org.bigbluebutton.main.events.ParticipantJoinEvent; import org.bigbluebutton.main.events.PresenterStatusEvent; + import org.bigbluebutton.main.events.UserJoinedEvent; + import org.bigbluebutton.main.events.UserLeftEvent; import org.bigbluebutton.main.model.ConferenceParameters; import org.bigbluebutton.main.model.users.events.ConnectionFailedEvent; import org.bigbluebutton.main.model.users.events.RoleChangeEvent; @@ -164,7 +165,7 @@ package org.bigbluebutton.main.model.users { * Called by the server to assign a presenter */ public function assignPresenterCallback(userid:String, name:String, assignedBy:String):void { - LogUtil.debug("assignPresenterCallback " + userid + "," + name + "," + assignedBy); + trace("assignPresenterCallback " + userid + "," + name + "," + assignedBy); var dispatcher:Dispatcher = new Dispatcher(); var meeting:Conference = UserManager.getInstance().getConference(); if (meeting.amIThisUser(userid)) { @@ -175,8 +176,8 @@ package org.bigbluebutton.main.model.users { e.assignerBy = assignedBy; dispatcher.dispatchEvent(e); - - var roleEvent:CoreEvent = new CoreEvent(EventConstants.NEW_ROLE); + trace("Switching to [" + e.presenterName + "] to presenter"); + var roleEvent:CoreEvent = new CoreEvent(EventConstants.SWITCHED_PRESENTER); roleEvent.message.role = Role.PRESENTER; dispatcher.dispatchEvent(roleEvent); @@ -189,7 +190,8 @@ package org.bigbluebutton.main.model.users { dispatcher.dispatchEvent(viewerEvent); - var newRoleEvent:CoreEvent = new CoreEvent(EventConstants.NEW_ROLE); + trace("Switching to [" + e.presenterName + "] to presenter. I am viewer."); + var newRoleEvent:CoreEvent = new CoreEvent(EventConstants.SWITCHED_PRESENTER); newRoleEvent.message.role = Role.VIEWER; dispatcher.dispatchEvent(newRoleEvent); } @@ -211,9 +213,8 @@ package org.bigbluebutton.main.model.users { UserManager.getInstance().getConference().removeUser(userID); var dispatcher:Dispatcher = new Dispatcher(); - var joinEvent:ParticipantJoinEvent = new ParticipantJoinEvent(ParticipantJoinEvent.PARTICIPANT_JOINED_EVENT); + var joinEvent:UserLeftEvent = new UserLeftEvent(UserLeftEvent.LEFT); joinEvent.userID = user.userID; - joinEvent.join = false; dispatcher.dispatchEvent(joinEvent); } @@ -233,9 +234,8 @@ package org.bigbluebutton.main.model.users { participantStatusChange(user.userID, "raiseHand", joinedUser.status.raiseHand); var dispatcher:Dispatcher = new Dispatcher(); - var joinEvent:ParticipantJoinEvent = new ParticipantJoinEvent(ParticipantJoinEvent.PARTICIPANT_JOINED_EVENT); + var joinEvent:UserJoinedEvent = new UserJoinedEvent(UserJoinedEvent.JOINED); joinEvent.userID = user.userID; - joinEvent.join = true; dispatcher.dispatchEvent(joinEvent); } diff --git a/bigbluebutton-client/src/org/bigbluebutton/main/model/users/events/BroadcastStartedEvent.as b/bigbluebutton-client/src/org/bigbluebutton/main/model/users/events/BroadcastStartedEvent.as index 9f615094d0b8d368f748f39d0f26d87889cdb9a6..96852e7db36fc4c37cfd2232975ba29f76ce98a9 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/main/model/users/events/BroadcastStartedEvent.as +++ b/bigbluebutton-client/src/org/bigbluebutton/main/model/users/events/BroadcastStartedEvent.as @@ -20,15 +20,18 @@ { import flash.events.Event; + import org.bigbluebutton.core.vo.CameraSettingsVO; + public class BroadcastStartedEvent extends Event { public static const BROADCAST_STARTED_EVENT:String = "BROADCAST_STARTED_EVENT"; public var stream:String; public var userid:String; - - public function BroadcastStartedEvent(type:String = BROADCAST_STARTED_EVENT) - { + public var isPresenter:Boolean; + public var camSettings:CameraSettingsVO; + + public function BroadcastStartedEvent(type:String = BROADCAST_STARTED_EVENT) { super(type, true, false); } diff --git a/bigbluebutton-client/src/org/bigbluebutton/main/views/CameraDisplaySettings.mxml b/bigbluebutton-client/src/org/bigbluebutton/main/views/CameraDisplaySettings.mxml new file mode 100755 index 0000000000000000000000000000000000000000..18b5fce6c538d711c21d70dffc17add3a6b7fd4a --- /dev/null +++ b/bigbluebutton-client/src/org/bigbluebutton/main/views/CameraDisplaySettings.mxml @@ -0,0 +1,273 @@ +<?xml version="1.0" encoding="utf-8"?> +<mx:TitleWindow xmlns:mx="http://www.adobe.com/2006/mxml" + xmlns:view="org.bigbluebutton.main.views.*" + layout="absolute" + width="600" height="400" creationComplete="onCreationComplete()" + showCloseButton="true" close="onCancelClicked()" keyDown="handleKeyDown(event)"> + <mx:Script> + <![CDATA[ + import com.asfusion.mate.events.Dispatcher; + + import flash.ui.Keyboard; + + import mx.events.CloseEvent; + import mx.events.ItemClickEvent; + import mx.managers.PopUpManager; + + import org.bigbluebutton.common.Images; + import org.bigbluebutton.common.LogUtil; + import org.bigbluebutton.core.UsersUtil; + import org.bigbluebutton.main.events.BBBEvent; + import org.bigbluebutton.modules.videoconf.model.VideoConfOptions; + import org.bigbluebutton.util.i18n.ResourceUtil; + + static public var PADDING_HORIZONTAL:Number = 6; + static public var PADDING_VERTICAL:Number = 29; + + private var images:Images = new Images(); + + [Bindable] + private var cancelIcon:Class = images.control_play; + + [Bindable] + public var resolutions:Array; + public var publishInClient:Boolean; + + public var camWidth:Number = 320; + public var camHeight:Number = 240; + private var _camera:Camera = null; + + // Timer used to enable the start publishing button, only after get any activity on the camera. + // It avoids the problem of publishing a blank video + private var _activationTimer:Timer = null; + private var _waitingForActivation:Boolean = false; + + static private var _cameraAccessDenied:Boolean = false; + + [Bindable] + private var camIcon:Class = images.webcam; + + private var _video:Video; + private var aspectRatio:Number = 1; + + private function onCreationComplete():void { + changeDefaultCamForMac(); + + if (UsersUtil.amIPresenter()) { + showResControls(true); + } + + if (Camera.names.length > 1) { + showVideoControls(true); + } + + updateCamera(); + } + + private function changeDefaultCamForMac():void { + for (var i:int = 0; i < Camera.names.length; i++){ + if (Camera.names[i] == "USB Video Class Video") { + /** Set as default for Macs */ + cmbCameraSelector.selectedIndex = i; + } + } + } + + private function showVideoControls(show:Boolean):void { + if (show) { + this.visible = true; + btnStartPublish.visible = true; + cmbCameraSelector.visible = true; + } else{ + this.width = 0; + this.height = 0; + btnStartPublish.visible = false; + cmbCameraSelector.visible = false; + this.visible = false; + } + } + + private function updateCamera():void { + _camera = null; + + _camera = Camera.getCamera(); + + if (_camera == null) { + _videoHolder.showWarning('bbb.video.publish.hint.cantOpenCamera'); + return; + } + + _camera.addEventListener(ActivityEvent.ACTIVITY, onActivityEvent); + _camera.addEventListener(StatusEvent.STATUS, onStatusEvent); + + if (_camera.muted) { + if (_cameraAccessDenied) { + onCameraAccessDisallowed(); + return; + } else { + _videoHolder.showWarning('bbb.video.publish.hint.waitingApproval'); + } + } else { + // if the camera isn't muted, that is because the user has + // previously allowed the camera capture on the flash privacy box + onCameraAccessAllowed(); + } + + displayVideoPreview(); + } + + private function displayVideoPreview():void { + var videoOptions:VideoConfOptions = new VideoConfOptions(); + _camera.setMotionLevel(5, 1000); + _camera.setKeyFrameInterval(videoOptions.camKeyFrameInterval); + _camera.setMode(camWidth, camHeight, videoOptions.camModeFps); + _camera.setQuality(videoOptions.camQualityBandwidth, videoOptions.camQualityPicture); + + if (_camera.width != camWidth || _camera.height != camHeight) { + LogUtil.debug("Resolution " + camWidth + "x" + camHeight + " is not supported, using " + _camera.width + "x" + _camera.height + " instead"); + setResolution(_camera.width, _camera.height); + } + + setComboResolution(); + + if (_video != null) { + _videoHolder.remove(_video); + } + + _video = new Video(); + _video.attachCamera(_camera); + + aspectRatio = (_video.width / _video.height); + + if (aspectRatio > _videoHolder.width / _videoHolder.height) { + _video.width = _videoHolder.width; + _video.height = _videoHolder.width / aspectRatio; + _video.x = 0; + _video.y = (_videoHolder.height - _video.height) / 2; + } else { + _video.width = _videoHolder.height * aspectRatio; + _video.height = _videoHolder.height; + _video.x = (_videoHolder.width - _video.width) / 2; + _video.y = 0; + } + + _videoHolder.add(_video); + } + + private function showResControls(show:Boolean):void { + if (show) cmbResolution.visible = true; + else cmbResolution.visible = false; + } + + private function setComboResolution():void { + var res:Array = cmbResolution.selectedLabel.split( "x" ); + setResolution(Number(res[0]), Number(res[1])); + } + + private function setAspectRatio(width:int, height:int):void { + aspectRatio = (width/height); + this.minHeight = Math.floor((this.minWidth - PADDING_HORIZONTAL) / aspectRatio) + PADDING_VERTICAL; + } + + private function setResolution(width:int, height:int):void { + camWidth = width; + camHeight = height; + setAspectRatio(camWidth, camHeight); + } + + private function startPublishing():void { + // Save the index of the camera. Need it to send the message. + var camIndex:int = _camera.index; + + disableCamera(); + + var globalDispatcher:Dispatcher = new Dispatcher(); + var camEvent:BBBEvent = new BBBEvent(BBBEvent.CAMERA_SETTING); + camEvent.payload.cameraIndex = camIndex; + camEvent.payload.cameraWidth = camWidth; + camEvent.payload.cameraHeight = camHeight; + camEvent.payload.publishInClient = publishInClient; + + globalDispatcher.dispatchEvent(camEvent); + + PopUpManager.removePopUp(this); + } + + private function disableCamera():void { + _camera = null; + _video.attachCamera(null); + _video.clear(); + _videoHolder.remove(_video); + _video = null; + } + + private function handleKeyDown(event:KeyboardEvent):void { + if (event.charCode == Keyboard.ESCAPE) { + disableCamera(); + this.dispatchEvent(new CloseEvent(CloseEvent.CLOSE)); + } + } + + private function onCancelClicked():void { + disableCamera(); + PopUpManager.removePopUp(this); + } + + private function onActivityEvent(e:ActivityEvent):void { + if (_waitingForActivation && e.activating) { + _activationTimer.stop(); + _videoHolder.showWarning('bbb.video.publish.hint.videoPreview', false, "0xFFFF00"); + // controls.btnStartPublish.enabled = true; + _waitingForActivation = false; + } + } + + private function onStatusEvent(e:StatusEvent):void { + if (e.code == "Camera.Unmuted") { + onCameraAccessAllowed(); + // this is just to overwrite the message of waiting for approval + _videoHolder.showWarning('bbb.video.publish.hint.openingCamera'); + } else if (e.code == "Camera.Muted") { + onCameraAccessDisallowed(); + } + } + + private function onCameraAccessAllowed():void { + // set timer to ensure that the camera activates. If not, it might be in use by another application + _waitingForActivation = true; + if (_activationTimer != null) { + _activationTimer.stop(); + } + + _activationTimer = new Timer(10000, 1); + _activationTimer.addEventListener(TimerEvent.TIMER, activationTimeout); + _activationTimer.start(); + } + + private function onCameraAccessDisallowed():void { + _videoHolder.showWarning('bbb.video.publish.hint.cameraDenied'); + _cameraAccessDenied = true; + } + + private function activationTimeout(e:TimerEvent):void { + _videoHolder.showWarning('bbb.video.publish.hint.cameraIsBeingUsed'); + } + + private function showCameraSettings():void { + Security.showSettings(SecurityPanel.CAMERA); + } + + ]]> + </mx:Script> + + <mx:VBox id="webcamDisplay" width="100%" height="100%" paddingBottom="5" paddingLeft="5" paddingRight="5" paddingTop="5"> + <view:VideoHolder id="_videoHolder" width="100%" height="90%"/> + <mx:HBox width="100%"> + <mx:Button id="btnStartPublish" toolTip="{ResourceUtil.getInstance().getString('bbb.publishVideo.startPublishBtn.toolTip')}" icon="{camIcon}" click="startPublishing()" enabled="true"/> + <mx:ComboBox id="cmbCameraSelector" dataProvider="{Camera.names}" width="150" visible="false" change="updateCamera()"/> + <mx:Button id="changeCamera" label="Change Camera" toolTip="{ResourceUtil.getInstance().getString('bbb.publishVideo.startPublishBtn.toolTip')}" click="showCameraSettings()"/> + <mx:ComboBox id="cmbResolution" dataProvider="{resolutions}" width="20%" visible="false" change="updateCamera()"/> + </mx:HBox> + + </mx:VBox> +</mx:TitleWindow> \ No newline at end of file diff --git a/bigbluebutton-client/src/org/bigbluebutton/main/views/MainApplicationShell.mxml b/bigbluebutton-client/src/org/bigbluebutton/main/views/MainApplicationShell.mxml index 69510d23fed3c9cd2132abd06d7260bc1c3439e3..2653e61f056c60dee0ba65a923d5680e3a4cbbfe 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/main/views/MainApplicationShell.mxml +++ b/bigbluebutton-client/src/org/bigbluebutton/main/views/MainApplicationShell.mxml @@ -44,7 +44,8 @@ <mate:Listener type="{ToolbarButtonEvent.ADD}" method="handleAddToolbarComponent" /> <mate:Listener type="{ToolbarButtonEvent.REMOVE}" method="handleRemoveToolbarComponent"/> <mate:Listener type="{ShortcutEvent.OPEN_SHORTCUT_WIN}" method="openShortcutHelpWindow" /> - + <mate:Listener type="{BBBEvent.OPEN_WEBCAM_PREVIEW}" method="openVideoPreviewWindow" /> + <mx:Script> <![CDATA[ import com.asfusion.mate.events.Dispatcher; @@ -61,7 +62,7 @@ import mx.core.UIComponent; import mx.events.CloseEvent; import mx.managers.PopUpManager; - + import flash.geom.Point; import org.bigbluebutton.common.IBbbModuleWindow; import org.bigbluebutton.common.Images; import org.bigbluebutton.common.LogUtil; @@ -102,7 +103,8 @@ [Bindable] private var fullscreen_icon:Class = images.full_screen; [Bindable] private var logs_icon:Class = images.table; [Bindable] private var reset_layout_icon:Class = images.layout; - + [Bindable] private var statsIcon:Class = images.bandwidth; + private var receivedConfigLocaleVer:Boolean = false; private var receivedResourceLocaleVer:Boolean = false; @@ -240,7 +242,7 @@ private function handleCloseWindowEvent(event:CloseWindowEvent):void { var window:IBbbModuleWindow = event.window; - mdiCanvas.removeWindow(window); + mdiCanvas.removeWindow(window); } private function resetLayout():void{ @@ -273,8 +275,21 @@ micSettings.x = point1.x - (micSettings.width/2); micSettings.y = point1.y - (micSettings.height/2); } - - private function wrongLocaleVersion():void { + + private function openVideoPreviewWindow(event:BBBEvent):void { + var camSettings:CameraDisplaySettings = CameraDisplaySettings(PopUpManager.createPopUp(mdiCanvas, CameraDisplaySettings, true)); + camSettings.resolutions = (event.payload.resolutions as String).split(","); + camSettings.publishInClient = event.payload.publishInClient; + + var point1:Point = new Point(); + // Calculate position of TitleWindow in Application's coordinates. + point1.x = width/2; + point1.y = height/2; + camSettings.x = point1.x - (camSettings.width/2); + camSettings.y = point1.y - (camSettings.height/2); + } + + private function wrongLocaleVersion():void { var localeWindow:OldLocaleWarnWindow = OldLocaleWarnWindow(PopUpManager.createPopUp(mdiCanvas, OldLocaleWarnWindow, false)); var point1:Point = new Point(); @@ -283,7 +298,7 @@ point1.y = height/2; localeWindow.x = point1.x - (localeWindow.width/2); localeWindow.y = point1.y - (localeWindow.height/2); - } + } private function handleLogout(e:ConnectionFailedEvent):void { if (layoutOptions.showLogoutWindow) { @@ -343,7 +358,27 @@ if (addedBtns.contains(event.button as UIComponent)) addedBtns.removeChild(event.button as UIComponent); } + + private var networkStatsWindow:NetworkStatsWindow = new NetworkStatsWindow(); + + private function openNetworkStatsWindow(e:Event = null):void { + /*var btnPosition:Point = new Point(btnNetwork.x, btnNetwork.y); + var btnPositionOnGlobal:Point = btnNetwork.localToGlobal(btnPosition); + var windowPosition:Point = networkStatsWindow.globalToLocal(btnPositionOnGlobal); + + windowPosition.x += btnNetwork.width + 10; + windowPosition.y -= networkStatsWindow.height - 10; + + networkStatsWindow.x = windowPosition.x; + networkStatsWindow.y = windowPosition.y;*/ + + networkStatsWindow.appear(); + } + private function closeNetworkStatsWindow(e:Event = null):void { + networkStatsWindow.disappear(); + } + ]]> </mx:Script> @@ -360,6 +395,7 @@ <mx:Label text="{isTunneling ? '[Tunnelling]' : ''}" id="isTunnellingLbl"/> <mx:Button width="20" height="20" visible="{layoutOptions.showLogButton}" toolTip="{ResourceUtil.getInstance().getString('bbb.mainshell.logBtn.toolTip')}" id="logBtn" icon="{logs_icon}" click="openLogWindow()" /> <!-- mx:Button width="20" height="20" visible="{layoutOptions.showResetLayout}" toolTip="{ResourceUtil.getInstance().getString('bbb.mainshell.resetLayoutBtn.toolTip')}" id="btnResetLayout" icon="{reset_layout_icon}" click="resetLayout()" /--> + <mx:Button id="btnNetwork" icon="{statsIcon}" width="20" height="20" mouseOver="openNetworkStatsWindow()" mouseOut="closeNetworkStatsWindow()" /> <mx:HBox id="addedBtns" /> </mx:ControlBar> </mx:VBox> diff --git a/bigbluebutton-client/src/org/bigbluebutton/main/views/MainToolbar.mxml b/bigbluebutton-client/src/org/bigbluebutton/main/views/MainToolbar.mxml index a8ea450bd833c91c2113b6c3734c1769c7bfbd0f..5cc376264472fbc2dadc1f876bf4e2ab52e2eb0d 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/main/views/MainToolbar.mxml +++ b/bigbluebutton-client/src/org/bigbluebutton/main/views/MainToolbar.mxml @@ -33,24 +33,25 @@ <mx:Script> <![CDATA[ - import com.asfusion.mate.events.Dispatcher; - - import mx.core.UIComponent; - - import org.bigbluebutton.common.IBbbToolbarComponent; - import org.bigbluebutton.common.LogUtil; - import org.bigbluebutton.common.events.CloseWindowEvent; - import org.bigbluebutton.common.events.ToolbarButtonEvent; - import org.bigbluebutton.core.BBB; - import org.bigbluebutton.main.events.BBBEvent; - import org.bigbluebutton.main.events.ConfigEvent; - import org.bigbluebutton.main.events.LogoutEvent; - import org.bigbluebutton.main.events.SettingsEvent; - import org.bigbluebutton.main.events.ShortcutEvent; - import org.bigbluebutton.main.events.SuccessfulLoginEvent; - import org.bigbluebutton.main.model.LayoutOptions; - import org.bigbluebutton.main.model.users.events.ConnectionFailedEvent; - import org.bigbluebutton.util.i18n.ResourceUtil; + import com.asfusion.mate.events.Dispatcher; + import mx.core.UIComponent; + import org.bigbluebutton.common.IBbbToolbarComponent; + import org.bigbluebutton.common.LogUtil; + import org.bigbluebutton.common.events.CloseWindowEvent; + import org.bigbluebutton.common.events.OpenWindowEvent; + import org.bigbluebutton.common.events.ToolbarButtonEvent; + import org.bigbluebutton.core.BBB; + import org.bigbluebutton.main.events.BBBEvent; + import org.bigbluebutton.main.events.ConfigEvent; + import org.bigbluebutton.main.events.LogoutEvent; + import org.bigbluebutton.main.events.SettingsEvent; + import org.bigbluebutton.main.events.ShortcutEvent; + import org.bigbluebutton.main.events.SuccessfulLoginEvent; + import org.bigbluebutton.main.model.LayoutOptions; + import org.bigbluebutton.main.model.users.events.ConnectionFailedEvent; + import org.bigbluebutton.util.i18n.ResourceUtil; + import org.bigbluebutton.core.services.BandwidthMonitor; + private var DEFAULT_HELP_URL:String = "http://www.bigbluebutton.org/content/videos"; @@ -160,7 +161,7 @@ var d:Dispatcher = new Dispatcher(); d.dispatchEvent(new ShortcutEvent(ShortcutEvent.OPEN_SHORTCUT_WIN)); } - + ]]> </mx:Script> <mx:HBox id="addedBtns"/> @@ -169,8 +170,11 @@ <!-- <mx:Button label="DISCONNECT!" click="BBB.initConnectionManager().forceClose()" height="22" toolTip="Click to simulate disconnection" /> --> - <mx:Button label="{ResourceUtil.getInstance().getString('bbb.mainToolbar.shortcutBtn')}" click="onShortcutButtonClick()" height="22" toolTip="{ResourceUtil.getInstance().getString('bbb.mainToolbar.shortcutBtn.toolTip')}" /> - <mx:LinkButton id="helpBtn" label="{ResourceUtil.getInstance().getString('bbb.mainToolbar.helpBtn')}" visible="{showHelpBtn}" click="onHelpButtonClicked()" height="22"/> - <mx:Button label="{ResourceUtil.getInstance().getString('bbb.mainToolbar.logoutBtn')}" id="btnLogout" + <mx:Button label="{ResourceUtil.getInstance().getString('bbb.mainToolbar.shortcutBtn')}" styleName="shortcutButtonStyle" + click="onShortcutButtonClick()" height="22" + toolTip="{ResourceUtil.getInstance().getString('bbb.mainToolbar.shortcutBtn.toolTip')}" /> + <mx:LinkButton id="helpBtn" label="{ResourceUtil.getInstance().getString('bbb.mainToolbar.helpBtn')}" + visible="{showHelpBtn}" click="onHelpButtonClicked()" height="22" styleName="helpLinkButtonStyle"/> + <mx:Button label="{ResourceUtil.getInstance().getString('bbb.mainToolbar.logoutBtn')}" id="btnLogout" styleName="logoutButtonStyle" toolTip="{ResourceUtil.getInstance().getString('bbb.mainToolbar.logoutBtn.toolTip')}" right="10" click="doLogout()" height="22"/> </mx:ApplicationControlBar> diff --git a/bigbluebutton-client/src/org/bigbluebutton/main/views/MicSettings.mxml b/bigbluebutton-client/src/org/bigbluebutton/main/views/MicSettings.mxml index f8931c8fbd682c36b94acec51569c73365f80911..fdc02cea62602590693544e34a7dcc90658f69dc 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/main/views/MicSettings.mxml +++ b/bigbluebutton-client/src/org/bigbluebutton/main/views/MicSettings.mxml @@ -3,6 +3,7 @@ width="600" height="300" creationComplete="initDefaultMic()" showCloseButton="true" close="onCancelClicked()" keyDown="handleKeyDown(event)"> <mx:Script> <![CDATA[ + import flash.system.Security; import flash.ui.Keyboard; import mx.events.CloseEvent; @@ -23,13 +24,20 @@ private var mic:Microphone; private var playingAudio:Boolean = false; private var images:Images = new Images(); - [Bindable] private var cancelIcon:Class = images.delete_icon; + [Bindable] private var cancelIcon:Class = images.cancel; private function initDefaultMic():void { - mic = Microphone.getMicrophone(-1); - mic.setLoopBack(true); + mic = Microphone.getMicrophone(); + if (mic != null) { - mic.addEventListener(StatusEvent.STATUS, micStatusEventHandler); + if (mic.muted) { + // user has disallowed access to the mic + mic.addEventListener(StatusEvent.STATUS, micStatusEventHandler); + Security.showSettings(); + } else { + // user has allowed access to the mic + mic.setLoopBack(true); + } } else { LogUtil.warn("Microphone is used by another application."); } @@ -51,6 +59,7 @@ LogUtil.warn("Access to microphone has been denied."); break; case "Microphone.Unmuted": + mic.setLoopBack(true); LogUtil.debug("Access to the microphone has been allowed."); break; default: diff --git a/bigbluebutton-client/src/org/bigbluebutton/main/views/NetworkStatsWindow.mxml b/bigbluebutton-client/src/org/bigbluebutton/main/views/NetworkStatsWindow.mxml new file mode 100644 index 0000000000000000000000000000000000000000..0524067aee8c69d783a158300decdd748a158867 --- /dev/null +++ b/bigbluebutton-client/src/org/bigbluebutton/main/views/NetworkStatsWindow.mxml @@ -0,0 +1,141 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- + BigBlueButton open source conferencing system - http://www.bigbluebutton.org + + Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below). + + BigBlueButton is free software; you can redistribute it and/or modify it under the + terms of the GNU Lesser General Public License as published by the Free Software + Foundation; either version 2.1 of the License, or (at your option) any later + version. + + BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License along + with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. + + Author: Felipe Cecagno <felipe@mconf.org> + + $Id: $ +--> + +<MDIWindow xmlns="flexlib.mdi.containers.*" + xmlns:mx="http://www.adobe.com/2006/mxml" + xmlns:mate="http://mate.asfusion.com/" + title="Network monitor" + creationComplete="onCreationComplete()" + resizable="false" + showCloseButton="false" + implements="org.bigbluebutton.common.IBbbModuleWindow" + width="210" height="261" minHeight="0" minWidth="0" + resize="onResize()"> + + <mx:Script> + <![CDATA[ + + import com.asfusion.mate.events.Dispatcher; + + import flash.utils.Timer; + + import org.bigbluebutton.common.events.CloseWindowEvent; + import org.bigbluebutton.common.events.OpenWindowEvent; + import org.bigbluebutton.common.LogUtil; + import org.bigbluebutton.main.model.NetworkStatsData; + + import mx.effects.Fade; + import mx.events.EffectEvent; + import mx.formatters.NumberFormatter; + + private var _globalDispatcher:Dispatcher = new Dispatcher(); + private var _updateTimer:Timer = new Timer(1000); + private var _numberFormatter:NumberFormatter = new NumberFormatter(); + + private function onCreationComplete():void { + this.windowControls.maximizeRestoreBtn.visible = false; + this.windowControls.minimizeBtn.visible = false; + + this.x = parent.width - this.width; + this.y = parent.height - this.height; + + _numberFormatter.precision = 2; + _numberFormatter.useThousandsSeparator = true; + + _updateTimer.addEventListener(TimerEvent.TIMER, timerHandler); + _updateTimer.start(); + } + + private function timerHandler(e:TimerEvent):void { + labelCurrentDownload.text = _numberFormatter.format(NetworkStatsData.getInstance().currentConsumedDownBW); + labelTotalDownload.text = _numberFormatter.format(NetworkStatsData.getInstance().totalConsumedDownBW); + labelAvailableDownload.text = _numberFormatter.format(NetworkStatsData.getInstance().measuredDownBW); + labelDownloadLatency.text = String(NetworkStatsData.getInstance().measuredDownLatency); + + labelCurrentUpload.text = _numberFormatter.format(NetworkStatsData.getInstance().currentConsumedUpBW); + labelTotalUpload.text = _numberFormatter.format(NetworkStatsData.getInstance().totalConsumedUpBW); + labelAvailableUpload.text = _numberFormatter.format(NetworkStatsData.getInstance().measuredUpBW); + labelUploadLatency.text = String(NetworkStatsData.getInstance().measuredUpLatency); + } + + public function getPrefferedPosition():String { + return MainCanvas.ABSOLUTE; + } + + private function onResize():void { + LogUtil.debug("width=" + width + " height=" + height); + } + + public function appear():void { + var fader:Fade = new Fade(); + fader.alphaFrom = 0; + fader.alphaTo = 1; + fader.duration = 500; + fader.target = this; +// fader.addEventListener(EffectEvent.EFFECT_START, function(e:EffectEvent):void { +// var windowEvent:OpenWindowEvent = new OpenWindowEvent(OpenWindowEvent.OPEN_WINDOW_EVENT); +// windowEvent.window = e.currentTarget as IBbbModuleWindow; +// _globalDispatcher.dispatchEvent(windowEvent); +// }); + fader.play(); + var windowEvent:OpenWindowEvent = new OpenWindowEvent(OpenWindowEvent.OPEN_WINDOW_EVENT); + windowEvent.window = this; + _globalDispatcher.dispatchEvent(windowEvent); + this.windowManager.bringToFront(this); + } + + public function disappear():void { + var fader:Fade = new Fade(); + fader.alphaFrom = 1; + fader.alphaTo = 0; + fader.duration = 500; + fader.target = this; + fader.addEventListener(EffectEvent.EFFECT_END, function(e:EffectEvent):void { + var windowEvent:CloseWindowEvent = new CloseWindowEvent(CloseWindowEvent.CLOSE_WINDOW_EVENT); + windowEvent.window = e.target as IBbbModuleWindow; + _globalDispatcher.dispatchEvent(windowEvent); + }); + fader.play(); + } + ]]> + </mx:Script> + + + <mx:Panel width="100%" height="100%" + paddingTop="10" paddingLeft="10" paddingRight="10" paddingBottom="10" headerHeight="10"> + <mx:VBox verticalGap="0" width="100%" height="100%"> + <mx:HBox backgroundColor="haloOrange" width="100%" horizontalAlign="center"><mx:Label fontWeight="bold" text="Upload"/></mx:HBox> + <mx:HBox horizontalGap="0"><mx:Label text="Total: "/><mx:Label id="labelTotalUpload" fontWeight="bold" text="-"/><mx:Label text="MB"/></mx:HBox> + <mx:HBox horizontalGap="0"><mx:Label text="Current: "/><mx:Label id="labelCurrentUpload" fontWeight="bold" text="-"/><mx:Label text="Kb/s"/></mx:HBox> + <mx:HBox horizontalGap="0"><mx:Label text="Available: "/><mx:Label id="labelAvailableUpload" fontWeight="bold" text="-"/><mx:Label text="Mb/s"/></mx:HBox> + <mx:HBox horizontalGap="0"><mx:Label text="Latency: "/><mx:Label id="labelUploadLatency" fontWeight="bold" text="-"/><mx:Label text="ms"/></mx:HBox> + <mx:HBox backgroundColor="haloOrange" width="100%" horizontalAlign="center"><mx:Label fontWeight="bold" text="Download"/></mx:HBox> + <mx:HBox horizontalGap="0"><mx:Label text="Total: "/><mx:Label id="labelTotalDownload" fontWeight="bold" text="-"/><mx:Label text="MB"/></mx:HBox> + <mx:HBox horizontalGap="0"><mx:Label text="Current: "/><mx:Label id="labelCurrentDownload" fontWeight="bold" text="-"/><mx:Label text="Kb/s"/></mx:HBox> + <mx:HBox horizontalGap="0"><mx:Label text="Available: "/><mx:Label id="labelAvailableDownload" fontWeight="bold" text="-"/><mx:Label text="Mb/s"/></mx:HBox> + <mx:HBox horizontalGap="0"><mx:Label text="Latency: "/><mx:Label id="labelDownloadLatency" fontWeight="bold" text="-"/><mx:Label text="ms"/></mx:HBox> + </mx:VBox> + </mx:Panel> + +</MDIWindow> diff --git a/bigbluebutton-client/src/org/bigbluebutton/main/views/VideoHolder.mxml b/bigbluebutton-client/src/org/bigbluebutton/main/views/VideoHolder.mxml new file mode 100755 index 0000000000000000000000000000000000000000..23791e20676ce321ed65a4e90c9aa37be9007231 --- /dev/null +++ b/bigbluebutton-client/src/org/bigbluebutton/main/views/VideoHolder.mxml @@ -0,0 +1,59 @@ +<?xml version="1.0" encoding="utf-8"?> +<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml"> + <mx:Script> + <![CDATA[ + import mx.core.UIComponent; + + import org.bigbluebutton.common.LogUtil; + import org.bigbluebutton.util.i18n.ResourceUtil; + private var hideWarningTimer:Timer = null; + + private function onCreationComplete():void { + this.addChild(_videoHolder); + } + + public function remove(video:Video):void { + _videoHolder.removeChild(video); + } + + public function add(video:Video):void { + _videoHolder.addChild(video); + } + + public function showWarning(resourceName:String, autoHide:Boolean=false, color:String="0xFF0000"):void { + const text:String = ResourceUtil.getInstance().getString(resourceName); + + if (hideWarningTimer != null) { + hideWarningTimer.stop(); + } + + if (autoHide) { + hideWarningTimer = new Timer(3000, 1); + hideWarningTimer.addEventListener(TimerEvent.TIMER, hideWarning); + hideWarningTimer.start(); + } + + // bring the label to front + setChildIndex(lblWarning, getChildren().length - 1); + lblWarning.text = text; + lblWarning.setStyle("color", color); + lblWarning.visible = true; + LogUtil.debug("Showing warning: " + text); + } + + private function hideWarning(e:TimerEvent):void { + lblWarning.visible = false; + } + + ]]> + </mx:Script> + + <mx:Fade id="dissolveOut" duration="1000" alphaFrom="1.0" alphaTo="0.0"/> + <mx:Fade id="dissolveIn" duration="1000" alphaFrom="0.0" alphaTo="1.0"/> + + <mx:UIComponent id="_videoHolder"/> + + <mx:Text id="lblWarning" width="100%" textAlign="center" fontSize="14" fontWeight="bold" + y="{this.height - lblWarning.height - 30}" + visible="true" selectable="false" hideEffect="{dissolveOut}" showEffect="{dissolveIn}"/> +</mx:Canvas> \ No newline at end of file diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/broadcast/events/PlayStreamEvent.as b/bigbluebutton-client/src/org/bigbluebutton/modules/broadcast/events/PlayStreamEvent.as new file mode 100755 index 0000000000000000000000000000000000000000..7efb875d86448ffa050a7e91d6bc51d169629def --- /dev/null +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/broadcast/events/PlayStreamEvent.as @@ -0,0 +1,12 @@ +package org.bigbluebutton.modules.broadcast.events +{ + import flash.events.Event; + + public class PlayStreamEvent extends Event + { + public function PlayStreamEvent(type:String, bubbles:Boolean=false, cancelable:Boolean=false) + { + super(type, bubbles, cancelable); + } + } +} \ No newline at end of file diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/broadcast/events/StreamsListLoadedEvent.as b/bigbluebutton-client/src/org/bigbluebutton/modules/broadcast/events/StreamsListLoadedEvent.as new file mode 100755 index 0000000000000000000000000000000000000000..663f208fe5bb9ad7e039375142aa36a4968d9024 --- /dev/null +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/broadcast/events/StreamsListLoadedEvent.as @@ -0,0 +1,14 @@ +package org.bigbluebutton.modules.broadcast.events +{ + import flash.events.Event; + + public class StreamsListLoadedEvent extends Event + { + public static const STREAMS_LIST_LOADED:String = "broadcast streams list loaded event"; + + public function StreamsListLoadedEvent(type:String, bubbles:Boolean=true, cancelable:Boolean=false) + { + super(type, bubbles, cancelable); + } + } +} \ No newline at end of file diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/broadcast/managers/BroadcastManager.as b/bigbluebutton-client/src/org/bigbluebutton/modules/broadcast/managers/BroadcastManager.as new file mode 100755 index 0000000000000000000000000000000000000000..0e5e9739749f5632c59777fecf6ba5e526325563 --- /dev/null +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/broadcast/managers/BroadcastManager.as @@ -0,0 +1,149 @@ +package org.bigbluebutton.modules.broadcast.managers +{ + import com.asfusion.mate.events.Dispatcher; + + import flash.events.AsyncErrorEvent; + import flash.events.Event; + import flash.events.NetStatusEvent; + import flash.events.SecurityErrorEvent; + import flash.media.Video; + import flash.net.NetConnection; + import flash.net.NetStream; + + import mx.core.UIComponent; + + import org.bigbluebutton.common.LogUtil; + import org.bigbluebutton.common.events.OpenWindowEvent; + import org.bigbluebutton.core.BBB; + import org.bigbluebutton.core.managers.UserManager; + import org.bigbluebutton.main.events.BBBEvent; + import org.bigbluebutton.modules.broadcast.models.BroadcastOptions; + import org.bigbluebutton.modules.broadcast.models.Stream; + import org.bigbluebutton.modules.broadcast.models.Streams; + import org.bigbluebutton.modules.broadcast.services.BroadcastService; + import org.bigbluebutton.modules.broadcast.services.StreamsService; + import org.bigbluebutton.modules.broadcast.views.BroadcastWindow; + + public class BroadcastManager { + private var broadcastWindow:BroadcastWindow; + private var dispatcher:Dispatcher; + private var broadcastService:BroadcastService = new BroadcastService(); + private var streamService:StreamsService; + private var opt:BroadcastOptions; + + [Bindable] + public var streams:Streams = new Streams(); + private var curStream:Stream; + + public function BroadcastManager() { + streamService = new StreamsService(this); + LogUtil.debug("BroadcastManager Created"); + } + + public function start():void { + LogUtil.debug("BroadcastManager Start"); + opt = new BroadcastOptions(); + dispatcher = new Dispatcher(); + streamService.queryAvailableStreams(opt.streamsUri); + } + + public function handleStreamsListLoadedEvent():void { + if (broadcastWindow == null){ + trace("*** BroadcastManager Opening BroadcastModule Window"); + + broadcastWindow = new BroadcastWindow(); + broadcastWindow.options = opt; + broadcastWindow.streams = streams; + + broadcastWindow.broadcastManager = this; + + var e:OpenWindowEvent = new OpenWindowEvent(OpenWindowEvent.OPEN_WINDOW_EVENT); + e.window = broadcastWindow; + dispatcher.dispatchEvent(e); + + } else { + trace("***BroadcastManager Not Opening BroadcastModule Window"); + } + + // sendWhatIsTheCurrentStreamRequest(); + + if (UserManager.getInstance().getConference().amIPresenter()) { + handleSwitchToPresenterMode(); + } else { + handleSwitchToViewerMode(); + } + } + + public function handleSwitchToPresenterMode():void { + if (broadcastWindow != null) { + broadcastWindow.becomePresenter(); + } + } + + public function handleSwitchToViewerMode():void { + if (broadcastWindow != null) { + broadcastWindow.becomeViewer(); + } + + } + + public function playVideo(index:int):void { + trace("BroadcastManager::playVideo [" + streams.streamUrls[index] + "],[" + streams.streamIds[index] + "],[" + streams.streamNames[index] + "]"); + broadcastService.playStream(streams.streamUrls[index], streams.streamIds[index], streams.streamNames[index]); + } + + public function stopVideo():void { + trace("BroadcastManager::stopVideo"); + broadcastService.stopStream(); + } + + public function sendWhatIsTheCurrentStreamRequest():void { + broadcastService.sendWhatIsTheCurrentStreamRequest(); + } + + public function handleWhatIsTheCurrentStreamRequest(event:BBBEvent):void { + trace("BroadcastManager:: Received " + event.payload["messageID"] ); + var isPresenter:Boolean = UserManager.getInstance().getConference().amIPresenter(); + if (isPresenter && curStream != null) { + broadcastService.sendWhatIsTheCurrentStreamReply(event.payload["requestedBy"], curStream.getStreamId()); + } + } + + public function handleWhatIsTheCurrentStreamReply(event:BBBEvent):void { + trace("BroadcastManager:: Received " + event.payload["messageID"] ); + var amIRequester:Boolean = UserManager.getInstance().getConference().amIThisUser(event.payload["requestedBy"]); + if (amIRequester) { + var streamId:String = event.payload["streamID"]; + var info:Object = streams.getStreamNameAndUrl(streamId); + if (info != null) { + playStream(info["url"], streamId, info["name"]); + } + } + } + + private function playStream(url:String, streamId:String, streamName:String):void { + trace("BroadcastManager::playStream [" + url + "], [" + streamId + "], [" + streamName + "]"); + curStream = new Stream(url, streamId, streamName); + broadcastWindow.curStream = curStream; + curStream.play(broadcastWindow); + } + + public function handlePlayStreamRequest(event:BBBEvent):void { + trace("BroadcastManager Received " + event.payload["messageID"]); + playStream(event.payload["uri"], event.payload["streamID"], event.payload["streamName"]); + } + + public function handleStopStreamRequest(event:BBBEvent):void { + trace("BroadcastManager Received " + event.payload["messageID"]); + stopPlayingStream(); + } + + public function stopPlayingStream():void { + if (curStream != null) { + curStream.stop(); + broadcastWindow.curStream = null; + curStream == null; + } + } + } +} \ No newline at end of file diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/broadcast/maps/BroadcastEventMap.mxml b/bigbluebutton-client/src/org/bigbluebutton/modules/broadcast/maps/BroadcastEventMap.mxml new file mode 100755 index 0000000000000000000000000000000000000000..ac84f31993bebf7f6544eb2e2ffd22d5b45bcfe0 --- /dev/null +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/broadcast/maps/BroadcastEventMap.mxml @@ -0,0 +1,89 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- + BigBlueButton open source conferencing system - http://www.bigbluebutton.org + + Copyright (c) 2010 BigBlueButton Inc. and by respective authors (see below). + + BigBlueButton is free software; you can redistribute it and/or modify it under the + terms of the GNU Lesser General Public License as published by the Free Software + Foundation; either version 2.1 of the License, or (at your option) any later + version. + + BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License along + with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. + + $Id: $ +--> + +<EventMap xmlns="http://mate.asfusion.com/" + xmlns:mx="http://www.adobe.com/2006/mxml"> + <mx:Script> + <![CDATA[ + import com.asfusion.mate.events.Dispatcher; + + import mx.events.FlexEvent; + + import org.bigbluebutton.main.events.MadePresenterEvent; + import org.bigbluebutton.modules.broadcast.events.StreamsListLoadedEvent; + import org.bigbluebutton.modules.broadcast.managers.BroadcastManager; + import org.bigbluebutton.modules.broadcast.services.BroadcastService; + import org.bigbluebutton.modules.broadcast.services.MessageReceiver; + import org.bigbluebutton.modules.broadcast.services.MessageSender; + + ]]> + </mx:Script> + + <EventHandlers type="{FlexEvent.PREINITIALIZE}"> + <ObjectBuilder generator="{BroadcastManager}" cache="global"/> + </EventHandlers> + + <EventHandlers type="BroadcastModuleStartEvent"> + <MethodInvoker generator="{BroadcastManager}" method="start" /> + </EventHandlers> + + <EventHandlers type="{MadePresenterEvent.SWITCH_TO_PRESENTER_MODE}"> + <MethodInvoker generator="{BroadcastManager}" method="handleSwitchToPresenterMode" /> + </EventHandlers> + + <EventHandlers type="{MadePresenterEvent.SWITCH_TO_VIEWER_MODE}"> + <MethodInvoker generator="{BroadcastManager}" method="handleSwitchToViewerMode" /> + </EventHandlers> + + <EventHandlers type="{StreamsListLoadedEvent.STREAMS_LIST_LOADED}"> + <MethodInvoker generator="{BroadcastManager}" method="handleStreamsListLoadedEvent" /> + </EventHandlers> + + <EventHandlers type="BroadcastPlayStreamCommand"> + <MethodInvoker generator="{BroadcastManager}" method="handlePlayStreamRequest" arguments="{event}"/> + </EventHandlers> + + <EventHandlers type="BroadcastStopStreamCommand"> + <MethodInvoker generator="{BroadcastManager}" method="handleStopStreamRequest" arguments="{event}"/> + </EventHandlers> + + <EventHandlers type="BroadcastWhatIsTheCurrentStreamRequest"> + <MethodInvoker generator="{BroadcastManager}" method="handleWhatIsTheCurrentStreamRequest" arguments="{event}"/> + </EventHandlers> + + <EventHandlers type="BroadcastWhatIsTheCurrentStreamReply"> + <MethodInvoker generator="{BroadcastManager}" method="handleWhatIsTheCurrentStreamReply" arguments="{event}"/> + </EventHandlers> + +<!-- + <Injectors target="{BroadcastService}"> + <ObjectBuilder generator="{MessageSender}" cache="global"/> + <PropertyInjector targetKey="receiver" source="{MessageReceiver}"/> + <PropertyInjector targetKey="sender" source="{MessageSender}"/> + </Injectors> + + <Injectors target="{MessageReceiver}"> + <PropertyInjector targetKey="dispatcher" source="{scope.dispatcher}"/> + </Injectors> +--> + +</EventMap> diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/broadcast/models/BroadcastModel.as b/bigbluebutton-client/src/org/bigbluebutton/modules/broadcast/models/BroadcastModel.as new file mode 100755 index 0000000000000000000000000000000000000000..32ea3fca2ab427a0267c0c322a211dbaa3c19b62 --- /dev/null +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/broadcast/models/BroadcastModel.as @@ -0,0 +1,9 @@ +package org.bigbluebutton.modules.broadcast.models +{ + public class BroadcastModel + { + public function BroadcastModel() + { + } + } +} \ No newline at end of file diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/broadcast/models/BroadcastOptions.as b/bigbluebutton-client/src/org/bigbluebutton/modules/broadcast/models/BroadcastOptions.as new file mode 100755 index 0000000000000000000000000000000000000000..c70b10995ef588a865fc78d49c29bd9bc3e79e32 --- /dev/null +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/broadcast/models/BroadcastOptions.as @@ -0,0 +1,37 @@ +package org.bigbluebutton.modules.broadcast.models +{ + import org.bigbluebutton.core.BBB; + + public class BroadcastOptions { + [Bindable] + public var streamsUri:String; + + [Bindable] + public var position:String = "absolute"; + + public var showWindowControls:Boolean = false; + public var showStreams:Boolean = true; + public var autoPlay:Boolean = false; + + public function BroadcastOptions() { + var cxml:XML = BBB.getConfigForModule("BroadcastModule"); + if (cxml != null) { + if (cxml.@streamsUri != undefined) { + streamsUri = cxml.@streamsUri.toString(); + } + if (cxml.@showWindowControls != undefined) { + showWindowControls = (cxml.@showWindowControls.toString().toUpperCase() == "TRUE") ? true : false; + } + if (cxml.@showStreams != undefined) { + showStreams = (cxml.@showStreams.toString().toUpperCase() == "TRUE") ? true : false; + } + if (cxml.@autoPlay != undefined) { + autoPlay = (cxml.@autoPlay.toString().toUpperCase() == "TRUE") ? true : false; + } + if (cxml.@position != undefined) { + position = cxml.@position.toString(); + } + } + } + } +} \ No newline at end of file diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/broadcast/models/Stream.as b/bigbluebutton-client/src/org/bigbluebutton/modules/broadcast/models/Stream.as new file mode 100755 index 0000000000000000000000000000000000000000..bf82cf3e54d6bfba49ea65bc5741ea9f8aa00444 --- /dev/null +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/broadcast/models/Stream.as @@ -0,0 +1,230 @@ +package org.bigbluebutton.modules.broadcast.models +{ + import flash.events.AsyncErrorEvent; + import flash.events.NetStatusEvent; + import flash.events.SecurityErrorEvent; + import flash.media.Video; + import flash.net.NetConnection; + import flash.net.NetStream; + + import mx.core.UIComponent; + + import org.bigbluebutton.common.LogUtil; + import org.bigbluebutton.modules.broadcast.views.BroadcastWindow; + + public class Stream { + private var uri:String; + private var streamId:String; + private var streamName:String; + private var window:BroadcastWindow; + private var ns:NetStream; + private var nc:NetConnection; + private var video:Video; + private var videoWidth:int; + private var videoHeight:int; + private var videoHolder:UIComponent; + + [Bindable] + public var width:int = 320; + [Bindable] + public var height:int = 240; + + public function Stream(uri:String, streamId:String, streamName:String) { + videoHolder = new UIComponent(); + this.uri = uri; + this.streamId = streamId; + this.streamName = streamName; + } + + public function play(w:BroadcastWindow):void { + window = w; + connect(); + } + + public function getStreamId():String { + return streamId; + } + + private function displayVideo():void { + video = new Video(); + ns = new NetStream(nc); + ns.client = this; + ns.bufferTime = 0; + ns.receiveVideo(true); + ns.receiveAudio(true); + ns.addEventListener(NetStatusEvent.NET_STATUS, netstreamStatus); + ns.addEventListener(AsyncErrorEvent.ASYNC_ERROR, nsAsyncErrorHandler); + video.attachNetStream(ns); + video.x = videoHolder.x; + video.y = videoHolder.y; + video.width = videoHolder.width; + video.height = videoHolder.height; + + + ns.play(streamId); + } + + private function netstreamStatus(evt:NetStatusEvent):void { + switch(evt.info.code) { + case "NetStream.Play.StreamNotFound": + LogUtil.debug("NetStream.Play.StreamNotFound"); + break; + case "NetStream.Play.Failed": + LogUtil.debug("NetStream.Play.Failed"); + break; + case "NetStream.Play.Start": + LogUtil.debug("NetStream.Play.Start"); + break; + case "NetStream.Play.Stop": + LogUtil.debug("NetStream.Play.Stop"); + break; + case "NetStream.Buffer.Full": + LogUtil.debug("NetStream.Buffer.Full"); + break; + default: + } + } + + private function nsAsyncErrorHandler(event:AsyncErrorEvent):void { + LogUtil.debug("nsAsyncErrorHandler: " + event); + } + + private function connect():void { + LogUtil.debug("Connecting " + uri); + nc = new NetConnection(); + nc.connect(uri); + nc.client = this; + nc.addEventListener(NetStatusEvent.NET_STATUS, netStatus); + nc.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler); + } + + private function netStatus(evt:NetStatusEvent ):void { + switch(evt.info.code) { + case "NetConnection.Connect.Success": + LogUtil.debug("Successfully connected to broadcast application."); + displayVideo(); + break; + case "NetConnection.Connect.Failed": + LogUtil.debug("Failed to connect to broadcast application."); + break; + case "NetConnection.Connect.Closed": + trace("Connection to broadcast application has closed."); + break; + case "NetConnection.Connect.Rejected": + LogUtil.debug("Connection to broadcast application was rejected."); + break; + default: + LogUtil.debug("Connection to broadcast application failed. " + evt.info.code); + } + } + + private function securityErrorHandler(event:SecurityErrorEvent):void { + LogUtil.debug("securityErrorHandler: " + event); + } + + public function onBWCheck(... rest):Number { + return 0; + } + + public function onBWDone(... rest):void { + var p_bw:Number; + if (rest.length > 0) p_bw = rest[0]; + // your application should do something here + // when the bandwidth check is complete + LogUtil.debug("bandwidth = " + p_bw + " Kbps."); + } + + public function stop():void { + window.videoHolderBox.removeChild(videoHolder); + videoHolder.removeChild(video); + ns.close(); + nc.close(); + video = null; + } + + public function onCuePoint(infoObject:Object):void { + LogUtil.debug("onCuePoint"); + } + + public function onMetaData(info:Object):void { + LogUtil.debug("****metadata: width=" + info.width + " height=" + info.height); + trace("****metadata: width=" + info.width + " height=" + info.height); + videoWidth = info.width; + videoHeight = info.height; + + determineHowToDisplayVideo(); + } + + public function onResize():void { + if (video != null) { + determineHowToDisplayVideo(); + } + } + + public function onPlayStatus(infoObject:Object):void { + LogUtil.debug("onPlayStatus"); + } + + private function centerToWindow():void{ + videoHolder.width = video.width = videoWidth; + videoHolder.height = video.height = videoHeight; + videoHolder.x = video.x = (window.width - video.width) / 2; + videoHolder.y = video.y = (window.height - video.height) / 2; + + videoHolder.addChild(video); + window.videoHolderBox.addChild(videoHolder); + } + + private function fitVideoToWindow():void { + if (window.width < window.height) { + fitToWidthAndAdjustHeightToMaintainAspectRatio(); + } else { + fitToHeightAndAdjustWidthToMaintainAspectRatio(); + } + } + + private function videoIsSmallerThanWindow():Boolean { + return (videoHeight < window.height) && (videoWidth < window.width); + } + + private const VIDEO_WIDTH_PADDING:int = 7; + private const VIDEO_HEIGHT_PADDING:int = 65; + + private function fitToWidthAndAdjustHeightToMaintainAspectRatio():void { + videoHolder.width = video.width = window.width - VIDEO_WIDTH_PADDING; + // Maintain aspect-ratio + videoHolder.height = video.height = (videoHeight * video.width) / videoWidth; + videoHolder.x = video.x = 0; + videoHolder.y = video.y = 0; + + videoHolder.addChild(video); + window.videoHolderBox.addChild(videoHolder); + } + + private function fitToHeightAndAdjustWidthToMaintainAspectRatio():void { + videoHolder.height = video.height = window.height - VIDEO_HEIGHT_PADDING; + // Maintain aspect-ratio + videoHolder.width = video.width = (videoWidth * video.height) / videoHeight; + + if (videoHolder.width > window.width - VIDEO_WIDTH_PADDING) { + videoHolder.width = video.width = window.width - VIDEO_WIDTH_PADDING; + videoHolder.height = video.height = (videoHeight * video.width) / videoWidth; + } + + videoHolder.x = video.x = (window.width - VIDEO_WIDTH_PADDING - video.width) / 2; + videoHolder.y = video.y = (window.height - VIDEO_WIDTH_PADDING - video.height) / 2; + + videoHolder.addChild(video); + window.videoHolderBox.addChild(videoHolder); + } + + private function determineHowToDisplayVideo():void { + if (videoIsSmallerThanWindow()) { + centerToWindow(); + } else { + fitVideoToWindow(); + } + } + + } +} \ No newline at end of file diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/broadcast/models/Streams.as b/bigbluebutton-client/src/org/bigbluebutton/modules/broadcast/models/Streams.as new file mode 100755 index 0000000000000000000000000000000000000000..29a72876643ecc02429307a93eb68f7c66a2c66e --- /dev/null +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/broadcast/models/Streams.as @@ -0,0 +1,35 @@ +package org.bigbluebutton.modules.broadcast.models +{ + [Bindable] + public class Streams { + public var streamNames:Array = new Array(); + public var streamUrls:Array = new Array(); + public var streamIds:Array = new Array(); + + + public function getStreamNameAndUrl(streamId:String):Object { + var streamIndex:int = 0; + for (var i:int = 0; i < streamIds.length; i++) { + if (streamId == streamIds[i]) { + var info:Object = new Object(); + info["name"] = streamNames[i]; + info["url"] = streamUrls[i]; + return info; + } + } + + return null; + } + + public function getStreamIndex(streamId:String):Number { + var streamIndex:int = 0; + for (var i:int = 0; i < streamIds.length; i++) { + if (streamId == streamIds[i]) { + return i; + } + } + + return 0; + } + } +} \ No newline at end of file diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/broadcast/services/BroadcastService.as b/bigbluebutton-client/src/org/bigbluebutton/modules/broadcast/services/BroadcastService.as new file mode 100755 index 0000000000000000000000000000000000000000..7e4751253a715470a687e6236a2c5bc8b7f189f6 --- /dev/null +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/broadcast/services/BroadcastService.as @@ -0,0 +1,55 @@ +package org.bigbluebutton.modules.broadcast.services +{ + import com.asfusion.mate.events.Dispatcher; + + import org.bigbluebutton.core.BBB; + import org.bigbluebutton.core.managers.UserManager; + import org.bigbluebutton.main.events.BBBEvent; + + public class BroadcastService { + private var sender:MessageSender; + private var receiver:MessageReceiver; + + public function BroadcastService() { + sender = new MessageSender(); + receiver = new MessageReceiver(); + } + + public function playStream(uri:String, streamID:String, streamName:String):void { + trace("BroadcastService::playStream"); + if (sender == null) { + trace("SENDER is NULL!!!!"); + } +// sender.playStream(uri, streamID, streamName); + + var event:BBBEvent = new BBBEvent("BroadcastPlayStreamCommand"); + event.payload["messageID"] = "BroadcastPlayStreamCommand"; + event.payload["uri"] = uri; + event.payload["streamID"] = streamID; + event.payload["streamName"] = streamName; + + var dispatcher:Dispatcher = new Dispatcher(); + dispatcher.dispatchEvent(event); + + } + + public function stopStream():void { + trace("BroadcastService::stopStream"); +// sender.stopStream(); + + var event:BBBEvent = new BBBEvent("BroadcastStopStreamCommand"); + event.payload["messageID"] = "BroadcastStopStreamCommand"; + + var dispatcher:Dispatcher = new Dispatcher(); + dispatcher.dispatchEvent(event); + } + + public function sendWhatIsTheCurrentStreamRequest():void { + sender.sendWhatIsTheCurrentStreamRequest(); + } + + public function sendWhatIsTheCurrentStreamReply(requestedByUserID:Number, streamID:String):void { + sender.sendWhatIsTheCurrentStreamReply(requestedByUserID, streamID); + } + } +} \ No newline at end of file diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/broadcast/services/MessageReceiver.as b/bigbluebutton-client/src/org/bigbluebutton/modules/broadcast/services/MessageReceiver.as new file mode 100755 index 0000000000000000000000000000000000000000..31352ccebd696bd1db4626e5376afa6df7e0b8f0 --- /dev/null +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/broadcast/services/MessageReceiver.as @@ -0,0 +1,72 @@ +package org.bigbluebutton.modules.broadcast.services +{ + import com.asfusion.mate.events.Dispatcher; + + import org.bigbluebutton.core.BBB; + import org.bigbluebutton.main.events.BBBEvent; + import org.bigbluebutton.main.model.users.IMessageListener; + + public class MessageReceiver implements IMessageListener + { + private var dispatcher:Dispatcher; + + public function MessageReceiver() { + dispatcher = new Dispatcher(); + BBB.initConnectionManager().addMessageListener(this); + } + + public function onMessage(messageName:String, message:Object):void { + trace("Broadcast: received message " + messageName); + + switch (messageName) { + case "BroadcastPlayStreamCommand": + handleBroadcastPlayStreamCommand(message); + break; + case "BroadcastStopStreamCommand": + handleBroadcastStopStreamCommand(message); + break; + case "BroadcastWhatIsTheCurrentStreamRequest": + handleBroadcastWhatIsTheCurrentStreamRequest(message); + break; + case "BroadcastWhatIsTheCurrentStreamReply": + handleBroadcastWhatIsTheCurrentStreamReply(message); + break; + default: + // LogUtil.warn("Cannot handle message [" + messageName + "]"); + } + } + + private function handleBroadcastPlayStreamCommand(message:Object):void { + var event:BBBEvent = new BBBEvent("BroadcastPlayStreamCommand"); + event.payload["messageID"] = "BroadcastPlayStreamCommand"; + event.payload["uri"] = message.uri; + event.payload["streamID"] = message.streamID; + event.payload["streamName"] = message.streamName; + + dispatcher.dispatchEvent(event); + } + + private function handleBroadcastStopStreamCommand(messsage:Object):void { + var event:BBBEvent = new BBBEvent("BroadcastStopStreamCommand"); + event.payload["messageID"] = "BroadcastStopStreamCommand"; + dispatcher.dispatchEvent(event); + } + + private function handleBroadcastWhatIsTheCurrentStreamRequest(message:Object):void { + var event:BBBEvent = new BBBEvent("BroadcastWhatIsTheCurrentStreamRequest"); + event.payload["messageID"] = "BroadcastWhatIsTheCurrentStreamRequest"; + event.payload["requestedBy"] = message.requestedByUserID; + + dispatcher.dispatchEvent(event); + } + + private function handleBroadcastWhatIsTheCurrentStreamReply(message:Object):void { + var event:BBBEvent = new BBBEvent("BroadcastWhatIsTheCurrentStreamReply"); + event.payload["messageID"] = "BroadcastWhatIsTheCurrentStreamReply"; + event.payload["requestedBy"] = message.requestedByUserID; + event.payload["streamID"] = message.streamID; + + dispatcher.dispatchEvent(event); + } + } +} \ No newline at end of file diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/broadcast/services/MessageSender.as b/bigbluebutton-client/src/org/bigbluebutton/modules/broadcast/services/MessageSender.as new file mode 100755 index 0000000000000000000000000000000000000000..42dca4bc04ab7e6727765c2663e4d96ec2f29d66 --- /dev/null +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/broadcast/services/MessageSender.as @@ -0,0 +1,79 @@ +package org.bigbluebutton.modules.broadcast.services +{ + import org.bigbluebutton.common.LogUtil; + import org.bigbluebutton.core.BBB; + import org.bigbluebutton.core.managers.ConnectionManager; + import org.bigbluebutton.core.managers.UserManager; + + public class MessageSender { + public function playStream(uri:String, streamId:String, streamName:String):void { + var message:Object = new Object(); + message["messageID"] = "BroadcastPlayStreamCommand"; + message["uri"] = uri; + message["streamID"] = streamId; + message["streamName"] = streamName; + + var _nc:ConnectionManager = BBB.initConnectionManager(); + _nc.sendMessage("bigbluebutton.sendMessage", + function(result:String):void { // On successful result + trace(result); + }, + function(status:String):void { // status - On error occurred + LogUtil.error(status); + }, + message + ); + } + + public function stopStream():void { + var message:Object = new Object(); + message["messageID"] = "BroadcastStopStreamCommand"; + + var _nc:ConnectionManager = BBB.initConnectionManager(); + _nc.sendMessage("bigbluebutton.sendMessage", + function(result:String):void { // On successful result + trace(result); + }, + function(status:String):void { // status - On error occurred + LogUtil.error(status); + }, + message + ); + } + + public function sendWhatIsTheCurrentStreamRequest():void { + var message:Object = new Object(); + message["messageID"] = "BroadcastWhatIsTheCurrentStreamRequest"; + message["requestedBy"] = UserManager.getInstance().getConference().getMyUserId(); + + var _nc:ConnectionManager = BBB.initConnectionManager(); + _nc.sendMessage("bigbluebutton.sendMessage", + function(result:String):void { // On successful result + trace(result); + }, + function(status:String):void { // status - On error occurred + LogUtil.error(status); + }, + message + ); + } + + public function sendWhatIsTheCurrentStreamReply(requestedByUserID:Number, streamID:String):void { + var message:Object = new Object(); + message["messageID"] = "BroadcastWhatIsTheCurrentStreamReply"; + message["requestedBy"] = requestedByUserID; + message["streamID"] = streamID; + + var _nc:ConnectionManager = BBB.initConnectionManager(); + _nc.sendMessage("bigbluebutton.sendMessage", + function(result:String):void { // On successful result + trace(result); + }, + function(status:String):void { // status - On error occurred + LogUtil.error(status); + }, + message + ); + } + } +} \ No newline at end of file diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/broadcast/services/StreamsService.as b/bigbluebutton-client/src/org/bigbluebutton/modules/broadcast/services/StreamsService.as new file mode 100755 index 0000000000000000000000000000000000000000..ee6eb57fb548eaf90b0d9581fb20ab251413c98e --- /dev/null +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/broadcast/services/StreamsService.as @@ -0,0 +1,55 @@ +package org.bigbluebutton.modules.broadcast.services +{ + import com.asfusion.mate.events.Dispatcher; + + import flash.events.Event; + import flash.net.URLLoader; + import flash.net.URLRequest; + + import org.bigbluebutton.common.LogUtil; + import org.bigbluebutton.modules.broadcast.events.StreamsListLoadedEvent; + import org.bigbluebutton.modules.broadcast.managers.BroadcastManager; + import org.bigbluebutton.modules.broadcast.models.Streams; + + public class StreamsService { + private var streamsXml:XML; + + private var broadcastManager:BroadcastManager; + + public function StreamsService(broadcastManager:BroadcastManager) { + this.broadcastManager = broadcastManager; + } + + public function queryAvailableStreams(uri:String):void { + trace("StreamsService::queryAvailableStreams"); + var urlLoader:URLLoader = new URLLoader(); + urlLoader.addEventListener(Event.COMPLETE, handleComplete); + var date:Date = new Date(); + + urlLoader.load(new URLRequest(uri + "?a=" + date.time)); + } + + private function handleComplete(e:Event):void { + streamsXml = new XML(e.target.data); + LogUtil.debug(streamsXml); + trace("StreamsService::handleComplete\n" + streamsXml.toXMLString()); + var mn:XMLList = streamsXml.stream..@name; + + for each (var n:XML in mn) { + LogUtil.debug(n); + } + var item:XML; + var list:XMLList = streamsXml.children(); + + for each(item in list) { + LogUtil.debug(item.@url + " " + item.@name + " " + item.@id); + broadcastManager.streams.streamNames.push(item.@name); + broadcastManager.streams.streamUrls.push(item.@url); + broadcastManager.streams.streamIds.push(item.@id); + } + + var dispatcher:Dispatcher = new Dispatcher(); + dispatcher.dispatchEvent(new StreamsListLoadedEvent(StreamsListLoadedEvent.STREAMS_LIST_LOADED)); + } + } +} \ No newline at end of file diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/broadcast/views/BroadcastWindow.mxml b/bigbluebutton-client/src/org/bigbluebutton/modules/broadcast/views/BroadcastWindow.mxml new file mode 100755 index 0000000000000000000000000000000000000000..3a0418bd938726eb98c7514e85c3875794ed6e20 --- /dev/null +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/broadcast/views/BroadcastWindow.mxml @@ -0,0 +1,118 @@ +<?xml version="1.0" encoding="utf-8"?> +<bcast:MDIWindow xmlns:mx="http://www.adobe.com/2006/mxml" + xmlns:bcast="flexlib.mdi.containers.*" + implements="org.bigbluebutton.common.IBbbModuleWindow" + styleNameFocus="broadcastStyleFocus" + styleNameNoFocus="broadcastStyleNoFocus" + creationComplete="onCreationComplete()" + visible="false" + xmlns:mate="http://mate.asfusion.com/" showCloseButton="false" + layout="absolute" title="Broadcast" resize="onResize()" resizeEndMDIWindow="onResize()"> + + <mx:Script> + <![CDATA[ + import flexlib.mdi.events.MDIWindowEvent; + import mx.core.UIComponent; + import mx.events.ResizeEvent; + import mx.skins.Border; + import org.bigbluebutton.common.LogUtil; + import org.bigbluebutton.modules.broadcast.managers.BroadcastManager; + import org.bigbluebutton.modules.broadcast.models.BroadcastOptions; + import org.bigbluebutton.modules.broadcast.models.Stream; + import org.bigbluebutton.modules.broadcast.models.Streams; + public var broadcastManager:BroadcastManager; + public var curStream:Stream; + + public var options:BroadcastOptions; + private var createComplete:Boolean = false; + + [Bindable] + public var streams:Streams; + + private var autoPlayTimer:Timer + + private function onCreationComplete():void { + options = new BroadcastOptions(); + streamsList.visible = options.showStreams; + this.showControls = options.showWindowControls; + + createComplete = true; + + autoPlayTimer = new Timer(3000,1); + autoPlayTimer.addEventListener(TimerEvent.TIMER, onTimerComplete); + autoPlayTimer.start(); + } + + private function onTimerComplete(event:TimerEvent = null):void { + if (options.autoPlay && visible) { + // Don't start playing the video if the video is hidden. + broadcastManager.playVideo(0); + } + } + + public function becomePresenter():void { + if (options.showStreams) { + videoStreamList.visible = true; + } + + if (curStream != null) { + playBtn.selected = true; + playBtn.label = "Stop"; + streamsComboBox.selectedIndex = streams.getStreamIndex(curStream.getStreamId()); + } else { + playBtn.selected = false; + playBtn.label = "Play"; + streamsComboBox.selectedIndex = 0; + } + } + + public function becomeViewer():void{ + if (options.showStreams) { + videoStreamList.visible = false; + } + + if (curStream != null) { + playBtn.selected = true; + playBtn.label = "Stop"; + streamsComboBox.selectedIndex = streams.getStreamIndex(curStream.getStreamId()); + } else { + playBtn.selected = false; + playBtn.label = "Play"; + streamsComboBox.selectedIndex = 0; + } + } + + private function playStopVideo():void { + if (playBtn.selected) { + broadcastManager.playVideo(streamsComboBox.selectedIndex); + playBtn.label = "Stop"; + } else { + playBtn.label = "Play"; + stopVideo(); + } + } + + private function stopVideo():void { + broadcastManager.stopVideo(); + } + + public function getPrefferedPosition():String { + return options.position; + } + + private function onResize():void { + if (curStream != null && createComplete) { + curStream.onResize(); + } + } + + ]]> + </mx:Script> + <mx:HBox id="videoHolderBox" width="100%" height="100%" /> + <mx:ControlBar id="streamsList" visible="false" width="100%" height="30" paddingTop="0" borderStyle="outset" horizontalAlign="center"> + <mx:HBox x="1" y="1" id="videoStreamList" visible="false" width="100%" height="30" horizontalAlign="center"> + <mx:ComboBox id="streamsComboBox" width="85%" dataProvider="{streams.streamNames}" /> + <mx:Button id="playBtn" label="Play" width="15% " click="playStopVideo()" toggle="true"/> + </mx:HBox> + </mx:ControlBar> +</bcast:MDIWindow> diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/chat/views/AdvancedList.as b/bigbluebutton-client/src/org/bigbluebutton/modules/chat/views/AdvancedList.as new file mode 100755 index 0000000000000000000000000000000000000000..ef19b3c416dae5c6ced9eb87edd040c95a63799b --- /dev/null +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/chat/views/AdvancedList.as @@ -0,0 +1,19 @@ +package org.bigbluebutton.modules.chat.views +{ + import mx.controls.List; + + public class AdvancedList extends List + { + public function AdvancedList() + { + super(); + } + + override protected function measure():void + { + super.measure(); + //sovled on forum by Flex HarUI + measuredHeight = measureHeightOfItems() + viewMetrics.top + viewMetrics.bottom; + } + } +} \ No newline at end of file diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/chat/views/ChatBox.mxml b/bigbluebutton-client/src/org/bigbluebutton/modules/chat/views/ChatBox.mxml index 970872e16754f523d907cfa84463906569dff1af..dcbbf4d5130d1596938adc6d7d84b8ebf863951a 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/chat/views/ChatBox.mxml +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/chat/views/ChatBox.mxml @@ -55,7 +55,7 @@ <mate:Listener type="{ShortcutEvent.GOREAD_MESSAGE}" method="goToLatestReadMessage" /> <mate:Listener type="{PrivateChatMessageEvent.PRIVATE_CHAT_MESSAGE_EVENT}" method="handlePrivateChatMessageEvent"/> <mate:Listener type="{PublicChatMessageEvent.PUBLIC_CHAT_MESSAGE_EVENT}" method="handlePublicChatMessageEvent"/> - <mate:Listener type="{ParticipantJoinEvent.PARTICIPANT_JOINED_EVENT}" method="handleParticipantJoinEvent"/> + <mate:Listener type="{UserLeftEvent.LEFT}" method="handleUserLeftEvent"/> <mx:Script> <![CDATA[ @@ -69,8 +69,9 @@ import org.bigbluebutton.common.LogUtil; import org.bigbluebutton.core.UsersUtil; - import org.bigbluebutton.main.events.ParticipantJoinEvent; import org.bigbluebutton.main.events.ShortcutEvent; + import org.bigbluebutton.main.events.UserJoinedEvent; + import org.bigbluebutton.main.events.UserLeftEvent; import org.bigbluebutton.modules.chat.ChatConstants; import org.bigbluebutton.modules.chat.ChatUtil; import org.bigbluebutton.modules.chat.events.ChatOptionsEvent; @@ -158,10 +159,7 @@ // Listen for the ENTER key to send the message. txtMsgArea.addEventListener(KeyboardEvent.KEY_UP, handleTextAreaKeyUpEvent); - - // Put the cursor on the txt message area when this chat box is in focus - this.addEventListener(FocusEvent.FOCUS_IN, handleFocusInEvent); - + Accessibility.updateProperties(); focusToTextMessageArea(); @@ -198,9 +196,9 @@ } } - private function handleParticipantJoinEvent(event:ParticipantJoinEvent):void { + private function handleUserLeftEvent(event:UserLeftEvent):void { // Handle user leaving so that the user won't be talking to someone not there. - if (!publicChat && !event.join && event.userID == chatWithUserID) { + if (!publicChat && event.userID == chatWithUserID) { displayUserHasLeftMessage(); txtMsgArea.enabled = false; scrollToEndOfMessage(); @@ -223,11 +221,7 @@ chatMessages.newChatMessage(msg); } - - private function handleFocusInEvent(event:FocusEvent):void { - focusToTextMessageArea(); - } - + public function focusToTextMessageArea():void { txtMsgArea.setFocus(); txtMsgArea.drawFocus(true); @@ -267,11 +261,11 @@ * doing as I don't know why it works, but it does. (ralam Nov 3, 2010) */ private function scrollToBottom():void { -// chatMessagesList.invalidateSize(); -// chatMessagesList.invalidateDisplayList(); - chatMessagesList.scrollToIndex(chatMessages.numMessages()); -// chatMessagesList.validateDisplayList(); -// chatMessagesList.validateNow(); + chatMessagesList.invalidateSize(); + chatMessagesList.invalidateDisplayList(); + chatMessagesList.validateDisplayList(); + chatMessagesList.validateNow(); + chatMessagesList.scrollToIndex(chatMessages.numMessages()); } private function onScrollTimer(event:TimerEvent):void { @@ -480,7 +474,7 @@ cm.fromTime = now.valueOf(); cm.fromTimezoneOffset = now.getTimezoneOffset(); - cm.message = ChatUtil.cleanup(message); + cm.message = ChatUtil.parseURLs(ChatUtil.cleanup(message)); publicEvent.chatMessage = cm; globalDispatcher.dispatchEvent(publicEvent); @@ -534,17 +528,18 @@ </mx:Script> - <mx:HBox width="100%" height="{chatListHeight}"> - <mx:List width="100%" height="{chatListHeight}" id="chatMessagesList" selectable="false" variableRowHeight="true" - itemRenderer="org.bigbluebutton.modules.chat.views.ChatMessageRenderer" + <mx:HBox width="100%" height="{chatListHeight}" verticalScrollPolicy="off"> + <chat:AdvancedList width="100%" height="{chatListHeight}" id="chatMessagesList" selectable="true" variableRowHeight="true" alternatingItemColors="[#EFEFEF, #FEFEFE]" + itemRenderer="org.bigbluebutton.modules.chat.views.ChatMessageRenderer" verticalScrollPolicy="auto" wordWrap="true" dataProvider="{chatMessages.messages}" /> </mx:HBox> - <mx:HBox id="chatCtrlBar" width="100%" height="50" styleName="chatControlBarStyle" paddingLeft="5" paddingRight="5"> + <mx:HBox id="chatCtrlBar" width="100%" height="50" styleName="chatControlBarStyle" verticalScrollPolicy="off" + paddingLeft="5" paddingRight="5"> <mx:TextArea id="txtMsgArea" width="100%" color="{cmpColorPicker.selectedColor}" styleName="chatControlBarTextMsgStyle" toolTip="{ResourceUtil.getInstance().getString('bbb.accessibility.chat.chatwindow.input')}"/> - <mx:VBox> + <mx:VBox verticalScrollPolicy="off"> <mx:Button label="{ResourceUtil.getInstance().getString('bbb.chat.sendBtn')}" id="sendBtn" styleName="chatControlBarSendButtonStyle" toolTip="{ResourceUtil.getInstance().getString('bbb.chat.sendBtn.toolTip')}" diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/chat/views/ChatControlBar.mxml b/bigbluebutton-client/src/org/bigbluebutton/modules/chat/views/ChatControlBar.mxml index c7e0aa2ae67e8b525e6f661e5527bf20b7eab6a2..7f841ec434e9da3bb4237055c7a906c8d2d8014d 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/chat/views/ChatControlBar.mxml +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/chat/views/ChatControlBar.mxml @@ -5,6 +5,6 @@ styleName="chatControlBarTextMsgStyle" toolTip="{ResourceUtil.getInstance().getString('bbb.accessibility.chat.chatwindow.input')}"/> <mx:ColorPicker id="cmpColorPicker" showTextField="false" toolTip="{ResourceUtil.getInstance().getString('bbb.chat.cmpColorPicker.toolTip')}" selectedColor="0x000000" dataProvider="{chatView.colorPickerColours}" swatchPanelStyleName="chatColorPickerStyle"/> - <mx:Button label="{ResourceUtil.getInstance().getString('bbb.chat.sendBtn')}" id="sendBtn" styleName="chatControlBarSendButtonStyle" + <mx:Button label="{ResourceUtil.getInstance().getString('bbb.chat.sendBtn')}" id="sendBtn" styleName="chatSendButtonStyle" toolTip="{ResourceUtil.getInstance().getString('bbb.chat.sendBtn.toolTip')}" click="chatView.sendMessages()"/> </mx:ControlBar> diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/chat/views/ChatMessageRenderer.mxml b/bigbluebutton-client/src/org/bigbluebutton/modules/chat/views/ChatMessageRenderer.mxml index 7dc0cdb22942e3b8779ade49f3ec3200d20e715b..cf6cc4b5e88fed3f4bb9b5bb515f75d15067cadf 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/chat/views/ChatMessageRenderer.mxml +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/chat/views/ChatMessageRenderer.mxml @@ -20,7 +20,7 @@ $Id: $ --> -<mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml" verticalAlign="top" height="{txtMessage.height + 3}" +<mx:VBox xmlns:mx="http://www.adobe.com/2006/mxml" verticalAlign="top" verticalScrollPolicy="off" xmlns:common="org.bigbluebutton.common.*" dataChange="validateNow()" creationComplete="onCreationComplete()"> @@ -35,6 +35,7 @@ [Bindable] private var chatTime:String; private function onLinkClick(e:TextEvent):void{ + trace("Clicked on link[" + e.text + "] from chat"); var url:URLRequest = new URLRequest(e.text); navigateToURL(url, '_blank'); } @@ -80,10 +81,16 @@ ]]> </mx:Script> - <mx:Label maxWidth="50" width="50" truncateToFit="true" id="lblName" text="{chatMsg.name} " visible="{!(chatMsg.senderId == chatMsg.lastSenderId)}" color="gray" /> - <mx:Text id="txtMessage" htmlText="{rolledOver ? chatMsg.senderText : chatMsg.translatedText}" link="onLinkClick(event)" color="{chatMsg.senderColor}" - rollOver="onRollOver()" rollOut="onRollOut()" width="100%" /> - <mx:Text id="lblTime" htmlText="{chatMsg.translateLocale} {rolledOver ? chatMsg.senderTime : chatMsg.time}" - visible="{!(chatMsg.lastTime == chatMsg.time) || !(chatMsg.senderId == chatMsg.lastSenderId)}" - color="gray" /> -</mx:HBox> + <mx:HBox width="100%"> + <mx:Label id="lblName" text="{chatMsg.name} " visible="{!(chatMsg.senderId == chatMsg.lastSenderId)}" color="gray" width="100%" textAlign="left"/> + <mx:Spacer width="100%"/> + <mx:Text id="lblTime" htmlText="{chatMsg.translateLocale} {rolledOver ? chatMsg.senderTime : chatMsg.time}" textAlign="right" + visible="{!(chatMsg.lastTime == chatMsg.time) || !(chatMsg.senderId == chatMsg.lastSenderId)}" + color="gray" width="100%"/> + </mx:HBox> + <mx:HBox width="100%"> + <mx:Spacer width="5"/> + <mx:Text id="txtMessage" htmlText="{rolledOver ? chatMsg.senderText : chatMsg.translatedText}" link="onLinkClick(event)" color="{chatMsg.senderColor}" + rollOver="onRollOver()" rollOut="onRollOut()" width="100%" selectable="true"/> + </mx:HBox> +</mx:VBox> diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/chat/views/ChatView.mxml b/bigbluebutton-client/src/org/bigbluebutton/modules/chat/views/ChatView.mxml index f70b284bb9925f56052ff74e2a1d4d2ec158e7bc..d877f923089e3163ba42ef97de6dca7b4f3c4821 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/chat/views/ChatView.mxml +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/chat/views/ChatView.mxml @@ -63,7 +63,6 @@ import org.bigbluebutton.core.events.CoreEvent; import org.bigbluebutton.core.managers.UserManager; import org.bigbluebutton.main.events.BBBEvent; - import org.bigbluebutton.main.events.ParticipantJoinEvent; import org.bigbluebutton.main.events.ShortcutEvent; import org.bigbluebutton.main.model.users.BBBUser; import org.bigbluebutton.modules.chat.ChatUtil; diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/layout/managers/LayoutManager.as b/bigbluebutton-client/src/org/bigbluebutton/modules/layout/managers/LayoutManager.as index eef563a5976b7d0f68e8f08c8bc5a8e7f6aae4b2..aad685d1acae7ef72e735bbdf5d99885d533f483 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/layout/managers/LayoutManager.as +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/layout/managers/LayoutManager.as @@ -39,6 +39,7 @@ package org.bigbluebutton.modules.layout.managers import org.bigbluebutton.common.LogUtil; import org.bigbluebutton.core.EventBroadcaster; + import org.bigbluebutton.core.events.SwitchedLayoutEvent; import org.bigbluebutton.core.managers.UserManager; import org.bigbluebutton.core.model.Config; import org.bigbluebutton.main.events.ModuleLoadEvent; @@ -195,23 +196,46 @@ package org.bigbluebutton.modules.layout.managers var newLayout:LayoutDefinition = _layouts.getLayout(name); if (newLayout == null) return; - LogUtil.debug("************** USING [" + newLayout.name + "] LAYOUT ***************************"); + trace("************** USING [" + newLayout.name + "] as new LAYOUT ***************************"); applyLayout(newLayout); - sendLayoutUpdate(_currentLayout); + sendLayoutUpdate(_currentLayout); + + dispatchSwitchedLayoutEvent(newLayout.name); } - public function applyDefaultLayout():void { + public function applyDefaultLayout():void { + var layoutOptions:LayoutOptions = new LayoutOptions(); layoutOptions.parseOptions(); var defaultLayout:LayoutDefinition = _layouts.getLayout(layoutOptions.defaultLayout); + + var sessionDefaulLayout:String = UserManager.getInstance().getConference().getDefaultLayout(); + + + if (sessionDefaulLayout != "NOLAYOUT") { + var sesLayout:LayoutDefinition = _layouts.getLayout(sessionDefaulLayout); + if (sesLayout != null) { + defaultLayout = sesLayout; + } + } + if (defaultLayout == null) { defaultLayout = _layouts.getDefault(); } - LogUtil.debug("************** USING [" + defaultLayout.name + "] LAYOUT ***************************"); + + trace("************** USING [" + defaultLayout.name + "] as default LAYOUT ***************************"); applyLayout(defaultLayout); sendLayoutUpdate(_currentLayout); + + dispatchSwitchedLayoutEvent(defaultLayout.name); } + private function dispatchSwitchedLayoutEvent(layoutID:String):void { + var layoutEvent:SwitchedLayoutEvent = new SwitchedLayoutEvent(); + layoutEvent.layoutID = layoutID; + _globalDispatcher.dispatchEvent(layoutEvent); + } + public function lockLayout():void { _locked = true; LogUtil.debug("LayoutManager: layout locked by myself"); diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/layout/model/LayoutDefinition.as b/bigbluebutton-client/src/org/bigbluebutton/modules/layout/model/LayoutDefinition.as index 92bc29a16f718e174c5208d41c6388e06c42f909..fea809c7d9140cc4ecb0b39c1e3b58a9fdfd3f09 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/layout/model/LayoutDefinition.as +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/layout/model/LayoutDefinition.as @@ -35,15 +35,11 @@ package org.bigbluebutton.modules.layout.model { [Bindable] public var defaultLayout:Boolean = false; private var _windows:Dictionary = new Dictionary(); - static private var _ignoredWindows:Array = new Array("PublishWindow", + static private var _ignoredWindows:Array = new Array("AvatarWindow", "PublishWindow", "VideoWindow", "DesktopPublishWindow", "DesktopViewWindow", "LogWindow"); static private var _roles:Array = new Array(Role.VIEWER, Role.MODERATOR, Role.PRESENTER); - - public function LayoutDefinition() { - - } - + private function loadLayout(vxml:XML):void { if (vxml.@name != undefined) { name = vxml.@name.toString(); @@ -81,18 +77,19 @@ package org.bigbluebutton.modules.layout.model { var hasModeratorLayout:Boolean = _windows.hasOwnProperty(Role.MODERATOR); var hasPresenterLayout:Boolean = _windows.hasOwnProperty(Role.PRESENTER); - if (UserManager.getInstance().getConference().amIPresenter() && hasPresenterLayout) - return _windows[Role.PRESENTER]; - else if (UserManager.getInstance().getConference().amIModerator() && hasModeratorLayout) - return _windows[Role.MODERATOR]; - else if (hasViewerLayout) - return _windows[Role.VIEWER]; - else if (hasModeratorLayout) - return _windows[Role.MODERATOR]; - else if (hasPresenterLayout) - return _windows[Role.PRESENTER]; - else { + if (UserManager.getInstance().getConference().amIPresenter() && hasPresenterLayout) { + return _windows[Role.PRESENTER]; + } else if (UserManager.getInstance().getConference().amIModerator() && hasModeratorLayout) { + return _windows[Role.MODERATOR]; + } else if (hasViewerLayout) { + return _windows[Role.VIEWER]; + } else if (hasModeratorLayout) { + return _windows[Role.MODERATOR]; + } else if (hasPresenterLayout) { + return _windows[Role.PRESENTER]; + } else { LogUtil.error("There's no layout that fits the participants profile"); + trace("LayoutDefinition::getMyLayout There's no layout that fits the participants profile"); return null; } } @@ -189,11 +186,15 @@ package org.bigbluebutton.modules.layout.model { } public function applyToWindow(canvas:MDICanvas, window:MDIWindow, type:String=null):void { - if (type == null) - type = WindowLayout.getType(window); + if (type == null) { + type = WindowLayout.getType(window); + } + - if (!ignoreWindowByType(type)) - WindowLayout.setLayout(canvas, window, myLayout[type]); + if (!ignoreWindowByType(type)) { + trace("LayoutDefinition::applyToWindow [" + window.name + ", type=" + type + "]"); + WindowLayout.setLayout(canvas, window, myLayout[type]); + } } static private function ignoreWindowByType(type:String):Boolean { diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/layout/model/WindowLayout.as b/bigbluebutton-client/src/org/bigbluebutton/modules/layout/model/WindowLayout.as index 4610e0c45d9cf2d218a51da13e2ecdec6f1cf8c6..1c0200579610a476176e7ab53c1710e49084beee 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/layout/model/WindowLayout.as +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/layout/model/WindowLayout.as @@ -23,13 +23,11 @@ package org.bigbluebutton.modules.layout.model { import flexlib.mdi.containers.MDICanvas; import flexlib.mdi.containers.MDIWindow; - import mx.effects.Fade; import mx.effects.Move; import mx.effects.Parallel; import mx.effects.Resize; import mx.events.EffectEvent; - import flash.display.DisplayObject; import flash.display.DisplayObjectContainer; import flash.utils.Dictionary; @@ -47,12 +45,15 @@ package org.bigbluebutton.modules.layout.model { [Bindable] public var minimized:Boolean = false; [Bindable] public var maximized:Boolean = false; [Bindable] public var hidden:Boolean = false; + [Bindable] public var resizable:Boolean = true; + [Bindable] public var draggable:Boolean = true; [Bindable] public var order:int = -1; static private var EVENT_DURATION:int = 500; public function load(vxml:XML):void { +// trace("Load layout \n" + vxml.toXMLString() + "\n"); if (vxml != null) { if (vxml.@name != undefined) { name = vxml.@name.toString(); @@ -78,10 +79,19 @@ package org.bigbluebutton.modules.layout.model { if (vxml.@hidden != undefined) { hidden = (vxml.@hidden.toString().toUpperCase() == "TRUE") ? true : false; } + if (vxml.@draggable != undefined) { + draggable = (vxml.@draggable.toString().toUpperCase() == "TRUE") ? true : false; + } + if (vxml.@resizable != undefined) { + resizable = (vxml.@resizable.toString().toUpperCase() == "TRUE") ? true : false; + } if (vxml.@order != undefined) { order = int(vxml.@order); } } + +// trace("WindowLayout::load for " + name + " [minimized=" + minimized + ",maximized=" +// + maximized + ",hidden=" + hidden + ",drag=" + draggable + ",resize=" + resizable + "]"); } static public function getLayout(canvas:MDICanvas, window:MDIWindow):WindowLayout { @@ -93,13 +103,27 @@ package org.bigbluebutton.modules.layout.model { layout.y = window.y / canvas.height; layout.minimized = window.minimized; layout.maximized = window.maximized; + layout.resizable = window.resizable; + layout.draggable = window.draggable; layout.hidden = !window.visible; layout.order = OrderManager.getInstance().getOrderByRef(window); + +// trace("WindowLayout::getLayout for " + layout.name + " [minimized=" + layout.minimized + ",maximized=" + layout.maximized + ",hidden=" + layout.hidden +// + ",drag=" + layout.draggable + ",resize=" + layout.resizable + "]"); + return layout; } static public function setLayout(canvas:MDICanvas, window:MDIWindow, layout:WindowLayout):void { - if (layout == null) return; +// trace("WindowLayout::setLayout for " + window.name + ",layout=" + layout.name + "]"); + + if (layout == null) { + return; + } + +// trace("WindowLayout::setLayout [minimized=" + layout.minimized + ",maximized=" + layout.maximized + ",hidden=" + layout.hidden +// + ",drag=" + layout.draggable + ",resize=" + layout.resizable + "]"); + layout.applyToWindow(canvas, window); } @@ -113,11 +137,14 @@ package org.bigbluebutton.modules.layout.model { } private function onTimerComplete(event:TimerEvent = null):void { +// trace("::onTimerComplete"); var obj:Object = _delayedEffects.pop(); applyToWindow(obj.canvas, obj.window); } public function applyToWindow(canvas:MDICanvas, window:MDIWindow):void { +// trace("WindowLayout::applyToWindow for " + window.name + " using layout " + this.name + "]"); + var effect:Parallel = new Parallel(); effect.duration = EVENT_DURATION; effect.target = window; @@ -130,6 +157,9 @@ package org.bigbluebutton.modules.layout.model { window.visible = true; } + window.draggable = this.draggable; + window.resizable = this.resizable; + if (this.minimized) { if (!window.minimized) window.minimize(); } else if (this.maximized) { @@ -165,29 +195,19 @@ package org.bigbluebutton.modules.layout.model { } } - - - /* - var layoutHidden:Boolean = this.hidden; -// var windowVisible:Boolean = (window.alpha == 1); - var windowVisible:Boolean = window.visible; - if (windowVisible == layoutHidden) { - var fader:Fade = new Fade(); - fader.alphaFrom = (layoutHidden? 1: 0); - fader.alphaTo = (layoutHidden? 0: 1); - fader.addEventListener(EffectEvent.EFFECT_START, function(e:EffectEvent):void { - if (!windowVisible) - window.enabled = window.visible = true; - }); - fader.addEventListener(EffectEvent.EFFECT_END, function(e:EffectEvent):void { - if (windowVisible) - window.enabled = window.visible = false; - }); - effect.addChild(fader); - } -*/ - if (effect.children.length > 0) + if (window.visible && this.hidden) { + window.visible = false; + } + +// trace("WindowLayout::applyToWindow Layout= [minimized=" + this.minimized + ",maximized=" + this.maximized + ",visible=" + !this.hidden +// + ",drag=" + this.draggable + ",resize=" + this.resizable + "]"); + +// trace("WindowLayout::applyToWindow Window = [minimized=" + window.minimized + ",maximized=" + window.maximized + ",visible=" + window.visible +// + ",drag=" + window.draggable + ",resize=" + window.resizable + "]"); + + if (effect.children.length > 0) { effect.play(); + } } static public function getType(obj:Object):String { diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/listeners/business/ListenersSOService.as b/bigbluebutton-client/src/org/bigbluebutton/modules/listeners/business/ListenersSOService.as index a144cb30a5d6d55ae0658b2e0d6b01467ff16aad..edbc3dbb6358872cc57a19e8a4f35a68f05b398e 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/listeners/business/ListenersSOService.as +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/listeners/business/ListenersSOService.as @@ -18,14 +18,12 @@ */ package org.bigbluebutton.modules.listeners.business { - import com.asfusion.mate.events.Dispatcher; - + import com.asfusion.mate.events.Dispatcher; import flash.events.AsyncErrorEvent; import flash.events.NetStatusEvent; import flash.net.NetConnection; import flash.net.Responder; - import flash.net.SharedObject; - + import flash.net.SharedObject; import org.bigbluebutton.common.LogUtil; import org.bigbluebutton.core.EventConstants; import org.bigbluebutton.core.UsersUtil; @@ -141,6 +139,10 @@ package org.bigbluebutton.modules.listeners.business bu.voiceUserid = n.userid; bu.voiceMuted = n.muted; bu.voiceJoined = true; + + var bbbEvent:BBBEvent = new BBBEvent(BBBEvent.USER_VOICE_JOINED); + bbbEvent.payload.userID = bu.userID; + globalDispatcher.dispatchEvent(bbbEvent); } n.callerName = result[2]; /* Store the username */ @@ -149,35 +151,49 @@ package org.bigbluebutton.modules.listeners.business LogUtil.info(LOGNAME + "Adding listener [" + n.callerName + "," + userId + "]"); _listeners.addListener(n); - globalDispatcher.dispatchEvent(new BBBEvent(BBBEvent.ADDED_LISTENER, n.callerName)); +// globalDispatcher.dispatchEvent(new BBBEvent(BBBEvent.USER_VOICE_JOINED, n.callerName)); } else { LogUtil.debug(LOGNAME + "There is a listener with userid " + userId + " " + cidName + " in the conference."); } } - public function userMute(userId:Number, mute:Boolean):void { - var l:Listener = _listeners.getListener(userId); + public function userMute(userID:Number, mute:Boolean):void { + var l:Listener = _listeners.getListener(userID); if (l != null) { l.muted = mute; + + if (l.muted) { + // When the user is muted, set the talking flag to false so that the UI will not display the + // user as talking even if muted. + userTalk(userID, false); + } + /** * Let's store the voice userid so we can do push to talk. */ - if (UserManager.getInstance().getConference().amIThisVoiceUser(userId)) { + if (UserManager.getInstance().getConference().amIThisVoiceUser(userID)) { UserManager.getInstance().getConference().muteMyVoice(l.muted); } - var bu:BBBUser = UsersUtil.getVoiceUser(userId) + var bu:BBBUser = UsersUtil.getVoiceUser(userID) if (bu != null) { bu.voiceMuted = l.muted; - } - } + + LogUtil.debug("[" + bu.name + "] is now muted=[" + bu.voiceMuted + "]"); + + var bbbEvent:BBBEvent = new BBBEvent(BBBEvent.USER_VOICE_MUTED); + bbbEvent.payload.muted = mute; + bbbEvent.payload.userID = bu.userID; + globalDispatcher.dispatchEvent(bbbEvent); + } + } } public function userLockedMute(userId:Number, locked:Boolean):void { var l:Listener = _listeners.getListener(userId); if (l != null) { l.locked = locked; - LogUtil.debug(LOGNAME + 'Lock Un/Muting user ' + userId + " locked=" + locked); + /** * Let's store the voice userid so we can do push to talk. */ @@ -188,12 +204,19 @@ package org.bigbluebutton.modules.listeners.business var bu:BBBUser = UsersUtil.getVoiceUser(userId) if (bu != null) { bu.voiceLocked = l.locked; + LogUtil.debug("[" + bu.name + "] is now locked=[" + bu.voiceLocked + "] muted=[" + bu.voiceMuted + "]"); + + var bbbEvent:BBBEvent = new BBBEvent(BBBEvent.USER_VOICE_LOCKED); + bbbEvent.payload.locked = bu.voiceLocked; + bbbEvent.payload.userID = bu.userID; + globalDispatcher.dispatchEvent(bbbEvent); } } } public function userTalk(userId:Number, talk:Boolean) : void { + trace("User talking event"); var l:Listener = _listeners.getListener(userId); if (l != null) { l.talking = talk; @@ -226,6 +249,10 @@ package org.bigbluebutton.modules.listeners.business bu.voiceUserid = 0; bu.voiceMuted = false; bu.voiceJoined = false; + + var bbbEvent:BBBEvent = new BBBEvent(BBBEvent.USER_VOICE_LEFT); + bbbEvent.payload.userID = bu.userID; + globalDispatcher.dispatchEvent(bbbEvent); } } diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/listeners/views/ListenerItem.mxml b/bigbluebutton-client/src/org/bigbluebutton/modules/listeners/views/ListenerItem.mxml index d6d81d4f84d4eec7ec10b0673d32895526d95e44..b9a86b82aa59fc8d33e63ce044130af8f7c7a145 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/listeners/views/ListenerItem.mxml +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/listeners/views/ListenerItem.mxml @@ -41,7 +41,7 @@ [Bindable] private var muteIcon:Class = images.sound_mute; [Bindable] private var unmuteIcon:Class = images.sound_none; [Bindable] private var talkIcon:Object = images.sound; - [Bindable] private var ejectIcon:Class = images.delete_icon; + [Bindable] private var ejectIcon:Class = images.cancel; [Bindable] private var statusTooltip : String = ""; [Bindable] private var ejectTooltip : String = ResourceUtil.getInstance().getString('bbb.listeners.ejectTooltip'); [Bindable] private var moderator:Boolean = false; diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/notes/events/NotesModuleStartEvent.as b/bigbluebutton-client/src/org/bigbluebutton/modules/notes/events/NotesModuleStartEvent.as new file mode 100755 index 0000000000000000000000000000000000000000..813f5a560112b05bb7de15040050cc1274c4ed22 --- /dev/null +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/notes/events/NotesModuleStartEvent.as @@ -0,0 +1,14 @@ +package org.bigbluebutton.modules.notes.events +{ + import flash.events.Event; + + public class NotesModuleStartEvent extends Event + { + public static const NOTES_MODULE_START:String = "notes module start event"; + + public function NotesModuleStartEvent(bubbles:Boolean=true, cancelable:Boolean=false) + { + super(NOTES_MODULE_START, bubbles, cancelable); + } + } +} \ No newline at end of file diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/notes/events/NotesModuleStopEvent.as b/bigbluebutton-client/src/org/bigbluebutton/modules/notes/events/NotesModuleStopEvent.as new file mode 100755 index 0000000000000000000000000000000000000000..51c4d9bb56c863158bb803be04b2dd58e2f02486 --- /dev/null +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/notes/events/NotesModuleStopEvent.as @@ -0,0 +1,14 @@ +package org.bigbluebutton.modules.notes.events +{ + import flash.events.Event; + + public class NotesModuleStopEvent extends Event + { + public static const NOTES_MODULE_STOP:String = "notes module stop event"; + + public function NotesModuleStopEvent(bubbles:Boolean=true, cancelable:Boolean=false) + { + super(NOTES_MODULE_STOP, bubbles, cancelable); + } + } +} \ No newline at end of file diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/notes/events/RetrieveNotesErrorEvent.as b/bigbluebutton-client/src/org/bigbluebutton/modules/notes/events/RetrieveNotesErrorEvent.as new file mode 100755 index 0000000000000000000000000000000000000000..aed1473faf210d5156d4c68569b97de09c0c8d91 --- /dev/null +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/notes/events/RetrieveNotesErrorEvent.as @@ -0,0 +1,14 @@ +package org.bigbluebutton.modules.notes.events +{ + import flash.events.Event; + + public class RetrieveNotesErrorEvent extends Event + { + public static const RETRIEVE_ERROR:String = "notes retrieve error event"; + + public function RetrieveNotesErrorEvent(bubbles:Boolean=true, cancelable:Boolean=false) + { + super(RETRIEVE_ERROR, bubbles, cancelable); + } + } +} \ No newline at end of file diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/notes/events/RetrieveNotesEvent.as b/bigbluebutton-client/src/org/bigbluebutton/modules/notes/events/RetrieveNotesEvent.as new file mode 100755 index 0000000000000000000000000000000000000000..79af669e474955d49c511d81d9f49d911ab6c528 --- /dev/null +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/notes/events/RetrieveNotesEvent.as @@ -0,0 +1,14 @@ +package org.bigbluebutton.modules.notes.events +{ + import flash.events.Event; + + public class RetrieveNotesEvent extends Event + { + public static const RETRIEVE:String = "notes retrieve request event"; + + public function RetrieveNotesEvent(bubbles:Boolean=true, cancelable:Boolean=false) + { + super(RETRIEVE, bubbles, cancelable); + } + } +} \ No newline at end of file diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/notes/events/RetrieveNotesSuccessEvent.as b/bigbluebutton-client/src/org/bigbluebutton/modules/notes/events/RetrieveNotesSuccessEvent.as new file mode 100755 index 0000000000000000000000000000000000000000..ddad695f12774a881eca83899585d5c6dc893ccc --- /dev/null +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/notes/events/RetrieveNotesSuccessEvent.as @@ -0,0 +1,18 @@ +package org.bigbluebutton.modules.notes.events +{ + import flash.events.Event; + + import mx.collections.ArrayCollection; + + public class RetrieveNotesSuccessEvent extends Event + { + public static const RETRIEVE_SUCCESS:String = "notes retrieve success event"; + + public var notes:ArrayCollection; + + public function RetrieveNotesSuccessEvent(bubbles:Boolean=true, cancelable:Boolean=false) + { + super(RETRIEVE_SUCCESS, bubbles, cancelable); + } + } +} \ No newline at end of file diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/notes/events/SaveErrorEvent.as b/bigbluebutton-client/src/org/bigbluebutton/modules/notes/events/SaveErrorEvent.as new file mode 100755 index 0000000000000000000000000000000000000000..2496154ca92555cac7c80a0b827c88958cdda027 --- /dev/null +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/notes/events/SaveErrorEvent.as @@ -0,0 +1,22 @@ +package org.bigbluebutton.modules.notes.events +{ + import flash.events.Event; + + public class SaveErrorEvent extends Event + { + public static const NOTE_SAVE_ERROR:String = "note save error event"; + + public static const SUCCESS:String = "note save success event"; + public static const FAILED_TO_SAVE:String = "note failed to save event"; + public static const SECURITY_ERROR:String = "note security error event"; + public static const IO_ERROR:String = "note io error event"; + + public var reason:String; + public var noteID:String; + + public function SaveErrorEvent(bubbles:Boolean=true, cancelable:Boolean=false) + { + super(NOTE_SAVE_ERROR, bubbles, cancelable); + } + } +} \ No newline at end of file diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/notes/events/SaveNoteEvent.as b/bigbluebutton-client/src/org/bigbluebutton/modules/notes/events/SaveNoteEvent.as new file mode 100755 index 0000000000000000000000000000000000000000..2a988822480c061d3a1a2040ebafb7a6d837e561 --- /dev/null +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/notes/events/SaveNoteEvent.as @@ -0,0 +1,16 @@ +package org.bigbluebutton.modules.notes.events +{ + import flash.events.Event; + + public class SaveNoteEvent extends Event + { + public static const SAVE_NOTE:String = "notes module save note event"; + + public var note:String; + + public function SaveNoteEvent(bubbles:Boolean=true, cancelable:Boolean=false) + { + super(SAVE_NOTE, bubbles, cancelable); + } + } +} \ No newline at end of file diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/notes/events/SaveSuccessEvent.as b/bigbluebutton-client/src/org/bigbluebutton/modules/notes/events/SaveSuccessEvent.as new file mode 100755 index 0000000000000000000000000000000000000000..504cb1cd90ac7a20a8bdcf73fde79ff944575149 --- /dev/null +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/notes/events/SaveSuccessEvent.as @@ -0,0 +1,17 @@ +package org.bigbluebutton.modules.notes.events +{ + import flash.events.Event; + + public class SaveSuccessEvent extends Event + { + public static const NOTE_SAVE_SUCCESS:String = "note save success event"; + + public var noteID:String; + public var timestamp:String; + + public function SaveSuccessEvent(bubbles:Boolean=true, cancelable:Boolean=false) + { + super(NOTE_SAVE_SUCCESS, bubbles, cancelable); + } + } +} \ No newline at end of file diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/notes/maps/NotesEventMap.mxml b/bigbluebutton-client/src/org/bigbluebutton/modules/notes/maps/NotesEventMap.mxml new file mode 100755 index 0000000000000000000000000000000000000000..704e9415853621c9ae823107d7e97b914bd76525 --- /dev/null +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/notes/maps/NotesEventMap.mxml @@ -0,0 +1,75 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + BigBlueButton open source conferencing system - http://www.bigbluebutton.org/ + + Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below). + + This program is free software; you can redistribute it and/or modify it under the + terms of the GNU Lesser General Public License as published by the Free Software + Foundation; either version 2.1 of the License, or (at your option) any later + version. + + BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License along + with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. + + Author: Felipe Cecagno <felipe@mconf.org> + + $Id: $ +--> +<EventMap xmlns:mx="http://www.adobe.com/2006/mxml" + xmlns="http://mate.asfusion.com/"> + <mx:Script> + <![CDATA[ + import mx.events.FlexEvent; + import org.bigbluebutton.modules.notes.events.NotesModuleStartEvent; + import org.bigbluebutton.modules.notes.events.RetrieveNotesEvent; + import org.bigbluebutton.modules.notes.events.RetrieveNotesSuccessEvent; + import org.bigbluebutton.modules.notes.events.SaveErrorEvent; + import org.bigbluebutton.modules.notes.events.SaveNoteEvent; + import org.bigbluebutton.modules.notes.events.SaveSuccessEvent; + import org.bigbluebutton.modules.notes.models.NotesModel; + import org.bigbluebutton.modules.notes.services.NotesMessageService; + import org.bigbluebutton.modules.notes.services.RetrieveNotesService; + + ]]> + </mx:Script> + + <EventHandlers type="{NotesModuleStartEvent.NOTES_MODULE_START}"> + <ObjectBuilder generator="{NotesModel}" cache="global"/> + <ObjectBuilder generator="{RetrieveNotesService}" cache="global" constructorArguments="{scope.dispatcher}"/> + <ObjectBuilder generator="{NotesMessageService}" cache="global" constructorArguments="{scope.dispatcher}"/> + <ObjectBuilder generator="{NotesEventMapDelegate}" cache="global" constructorArguments="{scope.dispatcher}"/> + </EventHandlers> + + <EventHandlers type="{RetrieveNotesEvent.RETRIEVE}"> + <MethodInvoker generator="{RetrieveNotesService}" method="retrieveNotes"/> + </EventHandlers> + + <EventHandlers type="{RetrieveNotesSuccessEvent.RETRIEVE_SUCCESS}"> + <MethodInvoker generator="{NotesEventMapDelegate}" method="handleRetrieveNotesSuccessEvent" arguments="{event}"/> + </EventHandlers> + + <EventHandlers type="{SaveNoteEvent.SAVE_NOTE}"> + <MethodInvoker generator="{NotesMessageService}" method="save" arguments="{event.note}" /> + </EventHandlers> + + <EventHandlers type="{SaveSuccessEvent.NOTE_SAVE_SUCCESS}"> + <MethodInvoker generator="{NotesMessageService}" method="saveSuccess" arguments="{event}" /> + </EventHandlers> + + <EventHandlers type="{SaveErrorEvent.FAILED_TO_SAVE}"> + <MethodInvoker generator="{NotesMessageService}" method="saveError" arguments="{event}" /> + </EventHandlers> + + <Injectors target="{NotesEventMapDelegate}"> + <PropertyInjector targetKey="notesModel" source="{NotesModel}"/> + </Injectors> + + <Injectors target="{NotesMessageService}"> + <PropertyInjector targetKey="notesModel" source="{NotesModel}"/> + </Injectors> +</EventMap> \ No newline at end of file diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/notes/maps/NotesEventMapDelegate.as b/bigbluebutton-client/src/org/bigbluebutton/modules/notes/maps/NotesEventMapDelegate.as new file mode 100755 index 0000000000000000000000000000000000000000..b290e41e06a0613f6fff6b6f78f13777dce4ae07 --- /dev/null +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/notes/maps/NotesEventMapDelegate.as @@ -0,0 +1,49 @@ +package org.bigbluebutton.modules.notes.maps +{ + import com.asfusion.mate.events.Dispatcher; + + import flash.events.IEventDispatcher; + + import org.bigbluebutton.common.events.OpenWindowEvent; + import org.bigbluebutton.modules.notes.events.RetrieveNotesEvent; + import org.bigbluebutton.modules.notes.events.RetrieveNotesSuccessEvent; + import org.bigbluebutton.modules.notes.events.SaveNoteEvent; + import org.bigbluebutton.modules.notes.models.NotesModel; + import org.bigbluebutton.modules.notes.views.NotesWindow; + + public class NotesEventMapDelegate + { + private var _notesWindow:NotesWindow; + private var _dispatcher:IEventDispatcher; + + public var notesModel:NotesModel; + + public function NotesEventMapDelegate(dispatcher:IEventDispatcher) + { + _dispatcher = dispatcher; + _dispatcher.dispatchEvent(new RetrieveNotesEvent()); + } + + public function handleRetrieveNotesSuccessEvent(event:RetrieveNotesSuccessEvent):void { + notesModel.notes = event.notes; + + openNotesWindow(); + saveALoginNote(); + } + + private function saveALoginNote():void { + var saveEvent:SaveNoteEvent = new SaveNoteEvent(); + saveEvent.note = "** Logged in. **"; + _dispatcher.dispatchEvent(saveEvent); + } + + public function openNotesWindow():void { + _notesWindow = new NotesWindow(); + _notesWindow.notesModel = notesModel; + + var event:OpenWindowEvent = new OpenWindowEvent(OpenWindowEvent.OPEN_WINDOW_EVENT); + event.window = _notesWindow; + _dispatcher.dispatchEvent(event); + } + } +} \ No newline at end of file diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/notes/models/Note.as b/bigbluebutton-client/src/org/bigbluebutton/modules/notes/models/Note.as new file mode 100755 index 0000000000000000000000000000000000000000..f035b849fab17583efcbe6cf8ec8604e30b6d7f2 --- /dev/null +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/notes/models/Note.as @@ -0,0 +1,11 @@ +package org.bigbluebutton.modules.notes.models +{ + [Bindable] + public class Note + { + public var noteID:String; + public var note:String; + public var saved:Boolean = false; + public var timestamp:String = ""; + } +} \ No newline at end of file diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/notes/models/NotesModel.as b/bigbluebutton-client/src/org/bigbluebutton/modules/notes/models/NotesModel.as new file mode 100755 index 0000000000000000000000000000000000000000..364b0c5545eda746858c135f90b4124a64ae01b2 --- /dev/null +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/notes/models/NotesModel.as @@ -0,0 +1,35 @@ +package org.bigbluebutton.modules.notes.models +{ + import mx.collections.ArrayCollection; + + public class NotesModel + { + [Bindable] + public var notes:ArrayCollection = new ArrayCollection(); + + public function addNote(n:Note):void { + notes.addItem(n); + } + + public function noteSaved(noteID:String, timestamp:String):void { + for (var i:int = 0; i < notes.length; i++){ + var item:Note = notes.getItemAt(i) as Note; + if (item.noteID == noteID) { + item.saved = true; + item.timestamp = timestamp; + } + } + } + + public function getNote(noteID:String):Note { + for (var i:int = 0; i < notes.length; i++){ + var item:Note = notes.getItemAt(i) as Note; + if (item.noteID == noteID) { + return item; + } + } + + return null; + } + } +} \ No newline at end of file diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/notes/models/NotesOptions.as b/bigbluebutton-client/src/org/bigbluebutton/modules/notes/models/NotesOptions.as new file mode 100755 index 0000000000000000000000000000000000000000..e20cc269a3f79b50a991a32d344e3b2168ebded0 --- /dev/null +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/notes/models/NotesOptions.as @@ -0,0 +1,23 @@ +package org.bigbluebutton.modules.notes.models +{ + import org.bigbluebutton.core.BBB; + + public class NotesOptions + { + public var saveURL:String; + public var position:String = "top-right"; + + public function NotesOptions() + { + var cxml:XML = BBB.getConfigForModule("NotesModule"); + if (cxml != null) { + if (cxml.@saveURL != undefined) { + saveURL = cxml.@saveURL.toString(); + } + if (cxml.@position != undefined) { + position = cxml.@position.toString(); + } + } + } + } +} \ No newline at end of file diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/notes/services/MessageReceiver.as b/bigbluebutton-client/src/org/bigbluebutton/modules/notes/services/MessageReceiver.as new file mode 100755 index 0000000000000000000000000000000000000000..f02c5bb05e6e63c5c04a7bb0df00a89f58a74d57 --- /dev/null +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/notes/services/MessageReceiver.as @@ -0,0 +1,9 @@ +package org.bigbluebutton.modules.notes.services +{ + public class MessageReceiver + { + public function MessageReceiver() + { + } + } +} \ No newline at end of file diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/notes/services/MessageSender.as b/bigbluebutton-client/src/org/bigbluebutton/modules/notes/services/MessageSender.as new file mode 100755 index 0000000000000000000000000000000000000000..d77e1be1b48eb09e3ba24c5cf40e3876dd5a6260 --- /dev/null +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/notes/services/MessageSender.as @@ -0,0 +1,9 @@ +package org.bigbluebutton.modules.notes.services +{ + public class MessageSender + { + public function MessageSender() + { + } + } +} \ No newline at end of file diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/notes/services/NoteSaver.as b/bigbluebutton-client/src/org/bigbluebutton/modules/notes/services/NoteSaver.as new file mode 100755 index 0000000000000000000000000000000000000000..ac7a1ef67be927e00351bae09842b2585e8f9c34 --- /dev/null +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/notes/services/NoteSaver.as @@ -0,0 +1,138 @@ +package org.bigbluebutton.modules.notes.services +{ + import flash.events.Event; + import flash.events.HTTPStatusEvent; + import flash.events.IEventDispatcher; + import flash.events.IOErrorEvent; + import flash.events.ProgressEvent; + import flash.events.SecurityErrorEvent; + import flash.net.URLLoader; + import flash.net.URLRequest; + import flash.net.URLRequestMethod; + import flash.net.URLVariables; + import flash.utils.ByteArray; + + import mx.utils.Base64Decoder; + import mx.utils.Base64Encoder; + + import org.bigbluebutton.core.UsersUtil; + import org.bigbluebutton.modules.notes.events.SaveErrorEvent; + import org.bigbluebutton.modules.notes.events.SaveSuccessEvent; + import org.bigbluebutton.modules.notes.models.Note; + import org.bigbluebutton.modules.notes.models.NotesOptions; + + public class NoteSaver + { + private var _serverURL:String; + + private var _request:URLRequest = new URLRequest(); + private var _vars:URLVariables; + private var _uri:String; + private var _loader:URLLoader = new URLLoader(); + private var _note:Note; + private var _dispatcher:IEventDispatcher; + + public function NoteSaver(note:Note, serverURL:String, dispatcher:IEventDispatcher) { + _note = note; + _dispatcher = dispatcher; + _serverURL = serverURL; + + _loader.addEventListener(Event.COMPLETE, completeHandler); + _loader.addEventListener(Event.OPEN, openHandler); + _loader.addEventListener(ProgressEvent.PROGRESS, progressHandler); + _loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler); + _loader.addEventListener(HTTPStatusEvent.HTTP_STATUS, httpStatusHandler); + _loader.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler); + } + + public function getNoteID():String { + return _note.noteID; + } + + public function save():void { + //_request.url = _serverURL + "/save"; + _request.url = _serverURL; + _request.method = URLRequestMethod.GET; + + _vars = new URLVariables(); + _vars.saveNote = ""; + _vars.noteID = _note.noteID; + _vars.text = base64Encode(_note.note); + _vars.externalMeetingID = UsersUtil.getExternalMeetingID(); + _vars.internalMeetingID = UsersUtil.getInternalMeetingID(); + _vars.userID = UsersUtil.internalUserIDToExternalUserID(UsersUtil.getMyUserID()); + _vars.username = base64Encode(UsersUtil.getMyUsername()); + + var dec:Base64Decoder = new Base64Decoder(); + dec.decode(_vars.text) + var decNote:String = dec.toByteArray().toString(); + trace("Saving note [" + _vars.noteID + "][" + _vars.note + "] to [" + _request.url + "]"); + + _request.data = _vars; + + try { + _loader.load(_request); + } catch (error:Error) { + trace("Unable to load requested document."); + var errorEvent:SaveErrorEvent = new SaveErrorEvent(); + errorEvent.reason = SaveErrorEvent.FAILED_TO_SAVE; + errorEvent.noteID = _note.noteID; + _dispatcher.dispatchEvent(errorEvent); + } + } + + private function base64Encode(data:String):String { + var encoder:Base64Encoder = new Base64Encoder(); + encoder.encode(data); + return encoder.toString(); + } + + private function completeHandler(event:Event):void { + var xml:XML = new XML(event.target.data); + + if (saveSuccess(xml)) { + trace("SAVED noteID [" + _note.noteID + "] on [" + xml.timestamp + "]"); + var successEvent:SaveSuccessEvent = new SaveSuccessEvent(); + successEvent.noteID = _note.noteID; + successEvent.timestamp = xml.timestamp; + _dispatcher.dispatchEvent(successEvent); + } else { + trace("NOT SAVED"); + } + } + + private function saveSuccess(xml:XML):Boolean { + if (xml.success == 'T' || xml.success == 't') return true; + return false; + } + + private function openHandler(event:Event):void { + trace("openHandler: " + event); + } + + private function progressHandler(event:ProgressEvent):void { + trace("progressHandler loaded:" + event.bytesLoaded + " total: " + event.bytesTotal); + } + + private function securityErrorHandler(event:SecurityErrorEvent):void { + trace("securityErrorHandler: " + event); + var errorEvent:SaveErrorEvent = new SaveErrorEvent(); + errorEvent.reason = SaveErrorEvent.SECURITY_ERROR; + errorEvent.noteID = _note.noteID; + _dispatcher.dispatchEvent(errorEvent); + } + + private function httpStatusHandler(event:HTTPStatusEvent):void { + trace("httpStatusHandler: " + event); + } + + private function ioErrorHandler(event:IOErrorEvent):void { + trace("ioErrorHandler: " + event); + var errorEvent:SaveErrorEvent = new SaveErrorEvent(); + errorEvent.reason = SaveErrorEvent.IO_ERROR; + errorEvent.noteID = _note.noteID; + _dispatcher.dispatchEvent(errorEvent); + } + + } +} \ No newline at end of file diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/notes/services/NotesMessageService.as b/bigbluebutton-client/src/org/bigbluebutton/modules/notes/services/NotesMessageService.as new file mode 100755 index 0000000000000000000000000000000000000000..84735d991dc35aadedc5910f7e90e6d010322c0e --- /dev/null +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/notes/services/NotesMessageService.as @@ -0,0 +1,61 @@ +package org.bigbluebutton.modules.notes.services +{ + import flash.events.IEventDispatcher; + + import mx.collections.ArrayCollection; + + import org.bigbluebutton.modules.notes.events.SaveErrorEvent; + import org.bigbluebutton.modules.notes.events.SaveSuccessEvent; + import org.bigbluebutton.modules.notes.models.Note; + import org.bigbluebutton.modules.notes.models.NotesModel; + import org.bigbluebutton.modules.notes.models.NotesOptions; + + public class NotesMessageService + { + public var notesModel:NotesModel; + + private var _noteSavers:ArrayCollection = new ArrayCollection(); + private var _options:NotesOptions; + private var _dispatcher:IEventDispatcher; + + public function NotesMessageService(dispatcher:IEventDispatcher) { + _options = new NotesOptions(); + _dispatcher = dispatcher; + } + + public function save(note:String):void { + var date:Date = new Date(); + + var n:Note = new Note(); + n.noteID = generateRandomString(5) + date.time; + n.note = note; + n.saved = false; + notesModel.addNote(n); + + var noteSaver:NoteSaver = new NoteSaver(n, _options.saveURL, _dispatcher); + _noteSavers.addItem(noteSaver); + + noteSaver.save(); + + } + + public function saveError(event:SaveErrorEvent):void { + // TODO: Re-save? + } + + public function saveSuccess(event:SaveSuccessEvent):void { + notesModel.noteSaved(event.noteID, event.timestamp); + } + + private function generateRandomString(strlen:Number):String{ + var chars:String = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + var num_chars:Number = chars.length - 1; + var randomChar:String = ""; + + for (var i:Number = 0; i < strlen; i++){ + randomChar += chars.charAt(Math.floor(Math.random() * num_chars)); + } + return randomChar; + } + } +} \ No newline at end of file diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/notes/services/RetrieveNotesService.as b/bigbluebutton-client/src/org/bigbluebutton/modules/notes/services/RetrieveNotesService.as new file mode 100755 index 0000000000000000000000000000000000000000..f22ff5e69d9a8d4913ea24e5a18b682e6f675024 --- /dev/null +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/notes/services/RetrieveNotesService.as @@ -0,0 +1,134 @@ +package org.bigbluebutton.modules.notes.services +{ + import flash.events.Event; + import flash.events.HTTPStatusEvent; + import flash.events.IEventDispatcher; + import flash.events.IOErrorEvent; + import flash.events.ProgressEvent; + import flash.events.SecurityErrorEvent; + import flash.net.URLLoader; + import flash.net.URLRequest; + import flash.net.URLRequestMethod; + import flash.net.URLVariables; + import flash.utils.ByteArray; + import mx.collections.ArrayCollection; + import mx.utils.Base64Decoder; + import mx.utils.Base64Encoder; + import org.bigbluebutton.core.UsersUtil; + import org.bigbluebutton.modules.notes.events.RetrieveNotesErrorEvent; + import org.bigbluebutton.modules.notes.events.RetrieveNotesSuccessEvent; + import org.bigbluebutton.modules.notes.events.SaveErrorEvent; + import org.bigbluebutton.modules.notes.events.SaveSuccessEvent; + import org.bigbluebutton.modules.notes.models.Note; + import org.bigbluebutton.modules.notes.models.NotesOptions; + + public class RetrieveNotesService + { + private var _options:NotesOptions; + private var _request:URLRequest = new URLRequest(); + private var _vars:URLVariables; + private var _uri:String; + private var _loader:URLLoader = new URLLoader(); + private var _dispatcher:IEventDispatcher; + + public function RetrieveNotesService(dispatcher:IEventDispatcher) { + _dispatcher = dispatcher; + _options = new NotesOptions(); + + _loader.addEventListener(Event.COMPLETE, completeHandler); + _loader.addEventListener(Event.OPEN, openHandler); + _loader.addEventListener(ProgressEvent.PROGRESS, progressHandler); + _loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler); + _loader.addEventListener(HTTPStatusEvent.HTTP_STATUS, httpStatusHandler); + _loader.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler); + } + + public function retrieveNotes():void { + //_request.url = _options.saveURL + "/notes"; + _request.url = _options.saveURL; + _request.method = URLRequestMethod.GET; + + _vars = new URLVariables(); + _vars.retrieveNotes = ""; + _vars.meetingID = UsersUtil.getExternalMeetingID(); + _vars.userID = UsersUtil.internalUserIDToExternalUserID(UsersUtil.getMyUserID()); + + try { + _request.data = _vars; + _loader.load(_request); + } catch (error:Error) { + trace("Unable to load requested document."); + var errorEvent:RetrieveNotesErrorEvent = new RetrieveNotesErrorEvent(); + _dispatcher.dispatchEvent(errorEvent); + } + + } + + private function base64Encode(data:String):String { + var encoder:Base64Encoder = new Base64Encoder(); + encoder.encode(data); + return encoder.toString(); + } + + private function completeHandler(event:Event):void { + var xml:XML = new XML(event.target.data) + var notes:ArrayCollection = parseNotes(xml); +// if (notes.length > 0) { + var successEvent:RetrieveNotesSuccessEvent = new RetrieveNotesSuccessEvent(); + successEvent.notes = notes; + _dispatcher.dispatchEvent(successEvent); +// } + } + + private function parseNotes(xml:XML):ArrayCollection { + var notes:ArrayCollection = new ArrayCollection(); + var list:XMLList = getNotes(xml); + var item:XML; + for each(item in list){ + trace("Saving note [" + item.noteID + "][" + item.text + "]"); + var note:Note = new Note(); + var dec:Base64Decoder = new Base64Decoder(); + dec.decode(item.text) + var decNote:String = dec.toByteArray().toString(); + trace("Saving note [" + item.noteID + "][" + decNote + "]"); + note.note = decNote; + note.noteID = item.noteID; + note.saved = true; + note.timestamp = item.timestamp; + notes.addItem(note); + } + return notes; + } + + private function getNotes(xml:XML):XMLList{ + trace("*** [" + xml.toXMLString() + "]"); + return xml.notes.note; + } + + private function openHandler(event:Event):void { + trace("openHandler: " + event); + } + + private function progressHandler(event:ProgressEvent):void { + trace("progressHandler loaded:" + event.bytesLoaded + " total: " + event.bytesTotal); + } + + private function securityErrorHandler(event:SecurityErrorEvent):void { + trace("securityErrorHandler: " + event); + var errorEvent:RetrieveNotesErrorEvent = new RetrieveNotesErrorEvent(); + _dispatcher.dispatchEvent(errorEvent); + } + + private function httpStatusHandler(event:HTTPStatusEvent):void { + trace("httpStatusHandler: " + event); + } + + private function ioErrorHandler(event:IOErrorEvent):void { + trace("ioErrorHandler: " + event); + var errorEvent:RetrieveNotesErrorEvent = new RetrieveNotesErrorEvent(); + _dispatcher.dispatchEvent(errorEvent); + } + + + } +} \ No newline at end of file diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/notes/views/AdvancedList.as b/bigbluebutton-client/src/org/bigbluebutton/modules/notes/views/AdvancedList.as new file mode 100755 index 0000000000000000000000000000000000000000..cbece7041c67da571a3320419199006d8101b9aa --- /dev/null +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/notes/views/AdvancedList.as @@ -0,0 +1,19 @@ +package org.bigbluebutton.modules.notes.views +{ + import mx.controls.List; + + public class AdvancedList extends List + { + public function AdvancedList() + { + super(); + } + + override protected function measure():void + { + super.measure(); + //sovled on forum by Flex HarUI + measuredHeight = measureHeightOfItems() + viewMetrics.top + viewMetrics.bottom; + } + } +} \ No newline at end of file diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/notes/views/NoteRenderer.mxml b/bigbluebutton-client/src/org/bigbluebutton/modules/notes/views/NoteRenderer.mxml new file mode 100755 index 0000000000000000000000000000000000000000..a6e98787075ad10cbe3e7230f7d7d19c278b42d6 --- /dev/null +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/notes/views/NoteRenderer.mxml @@ -0,0 +1,72 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- + BigBlueButton open source conferencing system - http://www.bigbluebutton.org + + Copyright (c) 2010 BigBlueButton Inc. and by respective authors (see below). + + BigBlueButton is free software; you can redistribute it and/or modify it under the + terms of the GNU Lesser General Public License as published by the Free Software + Foundation; either version 2.1 of the License, or (at your option) any later + version. + + BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License along + with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. + + $Id: $ +--> + +<mx:VBox xmlns:mx="http://www.adobe.com/2006/mxml" verticalAlign="top" styleName="noteRendererStyle" + verticalScrollPolicy="off" xmlns:common="org.bigbluebutton.common.*" dataChange="validateNow()" + creationComplete="onCreationComplete()"> + + <mx:Script> + <![CDATA[ + import mx.binding.utils.ChangeWatcher; + + import org.bigbluebutton.common.LogUtil; + import org.bigbluebutton.modules.notes.models.Note; + + [Bindable] private var rolledOver:Boolean = false; + [Bindable] private var note:Note; + + + private function onCreationComplete():void{ + /** Register a change handler as the renderer get recycled. If we don't + * do this, we'll get a stale data which will display the chat + * message repeatedly. (ralam Nov 1, 2010) + */ + ChangeWatcher.watch(this, "data", dataChangeHandler); + if (data != null) { + note = data as Note; + data = null; + } + + validateNow(); + } + + private function dataChangeHandler(e:Event = null):void{ + if (data == null) { + return; + } + note = data as Note; + data = null; + } + + + + ]]> + </mx:Script> + + <mx:HBox width="100%" height="15" styleName="noteRendererTimestampStyle"> + <mx:Text id="timestamp" htmlText="{note.timestamp}" textAlign="left" color="gray" width="100%"/> + </mx:HBox> + <mx:HBox width="100%" styleName="noteRendererTextStyle"> + <mx:Spacer width="5"/> + <mx:Text id="lblTime" htmlText="{note.note}" textAlign="left" color="gray" width="100%"/> + </mx:HBox> +</mx:VBox> diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/notes/views/NotesView.mxml b/bigbluebutton-client/src/org/bigbluebutton/modules/notes/views/NotesView.mxml new file mode 100755 index 0000000000000000000000000000000000000000..25f610482fd196868ca78fe55a24f12328c55cbc --- /dev/null +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/notes/views/NotesView.mxml @@ -0,0 +1,100 @@ +<?xml version="1.0" encoding="utf-8"?> +<mx:VBox xmlns:mx="http://www.adobe.com/2006/mxml" + xmlns:notes="org.bigbluebutton.modules.notes.views.*" + width="100%" height="100%" + verticalScrollPolicy="off" + creationComplete="onCreationComplete()"> + + <mx:Style> + .notesColorPickerStyle { + backgroundColor:#E5E6E7; + columnCount:12; + horizontalGap:0; + previewHeight:22; + previewWidth:45; + swatchGridBackgroundColor:#000000; + swatchGridBorderSize:0; + swatchHeight:60; + swatchHighlightColor:#FFFFFF; + swatchHighlightSize:1; + swatchWidth:12; + textFieldWidth:72; + verticalGap:0; + } + + </mx:Style> + + <mx:Script> + <![CDATA[ + import com.adobe.utils.StringUtil; + + import org.bigbluebutton.modules.notes.events.SaveNoteEvent; + import org.bigbluebutton.modules.notes.models.NotesModel; + import org.bigbluebutton.util.i18n.ResourceUtil; + + [Bindable] public var colorPickerColours:Array = ['0x000000', '0x7A7A7A' ,'0xFF0000', '0xFF8800', + '0x88FF00', '0x00FF00', '0x00FF88', '0x00FFFF', '0x0088FF', '0x0000FF', '0x8800FF', '0xFF00FF']; + + [Bindable] + public var notesArray:NotesModel; + + private function onCreationComplete():void { + // Listen for the ENTER key to send the message. + txtMsgArea.addEventListener(KeyboardEvent.KEY_UP, handleTextAreaKeyUpEvent); + } + + private function handleTextAreaKeyUpEvent(e:KeyboardEvent):void { + if (e.keyCode == Keyboard.ENTER) { + saveNote(); + } + } + + private function sendNoteToSave(note:String):void { + var saveEvent:SaveNoteEvent = new SaveNoteEvent(); + saveEvent.note = note; + dispatchEvent(saveEvent); + } + + private function saveNote():void { + // Trim the message. This will trim the '/n' char so we don't send a message when the + // user just hits the enter key + var noteToSave:String = StringUtil.trim(txtMsgArea.text); + + if (noteToSave != ""){ + sendNoteToSave(noteToSave); + } + + txtMsgArea.text = ""; + } + + //color="{cmpColorPicker.selectedColor}" + ]]> + + </mx:Script> + + <mx:HBox width="100%" height="100%" verticalScrollPolicy="off"> + <notes:AdvancedList width="100%" height="100%" id="notesMessagesList" selectable="false" + variableRowHeight="true" alternatingItemColors="[#EFEFEF, #FEFEFE]" + itemRenderer="org.bigbluebutton.modules.notes.views.NoteRenderer" + dataProvider="{notesArray.notes}" + verticalScrollPolicy="auto" wordWrap="true"/> + </mx:HBox> + + <mx:HBox id="notesCtrlBar" width="100%" height="55" styleName="notesControlBarStyle" + paddingLeft="5" paddingRight="5" verticalScrollPolicy="off"> + <mx:TextArea id="txtMsgArea" width="100%" + styleName="notesControlBarTextMsgStyle" + toolTip="{ResourceUtil.getInstance().getString('bbb.accessibility.notes.notesview.input')}"/> + <mx:VBox verticalScrollPolicy="off"> + <mx:Button label="{ResourceUtil.getInstance().getString('bbb.notes.saveBtn')}" id="saveBtn" + styleName="notesControlBarSendButtonStyle" + toolTip="{ResourceUtil.getInstance().getString('bbb.notes.saveBtn.toolTip')}" + click="saveNote()"/> + <!-- + <mx:ColorPicker id="cmpColorPicker" showTextField="false" + toolTip="{ResourceUtil.getInstance().getString('bbb.notes.cmpColorPicker.toolTip')}" + selectedColor="0x000000" dataProvider="{colorPickerColours}" swatchPanelStyleName="notesColorPickerStyle"/> + --> + </mx:VBox> + </mx:HBox> +</mx:VBox> diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/notes/views/NotesWindow.mxml b/bigbluebutton-client/src/org/bigbluebutton/modules/notes/views/NotesWindow.mxml new file mode 100755 index 0000000000000000000000000000000000000000..4c592e471844b5b8936844458456e0f44ea0ca65 --- /dev/null +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/notes/views/NotesWindow.mxml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<ns:MDIWindow xmlns:mx="http://www.adobe.com/2006/mxml" + xmlns:ns="http://code.google.com/p/flexlib/" + xmlns:views="org.bigbluebutton.modules.notes.views.*" + implements="org.bigbluebutton.common.IBbbModuleWindow" + title="Notes" + creationComplete="onCreationComplete()" + layout="absolute"> + <mx:Script> + <![CDATA[ + import org.bigbluebutton.modules.notes.models.NotesModel; + import org.bigbluebutton.modules.notes.models.NotesOptions; + import org.bigbluebutton.util.i18n.ResourceUtil; + + [Bindable] + public var notesModel:NotesModel; + + public function getPrefferedPosition():String{ + var options:NotesOptions = new NotesOptions(); + return options.position; + } + + private function onCreationComplete():void { + this.title = ResourceUtil.getInstance().getString("bbb.notes.title"); + } + ]]> + </mx:Script> + + <views:NotesView id="notesView" notesArray="{notesModel}"/> +</ns:MDIWindow> diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/phone/managers/PhoneManager.as b/bigbluebutton-client/src/org/bigbluebutton/modules/phone/managers/PhoneManager.as index 43d8760fc599d7addd3e8eb1c40168267d7c63bd..25749eff383e9695be696487cffbcf274ba159f1 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/phone/managers/PhoneManager.as +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/phone/managers/PhoneManager.as @@ -20,7 +20,9 @@ package org.bigbluebutton.modules.phone.managers { import com.asfusion.mate.events.Dispatcher; + import flash.events.StatusEvent; import flash.media.Microphone; + import flash.system.Security; import org.bigbluebutton.common.LogUtil; import org.bigbluebutton.core.BBB; @@ -41,7 +43,7 @@ package org.bigbluebutton.modules.phone.managers { private var rejoining:Boolean = false; // User has requested to leave the voice conference. private var userHangup:Boolean = false; - + private var mic:Microphone; public function PhoneManager() { connectionManager = new ConnectionManager(); @@ -60,10 +62,15 @@ package org.bigbluebutton.modules.phone.managers { if (phoneOptions.autoJoin) { if (phoneOptions.skipCheck || noMicrophone()) { - if (noMicrophone()) { + mic = Microphone.getMicrophone(); + + if (mic == null) { joinVoice(false); + } else if (mic.muted) { + Security.showSettings(); + mic.addEventListener(StatusEvent.STATUS, micStatusEventHandler); } else { - joinVoice(true); + joinVoice(true); } } else { var dispatcher:Dispatcher = new Dispatcher(); @@ -72,6 +79,21 @@ package org.bigbluebutton.modules.phone.managers { } } + private function micStatusEventHandler(event:StatusEvent):void { + switch(event.code) { + case "Microphone.Muted": + LogUtil.warn("Access to microphone has been denied."); + joinVoice(false); + break; + case "Microphone.Unmuted": + LogUtil.debug("Access to the microphone has been allowed."); + joinVoice(true); + break; + default: + LogUtil.debug("unknown micStatusHandler event: " + event); + } + } + private function noMicrophone():Boolean { return ((Microphone.getMicrophone() == null) || (Microphone.names.length == 0) || ((Microphone.names.length == 1) && (Microphone.names[0] == "Unknown Microphone"))); diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/phone/views/components/ToolbarButton.mxml b/bigbluebutton-client/src/org/bigbluebutton/modules/phone/views/components/ToolbarButton.mxml index d34ac06fff56779db38aa778c477c5e8e9717038..c7fc7683cc7738b729afae8c9ddc3b4e1715c69e 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/phone/views/components/ToolbarButton.mxml +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/phone/views/components/ToolbarButton.mxml @@ -46,7 +46,7 @@ private var images:Images = new Images(); private var dispatcher:Dispatcher = new Dispatcher(); - + private var mic:Microphone; public const DEFAULT_STATE:Number = 0; public const ACTIVE_STATE:Number = 1; @@ -67,17 +67,32 @@ phoneOptions.autoJoin = (vxml.@autoJoin.toString().toUpperCase() == "TRUE") ? true : false; phoneOptions.skipCheck = (vxml.@skipCheck.toString().toUpperCase() == "TRUE") ? true : false; } + + var joinEvent:BBBEvent; + if (phoneOptions.skipCheck || noMicrophone()) { + // need to figure out if the user has a mic and if they do if it's muted ask for access + mic = Microphone.getMicrophone(); - /* - * If the user had no mic, let her join but she'll just be listening. - * We should indicate a warning that the user is joining without mic - * so that he will know that others won't be able to hear him. - */ - var joinEvent:BBBEvent = new BBBEvent("JOIN_VOICE_CONFERENCE_EVENT"); - joinEvent.payload['useMicrophone'] = true; - dispatcher.dispatchEvent(joinEvent); - + if (mic == null) { + /* + * If the user had no mic, let her join but she'll just be listening. + * We should indicate a warning that the user is joining without mic + * so that he will know that others won't be able to hear him. + */ + joinEvent = new BBBEvent("JOIN_VOICE_CONFERENCE_EVENT"); + joinEvent.payload['useMicrophone'] = false; + dispatcher.dispatchEvent(joinEvent); + } else if (mic.muted) { + // user has disallowed access to the mic + mic.addEventListener(StatusEvent.STATUS, micStatusEventHandler); + Security.showSettings(); + } else { + // user has allowed access to the mic already + joinEvent = new BBBEvent("JOIN_VOICE_CONFERENCE_EVENT"); + joinEvent.payload['useMicrophone'] = true; + dispatcher.dispatchEvent(joinEvent); + } } else { dispatcher.dispatchEvent(new BBBEvent("SHOW_MIC_SETTINGS")); } @@ -89,6 +104,26 @@ } } + private function micStatusEventHandler(event:StatusEvent):void { + var joinEvent:BBBEvent; + switch(event.code) { + case "Microphone.Muted": + LogUtil.warn("Access to microphone has been denied."); + joinEvent = new BBBEvent("JOIN_VOICE_CONFERENCE_EVENT"); + joinEvent.payload['useMicrophone'] = false; + dispatcher.dispatchEvent(joinEvent); + break; + case "Microphone.Unmuted": + LogUtil.debug("Access to the microphone has been allowed."); + joinEvent = new BBBEvent("JOIN_VOICE_CONFERENCE_EVENT"); + joinEvent.payload['useMicrophone'] = true; + dispatcher.dispatchEvent(joinEvent); + break; + default: + LogUtil.debug("unknown micStatusHandler event: " + event); + } + } + private function mouseOverHandler(event:MouseEvent):void { if(_currentState == ACTIVE_STATE) phoneIcon = images.headsetInactiveIcon; diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/present/ui/views/FileUploadWindow.mxml b/bigbluebutton-client/src/org/bigbluebutton/modules/present/ui/views/FileUploadWindow.mxml index dd8dd1480296a4f8f5dbe43a1c39954c55c990ac..d0a9151e56d49f886f2e34a6c438995a51535ab8 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/present/ui/views/FileUploadWindow.mxml +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/present/ui/views/FileUploadWindow.mxml @@ -65,7 +65,7 @@ private var images:Images = new Images(); [Bindable] private var addIcon : Class = images.add; [Bindable] private var bulletGoIcon : Class = images.bulletGo; - [Bindable] private var deleteIcon : Class = images.delete_icon; + [Bindable] private var deleteIcon : Class = images.cancel; private var thumbnailTimer:Timer = new Timer(5000); private var genThumbText:String = ResourceUtil.getInstance().getString('bbb.fileupload.genThumbText'); diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/present/ui/views/PresentationWindow.mxml b/bigbluebutton-client/src/org/bigbluebutton/modules/present/ui/views/PresentationWindow.mxml index 4e8c78f8aa0a7108e0a09333c88e6137653558ae..28b5546dbd8d8c5f97fc545da3cbe4219cafedd7 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/present/ui/views/PresentationWindow.mxml +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/present/ui/views/PresentationWindow.mxml @@ -27,12 +27,12 @@ xmlns:code="http://code.google.com/p/flexlib/" xmlns:containers="flexlib.containers.*" xmlns:mate="http://mate.asfusion.com/" - paddingBottom="0" paddingTop="0" paddingLeft="0" paddingRight="0" showCloseButton="false" layout="absolute" verticalScrollPolicy="off" horizontalScrollPolicy="off" showControls="true" resize="maximizeHandler()" - backgroundColor="#efefef" + styleNameFocus="presentationWindowStyleFocus" + styleNameNoFocus="presentationWindowStyleNoFocus" implements="org.bigbluebutton.common.IBbbModuleWindow" creationComplete="onCreationComplete()" width="{DEFAULT_WINDOW_WIDTH}" height="{DEFAULT_WINDOW_HEIGHT}" @@ -55,11 +55,9 @@ <mx:Script> <![CDATA[ - import flash.geom.Point; - + import flash.geom.Point; import flexlib.containers.DockableToolBar; - import flexlib.mdi.events.MDIWindowEvent; - + import flexlib.mdi.events.MDIWindowEvent; import mx.binding.utils.BindingUtils; import mx.collections.ArrayCollection; import mx.containers.TitleWindow; @@ -68,8 +66,7 @@ import mx.events.ListEvent; import mx.events.ResizeEvent; import mx.managers.PopUpManager; - import mx.rpc.events.*; - + import mx.rpc.events.*; import org.bigbluebutton.common.IBbbCanvas; import org.bigbluebutton.common.Images; import org.bigbluebutton.common.LogUtil; @@ -93,21 +90,13 @@ import org.bigbluebutton.modules.present.managers.SlideManager; import org.bigbluebutton.util.i18n.ResourceUtil; - public static const TITLE:String = "Presentation"; + public static const TITLE:String = "Presentation"; private static const NEXT_BUTTON:String = "Next"; private static const PREVIOUS_BUTTON:String = "Previous"; - private static const GOTO_PAGE_BUTTON:String = "Go to Page..." - - private var images:Images = new Images(); - - [Bindable] private var uploadIcon:Class = images.upload; - [Bindable] private var forwardIcon:Class = images.forward; - [Bindable] private var backwardIcon:Class = images.backward; - [Bindable] private var magnifierIcon:Class = images.magnifier; - [Bindable] public var fitToWidthIcon:Class = images.fitToWidth; - [Bindable] public var fitToPageIcon:Class = images.fitToPage; + private static const GOTO_PAGE_BUTTON:String = "Go to Page..."; - [Bindable] private var thumbY:Number; + [Bindable] + private var thumbY:Number; public var uploadWindow:FileUploadWindow = null; private var slideManager:SlideManager = new SlideManager(); private var pageDialog:GotoPageDialog; @@ -496,6 +485,10 @@ slideView.switchToFitToPage(fitToPage); fitSlideToWindowMaintainingAspectRatio(); } + + private function onUploadButtonClicked():void { + dispatchEvent(new UploadEvent(UploadEvent.OPEN_UPLOAD_WINDOW)); + } ]]> </mx:Script> @@ -503,32 +496,32 @@ <mx:Fade id="thumbFadeIn" alphaFrom="1" alphaTo="0" duration="100" /> <mx:Fade id="thumbFadeOut" alphaFrom="0" alphaTo="1" duration="100" /> - <views:SlideView id="slideView" width="100%" height="100%" visible="false" mouseDown="mouseDown = true" backgroundColor="#efefef" + <views:SlideView id="slideView" width="100%" height="100%" visible="false" mouseDown="mouseDown = true" mouseUp="mouseDown = false" verticalScrollPolicy="off" horizontalScrollPolicy="off" /> - <mx:ApplicationControlBar id="presCtrlBar" width="100%" height="{CONTROL_BAR_HEIGHT}"> - <mx:Button id="uploadPres" icon="{uploadIcon}" visible="false" width="25" height="25" + <mx:ControlBar id="presCtrlBar" width="100%" height="{CONTROL_BAR_HEIGHT}" styleName="presentationWindowControlsStyle" > + <mx:Button id="uploadPres" visible="false" width="25" height="25" styleName="presentationUploadButtonStyle" toolTip="{ResourceUtil.getInstance().getString('bbb.presentation.uploadPresBtn.toolTip')}" - click="dispatchEvent(new UploadEvent(UploadEvent.OPEN_UPLOAD_WINDOW))"/> + click="onUploadButtonClicked()"/> <mx:Spacer width="50%" id="spacer1"/> - <mx:Button id="backButton" icon="{backwardIcon}" visible="false" width="30" height="20" + <mx:Button id="backButton" visible="false" width="30" height="20" styleName="presentationBackButtonStyle" toolTip="{ResourceUtil.getInstance().getString('bbb.presentation.backBtn.toolTip')}" click="gotoPreviousSlide()"/> <mx:Button id="btnSlideNum" visible="false" label="" click="showThumbnails()" doubleClick="showPageDialog()" toolTip="{ResourceUtil.getInstance().getString('bbb.presentation.slideNumLbl.toolTip')}"/> - <mx:Button id="forwardButton" icon="{forwardIcon}" visible="false" width="30" height="20" + <mx:Button id="forwardButton" visible="false" width="30" height="20" styleName="presentationForwardButtonStyle" toolTip="{ResourceUtil.getInstance().getString('bbb.presentation.forwardBtn.toolTip')}" click="gotoNextSlide()"/> <mx:Spacer width="10%" id="spacer2"/> - <mx:HSlider id="zoomSlider" visible="false" value="{slideView.zoomPercentage}" + <mx:HSlider id="zoomSlider" visible="false" value="{slideView.zoomPercentage}" styleName="presentationZoomSliderStyle" minimum="100" maximum="400" dataTipPlacement="top" labels="['100%','400%']" useHandCursor="true" snapInterval="5" allowTrackClick="true" liveDragging="true" dataTipFormatFunction="removeDecimalFromDataTip" change="onSliderZoom()" width="100"/> <mx:Spacer width="50%" id="spacer3"/> - <mx:Button id="btnFitToWidth" icon="{fitToWidthIcon}" visible="false" width="25" height="25" + <mx:Button id="btnFitToWidth" visible="false" width="25" height="25" styleName="presentationFitToWidthButtonStyle" toolTip="{ResourceUtil.getInstance().getString('bbb.presentation.fitToWidth.toolTip')}" click="onFitToPage(false)"/> - <mx:Button id="btnFitToPage" icon="{fitToPageIcon}" visible="false" width="25" height="25" + <mx:Button id="btnFitToPage" visible="false" width="25" height="25" styleName="presentationFitToPageButtonStyle" toolTip="{ResourceUtil.getInstance().getString('bbb.presentation.fitToPage.toolTip')}" click="onFitToPage(true)"/> <mx:Spacer width="5" id="spacer4"/> - </mx:ApplicationControlBar> + </mx:ControlBar> </pres:MDIWindow> diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/present/ui/views/SlideView.mxml b/bigbluebutton-client/src/org/bigbluebutton/modules/present/ui/views/SlideView.mxml index f29cb3915ccd7f4e945514fb76387728fbdbd6a1..c737579e7dc4315c294dc60dfc0bda935e3ca985 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/present/ui/views/SlideView.mxml +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/present/ui/views/SlideView.mxml @@ -29,7 +29,7 @@ creationComplete="onCreationComplete()" verticalScrollPolicy="off" horizontalScrollPolicy="off" - rollOut="hideCursor()" + rollOut="hideCursor()" styleName="presentationSlideViewStyle" xmlns:views="org.bigbluebutton.modules.present.views.*" implements="org.bigbluebutton.common.IBbbCanvas"> @@ -487,7 +487,7 @@ ]]> </mx:Script> <fimage:FocusableImage id="slideLoader" width="{slideModel.loaderW}" height="{slideModel.loaderH}" x="{slideModel.loaderX}" y="{slideModel.loaderY}" useHandCursor="true" buttonMode="true" - creationComplete="listenForSlideLoadedCompleteEvent()" scaleContent="false" showBusyCursor="true" completeEffect="Fade" /> + creationComplete="listenForSlideLoadedCompleteEvent()" styleName="presentationSlideViewStyle" scaleContent="false" showBusyCursor="true" completeEffect="Fade" /> <mx:HorizontalList id="thumbnailView" itemRenderer="org.bigbluebutton.modules.present.ui.views.Thumbnail" itemRollOver="changeIndex(event)" diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/present/ui/views/models/SlideViewModel.as b/bigbluebutton-client/src/org/bigbluebutton/modules/present/ui/views/models/SlideViewModel.as index 7dacbf9ecea7424f5ce231949cbaa081e5a79ad7..046c1a7868d7d66700efd7d66ead93cbdc2d6ec9 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/present/ui/views/models/SlideViewModel.as +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/present/ui/views/models/SlideViewModel.as @@ -202,7 +202,9 @@ package org.bigbluebutton.modules.present.ui.views.models } } else { if (viewportW < pageOrigW) { - viewportH = (viewportW/pageOrigW)*pageOrigH; + if (_calcPageH < viewportH) { + viewportH = (viewportW/pageOrigW)*pageOrigH; + } } } } @@ -287,7 +289,7 @@ package org.bigbluebutton.modules.present.ui.views.models viewportH = (vrhp/vrwp)*parentW; if (parentH < viewportH) { viewportH = parentH; - viewportW = ((vrwp * viewportH)/viewportH); + viewportW = ((vrwp * viewportH)/vrhp); // LogUtil.debug("calc viewport ***** resizing [" + viewportW + "," + viewportH + "] [" + parentW + "," + parentH + "," + fitToPage + "] [" + pageOrigW + "," + pageOrigH + "]"); } } else { diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/business/VideoProxy.as b/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/business/VideoProxy.as index 740571ab3f97e99e76cc293d14d0ea7b02d1795f..b502ae93886138c018e7254fed4ba691f68c444a 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/business/VideoProxy.as +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/business/VideoProxy.as @@ -18,12 +18,14 @@ */ package org.bigbluebutton.modules.videoconf.business { - import com.asfusion.mate.events.Dispatcher; - + import com.asfusion.mate.events.Dispatcher; import flash.events.AsyncErrorEvent; import flash.events.IOErrorEvent; import flash.events.NetStatusEvent; import flash.events.SecurityErrorEvent; + import flash.media.H264Level; + import flash.media.H264Profile; + import flash.media.H264VideoStreamSettings; import flash.net.NetConnection; import flash.net.NetStream; import flash.system.Capabilities; @@ -31,19 +33,14 @@ package org.bigbluebutton.modules.videoconf.business import mx.collections.ArrayCollection; import org.bigbluebutton.common.LogUtil; - import org.bigbluebutton.core.managers.UserManager; import org.bigbluebutton.core.BBB; + import org.bigbluebutton.core.managers.UserManager; import org.bigbluebutton.main.model.users.BBBUser; import org.bigbluebutton.main.model.users.events.StreamStartedEvent; + import org.bigbluebutton.modules.videoconf.events.ConnectedEvent; import org.bigbluebutton.modules.videoconf.events.StartBroadcastEvent; import org.bigbluebutton.modules.videoconf.model.VideoConfOptions; -// Uncomment if you want to build support for H264. But you need at least FP 11. (ralam july 23, 2011) - - import flash.media.H264VideoStreamSettings; - import flash.media.H264Profile; - import flash.media.H264Level; - public class VideoProxy { @@ -51,7 +48,8 @@ package org.bigbluebutton.modules.videoconf.business private var nc:NetConnection; private var ns:NetStream; - + private var _url:String; + private function parseOptions():void { videoOptions = new VideoConfOptions(); videoOptions.parseOptions(); @@ -59,66 +57,40 @@ package org.bigbluebutton.modules.videoconf.business public function VideoProxy(url:String) { - parseOptions(); - + _url = url; + parseOptions(); nc = new NetConnection(); nc.client = this; nc.addEventListener(AsyncErrorEvent.ASYNC_ERROR, onAsyncError); nc.addEventListener(IOErrorEvent.IO_ERROR, onIOError); nc.addEventListener(NetStatusEvent.NET_STATUS, onNetStatus); nc.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSecurityError); - nc.connect(url); + } + public function connect():void { + nc.connect(_url); + } + private function onAsyncError(event:AsyncErrorEvent):void{ } private function onIOError(event:NetStatusEvent):void{ } + private function onConnectedToVideoApp():void{ + var dispatcher:Dispatcher = new Dispatcher(); + dispatcher.dispatchEvent(new ConnectedEvent(ConnectedEvent.VIDEO_CONNECTED)); + } + private function onNetStatus(event:NetStatusEvent):void{ switch(event.info.code){ - case "NetConnection.Connect.Failed": - break; case "NetConnection.Connect.Success": ns = new NetStream(nc); - openAvailableVideos(); - break; - case "NetConnection.Connect.Rejected": - break; - case "NetConnection.Connect.Closed": - break; - case "NetConnection.Connect.InvalidApp": + onConnectedToVideoApp(); break; - case "NetConnection.Connect.AppShutdown": - break; - - case "NetStream.Publish.Start": - LogUtil.debug("NetStream.Publish.Start for broadcast stream ["); - break; - - case "NetStream.Publish.Idle": - LogUtil.debug("NetStream.Publish.Idle for broadcast stream ["); - break; - - case "NetStream.Record.Failed": - LogUtil.debug("NetStream.Record.Failed for broadcast stream ["); - break; - - case "NetStream.Record.Stop": - LogUtil.debug("NetStream.Record.Stop for broadcast stream ["); - break; - - case "NetStream.Record.Start": - LogUtil.debug("NetStream.Record.Start for broadcast stream ["); - break; - - case "NetStream.Unpublish.Success": - LogUtil.debug("NetStream.Unpublish.Success for broadcast stream ["); - break; - - case "NetStream.Publish.BadName": - LogUtil.debug("NetStream.Publish.BadName for broadcast stream ["); + default: + LogUtil.debug("[" + event.info.code + "] for [" + _url + "]"); break; } } @@ -215,13 +187,6 @@ package org.bigbluebutton.modules.videoconf.business trace("bandwidth = " + p_bw + " Kbps."); } - public function openAvailableVideos():void{ - var dispatcher:Dispatcher = new Dispatcher(); - var users:ArrayCollection = UserManager.getInstance().getConference().users; - for (var i:int = 0; i < users.length; i++){ - var user:BBBUser = (users.getItemAt(i) as BBBUser); - if (user.hasStream) dispatcher.dispatchEvent(new StreamStartedEvent(user.userID, user.name, user.streamName)); - } - } + } } diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/business/VideoWindowItf.as b/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/business/VideoWindowItf.as index a16756cfb053830031986d0551d6ea918057858c..b3d071bfa26806e247d5d719723126cf2ae9a7a7 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/business/VideoWindowItf.as +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/business/VideoWindowItf.as @@ -68,13 +68,19 @@ package org.bigbluebutton.modules.videoconf.business protected var mousePositionOnDragStart:Point; public var streamName:String; - - protected var _sharerUserID:String = null; + + private var windowType:String = "VideoWindowItf"; + + public var userID:String = null; protected var _controlButtons:ControlButtons = new ControlButtons(); [Bindable] public var resolutions:Array; - + + public function getWindowType():String { + return windowType; + } + protected function switchRole(presenter:Boolean):void { _controlButtons.handleNewRoleEvent(presenter); } @@ -86,7 +92,7 @@ package org.bigbluebutton.modules.videoconf.business var uid:String = UserManager.getInstance().getConference().getMyUserId(); LogUtil.debug("Stream resolution is [" + pattern.exec(stream)[1] + "]"); LogUtil.debug("Userid [" + pattern.exec(stream)[2] + "]"); - _sharerUserID = pattern.exec(stream)[2]; + userID = pattern.exec(stream)[2]; addControlButtons(); return pattern.exec(stream)[1].split("x"); } else { @@ -212,27 +218,7 @@ package org.bigbluebutton.modules.videoconf.business // the window is docked, so it should not be moved on reset layout return MainCanvas.ABSOLUTE; } - - public function onDrag(event:MDIWindowEvent = null):void { - var e:DragWindowEvent = new DragWindowEvent(DragWindowEvent.DRAG); - e.mouseGlobal = this.localToGlobal(new Point(mouseX, mouseY)); - e.window = this; - dispatchEvent(e); - } - - public function onDragStart(event:MDIWindowEvent = null):void { - var e:DragWindowEvent = new DragWindowEvent(DragWindowEvent.DRAG_START); - e.window = this; - dispatchEvent(e); - } - - public function onDragEnd(event:MDIWindowEvent = null):void { - var e:DragWindowEvent = new DragWindowEvent(DragWindowEvent.DRAG_END); - e.mouseGlobal = this.localToGlobal(new Point(mouseX, mouseY)); - e.window = this; - dispatchEvent(e); - } - + override public function close(event:MouseEvent = null):void{ var e:CloseWindowEvent = new CloseWindowEvent(); e.window = this; @@ -240,42 +226,27 @@ package org.bigbluebutton.modules.videoconf.business super.close(event); } - -// private var _controlButtons:ControlButtonsOverlay = null; - + private var _controlButtonsEnabled:Boolean = true; private var img_unlock_keep_aspect:Class = images.lock_open; private var img_lock_keep_aspect:Class = images.lock_close; private var img_fit_video:Class = images.arrow_in; private var img_original_size:Class = images.shape_handles; - private var img_mute_icon:Class = images.sound_mute; - private var signOutIcon:Class = images.signOutIcon; - private var adminIcon:Class = images.admin; - private var chatIcon:Class = images.chatIcon; + private var img_mute_icon:Class = images.webcam_mute; + private var signOutIcon:Class = images.webcam_kickuser; + private var adminIcon:Class = images.webcam_make_presenter; + private var chatIcon:Class = images.webcam_private_chat; protected function addControlButtons():void { - _controlButtons.sharerUserID = _sharerUserID; + _controlButtons.sharerUserID = userID; _controlButtons.visible = true; this.addChild(_controlButtons); } protected function get controlButtons():ControlButtons { - if (_controlButtons == null) { - - -// _controlButtons.add("originalSizeBtn", img_original_size, ResourceUtil.getInstance().getString('bbb.video.originalSizeBtn.tooltip'), onOriginalSizeClick); -// _controlButtons.add("muteUnmuteBtn", img_mute_icon, "mute / unmute", onMuteUnmuteClicked); -// _controlButtons.add("switchPresenter", adminIcon, "switch presenter", onSwitchPresenterClicked); -// _controlButtons.add("ejectUserBtn", signOutIcon, "eject user", onKickUserClicked); -// _controlButtons.add("privateChatBtn", chatIcon, "Start private chat", onPrivateChatClicked); -// - // hiding the other buttons - //_buttons.add("keepAspectBtn", img_lock_keep_aspect, ResourceUtil.getInstance().getString('bbb.video.keepAspectBtn.tooltip'), onKeepAspectClick); - //_buttons.add("fitVideoBtn", img_fit_video, ResourceUtil.getInstance().getString('bbb.video.fitVideoBtn.tooltip'), onFitVideoClick); - - _controlButtons.visible = false; - + if (_controlButtons == null) { + _controlButtons.visible = false; } return _controlButtons; } @@ -283,6 +254,7 @@ package org.bigbluebutton.modules.videoconf.business protected function createButtons():void { // creates the window keeping the aspect ratio onKeepAspectClick(); + updateButtonsPosition(); } protected function updateButtonsPosition():void { @@ -332,38 +304,7 @@ package org.bigbluebutton.modules.videoconf.business _video.height = _videoHolder.height = originalHeight; onFitVideoClick(); } -/* - protected function onKickUserClicked(event:MouseEvent = null):void { - var gd:Dispatcher = new Dispatcher(); - gd.dispatchEvent(new KickUserEvent(_sharerUserID)); - } - protected function onPrivateChatClicked(event:MouseEvent = null):void { - var e:CoreEvent = new CoreEvent(EventConstants.START_PRIVATE_CHAT); - e.message.chatWith = _sharerUserID; - var gd:Dispatcher = new Dispatcher(); - gd.dispatchEvent(e); - } - - protected function onSwitchPresenterClicked(event:MouseEvent = null):void { - var e:RoleChangeEvent = new RoleChangeEvent(RoleChangeEvent.ASSIGN_PRESENTER); - e.userid = _sharerUserID; - e.username = UsersUtil.getUserName(_sharerUserID); - var gd:Dispatcher = new Dispatcher(); - gd.dispatchEvent(e); - } - - protected function onMuteUnmuteClicked(event:MouseEvent = null):void { - var bu:BBBUser = UsersUtil.getUser(_sharerUserID); - if (bu != null) { - var e:ListenersCommand = new ListenersCommand(ListenersCommand.MUTE_USER); - e.userid = bu.voiceUserid; - e.mute = ! bu.voiceMuted; - var gd:Dispatcher = new Dispatcher(); - gd.dispatchEvent(e); - } - } -*/ protected function onFitVideoClick(event:MouseEvent = null):void { var newWidth:int = _video.width + paddingHorizontal; var newHeight:int = _video.height + paddingVertical; @@ -377,20 +318,12 @@ package org.bigbluebutton.modules.videoconf.business protected function onKeepAspectClick(event:MouseEvent = null):void { keepAspect = !keepAspect; - -// var keepAspectBtn:Button = controlButtons.get("keepAspectBtn"); -// if (keepAspectBtn != null) { -// keepAspectBtn.selected = keepAspect; -// keepAspectBtn.setStyle("icon", (keepAspect? img_lock_keep_aspect: img_unlock_keep_aspect)); -// } - -// var fitVideoBtn:Button = controlButtons.get("fitVideoBtn"); -// if (fitVideoBtn != null) { -// fitVideoBtn.enabled = !keepAspect; -// } - + onFitVideoClick(); } + protected function userMuted(muted:Boolean):void { + _controlButtons.userMuted(muted); + } } } \ No newline at end of file diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/events/ConnectedEvent.as b/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/events/ConnectedEvent.as new file mode 100755 index 0000000000000000000000000000000000000000..e845323b5889ae5851a776f3ee65b9628e672ded --- /dev/null +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/events/ConnectedEvent.as @@ -0,0 +1,14 @@ +package org.bigbluebutton.modules.videoconf.events +{ + import flash.events.Event; + + public class ConnectedEvent extends Event + { + public static const VIDEO_CONNECTED:String = "connected to video app event"; + + public function ConnectedEvent(type:String, bubbles:Boolean=true, cancelable:Boolean=false) + { + super(type, bubbles, cancelable); + } + } +} \ No newline at end of file diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/events/ShareCameraRequestEvent.as b/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/events/ShareCameraRequestEvent.as index aa9ec2bf5dccb9b0d0d1766b345eb9f2e73aac1d..c4fa440bf57f7a69c45822e6054fc462bb7c551e 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/events/ShareCameraRequestEvent.as +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/events/ShareCameraRequestEvent.as @@ -24,6 +24,8 @@ package org.bigbluebutton.modules.videoconf.events { public static const SHARE_CAMERA_REQUEST:String = "ShareCameraRequestEvent"; + public var publishInClient:Boolean = true; + public function ShareCameraRequestEvent(type:String = SHARE_CAMERA_REQUEST) { super(type, true, false); diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/events/VideoModuleStartEvent.as b/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/events/VideoModuleStartEvent.as new file mode 100755 index 0000000000000000000000000000000000000000..f0095d63bf5070963edc856ffe5eb720d2d41a91 --- /dev/null +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/events/VideoModuleStartEvent.as @@ -0,0 +1,14 @@ +package org.bigbluebutton.modules.videoconf.events +{ + import flash.events.Event; + + public class VideoModuleStartEvent extends Event + { + public static const START:String = "video module start event"; + + public function VideoModuleStartEvent(type:String, bubbles:Boolean=true, cancelable:Boolean=false) + { + super(type, bubbles, cancelable); + } + } +} \ No newline at end of file diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/events/VideoModuleStopEvent.as b/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/events/VideoModuleStopEvent.as new file mode 100755 index 0000000000000000000000000000000000000000..86811162f4b9b28a65627bc2b261f5357fdad7e7 --- /dev/null +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/events/VideoModuleStopEvent.as @@ -0,0 +1,14 @@ +package org.bigbluebutton.modules.videoconf.events +{ + import flash.events.Event; + + public class VideoModuleStopEvent extends Event + { + public static const STOP:String = "video module stop event"; + + public function VideoModuleStopEvent(type:String, bubbles:Boolean=true, cancelable:Boolean=false) + { + super(type, bubbles, cancelable); + } + } +} \ No newline at end of file diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/maps/VideoEventMap.mxml b/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/maps/VideoEventMap.mxml index 8d19f2889a33eb148f6f04d843cb58aa00f556c4..762cd326d0f80382e85422c9ecc8acb17b24cfb3 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/maps/VideoEventMap.mxml +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/maps/VideoEventMap.mxml @@ -23,203 +23,94 @@ <EventMap xmlns:mx="http://www.adobe.com/2006/mxml" xmlns="http://mate.asfusion.com/"> <mx:Script> <![CDATA[ - import mx.collections.ArrayCollection; - - import org.bigbluebutton.common.LogUtil; - import org.bigbluebutton.common.events.OpenWindowEvent; - import org.bigbluebutton.common.events.ToolbarButtonEvent; - import org.bigbluebutton.core.managers.UserManager; + import org.bigbluebutton.core.events.ConnectAppEvent; import org.bigbluebutton.main.events.BBBEvent; import org.bigbluebutton.main.events.MadePresenterEvent; - import org.bigbluebutton.main.model.users.BBBUser; - import org.bigbluebutton.main.model.users.events.BroadcastStartedEvent; - import org.bigbluebutton.main.model.users.events.BroadcastStoppedEvent; + import org.bigbluebutton.main.events.StoppedViewingWebcamEvent; + import org.bigbluebutton.main.events.UserJoinedEvent; + import org.bigbluebutton.main.events.UserLeftEvent; import org.bigbluebutton.main.model.users.events.StreamStartedEvent; - import org.bigbluebutton.modules.videoconf.business.VideoProxy; - import org.bigbluebutton.modules.videoconf.events.CloseAllWindowsEvent; import org.bigbluebutton.modules.videoconf.events.ClosePublishWindowEvent; - import org.bigbluebutton.modules.videoconf.events.OpenVideoWindowEvent; + import org.bigbluebutton.modules.videoconf.events.ConnectedEvent; import org.bigbluebutton.modules.videoconf.events.ShareCameraRequestEvent; import org.bigbluebutton.modules.videoconf.events.StartBroadcastEvent; import org.bigbluebutton.modules.videoconf.events.StopBroadcastEvent; - import org.bigbluebutton.modules.videoconf.views.PublishWindow; - import org.bigbluebutton.modules.videoconf.views.ToolbarButton; - import org.bigbluebutton.modules.videoconf.views.VideoWindow; - import org.bigbluebutton.modules.viewers.events.ViewCameraEvent; - import org.flexunit.runner.manipulation.filters.IncludeAllFilter; - - public var module:VideoconfModule; - private var button:ToolbarButton; - private var publishWindow:PublishWindow; - public var proxy:VideoProxy; - - // This variable is used to avoid dispatching the BroadcastStoppedEvent - // when the user just open the publish window and close it before start - // publishing - private var _publishing:Boolean = false; - - private function viewCamera(userid:String, stream:String, name:String, mock:Boolean = false):void { - LogUtil.debug("Viewing [" + userid + " stream [" + stream + "]"); - if (UserManager.getInstance().getConference().amIThisUser(userid)) return; - - var window:VideoWindow = new VideoWindow(); -// window.userId = userid; - window.videoOptions = proxy.videoOptions; - - window.resolutions = module.resolutions.split(","); - - if (mock) { - window.startVideo(module.mockConnection, stream); - } - else { - window.startVideo(module.connection, stream); - } - window.title = name; - var windowEvent:OpenWindowEvent = new OpenWindowEvent(OpenWindowEvent.OPEN_WINDOW_EVENT); - windowEvent.window = window; - globalDispatcher.dispatchEvent(windowEvent); - - // this event will dock the window, if it's enabled - var openVideoEvent:OpenVideoWindowEvent = new OpenVideoWindowEvent(); - openVideoEvent.window = window; - globalDispatcher.dispatchEvent(openVideoEvent); - } - - private function viewVideoFile(e:BBBEvent):void { - viewCamera("1", e.message, e.message, true); - } - - private function mockConnect():void { - module.mockConnect(); - } - - public function addToolbarButton():void{ - if (proxy.videoOptions.showButton) { - button = new ToolbarButton(); - button.isPresenter = !module.presenterShareOnly; - var event:ToolbarButtonEvent = new ToolbarButtonEvent(ToolbarButtonEvent.ADD); - event.button = button; - globalDispatcher.dispatchEvent(event); - } - } - - private function openPublishWindow():void{ - publishWindow = new PublishWindow(); - publishWindow.videoOptions = proxy.videoOptions; - publishWindow.userrole = module.role; - publishWindow.quality = module.quality; - publishWindow.resolutions = module.resolutions.split(","); - - var windowEvent:OpenWindowEvent = new OpenWindowEvent(OpenWindowEvent.OPEN_WINDOW_EVENT); - windowEvent.window = publishWindow; - globalDispatcher.dispatchEvent(windowEvent); - } - - - - private function closePublishWindow():void{ - publishWindow.close(); - } - - - private function startPublishing(e:StartBroadcastEvent):void{ - LogUtil.debug("Publishing stream to: " + proxy.connection.uri + "/" + e.stream); - proxy.startPublishing(e); - var broadcastEvent:BroadcastStartedEvent = new BroadcastStartedEvent(); - broadcastEvent.stream = e.stream; - broadcastEvent.userid = module.userid; - globalDispatcher.dispatchEvent(broadcastEvent); - publishWindow.title = module.username + " (you)"; - _publishing = true; - button.publishingStatus(button.START_PUBLISHING); - - } - - private function stopPublishing(e:StopBroadcastEvent):void{ - if (_publishing) { - proxy.stopBroadcasting(); - - var broadcastEvent:BroadcastStoppedEvent = new BroadcastStoppedEvent(); - broadcastEvent.stream = publishWindow.streamName; - broadcastEvent.userid = module.userid; - globalDispatcher.dispatchEvent(broadcastEvent); - _publishing = false; - } - - //Make toolbar button enabled again - - button.publishingStatus(button.STOP_PUBLISHING); - //button.show(); - } - - public function stopModule():void { - closeAllWindows(); - proxy.disconnect(); - } - - public function closeAllWindows():void{ - if (publishWindow != null) { - proxy.stopBroadcasting(); - publishWindow.close(); - } - globalDispatcher.dispatchEvent(new CloseAllWindowsEvent()); - } - - private function switchToPresenter():void{ - if (module.presenterShareOnly){ - button.isPresenter = true; - } - } - - private function switchToViewer():void{ - if (module.presenterShareOnly){ - button.isPresenter = false; - if (publishWindow != null) publishWindow.close(); - } - } - - + import org.bigbluebutton.modules.videoconf.events.VideoModuleStartEvent; + import org.bigbluebutton.modules.videoconf.events.VideoModuleStopEvent; + import org.bigbluebutton.modules.viewers.events.ViewCameraEvent; ]]> </mx:Script> + <EventHandlers type="{VideoModuleStartEvent.START}"> + <ObjectBuilder generator="{VideoEventMapDelegate}" cache="global" constructorArguments="{scope.dispatcher}"/> + <MethodInvoker generator="{VideoEventMapDelegate}" method="start"/> + </EventHandlers> + + <EventHandlers type="{VideoModuleStopEvent.STOP}"> + <MethodInvoker generator="{VideoEventMapDelegate}" method="stop"/> + </EventHandlers> + + <EventHandlers type="{BBBEvent.CAMERA_SETTING}" > + <MethodInvoker generator="{VideoEventMapDelegate}" method="handleCameraSetting" arguments="{event}"/> + </EventHandlers> + + <EventHandlers type="{ConnectAppEvent.CONNECT_VIDEO_APP}"> + <MethodInvoker generator="{VideoEventMapDelegate}" method="connectToVideoApp" /> + </EventHandlers> + <EventHandlers type="{ShareCameraRequestEvent.SHARE_CAMERA_REQUEST}"> - <InlineInvoker method="openPublishWindow" /> + <MethodInvoker generator="{VideoEventMapDelegate}" method="handleShareCameraRequestEvent" arguments="{event}"/> </EventHandlers> - <EventHandlers type="{ClosePublishWindowEvent.CLOSE_PUBLISH_WINDOW}"> - <InlineInvoker method="closePublishWindow" /> - </EventHandlers> - <EventHandlers type="{StartBroadcastEvent.START_BROADCAST}" > - <InlineInvoker method="startPublishing" arguments="{event}" /> + <MethodInvoker generator="{VideoEventMapDelegate}" method="startPublishing" arguments="{event}" /> </EventHandlers> <EventHandlers type="{StopBroadcastEvent.STOP_BROADCASTING}" > - <InlineInvoker method="stopPublishing" arguments="{event}" /> - </EventHandlers> - - <EventHandlers type="{BBBEvent.START_VIDEO_STREAM}" > - <InlineInvoker method="viewVideoFile" arguments="{event}" /> - </EventHandlers> - - <EventHandlers type="{BBBEvent.START_VIDEO_CONNECTION}"> - <InlineInvoker method="mockConnect" /> + <MethodInvoker generator="{VideoEventMapDelegate}" method="stopPublishing" arguments="{event}" /> </EventHandlers> <EventHandlers type="{StreamStartedEvent.STREAM_STARTED}"> - <InlineInvoker method="viewCamera" arguments="{[event.userID, event.stream, event.user]}" /> + <ObjectBuilder generator="{VideoEventMapDelegate}" cache="global" constructorArguments="{scope.dispatcher}"/> + <MethodInvoker generator="{VideoEventMapDelegate}" method="viewCamera" arguments="{[event.userID, event.stream, event.user]}" /> </EventHandlers> <EventHandlers type="{ViewCameraEvent.VIEW_CAMERA_EVENT}"> - <InlineInvoker method="viewCamera" arguments="{[event.userid, event.stream, event.viewedName]}" /> + <MethodInvoker generator="{VideoEventMapDelegate}" method="viewCamera" arguments="{[event.userID, event.stream, event.viewedName]}" /> </EventHandlers> - + + <EventHandlers type="{UserJoinedEvent.JOINED}"> + <ObjectBuilder generator="{VideoEventMapDelegate}" cache="global" constructorArguments="{scope.dispatcher}"/> + <MethodInvoker generator="{VideoEventMapDelegate}" method="handleUserJoinedEvent" arguments="{event}" /> + </EventHandlers> + + <EventHandlers type="{UserLeftEvent.LEFT}"> + <ObjectBuilder generator="{VideoEventMapDelegate}" cache="global" constructorArguments="{scope.dispatcher}"/> + <MethodInvoker generator="{VideoEventMapDelegate}" method="handleUserLeftEvent" arguments="{event}" /> + </EventHandlers> + <EventHandlers type="{MadePresenterEvent.SWITCH_TO_PRESENTER_MODE}" > - <InlineInvoker method="switchToPresenter" /> + <ObjectBuilder generator="{VideoEventMapDelegate}" cache="global" constructorArguments="{scope.dispatcher}"/> + <MethodInvoker generator="{VideoEventMapDelegate}" method="switchToPresenter" arguments="{event}"/> </EventHandlers> <EventHandlers type="{MadePresenterEvent.SWITCH_TO_VIEWER_MODE}"> - <InlineInvoker method="switchToViewer" /> + <ObjectBuilder generator="{VideoEventMapDelegate}" cache="global" constructorArguments="{scope.dispatcher}"/> + <MethodInvoker generator="{VideoEventMapDelegate}" method="switchToViewer" arguments="{event}"/> </EventHandlers> - + + <EventHandlers type="{ConnectedEvent.VIDEO_CONNECTED}"> + <MethodInvoker generator="{VideoEventMapDelegate}" method="connectedToVideoApp" /> + </EventHandlers> + + <EventHandlers type="{ClosePublishWindowEvent.CLOSE_PUBLISH_WINDOW}"> + <MethodInvoker generator="{VideoEventMapDelegate}" method="handleClosePublishWindowEvent" arguments="{event}"/> + </EventHandlers> + + <EventHandlers type="{StoppedViewingWebcamEvent.STOPPED_VIEWING_WEBCAM}"> + <MethodInvoker generator="{VideoEventMapDelegate}" method="handleStoppedViewingWebcamEvent" arguments="{event}"/> + </EventHandlers> + + <!-- ~~~~~~~~~~~~~~~~~~ INJECTORS ~~~~~~~~~~~~~~~~~~~~~~~~~~~ --> + </EventMap> diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/maps/VideoEventMapDelegate.as b/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/maps/VideoEventMapDelegate.as new file mode 100755 index 0000000000000000000000000000000000000000..59f30fef2408a2c649e65566eb5659e237491f7e --- /dev/null +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/maps/VideoEventMapDelegate.as @@ -0,0 +1,373 @@ +package org.bigbluebutton.modules.videoconf.maps +{ + import flash.events.IEventDispatcher; + + import mx.collections.ArrayCollection; + + import org.bigbluebutton.common.LogUtil; + import org.bigbluebutton.common.events.CloseWindowEvent; + import org.bigbluebutton.common.events.OpenWindowEvent; + import org.bigbluebutton.common.events.ToolbarButtonEvent; + import org.bigbluebutton.core.UsersUtil; + import org.bigbluebutton.core.events.ConnectAppEvent; + import org.bigbluebutton.core.managers.UserManager; + import org.bigbluebutton.core.vo.CameraSettingsVO; + import org.bigbluebutton.main.events.BBBEvent; + import org.bigbluebutton.main.events.MadePresenterEvent; + import org.bigbluebutton.main.events.StoppedViewingWebcamEvent; + import org.bigbluebutton.main.events.UserJoinedEvent; + import org.bigbluebutton.main.events.UserLeftEvent; + import org.bigbluebutton.main.model.users.BBBUser; + import org.bigbluebutton.main.model.users.events.BroadcastStartedEvent; + import org.bigbluebutton.main.model.users.events.BroadcastStoppedEvent; + import org.bigbluebutton.main.model.users.events.StreamStartedEvent; + import org.bigbluebutton.modules.videoconf.business.VideoProxy; + import org.bigbluebutton.modules.videoconf.business.VideoWindowItf; + import org.bigbluebutton.modules.videoconf.events.CloseAllWindowsEvent; + import org.bigbluebutton.modules.videoconf.events.ClosePublishWindowEvent; + import org.bigbluebutton.modules.videoconf.events.ConnectedEvent; + import org.bigbluebutton.modules.videoconf.events.OpenVideoWindowEvent; + import org.bigbluebutton.modules.videoconf.events.ShareCameraRequestEvent; + import org.bigbluebutton.modules.videoconf.events.StartBroadcastEvent; + import org.bigbluebutton.modules.videoconf.events.StopBroadcastEvent; + import org.bigbluebutton.modules.videoconf.model.VideoConfOptions; + import org.bigbluebutton.modules.videoconf.views.AvatarWindow; + import org.bigbluebutton.modules.videoconf.views.PublishWindow; + import org.bigbluebutton.modules.videoconf.views.ToolbarButton; + import org.bigbluebutton.modules.videoconf.views.VideoWindow; + import org.bigbluebutton.modules.viewers.events.ViewCameraEvent; + import org.flexunit.runner.manipulation.filters.IncludeAllFilter; + + public class VideoEventMapDelegate + { + private var options:VideoConfOptions = new VideoConfOptions(); + private var webcamWindows:WindowManager = new WindowManager(); + + private var button:ToolbarButton; + private var proxy:VideoProxy; + private var streamName:String; + + private var _dispatcher:IEventDispatcher; + private var _ready:Boolean = false; + private var _isPublishing:Boolean = false; + + public function VideoEventMapDelegate(dispatcher:IEventDispatcher) + { + _dispatcher = dispatcher; + } + + private function get me():String { + return UsersUtil.getMyUsername(); + } + + public function start():void { + trace("[" + me + "] Video Module Started."); + } + + public function stop():void { + + } + + public function viewCamera(userID:String, stream:String, name:String, mock:Boolean = false):void { + trace("[" + me + "] viewCamera. ready = [" + _ready + "]"); + + if (!_ready) return; + trace("[" + me + "] Viewing [" + userID + " stream [" + stream + "]"); + if (! UserManager.getInstance().getConference().amIThisUser(userID)) { + openViewWindowFor(userID); + } + } + + public function handleUserLeftEvent(event:UserLeftEvent):void { + trace("[" + me + "] handleUserLeftEvent. ready = [" + _ready + "]"); + + if (!_ready) return; + + closeWindow(event.userID); + } + + public function handleUserJoinedEvent(event:UserJoinedEvent):void { + trace("[" + me + "] handleUserJoinedEvent. ready = [" + _ready + "]"); + + if (!_ready) return; + + if (options.displayAvatar) { + openAvatarWindowFor(event.userID); + } + } + + private function addToolbarButton():void{ + if (proxy.videoOptions.showButton) { + button = new ToolbarButton(); + button.isPresenter = !options.presenterShareOnly; + var event:ToolbarButtonEvent = new ToolbarButtonEvent(ToolbarButtonEvent.ADD); + event.button = button; + _dispatcher.dispatchEvent(event); + } + } + + private function autoStart():void { + _dispatcher.dispatchEvent(new ShareCameraRequestEvent()); + } + + private function openWebcamWindows():void { + trace("[" + me + "] openWebcamWindows:: ready = [" + _ready + "]"); + + var uids:ArrayCollection = UsersUtil.getUserIDs(); + + for (var i:int = 0; i < uids.length; i++) { + var u:String = uids.getItemAt(i) as String; + trace("[" + me + "] openWebcamWindows:: open window for = [" + u + "]"); + openWebcamWindowFor(u); + } + } + + private function openWebcamWindowFor(userID:String):void { + trace("[" + me + "] openWebcamWindowFor:: open window for = [" + userID + "]"); + if (! UsersUtil.isMe(userID) && UsersUtil.hasWebcamStream(userID)) { + trace("[" + me + "] openWebcamWindowFor:: Not ME and user = [" + userID + "] is publishing."); + + if (webcamWindows.hasWindow(userID)) { + trace("[" + me + "] openWebcamWindowFor:: user = [" + userID + "] has a window open. Close it."); + closeWindow(userID); + } + trace("[" + me + "] openWebcamWindowFor:: View user's = [" + userID + "] webcam."); + openViewWindowFor(userID); + } else { + if (UsersUtil.isMe(userID) && options.autoStart) { + trace("[" + me + "] openWebcamWindowFor:: It's ME and AutoStart. Start publishing."); + autoStart(); + } else { + if (options.displayAvatar) { + trace("[" + me + "] openWebcamWindowFor:: It's NOT ME and NOT AutoStart. Open Avatar for user = [" + userID + "]"); + openAvatarWindowFor(userID); + } else { + trace("[" + me + "] openWebcamWindowFor:: Is THERE another option for user = [" + userID + "]"); + } + } + } + } + + private function openAvatarWindowFor(userID:String):void { + var window:AvatarWindow = new AvatarWindow(); + window.userID = userID; + window.title = UsersUtil.getUserName(userID); + + trace("[" + me + "] openAvatarWindowFor:: Closing window for [" + userID + "] [" + UsersUtil.getUserName(userID) + "]"); + closeWindow(userID); + + webcamWindows.addWindow(window); + + trace("[" + me + "] openAvatarWindowFor:: Opening AVATAR window for [" + userID + "] [" + UsersUtil.getUserName(userID) + "]"); + + openWindow(window); + dockWindow(window); + } + + private function openPublishWindowFor(userID:String, camIndex:int, camWidth:int, camHeight:int):void { + var publishWindow:PublishWindow = new PublishWindow(); + publishWindow.userID = userID; + publishWindow.title = UsersUtil.getUserName(userID); + publishWindow.camIndex = camIndex; + publishWindow.setResolution(camWidth, camHeight); + publishWindow.videoOptions = options; + publishWindow.quality = options.videoQuality; + publishWindow.resolutions = options.resolutions.split(","); + + + trace("[" + me + "] openPublishWindowFor:: Closing window for [" + userID + "] [" + UsersUtil.getUserName(userID) + "]"); + closeWindow(userID); + + webcamWindows.addWindow(publishWindow); + + trace("[" + me + "] openPublishWindowFor:: Opening PUBLISH window for [" + userID + "] [" + UsersUtil.getUserName(userID) + "]"); + + openWindow(publishWindow); + dockWindow(publishWindow); + } + + private function closeWindow(userID:String):void { + if (! webcamWindows.hasWindow(userID)) { + trace("[" + me + "] closeWindow:: No window for [" + userID + "] [" + UsersUtil.getUserName(userID) + "]"); + return; + } + + var win:VideoWindowItf = webcamWindows.removeWindow(userID); + if (win != null) { + trace("[" + me + "] closeWindow:: Closing [" + win.getWindowType() + "] for [" + userID + "] [" + UsersUtil.getUserName(userID) + "]"); + win.close(); + var cwe:CloseWindowEvent = new CloseWindowEvent(); + cwe.window = win; + _dispatcher.dispatchEvent(cwe); + } else { + trace("[" + me + "] closeWindow:: Not Closing. No window for [" + userID + "] [" + UsersUtil.getUserName(userID) + "]"); + } + } + + private function openViewWindowFor(userID:String):void { + trace("[" + me + "] openViewWindowFor:: Opening VIEW window for [" + userID + "] [" + UsersUtil.getUserName(userID) + "]"); + + var window:VideoWindow = new VideoWindow(); + window.userID = userID; + window.videoOptions = options; + window.resolutions = options.resolutions.split(","); + window.title = UsersUtil.getUserName(userID); + + closeWindow(userID); + + var bbbUser:BBBUser = UsersUtil.getUser(userID); + window.startVideo(proxy.connection, bbbUser.streamName); + + webcamWindows.addWindow(window); + openWindow(window); + dockWindow(window); + } + + private function openWindow(window:VideoWindowItf):void { + var windowEvent:OpenWindowEvent = new OpenWindowEvent(OpenWindowEvent.OPEN_WINDOW_EVENT); + windowEvent.window = window; + _dispatcher.dispatchEvent(windowEvent); + } + + private function dockWindow(window:VideoWindowItf):void { + // this event will dock the window, if it's enabled + var openVideoEvent:OpenVideoWindowEvent = new OpenVideoWindowEvent(); + openVideoEvent.window = window; + _dispatcher.dispatchEvent(openVideoEvent); + } + + public function connectToVideoApp():void { + proxy = new VideoProxy(options.uri + "/" + UsersUtil.getInternalMeetingID()); + proxy.connect(); + } + + public function startPublishing(e:StartBroadcastEvent):void{ + LogUtil.debug("[" + me + "] startPublishing:: Publishing stream to: " + proxy.connection.uri + "/" + e.stream); + streamName = e.stream; + proxy.startPublishing(e); + + _isPublishing = true; + UsersUtil.setIAmPublishing(true); + + var broadcastEvent:BroadcastStartedEvent = new BroadcastStartedEvent(); + broadcastEvent.stream = e.stream; + broadcastEvent.userid = UsersUtil.getMyUserID(); + broadcastEvent.isPresenter = UsersUtil.amIPresenter(); + broadcastEvent.camSettings = UsersUtil.amIPublishing(); + + _dispatcher.dispatchEvent(broadcastEvent); + button.publishingStatus(button.START_PUBLISHING); + } + + public function stopPublishing(e:StopBroadcastEvent):void{ + trace("[" + me + "] Stop publishing. ready = [" + _ready + "]"); + stopBroadcasting(); + } + + private function stopBroadcasting():void { + proxy.stopBroadcasting(); + + _isPublishing = false; + UsersUtil.setIAmPublishing(false); + var broadcastEvent:BroadcastStoppedEvent = new BroadcastStoppedEvent(); + broadcastEvent.stream = streamName; + broadcastEvent.userid = UsersUtil.getMyUserID(); + _dispatcher.dispatchEvent(broadcastEvent); + + //Make toolbar button enabled again + button.publishingStatus(button.STOP_PUBLISHING); + + closeWindow(UsersUtil.getMyUserID()); + + if (options.displayAvatar) { + trace("[" + me + "] Opening avatar"); + openAvatarWindowFor(UsersUtil.getMyUserID()); + } + } + + public function handleClosePublishWindowEvent(event:ClosePublishWindowEvent):void { + if (_isPublishing) { + stopBroadcasting(); + } + } + + public function handleShareCameraRequestEvent(event:ShareCameraRequestEvent):void { + openWebcamPreview(event.publishInClient); + } + + private function openWebcamPreview(publishInClient:Boolean):void { + var openEvent:BBBEvent = new BBBEvent(BBBEvent.OPEN_WEBCAM_PREVIEW); + openEvent.payload.publishInClient = publishInClient; + openEvent.payload.resolutions = options.resolutions; + + _dispatcher.dispatchEvent(openEvent); + } + + public function stopModule():void { + closeAllWindows(); + proxy.disconnect(); + } + + public function closeAllWindows():void{ + if (_isPublishing) { + stopBroadcasting(); + } + + _dispatcher.dispatchEvent(new CloseAllWindowsEvent()); + } + + public function switchToPresenter(event:MadePresenterEvent):void{ + trace("[" + me + "] Got Switch to presenter event. ready = [" + _ready + "]"); + + if (!_ready) return; + + if (options.presenterShareOnly){ + button.isPresenter = true; + } + } + + public function switchToViewer(event:MadePresenterEvent):void{ + trace("[" + me + "] Got Switch to viewer event. ready = [" + _ready + "]"); + + if (!_ready) return; + + if (options.presenterShareOnly){ + button.isPresenter = false; + if (_isPublishing) { + stopBroadcasting(); + } + } + } + + public function connectedToVideoApp():void{ + trace("[" + me + "] Connected to video application."); + _ready = true; + addToolbarButton(); + openWebcamWindows(); + } + + public function handleCameraSetting(event:BBBEvent):void { + var cameraIndex:int = event.payload.cameraIndex; + var camWidth:int = event.payload.cameraWidth; + var camHeight:int = event.payload.cameraHeight; + trace("VideoEventMapDelegate::handleCameraSettings [" + cameraIndex + "," + camWidth + "," + camHeight + "]"); + var camSettings:CameraSettingsVO = new CameraSettingsVO(); + camSettings.camIndex = cameraIndex; + camSettings.camWidth = camWidth; + camSettings.camHeight = camHeight; + + UsersUtil.setCameraSettings(camSettings); + + openPublishWindowFor(UsersUtil.getMyUserID(), cameraIndex, camWidth, camHeight); + } + + public function handleStoppedViewingWebcamEvent(event:StoppedViewingWebcamEvent):void { + closeWindow(event.webcamUserID); + + if (options.displayAvatar) { + trace("[" + me + "] Opening avatar"); + openAvatarWindowFor(event.webcamUserID); + } + } + } +} \ No newline at end of file diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/maps/WindowManager.as b/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/maps/WindowManager.as new file mode 100755 index 0000000000000000000000000000000000000000..8fd7a3e3d1063791b882fcbd4998994585bbade6 --- /dev/null +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/maps/WindowManager.as @@ -0,0 +1,59 @@ +package org.bigbluebutton.modules.videoconf.maps +{ + import flash.media.Video; + + import mx.collections.ArrayCollection; + + import org.bigbluebutton.core.UsersUtil; + import org.bigbluebutton.modules.videoconf.business.VideoWindowItf; + import org.bigbluebutton.modules.videoconf.views.AvatarWindow; + + public class WindowManager + { + private var webcamWindows:ArrayCollection = new ArrayCollection(); + + private function get me():String { + return UsersUtil.getMyUsername(); + } + + public function addWindow(window:VideoWindowItf):void { + webcamWindows.addItem(window); + trace("[" + me + "] addWindow:: userID = [" + window.userID + "] numWindows = [" + webcamWindows.length + "]"); + } + + public function removeWindow(userID:String):VideoWindowItf { + for (var i:int = 0; i < webcamWindows.length; i++) { + var win:VideoWindowItf = webcamWindows.getItemAt(i) as VideoWindowItf; + trace("[" + me + "] removeWindow:: [" + win.userID + " == " + userID + "] equal = [" + (win.userID == userID) + "]"); + if (win.userID == userID) { + return webcamWindows.removeItemAt(i) as VideoWindowItf; + } + } + + return null; + } + + public function hasWindow(userID:String):Boolean { + trace("[" + me + "] hasWindow:: user [" + userID + "] numWindows = [" + webcamWindows.length + "]"); + for (var i:int = 0; i < webcamWindows.length; i++) { + var win:VideoWindowItf = webcamWindows.getItemAt(i) as VideoWindowItf; + trace("[" + me + "] hasWindow:: [" + win.userID + " == " + userID + "] equal = [" + (win.userID == userID) + "]"); + if (win.userID == userID) { + return true; + } + } + + return false; + } + + public function getWindow(userID:String):VideoWindowItf { + for (var i:int = 0; i < webcamWindows.length; i++) { + var win:VideoWindowItf = webcamWindows.getItemAt(i) as VideoWindowItf; + trace("[" + me + "] getWindow:: [" + win.userID + " == " + userID + "] equal = [" + (win.userID == userID) + "]"); + if (win.userID == userID) return win; + } + + return null; + } + } +} \ No newline at end of file diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/model/PublishingModel.as b/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/model/PublishingModel.as new file mode 100755 index 0000000000000000000000000000000000000000..360d8a1fd6f66a1bc7c478d4affd5f7bab34e0d8 --- /dev/null +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/model/PublishingModel.as @@ -0,0 +1,15 @@ +package org.bigbluebutton.modules.videoconf.model +{ + public class PublishingModel + { + public var streamName; + public var camIndex:int; + public var camWidth:int; + public var camHeight:int; + public var isPublishing:Boolean = false; + + public function PublishingModel() + { + } + } +} \ No newline at end of file diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/model/VideoConfOptions.as b/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/model/VideoConfOptions.as index b082650627eead48a04d09e25cfbc255f9261b87..023a35a67efd91d51a2f4dc261ce449a0c2dd796 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/model/VideoConfOptions.as +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/model/VideoConfOptions.as @@ -4,6 +4,14 @@ package org.bigbluebutton.modules.videoconf.model public class VideoConfOptions { + public var uri:String = "rtmp://localhost/video"; + + [Bindable] + public var videoQuality:Number = 100; + + [Bindable] + public var resolutions:String = "320x240,640x480,1280x720"; + [Bindable] public var autoStart:Boolean = false; @@ -64,6 +72,9 @@ package org.bigbluebutton.modules.videoconf.model [Bindable] public var controlsForPresenter:Boolean = false; + [Bindable] + public var displayAvatar:Boolean = false; + public function VideoConfOptions() { parseOptions(); } @@ -71,6 +82,15 @@ package org.bigbluebutton.modules.videoconf.model public function parseOptions():void { var vxml:XML = BBB.getConfigForModule("VideoconfModule"); if (vxml != null) { + if (vxml.@uri != undefined) { + uri = vxml.@uri.toString(); + } + if (vxml.@videoQuality != undefined) { + videoQuality = Number(vxml.@videoQuality.toString()); + } + if (vxml.@resolutions != undefined) { + resolutions = vxml.@resolutions.toString(); + } if (vxml.@showCloseButton != undefined) { showCloseButton = (vxml.@showCloseButton.toString().toUpperCase() == "TRUE") ? true : false; } @@ -138,6 +158,9 @@ package org.bigbluebutton.modules.videoconf.model if (vxml.@h264Profile != undefined) { h264Profile = vxml.@h264Profile.toString(); } + if (vxml.@displayAvatar != undefined) { + displayAvatar = (vxml.@displayAvatar.toString().toUpperCase() == "TRUE") ? true : false; + } } } } diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/views/AvatarWindow.mxml b/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/views/AvatarWindow.mxml new file mode 100755 index 0000000000000000000000000000000000000000..1838ce987415d043355f98f23120706ecf071d1e --- /dev/null +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/views/AvatarWindow.mxml @@ -0,0 +1,189 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- + BigBlueButton open source conferencing system - http://www.bigbluebutton.org + + Copyright (c) 2010 BigBlueButton Inc. and by respective authors (see below). + + BigBlueButton is free software; you can redistribute it and/or modify it under the + terms of the GNU Lesser General Public License as published by the Free Software + Foundation; either version 2.1 of the License, or (at your option) any later + version. + + BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License along + with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. + + $Id: $ +--> + +<pubVid:VideoWindowItf + xmlns:mx="http://www.adobe.com/2006/mxml" + xmlns:pubVid="org.bigbluebutton.modules.videoconf.business.*" + implements="org.bigbluebutton.common.IBbbModuleWindow" + styleNameFocus="videoAvatarStyleFocus" + styleNameNoFocus="videoAvatarStyleNoFocus" + creationComplete="onCreationComplete()" + width="{defaultWidth + 6}" height="{defaultHeight + 6}" + xmlns:mate="http://mate.asfusion.com/" + resize="onResize()" + horizontalScrollPolicy="off" + verticalScrollPolicy="off" + layout="absolute"> + + <mate:Listener type="{BBBEvent.USER_VOICE_MUTED}" method="handleUserVoiceMutedEvent" /> + <mate:Listener type="{EventConstants.USER_TALKING}" method="handleUserTalkingEvent" /> + <mate:Listener type="{EventConstants.SWITCHED_PRESENTER}" method="handleNewRoleEvent" /> + <mate:Listener type="{CloseAllWindowsEvent.CLOSE_ALL_WINDOWS}" method="closeWindow" /> + + <mx:Script> + <![CDATA[ + import flexlib.mdi.events.MDIWindowEvent; + import mx.core.UIComponent; + import mx.events.ResizeEvent; + import org.bigbluebutton.common.Images; + import org.bigbluebutton.common.LogUtil; + import org.bigbluebutton.common.Role; + import org.bigbluebutton.common.events.CloseWindowEvent; + import org.bigbluebutton.common.events.LocaleChangeEvent; + import org.bigbluebutton.core.EventConstants; + import org.bigbluebutton.core.UsersUtil; + import org.bigbluebutton.core.events.CoreEvent; + import org.bigbluebutton.core.managers.UserManager; + import org.bigbluebutton.main.events.BBBEvent; + import org.bigbluebutton.main.views.MainCanvas; + import org.bigbluebutton.modules.videoconf.business.TalkingButtonOverlay; + import org.bigbluebutton.modules.videoconf.events.CloseAllWindowsEvent; + import org.bigbluebutton.modules.videoconf.events.OpenVideoWindowEvent; + import org.bigbluebutton.modules.videoconf.events.StartBroadcastEvent; + import org.bigbluebutton.modules.videoconf.model.VideoConfOptions; + import org.bigbluebutton.util.i18n.ResourceUtil; + + [Bindable] private var defaultWidth:Number = 320; + [Bindable] private var defaultHeight:Number = 240; + + private var camWidth:Number = 320; + private var camHeight:Number = 240; + + private var windowType:String = "AvatarWindowType"; + + override public function getWindowType():String { + return windowType; + } + + private var loader:Loader; + private var request:URLRequest; + + private function loadAvatar():void { + loader = new Loader(); + request = new URLRequest(UsersUtil.getAvatarURL()); + loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoadingComplete); + loader.load(request); + + } + + private function onLoadingComplete(event:Event):void { + loader.x = (_videoHolder.width - loader.width) / 2; + loader.y = (_videoHolder.height - loader.height) / 2; + _videoHolder.addChild(loader); + + onResize(); + } + + private function onCreationComplete():void { + _videoHolder = new UIComponent(); + _videoHolder.width = camWidth; + _videoHolder.height = camHeight; + this.addChild(_videoHolder); + + _video = new Video(); + + this.minWidth = _minWidth; + this.minHeight = _minHeight; + maximizeRestoreBtn.visible = false; + + showCloseButton = false; + + this.resizable = false; + setAspectRatio(camWidth, camHeight); + + addEventListener(MDIWindowEvent.RESIZE_START, onResizeStart); + addEventListener(MDIWindowEvent.RESIZE_END, onResizeEnd); + addEventListener(MouseEvent.MOUSE_OVER, showButtons); + addEventListener(MouseEvent.MOUSE_OUT, hideButtons); + + startPublishing(); + + loadAvatar(); + } + + override protected function onResize():void { + super.onResize(); + + if (loader != null && _videoHolder != null) { + loader.x = (_videoHolder.width - loader.width) / 2; + loader.y = (_videoHolder.height - loader.height) / 2; + } + } + + private function handleNewRoleEvent(event:CoreEvent):void { + switchRole(event.message.role == Role.PRESENTER) + } + + private function handleUserVoiceMutedEvent(event:BBBEvent):void { + if (event.payload.userID == userID) { + userMuted(event.payload.muted); + } + } + + private function handleUserTalkingEvent(event:CoreEvent):void { + if (event.message.userID == userID) { + if (event.message.talking) { + notTalkingEffect.end(); + talkingEffect.play([this]); + } else { + talkingEffect.end(); + notTalkingEffect.play([this]); + } + } + } + + private function startPublishing():void{ + defaultWidth = originalWidth; + defaultHeight = originalHeight; + + maximizeRestoreBtn.visible = true; + this.resizable = true; + onResize(); + + createButtons(); + addControlButtons(); + updateButtonsPosition(); + } + + override public function close(event:MouseEvent=null):void{ + closeThisWindow(); + super.close(event); + } + + private function closeWindow(e:CloseAllWindowsEvent):void{ + closeThisWindow(); + } + + private function closeThisWindow():void { + trace("******************** Closing avatar window for user [" + userID + "]*********************"); + } + + + ]]> + </mx:Script> + + <mx:Glow id="talkingEffect" duration="500" alphaFrom="1.0" alphaTo="0.3" + blurXFrom="0.0" blurXTo="30.0" blurYFrom="0.0" blurYTo="30.0" color="0x4A931D"/> + <mx:Glow id="notTalkingEffect" duration="500" alphaFrom="0.3" alphaTo="1.0" + blurXFrom="30.0" blurXTo="0.0" blurYFrom="30.0" blurYTo="0.0" color="0x4A931D"/> + +</pubVid:VideoWindowItf> diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/views/ControlButtons.mxml b/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/views/ControlButtons.mxml index 9f894a292071d6860c028d4d63590df5fc6314c5..85c49525b40f646df6f79f0be0f706fed6bec108 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/views/ControlButtons.mxml +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/views/ControlButtons.mxml @@ -5,8 +5,7 @@ <mx:Script> <![CDATA[ - import com.asfusion.mate.events.Dispatcher; - + import com.asfusion.mate.events.Dispatcher; import org.bigbluebutton.common.Images; import org.bigbluebutton.common.LogUtil; import org.bigbluebutton.core.EventConstants; @@ -22,20 +21,9 @@ public var sharerUserID:String; [Bindable] - private var BUTTONS_SIZE:int = 20; + private var BUTTONS_SIZE:int = 31; private var BUTTONS_PADDING:int = 10; - - private var images:Images = new Images(); - - [Bindable] - private var img_mute_icon:Class = images.sound_mute; - [Bindable] - private var signOutIcon:Class = images.signOutIcon; - [Bindable] - private var adminIcon:Class = images.admin; - [Bindable] - private var chatIcon:Class = images.chatIcon; - + [Bindable] private var showButton:Boolean; @@ -48,6 +36,14 @@ return BUTTONS_PADDING; } + public function userMuted(muted:Boolean):void { + if (muted) { + muteUnmuteBtn.styleName = "videoMutedButtonStyle"; + } else { + muteUnmuteBtn.styleName = "videoUnmutedButtonStyle"; + } + } + public function handleNewRoleEvent(presenter:Boolean):void { showControlButtons(presenter); } @@ -108,8 +104,15 @@ } ]]> </mx:Script> - <mx:Button id="muteUnmuteBtn" visible="false" click="onMuteUnmuteClicked()" icon="{img_mute_icon}" width="{BUTTONS_SIZE}" height="{BUTTONS_SIZE}"/> - <mx:Button id="switchPresenter" visible="false" click="onSwitchPresenterClicked()" icon="{adminIcon}" width="{BUTTONS_SIZE}" height="{BUTTONS_SIZE}"/> - <mx:Button id="ejectUserBtn" visible="false" click="onKickUserClicked()" icon="{signOutIcon}" width="{BUTTONS_SIZE}" height="{BUTTONS_SIZE}"/> - <mx:Button id="privateChatBtn" click="onPrivateChatClicked()" icon="{chatIcon}" width="{BUTTONS_SIZE}" height="{BUTTONS_SIZE}"/> + <mx:Button id="muteUnmuteBtn" visible="false" click="onMuteUnmuteClicked()" + width="{BUTTONS_SIZE}" height="{BUTTONS_SIZE}" styleName="videoUnmutedButtonStyle"/> + <mx:Spacer width="2"/> + <mx:Button id="switchPresenter" visible="false" click="onSwitchPresenterClicked()" + width="{BUTTONS_SIZE}" height="{BUTTONS_SIZE}" styleName="videoSwitchPresenterButtonStyle"/> + <mx:Spacer width="2"/> + <mx:Button id="ejectUserBtn" visible="false" click="onKickUserClicked()" + width="{BUTTONS_SIZE}" height="{BUTTONS_SIZE}" styleName="videoEjectUserButtonStyle"/> + <mx:Spacer width="2"/> + <mx:Button id="privateChatBtn" click="onPrivateChatClicked()" + width="{BUTTONS_SIZE}" height="{BUTTONS_SIZE}" styleName="videoPrivateChatButtonStyle"/> </mx:HBox> diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/views/PublishWindow.mxml b/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/views/PublishWindow.mxml index 14dfdff9fd2f93187dea6e26f6854f911a1461b2..41b46c6b80b8df6eb530693a68d666f3816dfd2f 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/views/PublishWindow.mxml +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/views/PublishWindow.mxml @@ -24,30 +24,38 @@ xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:pubVid="org.bigbluebutton.modules.videoconf.business.*" implements="org.bigbluebutton.common.IBbbModuleWindow" - creationComplete="init()" - width="{defaultWidth + 6}" height="{defaultHeight + 75}" + styleNameFocus="videoPublishStyleFocus" + styleNameNoFocus="videoPublishStyleNoFocus" + creationComplete="onCreationComplete()" + width="{defaultWidth + 6}" height="{defaultHeight + 6}" xmlns:mate="http://mate.asfusion.com/" resize="onResize()" horizontalScrollPolicy="off" verticalScrollPolicy="off" layout="absolute"> + <mate:Listener type="{BBBEvent.USER_VOICE_MUTED}" method="handleUserVoiceMutedEvent" /> <mate:Listener type="{EventConstants.USER_TALKING}" method="handleUserTalkingEvent" /> - <mate:Listener type="{EventConstants.NEW_ROLE}" method="handleNewRoleEvent" /> + <mate:Listener type="{EventConstants.SWITCHED_PRESENTER}" method="handleNewRoleEvent" /> + <mate:Listener type="{CloseAllWindowsEvent.CLOSE_ALL_WINDOWS}" method="closeWindow" /> <mx:Script> <![CDATA[ - import flexlib.mdi.events.MDIWindowEvent; + import flexlib.mdi.events.MDIWindowEvent; + import mx.core.UIComponent; - import mx.events.ResizeEvent; + import mx.events.ResizeEvent; + import org.bigbluebutton.common.Images; import org.bigbluebutton.common.LogUtil; + import org.bigbluebutton.common.Role; import org.bigbluebutton.common.events.CloseWindowEvent; import org.bigbluebutton.common.events.LocaleChangeEvent; import org.bigbluebutton.core.EventConstants; import org.bigbluebutton.core.UsersUtil; import org.bigbluebutton.core.events.CoreEvent; import org.bigbluebutton.core.managers.UserManager; + import org.bigbluebutton.main.events.BBBEvent; import org.bigbluebutton.main.views.MainCanvas; import org.bigbluebutton.modules.videoconf.business.TalkingButtonOverlay; import org.bigbluebutton.modules.videoconf.events.CloseAllWindowsEvent; @@ -56,17 +64,13 @@ import org.bigbluebutton.modules.videoconf.events.StopBroadcastEvent; import org.bigbluebutton.modules.videoconf.model.VideoConfOptions; import org.bigbluebutton.util.i18n.ResourceUtil; - import org.bigbluebutton.common.Role; - [Bindable] public var camIcon:Class = images.control_play; - [Bindable] public var bbbLogo:Class = images.bbb_logo; - [Bindable] private var defaultWidth:Number = 320; [Bindable] private var defaultHeight:Number = 240; - + + public var camIndex:int = 0; private var camWidth:Number = 320; - private var camHeight:Number = 240; - private var _userrole:String; + private var camHeight:Number = 240; private var _camera:Camera = null; public var quality:Number = 0; @@ -87,37 +91,31 @@ [Bindable] public var videoOptions:VideoConfOptions; - private function init():void{ + private var windowType:String = "PublishWindowType"; + + override public function getWindowType():String { + return windowType; + } + + private function onCreationComplete():void{ _videoHolder = new UIComponent(); _videoHolder.width = camWidth; _videoHolder.height = camHeight; this.addChild(_videoHolder); - this.title = ResourceUtil.getInstance().getString('bbb.publishVideo.title'); - - checkIfMacCamera(); - if (isPresenter()) showResControls(true); - if (Camera.names.length > 1) showVideoControls(true); - if (!isPresenter() && Camera.names.length == 1) startPublishing(); this.minWidth = _minWidth; this.minHeight = _minHeight; maximizeRestoreBtn.visible = false; this.resizable = false; - - loadPrefs(); - + this.visible = videoOptions.publishWindowVisible; - if (videoOptions.autoStart) { - /* - * Need to have a timer to trigger auto-publishing of webcam. - */ - autoPublishTimer = new Timer(3000, 1); - autoPublishTimer.addEventListener(TimerEvent.TIMER, autopublishTimerHandler); - autoPublishTimer.start(); - } - - // start the camera preview + addEventListener(MDIWindowEvent.RESIZE_START, onResizeStart); + addEventListener(MDIWindowEvent.RESIZE_END, onResizeEnd); + addEventListener(MouseEvent.MOUSE_OVER, showButtons); + addEventListener(MouseEvent.MOUSE_OUT, hideButtons); + addEventListener(MouseEvent.DOUBLE_CLICK, onDoubleClick); + updateCamera(); } @@ -130,37 +128,33 @@ switchRole(event.message.role == Role.PRESENTER) } + private function handleUserVoiceMutedEvent(event:BBBEvent):void { + if (event.payload.userID == userID) { + userMuted(event.payload.muted); + } + } + private function handleUserTalkingEvent(event:CoreEvent):void { - if (event.message.userID == _sharerUserID) { + if (event.message.userID == userID) { if (event.message.talking) { notTalkingEffect.end(); - talkingEffect.play([_videoHolder]); + talkingEffect.play([this]); } else { talkingEffect.end(); - notTalkingEffect.play([_videoHolder]); + notTalkingEffect.play([this]); } } } - - private function loadPrefs():void{ - var sharedObject:SharedObject = SharedObject.getLocal("bbbUserProperties", "/"); - var webcam:String = sharedObject.data["webcam"] as String; - for (var i:Number = 0; i<Camera.names.length; i++){ - if (Camera.names[i] == webcam) cmbCameraSelector.selectedIndex = i; - } - } - + private function updateCamera():void { stopCamera(); - - btnStartPublish.enabled = false; - + if (Camera.names.length == 0) { showWarning('bbb.video.publish.hint.noCamera'); return; } - _camera = Camera.getCamera(cmbCameraSelector.selectedIndex.toString()); + _camera = Camera.getCamera(); if (_camera == null) { showWarning('bbb.video.publish.hint.cantOpenCamera'); return; @@ -181,17 +175,15 @@ onCameraAccessAllowed(); } - _camera.addEventListener(ActivityEvent.ACTIVITY, onActivityEvent); - _camera.addEventListener(StatusEvent.STATUS, onStatusEvent); - - setComboResolution(); + _camera.addEventListener(ActivityEvent.ACTIVITY, onActivityEvent); + _camera.addEventListener(StatusEvent.STATUS, onStatusEvent); _camera.setKeyFrameInterval(videoOptions.camKeyFrameInterval); _camera.setMode(camWidth, camHeight, videoOptions.camModeFps); _camera.setQuality(videoOptions.camQualityBandwidth, videoOptions.camQualityPicture); if (_camera.width != camWidth || _camera.height != camHeight) { - LogUtil.debug("Resolution " + camWidth + "x" + camHeight + " is not supported, using " + _camera.width + "x" + _camera.height + " instead"); + trace("Resolution " + camWidth + "x" + camHeight + " is not supported, using " + _camera.width + "x" + _camera.height + " instead"); setResolution(_camera.width, _camera.height); } @@ -211,36 +203,43 @@ } _videoHolder.addChild(_video); - + } - private function onActivityEvent(e:ActivityEvent):void { - if (_waitingForActivation && e.activating) { - _activationTimer.stop(); - showWarning('bbb.video.publish.hint.videoPreview', false, "0xFFFF00"); - btnStartPublish.enabled = true; - _waitingForActivation = false; - } + private function onActivityEvent(e:ActivityEvent):void { + if (_waitingForActivation && e.activating) { + trace("Cam activity event: waitingForActivation = [" + _waitingForActivation + "] activating = [" + e.activating + "]"); + _activationTimer.stop(); + showWarning('bbb.video.publish.hint.videoPreview', false, "0xFFFF00"); + _waitingForActivation = false; + + trace("Starting auto-publisher timer."); + autoPublishTimer = new Timer(3000, 1); + autoPublishTimer.addEventListener(TimerEvent.TIMER, autopublishTimerHandler); + autoPublishTimer.start(); + } } private function onStatusEvent(e:StatusEvent):void { if (e.code == "Camera.Unmuted") { onCameraAccessAllowed(); // this is just to overwrite the message of waiting for approval - showWarning('bbb.video.publish.hint.openingCamera'); - } else if (e.code == "Camera.Muted") { - onCameraAccessDisallowed(); - } + showWarning('bbb.video.publish.hint.openingCamera'); + } else if (e.code == "Camera.Muted") { + onCameraAccessDisallowed(); + } } private function onCameraAccessAllowed():void { // set timer to ensure that the camera activates. If not, it might be in use by another application _waitingForActivation = true; - if (_activationTimer != null) - _activationTimer.stop(); - _activationTimer = new Timer(10000, 1); - _activationTimer.addEventListener(TimerEvent.TIMER, activationTimeout); - _activationTimer.start(); + if (_activationTimer != null) { + _activationTimer.stop(); + } + + _activationTimer = new Timer(10000, 1); + _activationTimer.addEventListener(TimerEvent.TIMER, activationTimeout); + _activationTimer.start(); } private function onCameraAccessDisallowed():void { @@ -257,9 +256,10 @@ private function startPublishing():void{ if (_camera == null) return; - if (autoPublishTimer != null) - autoPublishTimer.stop(); - + if (autoPublishTimer != null) { + autoPublishTimer.stop(); + } + showWarning('bbb.video.publish.hint.publishing', true, "0xFFFF00"); defaultWidth = originalWidth; @@ -270,63 +270,52 @@ e.camera = _camera; dispatchEvent(e); - showVideoControls(false); - showResControls(false); - maximizeRestoreBtn.visible = true; this.resizable = true; onResize(); - addEventListener(MDIWindowEvent.RESIZE_START, onResizeStart); - addEventListener(MDIWindowEvent.RESIZE_END, onResizeEnd); - addEventListener(MDIWindowEvent.DRAG_START, onDragStart); - addEventListener(MDIWindowEvent.DRAG, onDrag); - addEventListener(MDIWindowEvent.DRAG_END, onDragEnd); - addEventListener(MouseEvent.MOUSE_OVER, showButtons); - addEventListener(MouseEvent.MOUSE_OUT, hideButtons); - addEventListener(MouseEvent.DOUBLE_CLICK, onDoubleClick); + // Store the userid for the publisher. This allows us to control // the user's status from the video window - _sharerUserID = UsersUtil.getMyUserID(); + userID = UsersUtil.getMyUserID(); createButtons(); addControlButtons(); - - // this event will dock the window, if it's enabled - var openVideoEvent:OpenVideoWindowEvent = new OpenVideoWindowEvent(); - openVideoEvent.window = this; - dispatchEvent(openVideoEvent); + } + private var _isClosing:Boolean = false; + override public function close(event:MouseEvent=null):void{ - stopPublishing(); - super.close(event); + trace("PublishWindow::close"); + if (!_isClosing) { + _isClosing = true; + stopPublishing(); + } } private function stopCamera():void { + _camera = null; if (_video != null) { _videoHolder.removeChild(_video); _video.attachCamera(null); _video.clear(); _video = null; } - _camera = null; + } private function stopPublishing():void{ + trace("PublishWindow::stopPublishing"); stopCamera(); var e:StopBroadcastEvent = new StopBroadcastEvent() e.stream = streamName; dispatchEvent(e); } - private function setComboResolution():void { - var res:Array = cmbResolution.selectedLabel.split( "x" ); - setResolution(Number(res[0]), Number(res[1])); - } - - private function setResolution(width:int, height:int):void { + + public function setResolution(width:int, height:int):void { camWidth = originalWidth = width; camHeight = originalHeight = height; setAspectRatio(camWidth, camHeight); @@ -337,63 +326,20 @@ */ var curTime:Number = new Date().getTime(); var uid:String = UserManager.getInstance().getConference().getMyUserId(); - this.streamName = cmbResolution.selectedLabel.concat("-" + uid) + "-" + curTime; - } - - public function set userrole(role:String):void{ - this._userrole = role; + var res:String = camWidth + "x" + camHeight; + this.streamName = res.concat("-" + uid) + "-" + curTime; } - + private function isPresenter():Boolean{ - if (this._userrole == "MODERATOR" || this._userrole == "PRESENTER") return true; + if (UsersUtil.amIModerator() || UsersUtil.amIPresenter()) return true; else return false; } - - private function showVideoControls(show:Boolean):void{ - if (show){ - videoOptionsBar.visible = true; - btnStartPublish.visible = true; - cmbCameraSelector.visible = true; - } else{ - videoOptionsBar.visible = false; - videoOptionsBar.width = 0; - videoOptionsBar.height = 0; - btnStartPublish.visible = false; - cmbCameraSelector.visible = false; - videoOptionsBar.visible = false; - } - } - - private function showResControls(show:Boolean):void{ - if (show) cmbResolution.visible = true; - else cmbResolution.visible = false; - } - + private function closeWindow(e:CloseAllWindowsEvent):void{ - closeThisWindow(); - } - - private function closeThisWindow():void { - stopCamera(); - this.close(); + trace("PublishWindow::closeWindow"); + stopCamera(); } - - private function checkIfMacCamera():void{ - for (var i:int = 0; i<Camera.names.length; i++){ - if (Camera.names[i] == "USB Video Class Video") { - /** - * Set as default for Macs - */ - cmbCameraSelector.selectedIndex = i; - } - } - } - - override protected function resourcesChanged():void{ - super.resourcesChanged(); - } - - + private var hideWarningTimer:Timer = null; private function showWarning(resourceName:String, autoHide:Boolean=false, color:String="0xFF0000"):void { @@ -421,19 +367,15 @@ ]]> </mx:Script> - <mx:Glow id="talkingEffect" duration="500" alphaFrom="1.0" alphaTo="0.3" - blurXFrom="0.0" blurXTo="30.0" blurYFrom="0.0" blurYTo="30.0" color="0xFF0000"/> + <mx:Glow id="talkingEffect" duration="500" alphaFrom="1.0" alphaTo="0.3" + blurXFrom="0.0" blurXTo="30.0" blurYFrom="0.0" blurYTo="30.0" color="0x4A931D"/> <mx:Glow id="notTalkingEffect" duration="500" alphaFrom="0.3" alphaTo="1.0" - blurXFrom="30.0" blurXTo="0.0" blurYFrom="30.0" blurYTo="0.0" color="0xFF0000"/> + blurXFrom="30.0" blurXTo="0.0" blurYFrom="30.0" blurYTo="0.0" color="0x4A931D"/> <mx:Fade id="dissolveOut" duration="1000" alphaFrom="1.0" alphaTo="0.0"/> <mx:Fade id="dissolveIn" duration="1000" alphaFrom="0.0" alphaTo="1.0"/> - <mx:Text id="lblWarning" width="100%" textAlign="right" fontSize="14" fontWeight="bold" y="{this.height - (videoOptionsBar.visible? videoOptionsBar.height: 0) - lblWarning.height - 30}" visible="false" selectable="false" hideEffect="{dissolveOut}" showEffect="{dissolveIn}"/> - <mx:ControlBar id="videoOptionsBar" visible="true"> - <mx:Button id="btnStartPublish" toolTip="{ResourceUtil.getInstance().getString('bbb.publishVideo.startPublishBtn.toolTip')}" icon="{camIcon}" click="startPublishing()" enabled="false"/> - <mx:ComboBox id="cmbCameraSelector" dataProvider="{Camera.names}" width="150" visible="false" change="updateCamera()"/> - <mx:ComboBox id="cmbResolution" dataProvider="{resolutions}" width="20%" visible="false" change="updateCamera()"/> - </mx:ControlBar> - - <mate:Listener type="{CloseAllWindowsEvent.CLOSE_ALL_WINDOWS}" method="closeWindow" /> + <mx:Text id="lblWarning" width="100%" textAlign="right" fontSize="14" fontWeight="bold" y="{this.height - lblWarning.height - 30}" + visible="false" selectable="false" hideEffect="{dissolveOut}" showEffect="{dissolveIn}"/> + + </pubVid:VideoWindowItf> diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/views/ToolbarButton.mxml b/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/views/ToolbarButton.mxml index 9afb2fbe702ed4b4f73e9249bd53c42c846ce1e1..16dab102e1c6c1751d52eb9d464a2e021dda7a28 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/views/ToolbarButton.mxml +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/views/ToolbarButton.mxml @@ -44,17 +44,14 @@ private var images:Images = new Images(); [Bindable] public var camIcon:Class = images.webcam; - public const OFF_STATE:Number = 0; public const ON_STATE:Number = 1; public const STOP_PUBLISHING:Number = 0; public const START_PUBLISHING:Number = 1; - - + private var _currentState:Number = OFF_STATE; - private var dispatcher:Dispatcher; diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/views/VideoWindow.mxml b/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/views/VideoWindow.mxml index 958c5387932d5aa5de35e4208d5276418c7f0afc..529837030e9791fa7d796cafdfba6c5fb1c165ef 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/views/VideoWindow.mxml +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/views/VideoWindow.mxml @@ -23,31 +23,35 @@ <viewVid:VideoWindowItf xmlns:viewVid="org.bigbluebutton.modules.videoconf.business.*" xmlns:mx="http://www.adobe.com/2006/mxml" - creationComplete="init()" + creationComplete="onCreationComplete()" implements="org.bigbluebutton.common.IBbbModuleWindow" xmlns:mate="http://mate.asfusion.com/" + styleNameFocus="videoViewStyleFocus" + styleNameNoFocus="videoViewStyleNoFocus" resize="onResize()" layout="absolute"> + <mate:Listener type="{BBBEvent.USER_VOICE_MUTED}" method="handleUserVoiceMutedEvent" /> <mate:Listener type="{EventConstants.USER_TALKING}" method="handleUserTalkingEvent" /> - <mate:Listener type="{EventConstants.NEW_ROLE}" method="handleNewRoleEvent" /> + <mate:Listener type="{EventConstants.SWITCHED_PRESENTER}" method="handleNewRoleEvent" /> + <mate:Listener type="{CloseAllWindowsEvent.CLOSE_ALL_WINDOWS}" method="closeWindow" /> <mx:Script> <![CDATA[ import com.asfusion.mate.events.Dispatcher; - import flexlib.mdi.events.MDIWindowEvent; - import mx.controls.Alert; + import flexlib.mdi.events.MDIWindowEvent; import mx.core.UIComponent; import org.bigbluebutton.common.LogUtil; + import org.bigbluebutton.common.Role; import org.bigbluebutton.common.events.CloseWindowEvent; import org.bigbluebutton.core.EventConstants; import org.bigbluebutton.core.events.CoreEvent; import org.bigbluebutton.main.events.BBBEvent; + import org.bigbluebutton.main.events.StoppedViewingWebcamEvent; import org.bigbluebutton.main.views.MainCanvas; import org.bigbluebutton.modules.videoconf.business.TalkingButtonOverlay; import org.bigbluebutton.modules.videoconf.events.CloseAllWindowsEvent; import org.bigbluebutton.modules.videoconf.model.VideoConfOptions; - import org.bigbluebutton.common.Role; private var ns:NetStream; private var globalDispatcher:Dispatcher; @@ -55,7 +59,13 @@ [Bindable] public var videoOptions:VideoConfOptions; - private function init():void{ + private var windowType:String = "VideoWindowType"; + + override public function getWindowType():String { + return windowType; + } + + private function onCreationComplete():void{ _videoHolder = new UIComponent(); _videoHolder.addChild(_video); this.addChild(_videoHolder); @@ -64,9 +74,6 @@ addEventListener(MDIWindowEvent.RESIZE_END, onResizeEnd); addEventListener(MDIWindowEvent.CLOSE, onCloseEvent); - addEventListener(MDIWindowEvent.DRAG_START, onDragStart); - addEventListener(MDIWindowEvent.DRAG, onDrag); - addEventListener(MDIWindowEvent.DRAG_END, onDragEnd); addEventListener(MouseEvent.MOUSE_OVER, showButtons); addEventListener(MouseEvent.MOUSE_OUT, hideButtons); addEventListener(MouseEvent.DOUBLE_CLICK, onDoubleClick); @@ -100,29 +107,38 @@ switchRole(event.message.role == Role.PRESENTER) } + private function handleUserVoiceMutedEvent(event:BBBEvent):void { + if (event.payload.userID == userID) { + userMuted(event.payload.muted); + } + } + + private var _closing:Boolean = false; + private function onCloseEvent(event:MDIWindowEvent = null):void { LogUtil.debug("ViewWindow closing " + streamName); - var bbbEvt:BBBEvent = new BBBEvent("ViewVideoCloseEvent"); - bbbEvt.message = streamName; - dispatchEvent(bbbEvt); + if (!_closing) { + _closing = true; + var stopEvent:StoppedViewingWebcamEvent = new StoppedViewingWebcamEvent(); + stopEvent.webcamUserID = userID; + globalDispatcher.dispatchEvent(stopEvent); + } + } private function handleUserTalkingEvent(event:CoreEvent):void { - if (event.message.userID == _sharerUserID) { + if (event.message.userID == userID) { if (event.message.talking) { notTalkingEffect.end(); - talkingEffect.play([_videoHolder]); + talkingEffect.play([this]); } else { talkingEffect.end(); - notTalkingEffect.play([_videoHolder]); + notTalkingEffect.play([this]); } } } - protected function onTalkingClicked(event:MouseEvent = null):void { - // do nothing - } - + public function startVideo(connection:NetConnection, stream:String):void{ ns = new NetStream(connection); ns.addEventListener( NetStatusEvent.NET_STATUS, onNetStatus ); @@ -143,7 +159,7 @@ if (videoOptions.smoothVideo) { - LogUtil.debug("Smoothing video.") + trace("Smoothing video.") _video.smoothing = true; } @@ -151,7 +167,7 @@ var filter:ConvolutionFilter = new flash.filters.ConvolutionFilter(); filter.matrixX = 3; filter.matrixY = 3; - LogUtil.debug("Applying convolution filter =[" + videoOptions.convolutionFilter + "]"); + trace("Applying convolution filter =[" + videoOptions.convolutionFilter + "]"); filter.matrix = videoOptions.convolutionFilter; filter.bias = videoOptions.filterBias; filter.divisor = videoOptions.filterDivisor; @@ -170,7 +186,7 @@ } public function onMetaData(info:Object):void{ - LogUtil.debug("metadata: width=" + info.width + " height=" + info.height); + trace("metadata: width=" + info.width + " height=" + info.height); _video.width = info.width; _video.height = info.height; setAspectRatio(info.width, info.height); @@ -215,9 +231,9 @@ </mx:Script> <mx:Glow id="talkingEffect" duration="500" alphaFrom="1.0" alphaTo="0.3" - blurXFrom="0.0" blurXTo="10.0" blurYFrom="0.0" blurYTo="10.0" color="0xFF0000"/> + blurXFrom="0.0" blurXTo="10.0" blurYFrom="0.0" blurYTo="10.0" color="0x4A931D"/> <mx:Glow id="notTalkingEffect" duration="500" alphaFrom="0.3" alphaTo="1.0" - blurXFrom="10.0" blurXTo="0.0" blurYFrom="10.0" blurYTo="0.0" color="0xFF0000"/> + blurXFrom="10.0" blurXTo="0.0" blurYFrom="10.0" blurYTo="0.0" color="0x4A931D"/> - <mate:Listener type="{CloseAllWindowsEvent.CLOSE_ALL_WINDOWS}" method="closeWindow" /> + </viewVid:VideoWindowItf> diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/videodock/views/DockOptions.as b/bigbluebutton-client/src/org/bigbluebutton/modules/videodock/views/DockOptions.as index b6064234769977c335c3c4ffb7d9d7a21d9fd71c..221975faebcf2f768d843f2accd1f1e0540126d3 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/videodock/views/DockOptions.as +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/videodock/views/DockOptions.as @@ -4,6 +4,9 @@ package org.bigbluebutton.modules.videodock.views public class DockOptions { + [Bindable] + public var showControls:Boolean = true; + [Bindable] public var autoDock:Boolean = true; @@ -32,6 +35,9 @@ package org.bigbluebutton.modules.videodock.views { var vxml:XML = BBB.getConfigForModule("VideodockModule"); if (vxml != null) { + if (vxml.@showControls != undefined) { + showControls = (vxml.@showControls.toString().toUpperCase() == "TRUE") ? true : false; + } if (vxml.@autoDock != undefined) { autoDock = (vxml.@autoDock.toString().toUpperCase() == "TRUE") ? true : false; } diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/videodock/views/VideoDock.mxml b/bigbluebutton-client/src/org/bigbluebutton/modules/videodock/views/VideoDock.mxml index 0192e03427ba204f371110f36efd92c5f741e44b..367d02eb8c4203c9d2265694dc897de81f954446 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/videodock/views/VideoDock.mxml +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/videodock/views/VideoDock.mxml @@ -22,28 +22,34 @@ $Id: $ <MDIWindow xmlns="flexlib.mdi.containers.*" xmlns:mx="http://www.adobe.com/2006/mxml" - creationComplete="init()" + creationComplete="onCreationComplete()" implements="org.bigbluebutton.common.IBbbModuleWindow" title="{ResourceUtil.getInstance().getString('bbb.videodock.title')}" - xmlns:mate="http://mate.asfusion.com/" - layout="absolute" visible="false" - horizontalAlign="center" + xmlns:mate="http://mate.asfusion.com/" styleNameFocus="videoDockStyleFocus" + layout="absolute" visible="false" styleNameNoFocus="videoDockStyleNoFocus" + horizontalAlign="center" verticalAlign="middle" resize="onChildAdd()"> <mx:Script> <![CDATA[ - import mx.events.ChildExistenceChangedEvent; - import org.bigbluebutton.common.LogUtil; - import org.bigbluebutton.common.events.CloseWindowEvent; - import org.bigbluebutton.common.events.DragWindowEvent; - import org.bigbluebutton.common.events.OpenWindowEvent; - import org.bigbluebutton.core.BBB; - import org.bigbluebutton.main.model.users.Conference; - import org.bigbluebutton.main.views.MainCanvas; - import org.bigbluebutton.modules.videoconf.business.VideoWindowItf; - import org.bigbluebutton.modules.videoconf.events.OpenVideoWindowEvent; - import org.bigbluebutton.util.i18n.ResourceUtil; + //backgroundImage="{backgroundImage}" + import com.asfusion.mate.events.Dispatcher; + + import mx.events.ChildExistenceChangedEvent; + + import org.bigbluebutton.common.Images; + import org.bigbluebutton.common.LogUtil; + import org.bigbluebutton.common.events.CloseWindowEvent; + import org.bigbluebutton.common.events.DragWindowEvent; + import org.bigbluebutton.common.events.OpenWindowEvent; + import org.bigbluebutton.core.BBB; + import org.bigbluebutton.core.events.ConnectAppEvent; + import org.bigbluebutton.main.model.users.Conference; + import org.bigbluebutton.main.views.MainCanvas; + import org.bigbluebutton.modules.videoconf.business.VideoWindowItf; + import org.bigbluebutton.modules.videoconf.events.OpenVideoWindowEvent; + import org.bigbluebutton.util.i18n.ResourceUtil; private var childrenDimension:Dictionary = new Dictionary(); private var borderColor:String; @@ -59,9 +65,15 @@ $Id: $ private var options:DockOptions = new DockOptions(); - private function init():void { + private var images:Images = new Images(); + + [Bindable] + private var backgroundImage:Class = images.video_dock_bg; + + private function onCreationComplete():void { this.showCloseButton = false; - + this.showControls = options.showControls; + this.minWidth = options.width; this.minHeight = options.height; this.maxWidth = this.parent.width; @@ -74,6 +86,10 @@ $Id: $ addEventListener(ChildExistenceChangedEvent.CHILD_REMOVE, onChildRemove); if (options.maximize) this.maximize(); + + var gDispatcher:Dispatcher = new Dispatcher(); + var evt:ConnectAppEvent = new ConnectAppEvent(ConnectAppEvent.CONNECT_VIDEO_APP); + gDispatcher.dispatchEvent(evt); } /** @@ -122,33 +138,7 @@ $Id: $ //return MainCanvas.BOTTOM_RIGHT; return options.position; } - - private function onDragVideoWindow(e:DragWindowEvent):void{ - switch (e.mode) { - case DragWindowEvent.DRAG: - if (hitTestPoint(e.mouseGlobal.x, e.mouseGlobal.y, true)) { - setStyle("borderColor","0xFF0000"); - e.window.width = e.window.minWidth; - e.window.height = e.window.minHeight; - } else { - setStyle("borderColor",borderColor); - restoreWindowDimensions(e.window); - } - break; - case DragWindowEvent.DRAG_START: - borderColor = getStyle("borderColor"); - removeVideoChild(e.window as VideoWindowItf); - saveWindowDimensions(e.window); - break; - case DragWindowEvent.DRAG_END: - setStyle("borderColor",borderColor); - restoreWindowDimensions(e.window); - if (hitTestPoint(e.mouseGlobal.x, e.mouseGlobal.y, true)) - addVideoChild(e.window as VideoWindowItf); - break; - } - } - + private function saveWindowDimensions(window:MDIWindow):void { var dimensions:Object = {width:window.width, height:window.height}; childrenDimension[window] = dimensions; @@ -171,13 +161,16 @@ $Id: $ private function onCloseWindow(e:CloseWindowEvent):void { // it should not just undock the window, it should close the window forever - if (isVideoWindow(e.window) && this.contains(e.window as VideoWindowItf)) - this.removeChild(e.window as VideoWindowItf); + if (isVideoWindow(e.window) && this.contains(e.window as VideoWindowItf)) { + this.removeChild(e.window as VideoWindowItf); + } + } private function onOpenWindow(e:OpenVideoWindowEvent):void { - if (isVideoWindow(e.window) && options.autoDock) - addVideoChild(e.window as VideoWindowItf); + if (isVideoWindow(e.window) && options.autoDock) { + addVideoChild(e.window as VideoWindowItf); + } } private function addVideoChild(window:VideoWindowItf):void { @@ -197,7 +190,7 @@ $Id: $ window.minimizeBtn.visible = false; window.maximizeRestoreBtn.visible = false; window.resizable = false; -// window.buttonsEnabled = false; + window.draggable = false; window.addEventListener(MouseEvent.CLICK, onWindowClick); @@ -208,25 +201,6 @@ $Id: $ this.addChild(window); } - private function removeVideoChild(window:VideoWindowItf):void { - if (!this.contains(window)) - return; - - window.minimizeBtn.visible = true; - window.maximizeRestoreBtn.visible = true; - window.resizable = true; -// window.buttonsEnabled = true; - - window.removeEventListener(MouseEvent.CLICK, onWindowClick); - - this.removeChild(window); - var e:OpenWindowEvent = new OpenWindowEvent(OpenWindowEvent.OPEN_WINDOW_EVENT); - e.window = window; - dispatchEvent(e); - - restoreWindowDimensions(window); - } - override public function close(event:MouseEvent = null):void { removeAllChildren(); super.close(event); @@ -398,7 +372,6 @@ $Id: $ ]]> </mx:Script> - <mate:Listener type="{DragWindowEvent.DRAG_WINDOW_EVENT}" method="onDragVideoWindow" /> <mate:Listener type="{OpenVideoWindowEvent.OPEN_VIDEO_WINDOW_EVENT}" method="onOpenWindow" /> <mate:Listener type="{CloseWindowEvent.CLOSE_WINDOW_EVENT}" method="onCloseWindow" /> </MDIWindow> \ No newline at end of file diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/viewers/events/ViewCameraEvent.as b/bigbluebutton-client/src/org/bigbluebutton/modules/viewers/events/ViewCameraEvent.as index 8cbd6f34d0e5977d600cc32e142e5f7635f63547..5ea142c1302bc2dc5b4816aef1a5482c00857b86 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/viewers/events/ViewCameraEvent.as +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/viewers/events/ViewCameraEvent.as @@ -26,12 +26,12 @@ package org.bigbluebutton.modules.viewers.events public var stream:String; public var viewedName:String; - public var userid:Number; + public var userID:String; - public function ViewCameraEvent(userid:Number, stream:String, viewedName:String) + public function ViewCameraEvent(userID:String, stream:String, viewedName:String) { super(VIEW_CAMERA_EVENT,true); - this.userid = userid; + this.userID = userID; this.stream = stream; this.viewedName = viewedName; } diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/viewers/views/StatusItemRenderer.mxml b/bigbluebutton-client/src/org/bigbluebutton/modules/viewers/views/StatusItemRenderer.mxml index 08d3cfc54e392f3a5f0ef8a72507b761d6bce239..0aba9ab7925b2cf1b8cd5553d8f566951eddb62c 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/viewers/views/StatusItemRenderer.mxml +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/viewers/views/StatusItemRenderer.mxml @@ -25,28 +25,31 @@ verticalScrollPolicy="off" horizontalScrollPolicy="off" creationComplete="onCreationComplete()" > - <mate:Listener type="ViewVideoCloseEvent" method="onViewVideoCloseEvent"/> + <mate:Listener type="{StoppedViewingWebcamEvent.STOPPED_VIEWING_WEBCAM}" method="onViewVideoCloseEvent"/> <mate:Listener type="{ViewersRollEvent.VIEWERS_ROLL_OVER}" method="onRollOver" /> <mate:Listener type="{ViewersRollEvent.VIEWERS_ROLL_OUT}" method="onRollOut" /> <mx:Script> <![CDATA[ - import com.asfusion.mate.events.Dispatcher; - import mx.controls.Alert; - import mx.events.ListEvent; - import org.bigbluebutton.common.Images; - import org.bigbluebutton.common.LogUtil; - import org.bigbluebutton.common.Role; - import org.bigbluebutton.core.managers.UserManager; - import org.bigbluebutton.main.events.BBBEvent; - import org.bigbluebutton.main.model.users.Conference; - import org.bigbluebutton.main.model.users.events.KickUserEvent; - import org.bigbluebutton.main.model.users.events.LowerHandEvent; - import org.bigbluebutton.modules.viewers.events.ViewCameraEvent; - import org.bigbluebutton.util.i18n.ResourceUtil; + import com.asfusion.mate.events.Dispatcher; + + import mx.controls.Alert; + import mx.events.ListEvent; + + import org.bigbluebutton.common.Images; + import org.bigbluebutton.common.LogUtil; + import org.bigbluebutton.common.Role; + import org.bigbluebutton.core.managers.UserManager; + import org.bigbluebutton.main.events.BBBEvent; + import org.bigbluebutton.main.events.StoppedViewingWebcamEvent; + import org.bigbluebutton.main.model.users.Conference; + import org.bigbluebutton.main.model.users.events.KickUserEvent; + import org.bigbluebutton.main.model.users.events.LowerHandEvent; + import org.bigbluebutton.modules.viewers.events.ViewCameraEvent; + import org.bigbluebutton.util.i18n.ResourceUtil; private var images:Images = new Images(); - [Bindable] public var kickIcon:Class = images.delete_icon; + [Bindable] public var kickIcon:Class = images.cancel; private var isModerator:Boolean = false; private var allowKickUser:Boolean = false; @@ -77,9 +80,9 @@ dispatchEvent(new ViewCameraEvent(data.userID, data.streamName, data.name)); } - private function onViewVideoCloseEvent(event:BBBEvent):void { - LogUtil.debug("ViewWindoClosing event: " + event.message); - if (data.streamName == event.message) { + private function onViewVideoCloseEvent(event:StoppedViewingWebcamEvent):void { + LogUtil.debug("ViewWindoClosing event: " + event.webcamUserID); + if (data.userID == event.webcamUserID) { streamIcon.enabled = true; } } diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/viewers/views/ViewersWindow.mxml b/bigbluebutton-client/src/org/bigbluebutton/modules/viewers/views/ViewersWindow.mxml index 12de02927512697071bc57d824534e58bcc3e24f..a2fd5a5a763f665dae3862d95207e18422747e2e 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/viewers/views/ViewersWindow.mxml +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/viewers/views/ViewersWindow.mxml @@ -33,24 +33,20 @@ <mx:Script> <![CDATA[ - import com.asfusion.mate.events.Dispatcher; - - import flexlib.mdi.events.MDIWindowEvent; - + import com.asfusion.mate.events.Dispatcher; + import flexlib.mdi.events.MDIWindowEvent; import mx.binding.utils.BindingUtils; import mx.collections.ArrayCollection; import mx.controls.Alert; import mx.controls.listClasses.IListItemRenderer; import mx.events.DataGridEvent; - import mx.events.ListEvent; - + import mx.events.ListEvent; import org.bigbluebutton.common.Images; import org.bigbluebutton.common.Role; import org.bigbluebutton.common.events.LocaleChangeEvent; import org.bigbluebutton.core.EventConstants; import org.bigbluebutton.core.events.CoreEvent; import org.bigbluebutton.core.managers.UserManager; - import org.bigbluebutton.main.events.ParticipantJoinEvent; import org.bigbluebutton.main.model.users.BBBUser; import org.bigbluebutton.main.model.users.Conference; import org.bigbluebutton.main.model.users.events.ConferenceCreatedEvent; @@ -184,7 +180,7 @@ <mx:ControlBar width="100%"> <mx:Button id="presentBtn" label="{ResourceUtil.getInstance().getString('bbb.viewers.presentBtn.label')}" visible="{isModerator}" - width="130" height="20" + width="130" height="20" styleName="switchPresenterButtonStyle" toolTip="{ResourceUtil.getInstance().getString('bbb.viewers.presentBtn.toolTip')}" click="assignPresenterClicked()"/> <mx:Spacer width="100%"/> diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/whiteboard/views/WhiteboardToolbar.mxml b/bigbluebutton-client/src/org/bigbluebutton/modules/whiteboard/views/WhiteboardToolbar.mxml index 8459a14a72393f7f3097eb5f041c199cc5014485..3468973e19ea24409ebd84e22fb6ccd7e3913787 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/whiteboard/views/WhiteboardToolbar.mxml +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/whiteboard/views/WhiteboardToolbar.mxml @@ -25,8 +25,7 @@ xmlns:view="org.bigbluebutton.modules.whiteboard.views.*" xmlns:wbBtns="org.bigbluebutton.modules.whiteboard.views.buttons.*" xmlns:mate="http://mate.asfusion.com/" creationComplete="onCreationComplete()" - visible="true" backgroundColor="0xCCCCCC" cornerRadius="5" borderStyle="solid" - paddingBottom="3" paddingTop="3" paddingLeft="3" paddingRight="3" > + visible="{showWhiteboardToolbar}" styleName="whiteboardToolbarStyle"> <mate:Listener type="{MadePresenterEvent.SWITCH_TO_PRESENTER_MODE}" method="presenterMode" /> <mate:Listener type="{MadePresenterEvent.SWITCH_TO_VIEWER_MODE}" method="viewerMode" /> @@ -57,7 +56,7 @@ <mx:Script> <![CDATA[ - import flash.ui.Keyboard; + import flash.ui.Keyboard; import mx.events.MoveEvent; import mx.events.ResizeEvent; import mx.managers.CursorManager; @@ -82,7 +81,7 @@ import org.bigbluebutton.common.LogUtil; private var images:Images = new Images(); - [Bindable] private var hand_icon:Class = images.hand_icon; + [Bindable] private var hand_icon:Class = images.hand_icon; [Bindable] private var pencil_icon:Class = images.pencil_icon; [Bindable] private var undo_icon:Class = images.undo_icon; [Bindable] private var delete_icon:Class = images.delete_icon; @@ -90,8 +89,8 @@ [Bindable] private var ellipse_icon:Class = images.circle_icon; [Bindable] private var thick_icon:Class = images.whiteboard_thick; [Bindable] private var thin_icon:Class = images.whiteboard_thin; - [Bindable] private var scribble_icon:Class = images.scribble_icon; - [Bindable] private var text_icon:Class = images.text_icon; + [Bindable] private var scribble_icon:Class = images.scribble_icon; + [Bindable] private var text_icon:Class = images.text_icon; [Bindable] private var triangle_icon:Class = images.triangle_icon; [Bindable] private var line_icon:Class = images.line_icon; [Bindable] private var fill_icon:Class = images.fill_icon; @@ -101,84 +100,71 @@ [Bindable] private var select_icon:Class = images.select_icon; [Bindable] private var grid_icon:Class = images.grid_icon; + [Bindable] private var showWhiteboardToolbar:Boolean = true; + public var canvas:WhiteboardCanvas; private var presentationWindow:PresentationWindow; [Bindable] private var colorPickerColours:Array = ['0x000000', '0xFFFFFF' , '0xFF0000', '0xFF8800', '0xCCFF00', '0x00FF00', '0x00FF88', '0x00FFFF', '0x0088FF', '0x0000FF', '0x8800FF', '0xFF00FF', '0xC0C0C0']; - private function onCreationComplete():void { - setToolType(WhiteboardConstants.TYPE_ZOOM, null); - } + private function onCreationComplete():void { + setToolType(WhiteboardConstants.TYPE_ZOOM, null); + } private function handleWhiteboardButtonPressed(e:WhiteboardButtonEvent):void { - setToolType(e.graphicType, e.toolType); - } + setToolType(e.graphicType, e.toolType); + } - private function setToolType(graphicType:String, toolType:String):void { - canvas.setGraphicType(graphicType); - canvas.setTool(toolType); + private function setToolType(graphicType:String, toolType:String):void { + canvas.setGraphicType(graphicType); + canvas.setTool(toolType); - if (panzoomBtn != null) { - panzoomBtn.setTool(graphicType, toolType); - } + if (panzoomBtn != null) { + panzoomBtn.setTool(graphicType, toolType); + } - if (scribbleBtn != null) { - scribbleBtn.setTool(graphicType, toolType); - } + if (scribbleBtn != null) { + scribbleBtn.setTool(graphicType, toolType); + } - if (rectangleBtn != null) { - rectangleBtn.setTool(graphicType, toolType); - } + if (rectangleBtn != null) { + rectangleBtn.setTool(graphicType, toolType); + } - if (circleBtn != null) { - circleBtn.setTool(graphicType, toolType); - } + if (circleBtn != null) { + circleBtn.setTool(graphicType, toolType); + } - if (triangleBtn != null) { - triangleBtn.setTool(graphicType, toolType); - } + if (triangleBtn != null) { + triangleBtn.setTool(graphicType, toolType); + } - if (lineBtn != null) { - lineBtn.setTool(graphicType, toolType); - } + if (lineBtn != null) { + lineBtn.setTool(graphicType, toolType); + } - if (textBtn != null) { - textBtn.setTool(graphicType, toolType); - } + if (textBtn != null) { + textBtn.setTool(graphicType, toolType); + } - if(graphicType == WhiteboardConstants.TYPE_CLEAR) { - dispatchEvent(new WhiteboardDrawEvent(WhiteboardDrawEvent.CLEAR)); - } + if(graphicType == WhiteboardConstants.TYPE_CLEAR) { + dispatchEvent(new WhiteboardDrawEvent(WhiteboardDrawEvent.CLEAR)); + } - if (graphicType == WhiteboardConstants.TYPE_UNDO) { - sendUndoCommand(); - } + if (graphicType == WhiteboardConstants.TYPE_UNDO) { + sendUndoCommand(); + } } - /** - * Sets the color - * @param e event containing the color (e.target.selectedColor) - * - */ protected function changeColor(e:Event):void { canvas.changeColor(e); } - /** - * Sets the fill color - * @param e event containing the color (e.target.selectedColor) - * - */ protected function changeFillColor(e:Event):void { canvas.changeFillColor(e); } - /** - * Sets the thickness - * @param e event containing the thickness (e.target.value) - * - */ protected function changeThickness(e:Event):void { canvas.changeThickness(e); } @@ -192,61 +178,69 @@ } private function presenterMode(e:MadePresenterEvent):void { - if (canvas == null) return; + if (canvas == null) return; + canvas.makeTextObjectsEditable(e); - this.visible = true; - setToolType(WhiteboardConstants.TYPE_ZOOM, null); + hideOrDisplayToolbar(); + setToolType(WhiteboardConstants.TYPE_ZOOM, null); } private function viewerMode(e:MadePresenterEvent):void { - canvas.makeTextObjectsUneditable(e); - this.visible = false; - } + canvas.makeTextObjectsUneditable(e); + hideOrDisplayToolbar(); + } private function undoShortcut(e:ShortcutEvent):void{ LogUtil.debug("Ctrl-Z got into undoShortcut"); sendUndoCommand(); } - private function showToolbar(e:PresentationEvent):void { - this.visible = true; - } - private function sendUndoCommand():void { - if (!canvas.isPageEmpty()) + if (!canvas.isPageEmpty()) { dispatchEvent(new WhiteboardDrawEvent(WhiteboardDrawEvent.UNDO)); + } } public function positionToolbar(window:PresentationWindow):void { + trace("Positioning whiteboard toolbar"); presentationWindow = window; presentationWindow.addEventListener(MoveEvent.MOVE, setPositionAndDepth); presentationWindow.addEventListener(ResizeEvent.RESIZE, setPositionAndDepth); presentationWindow.addEventListener(MouseEvent.CLICK, setPositionAndDepth); this.x = presentationWindow.x + presentationWindow.width + 3; this.y = presentationWindow.y + 30; + hideOrDisplayToolbar(); parent.setChildIndex(this, parent.numChildren - 1); } private function setPositionAndDepth(e:Event = null):void { this.x = presentationWindow.x + presentationWindow.width + 3; this.y = presentationWindow.y + 30; + hideOrDisplayToolbar(); parent.setChildIndex(this, parent.numChildren - 1); } private function closeToolbar(e:StopWhiteboardModuleEvent):void { parent.removeChild(this); } - + + private function hideOrDisplayToolbar():void { + if (presentationWindow != null && presentationWindow.visible && ! presentationWindow.minimized) { + trace("Positioning whiteboard toolbar: showing"); + showWhiteboardToolbar = true; + } else { + trace("Positioning whiteboard toolbar: not showing"); + showWhiteboardToolbar = false; + } + } - private function graphicObjSelected(event:GraphicObjectFocusEvent):void { - var gobj:GraphicObject = event.data; -// LogUtil.debug("!!!!SELECTED:" ); - } + private function graphicObjSelected(event:GraphicObjectFocusEvent):void { + var gobj:GraphicObject = event.data; + } - private function graphicObjDeselected(event:GraphicObjectFocusEvent):void { - var gobj:GraphicObject = event.data; - // LogUtil.debug("!!!!DESELECTED:" + " " ); - } + private function graphicObjDeselected(event:GraphicObjectFocusEvent):void { + var gobj:GraphicObject = event.data; + } ]]> </mx:Script> @@ -255,20 +249,20 @@ that identifies the "category" of the tool (ex. shape vs text), and the other specifies the tool itself (ex. line tool vs triangle tool, even though both are "shapes") --> - <wbBtns:PanZoomButton id="panzoomBtn"/> - <mx:Spacer height="10"/> - <wbBtns:ScribbleButton id="scribbleBtn"/> - <wbBtns:RectangleButton id="rectangleBtn"/> - <wbBtns:CircleButton id="circleBtn"/> - <wbBtns:TriangleButton id="triangleBtn"/> - <wbBtns:LineButton id="lineBtn"/> - <wbBtns:TextButton id="textBtn"/> + <wbBtns:PanZoomButton id="panzoomBtn" visible="{showWhiteboardToolbar}"/> + <mx:Spacer height="10" visible="{showWhiteboardToolbar}"/> + <wbBtns:ScribbleButton id="scribbleBtn" visible="{showWhiteboardToolbar}"/> + <wbBtns:RectangleButton id="rectangleBtn" visible="{showWhiteboardToolbar}"/> + <wbBtns:CircleButton id="circleBtn" visible="{showWhiteboardToolbar}"/> + <wbBtns:TriangleButton id="triangleBtn" visible="{showWhiteboardToolbar}"/> + <wbBtns:LineButton id="lineBtn" visible="{showWhiteboardToolbar}"/> + <wbBtns:TextButton id="textBtn" visible="{showWhiteboardToolbar}"/> - <mx:Spacer height="5"/> - <wbBtns:ClearButton id="clearBtn"/> - <wbBtns:UndoButton id="undoBtn"/> + <mx:Spacer height="5" visible="{showWhiteboardToolbar}"/> + <wbBtns:ClearButton id="clearBtn" visible="{showWhiteboardToolbar}"/> + <wbBtns:UndoButton id="undoBtn" visible="{showWhiteboardToolbar}" /> - <mx:Spacer height="5"/> + <mx:Spacer height="5" visible="{showWhiteboardToolbar}"/> <!-- Properties that were removed from original color picker: @@ -281,14 +275,16 @@ of ColorPickers, one for the "line" color for the outlines of shapes, and the other for the "fill" color that is used only if "fill" is enabled in WhiteboardCanvasModel --> - <mx:ColorPicker change="changeColor(event)" id="cpik" selectedColor="0x000000" toolTip="{ResourceUtil.getInstance().getString('bbb.highlighter.toolbar.color')}"/> + <mx:ColorPicker change="changeColor(event)" id="cpik" visible="{showWhiteboardToolbar}" + selectedColor="0x000000" width="30" + toolTip="{ResourceUtil.getInstance().getString('bbb.highlighter.toolbar.color')}"/> - <mx:Spacer height="3" /> - <mx:Image source="{thick_icon}" horizontalAlign="center" width="20"/> - <mx:VSlider height="50" id="sld" change="changeThickness(event)" + <mx:Spacer height="3" visible="{showWhiteboardToolbar}"/> + <mx:Image source="{thick_icon}" horizontalAlign="center" width="30" visible="{showWhiteboardToolbar}"/> + <mx:VSlider height="50" id="sld" change="changeThickness(event)" visible="{showWhiteboardToolbar}" toolTip="{ResourceUtil.getInstance().getString('bbb.highlighter.toolbar.thickness')}" - minimum="2" maximum="20" + minimum="2" maximum="30" useHandCursor="true" value="2" showDataTip="true" snapInterval="1" dataTipOffset="0" labelOffset="0" /> - <mx:Image source="{thin_icon}" horizontalAlign="center" width="20" /> + <mx:Image source="{thin_icon}" horizontalAlign="center" width="30" visible="{showWhiteboardToolbar}"/> </mx:VBox> diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/whiteboard/views/buttons/CircleButton.mxml b/bigbluebutton-client/src/org/bigbluebutton/modules/whiteboard/views/buttons/CircleButton.mxml index 04c46c32a483ed9f20387c3d91fb9af044ee3aac..a887c70ac79440138d5301f611b4e03cc7db8e14 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/whiteboard/views/buttons/CircleButton.mxml +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/whiteboard/views/buttons/CircleButton.mxml @@ -1,8 +1,7 @@ <?xml version="1.0" encoding="utf-8"?> <mx:Button xmlns:mx="http://www.adobe.com/2006/mxml" - width="20" height="20" - click="onClick()" - icon="{ellipse_icon}" + width="30" height="30" + click="onClick()" styleName="whiteboardCircleButtonStyle" toolTip="{ResourceUtil.getInstance().getString('bbb.highlighter.toolbar.ellipse')}" toggle="true"> <mx:Script> @@ -12,10 +11,7 @@ import org.bigbluebutton.modules.whiteboard.business.shapes.WhiteboardConstants; import org.bigbluebutton.modules.whiteboard.events.WhiteboardButtonEvent; import org.bigbluebutton.util.i18n.ResourceUtil; - - private var images:Images = new Images(); - [Bindable] private var ellipse_icon:Class = images.circle_icon; - + private function onClick():void { var event:WhiteboardButtonEvent = new WhiteboardButtonEvent(WhiteboardButtonEvent.WHITEBOARD_BUTTON_PRESSED); event.graphicType = WhiteboardConstants.TYPE_SHAPE; diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/whiteboard/views/buttons/ClearButton.mxml b/bigbluebutton-client/src/org/bigbluebutton/modules/whiteboard/views/buttons/ClearButton.mxml index dd666c2301d81c58b948e170250f806600eb9849..221cfa39a80a8c62c522b24fc0be46990ab134d1 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/whiteboard/views/buttons/ClearButton.mxml +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/whiteboard/views/buttons/ClearButton.mxml @@ -1,17 +1,14 @@ <?xml version="1.0" encoding="utf-8"?> <mx:Button xmlns:mx="http://www.adobe.com/2006/mxml" - width="20" height="20" click="onClick()" icon="{delete_icon}" + width="30" height="30" + click="onClick()" styleName="whiteboardClearButtonStyle" toolTip="{ResourceUtil.getInstance().getString('bbb.highlighter.toolbar.clear')}"> <mx:Script> <![CDATA[ - import org.bigbluebutton.common.Images; import org.bigbluebutton.modules.whiteboard.business.shapes.WhiteboardConstants; import org.bigbluebutton.modules.whiteboard.events.WhiteboardButtonEvent; import org.bigbluebutton.util.i18n.ResourceUtil; - - private var images:Images = new Images(); - [Bindable] private var delete_icon:Class = images.delete_icon; - + private function onClick():void { var event:WhiteboardButtonEvent = new WhiteboardButtonEvent(WhiteboardButtonEvent.WHITEBOARD_BUTTON_PRESSED); event.graphicType = WhiteboardConstants.TYPE_CLEAR; diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/whiteboard/views/buttons/LineButton.mxml b/bigbluebutton-client/src/org/bigbluebutton/modules/whiteboard/views/buttons/LineButton.mxml index f6334b33bb728f04291d24fc6764dce048af7b2a..d22ef38a9341cbbb294943ac9496845c79ef4759 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/whiteboard/views/buttons/LineButton.mxml +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/whiteboard/views/buttons/LineButton.mxml @@ -1,18 +1,15 @@ <?xml version="1.0" encoding="utf-8"?> <mx:Button xmlns:mx="http://www.adobe.com/2006/mxml" - width="20" height="20" click="onClick()" icon="{line_icon}" + width="30" height="30" + click="onClick()" styleName="whiteboardLineButtonStyle" toolTip="{ResourceUtil.getInstance().getString('ltbcustom.bbb.highlighter.toolbar.line')}" toggle="true"> <mx:Script> <![CDATA[ - import org.bigbluebutton.common.Images; import org.bigbluebutton.modules.whiteboard.business.shapes.DrawObject; import org.bigbluebutton.modules.whiteboard.business.shapes.WhiteboardConstants; import org.bigbluebutton.modules.whiteboard.events.WhiteboardButtonEvent; import org.bigbluebutton.util.i18n.ResourceUtil; - - private var images:Images = new Images(); - [Bindable] private var line_icon:Class = images.line_icon; - + private function onClick():void { var event:WhiteboardButtonEvent = new WhiteboardButtonEvent(WhiteboardButtonEvent.WHITEBOARD_BUTTON_PRESSED); event.graphicType = WhiteboardConstants.TYPE_SHAPE; diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/whiteboard/views/buttons/PanZoomButton.mxml b/bigbluebutton-client/src/org/bigbluebutton/modules/whiteboard/views/buttons/PanZoomButton.mxml index af859c73082c15f9573e91e829c85822d7cff0d1..bca208eef6abd993428d3ca79a1bc4e1c28428bb 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/whiteboard/views/buttons/PanZoomButton.mxml +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/whiteboard/views/buttons/PanZoomButton.mxml @@ -1,18 +1,14 @@ <?xml version="1.0" encoding="utf-8"?> <mx:Button xmlns:mx="http://www.adobe.com/2006/mxml" - width="20" height="20" click="onClick()" icon="{hand_icon}" + width="30" height="30" + click="onClick()" styleName="whiteboardPanZoomButtonStyle" toolTip="{ResourceUtil.getInstance().getString('bbb.highlighter.toolbar.panzoom')}" toggle="true"> <mx:Script> <![CDATA[ - import org.bigbluebutton.common.Images; import org.bigbluebutton.modules.whiteboard.business.shapes.WhiteboardConstants; import org.bigbluebutton.modules.whiteboard.events.WhiteboardButtonEvent; import org.bigbluebutton.util.i18n.ResourceUtil; - - private var images:Images = new Images(); - - [Bindable] private var hand_icon:Class = images.hand_icon; - + private function onClick():void { var event:WhiteboardButtonEvent = new WhiteboardButtonEvent(WhiteboardButtonEvent.WHITEBOARD_BUTTON_PRESSED); event.graphicType = WhiteboardConstants.TYPE_ZOOM; diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/whiteboard/views/buttons/RectangleButton.mxml b/bigbluebutton-client/src/org/bigbluebutton/modules/whiteboard/views/buttons/RectangleButton.mxml index 8d17ffa9dab688dd50d32e8508c95e404f8ea1bb..65dacddc947c7e2ea07e1fd0d1d64c2334852b31 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/whiteboard/views/buttons/RectangleButton.mxml +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/whiteboard/views/buttons/RectangleButton.mxml @@ -1,19 +1,15 @@ <?xml version="1.0" encoding="utf-8"?> <mx:Button xmlns:mx="http://www.adobe.com/2006/mxml" - width="20" height="20" - click="onClick()" - icon="{rectangle_icon}" toolTip="{ResourceUtil.getInstance().getString('bbb.highlighter.toolbar.rectangle')}" toggle="true"> + width="30" height="30" + click="onClick()" styleName="whiteboardRectangleButtonStyle" + toolTip="{ResourceUtil.getInstance().getString('bbb.highlighter.toolbar.rectangle')}" toggle="true"> <mx:Script> <![CDATA[ - import org.bigbluebutton.common.Images; import org.bigbluebutton.modules.whiteboard.business.shapes.DrawObject; import org.bigbluebutton.modules.whiteboard.business.shapes.WhiteboardConstants; import org.bigbluebutton.modules.whiteboard.events.WhiteboardButtonEvent; import org.bigbluebutton.util.i18n.ResourceUtil; - - private var images:Images = new Images(); - [Bindable] private var rectangle_icon:Class = images.square_icon; - + private function onClick():void { var event:WhiteboardButtonEvent = new WhiteboardButtonEvent(WhiteboardButtonEvent.WHITEBOARD_BUTTON_PRESSED); event.graphicType = WhiteboardConstants.TYPE_SHAPE; diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/whiteboard/views/buttons/ScribbleButton.mxml b/bigbluebutton-client/src/org/bigbluebutton/modules/whiteboard/views/buttons/ScribbleButton.mxml index 97f7fd2d32f47f902c18e2b93752fa553b982666..6a6e6571fc2f0b419db4c810fdfe3aeaac2ab68b 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/whiteboard/views/buttons/ScribbleButton.mxml +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/whiteboard/views/buttons/ScribbleButton.mxml @@ -1,7 +1,8 @@ <?xml version="1.0" encoding="utf-8"?> <mx:Button xmlns:mx="http://www.adobe.com/2006/mxml" - width="20" height="20" click="onClick()" - icon="{scribble_icon}" toolTip="{ResourceUtil.getInstance().getString('bbb.highlighter.toolbar.pencil')}" + width="30" height="30" + click="onClick()" styleName="whiteboardScribbleButtonStyle" + toolTip="{ResourceUtil.getInstance().getString('bbb.highlighter.toolbar.pencil')}" toggle="true" selected="true"> <mx:Script> <![CDATA[ @@ -10,11 +11,7 @@ import org.bigbluebutton.modules.whiteboard.business.shapes.WhiteboardConstants; import org.bigbluebutton.modules.whiteboard.events.WhiteboardButtonEvent; import org.bigbluebutton.util.i18n.ResourceUtil; - - private var images:Images = new Images(); - - [Bindable] private var scribble_icon:Class = images.scribble_icon; - + private function onClick():void { var event:WhiteboardButtonEvent = new WhiteboardButtonEvent(WhiteboardButtonEvent.WHITEBOARD_BUTTON_PRESSED); event.graphicType = WhiteboardConstants.TYPE_SHAPE; diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/whiteboard/views/buttons/TextButton.mxml b/bigbluebutton-client/src/org/bigbluebutton/modules/whiteboard/views/buttons/TextButton.mxml index c0f7e59f8efbe0d48e52ef0d38632075c7626291..10d26e2234b42319c03d7a04b2f7b61315b3c8d3 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/whiteboard/views/buttons/TextButton.mxml +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/whiteboard/views/buttons/TextButton.mxml @@ -1,6 +1,7 @@ <?xml version="1.0" encoding="utf-8"?> <mx:Button xmlns:mx="http://www.adobe.com/2006/mxml" - width="20" height="20" click="onClick()" icon="{text_icon}" + width="30" height="30" + click="onClick()" styleName="whiteboardTextButtonStyle" toolTip="{ResourceUtil.getInstance().getString('ltbcustom.bbb.highlighter.toolbar.text')}" toggle="true"> <mx:Script> <![CDATA[ @@ -8,10 +9,7 @@ import org.bigbluebutton.modules.whiteboard.business.shapes.WhiteboardConstants; import org.bigbluebutton.modules.whiteboard.events.WhiteboardButtonEvent; import org.bigbluebutton.util.i18n.ResourceUtil; - - private var images:Images = new Images(); - [Bindable] private var text_icon:Class = images.text_icon; - + private function onClick():void { var event:WhiteboardButtonEvent = new WhiteboardButtonEvent(WhiteboardButtonEvent.WHITEBOARD_BUTTON_PRESSED); event.graphicType = WhiteboardConstants.TYPE_TEXT; diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/whiteboard/views/buttons/TriangleButton.mxml b/bigbluebutton-client/src/org/bigbluebutton/modules/whiteboard/views/buttons/TriangleButton.mxml index 0a603f1ac46bd334f11356d8ef3d508659543839..25623b78bb88a608bb634cd2ea293d92a2a53772 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/whiteboard/views/buttons/TriangleButton.mxml +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/whiteboard/views/buttons/TriangleButton.mxml @@ -1,6 +1,7 @@ <?xml version="1.0" encoding="utf-8"?> <mx:Button xmlns:mx="http://www.adobe.com/2006/mxml" - width="20" height="20" click="onClick()" icon="{triangle_icon}" + width="30" height="30" + click="onClick()" styleName="whiteboardTriangleButtonStyle" toolTip="{ResourceUtil.getInstance().getString('ltbcustom.bbb.highlighter.toolbar.triangle')}" toggle="true"> <mx:Script> @@ -10,10 +11,7 @@ import org.bigbluebutton.modules.whiteboard.business.shapes.WhiteboardConstants; import org.bigbluebutton.modules.whiteboard.events.WhiteboardButtonEvent; import org.bigbluebutton.util.i18n.ResourceUtil; - - private var images:Images = new Images(); - [Bindable] private var triangle_icon:Class = images.triangle_icon; - + private function onClick():void { var event:WhiteboardButtonEvent = new WhiteboardButtonEvent(WhiteboardButtonEvent.WHITEBOARD_BUTTON_PRESSED); event.graphicType = WhiteboardConstants.TYPE_SHAPE; diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/whiteboard/views/buttons/UndoButton.mxml b/bigbluebutton-client/src/org/bigbluebutton/modules/whiteboard/views/buttons/UndoButton.mxml index 347324984559ab5be9bd66d747bdd7b85ec1311e..6affcb9711819dae0857f84f7388171ad42d91e1 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/whiteboard/views/buttons/UndoButton.mxml +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/whiteboard/views/buttons/UndoButton.mxml @@ -1,17 +1,15 @@ <?xml version="1.0" encoding="utf-8"?> <mx:Button xmlns:mx="http://www.adobe.com/2006/mxml" - width="20" height="20" click="onClick()" icon="{undo_icon}" + width="30" height="30" + click="onClick()" styleName="whiteboardUndoButtonStyle" toolTip="{ResourceUtil.getInstance().getString('bbb.highlighter.toolbar.undo')}"> <mx:Script> <![CDATA[ - import org.bigbluebutton.common.Images; + import org.bigbluebutton.modules.whiteboard.business.shapes.WhiteboardConstants; import org.bigbluebutton.modules.whiteboard.events.WhiteboardButtonEvent; import org.bigbluebutton.util.i18n.ResourceUtil; - - private var images:Images = new Images(); - [Bindable] private var undo_icon:Class = images.undo_icon; - + private function onClick():void { var event:WhiteboardButtonEvent = new WhiteboardButtonEvent(WhiteboardButtonEvent.WHITEBOARD_BUTTON_PRESSED); event.graphicType = WhiteboardConstants.TYPE_UNDO; diff --git a/bigbluebutton-client/src/org/red5/flash/bwcheck/BandwidthDetection.as b/bigbluebutton-client/src/org/red5/flash/bwcheck/BandwidthDetection.as new file mode 100644 index 0000000000000000000000000000000000000000..7d1160ab9bd1bb47ec3e526c5582ac4203dc24fd --- /dev/null +++ b/bigbluebutton-client/src/org/red5/flash/bwcheck/BandwidthDetection.as @@ -0,0 +1,53 @@ +package org.red5.flash.bwcheck +{ + import flash.events.EventDispatcher; + import flash.net.NetConnection; + import org.red5.flash.bwcheck.events.BandwidthDetectEvent; + + [Event(name=BandwidthDetectEvent.DETECT_STATUS, type="org.red5.flash.bwcheck.events.BandwidthDetectEvent")] + [Event(name=BandwidthDetectEvent.DETECT_COMPLETE, type="org.red5.flash.bwcheck.events.BandwidthDetectEvent")] + + public class BandwidthDetection extends EventDispatcher + { + protected var nc:NetConnection; + + public function BandwidthDetection() + { + + } + + protected function dispatch(info:Object, eventName:String):void + { + var event:BandwidthDetectEvent = new BandwidthDetectEvent(eventName); + event.info = info; + dispatchEvent(event); + } + + protected function dispatchStatus(info:Object):void + { + var event:BandwidthDetectEvent = new BandwidthDetectEvent(BandwidthDetectEvent.DETECT_STATUS); + event.info = info; + dispatchEvent(event); + } + + protected function dispatchComplete(info:Object):void + { + var event:BandwidthDetectEvent = new BandwidthDetectEvent(BandwidthDetectEvent.DETECT_COMPLETE); + event.info = info; + dispatchEvent(event); + } + + protected function dispatchFailed(info:Object):void + { + var event:BandwidthDetectEvent = new BandwidthDetectEvent(BandwidthDetectEvent.DETECT_FAILED); + event.info = info; + dispatchEvent(event); + } + + public function set connection(connect:NetConnection):void + { + nc = connect; + } + + } +} \ No newline at end of file diff --git a/bigbluebutton-client/src/org/red5/flash/bwcheck/ClientServerBandwidth.as b/bigbluebutton-client/src/org/red5/flash/bwcheck/ClientServerBandwidth.as new file mode 100644 index 0000000000000000000000000000000000000000..22a97d38461ec4467f5740123b227bb07d59aad7 --- /dev/null +++ b/bigbluebutton-client/src/org/red5/flash/bwcheck/ClientServerBandwidth.as @@ -0,0 +1,143 @@ +package org.red5.flash.bwcheck +{ + + import flash.net.Responder; + + public class ClientServerBandwidth extends BandwidthDetection + { + + private var res:Responder; + private var payload:Array = new Array(); + + private var latency:int = 0; + private var cumLatency:int = 1; + private var bwTime:int = 0; + private var count:int = 0; + private var sent:int = 0; + private var kbitUp:int = 0; + private var KBytes:int = 0; + private var deltaUp:int = 0; + private var deltaTime:int = 0; + private var overhead:int = 0; + private var pakInterval:int = 0; + private var timePassed:int = 0; + private var now:int = 0; + + private var pakSent:Array = new Array(); + private var pakRecv:Array = new Array(); + private var beginningValues:Object = {}; + private var info:Object = new Object(); + + private var _service:String; + + public function ClientServerBandwidth() + { + for (var i:int = 0; i < 1200; i++){ + payload[i] = Math.random(); //16K approx + } + + res = new Responder(onResult, onStatus); + + + } + + public function set service(service:String):void + { + _service = service; + } + + public function start():void + { + nc.client = this; + var obj:Array = new Array(); + nc.call(_service, res); + } + + private function onResult(obj:Object):void + { + this.now = (new Date()).getTime()/1; + if(sent == 0) { + this.beginningValues = obj; + this.beginningValues.time = now; + this.pakSent[sent++] = now; + nc.call(_service, res, now); + } else { + this.pakRecv[this.count] = now; + this.pakInterval = (this.pakRecv[this.count] - this.pakSent[this.count])*1; + this.count++; + this.timePassed = (now - this.beginningValues.time); + + if (this.count == 1) { + this.latency = Math.min(timePassed, 800); + this.latency = Math.max(this.latency, 10); + this.overhead = obj.cOutBytes - this.beginningValues.cOutBytes; + + this.pakSent[sent++] = now; + nc.call(_service, res, now, payload); + dispatchStatus(info); + + } + // If we have a hi-speed network with low latency send more to determine + // better bandwidth numbers, send no more than 6 packets + if ( (this.count >= 1) && (timePassed<1000)) + { + this.pakSent[sent++] = now; + this.cumLatency++; + nc.call(_service, res, now, payload); + dispatchStatus(info); + } else if ( this.sent == this.count ) { + // See if we need to normalize latency + if ( this.latency >= 100 ) + { // make sure we detect sattelite and modem correctly + if ( this.pakRecv[1] - this.pakRecv[0] > 1000 ) + { + this.latency = 100; + } + } + payload = new Array(); + // Got back responses for all the packets compute the bandwidth. + var stats:Object = obj; + deltaUp = (stats.cOutBytes - this.beginningValues.cOutBytes)*8/1000; + deltaTime = ((now - this.beginningValues.time) - (this.latency * this.cumLatency) )/1000; + if ( deltaTime <= 0 ) + deltaTime = (now - this.beginningValues.time)/1000; + + kbitUp = Math.round(deltaUp/deltaTime); + KBytes = (stats.cOutBytes - this.beginningValues.cOutBytes)/1024; + + var info:Object = new Object(); + info.kbitUp = kbitUp; + info.deltaUp = deltaUp; + info.deltaTime = deltaTime; + info.latency = latency; + info.KBytes = KBytes; + + dispatchComplete(info); + } + } + } + + override protected function dispatchStatus(obj:Object):void + { + obj.count = this.count; + obj.sent = this.sent; + obj.timePassed = timePassed; + obj.latency = this.latency; + obj.overhead = this.overhead; + obj.pakInterval = this.pakInterval; + obj.cumLatency = this.cumLatency; + + super.dispatchStatus(info); + } + + private function onStatus(obj:Object):void + { + switch (obj.code) + { + case "NetConnection.Call.Failed": + dispatchFailed(obj); + break; + } + } + } +} diff --git a/bigbluebutton-client/src/org/red5/flash/bwcheck/ServerClientBandwidth.as b/bigbluebutton-client/src/org/red5/flash/bwcheck/ServerClientBandwidth.as new file mode 100644 index 0000000000000000000000000000000000000000..84b892589b001befab7f42b9ae3ec7a3fb18520d --- /dev/null +++ b/bigbluebutton-client/src/org/red5/flash/bwcheck/ServerClientBandwidth.as @@ -0,0 +1,55 @@ +package org.red5.flash.bwcheck +{ + import flash.net.Responder; + + public class ServerClientBandwidth extends BandwidthDetection + { + + private var _service:String; + private var info:Object = new Object(); + private var res:Responder; + + public function ServerClientBandwidth() + { + res = new Responder(onResult, onStatus); + } + + public function onBWCheck(obj:Object):void + { + dispatchStatus(obj); + } + + public function onBWDone(obj:Object):void + { + dispatchComplete(obj); + } + + public function set service(service:String):void + { + _service = service; + } + + public function start():void + { + nc.client = this; + nc.call(_service,res); + } + + private function onResult(obj:Object):void + { + dispatchStatus(obj); + + } + + private function onStatus(obj:Object):void + { + switch (obj.code) + { + case "NetConnection.Call.Failed": + dispatchFailed(obj); + break; + } + + } + } +} diff --git a/bigbluebutton-client/src/org/red5/flash/bwcheck/app/BandwidthDetectionApp.as b/bigbluebutton-client/src/org/red5/flash/bwcheck/app/BandwidthDetectionApp.as new file mode 100644 index 0000000000000000000000000000000000000000..77b3e2f4b7c9b7ee847af6e415f879148abeb852 --- /dev/null +++ b/bigbluebutton-client/src/org/red5/flash/bwcheck/app/BandwidthDetectionApp.as @@ -0,0 +1,131 @@ +package org.red5.flash.bwcheck.app +{ + import flash.events.NetStatusEvent; + import flash.net.NetConnection; + import flash.net.Responder; + + import mx.core.Application; + + import org.bigbluebutton.common.LogUtil; + + import org.red5.flash.bwcheck.ClientServerBandwidth; + import org.red5.flash.bwcheck.ServerClientBandwidth; + import org.red5.flash.bwcheck.events.BandwidthDetectEvent; + + public class BandwidthDetectionApp extends Application + { + private var _serverURL:String = "localhost"; + private var _serverApplication:String = ""; + private var _clientServerService:String = ""; + private var _serverClientService:String = ""; + + private var nc:NetConnection; + + public function BandwidthDetectionApp() + { + + } + + public function set serverURL(url:String):void + { + _serverURL = url; + } + + public function set serverApplication(app:String):void + { + _serverApplication = app; + } + + public function set clientServerService(service:String):void + { + _clientServerService = service; + } + + public function set serverClientService(service:String):void + { + _serverClientService = service; + } + + public function connect():void + { + nc = new NetConnection(); + nc.objectEncoding = flash.net.ObjectEncoding.AMF0; + nc.client = this; + nc.addEventListener(NetStatusEvent.NET_STATUS, onStatus); + nc.connect("rtmp://" + _serverURL + "/" + _serverApplication); + } + + + private function onStatus(event:NetStatusEvent):void + { + switch (event.info.code) + { + case "NetConnection.Connect.Success": + LogUtil.debug(event.info.code); + LogUtil.debug("Detecting Server Client Bandwidth"); + ServerClient(); + break; + } + + } + + public function ClientServer():void + { + var clientServer:ClientServerBandwidth = new ClientServerBandwidth(); + //connect(); + clientServer.connection = nc; + clientServer.service = _clientServerService; + clientServer.addEventListener(BandwidthDetectEvent.DETECT_COMPLETE,onClientServerComplete); + clientServer.addEventListener(BandwidthDetectEvent.DETECT_STATUS,onClientServerStatus); + clientServer.addEventListener(BandwidthDetectEvent.DETECT_FAILED,onDetectFailed); + clientServer.start(); + } + + public function ServerClient():void + { + var serverClient:ServerClientBandwidth = new ServerClientBandwidth(); + //connect(); + serverClient.connection = nc; + serverClient.service = _serverClientService; + serverClient.addEventListener(BandwidthDetectEvent.DETECT_COMPLETE,onServerClientComplete); + serverClient.addEventListener(BandwidthDetectEvent.DETECT_STATUS,onServerClientStatus); + serverClient.addEventListener(BandwidthDetectEvent.DETECT_FAILED,onDetectFailed); + + serverClient.start(); + } + + public function onDetectFailed(event:BandwidthDetectEvent):void + { + LogUtil.debug("Detection failed with error: " + event.info.application + " " + event.info.description); + } + + public function onClientServerComplete(event:BandwidthDetectEvent):void + { + LogUtil.debug("kbitUp = " + event.info.kbitUp + ", deltaUp= " + event.info.deltaUp + ", deltaTime = " + event.info.deltaTime + ", latency = " + event.info.latency + " KBytes " + event.info.KBytes); + LogUtil.debug("Client to Server Bandwidth Detection Complete"); + } + + public function onClientServerStatus(event:BandwidthDetectEvent):void + { + if (event.info) { + LogUtil.debug("count: "+event.info.count+ " sent: "+event.info.sent+" timePassed: "+event.info.timePassed+" latency: "+event.info.latency+" overhead: "+event.info.overhead+" packet interval: " + event.info.pakInterval + " cumLatency: " + event.info.cumLatency); + } + } + + public function onServerClientComplete(event:BandwidthDetectEvent):void + { + LogUtil.debug("kbit Down: " + event.info.kbitDown + " Delta Down: " + event.info.deltaDown + " Delta Time: " + event.info.deltaTime + " Latency: " + event.info.latency); + LogUtil.debug("Server Client Bandwidth Detect Complete"); + LogUtil.debug("Detecting Client Server Bandwidth)"; + ClientServer(); + } + + public function onServerClientStatus(event:BandwidthDetectEvent):void + { + if (event.info) { + LogUtil.debug("count: "+event.info.count+ " sent: "+event.info.sent+" timePassed: "+event.info.timePassed+" latency: "+event.info.latency+" cumLatency: " + event.info.cumLatency); + } + } + + } +} diff --git a/bigbluebutton-client/src/org/red5/flash/bwcheck/events/BandwidthDetectEvent.as b/bigbluebutton-client/src/org/red5/flash/bwcheck/events/BandwidthDetectEvent.as new file mode 100644 index 0000000000000000000000000000000000000000..3720aad5161245c95ef1d1f024ab3c713f780632 --- /dev/null +++ b/bigbluebutton-client/src/org/red5/flash/bwcheck/events/BandwidthDetectEvent.as @@ -0,0 +1,30 @@ +package org.red5.flash.bwcheck.events +{ + import flash.events.Event; + + public class BandwidthDetectEvent extends Event + { + public static const DETECT_STATUS:String = "detect_status"; + public static const DETECT_COMPLETE:String = "detect_complete"; + public static const DETECT_FAILED:String = "detect_failed"; + + private var _info:Object; + + public function BandwidthDetectEvent(eventName:String) + { + super (eventName); + } + + public function set info(obj:Object):void + { + _info = obj; + } + + public function get info():Object + { + return _info; + } + + + } +} \ No newline at end of file diff --git a/bigbluebutton-web/grails-app/conf/bigbluebutton.properties b/bigbluebutton-web/grails-app/conf/bigbluebutton.properties index 9a24ea09b58f54a3093f07ebc167174a431c0539..3ac8dc6f8431189a1f9f264663fa9afe3e082f05 100755 --- a/bigbluebutton-web/grails-app/conf/bigbluebutton.properties +++ b/bigbluebutton-web/grails-app/conf/bigbluebutton.properties @@ -97,6 +97,11 @@ bigbluebutton.web.logoutURL=default # successfully joining the meeting. defaultClientUrl=${bigbluebutton.web.serverURL}/client/BigBlueButton.html +# The default avatar image to display if nothing is passed on the JOIN API (avatarURL) +# call. This avatar is displayed if the user isn't sharing the webcam and +# the option (displayAvatar) is enabled in config.xml +defaultAvatarURL=${bigbluebutton.web.serverURL}/client/avatar.png + apiVersion=0.8 # Salt which is used by 3rd-party apps to authenticate api calls diff --git a/bigbluebutton-web/grails-app/conf/spring/resources.xml b/bigbluebutton-web/grails-app/conf/spring/resources.xml index f0c2e32c3c3f217494f408644441c651ae2911b6..c23bd41d15d41e46874298744a81cac1a0e997ef 100755 --- a/bigbluebutton-web/grails-app/conf/spring/resources.xml +++ b/bigbluebutton-web/grails-app/conf/spring/resources.xml @@ -83,6 +83,7 @@ <property name="defaultClientUrl" value="${defaultClientUrl}"/> <property name="defaultMeetingDuration" value="${defaultMeetingDuration}"/> <property name="disableRecordingDefault" value="${disableRecordingDefault}"/> + <property name="defaultAvatarURL" value="${defaultAvatarURL}"/> </bean> <import resource="doc-conversion.xml" /> diff --git a/bigbluebutton-web/grails-app/controllers/org/bigbluebutton/web/controllers/ApiController.groovy b/bigbluebutton-web/grails-app/controllers/org/bigbluebutton/web/controllers/ApiController.groovy index dc823c64af3219cbfd4556153f1160056a81fe3d..4bc98f14a753ecac67ef57fa50bc3e403e4c8d93 100755 --- a/bigbluebutton-web/grails-app/controllers/org/bigbluebutton/web/controllers/ApiController.groovy +++ b/bigbluebutton-web/grails-app/controllers/org/bigbluebutton/web/controllers/ApiController.groovy @@ -304,6 +304,7 @@ class ApiController { us.internalUserId = internalUserID us.conferencename = meeting.getName() us.meetingID = meeting.getInternalId() + us.externMeetingID = meeting.getExternalId() us.externUserID = externUserID us.fullname = fullName us.role = role @@ -315,7 +316,17 @@ class ApiController { us.record = meeting.isRecord() us.welcome = meeting.getWelcomeMessage() us.logoutUrl = meeting.getLogoutUrl(); - + + if (! StringUtils.isEmpty(params.defaulLayout)) { + us.defaultLayout = params.defaulLayout; + } + + if (! StringUtils.isEmpty(params.avatarURL)) { + us.avatarURL = params.avatarURL; + } else { + us.avatarURL = meeting.defaultAvatarURL + } + // Store the following into a session so we can handle // logout, restarts properly. session['meeting-id'] = us.meetingID @@ -324,9 +335,7 @@ class ApiController { meetingService.addUserSession(session['user-token'], us); - log.info("Session user token for " + us.fullname + " [" + session['user-token'] + "]") - - + log.info("Session user token for " + us.fullname + " [" + session['user-token'] + "]") session.setMaxInactiveInterval(SESSION_TIMEOUT); //check if exists the param redirect @@ -763,6 +772,7 @@ class ApiController { fullname(us.fullname) confname(us.conferencename) meetingID(us.meetingID) + externMeetingID(us.externMeetingID) externUserID(us.externUserID) internalUserID(us.internalUserId) role(us.role) @@ -774,6 +784,8 @@ class ApiController { record(us.record) welcome(us.welcome) logoutUrl(us.logoutUrl) + defaultLayout(us.defaultLayout) + avatarURL(us.avatarURL) } } } diff --git a/bigbluebutton-web/src/java/org/bigbluebutton/api/ParamsProcessorUtil.java b/bigbluebutton-web/src/java/org/bigbluebutton/api/ParamsProcessorUtil.java index 0f0065ede277084f610aa8baff107400bbf11ebe..107a28b629a8562fc5e2c47e73328ca65c9a977a 100755 --- a/bigbluebutton-web/src/java/org/bigbluebutton/api/ParamsProcessorUtil.java +++ b/bigbluebutton-web/src/java/org/bigbluebutton/api/ParamsProcessorUtil.java @@ -31,6 +31,7 @@ public class ParamsProcessorUtil { private String defaultServerUrl; private int defaultNumDigitsForTelVoice; private String defaultClientUrl; + private String defaultAvatarURL; private int defaultMeetingDuration; private boolean disableRecordingDefault; @@ -286,6 +287,7 @@ public class ParamsProcessorUtil { .withName(meetingName).withMaxUsers(maxUsers).withModeratorPass(modPass) .withViewerPass(viewerPass).withRecording(record).withDuration(meetingDuration) .withLogoutUrl(logoutUrl).withTelVoice(telVoice).withWebVoice(webVoice).withDialNumber(dialNumber) + .withDefaultAvatarURL(defaultAvatarURL) .withMetadata(meetingInfo).withWelcomeMessage(welcomeMessage).build(); return meeting; @@ -495,6 +497,10 @@ public class ParamsProcessorUtil { this.disableRecordingDefault = disabled; } + public void setdefaultAvatarURL(String url) { + this.defaultAvatarURL = url; + } + public ArrayList<String> decodeIds(String encodeid){ ArrayList<String> ids=new ArrayList<String>(); try { diff --git a/bigbluebutton-web/src/java/org/bigbluebutton/api/domain/Meeting.java b/bigbluebutton-web/src/java/org/bigbluebutton/api/domain/Meeting.java index dbc94b6a413b135dc803e890b340b8755df06b04..32dcb87b25fa14bd18bd8df93171b2b9573979e2 100755 --- a/bigbluebutton-web/src/java/org/bigbluebutton/api/domain/Meeting.java +++ b/bigbluebutton-web/src/java/org/bigbluebutton/api/domain/Meeting.java @@ -46,7 +46,8 @@ public class Meeting { private int maxUsers; private boolean record; private String dialNumber; - + private String defaultAvatarURL; + private Map<String, String> metadata; private final ConcurrentMap<String, User> users; @@ -58,6 +59,7 @@ public class Meeting { moderatorPass = builder.moderatorPass; maxUsers = builder.maxUsers; logoutUrl = builder.logoutUrl; + defaultAvatarURL = builder.defaultAvatarURL; record = builder.record; duration = builder.duration; webVoice = builder.webVoice; @@ -152,6 +154,10 @@ public class Meeting { return welcomeMsg; } + public String getDefaultAvatarURL() { + return defaultAvatarURL; + } + public String getLogoutUrl() { return logoutUrl; } @@ -249,6 +255,7 @@ public class Meeting { private String logoutUrl; private Map<String, String> metadata; private String dialNumber; + private String defaultAvatarURL; private long createdTime; public Builder(String externalId, String internalId, long createTime) { @@ -307,6 +314,11 @@ public class Meeting { return this; } + public Builder withDefaultAvatarURL(String w) { + defaultAvatarURL = w; + return this; + } + public Builder withLogoutUrl(String l) { logoutUrl = l; return this; diff --git a/bigbluebutton-web/src/java/org/bigbluebutton/api/domain/UserSession.java b/bigbluebutton-web/src/java/org/bigbluebutton/api/domain/UserSession.java index c5a437b6e7f6d50e4fb1733f3c24ce39a585e37c..70a28866c36a0f96035e29473428c8d7c3bcc2b1 100755 --- a/bigbluebutton-web/src/java/org/bigbluebutton/api/domain/UserSession.java +++ b/bigbluebutton-web/src/java/org/bigbluebutton/api/domain/UserSession.java @@ -4,6 +4,7 @@ public class UserSession { String internalUserId = null; String conferencename = null; String meetingID = null; + String externMeetingID = null; String externUserID = null; String fullname = null; String role = null; @@ -15,4 +16,6 @@ public class UserSession { String record = null; String welcome = null; String logoutUrl = null; + String defaultLayout = "NOLAYOUT"; + String avatarURL; } diff --git a/record-and-playback/presentation/playback/presentation/lib/popcorn-complete.min.js b/record-and-playback/presentation/playback/presentation/lib/popcorn-complete.min.js index f324d54362471bebecde22941d406ef443499053..8aeee4f2b58abed7b4b0fc7ffadfe30ae8db1cb4 100755 --- a/record-and-playback/presentation/playback/presentation/lib/popcorn-complete.min.js +++ b/record-and-playback/presentation/playback/presentation/lib/popcorn-complete.min.js @@ -1,200 +1,160 @@ /* - * popcorn.js version 1.2 + * popcorn.js version 1.3 * http://popcornjs.org * * Copyright 2011, Mozilla Foundation * Licensed under the MIT license */ -(function(g,b){function d(e,m){return function(){if(n.plugin.debug)return e.apply(this,arguments);try{return e.apply(this,arguments)}catch(q){n.plugin.errors.push({plugin:m,thrown:q,source:e.toString()});this.emit("error",n.plugin.errors)}}}if(b.addEventListener){var a=Array.prototype,c=Object.prototype,f=a.forEach,h=a.slice,p=c.hasOwnProperty,o=c.toString,l=g.Popcorn,i=/^(#([\w\-\_\.]+))$/,r=[],k=false,j={events:{hash:{},apis:{}}},v=function(){return g.requestAnimationFrame||g.webkitRequestAnimationFrame|| -g.mozRequestAnimationFrame||g.oRequestAnimationFrame||g.msRequestAnimationFrame||function(e){g.setTimeout(e,16)}}(),w=function(e){var m=e.media.currentTime,q=e.options.frameAnimation,s=e.data.disabled,t=e.data.trackEvents,u=t.animating,x=t.startIndex,A=n.registryByName,B=0,D,E;for(x=Math.min(x+1,t.byStart.length-2);x>0&&t.byStart[x];){B=t.byStart[x];E=(D=B._natives)&&D.type;if(!D||A[E]||e[E])if(B.start<=m&&B.end>m&&s.indexOf(E)===-1){if(!B._running){B._running=true;D.start.call(e,null,B);q&&B&&B._running&& -B.natives.frame&&D.frame.call(e,null,B,m)}}else if(B._running===true){B._running=false;D.end.call(e,null,B);if(q&&B._natives.frame){B=u.indexOf(B);B>=0&&u.splice(B,1)}}x--}},n=function(e,m){return new n.p.init(e,m||null)};n.version="1.2";n.isSupported=true;n.instances=[];n.p=n.prototype={init:function(e,m){var q,s=this;if(typeof e==="function")if(b.readyState==="complete")e(b,n);else{r.push(e);if(!k){k=true;var t=function(){b.removeEventListener("DOMContentLoaded",t,false);for(var x=0,A=r.length;x< -A;x++)r[x].call(b,n);r=null};b.addEventListener("DOMContentLoaded",t,false)}}else{this.media=(q=i.exec(e))&&q.length&&q[2]?b.getElementById(q[2]):e;this[this.media.nodeName&&this.media.nodeName.toLowerCase()||"video"]=this.media;n.instances.push(this);this.options=m||{};this.isDestroyed=false;this.data={timeUpdate:n.nop,disabled:[],events:{},hooks:{},history:[],state:{volume:this.media.volume},trackRefs:{},trackEvents:{byStart:[{start:-1,end:-1}],byEnd:[{start:-1,end:-1}],animating:[],startIndex:0, -endIndex:0,previousUpdateTime:-1}};var u=function(){s.media.removeEventListener("loadeddata",u,false);var x;x=s.media.duration;x=x!=x?Number.MAX_VALUE:x+1;n.addTrackEvent(s,{start:x,end:x});if(s.options.frameAnimation){s.data.timeUpdate=function(){n.timeUpdate(s,{});s.emit("timeupdate");!s.isDestroyed&&v(s.data.timeUpdate)};!s.isDestroyed&&v(s.data.timeUpdate)}else{s.data.timeUpdate=function(A){n.timeUpdate(s,A)};s.isDestroyed||s.media.addEventListener("timeupdate",s.data.timeUpdate,false)}};s.media.readyState>= -2?u():s.media.addEventListener("loadeddata",u,false);return this}}};n.p.init.prototype=n.p;n.forEach=function(e,m,q){if(!e||!m)return{};q=q||this;var s,t;if(f&&e.forEach===f)return e.forEach(m,q);if(o.call(e)==="[object NodeList]"){s=0;for(t=e.length;s<t;s++)m.call(q,e[s],s,e);return e}for(s in e)p.call(e,s)&&m.call(q,e[s],s,e);return e};n.extend=function(e){var m=h.call(arguments,1);n.forEach(m,function(q){for(var s in q)e[s]=q[s]});return e};n.extend(n,{noConflict:function(e){if(e)g.Popcorn=l;return n}, -error:function(e){throw Error(e);},guid:function(e){n.guid.counter++;return(e?e:"")+(+new Date+n.guid.counter)},sizeOf:function(e){var m=0,q;for(q in e)m++;return m},isArray:Array.isArray||function(e){return o.call(e)==="[object Array]"},nop:function(){},position:function(e){e=e.getBoundingClientRect();var m={},q=b.documentElement,s=b.body,t,u,x;t=q.clientTop||s.clientTop||0;u=q.clientLeft||s.clientLeft||0;x=g.pageYOffset&&q.scrollTop||s.scrollTop;q=g.pageXOffset&&q.scrollLeft||s.scrollLeft;t=Math.ceil(e.top+ -x-t);u=Math.ceil(e.left+q-u);for(var A in e)m[A]=Math.round(e[A]);return n.extend({},m,{top:t,left:u})},disable:function(e,m){var q=e.data.disabled;q.indexOf(m)===-1&&q.push(m);w(e);return e},enable:function(e,m){var q=e.data.disabled,s=q.indexOf(m);s>-1&&q.splice(s,1);w(e);return e},destroy:function(e){var m=e.data.events,q,s,t;for(s in m){q=m[s];for(t in q)delete q[t];m[s]=null}if(!e.isDestroyed){e.data.timeUpdate&&e.media.removeEventListener("timeupdate",e.data.timeUpdate,false);e.isDestroyed= -true}}});n.guid.counter=1;n.extend(n.p,function(){var e={};n.forEach("load play pause currentTime playbackRate volume duration preload playbackRate autoplay loop controls muted buffered readyState seeking paused played seekable ended".split(/\s+/g),function(m){e[m]=function(q){if(typeof this.media[m]==="function"){if(q!=null&&/play|pause/.test(m))this.media.currentTime=n.util.toSeconds(q);this.media[m]();return this}if(q!=null){this.media[m]=q;return this}return this.media[m]}});return e}());n.forEach("enable disable".split(" "), -function(e){n.p[e]=function(m){return n[e](this,m)}});n.extend(n.p,{roundTime:function(){return-~this.media.currentTime},exec:function(e,m){n.addTrackEvent(this,{start:e,end:e+1,_running:false,_natives:{start:m||n.nop,end:n.nop,type:"cue"}});return this},mute:function(e){e=e==null||e===true?"muted":"unmuted";if(e==="unmuted"){this.media.muted=false;this.media.volume=this.data.state.volume}if(e==="muted"){this.data.state.volume=this.media.volume;this.media.muted=true}this.emit(e);return this},unmute:function(e){return this.mute(e== -null?false:!e)},position:function(){return n.position(this.media)},toggle:function(e){return n[this.data.disabled.indexOf(e)>-1?"enable":"disable"](this,e)},defaults:function(e,m){if(n.isArray(e)){n.forEach(e,function(q){for(var s in q)this.defaults(s,q[s])},this);return this}if(!this.options.defaults)this.options.defaults={};this.options.defaults[e]||(this.options.defaults[e]={});n.extend(this.options.defaults[e],m);return this}});n.Events={UIEvents:"blur focus focusin focusout load resize scroll unload", -MouseEvents:"mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave click dblclick",Events:"loadstart progress suspend emptied stalled play pause loadedmetadata loadeddata waiting playing canplay canplaythrough seeking seeked timeupdate ended ratechange durationchange volumechange"};n.Events.Natives=n.Events.UIEvents+" "+n.Events.MouseEvents+" "+n.Events.Events;j.events.apiTypes=["UIEvents","MouseEvents","Events"];(function(e,m){for(var q=j.events.apiTypes,s=e.Natives.split(/\s+/g), -t=0,u=s.length;t<u;t++)m.hash[s[t]]=true;q.forEach(function(x){m.apis[x]={};for(var A=e[x].split(/\s+/g),B=A.length,D=0;D<B;D++)m.apis[x][A[D]]=true})})(n.Events,j.events);n.events={isNative:function(e){return!!j.events.hash[e]},getInterface:function(e){if(!n.events.isNative(e))return false;var m=j.events,q=m.apiTypes;m=m.apis;for(var s=0,t=q.length,u,x;s<t;s++){x=q[s];if(m[x][e]){u=x;break}}return u},all:n.Events.Natives.split(/\s+/g),fn:{trigger:function(e,m){var q;if(this.data.events[e]&&n.sizeOf(this.data.events[e])){if(q= -n.events.getInterface(e)){q=b.createEvent(q);q.initEvent(e,true,true,g,1);this.media.dispatchEvent(q);return this}n.forEach(this.data.events[e],function(s){s.call(this,m)},this)}return this},listen:function(e,m){var q=this,s=true,t=n.events.hooks[e],u;if(!this.data.events[e]){this.data.events[e]={};s=false}if(t){t.add&&t.add.call(this,{},m);if(t.bind)e=t.bind;if(t.handler){u=m;m=function(x){t.handler.call(q,x,u)}}s=true;if(!this.data.events[e]){this.data.events[e]={};s=false}}this.data.events[e][m.name|| -m.toString()+n.guid()]=m;!s&&n.events.all.indexOf(e)>-1&&this.media.addEventListener(e,function(x){n.forEach(q.data.events[e],function(A){typeof A==="function"&&A.call(q,x)})},false);return this},unlisten:function(e,m){if(this.data.events[e]&&this.data.events[e][m]){delete this.data.events[e][m];return this}this.data.events[e]=null;return this}},hooks:{canplayall:{bind:"canplaythrough",add:function(e,m){var q=false;if(this.media.readyState){m.call(this,e);q=true}this.data.hooks.canplayall={fired:q}}, -handler:function(e,m){if(!this.data.hooks.canplayall.fired){m.call(this,e);this.data.hooks.canplayall.fired=true}}}}};n.forEach([["trigger","emit"],["listen","on"],["unlisten","off"]],function(e){n.p[e[0]]=n.p[e[1]]=n.events.fn[e[0]]});n.addTrackEvent=function(e,m){if(m&&m._natives&&m._natives.type&&e.options.defaults&&e.options.defaults[m._natives.type])m=n.extend({},e.options.defaults[m._natives.type],m);if(m._natives){m._id=!m.id?n.guid(m._natives.type):m.id;e.data.history.push(m._id)}m.start= -n.util.toSeconds(m.start,e.options.framerate);m.end=n.util.toSeconds(m.end,e.options.framerate);var q=e.data.trackEvents.byStart,s=e.data.trackEvents.byEnd,t;for(t=q.length-1;t>=0;t--)if(m.start>=q[t].start){q.splice(t+1,0,m);break}for(q=s.length-1;q>=0;q--)if(m.end>s[q].end){s.splice(q+1,0,m);break}if(m._natives&&(n.registryByName[m._natives.type]||e[m._natives.type])){s=e.media.currentTime;if(m.end>s&&m.start<=s&&e.data.disabled.indexOf(m._natives.type)===-1){m._running=true;m._natives.start.call(e, -null,m);if(e.options.frameAnimation&&m._natives.frame){e.data.trackEvents.animating.push(m);m._natives.frame.call(e,null,m,s)}}}t<=e.data.trackEvents.startIndex&&m.start<=e.data.trackEvents.previousUpdateTime&&e.data.trackEvents.startIndex++;q<=e.data.trackEvents.endIndex&&m.end<e.data.trackEvents.previousUpdateTime&&e.data.trackEvents.endIndex++;this.timeUpdate(e,null,true);m._id&&n.addTrackEvent.ref(e,m)};n.addTrackEvent.ref=function(e,m){e.data.trackRefs[m._id]=m;return e};n.removeTrackEvent=function(e, -m){for(var q,s,t=e.data.history.length,u=e.data.trackEvents.byStart.length,x=0,A=0,B=[],D=[],E=[],z=[];--u>-1;){q=e.data.trackEvents.byStart[x];s=e.data.trackEvents.byEnd[x];if(!q._id){B.push(q);D.push(s)}if(q._id){q._id!==m&&B.push(q);s._id!==m&&D.push(s);if(q._id===m){A=x;q._natives._teardown&&q._natives._teardown.call(e,q)}}x++}u=e.data.trackEvents.animating.length;x=0;if(u)for(;--u>-1;){q=e.data.trackEvents.animating[x];q._id||E.push(q);q._id&&q._id!==m&&E.push(q);x++}A<=e.data.trackEvents.startIndex&& -e.data.trackEvents.startIndex--;A<=e.data.trackEvents.endIndex&&e.data.trackEvents.endIndex--;e.data.trackEvents.byStart=B;e.data.trackEvents.byEnd=D;e.data.trackEvents.animating=E;for(u=0;u<t;u++)e.data.history[u]!==m&&z.push(e.data.history[u]);e.data.history=z;n.removeTrackEvent.ref(e,m)};n.removeTrackEvent.ref=function(e,m){delete e.data.trackRefs[m];return e};n.getTrackEvents=function(e){var m=[];e=e.data.trackEvents.byStart;for(var q=e.length,s=0,t;s<q;s++){t=e[s];t._id&&m.push(t)}return m}; -n.getTrackEvents.ref=function(e){return e.data.trackRefs};n.getTrackEvent=function(e,m){return e.data.trackRefs[m]};n.getTrackEvent.ref=function(e,m){return e.data.trackRefs[m]};n.getLastTrackEventId=function(e){return e.data.history[e.data.history.length-1]};n.timeUpdate=function(e,m){var q=e.media.currentTime,s=e.data.trackEvents.previousUpdateTime,t=e.data.trackEvents,u=t.animating,x=t.endIndex,A=t.startIndex,B=0,D=t.byStart.length,E=t.byEnd.length,z=n.registryByName,F,H,G;if(s<=q){for(;t.byEnd[x]&& -t.byEnd[x].end<=q;){F=t.byEnd[x];G=(s=F._natives)&&s.type;if(!s||z[G]||e[G]){if(F._running===true){F._running=false;s.end.call(e,m,F);e.emit("trackend",n.extend({},F,{plugin:G,type:"trackend"}))}x++}else{n.removeTrackEvent(e,F._id);return}}for(;t.byStart[A]&&t.byStart[A].start<=q;){H=t.byStart[A];G=(s=H._natives)&&s.type;if(!s||z[G]||e[G]){if(H.end>q&&H._running===false&&e.data.disabled.indexOf(G)===-1){H._running=true;s.start.call(e,m,H);e.emit("trackstart",n.extend({},H,{plugin:G,type:"trackstart"})); -e.options.frameAnimation&&H&&H._running&&H._natives.frame&&u.push(H)}A++}else{n.removeTrackEvent(e,H._id);return}}if(e.options.frameAnimation)for(;B<u.length;){z=u[B];if(z._running){z._natives.frame.call(e,m,z,q);B++}else u.splice(B,1)}}else if(s>q){for(;t.byStart[A]&&t.byStart[A].start>q;){H=t.byStart[A];G=(s=H._natives)&&s.type;if(!s||z[G]||e[G]){if(H._running===true){H._running=false;s.end.call(e,m,H);e.emit("trackend",n.extend({},F,{plugin:G,type:"trackend"}))}A--}else{n.removeTrackEvent(e,H._id); -return}}for(;t.byEnd[x]&&t.byEnd[x].end>q;){F=t.byEnd[x];G=(s=F._natives)&&s.type;if(!s||z[G]||e[G]){if(F.start<=q&&F._running===false&&e.data.disabled.indexOf(G)===-1){F._running=true;s.start.call(e,m,F);e.emit("trackstart",n.extend({},H,{plugin:G,type:"trackstart"}));e.options.frameAnimation&&F&&F._running&&F._natives.frame&&u.push(F)}x--}else{n.removeTrackEvent(e,F._id);return}}if(e.options.frameAnimation)for(;B<u.length;){z=u[B];if(z._running){z._natives.frame.call(e,m,z,q);B++}else u.splice(B, -1)}}t.endIndex=x;t.startIndex=A;t.previousUpdateTime=q;t.byStart.length<D&&t.startIndex--;t.byEnd.length<E&&t.endIndex--};n.extend(n.p,{getTrackEvents:function(){return n.getTrackEvents.call(null,this)},getTrackEvent:function(e){return n.getTrackEvent.call(null,this,e)},getLastTrackEventId:function(){return n.getLastTrackEventId.call(null,this)},removeTrackEvent:function(e){n.removeTrackEvent.call(null,this,e);return this},removePlugin:function(e){n.removePlugin.call(null,this,e);return this},timeUpdate:function(e){n.timeUpdate.call(null, -this,e);return this},destroy:function(){n.destroy.call(null,this);return this}});n.manifest={};n.registry=[];n.registryByName={};n.plugin=function(e,m,q){if(n.protect.natives.indexOf(e.toLowerCase())>=0)n.error("'"+e+"' is a protected function name");else{var s=["start","end"],t={},u=typeof m==="function",x=["_setup","_teardown","start","end","frame"],A=function(E,z){E=E||n.nop;z=z||n.nop;return function(){E.apply(this,arguments);z.apply(this,arguments)}};n.manifest[e]=q=q||m.manifest||{};x.forEach(function(E){m[E]= -d(m[E]||n.nop,e)});var B=function(E,z){if(!z)return this;var F=z._natives={},H="",G;n.extend(F,E);z._natives.type=e;z._running=false;F.start=F.start||F["in"];F.end=F.end||F.out;F._teardown=A(function(){var I=h.call(arguments);I.unshift(null);I[1]._running&&F.end.apply(this,I)},F._teardown);z.compose=z.compose&&z.compose.split(" ")||[];z.effect=z.effect&&z.effect.split(" ")||[];z.compose=z.compose.concat(z.effect);z.compose.forEach(function(I){H=n.compositions[I]||{};x.forEach(function(J){F[J]=A(F[J], -H[J])})});z._natives.manifest=q;if(!("start"in z))z.start=z["in"]||0;if(!z.end&&z.end!==0)z.end=z.out||Number.MAX_VALUE;if(!p.call(z,"toString"))z.toString=function(){var I=["start: "+z.start,"end: "+z.end,"id: "+(z.id||z._id)];z.target!=null&&I.push("target: "+z.target);return e+" ( "+I.join(", ")+" )"};if(!z.target){G="options"in q&&q.options;z.target=G&&"target"in G&&G.target}z._natives._setup&&z._natives._setup.call(this,z);n.addTrackEvent(this,n.extend(z,z));n.forEach(E,function(I,J){J!=="type"&& -s.indexOf(J)===-1&&this.on(J,I)},this);return this};n.p[e]=t[e]=function(E){E=n.extend({},this.options.defaults&&this.options.defaults[e]||{},E);return B.call(this,u?m.call(this,E):m,E)};var D={fn:t[e],definition:m,base:m,parents:[],name:e};n.registry.push(n.extend(t,D,{type:e}));n.registryByName[e]=D;return t}};n.plugin.errors=[];n.plugin.debug=false;n.removePlugin=function(e,m){if(!m){m=e;e=n.p;if(n.protect.natives.indexOf(m.toLowerCase())>=0){n.error("'"+m+"' is a protected function name");return}var q= -n.registry.length,s;for(s=0;s<q;s++)if(n.registry[s].name===m){n.registry.splice(s,1);delete n.registryByName[m];delete n.manifest[m];delete e[m];return}}q=e.data.trackEvents.byStart;s=e.data.trackEvents.byEnd;var t=e.data.trackEvents.animating,u,x;u=0;for(x=q.length;u<x;u++){if(q[u]&&q[u]._natives&&q[u]._natives.type===m){q[u]._natives._teardown&&q[u]._natives._teardown.call(e,q[u]);q.splice(u,1);u--;x--;if(e.data.trackEvents.startIndex<=u){e.data.trackEvents.startIndex--;e.data.trackEvents.endIndex--}}s[u]&& -s[u]._natives&&s[u]._natives.type===m&&s.splice(u,1)}u=0;for(x=t.length;u<x;u++)if(t[u]&&t[u]._natives&&t[u]._natives.type===m){t.splice(u,1);u--;x--}};n.compositions={};n.compose=function(e,m,q){n.manifest[e]=q||m.manifest||{};n.compositions[e]=m};n.plugin.effect=n.effect=n.compose;var y=/\?/,C={url:"",data:"",dataType:"",success:n.nop,type:"GET",async:true,xhr:function(){return new g.XMLHttpRequest}};n.xhr=function(e){e.dataType=e.dataType&&e.dataType.toLowerCase()||null;if(e.dataType&&(e.dataType=== -"jsonp"||e.dataType==="script"))n.xhr.getJSONP(e.url,e.success,e.dataType==="script");else{e=n.extend({},C,e);e.ajax=e.xhr();if(e.ajax){if(e.type==="GET"&&e.data){e.url+=(y.test(e.url)?"&":"?")+e.data;e.data=null}e.ajax.open(e.type,e.url,e.async);e.ajax.send(e.data||null);return n.xhr.httpData(e)}}};n.xhr.httpData=function(e){var m,q=null,s,t=null;e.ajax.onreadystatechange=function(){if(e.ajax.readyState===4){try{q=JSON.parse(e.ajax.responseText)}catch(u){}m={xml:e.ajax.responseXML,text:e.ajax.responseText, -json:q};if(!m.xml||!m.xml.documentElement){m.xml=null;try{s=new DOMParser;t=s.parseFromString(e.ajax.responseText,"text/xml");if(!t.getElementsByTagName("parsererror").length)m.xml=t}catch(x){}}if(e.dataType)m=m[e.dataType];e.success.call(e.ajax,m)}};return m};n.xhr.getJSONP=function(e,m,q){var s=b.head||b.getElementsByTagName("head")[0]||b.documentElement,t=b.createElement("script"),u=e.split("?")[1],x=false,A=[],B,D;if(u&&!q)A=u.split("&");if(A.length)D=A[A.length-1].split("=");B=A.length?D[1]? -D[1]:D[0]:"jsonp";if(!u&&!q)e+="?callback="+B;if(B&&!q){if(window[B])B=n.guid(B);window[B]=function(E){m&&m(E);x=true};e=e.replace(D.join("="),D[0]+"="+B)}t.addEventListener("load",function(){q&&m&&m();x&&delete window[B];s.removeChild(t)},false);t.src=e;s.insertBefore(t,s.firstChild)};n.getJSONP=n.xhr.getJSONP;n.getScript=n.xhr.getScript=function(e,m){return n.xhr.getJSONP(e,m,true)};n.util={toSeconds:function(e,m){var q=/^([0-9]+:){0,2}[0-9]+([.;][0-9]+)?$/,s,t,u;if(typeof e==="number")return e; -typeof e==="string"&&!q.test(e)&&n.error("Invalid time format");q=e.split(":");s=q.length-1;t=q[s];if(t.indexOf(";")>-1){t=t.split(";");u=0;if(m&&typeof m==="number")u=parseFloat(t[1],10)/m;q[s]=parseInt(t[0],10)+u}s=q[0];return{1:parseFloat(s,10),2:parseInt(s,10)*60+parseFloat(q[1],10),3:parseInt(s,10)*3600+parseInt(q[1],10)*60+parseFloat(q[2],10)}[q.length||1]}};n.p.cue=n.p.exec;n.protect={natives:function(e){return Object.keys?Object.keys(e):function(m){var q,s=[];for(q in m)p.call(m,q)&&s.push(q); -return s}(e)}(n.p).map(function(e){return e.toLowerCase()})};n.forEach({listen:"on",unlisten:"off",trigger:"emit",exec:"cue"},function(e,m){var q=n.p[m];n.p[m]=function(){if(typeof console!=="undefined"&&console.warn){console.warn("Deprecated method '"+m+"', "+(e==null?"do not use.":"use '"+e+"' instead."));n.p[m]=q}return n.p[e].apply(this,[].slice.call(arguments))}});g.Popcorn=n}else{g.Popcorn={isSupported:false};for(a="removeInstance addInstance getInstanceById removeInstanceById forEach extend effects error guid sizeOf isArray nop position disable enable destroyaddTrackEvent removeTrackEvent getTrackEvents getTrackEvent getLastTrackEventId timeUpdate plugin removePlugin compose effect xhr getJSONP getScript".split(/\s+/);a.length;)g.Popcorn[a.shift()]= -function(){}}})(window,window.document);(function(g,b){var d=g.document,a=g.location,c=/:\/\//,f=a.href.replace(a.href.split("/").slice(-1)[0],""),h=function(o,l,i){o=o||0;l=(l||o||0)+1;i=i||1;l=Math.ceil((l-o)/i)||0;var r=0,k=[];for(k.length=l;r<l;){k[r++]=o;o+=i}return k};b.sequence=function(o,l){return new b.sequence.init(o,l)};b.sequence.init=function(o,l){this.parent=d.getElementById(o);this.seqId=b.guid("__sequenced");this.queue=[];this.playlist=[];this.inOuts={ofVideos:[],ofClips:[]};this.dims={width:0,height:0};this.active=0;this.playing= -this.cycling=false;this.times={last:0};this.events={};var i=this,r=0;b.forEach(l,function(k,j){var v=d.createElement("video");v.preload="auto";v.controls=true;v.style.display=j&&"none"||"";v.id=i.seqId+"-"+j;i.queue.push(v);var w=k["in"],n=k.out;i.inOuts.ofVideos.push({"in":w!==undefined&&w||1,out:n!==undefined&&n||0});i.inOuts.ofVideos[j].out=i.inOuts.ofVideos[j].out||i.inOuts.ofVideos[j]["in"]+2;v.src=!c.test(k.src)?f+k.src:k.src;v.setAttribute("data-sequence-owner",o);v.setAttribute("data-sequence-guid", -i.seqId);v.setAttribute("data-sequence-id",j);v.setAttribute("data-sequence-clip",[i.inOuts.ofVideos[j]["in"],i.inOuts.ofVideos[j].out].join(":"));i.parent.appendChild(v);i.playlist.push(b("#"+v.id))});i.inOuts.ofVideos.forEach(function(k){k={"in":r,out:r+(k.out-k["in"])};i.inOuts.ofClips.push(k);r=k.out+1});b.forEach(this.queue,function(k,j){function v(){if(!j){i.dims.width=k.videoWidth;i.dims.height=k.videoHeight}k.currentTime=i.inOuts.ofVideos[j]["in"]-0.5;k.removeEventListener("canplaythrough", -v,false);return true}k.addEventListener("canplaythrough",v,false);k.addEventListener("play",function(){i.playing=true},false);k.addEventListener("pause",function(){i.playing=false},false);k.addEventListener("timeupdate",function(w){w=w.srcElement||w.target;w=+(w.dataset&&w.dataset.sequenceId||w.getAttribute("data-sequence-id"));var n=Math.floor(k.currentTime);if(i.times.last!==n&&w===i.active){i.times.last=n;n===i.inOuts.ofVideos[w].out&&b.sequence.cycle.call(i,w)}},false)});return this};b.sequence.init.prototype= -b.sequence.prototype;b.sequence.cycle=function(o){this.queue||b.error("Popcorn.sequence.cycle is not a public method");var l=this.queue,i=this.inOuts.ofVideos,r=l[o],k=0,j;if(l[o+1])k=o+1;if(l[o+1]){l=l[k];i=i[k];b.extend(l,{width:this.dims.width,height:this.dims.height});j=this.playlist[k];r.pause();this.active=k;this.times.last=i["in"]-1;j.currentTime(i["in"]);j[k?"play":"pause"]();this.trigger("cycle",{position:{previous:o,current:k}});if(k){r.style.display="none";l.style.display=""}this.cycling= -false}else this.playlist[o].pause();return this};var p=["timeupdate","play","pause"];b.extend(b.sequence.prototype,{eq:function(o){return this.playlist[o]},remove:function(){this.parent.innerHTML=null},clip:function(o){return this.inOuts.ofVideos[o]},duration:function(){for(var o=0,l=this.inOuts.ofClips,i=0;i<l.length;i++)o+=l[i].out-l[i]["in"]+1;return o-1},play:function(){this.playlist[this.active].play();return this},exec:function(o,l){var i=this.active;this.inOuts.ofClips.forEach(function(r,k){if(o>= -r["in"]&&o<=r.out)i=k});o+=this.inOuts.ofVideos[i]["in"]-this.inOuts.ofClips[i]["in"];b.addTrackEvent(this.playlist[i],{start:o-1,end:o,_running:false,_natives:{start:l||b.nop,end:b.nop,type:"exec"}});return this},listen:function(o,l){var i=this,r=this.playlist,k=r.length,j=0;if(!l)l=b.nop;if(b.Events.Natives.indexOf(o)>-1)b.forEach(r,function(v){v.listen(o,function(w){w.active=i;if(p.indexOf(o)>-1)l.call(v,w);else++j===k&&l.call(v,w)})});else{this.events[o]||(this.events[o]={});r=l.name||b.guid("__"+ -o);this.events[o][r]=l}return this},unlisten:function(){},trigger:function(o,l){var i=this;if(!(b.Events.Natives.indexOf(o)>-1)){this.events[o]&&b.forEach(this.events[o],function(r){r.call(i,{type:o},l)});return this}}});b.forEach(b.manifest,function(o,l){b.sequence.prototype[l]=function(i){var r={},k=[],j,v,w,n,y;for(j=0;j<this.inOuts.ofClips.length;j++){k=this.inOuts.ofClips[j];v=h(k["in"],k.out);w=v.indexOf(i.start);n=v.indexOf(i.end);if(w>-1)r[j]=b.extend({},k,{start:v[w],clipIdx:w});if(n>-1)r[j]= -b.extend({},k,{end:v[n],clipIdx:n})}j=Object.keys(r).map(function(e){return+e});k=h(j[0],j[1]);for(j=0;j<k.length;j++){w={};n=k[j];var C=r[n];if(C){y=this.inOuts.ofVideos[n];v=C.clipIdx;y=h(y["in"],y.out);if(C.start){w.start=y[v];w.end=y[y.length-1]}if(C.end){w.start=y[0];w.end=y[v]}}else{w.start=this.inOuts.ofVideos[n]["in"];w.end=this.inOuts.ofVideos[n].out}this.playlist[n][l](b.extend({},i,w))}return this}})})(this,Popcorn);(function(g){document.addEventListener("DOMContentLoaded",function(){var b=document.querySelectorAll("[data-timeline-sources]");g.forEach(b,function(d,a){var c=b[a],f,h,p;if(!c.id)c.id=g.guid("__popcorn");if(c.nodeType&&c.nodeType===1){p=g("#"+c.id);f=(c.getAttribute("data-timeline-sources")||"").split(",");f[0]&&g.forEach(f,function(o){h=o.split("!");if(h.length===1){h=o.match(/(.*)[\/\\]([^\/\\]+\.\w+)$/)[2].split(".");h[0]="parse"+h[1].toUpperCase();h[1]=o}f[0]&&p[h[0]]&&p[h[0]](h[1])});p.autoplay&& -p.play()}})},false)})(Popcorn);(function(g,b){function d(f){f=typeof f==="string"?f:[f.language,f.region].join("-");var h=f.split("-");return{iso6391:f,language:h[0]||"",region:h[1]||""}}var a=g.navigator,c=d(a.userLanguage||a.language);b.locale={get:function(){return c},set:function(f){c=d(f);b.locale.broadcast();return c},broadcast:function(f){var h=b.instances,p=h.length,o=0,l;for(f=f||"locale:changed";o<p;o++){l=h[o];f in l.data.events&&l.trigger(f)}}}})(this,this.Popcorn);(function(g){var b=Object.prototype.hasOwnProperty;g.parsers={};g.parser=function(d,a,c){if(g.protect.natives.indexOf(d.toLowerCase())>=0)g.error("'"+d+"' is a protected function name");else{if(typeof a==="function"&&!c){c=a;a=""}if(!(typeof c!=="function"||typeof a!=="string")){var f={};f[d]=function(h,p){if(!h)return this;var o=this;g.xhr({url:h,dataType:a,success:function(l){var i,r,k=0;l=c(l).data||[];if(i=l.length){for(;k<i;k++){r=l[k];for(var j in r)b.call(r,j)&&o[j]&&o[j](r[j])}p&&p()}}}); -return this};g.extend(g.p,f);return f}}}})(Popcorn);(function(g){var b=function(a,c){a=a||g.nop;c=c||g.nop;return function(){a.apply(this,arguments);c.apply(this,arguments)}},d=/^(#([\w\-\_\.]+))$/;g.player=function(a,c){if(!g[a]){c=c||{};var f=function(h,p,o){o=o||{};var l=new Date/1E3,i=l,r=0,k=0,j=1,v=false,w={},n=document.getElementById(d.exec(h)&&d.exec(h)[2])||document.getElementById(h)||h,y={};Object.prototype.__defineGetter__||(y=n||document.createElement("div"));for(var C in n)if(!(C in y))if(typeof n[C]==="object")y[C]=n[C];else if(typeof n[C]=== -"function")y[C]=function(m){return"length"in n[m]&&!n[m].call?n[m]:function(){return n[m].apply(n,arguments)}}(C);else g.player.defineProperty(y,C,{get:function(m){return function(){return n[m]}}(C),set:g.nop,configurable:true});var e=function(){l=new Date/1E3;if(!y.paused){y.currentTime+=l-i;y.dispatchEvent("timeupdate");setTimeout(e,10)}i=l};y.play=function(){this.paused=false;if(y.readyState>=4){i=new Date/1E3;y.dispatchEvent("play");e()}};y.pause=function(){this.paused=true;y.dispatchEvent("pause")}; -g.player.defineProperty(y,"currentTime",{get:function(){return r},set:function(m){r=+m;y.dispatchEvent("timeupdate");return r},configurable:true});g.player.defineProperty(y,"volume",{get:function(){return j},set:function(m){j=+m;y.dispatchEvent("volumechange");return j},configurable:true});g.player.defineProperty(y,"muted",{get:function(){return v},set:function(m){v=+m;y.dispatchEvent("volumechange");return v},configurable:true});g.player.defineProperty(y,"readyState",{get:function(){return k},set:function(m){return k= -m},configurable:true});y.addEventListener=function(m,q){w[m]||(w[m]=[]);w[m].push(q);return q};y.removeEventListener=function(m,q){var s,t=w[m];if(t){for(s=w[m].length-1;s>=0;s--)q===t[s]&&t.splice(s,1);return q}};y.dispatchEvent=function(m){var q,s=m.type;if(!s){s=m;if(m=g.events.getInterface(s)){q=document.createEvent(m);q.initEvent(s,true,true,window,1)}}if(w[s])for(m=w[s].length-1;m>=0;m--)w[s][m].call(this,q,this)};y.src=p||"";y.duration=0;y.paused=true;y.ended=0;o&&o.events&&g.forEach(o.events, -function(m,q){y.addEventListener(q,m,false)});if(c._canPlayType(n.nodeName,p)!==false)if(c._setup)c._setup.call(y,o);else{y.readyState=4;y.dispatchEvent("loadedmetadata");y.dispatchEvent("loadeddata");y.dispatchEvent("canplaythrough")}else y.dispatchEvent("error");y.addEventListener("loadedmetadata",function(){y.currentTime=r;y.volume=j;y.muted=v});y.addEventListener("loadeddata",function(){!y.paused&&y.play()});h=new g.p.init(y,o);if(c._teardown)h.destroy=b(h.destroy,function(){c._teardown.call(y, -o)});return h};f.canPlayType=c._canPlayType=c._canPlayType||g.nop;g[a]=g.player.registry[a]=f}};g.player.registry={};g.player.defineProperty=Object.defineProperty||function(a,c,f){a.__defineGetter__(c,f.get||g.nop);a.__defineSetter__(c,f.set||g.nop)};g.smart=function(a,c,f){var h=d.exec(a);h=h&&h.length&&h[2]?document.getElementById(h[2]):a;if(h.nodeType==="VIDEO"&&!c){if(typeof c==="object")f=c;return g(h,f)}for(var p in g.player.registry)if(g.player.registry.hasOwnProperty(p))if(g.player.registry[p].canPlayType(h.nodeName, -c))return g[p](a,c,f);if(h.nodeType!=="VIDEO"){a=document.createElement("video");h.appendChild(a);h=a}f&&f.events&&f.events.error&&h.addEventListener("error",f.events.error,false);h.src=c;return g(h,f)}})(Popcorn);(function(g){var b=function(d,a){var c=0,f=0,h;g.forEach(a.classes,function(p,o){h=[];if(p==="parent")h[0]=document.querySelectorAll("#"+a.target)[0].parentNode;else h=document.querySelectorAll("#"+a.target+" "+p);c=0;for(f=h.length;c<f;c++)h[c].classList.toggle(o)})};g.compose("applyclass",{manifest:{about:{name:"Popcorn applyclass Effect",version:"0.1",author:"@scottdowne",website:"scottdowne.wordpress.com"},options:{}},_setup:function(d){d.classes={};d.applyclass=d.applyclass||"";for(var a=d.applyclass.replace(/\s/g, -"").split(","),c=[],f=0,h=a.length;f<h;f++){c=a[f].split(":");if(c[0])d.classes[c[0]]=c[1]||""}},start:b,end:b})})(Popcorn);(function(g){g.plugin("attribution",function(){var b={"cc-by":"data:image/png;base64,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", -"cc-by-sa":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFgAAAAfCAYAAABjyArgAAAACXBIWXMAAAsTAAALEwEAmpwYAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAAAEZ0FNQQAAsY58+1GTAAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1MAAA6mAAADqYAAAXb5JfxUYAAAj2SURBVHja7FpLbBvHGf72IaMyInZ9SgKqiHQTdfH6eUossmlTuI7tZS27dtzUpA8NGqMgldpy2kiiKFupo9qh2MIx2iYS4/QaaP0CGqcwV2qAWpRtUnAA6kYGkFDnJIVKAVvc3elhd4e7FPWgHkHj+BeGOzuPf3e/+eaff/4RQwhxMQzzFZ7ImgshhGEAEAC4cfM6WJYFy7LgOA4sy4FjWbCceWVZMAwLlmHAMAzAMJYWEBAQnUAnOnTdSJqmGVddg6bp0HWN1ulEp+0JIdbL0PzjIAf3HwIAMACIBS7HcUZiuVKe44w6ljNBZsAwrB1fExwTWN0AU9PMZM9rTpB1XafA2oF+nEDmATjB5XjwjquRrl25jmQyiVQqhdnCrENRnasOO3fuhO+HPuzd9zI0nQPLqsaAaCwYMOZY2qaPToyZAHMOMYuDe28sDfljGdls1lHu8XggHZCwdceWVYGxXvoZAOSTW/8Az/MUVJ7njcTxGFZG0HeuD1NTU8tS6Ha70f67drS07IKqadA0FapqJk2FqmqU4ZWYXM7iB//5EhfjFzGRnQAAeL1eiKIIAMhkMlAUBQDQ5GnCidAJPPPs01UBsJ76D+4/ZAD8z+FPwXN8CVi+BjU8j0hnN+QhmXYQBAGSJKGhoQEtLS0AgOHhYeTzeciyjJmZGdpW8ks42f5b1G6shaqqKKoqVLUIVVWdJsMCWDdtuQ3orwtfI3QijEKhAEmSEIvF0NDQ4PiIfD6PtrY2yLIMl8uF3r7eZYOw3vopwLf+dQs1FrA1PGr4Gge4giAgHA4jFApBEIQFFSYSCbS1tVGgmzxNeH/gb/hebS1UtYhisUiZXBHkMnvc+WYXJrITCAQCGBwcLE0707TYmZ5IJBAMBtHkacKZcz3LAqCS/snJSUxNThqzsb4e9fX1K9Z/cP8hsADAmTaY5zjwnJO5oiginU4jEoksCi4ABAIB5HI5OsUmshM433fBYctZ6pEwpWT+2QG8N5bGRHYCkiSh/dSpJT8mEAhAkiRMZCdwbyy9LJtbrv/vly/D+/wLOHr4CI4ePgLv8y/g05s3V6TfEhYAWMst4zgMKyMOcJPJ5Lxps5gIgoBkMklBlodkDA+PgOP4yiCzltsHB8jyx8Y7xGIxeJqby/3LigtiLBZz9F1MyvWP3r6N7q4I6p95Fl6vDwdaWwEAv/7Va/hTf3/V+h0AGww2WNx3ro8CNTg4uCRrFwPZ6tv3hz7TlzbBZUyfmjU9DAYlkM3pn81m4fV65w1uMBikzA8Gg466hoYGeL3eeZ5AJbHrLxQKyKbvAwD2Sz/D+4kBvHP+j3irq9MwDwODVet3Mtj8+GtXrlNvIRwOUxauRARBoCM+NTWFa1ev0w2LAfLCJsKSSs9PJBIV84v1WUjsbXvfNYj11w8/oGU/fuklAEChUMCXDx5UrZ8CbLEpmUxScEKhEG2kKAr8fj98Ph98Ph+i0eiCdf3mdLLslsXi5K2kjb0l08AwlU3ENykulwvxeBwbXXW4dOlSxTYPHz5akW5jo8EwYBkGqVTKcLEkiQKjKAp8Pp+jk6IoUBQFoVAIfr9/Xt34+DhdlSVJQiKRQCqVMnaANmCBErglr7ykK5PJVFzMLOYGAoF59ZX6LCT2tjU8j/aTJ7GxtpaWjd6+TfPPNTxXtX4bg40PtXZomzdvpg3a2tqo/cnlcnTRO3bsGGWyKIrI5XIYGhpy+MgAaH62MFsyB/Rq4TrfRHg8HiiKgnw+7yi3u2v2vOWzKooCj8ez5IeX65+cnER3VwSv/PwwenvOoLfnDLo6OgAAp06frlq/A2D74lJuZ6wRCwQC1MjncjkEAgFaZ20+JEmidfaFp+R+0Z8lX0w6IDkGeDlitbX6VqM/ePw4gsePGwM3MIDBgQE8evgIe/a+jCNHX6lav8NE/D/K1h1b0ORpgizLCAaD89haCVxZltHkaVpW3KCS/re6OvGT3bvxxRcGq5ubm6mLWK1+J4OJc1dktzMWmxOJBGZmZpDJZNDY2IhoNFrydc1tsr3OPm1L/iv9WdbLnf59O1wuFxKJBPx+P9Vl94Pz+Tz8fj/6+/vhcrlwInRi2R9fSf/2HdtxoLUVB1pb4WluXpV+ymDrhetcdZgtzGJ8fJw2iEQi9OGbNm1yAGfVZTKZeXWWWLrqXHUgxLYdBoE1pubdvJd7yvUU4hf78c7bfZBlGbIsQxRFiKIIQRCgKAolw0qCMeutn67bo3dHsWHDBkS7opCHZAiCgOnpaYdnEI/HaYzB6/UiEolQ9sbjcdrWXgcAjY2NyOfzePFHL+JC7Dwezc2hWJxDUS2iWFShWXEJXYOu6TQIX75T+zaGK2mw5/adf6OmZgM+G/kMod+E6LYwHA6v6qWtAAkAnH37LH66ZzfminOYKxahFosoqmUAVwj4fNsD7iwAeqTj9bXA7XYDAKLR6DwXqRqZmZmhq67b7TYD8VZoUodu2mLLXDyuwgKATnRomnGOdqa3hwLk9/sdMd5qwPX5fLRv+5vtZoBdK4FsC1HSRZY8XkdGdHEHQDoiHWTsXopk7qfJq7981VrqiSiKJJ1Ok+VKLpcjoijS/pJfIpn7aTJ2L0V6ento+XcolW7Cb4TInfQYyXyeIZJfouWCIJDu7m4yPT29ILDT09Oku7ubCIJA++3YuYOMf54hdzJjpCPS8V0ElzDlTmlnpAP7/RJ4nseFvgv46PJHKz4yip7phqqqGB1N4fXXXl/5FLOZDftphn33WX6/Vs+w36/KRNhTZ6TDYPL9NBlIfEDcbveyR8ztdpP4n+Mkcz9N7mTGyHt/eW/VLCCELJq3l61W/1LPXDWDLQm/EcLRXxylpxBKchhXr1xd9Nh+n7QPXm8LPWu7cuUqzkbPrn6RqMCutWJu+TMqnfethsXMYvvWrdu2oDPShfofuG2nEfZwIxx+q/WPJ1OTk3j3fAwjwyNrswrbQFxr07DQsxZ75poBbMmull3Ys3cPtm3fhu+7XM4YrulafVUo4O6du7hx7caaAftNMXgpG7/uAD+RlQtDCNnIMMx/n0CxDhsMQpj/DQDwRbusfJXB0QAAAABJRU5ErkJggg==", -"cc-by-nd":"data:image/png;base64,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", -"cc-by-nc":"data:image/png;base64,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", -"cc-by-nc-sa":"data:image/png;base64,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", -"cc-by-nc-nd":"data:image/png;base64,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"}, -d;return{_setup:function(a){var c="",f=a.license&&b[a.license.toLowerCase()];a._container=document.createElement("div");a._container.style.display="none";d=document.getElementById(a.target);if(a.nameofworkurl)c+="<a href='"+a.nameofworkurl+"' target=_blank>";if(a.nameofwork)c+=a.nameofwork;if(a.nameofworkurl)c+="</a>";if(a.copyrightholderurl)c+="<a href='"+a.copyrightholderurl+"' target=_blank>";if(a.copyrightholder)c+=", "+a.copyrightholder;if(a.copyrightholderurl)c+="</a>";if(c==="")c=a.text;if(a.license)if(f)c= -a.licenseurl?"<a href='"+a.licenseurl+"' target=_blank><img src='"+f+"' border='0'/></a> "+c:"<img src='"+f+"' />"+c;else{c+=", license: ";c+=a.licenseurl?"<a href='"+a.licenseurl+"' target=_blank>"+a.license+"</a> ":a.license}else if(a.licenseurl)c+=", <a href='"+a.licenseurl+"' target=_blank>license</a> ";a._container.innerHTML=c;if(!d&&g.plugin.debug)throw Error("target container doesn't exist");d&&d.appendChild(a._container)},start:function(a,c){c._container.style.display="inline"},end:function(a, -c){c._container.style.display="none"},_teardown:function(a){(d=document.getElementById(a.target))&&d.removeChild(a._container)}}}(),{about:{name:"Popcorn Attribution Plugin",version:"0.2",author:"@rwaldron",website:"github.com/rwldrn"},options:{start:{elem:"input",type:"text",label:"In"},end:{elem:"input",type:"text",label:"Out"},nameofwork:{elem:"input",type:"text",label:"Name of Work"},nameofworkurl:{elem:"input",type:"url",label:"Url of Work",optional:true},copyrightholder:{elem:"input",type:"text", -label:"Copyright Holder"},copyrightholderurl:{elem:"input",type:"url",label:"Copyright Holder Url",optional:true},license:{elem:"input",type:"text",label:"License type"},licenseurl:{elem:"input",type:"url",label:"License URL",optional:true},target:"attribution-container"}})})(Popcorn);(function(g){g.plugin("code",function(b){var d=false,a=function(){var c=function(f){return function(h){var p=function(){d&&h();d&&f(p)};p()}};return window.webkitRequestAnimationFrame?c(window.webkitRequestAnimationFrame):window.mozRequestAnimationFrame?c(window.mozRequestAnimationFrame):c(function(f){window.setTimeout(f,16)})}();if(!b.onStart||typeof b.onStart!=="function"){if(g.plugin.debug)throw Error("Popcorn Code Plugin Error: onStart must be a function.");b.onStart=g.nop}if(b.onEnd&&typeof b.onEnd!== -"function"){if(g.plugin.debug)throw Error("Popcorn Code Plugin Error: onEnd must be a function.");b.onEnd=undefined}if(b.onFrame&&typeof b.onFrame!=="function"){if(g.plugin.debug)throw Error("Popcorn Code Plugin Error: onFrame must be a function.");b.onFrame=undefined}return{start:function(c,f){f.onStart(f);if(f.onFrame){d=true;a(f.onFrame,f)}},end:function(c,f){if(f.onFrame)d=false;f.onEnd&&f.onEnd(f)}}},{about:{name:"Popcorn Code Plugin",version:"0.1",author:"David Humphrey (@humphd)",website:"http://vocamus.net/dave"}, -options:{start:{elem:"input",type:"text",label:"In"},end:{elem:"input",type:"text",label:"Out"},onStart:{elem:"input",type:"function",label:"onStart"},onFrame:{elem:"input",type:"function",label:"onFrame",optional:true},onEnd:{elem:"input",type:"function",label:"onEnd"}}})})(Popcorn);(function(g){var b=0;g.plugin("flickr",function(d){var a,c=document.getElementById(d.target),f,h,p,o,l=d.numberofimages||4,i=d.height||"50px",r=d.width||"50px",k=d.padding||"5px",j=d.border||"0px";a=document.createElement("div");a.id="flickr"+b;a.style.width="100%";a.style.height="100%";a.style.display="none";b++;if(!c&&g.plugin.debug)throw Error("flickr target container doesn't exist");c&&c.appendChild(a);var v=function(){if(f)setTimeout(function(){v()},5);else{h="http://api.flickr.com/services/rest/?method=flickr.people.findByUsername&"; -h+="username="+d.username+"&api_key="+d.apikey+"&format=json&jsoncallback=flickr";g.getJSONP(h,function(n){f=n.user.nsid;w()})}},w=function(){h="http://api.flickr.com/services/feeds/photos_public.gne?";if(f)h+="id="+f+"&";if(d.tags)h+="tags="+d.tags+"&";h+="lang=en-us&format=json&jsoncallback=flickr";g.xhr.getJSONP(h,function(n){var y=document.createElement("div");y.innerHTML="<p style='padding:"+k+";'>"+n.title+"<p/>";g.forEach(n.items,function(C,e){if(e<l){p=document.createElement("a");p.setAttribute("href", -C.link);p.setAttribute("target","_blank");o=document.createElement("img");o.setAttribute("src",C.media.m);o.setAttribute("height",i);o.setAttribute("width",r);o.setAttribute("style","border:"+j+";padding:"+k);p.appendChild(o);y.appendChild(p)}else return false});a.appendChild(y)})};if(d.username&&d.apikey)v();else{f=d.userid;w()}return{start:function(){a.style.display="inline"},end:function(){a.style.display="none"},_teardown:function(n){document.getElementById(n.target)&&document.getElementById(n.target).removeChild(a)}}}, -{about:{name:"Popcorn Flickr Plugin",version:"0.2",author:"Scott Downe, Steven Weerdenburg, Annasob",website:"http://scottdowne.wordpress.com/"},options:{start:{elem:"input",type:"number",label:"In"},end:{elem:"input",type:"number",label:"Out"},userid:{elem:"input",type:"text",label:"UserID",optional:true},tags:{elem:"input",type:"text",label:"Tags"},username:{elem:"input",type:"text",label:"Username",optional:true},apikey:{elem:"input",type:"text",label:"Api_key",optional:true},target:"flickr-container", -height:{elem:"input",type:"text",label:"Height",optional:true},width:{elem:"input",type:"text",label:"Width",optional:true},padding:{elem:"input",type:"text",label:"Padding",optional:true},border:{elem:"input",type:"text",label:"Border",optional:true},numberofimages:{elem:"input",type:"text",label:"Number of Images"}}})})(Popcorn);(function(g){g.forEach(["footnote","text"],function(b){g.plugin(b,{manifest:{about:{name:"Popcorn "+b+" Plugin",version:"0.2",author:"@annasob, @rwaldron",website:"annasob.wordpress.com"},options:{start:{elem:"input",type:"text",label:"In"},end:{elem:"input",type:"text",label:"Out"},text:{elem:"input",type:"text",label:"Text"},target:b+"-container"}},_setup:function(d){var a=document.getElementById(d.target);d._container=document.createElement("div");d._container.style.display="none";d._container.innerHTML= -d.text;if(!a&&g.plugin.debug)throw Error("target container doesn't exist");a&&a.appendChild(d._container)},start:function(d,a){a._container.style.display="inline"},end:function(d,a){a._container.style.display="none"},_teardown:function(d){document.getElementById(d.target)&&document.getElementById(d.target).removeChild(d._container)}})})})(Popcorn);(function(g,b){var d=false;g.plugin("facebook",{manifest:{about:{name:"Popcorn Facebook Plugin",version:"0.1",author:"Dan Ventura, Matthew Schranz: @mjschranz",website:"dsventura.blogspot.com, mschranz.wordpress.com"},options:{type:{elem:"select",options:["LIKE","LIKE-BOX","ACTIVITY","FACEPILE","LIVE-STREAM","SEND","COMMENTS"],label:"Type"},target:"facebook-container",start:{elem:"input",type:"number",label:"In"},end:{elem:"input",type:"number",label:"Out"},font:{elem:"input",type:"text",label:"font", -optional:true},xid:{elem:"input",type:"text",label:"Xid",optional:true},href:{elem:"input",type:"url",label:"Href",optional:true},site:{elem:"input",type:"url",label:"Site",optional:true},height:{elem:"input",type:"text",label:"Height",optional:true},width:{elem:"input",type:"text",label:"Width",optional:true},action:{elem:"select",options:["like","recommend"],label:"Action",optional:true},stream:{elem:"select",options:["false","true"],label:"Stream",optional:true},header:{elem:"select",options:["false", -"true"],label:"Header",optional:true},layout:{elem:"select",options:["standard","button_count","box_count"],label:"Layout",optional:true},max_rows:{elem:"input",type:"text",label:"Max_rows",optional:true},border_color:{elem:"input",type:"text",label:"Border_color",optional:true},event_app_id:{elem:"input",type:"text",label:"Event_app_id",optional:true},colorscheme:{elem:"select",options:["light","dark"],label:"Colorscheme",optional:true},show_faces:{elem:"select",options:["false","true"],label:"Showfaces", -optional:true},recommendations:{elem:"select",options:["false","true"],label:"Recommendations",optional:true},always_post_to_friends:{elem:"input",options:["false","true"],label:"Always_post_to_friends",optional:true},num_posts:{elem:"input",type:"text",label:"Number_of_Comments",optional:true}}},_setup:function(a){var c=document.getElementById(a.target),f=a.type;if(!document.getElementById("fb-root")){var h=document.createElement("div");h.setAttribute("id","fb-root");document.body.appendChild(h)}if(!d|| -a.event_app_id){d=true;g.getScript("//connect.facebook.net/en_US/all.js");b.fbAsyncInit=function(){FB.init({appId:a.event_app_id||"",status:true,cookie:true,xfbml:true})}}f=f.toLowerCase();if(!(["like","like-box","activity","facepile","live-stream","send","comments"].indexOf(f)>-1))throw Error("Facebook plugin type was invalid.");a._container=document.createElement("div");a._container.id="facebookdiv-"+g.guid();a._facebookdiv=document.createElement("fb:"+f);a._container.appendChild(a._facebookdiv); -a._container.style.display="none";f=f==="activity"?"site":"href";a._facebookdiv.setAttribute(f,a[f]||document.URL);f="width height layout show_faces stream header colorscheme maxrows border_color recommendations font always_post_to_friends xid num_posts".split(" ");g.forEach(f,function(p){a[p]!=null&&a._facebookdiv.setAttribute(p,a[p])});if(!c&&g.plugin.debug)throw Error("Facebook target container doesn't exist");c&&c.appendChild(a._container)},start:function(a,c){c._container.style.display=""},end:function(a, -c){c._container.style.display="none"},_teardown:function(a){var c=document.getElementById(a.target);c&&c.removeChild(a._container)}})})(Popcorn,this);var googleCallback; -(function(g){var b=1,d=false,a=false,c,f;googleCallback=function(h){if(typeof google!=="undefined"&&google.maps&&google.maps.Geocoder&&google.maps.LatLng){c=new google.maps.Geocoder;a=true}else setTimeout(function(){googleCallback(h)},1)};f=function(){if(document.body){d=true;g.getScript("//maps.google.com/maps/api/js?sensor=false&callback=googleCallback")}else setTimeout(function(){f()},1)};g.plugin("googlemap",function(h){var p,o,l,i=document.getElementById(h.target);d||f();p=document.createElement("div"); -p.id="actualmap"+b;p.style.width="100%";p.style.height="100%";b++;if(!i&&g.plugin.debug)throw Error("target container doesn't exist");i&&i.appendChild(p);var r=function(){if(a)if(h.location)c.geocode({address:h.location},function(k,j){if(j===google.maps.GeocoderStatus.OK){h.lat=k[0].geometry.location.lat();h.lng=k[0].geometry.location.lng();l=new google.maps.LatLng(h.lat,h.lng);o=new google.maps.Map(p,{mapTypeId:google.maps.MapTypeId[h.type]||google.maps.MapTypeId.HYBRID});o.getDiv().style.display= -"none"}});else{l=new google.maps.LatLng(h.lat,h.lng);o=new google.maps.Map(p,{mapTypeId:google.maps.MapTypeId[h.type]||google.maps.MapTypeId.HYBRID});o.getDiv().style.display="none"}else setTimeout(function(){r()},5)};r();return{start:function(k,j){var v=this,w,n=function(){if(o){o.getDiv().style.display="block";google.maps.event.trigger(o,"resize");o.setCenter(l);if(j.zoom&&typeof j.zoom!=="number")j.zoom=+j.zoom;j.zoom=j.zoom||8;o.setZoom(j.zoom);if(j.heading&&typeof j.heading!=="number")j.heading= -+j.heading;if(j.pitch&&typeof j.pitch!=="number")j.pitch=+j.pitch;if(j.type==="STREETVIEW"){o.setStreetView(w=new google.maps.StreetViewPanorama(p,{position:l,pov:{heading:j.heading=j.heading||0,pitch:j.pitch=j.pitch||0,zoom:j.zoom}}));var y=function(u,x){var A=google.maps.geometry.spherical.computeHeading;setTimeout(function(){var B=v.media.currentTime;if(typeof j.tween==="object"){for(var D=0,E=u.length;D<E;D++){var z=u[D];if(B>=z.interval*(D+1)/1E3&&(B<=z.interval*(D+2)/1E3||B>=z.interval*E/1E3)){s.setPosition(new google.maps.LatLng(z.position.lat, -z.position.lng));s.setPov({heading:z.pov.heading||A(z,u[D+1])||0,zoom:z.pov.zoom||0,pitch:z.pov.pitch||0})}}y(u,u[0].interval)}else{D=0;for(E=u.length;D<E;D++){z=j.interval;if(B>=z*(D+1)/1E3&&(B<=z*(D+2)/1E3||B>=z*E/1E3)){C.setPov({heading:A(u[D],u[D+1])||0,zoom:j.zoom,pitch:j.pitch||0});C.setPosition(e[D])}}y(e,j.interval)}},x)};if(j.location&&typeof j.tween==="string"){var C=w,e=[],m=new google.maps.DirectionsService,q=new google.maps.DirectionsRenderer(C);m.route({origin:j.location,destination:j.tween, -travelMode:google.maps.TravelMode.DRIVING},function(u,x){if(x==google.maps.DirectionsStatus.OK){q.setDirections(u);for(var A=u.routes[0].overview_path,B=0,D=A.length;B<D;B++)e.push(new google.maps.LatLng(A[B].lat(),A[B].lng()));j.interval=j.interval||1E3;y(e,10)}})}else if(typeof j.tween==="object"){var s=w;m=0;for(var t=j.tween.length;m<t;m++){j.tween[m].interval=j.tween[m].interval||1E3;y(j.tween,10)}}}}else setTimeout(function(){n()},13)};n()},end:function(){if(o)o.getDiv().style.display="none"}, -_teardown:function(k){(k=document.getElementById(k.target))&&k.removeChild(p);p=o=l=null}}},{about:{name:"Popcorn Google Map Plugin",version:"0.1",author:"@annasob",website:"annasob.wordpress.com"},options:{start:{elem:"input",type:"text",label:"In"},end:{elem:"input",type:"text",label:"Out"},target:"map-container",type:{elem:"select",options:["ROADMAP","SATELLITE","STREETVIEW","HYBRID","TERRAIN"],label:"Type",optional:true},zoom:{elem:"input",type:"text",label:"Zoom",optional:true},lat:{elem:"input", -type:"text",label:"Lat",optional:true},lng:{elem:"input",type:"text",label:"Lng",optional:true},location:{elem:"input",type:"text",label:"Location"},heading:{elem:"input",type:"text",label:"Heading",optional:true},pitch:{elem:"input",type:"text",label:"Pitch",optional:true}}})})(Popcorn);(function(g){g.plugin("image",{manifest:{about:{name:"Popcorn image Plugin",version:"0.1",author:"Scott Downe",website:"http://scottdowne.wordpress.com/"},options:{start:{elem:"input",type:"number",label:"In"},end:{elem:"input",type:"number",label:"Out"},href:{elem:"input",type:"url",label:"anchor URL",optional:true},target:"image-container",src:{elem:"input",type:"url",label:"Source URL"},text:{elem:"input",type:"text",label:"TEXT",optional:true}}},_setup:function(b){var d=document.createElement("img"), -a=document.getElementById(b.target);b.anchor=document.createElement("a");b.anchor.style.position="relative";b.anchor.style.textDecoration="none";b.anchor.style.display="none";if(!a&&g.plugin.debug)throw Error("target container doesn't exist");a&&a.appendChild(b.anchor);d.addEventListener("load",function(){d.style.borderStyle="none";b.anchor.href=b.href||b.src||"#";b.anchor.target="_blank";var c,f;if(b.text){c=d.height/12+"px";f=document.createElement("div");g.extend(f.style,{color:"black",fontSize:c, -fontWeight:"bold",position:"relative",textAlign:"center",width:d.width+"px",zIndex:"10"});f.innerHTML=b.text||"";f.style.top=d.height/2-f.offsetHeight/2+"px";b.anchor.appendChild(f)}b.anchor.appendChild(d)},false);d.src=b.src},start:function(b,d){d.anchor.style.display="inline"},end:function(b,d){d.anchor.style.display="none"},_teardown:function(b){document.getElementById(b.target)&&document.getElementById(b.target).removeChild(b.anchor)}})})(Popcorn);(function(g){var b=function(d){var a=0,c=0,f=0,h=null,p=null,o=false,l=0,i=function(){},r=function(){d.background(0);c=f=0;h=p=null};d.draw=function(){i()};d.setup=function(){};d.construct=function(k,j,v){var w=function(){if(j){l=j.gml.tag.drawing.stroke;var n=(v.end-v.start)/(l.pt||function(C){for(var e=[],m=0,q=C.length;m<q;m++)e=e.concat(C[m].pt);return e}(l)).length,y=j.gml.tag;y=y.header&&y.header.client&&y.header.client.name;o=y==="Graffiti Analysis 2.0: DustTag"||y==="DustTag: Graffiti Analysis 2.0"|| -y==="Fat Tag - Katsu Edition";i=function(){if(k.currentTime<v.endDrawing){var C=(k.currentTime-v.start)/n;for(C<c&&r();c<=C;){if(!l)break;a=l[f]||l;var e=a.pt[c],m=c;if(h!=null){var q=e.x,s=e.y,t=void 0,u=void 0,x=void 0,A=void 0;if(o){t=p*d.height;u=d.width-h*d.width;x=s*d.height;A=d.width-q*d.width}else{t=h*d.width;u=p*d.height;x=q*d.width;A=s*d.height}d.stroke(0);d.strokeWeight(13);d.strokeCap(d.SQUARE);d.line(t,u,x,A);d.stroke(255);d.strokeWeight(12);d.strokeCap(d.ROUND);d.line(t,u,x,A)}h=e.x; -p=e.y;c===m&&c++}}}}else setTimeout(w,5)};d.size(640,640);d.frameRate(60);d.smooth();r();d.noLoop();w()}};g.plugin("gml",{_setup:function(d){var a=this,c=document.getElementById(d.target);d.endDrawing=d.endDrawing||d.end;d.container=document.createElement("canvas");d.container.style.display="none";d.container.setAttribute("id","canvas"+d.gmltag);if(!c&&g.plugin.debug)throw Error("target container doesn't exist");c&&c.appendChild(d.container);c=function(){g.getJSONP("//000000book.com/data/"+d.gmltag+ -".json?callback=",function(f){d.pjsInstance=new Processing(d.container,b);d.pjsInstance.construct(a.media,f,d);d._running&&d.pjsInstance.loop()},false)};window.Processing?c():g.getScript("//cloud.github.com/downloads/processing-js/processing-js/processing-1.3.6.min.js",c)},start:function(d,a){a.pjsInstance&&a.pjsInstance.loop();a.container.style.display="block"},end:function(d,a){a.pjsInstance&&a.pjsInstance.noLoop();a.container.style.display="none"},_teardown:function(d){d.pjsInstance&&d.pjsInstance.exit(); -document.getElementById(d.target)&&document.getElementById(d.target).removeChild(d.container)}})})(Popcorn);(function(g){var b={},d=function(a){if(a.artist){var c="";c="<h3>"+a.artist.name+"</h3>";c+="<a href='"+a.artist.url+"' target='_blank' style='float:left;margin:0 10px 0 0;'><img src='"+a.artist.image[2]["#text"]+"' alt=''></a>";c+="<p>"+a.artist.bio.summary+"</p>";c+="<hr /><p><h4>Tags</h4><ul>";g.forEach(a.artist.tags.tag,function(f){c+="<li><a href='"+f.url+"'>"+f.name+"</a></li>"});c+="</ul></p>";c+="<hr /><p><h4>Similar</h4><ul>";g.forEach(a.artist.similar.artist,function(f){c+="<li><a href='"+ -f.url+"'>"+f.name+"</a></li>"});c+="</ul></p>";b[a.artist.name.toLowerCase()].htmlString=c}};g.plugin("lastfm",function(){return{_setup:function(a){a._container=document.createElement("div");a._container.style.display="none";a._container.innerHTML="";a.artist=a.artist&&a.artist.toLowerCase()||"";var c=document.getElementById(a.target);if(!c&&g.plugin.debug)throw Error("target container doesn't exist");c&&c.appendChild(a._container);if(!b[a.artist]){b[a.artist]={count:0,htmlString:"Unknown Artist"}; -g.getJSONP("//ws.audioscrobbler.com/2.0/?method=artist.getinfo&artist="+a.artist+"&api_key="+a.apikey+"&format=json&callback=lastFMcallback",d,false)}b[a.artist].count++},start:function(a,c){c._container.innerHTML=b[c.artist].htmlString;c._container.style.display="inline"},end:function(a,c){c._container.style.display="none";c._container.innerHTML=""},_teardown:function(a){--b[a.artist].count||delete b[a.artist];document.getElementById(a.target)&&document.getElementById(a.target).removeChild(a._container)}}}(), -{about:{name:"Popcorn LastFM Plugin",version:"0.1",author:"Steven Weerdenburg",website:"http://sweerdenburg.wordpress.com/"},options:{start:{elem:"input",type:"text",label:"In"},end:{elem:"input",type:"text",label:"Out"},target:"lastfm-container",artist:{elem:"input",type:"text",label:"Artist"}}})})(Popcorn);(function(g){g.plugin("lowerthird",{manifest:{about:{name:"Popcorn lowerthird Plugin",version:"0.1",author:"Scott Downe",website:"http://scottdowne.wordpress.com/"},options:{start:{elem:"input",type:"text",label:"In"},end:{elem:"input",type:"text",label:"Out"},target:"lowerthird-container",salutation:{elem:"input",type:"text",label:"Text",optional:true},name:{elem:"input",type:"text",label:"Text"},role:{elem:"input",type:"text",label:"Text",optional:true}}},_setup:function(b){var d=document.getElementById(b.target); -if(!this.container){this.container=document.createElement("div");this.container.style.position="absolute";this.container.style.color="white";this.container.style.textShadow="black 2px 2px 6px";this.container.style.fontSize="24px";this.container.style.fontWeight="bold";this.container.style.paddingLeft="40px";this.container.style.width=this.video.offsetWidth+"px";this.container.style.left=this.position().left+"px";this.video.parentNode.appendChild(this.container)}if(b.target&&b.target!=="lowerthird-container"){b.container= -document.createElement("div");if(!d&&g.plugin.debug)throw Error("target container doesn't exist");d&&d.appendChild(b.container)}else b.container=this.container},start:function(b,d){d.container.innerHTML=(d.salutation?d.salutation+" ":"")+d.name+(d.role?"<br />"+d.role:"");this.container.style.top=this.position().top+this.video.offsetHeight-(40+this.container.offsetHeight)+"px"},end:function(b,d){for(;d.container.firstChild;)d.container.removeChild(d.container.firstChild)}})})(Popcorn);(function(g){var b=1,d=false;g.plugin("googlefeed",function(a){var c=function(){var o=false,l=0,i=document.getElementsByTagName("link"),r=i.length,k=document.head||document.getElementsByTagName("head")[0],j=document.createElement("link");if(window.GFdynamicFeedControl)d=true;else g.getScript("//www.google.com/uds/solutions/dynamicfeed/gfdynamicfeedcontrol.js",function(){d=true});for(;l<r;l++)if(i[l].href==="//www.google.com/uds/solutions/dynamicfeed/gfdynamicfeedcontrol.css")o=true;if(!o){j.type= -"text/css";j.rel="stylesheet";j.href="//www.google.com/uds/solutions/dynamicfeed/gfdynamicfeedcontrol.css";k.insertBefore(j,k.firstChild)}};window.google?c():g.getScript("//www.google.com/jsapi",function(){google.load("feeds","1",{callback:function(){c()}})});var f=document.createElement("div"),h=document.getElementById(a.target),p=function(){if(d)a.feed=new GFdynamicFeedControl(a.url,f,{vertical:a.orientation.toLowerCase()==="vertical"?true:false,horizontal:a.orientation.toLowerCase()==="horizontal"? -true:false,title:a.title=a.title||"Blog"});else setTimeout(function(){p()},5)};if(!a.orientation||a.orientation.toLowerCase()!=="vertical"&&a.orientation.toLowerCase()!=="horizontal")a.orientation="vertical";f.style.display="none";f.id="_feed"+b;f.style.width="100%";f.style.height="100%";b++;if(!h&&g.plugin.debug)throw Error("target container doesn't exist");h&&h.appendChild(f);p();return{start:function(){f.setAttribute("style","display:inline")},end:function(){f.setAttribute("style","display:none")}, -_teardown:function(o){document.getElementById(o.target)&&document.getElementById(o.target).removeChild(f);delete o.feed}}},{about:{name:"Popcorn Google Feed Plugin",version:"0.1",author:"David Seifried",website:"dseifried.wordpress.com"},options:{start:{elem:"input",type:"text",label:"In"},end:{elem:"input",type:"text",label:"Out"},target:"feed-container",url:{elem:"input",type:"url",label:"url"},title:{elem:"input",type:"text",label:"title",optional:true},orientation:{elem:"select",options:["Vertical", -"Horizontal"],label:"orientation",optional:true}}})})(Popcorn);(function(g){var b={},d={},a={};g.plugin("rdio",function(){var c=function(f){var h=function(p){return"http://www.rdio.com/api/oembed/?format=json&url=http://www.rdio.com/%23"+{playlist:function(){return"/people/"+f.person+"/playlists/"+f.id+"/"},album:function(){return"/artist/"+f.artist+"/album/"}}[p]()+f[p]+"/&callback=_loadResults"}(f.type);g.getJSONP(h,function(p){var o=p.title,l=p.html;if(p&&o&&l)b[f.containerid].htmlString="<div>"+l+"</div>";else if(g.plugin.debug)throw Error("Did not receive data from server."); -},false)};return{_setup:function(f){var h=f.containerid=g.guid(),p=d[h]=document.createElement("div"),o=a[h]=document.getElementById(f.target);if(!o&&g.plugin.debug)throw Error("Target container could not be found.");p.style.display="none";p.innerHTML="";o.appendChild(p);b[h]={htmlString:f.playlist||"Unknown Source"||f.album||"Unknown Source"};c(f)},start:function(f,h){var p=h.containerid,o=d[p];o.innerHTML=b[p].htmlString;o.style.display="inline"},end:function(f,h){container=d[h.containerid];container.style.display= -"none";container.innerHTML=""},_teardown:function(f){f=f.containerid;var h=a[f];b[f]&&delete b[f];h&&h.removeChild(d[f]);delete a[f];delete d[f]}}}(),{manifest:{about:{name:"Popcorn Rdio Plugin",version:"0.1",author:"Denise Rigato"},options:{start:{elem:"input",type:"text",label:"In"},end:{elem:"input",type:"text",label:"Out"},target:"rdio",artist:{elem:"input",type:"text",label:"Artist"},album:{elem:"input",type:"text",label:"Album"},person:{elem:"input",type:"text",label:"Person"},id:{elem:"input", -type:"text",label:"Id"},playlist:{elem:"input",type:"text",label:"Playlist"}}}})})(Popcorn);(function(g){var b=0,d=function(a,c){var f=a.container=document.createElement("div"),h=f.style,p=a.media,o=function(){var l=a.position();h.fontSize="18px";h.width=p.offsetWidth+"px";h.top=l.top+p.offsetHeight-f.offsetHeight-40+"px";h.left=l.left+"px";setTimeout(o,10)};f.id=c||g.guid();h.position="absolute";h.color="white";h.textShadow="black 2px 2px 6px";h.fontWeight="bold";h.textAlign="center";o();a.media.parentNode.appendChild(f);return f};g.plugin("subtitle",{manifest:{about:{name:"Popcorn Subtitle Plugin", -version:"0.1",author:"Scott Downe",website:"http://scottdowne.wordpress.com/"},options:{start:{elem:"input",type:"text",label:"In"},end:{elem:"input",type:"text",label:"Out"},target:"subtitle-container",text:{elem:"input",type:"text",label:"Text"}}},_setup:function(a){var c=document.createElement("div");c.id="subtitle-"+b++;c.style.display="none";!this.container&&(!a.target||a.target==="subtitle-container")&&d(this);a.container=a.target&&a.target!=="subtitle-container"?document.getElementById(a.target)|| -d(this,a.target):this.container;document.getElementById(a.container.id)&&document.getElementById(a.container.id).appendChild(c);a.innerContainer=c;a.showSubtitle=function(){a.innerContainer.innerHTML=a.text||""}},start:function(a,c){c.innerContainer.style.display="inline";c.showSubtitle(c,c.text)},end:function(a,c){c.innerContainer.style.display="none";c.innerContainer.innerHTML=""},_teardown:function(a){a.container.removeChild(a.innerContainer)}})})(Popcorn);(function(g){var b=[],d=function(){this.name="";this.contains={};this.toString=function(){var a=[],c;for(c in this.contains)this.contains.hasOwnProperty(c)&&a.push(" "+this.contains[c]);return a.toString()}};g.plugin("tagthisperson",function(){return{_setup:function(a){var c=false;if(!document.getElementById(a.target)&&g.plugin.debug)throw Error("target container doesn't exist");for(var f=0;f<b.length;f++)if(b[f].name===a.target){a._p=b[f];c=true;break}if(!c){a._p=new d;a._p.name=a.target;b.push(a._p)}}, -start:function(a,c){c._p.contains[c.person]=c.image?"<img src='"+c.image+"'/> ":"";c._p.contains[c.person]+=c.href?"<a href='"+c.href+"' target='_blank'> "+c.person+"</a>":c.person;document.getElementById(c.target).innerHTML=c._p.toString()},end:function(a,c){delete c._p.contains[c.person];document.getElementById(c.target).innerHTML=c._p.toString()}}}(),{about:{name:"Popcorn tagthisperson Plugin",version:"0.1",author:"@annasob",website:"annasob.wordpress.com"},options:{start:{elem:"input",type:"text", -label:"In"},end:{elem:"input",type:"text",label:"Out"},target:"tagthisperson-container",person:{elem:"input",type:"text",label:"Name"},image:{elem:"input",type:"url",label:"Image Src",optional:true},href:{elem:"input",type:"url",label:"URL",optional:true}}})})(Popcorn);(function(g){var b=false;g.plugin("twitter",{manifest:{about:{name:"Popcorn Twitter Plugin",version:"0.1",author:"Scott Downe",website:"http://scottdowne.wordpress.com/"},options:{start:{elem:"input",type:"number",label:"In"},end:{elem:"input",type:"number",label:"Out"},src:{elem:"input",type:"text",label:"Source"},target:"twitter-container",height:{elem:"input",type:"number",label:"Height",optional:true},width:{elem:"input",type:"number",label:"Width",optional:true}}},_setup:function(d){if(!window.TWTR&& -!b){b=true;g.getScript("//widgets.twimg.com/j/2/widget.js")}var a=document.getElementById(d.target);d.container=document.createElement("div");d.container.setAttribute("id",g.guid());d.container.style.display="none";if(!a&&g.plugin.debug)throw Error("target container doesn't exist");a&&a.appendChild(d.container);var c=d.src||"";a=d.width||250;var f=d.height||200,h=/^@/.test(c),p={version:2,id:d.container.getAttribute("id"),rpp:30,width:a,height:f,interval:6E3,theme:{shell:{background:"#ffffff",color:"#000000"}, -tweets:{background:"#ffffff",color:"#444444",links:"#1985b5"}},features:{loop:true,timestamp:true,avatars:true,hashtags:true,toptweets:true,live:true,scrollbar:false,behavior:"default"}},o=function(l){if(window.TWTR)if(h){p.type="profile";(new TWTR.Widget(p)).render().setUser(c).start()}else{p.type="search";p.search=c;p.subject=c;(new TWTR.Widget(p)).render().start()}else setTimeout(function(){o(l)},1)};o(this)},start:function(d,a){a.container.style.display="inline"},end:function(d,a){a.container.style.display= -"none"},_teardown:function(d){document.getElementById(d.target)&&document.getElementById(d.target).removeChild(d.container)}})})(Popcorn);(function(g){g.plugin("webpage",{manifest:{about:{name:"Popcorn Webpage Plugin",version:"0.1",author:"@annasob",website:"annasob.wordpress.com"},options:{id:{elem:"input",type:"text",label:"Id",optional:true},start:{elem:"input",type:"text",label:"In"},end:{elem:"input",type:"text",label:"Out"},src:{elem:"input",type:"url",label:"Src"},target:"iframe-container"}},_setup:function(b){var d=document.getElementById(b.target);b.src=b.src.replace(/^(https?:)?(\/\/)?/,"//");b._iframe=document.createElement("iframe"); -b._iframe.setAttribute("width","100%");b._iframe.setAttribute("height","100%");b._iframe.id=b.id;b._iframe.src=b.src;b._iframe.style.display="none";if(!d&&g.plugin.debug)throw Error("target container doesn't exist");d&&d.appendChild(b._iframe)},start:function(b,d){d._iframe.src=d.src;d._iframe.style.display="inline"},end:function(b,d){d._iframe.style.display="none"},_teardown:function(b){document.getElementById(b.target)&&document.getElementById(b.target).removeChild(b._iframe)}})})(Popcorn);var wikiCallback; -(function(g){g.plugin("wikipedia",{manifest:{about:{name:"Popcorn Wikipedia Plugin",version:"0.1",author:"@annasob",website:"annasob.wordpress.com"},options:{start:{elem:"input",type:"text",label:"In"},end:{elem:"input",type:"text",label:"Out"},lang:{elem:"input",type:"text",label:"Language",optional:true},src:{elem:"input",type:"url",label:"Src"},title:{elem:"input",type:"text",label:"Title",optional:true},numberofwords:{elem:"input",type:"text",label:"Num Of Words",optional:true},target:"wikipedia-container"}}, -_setup:function(b){var d,a=g.guid();if(!b.lang)b.lang="en";b.numberofwords=b.numberofwords||200;window["wikiCallback"+a]=function(c){b._link=document.createElement("a");b._link.setAttribute("href",b.src);b._link.setAttribute("target","_blank");b._link.innerHTML=b.title||c.parse.displaytitle;b._desc=document.createElement("p");d=c.parse.text["*"].substr(c.parse.text["*"].indexOf("<p>"));d=d.replace(/((<(.|\n)+?>)|(\((.*?)\) )|(\[(.*?)\]))/g,"");d=d.split(" ");b._desc.innerHTML=d.slice(0,d.length>= -b.numberofwords?b.numberofwords:d.length).join(" ")+" ...";b._fired=true};if(b.src)g.getScript("//"+b.lang+".wikipedia.org/w/api.php?action=parse&props=text&page="+b.src.slice(b.src.lastIndexOf("/")+1)+"&format=json&callback=wikiCallback"+a);else if(g.plugin.debug)throw Error("Wikipedia plugin needs a 'src'");},start:function(b,d){var a=function(){if(d._fired){if(d._link&&d._desc)if(document.getElementById(d.target)){document.getElementById(d.target).appendChild(d._link);document.getElementById(d.target).appendChild(d._desc); -d._added=true}}else setTimeout(function(){a()},13)};a()},end:function(b,d){if(d._added){document.getElementById(d.target).removeChild(d._link);document.getElementById(d.target).removeChild(d._desc)}},_teardown:function(b){if(b._added){b._link.parentNode&&document.getElementById(b.target).removeChild(b._link);b._desc.parentNode&&document.getElementById(b.target).removeChild(b._desc);delete b.target}}})})(Popcorn);(function(g){var b={text:function(d){var a=d.post,c=document.createElement("a"),f=document.createTextNode(a.title),h=document.createElement("div");c.setAttribute("href",a.post_url);c.appendChild(f);h.appendChild(c);h.innerHTML+=a.body;d._container.appendChild(h)},photo:function(d){for(var a=d.width||250,c=-1,f=[d.post.photos.length],h=[d.post.photos.length],p=document.createElement("div"),o=document.createElement("img"),l=d.post,i=0,r=l.photos.length;i<r;i++){for(var k=l.photos[i],j=k.alt_sizes,v= -0,w=j.length;v<w;v++){var n=j[v];if(n.width===a){h[i]=n.url;f[i]=k.caption;c=0;break}else if(n.width===250)c=v}c===-1&&g.error("Clearly your blog has a picture that is so tiny it isn't even 250px wide. Consider using a bigger picture or try a smaller size.");if(v===j.length)h[i]=j[c].url}a=0;for(c=h.length;a<c;a++){p.innerHTML+=f[a]+"<br/>";o.setAttribute("src",h[a]);o.setAttribute("alt","Pic"+a);p.appendChild(o);p.innerHTML+="<br/>"}p.innerHTML+="<br/>"+l.caption;d._container.appendChild(p)},audio:function(d){var a= -document.createElement("div"),c=document.createElement("a"),f=d.post;if(f.artist){var h=document.createElement("img");a.innerHTML+="Artist: "+f.artist+"<br/>";c.setAttribute("href",f.source_url);h.setAttribute("src",f.album_art);h.setAttribute("alt",f.album);c.appendChild(h);a.appendChild(c);a.innerHTML+="<hr/>"+f.track_number+" - "+f.track_name+"<br/>"}else{h=document.createTextNode(f.source_title);c.setAttribute("href",f.source_url);c.appendChild(h);a.appendChild(c);a.innerHTML+="<br/>"}a.innerHTML+= -f.player+" "+f.plays+"plays<br/>"+f.caption;d._container.appendChild(a)},video:function(d){for(var a=d.width||400,c=-1,f=d.post,h=document.createElement("div"),p,o=0,l=f.player.length;o<l;o++){var i=f.player[o];if(i.width===a){p=i.embed_code;c=0;break}else if(i.width===400)c=o}if(o===d.post.player.length)p=f.player[c].embed_code;c===-1&&g.error("Specified video size was not found and default was never found. Please try another width.");h.innerHTML+=p+"<br/>"+f.caption;d._container.appendChild(h)}, -chat:function(d){var a=d.post,c,f=document.createElement("div");f.innerHTML+="<strong><u>"+a.title+"</u></strong><br/><br/>";for(var h=0,p=a.dialogue.length;h<p;h++){c=a.dialogue[h];f.innerHTML+=c.label+" "+c.phrase+"<br/>"}d._container.appendChild(f)},quote:function(d){var a=document.createElement("div"),c=document.createElement("a"),f=d.post,h=document.createTextNode(f.text);c.setAttribute("href",f.post_url);c.appendChild(h);a.appendChild(c);a.innerHTML+="<br/><br/>Source: <b>"+f.source+"</b>"; -d._container.appendChild(a)},link:function(d){var a=document.createElement("div"),c=document.createElement("a"),f=d.post,h=document.createTextNode(f.title);c.setAttribute("href",f.post_url);c.appendChild(h);a.appendChild(c);a.innerHTML+="<br/>"+f.description;d._container.appendChild(a)},answer:function(d){var a=document.createElement("div"),c=document.createElement("a"),f=d.post,h=document.createTextNode(f.asking_name);a.innerHTML="Inquirer: ";c.setAttribute("href",f.asking_url);c.appendChild(h); -a.appendChild(c);a.innerHTML+="<br/><br/>Question: "+f.question+"<br/>Answer: "+f.answer;d._container.appendChild(a)}};g.plugin("tumblr",{manifest:{about:{name:"Popcorn Tumblr Plugin",version:"0.1",author:"Matthew Schranz, @mjschranz",website:"mschranz.wordpress.com"},options:{requestType:{elem:"select",options:["INFO","AVATAR","BLOGPOST"],label:"Type_Of_Plugin"},target:"tumblr-container",start:{elem:"input",type:"number",label:"Start_Time"},end:{elem:"input",type:"number",label:"End_Time"},base_hostname:{elem:"input", -type:"text",label:"User_Name"},api_key:{elem:"input",type:"text",label:"Application_Key",optional:true},size:{elem:"select",options:[16,24,30,40,48,64,96,128,512],label:"avatarSize",optional:true},blogId:{elem:"input",type:"number",label:"Blog_ID",optional:true},width:{elem:"input",type:"number",label:"Photo_Width",optional:true}}},_setup:function(d){var a=document.getElementById(d.target),c,f,h=this;d.requestType=d.requestType.toLowerCase();(!d.base_hostname||!d.api_key&&(d.requestType==="info"|| -d.requestType==="blogpost"))&&g.error("Must provide a blog URL to the plugin and an api_key for Blog Info and Blog Post requests.");!(["info","avatar","blogpost"].indexOf(d.requestType)>-1)&&g.error("Invalid tumblr plugin type.");d.requestType==="blogpost"&&d.blogId===undefined&&g.error("Error. BlogId required for blogpost requests");!a&&g.plugin.debug&&g.error("Target Tumblr container doesn't exist.");c=d.base_hostname.slice(d.base_hostname.indexOf("/")+2,d.base_hostname.length);f=d.base_hostname.slice(0, -d.base_hostname.indexOf("/")+2);c=f==="http://"||f==="https://"?c:d.base_hostname;if(c.indexOf("/")>-1)c=c.slice(0,c.indexOf("/"));d.base_hostname=c;d._container=document.createElement("div");d._container.id="tumblrdiv-"+g.guid();if(d.requestType==="avatar")d._container.innerHTML="<img src=http://api.tumblr.com/v2/blog/"+d.base_hostname+"/avatar/"+d.size+" alt='BlogAvatar' />";else{c=d.requestType==="blogpost"?"posts":"info";c="http://api.tumblr.com/v2/blog/"+d.base_hostname+"/"+c+"?api_key="+d.api_key+ -"&id="+d.blogId+"&jsonp=tumblrCallBack";this.listen("tumblrError",function(p){g.error(p)});g.getJSONP(c,function(p){if(p.meta.msg==="OK"){var o=document.createElement("div");if(d.requestType==="blogpost"){d.post=p.response.posts[0];var l=d.post.type;p=d.post.tags;o.innerHTML="Date Published: "+d.post.date.slice(0,d.post.date.indexOf(" "))+"<br/>";if(p.length!==0){o.innerHTML+="Tags: "+p[0];for(var i=1,r=p.length;i<r;i++)o.innerHTML+=", "+p[i]}else o.innerHTML+="Tags: No Tags Used";d._container.appendChild(o); -b[l](d)}else{l=document.createElement("a");p=p.response.blog;i=document.createTextNode(p.title);l.setAttribute("href",p.url);l.appendChild(i);o.appendChild(l);o.innerHTML+=p.description;d._container.appendChild(o)}}else h.trigger("tumblrError","Error. Request failed. Status code: "+p.meta.status+" - Message: "+p.meta.msg)},false)}d._container.style.display="none";a&&a.appendChild(d._container)},start:function(d,a){if(a._container)a._container.style.display=""},end:function(d,a){if(a._container)a._container.style.display= -"none"},_teardown:function(d){document.getElementById(d.target)&&document.getElementById(d.target).removeChild(d._container)}})})(Popcorn,this);(function(g){g.plugin("linkedin",{manifest:{about:{name:"Popcorn LinkedIn Plugin",version:"0.1",author:"Dan Ventura",website:"dsventura.blogspot.com"},options:{type:{elem:"input",type:"text",label:"Type"},url:{elem:"input",type:"text",label:"URL"},apikey:{elem:"input",type:"text",label:"API Key"},counter:{elem:"input",type:"text",label:"Counter"},memberid:{elem:"input",type:"text",label:"Member ID",optional:true},format:{elem:"input",type:"text",label:"Format",optional:true},companyid:{elem:"input", -type:"text",label:"Company ID",optional:true},modules:{elem:"input",type:"text",label:"Modules",optional:true},productid:{elem:"input",type:"text",label:"productid",optional:true},related:{elem:"input",type:"text",label:"related",optional:true},start:{elem:"input",type:"text",label:"In"},end:{elem:"input",type:"text",label:"Out"},target:"linkedin-container"}},_setup:function(b){var d=b.apikey,a=document.getElementById(b.target),c=document.createElement("script");g.getScript("//platform.linkedin.com/in.js"); -b._container=document.createElement("div");b._container.appendChild(c);if(d)c.innerHTML="api_key: "+d;b.type=b.type&&b.type.toLowerCase()||"";var f=function(h){return{share:function(){c.setAttribute("type","IN/Share");h.counter&&c.setAttribute("data-counter",h.counter);h.url&&c.setAttribute("data-url",h.url)},memberprofile:function(){c.setAttribute("type","IN/MemberProfile");c.setAttribute("data-id",h.memberid);c.setAttribute("data-format",h.format||"inline");h.text&&h.format.toLowerCase()!=="inline"&& -c.setAttribute("data-text",h.text)},companyinsider:function(){c.setAttribute("type","IN/CompanyInsider");c.setAttribute("data-id",h.companyid);h.modules&&h._container.setAttribute("data-modules",h.modules)},companyprofile:function(){c.setAttribute("type","IN/CompanyProfile");c.setAttribute("data-id",h.companyid);c.setAttribute("data-format",h.format||"inline");h.text&&h.format.toLowerCase()!=="inline"&&c.setAttribute("data-text",h.text);h.related!==undefined&&c.setAttribute("data-related",h.related)}, -recommendproduct:function(){c.setAttribute("type","IN/RecommendProduct");c.setAttribute("data-company",h.companyid||"LinkedIn");c.setAttribute("data-product",h.productid||"201714");h.counter&&c.setAttribute("data-counter",h.counter)}}}(b);if(d)f[b.type]&&f[b.type]();else{b._container=document.createElement("p");b._container.innerHTML="Plugin requires a valid <a href='https://www.linkedin.com/secure/developer'>apikey</a>";if(!a&&g.plugin.debug)throw"target container doesn't exist";a&&a.appendChild(b._container)}if(!a&& -g.plugin.debug)throw Error("target container doesn't exist");a&&a.appendChild(b._container);b._container.style.display="none"},start:function(b,d){d._container.style.display="block"},end:function(b,d){d._container.style.display="none"},_teardown:function(b){var d=document.getElementById(b.target);d&&d.removeChild(b._container)}})})(Popcorn);(function(g){g.plugin("mustache",function(b){var d,a,c,f;g.getScript("http://mustache.github.com/extras/mustache.js");var h=!!b.dynamic,p=typeof b.template,o=typeof b.data,l=document.getElementById(b.target);if(!l&&g.plugin.debug)throw Error("target container doesn't exist");b.container=l||document.createElement("div");if(p==="function")if(h)c=b.template;else f=b.template(b);else if(p==="string")f=b.template;else if(g.plugin.debug)throw Error("Mustache Plugin Error: options.template must be a String or a Function."); -else f="";if(o==="function")if(h)d=b.data;else a=b.data(b);else if(o==="string")a=JSON.parse(b.data);else if(o==="object")a=b.data;else if(g.plugin.debug)throw Error("Mustache Plugin Error: options.data must be a String, Object, or Function.");else a="";return{start:function(i,r){var k=function(){if(window.Mustache){if(d)a=d(r);if(c)f=c(r);var j=Mustache.to_html(f,a).replace(/^\s*/mg,"");r.container.innerHTML=j}else setTimeout(function(){k()},10)};k()},end:function(i,r){r.container.innerHTML=""}, -_teardown:function(){d=a=c=f=null}}},{about:{name:"Popcorn Mustache Plugin",version:"0.1",author:"David Humphrey (@humphd)",website:"http://vocamus.net/dave"},options:{start:{elem:"input",type:"text",label:"In"},end:{elem:"input",type:"text",label:"Out"},target:"mustache-container",template:{elem:"input",type:"text",label:"Template"},data:{elem:"input",type:"text",label:"Data"},dynamic:{elem:"input",type:"text",label:"Dynamic",optional:true}}})})(Popcorn);(function(g){function b(a,c){if(a.map)a.map.div.style.display=c;else setTimeout(function(){b(a,c)},10)}var d=1;g.plugin("openmap",function(a){var c,f,h,p,o,l,i,r,k=document.getElementById(a.target);c=document.createElement("div");c.id="openmapdiv"+d;c.style.width="100%";c.style.height="100%";d++;if(!k&&g.plugin.debug)throw Error("target container doesn't exist");k&&k.appendChild(c);r=function(){if(window.OpenLayers){if(a.location){location=new OpenLayers.LonLat(0,0);g.getJSONP("//tinygeocoder.com/create-api.php?q="+ -a.location+"&callback=jsonp",function(v){f=new OpenLayers.LonLat(v[1],v[0]);a.map.setCenter(f)})}else f=new OpenLayers.LonLat(a.lng,a.lat);a.type=a.type||"ROADMAP";if(a.type==="SATELLITE"){a.map=new OpenLayers.Map({div:c,maxResolution:0.28125,tileSize:new OpenLayers.Size(512,512)});var j=new OpenLayers.Layer.WorldWind("LANDSAT","//worldwind25.arc.nasa.gov/tile/tile.aspx",2.25,4,{T:"105"});a.map.addLayer(j);p=new OpenLayers.Projection("EPSG:4326");h=new OpenLayers.Projection("EPSG:4326")}else if(a.type=== -"TERRAIN"){p=new OpenLayers.Projection("EPSG:4326");h=new OpenLayers.Projection("EPSG:4326");a.map=new OpenLayers.Map({div:c,projection:h});j=new OpenLayers.Layer.WMS("USGS Terraserver","//terraserver-usa.org/ogcmap.ashx?",{layers:"DRG"});a.map.addLayer(j)}else{h=new OpenLayers.Projection("EPSG:900913");p=new OpenLayers.Projection("EPSG:4326");f=f.transform(p,h);a.map=new OpenLayers.Map({div:c,projection:h,displayProjection:p});j=new OpenLayers.Layer.OSM;a.map.addLayer(j)}if(a.map)a.map.div.style.display= -"none"}else setTimeout(function(){r()},50)};r();return{_setup:function(j){window.OpenLayers||g.getScript("//openlayers.org/api/OpenLayers.js");var v=function(){if(j.map){j.zoom=j.zoom||2;if(j.zoom&&typeof j.zoom!=="number")j.zoom=+j.zoom;j.map.setCenter(f,j.zoom);if(j.markers){var w=OpenLayers.Util.extend({},OpenLayers.Feature.Vector.style["default"]),n=function(u){clickedFeature=u.feature;if(clickedFeature.attributes.text){i=new OpenLayers.Popup.FramedCloud("featurePopup",clickedFeature.geometry.getBounds().getCenterLonLat(), -new OpenLayers.Size(120,250),clickedFeature.attributes.text,null,true,function(){l.unselect(this.feature)});clickedFeature.popup=i;i.feature=clickedFeature;j.map.addPopup(i)}},y=function(u){feature=u.feature;if(feature.popup){i.feature=null;j.map.removePopup(feature.popup);feature.popup.destroy();feature.popup=null}},C=function(u){g.getJSONP("//tinygeocoder.com/create-api.php?q="+u.location+"&callback=jsonp",function(x){x=(new OpenLayers.Geometry.Point(x[1],x[0])).transform(p,h);var A=OpenLayers.Util.extend({}, -w);if(!u.size||isNaN(u.size))u.size=14;A.pointRadius=u.size;A.graphicOpacity=1;A.externalGraphic=u.icon;x=new OpenLayers.Feature.Vector(x,null,A);if(u.text)x.attributes={text:u.text};o.addFeatures([x])})};o=new OpenLayers.Layer.Vector("Point Layer",{style:w});j.map.addLayer(o);for(var e=0,m=j.markers.length;e<m;e++){var q=j.markers[e];if(q.text)if(!l){l=new OpenLayers.Control.SelectFeature(o);j.map.addControl(l);l.activate();o.events.on({featureselected:n,featureunselected:y})}if(q.location)C(q); -else{var s=(new OpenLayers.Geometry.Point(q.lng,q.lat)).transform(p,h),t=OpenLayers.Util.extend({},w);if(!q.size||isNaN(q.size))q.size=14;t.pointRadius=q.size;t.graphicOpacity=1;t.externalGraphic=q.icon;s=new OpenLayers.Feature.Vector(s,null,t);if(q.text)s.attributes={text:q.text};o.addFeatures([s])}}}}else setTimeout(function(){v()},13)};v()},start:function(j,v){b(v,"block")},end:function(j,v){b(v,"none")},_teardown:function(){k&&k.removeChild(c);c=map=f=h=p=o=l=i=null}}},{about:{name:"Popcorn OpenMap Plugin", -version:"0.3",author:"@mapmeld",website:"mapadelsur.blogspot.com"},options:{start:{elem:"input",type:"text",label:"In"},end:{elem:"input",type:"text",label:"Out"},target:"map-container",type:{elem:"select",options:["ROADMAP","SATELLITE","TERRAIN"],label:"Type",optional:true},zoom:{elem:"input",type:"text",label:"Zoom",optional:true},lat:{elem:"input",type:"text",label:"Lat",optional:true},lng:{elem:"input",type:"text",label:"Lng",optional:true},location:{elem:"input",type:"text",label:"Location"}, -markers:{elem:"input",type:"text",label:"List Markers",optional:true}}})})(Popcorn);document.addEventListener("click",function(g){g=g.target;if(g.nodeName==="A"||g.parentNode&&g.parentNode.nodeName==="A")Popcorn.instances.forEach(function(b){b.options.pauseOnLinkClicked&&b.pause()})},false);(function(g){var b={},d=0,a=document.createElement("span"),c=["webkit","Moz","ms","O",""],f=["Transform","TransitionDuration","TransitionTimingFunction"],h={},p;document.getElementsByTagName("head")[0].appendChild(a);for(var o=0,l=f.length;o<l;o++)for(var i=0,r=c.length;i<r;i++){p=c[i]+f[o];if(p in a.style){h[f[o].toLowerCase()]=p;break}}document.getElementsByTagName("head")[0].appendChild(a);g.plugin("wordriver",{manifest:{about:{name:"Popcorn WordRiver Plugin"},options:{start:{elem:"input",type:"text", -label:"In"},end:{elem:"input",type:"text",label:"Out"},target:"wordriver-container",text:{elem:"input",type:"text",label:"Text"},color:{elem:"input",type:"text",label:"Color",optional:true}}},_setup:function(k){if(!document.getElementById(k.target)&&g.plugin.debug)throw Error("target container doesn't exist");k._duration=k.end-k.start;var j;if(!(j=b[k.target])){j=k.target;b[j]=document.createElement("div");var v=document.getElementById(j);v&&v.appendChild(b[j]);b[j].style.height="100%";b[j].style.position= -"relative";j=b[j]}k._container=j;k.word=document.createElement("span");k.word.style.position="absolute";k.word.style.whiteSpace="nowrap";k.word.style.opacity=0;k.word.style.MozTransitionProperty="opacity, -moz-transform";k.word.style.webkitTransitionProperty="opacity, -webkit-transform";k.word.style.OTransitionProperty="opacity, -o-transform";k.word.style.transitionProperty="opacity, transform";k.word.style[h.transitionduration]="1s, "+k._duration+"s";k.word.style[h.transitiontimingfunction]="linear"; -k.word.innerHTML=k.text;k.word.style.color=k.color||"black"},start:function(k,j){j._container.appendChild(j.word);j.word.style[h.transform]="";j.word.style.fontSize=~~(30+20*Math.random())+"px";d%=j._container.offsetWidth-j.word.offsetWidth;j.word.style.left=d+"px";d+=j.word.offsetWidth+10;j.word.style[h.transform]="translateY("+(j._container.offsetHeight-j.word.offsetHeight)+"px)";j.word.style.opacity=1;setTimeout(function(){j.word.style.opacity=0},(j.end-j.start-1||1)*1E3)},end:function(k,j){j.word.style.opacity= -0},_teardown:function(k){var j=document.getElementById(k.target);k.word.parentNode&&k._container.removeChild(k.word);b[k.target]&&!b[k.target].childElementCount&&j&&j.removeChild(b[k.target])&&delete b[k.target]}})})(Popcorn);(function(g){g.plugin("processing",function(b){var d=function(a){function c(f){var h=function(){a.listen("pause",function(){f.canvas.style.display==="inline"&&f.pjsInstance.noLoop()});a.listen("play",function(){f.canvas.style.display==="inline"&&f.pjsInstance.loop()})};if(f.sketch)g.xhr({url:f.sketch,dataType:"text",success:function(p){f.codeReady=false;p=Processing.compile(p);f.pjsInstance=new Processing(f.canvas,p);f.seeking=false;f._running&&!a.media.paused&&f.pjsInstance.loop()||f.pjsInstance.noLoop(); -a.listen("seeking",function(){f._running&&f.canvas.style.display==="inline"&&f.noPause&&f.pjsInstance.loop()});f.noPause=f.noPause||false;!f.noPause&&h();f.codeReady=true}});else if(g.plugin.debug)throw Error("Popcorn.Processing: options.sketch is undefined");}window.Processing?c(b):g.getScript("//cloud.github.com/downloads/processing-js/processing-js/processing-1.3.6.min.js",function(){c(b)})};return{_setup:function(a){a.codeReady=false;a.parentTarget=document.getElementById(a.target);if(!a.parentTarget&& -g.plugin.debug)throw Error("target container doesn't exist");var c=document.createElement("canvas");c.id=g.guid(a.target+"-sketch");c.style.display="none";a.canvas=c;a.parentTarget&&a.parentTarget.appendChild(a.canvas);d(this)},start:function(a,c){c.codeReady&&!this.media.paused&&c.pjsInstance.loop();c.canvas.style.display="inline"},end:function(a,c){c.pjsInstance&&c.pjsInstance.noLoop();c.canvas.style.display="none"},_teardown:function(a){a.pjsInstance&&a.pjsInstance.exit();a.parentTarget&&a.parentTarget.removeChild(a.canvas)}}}, -{about:{name:"Popcorn Processing Plugin",version:"0.1",author:"Christopher De Cairos, Benjamin Chalovich",website:"cadecairos.blogspot.com, ben1amin.wordpress.org"},options:{start:{elem:"input",type:"text",label:"In"},end:{elem:"input",type:"text",label:"Out"},target:{elem:"input",type:"text",label:"Target"},sketch:{elem:"input",type:"url",label:"Sketch"},noPause:{elem:"select",options:["TRUE","FALSE"],label:"No Loop",optional:true}}})})(Popcorn);(function(g){var b=1;g.plugin("timeline",function(d){var a=document.getElementById(d.target),c=document.createElement("div"),f,h=true;if(a&&!a.firstChild){a.appendChild(f=document.createElement("div"));f.style.width="380px";f.style.height="600px";f.style.overflow="auto"}else f=a.firstChild;c.style.display="none";c.id="timelineDiv"+b;d.direction=d.direction||"up";if(d.direction.toLowerCase()==="down")h=false;if(a&&f)h?f.insertBefore(c,f.firstChild):f.appendChild(c);else if(g.plugin.debug)throw Error("target container doesn't exist"); -b++;c.innerHTML="<p><span id='big' style='font-size:24px; line-height: 130%;' >"+d.title+"</span><br /><span id='mid' style='font-size: 16px;'>"+d.text+"</span><br />"+d.innerHTML;return{start:function(p,o){c.style.display="block";if(o.direction==="down")f.scrollTop=f.scrollHeight},end:function(){c.style.display="none"},_teardown:function(){f&&c&&f.removeChild(c)&&!f.firstChild&&a.removeChild(f)}}},{about:{name:"Popcorn Timeline Plugin",version:"0.1",author:"David Seifried @dcseifried",website:"dseifried.wordpress.com"}, -options:{start:{elem:"input",type:"text",label:"In"},end:{elem:"input",type:"text",label:"Out"},target:"feed-container",title:{elem:"input",type:"text",label:"title"},text:{elem:"input",type:"text",label:"text"},innerHTML:{elem:"input",type:"text",label:"innerHTML",optional:true},direction:{elem:"input",type:"text",label:"direction",optional:true}}})})(Popcorn);(function(g,b){var d={};g.plugin("documentcloud",{manifest:{about:{name:"Popcorn Document Cloud Plugin",version:"0.1",author:"@humphd, @ChrisDeCairos",website:"http://vocamus.net/dave"},options:{start:{elem:"input",type:"text",label:"In"},end:{elem:"input",type:"text",label:"Out"},target:"documentcloud-container",width:{elem:"input",type:"text",label:"Width",optional:true},height:{elem:"input",type:"text",label:"Height",optional:true},src:{elem:"input",type:"text",label:"PDF URL"},preload:{elem:"input", -type:"boolean",label:"Preload",optional:true},page:{elem:"input",type:"number",label:"Page Number",optional:true},aid:{elem:"input",type:"number",label:"Annotation Id",optional:true}}},_setup:function(a){function c(){function i(u){a._key=u.api.getId();a._changeView=function(x){a.aid?x.pageSet.showAnnotation(x.api.getAnnotation(a.aid)):x.api.setCurrentPage(a.page)}}function r(){d[a._key]={num:1,id:a._containerId};h.loaded=true}h.loaded=false;var k=a.url.replace(/\.html$/,".js"),j=a.target,v=b.getElementById(j), -w=b.createElement("div"),n=g.position(v),y=a.width||n.width;n=a.height||n.height;var C=a.sidebar||true,e=a.text||true,m=a.pdf||true,q=a.showAnnotations||true,s=a.zoom||700,t=a.search||true;if(!function(u){var x=false;g.forEach(h.viewers,function(A){if(A.api.getSchema().canonicalURL===u){i(A);A=d[a._key];a._containerId=A.id;A.num+=1;x=true;h.loaded=true}});return x}(a.url)){w.id=a._containerId=g.guid(j);j="#"+w.id;v.appendChild(w);p.trigger("documentready");h.load(k,{width:y,height:n,sidebar:C,text:e, -pdf:m,showAnnotations:q,zoom:s,search:t,container:j,afterLoad:a.page||a.aid?function(u){i(u);a._changeView(u);w.style.visibility="hidden";u.elements.pages.hide();r()}:function(u){i(u);r();w.style.visibility="hidden";u.elements.pages.hide()}})}}function f(){window.DV.loaded?c():setTimeout(f,25)}var h=window.DV=window.DV||{},p=this;if(h.loading)f();else{h.loading=true;h.recordHit="//www.documentcloud.org/pixel.gif";var o=b.createElement("link"),l=b.getElementsByTagName("head")[0];o.rel="stylesheet"; -o.type="text/css";o.media="screen";o.href="//s3.documentcloud.org/viewer/viewer-datauri.css";l.appendChild(o);h.loaded=false;g.getScript("http://s3.documentcloud.org/viewer/viewer.js",function(){h.loading=false;c()})}},start:function(a,c){var f=b.getElementById(c._containerId),h=DV.viewers[c._key];(c.page||c.aid)&&h&&c._changeView(h);if(f&&h){f.style.visibility="visible";h.elements.pages.show()}},end:function(a,c){var f=b.getElementById(c._containerId);if(f&&DV.viewers[c._key]){f.style.visibility= -"hidden";DV.viewers[c._key].elements.pages.hide()}},_teardown:function(a){var c=b.getElementById(a._containerId);if((a=a._key)&&DV.viewers[a]&&--d[a].num===0){for(DV.viewers[a].api.unload();c.hasChildNodes();)c.removeChild(c.lastChild);c.parentNode.removeChild(c)}}})})(Popcorn,window.document);(function(g){g.parser("parseJSON","JSON",function(b){var d={title:"",remote:"",data:[]};g.forEach(b.data,function(a){d.data.push(a)});return d})})(Popcorn);(function(g){g.parser("parseSBV",function(b){var d={title:"",remote:"",data:[]},a=[],c=0,f=0,h=function(k){k=k.split(":");var j=k.length-1,v;try{v=parseInt(k[j-1],10)*60+parseFloat(k[j],10);if(j===2)v+=parseInt(k[0],10)*3600}catch(w){throw"Bad cue";}return v},p=function(k,j){var v={};v[k]=j;return v};b=b.text.split(/(?:\r\n|\r|\n)/gm);for(f=b.length;c<f;){var o={},l=[],i=b[c++].split(",");try{o.start=h(i[0]);for(o.end=h(i[1]);c<f&&b[c];)l.push(b[c++]);o.text=l.join("<br />");a.push(p("subtitle",o))}catch(r){for(;c< -f&&b[c];)c++}for(;c<f&&!b[c];)c++}d.data=a;return d})})(Popcorn);(function(g){g.parser("parseSRT",function(b){var d={title:"",remote:"",data:[]},a=[],c=0,f=0,h=0,p,o,l,i=function(k){k=k.split(":");try{var j=k[2].split(",");if(j.length===1)j=k[2].split(".");return parseFloat(k[0],10)*3600+parseFloat(k[1],10)*60+parseFloat(j[0],10)+parseFloat(j[1],10)/1E3}catch(v){return 0}},r=function(k,j){var v={};v[k]=j;return v};b=b.text.split(/(?:\r\n|\r|\n)/gm);f=b.length;for(c=0;c<f;c++){l={};o=[];l.id=parseInt(b[c++],10);p=b[c++].split(/[\t ]*--\>[\t ]*/);l.start=i(p[0]); -h=p[1].indexOf(" ");if(h!==-1)p[1]=p[1].substr(0,h);for(l.end=i(p[1]);c<f&&b[c];)o.push(b[c++]);l.text=o.join("\\N").replace(/\{(\\[\w]+\(?([\w\d]+,?)+\)?)+\}/gi,"");l.text=l.text.replace(/</g,"<").replace(/>/g,">");l.text=l.text.replace(/<(\/?(font|b|u|i|s))((\s+(\w|\w[\w\-]*\w)(\s*=\s*(?:\".*?\"|'.*?'|[^'\">\s]+))?)+\s*|\s*)(\/?)>/gi,"<$1$3$7>");l.text=l.text.replace(/\\N/gi,"<br />");a.push(r("subtitle",l))}d.data=a;return d})})(Popcorn);(function(g){function b(c,f){var h=c.substr(10).split(","),p;p={start:d(h[f.start]),end:d(h[f.end])};if(p.start===-1||p.end===-1)throw"Invalid time";var o=k.call(i,/\{(\\[\w]+\(?([\w\d]+,?)+\)?)+\}/gi,""),l=o.replace,i;i=h.length;k=[];for(var r=f.text;r<i;r++)k.push(h[r]);i=k.join(",");var k=i.replace;p.text=l.call(o,/\\N/gi,"<br />");return p}function d(c){var f=c.split(":");if(c.length!==10||f.length<3)return-1;return parseInt(f[0],10)*3600+parseInt(f[1],10)*60+parseFloat(f[2],10)}function a(c, -f){var h={};h[c]=f;return h}g.parser("parseSSA",function(c){var f={title:"",remote:"",data:[]},h=[],p=0,o;c=c.text.split(/(?:\r\n|\r|\n)/gm);for(o=c.length;p<o&&c[p]!=="[Events]";)p++;var l=c[++p].substr(8).split(", "),i={},r,k;k=0;for(r=l.length;k<r;k++)if(l[k]==="Start")i.start=k;else if(l[k]==="End")i.end=k;else if(l[k]==="Text")i.text=k;for(;++p<o&&c[p]&&c[p][0]!=="[";)try{h.push(a("subtitle",b(c[p],i)))}catch(j){}f.data=h;return f})})(Popcorn);(function(g){g.parser("parseTTML",function(b){var d={title:"",remote:"",data:[]},a,c=0,f,h=function(l,i){if(!l)return-1;var r=l.split(":"),k=r.length-1;if(k>=2)return parseInt(r[0],10)*3600+parseInt(r[k-1],10)*60+parseFloat(r[k],10);for(r=l.length-1;r>=0;r--)if(l[r]<="9"&&l[r]>="0")break;r++;k=l.substr(r);i=i||0;if(k==="h")k=3600;else if(k==="m")k=60;else if(k==="s")k=1;else if(k==="ms")k=0.0010;else return-1;return parseFloat(l.substr(0,r))*k+i},p=function(l,i){var r={};r[l]=i;return r},o=function(l, -i){for(var r=l.firstChild,k;r;){if(r.nodeType===1)if(r.nodeName==="p"){k=r;var j=i,v={};v.text=k.textContent.replace(/^[\s]+|[\s]+$/gm,"").replace(/(?:\r\n|\r|\n)/gm,"<br />");v.id=k.getAttribute("xml:id")||k.getAttribute("id");v.start=h(k.getAttribute("begin"),j);v.end=h(k.getAttribute("end"),j);v.target=f;if(v.end<0){v.end=h(k.getAttribute("duration"),0);if(v.end>=0)v.end+=v.start;else v.end=Number.MAX_VALUE}k=v;d.data.push(p("subtitle",k));c++}else if(r.nodeName==="div"){k=h(r.getAttribute("begin")); -if(k<0)k=i;o(r,k)}r=r.nextSibling}};if(!b.xml||!b.xml.documentElement||!(a=b.xml.documentElement.firstChild))return d;for(;a.nodeName!=="body";)a=a.nextSibling;f="";o(a,0);return d})})(Popcorn);(function(g){g.parser("parseTTXT",function(b){var d={title:"",remote:"",data:[]},a=function(o){o=o.split(":");var l=0;try{return parseFloat(o[0],10)*60*60+parseFloat(o[1],10)*60+parseFloat(o[2],10)}catch(i){l=0}return l},c=function(o,l){var i={};i[o]=l;return i};b=b.xml.lastChild.lastChild;for(var f=Number.MAX_VALUE,h=[];b;){if(b.nodeType===1&&b.nodeName==="TextSample"){var p={};p.start=a(b.getAttribute("sampleTime"));p.text=b.getAttribute("text");if(p.text){p.end=f-0.0010;h.push(c("subtitle",p))}f= -p.start}b=b.previousSibling}d.data=h.reverse();return d})})(Popcorn);(function(g){function b(a){var c=a.split(":");a=a.length;var f;if(a!==12&&a!==9)throw"Bad cue";a=c.length-1;try{f=parseInt(c[a-1],10)*60+parseFloat(c[a],10);if(a===2)f+=parseInt(c[0],10)*3600}catch(h){throw"Bad cue";}return f}function d(a,c){var f={};f[a]=c;return f}g.parser("parseVTT",function(a){var c={title:"",remote:"",data:[]},f=[],h=0,p=0,o,l;a=a.text.split(/(?:\r\n|\r|\n)/gm);p=a.length;if(p===0||a[0]!=="WEBVTT")return c;for(h++;h<p;){o=[];try{for(var i=h;i<p&&!a[i];)i++;h=i;var r=a[h++];i= -void 0;var k={};if(!r||r.indexOf("--\>")===-1)throw"Bad cue";i=r.replace(/--\>/," --\> ").split(/[\t ]+/);if(i.length<2)throw"Bad cue";k.id=r;k.start=b(i[0]);k.end=b(i[2]);for(l=k;h<p&&a[h];)o.push(a[h++]);l.text=o.join("<br />");f.push(d("subtitle",l))}catch(j){for(h=h;h<p&&a[h];)h++;h=h}}c.data=f;return c})})(Popcorn);(function(g){g.parser("parseXML","XML",function(b){var d={title:"",remote:"",data:[]},a={},c=function(i){i=i.split(":");if(i.length===1)return parseFloat(i[0],10);else if(i.length===2)return parseFloat(i[0],10)+parseFloat(i[1]/12,10);else if(i.length===3)return parseInt(i[0]*60,10)+parseFloat(i[1],10)+parseFloat(i[2]/12,10);else if(i.length===4)return parseInt(i[0]*3600,10)+parseInt(i[1]*60,10)+parseFloat(i[2],10)+parseFloat(i[3]/12,10)},f=function(i){for(var r={},k=0,j=i.length;k<j;k++){var v=i.item(k).nodeName, -w=i.item(k).nodeValue;if(v==="in")r.start=c(w);else if(v==="out")r.end=c(w);else if(v==="resourceid")g.extend(r,a[w]);else r[v]=w}return r},h=function(i,r){var k={};k[i]=r;return k},p=function(i,r,k){var j={};g.extend(j,r,f(i.attributes),{text:i.textContent});r=i.childNodes;if(r.length<1||r.length===1&&r[0].nodeType===3)if(k)a[j.id]=j;else d.data.push(h(i.nodeName,j));else for(i=0;i<r.length;i++)r[i].nodeType===1&&p(r[i],j,k)};b=b.documentElement.childNodes;for(var o=0,l=b.length;o<l;o++)if(b[o].nodeType=== -1)b[o].nodeName==="manifest"?p(b[o],{},true):p(b[o],{},false);return d})})(Popcorn);(function(g,b){function d(l,i){if(l.currentStyle)return l.currentStyle[i];else if(b.getComputedStyle)return document.defaultView.getComputedStyle(l,null).getPropertyValue(i)}function a(l){return'<div><a href="'+l.user.profile+'"><img width="16px height="16px" src="'+l.user.avatar+'"></img>'+l.user.name+"</a> at "+function(i){var r=h(i/60);i=p(i%60);return r+"."+(i<10?"0":"")+i}(l.start)+" "+function(i){function r(k,j){return k+" "+j+(k>1?"s":"")+" ago"}i=((new Date).getTime()-i.getTime())/1E3;if(i< -60)return r(p(i),"second");i/=60;if(i<60)return r(p(i),"minute");i/=60;if(i<24)return r(p(i),"hour");i/=24;if(i<30)return r(p(i),"day");if(i<365)return r(p(i/30),"month");return r(p(i/365),"year")}(l.date)+"<br />"+l.text+"</span>"}function c(l){if(b.swfobject&&b.soundcloud){var i={enable_api:true,object_id:l._playerId,url:l.src,show_comments:!l._options.api.key&&!l._options.api.commentdiv},r={id:l._playerId,name:l._playerId},k=document.createElement("div");k.setAttribute("id",l._playerId);l._container.appendChild(k); -swfobject.embedSWF("http://player.soundcloud.com/player.swf",l._playerId,l.offsetWidth,l.height,"9.0.0","expressInstall.swf",i,{allowscriptaccess:"always",wmode:"transparent"},r)}else setTimeout(function(){c(l)},15)}var f=Math.abs,h=Math.floor,p=Math.round,o={};g.soundcloud=function(l,i,r){g.getScript("http://ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js");g.getScript("http://popcornjs.org/code/players/soundcloud/lib/soundcloud.player.api.js",function(){var k=1;soundcloud.addEventListener("onPlayerReady", -function(j,v){var w=o[j.api_getFlashId()];w.swfObj=j;w.duration=j.api_getTrackDuration();w.currentTime=j.api_getTrackPosition();w.volume=w.previousVolume=j.api_getVolume()/100;w._mediaId=v.mediaId;w.dispatchEvent("load");w.dispatchEvent("canplay");w.dispatchEvent("durationchange");w.timeupdate()});soundcloud.addEventListener("onMediaStart",function(j){j=o[j.api_getFlashId()];j.played=1;j.dispatchEvent("playing")});soundcloud.addEventListener("onMediaPlay",function(j){if(k)k=0;else o[j.api_getFlashId()].dispatchEvent("play")}); -soundcloud.addEventListener("onMediaPause",function(j){o[j.api_getFlashId()].dispatchEvent("pause")});soundcloud.addEventListener("onMediaBuffering",function(j){j=o[j.api_getFlashId()];j.dispatchEvent("progress");if(j.readyState===0){j.readyState=3;j.dispatchEvent("readystatechange")}});soundcloud.addEventListener("onMediaDoneBuffering",function(j){o[j.api_getFlashId()].dispatchEvent("canplaythrough")});soundcloud.addEventListener("onMediaEnd",function(j){j=o[j.api_getFlashId()];j.paused=1;j.dispatchEvent("ended")}); -soundcloud.addEventListener("onMediaSeek",function(j){var v=o[j.api_getFlashId()];v.setCurrentTime(j.api_getTrackPosition());v.paused&&v.dispatchEvent("timeupdate")});soundcloud.addEventListener("onPlayerError",function(j){o[j.api_getFlashId()].dispatchEvent("error")})});return new g.soundcloud.init(l,i,r)};g.soundcloud.init=function(){function l(i){var r=i._options,k=i._container,j=k.getBoundingClientRect();i.width=r.width||d(k,"width")||"100%";i.height=r.height||d(k,"height")||"81px";i.src=r.src; -i.autoplay=r.autoplay;if(parseFloat(i.height,10)!==81)i.height="81px";i.offsetLeft=j.left;i.offsetTop=j.top;i.offsetHeight=parseFloat(i.height,10);i.offsetWidth=parseFloat(i.width,10);if(/[\d]+%/.test(i.width)){r=d(k,"width");i._container.style.width=i.width;i.offsetWidth=i._container.offsetWidth;i._container.style.width=r}if(/[\d]+%/.test(i.height)){r=d(k,"height");i._container.style.height=i.height;i.offsetHeight=i._container.offsetHeight;i._container.style.height=r}}return function(i,r,k){if(i)if(r){if(/file/.test(location.protocol))throw"Must run from a web server!"; -}else throw"Must supply a source!";else throw"Must supply an id!";if(!(this._container=document.getElementById(i)))throw"Could not find that container in the DOM!";k=k||{};k.api=k.api||{};k.target=i;k.src=r;k.api.commentformat=k.api.commentformat||a;this._mediaId=0;this._listeners={};this._playerId=g.guid(k.target);this._containerId=k.target;this._options=k;this._comments=[];this._popcorn=null;l(this);this.duration=0;this.volume=1;this.ended=this.currentTime=0;this.paused=1;this.readyState=0;this.playbackRate= -1;this.left=this.top=0;this.autoplay=null;this.played=0;this.addEventListener("load",function(){var j=this.getBoundingClientRect();this.top=j.top;this.left=j.left;this.offsetWidth=this.swfObj.offsetWidth;this.offsetHeight=this.swfObj.offsetHeight;this.offsetLeft=this.swfObj.offsetLeft;this.offsetTop=this.swfObj.offsetTop});o[this._playerId]=this;c(this)}}();g.soundcloud.init.prototype=g.soundcloud.prototype;g.extend(g.soundcloud.prototype,{setVolume:function(l){if(!(!l&&l!==0)){if(l<0)l=-l;if(l>1)l%= -1;this.volume=this.previousVolume=l;this.swfObj.api_setVolume(l*100);this.dispatchEvent("volumechange")}},setCurrentTime:function(l){if(!(!l&&l!==0)){this.currentTime=this.previousCurrentTime=l;this.ended=l>=this.duration;this.dispatchEvent("seeked")}},play:function(){if(this.swfObj){if(this.paused){this.paused=0;this.swfObj.api_play()}}else this.addEventListener("load",this.play)},pause:function(){if(this.swfObj){if(!this.paused){this.paused=1;this.swfObj.api_pause()}}else this.addEventListener("load", -this.pause)},mute:function(){if(this.swfObj)if(this.muted())if(this.paused)this.setVolume(this.oldVol);else this.volume=this.oldVol;else{this.oldVol=this.volume;if(this.paused)this.setVolume(0);else this.volume=0}else this.addEventListener("load",this.mute)},muted:function(){return this.volume===0},load:function(){if(this.swfObj){this.play();this.pause()}else this.addEventListener("load",this.load)},addEventListener:function(l,i){this._listeners[l]||(this._listeners[l]=[]);this._listeners[l].push(i); -return i},dispatchEvent:function(l){var i=this;l=l.type||l;l==="play"&&this.paused||l==="pause"&&!this.paused?this[l]():g.forEach(this._listeners[l],function(r){r.call(i)})},timeupdate:function(){var l=this,i=this.swfObj.api_getVolume()/100;if(f(this.currentTime-this.previousCurrentTime)>0.25)this.swfObj.api_seekTo(this.currentTime);else this.previousCurrentTime=this.currentTime=this.swfObj.api_getTrackPosition();if(i!==this.previousVolume)this.setVolume(i);else this.volume!==this.previousVolume&& -this.setVolume(this.volume);this.paused||this.dispatchEvent("timeupdate");l.ended||setTimeout(function(){l.timeupdate.call(l)},33)},getBoundingClientRect:function(){var l;if(this.swfObj){l=this.swfObj.getBoundingClientRect();return{bottom:l.bottom,left:l.left,right:l.right,top:l.top,width:l.width||l.right-l.left,height:l.height||l.bottom-l.top}}else{tmp=this._container.getBoundingClientRect();return{left:tmp.left,top:tmp.top,width:this.offsetWidth,height:this.offsetHeight,bottom:tmp.top+this.width, -right:tmp.top+this.height}}},registerPopcornWithPlayer:function(l){if(this.swfObj){this._popcorn=l;var i=this._options.api;if(i.key&&i.commentdiv){var r=this;g.xhr({url:"http://api.soundcloud.com/tracks/"+r._mediaId+"/comments.js?consumer_key="+i.key,success:function(k){g.forEach(k.json,function(j){r.addComment({start:j.timestamp/1E3,date:new Date(j.created_at),text:j.body,user:{name:j.user.username,profile:j.user.permalink_url,avatar:j.user.avatar_url}})})}})}}else this.addEventListener("load",function(){this.registerPopcornWithPlayer(l)})}}); -g.extend(g.soundcloud.prototype,{addComment:function(l,i){var r=this,k={start:l.start||0,date:l.date||new Date,text:l.text||"",user:{name:l.user.name||"",profile:l.user.profile||"",avatar:l.user.avatar||""},display:function(){return(i||r._options.api.commentformat)(k)}};this._comments.push(k);this._popcorn&&this._popcorn.subtitle({start:k.start,target:this._options.api.commentdiv,display:"inline",language:"en",text:k.display()})}})})(Popcorn,window);(function(){vimeo_player_loaded=function(g){vimeo_player_loaded[g]&&vimeo_player_loaded[g]()};vimeo_player_loaded.seek={};vimeo_player_loaded.loadProgress={};vimeo_player_loaded.play={};vimeo_player_loaded.pause={};Popcorn.player("vimeo",{_canPlayType:function(g,b){return/(?:http:\/\/www\.|http:\/\/|www\.|\.|^)(vimeo)/.test(b)&&g.toLowerCase()!=="video"},_setup:function(g){var b=this,d,a=document.createElement("div"),c=0,f=false,h=0,p,o;a.id=b.id+Popcorn.guid();b.appendChild(a);o=b.style.width?""+ -b.offsetWidth:"560";p=b.style.height?""+b.offsetHeight:"315";var l=function(){var i;i=b.src;var r=0,k=false;vimeo_player_loaded[a.id]=function(){d=document.getElementById(a.id);vimeo_player_loaded.seek[a.id]=function(w){if(w!==c){c=w;b.dispatchEvent("seeked");b.dispatchEvent("timeupdate")}};vimeo_player_loaded.play[a.id]=function(){if(b.paused){b.paused=false;b.dispatchEvent("play");b.dispatchEvent("playing");j()}};vimeo_player_loaded.pause[a.id]=function(){if(!b.paused){b.paused=true;b.dispatchEvent("pause")}}; -vimeo_player_loaded.loadProgress[a.id]=function(w){if(!k){k=true;b.dispatchEvent("loadstart")}w.percent===100&&b.dispatchEvent("canplaythrough")};d.api_addEventListener("seek","vimeo_player_loaded.seek."+a.id);d.api_addEventListener("loadProgress","vimeo_player_loaded.loadProgress."+a.id);d.api_addEventListener("play","vimeo_player_loaded.play."+a.id);d.api_addEventListener("pause","vimeo_player_loaded.pause."+a.id);var j=function(){if(!b.paused){c=d.api_getCurrentTime();b.dispatchEvent("timeupdate"); -setTimeout(j,10)}},v=function(){var w=d.api_getVolume()===0,n=d.api_getVolume();if(f!==w){f=w;b.dispatchEvent("volumechange")}if(h!==n){h=n;b.dispatchEvent("volumechange")}setTimeout(v,250)};b.play=function(){b.paused=false;b.dispatchEvent("play");b.dispatchEvent("playing");j();d.api_play()};b.pause=function(){if(!b.paused){b.paused=true;b.dispatchEvent("pause");d.api_pause()}};Popcorn.player.defineProperty(b,"currentTime",{set:function(w){if(!w)return c;c=+w;b.dispatchEvent("seeked");b.dispatchEvent("timeupdate"); -d.api_seekTo(c);return c},get:function(){return c}});Popcorn.player.defineProperty(b,"muted",{set:function(w){if(d.api_getVolume()===0!==w)if(w){r=d.api_getVolume();d.api_setVolume(0)}else d.api_setVolume(r)},get:function(){return d.api_getVolume()===0}});Popcorn.player.defineProperty(b,"volume",{set:function(w){if(!w||typeof w!=="number"||w<0||w>1)return d.api_getVolume()/100;if(d.api_getVolume()!==w){d.api_setVolume(w*100);h=d.api_getVolume();b.dispatchEvent("volumechange")}return d.api_getVolume()/ -100},get:function(){return d.api_getVolume()/100}});b.dispatchEvent("loadedmetadata");b.dispatchEvent("loadeddata");b.duration=d.api_getDuration();b.dispatchEvent("durationchange");v();b.readyState=4;b.dispatchEvent("canplaythrough")};i=/\d+$/.exec(i);i={clip_id:i?i[0]:0,api:1,js_swf_id:a.id};Popcorn.extend(i,g);swfobject.embedSWF("//vimeo.com/moogaloop.swf",a.id,o,p,"9.0.0","expressInstall.swf",i,{allowscriptaccess:"always",allowfullscreen:"true",wmode:"transparent"},{})};window.swfobject?l():Popcorn.getScript("//ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js", -l)}})})();var onYouTubePlayerReady=function(g){onYouTubePlayerReady[g]&&onYouTubePlayerReady[g]()};onYouTubePlayerReady.stateChangeEventHandler={};onYouTubePlayerReady.onErrorEventHandler={}; -Popcorn.player("youtube",{_canPlayType:function(g,b){return/(?:http:\/\/www\.|http:\/\/|www\.|\.|^)(youtu)/.test(b)&&g.toLowerCase()!=="video"},_setup:function(g){var b=this,d=false,a=document.createElement("div"),c=0,f=0,h=true,p=false,o=false,l=100;b.paused=undefined;a.id=b.id+Popcorn.guid();g._container=a;b.appendChild(a);var i=function(){var r,k,j,v,w;onYouTubePlayerReady[a.id]=function(){g.youtubeObject=document.getElementById(a.id);onYouTubePlayerReady.stateChangeEventHandler[a.id]=function(C){if(!g.destroyed)if(C=== -2)if(p&&f===c&&f!==g.youtubeObject.getCurrentTime()){p=false;g.youtubeObject.seekTo(c)}else{c=g.youtubeObject.getCurrentTime();b.dispatchEvent("timeupdate");!b.paused&&b.pause()}else if(C===1&&!h)b.paused&&b.play();else if(C===-1)g.youtubeObject.playVideo();else if(C===1&&h){h=false;if(b.paused===true)b.pause();else if(b.paused===false)b.play();else if(d)b.play();else d||b.pause();b.duration=g.youtubeObject.getDuration();b.dispatchEvent("durationchange");y();b.dispatchEvent("loadedmetadata");b.dispatchEvent("loadeddata"); -b.readyState=4;b.dispatchEvent("canplaythrough")}else C===0&&b.dispatchEvent("ended")};onYouTubePlayerReady.onErrorEventHandler[a.id]=function(C){[2,100,101,150].indexOf(C)!==-1&&b.dispatchEvent("error")};g.youtubeObject.addEventListener("onStateChange","onYouTubePlayerReady.stateChangeEventHandler."+a.id);g.youtubeObject.addEventListener("onError","onYouTubePlayerReady.onErrorEventHandler."+a.id);var n=function(){if(!g.destroyed)if(!b.paused){c=g.youtubeObject.getCurrentTime();b.dispatchEvent("timeupdate"); -setTimeout(n,10)}},y=function(){if(!g.destroyed){if(o!==g.youtubeObject.isMuted()){o=g.youtubeObject.isMuted();b.dispatchEvent("volumechange")}if(l!==g.youtubeObject.getVolume()){l=g.youtubeObject.getVolume();b.dispatchEvent("volumechange")}setTimeout(y,250)}};b.play=function(){if(!g.destroyed){if(b.paused!==false||g.youtubeObject.getPlayerState()!==1){b.paused=false;b.dispatchEvent("play");b.dispatchEvent("playing")}n();g.youtubeObject.playVideo()}};b.pause=function(){if(!g.destroyed)if(b.paused!== -true||g.youtubeObject.getPlayerState()!==2){b.paused=true;b.dispatchEvent("pause");g.youtubeObject.pauseVideo()}};Popcorn.player.defineProperty(b,"currentTime",{set:function(C){c=f=+C;p=true;if(g.destroyed)return c;b.dispatchEvent("seeked");b.dispatchEvent("timeupdate");g.youtubeObject.seekTo(c);return c},get:function(){return c}});Popcorn.player.defineProperty(b,"muted",{set:function(C){if(g.destroyed)return C;if(g.youtubeObject.isMuted()!==C){C?g.youtubeObject.mute():g.youtubeObject.unMute();o= -g.youtubeObject.isMuted();b.dispatchEvent("volumechange")}return g.youtubeObject.isMuted()},get:function(){if(g.destroyed)return 0;return g.youtubeObject.isMuted()}});Popcorn.player.defineProperty(b,"volume",{set:function(C){if(g.destroyed)return C;if(g.youtubeObject.getVolume()/100!==C){g.youtubeObject.setVolume(C*100);l=g.youtubeObject.getVolume();b.dispatchEvent("volumechange")}return g.youtubeObject.getVolume()/100},get:function(){if(g.destroyed)return 0;return g.youtubeObject.getVolume()/100}})}; -g.controls=+g.controls===0||+g.controls===1?g.controls:1;g.annotations=+g.annotations===1||+g.annotations===3?g.annotations:1;r={playerapiid:a.id};k=/^.*(?:\/|v=)(.{11})/.exec(b.src)[1];w=(b.src.split("?")[1]||"").replace(/v=.{11}/,"");d=/autoplay=1/.test(w);j=b.style.width?""+b.offsetWidth:"560";v=b.style.height?""+b.offsetHeight:"315";k={id:a.id,"data-youtube-player":"//www.youtube.com/e/"+k+"?"+w+"&enablejsapi=1&playerapiid="+a.id+"&version=3"};swfobject.embedSWF(k["data-youtube-player"],a.id, -j,v,"8",undefined,r,{wmode:"transparent",allowScriptAccess:"always"},k)};window.swfobject?i():Popcorn.getScript("//ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js",i)},_teardown:function(g){g.destroyed=true;g.youtubeObject.stopVideo();g.youtubeObject.clearVideo();this.removeChild(document.getElementById(g._container.id))}}); +(function(r,f){function n(a,g){return function(){if(d.plugin.debug)return a.apply(this,arguments);try{return a.apply(this,arguments)}catch(l){d.plugin.errors.push({plugin:g,thrown:l,source:a.toString()});this.emit("pluginerror",d.plugin.errors)}}}if(f.addEventListener){var c=Array.prototype,b=Object.prototype,e=c.forEach,h=c.slice,i=b.hasOwnProperty,j=b.toString,p=r.Popcorn,m=[],o=false,q={events:{hash:{},apis:{}}},s=function(){return r.requestAnimationFrame||r.webkitRequestAnimationFrame||r.mozRequestAnimationFrame|| +r.oRequestAnimationFrame||r.msRequestAnimationFrame||function(a){r.setTimeout(a,16)}}(),d=function(a,g){return new d.p.init(a,g||null)};d.version="1.3";d.isSupported=true;d.instances=[];d.p=d.prototype={init:function(a,g){var l,k=this;if(typeof a==="function")if(f.readyState==="complete")a(f,d);else{m.push(a);if(!o){o=true;var t=function(){f.removeEventListener("DOMContentLoaded",t,false);for(var z=0,C=m.length;z<C;z++)m[z].call(f,d);m=null};f.addEventListener("DOMContentLoaded",t,false)}}else{if(typeof a=== +"string")try{l=f.querySelector(a)}catch(u){throw Error("Popcorn.js Error: Invalid media element selector: "+a);}this.media=l||a;l=this.media.nodeName&&this.media.nodeName.toLowerCase()||"video";this[l]=this.media;this.options=g||{};this.id=this.options.id||d.guid(l);if(d.byId(this.id))throw Error("Popcorn.js Error: Cannot use duplicate ID ("+this.id+")");this.isDestroyed=false;this.data={running:{cue:[]},timeUpdate:d.nop,disabled:{},events:{},hooks:{},history:[],state:{volume:this.media.volume},trackRefs:{}, +trackEvents:{byStart:[{start:-1,end:-1}],byEnd:[{start:-1,end:-1}],animating:[],startIndex:0,endIndex:0,previousUpdateTime:-1}};d.instances.push(this);var v=function(){if(k.media.currentTime<0)k.media.currentTime=0;k.media.removeEventListener("loadeddata",v,false);var z,C,E,B,w;z=k.media.duration;z=z!=z?Number.MAX_VALUE:z+1;d.addTrackEvent(k,{start:z,end:z});if(k.options.frameAnimation){k.data.timeUpdate=function(){d.timeUpdate(k,{});d.forEach(d.manifest,function(D,F){if(C=k.data.running[F]){B=C.length; +for(var I=0;I<B;I++){E=C[I];(w=E._natives)&&w.frame&&w.frame.call(k,{},E,k.currentTime())}}});k.emit("timeupdate");!k.isDestroyed&&s(k.data.timeUpdate)};!k.isDestroyed&&s(k.data.timeUpdate)}else{k.data.timeUpdate=function(D){d.timeUpdate(k,D)};k.isDestroyed||k.media.addEventListener("timeupdate",k.data.timeUpdate,false)}};Object.defineProperty(this,"error",{get:function(){return k.media.error}});k.media.readyState>=2?v():k.media.addEventListener("loadeddata",v,false);return this}}};d.p.init.prototype= +d.p;d.byId=function(a){for(var g=d.instances,l=g.length,k=0;k<l;k++)if(g[k].id===a)return g[k];return null};d.forEach=function(a,g,l){if(!a||!g)return{};l=l||this;var k,t;if(e&&a.forEach===e)return a.forEach(g,l);if(j.call(a)==="[object NodeList]"){k=0;for(t=a.length;k<t;k++)g.call(l,a[k],k,a);return a}for(k in a)i.call(a,k)&&g.call(l,a[k],k,a);return a};d.extend=function(a){var g=h.call(arguments,1);d.forEach(g,function(l){for(var k in l)a[k]=l[k]});return a};d.extend(d,{noConflict:function(a){if(a)r.Popcorn= +p;return d},error:function(a){throw Error(a);},guid:function(a){d.guid.counter++;return(a?a:"")+(+new Date+d.guid.counter)},sizeOf:function(a){var g=0,l;for(l in a)g++;return g},isArray:Array.isArray||function(a){return j.call(a)==="[object Array]"},nop:function(){},position:function(a){a=a.getBoundingClientRect();var g={},l=f.documentElement,k=f.body,t,u,v;t=l.clientTop||k.clientTop||0;u=l.clientLeft||k.clientLeft||0;v=r.pageYOffset&&l.scrollTop||k.scrollTop;l=r.pageXOffset&&l.scrollLeft||k.scrollLeft; +t=Math.ceil(a.top+v-t);u=Math.ceil(a.left+l-u);for(var z in a)g[z]=Math.round(a[z]);return d.extend({},g,{top:t,left:u})},disable:function(a,g){if(!a.data.disabled[g]){a.data.disabled[g]=true;for(var l=a.data.running[g].length-1,k;l>=0;l--){k=a.data.running[g][l];k._natives.end.call(a,null,k)}}return a},enable:function(a,g){if(a.data.disabled[g]){a.data.disabled[g]=false;for(var l=a.data.running[g].length-1,k;l>=0;l--){k=a.data.running[g][l];k._natives.start.call(a,null,k)}}return a},destroy:function(a){var g= +a.data.events,l=a.data.trackEvents,k,t,u,v;for(t in g){k=g[t];for(u in k)delete k[u];g[t]=null}for(v in d.registryByName)d.removePlugin(a,v);l.byStart.length=0;l.byEnd.length=0;if(!a.isDestroyed){a.data.timeUpdate&&a.media.removeEventListener("timeupdate",a.data.timeUpdate,false);a.isDestroyed=true}}});d.guid.counter=1;d.extend(d.p,function(){var a={};d.forEach("load play pause currentTime playbackRate volume duration preload playbackRate autoplay loop controls muted buffered readyState seeking paused played seekable ended".split(/\s+/g), +function(g){a[g]=function(l){var k;if(typeof this.media[g]==="function"){if(l!=null&&/play|pause/.test(g))this.media.currentTime=d.util.toSeconds(l);this.media[g]();return this}if(l!=null){k=this.media[g];this.media[g]=l;k!==l&&this.emit("attrchange",{attribute:g,previousValue:k,currentValue:l});return this}return this.media[g]}});return a}());d.forEach("enable disable".split(" "),function(a){d.p[a]=function(g){return d[a](this,g)}});d.extend(d.p,{roundTime:function(){return Math.round(this.media.currentTime)}, +exec:function(a,g,l){var k=arguments.length,t,u;try{u=d.util.toSeconds(a)}catch(v){}if(typeof u==="number")a=u;if(typeof a==="number"&&k===2){l=g;g=a;a=d.guid("cue")}else if(k===1)g=-1;else if(t=this.getTrackEvent(a)){if(typeof a==="string"&&k===2){if(typeof g==="number")l=t._natives.start;if(typeof g==="function"){l=g;g=t.start}}}else if(k>=2){if(typeof g==="string"){try{u=d.util.toSeconds(g)}catch(z){}g=u}if(typeof g==="number")l=d.nop();if(typeof g==="function"){l=g;g=-1}}d.addTrackEvent(this, +{id:a,start:g,end:g+1,_running:false,_natives:{start:l||d.nop,end:d.nop,type:"cue"}});return this},mute:function(a){a=a==null||a===true?"muted":"unmuted";if(a==="unmuted"){this.media.muted=false;this.media.volume=this.data.state.volume}if(a==="muted"){this.data.state.volume=this.media.volume;this.media.muted=true}this.emit(a);return this},unmute:function(a){return this.mute(a==null?false:!a)},position:function(){return d.position(this.media)},toggle:function(a){return d[this.data.disabled[a]?"enable": +"disable"](this,a)},defaults:function(a,g){if(d.isArray(a)){d.forEach(a,function(l){for(var k in l)this.defaults(k,l[k])},this);return this}if(!this.options.defaults)this.options.defaults={};this.options.defaults[a]||(this.options.defaults[a]={});d.extend(this.options.defaults[a],g);return this}});d.Events={UIEvents:"blur focus focusin focusout load resize scroll unload",MouseEvents:"mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave click dblclick",Events:"loadstart progress suspend emptied stalled play pause error loadedmetadata loadeddata waiting playing canplay canplaythrough seeking seeked timeupdate ended ratechange durationchange volumechange"}; +d.Events.Natives=d.Events.UIEvents+" "+d.Events.MouseEvents+" "+d.Events.Events;q.events.apiTypes=["UIEvents","MouseEvents","Events"];(function(a,g){for(var l=q.events.apiTypes,k=a.Natives.split(/\s+/g),t=0,u=k.length;t<u;t++)g.hash[k[t]]=true;l.forEach(function(v){g.apis[v]={};for(var z=a[v].split(/\s+/g),C=z.length,E=0;E<C;E++)g.apis[v][z[E]]=true})})(d.Events,q.events);d.events={isNative:function(a){return!!q.events.hash[a]},getInterface:function(a){if(!d.events.isNative(a))return false;var g= +q.events,l=g.apiTypes;g=g.apis;for(var k=0,t=l.length,u,v;k<t;k++){v=l[k];if(g[v][a]){u=v;break}}return u},all:d.Events.Natives.split(/\s+/g),fn:{trigger:function(a,g){var l;if(this.data.events[a]&&d.sizeOf(this.data.events[a])){if(l=d.events.getInterface(a)){l=f.createEvent(l);l.initEvent(a,true,true,r,1);this.media.dispatchEvent(l);return this}d.forEach(this.data.events[a],function(k){k.call(this,g)},this)}return this},listen:function(a,g){var l=this,k=true,t=d.events.hooks[a],u;if(!this.data.events[a]){this.data.events[a]= +{};k=false}if(t){t.add&&t.add.call(this,{},g);if(t.bind)a=t.bind;if(t.handler){u=g;g=function(v){t.handler.call(l,v,u)}}k=true;if(!this.data.events[a]){this.data.events[a]={};k=false}}this.data.events[a][g.name||g.toString()+d.guid()]=g;!k&&d.events.all.indexOf(a)>-1&&this.media.addEventListener(a,function(v){d.forEach(l.data.events[a],function(z){typeof z==="function"&&z.call(l,v)})},false);return this},unlisten:function(a,g){if(this.data.events[a]&&this.data.events[a][g]){delete this.data.events[a][g]; +return this}this.data.events[a]=null;return this}},hooks:{canplayall:{bind:"canplaythrough",add:function(a,g){var l=false;if(this.media.readyState){g.call(this,a);l=true}this.data.hooks.canplayall={fired:l}},handler:function(a,g){if(!this.data.hooks.canplayall.fired){g.call(this,a);this.data.hooks.canplayall.fired=true}}}}};d.forEach([["trigger","emit"],["listen","on"],["unlisten","off"]],function(a){d.p[a[0]]=d.p[a[1]]=d.events.fn[a[0]]});d.addTrackEvent=function(a,g){var l,k;if(g.id)l=a.getTrackEvent(g.id); +if(l){k=true;g=d.extend({},l,g);a.removeTrackEvent(g.id)}if(g&&g._natives&&g._natives.type&&a.options.defaults&&a.options.defaults[g._natives.type])g=d.extend({},a.options.defaults[g._natives.type],g);if(g._natives){g._id=g.id||g._id||d.guid(g._natives.type);a.data.history.push(g._id)}g.start=d.util.toSeconds(g.start,a.options.framerate);g.end=d.util.toSeconds(g.end,a.options.framerate);var t=a.data.trackEvents.byStart,u=a.data.trackEvents.byEnd,v;for(v=t.length-1;v>=0;v--)if(g.start>=t[v].start){t.splice(v+ +1,0,g);break}for(t=u.length-1;t>=0;t--)if(g.end>u[t].end){u.splice(t+1,0,g);break}if(g.end>a.media.currentTime&&g.start<=a.media.currentTime){g._running=true;a.data.running[g._natives.type].push(g);a.data.disabled[g._natives.type]||g._natives.start.call(a,null,g)}v<=a.data.trackEvents.startIndex&&g.start<=a.data.trackEvents.previousUpdateTime&&a.data.trackEvents.startIndex++;t<=a.data.trackEvents.endIndex&&g.end<a.data.trackEvents.previousUpdateTime&&a.data.trackEvents.endIndex++;this.timeUpdate(a, +null,true);g._id&&d.addTrackEvent.ref(a,g);if(k){k=g._natives.type==="cue"?"cuechange":"trackchange";a.emit(k,{id:g.id,previousValue:{time:l.start,fn:l._natives.start},currentValue:{time:g.start,fn:g._natives.start}})}};d.addTrackEvent.ref=function(a,g){a.data.trackRefs[g._id]=g;return a};d.removeTrackEvent=function(a,g){for(var l,k,t=a.data.history.length,u=a.data.trackEvents.byStart.length,v=0,z=0,C=[],E=[],B=[],w=[];--u>-1;){l=a.data.trackEvents.byStart[v];k=a.data.trackEvents.byEnd[v];if(!l._id){C.push(l); +E.push(k)}if(l._id){l._id!==g&&C.push(l);k._id!==g&&E.push(k);if(l._id===g){z=v;l._natives._teardown&&l._natives._teardown.call(a,l)}}v++}u=a.data.trackEvents.animating.length;v=0;if(u)for(;--u>-1;){l=a.data.trackEvents.animating[v];l._id||B.push(l);l._id&&l._id!==g&&B.push(l);v++}z<=a.data.trackEvents.startIndex&&a.data.trackEvents.startIndex--;z<=a.data.trackEvents.endIndex&&a.data.trackEvents.endIndex--;a.data.trackEvents.byStart=C;a.data.trackEvents.byEnd=E;a.data.trackEvents.animating=B;for(u= +0;u<t;u++)a.data.history[u]!==g&&w.push(a.data.history[u]);a.data.history=w;d.removeTrackEvent.ref(a,g)};d.removeTrackEvent.ref=function(a,g){delete a.data.trackRefs[g];return a};d.getTrackEvents=function(a){var g=[];a=a.data.trackEvents.byStart;for(var l=a.length,k=0,t;k<l;k++){t=a[k];t._id&&g.push(t)}return g};d.getTrackEvents.ref=function(a){return a.data.trackRefs};d.getTrackEvent=function(a,g){return a.data.trackRefs[g]};d.getTrackEvent.ref=function(a,g){return a.data.trackRefs[g]};d.getLastTrackEventId= +function(a){return a.data.history[a.data.history.length-1]};d.timeUpdate=function(a,g){var l=a.media.currentTime,k=a.data.trackEvents.previousUpdateTime,t=a.data.trackEvents,u=t.endIndex,v=t.startIndex,z=t.byStart.length,C=t.byEnd.length,E=d.registryByName,B,w,D;if(k<=l){for(;t.byEnd[u]&&t.byEnd[u].end<=l;){B=t.byEnd[u];w=(k=B._natives)&&k.type;if(!k||E[w]||a[w]){if(B._running===true){B._running=false;D=a.data.running[w];D.splice(D.indexOf(B),1);if(!a.data.disabled[w]){k.end.call(a,g,B);a.emit("trackend", +d.extend({},B,{plugin:w,type:"trackend"}))}}u++}else{d.removeTrackEvent(a,B._id);return}}for(;t.byStart[v]&&t.byStart[v].start<=l;){B=t.byStart[v];w=(k=B._natives)&&k.type;if(!k||E[w]||a[w]){if(B.end>l&&B._running===false){B._running=true;a.data.running[w].push(B);if(!a.data.disabled[w]){k.start.call(a,g,B);a.emit("trackstart",d.extend({},B,{plugin:w,type:"trackstart"}))}}v++}else{d.removeTrackEvent(a,B._id);return}}}else if(k>l){for(;t.byStart[v]&&t.byStart[v].start>l;){B=t.byStart[v];w=(k=B._natives)&& +k.type;if(!k||E[w]||a[w]){if(B._running===true){B._running=false;D=a.data.running[w];D.splice(D.indexOf(B),1);if(!a.data.disabled[w]){k.end.call(a,g,B);a.emit("trackend",d.extend({},B,{plugin:w,type:"trackend"}))}}v--}else{d.removeTrackEvent(a,B._id);return}}for(;t.byEnd[u]&&t.byEnd[u].end>l;){B=t.byEnd[u];w=(k=B._natives)&&k.type;if(!k||E[w]||a[w]){if(B.start<=l&&B._running===false){B._running=true;a.data.running[w].push(B);if(!a.data.disabled[w]){k.start.call(a,g,B);a.emit("trackstart",d.extend({}, +B,{plugin:w,type:"trackstart"}))}}u--}else{d.removeTrackEvent(a,B._id);return}}}t.endIndex=u;t.startIndex=v;t.previousUpdateTime=l;t.byStart.length<z&&t.startIndex--;t.byEnd.length<C&&t.endIndex--};d.extend(d.p,{getTrackEvents:function(){return d.getTrackEvents.call(null,this)},getTrackEvent:function(a){return d.getTrackEvent.call(null,this,a)},getLastTrackEventId:function(){return d.getLastTrackEventId.call(null,this)},removeTrackEvent:function(a){d.removeTrackEvent.call(null,this,a);return this}, +removePlugin:function(a){d.removePlugin.call(null,this,a);return this},timeUpdate:function(a){d.timeUpdate.call(null,this,a);return this},destroy:function(){d.destroy.call(null,this);return this}});d.manifest={};d.registry=[];d.registryByName={};d.plugin=function(a,g,l){if(d.protect.natives.indexOf(a.toLowerCase())>=0)d.error("'"+a+"' is a protected function name");else{var k=["start","end"],t={},u=typeof g==="function",v=["_setup","_teardown","start","end","frame"],z=function(B,w){B=B||d.nop;w=w|| +d.nop;return function(){B.apply(this,arguments);w.apply(this,arguments)}};d.manifest[a]=l=l||g.manifest||{};v.forEach(function(B){g[B]=n(g[B]||d.nop,a)});var C=function(B,w){if(!w)return this;if(w.ranges&&d.isArray(w.ranges)){d.forEach(w.ranges,function(G){G=d.extend({},w,G);delete G.ranges;this[a](G)},this);return this}var D=w._natives={},F="",I;d.extend(D,B);w._natives.type=a;w._running=false;D.start=D.start||D["in"];D.end=D.end||D.out;if(w.once)D.end=z(D.end,function(){this.removeTrackEvent(w._id)}); +D._teardown=z(function(){var G=h.call(arguments),H=this.data.running[D.type];G.unshift(null);G[1]._running&&H.splice(H.indexOf(w),1)&&D.end.apply(this,G)},D._teardown);w.compose=w.compose&&w.compose.split(" ")||[];w.effect=w.effect&&w.effect.split(" ")||[];w.compose=w.compose.concat(w.effect);w.compose.forEach(function(G){F=d.compositions[G]||{};v.forEach(function(H){D[H]=z(D[H],F[H])})});w._natives.manifest=l;if(!("start"in w))w.start=w["in"]||0;if(!w.end&&w.end!==0)w.end=w.out||Number.MAX_VALUE; +if(!i.call(w,"toString"))w.toString=function(){var G=["start: "+w.start,"end: "+w.end,"id: "+(w.id||w._id)];w.target!=null&&G.push("target: "+w.target);return a+" ( "+G.join(", ")+" )"};if(!w.target){I="options"in l&&l.options;w.target=I&&"target"in I&&I.target}if(w._natives)w._id=d.guid(w._natives.type);w._natives._setup&&w._natives._setup.call(this,w);d.addTrackEvent(this,w);d.forEach(B,function(G,H){H!=="type"&&k.indexOf(H)===-1&&this.on(H,G)},this);return this};d.p[a]=t[a]=function(B,w){var D; +if(B&&!w)w=B;else if(D=this.getTrackEvent(B)){w=d.extend({},D,w);d.addTrackEvent(this,w);return this}else w.id=B;this.data.running[a]=this.data.running[a]||[];D=d.extend({},this.options.defaults&&this.options.defaults[a]||{},w);return C.call(this,u?g.call(this,D):g,D)};l&&d.extend(g,{manifest:l});var E={fn:t[a],definition:g,base:g,parents:[],name:a};d.registry.push(d.extend(t,E,{type:a}));d.registryByName[a]=E;return t}};d.plugin.errors=[];d.plugin.debug=d.version==="1.3";d.removePlugin=function(a, +g){if(!g){g=a;a=d.p;if(d.protect.natives.indexOf(g.toLowerCase())>=0){d.error("'"+g+"' is a protected function name");return}var l=d.registry.length,k;for(k=0;k<l;k++)if(d.registry[k].name===g){d.registry.splice(k,1);delete d.registryByName[g];delete d.manifest[g];delete a[g];return}}l=a.data.trackEvents.byStart;k=a.data.trackEvents.byEnd;var t=a.data.trackEvents.animating,u,v;u=0;for(v=l.length;u<v;u++){if(l[u]&&l[u]._natives&&l[u]._natives.type===g){l[u]._natives._teardown&&l[u]._natives._teardown.call(a, +l[u]);l.splice(u,1);u--;v--;if(a.data.trackEvents.startIndex<=u){a.data.trackEvents.startIndex--;a.data.trackEvents.endIndex--}}k[u]&&k[u]._natives&&k[u]._natives.type===g&&k.splice(u,1)}u=0;for(v=t.length;u<v;u++)if(t[u]&&t[u]._natives&&t[u]._natives.type===g){t.splice(u,1);u--;v--}};d.compositions={};d.compose=function(a,g,l){d.manifest[a]=l||g.manifest||{};d.compositions[a]=g};d.plugin.effect=d.effect=d.compose;var A=/^(?:\.|#|\[)/;d.dom={debug:false,find:function(a,g){var l=null;a=a.trim();g= +g||f;if(a){if(!A.test(a)){l=f.getElementById(a);if(l!==null)return l}try{l=g.querySelector(a)}catch(k){if(d.dom.debug)throw Error(k);}}return l}};var y=/\?/,x={url:"",data:"",dataType:"",success:d.nop,type:"GET",async:true,xhr:function(){return new r.XMLHttpRequest}};d.xhr=function(a){a.dataType=a.dataType&&a.dataType.toLowerCase()||null;if(a.dataType&&(a.dataType==="jsonp"||a.dataType==="script"))d.xhr.getJSONP(a.url,a.success,a.dataType==="script");else{a=d.extend({},x,a);a.ajax=a.xhr();if(a.ajax){if(a.type=== +"GET"&&a.data){a.url+=(y.test(a.url)?"&":"?")+a.data;a.data=null}a.ajax.open(a.type,a.url,a.async);a.ajax.send(a.data||null);return d.xhr.httpData(a)}}};d.xhr.httpData=function(a){var g,l=null,k,t=null;a.ajax.onreadystatechange=function(){if(a.ajax.readyState===4){try{l=JSON.parse(a.ajax.responseText)}catch(u){}g={xml:a.ajax.responseXML,text:a.ajax.responseText,json:l};if(!g.xml||!g.xml.documentElement){g.xml=null;try{k=new DOMParser;t=k.parseFromString(a.ajax.responseText,"text/xml");if(!t.getElementsByTagName("parsererror").length)g.xml= +t}catch(v){}}if(a.dataType)g=g[a.dataType];a.success.call(a.ajax,g)}};return g};d.xhr.getJSONP=function(a,g,l){var k=f.head||f.getElementsByTagName("head")[0]||f.documentElement,t=f.createElement("script"),u=false,v=[];v=/(=)\?(?=&|$)|\?\?/;var z,C;if(!l){C=a.match(/(callback=[^&]*)/);if(C!==null&&C.length){v=C[1].split("=")[1];if(v==="?")v="jsonp";z=d.guid(v);a=a.replace(/(callback=[^&]*)/,"callback="+z)}else{z=d.guid("jsonp");if(v.test(a))a=a.replace(v,"$1"+z);v=a.split(/\?(.+)?/);a=v[0]+"?";if(v[1])a+= +v[1]+"&";a+="callback="+z}window[z]=function(E){g&&g(E);u=true}}t.addEventListener("load",function(){l&&g&&g();u&&delete window[z];k.removeChild(t)},false);t.src=a;k.insertBefore(t,k.firstChild)};d.getJSONP=d.xhr.getJSONP;d.getScript=d.xhr.getScript=function(a,g){return d.xhr.getJSONP(a,g,true)};d.util={toSeconds:function(a,g){var l=/^([0-9]+:){0,2}[0-9]+([.;][0-9]+)?$/,k,t,u;if(typeof a==="number")return a;typeof a==="string"&&!l.test(a)&&d.error("Invalid time format");l=a.split(":");k=l.length- +1;t=l[k];if(t.indexOf(";")>-1){t=t.split(";");u=0;if(g&&typeof g==="number")u=parseFloat(t[1],10)/g;l[k]=parseInt(t[0],10)+u}k=l[0];return{1:parseFloat(k,10),2:parseInt(k,10)*60+parseFloat(l[1],10),3:parseInt(k,10)*3600+parseInt(l[1],10)*60+parseFloat(l[2],10)}[l.length||1]}};d.p.cue=d.p.exec;d.protect={natives:function(a){return Object.keys?Object.keys(a):function(g){var l,k=[];for(l in g)i.call(g,l)&&k.push(l);return k}(a)}(d.p).map(function(a){return a.toLowerCase()})};d.forEach({listen:"on",unlisten:"off", +trigger:"emit",exec:"cue"},function(a,g){var l=d.p[g];d.p[g]=function(){if(typeof console!=="undefined"&&console.warn){console.warn("Deprecated method '"+g+"', "+(a==null?"do not use.":"use '"+a+"' instead."));d.p[g]=l}return d.p[a].apply(this,[].slice.call(arguments))}});r.Popcorn=d}else{r.Popcorn={isSupported:false};for(c="byId forEach extend effects error guid sizeOf isArray nop position disable enable destroyaddTrackEvent removeTrackEvent getTrackEvents getTrackEvent getLastTrackEventId timeUpdate plugin removePlugin compose effect xhr getJSONP getScript".split(/\s+/);c.length;)r.Popcorn[c.shift()]= +function(){}}})(window,window.document);(function(r,f){var n=r.document,c=r.location,b=/:\/\//,e=c.href.replace(c.href.split("/").slice(-1)[0],""),h=function(j,p,m){j=j||0;p=(p||j||0)+1;m=m||1;p=Math.ceil((p-j)/m)||0;var o=0,q=[];for(q.length=p;o<p;){q[o++]=j;j+=m}return q};f.sequence=function(j,p){return new f.sequence.init(j,p)};f.sequence.init=function(j,p){this.parent=n.getElementById(j);this.seqId=f.guid("__sequenced");this.queue=[];this.playlist=[];this.inOuts={ofVideos:[],ofClips:[]};this.dims={width:0,height:0};this.active=0;this.playing= +this.cycling=false;this.times={last:0};this.events={};var m=this,o=0;f.forEach(p,function(q,s){var d=n.createElement("video");d.preload="auto";d.controls=true;d.style.display=s&&"none"||"";d.id=m.seqId+"-"+s;m.queue.push(d);var A=q["in"],y=q.out;m.inOuts.ofVideos.push({"in":A!==undefined&&A||1,out:y!==undefined&&y||0});m.inOuts.ofVideos[s].out=m.inOuts.ofVideos[s].out||m.inOuts.ofVideos[s]["in"]+2;d.src=!b.test(q.src)?e+q.src:q.src;d.setAttribute("data-sequence-owner",j);d.setAttribute("data-sequence-guid", +m.seqId);d.setAttribute("data-sequence-id",s);d.setAttribute("data-sequence-clip",[m.inOuts.ofVideos[s]["in"],m.inOuts.ofVideos[s].out].join(":"));m.parent.appendChild(d);m.playlist.push(f("#"+d.id))});m.inOuts.ofVideos.forEach(function(q){q={"in":o,out:o+(q.out-q["in"])};m.inOuts.ofClips.push(q);o=q.out+1});f.forEach(this.queue,function(q,s){function d(){if(!s){m.dims.width=q.videoWidth;m.dims.height=q.videoHeight}q.currentTime=m.inOuts.ofVideos[s]["in"]-0.5;q.removeEventListener("canplaythrough", +d,false);return true}q.addEventListener("canplaythrough",d,false);q.addEventListener("play",function(){m.playing=true},false);q.addEventListener("pause",function(){m.playing=false},false);q.addEventListener("timeupdate",function(A){A=A.srcElement||A.target;A=+(A.dataset&&A.dataset.sequenceId||A.getAttribute("data-sequence-id"));var y=Math.floor(q.currentTime);if(m.times.last!==y&&A===m.active){m.times.last=y;y===m.inOuts.ofVideos[A].out&&f.sequence.cycle.call(m,A)}},false)});return this};f.sequence.init.prototype= +f.sequence.prototype;f.sequence.cycle=function(j){this.queue||f.error("Popcorn.sequence.cycle is not a public method");var p=this.queue,m=this.inOuts.ofVideos,o=p[j],q=0,s;if(p[j+1])q=j+1;if(p[j+1]){p=p[q];m=m[q];f.extend(p,{width:this.dims.width,height:this.dims.height});s=this.playlist[q];o.pause();this.active=q;this.times.last=m["in"]-1;s.currentTime(m["in"]);s[q?"play":"pause"]();this.trigger("cycle",{position:{previous:j,current:q}});if(q){o.style.display="none";p.style.display=""}this.cycling= +false}else this.playlist[j].pause();return this};var i=["timeupdate","play","pause"];f.extend(f.sequence.prototype,{eq:function(j){return this.playlist[j]},remove:function(){this.parent.innerHTML=null},clip:function(j){return this.inOuts.ofVideos[j]},duration:function(){for(var j=0,p=this.inOuts.ofClips,m=0;m<p.length;m++)j+=p[m].out-p[m]["in"]+1;return j-1},play:function(){this.playlist[this.active].play();return this},exec:function(j,p){var m=this.active;this.inOuts.ofClips.forEach(function(o,q){if(j>= +o["in"]&&j<=o.out)m=q});j+=this.inOuts.ofVideos[m]["in"]-this.inOuts.ofClips[m]["in"];f.addTrackEvent(this.playlist[m],{start:j-1,end:j,_running:false,_natives:{start:p||f.nop,end:f.nop,type:"exec"}});return this},listen:function(j,p){var m=this,o=this.playlist,q=o.length,s=0;if(!p)p=f.nop;if(f.Events.Natives.indexOf(j)>-1)f.forEach(o,function(d){d.listen(j,function(A){A.active=m;if(i.indexOf(j)>-1)p.call(d,A);else++s===q&&p.call(d,A)})});else{this.events[j]||(this.events[j]={});o=p.name||f.guid("__"+ +j);this.events[j][o]=p}return this},unlisten:function(){},trigger:function(j,p){var m=this;if(!(f.Events.Natives.indexOf(j)>-1)){this.events[j]&&f.forEach(this.events[j],function(o){o.call(m,{type:j},p)});return this}}});f.forEach(f.manifest,function(j,p){f.sequence.prototype[p]=function(m){var o={},q=[],s,d,A,y,x;for(s=0;s<this.inOuts.ofClips.length;s++){q=this.inOuts.ofClips[s];d=h(q["in"],q.out);A=d.indexOf(m.start);y=d.indexOf(m.end);if(A>-1)o[s]=f.extend({},q,{start:d[A],clipIdx:A});if(y>-1)o[s]= +f.extend({},q,{end:d[y],clipIdx:y})}s=Object.keys(o).map(function(g){return+g});q=h(s[0],s[1]);for(s=0;s<q.length;s++){A={};y=q[s];var a=o[y];if(a){x=this.inOuts.ofVideos[y];d=a.clipIdx;x=h(x["in"],x.out);if(a.start){A.start=x[d];A.end=x[x.length-1]}if(a.end){A.start=x[0];A.end=x[d]}}else{A.start=this.inOuts.ofVideos[y]["in"];A.end=this.inOuts.ofVideos[y].out}this.playlist[y][p](f.extend({},m,A))}return this}})})(this,Popcorn);(function(r){document.addEventListener("DOMContentLoaded",function(){var f=document.querySelectorAll("[data-timeline-sources]");r.forEach(f,function(n,c){var b=f[c],e,h,i;if(!b.id)b.id=r.guid("__popcorn");if(b.nodeType&&b.nodeType===1){i=r("#"+b.id);e=(b.getAttribute("data-timeline-sources")||"").split(",");e[0]&&r.forEach(e,function(j){h=j.split("!");if(h.length===1){h=j.match(/(.*)[\/\\]([^\/\\]+\.\w+)$/)[2].split(".");h[0]="parse"+h[1].toUpperCase();h[1]=j}e[0]&&i[h[0]]&&i[h[0]](h[1])});i.autoplay()&& +i.play()}})},false)})(Popcorn);(function(r,f){function n(e){e=typeof e==="string"?e:[e.language,e.region].join("-");var h=e.split("-");return{iso6391:e,language:h[0]||"",region:h[1]||""}}var c=r.navigator,b=n(c.userLanguage||c.language);f.locale={get:function(){return b},set:function(e){b=n(e);f.locale.broadcast();return b},broadcast:function(e){var h=f.instances,i=h.length,j=0,p;for(e=e||"locale:changed";j<i;j++){p=h[j];e in p.data.events&&p.trigger(e)}}}})(this,this.Popcorn);(function(r){var f=Object.prototype.hasOwnProperty;r.parsers={};r.parser=function(n,c,b){if(r.protect.natives.indexOf(n.toLowerCase())>=0)r.error("'"+n+"' is a protected function name");else{if(typeof c==="function"&&!b){b=c;c=""}if(!(typeof b!=="function"||typeof c!=="string")){var e={};e[n]=function(h,i){if(!h)return this;var j=this;r.xhr({url:h,dataType:c,success:function(p){var m,o,q=0;p=b(p).data||[];if(m=p.length){for(;q<m;q++){o=p[q];for(var s in o)f.call(o,s)&&j[s]&&j[s](o[s])}i&&i()}}}); +return this};r.extend(r.p,e);return e}}}})(Popcorn);(function(r){var f=function(b,e){b=b||r.nop;e=e||r.nop;return function(){b.apply(this,arguments);e.apply(this,arguments)}},n=/^.*\.(ogg|oga|aac|mp3|wav)($|\?)/,c=/^.*\.(ogg|oga|aac|mp3|wav|ogg|ogv|mp4|webm)($|\?)/;r.player=function(b,e){if(!r[b]){e=e||{};var h=function(i,j,p){p=p||{};var m=new Date/1E3,o=m,q=0,s=0,d=1,A=false,y={},x=typeof i==="string"?r.dom.find(i):i,a={};Object.prototype.__defineGetter__||(a=x||document.createElement("div"));for(var g in x)if(!(g in a))if(typeof x[g]==="object")a[g]= +x[g];else if(typeof x[g]==="function")a[g]=function(k){return"length"in x[k]&&!x[k].call?x[k]:function(){return x[k].apply(x,arguments)}}(g);else r.player.defineProperty(a,g,{get:function(k){return function(){return x[k]}}(g),set:r.nop,configurable:true});var l=function(){m=new Date/1E3;if(!a.paused){a.currentTime+=m-o;a.dispatchEvent("timeupdate");setTimeout(l,10)}o=m};a.play=function(){this.paused=false;if(a.readyState>=4){o=new Date/1E3;a.dispatchEvent("play");l()}};a.pause=function(){this.paused= +true;a.dispatchEvent("pause")};r.player.defineProperty(a,"currentTime",{get:function(){return q},set:function(k){q=+k;a.dispatchEvent("timeupdate");return q},configurable:true});r.player.defineProperty(a,"volume",{get:function(){return d},set:function(k){d=+k;a.dispatchEvent("volumechange");return d},configurable:true});r.player.defineProperty(a,"muted",{get:function(){return A},set:function(k){A=+k;a.dispatchEvent("volumechange");return A},configurable:true});r.player.defineProperty(a,"readyState", +{get:function(){return s},set:function(k){return s=k},configurable:true});a.addEventListener=function(k,t){y[k]||(y[k]=[]);y[k].push(t);return t};a.removeEventListener=function(k,t){var u,v=y[k];if(v){for(u=y[k].length-1;u>=0;u--)t===v[u]&&v.splice(u,1);return t}};a.dispatchEvent=function(k){var t,u=k.type;if(!u){u=k;if(k=r.events.getInterface(u)){t=document.createEvent(k);t.initEvent(u,true,true,window,1)}}if(y[u])for(k=y[u].length-1;k>=0;k--)y[u][k].call(this,t,this)};a.src=j||"";a.duration=0;a.paused= +true;a.ended=0;p&&p.events&&r.forEach(p.events,function(k,t){a.addEventListener(t,k,false)});if(e._canPlayType(x.nodeName,j)!==false)if(e._setup)e._setup.call(a,p);else{a.readyState=4;a.dispatchEvent("loadedmetadata");a.dispatchEvent("loadeddata");a.dispatchEvent("canplaythrough")}else setTimeout(function(){a.dispatchEvent("error")},0);i=new r.p.init(a,p);if(e._teardown)i.destroy=f(i.destroy,function(){e._teardown.call(a,p)});return i};h.canPlayType=e._canPlayType=e._canPlayType||r.nop;r[b]=r.player.registry[b]= +h}};r.player.registry={};r.player.defineProperty=Object.defineProperty||function(b,e,h){b.__defineGetter__(e,h.get||r.nop);b.__defineSetter__(e,h.set||r.nop)};r.player.playerQueue=function(){var b=[],e=false;return{next:function(){e=false;b.shift();b[0]&&b[0]()},add:function(h){b.push(function(){e=true;h&&h()});!e&&b[0]()}}};r.smart=function(b,e,h){var i=["AUDIO","VIDEO"],j,p=r.dom.find(b),m;j=document.createElement("video");var o={ogg:"video/ogg",ogv:"video/ogg",oga:"audio/ogg",webm:"video/webm", +mp4:"video/mp4",mp3:"audio/mp3"};if(p){if(i.indexOf(p.nodeName)>-1&&!e){if(typeof e==="object")h=e;return r(p,h)}if(typeof e==="string")e=[e];b=0;for(srcLength=e.length;b<srcLength;b++){m=c.exec(e[b]);m=!m||!m[1]?false:j.canPlayType(o[m[1]]);if(m){e=e[b];break}for(var q in r.player.registry)if(r.player.registry.hasOwnProperty(q))if(r.player.registry[q].canPlayType(p.nodeName,e[b]))return r[q](p,e[b],h)}if(i.indexOf(p.nodeName)===-1){j=typeof e==="string"?e:e.length?e[0]:e;b=document.createElement(n.exec(j)? +i[0]:i[1]);b.controls=true;p.appendChild(b);p=b}h&&h.events&&h.events.error&&p.addEventListener("error",h.events.error,false);p.src=e;return r(p,h)}else r.error("Specified target "+b+" was not found.")}})(Popcorn);(function(r){var f=function(n,c){var b=0,e=0,h;r.forEach(c.classes,function(i,j){h=[];if(i==="parent")h[0]=document.querySelectorAll("#"+c.target)[0].parentNode;else h=document.querySelectorAll("#"+c.target+" "+i);b=0;for(e=h.length;b<e;b++)h[b].classList.toggle(j)})};r.compose("applyclass",{manifest:{about:{name:"Popcorn applyclass Effect",version:"0.1",author:"@scottdowne",website:"scottdowne.wordpress.com"},options:{}},_setup:function(n){n.classes={};n.applyclass=n.applyclass||"";for(var c=n.applyclass.replace(/\s/g, +"").split(","),b=[],e=0,h=c.length;e<h;e++){b=c[e].split(":");if(b[0])n.classes[b[0]]=b[1]||""}},start:f,end:f})})(Popcorn);(function(r){var f=/(?:http:\/\/www\.|http:\/\/|www\.|\.|^)(youtu|vimeo|soundcloud|baseplayer)/,n={},c={vimeo:false,youtube:false,soundcloud:false,module:false};Object.defineProperty(n,void 0,{get:function(){return c[void 0]},set:function(b){c[void 0]=b}});r.plugin("mediaspawner",{manifest:{about:{name:"Popcorn Media Spawner Plugin",version:"0.1",author:"Matthew Schranz, @mjschranz",website:"mschranz.wordpress.com"},options:{source:{elem:"input",type:"text",label:"Media Source","default":"http://www.youtube.com/watch?v=CXDstfD9eJ0"}, +caption:{elem:"input",type:"text",label:"Media Caption","default":"Popcorn Popping",optional:true},target:"mediaspawner-container",start:{elem:"input",type:"number",label:"Start"},end:{elem:"input",type:"number",label:"End"},autoplay:{elem:"input",type:"checkbox",label:"Autoplay Video",optional:true},width:{elem:"input",type:"number",label:"Media Width","default":400,units:"px",optional:true},height:{elem:"input",type:"number",label:"Media Height","default":200,units:"px",optional:true}}},_setup:function(b){function e(){function o(){if(j!== +"HTML5"&&!window.Popcorn[j])setTimeout(function(){o()},300);else{b.id=b._container.id;b._container.style.width=b.width+"px";b._container.style.height=b.height+"px";b.popcorn=r.smart("#"+b.id,b.source);j==="HTML5"&&b.popcorn.controls(true);b._container.style.width="0px";b._container.style.height="0px";b._container.style.visibility="hidden";b._container.style.overflow="hidden"}}if(j!=="HTML5"&&!window.Popcorn[j]&&!n[j]){n[j]=true;r.getScript("http://popcornjs.org/code/players/"+j+"/popcorn."+j+".js", +function(){o()})}else o()}function h(){window.Popcorn.player?e():setTimeout(function(){h()},300)}var i=document.getElementById(b.target)||{},j,p,m;if(p=f.exec(b.source)){j=p[1];if(j==="youtu")j="youtube"}else j="HTML5";b._type=j;b._container=document.createElement("div");p=b._container;p.id="mediaSpawnerdiv-"+r.guid();b.width=b.width||400;b.height=b.height||200;if(b.caption){m=document.createElement("div");m.innerHTML=b.caption;m.style.display="none";b._capCont=m;p.appendChild(m)}i&&i.appendChild(p); +if(!window.Popcorn.player&&!n.module){n.module=true;r.getScript("http://popcornjs.org/code/modules/player/popcorn.player.js",h)}else h()},start:function(b,e){if(e._capCont)e._capCont.style.display="";e._container.style.width=e.width+"px";e._container.style.height=e.height+"px";e._container.style.visibility="visible";e._container.style.overflow="visible";e.autoplay&&e.popcorn.play()},end:function(b,e){if(e._capCont)e._capCont.style.display="none";e._container.style.width="0px";e._container.style.height= +"0px";e._container.style.visibility="hidden";e._container.style.overflow="hidden";e.popcorn.pause()},_teardown:function(b){b.popcorn&&b.popcorn.destory&&b.popcorn.destroy();document.getElementById(b.target)&&document.getElementById(b.target).removeChild(b._container)}})})(Popcorn,this);(function(r){r.plugin("code",function(f){var n=false,c=this,b=function(){var e=function(h){return function(i,j){var p=function(){n&&i.call(c,j);n&&h(p)};p()}};return window.webkitRequestAnimationFrame?e(window.webkitRequestAnimationFrame):window.mozRequestAnimationFrame?e(window.mozRequestAnimationFrame):e(function(h){window.setTimeout(h,16)})}();if(!f.onStart||typeof f.onStart!=="function")f.onStart=r.nop;if(f.onEnd&&typeof f.onEnd!=="function")f.onEnd=undefined;if(f.onFrame&&typeof f.onFrame!== +"function")f.onFrame=undefined;return{start:function(e,h){h.onStart.call(c,h);if(h.onFrame){n=true;b(h.onFrame,h)}},end:function(e,h){if(h.onFrame)n=false;h.onEnd&&h.onEnd.call(c,h)}}},{about:{name:"Popcorn Code Plugin",version:"0.1",author:"David Humphrey (@humphd)",website:"http://vocamus.net/dave"},options:{start:{elem:"input",type:"number",label:"Start"},end:{elem:"input",type:"number",label:"End"},onStart:{elem:"input",type:"function",label:"onStart"},onFrame:{elem:"input",type:"function",label:"onFrame", +optional:true},onEnd:{elem:"input",type:"function",label:"onEnd"}}})})(Popcorn);(function(r){var f=0;r.plugin("flickr",function(n){var c,b=document.getElementById(n.target),e,h,i,j,p=n.numberofimages||4,m=n.height||"50px",o=n.width||"50px",q=n.padding||"5px",s=n.border||"0px";c=document.createElement("div");c.id="flickr"+f;c.style.width="100%";c.style.height="100%";c.style.display="none";f++;b&&b.appendChild(c);var d=function(){if(e)setTimeout(function(){d()},5);else{h="http://api.flickr.com/services/rest/?method=flickr.people.findByUsername&";h+="username="+n.username+"&api_key="+ +n.apikey+"&format=json&jsoncallback=flickr";r.getJSONP(h,function(y){e=y.user.nsid;A()})}},A=function(){h="http://api.flickr.com/services/feeds/photos_public.gne?";if(e)h+="id="+e+"&";if(n.tags)h+="tags="+n.tags+"&";h+="lang=en-us&format=json&jsoncallback=flickr";r.xhr.getJSONP(h,function(y){var x=document.createElement("div");x.innerHTML="<p style='padding:"+q+";'>"+y.title+"<p/>";r.forEach(y.items,function(a,g){if(g<p){i=document.createElement("a");i.setAttribute("href",a.link);i.setAttribute("target", +"_blank");j=document.createElement("img");j.setAttribute("src",a.media.m);j.setAttribute("height",m);j.setAttribute("width",o);j.setAttribute("style","border:"+s+";padding:"+q);i.appendChild(j);x.appendChild(i)}else return false});c.appendChild(x)})};if(n.username&&n.apikey)d();else{e=n.userid;A()}return{start:function(){c.style.display="inline"},end:function(){c.style.display="none"},_teardown:function(y){document.getElementById(y.target)&&document.getElementById(y.target).removeChild(c)}}},{about:{name:"Popcorn Flickr Plugin", +version:"0.2",author:"Scott Downe, Steven Weerdenburg, Annasob",website:"http://scottdowne.wordpress.com/"},options:{start:{elem:"input",type:"number",label:"Start"},end:{elem:"input",type:"number",label:"End"},userid:{elem:"input",type:"text",label:"User ID",optional:true},tags:{elem:"input",type:"text",label:"Tags"},username:{elem:"input",type:"text",label:"Username",optional:true},apikey:{elem:"input",type:"text",label:"API Key",optional:true},target:"flickr-container",height:{elem:"input",type:"text", +label:"Height","default":"50px",optional:true},width:{elem:"input",type:"text",label:"Width","default":"50px",optional:true},padding:{elem:"input",type:"text",label:"Padding",optional:true},border:{elem:"input",type:"text",label:"Border","default":"5px",optional:true},numberofimages:{elem:"input",type:"number","default":4,label:"Number of Images"}}})})(Popcorn);(function(r){r.plugin("footnote",{manifest:{about:{name:"Popcorn Footnote Plugin",version:"0.2",author:"@annasob, @rwaldron",website:"annasob.wordpress.com"},options:{start:{elem:"input",type:"number",label:"Start"},end:{elem:"input",type:"number",label:"End"},text:{elem:"input",type:"text",label:"Text"},target:"footnote-container"}},_setup:function(f){var n=r.dom.find(f.target);f._container=document.createElement("div");f._container.style.display="none";f._container.innerHTML=f.text;n.appendChild(f._container)}, +start:function(f,n){n._container.style.display="inline"},end:function(f,n){n._container.style.display="none"},_teardown:function(f){var n=r.dom.find(f.target);n&&n.removeChild(f._container)}})})(Popcorn);(function(r){function f(b){return String(b).replace(/&(?!\w+;)|[<>"']/g,function(e){return c[e]||e})}function n(b,e){var h=b.container=document.createElement("div"),i=h.style,j=b.media,p=function(){var m=b.position();i.fontSize="18px";i.width=j.offsetWidth+"px";i.top=m.top+j.offsetHeight-h.offsetHeight-40+"px";i.left=m.left+"px";setTimeout(p,10)};h.id=e||"";i.position="absolute";i.color="white";i.textShadow="black 2px 2px 6px";i.fontWeight="bold";i.textAlign="center";p();b.media.parentNode.appendChild(h); +return h}var c={"&":"&","<":"<",">":">",'"':""","'":"'"};r.plugin("text",{manifest:{about:{name:"Popcorn Text Plugin",version:"0.1",author:"@humphd"},options:{start:{elem:"input",type:"number",label:"Start"},end:{elem:"input",type:"number",label:"End"},text:{elem:"input",type:"text",label:"Text","default":"Popcorn.js"},escape:{elem:"input",type:"checkbox",label:"Escape"},multiline:{elem:"input",type:"checkbox",label:"Multiline"}}},_setup:function(b){var e,h,i=b._container=document.createElement("div"); +i.style.display="none";if(b.target)if(e=r.dom.find(b.target)){if(["VIDEO","AUDIO"].indexOf(e.nodeName)>-1)e=n(this,b.target+"-overlay")}else e=n(this,b.target);else e=this.container?this.container:n(this);b._target=e;h=b.escape?f(b.text):b.text;h=b.multiline?h.replace(/\r?\n/gm,"<br>"):h;i.innerHTML=h||"";e.appendChild(i)},start:function(b,e){e._container.style.display="inline"},end:function(b,e){e._container.style.display="none"},_teardown:function(b){var e=b._target;e&&e.removeChild(b._container)}})})(Popcorn);var googleCallback; +(function(r){function f(i,j,p){i=i.type?i.type.toUpperCase():"HYBRID";var m;if(i==="STAMEN-WATERCOLOR"||i==="STAMEN-TERRAIN"||i==="STAMEN-TONER")m=i.replace("STAMEN-","").toLowerCase();p=new google.maps.Map(p,{mapTypeId:m?m:google.maps.MapTypeId[i],mapTypeControlOptions:{mapTypeIds:[]}});m&&p.mapTypes.set(m,new google.maps.StamenMapType(m));p.getDiv().style.display="none";return p}var n=1,c=false,b=false,e,h;googleCallback=function(i){if(typeof google!=="undefined"&&google.maps&&google.maps.Geocoder&& +google.maps.LatLng){e=new google.maps.Geocoder;r.getScript("//maps.stamen.com/js/tile.stamen.js",function(){b=true})}else setTimeout(function(){googleCallback(i)},1)};h=function(){if(document.body){c=true;r.getScript("//maps.google.com/maps/api/js?sensor=false&callback=googleCallback")}else setTimeout(function(){h()},1)};r.plugin("googlemap",function(i){var j,p,m,o=document.getElementById(i.target);i.type=i.type||"ROADMAP";i.zoom=i.zoom||1;i.lat=i.lat||0;i.lng=i.lng||0;c||h();j=document.createElement("div"); +j.id="actualmap"+n;j.style.width=i.width||"100%";j.style.height=i.height?i.height:o&&o.clientHeight?o.clientHeight+"px":"100%";n++;o&&o.appendChild(j);var q=function(){if(b){if(j)if(i.location)e.geocode({address:i.location},function(s,d){if(j&&d===google.maps.GeocoderStatus.OK){i.lat=s[0].geometry.location.lat();i.lng=s[0].geometry.location.lng();m=new google.maps.LatLng(i.lat,i.lng);p=f(i,m,j)}});else{m=new google.maps.LatLng(i.lat,i.lng);p=f(i,m,j)}}else setTimeout(function(){q()},5)};q();return{start:function(s, +d){var A=this,y,x=function(){if(p){d._map=p;p.getDiv().style.display="block";google.maps.event.trigger(p,"resize");p.setCenter(m);if(d.zoom&&typeof d.zoom!=="number")d.zoom=+d.zoom;p.setZoom(d.zoom);if(d.heading&&typeof d.heading!=="number")d.heading=+d.heading;if(d.pitch&&typeof d.pitch!=="number")d.pitch=+d.pitch;if(d.type==="STREETVIEW"){p.setStreetView(y=new google.maps.StreetViewPanorama(j,{position:m,pov:{heading:d.heading=d.heading||0,pitch:d.pitch=d.pitch||0,zoom:d.zoom}}));var a=function(z, +C){var E=google.maps.geometry.spherical.computeHeading;setTimeout(function(){var B=A.media.currentTime;if(typeof d.tween==="object"){for(var w=0,D=z.length;w<D;w++){var F=z[w];if(B>=F.interval*(w+1)/1E3&&(B<=F.interval*(w+2)/1E3||B>=F.interval*D/1E3)){u.setPosition(new google.maps.LatLng(F.position.lat,F.position.lng));u.setPov({heading:F.pov.heading||E(F,z[w+1])||0,zoom:F.pov.zoom||0,pitch:F.pov.pitch||0})}}a(z,z[0].interval)}else{w=0;for(D=z.length;w<D;w++){F=d.interval;if(B>=F*(w+1)/1E3&&(B<=F* +(w+2)/1E3||B>=F*D/1E3)){g.setPov({heading:E(z[w],z[w+1])||0,zoom:d.zoom,pitch:d.pitch||0});g.setPosition(l[w])}}a(l,d.interval)}},C)};if(d.location&&typeof d.tween==="string"){var g=y,l=[],k=new google.maps.DirectionsService,t=new google.maps.DirectionsRenderer(g);k.route({origin:d.location,destination:d.tween,travelMode:google.maps.TravelMode.DRIVING},function(z,C){if(C==google.maps.DirectionsStatus.OK){t.setDirections(z);for(var E=z.routes[0].overview_path,B=0,w=E.length;B<w;B++)l.push(new google.maps.LatLng(E[B].lat(), +E[B].lng()));d.interval=d.interval||1E3;a(l,10)}})}else if(typeof d.tween==="object"){var u=y;k=0;for(var v=d.tween.length;k<v;k++){d.tween[k].interval=d.tween[k].interval||1E3;a(d.tween,10)}}}d.onmaploaded&&d.onmaploaded(d,p)}else setTimeout(function(){x()},13)};x()},end:function(){if(p)p.getDiv().style.display="none"},_teardown:function(s){var d=document.getElementById(s.target);d&&d.removeChild(j);j=p=m=null;s._map=null}}},{about:{name:"Popcorn Google Map Plugin",version:"0.1",author:"@annasob", +website:"annasob.wordpress.com"},options:{start:{elem:"input",type:"start",label:"Start"},end:{elem:"input",type:"start",label:"End"},target:"map-container",type:{elem:"select",options:["ROADMAP","SATELLITE","STREETVIEW","HYBRID","TERRAIN","STAMEN-WATERCOLOR","STAMEN-TERRAIN","STAMEN-TONER"],label:"Map Type",optional:true},zoom:{elem:"input",type:"text",label:"Zoom","default":0,optional:true},lat:{elem:"input",type:"text",label:"Lat",optional:true},lng:{elem:"input",type:"text",label:"Lng",optional:true}, +location:{elem:"input",type:"text",label:"Location","default":"Toronto, Ontario, Canada"},heading:{elem:"input",type:"text",label:"Heading","default":0,optional:true},pitch:{elem:"input",type:"text",label:"Pitch","default":1,optional:true}}})})(Popcorn);(function(r){function f(b){function e(){var p=b.getBoundingClientRect(),m=i.getBoundingClientRect();if(m.left!==p.left)i.style.left=p.left+"px";if(m.top!==p.top)i.style.top=p.top+"px"}var h=-1,i=document.createElement("div"),j=getComputedStyle(b).zIndex;i.setAttribute("data-popcorn-helper-container",true);i.style.position="absolute";i.style.zIndex=isNaN(j)?n:j+1;document.body.appendChild(i);return{element:i,start:function(){h=setInterval(e,c)},stop:function(){clearInterval(h);h=-1},destroy:function(){document.body.removeChild(i); +h!==-1&&clearInterval(h)}}}var n=2E3,c=10;r.plugin("image",{manifest:{about:{name:"Popcorn image Plugin",version:"0.1",author:"Scott Downe",website:"http://scottdowne.wordpress.com/"},options:{start:{elem:"input",type:"number",label:"Start"},end:{elem:"input",type:"number",label:"End"},src:{elem:"input",type:"url",label:"Image URL","default":"http://mozillapopcorn.org/wp-content/themes/popcorn/images/for_developers.png"},href:{elem:"input",type:"url",label:"Link","default":"http://mozillapopcorn.org/wp-content/themes/popcorn/images/for_developers.png", +optional:true},target:"image-container",text:{elem:"input",type:"text",label:"Caption","default":"Popcorn.js",optional:true}}},_setup:function(b){var e=document.createElement("img"),h=document.getElementById(b.target);b.anchor=document.createElement("a");b.anchor.style.position="relative";b.anchor.style.textDecoration="none";b.anchor.style.display="none";if(h)if(["VIDEO","AUDIO"].indexOf(h.nodeName)>-1){b.trackedContainer=f(h);b.trackedContainer.element.appendChild(b.anchor)}else h&&h.appendChild(b.anchor); +e.addEventListener("load",function(){e.style.borderStyle="none";b.anchor.href=b.href||b.src||"#";b.anchor.target="_blank";var i,j;e.style.height=h.style.height;e.style.width=h.style.width;b.anchor.appendChild(e);if(b.text){i=e.height/12+"px";j=document.createElement("div");r.extend(j.style,{color:"black",fontSize:i,fontWeight:"bold",position:"relative",textAlign:"center",width:e.style.width||e.width+"px",zIndex:"10"});j.innerHTML=b.text||"";j.style.top=(e.style.height.replace("px","")||e.height)/ +2-j.offsetHeight/2+"px";b.anchor.insertBefore(j,e)}},false);e.src=b.src},start:function(b,e){e.anchor.style.display="inline";e.trackedContainer&&e.trackedContainer.start()},end:function(b,e){e.anchor.style.display="none";e.trackedContainer&&e.trackedContainer.stop()},_teardown:function(b){if(b.trackedContainer)b.trackedContainer.destroy();else b.anchor.parentNode&&b.anchor.parentNode.removeChild(b.anchor)}})})(Popcorn);(function(r){var f=1,n=false;r.plugin("googlefeed",function(c){var b=function(){var j=false,p=0,m=document.getElementsByTagName("link"),o=m.length,q=document.head||document.getElementsByTagName("head")[0],s=document.createElement("link");if(window.GFdynamicFeedControl)n=true;else r.getScript("//www.google.com/uds/solutions/dynamicfeed/gfdynamicfeedcontrol.js",function(){n=true});for(;p<o;p++)if(m[p].href==="//www.google.com/uds/solutions/dynamicfeed/gfdynamicfeedcontrol.css")j=true;if(!j){s.type= +"text/css";s.rel="stylesheet";s.href="//www.google.com/uds/solutions/dynamicfeed/gfdynamicfeedcontrol.css";q.insertBefore(s,q.firstChild)}};window.google?b():r.getScript("//www.google.com/jsapi",function(){google.load("feeds","1",{callback:function(){b()}})});var e=document.createElement("div"),h=document.getElementById(c.target),i=function(){if(n)c.feed=new GFdynamicFeedControl(c.url,e,{vertical:c.orientation.toLowerCase()==="vertical"?true:false,horizontal:c.orientation.toLowerCase()==="horizontal"? +true:false,title:c.title=c.title||"Blog"});else setTimeout(function(){i()},5)};if(!c.orientation||c.orientation.toLowerCase()!=="vertical"&&c.orientation.toLowerCase()!=="horizontal")c.orientation="vertical";e.style.display="none";e.id="_feed"+f;e.style.width="100%";e.style.height="100%";f++;h&&h.appendChild(e);i();return{start:function(){e.setAttribute("style","display:inline")},end:function(){e.setAttribute("style","display:none")},_teardown:function(j){document.getElementById(j.target)&&document.getElementById(j.target).removeChild(e); +delete j.feed}}},{about:{name:"Popcorn Google Feed Plugin",version:"0.1",author:"David Seifried",website:"dseifried.wordpress.com"},options:{start:{elem:"input",type:"number",label:"Start"},end:{elem:"input",type:"number",label:"End"},target:"feed-container",url:{elem:"input",type:"url",label:"Feed URL","default":"http://planet.mozilla.org/rss20.xml"},title:{elem:"input",type:"text",label:"Title","default":"Planet Mozilla",optional:true},orientation:{elem:"select",options:["Vertical","Horizontal"], +label:"Orientation","default":"Vertical",optional:true}}})})(Popcorn);(function(r){var f=0,n=function(c,b){var e=c.container=document.createElement("div"),h=e.style,i=c.media,j=function(){var p=c.position();h.fontSize="18px";h.width=i.offsetWidth+"px";h.top=p.top+i.offsetHeight-e.offsetHeight-40+"px";h.left=p.left+"px";setTimeout(j,10)};e.id=b||r.guid();h.position="absolute";h.color="white";h.textShadow="black 2px 2px 6px";h.fontWeight="bold";h.textAlign="center";j();c.media.parentNode.appendChild(e);return e};r.plugin("subtitle",{manifest:{about:{name:"Popcorn Subtitle Plugin", +version:"0.1",author:"Scott Downe",website:"http://scottdowne.wordpress.com/"},options:{start:{elem:"input",type:"text",label:"Start"},end:{elem:"input",type:"text",label:"End"},target:"subtitle-container",text:{elem:"input",type:"text",label:"Text"}}},_setup:function(c){var b=document.createElement("div");b.id="subtitle-"+f++;b.style.display="none";!this.container&&(!c.target||c.target==="subtitle-container")&&n(this);c.container=c.target&&c.target!=="subtitle-container"?document.getElementById(c.target)|| +n(this,c.target):this.container;document.getElementById(c.container.id)&&document.getElementById(c.container.id).appendChild(b);c.innerContainer=b;c.showSubtitle=function(){c.innerContainer.innerHTML=c.text||""}},start:function(c,b){b.innerContainer.style.display="inline";b.showSubtitle(b,b.text)},end:function(c,b){b.innerContainer.style.display="none";b.innerContainer.innerHTML=""},_teardown:function(c){c.container.removeChild(c.innerContainer)}})})(Popcorn);(function(r){var f=false;r.plugin("twitter",{manifest:{about:{name:"Popcorn Twitter Plugin",version:"0.1",author:"Scott Downe",website:"http://scottdowne.wordpress.com/"},options:{start:{elem:"input",type:"number",label:"Start"},end:{elem:"input",type:"number",label:"End"},src:{elem:"input",type:"text",label:"Tweet Source (# or @)","default":"@popcornjs"},target:"twitter-container",height:{elem:"input",type:"number",label:"Height","default":"200",optional:true},width:{elem:"input",type:"number",label:"Width", +"default":"250",optional:true}}},_setup:function(n){if(!window.TWTR&&!f){f=true;r.getScript("//widgets.twimg.com/j/2/widget.js")}var c=document.getElementById(n.target);n.container=document.createElement("div");n.container.setAttribute("id",r.guid());n.container.style.display="none";c&&c.appendChild(n.container);var b=n.src||"";c=n.width||250;var e=n.height||200,h=/^@/.test(b),i={version:2,id:n.container.getAttribute("id"),rpp:30,width:c,height:e,interval:6E3,theme:{shell:{background:"#ffffff",color:"#000000"}, +tweets:{background:"#ffffff",color:"#444444",links:"#1985b5"}},features:{loop:true,timestamp:true,avatars:true,hashtags:true,toptweets:true,live:true,scrollbar:false,behavior:"default"}},j=function(p){if(window.TWTR)if(h){i.type="profile";(new TWTR.Widget(i)).render().setUser(b).start()}else{i.type="search";i.search=b;i.subject=b;(new TWTR.Widget(i)).render().start()}else setTimeout(function(){j(p)},1)};j(this)},start:function(n,c){c.container.style.display="inline"},end:function(n,c){c.container.style.display= +"none"},_teardown:function(n){document.getElementById(n.target)&&document.getElementById(n.target).removeChild(n.container)}})})(Popcorn);(function(r){r.plugin("webpage",{manifest:{about:{name:"Popcorn Webpage Plugin",version:"0.1",author:"@annasob",website:"annasob.wordpress.com"},options:{id:{elem:"input",type:"text",label:"Id",optional:true},start:{elem:"input",type:"number",label:"Start"},end:{elem:"input",type:"number",label:"End"},src:{elem:"input",type:"url",label:"Webpage URL","default":"http://mozillapopcorn.org"},target:"iframe-container"}},_setup:function(f){var n=document.getElementById(f.target);f.src=f.src.replace(/^(https?:)?(\/\/)?/, +"//");f._iframe=document.createElement("iframe");f._iframe.setAttribute("width","100%");f._iframe.setAttribute("height","100%");f._iframe.id=f.id;f._iframe.src=f.src;f._iframe.style.display="none";n&&n.appendChild(f._iframe)},start:function(f,n){n._iframe.src=n.src;n._iframe.style.display="inline"},end:function(f,n){n._iframe.style.display="none"},_teardown:function(f){document.getElementById(f.target)&&document.getElementById(f.target).removeChild(f._iframe)}})})(Popcorn);var wikiCallback; +(function(r){r.plugin("wikipedia",{manifest:{about:{name:"Popcorn Wikipedia Plugin",version:"0.1",author:"@annasob",website:"annasob.wordpress.com"},options:{start:{elem:"input",type:"number",label:"Start"},end:{elem:"input",type:"number",label:"End"},lang:{elem:"input",type:"text",label:"Language","default":"english",optional:true},src:{elem:"input",type:"url",label:"Wikipedia URL","default":"http://en.wikipedia.org/wiki/Cat"},title:{elem:"input",type:"text",label:"Title","default":"Cats",optional:true}, +numberofwords:{elem:"input",type:"number",label:"Number of Words","default":"200",optional:true},target:"wikipedia-container"}},_setup:function(f){var n,c=r.guid();if(!f.lang)f.lang="en";f.numberofwords=f.numberofwords||200;window["wikiCallback"+c]=function(b){f._link=document.createElement("a");f._link.setAttribute("href",f.src);f._link.setAttribute("target","_blank");f._link.innerHTML=f.title||b.parse.displaytitle;f._desc=document.createElement("p");n=b.parse.text["*"].substr(b.parse.text["*"].indexOf("<p>")); +n=n.replace(/((<(.|\n)+?>)|(\((.*?)\) )|(\[(.*?)\]))/g,"");n=n.split(" ");f._desc.innerHTML=n.slice(0,n.length>=f.numberofwords?f.numberofwords:n.length).join(" ")+" ...";f._fired=true};f.src&&r.getScript("//"+f.lang+".wikipedia.org/w/api.php?action=parse&props=text&redirects&page="+f.src.slice(f.src.lastIndexOf("/")+1)+"&format=json&callback=wikiCallback"+c)},start:function(f,n){var c=function(){if(n._fired){if(n._link&&n._desc)if(document.getElementById(n.target)){document.getElementById(n.target).appendChild(n._link); +document.getElementById(n.target).appendChild(n._desc);n._added=true}}else setTimeout(function(){c()},13)};c()},end:function(f,n){if(n._added){document.getElementById(n.target).removeChild(n._link);document.getElementById(n.target).removeChild(n._desc)}},_teardown:function(f){if(f._added){f._link.parentNode&&document.getElementById(f.target).removeChild(f._link);f._desc.parentNode&&document.getElementById(f.target).removeChild(f._desc);delete f.target}}})})(Popcorn);(function(r){r.plugin("mustache",function(f){var n,c,b,e;r.getScript("http://mustache.github.com/extras/mustache.js");var h=!!f.dynamic,i=typeof f.template,j=typeof f.data,p=document.getElementById(f.target);f.container=p||document.createElement("div");if(i==="function")if(h)b=f.template;else e=f.template(f);else e=i==="string"?f.template:"";if(j==="function")if(h)n=f.data;else c=f.data(f);else c=j==="string"?JSON.parse(f.data):j==="object"?f.data:"";return{start:function(m,o){var q=function(){if(window.Mustache){if(n)c= +n(o);if(b)e=b(o);var s=Mustache.to_html(e,c).replace(/^\s*/mg,"");o.container.innerHTML=s}else setTimeout(function(){q()},10)};q()},end:function(m,o){o.container.innerHTML=""},_teardown:function(){n=c=b=e=null}}},{about:{name:"Popcorn Mustache Plugin",version:"0.1",author:"David Humphrey (@humphd)",website:"http://vocamus.net/dave"},options:{start:{elem:"input",type:"number",label:"Start"},end:{elem:"input",type:"number",label:"End"},target:"mustache-container",template:{elem:"input",type:"text", +label:"Template"},data:{elem:"input",type:"text",label:"Data"},dynamic:{elem:"input",type:"checkbox",label:"Dynamic","default":true}}})})(Popcorn);(function(r){function f(c,b){if(c.map)c.map.div.style.display=b;else setTimeout(function(){f(c,b)},10)}var n=1;r.plugin("openmap",function(c){var b,e,h,i,j,p,m,o,q=document.getElementById(c.target);b=document.createElement("div");b.id="openmapdiv"+n;b.style.width="100%";b.style.height="100%";n++;q&&q.appendChild(b);o=function(){if(window.OpenLayers&&window.OpenLayers.Layer.Stamen){if(c.location){location=new OpenLayers.LonLat(0,0);r.getJSONP("//tinygeocoder.com/create-api.php?q="+c.location+"&callback=jsonp", +function(d){e=new OpenLayers.LonLat(d[1],d[0])})}else e=new OpenLayers.LonLat(c.lng,c.lat);c.type=c.type||"ROADMAP";switch(c.type){case "SATELLITE":c.map=new OpenLayers.Map({div:b,maxResolution:0.28125,tileSize:new OpenLayers.Size(512,512)});var s=new OpenLayers.Layer.WorldWind("LANDSAT","//worldwind25.arc.nasa.gov/tile/tile.aspx",2.25,4,{T:"105"});c.map.addLayer(s);i=new OpenLayers.Projection("EPSG:4326");h=new OpenLayers.Projection("EPSG:4326");break;case "TERRAIN":i=new OpenLayers.Projection("EPSG:4326"); +h=new OpenLayers.Projection("EPSG:4326");c.map=new OpenLayers.Map({div:b,projection:h});s=new OpenLayers.Layer.WMS("USGS Terraserver","//terraserver-usa.org/ogcmap.ashx?",{layers:"DRG"});c.map.addLayer(s);break;case "STAMEN-TONER":case "STAMEN-WATERCOLOR":case "STAMEN-TERRAIN":s=c.type.replace("STAMEN-","").toLowerCase();s=new OpenLayers.Layer.Stamen(s);i=new OpenLayers.Projection("EPSG:4326");h=new OpenLayers.Projection("EPSG:900913");e=e.transform(i,h);c.map=new OpenLayers.Map({div:b,projection:h, +displayProjection:i,controls:[new OpenLayers.Control.Navigation,new OpenLayers.Control.PanPanel,new OpenLayers.Control.ZoomPanel]});c.map.addLayer(s);break;default:h=new OpenLayers.Projection("EPSG:900913");i=new OpenLayers.Projection("EPSG:4326");e=e.transform(i,h);c.map=new OpenLayers.Map({div:b,projection:h,displayProjection:i});s=new OpenLayers.Layer.OSM;c.map.addLayer(s)}if(c.map){c.map.setCenter(e,c.zoom||10);c.map.div.style.display="none"}}else setTimeout(function(){o()},50)};o();return{_setup:function(s){window.OpenLayers|| +r.getScript("//openlayers.org/api/OpenLayers.js",function(){r.getScript("//maps.stamen.com/js/tile.stamen.js")});var d=function(){if(s.map){s.zoom=s.zoom||2;if(s.zoom&&typeof s.zoom!=="number")s.zoom=+s.zoom;s.map.setCenter(e,s.zoom);if(s.markers){var A=OpenLayers.Util.extend({},OpenLayers.Feature.Vector.style["default"]),y=function(v){clickedFeature=v.feature;if(clickedFeature.attributes.text){m=new OpenLayers.Popup.FramedCloud("featurePopup",clickedFeature.geometry.getBounds().getCenterLonLat(), +new OpenLayers.Size(120,250),clickedFeature.attributes.text,null,true,function(){p.unselect(this.feature)});clickedFeature.popup=m;m.feature=clickedFeature;s.map.addPopup(m)}},x=function(v){feature=v.feature;if(feature.popup){m.feature=null;s.map.removePopup(feature.popup);feature.popup.destroy();feature.popup=null}},a=function(v){r.getJSONP("//tinygeocoder.com/create-api.php?q="+v.location+"&callback=jsonp",function(z){z=(new OpenLayers.Geometry.Point(z[1],z[0])).transform(i,h);var C=OpenLayers.Util.extend({}, +A);if(!v.size||isNaN(v.size))v.size=14;C.pointRadius=v.size;C.graphicOpacity=1;C.externalGraphic=v.icon;z=new OpenLayers.Feature.Vector(z,null,C);if(v.text)z.attributes={text:v.text};j.addFeatures([z])})};j=new OpenLayers.Layer.Vector("Point Layer",{style:A});s.map.addLayer(j);for(var g=0,l=s.markers.length;g<l;g++){var k=s.markers[g];if(k.text)if(!p){p=new OpenLayers.Control.SelectFeature(j);s.map.addControl(p);p.activate();j.events.on({featureselected:y,featureunselected:x})}if(k.location)a(k); +else{var t=(new OpenLayers.Geometry.Point(k.lng,k.lat)).transform(i,h),u=OpenLayers.Util.extend({},A);if(!k.size||isNaN(k.size))k.size=14;u.pointRadius=k.size;u.graphicOpacity=1;u.externalGraphic=k.icon;t=new OpenLayers.Feature.Vector(t,null,u);if(k.text)t.attributes={text:k.text};j.addFeatures([t])}}}}else setTimeout(function(){d()},13)};d()},start:function(s,d){f(d,"block")},end:function(s,d){f(d,"none")},_teardown:function(){q&&q.removeChild(b);b=map=e=h=i=j=p=m=null}}},{about:{name:"Popcorn OpenMap Plugin", +version:"0.3",author:"@mapmeld",website:"mapadelsur.blogspot.com"},options:{start:{elem:"input",type:"number",label:"Start"},end:{elem:"input",type:"number",label:"End"},target:"map-container",type:{elem:"select",options:["ROADMAP","SATELLITE","TERRAIN"],label:"Map Type",optional:true},zoom:{elem:"input",type:"number",label:"Zoom","default":2},lat:{elem:"input",type:"text",label:"Lat",optional:true},lng:{elem:"input",type:"text",label:"Lng",optional:true},location:{elem:"input",type:"text",label:"Location", +"default":"Toronto, Ontario, Canada"},markers:{elem:"input",type:"text",label:"List Markers",optional:true}}})})(Popcorn);document.addEventListener("click",function(r){r=r.target;if(r.nodeName==="A"||r.parentNode&&r.parentNode.nodeName==="A")Popcorn.instances.forEach(function(f){f.options.pauseOnLinkClicked&&f.pause()})},false);(function(r){var f={},n=0,c=document.createElement("span"),b=["webkit","Moz","ms","O",""],e=["Transform","TransitionDuration","TransitionTimingFunction"],h={},i;document.getElementsByTagName("head")[0].appendChild(c);for(var j=0,p=e.length;j<p;j++)for(var m=0,o=b.length;m<o;m++){i=b[m]+e[j];if(i in c.style){h[e[j].toLowerCase()]=i;break}}document.getElementsByTagName("head")[0].appendChild(c);r.plugin("wordriver",{manifest:{about:{name:"Popcorn WordRiver Plugin"},options:{start:{elem:"input",type:"number", +label:"Start"},end:{elem:"input",type:"number",label:"End"},target:"wordriver-container",text:{elem:"input",type:"text",label:"Text","default":"Popcorn.js"},color:{elem:"input",type:"text",label:"Color","default":"Green",optional:true}}},_setup:function(q){q._duration=q.end-q.start;var s;if(!(s=f[q.target])){s=q.target;f[s]=document.createElement("div");var d=document.getElementById(s);d&&d.appendChild(f[s]);f[s].style.height="100%";f[s].style.position="relative";s=f[s]}q._container=s;q.word=document.createElement("span"); +q.word.style.position="absolute";q.word.style.whiteSpace="nowrap";q.word.style.opacity=0;q.word.style.MozTransitionProperty="opacity, -moz-transform";q.word.style.webkitTransitionProperty="opacity, -webkit-transform";q.word.style.OTransitionProperty="opacity, -o-transform";q.word.style.transitionProperty="opacity, transform";q.word.style[h.transitionduration]="1s, "+q._duration+"s";q.word.style[h.transitiontimingfunction]="linear";q.word.innerHTML=q.text;q.word.style.color=q.color||"black"},start:function(q, +s){s._container.appendChild(s.word);s.word.style[h.transform]="";s.word.style.fontSize=~~(30+20*Math.random())+"px";n%=s._container.offsetWidth-s.word.offsetWidth;s.word.style.left=n+"px";n+=s.word.offsetWidth+10;s.word.style[h.transform]="translateY("+(s._container.offsetHeight-s.word.offsetHeight)+"px)";s.word.style.opacity=1;setTimeout(function(){s.word.style.opacity=0},(s.end-s.start-1||1)*1E3)},end:function(q,s){s.word.style.opacity=0},_teardown:function(q){var s=document.getElementById(q.target); +q.word.parentNode&&q._container.removeChild(q.word);f[q.target]&&!f[q.target].childElementCount&&s&&s.removeChild(f[q.target])&&delete f[q.target]}})})(Popcorn);(function(r){var f=1;r.plugin("timeline",function(n){var c=document.getElementById(n.target),b=document.createElement("div"),e,h=true;if(c&&!c.firstChild){c.appendChild(e=document.createElement("div"));e.style.width="inherit";e.style.height="inherit";e.style.overflow="auto"}else e=c.firstChild;b.style.display="none";b.id="timelineDiv"+f;n.direction=n.direction||"up";if(n.direction.toLowerCase()==="down")h=false;if(c&&e)h?e.insertBefore(b,e.firstChild):e.appendChild(b);f++;b.innerHTML="<p><span id='big' style='font-size:24px; line-height: 130%;' >"+ +n.title+"</span><br /><span id='mid' style='font-size: 16px;'>"+n.text+"</span><br />"+n.innerHTML;return{start:function(i,j){b.style.display="block";if(j.direction==="down")e.scrollTop=e.scrollHeight},end:function(){b.style.display="none"},_teardown:function(){e&&b&&e.removeChild(b)&&!e.firstChild&&c.removeChild(e)}}},{about:{name:"Popcorn Timeline Plugin",version:"0.1",author:"David Seifried @dcseifried",website:"dseifried.wordpress.com"},options:{start:{elem:"input",type:"number",label:"Start"}, +end:{elem:"input",type:"number",label:"End"},target:"feed-container",title:{elem:"input",type:"text",label:"Title"},text:{elem:"input",type:"text",label:"Text"},innerHTML:{elem:"input",type:"text",label:"HTML Code",optional:true},direction:{elem:"select",options:["DOWN","UP"],label:"Direction",optional:true}}})})(Popcorn);(function(r,f){var n={};r.plugin("documentcloud",{manifest:{about:{name:"Popcorn Document Cloud Plugin",version:"0.1",author:"@humphd, @ChrisDeCairos",website:"http://vocamus.net/dave"},options:{start:{elem:"input",type:"number",label:"Start"},end:{elem:"input",type:"number",label:"End"},target:"documentcloud-container",width:{elem:"input",type:"text",label:"Width",optional:true},height:{elem:"input",type:"text",label:"Height",optional:true},src:{elem:"input",type:"url",label:"PDF URL","default":"http://www.documentcloud.org/documents/70050-urbina-day-1-in-progress.html"}, +preload:{elem:"input",type:"checkbox",label:"Preload","default":true},page:{elem:"input",type:"number",label:"Page Number",optional:true},aid:{elem:"input",type:"number",label:"Annotation Id",optional:true}}},_setup:function(c){function b(){function m(v){c._key=v.api.getId();c._changeView=function(z){c.aid?z.pageSet.showAnnotation(z.api.getAnnotation(c.aid)):z.api.setCurrentPage(c.page)}}function o(){n[c._key]={num:1,id:c._containerId};h.loaded=true}h.loaded=false;var q=c.url.replace(/\.html$/,".js"), +s=c.target,d=f.getElementById(s),A=f.createElement("div"),y=r.position(d),x=c.width||y.width;y=c.height||y.height;var a=c.sidebar||true,g=c.text||true,l=c.pdf||true,k=c.showAnnotations||true,t=c.zoom||700,u=c.search||true;if(!function(v){var z=false;r.forEach(h.viewers,function(C){if(C.api.getSchema().canonicalURL===v){m(C);C=n[c._key];c._containerId=C.id;C.num+=1;z=true;h.loaded=true}});return z}(c.url)){A.id=c._containerId=r.guid(s);s="#"+A.id;d.appendChild(A);i.trigger("documentready");h.load(q, +{width:x,height:y,sidebar:a,text:g,pdf:l,showAnnotations:k,zoom:t,search:u,container:s,afterLoad:c.page||c.aid?function(v){m(v);c._changeView(v);A.style.visibility="hidden";v.elements.pages.hide();o()}:function(v){m(v);o();A.style.visibility="hidden";v.elements.pages.hide()}})}}function e(){window.DV.loaded?b():setTimeout(e,25)}var h=window.DV=window.DV||{},i=this;if(h.loading)e();else{h.loading=true;h.recordHit="//www.documentcloud.org/pixel.gif";var j=f.createElement("link"),p=f.getElementsByTagName("head")[0]; +j.rel="stylesheet";j.type="text/css";j.media="screen";j.href="//s3.documentcloud.org/viewer/viewer-datauri.css";p.appendChild(j);h.loaded=false;r.getScript("http://s3.documentcloud.org/viewer/viewer.js",function(){h.loading=false;b()})}},start:function(c,b){var e=f.getElementById(b._containerId),h=DV.viewers[b._key];(b.page||b.aid)&&h&&b._changeView(h);if(e&&h){e.style.visibility="visible";h.elements.pages.show()}},end:function(c,b){var e=f.getElementById(b._containerId);if(e&&DV.viewers[b._key]){e.style.visibility= +"hidden";DV.viewers[b._key].elements.pages.hide()}},_teardown:function(c){var b=f.getElementById(c._containerId);if((c=c._key)&&DV.viewers[c]&&--n[c].num===0){for(DV.viewers[c].api.unload();b.hasChildNodes();)b.removeChild(b.lastChild);b.parentNode.removeChild(b)}}})})(Popcorn,window.document);(function(r){r.parser("parseJSON","JSON",function(f){var n={title:"",remote:"",data:[]};r.forEach(f.data,function(c){n.data.push(c)});return n})})(Popcorn);(function(r){r.parser("parseSBV",function(f){var n={title:"",remote:"",data:[]},c=[],b=0,e=0,h=function(q){q=q.split(":");var s=q.length-1,d;try{d=parseInt(q[s-1],10)*60+parseFloat(q[s],10);if(s===2)d+=parseInt(q[0],10)*3600}catch(A){throw"Bad cue";}return d},i=function(q,s){var d={};d[q]=s;return d};f=f.text.split(/(?:\r\n|\r|\n)/gm);for(e=f.length;b<e;){var j={},p=[],m=f[b++].split(",");try{j.start=h(m[0]);for(j.end=h(m[1]);b<e&&f[b];)p.push(f[b++]);j.text=p.join("<br />");c.push(i("subtitle",j))}catch(o){for(;b< +e&&f[b];)b++}for(;b<e&&!f[b];)b++}n.data=c;return n})})(Popcorn);(function(r){function f(c,b){var e={};e[c]=b;return e}function n(c){c=c.split(":");try{var b=c[2].split(",");if(b.length===1)b=c[2].split(".");return parseFloat(c[0],10)*3600+parseFloat(c[1],10)*60+parseFloat(b[0],10)+parseFloat(b[1],10)/1E3}catch(e){return 0}}r.parser("parseSRT",function(c){var b={title:"",remote:"",data:[]},e=[],h=0,i=0,j,p,m,o;c=c.text.split(/(?:\r\n|\r|\n)/gm);for(h=c.length-1;h>=0&&!c[h];)h--;m=h+1;for(h=0;h<m;h++){o={};p=[];o.id=parseInt(c[h++],10);j=c[h++].split(/[\t ]*--\>[\t ]*/); +o.start=n(j[0]);i=j[1].indexOf(" ");if(i!==-1)j[1]=j[1].substr(0,i);for(o.end=n(j[1]);h<m&&c[h];)p.push(c[h++]);o.text=p.join("\\N").replace(/\{(\\[\w]+\(?([\w\d]+,?)+\)?)+\}/gi,"");o.text=o.text.replace(/</g,"<").replace(/>/g,">");o.text=o.text.replace(/<(\/?(font|b|u|i|s))((\s+(\w|\w[\w\-]*\w)(\s*=\s*(?:\".*?\"|'.*?'|[^'\">\s]+))?)+\s*|\s*)(\/?)>/gi,"<$1$3$7>");o.text=o.text.replace(/\\N/gi,"<br />");e.push(f("subtitle",o))}b.data=e;return b})})(Popcorn);(function(r){function f(b,e){var h=b.substr(10).split(","),i;i={start:n(h[e.start]),end:n(h[e.end])};if(i.start===-1||i.end===-1)throw"Invalid time";var j=q.call(m,/\{(\\[\w]+\(?([\w\d]+,?)+\)?)+\}/gi,""),p=j.replace,m;m=h.length;q=[];for(var o=e.text;o<m;o++)q.push(h[o]);m=q.join(",");var q=m.replace;i.text=p.call(j,/\\N/gi,"<br />");return i}function n(b){var e=b.split(":");if(b.length!==10||e.length<3)return-1;return parseInt(e[0],10)*3600+parseInt(e[1],10)*60+parseFloat(e[2],10)}function c(b, +e){var h={};h[b]=e;return h}r.parser("parseSSA",function(b){var e={title:"",remote:"",data:[]},h=[],i=0,j;b=b.text.split(/(?:\r\n|\r|\n)/gm);for(j=b.length;i<j&&b[i]!=="[Events]";)i++;var p=b[++i].substr(8).split(", "),m={},o,q;q=0;for(o=p.length;q<o;q++)if(p[q]==="Start")m.start=q;else if(p[q]==="End")m.end=q;else if(p[q]==="Text")m.text=q;for(;++i<j&&b[i]&&b[i][0]!=="[";)try{h.push(c("subtitle",f(b[i],m)))}catch(s){}e.data=h;return e})})(Popcorn);(function(r){function f(i,j,p){var m=i.firstChild;i=n(i,p);p=[];for(var o;m;){if(m.nodeType===1)if(m.nodeName==="p")p.push(c(m,j,i));else if(m.nodeName==="div"){o=b(m.getAttribute("begin"));if(o<0)o=j;p.push.apply(p,f(m,o,i))}m=m.nextSibling}return p}function n(i,j){var p=i.getAttribute("region");return p!==null?p:j||""}function c(i,j,p){var m={};m.text=(i.textContent||i.text).replace(e,"").replace(h,"<br />");m.id=i.getAttribute("xml:id")||i.getAttribute("id");m.start=b(i.getAttribute("begin"),j); +m.end=b(i.getAttribute("end"),j);m.target=n(i,p);if(m.end<0){m.end=b(i.getAttribute("duration"),0);if(m.end>=0)m.end+=m.start;else m.end=Number.MAX_VALUE}return{subtitle:m}}function b(i,j){var p;if(!i)return-1;try{return r.util.toSeconds(i)}catch(m){for(var o=i.length-1;o>=0&&i[o]<="9"&&i[o]>="0";)o--;p=o;o=parseFloat(i.substring(0,p));p=i.substring(p);return o*({h:3600,m:60,s:1,ms:0.0010}[p]||-1)+(j||0)}}var e=/^[\s]+|[\s]+$/gm,h=/(?:\r\n|\r|\n)/gm;r.parser("parseTTML",function(i){var j={title:"", +remote:"",data:[]};if(!i.xml||!i.xml.documentElement)return j;i=i.xml.documentElement.firstChild;if(!i)return j;for(;i.nodeName!=="body";)i=i.nextSibling;if(i)j.data=f(i,0);return j})})(Popcorn);(function(r){r.parser("parseTTXT",function(f){var n={title:"",remote:"",data:[]},c=function(j){j=j.split(":");var p=0;try{return parseFloat(j[0],10)*60*60+parseFloat(j[1],10)*60+parseFloat(j[2],10)}catch(m){p=0}return p},b=function(j,p){var m={};m[j]=p;return m};f=f.xml.lastChild.lastChild;for(var e=Number.MAX_VALUE,h=[];f;){if(f.nodeType===1&&f.nodeName==="TextSample"){var i={};i.start=c(f.getAttribute("sampleTime"));i.text=f.getAttribute("text");if(i.text){i.end=e-0.0010;h.push(b("subtitle",i))}e= +i.start}f=f.previousSibling}n.data=h.reverse();return n})})(Popcorn);(function(r){function f(c){var b=c.split(":");c=c.length;var e;if(c!==12&&c!==9)throw"Bad cue";c=b.length-1;try{e=parseInt(b[c-1],10)*60+parseFloat(b[c],10);if(c===2)e+=parseInt(b[0],10)*3600}catch(h){throw"Bad cue";}return e}function n(c,b){var e={};e[c]=b;return e}r.parser("parseVTT",function(c){var b={title:"",remote:"",data:[]},e=[],h=0,i=0,j,p;c=c.text.split(/(?:\r\n|\r|\n)/gm);i=c.length;if(i===0||c[0]!=="WEBVTT")return b;for(h++;h<i;){j=[];try{for(var m=h;m<i&&!c[m];)m++;h=m;var o=c[h++];m= +void 0;var q={};if(!o||o.indexOf("--\>")===-1)throw"Bad cue";m=o.replace(/--\>/," --\> ").split(/[\t ]+/);if(m.length<2)throw"Bad cue";q.id=o;q.start=f(m[0]);q.end=f(m[2]);for(p=q;h<i&&c[h];)j.push(c[h++]);p.text=j.join("<br />");e.push(n("subtitle",p))}catch(s){for(h=h;h<i&&c[h];)h++;h=h}}b.data=e;return b})})(Popcorn);(function(r){r.parser("parseXML","XML",function(f){var n={title:"",remote:"",data:[]},c={},b=function(m){m=m.split(":");if(m.length===1)return parseFloat(m[0],10);else if(m.length===2)return parseFloat(m[0],10)+parseFloat(m[1]/12,10);else if(m.length===3)return parseInt(m[0]*60,10)+parseFloat(m[1],10)+parseFloat(m[2]/12,10);else if(m.length===4)return parseInt(m[0]*3600,10)+parseInt(m[1]*60,10)+parseFloat(m[2],10)+parseFloat(m[3]/12,10)},e=function(m){for(var o={},q=0,s=m.length;q<s;q++){var d=m.item(q).nodeName, +A=m.item(q).nodeValue,y=c[A];if(d==="in")o.start=b(A);else if(d==="out")o.end=b(A);else if(d==="resourceid")for(var x in y){if(y.hasOwnProperty(x))if(!o[x]&&x!=="id")o[x]=y[x]}else o[d]=A}return o},h=function(m,o){var q={};q[m]=o;return q},i=function(m,o,q){var s={};r.extend(s,o,e(m.attributes),{text:m.textContent||m.text});o=m.childNodes;if(o.length<1||o.length===1&&o[0].nodeType===3)if(q)c[s.id]=s;else n.data.push(h(m.nodeName,s));else for(m=0;m<o.length;m++)o[m].nodeType===1&&i(o[m],s,q)};f=f.documentElement.childNodes; +for(var j=0,p=f.length;j<p;j++)if(f[j].nodeType===1)f[j].nodeName==="manifest"?i(f[j],{},true):i(f[j],{},false);return n})})(Popcorn);(function(){var r=false,f=false;Popcorn.player("soundcloud",{_canPlayType:function(n,c){return/(?:http:\/\/www\.|http:\/\/|www\.|\.|^)(soundcloud)/.test(c)&&n.toLowerCase()!=="video"},_setup:function(n){function c(){r=true;SC.initialize({client_id:"PRaNFlda6Bhf5utPjUsptg"});SC.get("/resolve",{url:e.src},function(A){e.width=e.style.width?""+e.offsetWidth:"560";e.height=e.style.height?""+e.offsetHeight:"315";h.scrolling="no";h.frameborder="no";h.id="soundcloud-"+Popcorn.guid();h.src="http://w.soundcloud.com/player/?url="+ +A.uri+"&show_artwork=false&buying=false&liking=false&sharing=false";h.width="100%";h.height="100%";n.loadListener=function(){n.widget=o=SC.Widget(h.id);o.bind(SC.Widget.Events.FINISH,function(){e.pause();e.dispatchEvent("ended")});o.bind(SC.Widget.Events.PLAY_PROGRESS,function(y){j=y.currentPosition/1E3;e.dispatchEvent("timeupdate")});o.bind(SC.Widget.Events.PLAY,function(){p=m=false;e.dispatchEvent("play");e.dispatchEvent("playing");e.currentTime=j;d.next()});o.bind(SC.Widget.Events.PAUSE,function(){p= +m=true;e.dispatchEvent("pause");d.next()});o.bind(SC.Widget.Events.READY,function(){o.getDuration(function(y){q=y/1E3;e.style.visibility="visible";e.dispatchEvent("durationchange");e.readyState=4;e.dispatchEvent("readystatechange");e.dispatchEvent("loadedmetadata");e.dispatchEvent("loadeddata");e.dispatchEvent("canplaythrough");e.dispatchEvent("load");!e.paused&&e.play()});o.getVolume(function(y){i=y/100})})};h.addEventListener("load",n.loadListener,false);e.appendChild(h)})}function b(){if(f)(function A(){setTimeout(function(){r? +c():A()},100)})();else{f=true;Popcorn.getScript("http://w.soundcloud.com/player/api.js",function(){Popcorn.getScript("http://connect.soundcloud.com/sdk.js",function(){c()})})}}var e=this,h=document.createElement("iframe"),i=1,j=0,p=true,m=true,o,q=0,s=false,d=Popcorn.player.playerQueue();n._container=h;e.style.visibility="hidden";e.play=function(){p=false;d.add(function(){if(m)o&&o.play();else d.next()})};e.pause=function(){p=true;d.add(function(){if(m)d.next();else o&&o.pause()})};Object.defineProperties(e, +{muted:{set:function(A){if(A){o&&o.getVolume(function(y){i=y/100});o&&o.setVolume(0);s=true}else{o&&o.setVolume(i*100);s=false}e.dispatchEvent("volumechange")},get:function(){return s}},volume:{set:function(A){o&&o.setVolume(A*100);i=A;e.dispatchEvent("volumechange")},get:function(){return s?0:i}},currentTime:{set:function(A){j=A;o&&o.seekTo(A*1E3);e.dispatchEvent("seeked");e.dispatchEvent("timeupdate")},get:function(){return j}},duration:{get:function(){return q}},paused:{get:function(){return p}}}); +r?c():b()},_teardown:function(n){var c=n.widget,b=SC.Widget.Events,e=n._container;n.destroyed=true;if(c)for(var h in b)c&&c.unbind(b[h]);else e.removeEventListener("load",n.loadEventListener,false)}})})();(function(){function r(n){var c=r.options;n=c.parser[c.strictMode?"strict":"loose"].exec(n);for(var b={},e=14;e--;)b[c.key[e]]=n[e]||"";b[c.q.name]={};b[c.key[12]].replace(c.q.parser,function(h,i,j){if(i)b[c.q.name][i]=j});return b}function f(n,c){return/player.vimeo.com\/video\/\d+/.test(c)||/vimeo.com\/\d+/.test(c)}r.options={strictMode:false,key:["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"],q:{name:"queryKey", +parser:/(?:^|&)([^&=]*)=?([^&]*)/g},parser:{strict:/^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,loose:/^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/}};Popcorn.player("vimeo",{_canPlayType:f,_setup:function(n){function c(l,k){var t=y.src.split("?")[0],u=JSON.stringify({method:l, +value:k});if(t.substr(0,2)==="//")t=window.location.protocol+t;y.contentWindow?y.contentWindow.postMessage(u,t):o.unload()}function b(l){if(l.origin==="http://player.vimeo.com"){var k;try{k=JSON.parse(l.data)}catch(t){console.warn(t)}if(k.player_id==m){k.method&&a[k.method]&&a[k.method](k);k.event&&g[k.event]&&g[k.event](k)}}}function e(){d||(d=setInterval(function(){o.dispatchEvent("timeupdate")},i));s||(s=setInterval(function(){c("getCurrentTime")},j))}function h(){if(d){clearInterval(d);d=0}if(s){clearInterval(s); +s=0}}var i=250,j=16,p={MEDIA_ERR_ABORTED:1,MEDIA_ERR_NETWORK:2,MEDIA_ERR_DECODE:3,MEDIA_ERR_SRC_NOT_SUPPORTED:4},m,o=this,q={q:[],queue:function(l){this.q.push(l);this.process()},process:function(){if(A)for(;this.q.length;)this.q.shift()()}},s,d,A,y=document.createElement("iframe"),x={error:null,src:o.src,NETWORK_EMPTY:0,NETWORK_IDLE:1,NETWORK_LOADING:2,NETWORK_NO_SOURCE:3,networkState:0,HAVE_NOTHING:0,HAVE_METADATA:1,HAVE_CURRENT_DATA:2,HAVE_FUTURE_DATA:3,HAVE_ENOUGH_DATA:4,readyState:0,seeking:false, +currentTime:0,duration:NaN,paused:true,ended:false,autoplay:false,loop:false,volume:1,muted:false,width:0,height:0};Popcorn.forEach("error networkState readyState seeking duration paused ended".split(" "),function(l){Object.defineProperty(o,l,{get:function(){return x[l]}})});Object.defineProperties(o,{src:{get:function(){return x.src},set:function(l){x.src=l;o.load()}},currentTime:{get:function(){return x.currentTime},set:function(l){q.queue(function(){c("seekTo",l)});x.seeking=true;o.dispatchEvent("seeking")}}, +autoplay:{get:function(){return x.autoplay},set:function(l){x.autoplay=!!l}},loop:{get:function(){return x.loop},set:function(l){x.loop=!!l;q.queue(function(){c("setLoop",loop)})}},volume:{get:function(){return x.volume},set:function(l){x.volume=l;q.queue(function(){c("setVolume",x.muted?0:x.volume)});o.dispatchEvent("volumechange")}},muted:{get:function(){return x.muted},set:function(l){x.muted=!!l;q.queue(function(){c("setVolume",x.muted?0:x.volume)});o.dispatchEvent("volumechange")}},width:{get:function(){return y.width}, +set:function(l){y.width=l}},height:{get:function(){return y.height},set:function(l){y.height=l}}});var a={getCurrentTime:function(l){x.currentTime=parseFloat(l.value)},getDuration:function(l){x.duration=parseFloat(l.value);if(!isNaN(x.duration)){x.readyState=4;o.dispatchEvent("durationchange");o.dispatchEvent("loadedmetadata");o.dispatchEvent("loadeddata");o.dispatchEvent("canplay");o.dispatchEvent("canplaythrough")}},getVolume:function(l){x.volume=parseFloat(l.value)}},g={ready:function(){c("addEventListener", +"loadProgress");c("addEventListener","playProgress");c("addEventListener","play");c("addEventListener","pause");c("addEventListener","finish");c("addEventListener","seek");c("getDuration");A=true;q.process();o.dispatchEvent("loadstart")},loadProgress:function(l){o.dispatchEvent("progress");x.duration=parseFloat(l.data.duration)},playProgress:function(l){x.currentTime=parseFloat(l.data.seconds)},play:function(){if(x.seeking){x.seeking=false;o.dispatchEvent("seeked")}x.paused=false;x.ended=false;e(); +o.dispatchEvent("play")},pause:function(){x.paused=true;h();o.dispatchEvent("pause")},finish:function(){x.ended=true;h();o.dispatchEvent("ended")},seek:function(l){x.currentTime=parseFloat(l.data.seconds);x.seeking=false;x.ended=false;o.dispatchEvent("timeupdate");o.dispatchEvent("seeked")}};o.load=function(){A=false;m=Popcorn.guid();var l=r(x.src),k={},t=[],u={api:1,player_id:m};if(f(o.nodeName,l.source)){Popcorn.extend(k,n);Popcorn.extend(k,l.queryKey);Popcorn.extend(k,u);l="http://player.vimeo.com/video/"+ +/\d+$/.exec(l.path)+"?";for(var v in k)k.hasOwnProperty(v)&&t.push(encodeURIComponent(v)+"="+encodeURIComponent(k[v]));l+=t.join("&");x.loop=!!l.match(/loop=1/);x.autoplay=!!l.match(/autoplay=1/);y.width=o.style.width?o.style.width:500;y.height=o.style.height?o.style.height:281;y.frameBorder=0;y.webkitAllowFullScreen=true;y.mozAllowFullScreen=true;y.allowFullScreen=true;y.src=l;o.appendChild(y)}else{l=x.MEDIA_ERR_SRC_NOT_SUPPORTED;x.error={};Popcorn.extend(x.error,p);x.error.code=l;o.dispatchEvent("error")}}; +o.unload=function(){h();window.removeEventListener("message",b,false)};o.play=function(){q.queue(function(){c("play")})};o.pause=function(){q.queue(function(){c("pause")})};setTimeout(function(){window.addEventListener("message",b,false);o.load()},0)},_teardown:function(){this.unload&&this.unload()}})})();(function(r,f){r.onYouTubePlayerAPIReady=function(){onYouTubePlayerAPIReady.ready=true;for(var c=0;c<onYouTubePlayerAPIReady.waiting.length;c++)onYouTubePlayerAPIReady.waiting[c]()};if(r.YT){r.quarantineYT=r.YT;r.YT=null}onYouTubePlayerAPIReady.waiting=[];var n=false;f.player("youtube",{_canPlayType:function(c,b){return typeof b==="string"&&/(?:http:\/\/www\.|http:\/\/|www\.|\.|^)(youtu)/.test(b)&&c.toLowerCase()!=="video"},_setup:function(c){if(!r.YT&&!n){n=true;f.getScript("//youtube.com/player_api")}var b= +this,e=false,h=document.createElement("div"),i=0,j=true,p=false,m=0,o=false,q=100,s=f.player.playerQueue(),d=function(){f.player.defineProperty(b,"currentTime",{set:function(y){if(!c.destroyed){p=true;i=Math.round(+y*100)/100}},get:function(){return i}});f.player.defineProperty(b,"paused",{get:function(){return j}});f.player.defineProperty(b,"muted",{set:function(y){if(c.destroyed)return y;if(c.youtubeObject.isMuted()!==y){y?c.youtubeObject.mute():c.youtubeObject.unMute();o=c.youtubeObject.isMuted(); +b.dispatchEvent("volumechange")}return c.youtubeObject.isMuted()},get:function(){if(c.destroyed)return 0;return c.youtubeObject.isMuted()}});f.player.defineProperty(b,"volume",{set:function(y){if(c.destroyed)return y;if(c.youtubeObject.getVolume()/100!==y){c.youtubeObject.setVolume(y*100);q=c.youtubeObject.getVolume();b.dispatchEvent("volumechange")}return c.youtubeObject.getVolume()/100},get:function(){if(c.destroyed)return 0;return c.youtubeObject.getVolume()/100}});b.play=function(){if(!c.destroyed){j= +false;s.add(function(){if(c.youtubeObject.getPlayerState()!==1){p=false;c.youtubeObject.playVideo()}else s.next()})}};b.pause=function(){if(!c.destroyed){j=true;s.add(function(){c.youtubeObject.getPlayerState()!==2?c.youtubeObject.pauseVideo():s.next()})}}};h.id=b.id+f.guid();c._container=h;b.appendChild(h);var A=function(){var y,x,a,g,l=true,k=function(){if(!c.destroyed){if(p)if(i===c.youtubeObject.getCurrentTime()){p=false;b.dispatchEvent("seeked");b.dispatchEvent("timeupdate")}else c.youtubeObject.seekTo(i); +else{i=c.youtubeObject.getCurrentTime();b.dispatchEvent("timeupdate")}setTimeout(k,250)}},t=function(z){var C=c.youtubeObject.getDuration();if(isNaN(C)||C===0)setTimeout(function(){t(z*2)},z*1E3);else{b.duration=C;b.dispatchEvent("durationchange");b.dispatchEvent("loadedmetadata");b.dispatchEvent("loadeddata");b.readyState=4;k();b.dispatchEvent("canplaythrough")}};c.controls=+c.controls===0||+c.controls===1?c.controls:1;c.annotations=+c.annotations===1||+c.annotations===3?c.annotations:1;y=/^.*(?:\/|v=)(.{11})/.exec(b.src)[1]; +x=(b.src.split("?")[1]||"").replace(/v=.{11}/,"");x=x.replace(/&t=(?:(\d+)m)?(?:(\d+)s)?/,function(z,C,E){C|=0;E|=0;m=+E+C*60;return""});x=x.replace(/&start=(\d+)?/,function(z,C){C|=0;m=C;return""});e=/autoplay=1/.test(x);x=x.split(/[\&\?]/g);a={wmode:"transparent"};for(var u=0;u<x.length;u++){g=x[u].split("=");a[g[0]]=g[1]}c.youtubeObject=new YT.Player(h.id,{height:"100%",width:"100%",wmode:"transparent",playerVars:a,videoId:y,events:{onReady:function(){q=b.volume;o=b.muted;v();j=b.paused;d();c.youtubeObject.playVideo(); +b.currentTime=m},onStateChange:function(z){if(!(c.destroyed||z.data===-1))if(z.data===2){j=true;b.dispatchEvent("pause");s.next()}else if(z.data===1&&!l){j=false;b.dispatchEvent("play");b.dispatchEvent("playing");s.next()}else if(z.data===0)b.dispatchEvent("ended");else if(z.data===1&&l){l=false;if(e||!b.paused)j=false;j&&c.youtubeObject.pauseVideo();t(0.025)}},onError:function(z){if([2,100,101,150].indexOf(z.data)!==-1){b.error={customCode:z.data};b.dispatchEvent("error")}}}});var v=function(){if(!c.destroyed){if(o!== +c.youtubeObject.isMuted()){o=c.youtubeObject.isMuted();b.dispatchEvent("volumechange")}if(q!==c.youtubeObject.getVolume()){q=c.youtubeObject.getVolume();b.dispatchEvent("volumechange")}setTimeout(v,250)}}};onYouTubePlayerAPIReady.ready?A():onYouTubePlayerAPIReady.waiting.push(A)},_teardown:function(c){c.destroyed=true;var b=c.youtubeObject;if(b){b.stopVideo();b.clearVideo&&b.clearVideo()}this.removeChild(document.getElementById(c._container.id))}})})(window,Popcorn); diff --git a/record-and-playback/presentation/playback/presentation/lib/writing.js b/record-and-playback/presentation/playback/presentation/lib/writing.js index 42dcf0920b771ecf7440f16dfb26ff7471d1a8c7..07563e33fd4b2100c726a1a71264346047be3a66 100755 --- a/record-and-playback/presentation/playback/presentation/lib/writing.js +++ b/record-and-playback/presentation/playback/presentation/lib/writing.js @@ -315,8 +315,10 @@ function runPopcorn() { else var thisimg = svgobj.getSVGDocument('svgfile').getElementById(current_image); var offsets = thisimg.getBoundingClientRect(); - imageXOffset = (1600 - parseInt(thisimg.getAttribute("width"), 10))/2; - imageYOffset = (1200 - parseInt(thisimg.getAttribute("height"), 10))/2; + // Offsets divided by 4. By 2 because of the padding and by 2 again because 800x600 is half 1600x1200 + imageXOffset = (1600 - parseInt(thisimg.getAttribute("width"), 10))/4; + imageYOffset = (1200 - parseInt(thisimg.getAttribute("height"), 10))/4; + var vboxVal = getViewboxAtTime(t); if(vboxVal !== undefined) { diff --git a/record-and-playback/presentation/scripts/publish/presentation.rb b/record-and-playback/presentation/scripts/publish/presentation.rb index ebbbd504ea50d32a31b3db668bc11b65863f4797..87e4d6316d3ae897ceddf4e6ee4d110bfb6df274 100644 --- a/record-and-playback/presentation/scripts/publish/presentation.rb +++ b/record-and-playback/presentation/scripts/publish/presentation.rb @@ -155,7 +155,7 @@ def processClearEvents $pageCleared = clearEvent.xpath(".//pageNumber")[0].text() slideFolder = clearEvent.xpath(".//presentation")[0].text() #$clearPageTimes[clearTime] = [$pageCleared, $canvas_number, "presentation/#{slideFolder}/slide-#{$pageCleared.to_i+1}.png", nil] - $clearPageTimes[($prev_clear_time..clearTime)] = [$pageCleared, $canvas_number, "presentation/#{slideFolder}/slide-#{$pageCleared.to_i+1}.png", nil] + $clearPageTimes[($prev_clear_time..clearTime)] = [$pageCleared, $canvas_number, "presentation/#{slideFolder}/slide-#{$pageCleared}.png", nil] $prev_clear_time = clearTime $canvas_number+=1 end @@ -469,7 +469,7 @@ def processShapesAndClears # Print out the gathered/detected images. $slides_compiled.each do |key, val| $val = val - $xml.image(:id => "image#{$val[2].to_i}", :in => $val[0].join(' '), :out => $val[1].join(' '), 'xlink:href' => key[0], :height => key[1], :width => key[2], :visibility => :hidden, :text => $val[3]) + $xml.image(:id => "image#{$val[2].to_i}", :in => $val[0].join(' '), :out => $val[1].join(' '), 'xlink:href' => key[0], :height => key[1], :width => key[2], :visibility => :hidden, :text => $val[3], :x => 0) $canvas_number+=1 $xml.g(:class => :canvas, :id => "canvas#{$val[2].to_i}", :image => "image#{$val[2].to_i}", :display => :none) do @@ -792,3 +792,4 @@ end performance_end = Time.now +