Cannot dynamically change a caption 'track' in Video.js
Asked Answered
M

2

7

I'm coding a basic video marquee and one of the key requirements is that the videos need to be able to advance while keeping the player in full screen.

Using Video.js (4.1.0) I have been able to get everything work correctly except that I cannot get the captions to change when switching to another video.

Either inserting a "track" tag when the player HTML is first created or adding a track to the 'options' object when the player is initialized are the only ways I can get the player to display the "CC" button and show captions. However, I cannot re-initialize the player while in full screen so changing the track that way will not work.

I have tried addTextTrack and addTextTracks and both show that the tracks have been added - using something like console.log(videoObject.textTracks()) - but the player never shows them or the "CC" button.

Here is my code, any help is greatly appreciated:

;(function(window,undefined) {

    // VIDEOS OBJECT
    var videos = [
        {"volume":"70","title":"TEST 1","url":"test1.mp4","type":"mp4"},
        {"volume":"80","title":"TEST 2","url":"test2.mp4","type":"mp4"},
        {"volume":"90","title":"TEST 3","url":"test3.mp4","type":"mp4"}
    ];

    // CONSTANTS
    var VIDEO_BOX_ID = "jbunow_marquee_video_box", NAV_TEXT_ID = "jbunow_marquee_nav_text", NAV_ARROWS_ID = "jbunow_marquee_nav_arrows", VIDEO_OBJ_ID = "jbunow_marquee_video", NAV_PREV_ID = "jbunow_nav_prev", NAV_NEXT_ID = "jbunow_nav_next";

    // GLOBAL VARIABLS
    var videoObject;
    var currentTrack = 0;
    var videoObjectCreated = false;
    var controlBarHideTimeout;

    jQuery(document).ready(function(){
        // CREATE NAV ARROWS AND LISTENERS, THEN START MARQUEE
        var navArrowsHtml = "<div id='" + NAV_PREV_ID + "' title='Play Previous Video'></div>";
        navArrowsHtml += "<div id='" + NAV_NEXT_ID + "' title='Play Next Video'></div>";
        jQuery('#' + NAV_ARROWS_ID).html(navArrowsHtml);
        jQuery('#' + NAV_PREV_ID).on('click',function() { ChangeVideo(GetPrevVideo()); });
        jQuery('#' + NAV_NEXT_ID).on('click',function() { ChangeVideo(GetNextVideo()); });

        ChangeVideo(currentTrack);
    });

    var ChangeVideo = function(newIndex) {
        var videoBox = jQuery('#' + VIDEO_BOX_ID);
        if (!videoObjectCreated) {
            // LOAD PLAYER HTML
            videoBox.html(GetPlayerHtml());

            // INITIALIZE VIDEO-JS
            videojs(VIDEO_OBJ_ID, {}, function(){
                videoObject = this;

                // LISTENERS
                videoObject.on("ended", function() { ChangeVideo(GetNextVideo()); });
                videoObject.on("loadeddata", function () { videoObject.play(); });

                videoObjectCreated = true;
                PlayVideo(newIndex);
            });

        } else { PlayVideo(newIndex); }
    }

    var PlayVideo = function(newIndex) {

        // TRY ADDING MULTIPLE TRACKS
        videoObject.addTextTracks([{ kind: 'captions', label: 'English2', language: 'en', srclang: 'en', src: 'track2.vtt' }]);

        // TRY ADDING HTML
        //jQuery('#' + VIDEO_OBJ_ID + ' video').eq(0).append("<track kind='captions' src='track2.vtt' srclang='en' label='English' default />");

        // TRY ADDING SINGLE TRACK THEN SHOWING USING RETURNED ID
        //var newTrack = videoObject.addTextTrack('captions', 'English2', 'en', { kind: 'captions', label: 'English2', language: 'en', srclang: 'en', src: 'track2.vtt' });
        //videoObject.showTextTrack(newTrack.id_, newTrack.kind_);        

        videoObject.volume(parseFloat(videos[newIndex]["volume"]) / 100); // SET START VOLUME
        videoObject.src({ type: "video/" + videos[newIndex]["type"], src: videos[newIndex]["url"] }); // SET NEW SRC
        videoObject.load();

        videoObject.ready(function () {
            videoObject.play();

            clearTimeout(controlBarHideTimeout);
            controlBarHideTimeout = setTimeout(function() { videoObject.controlBar.fadeOut(); }, 2000);

            jQuery('#' + NAV_TEXT_ID).fadeOut(150, function() {
                currentTrack = newIndex;
                var navHtml = "";
                navHtml += "<h1>Now&nbsp;Playing</h1><h2>" + videos[newIndex]["title"] + "</h2>";
                if (videos.length > 1) { navHtml += "<h1>Up&nbsp;Next</h1><h2>" + videos[GetNextVideo()]["title"] + "</h2>"; }
                jQuery('#' + NAV_TEXT_ID).html(navHtml).fadeIn(250);
            });
        });
    }

    var GetPlayerHtml = function() {
        var playerHtml = "";        
        playerHtml += "<video id='" + VIDEO_OBJ_ID + "' class='video-js vjs-default-skin' controls='controls' preload='auto' width='560' height='315'>";
        playerHtml += "<source src='' type='video/mp4' />";
        //playerHtml += "<track kind='captions' src='track.vtt' srclang='en' label='English' default='default' />";
        playerHtml += "</video>";
        return playerHtml;
    }

    var GetNextVideo = function() {
        if (currentTrack >= videos.length - 1) { return 0; }
        else { return (currentTrack + 1); }
    }

    var GetPrevVideo = function() {
        if (currentTrack <= 0) { return videos.length - 1; }
        else { return (currentTrack - 1); }
    }

})(window);
Milord answered 7/8, 2013 at 21:24 Comment(2)
Hi - did you get anywhere with this? I am having a similar issue. I want to give it a VTT url when I change the video src programticallyDrift
Nope, just posted this yesterday and am hoping that someone knowledgeable will chime in. There are quite a few similar questions in various forums for older versions of Video.js, but so far I haven't found any answers that work.Milord
J
6

