X-Git-Url: http://git.rot13.org/?p=bookreader.git;a=blobdiff_plain;f=BookReader%2FBookReader.js;fp=BookReader%2FBookReader.js;h=36379a126d77b251fcde88a8f45dc2c324022ef4;hp=abc931bad89cb7b68476259171747169a2cf047a;hb=32dcce1c6c33c22670b8c651356595c6a8c2a0ad;hpb=b3bd264dcbb1471621319d5c24c54923fe0c7e9d diff --git a/BookReader/BookReader.js b/BookReader/BookReader.js index abc931b..36379a1 100644 --- a/BookReader/BookReader.js +++ b/BookReader/BookReader.js @@ -123,6 +123,14 @@ function BookReader() { // embed/share ui // info ui }; + + // Text-to-Speech params + this.ttsPlaying = false; + this.ttsIndex = null; //leaf index + this.ttsPosition = -1; //chunk (paragraph) number + this.ttsBuffering = false; + this.ttsPoller = null; + this.ttsFormat = null; return this; }; @@ -1297,6 +1305,7 @@ BookReader.prototype.switchMode = function(mode) { } this.autoStop(); + this.ttsStop(); this.removeSearchHilites(); this.mode = mode; @@ -1579,12 +1588,14 @@ BookReader.prototype.prepareTwoPagePopUp = function() { $(this.leafEdgeL).bind('click', this, function(e) { e.data.autoStop(); + e.data.ttsStop(); var jumpIndex = e.data.jumpIndexForLeftEdgePageX(e.pageX); e.data.jumpToIndex(jumpIndex); }); $(this.leafEdgeR).bind('click', this, function(e) { e.data.autoStop(); + e.data.ttsStop(); var jumpIndex = e.data.jumpIndexForRightEdgePageX(e.pageX); e.data.jumpToIndex(jumpIndex); }); @@ -2323,6 +2334,7 @@ BookReader.prototype.setMouseHandlers2UP = function() { this.setClickHandler2UP( this.prefetchedImgs[this.twoPage.currentIndexL], { self: this }, function(e) { + e.data.self.ttsStop(); e.data.self.left(); e.preventDefault(); } @@ -2331,6 +2343,7 @@ BookReader.prototype.setMouseHandlers2UP = function() { this.setClickHandler2UP( this.prefetchedImgs[this.twoPage.currentIndexR], { self: this }, function(e) { + e.data.self.ttsStop(); e.data.self.right(); e.preventDefault(); } @@ -2843,28 +2856,7 @@ BookReader.prototype.updateSearchHilites2UP = function() { //console.log('appending ' + key); } - // We calculate the reduction factor for the specific page because it can be different - // for each page in the spread - var height = this._getPageHeight(key); - var width = this._getPageWidth(key) - var reduce = this.twoPage.height/height; - var scaledW = parseInt(width*reduce); - - var gutter = this.twoPageGutter(); - var pageL; - if ('L' == this.getPageSide(key)) { - pageL = gutter-scaledW; - } else { - pageL = gutter; - } - var pageT = this.twoPageTop(); - - $(result.div).css({ - width: (result.r-result.l)*reduce + 'px', - height: (result.b-result.t)*reduce + 'px', - left: pageL+(result.l)*reduce + 'px', - top: pageT+(result.t)*reduce +'px' - }); + this.setHilightCss2UP(result.div, key, result.l, result.r, result.t, result.b); } else { //console.log(key + ' not displayed'); @@ -2877,6 +2869,35 @@ BookReader.prototype.updateSearchHilites2UP = function() { } } +// setHilightCss2UP() +//______________________________________________________________________________ +//position calculation shared between search and text-to-speech functions +BookReader.prototype.setHilightCss2UP = function(div, index, left, right, top, bottom) { + + // We calculate the reduction factor for the specific page because it can be different + // for each page in the spread + var height = this._getPageHeight(index); + var width = this._getPageWidth(index) + var reduce = this.twoPage.height/height; + var scaledW = parseInt(width*reduce); + + var gutter = this.twoPageGutter(); + var pageL; + if ('L' == this.getPageSide(index)) { + pageL = gutter-scaledW; + } else { + pageL = gutter; + } + var pageT = this.twoPageTop(); + + $(div).css({ + width: (right-left)*reduce + 'px', + height: (bottom-top)*reduce + 'px', + left: pageL+left*reduce + 'px', + top: pageT+top*reduce +'px' + }); +} + // removeSearchHilites() //______________________________________________________________________________ BookReader.prototype.removeSearchHilites = function() { @@ -2970,6 +2991,12 @@ BookReader.prototype.updatePrintFrame = function(delta) { // showEmbedCode() //______________________________________________________________________________ BookReader.prototype.showEmbedCode = function() { + if (null != this.embedPopup) { // check if already showing + return; + } + this.autoStop(); + this.ttsStop(); + this.embedPopup = document.createElement("div"); $(this.embedPopup).css({ position: 'absolute', @@ -3720,6 +3747,11 @@ BookReader.prototype.bindToolbarNavHandlers = function(jToolbar) { self.rightmost(); return false; }); + + jToolbar.find('.read').click(function(e) { + self.ttsToggle(); + return false; + }); // $$$mang cleanup $('#BRzoomer .zoom_in').bind('click', function() { @@ -4143,6 +4175,8 @@ BookReader.prototype.startLocationPolling = function() { if (newHash != self.oldUserHash) { // Only process new user hash once //console.log('url change detected ' + self.oldLocationHash + " -> " + newHash); + self.ttsStop(); + // Queue change if animating if (self.animating) { self.autoStop(); @@ -4304,3 +4338,413 @@ BookReader.util = { } // The final property here must NOT have a comma after it - IE7 } + + +// ttsToggle() +//______________________________________________________________________________ +BookReader.prototype.ttsToggle = function () { + if (false == this.ttsPlaying) { + if(soundManager.supported()) { + this.ttsStart(); + } else { + soundManager.onready(function(oStatus) { + if (oStatus.success) { + this.ttsStart(); + } else { + alert('Could not load soundManger2, possibly due to FlashBlock. Audio playback is disabled'); + } + }, this); + } + } else { + this.ttsStop(); + } +} + +// ttsStart() +//______________________________________________________________________________ +BookReader.prototype.ttsStart = function () { + if (soundManager.debugMode) console.log('starting readAloud'); + if (this.constModeThumb == this.mode) this.switchMode(this.constMode1up); + + this.ttsPlaying = true; + this.ttsIndex = this.currentIndex(); + this.ttsFormat = 'mp3'; + if ($.browser.mozilla) { + this.ttsFormat = 'ogg'; + } + this.ttsGetText(this.ttsIndex, 'ttsStartCB'); +} + +// ttsStop() +//______________________________________________________________________________ +BookReader.prototype.ttsStop = function () { + if (false == this.ttsPlaying) return; + + if (soundManager.debugMode) console.log('stopping readaloud'); + soundManager.stopAll(); + soundManager.destroySound('chunk'+this.ttsIndex+'-'+this.ttsPosition); + this.ttsRemoveHilites(); + this.ttsRemovePopup(); + + this.ttsPlaying = false; + this.ttsIndex = null; //leaf index + this.ttsPosition = -1; //chunk (paragraph) number + this.ttsBuffering = false; + this.ttsPoller = null; +} + +// ttsGetText() +//______________________________________________________________________________ +BookReader.prototype.ttsGetText = function(index, callback) { + var url = 'http://'+this.server+'/BookReader/BookReaderGetTextWrapper.php?path='+this.bookPath+'_djvu.xml&page='+index; + this.ttsAjax = $.ajax({url:url, dataType:'jsonp', jsonpCallback:callback}); +} + +// ttsStartCB(): text-to-speech callback +//______________________________________________________________________________ +BookReader.prototype.ttsStartCB = function (data) { + if (soundManager.debugMode) console.log('ttsStartCB got data: ' + data); + this.ttsChunks = data; + this.ttsHilites = []; + + //deal with the page being blank + if (0 == data.length) { + if (soundManager.debugMode) console.log('first page is blank!'); + if(this.ttsAdvance(true)) { + this.ttsGetText(this.ttsIndex, 'ttsStartCB'); + } + return; + } + + this.ttsShowPopup(); + + ///// whileloading: broken on safari + ///// onload fires on safari, but *after* the sound starts playing.. + this.ttsPosition = -1; + var snd = soundManager.createSound({ + id: 'chunk'+this.ttsIndex+'-0', + //url: 'http://home.us.archive.org/~rkumar/arctic.ogg', + url: 'http://'+this.server+'/BookReader/BookReaderGetTTS.php?string=' + escape(data[0][0]) + '&format=.'+this.ttsFormat, //the .ogg is to trick SoundManager2 to use the HTML5 audio player + whileloading: function(){if (this.bytesLoaded == this.bytesTotal) this.br.ttsRemovePopup();}, //onload never fires in FF... + onload: function(){this.br.ttsRemovePopup();} //whileloading never fires in safari... + }); + snd.br = this; + snd.load(); + + this.ttsNextChunk(); +} + +// ttsShowPopup +//______________________________________________________________________________ +BookReader.prototype.ttsShowPopup = function() { + if (soundManager.debugMode) console.log('ttsShowPopup index='+this.ttsIndex+' pos='+this.ttsPosition); + + this.popup = document.createElement("div"); + $(this.popup).css({ + top: $('#BRtoolbar').height() + 'px', + left: $('#BookReader').width()-220 + 'px', + width: '220px', + height: '20px', + }).attr('className', 'BRttsPopUp').appendTo('#BookReader'); + + htmlStr = ' '; + + this.popup.innerHTML = htmlStr; +} + +// ttsRemovePopup +//______________________________________________________________________________ +BookReader.prototype.ttsRemovePopup = function() { + $(this.popup).remove(); + this.popup=null; +} + +// ttsNextPageCB +//______________________________________________________________________________ +BookReader.prototype.ttsNextPageCB = function (data) { + this.ttsNextChunks = data; + if (soundManager.debugMode) console.log('preloaded next chunks.. data is ' + data); + + if (true == this.ttsBuffering) { + if (soundManager.debugMode) console.log('ttsNextPageCB: ttsBuffering is true'); + this.ttsBuffering = false; + } +} + +// ttsLoadChunk +//______________________________________________________________________________ +BookReader.prototype.ttsLoadChunk = function (page, pos, string) { + var snd = soundManager.createSound({ + id: 'chunk'+page+'-'+pos, + url: 'http://'+this.server+'/BookReader/BookReaderGetTTS.php?string=' + escape(string) + '&format=.'+this.ttsFormat //the .ogg is to trick SoundManager2 to use the HTML5 audio player + }); + snd.br = this; + snd.load() +} + + +// ttsNextChunk() +//______________________________________________________________________________ +// This function into two parts: ttsNextChunk gets run before page flip animation +// and ttsNextChunkPhase2 get run after page flip animation. +// If a page flip is necessary, ttsAdvance() will return false so Phase2 isn't +// called. Instead, this.animationFinishedCallback is set, so that Phase2 +// continues after animation is finished. + +BookReader.prototype.ttsNextChunk = function () { + if (soundManager.debugMode) console.log('nextchunk pos=' + this.ttsPosition); + + if (-1 != this.ttsPosition) { + soundManager.destroySound('chunk'+this.ttsIndex+'-'+this.ttsPosition); + } + + this.ttsRemoveHilites(); //remove old hilights + + var moreToPlay = this.ttsAdvance(); + + if (moreToPlay) { + this.ttsNextChunkPhase2(); + } + + //This function is called again when ttsPlay() has finished playback. + //If the next chunk of text has not yet finished loading, ttsPlay() + //will start polling until the next chunk is ready. +} + +// ttsNextChunkPhase2() +//______________________________________________________________________________ +// page flip animation has now completed +BookReader.prototype.ttsNextChunkPhase2 = function () { + if (null == this.ttsChunks) { + alert('error: ttsChunks is null?'); //TODO + return; + } + + if (0 == this.ttsChunks.length) { + if (soundManager.debugMode) console.log('ttsNextChunk2: ttsChunks.length is zero.. hacking...'); + this.ttsStartCB(this.ttsChunks); + return; + } + + if (soundManager.debugMode) console.log('next chunk is ' + this.ttsPosition); + + //prefetch next page of text + if (0 == this.ttsPosition) { + if (this.ttsIndex<(this.numLeafs-1)) { + this.ttsGetText(this.ttsIndex+1, 'ttsNextPageCB'); + } + } + + this.ttsPrefetchAudio(); + + this.ttsPlay(); +} + +// ttsAdvance() +//______________________________________________________________________________ +// 1. advance ttsPosition +// 2. if necessary, advance ttsIndex, and copy ttsNextChunks to ttsChunks +// 3. if necessary, flip to current page, or scroll so chunk is visible +// 4. do something smart is ttsNextChunks has not yet finished preloading (TODO) +// 5. stop playing at end of book + +BookReader.prototype.ttsAdvance = function (starting) { + this.ttsPosition++; + + if (this.ttsPosition >= this.ttsChunks.length) { + + if (this.ttsIndex == (this.numLeafs-1)) { + if (soundManager.debugMode) console.log('tts stop'); + return false; + } else { + if ((null != this.ttsNextChunks) || (starting)) { + if (soundManager.debugMode) console.log('moving to next page!'); + this.ttsIndex++; + this.ttsPosition = 0; + this.ttsChunks = this.ttsNextChunks; + this.ttsNextChunks = null; + + //A page flip might be necessary. This code is confusing since + //ttsNextChunks might be null if we are starting on a blank page. + if (2 == this.mode) { + if ((this.ttsIndex != this.twoPage.currentIndexL) && (this.ttsIndex != this.twoPage.currentIndexR)) { + if (!starting) { + this.animationFinishedCallback = this.ttsNextChunkPhase2; + this.next(); + return false; + } else { + this.next(); + return true; + } + } else { + return true; + } + } + } else { + if (soundManager.debugMode) console.log('ttsAdvance: ttsNextChunks is null'); + return false; + } + } + } + + return true; +} + +// ttsPrefetchAudio() +//______________________________________________________________________________ +BookReader.prototype.ttsPrefetchAudio = function () { + + if(false != this.ttsBuffering) { + alert('TTS Error: prefetch() called while content still buffering!'); + return; + } + + //preload next chunk + var nextPos = this.ttsPosition+1; + if (nextPos < this.ttsChunks.length) { + this.ttsLoadChunk(this.ttsIndex, nextPos, this.ttsChunks[nextPos][0]); + } else { + //for a short page, preload might nt have yet returned.. + if (soundManager.debugMode) console.log('preloading chunk 0 from next page, index='+(this.ttsIndex+1)); + if (null != this.ttsNextChunks) { + if (0 != this.ttsNextChunks.length) { + this.ttsLoadChunk(this.ttsIndex+1, 0, this.ttsNextChunks[0][0]); + } else { + if (soundManager.debugMode) console.log('prefetchAudio(): ttsNextChunks is zero length!'); + } + } else { + if (soundManager.debugMode) console.log('ttsNextChunks is null, not preloading next page'); + this.ttsBuffering = true; + } + } + +} + +// ttsPlay() +//______________________________________________________________________________ +BookReader.prototype.ttsPlay = function () { + + var chunk = this.ttsChunks[this.ttsPosition]; + if (soundManager.debugMode) { + console.log('ttsPlay position = ' + this.ttsPosition); + console.log('chunk = ' + chunk); + console.log(this.ttsChunks); + } + + //add new hilights + if (2 == this.mode) { + this.ttsHilite2UP(chunk); + } else { + this.ttsHilite1UP(chunk); + } + + this.ttsScrollToChunk(chunk); + + //play current chunk + if (false == this.ttsBuffering) { + soundManager.play('chunk'+this.ttsIndex+'-'+this.ttsPosition,{onfinish:function(){br.ttsNextChunk();}}); + } else { + soundManager.play('chunk'+this.ttsIndex+'-'+this.ttsPosition,{onfinish:function(){br.ttsStartPolling();}}); + } +} + +// scrollToChunk() +//______________________________________________________________________________ +BookReader.prototype.ttsScrollToChunk = function(chunk) { + if (this.constMode1up != this.mode) return; + + var leafTop = 0; + var h; + var i; + for (i=0; i