Lyrical thoughts and moans and groans

HMMMM

Active member
I’ve just finished attempting to add lyrics and synchronize them with the music on my website. It’s a mess, but it has been fun! It all boils down to synchronizing lyrics.

Can anyone create a system that will automatically synchronize lyrics with songs across a large library (like 40,000 songs) using AI and metadata such as ID3 tags? This would involve:

  1. Reading the metadata (ID3 tags) from MP3 files.
  2. Fetching or generating the lyrics for each song.
  3. Synchronizing the lyrics with the song’s timeline (timestamps for each line or verse).
  4. Embedding the lyrics back into the MP3 file with synchronized timing.

Plan Outline for the Project:​

This could be broken down into key components, and the tool would be designed to:

  1. Read the MP3 file’s metadata to get song details like artist, album, title, etc. This will help us match the song to its lyrics.
  2. Use an AI-powered tool or service to fetch lyrics for the given song. You could use databases like Genius API or Musixmatch API to get lyrics based on song metadata. Optionally, a model could be trained to extract lyrics from the song itself if they aren’t available.
  3. Sync the lyrics to the music: This is the most complex part. You would need to analyze the audio of the song to sync the lyrics with the music. Using audio analysis tools (like SonicAPI, Aubio, or machine learning models) to identify when specific words or phrases are sung would work. These timestamps would need to be calibrated to match the flow of the song accurately.
  4. Embed the lyrics into the MP3: After synchronizing the lyrics, we need to embed them back into the MP3 file using the ID3 tags (specifically the USLT (Unsynchronized Lyrics) frame for lyrics, or if using LRC format, embed that too).
  5. Batch processing for efficiency: The program will need to handle batch processing efficiently to process thousands of songs at once. You could run it in parallel for multiple files to speed up the process.

Key Steps in More Detail:​

Step 1:Use a Python library like eyed3 or tinytag to extract MP3 file metadata.

Example:

python
CopyEdit
<span><span>import</span> eyed3<br><br><span>def</span> <span>get_mp3_metadata</span>(<span>mp3_path</span>):<br> audio_file = eyed3.load(mp3_path)<br> title = audio_file.tag.title<br> artist = audio_file.tag.artist<br> album = audio_file.tag.album<br> <span>return</span> title, artist, album<br></span>
Step 2:For fetching lyrics from an API (e.g., Genius API), you can use the song title and artist to search.

Example using requests:

python
CopyEdit
<span><span>import</span> requests<br><br><span>def</span> <span>fetch_lyrics</span>(<span>song_title, artist_name</span>):<br> url = <span>f"https://api.genius.com/search?q=<span>{song_title}</span> <span>{artist_name}</span>"</span><br> headers = {<span>"Authorization"</span>: <span>"Bearer YOUR_ACCESS_TOKEN"</span>}<br> response = requests.get(url, headers=headers)<br> json_data = response.json()<br> song_path = json_data[<span>'response'</span>][<span>'hits'</span>][<span>0</span>][<span>'result'</span>][<span>'url'</span>]<br> lyrics = extract_lyrics_from_url(song_path) <span># You'd have to scrape the lyrics from the song URL</span><br> <span>return</span> lyrics<br></span>
Step 3:For this, you'd need audio analysis. This can be done using libraries like Aubio or a custom deep learning model trained on song lyrics and timings.

A simpler method would involve:

  • Audio segmentation to identify the tempo and beats.
  • Speech-to-text or lyric recognition tools to detect the timing of lyrics.For more precision, consider using an existing deep learning model or a tool like SonicAPI, which can generate timestamps for lyrics.
Step 4:After synchronizing the lyrics with timestamps, embed them into the MP3. For this, you’d use the eyed3 library to add or update ID3 tags.

Example:

python
CopyEdit
<span><span>import</span> eyed3<br><br><span>def</span> <span>embed_lyrics_in_mp3</span>(<span>mp3_path, lyrics</span>):<br> audio_file = eyed3.load(mp3_path)<br> frame = audio_file.tag.frame_set(eyed3.id3.frames.USLT)<br> frame.set_text(lyrics) <span># Set the lyrics with timing</span><br> audio_file.tag.save()<br></span>
Step 5:To handle 40,000 songs, you can run the script in batches, processing files in parallel (using multiprocessing or an async approach).

Example:

python
CopyEdit
<span><span>from</span> concurrent.futures <span>import</span> ThreadPoolExecutor<br><br><span>def</span> <span>process_mp3_file</span>(<span>mp3_file</span>):<br> title, artist, album = get_mp3_metadata(mp3_file)<br> lyrics = fetch_lyrics(title, artist)<br> sync_lyrics = synchronize_lyrics(lyrics, mp3_file)<br> embed_lyrics_in_mp3(mp3_file, sync_lyrics)<br><br>mp3_files = [<span>"song1.mp3"</span>, <span>"song2.mp3"</span>, <span>"song3.mp3"</span>]<br><span>with</span> ThreadPoolExecutor() <span>as</span> executor:<br> executor.<span>map</span>(process_mp3_file, mp3_files)<br></span>

Challenges and Considerations:​

  • Accuracy of Lyrics Sync: Automatically syncing lyrics is quite challenging, especially with songs that have fast lyrics or varying tempos. Using pre-trained models for speech recognition or music-specific analysis would help, but it may require fine-tuning.
  • Handling Missing Lyrics: Not every song will have available lyrics through APIs. A fallback method could involve generating approximate timing based on audio features.
  • Performance: Processing 40,000 songs requires optimization. Ensure you’re handling memory usage, concurrency, and large file sizes effectively.

Tools/Libraries to Consider:​

  • eyed3: For reading/writing ID3 tags.
  • requests: For interacting with external lyric APIs.
  • Aubio: For audio analysis and tempo detection.
  • SonicAPI or Google Speech-to-Text: For more advanced audio-to-lyrics synchronization.
  • multiprocessing: For parallel processing.

Conclusion:​

Creating an executable that processes 40,000 songs, extracts metadata, fetches lyrics, syncs them, and embeds them back into the MP3s is definitely feasible. The core challenge is syncing the lyrics with the audio, but leveraging existing APIs and audio analysis libraries can simplify this process.

 
My new website is a bit more complicated. It uses AJAX to spawn a new copy of itself, embedding server-run PHP code, which restarts the whole process. I'm encountering some teething problems, including CSS issues and a lyrics popup that isn't working.

The reading and dumping to SQL are faster now, and I've hooked the lyrics into JavaScript on the backend. The idea was to time the lyrics by breaking them down into sentences and calculating the duration for each word. This would then let me multiply up the sentence to get the timings. It would have been great, but the issue is that the songs don't start with lyrics but have an iteration. I wondered if these timings could be acquired but have not found anything to date. Have you any ideas?

hugs D x
 
  • Time A: The timestamp when the last lyric finishes (the end of the lyrics).
  • Time B: The timestamp when the song actually ends (the very last note, beat, or fade-out of the song).
I’ve had another thought, and I believe this is doable.

Time A would be the timestamp when the last lyric finishes (the end of the lyrics), and Time B would be the timestamp when the song actually ends (the very last note, beat, or fade-out). This would give us the amount of time before the lyrics start, essentially calculating the intro length of the song, excluding the outro.

Knowing the total length of the lyrics can be calculated by determining the number of words in the sentence multiplied by the interval time for each word, which can be determined by dividing the total number of words by the length of the song. If we have the song length and the timings for each sentence, we should be able to work out the first intro before the lyrics.

For example, if the entire song’s lyrics take 8 seconds per word (based on BPM and track length) and there are 23 words in the song, we can calculate the total length of the lyrics as: 8 seconds × 23 words = 184 seconds. If the total song length is 202 seconds, we can then subtract the length of the lyrics (184 seconds) from the total song length (202 seconds), giving us 18 seconds of intro before the lyrics start.

Hugs D
 
Well, I was getting on ok, but the Lyrics didn't work very well with the timings for a base time in the sentences adding up the beat per min didn't seem to get them on the right place I Introduced a WordsForbeat however my web services have gone down and I cannot do much. I was setting a check on the artwork file that tested for its change driving all content. The Change requiring WordsForbeat seemed better but the calculations for the start of lyrics are a bit hazy as the service on my website was barely working.

