OLD | NEW |
(Empty) | |
| 1 (function() { |
| 2 function getPerfTimestamp() { |
| 3 return performance.now(); |
| 4 } |
| 5 |
| 6 var pageStartTime = getPerfTimestamp(); |
| 7 var bodyLoadTime; |
| 8 var pageEndTime; |
| 9 |
| 10 function parseQueryParameters() { |
| 11 var params = {}; |
| 12 var r = /([^&=]+)=?([^&]*)/g; |
| 13 |
| 14 function d(s) { return decodeURIComponent(s.replace(/\+/g, ' ')); } |
| 15 |
| 16 var match; |
| 17 while (match = r.exec(window.location.search.substring(1))) |
| 18 params[d(match[1])] = d(match[2]); |
| 19 |
| 20 return params; |
| 21 } |
| 22 |
| 23 var testParams; |
| 24 function loadTestParams() { |
| 25 var queryParameters = parseQueryParameters(); |
| 26 testParams = {}; |
| 27 testParams.testType = queryParameters["testType"] || "AV"; |
| 28 testParams.useAppendStream = (queryParameters["useAppendStream"] == "true"); |
| 29 testParams.doNotWaitForBodyOnLoad = (queryParameters["doNotWaitForBodyOnLoad
"] == "true"); |
| 30 testParams.startOffset = 0; |
| 31 testParams.appendSize = parseInt(queryParameters["appendSize"] || "65536"); |
| 32 testParams.graphDuration = parseInt(queryParameters["graphDuration"] || "100
0"); |
| 33 } |
| 34 |
| 35 function plotTimestamps(timestamps, graphDuration, element) { |
| 36 var c = document.getElementById('c'); |
| 37 var ctx = c.getContext('2d'); |
| 38 |
| 39 var bars = [ |
| 40 { label: 'Page Load Total', |
| 41 start: pageStartTime, |
| 42 end: pageEndTime, |
| 43 color: '#404040' }, |
| 44 { label: 'body.onload Delay', |
| 45 start: pageStartTime, |
| 46 end: bodyLoadTime, |
| 47 color: '#808080' }, |
| 48 { label: 'Test Total', |
| 49 start: timestamps.testStartTime, |
| 50 end: timestamps.testEndTime, |
| 51 color: '#00FF00' }, |
| 52 { label: 'MediaSource opening', |
| 53 start: timestamps.mediaSourceOpenStartTime, |
| 54 end: timestamps.mediaSourceOpenEndTime, |
| 55 color: '#008800' } |
| 56 ]; |
| 57 |
| 58 var maxAppendEndTime = 0; |
| 59 for (var i = 0; i < timestamps.appenders.length; ++i) { |
| 60 var appender = timestamps.appenders[i]; |
| 61 bars.push({ label: 'XHR', |
| 62 start: appender.xhrStartTime, |
| 63 end: appender.xhrEndTime, |
| 64 color: '#0088FF' }); |
| 65 bars.push({ label: 'Append', |
| 66 start: appender.appendStartTime, |
| 67 end: appender.appendEndTime, |
| 68 color: '#00FFFF' }); |
| 69 if (appender.appendEndTime > maxAppendEndTime) { |
| 70 maxAppendEndTime = appender.appendEndTime; |
| 71 } |
| 72 } |
| 73 |
| 74 bars.push({label: 'Post Append Delay', start: maxAppendEndTime, end: timesta
mps.testEndTime, color: '#B0B0B0' }); |
| 75 |
| 76 var minTimestamp = Number.MAX_VALUE; |
| 77 for (var i = 0; i < bars.length; ++i) { |
| 78 minTimestamp = Math.min(minTimestamp, bars[i].start); |
| 79 } |
| 80 |
| 81 var graphWidth = c.width - 100; |
| 82 function convertTimestampToX(t) { |
| 83 return graphWidth * (t - minTimestamp) / graphDuration; |
| 84 } |
| 85 var y = 0; |
| 86 var barThickness = 20; |
| 87 c.height = bars.length * barThickness; |
| 88 ctx.font = (0.75 * barThickness) + 'px arial'; |
| 89 for (var i = 0; i < bars.length; ++i) { |
| 90 var bar = bars[i]; |
| 91 var xStart = convertTimestampToX(bar.start); |
| 92 var xEnd = convertTimestampToX(bar.end); |
| 93 ctx.fillStyle = bar.color; |
| 94 ctx.fillRect(xStart, y, xEnd - xStart, barThickness); |
| 95 |
| 96 ctx.fillStyle = 'black'; |
| 97 var text = bar.label + ' (' + (bar.end - bar.start).toFixed(3) + ' ms)'; |
| 98 ctx.fillText(text, xEnd + 10, y + (0.75 * barThickness)); |
| 99 y += barThickness; |
| 100 } |
| 101 reportTelemetryMediaMetrics(bars, element); |
| 102 } |
| 103 |
| 104 function displayResults(stats) { |
| 105 var statsDiv = document.getElementById('stats'); |
| 106 |
| 107 if (!stats) { |
| 108 statsDiv.innerHTML = "Test failed"; |
| 109 return; |
| 110 } |
| 111 |
| 112 var statsMarkup = "Test passed<br><table>"; |
| 113 for (var i in stats) { |
| 114 statsMarkup += "<tr><td style=\"text-align:right\">" + i + ":</td><td>" +
stats[i].toFixed(3) + " ms</td>"; |
| 115 } |
| 116 statsMarkup += "</table>"; |
| 117 statsDiv.innerHTML = statsMarkup; |
| 118 } |
| 119 |
| 120 function reportTelemetryMediaMetrics(stats, element) { |
| 121 if (!stats || !window.__getMediaMetric) { |
| 122 console.error("Stats not collected or could not find getMediaMetric()."); |
| 123 return; |
| 124 } |
| 125 var metric = window.__getMediaMetric(element); |
| 126 if (!metric) { |
| 127 console.error("Can not report Telemetry media metrics."); |
| 128 return |
| 129 } |
| 130 for (var i = 0; i < stats.length; ++i) { |
| 131 var bar = stats[i]; |
| 132 var label = bar.label.toLowerCase().replace(/\s+|\./g, '_'); |
| 133 var value = (bar.end - bar.start).toFixed(3); |
| 134 console.log("appending to telemetry " + label + " : " + value); |
| 135 metric.appendMetric("mse_" + label, value); |
| 136 } |
| 137 } |
| 138 |
| 139 function updateControls(testParams) { |
| 140 var testTypeElement = document.getElementById("testType"); |
| 141 for (var i in testTypeElement.options) { |
| 142 var option = testTypeElement.options[i]; |
| 143 if (option.value == testParams.testType) { |
| 144 testTypeElement.selectedIndex = option.index; |
| 145 } |
| 146 } |
| 147 |
| 148 document.getElementById("useAppendStream").checked = testParams.useAppendStr
eam; |
| 149 document.getElementById("doNotWaitForBodyOnLoad").checked = testParams.doNot
WaitForBodyOnLoad; |
| 150 document.getElementById("appendSize").value = testParams.appendSize; |
| 151 document.getElementById("graphDuration").value = testParams.graphDuration; |
| 152 } |
| 153 |
| 154 function BufferAppender(mimetype, url, id, startOffset, appendSize) { |
| 155 this.mimetype = mimetype; |
| 156 this.url = url; |
| 157 this.id = id; |
| 158 this.startOffset = startOffset; |
| 159 this.appendSize = appendSize; |
| 160 this.xhr = new XMLHttpRequest(); |
| 161 this.sourceBuffer = null; |
| 162 } |
| 163 |
| 164 BufferAppender.prototype.start = function() { |
| 165 this.xhr.addEventListener('loadend', this.onLoadEnd.bind(this)); |
| 166 this.xhr.open('GET', this.url); |
| 167 this.xhr.setRequestHeader('Range', 'bytes=' + this.startOffset + '-' + |
| 168 (this.startOffset + this.appendSize - 1)); |
| 169 this.xhr.responseType = 'arraybuffer'; |
| 170 this.xhr.send(); |
| 171 |
| 172 this.xhrStartTime = getPerfTimestamp(); |
| 173 }; |
| 174 |
| 175 BufferAppender.prototype.onLoadEnd = function() { |
| 176 this.xhrEndTime = getPerfTimestamp(); |
| 177 this.attemptAppend(); |
| 178 }; |
| 179 |
| 180 BufferAppender.prototype.onSourceOpen = function(mediaSource) { |
| 181 if (this.sourceBuffer) |
| 182 return; |
| 183 this.sourceBuffer = mediaSource.addSourceBuffer(this.mimetype); |
| 184 }; |
| 185 |
| 186 BufferAppender.prototype.attemptAppend = function() { |
| 187 if (!this.xhr.response || !this.sourceBuffer) |
| 188 return; |
| 189 |
| 190 this.appendStartTime = getPerfTimestamp(); |
| 191 |
| 192 if (this.sourceBuffer.appendBuffer) { |
| 193 this.sourceBuffer.addEventListener('updateend', |
| 194 this.onUpdateEnd.bind(this)); |
| 195 this.sourceBuffer.appendBuffer(this.xhr.response); |
| 196 } else { |
| 197 this.sourceBuffer.append(new Uint8Array(this.xhr.response)); |
| 198 this.appendEndTime = getPerfTimestamp(); |
| 199 } |
| 200 |
| 201 this.xhr = null; |
| 202 }; |
| 203 |
| 204 BufferAppender.prototype.onUpdateEnd = function() { |
| 205 this.appendEndTime = getPerfTimestamp(); |
| 206 }; |
| 207 |
| 208 BufferAppender.prototype.onPlaybackStarted = function() { |
| 209 var now = getPerfTimestamp(); |
| 210 this.playbackStartTime = now; |
| 211 if (this.sourceBuffer.updating) { |
| 212 // Still appending but playback has already started so just abort the XHR |
| 213 // and append. |
| 214 this.sourceBuffer.abort(); |
| 215 this.xhr.abort(); |
| 216 } |
| 217 }; |
| 218 |
| 219 BufferAppender.prototype.getXHRLoadDuration = function() { |
| 220 return this.xhrEndTime - this.xhrStartTime; |
| 221 }; |
| 222 |
| 223 BufferAppender.prototype.getAppendDuration = function() { |
| 224 return this.appendEndTime - this.appendStartTime; |
| 225 }; |
| 226 |
| 227 function StreamAppender(mimetype, url, id, startOffset, appendSize) { |
| 228 this.mimetype = mimetype; |
| 229 this.url = url; |
| 230 this.id = id; |
| 231 this.startOffset = startOffset; |
| 232 this.appendSize = appendSize; |
| 233 this.xhr = new XMLHttpRequest(); |
| 234 this.sourceBuffer = null; |
| 235 this.appendStarted = false; |
| 236 } |
| 237 |
| 238 StreamAppender.prototype.start = function() { |
| 239 this.xhr.addEventListener('readystatechange', |
| 240 this.attemptAppend.bind(this)); |
| 241 this.xhr.addEventListener('loadend', this.onLoadEnd.bind(this)); |
| 242 this.xhr.open('GET', this.url); |
| 243 this.xhr.setRequestHeader('Range', 'bytes=' + this.startOffset + '-' + |
| 244 (this.startOffset + this.appendSize - 1)); |
| 245 this.xhr.responseType = 'stream'; |
| 246 if (this.xhr.responseType != 'stream') { |
| 247 throw "XHR does not support 'stream' responses."; |
| 248 } |
| 249 this.xhr.send(); |
| 250 |
| 251 this.xhrStartTime = getPerfTimestamp(); |
| 252 }; |
| 253 |
| 254 StreamAppender.prototype.onLoadEnd = function() { |
| 255 this.xhrEndTime = getPerfTimestamp(); |
| 256 this.attemptAppend(); |
| 257 }; |
| 258 |
| 259 StreamAppender.prototype.onSourceOpen = function(mediaSource) { |
| 260 if (this.sourceBuffer) |
| 261 return; |
| 262 this.sourceBuffer = mediaSource.addSourceBuffer(this.mimetype); |
| 263 }; |
| 264 |
| 265 StreamAppender.prototype.attemptAppend = function() { |
| 266 if (this.xhr.readyState < this.xhr.LOADING) { |
| 267 return; |
| 268 } |
| 269 |
| 270 if (!this.xhr.response || !this.sourceBuffer || this.appendStarted) |
| 271 return; |
| 272 |
| 273 this.appendStartTime = getPerfTimestamp(); |
| 274 this.appendStarted = true; |
| 275 this.sourceBuffer.addEventListener('updateend', |
| 276 this.onUpdateEnd.bind(this)); |
| 277 this.sourceBuffer.appendStream(this.xhr.response); |
| 278 }; |
| 279 |
| 280 StreamAppender.prototype.onUpdateEnd = function() { |
| 281 this.appendEndTime = getPerfTimestamp(); |
| 282 }; |
| 283 |
| 284 StreamAppender.prototype.onPlaybackStarted = function() { |
| 285 var now = getPerfTimestamp(); |
| 286 this.playbackStartTime = now; |
| 287 if (this.sourceBuffer.updating) { |
| 288 // Still appending but playback has already started so just abort the XHR |
| 289 // and append. |
| 290 this.sourceBuffer.abort(); |
| 291 this.xhr.abort(); |
| 292 if (!this.appendEndTime) |
| 293 this.appendEndTime = now; |
| 294 |
| 295 if (!this.xhrEndTime) |
| 296 this.xhrEndTime = now; |
| 297 } |
| 298 }; |
| 299 |
| 300 StreamAppender.prototype.getXHRLoadDuration = function() { |
| 301 return this.xhrEndTime - this.xhrStartTime; |
| 302 }; |
| 303 |
| 304 StreamAppender.prototype.getAppendDuration = function() { |
| 305 return this.appendEndTime - this.appendStartTime; |
| 306 }; |
| 307 |
| 308 // runAppendTest() sets testDone to true once all appends finish. |
| 309 var testDone = false; |
| 310 function runAppendTest(mediaElement, appenders, doneCallback) { |
| 311 var testStartTime = getPerfTimestamp(); |
| 312 var mediaSourceOpenStartTime; |
| 313 var mediaSourceOpenEndTime; |
| 314 |
| 315 for (var i = 0; i < appenders.length; ++i) { |
| 316 appenders[i].start(); |
| 317 } |
| 318 |
| 319 function onSourceOpen(event) { |
| 320 var mediaSource = event.target; |
| 321 |
| 322 mediaSourceOpenEndTime = getPerfTimestamp(); |
| 323 |
| 324 for (var i = 0; i < appenders.length; ++i) { |
| 325 appenders[i].onSourceOpen(mediaSource); |
| 326 } |
| 327 |
| 328 for (var i = 0; i < appenders.length; ++i) { |
| 329 appenders[i].attemptAppend(mediaSource); |
| 330 } |
| 331 |
| 332 mediaElement.play(); |
| 333 } |
| 334 |
| 335 var mediaSource; |
| 336 if (window['MediaSource']) { |
| 337 mediaSource = new window.MediaSource(); |
| 338 mediaSource.addEventListener('sourceopen', onSourceOpen); |
| 339 } else { |
| 340 mediaSource = new window.WebKitMediaSource(); |
| 341 mediaSource.addEventListener('webkitsourceopen', onSourceOpen); |
| 342 } |
| 343 |
| 344 var listener; |
| 345 var timeout; |
| 346 function checkForCurrentTimeChange() { |
| 347 if (testDone) |
| 348 return; |
| 349 |
| 350 if (mediaElement.readyState < mediaElement.HAVE_METADATA || |
| 351 mediaElement.currentTime <= 0) |
| 352 return; |
| 353 |
| 354 for (var i = 0; i < appenders.length; ++i) { |
| 355 appenders[i].onPlaybackStarted(mediaSource); |
| 356 } |
| 357 |
| 358 var testEndTime = getPerfTimestamp(); |
| 359 |
| 360 testDone = true; |
| 361 window.clearInterval(listener); |
| 362 window.clearTimeout(timeout); |
| 363 |
| 364 var stats = {}; |
| 365 stats.total = testEndTime - testStartTime; |
| 366 stats.sourceOpen = mediaSourceOpenEndTime - mediaSourceOpenStartTime; |
| 367 stats.maxXHRLoadDuration = appenders[0].getXHRLoadDuration(); |
| 368 stats.maxAppendDuration = appenders[0].getAppendDuration(); |
| 369 |
| 370 var timestamps = {}; |
| 371 timestamps.testStartTime = testStartTime; |
| 372 timestamps.testEndTime = testEndTime; |
| 373 timestamps.mediaSourceOpenStartTime = mediaSourceOpenStartTime; |
| 374 timestamps.mediaSourceOpenEndTime = mediaSourceOpenEndTime; |
| 375 timestamps.appenders = []; |
| 376 |
| 377 for (var i = 1; i < appenders.length; ++i) { |
| 378 var appender = appenders[i]; |
| 379 var xhrLoadDuration = appender.getXHRLoadDuration(); |
| 380 var appendDuration = appender.getAppendDuration(); |
| 381 |
| 382 if (xhrLoadDuration > stats.maxXHRLoadDuration) |
| 383 stats.maxXHRLoadDuration = xhrLoadDuration; |
| 384 |
| 385 if (appendDuration > stats.maxAppendDuration) |
| 386 stats.maxAppendDuration = appendDuration; |
| 387 } |
| 388 |
| 389 for (var i = 0; i < appenders.length; ++i) { |
| 390 var appender = appenders[i]; |
| 391 var appenderTimestamps = {}; |
| 392 appenderTimestamps.xhrStartTime = appender.xhrStartTime; |
| 393 appenderTimestamps.xhrEndTime = appender.xhrEndTime; |
| 394 appenderTimestamps.appendStartTime = appender.appendStartTime; |
| 395 appenderTimestamps.appendEndTime = appender.appendEndTime; |
| 396 appenderTimestamps.playbackStartTime = appender.playbackStartTime; |
| 397 timestamps.appenders.push(appenderTimestamps); |
| 398 } |
| 399 |
| 400 mediaElement.pause(); |
| 401 |
| 402 pageEndTime = getPerfTimestamp(); |
| 403 doneCallback(stats, timestamps); |
| 404 }; |
| 405 |
| 406 mediaElement.addEventListener('timeupdate', checkForCurrentTimeChange); |
| 407 |
| 408 listener = setInterval(checkForCurrentTimeChange, 15); |
| 409 timeout = setTimeout(function() { |
| 410 if (testDone) |
| 411 return; |
| 412 |
| 413 console.log('Test timed out.'); |
| 414 testDone = true; |
| 415 window.clearInterval(listener); |
| 416 |
| 417 mediaElement.pause(); |
| 418 doneCallback(null); |
| 419 }, 10000); |
| 420 |
| 421 mediaSourceOpenStartTime = getPerfTimestamp(); |
| 422 mediaElement.src = URL.createObjectURL(mediaSource); |
| 423 }; |
| 424 |
| 425 function onBodyLoad() { |
| 426 bodyLoadTime = getPerfTimestamp(); |
| 427 |
| 428 if (!testParams.doNotWaitForBodyOnLoad) { |
| 429 startTest(); |
| 430 } |
| 431 } |
| 432 |
| 433 function startTest() { |
| 434 updateControls(testParams); |
| 435 |
| 436 var appenders = []; |
| 437 |
| 438 if (useAppendStream && !window.MediaSource) |
| 439 throw "Can't use appendStream() because the unprefixed MediaSource object
is not present."; |
| 440 |
| 441 var Appender = testParams.useAppendStream ? StreamAppender : BufferAppender; |
| 442 |
| 443 if (testParams.testType.indexOf("A") != -1) { |
| 444 appenders.push(new Appender("audio/mp4; codecs=\"mp4a.40.2\"", "audio.mp4"
, "a", testParams.startOffset, testParams.appendSize)); |
| 445 } |
| 446 |
| 447 if (testParams.testType.indexOf("V") != -1) { |
| 448 appenders.push(new Appender("video/mp4; codecs=\"avc1.640028\"", "video.mp
4", "v", testParams.startOffset, testParams.appendSize)); |
| 449 } |
| 450 |
| 451 var video = document.getElementById('v'); |
| 452 video.id = getTestID(); |
| 453 runAppendTest(video, appenders, function(stats, timestamps) { |
| 454 displayResults(stats); |
| 455 plotTimestamps(timestamps, testParams.graphDuration, video); |
| 456 }); |
| 457 } |
| 458 |
| 459 function getTestID() { |
| 460 console.log("setting test ID") |
| 461 console.log(testParams.doNotWaitForBodyOnLoad) |
| 462 var id = testParams.testType; |
| 463 if (testParams.useAppendStream) |
| 464 id += "_stream" |
| 465 else |
| 466 id += "_buffer" |
| 467 if (testParams.doNotWaitForBodyOnLoad) |
| 468 id += "_pre_load" |
| 469 else |
| 470 id += "_post_load" |
| 471 return id; |
| 472 } |
| 473 |
| 474 function setupTest() { |
| 475 loadTestParams(); |
| 476 document.body.onload = onBodyLoad; |
| 477 |
| 478 if (testParams.doNotWaitForBodyOnLoad) { |
| 479 startTest(); |
| 480 } |
| 481 } |
| 482 |
| 483 window["setupTest"] = setupTest; |
| 484 window.__testDone = function() { |
| 485 return testDone; |
| 486 }; |
| 487 })(); |
OLD | NEW |