2021-11-22 19:54:04 +00:00
|
|
|
|
|
|
|
class Player {
|
2021-12-21 13:31:02 +00:00
|
|
|
constructor(wrapperEl, fileurl) {
|
2021-11-22 19:54:04 +00:00
|
|
|
this.wrapperEl = wrapperEl;
|
|
|
|
this.svgEl = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
|
|
this.wrapperEl.appendChild(this.svgEl);
|
|
|
|
|
2021-12-20 12:36:18 +00:00
|
|
|
this.scrubberElOld = document.createElement('input');
|
|
|
|
this.scrubberElOld.type = "range";
|
|
|
|
this.scrubberElOld.min = 0;
|
|
|
|
this.scrubberElOld.step = 0.01;
|
|
|
|
this.wrapperEl.appendChild(this.scrubberElOld);
|
|
|
|
|
|
|
|
this.scrubberEl = document.createElement('div');
|
|
|
|
this.scrubberEl.classList.add('scrubber')
|
|
|
|
this.wrapperEl.appendChild(this.scrubberEl);
|
|
|
|
|
|
|
|
this.scrubberElOld.addEventListener("input", (ev) => {
|
|
|
|
this.scrubTo(ev.target.value);
|
|
|
|
})
|
|
|
|
|
|
|
|
this.inPointPosition = null;
|
|
|
|
this.outPointPosition = null;
|
|
|
|
this.currentTime = 0;
|
|
|
|
this.isPlaying = false;
|
2021-11-22 19:54:04 +00:00
|
|
|
|
2021-12-21 13:31:02 +00:00
|
|
|
this.play(fileurl);
|
2021-11-22 19:54:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
play(file) {
|
|
|
|
const request = new Request(file, {
|
|
|
|
method: 'GET',
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
fetch(request)
|
2021-12-20 12:36:18 +00:00
|
|
|
.then(response => response.json())
|
|
|
|
.then(data => {
|
|
|
|
this.playStrokes(data)
|
|
|
|
// do something with the data sent in the request
|
|
|
|
});
|
2021-11-22 19:54:04 +00:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2021-12-20 12:36:18 +00:00
|
|
|
playStrokes(drawing) {
|
2021-11-22 19:54:04 +00:00
|
|
|
this.strokes = drawing.shape;
|
2021-12-20 12:36:18 +00:00
|
|
|
this.currentPathI = null;
|
|
|
|
this.currentPointI = null;
|
2021-11-22 19:54:04 +00:00
|
|
|
this.dimensions = drawing.dimensions;
|
2021-11-23 11:17:48 +00:00
|
|
|
this.svgEl.setAttribute('viewBox', `0 0 ${this.dimensions[0]} ${this.dimensions[1]}`)
|
2021-11-22 19:54:04 +00:00
|
|
|
this.startTime = window.performance.now() - this.strokes[0].points[0][3];
|
2021-12-20 12:36:18 +00:00
|
|
|
this.playStrokePosition(0, 1);
|
|
|
|
this.duration = this.getDuration();
|
|
|
|
this.scrubberElOld.max = this.duration;
|
|
|
|
this.playTimout = null;
|
|
|
|
|
|
|
|
const slider = noUiSlider.create(this.scrubberEl, {
|
|
|
|
start: [this.currentTime, this.duration],
|
|
|
|
connect: true,
|
|
|
|
range: {
|
|
|
|
'min': 0,
|
|
|
|
'max': this.duration
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
slider.on("slide", (values, handle) => {
|
|
|
|
this.isPlaying = false;
|
|
|
|
// console.log(values, handle);
|
|
|
|
// both in and out need to have a value
|
|
|
|
this.inPointPosition = this.findPositionForTime(values[0]);
|
|
|
|
this.outPointPosition = this.findPositionForTime(values[1]);
|
|
|
|
if (handle === 0) {
|
|
|
|
// in point
|
|
|
|
if (
|
|
|
|
this.currentPathI < this.inPointPosition[0] ||
|
|
|
|
this.currentPointI < this.inPointPosition[1]) {
|
2022-02-09 07:18:28 +00:00
|
|
|
this.drawStrokePosition(
|
|
|
|
// this.inPointPosition[0],
|
|
|
|
// this.inPointPosition[1],
|
|
|
|
// always draw at out position, as to see the whole shape of the range
|
|
|
|
this.outPointPosition[0],
|
|
|
|
this.outPointPosition[1],
|
|
|
|
);
|
2021-12-20 12:36:18 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (handle === 1) {
|
|
|
|
// out point
|
|
|
|
// this.outPointPosition = this.findPositionForTime(values[1]);
|
|
|
|
this.drawStrokePosition(
|
|
|
|
this.outPointPosition[0],
|
|
|
|
this.outPointPosition[1],
|
|
|
|
);
|
|
|
|
}
|
|
|
|
// this.inPointPosition = values;
|
|
|
|
// this.outPointPosition = vaalues[0];
|
|
|
|
// this.scrubTo()
|
|
|
|
// this.scrubTo(ev.target.value);
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
getDuration() {
|
|
|
|
const points = this.strokes[this.strokes.length - 1].points;
|
|
|
|
return points[points.length - 1][3];
|
|
|
|
}
|
|
|
|
|
|
|
|
getStrokesForPathRange(in_point, out_point) {
|
|
|
|
// get paths for given range. Also, split path at in & out if necessary.
|
|
|
|
strokes = {};
|
|
|
|
for (let i = in_point[0]; i <= out_point[0]; i++) {
|
|
|
|
const path = this.strokes[i];
|
|
|
|
const in_i = (in_point[0] === i) ? in_point[1] : 0;
|
|
|
|
const out_i = (out_point[0] === i) ? out_point[1] : Math.inf;
|
|
|
|
const points = path.points.slice(in_i, out_i);
|
|
|
|
strokes[i] = points; // preserve indexes
|
|
|
|
}
|
|
|
|
return strokes;
|
2021-11-22 19:54:04 +00:00
|
|
|
}
|
|
|
|
|
2021-12-20 12:36:18 +00:00
|
|
|
// TODO: when drawing, have a group active & inactive.
|
|
|
|
// active is getPathRange(currentIn, currentOut)
|
|
|
|
// inactive is what comes before and after.
|
|
|
|
// then, playing the video is just running pathRanghe(0, playhead)
|
|
|
|
drawStrokePosition(path_i, point_i, show_all) {
|
2022-02-09 07:18:28 +00:00
|
|
|
if (typeof show_all === 'undefined')
|
2021-12-20 12:36:18 +00:00
|
|
|
show_all = false;
|
|
|
|
|
|
|
|
// check if anything is placed that is in the future from the current playhead
|
|
|
|
if (this.currentPathI !== null && this.currentPoint !== null) {
|
|
|
|
if (this.currentPathI > path_i) {
|
|
|
|
console.log('remove', path_i, ' -> ', this.currentPathI)
|
|
|
|
// remove everything that comes later
|
|
|
|
for (let i = path_i + 1; i <= this.currentPathI; i++) {
|
|
|
|
// console.log('remove', i);
|
|
|
|
const pathEl = this.svgEl.querySelector(`.path${i}`);
|
|
|
|
if (pathEl) {
|
|
|
|
pathEl.parentNode.removeChild(pathEl);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// an inpoint is set, so we're annotating
|
|
|
|
// make everything coming before translucent
|
|
|
|
if (this.inPointPosition !== null) {
|
|
|
|
const [inPath_i, inPoint_i] = this.inPointPosition;
|
|
|
|
// returns a static NodeList
|
|
|
|
const currentBeforeEls = this.svgEl.querySelectorAll(`.before_in`);
|
|
|
|
for (let currentBeforeEl of currentBeforeEls) {
|
|
|
|
currentBeforeEl.classList.remove('before_in');
|
|
|
|
}
|
|
|
|
|
|
|
|
for (let index = 0; index < inPath_i; index++) {
|
|
|
|
const pathEl = this.svgEl.querySelector(`.path${index}`);
|
|
|
|
if (pathEl) {
|
|
|
|
pathEl.classList.add('before_in');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
this.currentPathI = path_i;
|
|
|
|
this.currentPointI = point_i;
|
|
|
|
|
2021-11-22 19:54:04 +00:00
|
|
|
const path = this.strokes[path_i];
|
|
|
|
// console.log(path);
|
|
|
|
let pathEl = this.svgEl.querySelector(`.path${path_i}`);
|
2021-12-20 12:36:18 +00:00
|
|
|
if (!pathEl) {
|
2021-11-22 19:54:04 +00:00
|
|
|
pathEl = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
|
|
pathEl.style.stroke = path.color;
|
2021-12-20 12:36:18 +00:00
|
|
|
pathEl.classList.add('path' + path_i)
|
2021-11-22 19:54:04 +00:00
|
|
|
this.svgEl.appendChild(pathEl)
|
|
|
|
}
|
2021-12-20 12:36:18 +00:00
|
|
|
|
|
|
|
const stroke = path.points.slice(0, point_i);
|
|
|
|
const d = this.strokes2D(stroke);
|
2021-11-22 19:54:04 +00:00
|
|
|
pathEl.setAttribute('d', d);
|
2021-12-20 12:36:18 +00:00
|
|
|
|
|
|
|
this.scrubberElOld.value = path.points[point_i][3];
|
|
|
|
this.currentTime = path.points[point_i][3];
|
|
|
|
}
|
|
|
|
|
|
|
|
getNextPosition(path_i, point_i) {
|
|
|
|
const path = this.strokes[path_i];
|
|
|
|
let next_path, next_point;
|
|
|
|
if (path.points.length > point_i + 1) {
|
2021-11-22 19:54:04 +00:00
|
|
|
next_path = path_i;
|
|
|
|
next_point = point_i + 1;
|
|
|
|
// setTimeout(() => this.playStroke(next_path, next_point), dt);
|
2021-12-20 12:36:18 +00:00
|
|
|
} else if (this.strokes.length > path_i + 1) {
|
2021-11-22 19:54:04 +00:00
|
|
|
next_path = path_i + 1;
|
|
|
|
next_point = 1;
|
|
|
|
// use starttime instead of diff, to prevent floating
|
|
|
|
} else {
|
2021-12-20 12:36:18 +00:00
|
|
|
return [null, null];
|
|
|
|
}
|
|
|
|
|
|
|
|
// when an outpoint is set, stop playing there
|
2022-02-09 07:18:28 +00:00
|
|
|
if (this.outPointPosition && (next_path > this.outPointPosition[0] || next_point > this.outPointPosition[1])) {
|
2021-12-20 12:36:18 +00:00
|
|
|
return [null, null];
|
|
|
|
}
|
|
|
|
|
|
|
|
return [next_path, next_point];
|
|
|
|
}
|
|
|
|
|
|
|
|
playStrokePosition(path_i, point_i, allow_interrupt) {
|
2022-02-09 07:18:28 +00:00
|
|
|
if (allow_interrupt) {
|
|
|
|
if (!this.isPlaying) {
|
2021-12-20 12:36:18 +00:00
|
|
|
console.log('not playing because of interrupt');
|
|
|
|
return;
|
|
|
|
}
|
2022-02-09 07:18:28 +00:00
|
|
|
} else {
|
2021-12-20 12:36:18 +00:00
|
|
|
this.isPlaying = true;
|
2021-11-22 19:54:04 +00:00
|
|
|
}
|
2021-12-20 12:36:18 +00:00
|
|
|
this.drawStrokePosition(path_i, point_i);
|
|
|
|
|
|
|
|
const [next_path, next_point] = this.getNextPosition(path_i, point_i);
|
|
|
|
if (next_path === null) {
|
|
|
|
console.log('done playing');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const t = this.strokes[next_path].points[next_point][3];// - path.points[point_i][3];
|
2021-11-22 19:54:04 +00:00
|
|
|
|
|
|
|
const dt = t - (window.performance.now() - this.startTime);
|
2021-12-20 12:36:18 +00:00
|
|
|
this.playTimout = setTimeout(() => this.playStrokePosition(next_path, next_point, true), dt);
|
2021-11-22 19:54:04 +00:00
|
|
|
}
|
|
|
|
|
2021-12-20 12:36:18 +00:00
|
|
|
playUntil(path_i) {
|
2021-11-22 19:54:04 +00:00
|
|
|
// for scrubber
|
|
|
|
}
|
|
|
|
|
2021-12-20 12:36:18 +00:00
|
|
|
scrubTo(ms) {
|
|
|
|
const [path_i, point_i] = this.findPositionForTime(ms);
|
|
|
|
// console.log(path_i, point_i);
|
|
|
|
clearTimeout(this.playTimout);
|
|
|
|
this.playStrokePosition(path_i, point_i);
|
|
|
|
// this.playHead = ms;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
findPositionForTime(ms) {
|
|
|
|
ms = Math.min(Math.max(ms, 0), this.duration);
|
|
|
|
console.log('scrub to', ms)
|
|
|
|
let path_i = 0;
|
|
|
|
let point_i = 0;
|
|
|
|
this.strokes.every((stroke, index) => {
|
|
|
|
const startAt = stroke.points[0][3];
|
|
|
|
const endAt = stroke.points[stroke.points.length - 1][3];
|
|
|
|
|
|
|
|
if (startAt > ms) {
|
|
|
|
return false; // too far
|
|
|
|
}
|
|
|
|
if (endAt > ms) {
|
|
|
|
// we're getting close. Find the right point_i
|
|
|
|
path_i = index;
|
|
|
|
stroke.points.every((point, pi) => {
|
|
|
|
if (point[3] > ms) {
|
|
|
|
// too far
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
point_i = pi;
|
|
|
|
return true;
|
|
|
|
});
|
|
|
|
return false;
|
|
|
|
} else {
|
|
|
|
// in case nothings comes after, we store the last best option thus far
|
|
|
|
path_i = index;
|
|
|
|
point_i = stroke.points.length - 1;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
return [path_i, point_i];
|
|
|
|
}
|
|
|
|
|
2021-11-22 19:54:04 +00:00
|
|
|
|
|
|
|
strokes2D(strokes) {
|
|
|
|
// strokes to a d attribute for a path
|
|
|
|
let d = "";
|
|
|
|
let last_stroke = undefined;
|
|
|
|
let cmd = "";
|
|
|
|
for (let stroke of strokes) {
|
|
|
|
if (!last_stroke) {
|
2022-02-09 07:18:28 +00:00
|
|
|
d += `M${stroke[0]},${stroke[1]} `;
|
2021-11-22 19:54:04 +00:00
|
|
|
cmd = 'M';
|
|
|
|
} else {
|
|
|
|
if (last_stroke[2] == 1) {
|
|
|
|
d += " m";
|
|
|
|
cmd = 'm';
|
|
|
|
} else if (cmd != 'l') {
|
|
|
|
d += ' l ';
|
|
|
|
cmd = 'l';
|
|
|
|
}
|
|
|
|
let rel_stroke = [stroke[0] - last_stroke[0], stroke[1] - last_stroke[1]];
|
2022-02-09 07:18:28 +00:00
|
|
|
d += `${rel_stroke[0]},${rel_stroke[1]} `;
|
2021-11-22 19:54:04 +00:00
|
|
|
}
|
|
|
|
last_stroke = stroke;
|
|
|
|
|
|
|
|
}
|
|
|
|
return d;
|
|
|
|
}
|
|
|
|
}
|