I am attaching code here as conversation piece as it doesn't work and don't know when my webservices will be back It its in a DIV ID 'Lyric-handler' and requites a gCheckURL = '/fetch_sql2.php'; to fetch the list of playlists which has a hidden Lyric table in it. The general process is that it runs through when the image changes which is detected in checkArtworkFileChange() running on an interval. You should note this uses eval on this script every time a new page is drawn up via fetch routines.


JavaScript:
<script id='Lyric-handler' type='text/javascript'>
    var sentences = null;
    var wordCount = 0;
    var displayDuration = 0;
    var total_words  = 0 ;
    var timePerSentence = 0;
    var songStartTime = 0;
    var songEndTime = 0;
    var songstartingat =0;
    var lyricsContainer = 0;
    var gCheckURL = '/fetch_sql2.php';
    var lyricsStarted = false;
    var WordsPerBeat = 0;
    var trackLength = 0;

    function logWithTimestamp(message) {
            let now = new Date();
            let formattedTime = now.toLocaleTimeString('en-GB', { hourCycle: 'h23' });
            let formattedDate = now.toISOString().split('T')[0]; // YYYY-MM-DD
            console.log('[' , formattedDate , ' ' , formattedTime , '] ' , message);
    }
    function loadVariables() {

            var sentences = ".json_encode($WordsArray).";
            var baseIntervalPerWord = ".$interval." * 1000;
            var lyricsContainer = document.getElementById('lyrics-container'); // new container
            var trackLength = " . ($totalSeconds) . ";

            var trackLengthInSeconds = trackLength; // Seconds to Convert ms

            var player = document.getElementById('player');
            var lyricsInterval;
            var gCheckURL = '/fetch_sql2.php';
            var gCheckJavaURL = '/js/fetch_java.js';      

            var lyricsStarted = false;  // ✅ This must be declared at the top

            // Function to count words in a sentence (for lyrics or other text)
            function countWords(sentence) {
            if (!sentence) return 0; // Handle empty or null sentences
               return sentence.trim().split(/\s+/).filter(function(word) {
                  return word.length > 0;
               }).length;
            }

            // Calculate Lyric intro
            // Ensure trackLength is in seconds if in milliseconds
            var trackLengthInSeconds = trackLength / 1000;  // Convert milliseconds to seconds
            var total_words =  ".$total_words.";
            var bpm = ".$bpm.";
            var totalSentences = sentences.length;
            var totalWordsMillionSeconds = total_words * 1000;
           
            var timePerSentence = trackLength / totalSentences;
            logWithTimestamp('Total Sentances: '+ totalSentences + ' Time Per Sentance: '+ timePerSentence + ' Sentences:'+ sentences);
         
            var WordsPerBeat = total_words / (trackLength * bpm/60);
            logWithTimestamp('WordsPerbeat '+WordsPerBeat  + 's'); // This will echo the string with the result
            logWithTimestamp('Nowords: '+total_words); // This will echo the string with the result
            logWithTimestamp('bpm: '+".$bpm."); // This will echo the string with the result
            logWithTimestamp('Raw: Track Length: '+ trackLength+' s');              
            logWithTimestamp('Track Length (Million seconds): '+ totalWordsMillionSeconds+ ' ms');
             var milliseconds_per_beat = 60000 / (bpm/2);  // Convert bpm to milliseconds per beat
            // Ensure that trackLengthInSeconds, total_words, and bpm are valid numbers
            if (isNaN(trackLength) || isNaN(total_words) || isNaN(bpm) || bpm === 0) {
                 logWithTimestamp('Invalid values detected');
                 var lyric_intro = 0; // Default to 0
            } else {
                // Calculate the total duration of lyrics in seconds
                 var wordTimeInSeconds = (total_words * 60) / (bpm/2);
   
                // If lyrics take up the full song, intro must be 0
                if (Math.abs(trackLength- wordTimeInSeconds) < 1) {
                      lyric_intro = 0;
                      WordsPerBeat = 0;
                      logWithTimestamp('Lyrics start immediately and go to the end. No intro.');
                } else {
                     // Otherwise, calculate the intro correctly
                     lyric_intro = Math.max(0, trackLength - wordTimeInSeconds)-5000;// 5 s delay for loading
                     if (lyric_intro < 0) {
                        lyric_intro = 0;  // Ensure intro time is non-negative
                     }
               
                }

            }              

            logWithTimestamp('Intro: '+ lyric_intro); // This will echo the string with the result

            // Abort any active AJAX requests
            //if (window.ajaxRequest) {
            //     window.ajaxRequest.abort();  // Abort any ongoing AJAX request
            //     logWithTimestamp('AJAX request aborted.');
            //}
    }
    loadVariables();
 
 
   
    if (document.removeEventListener) {
        document.removeEventListener('DOMContentLoaded', onDOMContentLoaded);
    }
   
 
    document.addEventListener('DOMContentLoaded', onDOMContentLoaded);  
    function onDOMContentLoaded() {
           
            logWithTimestamp('DCOM was reloaded after new artwork frm AJAX');

           
           
           

            function checkAudioReadyAndStartLyrics() {
               
              var track = document.getElementById('MediaPlayer1'); // Your audio element
              track.load(); // Reload the audio to ensure it's initialized
              logWithTimestamp('Checking for Audio.');
              // Check if the audio is playing and has enough data to start
              if (track) {
                  logWithTimestamp('Audio is ready and playing, starting lyrics...');
                   startLyricsAfterDelay();  // Start the lyrics after the initial delay
              } else {
                  logWithTimestamp('Audio not playing or not ready, doing nothing.');
              }
            }
           

            // Function to display sentences one by one
         


            function displaySentences(index) {
               
                songEndTime = songStartTime + trackLength;  // The time when song ends (in milliseconds)
                songstartingat =  Date.now();  // Track when song starts
                console.log('Executing Lyrics: ', new Date(songstartingat).toLocaleString(), 'Ends: ', new Date(songEndTime).toLocaleString());  if (Date.now() > songEndTime) {
                console.log('Track Length: ' , trackLength);
                    logWithTimestamp('Song has ended. Stopping lyrics.');
                    sentences=[];
                    total_words=0;
                    lyricsContainer.innerText = '🎶 End of Lyrics 🎶';
                    return;
                }

                if(total_words === 0)
                {
                    lyricsContainer.innerText = 'Sorry No Lyrics 🎶';
                    logWithTimestamp('Sorry No Lyrics.');
                    return;
                }
                // Check how much time has passed since we started playing music
                var songAlreadyPlayed = songstartingat -songStartTime;
                logWithTimestamp('We missed seconds from start while we was processing: '+ songAlreadyPlayed);

                if (index < sentences.length) {
                    var sentence = sentences[index].trim();
                    var wordCount = countWords(sentence);
                    //var displayDuration = wordCount * baseIntervalPerWord;
                    var displayDuration = wordCount * WordsPerBeat;

                    lyricsContainer = document.getElementById('lyrics-container');
                    lyricsContainer.innerText = '🎶 ' + sentence + ' 🎶';
                   
                    logWithTimestamp('Displaying sentence ' + index + ': \'' + sentence + '\' for ' + displayDuration + 'ms');

                   
                   

                    // Wait for the sentence to finish displaying before showing the next one
                    setTimeout(function() {

                        if (Date.now() <= songEndTime) {
                             displaySentences(index + 1);
                        }
                    }, displayDuration); // was timePerSentence but we changed to distribute
                 } else {  
                    if(index > totalSentences-1 ) {
                        logWithTimestamp('Lyrics Ended.');
                        lyricsContainer.innerText = 'End of Lyrics 🎶';
                    }
                }
   
            }
           // Accessing the script inside the div and running it with eval()
           function evaluateScript() {
              // Get the script content from the div
               const scriptContent = document.getElementById('Lyric-handler').innerText;
              // Execute the script content with eval
              logWithTimestamp('We attached listener: checkAudioReadyAndStartLyrics');
              setTimeout(checkAudioReadyAndStartLyrics, 1000); // Retry in 1 second
              logWithTimestamp('Evaluating the Script Again');
              try {
                     // Try evaluating the script content
                    eval(scriptContent);
                    console.log('Script executed successfully');
                  } catch (error) {
                    // Catch any error that occurs during the execution
                    console.error('Error during eval execution:', error);
                   
              }
              logWithTimestamp('Finished Evaluating the Script.');
       
             
           }

           function parseLyrics(lyrics, baseInterval) {
                var words = [];
                var currentTime = 0; // Start time in milliseconds

                const lines = total_words.split(String.fromCharCode(10)); // Now correctly splits lyrics newline
                lines.forEach(line => {
                    const lineWords = line.split(/\s+/); // Split words by spaces
                    lineWords.forEach(word => {
                         words.push({ word, timestamp: currentTime });
                         currentTime += baseInterval; // Increase time for next word
                     });
                 });
         
                return words;
           }
           function searchWord(words, searchTerm, partialMatch = false) {
               var results = words.filter(entry => {
               if (partialMatch) {
                   return entry.word.toLowerCase().includes(searchTerm.toLowerCase());
               }
                return entry.word.toLowerCase() === searchTerm.toLowerCase();}).map(entry => ({ time: entry.timestamp, section: entry.section }));

                return results.length > 0 ? results : null;
           }        

           function analyzeLyrics(sections) {
                  let analysis = {
                  sectionOrder: [],
                  sectionCounts: {},
                  sectionTimings: {},
           };

           for (
                 let sectionName in sections) {
                 let section = sections[sectionName];

                // Store section order
                analysis.sectionOrder.push(sectionName);

                // Count occurrences of each section
                analysis.sectionCounts[sectionName] = (analysis.sectionCounts[sectionName] || 0) + 1;

                // Store section start and end times
                let start = section.start;
                let end = section.words.length > 0 ? section.words[section.words.length - 1].timestamp : start;
                analysis.sectionTimings[sectionName] = { start, end };
             }

            return analysis;
            }
            // Function to start the lyric display after an initial delay
            function isSentencesValid(sentences) {
                   return sentences !== null ;
            }


           async function waitForSentencesToLoad(maxRetries = 10, retryInterval = 100) {
             return new Promise((resolve, reject) => {
                 let retries = 0;
 
                // Immediately try loading the variables once when the function is called
                loadVariables();
                console.log('Raw Sentences Data:', sentences);
                // Check at intervals until sentences have been populated
                var interval = setInterval(() => {
                if (isSentencesValid(sentences)) {
                 // Sentences are loaded, clear the interval and resolve the promise
                 clearInterval(interval);
                  resolve();
                 } else if (retries >= maxRetries) {
                    // Retry limit exceeded, reject the promise
                    clearInterval(interval);
                    reject('Failed to load sentences after maximum retries');
                 }
                 retries++;
                  }, retryInterval);  // Check every 100ms (or your desired interval)
               });
           }

           function startLyricsAfterDelay() {

            waitForSentencesToLoad().then(() => {
                logWithTimestamp('Starting lyrics...');      
                if (lyricsStarted)
                {  
                   
                    logWithTimestamp('Lyrics already started, skipping...');
                }
                lyricsStarted = true;
                var track = document.getElementById('MediaPlayer1'); // Your audio element
                if (track.readyState <= 3 && !track.paused && !track.ended)  {
                    startLyricsAfterDelay() ;  // `3` means enough data to play
                    logWithTimestamp('Music not started, skipping...');
                }
                // Calculate total lyric time
                var totalLyricTime = total_words / WordsPerBeat;  // Total time in seconds to display all lyrics

                // Calculate the delay in milliseconds before starting the lyrics
                var delay = (trackLength - totalLyricTime) * 1000;  // Delay in milliseconds
                lyric_intro = delay; // Default to milliseconds
                logWithTimestamp('Starting lyrics in'+ delay+ 's');

                // If `lyric_intro` is in seconds, convert to milliseconds
                if (lyric_intro < 300) { // Assuming no song is longer than 5 minutes (300 sec)
                   delay = lyric_intro * 1000;
                }

                logWithTimestamp('Using Delay (ms):'+ delay); // Debugging

                setTimeout(function() {
                 logWithTimestamp('Starting lyrics...');
                 if (isSentencesValid(sentences )) {
                      displaySentences(0);
                  } else {
                     logWithTimestamp('No lyrics loaded 🎶. We had no sentances');
                  }
                }, delay);
           
            }).catch(() => {
             logWithTimestamp('Error: Failed to load sentences');
            });
        }
         
            // Function to handle new song response (AJAX Response)
            function checkForSentences() {
                var lyrics = document.getElementById('onhoverlyric0'); // Assuming this is the element for lyrics
                if (lyrics && lyrics.innerHTML.trim() !== '') {
                  logWithTimestamp('Lyrics populated successfully!');
                   // Proceed with further actions after lyrics are populated
                   startLyricsAfterDelay();
                } else {
                   logWithTimestamp('Lyrics not populated yet, retrying...');
                   setTimeout(checkForSentences, 1000);  // Retry after another 1 second
                }
            }
            function CheckResponse(transport) {
             
                logWithTimestamp('Aborting Listener and removing event and lyrics ..');
                document.removeEventListener('DOMContentLoaded', onDOMContentLoaded);
                logWithTimestamp('Wait for new spawned version and listener');               
                var table = document.getElementById('np_track_text');
                table.innerHTML = transport.responseText;
                logWithTimestamp('Songs table updated.');
               
                 // Reattach the event listener for the new song after AJAX processing
                 if (document.addEventListener) {
                     document.addEventListener('DOMContentLoaded', onDOMContentLoaded);
                 }
                logWithTimestamp('Reattached DCOM Listener');
                evaluateScript();    
                setTimeout(checkForSentences, 1000);  // Check after 1 second delay
                // Once the song info is updated, start displaying the
                //lyrics We have finished and the new  version of this will be launched  from fetch
            }
                 
            function JavaResponse(trasport) {
            //Do somthing

            }

            function RemoveVariables() {
                // Clear any ongoing intervals
                sentences = [];
                total_words = 0;
                lyricsContainer.innerText = '';
                lyric_intro = 0;
                wordCount = 0;
                displayDuration = 0;
                ordsPerBeat = 0;
                timePerSentence = 0;
                if (window.artworkCheckInterval) {
                      clearInterval(window.artworkCheckInterval);  // Stop the artwork checking interval
                      logWithTimestamp('Artwork check interval stopped.');
                }
                // Clear any ongoing timeouts (if necessary)

                if (window.someTimeoutVariable) {
                      clearTimeout(window.someTimeoutVariable);  // Stop any active timeouts
                      logWithTimestamp('Timeout cleared.');
                }
                   
                // Stop any periodic song checks or other background tasks
                if (window.songCheckInterval) {
                     clearInterval(window.songCheckInterval);  // Stop song checking intervals
                     logWithTimestamp('Song check interval stopped.');
                }
               
            }

            // Function to check if a new song has started
            function checkForNewSong() {
               songStartTime = Date.now();
               console.log('New Song Started: ', new Date(songStartTime).toLocaleString());

                logWithTimestamp('Killing of events in preparation for AJAX spawn...');
                RemoveVariables();
                logWithTimestamp('We have new song...AJAX');
                // Your AJAX request to check for a new song
               
                logWithTimestamp('We have new song...Starting AJAX Process');
                new Ajax.Request(gCheckURL, {
                    method: 'get',
                    onSuccess: CheckResponse  // Handle the response in CheckResponse function
                });
      
            }

            // Monitor artwork file changes
            function checkArtworkFileChange() {
                fetch('/images/nowplaying_artwork_2.png', { method: 'HEAD' })
                    .then(response => {
                        var fileTimestamp = new Date(response.headers.get('Last-Modified')).getTime();
                        var lastModTime = localStorage.getItem('lastModTime');
                        if (!lastModTime || fileTimestamp !== parseInt(lastModTime)) {
                            logWithTimestamp('Artwork has changed!');
                            localStorage.setItem('lastModTime', fileTimestamp);  // Update timestamp
                            var main_artwork = document.getElementById('np_track_artwork');
                            main_artwork.src = '/images/nowplaying_artwork_2.png?t=' + Date.now();  // Refresh the artwork
                            checkForNewSong();  // Call checkForNewSong after artwork changes
                        }
                    })
                    .catch(error => console.error('Error checking artwork:', error));
            }
           
            // Check for artwork file changes every song
            setInterval(checkArtworkFileChange, 14000);

            // Handle DJ image change based on BPM
            var bpm = document.getElementById('bpm');
            var object_dj = document.getElementById('DJ');

            if (bpm && object_dj) {
                var pImageUrl = 'images/ezgif.com-medium.gif';
                if (bpm.innerHTML < 135) {
                    pImageUrl = 'images/ezgif-2-f0284e7093b1.gif';
                } else if (bpm.innerHTML > 180) {
                    pImageUrl = 'images/bandicam-2023-03-31-14-59-01-846.gif';
                }
                object_dj.style.backgroundImage = 'url(' + pImageUrl + ')';
            }

            // Restart song and lyrics when song ends
            player.addEventListener('ended', function() {
                logWithTimestamp('Song ended. Checking for new song...');
                checkForNewSong();
            });
           

            // Track progress and update accordingly

         

            player.addEventListener('timeupdate', function() {
                if (player.currentTime < 1 && !player.paused) {
                    logWithTimestamp('New song detected!');
                    //checkForNewSong();
                }
            });
    };
    onDOMContentLoaded();
    </script>


