3

with this code I managed to make a video. I am not a very expert on the subject, but I would think that my problem occurs because I do not know how many frames I should use. for example I want my video to last 15 seconds (I don't know how to set it, I don't know how many frames I should indicate)

    var queue = d3.queue(1);

    d3.range(240).forEach(function (frame) {
        queue.defer(drawFrame, frame / 240);
    });

    queue.awaitAll(function (err, frames) {
        recorder.start();
        drawFrame();

        function drawFrame() {
            if (frames.length) {
                context.drawImage(frames.shift(), 0, 0, width, height);
                requestAnimationFrame(drawFrame);
            } else {
                recorder.stop();
            }
        }
    });

how can I improve my code to get a smooth video?

const progressbar = document.getElementById('progressbar');

d3.select("#visualization").append('svg');
const vis = d3.select("svg").attr("width", 800).attr("height", 150).attr("xmlns", "http://www.w3.org/2000/svg").style("border", "1px solid red").style("fill", "white").style("background", "white");

const rectangle = vis.append("rect")
const circle = vis.append("circle");

let second = 0;
let interval;

const rectValues = {
  "delay": 0000,
  "duration": 5000,
  "begin": {
    "x": 0,
    "y": 0,
    "height": 70,
    "width": 100,
    "opacity": 1,
    "fill": "red"
  },
  "destiny": {
    "x": 250,
    "y": 1,
    "height": 100,
    "width": 120,
    "opacity": 0.8,
    "fill": "green"
  }
};

const circleValues = {
  "delay": 4000,
  "duration": 3000,
  "begin": {
    "cx": 250,
    "r": 20,
    "fill": "blue"
  },
  "destiny": {
    "cx": 0,
    "fill": "orange"
  }
}

function startAnimation() {
  //rectangle properties
  startShapeAnimation(rectangle, rectValues);
  //circle properties  
  startShapeAnimation(circle, circleValues);
}

function startShapeAnimation(shape, shapeValues) {
  shape.attrs(shapeValues.begin)
    .transition()
    .duration(0)
    .attrs(shapeValues.begin)
    .transition()
    .delay(shapeValues.delay)
    .duration(shapeValues.duration)
    .ease(d3.easeLinear)
    .attrs(shapeValues.destiny);
}

startAnimation(0);

/***** CREATION OF VIDEO *******/
var canvas = document.createElement("canvas"),
  width = 800,
  height = 150,
  context = canvas.getContext("2d");


var data = [],
  stream = canvas.captureStream(),
  recorder = new MediaRecorder(stream, {
    mimeType: "video/webm"
  });

recorder.ondataavailable = function(event) {
  if (event.data && event.data.size) {
    data.push(event.data);
  }
};
recorder.onstop = () => {
  var url = URL.createObjectURL(new Blob(data, {
    type: "video/webm"
  }));
  //d3.selectAll("canvas, svg").remove();
  d3.select("body")
    .append("video")
    .attr("src", url)
    .attr("controls", true)
    .attr("autoplay", true);
};

var queue = d3.queue(1);

d3.range(240).forEach(function(frame) {
  queue.defer(drawFrame, frame / 240);
});

queue.awaitAll(function(err, frames) {
  recorder.start();
  drawFrame();

  function drawFrame() {
    if (frames.length) {
      context.drawImage(frames.shift(), 0, 0, width, height);
      requestAnimationFrame(drawFrame);
    } else {
      recorder.stop();
    }
  }
});

function drawFrame(t, cb) {
  var img = new Image(),
    serialized = new XMLSerializer().serializeToString(vis.node()),
    url = URL.createObjectURL(new Blob([serialized], {
      type: "image/svg+xml"
    }));

  img.onload = function() {
    cb(null, img);
  };

  img.src = url;

}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.2.3/d3.js"></script>
<script src="https://d3js.org/d3-selection-multi.v1.min.js"></script>
<div id="visualization"></div>
<div id="contenedor_canvas"></div>
Ruben Helsloot
  • 12,582
  • 6
  • 26
  • 49
yavg
  • 2,761
  • 7
  • 45
  • 115

1 Answers1

0

