test_getUserMedia_basicScreenshare.html (11383B)
1 <!DOCTYPE HTML> 2 <html> 3 <head> 4 <script type="application/javascript" src="mediaStreamPlayback.js"></script> 5 <script type="application/javascript" src="/tests/dom/canvas/test/captureStream_common.js"></script> 6 </head> 7 <body> 8 <pre id="test"> 9 <script type="application/javascript"> 10 createHTML({ 11 title: "getUserMedia Basic Screenshare Test", 12 bug: "1211656", 13 visible: true, 14 }); 15 16 const {AppConstants} = SpecialPowers.ChromeUtils.importESModule( 17 "resource://gre/modules/AppConstants.sys.mjs" 18 ); 19 20 // Since the MacOS backend captures in the wrong rgb color profile we need 21 // large thresholds there, and they vary greatly by color. We define 22 // thresholds per platform and color here to still allow the test to run. 23 // Since the colors used (red, green, blue, white) consist only of "pure" 24 // components (0 or 255 for each component), the high thresholds on Mac will 25 // still be able to catch an error where the image is corrupt, or if frames 26 // don't flow. 27 const thresholds = { 28 // macos: captures in the display rgb color profile, which we treat as 29 // sRGB, which is most likely wrong. These thresholds are needed currently 30 // in CI. See bug 1827606. 31 "macosx": { "red": 120, "green": 135, "blue": 35, "white": 10 }, 32 // windows: rounding errors in 1) conversion to I420 (the capture), 33 // 2) downscaling, 3) conversion to RGB (for rendering). 34 "win": { "red": 5, "green": 5, "blue": 10, "white": 5 }, 35 // linux: rounding errors in 1) conversion to I420 (the capture), 36 // 2) downscaling, 3) conversion to RGB (for rendering). 37 "linux": { "red": 5, "green": 5, "blue": 10, "white": 5 }, 38 // android: we don't have a screen capture backend. 39 "android": { "red": 0, "green": 0, "blue": 0, "white": 0 }, 40 // other: here just because it's supported by AppConstants.platform. 41 "other": { "red": 0, "green": 0, "blue": 0, "white": 0 }, 42 }; 43 44 const verifyScreenshare = 45 async (video, helper, upleft, upright, downleft, downright) => { 46 if (video.readyState < video.HAVE_CURRENT_DATA) { 47 info("Waiting for data"); 48 await new Promise(r => video.onloadeddata = r); 49 } 50 51 // We assume video size will not change. Offsets help to account for a 52 // square fullscreen-canvas, while the screen is rectangular. 53 const offsetX = Math.max(0, video.videoWidth - video.videoHeight) / 2; 54 const offsetY = Math.max(0, video.videoHeight - video.videoWidth) / 2; 55 56 const verifyAround = async (internalX, internalY, color) => { 57 // Pick a couple of samples around a coordinate to check for a color. 58 // We check multiple rows and columns, to avoid most artifact issues. 59 let areaSamples = [ 60 {dx: 0, dy: 0}, 61 {dx: 1, dy: 3}, 62 {dx: 8, dy: 5}, 63 ]; 64 const threshold = thresholds[AppConstants.platform][color.name]; 65 for (let {dx, dy} of areaSamples) { 66 const x = offsetX + dx + internalX; 67 const y = offsetY + dy + internalY; 68 info(`Checking pixel (${[x,y]}) of total resolution ` 69 + `${video.videoWidth}x${video.videoHeight} against ${color.name}.`); 70 let lastPixel = [-1, -1, -1, -1]; 71 await helper.waitForPixel(video, px => { 72 lastPixel = Array.from(px); 73 return helper.isPixel(px, color, threshold); 74 }, { 75 offsetX: x, 76 offsetY: y, 77 cancel: wait(30000).then(_ => 78 new Error(`Checking ${[x,y]} against ${color.name} timed out. ` + 79 `Got [${lastPixel}]. Threshold ${threshold}.`)), 80 }); 81 ok(true, `Pixel (${[x,y]}) passed. Got [${lastPixel}].`); 82 } 83 }; 84 85 const screenSizeSq = Math.min(video.videoWidth, video.videoHeight); 86 87 info("Waiting for upper left quadrant to become " + upleft.name); 88 await verifyAround(screenSizeSq / 4, screenSizeSq / 4, upleft); 89 90 info("Waiting for upper right quadrant to become " + upright.name); 91 await verifyAround(screenSizeSq * 3 / 4, screenSizeSq / 4, upright); 92 93 info("Waiting for lower left quadrant to become " + downleft.name); 94 await verifyAround(screenSizeSq / 4, screenSizeSq * 3 / 4, downleft); 95 96 info("Waiting for lower right quadrant to become " + downright.name); 97 await verifyAround(screenSizeSq * 3 / 4, screenSizeSq * 3 / 4, downright); 98 }; 99 100 /** 101 * Run a test to verify that we can complete a start and stop media playback 102 * cycle for a screenshare MediaStream on a video HTMLMediaElement. 103 */ 104 runTest(async function () { 105 await pushPrefs( 106 ["full-screen-api.enabled", true], 107 ["full-screen-api.allow-trusted-requests-only", false], 108 ["full-screen-api.transition-duration.enter", "0 0"], 109 ["full-screen-api.transition-duration.leave", "0 0"], 110 ); 111 112 // Improve real estate for screenshots 113 const test = document.getElementById("test"); 114 test.setAttribute("style", "height:0;margin:0;"); 115 const display = document.getElementById("display"); 116 display.setAttribute("style", "margin:0;"); 117 const testVideo = createMediaElement('video', 'testVideo'); 118 testVideo.removeAttribute("width"); 119 testVideo.removeAttribute("height"); 120 testVideo.setAttribute("style", "max-height:240px;"); 121 122 const canvas = document.createElement("canvas"); 123 canvas.width = canvas.height = 20; 124 document.getElementById("content").appendChild(canvas); 125 const draw = ([upleft, upright, downleft, downright]) => { 126 helper.drawColor(canvas, helper[upleft], {offsetX: 0, offsetY: 0}); 127 helper.drawColor(canvas, helper[upright], {offsetX: 10, offsetY: 0}); 128 helper.drawColor(canvas, helper[downleft], {offsetX: 0, offsetY: 10}); 129 helper.drawColor(canvas, helper[downright], {offsetX: 10, offsetY: 10}); 130 }; 131 const helper = new CaptureStreamTestHelper2D(1, 1); 132 133 const doVerify = async (stream, [upleft, upright, downleft, downright]) => { 134 // Reset from potential earlier verification runs. 135 testVideo.srcObject = null; 136 const playback = new MediaStreamPlayback(testVideo, stream); 137 playback.startMedia(); 138 await playback.verifyPlaying(); 139 const settings = stream.getTracks()[0].getSettings(); 140 is(settings.width, testVideo.videoWidth, 141 "Width setting should match video width"); 142 is(settings.height, testVideo.videoHeight, 143 "Height setting should match video height"); 144 await SpecialPowers.wrap(canvas).requestFullscreen(); 145 try { 146 await verifyScreenshare(testVideo, helper, helper[upleft], helper[upright], 147 helper[downleft], helper[downright]); 148 } finally { 149 await playback.stopTracksForStreamInMediaPlayback(); 150 await SpecialPowers.wrap(document).exitFullscreen(); 151 // We wait a bit extra here to make sure we have completely left 152 // fullscreen when the --screenshot-on-fail screenshot is captured. 153 await wait(300); 154 } 155 }; 156 157 info("Testing screenshare without constraints"); 158 SpecialPowers.wrap(document).notifyUserGestureActivation(); 159 let stream = await getUserMedia({video: {mediaSource: "screen"}}); 160 let settings = stream.getTracks()[0].getSettings(); 161 ok(settings.width > 0 && settings.width <= 8192, 162 `Width setting ${settings.width} should be set after gUM`); 163 ok(settings.height > 0 && settings.height <= 8192, 164 `Height setting ${settings.height} should be set after gUM`); 165 let colors = ["red", "blue", "green", "white"]; 166 draw(colors); 167 await doVerify(stream, colors); 168 const screenWidth = testVideo.videoWidth; 169 const screenHeight = testVideo.videoHeight; 170 171 info("Testing screenshare with size and framerate constraints"); 172 SpecialPowers.wrap(document).notifyUserGestureActivation(); 173 for (const track of stream.getTracks()) { 174 track.stop(); 175 } 176 stream = await getUserMedia({ 177 video: { 178 mediaSource: 'screen', 179 width: { 180 min: '10', 181 max: '100' 182 }, 183 height: { 184 min: '10', 185 max: '100' 186 }, 187 frameRate: { 188 min: '10', 189 max: '15' 190 }, 191 }, 192 }); 193 settings = stream.getTracks()[0].getSettings(); 194 ok(settings.width >= 10 && settings.width <= 100, 195 `Width setting ${settings.width} should be correct after gUM`); 196 ok(settings.height >= 10 && settings.height <= 100, 197 `Height setting ${settings.height} should be correct after gUM`); 198 colors = ["green", "red", "white", "blue"]; 199 draw(colors); 200 const streamClone = stream.clone(); 201 await doVerify(streamClone, colors); 202 settings = stream.getTracks()[0].getSettings(); 203 ok(settings.width >= 10 && settings.width <= 100, 204 `Width setting ${settings.width} should be within constraints`); 205 ok(settings.height >= 10 && settings.height <= 100, 206 `Height setting ${settings.height} should be within constraints`); 207 is(settings.width, testVideo.videoWidth, 208 "Width setting should match video width"); 209 is(settings.height, testVideo.videoHeight, 210 "Height setting should match video height"); 211 let expectedHeight = (screenHeight * settings.width) / screenWidth; 212 ok(Math.abs(expectedHeight - settings.height) <= 1, 213 "Aspect ratio after constrained gUM should be close enough"); 214 215 info("Testing modifying screenshare with applyConstraints"); 216 testVideo.srcObject = stream; 217 testVideo.play(); 218 await new Promise(r => testVideo.onloadeddata = r); 219 const resize = haveEvent( 220 testVideo, "resize", wait(5000, new Error("Timeout waiting for resize"))); 221 await stream.getVideoTracks()[0].applyConstraints({ 222 mediaSource: 'screen', 223 width: 200, 224 height: 200, 225 frameRate: { 226 min: '5', 227 max: '10' 228 } 229 }); 230 // getSettings() should report correct size as soon as applyConstraints() 231 // resolves - bug 1453259. Until fixed, check that we at least report 232 // something sane. 233 const newSettings = stream.getTracks()[0].getSettings(); 234 ok(newSettings.width > settings.width && newSettings.width < screenWidth, 235 `Width setting ${newSettings.width} should have increased after applyConstraints`); 236 ok(newSettings.height > settings.height && newSettings.height < screenHeight, 237 `Height setting ${newSettings.height} should have increased after applyConstraints`); 238 await resize; 239 settings = stream.getTracks()[0].getSettings(); 240 ok(settings.width > 100 && settings.width < screenWidth, 241 `Width setting ${settings.width} should have increased after first frame after applyConstraints`); 242 ok(settings.height > 100 && settings.height < screenHeight, 243 `Height setting ${settings.height} should have increased after first frame after applyConstraints`); 244 is(settings.width, testVideo.videoWidth, 245 "Width setting should match video width"); 246 is(settings.height, testVideo.videoHeight, 247 "Height setting should match video height"); 248 expectedHeight = (screenHeight * settings.width) / screenWidth; 249 ok(Math.abs(expectedHeight - settings.height) <= 1, 250 "Aspect ratio after applying constraints should be close enough"); 251 colors = ["white", "green", "blue", "red"]; 252 draw(colors); 253 await doVerify(stream, colors); 254 for (const track of [...stream.getTracks(), ...streamClone.getTracks()]) { 255 track.stop(); 256 } 257 }); 258 </script> 259 </pre> 260 </body> 261 </html>