MediaWiki:Gadget-tts.js

MediaWiki interface page

Note: After publishing, you may have to bypass your browser's cache to see the changes.

  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
  • Internet Explorer / Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5
  • Opera: Press Ctrl-F5.
const SENTENCE_REGEX = /[^.!?()"']*[.!?()"']*/g;
$("#content p").contents().filter(function() {
    return this.nodeType == Node.TEXT_NODE;
}).each(function () {
    this.replaceWith(...this.textContent.match(SENTENCE_REGEX).map(sentence =>
	$(`<span class="tts-sentence">${sentence}</span>`)[0]));
});

const $ttsCaret = () => {
    const $existing = $("#tts-caret");
    if ($existing.length) {
	return $existing.detach();
    }
    return $('<span id="tts-caret">|</span>');
};

let programmaticPlay = false;
let programmaticPause = false;
const audio = $('<audio controls muted loop id="tts-audio" src="//upload.wikimedia.org/wikipedia/commons/4/40/Toreador_song_cleaned.ogg"></audio>').appendTo("body")[0];

const stop = () => {
    window.speechSynthesis.cancel();
    $(".tts-button").css("display", "");

    programmaticPause = true;
    audio.pause();
};

const play = ($startingElement) => {
    stop();

    programmaticPlay = true;
    audio.play();

    let $selected = $startingElement.length ? $startingElement.parents().nextAll() : $("#content");
    $selected.find(".tts-play").css("display", "none");
    $selected.find(".tts-pause").css("display", "inline-block");
    const $caret = $selected.find("#tts-caret");
    $selected = $selected.find(".tts-sentence");
    if ($caret.length) {
	$selected = $selected.filter($caret.nextAll());
    }
    $selected.each(tts);
};

audio.onplay  = () => programmaticPlay  ? programmaticPlay  = false : play($("#tts-caret"));
audio.onpause = () => programmaticPause ? programmaticPause = false : stop();

let queue = 0;
function tts() { 
    const utterance = new SpeechSynthesisUtterance(this.innerText);
    const utteranceFinished = () => {
	queue--;
	if (queue <= 0) {
	    stop();
	    $("#tts-caret").remove();
	} else {
	    $(this).after($ttsCaret());
	}
    };
    utterance.onerror = utteranceFinished;
    utterance.onend = utteranceFinished;
    window.speechSynthesis.speak(utterance);
    queue++;
};

mw.loader.using(["oojs-ui-core", "oojs-ui.styles.icons-media"], () => $(() => {
    // $audio.on("ended", stop);
    $("#content :header").append(() => {
	const ttsPlayButton = new OO.ui.ButtonWidget({
	    framed: false,
	    icon: "play",
	    label: "Read aloud",
	    invisibleLabel: true,
	    title: "Icon only",
	    classes: ["mw-editsection", "tts-play", "tts-button"]
	});
	ttsPlayButton.on("click", () => play(ttsPlayButton.$element));
	return ttsPlayButton.$element;
    }).append(() => {
	const ttsPauseButton = new OO.ui.ButtonWidget({
	    framed: false,
	    icon: "pause",
	    label: "Pause reading aloud",
	    invisibleLabel: true,
	    title: "Icon only",
	    classes: ["mw-editsection", "tts-pause", "tts-button"]
	});
	ttsPauseButton.on("click", stop);
	return ttsPauseButton.$element;
    });
}));