mediasource.js (5910B)
1 // Helpers for Media Source Extensions tests 2 3 let gMSETestPrefs = [ 4 ["media.mediasource.enabled", true], 5 ["media.audio-max-decode-error", 0], 6 ["media.video-max-decode-error", 0], 7 ]; 8 9 // Called before runWithMSE() to set the prefs before running MSE tests. 10 function addMSEPrefs(...prefs) { 11 gMSETestPrefs = gMSETestPrefs.concat(prefs); 12 } 13 14 async function runWithMSE(testFunction) { 15 await once(window, "load"); 16 await SpecialPowers.pushPrefEnv({ set: gMSETestPrefs }); 17 18 const ms = new MediaSource(); 19 20 const el = document.createElement("video"); 21 el.src = URL.createObjectURL(ms); 22 el.preload = "auto"; 23 24 document.body.appendChild(el); 25 SimpleTest.registerCleanupFunction(() => { 26 el.remove(); 27 el.removeAttribute("src"); 28 el.load(); 29 }); 30 try { 31 await testFunction(ms, el); 32 } catch (e) { 33 ok(false, `${testFunction.name} failed with error ${e.name}`); 34 throw e; 35 } 36 } 37 38 async function fetchWithXHR(uri) { 39 return new Promise(resolve => { 40 const xhr = new XMLHttpRequest(); 41 xhr.open("GET", uri, true); 42 xhr.responseType = "arraybuffer"; 43 xhr.addEventListener("load", function () { 44 is( 45 xhr.status, 46 200, 47 "fetchWithXHR load uri='" + uri + "' status=" + xhr.status 48 ); 49 resolve(xhr.response); 50 }); 51 xhr.send(); 52 }); 53 } 54 55 function range(start, end) { 56 const rv = []; 57 for (let i = start; i < end; ++i) { 58 rv.push(i); 59 } 60 return rv; 61 } 62 63 function must_throw(f, msg, error = true) { 64 try { 65 f(); 66 ok(!error, msg); 67 } catch (e) { 68 ok(error, msg); 69 if (error === true) { 70 ok( 71 false, 72 `Please provide name of expected error! Got ${e.name}: ${e.message}.` 73 ); 74 } else if (e.name != error) { 75 throw e; 76 } 77 } 78 } 79 80 async function must_reject(f, msg, error = true) { 81 try { 82 await f(); 83 ok(!error, msg); 84 } catch (e) { 85 ok(error, msg); 86 if (error === true) { 87 ok( 88 false, 89 `Please provide name of expected error! Got ${e.name}: ${e.message}.` 90 ); 91 } else if (e.name != error) { 92 throw e; 93 } 94 } 95 } 96 97 const wait = ms => new Promise(resolve => setTimeout(resolve, ms)); 98 99 const must_not_throw = (f, msg) => must_throw(f, msg, false); 100 const must_not_reject = (f, msg) => must_reject(f, msg, false); 101 102 async function once(target, name) { 103 return new Promise(r => target.addEventListener(name, r, { once: true })); 104 } 105 106 function timeRangeToString(r) { 107 let str = "TimeRanges: "; 108 for (let i = 0; i < r.length; i++) { 109 str += "[" + r.start(i) + ", " + r.end(i) + ")"; 110 } 111 return str; 112 } 113 114 async function loadSegment(sb, typedArrayOrArrayBuffer) { 115 const typedArray = 116 typedArrayOrArrayBuffer instanceof ArrayBuffer 117 ? new Uint8Array(typedArrayOrArrayBuffer) 118 : typedArrayOrArrayBuffer; 119 info( 120 `Loading buffer: [${typedArray.byteOffset}, ${ 121 typedArray.byteOffset + typedArray.byteLength 122 })` 123 ); 124 const beforeBuffered = timeRangeToString(sb.buffered); 125 const p = once(sb, "update"); 126 sb.appendBuffer(typedArray); 127 await p; 128 const afterBuffered = timeRangeToString(sb.buffered); 129 info( 130 `SourceBuffer buffered ranges grew from ${beforeBuffered} to ${afterBuffered}` 131 ); 132 } 133 134 async function fetchAndLoad(sb, prefix, chunks, suffix) { 135 // Fetch the buffers in parallel. 136 const buffers = await Promise.all( 137 chunks.map(c => fetchWithXHR(prefix + c + suffix)) 138 ); 139 140 // Load them in series, as required per spec. 141 for (const buffer of buffers) { 142 await loadSegment(sb, buffer); 143 } 144 } 145 146 function loadSegmentAsync(sb, typedArrayOrArrayBuffer) { 147 const typedArray = 148 typedArrayOrArrayBuffer instanceof ArrayBuffer 149 ? new Uint8Array(typedArrayOrArrayBuffer) 150 : typedArrayOrArrayBuffer; 151 info( 152 `Loading buffer2: [${typedArray.byteOffset}, ${ 153 typedArray.byteOffset + typedArray.byteLength 154 })` 155 ); 156 const beforeBuffered = timeRangeToString(sb.buffered); 157 return sb.appendBufferAsync(typedArray).then(() => { 158 const afterBuffered = timeRangeToString(sb.buffered); 159 info( 160 `SourceBuffer buffered ranges grew from ${beforeBuffered} to ${afterBuffered}` 161 ); 162 }); 163 } 164 165 function fetchAndLoadAsync(sb, prefix, chunks, suffix) { 166 // Fetch the buffers in parallel. 167 const buffers = {}; 168 const fetches = []; 169 for (const chunk of chunks) { 170 fetches.push( 171 fetchWithXHR(prefix + chunk + suffix).then( 172 ((c, x) => (buffers[c] = x)).bind(null, chunk) 173 ) 174 ); 175 } 176 177 // Load them in series, as required per spec. 178 return Promise.all(fetches).then(function () { 179 let rv = Promise.resolve(); 180 for (const chunk of chunks) { 181 rv = rv.then(loadSegmentAsync.bind(null, sb, buffers[chunk])); 182 } 183 return rv; 184 }); 185 } 186 187 // Register timeout function to dump debugging logs. 188 SimpleTest.registerTimeoutFunction(async function () { 189 for (const v of document.getElementsByTagName("video")) { 190 console.log(await SpecialPowers.wrap(v).mozRequestDebugInfo()); 191 } 192 for (const a of document.getElementsByTagName("audio")) { 193 console.log(await SpecialPowers.wrap(a).mozRequestDebugInfo()); 194 } 195 }); 196 197 async function waitUntilTime(target, targetTime) { 198 await new Promise(resolve => { 199 target.addEventListener("waiting", function onwaiting() { 200 info("Got a waiting event at " + target.currentTime); 201 if (target.currentTime >= targetTime) { 202 target.removeEventListener("waiting", onwaiting); 203 resolve(); 204 } 205 }); 206 }); 207 ok(true, "Reached target time of: " + targetTime); 208 } 209 210 // Log events for debugging. 211 212 function logEvents(el) { 213 [ 214 "suspend", 215 "play", 216 "canplay", 217 "canplaythrough", 218 "loadstart", 219 "loadedmetadata", 220 "loadeddata", 221 "playing", 222 "ended", 223 "error", 224 "stalled", 225 "emptied", 226 "abort", 227 "waiting", 228 "pause", 229 "durationchange", 230 "seeking", 231 "seeked", 232 ].forEach(type => 233 el.addEventListener(type, e => info(`got ${e.type} event`)) 234 ); 235 }