[Pkg-voip-commits] [janus] 19/163: Reorganized simulcast-related JS code to make integration cleaner

Jonas Smedegaard dr at jones.dk
Sat Oct 28 01:22:04 UTC 2017


This is an automated email from the git hooks/post-receive script.

js pushed a commit to annotated tag debian/0.2.5-1
in repository janus.

commit 05f1a963813831ca7be5ab32c9523d6a7fc0156f
Author: Lorenzo Miniero <lminiero at gmail.com>
Date:   Mon Jul 10 17:10:10 2017 +0200

    Reorganized simulcast-related JS code to make integration cleaner
---
 html/devicetest.html    |  22 +----
 html/devicetest.js      | 225 +++++++++++++++++++++++++++------------------
 html/echotest.html      |  28 ++----
 html/echotest.js        | 239 ++++++++++++++++++++++++++---------------------
 html/videoroomtest.html |  11 +++
 html/videoroomtest.js   | 240 +++++++++++++++++++++++++++---------------------
 6 files changed, 428 insertions(+), 337 deletions(-)

diff --git a/html/devicetest.html b/html/devicetest.html
index fb8a075..ab0753b 100644
--- a/html/devicetest.html
+++ b/html/devicetest.html
@@ -55,8 +55,10 @@
 						pretty much as the regular Echo Test demo already does. Once done,
 						you'll be able to individually change the capture audio and/or video
 						device: this will result in the Echo Test channel being reset and
-						recreated, in order to use the device(s) you selected instead.</p>
-						<p>The feature exploits a functionality made available in the
+						recreated, in order to use the device(s) you selected instead. Just
+						as with the regular Echo Test demo, the <code>?simulcast=true</code>
+						query string will allow you to test simulcasting as well.</p>
+						<p>The demo exploits a functionality made available in the
 						<code>janus.js</code> library, meaning you should be able to
 						easily adapt all the other demos to follow the same approach as
 						well, and that you'll be able to do the same in your Janus-based
@@ -120,22 +122,6 @@
 								<h3 class="panel-title">Remote Stream
 									<span class="label label-primary hide" id="curres"></span>
 									<span class="label label-info hide" id="curbitrate"></span>
-									<div id="simulcast" class="btn-group-vertical btn-group-vertical-xs pull-right hide">
-										<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" 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>
 									<div class="btn-group btn-group-xs pull-right hide" id="output-devices">
 										<div class="btn-group btn-group-xs">
 											<button id="outputdeviceset" autocomplete="off" class="btn btn-primary dropdown-toggle" data-toggle="dropdown">
diff --git a/html/devicetest.js b/html/devicetest.js
index 52c372e..a8430c7 100644
--- a/html/devicetest.js
+++ b/html/devicetest.js
@@ -58,6 +58,8 @@ var spinner = null;
 
 var audioenabled = false;
 var videoenabled = false;
+
+var doSimulcast = (getQueryStringValue("simulcast") === "yes" || getQueryStringValue("simulcast") === "true");
 var simulcastStarted = false;
 
 // Helper method to prepare a UI selection of the available devices
@@ -147,8 +149,9 @@ function restartCapture() {
 				data: true	// Let's negotiate data channels as well
 			},
 			// If you want to test simulcasting (Chrome and Firefox only), then
