function ttsHiliteModule(window) {
var window = window,
document = window.document;
function getXPath(element) {
var xpath = '';
for (; element && element.nodeType == 1; element = element.parentNode) {
/* jQuery code: var id = $(element.parentNode).children(element.tagName).index(element) + 1; */
/* calculate the index of occurance of the "element" w.r.t the parentNode */
var checkPrevSiblingsOfElement = element.previousSibling;
var id = 1;
while (checkPrevSiblingsOfElement != null) { // It becomes null when it runs out of previous nodes
if (checkPrevSiblingsOfElement.nodeType === 1) { // Execute only if previousNode is an ELEMENT_NODE (especially omit text nodes)
if (checkPrevSiblingsOfElement.tagName.toLowerCase() === element.tagName.toLowerCase()) {
id++;
}
}
checkPrevSiblingsOfElement = checkPrevSiblingsOfElement.previousSibling;
}
id > 1 ? (id = '[' + id + ']') : (id = '');
xpath = '/' + element.tagName.toLowerCase() + id + xpath;
}
return xpath;
}
function applyHighlight(doc, serializedStr, className) {
var rangeParams = base64.decode(serializedStr).split(",");
var startOffset = parseInt(rangeParams[2]);
var endOffset = parseInt(rangeParams[3]);
var startContainerRelation = rangeParams[0].split("::")[1];
var endContainerRelation = rangeParams[1].split("::")[1];
var startContainer, endContainer;
if (startContainerRelation !== "null") {
startContainer = rangeParams[0].split("::")[1] === "prevSib" ? Hilite.getElementFromXPath(doc, rangeParams[0].split("::")[0]).nextSibling : Hilite.getElementFromXPath(doc, rangeParams[0].split("::")[0]).childNodes[0];
if (startContainer.nodeType === 1 && startContainer.className.split(" ")[0] === "highlight") {
startContainer = startContainer.childNodes[0];
}
//step 1. check if offsets are available in the textNode
if (startContainer.length <= startOffset) {
var shiftedStart = Hilite.shiftContainerAndOffset(startContainer, startOffset);
startContainer = shiftedStart.shiftedContainer;
startOffset = shiftedStart.shiftedOffset;
}
} else {
startContainer = Hilite.getElementFromXPath(doc, rangeParams[0].split("::")[0]);
}
if (endContainerRelation !== "null") {
endContainer = rangeParams[1].split("::")[1] === "prevSib" ? Hilite.getElementFromXPath(doc, rangeParams[1].split("::")[0]).nextSibling : Hilite.getElementFromXPath(doc, rangeParams[1].split("::")[0]).childNodes[0];
if (endContainer.nodeType === 1 && endContainer.className.split(" ")[0] === "highlight") {
endContainer = endContainer.childNodes[0];
}
//step 1. check if offsets are available in the textNode
if (endContainer.length < endOffset) {
var shiftedEnd = Hilite.shiftContainerAndOffset(endContainer, endOffset);
endContainer = shiftedEnd.shiftedContainer;
endOffset = shiftedEnd.shiftedOffset;
}
} else {
endContainer = Hilite.getElementFromXPath(doc, rangeParams[1].split("::")[0]);
}
var rangeObj = doc.createRange();
rangeObj.setStart(startContainer, startOffset);
rangeObj.setEnd(endContainer, endOffset);
getNodesInRange(rangeObj, doc, className);
}
function getNodesInRange(rangeObj, doc, className) {
var xpaths = []; /* NR - array of XPATH addresses */
//this.removeClasses = []; /* Merged classes, needs to be removed */
var range = rangeObj,
nodes = [],
/* Collection of text nodes to be wrapped */
mergeClasses = [],
/* NR - Elements are objects {"className": classHighlight[1], "element": node, "position": position} */
newStartContainer = false,
newEndContainer = false,
mergeClassesSeen = []; /* To maintain uniqueness of merged classes */
/* When documnet body gets selected */
if (navigator.userAgent.toLowerCase().indexOf("ipad") > -1 || navigator.userAgent.toLowerCase().indexOf("iphone") > -1) {
if (range.startContainer === doc.body || range.endContainer === doc.body) {
return "invalid:
selected";
}
}
/* start container or end container is null logic */
if (range.startContainer === null && range.endContainer === null) {
return "invalid: (SC,EC)=NULL";
} else if (range.startContainer === null) {
range.startContainer = range.endContainer;
} else if (range.endContainer === null) {
range.endContainer = range.startContainer;
}
/* fix for firefox - when the selected region has endContainer before the startContainer (or) has "\n\t" text nodes*/
var nodesBeforeStartContainer = [];
for (var node = doc.getElementsByTagName('body')[0], i = 0; node; node = Hilite.getNextNode(node)) {
if (node === range.startContainer) {
break;
} else {
nodesBeforeStartContainer[i] = node;
i++;
}
}
if (Hilite.arrayContainsObject(nodesBeforeStartContainer, range.endContainer)) {
range.setEnd(range.startContainer, range.endOffset);
}
if (range.endContainer.nodeType === 3) {
var tempEndNode = range.endContainer.nodeValue.replace(/[\t\n\r]/g, "");
if (tempEndNode.length === 0) {
//range.setEnd(range.startContainer, range.endOffset);
range.setEnd(range.startContainer, range.startContainer.length);
}
}
/* end of fix */
// populate nodes[] & mergeClasses[]
for (var node = range.startContainer, position = 0; node; node = Hilite.getNextNode(node)) {
if (node.nodeType === 3) { /* text nodes which does not belong to SVG */
/* filter out text nodes with escape sequences */
var nodeLength = node.nodeValue.replace(/[ \t\n\r]/g, "");
if (nodeLength.length === 0) {
continue;
}
// push textNodes not wrapped in nodes with "highlight" class to nodes[]
/* if the text is child node of of , type: svg */
if (node.parentNode.nodeName === "text")
nodes.push({
"type": "svgText",
"element": node,
"position": position++
});
else
nodes.push({
"type": "default",
"element": node,
"position": position++
});
}
// if the nodes in selection contain the "highlight" class then its a merge operation
if (node == range.endContainer)
break;
}
//Here the selection is a subset of the existing selection
if (nodes.length === 0) {
if (mergeClasses.length !== 0) {
if (Hilite.merge === true) { //return the selection if it is the duplicate of an existing, before returning
return savedSelection;
} else {
return;
}
} else {
return "invalid: no text";
}
}
//1. If range.startContainer == range.endContainer, then slection made within the same Element
// also in case of merge highlight, this condition would fail
//if(range.startContainer == range.endContainer){or
if (nodes.length === 1) {
var startOffset, endOffset, xpathValues, xpath;
if (range.startContainer.previousSibling) {
//check for "highlight" class
//var classHighlight = range.startContainer.previousSibling.getAttribute('class').split(" ");
var classHighlight = (range.startContainer.previousSibling.getAttribute('class') !== null) ?
range.startContainer.previousSibling.getAttribute('class').split(" ") :
[null];
if (classHighlight[0] === 'highlight') {
xpathValues = range.startContainer.previousSibling.getAttribute("data-offset");
xpathValues = base64.decode(xpathValues).split("|");
startOffset = range.startOffset + parseInt(xpathValues[1]);
endOffset = range.endOffset + parseInt(xpathValues[1]);
relation = xpathValues[2];
xpath = xpathValues[3];
} else {
startOffset = range.startOffset;
endOffset = range.endOffset;
relation = "prevSib";
xpath = getXPath(range.startContainer.previousSibling);
}
} else {
startOffset = range.startOffset;
endOffset = range.endOffset;
relation = "parentNode";
xpath = getXPath(range.startContainer.parentNode);
}
var dataOffset = startOffset + "|" + endOffset + "|" + relation + "|" + xpath;
if (window.getSelection) {
if (nodes[0].type === "svgText") {
var newNode = document.createElementNS("http://www.w3.org/2000/svg", "tspan");
newNode.setAttribute('class', 'highlight ' + className);
} else {
var newNode = doc.createElement(Hilite.hiliteTag);
newNode.className = newNode.className + "highlight " + className;
}
// Split the text
var text1 = nodes[0].element.nodeValue.substr(0, range.startOffset);
var text2 = nodes[0].element.nodeValue.substr(range.startOffset, range.endOffset - range.startOffset);
var text3 = nodes[0].element.nodeValue.substr(range.endOffset);
newNode.appendChild(doc.createTextNode(text2));
newNode.setAttribute('data-offset', base64.encode(dataOffset));
//range.deleteContents();
//range.insertNode(newNode);
// Replace with existing text
nodes[0].element.parentNode.replaceChild(newNode, nodes[0].element);
// Put text1 before newNode
if (text2.length > 0) {
newNode.parentNode.insertBefore(doc.createTextNode(text1), newNode);
}
// Put text3 before newNode
if (text3.length > 0) {
if (newNode.nextSibling) {
newNode.parentNode.insertBefore(doc.createTextNode(text3), newNode.nextSibling);
} else {
newNode.parentNode.appendChild(doc.createTextNode(text3));
}
}
} else if (window.document.selection) {
var rangeIE = doc.selection.createRange();
rangeIE.pasteHTML("" + rangeIE.text + " \n
for (var i = 0; i < nodes.length; i++) {
if (nodes[i].element.nodeType == 3) {
//alert(nodes[i].nodeValue.charCodeAt(0));
//To eliminate textNode's with a newline nodeValue
//if((nodes[i].nodeValue === "\t\t\n\t\t") || (nodes[i].length <= 0))
//if((nodes[i].nodeValue.indexOf("\t\t\n\t\t") !== -1) || (nodes[i].length <= 0))
//if((nodes[i].nodeValue === "\t\t\n\t\t") || (nodes[i].length <= 0) || (nodes[i].nodeValue === "\n\t\t\n\t\t") || (nodes[i].nodeValue === "\n\t\t"))
//var tempNode = nodes[i].nodeValue.replace(/[\t\n\r]/g, "");
//if(tempNode.length === 0){
//continue;
//}
if (i == 0) { // 1st node - begin from startOffset
var startOffset = range.startOffset,
xpathValues, relation, xpath;
var parent = nodes[i].element.parentNode;
// Split the text
var text1 = nodes[i].element.nodeValue.substr(0, startOffset);
var text2 = nodes[i].element.nodeValue.substr(startOffset);
//############################Check for existing SPANS(.highlight) #############################
if (nodes[i].element.previousSibling) {
//check for "highlight" in previous node
//var classHighlight = nodes[i].element.previousSibling.getAttribute('class').split(" ");
var classHighlight = ((typeof nodes[i].element.previousSibling.getAttribute === 'function') && (nodes[i].element.previousSibling.getAttribute('class') !== null)) ?
nodes[i].element.previousSibling.getAttribute('class').split(" ") :
[null];
if (classHighlight[0] === 'highlight') {
xpathValues = nodes[i].element.previousSibling.getAttribute("data-offset");
xpathValues = base64.decode(xpathValues).split("|");
startOffset = range.startOffset + parseInt(xpathValues[1]);
endOffset = parseInt(xpathValues[1]) + nodes[i].element.length;
relation = xpathValues[2];
xpath = xpathValues[3];
//endOffset = range.endOffset + parseInt(startEndRange[1]);
} else {
startOffset = range.startOffset;
endOffset = nodes[i].element.length;
relation = "prevSib";
xpath = getXPath(nodes[i].element.previousSibling);
//endOffset = range.endOffset;
}
} else {
startOffset = range.startOffset;
endOffset = nodes[i].element.length;
relation = "parentNode";
xpath = getXPath(nodes[i].element.parentNode);
//endOffset = range.endOffset;
}
//###################################################################################
//wrap the slected text
//var wrapper = doc.createElement('span');
if (nodes[i].type === "svgText") {
var wrapper = document.createElementNS("http://www.w3.org/2000/svg", "tspan");
wrapper.setAttribute('class', 'highlight ' + className);
} else {
var wrapper = doc.createElement(Hilite.hiliteTag);
wrapper.className = wrapper.className + "highlight " + className;
}
// set element as child of wrapper
wrapper.appendChild(doc.createTextNode(text2));
// Replace with existing text
parent.replaceChild(wrapper, nodes[i].element);
// Put text1 before wrapper
parent.insertBefore(doc.createTextNode(text1), wrapper);
//set class, id and data-offset
//wrapper.className = wrapper.className + "highlight " + className;
var dataOffset = startOffset + "|" + endOffset + "|" + relation + "|" + xpath; //EOE - End Of Element
wrapper.setAttribute('data-offset', base64.encode(dataOffset));
xpaths[nodes[i].position] = base64.encode(dataOffset);
} else if (i == (nodes.length - 1)) { //last node - stop at endOffset
var endOffset = range.endOffset,
relation, xpath;
var parent = nodes[i].element.parentNode;
// Split the text
var text1 = nodes[i].element.nodeValue.substr(0, endOffset);
var text2 = nodes[i].element.nodeValue.substr(endOffset);
//############################ Get offsets|relation|xpath for the last element #############################c
//############################Check for existing SPANS(.highlight) #############################
if (nodes[i].element.previousSibling) {
//check for "highlight" in previous node
var classHighlight = ((typeof nodes[i].element.previousSibling.getAttribute === 'function') && (nodes[i].element.previousSibling.getAttribute('class') !== null)) ?
nodes[i].element.previousSibling.getAttribute('class').split(" ") :
[null];
if (classHighlight[0] === 'highlight') {
xpathValues = nodes[i].element.previousSibling.getAttribute("data-offset");
xpathValues = base64.decode(xpathValues).split("|");
startOffset = parseInt(xpathValues[1]);
endOffset = range.endOffset + parseInt(xpathValues[1]);
relation = xpathValues[2];
xpath = xpathValues[3];
//endOffset = range.endOffset + parseInt(startEndRange[1]);
} else {
startOffset = 0;
endOffset = range.endOffset;
relation = "prevSib";
xpath = getXPath(nodes[i].element.previousSibling);
//endOffset = range.endOffset;
}
} else {
startOffset = 0;
endOffset = range.endOffset;
relation = "parentNode";
xpath = getXPath(nodes[i].element.parentNode);
//endOffset = range.endOffset;
}
//###################################################################################
//###################################################################################
//wrap the slected text
//var wrapper = doc.createElement('span');
if (nodes[i].type === "svgText") {
var wrapper = document.createElementNS("http://www.w3.org/2000/svg", "tspan");
wrapper.setAttribute('class', 'highlight ' + className);
} else {
var wrapper = doc.createElement(Hilite.hiliteTag);
wrapper.className = wrapper.className + "highlight " + className;
}
// set text1 as child of wrapper
wrapper.appendChild(doc.createTextNode(text1));
// Replace with existing text
parent.replaceChild(wrapper, nodes[i].element);
// append text2 to the wrapper
parent.insertBefore(doc.createTextNode(text2), wrapper.nextSibling);
//set class and data-offset attr
//wrapper.className = wrapper.className + "highlight " + className;
var dataOffset = startOffset + "|" + endOffset + "|" + relation + "|" + xpath;
wrapper.setAttribute('data-offset', base64.encode(dataOffset));
xpaths[nodes[i].position] = base64.encode(dataOffset);
} else { // else apply full highlight
var parent = nodes[i].element.parentNode,
relation, xpath;
//############################ Get relation|xpath for the inbetween elements #############################
//############################Check for existing SPANS(.highlight) #############################
if (nodes[i].element.previousSibling) {
//check for "highlight" in previous node
var classHighlight = ((typeof nodes[i].element.previousSibling.getAttribute === 'function') && (nodes[i].element.previousSibling.getAttribute('class') !== null)) ?
nodes[i].element.previousSibling.getAttribute('class').split(" ") :
[null];
if (classHighlight[0] === 'highlight') {
xpathValues = nodes[i].element.previousSibling.getAttribute("data-offset");
xpathValues = base64.decode(xpathValues).split("|");
startOffset = parseInt(xpathValues[1]);
endOffset = startOffset + nodes[i].element.length;
relation = xpathValues[2];
xpath = xpathValues[3];
//endOffset = range.endOffset + parseInt(startEndRange[1]);
} else {
startOffset = 0;
endOffset = nodes[i].element.length;
relation = "prevSib";
xpath = getXPath(nodes[i].element.previousSibling);
//endOffset = range.endOffset;
}
} else {
startOffset = 0;
endOffset = nodes[i].element.length;
relation = "parentNode";
xpath = getXPath(nodes[i].element.parentNode);
//endOffset = range.endOffset;
}
//###################################################################################
//wrap the slected text
//var wrapper = doc.createElement('span');
if (nodes[i].type === "svgText") {
var wrapper = document.createElementNS("http://www.w3.org/2000/svg", "tspan");
wrapper.setAttribute('class', 'highlight ' + className);
} else {
var wrapper = doc.createElement(Hilite.hiliteTag);
wrapper.className = wrapper.className + "highlight " + className;
}
// set the wrapper as child (instead of the element)
parent.replaceChild(wrapper, nodes[i].element);
// set element as child of wrapper
wrapper.appendChild(nodes[i].element);
//set class and data-attr
//wrapper.className = wrapper.className + "highlight " + className;
var dataOffset = startOffset + "|" + endOffset + "|" + relation + "|" + xpath;
wrapper.setAttribute('data-offset', base64.encode(dataOffset)); //EOE - End Of Element
xpaths[nodes[i].position] = base64.encode(dataOffset);
}
}
}
}
/* ADD ID TO THE FIST TEXT NODE */
//alert('asasasas: '+ className);
doc.getElementsByClassName(className)[0].setAttribute('id', className.split(" ")[0]);
//Hilite.log(nodes);
//var serializedString = "";
//for(var i=0; i < xpaths.length; i++)
//serializedString += xpaths[i] + ",";
//return serializedString;
//return savedSelection;
}
function serializeHilite(hiliteData) {
var sRelation, eRelation, sPath, ePath;
if (hiliteData.startNode.previousSibling) {
sRelation = "prevSib";
sPath = getXPath(hiliteData.startNode.previousSibling)
} else {
sRelation = "parentNode";
sPath = getXPath(hiliteData.startNode.parentNode)
}
if (hiliteData.endNode.previousSibling) {
eRelation = "prevSib";
ePath = getXPath(hiliteData.endNode.previousSibling)
} else {
eRelation = "parentNode";
ePath = getXPath(hiliteData.endNode.parentNode)
}
return base64.encode(sPath + '::' + sRelation + ',' + ePath + '::' + eRelation + ',' + hiliteData.startOffset + ',' + hiliteData.endOffset);
}
return {
applyHighlight: applyHighlight,
serializeHilite: serializeHilite,
getXPath: getXPath
}
}
function ttsParserModule(window) {
var window = window,
document = window.document,
//exceptions array previously had '.' in it, was removed to handle a CONTENT ISSUE where a '.' came alone and as a result two lines were
//getting higlighted together. Since the chances of a dot coming alone with spaces before and after it are slim, this change shouldnt create regression.
exceptions = ['Mr.', 'Dr.', 'Mrs.', 'Ms.', 'Pres.', 'Gov.', 'U.S.', 'v.', 'al.'],
inlineTags = ['SPAN', 'SMALL', 'EM', 'A', 'SUB', 'SUP', 'U', 'B', 'I', 'STRONG','FONT'],
blockTags = ['P', 'DIV', 'ARTICLE', 'SECTION', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'ASIDE', 'NAV', 'HEADER', 'FOOTER', 'IMG', 'BR','LI'],
currNode = null,
endNode = null,
startNode = null,
serializedCurrNode = null,
sentence = '',
carryOver = false,
/* carry over sentence from previous tect node */
carryOverIndex = 0,
textNodeLengthParsed = 0,
words = [];
/* public methods */
function reset() {
currNode = serializedCurrNode = null;
carryOverIndex = textNodeLengthParsed = 0;
sentence = '';
carryOver = false;
words = [];
}
function breakOnTag() {
/*currNode.parantNode = inline tag OR currNode.nextSibling = inline tag
Also, there is no text node available until the next Block node (e.g.,
)*/
if (inlineTags.indexOf(currNode.parentNode.tagName.toUpperCase()) > -1 || (currNode.nextSibling && inlineTags.indexOf(currNode.nextSibling.tagName.toUpperCase()) > -1)) {
for (var node = currNode; node; node = getNextNode(node)) {
if (node.nodeType === 3 && node.nodeValue.trim().length > 0 && node !== currNode) return false;
else if (node.nodeType === 1 && blockTags.indexOf(node.tagName.toUpperCase()) > -1) break;
}
}
return true;
}
function getSentence() {
var ready = false,
textNode,
startOffset,
tagBreak = false,
terminate = false,
trim = false;
sentence = '';
startNode = null;
if (currNode !== null) {
currNode = deserializeTextNode(serializedCurrNode);
}
// set end node to null after speakText
if (endNode !== null) {
if (!carryOver) {
currNode = getTextNode();
}
ready = true;
sentence += currNode.textContent;
startNode = currNode;
startOffset = 0;
textNodeLengthParsed = currNode.length + 1;
if (currNode === endNode) terminate = true;
if (carryOver) {
carryOver = false;
}
}
while (!ready) {
sentence = sentence.trim(); /* remove any trailing spaces */
if (!carryOver) {
textNode = getTextNode();
if (typeof textNode === "undefined") return undefined; /* end of audio */
tagBreak = breakOnTag();
if (startNode === null) {
startNode = textNode;
startOffset = 0
}
if (!/[\.!\?]/g.test(textNode.textContent)) {
sentence += textNode.textContent;
if (tagBreak) {
textNodeLengthParsed = currNode.length + 1;
break;
} else continue;
} else {
words = textNode.nodeValue.trim().split(" ");
carryOverIndex = 0;
textNodeLengthParsed = 0;
}
} else {
carryOver = false;
startNode = currNode;
startOffset = textNodeLengthParsed;
}
if (words && words instanceof Array) {
for (var i = carryOverIndex, l = words.length, w = null; i < l; i++) {
textNodeLengthParsed += words[i].length + 1;
w = words[i].trim();
sentence += w + ' ';
if (/[\.!\?]/g.test(w.charAt(w.length - 1)) && exceptions.indexOf(w) === -1) {
ready = true;
if (l - i > 1) {
carryOver = true;
carryOverIndex = ++i
}
break;
}
}
if (tagBreak && !ready) { /* if sentences have exceptions words */
ready = true;
textNodeLengthParsed = currNode.length + 1;
break;
}
}
}
serializedCurrNode = serializeTextNode(currNode);
return {
sentence: sentence,
hiliteData: ttsHilite.serializeHilite({
startNode: startNode,
endNode: currNode,
startOffset: startOffset,
endOffset: textNodeLengthParsed - 1
}),
terminate: terminate
}
}
function getTextNode() {
if (currNode === null) { // first time
currNode = document.body;
}
while (1) {
currNode = getNextNode(currNode);
if (currNode === undefined) {
currNode = document.body;
return undefined;
}
if (currNode.nodeType === 3 && currNode.nodeValue.trim().length > 0) {
return currNode;
}
}
}
function serializeTextNode(textNode) {
var path, relation;
if (textNode.previousSibling) {
relation = "prevSib";
path = ttsHilite.getXPath(textNode.previousSibling)
} else {
relation = "parentNode";
path = ttsHilite.getXPath(textNode.parentNode)
}
//console.log(path + '::' + relation)
return path + '::' + relation;
}
function deserializeTextNode(serializedString) {
var path = serializedString.split('::')[0],
relation = serializedString.split('::')[1],
element;
element = Hilite.getElementFromXPath(document, path);
if (relation === "parentNode") {
element = element.childNodes[0]
} else {
element = element.nextSibling
}
//console.log(element)
return element;
}
function getNextNode(node) {
if (node.firstChild)
return node.firstChild;
while (node) {
if (node.nextSibling)
return node.nextSibling;
node = node.parentNode;
}
}
function setCurrNode(node) {
currNode = node;
currNode = getTextNode();
serializedCurrNode = serializeTextNode(currNode);
carryOver = true;
carryOverIndex = 0;
textNodeLengthParsed = 0;
words = currNode.nodeValue.trim().split(" ");
}
function setEndNode(node) {
endNode = node;
}
function getEndNode(node) {
return endNode;
}
function getCurrNode() {
return currNode
}
function getStartNode() {
return startNode
}
return {
getNextNode: getNextNode,
getSentence: getSentence,
reset: reset,
setCurrNode: setCurrNode,
setEndNode: setEndNode,
getEndNode: getEndNode,
getCurrNode: getCurrNode,
getStartNode: getStartNode
}
}
function tts(window, readerWindow, serviceUrl, scrollDirection, serviceInfo, readText) {// Vivek : aaded new parameter serviceInfo to accomodate new api which required jwt token, and readText to read a particular text
/* private variables */
var window = window,
document = window.document,
ttsEndPt = serviceUrl.trim().length !== 0 ? serviceUrl : 'http://tts-api.com/tts.mp3?q={text}',
currSent = null,
nextSent = null,
currHilite = null,
nextHilte = null,
currAudio = document.createElement('audio'),
nextAudio = document.createElement('audio'),
speakClass = 'tts-speak',
currTerminate = false,
nextTerminate = false,
speak = false,
click = false,
active = false,
uA = navigator.userAgent.toLowerCase(),
mobile = (uA.indexOf("android") > -1 || uA.indexOf("ipad") > -1 || uA.indexOf("iphone") > -1) ? true : false,
stopEvent = readerWindow.document.createEvent("HTMLEvents"),
scrollDirection = scrollDirection === undefined ? 'vertical' : scrollDirection;
playAfterLoad = false,
ttsVoice = 'Emma';
ttsSourceLanguage = "en";
ttsPlaybackSpeed = 1;
playOnlyAudioElementFlag = false;
voiceListObject = {
en: ["Emma", "Joanna", "Ivy", "Joey", "Kendra", "Justin", "Mathieu", "Kimberly", "Salli", "Brian", "Amy", "Russell", "Nicole", "Aditi", "Raveena", "Geraint"], es: ["Lupe", "Penelope", "Miguel", "Conchita", "Lucia", "Enrique", "Mia"], fr: ["Celine", "Léa", "Mathieu"], da: ["Naja", "Mads"], nl: ["Lotte", "Ruben"], de: ["Marlene", "Vicki", "Hans"], hi: ["Aditi"], it: ["Carla", "Bianca", "Giorgio"], ja: ["Mizuki", "Takumi"], ko: ["Seoyeon"], pl: ["Ewa", "Maja", "Jacek", "Jan"], pt: ["Camila", "Vitoria", "Ricardo", "Ines", "Cristiano"], sv: ["Astrid"], arb: ["Zeina"] };
retryCount = 0;
// $("#toggleTtsIcon .ttsModal").remove();
stopEvent.initEvent("TtsStopped", true, true);
/* private functions */
function init() {
ttsParser = ttsHilite = undefined;
playAfterLoad = false;
currAudio = document.createElement('audio');
nextAudio = document.createElement('audio');
/* Initialize ttsParser and ttsHilite modules */
if (typeof ttsHilite === "undefined" || typeof ttsParser === "undefined") {
ttsHilite = ttsHiliteModule(window);
ttsParser = ttsParserModule(window);
}
if (!speak && !click) scrollHandlerHorizontal(); /* locating text not required for speak tts */
//currAudio = document.createElement('audio');
var currPassage = ttsParser.getSentence();
if(!currPassage) {
$("#ttsPause").css('display', 'none');
$("#ttsPlay").css('display', 'inline-block');
return;
}
currSent = currPassage.sentence;
currHilite = currPassage.hiliteData;
currTerminate = currPassage.terminate;
if (!mobile) {
sendRequest(currAudio, currSent, false);
//nextAudio = document.createElement('audio');
if (!currTerminate) {
var nextPassage = ttsParser.getSentence();
if (typeof nextPassage === "undefined") { /* end of audio */
currTerminate = true;
} else {
nextSent = nextPassage.sentence;
nextHilite = nextPassage.hiliteData;
nextTerminate = nextPassage.terminate;
sendRequest(nextAudio, nextSent, false);
}
}
nextAudio.addEventListener('canplay', canPlayHandler);
// nextAudio.addEventListener('timeupdate', timeUpdateHandler);
nextAudio.addEventListener('ended', endedHandler);
}
/* Event Listensers */
// if (!mobile) document.addEventListener('mousedown', clickHandler);
currAudio.addEventListener('canplay', canPlayHandler);
// currAudio.addEventListener('timeupdate', timeUpdateHandler);
currAudio.addEventListener('ended', endedHandler);
currAudio.addEventListener('error', errorHandler);//Added to handel server errors -- Minati
//document.addEventListener('touchend', clickHandler); parent.document.getElementById(dlayer).addEventListener('scroll', scrollHandler);
}
function updateState(prevFlag) { // prevFlag :: differentiation b/w next and prev
if (prevFlag) {
alert('prev coming soon...');
}
else {
if (mobile) {
var currPassage = ttsParser.getSentence();
if (typeof currPassage === "undefined") { /* end of audio */
stop(true);
return;
} else {
currSent = currPassage.sentence;
currHilite = currPassage.hiliteData;
currTerminate = currPassage.terminate;
}
} else {
if (!playAfterLoad) {
var tempAudio = currAudio;
currAudio = nextAudio;
nextAudio = tempAudio;
playAfterLoad = true;
currSent = nextSent;
currHilite = nextHilite;
currTerminate = nextTerminate;
if (!currTerminate) {
var nextPassage = ttsParser.getSentence();
if (typeof nextPassage === "undefined") { /* end of audio */
currTerminate = true;
} else {
nextSent = nextPassage.sentence;
nextHilite = nextPassage.hiliteData;
nextTerminate = nextPassage.terminate;
sendRequest(nextAudio, nextSent, false);
}
}
if (active) play();
} else {
retryCount--;
if (retryCount > 0)
setTimeout(function () { updateState() }, 50);
return;
}
}
}
}
function applyHilite(hiliteData) {
ttsHilite.applyHighlight(document, hiliteData, 'tts-highlight');
var hilight = document.getElementsByClassName('tts-highlight');
/*
for(var i = 0; i < hilight.length; i++){
hilight[i].style.backgroundColor = '#F5EBDB';
hilight[i].style.color = 'rgb(82, 2, 2)';
hilight[i].style.boxShadow = '#D6C19F 0px 1px 2px 1px';
hilight[i].style.zIndex = '10';
} */
/* scroll the TTS document */
if (scrollDirection === 'horizontal') {
var leftOffset = document.querySelector('.tts-highlight').getBoundingClientRect().left;
var columnWidth;
if (document.getElementsByTagName('html')[0].style.webkitColumnWidth !== undefined) {
columnWidth = parseInt(document.getElementsByTagName('html')[0].style.webkitColumnWidth);
} else if (document.getElementsByTagName('html')[0].style.MozColumnWidth !== undefined) {
columnWidth = parseInt(document.getElementsByTagName('html')[0].style.MozColumnWidth);
}
var columnGap;
if (document.getElementsByTagName('html')[0].style.webkitColumnGap !== undefined) {
columnGap = parseInt(document.getElementsByTagName('html')[0].style.webkitColumnGap);
} else if (document.getElementsByTagName('html')[0].style.MozColumnGap !== undefined) {
columnGap = parseInt(document.getElementsByTagName('html')[0].style.MozColumnGap);
}
var columnCount = parseInt(document.getElementsByTagName('html')[0].style.webkitColumnCount);
columnCount = isNaN(columnCount) ? 1 : columnCount;
if(columnCount === 2 && isNaN(columnWidth)) {
columnWidth = parseInt(document.getElementsByTagName('html')[0].style.width);
columnWidth = (columnWidth / 2) - (columnGap / 2);
}
var screenWidth = (columnWidth + columnGap) * columnCount;
var shiftScreens = Math.floor(leftOffset / screenWidth);
//window.scrollTo(window.scrollX + (shiftScreens * screenWidth), 0);
for (var i = 0, count = Math.abs(shiftScreens); i < count; i++) {
if (shiftScreens > 0) $(window.parent.document.querySelector('#next')).trigger('click');
else $(window.parent.document.querySelector('#prev')).trigger('click');
}
} else if (scrollDirection === 'vertical') {
var topOffset = document.querySelector('.tts-highlight').parentNode.getBoundingClientRect().top,
scrollTop = $(window).scrollTop() + topOffset - 100;
$("#scrolled-content-frame").animate({
scrollTop: scrollTop
});
// window.scrollTo(0, scrollTop);
}
}
function removeHilite() {
var highlighNodes = document.getElementsByClassName('tts-highlight'),
parent,
childNodes,
text;
while (highlighNodes.length > 0) {
parent = highlighNodes[0].parentNode,
childNodes = parent.childNodes,
text = highlighNodes[0].removeChild(highlighNodes[0].firstChild);
parent.insertBefore(text, highlighNodes[0]);
parent.removeChild(highlighNodes[0]);
/* To find out the childNode index of the highlighted text after normalization - Eg:
*/
for (var i = 0, l = childNodes.length; i < l; i++) {
if (childNodes[i] === text) break
}
while (i >= 0 && childNodes[i].previousSibling !== null && childNodes[i].previousSibling.nodeType === 3) {
i--
}
/* end of logic */
(navigator.userAgent.indexOf('rv:11')) === -1 ? parent.normalize() : Hilite.normalize(parent); /* parent.normalize(); */
}
}
function reset() {
removeHilite();
ttsParser.reset();
}
function setVoice(voice) {
ttsVoice = voice;
currAudio.pause();
if($("#ttsPlay").is(":visible")) {
playOnlyAudioElementFlag = true;
}
sendRequest(currAudio, currSent, true);
// currAudio.play();
// updateState();
}
function setScrollDirection(direction) {
scrollDirection = direction;
}
function setPlaybackSpeed(speed) {
currAudio.playbackRate = speed;
nextAudio.playbackRate = speed;
ttsPlaybackSpeed = speed;
}
function getSourceLanguage(text ,cb) {
if(!text) {
cb({ SourceLanguageCode: "en" });
}
else {
var inputText = text;
var sourceLanguageCode = 'auto';
var targetLanguageCode = "en";
var params = {
Text: inputText,
SourceLanguageCode: sourceLanguageCode,
TargetLanguageCode: targetLanguageCode
};
AWS.config.region = 'us-east-1'; // Region
var that = this;
//AWS.config.credentials = new AWS.Credentials("access key", "secret key");
AWS.config.credentials = new AWS.Credentials("AKIAWTAXOEBHTZXLS6M6", "hB16iVgHZ/J+UY6zi1E8TClhb2yKbE2g2AUpIEJ2");
this.translate = new AWS.Translate({ region: AWS.config.region });
this.polly = new AWS.Polly();
this.translate.translateText(params, function (err, data) {
if (err) {
console.log(err, err.stack);
//alert("Error calling " + err.message);
//return;
cb(null);
}
if (data) {
cb(data);
}
});
}
}
function sendRequest(audioElement, text, currAudioPlayFlag) {
var readingText = readText ? readText : text;
if (!readingText) {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function () {
if (this.readyState == 4 && this.status == 200) {
var audioUrl = JSON.parse(this.responseText).data.url
playAfterLoad = false;
audioElement.setAttribute('src', audioUrl);
audioElement.playbackRate = ttsPlaybackSpeed;
if (currAudioPlayFlag) {
if ($("#ttsPause").is(":visible")) {
audioElement.play();
} else {
audioElement.pause();
}
sendRequest(nextAudio, nextSent, false);
playAfterLoad = true;
}
}
};
var audioUrl = ttsEndPt.replace('{text}', text).replace('{voice}', ttsVoice);
xhttp.open("GET", audioUrl, true);
xhttp.setRequestHeader("Content-type", "application/json");
xhttp.setRequestHeader("X-Jwt-Token", serviceInfo.jwtToken);
xhttp.send();
}
else {
getSourceLanguage(readingText, function (res) {
if (!res) {
res = {
SourceLanguageCode: ttsSourceLanguage
}
}
if (ttsSourceLanguage != res.SourceLanguageCode) {
ttsVoice = voiceListObject[res.SourceLanguageCode][0];
ttsSourceLanguage = res.SourceLanguageCode;
if (document.getElementById("voiceListContainer")) {
/* document.getElementById("voiceListContainer").innerHTML = voiceListObject[res.SourceLanguageCode].map((data) => {
return `${data}
`
}).join(""); */
document.getElementById("voiceListContainer").innerHTML = voiceListObject[res.SourceLanguageCode].map(function (el) {
return "${data}
"
}).join("");
// var vector = labelsPrint.map(function (el) { return el.id; });
let childTags = document.getElementById("voiceListContainer").querySelectorAll("p");
for (let index = 0; index < childTags.length; index++) {
childTags[index].classList.remove("active");
if (childTags[index].innerHTML === ttsVoice) {
childTags[index].classList.add("active");
}
}
}
}
text = readText ? encodeURIComponent(readText) : encodeURIComponent(text); // Fixing -> TTS skip the highlighted text after semicolon
//console.log('Sending requesting'); console.log(ttsEndPt.replace('{text}', text));
// Vivek: changes to accomodate new api
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function () {
if (this.readyState == 4 && this.status == 200) {
var audioUrl = JSON.parse(this.responseText).data.url
playAfterLoad = false;
audioElement.setAttribute('src', audioUrl);
audioElement.playbackRate = ttsPlaybackSpeed;
if (currAudioPlayFlag) {
if ($("#ttsPause").is(":visible")) {
audioElement.play();
} else {
audioElement.pause();
}
sendRequest(nextAudio, nextSent, false);
playAfterLoad = true;
}
}
};
var audioUrl = ttsEndPt.replace('{text}', text).replace('{voice}', ttsVoice);
xhttp.open("GET", audioUrl, true);
xhttp.setRequestHeader("Content-type", "application/json");
xhttp.setRequestHeader("X-Jwt-Token", serviceInfo.jwtToken);
xhttp.send();
//audioElement.setAttribute('src', ttsEndPt.replace('{text}', text));
if (mobile) {
showMsg('buffering');
} else {
if (currAudio.readyState !== 4) showMsg('buffering');
}
});
}
}
/* public functions */
function play() {
if(playOnlyAudioElementFlag) {
currAudio.play();
playOnlyAudioElementFlag = false;
return;
}
if (!active) {
init()
} /* if first play initialize*/
if (currSent.length === 0) {
stop(true);
return
}
if (currAudio.paused && currAudio.currentTime > 0 && currAudio.currentTime < currAudio.duration) {
currAudio.play();
return
}
applyHilite(currHilite);
$iframe=$("#epubContentIframe").contents().find(".tts-highlight")[0];
if($iframe) {
let windowHeight = $("#epubContentIframe").contents().find("html").height();
}
if (mobile) sendRequest(currAudio, currSent, false);
currAudio.play();
active = true;
//sendRequest(nextAudio, nextSent);
}
function pause() {
currAudio.pause();
hideMsg('buffering', false);
//console.log('Paused');
}
function stop(endOfPage) {
if(!active) {
return;
}
if(currAudio.duration) {
currAudio.pause();
}
if (ttsParser.getEndNode !== null) {
removeHilite();
clearSpeak();
}
reset();
//init();
active = false;
// document.getElementById("ttsPause").style.display = 'none';
// document.getElementById("ttsPlay").style.display = 'inline-block';
$("#ttsPause").css('display', 'none');
$("#ttsPlay").css('display', 'inline-block');
if (endOfPage){
setTimeout(function() {
if(currAudio.paused) {
$("#toggleTtsIcon .ttsModal").hide();
}
}, 5000);
readerWindow.document.dispatchEvent(stopEvent);
}
}
/* Event handlers */
function endedHandler() {
removeHilite();
if (currTerminate) {
stop(true);
return
} /* stop caching audio */
if (currTerminate && speak) {
clearSpeak();
stop();
return
} /* stop speak audio*/
retryCount = 1000;
updateState();
//play();
// if (active) play(); /* active check added to avoid audio replay from the beginning */
}
function timeUpdateHandler(e) {
if (currAudio.currentTime >= currAudio.duration) {
endedHandler();
}
}
function clickHandler(e) {
if (speak) {
e.preventDefault();
return;
}
if (active) {
if (getPlayState()) {
e.preventDefault();
pause();
reset();
ttsParser.setCurrNode(e.target);
click = true;
init();
click = false;
play();
}
else
currAudio.pause();
}
}
function canPlayHandler(e) {
hideMsg('buffering', true);
}
function errorHandler(e) {
showMsg('no_network');
}
function scrollHandler() {
//console.log(active);
//if(active) return;
var findTextNode = null,
prevLikelyElement = null,
likelyElement = document.body;
/*cuttOff = $(parent.document.getElementById(dlayer)).scrollTop();*/
/* redefine dlayer */
while (prevLikelyElement !== likelyElement) {
prevLikelyElement = likelyElement;
for (var i = 0, childNodes = likelyElement.childNodes, l = childNodes.length; i < l; i++) {
if (childNodes[i].nodeType === 1) {
//if(childNodes[i].getBoundingClientRect().top >= 0) break;
if (childNodes[i].getBoundingClientRect().top >= cuttOff) break;
likelyElement = childNodes[i];
}
}
}
for (var node = likelyElement; node; node = ttsParser.getNextNode(node)) {
if (node.nodeType === 3 && node.nodeValue.trim().length > 0 && node.parentNode.getBoundingClientRect().top >= 0) {
findTextNode = node;
break;
}
}
if (findTextNode === null) return;
reset();
ttsParser.setCurrNode(findTextNode.parentNode);
//init();
}
function scrollHandlerHorizontal() {
var findTextNode = null,
prevLikelyElement = null,
likelyElement = document.body;
cuttOff = window.scrollX;
for (var node = document.body; node; node = ttsParser.getNextNode(node)) {
if (node.nodeType === 1 && node.getBoundingClientRect().left > 0 && node.getBoundingClientRect().left < parseInt(document.getElementsByTagName('html')[0].style.webkitColumnWidth) * 2 + 60) {
likelyElement = node;
break;
}
}
for (var node = likelyElement; node; node = ttsParser.getNextNode(node)) {
if (node.nodeType === 3 && node.nodeValue.trim().length > 0) {
findTextNode = node;
break;
}
}
if (findTextNode === null) return;
reset();
ttsParser.setCurrNode(findTextNode.parentNode);
}
function showMsg(flag) {
switch (flag) {
case 'buffering':
//console.log('buffering...');
$("#ttsPause").css('display', 'none');
$("#buffer-loader").css('display', 'inline-block');
// $(readerWindow.document.body).append('');
break;
case 'no_network':
//console.log('error!');
$(readerWindow.document.body).append('');
errTimer = setTimeout(function () {
hideMsg('no_network', true);
hideMsg('buffering', true);
}, 5000);
break;
}
}
function hideMsg(flag, loadingFlag) {
switch (flag) {
case 'buffering':
//console.log('finished buffering...');
if(loadingFlag && $("#buffer-loader").is(":visible")) {
$("#ttsPause").css('display', 'inline-block');
$("#buffer-loader").css('display', 'none');
}
$(readerWindow.document.body).find('.msg_buf').remove();
break;
case 'no_network':
//console.log('removing error!');
clearTimeout(errTimer);
$(readerWindow.document.body).find('.msg_err').remove();
break;
}
}
function getPlayState() {
if (currAudio === null) return false;
return !currAudio.paused
}
function setVolume(vol) {
currAudio.volume = nextAudio.volume = vol;
}
function clear() {
ttsParser = ttsHilite = undefined;
hideMsg('buffering', true);
}
function speakText() {
/* In case speak is called before TTS */
if (typeof ttsHilite === "undefined" || typeof ttsParser === "undefined") {
ttsHilite = ttsHiliteModule(window);
ttsParser = ttsParserModule(window);
}
speak = true;
var speakNodes = document.getElementsByClassName(speakClass);
angular.element(speakNodes).find('.nt-icon').remove();
reset();
ttsParser.setCurrNode(speakNodes[0]);
ttsParser.setEndNode(speakNodes[speakNodes.length - 1].firstChild);
//init();
play();
}
var clearSpeak = function () {
currTerminate = false;
speak = false;
ttsParser.setEndNode(null);
Hilite.removeHilite(document.body, speakClass);
//readerWindow.doGetBackHL();
};
document.addEventListener('DOMContentLoaded', function () {
//init()
});
function initpublic() { }
/* exposing public functions */
function playNext(){
currAudio.pause();
endedHandler();
}
function playPrevious(){
currAudio.pause();
removeHilite();
if (currTerminate) {
stop(true);
return
} /* stop caching audio */
if (currTerminate && speak) {
clearSpeak();
stop();
return
} /* stop speak audio*/
retryCount = 1000;
updateState(true);
}
function getCurrentAudio() {
return currAudio;
}
return {
init: initpublic,
play: play,
pause: pause,
stop: stop,
speakText: speakText,
getPlayState: getPlayState,
setVolume: setVolume,
clear: clear,
reset: reset,
clickHandler: clickHandler,
playNext: playNext,
playPrevious: playPrevious,
setVoice: setVoice,
setPlaybackSpeed: setPlaybackSpeed,
getCurrentAudio: getCurrentAudio,
setScrollDirection: setScrollDirection
}
}
function hideTTSPopup() {
$("#toggleTtsIcon .ttsModal").hide();
}
/* Depedencies
1. stop(): TtsStopped event triggered on tag of the readerWindow
*/