diff --git a/src/playground/motion.html b/src/playground/motion.html
new file mode 100644
index 000000000..c79deb0e9
--- /dev/null
+++ b/src/playground/motion.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>Video Motion Test Playground</title>
+</head>
+<body>
+    <!-- FPS counter, Syntax highlighter, Blocks, Renderer -->
+    <script src="./vendor.js"></script>
+    <!-- Storage module -->
+    <script src="./scratch-storage.js"></script>
+    <!-- Stage rendering -->
+    <script src="./scratch-render.js"></script>
+    <!-- Extension -->
+    <script src="./motion-extension.js"></script>
+    <!-- Motion -->
+    <script src="./motion.js"></script>
+</body>
+</html>
diff --git a/src/playground/motion.js b/src/playground/motion.js
new file mode 100644
index 000000000..8d801d755
--- /dev/null
+++ b/src/playground/motion.js
@@ -0,0 +1,118 @@
+(function () {
+    const video = document.createElement('video');
+    navigator.getUserMedia({
+        audio: false,
+        video: {
+            width: {min: 480, ideal: 640},
+            height: {min: 360, ideal: 480}
+        }
+    }, stream => {
+        video.autoplay = true;
+        video.src = window.URL.createObjectURL(stream);
+        // Get the track to hint to the browser the stream needs to be running
+        // even though we don't add the video tag to the DOM.
+        stream.getTracks();
+        video.addEventListener('play', () => {
+            video.width = video.videoWidth;
+            video.height = video.videoHeight;
+        });
+    }, err => {
+        /* eslint no-console:0 */
+        console.log(err);
+    });
+
+    const VideoMotion = window.Scratch3MotionDetect.VideoMotion;
+    const VideoMotionView = window.Scratch3MotionDetect.VideoMotionView;
+
+    // Create motion detector
+    const motion = new VideoMotion();
+
+    // Create debug views that will render different slices of how the detector
+    // uses the a frame of input.
+    const OUTPUT = VideoMotionView.OUTPUT;
+    const outputKeys = Object.keys(OUTPUT);
+    const outputValues = Object.values(OUTPUT);
+    const views = outputValues
+        .map(output => new VideoMotionView(motion, output));
+    const view = views[0];
+
+    const defaultViews = [OUTPUT.INPUT, OUTPUT.XY_CELL, OUTPUT.T_CELL, OUTPUT.UV];
+
+    const activators = document.createElement('div');
+    activators.style.userSelect = 'none';
+    outputValues.forEach((output, index) => {
+        const checkboxLabel = document.createElement('label');
+        const checkbox = document.createElement('input');
+        checkbox.type = 'checkbox';
+        checkbox.checked = defaultViews.indexOf(output) !== -1;
+        const checkboxSpan = document.createElement('span');
+        checkboxSpan.innerText = outputKeys[index];
+        checkboxLabel.appendChild(checkbox);
+        checkboxLabel.appendChild(checkboxSpan);
+
+        const _view = views[index];
+        _view.canvas.style.display = checkbox.checked ? '' : 'none';
+        _view.active = checkbox.checked;
+        checkbox.onchange = event => {
+            _view.canvas.style.display = checkbox.checked ? '' : 'none';
+            _view.active = checkbox.checked;
+            event.preventDefault();
+            return false;
+        };
+
+        activators.appendChild(checkboxLabel);
+    });
+    document.body.appendChild(activators);
+
+    // Add a text line to display milliseconds per frame, motion value, and
+    // motion direction
+    const textEl = document.createElement('div');
+    document.body.appendChild(textEl);
+    let textTimer = Date.now();
+
+    // Add the motion debug views to the dom after the text line, so the text
+    // appears first.
+    views.forEach(_view => document.body.appendChild(_view.canvas));
+
+    // Create a temporary canvas the video will be drawn to so the video's
+    // bitmap data can be transformed into a TypeArray.
+    const tempCanvas = document.createElement('canvas');
+    tempCanvas.width = view.canvas.width;
+    tempCanvas.height = view.canvas.height;
+    const ctx = tempCanvas.getContext('2d');
+
+    const loop = function () {
+        const timeoutId = setTimeout(loop, 33);
+
+        try {
+            // Get the bitmap data for the video frame
+            ctx.scale(-1, 1);
+            ctx.drawImage(
+                video,
+                0, 0, video.width || video.clientWidth, video.height || video.clientHeight,
+                -480, 0, tempCanvas.width, tempCanvas.height
+            );
+            ctx.resetTransform();
+            const data = ctx.getImageData(0, 0, tempCanvas.width, tempCanvas.height);
+
+            const b = performance.now();
+            motion.addFrame(data.data);
+            motion.analyzeFrame();
+            if (Date.now() - textTimer > 250) {
+                const e = performance.now();
+                const analyzeDuration = ((e - b) * 1000).toFixed(0);
+                const motionAmount = motion.motionAmount.toFixed(1);
+                const motionDirection = motion.motionDirection.toFixed(1);
+                textEl.innerText = `${analyzeDuration} :: ${motionAmount} :: ${motionDirection}`;
+                textTimer = Date.now();
+            }
+            views.forEach(_view => _view.active && _view.draw());
+        } catch (error) {
+            /* eslint no-console:0 */
+            console.error(error.stack || error);
+            clearTimeout(timeoutId);
+        }
+    };
+
+    loop();
+}());
diff --git a/webpack.config.js b/webpack.config.js
index 394b6e2a2..9580fc023 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -86,7 +86,8 @@ module.exports = [
                 'scratch-storage',
                 // Renderer
                 'scratch-render'
-            ]
+            ],
+            'motion-extension': './src/extensions/scratch3_video_sensing/debug'
         },
         output: {
             path: path.resolve(__dirname, 'playground'),
@@ -98,6 +99,10 @@ module.exports = [
                     test: require.resolve('./src/index.js'),
                     loader: 'expose-loader?VirtualMachine'
                 },
+                {
+                    test: require.resolve('./src/extensions/scratch3_video_sensing/debug.js'),
+                    loader: 'expose-loader?Scratch3MotionDetect'
+                },
                 {
                     test: require.resolve('stats.js/build/stats.min.js'),
                     loader: 'script-loader'