HUGS D X
 
I got back into my website, but something strange was happening: others could see it, but I couldn't. There seemed to be an issue with variables and scope, so I resolved it by changing var to a globalThis type variable. However, Chrome did not support this variable type globalThis, so I added a function to define globalThis in case it wasn't already defined, as it was introduced only in 2020.

JavaScript:
if (typeof globalThis === 'undefined') {
(function() {
    // Polyfill for globalThis
    Object.defineProperty(window, 'globalThis', {
        get: function() {
            return this;
        }
    });
})();
}

Hugs
 
Update - LYrics
OK I had some more time today and got it dispaying lyrics like alst week but with better code. I t went from something to nothing working and back up over the last time I posted. Even if they are at the wrong start time, they seem to be in the correct timings. Less stressful on the server too I reordered the Functions so they called nin sequence so overall its better but certainly no final version the start time is a really thorny issue the first song, i think, will be as foobar as you cannot get a start time only from the image update. I hope i get more time the dates from 1970 are just like cobal but in milliseconds. Anyway it is what it is so far. Let me know if you have any ideas.


Code:
<script id='Lyric-handler' type='text/javascript'>
    if (typeof globalThis === 'undefined') {
        (function() {
            // Polyfill for globalThis
            Object.defineProperty(window, 'globalThis', {
                get: function() {
                    return this;
                }
            });
        })();
    }
