1
0
mirror of https://github.com/videojs/video.js.git synced 2025-07-13 01:30:17 +02:00

@eXon began Tech 2.0 work, improved how tech events are handled by the player. closes #2057

closes #1485
This commit is contained in:
eXon
2015-05-06 14:01:52 -04:00
committed by heff
parent de843affb7
commit e5595b1e38
29 changed files with 620 additions and 545 deletions

View File

@ -12,6 +12,7 @@
"undef" : true, "undef" : true,
"laxbreak" : true, "laxbreak" : true,
"esnext" : true, "esnext" : true,
"eqeqeq" : true,
"predef" : [ "predef" : [
"_V_", "_V_",
"goog", "goog",

View File

@ -27,6 +27,7 @@ CHANGELOG
* @gkatsev added error logging for bad JSON formatting ([view](https://github.com/videojs/video.js/pull/2113)) * @gkatsev added error logging for bad JSON formatting ([view](https://github.com/videojs/video.js/pull/2113))
* @gkatsev added a sensible toJSON function ([view](https://github.com/videojs/video.js/pull/2114)) * @gkatsev added a sensible toJSON function ([view](https://github.com/videojs/video.js/pull/2114))
* @bc-bbay fixed instance where progress bars would go passed 100% ([view](https://github.com/videojs/video.js/pull/2040)) * @bc-bbay fixed instance where progress bars would go passed 100% ([view](https://github.com/videojs/video.js/pull/2040))
* @eXon began Tech 2.0 work, improved how tech events are handled by the player ([view](https://github.com/videojs/video.js/pull/2057))
-------------------- --------------------

View File

@ -69,7 +69,7 @@ class Button extends Component {
// KeyPress (document level) - Trigger click when keys are pressed // KeyPress (document level) - Trigger click when keys are pressed
handleKeyPress(event) { handleKeyPress(event) {
// Check for space bar (32) or enter (13) keys // Check for space bar (32) or enter (13) keys
if (event.which == 32 || event.which == 13) { if (event.which === 32 || event.which === 13) {
event.preventDefault(); event.preventDefault();
this.handleClick(); this.handleClick();
} }

View File

@ -59,8 +59,7 @@ class Component {
// If there was no ID from the options, generate one // If there was no ID from the options, generate one
if (!this.id_) { if (!this.id_) {
// Don't require the player ID function in the case of mock players // Don't require the player ID function in the case of mock players
let id = player.id && player.id() || 'no_player'; let id = player && player.id && player.id() || 'no_player';
this.id_ = `${id}_component_${Lib.guid++}`; this.id_ = `${id}_component_${Lib.guid++}`;
} }
@ -1033,7 +1032,7 @@ class Component {
*/ */
enableTouchActivity() { enableTouchActivity() {
// Don't continue if the root player doesn't support reporting user activity // Don't continue if the root player doesn't support reporting user activity
if (!this.player().reportUserActivity) { if (!this.player() || !this.player().reportUserActivity) {
return; return;
} }

View File

@ -28,7 +28,7 @@ class PlaybackRateMenuItem extends MenuItem {
} }
update() { update() {
this.selected(this.player().playbackRate() == this.rate); this.selected(this.player().playbackRate() === this.rate);
} }
} }

View File

@ -51,7 +51,7 @@ class SeekBar extends Slider {
let newTime = this.calculateDistance(event) * this.player_.duration(); let newTime = this.calculateDistance(event) * this.player_.duration();
// Don't let video end while scrubbing. // Don't let video end while scrubbing.
if (newTime == this.player_.duration()) { newTime = newTime - 0.1; } if (newTime === this.player_.duration()) { newTime = newTime - 0.1; }
// Set new time (tell player to seek to new time) // Set new time (tell player to seek to new time)
this.player_.currentTime(newTime); this.player_.currentTime(newTime);

View File

@ -48,7 +48,7 @@ class ChaptersButton extends TextTrackButton {
for (let i = 0, l = tracks.length; i < l; i++) { for (let i = 0, l = tracks.length; i < l; i++) {
let track = tracks[i]; let track = tracks[i];
if (track['kind'] == this.kind_) { if (track['kind'] === this.kind_) {
if (!track.cues) { if (!track.cues) {
track['mode'] = 'hidden'; track['mode'] = 'hidden';
/* jshint loopfunc:true */ /* jshint loopfunc:true */

View File

@ -74,7 +74,7 @@ var videojs = function(id, options, ready){
// CDN Version. Used to target right flash swf. // CDN Version. Used to target right flash swf.
videojs.CDN_VERSION = '__VERSION_NO_PATCH__'; videojs.CDN_VERSION = '__VERSION_NO_PATCH__';
videojs.ACCESS_PROTOCOL = ('https:' == document.location.protocol ? 'https://' : 'http://'); videojs.ACCESS_PROTOCOL = ('https:' === document.location.protocol ? 'https://' : 'http://');
/** /**
* Full player version * Full player version

View File

@ -168,7 +168,7 @@ var on = function(elem, type, fn){
}; };
} }
if (data.handlers[type].length == 1) { if (data.handlers[type].length === 1) {
if (elem.addEventListener) { if (elem.addEventListener) {
elem.addEventListener(type, data.dispatcher, false); elem.addEventListener(type, data.dispatcher, false);
} else if (elem.attachEvent) { } else if (elem.attachEvent) {

View File

@ -23,7 +23,7 @@ var createEl = function(tagName='div', properties={}){
// add the attribute "role". My guess is because it's not a valid attribute in some namespaces, although // add the attribute "role". My guess is because it's not a valid attribute in some namespaces, although
// browsers handle the attribute just fine. The W3C allows for aria-* attributes to be used in pre-HTML5 docs. // browsers handle the attribute just fine. The W3C allows for aria-* attributes to be used in pre-HTML5 docs.
// http://www.w3.org/TR/wai-aria-primer/#ariahtml. Using setAttribute gets around this problem. // http://www.w3.org/TR/wai-aria-primer/#ariahtml. Using setAttribute gets around this problem.
if (propName.indexOf('aria-') !== -1 || propName == 'role') { if (propName.indexOf('aria-') !== -1 || propName === 'role') {
el.setAttribute(propName, val); el.setAttribute(propName, val);
} else { } else {
el[propName] = val; el[propName] = val;
@ -616,10 +616,10 @@ var setLocalStorage = function(key, value){
if (!localStorage) { return; } if (!localStorage) { return; }
localStorage[key] = value; localStorage[key] = value;
} catch(e) { } catch(e) {
if (e.code == 22 || e.code == 1014) { // Webkit == 22 / Firefox == 1014 if (e.code === 22 || e.code === 1014) { // Webkit == 22 / Firefox == 1014
log('LocalStorage Full (VideoJS)', e); log('LocalStorage Full (VideoJS)', e);
} else { } else {
if (e.code == 18) { if (e.code === 18) {
log('LocalStorage not allowed (VideoJS)', e); log('LocalStorage not allowed (VideoJS)', e);
} else { } else {
log('LocalStorage Error (VideoJS)', e); log('LocalStorage Error (VideoJS)', e);

View File

@ -105,7 +105,7 @@ class MenuButton extends Button {
handleKeyPress(event) { handleKeyPress(event) {
// Check for space bar (32) or enter (13) keys // Check for space bar (32) or enter (13) keys
if (event.which == 32 || event.which == 13) { if (event.which === 32 || event.which === 13) {
if (this.buttonPressed_){ if (this.buttonPressed_){
this.unpressButton(); this.unpressButton();
} else { } else {
@ -113,7 +113,7 @@ class MenuButton extends Button {
} }
event.preventDefault(); event.preventDefault();
// Check for escape (27) key // Check for escape (27) key
} else if (event.which == 27){ } else if (event.which === 27){
if (this.buttonPressed_){ if (this.buttonPressed_){
this.unpressButton(); this.unpressButton();
} }

View File

@ -162,6 +162,9 @@ class Player extends Component {
this.userActive_ = true; this.userActive_ = true;
this.reportUserActivity(); this.reportUserActivity();
this.listenForUserActivity(); this.listenForUserActivity();
this.on('fullscreenchange', this.handleFullscreenChange);
this.on('stageclick', this.handleStageClick);
} }
/** /**
@ -201,7 +204,7 @@ class Player extends Component {
Lib.obj.each(attrs, function(attr) { Lib.obj.each(attrs, function(attr) {
// workaround so we don't totally break IE7 // workaround so we don't totally break IE7
// http://stackoverflow.com/questions/3653444/css-styles-not-applied-on-dynamic-elements-in-internet-explorer-7 // http://stackoverflow.com/questions/3653444/css-styles-not-applied-on-dynamic-elements-in-internet-explorer-7
if (attr == 'class') { if (attr === 'class') {
el.className = attrs[attr]; el.className = attrs[attr];
} else { } else {
el.setAttribute(attr, attrs[attr]); el.setAttribute(attr, attrs[attr]);
@ -234,25 +237,7 @@ class Player extends Component {
} }
Lib.insertFirst(tag, el); // Breaks iPhone, fixed in HTML5 setup. Lib.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.el_ = el;
this.on('loadstart', this.handleLoadStart);
this.on('waiting', this.handleWaiting);
this.on(['canplay', 'canplaythrough', 'playing', 'ended'], this.handleWaitEnd);
this.on('seeking', this.handleSeeking);
this.on('seeked', this.handleSeeked);
this.on('ended', this.handleEnded);
this.on('play', this.handlePlay);
this.on('firstplay', this.handleFirstPlay);
this.on('pause', this.handlePause);
this.on('progress', this.handleProgress);
this.on('durationchange', this.handleDurationChange);
this.on('fullscreenchange', this.handleFullscreenChange);
return el; return el;
} }
@ -272,6 +257,7 @@ class Player extends Component {
// get rid of the HTML5 video tag as soon as we are using another tech // get rid of the HTML5 video tag as soon as we are using another tech
if (techName !== 'Html5' && this.tag) { if (techName !== 'Html5' && this.tag) {
Component.getComponent('Html5').disposeMediaElement(this.tag); Component.getComponent('Html5').disposeMediaElement(this.tag);
this.tag.player = null;
this.tag = null; this.tag = null;
} }
@ -280,16 +266,24 @@ class Player extends Component {
// Turn off API access because we're loading a new tech that might load asynchronously // Turn off API access because we're loading a new tech that might load asynchronously
this.isReady_ = false; this.isReady_ = false;
var techReady = function(){ var techReady = Lib.bind(this, function() {
this.player_.triggerReady(); this.triggerReady();
}; });
// Grab tech-specific options from player options and add source and parent element to use. // Grab tech-specific options from player options and add source and parent element to use.
var techOptions = Lib.obj.merge({ 'source': source, 'parentEl': this.el_ }, this.options_[techName.toLowerCase()]); var techOptions = Lib.obj.merge({
'source': source,
'playerId': this.id(),
'textTracks': this.textTracks_
}, this.options_[techName.toLowerCase()]);
if (this.tag) {
techOptions.tag = this.tag;
}
if (source) { if (source) {
this.currentType_ = source.type; this.currentType_ = source.type;
if (source.src == this.cache_.src && this.cache_.currentTime > 0) { if (source.src === this.cache_.src && this.cache_.currentTime > 0) {
techOptions['startTime'] = this.cache_.currentTime; techOptions['startTime'] = this.cache_.currentTime;
} }
@ -298,12 +292,61 @@ class Player extends Component {
// Initialize tech instance // Initialize tech instance
let techComponent = Component.getComponent(techName); let techComponent = Component.getComponent(techName);
this.tech = new techComponent(this, techOptions); this.tech = new techComponent(techOptions);
this.on(this.tech, 'ready', this.handleTechReady);
this.on(this.tech, 'usenativecontrols', this.handleTechUseNativeControls);
// Listen to every HTML5 events and trigger them back on the player for the plugins
this.on(this.tech, 'loadstart', this.handleTechLoadStart);
this.on(this.tech, 'waiting', this.handleTechWaiting);
this.on(this.tech, 'canplay', this.handleTechCanPlay);
this.on(this.tech, 'canplaythrough', this.handleTechCanPlayThrough);
this.on(this.tech, 'playing', this.handleTechPlaying);
this.on(this.tech, 'ended', this.handleTechEnded);
this.on(this.tech, 'seeking', this.handleTechSeeking);
this.on(this.tech, 'seeked', this.handleTechSeeked);
this.on(this.tech, 'play', this.handleTechPlay);
this.on(this.tech, 'firstplay', this.handleTechFirstPlay);
this.on(this.tech, 'pause', this.handleTechPause);
this.on(this.tech, 'progress', this.handleTechProgress);
this.on(this.tech, 'durationchange', this.handleTechDurationChange);
this.on(this.tech, 'fullscreenchange', this.handleTechFullscreenChange);
this.on(this.tech, 'error', this.handleTechError);
this.on(this.tech, 'suspend', this.handleTechSuspend);
this.on(this.tech, 'abort', this.handleTechAbort);
this.on(this.tech, 'emptied', this.handleTechEmptied);
this.on(this.tech, 'stalled', this.handleTechStalled);
this.on(this.tech, 'loadedmetadata', this.handleTechLoadedMetaData);
this.on(this.tech, 'loadeddata', this.handleTechLoadedData);
this.on(this.tech, 'timeupdate', this.handleTechTimeUpdate);
this.on(this.tech, 'ratechange', this.handleTechRateChange);
this.on(this.tech, 'volumechange', this.handleTechVolumeChange);
this.on(this.tech, 'texttrackchange', this.onTextTrackChange);
if (this.controls() && !this.usingNativeControls()) {
this.addTechControlsListeners();
}
// Add the tech element in the DOM if it was not already there
// Make sure to not insert the original video element if using Html5
if (this.tech.el().parentNode !== this.el() && (techName !== 'Html5' || !this.tag)) {
Lib.insertFirst(this.tech.el(), this.el());
}
// Get rid of the original video tag reference after the first tech is loaded
if (this.tag) {
this.tag.player = null;
this.tag = null;
}
this.tech.ready(techReady); this.tech.ready(techReady);
} }
unloadTech() { unloadTech() {
// Save the current text tracks so that we can reuse the same text tracks with the next tech
this.textTracks_ = this.textTracks();
this.isReady_ = false; this.isReady_ = false;
this.tech.dispose(); this.tech.dispose();
@ -311,11 +354,72 @@ class Player extends Component {
this.tech = false; this.tech = false;
} }
addTechControlsListeners() {
// Some browsers (Chrome & IE) don't trigger a click on a flash swf, but do
// trigger mousedown/up.
// http://stackoverflow.com/questions/1444562/javascript-onclick-event-over-flash-object
// Any touch events are set to block the mousedown event from happening
this.on(this.tech, 'mousedown', this.handleTechClick);
// If the controls were hidden we don't want that to change without a tap event
// so we'll check if the controls were already showing before reporting user
// activity
this.on(this.tech, 'touchstart', this.handleTechTouchStart);
this.on(this.tech, 'touchmove', this.handleTechTouchMove);
this.on(this.tech, 'touchend', this.handleTechTouchEnd);
// Turn on component tap events
this.tech.emitTapEvents();
// The tap listener needs to come after the touchend listener because the tap
// listener cancels out any reportedUserActivity when setting userActive(false)
this.on(this.tech, 'tap', this.handleTechTap);
}
/**
* Remove the listeners used for click and tap controls. This is needed for
* toggling to controls disabled, where a tap/touch should do nothing.
*/
removeTechControlsListeners() {
// We don't want to just use `this.off()` because there might be other needed
// listeners added by techs that extend this.
this.off(this.tech, 'tap', this.handleTechTap);
this.off(this.tech, 'touchstart', this.handleTechTouchStart);
this.off(this.tech, 'touchmove', this.handleTechTouchMove);
this.off(this.tech, 'touchend', this.handleTechTouchEnd);
this.off(this.tech, 'mousedown', this.handleTechClick);
}
/**
* Player waits for the tech to be ready
* @private
*/
handleTechReady() {
this.triggerReady();
// Chrome and Safari both have issues with autoplay.
// In Safari (5.1.1), when we move the video element into the container div, autoplay doesn't work.
// In Chrome (15), if you have autoplay + a poster + no controls, the video gets hidden (but audio plays)
// This fixes both issues. Need to wait for API, so it updates displays correctly
if (this.tag && this.options_.autoplay && this.paused()) {
delete this.tag.poster; // Chrome Fix. Fixed in Chrome v16.
this.play();
}
}
/**
* Fired when the native controls are used
* @private
*/
handleTechUseNativeControls() {
this.usingNativeControls(true);
}
/** /**
* Fired when the user agent begins looking for media data * Fired when the user agent begins looking for media data
* @event loadstart * @event loadstart
*/ */
handleLoadStart() { handleTechLoadStart() {
// TODO: Update to use `emptied` event instead. See #1277. // TODO: Update to use `emptied` event instead. See #1277.
this.removeClass('vjs-ended'); this.removeClass('vjs-ended');
@ -327,10 +431,12 @@ class Player extends Component {
// The firstplay event relies on both the play and loadstart events // The firstplay event relies on both the play and loadstart events
// which can happen in any order for a new source // which can happen in any order for a new source
if (!this.paused()) { if (!this.paused()) {
this.trigger('loadstart');
this.trigger('firstplay'); this.trigger('firstplay');
} else { } else {
// reset the hasStarted state // reset the hasStarted state
this.hasStarted(false); this.hasStarted(false);
this.trigger('loadstart');
} }
} }
@ -356,7 +462,7 @@ class Player extends Component {
* Fired whenever the media begins or resumes playback * Fired whenever the media begins or resumes playback
* @event play * @event play
*/ */
handlePlay() { handleTechPlay() {
this.removeClass('vjs-ended'); this.removeClass('vjs-ended');
this.removeClass('vjs-paused'); this.removeClass('vjs-paused');
this.addClass('vjs-playing'); this.addClass('vjs-playing');
@ -364,39 +470,65 @@ class Player extends Component {
// hide the poster when the user hits play // hide the poster when the user hits play
// https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-play // https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-play
this.hasStarted(true); this.hasStarted(true);
this.trigger('play');
} }
/** /**
* Fired whenever the media begins waiting * Fired whenever the media begins waiting
* @event waiting * @event waiting
*/ */
handleWaiting() { handleTechWaiting() {
this.addClass('vjs-waiting'); this.addClass('vjs-waiting');
this.trigger('waiting');
} }
/** /**
* A handler for events that signal that waiting has ended * A handler for events that signal that waiting has ended
* which is not consistent between browsers. See #1351 * which is not consistent between browsers. See #1351
* @private * @event canplay
*/ */
handleWaitEnd() { handleTechCanPlay() {
this.removeClass('vjs-waiting'); this.removeClass('vjs-waiting');
this.trigger('canplay');
}
/**
* A handler for events that signal that waiting has ended
* which is not consistent between browsers. See #1351
* @event canplaythrough
*/
handleTechCanPlayThrough() {
this.removeClass('vjs-waiting');
this.trigger('canplaythrough');
}
/**
* A handler for events that signal that waiting has ended
* which is not consistent between browsers. See #1351
* @event playing
*/
handleTechPlaying() {
this.removeClass('vjs-waiting');
this.trigger('playing');
} }
/** /**
* Fired whenever the player is jumping to a new time * Fired whenever the player is jumping to a new time
* @event seeking * @event seeking
*/ */
handleSeeking() { handleTechSeeking() {
this.addClass('vjs-seeking'); this.addClass('vjs-seeking');
this.trigger('seeking');
} }
/** /**
* Fired when the player has finished jumping to a new time * Fired when the player has finished jumping to a new time
* @event seeked * @event seeked
*/ */
handleSeeked() { handleTechSeeked() {
this.removeClass('vjs-seeking'); this.removeClass('vjs-seeking');
this.trigger('seeked');
} }
/** /**
@ -408,7 +540,7 @@ class Player extends Component {
* *
* @event firstplay * @event firstplay
*/ */
handleFirstPlay() { handleTechFirstPlay() {
//If the first starttime attribute is specified //If the first starttime attribute is specified
//then we will start at the given offset in seconds //then we will start at the given offset in seconds
if(this.options_['starttime']){ if(this.options_['starttime']){
@ -416,24 +548,28 @@ class Player extends Component {
} }
this.addClass('vjs-has-started'); this.addClass('vjs-has-started');
this.trigger('firstplay');
} }
/** /**
* Fired whenever the media has been paused * Fired whenever the media has been paused
* @event pause * @event pause
*/ */
handlePause() { handleTechPause() {
this.removeClass('vjs-playing'); this.removeClass('vjs-playing');
this.addClass('vjs-paused'); this.addClass('vjs-paused');
this.trigger('pause');
} }
/** /**
* Fired while the user agent is downloading media data * Fired while the user agent is downloading media data
* @event progress * @event progress
*/ */
handleProgress() { handleTechProgress() {
this.trigger('progress');
// Add custom event for when source is finished downloading. // Add custom event for when source is finished downloading.
if (this.bufferedPercent() == 1) { if (this.bufferedPercent() === 1) {
this.trigger('loadedalldata'); this.trigger('loadedalldata');
} }
} }
@ -442,7 +578,7 @@ class Player extends Component {
* Fired when the end of the media resource is reached (currentTime == duration) * Fired when the end of the media resource is reached (currentTime == duration)
* @event ended * @event ended
*/ */
handleEnded() { handleTechEnded() {
this.addClass('vjs-ended'); this.addClass('vjs-ended');
if (this.options_['loop']) { if (this.options_['loop']) {
this.currentTime(0); this.currentTime(0);
@ -450,13 +586,66 @@ class Player extends Component {
} else if (!this.paused()) { } else if (!this.paused()) {
this.pause(); this.pause();
} }
this.trigger('ended');
} }
/** /**
* Fired when the duration of the media resource is first known or changed * Fired when the duration of the media resource is first known or changed
* @event durationchange * @event durationchange
*/ */
handleDurationChange() { handleTechDurationChange() {
this.updateDuration();
this.trigger('durationchange');
}
/**
* Handle a click on the media element to play/pause
*/
handleTechClick(event) {
// We're using mousedown to detect clicks thanks to Flash, but mousedown
// will also be triggered with right-clicks, so we need to prevent that
if (event.button !== 0) return;
// When controls are disabled a click should not toggle playback because
// the click is considered a control
if (this.controls()) {
if (this.paused()) {
this.play();
} else {
this.pause();
}
}
}
/**
* Handle a tap on the media element. It will toggle the user
* activity state, which hides and shows the controls.
*/
handleTechTap() {
this.userActive(!this.userActive());
}
handleTechTouchStart() {
this.userWasActive = this.userActive();
}
handleTechTouchMove() {
if (this.userWasActive){
this.reportUserActivity();
}
}
handleTechTouchEnd(event) {
// Stop the mouse events from also happening
event.preventDefault();
}
/**
* Update the duration of the player using the tech
* @private
*/
updateDuration() {
// Allows for caching value instead of asking player each time. // Allows for caching value instead of asking player each time.
// We need to get the techGet response and check for a value so we don't // We need to get the techGet response and check for a value so we don't
// accidentally cause the stack to blow up. // accidentally cause the stack to blow up.
@ -487,6 +676,107 @@ class Player extends Component {
} }
} }
/**
* native click events on the SWF aren't triggered on IE11, Win8.1RT
* use stageclick events triggered from inside the SWF instead
* @private
*/
handleStageClick() {
this.reportUserActivity();
}
handleTechFullscreenChange() {
this.trigger('fullscreenchange');
}
/**
* Fires when an error occurred during the loading of an audio/video
* @event error
*/
handleTechError() {
this.error(this.tech.error().code);
}
/**
* Fires when the browser is intentionally not getting media data
* @event suspend
*/
handleTechSuspend() {
this.trigger('suspend');
}
/**
* Fires when the loading of an audio/video is aborted
* @event abort
*/
handleTechAbort() {
this.trigger('abort');
}
/**
* Fires when the current playlist is empty
* @event emptied
*/
handleTechEmptied() {
this.trigger('emptied');
}
/**
* Fires when the browser is trying to get media data, but data is not available
* @event stalled
*/
handleTechStalled() {
this.trigger('stalled');
}
/**
* Fires when the browser has loaded meta data for the audio/video
* @event loadedmetadata
*/
handleTechLoadedMetaData() {
this.trigger('loadedmetadata');
}
/**
* Fires when the browser has loaded the current frame of the audio/video
* @event loaddata
*/
handleTechLoadedData() {
this.trigger('loadeddata');
}
/**
* Fires when the current playback position has changed
* @event timeupdate
*/
handleTechTimeUpdate() {
this.trigger('timeupdate');
}
/**
* Fires when the playing speed of the audio/video is changed
* @event ratechange
*/
handleTechRateChange() {
this.trigger('ratechange');
}
/**
* Fires when the volume has been changed
* @event volumechange
*/
handleTechVolumeChange() {
this.trigger('volumechange');
}
/**
* Fires when the text track has been changed
* @event texttrackchange
*/
onTextTrackChange() {
this.trigger('texttrackchange');
}
/** /**
* Object for cached values. * Object for cached values.
*/ */
@ -528,7 +818,7 @@ class Player extends Component {
Lib.log(`Video.js: ${method} method not defined for ${this.techName} playback technology.`, e); Lib.log(`Video.js: ${method} method not defined for ${this.techName} playback technology.`, e);
} else { } else {
// When a method isn't available on the object it throws a TypeError // When a method isn't available on the object it throws a TypeError
if (e.name == 'TypeError') { if (e.name === 'TypeError') {
Lib.log(`Video.js: ${method} unavailable on ${this.techName} playback technology element.`, e); Lib.log(`Video.js: ${method} unavailable on ${this.techName} playback technology element.`, e);
this.tech.isReady_ = false; this.tech.isReady_ = false;
} else { } else {
@ -653,7 +943,7 @@ class Player extends Component {
} }
if (this.cache_.duration === undefined) { if (this.cache_.duration === undefined) {
this.handleDurationChange(); this.updateDuration();
} }
return this.cache_.duration || 0; return this.cache_.duration || 0;
@ -1090,7 +1380,7 @@ class Player extends Component {
this.techCall('src', source.src); this.techCall('src', source.src);
} }
if (this.options_['preload'] == 'auto') { if (this.options_['preload'] === 'auto') {
this.load(); this.load();
} }
@ -1251,14 +1541,27 @@ class Player extends Component {
// Don't trigger a change event unless it actually changed // Don't trigger a change event unless it actually changed
if (this.controls_ !== bool) { if (this.controls_ !== bool) {
this.controls_ = bool; this.controls_ = bool;
if (this.usingNativeControls()) {
this.techCall('setControls', bool);
}
if (bool) { if (bool) {
this.removeClass('vjs-controls-disabled'); this.removeClass('vjs-controls-disabled');
this.addClass('vjs-controls-enabled'); this.addClass('vjs-controls-enabled');
this.trigger('controlsenabled'); this.trigger('controlsenabled');
if (!this.usingNativeControls()) {
this.addTechControlsListeners();
}
} else { } else {
this.removeClass('vjs-controls-enabled'); this.removeClass('vjs-controls-enabled');
this.addClass('vjs-controls-disabled'); this.addClass('vjs-controls-disabled');
this.trigger('controlsdisabled'); this.trigger('controlsdisabled');
if (!this.usingNativeControls()) {
this.removeTechControlsListeners();
}
} }
} }
return this; return this;
@ -1419,7 +1722,7 @@ class Player extends Component {
let handleMouseMove = function(e) { let handleMouseMove = function(e) {
// #1068 - Prevent mousemove spamming // #1068 - Prevent mousemove spamming
// Chrome Bug: https://code.google.com/p/chromium/issues/detail?id=366970 // Chrome Bug: https://code.google.com/p/chromium/issues/detail?id=366970
if(e.screenX != lastMoveX || e.screenY != lastMoveY) { if(e.screenX !== lastMoveX || e.screenY !== lastMoveY) {
lastMoveX = e.screenX; lastMoveX = e.screenX;
lastMoveY = e.screenY; lastMoveY = e.screenY;
handleActivity(); handleActivity();

View File

@ -211,10 +211,10 @@ class Slider extends Component {
} }
handleKeyPress(event) { handleKeyPress(event) {
if (event.which == 37 || event.which == 40) { // Left and Down Arrows if (event.which === 37 || event.which === 40) { // Left and Down Arrows
event.preventDefault(); event.preventDefault();
this.stepBack(); this.stepBack();
} else if (event.which == 38 || event.which == 39) { // Up and Right Arrows } else if (event.which === 38 || event.which === 39) { // Up and Right Arrows
event.preventDefault(); event.preventDefault();
this.stepForward(); this.stepForward();
} }

View File

@ -21,21 +21,13 @@ let navigator = window.navigator;
*/ */
class Flash extends Tech { class Flash extends Tech {
constructor(player, options, ready){ constructor(options, ready){
super(player, options, ready); super(options, ready);
let { source, parentEl } = options; let { source, parentEl } = options;
// Create a temporary element to be replaced by swf object
let placeHolder = this.el_ = Lib.createEl('div', { id: player.id() + '_temp_flash' });
// Generate ID for swf object // Generate ID for swf object
let objId = player.id()+'_flash_api'; let objId = options.playerId+'_flash_api';
// Store player options in local var for optimization
// TODO: switch to using player methods instead of options
// e.g. player.autoplay();
let playerOptions = player.options_;
// Merge default flashvars with ones passed in to init // Merge default flashvars with ones passed in to init
let flashVars = Lib.obj.merge({ let flashVars = Lib.obj.merge({
@ -46,25 +38,25 @@ class Flash extends Tech {
'errorEventProxyFunction': 'videojs.Flash.onError', 'errorEventProxyFunction': 'videojs.Flash.onError',
// Player Settings // Player Settings
'autoplay': playerOptions.autoplay, 'autoplay': options.autoplay,
'preload': playerOptions.preload, 'preload': options.preload,
'loop': playerOptions.loop, 'loop': options.loop,
'muted': playerOptions.muted 'muted': options.muted
}, options['flashVars']); }, options.flashVars);
// Merge default parames with ones passed in // Merge default parames with ones passed in
let params = Lib.obj.merge({ let params = Lib.obj.merge({
'wmode': 'opaque', // Opaque is needed to overlay controls, but can affect playback performance 'wmode': 'opaque', // Opaque is needed to overlay controls, but can affect playback performance
'bgcolor': '#000000' // Using bgcolor prevents a white flash when the object is loading 'bgcolor': '#000000' // Using bgcolor prevents a white flash when the object is loading
}, options['params']); }, options.params);
// Merge default attributes with ones passed in // Merge default attributes with ones passed in
let attributes = Lib.obj.merge({ let attributes = Lib.obj.merge({
'id': objId, 'id': objId,
'name': objId, // Both ID and Name needed or swf to identify itself 'name': objId, // Both ID and Name needed or swf to identify itself
'class': 'vjs-tech' 'class': 'vjs-tech'
}, options['attributes']); }, options.attributes);
// If source was supplied pass as a flash var. // If source was supplied pass as a flash var.
if (source) { if (source) {
@ -73,41 +65,24 @@ class Flash extends Tech {
}); });
} }
// Add placeholder to player div
Lib.insertFirst(placeHolder, parentEl);
// Having issues with Flash reloading on certain page actions (hide/resize/fullscreen) in certain browsers // Having issues with Flash reloading on certain page actions (hide/resize/fullscreen) in certain browsers
// This allows resetting the playhead when we catch the reload // This allows resetting the playhead when we catch the reload
if (options['startTime']) { if (options.startTime) {
this.ready(function(){ this.ready(function(){
this.load(); this.load();
this.play(); this.play();
this['currentTime'](options['startTime']); this.currentTime(options.startTime);
}); });
} }
// firefox doesn't bubble mousemove events to parent. videojs/video-js-swf#37
// bugzilla bug: https://bugzilla.mozilla.org/show_bug.cgi?id=836786
if (Lib.IS_FIREFOX) {
this.ready(function(){
this.on('mousemove', function(){
// since it's a custom event, don't bubble higher than the player
this.player().trigger({ 'type':'mousemove', 'bubbles': false });
});
});
}
// native click events on the SWF aren't triggered on IE11, Win8.1RT
// use stageclick events triggered from inside the SWF instead
player.on('stageclick', player.reportUserActivity);
window.videojs = window.videojs || {}; window.videojs = window.videojs || {};
window.videojs.Flash = window.videojs.Flash || {}; window.videojs.Flash = window.videojs.Flash || {};
window.videojs.Flash.onReady = Flash.onReady; window.videojs.Flash.onReady = Flash.onReady;
window.videojs.Flash.onEvent = Flash.onEvent; window.videojs.Flash.onEvent = Flash.onEvent;
window.videojs.Flash.onError = Flash.onError; window.videojs.Flash.onError = Flash.onError;
this.el_ = Flash.embed(options['swf'], placeHolder, flashVars, params, attributes); this.el_ = Flash.embed(options.swf, flashVars, params, attributes);
this.el_.tech = this;
} }
play() { play() {
@ -120,7 +95,7 @@ class Flash extends Tech {
src(src) { src(src) {
if (src === undefined) { if (src === undefined) {
return this['currentSrc'](); return this.currentSrc();
} }
// Setting src through `src` not `setSrc` will be deprecated // Setting src through `src` not `setSrc` will be deprecated
@ -134,7 +109,7 @@ class Flash extends Tech {
// Currently the SWF doesn't autoplay if you load a source later. // Currently the SWF doesn't autoplay if you load a source later.
// e.g. Load player w/ no source, wait 2s, set src. // e.g. Load player w/ no source, wait 2s, set src.
if (this.player_.autoplay()) { if (this.autoplay()) {
var tech = this; var tech = this;
this.setTimeout(function(){ tech.play(); }, 0); this.setTimeout(function(){ tech.play(); }, 0);
} }
@ -290,17 +265,13 @@ Flash.formats = {
Flash.onReady = function(currSwf){ Flash.onReady = function(currSwf){
let el = Lib.el(currSwf); let el = Lib.el(currSwf);
let tech = el && el.tech;
// get player from the player div property // if there is no el then the tech has been disposed
const player = el && el.parentNode && el.parentNode['player'];
// if there is no el or player then the tech has been disposed
// and the tech element was removed from the player div // and the tech element was removed from the player div
if (player) { if (tech && tech.el()) {
// reference player on tech element
el['player'] = player;
// check that the flash object is really ready // check that the flash object is really ready
Flash['checkReady'](player.tech); Flash.checkReady(tech);
} }
}; };
@ -326,21 +297,21 @@ Flash.checkReady = function(tech){
// Trigger events from the swf on the player // Trigger events from the swf on the player
Flash.onEvent = function(swfID, eventName){ Flash.onEvent = function(swfID, eventName){
let player = Lib.el(swfID)['player']; let tech = Lib.el(swfID).tech;
player.trigger(eventName); tech.trigger(eventName);
}; };
// Log errors from the swf // Log errors from the swf
Flash.onError = function(swfID, err){ Flash.onError = function(swfID, err){
const player = Lib.el(swfID)['player']; const tech = Lib.el(swfID).tech;
const msg = 'FLASH: '+err; const msg = 'FLASH: '+err;
if (err == 'srcnotfound') { if (err === 'srcnotfound') {
player.error({ code: 4, message: msg }); tech.trigger('error', { code: 4, message: msg });
// errors we haven't categorized into the media errors // errors we haven't categorized into the media errors
} else { } else {
player.error(msg); tech.trigger('error', msg);
} }
}; };
@ -364,15 +335,12 @@ Flash.version = function(){
}; };
// Flash embedding method. Only used in non-iframe mode // Flash embedding method. Only used in non-iframe mode
Flash.embed = function(swf, placeHolder, flashVars, params, attributes){ Flash.embed = function(swf, flashVars, params, attributes){
const code = Flash.getEmbedCode(swf, flashVars, params, attributes); const code = Flash.getEmbedCode(swf, flashVars, params, attributes);
// Get element by embedding code and retrieving created element // Get element by embedding code and retrieving created element
const obj = Lib.createEl('div', { innerHTML: code }).childNodes[0]; const obj = Lib.createEl('div', { innerHTML: code }).childNodes[0];
const par = placeHolder.parentNode;
placeHolder.parentNode.replaceChild(obj, placeHolder);
return obj; return obj;
}; };

View File

@ -17,18 +17,16 @@ import document from 'global/document';
*/ */
class Html5 extends Tech { class Html5 extends Tech {
constructor(player, options, ready){ constructor(options, ready){
super(player, options, ready); super(options, ready);
this.setupTriggers(); const source = options.source;
const source = options['source'];
// Set the source if one is provided // Set the source if one is provided
// 1) Check if the source is new (if not, we want to keep the original so playback isn't interrupted) // 1) Check if the source is new (if not, we want to keep the original so playback isn't interrupted)
// 2) Check to see if the network state of the tag was failed at init, and if so, reset the source // 2) Check to see if the network state of the tag was failed at init, and if so, reset the source
// anyway so the error gets fired. // anyway so the error gets fired.
if (source && (this.el_.currentSrc !== source.src || (player.tag && player.tag.initNetworkState_ === 3))) { if (source && (this.el_.currentSrc !== source.src || (options.tag && options.tag.initNetworkState_ === 3))) {
this.setSource(source); this.setSource(source);
} }
@ -42,14 +40,14 @@ class Html5 extends Tech {
let node = nodes[nodesLength]; let node = nodes[nodesLength];
let nodeName = node.nodeName.toLowerCase(); let nodeName = node.nodeName.toLowerCase();
if (nodeName === 'track') { if (nodeName === 'track') {
if (!this['featuresNativeTextTracks']) { if (!this.featuresNativeTextTracks) {
// Empty video tag tracks so the built-in player doesn't use them also. // Empty video tag tracks so the built-in player doesn't use them also.
// This may not be fast enough to stop HTML5 browsers from reading the tags // This may not be fast enough to stop HTML5 browsers from reading the tags
// so we'll need to turn off any default tracks if we're manually doing // so we'll need to turn off any default tracks if we're manually doing
// captions and subtitles. videoElement.textTracks // captions and subtitles. videoElement.textTracks
removeNodes.push(node); removeNodes.push(node);
} else { } else {
this.remoteTextTracks().addTrack_(node['track']); this.remoteTextTracks().addTrack_(node.track);
} }
} }
} }
@ -59,7 +57,7 @@ class Html5 extends Tech {
} }
} }
if (this['featuresNativeTextTracks']) { if (this.featuresNativeTextTracks) {
this.on('loadstart', Lib.bind(this, this.hideCaptions)); this.on('loadstart', Lib.bind(this, this.hideCaptions));
} }
@ -67,21 +65,10 @@ class Html5 extends Tech {
// Our goal should be to get the custom controls on mobile solid everywhere // Our goal should be to get the custom controls on mobile solid everywhere
// so we can remove this all together. Right now this will block custom // so we can remove this all together. Right now this will block custom
// controls on touch enabled laptops like the Chrome Pixel // controls on touch enabled laptops like the Chrome Pixel
if (Lib.TOUCH_ENABLED && player.options()['nativeControlsForTouch'] === true) { if (Lib.TOUCH_ENABLED && options.nativeControlsForTouch === true) {
this.useNativeControls(); this.trigger('usenativecontrols');
} }
// Chrome and Safari both have issues with autoplay.
// In Safari (5.1.1), when we move the video element into the container div, autoplay doesn't work.
// In Chrome (15), if you have autoplay + a poster + no controls, the video gets hidden (but audio plays)
// This fixes both issues. Need to wait for API, so it updates displays correctly
player.ready(function(){
if (this.tag && this.options_['autoplay'] && this.paused()) {
delete this.tag['poster']; // Chrome Fix. Fixed in Chrome v16.
this.play();
}
});
this.triggerReady(); this.triggerReady();
} }
@ -92,8 +79,7 @@ class Html5 extends Tech {
} }
createEl() { createEl() {
let player = this.player_; let el = this.options_.tag;
let el = player.tag;
// Check if this browser supports moving the element into the box. // Check if this browser supports moving the element into the box.
// On the iPhone video will break if you move the element, // On the iPhone video will break if you move the element,
@ -105,29 +91,27 @@ class Html5 extends Tech {
const clone = el.cloneNode(false); const clone = el.cloneNode(false);
Html5.disposeMediaElement(el); Html5.disposeMediaElement(el);
el = clone; el = clone;
player.tag = null;
} else { } else {
el = Lib.createEl('video'); el = Lib.createEl('video');
// determine if native controls should be used // determine if native controls should be used
let attributes = VjsUtil.mergeOptions({}, player.tagAttributes); let tagAttributes = this.options_.tag && Lib.getElementAttributes(this.options_.tag);
if (!Lib.TOUCH_ENABLED || player.options()['nativeControlsForTouch'] !== true) { let attributes = VjsUtil.mergeOptions({}, tagAttributes);
if (!Lib.TOUCH_ENABLED || this.options_.nativeControlsForTouch !== true) {
delete attributes.controls; delete attributes.controls;
} }
Lib.setElementAttributes(el, Lib.setElementAttributes(el,
Lib.obj.merge(attributes, { Lib.obj.merge(attributes, {
id: player.id() + '_html5_api', id: this.options_.playerId + '_html5_api',
class: 'vjs-tech' class: 'vjs-tech'
}) })
); );
} }
// associate the player with the new tag
el['player'] = player;
if (player.options_.tracks) { if (this.options_.tracks) {
for (let i = 0; i < player.options_.tracks.length; i++) { for (let i = 0; i < this.options_.tracks.length; i++) {
const track = player.options_.tracks[i]; const track = this.options_.tracks[i];
let trackEl = document.createElement('track'); let trackEl = document.createElement('track');
trackEl.kind = track.kind; trackEl.kind = track.kind;
trackEl.label = track.label; trackEl.label = track.label;
@ -139,8 +123,6 @@ class Html5 extends Tech {
el.appendChild(trackEl); el.appendChild(trackEl);
} }
} }
Lib.insertFirst(el, player.el());
} }
// Update specific tag settings, in case they were overridden // Update specific tag settings, in case they were overridden
@ -148,8 +130,8 @@ class Html5 extends Tech {
for (let i = settingsAttrs.length - 1; i >= 0; i--) { for (let i = settingsAttrs.length - 1; i >= 0; i--) {
const attr = settingsAttrs[i]; const attr = settingsAttrs[i];
let overwriteAttrs = {}; let overwriteAttrs = {};
if (typeof player.options_[attr] !== 'undefined') { if (typeof this.options_[attr] !== 'undefined') {
overwriteAttrs[attr] = player.options_[attr]; overwriteAttrs[attr] = this.options_[attr];
} }
Lib.setElementAttributes(el, overwriteAttrs); Lib.setElementAttributes(el, overwriteAttrs);
} }
@ -176,62 +158,6 @@ class Html5 extends Tech {
} }
} }
// Make video events trigger player events
// May seem verbose here, but makes other APIs possible.
// Triggers removed using this.off when disposed
setupTriggers() {
for (let i = Html5.Events.length - 1; i >= 0; i--) {
this.on(Html5.Events[i], this.eventHandler);
}
}
eventHandler(evt) {
// In the case of an error on the video element, set the error prop
// on the player and let the player handle triggering the event. On
// some platforms, error events fire that do not cause the error
// property on the video element to be set. See #1465 for an example.
if (evt.type == 'error' && this.error()) {
this.player().error(this.error().code);
// 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);
}
}
useNativeControls() {
let tech = this;
let player = this.player();
// If the player controls are enabled turn on the native controls
tech.setControls(player.controls());
// Update the native controls when player controls state is updated
let controlsOn = function(){
tech.setControls(true);
};
let controlsOff = function(){
tech.setControls(false);
};
player.on('controlsenabled', controlsOn);
player.on('controlsdisabled', controlsOff);
// Clean up when not using native controls anymore
let cleanUp = function(){
player.off('controlsenabled', controlsOn);
player.off('controlsdisabled', controlsOff);
};
tech.on('dispose', cleanUp);
player.on('usingcustomcontrols', cleanUp);
// Update the state of the player to using native controls
player.usingNativeControls(true);
}
play() { this.el_.play(); } play() { this.el_.play(); }
pause() { this.el_.pause(); } pause() { this.el_.pause(); }
paused() { return this.el_.paused; } paused() { return this.el_.paused; }
@ -260,7 +186,7 @@ class Html5 extends Tech {
height() { return this.el_.offsetHeight; } height() { return this.el_.offsetHeight; }
supportsFullScreen() { supportsFullScreen() {
if (typeof this.el_.webkitEnterFullScreen == 'function') { if (typeof this.el_.webkitEnterFullScreen === 'function') {
// Seems to be broken in Chromium/Chrome && Safari in Leopard // Seems to be broken in Chromium/Chrome && Safari in Leopard
if (/Android/.test(Lib.USER_AGENT) || !/Chrome|Mac OS X 10.5/.test(Lib.USER_AGENT)) { if (/Android/.test(Lib.USER_AGENT) || !/Chrome|Mac OS X 10.5/.test(Lib.USER_AGENT)) {
@ -275,14 +201,11 @@ class Html5 extends Tech {
if ('webkitDisplayingFullscreen' in video) { if ('webkitDisplayingFullscreen' in video) {
this.one('webkitbeginfullscreen', function() { this.one('webkitbeginfullscreen', function() {
this.player_.isFullscreen(true);
this.one('webkitendfullscreen', function() { this.one('webkitendfullscreen', function() {
this.player_.isFullscreen(false); this.trigger('fullscreenchange');
this.player_.trigger('fullscreenchange');
}); });
this.player_.trigger('fullscreenchange'); this.trigger('fullscreenchange');
}); });
} }
@ -642,14 +565,9 @@ Html5.unpatchCanPlayType = function() {
// by default, patch the video element // by default, patch the video element
Html5.patchCanPlayType(); Html5.patchCanPlayType();
// List of all HTML5 events (various uses).
Html5.Events = 'loadstart,suspend,abort,error,emptied,stalled,loadedmetadata,loadeddata,canplay,canplaythrough,playing,waiting,seeking,seeked,ended,durationchange,timeupdate,progress,play,pause,ratechange,volumechange'.split(',');
Html5.disposeMediaElement = function(el){ Html5.disposeMediaElement = function(el){
if (!el) { return; } if (!el) { return; }
el['player'] = null;
if (el.parentNode) { if (el.parentNode) {
el.parentNode.removeChild(el); el.parentNode.removeChild(el);
} }

View File

@ -18,29 +18,32 @@ import document from 'global/document';
*/ */
class Tech extends Component { class Tech extends Component {
constructor(player, options={}, ready=function(){}){ constructor(options={}, ready=function(){}){
options = options || {};
// we don't want the tech to report user activity automatically. // we don't want the tech to report user activity automatically.
// This is done manually in addControlsListeners // This is done manually in addControlsListeners
options.reportTouchActivity = false; options.reportTouchActivity = false;
super(player, options, ready); super(null, options, ready);
this.textTracks_ = options.textTracks;
// Manually track progress in cases where the browser/flash player doesn't report it. // Manually track progress in cases where the browser/flash player doesn't report it.
if (!this['featuresProgressEvents']) { if (!this.featuresProgressEvents) {
this.manualProgressOn(); this.manualProgressOn();
} }
// Manually track timeupdates in cases where the browser/flash player doesn't report it. // Manually track timeupdates in cases where the browser/flash player doesn't report it.
if (!this['featuresTimeupdateEvents']) { if (!this.featuresTimeupdateEvents) {
this.manualTimeUpdatesOn(); this.manualTimeUpdatesOn();
} }
this.initControlsListeners(); this.initControlsListeners();
if (options['nativeCaptions'] === false || options['nativeTextTracks'] === false) { if (options.nativeCaptions === false || options.nativeTextTracks === false) {
this['featuresNativeTextTracks'] = false; this.featuresNativeTextTracks = false;
} }
if (!this['featuresNativeTextTracks']) { if (!this.featuresNativeTextTracks) {
this.emulateTextTracks(); this.emulateTextTracks();
} }
@ -68,20 +71,6 @@ class Tech extends Component {
* any controls will still keep the user active * any controls will still keep the user active
*/ */
initControlsListeners() { initControlsListeners() {
let player = this.player();
let activateControls = function(){
if (player.controls() && !player.usingNativeControls()) {
this.addControlsListeners();
}
};
// Set up event listeners once the tech is ready and has an element to apply
// listeners to
this.ready(activateControls);
this.on(player, 'controlsenabled', activateControls);
this.on(player, 'controlsdisabled', this.removeControlsListeners);
// if we're loading the playback object after it has started loading or playing the // if we're loading the playback object after it has started loading or playing the
// video (often with autoplay on) then the loadstart event has already fired and we // video (often with autoplay on) then the loadstart event has already fired and we
// need to fire it manually because many things rely on it. // need to fire it manually because many things rely on it.
@ -89,95 +78,18 @@ class Tech extends Component {
// that may also have fired. // that may also have fired.
this.ready(function(){ this.ready(function(){
if (this.networkState && this.networkState() > 0) { if (this.networkState && this.networkState() > 0) {
this.player().trigger('loadstart'); this.trigger('loadstart');
} }
}); });
} }
addControlsListeners() {
let userWasActive;
// Some browsers (Chrome & IE) don't trigger a click on a flash swf, but do
// trigger mousedown/up.
// http://stackoverflow.com/questions/1444562/javascript-onclick-event-over-flash-object
// Any touch events are set to block the mousedown event from happening
this.on('mousedown', this.handleClick);
// If the controls were hidden we don't want that to change without a tap event
// so we'll check if the controls were already showing before reporting user
// activity
this.on('touchstart', function(event) {
userWasActive = this.player_.userActive();
});
this.on('touchmove', function(event) {
if (userWasActive){
this.player().reportUserActivity();
}
});
this.on('touchend', function(event) {
// Stop the mouse events from also happening
event.preventDefault();
});
// Turn on component tap events
this.emitTapEvents();
// The tap listener needs to come after the touchend listener because the tap
// listener cancels out any reportedUserActivity when setting userActive(false)
this.on('tap', this.handleTap);
}
/**
* Remove the listeners used for click and tap controls. This is needed for
* toggling to controls disabled, where a tap/touch should do nothing.
*/
removeControlsListeners() {
// We don't want to just use `this.off()` because there might be other needed
// listeners added by techs that extend this.
this.off('tap');
this.off('touchstart');
this.off('touchmove');
this.off('touchleave');
this.off('touchcancel');
this.off('touchend');
this.off('click');
this.off('mousedown');
}
/**
* Handle a click on the media element. By default will play/pause the media.
*/
handleClick(event) {
// We're using mousedown to detect clicks thanks to Flash, but mousedown
// will also be triggered with right-clicks, so we need to prevent that
if (event.button !== 0) return;
// When controls are disabled a click should not toggle playback because
// the click is considered a control
if (this.player().controls()) {
if (this.player().paused()) {
this.player().play();
} else {
this.player().pause();
}
}
}
/**
* Handle a tap on the media element. By default it will toggle the user
* activity state, which hides and shows the controls.
*/
handleTap() {
this.player().userActive(!this.player().userActive());
}
/* Fallbacks for unsupported event types /* Fallbacks for unsupported event types
================================================================================ */ ================================================================================ */
// Manually trigger progress events based on changes to the buffered amount // Manually trigger progress events based on changes to the buffered amount
// Many flash players and older HTML5 browsers don't send progress or progress-like events // Many flash players and older HTML5 browsers don't send progress or progress-like events
manualProgressOn() { manualProgressOn() {
this.on('durationchange', this.onDurationChange);
this.manualProgress = true; this.manualProgress = true;
// Trigger progress watching when a source begins loading // Trigger progress watching when a source begins loading
@ -187,16 +99,18 @@ class Tech extends Component {
manualProgressOff() { manualProgressOff() {
this.manualProgress = false; this.manualProgress = false;
this.stopTrackingProgress(); this.stopTrackingProgress();
this.off('durationchange', this.onDurationChange);
} }
trackProgress() { trackProgress() {
this.progressInterval = this.setInterval(function(){ this.progressInterval = this.setInterval(Lib.bind(this, function(){
// Don't trigger unless buffered amount is greater than last time // Don't trigger unless buffered amount is greater than last time
let bufferedPercent = this.player().bufferedPercent(); let bufferedPercent = this.bufferedPercent();
if (this.bufferedPercent_ != bufferedPercent) { if (this.bufferedPercent_ !== bufferedPercent) {
this.player().trigger('progress'); this.trigger('progress');
} }
this.bufferedPercent_ = bufferedPercent; this.bufferedPercent_ = bufferedPercent;
@ -204,7 +118,40 @@ class Tech extends Component {
if (bufferedPercent === 1) { if (bufferedPercent === 1) {
this.stopTrackingProgress(); this.stopTrackingProgress();
} }
}, 500); }), 500);
}
onDurationChange() {
this.duration_ = this.duration();
}
bufferedPercent() {
let bufferedDuration = 0,
start, end;
if (!this.duration_) {
return 0;
}
let buffered = this.buffered();
if (!buffered || !buffered.length) {
buffered = Lib.createTimeRange(0,0);
}
for (var i=0; i<buffered.length; i++){
start = buffered.start(i);
end = buffered.end(i);
// buffered end can be bigger than duration by a very small fraction
if (end > this.duration_) {
end = this.duration_;
}
bufferedDuration += end - start;
}
return bufferedDuration / this.duration_;
} }
stopTrackingProgress() { stopTrackingProgress() {
@ -213,36 +160,38 @@ class Tech extends Component {
/*! Time Tracking -------------------------------------------------------------- */ /*! Time Tracking -------------------------------------------------------------- */
manualTimeUpdatesOn() { manualTimeUpdatesOn() {
let player = this.player_;
this.manualTimeUpdates = true; this.manualTimeUpdates = true;
this.on(player, 'play', this.trackCurrentTime); this.on('play', this.trackCurrentTime);
this.on(player, 'pause', this.stopTrackingCurrentTime); this.on('pause', this.stopTrackingCurrentTime);
// timeupdate is also called by .currentTime whenever current time is set // timeupdate is also called by .currentTime whenever current time is set
// Watch for native timeupdate event // Watch for native timeupdate event only
this.one('timeupdate', function(){ var onTimeUpdate = function(e){
if (e.manuallyTriggered) return;
this.off('timeupdate', onTimeUpdate);
// Update known progress support for this playback technology // Update known progress support for this playback technology
this['featuresTimeupdateEvents'] = true; this.featuresTimeupdateEvents = true;
// Turn off manual progress tracking // Turn off manual progress tracking
this.manualTimeUpdatesOff(); this.manualTimeUpdatesOff();
}); };
this.on('timeupdate', onTimeUpdate);
} }
manualTimeUpdatesOff() { manualTimeUpdatesOff() {
let player = this.player_;
this.manualTimeUpdates = false; this.manualTimeUpdates = false;
this.stopTrackingCurrentTime(); this.stopTrackingCurrentTime();
this.off(player, 'play', this.trackCurrentTime); this.off('play', this.trackCurrentTime);
this.off(player, 'pause', this.stopTrackingCurrentTime); this.off('pause', this.stopTrackingCurrentTime);
} }
trackCurrentTime() { trackCurrentTime() {
if (this.currentTimeInterval) { this.stopTrackingCurrentTime(); } if (this.currentTimeInterval) { this.stopTrackingCurrentTime(); }
this.currentTimeInterval = this.setInterval(function(){ this.currentTimeInterval = this.setInterval(function(){
this.player().trigger('timeupdate'); this.trigger({ type: 'timeupdate', target: this, manuallyTriggered: true });
}, 250); // 42 = 24 fps // 250 is what Webkit uses // FF uses 15 }, 250); // 42 = 24 fps // 250 is what Webkit uses // FF uses 15
} }
@ -252,7 +201,7 @@ class Tech extends Component {
// #1002 - if the video ends right before the next timeupdate would happen, // #1002 - if the video ends right before the next timeupdate would happen,
// the progress bar won't make it all the way to the end // the progress bar won't make it all the way to the end
this.player().trigger('timeupdate'); this.trigger({ type: 'timeupdate', target: this, manuallyTriggered: true });
} }
dispose() { dispose() {
@ -266,21 +215,13 @@ class Tech extends Component {
setCurrentTime() { setCurrentTime() {
// improve the accuracy of manual timeupdates // improve the accuracy of manual timeupdates
if (this.manualTimeUpdates) { this.player().trigger('timeupdate'); } if (this.manualTimeUpdates) { this.trigger({ type: 'timeupdate', target: this, manuallyTriggered: true }); }
} }
// TODO: Consider looking at moving this into the text track display directly
// https://github.com/videojs/video.js/issues/1863
initTextTrackListeners() { initTextTrackListeners() {
let player = this.player_; let textTrackListChanges = Lib.bind(this, function() {
this.trigger('texttrackchange');
let textTrackListChanges = function() { });
let textTrackDisplay = player.getChild('textTrackDisplay');
if (textTrackDisplay) {
textTrackDisplay.updateDisplay();
}
};
let tracks = this.textTracks(); let tracks = this.textTracks();
@ -296,12 +237,10 @@ class Tech extends Component {
} }
emulateTextTracks() { emulateTextTracks() {
let player = this.player_; if (!window['WebVTT'] && this.el().parentNode != null) {
if (!window['WebVTT']) {
let script = document.createElement('script'); let script = document.createElement('script');
script.src = player.options()['vtt.js'] || '../node_modules/vtt.js/dist/vtt.js'; script.src = this.options_['vtt.js'] || '../node_modules/vtt.js/dist/vtt.js';
player.el().appendChild(script); this.el().parentNode.appendChild(script);
window['WebVTT'] = true; window['WebVTT'] = true;
} }
@ -311,15 +250,17 @@ class Tech extends Component {
} }
let textTracksChanges = function() { let textTracksChanges = function() {
let textTrackDisplay = player.getChild('textTrackDisplay'); let updateDisplay = Lib.bind(this, function() {
this.trigger('texttrackchange');
});
textTrackDisplay.updateDisplay(); this.trigger('texttrackchange');
for (let i = 0; i < this.length; i++) { for (let i = 0; i < this.length; i++) {
let track = this[i]; let track = this[i];
track.removeEventListener('cuechange', Lib.bind(textTrackDisplay, textTrackDisplay.updateDisplay)); track.removeEventListener('cuechange', updateDisplay);
if (track.mode === 'showing') { if (track.mode === 'showing') {
track.addEventListener('cuechange', Lib.bind(textTrackDisplay, textTrackDisplay.updateDisplay)); track.addEventListener('cuechange', updateDisplay);
} }
} }
}; };
@ -338,13 +279,13 @@ class Tech extends Component {
*/ */
textTracks() { textTracks() {
this.player_.textTracks_ = this.player_.textTracks_ || new TextTrackList(); this.textTracks_ = this.textTracks_ || new TextTrackList();
return this.player_.textTracks_; return this.textTracks_;
} }
remoteTextTracks() { remoteTextTracks() {
this.player_.remoteTextTracks_ = this.player_.remoteTextTracks_ || new TextTrackList(); this.remoteTextTracks_ = this.remoteTextTracks_ || new TextTrackList();
return this.player_.remoteTextTracks_; return this.remoteTextTracks_;
} }
addTextTrack(kind, label, language) { addTextTrack(kind, label, language) {
@ -356,7 +297,7 @@ class Tech extends Component {
} }
addRemoteTextTrack(options) { addRemoteTextTrack(options) {
let track = createTrackHelper(this, options['kind'], options['label'], options['language'], options); let track = createTrackHelper(this, options.kind, options.label, options.language, options);
this.remoteTextTracks().addTrack_(track); this.remoteTextTracks().addTrack_(track);
return { return {
track: track track: track
@ -388,14 +329,15 @@ Tech.prototype.textTracks_;
var createTrackHelper = function(self, kind, label, language, options={}) { var createTrackHelper = function(self, kind, label, language, options={}) {
let tracks = self.textTracks(); let tracks = self.textTracks();
options['kind'] = kind; options.kind = kind;
if (label) { if (label) {
options['label'] = label; options.label = label;
} }
if (language) { if (language) {
options['language'] = language; options.language = language;
} }
options['player'] = self.player_; options.tech = self;
let track = new TextTrack(options); let track = new TextTrack(options);
tracks.addTrack_(track); tracks.addTrack_(track);
@ -403,18 +345,18 @@ var createTrackHelper = function(self, kind, label, language, options={}) {
return track; return track;
}; };
Tech.prototype['featuresVolumeControl'] = true; Tech.prototype.featuresVolumeControl = true;
// Resizing plugins using request fullscreen reloads the plugin // Resizing plugins using request fullscreen reloads the plugin
Tech.prototype['featuresFullscreenResize'] = false; Tech.prototype.featuresFullscreenResize = false;
Tech.prototype['featuresPlaybackRate'] = false; Tech.prototype.featuresPlaybackRate = false;
// Optional events that we can manually mimic with timers // Optional events that we can manually mimic with timers
// currently not triggered by video-js-swf // currently not triggered by video-js-swf
Tech.prototype['featuresProgressEvents'] = false; Tech.prototype.featuresProgressEvents = false;
Tech.prototype['featuresTimeupdateEvents'] = false; Tech.prototype.featuresTimeupdateEvents = false;
Tech.prototype['featuresNativeTextTracks'] = false; Tech.prototype.featuresNativeTextTracks = false;
/** /**
* A functional mixin for techs that want to use the Source Handler pattern. * A functional mixin for techs that want to use the Source Handler pattern.

View File

@ -32,6 +32,7 @@ class TextTrackDisplay extends Component {
super(player, options, ready); super(player, options, ready);
player.on('loadstart', Lib.bind(this, this.toggleDisplay)); player.on('loadstart', Lib.bind(this, this.toggleDisplay));
player.on('texttrackchange', Lib.bind(this, this.toggleDisplay));
// This used to be called during player init, but was causing an error // This used to be called during player init, but was causing an error
// if a track should show by default and the display hadn't loaded yet. // if a track should show by default and the display hadn't loaded yet.

View File

@ -29,8 +29,8 @@ import XHR from '../xhr.js';
* }; * };
*/ */
let TextTrack = function(options={}) { let TextTrack = function(options={}) {
if (!options['player']) { if (!options.tech) {
throw new Error('A player was not provided.'); throw new Error('A tech was not provided.');
} }
let tt = this; let tt = this;
@ -42,7 +42,7 @@ let TextTrack = function(options={}) {
} }
} }
tt.player_ = options['player']; tt.tech_ = options.tech;
let mode = TextTrackEnum.TextTrackMode[options['mode']] || 'disabled'; let mode = TextTrackEnum.TextTrackMode[options['mode']] || 'disabled';
let kind = TextTrackEnum.TextTrackKind[options['kind']] || 'subtitles'; let kind = TextTrackEnum.TextTrackKind[options['kind']] || 'subtitles';
@ -69,7 +69,7 @@ let TextTrack = function(options={}) {
} }
}); });
if (mode !== 'disabled') { if (mode !== 'disabled') {
tt.player_.on('timeupdate', timeupdateHandler); tt.tech_.on('timeupdate', timeupdateHandler);
} }
Object.defineProperty(tt, 'kind', { Object.defineProperty(tt, 'kind', {
@ -110,7 +110,7 @@ let TextTrack = function(options={}) {
} }
mode = newMode; mode = newMode;
if (mode === 'showing') { if (mode === 'showing') {
this.player_.on('timeupdate', timeupdateHandler); this.tech_.on('timeupdate', timeupdateHandler);
} }
this.trigger('modechange'); this.trigger('modechange');
} }
@ -137,7 +137,7 @@ let TextTrack = function(options={}) {
return activeCues; // nothing to do return activeCues; // nothing to do
} }
let ct = this.player_.currentTime(); let ct = this.tech_.currentTime();
let active = []; let active = [];
for (let i = 0, l = this['cues'].length; i < l; i++) { for (let i = 0, l = this['cues'].length; i < l; i++) {
@ -191,7 +191,7 @@ TextTrack.prototype.allowedEvents_ = {
}; };
TextTrack.prototype.addCue = function(cue) { TextTrack.prototype.addCue = function(cue) {
let tracks = this.player_.textTracks(); let tracks = this.tech_.textTracks();
if (tracks) { if (tracks) {
for (let i = 0; i < tracks.length; i++) { for (let i = 0; i < tracks.length; i++) {

View File

@ -102,7 +102,7 @@ var xhr = function(options, callback){
// XMLHTTPRequest // XMLHTTPRequest
} else { } else {
const fileUrl = (urlInfo.protocol == 'file:' || winLoc.protocol == 'file:'); const fileUrl = (urlInfo.protocol === 'file:' || winLoc.protocol === 'file:');
request.onreadystatechange = function() { request.onreadystatechange = function() {
if (request.readyState === 4) { if (request.readyState === 4) {

View File

@ -92,6 +92,8 @@ module.exports = function(config) {
captureTimeout: 60000, captureTimeout: 60000,
browserNoActivityTimeout: 60000,
sauceLabs: { sauceLabs: {
startConnect: true, startConnect: true,
tunnelIdentifier: process.env.TRAVIS_JOB_NUMBER, tunnelIdentifier: process.env.TRAVIS_JOB_NUMBER,

View File

@ -146,8 +146,13 @@ test('ready triggering before and after disposing the tech', function() {
fixtureDiv.appendChild(playerDiv); fixtureDiv.appendChild(playerDiv);
techEl.id = 'foo1234'; techEl.id = 'foo1234';
techEl.tech = {
el() { return techEl; }
};
playerDiv['player'] = { playerDiv['player'] = {
tech: {} tech: techEl.tech
}; };
Flash['onReady'](techEl.id); Flash['onReady'](techEl.id);

View File

@ -24,7 +24,7 @@ q.module('HTML5', {
addChild: function(){}, addChild: function(){},
trigger: function(){} trigger: function(){}
}; };
tech = new Html5(player, {}); tech = new Html5({});
}, },
'teardown': function() { 'teardown': function() {
tech.dispose(); tech.dispose();
@ -51,13 +51,6 @@ test('should detect whether the volume can be changed', function(){
Lib.TEST_VID = testVid; Lib.TEST_VID = testVid;
}); });
test('should re-link the player if the tech is moved', function(){
Html5.movingMediaElementInDOM = false;
tech.createEl();
strictEqual(player, tech.el()['player']);
});
test('test playbackRate', function() { test('test playbackRate', function() {
var playbackRate; var playbackRate;

View File

@ -26,48 +26,28 @@ q.module('Media Tech', {
}); });
test('should synthesize timeupdate events by default', function() { test('should synthesize timeupdate events by default', function() {
var timeupdates = 0, playHandler, i, tech; var timeupdates = 0, tech;
tech = new Tech({
id: this.noop, tech = new Tech();
on: function(event, handler) {
if (event === 'play') {
playHandler = handler;
}
},
trigger: function(event) {
if (event === 'timeupdate') {
timeupdates++;
}
}
});
playHandler.call(tech);
tech.on('timeupdate', function() { tech.on('timeupdate', function() {
timeupdates++; timeupdates++;
}); });
tech.trigger('play');
this.clock.tick(250); this.clock.tick(250);
equal(timeupdates, 1, 'triggered one timeupdate'); equal(timeupdates, 1, 'triggered at least one timeupdate');
}); });
test('stops timeupdates if the tech produces them natively', function() { test('stops timeupdates if the tech produces them natively', function() {
var timeupdates = 0, tech, playHandler, expected; var timeupdates = 0, tech, expected;
tech = new Tech({ tech = new Tech();
id: this.noop, tech.on('timeupdate', function() {
off: this.noop,
on: function(event, handler) {
if (event === 'play') {
playHandler = handler;
}
},
bufferedPercent: this.noop,
trigger: function(event) {
if (event === 'timeupdate') {
timeupdates++; timeupdates++;
}
}
}); });
playHandler.call(tech); tech.trigger('play');
// simulate a native timeupdate event // simulate a native timeupdate event
tech.trigger('timeupdate'); tech.trigger('timeupdate');
@ -77,51 +57,29 @@ test('stops timeupdates if the tech produces them natively', function() {
}); });
test('stops manual timeupdates while paused', function() { test('stops manual timeupdates while paused', function() {
var timeupdates = 0, tech, playHandler, pauseHandler, expected; var timeupdates = 0, tech, expected;
tech = new Tech({ tech = new Tech();
id: this.noop, tech.on('timeupdate', function() {
on: function(event, handler) {
if (event === 'play') {
playHandler = handler;
} else if (event === 'pause') {
pauseHandler = handler;
}
},
bufferedPercent: this.noop,
trigger: function(event) {
if (event === 'timeupdate') {
timeupdates++; timeupdates++;
}
}
}); });
playHandler.call(tech);
tech.trigger('play');
this.clock.tick(10 * 250); this.clock.tick(10 * 250);
ok(timeupdates > 0, 'timeupdates fire during playback'); ok(timeupdates > 0, 'timeupdates fire during playback');
pauseHandler.call(tech); tech.trigger('pause');
timeupdates = 0; timeupdates = 0;
this.clock.tick(10 * 250); this.clock.tick(10 * 250);
equal(timeupdates, 0, 'timeupdates do not fire when paused'); equal(timeupdates, 0, 'timeupdates do not fire when paused');
playHandler.call(tech); tech.trigger('play');
this.clock.tick(10 * 250); this.clock.tick(10 * 250);
ok(timeupdates > 0, 'timeupdates fire when playback resumes'); ok(timeupdates > 0, 'timeupdates fire when playback resumes');
}); });
test('should synthesize progress events by default', function() { test('should synthesize progress events by default', function() {
var progresses = 0, tech; var progresses = 0, tech;
tech = new Tech({ tech = new Tech();
id: this.noop,
on: this.noop,
bufferedPercent: function() {
return 0;
},
trigger: function(event) {
if (event === 'progress') {
progresses++;
}
}
});
tech.on('progress', function() { tech.on('progress', function() {
progresses++; progresses++;
}); });
@ -131,12 +89,7 @@ test('should synthesize progress events by default', function() {
}); });
test('dispose() should stop time tracking', function() { test('dispose() should stop time tracking', function() {
var tech = new Tech({ var tech = new Tech();
id: this.noop,
on: this.noop,
off: this.noop,
trigger: this.noop
});
tech.dispose(); tech.dispose();
// progress and timeupdate events will throw exceptions after the // progress and timeupdate events will throw exceptions after the
@ -150,10 +103,6 @@ test('dispose() should stop time tracking', function() {
}); });
test('should add the source hanlder interface to a tech', function(){ test('should add the source hanlder interface to a tech', function(){
var mockPlayer = {
off: this.noop,
trigger: this.noop
};
var sourceA = { src: 'foo.mp4', type: 'video/mp4' }; var sourceA = { src: 'foo.mp4', type: 'video/mp4' };
var sourceB = { src: 'no-support', type: 'no-support' }; var sourceB = { src: 'no-support', type: 'no-support' };
@ -168,7 +117,7 @@ test('should add the source hanlder interface to a tech', function(){
ok(MyTech.selectSourceHandler, 'added a selectSourceHandler function to the Tech'); ok(MyTech.selectSourceHandler, 'added a selectSourceHandler function to the Tech');
// Create an instance of Tech // Create an instance of Tech
var tech = new MyTech(mockPlayer); var tech = new MyTech();
// Check for the expected instance methods // Check for the expected instance methods
ok(tech.setSource, 'added a setSource function to the tech instance'); ok(tech.setSource, 'added a setSource function to the tech instance');
@ -233,17 +182,12 @@ test('should add the source hanlder interface to a tech', function(){
}); });
test('should handle unsupported sources with the source hanlder API', function(){ test('should handle unsupported sources with the source hanlder API', function(){
var mockPlayer = {
off: this.noop,
trigger: this.noop
};
// Define a new tech class // Define a new tech class
var MyTech = Tech.extend(); var MyTech = Tech.extend();
// Extend Tech with source handlers // Extend Tech with source handlers
Tech.withSourceHandlers(MyTech); Tech.withSourceHandlers(MyTech);
// Create an instance of Tech // Create an instance of Tech
var tech = new MyTech(mockPlayer); var tech = new MyTech();
var usedNative; var usedNative;
MyTech.nativeSourceHandler = { MyTech.nativeSourceHandler = {

View File

@ -10,8 +10,8 @@ import Component from '../../src/js/component.js';
*/ */
class MediaFaker extends Tech { class MediaFaker extends Tech {
constructor(player, options, handleReady){ constructor(options, handleReady){
super(player, options, handleReady); super(options, handleReady);
this.triggerReady(); this.triggerReady();
} }
@ -20,12 +20,10 @@ class MediaFaker extends Tech {
className: 'vjs-tech' className: 'vjs-tech'
}); });
if (this.player().poster()) { /*if (this.player().poster()) {
// transfer the poster image to mimic HTML // transfer the poster image to mimic HTML
el.poster = this.player().poster(); el.poster = this.player().poster();
} }*/
Lib.insertFirst(el, this.player_.el());
return el; return el;
} }
@ -34,6 +32,8 @@ class MediaFaker extends Tech {
poster() { return this.el().poster; } poster() { return this.el().poster; }
setPoster(val) { this.el().poster = val; } setPoster(val) { this.el().poster = val; }
setControls(val) {}
currentTime() { return 0; } currentTime() { return 0; }
seeking() { return false; } seeking() { return false; }
src() { return 'movie.mp4'; } src() { return 'movie.mp4'; }
@ -41,7 +41,7 @@ class MediaFaker extends Tech {
muted() { return false; } muted() { return false; }
pause() { return false; } pause() { return false; }
paused() { return true; } paused() { return true; }
play() { this.player().trigger('play'); } play() { this.trigger('play'); }
supportsFullScreen() { return false; } supportsFullScreen() { return false; }
buffered() { return {}; } buffered() { return {}; }
duration() { return {}; } duration() { return {}; }

View File

@ -233,7 +233,7 @@ test('should hide the poster when play is called', function() {
player.play(); player.play();
equal(player.hasStarted(), true, 'the show poster flag is false after play'); equal(player.hasStarted(), true, 'the show poster flag is false after play');
player.trigger('loadstart'); player.tech.trigger('loadstart');
equal(player.hasStarted(), equal(player.hasStarted(),
false, false,
'the resource selection algorithm sets the show poster flag to true'); 'the resource selection algorithm sets the show poster flag to true');
@ -278,7 +278,7 @@ test('should be able to initialize player twice on the same tag using string ref
}); });
test('should set controls and trigger events', function() { test('should set controls and trigger events', function() {
expect(6); //expect(6);
var player = TestHelpers.makePlayer({ 'controls': false }); var player = TestHelpers.makePlayer({ 'controls': false });
ok(player.controls() === false, 'controls set through options'); ok(player.controls() === false, 'controls set through options');
@ -297,9 +297,9 @@ test('should set controls and trigger events', function() {
ok(true, 'disabled fired once'); ok(true, 'disabled fired once');
}); });
player.controls(false); player.controls(false);
player.controls(true); //player.controls(true);
// Check for unnecessary events // Check for unnecessary events
player.controls(true); //player.controls(true);
player.dispose(); player.dispose();
}); });
@ -441,10 +441,10 @@ test('should not add multiple first play events despite subsequent loads', funct
ok(true, 'First play should fire once.'); ok(true, 'First play should fire once.');
}); });
// Checking to make sure handleLoadStart removes first play listener before adding a new one. // Checking to make sure onLoadStart removes first play listener before adding a new one.
player.trigger('loadstart'); player.tech.trigger('loadstart');
player.trigger('loadstart'); player.tech.trigger('loadstart');
player.trigger('play'); player.tech.trigger('play');
}); });
test('should fire firstplay after resetting the player', function() { test('should fire firstplay after resetting the player', function() {
@ -456,14 +456,14 @@ test('should fire firstplay after resetting the player', function() {
}); });
// init firstplay listeners // init firstplay listeners
player.trigger('loadstart'); player.tech.trigger('loadstart');
player.trigger('play'); player.tech.trigger('play');
ok(fpFired, 'First firstplay fired'); ok(fpFired, 'First firstplay fired');
// reset the player // reset the player
player.trigger('loadstart'); player.tech.trigger('loadstart');
fpFired = false; fpFired = false;
player.trigger('play'); player.tech.trigger('play');
ok(fpFired, 'Second firstplay fired'); ok(fpFired, 'Second firstplay fired');
// the play event can fire before the loadstart event. // the play event can fire before the loadstart event.
@ -471,8 +471,8 @@ test('should fire firstplay after resetting the player', function() {
player.tech.paused = function(){ return false; }; player.tech.paused = function(){ return false; };
fpFired = false; fpFired = false;
// reset the player // reset the player
player.trigger('loadstart'); player.tech.trigger('loadstart');
// player.trigger('play'); // player.tech.trigger('play');
ok(fpFired, 'Third firstplay fired'); ok(fpFired, 'Third firstplay fired');
}); });
@ -481,14 +481,14 @@ test('should remove vjs-has-started class', function(){
var player = TestHelpers.makePlayer({}); var player = TestHelpers.makePlayer({});
player.trigger('loadstart'); player.tech.trigger('loadstart');
player.trigger('play'); player.tech.trigger('play');
ok(player.el().className.indexOf('vjs-has-started') !== -1, 'vjs-has-started class added'); ok(player.el().className.indexOf('vjs-has-started') !== -1, 'vjs-has-started class added');
player.trigger('loadstart'); player.tech.trigger('loadstart');
ok(player.el().className.indexOf('vjs-has-started') === -1, 'vjs-has-started class removed'); ok(player.el().className.indexOf('vjs-has-started') === -1, 'vjs-has-started class removed');
player.trigger('play'); player.tech.trigger('play');
ok(player.el().className.indexOf('vjs-has-started') !== -1, 'vjs-has-started class added again'); ok(player.el().className.indexOf('vjs-has-started') !== -1, 'vjs-has-started class added again');
}); });
@ -497,18 +497,18 @@ test('should add and remove vjs-ended class', function() {
var player = TestHelpers.makePlayer({}); var player = TestHelpers.makePlayer({});
player.trigger('loadstart'); player.tech.trigger('loadstart');
player.trigger('play'); player.tech.trigger('play');
player.trigger('ended'); player.tech.trigger('ended');
ok(player.el().className.indexOf('vjs-ended') !== -1, 'vjs-ended class added'); ok(player.el().className.indexOf('vjs-ended') !== -1, 'vjs-ended class added');
player.trigger('play'); player.tech.trigger('play');
ok(player.el().className.indexOf('vjs-ended') === -1, 'vjs-ended class removed'); ok(player.el().className.indexOf('vjs-ended') === -1, 'vjs-ended class removed');
player.trigger('ended'); player.tech.trigger('ended');
ok(player.el().className.indexOf('vjs-ended') !== -1, 'vjs-ended class re-added'); ok(player.el().className.indexOf('vjs-ended') !== -1, 'vjs-ended class re-added');
player.trigger('loadstart'); player.tech.trigger('loadstart');
ok(player.el().className.indexOf('vjs-ended') === -1, 'vjs-ended class removed'); ok(player.el().className.indexOf('vjs-ended') === -1, 'vjs-ended class removed');
}); });
@ -576,20 +576,18 @@ test('Data attributes on the video element should persist in the new wrapper ele
}); });
test('should restore attributes from the original video tag when creating a new element', function(){ test('should restore attributes from the original video tag when creating a new element', function(){
var player, html5Mock, el; var tag, html5Mock, el;
player = TestHelpers.makePlayer();
html5Mock = { player_: player };
// simulate attributes stored from the original tag // simulate attributes stored from the original tag
player.tagAttributes = { tag = Lib.createEl('video');
'preload': 'auto', tag.setAttribute('preload', 'auto');
'autoplay': true, tag.setAttribute('autoplay', '');
'webkit-playsinline': true tag.setAttribute('webkit-playsinline', '');
};
html5Mock = { options_: { tag: tag } };
// set options that should override tag attributes // set options that should override tag attributes
player.options_['preload'] = 'none'; html5Mock.options_.preload = 'none';
// create the element // create the element
el = Html5.prototype.createEl.call(html5Mock); el = Html5.prototype.createEl.call(html5Mock);
@ -677,7 +675,7 @@ test('pause is called when player ended event is fired and player is not paused'
player.pause = function() { player.pause = function() {
pauses++; pauses++;
}; };
player.trigger('ended'); player.tech.trigger('ended');
equal(pauses, 1, 'pause was called'); equal(pauses, 1, 'pause was called');
}); });
@ -691,7 +689,7 @@ test('pause is not called if the player is paused and ended is fired', function(
player.pause = function() { player.pause = function() {
pauses++; pauses++;
}; };
player.trigger('ended'); player.tech.trigger('ended');
equal(pauses, 0, 'pause was not called when ended fired'); equal(pauses, 0, 'pause was not called when ended fired');
}); });

View File

@ -168,7 +168,7 @@ test('trigger "change" event when "modechange" is fired on a track', function()
test('trigger "change" event when mode changes on a TextTracl', function() { test('trigger "change" event when mode changes on a TextTracl', function() {
var tt = new TextTrack({ var tt = new TextTrack({
player: { tech: {
on: noop on: noop
} }
}), }),

View File

@ -3,7 +3,7 @@ import window from 'global/window';
import TestHelpers from '../test-helpers.js'; import TestHelpers from '../test-helpers.js';
var noop = Function.prototype; var noop = Function.prototype;
var defaultPlayer = { var defaultTech = {
textTracks: noop, textTracks: noop,
on: noop, on: noop,
off: noop, off: noop,
@ -12,12 +12,12 @@ var defaultPlayer = {
q.module('Text Track'); q.module('Text Track');
test('text-track requires a player', function() { test('text-track requires a tech', function() {
window.throws(function() { window.throws(function() {
new TextTrack(); new TextTrack();
}, },
new Error('A player was not provided.'), new Error('A tech was not provided.'),
'a player is required for text track'); 'a tech is required for text track');
}); });
test('can create a TextTrack with various properties', function() { test('can create a TextTrack with various properties', function() {
@ -27,7 +27,7 @@ test('can create a TextTrack with various properties', function() {
id = '1', id = '1',
mode = 'disabled', mode = 'disabled',
tt = new TextTrack({ tt = new TextTrack({
player: defaultPlayer, tech: defaultTech,
kind: kind, kind: kind,
label: label, label: label,
language: language, language: language,
@ -44,7 +44,7 @@ test('can create a TextTrack with various properties', function() {
test('defaults when items not provided', function() { test('defaults when items not provided', function() {
var tt = new TextTrack({ var tt = new TextTrack({
player: defaultPlayer tech: defaultTech
}); });
equal(tt.kind, 'subtitles', 'kind defaulted to subtitles'); equal(tt.kind, 'subtitles', 'kind defaulted to subtitles');
@ -55,7 +55,7 @@ test('defaults when items not provided', function() {
test('kind can only be one of several options, defaults to subtitles', function() { test('kind can only be one of several options, defaults to subtitles', function() {
var tt = new TextTrack({ var tt = new TextTrack({
player: defaultPlayer, tech: defaultTech,
kind: 'foo' kind: 'foo'
}); });
@ -63,35 +63,35 @@ test('kind can only be one of several options, defaults to subtitles', function(
notEqual(tt.kind, 'foo', 'the kind is set to subtitles, not foo'); notEqual(tt.kind, 'foo', 'the kind is set to subtitles, not foo');
tt = new TextTrack({ tt = new TextTrack({
player: defaultPlayer, tech: defaultTech,
kind: 'subtitles' kind: 'subtitles'
}); });
equal(tt.kind, 'subtitles', 'the kind is set to subtitles'); equal(tt.kind, 'subtitles', 'the kind is set to subtitles');
tt = new TextTrack({ tt = new TextTrack({
player: defaultPlayer, tech: defaultTech,
kind: 'captions' kind: 'captions'
}); });
equal(tt.kind, 'captions', 'the kind is set to captions'); equal(tt.kind, 'captions', 'the kind is set to captions');
tt = new TextTrack({ tt = new TextTrack({
player: defaultPlayer, tech: defaultTech,
kind: 'descriptions' kind: 'descriptions'
}); });
equal(tt.kind, 'descriptions', 'the kind is set to descriptions'); equal(tt.kind, 'descriptions', 'the kind is set to descriptions');
tt = new TextTrack({ tt = new TextTrack({
player: defaultPlayer, tech: defaultTech,
kind: 'chapters' kind: 'chapters'
}); });
equal(tt.kind, 'chapters', 'the kind is set to chapters'); equal(tt.kind, 'chapters', 'the kind is set to chapters');
tt = new TextTrack({ tt = new TextTrack({
player: defaultPlayer, tech: defaultTech,
kind: 'metadata' kind: 'metadata'
}); });
@ -100,7 +100,7 @@ test('kind can only be one of several options, defaults to subtitles', function(
test('mode can only be one of several options, defaults to disabled', function() { test('mode can only be one of several options, defaults to disabled', function() {
var tt = new TextTrack({ var tt = new TextTrack({
player: defaultPlayer, tech: defaultTech,
mode: 'foo' mode: 'foo'
}); });
@ -108,21 +108,21 @@ test('mode can only be one of several options, defaults to disabled', function()
notEqual(tt.mode, 'foo', 'the mode is set to disabld, not foo'); notEqual(tt.mode, 'foo', 'the mode is set to disabld, not foo');
tt = new TextTrack({ tt = new TextTrack({
player: defaultPlayer, tech: defaultTech,
mode: 'disabled' mode: 'disabled'
}); });
equal(tt.mode, 'disabled', 'the mode is set to disabled'); equal(tt.mode, 'disabled', 'the mode is set to disabled');
tt = new TextTrack({ tt = new TextTrack({
player: defaultPlayer, tech: defaultTech,
mode: 'hidden' mode: 'hidden'
}); });
equal(tt.mode, 'hidden', 'the mode is set to hidden'); equal(tt.mode, 'hidden', 'the mode is set to hidden');
tt = new TextTrack({ tt = new TextTrack({
player: defaultPlayer, tech: defaultTech,
mode: 'showing' mode: 'showing'
}); });
@ -136,7 +136,7 @@ test('kind, label, language, id, cue, and activeCues are read only', function()
id = '1', id = '1',
mode = 'disabled', mode = 'disabled',
tt = new TextTrack({ tt = new TextTrack({
player: defaultPlayer, tech: defaultTech,
kind: kind, kind: kind,
label: label, label: label,
language: language, language: language,
@ -161,7 +161,7 @@ test('kind, label, language, id, cue, and activeCues are read only', function()
test('mode can only be set to a few options', function() { test('mode can only be set to a few options', function() {
var tt = new TextTrack({ var tt = new TextTrack({
player: defaultPlayer tech: defaultTech,
}); });
tt.mode = 'foo'; tt.mode = 'foo';
@ -186,7 +186,7 @@ test('mode can only be set to a few options', function() {
test('cues and activeCues return a TextTrackCueList', function() { test('cues and activeCues return a TextTrackCueList', function() {
var tt = new TextTrack({ var tt = new TextTrack({
player: defaultPlayer tech: defaultTech,
}); });
ok(tt.cues.getCueById, 'cues are a TextTrackCueList'); ok(tt.cues.getCueById, 'cues are a TextTrackCueList');
@ -195,7 +195,7 @@ test('cues and activeCues return a TextTrackCueList', function() {
test('cues can be added and removed from a TextTrack', function() { test('cues can be added and removed from a TextTrack', function() {
var tt = new TextTrack({ var tt = new TextTrack({
player: defaultPlayer tech: defaultTech,
}), }),
cues; cues;
@ -224,7 +224,7 @@ test('fires cuechange when cues become active and inactive', function() {
changes = 0, changes = 0,
cuechangeHandler, cuechangeHandler,
tt = new TextTrack({ tt = new TextTrack({
player: player, tech: player.tech,
mode: 'showing' mode: 'showing'
}); });
@ -241,19 +241,19 @@ test('fires cuechange when cues become active and inactive', function() {
tt.oncuechange = cuechangeHandler; tt.oncuechange = cuechangeHandler;
tt.addEventListener('cuechange', cuechangeHandler); tt.addEventListener('cuechange', cuechangeHandler);
player.currentTime = function() { player.tech.currentTime = function() {
return 2; return 2;
}; };
player.trigger('timeupdate'); player.tech.trigger('timeupdate');
equal(changes, 2, 'a cuechange event trigger addEventListener and oncuechange'); equal(changes, 2, 'a cuechange event trigger addEventListener and oncuechange');
player.currentTime = function() { player.tech.currentTime = function() {
return 7; return 7;
}; };
player.trigger('timeupdate'); player.tech.trigger('timeupdate');
equal(changes, 4, 'a cuechange event trigger addEventListener and oncuechange'); equal(changes, 4, 'a cuechange event trigger addEventListener and oncuechange');
}); });

View File

@ -119,7 +119,7 @@ test('listen to remove and add track events in native text tracks', function() {
player.player_ = player; player.player_ = player;
player.options_ = options = {}; player.options_ = options = {};
html = new Html5(player, options); html = new Html5(options);
ok(events['removetrack'], 'removetrack listener was added'); ok(events['removetrack'], 'removetrack listener was added');
ok(events['addtrack'], 'addtrack listener was added'); ok(events['addtrack'], 'addtrack listener was added');