diff --git a/wp-hyperaudio/js/hyperaudio-lite.js b/wp-hyperaudio/js/hyperaudio-lite.js index 237e109..7b6c9e5 100644 --- a/wp-hyperaudio/js/hyperaudio-lite.js +++ b/wp-hyperaudio/js/hyperaudio-lite.js @@ -1,8 +1,117 @@ /*! (C) The Hyperaudio Project. MIT @license: en.wikipedia.org/wiki/MIT_License. */ -/*! Version 2.0.14 */ +/*! Version 2.0.17 */ 'use strict'; +function nativePlayer(instance) { + this.player = instance.player; + this.player.addEventListener('pause', instance.clearTimer, false); + this.player.addEventListener('play', instance.checkPlayHead, false); + + this.getTime = () => { + return new Promise((resolve) => { + resolve(this.player.currentTime); + }); + } + + this.setTime = (seconds) => { + this.player.currentTime = seconds; + } + + this.play = () => { + this.player.play(); + } + + this.pause = () => { + this.player.pause(); + } +} + +function soundcloudPlayer(instance) { + this.player = SC.Widget(instance.player.id); + this.player.bind(SC.Widget.Events.PAUSE, instance.clearTimer); + this.player.bind(SC.Widget.Events.PLAY, instance.checkPlayHead); + + this.getTime = () => { + return new Promise((resolve) => { + this.player.getPosition(ms => { + resolve(ms / 1000); + }); + }); + } + + this.setTime = (seconds) => { + this.player.seekTo(seconds * 1000); + } + + this.play = () => { + this.player.play(); + } + + this.pause = () => { + this.player.pause(); + } +} + +function youtubePlayer(instance) { + const tag = document.createElement('script'); + tag.id = 'iframe-demo'; + tag.src = 'https://www.youtube.com/iframe_api'; + const firstScriptTag = document.getElementsByTagName('script')[0]; + firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); + + const previousYTEvent = window.onYouTubeIframeAPIReady; + window.onYouTubeIframeAPIReady = () => { + if (typeof previousYTEvent !== 'undefined') { // used for multiple YouTube players + previousYTEvent(); + } + + this.player = new YT.Player(instance.player.id, { + events: { + onStateChange: onPlayerStateChange, + }, + }); + }; + + let onPlayerStateChange = event => { + if (event.data === 1) { + // playing + instance.checkPlayHead(); + } else if (event.data === 2) { + // paused + instance.clearTimer(); + } + }; + + this.getTime = () => { + return new Promise((resolve) => { + resolve(this.player.getCurrentTime()); + }); + } + + this.setTime = (seconds) => { + this.player.seekTo(seconds, true); + } + + this.play = () => { + this.player.playVideo(); + } + + this.pause = () => { + this.player.pauseVideo(); + } +} + +const hyperaudioPlayerOptions = { + "native": nativePlayer, + "soundcloud": soundcloudPlayer, + "youtube": youtubePlayer +} + +function hyperaudioPlayer(playerType, instance) { + return new playerType(instance); +} + class HyperaudioLite { constructor(transcriptId, mediaElementId, minimizedMode, autoscroll, doubleClick, webMonetization) { this.transcript = document.getElementById(transcriptId); @@ -14,7 +123,7 @@ class HyperaudioLite { const hashVar = windowHash.substring(1, windowHash.indexOf('=')); if (hashVar === this.transcript.id) { - this.hashArray = windowHash.substr(this.transcript.id.length + 2).split(','); + this.hashArray = windowHash.substring(this.transcript.id.length + 2).split(','); } else { this.hashArray = []; } @@ -45,22 +154,18 @@ class HyperaudioLite { this.webMonetization = w; this.highlightedText = false; this.start = null; - + + this.myPlayer = null; if (this.autoscroll === true) { this.scroller = window.Velocity || window.jQuery.Velocity; } //Create the array of timed elements (wordArr) - const words = this.transcript.querySelectorAll('[data-m]'); - this.wordArr = this.createWordArray(words); - this.parentTag = words[0].parentElement.tagName; this.parentElements = this.transcript.getElementsByTagName(this.parentTag); - - this.player = document.getElementById(mediaElementId); if (this.player.tagName == 'VIDEO' || this.player.tagName == 'AUDIO') { @@ -71,52 +176,10 @@ class HyperaudioLite { this.playerType = this.player.getAttribute('data-player-type'); } - if (this.playerType === 'native') { - this.player.addEventListener('pause', this.clearTimer, false); - this.player.addEventListener('play', this.checkPlayHead, false); - } else if (this.playerType === 'soundcloud') { - // SoundCloud - this.player = SC.Widget(mediaElementId); - this.player.bind(SC.Widget.Events.PAUSE, this.clearTimer); - this.player.bind(SC.Widget.Events.PLAY, this.checkPlayHead); - } else { - // assume YouTube - const tag = document.createElement('script'); - tag.id = 'iframe-demo'; - tag.src = 'https://www.youtube.com/iframe_api'; - const firstScriptTag = document.getElementsByTagName('script')[0]; - firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); - - const previousYTEvent = window.onYouTubeIframeAPIReady; - window.onYouTubeIframeAPIReady = () => { - - if (typeof previousYTEvent !== 'undefined') { // used for multiple YouTube players - previousYTEvent(); - } - - this.player = new YT.Player(mediaElementId, { - events: { - onStateChange: onPlayerStateChange, - }, - }); - }; - - let onPlayerStateChange = event => { - if (event.data === 1) { - // playing - this.checkPlayHead(); - } else if (event.data === 2) { - // paused - this.clearTimer(); - } - }; - } - + this.myPlayer = hyperaudioPlayer(hyperaudioPlayerOptions[this.playerType], this); this.parentElementIndex = 0; - words[0].classList.add('active'); this.parentElements[0].classList.add('active'); - let playHeadEvent = 'click'; if (this.doubleClick === true) { @@ -130,7 +193,7 @@ class HyperaudioLite { if (!isNaN(parseFloat(this.start))) { this.highlightedText = true; - + let indices = this.updateTranscriptVisualState(this.start); let index = indices.currentWordIndex; @@ -142,16 +205,15 @@ class HyperaudioLite { this.end = this.hashArray[1]; //TODO convert to binary search for below for quicker startup - if (this.start && this.end) { for (let i = 1; i < words.length; i++) { const wordStart = parseInt(words[i].getAttribute('data-m')) / 1000; - if (wordStart > this.start && this.end > wordStart) { + if (wordStart > parseFloat(this.start) && parseFloat(this.end) > wordStart) { words[i].classList.add('share-match'); } } } - }; + }; // end init createWordArray = words => { let wordArr = []; @@ -189,7 +251,7 @@ class HyperaudioLite { if (selection.toString() !== '') { let fNode = selection.focusNode.parentNode; let aNode = selection.anchorNode.parentNode; - + if (aNode.tagName === "P") { aNode = selection.anchorNode.nextElementSibling; } @@ -206,18 +268,30 @@ class HyperaudioLite { fNode = fNode.previousElementSibling; } + // if the selection starts with a space we want the next element + if(selection.toString().charAt(0) == " ") { + aNode = aNode.nextElementSibling; + } + let aNodeTime = parseInt(aNode.getAttribute('data-m'), 10); let aNodeDuration = parseInt(aNode.getAttribute('data-d'), 10); let fNodeTime; let fNodeDuration; if (fNode != null && fNode.getAttribute('data-m') != null) { + // if the selection ends in a space we want the previous element + if(selection.toString().slice(-1) == " ") { + fNode = fNode.previousElementSibling; + } + fNodeTime = parseInt(fNode.getAttribute('data-m'), 10); fNodeDuration = parseInt(fNode.getAttribute('data-d'), 10); + + // if the selection starts with a space we want the next element + } // 1 decimal place will do - aNodeTime = Math.round(aNodeTime / 100) / 10; aNodeDuration = Math.round(aNodeDuration / 100) / 10; fNodeTime = Math.round(fNodeTime / 100) / 10; @@ -248,7 +322,6 @@ class HyperaudioLite { this.highlightedText = false; // clear elements with class='active' - let activeElements = Array.from(this.transcript.getElementsByClassName('active')); activeElements.forEach(e => { @@ -261,18 +334,7 @@ class HyperaudioLite { if (!isNaN(parseFloat(timeSecs))) { this.end = null; - - if (this.playerType === 'native') { - this.player.currentTime = timeSecs; - this.player.play(); - } else if (this.playerType === 'soundcloud') { - this.player.seekTo(timeSecs * 1000); - this.player.play(); - } else { - //assume YouTube - this.player.seekTo(timeSecs, true); - this.player.playVideo(); - } + this.myPlayer.setTime(timeSecs); } }; @@ -281,47 +343,18 @@ class HyperaudioLite { }; checkPlayHead = () => { - this.clearTimer(); - if (this.playerType === 'native') { - this.currentTime = this.player.currentTime; - if (this.highlightedText === true) { - this.currentTime = this.start; - this.player.currentTime = this.currentTime; - this.highlightedText = false; - } - this.checkStatus(); - } else if (this.playerType === 'soundcloud') { - - this.player.getPosition(ms => { - this.currentTime = ms / 1000; - if (this.highlightedText === true) { - this.currentTime = this.start; - this.player.seekTo(this.currentTime * 1000); - this.highlightedText = false; - } - - this.checkStatus(); - }); - } else { - // assume YouTube - - // Note – there is an issue when the video is already cached - // and we're attempting to play a highlighted section, - // where it jumps to the buffered point before seeking to - // the correct place – but doesn't play. - // May have to detect a suitable event before seeking. - - this.currentTime = this.player.getCurrentTime(); + (async (instance) => { + instance.currentTime = await instance.myPlayer.getTime(); - if (this.highlightedText === true) { - this.currentTime = this.start; - this.player.seekTo(this.start, true); - this.highlightedText = false; + if (instance.highlightedText === true) { + instance.currentTime = instance.start; + instance.myPlayer.setTime(instance.currentTime); + instance.highlightedText = false; } - this.checkStatus(); - } + instance.checkStatus(); + })(this); } scrollToParagraph = (currentParentElementIndex, index) => { @@ -360,7 +393,6 @@ class HyperaudioLite { } newPara = true; - this.parentElementIndex = currentParentElementIndex; } return(newPara); @@ -368,16 +400,8 @@ class HyperaudioLite { checkStatus = () => { //check for end time of shared piece - - if (this.end && this.end < this.currentTime) { - if (this.playerType === 'native') { - this.player.pause(); - } else if (this.playerType === 'soundcloud') { - this.player.pause(); - } else { - // assume YouTube - this.player.pauseVideo(); - } + if (this.end && parseInt(this.end) < parseInt(this.currentTime)) { + this.myPlayer.pause(); this.end = null; } else { let newPara = false; @@ -391,7 +415,6 @@ class HyperaudioLite { } //minimizedMode is still experimental - it changes document.title upon every new word - if (this.minimizedMode) { const elements = transcript.querySelectorAll('[data-m]'); let currentWord = ''; @@ -428,9 +451,7 @@ class HyperaudioLite { if (this.webMonetization === true) { //check for payment pointer - let activeElements = this.transcript.getElementsByClassName('active'); - let paymentPointer = this.checkPaymentPointer(activeElements[activeElements.length - 1]); if (paymentPointer !== null) { @@ -451,7 +472,7 @@ class HyperaudioLite { checkPaymentPointer = element => { let paymentPointer = null; - + if (typeof(element) != "undefined") { paymentPointer = element.getAttribute('data-wm'); } @@ -497,6 +518,7 @@ class HyperaudioLite { this.wordArr.forEach((word, i) => { let classList = word.n.classList; + if (i < index) { classList.add('read'); classList.remove('unread'); @@ -510,7 +532,6 @@ class HyperaudioLite { this.parentElements = this.transcript.getElementsByTagName(this.parentTag); //remove active class from all paras - Array.from(this.parentElements).forEach(el => { if (el.classList.contains('active')) { el.classList.remove('active'); @@ -518,7 +539,6 @@ class HyperaudioLite { }); // set current word and para to active - if (index > 0) { this.wordArr[index - 1].n.classList.add('active'); @@ -528,7 +548,6 @@ class HyperaudioLite { } // Establish current paragraph index - let currentParentElementIndex; Array.from(this.parentElements).every((el, i) => { @@ -568,4 +587,4 @@ if (typeof module !== 'undefined' && module.exports) { module.exports = { HyperaudioLite }; } -//export default HyperaudioLite; +//export default HyperaudioLite; \ No newline at end of file