aboutsummaryrefslogtreecommitdiff
path: root/website/mic.js
blob: 4aa83158bb38093cebc9e9243fd892f371cc5a4b (plain)
  1. var websocket_server = null;
  2. if(window.location.protocol === 'http:')
  3. websocket_server = "ws://" + window.location.hostname + "/janus-ws/janus";
  4. else
  5. websocket_server = "wss://" + window.location.hostname + "/janus-ws/janus";
  6. var janus = null;
  7. var streaming = null;
  8. var mixertest = null;
  9. var opaqueId = "streamingwithfeedback-"+Janus.randomString(12);
  10. var bitrateTimer = null;
  11. var spinner = null;
  12. var simulcastStarted = false, svcStarted = false;
  13. var selectedStream = null;
  14. var myroom = null;
  15. var myusername = null;
  16. var myid = null;
  17. var webrtcUp = false;
  18. var audioenabled = false;
  19. $(document).ready(function() {
  20. // Initialize the library (all console debuggers enabled)
  21. Janus.init({debug: "all", callback: function() {
  22. // Use a button to start the demo
  23. $('#start').one('click', function() {
  24. $(this).attr('disabled', true).unbind('click');
  25. // Make sure the browser supports WebRTC
  26. if(!Janus.isWebrtcSupported()) {
  27. bootbox.alert("No WebRTC support... ");
  28. return;
  29. }
  30. // Create session
  31. janus = new Janus(
  32. {
  33. server: [websocket_server, "/janus"],
  34. iceServers: [{url: "turn:morla.jones.dk", username: "myturn", credential: "notsecure"},
  35. {url: "turn:jawa.homebase.dk", username: "myturn", credential: "notsecure"}],
  36. success: function() {
  37. // Attach to streaming plugin
  38. janus.attach(
  39. {
  40. plugin: "janus.plugin.streaming",
  41. opaqueId: opaqueId,
  42. success: function(pluginHandle) {
  43. $('#details').remove();
  44. streaming = pluginHandle;
  45. Janus.log("Plugin attached! (" + streaming.getPlugin() + ", id=" + streaming.getId() + ")");
  46. // Setup streaming session
  47. $('#update-streams').click(updateStreamsList);
  48. updateStreamsList();
  49. $('#start').removeAttr('disabled').html("Stop")
  50. .click(function() {
  51. $(this).attr('disabled', true);
  52. clearInterval(bitrateTimer);
  53. janus.destroy();
  54. $('#streamslist').attr('disabled', true);
  55. $('#watch').attr('disabled', true).unbind('click');
  56. $('#start').attr('disabled', true).html("Bye").unbind('click');
  57. });
  58. },
  59. error: function(error) {
  60. Janus.error(" -- Error attaching plugin... ", error);
  61. bootbox.alert("Error attaching plugin... " + error);
  62. },
  63. onmessage: function(msg, jsep) {
  64. Janus.debug(" ::: Got a message :::");
  65. Janus.debug(msg);
  66. var result = msg["result"];
  67. if(result !== null && result !== undefined) {
  68. if(result["status"] !== undefined && result["status"] !== null) {
  69. var status = result["status"];
  70. if(status === 'starting')
  71. $('#status').removeClass('hide').text("Starting, please wait...").show();
  72. else if(status === 'started')
  73. $('#status').removeClass('hide').text("Started").show();
  74. else if(status === 'stopped')
  75. stopStream();
  76. } else if(msg["streaming"] === "event") {
  77. // Is simulcast in place?
  78. var substream = result["substream"];
  79. var temporal = result["temporal"];
  80. if((substream !== null && substream !== undefined) || (temporal !== null && temporal !== undefined)) {
  81. if(!simulcastStarted) {
  82. simulcastStarted = true;
  83. addSimulcastButtons(temporal !== null && temporal !== undefined);
  84. }
  85. // We just received notice that there's been a switch, update the buttons
  86. updateSimulcastButtons(substream, temporal);
  87. }
  88. // Is VP9/SVC in place?
  89. var spatial = result["spatial_layer"];
  90. temporal = result["temporal_layer"];
  91. if((spatial !== null && spatial !== undefined) || (temporal !== null && temporal !== undefined)) {
  92. if(!svcStarted) {
  93. svcStarted = true;
  94. addSvcButtons();
  95. }
  96. // We just received notice that there's been a switch, update the buttons
  97. updateSvcButtons(spatial, temporal);
  98. }
  99. }
  100. } else if(msg["error"] !== undefined && msg["error"] !== null) {
  101. bootbox.alert(msg["error"]);
  102. stopStream();
  103. return;
  104. }
  105. if(jsep !== undefined && jsep !== null) {
  106. Janus.debug("Handling SDP as well...");
  107. Janus.debug(jsep);
  108. // Offer from the plugin, let's answer
  109. streaming.createAnswer(
  110. {
  111. jsep: jsep,
  112. // We want recvonly audio/video and, if negotiated, datachannels
  113. media: { audioSend: false, videoSend: false, data: true },
  114. success: function(jsep) {
  115. Janus.debug("Got SDP!");
  116. Janus.debug(jsep);
  117. var body = { "request": "start" };
  118. streaming.send({"message": body, "jsep": jsep});
  119. $('#watch').html("Stop").removeAttr('disabled').click(stopStream);
  120. },
  121. error: function(error) {
  122. Janus.error("WebRTC error:", error);
  123. bootbox.alert("WebRTC error... " + JSON.stringify(error));
  124. }
  125. });
  126. }
  127. },
  128. onremotestream: function(stream) {
  129. Janus.debug(" ::: Got a remote stream :::");
  130. Janus.debug(stream);
  131. var addButtons = false;
  132. if($('#remotevideo').length === 0) {
  133. addButtons = true;
  134. $('#stream').append('<video class="rounded centered hide" id="remotevideo" width=320 height=240 autoplay playsinline/>');
  135. // Show the stream and hide the spinner when we get a playing event
  136. $("#remotevideo").bind("playing", function () {
  137. $('#waitingvideo').remove();
  138. if(this.videoWidth)
  139. $('#remotevideo').removeClass('hide').show();
  140. if(spinner !== null && spinner !== undefined)
  141. spinner.stop();
  142. spinner = null;
  143. var videoTracks = stream.getVideoTracks();
  144. if(videoTracks === null || videoTracks === undefined || videoTracks.length === 0)
  145. return;
  146. var width = this.videoWidth;
  147. var height = this.videoHeight;
  148. $('#curres').removeClass('hide').text(width+'x'+height).show();
  149. if(Janus.webRTCAdapter.browserDetails.browser === "firefox") {
  150. // Firefox Stable has a bug: width and height are not immediately available after a playing
  151. setTimeout(function() {
  152. var width = $("#remotevideo").get(0).videoWidth;
  153. var height = $("#remotevideo").get(0).videoHeight;
  154. $('#curres').removeClass('hide').text(width+'x'+height).show();
  155. }, 2000);
  156. }
  157. });
  158. }
  159. Janus.attachMediaStream($('#remotevideo').get(0), stream);
  160. var videoTracks = stream.getVideoTracks();
  161. if(videoTracks === null || videoTracks === undefined || videoTracks.length === 0) {
  162. // No remote video
  163. $('#remotevideo').hide();
  164. if($('#stream .no-video-container').length === 0) {
  165. $('#stream').append(
  166. '<div class="no-video-container">' +
  167. '<i class="fa fa-video-camera fa-5 no-video-icon"></i>' +
  168. '<span class="no-video-text">No remote video available</span>' +
  169. '</div>');
  170. }
  171. } else {
  172. $('#stream .no-video-container').remove();
  173. $('#remotevideo').removeClass('hide').show();
  174. }
  175. if(!addButtons)
  176. return;
  177. if(videoTracks && videoTracks.length &&
  178. (Janus.webRTCAdapter.browserDetails.browser === "chrome" ||
  179. Janus.webRTCAdapter.browserDetails.browser === "firefox" ||
  180. Janus.webRTCAdapter.browserDetails.browser === "safari")) {
  181. $('#curbitrate').removeClass('hide').show();
  182. bitrateTimer = setInterval(function() {
  183. // Display updated bitrate, if supported
  184. var bitrate = streaming.getBitrate();
  185. //~ Janus.debug("Current bitrate is " + streaming.getBitrate());
  186. $('#curbitrate').text(bitrate);
  187. // Check if the resolution changed too
  188. var width = $("#remotevideo").get(0).videoWidth;
  189. var height = $("#remotevideo").get(0).videoHeight;
  190. if(width > 0 && height > 0)
  191. $('#curres').removeClass('hide').text(width+'x'+height).show();
  192. }, 1000);
  193. }
  194. },
  195. ondataopen: function(data) {
  196. Janus.log("The DataChannel is available!");
  197. $('#waitingvideo').remove();
  198. $('#stream').append(
  199. '<input class="form-control" type="text" id="datarecv" disabled></input>'
  200. );
  201. if(spinner !== null && spinner !== undefined)
  202. spinner.stop();
  203. spinner = null;
  204. },
  205. ondata: function(data) {
  206. Janus.debug("We got data from the DataChannel! " + data);
  207. $('#datarecv').val(data);
  208. },
  209. oncleanup: function() {
  210. Janus.log(" ::: Got a cleanup notification :::");
  211. $('#waitingvideo').remove();
  212. $('#remotevideo').remove();
  213. $('#datarecv').remove();
  214. $('.no-video-container').remove();
  215. $('#bitrate').attr('disabled', true);
  216. $('#bitrateset').html('Bandwidth<span class="caret"></span>');
  217. $('#curbitrate').hide();
  218. if(bitrateTimer !== null && bitrateTimer !== undefined)
  219. clearInterval(bitrateTimer);
  220. bitrateTimer = null;
  221. $('#curres').hide();
  222. $('#simulcast').remove();
  223. simulcastStarted = false;
  224. }
  225. });
  226. // Attach to Audio Bridge test plugin
  227. janus.attach(
  228. {
  229. plugin: "janus.plugin.audiobridge",
  230. opaqueId: opaqueId,
  231. success: function(pluginHandle) {
  232. $('#details').remove();
  233. mixertest = pluginHandle;
  234. Janus.log("Plugin attached! (" + mixertest.getPlugin() + ", id=" + mixertest.getId() + ")");
  235. },
  236. error: function(error) {
  237. Janus.error(" -- Error attaching plugin...", error);
  238. bootbox.alert("Error attaching plugin... " + error);
  239. },
  240. consentDialog: function(on) {
  241. Janus.debug("Consent dialog should be " + (on ? "on" : "off") + " now");
  242. if(on) {
  243. // Darken screen and show hint
  244. $.blockUI({
  245. message: '<div><img src="img/up_arrow.png"/></div>',
  246. css: {
  247. border: 'none',
  248. padding: '15px',
  249. backgroundColor: 'transparent',
  250. color: '#aaa',
  251. top: '10px',
  252. left: (navigator.mozGetUserMedia ? '-100px' : '300px')
  253. } });
  254. } else {
  255. // Restore screen
  256. $.unblockUI();
  257. }
  258. },
  259. onmessage: function(msg, jsep) {
  260. Janus.debug(" ::: Got a message :::");
  261. Janus.debug(msg);
  262. var event = msg["audiobridge"];
  263. Janus.debug("Event: " + event);
  264. if(event != undefined && event != null) {
  265. if(event === "joined") {
  266. // Successfully joined, negotiate WebRTC now
  267. myid = msg["id"];
  268. Janus.log("Successfully joined room " + msg["room"] + " with ID " + myid);
  269. if(!webrtcUp) {
  270. webrtcUp = true;
  271. // Publish our stream
  272. mixertest.createOffer(
  273. {
  274. media: { video: false}, // This is an audio only room
  275. success: function(jsep) {
  276. Janus.debug("Got SDP!");
  277. Janus.debug(jsep);
  278. var publish = { "request": "configure", "muted": false };
  279. mixertest.send({"message": publish, "jsep": jsep});
  280. },
  281. error: function(error) {
  282. Janus.error("WebRTC error:", error);
  283. bootbox.alert("WebRTC error... " + JSON.stringify(error));
  284. }
  285. });
  286. }
  287. // Any room participant?
  288. if(msg["participants"] !== undefined && msg["participants"] !== null) {
  289. var list = msg["participants"];
  290. Janus.debug("Got a list of participants:");
  291. Janus.debug(list);
  292. for(var f in list) {
  293. var id = list[f]["id"];
  294. var display = list[f]["display"];
  295. var setup = list[f]["setup"];
  296. var muted = list[f]["muted"];
  297. Janus.debug(" >> [" + id + "] " + display + " (setup=" + setup + ", muted=" + muted + ")");
  298. if($('#rp'+id).length === 0) {
  299. // Add to the participants list
  300. $('#list').append('<li id="rp'+id+'" class="list-group-item">'+display+
  301. ' <i class="absetup fa fa-chain-broken"></i>' +
  302. ' <i class="abmuted fa fa-microphone-slash"></i></li>');
  303. $('#rp'+id + ' > i').hide();
  304. }
  305. if(muted === true || muted === "true")
  306. $('#rp'+id + ' > i.abmuted').removeClass('hide').show();
  307. else
  308. $('#rp'+id + ' > i.abmuted').hide();
  309. if(setup === true || setup === "true")
  310. $('#rp'+id + ' > i.absetup').hide();
  311. else
  312. $('#rp'+id + ' > i.absetup').removeClass('hide').show();
  313. }
  314. }
  315. } else if(event === "roomchanged") {
  316. // The user switched to a different room
  317. myid = msg["id"];
  318. Janus.log("Moved to room " + msg["room"] + ", new ID: " + myid);
  319. // Any room participant?
  320. $('#list').empty();
  321. if(msg["participants"] !== undefined && msg["participants"] !== null) {
  322. var list = msg["participants"];
  323. Janus.debug("Got a list of participants:");
  324. Janus.debug(list);
  325. for(var f in list) {
  326. var id = list[f]["id"];
  327. var display = list[f]["display"];
  328. var setup = list[f]["setup"];
  329. var muted = list[f]["muted"];
  330. Janus.debug(" >> [" + id + "] " + display + " (setup=" + setup + ", muted=" + muted + ")");
  331. if($('#rp'+id).length === 0) {
  332. // Add to the participants list
  333. $('#list').append('<li id="rp'+id+'" class="list-group-item">'+display+
  334. ' <i class="absetup fa fa-chain-broken"></i>' +
  335. ' <i class="abmuted fa fa-microphone-slash"></i></li>');
  336. $('#rp'+id + ' > i').hide();
  337. }
  338. if(muted === true || muted === "true")
  339. $('#rp'+id + ' > i.abmuted').removeClass('hide').show();
  340. else
  341. $('#rp'+id + ' > i.abmuted').hide();
  342. if(setup === true || setup === "true")
  343. $('#rp'+id + ' > i.absetup').hide();
  344. else
  345. $('#rp'+id + ' > i.absetup').removeClass('hide').show();
  346. }
  347. }
  348. } else if(event === "destroyed") {
  349. // The room has been destroyed
  350. Janus.warn("The room has been destroyed!");
  351. bootbox.alert("The room has been destroyed", function() {
  352. window.location.reload();
  353. });
  354. } else if(event === "event") {
  355. if(msg["participants"] !== undefined && msg["participants"] !== null) {
  356. var list = msg["participants"];
  357. Janus.debug("Got a list of participants:");
  358. Janus.debug(list);
  359. for(var f in list) {
  360. var id = list[f]["id"];
  361. var display = list[f]["display"];
  362. var setup = list[f]["setup"];
  363. var muted = list[f]["muted"];
  364. Janus.debug(" >> [" + id + "] " + display + " (setup=" + setup + ", muted=" + muted + ")");
  365. if($('#rp'+id).length === 0) {
  366. // Add to the participants list
  367. $('#list').append('<li id="rp'+id+'" class="list-group-item">'+display+
  368. ' <i class="absetup fa fa-chain-broken"></i>' +
  369. ' <i class="abmuted fa fa-microphone-slash"></i></li>');
  370. $('#rp'+id + ' > i').hide();
  371. }
  372. if(muted === true || muted === "true")
  373. $('#rp'+id + ' > i.abmuted').removeClass('hide').show();
  374. else
  375. $('#rp'+id + ' > i.abmuted').hide();
  376. if(setup === true || setup === "true")
  377. $('#rp'+id + ' > i.absetup').hide();
  378. else
  379. $('#rp'+id + ' > i.absetup').removeClass('hide').show();
  380. }
  381. } else if(msg["error"] !== undefined && msg["error"] !== null) {
  382. if(msg["error_code"] === 485) {
  383. // This is a "no such room" error: give a more meaningful description
  384. bootbox.alert(
  385. "<p>Apparently room <code>" + myroom + "</code> (the one this demo uses as a test room) " +
  386. "does not exist...</p><p>Do you have an updated <code>janus.plugin.audiobridge.cfg</code> " +
  387. "configuration file? If not, make sure you copy the details of room <code>" + myroom + "</code> " +
  388. "from that sample in your current configuration file, then restart Janus and try again."
  389. );
  390. } else {
  391. bootbox.alert(msg["error"]);
  392. }
  393. return;
  394. }
  395. // Any new feed to attach to?
  396. if(msg["leaving"] !== undefined && msg["leaving"] !== null) {
  397. // One of the participants has gone away?
  398. var leaving = msg["leaving"];
  399. Janus.log("Participant left: " + leaving + " (we have " + $('#rp'+leaving).length + " elements with ID #rp" +leaving + ")");
  400. $('#rp'+leaving).remove();
  401. }
  402. }
  403. }
  404. if(jsep !== undefined && jsep !== null) {
  405. Janus.debug("Handling SDP as well...");
  406. Janus.debug(jsep);
  407. mixertest.handleRemoteJsep({jsep: jsep});
  408. }
  409. },
  410. onlocalstream: function(stream) {
  411. Janus.debug(" ::: Got a local stream :::");
  412. Janus.debug(stream);
  413. // We're not going to attach the local audio stream
  414. $('#audiojoin').hide();
  415. $('#room').removeClass('hide').show();
  416. $('#participant').removeClass('hide').html(myusername).show();
  417. },
  418. onremotestream: function(stream) {
  419. $('#room').removeClass('hide').show();
  420. var addButtons = false;
  421. if($('#roomaudio').length === 0) {
  422. addButtons = true;
  423. $('#mixedaudio').append('<audio class="rounded centered" id="roomaudio" width="100%" height="100%" autoplay/>');
  424. }
  425. Janus.attachMediaStream($('#roomaudio').get(0), stream);
  426. if(!addButtons)
  427. return;
  428. // Mute button
  429. audioenabled = true;
  430. $('#toggleaudio').click(
  431. function() {
  432. audioenabled = !audioenabled;
  433. if(audioenabled)
  434. $('#toggleaudio').html("Mute").removeClass("btn-success").addClass("btn-danger");
  435. else
  436. $('#toggleaudio').html("Unmute").removeClass("btn-danger").addClass("btn-success");
  437. mixertest.send({message: { "request": "configure", "muted": !audioenabled }});
  438. }).removeClass('hide').show();
  439. },
  440. oncleanup: function() {
  441. webrtcUp = false;
  442. Janus.log(" ::: Got a cleanup notification :::");
  443. $('#participant').empty().hide();
  444. $('#list').empty();
  445. $('#mixedaudio').empty();
  446. $('#room').hide();
  447. }
  448. });
  449. },
  450. error: function(error) {
  451. Janus.error(error);
  452. bootbox.alert(error, function() {
  453. window.location.reload();
  454. });
  455. },
  456. destroyed: function() {
  457. window.location.reload();
  458. }
  459. });
  460. });
  461. }});
  462. });
  463. function updateStreamsList() {
  464. $('#audiojoin').hide();
  465. $('#update-streams').unbind('click').addClass('fa-spin');
  466. var body = { "request": "list" };
  467. Janus.debug("Sending message (" + JSON.stringify(body) + ")");
  468. streaming.send({"message": body, success: function(result) {
  469. setTimeout(function() {
  470. $('#update-streams').removeClass('fa-spin').click(updateStreamsList);
  471. }, 500);
  472. if(result === null || result === undefined) {
  473. bootbox.alert("Got no response to our query for available streams");
  474. return;
  475. }
  476. if(result["list"] !== undefined && result["list"] !== null) {
  477. $('#streams').removeClass('hide').show();
  478. $('#streamslist').empty();
  479. $('#watch').attr('disabled', true).unbind('click');
  480. var list = result["list"];
  481. Janus.log("Got a list of available streams");
  482. Janus.debug(list);
  483. for(var mp in list) {
  484. Janus.debug(" >> [" + list[mp]["id"] + "] " + list[mp]["description"] + " (" + list[mp]["type"] + ")");
  485. $('#streamslist').append("<li><a href='#' id='" + list[mp]["id"] + "'>" + list[mp]["description"] + " (" + list[mp]["type"] + ")" + "</a></li>");
  486. }
  487. $('#streamslist a').unbind('click').click(function() {
  488. selectedStream = $(this).attr("id");
  489. $('#streamset').html($(this).html()).parent().removeClass('open');
  490. return false;
  491. });
  492. $('#watch').removeAttr('disabled').unbind('click').click(startStream);
  493. }
  494. }});
  495. }
  496. function startStream() {
  497. Janus.log("Selected video id #" + selectedStream);
  498. if(selectedStream === undefined || selectedStream === null) {
  499. bootbox.alert("Select a stream from the list");
  500. return;
  501. }
  502. $('#streamset').attr('disabled', true);
  503. $('#streamslist').attr('disabled', true);
  504. $('#watch').attr('disabled', true).unbind('click');
  505. var body = { "request": "watch", id: parseInt(selectedStream) };
  506. streaming.send({"message": body});
  507. // No remote video yet
  508. $('#stream').append('<video class="rounded centered" id="waitingvideo" width=320 height=240 />');
  509. if(spinner == null) {
  510. var target = document.getElementById('stream');
  511. spinner = new Spinner({top:100}).spin(target);
  512. } else {
  513. spinner.spin();
  514. }
  515. // Prepare the username registration
  516. myroom = parseInt(selectedStream);
  517. $('#audiojoin').removeClass('hide').show();
  518. $('#registernow').removeClass('hide').show();
  519. $('#register').click(registerUsername);
  520. $('#username').focus();
  521. $('#start').removeAttr('disabled').html("Stop")
  522. .click(function() {
  523. $(this).attr('disabled', true);
  524. janus.destroy();
  525. });
  526. }
  527. function stopStream() {
  528. $('#audiojoin').hide();
  529. $('#watch').attr('disabled', true).unbind('click');
  530. var body = { "request": "stop" };
  531. streaming.send({"message": body});
  532. streaming.hangup();
  533. $('#streamset').removeAttr('disabled');
  534. $('#streamslist').removeAttr('disabled');
  535. $('#watch').html("Watch or Listen").removeAttr('disabled').unbind('click').click(startStream);
  536. $('#status').empty().hide();
  537. $('#bitrate').attr('disabled', true);
  538. $('#bitrateset').html('Bandwidth<span class="caret"></span>');
  539. $('#curbitrate').hide();
  540. if(bitrateTimer !== null && bitrateTimer !== undefined)
  541. clearInterval(bitrateTimer);
  542. bitrateTimer = null;
  543. $('#curres').empty().hide();
  544. $('#simulcast').remove();
  545. simulcastStarted = false;
  546. }
  547. // Helpers to create Simulcast-related UI, if enabled
  548. function addSimulcastButtons(temporal) {
  549. $('#curres').parent().append(
  550. '<div id="simulcast" class="btn-group-vertical btn-group-vertical-xs pull-right">' +
  551. ' <div class"row">' +
  552. ' <div class="btn-group btn-group-xs" style="width: 100%">' +
  553. ' <button id="sl-2" type="button" class="btn btn-primary" data-toggle="tooltip" title="Switch to higher quality" style="width: 33%">SL 2</button>' +
  554. ' <button id="sl-1" type="button" class="btn btn-primary" data-toggle="tooltip" title="Switch to normal quality" style="width: 33%">SL 1</button>' +
  555. ' <button id="sl-0" type="button" class="btn btn-primary" data-toggle="tooltip" title="Switch to lower quality" style="width: 34%">SL 0</button>' +
  556. ' </div>' +
  557. ' </div>' +
  558. ' <div class"row">' +
  559. ' <div class="btn-group btn-group-xs hide" style="width: 100%">' +
  560. ' <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>' +
  561. ' <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>' +
  562. ' <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>' +
  563. ' </div>' +
  564. ' </div>' +
  565. '</div>');
  566. // Enable the simulcast selection buttons
  567. $('#sl-0').removeClass('btn-primary btn-success').addClass('btn-primary')
  568. .unbind('click').click(function() {
  569. toastr.info("Switching simulcast substream, wait for it... (lower quality)", null, {timeOut: 2000});
  570. if(!$('#sl-2').hasClass('btn-success'))
  571. $('#sl-2').removeClass('btn-primary btn-info').addClass('btn-primary');
  572. if(!$('#sl-1').hasClass('btn-success'))
  573. $('#sl-1').removeClass('btn-primary btn-info').addClass('btn-primary');
  574. $('#sl-0').removeClass('btn-primary btn-info btn-success').addClass('btn-info');
  575. streaming.send({message: { request: "configure", substream: 0 }});
  576. });
  577. $('#sl-1').removeClass('btn-primary btn-success').addClass('btn-primary')
  578. .unbind('click').click(function() {
  579. toastr.info("Switching simulcast substream, wait for it... (normal quality)", null, {timeOut: 2000});
  580. if(!$('#sl-2').hasClass('btn-success'))
  581. $('#sl-2').removeClass('btn-primary btn-info').addClass('btn-primary');
  582. $('#sl-1').removeClass('btn-primary btn-info btn-success').addClass('btn-info');
  583. if(!$('#sl-0').hasClass('btn-success'))
  584. $('#sl-0').removeClass('btn-primary btn-info').addClass('btn-primary');
  585. streaming.send({message: { request: "configure", substream: 1 }});
  586. });
  587. $('#sl-2').removeClass('btn-primary btn-success').addClass('btn-primary')
  588. .unbind('click').click(function() {
  589. toastr.info("Switching simulcast substream, wait for it... (higher quality)", null, {timeOut: 2000});
  590. $('#sl-2').removeClass('btn-primary btn-info btn-success').addClass('btn-info');
  591. if(!$('#sl-1').hasClass('btn-success'))
  592. $('#sl-1').removeClass('btn-primary btn-info').addClass('btn-primary');
  593. if(!$('#sl-0').hasClass('btn-success'))
  594. $('#sl-0').removeClass('btn-primary btn-info').addClass('btn-primary');
  595. streaming.send({message: { request: "configure", substream: 2 }});
  596. });
  597. if(!temporal) // No temporal layer support
  598. return;
  599. $('#tl-0').parent().removeClass('hide');
  600. $('#tl-0').removeClass('btn-primary btn-success').addClass('btn-primary')
  601. .unbind('click').click(function() {
  602. toastr.info("Capping simulcast temporal layer, wait for it... (lowest FPS)", null, {timeOut: 2000});
  603. if(!$('#tl-2').hasClass('btn-success'))
  604. $('#tl-2').removeClass('btn-primary btn-info').addClass('btn-primary');
  605. if(!$('#tl-1').hasClass('btn-success'))
  606. $('#tl-1').removeClass('btn-primary btn-info').addClass('btn-primary');
  607. $('#tl-0').removeClass('btn-primary btn-info btn-success').addClass('btn-info');
  608. streaming.send({message: { request: "configure", temporal: 0 }});
  609. });
  610. $('#tl-1').removeClass('btn-primary btn-success').addClass('btn-primary')
  611. .unbind('click').click(function() {
  612. toastr.info("Capping simulcast temporal layer, wait for it... (medium FPS)", null, {timeOut: 2000});
  613. if(!$('#tl-2').hasClass('btn-success'))
  614. $('#tl-2').removeClass('btn-primary btn-info').addClass('btn-primary');
  615. $('#tl-1').removeClass('btn-primary btn-info').addClass('btn-info');
  616. if(!$('#tl-0').hasClass('btn-success'))
  617. $('#tl-0').removeClass('btn-primary btn-info').addClass('btn-primary');
  618. streaming.send({message: { request: "configure", temporal: 1 }});
  619. });
  620. $('#tl-2').removeClass('btn-primary btn-success').addClass('btn-primary')
  621. .unbind('click').click(function() {
  622. toastr.info("Capping simulcast temporal layer, wait for it... (highest FPS)", null, {timeOut: 2000});
  623. $('#tl-2').removeClass('btn-primary btn-info btn-success').addClass('btn-info');
  624. if(!$('#tl-1').hasClass('btn-success'))
  625. $('#tl-1').removeClass('btn-primary btn-info').addClass('btn-primary');
  626. if(!$('#tl-0').hasClass('btn-success'))
  627. $('#tl-0').removeClass('btn-primary btn-info').addClass('btn-primary');
  628. streaming.send({message: { request: "configure", temporal: 2 }});
  629. });
  630. }
  631. function updateSimulcastButtons(substream, temporal) {
  632. // Check the substream
  633. if(substream === 0) {
  634. toastr.success("Switched simulcast substream! (lower quality)", null, {timeOut: 2000});
  635. $('#sl-2').removeClass('btn-primary btn-success').addClass('btn-primary');
  636. $('#sl-1').removeClass('btn-primary btn-success').addClass('btn-primary');
  637. $('#sl-0').removeClass('btn-primary btn-info btn-success').addClass('btn-success');
  638. } else if(substream === 1) {
  639. toastr.success("Switched simulcast substream! (normal quality)", null, {timeOut: 2000});
  640. $('#sl-2').removeClass('btn-primary btn-success').addClass('btn-primary');
  641. $('#sl-1').removeClass('btn-primary btn-info btn-success').addClass('btn-success');
  642. $('#sl-0').removeClass('btn-primary btn-success').addClass('btn-primary');
  643. } else if(substream === 2) {
  644. toastr.success("Switched simulcast substream! (higher quality)", null, {timeOut: 2000});
  645. $('#sl-2').removeClass('btn-primary btn-info btn-success').addClass('btn-success');
  646. $('#sl-1').removeClass('btn-primary btn-success').addClass('btn-primary');
  647. $('#sl-0').removeClass('btn-primary btn-success').addClass('btn-primary');
  648. }
  649. // Check the temporal layer
  650. if(temporal === 0) {
  651. toastr.success("Capped simulcast temporal layer! (lowest FPS)", null, {timeOut: 2000});
  652. $('#tl-2').removeClass('btn-primary btn-success').addClass('btn-primary');
  653. $('#tl-1').removeClass('btn-primary btn-success').addClass('btn-primary');
  654. $('#tl-0').removeClass('btn-primary btn-info btn-success').addClass('btn-success');
  655. } else if(temporal === 1) {
  656. toastr.success("Capped simulcast temporal layer! (medium FPS)", null, {timeOut: 2000});
  657. $('#tl-2').removeClass('btn-primary btn-success').addClass('btn-primary');
  658. $('#tl-1').removeClass('btn-primary btn-info btn-success').addClass('btn-success');
  659. $('#tl-0').removeClass('btn-primary btn-success').addClass('btn-primary');
  660. } else if(temporal === 2) {
  661. toastr.success("Capped simulcast temporal layer! (highest FPS)", null, {timeOut: 2000});
  662. $('#tl-2').removeClass('btn-primary btn-info btn-success').addClass('btn-success');
  663. $('#tl-1').removeClass('btn-primary btn-success').addClass('btn-primary');
  664. $('#tl-0').removeClass('btn-primary btn-success').addClass('btn-primary');
  665. }
  666. }
  667. // Helpers to create SVC-related UI for a new viewer
  668. function addSvcButtons() {
  669. if($('#svc').length > 0)
  670. return;
  671. $('#curres').parent().append(
  672. '<div id="svc" class="btn-group-vertical btn-group-vertical-xs pull-right">' +
  673. ' <div class"row">' +
  674. ' <div class="btn-group btn-group-xs" style="width: 100%">' +
  675. ' <button id="sl-1" type="button" class="btn btn-primary" data-toggle="tooltip" title="Switch to normal resolution" style="width: 50%">SL 1</button>' +
  676. ' <button id="sl-0" type="button" class="btn btn-primary" data-toggle="tooltip" title="Switch to low resolution" style="width: 50%">SL 0</button>' +
  677. ' </div>' +
  678. ' </div>' +
  679. ' <div class"row">' +
  680. ' <div class="btn-group btn-group-xs" style="width: 100%">' +
  681. ' <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>' +
  682. ' <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>' +
  683. ' <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>' +
  684. ' </div>' +
  685. ' </div>' +
  686. '</div>'
  687. );
  688. // Enable the VP8 simulcast selection buttons
  689. $('#sl-0').removeClass('btn-primary btn-success').addClass('btn-primary')
  690. .unbind('click').click(function() {
  691. toastr.info("Switching SVC spatial layer, wait for it... (low resolution)", null, {timeOut: 2000});
  692. if(!$('#sl-1').hasClass('btn-success'))
  693. $('#sl-1').removeClass('btn-primary btn-info').addClass('btn-primary');
  694. $('#sl-0').removeClass('btn-primary btn-info btn-success').addClass('btn-info');
  695. streaming.send({message: { request: "configure", spatial_layer: 0 }});
  696. });
  697. $('#sl-1').removeClass('btn-primary btn-success').addClass('btn-primary')
  698. .unbind('click').click(function() {
  699. toastr.info("Switching SVC spatial layer, wait for it... (normal resolution)", null, {timeOut: 2000});
  700. $('#sl-1').removeClass('btn-primary btn-info btn-success').addClass('btn-info');
  701. if(!$('#sl-0').hasClass('btn-success'))
  702. $('#sl-0').removeClass('btn-primary btn-info').addClass('btn-primary');
  703. streaming.send({message: { request: "configure", spatial_layer: 1 }});
  704. });
  705. $('#tl-0').removeClass('btn-primary btn-success').addClass('btn-primary')
  706. .unbind('click').click(function() {
  707. toastr.info("Capping SVC temporal layer, wait for it... (lowest FPS)", null, {timeOut: 2000});
  708. if(!$('#tl-2').hasClass('btn-success'))
  709. $('#tl-2').removeClass('btn-primary btn-info').addClass('btn-primary');
  710. if(!$('#tl-1').hasClass('btn-success'))
  711. $('#tl-1').removeClass('btn-primary btn-info').addClass('btn-primary');
  712. $('#tl-0').removeClass('btn-primary btn-info btn-success').addClass('btn-info');
  713. streaming.send({message: { request: "configure", temporal_layer: 0 }});
  714. });
  715. $('#tl-1').removeClass('btn-primary btn-success').addClass('btn-primary')
  716. .unbind('click').click(function() {
  717. toastr.info("Capping SVC temporal layer, wait for it... (medium FPS)", null, {timeOut: 2000});
  718. if(!$('#tl-2').hasClass('btn-success'))
  719. $('#tl-2').removeClass('btn-primary btn-info').addClass('btn-primary');
  720. $('#tl-1').removeClass('btn-primary btn-info').addClass('btn-info');
  721. if(!$('#tl-0').hasClass('btn-success'))
  722. $('#tl-0').removeClass('btn-primary btn-info').addClass('btn-primary');
  723. streaming.send({message: { request: "configure", temporal_layer: 1 }});
  724. });
  725. $('#tl-2').removeClass('btn-primary btn-success').addClass('btn-primary')
  726. .unbind('click').click(function() {
  727. toastr.info("Capping SVC temporal layer, wait for it... (highest FPS)", null, {timeOut: 2000});
  728. $('#tl-2').removeClass('btn-primary btn-info btn-success').addClass('btn-info');
  729. if(!$('#tl-1').hasClass('btn-success'))
  730. $('#tl-1').removeClass('btn-primary btn-info').addClass('btn-primary');
  731. if(!$('#tl-0').hasClass('btn-success'))
  732. $('#tl-0').removeClass('btn-primary btn-info').addClass('btn-primary');
  733. streaming.send({message: { request: "configure", temporal_layer: 2 }});
  734. });
  735. }
  736. function updateSvcButtons(spatial, temporal) {
  737. // Check the spatial layer
  738. if(spatial === 0) {
  739. toastr.success("Switched SVC spatial layer! (lower resolution)", null, {timeOut: 2000});
  740. $('#sl-1').removeClass('btn-primary btn-success').addClass('btn-primary');
  741. $('#sl-0').removeClass('btn-primary btn-info btn-success').addClass('btn-success');
  742. } else if(spatial === 1) {
  743. toastr.success("Switched SVC spatial layer! (normal resolution)", null, {timeOut: 2000});
  744. $('#sl-1').removeClass('btn-primary btn-info btn-success').addClass('btn-success');
  745. $('#sl-0').removeClass('btn-primary btn-success').addClass('btn-primary');
  746. }
  747. // Check the temporal layer
  748. if(temporal === 0) {
  749. toastr.success("Capped SVC temporal layer! (lowest FPS)", null, {timeOut: 2000});
  750. $('#tl-2').removeClass('btn-primary btn-success').addClass('btn-primary');
  751. $('#tl-1').removeClass('btn-primary btn-success').addClass('btn-primary');
  752. $('#tl-0').removeClass('btn-primary btn-info btn-success').addClass('btn-success');
  753. } else if(temporal === 1) {
  754. toastr.success("Capped SVC temporal layer! (medium FPS)", null, {timeOut: 2000});
  755. $('#tl-2').removeClass('btn-primary btn-success').addClass('btn-primary');
  756. $('#tl-1').removeClass('btn-primary btn-info btn-success').addClass('btn-success');
  757. $('#tl-0').removeClass('btn-primary btn-success').addClass('btn-primary');
  758. } else if(temporal === 2) {
  759. toastr.success("Capped SVC temporal layer! (highest FPS)", null, {timeOut: 2000});
  760. $('#tl-2').removeClass('btn-primary btn-info btn-success').addClass('btn-success');
  761. $('#tl-1').removeClass('btn-primary btn-success').addClass('btn-primary');
  762. $('#tl-0').removeClass('btn-primary btn-success').addClass('btn-primary');
  763. }
  764. }
  765. function checkEnter(field, event) {
  766. var theCode = event.keyCode ? event.keyCode : event.which ? event.which : event.charCode;
  767. if(theCode == 13) {
  768. registerUsername();
  769. return false;
  770. } else {
  771. return true;
  772. }
  773. }
  774. function registerUsername() {
  775. if($('#username').length === 0) {
  776. // Create fields to register
  777. $('#register').click(registerUsername);
  778. $('#username').focus();
  779. } else {
  780. // Try a registration
  781. $('#username').attr('disabled', true);
  782. $('#register').attr('disabled', true).unbind('click');
  783. var username = $('#username').val();
  784. if(username === "") {
  785. $('#you')
  786. .removeClass().addClass('label label-warning')
  787. .html("Insert your display name (e.g., pippo)");
  788. $('#username').removeAttr('disabled');
  789. $('#register').removeAttr('disabled').click(registerUsername);
  790. return;
  791. }
  792. if(/[^a-zA-Z0-9]/.test(username)) {
  793. $('#you')
  794. .removeClass().addClass('label label-warning')
  795. .html('Input is not alphanumeric');
  796. $('#username').removeAttr('disabled').val("");
  797. $('#register').removeAttr('disabled').click(registerUsername);
  798. return;
  799. }
  800. var register = { "request": "join", "room": myroom, "display": username };
  801. myusername = username;
  802. mixertest.send({"message": register});
  803. }
  804. }