speak_button.html (codi)

Conversor de text a veu

🔊 Conversor de text a veu

to run into somebody.
have a good trip.
to pass on greetings.
there’s a restroom in the café.

<!DOCTYPE html>
<html lang="ca">
<head>
  <meta charset="UTF-8">
  <title>Conversor de text a veu</title>
  <style>
    /* Estil general de la pàgina */
    body {
      font-family: sans-serif;
      padding: 2em;
      background-color: lab(90.09% -0.67 -0.25);
    }

    /* Editor de text editable */
    #editor {
      font-size: 1.2em;
      padding: 0.5em;
      margin-top: 1em;
      width: 100%;
      max-width: 500px;
      height: 15em;
      overflow-y: auto;
      border: 1px solid #ccc;
      white-space: pre-wrap;
    }

    /* Estil dels selectors (idioma i pausa) */
    select {
      font-size: 1.2em;
      padding: 0.5em;
      margin-top: 1em;
      width: 45%;
      max-width: 220px;
      height: 2.5em;
      overflow-y: hidden;
      display: inline-block;
      vertical-align: top;
    }

    /* Estil dels botons de control */
    #speakBtn, #toggleBtn {
      font-size: 2em;
      background: none;
      border: none;
      cursor: pointer;
      margin-top: 1em;
    }

    #speakBtn:hover, #toggleBtn:hover {
      color: hsl(180, 1%, 72%);
    }

    /* Estil de cada línia destacable */
    .linia {
      display: block;
      padding: 0.2em;
      transition: background-color 0.3s ease;
    }

    /* Línia activa mentre es llegeix */
    .activa {
      background-color: hsl(180, 1%, 72%);
    }
  </style>
</head>
<body>
  <h2>🔊 Conversor de text a veu</h2>

  <!-- Editor de text on l'usuari pot escriure -->
  <div id="editor" contenteditable="true" placeholder="Escriu el text que vols escoltar">
to run into somebody.<br>
have a good trip.<br>
to pass on greetings.<br>
there’s a restroom in the café.
  </div>

  <!-- Selectors d'idioma i pausa -->
  <div>
    <select id="voiceSelect">
      <option value="">Selecciona idioma</option>
      <option value="en-US">🇺🇸 Anglès (US)</option>
      <option value="en-GB">🇬🇧 Anglès (UK)</option>
      <option value="fr-FR">🇫🇷 Francès</option>
      <option value="de-DE">🇩🇪 Alemany</option>
      <option value="it-IT">🇮🇹 Italià</option>
      <option value="pt-PT">🇵🇹 Portuguès</option>
      <option value="es-ES">🇪🇸 Espanyol</option>
      <option value="ca-ES">🎗️ Català</option>
    </select>

    <select id="pauseSelect">
      <option value="1">⏱️ Pausa x1</option>
      <option value="2">⏱️ Pausa x2</option>
      <option value="3">⏱️ Pausa x3</option>
      <option value="4">⏱️ Pausa x4</option>
      <option value="10">⏱️ Pausa x10</option>
    </select>
  </div>

  <!-- Botons de control -->
  <button id="speakBtn" title="Escolta el text">🔊</button>
  <button id="toggleBtn" title="Atura o reprèn">⏹️</button>

  <script>
    document.addEventListener('DOMContentLoaded', () => {
      // Referències als elements HTML
      const editor = document.getElementById('editor');
      const speakBtn = document.getElementById('speakBtn');
      const toggleBtn = document.getElementById('toggleBtn');
      const voiceSelect = document.getElementById('voiceSelect');
      const pauseSelect = document.getElementById('pauseSelect');

      // Variables de control
      let voices = [];
      let isSpeaking = false;
      let liniesGuardades = [];
      let currentIndex = 0;

      // Carrega les veus disponibles
      function loadVoices() {
        voices = speechSynthesis.getVoices();
      }

      speechSynthesis.onvoiceschanged = loadVoices;
      loadVoices();

      // Recupera l'idioma seleccionat anteriorment
      const idiomaGuardat = localStorage.getItem('idiomaSeleccionat');
      if (idiomaGuardat) {
        voiceSelect.value = idiomaGuardat;
      }

      // Guarda l'idioma seleccionat
      voiceSelect.addEventListener('change', () => {
        localStorage.setItem('idiomaSeleccionat', voiceSelect.value);
      });

      // Obté les línies del text
      function getLinies() {
        const text = editor.innerText || editor.textContent;
        return text.split('\n').map(l => l.trim()).filter(l => l);
      }

      // Marca visualment la línia que s'està llegint
      function marcarLinia(index) {
        const linies = editor.querySelectorAll('.linia');
        linies.forEach((el, i) => {
          el.classList.toggle('activa', i === index);
        });
      }

      // Converteix el text en elements <span> per poder destacar línies
      function prepararEditor() {
        const linies = getLinies();
        editor.innerHTML = '';
        linies.forEach((text, i) => {
          const span = document.createElement('span');
          span.className = 'linia';
          span.textContent = text;
          editor.appendChild(span);
          editor.appendChild(document.createElement('br'));
        });
      }

      // Calcula la pausa entre línies segons el selector
      function getPausaEntreLinies() {
        const factor = parseInt(pauseSelect.value || '1');
        return 500 * factor;
      }

      // Llegeix una línia i programa la següent
      function speakLinia() {
        if (currentIndex >= liniesGuardades.length) {
          isSpeaking = false;
          setTimeout(() => {
            speakText(); // Reinicia automàticament
          }, 2000);
          return;
        }

        const linia = liniesGuardades[currentIndex];
        const utterance = new SpeechSynthesisUtterance(linia);
        const voice = voices.find(v => v.lang === voiceSelect.value);
        if (voice) {
          utterance.voice = voice;
          utterance.lang = voice.lang;
        }

        utterance.rate = 1;
        utterance.pitch = 1;

        utterance.onstart = () => {
          isSpeaking = true;
          marcarLinia(currentIndex);
        };

        utterance.onend = () => {
          currentIndex++;
          setTimeout(() => {
            speakLinia();
          }, getPausaEntreLinies());
        };

        speechSynthesis.speak(utterance);
      }

      // Inicia la lectura des del principi
      function speakText() {
        if (isSpeaking) return;
        prepararEditor();
        liniesGuardades = getLinies();
        currentIndex = 0;
        speechSynthesis.cancel();
        speakLinia();
      }

      // Botó 🔊 per iniciar la lectura
      speakBtn.addEventListener('click', speakText);

      // Botó ⏹️ per aturar o reprendre
      toggleBtn.addEventListener('click', () => {
        if (isSpeaking) {
          speechSynthesis.cancel();
          isSpeaking = false;
        } else {
          speakLinia();
        }
      });
    });
  </script>
</body>
</html>