161

I'm trying to build a video player that works everywhere. so far I'd be going with:

<video>
    <source src="video.mp4"></source>
    <source src="video.ogv"></source>
    <object data="flowplayer.swf" type="application/x-shockwave-flash">
        <param name="movie" value="flowplayer.swf" />
        <param name="flashvars" value='config={"clip":"video.mp4"}' />
    </object>
</video>

(as seen on several sites, for example video for everybody) so far, so good.

But now I also want some kind of playlist/menu along with the video player, from which I can select other videos. Those should be opened within my player right away. So I will have to "dynamically change the source of the video" (as seen on dev.opera.com/articles/everything-you-need-to-know-html5-video-audio/ - section "Let's look at another movie") with Javascript. Let's forget about the Flash player (and thus IE) part for the time being, I will try to deal with that later.

So my JS to change the <source> tags should be something like:

<script>
function loadAnotherVideo() {
    var video = document.getElementsByTagName('video')[0];
    var sources = video.getElementsByTagName('source');
    sources[0].src = 'video2.mp4';
    sources[1].src = 'video2.ogv';
    video.load();
}
</script>

The problem is, this doesn't work in all browsers. Namely, in Firefox there is a nice page where you can observe the problem I'm having: http://www.w3.org/2010/05/video/mediaevents.html

As soon as I trigger the load() method (in Firefox, mind you), the video player dies.

Now I have found out that when I don't use multiple <source> tags, but instead just one src attribute within the <video> tag, the whole thing does work in Firefox.

So my plan is to just use that src attribute and determine the appropriate file using the canPlayType() function.

Am I doing it wrong somehow or complicating things?

Null
  • 1,940
  • 9
  • 24
  • 29
sashn
  • 2,389
  • 3
  • 18
  • 15
  • That sounds just fine to me. How is it "complicating" to simplify the markup? – Matt Ball Mar 08 '11 at 16:17
  • the problem is i see myself running into a lot of javascript and distinction of cases. if maybe i missed something, like there was a way to make it work in firefox WITH multiple `` tags. then i suppose that would be easier – sashn Mar 08 '11 at 16:57
  • did you ever figure out about the flash part on ie8? – Neeraj Feb 26 '15 at 23:33
  • @Neeraj the final solution involved the plugin [video.js](http://www.videojs.com/) which uses a flash player as a fallback for IE8 and the likes. there might be superior plugins today though. using the plugin also did not help with the firefox issue regarding the load() method, which was the initial motivation for this post. today, that issue has long been fixed, it seems. – sashn Mar 02 '15 at 15:02

16 Answers16

193

I hated all these answers because they were too short or relied on other frameworks.

Here is "one" vanilla JS way of doing this, working in Chrome, please test in other browsers:

http://jsfiddle.net/mattdlockyer/5eCEu/2/

HTML:

<video id="video" width="320" height="240"></video>

JS:

var video = document.getElementById('video');
var source = document.createElement('source');

source.setAttribute('src', 'http://www.tools4movies.com/trailers/1012/Kill%20Bill%20Vol.3.mp4');

video.appendChild(source);
video.play();

setTimeout(function() {  
    video.pause();

    source.setAttribute('src', 'http://www.tools4movies.com/trailers/1012/Despicable%20Me%202.mp4'); 

    video.load();
    video.play();
}, 3000);
Hakan Fıstık
  • 11,376
  • 8
  • 74
  • 105
mattdlockyer
  • 6,057
  • 4
  • 34
  • 43
  • 11
    This was the cleanest and most efficient for me. – Phil McCarty Apr 08 '14 at 17:15
  • I couldn't get Chrome to play ball with this. ONLY if I set the video src attribute to the webm path – Adam Hey May 22 '15 at 23:15
  • 2
    The **play** method is only allowed by a user gesture in some browser policies. – Anson Mar 07 '18 at 13:29
  • I appreciate this so much for another reason! I've been trying to set up a video page that doesn't show ANY video until the visitor selects one. I could set up a video tag with an empty source tag, but it would always generate a failure on a javascript debug console. Now I know I can hold off on adding a 'source' until the user selects one. Till now, I didn't understand I needed to make the use CreateElement() to make a source, and then use appendChild() to add it to the video element! For subsequent selection, I can check first to see if the source element exists so I don't repeat that step. – Randy Dec 18 '18 at 17:07
  • This solution works great if anyone is facing the issue with content-type from payload response even if the content-type specified is correct but still behaves weird. – meDeepakJain Jan 08 '20 at 11:14
  • For me `video.load()` was the key – Izhar Aazmi Feb 21 '20 at 06:19
  • The video links in his fiddle are dead. I forked it with links that should work forever. http://jsfiddle.net/4s1ercxw/ – dmzza May 19 '21 at 02:50
63

Modernizr worked like a charm for me.

What I did is that I didn't use <source>. Somehow this screwed things up, since the video only worked the first time load() was called. Instead I used the source attribute inside the video tag -> <video src="blabla.webm" /> and used Modernizr to determine what format the browser supported.

<script>
var v = new Array();

v[0] = [
        "videos/video1.webmvp8.webm",
        "videos/video1.theora.ogv",
        "videos/video1.mp4video.mp4"
        ];
v[1] = [
        "videos/video2.webmvp8.webm",
        "videos/video2.theora.ogv",
        "videos/video2.mp4video.mp4"
        ];
v[2] = [
        "videos/video3.webmvp8.webm",
        "videos/video3.theora.ogv",
        "videos/video3.mp4video.mp4"
        ];

function changeVid(n){
    var video = document.getElementById('video');

    if(Modernizr.video && Modernizr.video.webm) {
        video.setAttribute("src", v[n][0]);
    } else if(Modernizr.video && Modernizr.video.ogg) {
        video.setAttribute("src", v[n][1]);
    } else if(Modernizr.video && Modernizr.video.h264) {
        video.setAttribute("src", v[n][2]);
    }

    video.load();
}
</script>

Hopefully this will help you :)

If you don't want to use Modernizr , you can always use CanPlayType().

33

Your original plan sounds fine to me. You'll probably find more browser quirks dealing with dynamically managing the <source> elements, as indicated here by the W3 spec note:

Dynamically modifying a source element and its attribute when the element is already inserted in a video or audio element will have no effect. To change what is playing, just use the src attribute on the media element directly, possibly making use of the canPlayType() method to pick from amongst available resources. Generally, manipulating source elements manually after the document has been parsed is an unncessarily[sic] complicated approach.

http://dev.w3.org/html5/spec/Overview.html#the-source-element

grae.kindel
  • 2,604
  • 1
  • 21
  • 18
22

I solved this with this simple method

function changeSource(url) {
   var video = document.getElementById('video');
   video.src = url;
   video.play();
}
Joaquin Iurchuk
  • 5,003
  • 1
  • 40
  • 61
11

Instead of getting the same video player to load new files, why not erase the entire <video> element and recreate it. Most browsers will automatically load it if the src's are correct.

Example (using Prototype):

var vid = new Element('video', { 'autoplay': 'autoplay', 'controls': 'controls' });
var src = new Element('source', { 'src': 'video.ogg', 'type': 'video/ogg' });

vid.update(src);
src.insert({ before: new Element('source', { 'src': 'video.mp4', 'type': 'video/mp4' }) });

$('container_div').update(vid);
Stephen Walcher
  • 2,555
  • 1
  • 21
  • 22
  • 6
    That significantly slows down the overall browser since even after the video tag is removed, the browser still loads the removed videos till the end. – Moe Sweet Nov 08 '11 at 10:54
  • 1
    Some browsers (mobile) don't allow you to programmatically play media without a user interaction, and since you've already had an interaction on the element, replacing it with a new one will lose that ability. – Max Waterman May 11 '20 at 10:48
7

According to the spec

Dynamically modifying a source element and its attribute when the element is already inserted in a video or audio element will have no effect. To change what is playing, just use the src attribute on the media element directly, possibly making use of the canPlayType() method to pick from amongst available resources. Generally, manipulating source elements manually after the document has been parsed is an unncessarily complicated approach.

So what you are trying to do is apparently not supposed to work.

Community
  • 1
  • 1