globalThis.sentences = null;

globalThis.wordCount = 0;
globalThis.displayDuration = 0;
globalThis.total_words = 0;
globalThis.timePerSentence = 0;
globalThis.songStartTime = 0;
globalThis.songEndTime = 0;
globalThis.songstartingat = 0;
globalThis.lyricsContainer = 0;
globalThis.gCheckURL = '/fetch_sql2.php';
globalThis.lyricsStarted = false;
globalThis.WordsPerBeat = 0;
globalThis.trackLength = 0;
globalThis.songAlreadyPlayed = 0;

function logWithTimestamp(message) {
    let now = new Date();
    let formattedTime = now.toLocaleTimeString('en-GB', {
        hourCycle: 'h23'
    });
    let formattedDate = now.toISOString().split('T')[0]; // YYYY-MM-DD
    console.log('[', formattedDate, ' ', formattedTime, '] ', message);
}

// Function to count words in a sentence (for lyrics or other text)
function countWords(sent) {
    if (!sent) return 0; // Handle empty or null sentences
    return sent.trim().split(/\s+/).filter(function(word) {
        return word.length > 0;
    }).length;
}

function loadVariables() {

    globalThis.sentences = ".json_encode($WordsArray).";
    globalThis.baseIntervalPerWord = ".$interval." * 1000;
    globalThis.lyricsContainer = document.getElementById('lyrics-container'); // new container
    globalThis.trackLength = " . ($totalSeconds) . ";

    globalThis.trackLengthInSeconds = globalThis.trackLength; // Seconds to Convert ms

    globalThis.player = document.getElementById('player');
    globalThis.lyricsInterval;
    globalThis.gCheckURL = '/fetch_sql2.php';
    globalThis.gCheckJavaURL = '/js/fetch_java.js';
    globalThis.lyricsStarted = false; // ✅ This must be declared at the top
    // Calculate Lyric intro
    // Ensure trackLength is in seconds if in milliseconds
    globalThis.trackLengthInSeconds = globalThis.trackLength / 1000; // Convert milliseconds to seconds
    globalThis.total_words = ".$total_words.";
    globalThis.bpm = ".$bpm.";
    globalThis.totalSentences = globalThis.sentences.length;
    globalThis.totalWordsMillionSeconds = globalThis.total_words * 1000;

    globalThis.timePerSentence = globalThis.trackLength / globalThis.totalSentences;


    globalThis.WordsPerBeat = globalThis.total_words / (globalThis.trackLength * bpm / 60);
    globalThis.milliseconds_per_beat = 60000 / (globalThis.bpm / 2); // Convert bpm to milliseconds per beat
    logWithTimestamp('Total Sentances: ' + globalThis.totalSentences + ' Time Per Sentance: ' + globalThis.timePerSentence + ' Sentences:' + globalThis.sentences);
    logWithTimestamp('WordsPerbeat ' + globalThis.WordsPerBeat + 's'); // This will echo the string with the result
    logWithTimestamp('Nowords: ' + globalThis.globalThis.total_words); // This will echo the string with the result
    logWithTimestamp('bpm: ' + ".$bpm."); // This will echo the string with the result
    logWithTimestamp('Raw: Track Length: ' + globalThis.trackLength + ' s');
    logWithTimestamp('Track Length (Million seconds): ' + totalWordsMillionSeconds + ' ms');


    // Ensure that trackLengthInSeconds, total_words, and bpm are valid numbers
    if (isNaN(globalThis.trackLength) || isNaN(globalThis.total_words) || isNaN(globalThis.bpm) || globalThis.bpm === 0) {
        logWithTimestamp('Invalid values detected');
        globalThis.lyric_intro = 0; // Default to 0
    } else {
        // Calculate the total duration of lyrics in seconds
        var wordTimeInSeconds = (globalThis.total_words * 60) / (bpm / 2);

        // If lyrics take up the full song, intro must be 0
        if (Math.abs(globalThis.trackLength - wordTimeInSeconds) < 1) {
            globalThis.lyric_intro = 0;
            globalThis.WordsPerBeat = 0;
            logWithTimestamp('Lyrics start immediately and go to the end. No intro.');
        } else {
            // Otherwise, calculate the intro correctly
            lyric_intro = Math.max(0, globalThis.trackLength - wordTimeInSeconds) - 5000; // 5 s delay for loading
            if (globalThis.lyric_intro < 0) {
                globalThis.lyric_intro = 0; // Ensure intro time is non-negative
            }

        }

    }
    //checkArtworkFileChange(); 

    logWithTimestamp('Intro: ' + lyric_intro); // This will echo the string with the result
 
}
// Function to display sentences one by one         
function displaySentences(index) {

    // Calculate song end time (make sure to convert songStartTime to a timestamp)
    // Log the current time to ensure it's correctly set
    console.log('Song Start Time:', new Date(globalThis.songStartTime).toLocaleString()); // This should log a valid date

    // Calculate the song end time by adding the track length (in seconds) to the start time
    globalThis.songEndTime = globalThis.songStartTime + globalThis.trackLength * 1000; // Add track length  to lobalThis.songStartTime in milliseconds
    
    // Check how much time has passed since we started playing music
    
    globalThis.songAlreadyPlayed = globalThis.songstartingat - globalThis.songStartTime;
    var differenceInTime = Date.now() - globalThis.songAlreadyPlayed;
    logWithTimestamp('We missed seconds from start while we was processing: ' + differenceInTime);
    

    // Log the start and end times
    console.log(
        'Executing Lyrics: Track Length', globalThis.trackLength,
        ', Start Time:', new Date(globalThis.songStartTime).toLocaleString(),
        ', Ending:', new Date(globalThis.songEndTime).toLocaleString()
    );

    if (globalThis.total_words === 0) {
        lyricsContainer.innerText = 'Sorry No Lyrics 🎶';
        logWithTimestamp('Sorry No Lyrics.');
        return;
    }
    


    if (index < globalThis.sentences.length) {
        var sentence = globalThis.sentences[index].trim();
        wordCount = countWords(sentence);
        var displayDuration = wordCount * baseIntervalPerWord;
        //var displayDuration = wordCount * WordsPerBeat;

        lyricsContainer = document.getElementById('lyrics-container');
        lyricsContainer.innerText = '🎶 ' + sentence + ' 🎶';

        logWithTimestamp('Displaying sentence ' + index + ': \'' + sentence + '\' for ' + displayDuration + 'ms');




        // Wait for the sentence to finish displaying before showing the next one
        setTimeout(function() {

            if (Date.now() <= globalThis.songEndTime) {
                displaySentences(index + 1);
            }
        }, displayDuration); // was timePerSentence but we changed to distribute
    } else {
        if (index > totalSentences - 1) {
            logWithTimestamp('Lyrics Ended.');
            lyricsContainer.innerText = 'End of Lyrics 🎶';
        }
    }

  }
