1674892.html (4638B)
1 <!DOCTYPE html> 2 <!-- 3 Verifies cross-graph ports are cleaned up when a MediaStreamTrack connected 4 to MediaStreamAudioSourceNode is stopped. 5 --> 6 <html class="reftest-wait"> 7 <head> 8 <title>Test bug 1674892 - MediaStreamAudioSourceNode with getUserMedia and different sample rate</title> 9 <script> 10 document.addEventListener("DOMContentLoaded", async () => { 11 const iframe = document.createElement("iframe"); 12 iframe.srcdoc = ` 13 <!DOCTYPE html> 14 <html> 15 <head> 16 <script> 17 let SpecialStream = SpecialPowers.wrap(MediaStream); 18 let ctx, stream, sourceNode, analyser, dataArray; 19 20 window.addEventListener("message", async (event) => { 21 if (event.data === "startAudio") { 22 ctx = new AudioContext({ sampleRate: 32000 }); 23 24 stream = await navigator.mediaDevices.getUserMedia({ 25 audio: true, 26 fake: true 27 }); 28 29 sourceNode = ctx.createMediaStreamSource(stream); 30 sourceNode.connect(ctx.destination); 31 32 analyser = ctx.createAnalyser(); 33 analyser.fftSize = 256; 34 dataArray = new Uint8Array(analyser.frequencyBinCount); 35 sourceNode.connect(analyser); 36 37 while ((analyser.getByteFrequencyData(dataArray), dataArray.every(v => v == 0))) { 38 await new Promise(r => requestAnimationFrame(r)); 39 } 40 41 const count = await SpecialStream.countUnderlyingStreams(); 42 parent.postMessage({ type: "ready", value: count }, "*"); 43 } else if (event.data === "destroy") { 44 // Clean up all resources 45 if (stream) { 46 // Stopping the MediaStreamTrack breaks connection to 47 // CrossGraphTransmitter, which can cause issues if the 48 // transmitter attempts to read from that connection during 49 // ::ProcessInput before the CrossGraph pair is fully torn 50 // down. 51 stream.getTracks().forEach(track => track.stop()); 52 stream = null; 53 } 54 if (analyser) { 55 analyser.disconnect(); 56 analyser = null; 57 } 58 if (sourceNode) { 59 sourceNode.disconnect(); 60 sourceNode = null; 61 } 62 if (ctx) { 63 await ctx.close(); 64 ctx = null; 65 } 66 dataArray = null; 67 68 // Force garbage collection 69 await new Promise(r => SpecialPowers.exactGC(r)); 70 71 const count = await SpecialStream.countUnderlyingStreams(); 72 parent.postMessage({ type: "destroyed", value: count }, "*"); 73 } 74 }); 75 76 // Notify parent that iframe is ready with initial stream count 77 (async () => { 78 const count = await SpecialStream.countUnderlyingStreams(); 79 parent.postMessage({ type: "init", value: count }, "*"); 80 })(); 81 <\/script> 82 </head> 83 <body></body> 84 </html> 85 `; 86 87 document.body.appendChild(iframe); 88 89 // Helper to wait for iframe messages 90 const waitForMessage = (type) => { 91 return new Promise(resolve => { 92 const handler = (event) => { 93 if (event.data.type === type) { 94 window.removeEventListener("message", handler); 95 resolve(event.data.value); 96 } 97 }; 98 window.addEventListener("message", handler); 99 }); 100 }; 101 102 // Wait for iframe to initialize and get initial stream count 103 const initialCount = await waitForMessage("init"); 104 dump(`Initial stream count: ${initialCount}\n`); 105 106 // Start audio and wait for it to be ready 107 iframe.contentWindow.postMessage("startAudio", "*"); 108 const readyCount = await waitForMessage("ready"); 109 dump(`Stream count when audio ready: ${readyCount}\n`); 110 111 // Destroy all resources in the iframe while MediaStream is active 112 iframe.contentWindow.postMessage("destroy", "*"); 113 const finalCount = await waitForMessage("destroyed"); 114 dump(`Final stream count: ${finalCount}\n`); 115 116 // Check if streams were cleaned up 117 if (finalCount === initialCount) { 118 document.documentElement.removeAttribute("class"); 119 } else { 120 dump("Streams were not cleaned up properly.\n"); 121 } 122 }); 123 </script> 124 </head> 125 <body></body> 126 </html>