GeSHi Source Viewer: loopplayer.html5.core.jsView Raw


  1. /*!
  2.  * @file Planeptune Loop Player Core
  3.  * @version 2025.05.25.021
  4.  * @author zefie <zefie@zefie.net>
  5.  * @copyright 2017-2021 Zefie Networks
  6.  * @license CC-BY-SA-4.0
  7.  *
  8.  * Minified using uglify-js v3.x
  9.  * For human readable version, see https://loops.planeptune.org/geshi/source/js/loopplayer.html5.core.js
  10.  */
  11. (function () {
  12.     "use strict";
  13.     // objects for days
  14.     var config = {
  15.         "autoplay": false,
  16.         "bootSeek": false,
  17.         "offline": false,
  18.         "debug": false,
  19.         "defaults": {
  20.             "autoplay": false,
  21.             "debug": false,
  22.             "initSeek": false,
  23.             "lossless": 0,
  24.             "forceformat": 0,
  25.             "fps": 60,
  26.             "seekopt": 0,
  27. 			"rate": 100,
  28. 			"volume": 50,
  29. 			"audioconfig": "audio",
  30.             "ctxHue": {
  31.                 "bar": {
  32.                     "start": 260,
  33.                     "end": 296,
  34.                     "direction": 1
  35.                 },
  36.                 "mode": 0,
  37.                 "modifier": 1,
  38.                 "multiplier": 1.5
  39.             }
  40.         },
  41.         "detection": {
  42.             "codecs": null,
  43.             "microsoft": false,
  44.             "mobile": false
  45.         },
  46.         "effects": {
  47.             "slideSpeed": {
  48.                 "wrapper": 150,
  49.                 "content": 300
  50.             }
  51.         },
  52. 		"availfps": { "values": [2, 5, 10, 15, 20, 25, 30, 45, 50, 60, 100, 120, 144, 165, 240] },
  53.         "forceFormat": 0,
  54.         "howl": {
  55.             "currentPart": 0,
  56.             "file": null,
  57.             "name": null,
  58.             "loading": false,
  59.             "mptest": false,
  60.             "numParts": 0,
  61.             "seeking": false
  62.         },
  63.         "initSeek": false,
  64.         "loop": null,
  65.         "lossless": 0,
  66.         "seekOffset": -500,
  67.         "seekopt": 0,
  68.         "siteURL": document.URL.substr(0, document.URL.lastIndexOf("?")) || document.URL.substr(0, document.URL.lastIndexOf("/")),
  69.         "toggles": {
  70.             "looper": {
  71.                 "checked": false,
  72.                 "seeking": false
  73.             },
  74.             "visualization": {
  75.                 "enabled": true,
  76.                 "configShown": false
  77.             },
  78.             "sliderSeeking": false,
  79.             "waveform": {
  80.                 "enabled": true,
  81.                 "seeking": false
  82.             }
  83.         },
  84.         "version": "2025.05.25.021",
  85.         "usingWebAudio": true,
  86.         "wavesurferMaxZoom": 500
  87.     },
  88.         media = {
  89.             "channels": null,
  90.             "codec": null,
  91.             "dataBuffer": null,
  92.             "duration": 0,
  93.             "external": {
  94.                 "loop": null,
  95.                 "flags": null
  96.             },
  97.             "rate": null,
  98.             "volume": null
  99.         },
  100.         obj = {
  101.             "conf": null,
  102.             "ctx": {
  103.                 "analyzer": null,
  104.                 "canvas": null,
  105.                 "canvasContext": null,
  106.                 "freqBufLen": null,
  107.                 "freqData": null,
  108.                 "hue": $.extend(true, {}, config.defaults.ctxHue),
  109.                 "visual": {
  110.                     "height": null,
  111.                     "width": null
  112.                 }
  113.             },
  114.             "flags": null,
  115.             "player": null,
  116.             "timing": {
  117.                 "fps": {
  118.                     "current": 0,
  119.                     "last": 0
  120.                 },
  121.                 "timestamp": Math.round(Date.now() / 1000)
  122.             },
  123.             "wavesurfer": null
  124.         }
  125.  
  126.     $(document).ready(function () {
  127. 		console.log("loopplayer v"+config.version+" initializing")
  128.         console.log("welcome to debug land");
  129.         console.log("initializing variables");
  130.         config.detection.mobile = !(document.getElementById('playspeed') instanceof Object);
  131.         config.detection.microsoft = Howler.detectIE();
  132.         console.log("browser detect: mobile: " + config.detection.mobile + ", pre-chromium ms: " + config.detection.microsoft);
  133.         if (offline) {
  134.           console.log("nwjs offline mode");
  135.           config.offline = true;
  136.         }
  137.         config.loop = z_urlp("loop") || null;
  138.         config.autoplay = (typeof z_urlp("autoplay") !== "undefined") || config.defaults.autoplay;
  139.         config.initSeek = (typeof z_urlp("doseek") !== "undefined") || config.defaults.initSeek;
  140.         config.lossless = parseInt(z_urlp("lossless")) || config.defaults.lossless;
  141.         config.debug = z_urlp("debug") || config.defaults.debug;
  142.         config.forceFormat = parseInt(z_urlp("forceformat")) || config.defaults.forceformat;
  143.         media.volume = parseInt(z_urlp("volume")) || config.defaults.volume;
  144.         media.rate = parseInt(z_urlp("playspeed") * 100) || config.defaults.rate;
  145.         config.seekopt = parseInt(z_urlp("seekopt")) || config.defaults.seekopt;
  146.         obj.ctx.hue.bar.start = parseInt(z_urlp("starthue")) || config.defaults.ctxHue.bar.start;
  147.         obj.ctx.hue.bar.end = parseInt(z_urlp("endhue")) || config.defaults.ctxHue.bar.end;
  148.         obj.ctx.hue.mode = parseInt(z_urlp("huemode")) || config.defaults.ctxHue.mode;
  149.         config.toggles.visualization.enabled = (typeof z_urlp("novu") === "undefined" || z_urlp("novu") == 0)
  150.         config.toggles.waveform.enabled = (typeof z_urlp("nowf") === "undefined" || z_urlp("nowf") == 0)
  151.  
  152. 		config.url = z_urlp("config") || config.defaults.audioconfig;		
  153. 		config.url += ".conf.js";
  154.  
  155.         media.external.loop = z_urlp("external") || null;
  156.         media.external.flags = z_urlp("externalflags") || null;
  157.         var webAudioParam = z_urlp("html5") || null;
  158.  
  159.         if (webAudioParam) {
  160.             // force html5
  161.             config.usingWebAudio = false;
  162.         } else {
  163.             // browser detect
  164.             config.usingWebAudio = testWebAudio();
  165.         }
  166.  
  167.         var lowendPC = z_urlp("lowend") || false;
  168.  
  169.         if (config.detection.mobile || lowendPC) {
  170.             config.toggles.visualization.enabled = false;
  171.             config.toggles.waveform.enabled = false;
  172.             config.defaults.fps = 25;
  173.         }
  174.  
  175.         if (lowendPC) {
  176.             config.defaults.fps = 5;
  177.             lowendPC = true;
  178.         }
  179.  
  180.         if (!config.usingWebAudio) {
  181.             config.toggles.visualization.enabled = false;
  182.             config.toggles.waveform.enabled = false;
  183.             $("#vutoggle").remove();
  184.             $("#wftoggle").remove();
  185.             $("#letoggle").remove();
  186.         }
  187.  
  188.         console.log("using webaudio: " + config.usingWebAudio);
  189.  
  190.         config.fps = parseInt(z_urlp("fps")) || config.defaults.fps;
  191.  
  192.         if (z_urlp("doloop") === "on") {
  193.             config.toggles.looper.checked = true;
  194.         }
  195.         if (parseFloat(getFormValue(0, "seek")) > 0 && config.initSeek) {
  196.             config.bootSeek = true;
  197.         }
  198.         config.detection.codecs = testCodecs();
  199.         console.log("sanity checking variables");
  200.         if (media.volume > 200) {
  201.             media.volume = 200;
  202.         }
  203.         if (media.volume < 0) {
  204.             media.volume = 1;
  205.         }
  206.         if (media.rate > 300) {
  207.             media.rate = 300;
  208.         }
  209.         if (media.rate < 0) {
  210.             media.rate = 10;
  211.         }
  212.         console.log("initializing form values");
  213.  
  214.         var seekval = parseInt(z_urlp("seek")) || parseInt(z_urlp("samples")) || 0;
  215.         // try to be backwards compatible
  216.         if (parseInt(z_urlp("samples"))) {
  217.             switch (config.seekopt) {
  218.                 case 1:
  219.                     config.seekopt = 0;
  220.                     seekval = (seekval / 44100) * 1000;
  221.                 case 2:
  222.                     config.seekopt = 1;
  223.             }
  224.         }
  225.  
  226.         setFormValue(1, "doloop", config.toggles.looper.checked);
  227.         setFormValue(1, "lowend", lowendPC);
  228.         setFormValue(1, "novu", !config.toggles.visualization.enabled);
  229.         setFormValue(1, "nowf", !config.toggles.waveform.enabled);
  230.         setFormValue(0, "looper", parseInt(z_urlp("looper")) || "");
  231.         setFormValue(0, "seek", seekval);
  232.         setFormValue(0, "playspeed", (media.rate / 100));
  233.         setFormValue(0, "volume", media.volume);
  234.         setFormValue(2, "seekopt", config.seekopt);
  235.  
  236.         console.log("loading loop config data");
  237.         $.ajaxSetup({
  238.             cache: false
  239.         });
  240.         $.getJSON({
  241.             url: config.url
  242.         }).done(function (data) {
  243.             obj.conf = data;
  244.             pageLoadStage2();
  245.         }).fail(function (obj, txt, err) {
  246.             console.log("a config file error was detected");
  247.             console.log(err);
  248.             var thehtml = "<strong>ERROR</strong><br>There was an error loading the audio config data.<br>";
  249.             thehtml += "Please reload the page to try again.<br>&nbsp;<br>If the issue persists, please contact zefie.<br>";
  250.             $("#playdata").html(thehtml);
  251.             $("#content_wrapper").slideDown(config.effects.slideSpeed.wrapper);
  252.         });
  253.         if (dm == "howler" || dm == "howler_testing") {
  254.             $("#notice").append("<button type=\"button\" id=\"howler_multipart\" class=\"darkform\">Howler Multipart Test</button><br><br>");
  255.             $("#howler_multipart").bind("click", function () {
  256.                 loaderStage1("howler_multipart_test");
  257.             });
  258.         }
  259.     });
  260.  
  261.     function getFormValue(t, n) {
  262.         switch (t) {
  263.             case 0:
  264.                 return $("input[name=" + n + "]").val();
  265.  
  266.             case 1:
  267.                 return $("input[name=" + n + "]").prop("checked");
  268.  
  269.             case 2:
  270.                 return $("select[name=" + n + "]").prop("selectedIndex");
  271.  
  272.             case 3:
  273.                 return $("select[name=" + n + "]").val();
  274.  
  275.             case 4:
  276.                 return $("select[name=" + n + "]").length;
  277.  
  278.         }
  279.     }
  280.  
  281.     function setFormValue(t, n, v) {
  282.         switch (t) {
  283.             case 0:
  284.                 return $("input[name=" + n + "]").val(v);
  285.  
  286.             case 1:
  287.                 return $("input[name=" + n + "]").prop("checked", v);
  288.  
  289.             case 2:
  290.                 return $("select[name=" + n + "]").prop("selectedIndex", v);
  291.  
  292.             case 3:
  293.                 return $("select[name=" + n + "]").val(v);
  294.         }
  295.     }
  296.  
  297.     function pageLoadStage2() {
  298.         console.log("config loaded with " + Object.keys(obj.conf.loops).length + " loops");
  299.         $.ajaxSetup({
  300.             cache: true
  301.         });
  302.         console.log("populating menus");
  303.         $.each(config.availfps.values, function (i) {
  304.             $("#fpssel").append($("<option />").val(config.availfps.values[i]).text(config.availfps.values[i] + 'fps'));
  305.             if (config.fps && config.fps === config.availfps.values[i]) {
  306.                 $("#fpssel option[value=\"" + config.availfps.values[i] + "\"]").prop("selected", true);
  307.             }
  308.         });
  309. 		if (obj.conf.sortlist === true) {
  310. 			obj.conf.loops.sort(function(a, b) {
  311. 				return a.title.localeCompare(b.title);
  312. 			});
  313. 		}
  314.         $.each(obj.conf.loops, function (i) {
  315.             if (config.lossless === 2 && !obj.conf.loops[i].flags.lossless) {
  316.                 return true;
  317.             }
  318.             $("#loopselect").append($("<option />").val(obj.conf.loops[i].name).text(obj.conf.loops[i].title));
  319.             if (config.loop && config.loop === obj.conf.loops[i].name) {
  320.                 $("#loopselect option[value=\"" + obj.conf.loops[i].name + "\"]").prop("selected", true);
  321.             }
  322.         });
  323.         if (media.external.loop) {
  324.             $("#loopselect").append($("<option />").val("external").text("* External Loop"));
  325.             if (!config.loop || config.loop == "external") {
  326.                 $("#loopselect option[value=\"external\"]").prop("selected", true);
  327.             }
  328.         }
  329.         console.log("initializing sliders");
  330.         $("#sseek").slider({
  331.             min: 0,
  332.             max: 0,
  333.             value: 0,
  334.             range: "min",
  335.             animate: true,
  336.             start: function (event, ui) {
  337.                 config.toggles.sliderSeeking = true;
  338.             },
  339.             stop: function (event, ui) {
  340.                 config.toggles.sliderSeeking = false;
  341.                 setPos(ui.value);
  342.             }
  343.         });
  344.         $("#svol").slider({
  345.             min: 0,
  346.             max: 100,
  347.             value: media.volume,
  348.             range: "min",
  349.             animate: true,
  350.             slide: function (event, ui) {
  351.                 setVolume(ui.value);
  352.             }
  353.         });
  354.         $("#splayspeed").slider({
  355.             min: 10,
  356.             max: 300,
  357.             value: media.rate,
  358.             range: "min",
  359.             animate: true,
  360.             slide: function (event, ui) {
  361.                 setRate(ui.value);
  362.             }
  363.         });
  364.         if (config.detection.mobile) {
  365.             $(window).resize(function () {
  366.                 setElementSize();
  367.             });
  368.             $(document).on("touchmove", function (event) {
  369.                 event.preventDefault();
  370.             });
  371.             setElementSize();
  372.         }
  373.  
  374.         if (!config.toggles.waveform.enabled) {
  375.             $("#wfzoom").remove();
  376.         }
  377.  
  378.         console.log("initializing events");
  379.         if (!config.detection.mobile) {
  380.             if (!dm) {
  381.                 $("#devnotice").html("Are you a developer? Check out the <a id=\"dbglink\" href=\"#\">debug</a> editions.");
  382.                 $("#dbglink").bind("click", function () {
  383.                     getSubmitURL("core");
  384.                 });
  385.             } else {
  386.                 $("#devnotice").html("You are in debug mode. You may choose from the following modes: ");
  387.                 var dbgmodes = ["core", "howler", "production"];
  388.                 for (var i = 0; i < dbgmodes.length; i++) {
  389.                     var dbgmode = dbgmodes[i];
  390.                     $("#devnotice").append("[ <a id=\"dbglink_" + dbgmode + "\" href=\"#\">" + dbgmode + "</a> ] ");
  391.                     $("#dbglink_" + dbgmode).bind("click", function () {
  392.                         getSubmitURL(this.id.replace("dbglink_", ""));
  393.                     });
  394.                 }
  395.                 $("#devnotice").append("<br>Also, be sure to check out the source code <a href=\"/source/\">available here</a> in human readable format.</a>");
  396.             }
  397.             $("#devnotice").append("<span style=\"float: right\">v" + config.version + "</span>");
  398.         }
  399.         $("#submitbutton").bind("click", function () {
  400.             getSubmitURL();
  401.             return false;
  402.         });
  403.         $("#resetbutton").bind("click", function () {
  404.             clearURLParams();
  405.             return false;
  406.         });
  407.         $("#fpssel").bind("change", function () {
  408.             if (obj.fps) {
  409.                 var val = getFormValue(3, "fps");
  410.                 setFPS(parseInt(val.replace("fps", "")), obj.fps.isPlaying);
  411.             }
  412.         });
  413.         $("#vol, #svol").bind("wheel", function (e) {
  414.             wheelTrigger("volume", e.originalEvent.deltaY, e.ctrlKey);
  415.             return false;
  416.         });
  417.         $("#playspeed, #splayspeed, .speedbutton").bind("wheel", function (e) {
  418.             wheelTrigger("rate", e.originalEvent.deltaY, e.ctrlKey);
  419.             return false;
  420.         });
  421.         $("#visualization").bind("wheel", function (e) {
  422.             wheelTrigger("vishue", e.originalEvent.deltaY, e.ctrlKey);
  423.             updateVUConf();
  424.             return false;
  425.         });
  426.         $("#visualization").bind("click", function (e) {
  427.             if (obj.ctx.hue.mode == 0) {
  428.                 obj.ctx.hue.mode = 1;
  429.             } else {
  430.                 obj.ctx.hue.mode = 0;
  431.             }
  432.             updateVUConf();
  433.         });
  434.         $("#visualization").bind("dblclick", function (e) {
  435.             toggleVUConf();
  436.         });
  437.         $("#sseek, #seek, #seekbutton").bind("wheel", function (e) {
  438.             wheelTrigger("seek", e.originalEvent.deltaY, e.ctrlKey);
  439.             return false;
  440.         });
  441.         $("#seekopt").bind("wheel", function (e) {
  442.             wheelTrigger("seekopt", e.originalEvent.deltaY, e.ctrlKey);
  443.             return false;
  444.         });
  445.         $("#loadbutton").bind("click", function () {
  446.             loaderStage1(getFormValue(3, 'loop'));
  447.             return false;
  448.         });
  449.         $("#loopselect").bind("change", function () {
  450.             loaderStage1(getFormValue(3, 'loop'));
  451.         });
  452.         $("#seekopt").bind("change", function () {
  453.             switchSeekDisplay();
  454.         });
  455.         $("#playpause1, #playpause2").bind("click", function () {
  456.             doPlayPause();
  457.             return false;
  458.         });
  459.         $("#speedreset").bind("click", function () {
  460.             setRateB(1);
  461.             return false;
  462.         });
  463.         $("#lowend").bind("change", function () {
  464.             if (getFormValue(1, "lowend")) {
  465.                 setFormValue(2, "fps", 0);
  466.                 setFormValue(1, "novu", true);
  467.                 $("#novu").trigger("change");
  468.                 setFormValue(1, "nowf", true);
  469.                 $("#nowf").trigger("change");
  470.             } else {
  471.                 setFormValue(1, "novu", false);
  472.                 $("#novu").trigger("change");
  473.                 setFormValue(1, "nowf", false);
  474.                 $("#nowf").trigger("change");
  475.                 setFormValue(2, "fps", 8);
  476.             }
  477.             $("#fpssel").trigger("change");
  478.         });
  479.         $("#novu").bind("change", function () {
  480.             if (getFormValue(1, "lowend") && !getFormValue(1, "novu")) {
  481.                 setFormValue(1, "lowend", false);
  482.             }
  483.             if (getFormValue(1, "novu")) {
  484.                 config.toggles.visualization.enabled = false;
  485.                 $("#visualization").empty();
  486.                 $("#visualization").hide();
  487.             } else {
  488.                 config.toggles.visualization.enabled = true;
  489.                 if (obj.player) {
  490.                     if (obj.player.playing()) {
  491.                         $("#visualization").css("height", $("#playdata").height() + "px");
  492.                         $("#visualization").show();
  493.                         setupVU();
  494.                     }
  495.                 }
  496.             }
  497.         });
  498.         $("#nowf").bind("change", function () {
  499.             if (getFormValue(1, "lowend") && !getFormValue(1, "nowf")) {
  500.                 setFormValue(1, "lowend", false);
  501.             }
  502.             if (getFormValue(1, "nowf")) {
  503.                 config.toggles.waveform.enabled = false;
  504.                 if (obj.wavesurfer) {
  505.                     obj.wavesurfer.stop();
  506.                     obj.wavesurfer.destroy();
  507.                     $("#wfzoom").hide();
  508.                     $("#waveform").hide();
  509.                     $("#waveform").empty();
  510.                 }
  511.             } else {
  512.                 config.toggles.waveform.enabled = true;
  513.                 if (obj.player) {
  514.                     if (obj.player.playing()) {
  515.                         $("#waveform").show();
  516.                         setupWaveform();
  517.                     }
  518.                 }
  519.             }
  520.         });
  521.         $("#wfzoom").bind("click", function () {
  522.             toggleWFZoom();
  523.             return false;
  524.         });
  525.         $("#speedset").bind("click", function () {
  526.             setRateB(getFormValue(0, 'playspeed'));
  527.             return false;
  528.         });
  529.         $("#seek, #looper").bind("keypress", function () {
  530.             return catchEnter(event, doSeek);
  531.         });
  532.         $("#vol").bind("keypress", function () {
  533.             return catchEnter(event, setVolumeC);
  534.         });
  535.         $("#playspeed").bind("keypress", function () {
  536.             return catchEnter(event, setRateC);
  537.         });
  538.         $("#seekbutton").bind("click", function () {
  539.             doSeek();
  540.             return false;
  541.         });
  542.         $("#seekbutton2").bind("click", function () {
  543.             setSeekData();
  544.             return false;
  545.         });
  546.  
  547.         if (config.detection.microsoft) {
  548.             console.log("microsoft browser detected, show warning");
  549.             if (config.detection.mobile) {
  550.                 $("#notice").append("Mobile IE? Your browser will likely have a gap in the loops.<br><br>");
  551.             } else if (config.detection.microsoft < 18) {				
  552.                 if (config.detection.microsoft >= 14) {
  553.                     $("#notice").append("Gap in your loop? Update to the latest version of Microsoft Edge!<br><br>");
  554.                 } else {
  555.                     $("#notice").append("This browser (Internet Explorer " + config.detection.microsoft + ") is not fully supported. Some features may not be available or work correctly.<br><br>");
  556.                 }
  557.             }
  558.         }
  559.  
  560.         if (!config.usingWebAudio) {
  561.             $("#notice").append("Running in HTML5 compatiblity mode, many features are unavailable in this mode.<br><br>");
  562.         }
  563.  
  564.         console.log("initialization complete, showing user interface");
  565.         console.log("--------------");
  566.  
  567.         $("#content_wrapper").slideDown(config.effects.slideSpeed.wrapper, 'swing', function () {
  568.             $("#content").slideDown(config.effects.slideSpeed.content);
  569.         });
  570.  
  571.         if (config.toggles.visualization.enabled) {
  572.             $("#visualization").css("height", $("#playdata").height() + "px");
  573.         }
  574.         playbackStopped();
  575.         if (config.autoplay && getFormValue(3, "loop")) {
  576.             console.log("autoplay specified, initiate loading...");
  577.             loaderStage1(getFormValue(3, "loop"));
  578.         }
  579.     }
  580.  
  581.     function checkHueSanity(v) {
  582.         if (v < 0 || v > 360) {
  583.             return false;
  584.         } else {
  585.             return true;
  586.         }
  587.     }
  588.  
  589.     function updateVUConf() {
  590.         if (config.toggles.visualization.enabled) {
  591.             setFormValue(0, "starthue", obj.ctx.hue.bar.start);
  592.             setFormValue(0, "endhue", obj.ctx.hue.bar.end);
  593.             setFormValue(2, "huemode", obj.ctx.hue.mode);
  594.         }
  595.     }
  596.  
  597.     function applyVUConf() {
  598.         if (config.toggles.visualization.enabled) {
  599.             if (checkHueSanity(parseInt(getFormValue(0, "starthue")))) {
  600.                 obj.ctx.hue.bar.start = parseInt(getFormValue(0, "starthue"));
  601.             }
  602.             if (checkHueSanity(parseInt(getFormValue(0, "endhue")))) {
  603.                 obj.ctx.hue.bar.end = parseInt(getFormValue(0, "endhue"));
  604.             }
  605.             obj.ctx.hue.mode = getFormValue(2, "huemode");
  606.             updateVUConf();
  607.             if (obj.ctx.hue.mode == 0) {
  608.                 obj.ctx.hue.bar.direction = 0;
  609.             }
  610.         }
  611.     }
  612.  
  613.     function toggleVUConf() {
  614.         if (config.toggles.visualization.enabled) {
  615.             if (!config.toggles.visualization.configShown) {
  616.                 var thehtml = '<div id="vuconfig">';
  617.                 thehtml += 'Bar Hue: <span class="bracket">[</span>Start: <input id="starthue" name="starthue" class="smallform darkform"><span class="bracket">]</span>';
  618.                 thehtml += '<span class="bracket">[</span>End: <input id="endhue" name="endhue" class="smallform darkform"><span class="bracket">]</span><br>';
  619.                 thehtml += '<span class="bracket">[</span>Mode: <select id="huemode" name="huemode" class="darkform"><span class="bracket">]</span>';
  620.                 thehtml += '<option value="0">Repeat</option>';
  621.                 thehtml += '<option value="1">Single</option>';
  622.                 thehtml += '</select><span class="bracket">]</span></div>';
  623.                 $("#settings").append(thehtml);
  624.                 $("#starthue, #endhue").bind("keypress", function () {
  625.                     return catchEnter(event, applyVUConf);
  626.                 });
  627.                 $("#huemode").bind("change", function () {
  628.                     applyVUConf();
  629.                 });
  630.                 updateVUConf();
  631.                 config.toggles.visualization.configShown = true;
  632.             } else {
  633.                 $("#vuconfig").remove();
  634.                 config.toggles.visualization.configShown = false;
  635.             }
  636.         }
  637.     }
  638. 	function WFZoom(zoom) {
  639. 		if (zoom <= 0) {
  640. 			obj.wavesurfer.zoom(0);
  641. 			$("#wfzoom").text("Zoom In");
  642. 		} else {
  643. 			obj.wavesurfer.zoom(zoom);
  644. 			$("#wfzoom").text("Zoom Out");
  645. 		}
  646. 	}
  647.  
  648.     function toggleWFZoom() {
  649.         if (obj.wavesurfer && config.toggles.waveform.enabled) {
  650.             if ($("#wfzoom").text() == "Zoom Out") {
  651. 				WFZoom(0);
  652.             } else {
  653. 				WFZoom(config.wavesurferMaxZoom);
  654.             }
  655.         }
  656.     }
  657.     function setElementSize() {
  658.         var width = (window.innerWidth > 0) ? window.innerWidth : screen.width;
  659.         $("#loopselect").css("width", width - 95 + "px");
  660.         $("#sseek").css("width", width - 100 + "px");
  661.         $("#svol").css("width", width - 100 + "px");
  662.         $("#splayspeed").css("width", width - 100 + "px");
  663.         window.scrollTo(0, 0);
  664.     }
  665.  
  666.     function wheelTrigger(e, d, c) {
  667.         if (d < 0) {
  668.             d = 1;
  669.         } else {
  670.             d = 0;
  671.         }
  672.         var valchg;
  673.         switch (e) {
  674.             case "volume":
  675.                 var valchg = 1;
  676.                 if (c) {
  677.                     valchg = 5;
  678.                 }
  679.                 if (d) {
  680.                     valchg = parseInt(getFormValue(0, "volume")) + valchg;
  681.                     if (valchg <= 100) {
  682.                         setVolumeB(valchg);
  683.                     }
  684.                 } else {
  685.                     valchg = parseInt(getFormValue(0, "volume")) - valchg;
  686.                     if (valchg >= 0) {
  687.                         setVolumeB(valchg);
  688.                     }
  689.                 }
  690.                 break;
  691.             case "rate":
  692.                 var valchg = 0.01;
  693.                 if (c) {
  694.                     valchg = 0.10;
  695.                 }
  696.                 if (d) {
  697.                     valchg = roundNumber(parseFloat(getFormValue(0, "playspeed")) + valchg, 2);
  698.                     if (valchg <= 3) {
  699.                         setRateB(valchg);
  700.                     }
  701.                 } else {
  702.                     valchg = roundNumber(parseFloat(getFormValue(0, "playspeed")) - valchg, 2);
  703.                     if (valchg >= 0.10) {
  704.                         setRateB(valchg);
  705.                     }
  706.                 }
  707.                 break;
  708.             case "seek":
  709.                 if (d) {
  710.                     scrollSeek(c, 1);
  711.                 } else {
  712.                     scrollSeek(c, 0);
  713.                 }
  714.                 break;
  715.             case "wfzoom": {
  716.                 if (obj.wavesurfer && config.toggles.waveform.enabled) {
  717.                     var valchg = 50;
  718.                     if (c) {
  719.                         valchg = 100;
  720.                     }
  721.                     if (d) {
  722.                         var newval = obj.wavesurfer.params.minPxPerSec + valchg;
  723.                         if (newval >= config.wavesurferMaxZoom) {
  724.                             newval = config.wavesurferMaxZoom;
  725.                             $("#wfzoom").text("Zoom Out");
  726.                         }
  727.                         if (obj.wavesurfer.params.minPxPerSec != newval) {
  728.                             obj.wavesurfer.zoom(newval);
  729.                         }
  730.                     } else {
  731.                         var newval = obj.wavesurfer.params.minPxPerSec - valchg;
  732.                         if (newval <= 0) {
  733.                             newval = 0;
  734.                             $("#wfzoom").text("Zoom In");
  735.                         }
  736.                         if (obj.wavesurfer.params.minPxPerSec != newval) {
  737.                             obj.wavesurfer.zoom(newval);
  738.                         }
  739.                     }
  740.                 }
  741.                 break;
  742.             }
  743.             case "vishue":
  744.                 var valchg = 2;
  745.                 if (c) {
  746.                     valchg = 10;
  747.                 }
  748.                 if (d) {
  749.                     if (checkHueSanity(obj.ctx.hue.bar.start + valchg) &&
  750.                         checkHueSanity(obj.ctx.hue.bar.end + valchg)) {
  751.                         obj.ctx.hue.bar.start += valchg;
  752.                         obj.ctx.hue.bar.end += valchg;
  753.                     }
  754.                 } else {
  755.                     if (checkHueSanity(obj.ctx.hue.bar.start - valchg) &&
  756.                         checkHueSanity(obj.ctx.hue.bar.end - valchg)) {
  757.                         obj.ctx.hue.bar.start -= valchg;
  758.                         obj.ctx.hue.bar.end -= valchg;
  759.                     }
  760.                 }
  761.                 break;
  762.             case "seekopt":
  763.                 if (d) {
  764.                     if (getFormValue(2, "seekopt") > 0) {
  765.                         setFormValue(2, "seekopt", getFormValue(2, "seekopt") - 1);
  766.                     } else {
  767.                         setFormValue(2, "seekopt", getFormValue(4, "seekopt") - 1);
  768.                     }
  769.                 } else {
  770.                     if (getFormValue(2, "seekopt") < getFormValue(4, "seekopt") - 1) {
  771.                         setFormValue(2, "seekopt", getFormValue(2, "seekopt") + 1);
  772.                     } else {
  773.                         setFormValue(2, "seekopt", 0);
  774.                     }
  775.                 }
  776.                 switchSeekDisplay();
  777.                 break;
  778.         }
  779.     }
  780.  
  781.     function scrollSeek(c, d) {
  782.         if (obj.player) {
  783.             var seekOffset = 250;
  784.             if (c) {
  785.                 seekOffset = 1000
  786.             };
  787.             if (obj.player.playing()) {
  788.                 var currpos = (obj.player.seek(obj.player._sounds[0]._id) * 1000);
  789.                 if (d) {
  790.                     if (currpos + seekOffset <= media.duration) {
  791.                         setSeekMs(currpos + seekOffset);
  792.                     } else {
  793.                         setSeekMs(media.duration);
  794.                     }
  795.                 } else {
  796.                     if (currpos - seekOffset >= 0) {
  797.                         setSeekMs(currpos - seekOffset);
  798.                     }
  799.                 }
  800.                 setSeekData();
  801.             }
  802.         }
  803.     }
  804.  
  805.     function getVars(f) {
  806.         var result = false;
  807.         if (media.external.flags && config.debug) {
  808.             console.log("overriding flags with externalflags...");
  809.             result = getExternalFlags(media.external.flags);
  810.         } else {
  811.             $.each(obj.conf.loops, function (i) {
  812.                 if (obj.conf.loops[i].name === f) {
  813.                     result = obj.conf.loops[i].flags;
  814.                 }
  815.             });
  816.         }
  817.         return result;
  818.     }
  819.  
  820.     function setMediaCodec(ext) {
  821.         switch (ext) {
  822.             case "webm":
  823.                 media.codec = "audio/webm";
  824.                 break;
  825.             case "opus":
  826.                 media.codec = "audio/ogg; codecs=opus";
  827.                 break;
  828.             case "m4a":
  829.                 media.codec = "audio/mp4";
  830.                 break;
  831.             case "mp3":
  832.                 media.codec = "audio/mp3";
  833.                 break;
  834.             case "wav":
  835.                 media.codec = "audio/wav";
  836.                 break;
  837.             case "flac":
  838.                 media.codec = "audio/flac";
  839.                 break;
  840.         }
  841.         if (media.codec === null) {
  842.             console.log("Unsupported media type");
  843.             return false;
  844.         }
  845.         if (config.forceFormat) {
  846.             console.log("forcing codec " + media.codec);
  847.         } else {
  848.             console.log("chose codec " + media.codec + " as best match for this browser");
  849.         }
  850.         return true;
  851.     }
  852.  
  853.     function checkMediaSupported(mime) {
  854.         var ext = mime.split("/")[1];
  855.         if (ext.substr(0, 5) == "x-ms-") {
  856.             ext = ext.substr(5);
  857.         }
  858.         if (ext.substr(0, 2) == "x-") {
  859.             ext = ext.substr(2);
  860.         }
  861.         return config.detection.codecs[ext];
  862.     }
  863.  
  864.     function showFriendlyError(httperr) {
  865.         switch (httperr) {
  866.             case 401:
  867.                 return "Authorization Required";
  868.             case 403:
  869.                 return "Access Denied";
  870.             case 500:
  871.                 return "Internal Server Error (Remote End)";
  872.             default:
  873.                 return null;
  874.         }
  875.     }
  876.  
  877.     function getRemoteMediaType(url) {
  878.         var req = new XMLHttpRequest();
  879.         req.open('HEAD', url);
  880.         req.onload = function (e) {
  881.             if (e.target.status != 200) {
  882.                 $("#playdata").html("<strong>Error loading remote file...</strong><br>The remote server reported error " + e.target.status + "...<br>" + showFriendlyError(e.target.status) + "<br><br><br><br>");
  883.                 config.howl.loading = false;
  884.             } else {
  885.                 media.codec = this.getResponseHeader('content-type');
  886.                 remoteLoaderStage();
  887.             }
  888.         }
  889.         req.onerror = function (e) {
  890.             $("#playdata").html("<strong>Error loading remote file...</strong><br>This is likely because the remote server<br>has not authorized us via CORS<br>to access this file in our script.<br><br>");
  891.             config.howl.loading = false;
  892.         }
  893.         req.send();
  894.     }
  895.  
  896.     function remoteLoaderStage() {
  897.         console.log("entering remoteLoaderStage");
  898.         if (!checkMediaSupported(media.codec)) {
  899.             $("#playdata").html("<strong>Error playing remote file...</strong><br>Your browser does not appear to<br>support the format of the external file provided.<br><br><br>");
  900.             config.howl.loading = false;
  901.             return false;
  902.         }
  903.         if (media.external.flags) {
  904.             obj.flags = getExternalFlags(media.external.flags);
  905.         }
  906.         getDataFile(media.external.loop);
  907.         return true;
  908.     }
  909.  
  910.     function getExternalFlags(flags) {
  911.         var extflag = flags.split(":");
  912.         var extobj = {};
  913.         for (var i = 0; i < extflag.length; i++) {
  914.             switch (extflag[i]) {
  915.                 case "duration":
  916.                     if (i + 1 < extflag.length) {
  917.                         extobj.duration = parseInt(extflag[i + 1]);
  918.                     }
  919.                     break;
  920.                 case "loop":
  921.                     if (i + 1 < extflag.length) {
  922.                         extobj.loop = parseInt(extflag[i + 1]);
  923.                     }
  924.                     break;
  925.                 case "start":
  926.                     if (i + 1 < extflag.length) {
  927.                         extobj.start = parseInt(extflag[i + 1]);
  928.                     }
  929.                     break;
  930.             }
  931.         }
  932.         return extobj;
  933.     }
  934.  
  935.     function loaderStage1(loopname) {
  936.         if (config.toggles.waveform.enabled) {
  937.             $("#waveform").hide();
  938.         }
  939.         if (config.toggles.visualization.enabled) {
  940.             $("#visualization").hide();
  941.         }
  942.         if (config.detection.mobile) {
  943.             window.scrollTo(0, 0);
  944.         }
  945.         console.log("entering loaderStage1 for " + loopname);
  946.         config.howl.mptest = false;
  947.         config.howl.currentPart = 1;
  948.         if (loopname === "howler_multipart_test") {
  949.             loopname = "music_loop_001";
  950.             $("#loopselect option[value=\"" + loopname + "\"]").prop("selected", true);
  951.             config.howl.mptest = true;
  952.         }
  953.         media.duration = 0;
  954.         media.codec = null;
  955.         obj.flags = {};
  956.         var fileURL = null;
  957.         if (!config.howl.loading) {
  958.             config.howl.loading = true;
  959.             if (obj.player) {
  960.                 obj.player.stop();
  961.                 obj.player.unload();
  962.                 obj.player = null;
  963.             }
  964.             var fileExt;
  965.             if (media.external.loop && loopname == "external") {
  966.                 fileURL = media.external.loop;
  967.                 getRemoteMediaType(fileURL);
  968.                 return false;
  969.             } else {
  970.                 config.howl.name = loopname;
  971.                 obj.flags = getVars(config.howl.name);
  972.                 if (obj.flags.file) {
  973.                     config.howl.file = obj.flags.file;
  974.                 } else {
  975.                     config.howl.file = loopname;
  976.                 }
  977.  
  978. 				if (typeof obj.flags.ext !== 'undefined') {
  979. 					console.log("forced extension: "+obj.flags.ext+"...");
  980. 					fileExt = obj.flags.ext;
  981. 					fileURL = config.siteURL + obj.conf.audioPath + config.howl.file + "." + fileExt;
  982. 					setMediaCodec(fileExt);
  983. 					getDataFile(fileURL);
  984. 					return true;
  985. 				} else {			
  986. 					console.log("searching for most optimal file...");
  987. 					findFile().then(function (fileRes) {
  988. 						if (typeof fileRes !== 'undefined') {
  989. 							fileURL = fileRes[0];
  990. 							fileExt = fileRes[1];
  991. 						}
  992. 						if (typeof fileURL !== 'undefined' && typeof fileExt !== 'undefined') {
  993. 							setMediaCodec(fileExt);
  994. 							getDataFile(fileURL);
  995. 							return true;
  996. 						} else {
  997. 							$("#playdata").html("<strong>Error playing file...</strong><br>Your browser does not appear to<br>support any of our available formats for this loop.<br><br><br><br>");
  998. 							config.howl.loading = false;
  999. 							return false;
  1000. 						}	
  1001. 					});
  1002. 				}
  1003.  
  1004.  
  1005.             }
  1006.         }
  1007.         config.howl.loading = false;
  1008.         return false;
  1009.     }
  1010.  
  1011.     function findFile(blacklist) {
  1012.         return new Promise(function (resolve, reject) {
  1013.             blacklist = typeof blacklist !== 'undefined' ? blacklist : {};
  1014.             var fileExt, fileURL;
  1015.  
  1016.             if ((config.lossless == 1 || config.lossless == 2) && obj.flags.lossless) {
  1017.                 if (config.detection.codecs.flac && (!config.forceFormat || config.forceFormat == 4) && !("flac" in blacklist) && !fileExt) {
  1018.                     fileExt = "flac";
  1019.                 }
  1020.                 if (config.detection.codecs.wav && (!config.forceFormat || config.forceFormat == 5) && !("wav" in blacklist) && !fileExt) {
  1021.                     fileExt = "wav";
  1022.                 }
  1023.             }
  1024.             if (config.lossless != 2) {
  1025.                 if (config.detection.codecs.opus && (!config.forceFormat || config.forceFormat == 1) && !("opus" in blacklist) && !fileExt) {
  1026.                     fileExt = "opus";
  1027.                 }
  1028.                 if (config.detection.codecs.webm && (!config.forceFormat || config.forceFormat == 1) && !("webm" in blacklist) && !fileExt) {
  1029.                     fileExt = "webm";
  1030.                 }
  1031.                 if (config.detection.codecs.m4a && (!config.forceFormat || config.forceFormat == 2) && !("m4a" in blacklist) && !fileExt) {
  1032.                     fileExt = "m4a";
  1033.                 }
  1034.                 if (config.detection.codecs.mp3 && (!config.forceFormat || config.forceFormat == 3) && !("mp3" in blacklist) && !fileExt) {
  1035.                     fileExt = "mp3";
  1036.                 }
  1037.             }
  1038.             if (config.siteURL && config.howl.file && fileExt) {
  1039.                 fileURL = config.siteURL + obj.conf.audioPath + config.howl.file + "." + fileExt;
  1040.                 if (obj.flags.timestamp) {
  1041.                     var epoch = (new Date(obj.flags.timestamp).getTime() / 1000);
  1042.                     fileURL += "?_ts=" + epoch;
  1043.                 }
  1044.                 UrlExists(fileURL).then(function (result) {
  1045.                     console.log(fileExt + " media found");
  1046.                     resolve(Array(fileURL, fileExt));
  1047.                 }).catch(function (err) {
  1048.                     console.log(fileExt + " media does not exist, continue searching...");
  1049.                     blacklist[fileExt] = true;
  1050.                     findFile(blacklist).then(function (res) {
  1051.                         resolve(res);
  1052.                     }).catch(function (res) {
  1053.                         reject(res);
  1054.                     });
  1055.                 });
  1056.             } else {
  1057.                 reject(Array(undefined, undefined));
  1058.             }
  1059.         });
  1060.     }
  1061.  
  1062.     function UrlExists(url) {
  1063.       if (typeof offlineFileExists === "function") {
  1064.         return new Promise(function (resolve, reject) {
  1065.           if (offlineFileExists(url))
  1066.             resolve(true);
  1067.           else
  1068.             reject(false)
  1069.         });
  1070.       } else {
  1071.         return new Promise(function (resolve, reject) {
  1072.             var http = new XMLHttpRequest();
  1073.             http.open('HEAD', url, true);
  1074.             http.onload = function (e) {
  1075.                 if (http.status != 404) {
  1076.                     resolve(true);
  1077.                 } else {
  1078.                     reject(false);
  1079.                 }
  1080.             }
  1081.             http.error = function (e) {
  1082.                 reject(false);
  1083.             }
  1084.             http.send();
  1085.         });
  1086.       }
  1087.     }
  1088.  
  1089.   function loaderStage2() {
  1090.     console.log("entering loaderStage2");
  1091.     if (!obj.flags.duration) {
  1092.       console.log("duration var not defined, calculating full file length. result may create gap");
  1093.       var howlInitializers = {
  1094.         autoplay: false,
  1095.         loop: false,
  1096.         onload: loaderStageFinal,
  1097.         onloaderror: howlError
  1098.       }
  1099.       if (config.usingWebAudio) {
  1100.         howlInitializers.html5 = false;
  1101.         howlInitializers.arraybuffer = media.dataBuffer;
  1102.         howlInitializers.contenttype = media.codec;
  1103.       } else {
  1104.         var dataUri = "data:" + media.codec + ";base64," + base64ArrayBuffer(media.dataBuffer);
  1105.         howlInitializers.html5 = true;
  1106.         howlInitializers.src = [dataUri];
  1107.       }
  1108.       obj.player = new Howl(howlInitializers);
  1109.     } else {
  1110.       loaderStageFinal();
  1111.     }
  1112.   }
  1113.  
  1114.   function loaderStageFinal() {
  1115.     console.log("entering loaderStageFinal");
  1116.     if (obj.player) {
  1117.       media.duration = Math.round(obj.player._duration * 1000);
  1118.       console.log("calculated result: " + media.duration + "ms");
  1119.       obj.player.unload();
  1120.       obj.player = null;
  1121.     }
  1122.     console.log("contents of the file flags object:");
  1123.     console.log(obj.flags);
  1124.     var howlSprite;
  1125.     var nextSprite = "part2";
  1126.     var start = 0;
  1127.     if (obj.flags.loop) {
  1128.       if (obj.flags.duration) {
  1129.         media.duration = obj.flags.duration;
  1130.       }
  1131.       var loopms = obj.flags.loop;
  1132.       if (obj.flags.start) {
  1133.         start = obj.flags.start;
  1134.       }
  1135.       if (start == loopms) {
  1136.         howlSprite = {
  1137.           part1: [start, (media.duration - start)]
  1138.         };
  1139.         nextSprite = null;
  1140.       } else {
  1141.         howlSprite = {
  1142.           part1: [start, loopms],
  1143.           part2: [loopms, (media.duration - loopms)]
  1144.         };
  1145.       }
  1146.     } else {
  1147.       if (obj.flags.duration) {
  1148.         media.duration = obj.flags.duration;
  1149.       }
  1150.       if (obj.flags.start) {
  1151.         start = obj.flags.start;
  1152.       }
  1153.       nextSprite = null;
  1154.       howlSprite = {
  1155.         part1: [start, media.duration]
  1156.       };
  1157.     }
  1158.     if (config.howl.mptest) {
  1159.       var partLen = 9146;
  1160.       howlSprite = {
  1161.         part1: [0, partLen],
  1162.         part2: [partLen, partLen],
  1163.         part3: [partLen * 2, partLen],
  1164.         part4: [partLen * 3, partLen],
  1165.         part5: [partLen * 4, partLen],
  1166.         part6: [partLen * 5, partLen],
  1167.         part7: [partLen * 6, partLen],
  1168.         part8: [partLen * 7, partLen],
  1169.         part9: [partLen * 8, partLen],
  1170.         part10: [partLen * 9, (media.duration - (partLen * 9))]
  1171.       };
  1172.     }
  1173.     config.howl.numParts = Object.keys(howlSprite).length;
  1174.     console.log("contents of the howl sprite object we built (" + config.howl.numParts + " parts):");
  1175.     console.log(howlSprite);
  1176.     $("#sseek").slider("value", 0);
  1177.     $("#sseek").slider("option", "max", 0);
  1178.     var howlInitializers = {
  1179.       autoplay: false,
  1180.       loop: true,
  1181.       sprite: howlSprite,
  1182.       nextsprite: nextSprite,
  1183.       onspritechange: spriteChanged,
  1184.       onload: loadComplete,
  1185.       onseek: getplaydata,
  1186.       onplay: playbackStarted,
  1187.       onstop: playbackStopped,
  1188.       onloaderror: howlError,
  1189.       rate: (media.rate / 100),
  1190.       volume: (media.volume / 100)
  1191.     }
  1192.     if (config.usingWebAudio) {
  1193.       howlInitializers.html5 = false;
  1194.       howlInitializers.arraybuffer = media.dataBuffer;
  1195.       howlInitializers.contenttype = media.codec;
  1196.     } else {
  1197.       var dataUri = "data:" + media.codec + ";base64," + base64ArrayBuffer(media.dataBuffer);
  1198.       howlInitializers.html5 = true;
  1199.       howlInitializers.src = [dataUri];
  1200.     }
  1201.     console.log("contents of the howl initialization object:");
  1202.     console.log(howlInitializers);
  1203.     obj.player = new Howl(howlInitializers);
  1204.   }
  1205.  
  1206.   function howlError() {
  1207.     $("#playdata").html("<strong>Error playing file...</strong><br>There was an internal error decoding this file.<br>The file is unusable.<br><br><br>");
  1208.     config.howl.loading = false;
  1209.   }
  1210.  
  1211.   function loadComplete() {
  1212.     if (obj.player) {
  1213.       if (config.usingWebAudio) {
  1214.         media.channels = obj.player._decoded.numberOfChannels;
  1215.       }
  1216.       if (config.toggles.waveform.enabled && config.usingWebAudio) {
  1217.         setupWaveform();
  1218.       }
  1219.       if (config.toggles.visualization.enabled && config.usingWebAudio) {
  1220.         setupVU();
  1221.       }	  
  1222.       $("#sseek").slider("option", "max", media.duration);
  1223.       obj.player.play("part" + config.howl.currentPart);
  1224.       debugPartPlayback();
  1225.     }
  1226.   }
  1227.  
  1228.   function setupVU() {
  1229.     if (obj.player) {
  1230.       if (obj.ctx.canvas) {
  1231.         $("#ctxCanvas").remove();
  1232.       }
  1233.       obj.ctx.visual.width = Math.round($("#visualization").innerWidth());
  1234.       obj.ctx.visual.height = Math.round($("#visualization").innerHeight());
  1235.       obj.ctx.analyzer = Howler.ctx.createAnalyser();
  1236. 	  obj.ctx.analyzer.minDecibels = -76;
  1237. 	  obj.ctx.analyzer.maxDecibels = -20;
  1238.       obj.ctx.analyzer.smoothingTimeConstant = 0.75;
  1239.       obj.ctx.analyzer.fftSize = 1024;
  1240.       obj.player._sounds[0]._node.connect(obj.ctx.analyzer);
  1241.       $('<canvas>').attr({
  1242.         id: 'ctxCanvas',
  1243. 		width: obj.ctx.visual.width + 'px',
  1244.         height: obj.ctx.visual.height + 'px'		
  1245.       }).appendTo("#visualization");
  1246.       obj.ctx.canvas = document.getElementById("ctxCanvas");
  1247.       obj.ctx.canvasContext = obj.ctx.canvas.getContext("2d");
  1248.       $("#visualization").show();
  1249.       renderVUFrame();
  1250.     }
  1251.   }
  1252.  
  1253.   function renderVUFrame() {
  1254. 	var x = 0;
  1255.     var bufferLength = obj.ctx.analyzer.frequencyBinCount;
  1256.     var dataArray = new Uint8Array(bufferLength);
  1257. 	obj.ctx.analyzer.getByteFrequencyData(dataArray);
  1258.     obj.ctx.canvasContext.clearRect(0, 0, obj.ctx.canvas.width, obj.ctx.canvas.height);
  1259.     var barWidth = (obj.ctx.canvas.width / bufferLength) * obj.ctx.hue.multiplier;
  1260.     var barHue = obj.ctx.hue.bar.start;
  1261.     var barHueMod = obj.ctx.hue.modifier;
  1262.     if (obj.ctx.hue.mode == 1) {
  1263.       barHueMod = ((obj.ctx.hue.bar.end - obj.ctx.hue.bar.start) / obj.ctx.analyzer.fftSize);
  1264.     }
  1265.     for (var i = 0; i < bufferLength; i = i + obj.ctx.hue.multiplier) {
  1266.       var barHeightPercent = ((dataArray[parseInt(i)] / 255) * 100);
  1267.       var barHeight = ((barHeightPercent / 100) * obj.ctx.visual.height);
  1268.       switch (obj.ctx.hue.mode) {
  1269.         case 0:
  1270.          if (obj.ctx.hue.bar.start < obj.ctx.hue.bar.end) {
  1271.            if (obj.ctx.hue.bar.direction == 1) {
  1272.              barHue += barHueMod;
  1273.              if (barHue >= obj.ctx.hue.bar.end) {
  1274.                obj.ctx.hue.bar.direction = 0;
  1275.              }
  1276.            } else {
  1277.              barHue -= barHueMod;
  1278.              if (barHue <= obj.ctx.hue.bar.start) {
  1279.                obj.ctx.hue.bar.direction = 1;
  1280.              }
  1281.            }
  1282.          } else {
  1283.            if (obj.ctx.hue.bar.direction == 1) {
  1284.              barHue -= barHueMod;
  1285.              if (barHue <= obj.ctx.hue.bar.end) {
  1286.                obj.ctx.hue.bar.direction = 0;
  1287.              }
  1288.            } else {
  1289.              barHue += barHueMod;
  1290.              if (barHue >= obj.ctx.hue.bar.start) {
  1291.                obj.ctx.hue.bar.direction = 1;
  1292.              }
  1293.           }
  1294.        }
  1295.        break;
  1296.        case 1:
  1297.          barHue += barHueMod;
  1298.          break;
  1299.       }
  1300.       obj.ctx.canvasContext.fillStyle = 'hsl(' + barHue + ', 100%, 65%)';
  1301.       obj.ctx.canvasContext.fillRect(x, obj.ctx.canvas.height - barHeight, barWidth, barHeight);
  1302.       x += barWidth + 1;
  1303.     }
  1304.   }
  1305.  
  1306.   function setupWaveform() {
  1307.     if (obj.player) {
  1308.       console.log("binding click event for waveform");
  1309.       $("#waveform").on('click', function () {
  1310.         config.toggles.waveform.seeking = true;
  1311.       });
  1312.       $("#waveform").on('wheel', function (e) {
  1313.         if (e.shiftKey) {
  1314.           wheelTrigger("wfzoom", e.originalEvent.deltaY, e.ctrlKey);
  1315.         } else {
  1316.           wheelTrigger("seek", e.originalEvent.deltaY, e.ctrlKey);
  1317.         }
  1318.         return false;
  1319.       });
  1320.       if (obj.wavesurfer) {
  1321.         obj.wavesurfer.destroy();
  1322.         obj.wavesurfer = null;
  1323.       }
  1324.       var stereo,
  1325.       wfheight;
  1326.       if (media.channels == 2) {
  1327.         stereo = true;
  1328.         wfheight = ($("#waveform").height() / 2) + 1;
  1329.       } else {
  1330.         stereo = false;
  1331.         wfheight = $("#waveform").height() + 1;
  1332.       }
  1333.       console.log("creating wavesurfer object");
  1334.       obj.wavesurfer = WaveSurfer.create({
  1335.           audioContext: Howler.ctx,
  1336.           height: wfheight,
  1337.           container: '#waveform',
  1338.           waveColor: '#959',
  1339.           progressColor: 'purple',
  1340.           cursorColor: '#999',
  1341.           autoCenter: true,
  1342.           splitChannels: stereo,
  1343.           hideScrollbar: true,
  1344.           minPxPerSec: config.wavesurferMaxZoom
  1345.         });
  1346.       obj.wavesurfer.on('seek', function (v) {
  1347.         if (config.toggles.waveform.seeking) {
  1348.           var newpos = (obj.wavesurfer.backend.buffer.duration * v) * 1000;
  1349.           if (newpos <= media.duration) {
  1350.             setPos(newpos, true);
  1351.           }
  1352.           config.toggles.waveform.seeking = false;
  1353.         }
  1354.       });
  1355.       obj.wavesurfer.on('backend-created', function () {
  1356.         if (obj.wavesurfer.backend.buffer) {
  1357.           obj.wavesurfer.drawBuffer();
  1358.         }
  1359.       });
  1360.       obj.wavesurfer.on('ready', function () {
  1361. 		  WFZoom(obj.wavesurfer.params.minPxPerSec);
  1362.       });
  1363.       obj.wavesurfer.on('redraw', function () {
  1364.         $("#wfzoom").show();
  1365.         $("#waveform").show();
  1366.       });
  1367.       console.log("loading media buffer into obj.wavesurfer");
  1368.       if (!obj.wavesurfer.isDestroyed) {
  1369.         obj.wavesurfer.loadDecodedBuffer(obj.player._decoded);
  1370.       }
  1371.     }
  1372.   }
  1373.  
  1374.   function debugPartPlayback() {
  1375.     var logstr = "playing audio segment part " + config.howl.currentPart;
  1376.     var nextpart = obj.player._nextsprite;
  1377.     if (nextpart) {
  1378.       nextpart = nextpart.replace("part", "");
  1379.       logstr += ", next part is " + nextpart;
  1380.     } else {
  1381.       logstr += ", this is the final part";
  1382.     }
  1383.     console.log(logstr);
  1384.   }
  1385.  
  1386.   function playbackStarted() {
  1387.     if (obj.player) {
  1388.       config.howl.loading = false;
  1389.       $("#playpause1").hide();
  1390.       $("#playpause2").show();
  1391.       if (obj.fps) {
  1392.         if (!obj.fps.isPlaying) {
  1393.           obj.fps.start();
  1394.         }
  1395.       } else {
  1396.         obj.fps = new FpsCtrl(config.fps, getplaydata);
  1397.         obj.fps.start();
  1398.       }
  1399. 	  if (config.toggles.visualization.enabled) {
  1400. 		setupVU();
  1401. 	  }
  1402.     }
  1403.   }
  1404.  
  1405.   function doPlayPause() {
  1406.     if (obj.player) {
  1407.       if (obj.player.playing()) {
  1408.         $("#playpause1").show();
  1409.         $("#playpause2").hide();
  1410.         obj.player.pause();
  1411.         if (obj.fps) {
  1412.           if (obj.fps.isPlaying) {
  1413.             obj.fps.pause();
  1414.           }
  1415.         }
  1416.       } else {
  1417.         $("#playpause1").hide();
  1418.         $("#playpause2").show();
  1419.         obj.player.play();
  1420.         if (obj.fps) {
  1421.           if (!obj.fps.isPlaying) {
  1422.             obj.fps.start();
  1423.           }
  1424.         }
  1425.       }
  1426.     } else {
  1427.       loaderStage1(getFormValue(3, "loop"));
  1428.     }
  1429.   }
  1430.  
  1431.   function playbackStopped() {
  1432.     if (obj.fps) {
  1433.       if (obj.player) {
  1434.         if ((obj.fps.isPlaying || obj.player.playing()) && !config.howl.seeking) {
  1435.           obj.fps.pause();
  1436.         }
  1437.       } else {
  1438.         obj.fps.pause();
  1439.       }
  1440.     }
  1441.     var thehtml = "<strong>Please select a file...</strong><br>or click play to begin...";
  1442.     if (!config.detection.mobile) {
  1443.       thehtml += "<br>&nbsp;<br>&nbsp;<br>&nbsp;<br>";
  1444.     }
  1445.     $("#playdata").html(thehtml);
  1446.   }
  1447.  
  1448.   function spriteChanged() {
  1449.     if (!config.howl.seeking) {
  1450.       if (config.howl.currentPart <= config.howl.numParts) {
  1451.         config.howl.currentPart = parseInt(obj.player._sounds[0]._sprite.replace("part", ""));
  1452.         if ((config.howl.currentPart + 1) <= config.howl.numParts) {
  1453.           obj.player._nextsprite = "part" + (config.howl.currentPart + 1);
  1454.         } else {
  1455.           if (config.howl.mptest) {
  1456.             obj.player._nextsprite = "part5";
  1457.           } else {
  1458.             obj.player._nextsprite = null;
  1459.           }
  1460.         }
  1461.         debugPartPlayback();
  1462.       }
  1463.     }
  1464.   }
  1465.  
  1466.   function setPos(pos, updateSeekData) {
  1467.     if (typeof updateSeekData == "undefined" || updateSeekData === null) {
  1468.       updateSeekData = true;
  1469.     }
  1470.     if (obj.player) {
  1471.       if (config.howl.currentPart != config.howl.numParts) {
  1472.         config.howl.currentPart = config.howl.numParts;
  1473.         var wasPlaying = obj.player.playing();
  1474.         obj.player.stop();
  1475.         if (config.howl.mptest) {
  1476.           obj.player._nextsprite = "part5";
  1477.         } else {
  1478.           obj.player._nextsprite = null;
  1479.         }
  1480.         if (wasPlaying) {
  1481.           obj.player.play("part" + config.howl.currentPart);
  1482.         } else {
  1483.           obj.player._sounds[0]._sprite = "part" + config.howl.currentPart;
  1484.         }
  1485.         debugPartPlayback();
  1486.       }
  1487.       config.howl.seeking = true;
  1488.       $("#sseek").slider("value", pos);
  1489.       obj.player.seek(pos / 1000, obj.player._sounds[0]._id);
  1490.       config.howl.seeking = false;
  1491.     }
  1492.     if (updateSeekData) {
  1493.       setTimeout(function () {
  1494.         setSeekData(-250);
  1495.       }, 250);
  1496.     }
  1497.   }
  1498.  
  1499.   function setVolume(vol) {
  1500.     media.volume = vol;
  1501.     setFormValue(0, "volume", vol);
  1502.     if (obj.player) {
  1503.       obj.player.volume(vol / 100);
  1504.     }
  1505.   }
  1506.  
  1507.   function setVolumeB(vol) {
  1508.     setVolume(vol);
  1509.     $("#svol").slider("value", vol);
  1510.   }
  1511.  
  1512.   function setVolumeC() {
  1513.     var vol = getFormValue(0, "volume");
  1514.     setVolume(vol);
  1515.     $("#svol").slider("value", vol);
  1516.   }
  1517.  
  1518.   function setRate(rate) {
  1519.     setFormValue(0, "playspeed", rate / 100);
  1520.     media.rate = rate;
  1521.     if (obj.player) {
  1522.       obj.player.rate(rate / 100, obj.player._sounds[0]._id);
  1523.     }
  1524.   }
  1525.  
  1526.   function setRateB(rate) {
  1527.     $("#splayspeed").slider("value", rate * 100);
  1528.     setRate(rate * 100);
  1529.   }
  1530.  
  1531.   function setRateC() {
  1532.     setRateB(getFormValue(0, "playspeed"));
  1533.   }
  1534.  
  1535.   function roundNumber(num, dec, exact) {
  1536.     var numtxt = num + "";
  1537.     numtxt = numtxt.split(".")[0];
  1538.     var expectedlen = ((numtxt.length + 1) + dec);
  1539.     var result = Math.round(num * Math.pow(10, dec)) / Math.pow(10, dec);
  1540.     var restxt = result + "";
  1541.     if (exact === true) {
  1542.       while (restxt.length != expectedlen) {
  1543.         if (restxt.length == numtxt.length) {
  1544.           restxt = restxt + ".";
  1545.         } else {
  1546.           restxt = restxt + "0";
  1547.         }
  1548.       }
  1549.       return restxt;
  1550.     }
  1551.     return result;
  1552.   }
  1553.  
  1554.   function setSeekMs(v) {
  1555.     setPos(v, false);
  1556.   }
  1557.  
  1558.   function setSeekPercentage(v, u) {
  1559.     if (!u) {
  1560.       u = false;
  1561.     }
  1562.     if (v > 100 || v < 0) {
  1563.       v = 0;
  1564.     }
  1565.     var percentageEstMs = Math.round((media.duration * v) / 100);
  1566.     if (percentageEstMs >= media.duration) {
  1567.       percentageEstMs = media.duration - 1;
  1568.     }
  1569.  
  1570.     setPos(percentageEstMs, u);
  1571.   }
  1572.  
  1573.   function doSeek() {
  1574.     switch (getFormValue(2, "seekopt")) {
  1575.     case 0:
  1576.       setSeekMs(getFormValue(0, "seek"));
  1577.       break;
  1578.     case 1:
  1579.       setSeekPercentage(getFormValue(0, "seek"));
  1580.       break;
  1581.     }
  1582.   }
  1583.  
  1584.   function setSeekData(offset) {
  1585.     if (obj.player) {
  1586.       var currentpos = obj.player.seek(obj.player._sounds[0]._id);
  1587.       var currms = Math.round((currentpos * 1000) + 500);
  1588.       if (!offset) {
  1589.         offset = 0;
  1590.       }
  1591.       offset = (offset + config.seekOffset);
  1592.       setFormValue(1, "doloop", false);
  1593.       switch (getFormValue(2, "seekopt")) {
  1594.       case 0:
  1595.         setFormValue(0, "seek", currms + offset);
  1596.         break;
  1597.       case 1:
  1598.         setFormValue(0, "seek", roundNumber(((currms + offset) / media.duration * 100), 2, true));
  1599.         break;
  1600.       }
  1601.     }
  1602.   }
  1603.  
  1604.   function switchSeekDisplay() {
  1605.     if (obj.player) {
  1606.       var currval = parseFloat(getFormValue(0, "seek"));
  1607.       if (Number.isNaN(currval)) {
  1608.         currval = 0;
  1609.       }
  1610.       if (media.duration) {
  1611.         switch (config.seekopt) {
  1612.         case 0:
  1613.           switch (getFormValue(2, "seekopt")) {
  1614.           case 1:
  1615.             setFormValue(0, "seek", roundNumber(currval / media.duration * 100, 2, true));
  1616.             break;
  1617.           }
  1618.           break;
  1619.         case 1:
  1620.           switch (getFormValue(2, "seekopt")) {
  1621.           case 0:
  1622.             setFormValue(0, "seek", Math.round((media.duration * currval) / 100));
  1623.             break;
  1624.           }
  1625.           break;
  1626.         }
  1627.         config.seekopt = getFormValue(2, "seekopt")
  1628.       } else {
  1629.         setFormValue(2, "seekopt", config.seekopt);
  1630.       }
  1631.     }
  1632.   }
  1633.  
  1634.   function catchEnter(e, functionCallback) {
  1635.     if (window.event && window.event.keyCode === 13) {
  1636.       functionCallback();
  1637.       return false;
  1638.     }
  1639.     if (e && e.keyCode === 13) {
  1640.       functionCallback();
  1641.       return false;
  1642.     }
  1643.     return true;
  1644.   }
  1645.  
  1646.   function getplaydata() {
  1647.     if (obj.player) {
  1648.       if (obj.player.playing()) {
  1649.         if (config.toggles.visualization.enabled) {
  1650.           renderVUFrame();
  1651.         }
  1652.         var currentpos = obj.player.seek(obj.player._sounds[0]._id);
  1653.         if (!config.toggles.sliderSeeking) {
  1654.           $("#sseek").slider("value", (currentpos * 1000));
  1655.         }
  1656.         var currms = Math.round(currentpos * 1000);
  1657.         var percent = currms / media.duration * 100;
  1658.         var seconds = Math.floor(currentpos);
  1659.         var currmin = Math.floor(seconds / 60);
  1660.         var currsec = seconds % 60;
  1661. 		var currmsec = currms - (seconds * 1000);
  1662. 		var totalms = Math.round(media.duration);
  1663.         var totalsecs = Math.floor(media.duration / 1000);
  1664.         var totalmin = Math.floor(totalsecs / 60);
  1665.         var totalsec = totalsecs % 60;
  1666. 		var totalmsec = Math.round(totalms - (totalsecs * 1000), 3)
  1667.         if (currms > 0 && config.bootSeek) {
  1668.           config.bootSeek = false;
  1669.           doSeek();
  1670.         }
  1671.         if (seconds % 60 < 10) {
  1672.           currsec = "0" + currsec;
  1673.         }
  1674.         if (totalsecs % 60 < 10) {
  1675.           totalsec = "0" + totalsec;
  1676.         }
  1677. 		if (currmsec < 10) {
  1678. 			currmsec = "00" + currmsec
  1679. 		} else if (currmsec < 100) {
  1680. 			currmsec = "0" + currmsec
  1681. 		}
  1682. 		if (totalmsec < 10) {
  1683. 			totalmsec = "00" + totalmsec
  1684. 		} else if (totalmsec < 100) {
  1685. 			totalmsec = "0" + totalmsec
  1686. 		}
  1687.  
  1688.  
  1689.  
  1690.         var oldts = obj.timing.timestamp;
  1691.         obj.timing.timestamp = Math.round(new Date().getTime() / 1000);
  1692.         if (oldts === obj.timing.timestamp) {
  1693.           obj.timing.fps.current++;
  1694.         } else {
  1695.           obj.timing.fps.last = obj.timing.fps.current;
  1696.           obj.timing.fps.current = 0;
  1697.         }
  1698.         if (getFormValue(1, "doloop") === true) {
  1699.           if (!Number.isNaN(getFormValue(0, "looper"))) {
  1700.             var looper = Number(getFormValue(0, "looper"));
  1701.             var seek = Number(getFormValue(0, "seek"));
  1702.             config.seekopt = getFormValue(2, "seekopt")
  1703.               if (looper >= seek && config.toggles.looper.seeking === false) {
  1704.                 config.toggles.looper.seeking = true;
  1705.                 switch (config.seekopt) {
  1706.                 case 0:
  1707.                   if (currms >= looper) {
  1708.                     setSeekMs(seek);
  1709.                   }
  1710.                   break;
  1711.                 case 1:
  1712.                   if (percent >= looper) {
  1713.                     setSeekPercentage(seek);
  1714.                   }
  1715.                   break;
  1716.                 }
  1717.               } else {
  1718.                 config.toggles.looper.seeking = false;
  1719.               }
  1720.           }
  1721.         }
  1722.         var thehtml = "<strong>Playback Information</strong><br>";
  1723.         if (!config.detection.mobile) {
  1724.           // Detect playback mode and codec
  1725.           thehtml += "Mode: ";
  1726.           if (obj.player._webAudio) {
  1727.             thehtml += "WebAudio";
  1728.           } else {
  1729.             if (obj.player._html5) {
  1730.               thehtml += "HTML5";
  1731.             } else {
  1732.               thehtml += "Unknown";
  1733.             }
  1734.           }
  1735.           thehtml += " - Codec: " + getPlayingCodec() + "<br>";
  1736.         }
  1737.         thehtml += "Current Position: " + currmin + ":" + currsec + "." + currmsec+ " of " + totalmin + ":" + totalsec + "." + totalmsec + " (" + roundNumber(percent, 2, true) + "%)";
  1738.         if (!config.detection.mobile) {
  1739.           thehtml += "<br>";
  1740.           thehtml += "Current FPS: " + (obj.timing.fps.last + 1) + " (" + Math.round(1000 / (obj.timing.fps.last + 1)) + "ms per frame)<br>";
  1741.         }
  1742.         $("#playdata").html(thehtml);
  1743.         if (!config.toggles.waveform.seeking && obj.wavesurfer && config.toggles.waveform.enabled) {
  1744.           var offsetpos = ((currms / 1000) / obj.wavesurfer.backend.buffer.duration);
  1745.           var offset = percent - offsetpos;
  1746.           var newpos = percent - offset;
  1747. 		  if (newpos < 0) newpos = 0;
  1748. 		  if (newpos > 1) newpos = 1;
  1749.           obj.wavesurfer.seekTo(newpos);
  1750.           obj.wavesurfer.drawer.recenter(newpos);
  1751.         }
  1752.       }
  1753.     }
  1754.   }
  1755.  
  1756.   function getPlayingCodec() {
  1757.     switch (media.codec) {
  1758.     case "audio/webm":
  1759.       return "Xiph Vorbis (webm)";
  1760.     case "audio/ogg":
  1761.     case "audio/x-ogg":
  1762.       return "Xiph Vorbis (ogg)";
  1763.     case "audio/ogg; codecs=opus":
  1764.       return "Xiph Opus";
  1765.     case "audio/mp4":
  1766.     case "audio/m4a":
  1767.       return "Apple AAC";
  1768.     case "audio/mp3":
  1769.     case "audio/mpeg":
  1770.       return "MPEG (Layer 1/2/3)";
  1771.     case "audio/wav":
  1772.     case "audio/x-wav":
  1773.       return "PCM S16LE";
  1774.     case "audio/flac":
  1775.     case "audio/x-flac":
  1776.       return "FLAC";
  1777.     }
  1778.     return false;
  1779.   }
  1780.  
  1781.   function setReloadText() {
  1782.     $("#playdata").html("<strong>Reloading with configured URL...</strong><br><br><br><br><br>");
  1783.   }
  1784.  
  1785.   function clearURLParams() {
  1786.     setReloadText();
  1787.     $("#content").slideUp(config.effects.slideSpeed.content, 'swing', function () {
  1788.       var targeturl = location.href;
  1789.       if (location.href.indexOf("?")) {
  1790.         targeturl = location.href.split("?")[0];
  1791.       }
  1792.       targeturl += "?loop=" + getFormValue(3, "loop");
  1793.       if (z_urlp("external") && getFormValue(3, "loop") == "external") {
  1794.         targeturl += "&external=" + z_urlp("external");
  1795.         if (z_urlp("externalflags")) {
  1796.           targeturl += "&externalflags=" + z_urlp("externalflags");
  1797.         }
  1798.       }
  1799.       if (z_urlp("debug")) {
  1800.         targeturl += "&debug=" + z_urlp("debug");
  1801.       }
  1802.       $("#content_wrapper").slideUp(config.effects.slideSpeed.wrapper, 'swing', function () {
  1803.         window.location.href = targeturl;
  1804.       });
  1805.     });
  1806.   }
  1807.  
  1808.   function getSubmitURL(debug) {
  1809.     setReloadText();
  1810.     $("#content").slideUp(config.effects.slideSpeed.content, 'swing', function () {
  1811.       var x = document.getElementById("loopselect");
  1812.       var filename = x.options[x.options.selectedIndex].value;
  1813.       var split = filename.indexOf(",");
  1814.       var splitary;
  1815.       if (split > 0) {
  1816.         splitary = filename.split(",");
  1817.         x.options[x.options.selectedIndex].value = splitary[0];
  1818.       }
  1819.  
  1820.       // purge existing form fields that are set to their default values
  1821.       if (getFormValue(2, "seekopt") == config.defaults.seekopt) {
  1822.         $("#seekopt").remove();
  1823.       }
  1824.       if (getFormValue(3, "fps").replace("fps", "") == config.defaults.fps) {
  1825.         $("#fpssel").remove();
  1826.       }
  1827.       if (getFormValue(0, "playspeed") == (config.defaults.rate / 100)) {
  1828.         $("#playspeed").remove();
  1829.       }
  1830.       if (getFormValue(0, "volume") == config.defaults.volume) {
  1831.         $("#vol").remove();
  1832.       }
  1833.       if (getFormValue(0, "seek") == "0" || getFormValue(0, "seek") == "") {
  1834.         $("#seek").remove();
  1835.       }
  1836.       if (getFormValue(0, "looper") == "0" || getFormValue(0, "looper") == "") {
  1837.         $("#looper").remove();
  1838.       }
  1839.       if (!getFormValue(1, "doloop")) {
  1840.         $("#doloop").remove();
  1841.       }
  1842.       if (!getFormValue(1, "lowend")) {
  1843.         $("#lowend").remove();
  1844.       }
  1845.       if (!getFormValue(1, "novu")) {
  1846.         $("#novu").remove();
  1847.       }
  1848.       if (!getFormValue(1, "nowf")) {
  1849.         $("#nowf").remove();
  1850.       }
  1851.  
  1852.       // check for variables that are not part of the main form and inject them if they exist
  1853.       var checkMissingParams = ["config", "debug", "mobile", "doseek", "lossless", "autoplay", "forceformat", "hue", "external", "externalflags"];
  1854.       for (var i = 0; i < checkMissingParams.length; i++) {
  1855.         var pval = z_urlp(checkMissingParams[i]);
  1856.         if (pval) {
  1857.           $("#mainform").append("<input id=\"form_" + checkMissingParams[i] + "\" type=\"hidden\" name=\"" + checkMissingParams[i] + "\" value=\"" + pval + "\">");
  1858.         }
  1859.       }
  1860.  
  1861.       // custom checks
  1862.       if (obj.ctx.hue.bar.start != config.defaults.ctxHue.bar.start) {
  1863.         if (!$("#form_starthue").val()) {
  1864.           $("#mainform").append("<input id=\"form_starthue\" type=\"hidden\" name=\"starthue\" value=\"" + obj.ctx.hue.bar.start + "\">");
  1865.         } else {
  1866.           setFormValue(0, "starthue", obj.ctx.hue.bar.start);
  1867.         }
  1868.       }
  1869.       if (obj.ctx.hue.bar.end != config.defaults.ctxHue.bar.end) {
  1870.         if (!$("#form_endhue").val()) {
  1871.           $("#mainform").append("<input id=\"form_endhue\" type=\"hidden\" name=\"endhue\" value=\"" + obj.ctx.hue.bar.end + "\">");
  1872.         } else {
  1873.           setFormValue(0, "endhue", obj.ctx.hue.bar.end);
  1874.         }
  1875.       }
  1876.       if (obj.ctx.hue.mode != config.defaults.ctxHue.mode) {
  1877.         if (!$("#form_huemode").val()) {
  1878.           $("#mainform").append("<input id=\"form_huemode\" type=\"hidden\" name=\"huemode\" value=\"" + obj.ctx.hue.mode + "\">");
  1879.         } else {
  1880.           setFormValue(0, "huemode", obj.ctx.hue.mode);
  1881.         }
  1882.       }
  1883.  
  1884.       if (debug) {
  1885.         $("#form_debug").remove();
  1886.         if (debug != "production") {
  1887.           $("#mainform").append("<input id=\"form_debug\" type=\"hidden\" name=\"debug\" value=\"" + debug + "\">");
  1888.         }
  1889.       }
  1890.       $("#content_wrapper").slideUp(config.effects.slideSpeed.wrapper, 'swing', function () {
  1891.         $("#mainform").submit();
  1892.       });
  1893.     });
  1894.     return false;
  1895.   }
  1896.  
  1897.   function updateProgress(evt) {
  1898.     if (evt.lengthComputable) {
  1899.  
  1900.       var thehtml = "<strong>Downloading media...</strong><br>";
  1901.       var percentComplete = (evt.loaded / evt.total) * 100;
  1902.       thehtml += friendlyBytes(evt.loaded) + " of " + friendlyBytes(evt.total) + " bytes (" + roundNumber(percentComplete, 1, true) + "%)";
  1903.       if (!config.detection.mobile) {
  1904.         thehtml += "<br>&nbsp;<br>&nbsp;<br>&nbsp;<br>";
  1905.       } else {
  1906.         if (percentComplete == 100) {
  1907.           thehtml += "<br>Complete. Tap anywhere to begin playback.";
  1908.         }
  1909.       }
  1910.       $("#playdata").html(thehtml);
  1911.     }
  1912.   }
  1913.  
  1914.   function getDataFile(url) {
  1915.     var req = new XMLHttpRequest();
  1916.     req.onprogress = updateProgress;
  1917.     req.open("GET", url, true);
  1918.     req.responseType = "arraybuffer";
  1919.     if (config.detection.microsoft == 11) {
  1920.       req.onload = function (e) {
  1921.         httpComplete(e.currentTarget.response);
  1922.       }
  1923.     } else {
  1924.       req.onreadystatechange = function (e) {
  1925.         if (req.readyState == 4) {
  1926.           httpComplete(e.currentTarget.response);
  1927.         }
  1928.       }
  1929.     }
  1930.  
  1931.     req.send();
  1932.   }
  1933.  
  1934.   function httpComplete(data) {
  1935.     media.dataBuffer = data;
  1936.     loaderStage2();
  1937.   }
  1938.  
  1939.   function base64ArrayBuffer(arrayBuffer) {
  1940.     var base64 = "",
  1941.     encodings = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
  1942.     bytes = new Uint8Array(arrayBuffer),
  1943.     byteLength = bytes.byteLength,
  1944.     byteRemainder = byteLength % 3,
  1945.     mainLength = byteLength - byteRemainder,
  1946.     a,
  1947.     b,
  1948.     c,
  1949.     d,
  1950.     chunk;
  1951.     // Main loop deals with bytes in chunks of 3
  1952.     for (var i = 0; i < mainLength; i = i + 3) {
  1953.       // Combine the three bytes into a single integer
  1954.       chunk = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2];
  1955.       // Use bitmasks to extract 6-bit segments from the triplet
  1956.       a = (chunk & 16515072) >> 18; // 16515072 = (2^6 - 1) << 18
  1957.       b = (chunk & 258048) >> 12; // 258048   = (2^6 - 1) << 12
  1958.       c = (chunk & 4032) >> 6; // 4032     = (2^6 - 1) << 6
  1959.       d = chunk & 63; // 63       = 2^6 - 1
  1960.       // Convert the raw binary segments to the appropriate ASCII encoding
  1961.       base64 += encodings[a] + encodings[b] + encodings[c] + encodings[d];
  1962.     }
  1963.     // Deal with the remaining bytes and padding
  1964.     if (byteRemainder == 1) {
  1965.       chunk = bytes[mainLength];
  1966.       a = (chunk & 252) >> 2; // 252 = (2^6 - 1) << 2
  1967.       // Set the 4 least significant bits to zero
  1968.       b = (chunk & 3) << 4; // 3   = 2^2 - 1
  1969.       base64 += encodings[a] + encodings[b] + "==";
  1970.     } else if (byteRemainder == 2) {
  1971.       chunk = (bytes[mainLength] << 8) | bytes[mainLength + 1];
  1972.       a = (chunk & 64512) >> 10; // 64512 = (2^6 - 1) << 10
  1973.       b = (chunk & 1008) >> 4; // 1008  = (2^6 - 1) << 4
  1974.       // Set the 2 least significant bits to zero
  1975.       c = (chunk & 15) << 2; // 15    = 2^4 - 1
  1976.       base64 += encodings[a] + encodings[b] + encodings[c] + "=";
  1977.     }
  1978.     return base64;
  1979.   }
  1980.  
  1981.   function base64ToArrayBuffer(base64) {
  1982.     var binary_string = window.atob(base64);
  1983.     var len = binary_string.length;
  1984.     var bytes = new Uint8Array(len);
  1985.     for (var i = 0; i < len; i++) {
  1986.       bytes[i] = binary_string.charCodeAt(i);
  1987.     }
  1988.     return bytes.buffer;
  1989.   }
  1990.  
  1991.   function testCodecs() {
  1992.     var result = Howler._setupCodecs();
  1993.     var codecs = result._codecs;
  1994.     result.unload();
  1995.     return codecs;
  1996.   }
  1997.  
  1998.   function friendlyBytes(size) {
  1999.     if (size > 1073741824) {
  2000.       return roundNumber((size / 1073741824), 2, true) + "GB";
  2001.     }
  2002.     if (size > 1048576) {
  2003.       return roundNumber((size / 1048576), 2, true) + "MB";
  2004.     }
  2005.     if (size > 1024) {
  2006.       return roundNumber((size / 1024), 2, true) + "KB";
  2007.     }
  2008.     return size + "B";
  2009.   }
  2010.  
  2011.   function setFPS(fps, restart) {
  2012.     if (obj.fps) {
  2013.       if (restart) {
  2014.         obj.fps.pause();
  2015.       }
  2016.       obj.fps.frameRate(fps);
  2017.       if (restart) {
  2018.         obj.fps.start();
  2019.       }
  2020.     }
  2021.   }
  2022.  
  2023.   function FpsCtrl(fps, callback) {
  2024.     var delay = 1000 / fps,
  2025.     time = null,
  2026.     frame = -1,
  2027.     tref;
  2028.  
  2029.     function loop(timestamp) {
  2030.       if (time === null)
  2031.         time = timestamp;
  2032.       var seg = Math.floor((timestamp - time) / delay);
  2033.       if (seg > frame) {
  2034.         frame = seg;
  2035.         callback({
  2036.           time: timestamp,
  2037.           frame: frame
  2038.         })
  2039.       }
  2040.       tref = requestAnimationFrame(loop)
  2041.     }
  2042.  
  2043.     this.isPlaying = false;
  2044.  
  2045.     this.frameRate = function (newfps) {
  2046.       if (!arguments.length)
  2047.         return fps;
  2048.       fps = newfps;
  2049.       delay = 1000 / fps;
  2050.       frame = -1;
  2051.       time = null;
  2052.     };
  2053.  
  2054.     this.start = function () {
  2055.       if (!this.isPlaying) {
  2056.         this.isPlaying = true;
  2057.         tref = requestAnimationFrame(loop);
  2058.       }
  2059.     };
  2060.  
  2061.     this.pause = function () {
  2062.       if (this.isPlaying) {
  2063.         cancelAnimationFrame(tref);
  2064.         this.isPlaying = false;
  2065.         time = null;
  2066.         frame = -1;
  2067.       }
  2068.     };
  2069.   }
  2070.   function testWebAudio() {
  2071.     var textContext;
  2072.     try {
  2073.       if (typeof AudioContext !== 'undefined') {
  2074.         textContext = new AudioContext();
  2075.       } else if (typeof webkitAudioContext !== 'undefined') {
  2076.         textContext = new webkitAudioContext();
  2077.       } else {
  2078.         return false;
  2079.       }
  2080.       textContext = null;
  2081.       return true;
  2082.     } catch (e) {
  2083.       return false;
  2084.     }
  2085.   }
  2086. })();
  2087.