// Function to start the lyric display after an initial delay
    function isSentencesValid(sentences) {
        return sentences !== null;
    }

    // Function to handle new song response (AJAX Response)
    function checkForSentences() {
        var lyrics = document.getElementById('onhoverlyric0'); // Assuming this is the element for lyrics
        if (lyrics && lyrics.innerHTML.trim() !== '') {
            logWithTimestamp('Lyrics populated successfully!');
            // Proceed with further actions after lyrics are populated
            startLyricsAfterDelay();
        } else {
            logWithTimestamp('Lyrics not populated yet, retrying...');
            setTimeout(checkForSentences, 1000); // Retry after another 1 second
        }
    }
  async function waitForSentencesToLoad(maxRetries = 10, retryInterval = 100) {
        return new Promise((resolve, reject) => {
            let retries = 0;

            // Immediately try loading the variables once when the function is called
            loadVariables();
            console.log('Raw Sentences Data:', globalThis.sentences);
            // Check at intervals until sentences have been populated
            var interval = setInterval(() => {
                if (isSentencesValid(globalThis.sentences)) {
                    // Sentences are loaded, clear the interval and resolve the promise
                    clearInterval(interval);
                    resolve();
                } else if (retries >= maxRetries) {
                    // Retry limit exceeded, reject the promise
                    clearInterval(interval);
                    reject('Failed to load sentences after maximum retries');
                }
                retries++;
            }, retryInterval); // Check every 100ms (or your desired interval)
        });
    }
    function startLyricsAfterDelay() {

        waitForSentencesToLoad().then(() => {
            logWithTimestamp('Starting lyrics...');
            if (lyricsStarted) {

                logWithTimestamp('Lyrics already started, skipping...');
            }
            globalThis.lyricsStarted = true;
            var track = document.getElementById('MediaPlayer1'); // Your audio element
            if (track.readyState <= 3 && !track.paused && !track.ended) {
                startLyricsAfterDelay(); // `3` means enough data to play
                logWithTimestamp('Music not started, skipping...');
            }
            // Calculate total lyric time
            var totalLyricTime = globalThis.total_words / WordsPerBeat; // Total time in seconds to display all lyrics

            // Calculate the delay in milliseconds before starting the lyrics
            var delay = (trackLength - totalLyricTime) * 1000; // Delay in milliseconds
            globalThis.lyric_intro = delay; // Default to milliseconds
            logWithTimestamp('Starting lyrics in' + delay + 's');

            // If `lyric_intro` is in seconds, convert to milliseconds
            if (globalThis.lyric_intro < 300) { // Assuming no song is longer than 5 minutes (300 sec)
                delay = globalThis.lyric_intro * 1000;
            }

            logWithTimestamp('Using Delay (ms):' + delay); // Debugging

            setTimeout(function() {
                logWithTimestamp('Starting lyrics...');
                if (isSentencesValid(globalThis.sentences)) {
                    displaySentences(0);
                } else {
                    logWithTimestamp('No lyrics loaded 🎶. We had no sentances');
                }
            }, delay);

        }).catch(() => {
            logWithTimestamp('Error: Failed to load sentences');
        });

    }


// Accessing the script inside the div and running it with eval()
function evaluateScript() {
    // Get the script content from the div
    const scriptContent = document.getElementById('Lyric-handler').innerText;
    // Execute the script content with eval
    logWithTimestamp('We attached listener: checkAudioReadyAndStartLyrics');
    setTimeout(checkAudioReadyAndStartLyrics, 1000); // Retry in 1 second
    logWithTimestamp('Evaluating the Script Again');
    try {
        // Try evaluating the script content
        eval(scriptContent);
        console.log('Script executed successfully');
    } catch (error) {
        // Catch any error that occurs during the execution
        console.error('Error during eval execution:', error);

    }
    logWithTimestamp('Finished Evaluating the Script.');


}
  function CheckResponse(transport) {

        logWithTimestamp('Aborting Listener and removing event and lyrics ..');
        document.removeEventListener('DOMContentLoaded', onDOMContentLoaded);
        logWithTimestamp('Wait for new spawned version and listener');
        var table = document.getElementById('np_track_text');
        table.innerHTML = transport.responseText;
        logWithTimestamp('Songs table updated.');

        // Reattach the event listener for the new song after AJAX processing
        if (document.addEventListener) {
            document.addEventListener('DOMContentLoaded', onDOMContentLoaded);
        }
        logWithTimestamp('Reattached DCOM Listener');
        evaluateScript();
        setTimeout(checkForSentences, 1000); // Check after 1 second delay
        // Once the song info is updated, start displaying the
        //lyrics We have finished and the new  version of this will be launched  from fetch
    }

    function JavaResponse(trasport) {
        //Do somthing

    }

    function RemoveVariables() {
        // Clear any ongoing intervals
        globalThis.sentences = [];
        globalThis.total_words = 0;
        globalThis.lyricsContainer.innerText = '';
        globalThis.lyric_intro = 0;
        globalThis.wordCount = 0;
        globalThis.displayDuration = 0;
        globalThis.WordsPerBeat = 0;
        globalThis.timePerSentence = 0;
        //if (window.artworkCheckInterval) {
        //       clearInterval(window.artworkCheckInterval);  // Stop the artwork checking interval
        //      logWithTimestamp('Artwork check interval stopped.');
        // }
        // Clear any ongoing timeouts (if necessary)

        //if (window.someTimeoutVariable) {
        //      clearTimeout(window.someTimeoutVariable);  // Stop any active timeouts
        //      logWithTimestamp('Timeout cleared.');
        //}

        // Stop any periodic song checks or other background tasks
        //if (window.songCheckInterval) {
        //     clearInterval(window.songCheckInterval);  // Stop song checking intervals
        //     logWithTimestamp('Song check interval stopped.');
        //}

    }

    // Function to check if a new song has started
    function checkForNewSong() {
        globalThis.songStartTime = Date.now();
        console.log('New Song Started: ', new Date(songStartTime).toLocaleString());

        logWithTimestamp('Killing of events in preparation for AJAX spawn...');
        RemoveVariables();
        logWithTimestamp('We have new song...AJAX');
        // Your AJAX request to check for a new song

        logWithTimestamp('We have new song...Starting AJAX Process');
        new Ajax.Request(globalThis.gCheckURL, {
            method: 'get',
            onSuccess: CheckResponse // Handle the response in CheckResponse function
        });
        //new Ajax.Request(gCheckJavaURL, {
        //     method: 'get',
        //     onSuccess: JavaResponse  // Handle the response in CheckResponse function
        // });
    }

  // Monitor artwork file changes
function checkArtworkFileChange() {
    console.log('Checking artwork file...');
    
    fetch('/images/nowplaying_artwork_2.png', { method: 'HEAD' })
        .then(response => {
            var fileTimestamp = new Date(response.headers.get('Last-Modified')).getTime();
            var lastModTime = localStorage.getItem('lastModTime');

            if (!lastModTime || fileTimestamp !== parseInt(lastModTime)) {
                logWithTimestamp('Artwork has changed!');
                localStorage.setItem('lastModTime', fileTimestamp); // Update timestamp
                
                var main_artwork = document.getElementById('np_track_artwork');
                main_artwork.src = '/images/nowplaying_artwork_2.png?t=' + Date.now(); // Refresh the artwork
                
                checkForNewSong(); // Call checkForNewSong after artwork changes
            }
        })
        .catch(error => console.error('Error checking artwork:', error));

    // Set interval to repeat every 15 seconds
    setTimeout(checkArtworkFileChange, 15000);
}

