From 67945cc32f134a083aec5d447060568b6a519b5b Mon Sep 17 00:00:00 2001 From: Ruben van de Ven Date: Fri, 19 Jun 2020 16:45:30 +0200 Subject: [PATCH] No pause when finished tagging. Resume where left of with tagging and playback. Two subs. Better playback duration. --- index.html | 42 +++++++++++---- parade.js | 147 +++++++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 154 insertions(+), 35 deletions(-) diff --git a/index.html b/index.html index dcf24bd..88c2390 100644 --- a/index.html +++ b/index.html @@ -84,6 +84,22 @@ left: calc(50% - 150px); font-size: 30px; } + + #resetButton, #resumeButton{ + display:none; + } + body.finished #resetButton{ + display:inline; + } + body.finished.paused #resumeButton{ + display:block; + } + #nextButton{ + } + body.finished #nextButton{ + display:none; + } + @@ -109,20 +125,28 @@ + + + + + -
- +
+
diff --git a/parade.js b/parade.js index 85bbf00..79d24c6 100644 --- a/parade.js +++ b/parade.js @@ -6,7 +6,8 @@ var config = { distanceFactor: 20, // distance between the shapes startOffset: 0.001, // starting position 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 @@ -35,7 +36,6 @@ fetch(request) // loaded data from json, initialise interface const canvas = document.getElementById("renderCanvas"); parade = new Parade(canvas, data['image'], config); - parade.createScene(); // parade.scene.debugLayer.show(); }).catch(error => { console.error(error); @@ -50,6 +50,7 @@ class Parade { this.canvasEl = canvasEl; this.engine = new BABYLON.Engine(canvasEl, true); // create 3d engine this.isPlaying = false; + this.t = 0; // playback timer // initialise image objects: this.images = []; @@ -80,6 +81,7 @@ class Parade { this.setupSubtitles(); this.setupSorter(); + this.restoreTime(); // Watch for browser/canvas resize events window.addEventListener("resize", function () { @@ -88,26 +90,47 @@ class Parade { //Register a render loop to repeatedly render the scene this.engine.runRenderLoop(this.renderLoop.bind(this)); + + this.createScene(); + + // browsers block auto-play of audio + // if(this.imagesToSort.length < 1){ + // this.play(); + // } } setupSubtitles() { this.audioEl = document.getElementById('interview'); // audio player - this.subtitleEl = document.getElementById('subtitles'); // the subtitle div - this.trackEl = this.audioEl.querySelector('track'); // the subtitle track + this.subtitleEls = { + '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 // set up subtitles: - this.trackEl.addEventListener("cuechange", (event) => { - let cues = event.target.track.activeCues; - let content = ""; - for(let cue of cues) { - content += "

"+cue.text+"

"; - } - this.subtitleEl.innerHTML = content; - }); + for(let trackEl of this.trackEls){ + trackEl.addEventListener("cuechange", (event) => { + let lang = event.srcElement.srclang; + let cues = event.target.track.activeCues; + let content = ""; + for(let cue of cues) { + content += "

"+cue.text+"

"; + } + 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.storeTime.bind(this)); + + this.updateTimestring(); } @@ -123,25 +146,87 @@ class Parade { 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() { this.sorterTemplate = document.getElementById('annotationContentTemplate'); this.sorter = document.getElementById('annotation'); 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'); 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 this.sorterTemplate.content.querySelector('.weight').max = this.images.length - 1; 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() { + + 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 - this.imageToSort = this.imagesToSort[Math.floor(Math.random()*this.imagesToSort.length)]; - // this.imageToSort = this.imagesToSort[0]; // always the first instead of random + this.imageIdToSort = this.imagesToSort[Math.floor(Math.random()*this.imagesToSort.length)]; + this.imageToSort = this.images[this.imageIdToSort]; let clone = this.sorterTemplate.content.cloneNode(true); 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.addEventListener('input', this.changeSorterWeight.bind(this)); - let sorterContent = this.sorter.querySelector('#annotationContent'); sorterContent.innerHTML = ""; sorterContent.appendChild(clone); } @@ -190,8 +274,10 @@ class Parade { nextSorterImage() { this.play(); - let i = this.imagesToSort.indexOf(this.imageToSort); - this.imagesToSort.splice(i, 1); // remove from images to position + let idx = this.imagesToSort.indexOf(this.imageIdToSort); + this.imagesToSort.splice(idx, 1); // remove from images to position + // store the array + localStorage.setItem('imagesToSort', JSON.stringify(this.imagesToSort)); this.changeSorterImage(); } @@ -199,23 +285,32 @@ class Parade { play() { // play audio for n seconds: // extend timeout if it's already playing - const duration = 10; // seconds + const duration = this.config.playDuration; // seconds if(this.pauseTimeout) { clearTimeout(this.pauseTimeout); } - this.pauseTimeout = setTimeout(() => { - this.pause(); - }, duration * 1000); + if(this.imagesToSort.length > 0){ + this.pauseTimeout = setTimeout(() => { + this.pause(); + }, duration * 1000); + } else { + // don't pause anymore when everything is sorted. + this.pauseTimeout = null; + } this.audioEl.play(); document.body.classList.remove('paused'); 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 - this.audioEl.pause(); document.body.classList.add('paused'); this.isPlaying = false; + this.audioEl.pause(); } renderLoop() { @@ -237,6 +332,8 @@ class Parade { if(this.isPlaying) { // todo: make it come to a slow halt, instead of sudden; dt = n - this.lastRenderTime; + if(dt < 0) + console.log('change',dt); this.t += dt; } this.lastRenderTime = n; // also increment when paused; @@ -270,7 +367,6 @@ class Parade { // This attaches the camera to the canvas camera.attachControl(this.canvasEl, true); - let radius = 60; for(let i in this.images) { // create the Meshes with the images in the scene this.images[i].makeMesh(this.scene); @@ -278,7 +374,6 @@ class Parade { this.lastRenderTime = window.performance.now(); - this.t = 0; // playback timer return this.scene; } }