aboutsummaryrefslogtreecommitdiff
path: root/website
diff options
context:
space:
mode:
authorJonas Smedegaard <dr@jones.dk>2019-07-09 00:06:45 -0300
committerJonas Smedegaard <dr@jones.dk>2019-07-09 00:06:45 -0300
commit560b5aa2fe651fc4cc649f74efc5e62cf773e250 (patch)
treef3d8ba9d6d1fc9cb0abc6f2f9de5864d785839a1 /website
parent58f371bbc0091dd5f642c34dff9ebc1ad2018aee (diff)
Add website.
Diffstat (limited to 'website')
-rw-r--r--website/css/style.css117
-rw-r--r--website/ext/toastr.min.css1
-rw-r--r--website/ext/toastr.min.js2
l---------website/fonts/font-awesome1
-rw-r--r--website/img/up_arrow.pngbin0 -> 2611 bytes
l---------website/js1
-rw-r--r--website/mic.js819
-rw-r--r--website/mic/index.html130
8 files changed, 1071 insertions, 0 deletions
diff --git a/website/css/style.css b/website/css/style.css
new file mode 100644
index 0000000..6b30c30
--- /dev/null
+++ b/website/css/style.css
@@ -0,0 +1,117 @@
+.rounded {
+ border-radius: 5px;
+}
+
+.centered {
+ display: block;
+ margin: auto;
+}
+
+.relative {
+ position: relative;
+}
+
+.navbar-brand {
+ margin-left: 0px !important;
+}
+
+.navbar-default {
+ -webkit-box-shadow: 0px 3px 5px rgba(100, 100, 100, 0.49);
+ -moz-box-shadow: 0px 3px 5px rgba(100, 100, 100, 0.49);
+ box-shadow: 0px 3px 5px rgba(100, 100, 100, 0.49);
+}
+
+.navbar-header {
+ padding-left: 40px;
+}
+
+.margin-sm {
+ margin: 5px !important;
+}
+.margin-md {
+ margin: 10px !important;
+}
+.margin-xl {
+ margin: 20px !important;
+}
+.margin-bottom-sm {
+ margin-bottom: 5px !important;
+}
+.margin-bottom-md {
+ margin-bottom: 10px !important;
+}
+.margin-bottom-xl {
+ margin-bottom: 20px !important;
+}
+
+.divider {
+ width: 100%;
+ text-align: center;
+}
+
+.divider hr {
+ margin-left: auto;
+ margin-right: auto;
+ width: 45%;
+}
+
+.fa-2 {
+ font-size: 2em !important;
+}
+.fa-3 {
+ font-size: 4em !important;
+}
+.fa-4 {
+ font-size: 7em !important;
+}
+.fa-5 {
+ font-size: 12em !important;
+}
+.fa-6 {
+ font-size: 20em !important;
+}
+
+div.no-video-container {
+ position: relative;
+}
+
+.no-video-icon {
+ width: 100%;
+ height: 240px;
+ text-align: center;
+}
+
+.no-video-text {
+ text-align: center;
+ position: absolute;
+ bottom: 0px;
+ right: 0px;
+ left: 0px;
+ font-size: 24px;
+}
+
+.meetecho-logo {
+ padding: 12px !important;
+}
+
+.meetecho-logo > img {
+ height: 26px;
+}
+
+pre {
+ white-space: pre-wrap;
+ white-space: -moz-pre-wrap;
+ white-space: -pre-wrap;
+ white-space: -o-pre-wrap;
+ word-wrap: break-word;
+}
+
+.januscon {
+ font-weight: bold;
+ animation: pulsating 1s infinite;
+}
+@keyframes pulsating {
+ 30% {
+ color: #FFD700;
+ }
+}
diff --git a/website/ext/toastr.min.css b/website/ext/toastr.min.css
new file mode 100644
index 0000000..064afd0
--- /dev/null
+++ b/website/ext/toastr.min.css
@@ -0,0 +1 @@
+.toast-title{font-weight:700}.toast-message{-ms-word-wrap:break-word;word-wrap:break-word}.toast-message a,.toast-message label{color:#FFF}.toast-message a:hover{color:#CCC;text-decoration:none}.toast-close-button{position:relative;right:-.3em;top:-.3em;float:right;font-size:20px;font-weight:700;color:#FFF;-webkit-text-shadow:0 1px 0 #fff;text-shadow:0 1px 0 #fff;opacity:.8;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80);filter:alpha(opacity=80);line-height:1}.toast-close-button:focus,.toast-close-button:hover{color:#000;text-decoration:none;cursor:pointer;opacity:.4;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=40);filter:alpha(opacity=40)}.rtl .toast-close-button{left:-.3em;float:left;right:.3em}button.toast-close-button{padding:0;cursor:pointer;background:0 0;border:0;-webkit-appearance:none}.toast-top-center{top:0;right:0;width:100%}.toast-bottom-center{bottom:0;right:0;width:100%}.toast-top-full-width{top:0;right:0;width:100%}.toast-bottom-full-width{bottom:0;right:0;width:100%}.toast-top-left{top:12px;left:12px}.toast-top-right{top:12px;right:12px}.toast-bottom-right{right:12px;bottom:12px}.toast-bottom-left{bottom:12px;left:12px}#toast-container{position:fixed;z-index:999999;pointer-events:none}#toast-container *{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}#toast-container>div{position:relative;pointer-events:auto;overflow:hidden;margin:0 0 6px;padding:15px 15px 15px 50px;width:300px;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;background-position:15px center;background-repeat:no-repeat;-moz-box-shadow:0 0 12px #999;-webkit-box-shadow:0 0 12px #999;box-shadow:0 0 12px #999;color:#FFF;opacity:.8;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80);filter:alpha(opacity=80)}#toast-container>div.rtl{direction:rtl;padding:15px 50px 15px 15px;background-position:right 15px center}#toast-container>div:hover{-moz-box-shadow:0 0 12px #000;-webkit-box-shadow:0 0 12px #000;box-shadow:0 0 12px #000;opacity:1;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=100);filter:alpha(opacity=100);cursor:pointer}#toast-container>.toast-info{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGwSURBVEhLtZa9SgNBEMc9sUxxRcoUKSzSWIhXpFMhhYWFhaBg4yPYiWCXZxBLERsLRS3EQkEfwCKdjWJAwSKCgoKCcudv4O5YLrt7EzgXhiU3/4+b2ckmwVjJSpKkQ6wAi4gwhT+z3wRBcEz0yjSseUTrcRyfsHsXmD0AmbHOC9Ii8VImnuXBPglHpQ5wwSVM7sNnTG7Za4JwDdCjxyAiH3nyA2mtaTJufiDZ5dCaqlItILh1NHatfN5skvjx9Z38m69CgzuXmZgVrPIGE763Jx9qKsRozWYw6xOHdER+nn2KkO+Bb+UV5CBN6WC6QtBgbRVozrahAbmm6HtUsgtPC19tFdxXZYBOfkbmFJ1VaHA1VAHjd0pp70oTZzvR+EVrx2Ygfdsq6eu55BHYR8hlcki+n+kERUFG8BrA0BwjeAv2M8WLQBtcy+SD6fNsmnB3AlBLrgTtVW1c2QN4bVWLATaIS60J2Du5y1TiJgjSBvFVZgTmwCU+dAZFoPxGEEs8nyHC9Bwe2GvEJv2WXZb0vjdyFT4Cxk3e/kIqlOGoVLwwPevpYHT+00T+hWwXDf4AJAOUqWcDhbwAAAAASUVORK5CYII=)!important}#toast-container>.toast-error{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHOSURBVEhLrZa/SgNBEMZzh0WKCClSCKaIYOED+AAKeQQLG8HWztLCImBrYadgIdY+gIKNYkBFSwu7CAoqCgkkoGBI/E28PdbLZmeDLgzZzcx83/zZ2SSXC1j9fr+I1Hq93g2yxH4iwM1vkoBWAdxCmpzTxfkN2RcyZNaHFIkSo10+8kgxkXIURV5HGxTmFuc75B2RfQkpxHG8aAgaAFa0tAHqYFfQ7Iwe2yhODk8+J4C7yAoRTWI3w/4klGRgR4lO7Rpn9+gvMyWp+uxFh8+H+ARlgN1nJuJuQAYvNkEnwGFck18Er4q3egEc/oO+mhLdKgRyhdNFiacC0rlOCbhNVz4H9FnAYgDBvU3QIioZlJFLJtsoHYRDfiZoUyIxqCtRpVlANq0EU4dApjrtgezPFad5S19Wgjkc0hNVnuF4HjVA6C7QrSIbylB+oZe3aHgBsqlNqKYH48jXyJKMuAbiyVJ8KzaB3eRc0pg9VwQ4niFryI68qiOi3AbjwdsfnAtk0bCjTLJKr6mrD9g8iq/S/B81hguOMlQTnVyG40wAcjnmgsCNESDrjme7wfftP4P7SP4N3CJZdvzoNyGq2c/HWOXJGsvVg+RA/k2MC/wN6I2YA2Pt8GkAAAAASUVORK5CYII=)!important}#toast-container>.toast-success{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADsSURBVEhLY2AYBfQMgf///3P8+/evAIgvA/FsIF+BavYDDWMBGroaSMMBiE8VC7AZDrIFaMFnii3AZTjUgsUUWUDA8OdAH6iQbQEhw4HyGsPEcKBXBIC4ARhex4G4BsjmweU1soIFaGg/WtoFZRIZdEvIMhxkCCjXIVsATV6gFGACs4Rsw0EGgIIH3QJYJgHSARQZDrWAB+jawzgs+Q2UO49D7jnRSRGoEFRILcdmEMWGI0cm0JJ2QpYA1RDvcmzJEWhABhD/pqrL0S0CWuABKgnRki9lLseS7g2AlqwHWQSKH4oKLrILpRGhEQCw2LiRUIa4lwAAAABJRU5ErkJggg==)!important}#toast-container>.toast-warning{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGYSURBVEhL5ZSvTsNQFMbXZGICMYGYmJhAQIJAICYQPAACiSDB8AiICQQJT4CqQEwgJvYASAQCiZiYmJhAIBATCARJy+9rTsldd8sKu1M0+dLb057v6/lbq/2rK0mS/TRNj9cWNAKPYIJII7gIxCcQ51cvqID+GIEX8ASG4B1bK5gIZFeQfoJdEXOfgX4QAQg7kH2A65yQ87lyxb27sggkAzAuFhbbg1K2kgCkB1bVwyIR9m2L7PRPIhDUIXgGtyKw575yz3lTNs6X4JXnjV+LKM/m3MydnTbtOKIjtz6VhCBq4vSm3ncdrD2lk0VgUXSVKjVDJXJzijW1RQdsU7F77He8u68koNZTz8Oz5yGa6J3H3lZ0xYgXBK2QymlWWA+RWnYhskLBv2vmE+hBMCtbA7KX5drWyRT/2JsqZ2IvfB9Y4bWDNMFbJRFmC9E74SoS0CqulwjkC0+5bpcV1CZ8NMej4pjy0U+doDQsGyo1hzVJttIjhQ7GnBtRFN1UarUlH8F3xict+HY07rEzoUGPlWcjRFRr4/gChZgc3ZL2d8oAAAAASUVORK5CYII=)!important}#toast-container.toast-bottom-center>div,#toast-container.toast-top-center>div{width:300px;margin-left:auto;margin-right:auto}#toast-container.toast-bottom-full-width>div,#toast-container.toast-top-full-width>div{width:96%;margin-left:auto;margin-right:auto}.toast{background-color:#030303}.toast-success{background-color:#51A351}.toast-error{background-color:#BD362F}.toast-info{background-color:#2F96B4}.toast-warning{background-color:#F89406}.toast-progress{position:absolute;left:0;bottom:0;height:4px;background-color:#000;opacity:.4;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=40);filter:alpha(opacity=40)}@media all and (max-width:240px){#toast-container>div{padding:8px 8px 8px 50px;width:11em}#toast-container>div.rtl{padding:8px 50px 8px 8px}#toast-container .toast-close-button{right:-.2em;top:-.2em}#toast-container .rtl .toast-close-button{left:-.2em;right:.2em}}@media all and (min-width:241px) and (max-width:480px){#toast-container>div{padding:8px 8px 8px 50px;width:18em}#toast-container>div.rtl{padding:8px 50px 8px 8px}#toast-container .toast-close-button{right:-.2em;top:-.2em}#toast-container .rtl .toast-close-button{left:-.2em;right:.2em}}@media all and (min-width:481px) and (max-width:768px){#toast-container>div{padding:15px 15px 15px 50px;width:25em}#toast-container>div.rtl{padding:15px 50px 15px 15px}} \ No newline at end of file
diff --git a/website/ext/toastr.min.js b/website/ext/toastr.min.js
new file mode 100644
index 0000000..7c0c07c
--- /dev/null
+++ b/website/ext/toastr.min.js
@@ -0,0 +1,2 @@
+!function(e){e(["jquery"],function(e){return function(){function t(e,t,n){return g({type:O.error,iconClass:m().iconClasses.error,message:e,optionsOverride:n,title:t})}function n(t,n){return t||(t=m()),v=e("#"+t.containerId),v.length?v:(n&&(v=d(t)),v)}function o(e,t,n){return g({type:O.info,iconClass:m().iconClasses.info,message:e,optionsOverride:n,title:t})}function s(e){C=e}function i(e,t,n){return g({type:O.success,iconClass:m().iconClasses.success,message:e,optionsOverride:n,title:t})}function a(e,t,n){return g({type:O.warning,iconClass:m().iconClasses.warning,message:e,optionsOverride:n,title:t})}function r(e,t){var o=m();v||n(o),u(e,o,t)||l(o)}function c(t){var o=m();return v||n(o),t&&0===e(":focus",t).length?void h(t):void(v.children().length&&v.remove())}function l(t){for(var n=v.children(),o=n.length-1;o>=0;o--)u(e(n[o]),t)}function u(t,n,o){var s=!(!o||!o.force)&&o.force;return!(!t||!s&&0!==e(":focus",t).length)&&(t[n.hideMethod]({duration:n.hideDuration,easing:n.hideEasing,complete:function(){h(t)}}),!0)}function d(t){return v=e("<div/>").attr("id",t.containerId).addClass(t.positionClass),v.appendTo(e(t.target)),v}function p(){return{tapToDismiss:!0,toastClass:"toast",containerId:"toast-container",debug:!1,showMethod:"fadeIn",showDuration:300,showEasing:"swing",onShown:void 0,hideMethod:"fadeOut",hideDuration:1e3,hideEasing:"swing",onHidden:void 0,closeMethod:!1,closeDuration:!1,closeEasing:!1,closeOnHover:!0,extendedTimeOut:1e3,iconClasses:{error:"toast-error",info:"toast-info",success:"toast-success",warning:"toast-warning"},iconClass:"toast-info",positionClass:"toast-top-right",timeOut:5e3,titleClass:"toast-title",messageClass:"toast-message",escapeHtml:!1,target:"body",closeHtml:'<button type="button">&times;</button>',closeClass:"toast-close-button",newestOnTop:!0,preventDuplicates:!1,progressBar:!1,progressClass:"toast-progress",rtl:!1}}function f(e){C&&C(e)}function g(t){function o(e){return null==e&&(e=""),e.replace(/&/g,"&amp;").replace(/"/g,"&quot;").replace(/'/g,"&#39;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}function s(){c(),u(),d(),p(),g(),C(),l(),i()}function i(){var e="";switch(t.iconClass){case"toast-success":case"toast-info":e="polite";break;default:e="assertive"}I.attr("aria-live",e)}function a(){E.closeOnHover&&I.hover(H,D),!E.onclick&&E.tapToDismiss&&I.click(b),E.closeButton&&j&&j.click(function(e){e.stopPropagation?e.stopPropagation():void 0!==e.cancelBubble&&e.cancelBubble!==!0&&(e.cancelBubble=!0),E.onCloseClick&&E.onCloseClick(e),b(!0)}),E.onclick&&I.click(function(e){E.onclick(e),b()})}function r(){I.hide(),I[E.showMethod]({duration:E.showDuration,easing:E.showEasing,complete:E.onShown}),E.timeOut>0&&(k=setTimeout(b,E.timeOut),F.maxHideTime=parseFloat(E.timeOut),F.hideEta=(new Date).getTime()+F.maxHideTime,E.progressBar&&(F.intervalId=setInterval(x,10)))}function c(){t.iconClass&&I.addClass(E.toastClass).addClass(y)}function l(){E.newestOnTop?v.prepend(I):v.append(I)}function u(){if(t.title){var e=t.title;E.escapeHtml&&(e=o(t.title)),M.append(e).addClass(E.titleClass),I.append(M)}}function d(){if(t.message){var e=t.message;E.escapeHtml&&(e=o(t.message)),B.append(e).addClass(E.messageClass),I.append(B)}}function p(){E.closeButton&&(j.addClass(E.closeClass).attr("role","button"),I.prepend(j))}function g(){E.progressBar&&(q.addClass(E.progressClass),I.prepend(q))}function C(){E.rtl&&I.addClass("rtl")}function O(e,t){if(e.preventDuplicates){if(t.message===w)return!0;w=t.message}return!1}function b(t){var n=t&&E.closeMethod!==!1?E.closeMethod:E.hideMethod,o=t&&E.closeDuration!==!1?E.closeDuration:E.hideDuration,s=t&&E.closeEasing!==!1?E.closeEasing:E.hideEasing;if(!e(":focus",I).length||t)return clearTimeout(F.intervalId),I[n]({duration:o,easing:s,complete:function(){h(I),clearTimeout(k),E.onHidden&&"hidden"!==P.state&&E.onHidden(),P.state="hidden",P.endTime=new Date,f(P)}})}function D(){(E.timeOut>0||E.extendedTimeOut>0)&&(k=setTimeout(b,E.extendedTimeOut),F.maxHideTime=parseFloat(E.extendedTimeOut),F.hideEta=(new Date).getTime()+F.maxHideTime)}function H(){clearTimeout(k),F.hideEta=0,I.stop(!0,!0)[E.showMethod]({duration:E.showDuration,easing:E.showEasing})}function x(){var e=(F.hideEta-(new Date).getTime())/F.maxHideTime*100;q.width(e+"%")}var E=m(),y=t.iconClass||E.iconClass;if("undefined"!=typeof t.optionsOverride&&(E=e.extend(E,t.optionsOverride),y=t.optionsOverride.iconClass||y),!O(E,t)){T++,v=n(E,!0);var k=null,I=e("<div/>"),M=e("<div/>"),B=e("<div/>"),q=e("<div/>"),j=e(E.closeHtml),F={intervalId:null,hideEta:null,maxHideTime:null},P={toastId:T,state:"visible",startTime:new Date,options:E,map:t};return s(),r(),a(),f(P),E.debug&&console&&console.log(P),I}}function m(){return e.extend({},p(),b.options)}function h(e){v||(v=n()),e.is(":visible")||(e.remove(),e=null,0===v.children().length&&(v.remove(),w=void 0))}var v,C,w,T=0,O={error:"error",info:"info",success:"success",warning:"warning"},b={clear:r,remove:c,error:t,getContainer:n,info:o,options:{},subscribe:s,success:i,version:"2.1.3",warning:a};return b}()})}("function"==typeof define&&define.amd?define:function(e,t){"undefined"!=typeof module&&module.exports?module.exports=t(require("jquery")):window.toastr=t(window.jQuery)});
+//# sourceMappingURL=toastr.js.map
diff --git a/website/fonts/font-awesome b/website/fonts/font-awesome
new file mode 120000
index 0000000..3437336
--- /dev/null
+++ b/website/fonts/font-awesome
@@ -0,0 +1 @@
+/usr/share/fonts-font-awesome \ No newline at end of file
diff --git a/website/img/up_arrow.png b/website/img/up_arrow.png
new file mode 100644
index 0000000..51b8a87
--- /dev/null
+++ b/website/img/up_arrow.png
Binary files differ
diff --git a/website/js b/website/js
new file mode 120000
index 0000000..e3b95b4
--- /dev/null
+++ b/website/js
@@ -0,0 +1 @@
+/usr/share/javascript \ No newline at end of file
diff --git a/website/mic.js b/website/mic.js
new file mode 100644
index 0000000..ecbd682
--- /dev/null
+++ b/website/mic.js
@@ -0,0 +1,819 @@
+var websocket_server = null;
+if(window.location.protocol === 'http:')
+ websocket_server = "ws://" + window.location.hostname + "/janus-ws/janus";
+else
+ websocket_server = "wss://" + window.location.hostname + "/janus-ws/janus";
+
+var janus = null;
+var streaming = null;
+var mixertest = null;
+var opaqueId = "streamingwithfeedback-"+Janus.randomString(12);
+
+var bitrateTimer = null;
+var spinner = null;
+
+var simulcastStarted = false, svcStarted = false;
+
+var selectedStream = null;
+
+var myroom = 1234; // Demo room
+var myusername = null;
+var myid = null;
+var webrtcUp = false;
+var audioenabled = false;
+
+
+$(document).ready(function() {
+ // Initialize the library (all console debuggers enabled)
+ Janus.init({debug: "all", callback: function() {
+ // Use a button to start the demo
+ $('#start').one('click', function() {
+ $(this).attr('disabled', true).unbind('click');
+ // Make sure the browser supports WebRTC
+ if(!Janus.isWebrtcSupported()) {
+ bootbox.alert("No WebRTC support... ");
+ return;
+ }
+ // Create session
+ janus = new Janus(
+ {
+ server: [websocket_server, "/janus"],
+ iceServers: [{url: "turn:morla.jones.dk", username: "myturn", credential: "notsecure"},
+ {url: "turn:jawa.homebase.dk", username: "myturn", credential: "notsecure"}],
+ success: function() {
+ // Attach to streaming plugin
+ janus.attach(
+ {
+ plugin: "janus.plugin.streaming",
+ opaqueId: opaqueId,
+ success: function(pluginHandle) {
+ $('#details').remove();
+ streaming = pluginHandle;
+ Janus.log("Plugin attached! (" + streaming.getPlugin() + ", id=" + streaming.getId() + ")");
+ // Setup streaming session
+ $('#update-streams').click(updateStreamsList);
+ updateStreamsList();
+ $('#start').removeAttr('disabled').html("Stop")
+ .click(function() {
+ $(this).attr('disabled', true);
+ clearInterval(bitrateTimer);
+ janus.destroy();
+ $('#streamslist').attr('disabled', true);
+ $('#watch').attr('disabled', true).unbind('click');
+ $('#start').attr('disabled', true).html("Bye").unbind('click');
+ });
+ },
+ error: function(error) {
+ Janus.error(" -- Error attaching plugin... ", error);
+ bootbox.alert("Error attaching plugin... " + error);
+ },
+ onmessage: function(msg, jsep) {
+ Janus.debug(" ::: Got a message :::");
+ Janus.debug(msg);
+ var result = msg["result"];
+ if(result !== null && result !== undefined) {
+ if(result["status"] !== undefined && result["status"] !== null) {
+ var status = result["status"];
+ if(status === 'starting')
+ $('#status').removeClass('hide').text("Starting, please wait...").show();
+ else if(status === 'started')
+ $('#status').removeClass('hide').text("Started").show();
+ else if(status === 'stopped')
+ stopStream();
+ } else if(msg["streaming"] === "event") {
+ // Is simulcast in place?
+ var substream = result["substream"];
+ var temporal = result["temporal"];
+ if((substream !== null && substream !== undefined) || (temporal !== null && temporal !== undefined)) {
+ if(!simulcastStarted) {
+ simulcastStarted = true;
+ addSimulcastButtons(temporal !== null && temporal !== undefined);
+ }
+ // We just received notice that there's been a switch, update the buttons
+ updateSimulcastButtons(substream, temporal);
+ }
+ // Is VP9/SVC in place?
+ var spatial = result["spatial_layer"];
+ temporal = result["temporal_layer"];
+ if((spatial !== null && spatial !== undefined) || (temporal !== null && temporal !== undefined)) {
+ if(!svcStarted) {
+ svcStarted = true;
+ addSvcButtons();
+ }
+ // We just received notice that there's been a switch, update the buttons
+ updateSvcButtons(spatial, temporal);
+ }
+ }
+ } else if(msg["error"] !== undefined && msg["error"] !== null) {
+ bootbox.alert(msg["error"]);
+ stopStream();
+ return;
+ }
+ if(jsep !== undefined && jsep !== null) {
+ Janus.debug("Handling SDP as well...");
+ Janus.debug(jsep);
+ // Offer from the plugin, let's answer
+ streaming.createAnswer(
+ {
+ jsep: jsep,
+ // We want recvonly audio/video and, if negotiated, datachannels
+ media: { audioSend: false, videoSend: false, data: true },
+ success: function(jsep) {
+ Janus.debug("Got SDP!");
+ Janus.debug(jsep);
+ var body = { "request": "start" };
+ streaming.send({"message": body, "jsep": jsep});
+ $('#watch').html("Stop").removeAttr('disabled').click(stopStream);
+ },
+ error: function(error) {
+ Janus.error("WebRTC error:", error);
+ bootbox.alert("WebRTC error... " + JSON.stringify(error));
+ }
+ });
+ }
+ },
+ onremotestream: function(stream) {
+ Janus.debug(" ::: Got a remote stream :::");
+ Janus.debug(stream);
+ var addButtons = false;
+ if($('#remotevideo').length === 0) {
+ addButtons = true;
+ $('#stream').append('<video class="rounded centered hide" id="remotevideo" width=320 height=240 autoplay playsinline/>');
+ // Show the stream and hide the spinner when we get a playing event
+ $("#remotevideo").bind("playing", function () {
+ $('#waitingvideo').remove();
+ if(this.videoWidth)
+ $('#remotevideo').removeClass('hide').show();
+ if(spinner !== null && spinner !== undefined)
+ spinner.stop();
+ spinner = null;
+ var videoTracks = stream.getVideoTracks();
+ if(videoTracks === null || videoTracks === undefined || videoTracks.length === 0)
+ return;
+ var width = this.videoWidth;
+ var height = this.videoHeight;
+ $('#curres').removeClass('hide').text(width+'x'+height).show();
+ if(Janus.webRTCAdapter.browserDetails.browser === "firefox") {
+ // Firefox Stable has a bug: width and height are not immediately available after a playing
+ setTimeout(function() {
+ var width = $("#remotevideo").get(0).videoWidth;
+ var height = $("#remotevideo").get(0).videoHeight;
+ $('#curres').removeClass('hide').text(width+'x'+height).show();
+ }, 2000);
+ }
+ });
+ }
+ Janus.attachMediaStream($('#remotevideo').get(0), stream);
+ var videoTracks = stream.getVideoTracks();
+ if(videoTracks === null || videoTracks === undefined || videoTracks.length === 0) {
+ // No remote video
+ $('#remotevideo').hide();
+ if($('#stream .no-video-container').length === 0) {
+ $('#stream').append(
+ '<div class="no-video-container">' +
+ '<i class="fa fa-video-camera fa-5 no-video-icon"></i>' +
+ '<span class="no-video-text">No remote video available</span>' +
+ '</div>');
+ }
+ } else {
+ $('#stream .no-video-container').remove();
+ $('#remotevideo').removeClass('hide').show();
+ }
+ if(!addButtons)
+ return;
+ if(videoTracks && videoTracks.length &&
+ (Janus.webRTCAdapter.browserDetails.browser === "chrome" ||
+ Janus.webRTCAdapter.browserDetails.browser === "firefox" ||
+ Janus.webRTCAdapter.browserDetails.browser === "safari")) {
+ $('#curbitrate').removeClass('hide').show();
+ bitrateTimer = setInterval(function() {
+ // Display updated bitrate, if supported
+ var bitrate = streaming.getBitrate();
+ //~ Janus.debug("Current bitrate is " + streaming.getBitrate());
+ $('#curbitrate').text(bitrate);
+ // Check if the resolution changed too
+ var width = $("#remotevideo").get(0).videoWidth;
+ var height = $("#remotevideo").get(0).videoHeight;
+ if(width > 0 && height > 0)
+ $('#curres').removeClass('hide').text(width+'x'+height).show();
+ }, 1000);
+ }
+ },
+ ondataopen: function(data) {
+ Janus.log("The DataChannel is available!");
+ $('#waitingvideo').remove();
+ $('#stream').append(
+ '<input class="form-control" type="text" id="datarecv" disabled></input>'
+ );
+ if(spinner !== null && spinner !== undefined)
+ spinner.stop();
+ spinner = null;
+ },
+ ondata: function(data) {
+ Janus.debug("We got data from the DataChannel! " + data);
+ $('#datarecv').val(data);
+ },
+ oncleanup: function() {
+ Janus.log(" ::: Got a cleanup notification :::");
+ $('#waitingvideo').remove();
+ $('#remotevideo').remove();
+ $('#datarecv').remove();
+ $('.no-video-container').remove();
+ $('#bitrate').attr('disabled', true);
+ $('#bitrateset').html('Bandwidth<span class="caret"></span>');
+ $('#curbitrate').hide();
+ if(bitrateTimer !== null && bitrateTimer !== undefined)
+ clearInterval(bitrateTimer);
+ bitrateTimer = null;
+ $('#curres').hide();
+ $('#simulcast').remove();
+ simulcastStarted = false;
+ }
+ });
+ // Attach to Audio Bridge test plugin
+ janus.attach(
+ {
+ plugin: "janus.plugin.audiobridge",
+ opaqueId: opaqueId,
+ success: function(pluginHandle) {
+ $('#details').remove();
+ mixertest = pluginHandle;
+ Janus.log("Plugin attached! (" + mixertest.getPlugin() + ", id=" + mixertest.getId() + ")");
+ // Prepare the username registration
+ $('#audiojoin').removeClass('hide').show();
+ $('#registernow').removeClass('hide').show();
+ $('#register').click(registerUsername);
+ $('#username').focus();
+ $('#start').removeAttr('disabled').html("Stop")
+ .click(function() {
+ $(this).attr('disabled', true);
+ janus.destroy();
+ });
+ },
+ error: function(error) {
+ Janus.error(" -- Error attaching plugin...", error);
+ bootbox.alert("Error attaching plugin... " + error);
+ },
+ consentDialog: function(on) {
+ Janus.debug("Consent dialog should be " + (on ? "on" : "off") + " now");
+ if(on) {
+ // Darken screen and show hint
+ $.blockUI({
+ message: '<div><img src="img/up_arrow.png"/></div>',
+ css: {
+ border: 'none',
+ padding: '15px',
+ backgroundColor: 'transparent',
+ color: '#aaa',
+ top: '10px',
+ left: (navigator.mozGetUserMedia ? '-100px' : '300px')
+ } });
+ } else {
+ // Restore screen
+ $.unblockUI();
+ }
+ },
+ onmessage: function(msg, jsep) {
+ Janus.debug(" ::: Got a message :::");
+ Janus.debug(msg);
+ var event = msg["audiobridge"];
+ Janus.debug("Event: " + event);
+ if(event != undefined && event != null) {
+ if(event === "joined") {
+ // Successfully joined, negotiate WebRTC now
+ myid = msg["id"];
+ Janus.log("Successfully joined room " + msg["room"] + " with ID " + myid);
+ if(!webrtcUp) {
+ webrtcUp = true;
+ // Publish our stream
+ mixertest.createOffer(
+ {
+ media: { video: false}, // This is an audio only room
+ success: function(jsep) {
+ Janus.debug("Got SDP!");
+ Janus.debug(jsep);
+ var publish = { "request": "configure", "muted": false };
+ mixertest.send({"message": publish, "jsep": jsep});
+ },
+ error: function(error) {
+ Janus.error("WebRTC error:", error);
+ bootbox.alert("WebRTC error... " + JSON.stringify(error));
+ }
+ });
+ }
+ // Any room participant?
+ if(msg["participants"] !== undefined && msg["participants"] !== null) {
+ var list = msg["participants"];
+ Janus.debug("Got a list of participants:");
+ Janus.debug(list);
+ for(var f in list) {
+ var id = list[f]["id"];
+ var display = list[f]["display"];
+ var setup = list[f]["setup"];
+ var muted = list[f]["muted"];
+ Janus.debug(" >> [" + id + "] " + display + " (setup=" + setup + ", muted=" + muted + ")");
+ if($('#rp'+id).length === 0) {
+ // Add to the participants list
+ $('#list').append('<li id="rp'+id+'" class="list-group-item">'+display+
+ ' <i class="absetup fa fa-chain-broken"></i>' +
+ ' <i class="abmuted fa fa-microphone-slash"></i></li>');
+ $('#rp'+id + ' > i').hide();
+ }
+ if(muted === true || muted === "true")
+ $('#rp'+id + ' > i.abmuted').removeClass('hide').show();
+ else
+ $('#rp'+id + ' > i.abmuted').hide();
+ if(setup === true || setup === "true")
+ $('#rp'+id + ' > i.absetup').hide();
+ else
+ $('#rp'+id + ' > i.absetup').removeClass('hide').show();
+ }
+ }
+ } else if(event === "roomchanged") {
+ // The user switched to a different room
+ myid = msg["id"];
+ Janus.log("Moved to room " + msg["room"] + ", new ID: " + myid);
+ // Any room participant?
+ $('#list').empty();
+ if(msg["participants"] !== undefined && msg["participants"] !== null) {
+ var list = msg["participants"];
+ Janus.debug("Got a list of participants:");
+ Janus.debug(list);
+ for(var f in list) {
+ var id = list[f]["id"];
+ var display = list[f]["display"];
+ var setup = list[f]["setup"];
+ var muted = list[f]["muted"];
+ Janus.debug(" >> [" + id + "] " + display + " (setup=" + setup + ", muted=" + muted + ")");
+ if($('#rp'+id).length === 0) {
+ // Add to the participants list
+ $('#list').append('<li id="rp'+id+'" class="list-group-item">'+display+
+ ' <i class="absetup fa fa-chain-broken"></i>' +
+ ' <i class="abmuted fa fa-microphone-slash"></i></li>');
+ $('#rp'+id + ' > i').hide();
+ }
+ if(muted === true || muted === "true")
+ $('#rp'+id + ' > i.abmuted').removeClass('hide').show();
+ else
+ $('#rp'+id + ' > i.abmuted').hide();
+ if(setup === true || setup === "true")
+ $('#rp'+id + ' > i.absetup').hide();
+ else
+ $('#rp'+id + ' > i.absetup').removeClass('hide').show();
+ }
+ }
+ } else if(event === "destroyed") {
+ // The room has been destroyed
+ Janus.warn("The room has been destroyed!");
+ bootbox.alert("The room has been destroyed", function() {
+ window.location.reload();
+ });
+ } else if(event === "event") {
+ if(msg["participants"] !== undefined && msg["participants"] !== null) {
+ var list = msg["participants"];
+ Janus.debug("Got a list of participants:");
+ Janus.debug(list);
+ for(var f in list) {
+ var id = list[f]["id"];
+ var display = list[f]["display"];
+ var setup = list[f]["setup"];
+ var muted = list[f]["muted"];
+ Janus.debug(" >> [" + id + "] " + display + " (setup=" + setup + ", muted=" + muted + ")");
+ if($('#rp'+id).length === 0) {
+ // Add to the participants list
+ $('#list').append('<li id="rp'+id+'" class="list-group-item">'+display+
+ ' <i class="absetup fa fa-chain-broken"></i>' +
+ ' <i class="abmuted fa fa-microphone-slash"></i></li>');
+ $('#rp'+id + ' > i').hide();
+ }
+ if(muted === true || muted === "true")
+ $('#rp'+id + ' > i.abmuted').removeClass('hide').show();
+ else
+ $('#rp'+id + ' > i.abmuted').hide();
+ if(setup === true || setup === "true")
+ $('#rp'+id + ' > i.absetup').hide();
+ else
+ $('#rp'+id + ' > i.absetup').removeClass('hide').show();
+ }
+ } else if(msg["error"] !== undefined && msg["error"] !== null) {
+ if(msg["error_code"] === 485) {
+ // This is a "no such room" error: give a more meaningful description
+ bootbox.alert(
+ "<p>Apparently room <code>" + myroom + "</code> (the one this demo uses as a test room) " +
+ "does not exist...</p><p>Do you have an updated <code>janus.plugin.audiobridge.cfg</code> " +
+ "configuration file? If not, make sure you copy the details of room <code>" + myroom + "</code> " +
+ "from that sample in your current configuration file, then restart Janus and try again."
+ );
+ } else {
+ bootbox.alert(msg["error"]);
+ }
+ return;
+ }
+ // Any new feed to attach to?
+ if(msg["leaving"] !== undefined && msg["leaving"] !== null) {
+ // One of the participants has gone away?
+ var leaving = msg["leaving"];
+ Janus.log("Participant left: " + leaving + " (we have " + $('#rp'+leaving).length + " elements with ID #rp" +leaving + ")");
+ $('#rp'+leaving).remove();
+ }
+ }
+ }
+ if(jsep !== undefined && jsep !== null) {
+ Janus.debug("Handling SDP as well...");
+ Janus.debug(jsep);
+ mixertest.handleRemoteJsep({jsep: jsep});
+ }
+ },
+ onlocalstream: function(stream) {
+ Janus.debug(" ::: Got a local stream :::");
+ Janus.debug(stream);
+ // We're not going to attach the local audio stream
+ $('#audiojoin').hide();
+ $('#room').removeClass('hide').show();
+ $('#participant').removeClass('hide').html(myusername).show();
+ },
+ onremotestream: function(stream) {
+ $('#room').removeClass('hide').show();
+ var addButtons = false;
+ if($('#roomaudio').length === 0) {
+ addButtons = true;
+ $('#mixedaudio').append('<audio class="rounded centered" id="roomaudio" width="100%" height="100%" autoplay/>');
+ }
+ Janus.attachMediaStream($('#roomaudio').get(0), stream);
+ if(!addButtons)
+ return;
+ // Mute button
+ audioenabled = true;
+ $('#toggleaudio').click(
+ function() {
+ audioenabled = !audioenabled;
+ if(audioenabled)
+ $('#toggleaudio').html("Mute").removeClass("btn-success").addClass("btn-danger");
+ else
+ $('#toggleaudio').html("Unmute").removeClass("btn-danger").addClass("btn-success");
+ mixertest.send({message: { "request": "configure", "muted": !audioenabled }});
+ }).removeClass('hide').show();
+
+ },
+ oncleanup: function() {
+ webrtcUp = false;
+ Janus.log(" ::: Got a cleanup notification :::");
+ $('#participant').empty().hide();
+ $('#list').empty();
+ $('#mixedaudio').empty();
+ $('#room').hide();
+ }
+ });
+ },
+ error: function(error) {
+ Janus.error(error);
+ bootbox.alert(error, function() {
+ window.location.reload();
+ });
+ },
+ destroyed: function() {
+ window.location.reload();
+ }
+ });
+ });
+ }});
+});
+
+function updateStreamsList() {
+ $('#update-streams').unbind('click').addClass('fa-spin');
+ var body = { "request": "list" };
+ Janus.debug("Sending message (" + JSON.stringify(body) + ")");
+ streaming.send({"message": body, success: function(result) {
+ setTimeout(function() {
+ $('#update-streams').removeClass('fa-spin').click(updateStreamsList);
+ }, 500);
+ if(result === null || result === undefined) {
+ bootbox.alert("Got no response to our query for available streams");
+ return;
+ }
+ if(result["list"] !== undefined && result["list"] !== null) {
+ $('#streams').removeClass('hide').show();
+ $('#streamslist').empty();
+ $('#watch').attr('disabled', true).unbind('click');
+ var list = result["list"];
+ Janus.log("Got a list of available streams");
+ Janus.debug(list);
+ for(var mp in list) {
+ Janus.debug(" >> [" + list[mp]["id"] + "] " + list[mp]["description"] + " (" + list[mp]["type"] + ")");
+ $('#streamslist').append("<li><a href='#' id='" + list[mp]["id"] + "'>" + list[mp]["description"] + " (" + list[mp]["type"] + ")" + "</a></li>");
+ }
+ $('#streamslist a').unbind('click').click(function() {
+ selectedStream = $(this).attr("id");
+ $('#streamset').html($(this).html()).parent().removeClass('open');
+ return false;
+
+ });
+ $('#watch').removeAttr('disabled').unbind('click').click(startStream);
+ }
+ }});
+}
+
+function startStream() {
+ Janus.log("Selected video id #" + selectedStream);
+ if(selectedStream === undefined || selectedStream === null) {
+ bootbox.alert("Select a stream from the list");
+ return;
+ }
+ $('#streamset').attr('disabled', true);
+ $('#streamslist').attr('disabled', true);
+ $('#watch').attr('disabled', true).unbind('click');
+ var body = { "request": "watch", id: parseInt(selectedStream) };
+ streaming.send({"message": body});
+ // No remote video yet
+ $('#stream').append('<video class="rounded centered" id="waitingvideo" width=320 height=240 />');
+ if(spinner == null) {
+ var target = document.getElementById('stream');
+ spinner = new Spinner({top:100}).spin(target);
+ } else {
+ spinner.spin();
+ }
+}
+
+function stopStream() {
+ $('#watch').attr('disabled', true).unbind('click');
+ var body = { "request": "stop" };
+ streaming.send({"message": body});
+ streaming.hangup();
+ $('#streamset').removeAttr('disabled');
+ $('#streamslist').removeAttr('disabled');
+ $('#watch').html("Watch or Listen").removeAttr('disabled').unbind('click').click(startStream);
+ $('#status').empty().hide();
+ $('#bitrate').attr('disabled', true);
+ $('#bitrateset').html('Bandwidth<span class="caret"></span>');
+ $('#curbitrate').hide();
+ if(bitrateTimer !== null && bitrateTimer !== undefined)
+ clearInterval(bitrateTimer);
+ bitrateTimer = null;
+ $('#curres').empty().hide();
+ $('#simulcast').remove();
+ simulcastStarted = false;
+}
+
+// Helpers to create Simulcast-related UI, if enabled
+function addSimulcastButtons(temporal) {
+ $('#curres').parent().append(
+ '<div id="simulcast" class="btn-group-vertical btn-group-vertical-xs pull-right">' +
+ ' <div class"row">' +
+ ' <div class="btn-group btn-group-xs" style="width: 100%">' +
+ ' <button id="sl-2" type="button" class="btn btn-primary" data-toggle="tooltip" title="Switch to higher quality" style="width: 33%">SL 2</button>' +
+ ' <button id="sl-1" type="button" class="btn btn-primary" data-toggle="tooltip" title="Switch to normal quality" style="width: 33%">SL 1</button>' +
+ ' <button id="sl-0" type="button" class="btn btn-primary" data-toggle="tooltip" title="Switch to lower quality" style="width: 34%">SL 0</button>' +
+ ' </div>' +
+ ' </div>' +
+ ' <div class"row">' +
+ ' <div class="btn-group btn-group-xs hide" style="width: 100%">' +
+ ' <button id="tl-2" type="button" class="btn btn-primary" data-toggle="tooltip" title="Cap to temporal layer 2" style="width: 34%">TL 2</button>' +
+ ' <button id="tl-1" type="button" class="btn btn-primary" data-toggle="tooltip" title="Cap to temporal layer 1" style="width: 33%">TL 1</button>' +
+ ' <button id="tl-0" type="button" class="btn btn-primary" data-toggle="tooltip" title="Cap to temporal layer 0" style="width: 33%">TL 0</button>' +
+ ' </div>' +
+ ' </div>' +
+ '</div>');
+ // Enable the simulcast selection buttons
+ $('#sl-0').removeClass('btn-primary btn-success').addClass('btn-primary')
+ .unbind('click').click(function() {
+ toastr.info("Switching simulcast substream, wait for it... (lower quality)", null, {timeOut: 2000});
+ if(!$('#sl-2').hasClass('btn-success'))
+ $('#sl-2').removeClass('btn-primary btn-info').addClass('btn-primary');
+ if(!$('#sl-1').hasClass('btn-success'))
+ $('#sl-1').removeClass('btn-primary btn-info').addClass('btn-primary');
+ $('#sl-0').removeClass('btn-primary btn-info btn-success').addClass('btn-info');
+ streaming.send({message: { request: "configure", substream: 0 }});
+ });
+ $('#sl-1').removeClass('btn-primary btn-success').addClass('btn-primary')
+ .unbind('click').click(function() {
+ toastr.info("Switching simulcast substream, wait for it... (normal quality)", null, {timeOut: 2000});
+ if(!$('#sl-2').hasClass('btn-success'))
+ $('#sl-2').removeClass('btn-primary btn-info').addClass('btn-primary');
+ $('#sl-1').removeClass('btn-primary btn-info btn-success').addClass('btn-info');
+ if(!$('#sl-0').hasClass('btn-success'))
+ $('#sl-0').removeClass('btn-primary btn-info').addClass('btn-primary');
+ streaming.send({message: { request: "configure", substream: 1 }});
+ });
+ $('#sl-2').removeClass('btn-primary btn-success').addClass('btn-primary')
+ .unbind('click').click(function() {
+ toastr.info("Switching simulcast substream, wait for it... (higher quality)", null, {timeOut: 2000});
+ $('#sl-2').removeClass('btn-primary btn-info btn-success').addClass('btn-info');
+ if(!$('#sl-1').hasClass('btn-success'))
+ $('#sl-1').removeClass('btn-primary btn-info').addClass('btn-primary');
+ if(!$('#sl-0').hasClass('btn-success'))
+ $('#sl-0').removeClass('btn-primary btn-info').addClass('btn-primary');
+ streaming.send({message: { request: "configure", substream: 2 }});
+ });
+ if(!temporal) // No temporal layer support
+ return;
+ $('#tl-0').parent().removeClass('hide');
+ $('#tl-0').removeClass('btn-primary btn-success').addClass('btn-primary')
+ .unbind('click').click(function() {
+ toastr.info("Capping simulcast temporal layer, wait for it... (lowest FPS)", null, {timeOut: 2000});
+ if(!$('#tl-2').hasClass('btn-success'))
+ $('#tl-2').removeClass('btn-primary btn-info').addClass('btn-primary');
+ if(!$('#tl-1').hasClass('btn-success'))
+ $('#tl-1').removeClass('btn-primary btn-info').addClass('btn-primary');
+ $('#tl-0').removeClass('btn-primary btn-info btn-success').addClass('btn-info');
+ streaming.send({message: { request: "configure", temporal: 0 }});
+ });
+ $('#tl-1').removeClass('btn-primary btn-success').addClass('btn-primary')
+ .unbind('click').click(function() {
+ toastr.info("Capping simulcast temporal layer, wait for it... (medium FPS)", null, {timeOut: 2000});
+ if(!$('#tl-2').hasClass('btn-success'))
+ $('#tl-2').removeClass('btn-primary btn-info').addClass('btn-primary');
+ $('#tl-1').removeClass('btn-primary btn-info').addClass('btn-info');
+ if(!$('#tl-0').hasClass('btn-success'))
+ $('#tl-0').removeClass('btn-primary btn-info').addClass('btn-primary');
+ streaming.send({message: { request: "configure", temporal: 1 }});
+ });
+ $('#tl-2').removeClass('btn-primary btn-success').addClass('btn-primary')
+ .unbind('click').click(function() {
+ toastr.info("Capping simulcast temporal layer, wait for it... (highest FPS)", null, {timeOut: 2000});
+ $('#tl-2').removeClass('btn-primary btn-info btn-success').addClass('btn-info');
+ if(!$('#tl-1').hasClass('btn-success'))
+ $('#tl-1').removeClass('btn-primary btn-info').addClass('btn-primary');
+ if(!$('#tl-0').hasClass('btn-success'))
+ $('#tl-0').removeClass('btn-primary btn-info').addClass('btn-primary');
+ streaming.send({message: { request: "configure", temporal: 2 }});
+ });
+}
+
+function updateSimulcastButtons(substream, temporal) {
+ // Check the substream
+ if(substream === 0) {
+ toastr.success("Switched simulcast substream! (lower quality)", null, {timeOut: 2000});
+ $('#sl-2').removeClass('btn-primary btn-success').addClass('btn-primary');
+ $('#sl-1').removeClass('btn-primary btn-success').addClass('btn-primary');
+ $('#sl-0').removeClass('btn-primary btn-info btn-success').addClass('btn-success');
+ } else if(substream === 1) {
+ toastr.success("Switched simulcast substream! (normal quality)", null, {timeOut: 2000});
+ $('#sl-2').removeClass('btn-primary btn-success').addClass('btn-primary');
+ $('#sl-1').removeClass('btn-primary btn-info btn-success').addClass('btn-success');
+ $('#sl-0').removeClass('btn-primary btn-success').addClass('btn-primary');
+ } else if(substream === 2) {
+ toastr.success("Switched simulcast substream! (higher quality)", null, {timeOut: 2000});
+ $('#sl-2').removeClass('btn-primary btn-info btn-success').addClass('btn-success');
+ $('#sl-1').removeClass('btn-primary btn-success').addClass('btn-primary');
+ $('#sl-0').removeClass('btn-primary btn-success').addClass('btn-primary');
+ }
+ // Check the temporal layer
+ if(temporal === 0) {
+ toastr.success("Capped simulcast temporal layer! (lowest FPS)", null, {timeOut: 2000});
+ $('#tl-2').removeClass('btn-primary btn-success').addClass('btn-primary');
+ $('#tl-1').removeClass('btn-primary btn-success').addClass('btn-primary');
+ $('#tl-0').removeClass('btn-primary btn-info btn-success').addClass('btn-success');
+ } else if(temporal === 1) {
+ toastr.success("Capped simulcast temporal layer! (medium FPS)", null, {timeOut: 2000});
+ $('#tl-2').removeClass('btn-primary btn-success').addClass('btn-primary');
+ $('#tl-1').removeClass('btn-primary btn-info btn-success').addClass('btn-success');
+ $('#tl-0').removeClass('btn-primary btn-success').addClass('btn-primary');
+ } else if(temporal === 2) {
+ toastr.success("Capped simulcast temporal layer! (highest FPS)", null, {timeOut: 2000});
+ $('#tl-2').removeClass('btn-primary btn-info btn-success').addClass('btn-success');
+ $('#tl-1').removeClass('btn-primary btn-success').addClass('btn-primary');
+ $('#tl-0').removeClass('btn-primary btn-success').addClass('btn-primary');
+ }
+}
+
+// Helpers to create SVC-related UI for a new viewer
+function addSvcButtons() {
+ if($('#svc').length > 0)
+ return;
+ $('#curres').parent().append(
+ '<div id="svc" class="btn-group-vertical btn-group-vertical-xs pull-right">' +
+ ' <div class"row">' +
+ ' <div class="btn-group btn-group-xs" style="width: 100%">' +
+ ' <button id="sl-1" type="button" class="btn btn-primary" data-toggle="tooltip" title="Switch to normal resolution" style="width: 50%">SL 1</button>' +
+ ' <button id="sl-0" type="button" class="btn btn-primary" data-toggle="tooltip" title="Switch to low resolution" style="width: 50%">SL 0</button>' +
+ ' </div>' +
+ ' </div>' +
+ ' <div class"row">' +
+ ' <div class="btn-group btn-group-xs" style="width: 100%">' +
+ ' <button id="tl-2" type="button" class="btn btn-primary" data-toggle="tooltip" title="Cap to temporal layer 2 (high FPS)" style="width: 34%">TL 2</button>' +
+ ' <button id="tl-1" type="button" class="btn btn-primary" data-toggle="tooltip" title="Cap to temporal layer 1 (medium FPS)" style="width: 33%">TL 1</button>' +
+ ' <button id="tl-0" type="button" class="btn btn-primary" data-toggle="tooltip" title="Cap to temporal layer 0 (low FPS)" style="width: 33%">TL 0</button>' +
+ ' </div>' +
+ ' </div>' +
+ '</div>'
+ );
+ // Enable the VP8 simulcast selection buttons
+ $('#sl-0').removeClass('btn-primary btn-success').addClass('btn-primary')
+ .unbind('click').click(function() {
+ toastr.info("Switching SVC spatial layer, wait for it... (low resolution)", null, {timeOut: 2000});
+ if(!$('#sl-1').hasClass('btn-success'))
+ $('#sl-1').removeClass('btn-primary btn-info').addClass('btn-primary');
+ $('#sl-0').removeClass('btn-primary btn-info btn-success').addClass('btn-info');
+ streaming.send({message: { request: "configure", spatial_layer: 0 }});
+ });
+ $('#sl-1').removeClass('btn-primary btn-success').addClass('btn-primary')
+ .unbind('click').click(function() {
+ toastr.info("Switching SVC spatial layer, wait for it... (normal resolution)", null, {timeOut: 2000});
+ $('#sl-1').removeClass('btn-primary btn-info btn-success').addClass('btn-info');
+ if(!$('#sl-0').hasClass('btn-success'))
+ $('#sl-0').removeClass('btn-primary btn-info').addClass('btn-primary');
+ streaming.send({message: { request: "configure", spatial_layer: 1 }});
+ });
+ $('#tl-0').removeClass('btn-primary btn-success').addClass('btn-primary')
+ .unbind('click').click(function() {
+ toastr.info("Capping SVC temporal layer, wait for it... (lowest FPS)", null, {timeOut: 2000});
+ if(!$('#tl-2').hasClass('btn-success'))
+ $('#tl-2').removeClass('btn-primary btn-info').addClass('btn-primary');
+ if(!$('#tl-1').hasClass('btn-success'))
+ $('#tl-1').removeClass('btn-primary btn-info').addClass('btn-primary');
+ $('#tl-0').removeClass('btn-primary btn-info btn-success').addClass('btn-info');
+ streaming.send({message: { request: "configure", temporal_layer: 0 }});
+ });
+ $('#tl-1').removeClass('btn-primary btn-success').addClass('btn-primary')
+ .unbind('click').click(function() {
+ toastr.info("Capping SVC temporal layer, wait for it... (medium FPS)", null, {timeOut: 2000});
+ if(!$('#tl-2').hasClass('btn-success'))
+ $('#tl-2').removeClass('btn-primary btn-info').addClass('btn-primary');
+ $('#tl-1').removeClass('btn-primary btn-info').addClass('btn-info');
+ if(!$('#tl-0').hasClass('btn-success'))
+ $('#tl-0').removeClass('btn-primary btn-info').addClass('btn-primary');
+ streaming.send({message: { request: "configure", temporal_layer: 1 }});
+ });
+ $('#tl-2').removeClass('btn-primary btn-success').addClass('btn-primary')
+ .unbind('click').click(function() {
+ toastr.info("Capping SVC temporal layer, wait for it... (highest FPS)", null, {timeOut: 2000});
+ $('#tl-2').removeClass('btn-primary btn-info btn-success').addClass('btn-info');
+ if(!$('#tl-1').hasClass('btn-success'))
+ $('#tl-1').removeClass('btn-primary btn-info').addClass('btn-primary');
+ if(!$('#tl-0').hasClass('btn-success'))
+ $('#tl-0').removeClass('btn-primary btn-info').addClass('btn-primary');
+ streaming.send({message: { request: "configure", temporal_layer: 2 }});
+ });
+}
+
+function updateSvcButtons(spatial, temporal) {
+ // Check the spatial layer
+ if(spatial === 0) {
+ toastr.success("Switched SVC spatial layer! (lower resolution)", null, {timeOut: 2000});
+ $('#sl-1').removeClass('btn-primary btn-success').addClass('btn-primary');
+ $('#sl-0').removeClass('btn-primary btn-info btn-success').addClass('btn-success');
+ } else if(spatial === 1) {
+ toastr.success("Switched SVC spatial layer! (normal resolution)", null, {timeOut: 2000});
+ $('#sl-1').removeClass('btn-primary btn-info btn-success').addClass('btn-success');
+ $('#sl-0').removeClass('btn-primary btn-success').addClass('btn-primary');
+ }
+ // Check the temporal layer
+ if(temporal === 0) {
+ toastr.success("Capped SVC temporal layer! (lowest FPS)", null, {timeOut: 2000});
+ $('#tl-2').removeClass('btn-primary btn-success').addClass('btn-primary');
+ $('#tl-1').removeClass('btn-primary btn-success').addClass('btn-primary');
+ $('#tl-0').removeClass('btn-primary btn-info btn-success').addClass('btn-success');
+ } else if(temporal === 1) {
+ toastr.success("Capped SVC temporal layer! (medium FPS)", null, {timeOut: 2000});
+ $('#tl-2').removeClass('btn-primary btn-success').addClass('btn-primary');
+ $('#tl-1').removeClass('btn-primary btn-info btn-success').addClass('btn-success');
+ $('#tl-0').removeClass('btn-primary btn-success').addClass('btn-primary');
+ } else if(temporal === 2) {
+ toastr.success("Capped SVC temporal layer! (highest FPS)", null, {timeOut: 2000});
+ $('#tl-2').removeClass('btn-primary btn-info btn-success').addClass('btn-success');
+ $('#tl-1').removeClass('btn-primary btn-success').addClass('btn-primary');
+ $('#tl-0').removeClass('btn-primary btn-success').addClass('btn-primary');
+ }
+}
+
+function checkEnter(field, event) {
+ var theCode = event.keyCode ? event.keyCode : event.which ? event.which : event.charCode;
+ if(theCode == 13) {
+ registerUsername();
+ return false;
+ } else {
+ return true;
+ }
+}
+
+function registerUsername() {
+ if($('#username').length === 0) {
+ // Create fields to register
+ $('#register').click(registerUsername);
+ $('#username').focus();
+ } else {
+ // Try a registration
+ $('#username').attr('disabled', true);
+ $('#register').attr('disabled', true).unbind('click');
+ var username = $('#username').val();
+ if(username === "") {
+ $('#you')
+ .removeClass().addClass('label label-warning')
+ .html("Insert your display name (e.g., pippo)");
+ $('#username').removeAttr('disabled');
+ $('#register').removeAttr('disabled').click(registerUsername);
+ return;
+ }
+ if(/[^a-zA-Z0-9]/.test(username)) {
+ $('#you')
+ .removeClass().addClass('label label-warning')
+ .html('Input is not alphanumeric');
+ $('#username').removeAttr('disabled').val("");
+ $('#register').removeAttr('disabled').click(registerUsername);
+ return;
+ }
+ var register = { "request": "join", "room": myroom, "display": username };
+ myusername = username;
+ mixertest.send({"message": register});
+ }
+}
diff --git a/website/mic/index.html b/website/mic/index.html
new file mode 100644
index 0000000..1d9b16f
--- /dev/null
+++ b/website/mic/index.html
@@ -0,0 +1,130 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<title>Streaming with feedback</title>
+<script type="text/javascript" src="../js/webrtc-adapter/adapter.min.js" ></script>
+<script type="text/javascript" src="../js/jquery/jquery.min.js" ></script>
+<script type="text/javascript" src="../js/jquery-blockui/jquery.blockUI.js" ></script>
+<script type="text/javascript" src="../js/bootstrap/js/bootstrap.min.js"></script>
+<script type="text/javascript" src="../js/bootbox/bootbox.min.js"></script>
+<script type="text/javascript" src="../js/spin.js/spin.min.js"></script>
+<script type="text/javascript" src="../ext/toastr.min.js"></script>
+<script type="text/javascript" src="../js/janus/janus.min.js" ></script>
+<script type="text/javascript" src="../mic.js"></script>
+<link rel="stylesheet" href="../js/bootswatch/cerulean/bootstrap.min.css" type="text/css"/>
+<link rel="stylesheet" href="../css/style.css" type="text/css"/>
+<link rel="stylesheet" href="../fonts/font-awesome/css/font-awesome.min.css" type="text/css"/>
+<link rel="stylesheet" href="../ext/toastr.min.css"/">
+</head>
+<body>
+
+<div class="container">
+ <div class="row">
+ <div class="col-md-12">
+ <div class="page-header">
+ <h1>Streaming with feedback
+ <button class="btn btn-default" autocomplete="off" id="start">Start</button>
+ </h1>
+ </div>
+ <div class="container" id="details">
+ <div class="row">
+ <div class="col-md-12">
+ <h3>How it works</h3>
+ <p>This service consist of two parts:</p>
+ <p><ol>
+ <li>A live streaming of a conference room.</li>
+ <li>A live audio feed back to same conference room.</li>
+ </ol></p>
+ <p>The audio feed is shared among all participants.<pi>
+ <p>Press the <code>Start</code> button above to launch the service,
+ choose the stream you're interested in and press the <code>Watch</code> button to start the playout.
+ Stopping it will allow you to switch to a different one.</p>
+ </div>
+ </div>
+ </div>
+ <div class="container hide" id="streams">
+ <div class="row">
+ <div class="col-md-6">
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ <h3 class="panel-title">Streams <i id="update-streams" class="fa fa-refresh" title="Update list of streams" style="cursor: pointer;"></i></h3>
+ </div>
+ <div class="panel-body" id="list">
+ <div class="btn-group btn-group-sm">
+ <button class="btn btn-primary" autocomplete="off" id="watch">Watch or Listen</button>
+ <div class="btn-group btn-group-sm">
+ <button autocomplete="off" id="streamset" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
+ Streams list<span class="caret"></span>
+ </button>
+ <ul id="streamslist" class="dropdown-menu" role="menu">
+ </ul>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="col-md-6">
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ <h3 class="panel-title">Stream
+ <span class="label label-info hide" id="status"></span>
+ <span class="label label-primary hide" id="curres"></span>
+ <span class="label label-info hide" id="curbitrate"></span>
+ </h3>
+ </div>
+ <div class="panel-body" id="stream"></div>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="container hide" id="audiojoin">
+ <div class="row">
+ <span class="label label-info" id="you"></span>
+ <div class="col-md-12" id="controls">
+ <div class="input-group margin-bottom-md hide" id="registernow">
+ <span class="input-group-addon">@</span>
+ <input class="form-control" type="text" placeholder="Choose a display name" autocomplete="off" id="username" onkeypress="return checkEnter(this, event);"></input>
+ <span class="input-group-btn">
+ <button class="btn btn-success" autocomplete="off" id="register">Join the room</button>
+ </span>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="container hide" id="room">
+ <div class="row">
+ <div class="col-md-6">
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ <h3 class="panel-title">Participants <span class="label label-info hide" id="participant"></span>
+ <button class="btn-xs btn-danger hide pull-right" autocomplete="off" id="toggleaudio">Mute</button></h3>
+ </div>
+ <div class="panel-body">
+ <ul id="list" class="list-group">
+ </ul>
+ </div>
+ </div>
+ </div>
+ <div class="col-md-6">
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ <h3 class="panel-title">Mixed Audio</span></h3>
+ </div>
+ <div class="panel-body" id="mixedaudio"></div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <hr>
+ <div class="footer">
+ </div>
+</div>
+
+</body>
+</html>