document.addEventListener("DOMContentLoaded", function () {
const lastChapter = localStorage.getItem("lastAccessedChapter");
console.log("Last Save Chapter: "+ lastChapter)
if (lastChapter) {
const chapter = JSON.parse(lastChapter);
loadChapter({ chapterTitle: chapter.chapterTitle }); // Auto-load last accessed chapter
}
const loginLink = document.getElementById('login-link'); // Login button container
const userInfoDiv = document.getElementById('user-info'); // User info container (after login)
const logoutButton = document.getElementById('logout-button'); // Logout button
const userNameDisplay = document.getElementById('user-name'); // Where to display the username
const userButton = document.getElementById('user-button'); // User initials button
const token = localStorage.getItem('authToken'); // Get token from localStorage
const menuButton = document.getElementById("menuButton");
const sidebar = document.querySelector(".sidebar");
const overlay = document.querySelector(".overlay");
const content = document.querySelector("#content"); // Main content area
const nextBtn = document.getElementById('nextChapterButton');
if (nextBtn) nextBtn.addEventListener('click', loadNextChapter);
const loadingContainer = document.getElementById('loading-container'); // Loading container
const loadingBar = document.getElementById('loading-bar'); // Loading bar
const loadingText = document.getElementById('loading-text'); // Loading text
// Show loading bar when the page starts
loadingContainer.style.display = 'flex';
// Fetch and initialize data from MongoDB
fetch('https://thevera-botveradb.hf.space/get_chapter_titles')
// fetch('https://thevera-botveradb.hf.space/get_chapters')
.then(response => response.json())
.then(responseData => {
if (responseData.success) {
initializePage(responseData.data);
} else {
console.error('Error loading data:', responseData.message);
}
// Hide the loading bar after data is loaded
loadingContainer.style.display = 'none';
})
.catch(error => {
console.error('Error loading data:', error);
// Hide the loading bar in case of error
loadingContainer.style.display = 'none';
});
if (token) {
// Check token validity with backend
fetch('https://thevera-botveradb.hf.space/validate_token', {
method: 'GET',
headers: {
'Authorization': `Bearer ${token}`
}
})
.then(response => response.json())
.then(data => {
if (data.message === "Token is valid!") {
// Token is valid, decode it to get user info
const userObject = jwt_decode(token); // Decode the token to get user info
const email = userObject.email; // Assuming the email is stored in the token
const username = email.split('@')[0]; // Extract username (first part of email)
// Update UI with user's info
userNameDisplay.innerText = username; // Display first part of email as username
userInfoDiv.style.display = 'block'; // Show user info section
// Change user button to show user's initials
const userInitials = username.charAt(0).toUpperCase(); // Get first letter of username
userButton.innerHTML = `
${userInitials}
`; // Display initials
// Show the logout button and hide the login link
logoutButton.style.display = 'inline';
loginLink.style.display = 'none';
// **Update UI for Mobile**
document.getElementById('user-info-mobile').style.display = 'block';
document.getElementById('user-initials').innerText = userInitials;
document.getElementById('user-name-mobile').innerText = email.split('@')[0];
document.getElementById('logout-button-mobile').style.display = 'block';
document.getElementById('loginButton').style.display = 'none';
} else {
// Token is invalid, clear invalid token
localStorage.removeItem('authToken');
console.log('Invalid token, user not logged in.');
loginLink.style.display = 'inline'; // Show login link
}
})
.catch(error => {
console.error('Error:', error);
// Handle the error (e.g., network issues)
localStorage.removeItem('authToken'); // Clear token in case of error
loginLink.style.display = 'inline'; // Show login link
});
} else {
// No token found, show login link
console.log('No token found, user not logged in.');
loginLink.style.display = 'inline';
}
// Logout functionality
if (logoutButton) {
logoutButton.onclick = function() {
localStorage.removeItem('authToken'); // Clear token
window.location.reload(); // Refresh page to update UI
};
}
// Optional: If you want to show the dropdown when clicking on the user initials button
if (userButton) {
userButton.addEventListener('click', () => {
const userDropdown = document.getElementById('user-dropdown');
userDropdown.style.display = userDropdown.style.display === 'block' ? 'none' : 'block';
});
}
// Sidebar toggle for mobile and desktop
menuButton.addEventListener("click", function () {
if (sidebar && overlay && content) {
sidebar.classList.toggle("shown");
overlay.classList.toggle("active");
// Adjust content margin when sidebar is visible
if (sidebar.classList.contains("shown")) {
content.style.marginLeft = "250px"; // Sidebar width
} else {
content.style.marginLeft = "0";
}
}
});
// Close sidebar on overlay click for mobile view
overlay.addEventListener("click", function () {
if (sidebar && overlay && content) {
sidebar.classList.remove("shown");
overlay.classList.remove("active");
content.style.marginLeft = "0"; // Reset content margin
}
});
});
// Mobile Menu Buttion js code
document.addEventListener("DOMContentLoaded", function () {
const menuButton = document.getElementById("menuButton");
const mobileMenu = document.getElementById("mobileMenu");
if (menuButton && mobileMenu) {
menuButton.addEventListener("click", function (event) {
event.stopPropagation(); // Prevents immediate close
mobileMenu.classList.toggle("show"); // Toggle menu visibility
});
// Close menu when clicking outside
document.addEventListener("click", function (event) {
if (!mobileMenu.contains(event.target) && !menuButton.contains(event.target)) {
mobileMenu.classList.remove("show");
}
});
} else {
console.error("Menu button or mobile menu not found in the DOM.");
}
});
// ===== Voice Manager (persisted TTS settings) =====
// const TTS_STORE_KEY = 'ttsSettings_v1';
// const defaultTTS = { voiceURI: null, rate: 0.9, pitch: 0.9 };
// function getTTSSettings(){
// try { return { ...defaultTTS, ...(JSON.parse(localStorage.getItem(TTS_STORE_KEY))||{}) }; }
// catch { return { ...defaultTTS }; }
// }
// function saveTTSSettings(patch){
// const now = { ...getTTSSettings(), ...(patch||{}) };
// localStorage.setItem(TTS_STORE_KEY, JSON.stringify(now));
// return now;
// }
// function waitForVoices(timeoutMs = 1500) {
// return new Promise(resolve => {
// const have = speechSynthesis.getVoices();
// if (have && have.length) return resolve(have);
// const onVoices = () => {
// const v = speechSynthesis.getVoices();
// if (v && v.length) {
// speechSynthesis.removeEventListener('voiceschanged', onVoices);
// resolve(v);
// }
// };
// speechSynthesis.addEventListener('voiceschanged', onVoices);
// setTimeout(() => {
// speechSynthesis.removeEventListener('voiceschanged', onVoices);
// resolve(speechSynthesis.getVoices() || []);
// }, timeoutMs);
// });
// }
// function preferredSort(a,b){
// // Prefer sa-IN > hi-IN > en-IN, then by name
// const rank = v => (/sa-IN/i.test(v.lang)?0:(/hi-IN/i.test(v.lang)?1:(/en-IN/i.test(v.lang)?2:3)));
// const r = rank(a)-rank(b);
// if (r!==0) return r; return a.name.localeCompare(b.name);
// }
// async function populateVoiceSelect(){
// const select = document.getElementById('voiceSelect');
// if (!select) return;
// select.innerHTML = '';
// const voices = await waitForVoices();
// voices.sort(preferredSort).forEach(v=>{
// const opt = document.createElement('option');
// opt.value = v.voiceURI; // persistable id
// opt.textContent = `${v.name} — ${v.lang}`;
// select.appendChild(opt);
// });
// const st = getTTSSettings();
// if (st.voiceURI){
// const found = [...select.options].some(o=>o.value===st.voiceURI);
// if (found) select.value = st.voiceURI;
// } else if (voices.length){
// // auto-pick first preferred
// saveTTSSettings({ voiceURI: voices[0].voiceURI });
// select.value = voices[0].voiceURI;
// }
// }
// function attachVoiceUI(){
// const select = document.getElementById('voiceSelect');
// const refresh = document.getElementById('voiceRefresh');
// const sample = document.getElementById('voiceSample');
// const rate = document.getElementById('voiceRate');
// const pitch = document.getElementById('voicePitch');
// const st = getTTSSettings();
// if (rate) rate.value = st.rate; if (pitch) pitch.value = st.pitch;
// if (select) select.addEventListener('change', ()=> saveTTSSettings({ voiceURI: select.value }));
// if (rate) rate.addEventListener('input', ()=> saveTTSSettings({ rate: parseFloat(rate.value) }));
// if (pitch) pitch.addEventListener('input', ()=> saveTTSSettings({ pitch: parseFloat(pitch.value) }));
// if (refresh) refresh.addEventListener('click', populateVoiceSelect);
// if (sample) sample.addEventListener('click', async ()=>{
// try { await speakClientSide("ॐ श्रीं नमः | यह एक नमूना आवाज़ है ।"); } catch(e){ console.warn(e); }
// });
// }
// function resolveSelectedVoice(voices){
// const st = getTTSSettings();
// let v = voices.find(v=>v.voiceURI===st.voiceURI);
// if (!v){
// // best-effort locale pick
// v = voices.sort(preferredSort)[0] || null;
// if (v) saveTTSSettings({ voiceURI: v.voiceURI });
// }
// return v;
// }
// Call once on load (safe no-op if controls are not present)
// (function initVoiceControls(){
// if (!('speechSynthesis' in window)) return;
// populateVoiceSelect();
// attachVoiceUI();
// })();
// Handle toggle for "Shastra Collection" header
document.getElementById('shastraHeader').addEventListener('click', function() {
const shastraList = document.getElementById('shastraList');
shastraList.classList.toggle('hidden'); // Toggle the 'hidden' class on the sidebar
});
let chaptersArray = [];
let currentChapterIndex = -1; // Initialize with -1 as default to indicate no chapter is loaded yet
let resumeTime = 0;
let player;
function initializePage(data) {
const shastraList = document.getElementById('shastraList');
if (!shastraList) {
console.error("Element with ID 'shastraList' not found.");
return;
}
Object.keys(data).forEach((collection) => {
// Create main group (e.g., Bhagwat Mahapuran)
const mainGroupItem = document.createElement('li');
mainGroupItem.classList.add('main-group');
mainGroupItem.innerHTML = `
${collection}
`;
const itemList = document.createElement('ul');
itemList.classList.add('nested-list');
itemList.style.display = 'none';
// Toggle display of main group chapters
mainGroupItem.addEventListener('click', () => {
itemList.style.display = itemList.style.display === 'none' ? 'block' : 'none';
});
data[collection].forEach((item) => {
if (item.skandaTitle) {
// Create Skanda group with + icon
const skandaGroup = document.createElement('li');
skandaGroup.classList.add('skanda-group');
skandaGroup.innerHTML = `
${item.skandaTitle}
`;
const skandaChapterList = document.createElement('ul');
skandaChapterList.classList.add('nested-list');
skandaChapterList.style.display = 'none';
item.chapters.forEach((chapter) => {
const chapterItem = document.createElement('li');
chapterItem.classList.add('chapter-item');
chapterItem.innerHTML = `
${chapter.chapterTitle}
`;
chapterItem.addEventListener('click', (event) => {
event.stopPropagation();
fetchChapterData(chapter.chapterTitle).then(chapterDetails => {
if (chapterDetails) {
loadChapter(chapterDetails);
closeSidebarOnMobile();
}
});
});
skandaChapterList.appendChild(chapterItem);
chaptersArray.push({ chapterTitle: chapter.chapterTitle });
});
skandaGroup.addEventListener('click', (event) => {
event.stopPropagation();
skandaChapterList.style.display = skandaChapterList.style.display === 'none' ? 'block' : 'none';
const toggleIcon = skandaGroup.querySelector('.toggle-icon');
if (toggleIcon) {
toggleIcon.classList.toggle('fa-plus');
toggleIcon.classList.toggle('fa-minus');
}
});
skandaGroup.appendChild(skandaChapterList);
itemList.appendChild(skandaGroup);
} else {
const chapterItem = document.createElement('li');
chapterItem.classList.add('chapter-item');
chapterItem.innerHTML = ` ${item.chapterTitle}`;
chapterItem.addEventListener('click', (event) => {
event.stopPropagation();
fetchChapterData(item.chapterTitle).then(chapterDetails => {
if (chapterDetails) {
loadChapter(chapterDetails);
closeSidebarOnMobile();
}
});
});
itemList.appendChild(chapterItem);
chaptersArray.push({ chapterTitle: item.chapterTitle });
}
});
mainGroupItem.appendChild(itemList);
shastraList.appendChild(mainGroupItem);
});
// Place this right after the forEach loop ends inside initializePage
if (!localStorage.getItem("menuTipShown")) {
const tip = document.createElement('div');
tip.innerText = "📚 Browse chapters here!";
tip.style.cssText = `
position: fixed;
left: 20px;
top: 70px;
background: #fff8e1;
padding: 8px 12px;
border-radius: 5px;
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
z-index: 9999;
font-weight: bold;
`;
document.body.appendChild(tip);
setTimeout(() => {
tip.remove();
localStorage.setItem("menuTipShown", "true");
}, 4000);
}
}
function extractSkandaNumber(skandaTitle) {
// e.g. "Skanda 1" or "Skanda One" or "Skanda I"
const match = skandaTitle?.match(/(\d+)/);
return match ? parseInt(match[1], 10) : 0;
}
function initializePageOld(data) {
const shastraList = document.getElementById('shastraList');
if (!shastraList) {
console.error("Element with ID 'shastraList' not found.");
return;
}
Object.keys(data).forEach((collection) => {
const mainGroupItem = document.createElement('li');
mainGroupItem.textContent = collection;
mainGroupItem.classList.add('main-group');
const itemList = document.createElement('ul');
itemList.classList.add('nested-list');
itemList.style.display = 'none';
data[collection].forEach((item) => {
if (item.skandaTitle) {
const skandaGroup = document.createElement('li');
// skandaGroup.textContent = item.skandaTitle;
skandaGroup.innerHTML = ` ${item.skandaTitle}`;
skandaGroup.classList.add('skanda-group');
const skandaChapterList = document.createElement('ul');
skandaChapterList.classList.add('nested-list');
skandaChapterList.style.display = 'none';
item.chapters.forEach((chapter) => {
const chapterItem = document.createElement('li');
// chapterItem.textContent = chapter.chapterTitle;
chapterItem.innerHTML = ` ${chapter.chapterTitle}`;
chapterItem.classList.add('chapter-item');
chapterItem.addEventListener('click', (event) => {
event.stopPropagation();
fetchChapterData(chapter.chapterTitle).then(chapterDetails => {
if (chapterDetails) {
loadChapter(chapterDetails);
closeSidebarOnMobile();
}
});
});
skandaChapterList.appendChild(chapterItem);
// Populate chaptersArray with chapter metadata (title only for now)
chaptersArray.push({ chapterTitle: chapter.chapterTitle });
});
skandaGroup.addEventListener('click', (event) => {
event.stopPropagation();
skandaChapterList.style.display = skandaChapterList.style.display === 'none' ? 'block' : 'none';
});
skandaGroup.appendChild(skandaChapterList);
itemList.appendChild(skandaGroup);
} else {
const chapterItem = document.createElement('li');
chapterItem.textContent = item.chapterTitle;
chapterItem.classList.add('chapter-item');
chapterItem.addEventListener('click', (event) => {
event.stopPropagation();
fetchChapterData(item.chapterTitle).then(chapterDetails => {
if (chapterDetails) {
loadChapter(chapterDetails);
closeSidebarOnMobile();
}
});
});
itemList.appendChild(chapterItem);
// Populate chaptersArray with chapter metadata (title only for now)
chaptersArray.push({ chapterTitle: item.chapterTitle });
}
});
mainGroupItem.addEventListener('click', () => {
itemList.style.display = itemList.style.display === 'none' ? 'block' : 'none';
});
mainGroupItem.appendChild(itemList);
shastraList.appendChild(mainGroupItem);
});
}
async function fetchChapterData(chapterTitle) {
try {
const response = await fetch(`https://thevera-botveradb.hf.space/get_chapter_details?chapterTitle=${encodeURIComponent(chapterTitle)}`);
const result = await response.json();
if (response.ok && result.success) {
return result.data; // Return chapter details
} else {
console.error("Error fetching chapter details:", result.message);
return null;
}
} catch (error) {
console.error("Error in fetchChapterData:", error);
return null;
}
}
// Function to fetch audio URL
async function fetchAudioUrl(chapterTitle) {
try {
const response = await fetch(`https://thevera-botveradb.hf.space/get_audio_url?chapterTitle=${encodeURIComponent(chapterTitle)}`);
const result = await response.json();
if (response.ok && result.success) {
return result.audioUrl;
} else {
console.error('Error fetching audio:', result.message);
return null;
}
} catch (error) {
console.error('Error fetching audio:', error);
return null;
}
}
async function loadChapter(chapter) {
console.log("Loading chapter...");
console.log(chapter.chapterTitle);
// Save the last accessed chapter in localStorage
localStorage.setItem("lastAccessedChapter", JSON.stringify({
chapterTitle: chapter.chapterTitle
}));
const chapterTitle = document.getElementById('chapterTitle');
const slokeContent = document.getElementById('slokeContent');
const videoPlayer = document.getElementById('videoPlayer');
const transcriptBlock = document.getElementById('transcript-content');
// Ensure the elements are loaded correctly
if (!chapterTitle || !slokeContent || !videoPlayer || !transcriptBlock) {
console.error("Missing elements in chapter display.");
return;
}
// Reset content
chapterTitle.textContent = 'Loading...'; // Temporary loading message
slokeContent.innerHTML = '';
videoPlayer.src = '';
transcriptBlock.innerHTML = ''; // Clear all transcript content
try {
// Fetch chapter details from the backend
const chapterDetails = await fetchChapterData(chapter.chapterTitle);
if (!chapterDetails) {
chapterTitle.textContent = 'Chapter details not found.';
return;
}
// Update UI with chapter details
chapterTitle.textContent = chapterDetails.chapterTitle + " / " + chapterDetails.about || 'Chapter Details';
// Process and display the slokes
const slokesArray = chapterDetails.slokes ? chapterDetails.slokes.split('\n') : [];
slokesArray.forEach((slokeText) => {
const slokeElement = document.createElement('p');
slokeElement.classList.add('sloke-text');
// slokeElement.innerHTML = slokeText.replace(/श्लोक \d+:/g, '$&');
slokeElement.innerHTML = slokeText.replace(/(श्लोक \d+:|अर्थ \d+:)/g, '$&');
const speakIcon = createSpeakIcon(slokeText);
slokeElement.appendChild(speakIcon);
slokeContent.appendChild(slokeElement);
});
// Update video and transcript blocks
// videoPlayer.src = chapterDetails.video || '';
loadYouTubeVideo(chapterDetails.video);
const transcriptArray = chapterDetails.transcript ? chapterDetails.transcript.split('\n') : [];
transcriptArray.forEach((transcriptText) => {
const transcriptElement = document.createElement('p');
transcriptElement.classList.add('transcript-text');
transcriptElement.textContent = transcriptText; // Add transcript text
transcriptBlock.appendChild(transcriptElement);
});
// Show the content blocks
document.querySelector('.main').classList.add('active');
document.querySelector('.sloke-block').classList.add('active');
document.querySelector('.video-block').classList.add('active');
document.querySelector('.transcript-block').classList.add('active');
// Setup play buttons
setupPlayAllButton(slokesArray);
setupPlayAllTranscriptButton(transcriptArray);
// Auto-click to minimize the sidebar group
const chapterGroup = document.querySelector(`.main-group`);
if (chapterGroup) {
chapterGroup.click(); // This simulates a click to collapse the group
}
// Update currentChapterIndex for "Next Chapter" functionality
currentChapterIndex = chaptersArray.findIndex(ch => ch.chapterTitle === chapter.chapterTitle);
console.log("Updated Current Index:", currentChapterIndex);
document.getElementById('shastraHeader').click(); // Simulate a click on the header
} catch (error) {
console.error("Error loading chapter details:", error);
chapterTitle.textContent = 'Failed to load chapter details. Please try again.';
}
}
function loadYouTubeVideo(videoUrl) {
if (!videoUrl) {
console.warn("[Video] No video URL provided");
return;
}
const videoIdMatch = videoUrl.match(/\/embed\/([a-zA-Z0-9_-]+)/);
if (!videoIdMatch) {
console.error("[Video] Invalid video URL format", videoUrl);
return;
}
const videoId = videoIdMatch[1];
currentVideoId = videoId;
const storageKey = `videoTime_${videoId}`;
// resumeTime = parseFloat(localStorage.getItem(storageKey)) || 0;
resumeTime = Math.max((parseFloat(localStorage.getItem(storageKey)) || 0) - 5, 0);
console.log(`[Video] Loading ID: ${videoId}, Resuming at: ${resumeTime}s`);
if (player) {
player.loadVideoById({ videoId, startSeconds: resumeTime });
} else {
player = new YT.Player('videoPlayer', {
height: '315',
width: '100%',
videoId,
playerVars: {
autoplay: 1,
controls: 1,
start: resumeTime
},
events: {
'onReady': (event) => {
console.log("[Player] onReady");
event.target.seekTo(resumeTime, true);
event.target.playVideo();
},
'onStateChange': onPlayerStateChange
}
});
}
}
function onPlayerStateChange(event) {
if (event.data === YT.PlayerState.PLAYING) {
console.log("[Player] Playing");
// Save playback time every 5 seconds
clearInterval(window.saveTimeInterval);
window.saveTimeInterval = setInterval(() => {
if (player && currentVideoId) {
const time = player.getCurrentTime();
localStorage.setItem(`videoTime_${currentVideoId}`, time);
console.log(`[Resume Save] ${currentVideoId}: ${time.toFixed(1)}s`);
}
}, 5000);
}
if (event.data === YT.PlayerState.ENDED) {
console.log("[Player] Video Ended. Clearing resume time.");
localStorage.removeItem(`videoTime_${currentVideoId}`);
clearInterval(window.saveTimeInterval);
}
}
function loadNextChapter() {
console.log("Next Chapter clicked...");
if (currentChapterIndex + 1 < chaptersArray.length) {
currentChapterIndex++; // Increment currentChapterIndex to move to the next chapter
// Auto-click to minimize the sidebar group
const chapterGroup = document.querySelector(`.main-group`);
if (chapterGroup) {
chapterGroup.click(); // This simulates a click to collapse the group
}
loadChapter(chaptersArray[currentChapterIndex]);
} else {
alert("This is the last chapter.");
}
}
function closeSidebarOnMobile() {
const sidebar = document.querySelector(".sidebar");
const overlay = document.querySelector(".overlay");
if (window.innerWidth < 768 && sidebar && overlay) {
sidebar.classList.remove("shown");
overlay.classList.remove("active");
}
}
// Other functions such as createSpeakIcon, cleanSlokText, playTTS, and setupPlayAllButton remain as you provided
function createSpeakIcon(slokeText) {
const speakIcon = document.createElement('i');
speakIcon.classList.add('fas', 'fa-volume-up', 'speak-icon');
speakIcon.setAttribute('title', 'Speak Slok');
speakIcon.addEventListener('click', () => {
console.log(`Speaking: ${slokeText}`);
playTTS(slokeText);
});
return speakIcon;
}
function cleanSlokText(text) {
// Regex to match and remove introductory (e.g., "श्लोक 1:") and ending markers (e.g., "१")
// return text.replace(/श्लोक \d+: /g, '').replace(/ \d+$/g, '');
return text.replace(/(श्लोक \d+:|अर्थ \d+:)/g,'').replace(/ \d+$/g, '');
}
let currentSlokeIndex = 0; // Track the current sloke index
const audioPlayer = document.getElementById('audioPlayer');
// Play all slokes function (replace your existing setupPlayAllButton function)
function setupPlayAllButton(slokesArray) {
const playAllButton = document.getElementById('playAllButton');
playAllButton.onclick = async () => {
currentSlokeIndex = 0; // Reset to start
playNextSloke(slokesArray);
};
}
// Function to play each sloke in sequence
async function playNextSloke(slokesArray) {
if (currentSlokeIndex >= slokesArray.length) {
return; // End if all slokes have been played
}
// Clean and highlight the current sloke text
// const slokeText = cleanSlokText(slokesArray[currentSlokeIndex]);
const slokeText = slokesArray[currentSlokeIndex];
highlightCurrentSloke(currentSlokeIndex);
// Generate TTS audio for the current sloke text
playTTS(slokeText).then(() => {
// Move to the next sloke after the current one finishes
currentSlokeIndex++;
playNextSloke(slokesArray);
});
}
// Function to highlight the current sloke and scroll it into view
function highlightCurrentSloke(index) {
const slokeElements = document.querySelectorAll('.sloke-text'); // Assume each sloke text has this class
// Remove previous highlights
slokeElements.forEach(el => el.classList.remove('highlight'));
// Highlight and scroll the current sloke
const currentSloke = slokeElements[index];
if (currentSloke) {
currentSloke.classList.add('highlight');
currentSloke.scrollIntoView({ behavior: "smooth", block: "center" });
}
}
let currentTranscriptIndex = 0; // Track the current transcript index
// Setup Play All for Transcript
function setupPlayAllTranscriptButton(transcriptArray) {
const playAllTranscriptButton = document.getElementById('playTranscriptButton');
playAllTranscriptButton.onclick = async () => {
currentTranscriptIndex = 0; // Reset to start
playNextTranscript(transcriptArray);
};
}
// Function to play each transcript in sequence
async function playNextTranscript(transcriptArray) {
if (currentTranscriptIndex >= transcriptArray.length) {
return; // End if all transcripts have been played
}
// Highlight the current transcript text
highlightCurrentTranscript(currentTranscriptIndex);
// Generate TTS audio for the current transcript text
const transcriptText = transcriptArray[currentTranscriptIndex];
playTTS(transcriptText).then(() => {
// Move to the next transcript after the current one finishes
currentTranscriptIndex++;
playNextTranscript(transcriptArray);
});
}
// Function to highlight the current transcript text
function highlightCurrentTranscript(index) {
const allTranscripts = document.querySelectorAll('.transcript-text');
allTranscripts.forEach((transcript, i) => {
if (i === index) {
transcript.classList.add('highlight'); // Add highlight class
} else {
transcript.classList.remove('highlight'); // Remove highlight class
}
});
}
let currentAudio; // Global variable to hold the audio object for server-generated audio
let isPlaying = false; // Track play/pause state for playback
let currentUtterance; // Track current SpeechSynthesisUtterance for sa-IN playback
// --- Voice helpers ---
function pickPreferredVoice(voices) {
const tests = [
v => /sa-IN/i.test(v.lang) && /(male|deep|bass|india)/i.test(v.name),
v => /sa-IN/i.test(v.lang),
v => /hi-IN/i.test(v.lang) && /(male|deep|bass|india)/i.test(v.name),
v => /hi-IN/i.test(v.lang),
v => /en-IN/i.test(v.lang) && /(male|deep|bass|india)/i.test(v.name),
v => /en-IN/i.test(v.lang),
];
for (const t of tests) {
const found = voices.find(t);
if (found) return found;
}
return voices[0] || null;
}
function waitForVoices(timeoutMs = 1500) {
return new Promise(resolve => {
const have = speechSynthesis.getVoices();
if (have && have.length) return resolve(have);
const onVoices = () => {
const v = speechSynthesis.getVoices();
if (v && v.length) {
speechSynthesis.removeEventListener('voiceschanged', onVoices);
resolve(v);
}
};
speechSynthesis.addEventListener('voiceschanged', onVoices);
setTimeout(() => {
speechSynthesis.removeEventListener('voiceschanged', onVoices);
resolve(speechSynthesis.getVoices() || []);
}, timeoutMs);
});
}
async function speakClientSide(text) {
const voices = await waitForVoices();
if (!voices.length) throw new Error('No TTS voices available');
const voice = pickPreferredVoice(voices);
const langFallback = voice?.lang || 'sa-IN';
// Stop any previous utterances
try { speechSynthesis.cancel(); } catch {}
// Split by pipe/comma and add small pauses after delimiters
const parts = text.split(/(\||,)/).map(s => s.trim()).filter(Boolean);
for (const chunk of parts) {
const u = new SpeechSynthesisUtterance(chunk);
if (voice) u.voice = voice;
u.lang = voice?.lang || langFallback;
u.rate = 0.9; // or your saved rate
u.pitch = 0.9; // or your saved pitch
u.volume = 1.0;
await new Promise((res, rej) => {
u.onend = res;
u.onerror = rej;
speechSynthesis.speak(u);
});
if (chunk === '|' || chunk === ',') {
await new Promise(r => setTimeout(r, 220));
}
}
}
// Speak text in segments (pauses at "|" and ",")
async function speakClientSideOld(text) {
const st = ttsGet();
const all = await getAllVoices();
if (!all.length) throw new Error('No TTS voices available');
// pick language preference sa-IN -> hi-IN -> en-IN
const prefLangs = ['sa-IN', 'hi-IN', 'en-IN'];
let chosenLang = prefLangs.find(l => st.byLang[l]?.voiceURI) || prefLangs.find(l => all.some(v => v.lang && v.lang.startsWith(l)));
if (!chosenLang) chosenLang = all[0].lang || 'hi-IN';
let voice = null;
const savedURI = st.byLang[chosenLang]?.voiceURI;
if (savedURI) voice = all.find(v => v.voiceURI === savedURI) || null;
if (!voice) {
const list = voicesForLang(all, chosenLang);
voice = list[0] || all[0] || null;
}
const parts = text.split(/(\||,)/).map(s => s.trim()).filter(Boolean);
for (const chunk of parts) {
const u = new SpeechSynthesisUtterance(chunk);
if (voice) u.voice = voice;
u.lang = voice?.lang || chosenLang;
u.rate = st.rate;
u.pitch = st.pitch;
await new Promise((res, rej)=>{ u.onend = res; u.onerror = rej; speechSynthesis.speak(u); });
if (chunk === '|' || chunk === ',') await new Promise(r=>setTimeout(r,220));
}
}
async function playTTS(text) {
console.log('Speaking mantra');
if (!text || text.trim() === '') {
console.error('No text provided');
return;
}
const cleanText = typeof cleanSlokText === 'function' ? cleanSlokText(text) : text;
// Helper to play a URL in your existing