Yaur
  • 7,016
  • 1
  • 22
  • 35
  • @greg.kindel I noticed after I posted... but the reason I didn't notice in the first place is that your intro text is confusing. Which original plan are we talking about? The original original plan that won't work or the updated original plan that follows the guidance that we both posted? – Yaur Apr 26 '13 at 20:53
  • fair enough, I should have quoted. I meant his plan to "just use that src attribute and determine the appropriate file using the canPlayType() function" which does work. – grae.kindel Nov 07 '13 at 02:37
6

Just put a div and update the content...

<script>
function setvideo(src) {
    document.getElementById('div_video').innerHTML = '<video autoplay controls id="video_ctrl" style="height: 100px; width: 100px;"><source src="'+src+'" type="video/mp4"></video>';
    document.getElementById('video_ctrl').play();
}
</script>
<button onClick="setvideo('video1.mp4');">Video1</button>
<div id="div_video"> </div>
joseagaleanoc
  • 507
  • 1
  • 7
  • 18
4

Yaur: Although what you have copied and pasted is good advice, this does not mean that it is impossible to change the source element of an HTML5 video element elegantly, even in IE9 (or IE8 for that matter).(This solution does NOT involve replacing the entire video element, as it is bad coding practice).

A complete solution to changing/switching videos in HTML5 video tags via javascript can be found here and is tested in all HTML5 browser (Firefox, Chrome, Safari, IE9, etc).

If this helps, or if you're having trouble, please let me know.

Community
  • 1
  • 1
mkralla11
  • 1,279
  • 1
  • 13
  • 19
  • Brilliant! This is the way to do it. You don't even need jQuery; in the linked code, substitute: mp4Vid.setAttribute('src', "/pathTo/newVideo.mp4"); – Velojet Mar 16 '16 at 07:20
4

I have a similar web app and am not facing that sort of problem at all. What i do is something like this:

var sources = new Array();

sources[0] = /path/to/file.mp4
sources[1] = /path/to/another/file.ogg
etc..

then when i want to change the sources i have a function that does something like this:

this.loadTrack = function(track){
var mediaSource = document.getElementsByTagName('source')[0];
mediaSource.src = sources[track];

    var player = document.getElementsByTagName('video')[0];
    player.load();

}

I do this so that the user can make their way through a playlist, but you could check for userAgent and then load the appropriate file that way. I tried using multiple source tags like everyone on the internet suggested, but i found it much cleaner, and much more reliable to manipulate the src attribute of a single source tag. The code above was written from memory, so i may have glossed over some of hte details, but the general idea is to dynamically change the src attribute of the source tag using javascript, when appropriate.

Sebastian Olsen
  • 7,528
  • 8
  • 35
  • 78
aamiri
  • 2,260
  • 4
  • 31
  • 57
3

I come with this to change video source dynamically. "canplay" event sometime doesn't fire in Firefox so i have added "loadedmetadata". Also i pause previous video if there is one...