function  checkAudioReadyAndStartLyrics() {
    // Start function immediately
    checkArtworkFileChange();

    var track = document.getElementById('MediaPlayer1'); // Your audio element
    track.load(); // Reload the audio to ensure it's initialized
    logWithTimestamp('Checking for Audio.');
    if (globalThis.songEndTime === 0) {
      // Calculate the end time (based on song start time + track length)
      globalThis.songEndTime = Date.now();
     }
    // Check if the audio is playing and has enough data to start
    globalThis.songStartTime = Date.now();
    if (track) {
        logWithTimestamp('Audio is ready and playing, starting lyrics...');
        startLyricsAfterDelay(); // Start the lyrics after the initial delay
    } else {
        logWithTimestamp('Audio not playing or not ready, doing nothing.');
    }
    
}
function parseLyrics(lyrics, baseInterval) {
    var words = [];
    var currentTime = 0; // Start time in milliseconds

    const lines = total_words.split(String.fromCharCode(10)); // Now correctly splits lyrics newline
    lines.forEach(line => {
        const lineWords = line.split(/\s+/); // Split words by spaces
        lineWords.forEach(word => {
            words.push({
                word,
                timestamp: currentTime
            });
            currentTime += baseInterval; // Increase time for next word
        });
    });

    return words;
}

function searchWord(words, searchTerm, partialMatch = false) {
    var results = words.filter(entry => {
        if (partialMatch) {
            return entry.word.toLowerCase().includes(searchTerm.toLowerCase());
        }
        return entry.word.toLowerCase() === searchTerm.toLowerCase();
    }).map(entry => ({
        time: entry.timestamp,
        section: entry.section
    }));

    return results.length > 0 ? results : null;
}

function analyzeLyrics(sections) {
    let analysis = {
        sectionOrder: [],
        sectionCounts: {},
        sectionTimings: {},
};


    if (document.removeEventListener) {
        document.removeEventListener('DOMContentLoaded', onDOMContentLoaded);
    }
      // Handle DJ image change based on BPM
    var bpmdiv = document.getElementById('bpm');
    var object_dj = document.getElementById('DJ');

    if (bpmdiv && object_dj) {
        var pImageUrl = 'images/ezgif.com-medium.gif';
        if (bpmdiv.innerHTML < 135) {
            pImageUrl = 'images/ezgif-2-f0284e7093b1.gif';
        } else if (bpmdiv.innerHTML > 180) {
            pImageUrl = 'images/bandicam-2023-03-31-14-59-01-846.gif';
        }
        object_dj.style.backgroundImage = 'url(' + pImageUrl + ')';
    }
    // Restart song and lyrics when song ends
    player.addEventListener('ended', function() {
        logWithTimestamp('Song ended. Checking for new song...');
        checkForNewSong();
    });
    // Track progress and update accordingly
    player.addEventListener('timeupdate', function() {
        if (player.currentTime < 1 && !player.paused) {
            logWithTimestamp('New song detected!');
            //checkForNewSong();
        }
    });
}
loadVariables();
checkAudioReadyAndStartLyrics();
//checkForNewSong() ;
//checkArtworkFileChange();
</script>
 
I did some work on this today and got it working, if not badly again with lyric timing, but at least it survives now without failing quite so much . I put it here for open discussion as normally .


