Advertisement
here2share

particle_life.html

Jun 24th, 2024
583
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. <html>
  2. <title>Life</title>
  3.  
  4. <head>
  5.  
  6.     <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7.  
  8.     <style>
  9.         :root {
  10.             color-scheme: dark;
  11.             --canvas-bg-color: rgba(0, 0, 0, 0); /* transparent */
  12.         }
  13.  
  14.         body {
  15.             display: flex;
  16.             justify-content: center;
  17.             align-items: center;
  18.         }
  19.  
  20.         #canvas {
  21.             border: #504e52;
  22.             border-style: dashed;
  23.             border-width: 5;
  24.             outline: none;  /* because of the 'tabindex' focus trick */
  25.             background-color: var(--canvas-bg-color);
  26.         }
  27.     </style>
  28. </head>
  29.  
  30. <body>
  31.     <!-- 'tabindex' is a trick to make the canvas capture key events -->
  32.     <canvas id="canvas" tabindex="1"></canvas>
  33.  
  34.     <script src="https://cdn.jsdelivr.net/npm/lil-gui@0.17"></script>
  35.  
  36.     <script>
  37.         const maxRadius = 200;
  38.         const maxClusters = 20;
  39.         const minClusterSize = 50;
  40.         const predefinedColors = ['green', 'red', 'orange', 'cyan', 'magenta', 'lavender', 'teal'];
  41.         const settings = {
  42.             seed: 91651088029,
  43.             fps: 0,
  44.             atoms: {
  45.                 count: 500,  // Per Color
  46.                 radius: 1,
  47.             },
  48.             drawings: {  // Drawing options can be expensive on performance
  49.                 lines: false,   // draw lines between atoms that arr effecting each other
  50.                 circle: false,  // draw atoms as circles
  51.                 clusters: false,
  52.                 background_color: '#000000', // Background color
  53.             },
  54.             export: {
  55.                 // Export a Screenshot image
  56.                 image: () => {
  57.                     const imageDataURL = canvas.toDataURL({
  58.                         format: 'png',
  59.                         quality: 1
  60.                     });
  61.                     dataURL_downloader(imageDataURL);
  62.                 },
  63.                 // Export a video recording
  64.                 video: () => {
  65.                     mediaRecorder.state == 'recording' ? mediaRecorder.stop() : mediaRecorder.start();
  66.                 },
  67.             },
  68.             explore: false,
  69.             explorePeriod: 100,
  70.             rules: {},
  71.             rulesArray: [],
  72.             radii: {},
  73.             radii2Array: [],
  74.             colors: [],
  75.             numColors: 4,
  76.             time_scale: 1.0,
  77.             viscosity: 0.7,  // speed-dampening (can be >1 !)
  78.             gravity: 0.0,  // pulling downward
  79.             pulseDuration: 10,
  80.             wallRepel: 40,
  81.             reset: () => {
  82.                 randomAtoms(settings.atoms.count, true)
  83.             },
  84.             randomRules: () => {
  85.                 settings.seed = local_seed   // last used seed is the new starting seed
  86.                 startRandom()
  87.             },
  88.             symmetricRules: () => {
  89.                 symmetricRules()
  90.                 randomAtoms(settings.atoms.count, true)
  91.                 updateGUIDisplay()
  92.             },
  93.             gui: null,
  94.         }
  95.  
  96.         const setupClicks = () => {
  97.             canvas.addEventListener('click',
  98.                 (e) => {
  99.                     pulse = settings.pulseDuration;
  100.                     if (e.shiftKey) pulse = -pulse;
  101.                     pulse_x = e.clientX;
  102.                     pulse_y = e.clientY;
  103.                 }
  104.             )
  105.         }
  106.         const setupKeys = () => {
  107.             canvas.addEventListener('keydown',
  108.                 function (e) {
  109.                     console.log(e.key)
  110.                     switch (e.key) {
  111.                         case 'r':
  112.                           settings.randomRules()
  113.                         break;
  114.                         case 't':
  115.                           settings.drawings.clusters = !settings.drawings.clusters
  116.                         break;
  117.                         case 'o':
  118.                           settings.reset()
  119.                         break;
  120.                         case 's':
  121.                           settings.symmetricRules()
  122.                         break;
  123.                         default:
  124.                           console.log(e.key)
  125.                     }
  126.                 })
  127.         }
  128.         const updateGUIDisplay = () => {
  129.             console.log('gui', settings.gui)
  130.             settings.gui.destroy()
  131.             setupGUI()
  132.         }
  133.         Object.defineProperty(String.prototype, 'capitalise', {
  134.             value: function() {
  135.                 return this.charAt(0).toUpperCase() + this.slice(1);
  136.             },
  137.             enumerable: false
  138.         })
  139.  
  140.         // Build GUI
  141.         const setupGUI = () => {
  142.             settings.gui = new lil.GUI()
  143.             // Configs
  144.             const configFolder = settings.gui.addFolder('Config')
  145.             configFolder.add(settings, 'reset').name('Reset')
  146.             configFolder.add(settings, 'randomRules').name('Random Rules')
  147.             configFolder.add(settings, 'symmetricRules').name('Symmetric Rules')
  148.             configFolder.add(settings, 'numColors', 1, 7, 1).name('Number of Colors')
  149.                 .listen().onFinishChange(v => {
  150.                     setNumberOfColors();
  151.                     startRandom();
  152.                 })
  153.             configFolder.add(settings, 'seed').name('Seed').listen().onFinishChange(v => {
  154.                 startRandom();
  155.             })
  156.             configFolder.add(settings, 'fps').name('FPS - (Live)').listen().disable()
  157.             configFolder.add(settings.atoms, 'count', 1, 1000, 1).name('Atoms per-color').listen().onFinishChange(v => {
  158.                 randomAtoms(v, true)
  159.             })
  160.             configFolder.add(settings, 'time_scale', 0.1, 5, 0.01).name('Time Scale').listen()
  161.             configFolder.add(settings, 'viscosity', 0.1, 2, 0.1).name('Viscosity').listen()
  162.  
  163.             configFolder.add(settings, 'gravity', 0., 1., 0.05).name('Gravity').listen()
  164.             configFolder.add(settings, 'pulseDuration', 1, 100, 1).name('Click Pulse Duration').listen()
  165.  
  166.             configFolder.add(settings, 'wallRepel', 0, 100, 1).name('Wall Repel').listen()
  167.             configFolder.add(settings, 'explore').name('Random Exploration').listen()
  168.             // Drawings
  169.             const drawingsFolder = settings.gui.addFolder('Drawings')
  170.             drawingsFolder.add(settings.atoms, 'radius', 1, 10, 0.5).name('Radius').listen()
  171.             drawingsFolder.add(settings.drawings, 'circle').name('Circle Shape').listen()
  172.             drawingsFolder.add(settings.drawings, 'clusters').name('Track Clusters').listen()
  173.             drawingsFolder.add(settings.drawings, 'lines').name('Draw Lines').listen()
  174.             drawingsFolder.addColor(settings.drawings, 'background_color').name('Background Color').listen()
  175.             // Export
  176.             const exportFolder = settings.gui.addFolder('Export')
  177.             exportFolder.add(settings.export, 'image').name('Image')
  178.             exportFolder.add(settings.export, 'video').name('Video: Start / stop')
  179.             // Colors
  180.             for (const atomColor of settings.colors) {
  181.                 const colorFolder =
  182.                     settings.gui.addFolder(`Rules: <font color=\'${atomColor}\'>${atomColor.capitalise()}</font>`)
  183.                for (const ruleColor of settings.colors) {
  184.                    colorFolder.add(settings.rules[atomColor], ruleColor, -1, 1, 0.001)
  185.                         .name(`<-> <font color=\'${ruleColor}\'>${ruleColor.capitalise()}</font>`)
  186.                         .listen().onFinishChange(v => { flattenRules() }
  187.                    )
  188.                }
  189.                colorFolder.add(settings.radii, atomColor, 1, maxRadius, 5).name('Radius')
  190.                    .listen().onFinishChange(v => { flattenRules() }
  191.                )
  192.            }
  193.  
  194.  
  195.        }
  196.  
  197.  
  198.        // Seedable 'decent' random generator
  199.        var local_seed = settings.seed;
  200.        function mulberry32() {
  201.            let t = local_seed += 0x6D2B79F5;
  202.            t = Math.imul(t ^ t >>> 15, t | 1);
  203.            t ^= t + Math.imul(t ^ t >>> 7, t | 61);
  204.            return ((t ^ t >>> 14) >>> 0) / 4294967296.;
  205.        }
  206.  
  207.        function loadSeedFromUrl() {
  208.            let hash = window.location.hash;
  209.            if (hash != undefined && hash[0] == '#') {
  210.                let param = Number(hash.substr(1)); // remove the leading '#'
  211.                if (isFinite(param)) {
  212.                    settings.seed = param;
  213.                    console.log("Using seed " + settings.seed);
  214.                }
  215.            }
  216.        }
  217.  
  218.        function randomRules() {
  219.            if (!isFinite(settings.seed)) settings.seed = 0xcafecafe;
  220.            window.location.hash = "#" + settings.seed;
  221.            document.title = "Life #" + settings.seed;
  222.            local_seed = settings.seed;
  223.            console.log("Seed=" + local_seed);
  224.            for (const i of settings.colors) {
  225.                settings.rules[i] = {};
  226.                for (const j of settings.colors) {
  227.                    settings.rules[i][j] = mulberry32() * 2 - 1;
  228.                }
  229.                settings.radii[i] = 80;
  230.            }
  231.            console.log(JSON.stringify(settings.rules));
  232.            flattenRules()
  233.        }
  234.  
  235.        function symmetricRules() {
  236.            for (const i of settings.colors) {
  237.                for (const j of settings.colors) {
  238.                    if (j < i) {
  239.                        let v = 0.5 * (settings.rules[i][j] + settings.rules[j][i]);
  240.                        settings.rules[i][j] = settings.rules[j][i] = v;
  241.                    }
  242.                }
  243.            }
  244.            console.log(JSON.stringify(settings.rules));
  245.            flattenRules()
  246.        }
  247.  
  248.        function flattenRules() {
  249.            settings.rulesArray = []
  250.            settings.radii2Array = []
  251.            for (const c1 of settings.colors) {
  252.                for (const c2 of settings.colors) {
  253.                    settings.rulesArray.push(settings.rules[c1][c2])
  254.                }
  255.                settings.radii2Array.push(settings.radii[c1] * settings.radii[c1])
  256.            }
  257.        }
  258.  
  259.        function updateCanvasDimensions() {
  260.            canvas.width = window.innerWidth * 0.9;
  261.            canvas.height = window.innerHeight * 0.9;
  262.        }
  263.  
  264.        // Initiate Random locations for Atoms ( used when atoms created )
  265.        function randomX() {
  266.            return mulberry32() * (canvas.width - 100) + 50;
  267.        };
  268.  
  269.        function randomY() {
  270.            return mulberry32() * (canvas.height - 100) + 50;
  271.        };
  272.  
  273.        /* Create an Atom - Use matrices for x4/5 performance improvement
  274.        atom[0] = x
  275.        atom[1] = y
  276.        atom[2] = ax
  277.        atom[3] = ay
  278.        atom[4] = color (index)
  279.        */
  280.        const create = (number, color) => {
  281.            for (let i = 0; i < number; i++) {
  282.                atoms.push([randomX(), randomY(), 0, 0, color])
  283.            }
  284.        };
  285.  
  286.        function randomAtoms(number_of_atoms_per_color, clear_previous) {
  287.            if (clear_previous) atoms.length = 0;
  288.            for (let c = 0; c < settings.colors.length; c++) {
  289.                create(number_of_atoms_per_color, c)
  290.            }
  291.            clusters.length = 0;
  292.        }
  293.  
  294.        function startRandom() {
  295.            randomRules();
  296.            randomAtoms(settings.atoms.count, true);
  297.            updateGUIDisplay()
  298.        }
  299.  
  300.        function setNumberOfColors() {
  301.            settings.colors = [];
  302.            for (let i = 0; i < settings.numColors; ++i) {
  303.                settings.colors.push(predefinedColors[i]);
  304.            }
  305.        }
  306.  
  307.        // Run Application
  308.        loadSeedFromUrl()
  309.  
  310.        // Canvas
  311.        const canvas = document.getElementById('canvas');
  312.        const m = canvas.getContext("2d");
  313.        // Draw a square
  314.        const drawSquare = (x, y, color, radius) => {
  315.            m.fillStyle = color;
  316.            m.fillRect(x - radius, y - radius, 2 * radius, 2 * radius);
  317.        }
  318.  
  319.        // Draw a circle
  320.        function drawCircle(x, y, color, radius, fill = true) {
  321.            m.beginPath();
  322.            m.arc(x, y, radius, 0 * Math.PI, 2 * Math.PI);  // x, y, radius, ArcStart, ArcEnd
  323.            m.closePath();
  324.            m.strokeStyle = m.fillStyle = color;
  325.            fill ? m.fill() : m.stroke()
  326.        };
  327.  
  328.        // Draw a line between two atoms
  329.        function drawLineBetweenAtoms(ax, ay, bx, by, color) {
  330.            m.beginPath();
  331.            m.moveTo(ax, ay);
  332.            m.lineTo(bx, by);
  333.            m.closePath();
  334.            m.strokeStyle = color;
  335.            m.stroke();
  336.        };
  337.  
  338.        // [position-x, position-y, radius, color]
  339.        //    /* tmp accumulators: */
  340.        //  {count, accum-x, accum-y, accum-d^2, accum-color}]
  341.        let clusters = [];
  342.        function newCluster() {
  343.            return [randomX(), randomY(), maxRadius, 'white'];
  344.        }
  345.        function addNewClusters(num_clusters) {
  346.            if (clusters.length < num_clusters / 2) {
  347.                while (clusters.length < num_clusters) clusters.push(newCluster());
  348.            }
  349.        }
  350.        function findNearestCluster(x, y) {
  351.            let best = -1;
  352.            let best_d2 = 1.e38;
  353.            for (let i = 0; i < clusters.length; ++i) {
  354.                const c = clusters[i];
  355.                const dx = c[0] - x;
  356.                const dy = c[1] - y;
  357.                const d2 = dx * dx + dy * dy;
  358.                if (d2 < best_d2) {
  359.                    best = i;
  360.                    best_d2 = d2;
  361.                }
  362.            }
  363.            return [best, best_d2];
  364.        }
  365.        function moveClusters(accums) {
  366.            let max_d = 0.;   // record max cluster displacement
  367.            for (let i = 0; i < clusters.length; ++i) {
  368.                let c = clusters[i];
  369.                const a = accums[i];
  370.                if (a[0] > minClusterSize) {
  371.                    const norm = 1. / a[0];
  372.                    const new_x = a[1] * norm;
  373.                    const new_y = a[2] * norm;
  374.                    max_d = Math.max(max_d, Math.abs(c[0] - new_x), Math.abs(c[1 ] - new_y));
  375.                    c[0] = new_x;
  376.                    c[1] = new_y;
  377.                }
  378.            }
  379.            return max_d;
  380.        }
  381.        function finalizeClusters(accums) {
  382.            for (let i = 0; i < clusters.length; ++i) {
  383.                let c = clusters[i];
  384.                const a = accums[i];
  385.                if (a[0] > minClusterSize) {
  386.                    const norm = 1. / a[0];
  387.                    const new_r = 1.10 * Math.sqrt(a[3] * norm);  // with 10% extra room
  388.                    c[2] = 0.95 * c[2] + 0.05 * new_r;  // exponential smoothing
  389.                    // 'average' color
  390.                    c[3] = settings.colors[Math.floor(a[4] * norm + .5)];
  391.                } else {
  392.                    c[2] = 0.;   // disable the weak cluster
  393.                }
  394.            }
  395.            // note: if half of the particles are not within the average
  396.            // radius of the cluster, we should probably split it in two
  397.            // along the main axis!
  398.        }
  399.        function trackClusters() {
  400.            addNewClusters(maxClusters);
  401.            let accums = [];
  402.            for (const c of clusters) accums.push([0, 0., 0., 0., 0]);
  403.            const maxKMeanPasses = 10;
  404.            for (let pass = maxKMeanPasses; pass >= 0; --pass) {
  405.                for (let a of accums) a = [0, 0., 0., 0., 0];
  406.                for (const c of atoms) {
  407.                    const [best, best_d2] = findNearestCluster(c[0], c[1]);
  408.                    if (best >= 0 && best_d2 < maxRadius * maxRadius) {
  409.                        accums[best][0] += 1;
  410.                        accums[best][1] += c[0];
  411.                        accums[best][2] += c[1];
  412.                        accums[best][3] += best_d2;
  413.                        accums[best][4] += c[4];
  414.                    }
  415.                }
  416.                const max_d = moveClusters(accums);
  417.                if (max_d < 1.) break;
  418.            }
  419.            finalizeClusters(accums);
  420.        }
  421.        function drawClusters() {
  422.            let i = 0;
  423.            while (i < clusters.length) {
  424.                let c = clusters[i];
  425.                if (c[2] > 0.) {
  426.                    drawCircle(c[0], c[1], c[3], c[2], false);
  427.                    ++i;
  428.                } else {
  429.                    // remove cluster by swapping with last
  430.                    const last = clusters.pop();
  431.                    if (i < clusters.length) clusters[i] = last;
  432.                }
  433.            }
  434.        }
  435.  
  436.        // Canvas Dimensions
  437.        updateCanvasDimensions()
  438.  
  439.  
  440.        // Params for click-based pulse event
  441.        var pulse = 0;
  442.        var pulse_x = 0,
  443.            pulse_y = 0;
  444.  
  445.        var exploration_timer = 0;
  446.        function exploreParameters() {
  447.            if (exploration_timer <= 0) {
  448.                let c1 = settings.colors[Math.floor(mulberry32() * settings.numColors)];
  449.                if (mulberry32() >= 0.2) {  // 80% of the time, we change the strength
  450.                  let c2 = settings.colors[Math.floor(mulberry32() * settings.numColors)];
  451.                  let new_strength = mulberry32();
  452.                  // for better results, we force opposite-signed values
  453.                  if (settings.rules[c1][c2] > 0) new_strength = -new_strength;
  454.                  settings.rules[c1][c2] = new_strength;
  455.                } else {  // ...otherwise, the radius
  456.                  settings.radii[c1] = 1 + Math.floor(mulberry32() * maxRadius);
  457.                }
  458.                flattenRules();
  459.                exploration_timer = settings.explorePeriod;
  460.            }
  461.            exploration_timer -= 1;
  462.        }
  463.  
  464.        var total_v; // global velocity as a estimate of on-screen activity
  465.  
  466.        // Apply Rules ( How atoms interact with each other )
  467.        const applyRules = () => {
  468.            total_v = 0.;
  469.            // update velocity first
  470.            for (const a of atoms) {
  471.                let fx = 0;
  472.                let fy = 0;
  473.                const idx = a[4] * settings.numColors;
  474.                const r2 = settings.radii2Array[a[4]]
  475.                for (const b of atoms) {
  476.                    const g = settings.rulesArray[idx + b[4]];
  477.                    const dx = a[0] - b[0];
  478.                    const dy = a[1] - b[1];
  479.                    if (dx !== 0 || dy !== 0) {
  480.                        const d = dx * dx + dy * dy;
  481.                        if (d < r2) {
  482.                            const F = g / Math.sqrt(d);
  483.                            fx += F * dx;
  484.                            fy += F * dy;
  485.  
  486.                            // Draw lines between atoms that are effecting each other.
  487.                            if (settings.drawings.lines) {
  488.                                drawLineBetweenAtoms(a[0], a[1], b[0], b[1], settings.colors[b[4]]);
  489.                            }
  490.                        }
  491.                    }
  492.                }
  493.                if (pulse != 0) {
  494.                    const dx = a[0] - pulse_x;
  495.                    const dy = a[1] - pulse_y;
  496.                    const d = dx * dx + dy * dy;
  497.                    if (d > 0) {
  498.                        const F = 100. * pulse / (d * settings.time_scale);
  499.                        fx += F * dx;
  500.                        fy += F * dy;
  501.                    }
  502.                }
  503.                if (settings.wallRepel > 0) {
  504.                  const d = settings.wallRepel
  505.                  const strength = 0.1
  506.                  if (a[0] <                d) fx += (d -                a[0]) * strength
  507.                  if (a[0] > canvas.width - d) fx += (canvas.width - d - a[0]) * strength
  508.                  if (a[1] <                 d) fy += (d                 - a[1]) * strength
  509.                  if (a[1] > canvas.height - d) fy += (canvas.height - d - a[1]) * strength
  510.                }
  511.                fy += settings.gravity;
  512.                const vmix = (1. - settings.viscosity);
  513.                a[2] = a[2] * vmix + fx * settings.time_scale;
  514.                a[3] = a[3] * vmix + fy * settings.time_scale;
  515.                // record typical activity, so that we can scale the
  516.                // time_scale later accordingly
  517.                total_v += Math.abs(a[2]);
  518.                total_v += Math.abs(a[3]);
  519.            }
  520.            // update positions now
  521.            for (const a of atoms) {
  522.                a[0] += a[2]
  523.                a[1] += a[3]
  524.  
  525.                // When Atoms touch or bypass canvas borders
  526.                if (a[0] < 0) {
  527.                    a[0] = -a[0];
  528.                    a[2] *= -1;
  529.                }
  530.                if (a[0] >= canvas.width) {
  531.                    a[0] = 2 * canvas.width - a[0];
  532.                    a[2] *= -1;
  533.                }
  534.                if (a[1] < 0) {
  535.                    a[1] = -a[1];
  536.                    a[3] *= -1;
  537.                }
  538.                if (a[1] >= canvas.height) {
  539.                    a[1] = 2 * canvas.height - a[1];
  540.                    a[3] *= -1;
  541.                }
  542.  
  543.            }
  544.            total_v /= atoms.length;
  545.        };
  546.  
  547.  
  548.        // Generate Rules
  549.        setNumberOfColors()
  550.        randomRules()
  551.  
  552.        // Generate Atoms
  553.        const atoms = []
  554.        randomAtoms(settings.atoms.count, true)
  555.  
  556.  
  557.        setupClicks()
  558.        setupKeys()
  559.        setupGUI()
  560.  
  561.        console.log('settings', settings)
  562.  
  563.        // Update Frames
  564.        var lastT = Date.now();
  565.        update();
  566.  
  567.        function update() {
  568.            // Update Canvas Dimensions - if screen size changed
  569.            updateCanvasDimensions()
  570.            // Background color
  571.            m.fillStyle = settings.drawings.background_color;
  572.            m.fillRect(0, 0, canvas.width, canvas.height);
  573.            // Appy Rules
  574.            applyRules();
  575.            // Draw Atoms
  576.            for (const a of atoms) {
  577.                if (settings.drawings.circle) {
  578.                    drawCircle(a[0], a[1], settings.colors[a[4]], settings.atoms.radius);
  579.                } else {
  580.                    drawSquare(a[0], a[1], settings.colors[a[4]], settings.atoms.radius);
  581.                }
  582.            }
  583.            if (settings.drawings.clusters) {
  584.                trackClusters();
  585.                drawClusters();
  586.            }
  587.  
  588.            updateParams();
  589.  
  590.            // const inRange = (a) => 0 <= a[0] && a[0] < canvas.width && 0 <= a[1] && a[1] < canvas.height
  591.            // console.log('inRange', atoms.filter(inRange).length)
  592.  
  593.            requestAnimationFrame(update);
  594.        };
  595.  
  596.        // post-frame stats and updates
  597.        function updateParams() {
  598.            // record FPS
  599.            var curT = Date.now();
  600.            if (curT > lastT) {
  601.                const new_fps = 1000. / (curT - lastT);
  602.                settings.fps = Math.round(settings.fps * 0.8 + new_fps * 0.2)
  603.                lastT = curT;
  604.            }
  605.  
  606.            // adapt time_scale based on activity
  607.            if (total_v > 30. && settings.time_scale > 5.) settings.time_scale /= 1.1;
  608.            if (settings.time_scale < 0.9) settings.time_scale *= 1.01;
  609.            if (settings.time_scale > 1.1) settings.time_scale /= 1.01;
  610.  
  611.            if (pulse != 0) pulse -= (pulse > 0) ? 1 : -1;
  612.            if (settings.explore) exploreParameters();
  613.        }
  614.  
  615.  
  616.        
  617.        // Download DataURL
  618.        function dataURL_downloader(dataURL, name = `particle_life_${settings.seed}`) {
  619.            const hyperlink = document.createElement("a");
  620.            // document.body.appendChild(hyperlink);
  621.            hyperlink.download = name;
  622.            hyperlink.target = '_blank';
  623.            hyperlink.href = dataURL;
  624.            hyperlink.click();
  625.            hyperlink.remove();
  626.        };
  627.  
  628.  
  629.        // Recorde a video ----------------------------------
  630.        // Stream
  631.        const videoStream = canvas.captureStream();
  632.        // Video Recorder
  633.        const mediaRecorder = new MediaRecorder(videoStream);
  634.        // temp chunks
  635.        let chunks = [];
  636.        // Store chanks
  637.        mediaRecorder.ondataavailable = function (e) {
  638.            chunks.push(e.data);
  639.        };
  640.        // Download video after recording is stopped
  641.        mediaRecorder.onstop = function (e) {
  642.            // Chunks ---> Blob
  643.            const blob = new Blob(chunks, { 'type': 'video/mp4' });
  644.            // Blob -----> DataURL
  645.            const videoDataURL = URL.createObjectURL(blob);
  646.  
  647.            // Download video
  648.            dataURL_downloader(videoDataURL);
  649.  
  650.            // Reset Chunks
  651.            chunks = [];
  652.        };
  653.        
  654.        // mediaRecorder.start(); // Start recording
  655.        // mediaRecorder.stop(); // Stop recording
  656.        // --------------------------------------------------
  657.    </script>
  658.  
  659. </body>
  660.  
  661. </html>
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement