Multiple HTML5 videos audio captions not working correctly

Viewed 209

I am adding multiple HTML5 videos onto a webpage.

The code I am replicating is from this recommended accessible approach. http://jspro.brothercake.com/audio-descriptions/ The video plays fine, and audio captions work, but when I add a new video to the same page the second video does not play the audio captions at all. Does anyone have suggestions on how I can fix this issue?

<video id="video" preload="auto" controls="controls" 
    width="640" height="360" poster="./media/HorribleHistories.jpg">
    
    <source src="./media/HorribleHistories.mp4" type="video/mp4" />
    <source src="./media/HorribleHistories.webm" type="video/webm" />
    
</video>

<audio id="audio" preload="auto">
    <source src="./media/HorribleHistories.mp3" type="audio/mp3" />
    <source src="./media/HorribleHistories.ogg" type="audio/ogg" />
</audio>


<script type="text/javascript">
(function()
{

    //get references to the video and audio elements
    var video = document.getElementById('video');
    var audio = document.getElementById('audio');


    //if media controllers are supported, 
    //create a controller instance for the video and audio
    if(typeof(window.MediaController) === 'function')
    {
        var controller = new MediaController();
        audio.controller = controller;
        video.controller = controller;
    }   
    
    //else create a null controller reference for comparison
    else
    {
        controller = null;
    }
    
    //reduce the video volume slightly to emphasise the audio
    audio.volume = 1;
    video.volume = 0.8;


    //when the video plays
    video.addEventListener('play', function() 
    {
        //if we have audio but no controller 
        //and the audio is paused, play that too
        if(!controller && audio.paused)
        {
            audio.play();
        }
    }, false);
    
    //when the video pauses
    video.addEventListener('pause', function()
    {
        //if we have audio but no controller 
        //and the audio isn't paused, pause that too
        if(!controller && !audio.paused)
        {
            audio.pause();
        }
    }, false);

    //when the video ends
    video.addEventListener('ended', function()
    {
        //if we have a controller, pause that
        if(controller)
        {
            controller.pause();
        }
        //otherwise pause the video and audio separately
        else
        {
            video.pause();
            audio.pause();
        }
    }, false);
    
    
    //when the video time is updated
    video.addEventListener('timeupdate', function()
    {
        //if we have audio but no controller, 
        //and the audio has sufficiently loaded
        if(!controller && audio.readyState >= 4)
        {
            //if the audio and video times are different,
            //update the audio time to keep it in sync
            if(Math.ceil(audio.currentTime) != Math.ceil(video.currentTime))
            {
                audio.currentTime = video.currentTime;
            }
        }
    }, false);
    


})();
</script>
2 Answers

So your problem is to do with how you are grabbing the elements in the first place.

var video = document.getElementById('video');
var audio = document.getElementById('audio');

What you are doing is grabbing a single item on the page with the ID of "video" (same for "audio").

IDs have to be unique, so what you want to do is use classes instead.

<video class="video" preload="auto" controls="controls" 
    width="640" height="360" poster="./media/HorribleHistories.jpg">

See I changed the ID to a class.

Now any element with the class "video" can be used in our code.

However we do need to modify our code a bit as now we have multiple items to bind to.

please note the below is to give you an idea of how you loop items etc. You would need to rewrite your code to move each of the steps into functions etc. as your original code is not designed to work with multiple items


(function()
{

    //get references to every single video and audio element
    var videos = document.querySelectorAll('.video');
    var audios = document.querySelectorAll('.audio');

    // loop through all videos adding logic etc.
    for(x = 0; x < videos.length; x++){
        // grab a single video from our list to make our code neater
        var video = videos[x];

    if(typeof(window.MediaController) === 'function')
    {
        var controller = new MediaController();
        video.controller = controller;
    } else {
        controller = null;
    }

    video.volume = 0.8;
    //...etc.
    
    

    }

    
})();

Quick Tip:

I would wrap your <video> and <audio> elements that are related in a <div> with a class (e.g. class="video-audio-wrapper").

This way you can change your CSS selector to something like:

var videoContainers = document.querySelectorAll('.video-audio-wrapper');

Then loop through them instead and check if they have a video and / or audio element

for(x = 0; x < videoContainers.length; x++){
   var thisVideoContainer = videoContainers[x];

   //query this container only - we can use `querySelector` as there should only be one video per container and that returns a single item / the first item it finds.
   var video = thisVideoContainer.querySelector('video');
   var audio = thisVideoContainer.querySelector('audio');

   //now we can check if an element exists
   if(video.length == 1){
      //apply video logic
   }
   if(audio.length == 1){
      //apply audio logic
   }

   // alternatively we can check both exist if we have to have both
   if(video.length != 1 || audio.length != 1){
      // we either have one or both missing. 
      
      // apply any logic for when a video / audio element is missing

      //using "return" we can exit the function early, meaning all code after this point is not run.
      return false;
      
   }
 

   ///The beauty of this approach is you could then just use your original code!

}

Doing it this way you could recycle most of your code.

Thank you for your suggestions in changing the ID's into classes and adding the video wrapper <div> to the video container. That all makes sense in grouping each video on 1 page. I updated the the following code, but the audio captions won't play at all. The video plays and pauses fine, and the volume works. I am also not getting any syntax errors in the browser console. Here's what I got for my HTML and JS. I appreciate your help/feedback.

<div class="video-container-wrapper">
   <div class="video-container">
      <video class="video" preload="auto" controls="controls" width="640" height="360" poster="img/red-zone-thumb.png">
        <source src="https://player.vimeo.com/external/395077086.hd.mp4?s=1514637c1ac308a950fafc00ad46c0a113c6e8be&profile_id=175" type="video/mp4">
        <track kind="captions" label="English captions" src="captions/redzone-script.vtt" srclang="en" default="">
      </video>
      <audio class="audio" preload="auto">
        <source src="captions/redzone-message.mp3" type="audio/mp3">
      </audio>
   </div>
</div>

Javascript:

var videoContainers = document.querySelectorAll('.video-container-wrapper');

for (x = 0; x < videoContainers.length; x++) {
    var thisVideoContainer = videoContainers[x];

    //query this container only - we can use `querySelector` as there should only be one video per container and that returns a single item / the first item it finds.
    var video = thisVideoContainer.querySelector('video');
    var audio = thisVideoContainer.querySelector('audio');

    //now we can check if an element exists
    if (video.length == 1) {
        //apply video logic

        //reduce the video volume slightly to emphasise the audio
        video.volume = 0.8;

        //when the video ends
        video.addEventListener('ended', function () {
            video.pause();
        }, false);
    }

    if (audio.length == 1) {
        //apply audio logic
        audio.volume = 1;
        //when the video plays
        video.addEventListener('play', function () {
            if (audio.paused) {
                audio.play();
            }
        }, false);
        // when the video ends
        video.addEventListener('ended', function () {
            audio.pause();
        }, false);
        //when the video time is updated
        video.addEventListener('timeupdate', function () {
            if (audio.readyState >= 4) {
                //if the audio and video times are different,
                //update the audio time to keep it in sync
                if (Math.ceil(audio.currentTime) != Math.ceil(video.currentTime)) {
                    audio.currentTime = video.currentTime;
                }
            }
        }, false);
    }
}
Related