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:
parent
740014c57b
commit
56cbe66f42
@ -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",
|
||||
|
@ -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;
|
||||
|
@ -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
19
src/js/error-display.js
Normal 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;
|
||||
};
|
@ -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.
|
||||
|
@ -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(){
|
||||
|
126
src/js/player.js
126
src/js/player.js
@ -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'); };
|
||||
|
||||
|
@ -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();
|
||||
});
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user