No pause when finished tagging. Resume where left of with tagging and playback. Two subs. Better playback duration.

This commit is contained in:
Ruben van de Ven 2020-06-19 16:45:30 +02:00
parent 9eef55db16
commit 67945cc32f
2 changed files with 154 additions and 35 deletions

View file

@ -84,6 +84,22 @@
left: calc(50% - 150px); left: calc(50% - 150px);
font-size: 30px; font-size: 30px;
} }
#resetButton, #resumeButton{
display:none;
}
body.finished #resetButton{
display:inline;
}
body.finished.paused #resumeButton{
display:block;
}
#nextButton{
}
body.finished #nextButton{
display:none;
}
</style> </style>
@ -109,20 +125,28 @@
<!-- contents of template will come here through the javascript --> <!-- contents of template will come here through the javascript -->
</div> </div>
<input type='button' value='Next image' id='nextButton'> <input type='button' value='Next image' id='nextButton'>
<input type='button' value='Restart weighing' id='resetButton'>
<!-- button is needed because audio cannot auto-start without a user clicking on the page -->
<input type='button' value='Resume interview' id='resumeButton'>
<audio id='interview'
src="/interview/interview-with-ellen.mp3">
<track kind="captions"
srclang="en"
src="/interview/interview-with-ellen_v3.mp3.vtt"/>
<track default kind="captions"
srclang="nl"
src="/interview/interview-with-ellen_v3.mp3 copy.vtt"/>
</audio>
</div> </div>
<audio id='interview'
src="/interview/interview-with-ellen.mp3">
<track default kind="captions"
srclang="en"
src="/interview/interview-with-ellen_v3.mp3.vtt"/>
</audio>
<div id='subtitles'> <div id='subtitles'>
<div id='sub_nl'></div>
<div id='sub_en'></div>
</div> </div>
<div id='workmsg'> <div id='workmsg'>

147
parade.js
View file

