Question

I am trying to implement an HTML5 video player for viewing local videos using a local HTML file, and I would like to have VTT captions and subtitles capability.

I am currently using VideoJS for playback; however, my question is not implementation specific. When I attempt to use the VTT files, I get a cross-origin error saying that the files cannot be used. (This depends on the browser though, as IE 10 seems to play just fine while Firefox and Chrome throw the error.)

I would like to have a cross-browser way of doing this, and I would be interested in any solutions/work arounds that anyone has found. One solution I am aware of would be to use Node Web Kit to create web-server hybrid package; however, I am not able to use this solution, as I want to keep my solution platform independent and installation free.

Was it helpful?

Solution

I've hacked up a solution! It requires only two files:

  • The video file.
  • The HTML file, containing:
    • The subtitles in either VTT or SRT format.
    • A script to parse the embedded VTT text and add it to the video.

Get my solution from this GitHub Gist, or from below:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Playing a local video</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<style>
html, body {
    background: black;
    color: white;
}
html, body, video {
    padding: 0;
    margin: 0;
}
video {
    position: absolute;
    top: 0;
    left: 0;
    bottom: 0;
    right: 0;
    width: 100%;
    height: 100%;
}
</style>
</head>
<body>

<video controls>
<source src="Your local video file.mp4" type="video/mp4">
</video>

<!-- Check https://quuz.org/webvtt/ for validation. -->
<script type="text/vtt" id="subtitle" data-label="English" data-lang="en">
WEBVTT

1
00:00:02.500 --> 00:00:05.250
Instead of loading an external .vtt file,

2
00:00:05.250 --> 00:00:09.750
The workaround is to embed it inside a script tag,

3
00:00:10.001 --> 00:00:15.000
And then parse it using JavaScript
and dynamically add it as a new TextTrack.

</script>

<script>
function parse_timestamp(s) {
    var match = s.match(/^(?:([0-9]{2,}):)?([0-5][0-9]):([0-5][0-9][.,][0-9]{0,3})/);
    if (match == null) {
        throw 'Invalid timestamp format: ' + s;
    }
    var hours = parseInt(match[1] || "0", 10);
    var minutes = parseInt(match[2], 10);
    var seconds = parseFloat(match[3].replace(',', '.'));
    return seconds + 60 * minutes + 60 * 60 * hours;
}

// https://w3c.github.io/webvtt/
// https://developer.mozilla.org/en/docs/Web/API/Web_Video_Text_Tracks_Format
// https://en.wikipedia.org/wiki/WebVTT
//
// For better parsers, look at:
// https://github.com/annevk/webvtt
// https://github.com/mozilla/vtt.js
function quick_and_dirty_vtt_or_srt_parser(vtt) {
    var lines = vtt.trim().replace('\r\n', '\n').split(/[\r\n]/).map(function(line) {
        return line.trim();
    });
    var cues = [];
    var start = null;
    var end = null;
    var payload = null;
    for (var i = 0; i < lines.length; i++) {
        if (lines[i].indexOf('-->') >= 0) {
            var splitted = lines[i].split(/[ \t]+-->[ \t]+/);
            if (splitted.length != 2) {
                throw 'Error when splitting "-->": ' + lines[i];
            }

            // Already ignoring anything past the "end" timestamp (i.e. cue settings).
            start = parse_timestamp(splitted[0]);
            end = parse_timestamp(splitted[1]);
        } else if (lines[i] == '') {
            if (start && end) {
                var cue = new VTTCue(start, end, payload);
                cues.push(cue);
                start = null;
                end = null;
                payload = null;
            }
        } else if(start && end) {
            if (payload == null) {
                payload = lines[i];
            } else {
                payload += '\n' + lines[i];
            }
        }
    }

    return cues;
}

function init() {
    // http://www.html5rocks.com/en/tutorials/track/basics/
    // https://www.iandevlin.com/blog/2015/02/javascript/dynamically-adding-text-tracks-to-html5-video
    var video = document.querySelector('video');
    var subtitle = document.getElementById('subtitle');
    var track = video.addTextTrack('subtitles', subtitle.dataset.label, subtitle.dataset.lang);
    track.mode = "showing";
    quick_and_dirty_vtt_or_srt_parser(subtitle.innerHTML).map(function(cue) {
        track.addCue(cue);
    });
}
init();
</script>

</body>
</html>

Alternative approaches:

OTHER TIPS

Long story short, you're pretty much SOL unless you either a) put the VTT files on a public server with cross-origin headers, or b) add a simple web server to your project to serve the files.

If you want to make this work offline and not monkey with Chrome flags, option B is probably your best bet. It would require an installation of some kind, but there are countless ways to set up a simple webserver across platforms.

This is an example of multi-language subtitles using HTML5:

<video controls="controls" id="video1" poster="images/01 - Tactical Empathy.webp" width="960">
  <source src="01 - Tactical Empathy.mp4" type="video/mp4" />
  <track kind="captions" srclang="en" label="English" default src="subtitles/01 - Tactical Empathy.en.vtt" />
  <track kind="captions" srclang="pt" label="Portugues" src="subtitles/01 - Tactical Empathy.pt.vtt" />
</video>

However, as you have already noticed, subtitles won't display when this HTML file is opened locally. That's because of the local link to the VTT/SRT file. If your goal is off-line usage without the need of hosting the subtitle files in a server, then the best solution is to convert those links to embedded base64 content. For such, I wrote a small script in Node.js, which does the job for all local subtitles linked in an HTML file. The script is here:

https://gist.github.com/Murilo-Perrone/5ffd992159cb67bb5474af0bb0329772

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top