Skip to content
This repository has been archived by the owner on Nov 22, 2022. It is now read-only.

Add text-to-speech feature #63

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 12 additions & 0 deletions data/voice.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions 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
21 changes: 21 additions & 0 deletions index.js
Expand Up @@ -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')
Expand All @@ -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) => {
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -280,6 +289,7 @@ const initMenu = (win, languages) => {
}
if (sp.prefs.langFrom === 'auto') {
updateLangMenuLabel(res.detectedSource)
detectedFromCode = res.detectedSource
}
})
}
Expand Down Expand Up @@ -328,19 +338,30 @@ 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)
cmNode.addEventListener('popupshowing', onPopupshowing)
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)
}
Expand Down
32 changes: 32 additions & 0 deletions providers/google-translate.js
Expand Up @@ -2,6 +2,7 @@
'use strict'

const request = require('sdk/request').Request
const xhr = require('sdk/net/xhr')

function translationResult(str, onError) {
let newstr = '['
Expand Down Expand Up @@ -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