Merge branch 'mconf-live0.6.4' into base-for-0.6.4
Conflicts: bigbluebutton-apps/src/main/scala/org/bigbluebutton/core/apps/users/UsersApp.scala bigbluebutton-client/src/org/bigbluebutton/main/views/CameraDisplaySettings.mxml bigbluebutton-client/src/org/bigbluebutton/main/views/MainApplicationShell.mxml bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/maps/VideoEventMap.mxml bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/maps/VideoEventMapDelegate.as bigbluebutton-client/src/org/bigbluebutton/modules/videoconf/views/UserGraphicHolder.mxml
@ -122,6 +122,27 @@ public String createMeeting(String meetingID, String welcome, String moderatorPa
|
||||
.trim();
|
||||
}
|
||||
|
||||
//
|
||||
// getJoinMeetingURL() -- get join meeting URL for both viewer and moderator as guest
|
||||
//
|
||||
|
||||
public String getJoinMeetingURL(String username, String meetingID, String password, String clientURL, Boolean guest) {
|
||||
String base_url_join = BigBlueButtonURL + "api/join?";
|
||||
String clientURL_param = "";
|
||||
|
||||
if ((clientURL != null) && !clientURL.equals("")) {
|
||||
clientURL_param = "&redirectClient=true&clientURL=" + urlEncode( clientURL );
|
||||
}
|
||||
|
||||
|
||||
String join_parameters = "meetingID=" + urlEncode(meetingID)
|
||||
+ "&fullName=" + urlEncode(username) + "&password="
|
||||
+ urlEncode(password) + "&guest=" + urlEncode(guest.toString()) + clientURL_param;
|
||||
|
||||
return base_url_join + join_parameters + "&checksum="
|
||||
+ checksum("join" + join_parameters + salt);
|
||||
}
|
||||
|
||||
//
|
||||
// getJoinMeetingURL() -- get join meeting URL for both viewer and moderator
|
||||
//
|
||||
|
689
bbb-api-demo/src/main/webapp/css/mconf-bootstrap.min.css
vendored
Normal file
@ -0,0 +1,689 @@
|
||||
article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block;}
|
||||
audio,canvas,video{display:inline-block;*display:inline;*zoom:1;}
|
||||
audio:not([controls]){display:none;}
|
||||
html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;}
|
||||
a:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px;}
|
||||
a:hover,a:active{outline:0;}
|
||||
sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline;}
|
||||
sup{top:-0.5em;}
|
||||
sub{bottom:-0.25em;}
|
||||
img{height:auto;border:0;-ms-interpolation-mode:bicubic;vertical-align:middle;}
|
||||
button,input,select,textarea{margin:0;font-size:100%;vertical-align:middle;}
|
||||
button,input{*overflow:visible;line-height:normal;}
|
||||
button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0;}
|
||||
button,input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button;}
|
||||
input[type="search"]{-webkit-appearance:textfield;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;}
|
||||
input[type="search"]::-webkit-search-decoration,input[type="search"]::-webkit-search-cancel-button{-webkit-appearance:none;}
|
||||
textarea{overflow:auto;vertical-align:top;}
|
||||
.clearfix{*zoom:1;}.clearfix:before,.clearfix:after{display:table;content:"";}
|
||||
.clearfix:after{clear:both;}
|
||||
.hide-text{overflow:hidden;text-indent:100%;white-space:nowrap;}
|
||||
.input-block-level{display:block;width:100%;min-height:28px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box;}
|
||||
body{margin:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;line-height:18px;color:#333333;background-color:#ffffff;}
|
||||
a{color:#367380;text-decoration:none;}
|
||||
a:hover{color:#1f434a;text-decoration:underline;}
|
||||
.row{margin-left:-20px;*zoom:1;}.row:before,.row:after{display:table;content:"";}
|
||||
.row:after{clear:both;}
|
||||
[class*="span"]{float:left;margin-left:20px;}
|
||||
.container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px;}
|
||||
.span12{width:940px;}
|
||||
.span11{width:860px;}
|
||||
.span10{width:780px;}
|
||||
.span9{width:700px;}
|
||||
.span8{width:620px;}
|
||||
.span7{width:540px;}
|
||||
.span6{width:460px;}
|
||||
.span5{width:380px;}
|
||||
.span4{width:300px;}
|
||||
.span3{width:220px;}
|
||||
.span2{width:140px;}
|
||||
.span1{width:60px;}
|
||||
.offset12{margin-left:980px;}
|
||||
.offset11{margin-left:900px;}
|
||||
.offset10{margin-left:820px;}
|
||||
.offset9{margin-left:740px;}
|
||||
.offset8{margin-left:660px;}
|
||||
.offset7{margin-left:580px;}
|
||||
.offset6{margin-left:500px;}
|
||||
.offset5{margin-left:420px;}
|
||||
.offset4{margin-left:340px;}
|
||||
.offset3{margin-left:260px;}
|
||||
.offset2{margin-left:180px;}
|
||||
.offset1{margin-left:100px;}
|
||||
.row-fluid{width:100%;*zoom:1;}.row-fluid:before,.row-fluid:after{display:table;content:"";}
|
||||
.row-fluid:after{clear:both;}
|
||||
.row-fluid>[class*="span"]{float:left;margin-left:2.127659574%;}
|
||||
.row-fluid>[class*="span"]:first-child{margin-left:0;}
|
||||
.row-fluid > .span12{width:99.99999998999999%;}
|
||||
.row-fluid > .span11{width:91.489361693%;}
|
||||
.row-fluid > .span10{width:82.97872339599999%;}
|
||||
.row-fluid > .span9{width:74.468085099%;}
|
||||
.row-fluid > .span8{width:65.95744680199999%;}
|
||||
.row-fluid > .span7{width:57.446808505%;}
|
||||
.row-fluid > .span6{width:48.93617020799999%;}
|
||||
.row-fluid > .span5{width:40.425531911%;}
|
||||
.row-fluid > .span4{width:31.914893614%;}
|
||||
.row-fluid > .span3{width:23.404255317%;}
|
||||
.row-fluid > .span2{width:14.89361702%;}
|
||||
.row-fluid > .span1{width:6.382978723%;}
|
||||
.container{margin-left:auto;margin-right:auto;*zoom:1;}.container:before,.container:after{display:table;content:"";}
|
||||
.container:after{clear:both;}
|
||||
.container-fluid{padding-left:20px;padding-right:20px;*zoom:1;}.container-fluid:before,.container-fluid:after{display:table;content:"";}
|
||||
.container-fluid:after{clear:both;}
|
||||
p{margin:0 0 9px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;line-height:18px;}p small{font-size:11px;color:#999999;}
|
||||
.lead{margin-bottom:18px;font-size:20px;font-weight:200;line-height:27px;}
|
||||
h1,h2,h3,h4,h5,h6{margin:0;font-family:inherit;font-weight:bold;color:inherit;text-rendering:optimizelegibility;}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{font-weight:normal;color:#999999;}
|
||||
h1{font-size:30px;line-height:36px;}h1 small{font-size:18px;}
|
||||
h2{font-size:24px;line-height:36px;}h2 small{font-size:18px;}
|
||||
h3{line-height:27px;font-size:18px;}h3 small{font-size:14px;}
|
||||
h4,h5,h6{line-height:18px;}
|
||||
h4{font-size:14px;}h4 small{font-size:12px;}
|
||||
h5{font-size:12px;}
|
||||
h6{font-size:11px;color:#999999;text-transform:uppercase;}
|
||||
.page-header{padding-bottom:17px;margin:18px 0;border-bottom:1px solid #eeeeee;}
|
||||
.page-header h1{line-height:1;}
|
||||
ul,ol{padding:0;margin:0 0 9px 25px;}
|
||||
ul ul,ul ol,ol ol,ol ul{margin-bottom:0;}
|
||||
ul{list-style:disc;}
|
||||
ol{list-style:decimal;}
|
||||
li{line-height:18px;}
|
||||
ul.unstyled,ol.unstyled{margin-left:0;list-style:none;}
|
||||
dl{margin-bottom:18px;}
|
||||
dt,dd{line-height:18px;}
|
||||
dt{font-weight:bold;line-height:17px;}
|
||||
dd{margin-left:9px;}
|
||||
.dl-horizontal dt{float:left;clear:left;width:120px;text-align:right;}
|
||||
.dl-horizontal dd{margin-left:130px;}
|
||||
hr{margin:18px 0;border:0;border-top:1px solid #eeeeee;border-bottom:1px solid #ffffff;}
|
||||
strong{font-weight:bold;}
|
||||
em{font-style:italic;}
|
||||
.muted{color:#999999;}
|
||||
abbr[title]{border-bottom:1px dotted #ddd;cursor:help;}
|
||||
abbr.initialism{font-size:90%;text-transform:uppercase;}
|
||||
blockquote{padding:0 0 0 15px;margin:0 0 18px;border-left:5px solid #eeeeee;}blockquote p{margin-bottom:0;font-size:16px;font-weight:300;line-height:22.5px;}
|
||||
blockquote small{display:block;line-height:18px;color:#999999;}blockquote small:before{content:'\2014 \00A0';}
|
||||
blockquote.pull-right{float:right;padding-left:0;padding-right:15px;border-left:0;border-right:5px solid #eeeeee;}blockquote.pull-right p,blockquote.pull-right small{text-align:right;}
|
||||
q:before,q:after,blockquote:before,blockquote:after{content:"";}
|
||||
address{display:block;margin-bottom:18px;line-height:18px;font-style:normal;}
|
||||
small{font-size:100%;}
|
||||
cite{font-style:normal;}
|
||||
code,pre{padding:0 3px 2px;font-family:Menlo,Monaco,"Courier New",monospace;font-size:12px;color:#333333;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;}
|
||||
code{padding:2px 4px;color:#d14;background-color:#f7f7f9;border:1px solid #e1e1e8;}
|
||||
pre{display:block;padding:8.5px;margin:0 0 9px;font-size:12.025px;line-height:18px;background-color:#f5f5f5;border:1px solid #ccc;border:1px solid rgba(0, 0, 0, 0.15);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;white-space:pre;white-space:pre-wrap;word-break:break-all;word-wrap:break-word;}pre.prettyprint{margin-bottom:18px;}
|
||||
pre code{padding:0;color:inherit;background-color:transparent;border:0;}
|
||||
.pre-scrollable{max-height:340px;overflow-y:scroll;}
|
||||
form{margin:0 0 18px;}
|
||||
fieldset{padding:0;margin:0;border:0;}
|
||||
legend{display:block;width:100%;padding:0;margin-bottom:27px;font-size:19.5px;line-height:36px;color:#333333;border:0;border-bottom:1px solid #eee;}legend small{font-size:13.5px;color:#999999;}
|
||||
label,input,button,select,textarea{font-size:13px;font-weight:normal;line-height:18px;}
|
||||
input,button,select,textarea{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;}
|
||||
label{display:block;margin-bottom:5px;color:#333333;}
|
||||
input,textarea,select,.uneditable-input{display:inline-block;width:210px;height:18px;padding:4px;margin-bottom:9px;font-size:13px;line-height:18px;color:#555555;border:1px solid #cccccc;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;}
|
||||
.uneditable-textarea{width:auto;height:auto;}
|
||||
label input,label textarea,label select{display:block;}
|
||||
input[type="image"],input[type="checkbox"],input[type="radio"]{width:auto;height:auto;padding:0;margin:3px 0;*margin-top:0;line-height:normal;cursor:pointer;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;border:0 \9;}
|
||||
input[type="image"]{border:0;}
|
||||
input[type="file"]{width:auto;padding:initial;line-height:initial;border:initial;background-color:#ffffff;background-color:initial;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;}
|
||||
input[type="button"],input[type="reset"],input[type="submit"]{width:auto;height:auto;}
|
||||
select,input[type="file"]{height:28px;*margin-top:4px;line-height:28px;}
|
||||
input[type="file"]{line-height:18px \9;}
|
||||
select{width:220px;background-color:#ffffff;}
|
||||
select[multiple],select[size]{height:auto;}
|
||||
input[type="image"]{-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;}
|
||||
textarea{height:auto;}
|
||||
input[type="hidden"]{display:none;}
|
||||
.radio,.checkbox{padding-left:18px;}
|
||||
.radio input[type="radio"],.checkbox input[type="checkbox"]{float:left;margin-left:-18px;}
|
||||
.controls>.radio:first-child,.controls>.checkbox:first-child{padding-top:5px;}
|
||||
.radio.inline,.checkbox.inline{display:inline-block;padding-top:5px;margin-bottom:0;vertical-align:middle;}
|
||||
.radio.inline+.radio.inline,.checkbox.inline+.checkbox.inline{margin-left:10px;}
|
||||
input,textarea{-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);-webkit-transition:border linear 0.2s,box-shadow linear 0.2s;-moz-transition:border linear 0.2s,box-shadow linear 0.2s;-ms-transition:border linear 0.2s,box-shadow linear 0.2s;-o-transition:border linear 0.2s,box-shadow linear 0.2s;transition:border linear 0.2s,box-shadow linear 0.2s;}
|
||||
input:focus,textarea:focus{border-color:#367380;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 8px #367380;-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 8px #367380;box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 8px #367380;outline:0;outline:thin dotted \9;}
|
||||
input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus,select:focus{-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px;}
|
||||
.input-mini{width:60px;}
|
||||
.input-small{width:90px;}
|
||||
.input-medium{width:150px;}
|
||||
.input-large{width:210px;}
|
||||
.input-xlarge{width:270px;}
|
||||
.input-xxlarge{width:530px;}
|
||||
input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input{float:none;margin-left:0;}
|
||||
input,textarea,.uneditable-input{margin-left:0;}
|
||||
input.span12, textarea.span12, .uneditable-input.span12{width:930px;}
|
||||
input.span11, textarea.span11, .uneditable-input.span11{width:850px;}
|
||||
input.span10, textarea.span10, .uneditable-input.span10{width:770px;}
|
||||
input.span9, textarea.span9, .uneditable-input.span9{width:690px;}
|
||||
input.span8, textarea.span8, .uneditable-input.span8{width:610px;}
|
||||
input.span7, textarea.span7, .uneditable-input.span7{width:530px;}
|
||||
input.span6, textarea.span6, .uneditable-input.span6{width:450px;}
|
||||
input.span5, textarea.span5, .uneditable-input.span5{width:370px;}
|
||||
input.span4, textarea.span4, .uneditable-input.span4{width:290px;}
|
||||
input.span3, textarea.span3, .uneditable-input.span3{width:210px;}
|
||||
input.span2, textarea.span2, .uneditable-input.span2{width:130px;}
|
||||
input.span1, textarea.span1, .uneditable-input.span1{width:50px;}
|
||||
input[disabled],select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly]{background-color:#eeeeee;border-color:#ddd;cursor:not-allowed;}
|
||||
.control-group.warning>label,.control-group.warning .help-block,.control-group.warning .help-inline{color:#c09853;}
|
||||
.control-group.warning input,.control-group.warning select,.control-group.warning textarea{color:#c09853;border-color:#c09853;}.control-group.warning input:focus,.control-group.warning select:focus,.control-group.warning textarea:focus{border-color:#a47e3c;-webkit-box-shadow:0 0 6px #dbc59e;-moz-box-shadow:0 0 6px #dbc59e;box-shadow:0 0 6px #dbc59e;}
|
||||
.control-group.warning .input-prepend .add-on,.control-group.warning .input-append .add-on{color:#c09853;background-color:#fcf8e3;border-color:#c09853;}
|
||||
.control-group.error>label,.control-group.error .help-block,.control-group.error .help-inline{color:#b94a48;}
|
||||
.control-group.error input,.control-group.error select,.control-group.error textarea{color:#b94a48;border-color:#b94a48;}.control-group.error input:focus,.control-group.error select:focus,.control-group.error textarea:focus{border-color:#953b39;-webkit-box-shadow:0 0 6px #d59392;-moz-box-shadow:0 0 6px #d59392;box-shadow:0 0 6px #d59392;}
|
||||
.control-group.error .input-prepend .add-on,.control-group.error .input-append .add-on{color:#b94a48;background-color:#f2dede;border-color:#b94a48;}
|
||||
.control-group.success>label,.control-group.success .help-block,.control-group.success .help-inline{color:#468847;}
|
||||
.control-group.success input,.control-group.success select,.control-group.success textarea{color:#468847;border-color:#468847;}.control-group.success input:focus,.control-group.success select:focus,.control-group.success textarea:focus{border-color:#356635;-webkit-box-shadow:0 0 6px #7aba7b;-moz-box-shadow:0 0 6px #7aba7b;box-shadow:0 0 6px #7aba7b;}
|
||||
.control-group.success .input-prepend .add-on,.control-group.success .input-append .add-on{color:#468847;background-color:#dff0d8;border-color:#468847;}
|
||||
input:focus:required:invalid,textarea:focus:required:invalid,select:focus:required:invalid{color:#b94a48;border-color:#ee5f5b;}input:focus:required:invalid:focus,textarea:focus:required:invalid:focus,select:focus:required:invalid:focus{border-color:#e9322d;-webkit-box-shadow:0 0 6px #f8b9b7;-moz-box-shadow:0 0 6px #f8b9b7;box-shadow:0 0 6px #f8b9b7;}
|
||||
.form-actions{padding:17px 20px 18px;margin-top:18px;margin-bottom:18px;background-color:#eeeeee;border-top:1px solid #ddd;*zoom:1;}.form-actions:before,.form-actions:after{display:table;content:"";}
|
||||
.form-actions:after{clear:both;}
|
||||
.uneditable-input{display:block;background-color:#ffffff;border-color:#eee;-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);cursor:not-allowed;}
|
||||
:-moz-placeholder{color:#999999;}
|
||||
::-webkit-input-placeholder{color:#999999;}
|
||||
.help-block,.help-inline{color:#555555;}
|
||||
.help-block{display:block;margin-bottom:9px;}
|
||||
.help-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle;padding-left:5px;}
|
||||
.input-prepend,.input-append{margin-bottom:5px;}.input-prepend input,.input-append input,.input-prepend select,.input-append select,.input-prepend .uneditable-input,.input-append .uneditable-input{*margin-left:0;-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;}.input-prepend input:focus,.input-append input:focus,.input-prepend select:focus,.input-append select:focus,.input-prepend .uneditable-input:focus,.input-append .uneditable-input:focus{position:relative;z-index:2;}
|
||||
.input-prepend .uneditable-input,.input-append .uneditable-input{border-left-color:#ccc;}
|
||||
.input-prepend .add-on,.input-append .add-on{display:inline-block;width:auto;min-width:16px;height:18px;padding:4px 5px;font-weight:normal;line-height:18px;text-align:center;text-shadow:0 1px 0 #ffffff;vertical-align:middle;background-color:#eeeeee;border:1px solid #ccc;}
|
||||
.input-prepend .add-on,.input-append .add-on,.input-prepend .btn,.input-append .btn{-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;}
|
||||
.input-prepend .active,.input-append .active{background-color:#a9dba9;border-color:#46a546;}
|
||||
.input-prepend .add-on,.input-prepend .btn{margin-right:-1px;}
|
||||
.input-append input,.input-append select .uneditable-input{-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;}
|
||||
.input-append .uneditable-input{border-left-color:#eee;border-right-color:#ccc;}
|
||||
.input-append .add-on,.input-append .btn{margin-left:-1px;-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;}
|
||||
.input-prepend.input-append input,.input-prepend.input-append select,.input-prepend.input-append .uneditable-input{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;}
|
||||
.input-prepend.input-append .add-on:first-child,.input-prepend.input-append .btn:first-child{margin-right:-1px;-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;}
|
||||
.input-prepend.input-append .add-on:last-child,.input-prepend.input-append .btn:last-child{margin-left:-1px;-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;}
|
||||
.search-query{padding-left:14px;padding-right:14px;margin-bottom:0;-webkit-border-radius:14px;-moz-border-radius:14px;border-radius:14px;}
|
||||
.form-search input,.form-inline input,.form-horizontal input,.form-search textarea,.form-inline textarea,.form-horizontal textarea,.form-search select,.form-inline select,.form-horizontal select,.form-search .help-inline,.form-inline .help-inline,.form-horizontal .help-inline,.form-search .uneditable-input,.form-inline .uneditable-input,.form-horizontal .uneditable-input,.form-search .input-prepend,.form-inline .input-prepend,.form-horizontal .input-prepend,.form-search .input-append,.form-inline .input-append,.form-horizontal .input-append{display:inline-block;margin-bottom:0;}
|
||||
.form-search .hide,.form-inline .hide,.form-horizontal .hide{display:none;}
|
||||
.form-search label,.form-inline label{display:inline-block;}
|
||||
.form-search .input-append,.form-inline .input-append,.form-search .input-prepend,.form-inline .input-prepend{margin-bottom:0;}
|
||||
.form-search .radio,.form-search .checkbox,.form-inline .radio,.form-inline .checkbox{padding-left:0;margin-bottom:0;vertical-align:middle;}
|
||||
.form-search .radio input[type="radio"],.form-search .checkbox input[type="checkbox"],.form-inline .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"]{float:left;margin-left:0;margin-right:3px;}
|
||||
.control-group{margin-bottom:9px;}
|
||||
legend+.control-group{margin-top:18px;-webkit-margin-top-collapse:separate;}
|
||||
.form-horizontal .control-group{margin-bottom:18px;*zoom:1;}.form-horizontal .control-group:before,.form-horizontal .control-group:after{display:table;content:"";}
|
||||
.form-horizontal .control-group:after{clear:both;}
|
||||
.form-horizontal .control-label{float:left;width:140px;padding-top:5px;text-align:right;}
|
||||
.form-horizontal .controls{margin-left:160px;*display:inline-block;*margin-left:0;*padding-left:20px;}
|
||||
.form-horizontal .help-block{margin-top:9px;margin-bottom:0;}
|
||||
.form-horizontal .form-actions{padding-left:160px;}
|
||||
table{max-width:100%;border-collapse:collapse;border-spacing:0;background-color:transparent;}
|
||||
.table{width:100%;margin-bottom:18px;}.table th,.table td{padding:8px;line-height:18px;text-align:left;vertical-align:top;border-top:1px solid #dddddd;}
|
||||
.table th{font-weight:bold;}
|
||||
.table thead th{vertical-align:bottom;}
|
||||
.table colgroup+thead tr:first-child th,.table colgroup+thead tr:first-child td,.table thead:first-child tr:first-child th,.table thead:first-child tr:first-child td{border-top:0;}
|
||||
.table tbody+tbody{border-top:2px solid #dddddd;}
|
||||
.table-condensed th,.table-condensed td{padding:4px 5px;}
|
||||
.table-bordered{border:1px solid #dddddd;border-left:0;border-collapse:separate;*border-collapse:collapsed;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}.table-bordered th,.table-bordered td{border-left:1px solid #dddddd;}
|
||||
.table-bordered thead:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child td{border-top:0;}
|
||||
.table-bordered thead:first-child tr:first-child th:first-child,.table-bordered tbody:first-child tr:first-child td:first-child{-webkit-border-radius:4px 0 0 0;-moz-border-radius:4px 0 0 0;border-radius:4px 0 0 0;}
|
||||
.table-bordered thead:first-child tr:first-child th:last-child,.table-bordered tbody:first-child tr:first-child td:last-child{-webkit-border-radius:0 4px 0 0;-moz-border-radius:0 4px 0 0;border-radius:0 4px 0 0;}
|
||||
.table-bordered thead:last-child tr:last-child th:first-child,.table-bordered tbody:last-child tr:last-child td:first-child{-webkit-border-radius:0 0 0 4px;-moz-border-radius:0 0 0 4px;border-radius:0 0 0 4px;}
|
||||
.table-bordered thead:last-child tr:last-child th:last-child,.table-bordered tbody:last-child tr:last-child td:last-child{-webkit-border-radius:0 0 4px 0;-moz-border-radius:0 0 4px 0;border-radius:0 0 4px 0;}
|
||||
.table-striped tbody tr:nth-child(odd) td,.table-striped tbody tr:nth-child(odd) th{background-color:#f9f9f9;}
|
||||
.table tbody tr:hover td,.table tbody tr:hover th{background-color:#f5f5f5;}
|
||||
table .span1{float:none;width:44px;margin-left:0;}
|
||||
table .span2{float:none;width:124px;margin-left:0;}
|
||||
table .span3{float:none;width:204px;margin-left:0;}
|
||||
table .span4{float:none;width:284px;margin-left:0;}
|
||||
table .span5{float:none;width:364px;margin-left:0;}
|
||||
table .span6{float:none;width:444px;margin-left:0;}
|
||||
table .span7{float:none;width:524px;margin-left:0;}
|
||||
table .span8{float:none;width:604px;margin-left:0;}
|
||||
table .span9{float:none;width:684px;margin-left:0;}
|
||||
table .span10{float:none;width:764px;margin-left:0;}
|
||||
table .span11{float:none;width:844px;margin-left:0;}
|
||||
table .span12{float:none;width:924px;margin-left:0;}
|
||||
table .span13{float:none;width:1004px;margin-left:0;}
|
||||
table .span14{float:none;width:1084px;margin-left:0;}
|
||||
table .span15{float:none;width:1164px;margin-left:0;}
|
||||
table .span16{float:none;width:1244px;margin-left:0;}
|
||||
table .span17{float:none;width:1324px;margin-left:0;}
|
||||
table .span18{float:none;width:1404px;margin-left:0;}
|
||||
table .span19{float:none;width:1484px;margin-left:0;}
|
||||
table .span20{float:none;width:1564px;margin-left:0;}
|
||||
table .span21{float:none;width:1644px;margin-left:0;}
|
||||
table .span22{float:none;width:1724px;margin-left:0;}
|
||||
table .span23{float:none;width:1804px;margin-left:0;}
|
||||
table .span24{float:none;width:1884px;margin-left:0;}
|
||||
[class^="icon-"],[class*=" icon-"]{display:inline-block;width:14px;height:14px;line-height:14px;vertical-align:text-top;background-image:url("../img/glyphicons-halflings.png");background-position:14px 14px;background-repeat:no-repeat;*margin-right:.3em;}[class^="icon-"]:last-child,[class*=" icon-"]:last-child{*margin-left:0;}
|
||||
.icon-white{background-image:url("../img/glyphicons-halflings-white.png");}
|
||||
.icon-glass{background-position:0 0;}
|
||||
.icon-music{background-position:-24px 0;}
|
||||
.icon-search{background-position:-48px 0;}
|
||||
.icon-envelope{background-position:-72px 0;}
|
||||
.icon-heart{background-position:-96px 0;}
|
||||
.icon-star{background-position:-120px 0;}
|
||||
.icon-star-empty{background-position:-144px 0;}
|
||||
.icon-user{background-position:-168px 0;}
|
||||
.icon-film{background-position:-192px 0;}
|
||||
.icon-th-large{background-position:-216px 0;}
|
||||
.icon-th{background-position:-240px 0;}
|
||||
.icon-th-list{background-position:-264px 0;}
|
||||
.icon-ok{background-position:-288px 0;}
|
||||
.icon-remove{background-position:-312px 0;}
|
||||
.icon-zoom-in{background-position:-336px 0;}
|
||||
.icon-zoom-out{background-position:-360px 0;}
|
||||
.icon-off{background-position:-384px 0;}
|
||||
.icon-signal{background-position:-408px 0;}
|
||||
.icon-cog{background-position:-432px 0;}
|
||||
.icon-trash{background-position:-456px 0;}
|
||||
.icon-home{background-position:0 -24px;}
|
||||
.icon-file{background-position:-24px -24px;}
|
||||
.icon-time{background-position:-48px -24px;}
|
||||
.icon-road{background-position:-72px -24px;}
|
||||
.icon-download-alt{background-position:-96px -24px;}
|
||||
.icon-download{background-position:-120px -24px;}
|
||||
.icon-upload{background-position:-144px -24px;}
|
||||
.icon-inbox{background-position:-168px -24px;}
|
||||
.icon-play-circle{background-position:-192px -24px;}
|
||||
.icon-repeat{background-position:-216px -24px;}
|
||||
.icon-refresh{background-position:-240px -24px;}
|
||||
.icon-list-alt{background-position:-264px -24px;}
|
||||
.icon-lock{background-position:-287px -24px;}
|
||||
.icon-flag{background-position:-312px -24px;}
|
||||
.icon-headphones{background-position:-336px -24px;}
|
||||
.icon-volume-off{background-position:-360px -24px;}
|
||||
.icon-volume-down{background-position:-384px -24px;}
|
||||
.icon-volume-up{background-position:-408px -24px;}
|
||||
.icon-qrcode{background-position:-432px -24px;}
|
||||
.icon-barcode{background-position:-456px -24px;}
|
||||
.icon-tag{background-position:0 -48px;}
|
||||
.icon-tags{background-position:-25px -48px;}
|
||||
.icon-book{background-position:-48px -48px;}
|
||||
.icon-bookmark{background-position:-72px -48px;}
|
||||
.icon-print{background-position:-96px -48px;}
|
||||
.icon-camera{background-position:-120px -48px;}
|
||||
.icon-font{background-position:-144px -48px;}
|
||||
.icon-bold{background-position:-167px -48px;}
|
||||
.icon-italic{background-position:-192px -48px;}
|
||||
.icon-text-height{background-position:-216px -48px;}
|
||||
.icon-text-width{background-position:-240px -48px;}
|
||||
.icon-align-left{background-position:-264px -48px;}
|
||||
.icon-align-center{background-position:-288px -48px;}
|
||||
.icon-align-right{background-position:-312px -48px;}
|
||||
.icon-align-justify{background-position:-336px -48px;}
|
||||
.icon-list{background-position:-360px -48px;}
|
||||
.icon-indent-left{background-position:-384px -48px;}
|
||||
.icon-indent-right{background-position:-408px -48px;}
|
||||
.icon-facetime-video{background-position:-432px -48px;}
|
||||
.icon-picture{background-position:-456px -48px;}
|
||||
.icon-pencil{background-position:0 -72px;}
|
||||
.icon-map-marker{background-position:-24px -72px;}
|
||||
.icon-adjust{background-position:-48px -72px;}
|
||||
.icon-tint{background-position:-72px -72px;}
|
||||
.icon-edit{background-position:-96px -72px;}
|
||||
.icon-share{background-position:-120px -72px;}
|
||||
.icon-check{background-position:-144px -72px;}
|
||||
.icon-move{background-position:-168px -72px;}
|
||||
.icon-step-backward{background-position:-192px -72px;}
|
||||
.icon-fast-backward{background-position:-216px -72px;}
|
||||
.icon-backward{background-position:-240px -72px;}
|
||||
.icon-play{background-position:-264px -72px;}
|
||||
.icon-pause{background-position:-288px -72px;}
|
||||
.icon-stop{background-position:-312px -72px;}
|
||||
.icon-forward{background-position:-336px -72px;}
|
||||
.icon-fast-forward{background-position:-360px -72px;}
|
||||
.icon-step-forward{background-position:-384px -72px;}
|
||||
.icon-eject{background-position:-408px -72px;}
|
||||
.icon-chevron-left{background-position:-432px -72px;}
|
||||
.icon-chevron-right{background-position:-456px -72px;}
|
||||
.icon-plus-sign{background-position:0 -96px;}
|
||||
.icon-minus-sign{background-position:-24px -96px;}
|
||||
.icon-remove-sign{background-position:-48px -96px;}
|
||||
.icon-ok-sign{background-position:-72px -96px;}
|
||||
.icon-question-sign{background-position:-96px -96px;}
|
||||
.icon-info-sign{background-position:-120px -96px;}
|
||||
.icon-screenshot{background-position:-144px -96px;}
|
||||
.icon-remove-circle{background-position:-168px -96px;}
|
||||
.icon-ok-circle{background-position:-192px -96px;}
|
||||
.icon-ban-circle{background-position:-216px -96px;}
|
||||
.icon-arrow-left{background-position:-240px -96px;}
|
||||
.icon-arrow-right{background-position:-264px -96px;}
|
||||
.icon-arrow-up{background-position:-289px -96px;}
|
||||
.icon-arrow-down{background-position:-312px -96px;}
|
||||
.icon-share-alt{background-position:-336px -96px;}
|
||||
.icon-resize-full{background-position:-360px -96px;}
|
||||
.icon-resize-small{background-position:-384px -96px;}
|
||||
.icon-plus{background-position:-408px -96px;}
|
||||
.icon-minus{background-position:-433px -96px;}
|
||||
.icon-asterisk{background-position:-456px -96px;}
|
||||
.icon-exclamation-sign{background-position:0 -120px;}
|
||||
.icon-gift{background-position:-24px -120px;}
|
||||
.icon-leaf{background-position:-48px -120px;}
|
||||
.icon-fire{background-position:-72px -120px;}
|
||||
.icon-eye-open{background-position:-96px -120px;}
|
||||
.icon-eye-close{background-position:-120px -120px;}
|
||||
.icon-warning-sign{background-position:-144px -120px;}
|
||||
.icon-plane{background-position:-168px -120px;}
|
||||
.icon-calendar{background-position:-192px -120px;}
|
||||
.icon-random{background-position:-216px -120px;}
|
||||
.icon-comment{background-position:-240px -120px;}
|
||||
.icon-magnet{background-position:-264px -120px;}
|
||||
.icon-chevron-up{background-position:-288px -120px;}
|
||||
.icon-chevron-down{background-position:-313px -119px;}
|
||||
.icon-retweet{background-position:-336px -120px;}
|
||||
.icon-shopping-cart{background-position:-360px -120px;}
|
||||
.icon-folder-close{background-position:-384px -120px;}
|
||||
.icon-folder-open{background-position:-408px -120px;}
|
||||
.icon-resize-vertical{background-position:-432px -119px;}
|
||||
.icon-resize-horizontal{background-position:-456px -118px;}
|
||||
.dropdown{position:relative;}
|
||||
.dropdown-toggle{*margin-bottom:-3px;}
|
||||
.dropdown-toggle:active,.open .dropdown-toggle{outline:0;}
|
||||
.caret{display:inline-block;width:0;height:0;vertical-align:top;border-left:4px solid transparent;border-right:4px solid transparent;border-top:4px solid #000000;opacity:0.3;filter:alpha(opacity=30);content:"";}
|
||||
.dropdown .caret{margin-top:8px;margin-left:2px;}
|
||||
.dropdown:hover .caret,.open.dropdown .caret{opacity:1;filter:alpha(opacity=100);}
|
||||
.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;float:left;display:none;min-width:160px;padding:4px 0;margin:0;list-style:none;background-color:#ffffff;border-color:#ccc;border-color:rgba(0, 0, 0, 0.2);border-style:solid;border-width:1px;-webkit-border-radius:0 0 5px 5px;-moz-border-radius:0 0 5px 5px;border-radius:0 0 5px 5px;-webkit-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);-moz-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box;*border-right-width:2px;*border-bottom-width:2px;}.dropdown-menu.pull-right{right:0;left:auto;}
|
||||
.dropdown-menu .divider{height:1px;margin:8px 1px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #ffffff;*width:100%;*margin:-5px 0 5px;}
|
||||
.dropdown-menu a{display:block;padding:3px 15px;clear:both;font-weight:normal;line-height:18px;color:#333333;white-space:nowrap;}
|
||||
.dropdown-menu li>a:hover,.dropdown-menu .active>a,.dropdown-menu .active>a:hover{color:#ffffff;text-decoration:none;background-color:#367380;}
|
||||
.dropdown.open{*z-index:1000;}.dropdown.open .dropdown-toggle{color:#ffffff;background:#ccc;background:rgba(0, 0, 0, 0.3);}
|
||||
.dropdown.open .dropdown-menu{display:block;}
|
||||
.pull-right .dropdown-menu{left:auto;right:0;}
|
||||
.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px solid #000000;content:"\2191";}
|
||||
.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px;}
|
||||
.typeahead{margin-top:2px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}
|
||||
.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #eee;border:1px solid rgba(0, 0, 0, 0.05);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);}.well blockquote{border-color:#ddd;border-color:rgba(0, 0, 0, 0.15);}
|
||||
.well-large{padding:24px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;}
|
||||
.well-small{padding:9px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;}
|
||||
.fade{-webkit-transition:opacity 0.15s linear;-moz-transition:opacity 0.15s linear;-ms-transition:opacity 0.15s linear;-o-transition:opacity 0.15s linear;transition:opacity 0.15s linear;opacity:0;}.fade.in{opacity:1;}
|
||||
.collapse{-webkit-transition:height 0.35s ease;-moz-transition:height 0.35s ease;-ms-transition:height 0.35s ease;-o-transition:height 0.35s ease;transition:height 0.35s ease;position:relative;overflow:hidden;height:0;}.collapse.in{height:auto;}
|
||||
.close{float:right;font-size:20px;font-weight:bold;line-height:18px;color:#000000;text-shadow:0 1px 0 #ffffff;opacity:0.2;filter:alpha(opacity=20);}.close:hover{color:#000000;text-decoration:none;opacity:0.4;filter:alpha(opacity=40);cursor:pointer;}
|
||||
.btn{display:inline-block;*display:inline;*zoom:1;padding:4px 10px 4px;margin-bottom:0;font-size:13px;line-height:18px;color:#333333;text-align:center;text-shadow:0 1px 1px rgba(255, 255, 255, 0.75);vertical-align:middle;background-color:#f5f5f5;background-image:-moz-linear-gradient(top, #ffffff, #e6e6e6);background-image:-ms-linear-gradient(top, #ffffff, #e6e6e6);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6));background-image:-webkit-linear-gradient(top, #ffffff, #e6e6e6);background-image:-o-linear-gradient(top, #ffffff, #e6e6e6);background-image:linear-gradient(top, #ffffff, #e6e6e6);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#e6e6e6', GradientType=0);border-color:#e6e6e6 #e6e6e6 #bfbfbf;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);filter:progid:dximagetransform.microsoft.gradient(enabled=false);border:1px solid #cccccc;border-bottom-color:#b3b3b3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);cursor:pointer;*margin-left:.3em;}.btn:hover,.btn:active,.btn.active,.btn.disabled,.btn[disabled]{background-color:#e6e6e6;}
|
||||
.btn:active,.btn.active{background-color:#cccccc \9;}
|
||||
.btn:first-child{*margin-left:0;}
|
||||
.btn:hover{color:#333333;text-decoration:none;background-color:#e6e6e6;background-position:0 -15px;-webkit-transition:background-position 0.1s linear;-moz-transition:background-position 0.1s linear;-ms-transition:background-position 0.1s linear;-o-transition:background-position 0.1s linear;transition:background-position 0.1s linear;}
|
||||
.btn:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px;}
|
||||
.btn.active,.btn:active{background-image:none;-webkit-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);background-color:#e6e6e6;background-color:#d9d9d9 \9;outline:0;}
|
||||
.btn.disabled,.btn[disabled]{cursor:default;background-image:none;background-color:#e6e6e6;opacity:0.65;filter:alpha(opacity=65);-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;}
|
||||
.btn-large{padding:9px 14px;font-size:15px;line-height:normal;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;}
|
||||
.btn-large [class^="icon-"]{margin-top:1px;}
|
||||
.btn-small{padding:5px 9px;font-size:11px;line-height:16px;}
|
||||
.btn-small [class^="icon-"]{margin-top:-1px;}
|
||||
.btn-mini{padding:2px 6px;font-size:11px;line-height:14px;}
|
||||
.btn-primary,.btn-primary:hover,.btn-warning,.btn-warning:hover,.btn-danger,.btn-danger:hover,.btn-success,.btn-success:hover,.btn-info,.btn-info:hover,.btn-inverse,.btn-inverse:hover{text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);color:#ffffff;}
|
||||
.btn-primary.active,.btn-warning.active,.btn-danger.active,.btn-success.active,.btn-info.active,.btn-inverse.active{color:rgba(255, 255, 255, 0.75);}
|
||||
.btn-primary{background-color:#366c80;background-image:-moz-linear-gradient(top, #367380, #366180);background-image:-ms-linear-gradient(top, #367380, #366180);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#367380), to(#366180));background-image:-webkit-linear-gradient(top, #367380, #366180);background-image:-o-linear-gradient(top, #367380, #366180);background-image:linear-gradient(top, #367380, #366180);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#367380', endColorstr='#366180', GradientType=0);border-color:#366180 #366180 #1f384a;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);filter:progid:dximagetransform.microsoft.gradient(enabled=false);}.btn-primary:hover,.btn-primary:active,.btn-primary.active,.btn-primary.disabled,.btn-primary[disabled]{background-color:#366180;}
|
||||
.btn-primary:active,.btn-primary.active{background-color:#27455c \9;}
|
||||
.btn-warning{background-color:#faa732;background-image:-moz-linear-gradient(top, #fbb450, #f89406);background-image:-ms-linear-gradient(top, #fbb450, #f89406);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#fbb450), to(#f89406));background-image:-webkit-linear-gradient(top, #fbb450, #f89406);background-image:-o-linear-gradient(top, #fbb450, #f89406);background-image:linear-gradient(top, #fbb450, #f89406);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fbb450', endColorstr='#f89406', GradientType=0);border-color:#f89406 #f89406 #ad6704;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);filter:progid:dximagetransform.microsoft.gradient(enabled=false);}.btn-warning:hover,.btn-warning:active,.btn-warning.active,.btn-warning.disabled,.btn-warning[disabled]{background-color:#f89406;}
|
||||
.btn-warning:active,.btn-warning.active{background-color:#c67605 \9;}
|
||||
.btn-danger{background-color:#da4f49;background-image:-moz-linear-gradient(top, #ee5f5b, #bd362f);background-image:-ms-linear-gradient(top, #ee5f5b, #bd362f);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#bd362f));background-image:-webkit-linear-gradient(top, #ee5f5b, #bd362f);background-image:-o-linear-gradient(top, #ee5f5b, #bd362f);background-image:linear-gradient(top, #ee5f5b, #bd362f);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ee5f5b', endColorstr='#bd362f', GradientType=0);border-color:#bd362f #bd362f #802420;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);filter:progid:dximagetransform.microsoft.gradient(enabled=false);}.btn-danger:hover,.btn-danger:active,.btn-danger.active,.btn-danger.disabled,.btn-danger[disabled]{background-color:#bd362f;}
|
||||
.btn-danger:active,.btn-danger.active{background-color:#942a25 \9;}
|
||||
.btn-success{background-color:#5bb75b;background-image:-moz-linear-gradient(top, #62c462, #51a351);background-image:-ms-linear-gradient(top, #62c462, #51a351);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#51a351));background-image:-webkit-linear-gradient(top, #62c462, #51a351);background-image:-o-linear-gradient(top, #62c462, #51a351);background-image:linear-gradient(top, #62c462, #51a351);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#62c462', endColorstr='#51a351', GradientType=0);border-color:#51a351 #51a351 #387038;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);filter:progid:dximagetransform.microsoft.gradient(enabled=false);}.btn-success:hover,.btn-success:active,.btn-success.active,.btn-success.disabled,.btn-success[disabled]{background-color:#51a351;}
|
||||
.btn-success:active,.btn-success.active{background-color:#408140 \9;}
|
||||
.btn-info{background-color:#49afcd;background-image:-moz-linear-gradient(top, #5bc0de, #2f96b4);background-image:-ms-linear-gradient(top, #5bc0de, #2f96b4);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#2f96b4));background-image:-webkit-linear-gradient(top, #5bc0de, #2f96b4);background-image:-o-linear-gradient(top, #5bc0de, #2f96b4);background-image:linear-gradient(top, #5bc0de, #2f96b4);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#5bc0de', endColorstr='#2f96b4', GradientType=0);border-color:#2f96b4 #2f96b4 #1f6377;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);filter:progid:dximagetransform.microsoft.gradient(enabled=false);}.btn-info:hover,.btn-info:active,.btn-info.active,.btn-info.disabled,.btn-info[disabled]{background-color:#2f96b4;}
|
||||
.btn-info:active,.btn-info.active{background-color:#24748c \9;}
|
||||
.btn-inverse{background-color:#414141;background-image:-moz-linear-gradient(top, #555555, #222222);background-image:-ms-linear-gradient(top, #555555, #222222);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#555555), to(#222222));background-image:-webkit-linear-gradient(top, #555555, #222222);background-image:-o-linear-gradient(top, #555555, #222222);background-image:linear-gradient(top, #555555, #222222);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#555555', endColorstr='#222222', GradientType=0);border-color:#222222 #222222 #000000;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);filter:progid:dximagetransform.microsoft.gradient(enabled=false);}.btn-inverse:hover,.btn-inverse:active,.btn-inverse.active,.btn-inverse.disabled,.btn-inverse[disabled]{background-color:#222222;}
|
||||
.btn-inverse:active,.btn-inverse.active{background-color:#080808 \9;}
|
||||
button.btn,input[type="submit"].btn{*padding-top:2px;*padding-bottom:2px;}button.btn::-moz-focus-inner,input[type="submit"].btn::-moz-focus-inner{padding:0;border:0;}
|
||||
button.btn.btn-large,input[type="submit"].btn.btn-large{*padding-top:7px;*padding-bottom:7px;}
|
||||
button.btn.btn-small,input[type="submit"].btn.btn-small{*padding-top:3px;*padding-bottom:3px;}
|
||||
button.btn.btn-mini,input[type="submit"].btn.btn-mini{*padding-top:1px;*padding-bottom:1px;}
|
||||
.btn-group{position:relative;*zoom:1;*margin-left:.3em;}.btn-group:before,.btn-group:after{display:table;content:"";}
|
||||
.btn-group:after{clear:both;}
|
||||
.btn-group:first-child{*margin-left:0;}
|
||||
.btn-group+.btn-group{margin-left:5px;}
|
||||
.btn-toolbar{margin-top:9px;margin-bottom:9px;}.btn-toolbar .btn-group{display:inline-block;*display:inline;*zoom:1;}
|
||||
.btn-group .btn{position:relative;float:left;margin-left:-1px;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;}
|
||||
.btn-group .btn:first-child{margin-left:0;-webkit-border-top-left-radius:4px;-moz-border-radius-topleft:4px;border-top-left-radius:4px;-webkit-border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px;border-bottom-left-radius:4px;}
|
||||
.btn-group .btn:last-child,.btn-group .dropdown-toggle{-webkit-border-top-right-radius:4px;-moz-border-radius-topright:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px;border-bottom-right-radius:4px;}
|
||||
.btn-group .btn.large:first-child{margin-left:0;-webkit-border-top-left-radius:6px;-moz-border-radius-topleft:6px;border-top-left-radius:6px;-webkit-border-bottom-left-radius:6px;-moz-border-radius-bottomleft:6px;border-bottom-left-radius:6px;}
|
||||
.btn-group .btn.large:last-child,.btn-group .large.dropdown-toggle{-webkit-border-top-right-radius:6px;-moz-border-radius-topright:6px;border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;-moz-border-radius-bottomright:6px;border-bottom-right-radius:6px;}
|
||||
.btn-group .btn:hover,.btn-group .btn:focus,.btn-group .btn:active,.btn-group .btn.active{z-index:2;}
|
||||
.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0;}
|
||||
.btn-group .dropdown-toggle{padding-left:8px;padding-right:8px;-webkit-box-shadow:inset 1px 0 0 rgba(255, 255, 255, 0.125),inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 1px 0 0 rgba(255, 255, 255, 0.125),inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 1px 0 0 rgba(255, 255, 255, 0.125),inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);*padding-top:3px;*padding-bottom:3px;}
|
||||
.btn-group .btn-mini.dropdown-toggle{padding-left:5px;padding-right:5px;*padding-top:1px;*padding-bottom:1px;}
|
||||
.btn-group .btn-small.dropdown-toggle{*padding-top:4px;*padding-bottom:4px;}
|
||||
.btn-group .btn-large.dropdown-toggle{padding-left:12px;padding-right:12px;}
|
||||
.btn-group.open{*z-index:1000;}.btn-group.open .dropdown-menu{display:block;margin-top:1px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;}
|
||||
.btn-group.open .dropdown-toggle{background-image:none;-webkit-box-shadow:inset 0 1px 6px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 1px 6px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 6px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);}
|
||||
.btn .caret{margin-top:7px;margin-left:0;}
|
||||
.btn:hover .caret,.open.btn-group .caret{opacity:1;filter:alpha(opacity=100);}
|
||||
.btn-mini .caret{margin-top:5px;}
|
||||
.btn-small .caret{margin-top:6px;}
|
||||
.btn-large .caret{margin-top:6px;border-left:5px solid transparent;border-right:5px solid transparent;border-top:5px solid #000000;}
|
||||
.btn-primary .caret,.btn-warning .caret,.btn-danger .caret,.btn-info .caret,.btn-success .caret,.btn-inverse .caret{border-top-color:#ffffff;border-bottom-color:#ffffff;opacity:0.75;filter:alpha(opacity=75);}
|
||||
.alert{padding:8px 35px 8px 14px;margin-bottom:18px;text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);background-color:#fcf8e3;border:1px solid #fbeed5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;color:#c09853;}
|
||||
.alert-heading{color:inherit;}
|
||||
.alert .close{position:relative;top:-2px;right:-21px;line-height:18px;}
|
||||
.alert-success{background-color:#dff0d8;border-color:#d6e9c6;color:#468847;}
|
||||
.alert-danger,.alert-error{background-color:#f2dede;border-color:#eed3d7;color:#b94a48;}
|
||||
.alert-info{background-color:#d9edf7;border-color:#bce8f1;color:#3a87ad;}
|
||||
.alert-block{padding-top:14px;padding-bottom:14px;}
|
||||
.alert-block>p,.alert-block>ul{margin-bottom:0;}
|
||||
.alert-block p+p{margin-top:5px;}
|
||||
.nav{margin-left:0;margin-bottom:18px;list-style:none;}
|
||||
.nav>li>a{display:block;}
|
||||
.nav>li>a:hover{text-decoration:none;background-color:#eeeeee;}
|
||||
.nav .nav-header{display:block;padding:3px 15px;font-size:11px;font-weight:bold;line-height:18px;color:#999999;text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);text-transform:uppercase;}
|
||||
.nav li+.nav-header{margin-top:9px;}
|
||||
.nav-list{padding-left:15px;padding-right:15px;margin-bottom:0;}
|
||||
.nav-list>li>a,.nav-list .nav-header{margin-left:-15px;margin-right:-15px;text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);}
|
||||
.nav-list>li>a{padding:3px 15px;}
|
||||
.nav-list>.active>a,.nav-list>.active>a:hover{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.2);background-color:#367380;}
|
||||
.nav-list [class^="icon-"]{margin-right:2px;}
|
||||
.nav-list .divider{height:1px;margin:8px 1px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #ffffff;*width:100%;*margin:-5px 0 5px;}
|
||||
.nav-tabs,.nav-pills{*zoom:1;}.nav-tabs:before,.nav-pills:before,.nav-tabs:after,.nav-pills:after{display:table;content:"";}
|
||||
.nav-tabs:after,.nav-pills:after{clear:both;}
|
||||
.nav-tabs>li,.nav-pills>li{float:left;}
|
||||
.nav-tabs>li>a,.nav-pills>li>a{padding-right:12px;padding-left:12px;margin-right:2px;line-height:14px;}
|
||||
.nav-tabs{border-bottom:1px solid #ddd;}
|
||||
.nav-tabs>li{margin-bottom:-1px;}
|
||||
.nav-tabs>li>a{padding-top:8px;padding-bottom:8px;line-height:18px;border:1px solid transparent;-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0;}.nav-tabs>li>a:hover{border-color:#eeeeee #eeeeee #dddddd;}
|
||||
.nav-tabs>.active>a,.nav-tabs>.active>a:hover{color:#555555;background-color:#ffffff;border:1px solid #ddd;border-bottom-color:transparent;cursor:default;}
|
||||
.nav-pills>li>a{padding-top:8px;padding-bottom:8px;margin-top:2px;margin-bottom:2px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;}
|
||||
.nav-pills>.active>a,.nav-pills>.active>a:hover{color:#ffffff;background-color:#367380;}
|
||||
.nav-stacked>li{float:none;}
|
||||
.nav-stacked>li>a{margin-right:0;}
|
||||
.nav-tabs.nav-stacked{border-bottom:0;}
|
||||
.nav-tabs.nav-stacked>li>a{border:1px solid #ddd;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;}
|
||||
.nav-tabs.nav-stacked>li:first-child>a{-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0;}
|
||||
.nav-tabs.nav-stacked>li:last-child>a{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px;}
|
||||
.nav-tabs.nav-stacked>li>a:hover{border-color:#ddd;z-index:2;}
|
||||
.nav-pills.nav-stacked>li>a{margin-bottom:3px;}
|
||||
.nav-pills.nav-stacked>li:last-child>a{margin-bottom:1px;}
|
||||
.nav-tabs .dropdown-menu,.nav-pills .dropdown-menu{margin-top:1px;border-width:1px;}
|
||||
.nav-pills .dropdown-menu{-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}
|
||||
.nav-tabs .dropdown-toggle .caret,.nav-pills .dropdown-toggle .caret{border-top-color:#367380;border-bottom-color:#367380;margin-top:6px;}
|
||||
.nav-tabs .dropdown-toggle:hover .caret,.nav-pills .dropdown-toggle:hover .caret{border-top-color:#1f434a;border-bottom-color:#1f434a;}
|
||||
.nav-tabs .active .dropdown-toggle .caret,.nav-pills .active .dropdown-toggle .caret{border-top-color:#333333;border-bottom-color:#333333;}
|
||||
.nav>.dropdown.active>a:hover{color:#000000;cursor:pointer;}
|
||||
.nav-tabs .open .dropdown-toggle,.nav-pills .open .dropdown-toggle,.nav>.open.active>a:hover{color:#ffffff;background-color:#999999;border-color:#999999;}
|
||||
.nav .open .caret,.nav .open.active .caret,.nav .open a:hover .caret{border-top-color:#ffffff;border-bottom-color:#ffffff;opacity:1;filter:alpha(opacity=100);}
|
||||
.tabs-stacked .open>a:hover{border-color:#999999;}
|
||||
.tabbable{*zoom:1;}.tabbable:before,.tabbable:after{display:table;content:"";}
|
||||
.tabbable:after{clear:both;}
|
||||
.tab-content{display:table;width:100%;}
|
||||
.tabs-below .nav-tabs,.tabs-right .nav-tabs,.tabs-left .nav-tabs{border-bottom:0;}
|
||||
.tab-content>.tab-pane,.pill-content>.pill-pane{display:none;}
|
||||
.tab-content>.active,.pill-content>.active{display:block;}
|
||||
.tabs-below .nav-tabs{border-top:1px solid #ddd;}
|
||||
.tabs-below .nav-tabs>li{margin-top:-1px;margin-bottom:0;}
|
||||
.tabs-below .nav-tabs>li>a{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px;}.tabs-below .nav-tabs>li>a:hover{border-bottom-color:transparent;border-top-color:#ddd;}
|
||||
.tabs-below .nav-tabs .active>a,.tabs-below .nav-tabs .active>a:hover{border-color:transparent #ddd #ddd #ddd;}
|
||||
.tabs-left .nav-tabs>li,.tabs-right .nav-tabs>li{float:none;}
|
||||
.tabs-left .nav-tabs>li>a,.tabs-right .nav-tabs>li>a{min-width:74px;margin-right:0;margin-bottom:3px;}
|
||||
.tabs-left .nav-tabs{float:left;margin-right:19px;border-right:1px solid #ddd;}
|
||||
.tabs-left .nav-tabs>li>a{margin-right:-1px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px;}
|
||||
.tabs-left .nav-tabs>li>a:hover{border-color:#eeeeee #dddddd #eeeeee #eeeeee;}
|
||||
.tabs-left .nav-tabs .active>a,.tabs-left .nav-tabs .active>a:hover{border-color:#ddd transparent #ddd #ddd;*border-right-color:#ffffff;}
|
||||
.tabs-right .nav-tabs{float:right;margin-left:19px;border-left:1px solid #ddd;}
|
||||
.tabs-right .nav-tabs>li>a{margin-left:-1px;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0;}
|
||||
.tabs-right .nav-tabs>li>a:hover{border-color:#eeeeee #eeeeee #eeeeee #dddddd;}
|
||||
.tabs-right .nav-tabs .active>a,.tabs-right .nav-tabs .active>a:hover{border-color:#ddd #ddd #ddd transparent;*border-left-color:#ffffff;}
|
||||
.navbar{*position:relative;*z-index:2;overflow:visible;margin-bottom:18px;}
|
||||
.navbar-inner{padding-left:20px;padding-right:20px;background-color:#1b454e;background-image:-moz-linear-gradient(top, #27535c, #0a3138);background-image:-ms-linear-gradient(top, #27535c, #0a3138);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#27535c), to(#0a3138));background-image:-webkit-linear-gradient(top, #27535c, #0a3138);background-image:-o-linear-gradient(top, #27535c, #0a3138);background-image:linear-gradient(top, #27535c, #0a3138);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#27535c', endColorstr='#0a3138', GradientType=0);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 3px rgba(0, 0, 0, 0.25),inset 0 -1px 0 rgba(0, 0, 0, 0.1);-moz-box-shadow:0 1px 3px rgba(0, 0, 0, 0.25),inset 0 -1px 0 rgba(0, 0, 0, 0.1);box-shadow:0 1px 3px rgba(0, 0, 0, 0.25),inset 0 -1px 0 rgba(0, 0, 0, 0.1);}
|
||||
.navbar .container{width:auto;}
|
||||
.btn-navbar{display:none;float:right;padding:7px 10px;margin-left:5px;margin-right:5px;background-color:#1b454e;background-image:-moz-linear-gradient(top, #27535c, #0a3138);background-image:-ms-linear-gradient(top, #27535c, #0a3138);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#27535c), to(#0a3138));background-image:-webkit-linear-gradient(top, #27535c, #0a3138);background-image:-o-linear-gradient(top, #27535c, #0a3138);background-image:linear-gradient(top, #27535c, #0a3138);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#27535c', endColorstr='#0a3138', GradientType=0);border-color:#0a3138 #0a3138 #000000;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);filter:progid:dximagetransform.microsoft.gradient(enabled=false);-webkit-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.1),0 1px 0 rgba(255, 255, 255, 0.075);-moz-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.1),0 1px 0 rgba(255, 255, 255, 0.075);box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.1),0 1px 0 rgba(255, 255, 255, 0.075);}.btn-navbar:hover,.btn-navbar:active,.btn-navbar.active,.btn-navbar.disabled,.btn-navbar[disabled]{background-color:#0a3138;}
|
||||
.btn-navbar:active,.btn-navbar.active{background-color:#020b0d \9;}
|
||||
.btn-navbar .icon-bar{display:block;width:18px;height:2px;background-color:#f5f5f5;-webkit-border-radius:1px;-moz-border-radius:1px;border-radius:1px;-webkit-box-shadow:0 1px 0 rgba(0, 0, 0, 0.25);-moz-box-shadow:0 1px 0 rgba(0, 0, 0, 0.25);box-shadow:0 1px 0 rgba(0, 0, 0, 0.25);}
|
||||
.btn-navbar .icon-bar+.icon-bar{margin-top:3px;}
|
||||
.nav-collapse.collapse{height:auto;}
|
||||
.navbar{color:#bbbbbb;}.navbar .brand:hover{text-decoration:none;}
|
||||
.navbar .brand{float:left;display:block;padding:8px 20px 12px;margin-left:-20px;font-size:20px;font-weight:200;line-height:1;color:#ffffff;}
|
||||
.navbar .navbar-text{margin-bottom:0;line-height:40px;}
|
||||
.navbar .btn,.navbar .btn-group{margin-top:5px;}
|
||||
.navbar .btn-group .btn{margin-top:0;}
|
||||
.navbar-form{margin-bottom:0;*zoom:1;}.navbar-form:before,.navbar-form:after{display:table;content:"";}
|
||||
.navbar-form:after{clear:both;}
|
||||
.navbar-form input,.navbar-form select,.navbar-form .radio,.navbar-form .checkbox{margin-top:5px;}
|
||||
.navbar-form input,.navbar-form select{display:inline-block;margin-bottom:0;}
|
||||
.navbar-form input[type="image"],.navbar-form input[type="checkbox"],.navbar-form input[type="radio"]{margin-top:3px;}
|
||||
.navbar-form .input-append,.navbar-form .input-prepend{margin-top:6px;white-space:nowrap;}.navbar-form .input-append input,.navbar-form .input-prepend input{margin-top:0;}
|
||||
.navbar-search{position:relative;float:left;margin-top:6px;margin-bottom:0;}.navbar-search .search-query{padding:4px 9px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:1;color:#ffffff;background-color:#1d90a4;border:1px solid #061e22;-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1),0 1px 0px rgba(255, 255, 255, 0.15);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1),0 1px 0px rgba(255, 255, 255, 0.15);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1),0 1px 0px rgba(255, 255, 255, 0.15);-webkit-transition:none;-moz-transition:none;-ms-transition:none;-o-transition:none;transition:none;}.navbar-search .search-query:-moz-placeholder{color:#cccccc;}
|
||||
.navbar-search .search-query::-webkit-input-placeholder{color:#cccccc;}
|
||||
.navbar-search .search-query:focus,.navbar-search .search-query.focused{padding:5px 10px;color:#333333;text-shadow:0 1px 0 #ffffff;background-color:#ffffff;border:0;-webkit-box-shadow:0 0 3px rgba(0, 0, 0, 0.15);-moz-box-shadow:0 0 3px rgba(0, 0, 0, 0.15);box-shadow:0 0 3px rgba(0, 0, 0, 0.15);outline:0;}
|
||||
.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030;margin-bottom:0;}
|
||||
.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding-left:0;padding-right:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;}
|
||||
.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px;}
|
||||
.navbar-fixed-top{top:0;}
|
||||
.navbar-fixed-bottom{bottom:0;}
|
||||
.navbar .nav{position:relative;left:0;display:block;float:left;margin:0 10px 0 0;}
|
||||
.navbar .nav.pull-right{float:right;}
|
||||
.navbar .nav>li{display:block;float:left;}
|
||||
.navbar .nav>li>a{float:none;padding:10px 10px 11px;line-height:19px;color:#bbbbbb;text-decoration:none;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);}
|
||||
.navbar .nav>li>a:hover{background-color:transparent;color:#ffffff;text-decoration:none;}
|
||||
.navbar .nav .active>a,.navbar .nav .active>a:hover{color:#ffffff;text-decoration:none;background-color:#0a3138;}
|
||||
.navbar .divider-vertical{height:40px;width:1px;margin:0 9px;overflow:hidden;background-color:#0a3138;border-right:1px solid #27535c;}
|
||||
.navbar .nav.pull-right{margin-left:10px;margin-right:0;}
|
||||
.navbar .dropdown-menu{margin-top:1px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}.navbar .dropdown-menu:before{content:'';display:inline-block;border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-bottom-color:rgba(0, 0, 0, 0.2);position:absolute;top:-7px;left:9px;}
|
||||
.navbar .dropdown-menu:after{content:'';display:inline-block;border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #ffffff;position:absolute;top:-6px;left:10px;}
|
||||
.navbar-fixed-bottom .dropdown-menu:before{border-top:7px solid #ccc;border-top-color:rgba(0, 0, 0, 0.2);border-bottom:0;bottom:-7px;top:auto;}
|
||||
.navbar-fixed-bottom .dropdown-menu:after{border-top:6px solid #ffffff;border-bottom:0;bottom:-6px;top:auto;}
|
||||
.navbar .nav .dropdown-toggle .caret,.navbar .nav .open.dropdown .caret{border-top-color:#ffffff;border-bottom-color:#ffffff;}
|
||||
.navbar .nav .active .caret{opacity:1;filter:alpha(opacity=100);}
|
||||
.navbar .nav .open>.dropdown-toggle,.navbar .nav .active>.dropdown-toggle,.navbar .nav .open.active>.dropdown-toggle{background-color:transparent;}
|
||||
.navbar .nav .active>.dropdown-toggle:hover{color:#ffffff;}
|
||||
.navbar .nav.pull-right .dropdown-menu,.navbar .nav .dropdown-menu.pull-right{left:auto;right:0;}.navbar .nav.pull-right .dropdown-menu:before,.navbar .nav .dropdown-menu.pull-right:before{left:auto;right:12px;}
|
||||
.navbar .nav.pull-right .dropdown-menu:after,.navbar .nav .dropdown-menu.pull-right:after{left:auto;right:13px;}
|
||||
.breadcrumb{padding:7px 14px;margin:0 0 18px;list-style:none;background-color:#fbfbfb;background-image:-moz-linear-gradient(top, #ffffff, #f5f5f5);background-image:-ms-linear-gradient(top, #ffffff, #f5f5f5);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#f5f5f5));background-image:-webkit-linear-gradient(top, #ffffff, #f5f5f5);background-image:-o-linear-gradient(top, #ffffff, #f5f5f5);background-image:linear-gradient(top, #ffffff, #f5f5f5);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#f5f5f5', GradientType=0);border:1px solid #ddd;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:inset 0 1px 0 #ffffff;-moz-box-shadow:inset 0 1px 0 #ffffff;box-shadow:inset 0 1px 0 #ffffff;}.breadcrumb li{display:inline-block;*display:inline;*zoom:1;text-shadow:0 1px 0 #ffffff;}
|
||||
.breadcrumb .divider{padding:0 5px;color:#999999;}
|
||||
.breadcrumb .active a{color:#333333;}
|
||||
.pagination{height:36px;margin:18px 0;}
|
||||
.pagination ul{display:inline-block;*display:inline;*zoom:1;margin-left:0;margin-bottom:0;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);}
|
||||
.pagination li{display:inline;}
|
||||
.pagination a{float:left;padding:0 14px;line-height:34px;text-decoration:none;border:1px solid #ddd;border-left-width:0;}
|
||||
.pagination a:hover,.pagination .active a{background-color:#f5f5f5;}
|
||||
.pagination .active a{color:#999999;cursor:default;}
|
||||
.pagination .disabled span,.pagination .disabled a,.pagination .disabled a:hover{color:#999999;background-color:transparent;cursor:default;}
|
||||
.pagination li:first-child a{border-left-width:1px;-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;}
|
||||
.pagination li:last-child a{-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;}
|
||||
.pagination-centered{text-align:center;}
|
||||
.pagination-right{text-align:right;}
|
||||
.pager{margin-left:0;margin-bottom:18px;list-style:none;text-align:center;*zoom:1;}.pager:before,.pager:after{display:table;content:"";}
|
||||
.pager:after{clear:both;}
|
||||
.pager li{display:inline;}
|
||||
.pager a{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px;}
|
||||
.pager a:hover{text-decoration:none;background-color:#f5f5f5;}
|
||||
.pager .next a{float:right;}
|
||||
.pager .previous a{float:left;}
|
||||
.pager .disabled a,.pager .disabled a:hover{color:#999999;background-color:#fff;cursor:default;}
|
||||
.modal-open .dropdown-menu{z-index:2050;}
|
||||
.modal-open .dropdown.open{*z-index:2050;}
|
||||
.modal-open .popover{z-index:2060;}
|
||||
.modal-open .tooltip{z-index:2070;}
|
||||
.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000000;}.modal-backdrop.fade{opacity:0;}
|
||||
.modal-backdrop,.modal-backdrop.fade.in{opacity:0.8;filter:alpha(opacity=80);}
|
||||
.modal{position:fixed;top:50%;left:50%;z-index:1050;overflow:auto;width:560px;margin:-250px 0 0 -280px;background-color:#ffffff;border:1px solid #999;border:1px solid rgba(0, 0, 0, 0.3);*border:1px solid #999;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-moz-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box;}.modal.fade{-webkit-transition:opacity .3s linear, top .3s ease-out;-moz-transition:opacity .3s linear, top .3s ease-out;-ms-transition:opacity .3s linear, top .3s ease-out;-o-transition:opacity .3s linear, top .3s ease-out;transition:opacity .3s linear, top .3s ease-out;top:-25%;}
|
||||
.modal.fade.in{top:50%;}
|
||||
.modal-header{padding:9px 15px;border-bottom:1px solid #eee;}.modal-header .close{margin-top:2px;}
|
||||
.modal-body{overflow-y:auto;max-height:400px;padding:15px;}
|
||||
.modal-form{margin-bottom:0;}
|
||||
.modal-footer{padding:14px 15px 15px;margin-bottom:0;text-align:right;background-color:#f5f5f5;border-top:1px solid #ddd;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;-webkit-box-shadow:inset 0 1px 0 #ffffff;-moz-box-shadow:inset 0 1px 0 #ffffff;box-shadow:inset 0 1px 0 #ffffff;*zoom:1;}.modal-footer:before,.modal-footer:after{display:table;content:"";}
|
||||
.modal-footer:after{clear:both;}
|
||||
.modal-footer .btn+.btn{margin-left:5px;margin-bottom:0;}
|
||||
.modal-footer .btn-group .btn+.btn{margin-left:-1px;}
|
||||
.tooltip{position:absolute;z-index:1020;display:block;visibility:visible;padding:5px;font-size:11px;opacity:0;filter:alpha(opacity=0);}.tooltip.in{opacity:0.8;filter:alpha(opacity=80);}
|
||||
.tooltip.top{margin-top:-2px;}
|
||||
.tooltip.right{margin-left:2px;}
|
||||
.tooltip.bottom{margin-top:2px;}
|
||||
.tooltip.left{margin-left:-2px;}
|
||||
.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-top:5px solid #000000;}
|
||||
.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-left:5px solid #000000;}
|
||||
.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-bottom:5px solid #000000;}
|
||||
.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-right:5px solid #000000;}
|
||||
.tooltip-inner{max-width:200px;padding:3px 8px;color:#ffffff;text-align:center;text-decoration:none;background-color:#000000;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}
|
||||
.tooltip-arrow{position:absolute;width:0;height:0;}
|
||||
.popover{position:absolute;top:0;left:0;z-index:1010;display:none;padding:5px;}.popover.top{margin-top:-5px;}
|
||||
.popover.right{margin-left:5px;}
|
||||
.popover.bottom{margin-top:5px;}
|
||||
.popover.left{margin-left:-5px;}
|
||||
.popover.top .arrow{bottom:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-top:5px solid #000000;}
|
||||
.popover.right .arrow{top:50%;left:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-right:5px solid #000000;}
|
||||
.popover.bottom .arrow{top:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-bottom:5px solid #000000;}
|
||||
.popover.left .arrow{top:50%;right:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-left:5px solid #000000;}
|
||||
.popover .arrow{position:absolute;width:0;height:0;}
|
||||
.popover-inner{padding:3px;width:280px;overflow:hidden;background:#000000;background:rgba(0, 0, 0, 0.8);-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-moz-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);}
|
||||
.popover-title{padding:9px 15px;line-height:1;background-color:#f5f5f5;border-bottom:1px solid #eee;-webkit-border-radius:3px 3px 0 0;-moz-border-radius:3px 3px 0 0;border-radius:3px 3px 0 0;}
|
||||
.popover-content{padding:14px;background-color:#ffffff;-webkit-border-radius:0 0 3px 3px;-moz-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px;-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box;}.popover-content p,.popover-content ul,.popover-content ol{margin-bottom:0;}
|
||||
.thumbnails{margin-left:-20px;list-style:none;*zoom:1;}.thumbnails:before,.thumbnails:after{display:table;content:"";}
|
||||
.thumbnails:after{clear:both;}
|
||||
.thumbnails>li{float:left;margin:0 0 18px 20px;}
|
||||
.thumbnail{display:block;padding:4px;line-height:1;border:1px solid #ddd;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:0 1px 1px rgba(0, 0, 0, 0.075);}
|
||||
a.thumbnail:hover{border-color:#367380;-webkit-box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);-moz-box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);}
|
||||
.thumbnail>img{display:block;max-width:100%;margin-left:auto;margin-right:auto;}
|
||||
.thumbnail .caption{padding:9px;}
|
||||
.label{padding:1px 4px 2px;font-size:10.998px;font-weight:bold;line-height:13px;color:#ffffff;vertical-align:middle;white-space:nowrap;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#999999;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;}
|
||||
.label:hover{color:#ffffff;text-decoration:none;}
|
||||
.label-important{background-color:#b94a48;}
|
||||
.label-important:hover{background-color:#953b39;}
|
||||
.label-warning{background-color:#f89406;}
|
||||
.label-warning:hover{background-color:#c67605;}
|
||||
.label-success{background-color:#468847;}
|
||||
.label-success:hover{background-color:#356635;}
|
||||
.label-info{background-color:#3a87ad;}
|
||||
.label-info:hover{background-color:#2d6987;}
|
||||
.label-inverse{background-color:#333333;}
|
||||
.label-inverse:hover{background-color:#1a1a1a;}
|
||||
.badge{padding:1px 9px 2px;font-size:12.025px;font-weight:bold;white-space:nowrap;color:#ffffff;background-color:#999999;-webkit-border-radius:9px;-moz-border-radius:9px;border-radius:9px;}
|
||||
.badge:hover{color:#ffffff;text-decoration:none;cursor:pointer;}
|
||||
.badge-error{background-color:#b94a48;}
|
||||
.badge-error:hover{background-color:#953b39;}
|
||||
.badge-warning{background-color:#f89406;}
|
||||
.badge-warning:hover{background-color:#c67605;}
|
||||
.badge-success{background-color:#468847;}
|
||||
.badge-success:hover{background-color:#356635;}
|
||||
.badge-info{background-color:#3a87ad;}
|
||||
.badge-info:hover{background-color:#2d6987;}
|
||||
.badge-inverse{background-color:#333333;}
|
||||
.badge-inverse:hover{background-color:#1a1a1a;}
|
||||
@-webkit-keyframes progress-bar-stripes{from{background-position:0 0;} to{background-position:40px 0;}}@-moz-keyframes progress-bar-stripes{from{background-position:0 0;} to{background-position:40px 0;}}@-ms-keyframes progress-bar-stripes{from{background-position:0 0;} to{background-position:40px 0;}}@keyframes progress-bar-stripes{from{background-position:0 0;} to{background-position:40px 0;}}.progress{overflow:hidden;height:18px;margin-bottom:18px;background-color:#f7f7f7;background-image:-moz-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:-ms-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#f5f5f5), to(#f9f9f9));background-image:-webkit-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:-o-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:linear-gradient(top, #f5f5f5, #f9f9f9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#f5f5f5', endColorstr='#f9f9f9', GradientType=0);-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}
|
||||
.progress .bar{width:0%;height:18px;color:#ffffff;font-size:12px;text-align:center;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#0e90d2;background-image:-moz-linear-gradient(top, #149bdf, #0480be);background-image:-ms-linear-gradient(top, #149bdf, #0480be);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#149bdf), to(#0480be));background-image:-webkit-linear-gradient(top, #149bdf, #0480be);background-image:-o-linear-gradient(top, #149bdf, #0480be);background-image:linear-gradient(top, #149bdf, #0480be);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#149bdf', endColorstr='#0480be', GradientType=0);-webkit-box-shadow:inset 0 -1px 0 rgba(0, 0, 0, 0.15);-moz-box-shadow:inset 0 -1px 0 rgba(0, 0, 0, 0.15);box-shadow:inset 0 -1px 0 rgba(0, 0, 0, 0.15);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box;-webkit-transition:width 0.6s ease;-moz-transition:width 0.6s ease;-ms-transition:width 0.6s ease;-o-transition:width 0.6s ease;transition:width 0.6s ease;}
|
||||
.progress-striped .bar{background-color:#149bdf;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-ms-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);-webkit-background-size:40px 40px;-moz-background-size:40px 40px;-o-background-size:40px 40px;background-size:40px 40px;}
|
||||
.progress.active .bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-moz-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite;}
|
||||
.progress-danger .bar{background-color:#dd514c;background-image:-moz-linear-gradient(top, #ee5f5b, #c43c35);background-image:-ms-linear-gradient(top, #ee5f5b, #c43c35);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#c43c35));background-image:-webkit-linear-gradient(top, #ee5f5b, #c43c35);background-image:-o-linear-gradient(top, #ee5f5b, #c43c35);background-image:linear-gradient(top, #ee5f5b, #c43c35);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ee5f5b', endColorstr='#c43c35', GradientType=0);}
|
||||
.progress-danger.progress-striped .bar{background-color:#ee5f5b;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-ms-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);}
|
||||
.progress-success .bar{background-color:#5eb95e;background-image:-moz-linear-gradient(top, #62c462, #57a957);background-image:-ms-linear-gradient(top, #62c462, #57a957);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#57a957));background-image:-webkit-linear-gradient(top, #62c462, #57a957);background-image:-o-linear-gradient(top, #62c462, #57a957);background-image:linear-gradient(top, #62c462, #57a957);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#62c462', endColorstr='#57a957', GradientType=0);}
|
||||
.progress-success.progress-striped .bar{background-color:#62c462;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-ms-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);}
|
||||
.progress-info .bar{background-color:#4bb1cf;background-image:-moz-linear-gradient(top, #5bc0de, #339bb9);background-image:-ms-linear-gradient(top, #5bc0de, #339bb9);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#339bb9));background-image:-webkit-linear-gradient(top, #5bc0de, #339bb9);background-image:-o-linear-gradient(top, #5bc0de, #339bb9);background-image:linear-gradient(top, #5bc0de, #339bb9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#5bc0de', endColorstr='#339bb9', GradientType=0);}
|
||||
.progress-info.progress-striped .bar{background-color:#5bc0de;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-ms-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);}
|
||||
.progress-warning .bar{background-color:#faa732;background-image:-moz-linear-gradient(top, #fbb450, #f89406);background-image:-ms-linear-gradient(top, #fbb450, #f89406);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#fbb450), to(#f89406));background-image:-webkit-linear-gradient(top, #fbb450, #f89406);background-image:-o-linear-gradient(top, #fbb450, #f89406);background-image:linear-gradient(top, #fbb450, #f89406);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fbb450', endColorstr='#f89406', GradientType=0);}
|
||||
.progress-warning.progress-striped .bar{background-color:#fbb450;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-ms-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);}
|
||||
.accordion{margin-bottom:18px;}
|
||||
.accordion-group{margin-bottom:2px;border:1px solid #e5e5e5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}
|
||||
.accordion-heading{border-bottom:0;}
|
||||
.accordion-heading .accordion-toggle{display:block;padding:8px 15px;}
|
||||
.accordion-inner{padding:9px 15px;border-top:1px solid #e5e5e5;}
|
||||
.carousel{position:relative;margin-bottom:18px;line-height:1;}
|
||||
.carousel-inner{overflow:hidden;width:100%;position:relative;}
|
||||
.carousel .item{display:none;position:relative;-webkit-transition:0.6s ease-in-out left;-moz-transition:0.6s ease-in-out left;-ms-transition:0.6s ease-in-out left;-o-transition:0.6s ease-in-out left;transition:0.6s ease-in-out left;}
|
||||
.carousel .item>img{display:block;line-height:1;}
|
||||
.carousel .active,.carousel .next,.carousel .prev{display:block;}
|
||||
.carousel .active{left:0;}
|
||||
.carousel .next,.carousel .prev{position:absolute;top:0;width:100%;}
|
||||
.carousel .next{left:100%;}
|
||||
.carousel .prev{left:-100%;}
|
||||
.carousel .next.left,.carousel .prev.right{left:0;}
|
||||
.carousel .active.left{left:-100%;}
|
||||
.carousel .active.right{left:100%;}
|
||||
.carousel-control{position:absolute;top:40%;left:15px;width:40px;height:40px;margin-top:-20px;font-size:60px;font-weight:100;line-height:30px;color:#ffffff;text-align:center;background:#222222;border:3px solid #ffffff;-webkit-border-radius:23px;-moz-border-radius:23px;border-radius:23px;opacity:0.5;filter:alpha(opacity=50);}.carousel-control.right{left:auto;right:15px;}
|
||||
.carousel-control:hover{color:#ffffff;text-decoration:none;opacity:0.9;filter:alpha(opacity=90);}
|
||||
.carousel-caption{position:absolute;left:0;right:0;bottom:0;padding:10px 15px 5px;background:#333333;background:rgba(0, 0, 0, 0.75);}
|
||||
.carousel-caption h4,.carousel-caption p{color:#ffffff;}
|
||||
.hero-unit{padding:60px;margin-bottom:30px;background-color:#eeeeee;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;}.hero-unit h1{margin-bottom:0;font-size:60px;line-height:1;color:inherit;letter-spacing:-1px;}
|
||||
.hero-unit p{font-size:18px;font-weight:200;line-height:27px;color:inherit;}
|
||||
.pull-right{float:right;}
|
||||
.pull-left{float:left;}
|
||||
.hide{display:none;}
|
||||
.show{display:block;}
|
||||
.invisible{visibility:hidden;}
|
@ -14,7 +14,7 @@ String locIP=request.getLocalAddr();
|
||||
<running><%= isMeetingRunning(request.getParameter("meetingID")) %></running>
|
||||
</response>
|
||||
<% } else if(request.getParameter("command").equals("getRecords")){%>
|
||||
<%= getRecordings("English 101,English 102,English 103,English 104,English 105,English 106,English 107,English 108,English 109,English 110")%>
|
||||
<%= getRecordings(request.getParameter("meetingID"))%>
|
||||
<% } else if(request.getParameter("command").equals("publish")||request.getParameter("command").equals("unpublish")){%>
|
||||
<%= setPublishRecordings( (request.getParameter("command").equals("publish")) ? true : false , request.getParameter("recordID"))%>
|
||||
<% } else if(request.getParameter("command").equals("delete")){%>
|
||||
|
@ -197,6 +197,16 @@ if (request.getParameterMap().isEmpty()) {
|
||||
<td>
|
||||
<input type="password" required name="password" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
</td>
|
||||
<td style="text-align: right; ">
|
||||
Guest:</td>
|
||||
<td>
|
||||
</td>
|
||||
<td>
|
||||
<input type="checkbox" name="guest" value="guest" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
</td>
|
||||
@ -273,7 +283,11 @@ Error: createMeeting() failed
|
||||
// We've got a valid meeting_ID and passoword -- let's join!
|
||||
//
|
||||
|
||||
String joinURL = getJoinMeetingURL(username, meeting_ID, password, null);
|
||||
String joinURL;
|
||||
if(request.getParameter("guest") != null)
|
||||
joinURL = getJoinMeetingURL(username, meeting_ID, password, null, true);
|
||||
else
|
||||
joinURL = getJoinMeetingURL(username, meeting_ID, password, null);
|
||||
%>
|
||||
|
||||
<script language="javascript" type="text/javascript">
|
||||
|
445
bbb-api-demo/src/main/webapp/demo_mconf.jsp
Normal file
@ -0,0 +1,445 @@
|
||||
<!--
|
||||
|
||||
BigBlueButton - http://www.bigbluebutton.org
|
||||
|
||||
Copyright (c) 2008-2009 by respective authors (see below). All rights reserved.
|
||||
|
||||
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 3 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, If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Author: Fred Dixon <ffdixon@bigbluebutton.org>
|
||||
|
||||
-->
|
||||
|
||||
<%@ page language="java" contentType="text/html; charset=UTF-8"
|
||||
pageEncoding="UTF-8"%>
|
||||
<%
|
||||
request.setCharacterEncoding("UTF-8");
|
||||
response.setCharacterEncoding("UTF-8");
|
||||
%>
|
||||
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<title>Mconf-Live Demo</title>
|
||||
<link rel="stylesheet" href="css/mconf-bootstrap.min.css" type="text/css" />
|
||||
<link rel="stylesheet" type="text/css" href="css/ui.jqgrid.css" />
|
||||
<link rel="stylesheet" type="text/css" href="css/redmond/jquery-ui-redmond.css" />
|
||||
<script type="text/javascript" src="js/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="js/jquery-ui.js"></script>
|
||||
<script type="text/javascript" src="js/jquery.validate.min.js"></script>
|
||||
<script src="js/grid.locale-en.js" type="text/javascript"></script>
|
||||
<script src="js/jquery.jqGrid.min.js" type="text/javascript"></script>
|
||||
<script src="js/jquery.xml2json.js" type="text/javascript"></script>
|
||||
<style type="text/css">
|
||||
.ui-jqgrid{
|
||||
font-size:0.7em;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
label.error{
|
||||
float: none;
|
||||
color: red;
|
||||
padding-left: .5em;
|
||||
vertical-align: top;
|
||||
width:200px;
|
||||
text-align:left;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<%@ include file="bbb_api.jsp"%>
|
||||
|
||||
<%
|
||||
|
||||
//
|
||||
// We're going to define some sample courses (meetings) below. This API exampe shows how you can create a login page for a course.
|
||||
// The password below are not available to users as they are compiled on the server.
|
||||
//
|
||||
|
||||
HashMap<String, HashMap> allMeetings = new HashMap<String, HashMap>();
|
||||
HashMap<String, String> meeting;
|
||||
|
||||
// String welcome = "<br>Welcome to %%CONFNAME%%!<br><br>For help see our <a href=\"event:http://www.bigbluebutton.org/content/videos\"><u>tutorial videos</u></a>.<br><br>To join the voice bridge for this meeting:<br> (1) click the headset icon in the upper-left, or<br> (2) dial xxx-xxx-xxxx (toll free:1-xxx-xxx-xxxx) and enter conference ID: %%CONFNUM%%.<br><br>";
|
||||
|
||||
String welcome = "<br>Welcome to <b>%%CONFNAME%%</b>!<br><br>In order to speak, click on the headset icon.";
|
||||
|
||||
meeting = new HashMap<String, String>();
|
||||
allMeetings.put( "Test room 1", meeting ); // The title that will appear in the drop-down menu
|
||||
meeting.put("welcomeMsg", welcome); // The welcome mesage
|
||||
meeting.put("moderatorPW", "prof123"); // The password for moderator
|
||||
meeting.put("viewerPW", "student123"); // The password for viewer
|
||||
meeting.put("voiceBridge", "72013"); // The extension number for the voice bridge (use if connected to phone system)
|
||||
meeting.put("logoutURL", "/demo/demo_mconf.jsp"); // The logout URL (use if you want to return to your pages)
|
||||
|
||||
meeting = new HashMap<String, String>();
|
||||
allMeetings.put( "Test room 2", meeting ); // The title that will appear in the drop-down menu
|
||||
meeting.put("welcomeMsg", welcome); // The welcome mesage
|
||||
meeting.put("moderatorPW", "prof123"); // The password for moderator
|
||||
meeting.put("viewerPW", "student123"); // The password for viewer
|
||||
meeting.put("voiceBridge", "72014"); // The extension number for the voice bridge (use if connected to phone system)
|
||||
meeting.put("logoutURL", "/demo/demo_mconf.jsp"); // The logout URL (use if you want to return to your pages)
|
||||
|
||||
meeting = new HashMap<String, String>();
|
||||
allMeetings.put( "Test room 3", meeting ); // The title that will appear in the drop-down menu
|
||||
meeting.put("welcomeMsg", welcome); // The welcome mesage
|
||||
meeting.put("moderatorPW", "prof123"); // The password for moderator
|
||||
meeting.put("viewerPW", "student123"); // The password for viewer
|
||||
meeting.put("voiceBridge", "72015"); // The extension number for the voice bridge (use if connected to phone system)
|
||||
meeting.put("logoutURL", "/demo/demo_mconf.jsp"); // The logout URL (use if you want to return to your pages)
|
||||
|
||||
meeting = new HashMap<String, String>();
|
||||
allMeetings.put( "Test room 4", meeting ); // The title that will appear in the drop-down menu
|
||||
meeting.put("welcomeMsg", welcome); // The welcome mesage
|
||||
meeting.put("moderatorPW", "prof123"); // The password for moderator
|
||||
meeting.put("viewerPW", "student123"); // The password for viewer
|
||||
meeting.put("voiceBridge", "72016"); // The extension number for the voice bridge (use if connected to phone system)
|
||||
meeting.put("logoutURL", "/demo/demo_mconf.jsp"); // The logout URL (use if you want to return to your pages)
|
||||
|
||||
meeting = null;
|
||||
|
||||
Iterator<String> meetingIterator = new TreeSet<String>(allMeetings.keySet()).iterator();
|
||||
|
||||
if (request.getParameterMap().isEmpty()) {
|
||||
//
|
||||
// Assume we want to join a course
|
||||
//
|
||||
%>
|
||||
|
||||
|
||||
<div style="width: 400px; margin: auto auto; ">
|
||||
<div style="text-align: center; ">
|
||||
<img src="images/mconf.png" style="
|
||||
width: 300px;
|
||||
height: auto;
|
||||
display: block; margin-left: auto; margin-right: auto;
|
||||
">
|
||||
</div>
|
||||
|
||||
<span style="text-align: center; ">
|
||||
<h3>Join a test room</h3>
|
||||
</span>
|
||||
|
||||
<FORM NAME="form1" METHOD="GET">
|
||||
<table cellpadding="3" cellspacing="5">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
</td>
|
||||
<td style="text-align: right; ">
|
||||
Room:</td>
|
||||
<td>
|
||||
|
||||
</td>
|
||||
<td style="text-align: left ">
|
||||
<select name="meetingID" onchange="onChangeMeeting(this.value);">
|
||||
<%
|
||||
String key;
|
||||
while (meetingIterator.hasNext()) {
|
||||
key = meetingIterator.next();
|
||||
out.println("<option value=\"" + key + "\">" + key + "</option>");
|
||||
}
|
||||
%>
|
||||
</select><span id="label_meeting_running" hidden><i> Running!</i></span>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
</td>
|
||||
<td style="text-align: right; ">
|
||||
Full Name:</td>
|
||||
<td style="width: 5px; ">
|
||||
</td>
|
||||
<td style="text-align: left ">
|
||||
<input type="text" autofocus required name="username" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
</td>
|
||||
<td style="text-align: right; ">
|
||||
Role:</td>
|
||||
<td>
|
||||
</td>
|
||||
<td>
|
||||
<input type="radio" name="password" value="prof123" text"Moderator" checked>Moderator</input>
|
||||
<input type="radio" name="password" value="student123">Viewer</input>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
</td>
|
||||
<td style="text-align: right; ">
|
||||
Guest:</td>
|
||||
<td>
|
||||
</td>
|
||||
<td>
|
||||
<input id="check_guest" type="checkbox" name="guest" value="guest" /> (authorization required)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
</td>
|
||||
<td>
|
||||
</td>
|
||||
<td>
|
||||
</td>
|
||||
<td>
|
||||
<input type="submit" value="Join" style="width: 220px; "></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<INPUT TYPE=hidden NAME=action VALUE="create">
|
||||
</FORM>
|
||||
</div>
|
||||
|
||||
<div style="text-align: center; ">
|
||||
<h3>Recorded Sessions</h3>
|
||||
|
||||
<select id="actionscmb" name="actions" onchange="recordedAction(this.value);">
|
||||
<option value="novalue" selected>Actions...</option>
|
||||
<option value="publish">Publish</option>
|
||||
<option value="unpublish">Unpublish</option>
|
||||
<option value="delete">Delete</option>
|
||||
</select>
|
||||
<table id="recordgrid"></table>
|
||||
<div id="pager"></div>
|
||||
<p>Note: New recordings will appear in the above list after processing. Refresh your browser to update the list.</p>
|
||||
<script>
|
||||
function onChangeMeeting(meetingID){
|
||||
isRunningMeeting(meetingID);
|
||||
}
|
||||
function recordedAction(action){
|
||||
if(action=="novalue"){
|
||||
return;
|
||||
}
|
||||
|
||||
var s = jQuery("#recordgrid").jqGrid('getGridParam','selarrrow');
|
||||
if(s.length==0){
|
||||
alert("Select at least one row");
|
||||
$("#actionscmb").val("novalue");
|
||||
return;
|
||||
}
|
||||
var recordid="";
|
||||
for(var i=0;i<s.length;i++){
|
||||
var d = jQuery("#recordgrid").jqGrid('getRowData',s[i]);
|
||||
recordid+=d.id;
|
||||
if(i!=s.length-1)
|
||||
recordid+=",";
|
||||
}
|
||||
if(action=="delete"){
|
||||
var answer = confirm ("Are you sure to delete the selected recordings?");
|
||||
if (answer)
|
||||
sendRecordingAction(recordid,action);
|
||||
else{
|
||||
$("#actionscmb").val("novalue");
|
||||
return;
|
||||
}
|
||||
}else{
|
||||
sendRecordingAction(recordid,action);
|
||||
}
|
||||
$("#actionscmb").val("novalue");
|
||||
}
|
||||
|
||||
function sendRecordingAction(recordID,action){
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: 'demo10_helper.jsp',
|
||||
data: "command="+action+"&recordID="+recordID,
|
||||
dataType: "xml",
|
||||
cache: false,
|
||||
success: function(xml) {
|
||||
window.location.reload(true);
|
||||
$("#recordgrid").trigger("reloadGrid");
|
||||
},
|
||||
error: function() {
|
||||
alert("Failed to connect to API.");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function isRunningMeeting(meetingID) {
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: 'demo10_helper.jsp',
|
||||
data: "command=isRunning&meetingID="+meetingID,
|
||||
dataType: "xml",
|
||||
cache: false,
|
||||
success: function(xml) {
|
||||
response = $.xml2json(xml);
|
||||
if(response.running=="true"){
|
||||
$("#check_record").attr("readonly","readonly");
|
||||
$("#check_record").attr("disabled","disabled");
|
||||
$("#label_meeting_running").removeAttr("hidden");
|
||||
$("#meta_description").val("An active session exists for "+meetingID+". This session is being recorded.");
|
||||
$("#meta_description").attr("readonly","readonly");
|
||||
$("#meta_description").attr("disabled","disabled");
|
||||
}else{
|
||||
$("#check_record").removeAttr("readonly");
|
||||
$("#check_record").removeAttr("disabled");
|
||||
$("#label_meeting_running").attr("hidden","");
|
||||
$("#meta_description").val("");
|
||||
$("#meta_description").removeAttr("readonly");
|
||||
$("#meta_description").removeAttr("disabled");
|
||||
}
|
||||
|
||||
},
|
||||
error: function() {
|
||||
alert("Failed to connect to API.");
|
||||
}
|
||||
});
|
||||
}
|
||||
var meetingID="Test room 1,Test room 2,Test room 3,Test room 4";
|
||||
$(document).ready(function(){
|
||||
isRunningMeeting("Test room 1");
|
||||
$("#formcreate").validate();
|
||||
$("#meetingID option[value='Test room 1']").attr("selected","selected");
|
||||
jQuery("#recordgrid").jqGrid({
|
||||
url: "demo10_helper.jsp?command=getRecords&meetingID="+meetingID,
|
||||
datatype: "xml",
|
||||
height: 150,
|
||||
loadonce: true,
|
||||
sortable: true,
|
||||
colNames:['Id','Room','Date Recorded', 'Published', 'Playback', 'Length'],
|
||||
colModel:[
|
||||
{name:'id',index:'id', width:50, hidden:true, xmlmap: "recordID"},
|
||||
{name:'course',index:'course', width:150, xmlmap: "name", sortable:true},
|
||||
{name:'daterecorded',index:'daterecorded', width:200, xmlmap: "startTime", sortable: true, sorttype: "datetime", datefmt: "d-m-y h:i:s"},
|
||||
{name:'published',index:'published', width:80, xmlmap: "published", sortable:true },
|
||||
{name:'playback',index:'playback', width:150, xmlmap:"playback", sortable:false},
|
||||
{name:'length',index:'length', width:80, xmlmap:"length", sortable:true}
|
||||
],
|
||||
xmlReader: {
|
||||
root : "recordings",
|
||||
row: "recording",
|
||||
repeatitems:false,
|
||||
id: "recordID"
|
||||
},
|
||||
pager : '#pager',
|
||||
emptyrecords: "Nothing to display",
|
||||
multiselect: true,
|
||||
caption: "Recorded Sessions",
|
||||
loadComplete: function(){
|
||||
$("#recordgrid").trigger("reloadGrid");
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
|
||||
<%
|
||||
} else if (request.getParameter("action").equals("create")) {
|
||||
//
|
||||
// Got an action=create
|
||||
//
|
||||
|
||||
String username = request.getParameter("username");
|
||||
String meetingID = request.getParameter("meetingID");
|
||||
String password = request.getParameter("password");
|
||||
|
||||
meeting = allMeetings.get( meetingID );
|
||||
|
||||
String welcomeMsg = meeting.get( "welcomeMsg" );
|
||||
String logoutURL = meeting.get( "logoutURL" );
|
||||
Integer voiceBridge = Integer.parseInt( meeting.get( "voiceBridge" ).trim() );
|
||||
|
||||
String viewerPW = meeting.get( "viewerPW" );
|
||||
String moderatorPW = meeting.get( "moderatorPW" );
|
||||
Boolean guest = request.getParameter("guest") != null;
|
||||
Boolean record = request.getParameter("record") != null;
|
||||
|
||||
//
|
||||
// Check if we have a valid password
|
||||
//
|
||||
if ( ! password.equals(viewerPW) && ! password.equals(moderatorPW) ) {
|
||||
%>
|
||||
|
||||
Invalid Password, please <a href="javascript:history.go(-1)">try again</a>.
|
||||
|
||||
<%
|
||||
return;
|
||||
}
|
||||
|
||||
// create the meeting
|
||||
String base_url_create = BigBlueButtonURL + "api/create?";
|
||||
String base_url_join = BigBlueButtonURL + "api/join?";
|
||||
String welcome_param = "&welcome=" + urlEncode(welcomeMsg);
|
||||
String voiceBridge_param = "&voiceBridge=" + voiceBridge;
|
||||
String moderator_password_param = "&moderatorPW=" + urlEncode(moderatorPW);
|
||||
String attendee_password_param = "&attendeePW=" + urlEncode(viewerPW);
|
||||
String logoutURL_param = "&logoutURL=" + urlEncode(logoutURL);
|
||||
|
||||
String create_parameters = "name=" + urlEncode(meetingID)
|
||||
+ "&meetingID=" + urlEncode(meetingID) + welcome_param + voiceBridge_param
|
||||
+ moderator_password_param + attendee_password_param + logoutURL_param
|
||||
+ "&record=true";
|
||||
|
||||
// Attempt to create a meeting using meetingID
|
||||
Document doc = null;
|
||||
try {
|
||||
String url = base_url_create + create_parameters
|
||||
+ "&checksum="
|
||||
+ checksum("create" + create_parameters + salt);
|
||||
doc = parseXml( postURL( url, "" ) );
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
if (! doc.getElementsByTagName("returncode").item(0).getTextContent()
|
||||
.trim().equals("SUCCESS")) {
|
||||
%>
|
||||
|
||||
Error: createMeeting() failed
|
||||
<p /><%=meetingID%>
|
||||
|
||||
<%
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// Looks good, now return a URL to join that meeting
|
||||
//
|
||||
|
||||
String join_parameters = "meetingID=" + urlEncode(meetingID)
|
||||
+ "&fullName=" + urlEncode(username) + "&password=" + urlEncode(password) + "&guest="+ urlEncode(guest.toString());
|
||||
String joinURL = base_url_join + join_parameters + "&checksum="
|
||||
+ checksum("join" + join_parameters + salt);
|
||||
%>
|
||||
|
||||
<script language="javascript" type="text/javascript">
|
||||
// http://stackoverflow.com/a/11381730
|
||||
mobileAndTabletcheck = function() {
|
||||
var check = false;
|
||||
(function(a){if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4)))check = true})(navigator.userAgent||navigator.vendor||window.opera);
|
||||
return check;
|
||||
}
|
||||
|
||||
processJoinUrl = function(url) {
|
||||
if (mobileAndTabletcheck()) {
|
||||
return url.replace("http://", "bigbluebutton://");
|
||||
} else {
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
||||
window.location.href = processJoinUrl("<%=joinURL%>");
|
||||
</script>
|
||||
|
||||
<%
|
||||
}
|
||||
%>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
BIN
bbb-api-demo/src/main/webapp/images/mconf.png
Normal file
After Width: | Height: | Size: 21 KiB |
2
bbb-client-check/.gitignore
vendored
@ -9,3 +9,5 @@ index.template.html
|
||||
conf/config.xml
|
||||
resources/lib/bbb_webrtc_bridge_sip.js
|
||||
resources/lib/sip.js
|
||||
resources/lib/bbb_localization.js
|
||||
resources/lib/jquery-1.5.1.min.js
|
||||
|
@ -55,7 +55,7 @@
|
||||
<mxmlc file="${SRC_DIR}/BBBClientCheck.mxml"
|
||||
output="check/BBBClientCheck.swf"
|
||||
debug="false"
|
||||
locale="en_US"
|
||||
locale="en_US,pt_BR"
|
||||
actionscript-file-encoding="UTF-8"
|
||||
incremental="false">
|
||||
<static-link-runtime-shared-libraries>false</static-link-runtime-shared-libraries>
|
||||
@ -106,6 +106,8 @@
|
||||
<copy todir="resources/lib/" >
|
||||
<fileset file="../bigbluebutton-client/resources/prod/lib/bbb_webrtc_bridge_sip.js" />
|
||||
<fileset file="../bigbluebutton-client/resources/prod/lib/sip.js" />
|
||||
<fileset file="../bigbluebutton-client/resources/prod/lib/bbb_localization.js" />
|
||||
<fileset file="../bigbluebutton-client/resources/prod/lib/jquery-1.5.1.min.js" />
|
||||
</copy>
|
||||
|
||||
<get src="${TEST_IMAGE_URL}" dest="${html.output}/test_image.jpg" skipexisting="true" />
|
||||
|
@ -34,10 +34,12 @@
|
||||
<script type="text/javascript" src="history/history.js"></script>
|
||||
<!-- END Browser History required section -->
|
||||
|
||||
<script type="text/javascript" src="resources/lib/jquery-1.5.1.min.js"></script>
|
||||
<script type="text/javascript" src="resources/lib/api-bridge.js"></script>
|
||||
<script type="text/javascript" src="resources/lib/sip.js"></script>
|
||||
<script type="text/javascript" src="resources/lib/bbb_webrtc_bridge_sip.js"></script>
|
||||
<script type="text/javascript" src="resources/lib/deployJava.js"></script>
|
||||
<script type="text/javascript" src="resources/lib/bbb_localization.js"></script>
|
||||
<script type="text/javascript" src="swfobject.js"></script>
|
||||
<script type="text/javascript">
|
||||
// For version detection, set to min. required Flash Player version, or 0 (or 0.0.0), for no version detection.
|
||||
|
@ -38,6 +38,7 @@
|
||||
<script type="text/javascript" src="resources/lib/sip.js"></script>
|
||||
<script type="text/javascript" src="resources/lib/bbb_webrtc_bridge_sip.js"></script>
|
||||
<script type="text/javascript" src="resources/lib/deployJava.js"></script>
|
||||
<script type="text/javascript" src="resources/lib/bbb_localization.js"></script>
|
||||
<script type="text/javascript" src="swfobject.js"></script>
|
||||
<script type="text/javascript">
|
||||
// For version detection, set to min. required Flash Player version, or 0 (or 0.0.0), for no version detection.
|
||||
|
@ -1,4 +1,4 @@
|
||||
bbbsystemcheck.title = BigBlueButton Client Check
|
||||
bbbsystemcheck.title = Mconf-Live Client Check
|
||||
bbbsystemcheck.refresh = Refresh
|
||||
bbbsystemcheck.mail = Mail
|
||||
bbbsystemcheck.version = Client Check Version
|
||||
@ -29,3 +29,12 @@ bbbsystemcheck.test.name.userAgent = User Agent
|
||||
bbbsystemcheck.test.name.webRTCEcho = WebRTC Echo Test
|
||||
bbbsystemcheck.test.name.webRTCSocket = WebRTC Socket Test
|
||||
bbbsystemcheck.test.name.webRTCSupported = WebRTC Supported
|
||||
bbbsystemcheck.test.name.port9123 = Port 9123
|
||||
bbbsystemcheck.test.name.rtmpBigBlueButton = RTMP main app
|
||||
bbbsystemcheck.test.name.rtmpDeskshare = RTMP deskshare app
|
||||
bbbsystemcheck.test.name.rtmpSip = RTMP sip app
|
||||
bbbsystemcheck.test.name.rtmpVideo = RTMP video app
|
||||
bbbsystemcheck.test.name.rtmptBigBlueButton = RTMPT main app
|
||||
bbbsystemcheck.test.name.rtmptDeskshare = RTMPT deskshare app
|
||||
bbbsystemcheck.test.name.rtmptSip = RTMPT sip app
|
||||
bbbsystemcheck.test.name.rtmptVideo = RTMPT video app
|
||||
|
40
bbb-client-check/locale/pt_BR/resources.properties
Normal file
@ -0,0 +1,40 @@
|
||||
bbbsystemcheck.title = Diagnóstico do cliente Mconf-Live
|
||||
bbbsystemcheck.refresh = Recarregar
|
||||
bbbsystemcheck.mail = E-mail
|
||||
bbbsystemcheck.version = Versão deste verificador
|
||||
bbbsystemcheck.dataGridColumn.item = Item
|
||||
bbbsystemcheck.dataGridColumn.status = Status
|
||||
bbbsystemcheck.dataGridColumn.result = Resultado
|
||||
bbbsystemcheck.copyAllText = Copiar resultados
|
||||
bbbsystemcheck.result.undefined = Indefinido
|
||||
bbbsystemcheck.result.javaEnabled.disabled = O Java está desabilitado em seu navegador
|
||||
bbbsystemcheck.result.javaEnabled.notDetected = Java não detectado
|
||||
bbbsystemcheck.result.browser.changeBrowser = Recomendamos o uso de Firefox ou Chrome para uma melhor qualidade de áudio
|
||||
bbbsystemcheck.result.browser.browserOutOfDate = Seu navegador está desatualizado. Recomendamos que você o atualize para uma versão mais nova.
|
||||
bbbsystemcheck.status.succeeded = Sucesso
|
||||
bbbsystemcheck.status.warning = Atenção
|
||||
bbbsystemcheck.status.failed = Falha
|
||||
bbbsystemcheck.status.loading = Carregando...
|
||||
bbbsystemcheck.test.name.browser = Navegador
|
||||
bbbsystemcheck.test.name.cookieEnabled = Cookies habilitados
|
||||
bbbsystemcheck.test.name.downloadSpeed = Velocidade de download
|
||||
bbbsystemcheck.test.name.flashVersion = Versão do Adobe Flash Player
|
||||
bbbsystemcheck.test.name.pepperFlash = Pepper Flash
|
||||
bbbsystemcheck.test.name.javaEnabled = Java habilitado
|
||||
bbbsystemcheck.test.name.language = Idioma
|
||||
bbbsystemcheck.test.name.ping = Ping
|
||||
bbbsystemcheck.test.name.screenSize = Tamanho da tela
|
||||
bbbsystemcheck.test.name.uploadSpeed = Velocidade de upload
|
||||
bbbsystemcheck.test.name.userAgent = User Agent
|
||||
bbbsystemcheck.test.name.webRTCEcho = Eco WebRTC
|
||||
bbbsystemcheck.test.name.webRTCSocket = Socket WebRTC
|
||||
bbbsystemcheck.test.name.webRTCSupported = Suporte a WebRTC
|
||||
bbbsystemcheck.test.name.port9123 = Porta 9123
|
||||
bbbsystemcheck.test.name.rtmpBigBlueButton = RTMP main app
|
||||
bbbsystemcheck.test.name.rtmpDeskshare = RTMP deskshare app
|
||||
bbbsystemcheck.test.name.rtmpSip = RTMP sip app
|
||||
bbbsystemcheck.test.name.rtmpVideo = RTMP video app
|
||||
bbbsystemcheck.test.name.rtmptBigBlueButton = RTMPT main app
|
||||
bbbsystemcheck.test.name.rtmptDeskshare = RTMPT deskshare app
|
||||
bbbsystemcheck.test.name.rtmptSip = RTMPT sip app
|
||||
bbbsystemcheck.test.name.rtmptVideo = RTMPT video app
|
@ -7,41 +7,41 @@
|
||||
<downloadFilePath url="test_image.jpg"/>
|
||||
<ports>
|
||||
<port>
|
||||
<name>Port 9123</name>
|
||||
<name>bbbsystemcheck.test.name.port9123</name>
|
||||
<number>9123</number>
|
||||
</port>
|
||||
</ports>
|
||||
<rtmpapps>
|
||||
<app>
|
||||
<name>RTMP BigBlueButton app</name>
|
||||
<name>bbbsystemcheck.test.name.rtmpBigBlueButton</name>
|
||||
<uri>rtmp://HOST/bigbluebutton</uri>
|
||||
</app>
|
||||
<app>
|
||||
<name>RTMP deskShare app</name>
|
||||
<name>bbbsystemcheck.test.name.rtmpDeskshare</name>
|
||||
<uri>rtmp://HOST/deskShare</uri>
|
||||
</app>
|
||||
<app>
|
||||
<name>RTMP video app</name>
|
||||
<name>bbbsystemcheck.test.name.rtmpVideo</name>
|
||||
<uri>rtmp://HOST/video</uri>
|
||||
</app>
|
||||
<app>
|
||||
<name>RTMP sip app</name>
|
||||
<name>bbbsystemcheck.test.name.rtmpSip</name>
|
||||
<uri>rtmp://HOST/sip</uri>
|
||||
</app>
|
||||
<app>
|
||||
<name>RTMPT BigBlueButton app</name>
|
||||
<name>bbbsystemcheck.test.name.rtmptBigBlueButton</name>
|
||||
<uri>rtmpt://HOST/bigbluebutton</uri>
|
||||
</app>
|
||||
<app>
|
||||
<name>RTMPT deskShare app</name>
|
||||
<name>bbbsystemcheck.test.name.rtmptDeskshare</name>
|
||||
<uri>rtmpt://HOST/deskShare</uri>
|
||||
</app>
|
||||
<app>
|
||||
<name>RTMPT video app</name>
|
||||
<name>bbbsystemcheck.test.name.rtmptVideo</name>
|
||||
<uri>rtmpt://HOST/video</uri>
|
||||
</app>
|
||||
<app>
|
||||
<name>RTMPT sip app</name>
|
||||
<name>bbbsystemcheck.test.name.rtmptSip</name>
|
||||
<uri>rtmpt://HOST/sip</uri>
|
||||
</app>
|
||||
</rtmpapps>
|
||||
|
@ -208,12 +208,9 @@
|
||||
var swfObj = getSwfObj();
|
||||
swfObj.webRTCEchoTest(success, errorcode);
|
||||
|
||||
webrtc_hangup(function() {
|
||||
console.log("[BBBClientCheck] Handling webRTC hangup callback");
|
||||
var userAgentTemp = userAgent;
|
||||
userAgent = null;
|
||||
userAgentTemp.stop();
|
||||
});
|
||||
if (callActive === true) {
|
||||
leaveWebRTCVoiceConference();
|
||||
}
|
||||
}
|
||||
|
||||
BBB.getMyUserInfo = function(callback) {
|
||||
@ -224,7 +221,7 @@
|
||||
myRole: "undefined",
|
||||
amIPresenter: "undefined",
|
||||
dialNumber: "undefined",
|
||||
voiceBridge: "undefined",
|
||||
voiceBridge: "00000",
|
||||
customdata: "undefined"
|
||||
}
|
||||
|
||||
@ -232,67 +229,42 @@
|
||||
}
|
||||
|
||||
// webrtc test callbacks
|
||||
BBB.webRTCEchoTestFailed = function(errorcode) {
|
||||
console.log("[BBBClientCheck] Handling webRTCEchoTestFailed");
|
||||
sendWebRTCEchoTestAnswer(false, errorcode);
|
||||
}
|
||||
|
||||
BBB.webRTCEchoTestEnded = function() {
|
||||
console.log("[BBBClientCheck] Handling webRTCEchoTestEnded");
|
||||
}
|
||||
|
||||
BBB.webRTCEchoTestStarted = function() {
|
||||
console.log("[BBBClientCheck] Handling webRTCEchoTestStarted");
|
||||
sendWebRTCEchoTestAnswer(true, 'Connected');
|
||||
}
|
||||
|
||||
BBB.webRTCEchoTestConnecting = function() {
|
||||
console.log("[BBBClientCheck] Handling webRTCEchoTestConnecting");
|
||||
}
|
||||
|
||||
BBB.webRTCEchoTestWaitingForICE = function() {
|
||||
console.log("[BBBClientCheck] Handling webRTCEchoTestWaitingForICE");
|
||||
}
|
||||
|
||||
BBB.webRTCEchoTestWebsocketSucceeded = function() {
|
||||
console.log("[BBBClientCheck] Handling webRTCEchoTestWebsocketSucceeded");
|
||||
BBB.webRTCCallSucceeded = function() {
|
||||
console.log("[BBBClientCheck] Handling webRTCCallSucceeded");
|
||||
var swfObj = getSwfObj();
|
||||
swfObj.webRTCSocketTest(true, 'Connected');
|
||||
}
|
||||
|
||||
BBB.webRTCEchoTestWebsocketFailed = function(errorcode) {
|
||||
console.log("[BBBClientCheck] Handling webRTCEchoTestWebsocketFailed");
|
||||
BBB.webRTCCallFailed = function(inEchoTest, errorcode, cause) {
|
||||
console.log("[BBBClientCheck] Handling webRTCCallFailed, errorcode " + errorcode + ", cause: " + cause);
|
||||
if (errorcode == 1002) {
|
||||
// failed to connect the websocket
|
||||
var swfObj = getSwfObj();
|
||||
swfObj.webRTCSocketTest(false, errorcode);
|
||||
} else {
|
||||
sendWebRTCEchoTestAnswer(false, errorcode);
|
||||
}
|
||||
}
|
||||
|
||||
// webrtc callbacks
|
||||
BBB.webRTCConferenceCallFailed = function(errorcode) {
|
||||
console.log("[BBBClientCheck] Handling webRTCConferenceCallFailed");
|
||||
BBB.webRTCCallEnded = function(inEchoTest) {
|
||||
console.log("[BBBClientCheck] Handling webRTCCallEnded");
|
||||
}
|
||||
|
||||
BBB.webRTCConferenceCallEnded = function() {
|
||||
console.log("[BBBClientCheck] Handling webRTCConferenceCallEnded");
|
||||
BBB.webRTCCallStarted = function(inEchoTest) {
|
||||
console.log("[BBBClientCheck] Handling webRTCCallStarted");
|
||||
sendWebRTCEchoTestAnswer(true, 'Connected');
|
||||
}
|
||||
|
||||
BBB.webRTCConferenceCallStarted = function() {
|
||||
console.log("[BBBClientCheck] Handling webRTCConferenceCallStarted");
|
||||
BBB.webRTCCallConnecting = function(inEchoTest) {
|
||||
console.log("[BBBClientCheck] Handling webRTCCallConnecting");
|
||||
}
|
||||
|
||||
BBB.webRTCConferenceCallConnecting = function() {
|
||||
console.log("[BBBClientCheck] Handling webRTCConferenceCallConnecting");
|
||||
BBB.webRTCCallWaitingForICE = function(inEchoTest) {
|
||||
console.log("[BBBClientCheck] Handling webRTCCallWaitingForICE");
|
||||
}
|
||||
|
||||
BBB.webRTCConferenceCallWaitingForICE = function() {
|
||||
console.log("[BBBClientCheck] Handling webRTCConferenceCallWaitingForICE");
|
||||
}
|
||||
|
||||
BBB.webRTCConferenceCallWebsocketSucceeded = function() {
|
||||
console.log("[BBBClientCheck] Handling webRTCConferenceCallWebsocketSucceeded");
|
||||
}
|
||||
|
||||
BBB.webRTCConferenceCallWebsocketFailed = function(errorcode) {
|
||||
console.log("[BBBClientCheck] Handling webRTCConferenceCallWebsocketFailed");
|
||||
BBB.webRTCCallTransferring = function(inEchoTest) {
|
||||
console.log("[BBBClientCheck] Handling webRTCCallTransferring");
|
||||
}
|
||||
|
||||
BBB.webRTCMediaRequest = function() {
|
||||
|
@ -18,6 +18,8 @@
|
||||
<![CDATA[
|
||||
import mx.events.FlexEvent;
|
||||
|
||||
import flash.external.ExternalInterface;
|
||||
|
||||
import org.bigbluebutton.clientcheck.AppConfig;
|
||||
import org.bigbluebutton.clientcheck.view.mainview.MainViewConfig;
|
||||
import org.bigbluebutton.clientcheck.view.mainview.RefreshButtonConfig;
|
||||
@ -31,12 +33,25 @@
|
||||
|
||||
private static var robotlegsContext:IContext;
|
||||
|
||||
private static var DEFAULT_LOCALE:String = "en_US";
|
||||
private var language:String;
|
||||
|
||||
protected function preinitializeHandler(event:FlexEvent):void
|
||||
{
|
||||
setLanguage();
|
||||
setupRobotlegsContext();
|
||||
Security.allowDomain("*");
|
||||
}
|
||||
|
||||
private function setLanguage():void
|
||||
{
|
||||
language = ExternalInterface.call("getLanguage");
|
||||
if (resourceManager.getLocales().indexOf(language) != -1)
|
||||
{
|
||||
resourceManager.localeChain = [language, DEFAULT_LOCALE];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup robotlegs initial configuration
|
||||
*/
|
||||
|
@ -23,6 +23,7 @@ package org.bigbluebutton.clientcheck.command
|
||||
import flash.utils.getTimer;
|
||||
|
||||
import mx.core.FlexGlobals;
|
||||
import mx.resources.ResourceManager;
|
||||
import mx.utils.URLUtil;
|
||||
|
||||
import org.bigbluebutton.clientcheck.model.ISystemConfiguration;
|
||||
@ -78,7 +79,8 @@ package org.bigbluebutton.clientcheck.command
|
||||
for each (var _port:Object in config.getPorts())
|
||||
{
|
||||
var port:IPortTest=new PortTest();
|
||||
port.portName=_port.name;
|
||||
port.portName=ResourceManager.getInstance().getString('resources', _port.name);
|
||||
if (port.portName == "undefined") port.portName = _port.name;
|
||||
port.portNumber=_port.number;
|
||||
systemConfiguration.ports.push(port);
|
||||
}
|
||||
@ -86,7 +88,8 @@ package org.bigbluebutton.clientcheck.command
|
||||
for each (var _rtmpApp:Object in config.getRTMPApps())
|
||||
{
|
||||
var app:IRTMPAppTest=new RTMPAppTest();
|
||||
app.applicationName=_rtmpApp.name;
|
||||
app.applicationName=ResourceManager.getInstance().getString('resources', _rtmpApp.name);
|
||||
if (app.applicationName == "undefined") app.applicationName = _rtmpApp.name;
|
||||
app.applicationUri=_rtmpApp.uri;
|
||||
systemConfiguration.rtmpApps.push(app);
|
||||
}
|
||||
|
@ -22,6 +22,9 @@
|
||||
|
||||
switch (value.StatusPriority) {
|
||||
case SUCCEEDED:
|
||||
colorRect.visible = true;
|
||||
solid.color = 0x00FF00;
|
||||
break;
|
||||
case LOADING:
|
||||
colorRect.visible = false;
|
||||
break;
|
||||
|
@ -25,6 +25,6 @@ package org.bigbluebutton.clientcheck.view.mainview
|
||||
public static const FAILED:Object = {StatusMessage: ResourceManager.getInstance().getString('resources', 'bbbsystemcheck.status.failed'), StatusPriority: 1};
|
||||
public static const WARNING:Object = {StatusMessage: ResourceManager.getInstance().getString('resources', 'bbbsystemcheck.status.warning'), StatusPriority: 2};
|
||||
public static const LOADING:Object = {StatusMessage: ResourceManager.getInstance().getString('resources', 'bbbsystemcheck.status.loading'), StatusPriority: 3};
|
||||
public static const SUCCEED:Object = {StatusMessage: "", StatusPriority: 4};
|
||||
public static const SUCCEED:Object = {StatusMessage: ResourceManager.getInstance().getString('resources', 'bbbsystemcheck.status.succeeded'), StatusPriority: 4};
|
||||
}
|
||||
}
|
||||
|
@ -1,27 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" path="src/main/java"/>
|
||||
<classpathentry kind="src" path="src/test/java"/>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
|
||||
<classpathentry kind="lib" path="lib/com.springsource.slf4j.api-1.6.1.jar"/>
|
||||
<classpathentry kind="lib" path="lib/com.springsource.slf4j.bridge-1.6.1.jar"/>
|
||||
<classpathentry kind="lib" path="lib/commons-pool-1.5.6.jar"/>
|
||||
<classpathentry kind="lib" path="lib/easymock-2.4.jar"/>
|
||||
<classpathentry kind="lib" path="lib/jcl-over-slf4j-1.6.1.jar"/>
|
||||
<classpathentry kind="lib" path="lib/jedis-2.0.0.jar"/>
|
||||
<classpathentry kind="lib" path="lib/jul-to-slf4j-1.6.1.jar"/>
|
||||
<classpathentry kind="lib" path="lib/log4j-over-slf4j-1.6.1.jar"/>
|
||||
<classpathentry kind="lib" path="lib/logback-classic-0.9.28.jar"/>
|
||||
<classpathentry kind="lib" path="lib/logback-core-0.9.28.jar"/>
|
||||
<classpathentry kind="lib" path="lib/mina-core-2.0.4.jar"/>
|
||||
<classpathentry kind="lib" path="lib/mina-integration-beans-2.0.4.jar"/>
|
||||
<classpathentry kind="lib" path="lib/mina-integration-jmx-2.0.4.jar"/>
|
||||
<classpathentry kind="lib" path="lib/servlet-api-2.5.jar"/>
|
||||
<classpathentry kind="lib" path="lib/spring-beans-3.0.6.RELEASE.jar"/>
|
||||
<classpathentry kind="lib" path="lib/spring-context-3.0.6.RELEASE.jar"/>
|
||||
<classpathentry kind="lib" path="lib/spring-core-3.0.6.RELEASE.jar"/>
|
||||
<classpathentry kind="lib" path="lib/spring-web-3.0.6.RELEASE.jar"/>
|
||||
<classpathentry kind="lib" path="lib/testng-5.8.jar"/>
|
||||
<classpathentry kind="lib" path="lib/red5-1.0r4406.jar"/>
|
||||
<classpathentry kind="output" path="bin"/>
|
||||
</classpath>
|
4
bbb-video/.gitignore
vendored
@ -2,4 +2,6 @@ bin
|
||||
build
|
||||
dist
|
||||
lib
|
||||
build
|
||||
.classpath
|
||||
.project
|
||||
.settings
|
||||
|
@ -1,17 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>bbb-video</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
@ -65,6 +65,7 @@ dependencies {
|
||||
providedCompile 'org/red5:red5-server:1.0.5-RELEASE@jar'
|
||||
providedCompile 'org.red5:red5-server-common:1.0.5-RELEASE@jar'
|
||||
providedCompile 'org.red5:red5-io:1.0.5-RELEASE@jar'
|
||||
providedCompile 'org.red5:red5-client:1.0.5-RELEASE@jar'
|
||||
|
||||
// Logging
|
||||
providedCompile 'ch.qos.logback:logback-core:1.1.2@jar'
|
||||
@ -90,6 +91,9 @@ dependencies {
|
||||
compile 'redis.clients:jedis:2.0.0'
|
||||
providedCompile 'commons-pool:commons-pool:1.5.6'
|
||||
compile 'com.google.code.gson:gson:1.7.1'
|
||||
|
||||
// Needed for StringUtils
|
||||
providedCompile 'org.apache.commons:commons-lang3:3.1@jar'
|
||||
}
|
||||
|
||||
test {
|
||||
|
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* RED5 Open Source Flash Server - http://code.google.com/p/red5/
|
||||
*
|
||||
* Copyright 2006-2012 by respective authors (see below). All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.bigbluebutton.app.video;
|
||||
|
||||
import org.red5.client.net.rtmp.RTMPClient;
|
||||
import org.red5.server.service.PendingCall;
|
||||
import org.red5.server.net.rtmp.event.Ping;
|
||||
import org.red5.logging.Red5LoggerFactory;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
/**
|
||||
* Custom RTMP Client
|
||||
*
|
||||
* This client behaves like the flash player plugin when requesting to play a stream
|
||||
*
|
||||
* @author Mateus Dalepiane (mdalepiane@gmail.com)
|
||||
*/
|
||||
|
||||
public class CustomRTMPClient extends RTMPClient {
|
||||
private static Logger log = Red5LoggerFactory.getLogger(CustomRTMPClient.class);
|
||||
|
||||
public void play(int streamId, String name) {
|
||||
log.info("play stream "+ streamId + ", name: " + name);
|
||||
if (conn != null) {
|
||||
// get the channel
|
||||
int channel = getChannelForStreamId(streamId);
|
||||
// send our requested buffer size
|
||||
ping(Ping.CLIENT_BUFFER, streamId, 2000);
|
||||
// send our request for a/v
|
||||
PendingCall receiveAudioCall = new PendingCall("receiveAudio");
|
||||
conn.invoke(receiveAudioCall, channel);
|
||||
PendingCall receiveVideoCall = new PendingCall("receiveVideo");
|
||||
conn.invoke(receiveVideoCall, channel);
|
||||
// call play
|
||||
Object[] params = new Object[1];
|
||||
params[0] = name;
|
||||
PendingCall pendingCall = new PendingCall("play", params);
|
||||
conn.invoke(pendingCall, channel);
|
||||
} else {
|
||||
log.warn("Trying to play on a null connection");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,347 @@
|
||||
/*
|
||||
* RED5 Open Source Flash Server - http://code.google.com/p/red5/
|
||||
*
|
||||
* Copyright 2006-2012 by respective authors (see below). All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.bigbluebutton.app.video;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
import org.red5.client.net.rtmp.ClientExceptionHandler;
|
||||
import org.red5.client.net.rtmp.INetStreamEventHandler;
|
||||
import org.red5.client.net.rtmp.RTMPClient;
|
||||
import org.red5.io.utils.ObjectMap;
|
||||
import org.red5.proxy.StreamingProxy;
|
||||
import org.red5.server.api.event.IEvent;
|
||||
import org.red5.server.api.event.IEventDispatcher;
|
||||
import org.red5.server.api.service.IPendingServiceCall;
|
||||
import org.red5.server.api.service.IPendingServiceCallback;
|
||||
import org.red5.server.net.rtmp.event.IRTMPEvent;
|
||||
import org.red5.server.net.rtmp.event.Notify;
|
||||
import org.red5.server.net.rtmp.status.StatusCodes;
|
||||
import org.red5.server.stream.message.RTMPMessage;
|
||||
import org.red5.logging.Red5LoggerFactory;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
|
||||
/**
|
||||
* Relay a stream from one location to another via RTMP.
|
||||
*
|
||||
* @author Paul Gregoire (mondain@gmail.com)
|
||||
*/
|
||||
|
||||
|
||||
public class CustomStreamRelay {
|
||||
private static Logger log = Red5LoggerFactory.getLogger(CustomStreamRelay.class);
|
||||
|
||||
// our consumer
|
||||
private CustomRTMPClient client;
|
||||
|
||||
// our publisher
|
||||
private StreamingProxy proxy;
|
||||
|
||||
// task timer
|
||||
private Timer timer;
|
||||
|
||||
|
||||
private String sourceHost;
|
||||
private String destHost;
|
||||
private String sourceApp;
|
||||
private String destApp;
|
||||
private int sourcePort;
|
||||
private int destPort;
|
||||
private String sourceStreamName;
|
||||
private String destStreamName;
|
||||
private String publishMode;
|
||||
Map<String, Object> defParams;
|
||||
|
||||
private boolean isDisconnecting;
|
||||
|
||||
/**
|
||||
* Creates a stream client to consume a stream from an end point and a proxy to relay the stream
|
||||
* to another end point.
|
||||
*
|
||||
* @param args application arguments
|
||||
*/
|
||||
|
||||
|
||||
public void setSourceHost(String sourceHost) {
|
||||
this.sourceHost = sourceHost;
|
||||
}
|
||||
|
||||
public void setSourcePort(int sourcePort) {
|
||||
this.sourcePort = sourcePort;
|
||||
}
|
||||
|
||||
public void setDestinationHost(String destHost) {
|
||||
this.destHost = destHost;
|
||||
}
|
||||
|
||||
public void setDestinationPort(int destPort) {
|
||||
this.destPort = destPort;
|
||||
}
|
||||
|
||||
public void setSourceApp(String sourceApp) {
|
||||
this.sourceApp = sourceApp;
|
||||
}
|
||||
|
||||
public void setDestinationApp(String destApp) {
|
||||
this.destApp = destApp;
|
||||
}
|
||||
|
||||
public void setSourceStreamName(String sourceStreamName) {
|
||||
this.sourceStreamName = sourceStreamName;
|
||||
}
|
||||
|
||||
public void setDestinationStreamName(String destStreamName) {
|
||||
this.destStreamName = destStreamName;
|
||||
}
|
||||
|
||||
public void setPublishMode(String publishMode) {
|
||||
this.publishMode = publishMode;
|
||||
}
|
||||
|
||||
public void initRelay(String... args) {
|
||||
if (args == null || args.length < 7) {
|
||||
log.error("Not enough args supplied. Usage: <source uri> <source app> <source stream name> <destination uri> <destination app> <destination stream name> <publish mode>");
|
||||
}
|
||||
else {
|
||||
sourceHost = args[0];
|
||||
destHost = args[3];
|
||||
sourceApp = args[1];
|
||||
destApp = args[4];
|
||||
sourcePort = 1935;
|
||||
destPort = 1935;
|
||||
sourceStreamName = args[2];
|
||||
destStreamName = args[5];
|
||||
publishMode = args[6]; //live, record, or append
|
||||
|
||||
int colonIdx = sourceHost.indexOf(':');
|
||||
if (colonIdx > 0) {
|
||||
sourcePort = Integer.valueOf(sourceHost.substring(colonIdx + 1));
|
||||
sourceHost = sourceHost.substring(0, colonIdx);
|
||||
log.trace("Source host: %s port: %d\n", sourceHost, sourcePort);
|
||||
}
|
||||
colonIdx = destHost.indexOf(':');
|
||||
if (colonIdx > 0) {
|
||||
destPort = Integer.valueOf(destHost.substring(colonIdx + 1));
|
||||
destHost = destHost.substring(0, colonIdx);
|
||||
log.trace("Destination host: %s port: %d\n", destHost, destPort);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public void stopRelay() {
|
||||
isDisconnecting = true;
|
||||
client.disconnect();
|
||||
proxy.stop();
|
||||
}
|
||||
|
||||
public void startRelay() {
|
||||
|
||||
isDisconnecting = false;
|
||||
// create a timer
|
||||
timer = new Timer();
|
||||
// create our publisher
|
||||
proxy = new StreamingProxy();
|
||||
proxy.setHost(destHost);
|
||||
proxy.setPort(destPort);
|
||||
proxy.setApp(destApp);
|
||||
proxy.init();
|
||||
proxy.setConnectionClosedHandler(new Runnable() {
|
||||
public void run() {
|
||||
log.info("Publish connection has been closed, source will be disconnected");
|
||||
client.disconnect();
|
||||
}
|
||||
});
|
||||
proxy.setExceptionHandler(new ClientExceptionHandler() {
|
||||
@Override
|
||||
public void handleException(Throwable throwable) {
|
||||
throwable.printStackTrace();
|
||||
proxy.stop();
|
||||
}
|
||||
});
|
||||
proxy.start(destStreamName, publishMode, new Object[] {});
|
||||
// wait for the publish state
|
||||
|
||||
// Change to use signal or something more cleaner
|
||||
|
||||
do {
|
||||
try {
|
||||
Thread.sleep(100L);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} while (!proxy.isPublished());
|
||||
log.info("Publishing...");
|
||||
|
||||
// create the consumer
|
||||
client = new CustomRTMPClient();
|
||||
client.setStreamEventDispatcher(new StreamEventDispatcher());
|
||||
client.setStreamEventHandler(new INetStreamEventHandler() {
|
||||
public void onStreamEvent(Notify notify) {
|
||||
ObjectMap<?, ?> map = (ObjectMap<?, ?>) notify.getCall().getArguments()[0];
|
||||
String code = (String) map.get("code");
|
||||
if (StatusCodes.NS_PLAY_STREAMNOTFOUND.equals(code)) {
|
||||
log.info("Requested stream was not found");
|
||||
isDisconnecting = true;
|
||||
client.disconnect();
|
||||
} else if (StatusCodes.NS_PLAY_UNPUBLISHNOTIFY.equals(code) || StatusCodes.NS_PLAY_COMPLETE.equals(code)) {
|
||||
log.info("Source has stopped publishing or play is complete");
|
||||
isDisconnecting = true;
|
||||
client.disconnect();
|
||||
}
|
||||
}
|
||||
});
|
||||
client.setConnectionClosedHandler(new Runnable() {
|
||||
public void run() {
|
||||
log.info("Source connection has been closed");
|
||||
//System.exit(2);
|
||||
if(isDisconnecting) {
|
||||
log.info("Proxy will be stopped");
|
||||
client.disconnect();
|
||||
proxy.stop();
|
||||
} else {
|
||||
log.info("Reconnecting client...");
|
||||
client.connect(sourceHost, sourcePort, defParams, new ClientConnectCallback());
|
||||
}
|
||||
}
|
||||
});
|
||||
client.setExceptionHandler(new ClientExceptionHandler() {
|
||||
@Override
|
||||
public void handleException(Throwable throwable) {
|
||||
throwable.printStackTrace();
|
||||
//System.exit(1);
|
||||
client.disconnect();
|
||||
proxy.stop();
|
||||
}
|
||||
});
|
||||
// connect the consumer
|
||||
defParams = client.makeDefaultConnectionParams(sourceHost, sourcePort, sourceApp);
|
||||
// add pageurl and swfurl
|
||||
defParams.put("pageUrl", "");
|
||||
defParams.put("swfUrl", "app:/Red5-StreamRelay.swf");
|
||||
// indicate for the handshake to generate swf verification data
|
||||
client.setSwfVerification(true);
|
||||
// connect the client
|
||||
log.trace("startRelay:: ProxyRelay status is running: " + proxy.isRunning());
|
||||
client.connect(sourceHost, sourcePort, defParams, new ClientConnectCallback());
|
||||
}
|
||||
|
||||
private final class ClientConnectCallback implements IPendingServiceCallback{
|
||||
public void resultReceived(IPendingServiceCall call) {
|
||||
log.trace("connectCallback");
|
||||
ObjectMap<?, ?> map = (ObjectMap<?, ?>) call.getResult();
|
||||
String code = (String) map.get("code");
|
||||
if ("NetConnection.Connect.Rejected".equals(code)) {
|
||||
log.warn("Rejected: %s\n", map.get("description"));
|
||||
client.disconnect();
|
||||
proxy.stop();
|
||||
} else if ("NetConnection.Connect.Success".equals(code)) {
|
||||
// 1. Wait for onBWDone
|
||||
timer.schedule(new BandwidthStatusTask(), 2000L);
|
||||
} else {
|
||||
log.warn("Unhandled response code: %s\n", code);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches consumer events.
|
||||
*/
|
||||
private final class StreamEventDispatcher implements IEventDispatcher {
|
||||
|
||||
public void dispatchEvent(IEvent event) {
|
||||
try {
|
||||
proxy.pushMessage(null, RTMPMessage.build((IRTMPEvent) event));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles result from subscribe call.
|
||||
*/
|
||||
private final class SubscribeStreamCallBack implements IPendingServiceCallback {
|
||||
|
||||
public void resultReceived(IPendingServiceCall call) {
|
||||
log.trace("SubscirbeStreamCallBack::resultReceived: " + call);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a "stream" via playback, this is the source stream.
|
||||
*/
|
||||
private final class CreateStreamCallback implements IPendingServiceCallback {
|
||||
|
||||
public void resultReceived(IPendingServiceCall call) {
|
||||
log.trace("CreateStreamCallBack::resultReceived: " + call);
|
||||
int streamId = (Integer) call.getResult();
|
||||
log.trace("stream id: " + streamId);
|
||||
// send our buffer size request
|
||||
if (sourceStreamName.endsWith(".flv") || sourceStreamName.endsWith(".f4v") || sourceStreamName.endsWith(".mp4")) {
|
||||
log.trace("play stream name " + sourceStreamName + " start 0 lenght -1");
|
||||
client.play(streamId, sourceStreamName, 0, -1);
|
||||
} else {
|
||||
log.trace("play stream name " + sourceStreamName);
|
||||
client.play(streamId, sourceStreamName);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Continues to check for onBWDone
|
||||
*/
|
||||
private final class BandwidthStatusTask extends TimerTask {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
// check for onBWDone
|
||||
log.info("Bandwidth check done: " + client.isBandwidthCheckDone());
|
||||
// cancel this task
|
||||
this.cancel();
|
||||
// create a task to wait for subscribed
|
||||
timer.schedule(new PlayStatusTask(), 1000L);
|
||||
// 2. send FCSubscribe
|
||||
client.subscribe(new SubscribeStreamCallBack(), new Object[] { sourceStreamName });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private final class PlayStatusTask extends TimerTask {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
// checking subscribed
|
||||
log.info("Subscribed: " + client.isSubscribed());
|
||||
// cancel this task
|
||||
this.cancel();
|
||||
// 3. create stream
|
||||
client.createStream(new CreateStreamCallback());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -21,14 +21,26 @@ package org.bigbluebutton.app.video;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.bigbluebutton.app.video.converter.H263Converter;
|
||||
import org.bigbluebutton.app.video.converter.VideoRotator;
|
||||
import org.red5.logging.Red5LoggerFactory;
|
||||
import org.red5.server.adapter.MultiThreadedApplicationAdapter;
|
||||
import org.red5.server.api.IConnection;
|
||||
import org.red5.server.api.Red5;
|
||||
import org.red5.server.api.scope.IBasicScope;
|
||||
import org.red5.server.api.scope.IBroadcastScope;
|
||||
import org.red5.server.api.scope.IScope;
|
||||
import org.red5.server.api.scope.ScopeType;
|
||||
import org.red5.server.api.stream.IBroadcastStream;
|
||||
import org.red5.server.api.stream.IPlayItem;
|
||||
import org.red5.server.api.stream.IServerStream;
|
||||
import org.red5.server.api.stream.IStreamListener;
|
||||
import org.red5.server.api.stream.ISubscriberStream;
|
||||
import org.red5.server.stream.ClientBroadcastStream;
|
||||
import org.slf4j.Logger;
|
||||
import com.google.gson.Gson;
|
||||
@ -43,12 +55,25 @@ public class VideoApplication extends MultiThreadedApplicationAdapter {
|
||||
private EventRecordingService recordingService;
|
||||
private final Map<String, IStreamListener> streamListeners = new HashMap<String, IStreamListener>();
|
||||
|
||||
private Map<String, CustomStreamRelay> remoteStreams = new ConcurrentHashMap<String, CustomStreamRelay>();
|
||||
private Map<String, Integer> listenersOnRemoteStream = new ConcurrentHashMap<String, Integer>();
|
||||
|
||||
// Proxy disconnection timer
|
||||
private Timer timer;
|
||||
// Proxy disconnection timeout
|
||||
private long relayTimeout;
|
||||
|
||||
private final Map<String, H263Converter> h263Converters = new HashMap<String, H263Converter>();
|
||||
|
||||
private final Map<String, VideoRotator> videoRotators = new HashMap<String, VideoRotator>();
|
||||
|
||||
@Override
|
||||
public boolean appStart(IScope app) {
|
||||
super.appStart(app);
|
||||
log.info("BBB Video appStart");
|
||||
System.out.println("BBB Video appStart");
|
||||
appScope = app;
|
||||
timer = new Timer();
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -61,6 +86,13 @@ public class VideoApplication extends MultiThreadedApplicationAdapter {
|
||||
@Override
|
||||
public boolean roomConnect(IConnection conn, Object[] params) {
|
||||
log.info("BBB Video roomConnect");
|
||||
|
||||
if(params.length == 0) {
|
||||
params = new Object[2];
|
||||
params[0] = "UNKNOWN-MEETING-ID";
|
||||
params[1] = "UNKNOWN-USER-ID";
|
||||
}
|
||||
|
||||
String meetingId = ((String) params[0]).toString();
|
||||
String userId = ((String) params[1]).toString();
|
||||
|
||||
@ -162,18 +194,34 @@ public class VideoApplication extends MultiThreadedApplicationAdapter {
|
||||
super.streamPublishStart(stream);
|
||||
}
|
||||
|
||||
public IBroadcastScope getBroadcastScope(IScope scope, String name) {
|
||||
IBasicScope basicScope = scope.getBasicScope(ScopeType.BROADCAST, name);
|
||||
if (basicScope instanceof IBroadcastScope) {
|
||||
return (IBroadcastScope) basicScope;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void streamBroadcastStart(IBroadcastStream stream) {
|
||||
IConnection conn = Red5.getConnectionLocal();
|
||||
super.streamBroadcastStart(stream);
|
||||
log.info("streamBroadcastStart " + stream.getPublishedName() + " " + System.currentTimeMillis() + " " + conn.getScope().getName());
|
||||
String streamName = stream.getPublishedName();
|
||||
log.info("streamBroadcastStart " + streamName + " " + System.currentTimeMillis() + " " + conn.getScope().getName());
|
||||
|
||||
if (recordVideoStream) {
|
||||
if (streamName.contains("/")) {
|
||||
if(VideoRotator.getDirection(streamName) != null) {
|
||||
VideoRotator rotator = new VideoRotator(streamName);
|
||||
videoRotators.put(streamName, rotator);
|
||||
}
|
||||
}
|
||||
else if (recordVideoStream) {
|
||||
recordStream(stream);
|
||||
VideoStreamListener listener = new VideoStreamListener();
|
||||
listener.setEventRecordingService(recordingService);
|
||||
stream.addStreamListener(listener);
|
||||
streamListeners.put(conn.getScope().getName() + "-" + stream.getPublishedName(), listener);
|
||||
streamListeners.put(conn.getScope().getName() + "-" + streamName, listener);
|
||||
}
|
||||
}
|
||||
|
||||
@ -181,6 +229,11 @@ public class VideoApplication extends MultiThreadedApplicationAdapter {
|
||||
return TimeUnit.NANOSECONDS.toMillis(System.nanoTime());
|
||||
}
|
||||
|
||||
private boolean isH263Stream(ISubscriberStream stream) {
|
||||
String streamName = stream.getBroadcastStreamPublishName();
|
||||
return streamName.startsWith(H263Converter.H263PREFIX);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void streamBroadcastClose(IBroadcastStream stream) {
|
||||
super.streamBroadcastClose(stream);
|
||||
@ -209,6 +262,17 @@ public class VideoApplication extends MultiThreadedApplicationAdapter {
|
||||
event.put("eventName", "StopWebcamShareEvent");
|
||||
recordingService.record(scopeName, event);
|
||||
}
|
||||
|
||||
String streamName = stream.getName();
|
||||
if(h263Converters.containsKey(streamName)) {
|
||||
// Stop converter
|
||||
h263Converters.remove(streamName).stopConverter();
|
||||
}
|
||||
|
||||
if(videoRotators.containsKey(streamName)) {
|
||||
// Stop rotator
|
||||
videoRotators.remove(streamName).stop();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -238,4 +302,125 @@ public class VideoApplication extends MultiThreadedApplicationAdapter {
|
||||
recordingService = s;
|
||||
}
|
||||
|
||||
public void setRelayTimeout(long timeout) {
|
||||
this.relayTimeout = timeout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void streamPlayItemPlay(ISubscriberStream stream, IPlayItem item, boolean isLive) {
|
||||
// log w3c connect event
|
||||
String streamName = item.getName();
|
||||
streamName = streamName.replaceAll(H263Converter.H263PREFIX, "");
|
||||
|
||||
if(isH263Stream(stream)) {
|
||||
log.trace("Detected H263 stream request [{}]", streamName);
|
||||
|
||||
synchronized (h263Converters) {
|
||||
// Check if a new stream converter is necessary
|
||||
if(!h263Converters.containsKey(streamName)) {
|
||||
H263Converter converter = new H263Converter(streamName);
|
||||
h263Converters.put(streamName, converter);
|
||||
}
|
||||
else {
|
||||
H263Converter converter = h263Converters.get(streamName);
|
||||
converter.addListener();
|
||||
}
|
||||
}
|
||||
}
|
||||
if(streamName.contains("/")) {
|
||||
synchronized(remoteStreams) {
|
||||
if(remoteStreams.containsKey(streamName) == false) {
|
||||
String[] parts = streamName.split("/");
|
||||
String sourceServer = parts[0];
|
||||
String sourceStreamName = StringUtils.join(parts, '/', 1, parts.length);
|
||||
String destinationServer = Red5.getConnectionLocal().getHost();
|
||||
String destinationStreamName = streamName;
|
||||
String app = "video/"+Red5.getConnectionLocal().getScope().getName();
|
||||
log.trace("streamPlayItemPlay:: streamName [" + streamName + "]");
|
||||
log.trace("streamPlayItemPlay:: sourceServer [" + sourceServer + "]");
|
||||
log.trace("streamPlayItemPlay:: sourceStreamName [" + sourceStreamName + "]");
|
||||
log.trace("streamPlayItemPlay:: destinationServer [" + destinationServer + "]");
|
||||
log.trace("streamPlayItemPlay:: destinationStreamName [" + destinationStreamName + "]");
|
||||
log.trace("streamPlayItemPlay:: app [" + app + "]");
|
||||
|
||||
CustomStreamRelay remoteRelay = new CustomStreamRelay();
|
||||
remoteRelay.initRelay(new String[]{sourceServer, app, sourceStreamName, destinationServer, app, destinationStreamName, "live"});
|
||||
remoteRelay.startRelay();
|
||||
remoteStreams.put(destinationStreamName, remoteRelay);
|
||||
listenersOnRemoteStream.put(streamName, 1);
|
||||
}
|
||||
else {
|
||||
Integer numberOfListeners = listenersOnRemoteStream.get(streamName) + 1;
|
||||
listenersOnRemoteStream.put(streamName,numberOfListeners);
|
||||
}
|
||||
}
|
||||
}
|
||||
log.info("W3C x-category:stream x-event:play c-ip:{} x-sname:{} x-name:{}", new Object[] { Red5.getConnectionLocal().getRemoteAddress(), stream.getName(), item.getName() });
|
||||
}
|
||||
|
||||
@Override
|
||||
public void streamSubscriberClose(ISubscriberStream stream) {
|
||||
String streamName = stream.getBroadcastStreamPublishName();
|
||||
streamName = streamName.replaceAll(H263Converter.H263PREFIX, "");
|
||||
|
||||
if(isH263Stream(stream)) {
|
||||
synchronized (h263Converters) {
|
||||
// Remove prefix
|
||||
if(h263Converters.containsKey(streamName)) {
|
||||
H263Converter converter = h263Converters.get(streamName);
|
||||
converter.removeListener();
|
||||
}
|
||||
else {
|
||||
log.warn("Converter not found for H263 stream [{}]", streamName);
|
||||
}
|
||||
}
|
||||
}
|
||||
synchronized(remoteStreams) {
|
||||
super.streamSubscriberClose(stream);
|
||||
log.trace("Subscriber close for stream [{}]", streamName);
|
||||
if(streamName.contains("/")) {
|
||||
if(remoteStreams.containsKey(streamName)) {
|
||||
Integer numberOfListeners = listenersOnRemoteStream.get(streamName);
|
||||
if(numberOfListeners != null) {
|
||||
numberOfListeners = numberOfListeners - 1;
|
||||
listenersOnRemoteStream.put(streamName, numberOfListeners);
|
||||
log.trace("Stream [{}] has {} subscribers left", streamName, numberOfListeners);
|
||||
if(numberOfListeners < 1) {
|
||||
log.info("Starting timeout to close proxy for stream: {}", streamName);
|
||||
timer.schedule(new DisconnectProxyTask(streamName), relayTimeout);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class DisconnectProxyTask extends TimerTask {
|
||||
// Stream name that should be disconnected
|
||||
private String streamName;
|
||||
|
||||
public DisconnectProxyTask(String streamName) {
|
||||
this.streamName = streamName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
// Cancel this task
|
||||
this.cancel();
|
||||
// Check if someone reconnected
|
||||
synchronized(remoteStreams) {
|
||||
Integer numberOfListeners = listenersOnRemoteStream.get(streamName);
|
||||
log.trace("Stream [{}] has {} subscribers", streamName, numberOfListeners);
|
||||
if(numberOfListeners != null) {
|
||||
if(numberOfListeners < 1) {
|
||||
// No one else is connected to this stream, close relay
|
||||
log.info("Stopping relay for stream [{}]", streamName);
|
||||
listenersOnRemoteStream.remove(streamName);
|
||||
CustomStreamRelay remoteRelay = remoteStreams.remove(streamName);
|
||||
remoteRelay.stopRelay();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,107 @@
|
||||
package org.bigbluebutton.app.video.converter;
|
||||
|
||||
import org.bigbluebutton.app.video.ffmpeg.FFmpegCommand;
|
||||
import org.bigbluebutton.app.video.ffmpeg.ProcessMonitor;
|
||||
import org.red5.logging.Red5LoggerFactory;
|
||||
import org.red5.server.api.IConnection;
|
||||
import org.red5.server.api.Red5;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
/**
|
||||
* Represents a stream converter to H263. This class is responsible
|
||||
* for managing the execution of FFmpeg based on the number of listeners
|
||||
* connected to the stream. When the first listener is added FFmpef is
|
||||
* launched, and when the last listener is removed FFmpeg is stopped.
|
||||
* Converted streams are published in the same scope as the original ones,
|
||||
* with 'h263/' appended in the beginning.
|
||||
*/
|
||||
public class H263Converter {
|
||||
|
||||
private static Logger log = Red5LoggerFactory.getLogger(H263Converter.class, "video");
|
||||
|
||||
public final static String H263PREFIX = "h263/";
|
||||
|
||||
private String origin;
|
||||
private Integer numListeners = 0;
|
||||
|
||||
private FFmpegCommand ffmpeg;
|
||||
private ProcessMonitor processMonitor;
|
||||
|
||||
/**
|
||||
* Creates a H263Converter from a given streamName. It is assumed
|
||||
* that one listener is responsible for this creation, therefore
|
||||
* FFmpeg is launched.
|
||||
*
|
||||
* @param origin streamName of the stream that should be converted
|
||||
*/
|
||||
public H263Converter(String origin) {
|
||||
log.info("Spawn FFMpeg to convert H264 to H263 for stream [{}]", origin);
|
||||
this.origin = origin;
|
||||
IConnection conn = Red5.getConnectionLocal();
|
||||
String ip = conn.getHost();
|
||||
String conf = conn.getScope().getName();
|
||||
String inputLive = "rtmp://" + ip + "/video/" + conf + "/" + origin + " live=1";
|
||||
|
||||
String output = "rtmp://" + ip + "/video/" + conf + "/" + H263PREFIX + origin;
|
||||
|
||||
ffmpeg = new FFmpegCommand();
|
||||
ffmpeg.setFFmpegPath("/usr/local/bin/ffmpeg");
|
||||
ffmpeg.setInput(inputLive);
|
||||
ffmpeg.setCodec("flv1"); // Sorensen H263
|
||||
ffmpeg.setFormat("flv");
|
||||
ffmpeg.setOutput(output);
|
||||
ffmpeg.setLoglevel("warning");
|
||||
ffmpeg.setAnalyzeDuration("10000"); // 10ms
|
||||
|
||||
this.addListener();
|
||||
}
|
||||
|
||||
/**
|
||||
* Launches the process monitor responsible for FFmpeg.
|
||||
*/
|
||||
private void startConverter() {
|
||||
String[] command = ffmpeg.getFFmpegCommand(true);
|
||||
processMonitor = new ProcessMonitor(command);
|
||||
processMonitor.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a listener to H263Converter. If there were
|
||||
* zero listeners, FFmpeg is launched for this stream.
|
||||
*/
|
||||
public synchronized void addListener() {
|
||||
this.numListeners++;
|
||||
log.trace("Adding listener to [{}] ; [{}] current listeners ", origin, this.numListeners);
|
||||
|
||||
if(this.numListeners.equals(1)) {
|
||||
log.debug("First listener just joined, must start H263Converter for [{}]", origin);
|
||||
startConverter();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a listener from H263Converter. There are
|
||||
* zero listeners left, FFmpeg is stopped this stream.
|
||||
*/
|
||||
public synchronized void removeListener() {
|
||||
this.numListeners--;
|
||||
log.trace("Removing listener from [{}] ; [{}] current listeners ", origin, this.numListeners);
|
||||
|
||||
if(this.numListeners <= 0) {
|
||||
log.debug("No more listeners, may close H263Converter for [{}]", origin);
|
||||
this.stopConverter();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops FFmpeg for this stream and sets the number of
|
||||
* listeners to zero.
|
||||
*/
|
||||
public synchronized void stopConverter() {
|
||||
this.numListeners = 0;
|
||||
if(processMonitor != null) {
|
||||
processMonitor.destroy();
|
||||
processMonitor = null;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,112 @@
|
||||
package org.bigbluebutton.app.video.converter;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.bigbluebutton.app.video.ffmpeg.FFmpegCommand;
|
||||
import org.bigbluebutton.app.video.ffmpeg.ProcessMonitor;
|
||||
import org.red5.logging.Red5LoggerFactory;
|
||||
import org.red5.server.api.IConnection;
|
||||
import org.red5.server.api.Red5;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
/**
|
||||
* Represents a stream rotator. This class is responsible
|
||||
* for choosing the rotate direction based on the stream name
|
||||
* and starting FFmpeg to rotate and re-publish the stream.
|
||||
*/
|
||||
public class VideoRotator {
|
||||
|
||||
private static Logger log = Red5LoggerFactory.getLogger(VideoRotator.class, "video");
|
||||
|
||||
public static final String ROTATE_LEFT = "rotate_left";
|
||||
public static final String ROTATE_RIGHT = "rotate_right";
|
||||
|
||||
private String streamName;
|
||||
private FFmpegCommand.ROTATE direction;
|
||||
|
||||
private FFmpegCommand ffmpeg;
|
||||
private ProcessMonitor processMonitor;
|
||||
|
||||
/**
|
||||
* Create a new video rotator for the specified stream.
|
||||
* The streamName should be of the form:
|
||||
* rotate_[left|right]/streamName
|
||||
* The rotated stream will be published as streamName.
|
||||
*
|
||||
* @param origin Name of the stream that will be rotated
|
||||
*/
|
||||
public VideoRotator(String origin) {
|
||||
this.streamName = getStreamName(origin);
|
||||
this.direction = getDirection(origin);
|
||||
|
||||
IConnection conn = Red5.getConnectionLocal();
|
||||
String ip = conn.getHost();
|
||||
String conf = conn.getScope().getName();
|
||||
String inputLive = "rtmp://" + ip + "/video/" + conf + "/" + origin + " live=1";
|
||||
|
||||
String output = "rtmp://" + ip + "/video/" + conf + "/" + streamName;
|
||||
|
||||
ffmpeg = new FFmpegCommand();
|
||||
ffmpeg.setFFmpegPath("/usr/local/bin/ffmpeg");
|
||||
ffmpeg.setInput(inputLive);
|
||||
ffmpeg.setFormat("flv");
|
||||
ffmpeg.setOutput(output);
|
||||
ffmpeg.setLoglevel("warning");
|
||||
ffmpeg.setRotation(direction);
|
||||
ffmpeg.setAnalyzeDuration("10000"); // 10ms
|
||||
|
||||
start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the stream name from the direction/streamName string
|
||||
* @param streamName Name of the stream with rotate direction
|
||||
* @return The stream name used for re-publish
|
||||
*/
|
||||
private String getStreamName(String streamName) {
|
||||
String parts[] = streamName.split("/");
|
||||
if(parts.length > 1)
|
||||
return StringUtils.join(parts, '/', 1, parts.length);
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the rotate direction from the streamName string.
|
||||
* @param streamName Name of the stream with rotate direction
|
||||
* @return FFmpegCommand.ROTATE for the given direction if present, null otherwise
|
||||
*/
|
||||
public static FFmpegCommand.ROTATE getDirection(String streamName) {
|
||||
String parts[] = streamName.split("/");
|
||||
|
||||
switch(parts[0]) {
|
||||
case ROTATE_LEFT:
|
||||
return FFmpegCommand.ROTATE.LEFT;
|
||||
case ROTATE_RIGHT:
|
||||
return FFmpegCommand.ROTATE.RIGHT;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start FFmpeg process to rotate and re-publish the stream.
|
||||
*/
|
||||
public void start() {
|
||||
log.debug("Spawn FFMpeg to rotate [{}] stream [{}]", direction.name(), streamName);
|
||||
String[] command = ffmpeg.getFFmpegCommand(true);
|
||||
if (processMonitor == null) {
|
||||
processMonitor = new ProcessMonitor(command);
|
||||
}
|
||||
processMonitor.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop FFmpeg process that is rotating and re-publishing the stream.
|
||||
*/
|
||||
public void stop() {
|
||||
log.debug("Stopping FFMpeg from rotate [{}] stream [{}]", direction.name(), streamName);
|
||||
if(processMonitor != null) {
|
||||
processMonitor.destroy();
|
||||
processMonitor = null;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,191 @@
|
||||
package org.bigbluebutton.app.video.ffmpeg;
|
||||
|
||||
import java.security.InvalidParameterException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
|
||||
public class FFmpegCommand {
|
||||
|
||||
/**
|
||||
* Indicate the direction to rotate the video
|
||||
*/
|
||||
public enum ROTATE { LEFT, RIGHT };
|
||||
|
||||
private HashMap args;
|
||||
private HashMap x264Params;
|
||||
|
||||
private String[] command;
|
||||
|
||||
private String ffmpegPath;
|
||||
private String input;
|
||||
private String output;
|
||||
|
||||
/* Analyze duration is a special parameter that MUST come before the input */
|
||||
private String analyzeDuration;
|
||||
|
||||
public FFmpegCommand() {
|
||||
this.args = new HashMap();
|
||||
this.x264Params = new HashMap();
|
||||
|
||||
/* Prevent quality loss by default */
|
||||
try {
|
||||
this.setVideoQualityScale(1);
|
||||
} catch (InvalidParameterException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
};
|
||||
|
||||
this.ffmpegPath = null;
|
||||
}
|
||||
|
||||
public String[] getFFmpegCommand(boolean shouldBuild) {
|
||||
if(shouldBuild)
|
||||
buildFFmpegCommand();
|
||||
|
||||
return this.command;
|
||||
}
|
||||
|
||||
public void buildFFmpegCommand() {
|
||||
List comm = new ArrayList<String>();
|
||||
|
||||
if(this.ffmpegPath == null)
|
||||
this.ffmpegPath = "/usr/local/bin/ffmpeg";
|
||||
|
||||
comm.add(this.ffmpegPath);
|
||||
|
||||
/* Analyze duration MUST come before the input */
|
||||
if(analyzeDuration != null && !analyzeDuration.isEmpty()) {
|
||||
comm.add("-analyzeduration");
|
||||
comm.add(analyzeDuration);
|
||||
}
|
||||
|
||||
comm.add("-i");
|
||||
comm.add(input);
|
||||
|
||||
Iterator argsIter = this.args.entrySet().iterator();
|
||||
while (argsIter.hasNext()) {
|
||||
Map.Entry pairs = (Map.Entry)argsIter.next();
|
||||
comm.add(pairs.getKey());
|
||||
comm.add(pairs.getValue());
|
||||
}
|
||||
|
||||
if(!x264Params.isEmpty()) {
|
||||
comm.add("-x264-params");
|
||||
String params = "";
|
||||
Iterator x264Iter = this.x264Params.entrySet().iterator();
|
||||
while (x264Iter.hasNext()) {
|
||||
Map.Entry pairs = (Map.Entry)x264Iter.next();
|
||||
String argValue = pairs.getKey() + "=" + pairs.getValue();
|
||||
params += argValue;
|
||||
// x264-params are separated by ':'
|
||||
params += ":";
|
||||
}
|
||||
// Remove trailing ':'
|
||||
params.replaceAll(":+$", "");
|
||||
comm.add(params);
|
||||
}
|
||||
|
||||
comm.add(this.output);
|
||||
|
||||
this.command = new String[comm.size()];
|
||||
comm.toArray(this.command);
|
||||
}
|
||||
|
||||
public void setFFmpegPath(String arg) {
|
||||
this.ffmpegPath = arg;
|
||||
}
|
||||
|
||||
public void setInput(String arg) {
|
||||
this.input = arg;
|
||||
}
|
||||
|
||||
public void setOutput(String arg) {
|
||||
this.output = arg;
|
||||
}
|
||||
|
||||
public void setCodec(String arg) {
|
||||
this.args.put("-vcodec", arg);
|
||||
}
|
||||
|
||||
public void setLevel(String arg) {
|
||||
this.args.put("-level", arg);
|
||||
}
|
||||
|
||||
public void setPreset(String arg) {
|
||||
this.args.put("-preset", arg);
|
||||
}
|
||||
|
||||
public void setProfile(String arg) {
|
||||
this.args.put("-profile:v", arg);
|
||||
}
|
||||
|
||||
public void setFormat(String arg) {
|
||||
this.args.put("-f", arg);
|
||||
}
|
||||
|
||||
public void setPayloadType(String arg) {
|
||||
this.args.put("-payload_type", arg);
|
||||
}
|
||||
|
||||
public void setLoglevel(String arg) {
|
||||
this.args.put("-loglevel", arg);
|
||||
}
|
||||
|
||||
public void setSliceMaxSize(String arg) {
|
||||
this.x264Params.put("slice-max-size", arg);
|
||||
}
|
||||
|
||||
public void setMaxKeyFrameInterval(String arg) {
|
||||
this.x264Params.put("keyint", arg);
|
||||
}
|
||||
|
||||
public void setResolution(String arg) {
|
||||
this.args.put("-s", arg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the direction to rotate the video
|
||||
* @param arg Rotate direction
|
||||
*/
|
||||
public void setRotation(ROTATE arg) {
|
||||
switch (arg) {
|
||||
case LEFT:
|
||||
this.args.put("-vf", "transpose=2");
|
||||
break;
|
||||
case RIGHT:
|
||||
this.args.put("-vf", "transpose=1");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set how much time FFmpeg should analyze stream
|
||||
* data to get stream information. Note that this
|
||||
* affects directly the delay to start the stream.
|
||||
*
|
||||
* @param duration Rotate direction
|
||||
*/
|
||||
public void setAnalyzeDuration(String duration) {
|
||||
this.analyzeDuration = duration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set video quality scale to a value (1-31).
|
||||
* 1 is the highest quality and 31 the lowest.
|
||||
* <p>
|
||||
* <b> Note: Does NOT apply to h264 encoder. </b>
|
||||
* </p>
|
||||
*
|
||||
* @param scale Scale value (1-31)
|
||||
* @throws InvalidParameterException
|
||||
*/
|
||||
public void setVideoQualityScale(Integer scale) throws InvalidParameterException {
|
||||
if(scale < 1 || scale > 31)
|
||||
throw new InvalidParameterException("Scale must be a value in 1-31 range");
|
||||
|
||||
this.args.put("-q:v", scale.toString());
|
||||
}
|
||||
}
|
@ -0,0 +1,105 @@
|
||||
package org.bigbluebutton.app.video.ffmpeg;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.red5.logging.Red5LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class ProcessMonitor implements Runnable {
|
||||
private static Logger log = Red5LoggerFactory.getLogger(ProcessMonitor.class, "video");
|
||||
|
||||
private String[] command;
|
||||
private Process process;
|
||||
|
||||
ProcessStream inputStreamMonitor;
|
||||
ProcessStream errorStreamMonitor;
|
||||
|
||||
private Thread thread = null;
|
||||
|
||||
public ProcessMonitor(String[] command) {
|
||||
this.command = command;
|
||||
this.process = null;
|
||||
this.inputStreamMonitor = null;
|
||||
this.errorStreamMonitor = null;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
if (this.command == null || this.command.length == 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
StringBuffer result = new StringBuffer();
|
||||
String delim = "";
|
||||
for (String i : this.command) {
|
||||
result.append(delim).append(i);
|
||||
delim = " ";
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
log.debug("Creating thread to execute FFmpeg");
|
||||
log.debug("Executing: " + this.toString());
|
||||
this.process = Runtime.getRuntime().exec(this.command);
|
||||
|
||||
if(this.process == null) {
|
||||
log.debug("process is null");
|
||||
return;
|
||||
}
|
||||
|
||||
InputStream is = this.process.getInputStream();
|
||||
InputStream es = this.process.getErrorStream();
|
||||
|
||||
inputStreamMonitor = new ProcessStream(is);
|
||||
errorStreamMonitor = new ProcessStream(es);
|
||||
|
||||
inputStreamMonitor.start();
|
||||
errorStreamMonitor.start();
|
||||
|
||||
this.process.waitFor();
|
||||
|
||||
int ret = this.process.exitValue();
|
||||
log.debug("Exit value: " + ret);
|
||||
|
||||
destroy();
|
||||
}
|
||||
catch(SecurityException se) {
|
||||
log.debug("Security Exception");
|
||||
}
|
||||
catch(IOException ioe) {
|
||||
log.debug("IO Exception");
|
||||
}
|
||||
catch(NullPointerException npe) {
|
||||
log.debug("NullPointer Exception");
|
||||
}
|
||||
catch(IllegalArgumentException iae) {
|
||||
log.debug("IllegalArgument Exception");
|
||||
}
|
||||
catch(InterruptedException ie) {
|
||||
log.debug("Interrupted Excetion");
|
||||
}
|
||||
|
||||
log.debug("Exiting thread that executes FFmpeg");
|
||||
}
|
||||
|
||||
public void start() {
|
||||
this.thread = new Thread(this);
|
||||
this.thread.start();
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
if(this.inputStreamMonitor != null
|
||||
&& this.errorStreamMonitor != null) {
|
||||
this.inputStreamMonitor.close();
|
||||
this.errorStreamMonitor.close();
|
||||
}
|
||||
|
||||
if(this.process != null) {
|
||||
log.debug("Closing FFmpeg process");
|
||||
this.process.destroy();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
package org.bigbluebutton.app.video.ffmpeg;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.red5.logging.Red5LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class ProcessStream implements Runnable {
|
||||
private static Logger log = Red5LoggerFactory.getLogger(ProcessStream.class, "video");
|
||||
private InputStream stream;
|
||||
private Thread thread;
|
||||
|
||||
ProcessStream(InputStream stream) {
|
||||
if(stream != null)
|
||||
this.stream = stream;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
log.debug("Creating thread to execute the process stream");
|
||||
String line;
|
||||
InputStreamReader isr = new InputStreamReader(this.stream);
|
||||
BufferedReader ibr = new BufferedReader(isr);
|
||||
while ((line = ibr.readLine()) != null) {
|
||||
log.debug(line);
|
||||
}
|
||||
|
||||
close();
|
||||
}
|
||||
catch(IOException ioe) {
|
||||
log.debug("IOException");
|
||||
close();
|
||||
}
|
||||
|
||||
log.debug("Exiting thread that handles process stream");
|
||||
}
|
||||
|
||||
public void start() {
|
||||
this.thread = new Thread(this);
|
||||
this.thread.start();
|
||||
}
|
||||
|
||||
public void close() {
|
||||
try {
|
||||
if(this.stream != null) {
|
||||
log.debug("Closing process stream");
|
||||
this.stream.close();
|
||||
this.stream = null;
|
||||
}
|
||||
}
|
||||
catch(IOException ioe) {
|
||||
log.debug("IOException");
|
||||
}
|
||||
}
|
||||
}
|
@ -1,2 +1,5 @@
|
||||
redis.host=127.0.0.1
|
||||
redis.port=6379
|
||||
|
||||
# timeout (ms) to close the relay after the last listener disconnected
|
||||
relayTimeout=60000
|
||||
|
@ -48,6 +48,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
<bean id="web.handler" class="org.bigbluebutton.app.video.VideoApplication">
|
||||
<property name="recordVideoStream" value="true"/>
|
||||
<property name="eventRecordingService" ref="redisRecorder"/>
|
||||
<property name="relayTimeout" value="${relayTimeout}"/>
|
||||
</bean>
|
||||
|
||||
<bean id="redisRecorder" class="org.bigbluebutton.app.video.EventRecordingService">
|
||||
|
@ -127,6 +127,11 @@ public class BigBlueButtonApplication extends MultiThreadedApplicationAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
Boolean guest = false;
|
||||
if (params.length >= 9 && ((Boolean) params[9])) {
|
||||
guest = true;
|
||||
}
|
||||
|
||||
if (record == true) {
|
||||
recorderApplication.createRecordSession(room);
|
||||
}
|
||||
@ -134,7 +139,7 @@ public class BigBlueButtonApplication extends MultiThreadedApplicationAdapter {
|
||||
String userId = internalUserID;
|
||||
String sessionId = CONN + userId;
|
||||
BigBlueButtonSession bbbSession = new BigBlueButtonSession(room, internalUserID, username, role,
|
||||
voiceBridge, record, externalUserID, muted, sessionId);
|
||||
voiceBridge, record, externalUserID, muted, sessionId, guest);
|
||||
connection.setAttribute(Constants.SESSION, bbbSession);
|
||||
connection.setAttribute("INTERNAL_USER_ID", internalUserID);
|
||||
connection.setAttribute("USER_SESSION_ID", sessionId);
|
||||
|
@ -29,10 +29,11 @@ public class BigBlueButtonSession {
|
||||
private final String externalUserID;
|
||||
private final Boolean startAsMuted;
|
||||
private final String sessionId;
|
||||
private final Boolean guest;
|
||||
|
||||
public BigBlueButtonSession(String room, String internalUserID, String username,
|
||||
String role, String voiceBridge, Boolean record,
|
||||
String externalUserID, Boolean startAsMuted, String sessionId){
|
||||
String externalUserID, Boolean startAsMuted, String sessionId, Boolean guest){
|
||||
this.internalUserID = internalUserID;
|
||||
this.username = username;
|
||||
this.role = role;
|
||||
@ -42,6 +43,7 @@ public class BigBlueButtonSession {
|
||||
this.externalUserID = externalUserID;
|
||||
this.startAsMuted = startAsMuted;
|
||||
this.sessionId = sessionId;
|
||||
this.guest = guest;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
@ -79,4 +81,8 @@ public class BigBlueButtonSession {
|
||||
public String getSessionId() {
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
public Boolean isGuest() {
|
||||
return guest;
|
||||
}
|
||||
}
|
||||
|
@ -46,8 +46,8 @@ public class MeetingMessageHandler implements MessageHandler {
|
||||
emm.moderatorPass, emm.viewerPass, emm.createTime, emm.createDate);
|
||||
} else if (msg instanceof RegisterUserMessage) {
|
||||
RegisterUserMessage emm = (RegisterUserMessage) msg;
|
||||
log.info("Received register user request. Meeting id [{}], userid=[{}], token=[{}]", emm.meetingID, emm.internalUserId, emm.authToken);
|
||||
bbbGW.registerUser(emm.meetingID, emm.internalUserId, emm.fullname, emm.role, emm.externUserID, emm.authToken);
|
||||
log.info("Received register user request. Meeting id [{}], userid=[{}], token=[{}], guest=[{}]", emm.meetingID, emm.internalUserId, emm.authToken, emm.guest);
|
||||
bbbGW.registerUser(emm.meetingID, emm.internalUserId, emm.fullname, emm.role, emm.externUserID, emm.authToken, emm.guest);
|
||||
} else if (msg instanceof DestroyMeetingMessage) {
|
||||
DestroyMeetingMessage emm = (DestroyMeetingMessage) msg;
|
||||
log.info("Received destroy meeting request. Meeting id [{}]", emm.meetingId);
|
||||
|
@ -93,4 +93,5 @@ public class Constants {
|
||||
public static final String VIEWER_PASS = "viewer_pass";
|
||||
public static final String CREATE_TIME = "create_time";
|
||||
public static final String CREATE_DATE = "create_date";
|
||||
public static final String GUEST = "guest";
|
||||
}
|
||||
|
@ -51,6 +51,7 @@ public class MessagingConstants {
|
||||
public static final String USER_LEFT_EVENT = "UserLeftEvent";
|
||||
public static final String USER_LEFT_VOICE_REQUEST = "user_left_voice_request";
|
||||
public static final String USER_STATUS_CHANGE_EVENT = "UserStatusChangeEvent";
|
||||
public static final String USER_ROLE_CHANGE_EVENT = "UserRoleChangeEvent";
|
||||
public static final String SEND_POLLS_EVENT = "SendPollsEvent";
|
||||
public static final String RECORD_STATUS_EVENT = "RecordStatusEvent";
|
||||
public static final String SEND_PUBLIC_CHAT_MESSAGE_REQUEST = "send_public_chat_message_request";
|
||||
|
@ -14,14 +14,16 @@ public class RegisterUserMessage implements IMessage {
|
||||
public final String role;
|
||||
public final String externUserID;
|
||||
public final String authToken;
|
||||
public final Boolean guest;
|
||||
|
||||
public RegisterUserMessage(String meetingID, String internalUserId, String fullname, String role, String externUserID, String authToken) {
|
||||
public RegisterUserMessage(String meetingID, String internalUserId, String fullname, String role, String externUserID, String authToken, Boolean guest) {
|
||||
this.meetingID = meetingID;
|
||||
this.internalUserId = internalUserId;
|
||||
this.fullname = fullname;
|
||||
this.role = role;
|
||||
this.externUserID = externUserID;
|
||||
this.authToken = authToken;
|
||||
this.guest = guest;
|
||||
}
|
||||
|
||||
public String toJson() {
|
||||
@ -33,6 +35,7 @@ public class RegisterUserMessage implements IMessage {
|
||||
payload.put(Constants.ROLE, role);
|
||||
payload.put(Constants.EXT_USER_ID, externUserID);
|
||||
payload.put(Constants.AUTH_TOKEN, authToken);
|
||||
payload.put(Constants.GUEST, guest.toString());
|
||||
|
||||
java.util.HashMap<String, Object> header = MessageBuilder.buildHeader(REGISTER_USER, VERSION, null);
|
||||
|
||||
@ -53,16 +56,18 @@ public class RegisterUserMessage implements IMessage {
|
||||
&& payload.has(Constants.NAME)
|
||||
&& payload.has(Constants.ROLE)
|
||||
&& payload.has(Constants.EXT_USER_ID)
|
||||
&& payload.has(Constants.AUTH_TOKEN)) {
|
||||
&& payload.has(Constants.AUTH_TOKEN)
|
||||
&& payload.has(Constants.GUEST)) {
|
||||
|
||||
String meetingID = payload.get(Constants.MEETING_ID).getAsString();
|
||||
String fullname = payload.get(Constants.NAME).getAsString();
|
||||
String role = payload.get(Constants.ROLE).getAsString();
|
||||
String externUserID = payload.get(Constants.EXT_USER_ID).getAsString();
|
||||
String authToken = payload.get(Constants.AUTH_TOKEN).getAsString();
|
||||
Boolean guest = payload.get(Constants.GUEST).getAsBoolean();
|
||||
|
||||
//use externalUserId twice - once for external, once for internal
|
||||
return new RegisterUserMessage(meetingID, externUserID, fullname, role, externUserID, authToken);
|
||||
return new RegisterUserMessage(meetingID, externUserID, fullname, role, externUserID, authToken, guest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -26,14 +26,6 @@ public class ParticipantsApplication {
|
||||
private static Logger log = Red5LoggerFactory.getLogger( ParticipantsApplication.class, "bigbluebutton" );
|
||||
private IBigBlueButtonInGW bbbInGW;
|
||||
|
||||
public void userRaiseHand(String meetingId, String userId) {
|
||||
bbbInGW.userRaiseHand(meetingId, userId);
|
||||
}
|
||||
|
||||
public void lowerHand(String meetingId, String userId, String loweredBy) {
|
||||
bbbInGW.lowerHand(meetingId, userId, loweredBy);
|
||||
}
|
||||
|
||||
public void ejectUserFromMeeting(String meetingId, String userId, String ejectedBy) {
|
||||
bbbInGW.ejectUserFromMeeting(meetingId, userId, ejectedBy);
|
||||
}
|
||||
@ -50,8 +42,8 @@ public class ParticipantsApplication {
|
||||
bbbInGW.setUserStatus(room, userid, status, value);
|
||||
}
|
||||
|
||||
public boolean registerUser(String roomName, String userid, String username, String role, String externUserID) {
|
||||
bbbInGW.registerUser(roomName, userid, username, role, externUserID, userid);
|
||||
public boolean registerUser(String roomName, String userid, String username, String role, String externUserID, Boolean guest) {
|
||||
bbbInGW.registerUser(roomName, userid, username, role, externUserID, userid, guest);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -74,4 +66,20 @@ public class ParticipantsApplication {
|
||||
public void getRecordingStatus(String meetingId, String userId) {
|
||||
bbbInGW.getRecordingStatus(meetingId, userId);
|
||||
}
|
||||
|
||||
public void getGuestPolicy(String meetingId, String requesterId) {
|
||||
bbbInGW.getGuestPolicy(meetingId, requesterId);
|
||||
}
|
||||
|
||||
public void newGuestPolicy(String meetingId, String guestPolicy, String setBy) {
|
||||
bbbInGW.setGuestPolicy(meetingId, guestPolicy, setBy);
|
||||
}
|
||||
|
||||
public void responseToGuest(String meetingId, String userId, Boolean response, String requesterId) {
|
||||
bbbInGW.responseToGuest(meetingId, userId, response, requesterId);
|
||||
}
|
||||
|
||||
public void setParticipantRole(String meetingId, String userId, String role) {
|
||||
bbbInGW.setUserRole(meetingId, userId, role);
|
||||
}
|
||||
}
|
||||
|
@ -34,9 +34,7 @@ public class ParticipantsListener implements MessageHandler{
|
||||
|
||||
String eventName = headerObject.get("name").toString().replace("\"", "");
|
||||
|
||||
if(eventName.equalsIgnoreCase("user_leaving_request") ||
|
||||
eventName.equalsIgnoreCase("user_raised_hand_message") ||
|
||||
eventName.equalsIgnoreCase("user_lowered_hand_message")){
|
||||
if(eventName.equalsIgnoreCase("user_leaving_request")){
|
||||
|
||||
String roomName = payloadObject.get("meeting_id").toString().replace("\"", "");
|
||||
String userID = payloadObject.get("userid").toString().replace("\"", "");
|
||||
@ -49,13 +47,6 @@ public class ParticipantsListener implements MessageHandler{
|
||||
String sessionId = "tobeimplemented";
|
||||
bbbInGW.userLeft(roomName, userID, sessionId);
|
||||
}
|
||||
else if(eventName.equalsIgnoreCase("user_raised_hand_message")){
|
||||
bbbInGW.userRaiseHand(roomName, userID);
|
||||
}
|
||||
else if(eventName.equalsIgnoreCase("user_lowered_hand_message")){
|
||||
String requesterID = payloadObject.get("lowered_by").toString().replace("\"", "");
|
||||
bbbInGW.lowerHand(roomName, userID, requesterID);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -45,19 +45,6 @@ public class ParticipantsService {
|
||||
application.getUsers(scope.getName(), getBbbSession().getInternalUserID(), sessionId);
|
||||
}
|
||||
|
||||
public void userRaiseHand() {
|
||||
IScope scope = Red5.getConnectionLocal().getScope();
|
||||
String userId = getBbbSession().getInternalUserID();
|
||||
application.userRaiseHand(scope.getName(), userId);
|
||||
}
|
||||
|
||||
public void lowerHand(Map<String, String> msg) {
|
||||
String userId = (String) msg.get("userId");
|
||||
String loweredBy = (String) msg.get("loweredBy");
|
||||
IScope scope = Red5.getConnectionLocal().getScope();
|
||||
application.lowerHand(scope.getName(), userId, loweredBy);
|
||||
}
|
||||
|
||||
public void ejectUserFromMeeting(Map<String, String> msg) {
|
||||
String userId = (String) msg.get("userId");
|
||||
String ejectedBy = (String) msg.get("ejectedBy");
|
||||
@ -80,7 +67,15 @@ public class ParticipantsService {
|
||||
public void setParticipantStatus(Map<String, Object> msg) {
|
||||
String roomName = Red5.getConnectionLocal().getScope().getName();
|
||||
|
||||
application.setParticipantStatus(roomName, (String) msg.get("userID"), (String) msg.get("status"), (Object) msg.get("value"));
|
||||
String userid = (String) msg.get("userID");
|
||||
String status = (String) msg.get("status");
|
||||
Object value = (Object) msg.get("value");
|
||||
if (status.equals("mood")) {
|
||||
value = ((String) value) + "," + System.currentTimeMillis();
|
||||
}
|
||||
|
||||
log.debug("Setting participant status " + roomName + " " + userid + " " + status + " " + value);
|
||||
application.setParticipantStatus(roomName, userid, status, value);
|
||||
}
|
||||
|
||||
public void setParticipantsApplication(ParticipantsApplication a) {
|
||||
@ -107,4 +102,29 @@ public class ParticipantsService {
|
||||
return (BigBlueButtonSession) Red5.getConnectionLocal().getAttribute(Constants.SESSION);
|
||||
}
|
||||
|
||||
public void getGuestPolicy() {
|
||||
String requesterId = getBbbSession().getInternalUserID();
|
||||
String roomName = Red5.getConnectionLocal().getScope().getName();
|
||||
application.getGuestPolicy(roomName, requesterId);
|
||||
}
|
||||
|
||||
public void setGuestPolicy(String guestPolicy) {
|
||||
String requesterId = getBbbSession().getInternalUserID();
|
||||
String roomName = Red5.getConnectionLocal().getScope().getName();
|
||||
application.newGuestPolicy(roomName, guestPolicy, requesterId);
|
||||
}
|
||||
|
||||
public void responseToGuest(Map<String, Object> msg) {
|
||||
String requesterId = getBbbSession().getInternalUserID();
|
||||
String roomName = Red5.getConnectionLocal().getScope().getName();
|
||||
application.responseToGuest(roomName, (String) msg.get("userId"), (Boolean) msg.get("response"), requesterId);
|
||||
}
|
||||
|
||||
public void setParticipantRole(Map<String, String> msg) {
|
||||
String roomName = Red5.getConnectionLocal().getScope().getName();
|
||||
String userId = (String) msg.get("userId");
|
||||
String role = (String) msg.get("role");
|
||||
log.debug("Setting participant role " + roomName + " " + userId + " " + role);
|
||||
application.setParticipantRole(roomName, userId, role);
|
||||
}
|
||||
}
|
||||
|
@ -48,9 +48,9 @@ public class ConversionUpdatesProcessor {
|
||||
|
||||
public void sendConversionCompleted(String messageKey, String conference,
|
||||
String code, String presId, Integer numberOfPages, String presName,
|
||||
String presBaseUrl) {
|
||||
String presBaseUrl, Boolean presDownloadable) {
|
||||
presentationApplication.sendConversionCompleted(messageKey, conference,
|
||||
code, presId, numberOfPages, presName, presBaseUrl);
|
||||
code, presId, numberOfPages, presName, presBaseUrl, presDownloadable);
|
||||
}
|
||||
|
||||
public void setPresentationApplication(PresentationApplication a) {
|
||||
|
@ -57,9 +57,9 @@ public class PresentationApplication {
|
||||
|
||||
public void sendConversionCompleted(String messageKey, String meetingId,
|
||||
String code, String presentation, int numberOfPages,
|
||||
String presName, String presBaseUrl) {
|
||||
String presName, String presBaseUrl, Boolean presDownloadable) {
|
||||
bbbInGW.sendConversionCompleted(messageKey, meetingId,
|
||||
code, presentation, numberOfPages, presName, presBaseUrl);
|
||||
code, presentation, numberOfPages, presName, presBaseUrl, presDownloadable);
|
||||
}
|
||||
|
||||
public void removePresentation(String meetingID, String presentationID){
|
||||
|
@ -49,10 +49,10 @@ public class PresentationMessageListener implements MessageHandler {
|
||||
|
||||
private void sendConversionCompleted(String messageKey, String conference,
|
||||
String code, String presId, Integer numberOfPages,
|
||||
String filename, String presBaseUrl) {
|
||||
String filename, String presBaseUrl, Boolean presDownloadable) {
|
||||
|
||||
conversionUpdatesProcessor.sendConversionCompleted(messageKey, conference,
|
||||
code, presId, numberOfPages, filename, presBaseUrl);
|
||||
code, presId, numberOfPages, filename, presBaseUrl, presDownloadable);
|
||||
}
|
||||
|
||||
|
||||
@ -96,8 +96,9 @@ public class PresentationMessageListener implements MessageHandler {
|
||||
} else if(messageKey.equalsIgnoreCase(CONVERSION_COMPLETED_KEY)){
|
||||
Integer numberOfPages = new Integer((String) map.get("numberOfPages"));
|
||||
String presBaseUrl = (String) map.get("presentationBaseUrl");
|
||||
Boolean presDownloadable = new Boolean((String) map.get("presDownloadable"));
|
||||
sendConversionCompleted(messageKey, conference, code,
|
||||
presId, numberOfPages, filename, presBaseUrl);
|
||||
presId, numberOfPages, filename, presBaseUrl, presDownloadable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,19 @@
|
||||
package org.bigbluebutton.conference.service.recorder.participants;
|
||||
|
||||
public class GuestAskToEnterRecordEvent extends AbstractParticipantRecordEvent {
|
||||
|
||||
public GuestAskToEnterRecordEvent() {
|
||||
super();
|
||||
setEvent("GuestAskToEnterEvent");
|
||||
}
|
||||
|
||||
public void setUserId(String userId) {
|
||||
eventMap.put("userId", userId);
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
eventMap.put("name", name);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package org.bigbluebutton.conference.service.recorder.participants;
|
||||
|
||||
public class GuestPolicyEvent extends AbstractParticipantRecordEvent {
|
||||
|
||||
public GuestPolicyEvent() {
|
||||
super();
|
||||
setEvent("GuestPolicyEvent");
|
||||
}
|
||||
|
||||
public void setPolicy(String guestPolicy) {
|
||||
eventMap.put("guestPolicy", guestPolicy);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package org.bigbluebutton.conference.service.recorder.participants;
|
||||
|
||||
public class ModeratorResponseEvent extends AbstractParticipantRecordEvent {
|
||||
|
||||
public ModeratorResponseEvent() {
|
||||
super();
|
||||
setEvent("ModeratorResponseEvent");
|
||||
}
|
||||
|
||||
public void setUserId(String userId) {
|
||||
eventMap.put("userId", userId);
|
||||
}
|
||||
|
||||
public void setResp(Boolean resp) {
|
||||
eventMap.put("resp", resp.toString());
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
/**
|
||||
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
|
||||
*
|
||||
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the
|
||||
* terms of the GNU Lesser General Public License as published by the Free Software
|
||||
* Foundation; either version 3.0 of the License, or (at your option) any later
|
||||
* version.
|
||||
*
|
||||
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License along
|
||||
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package org.bigbluebutton.conference.service.recorder.participants;
|
||||
|
||||
public class ParticipantRoleChangeRecordEvent extends AbstractParticipantRecordEvent {
|
||||
|
||||
public ParticipantRoleChangeRecordEvent() {
|
||||
super();
|
||||
setEvent("ParticipantRoleChangeEvent");
|
||||
}
|
||||
|
||||
public void setUserId(String userId) {
|
||||
eventMap.put("userId", userId);
|
||||
}
|
||||
|
||||
public void setRole(String role) {
|
||||
eventMap.put("role", role);
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package org.bigbluebutton.conference.service.recorder.participants;
|
||||
|
||||
public class WaitingForModeratorEvent extends AbstractParticipantRecordEvent {
|
||||
|
||||
public WaitingForModeratorEvent() {
|
||||
super();
|
||||
setEvent("WaitingForModeratorEvent");
|
||||
}
|
||||
|
||||
public void setUserId(String userId) {
|
||||
eventMap.put("userId", userId);
|
||||
}
|
||||
public void setArg(String arg) {
|
||||
eventMap.put("userId_userName", arg);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
/**
|
||||
* 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>
|
||||
*/
|
||||
package org.bigbluebutton.conference.service.sharednotes;
|
||||
|
||||
import org.bigbluebutton.core.api.IBigBlueButtonInGW;
|
||||
import org.red5.logging.Red5LoggerFactory;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
public class SharedNotesApplication {
|
||||
private static Logger log = Red5LoggerFactory.getLogger( SharedNotesApplication.class, "bigbluebutton" );
|
||||
|
||||
private IBigBlueButtonInGW bbbInGW;
|
||||
|
||||
public void setBigBlueButtonInGW(IBigBlueButtonInGW inGW) {
|
||||
bbbInGW = inGW;
|
||||
}
|
||||
|
||||
public void patchDocument(String meetingID, String requesterID, String noteID, String patch, Integer beginIndex, Integer endIndex) {
|
||||
bbbInGW.patchDocument(meetingID, requesterID, noteID, patch, beginIndex, endIndex);
|
||||
}
|
||||
|
||||
public void currentDocument(String meetingID, String requesterID) {
|
||||
bbbInGW.getCurrentDocument(meetingID, requesterID);
|
||||
}
|
||||
|
||||
public void createAdditionalNotes(String meetingID, String requesterID) {
|
||||
bbbInGW.createAdditionalNotes(meetingID, requesterID);
|
||||
}
|
||||
|
||||
public void destroyAdditionalNotes(String meetingID, String requesterID, String noteID) {
|
||||
bbbInGW.destroyAdditionalNotes(meetingID, requesterID, noteID);
|
||||
}
|
||||
|
||||
public void requestAdditionalNotesSet(String meetingID, String requesterID, int additionalNotesSetSize) {
|
||||
bbbInGW.requestAdditionalNotesSet(meetingID, requesterID, additionalNotesSetSize);
|
||||
}
|
||||
}
|
@ -0,0 +1,110 @@
|
||||
/**
|
||||
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
|
||||
*
|
||||
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the
|
||||
* terms of the GNU Lesser General Public License as published by the Free Software
|
||||
* Foundation; either version 3.0 of the License, or (at your option) any later
|
||||
* version.
|
||||
*
|
||||
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License along
|
||||
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.bigbluebutton.conference.service.sharednotes;
|
||||
|
||||
import org.red5.server.adapter.IApplication;
|
||||
import org.red5.server.api.IClient;
|
||||
import org.red5.server.api.IConnection;
|
||||
import org.slf4j.Logger;
|
||||
import org.red5.logging.Red5LoggerFactory;
|
||||
import org.red5.server.api.scope.IScope;
|
||||
import org.bigbluebutton.conference.service.recorder.RecorderApplication;
|
||||
import org.bigbluebutton.conference.service.sharednotes.SharedNotesApplication;
|
||||
|
||||
public class SharedNotesHandler implements IApplication{
|
||||
private static Logger log = Red5LoggerFactory.getLogger( SharedNotesHandler.class, "bigbluebutton" );
|
||||
|
||||
private RecorderApplication recorderApplication;
|
||||
private SharedNotesApplication sharedNotesApplication;
|
||||
|
||||
private static final String APP = "SHARED NOTES";
|
||||
|
||||
@Override
|
||||
public boolean appConnect(IConnection conn, Object[] params) {
|
||||
log.debug("***** " + APP + " [ " + " appConnect *********");
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appDisconnect(IConnection conn) {
|
||||
log.debug("***** " + APP + " [ " + " appDisconnect *********");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean appJoin(IClient client, IScope scope) {
|
||||
log.debug("***** " + APP + " [ " + " appJoin [ " + scope.getName() + "] *********");
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appLeave(IClient client, IScope scope) {
|
||||
log.debug("***** " + APP + " [ " + " appLeave [ " + scope.getName() + "] *********");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean appStart(IScope scope) {
|
||||
log.debug("***** " + APP + " [ " + " appStart [ " + scope.getName() + "] *********");
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appStop(IScope scope) {
|
||||
log.debug("***** " + APP + " [ " + " appStop [ " + scope.getName() + "] *********");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void roomDisconnect(IConnection connection) {
|
||||
log.debug("***** " + APP + " [ " + " roomDisconnect [ " + connection.getScope().getName() + "] *********");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean roomJoin(IClient client, IScope scope) {
|
||||
log.debug("***** " + APP + " [ " + " roomJoin [ " + scope.getName() + "] *********");
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void roomLeave(IClient client, IScope scope) {
|
||||
log.debug("***** " + APP + " [ " + " roomLeave [ " + scope.getName() + "] *********");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean roomConnect(IConnection connection, Object[] params) {
|
||||
log.debug("***** " + APP + " [ " + " roomConnect [ " + connection.getScope().getName() + "] *********");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean roomStart(IScope scope) {
|
||||
log.debug("***** " + APP + " [ " + " roomStart [ " + scope.getName() + "] *********");
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void roomStop(IScope scope) {
|
||||
log.debug("***** " + APP + " [ " + " roomStop [ " + scope.getName() + "] *********");
|
||||
}
|
||||
|
||||
public void setSharedNotesApplication(SharedNotesApplication a) {
|
||||
log.debug("Setting shared notes application");
|
||||
sharedNotesApplication = a;
|
||||
}
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
/**
|
||||
* 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: Hugo Lazzari <hslazzari@gmail.com>
|
||||
*/
|
||||
package org.bigbluebutton.conference.service.sharednotes;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.bigbluebutton.conference.BigBlueButtonSession;
|
||||
import org.bigbluebutton.conference.Constants;
|
||||
import org.red5.logging.Red5LoggerFactory;
|
||||
import org.red5.server.api.Red5;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
public class SharedNotesService {
|
||||
|
||||
private static Logger log = Red5LoggerFactory.getLogger( SharedNotesService.class, "bigbluebutton" );
|
||||
|
||||
private SharedNotesApplication application;
|
||||
|
||||
private BigBlueButtonSession getBbbSession() {
|
||||
return (BigBlueButtonSession) Red5.getConnectionLocal().getAttribute(Constants.SESSION);
|
||||
}
|
||||
|
||||
public void currentDocument() {
|
||||
log.debug("SharedNotesService.currentDocument");
|
||||
String meetingID = Red5.getConnectionLocal().getScope().getName();
|
||||
String requesterID = getBbbSession().getInternalUserID();
|
||||
|
||||
application.currentDocument(meetingID, requesterID);
|
||||
}
|
||||
|
||||
public void patchDocument(Map<String, Object> msg) {
|
||||
log.debug("SharedNotesService.patchDocument");
|
||||
String noteID = msg.get("noteID").toString();
|
||||
String patch = msg.get("patch").toString();
|
||||
Integer beginIndex = (Integer) msg.get("beginIndex");
|
||||
Integer endIndex = (Integer) msg.get("endIndex");
|
||||
|
||||
String meetingID = Red5.getConnectionLocal().getScope().getName();
|
||||
String requesterID = getBbbSession().getInternalUserID();
|
||||
|
||||
application.patchDocument(meetingID, requesterID, noteID, patch, beginIndex, endIndex);
|
||||
}
|
||||
|
||||
public void createAdditionalNotes() {
|
||||
log.debug("SharedNotesService.createAdditionalNotes");
|
||||
String meetingID = Red5.getConnectionLocal().getScope().getName();
|
||||
String requesterID = getBbbSession().getInternalUserID();
|
||||
|
||||
application.createAdditionalNotes(meetingID, requesterID);
|
||||
}
|
||||
|
||||
public void destroyAdditionalNotes(Map<String, Object> msg) {
|
||||
log.debug("SharedNotesService.destroyAdditionalNotes");
|
||||
String noteID = msg.get("noteID").toString();
|
||||
|
||||
String meetingID = Red5.getConnectionLocal().getScope().getName();
|
||||
String requesterID = getBbbSession().getInternalUserID();
|
||||
|
||||
application.destroyAdditionalNotes(meetingID, requesterID, noteID);
|
||||
}
|
||||
|
||||
public void requestAdditionalNotesSet(Map<String, Object> msg) {
|
||||
log.debug("SharedNotesService.requestAdditionalNotesSet");
|
||||
Integer additionalNotesSetSize = (Integer) msg.get("additionalNotesSetSize");
|
||||
|
||||
String meetingID = Red5.getConnectionLocal().getScope().getName();
|
||||
String requesterID = getBbbSession().getInternalUserID();
|
||||
|
||||
application.requestAdditionalNotesSet(meetingID, requesterID, additionalNotesSetSize);
|
||||
}
|
||||
|
||||
public void setSharedNotesApplication(SharedNotesApplication a) {
|
||||
log.debug("Setting sharedNotes application");
|
||||
application = a;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,52 @@
|
||||
/**
|
||||
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
|
||||
*
|
||||
* Copyright (c) 2014 BigBlueButton Inc. and by respective authors (see below).
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the
|
||||
* terms of the GNU Lesser General Public License as published by the Free Software
|
||||
* Foundation; either version 3.0 of the License, or (at your option) any later
|
||||
* version.
|
||||
*
|
||||
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License along
|
||||
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package org.bigbluebutton.conference.service.video;
|
||||
|
||||
import org.bigbluebutton.conference.BigBlueButtonSession;
|
||||
import org.bigbluebutton.conference.Constants;
|
||||
import org.bigbluebutton.core.api.IBigBlueButtonInGW;
|
||||
import org.red5.logging.Red5LoggerFactory;
|
||||
import org.red5.server.api.Red5;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
public class VideoApplication {
|
||||
|
||||
private static Logger log = Red5LoggerFactory.getLogger(VideoService.class, "bigbluebutton");
|
||||
|
||||
private IBigBlueButtonInGW bbbInGW;
|
||||
private String defaultStreampath;
|
||||
|
||||
public void getStreamPath(String streamName) {
|
||||
String meetingId = getBbbSession().getRoom();
|
||||
String userId = getBbbSession().getInternalUserID();
|
||||
bbbInGW.getStreamPath(meetingId, userId, streamName, defaultStreampath);
|
||||
}
|
||||
|
||||
private BigBlueButtonSession getBbbSession() {
|
||||
return (BigBlueButtonSession) Red5.getConnectionLocal().getAttribute(Constants.SESSION);
|
||||
}
|
||||
|
||||
public void setBigBlueButtonInGW(IBigBlueButtonInGW inGW) {
|
||||
bbbInGW = inGW;
|
||||
}
|
||||
|
||||
public void setDefaultStreamPath(String path) {
|
||||
this.defaultStreampath = path;
|
||||
}
|
||||
}
|
@ -0,0 +1,106 @@
|
||||
/**
|
||||
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
|
||||
*
|
||||
* Copyright (c) 2014 BigBlueButton Inc. and by respective authors (see below).
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the
|
||||
* terms of the GNU Lesser General Public License as published by the Free Software
|
||||
* Foundation; either version 3.0 of the License, or (at your option) any later
|
||||
* version.
|
||||
*
|
||||
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License along
|
||||
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package org.bigbluebutton.conference.service.video;
|
||||
|
||||
import org.red5.logging.Red5LoggerFactory;
|
||||
import org.red5.server.adapter.ApplicationAdapter;
|
||||
import org.red5.server.adapter.IApplication;
|
||||
import org.red5.server.api.IClient;
|
||||
import org.red5.server.api.IConnection;
|
||||
import org.red5.server.api.scope.IScope;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
public class VideoHandler extends ApplicationAdapter implements IApplication {
|
||||
|
||||
private static Logger log = Red5LoggerFactory.getLogger(VideoService.class, "bigbluebutton");
|
||||
|
||||
private static final String APP = "VIDEO";
|
||||
private VideoApplication videoApplication;
|
||||
|
||||
@Override
|
||||
public boolean appConnect(IConnection conn, Object[] params) {
|
||||
log.debug("***** " + APP + " [ " + " appConnect *********");
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appDisconnect(IConnection conn) {
|
||||
log.debug("***** " + APP + " [ " + " appDisconnect *********");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean appJoin(IClient client, IScope scope) {
|
||||
log.debug("***** " + APP + " [ " + " appJoin [ " + scope.getName() + "] *********");
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appLeave(IClient client, IScope scope) {
|
||||
log.debug("***** " + APP + " [ " + " appLeave [ " + scope.getName() + "] *********");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean appStart(IScope scope) {
|
||||
log.debug("***** " + APP + " [ " + " appStart [ " + scope.getName() + "] *********");
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appStop(IScope scope) {
|
||||
log.debug("***** " + APP + " [ " + " appStop [ " + scope.getName() + "] *********");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void roomDisconnect(IConnection connection) {
|
||||
log.debug("***** " + APP + " [ " + " roomDisconnect [ " + connection.getScope().getName() + "] *********");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean roomJoin(IClient client, IScope scope) {
|
||||
log.debug("***** " + APP + " [ " + " roomJoin [ " + scope.getName() + "] *********");
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void roomLeave(IClient client, IScope scope) {
|
||||
log.debug("***** " + APP + " [ " + " roomLeave [ " + scope.getName() + "] *********");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean roomConnect(IConnection connection, Object[] params) {
|
||||
log.debug("***** " + APP + " [ " + " roomConnect [ " + connection.getScope().getName() + "] *********");
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean roomStart(IScope scope) {
|
||||
log.debug("***** " + APP + " [ " + " roomStart [ " + scope.getName() + "] *********");
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void roomStop(IScope scope) {
|
||||
log.debug("***** " + APP + " [ " + " roomStop [ " + scope.getName() + "] *********");
|
||||
}
|
||||
|
||||
public void setVideoApplication(VideoApplication a) {
|
||||
log.debug("****** Setting video application ********");
|
||||
videoApplication = a;
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
/**
|
||||
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
|
||||
*
|
||||
* Copyright (c) 2014 BigBlueButton Inc. and by respective authors (see below).
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the
|
||||
* terms of the GNU Lesser General Public License as published by the Free Software
|
||||
* Foundation; either version 3.0 of the License, or (at your option) any later
|
||||
* version.
|
||||
*
|
||||
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License along
|
||||
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package org.bigbluebutton.conference.service.video;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.red5.logging.Red5LoggerFactory;
|
||||
|
||||
public class VideoService {
|
||||
|
||||
private static Logger log = Red5LoggerFactory.getLogger(VideoService.class, "bigbluebutton");
|
||||
|
||||
private VideoApplication videoApplication;
|
||||
|
||||
public void getStreamPath(String streamName) {
|
||||
log.debug("Stream Path requested for [{}]", streamName);
|
||||
videoApplication.getStreamPath(streamName);
|
||||
}
|
||||
|
||||
public void setVideoApplication(VideoApplication a) {
|
||||
log.debug("Setting video application");
|
||||
this.videoApplication = a;
|
||||
}
|
||||
}
|
@ -27,12 +27,11 @@ public interface IBigBlueButtonInGW {
|
||||
|
||||
// Users
|
||||
void validateAuthToken(String meetingId, String userId, String token, String correlationId, String sessionId);
|
||||
void registerUser(String roomName, String userid, String username, String role, String externUserID, String authToken);
|
||||
void userRaiseHand(String meetingId, String userId);
|
||||
void lowerHand(String meetingId, String userId, String loweredBy);
|
||||
void registerUser(String roomName, String userid, String username, String role, String externUserID, String authToken, Boolean guest);
|
||||
void shareWebcam(String meetingId, String userId, String stream);
|
||||
void unshareWebcam(String meetingId, String userId, String stream);
|
||||
void setUserStatus(String meetingID, String userID, String status, Object value);
|
||||
void setUserRole(String meetingID, String userID, String role);
|
||||
void getUsers(String meetingID, String requesterID);
|
||||
void userLeft(String meetingID, String userID, String sessionId);
|
||||
void userJoin(String meetingID, String userID, String authToken);
|
||||
@ -42,6 +41,9 @@ public interface IBigBlueButtonInGW {
|
||||
void getRecordingStatus(String meetingId, String userId);
|
||||
void userConnectedToGlobalAudio(String voiceConf, String userid, String name);
|
||||
void userDisconnectedFromGlobalAudio(String voiceConf, String userid, String name);
|
||||
void getGuestPolicy(String meetingID, String userID);
|
||||
void setGuestPolicy(String meetingID, String guestPolicy, String setBy);
|
||||
void responseToGuest(String meetingID, String userID, Boolean response, String requesterID);
|
||||
|
||||
// Voice
|
||||
void initAudioSettings(String meetingID, String requesterID, Boolean muted);
|
||||
@ -84,7 +86,7 @@ public interface IBigBlueButtonInGW {
|
||||
int pagesCompleted, String presName);
|
||||
|
||||
void sendConversionCompleted(String messageKey, String meetingId,
|
||||
String code, String presId, int numPages, String presName, String presBaseUrl);
|
||||
String code, String presId, int numPages, String presName, String presBaseUrl, boolean presDownloadable);
|
||||
|
||||
// Polling
|
||||
void getPolls(String meetingID, String requesterID);
|
||||
@ -116,4 +118,17 @@ public interface IBigBlueButtonInGW {
|
||||
void enableWhiteboard(String meetingID, String requesterID, Boolean enable);
|
||||
void isWhiteboardEnabled(String meetingID, String requesterID, String replyTo);
|
||||
|
||||
// Shared notes
|
||||
void patchDocument(String meetingID, String requesterID, String noteID,
|
||||
String patch, int beginIndex, int endIndex);
|
||||
void getCurrentDocument(String meetingID, String requesterID);
|
||||
void createAdditionalNotes(String meetingID, String requesterID);
|
||||
void destroyAdditionalNotes(String meetingID, String requesterID,
|
||||
String noteID);
|
||||
void requestAdditionalNotesSet(String meetingID, String requesterID,
|
||||
int additionalNotesSetSize);
|
||||
|
||||
// Video
|
||||
void getStreamPath(String meetingID, String requesterID, String streamName, String defaultPath);
|
||||
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import org.bigbluebutton.core.apps.poll.PollInGateway
|
||||
import org.bigbluebutton.core.apps.layout.LayoutInGateway
|
||||
import org.bigbluebutton.core.apps.chat.ChatInGateway
|
||||
import scala.collection.JavaConversions._
|
||||
import org.bigbluebutton.core.apps.sharednotes.SharedNotesInGateway
|
||||
import org.bigbluebutton.core.apps.whiteboard.WhiteboardInGateway
|
||||
import org.bigbluebutton.core.apps.voice.VoiceInGateway
|
||||
import java.util.ArrayList
|
||||
@ -68,9 +69,9 @@ class BigBlueButtonInGW(bbbGW: BigBlueButtonGateway, presUtil: PreuploadedPresen
|
||||
bbbGW.accept(new ValidateAuthToken(meetingId, userId, token, correlationId, sessionId))
|
||||
}
|
||||
|
||||
def registerUser(meetingID: String, userID: String, name: String, role: String, extUserID: String, authToken: String):Unit = {
|
||||
def registerUser(meetingID: String, userID: String, name: String, role: String, extUserID: String, authToken: String, guest: java.lang.Boolean):Unit = {
|
||||
val userRole = if (role == "MODERATOR") Role.MODERATOR else Role.VIEWER
|
||||
bbbGW.accept(new RegisterUser(meetingID, userID, name, userRole, extUserID, authToken))
|
||||
bbbGW.accept(new RegisterUser(meetingID, userID, name, userRole, extUserID, authToken, guest))
|
||||
}
|
||||
|
||||
def sendLockSettings(meetingID: String, userId: String, settings: java.util.Map[String, java.lang.Boolean]) {
|
||||
@ -170,6 +171,11 @@ class BigBlueButtonInGW(bbbGW: BigBlueButtonGateway, presUtil: PreuploadedPresen
|
||||
bbbGW.accept(new ChangeUserStatus(meetingID, userID, status, value));
|
||||
}
|
||||
|
||||
def setUserRole(meetingID: String, userID: String, role: String) {
|
||||
val userRole = if (role == "MODERATOR") Role.MODERATOR else Role.VIEWER
|
||||
bbbGW.accept(new ChangeUserRole(meetingID, userID, userRole));
|
||||
}
|
||||
|
||||
def getUsers(meetingID: String, requesterID: String):Unit = {
|
||||
bbbGW.accept(new GetUsers(meetingID, requesterID))
|
||||
}
|
||||
@ -202,6 +208,26 @@ class BigBlueButtonInGW(bbbGW: BigBlueButtonGateway, presUtil: PreuploadedPresen
|
||||
bbbGW.accept(new UserDisconnectedFromGlobalAudio(voiceConf, voiceConf, userid, name))
|
||||
}
|
||||
|
||||
// Guest support
|
||||
def getGuestPolicy(meetingID: String, requesterID: String) {
|
||||
bbbGW.accept(new GetGuestPolicy(meetingID, requesterID))
|
||||
}
|
||||
|
||||
def setGuestPolicy(meetingID: String, guestPolicy: String, setBy: String) {
|
||||
val policy = guestPolicy.toUpperCase() match {
|
||||
case "ALWAYS_ACCEPT" => GuestPolicy.ALWAYS_ACCEPT
|
||||
case "ALWAYS_DENY" => GuestPolicy.ALWAYS_DENY
|
||||
case "ASK_MODERATOR" => GuestPolicy.ASK_MODERATOR
|
||||
//default
|
||||
case undef => GuestPolicy.ASK_MODERATOR
|
||||
}
|
||||
bbbGW.accept(new SetGuestPolicy(meetingID, policy, setBy))
|
||||
}
|
||||
|
||||
def responseToGuest(meetingID: String, userId: String, response: java.lang.Boolean, requesterID: String) {
|
||||
bbbGW.accept(new RespondToGuest(meetingID, userId, response, requesterID))
|
||||
}
|
||||
|
||||
/**************************************************************************************
|
||||
* Message Interface for Presentation
|
||||
**************************************************************************************/
|
||||
@ -254,11 +280,11 @@ class BigBlueButtonInGW(bbbGW: BigBlueButtonGateway, presUtil: PreuploadedPresen
|
||||
|
||||
def sendConversionCompleted(messageKey: String, meetingId: String,
|
||||
code: String, presentationId: String, numPages: Int,
|
||||
presName: String, presBaseUrl: String) {
|
||||
presName: String, presBaseUrl: String, presDownloadable: Boolean) {
|
||||
// println("******************** PRESENTATION CONVERSION COMPLETED MESSAGE ***************************** ")
|
||||
val pages = generatePresentationPages(presentationId, numPages, presBaseUrl)
|
||||
|
||||
val presentation = new Presentation(id=presentationId, name=presName, pages=pages)
|
||||
val presentation = new Presentation(id=presentationId, name=presName, pages=pages, downloadable=presDownloadable)
|
||||
bbbGW.accept(new PresentationConversionCompleted(meetingId, messageKey,
|
||||
code, presentation))
|
||||
|
||||
@ -465,4 +491,32 @@ class BigBlueButtonInGW(bbbGW: BigBlueButtonGateway, presUtil: PreuploadedPresen
|
||||
voiceGW.voiceRecording(meetingId, recordingFile,
|
||||
timestamp, recording)
|
||||
}
|
||||
|
||||
val sharedNotesGW = new SharedNotesInGateway(bbbGW)
|
||||
|
||||
def patchDocument(meetingId: String, userId: String, noteId: String,
|
||||
patch: String, beginIndex: Int, endIndex: Int) {
|
||||
sharedNotesGW.patchDocument(meetingId, userId, noteId, patch, beginIndex, endIndex)
|
||||
}
|
||||
|
||||
def getCurrentDocument(meetingId: String, userId: String) {
|
||||
sharedNotesGW.getCurrentDocument(meetingId, userId)
|
||||
}
|
||||
|
||||
def createAdditionalNotes(meetingId: String, userId: String) {
|
||||
sharedNotesGW.createAdditionalNotes(meetingId, userId)
|
||||
}
|
||||
def destroyAdditionalNotes(meetingId: String, userId: String, noteId: String) {
|
||||
sharedNotesGW.destroyAdditionalNotes(meetingId, userId, noteId)
|
||||
}
|
||||
def requestAdditionalNotesSet(meetingId: String, userId: String, additionalNotesSetSize: Int) {
|
||||
sharedNotesGW.requestAdditionalNotesSet(meetingId, userId, additionalNotesSetSize)
|
||||
}
|
||||
|
||||
/*********************************************************************
|
||||
* Message Interface for Video
|
||||
*******************************************************************/
|
||||
def getStreamPath(meetingId:String, requesterId:String, streamName: String, defaultPath:String) {
|
||||
bbbGW.accept(new GetStreamPath(meetingId, requesterId, streamName, defaultPath));
|
||||
}
|
||||
}
|
||||
|
@ -39,11 +39,10 @@ class CollectorActor(dispatcher: IDispatcher) extends Actor {
|
||||
case msg: UserJoining => handleUserJoining(msg)
|
||||
case msg: UserLeaving => handleUserLeaving(msg)
|
||||
case msg: GetUsers => handleGetUsers(msg)
|
||||
case msg: UserRaiseHand => handleUserRaiseHand(msg)
|
||||
case msg: UserLowerHand => handleUserLowerHand(msg)
|
||||
case msg: UserShareWebcam => handleUserShareWebcam(msg)
|
||||
case msg: UserUnshareWebcam => handleUserUnshareWebcam(msg)
|
||||
case msg: ChangeUserStatus => handleChangeUserStatus(msg)
|
||||
case msg: ChangeUserRole => handleChangeUserRole(msg)
|
||||
case msg: AssignPresenter => handleAssignPresenter(msg)
|
||||
case msg: SetRecordingStatus => handleSetRecordingStatus(msg)
|
||||
case msg: GetChatHistoryRequest => handleGetChatHistoryRequest(msg)
|
||||
@ -97,6 +96,10 @@ class CollectorActor(dispatcher: IDispatcher) extends Actor {
|
||||
case msg: UndoWhiteboardRequest => handleUndoWhiteboardRequest(msg)
|
||||
case msg: EnableWhiteboardRequest => handleEnableWhiteboardRequest(msg)
|
||||
case msg: IsWhiteboardEnabledRequest => handleIsWhiteboardEnabledRequest(msg)
|
||||
case msg: GetStreamPath => handleGetStreamPath(msg)
|
||||
case msg: GetGuestPolicy => handleGetGuestPolicy(msg)
|
||||
case msg: SetGuestPolicy => handleSetGuestPolicy(msg)
|
||||
case msg: RespondToGuest => handleRespondToGuest(msg)
|
||||
case msg: GetAllMeetingsRequest => handleGetAllMeetingsRequest(msg)
|
||||
|
||||
//OUT MESSAGES
|
||||
@ -121,11 +124,11 @@ class CollectorActor(dispatcher: IDispatcher) extends Actor {
|
||||
case msg: GetUsersReply => handleGetUsersReply(msg)
|
||||
case msg: ValidateAuthTokenReply => handleValidateAuthTokenReply(msg)
|
||||
case msg: UserJoined => handleUserJoined(msg)
|
||||
case msg: UserRaisedHand => handleUserRaisedHand(msg)
|
||||
case msg: UserLoweredHand => handleUserLoweredHand(msg)
|
||||
case msg: UserListeningOnly => handleUserListeningOnly(msg)
|
||||
case msg: UserSharedWebcam => handleUserSharedWebcam(msg)
|
||||
case msg: UserUnsharedWebcam => handleUserUnsharedWebcam(msg)
|
||||
case msg: UserStatusChange => handleUserStatusChange(msg)
|
||||
case msg: UserRoleChange => handleUserRoleChange(msg)
|
||||
case msg: MuteVoiceUser => handleMuteVoiceUser(msg)
|
||||
case msg: UserVoiceMuted => handleUserVoiceMuted(msg)
|
||||
case msg: UserVoiceTalking => handleUserVoiceTalking(msg)
|
||||
@ -175,6 +178,9 @@ class CollectorActor(dispatcher: IDispatcher) extends Actor {
|
||||
case msg: UndoWhiteboardEvent => handleUndoWhiteboardEvent(msg)
|
||||
case msg: WhiteboardEnabledEvent => handleWhiteboardEnabledEvent(msg)
|
||||
case msg: IsWhiteboardEnabledReply => handleIsWhiteboardEnabledReply(msg)
|
||||
case msg: GetGuestPolicyReply => handleGetGuestPolicyReply(msg)
|
||||
case msg: GuestPolicyChanged => handleGuestPolicyChanged(msg)
|
||||
case msg: GuestAccessDenied => handleGuestAccessDenied(msg)
|
||||
case msg: GetAllMeetingsReply => handleGetAllMeetingsReply(msg)
|
||||
|
||||
case _ => // do nothing
|
||||
@ -199,12 +205,14 @@ class CollectorActor(dispatcher: IDispatcher) extends Actor {
|
||||
wuser.put(Constants.EXT_USER_ID, user.externUserID)
|
||||
wuser.put(Constants.NAME, user.name)
|
||||
wuser.put(Constants.ROLE, user.role.toString())
|
||||
wuser.put(Constants.RAISE_HAND, user.raiseHand:java.lang.Boolean)
|
||||
wuser.put(Constants.MOOD, user.mood:java.lang.String)
|
||||
wuser.put(Constants.PRESENTER, user.presenter:java.lang.Boolean)
|
||||
wuser.put(Constants.HAS_STREAM, user.hasStream:java.lang.Boolean)
|
||||
wuser.put(Constants.LOCKED, user.locked:java.lang.Boolean)
|
||||
wuser.put("webcamStream", user.webcamStreams mkString("|"))
|
||||
wuser.put(Constants.WEBCAM_STREAM, user.webcamStreams)
|
||||
wuser.put(Constants.PHONE_USER, user.phoneUser:java.lang.Boolean)
|
||||
wuser.put(Constants.GUEST, user.guest:java.lang.Boolean)
|
||||
wuser.put(Constants.WAITING_FOR_ACCEPTANCE, user.waitingForAcceptance:java.lang.Boolean)
|
||||
wuser.put(Constants.VOICE_USER, vuser)
|
||||
|
||||
wuser
|
||||
@ -412,6 +420,7 @@ class CollectorActor(dispatcher: IDispatcher) extends Actor {
|
||||
payload.put(Constants.NAME, msg.name)
|
||||
payload.put(Constants.ROLE, msg.role.toString())
|
||||
payload.put(Constants.EXT_USER_ID, msg.extUserID)
|
||||
payload.put(Constants.GUEST, msg.guest.toString())
|
||||
|
||||
val header = new java.util.HashMap[String, Any]()
|
||||
header.put(Constants.NAME, MessageNames.REGISTER_USER)
|
||||
@ -470,35 +479,6 @@ class CollectorActor(dispatcher: IDispatcher) extends Actor {
|
||||
dispatcher.dispatch(buildJson(header, payload))
|
||||
}
|
||||
|
||||
private def handleUserRaiseHand(msg: UserRaiseHand) {
|
||||
val payload = new java.util.HashMap[String, Any]()
|
||||
payload.put(Constants.MEETING_ID, msg.meetingID)
|
||||
payload.put(Constants.USER_ID, msg.userId)
|
||||
|
||||
val header = new java.util.HashMap[String, Any]()
|
||||
header.put(Constants.NAME, MessageNames.RAISE_HAND)
|
||||
header.put(Constants.TIMESTAMP, TimestampGenerator.generateTimestamp)
|
||||
header.put(Constants.CURRENT_TIME, TimestampGenerator.getCurrentTime)
|
||||
|
||||
// println("***** DISPATCHING USER RAISE HAND *****************")
|
||||
dispatcher.dispatch(buildJson(header, payload))
|
||||
}
|
||||
|
||||
private def handleUserLowerHand(msg: UserLowerHand) {
|
||||
val payload = new java.util.HashMap[String, Any]()
|
||||
payload.put(Constants.MEETING_ID, msg.meetingID)
|
||||
payload.put(Constants.USER_ID, msg.userId)
|
||||
payload.put(Constants.LOWERED_BY, msg.loweredBy)
|
||||
|
||||
val header = new java.util.HashMap[String, Any]()
|
||||
header.put(Constants.NAME, MessageNames.LOWER_HAND)
|
||||
header.put(Constants.TIMESTAMP, TimestampGenerator.generateTimestamp)
|
||||
header.put(Constants.CURRENT_TIME, TimestampGenerator.getCurrentTime)
|
||||
|
||||
// println("***** DISPATCHING USER LOWER HAND *****************")
|
||||
dispatcher.dispatch(buildJson(header, payload))
|
||||
}
|
||||
|
||||
private def handleUserShareWebcam(msg: UserShareWebcam) {
|
||||
val payload = new java.util.HashMap[String, Any]()
|
||||
payload.put(Constants.MEETING_ID, msg.meetingID)
|
||||
@ -544,6 +524,21 @@ class CollectorActor(dispatcher: IDispatcher) extends Actor {
|
||||
dispatcher.dispatch(buildJson(header, payload))
|
||||
}
|
||||
|
||||
private def handleChangeUserRole(msg: ChangeUserRole) {
|
||||
val payload = new java.util.HashMap[String, Any]()
|
||||
payload.put(Constants.MEETING_ID, msg.meetingID)
|
||||
payload.put(Constants.USER_ID, msg.userID)
|
||||
payload.put(Constants.ROLE, msg.role)
|
||||
|
||||
val header = new java.util.HashMap[String, Any]()
|
||||
header.put(Constants.NAME, MessageNames.CHANGE_USER_ROLE)
|
||||
header.put(Constants.TIMESTAMP, TimestampGenerator.generateTimestamp)
|
||||
header.put(Constants.CURRENT_TIME, TimestampGenerator.getCurrentTime)
|
||||
|
||||
// println("***** DISPATCHING CHANGE USER ROLE *****************")
|
||||
dispatcher.dispatch(buildJson(header, payload))
|
||||
}
|
||||
|
||||
private def handleAssignPresenter(msg: AssignPresenter) {
|
||||
val payload = new java.util.HashMap[String, Any]()
|
||||
payload.put(Constants.MEETING_ID, msg.meetingID)
|
||||
@ -1586,34 +1581,18 @@ class CollectorActor(dispatcher: IDispatcher) extends Actor {
|
||||
dispatcher.dispatch(json)
|
||||
}
|
||||
|
||||
private def handleUserRaisedHand(msg: UserRaisedHand) {
|
||||
private def handleUserListeningOnly(msg: UserListeningOnly) {
|
||||
val payload = new java.util.HashMap[String, Any]()
|
||||
payload.put(Constants.MEETING_ID, msg.meetingID)
|
||||
payload.put(Constants.RAISE_HAND, msg.recorded)
|
||||
payload.put(Constants.USER_ID, msg.userID)
|
||||
payload.put(Constants.LISTEN_ONLY, msg.listenOnly)
|
||||
|
||||
val header = new java.util.HashMap[String, Any]()
|
||||
header.put(Constants.NAME, MessageNames.USER_RAISED_HAND)
|
||||
header.put(Constants.NAME, MessageNames.USER_LISTEN_ONLY)
|
||||
header.put(Constants.TIMESTAMP, TimestampGenerator.generateTimestamp)
|
||||
header.put(Constants.CURRENT_TIME, TimestampGenerator.getCurrentTime)
|
||||
|
||||
// println("***** DISPATCHING USER RAISED HAND *****************")
|
||||
dispatcher.dispatch(buildJson(header, payload))
|
||||
}
|
||||
|
||||
private def handleUserLoweredHand(msg: UserLoweredHand) {
|
||||
val payload = new java.util.HashMap[String, Any]()
|
||||
payload.put(Constants.MEETING_ID, msg.meetingID)
|
||||
payload.put(Constants.RAISE_HAND, msg.recorded)
|
||||
payload.put(Constants.USER_ID, msg.userID)
|
||||
payload.put(Constants.LOWERED_BY, msg.loweredBy)
|
||||
|
||||
val header = new java.util.HashMap[String, Any]()
|
||||
header.put(Constants.NAME, MessageNames.USER_LOWERED_HAND)
|
||||
header.put(Constants.TIMESTAMP, TimestampGenerator.generateTimestamp)
|
||||
header.put(Constants.CURRENT_TIME, TimestampGenerator.getCurrentTime)
|
||||
|
||||
// println("***** DISPATCHING USER LOWERED HAND *****************")
|
||||
// println("***** DISPATCHING USER LISTENING ONLY *****************")
|
||||
dispatcher.dispatch(buildJson(header, payload))
|
||||
}
|
||||
|
||||
@ -1666,6 +1645,22 @@ class CollectorActor(dispatcher: IDispatcher) extends Actor {
|
||||
dispatcher.dispatch(buildJson(header, payload))
|
||||
}
|
||||
|
||||
private def handleUserRoleChange(msg: UserRoleChange) {
|
||||
val payload = new java.util.HashMap[String, Any]()
|
||||
payload.put(Constants.MEETING_ID, msg.meetingID)
|
||||
payload.put(Constants.RECORDED, msg.recorded)
|
||||
payload.put(Constants.USER_ID, msg.userID)
|
||||
payload.put(Constants.ROLE, msg.role)
|
||||
|
||||
val header = new java.util.HashMap[String, Any]()
|
||||
header.put(Constants.NAME, MessageNames.USER_ROLE_CHANGED)
|
||||
header.put(Constants.TIMESTAMP, TimestampGenerator.generateTimestamp)
|
||||
header.put(Constants.CURRENT_TIME, TimestampGenerator.getCurrentTime)
|
||||
|
||||
// println("***** DISPATCHING USER ROLE CHANGE *****************")
|
||||
dispatcher.dispatch(buildJson(header, payload))
|
||||
}
|
||||
|
||||
private def handleMuteVoiceUser(msg: MuteVoiceUser) {
|
||||
val payload = new java.util.HashMap[String, Any]()
|
||||
payload.put(Constants.MEETING_ID, msg.meetingID)
|
||||
@ -2159,6 +2154,109 @@ class CollectorActor(dispatcher: IDispatcher) extends Actor {
|
||||
val json = WhiteboardMessageToJsonConverter.isWhiteboardEnabledReplyToJson(msg)
|
||||
dispatcher.dispatch(json)
|
||||
}
|
||||
|
||||
private def handleGetStreamPath(msg: GetStreamPath) {
|
||||
val payload = new java.util.HashMap[String, Any]()
|
||||
|
||||
payload.put(Constants.MEETING_ID, msg.meetingID)
|
||||
payload.put(Constants.REQUESTER_ID, msg.requesterID)
|
||||
payload.put(Constants.STREAM, msg.streamName)
|
||||
payload.put(Constants.STREAM_PATH_DEFAULT, msg.streamName)
|
||||
|
||||
val header = new java.util.HashMap[String, Any]()
|
||||
header.put(Constants.NAME, MessageNames.GET_STREAM_PATH)
|
||||
|
||||
println("***** DISPATCHING GET STREAM PATH *****************")
|
||||
dispatcher.dispatch(buildJson(header, payload))
|
||||
}
|
||||
|
||||
private def handleGetGuestPolicy(msg: GetGuestPolicy) {
|
||||
val payload = new java.util.HashMap[String, Any]()
|
||||
payload.put(Constants.MEETING_ID, msg.meetingID)
|
||||
payload.put(Constants.REQUESTER_ID, msg.requesterID)
|
||||
|
||||
val header = new java.util.HashMap[String, Any]()
|
||||
header.put(Constants.NAME, MessageNames.GET_GUEST_POLICY)
|
||||
header.put(Constants.TIMESTAMP, TimestampGenerator.generateTimestamp)
|
||||
header.put(Constants.CURRENT_TIME, TimestampGenerator.getCurrentTime)
|
||||
|
||||
// println("***** DISPATCHING GET GUEST POLICY *****************")
|
||||
dispatcher.dispatch(buildJson(header, payload))
|
||||
}
|
||||
|
||||
private def handleSetGuestPolicy(msg: SetGuestPolicy) {
|
||||
val payload = new java.util.HashMap[String, Any]()
|
||||
payload.put(Constants.MEETING_ID, msg.meetingID)
|
||||
payload.put(Constants.GUEST_POLICY, msg.policy.toString())
|
||||
|
||||
val header = new java.util.HashMap[String, Any]()
|
||||
header.put(Constants.NAME, MessageNames.SET_GUEST_POLICY)
|
||||
header.put(Constants.TIMESTAMP, TimestampGenerator.generateTimestamp)
|
||||
header.put(Constants.CURRENT_TIME, TimestampGenerator.getCurrentTime)
|
||||
|
||||
// println("***** DISPATCHING SET GUEST POLICY *****************")
|
||||
dispatcher.dispatch(buildJson(header, payload))
|
||||
}
|
||||
|
||||
private def handleRespondToGuest(msg: RespondToGuest) {
|
||||
val payload = new java.util.HashMap[String, Any]()
|
||||
payload.put(Constants.MEETING_ID, msg.meetingID)
|
||||
payload.put(Constants.USER_ID, msg.userId)
|
||||
payload.put(Constants.RESPONSE, msg.response.toString())
|
||||
payload.put(Constants.REQUESTER_ID, msg.requesterID)
|
||||
|
||||
val header = new java.util.HashMap[String, Any]()
|
||||
header.put(Constants.NAME, MessageNames.RESPOND_TO_GUEST)
|
||||
header.put(Constants.TIMESTAMP, TimestampGenerator.generateTimestamp)
|
||||
header.put(Constants.CURRENT_TIME, TimestampGenerator.getCurrentTime)
|
||||
|
||||
// println("***** DISPATCHING RESPOND TO GUEST *****************")
|
||||
dispatcher.dispatch(buildJson(header, payload))
|
||||
}
|
||||
|
||||
private def handleGetGuestPolicyReply(msg: GetGuestPolicyReply) {
|
||||
val payload = new java.util.HashMap[String, Any]()
|
||||
payload.put(Constants.MEETING_ID, msg.meetingID)
|
||||
payload.put(Constants.REQUESTER_ID, msg.requesterID)
|
||||
payload.put(Constants.GUEST_POLICY, msg.policy)
|
||||
|
||||
val header = new java.util.HashMap[String, Any]()
|
||||
header.put(Constants.NAME, MessageNames.GET_GUEST_POLICY_REPLY)
|
||||
header.put(Constants.TIMESTAMP, TimestampGenerator.generateTimestamp)
|
||||
header.put(Constants.CURRENT_TIME, TimestampGenerator.getCurrentTime)
|
||||
|
||||
// println("***** DISPATCHING GET GUEST POLICY REPLY *****************")
|
||||
dispatcher.dispatch(buildJson(header, payload))
|
||||
}
|
||||
|
||||
private def handleGuestPolicyChanged(msg: GuestPolicyChanged) {
|
||||
val payload = new java.util.HashMap[String, Any]()
|
||||
payload.put(Constants.MEETING_ID, msg.meetingID)
|
||||
payload.put(Constants.GUEST_POLICY, msg.policy)
|
||||
|
||||
val header = new java.util.HashMap[String, Any]()
|
||||
header.put(Constants.NAME, MessageNames.GUEST_POLICY_CHANGED)
|
||||
header.put(Constants.TIMESTAMP, TimestampGenerator.generateTimestamp)
|
||||
header.put(Constants.CURRENT_TIME, TimestampGenerator.getCurrentTime)
|
||||
|
||||
// println("***** DISPATCHING GUEST POLICY CHANGED *****************")
|
||||
dispatcher.dispatch(buildJson(header, payload))
|
||||
}
|
||||
|
||||
private def handleGuestAccessDenied(msg: GuestAccessDenied) {
|
||||
val payload = new java.util.HashMap[String, Any]()
|
||||
payload.put(Constants.MEETING_ID, msg.meetingID)
|
||||
payload.put(Constants.USER_ID, msg.userId)
|
||||
|
||||
val header = new java.util.HashMap[String, Any]()
|
||||
header.put(Constants.NAME, MessageNames.GUEST_ACCESS_DENIED)
|
||||
header.put(Constants.TIMESTAMP, TimestampGenerator.generateTimestamp)
|
||||
header.put(Constants.CURRENT_TIME, TimestampGenerator.getCurrentTime)
|
||||
|
||||
// println("***** DISPATCHING RESPONSE TO GUEST *****************")
|
||||
dispatcher.dispatch(buildJson(header, payload))
|
||||
}
|
||||
|
||||
private def handleGetAllMeetingsReply(msg: GetAllMeetingsReply) {
|
||||
val json = MeetingMessageToJsonConverter.getAllMeetingsReplyToJson(msg)
|
||||
println("***** DISPATCHING GET ALL MEETINGS REPLY OUTMSG *****************")
|
||||
|
@ -4,12 +4,14 @@ import scala.actors.Actor
|
||||
import scala.actors.Actor._
|
||||
import org.bigbluebutton.core.apps.poll.Poll
|
||||
import org.bigbluebutton.core.apps.poll.PollApp
|
||||
import org.bigbluebutton.core.apps.sharednotes.SharedNotesApp
|
||||
import org.bigbluebutton.core.apps.users.UsersApp
|
||||
import org.bigbluebutton.core.api._
|
||||
import org.bigbluebutton.core.apps.presentation.PresentationApp
|
||||
import org.bigbluebutton.core.apps.layout.LayoutApp
|
||||
import org.bigbluebutton.core.apps.chat.ChatApp
|
||||
import org.bigbluebutton.core.apps.whiteboard.WhiteboardApp
|
||||
import org.bigbluebutton.core.apps.video.VideoApp
|
||||
import scala.actors.TIMEOUT
|
||||
import java.util.concurrent.TimeUnit
|
||||
import org.bigbluebutton.core.util._
|
||||
@ -24,7 +26,7 @@ class MeetingActor(val meetingID: String, val externalMeetingID: String, val mee
|
||||
val outGW: MessageOutGateway)
|
||||
extends Actor with UsersApp with PresentationApp
|
||||
with PollApp with LayoutApp with ChatApp
|
||||
with WhiteboardApp with LogHelper {
|
||||
with WhiteboardApp with LogHelper with SharedNotesApp with VideoApp {
|
||||
|
||||
var audioSettingsInited = false
|
||||
var permissionsInited = false
|
||||
@ -33,6 +35,9 @@ class MeetingActor(val meetingID: String, val externalMeetingID: String, val mee
|
||||
var muted = false;
|
||||
var meetingEnded = false
|
||||
|
||||
var guestPolicy = GuestPolicy.ASK_MODERATOR
|
||||
var guestPolicySetBy:String = null
|
||||
|
||||
def getDuration():Long = {
|
||||
duration
|
||||
}
|
||||
@ -78,9 +83,8 @@ class MeetingActor(val meetingID: String, val externalMeetingID: String, val mee
|
||||
case msg: AssignPresenter => handleAssignPresenter(msg)
|
||||
case msg: GetUsers => handleGetUsers(msg)
|
||||
case msg: ChangeUserStatus => handleChangeUserStatus(msg)
|
||||
case msg: ChangeUserRole => handleChangeUserRole(msg)
|
||||
case msg: EjectUserFromMeeting => handleEjectUserFromMeeting(msg)
|
||||
case msg: UserRaiseHand => handleUserRaiseHand(msg)
|
||||
case msg: UserLowerHand => handleUserLowerHand(msg)
|
||||
case msg: UserShareWebcam => handleUserShareWebcam(msg)
|
||||
case msg: UserUnshareWebcam => handleUserunshareWebcam(msg)
|
||||
case msg: MuteMeetingRequest => handleMuteMeetingRequest(msg)
|
||||
@ -136,6 +140,16 @@ class MeetingActor(val meetingID: String, val externalMeetingID: String, val mee
|
||||
case msg: SetRecordingStatus => handleSetRecordingStatus(msg)
|
||||
case msg: GetRecordingStatus => handleGetRecordingStatus(msg)
|
||||
case msg: VoiceRecording => handleVoiceRecording(msg)
|
||||
case msg: GetStreamPath => handleGetStreamPath(msg)
|
||||
case msg: GetGuestPolicy => handleGetGuestPolicy(msg)
|
||||
case msg: SetGuestPolicy => handleSetGuestPolicy(msg)
|
||||
case msg: RespondToGuest => handleRespondToGuest(msg)
|
||||
|
||||
case msg: PatchDocumentRequest => handlePatchDocumentRequest(msg)
|
||||
case msg: GetCurrentDocumentRequest => handleGetCurrentDocumentRequest(msg)
|
||||
case msg: CreateAdditionalNotesRequest => handleCreateAdditionalNotesRequest(msg)
|
||||
case msg: DestroyAdditionalNotesRequest => handleDestroyAdditionalNotesRequest(msg)
|
||||
case msg: RequestAdditionalNotesSetRequest => handleRequestAdditionalNotesSetRequest(msg)
|
||||
|
||||
case msg: EndMeeting => handleEndMeeting(msg)
|
||||
case StopMeetingActor => exit
|
||||
@ -248,6 +262,16 @@ class MeetingActor(val meetingID: String, val externalMeetingID: String, val mee
|
||||
outGW.send(new GetRecordingStatusReply(meetingID, recorded, msg.userId, recording.booleanValue()))
|
||||
}
|
||||
|
||||
private def handleGetGuestPolicy(msg: GetGuestPolicy) {
|
||||
outGW.send(new GetGuestPolicyReply(msg.meetingID, recorded, msg.requesterID, guestPolicy.toString()))
|
||||
}
|
||||
|
||||
private def handleSetGuestPolicy(msg: SetGuestPolicy) {
|
||||
guestPolicy = msg.policy
|
||||
guestPolicySetBy = msg.setBy
|
||||
outGW.send(new GuestPolicyChanged(msg.meetingID, recorded, guestPolicy.toString()))
|
||||
}
|
||||
|
||||
def lockLayout(lock: Boolean) {
|
||||
permissions = permissions.copy(lockedLayout=lock)
|
||||
}
|
||||
|
@ -37,6 +37,7 @@ object Constants {
|
||||
val FORCE = "force"
|
||||
val RESPONSE = "response"
|
||||
val PRESENTATION_ID = "presentation_id"
|
||||
val DOWNLOADABLE = "downloadable"
|
||||
val X_OFFSET = "x_offset"
|
||||
val Y_OFFSET = "y_offset"
|
||||
val WIDTH_RATIO = "width_ratio"
|
||||
@ -63,7 +64,7 @@ object Constants {
|
||||
val ENABLE = "enable"
|
||||
val PRESENTER = "presenter"
|
||||
val USERS = "users"
|
||||
val RAISE_HAND = "raise_hand"
|
||||
val MOOD = "mood"
|
||||
val HAS_STREAM = "has_stream"
|
||||
val WEBCAM_STREAM = "webcam_stream"
|
||||
val PHONE_USER = "phone_user"
|
||||
@ -93,4 +94,10 @@ object Constants {
|
||||
val VIEWER_PASS = "viewer_pass"
|
||||
val CREATE_TIME = "create_time"
|
||||
val CREATE_DATE = "create_date"
|
||||
val STREAM_PATH = "stream_path"
|
||||
val STREAM_PATH_DEFAULT = "stream_path_default"
|
||||
val GUEST = "guest"
|
||||
val WAITING_FOR_ACCEPTANCE = "waiting_for_acceptance"
|
||||
val GUEST_POLICY = "guest_policy"
|
||||
val GUESTS_WAITING = "guests_waiting"
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package org.bigbluebutton.core.api
|
||||
|
||||
import org.bigbluebutton.core.api.Role._
|
||||
import org.bigbluebutton.core.api.GuestPolicy._
|
||||
import org.bigbluebutton.core.apps.poll._
|
||||
import org.bigbluebutton.core.apps.whiteboard.vo.AnnotationVO
|
||||
import org.bigbluebutton.core.apps.presentation.Presentation
|
||||
@ -85,7 +86,8 @@ case class RegisterUser(
|
||||
name: String,
|
||||
role: Role,
|
||||
extUserID: String,
|
||||
authToken: String
|
||||
authToken: String,
|
||||
guest: Boolean
|
||||
) extends InMessage
|
||||
|
||||
case class UserJoining(
|
||||
@ -137,6 +139,12 @@ case class ChangeUserStatus(
|
||||
value: Object
|
||||
) extends InMessage
|
||||
|
||||
case class ChangeUserRole(
|
||||
meetingID: String,
|
||||
userID: String,
|
||||
role: Role
|
||||
) extends InMessage
|
||||
|
||||
case class AssignPresenter(
|
||||
meetingID: String,
|
||||
newPresenterID: String,
|
||||
@ -188,6 +196,25 @@ case class UserDisconnectedFromGlobalAudio(
|
||||
name: String
|
||||
) extends InMessage
|
||||
|
||||
// Guest support
|
||||
case class GetGuestPolicy(
|
||||
meetingID: String,
|
||||
requesterID: String
|
||||
) extends InMessage
|
||||
|
||||
case class SetGuestPolicy(
|
||||
meetingID: String,
|
||||
policy: GuestPolicy,
|
||||
setBy: String
|
||||
) extends InMessage
|
||||
|
||||
case class RespondToGuest(
|
||||
meetingID: String,
|
||||
userId: String,
|
||||
response: Boolean,
|
||||
requesterID: String
|
||||
) extends InMessage
|
||||
|
||||
// Layout
|
||||
case class GetCurrentLayoutRequest(
|
||||
meetingID: String,
|
||||
@ -512,3 +539,43 @@ case class IsWhiteboardEnabledRequest(
|
||||
case class GetAllMeetingsRequest(
|
||||
meetingID: String /** Not used. Just to satisfy trait **/
|
||||
) extends InMessage
|
||||
|
||||
// Shared notes
|
||||
case class PatchDocumentRequest(
|
||||
meetingID: String,
|
||||
requesterID: String,
|
||||
noteID: String,
|
||||
patch: String,
|
||||
beginIndex: Int,
|
||||
endIndex: Int
|
||||
) extends InMessage
|
||||
|
||||
case class GetCurrentDocumentRequest(
|
||||
meetingID: String,
|
||||
requesterID: String
|
||||
) extends InMessage
|
||||
|
||||
case class CreateAdditionalNotesRequest(
|
||||
meetingID: String,
|
||||
requesterID: String
|
||||
) extends InMessage
|
||||
|
||||
case class DestroyAdditionalNotesRequest(
|
||||
meetingID: String,
|
||||
requesterID: String,
|
||||
noteID: String
|
||||
) extends InMessage
|
||||
|
||||
case class RequestAdditionalNotesSetRequest(
|
||||
meetingID: String,
|
||||
requesterID: String,
|
||||
additionalNotesSetSize: Int
|
||||
) extends InMessage
|
||||
|
||||
// Video
|
||||
case class GetStreamPath(
|
||||
meetingID: String,
|
||||
requesterID: String,
|
||||
streamName: String,
|
||||
defaultPath: String
|
||||
) extends InMessage
|
||||
|
@ -24,6 +24,7 @@ object MessageNames {
|
||||
val USER_SHARE_WEBCAM = "user_share_webcam_request"
|
||||
val USER_UNSHARE_WEBCAM = "user_unshare_webcam_request"
|
||||
val CHANGE_USER_STATUS = "change_user_status_request"
|
||||
val CHANGE_USER_ROLE = "change_user_role_request"
|
||||
val ASSIGN_PRESENTER = "assign_presenter_request"
|
||||
val SET_RECORDING_STATUS = "set_recording_status_request"
|
||||
val GET_CHAT_HISTORY = "get_chat_history_request"
|
||||
@ -79,6 +80,10 @@ object MessageNames {
|
||||
val UNDO_WHITEBOARD = "undo_whiteboard_request"
|
||||
val ENABLE_WHITEBOARD = "enable_whiteboard_request"
|
||||
val IS_WHITEBOARD_ENABLED = "is_whiteboard_enabled_request"
|
||||
val GET_STREAM_PATH = "get_stream_path_request"
|
||||
var GET_GUEST_POLICY = "get_guest_policy"
|
||||
val SET_GUEST_POLICY = "set_guest_policy"
|
||||
val RESPOND_TO_GUEST = "respond_to_guest"
|
||||
val GET_ALL_MEETINGS_REQUEST = "get_all_meetings_request"
|
||||
|
||||
// OUT MESSAGES
|
||||
@ -109,6 +114,7 @@ object MessageNames {
|
||||
val USER_SHARED_WEBCAM = "user_shared_webcam_message"
|
||||
val USER_UNSHARED_WEBCAM = "user_unshared_webcam_message"
|
||||
val USER_STATUS_CHANGED = "user_status_changed_message"
|
||||
val USER_ROLE_CHANGED = "user_role_changed_message"
|
||||
val MUTE_VOICE_USER = "mute_voice_user_request"
|
||||
val USER_VOICE_MUTED = "user_voice_muted_message"
|
||||
val USER_VOICE_TALKING = "user_voice_talking_message"
|
||||
@ -160,5 +166,9 @@ object MessageNames {
|
||||
val MEETING_DESTROYED_EVENT = "meeting_destroyed_event"
|
||||
val KEEP_ALIVE_REPLY = "keep_alive_reply"
|
||||
val USER_LISTEN_ONLY = "user_listening_only"
|
||||
val GET_STREAM_PATH_REPLY = "get_stream_path_reply"
|
||||
var GET_GUEST_POLICY_REPLY = "get_guest_policy_reply"
|
||||
val GUEST_POLICY_CHANGED = "guest_policy_changed"
|
||||
val GUEST_ACCESS_DENIED = "guest_access_denied"
|
||||
val GET_ALL_MEETINGS_REPLY = "get_all_meetings_reply"
|
||||
}
|
@ -255,6 +255,14 @@ case class UserStatusChange(
|
||||
version:String = Versions.V_0_0_1
|
||||
) extends IOutMessage
|
||||
|
||||
case class UserRoleChange(
|
||||
meetingID: String,
|
||||
recorded: Boolean,
|
||||
userID: String,
|
||||
role: String,
|
||||
version:String = Versions.V_0_0_1
|
||||
) extends IOutMessage
|
||||
|
||||
case class MuteVoiceUser(
|
||||
meetingID: String,
|
||||
recorded: Boolean,
|
||||
@ -648,15 +656,76 @@ case class IsWhiteboardEnabledReply(
|
||||
version:String = Versions.V_0_0_1
|
||||
) extends IOutMessage
|
||||
|
||||
case class GetGuestPolicyReply(
|
||||
meetingID: String,
|
||||
recorded: Boolean,
|
||||
requesterID: String,
|
||||
policy: String
|
||||
) extends IOutMessage
|
||||
|
||||
case class GuestPolicyChanged(
|
||||
meetingID: String,
|
||||
recorded: Boolean,
|
||||
policy: String
|
||||
) extends IOutMessage
|
||||
|
||||
case class GuestAccessDenied(
|
||||
meetingID: String,
|
||||
recorded: Boolean,
|
||||
userId: String
|
||||
) extends IOutMessage
|
||||
|
||||
case class GetAllMeetingsReply(
|
||||
meetings: Array[MeetingInfo],
|
||||
version:String = Versions.V_0_0_1
|
||||
) extends IOutMessage
|
||||
|
||||
case class PatchDocumentReply(
|
||||
meetingID: String,
|
||||
recorded: Boolean,
|
||||
requesterID: String,
|
||||
noteID: String,
|
||||
patch: String,
|
||||
beginIndex: Int,
|
||||
endIndex: Int,
|
||||
version:String = Versions.V_0_0_1
|
||||
) extends IOutMessage
|
||||
|
||||
case class GetCurrentDocumentReply(
|
||||
meetingID: String,
|
||||
recorded: Boolean,
|
||||
requesterID: String,
|
||||
notes: Map[String, String],
|
||||
version:String = Versions.V_0_0_1
|
||||
) extends IOutMessage
|
||||
|
||||
case class CreateAdditionalNotesReply(
|
||||
meetingID: String,
|
||||
recorded: Boolean,
|
||||
requesterID: String,
|
||||
noteID: String,
|
||||
version:String = Versions.V_0_0_1
|
||||
) extends IOutMessage
|
||||
|
||||
case class DestroyAdditionalNotesReply(
|
||||
meetingID: String,
|
||||
recorded: Boolean,
|
||||
requesterID: String,
|
||||
noteID: String,
|
||||
version:String = Versions.V_0_0_1
|
||||
) extends IOutMessage
|
||||
|
||||
// Value Objects
|
||||
case class MeetingVO(
|
||||
id: String,
|
||||
recorded: Boolean
|
||||
)
|
||||
|
||||
// Video
|
||||
case class GetStreamPathReply(
|
||||
meetingID: String,
|
||||
requesterID: String,
|
||||
streamName: String,
|
||||
streamPath: String
|
||||
) extends IOutMessage
|
||||
|
||||
|
@ -8,6 +8,13 @@ object Role extends Enumeration {
|
||||
val VIEWER = Value("VIEWER")
|
||||
}
|
||||
|
||||
object GuestPolicy extends Enumeration {
|
||||
type GuestPolicy = Value
|
||||
val ALWAYS_ACCEPT = Value("ALWAYS_ACCEPT")
|
||||
val ALWAYS_DENY = Value("ALWAYS_DENY")
|
||||
val ASK_MODERATOR = Value("ASK_MODERATOR")
|
||||
}
|
||||
|
||||
case class Presenter(
|
||||
presenterID: String,
|
||||
presenterName: String,
|
||||
@ -48,7 +55,8 @@ case class RegisteredUser (
|
||||
externId: String,
|
||||
name: String,
|
||||
role: Role.Role,
|
||||
authToken: String
|
||||
authToken: String,
|
||||
guest: Boolean
|
||||
)
|
||||
|
||||
case class Voice(
|
||||
@ -67,7 +75,9 @@ case class UserVO(
|
||||
externUserID: String,
|
||||
name: String,
|
||||
role: Role.Role,
|
||||
raiseHand: Boolean,
|
||||
guest: Boolean,
|
||||
waitingForAcceptance: Boolean,
|
||||
mood: String,
|
||||
presenter: Boolean,
|
||||
hasStream: Boolean,
|
||||
locked: Boolean,
|
||||
@ -94,7 +104,8 @@ case class MeetingConfig(name: String,
|
||||
record: Boolean=false,
|
||||
duration: MeetingDuration,
|
||||
defaultAvatarURL: String,
|
||||
defaultConfigToken: String)
|
||||
defaultConfigToken: String,
|
||||
guestPolicy: GuestPolicy.GuestPolicy=GuestPolicy.ASK_MODERATOR)
|
||||
|
||||
case class MeetingName(name: String)
|
||||
|
||||
|
@ -12,7 +12,8 @@ trait LayoutApp {
|
||||
private var setByUser:String = "system";
|
||||
private var currentLayout = "";
|
||||
private var layoutLocked = false
|
||||
private var viewersOnly = true
|
||||
// this is not being set by the client, and we need to apply the layouts to all users, not just viewers, so will keep the default value of this as false
|
||||
private var viewersOnly = false
|
||||
|
||||
def handleGetCurrentLayoutRequest(msg: GetCurrentLayoutRequest) {
|
||||
outGW.send(new GetCurrentLayoutReply(msg.meetingID, recorded, msg.requesterID,
|
||||
|
@ -1,7 +1,8 @@
|
||||
package org.bigbluebutton.core.apps.presentation
|
||||
|
||||
case class Presentation(id: String, name: String, current: Boolean = false,
|
||||
pages: scala.collection.immutable.HashMap[String, Page])
|
||||
pages: scala.collection.immutable.HashMap[String, Page],
|
||||
downloadable: Boolean)
|
||||
|
||||
case class Page(id: String, num: Int,
|
||||
thumbUri: String = "",
|
||||
|
@ -113,6 +113,7 @@ class PresentationClientMessageSender(service: ConnectionInvokerService) extends
|
||||
presentation.put("id", msg.presentation.id)
|
||||
presentation.put("name", msg.presentation.name)
|
||||
presentation.put("current", msg.presentation.current:java.lang.Boolean)
|
||||
presentation.put("downloadable", msg.presentation.downloadable:java.lang.Boolean)
|
||||
|
||||
val pages = new ArrayList[Page]()
|
||||
|
||||
@ -169,6 +170,7 @@ class PresentationClientMessageSender(service: ConnectionInvokerService) extends
|
||||
presentation.put("id", pres.id)
|
||||
presentation.put("name", pres.name)
|
||||
presentation.put("current", pres.current:java.lang.Boolean)
|
||||
presentation.put("downloadable", pres.downloadable:java.lang.Boolean)
|
||||
|
||||
// Get the pages for a presentation
|
||||
val pages = new ArrayList[Page]()
|
||||
@ -259,6 +261,7 @@ class PresentationClientMessageSender(service: ConnectionInvokerService) extends
|
||||
presentation.put("id", msg.presentation.id)
|
||||
presentation.put("name", msg.presentation.name)
|
||||
presentation.put("current", msg.presentation.current:java.lang.Boolean)
|
||||
presentation.put("downloadable", msg.presentation.downloadable:java.lang.Boolean)
|
||||
|
||||
// Get the pages for a presentation
|
||||
val pages = new ArrayList[Page]()
|
||||
|
@ -64,6 +64,7 @@ object PesentationMessageToJsonConverter {
|
||||
presentation.put(Constants.ID, pres.id)
|
||||
presentation.put(Constants.NAME, pres.name)
|
||||
presentation.put(Constants.CURRENT, pres.current:java.lang.Boolean)
|
||||
presentation.put(Constants.DOWNLOADABLE, pres.downloadable:java.lang.Boolean)
|
||||
|
||||
// Get the pages for a presentation
|
||||
val pages = new java.util.ArrayList[java.util.Map[String, Any]]()
|
||||
@ -120,6 +121,7 @@ object PesentationMessageToJsonConverter {
|
||||
presentation.put(Constants.ID, msg.presentation.id)
|
||||
presentation.put(Constants.NAME, msg.presentation.name)
|
||||
presentation.put(Constants.CURRENT, msg.presentation.current:java.lang.Boolean)
|
||||
presentation.put(Constants.DOWNLOADABLE, msg.presentation.downloadable:java.lang.Boolean)
|
||||
|
||||
// Get the pages for a presentation
|
||||
val pages = new java.util.ArrayList[java.util.Map[String, Any]]()
|
||||
@ -204,6 +206,7 @@ object PesentationMessageToJsonConverter {
|
||||
presentation.put(Constants.ID, msg.presentation.id)
|
||||
presentation.put(Constants.NAME, msg.presentation.name)
|
||||
presentation.put(Constants.CURRENT, msg.presentation.current:java.lang.Boolean)
|
||||
presentation.put(Constants.DOWNLOADABLE, msg.presentation.downloadable:java.lang.Boolean)
|
||||
|
||||
val pages = new java.util.ArrayList[java.util.Map[String, Any]]()
|
||||
msg.presentation.pages.values foreach {p =>
|
||||
@ -225,6 +228,7 @@ object PesentationMessageToJsonConverter {
|
||||
presentation.put(Constants.ID, msg.presentation.id)
|
||||
presentation.put(Constants.NAME, msg.presentation.name)
|
||||
presentation.put(Constants.CURRENT, msg.presentation.current:java.lang.Boolean)
|
||||
presentation.put(Constants.DOWNLOADABLE, msg.presentation.downloadable:java.lang.Boolean)
|
||||
|
||||
val pages = new java.util.ArrayList[java.util.Map[String, Any]]()
|
||||
msg.presentation.pages.values foreach {p =>
|
||||
@ -246,6 +250,7 @@ object PesentationMessageToJsonConverter {
|
||||
presentation.put(Constants.ID, msg.current.id)
|
||||
presentation.put(Constants.NAME, msg.current.name)
|
||||
presentation.put(Constants.CURRENT, msg.current.current:java.lang.Boolean)
|
||||
presentation.put(Constants.DOWNLOADABLE, msg.current.downloadable:java.lang.Boolean)
|
||||
|
||||
val pages = new java.util.ArrayList[java.util.Map[String, Any]]()
|
||||
|
||||
|
@ -0,0 +1,78 @@
|
||||
package org.bigbluebutton.core.apps.sharednotes
|
||||
|
||||
import name.fraser.neil.plaintext.diff_match_patch
|
||||
import name.fraser.neil.plaintext.diff_match_patch._
|
||||
import org.bigbluebutton.core.api._
|
||||
import org.bigbluebutton.core.MeetingActor
|
||||
import scala.collection.JavaConversions._
|
||||
import scala.collection._
|
||||
import java.util.Collections
|
||||
|
||||
|
||||
trait SharedNotesApp {
|
||||
this : MeetingActor =>
|
||||
|
||||
val outGW: MessageOutGateway
|
||||
|
||||
val notes = new scala.collection.mutable.HashMap[String, String]()
|
||||
notes += ("MAIN_WINDOW" -> "")
|
||||
val patcher = new diff_match_patch()
|
||||
var notesCounter = 0;
|
||||
var removedNotes : Set[Int] = Set()
|
||||
|
||||
def handlePatchDocumentRequest(msg: PatchDocumentRequest) {
|
||||
// meetingId, userId, noteId, patch, beginIndex, endIndex
|
||||
notes.synchronized {
|
||||
val document = notes(msg.noteID)
|
||||
val patchObjects = patcher.patch_fromText(msg.patch)
|
||||
val result = patcher.patch_apply(patchObjects, document)
|
||||
notes(msg.noteID) = result(0).toString()
|
||||
}
|
||||
|
||||
outGW.send(new PatchDocumentReply(meetingID, recorded, msg.requesterID, msg.noteID, msg.patch, msg.beginIndex, msg.endIndex))
|
||||
}
|
||||
|
||||
def handleGetCurrentDocumentRequest(msg: GetCurrentDocumentRequest) {
|
||||
val copyNotes = notes.toMap
|
||||
|
||||
outGW.send(new GetCurrentDocumentReply(meetingID, recorded, msg.requesterID, copyNotes))
|
||||
}
|
||||
|
||||
private def createAdditionalNotesNonSync(requesterID:String) {
|
||||
var noteID = 0
|
||||
if (removedNotes.isEmpty()) {
|
||||
notesCounter += 1
|
||||
noteID = notesCounter
|
||||
} else {
|
||||
noteID = removedNotes.min
|
||||
removedNotes -= noteID
|
||||
}
|
||||
notes += (noteID.toString -> "")
|
||||
|
||||
outGW.send(new CreateAdditionalNotesReply(meetingID, recorded, requesterID, noteID.toString))
|
||||
}
|
||||
|
||||
def handleCreateAdditionalNotesRequest(msg: CreateAdditionalNotesRequest) {
|
||||
notes.synchronized {
|
||||
createAdditionalNotesNonSync(msg.requesterID)
|
||||
}
|
||||
}
|
||||
|
||||
def handleDestroyAdditionalNotesRequest(msg: DestroyAdditionalNotesRequest) {
|
||||
notes.synchronized {
|
||||
removedNotes += msg.noteID.toInt
|
||||
notes -= msg.noteID
|
||||
}
|
||||
|
||||
outGW.send(new DestroyAdditionalNotesReply(meetingID, recorded, msg.requesterID, msg.noteID))
|
||||
}
|
||||
|
||||
def handleRequestAdditionalNotesSetRequest(msg: RequestAdditionalNotesSetRequest) {
|
||||
notes.synchronized {
|
||||
var num = msg.additionalNotesSetSize - notes.size + 1
|
||||
for (i <- 1 to num) {
|
||||
createAdditionalNotesNonSync(msg.requesterID)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package org.bigbluebutton.core.apps.sharednotes
|
||||
|
||||
import org.bigbluebutton.core.BigBlueButtonGateway
|
||||
import org.bigbluebutton.core.api._
|
||||
|
||||
class SharedNotesInGateway(bbbGW: BigBlueButtonGateway) {
|
||||
|
||||
def patchDocument(meetingId: String, userId: String, noteId: String,
|
||||
patch: String, beginIndex: Int, endIndex: Int) {
|
||||
bbbGW.accept(new PatchDocumentRequest(meetingId, userId, noteId, patch, beginIndex, endIndex));
|
||||
}
|
||||
|
||||
def getCurrentDocument(meetingId: String, userId: String) {
|
||||
bbbGW.accept(new GetCurrentDocumentRequest(meetingId, userId));
|
||||
}
|
||||
|
||||
def createAdditionalNotes(meetingId: String, userId: String) {
|
||||
bbbGW.accept(new CreateAdditionalNotesRequest(meetingId, userId));
|
||||
}
|
||||
|
||||
def destroyAdditionalNotes(meetingId: String, userId: String, noteId: String) {
|
||||
bbbGW.accept(new DestroyAdditionalNotesRequest(meetingId, userId, noteId));
|
||||
}
|
||||
|
||||
def requestAdditionalNotesSet(meetingId: String, userId: String, additionalNotesSetSize: Int) {
|
||||
bbbGW.accept(new RequestAdditionalNotesSetRequest(meetingId, userId, additionalNotesSetSize));
|
||||
}
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
package org.bigbluebutton.core.apps.sharednotes.red5
|
||||
|
||||
import org.bigbluebutton.conference.meeting.messaging.red5.ConnectionInvokerService
|
||||
import org.bigbluebutton.core.api._
|
||||
import org.bigbluebutton.conference.meeting.messaging.red5.DirectClientMessage
|
||||
import com.google.gson.Gson
|
||||
import org.bigbluebutton.conference.meeting.messaging.red5.BroadcastClientMessage
|
||||
import scala.collection.mutable.HashMap
|
||||
import collection.JavaConverters._
|
||||
import scala.collection.JavaConversions._
|
||||
import java.util.ArrayList
|
||||
|
||||
class SharedNotesClientMessageSender(service: ConnectionInvokerService) extends OutMessageListener2 {
|
||||
|
||||
def handleMessage(msg: IOutMessage) {
|
||||
msg match {
|
||||
case msg: PatchDocumentReply => handlePatchDocumentReply(msg)
|
||||
case msg: GetCurrentDocumentReply => handleGetCurrentDocumentReply(msg)
|
||||
case msg: CreateAdditionalNotesReply => handleCreateAdditionalNotesReply(msg)
|
||||
case msg: DestroyAdditionalNotesReply => handleDestroyAdditionalNotesReply(msg)
|
||||
case _ => // do nothing
|
||||
}
|
||||
}
|
||||
|
||||
private def handlePatchDocumentReply(msg: PatchDocumentReply) {
|
||||
val message = new java.util.HashMap[String, Object]()
|
||||
message.put("userID", msg.requesterID)
|
||||
message.put("noteID", msg.noteID)
|
||||
message.put("patch", msg.patch)
|
||||
message.put("beginIndex", msg.beginIndex.toString)
|
||||
message.put("endIndex", msg.endIndex.toString)
|
||||
|
||||
val m = new BroadcastClientMessage(msg.meetingID, "PatchDocumentCommand", message);
|
||||
service.sendMessage(m);
|
||||
}
|
||||
|
||||
private def handleGetCurrentDocumentReply(msg: GetCurrentDocumentReply) {
|
||||
val gson = new Gson();
|
||||
val message = new java.util.HashMap[String, Object]()
|
||||
|
||||
val jsonMsg = gson.toJson(mapAsJavaMap(msg.notes))
|
||||
|
||||
message.put("notes", jsonMsg)
|
||||
|
||||
val m = new DirectClientMessage(msg.meetingID, msg.requesterID, "GetCurrentDocumentCommand", message);
|
||||
service.sendMessage(m);
|
||||
}
|
||||
|
||||
private def handleCreateAdditionalNotesReply(msg: CreateAdditionalNotesReply) {
|
||||
val message = new java.util.HashMap[String, Object]()
|
||||
message.put("noteID", msg.noteID)
|
||||
|
||||
val m = new BroadcastClientMessage(msg.meetingID, "CreateAdditionalNotesCommand", message);
|
||||
service.sendMessage(m);
|
||||
}
|
||||
|
||||
private def handleDestroyAdditionalNotesReply(msg: DestroyAdditionalNotesReply) {
|
||||
val message = new java.util.HashMap[String, Object]()
|
||||
message.put("noteID", msg.noteID)
|
||||
|
||||
val m = new BroadcastClientMessage(msg.meetingID, "DestroyAdditionalNotesCommand", message);
|
||||
service.sendMessage(m);
|
||||
}
|
||||
}
|
@ -108,7 +108,7 @@ trait UsersApp {
|
||||
logger.info("Register user failed: reason=[meeting has ended] mid=[" + meetingID + "] uid=[" + msg.userID + "]")
|
||||
sendMeetingHasEnded(msg.userID)
|
||||
} else {
|
||||
val regUser = new RegisteredUser(msg.userID, msg.extUserID, msg.name, msg.role, msg.authToken)
|
||||
val regUser = new RegisteredUser(msg.userID, msg.extUserID, msg.name, msg.role, msg.authToken, msg.guest)
|
||||
regUsers += msg.authToken -> regUser
|
||||
logger.info("Register user success: mid=[" + meetingID + "] uid=[" + msg.userID + "]")
|
||||
outGW.send(new UserRegistered(meetingID, recorded, regUser))
|
||||
@ -208,22 +208,6 @@ trait UsersApp {
|
||||
au.toArray
|
||||
}
|
||||
|
||||
def handleUserRaiseHand(msg: UserRaiseHand) {
|
||||
users.getUser(msg.userId) foreach {user =>
|
||||
val uvo = user.copy(raiseHand=true)
|
||||
users.addUser(uvo)
|
||||
outGW.send(new UserRaisedHand(meetingID, recorded, uvo.userID))
|
||||
}
|
||||
}
|
||||
|
||||
def handleUserLowerHand(msg: UserLowerHand) {
|
||||
users.getUser(msg.userId) foreach {user =>
|
||||
val uvo = user.copy(raiseHand=false)
|
||||
users.addUser(uvo)
|
||||
outGW.send(new UserLoweredHand(meetingID, recorded, uvo.userID, msg.loweredBy))
|
||||
}
|
||||
}
|
||||
|
||||
def handleEjectUserFromMeeting(msg: EjectUserFromMeeting) {
|
||||
users.getUser(msg.userId) foreach {user =>
|
||||
if (user.voiceUser.joined) {
|
||||
@ -261,11 +245,28 @@ trait UsersApp {
|
||||
}
|
||||
|
||||
def handleChangeUserStatus(msg: ChangeUserStatus):Unit = {
|
||||
if (users.hasUser(msg.userID)) {
|
||||
users.getUser(msg.userID) foreach {user =>
|
||||
val uvo = msg.status match {
|
||||
case "mood" => user.copy( mood=msg.value.asInstanceOf[String])
|
||||
case _ => null
|
||||
}
|
||||
if (uvo != null) {
|
||||
logger.info("User changed mood: mid=[" + meetingID + "] uid=[" + uvo.userID + "] mood=[" + msg.value + "]")
|
||||
users.addUser(uvo)
|
||||
}
|
||||
outGW.send(new UserStatusChange(meetingID, recorded, msg.userID, msg.status, msg.value))
|
||||
}
|
||||
}
|
||||
|
||||
def handleChangeUserRole(msg: ChangeUserRole) {
|
||||
users.getUser(msg.userID) foreach {user =>
|
||||
val uvo = user.copy(role=msg.role)
|
||||
users.addUser(uvo)
|
||||
val userRole = if(msg.role == Role.MODERATOR) "MODERATOR" else "VIEWER"
|
||||
outGW.send(new UserRoleChange(meetingID, recorded, msg.userID, userRole))
|
||||
}
|
||||
}
|
||||
|
||||
def handleGetUsers(msg: GetUsers):Unit = {
|
||||
outGW.send(new GetUsersReply(msg.meetingID, msg.requesterID, users.getUsers))
|
||||
}
|
||||
@ -275,28 +276,35 @@ trait UsersApp {
|
||||
regUser foreach { ru =>
|
||||
val vu = new VoiceUser(msg.userID, msg.userID, ru.name, ru.name,
|
||||
false, false, false, false)
|
||||
val waitingForAcceptance = ru.guest && guestPolicy == GuestPolicy.ASK_MODERATOR;
|
||||
val uvo = new UserVO(msg.userID, ru.externId, ru.name,
|
||||
ru.role, raiseHand=false, presenter=false,
|
||||
ru.role, ru.guest, waitingForAcceptance=waitingForAcceptance, mood="", presenter=false,
|
||||
hasStream=false, locked=getInitialLockStatus(ru.role),
|
||||
webcamStreams=new ListSet[String](), phoneUser=false, vu, listenOnly=false)
|
||||
|
||||
users.addUser(uvo)
|
||||
|
||||
logger.info("User joined meeting: mid=[" + meetingID + "] uid=[" + uvo.userID + "] role=[" + uvo.role + "] locked=[" + uvo.locked + "] permissions.lockOnJoin=[" + permissions.lockOnJoin + "] permissions.lockOnJoinConfigurable=[" + permissions.lockOnJoinConfigurable + "]")
|
||||
|
||||
if (uvo.guest && guestPolicy == GuestPolicy.ALWAYS_DENY) {
|
||||
outGW.send(new GuestAccessDenied(meetingID, recorded, uvo.userID))
|
||||
} else {
|
||||
outGW.send(new UserJoined(meetingID, recorded, uvo))
|
||||
|
||||
outGW.send(new MeetingState(meetingID, recorded, uvo.userID, permissions, meetingMuted))
|
||||
|
||||
if (!waitingForAcceptance) {
|
||||
// Become presenter if the only moderator
|
||||
if (users.numModerators == 1) {
|
||||
if (ru.role == Role.MODERATOR) {
|
||||
assignNewPresenter(msg.userID, ru.name, msg.userID)
|
||||
}
|
||||
}
|
||||
}
|
||||
webUserJoined
|
||||
startRecordingIfAutoStart()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def handleUserLeft(msg: UserLeaving):Unit = {
|
||||
if (users.hasUser(msg.userID)) {
|
||||
@ -344,7 +352,7 @@ trait UsersApp {
|
||||
val sessionId = "PHONE-" + webUserId;
|
||||
|
||||
val uvo = new UserVO(webUserId, webUserId, msg.voiceUser.callerName,
|
||||
Role.VIEWER, raiseHand=false, presenter=false,
|
||||
Role.VIEWER, guest=false, waitingForAcceptance=false, mood="", presenter=false,
|
||||
hasStream=false, locked=getInitialLockStatus(Role.VIEWER), webcamStreams=new ListSet[String](),
|
||||
phoneUser=true, vu, listenOnly=false)
|
||||
|
||||
@ -363,13 +371,15 @@ trait UsersApp {
|
||||
def handleVoiceUserJoined(msg: VoiceUserJoined) = {
|
||||
val user = users.getUser(msg.voiceUser.webUserId) match {
|
||||
case Some(user) => {
|
||||
// this is used to restore the mute state on reconnect
|
||||
val previouslyMuted = user.voiceUser.muted
|
||||
val nu = user.copy(voiceUser=msg.voiceUser)
|
||||
users.addUser(nu)
|
||||
logger.info("Received user joined voice for user [" + nu.name + "] userid=[" + msg.voiceUser.webUserId + "]" )
|
||||
outGW.send(new UserJoinedVoice(meetingID, recorded, voiceBridge, nu))
|
||||
|
||||
if (meetingMuted)
|
||||
outGW.send(new MuteVoiceUser(meetingID, recorded, nu.userID, nu.userID, meetingMuted))
|
||||
if (meetingMuted || previouslyMuted)
|
||||
outGW.send(new MuteVoiceUser(meetingID, recorded, nu.userID, nu.userID, true))
|
||||
}
|
||||
case None => {
|
||||
handleUserJoinedVoiceFromPhone(msg)
|
||||
@ -445,4 +455,32 @@ trait UsersApp {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private def isModerator(userId: String):Boolean = {
|
||||
users.getUser(userId) match {
|
||||
case Some(user) => return user.role == Role.MODERATOR && !user.waitingForAcceptance
|
||||
case None => return false
|
||||
}
|
||||
}
|
||||
|
||||
def handleRespondToGuest(msg: RespondToGuest) {
|
||||
if (isModerator(msg.requesterID)) {
|
||||
var usersToAnswer:Array[UserVO] = null;
|
||||
if (msg.userId == null) {
|
||||
usersToAnswer = users.getUsers.filter(u => u.waitingForAcceptance == true)
|
||||
} else {
|
||||
usersToAnswer = users.getUsers.filter(u => u.waitingForAcceptance == true && u.userID == msg.userId)
|
||||
}
|
||||
usersToAnswer foreach {user =>
|
||||
println("UsersApp - handleGuestAccessDenied for user [" + user.userID + "]");
|
||||
if (msg.response == true) {
|
||||
val nu = user.copy(waitingForAcceptance=false)
|
||||
users.addUser(nu)
|
||||
outGW.send(new UserJoined(meetingID, recorded, nu))
|
||||
} else {
|
||||
outGW.send(new GuestAccessDenied(meetingID, recorded, user.userID))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ package org.bigbluebutton.core.apps.users.red5
|
||||
import org.bigbluebutton.conference.meeting.messaging.red5.ConnectionInvokerService
|
||||
import org.bigbluebutton.conference.meeting.messaging.red5.SharedObjectClientMessage
|
||||
import java.util.ArrayList
|
||||
import java.util.List
|
||||
import java.util.Map
|
||||
import java.util.HashMap
|
||||
import org.bigbluebutton.core.api._
|
||||
@ -26,8 +25,7 @@ class UsersClientMessageSender(service: ConnectionInvokerService) extends OutMes
|
||||
case msg: UserJoined => handleUserJoined(msg)
|
||||
case msg: UserLeft => handleUserLeft(msg)
|
||||
case msg: UserStatusChange => handleUserStatusChange(msg)
|
||||
case msg: UserRaisedHand => handleUserRaisedHand(msg)
|
||||
case msg: UserLoweredHand => handleUserLoweredHand(msg)
|
||||
case msg: UserRoleChange => handleUserRoleChange(msg)
|
||||
case msg: UserSharedWebcam => handleUserSharedWebcam(msg)
|
||||
case msg: UserUnsharedWebcam => handleUserUnshareWebcam(msg)
|
||||
case msg: GetUsersReply => handleGetUsersReply(msg)
|
||||
@ -45,6 +43,9 @@ class UsersClientMessageSender(service: ConnectionInvokerService) extends OutMes
|
||||
case msg: UserLocked => handleUserLocked(msg)
|
||||
case msg: MeetingMuted => handleMeetingMuted(msg)
|
||||
case msg: MeetingState => handleMeetingState(msg)
|
||||
case msg: GetGuestPolicyReply => handleGetGuestPolicyReply(msg)
|
||||
case msg: GuestPolicyChanged => handleGuestPolicyChanged(msg)
|
||||
case msg: GuestAccessDenied => handleGuestAccessDenied(msg)
|
||||
|
||||
case _ => // println("Unhandled message in UsersClientMessageSender")
|
||||
}
|
||||
@ -79,7 +80,9 @@ class UsersClientMessageSender(service: ConnectionInvokerService) extends OutMes
|
||||
wuser.put("externUserID", user.externUserID)
|
||||
wuser.put("name", user.name)
|
||||
wuser.put("role", user.role.toString())
|
||||
wuser.put("raiseHand", user.raiseHand:java.lang.Boolean)
|
||||
wuser.put("guest", user.guest:java.lang.Boolean)
|
||||
wuser.put("waitingForAcceptance", user.waitingForAcceptance:java.lang.Boolean)
|
||||
wuser.put("mood", user.mood:java.lang.String)
|
||||
wuser.put("presenter", user.presenter:java.lang.Boolean)
|
||||
wuser.put("hasStream", user.hasStream:java.lang.Boolean)
|
||||
wuser.put("locked", user.locked:java.lang.Boolean)
|
||||
@ -402,35 +405,6 @@ class UsersClientMessageSender(service: ConnectionInvokerService) extends OutMes
|
||||
service.sendMessage(m);
|
||||
}
|
||||
|
||||
def handleUserRaisedHand(msg: UserRaisedHand) {
|
||||
var args = new HashMap[String, Object]()
|
||||
args.put("userId", msg.userID)
|
||||
|
||||
val message = new java.util.HashMap[String, Object]()
|
||||
val gson = new Gson();
|
||||
message.put("msg", gson.toJson(args))
|
||||
|
||||
// println("UsersClientMessageSender - handleUserRaisedHand \n" + message.get("msg") + "\n")
|
||||
|
||||
var m = new BroadcastClientMessage(msg.meetingID, "userRaisedHand", message);
|
||||
service.sendMessage(m);
|
||||
}
|
||||
|
||||
def handleUserLoweredHand(msg: UserLoweredHand) {
|
||||
var args = new HashMap[String, Object]();
|
||||
args.put("userId", msg.userID)
|
||||
args.put("loweredBy", msg.loweredBy)
|
||||
|
||||
val message = new java.util.HashMap[String, Object]()
|
||||
val gson = new Gson();
|
||||
message.put("msg", gson.toJson(args))
|
||||
|
||||
// println("UsersClientMessageSender - handleUserLoweredHand \n" + message.get("msg") + "\n")
|
||||
|
||||
var m = new BroadcastClientMessage(msg.meetingID, "userLoweredHand", message);
|
||||
service.sendMessage(m);
|
||||
}
|
||||
|
||||
def handleUserSharedWebcam(msg: UserSharedWebcam) {
|
||||
var args = new HashMap[String, Object]()
|
||||
args.put("userId", msg.userID)
|
||||
@ -477,6 +451,21 @@ class UsersClientMessageSender(service: ConnectionInvokerService) extends OutMes
|
||||
service.sendMessage(m);
|
||||
}
|
||||
|
||||
private def handleUserRoleChange(msg: UserRoleChange) {
|
||||
var args = new HashMap[String, Object]();
|
||||
args.put("userID", msg.userID);
|
||||
args.put("role", msg.role);
|
||||
|
||||
val message = new java.util.HashMap[String, Object]()
|
||||
val gson = new Gson();
|
||||
message.put("msg", gson.toJson(args))
|
||||
|
||||
// println("UsersClientMessageSender - handleUserRoleChange \n" + message.get("msg") + "\n")
|
||||
|
||||
var m = new BroadcastClientMessage(msg.meetingID, "participantRoleChange", message);
|
||||
service.sendMessage(m);
|
||||
}
|
||||
|
||||
private def handleUserListeningOnly(msg: UserListeningOnly) {
|
||||
var args = new HashMap[String, Object]();
|
||||
args.put("userId", msg.userID);
|
||||
@ -491,4 +480,46 @@ class UsersClientMessageSender(service: ConnectionInvokerService) extends OutMes
|
||||
var m = new BroadcastClientMessage(msg.meetingID, "user_listening_only", message);
|
||||
service.sendMessage(m);
|
||||
}
|
||||
|
||||
private def handleGetGuestPolicyReply(msg: GetGuestPolicyReply) {
|
||||
var args = new HashMap[String, Object]();
|
||||
args.put("guestPolicy", msg.policy.toString());
|
||||
|
||||
val message = new java.util.HashMap[String, Object]()
|
||||
val gson = new Gson();
|
||||
message.put("msg", gson.toJson(args))
|
||||
|
||||
// println("UsersClientMessageSender - handleGetGuestPolicyReply \n" + message.get("msg") + "\n")
|
||||
|
||||
val m = new DirectClientMessage(msg.meetingID, msg.requesterID,"get_guest_policy_reply", message);
|
||||
service.sendMessage(m);
|
||||
}
|
||||
|
||||
private def handleGuestPolicyChanged(msg: GuestPolicyChanged) {
|
||||
var args = new HashMap[String, Object]();
|
||||
args.put("guestPolicy", msg.policy.toString());
|
||||
|
||||
val message = new java.util.HashMap[String, Object]()
|
||||
val gson = new Gson();
|
||||
message.put("msg", gson.toJson(args))
|
||||
|
||||
// println("UsersClientMessageSender - handleGuestPolicyChanged \n" + message.get("msg") + "\n")
|
||||
|
||||
var m = new BroadcastClientMessage(msg.meetingID, "guest_policy_changed", message);
|
||||
service.sendMessage(m);
|
||||
}
|
||||
|
||||
private def handleGuestAccessDenied(msg: GuestAccessDenied) {
|
||||
var args = new HashMap[String, Object]();
|
||||
args.put("userId", msg.userId);
|
||||
|
||||
val message = new java.util.HashMap[String, Object]()
|
||||
val gson = new Gson();
|
||||
message.put("msg", gson.toJson(args))
|
||||
|
||||
// println("UsersClientMessageSender - handleGuestAccessDenied \n" + message.get("msg") + "\n")
|
||||
|
||||
val m = new DirectClientMessage(msg.meetingID, msg.userId, "guest_access_denied", message);
|
||||
service.sendMessage(m);
|
||||
}
|
||||
}
|
@ -23,11 +23,10 @@ class UsersEventRedisPublisher(service: MessageSender) extends OutMessageListene
|
||||
case msg: GetUsersReply => handleGetUsersReply(msg)
|
||||
case msg: ValidateAuthTokenReply => handleValidateAuthTokenReply(msg)
|
||||
case msg: UserJoined => handleUserJoined(msg)
|
||||
case msg: UserRaisedHand => handleUserRaisedHand(msg)
|
||||
case msg: UserLoweredHand => handleUserLoweredHand(msg)
|
||||
case msg: UserSharedWebcam => handleUserSharedWebcam(msg)
|
||||
case msg: UserUnsharedWebcam => handleUserUnsharedWebcam(msg)
|
||||
case msg: UserStatusChange => handleUserStatusChange(msg)
|
||||
case msg: UserRoleChange => handleUserRoleChange(msg)
|
||||
case msg: UserVoiceMuted => handleUserVoiceMuted(msg)
|
||||
case msg: UserVoiceTalking => handleUserVoiceTalking(msg)
|
||||
case msg: MuteVoiceUser => handleMuteVoiceUser(msg)
|
||||
@ -80,13 +79,8 @@ class UsersEventRedisPublisher(service: MessageSender) extends OutMessageListene
|
||||
service.send(MessagingConstants.FROM_USERS_CHANNEL, json)
|
||||
}
|
||||
|
||||
private def handleUserRaisedHand(msg: UserRaisedHand) {
|
||||
val json = UsersMessageToJsonConverter.userRaisedHandToJson(msg)
|
||||
service.send(MessagingConstants.FROM_USERS_CHANNEL, json)
|
||||
}
|
||||
|
||||
private def handleUserLoweredHand(msg: UserLoweredHand) {
|
||||
val json = UsersMessageToJsonConverter.userLoweredHandToJson(msg)
|
||||
private def handleUserRoleChange(msg: UserRoleChange) {
|
||||
val json = UsersMessageToJsonConverter.userRoleChangeToJson(msg)
|
||||
service.send(MessagingConstants.FROM_USERS_CHANNEL, json)
|
||||
}
|
||||
|
||||
|
@ -16,7 +16,9 @@ object UsersMessageToJsonConverter {
|
||||
wuser += "extern_userid" -> user.externUserID
|
||||
wuser += "name" -> user.name
|
||||
wuser += "role" -> user.role.toString()
|
||||
wuser += "raise_hand" -> user.raiseHand
|
||||
wuser += "guest" -> user.guest
|
||||
wuser += "waiting_for_acceptance" -> user.waitingForAcceptance
|
||||
wuser += "mood" -> user.mood
|
||||
wuser += "presenter" -> user.presenter
|
||||
wuser += "has_stream" -> user.hasStream
|
||||
wuser += "locked" -> user.locked
|
||||
@ -46,6 +48,7 @@ object UsersMessageToJsonConverter {
|
||||
wuser += "name" -> user.name
|
||||
wuser += "role" -> user.role.toString()
|
||||
wuser += "authToken" -> user.authToken
|
||||
wuser += "guest" -> user.guest
|
||||
|
||||
mapAsJavaMap(wuser)
|
||||
}
|
||||
@ -127,28 +130,6 @@ object UsersMessageToJsonConverter {
|
||||
Util.buildJson(header, payload)
|
||||
}
|
||||
|
||||
|
||||
def userRaisedHandToJson(msg: UserRaisedHand):String = {
|
||||
val payload = new java.util.HashMap[String, Any]()
|
||||
payload.put(Constants.MEETING_ID, msg.meetingID)
|
||||
payload.put(Constants.RAISE_HAND, msg.recorded)
|
||||
payload.put(Constants.USER_ID, msg.userID)
|
||||
|
||||
val header = Util.buildHeader(MessageNames.USER_RAISED_HAND, msg.version, None)
|
||||
Util.buildJson(header, payload)
|
||||
}
|
||||
|
||||
def userLoweredHandToJson(msg: UserLoweredHand):String = {
|
||||
val payload = new java.util.HashMap[String, Any]()
|
||||
payload.put(Constants.MEETING_ID, msg.meetingID)
|
||||
payload.put(Constants.RAISE_HAND, msg.recorded)
|
||||
payload.put(Constants.USER_ID, msg.userID)
|
||||
payload.put(Constants.LOWERED_BY, msg.loweredBy)
|
||||
|
||||
val header = Util.buildHeader(MessageNames.USER_LOWERED_HAND, msg.version, None)
|
||||
Util.buildJson(header, payload)
|
||||
}
|
||||
|
||||
def userStatusChangeToJson(msg: UserStatusChange):String = {
|
||||
val payload = new java.util.HashMap[String, Any]()
|
||||
payload.put(Constants.MEETING_ID, msg.meetingID)
|
||||
@ -160,6 +141,16 @@ object UsersMessageToJsonConverter {
|
||||
Util.buildJson(header, payload)
|
||||
}
|
||||
|
||||
def userRoleChangeToJson(msg: UserRoleChange):String = {
|
||||
val payload = new java.util.HashMap[String, Any]()
|
||||
payload.put(Constants.MEETING_ID, msg.meetingID)
|
||||
payload.put(Constants.USER_ID, msg.userID)
|
||||
payload.put(Constants.ROLE, msg.role)
|
||||
|
||||
val header = Util.buildHeader(MessageNames.USER_ROLE_CHANGED, msg.version, None)
|
||||
Util.buildJson(header, payload)
|
||||
}
|
||||
|
||||
def userSharedWebcamToJson(msg: UserSharedWebcam):String = {
|
||||
val payload = new java.util.HashMap[String, Any]()
|
||||
payload.put(Constants.MEETING_ID, msg.meetingID)
|
||||
|
@ -0,0 +1,16 @@
|
||||
package org.bigbluebutton.core.apps.video
|
||||
|
||||
import org.bigbluebutton.core.api._
|
||||
import org.bigbluebutton.core.MeetingActor
|
||||
|
||||
trait VideoApp {
|
||||
this : MeetingActor =>
|
||||
|
||||
val outGW: MessageOutGateway
|
||||
|
||||
def handleGetStreamPath(msg: GetStreamPath) {
|
||||
// TODO: Request stream path from bbbWeb here
|
||||
val streamPath = msg.defaultPath
|
||||
outGW.send(new GetStreamPathReply(msg.meetingID, msg.requesterID, msg.streamName, streamPath))
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package org.bigbluebutton.core.apps.video.red5
|
||||
|
||||
import org.bigbluebutton.conference.meeting.messaging.red5.ConnectionInvokerService
|
||||
import org.bigbluebutton.core.api._
|
||||
import org.bigbluebutton.conference.meeting.messaging.red5.DirectClientMessage
|
||||
import com.google.gson.Gson
|
||||
import org.bigbluebutton.conference.meeting.messaging.red5.BroadcastClientMessage
|
||||
|
||||
class VideoClientMessageSender(service: ConnectionInvokerService) extends OutMessageListener2 {
|
||||
|
||||
def handleMessage(msg: IOutMessage) {
|
||||
msg match {
|
||||
case msg:GetStreamPathReply => handleGetStreamPathReply(msg)
|
||||
case _ => // do nothing
|
||||
}
|
||||
}
|
||||
|
||||
private def handleGetStreamPathReply(msg: GetStreamPathReply) {
|
||||
// Build JSON
|
||||
val args = new java.util.HashMap[String, Object]()
|
||||
args.put("streamName", msg.streamName);
|
||||
args.put("streamPath", msg.streamPath);
|
||||
|
||||
val message = new java.util.HashMap[String, Object]()
|
||||
val gson = new Gson();
|
||||
message.put("msg", gson.toJson(args))
|
||||
|
||||
var m = new DirectClientMessage(msg.meetingID, msg.requesterID, "getStreamPathReply", message);
|
||||
service.sendMessage(m);
|
||||
}
|
||||
}
|
40
bigbluebutton-apps/src/main/webapp/WEB-INF/bbb-app-sharednotes.xml
Executable file
@ -0,0 +1,40 @@
|
||||
<?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 3.0 of the License, or (at your option) any later
|
||||
version.
|
||||
|
||||
BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License along
|
||||
with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
-->
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:util="http://www.springframework.org/schema/util"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
|
||||
http://www.springframework.org/schema/util
|
||||
http://www.springframework.org/schema/util/spring-util-2.0.xsd">
|
||||
|
||||
<bean id="sharedNotesHandler" class="org.bigbluebutton.conference.service.sharednotes.SharedNotesHandler">
|
||||
<property name="sharedNotesApplication"> <ref local="sharedNotesApplication"/></property>
|
||||
</bean>
|
||||
|
||||
<bean id="sharedNotesApplication" class="org.bigbluebutton.conference.service.sharednotes.SharedNotesApplication">
|
||||
<property name="bigBlueButtonInGW"> <ref bean="bbbInGW"/></property>
|
||||
</bean>
|
||||
|
||||
<bean id="sharednotes.service" class="org.bigbluebutton.conference.service.sharednotes.SharedNotesService">
|
||||
<property name="sharedNotesApplication"> <ref local="sharedNotesApplication"/></property>
|
||||
</bean>
|
||||
</beans>
|
42
bigbluebutton-apps/src/main/webapp/WEB-INF/bbb-app-video.xml
Normal file
@ -0,0 +1,42 @@
|
||||
<?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 3.0 of the License, or (at your option) any later
|
||||
version.
|
||||
|
||||
BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License along
|
||||
with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
-->
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:util="http://www.springframework.org/schema/util"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
|
||||
http://www.springframework.org/schema/util
|
||||
http://www.springframework.org/schema/util/spring-util-2.0.xsd
|
||||
">
|
||||
|
||||
<bean id="videoHandler" class="org.bigbluebutton.conference.service.video.VideoHandler">
|
||||
<property name="videoApplication"> <ref local="videoApplication"/></property>
|
||||
</bean>
|
||||
|
||||
<bean id="videoApplication" class="org.bigbluebutton.conference.service.video.VideoApplication">
|
||||
<property name="bigBlueButtonInGW"> <ref bean="bbbInGW"/></property>
|
||||
<property name="defaultStreamPath" value="${video.defaultStreamPath}"/>
|
||||
</bean>
|
||||
|
||||
<bean id="video.service" class="org.bigbluebutton.conference.service.video.VideoService">
|
||||
<property name="videoApplication"> <ref local="videoApplication"/></property>
|
||||
</bean>
|
||||
</beans>
|
@ -36,3 +36,9 @@ icecast.port=8000
|
||||
icecast.username=source
|
||||
icecast.password=hackme
|
||||
icecast.broadcast=true
|
||||
|
||||
# This setting enable the use of proxy servers for the video stream
|
||||
# To use it, set it to the proxy server addresses, ending with the
|
||||
# conference address
|
||||
# Example: proxy1_ip/proxy2_ip/conference_ip
|
||||
video.defaultStreamPath=
|
||||
|
@ -53,6 +53,8 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
<set>
|
||||
<ref bean="participantsHandler" />
|
||||
<ref bean="whiteboardApplication" />
|
||||
<ref bean="sharedNotesHandler" />
|
||||
<ref bean="videoHandler" />
|
||||
</set>
|
||||
</property>
|
||||
<property name="recorderApplication">
|
||||
@ -120,6 +122,8 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
<ref bean="chatRedisPublisher"/>
|
||||
<ref bean="fsConfService"/>
|
||||
<ref bean="whiteboardEventRedisPublisher"/>
|
||||
<ref bean="sharedNotesRed5ClientSender"/>
|
||||
<ref bean="videoRed5ClientSender"/>
|
||||
</set>
|
||||
</property>
|
||||
</bean>
|
||||
@ -200,7 +204,13 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
<property name="bigBlueButtonInGW"> <ref bean="bbbInGW"/></property>
|
||||
</bean>
|
||||
|
||||
<bean id="sharedNotesRed5ClientSender" class="org.bigbluebutton.core.apps.sharednotes.red5.SharedNotesClientMessageSender">
|
||||
<constructor-arg index="0" ref="connInvokerService"/>
|
||||
</bean>
|
||||
|
||||
<bean id="videoRed5ClientSender" class="org.bigbluebutton.core.apps.video.red5.VideoClientMessageSender">
|
||||
<constructor-arg index="0" ref="connInvokerService"/>
|
||||
</bean>
|
||||
|
||||
<import resource="bbb-redis-pool.xml"/>
|
||||
<import resource="bbb-redis-recorder.xml"/>
|
||||
@ -210,6 +220,8 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
<import resource="bbb-app-presentation.xml" />
|
||||
<import resource="bbb-app-whiteboard.xml" />
|
||||
<import resource="bbb-app-users.xml" />
|
||||
<import resource="bbb-app-sharednotes.xml" />
|
||||
<import resource="bbb-app-video.xml" />
|
||||
|
||||
<import resource="bbb-voice-app.xml" />
|
||||
<import resource="bbb-voice-freeswitch.xml" />
|
||||
|
@ -99,7 +99,7 @@ ToolTip {
|
||||
fontFamily: Arial;
|
||||
}
|
||||
|
||||
Button, .logoutButtonStyle, .chatSendButtonStyle, .helpLinkButtonStyle, .cameraDisplaySettingsWindowProfileComboStyle, .cameraDisplaySettingsWindowCameraSelector, .languageSelectorStyle, .testJavaLinkButtonStyle, .recordButtonStyleNormal, .recordButtonStyleStart, .recordButtonStyleStop, .micSettingsWindowHelpButtonStyle {
|
||||
Button, .logoutButtonStyle, .chatSendButtonStyle, .helpLinkButtonStyle, .cameraDisplaySettingsWindowProfileComboStyle, .cameraDisplaySettingsWindowCameraSelector, .languageSelectorStyle, .testJavaLinkButtonStyle, .recordButtonStyleNormal, .recordButtonStyleStart, .recordButtonStyleStop, .micSettingsWindowHelpButtonStyle, .bandwidthButtonStyle, .settingsButtonStyle, .acceptButtonStyle, .denyButtonStyle {
|
||||
textIndent: 0;
|
||||
paddingLeft: 10;
|
||||
paddingRight: 10;
|
||||
@ -169,6 +169,18 @@ Button, .logoutButtonStyle, .chatSendButtonStyle, .helpLinkButtonStyle, .cameraD
|
||||
icon: Embed('assets/images/logout.png');
|
||||
}
|
||||
|
||||
.settingsButtonStyle {
|
||||
icon: Embed('assets/images/ic_settings_16px.png');
|
||||
}
|
||||
|
||||
.acceptButtonStyle {
|
||||
icon: Embed('assets/images/ic_thumb_up_16px.png');
|
||||
}
|
||||
|
||||
.denyButtonStyle {
|
||||
icon: Embed('assets/images/ic_thumb_down_16px.png');
|
||||
}
|
||||
|
||||
DataGrid {
|
||||
backgroundColor: #e1e2e5;
|
||||
rollOverColor: #f3f3f3;
|
||||
@ -285,7 +297,7 @@ DataGrid {
|
||||
|
||||
|
||||
.presentationUploadButtonStyle, .presentationBackButtonStyle, .presentationBackButtonDisabledStyle, .presentationForwardButtonStyle, .presentationForwardButtonDisabledStyle,
|
||||
.presentationFitToWidthButtonStyle, .presentationFitToPageButtonStyle
|
||||
.presentationFitToWidthButtonStyle, .presentationFitToPageButtonStyle, .presentationDownloadButtonStyle
|
||||
{
|
||||
textIndent: 0;
|
||||
paddingLeft: 10;
|
||||
@ -305,23 +317,23 @@ DataGrid {
|
||||
}
|
||||
|
||||
.presentationUploadButtonStyle {
|
||||
icon: Embed('assets/images/upload.png');
|
||||
icon: Embed('assets/images/ic_file_upload_16px.png');
|
||||
}
|
||||
|
||||
.presentationBackButtonStyle {
|
||||
icon: Embed('assets/images/left-arrow.png');
|
||||
icon: Embed('assets/images/ic_arrow_back_24px.png');
|
||||
}
|
||||
|
||||
.presentationBackButtonDisabledStyle {
|
||||
icon: Embed('assets/images/left-arrow-disabled.png');
|
||||
icon: Embed('assets/images/ic_arrow_back_grey_24px.png');
|
||||
}
|
||||
|
||||
.presentationForwardButtonStyle {
|
||||
icon: Embed('assets/images/right-arrow.png');
|
||||
icon: Embed('assets/images/ic_arrow_forward_24px.png');
|
||||
}
|
||||
|
||||
.presentationForwardButtonDisabledStyle {
|
||||
icon: Embed('assets/images/right-arrow-disabled.png');
|
||||
icon: Embed('assets/images/ic_arrow_forward_grey_24px.png');
|
||||
}
|
||||
|
||||
.presentationFitToWidthButtonStyle {
|
||||
@ -332,6 +344,10 @@ DataGrid {
|
||||
icon: Embed('assets/images/fit-to-screen.png');
|
||||
}
|
||||
|
||||
.presentationDownloadButtonStyle {
|
||||
icon: Embed('assets/images/ic_file_download_16px.png');
|
||||
}
|
||||
|
||||
.presentationZoomSliderStyle{
|
||||
labelOffset: 0;
|
||||
thumbOffset: 3;
|
||||
@ -559,6 +575,19 @@ DataGrid {
|
||||
fontSize: 12;
|
||||
}
|
||||
|
||||
.micSettingsWindowOpenDialogLabelStyle {
|
||||
fontFamily: Arial;
|
||||
fontSize: 14;
|
||||
fontWeight: bold;
|
||||
color: #e1e2e5;
|
||||
}
|
||||
|
||||
.micSettingsWindowShareMicrophoneLabelStyle {
|
||||
fontFamily: Arial;
|
||||
fontSize: 14;
|
||||
color: #5e5f63;
|
||||
}
|
||||
|
||||
.micSettingsWindowPlaySoundButtonStyle, .micSettingsWindowChangeMicButtonStyle {
|
||||
fillAlphas: 1, 1, 1, 1;
|
||||
fillColors: #fefeff, #e1e2e5, #ffffff, #eeeeee;
|
||||
@ -897,6 +926,13 @@ Alert {
|
||||
icon: Embed('assets/images/control-record-stop.png');
|
||||
}
|
||||
|
||||
.bandwidthButtonStyle {
|
||||
paddingTop: 0;
|
||||
paddingBottom: 0;
|
||||
height: 22;
|
||||
icon: Embed('assets/images/ic_swap_vert_16px.png');
|
||||
}
|
||||
|
||||
.statusImageStyle {
|
||||
successImage: Embed(source='assets/images/status_success.png');
|
||||
warningImage: Embed(source='assets/images/status_warning.png');
|
||||
@ -917,3 +953,63 @@ Alert {
|
||||
fontSize: 12;
|
||||
paddingTop: 0;
|
||||
}
|
||||
|
||||
.addLayoutButtonStyle {
|
||||
icon: Embed('assets/images/ic_add_circle_outline_16px.png');
|
||||
}
|
||||
|
||||
.saveLayoutButtonStyle {
|
||||
icon: Embed('assets/images/ic_file_download_16px.png');
|
||||
}
|
||||
|
||||
.loadLayoutButtonStyle {
|
||||
icon: Embed('assets/images/ic_file_upload_16px.png');
|
||||
}
|
||||
|
||||
.broadcastLayoutButtonStyle {
|
||||
icon: Embed('assets/images/ic_send_16px.png');
|
||||
}
|
||||
|
||||
.moodStyle {
|
||||
icon: Embed('assets/images/ic_mood_black_18dp.png');
|
||||
}
|
||||
|
||||
.moodRaiseHandStyle {
|
||||
icon: Embed('assets/images/icon-3-high-five.png');
|
||||
}
|
||||
|
||||
.moodAgreedStyle {
|
||||
icon: Embed('assets/images/icon-6-thumb-up.png');
|
||||
}
|
||||
|
||||
.moodDisagreedStyle {
|
||||
icon: Embed('assets/images/icon-7-thumb-down.png');
|
||||
}
|
||||
|
||||
.moodSpeakFasterStyle {
|
||||
icon: Embed('assets/images/ic_fast_forward_black_18dp.png');
|
||||
}
|
||||
|
||||
.moodSpeakSlowerStyle {
|
||||
icon: Embed('assets/images/ic_fast_rewind_black_18dp.png');
|
||||
}
|
||||
|
||||
.moodSpeakLouderStyle {
|
||||
icon: Embed('assets/images/ic_volume_up_black_18dp.png');
|
||||
}
|
||||
|
||||
.moodSpeakSofterStyle {
|
||||
icon: Embed('assets/images/ic_volume_down_black_18dp.png');
|
||||
}
|
||||
|
||||
.moodBeRightBackStyle {
|
||||
icon: Embed('assets/images/ic_access_time_black_18dp.png');
|
||||
}
|
||||
|
||||
.moodHappyStyle {
|
||||
icon: Embed('assets/images/icon-6-smiling-face.png');
|
||||
}
|
||||
|
||||
.moodSadStyle {
|
||||
icon: Embed('assets/images/icon-7-sad-face.png');
|
||||
}
|
||||
|
After Width: | Height: | Size: 233 B |
After Width: | Height: | Size: 378 B |
After Width: | Height: | Size: 354 B |
After Width: | Height: | Size: 233 B |
After Width: | Height: | Size: 328 B |
After Width: | Height: | Size: 248 B |
After Width: | Height: | Size: 358 B |
After Width: | Height: | Size: 225 B |
After Width: | Height: | Size: 239 B |
After Width: | Height: | Size: 247 B |
After Width: | Height: | Size: 260 B |
After Width: | Height: | Size: 265 B |
After Width: | Height: | Size: 387 B |
After Width: | Height: | Size: 298 B |
After Width: | Height: | Size: 317 B |
After Width: | Height: | Size: 259 B |
After Width: | Height: | Size: 327 B |