Instead of limiting yourself to 240 frames, try to get as many as possible using requestAnimationFrame. Using this answer as inspiration, I was able to generate a much smoother video.

const progressbar = document.getElementById('progressbar');

d3.select("#visualization").append('svg');
const vis = d3.select("svg").attr("width", 800).attr("height", 150).attr("xmlns", "http://www.w3.org/2000/svg").style("border", "1px solid red").style("fill", "white").style("background", "white");

const rectangle = vis.append("rect")
const circle = vis.append("circle");

let second = 0;
let interval;

const rectValues = {
  "delay": 0000,
  "duration": 5000,
  "begin": {
    "x": 0,
    "y": 0,
    "height": 70,
    "width": 100,
    "opacity": 1,
    "fill": "red"
  },
  "destiny": {
    "x": 250,
    "y": 1,
    "height": 100,
    "width": 120,
    "opacity": 0.8,
    "fill": "green"
  }
};

const circleValues = {
  "delay": 4000,
  "duration": 3000,
  "begin": {
    "cx": 250,
    "r": 20,
    "fill": "blue"
  },
  "destiny": {
    "cx": 0,
    "fill": "orange"
  }
}

function startAnimation() {
  //rectangle properties
  startShapeAnimation(rectangle, rectValues);
  //circle properties  
  startShapeAnimation(circle, circleValues);
}

function startShapeAnimation(shape, shapeValues) {
  shape.attrs(shapeValues.begin)
    .transition()
    .duration(0)
    .attrs(shapeValues.begin)
    .transition()
    .delay(shapeValues.delay)
    .duration(shapeValues.duration)
    .ease(d3.easeLinear)
    .attrs(shapeValues.destiny);
}

startAnimation(0);

/***** CREATION OF VIDEO *******/
var canvas = document.createElement("canvas"),
  width = 800,
  height = 150,
  context = canvas.getContext("2d");

var data = [],
  stream = canvas.captureStream(),
  recorder = new MediaRecorder(stream, {
    mimeType: "video/webm"
  });

recorder.ondataavailable = function(event) {
  if (event.data && event.data.size) {
    data.push(event.data);
  }
};

recorder.onstop = () => {
  var url = URL.createObjectURL(new Blob(data, {
    type: "video/webm"
  }));
  //d3.selectAll("canvas, svg").remove();
  d3.select("body")
    .append("video")
    .attr("src", url)
    .attr("controls", true)
    .attr("autoplay", true);
};

var lengthInSeconds = 15;
var lengthInMS = lengthInSeconds * 1000;
var startTime = Date.now();

startRecording();
drawFrame();

function startRecording() {
  recorder.start();
  drawFrame();
}

function drawFrame() {
  var elapsedTime = Date.now() - startTime;
  if (elapsedTime > lengthInMS) {
    stopRecording();
    return;
  }

  console.log(((elapsedTime / lengthInMS) * 100).toFixed(2) + '%');
  requestAnimationFrame(drawFrame);
  var img = new Image(),
    serialized = new XMLSerializer().serializeToString(vis.node()),
    url = URL.createObjectURL(new Blob([serialized], {
      type: "image/svg+xml"
    }));

  img.onload = function() {
    context.drawImage(img, 0, 0, width, height);
  };

  img.src = url;
}

function stopRecording() {
  recorder.stop();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.2.3/d3.js"></script>
<script src="https://d3js.org/d3-selection-multi.v1.min.js"></script>
<div id="visualization"></div>
<div id="contenedor_canvas"></div>
Ruben Helsloot
  • 12,582
  • 6
  • 26
  • 49
  • woooow it works! but, do you know why i get this error? occurs when the video is rendered: https://i.imgur.com/UOipr5G.jpg . Taking advantage of your great knowledge I want to ask you something, if I run this code from a cell phone, the performance will be the same? I plan to do a 45 second animation. I also want to know if it is possible to add an audio – yavg Oct 08 '20 at 13:24
  • I have no experience with making video's, so I don't know if audio will work. On a new cell phone, the performance should be similar I think. You get the error because, for some reason, `stopRecording()` is called twice and you can't stop an already stopped recording. You can investigate it or just only stop the recording `if(recording.state !== 'inactive')` – Ruben Helsloot Oct 08 '20 at 13:41