User:Phlsph7/WikiNarrator.js
(function(){
const scriptName = 'WikiNarrator';
const intervalDelay = 5000;
let intervalId = '';
const states = {stopped: 0, playing: 1, paused: 2};
let currentState;
let initialized = false;
let selectedText = '';
speechSynthesis.onvoiceschanged = function(){
if(!initialized){
initialized = true;
if(getSettingsFromStorage() === null){
let settings = getDefaultSettings();
saveSettingsToStorage(settings);
}
initializePlayer();
if(getSettingsFromStorage().showPlayer){
showPlayer();
}
}
};
$.when(mw.loader.using('mediawiki.util'), $.ready).then(function(){
const portletLink = mw.util.addPortletLink('p-tb', '#', 'Show/hide ' + scriptName, scriptName + 'Id');
portletLink.onclick = function(e) {
e.preventDefault();
let settings = getSettingsFromStorage();
if(settings.showPlayer){
settings.showPlayer = false;
saveSettingsToStorage(settings);
stop();
hidePlayer();
}
else{
settings.showPlayer = true;
saveSettingsToStorage(settings);
showPlayer();
}
};
});
function initializePlayer(){
const htmlCode = `
`;const container = document.createElement('div');
document.body.append(container);
container.outerHTML = htmlCode;
if(!('speechSynthesis' in window)){
wikiNarratorControlButtons.innerHTML = '
WikiNarrator does not work in your browser
because it does not support speech synthesis.
}
else{
adjustState(states.stopped);
wikiNarratorVoice.onchange = saveSettingsFromInput;
wikiNarratorVolume.onchange = saveSettingsFromInput;
wikiNarratorSpeed.onchange = saveSettingsFromInput;
wikiNarratorPitch.onchange = saveSettingsFromInput;
wikiNarratorSettings.onclick = showHideSettings;
wikiNarratorPlay.onmousedown = function(){
selectedText = getSelection().toString();
};
wikiNarratorPlay.onclick = play;
wikiNarratorPause.onclick = pause;
wikiNarratorStop.onclick = stop;
wikiNarratorDefault.onclick = setDefaultSettings;
wikiNarratorClose.onclick = showHideSettings;
initializeInput();
}
}
function showPlayer(){
wikiNarratorContainer.style.display = '';
}
function hidePlayer(){
wikiNarratorContainer.style.display = "none";
}
function play(){
if(currentState == states.paused){
adjustState(states.playing);
speechSynthesis.resume();
setSpeechInterval();
}
else{
adjustState(states.playing);
let settings = getSettingsFromStorage();
hideRefs();
let text = selectedText;
if(text == ''){
let articleContainer;
if(document.getElementById('wikiPreview')){
articleContainer = document.getElementById('wikiPreview').querySelector('.mw-parser-output');
}
else{
articleContainer = document.getElementById('mw-content-text').querySelector('.mw-parser-output');
}
let readableElements = Array.from(articleContainer.querySelectorAll(':scope > p, :scope > blockquote, :scope > ul, :scope > ol, :scope > dl, :scope > .mw-heading'));
for(let element of readableElements){
text += element.innerText + '\n';
}
}
showRefs();
text = text.split('\r').join('\n')
.split('\n\n').join('\n')
.split('\n.').join('\n');
let textChunks = [''];
const chunkSize = 2000;
let sentences = text.split('. ');
let currentIndex = 0;
for(let sentence of sentences){
if(sentence.trim().length > 2){
if(textChunks[currentIndex].length + sentence.length < chunkSize){
textChunks[currentIndex] += sentence + '. ';
}
else{
currentIndex++;
textChunks[currentIndex] = sentence + '. ';
}
}
}
for(let i = 0; i < textChunks.length; i++){
const target = "\n. ";
if (textChunks[i].endsWith(target)) {
textChunks[i] = textChunks[i].slice(0, -target.length);
}
textChunks[i] = textChunks[i].trim();
}
let utterances = [];
for(let i = 0; i < textChunks.length; i++){
let utterance = new SpeechSynthesisUtterance();
utterance.text = textChunks[i].trim();
utterance.voice = speechSynthesis.getVoices().filter(function(voice) { return voice.name == settings.voiceName; })[0];
utterance.volume = settings.volume;
utterance.rate = settings.speed;
utterance.pitch = settings.pitch;
utterances.push(utterance);
}
for(let i = 0; i < utterances.length - 1; i++){
let currentUtterance = utterances[i];
let nextUtterance = utterances[i+1];
currentUtterance.onend = function(){
if(currentState == states.playing){
clearInterval(intervalId);
speechSynthesis.cancel();
speechSynthesis.speak(nextUtterance);
setSpeechInterval();
}
else{
adjustState(states.stopped);
clearInterval(intervalId);
}
};
}
utterances[utterances.length-1].onend = function() {
adjustState(states.stopped);
clearInterval(intervalId);
};
speechSynthesis.cancel();
speechSynthesis.speak(utterances[0]);
setSpeechInterval();
}
}
function pause(){
adjustState(states.paused);
clearInterval(intervalId);
speechSynthesis.pause();
}
function stop(){
adjustState(states.stopped);
clearInterval(intervalId);
speechSynthesis.cancel();
}
function adjustState(newState){
currentState = newState;
switch(currentState){
case states.stopped:
wikiNarratorPlay.disabled = false;
wikiNarratorPause.disabled = true;
wikiNarratorStop.disabled = true;
break;
case states.playing:
wikiNarratorPlay.disabled = true;
wikiNarratorPause.disabled = false;
wikiNarratorStop.disabled = false;
break;
case states.paused:
wikiNarratorPlay.disabled = false;
wikiNarratorPause.disabled = true;
wikiNarratorStop.disabled = false;
break;
}
}
function setSpeechInterval(){
intervalId = setInterval(function(){
speechSynthesis.pause();
speechSynthesis.resume();
}, intervalDelay);
}
function showHideSettings(){
if(wikiNarratorSettingsContainer.style.display != "none"){
wikiNarratorSettingsContainer.style.display = "none";
}
else{
wikiNarratorSettingsContainer.style.display = '';
}
}
function hideRefs(){
let refs = document.body.querySelectorAll('.reference, .Inline-Template, .mw-editsection');
for(let ref of refs){
ref.style.display = 'none';
}
}
function showRefs(){
let refs = document.body.querySelectorAll('.reference, .Inline-Template, .mw-editsection');
for(let ref of refs){
ref.style.display = '';
}
}
function getSettingsFromStorage(){
return JSON.parse(localStorage.getItem('wikiNarratorSettings'));
}
function getSettingsFromInput(){
let settings = {
voiceName: wikiNarratorVoice.value,
volume: wikiNarratorVolume.value,
speed: wikiNarratorSpeed.value,
pitch: wikiNarratorPitch.value,
showPlayer: true,
};
return settings;
}
function saveSettingsToStorage(settings){
localStorage.setItem('wikiNarratorSettings', JSON.stringify(settings));
}
function initializeInput(){
let settings = getSettingsFromStorage();
wikiNarratorVolume.value = settings.volume;
wikiNarratorSpeed.value = settings.speed;
wikiNarratorPitch.value = settings.pitch;
wikiNarratorVoice.innerHTML = '';
let voices = speechSynthesis.getVoices();
for(let voice of voices){
const option = document.createElement('option');
option.value = voice.name;
option.text = voice.name;
if(voice.name == settings.voiceName){
option.selected = true;
}
wikiNarratorVoice.add(option);
}
}
function saveSettingsFromInput(){
let settings = getSettingsFromInput();
saveSettingsToStorage(settings);
}
function setDefaultSettings(){
let settings = getDefaultSettings();
saveSettingsToStorage(settings);
initializeInput();
}
function getDefaultSettings(){
let voices = speechSynthesis.getVoices();
let filteredVoices = [];
for(let voice of voices){
if(voice.lang.includes('en-')){
filteredVoices.push(voice);
}
}
if(filteredVoices.length == 0){
filteredVoices = voices;
}
let defaultVoiceName = '';
for(let voice of filteredVoices){
if(voice.default == true){
defaultVoiceName = voice.name;
break;
}
}
if(defaultVoiceName == ''){
defaultVoiceName = filteredVoices[0].name;
}
return {
voiceName: defaultVoiceName,
volume: 1,
speed: 1,
pitch: 1,
showPlayer: true,
};
}
})();