Skip to content

Instantly share code, notes, and snippets.

@imjasonh
Last active October 28, 2021 06:11
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save imjasonh/8000c73c0223f572bec74e5c94d91db0 to your computer and use it in GitHub Desktop.
Save imjasonh/8000c73c0223f572bec74e5c94d91db0 to your computer and use it in GitHub Desktop.
Simple Chrome extension to speak selected text
// TODOs:
// - manage queue of selections to read, "read to me now" or
// "read to me next"
// - highlight current word/sentence
// - intelligently detect blocks of text and ignore ads, photo
// captions, etc., https://mercury.postlight.com/web-parser/
// - stop speaking when the text's tab is closed
var currentRate = 1; // Default rate until we consult settings.
var currentText = '';
var currentIndex = 0;
chrome.storage.sync.get('speed', function(opt) {
if ('speed' in opt) {
currentRate = parseFloat(opt['speed']);
} else {
currentRate = 1;
}
});
function read(text) {
currentIndex = 0;
currentText = text;
chrome.tts.speak(text, {
rate: currentRate,
onEvent: function(evt) {
console.log('event', evt.type);
if (evt.errorMessage != undefined) { console.error(evt); return; }
if (evt.charIndex == undefined) { return; }
currentIndex = evt.charIndex;
}
});
}
function changeSpeed(newRate) {
if (newRate > 3 || newRate < .25) { return; }
// Stop speaking at the current rate, chop off the part that's already been
// read, and begin speaking the rest at the new rate.
chrome.tts.stop();
currentRate = newRate;
chrome.storage.sync.set({'speed': currentRate});
console.log('new rate', newRate);
read(currentText.substr(currentIndex));
}
chrome.contextMenus.create({
title: 'Read to me',
contexts: ['selection'],
onclick: function(info) { read(info.selectionText); },
}, null);
chrome.commands.onCommand.addListener(function(cmd) {
console.log('command', cmd);
switch(cmd) {
case "startstop":
chrome.tts.isSpeaking(function(speaking) {
if (speaking) {
chrome.tts.stop();
} else {
chrome.tabs.executeScript( {
code: "window.getSelection().toString();"
}, function(selection) {
read(selection[0]);
});
}
});
break;
case "faster": changeSpeed(currentRate + .25); break;
case "slower": changeSpeed(currentRate - .25); break;
}
});
{
"manifest_version": 2,
"name": "Read to me",
"version": "0.0.1",
"permissions": [
"contextMenus",
"storage",
"tabs",
"tts",
"<all_urls>"
],
"icons": {
"16": "icon16.png"
},
"background": {
"scripts": ["background.js"]
},
"commands": {
"startstop": {
"suggested_key": {
"default": "Ctrl+Shift+Space"
},
"description": "Start/stop reading"
},
"faster": {
"suggested_key": {
"default": "Ctrl+Shift+Period"
},
"description": "Increase speed"
},
"slower": {
"suggested_key": {
"default": "Ctrl+Shift+Comma"
},
"description": "Decrease speed"
}
},
"options_ui": {
"page": "options.html",
"chrome_style": true
}
}
<!DOCTYPE html>
<html><head>
<style>
.right {
align: right;
}
span {
margin-left: 10px;
}
</style>
</head><body>
<table>
<tr><td class="right">
<label for="speed">Speed</label>:
</td><td>
<input name="speed" id="speed" type="range" min="0.25" max="3" step="0.25"></input><span id="speed-out"></span>x<br />
</td></tr>
</table>
<a href="chrome://extensions/configureCommands">Set up keyboard shortcuts</a>
<script src="options.js"></script>
</body></html>
// TODOs:
// - set gender/voice/lang
//
const keys = ['speed'];
chrome.storage.sync.get(keys, function(opt) {
for (var i = 0; i < keys.length; i++) {
var k = keys[i];
var el = document.getElementById(k);
el.value = opt[k];
update(k, opt[k]);
el.onchange = function(e) {
update(k, e.target.value);
var x = {};
x[k] = e.target.value;
chrome.storage.sync.set(x);
};
}
});
function update(k, v) {
var out = document.getElementById(k+'-out');
if (out != undefined) {
out.innerText = v;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment