Compare commits

..

No commits in common. "7e946cb3f687e2fdbad19e887e55864f89039181" and "d55c5b5486bb718cebcb509f6ff8029b154f06ed" have entirely different histories.

2 changed files with 177 additions and 313 deletions

View file

@ -98,7 +98,6 @@
position: absolute; position: absolute;
left: 100%; left: 100%;
width: 30px; width: 30px;
height:30px;
} }
.controls button.paused::before { .controls button.paused::before {
@ -109,22 +108,6 @@
content: '⏸'; content: '⏸';
} }
.buffering .controls button:is(.playing,.paused)::before {
content: '↺';
display:inline-block;
animation: rotate 1s infinite;
}
@keyframes rotate {
0% {
transform: rotate(359deg)
}
100% {
transform: rotate(0deg)
}
}
.controls { .controls {
position: absolute !important; position: absolute !important;
z-index: 100; z-index: 100;
@ -199,14 +182,6 @@
pointer-events: all; pointer-events: all;
} }
.controls .annotation-comment{
width: 100%;
visibility: hidden;
}
.selected-annotation .controls .annotation-comment{
visibility: visible;
}
.noUi-handle:focus { .noUi-handle:focus {
/* background: red;; */ /* background: red;; */
border: solid 2px #601be0; border: solid 2px #601be0;
@ -401,7 +376,6 @@
navigator.mediaSession.setActionHandler('seekforward', function () { /* Code excerpted. */ }); navigator.mediaSession.setActionHandler('seekforward', function () { /* Code excerpted. */ });
navigator.mediaSession.setActionHandler('previoustrack', function () { /* Code excerpted. */ }); navigator.mediaSession.setActionHandler('previoustrack', function () { /* Code excerpted. */ });
navigator.mediaSession.setActionHandler('nexttrack', function () { /* Code excerpted. */ }); navigator.mediaSession.setActionHandler('nexttrack', function () { /* Code excerpted. */ });
navigator.mediaSession.setActionHandler('playpause', function () { /* Code excerpted. */ });
</script> </script>
</body> </body>

View file

@ -1,9 +1,8 @@
class Annotation { class Annotation {
constructor(tag, t_in, t_out, comment) { constructor(tag, t_in, t_out) {
this.tag = tag; this.tag = tag;
this.t_in = Number.parseFloat(t_in); this.t_in = Number.parseFloat(t_in);
this.t_out = Number.parseFloat(t_out); this.t_out = Number.parseFloat(t_out);
this.comment = comment;
} }
} }
@ -111,14 +110,9 @@ class StrokeSlice {
} }
class Annotator extends EventTarget { class Annotator extends EventTarget {
constructor(wrapperEl, tagFile, fileurl, config) { constructor(wrapperEl, tagFile, fileurl) {
super(); super();
this.config = {
is_player: config && config.hasOwnProperty('is_player') ? config.is_player : false, // in player mode annotations are not loaded, nor is the annotator shown
crop_to_fit: config && config.hasOwnProperty('crop_to_fit') ? config.crop_to_fit : false, // don't animate viewport, but show the whole drawing
}
this.formatter = wNumb({ this.formatter = wNumb({
decimals: 2, decimals: 2,
edit: (time) => { edit: (time) => {
@ -148,7 +142,6 @@ class Annotator extends EventTarget {
this.wrapperEl = wrapperEl; this.wrapperEl = wrapperEl;
this.svgEl = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); this.svgEl = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
this.wrapperEl.appendChild(this.svgEl); this.wrapperEl.appendChild(this.svgEl);
this.wrapperEl.classList.add(this.config.is_player ? "svganim_player" : "svganim_annotator");
this.controlsEl = document.createElement('div'); this.controlsEl = document.createElement('div');
@ -168,7 +161,7 @@ class Annotator extends EventTarget {
this.playheadEl.addEventListener("input", (ev) => { this.playheadEl.addEventListener("input", (ev) => {
this.scrubTo(ev.target.value); this.scrubTo(ev.target.value);
}); });
this.playheadEl.addEventListener('keydown', (ev) => { this.playheadEl.addEventListener('keydown',(ev) => {
ev.preventDefault(); // we don't want to use arrow keys, as these are captured in the overall keydown event ev.preventDefault(); // we don't want to use arrow keys, as these are captured in the overall keydown event
}) })
@ -185,7 +178,7 @@ class Annotator extends EventTarget {
this.playPauseEl.addEventListener("click", (ev) => { this.playPauseEl.addEventListener("click", (ev) => {
this.playPause() this.playPause()
}) })
this.playPauseEl.addEventListener('keydown', (ev) => { this.playPauseEl.addEventListener('keydown',(ev) => {
ev.preventDefault(); // we don't want to spacebar, as this is captured in the overall keydown event ev.preventDefault(); // we don't want to spacebar, as this is captured in the overall keydown event
}) })
@ -198,101 +191,77 @@ class Annotator extends EventTarget {
this.annotationsEl.classList.add('annotations') this.annotationsEl.classList.add('annotations')
this.controlsEl.appendChild(this.annotationsEl); this.controlsEl.appendChild(this.annotationsEl);
this.loadTags(tagFile).then(() => {
this.tagsEl = document.createElement('ul');
this.tagsEl.classList.add('tags');
const addTags = (tags, tagsEl) => {
Object.entries(tags).forEach(([tag, tagData]) => {
let tagLiEl = document.createElement('li');
let tagEl = document.createElement('div');
this.inPointPosition = [0, 0]; tagEl.classList.add('tag');
this.inPointTimeMs = null; tagEl.dataset.tag = tag;
this.outPointPosition = null; tagEl.innerText = tagData.hasOwnProperty('fullname') ? tagData.fullname : tag;
this.outPointTimeMs = null; tagEl.addEventListener('click', (e) => {
this._currentTimeMs = 0; this.addTag(tag, this.inPointPosition, this.outPointPosition);
this.videoIsPlaying = false;
const groups = ['before', 'annotation', 'after']
this.strokeGroups = {};
groups.forEach(group => {
let groupEl = document.createElementNS('http://www.w3.org/2000/svg', 'g');
groupEl.classList.add(group)
this.svgEl.appendChild(groupEl);
this.strokeGroups[group] = new StrokeGroup(groupEl, this);
});
this.annotations = [];
if (this.config.is_player) {
this.load(fileurl);
} else {
this.loadTags(tagFile).then(() => {
this.tagsEl = document.createElement('ul');
this.tagsEl.classList.add('tags');
const addTags = (tags, tagsEl) => {
Object.entries(tags).forEach(([tag, tagData]) => {
let tagLiEl = document.createElement('li');
let tagEl = document.createElement('div');
tagEl.classList.add('tag');
tagEl.dataset.tag = tag;
tagEl.innerText = tagData.hasOwnProperty('fullname') ? tagData.fullname : tag;
tagEl.addEventListener('click', (e) => {
this.addTag(tag, this.inPointPosition, this.outPointPosition);
});
tagEl.title = tagData.hasOwnProperty('description') ? tagData.description : "";
let signEl = document.createElement('span');
signEl.classList.add('annotation-' + tag);
signEl.style.backgroundColor = this.getColorForTag(tag);
tagEl.prepend(signEl);
tagLiEl.appendChild(tagEl);
if (tagData.hasOwnProperty('sub')) {
const subEl = document.createElement('ul');
subEl.classList.add('subtags');
addTags(tagData.sub, subEl);
tagLiEl.appendChild(subEl);
}
tagsEl.appendChild(tagLiEl);
}); });
};
addTags(this.tags, this.tagsEl);
let tagEl = document.createElement('li'); tagEl.title = tagData.hasOwnProperty('description') ? tagData.description : "";
tagEl.classList.add('tag');
tagEl.classList.add('annotation-rm'); let signEl = document.createElement('span');
tagEl.dataset.tag = 'rm'; signEl.classList.add('annotation-' + tag);
tagEl.title = "Remove annotation"; signEl.style.backgroundColor = this.getColorForTag(tag);
tagEl.innerHTML = "🚮"; // &times; tagEl.prepend(signEl);
tagEl.addEventListener('click', (e) => {
if (this.selectedAnnotation) { tagLiEl.appendChild(tagEl);
this.removeAnnotation(this.selectedAnnotationI);
if (tagData.hasOwnProperty('sub')) {
const subEl = document.createElement('ul');
subEl.classList.add('subtags');
addTags(tagData.sub, subEl);
tagLiEl.appendChild(subEl);
} }
});
this.tagsEl.appendChild(tagEl);
this.controlsEl.appendChild(this.tagsEl); tagsEl.appendChild(tagLiEl);
this.commentEl = document.createElement('input');
this.commentEl.type = 'text';
this.commentEl.classList.add('annotation-comment');
this.commentEl.title = "Add comment to annotation";
this.commentEl.placeholder = "comment";
this.commentEl.value = "";
this.commentEl.addEventListener('keyup', (e) => {
e.stopPropagation(); // prevent keyup event to propagate and set i/o points
}); });
this.commentEl.addEventListener('input', (e) => { };
e.stopPropagation(); // prevent keyup event addTags(this.tags, this.tagsEl);
if (this.selectedAnnotation) {
this.selectedAnnotation.comment = this.commentEl.value;
this.updateAnnotations(true)
}
});
this.controlsEl.appendChild(this.commentEl);
this.load(fileurl); let tagEl = document.createElement('li');
tagEl.classList.add('tag');
tagEl.classList.add('annotation-rm');
tagEl.dataset.tag = 'rm';
tagEl.title = "Remove annotation";
tagEl.innerHTML = "🚮"; // &times;
tagEl.addEventListener('click', (e) => {
if (this.selectedAnnotation) {
this.removeAnnotation(this.selectedAnnotationI);
}
}); });
} this.tagsEl.appendChild(tagEl);
this.controlsEl.appendChild(this.tagsEl);
this.inPointPosition = [0, 0];
this.inPointTimeMs = null;
this.outPointPosition = null;
this.outPointTimeMs = null;
this._currentTimeMs = 0;
this.videoIsPlaying = false;
const groups = ['before', 'annotation', 'after']
this.strokeGroups = {};
groups.forEach(group => {
let groupEl = document.createElementNS('http://www.w3.org/2000/svg', 'g');
groupEl.classList.add(group)
this.svgEl.appendChild(groupEl);
this.strokeGroups[group] = new StrokeGroup(groupEl, this);
});
this.annotations = [];
this.load(fileurl);
});
} }
getColorForTag(tag) { getColorForTag(tag) {
@ -309,10 +278,6 @@ class Annotator extends EventTarget {
updateAnnotations(save) { updateAnnotations(save) {
if (this.config.is_player) {
return false;
}
this.annotationsEl.innerHTML = ""; this.annotationsEl.innerHTML = "";
for (let annotation_i in this.annotations) { for (let annotation_i in this.annotations) {
const annotation = this.annotations[annotation_i]; const annotation = this.annotations[annotation_i];
@ -330,7 +295,7 @@ class Annotator extends EventTarget {
if (this.selectedAnnotationI == annotation_i) { if (this.selectedAnnotationI == annotation_i) {
this.annotationEl.classList.add('selected'); this.annotationEl.classList.add('selected');
} }
this.annotationEl.title = `[${annotation.tag}] ${annotation.comment}`; this.annotationEl.title = annotation.tag;
this.annotationEl.addEventListener('mouseover', (e) => { this.annotationEl.addEventListener('mouseover', (e) => {
@ -379,7 +344,6 @@ class Annotator extends EventTarget {
this.updateAnnotations(false); //selects the right tag & highlights the annotation this.updateAnnotations(false); //selects the right tag & highlights the annotation
this.wrapperEl.classList.add('selected-annotation'); this.wrapperEl.classList.add('selected-annotation');
this.commentEl.value = this.selectedAnnotation.comment;
} }
deselectAnnotation(keep_position) { deselectAnnotation(keep_position) {
@ -388,7 +352,6 @@ class Annotator extends EventTarget {
} }
this.wrapperEl.classList.remove('selected-annotation'); this.wrapperEl.classList.remove('selected-annotation');
this.commentEl.value = "";
this.selectedAnnotationI = null; this.selectedAnnotationI = null;
this.selectedAnnotation = null; this.selectedAnnotation = null;
@ -446,24 +409,20 @@ class Annotator extends EventTarget {
fetch(request) fetch(request)
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
if (!this.config.is_player) { const metadata_req = new Request(`/annotations/${data.file}`, {
method: 'GET',
const metadata_req = new Request(`/annotations/${data.file}`, { });
method: 'GET', fetch(metadata_req)
}); .then(response => response.ok ? response.json() : null)
fetch(metadata_req) .then(metadata => {
.then(response => response.ok ? response.json() : null) if (metadata !== null) {
.then(metadata => { metadata.annotations = metadata.annotations.map((a) => new Annotation(a.tag, a.t_in, a.t_out))
if (metadata !== null) { }
metadata.annotations = metadata.annotations.map((a) => new Annotation(a.tag, a.t_in, a.t_out, a.hasOwnProperty('comment') ? a.comment : "")) this.loadStrokes(data, metadata)
} })
this.loadStrokes(data, metadata) .catch(e => console.log(e));
}) // do something with the data sent in the request
.catch(e => console.log(e)); });
} else {
this.loadStrokes(data, null);
}
}).catch(e => console.log(e));
} }
updateState() { updateState() {
@ -538,7 +497,7 @@ class Annotator extends EventTarget {
this.slider.destroy(); this.slider.destroy();
} }
this.annotations.push(new Annotation(tag, t_in, t_out, "")); this.annotations.push(new Annotation(tag, t_in, t_out));
this.updateAnnotations(true); this.updateAnnotations(true);
this._currentTimeMs = t_out; this._currentTimeMs = t_out;
@ -549,27 +508,15 @@ class Annotator extends EventTarget {
setUpAnnotator() { setUpAnnotator() {
this.playheadEl.min = this.audioOffset < 0 ? this.audioOffset * 1000 : 0; this.playheadEl.min = this.audioOffset < 0 ? this.audioOffset * 1000 : 0;
this.playheadEl.max = this.getEndTimeMs(); this.playheadEl.max = this.getEndTimeMs();
this._updatePlayhead(); this._updatePlayhead();
this.inPointPosition = this.findPositionForTime(this.currentTime); this.inPointPosition = this.findPositionForTime(this.currentTime);
this.inPointTimeMs = this._currentTimeMs; this.inPointTimeMs = this._currentTimeMs;
this.outPointPosition = this.findPositionForTime(this.lastFrameTime); // TODO: simplify to get the last frame indexes directly this.outPointPosition = this.findPositionForTime(this.lastFrameTime); // TODO: simplify to get the last frame indexes directly
this.outPointTimeMs = this.getEndTimeMs(); this.outPointTimeMs = this.getEndTimeMs();
if (!this.config.is_player) {
this.buildAnnotator();
}
this.drawStrokePosition(this.inPointPosition, this.outPointPosition);
}
buildAnnotator() {
if (this.scrubberEl.noUiSlider) { if (this.scrubberEl.noUiSlider) {
this.slider.destroy(); this.slider.destroy();
} }
@ -650,27 +597,21 @@ class Annotator extends EventTarget {
ttInputEl.addEventListener('blur', submit); ttInputEl.addEventListener('blur', submit);
}); });
}) })
this.drawStrokePosition(this.inPointPosition, this.outPointPosition);
} }
loadStrokes(drawing, metadata) { loadStrokes(drawing, metadata) {
this.audioOffset = 0; this.audioOffset = 0;
if (metadata) { if (metadata) {
this.annotations = metadata.annotations; this.annotations = metadata.annotations;
} this.audioFile = metadata.hasOwnProperty('audio') ? metadata.audio.file : null;
this.audioOffset = metadata.hasOwnProperty('audio') ? Number.parseFloat(metadata.audio.offset) : 0;
if ((metadata && metadata.hasOwnProperty('audio')) || drawing.hasOwnProperty('audio')) {
if (metadata && metadata.hasOwnProperty('audio')) {
this.audioFile = metadata.audio.file
this.audioOffset = Number.parseFloat(metadata.audio.offset);
} else {
this.audioFile = drawing.audio.file
this.audioOffset = Number.parseFloat(drawing.audio.offset);
}
this._currentTimeMs = this.audioOffset < 0 ? this.audioOffset * 1000 : 0; this._currentTimeMs = this.audioOffset < 0 ? this.audioOffset * 1000 : 0;
this._updatePlayhead(); this._updatePlayhead();
//
// load any saved metadata
} }
this.filename = drawing.file; this.filename = drawing.file;
this.strokes = drawing.shape.map(s => new Stroke(s['color'], s['points'])); this.strokes = drawing.shape.map(s => new Stroke(s['color'], s['points']));
this.viewboxes = drawing.viewboxes; this.viewboxes = drawing.viewboxes;
@ -678,11 +619,7 @@ class Annotator extends EventTarget {
this.currentPointI = null; this.currentPointI = null;
this.currentViewboxI = null; this.currentViewboxI = null;
this.dimensions = drawing.dimensions; this.dimensions = drawing.dimensions;
if (!this.config.crop_to_fit) { this.svgEl.setAttribute('viewBox', `0 0 ${this.dimensions[0]} ${this.dimensions[1]}`)
this.svgEl.setAttribute('viewBox', `0 0 ${this.dimensions[0]} ${this.dimensions[1]}`)
} else {
this.svgEl.setAttribute('viewBox', `${drawing.bounding_box.x} ${drawing.bounding_box.y} ${drawing.bounding_box.width} ${drawing.bounding_box.height}`)
}
// let bgEl = document.createElementNS('http://www.w3.org/2000/svg', 'rect'); // let bgEl = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
// bgEl.setAttribute("x", 0); // bgEl.setAttribute("x", 0);
@ -701,19 +638,34 @@ class Annotator extends EventTarget {
this.setupAudioConfig().then(() => { this.setupAudioConfig().then(() => {
// this.setUpAnnotator() // this.setUpAnnotator()
let keyEl; this.updateAnnotations(false);
if (this.config.is_player) {
keyEl = this.wrapperEl;
} else {
keyEl = document.body; // always capture
this.updateAnnotations(false);
}
keyEl.addEventListener('keyup', (ev) => { document.body.addEventListener('keyup', (ev) => {
if (ev.key == ' ') { if (ev.key == ' ') {
this.playPause(); this.playPause();
} }
console.log('key', ev);
if (ev.key == 'i') {
this.setInPoint(this.currentTime * 1000);
}
if (ev.key == 'o') {
this.setOutPoint(this.currentTime * 1000);
}
if (ev.key == 'I') {
// shift+i == jump to in point
this.scrubTo(this.inPointTimeMs);
}
if (ev.key == 'O') {
// shift+o == jump to end point
this.scrubTo(this.outPointTimeMs);
}
if (ev.key == 'Escape') {
if (this.selectedAnnotation) {
this.deselectAnnotation();
} else {
this.resetInOutPoint();
}
}
// shift+arrow keys, jump playhead (search position) // shift+arrow keys, jump playhead (search position)
// FIXME doesn't keep playback after initial load. Only after unfocussing the window, and refocussing it, do the keys capture. // FIXME doesn't keep playback after initial load. Only after unfocussing the window, and refocussing it, do the keys capture.
// Probably a wrong order // Probably a wrong order
@ -731,31 +683,6 @@ class Annotator extends EventTarget {
this.scrubTo(this._currentTimeMs + diff); this.scrubTo(this._currentTimeMs + diff);
if (!p) { console.log('play!'); this.play(); } // scrubTo() causes a pause(); if (!p) { console.log('play!'); this.play(); } // scrubTo() causes a pause();
} }
// additional keys only for annotation mode
if (!this.config.is_player) {
if (ev.key == 'i') {
this.setInPoint(this.currentTime * 1000);
}
if (ev.key == 'o') {
this.setOutPoint(this.currentTime * 1000);
}
if (ev.key == 'I') {
// shift+i == jump to in point
this.scrubTo(this.inPointTimeMs);
}
if (ev.key == 'O') {
// shift+o == jump to end point
this.scrubTo(this.outPointTimeMs);
}
if (ev.key == 'Escape') {
if (this.selectedAnnotation) {
this.deselectAnnotation();
} else {
this.resetInOutPoint();
}
}
}
}); });
}); });
@ -787,64 +714,56 @@ class Annotator extends EventTarget {
setupAudioConfig() { setupAudioConfig() {
// audio config // audio config
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.audioEl = document.createElement('audio');
if (!this.config.is_player)
this.audioEl.setAttribute('controls', true);
this.audioEl.addEventListener('canplaythrough', (ev) => { let audioConfigEl = document.createElement('div');
console.log('loaded audio', ev); audioConfigEl.classList.add('audioconfig')
// this.audioEl.play(); this.wrapperEl.appendChild(audioConfigEl);
let audioSelectEl = document.createElement('select');
audioSelectEl.classList.add('audioselect');
audioConfigEl.appendChild(audioSelectEl);
fetch('/audio')
.then(response => response.json())
.then(data => {
data.unshift(''); // add empty, to deselect any file
data.forEach(audioFile => {
let optionEl = document.createElement('option');
optionEl.selected = this.audioFile == audioFile;
optionEl.innerText = audioFile;
audioSelectEl.appendChild(optionEl);
});
})
audioSelectEl.addEventListener('change', (ev) => {
this.setAudioFile(ev.target.value);
}); });
if (this.config.is_player) {
this.wrapperEl.prepend(this.audioEl);
}
else {
let audioConfigEl = document.createElement('div'); let audioOffsetTextEl = document.createElement('label');
audioConfigEl.classList.add('audioconfig') audioOffsetTextEl.innerText = "Offset (s)";
this.wrapperEl.appendChild(audioConfigEl); audioConfigEl.appendChild(audioOffsetTextEl);
audioConfigEl.prepend(this.audioEl); let audioOffsetEl = document.createElement('input');
audioOffsetEl.setAttribute('type', 'number');
let audioSelectEl = document.createElement('select'); audioOffsetEl.setAttribute('step', '.01');
audioSelectEl.classList.add('audioselect'); audioOffsetEl.value = this.audioOffset ?? 0;
audioConfigEl.appendChild(audioSelectEl); audioOffsetEl.addEventListener('change', (ev) => {
this.setAudioOffset(ev.target.value);
fetch('/audio') });
.then(response => response.json()) audioOffsetTextEl.appendChild(audioOffsetEl);
.then(data => {
data.unshift(''); // add empty, to deselect any file
data.forEach(audioFile => {
let optionEl = document.createElement('option');
optionEl.selected = this.audioFile == audioFile;
optionEl.innerText = audioFile;
audioSelectEl.appendChild(optionEl);
});
})
audioSelectEl.addEventListener('change', (ev) => {
this.setAudioFile(ev.target.value);
});
let audioOffsetTextEl = document.createElement('label'); this.audioEl = document.createElement('audio');
audioOffsetTextEl.innerText = "Offset (s)"; this.audioEl.setAttribute('controls', true);
audioConfigEl.appendChild(audioOffsetTextEl); this.audioEl.addEventListener('canplaythrough', (ev) => {
console.log('loaded audio', ev);
let audioOffsetEl = document.createElement('input'); this.audioEl.play();
audioOffsetEl.setAttribute('type', 'number'); });
audioOffsetEl.setAttribute('step', '.01'); // this.audioEl.addEventListener('seeked', (ev)=>{
audioOffsetEl.value = this.audioOffset ?? 0; // console.log(ev);
audioOffsetEl.addEventListener('change', (ev) => { // })
this.setAudioOffset(ev.target.value); audioConfigEl.prepend(this.audioEl);
});
audioOffsetTextEl.appendChild(audioOffsetEl);
}
this.audioEl.addEventListener('loadedmetadata', (ev) => { this.audioEl.addEventListener('loadedmetadata', (ev) => {
// resolve the 'set up audio' when metadata has loaded // resolve the 'set up audio' when metadata has loaded
@ -908,15 +827,11 @@ class Annotator extends EventTarget {
console.debug('delay audio playback', t_start, t_diff); console.debug('delay audio playback', t_start, t_diff);
// a negative audiooffset delays playback from the start // a negative audiooffset delays playback from the start
// this.audioStartTimeout = setTimeout((e) => this.audioEl.play(), t*-1000); // this.audioStartTimeout = setTimeout((e) => this.audioEl.play(), t*-1000);
this.audioStartTimeout = setTimeout((e) => { this.audioEl.currentTime = 0; this.audioEl.play(); }, t_start * -1); // triggers play with "seeked" event this.audioStartTimeout = setTimeout((e) => { this.audioEl.currentTime = 0 }, t_start * -1); // triggers play with "seeked" event
// this.audioEl.currentTime = 0; // this.audioEl.currentTime = 0;
} }
} else { } else {
if (this.audioEl.currentTime !== t_start / 1000) { this.audioEl.currentTime = t_start / 1000;
console.log(this.audioEl.currentTime, t_start / 1000);
this.audioEl.currentTime = t_start / 1000;
}
this.audioEl.play();
// this.audioEl.play(); // play is done in "seeked" evenlistener // this.audioEl.play(); // play is done in "seeked" evenlistener
console.log(this.audioEl.currentTime, t_start, t_in, t_out); console.log(this.audioEl.currentTime, t_start, t_in, t_out);
} }
@ -929,10 +844,6 @@ class Annotator extends EventTarget {
}, t_diff); }, t_diff);
} }
_scrubAudio(time_ms) {
this.audioEl.currentTime = Math.max(0, this.getAudioTime(time_ms)) / 1000;
}
getFinalFrameTime() { getFinalFrameTime() {
const points = this.strokes[this.strokes.length - 1].points; const points = this.strokes[this.strokes.length - 1].points;
return points[points.length - 1][3]; return points[points.length - 1][3];
@ -1013,9 +924,7 @@ class Annotator extends EventTarget {
} }
this.currentViewboxI = box_i this.currentViewboxI = box_i
const b = this.viewboxes[box_i]; const b = this.viewboxes[box_i];
if (!this.config.crop_to_fit) { this.svgEl.setAttribute('viewBox', `${b.x} ${b.y} ${this.dimensions[0]} ${this.dimensions[1]}`)
this.svgEl.setAttribute('viewBox', `${b.x} ${b.y} ${this.dimensions[0]} ${this.dimensions[1]}`)
}
} }
getNextPosition(path_i, point_i) { getNextPosition(path_i, point_i) {
@ -1144,42 +1053,25 @@ class Annotator extends EventTarget {
this._seekByTimeMs(this._currentTimeMs); // prevent playback issue for initial load this._seekByTimeMs(this._currentTimeMs); // prevent playback issue for initial load
} }
this.startTimeMs = window.performance.now() - this._currentTimeMs;
// strokes
if (this._currentTimeMs < 0) {
this.startVideoTimeout = setTimeout((e) => this.playStrokePosition(this.currentPathI, this.currentPointI), this._currentTimeMs * -1);
} else {
this.playStrokePosition(this.currentPathI, this.currentPointI);
}
// viewboxes
// const nextViewboxI = Math.max(this.currentViewboxI++, this.viewboxes.length-1);
this.playViewboxPosition(this.currentViewboxI);
// audio
this.playAudioSegment(this._currentTimeMs, this.outPointTimeMs);
// this.playStrokePosition(this.currentPathI, this.currentPointI);
this._setPausedFlag(false); this._setPausedFlag(false);
const startPlayback = () => { this.dispatchEvent(new CustomEvent('play', {}));
console.log('start playback'); this._animationFrame();
this.startTimeMs = window.performance.now() - this._currentTimeMs; resolve();
// strokes
if (this._currentTimeMs < 0) {
this.startVideoTimeout = setTimeout((e) => this.playStrokePosition(this.currentPathI, this.currentPointI), this._currentTimeMs * -1);
} else {
this.playStrokePosition(this.currentPathI, this.currentPointI);
}
// viewboxes
// const nextViewboxI = Math.max(this.currentViewboxI++, this.viewboxes.length-1);
this.playViewboxPosition(this.currentViewboxI);
// audio
// TODO: use this.audioEl.readyState == 4 : play immediately, otherwise after event
this.playAudioSegment(this._currentTimeMs, this.outPointTimeMs);
// this.playStrokePosition(this.currentPathI, this.currentPointI);
this.dispatchEvent(new CustomEvent('play', {}));
this._animationFrame();
resolve();
}
if (this.audioEl.readyState !== 4) { // not ready to play after seeking audio.
console.log('wait for audio before playback');
this.wrapperEl.classList.add('buffering');
this.audioEl.addEventListener('canplaythrough', () => {
this.wrapperEl.classList.remove('buffering');
startPlayback()
}, { once: true }); // only once
} else {
startPlayback();
}
}); });
} }
@ -1253,7 +1145,6 @@ class Annotator extends EventTarget {
_seekByPoint(point) { _seekByPoint(point) {
this.dispatchEvent(new CustomEvent('seeking', {})); this.dispatchEvent(new CustomEvent('seeking', {}));
this._currentTimeMs = this.strokes[point[0]].points[point[1]][2]; this._currentTimeMs = this.strokes[point[0]].points[point[1]][2];
this.audioEl.currentTime = this.getAudioTime(this._currentTimeMs) / 1000;
[this.currentPathI, this.currentPointI] = point; [this.currentPathI, this.currentPointI] = point;
this._updatePlayhead(); this._updatePlayhead();
this._updateFrame(); this._updateFrame();
@ -1267,7 +1158,6 @@ class Annotator extends EventTarget {
_seekByTime(time) { _seekByTime(time) {
this.dispatchEvent(new CustomEvent('seeking', { detail: time })); this.dispatchEvent(new CustomEvent('seeking', { detail: time }));
this._currentTimeMs = Number.parseFloat(time) * 1000; this._currentTimeMs = Number.parseFloat(time) * 1000;
this.audioEl.currentTime = this.getAudioTime(this._currentTimeMs) / 1000;
[this.currentPathI, this.currentPointI] = this.findPositionForTime(this._currentTimeMs); [this.currentPathI, this.currentPointI] = this.findPositionForTime(this._currentTimeMs);
this._updatePlayhead(); this._updatePlayhead();