MediaRecorder-stop.html (12379B)
1 <!doctype html> 2 <html> 3 <head> 4 <title>MediaRecorder Stop</title> 5 <meta name=variant content="?mimeType=''"> 6 <meta name=variant content="?mimeType=video/webm;codecs=vp8,opus"> 7 <meta name=variant content="?mimeType=video/webm;codecs=vp9,opus"> 8 <meta name=variant content="?mimeType=video/webm;codecs=av1,opus"> 9 <meta name=variant content="?mimeType=video/mp4;codecs=avc1.64003E,mp4a.40.2"> 10 <meta name=variant content="?mimeType=video/mp4;codecs=avc3.64003E,mp4a.40.2"> 11 <meta name=variant content="?mimeType=video/mp4;codecs=vp9,opus"> 12 <meta name=variant content="?mimeType=video/mp4;codecs=av01,opus"> 13 <meta name=variant content="?mimeType=video/mp4;codecs=av01,mp4a.40.2"> 14 <meta name=variant content="?mimeType=video/mp4;codecs=hvc1.1.6.L186.B0,opus"> 15 <meta name=variant content="?mimeType=video/mp4;codecs=hev1.1.6.L186.B0,opus"> 16 <meta name=variant content="?mimeType=video/mp4;codecs=hvc1.1.6.L186.B0,mp4a.40.2"> 17 <meta name=variant content="?mimeType=video/mp4;codecs=hev1.1.6.L186.B0,mp4a.40.2"> 18 <meta name=variant content="?mimeType=video/x-matroska;codecs=hvc1.1.6.L186.B0,opus"> 19 <meta name=variant content="?mimeType=video/x-matroska;codecs=hev1.1.6.L186.B0,opus"> 20 <meta name=variant content="?mimeType=video/mp4"> 21 <link rel="help" href="https://w3c.github.io/mediacapture-record/MediaRecorder.html#mediarecorder"> 22 <script src="/resources/testharness.js"></script> 23 <script src="/resources/testharnessreport.js"></script> 24 <script src="utils/sources.js"></script> 25 </head> 26 <body> 27 <script> 28 function recordEvents(target, events) { 29 let arr = []; 30 for (let ev of events) { 31 target.addEventListener(ev, _ => arr.push(ev)); 32 } 33 return arr; 34 } 35 36 // This function is used to check that elements of |actual| is a sub 37 // sequence in the |expected| sequence. 38 function assertSequenceIn(actual, expected) { 39 let i = 0; 40 for (event of actual) { 41 const j = expected.slice(i).indexOf(event); 42 assert_greater_than_equal( 43 j, 0, "Sequence element " + event + " is not included in " + 44 expected.slice(i)); 45 // Ensure duplicates in actual aren't silently accepted by skipping 46 // past the matched element. 47 i = j + 1; 48 } 49 return true; 50 } 51 52 function isMimetypeSupported(mimeType, t) { 53 if (mimeType && !MediaRecorder.isTypeSupported(mimeType)) { 54 t.done(); 55 return false; 56 } 57 return true; 58 } 59 60 const params = new URLSearchParams(window.location.search); 61 const mimeType = params.get('mimeType'); 62 const tag = `mimeType "${mimeType}"`; 63 promise_test(async t => { 64 if (!isMimetypeSupported(mimeType, t)) { 65 return; 66 } 67 68 const {stream: video} = createVideoStream(t); 69 const recorder = new MediaRecorder(video, {mimeType}); 70 const events = recordEvents(recorder, 71 ["start", "stop", "dataavailable", "pause", "resume", "error"]); 72 assert_equals(video.getVideoTracks().length, 1, "video mediastream starts with one track"); 73 recorder.start(); 74 assert_equals(recorder.state, "recording", "MediaRecorder has been started successfully"); 75 video.getVideoTracks()[0].stop(); 76 assert_equals(recorder.state, "recording", "MediaRecorder state should be recording immediately following last track ending"); 77 const event = await new Promise(r => recorder.onstop = r); 78 79 assert_equals(event.type, "stop", "the event type should be stop"); 80 assert_true(event.isTrusted, "isTrusted should be true when the event is created by C++"); 81 assert_equals(recorder.state, "inactive", "MediaRecorder is inactive after stop event"); 82 83 // As the test is written, it's not guaranteed that 84 // onstart/ondataavailable is invoked, but it's fine if they are. 85 // The stop element is guaranteed to be in events when we get here. 86 assertSequenceIn(events, ["start", "dataavailable", "stop"]); 87 }, "MediaRecorder will stop recording and fire a stop event when all tracks are ended"); 88 89 promise_test(async t => { 90 if (!isMimetypeSupported(mimeType, t)) { 91 return; 92 } 93 94 const {stream: video} = createVideoStream(t); 95 const recorder = new MediaRecorder(video, {mimeType}); 96 const events = recordEvents(recorder, 97 ["start", "stop", "dataavailable", "pause", "resume", "error"]); 98 recorder.start(); 99 assert_equals(recorder.state, "recording", "MediaRecorder has been started successfully"); 100 recorder.stop(); 101 assert_equals(recorder.state, "inactive", "MediaRecorder state should be inactive immediately following stop() call"); 102 103 const event = await new Promise (r => recorder.onstop = r); 104 assert_equals(event.type, "stop", "the event type should be stop"); 105 assert_true(event.isTrusted, "isTrusted should be true when the event is created by C++"); 106 assert_equals(recorder.state, "inactive", "MediaRecorder is inactive after stop event"); 107 108 // As the test is written, it's not guaranteed that 109 // onstart/ondataavailable is invoked, but it's fine if they are. 110 // The stop element is guaranteed to be in events when we get here. 111 assertSequenceIn(events, ["start", "dataavailable", "stop"]); 112 }, "MediaRecorder will stop recording and fire a stop event when stop() is called"); 113 114 promise_test(async t => { 115 if (!isMimetypeSupported(mimeType, t)) { 116 return; 117 } 118 119 const recorder = new MediaRecorder(createVideoStream(t).stream, {mimeType}); 120 recorder.stop(); 121 await Promise.race([ 122 new Promise((_, reject) => recorder.onstop = 123 _ => reject(new Error("onstop should never have been called"))), 124 new Promise(r => t.step_timeout(r, 0))]); 125 }, "MediaRecorder will not fire an exception when stopped after creation"); 126 127 promise_test(async t => { 128 if (!isMimetypeSupported(mimeType, t)) { 129 return; 130 } 131 132 const recorder = new MediaRecorder(createVideoStream(t).stream, {mimeType}); 133 recorder.start(); 134 recorder.stop(); 135 const event = await new Promise(r => recorder.onstop = r); 136 recorder.stop(); 137 await Promise.race([ 138 new Promise((_, reject) => recorder.onstop = 139 _ => reject(new Error("onstop should never have been called"))), 140 new Promise(r => t.step_timeout(r, 0))]); 141 }, "MediaRecorder will not fire an exception when stopped after having just been stopped"); 142 143 promise_test(async t => { 144 if (!isMimetypeSupported(mimeType, t)) { 145 return; 146 } 147 148 const {stream} = createVideoStream(t); 149 const recorder = new MediaRecorder(stream, {mimeType}); 150 recorder.start(); 151 stream.getVideoTracks()[0].stop(); 152 const event = await new Promise(r => recorder.onstop = r); 153 recorder.stop(); 154 await Promise.race([ 155 new Promise((_, reject) => recorder.onstop = 156 _ => reject(new Error("onstop should never have been called"))), 157 new Promise(r => t.step_timeout(r, 0))]); 158 }, "MediaRecorder will not fire an exception when stopped after having just been spontaneously stopped"); 159 160 promise_test(async t => { 161 if (!isMimetypeSupported(mimeType, t)) { 162 return; 163 } 164 165 const {stream} = createAudioVideoStream(t); 166 const recorder = new MediaRecorder(stream, {mimeType}); 167 const events = []; 168 const startPromise = new Promise(resolve => recorder.onstart = resolve); 169 const stopPromise = new Promise(resolve => recorder.onstop = resolve); 170 171 startPromise.then(() => events.push("start")); 172 stopPromise.then(() => events.push("stop")); 173 174 recorder.start(); 175 recorder.stop(); 176 177 await stopPromise; 178 assert_array_equals(events, ["start", "stop"]); 179 }, "MediaRecorder will fire start event even if stopped synchronously"); 180 181 promise_test(async t => { 182 if (!isMimetypeSupported(mimeType, t)) { 183 return; 184 } 185 186 const {stream} = createAudioVideoStream(t); 187 const recorder = new MediaRecorder(stream, {mimeType}); 188 const events = []; 189 const startPromise = new Promise(resolve => recorder.onstart = resolve); 190 const stopPromise = new Promise(resolve => recorder.onstop = resolve); 191 const errorPromise = new Promise(resolve => recorder.onerror = resolve); 192 const dataPromise = new Promise(resolve => recorder.ondataavailable = resolve); 193 194 startPromise.then(() => events.push("start")); 195 stopPromise.then(() => events.push("stop")); 196 errorPromise.then(() => events.push("error")); 197 dataPromise.then(() => events.push("data")); 198 199 recorder.start(); 200 stream.removeTrack(stream.getAudioTracks()[0]); 201 202 await stopPromise; 203 assert_array_equals(events, ["start", "error", "data", "stop"]); 204 }, "MediaRecorder will fire start event even if a track is removed synchronously"); 205 206 promise_test(async t => { 207 if (!isMimetypeSupported(mimeType, t)) { 208 return; 209 } 210 211 const {stream} = createFlowingAudioVideoStream(t); 212 const recorder = new MediaRecorder(stream, {mimeType}); 213 const events = recordEvents(recorder, 214 ["start", "stop", "dataavailable", "pause", "resume", "error"]); 215 const dataPromise = new Promise(r => recorder.ondataavailable = r); 216 recorder.start(0); 217 await dataPromise; 218 recorder.stop(); 219 await new Promise (r => recorder.onstop = r); 220 assertSequenceIn(events, ["start", "dataavailable", "stop"]); 221 }, "MediaRecorder will fire only start and stop events in a basic recording flow."); 222 223 promise_test(async t => { 224 const {stream} = createFlowingAudioVideoStream(t); 225 const audioTrack = stream.getAudioTracks()[0]; 226 const videoTrack = stream.getVideoTracks()[0]; 227 t.add_cleanup(() => { 228 audioTrack.stop(); 229 videoTrack.stop(); 230 }); 231 const recorder = new MediaRecorder(stream); 232 233 let resolve; 234 const promise = new Promise((resolve_, reject) => { 235 resolve = resolve_; 236 t.step_timeout(() => reject("stop event time out"), 2000); 237 }); 238 239 let events = []; 240 recorder.onstart = () => events.push("start"); 241 recorder.onstop = () => { 242 events.push("stop"); 243 resolve(); 244 } 245 recorder.onerror = () => events.push("error"); 246 recorder.ondataavailable = ()=> events.push("data"); 247 248 recorder.start(); 249 await new Promise(resolve => t.step_timeout(resolve, 0)); 250 251 stream.removeTrack(stream.getAudioTracks()[0]); 252 recorder.stop(); 253 await promise; 254 assert_array_equals(events, ["start", "data", "stop"]); 255 }, "MediaRecorder will not fire an error event if a recorder is stopped synchronously after a track is removed"); 256 257 promise_test(async t => { 258 const {stream} = createFlowingAudioVideoStream(t); 259 const audioTrack = stream.getAudioTracks()[0]; 260 const videoTrack = stream.getVideoTracks()[0]; 261 t.add_cleanup(() => { 262 audioTrack.stop(); 263 videoTrack.stop(); 264 }); 265 const recorder = new MediaRecorder(stream); 266 267 let resolve; 268 const promise = new Promise((resolve_, reject) => { 269 resolve = resolve_; 270 t.step_timeout(() => reject("stop event time out"), 2000); 271 }); 272 273 let events = []; 274 recorder.onstart = ()=> events.push("start"); 275 recorder.onstop = () => { 276 events.push("stop"); 277 resolve(); 278 } 279 recorder.onerror = ()=> events.push("error"); 280 recorder.ondataavailable = ()=> events.push("data"); 281 282 recorder.start(); 283 await new Promise(resolve => t.step_timeout(resolve, 0)); 284 285 audioTrack.stop(); 286 videoTrack.stop(); 287 recorder.stop(); 288 await promise; 289 assert_array_equals(events, ["start", "data", "stop"]); 290 }, "MediaRecorder will not fire an error event if a recorder is stopped synchronously after all tracks are ended"); 291 </script> 292 </body> 293 </html>