@ -6,7 +6,8 @@ var config = {
distanceFactor: 20, // distance between the shapes distanceFactor: 20, // distance between the shapes
startOffset: 0.001, // starting position startOffset: 0.001, // starting position
shapeWidth: 100, // 8 shape of animation: width shapeWidth: 100, // 8 shape of animation: width
shapeHeight: 1300 // 8 shape of animation: height; shapeHeight: 1300, // 8 shape of animation: height;
playDuration: 16, // seconds. how long should audio play after dragging/pressing next. Something like audio duration / nr of images. Note that if everything is sorted interview will just continue;
} }
// polyfill // polyfill
@ -35,7 +36,6 @@ fetch(request)
// loaded data from json, initialise interface // loaded data from json, initialise interface
const canvas = document.getElementById("renderCanvas"); const canvas = document.getElementById("renderCanvas");
parade = new Parade(canvas, data['image'], config); parade = new Parade(canvas, data['image'], config);
parade.createScene();
// parade.scene.debugLayer.show(); // parade.scene.debugLayer.show();
}).catch(error => { }).catch(error => {
console.error(error); console.error(error);
@ -50,6 +50,7 @@ class Parade {
this.canvasEl = canvasEl; this.canvasEl = canvasEl;
this.engine = new BABYLON.Engine(canvasEl, true); // create 3d engine this.engine = new BABYLON.Engine(canvasEl, true); // create 3d engine
this.isPlaying = false; this.isPlaying = false;
this.t = 0; // playback timer
// initialise image objects: // initialise image objects:
this.images = []; this.images = [];
@ -80,6 +81,7 @@ class Parade {
this.setupSubtitles(); this.setupSubtitles();
this.setupSorter(); this.setupSorter();
this.restoreTime();
// Watch for browser/canvas resize events // Watch for browser/canvas resize events
window.addEventListener("resize", function () { window.addEventListener("resize", function () {
@ -88,26 +90,47 @@ class Parade {
//Register a render loop to repeatedly render the scene //Register a render loop to repeatedly render the scene
this.engine.runRenderLoop(this.renderLoop.bind(this)); this.engine.runRenderLoop(this.renderLoop.bind(this));
this.createScene();
// browsers block auto-play of audio
// if(this.imagesToSort.length < 1){
// this.play();
// }
} }
setupSubtitles() { setupSubtitles() {
this.audioEl = document.getElementById('interview'); // audio player this.audioEl = document.getElementById('interview'); // audio player
this.subtitleEl = document.getElementById('subtitles'); // the subtitle div this.subtitleEls = {
this.trackEl = this.audioEl.querySelector('track'); // the subtitle track 'en': document.getElementById('sub_en'),
'nl': document.getElementById('sub_nl'),
};
this.trackEls = this.audioEl.querySelectorAll('track'); // the subtitle track
this.timeEl = document.getElementById('time'); // the subtitle track this.timeEl = document.getElementById('time'); // the subtitle track
// set up subtitles: // set up subtitles:
this.trackEl.addEventListener("cuechange", (event) => { for(let trackEl of this.trackEls){
let cues = event.target.track.activeCues; trackEl.addEventListener("cuechange", (event) => {
let content = ""; let lang = event.srcElement.srclang;
for(let cue of cues) { let cues = event.target.track.activeCues;
content += "<p>"+cue.text+"</p>"; let content = "";
} for(let cue of cues) {
this.subtitleEl.innerHTML = content; content += "<p>"+cue.text+"</p>";
}); }
this.subtitleEls[lang].innerHTML = content;
});
}
for(let track of this.audioEl.textTracks){
// by default only one (or none) is set to showing.
// for some reason setting it on trackEl doesn't work, while this does the trick.
track.mode = 'showing';
}
this.audioEl.addEventListener('timeupdate', this.updateTimestring.bind(this)); this.audioEl.addEventListener('timeupdate', this.updateTimestring.bind(this));
this.audioEl.addEventListener('timeupdate', this.storeTime.bind(this));
this.updateTimestring(); this.updateTimestring();
} }
@ -123,25 +146,87 @@ class Parade {
this.timeEl.innerHTML = timestring; this.timeEl.innerHTML = timestring;
} }
storeTime() {
// do we use this.t or audioEl...?
localStorage.setItem('playhead', String(this.audioEl.currentTime))
}
restoreTime() {
let time = localStorage.getItem('playhead');
this.audioEl.addEventListener('playing', (e) => {
if(!this.isPlaying) this.play(); // make sure we're playing.
});
// causes odd glitch
// this.audioEl.addEventListener('pause', (e) => {
// if(this.isPlaying) this.pause(true); // make sure we're playing.
// });
this.audioEl.addEventListener('seeking',(e)=>{
this.t = parseInt(e.target.currentTime * 1000);
console.log('restore timer',this.t,e);
});
if(!time) return;
time = parseFloat(time);
this.audioEl.currentTime = time;
}
setupSorter() { setupSorter() {
this.sorterTemplate = document.getElementById('annotationContentTemplate'); this.sorterTemplate = document.getElementById('annotationContentTemplate');
this.sorter = document.getElementById('annotation'); this.sorter = document.getElementById('annotation');
this.pauseTimeout = null; this.pauseTimeout = null;
this.imagesToSort = this.images.slice(); // we will keep track of images that needs weighing (clone by reference) let imagesToSort = localStorage.getItem('imagesToSort');
if(imagesToSort) {
this.imagesToSort = JSON.parse(imagesToSort);
}
// else or invalid:
if(!imagesToSort || this.imagesToSort.length > this.images.length) {
console.log('createimages to sort')
this.imagesToSort = []; // we will keep track of images that needs weighing
for (let index = 0; index < this.images.length; index++) {
this.imagesToSort[index] = index;
}
}
let nextButton = document.getElementById('nextButton'); let nextButton = document.getElementById('nextButton');
nextButton.addEventListener('click', this.nextSorterImage.bind(this)); nextButton.addEventListener('click', this.nextSorterImage.bind(this));
let resetButton = document.getElementById('resetButton');
resetButton.addEventListener('click', this.resetSortedImages.bind(this));
let resumeButton = document.getElementById('resumeButton');
resumeButton.addEventListener('click', this.play.bind(this));
// set maximum to last image index // set maximum to last image index
this.sorterTemplate.content.querySelector('.weight').max = this.images.length - 1; this.sorterTemplate.content.querySelector('.weight').max = this.images.length - 1;
this.changeSorterImage(); this.changeSorterImage();
} }
resetSortedImages() {
this.imagesToSort = []; // we will keep track of images that needs weighing
for (let index = 0; index < this.images.length; index++) {
this.imagesToSort[index] = index;
}
this.changeSorterImage(); // display an image to sort
this.play(); // make sure playback timer gets set
}
changeSorterImage() { changeSorterImage() {
let sorterContent = this.sorter.querySelector('#annotationContent');
if(!this.imagesToSort.length) {
document.body.classList.add('finished');
sorterContent.innerHTML = "";
this.audioEl.controls = true;
return;
} else {
document.body.classList.remove('finished');
this.audioEl.controls = false;
}
// pick random image // pick random image
this.imageToSort = this.imagesToSort[Math.floor(Math.random()*this.imagesToSort.length)]; this.imageIdToSort = this.imagesToSort[Math.floor(Math.random()*this.imagesToSort.length)];
// this.imageToSort = this.imagesToSort[0]; // always the first instead of random this.imageToSort = this.images[this.imageIdToSort];
let clone = this.sorterTemplate.content.cloneNode(true); let clone = this.sorterTemplate.content.cloneNode(true);
let imgEl = clone.querySelector(".img"); let imgEl = clone.querySelector(".img");
@ -156,7 +241,6 @@ class Parade {
weightEl.value = parseInt(this.imageToSort.position); // don't use weight, but the image's actual position weightEl.value = parseInt(this.imageToSort.position); // don't use weight, but the image's actual position
weightEl.addEventListener('input', this.changeSorterWeight.bind(this)); weightEl.addEventListener('input', this.changeSorterWeight.bind(this));
let sorterContent = this.sorter.querySelector('#annotationContent');
sorterContent.innerHTML = ""; sorterContent.innerHTML = "";
sorterContent.appendChild(clone); sorterContent.appendChild(clone);
} }
@ -190,8 +274,10 @@ class Parade {
nextSorterImage() { nextSorterImage() {
this.play(); this.play();
let i = this.imagesToSort.indexOf(this.imageToSort); let idx = this.imagesToSort.indexOf(this.imageIdToSort);
this.imagesToSort.splice(i, 1); // remove from images to position this.imagesToSort.splice(idx, 1); // remove from images to position
// store the array
localStorage.setItem('imagesToSort', JSON.stringify(this.imagesToSort));
this.changeSorterImage(); this.changeSorterImage();
} }
@ -199,23 +285,32 @@ class Parade {
play() { play() {
// play audio for n seconds: // play audio for n seconds:
// extend timeout if it's already playing // extend timeout if it's already playing
const duration = 10; // seconds const duration = this.config.playDuration; // seconds
if(this.pauseTimeout) { if(this.pauseTimeout) {
clearTimeout(this.pauseTimeout); clearTimeout(this.pauseTimeout);
} }
this.pauseTimeout = setTimeout(() => { if(this.imagesToSort.length > 0){
this.pause(); this.pauseTimeout = setTimeout(() => {
}, duration * 1000); this.pause();
}, duration * 1000);
} else {
// don't pause anymore when everything is sorted.
this.pauseTimeout = null;
}
this.audioEl.play(); this.audioEl.play();
document.body.classList.remove('paused'); document.body.classList.remove('paused');
this.isPlaying = true; this.isPlaying = true;
} }
pause() { pause(dopause) {
if(this.imagesToSort.length < 1 && (typeof dopause == 'undefined' || false)){
console.log('do not pause');
return;
}
// pause audio and animation // pause audio and animation
this.audioEl.pause();
document.body.classList.add('paused'); document.body.classList.add('paused');
this.isPlaying = false; this.isPlaying = false;
this.audioEl.pause();
} }
renderLoop() { renderLoop() {
@ -237,6 +332,8 @@ class Parade {
if(this.isPlaying) { if(this.isPlaying) {
// todo: make it come to a slow halt, instead of sudden; // todo: make it come to a slow halt, instead of sudden;
dt = n - this.lastRenderTime; dt = n - this.lastRenderTime;
if(dt < 0)
console.log('change',dt);
this.t += dt; this.t += dt;
} }
this.lastRenderTime = n; // also increment when paused; this.lastRenderTime = n; // also increment when paused;
@ -270,7 +367,6 @@ class Parade {
// This attaches the camera to the canvas // This attaches the camera to the canvas
camera.attachControl(this.canvasEl, true); camera.attachControl(this.canvasEl, true);
let radius = 60;
for(let i in this.images) { for(let i in this.images) {
// create the Meshes with the images in the scene // create the Meshes with the images in the scene
this.images[i].makeMesh(this.scene); this.images[i].makeMesh(this.scene);
@ -278,7 +374,6 @@ class Parade {
this.lastRenderTime = window.performance.now(); this.lastRenderTime = window.performance.now();
this.t = 0; // playback timer
return this.scene; return this.scene;
} }
} }