5

Been trying to change the source video of an HTML5 video (after clicking a button). However I run into errors. Code and errors below:

Relevant HTML:

<video id="video2" playsinline controls muted preload="auto" style="z-index: -1; position: absolute;">
    <source src="exercise_media/vid_exercise_sample_1.mp4" type="video/mp4"  > 
</video>

(regarding the z-index stuff - this video plays then is grabbed by a canvas which re-draws it, as part of the overall application. Probably not relevant but figured I'd mention it.)

Relevant Javascript. Initiates the video, plays it fine. It's a little esoteric because I started from someone else's sample.

loadCompanionVideo();

export async function loadCompanionVideo() {
    setStatusText('Setting up companion video...');
  try {
    video2 = await loadVideo2();
  } catch (e) {
    let info = document.getElementById('info');
    info.textContent = '(video player) this device type is not supported yet, ' +
      'or this browser does not support video capture: ' + e.toString();
    info.style.display = 'block';
    throw e;
  }
}

async function loadVideo2() {
  const video2 = await setupVideo2();
  return video2;
}

async function setupVideo2() {
  
  const video2 = document.getElementById('video2');
  
  video2.width = videoWidth2;   // some external numbers
  video2.height = videoHeight2; // just to customize width and height; works fine
  
  return video2;
  
}

So this is fine, my first video exercise_media/vid_exercise_sample_1.mp4 plays just fine. However, I want to change the video source to exercise_media/vid_exercise_sample_2.mp4, same format, located in same folder, etc. (I made sure this video is fine by hard-coding it into the HTML above, and it also plays just fine.)

Here's what I've been trying for changing it:

function buttonFunctionChangeVideoSrc() {
    document.getElementById("video2").pause();
    //document.getElementById("#video2 > source").src = "exercise_media/vid_exercise_sample_2.mp4";
    document.getElementById("video2").src = "exercise_media/vid_exercise_sample_2.mp4";
    document.getElementById("video2").load();
    document.getElementById("video2").play();
    
}

To no avail, neither ("#video2 > source").src nor ("video2").src work. In the first case, the error is:

Uncaught TypeError: Cannot set property 'src' of null at HTMLButtonElement The video pauses and stays frozen.

In the second case, trying video2.src directly (same as document.getElementById("video2")), the error is

Uncaught (in promise) DOMException: Failed to load because no supported source was found.

The video portion of the screen goes white/blank.

Play/pause functionality works fine, so I know I have a valid reference to my video2 object. But I cannot seem to change the source. And I also know that the other source video works just fine, as I can hard-code it into the HTML and play it without issue. But I cannot seem to dynamically switch between them.

Any help is much appreciated.


Adding more of my code to take a look at. Don't worry about the pose stuff, it's for my application to analyze the videos, and works fine when I just have one video side by side the webcam (that's what my application does).

The problem is that I cannot change the video2 source to a different MP4. In fact the only way I can have it play an MP4 at all is if I explicitly set it in the HTML.

HTML:

<div id="canvases" class="canvas-container">
        <div id='main' style='display:none'>
            <video id="video" playsinline style=" -moz-transform: scaleX(-1);
            -o-transform: scaleX(-1);
            -webkit-transform: scaleX(-1);
            transform: scaleX(-1);
            display: none;
            ">
            </video>
            <canvas id="output" class="camera-canvas"></canvas>
            <canvas id="keypoints" class="camera-canvas"></canvas>
            
            <video id="video2" playsinline controls muted style="z-index: -1; position: absolute;" 
            >
            <source id="source2" src="exercise_media/vid_exercise_sample_1.mp4" type="video/mp4"  > 
<!-- hard-coding the src in source is the ONLY way I can play a video here at all...  --> 

            </video>
            
            <canvas id="output2" class="camera-canvas2"></canvas>
            <canvas id="keypoints2" class="camera-canvas2"></canvas>

        <canvas class="illustration-canvas"></cavnas>
        <canvas class="illustration-canvas2"></cavnas>

    </div>

Javascript:

export async function bindPage() {
  setupCanvas();
  setupCanvas2();
  buttonSetup();
  toggleLoadingUI(true);
  setStatusText('Loading AI models...');
  posenet = await posenet_module.load({
    architecture: defaultPoseNetArchitecture,
    outputStride: defaultStride,
    inputResolution: defaultInputResolution,
    multiplier: defaultMultiplier,
    quantBytes: defaultQuantBytes
  });
  setStatusText('Loading FaceMesh model...');
  facemesh = await facemesh_module.load();
  facemesh2 = await facemesh_module.load();
  setStatusText('Loading Avatar file...');
  let t0 = new Date();
  await parseSVG(Object.values(avatarSvgs)[0]);

  setStatusText('Setting up camera...');
  try {
    video = await loadVideo();
  } catch (e) {
    let info = document.getElementById('info');
    info.textContent = '(web cam) this device type is not supported yet, ' +
      'or this browser does not support video capture: ' + e.toString();
    info.style.display = 'block';
    throw e;
  }
  
  try {
    video2 = await loadVideo2();
  } catch (e) {
    console.log("Error loading companion video :: "+e);
  }
  console.log(video2); // shows the full html
  playpauseFunction(); // start video2
  
  toggleLoadingUI(false);
  
  detectPoseInRealTime(video, posenet); //actual application, works fine
  
}

async function loadVideo2() {
  const video2 = await setupVideo2();
  return video2;
}

async function setupVideo2() {
  
  const video2 = document.getElementById('video2');
  
  //video2.setAttribute("src", vid_url_1); //does not work 
  //document.getElementById("source2").src = vid_url_2; // does nothing
  
  videoWidth2orig = video2.videoWidth;
  videoHeight2orig = video2.videoHeight; // gives the actual e.g. 640 x 360
  //.. I do some stuff below to set video2 width/height, works fine
  
  return video2;
  
}

function playpauseFunction() {
    try {
        if (playpause == true) {
            video2.pause();
            playpause = false;
            //alert("Workout paused. Click Play/Pause to resume.");
        } else if (playpause == false) {
            video2.play();
            playpause = true;
        }   
    } catch (e) {
        console.log("playpauseFunction exception :: "+e);
    }
}

Now, like I said, if I hard-code into HTML the src of <source> tag, i.e. <source id="source2" src="exercise_media/vid_exercise_sample_1.mp4" type="video/mp4" > the application runs fine and looks like the following:

enter image description here

(Works fine, the webcam plays in real-time next to the video, and I have my skeletal tracking running on the rightside video as it plays.)

What I want to do is click on e.g. "Exercise Sample 2" and change the rightside video, obviously. Nothing I've tried has worked, such as:

function setupExercise2() {
    
    video2.setAttribute('autoplay', 'true');

    document.getElementById("source2").src = "exercise_media/vid_exercise_sample_2.mp4";
    //video2.setAttribute("src", "exercise_media/vid_exercise_sample_2.mp4"); // have tried both, neither work
    video2.load();

    const playPromise = video2.play()  // trigger video load

    console.log(playPromise); 

    if ( playPromise !== undefined ) {
       playPromise.then(() => {
          // video should start playing as soon as it buffers enough
       }).catch(error => {
          // playback failed, log error
          console.log(error);
       })
    }

}

Specifically for the first line (uncommented), the console says:

Promise {<pending>}__proto__: Promise[[PromiseState]]: "pending"[[PromiseResult]]: undefined

The right side video turns white and nothing plays. If I try instead with video2.setAttribute line uncommented, then the console logs:

Promise {<pending>}
__proto__: Promise
[[PromiseState]]: "rejected"
[[PromiseResult]]: DOMException: Failed to load because no supported source was found.
code: 9
message: "Failed to load because no supported source was found."
name: "NotSupportedError"
__proto__: DOMException

To be clear, I can hard-code in any of the vid_exercise_sample_{2,3,..}.mp4 into the html and they play and run just fine, so I don't believe that's the issue.

Hopefully I've provided a pretty full picture now!

MaxiGui
  • 4,537
  • 4
  • 9
  • 31
JDS
  • 14,991
  • 41
  • 142
  • 202
  • 2
    First is a typo: change `document.getElementById("#video2 > source")` to `document.querySelector("#video2 > source")`. Though this should lead to the second error anyway, and this error means that the media this URL points to is not supported by the browser. Double check what is returned in the Network panel of your dev-tools. – Kaiido Mar 11 '21 at 01:30
  • 1
    What browser and OS are you testing on? What happens if you try a basic setup (_ie:_ one video tag and one JS function to change video source)? By leaving out `` and `z-index` it might help to eliminate certain issues, one step at a time. PS: In an Answer box, I'll throw in some testable code for a basic setup in a few mins. From there you can report on issues found.. – VC.One Mar 11 '21 at 14:37
  • 1
    @VC.One answer boxes are not made for passing testable code to OPs hoping they'll clarify their issue from there. If you want to do so, many online services are available, choose one and post a comment linking to it instead. – Kaiido Mar 11 '21 at 14:44
  • 1
    @Kaiido I hear you but I do intend to answer the question (if solution still needed). I am not able to reproduce the asker's problem using my own basic code, so I need them to test same code too and tell me what issue they see on their screen. Then I can use edits to refine. – VC.One Mar 11 '21 at 15:00
  • 1
    Sure then use an online service like jsfiddle to setup your test case and post a comment asking OP for clarifications. Answers are for answering questions, comments are for requesting clarifications. – Kaiido Mar 11 '21 at 15:01
  • 1
    @JDS see if my answer helps you. **(1)** I tried to eliminate possibilities like maybe a silent security block happens if some JS code tries changing the `.src` of an off-screen video element. No issue there. **(2)** Another idea is to just try using full HTTP path (not just as a `folder/filename.mp4` setup). Could be that simple thing. **(3)** Try my answer code which works for my tests and should work for you too... – VC.One Mar 12 '21 at 04:13
  • 1
    Thanks, I will test tomorrow and get back to you:) – JDS Mar 12 '21 at 05:04
  • @VC.One OK I've put in a big edit to show everything going on, so hopefully the picture is clearer now – JDS Mar 12 '21 at 19:38
  • 1
    ... your code is fine... the problem is with your video. Once again, what does the Network panel of your dev tools say about that file? What happens if you try to open it directly in a new tab? – Kaiido Mar 13 '21 at 00:28
  • 1
    @JDS I've posted my answer from the HTML spec website https://stackoverflow.com/a/66663835/15350139 – a.mola Mar 17 '21 at 10:06

4 Answers4

3

The following example code shows a video .src being updated dynamically.
There are two buttons used to switch between the different sources.

It has been tested on Windows PC using Chrome, Edge and Firefox browsers.

<!DOCTYPE html>
<html><head> <meta content="text/html;charset=utf-8" http-equiv="Content-Type"> </head>

<body>

<video id="video2" width="600" height="480" controls style="z-index: 1; overflow:hidden; position: absolute; top: 10px; left: 10px">
<source type="video/mp4">
</video>

<canvas id="myCanvas" width="600" height="480" style="z-index: 1; overflow:hidden; position: absolute; top: 10px; left: 640px">
</canvas>

<div style="z-index: 1; overflow:hidden; position: absolute; top: 510px; left: 10px">
<button onclick="ChangeVideoSrc( 1 )">Play Video 1</button>
<button onclick="ChangeVideoSrc( 2 )">Play Video 2</button>
</div>

<script>

var canvas = document.getElementById('myCanvas');
var ctx = canvas.getContext('2d');
var timer_DrawCanvas; //# updated later in code

//# Example video links that work on my testing...
vid_url_1 = "https://seed191.bitchute.com/pupNZR0eMW9M/4CU38o2lkgyj.mp4";
vid_url_2 = "https://seed171.bitchute.com/VBdNMrOHylJh/2BMyQPl6BSpZ.mp4";

//# Try your own links if the above works...
//vid_url_1 = "exercise_media/vid_exercise_sample_1.mp4"
//vid_url_2 = "exercise_media/vid_exercise_sample_2.mp4"

//# Set initial starting vid SRC (used on page load)
const myVid = document.getElementById("video2");
myVid.setAttribute("src", vid_url_1);

//# Setup event listeners
myVid.addEventListener("canplay", handle_Media_Events);
myVid.addEventListener("loadeddata", handle_Media_Events);
myVid.addEventListener("play", handle_Media_Events);
myVid.addEventListener("pause", handle_Media_Events);
myVid.addEventListener("ended", handle_Media_Events);

function drawVideo() 
{
    ctx.drawImage(myVid, 0, 0, 600, 480);
}

function ChangeVideoSrc( vidNum ) 
{
    myVid.src = window[ "vid_url_" + vidNum ];
    myVid.load();
}

function handle_Media_Events()
{
    //# if enough data to begin playback
    if ( event.type == "canplay" ) { myVid.play(); }
    
    //# if first frame is available
    if ( event.type == "loadeddata" ) 
    { ctx.drawImage( myVid, 0, 0, 600, 480 ); } //# draws re-sized
    
    if ( event.type == "play" )
    { timer_DrawCanvas = setInterval( drawVideo, 30 ); }
    
    if ( (event.type == "pause") || (event.type == "ended") )
    { clearInterval( timer_DrawCanvas ); }
    
}

</script>

</body>
</html>

Hopefully this will give you a reference point towards achieving the quest of "dynamically change source of the HTML5 Video with Javascript". The quest continues...

Ask anything when you've had chance to test the code.

VC.One
  • 12,230
  • 4
  • 21
  • 51
  • 1
    OK, so I notice the following: `video2.setAttribute("src", vid_url_1);` does not work. The HTML does change (checked with console.log), but the video will not play, i.e. when I attempt `video2.play()` nothing happens. Same result for `document.getElementById("source2").src = vid_url_2;`. Something seems fundamentally wrong here, I will update my OP with more code. – JDS Mar 12 '21 at 19:17
  • 1
    _"Something seems fundamentally wrong here"_ is correct. Can you send the two video links to my email. `valerio_charles@yahoo.com` ? I want to check (amongst other things) if the GET request by browser's ` – VC.One Mar 13 '21 at 10:01
2

Just tried out a quick snippet and it works without a bother.. So weird..

what I used

const v = document.getElementById("vid");
const s = document.getElementById("src");

function changeVideo() { 
   s.src = 'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4';
   v.load();
   v.play();
   console.log(s,v);
}

document.getElementById("change").onclick=changeVideo;
<video id="vid" controls autoplay style="width:75%">
   <source id="src" src="http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4" type="video/mp4">
</video><br/><br/>
<button id="change">Change the video</button>

must be missing something. I'd be able to say for sure if I saw a live example, but that's completely up to you.

Otherwise found this on W3

'Warning: Don't make your function asynchronous with the async keyword. You'll lose the "user gesture token" required to allow your video to play later.'

Lakshya Raj
  • 814
  • 2
  • 19
zergski
  • 488
  • 8
  • 1
    Regarding this answer - unfortunately the codebase I'm working with is using `async` in quite a few places, and I'm not particularly looking to rewrite huge portions for what should be a simple feature. Surely there could be a workaround here. – JDS Mar 15 '21 at 06:18
  • 1
    It's not about changing async for you whole application but only for your loadCompanion video function – zergski Mar 16 '21 at 17:43
  • 1
    So how exactly should I change it? Perhaps you could refer to the code I edited/added in to the original question. – JDS Mar 16 '21 at 19:58
  • 1
    Got your own code to work. I'll post another answer with an explanation. – zergski Mar 17 '21 at 03:46
  • 1
    let me know, looking forward – JDS Mar 17 '21 at 17:45
  • @JDS look up :) hope the jsfiddle helps – zergski Mar 17 '21 at 21:08
