diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4ab7653..77c8a60 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,7 +2,7 @@ CHANGELOG
 =========
 
 ## HEAD (Unreleased)
-_(none)_
+* @gkatsev changed emulated tracks to in novtt to wait for vttjs to load or error before parsing ([view](https://github.com/videojs/video.js/pull/3181))
 
 --------------------
 
diff --git a/build/grunt.js b/build/grunt.js
index 11679a4..0f5d791 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 5af1285..fa3873f 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 3f28a02..c08fe90 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 2156194..1875e64 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;
+});
