বাংলা/हिंदी/English Input Text Reader auto Scroll: select text to start reading

 

🔊 বাংলায় শুনুন

Code:

Html:

<!-- 📱 Media query -->
<style>
  @media screen and (max-width: 768px) {
    #smartReaderBtn {
      right: 5px !important;
      top: 170px !important;
    }
  }
</style>

<!-- 📝 Input Box & Button -->
<div style="font-family: sans-serif; margin: 20px auto; max-width: 700px;">
  <div style="display: flex; justify-content: space-between; align-items: center; margin-top: 10px;">
    <p id="wordCount" style="color: #555; margin: 0;"></p>
    <div id="smartReaderBtn" onclick="toggleSmartReader()" style="align-items: center; background-color: #ff9900; border-radius: 50%; border: 2px solid white; box-shadow: rgba(0,0,0,0.1) 0px 4px 8px; color: white; cursor: pointer; display: flex; padding: 10px; position: fixed; right: 10%; top: 15%; z-index: 999;">
      <span style="font-size: 24px; margin-right: 5px;">🔊</span>
      <span id="smartReaderLabel" style="font-size: 16px;">বাংলায় শুনুন</span>
    </div>
  </div>

  <label style="display: block; font-weight: bold; margin-bottom: 10px;">✍️ Enter your text (Limit: 1,500 words):</label>
  <textarea class="post-body" id="wordInput" placeholder="তোমার লেখা এখানে দাও।
अपना पाठ यहां डालें।
Put your text here." rows="15" style="border-radius: 8px; border: 1px solid rgb(204, 204, 204); font-size: 16px; padding: 12px; width: 95%;"></textarea>
</div>

JS:

<script>
let smartQueue = [];
let currentIndex = 0;
let isReading = false;
let isPaused = false;
let wakeLock = null;

// 🔁 Toggle start/pause/resume
function toggleSmartReader() {
  if (!isReading && !isPaused) {
    startSmartReader();
  } else if (isReading) {
    pauseSmartReader();
  } else if (isPaused) {
    resumeFromCursor();
  }
}

// 🚀 Start reading
function startSmartReader() {
  const textarea = document.getElementById("wordInput");
  const text = textarea.value.trim();
  if (!text) return alert("❌ Enter some text first.");

  smartQueue = text
    .split(/(?<=[।!?.\n])\s+/)
    .map(s => s.trim())
    .filter(Boolean);

  if (smartQueue.length === 0) {
    alert("❌ No readable content.");
    return;
  }

  currentIndex = 0;
  isReading = true;
  isPaused = false;
  updateButtonUI("reading");
  requestWakeLock();
  readNextLine();
}

// 🔊 Read sentence & scroll
function readNextLine() {
  const textarea = document.getElementById("wordInput");

  if (currentIndex >= smartQueue.length) {
    stopSmartReader();
    return;
  }

  const fullText = textarea.value;
  const sentence = smartQueue[currentIndex++];
  const index = fullText.indexOf(sentence);

  if (index !== -1) {
    textarea.focus();
    textarea.setSelectionRange(index, index + sentence.length);
    scrollToCenter(textarea, index);
  }

  window.speechSynthesis.cancel();
  const speak = new SpeechSynthesisUtterance(sentence);
  speak.lang = "bn-IN"; // 🔁 Use "en-US" for English
  speak.rate = 1;
  speak.pitch = 1;
  speak.volume = 1;
  speak.onend = () => {
    if (isReading) setTimeout(readNextLine, 300);
  };
  window.speechSynthesis.speak(speak);
}

// 🎯 Accurate scroll to center using mirrored div
function scrollToCenter(textarea, index) {
  const beforeText = textarea.value.substring(0, index);

  const mirrorDiv = document.createElement("div");
  const style = window.getComputedStyle(textarea);

  mirrorDiv.style.position = "absolute";
  mirrorDiv.style.visibility = "hidden";
  mirrorDiv.style.whiteSpace = "pre-wrap";
  mirrorDiv.style.wordWrap = "break-word";
  mirrorDiv.style.padding = style.padding;
  mirrorDiv.style.font = style.font;
  mirrorDiv.style.lineHeight = style.lineHeight;
  mirrorDiv.style.width = `${textarea.clientWidth}px`;

  mirrorDiv.textContent = beforeText;
  document.body.appendChild(mirrorDiv);

  const pixelOffset = mirrorDiv.offsetHeight;
  textarea.scrollTo({
    top: pixelOffset - textarea.clientHeight / 2,
    behavior: "smooth"
  });

  document.body.removeChild(mirrorDiv);
}