JavaScript:
if (strlen($lyrics) > 1 && count($WordsArray) !== 0) {
    echo "<script id='Lyric-handler' type='text/javascript'>
    if (typeof globalThis === 'undefined') {
        (function() {
            // Polyfill for globalThis
            Object.defineProperty(window, 'globalThis', {
                get: function() {
                    return this;
                }
            });
        })();
    }
(function() {
    if (typeof Ajax === 'undefined') {  // Check if Prototype.js is missing
        let script = document.createElement('script');
        script.src = '/js/prototype.js';
        script.type = 'text/javascript';
        script.onload = function() {
            console.log('✅ Prototype.js loaded!');
        };
        document.head.appendChild(script);
    } else {
        console.log('✅ Prototype.js already loaded!');
    }
})();
globalThis.sentences = null;

globalThis.wordCount = 0;
globalThis.displayDuration = 0;
globalThis.total_words = 0;
globalThis.timePerSentence = 0;
globalThis.songStartTime = 0;
globalThis.songEndTime = 0;
globalThis.songstartingat = 0;
globalThis.lyricsContainer = 0;
globalThis.gCheckURL = '/fetch_sql2.php';
globalThis.lyricsStarted = false;
globalThis.WordsPerBeat = 0;
globalThis.trackLength = 0;
globalThis.songAlreadyPlayed = 0;
globalThis.disableArtCheck = 0;


function logWithTimestamp(message) {
    let now = new Date();
    let formattedTime = now.toLocaleTimeString('en-GB', {
        hourCycle: 'h23'
    });
    let formattedDate = now.toISOString().split('T')[0]; // YYYY-MM-DD
    console.log('[', formattedDate, ' ', formattedTime, '] ', message);
}

// Function to count words in a sentence (for lyrics or other text)
function countWords(sent) {
    if (!sent) return 0; // Handle empty or null sentences
    return sent.trim().split(/\s+/).filter(function(word) {
        return word.length > 0;
    }).length;
}

function loadVariables() {

    //globalThis.disableArtCheck = 0;
    globalThis.sentences = ".json_encode($WordsArray).";
    globalThis.baseIntervalPerWord = ".$interval." * 1000;
    globalThis.lyricsContainer = document.getElementById('lyrics-container'); // new container
    globalThis.trackLength = " . ($totalSeconds) . ";

    globalThis.trackLengthInSeconds = globalThis.trackLength; // Seconds to Convert ms

    globalThis.player = document.getElementById('player');
    globalThis.lyricsInterval;
    globalThis.gCheckURL = '/fetch_sql2.php';
    globalThis.gCheckJavaURL = '/js/fetch_java.js';
    globalThis.lyricsStarted = false; // ✅ This must be declared at the top
    // Calculate Lyric intro
    // Ensure trackLength is in seconds if in milliseconds
    globalThis.trackLengthInSeconds = globalThis.trackLength / 1000; // Convert milliseconds to seconds
    globalThis.total_words = ".$total_words.";
    globalThis.bpm = ".$bpm.";
    globalThis.totalSentences = globalThis.sentences.length;
    globalThis.totalWordsMillionSeconds = globalThis.total_words * 1000;

    globalThis.timePerSentence = globalThis.trackLength / globalThis.totalSentences;


    globalThis.WordsPerBeat = globalThis.total_words / (globalThis.trackLength * bpm / 60);
    globalThis.milliseconds_per_beat = 60000 / (globalThis.bpm / 2); // Convert bpm to milliseconds per beat
    logWithTimestamp('Total Sentances: ' + globalThis.totalSentences + ' Time Per Sentance: ' + globalThis.timePerSentence + ' Sentences:' + globalThis.sentences);
    logWithTimestamp('WordsPerbeat ' + globalThis.WordsPerBeat + 's'); // This will echo the string with the result
    logWithTimestamp('Nowords: ' + globalThis.globalThis.total_words); // This will echo the string with the result
    logWithTimestamp('bpm: ' + ".$bpm."); // This will echo the string with the result
    logWithTimestamp('Raw: Track Length: ' + globalThis.trackLength + ' s');
    logWithTimestamp('Track Length (Million seconds): ' + totalWordsMillionSeconds + ' ms');


    // Ensure that trackLengthInSeconds, total_words, and bpm are valid numbers
    if (isNaN(globalThis.trackLength) || isNaN(globalThis.total_words) || isNaN(globalThis.bpm) || globalThis.bpm === 0) {
        logWithTimestamp('Invalid values detected');
        globalThis.lyric_intro = 0; // Default to 0
    } else {
        // Calculate the total duration of lyrics in seconds
        var wordTimeInSeconds = (globalThis.total_words * 60) / (bpm / 2);

        // If lyrics take up the full song, intro must be 0
        if (Math.abs(globalThis.trackLength - wordTimeInSeconds) < 1) {
            globalThis.lyric_intro = 0;
            globalThis.WordsPerBeat = 0;
            logWithTimestamp('Lyrics start immediately and go to the end. No intro.');
        } else {
            // Otherwise, calculate the intro correctly
            lyric_intro = Math.max(0, globalThis.trackLength - wordTimeInSeconds) - 5000; // 5 s delay for loading
            if (globalThis.lyric_intro < 0) {
                globalThis.lyric_intro = 0; // Ensure intro time is non-negative
            }

        }

    }
    //checkArtworkFileChange(); 

    logWithTimestamp('Intro: ' + lyric_intro); // This will echo the string with the result
 
}
// Function to display sentences one by one         
function displaySentences(index) {

   // if (globalThis.total_words === 0) {
   //     lyricsContainer.innerText = 'Sorry No Lyrics 🎶';
  //      logWithTimestamp('Sorry No Lyrics.');
   //     return;
  //  }
  
    // Calculate song end time (make sure to convert songStartTime to a timestamp)
    // Log the current time to ensure it's correctly set
    console.log('Song Start Time:', new Date(globalThis.songStartTime).toLocaleString()); // This should log a valid date

    // Calculate the song end time by adding the track length (in seconds) to the start time
    globalThis.songEndTime = globalThis.songStartTime + globalThis.trackLength * 1000; // Add track length  to lobalThis.songStartTime in milliseconds
    
    // Check how much time has passed since we started playing music
    
    globalThis.songAlreadyPlayed = globalThis.songstartingat - globalThis.songStartTime;
    var differenceInTime = Date.now() - globalThis.songAlreadyPlayed;
    logWithTimestamp('We missed seconds from start while we was processing (ms): ' + differenceInTime);
  
// Log the start and end times
    console.log(
        'Executing Lyrics: Track Length', globalThis.trackLength,
        ', Start Time:', new Date(globalThis.songStartTime).toLocaleString(),
        ', Ending:', new Date(globalThis.songEndTime).toLocaleString()
    );
  
    if (index < globalThis.sentences.length) {
        var sentence = globalThis.sentences[index].trim();
        wordCount = countWords(sentence);
        var displayDuration = wordCount * baseIntervalPerWord;
        //var displayDuration = wordCount * WordsPerBeat;

        lyricsContainer = document.getElementById('lyrics-container');
        lyricsContainer.innerText = '🎶 ' + sentence + ' 🎶';

        logWithTimestamp('Displaying sentence ' + index + ': \'' + sentence + '\' for ' + displayDuration + 'ms');

        // Wait for the sentence to finish displaying before showing the next one
        setTimeout(function() {

            if (Date.now() <= globalThis.songEndTime) {
                displaySentences(index + 1);
            }
        }, displayDuration); // was timePerSentence but we changed to distribute
    } else {
        if (index > totalSentences - 1) {
            logWithTimestamp('Lyrics Ended.');
            globalThis.disableArtCheck = 0;
            lyricsContainer.innerText = 'End of Lyrics 🎶';
        }
    }

  }
// Function to start the lyric display after an initial delay
    function isSentencesValid(sentences) {
        return sentences !== null;
    }

    // Function to handle new song response (AJAX Response)
    function checkForSentences() {
        var lyrics = document.getElementById('onhoverlyric0'); // Assuming this is the element for lyrics
        if (lyrics && lyrics.innerHTML.trim() !== '') {
            logWithTimestamp('Lyrics populated successfully!');
            // Proceed with further actions after lyrics are populated
            startLyricsAfterDelay();
        } else {
            logWithTimestamp('Lyrics not populated yet, retrying...');
            //Stop call for new image temerorarily
            globalThis.disableArtCheck = 1;
            setTimeout(checkForSentences, 1000); // Retry after another 1 second
        }
    }
  async function waitForSentencesToLoad(maxRetries = 10, retryInterval = 100) {
        return new Promise((resolve, reject) => {
            let retries = 0;

            // Immediately try loading the variables once when the function is called
            loadVariables();
            console.log('Raw Sentences Data:', globalThis.sentences);
            // Check at intervals until sentences have been populated
            var interval = setInterval(() => {
                if (isSentencesValid(globalThis.sentences)) {
                    // Sentences are loaded, clear the interval and resolve the promise
                    clearInterval(interval);
                    resolve();
                } else if (retries >= maxRetries) {
                    // Retry limit exceeded, reject the promise
                    clearInterval(interval);
                    reject('Failed to load sentences after maximum retries');
                }
                retries++;
            }, retryInterval); // Check every 100ms (or your desired interval)
        });
    }
    function startLyricsAfterDelay() {

        waitForSentencesToLoad().then(() => {
            logWithTimestamp('Starting lyrics...');
            if (lyricsStarted) {

                logWithTimestamp('Lyrics already started, skipping...');
            }
            globalThis.lyricsStarted = true;
            var track = document.getElementById('MediaPlayer1'); // Your audio element
            if (track.readyState <= 3 && !track.paused && !track.ended) {
                startLyricsAfterDelay(); // `3` means enough data to play
                logWithTimestamp('Music not started, skipping...');
            }
            // Calculate total lyric time
            var totalLyricTime = globalThis.total_words / WordsPerBeat; // Total time in seconds to display all lyrics

            // Calculate the delay in milliseconds before starting the lyrics
            var delay = (trackLength - totalLyricTime) * 1000; // Delay in milliseconds
            globalThis.lyric_intro = delay; // Default to milliseconds
            logWithTimestamp('Starting lyrics in' + delay + 's');

            // If `lyric_intro` is in seconds, convert to milliseconds
            if (globalThis.lyric_intro < 300) { // Assuming no song is longer than 5 minutes (300 sec)
                delay = globalThis.lyric_intro * 1000;
            }

            logWithTimestamp('Using Delay (ms):' + delay); // Debugging

            setTimeout(function() {
                logWithTimestamp('Starting lyrics...');
                if (isSentencesValid(globalThis.sentences)) {
                    displaySentences(0);
                } else {
                    logWithTimestamp('No lyrics loaded 🎶. We had no sentances');
                }
            }, delay);

        }).catch(() => {
            logWithTimestamp('Error: Failed to load sentences');
        });

    }


// Accessing the script inside the div and running it with eval()
function evaluateScript() {
    // Get the script content from the div
    const scriptContent = document.getElementById('Lyric-handler').innerText;
    // Execute the script content with eval
    logWithTimestamp('We attached listener: checkAudioReadyAndStartLyrics');
    setTimeout(checkAudioReadyAndStartLyrics, 1000); // Retry in 1 second
    logWithTimestamp('Evaluating the Script Again');
    try {
        // Try evaluating the script content
        eval(scriptContent);
        console.log('Script executed successfully');
    } catch (error) {
        // Catch any error that occurs during the execution
        console.error('Error during eval execution:', error);

    }
    logWithTimestamp('Finished Evaluating the Script.');


}



  function CheckResponse(transport) {

        logWithTimestamp('Aborting Listener and removing event and lyrics ..');
        //document.removeEventListener('DOMContentLoaded', onDOMContentLoaded);
        logWithTimestamp('Wait for new spawned version and listener');
        var table = document.getElementById('np_track_text');
        table.innerHTML = transport.responseText;
        logWithTimestamp('Songs table updated.');

        // Reattach the event listener for the new song after AJAX processing
        //if (document.addEventListener) {
        //    document.addEventListener('DOMContentLoaded', onDOMContentLoaded);
        //}
        logWithTimestamp('Re-evalulating Code...');
        evaluateScript();
          logWithTimestamp('Code Eval finished...');
        setTimeout(checkForSentences() , 1000); // Check after 1 second delay
        // Once the song info is updated, start displaying the
        //lyrics We have finished and the new  version of this will be launched  from fetch
    }

    function JavaResponse(trasport) {
        //Do somthing

    }

    function RemoveVariables() {
        // Clear any ongoing intervals
        globalThis.sentences = [];
        globalThis.total_words = 0;
        globalThis.lyricsContainer.innerText = '';
        globalThis.lyric_intro = 0;
        globalThis.wordCount = 0;
        globalThis.displayDuration = 0;
        globalThis.WordsPerBeat = 0;
        globalThis.timePerSentence = 0;
      

    }

    // Function to check if a new song has started
    function checkForNewSong() {
        globalThis.songStartTime = Date.now();
        console.log('New Song Started: ', new Date(songStartTime).toLocaleString());

        logWithTimestamp('Killing of events in preparation for AJAX spawn...');
        RemoveVariables();
        logWithTimestamp('We have new song...AJAX');
        // Your AJAX request to check for a new song

        logWithTimestamp('We have new song...Starting AJAX Process');
        new Ajax.Request(globalThis.gCheckURL, {
            method: 'get',
            onSuccess: CheckResponse // Handle the response in CheckResponse function
        });
        
    }




 // Monitor artwork file changes
function checkArtworkFileChange() {

   console.log('Artwork check status:', globalThis.disableArtCheck);
   if (globalThis.disableArtCheck === 1) {
        logWithTimestamp('Artwork check is disabled.');
        return;
    }
    console.log('Checking artwork file...');
    fetch('/images/nowplaying_artwork_2.png', { method: 'HEAD' })
        .then(response => {
            var fileTimestamp = new Date(response.headers.get('Last-Modified')).getTime();
            var lastModTime = localStorage.getItem('lastModTime');

            if (!lastModTime || fileTimestamp !== parseInt(lastModTime)) {
                logWithTimestamp('Artwork has changed!');
                localStorage.setItem('lastModTime', fileTimestamp); // Update timestamp
                
                var main_artwork = document.getElementById('np_track_artwork');
                main_artwork.src = '/images/nowplaying_artwork_2.png?t=' + Date.now(); // Refresh the artwork
                
                checkForNewSong(); // Call checkForNewSong after artwork changes
            }
        })
        .catch(error => console.error('Error checking artwork:', error));

    // Set interval to repeat every 15 seconds
    setTimeout(checkArtworkFileChange, 15000);
}

function  checkAudioReadyAndStartLyrics() {
    // Start function immediately
    checkArtworkFileChange();

    var track = document.getElementById('MediaPlayer1'); // Your audio element
    track.load(); // Reload the audio to ensure it's initialized
    logWithTimestamp('Checking for Audio.');
    if( globalThis.songStartTime === 0) {
         globalThis.songStartTime = Date.now();
         globalThis.songEndTime = Date.now();
    }
    if (track) {
        logWithTimestamp('Audio is ready and playing, starting lyrics...');
        startLyricsAfterDelay(); // Start the lyrics after the initial delay
    } else {
        logWithTimestamp('Audio not playing or not ready, doing nothing.');
    }
    
}


function parseLyrics(lyrics, baseInterval) {
    var words = [];
    var currentTime = 0; // Start time in milliseconds

    const lines = total_words.split(String.fromCharCode(10)); // Now correctly splits lyrics newline
    lines.forEach(line => {
        const lineWords = line.split(/\s+/); // Split words by spaces
        lineWords.forEach(word => {
            words.push({
                word,
                timestamp: currentTime
            });
            currentTime += baseInterval; // Increase time for next word
        });
    });

    return words;
}

function searchWord(words, searchTerm, partialMatch = false) {
    var results = words.filter(entry => {
        if (partialMatch) {
            return entry.word.toLowerCase().includes(searchTerm.toLowerCase());
        }
        return entry.word.toLowerCase() === searchTerm.toLowerCase();
    }).map(entry => ({
        time: entry.timestamp,
        section: entry.section
    }));

    return results.length > 0 ? results : null;
}

function analyzeLyrics(sections) {
    let analysis = {
        sectionOrder: [],
        sectionCounts: {},
        sectionTimings: {},
};


    if (document.removeEventListener) {
        document.removeEventListener('DOMContentLoaded', onDOMContentLoaded);
    }
  

    // Handle DJ image change based on BPM
    var bpmdiv = document.getElementById('bpm');
    var object_dj = document.getElementById('DJ');

    if (bpmdiv && object_dj) {
        var pImageUrl = 'images/ezgif.com-medium.gif';
        if (bpmdiv.innerHTML < 135) {
            pImageUrl = 'images/ezgif-2-f0284e7093b1.gif';
        } else if (bpmdiv.innerHTML > 180) {
            pImageUrl = 'images/bandicam-2023-03-31-14-59-01-846.gif';
        }
        object_dj.style.backgroundImage = 'url(' + pImageUrl + ')';
    }
    // Restart song and lyrics when song ends
    player.addEventListener('ended', function() {
        logWithTimestamp('Song ended. Checking for new song...');
        checkForNewSong();
    });
    // Track progress and update accordingly
    player.addEventListener('timeupdate', function() {
        if (player.currentTime < 1 && !player.paused) {
            logWithTimestamp('New song detected!');
            //checkForNewSong();
        }
    });
}
loadVariables();

checkAudioReadyAndStartLyrics();
//checkForNewSong() ;
//checkArtworkFileChange();
</script>";
}

