diff --git a/CHANGELOG.md b/CHANGELOG.md index 351cdb573..ffb0157c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ CHANGELOG ========= ## HEAD (Unreleased) -_(none)_ +* Fixed an issue with the firstplay event not firing when autoplaying ([view](https://github.com/videojs/video.js/pull/1271)) -------------------- diff --git a/src/js/media/html5.js b/src/js/media/html5.js index 6cb8ff46c..66fcdb158 100644 --- a/src/js/media/html5.js +++ b/src/js/media/html5.js @@ -32,10 +32,7 @@ vjs.Html5 = vjs.MediaTechController.extend({ // If the element source is already set, we may have missed the loadstart event, and want to trigger it. // We don't want to set the source again and interrupt playback. if (source && this.el_.currentSrc === source.src && this.el_.networkState > 0) { - // wait for the player to be ready so the player listeners are attached - player.ready(function(){ - player.trigger('loadstart'); - }); + player.trigger('loadstart'); // Otherwise set the source if one was provided. } else if (source) { this.el_.src = source.src; diff --git a/src/js/player.js b/src/js/player.js index 9fdd95cca..c8d461682 100644 --- a/src/js/player.js +++ b/src/js/player.js @@ -58,20 +58,6 @@ vjs.Player = vjs.Component.extend({ // see enableTouchActivity in Component options.reportTouchActivity = false; - // Make sure the event listeners are the first things to happen when - // the player is ready. See #1208 - // If not, the tech might fire events before the listeners are attached. - this.ready(function(){ - this.on('loadstart', this.onLoadStart); - this.on('ended', this.onEnded); - this.on('play', this.onPlay); - this.on('firstplay', this.onFirstPlay); - this.on('pause', this.onPause); - this.on('progress', this.onProgress); - this.on('durationchange', this.onDurationChange); - this.on('fullscreenchange', this.onFullscreenChange); - }); - // Run base component initializing with new options. // Builds the element through createEl() // Inits and embeds any child components in opts @@ -230,6 +216,22 @@ vjs.Player.prototype.createEl = function(){ } vjs.insertFirst(tag, el); // Breaks iPhone, fixed in HTML5 setup. + // The event listeners need to be added before the children are added + // in the component init because the tech (loaded with mediaLoader) may + // fire events, like loadstart, that these events need to capture. + // Long term it might be better to expose a way to do this in component.init + // like component.initEventListeners() that runs between el creation and + // adding children + this.el_ = el; + this.on('loadstart', this.onLoadStart); + this.on('ended', this.onEnded); + this.on('play', this.onPlay); + this.on('firstplay', this.onFirstPlay); + this.on('pause', this.onPause); + this.on('progress', this.onProgress); + this.on('durationchange', this.onDurationChange); + this.on('fullscreenchange', this.onFullscreenChange); + return el; }; @@ -408,30 +410,44 @@ vjs.Player.prototype.stopTrackingCurrentTime = function(){ * @event loadstart */ vjs.Player.prototype.onLoadStart = function() { - // remove any first play listeners that weren't triggered from a previous video. - this.off('play', initFirstPlay); - this.one('play', initFirstPlay); + // TODO: Update to use `emptied` event instead. See #1277. - if (this.error()) { - this.error(null); + // reset the error state + this.error(null); + + // If it's already playing we want to trigger a firstplay event now. + // The firstplay event relies on both the play and loadstart events + // which can happen in any order for a new source + if (!this.paused()) { + this.trigger('firstplay'); + } else { + // reset the hasStarted state + this.hasStarted(false); + this.one('play', function(){ + this.hasStarted(true); + }); } - - vjs.removeClass(this.el_, 'vjs-has-started'); }; - // Need to create this outside the scope of onLoadStart so it - // can be added and removed (to avoid piling first play listeners). -function initFirstPlay(e) { - var fpEvent = { type: 'firstplay', target: this.el_ }; - // Using vjs.trigger so we can check if default was prevented - var keepGoing = vjs.trigger(this.el_, fpEvent); +vjs.Player.prototype.hasStarted_ = false; - if (!keepGoing) { - e.preventDefault(); - e.stopPropagation(); - e.stopImmediatePropagation(); +vjs.Player.prototype.hasStarted = function(hasStarted){ + if (hasStarted !== undefined) { + // only update if this is a new value + if (this.hasStarted_ !== hasStarted) { + this.hasStarted_ = hasStarted; + if (hasStarted) { + this.addClass('vjs-has-started'); + // trigger the firstplay event if this newly has played + this.trigger('firstplay'); + } else { + this.removeClass('vjs-has-started'); + } + } + return this; } -} + return this.hasStarted_; +}; /** * Fired when the player has initial duration and dimension information diff --git a/test/unit/mediafaker.js b/test/unit/mediafaker.js index 124e5a555..222a9c10e 100644 --- a/test/unit/mediafaker.js +++ b/test/unit/mediafaker.js @@ -40,6 +40,7 @@ vjs.MediaFaker.prototype.src = function(){ return 'movie.mp4'; }; vjs.MediaFaker.prototype.volume = function(){ return 0; }; vjs.MediaFaker.prototype.muted = function(){ return false; }; vjs.MediaFaker.prototype.pause = function(){ return false; }; +vjs.MediaFaker.prototype.paused = function(){ return true; }; vjs.MediaFaker.prototype.supportsFullScreen = function(){ return false; }; vjs.MediaFaker.prototype.features = {}; vjs.MediaFaker.prototype.buffered = function(){ return {}; }; diff --git a/test/unit/player.js b/test/unit/player.js index 70831b45d..7eb6db94e 100644 --- a/test/unit/player.js +++ b/test/unit/player.js @@ -378,7 +378,7 @@ test('should not add multiple first play events despite subsequent loads', funct var player = PlayerTest.makePlayer({}); player.on('firstplay', function(){ - ok('First play should fire once.'); + ok(true, 'First play should fire once.'); }); // Checking to make sure onLoadStart removes first play listener before adding a new one. @@ -387,6 +387,35 @@ test('should not add multiple first play events despite subsequent loads', funct player.trigger('play'); }); +test('should fire firstplay after resetting the player', function() { + var player = PlayerTest.makePlayer({}); + + var fpFired = false; + player.on('firstplay', function(){ + fpFired = true; + }); + + // init firstplay listeners + player.trigger('loadstart'); + player.trigger('play'); + ok(fpFired, 'First firstplay fired'); + + // reset the player + player.trigger('loadstart'); + fpFired = false; + player.trigger('play'); + ok(fpFired, 'Second firstplay fired'); + + // the play event can fire before the loadstart event. + // in that case we still want the firstplay even to fire. + player.tech.paused = function(){ return false; }; + fpFired = false; + // reset the player + player.trigger('loadstart'); + // player.trigger('play'); + ok(fpFired, 'Third firstplay fired'); +}); + test('should remove vjs-has-started class', function(){ expect(3);