VideoTrackGenerator.worker.js (7399B)
1 // META: title=VideoTrackGenerator tests. 2 3 importScripts("/resources/testharness.js"); 4 5 function make_audio_data(timestamp, channels, sampleRate, frames) { 6 let data = new Float32Array(frames*channels); 7 8 // This generates samples in a planar format. 9 for (var channel = 0; channel < channels; channel++) { 10 let hz = 100 + channel * 50; // sound frequency 11 let base_index = channel * frames; 12 for (var i = 0; i < frames; i++) { 13 let t = (i / sampleRate) * hz * (Math.PI * 2); 14 data[base_index + i] = Math.sin(t); 15 } 16 } 17 18 return new AudioData({ 19 timestamp: timestamp, 20 data: data, 21 numberOfChannels: channels, 22 numberOfFrames: frames, 23 sampleRate: sampleRate, 24 format: "f32-planar", 25 }); 26 } 27 28 const pixelColour = [50, 100, 150, 255]; 29 const height = 240; 30 const width = 320; 31 function makeVideoFrame(timestamp) { 32 const canvas = new OffscreenCanvas(width, height); 33 34 const ctx = canvas.getContext('2d', {alpha: false}); 35 ctx.fillStyle = `rgba(${pixelColour.join()})`; 36 ctx.fillRect(0, 0, width, height); 37 38 return new VideoFrame(canvas, {timestamp, alpha: 'discard'}); 39 } 40 41 promise_test(async t => { 42 const videoFrame = makeVideoFrame(1); 43 const originalWidth = videoFrame.displayWidth; 44 const originalHeight = videoFrame.displayHeight; 45 const originalTimestamp = videoFrame.timestamp; 46 const generator = new VideoTrackGenerator(); 47 t.add_cleanup(() => generator.track.stop()); 48 49 // Use a MediaStreamTrackProcessor as a sink for |generator| to verify 50 // that |processor| actually forwards the frames written to its writable 51 // field. 52 const processor = new MediaStreamTrackProcessor(generator); 53 const reader = processor.readable.getReader(); 54 const readerPromise = new Promise(async resolve => { 55 const result = await reader.read(); 56 t.add_cleanup(() => result.value.close()); 57 t.step_func(() => { 58 assert_equals(result.value.displayWidth, originalWidth); 59 assert_equals(result.value.displayHeight, originalHeight); 60 assert_equals(result.value.timestamp, originalTimestamp); 61 })(); 62 resolve(); 63 }); 64 65 generator.writable.getWriter().write(videoFrame); 66 return readerPromise; 67 }, 'Tests that VideoTrackGenerator forwards frames to sink'); 68 69 promise_test(async t => { 70 const generator = new VideoTrackGenerator(); 71 t.add_cleanup(() => generator.track.stop()); 72 73 const writer = generator.writable.getWriter(); 74 const frame = makeVideoFrame(1); 75 await writer.write(frame); 76 77 assert_equals(generator.track.kind, "video"); 78 assert_equals(generator.track.readyState, "live"); 79 }, "Tests that creating a VideoTrackGenerator works as expected"); 80 81 promise_test(async t => { 82 const generator = new VideoTrackGenerator(); 83 t.add_cleanup(() => generator.track.stop()); 84 85 const writer = generator.writable.getWriter(); 86 const frame = makeVideoFrame(1); 87 await writer.write(frame); 88 89 assert_throws_dom("InvalidStateError", () => frame.clone(), "VideoFrame wasn't destroyed on write."); 90 }, "Tests that VideoFrames are destroyed on write"); 91 92 promise_test(async t => { 93 const generator = new VideoTrackGenerator(); 94 t.add_cleanup(() => generator.track.stop()); 95 96 const writer = generator.writable.getWriter(); 97 98 if (!self.AudioData) 99 return; 100 101 const defaultInit = { 102 timestamp: 1234, 103 channels: 2, 104 sampleRate: 8000, 105 frames: 100, 106 }; 107 const audioData = make_audio_data(defaultInit.timestamp, defaultInit.channels, defaultInit.sampleRate, 108 defaultInit.frames); 109 110 await promise_rejects_js(t, TypeError, writer.write("test")); 111 }, "Generator writer rejects on mismatched media input"); 112 113 promise_test(async t => { 114 const generator = new VideoTrackGenerator(); 115 t.add_cleanup(() => generator.track.stop()); 116 117 const writer = generator.writable.getWriter(); 118 await promise_rejects_js(t, TypeError, writer.write("potatoe")); 119 }, "Generator writer rejects on non media input"); 120 121 promise_test(async t => { 122 const generator = new VideoTrackGenerator(); 123 124 const writer = generator.writable.getWriter(); 125 const frame1 = makeVideoFrame(1); 126 t.add_cleanup(() => frame1.close()); 127 await writer.write(frame1); 128 assert_equals(frame1.codedWidth, 0); 129 130 generator.track.stop(); 131 132 await writer.closed; 133 134 const frame2 = makeVideoFrame(1); 135 t.add_cleanup(() => frame2.close()); 136 await promise_rejects_js(t, TypeError, writer.write(frame2)); 137 138 assert_equals(frame2.codedWidth, 320); 139 }, "A writer rejects when generator's track is stopped"); 140 141 promise_test(async t => { 142 const generator = new VideoTrackGenerator(); 143 generator.muted = true; 144 145 const writer = generator.writable.getWriter(); 146 const frame1 = makeVideoFrame(1); 147 t.add_cleanup(() => frame1.close()); 148 await writer.write(frame1); 149 assert_equals(frame1.codedWidth, 0); 150 151 generator.track.stop(); 152 153 await writer.closed; 154 155 const frame2 = makeVideoFrame(1); 156 t.add_cleanup(() => frame2.close()); 157 await promise_rejects_js(t, TypeError, writer.write(frame2)); 158 159 assert_equals(frame2.codedWidth, 320); 160 }, "A muted writer rejects when generator's track is stopped"); 161 162 promise_test(async t => { 163 const generator = new VideoTrackGenerator(); 164 165 const writer = generator.writable.getWriter(); 166 const frame1 = makeVideoFrame(1); 167 t.add_cleanup(() => frame1.close()); 168 await writer.write(frame1); 169 assert_equals(frame1.codedWidth, 0); 170 171 const clonedTrack = generator.track.clone(); 172 generator.track.stop(); 173 174 await new Promise(resolve => t.step_timeout(resolve, 100)); 175 176 const frame2 = makeVideoFrame(1); 177 t.add_cleanup(() => frame2.close()); 178 await writer.write(frame2); 179 assert_equals(frame2.codedWidth, 0); 180 181 clonedTrack.stop(); 182 183 await writer.closed; 184 185 const frame3 = makeVideoFrame(1); 186 t.add_cleanup(() => frame3.close()); 187 await promise_rejects_js(t, TypeError, writer.write(frame3)); 188 189 assert_equals(frame3.codedWidth, 320); 190 }, "A writer rejects when generator's track and clones are stopped"); 191 192 promise_test(async t => { 193 const generator = new VideoTrackGenerator(); 194 t.add_cleanup(() => generator.track.stop()); 195 196 // Use a MediaStreamTrackProcessor as a sink for |generator| to verify 197 // that |processor| actually forwards the frames written to its writable 198 // field. 199 const processor = new MediaStreamTrackProcessor(generator); 200 const reader = processor.readable.getReader(); 201 const videoFrame = makeVideoFrame(1); 202 203 const writer = generator.writable.getWriter(); 204 const videoFrame1 = makeVideoFrame(1); 205 writer.write(videoFrame1); 206 const result1 = await reader.read(); 207 t.add_cleanup(() => result1.value.close()); 208 assert_equals(result1.value.timestamp, 1); 209 generator.muted = true; 210 211 // This frame is expected to be discarded. 212 const videoFrame2 = makeVideoFrame(2); 213 writer.write(videoFrame2); 214 generator.muted = false; 215 216 const videoFrame3 = makeVideoFrame(3); 217 writer.write(videoFrame3); 218 const result3 = await reader.read(); 219 t.add_cleanup(() => result3.value.close()); 220 assert_equals(result3.value.timestamp, 3); 221 222 // Set up a read ahead of time, then mute, enqueue and unmute. 223 const promise5 = reader.read(); 224 generator.muted = true; 225 writer.write(makeVideoFrame(4)); // Expected to be discarded. 226 generator.muted = false; 227 writer.write(makeVideoFrame(5)); 228 const result5 = await promise5; 229 t.add_cleanup(() => result5.value.close()); 230 assert_equals(result5.value.timestamp, 5); 231 }, 'Tests that VideoTrackGenerator forwards frames only when unmuted'); 232 233 done();