// ⏸ Pause reading
function pauseSmartReader() {
  window.speechSynthesis.cancel();
  isReading = false;
  isPaused = true;
  releaseWakeLock();
  updateButtonUI("paused");

  setTimeout(() => {
    const choose = confirm("📍 Select a point in the box to resume.\n\nOK = Place cursor\nCancel = Stop");
    if (!choose) {
      isPaused = false;
      updateButtonUI("default");
    }
  }, 200);
}

// ▶️ Resume from cursor
function resumeFromCursor() {
  const textarea = document.getElementById("wordInput");
  const pos = textarea.selectionStart;
  const fullText = textarea.value;

  let startIndex = 0;
  for (let i = 0; i < smartQueue.length; i++) {
    const idx = fullText.indexOf(smartQueue[i]);
    if (idx >= 0 && idx <= pos) {
      startIndex = i;
    }
  }

  currentIndex = startIndex;

  setTimeout(() => {
    const resume = confirm("✅ Start reading from selected point?");
    if (resume) {
      isPaused = false;
      isReading = true;
      updateButtonUI("reading");
      requestWakeLock();
      readNextLine();
    } else {
      isPaused = false;
      updateButtonUI("default");
    }
  }, 100);
}

// 🛑 Stop everything
function stopSmartReader() {
  isReading = false;
  isPaused = false;
  currentIndex = 0;
  updateButtonUI("default");
  window.speechSynthesis.cancel();
  releaseWakeLock();
}

// 🔁 Update button
function updateButtonUI(state) {
  const btn = document.getElementById("smartReaderBtn");
  const label = document.getElementById("smartReaderLabel");

  if (state === "reading") {
    btn.style.backgroundColor = "lightskyblue";
    label.textContent = "Reading...";
  } else if (state === "paused") {
    btn.style.backgroundColor = "#f0ad4e";
    label.textContent = "Paused — Resume?";
  } else {
    btn.style.backgroundColor = "#ff9900";
    label.textContent = "Listen in Bengali/English";
  }
}

// 🔋 Wake Lock Request
async function requestWakeLock() {
  if ('wakeLock' in navigator) {
    try {
      wakeLock = await navigator.wakeLock.request('screen');
    } catch (err) {
      console.warn("❌ Wake Lock error:", err);
    }
  }
}

// 🔓 Release Wake Lock
async function releaseWakeLock() {
  if (wakeLock) {
    try {
      await wakeLock.release();
      wakeLock = null;
    } catch (err) {
      console.warn("❌ Release Wake Lock failed:", err);
    }
  }
}

// 🔄 Resume wake lock on return
document.addEventListener("visibilitychange", () => {
  if (document.visibilityState === "visible" && isReading) {
    requestWakeLock();
  }
});

// 📝 Word Count
const textarea = document.getElementById("wordInput");
const countDisplay = document.getElementById("wordCount");
const maxWords = 1500;

textarea.addEventListener("input", () => {
  const text = textarea.value.trim();
  const words = text === "" ? [] : text.split(/\s+/);
  const count = words.length;

  if (count > maxWords) {
    countDisplay.innerHTML = `❌ Limit exceeded: <strong>${count}</strong> / ${maxWords}`;
    countDisplay.style.color = "red";
  } else {
    countDisplay.innerHTML = `✅ Word Count: <strong>${count}</strong> / ${maxWords}`;
    countDisplay.style.color = "green";
  }
});
</script>

Comments

Popular posts from this blog

हिंदी Page Reader auto Scroll: select text to start reading

বাংলা Page Reader auto Scroll: select text to start reading