var loadVideo = function(movieUrl) {
    console.log('loadVideo()');
    $videoLoading.show();
    var isReady = function (event) {
            console.log('video.isReady(event)', event.type);
            video.removeEventListener('canplay', isReady);
            video.removeEventListener('loadedmetadata', isReady);
            $videoLoading.hide();
            video.currentTime = 0;
            video.play();
        },
        whenPaused = function() {
            console.log('video.whenPaused()');
            video.removeEventListener('pause', whenPaused);
            video.addEventListener('canplay', isReady, false);
            video.addEventListener('loadedmetadata', isReady, false); // Sometimes Firefox don't trigger "canplay" event...
            video.src = movieUrl; // Change actual source
        };

    if (video.src && !video.paused) {
        video.addEventListener('pause', whenPaused, false);
        video.pause();
    }
    else whenPaused();
};
molokoloco
  • 4,126
  • 2
  • 29
  • 26
  • The fix, definitely worked for me, firefox is not handling property neither `canplay` nor `canplaythrough` and `lodedmetadata` needs to be added (and removed later) to the video handlers :-( – Lord of freaks May 05 '16 at 08:54
3

This is my solution:

<video id="playVideo" width="680" height="400" controls="controls">
    <source id="sourceVideo" src="{{video.videoHigh}}" type="video/mp4">
</video>
    <br />
<button class="btn btn-warning" id="{{video.videoHigh}}" onclick="changeSource(this)">HD</button>
<button class="btn btn-warning" id="{{video.videoLow}}" onclick="changeSource(this)">Regular</button>

<script type="text/javascript">
    var getVideo = document.getElementById("playVideo");
    var getSource = document.getElementById("sourceVideo");
    function changeSource(vid) {
        var geturl = vid.id;
        getSource .setAttribute("src", geturl);
        getVideo .load()
        getVideo .play();
        getVideo .volume = 0.5;
    }
</script>
IT Vlogs
  • 2,207
  • 25
  • 25
2

I have been researching this for quite a while and I am trying to do the same thing, so hopefully this will help someone else. I have been using crossbrowsertesting.com and literally testing this in almost every browser known to man. The solution I've got currently works in Opera, Chrome, Firefox 3.5+, IE8+, iPhone 3GS, iPhone 4, iPhone 4s, iPhone 5, iPhone 5s, iPad 1+, Android 2.3+, Windows Phone 8.

Dynamically Changing Sources

Dynamically changing the video is very difficult, and if you want a Flash fallback you will have to remove the video from the DOM/page and re-add it so that Flash will update because Flash will not recognize dynamic updates to Flash vars. If you're going to use JavaScript to change it dynamically, I would completely remove all <source> elements and just use canPlayType to set the src in JavaScript and break or return after the first supported video type and don't forget to dynamically update the flash var mp4. Also, some browsers won't register that you changed the source unless you call video.load(). I believe the issue with .load() you were experiencing can be fixed by first calling video.pause(). Removing and adding video elements can slow down the browser because it continues buffering the removed video, but there's a workaround.

Cross-browser Support

As far as the actual cross-browser portion, I arrived at Video For Everybody as well. I already tried the MediaelementJS Wordpress plugin, which turned out to cause a lot more issues than it resolved. I suspect the issues were due to the Wordpress plug-in and not the actually library. I'm trying to find something that works without JavaScript, if possible. So far, what I've come up with is this plain HTML:

<video width="300" height="150" controls="controls" poster="http://sandbox.thewikies.com/vfe-generator/images/big-buck-bunny_poster.jpg" class="responsive">
<source src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.ogv" type="video/ogg" />
<source src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4" type="video/mp4" />
<source src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.webm" type="video/webm" />
<source src="http://alex-watson.net/wp-content/uploads/2014/07/big_buck_bunny.iphone.mp4" type="video/mp4" />
<source src="http://alex-watson.net/wp-content/uploads/2014/07/big_buck_bunny.iphone3g.mp4" type="video/mp4" />
<object type="application/x-shockwave-flash" data="http://releases.flowplayer.org/swf/flowplayer-3.2.1.swf" width="561" height="297">
    <param name="movie" value="http://releases.flowplayer.org/swf/flowplayer-3.2.1.swf" />
    <param name="allowFullScreen" value="true" />
    <param name="wmode" value="transparent" />
    <param name="flashVars" value="config={'playlist':['http://sandbox.thewikies.com/vfe-generator/images/big-buck-bunny_poster.jpg',{'url':'http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4','autoPlay':false}]}" />
    <img alt="No Video" src="http://sandbox.thewikies.com/vfe-generator/images/big-buck-bunny_poster.jpg" width="561" height="297" title="No video playback capabilities, please download the video below" />
</object>
<strong>Download video:</strong>  <a href="video.mp4">MP4 format</a> | <a href="video.ogv">Ogg format</a> | <a href="video.webm">WebM format</a>
</video>

Important notes:

  • Ended up putting the ogg as the first <source> because Mac OS Firefox quits trying to play the video if it encounters an MP4 as the first <source>.
  • The correct MIME types are important .ogv files should be video/ogg, not video/ogv
  • If you have HD video, the best transcoder I've found for HD quality OGG files is Firefogg
  • The .iphone.mp4 file is for iPhone 4+ which will only play videos that are MPEG-4 with H.264 Baseline 3 Video and AAC audio. The best transcoder I found for that format is Handbrake, using the iPhone & iPod Touch preset will work on iPhone 4+, but to get iPhone 3GS to work you need to use the iPod preset which has much lower resolution which I added as video.iphone3g.mp4.
  • In the future we will be able to use a media attribute on the <source> elements to target mobile devices with media queries, but right now the older Apple and Android devices don't support it well enough.

Edit:

  • I'm still using Video For Everybody but now I've transitioned to using FlowPlayer, to control the Flash fallback, which has an awesome JavaScript API that can be used to control it.
Community
  • 1
  • 1
Alex W
  • 33,401
  • 9
  • 92
  • 97
  • This seems to be the answer to the question "How do I play video cross browser?" and not this question which is "How to I set up a playlist for my video?". – Quentin Jul 16 '14 at 15:40
  • @Quentin It was, to begin with, because that's how I found this question via Google and OP said he's trying to build a video player that works everywhere. I updated the answer to address his dynamic question too. – Alex W Jul 16 '14 at 16:47
2

Using the <source /> tags proved difficult for me in Chrome 14.0.835.202 specifically, although it worked fine for me in FireFox. (This could be my lack of knowledge, but I thought an alternate solution might be useful anyway.) So, I ended up just using a <video /> tag and setting the src attribute right on the video tag itself. The canPlayVideo('<mime type>') function was used to determine whether or not the specific browser could play the input video. The following works in FireFox and Chrome.

Incidently, both FireFox and Chrome are playing the "ogg" format, although Chrome recommends "webm". I put the check for browser support of "ogg" first only because other posts have mentioned that FireFox prefers the ogg source first (i.e. <source src="..." type="video/ogg"/> ). But, I haven't tested (and highly doubt) whether or not it the order in the code makes any difference at all when setting the "src" on the video tag.

HTML

<body onload="setupVideo();">
    <video id="media" controls="true" preload="auto" src="">
    </video>
</body>

JavaScript

function setupVideo() {
       // You will probably get your video name differently
       var videoName = "http://video-js.zencoder.com/oceans-clip.mp4";

       // Get all of the uri's we support
       var indexOfExtension = videoName.lastIndexOf(".");
       //window.alert("found index of extension " + indexOfExtension);
       var extension = videoName.substr(indexOfExtension, videoName.length - indexOfExtension);
       //window.alert("extension is " + extension);
       var ogguri = encodeURI(videoName.replace(extension, ".ogv"));
       var webmuri = encodeURI(videoName.replace(extension, ".webm"));
       var mp4uri = encodeURI(videoName.replace(extension, ".mp4"));
       //window.alert(" URI is " + webmuri);


       // Get the video element
       var v = document.getElementById("media");
       window.alert(" media is " + v);

       // Test for support
       if (v.canPlayType("video/ogg")) {
            v.setAttribute("src", ogguri);
           //window.alert("can play ogg");
       }
       else if (v.canPlayType("video/webm")) {
           v.setAttribute("src", webmuri);
           //window.alert("can play webm");
       }
       else if (v.canPlayType("video/mp4")) {
           v.setAttribute("src", mp4uri);
           //window.alert("can play mp4");
       }
       else {
           window.alert("Can't play anything");
       }

      v.load();
      v.play();
  }
1

Try moving the OGG source to the top. I've noticed Firefox sometimes gets confused and stops the player when the one it wants to play, OGG, isn't first.

Worth a try.

Ian Devlin
  • 17,608
  • 4
  • 53
  • 69
1

Another way you can do in Jquery.

HTML

<video id="videoclip" controls="controls" poster="" title="Video title">
    <source id="mp4video" src="video/bigbunny.mp4" type="video/mp4"  />
</video>

<div class="list-item">
     <ul>
         <li class="item" data-video = "video/bigbunny.mp4"><a href="javascript:void(0)">Big Bunny.</a></li>
     </ul>
</div>

Jquery

$(".list-item").find(".item").on("click", function() {
        let videoData = $(this).data("video");
        let videoSource = $("#videoclip").find("#mp4video");
        videoSource.attr("src", videoData);
        let autoplayVideo = $("#videoclip").get(0);
        autoplayVideo.load();
        autoplayVideo.play();
    });
quan_bui
  • 29
  • 3
  • 8
0

Using JavaScript and jQuery:

<script src="js/jquery.js"></script>
...
<video id="vid" width="1280" height="720" src="v/myvideo01.mp4" controls autoplay></video>
...
function chVid(vid) {
    $("#vid").attr("src",vid);
}
...
<div onclick="chVid('v/myvideo02.mp4')">See my video #2!</div>
pbies
  • 564
  • 8
  • 23