videojs-errors
Version:
A VideoJS plugin for custom error reporting
252 lines (219 loc) • 7.3 kB
JavaScript
import videojs from 'video.js';
import window from 'global/window';
import document from 'global/document';
const FlashObj = videojs.getComponent('Flash');
// Default options for the plugin.
const 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.
*/
const initPlugin = function(player, options) {
let monitor;
let listeners = [];
// clears the previous monitor timeout and sets up a new one
const resetMonitor = function() {
window.clearTimeout(monitor);
monitor = window.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
const cleanup = function() {
let listener;
while (listeners.length) {
listener = listeners.shift();
player.off(listener[0], listener[1]);
}
window.clearTimeout(monitor);
};
// creates and tracks a player listener if the player looks alive
const healthcheck = function(type, fn) {
let check = function() {
// 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
let 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]);
};
const onPlayStartMonitor = function() {
let lastTime = 0;
cleanup();
// if no playback is detected for long enough, trigger a timeout error
resetMonitor();
healthcheck(['timeupdate', 'adtimeupdate'], function() {
let currentTime = player.currentTime();
// playback is operating normally or has recovered
if (currentTime !== lastTime) {
lastTime = currentTime;
resetMonitor();
}
});
healthcheck('progress', resetMonitor);
};
const onPlayNoSource = function() {
if (!player.currentSrc()) {
player.error({
code: -1,
type: 'PLAYER_ERR_NO_SRC'
});
}
};
const onErrorHandler = function() {
let display;
let details = '';
let error = player.error();
let content = document.createElement('div');
// In the rare case when `error()` does not return an error object,
// defensively escape the handler function.
if (!error) {
return;
}
error = videojs.mergeOptions(error, options.errors[error.code || 0]);
if (error.message) {
details = `<div class="vjs-errors-details">${player.localize('Technical details')}
: <div class="vjs-errors-message">${player.localize(error.message)}</div>
</div>`;
}
if (error.code === 4 && !FlashObj.isSupported()) {
const 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">
<h2 class="vjs-errors-headline">${this.localize(error.headline)}</h2>
<div><b>${this.localize('Error Code')}</b>: ${(error.type || error.code)}</div>
${details}
</div>
<div class="vjs-errors-ok-button-container">
<button class="vjs-errors-ok-button">${this.localize('OK')}</button>
</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');
}
let okButton = display.el().querySelector('.vjs-errors-ok-button');
videojs.on(okButton, 'click', function() {
display.close();
});
};
const onDisposeHandler = function() {
cleanup();
player.removeClass('vjs-errors');
player.off('play', onPlayStartMonitor);
player.off('play', onPlayNoSource);
player.off('dispose', onDisposeHandler);
player.off('error', onErrorHandler);
};
const reInitPlugin = function(newOptions) {
onDisposeHandler();
initPlugin(player, videojs.mergeOptions(defaults, newOptions));
};
player.on('play', onPlayStartMonitor);
player.on('play', onPlayNoSource);
player.on('dispose', onDisposeHandler);
player.on('error', onErrorHandler);
player.ready(() => {
player.addClass('vjs-errors');
});
player.errors = reInitPlugin;
};
/**
* Initialize the plugin. Waits until the player is ready to do anything.
*/
const errors = function(options) {
initPlugin(this, videojs.mergeOptions(defaults, options));
};
// Register the plugin with video.js.
videojs.plugin('errors', errors);
export default errors;