User:Phlsph7/WikiNarrator.js
Appearance
Code that you insert on this page could contain malicious content capable of compromising your account. If you import a script from another page with "importScript", "mw.loader.load", "iusc", or "lusc", take note that this causes you to dynamically load a remote script, which could be changed by others. Editors are responsible for all edits and actions they perform, including by scripts. User scripts are not centrally supported and may malfunction or become inoperable due to software changes. A guide to help you find broken scripts is available. If you are unsure whether code you are adding to this page is safe, you can ask at the appropriate village pump. This code will be executed when previewing this page. |
![]() | This user script seems to have a documentation page at User:Phlsph7/WikiNarrator. |
(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 = `<div id="wikiNarratorContainer" style="position: fixed; right: 10px; bottom: 10px; display: none;">
<style>
.wiki-narrator-container{
background-color: #eee;
padding: 5px;
border-radius: 10px;
border: 1px solid #ccc;
}
.wiki-narrator-control-button {
font-size: 1.5em;
flex: 1;
margin: 2px;
padding-bottom: 4px;
width: 40px;
height: 40px;
}
.wiki-narrator-settings-button {
flex: 1;
margin: 2px;
}
.wiki-narrator-item {
margin-bottom: 15px;
}
.wiki-narrator-label{
display: block;
margin-bottom: 5px;
font-weight: bold;
}
.wiki-narrator-control{
width: 100%;
}
</style>
<div class="wiki-narrator-container" style="width: 300px; margin-bottom: 5px; display: none;" id="wikiNarratorSettingsContainer">
<div style="margin-bottom: 10px; text-align: center; font-size: 1.5em;">Settings</div>
<div class="wiki-narrator-item">
<label for="voice" class="wiki-narrator-label">Voice</label>
<select id="wikiNarratorVoice" class="wiki-narrator-control"></select>
</div>
<div class="wiki-narrator-item">
<label for="volume" class="wiki-narrator-label">Volume</label>
<input id="wikiNarratorVolume" type="range" min="0" max="1" step="0.1" value="1" class="wiki-narrator-control">
</div>
<div class="wiki-narrator-item">
<label for="speed" class="wiki-narrator-label">Speed</label>
<input id="wikiNarratorSpeed" type="range" min="0.1" max="2" step="0.1" value="1" class="wiki-narrator-control">
</div>
<div class="wiki-narrator-item">
<label for="pitch" class="wiki-narrator-label">Pitch</label>
<input id="wikiNarratorPitch" type="range" min="0" max="2" step="0.1" value="1" class="wiki-narrator-control">
</div>
<div style="display: flex">
<button id="wikiNarratorDefault" class="wiki-narrator-settings-button">Default</button>
<button id="wikiNarratorClose"class="wiki-narrator-settings-button">Close</button>
</div>
</div>
<div style="display:flex">
<div style="flex:1"></div>
<div id="wikiNarratorControlButtons" class="wiki-narrator-container" style="display: flex;">
<button id="wikiNarratorPlay" class="wiki-narrator-control-button" title="Play">▶</button>
<button id="wikiNarratorPause" class="wiki-narrator-control-button" title="Pause">⏸</button>
<button id="wikiNarratorStop" class="wiki-narrator-control-button" title="Stop">■</button>
<button id="wikiNarratorSettings" class="wiki-narrator-control-button" style="font-weight: bold;" title="Settings">⚙</button>
</div>
</div>
</div>`;
const container = document.createElement('div');
document.body.append(container);
container.outerHTML = htmlCode;
if(!('speechSynthesis' in window)){
wikiNarratorControlButtons.innerHTML = '<p><b>WikiNarrator</b> does not work in your browser<br>because it does <b>not support</b> <a href="https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis">speech synthesis</a>.</p>';
}
else{
adjustState(states.stopped);
wikiNarratorVoice.onchange = saveSettingsFromInput;
wikiNarratorVolume.onchange = saveSettingsFromInput;
wikiNarratorSpeed.onchange = saveSettingsFromInput;
wikiNarratorPitch.onchange = saveSettingsFromInput;
wikiNarratorSettings.onclick = showHideSettings;
wikiNarratorPlay.onmousedown = function(){
hideRefs();
selectedText = getSelection().toString();
showRefs();
};
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();
let text = selectedText;
if(text == ''){
hideRefs();
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,
};
}
})();