The current VideoJS implementation (4.4.2) loads every kind of text tracks (subtitles, captions, chapters) on initialization time of the player itself, so it grabs correctly only those, which are defined between the <video> tags.

EDIT: I meant it does load them when calling addTextTrack, but the player UI will never update after initialization time, and will always show the initialization time text tracks.

One possible workaround is if you destroy the complete videojs player and re-create it on video source change after you have refreshed the content between the <video> tags. So this way you don't update the source via the videojs player, but via dynamically adding the required DOM elements and initializing a new player on them. Probably this solution will cause some UI flashes, and is quite non-optimal for the problem. Here is a link about destroying the videojs player

Second option is to add the dynamic text track handling to the existing code, which is not as hard as it sounds if one knows where to look (I did it for only chapters, but could be similar for other text tracks as well). The code below works with the latest official build 4.4.2. Note that I'm using jQuery for removing the text track elements, so if anyone applies these changes as is, jQuery needs to be loaded before videojs.

Edit the video.dev.js file as follows:

1: Add a clearTextTracks function to the Player

vjs.Player.prototype.clearTextTracks = function() {
    var tracks = this.textTracks_ = this.textTracks_ || [];
    for (var i = 0; i != tracks.length; ++i)
        $(tracks[i].el()).remove();
    tracks.splice(0, tracks.length);
    this.trigger("textTracksChanged");
};

2: Add the new 'textTracksChanged' event trigger to the end of the existing addTextTrack method

vjs.Player.prototype.addTextTrack = function(kind, label, language, options) {
    ...
    this.trigger("textTracksChanged");
}

3: Handle the new event in the TextTrackButton constructor function

vjs.TextTrackButton = vjs.MenuButton.extend({
  /** @constructor */
    init: function(player, options) {
        vjs.MenuButton.call(this, player, options);
        if (this.items.length <= 1) {
            this.hide();
        }
        player.on('textTracksChanged', vjs.bind(this, this.refresh));
    }
});

4: Implement the refresh method on the TextTrackButton

// removes and recreates the texttrack menu
vjs.TextTrackButton.prototype.refresh = function () {
    this.removeChild(this.menu);
    this.menu = this.createMenu();
    this.addChild(this.menu);
    if (this.items && this.items.length <= this.kind_ == "chapters" ? 0 : 1) {
        this.hide();
    } else
        this.show();
};

Sorry, but for now I cannot link to a real working example, I hope the snippets above will be enough as a starting point to anyone intrested in this.

You can use this code when you update the source to a new video. Just call the clearTextTracks method, and add the new text tracks with the addTextTrack method, and the menus now should update themselves.

Jaquesdalcroze answered 13/3, 2014 at 20:42 Comment(4)
Sorry, Zoltán, I just saw this response (didn't get an email for some reason.) I'll definitely try this out to see if it works.Since I didn't get any responses last year I had to go the route of completely disposing the videojs object, which, like you said, is certainly not optimal, but at least it does work.Milord
Thanks for this. One change for those that don't want to use jQuery, switch this: $(tracks[i].el()).remove(); For this: tracks[i].disable();Hellish
I also noticed this wasn't working correctly for me: if (this.items && this.items.length <= this.kind_ == "chapters" ? 0 : 1) so I just changed it to if (this.items && this.items.length <= 1)Hellish
Thanks for the notes, actaully I can't remember why I needed that special condition there. I'm glad you could use this approach.Cruciferous
H
1

Doing the exact same thing (or rather NOT doing the exact same thing)... really need to figure out how to dynamically change / add a caption track.

This works to get it playing via the underlying HTML5, but it does not show the videojs CC button:

document.getElementById("HtmlFiveMediaPlayer_html5_api").innerHTML = '<track label="English Captions" srclang="en" kind="captions" src="http://localhost/media/captiontest/demo_Brian/demo_h264_1.vtt" type="text/vtt" default />';
Hangeron answered 13/2, 2014 at 17:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.