-			// uncomment the "simulcast:true," line: new buttons will appear
-			simulcast: true,
+			// pass a ?simulcast=true when opening this demo page: it will turn
+			// the following 'simulcast' property to pass to janus.js to true
+			simulcast: doSimulcast,
 			success: function(jsep) {
 				Janus.debug("Got SDP!");
 				Janus.debug(jsep);
@@ -278,90 +281,10 @@ $(document).ready(function() {
 									if((substream !== null && substream !== undefined) || (temporal !== null && temporal !== undefined)) {
 										if(!simulcastStarted) {
 											simulcastStarted = true;
-											$('#simulcast').removeClass('hide');
-											// Enable the VP8 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});
-													$('#sl-2').removeClass('btn-primary btn-info btn-success').addClass('btn-primary');
-													$('#sl-1').removeClass('btn-primary btn-info btn-success').addClass('btn-primary');
-													$('#sl-0').removeClass('btn-primary btn-info btn-success').addClass('btn-info');
-													echotest.send({message: { substream: 0 }});
-												});
-											$('#sl-1').removeClass('btn-primary btn-success').addClass('btn-success')
-												.unbind('click').click(function() {
-													toastr.info("Switching simulcast substream, wait for it... (normal quality)", null, {timeOut: 2000});
-													$('#sl-2').removeClass('btn-primary btn-info btn-success').addClass('btn-primary');
-													$('#sl-1').removeClass('btn-primary btn-info btn-success').addClass('btn-info');
-													$('#sl-0').removeClass('btn-primary btn-info btn-success').addClass('btn-primary');
-													echotest.send({message: { substream: 1 }});
-												});
-											$('#sl-2').removeClass('btn-primary btn-success').addClass('btn-success')
-												.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');
-													$('#sl-1').removeClass('btn-primary btn-info btn-success').addClass('btn-primary');
-													$('#sl-0').removeClass('btn-primary btn-info btn-success').addClass('btn-primary');
-													echotest.send({message: { substream: 2 }});
-												});
-											$('#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});
-													$('#tl-2').removeClass('btn-primary btn-info btn-success').addClass('btn-primary');
-													$('#tl-1').removeClass('btn-primary btn-info btn-success').addClass('btn-primary');
-													$('#tl-0').removeClass('btn-primary btn-info btn-success').addClass('btn-info');
-													echotest.send({message: { temporal: 0 }});
-												});
-											$('#tl-1').removeClass('btn-primary btn-success').addClass('btn-success')
-												.unbind('click').click(function() {
-													toastr.info("Capping simulcast temporal layer, wait for it... (medium FPS)", null, {timeOut: 2000});
-													$('#tl-2').removeClass('btn-primary btn-info btn-success').addClass('btn-primary');
-													$('#tl-1').removeClass('btn-primary btn-info btn-success').addClass('btn-info');
-													$('#tl-0').removeClass('btn-primary btn-info btn-success').addClass('btn-primary');
-													echotest.send({message: { temporal: 1 }});
-												});
-											$('#tl-2').removeClass('btn-primary btn-success').addClass('btn-success')
-												.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');
-													$('#tl-1').removeClass('btn-primary btn-info btn-success').addClass('btn-primary');
-													$('#tl-0').removeClass('btn-primary btn-info btn-success').addClass('btn-primary');
-													echotest.send({message: { temporal: 2 }});
-												});
+											addSimulcastButtons();
 										}
 										// We just received notice that there's been a switch, update the buttons
-										if(substream === 0) {
-											toastr.success("Switched simulcast substream! (lower quality)", null, {timeOut: 2000});
-											$('#sl-2').removeClass('btn-primary btn-info btn-success').addClass('btn-primary');
-											$('#sl-1').removeClass('btn-primary btn-info 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-info btn-success').addClass('btn-primary');
-											$('#sl-1').removeClass('btn-primary btn-info btn-success').addClass('btn-success');
-											$('#sl-0').removeClass('btn-primary btn-info 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-info btn-success').addClass('btn-primary');
-											$('#sl-0').removeClass('btn-primary btn-info btn-success').addClass('btn-primary');
-										}
-										if(temporal === 0) {
-											toastr.success("Capped simulcast temporal layer! (lowest FPS)", null, {timeOut: 2000});
-											$('#tl-2').removeClass('btn-primary btn-info btn-success').addClass('btn-primary');
-											$('#tl-1').removeClass('btn-primary btn-info 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-info btn-success').addClass('btn-primary');
-											$('#tl-1').removeClass('btn-primary btn-info btn-success').addClass('btn-success');
-											$('#tl-0').removeClass('btn-primary btn-info 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-info btn-success').addClass('btn-primary');
-											$('#tl-0').removeClass('btn-primary btn-info btn-success').addClass('btn-primary');
-										}
+										updateSimulcastButtons(substream, temporal);
 									}
 								},
 								onlocalstream: function(stream) {
@@ -514,13 +437,7 @@ $(document).ready(function() {
 									$('#datarecv').val('');
 									$('#outputdeviceset').html('Output device<span class="caret"></span>');
 									simulcastStarted = false;
-									$('#simulcast').addClass('hide');
-									$('#sl-0').unbind('click');
-									$('#sl-1').unbind('click');
-									$('#sl-2').unbind('click');
-									$('#tl-0').unbind('click');
-									$('#tl-1').unbind('click');
-									$('#tl-2').unbind('click');
+									$('#simulcast').remove();
 								}
 							});
 					},