HUGS D.
 
Potential Issue:
As it appeared, the Javascript route was not working faster than server.

PHP:
$t = fopen("images/nowplaying_artwork_2.png", "r");
if ($t !== false) {
    $artwork = fread($t, filesize("images/nowplaying_artwork_2.png"));
    fclose($t);
} else {
 
    ReturnError(500, "Failed to read artwork from a file.");
}
  • If the file length is less than the requested length (in this case, 1000 bytes), you could run into an error or unexpected result.
  • If the file is empty or the length requested exceeds the available data, the read operation might return an incomplete result.
The file error
It was reading the file image in Javacript faster, so we had to put a check on the PHP to check when it was reading it; it was fully there after transfer from RB. Sometimes, server-side file operations (like fopen() in PHP or similar) can encounter temporary issues that resolve quickly—like file access problems or minor server glitches.

PHP:
// Wait for the file to exist and have a valid size
 $file_path = "images/nowplaying_artwork_2.png";
 while (!file_exists($file_path) || filesize($file_path) == 0) {
     sleep($wait_interval);
     clearstatcache();
     $elapsed_time += $wait_interval;

     if ($elapsed_time >= $max_wait_time) {
          ReturnError(500, "File did not appear within the expected time.");
 exit;
 }
 // SUCCESS: File exists and is valid
 $artwork = file_get_contents($file_path);
 $base64artwork = base64_encode($artwork);


Although this introduced an error as file_exists could not function, we had to go back to the original. with changes
Why This Works
✅ Avoids file_exists() Issues – fopen() directly checks access instead of relying on file_exists() which can be unreliable if the file is being written to at the same time.
✅ Prevents Infinite Waiting – If the file isn't accessible within $max_wait_time, it exits gracefully.
✅ Ensures File Is Fully Written – fopen() ensures the file isn’t just present but is actually accessible for reading.


PHP:
$max_wait_time = 10; // Maximum wait time in seconds
$wait_interval = 1;  // Check every 1 second
$elapsed_time = 0;

// Wait for the file to exist and have a valid size
$file_path = "images/nowplaying_artwork_2.png";


 // Wait for the file to be accessible
while (($t = fopen($file_path, "r")) === false) {
    sleep($wait_interval);
     clearstatcache();
   $elapsed_time += $wait_interval;

  if ($elapsed_time >= $max_wait_time) {
     ReturnError(500, "File did not appear within the expected time.");
 exit;
}
// SUCCESS: File is accessible, now read the content
$artwork = fread($t, filesize($file_path));
fclose($t);
$base64artwork = base64_encode($artwork);


Hugs D
 
Last edited:
Still doing it occasionally
: Uncaught ValueError: fread(): Argument #2 ($length) must be greater than 0 in /home/vol11_2/ezyro.com/ezyro_33672165/htdocs/Create_SQL(Apache).php:195
Stack trace:
#0 /home/vol11_2/ezyro.com/ezyro_33672165/htdocs/Create_SQL(Apache).php(195): fread(Resource id #11, 0)
#1 /home/vol11_2/ezyro.com/ezyro_33672165/htdocs/fetch_sql2.php(87): include('/home/vol11_2/e...')
#2 {main}
thrown in
: Uncaught ValueError: fread(): Argument #2 ($length) must be greater than 0 in /home/vol11_2/ezyro.com/ezyro_33672165/htdocs/Create_SQL(Apache).php:195
Stack trace:
#0 /home/vol11_2/ezyro.com/ezyro_33672165/htdocs/Create_SQL(Apache).php(195): fread(Resource id #11, 0)
#1 /home/vol11_2/ezyro.com/ezyro_33672165/htdocs/fetch_sql2.php(87): include('/home/vol11_2/e...')
#2 {main}
thrown in 193
 
Back
Top