Fix recording and playback issues
- Fix download filename handling in converter and export functions - Make updateRecordCanvas return Promise to handle async loadFromJSON correctly - Add setCoords() call in setObjectValue to fix object selection after updates - Update recorder initialization and animation frame handling Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
+1
-1
@@ -94,7 +94,7 @@ function PostBlob(blob) {
|
|||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
a.style.display = 'none';
|
a.style.display = 'none';
|
||||||
a.href = url;
|
a.href = url;
|
||||||
a.download = name;
|
a.download = blob.name || 'video';
|
||||||
document.body.appendChild(a);
|
document.body.appendChild(a);
|
||||||
a.click();
|
a.click();
|
||||||
recording = false;
|
recording = false;
|
||||||
|
|||||||
+56
-36
@@ -506,27 +506,32 @@ function updateRecordCanvas() {
|
|||||||
'inGroup',
|
'inGroup',
|
||||||
]);
|
]);
|
||||||
canvas.clipPath = artboard;
|
canvas.clipPath = artboard;
|
||||||
canvasrecord.loadFromJSON(canvassave, function () {
|
// loadFromJSON is asynchronous (it clears the canvas first, then revives the
|
||||||
if (canvasrecord.getItemById('center_h')) {
|
// objects), so callers that record straight afterwards must await this.
|
||||||
canvasrecord.remove(canvasrecord.getItemById('center_h'));
|
return new Promise(function (resolve) {
|
||||||
canvasrecord.remove(canvasrecord.getItemById('center_v'));
|
canvasrecord.loadFromJSON(canvassave, function () {
|
||||||
}
|
if (canvasrecord.getItemById('center_h')) {
|
||||||
if (canvasrecord.getItemById('line_h')) {
|
canvasrecord.remove(canvasrecord.getItemById('center_h'));
|
||||||
canvasrecord.remove(canvasrecord.getItemById('line_h'));
|
canvasrecord.remove(canvasrecord.getItemById('center_v'));
|
||||||
canvasrecord.remove(canvasrecord.getItemById('line_v'));
|
}
|
||||||
}
|
if (canvasrecord.getItemById('line_h')) {
|
||||||
canvasrecord.renderAll();
|
canvasrecord.remove(canvasrecord.getItemById('line_h'));
|
||||||
canvasrecord.setWidth(artboard.width);
|
canvasrecord.remove(canvasrecord.getItemById('line_v'));
|
||||||
canvasrecord.setHeight(artboard.height);
|
}
|
||||||
canvasrecord.width = artboard.width;
|
canvasrecord.renderAll();
|
||||||
canvasrecord.height = artboard.height;
|
canvasrecord.setWidth(artboard.width);
|
||||||
canvasrecord.renderAll();
|
canvasrecord.setHeight(artboard.height);
|
||||||
objects.forEach(function (object) {
|
canvasrecord.width = artboard.width;
|
||||||
replaceSource(
|
canvasrecord.height = artboard.height;
|
||||||
canvasrecord.getItemById(object.id),
|
canvasrecord.renderAll();
|
||||||
canvasrecord
|
objects.forEach(function (object) {
|
||||||
);
|
replaceSource(
|
||||||
replaceSource(canvas.getItemById(object.id), canvas);
|
canvasrecord.getItemById(object.id),
|
||||||
|
canvasrecord
|
||||||
|
);
|
||||||
|
replaceSource(canvas.getItemById(object.id), canvas);
|
||||||
|
});
|
||||||
|
resolve();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -543,7 +548,7 @@ function downloadRecording(chunks) {
|
|||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
a.style.display = 'none';
|
a.style.display = 'none';
|
||||||
a.href = url;
|
a.href = url;
|
||||||
a.download = name;
|
a.download = 'video.webm';
|
||||||
document.body.appendChild(a);
|
document.body.appendChild(a);
|
||||||
a.click();
|
a.click();
|
||||||
recording = false;
|
recording = false;
|
||||||
@@ -1223,6 +1228,7 @@ function setObjectValue(prop, object, value, inst) {
|
|||||||
} else if (prop != 'width') {
|
} else if (prop != 'width') {
|
||||||
object.set(prop, value);
|
object.set(prop, value);
|
||||||
}
|
}
|
||||||
|
object.setCoords();
|
||||||
inst.renderAll();
|
inst.renderAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1508,18 +1514,20 @@ async function recordAnimate(time) {
|
|||||||
if (!canvas.getItemById(keyframe.id)) {
|
if (!canvas.getItemById(keyframe.id)) {
|
||||||
reGroup(keyframe.id);
|
reGroup(keyframe.id);
|
||||||
}
|
}
|
||||||
const object = canvas.getItemById(keyframe.id);
|
const object = inst.getItemById(keyframe.id);
|
||||||
|
if (!object) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
currenttime <
|
time <
|
||||||
p_keyframes.find((x) => x.id == keyframe.id).trimstart +
|
p_keyframes.find((x) => x.id == keyframe.id).trimstart +
|
||||||
p_keyframes.find((x) => x.id == keyframe.id).start
|
p_keyframes.find((x) => x.id == keyframe.id).start
|
||||||
) {
|
) {
|
||||||
object.set('visible', false);
|
object.set('visible', false);
|
||||||
inst.renderAll();
|
inst.renderAll();
|
||||||
} else if (
|
} else if (
|
||||||
currenttime >
|
time > p_keyframes.find((x) => x.id == keyframe.id).end ||
|
||||||
p_keyframes.find((x) => x.id == keyframe.id).end ||
|
time > duration
|
||||||
currenttime > duration
|
|
||||||
) {
|
) {
|
||||||
object.set('visible', false);
|
object.set('visible', false);
|
||||||
inst.renderAll();
|
inst.renderAll();
|
||||||
@@ -1528,7 +1536,7 @@ async function recordAnimate(time) {
|
|||||||
inst.renderAll();
|
inst.renderAll();
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
currenttime >=
|
time >=
|
||||||
p_keyframes.find((x) => x.id == keyframe.id).trimstart +
|
p_keyframes.find((x) => x.id == keyframe.id).trimstart +
|
||||||
p_keyframes.find((x) => x.id == keyframe.id).start
|
p_keyframes.find((x) => x.id == keyframe.id).start
|
||||||
) {
|
) {
|
||||||
@@ -1573,6 +1581,7 @@ async function recordAnimate(time) {
|
|||||||
} else if (prop != 'width') {
|
} else if (prop != 'width') {
|
||||||
object.set(prop, value);
|
object.set(prop, value);
|
||||||
}
|
}
|
||||||
|
object.setCoords();
|
||||||
inst.renderAll();
|
inst.renderAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1601,9 +1610,12 @@ async function recordAnimate(time) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var object = canvasrecord.getItemById(keyframe.id);
|
var object = canvasrecord.getItemById(keyframe.id);
|
||||||
|
if (!object) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
keyframe.t >= time &&
|
keyframe.t >= time &&
|
||||||
currenttime >=
|
time >=
|
||||||
p_keyframes.find((x) => x.id == keyframe.id).trimstart +
|
p_keyframes.find((x) => x.id == keyframe.id).trimstart +
|
||||||
p_keyframes.find((x) => x.id == keyframe.id).start
|
p_keyframes.find((x) => x.id == keyframe.id).start
|
||||||
) {
|
) {
|
||||||
@@ -1621,7 +1633,11 @@ async function recordAnimate(time) {
|
|||||||
lasttime = lastkey.t;
|
lasttime = lastkey.t;
|
||||||
lastprop = lastkey.value;
|
lastprop = lastkey.value;
|
||||||
}
|
}
|
||||||
if (lastkey && lastkey.t >= time && !play) {
|
// Every recorded frame is rendered like a seek, so a keyframe whose
|
||||||
|
// preceding keyframe is still in the future has not started yet.
|
||||||
|
// (This used to read an undeclared `play`, throwing a ReferenceError
|
||||||
|
// and aborting the rest of the frame.)
|
||||||
|
if (lastkey && lastkey.t >= time) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1686,24 +1702,28 @@ async function recordAnimate(time) {
|
|||||||
|
|
||||||
objects.forEach(function (object) {
|
objects.forEach(function (object) {
|
||||||
if (object.id.indexOf('Group') == -1) {
|
if (object.id.indexOf('Group') == -1) {
|
||||||
const object2 = canvas.getItemById(object.id);
|
// Visibility has to be applied to the object on the recording canvas,
|
||||||
|
// at the time of the frame being rendered (not the editor playhead).
|
||||||
|
const object2 = inst.getItemById(object.id);
|
||||||
|
if (!object2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
currenttime <
|
time <
|
||||||
p_keyframes.find((x) => x.id == object.id).trimstart +
|
p_keyframes.find((x) => x.id == object.id).trimstart +
|
||||||
p_keyframes.find((x) => x.id == object.id).start
|
p_keyframes.find((x) => x.id == object.id).start
|
||||||
) {
|
) {
|
||||||
object2.set('visible', false);
|
object2.set('visible', false);
|
||||||
} else if (
|
} else if (
|
||||||
currenttime >
|
time > p_keyframes.find((x) => x.id == object.id).end ||
|
||||||
p_keyframes.find((x) => x.id == object.id).end ||
|
time > duration
|
||||||
currenttime > duration
|
|
||||||
) {
|
) {
|
||||||
object2.set('visible', false);
|
object2.set('visible', false);
|
||||||
} else {
|
} else {
|
||||||
object2.set('visible', true);
|
object2.set('visible', true);
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
currenttime >=
|
time >=
|
||||||
p_keyframes.find((x) => x.id == object.id).trimstart +
|
p_keyframes.find((x) => x.id == object.id).trimstart +
|
||||||
p_keyframes.find((x) => x.id == object.id).start
|
p_keyframes.find((x) => x.id == object.id).start
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -988,6 +988,11 @@ var canvasrecord = new fabric.Canvas('canvasrecord', {
|
|||||||
backgroundColor: '#FFF',
|
backgroundColor: '#FFF',
|
||||||
width: artboard.width,
|
width: artboard.width,
|
||||||
height: artboard.height,
|
height: artboard.height,
|
||||||
|
// Objects are repositioned to artboard-relative coordinates while
|
||||||
|
// recording. Their cached aCoords are not always refreshed in step, and
|
||||||
|
// fabric's offscreen culling would then skip drawing them entirely,
|
||||||
|
// producing blank frames.
|
||||||
|
skipOffscreen: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
var timelineslider = document.getElementById('timeline-zoom');
|
var timelineslider = document.getElementById('timeline-zoom');
|
||||||
|
|||||||
+6
-4
@@ -67,11 +67,13 @@ async function exportRecording() {
|
|||||||
|
|
||||||
// Record canvas
|
// Record canvas
|
||||||
async function record() {
|
async function record() {
|
||||||
updateRecordCanvas();
|
// loadFromJSON clears the record canvas and revives the objects
|
||||||
|
// asynchronously, so nothing may be drawn or captured until it resolves.
|
||||||
|
await updateRecordCanvas();
|
||||||
if ($('input[name=radio]:checked').val() == 'image') {
|
if ($('input[name=radio]:checked').val() == 'image') {
|
||||||
recording = true;
|
recording = true;
|
||||||
paused = true;
|
paused = true;
|
||||||
animate(false, currenttime);
|
await recordAnimate(currenttime);
|
||||||
const dataURL = canvasrecord.toDataURL({
|
const dataURL = canvasrecord.toDataURL({
|
||||||
format: 'png',
|
format: 'png',
|
||||||
});
|
});
|
||||||
@@ -82,12 +84,12 @@ async function record() {
|
|||||||
link.click();
|
link.click();
|
||||||
document.body.removeChild(link);
|
document.body.removeChild(link);
|
||||||
recording = false;
|
recording = false;
|
||||||
|
updateRecordCanvas();
|
||||||
} else {
|
} else {
|
||||||
if (!recording) {
|
if (!recording) {
|
||||||
recording = true;
|
recording = true;
|
||||||
paused = true;
|
paused = true;
|
||||||
recordAnimate(false, (frame / FPS) * 1000);
|
await recordAnimate(0);
|
||||||
recording = true;
|
|
||||||
$('#download-real').html('Rendering...');
|
$('#download-real').html('Rendering...');
|
||||||
$('#download-real').addClass('downloading');
|
$('#download-real').addClass('downloading');
|
||||||
var fps = 60;
|
var fps = 60;
|
||||||
|
|||||||
Reference in New Issue
Block a user