@@ -560,3 +477,129 @@ function sendData() {
 		success: function() { $('#datasend').val(''); },
 	});
 }
+
+// Helper to parse query string
+function getQueryStringValue(name) {
+	name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
+	var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
+		results = regex.exec(location.search);
+	return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
+}
+
+// Helpers to create Simulcast-related UI, if enabled
+function addSimulcastButtons() {
+	$(	'<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" 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>').insertBefore('#output-devices');
+	// Enable the VP8 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');
+			echotest.send({message: { 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');
+			echotest.send({message: { 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');
+			echotest.send({message: { substream: 2 }});
+		});
+	$('#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');
+			echotest.send({message: { 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');
+			echotest.send({message: { 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');
+			echotest.send({message: { 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');
+	}
+}
diff --git a/html/echotest.html b/html/echotest.html
index 62b5f05..e8d379e 100644
--- a/html/echotest.html
+++ b/html/echotest.html
@@ -57,9 +57,12 @@
 						back to you. You can also try and cap the bitrate: such control
 						will tell the gateway to manipulate the RTCP REMB packets passing
 						through, in order to simulate a bandwidth limitation. In case
-						Simulcasting has been enabled, buttons will appear to allow you
-						to switch between lower and higher quality: notice that you may
-						have to increase the bandwidth to have the quality video appear.</p>
+						you're interested in testing simulcasting, add the <code>?simulcast=true</code>
+						query string to the url of this page and reload it: buttons will appear
+						to allow you to try and switch between lower and higher quality
+						versions of the video you're capturing: notice that you may have
+						to increase the bandwidth indicator to have the higher quality
+						versions appear, as the browser will not encode them otherwise.</p>
 						<p>Finally, this demo also includes Data Channels: whatever you
 						write in the text box under your local video, will be sent via
 						Data Channels to the plugins, modified by adding a fixed prefix,
@@ -109,24 +112,7 @@
 					<div class="col-md-6">
 						<div class="panel panel-default">
 							<div class="panel-heading">
-								<h3 class="panel-title">Remote Stream <span class="label label-primary hide" id="curres"></span> <span class="label label-info hide" id="curbitrate"></span>
-									<div id="simulcast" class="btn-group-vertical btn-group-vertical-xs pull-right hide">
-										<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" 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>
-								</h3>
+								<h3 class="panel-title">Remote Stream <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="videoright"></div>
 						</div>
diff --git a/html/echotest.js b/html/echotest.js
index 8aab102..44ba31a 100644
--- a/html/echotest.js
+++ b/html/echotest.js
@@ -58,6 +58,8 @@ var spinner = null;
 
 var audioenabled = false;
 var videoenabled = false;
+
+var doSimulcast = (getQueryStringValue("simulcast") === "yes" || getQueryStringValue("simulcast") === "true");
 var simulcastStarted = false;
 
 $(document).ready(function() {
@@ -107,8 +109,9 @@ $(document).ready(function() {
 											// No media provided: by default, it's sendrecv for audio and video
 											media: { data: true },	// Let's negotiate data channels as well
 											// If you want to test simulcasting (Chrome and Firefox only), then
-											// uncomment the "simulcast:true," line: new buttons will appear
-											simulcast: true,
+											// pass a ?simulcast=true when opening this demo page: it will turn
+											// the following 'simulcast' property to pass to janus.js to true
+											simulcast: doSimulcast,
 											success: function(jsep) {
 												Janus.debug("Got SDP!");
 												Janus.debug(jsep);
@@ -203,102 +206,10 @@ $(document).ready(function() {
 									if((substream !== null && substream !== undefined) || (temporal !== null && temporal !== undefined)) {
 										if(!simulcastStarted) {
 											simulcastStarted = true;
-											$('#simulcast').removeClass('hide');
-											// Enable the VP8 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');
-													echotest.send({message: { 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');
-													echotest.send({message: { 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');
-													echotest.send({message: { substream: 2 }});
-												});
-											$('#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');
-													echotest.send({message: { 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');
-													echotest.send({message: { 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');
-													echotest.send({message: { temporal: 2 }});
-												});
+											addSimulcastButtons();
 										}
 										// We just received notice that there's been a switch, update the buttons
-										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');
-										}
-										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');
-										}
+										updateSimulcastButtons(substream, temporal);
 									}
 								},
 								onlocalstream: function(stream) {
@@ -440,13 +351,8 @@ $(document).ready(function() {
 									$('#curbitrate').hide();
 									$('#curres').hide();
 									$('#datasend').attr('disabled', true);
-									$('#simulcast').addClass('hide');
-									$('#sl-0').unbind('click');
-									$('#sl-1').unbind('click');
-									$('#sl-2').unbind('click');
-									$('#tl-0').unbind('click');
-									$('#tl-1').unbind('click');
-									$('#tl-2').unbind('click');
+									simulcastStarted = false;
+									$('#simulcast').remove();
 								}
 							});
 					},
@@ -486,3 +392,130 @@ function sendData() {
 		success: function() { $('#datasend').val(''); },
 	});
 }
+
+// Helper to parse query string
+function getQueryStringValue(name) {
+	name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
+	var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
+		results = regex.exec(location.search);
+	return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
+}
+
+// Helpers to create Simulcast-related UI, if enabled
+function addSimulcastButtons() {
+	$('#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" 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 VP8 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');
+			echotest.send({message: { 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');
+			echotest.send({message: { 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');
+			echotest.send({message: { substream: 2 }});
+		});
+	$('#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');
+			echotest.send({message: { 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');
+			echotest.send({message: { 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');
+			echotest.send({message: { 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');
+	}
+}
diff --git a/html/videoroomtest.html b/html/videoroomtest.html
index 67b5d4d..68625bc 100644
--- a/html/videoroomtest.html
+++ b/html/videoroomtest.html
@@ -54,6 +54,17 @@
 						No mixing is involved: all media are just relayed in a publisher/subscriber
 						approach. This means that the plugin acts as a SFU (Selective Forwarding Unit)
 						rather than an MCU (Multipoint Control Unit).</p>
+						<p>If you're interested in testing how simulcasting can be used within
+						the context of a videoconferencing application, just pass the
+						<code>?simulcast=true</code> query string to the url of this page and
+						reload it. If you're using a browser that does support simulcasting
+						(Chrome or Firefox) and the room is configured to force VP8, you'll
+						send multiple qualities of the video you're capturing. Notice that
+						simulcasting will only occur if the browser thinks there is enough
+						bandwidth, so you'll have to play with the Bandwidth selector to
+						increase it. New buttons to play with the feature will automatically
+						appear for viewers when receiving any simulcasted stream. Notice that
+						no simulcast support is needed for watching, only for publishing.</p>
 						<p>To use the demo, just insert a username to join the default room that
 						is configured. This will add you to the list of participants, and allow
 						you to automatically send your audio/video frames and receive the other
diff --git a/html/videoroomtest.js b/html/videoroomtest.js
index 454f3cb..a241145 100644
--- a/html/videoroomtest.js
+++ b/html/videoroomtest.js
@@ -64,6 +64,7 @@ var mypvtid = null;
 var feeds = [];
 var bitrateTimer = [];
 
+var doSimulcast = (getQueryStringValue("simulcast") === "yes" || getQueryStringValue("simulcast") === "true");
 
 $(document).ready(function() {
 	// Initialize the library (all console debuggers enabled)
@@ -369,9 +370,9 @@ function publishOwnFeed(useAudio) {
 			// Add data:true here if you want to publish datachannels as well
 			media: { audioRecv: false, videoRecv: false, audioSend: useAudio, videoSend: true },	// Publishers are sendonly
 			// If you want to test simulcasting (Chrome and Firefox only), then
-			// uncomment the "simulcast:true," line: new buttons will appear
-			// for the viewers subscribing to this stream remotely
-			simulcast: true,
+			// pass a ?simulcast=true when opening this demo page: it will turn
+			// the following 'simulcast' property to pass to janus.js to true
+			simulcast: doSimulcast,
 			success: function(jsep) {
 				Janus.debug("Got publisher SDP!");
 				Janus.debug(jsep);
@@ -461,107 +462,10 @@ function newRemoteFeed(id, display) {
 							if(!remoteFeed.simulcastStarted) {
 								remoteFeed.simulcastStarted = true;
 								// Add some new buttons
-								$('#remote'+remoteFeed.rfindex).parent().append(
-									'<div id="simulcast'+remoteFeed.rfindex+'" 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'+remoteFeed.rfindex+'-2" type="button" class="btn btn-primary" data-toggle="tooltip" title="Switch to higher quality" style="width: 33%">SL 2</button>' +
-									'			<button id="sl'+remoteFeed.rfindex+'-1" type="button" class="btn btn-primary" data-toggle="tooltip" title="Switch to normal quality" style="width: 33%">SL 1</button>' +
-									'			<button id="sl'+remoteFeed.rfindex+'-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" style="width: 100%">' +
-									'			<button id="tl'+remoteFeed.rfindex+'-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'+remoteFeed.rfindex+'-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'+remoteFeed.rfindex+'-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 VP8 simulcast selection buttons
-								$('#sl'+remoteFeed.rfindex+'-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});
-										$('#sl'+remoteFeed.rfindex+'-2').removeClass('btn-primary btn-info btn-success').addClass('btn-primary');
-										$('#sl'+remoteFeed.rfindex+'-1').removeClass('btn-primary btn-info btn-success').addClass('btn-primary');
-										$('#sl'+remoteFeed.rfindex+'-0').removeClass('btn-primary btn-info btn-success').addClass('btn-info');
-										remoteFeed.send({message: { request: "configure", substream: 0 }});
-									});
-								$('#sl'+remoteFeed.rfindex+'-1').removeClass('btn-primary btn-success').addClass('btn-success')
-									.unbind('click').click(function() {
-										toastr.info("Switching simulcast substream, wait for it... (normal quality)", null, {timeOut: 2000});
-										$('#sl'+remoteFeed.rfindex+'-2').removeClass('btn-primary btn-info btn-success').addClass('btn-primary');
-										$('#sl'+remoteFeed.rfindex+'-1').removeClass('btn-primary btn-info btn-success').addClass('btn-info');
-										$('#sl'+remoteFeed.rfindex+'-0').removeClass('btn-primary btn-info btn-success').addClass('btn-primary');
-										remoteFeed.send({message: { request: "configure", substream: 1 }});
-									});
-								$('#sl'+remoteFeed.rfindex+'-2').removeClass('btn-primary btn-success').addClass('btn-success')
-									.unbind('click').click(function() {
-										toastr.info("Switching simulcast substream, wait for it... (higher quality)", null, {timeOut: 2000});
-										$('#sl'+remoteFeed.rfindex+'-2').removeClass('btn-primary btn-info btn-success').addClass('btn-info');
-										$('#sl'+remoteFeed.rfindex+'-1').removeClass('btn-primary btn-info btn-success').addClass('btn-primary');
-										$('#sl'+remoteFeed.rfindex+'-0').removeClass('btn-primary btn-info btn-success').addClass('btn-primary');
-										remoteFeed.send({message: { request: "configure", substream: 2 }});
-									});
-								$('#tl'+remoteFeed.rfindex+'-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});
-										$('#tl'+remoteFeed.rfindex+'-2').removeClass('btn-primary btn-info btn-success').addClass('btn-primary');
-										$('#tl'+remoteFeed.rfindex+'-1').removeClass('btn-primary btn-info btn-success').addClass('btn-primary');
-										$('#tl'+remoteFeed.rfindex+'-0').removeClass('btn-primary btn-info btn-success').addClass('btn-info');
-										remoteFeed.send({message: { request: "configure", temporal: 0 }});
-									});
-								$('#tl'+remoteFeed.rfindex+'-1').removeClass('btn-primary btn-success').addClass('btn-success')
-									.unbind('click').click(function() {
-										toastr.info("Capping simulcast temporal layer, wait for it... (medium FPS)", null, {timeOut: 2000});
-										$('#tl'+remoteFeed.rfindex+'-2').removeClass('btn-primary btn-info btn-success').addClass('btn-primary');
-										$('#tl'+remoteFeed.rfindex+'-1').removeClass('btn-primary btn-info btn-success').addClass('btn-info');
-										$('#tl'+remoteFeed.rfindex+'-0').removeClass('btn-primary btn-info btn-success').addClass('btn-primary');
-										remoteFeed.send({message: { request: "configure", temporal: 1 }});
-									});
-								$('#tl'+remoteFeed.rfindex+'-2').removeClass('btn-primary btn-success').addClass('btn-success')
-									.unbind('click').click(function() {
-										toastr.info("Capping simulcast temporal layer, wait for it... (highest FPS)", null, {timeOut: 2000});
-										$('#tl'+remoteFeed.rfindex+'-2').removeClass('btn-primary btn-info btn-success').addClass('btn-info');
-										$('#tl'+remoteFeed.rfindex+'-1').removeClass('btn-primary btn-info btn-success').addClass('btn-primary');
-										$('#tl'+remoteFeed.rfindex+'-0').removeClass('btn-primary btn-info btn-success').addClass('btn-primary');
-										remoteFeed.send({message: { request: "configure", temporal: 2 }});
-									});
+								addSimulcastButtons(remoteFeed.rfindex);
 							}
 							// We just received notice that there's been a switch, update the buttons
-							if(substream === 0) {
-								toastr.success("Switched simulcast substream! (lower quality)", null, {timeOut: 2000});
-								$('#sl'+remoteFeed.rfindex+'-2').removeClass('btn-primary btn-info btn-success').addClass('btn-primary');
-								$('#sl'+remoteFeed.rfindex+'-1').removeClass('btn-primary btn-info btn-success').addClass('btn-primary');
-								$('#sl'+remoteFeed.rfindex+'-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'+remoteFeed.rfindex+'-2').removeClass('btn-primary btn-info btn-success').addClass('btn-primary');
-								$('#sl'+remoteFeed.rfindex+'-1').removeClass('btn-primary btn-info btn-success').addClass('btn-success');
-								$('#sl'+remoteFeed.rfindex+'-0').removeClass('btn-primary btn-info btn-success').addClass('btn-primary');
-							} else if(substream === 2) {
-								toastr.success("Switched simulcast substream! (higher quality)", null, {timeOut: 2000});
-								$('#sl'+remoteFeed.rfindex+'-2').removeClass('btn-primary btn-info btn-success').addClass('btn-success');
-								$('#sl'+remoteFeed.rfindex+'-1').removeClass('btn-primary btn-info btn-success').addClass('btn-primary');
-								$('#sl'+remoteFeed.rfindex+'-0').removeClass('btn-primary btn-info btn-success').addClass('btn-primary');
-							}
-							if(temporal === 0) {
-								toastr.success("Capped simulcast temporal layer! (lowest FPS)", null, {timeOut: 2000});
-								$('#tl'+remoteFeed.rfindex+'-2').removeClass('btn-primary btn-info btn-success').addClass('btn-primary');
-								$('#tl'+remoteFeed.rfindex+'-1').removeClass('btn-primary btn-info btn-success').addClass('btn-primary');
-								$('#tl'+remoteFeed.rfindex+'-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'+remoteFeed.rfindex+'-2').removeClass('btn-primary btn-info btn-success').addClass('btn-primary');
-								$('#tl'+remoteFeed.rfindex+'-1').removeClass('btn-primary btn-info btn-success').addClass('btn-success');
-								$('#tl'+remoteFeed.rfindex+'-0').removeClass('btn-primary btn-info btn-success').addClass('btn-primary');
-							} else if(temporal === 2) {
-								toastr.success("Capped simulcast temporal layer! (highest FPS)", null, {timeOut: 2000});
-								$('#tl'+remoteFeed.rfindex+'-2').removeClass('btn-primary btn-info btn-success').addClass('btn-success');
-								$('#tl'+remoteFeed.rfindex+'-1').removeClass('btn-primary btn-info btn-success').addClass('btn-primary');
-								$('#tl'+remoteFeed.rfindex+'-0').removeClass('btn-primary btn-info btn-success').addClass('btn-primary');
-							}
+							updateSimulcastButtons(remoteFeed.rfindex, substream, temporal);
 						}
 					} else if(msg["error"] !== undefined && msg["error"] !== null) {
 						bootbox.alert(msg["error"]);
@@ -664,9 +568,137 @@ function newRemoteFeed(id, display) {
 				if(bitrateTimer[remoteFeed.rfindex] !== null && bitrateTimer[remoteFeed.rfindex] !== null) 
 					clearInterval(bitrateTimer[remoteFeed.rfindex]);
 				bitrateTimer[remoteFeed.rfindex] = null;
-				$('#sl'+remoteFeed.rfindex+'-1').unbind('click');
-				$('#sl'+remoteFeed.rfindex+'-0').unbind('click');
 				$('#simulcast'+remoteFeed.rfindex).remove();
 			}
 		});
 }
+
+// Helper to parse query string
+function getQueryStringValue(name) {
+	name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
+	var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
+		results = regex.exec(location.search);
+	return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
+}
+
+// Helpers to create Simulcast-related UI, if enabled
+function addSimulcastButtons(feed) {
+	var index = feed;
+	$('#remote'+index).parent().append(
+		'<div id="simulcast'+index+'" 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'+index+'-2" type="button" class="btn btn-primary" data-toggle="tooltip" title="Switch to higher quality" style="width: 33%">SL 2</button>' +
+		'			<button id="sl'+index+'-1" type="button" class="btn btn-primary" data-toggle="tooltip" title="Switch to normal quality" style="width: 33%">SL 1</button>' +
+		'			<button id="sl'+index+'-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" style="width: 100%">' +
+		'			<button id="tl'+index+'-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'+index+'-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'+index+'-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 VP8 simulcast selection buttons
+	$('#sl' + index + '-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' + index + '-2').hasClass('btn-success'))
+				$('#sl' + index + '-2').removeClass('btn-primary btn-info').addClass('btn-primary');
+			if(!$('#sl' + index + '-1').hasClass('btn-success'))
+				$('#sl' + index + '-1').removeClass('btn-primary btn-info').addClass('btn-primary');
+			$('#sl' + index + '-0').removeClass('btn-primary btn-info btn-success').addClass('btn-info');
+			feeds[index].send({message: { request: "configure", substream: 0 }});
+		});
+	$('#sl' + index + '-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' + index + '-2').hasClass('btn-success'))
+				$('#sl' + index + '-2').removeClass('btn-primary btn-info').addClass('btn-primary');
+			$('#sl' + index + '-1').removeClass('btn-primary btn-info btn-success').addClass('btn-info');
+			if(!$('#sl' + index + '-0').hasClass('btn-success'))
+				$('#sl' + index + '-0').removeClass('btn-primary btn-info').addClass('btn-primary');
+			feeds[index].send({message: { request: "configure", substream: 1 }});
+		});
+	$('#sl' + index + '-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' + index + '-2').removeClass('btn-primary btn-info btn-success').addClass('btn-info');
+			if(!$('#sl' + index + '-1').hasClass('btn-success'))
+				$('#sl' + index + '-1').removeClass('btn-primary btn-info').addClass('btn-primary');
+			if(!$('#sl' + index + '-0').hasClass('btn-success'))
+				$('#sl' + index + '-0').removeClass('btn-primary btn-info').addClass('btn-primary');
+			feeds[index].send({message: { request: "configure", substream: 2 }});
+		});
+	$('#tl' + index + '-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' + index + '-2').hasClass('btn-success'))
+				$('#tl' + index + '-2').removeClass('btn-primary btn-info').addClass('btn-primary');
+			if(!$('#tl' + index + '-1').hasClass('btn-success'))
+				$('#tl' + index + '-1').removeClass('btn-primary btn-info').addClass('btn-primary');
+			$('#tl' + index + '-0').removeClass('btn-primary btn-info btn-success').addClass('btn-info');
+			feeds[index].send({message: { request: "configure", temporal: 0 }});
+		});
+	$('#tl' + index + '-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' + index + '-2').hasClass('btn-success'))
+				$('#tl' + index + '-2').removeClass('btn-primary btn-info').addClass('btn-primary');
+			$('#tl' + index + '-1').removeClass('btn-primary btn-info').addClass('btn-info');
+			if(!$('#tl' + index + '-0').hasClass('btn-success'))
+				$('#tl' + index + '-0').removeClass('btn-primary btn-info').addClass('btn-primary');
+			feeds[index].send({message: { request: "configure", temporal: 1 }});
+		});
+	$('#tl' + index + '-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' + index + '-2').removeClass('btn-primary btn-info btn-success').addClass('btn-info');
+			if(!$('#tl' + index + '-1').hasClass('btn-success'))
+				$('#tl' + index + '-1').removeClass('btn-primary btn-info').addClass('btn-primary');
+			if(!$('#tl' + index + '-0').hasClass('btn-success'))
+				$('#tl' + index + '-0').removeClass('btn-primary btn-info').addClass('btn-primary');
+			feeds[index].send({message: { request: "configure", temporal: 2 }});
+		});
+}
+
+function updateSimulcastButtons(feed, substream, temporal) {
+	// Check the substream
+	var index = feed;
+	if(substream === 0) {
+		toastr.success("Switched simulcast substream! (lower quality)", null, {timeOut: 2000});
+		$('#sl' + index + '-2').removeClass('btn-primary btn-success').addClass('btn-primary');
+		$('#sl' + index + '-1').removeClass('btn-primary btn-success').addClass('btn-primary');
+		$('#sl' + index + '-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' + index + '-2').removeClass('btn-primary btn-success').addClass('btn-primary');
+		$('#sl' + index + '-1').removeClass('btn-primary btn-info btn-success').addClass('btn-success');
+		$('#sl' + index + '-0').removeClass('btn-primary btn-success').addClass('btn-primary');
+	} else if(substream === 2) {
+		toastr.success("Switched simulcast substream! (higher quality)", null, {timeOut: 2000});
+		$('#sl' + index + '-2').removeClass('btn-primary btn-info btn-success').addClass('btn-success');
+		$('#sl' + index + '-1').removeClass('btn-primary btn-success').addClass('btn-primary');
+		$('#sl' + index + '-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' + index + '-2').removeClass('btn-primary btn-success').addClass('btn-primary');
+		$('#tl' + index + '-1').removeClass('btn-primary btn-success').addClass('btn-primary');
+		$('#tl' + index + '-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' + index + '-2').removeClass('btn-primary btn-success').addClass('btn-primary');
+		$('#tl' + index + '-1').removeClass('btn-primary btn-info btn-success').addClass('btn-success');
+		$('#tl' + index + '-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' + index + '-2').removeClass('btn-primary btn-info btn-success').addClass('btn-success');
+		$('#tl' + index + '-1').removeClass('btn-primary btn-success').addClass('btn-primary');
+		$('#tl' + index + '-0').removeClass('btn-primary btn-success').addClass('btn-primary');
+	}
+}

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-voip/janus.git



More information about the Pkg-voip-commits mailing list