Interactive Transcript (started as a question, but I replied as I fixed it)

Hello!

This is related to my last post, but a different rabbit trail.

I am trying to make a media into an interactive transcript that highlights each line as it comes to it, or you can click on the line that you want to hear and the audio/video will play from there. There is a working codepen here, and I have some media that I’ve been playing with on my Sandbox page. The only one that complete works so far is the one that is pretty much a character for character cut and paste from the codepen, but it doesn’t show the timestamp and it’s not as user-friendly for inputing the data. The two main others that I’ve been concentrating on are two versions of the same thing: an audio and a video (I wanted to make sure that either media type would work).

Thus far, I have gotten it where it is not throwing any errors, but only bits and pieces of the code works. I think it’s mainly it’s the EventListeners that don’t work.

This is my code:

document.addEventListener("DOMContentLoaded", function() {
        
    intTranscriptScript = () => {
        // get media element
        const mediaContainer = document.querySelector(".media-render.file");
        const mediaTag = mediaContainer.firstElementChild.tagName;
        let mediaThing;
        
        if (mediaTag === "VIDEO") {
                mediaThing = document.querySelector("video"); 
            } else if (mediaTag === "AUDIO") {
                mediaThing = document.querySelector("audio"); 
            }
        
        function testing(){
            mediaThing.play();
            console.log("Testing " + mediaTag + "!");
        }
        
        // register the time cues
        const cues = document.querySelectorAll("tbody#transcriptText > tr.cue");
        const dTimes = document.querySelectorAll("td.date-time");
    
        // Add click event listener on each cue to jump to respective time
        // Event listener to update current line hightlight as audio plays
        mediaThing.addEventListener("timeupdate", updateTime);
        
        
        // convert the "hh:mm:ss" time notation to total seconds
        function timeToSeconds(time) {
            const [hours, minutes, seconds] = time.split(":").map(Number);
            return hours * 3600 + minutes * 60 + seconds;
        }
        
        for (i=0; i<cues.length && i<dTimes.length; i++) {
               const cue = cues[i];
               const dT = dTimes[i];
               const dTime = dT.innerText;
    
            function updateTime() {
                const currentTime = mediaThing.currentTime;
                //console.log(currentTime);
        
                const cueTime = timeToSeconds(dTime);
                
                const nextCue = cue.nextElementSibling;
                const nextCueTime = nextCue ? timeToSeconds(nextCue.getAttribute("data-time")) : Infinity;
      
                function cueClick() {
                    mediaThing.currentTime = timeToSeconds(dTime);
                    mediaThing.play();
                    console.log("cue" + i + " clicked");
                }
                
                if (!cue.getAttribute("data-time")) {
                    cue.setAttribute("data-time", dTime);
                }
    
                if (currentTime >= cueTime && currentTime < nextCueTime) {
                    cue.classList.add("active");
                } else {
                    cue.classList.remove("active");
                }
            }
        }
    };
    
    /*
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', intTranscriptScript);
    } else {
        intTranscriptScript();
    }
    */

});

I have tried both putting it in the HTML block and uploading it into my asset/js folder. I do have a “click me” button to as a tester, but it has a built-in Event Listener and definition through its HTML block since it’s so small.

Can anyone tell me what I’m doing wrong?

Yay! I got the file uploads to work! (One thing that might’ve helped was that I refered to “data-time” as “date-time” in one place… I fixed it.)

I’m still working on digging into a Youtube embed (either through the Youtube add-media option or through putting the embed code into the HTML add media option). So you will see that I start with specifying the accompanying class to .media-renderer. But here is the code that works–at least for the file uploads. You’ll also notice A LOT of console logs. That was an attempt on my part to see what was working and what was not.
(And yes, you can laugh at my “mediaThing”. I named it that after I thought that maybe naming it “media” was conflicting with something. After I figured out it wasn’t, I kept it since it made me grin a little :smirk:. Same thing for referring to “children” as “kids”.)

document.addEventListener("DOMContentLoaded", function() {
    const mediaContainer = document.querySelector(".media-render");
    let mediaTypes = mediaContainer.classList;
    let mType = "";
    for (let x of mediaTypes.values()) {
        mType += x + "<br>";
        console.log(mType);
    }
    
    let fT = mediaTypes.contains("file");
    let yT = mediaTypes.contains("youtube");
    let hT = mediaTypes.contains("html");
    
    let mediaThing;
    
    const mKids = mediaContainer.children;
    let tagKids = "";
    for (let i = 0; i < mKids.length; i++) {
        tagKids += mKids[i].tagName + "<br>";
        console.log("mediaContainer child " + (i+1) + " " + tagKids);
    }
    
    
    if (mediaContainer) {
        const mediaTag = mediaContainer.firstElementChild ? mediaContainer.firstElementChild.tagName : null;
        
        if (mediaTag === "VIDEO") {
            mediaThing = document.querySelector("video"); 
        } else if (mediaTag === "AUDIO") {
            mediaThing = document.querySelector("audio"); 
        }
        console.log(mediaThing);
        
        if (mediaThing) {
            function testing(){
                mediaThing.play();
                console.log("Testing " + mediaTag + "!");
            }
            
            // register the time cues
            const cues = document.querySelectorAll("#transcriptText .cue");
                
            // convert the "hh:mm:ss" time notation to total seconds
            function timeToSeconds(time) {
                const [hours, minutes, seconds] = time.split(":").map(Number);
                return hours * 3600 + minutes * 60 + seconds;
            }
              
            // Add click event listener on each cue to jump to respective time
            cues.forEach(cue => {
                cue.addEventListener("click", function() {
                    const ctime = cue.getAttribute("data-time");
                    mediaThing.currentTime = timeToSeconds(ctime);
                    mediaThing.play();
                
                    const cueData = cue.querySelector(".cue-data").innerText;
                    if (ctime === cueData) {
                        console.log("time = " + cueData);
                    }
                });
            });
            
            // Event listener to update current line highlight as audio/video plays
            mediaThing.addEventListener("timeupdate", () => {
                const currentTime = mediaThing.currentTime;
                
                cues.forEach(cue => {
                    const cueTime = timeToSeconds(cue.getAttribute("data-time"));
                    const nextCue = cue.nextElementSibling;
                    const nextCueTime = nextCue ? timeToSeconds(nextCue.getAttribute("data-time")) : Infinity;
                  
                    if (currentTime >= cueTime && currentTime < nextCueTime) {
                        cue.classList.add("active");
                    } else {
                        cue.classList.remove("active");
                    }
                });
            });
        } else {
            console.error("No media element found.");
        }
    } else {
        console.error(".media-render.file element not found.");
    }
    
});

If you want to see the result, I have an audio example and a video example, both using the same code from above (I hope you like the song “Walking in Memphis”).

I will update once I get the Youtube and HTML-embedded Youtube cooperating as well.