diff --git a/build/grunt.js b/build/grunt.js index 11679a4e64..0f5d791ee0 100644 --- a/build/grunt.js +++ b/build/grunt.js @@ -149,6 +149,10 @@ module.exports = function(grunt) { }, dist: {}, watch: { + novtt: { + files: ['build/temp/video.js'], + tasks: ['concat:novtt'] + }, minify: { files: ['build/temp/video.js'], tasks: ['uglify'] diff --git a/src/js/tech/tech.js b/src/js/tech/tech.js index 5af128535d..fa3873ffbd 100644 --- a/src/js/tech/tech.js +++ b/src/js/tech/tech.js @@ -327,6 +327,12 @@ class Tech extends Component { if (!window['WebVTT'] && this.el().parentNode != null) { let script = document.createElement('script'); script.src = this.options_['vtt.js'] || '../node_modules/videojs-vtt.js/dist/vtt.js'; + script.onload = () => { + this.trigger('vttjsloaded'); + }; + script.onerror = () => { + this.trigger('vttjserror'); + }; this.el().parentNode.appendChild(script); window['WebVTT'] = true; } diff --git a/src/js/tracks/text-track.js b/src/js/tracks/text-track.js index 3f28a02fbf..c08fe90747 100644 --- a/src/js/tracks/text-track.js +++ b/src/js/tracks/text-track.js @@ -67,14 +67,22 @@ const loadTrack = function(src, track) { track.loaded_ = true; + // Make sure that vttjs has loaded, otherwise, wait till it finished loading // NOTE: this is only used for the alt/video.novtt.js build if (typeof window.WebVTT !== 'function') { - window.setTimeout(function() { - parseCues(responseBody, track); - }, 100); + if (track.tech_) { + let loadHandler = () => parseCues(responseBody, track); + track.tech_.on('vttjsloaded', loadHandler); + track.tech_.on('vttjserror', () => { + log.error(`vttjs failed to load, stopping trying to process ${track.src}`); + track.tech_.off('vttjsloaded', loadHandler); + }); + + } } else { parseCues(responseBody, track); } + })); }; diff --git a/test/unit/tracks/text-track.test.js b/test/unit/tracks/text-track.test.js index 215619468e..1875e64eb7 100644 --- a/test/unit/tracks/text-track.test.js +++ b/test/unit/tracks/text-track.test.js @@ -1,5 +1,8 @@ +import window from 'global/window'; +import EventTarget from '../../../src/js/event-target.js'; import TextTrack from '../../../src/js/tracks/text-track.js'; import TestHelpers from '../test-helpers.js'; +import log from '../../../src/js/utils/log.js'; const defaultTech = { textTracks() {}, @@ -254,3 +257,124 @@ test('fires cuechange when cues become active and inactive', function() { player.dispose(); }); + +test('tracks are parsed if vttjs is loaded', function() { + const clock = sinon.useFakeTimers(); + const oldVTT = window.WebVTT; + let parserCreated = false; + + window.WebVTT = () => {}; + window.WebVTT.StringDecoder = () => {}; + window.WebVTT.Parser = () => { + parserCreated = true; + return { + oncue() {}, + onparsingerror() {}, + onflush() {}, + parse() {}, + flush() {} + }; + }; + + let xhr; + window.xhr.onCreate = (newXhr) => xhr = newXhr; + + let tt = new TextTrack({ + tech: defaultTech, + src: 'http://example.com' + }); + + xhr.respond(200, {}, 'WebVTT\n'); + + ok(parserCreated, 'WebVTT is loaded, so we can just parse'); + + clock.restore(); + window.WebVTT = oldVTT; +}); + +test('tracks are parsed once vttjs is loaded', function() { + const clock = sinon.useFakeTimers(); + const oldVTT = window.WebVTT; + let parserCreated = false; + + window.WebVTT = true; + + let xhr; + window.xhr.onCreate = (newXhr) => xhr = newXhr; + + let testTech = new EventTarget(); + testTech.textTracks = () => {}; + testTech.currentTime = () => {}; + + let tt = new TextTrack({ + tech: testTech, + src: 'http://example.com' + }); + + xhr.respond(200, {}, 'WebVTT\n'); + + ok(!parserCreated, 'WebVTT is not loaded, do not try to parse yet'); + + clock.tick(100); + ok(!parserCreated, 'WebVTT still not loaded, do not try to parse yet'); + + window.WebVTT = () => {}; + window.WebVTT.StringDecoder = () => {}; + window.WebVTT.Parser = () => { + parserCreated = true; + return { + oncue() {}, + onparsingerror() {}, + onflush() {}, + parse() {}, + flush() {} + }; + }; + + testTech.trigger('vttjsloaded'); + ok(parserCreated, 'WebVTT is loaded, so we can parse now'); + + clock.restore(); + window.WebVTT = oldVTT; +}); + +test('stops processing if vttjs loading errored out', function() { + const clock = sinon.useFakeTimers(); + sinon.stub(log, 'error'); + const oldVTT = window.WebVTT; + let parserCreated = false; + + window.WebVTT = true; + + let xhr; + window.xhr.onCreate = (newXhr) => xhr = newXhr; + + let testTech = new EventTarget(); + testTech.textTracks = () => {}; + testTech.currentTime = () => {}; + + sinon.stub(testTech, 'off'); + testTech.off.withArgs('vttjsloaded'); + + let tt = new TextTrack({ + tech: testTech, + src: 'http://example.com' + }); + + xhr.respond(200, {}, 'WebVTT\n'); + + ok(!parserCreated, 'WebVTT is not loaded, do not try to parse yet'); + + testTech.trigger('vttjserror'); + let errorSpyCall = log.error.getCall(0); + let offSpyCall = testTech.off.getCall(0); + + ok(errorSpyCall.calledWithMatch('vttjs failed to load, stopping trying to process'), + 'vttjs failed to load, so, we logged an error'); + ok(!parserCreated, 'WebVTT is not loaded, do not try to parse yet'); + ok(offSpyCall, 'tech.off was called'); + + clock.restore(); + log.error.restore(); + window.WebVTT = oldVTT; +});