mediasource-util.js (16441B)
1 (function(window) { 2 var SEGMENT_INFO_LIST = [ 3 { 4 url: 'mp4/test.mp4', 5 type: 'video/mp4; codecs="mp4a.40.2,avc1.4d400d"', 6 duration: 6.549, 7 init: { offset: 0, size: 1413 }, 8 media: [ 9 { offset: 1413, size: 24034, timev: 0.095000, timea: 0, endtimev: 0.896666, endtimea: 0.882358 }, 10 { offset: 25447, size: 21757, timev: 0.896666, timea: 0.882358, endtimev: 1.696666, endtimea: 1.671836 }, 11 { offset: 47204, size: 23591, timev: 1.696666, timea: 1.671836, endtimev: 2.498333, endtimea: 2.461315 }, 12 { offset: 70795, size: 22614, timev: 2.498333, timea: 2.461315, endtimev: 3.298333, endtimea: 3.297233 }, 13 { offset: 93409, size: 18353, timev: 3.298333, timea: 3.297233, endtimev: 4.100000, endtimea: 4.086712}, 14 { offset: 111762, size: 23935, timev: 4.100000, timea: 4.086712, endtimev: 4.900000, endtimea: 4.876190 }, 15 { offset: 135697, size: 21911, timev: 4.900000, timea: 4.876190, endtimev: 5.701666, endtimea: 5.665668 }, 16 { offset: 157608, size: 23776, timev: 5.701666, timea: 5.665668, endtimev: 6.501666, endtimea: 6.501587 }, 17 { offset: 181384, size: 5843, timev: 6.501666, timea: 6.501587, endtimev: 6.501666, endtimea: 6.501678 }, 18 ] 19 }, 20 { 21 url: 'webm/test.webm', 22 type: 'video/webm; codecs="vp8, vorbis"', 23 duration: 6.552, 24 init: { offset: 0, size: 4116 }, 25 media: [ 26 { offset: 4116, size: 26583, timev: 0.112000, timea: 0, endtimev: 0.913000, endtimea: 0.912000 }, 27 { offset: 30699, size: 20555, timev: 0.913000, timea: 0.912000, endtimev: 1.714000, endtimea: 1.701000 }, 28 { offset: 51254, size: 22668, timev: 1.714000, timea: 1.701000, endtimev: 2.515000, endtimea: 2.514000 }, 29 { offset: 73922, size: 21943, timev: 2.515000, timea: 2.514000, endtimev: 3.315000, endtimea: 3.303000 }, 30 { offset: 95865, size: 23015, timev: 3.315000, timea: 3.303000, endtimev: 4.116000, endtimea: 4.093000}, 31 { offset: 118880, size: 20406, timev: 4.116000, timea: 4.093000, endtimev: 4.917000, endtimea: 4.906000 }, 32 { offset: 139286, size: 21537, timev: 4.917000, timea: 4.906000, endtimev: 5.718000, endtimea: 5.695000 }, 33 { offset: 160823, size: 24027, timev: 5.718000, timea: 5.695000, endtimev: 6.519000, endtimea: 6.508000 }, 34 { offset: 184850, size: 5955, timev: 6.519000, timea: 6.508000, endtimev: 6.577000, endtimea: 6.577000}, 35 ], 36 } 37 ]; 38 EventExpectationsManager = function(test) 39 { 40 this.test_ = test; 41 this.eventTargetList_ = []; 42 this.waitCallbacks_ = []; 43 }; 44 45 EventExpectationsManager.prototype.expectEvent = function(object, eventName, description) 46 { 47 var eventInfo = { 'target': object, 'type': eventName, 'description': description}; 48 var expectations = this.getExpectations_(object); 49 expectations.push(eventInfo); 50 51 var t = this; 52 var waitHandler = this.test_.step_func(this.handleWaitCallback_.bind(this)); 53 var eventHandler = this.test_.step_func(function(event) 54 { 55 object.removeEventListener(eventName, eventHandler); 56 var expected = expectations[0]; 57 assert_equals(event.target, expected.target, "Event target match."); 58 assert_equals(event.type, expected.type, "Event types match."); 59 assert_equals(eventInfo.description, expected.description, "Descriptions match for '" + event.type + "'."); 60 61 expectations.shift(1); 62 if (t.waitCallbacks_.length > 1) 63 setTimeout(waitHandler, 0); 64 else if (t.waitCallbacks_.length == 1) { 65 // Immediately call the callback. 66 waitHandler(); 67 } 68 }); 69 object.addEventListener(eventName, eventHandler); 70 }; 71 72 EventExpectationsManager.prototype.waitForExpectedEvents = function(callback) 73 { 74 this.waitCallbacks_.push(callback); 75 setTimeout(this.test_.step_func(this.handleWaitCallback_.bind(this)), 0); 76 }; 77 78 EventExpectationsManager.prototype.expectingEvents = function() 79 { 80 for (var i = 0; i < this.eventTargetList_.length; ++i) { 81 if (this.eventTargetList_[i].expectations.length > 0) { 82 return true; 83 } 84 } 85 return false; 86 } 87 88 EventExpectationsManager.prototype.handleWaitCallback_ = function() 89 { 90 if (this.waitCallbacks_.length == 0 || this.expectingEvents()) 91 return; 92 var callback = this.waitCallbacks_.shift(1); 93 callback(); 94 }; 95 96 EventExpectationsManager.prototype.getExpectations_ = function(target) 97 { 98 for (var i = 0; i < this.eventTargetList_.length; ++i) { 99 var info = this.eventTargetList_[i]; 100 if (info.target == target) { 101 return info.expectations; 102 } 103 } 104 var expectations = []; 105 this.eventTargetList_.push({ 'target': target, 'expectations': expectations }); 106 return expectations; 107 }; 108 109 function loadData_(test, url, callback, isBinary) 110 { 111 var request = new XMLHttpRequest(); 112 request.open("GET", url, true); 113 if (isBinary) { 114 request.responseType = 'arraybuffer'; 115 } 116 request.onload = test.step_func(function(event) 117 { 118 if (request.status != 200) { 119 assert_unreached("Unexpected status code : " + request.status); 120 return; 121 } 122 var response = request.response; 123 if (isBinary) { 124 response = new Uint8Array(response); 125 } 126 callback(response); 127 }); 128 request.onerror = test.step_func(function(event) 129 { 130 assert_unreached("Unexpected error"); 131 }); 132 request.send(); 133 } 134 135 function openMediaSource_(test, mediaTag, callback) 136 { 137 var mediaSource = new MediaSource(); 138 var mediaSourceURL = URL.createObjectURL(mediaSource); 139 140 var eventHandler = test.step_func(onSourceOpen); 141 function onSourceOpen(event) 142 { 143 mediaSource.removeEventListener('sourceopen', eventHandler); 144 URL.revokeObjectURL(mediaSourceURL); 145 callback(mediaSource); 146 } 147 148 mediaSource.addEventListener('sourceopen', eventHandler); 149 mediaTag.src = mediaSourceURL; 150 } 151 152 var MediaSourceUtil = {}; 153 154 MediaSourceUtil.loadTextData = function(test, url, callback) 155 { 156 loadData_(test, url, callback, false); 157 }; 158 159 MediaSourceUtil.loadBinaryData = function(test, url, callback) 160 { 161 loadData_(test, url, callback, true); 162 }; 163 164 /** 165 * Return a promise that resolves to an object having the properties 166 * specified in the manifest plus a 'data' property with the media data. 167 */ 168 MediaSourceUtil.fetchResourceOfManifest = function(test, manifestFilename) 169 { 170 var baseURL = ''; 171 var manifestURL = baseURL + manifestFilename; 172 return new Promise(resolve => { 173 MediaSourceUtil.loadTextData(test, manifestURL, function(manifestText) 174 { 175 var manifest = JSON.parse(manifestText); 176 177 assert_true(MediaSource.isTypeSupported(manifest.type), manifest.type + " is supported."); 178 179 var mediaURL = manifest.url; 180 MediaSourceUtil.loadBinaryData(test, mediaURL, function(mediaData) 181 { 182 manifest.data = mediaData; 183 resolve(manifest); 184 }); 185 }); 186 }); 187 }; 188 189 MediaSourceUtil.fetchManifestAndData = async function(test, manifestFilename, callback) 190 { 191 const resource = await MediaSourceUtil.fetchResourceOfManifest(test, manifestFilename); 192 test.step(() => callback(resource.type, resource.data)); 193 }; 194 195 MediaSourceUtil.extractSegmentData = function(mediaData, info) 196 { 197 var start = info.offset; 198 var end = start + info.size; 199 return mediaData.subarray(start, end); 200 } 201 202 MediaSourceUtil.WriteBigEndianInteger32ToUint8Array = function(integer32, array) 203 { 204 array[0] = integer32 >> 24; 205 array[1] = integer32 >> 16; 206 array[2] = integer32 >> 8; 207 array[3] = integer32; 208 } 209 210 MediaSourceUtil.getMediaDataForPlaybackTime = function(mediaData, segmentInfo, playbackTimeToAdd) 211 { 212 assert_less_than_equal(playbackTimeToAdd, segmentInfo.duration); 213 var mediaInfo = segmentInfo.media; 214 var start = mediaInfo[0].offset; 215 var numBytes = 0; 216 var segmentIndex = 0; 217 while (segmentIndex < mediaInfo.length 218 && Math.min(mediaInfo[segmentIndex].timev, mediaInfo[segmentIndex].timea) <= playbackTimeToAdd) 219 { 220 numBytes += mediaInfo[segmentIndex].size; 221 ++segmentIndex; 222 } 223 return mediaData.subarray(start, numBytes + start); 224 } 225 226 function getFirstSupportedType(typeList) 227 { 228 for (var i = 0; i < typeList.length; ++i) { 229 if (window.MediaSource && MediaSource.isTypeSupported(typeList[i])) 230 return typeList[i]; 231 } 232 return ""; 233 } 234 235 function getSegmentInfo() 236 { 237 for (var i = 0; i < SEGMENT_INFO_LIST.length; ++i) { 238 var segmentInfo = SEGMENT_INFO_LIST[i]; 239 if (window.MediaSource && MediaSource.isTypeSupported(segmentInfo.type)) { 240 return segmentInfo; 241 } 242 } 243 return null; 244 } 245 246 // To support mediasource-changetype tests, do not use any types that 247 // indicate automatic timestamp generation in this audioOnlyTypes list. 248 var audioOnlyTypes = ['audio/mp4;codecs="mp4a.40.2"', 'audio/webm;codecs="vorbis"']; 249 250 var videoOnlyTypes = ['video/mp4;codecs="avc1.4D4001"', 'video/webm;codecs="vp8"']; 251 var audioVideoTypes = ['video/mp4;codecs="avc1.4D4001,mp4a.40.2"', 'video/webm;codecs="vp8,vorbis"']; 252 MediaSourceUtil.AUDIO_ONLY_TYPE = getFirstSupportedType(audioOnlyTypes); 253 MediaSourceUtil.VIDEO_ONLY_TYPE = getFirstSupportedType(videoOnlyTypes); 254 MediaSourceUtil.AUDIO_VIDEO_TYPE = getFirstSupportedType(audioVideoTypes); 255 MediaSourceUtil.SEGMENT_INFO = getSegmentInfo(); 256 257 MediaSourceUtil.getSubType = function(mimetype) { 258 var slashIndex = mimetype.indexOf("/"); 259 var semicolonIndex = mimetype.indexOf(";"); 260 if (slashIndex <= 0) { 261 assert_unreached("Invalid mimetype '" + mimetype + "'"); 262 return; 263 } 264 265 var start = slashIndex + 1; 266 if (semicolonIndex >= 0) { 267 if (semicolonIndex <= start) { 268 assert_unreached("Invalid mimetype '" + mimetype + "'"); 269 return; 270 } 271 272 return mimetype.substr(start, semicolonIndex - start) 273 } 274 275 return mimetype.substr(start); 276 }; 277 278 MediaSourceUtil.append = function(test, sourceBuffer, data, callback) 279 { 280 function onUpdate() { 281 sourceBuffer.removeEventListener("update", onUpdate); 282 callback(); 283 } 284 sourceBuffer.addEventListener("update", onUpdate); 285 286 sourceBuffer.addEventListener('error', test.unreached_func("Unexpected event 'error'")); 287 288 sourceBuffer.appendBuffer(data); 289 }; 290 291 MediaSourceUtil.appendUntilEventFires = function(test, mediaElement, eventName, sourceBuffer, mediaData, segmentInfo, startingIndex) 292 { 293 var eventFired = false; 294 function onEvent() { 295 mediaElement.removeEventListener(eventName, onEvent); 296 eventFired = true; 297 } 298 mediaElement.addEventListener(eventName, onEvent); 299 300 var i = startingIndex; 301 var onAppendDone = function() { 302 if (eventFired || (i >= (segmentInfo.media.length - 1))) 303 return; 304 305 i++; 306 if (i < segmentInfo.media.length) 307 { 308 MediaSourceUtil.append(test, sourceBuffer, MediaSourceUtil.extractSegmentData(mediaData, segmentInfo.media[i]), onAppendDone); 309 } 310 }; 311 MediaSourceUtil.append(test, sourceBuffer, MediaSourceUtil.extractSegmentData(mediaData, segmentInfo.media[i]), onAppendDone); 312 313 }; 314 315 function addExtraTestMethods(test) 316 { 317 test.eventExpectations_ = new EventExpectationsManager(test); 318 test.expectEvent = function(object, eventName, description) 319 { 320 test.eventExpectations_.expectEvent(object, eventName, description); 321 }; 322 323 test.waitForExpectedEvents = function(callback) 324 { 325 test.eventExpectations_.waitForExpectedEvents(callback); 326 }; 327 328 test.waitForCurrentTimeChange = function(mediaElement, callback) 329 { 330 var initialTime = mediaElement.currentTime; 331 332 var onTimeUpdate = test.step_func(function() 333 { 334 if (mediaElement.currentTime != initialTime) { 335 mediaElement.removeEventListener('timeupdate', onTimeUpdate); 336 callback(); 337 } 338 }); 339 340 mediaElement.addEventListener('timeupdate', onTimeUpdate); 341 } 342 343 var oldTestDone = test.done.bind(test); 344 test.done = function() 345 { 346 if (test.status == test.PASS) { 347 test.step(function() { 348 assert_false(test.eventExpectations_.expectingEvents(), "No pending event expectations."); 349 }); 350 } 351 oldTestDone(); 352 }; 353 }; 354 355 window['MediaSourceUtil'] = MediaSourceUtil; 356 window['media_test'] = function(testFunction, description, options) 357 { 358 options = options || {}; 359 return async_test(function(test) 360 { 361 addExtraTestMethods(test); 362 testFunction(test); 363 }, description, options); 364 }; 365 window['mediasource_test'] = function(testFunction, description, options) 366 { 367 return media_test(function(test) 368 { 369 var mediaTag = document.createElement("video"); 370 if (!document.body) { 371 document.body = document.createElement("body"); 372 } 373 document.body.appendChild(mediaTag); 374 375 test.removeMediaElement_ = true; 376 test.add_cleanup(function() 377 { 378 if (test.removeMediaElement_) { 379 document.body.removeChild(mediaTag); 380 test.removeMediaElement_ = false; 381 } 382 }); 383 384 openMediaSource_(test, mediaTag, function(mediaSource) 385 { 386 testFunction(test, mediaTag, mediaSource); 387 }); 388 }, description, options); 389 }; 390 391 window['mediasource_testafterdataloaded'] = function(testFunction, description, options) 392 { 393 mediasource_test(function(test, mediaElement, mediaSource) 394 { 395 var segmentInfo = MediaSourceUtil.SEGMENT_INFO; 396 397 if (!segmentInfo) { 398 assert_unreached("No segment info compatible with this MediaSource implementation."); 399 return; 400 } 401 402 mediaElement.addEventListener('error', test.unreached_func("Unexpected event 'error'")); 403 404 var sourceBuffer = mediaSource.addSourceBuffer(segmentInfo.type); 405 MediaSourceUtil.loadBinaryData(test, segmentInfo.url, function(mediaData) 406 { 407 testFunction(test, mediaElement, mediaSource, segmentInfo, sourceBuffer, mediaData); 408 }); 409 }, description, options); 410 } 411 412 function timeRangesToString(ranges) 413 { 414 var s = "{"; 415 for (var i = 0; i < ranges.length; ++i) { 416 s += " [" + ranges.start(i).toFixed(3) + ", " + ranges.end(i).toFixed(3) + ")"; 417 } 418 return s + " }"; 419 } 420 421 window['assertBufferedEquals'] = function(obj, expected, description) 422 { 423 var actual = timeRangesToString(obj.buffered); 424 assert_equals(actual, expected, description); 425 }; 426 427 window['assertSeekableEquals'] = function(obj, expected, description) 428 { 429 var actual = timeRangesToString(obj.seekable); 430 assert_equals(actual, expected, description); 431 }; 432 433 })(window);