annotate mushra.js @ 759:801e1977ab55

MUSHRA: Loading progress feedback
author Nicholas Jillings <nicholas.jillings@eecs.qmul.ac.uk>
date Thu, 17 Dec 2015 16:29:49 +0000
parents 57a3cbf90e01
children 01c026742e1e
rev   line source
nicholas@755 1 /**
nicholas@755 2 * mushra.js
nicholas@755 3 * Create the MUSHRA interface
nicholas@755 4 */
nicholas@755 5
nicholas@755 6 // Once this is loaded and parsed, begin execution
nicholas@755 7 loadInterface();
nicholas@755 8
nicholas@755 9 function loadInterface() {
nicholas@755 10 // Get the dimensions of the screen available to the page
nicholas@755 11 var width = window.innerWidth;
nicholas@755 12 var height = window.innerHeight;
nicholas@755 13
nicholas@755 14 // The injection point into the HTML page
nicholas@755 15 interfaceContext.insertPoint = document.getElementById("topLevelBody");
nicholas@755 16 var testContent = document.createElement('div');
nicholas@755 17 testContent.id = 'testContent';
nicholas@755 18
nicholas@755 19 // Create the top div for the Title element
nicholas@755 20 var titleAttr = specification.title;
nicholas@755 21 var title = document.createElement('div');
nicholas@755 22 title.className = "title";
nicholas@755 23 title.align = "center";
nicholas@755 24 var titleSpan = document.createElement('span');
nicholas@755 25
nicholas@755 26 // Set title to that defined in XML, else set to default
nicholas@755 27 if (titleAttr != undefined) {
nicholas@755 28 titleSpan.textContent = titleAttr;
nicholas@755 29 } else {
nicholas@755 30 titleSpan.textContent = 'Listening test';
nicholas@755 31 }
nicholas@755 32 // Insert the titleSpan element into the title div element.
nicholas@755 33 title.appendChild(titleSpan);
nicholas@755 34
nicholas@755 35 var pagetitle = document.createElement('div');
nicholas@755 36 pagetitle.className = "pageTitle";
nicholas@755 37 pagetitle.align = "center";
nicholas@755 38 var titleSpan = document.createElement('span');
nicholas@755 39 titleSpan.id = "pageTitle";
nicholas@755 40 pagetitle.appendChild(titleSpan);
nicholas@755 41
nicholas@755 42 // Create Interface buttons!
nicholas@755 43 var interfaceButtons = document.createElement('div');
nicholas@755 44 interfaceButtons.id = 'interface-buttons';
nicholas@755 45
nicholas@755 46 // Create playback start/stop points
nicholas@755 47 var playback = document.createElement("button");
nicholas@755 48 playback.innerHTML = 'Stop';
nicholas@755 49 playback.id = 'playback-button';
nicholas@755 50 // onclick function. Check if it is playing or not, call the correct function in the
nicholas@755 51 // audioEngine, change the button text to reflect the next state.
nicholas@755 52 playback.onclick = function() {
nicholas@755 53 if (audioEngineContext.status == 1) {
nicholas@755 54 audioEngineContext.stop();
nicholas@755 55 this.innerHTML = 'Stop';
nicholas@755 56 var time = audioEngineContext.timer.getTestTime();
nicholas@755 57 console.log('Stopped at ' + time); // DEBUG/SAFETY
nicholas@755 58 }
nicholas@755 59 };
nicholas@755 60 // Create Submit (save) button
nicholas@755 61 var submit = document.createElement("button");
nicholas@755 62 submit.innerHTML = 'Submit';
nicholas@755 63 submit.onclick = buttonSubmitClick;
nicholas@755 64 submit.id = 'submit-button';
nicholas@755 65 // Append the interface buttons into the interfaceButtons object.
nicholas@755 66 interfaceButtons.appendChild(playback);
nicholas@755 67 interfaceButtons.appendChild(submit);
nicholas@755 68
nicholas@755 69 // Create a slider box
nicholas@755 70 var sliderBox = document.createElement('div');
nicholas@755 71 sliderBox.style.width = "100%";
nicholas@755 72 sliderBox.style.height = window.innerHeight - 180 + 'px';
nicholas@755 73 sliderBox.id = 'slider';
nicholas@755 74 sliderBox.align = "center";
nicholas@755 75
nicholas@755 76 // Global parent for the comment boxes on the page
nicholas@755 77 var feedbackHolder = document.createElement('div');
nicholas@755 78 feedbackHolder.id = 'feedbackHolder';
nicholas@755 79
nicholas@755 80 testContent.style.zIndex = 1;
nicholas@755 81 interfaceContext.insertPoint.innerHTML = null; // Clear the current schema
nicholas@755 82
nicholas@755 83 // Inject into HTML
nicholas@755 84 testContent.appendChild(title); // Insert the title
nicholas@755 85 testContent.appendChild(pagetitle);
nicholas@755 86 testContent.appendChild(interfaceButtons);
nicholas@755 87 testContent.appendChild(sliderBox);
nicholas@755 88 testContent.appendChild(feedbackHolder);
nicholas@755 89 interfaceContext.insertPoint.appendChild(testContent);
nicholas@755 90
nicholas@755 91 // Load the full interface
nicholas@755 92 testState.initialise();
nicholas@755 93 testState.advanceState();
nicholas@755 94 }
nicholas@755 95
nicholas@755 96 function loadTest(audioHolderObject)
nicholas@755 97 {
nicholas@755 98 var id = audioHolderObject.id;
nicholas@755 99
nicholas@755 100 var feedbackHolder = document.getElementById('feedbackHolder');
nicholas@755 101 var interfaceObj = audioHolderObject.interfaces;
nicholas@756 102 if (interfaceObj.length > 1)
nicholas@756 103 {
nicholas@756 104 console.log("WARNING - This interface only supports one <interface> node per page. Using first interface node");
nicholas@756 105 }
nicholas@755 106
nicholas@755 107 var sliderBox = document.getElementById('slider');
nicholas@755 108 feedbackHolder.innerHTML = null;
nicholas@755 109 sliderBox.innerHTML = null;
nicholas@755 110
nicholas@755 111 var commentBoxPrefix = "Comment on track";
nicholas@755 112 if (interfaceObj.commentBoxPrefix != undefined) {
nicholas@755 113 commentBoxPrefix = interfaceObj.commentBoxPrefix;
nicholas@755 114 }
nicholas@755 115 var loopPlayback = audioHolderObject.loop;
nicholas@755 116
nicholas@755 117 currentTestHolder = document.createElement('audioHolder');
nicholas@755 118 currentTestHolder.id = audioHolderObject.id;
nicholas@755 119 currentTestHolder.repeatCount = audioHolderObject.repeatCount;
nicholas@755 120
nicholas@755 121 $(audioHolderObject.commentQuestions).each(function(index,element) {
nicholas@755 122 var node = interfaceContext.createCommentQuestion(element);
nicholas@755 123 feedbackHolder.appendChild(node.holder);
nicholas@755 124 });
nicholas@755 125
nicholas@755 126 // Find all the audioElements from the audioHolder
nicholas@755 127 $(audioHolderObject.audioElements).each(function(index,element){
nicholas@755 128 // Find URL of track
nicholas@755 129 // In this jQuery loop, variable 'this' holds the current audioElement.
nicholas@755 130
nicholas@755 131 // Now load each audio sample. First create the new track by passing the full URL
nicholas@755 132 var trackURL = audioHolderObject.hostURL + element.url;
nicholas@755 133 var audioObject = audioEngineContext.newTrack(element);
nicholas@755 134
nicholas@755 135 var node = interfaceContext.createCommentBox(audioObject);
nicholas@755 136
nicholas@755 137 // Create a slider per track
nicholas@755 138 audioObject.interfaceDOM = new sliderObject(audioObject);
nicholas@755 139
nicholas@758 140 if (typeof audioHolderObject.initialPosition === "number")
nicholas@758 141 {
nicholas@758 142 // Set the values
nicholas@758 143 audioObject.interfaceDOM.slider.value = audioHolderObject.initalPosition;
nicholas@758 144 } else {
nicholas@758 145 // Distribute it randomnly
nicholas@758 146 audioObject.interfaceDOM.slider.value = Math.random();
nicholas@758 147 }
nicholas@755 148
nicholas@755 149 sliderBox.appendChild(audioObject.interfaceDOM.holder);
nicholas@755 150 audioObject.metric.initialised(audioObject.interfaceDOM.slider.value);
nicholas@755 151
nicholas@755 152 });
nicholas@755 153
nicholas@755 154 // Auto-align
nicholas@755 155 var numObj = audioHolderObject.audioElements.length;
nicholas@755 156 var totalWidth = (numObj-1)*150+100;
nicholas@755 157 var diff = (window.innerWidth - totalWidth)/2;
nicholas@755 158 audioEngineContext.audioObjects[0].interfaceDOM.holder.style.marginLeft = diff + 'px';
nicholas@755 159 }
nicholas@755 160
nicholas@755 161 function sliderObject(audioObject)
nicholas@755 162 {
nicholas@755 163 // Constructs the slider object. We use the HTML5 slider object
nicholas@755 164 this.parent = audioObject;
nicholas@755 165 this.holder = document.createElement('div');
nicholas@755 166 this.title = document.createElement('span');
nicholas@755 167 this.slider = document.createElement('input');
nicholas@755 168 this.play = document.createElement('button');
nicholas@755 169
nicholas@755 170 this.holder.className = 'track-slider';
nicholas@755 171 this.holder.style.height = window.innerHeight-200 + 'px';
nicholas@755 172 this.holder.appendChild(this.title);
nicholas@755 173 this.holder.appendChild(this.slider);
nicholas@755 174 this.holder.appendChild(this.play);
nicholas@755 175 this.holder.align = "center";
nicholas@755 176 this.holder.style.marginLeft = "50px";
nicholas@755 177 this.holder.setAttribute('trackIndex',audioObject.id);
nicholas@755 178
nicholas@755 179 this.title.textContent = audioObject.id;
nicholas@755 180 this.title.style.width = "100%";
nicholas@755 181 this.title.style.float = "left";
nicholas@755 182
nicholas@755 183 this.slider.type = "range";
nicholas@756 184 this.slider.className = "track-slider-range";
nicholas@755 185 this.slider.min = "0";
nicholas@755 186 this.slider.max = "1";
nicholas@755 187 this.slider.step = "0.01";
nicholas@755 188 this.slider.setAttribute('orient','vertical');
nicholas@755 189 this.slider.style.height = window.innerHeight-250 + 'px';
nicholas@755 190 this.slider.onchange = function()
nicholas@755 191 {
nicholas@755 192 var time = audioEngineContext.timer.getTestTime();
nicholas@755 193 var id = Number(this.parentNode.getAttribute('trackIndex'));
nicholas@755 194 audioEngineContext.audioObjects[id].metric.moved(time,this.value);
nicholas@755 195 console.log('slider '+id+' moved to '+this.value+' ('+time+')');
nicholas@755 196 };
nicholas@755 197
nicholas@759 198 this.play.textContent = "Loading...";
nicholas@755 199 this.play.value = audioObject.id;
nicholas@755 200 this.play.style.float = "left";
nicholas@755 201 this.play.style.width = "100%";
nicholas@756 202 this.play.disabled = true;
nicholas@756 203 this.play.onclick = function(event)
nicholas@755 204 {
nicholas@756 205 var id = Number(event.srcElement.value);
nicholas@756 206 //audioEngineContext.metric.sliderPlayed(id);
nicholas@756 207 audioEngineContext.play(id);
nicholas@756 208 $(".track-slider").removeClass('track-slider-playing');
nicholas@756 209 $(event.currentTarget.parentElement).addClass('track-slider-playing');
nicholas@755 210 };
nicholas@755 211
nicholas@755 212 this.enable = function() {
nicholas@756 213 this.play.disabled = false;
nicholas@759 214 this.play.textContent = "Play";
nicholas@756 215 $(this.slider).removeClass('track-slider-disabled');
nicholas@755 216 };
nicholas@755 217
nicholas@755 218 this.exportXMLDOM = function(audioObject) {
nicholas@755 219 // Called by the audioObject holding this element. Must be present
nicholas@755 220 var node = document.createElement('value');
nicholas@755 221 node.textContent = this.slider.value;
nicholas@755 222 return node;
nicholas@755 223 };
nicholas@755 224 this.getValue = function() {
nicholas@755 225 return this.slider.value;
nicholas@755 226 };
nicholas@756 227
nicholas@757 228 this.resize = function(event)
nicholas@757 229 {
nicholas@757 230 this.holder.style.height = window.innerHeight-200 + 'px';
nicholas@757 231 this.slider.style.height = window.innerHeight-250 + 'px';
nicholas@757 232 }
nicholas@759 233 this.updateLoading = function(progress)
nicholas@759 234 {
nicholas@759 235 progress = String(progress);
nicholas@759 236 progress = progress.substr(0,5);
nicholas@759 237 this.play.textContent = "Loading: "+progress+"%";
nicholas@759 238 }
nicholas@757 239
nicholas@756 240 if (this.parent.state == 1)
nicholas@756 241 {
nicholas@756 242 this.enable();
nicholas@756 243 }
nicholas@755 244 }
nicholas@755 245
nicholas@757 246 function resizeWindow(event)
nicholas@757 247 {
nicholas@757 248 // Function called when the window has been resized.
nicholas@757 249 // MANDATORY FUNCTION
nicholas@757 250
nicholas@757 251 // Auto-align
nicholas@757 252 var numObj = audioEngineContext.audioObjects.length;
nicholas@757 253 var totalWidth = (numObj-1)*150+100;
nicholas@757 254 var diff = (window.innerWidth - totalWidth)/2;
nicholas@757 255 document.getElementById('slider').style.height = window.innerHeight - 180 + 'px';
nicholas@757 256 audioEngineContext.audioObjects[0].interfaceDOM.holder.style.marginLeft = diff + 'px';
nicholas@757 257 for (var i in audioEngineContext.audioObjects)
nicholas@757 258 {
nicholas@757 259 audioEngineContext.audioObjects[i].interfaceDOM.resize(event);
nicholas@757 260 }
nicholas@757 261 }
nicholas@757 262
nicholas@755 263
nicholas@755 264 function buttonSubmitClick() // TODO: Only when all songs have been played!
nicholas@755 265 {
nicholas@755 266 var checks = testState.currentStateMap[testState.currentIndex].interfaces[0].options;
nicholas@755 267 var canContinue = true;
nicholas@755 268
nicholas@755 269 // Check that the anchor and reference objects are correctly placed
nicholas@755 270 if (interfaceContext.checkHiddenAnchor() == false) {return;}
nicholas@755 271 if (interfaceContext.checkHiddenReference() == false) {return;}
nicholas@755 272 /*
nicholas@755 273 for (var i=0; i<checks.length; i++) {
nicholas@755 274 if (checks[i].type == 'check')
nicholas@755 275 {
nicholas@755 276 switch(checks[i].check) {
nicholas@755 277 case 'fragmentPlayed':
nicholas@755 278 // Check if all fragments have been played
nicholas@755 279 var checkState = interfaceContext.checkAllPlayed();
nicholas@755 280 if (checkState == false) {canContinue = false;}
nicholas@755 281 break;
nicholas@755 282 case 'fragmentFullPlayback':
nicholas@755 283 // Check all fragments have been played to their full length
nicholas@755 284 var checkState = interfaceContext.checkAllPlayed();
nicholas@755 285 if (checkState == false) {canContinue = false;}
nicholas@755 286 console.log('NOTE: fragmentFullPlayback not currently implemented, performing check fragmentPlayed instead');
nicholas@755 287 break;
nicholas@755 288 case 'fragmentMoved':
nicholas@755 289 // Check all fragment sliders have been moved.
nicholas@755 290 var checkState = interfaceContext.checkAllMoved();
nicholas@755 291 if (checkState == false) {canContinue = false;}
nicholas@755 292 break;
nicholas@755 293 case 'fragmentComments':
nicholas@755 294 // Check all fragment sliders have been moved.
nicholas@755 295 var checkState = interfaceContext.checkAllCommented();
nicholas@755 296 if (checkState == false) {canContinue = false;}
nicholas@755 297 break;
nicholas@755 298 case 'scalerange':
nicholas@755 299 // Check the scale is used to its full width outlined by the node
nicholas@755 300 var checkState = interfaceContext.checkScaleRange();
nicholas@755 301 if (checkState == false) {canContinue = false;}
nicholas@755 302 break;
nicholas@755 303 }
nicholas@755 304
nicholas@755 305 }
nicholas@755 306 if (!canContinue) {break;}
nicholas@755 307 }
nicholas@755 308 */
nicholas@755 309 if (canContinue) {
nicholas@755 310 if (audioEngineContext.status == 1) {
nicholas@755 311 var playback = document.getElementById('playback-button');
nicholas@755 312 playback.click();
nicholas@755 313 // This function is called when the submit button is clicked. Will check for any further tests to perform, or any post-test options
nicholas@755 314 } else
nicholas@755 315 {
nicholas@755 316 if (audioEngineContext.timer.testStarted == false)
nicholas@755 317 {
nicholas@755 318 alert('You have not started the test! Please press start to begin the test!');
nicholas@755 319 return;
nicholas@755 320 }
nicholas@755 321 }
nicholas@755 322 testState.advanceState();
nicholas@755 323 }
nicholas@755 324 }
nicholas@755 325
nicholas@755 326 function pageXMLSave(store, testXML)
nicholas@755 327 {
nicholas@755 328 // MANDATORY
nicholas@755 329 // Saves a specific test page
nicholas@755 330 // You can use this space to add any extra nodes to your XML saves
nicholas@755 331 }