diff --git a/data/voice.svg b/data/voice.svg new file mode 100644 index 0000000..b600182 --- /dev/null +++ b/data/voice.svg @@ -0,0 +1,12 @@ + + + + + + + + + diff --git a/graphics/voice_svg_source.txt b/graphics/voice_svg_source.txt new file mode 100644 index 0000000..00ded7b --- /dev/null +++ b/graphics/voice_svg_source.txt @@ -0,0 +1,4 @@ +voice.svg made by David Merfield. Licensed as CC0 public domain. + +http://publicicons.org/audio-icon/ +https://github.com/davidmerfield/Public-Icons/raw/master/LICENSE diff --git a/index.js b/index.js index 2cdbcb9..529531d 100644 --- a/index.js +++ b/index.js @@ -9,6 +9,7 @@ const { translate, translateUrl, translatePageUrl, + listen, } = require('./providers/google-translate') const { getMostRecentBrowserWindow } = require('sdk/window/utils') const addonUnload = require('sdk/system/unload') @@ -21,6 +22,7 @@ const LABEL_LOADING = 'Fetching translation…' const LABEL_TRANSLATE = 'Translate “{0}”' const LABEL_TRANSLATE_PAGE = 'Translate Page ({0} > {1})' const LABEL_CHANGE_LANGUAGES = 'Change Languages ({0} > {1})' +const LABEL_LISTEN = 'Listen' // Get the available languages const getLanguages = () => new Promise((resolve) => { @@ -146,6 +148,8 @@ const openTab = url => { const initMenu = (win, languages) => { let selection = '' + // translate() updates the from code if it's set to auto. + let detectedFromCode = '' const doc = win.document const cmNode = doc.getElementById('contentAreaContextMenu') const elt = eltCreator(doc) @@ -162,6 +166,11 @@ const initMenu = (win, languages) => { const translatePopup = elt('menupopup', null, null, translateMenu) const result = elt('menuitem', null, null, translatePopup) + const listenLabel = elt( + 'menuitem', { className: 'menuitem-iconic' }, + { label: LABEL_LISTEN, image: self.data.url('voice.svg') }, + translatePopup + ) elt('menuseparator', null, null, translatePopup) const langMenu = elt('menu', null, null, translatePopup) const fromPopup = elt('menupopup', null, null, langMenu) @@ -280,6 +289,7 @@ const initMenu = (win, languages) => { } if (sp.prefs.langFrom === 'auto') { updateLangMenuLabel(res.detectedSource) + detectedFromCode = res.detectedSource } }) } @@ -328,6 +338,14 @@ const initMenu = (win, languages) => { } } + // Play speech on click. + const onClickListen = () => { + const from = sp.prefs.langFrom === 'auto' ? detectedFromCode : + currentFrom(languages).code + const sel = selection === '' ? getSelectionFromWin(win) : selection + listen(from, sel, win) + } + const inspectorSeparatorElement = doc.getElementById('inspect-separator') cmNode.insertBefore(translateMenu, inspectorSeparatorElement) cmNode.insertBefore(translatePage, inspectorSeparatorElement) @@ -335,12 +353,15 @@ const initMenu = (win, languages) => { cmNode.addEventListener('popuphiding', onPopuphiding) cmNode.addEventListener('command', onContextCommand) + listenLabel.addEventListener('click', onClickListen) + updateLangMenuChecks() return function destroy() { cmNode.removeEventListener('popupshowing', onPopupshowing) cmNode.removeEventListener('popuphiding', onPopuphiding) cmNode.removeEventListener('command', onContextCommand) + listenLabel.removeEventListener('click', onClickListen) cmNode.removeChild(translateMenu) cmNode.removeChild(translatePage) } diff --git a/providers/google-translate.js b/providers/google-translate.js index cae78ae..e65be17 100644 --- a/providers/google-translate.js +++ b/providers/google-translate.js @@ -2,6 +2,7 @@ 'use strict' const request = require('sdk/request').Request +const xhr = require('sdk/net/xhr') function translationResult(str, onError) { let newstr = '[' @@ -166,6 +167,37 @@ function translate(from, to, text, cb) { } } +function apiListenUrl(from, text) { + const protocol = 'https://' + const host = 'translate.google.com' + const token = generateToken(text) + let path = ( + `/translate_tts?ie=UTF-8&q=${encodeURIComponent(text)}&tl=${from}&` + + `total=1&idx=0&textlen=${text.length}&tk=${token}&client=t&prev=input&` + + `ttsspeed=0.48` + ) + + return `${protocol}${host}${path}` +} + +function listen(from, text, win) { + const url = apiListenUrl(from, text) + const req = new xhr.XMLHttpRequest() + req.open('GET', url, true) + req.responseType = 'arraybuffer' + req.onload = () => { + const audioContext = new win.AudioContext() + audioContext.decodeAudioData(req.response, (buffer) => { + const source = audioContext.createBufferSource() + source.buffer = buffer + source.connect(audioContext.destination) + source.start() + }) + } + req.send() +} + exports.translate = translate exports.translateUrl = pageUrl exports.translatePageUrl = wholePageUrl +exports.listen = listen