1
0
mirror of https://github.com/videojs/video.js.git synced 2024-11-28 08:58:46 +02:00

Started on better error handling and displaying in the UI when an error has occurred.

This commit is contained in:
Steve Heffernan 2014-05-02 13:06:49 -07:00
parent 740014c57b
commit 56cbe66f42
8 changed files with 247 additions and 20 deletions

View File

@ -37,6 +37,7 @@ var sourceFiles = [
"src/js/poster.js",
"src/js/loading-spinner.js",
"src/js/big-play-button.js",
"src/js/error-display.js",
"src/js/media/media.js",
"src/js/media/html5.js",
"src/js/media/flash.js",

View File

@ -193,6 +193,11 @@ The default control bar that is a container for most of the controls.
display: none;
}
/* The control bar shouldn't show after an error */
.vjs-default-skin.vjs-error .vjs-control-bar {
display: none;
}
/* IE8 is flakey with fonts, and you have to change the actual content to force
fonts to show/hide properly.
- "\9" IE8 hack didn't work for this
@ -543,6 +548,41 @@ easily in the skin designer. http://designer.videojs.com/
height: 100%;
}
.vjs-error .vjs-big-play-button {
display: none;
}
/* Error Display
--------------------------------------------------------------------------------
*/
.vjs-error .vjs-error-display {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
}
.vjs-error .vjs-error-display:before {
// content: @play-icon;
// font-family: VideoJS;
content: 'X';
font-family: Arial;
font-size: 4em;
/* In order to center the play icon vertically we need to set the line height
to the same as the button height */
line-height: 1;
text-shadow: 0.05em 0.05em 0.1em #000;
text-align: center /* Needed for IE8 */;
vertical-align: middle;
position: absolute;
top: 50%;
margin-top: -0.5em;
width: 100%;
}
/* Loading Spinner
--------------------------------------------------------------------------------
*/
@ -566,6 +606,12 @@ easily in the skin designer. http://designer.videojs.com/
.animation(spin 1.5s infinite linear);
}
/* Errors are unrecoverable without user interaction,
so hide the spinner in the case of an error */
.video-js.vjs-error .vjs-loading-spinner {
display: none;
}
.vjs-default-skin .vjs-loading-spinner:before {
content: @spinner3-icon;
font-family: VideoJS;

View File

@ -95,7 +95,8 @@ vjs.options = {
'textTrackDisplay': {},
'loadingSpinner': {},
'bigPlayButton': {},
'controlBar': {}
'controlBar': {},
'errorDisplay': {}
},
// Default message to show when a video cannot be played.

19
src/js/error-display.js Normal file
View File

@ -0,0 +1,19 @@
/**
* Display that an error has occurred making the video unplayable
* @param {vjs.Player|Object} player
* @param {Object=} options
* @constructor
*/
vjs.ErrorDisplay = vjs.Component.extend({
init: function(player, options){
vjs.Component.call(this, player, options);
}
});
vjs.ErrorDisplay.prototype.createEl = function(){
var el = vjs.Component.prototype.createEl.call(this, 'div', {
className: 'vjs-error-display'
});
return el;
};

View File

@ -22,7 +22,6 @@ vjs.LoadingSpinner = vjs.Component.extend({
// 'seeking' event
player.on('seeked', vjs.bind(this, this.hide));
player.on('error', vjs.bind(this, this.show));
player.on('ended', vjs.bind(this, this.hide));
// Not showing spinner on stalled any more. Browsers may stall and then not trigger any events that would remove the spinner.

View File

@ -108,18 +108,26 @@ vjs.Html5.prototype.createEl = function(){
// Make video events trigger player events
// May seem verbose here, but makes other APIs possible.
// Triggers removed using this.off when disposed
vjs.Html5.prototype.setupTriggers = function(){
for (var i = vjs.Html5.Events.length - 1; i >= 0; i--) {
vjs.on(this.el_, vjs.Html5.Events[i], vjs.bind(this.player_, this.eventHandler));
vjs.on(this.el_, vjs.Html5.Events[i], vjs.bind(this, this.eventHandler));
}
};
// Triggers removed using this.off when disposed
vjs.Html5.prototype.eventHandler = function(e){
this.trigger(e);
vjs.Html5.prototype.eventHandler = function(evt){
// In the case of an error, set the error prop on the player
// and let the player handle triggering the event.
if (evt.type == 'error') {
this.player().error(this.error().code);
// No need for media events to bubble up.
e.stopPropagation();
// in some cases we pass the event directly to the player
} else {
// No need for media events to bubble up.
evt.bubbles = false;
this.player().trigger(evt);
}
};
vjs.Html5.prototype.useNativeControls = function(){

View File

@ -84,7 +84,6 @@ vjs.Player = vjs.Component.extend({
this.on('pause', this.onPause);
this.on('progress', this.onProgress);
this.on('durationchange', this.onDurationChange);
this.on('error', this.onError);
this.on('fullscreenchange', this.onFullscreenChange);
// Make player easily findable by ID
@ -552,14 +551,6 @@ vjs.Player.prototype.onFullscreenChange = function() {
}
};
/**
* Fired when there is an error in playback
* @event error
*/
vjs.Player.prototype.onError = function(e) {
vjs.log('Video Error', e);
};
// /* Player API
// ================================================================================ */
@ -594,7 +585,6 @@ vjs.Player.prototype.techCall = function(method, arg){
// Get calls can't wait for the tech, and sometimes don't need to.
vjs.Player.prototype.techGet = function(method){
if (this.tech && this.tech.isReady_) {
// Flash likes to die and reload when you hide or reposition it.
@ -630,7 +620,15 @@ vjs.Player.prototype.techGet = function(method){
* @return {vjs.Player} self
*/
vjs.Player.prototype.play = function(){
this.techCall('play');
if (this.error()) {
// In the case of an error, trying to play again wont fix the issue
// so we're blocking calling play in this case.
// We might log an error when this happpens, but this is probably too chatty.
// vjs.log.error('The error must be resolved before attempting to play the video');
} else {
this.techCall('play');
}
return this;
};
@ -1268,7 +1266,111 @@ vjs.Player.prototype.usingNativeControls = function(bool){
return this.usingNativeControls_;
};
vjs.Player.prototype.error = function(){ return this.techGet('error'); };
/**
* Custom MediaError to mimic the HTML5 MediaError
* @param {Number} code The media error code
*/
vjs.MediaError = function(code){
if (typeof code == 'number') {
this.code = code;
} else if (typeof code == 'string') {
// default code is zero, so this is a custom error
this.message = code;
} else if (typeof code == 'object') { // object
vjs.obj.merge(this, code);
}
};
vjs.MediaError.prototype.code = 0;
// message is not part of the HTML5 video spec
// but allows for more informative custom errors
vjs.MediaError.prototype.message = '';
vjs.MediaError.prototype.status = null;
vjs.MediaError.errorTypes = [
'MEDIA_ERR_CUSTOM', // = 0
'MEDIA_ERR_ABORTED', // = 1
'MEDIA_ERR_NETWORK', // = 2
'MEDIA_ERR_DECODE', // = 3
'MEDIA_ERR_SRC_NOT_SUPPORTED', // = 4
'MEDIA_ERR_ENCRYPTED' // = 5
];
// Add types as properties on MediaError
// e.g. MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED = 4;
for (var errNum = 0; errNum < vjs.MediaError.errorTypes.length; errNum++) {
vjs.MediaError[vjs.MediaError.errorTypes[errNum]] = errNum;
// values should be accessible on both the class and instance
vjs.MediaError.prototype[vjs.MediaError.errorTypes[errNum]] = errNum;
}
/**
* Store the current media error
* @type {Object}
* @private
*/
vjs.Player.prototype.error_ = null;
/**
* Set or get the current MediaError
* @param {*} err A MediaError or a String/Number to be turned into a MediaError
* @return {vjs.MediaError|null} when getting
* @return {vjs.Player} when setting
*/
vjs.Player.prototype.error = function(err){
if (err === undefined) {
return this.error_;
}
// restoring to default
if (err === null) {
this.error_ = err;
this.removeClass('vjs-error');
return this;
}
// error instance
if (err instanceof vjs.MediaError) {
this.error_ = err;
} else {
this.error_ = new vjs.MediaError(err);
}
// fire an error event on the player
this.trigger('error');
// add the vjs-error classname to the player
this.addClass('vjs-error');
// log the name of the error type and any message
vjs.log.error(this.error_);
return this;
};
vjs.Player.prototype.waiting_ = false;
vjs.Player.prototype.waiting = function(bool){
if (bool === undefined) {
return this.waiting_;
}
var wasWaiting = this.waiting_;
this.waiting_ = bool;
// trigger an event if it's newly waiting
if (!wasWaiting && bool) {
this.addClass('vjs-waiting');
this.trigger('waiting');
} else {
this.removeClass('vjs-waiting');
}
return this;
};
vjs.Player.prototype.ended = function(){ return this.techGet('ended'); };
vjs.Player.prototype.seeking = function(){ return this.techGet('seeking'); };

View File

@ -402,3 +402,54 @@ test('should remove vjs-has-started class', function(){
player.trigger('play');
ok(player.el().className.indexOf('vjs-has-started') !== -1, 'vjs-has-started class added again');
});
test('player should handle different error types', function(){
expect(8);
var player = PlayerTest.makePlayer({});
var testMsg = 'test message';
// prevent error log messages in the console
sinon.stub(vjs.log, 'error');
// error code supplied
function errCode(){
equal(player.error().code, 1, 'error code is correct');
}
player.on('error', errCode);
player.error(1);
player.off('error', errCode);
// error instance supplied
function errInst(){
equal(player.error().code, 2, 'MediaError code is correct');
equal(player.error().message, testMsg, 'MediaError message is correct');
}
player.on('error', errInst);
player.error(new vjs.MediaError({ code: 2, message: testMsg }));
player.off('error', errInst);
// error message supplied
function errMsg(){
equal(player.error().code, 0, 'error message code is correct');
equal(player.error().message, testMsg, 'error message is correct');
}
player.on('error', errMsg);
player.error(testMsg);
player.off('error', errMsg);
// error config supplied
function errConfig(){
equal(player.error().code, 3, 'error config code is correct');
equal(player.error().message, testMsg, 'error config message is correct');
}
player.on('error', errConfig);
player.error({ code: 3, message: testMsg });
player.off('error', errConfig);
// check for vjs-error classname
ok(player.el().className.indexOf('vjs-error') >= 0, 'player does not have vjs-error classname');
// restore error logging
vjs.log.error.restore();
});