videojs-errors
Version:
A VideoJS plugin for custom error reporting
701 lines (567 loc) • 22.8 kB
JavaScript
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
},{}],2:[function(require,module,exports){
(function (global){
var topLevel = typeof global !== 'undefined' ? global :
typeof window !== 'undefined' ? window : {}
var minDoc = require('min-document');
if (typeof document !== 'undefined') {
module.exports = document;
} else {
var doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4'];
if (!doccy) {
doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4'] = minDoc;
}
module.exports = doccy;
}
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{"min-document":1}],3:[function(require,module,exports){
(function (global){
if (typeof window !== "undefined") {
module.exports = window;
} else if (typeof global !== "undefined") {
module.exports = global;
} else if (typeof self !== "undefined"){
module.exports = self;
} else {
module.exports = {};
}
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{}],4:[function(require,module,exports){
(function (global){
'use strict';
Object.defineProperty(exports, '__esModule', {
value: true
});
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null);
var _videoJs2 = _interopRequireDefault(_videoJs);
var _globalWindow = require('global/window');
var _globalWindow2 = _interopRequireDefault(_globalWindow);
var _globalDocument = require('global/document');
var _globalDocument2 = _interopRequireDefault(_globalDocument);
var FlashObj = _videoJs2['default'].getComponent('Flash');
// Default options for the plugin.
var defaults = {
header: '',
code: '',
message: '',
timeout: 45 * 1000,
errors: {
'1': {
type: 'MEDIA_ERR_ABORTED',
headline: 'The video download was cancelled'
},
'2': {
type: 'MEDIA_ERR_NETWORK',
headline: 'The video connection was lost, please confirm you are ' + 'connected to the internet'
},
'3': {
type: 'MEDIA_ERR_DECODE',
headline: 'The video is bad or in a format that cannot be played on your browser'
},
'4': {
type: 'MEDIA_ERR_SRC_NOT_SUPPORTED',
headline: 'This video is either unavailable or not supported in this browser'
},
'5': {
type: 'MEDIA_ERR_ENCRYPTED',
headline: 'The video you are trying to watch is encrypted and we do not know how ' + 'to decrypt it'
},
'unknown': {
type: 'MEDIA_ERR_UNKNOWN',
headline: 'An unanticipated problem was encountered, check back soon and try again'
},
'-1': {
type: 'PLAYER_ERR_NO_SRC',
headline: 'No video has been loaded'
},
'-2': {
type: 'PLAYER_ERR_TIMEOUT',
headline: 'Could not download the video'
}
}
};
/**
* Monitors a player for signs of life during playback and
* triggers PLAYER_ERR_TIMEOUT if none occur within a reasonable
* timeframe.
*/
var initPlugin = function initPlugin(player, options) {
var monitor = undefined;
var listeners = [];
// clears the previous monitor timeout and sets up a new one
var resetMonitor = function resetMonitor() {
_globalWindow2['default'].clearTimeout(monitor);
monitor = _globalWindow2['default'].setTimeout(function () {
// player already has an error
// or is not playing under normal conditions
if (player.error() || player.paused() || player.ended()) {
return;
}
player.error({
code: -2,
type: 'PLAYER_ERR_TIMEOUT'
});
}, options.timeout);
// clear out any existing player timeout
// playback has recovered
if (player.error() && player.error().code === -2) {
player.error(null);
}
};
// clear any previously registered listeners
var cleanup = function cleanup() {
var listener = undefined;
while (listeners.length) {
listener = listeners.shift();
player.off(listener[0], listener[1]);
}
_globalWindow2['default'].clearTimeout(monitor);
};
// creates and tracks a player listener if the player looks alive
var healthcheck = function healthcheck(type, fn) {
var check = function check() {
// if there's an error do not reset the monitor and
// clear the error unless time is progressing
if (!player.error()) {
// error if using Flash and its API is unavailable
var tech = player.$('.vjs-tech');
if (tech && tech.type === 'application/x-shockwave-flash' && !tech.vjs_getProperty) {
player.error({
code: -2,
type: 'PLAYER_ERR_TIMEOUT'
});
return;
}
// playback isn't expected if the player is paused
if (player.paused()) {
return resetMonitor();
}
// playback isn't expected once the video has ended
if (player.ended()) {
return resetMonitor();
}
}
fn.call(this);
};
player.on(type, check);
listeners.push([type, check]);
};
var onPlayStartMonitor = function onPlayStartMonitor() {
var lastTime = 0;
cleanup();
// if no playback is detected for long enough, trigger a timeout error
resetMonitor();
healthcheck(['timeupdate', 'adtimeupdate'], function () {
var currentTime = player.currentTime();
// playback is operating normally or has recovered
if (currentTime !== lastTime) {
lastTime = currentTime;
resetMonitor();
}
});
healthcheck('progress', resetMonitor);
};
var onPlayNoSource = function onPlayNoSource() {
if (!player.currentSrc()) {
player.error({
code: -1,
type: 'PLAYER_ERR_NO_SRC'
});
}
};
var onErrorHandler = function onErrorHandler() {
var display = undefined;
var details = '';
var error = player.error();
var content = _globalDocument2['default'].createElement('div');
// In the rare case when `error()` does not return an error object,
// defensively escape the handler function.
if (!error) {
return;
}
error = _videoJs2['default'].mergeOptions(error, options.errors[error.code || 0]);
if (error.message) {
details = '<div class="vjs-errors-details">' + player.localize('Technical details') + '\n : <div class="vjs-errors-message">' + player.localize(error.message) + '</div>\n </div>';
}
if (error.code === 4 && !FlashObj.isSupported()) {
var flashMessage = player.localize(' * If you are using an older browser' + ' please try upgrading or installing Flash.');
details += '<span class="vjs-errors-flashmessage">' + flashMessage + '</span>';
}
display = player.getChild('errorDisplay');
// The code snippet below is to make sure we dispose any child closeButtons before
// making the display closeable
if (display.getChild('closeButton')) {
display.removeChild('closeButton');
}
// Make the error display closeable, and we should get a close button
display.closeable(true);
content.className = 'vjs-errors-dialog';
content.id = 'vjs-errors-dialog';
content.innerHTML = '<div class="vjs-errors-content-container">\n <h2 class="vjs-errors-headline">' + this.localize(error.headline) + '</h2>\n <div><b>' + this.localize('Error Code') + '</b>: ' + (error.type || error.code) + '</div>\n ' + details + '\n </div>\n <div class="vjs-errors-ok-button-container">\n <button class="vjs-errors-ok-button">' + this.localize('OK') + '</button>\n </div>';
display.fillWith(content);
// Get the close button inside the error display
display.contentEl().firstChild.appendChild(display.getChild('closeButton').el());
if (player.width() <= 600 || player.height() <= 250) {
display.addClass('vjs-xs');
}
var okButton = display.el().querySelector('.vjs-errors-ok-button');
_videoJs2['default'].on(okButton, 'click', function () {
display.close();
});
};
var onDisposeHandler = function onDisposeHandler() {
cleanup();
player.removeClass('vjs-errors');
player.off('play', onPlayStartMonitor);
player.off('play', onPlayNoSource);
player.off('dispose', onDisposeHandler);
player.off('error', onErrorHandler);
};
var reInitPlugin = function reInitPlugin(newOptions) {
onDisposeHandler();
initPlugin(player, _videoJs2['default'].mergeOptions(defaults, newOptions));
};
player.on('play', onPlayStartMonitor);
player.on('play', onPlayNoSource);
player.on('dispose', onDisposeHandler);
player.on('error', onErrorHandler);
player.ready(function () {
player.addClass('vjs-errors');
});
player.errors = reInitPlugin;
};
/**
* Initialize the plugin. Waits until the player is ready to do anything.
*/
var errors = function errors(options) {
initPlugin(this, _videoJs2['default'].mergeOptions(defaults, options));
};
// Register the plugin with video.js.
_videoJs2['default'].plugin('errors', errors);
exports['default'] = errors;
module.exports = exports['default'];
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{"global/document":2,"global/window":3}],5:[function(require,module,exports){
(function (global){
'use strict';
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
var _globalDocument = require('global/document');
var _globalDocument2 = _interopRequireDefault(_globalDocument);
var _qunit = (typeof window !== "undefined" ? window['QUnit'] : typeof global !== "undefined" ? global['QUnit'] : null);
var _qunit2 = _interopRequireDefault(_qunit);
var _sinon = (typeof window !== "undefined" ? window['sinon'] : typeof global !== "undefined" ? global['sinon'] : null);
var _sinon2 = _interopRequireDefault(_sinon);
var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null);
var _videoJs2 = _interopRequireDefault(_videoJs);
var _srcPlugin = require('../src/plugin');
var _srcPlugin2 = _interopRequireDefault(_srcPlugin);
var Player = _videoJs2['default'].getComponent('Player');
var sources = [{
src: 'movie.mp4',
type: 'video/mp4'
}, {
src: 'movie.webm',
type: 'video/webm'
}];
_qunit2['default'].test('the environment is sane', function (assert) {
assert.strictEqual(typeof Array.isArray, 'function', 'es5 exists');
assert.strictEqual(typeof _sinon2['default'], 'object', 'sinon exists');
assert.strictEqual(typeof _videoJs2['default'], 'function', 'videojs exists');
assert.strictEqual(typeof _srcPlugin2['default'], 'function', 'plugin is a function');
});
_qunit2['default'].module('videojs-errors', {
beforeEach: function beforeEach() {
// Mock the environment's timers because certain things - particularly
// player readiness - are asynchronous in video.js 5.
this.clock = _sinon2['default'].useFakeTimers();
this.fixture = _globalDocument2['default'].getElementById('qunit-fixture');
this.video = _globalDocument2['default'].createElement('video');
this.fixture.appendChild(this.video);
this.player = (0, _videoJs2['default'])(this.video);
this.player.buffered = function () {
return _videoJs2['default'].createTimeRange(0, 0);
};
this.player.paused = function () {
return false;
};
this.player.pause = function () {
return false;
};
// initialize the plugin with the default options
this.player.errors();
// Tick forward so the player is ready.
this.clock.tick(1);
},
afterEach: function afterEach() {
this.player.dispose();
this.clock.restore();
}
});
_qunit2['default'].test('registers itself with video.js', function (assert) {
assert.expect(2);
assert.strictEqual(Player.prototype.errors, _srcPlugin2['default'], 'videojs-errors plugin was registered');
this.player.errors();
// Tick the clock forward enough to trigger the player to be "ready".
this.clock.tick(1);
assert.ok(this.player.hasClass('vjs-errors'), 'the plugin adds a class to the player');
});
_qunit2['default'].test('play() without a src is an error', function (assert) {
var errors = 0;
this.player.on('error', function () {
errors++;
});
this.player.trigger('play');
assert.strictEqual(errors, 1, 'emitted an error');
assert.strictEqual(this.player.error().code, -1, 'error code is -1');
assert.strictEqual(this.player.error().type, 'PLAYER_ERR_NO_SRC', 'error type is no source');
});
_qunit2['default'].test('no progress for 45 seconds is an error', function (assert) {
var errors = 0;
this.player.on('error', function () {
errors++;
});
this.player.src(sources);
this.player.trigger('play');
this.clock.tick(45 * 1000);
assert.strictEqual(errors, 1, 'emitted an error');
assert.strictEqual(this.player.error().code, -2, 'error code is -2');
assert.strictEqual(this.player.error().type, 'PLAYER_ERR_TIMEOUT');
});
_qunit2['default'].test('Flash API is unavailable when using Flash is an error', function (assert) {
this.player.tech_.el_.type = 'application/x-shockwave-flash';
// when Flash dies the object methods go away
/* eslint-disable camelcase */
this.player.tech_.el_.vjs_getProperty = null;
/* eslint-enable camelcase */
this.player.paused = function () {
return true;
};
var errors = 0;
this.player.on('error', function () {
errors++;
});
this.player.src(sources);
this.player.trigger('play');
this.player.trigger('timeupdate');
assert.strictEqual(errors, 1, 'emitted an error');
assert.strictEqual(this.player.error().code, -2, 'error code is -2');
assert.strictEqual(this.player.error().type, 'PLAYER_ERR_TIMEOUT');
});
_qunit2['default'].test('the plugin cleans up after its previous incarnation when called again', function (assert) {
var errors = 0;
this.player.on('error', function () {
return errors++;
});
// Call plugin multiple times
this.player.errors();
this.player.errors();
// Tick the clock forward enough to trigger the player to be "ready".
this.clock.tick(1);
this.player.trigger('play');
assert.strictEqual(errors, 1, 'emitted a single error');
assert.strictEqual(this.player.error().code, -1, 'error code is -1');
assert.strictEqual(this.player.error().type, 'PLAYER_ERR_NO_SRC');
});
_qunit2['default'].test('when dispose is triggered should not throw error ', function (assert) {
this.player.src(sources);
this.player.trigger('play');
this.player.trigger('dispose');
this.clock.tick(45 * 1000);
assert.ok(!this.player.error(), 'should not throw player error when dispose is called.');
});
_qunit2['default'].test('progress clears player timeout errors', function (assert) {
var errors = 0;
this.player.on('error', function () {
errors++;
});
this.player.src(sources);
this.player.trigger('play');
this.clock.tick(45 * 1000);
assert.strictEqual(errors, 1, 'emitted an error');
assert.strictEqual(this.player.error().code, -2, 'error code is -2');
assert.strictEqual(this.player.error().type, 'PLAYER_ERR_TIMEOUT');
this.player.trigger('progress');
assert.strictEqual(this.player.error(), null, 'error removed');
});
// safari 7 on OSX can emit stalls when playback is just fine
_qunit2['default'].test('stalling by itself is not an error', function (assert) {
this.player.src(sources);
this.player.trigger('play');
this.player.trigger('stalled');
assert.ok(!this.player.error(), 'no error fired');
});
_qunit2['default'].test('timing out multiple times only throws a single error', function (assert) {
var errors = 0;
this.player.on('error', function () {
errors++;
});
this.player.src(sources);
this.player.trigger('play');
// trigger a player timeout
this.clock.tick(45 * 1000);
assert.strictEqual(errors, 1, 'one error fired');
// wait long enough for another timeout
this.clock.tick(50 * 1000);
assert.strictEqual(errors, 1, 'only one error fired');
});
_qunit2['default'].test('progress events while playing reset the player timeout', function (assert) {
var errors = 0;
this.player.on('error', function () {
errors++;
});
this.player.src(sources);
this.player.trigger('play');
// stalled for awhile
this.clock.tick(44 * 1000);
// but playback resumes!
this.player.trigger('progress');
this.clock.tick(44 * 1000);
assert.strictEqual(errors, 0, 'no errors emitted');
});
_qunit2['default'].test('no signs of playback triggers a player timeout', function (assert) {
var errors = 0;
this.player.src(sources);
this.player.on('error', function () {
errors++;
});
// swallow any timeupdate events
this.player.on('timeupdate', function (event) {
event.stopImmediatePropagation();
});
this.player.trigger('play');
this.clock.tick(45 * 1000);
assert.strictEqual(errors, 1, 'emitted a single error');
assert.strictEqual(this.player.error().code, -2, 'error code is -2');
assert.strictEqual(this.player.error().type, 'PLAYER_ERR_TIMEOUT', 'type is player timeout');
});
_qunit2['default'].test('time changes while playing reset the player timeout', function (assert) {
var errors = 0;
this.player.src(sources);
this.player.on('error', function () {
errors++;
});
this.player.trigger('play');
this.clock.tick(44 * 1000);
this.player.currentTime = function () {
return 1;
};
this.player.trigger('timeupdate');
this.clock.tick(10 * 1000);
assert.strictEqual(errors, 0, 'no error emitted');
});
_qunit2['default'].test('time changes while playing ads reset the player timeout', function (assert) {
var errors = 0;
this.player.src(sources);
this.player.on('error', function () {
errors++;
});
this.player.trigger('play');
this.clock.tick(44 * 1000);
this.player.currentTime = function () {
return 1;
};
this.player.trigger('adtimeupdate');
this.clock.tick(10 * 1000);
assert.strictEqual(errors, 0, 'no error emitted');
});
_qunit2['default'].test('time changes after a player timeout clears the error', function (assert) {
this.player.src(sources);
this.player.trigger('play');
this.clock.tick(45 * 1000);
this.player.currentTime = function () {
return 1;
};
this.player.trigger('timeupdate');
assert.ok(!this.player.error(), 'cleared the timeout');
});
_qunit2['default'].test('player timeouts do not occur if the player is paused', function (assert) {
var errors = 0;
this.player.src(sources);
this.player.on('error', function () {
errors++;
});
this.player.on('timeupdate', function (event) {
event.stopImmediatePropagation();
});
this.player.trigger('play');
// simulate a misbehaving player that doesn't fire `paused`
this.player.paused = function () {
return true;
};
this.clock.tick(45 * 1000);
assert.strictEqual(errors, 0, 'no error emitted');
});
// video.paused is false at the end of a video on IE11, Win8 RT
_qunit2['default'].test('player timeouts do not occur if the video is ended', function (assert) {
var errors = 0;
this.player.src(sources);
this.player.on('error', function () {
errors++;
});
this.player.on('timeupdate', function (event) {
event.stopImmediatePropagation();
});
this.player.trigger('play');
// simulate a misbehaving player that doesn't fire `ended`
this.player.ended = function () {
return true;
};
this.clock.tick(45 * 1000);
assert.strictEqual(errors, 0, 'no error emitted');
});
_qunit2['default'].test('player timeouts do not overwrite existing errors', function (assert) {
this.player.src(sources);
this.player.trigger('play');
this.player.error({
type: 'custom',
code: -7
});
this.clock.tick(45 * 1000);
assert.strictEqual(-7, this.player.error().code, 'error was not overwritten');
});
_qunit2['default'].test('unrecognized error codes do not cause exceptions', function (assert) {
var errors = 0;
this.player.on('error', function () {
errors++;
});
this.player.error({
code: 'something-custom-that-no-one-could-have-predicted',
type: 'NOT_AN_ERROR_CONSTANT'
});
assert.ok(true, 'does not throw an exception');
assert.strictEqual(errors, 1, 'emitted an error');
// intentionally missing properties
this.player.error({});
assert.ok(true, 'does not throw an exception');
assert.strictEqual(errors, 2, 'emitted an error');
});
_qunit2['default'].test('custom error details should override defaults', function (assert) {
var customError = { headline: 'test headline', message: 'test details' };
// initialize the plugin with custom options
this.player.errors({ errors: { 4: customError } });
// tick forward enough to ready the player
this.clock.tick(1);
// trigger the error in question
this.player.error(4);
// confirm results
assert.strictEqual(_globalDocument2['default'].querySelector('.vjs-errors-headline').textContent, customError.headline, 'headline should match custom override value');
assert.strictEqual(_globalDocument2['default'].querySelector('.vjs-errors-message').textContent, customError.message, 'message should match custom override value');
});
_qunit2['default'].test('Append Flash error details when flash is not supported', function (assert) {
var oldIsSupported = _videoJs2['default'].getComponent('Flash').isSupported;
// Mock up isSupported to be false
_videoJs2['default'].getComponent('Flash').isSupported = function () {
return false;
};
// tick forward enough to ready the player
this.clock.tick(1);
// trigger the error in question
this.player.error(4);
// confirm results
assert.equal(_globalDocument2['default'].querySelector('.vjs-errors-flashmessage').textContent, ' * If you are using an older browser please try upgrading or installing Flash.', 'Flash Error message should be displayed');
// Restoring isSupported to the old value
_videoJs2['default'].getComponent('Flash').isSupported = oldIsSupported;
});
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{"../src/plugin":4,"global/document":2}]},{},[5]);