1

You're trying to change the source of the <video> element, not the <source> element.. For a quick solution just add an 'id' to that element and change the 'src' property accordingly.

<source id="video2source" src="yoursource.vid">

JS

document.getElementById('video2source').src = 'newsource.vid';

Keep in mind, a good practice is to store DOM references in variables. Makes it easier for the browser if you don't perform JS DOM reads all the time.

const vid2src = document.getElementById('video2source'); // define variable in the global scope

vid2src.src = 'newsource.vid'; // use the reference everywhere else

Hope this helps! =)

Edit..

Question with a similar problem: changing source on html5 video tag

Edit numero dos...

Because the play() method is asynchronous, lets try an async solution. =)

const video = document.getElementById("video2");
video.setAttribute('autoplay', 'true');  // just in case    

/// ... your previous code ... switch source, and so on and so on

const playPromise = video.play()  // trigger video load

console.log(playPromise); // just check what the console tells you

if ( playPromise !== undefined ) {
   playPromise.then(() => {
      // video should start playing as soon as it buffers enough
   }).catch(error => {
      // playback failed, log error
      console.log(error);
   })
}
zergski
  • 488
  • 8
  • 1
    I believe I wrote that I tried that exactly, via `document.getElementById("#video2 > source").src = "exercise_media/vid_exercise_sample_2.mp4";`. Is this not the same thing? I tried changing the `.src` of the `source`. – JDS Mar 09 '21 at 00:58
  • 1
    no, selectors do not work the same way in JS as they do in CSS. Here you're looking for an element with an id of "#video2 > source". The correct way would be `document.getElementById('video2').getElementsByTagName('source')[0].src = 'foo'`. Note the `[0]` is selecting the first element in an HTML collection, which is what is given by using any other selector except for `getElementById()` or `querySelector()` which get the first element with the specified selector.. – zergski Mar 09 '21 at 01:10
  • Alright I tested, it's slightly different in that I don't see an error message in console. But the screen still turns white with no video playing. After adding an id to my I just ran `document.getElementById("source2").src = "exercise_media/vid_exercise_sample_2.mp4";` followed by `document.getElementById("video2").load();`. Nothing happens just blank screen. Any more ideas? – JDS Mar 09 '21 at 18:01
  • 1
    No errors is good! A step in the right direction. =P I think that issue is simply because you don't have an `autoplay` property in your player. Try either adding that or run `document.getElementById("video2").play()` after switching sources. Also try debugging by logging the element to console see if the source changes. – zergski Mar 09 '21 at 20:54
  • 1
    No unfortunately. I added the `play()` call and nothing happened. I did a string dump of the whole element using `for (var key in d) { str += key + ': ' + d[key] + '\n' }` to check source, and it does say that `currentSrc: http://localhost:1234/exercise_media/vid_exercise_sample_2.mp4`. But the video is just not playing! If I click the button again to test I see `Uncaught (in promise) DOMException: The play() request was interrupted by a call to pause().` This is pretty important to me so I'm going to start a bounty asap. Thanks for your help so far. – JDS Mar 10 '21 at 07:52
  • 1
    For clarity, the main code I'm currently testing: `document.getElementById("source2").src = "exercise_media/vid_exercise_sample_2.mp4"; document.getElementById("video2").load(); document.getElementById("video2").play();` – JDS Mar 10 '21 at 07:54
  • 1
    If `play()` is interrupted with that error it means that the video is not loading, in most cases, fast enough. That's why a `pause()` method is called and it doesn't resume automatically. I'll update my answer with a possible solution to make it a bit easier. – zergski Mar 10 '21 at 08:13
  • 1
    @JDS I've updated my answer – zergski Mar 10 '21 at 08:28
  • 1
    Ok, the promise shows me in console: `Promise {} __proto__: Promise [[PromiseState]]: "pending" [[PromiseResult]]: undefined` Otherwise same result of blank screen and not playing =/ – JDS Mar 10 '21 at 20:23
  • 1
    Another error I see when I click between buttons to try and load and reload different the two different videos: `Uncaught (in promise) DOMException: The play() request was interrupted by a new load request.` – JDS Mar 10 '21 at 20:30
0

When async is great and all, using it everywhere is not the best idea.. And some methods(like load() and play() are already asynchronous).

In this fiddle:

https://jsfiddle.net/azdpu3Ly/13/

I used a timer to trigger the change of video source through your setupVideo2 function. But as soon as I add await to the function call, it stops working.

So make sure to NOT call your functions related to the video with an await or follow it with .then(). Synchronous code will work just fine and you won't gain anything with the former..

zergski
  • 488
  • 8