mirror of
				https://github.com/videojs/video.js.git
				synced 2025-10-31 00:08:01 +02:00 
			
		
		
		
	@misteroneill updated source code to pass linter
This commit is contained in:
		
				
					committed by
					
						 Gary Katsevman
						Gary Katsevman
					
				
			
			
				
	
			
			
			
						parent
						
							c89b75699e
						
					
				
				
					commit
					e85c1c0391
				
			
							
								
								
									
										47
									
								
								.jshintrc
									
									
									
									
									
								
							
							
						
						
									
										47
									
								
								.jshintrc
									
									
									
									
									
								
							| @@ -1,47 +0,0 @@ | ||||
| { | ||||
|     "evil" : true, | ||||
|     "validthis": true, | ||||
|     "node"     : true, | ||||
|     "debug"    : true, | ||||
|     "boss"     : true, | ||||
|     "expr"     : true, | ||||
|     "eqnull"   : true, | ||||
|     "quotmark" : "single", | ||||
|     "sub"      : true, | ||||
|     "trailing" : true, | ||||
|     "undef"    : true, | ||||
|     "laxbreak" : true, | ||||
|     "esnext"   : true, | ||||
|     "eqeqeq"   : true, | ||||
|     "predef"   : [ | ||||
|         "_V_", | ||||
|         "goog", | ||||
|         "console", | ||||
|  | ||||
|         "require", | ||||
|         "define", | ||||
|         "module", | ||||
|         "exports", | ||||
|         "process", | ||||
|  | ||||
|         "q", | ||||
|         "asyncTest", | ||||
|         "deepEqual", | ||||
|         "equal", | ||||
|         "expect", | ||||
|         "module", | ||||
|         "notDeepEqual", | ||||
|         "notEqual", | ||||
|         "notStrictEqual", | ||||
|         "ok", | ||||
|         "throws", | ||||
|         "QUnit", | ||||
|         "raises", | ||||
|         "start", | ||||
|         "stop", | ||||
|         "strictEqual", | ||||
|         "test", | ||||
|         "throws", | ||||
|         "sinon" | ||||
|     ] | ||||
| } | ||||
| @@ -114,14 +114,6 @@ module.exports = function(grunt) { | ||||
|       build: ['build/temp/*'], | ||||
|       dist: ['dist/*'] | ||||
|     }, | ||||
|     jshint: { | ||||
|       src: { | ||||
|         src: ['src/js/**/*.js', 'Gruntfile.js', 'test/unit/**/*.js'], | ||||
|         options: { | ||||
|           jshintrc: '.jshintrc' | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     uglify: { | ||||
|       options: { | ||||
|         sourceMap: true, | ||||
| @@ -160,10 +152,6 @@ module.exports = function(grunt) { | ||||
|       skin: { | ||||
|         files: ['src/css/**/*'], | ||||
|         tasks: ['sass'] | ||||
|       }, | ||||
|       jshint: { | ||||
|         files: ['src/**/*', 'test/unit/**/*.js', 'Gruntfile.js'], | ||||
|         tasks: 'jshint' | ||||
|       } | ||||
|     }, | ||||
|     connect: { | ||||
| @@ -443,6 +431,14 @@ module.exports = function(grunt) { | ||||
|           src: ['build/temp/video.js'] | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     shell: { | ||||
|       lint: { | ||||
|         command: 'vjsstandard', | ||||
|         options: { | ||||
|           preferLocal: true | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   }); | ||||
|  | ||||
| @@ -455,7 +451,6 @@ module.exports = function(grunt) { | ||||
|   const buildDependents = [ | ||||
|     'clean:build', | ||||
|  | ||||
|     'jshint', | ||||
|     'browserify:build', | ||||
|     'exorcise:build', | ||||
|     'concat:novtt', | ||||
|   | ||||
							
								
								
									
										12
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								package.json
									
									
									
									
									
								
							| @@ -14,6 +14,7 @@ | ||||
|   "homepage": "http://videojs.com", | ||||
|   "author": "Steve Heffernan", | ||||
|   "scripts": { | ||||
|     "lint": "vjsstandard", | ||||
|     "test": "grunt test" | ||||
|   }, | ||||
|   "repository": { | ||||
| @@ -57,7 +58,6 @@ | ||||
|     "grunt-contrib-connect": "~0.7.1", | ||||
|     "grunt-contrib-copy": "^0.8.0", | ||||
|     "grunt-contrib-cssmin": "~0.6.0", | ||||
|     "grunt-contrib-jshint": "~0.11.3", | ||||
|     "grunt-contrib-less": "~0.6.4", | ||||
|     "grunt-contrib-uglify": "^0.8.0", | ||||
|     "grunt-contrib-watch": "~0.1.4", | ||||
| @@ -66,6 +66,7 @@ | ||||
|     "grunt-fastly": "^0.1.3", | ||||
|     "grunt-github-releaser": "^0.1.17", | ||||
|     "grunt-karma": "^0.8.3", | ||||
|     "grunt-shell": "^1.3.0", | ||||
|     "grunt-version": "~0.3.0", | ||||
|     "grunt-videojs-languages": "0.0.4", | ||||
|     "grunt-zip": "0.10.2", | ||||
| @@ -87,9 +88,10 @@ | ||||
|     "sinon": "^1.16.1", | ||||
|     "time-grunt": "^1.1.1", | ||||
|     "uglify-js": "~2.3.6", | ||||
|     "videojs-doc-generator": "0.0.1" | ||||
|     "videojs-doc-generator": "0.0.1", | ||||
|     "videojs-standard": "^5.0.0" | ||||
|   }, | ||||
|   "standard": { | ||||
|   "vjsstandard": { | ||||
|     "ignore": [ | ||||
|       "**/Gruntfile.js", | ||||
|       "**/build/**", | ||||
| @@ -97,7 +99,9 @@ | ||||
|       "**/docs/**", | ||||
|       "**/lang/**", | ||||
|       "**/sandbox/**", | ||||
|       "**/test/**" | ||||
|       "**/test/api/**", | ||||
|       "**/test/coverage/**", | ||||
|       "**/test/karma.conf.js" | ||||
|     ] | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -6,13 +6,17 @@ | ||||
| import window from 'global/window'; | ||||
| import document from 'global/document'; | ||||
|  | ||||
| if (window.VIDEOJS_NO_BASE_THEME) return; | ||||
| if (window.VIDEOJS_NO_BASE_THEME) { | ||||
|   return; | ||||
| } | ||||
|  | ||||
| const styles = '{{GENERATED_STYLES}}'; | ||||
|  | ||||
| if (styles === '{{GENERATED'+'_STYLES}}'); | ||||
| // Don't think we need this as it's a noop? | ||||
| // if (styles === '{{GENERATED'+'_STYLES}}'); | ||||
|  | ||||
| const styleNode = document.createElement('style'); | ||||
|  | ||||
| styleNode.innerHTML = styles; | ||||
|  | ||||
| document.head.insertBefore(styleNode, document.head.firstChild); | ||||
|   | ||||
| @@ -3,10 +3,7 @@ | ||||
|  */ | ||||
| import ClickableComponent from './clickable-component.js'; | ||||
| import Component from './component'; | ||||
| import * as Events from './utils/events.js'; | ||||
| import * as Fn from './utils/fn.js'; | ||||
| import log from './utils/log.js'; | ||||
| import document from 'global/document'; | ||||
| import assign from 'object.assign'; | ||||
|  | ||||
| /** | ||||
| @@ -32,7 +29,7 @@ class Button extends ClickableComponent { | ||||
|    * @return {Element} | ||||
|    * @method createEl | ||||
|    */ | ||||
|   createEl(tag='button', props={}, attributes={}) { | ||||
|   createEl(tag = 'button', props = {}, attributes = {}) { | ||||
|     props = assign({ | ||||
|       className: this.buildCSSClass() | ||||
|     }, props); | ||||
| @@ -53,8 +50,12 @@ class Button extends ClickableComponent { | ||||
|  | ||||
|     // Add attributes for button element | ||||
|     attributes = assign({ | ||||
|       type: 'button', // Necessary since the default button type is "submit" | ||||
|       'aria-live': 'polite' // let the screen reader user know that the text of the button may change | ||||
|  | ||||
|       // Necessary since the default button type is "submit" | ||||
|       'type': 'button', | ||||
|  | ||||
|       // let the screen reader user know that the text of the button may change | ||||
|       'aria-live': 'polite' | ||||
|     }, attributes); | ||||
|  | ||||
|     let el = Component.prototype.createEl.call(this, tag, props, attributes); | ||||
| @@ -73,8 +74,9 @@ class Button extends ClickableComponent { | ||||
|    * @deprecated | ||||
|    * @method addChild | ||||
|    */ | ||||
|   addChild(child, options={}) { | ||||
|   addChild(child, options = {}) { | ||||
|     let className = this.constructor.name; | ||||
|  | ||||
|     log.warn(`Adding an actionable (user controllable) child to a Button (${className}) is not supported; use a ClickableComponent instead.`); | ||||
|  | ||||
|     // Avoid the error message generated by ClickableComponent's addChild method | ||||
| @@ -87,13 +89,15 @@ class Button extends ClickableComponent { | ||||
|    * @method handleKeyPress | ||||
|    */ | ||||
|   handleKeyPress(event) { | ||||
|  | ||||
|     // Ignore Space (32) or Enter (13) key operation, which is handled by the browser for a button. | ||||
|     if (event.which === 32 || event.which === 13) { | ||||
|     } else { | ||||
|       super.handleKeyPress(event); // Pass keypress handling up for unsupported keys | ||||
|       return; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|     // Pass keypress handling up for unsupported keys | ||||
|     super.handleKeyPress(event); | ||||
|   } | ||||
| } | ||||
|  | ||||
| Component.registerComponent('Button', Button); | ||||
|   | ||||
| @@ -39,7 +39,7 @@ class ClickableComponent extends Component { | ||||
|    * @return {Element} | ||||
|    * @method createEl | ||||
|    */ | ||||
|   createEl(tag='div', props={}, attributes={}) { | ||||
|   createEl(tag = 'div', props = {}, attributes = {}) { | ||||
|     props = assign({ | ||||
|       className: this.buildCSSClass(), | ||||
|       tabIndex: 0 | ||||
| @@ -51,8 +51,10 @@ class ClickableComponent extends Component { | ||||
|  | ||||
|     // Add ARIA attributes for clickable element which is not a native HTML button | ||||
|     attributes = assign({ | ||||
|       role: 'button', | ||||
|       'aria-live': 'polite' // let the screen reader user know that the text of the element may change | ||||
|       'role': 'button', | ||||
|  | ||||
|       // let the screen reader user know that the text of the element may change | ||||
|       'aria-live': 'polite' | ||||
|     }, attributes); | ||||
|  | ||||
|     let el = super.createEl(tag, props, attributes); | ||||
| @@ -91,9 +93,11 @@ class ClickableComponent extends Component { | ||||
|    * @return {String} | ||||
|    * @method controlText | ||||
|    */ | ||||
|   controlText(text, el=this.el()) { | ||||
|     if (!text) return this.controlText_ || 'Need Text'; | ||||
|      | ||||
|   controlText(text, el = this.el()) { | ||||
|     if (!text) { | ||||
|       return this.controlText_ || 'Need Text'; | ||||
|     } | ||||
|  | ||||
|     const localizedText = this.localize(text); | ||||
|  | ||||
|     this.controlText_ = text; | ||||
| @@ -121,14 +125,14 @@ class ClickableComponent extends Component { | ||||
|    * @return {Component} The child component (created by this process if a string was used) | ||||
|    * @method addChild | ||||
|    */ | ||||
|   addChild(child, options={}) { | ||||
|   addChild(child, options = {}) { | ||||
|     // TODO: Fix adding an actionable child to a ClickableComponent; currently | ||||
|     // it will cause issues with assistive technology (e.g. screen readers) | ||||
|     // which support ARIA, since an element with role="button" cannot have | ||||
|     // actionable child elements. | ||||
|  | ||||
|     //let className = this.constructor.name; | ||||
|     //log.warn(`Adding a child to a ClickableComponent (${className}) can cause issues with assistive technology which supports ARIA, since an element with role="button" cannot have actionable child elements.`); | ||||
|     // let className = this.constructor.name; | ||||
|     // log.warn(`Adding a child to a ClickableComponent (${className}) can cause issues with assistive technology which supports ARIA, since an element with role="button" cannot have actionable child elements.`); | ||||
|  | ||||
|     return super.addChild(child, options); | ||||
|   } | ||||
| @@ -179,12 +183,15 @@ class ClickableComponent extends Component { | ||||
|    * @method handleKeyPress | ||||
|    */ | ||||
|   handleKeyPress(event) { | ||||
|  | ||||
|     // Support Space (32) or Enter (13) key operation to fire a click event | ||||
|     if (event.which === 32 || event.which === 13) { | ||||
|       event.preventDefault(); | ||||
|       this.handleClick(event); | ||||
|     } else if (super.handleKeyPress) { | ||||
|       super.handleKeyPress(event); // Pass keypress handling up for unsupported keys | ||||
|  | ||||
|       // Pass keypress handling up for unsupported keys | ||||
|       super.handleKeyPress(event); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   | ||||
| @@ -3,7 +3,6 @@ | ||||
|  * | ||||
|  * Player Component - Base class for all UI objects | ||||
|  */ | ||||
|  | ||||
| import window from 'global/window'; | ||||
| import * as Dom from './utils/dom.js'; | ||||
| import * as Fn from './utils/fn.js'; | ||||
| @@ -14,7 +13,6 @@ import toTitleCase from './utils/to-title-case.js'; | ||||
| import assign from 'object.assign'; | ||||
| import mergeOptions from './utils/merge-options.js'; | ||||
|  | ||||
|  | ||||
| /** | ||||
|  * Base UI Component class | ||||
|  * Components are embeddable UI objects that are represented by both a | ||||
| @@ -32,7 +30,7 @@ import mergeOptions from './utils/merge-options.js'; | ||||
|  * ``` | ||||
|  * Components are also event targets. | ||||
|  * ```js | ||||
|  *     button.on('click', function(){ | ||||
|  *     button.on('click', function() { | ||||
|  *       console.log('Button Clicked!'); | ||||
|  *     }); | ||||
|  *     button.trigger('customevent'); | ||||
| @@ -340,7 +338,7 @@ class Component { | ||||
|    * @return {Component} The child component (created by this process if a string was used) | ||||
|    * @method addChild | ||||
|    */ | ||||
|   addChild(child, options={}, index=this.children_.length) { | ||||
|   addChild(child, options = {}, index = this.children_.length) { | ||||
|     let component; | ||||
|     let componentName; | ||||
|  | ||||
| @@ -408,6 +406,7 @@ class Component { | ||||
|     if (typeof component.el === 'function' && component.el()) { | ||||
|       let childNodes = this.contentEl().children; | ||||
|       let refNode = childNodes[index] || null; | ||||
|  | ||||
|       this.contentEl().insertBefore(component.el(), refNode); | ||||
|     } | ||||
|  | ||||
| @@ -540,6 +539,7 @@ class Component { | ||||
|         // If two of the same component are used, different names should be supplied | ||||
|         // for each | ||||
|         let newChild = this.addChild(name, opts); | ||||
|  | ||||
|         if (newChild) { | ||||
|           this[name] = newChild; | ||||
|         } | ||||
| @@ -563,13 +563,13 @@ class Component { | ||||
|                 return !workingChildren.some(function(wchild) { | ||||
|                   if (typeof wchild === 'string') { | ||||
|                     return child === wchild; | ||||
|                   } else { | ||||
|                     return child === wchild.name; | ||||
|                   } | ||||
|                   return child === wchild.name; | ||||
|                 }); | ||||
|               })) | ||||
|       .map((child) => { | ||||
|         let name, opts; | ||||
|         let name; | ||||
|         let opts; | ||||
|  | ||||
|         if (typeof child === 'string') { | ||||
|           name = child; | ||||
| @@ -587,6 +587,7 @@ class Component { | ||||
|         // See https://github.com/videojs/video.js/issues/2772 | ||||
|         let c = Component.getComponent(child.opts.componentClass || | ||||
|                                        toTitleCase(child.name)); | ||||
|  | ||||
|         return c && !Tech.isTech(c); | ||||
|       }) | ||||
|       .forEach(handleAdd); | ||||
| @@ -608,7 +609,7 @@ class Component { | ||||
|   /** | ||||
|    * Add an event listener to this component's element | ||||
|    * ```js | ||||
|    *     var myFunc = function(){ | ||||
|    *     var myFunc = function() { | ||||
|    *       var myComponent = this; | ||||
|    *       // Do something when the event is fired | ||||
|    *     }; | ||||
| @@ -797,7 +798,7 @@ class Component { | ||||
|    * @return {Component} | ||||
|    * @method ready | ||||
|    */ | ||||
|   ready(fn, sync=false) { | ||||
|   ready(fn, sync = false) { | ||||
|     if (fn) { | ||||
|       if (this.isReady_) { | ||||
|         if (sync) { | ||||
| @@ -824,14 +825,14 @@ class Component { | ||||
|     this.isReady_ = true; | ||||
|  | ||||
|     // Ensure ready is triggerd asynchronously | ||||
|     this.setTimeout(function(){ | ||||
|     this.setTimeout(function() { | ||||
|       let readyQueue = this.readyQueue_; | ||||
|  | ||||
|       // Reset Ready Queue | ||||
|       this.readyQueue_ = []; | ||||
|  | ||||
|       if (readyQueue && readyQueue.length > 0) { | ||||
|         readyQueue.forEach(function(fn){ | ||||
|         readyQueue.forEach(function(fn) { | ||||
|           fn.call(this); | ||||
|         }, this); | ||||
|       } | ||||
| @@ -1106,11 +1107,13 @@ class Component { | ||||
|  | ||||
|     if (typeof window.getComputedStyle === 'function') { | ||||
|       const computedStyle = window.getComputedStyle(this.el_); | ||||
|  | ||||
|       computedWidthOrHeight = computedStyle.getPropertyValue(widthOrHeight) || computedStyle[widthOrHeight]; | ||||
|     } else if (this.el_.currentStyle) { | ||||
|       // ie 8 doesn't support computed style, shim it | ||||
|       // return clientWidth or clientHeight instead for better accuracy | ||||
|       const rule = `offset${toTitleCase(widthOrHeight)}`; | ||||
|  | ||||
|       computedWidthOrHeight = this.el_[rule]; | ||||
|     } | ||||
|  | ||||
| @@ -1194,7 +1197,7 @@ class Component { | ||||
|         // So, if we moved only a small distance, this could still be a tap | ||||
|         const xdiff = event.touches[0].pageX - firstTouch.pageX; | ||||
|         const ydiff = event.touches[0].pageY - firstTouch.pageY; | ||||
|         const touchDistance = Math.sqrt(xdiff  * xdiff + ydiff  * ydiff); | ||||
|         const touchDistance = Math.sqrt(xdiff * xdiff + ydiff * ydiff); | ||||
|  | ||||
|         if (touchDistance > tapMovementThreshold) { | ||||
|           couldBeTap = false; | ||||
|   | ||||
| @@ -3,7 +3,6 @@ | ||||
|  */ | ||||
| import TrackButton from '../track-button.js'; | ||||
| import Component from '../../component.js'; | ||||
| import * as Fn from '../../utils/fn.js'; | ||||
| import AudioTrackMenuItem from './audio-track-menu-item.js'; | ||||
|  | ||||
| /** | ||||
| @@ -40,19 +39,19 @@ class AudioTrackButton extends TrackButton { | ||||
|    * @method createItems | ||||
|    */ | ||||
|   createItems(items = []) { | ||||
|     let tracks = this.player_.audioTracks && this.player_.audioTracks(); | ||||
|     const tracks = this.player_.audioTracks && this.player_.audioTracks(); | ||||
|  | ||||
|     if (!tracks) { | ||||
|       return items; | ||||
|     } | ||||
|  | ||||
|     for (let i = 0; i < tracks.length; i++) { | ||||
|       let track = tracks[i]; | ||||
|       const track = tracks[i]; | ||||
|  | ||||
|       items.push(new AudioTrackMenuItem(this.player_, { | ||||
|         track, | ||||
|         // MenuItem is selectable | ||||
|         'selectable': true, | ||||
|         'track': track | ||||
|         selectable: true | ||||
|       })); | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -15,8 +15,8 @@ import * as Fn from '../../utils/fn.js'; | ||||
|  */ | ||||
| class AudioTrackMenuItem extends MenuItem { | ||||
|   constructor(player, options) { | ||||
|     let track = options.track; | ||||
|     let tracks = player.audioTracks(); | ||||
|     const track = options.track; | ||||
|     const tracks = player.audioTracks(); | ||||
|  | ||||
|     // Modify options for parent MenuItem class's init. | ||||
|     options.label = track.label || track.language || 'Unknown'; | ||||
| @@ -27,7 +27,7 @@ class AudioTrackMenuItem extends MenuItem { | ||||
|     this.track = track; | ||||
|  | ||||
|     if (tracks) { | ||||
|       let changeHandler = Fn.bind(this, this.handleTracksChange); | ||||
|       const changeHandler = Fn.bind(this, this.handleTracksChange); | ||||
|  | ||||
|       tracks.addEventListener('change', changeHandler); | ||||
|       this.on('dispose', () => { | ||||
| @@ -42,14 +42,16 @@ class AudioTrackMenuItem extends MenuItem { | ||||
|    * @method handleClick | ||||
|    */ | ||||
|   handleClick(event) { | ||||
|     let tracks = this.player_.audioTracks(); | ||||
|     const tracks = this.player_.audioTracks(); | ||||
|  | ||||
|     super.handleClick(event); | ||||
|  | ||||
|     if (!tracks) return; | ||||
|     if (!tracks) { | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     for (let i = 0; i < tracks.length; i++) { | ||||
|       let track = tracks[i]; | ||||
|       const track = tracks[i]; | ||||
|  | ||||
|       if (track === this.track) { | ||||
|         track.enabled = true; | ||||
|   | ||||
| @@ -4,24 +4,24 @@ | ||||
| import Component from '../component.js'; | ||||
|  | ||||
| // Required children | ||||
| import PlayToggle from './play-toggle.js'; | ||||
| import CurrentTimeDisplay from './time-controls/current-time-display.js'; | ||||
| import DurationDisplay from './time-controls/duration-display.js'; | ||||
| import TimeDivider from './time-controls/time-divider.js'; | ||||
| import RemainingTimeDisplay from './time-controls/remaining-time-display.js'; | ||||
| import LiveDisplay from './live-display.js'; | ||||
| import ProgressControl from './progress-control/progress-control.js'; | ||||
| import FullscreenToggle from './fullscreen-toggle.js'; | ||||
| import VolumeControl from './volume-control/volume-control.js'; | ||||
| import VolumeMenuButton from './volume-menu-button.js'; | ||||
| import MuteToggle from './mute-toggle.js'; | ||||
| import ChaptersButton from './text-track-controls/chapters-button.js'; | ||||
| import DescriptionsButton from './text-track-controls/descriptions-button.js'; | ||||
| import SubtitlesButton from './text-track-controls/subtitles-button.js'; | ||||
| import CaptionsButton from './text-track-controls/captions-button.js'; | ||||
| import AudioTrackButton from './audio-track-controls/audio-track-button.js'; | ||||
| import PlaybackRateMenuButton from './playback-rate-menu/playback-rate-menu-button.js'; | ||||
| import CustomControlSpacer from './spacer-controls/custom-control-spacer.js'; | ||||
| import './play-toggle.js'; | ||||
| import './time-controls/current-time-display.js'; | ||||
| import './time-controls/duration-display.js'; | ||||
| import './time-controls/time-divider.js'; | ||||
| import './time-controls/remaining-time-display.js'; | ||||
| import './live-display.js'; | ||||
| import './progress-control/progress-control.js'; | ||||
| import './fullscreen-toggle.js'; | ||||
| import './volume-control/volume-control.js'; | ||||
| import './volume-menu-button.js'; | ||||
| import './mute-toggle.js'; | ||||
| import './text-track-controls/chapters-button.js'; | ||||
| import './text-track-controls/descriptions-button.js'; | ||||
| import './text-track-controls/subtitles-button.js'; | ||||
| import './text-track-controls/captions-button.js'; | ||||
| import './audio-track-controls/audio-track-button.js'; | ||||
| import './playback-rate-menu/playback-rate-menu-button.js'; | ||||
| import './spacer-controls/custom-control-spacer.js'; | ||||
|  | ||||
| /** | ||||
|  * Container of main controls | ||||
| @@ -42,7 +42,8 @@ class ControlBar extends Component { | ||||
|       className: 'vjs-control-bar', | ||||
|       dir: 'ltr' | ||||
|     }, { | ||||
|       'role': 'group' // The control bar is a group, so it can contain menuitems | ||||
|       // The control bar is a group, so it can contain menuitems | ||||
|       role: 'group' | ||||
|     }); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -27,7 +27,7 @@ class LiveDisplay extends Component { | ||||
|    * @method createEl | ||||
|    */ | ||||
|   createEl() { | ||||
|     var el = super.createEl('div', { | ||||
|     const el = super.createEl('div', { | ||||
|       className: 'vjs-live-control vjs-control' | ||||
|     }); | ||||
|  | ||||
|   | ||||
| @@ -21,14 +21,15 @@ class MuteToggle extends Button { | ||||
|     this.on(player, 'volumechange', this.update); | ||||
|  | ||||
|     // hide mute toggle if the current tech doesn't support volume control | ||||
|     if (player.tech_ && player.tech_['featuresVolumeControl'] === false) { | ||||
|     if (player.tech_ && player.tech_.featuresVolumeControl === false) { | ||||
|       this.addClass('vjs-hidden'); | ||||
|     } | ||||
|  | ||||
|     this.on(player, 'loadstart', function() { | ||||
|       this.update(); // We need to update the button to account for a default muted state. | ||||
|       // We need to update the button to account for a default muted state. | ||||
|       this.update(); | ||||
|  | ||||
|       if (player.tech_['featuresVolumeControl'] === false) { | ||||
|       if (player.tech_.featuresVolumeControl === false) { | ||||
|         this.addClass('vjs-hidden'); | ||||
|       } else { | ||||
|         this.removeClass('vjs-hidden'); | ||||
| @@ -52,7 +53,7 @@ class MuteToggle extends Button { | ||||
|    * @method handleClick | ||||
|    */ | ||||
|   handleClick() { | ||||
|     this.player_.muted( this.player_.muted() ? false : true ); | ||||
|     this.player_.muted(this.player_.muted() ? false : true); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
| @@ -61,8 +62,8 @@ class MuteToggle extends Button { | ||||
|    * @method update | ||||
|    */ | ||||
|   update() { | ||||
|     var vol = this.player_.volume(), | ||||
|         level = 3; | ||||
|     const vol = this.player_.volume(); | ||||
|     let level = 3; | ||||
|  | ||||
|     if (vol === 0 || this.player_.muted()) { | ||||
|       level = 0; | ||||
| @@ -75,13 +76,14 @@ class MuteToggle extends Button { | ||||
|     // Don't rewrite the button text if the actual text doesn't change. | ||||
|     // This causes unnecessary and confusing information for screen reader users. | ||||
|     // This check is needed because this function gets called every time the volume level is changed. | ||||
|     let toMute = this.player_.muted() ? 'Unmute' : 'Mute'; | ||||
|     const toMute = this.player_.muted() ? 'Unmute' : 'Mute'; | ||||
|  | ||||
|     if (this.controlText() !== toMute) { | ||||
|       this.controlText(toMute); | ||||
|     } | ||||
|  | ||||
|     /* TODO improve muted icon classes */ | ||||
|     for (var i = 0; i < 4; i++) { | ||||
|     // TODO improve muted icon classes | ||||
|     for (let i = 0; i < 4; i++) { | ||||
|       Dom.removeElClass(this.el_, `vjs-vol-${i}`); | ||||
|     } | ||||
|     Dom.addElClass(this.el_, `vjs-vol-${level}`); | ||||
|   | ||||
| @@ -14,7 +14,7 @@ import Component from '../component.js'; | ||||
|  */ | ||||
| class PlayToggle extends Button { | ||||
|  | ||||
|   constructor(player, options){ | ||||
|   constructor(player, options) { | ||||
|     super(player, options); | ||||
|  | ||||
|     this.on(player, 'play', this.handlePlay); | ||||
| @@ -52,7 +52,8 @@ class PlayToggle extends Button { | ||||
|   handlePlay() { | ||||
|     this.removeClass('vjs-paused'); | ||||
|     this.addClass('vjs-playing'); | ||||
|     this.controlText('Pause'); // change the button text to "Pause" | ||||
|     // change the button text to "Pause" | ||||
|     this.controlText('Pause'); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
| @@ -63,7 +64,8 @@ class PlayToggle extends Button { | ||||
|   handlePause() { | ||||
|     this.removeClass('vjs-playing'); | ||||
|     this.addClass('vjs-paused'); | ||||
|     this.controlText('Play'); // change the button text to "Play" | ||||
|     // change the button text to "Play" | ||||
|     this.controlText('Play'); | ||||
|   } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -17,7 +17,7 @@ import * as Dom from '../../utils/dom.js'; | ||||
|  */ | ||||
| class PlaybackRateMenuButton extends MenuButton { | ||||
|  | ||||
|   constructor(player, options){ | ||||
|   constructor(player, options) { | ||||
|     super(player, options); | ||||
|  | ||||
|     this.updateVisibility(); | ||||
| @@ -34,7 +34,7 @@ class PlaybackRateMenuButton extends MenuButton { | ||||
|    * @method createEl | ||||
|    */ | ||||
|   createEl() { | ||||
|     let el = super.createEl(); | ||||
|     const el = super.createEl(); | ||||
|  | ||||
|     this.labelEl_ = Dom.createEl('div', { | ||||
|       className: 'vjs-playback-rate-value', | ||||
| @@ -63,13 +63,13 @@ class PlaybackRateMenuButton extends MenuButton { | ||||
|    * @method createMenu | ||||
|    */ | ||||
|   createMenu() { | ||||
|     let menu = new Menu(this.player()); | ||||
|     let rates = this.playbackRates(); | ||||
|     const menu = new Menu(this.player()); | ||||
|     const rates = this.playbackRates(); | ||||
|  | ||||
|     if (rates) { | ||||
|       for (let i = rates.length - 1; i >= 0; i--) { | ||||
|         menu.addChild( | ||||
|           new PlaybackRateMenuItem(this.player(), { 'rate': rates[i] + 'x'}) | ||||
|           new PlaybackRateMenuItem(this.player(), {rate: rates[i] + 'x'}) | ||||
|         ); | ||||
|       } | ||||
|     } | ||||
| @@ -94,12 +94,13 @@ class PlaybackRateMenuButton extends MenuButton { | ||||
|    */ | ||||
|   handleClick() { | ||||
|     // select next rate option | ||||
|     let currentRate = this.player().playbackRate(); | ||||
|     let rates = this.playbackRates(); | ||||
|     const currentRate = this.player().playbackRate(); | ||||
|     const rates = this.playbackRates(); | ||||
|  | ||||
|     // this will select first one if the last one currently selected | ||||
|     let newRate = rates[0]; | ||||
|     for (let i = 0; i < rates.length ; i++) { | ||||
|  | ||||
|     for (let i = 0; i < rates.length; i++) { | ||||
|       if (rates[i] > currentRate) { | ||||
|         newRate = rates[i]; | ||||
|         break; | ||||
| @@ -115,7 +116,7 @@ class PlaybackRateMenuButton extends MenuButton { | ||||
|    * @method playbackRates | ||||
|    */ | ||||
|   playbackRates() { | ||||
|     return this.options_['playbackRates'] || (this.options_.playerOptions && this.options_.playerOptions['playbackRates']); | ||||
|     return this.options_.playbackRates || (this.options_.playerOptions && this.options_.playerOptions.playbackRates); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
| @@ -126,10 +127,10 @@ class PlaybackRateMenuButton extends MenuButton { | ||||
|    * @method playbackRateSupported | ||||
|    */ | ||||
|   playbackRateSupported() { | ||||
|     return this.player().tech_ | ||||
|       && this.player().tech_['featuresPlaybackRate'] | ||||
|       && this.playbackRates() | ||||
|       && this.playbackRates().length > 0 | ||||
|     return this.player().tech_ && | ||||
|       this.player().tech_.featuresPlaybackRate && | ||||
|       this.playbackRates() && | ||||
|       this.playbackRates().length > 0 | ||||
|     ; | ||||
|   } | ||||
|  | ||||
|   | ||||
| @@ -14,13 +14,13 @@ import Component from '../../component.js'; | ||||
|  */ | ||||
| class PlaybackRateMenuItem extends MenuItem { | ||||
|  | ||||
|   constructor(player, options){ | ||||
|     let label = options['rate']; | ||||
|     let rate = parseFloat(label, 10); | ||||
|   constructor(player, options) { | ||||
|     const label = options.rate; | ||||
|     const rate = parseFloat(label, 10); | ||||
|  | ||||
|     // Modify options for parent MenuItem class's init. | ||||
|     options['label'] = label; | ||||
|     options['selected'] = rate === 1; | ||||
|     options.label = label; | ||||
|     options.selected = rate === 1; | ||||
|     super(player, options); | ||||
|  | ||||
|     this.label = label; | ||||
|   | ||||
| @@ -14,7 +14,7 @@ import * as Dom from '../../utils/dom.js'; | ||||
|  */ | ||||
| class LoadProgressBar extends Component { | ||||
|  | ||||
|   constructor(player, options){ | ||||
|   constructor(player, options) { | ||||
|     super(player, options); | ||||
|     this.on(player, 'progress', this.update); | ||||
|   } | ||||
| @@ -38,14 +38,16 @@ class LoadProgressBar extends Component { | ||||
|    * @method update | ||||
|    */ | ||||
|   update() { | ||||
|     let buffered = this.player_.buffered(); | ||||
|     let duration = this.player_.duration(); | ||||
|     let bufferedEnd = this.player_.bufferedEnd(); | ||||
|     let children = this.el_.children; | ||||
|     const buffered = this.player_.buffered(); | ||||
|     const duration = this.player_.duration(); | ||||
|     const bufferedEnd = this.player_.bufferedEnd(); | ||||
|     const children = this.el_.children; | ||||
|  | ||||
|     // get the percent width of a time compared to the total end | ||||
|     let percentify = function (time, end){ | ||||
|       let percent = (time / end) || 0; // no NaN | ||||
|     const percentify = function(time, end) { | ||||
|       // no NaN | ||||
|       const percent = (time / end) || 0; | ||||
|  | ||||
|       return ((percent >= 1 ? 1 : percent) * 100) + '%'; | ||||
|     }; | ||||
|  | ||||
| @@ -54,8 +56,8 @@ class LoadProgressBar extends Component { | ||||
|  | ||||
|     // add child elements to represent the individual buffered time ranges | ||||
|     for (let i = 0; i < buffered.length; i++) { | ||||
|       let start = buffered.start(i); | ||||
|       let end = buffered.end(i); | ||||
|       const start = buffered.start(i); | ||||
|       const end = buffered.end(i); | ||||
|       let part = children[i]; | ||||
|  | ||||
|       if (!part) { | ||||
| @@ -69,7 +71,7 @@ class LoadProgressBar extends Component { | ||||
|  | ||||
|     // remove unused buffered range elements | ||||
|     for (let i = children.length; i > buffered.length; i--) { | ||||
|       this.el_.removeChild(children[i-1]); | ||||
|       this.el_.removeChild(children[i - 1]); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   | ||||
| @@ -55,24 +55,24 @@ class MouseTimeDisplay extends Component { | ||||
|   } | ||||
|  | ||||
|   handleMouseMove(event) { | ||||
|     let duration = this.player_.duration(); | ||||
|     let newTime = this.calculateDistance(event) * duration; | ||||
|     let position = event.pageX - Dom.findElPosition(this.el().parentNode).left; | ||||
|     const duration = this.player_.duration(); | ||||
|     const newTime = this.calculateDistance(event) * duration; | ||||
|     const position = event.pageX - Dom.findElPosition(this.el().parentNode).left; | ||||
|  | ||||
|     this.update(newTime, position); | ||||
|   } | ||||
|  | ||||
|   update(newTime, position) { | ||||
|     let time = formatTime(newTime, this.player_.duration()); | ||||
|     const time = formatTime(newTime, this.player_.duration()); | ||||
|  | ||||
|     this.el().style.left = position + 'px'; | ||||
|     this.el().setAttribute('data-current-time', time); | ||||
|  | ||||
|     if (this.keepTooltipsInside) { | ||||
|       let clampedPosition = this.clampPosition_(position); | ||||
|       let difference = position - clampedPosition + 1; | ||||
|       let tooltipWidth = parseFloat(window.getComputedStyle(this.tooltip).width); | ||||
|       let tooltipWidthHalf = tooltipWidth / 2; | ||||
|       const clampedPosition = this.clampPosition_(position); | ||||
|       const difference = position - clampedPosition + 1; | ||||
|       const tooltipWidth = parseFloat(window.getComputedStyle(this.tooltip).width); | ||||
|       const tooltipWidthHalf = tooltipWidth / 2; | ||||
|  | ||||
|       this.tooltip.innerHTML = time; | ||||
|       this.tooltip.style.right = `-${tooltipWidthHalf - difference}px`; | ||||
| @@ -98,9 +98,9 @@ class MouseTimeDisplay extends Component { | ||||
|       return position; | ||||
|     } | ||||
|  | ||||
|     let playerWidth = parseFloat(window.getComputedStyle(this.player().el()).width); | ||||
|     let tooltipWidth = parseFloat(window.getComputedStyle(this.tooltip).width); | ||||
|     let tooltipWidthHalf = tooltipWidth / 2; | ||||
|     const playerWidth = parseFloat(window.getComputedStyle(this.player().el()).width); | ||||
|     const tooltipWidth = parseFloat(window.getComputedStyle(this.tooltip).width); | ||||
|     const tooltipWidthHalf = tooltipWidth / 2; | ||||
|     let actualPosition = position; | ||||
|  | ||||
|     if (position < tooltipWidthHalf) { | ||||
|   | ||||
| @@ -3,7 +3,6 @@ | ||||
|  */ | ||||
| import Component from '../../component.js'; | ||||
| import * as Fn from '../../utils/fn.js'; | ||||
| import * as Dom from '../../utils/dom.js'; | ||||
| import formatTime from '../../utils/format-time.js'; | ||||
|  | ||||
| /** | ||||
| @@ -16,7 +15,7 @@ import formatTime from '../../utils/format-time.js'; | ||||
|  */ | ||||
| class PlayProgressBar extends Component { | ||||
|  | ||||
|   constructor(player, options){ | ||||
|   constructor(player, options) { | ||||
|     super(player, options); | ||||
|     this.updateDataAttr(); | ||||
|     this.on(player, 'timeupdate', this.updateDataAttr); | ||||
| @@ -48,7 +47,8 @@ class PlayProgressBar extends Component { | ||||
|   } | ||||
|  | ||||
|   updateDataAttr() { | ||||
|     let time = (this.player_.scrubbing()) ? this.player_.getCache().currentTime : this.player_.currentTime(); | ||||
|     const time = (this.player_.scrubbing()) ? this.player_.getCache().currentTime : this.player_.currentTime(); | ||||
|  | ||||
|     this.el_.setAttribute('data-current-time', formatTime(time, this.player_.duration())); | ||||
|   } | ||||
|  | ||||
|   | ||||
| @@ -2,8 +2,9 @@ | ||||
|  * @file progress-control.js | ||||
|  */ | ||||
| import Component from '../../component.js'; | ||||
| import SeekBar from './seek-bar.js'; | ||||
| import MouseTimeDisplay from './mouse-time-display.js'; | ||||
|  | ||||
| import './seek-bar.js'; | ||||
| import './mouse-time-display.js'; | ||||
|  | ||||
| /** | ||||
|  * The Progress Control component contains the seek bar, load progress, | ||||
|   | ||||
| @@ -4,12 +4,12 @@ | ||||
| import window from 'global/window'; | ||||
| import Slider from '../../slider/slider.js'; | ||||
| import Component from '../../component.js'; | ||||
| import LoadProgressBar from './load-progress-bar.js'; | ||||
| import PlayProgressBar from './play-progress-bar.js'; | ||||
| import TooltipProgressBar from './tooltip-progress-bar.js'; | ||||
| import * as Fn from '../../utils/fn.js'; | ||||
| import formatTime from '../../utils/format-time.js'; | ||||
| import assign from 'object.assign'; | ||||
|  | ||||
| import './load-progress-bar.js'; | ||||
| import './play-progress-bar.js'; | ||||
| import './tooltip-progress-bar.js'; | ||||
|  | ||||
| /** | ||||
|  * Seek Bar and holder for the progress bars | ||||
| @@ -21,7 +21,7 @@ import assign from 'object.assign'; | ||||
|  */ | ||||
| class SeekBar extends Slider { | ||||
|  | ||||
|   constructor(player, options){ | ||||
|   constructor(player, options) { | ||||
|     super(player, options); | ||||
|     this.on(player, 'timeupdate', this.updateProgress); | ||||
|     this.on(player, 'ended', this.updateProgress); | ||||
| @@ -65,9 +65,10 @@ class SeekBar extends Slider { | ||||
|       this.updateAriaAttributes(this.tooltipProgressBar.el_); | ||||
|       this.tooltipProgressBar.el_.style.width = this.bar.el_.style.width; | ||||
|  | ||||
|       let playerWidth = parseFloat(window.getComputedStyle(this.player().el()).width); | ||||
|       let tooltipWidth = parseFloat(window.getComputedStyle(this.tooltipProgressBar.tooltip).width); | ||||
|       let tooltipStyle = this.tooltipProgressBar.el().style; | ||||
|       const playerWidth = parseFloat(window.getComputedStyle(this.player().el()).width); | ||||
|       const tooltipWidth = parseFloat(window.getComputedStyle(this.tooltipProgressBar.tooltip).width); | ||||
|       const tooltipStyle = this.tooltipProgressBar.el().style; | ||||
|  | ||||
|       tooltipStyle.maxWidth = Math.floor(playerWidth - (tooltipWidth / 2)) + 'px'; | ||||
|       tooltipStyle.minWidth = Math.ceil(tooltipWidth / 2) + 'px'; | ||||
|       tooltipStyle.right = `-${tooltipWidth / 2}px`; | ||||
| @@ -76,9 +77,12 @@ class SeekBar extends Slider { | ||||
|  | ||||
|   updateAriaAttributes(el) { | ||||
|     // Allows for smooth scrubbing, when player can't keep up. | ||||
|     let time = (this.player_.scrubbing()) ? this.player_.getCache().currentTime : this.player_.currentTime(); | ||||
|     el.setAttribute('aria-valuenow', (this.getPercent() * 100).toFixed(2)); // machine readable value of progress bar (percentage complete) | ||||
|     el.setAttribute('aria-valuetext', formatTime(time, this.player_.duration())); // human readable value of progress bar (time complete) | ||||
|     const time = (this.player_.scrubbing()) ? this.player_.getCache().currentTime : this.player_.currentTime(); | ||||
|  | ||||
|     // machine readable value of progress bar (percentage complete) | ||||
|     el.setAttribute('aria-valuenow', (this.getPercent() * 100).toFixed(2)); | ||||
|     // human readable value of progress bar (time complete) | ||||
|     el.setAttribute('aria-valuetext', formatTime(time, this.player_.duration())); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
| @@ -88,7 +92,8 @@ class SeekBar extends Slider { | ||||
|    * @method getPercent | ||||
|    */ | ||||
|   getPercent() { | ||||
|     let percent = this.player_.currentTime() / this.player_.duration(); | ||||
|     const percent = this.player_.currentTime() / this.player_.duration(); | ||||
|  | ||||
|     return percent >= 1 ? 1 : percent; | ||||
|   } | ||||
|  | ||||
| @@ -115,7 +120,9 @@ class SeekBar extends Slider { | ||||
|     let newTime = this.calculateDistance(event) * this.player_.duration(); | ||||
|  | ||||
|     // 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) | ||||
|     this.player_.currentTime(newTime); | ||||
| @@ -141,7 +148,8 @@ class SeekBar extends Slider { | ||||
|    * @method stepForward | ||||
|    */ | ||||
|   stepForward() { | ||||
|     this.player_.currentTime(this.player_.currentTime() + 5); // more quickly fast forward for keyboard-only users | ||||
|     // more quickly fast forward for keyboard-only users | ||||
|     this.player_.currentTime(this.player_.currentTime() + 5); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
| @@ -150,7 +158,8 @@ class SeekBar extends Slider { | ||||
|    * @method stepBack | ||||
|    */ | ||||
|   stepBack() { | ||||
|     this.player_.currentTime(this.player_.currentTime() - 5); // more quickly rewind for keyboard-only users | ||||
|     // more quickly rewind for keyboard-only users | ||||
|     this.player_.currentTime(this.player_.currentTime() - 5); | ||||
|   } | ||||
|  | ||||
| } | ||||
| @@ -161,7 +170,7 @@ SeekBar.prototype.options_ = { | ||||
|     'mouseTimeDisplay', | ||||
|     'playProgressBar' | ||||
|   ], | ||||
|   'barName': 'playProgressBar' | ||||
|   barName: 'playProgressBar' | ||||
| }; | ||||
|  | ||||
| SeekBar.prototype.playerEvent = 'timeupdate'; | ||||
|   | ||||
| @@ -3,7 +3,6 @@ | ||||
|  */ | ||||
| import Component from '../../component.js'; | ||||
| import * as Fn from '../../utils/fn.js'; | ||||
| import * as Dom from '../../utils/dom.js'; | ||||
| import formatTime from '../../utils/format-time.js'; | ||||
|  | ||||
| /** | ||||
| @@ -16,7 +15,7 @@ import formatTime from '../../utils/format-time.js'; | ||||
|  */ | ||||
| class TooltipProgressBar extends Component { | ||||
|  | ||||
|   constructor(player, options){ | ||||
|   constructor(player, options) { | ||||
|     super(player, options); | ||||
|     this.updateDataAttr(); | ||||
|     this.on(player, 'timeupdate', this.updateDataAttr); | ||||
| @@ -30,7 +29,7 @@ class TooltipProgressBar extends Component { | ||||
|    * @method createEl | ||||
|    */ | ||||
|   createEl() { | ||||
|     let el = super.createEl('div', { | ||||
|     const el = super.createEl('div', { | ||||
|       className: 'vjs-tooltip-progress-bar vjs-slider-bar', | ||||
|       innerHTML: `<div class="vjs-time-tooltip"></div> | ||||
|         <span class="vjs-control-text"><span>${this.localize('Progress')}</span>: 0%</span>` | ||||
| @@ -42,8 +41,9 @@ class TooltipProgressBar extends Component { | ||||
|   } | ||||
|  | ||||
|   updateDataAttr() { | ||||
|     let time = (this.player_.scrubbing()) ? this.player_.getCache().currentTime : this.player_.currentTime(); | ||||
|     let formattedTime = formatTime(time, this.player_.duration()); | ||||
|     const time = (this.player_.scrubbing()) ? this.player_.getCache().currentTime : this.player_.currentTime(); | ||||
|     const formattedTime = formatTime(time, this.player_.duration()); | ||||
|  | ||||
|     this.el_.setAttribute('data-current-time', formattedTime); | ||||
|     this.tooltip.innerHTML = formattedTime; | ||||
|   } | ||||
|   | ||||
| @@ -29,8 +29,8 @@ class CustomControlSpacer extends Spacer { | ||||
|    * @method createEl | ||||
|    */ | ||||
|   createEl() { | ||||
|     let el = super.createEl({ | ||||
|       className: this.buildCSSClass(), | ||||
|     const el = super.createEl({ | ||||
|       className: this.buildCSSClass() | ||||
|     }); | ||||
|  | ||||
|     // No-flex/table-cell mode requires there be some content | ||||
|   | ||||
| @@ -12,24 +12,24 @@ import Component from '../../component.js'; | ||||
|  * @extends TextTrackMenuItem | ||||
|  * @class CaptionSettingsMenuItem | ||||
|  */ | ||||
|  class CaptionSettingsMenuItem extends TextTrackMenuItem { | ||||
| class CaptionSettingsMenuItem extends TextTrackMenuItem { | ||||
|  | ||||
|   constructor(player, options) { | ||||
|     options['track'] = { | ||||
|       'kind': options['kind'], | ||||
|       'player': player, | ||||
|       'label': options['kind'] + ' settings', | ||||
|       'selectable': false, | ||||
|       'default': false, | ||||
|     options.track = { | ||||
|       player, | ||||
|       kind: options.kind, | ||||
|       label: options.kind + ' settings', | ||||
|       selectable: false, | ||||
|       default: false, | ||||
|       mode: 'disabled' | ||||
|     }; | ||||
|  | ||||
|     // CaptionSettingsMenuItem has no concept of 'selected' | ||||
|     options['selectable'] = false; | ||||
|     options.selectable = false; | ||||
|  | ||||
|     super(player, options); | ||||
|     this.addClass('vjs-texttrack-settings'); | ||||
|     this.controlText(', opens ' + options['kind'] + ' settings dialog'); | ||||
|     this.controlText(', opens ' + options.kind + ' settings dialog'); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|   | ||||
| @@ -16,9 +16,9 @@ import CaptionSettingsMenuItem from './caption-settings-menu-item.js'; | ||||
|  */ | ||||
| class CaptionsButton extends TextTrackButton { | ||||
|  | ||||
|   constructor(player, options, ready){ | ||||
|   constructor(player, options, ready) { | ||||
|     super(player, options, ready); | ||||
|     this.el_.setAttribute('aria-label','Captions Menu'); | ||||
|     this.el_.setAttribute('aria-label', 'Captions Menu'); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
| @@ -38,10 +38,11 @@ class CaptionsButton extends TextTrackButton { | ||||
|    */ | ||||
|   update() { | ||||
|     let threshold = 2; | ||||
|  | ||||
|     super.update(); | ||||
|  | ||||
|     // if native, then threshold is 1 because no settings button | ||||
|     if (this.player().tech_ && this.player().tech_['featuresNativeTextTracks']) { | ||||
|     if (this.player().tech_ && this.player().tech_.featuresNativeTextTracks) { | ||||
|       threshold = 1; | ||||
|     } | ||||
|  | ||||
| @@ -59,10 +60,10 @@ class CaptionsButton extends TextTrackButton { | ||||
|    * @method createItems | ||||
|    */ | ||||
|   createItems() { | ||||
|     let items = []; | ||||
|     const items = []; | ||||
|  | ||||
|     if (!(this.player().tech_ && this.player().tech_['featuresNativeTextTracks'])) { | ||||
|       items.push(new CaptionSettingsMenuItem(this.player_, { 'kind': this.kind_ })); | ||||
|     if (!(this.player().tech_ && this.player().tech_.featuresNativeTextTracks)) { | ||||
|       items.push(new CaptionSettingsMenuItem(this.player_, {kind: this.kind_})); | ||||
|     } | ||||
|  | ||||
|     return super.createItems(items); | ||||
|   | ||||
| @@ -7,9 +7,7 @@ import TextTrackMenuItem from './text-track-menu-item.js'; | ||||
| import ChaptersTrackMenuItem from './chapters-track-menu-item.js'; | ||||
| import Menu from '../../menu/menu.js'; | ||||
| import * as Dom from '../../utils/dom.js'; | ||||
| import * as Fn from '../../utils/fn.js'; | ||||
| import toTitleCase from '../../utils/to-title-case.js'; | ||||
| import window from 'global/window'; | ||||
|  | ||||
| /** | ||||
|  * The button component for toggling and selecting chapters | ||||
| @@ -24,9 +22,9 @@ import window from 'global/window'; | ||||
|  */ | ||||
| class ChaptersButton extends TextTrackButton { | ||||
|  | ||||
|   constructor(player, options, ready){ | ||||
|   constructor(player, options, ready) { | ||||
|     super(player, options, ready); | ||||
|     this.el_.setAttribute('aria-label','Chapters Menu'); | ||||
|     this.el_.setAttribute('aria-label', 'Chapters Menu'); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
| @@ -46,20 +44,18 @@ class ChaptersButton extends TextTrackButton { | ||||
|    * @method createItems | ||||
|    */ | ||||
|   createItems() { | ||||
|     let items = []; | ||||
|  | ||||
|     let tracks = this.player_.textTracks(); | ||||
|     const items = []; | ||||
|     const tracks = this.player_.textTracks(); | ||||
|  | ||||
|     if (!tracks) { | ||||
|       return items; | ||||
|     } | ||||
|  | ||||
|     for (let i = 0; i < tracks.length; i++) { | ||||
|       let track = tracks[i]; | ||||
|       if (track['kind'] === this.kind_) { | ||||
|         items.push(new TextTrackMenuItem(this.player_, { | ||||
|           'track': track | ||||
|         })); | ||||
|       const track = tracks[i]; | ||||
|  | ||||
|       if (track.kind === this.kind_) { | ||||
|         items.push(new TextTrackMenuItem(this.player_, {track})); | ||||
|       } | ||||
|     } | ||||
|  | ||||
| @@ -73,16 +69,16 @@ class ChaptersButton extends TextTrackButton { | ||||
|    * @method createMenu | ||||
|    */ | ||||
|   createMenu() { | ||||
|     let tracks = this.player_.textTracks() || []; | ||||
|     const tracks = this.player_.textTracks() || []; | ||||
|     let chaptersTrack; | ||||
|     let items = this.items || []; | ||||
|  | ||||
|     for (let i = tracks.length - 1; i >= 0; i--) { | ||||
|  | ||||
|       // We will always choose the last track as our chaptersTrack | ||||
|       let track = tracks[i]; | ||||
|       const track = tracks[i]; | ||||
|  | ||||
|       if (track['kind'] === this.kind_) { | ||||
|       if (track.kind === this.kind_) { | ||||
|         chaptersTrack = track; | ||||
|  | ||||
|         break; | ||||
| @@ -90,27 +86,30 @@ class ChaptersButton extends TextTrackButton { | ||||
|     } | ||||
|  | ||||
|     let menu = this.menu; | ||||
|  | ||||
|     if (menu === undefined) { | ||||
|       menu = new Menu(this.player_); | ||||
|       let title = Dom.createEl('li', { | ||||
|  | ||||
|       const title = Dom.createEl('li', { | ||||
|         className: 'vjs-menu-title', | ||||
|         innerHTML: toTitleCase(this.kind_), | ||||
|         tabIndex: -1 | ||||
|       }); | ||||
|  | ||||
|       menu.children_.unshift(title); | ||||
|       Dom.insertElFirst(title, menu.contentEl()); | ||||
|     } else { | ||||
|         // We will empty out the menu children each time because we want a  | ||||
|         // fresh new menu child list each time | ||||
|         items.forEach(item => menu.removeChild(item)); | ||||
|         // Empty out the ChaptersButton menu items because we no longer need them | ||||
|         items = []; | ||||
|       // We will empty out the menu children each time because we want a | ||||
|       // fresh new menu child list each time | ||||
|       items.forEach(item => menu.removeChild(item)); | ||||
|       // Empty out the ChaptersButton menu items because we no longer need them | ||||
|       items = []; | ||||
|     } | ||||
|  | ||||
|     if (chaptersTrack && chaptersTrack.cues == null) { | ||||
|       chaptersTrack['mode'] = 'hidden'; | ||||
|     if (chaptersTrack && (chaptersTrack.cues === null || chaptersTrack.cues === undefined)) { | ||||
|       chaptersTrack.mode = 'hidden'; | ||||
|  | ||||
|       let remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(chaptersTrack); | ||||
|       const remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(chaptersTrack); | ||||
|  | ||||
|       if (remoteTextTrackEl) { | ||||
|         remoteTextTrackEl.addEventListener('load', (event) => this.update()); | ||||
| @@ -118,14 +117,14 @@ class ChaptersButton extends TextTrackButton { | ||||
|     } | ||||
|  | ||||
|     if (chaptersTrack && chaptersTrack.cues && chaptersTrack.cues.length > 0) { | ||||
|       let cues = chaptersTrack['cues'], cue; | ||||
|       const cues = chaptersTrack.cues; | ||||
|  | ||||
|       for (let i = 0, l = cues.length; i < l; i++) { | ||||
|         cue = cues[i]; | ||||
|         const cue = cues[i]; | ||||
|  | ||||
|         let mi = new ChaptersTrackMenuItem(this.player_, { | ||||
|           'track': chaptersTrack, | ||||
|           'cue': cue | ||||
|         const mi = new ChaptersTrackMenuItem(this.player_, { | ||||
|           cue, | ||||
|           track: chaptersTrack | ||||
|         }); | ||||
|  | ||||
|         items.push(mi); | ||||
|   | ||||
| @@ -15,14 +15,14 @@ import * as Fn from '../../utils/fn.js'; | ||||
|  */ | ||||
| class ChaptersTrackMenuItem extends MenuItem { | ||||
|  | ||||
|   constructor(player, options){ | ||||
|     let track = options['track']; | ||||
|     let cue = options['cue']; | ||||
|     let currentTime = player.currentTime(); | ||||
|   constructor(player, options) { | ||||
|     const track = options.track; | ||||
|     const cue = options.cue; | ||||
|     const currentTime = player.currentTime(); | ||||
|  | ||||
|     // Modify options for parent MenuItem class's init. | ||||
|     options['label'] = cue.text; | ||||
|     options['selected'] = (cue['startTime'] <= currentTime && currentTime < cue['endTime']); | ||||
|     options.label = cue.text; | ||||
|     options.selected = (cue.startTime <= currentTime && currentTime < cue.endTime); | ||||
|     super(player, options); | ||||
|  | ||||
|     this.track = track; | ||||
| @@ -47,11 +47,11 @@ class ChaptersTrackMenuItem extends MenuItem { | ||||
|    * @method update | ||||
|    */ | ||||
|   update() { | ||||
|     let cue = this.cue; | ||||
|     let currentTime = this.player_.currentTime(); | ||||
|     const cue = this.cue; | ||||
|     const currentTime = this.player_.currentTime(); | ||||
|  | ||||
|     // vjs.log(currentTime, cue.startTime); | ||||
|     this.selected(cue['startTime'] <= currentTime && currentTime < cue['endTime']); | ||||
|     this.selected(cue.startTime <= currentTime && currentTime < cue.endTime); | ||||
|   } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -16,14 +16,14 @@ import * as Fn from '../../utils/fn.js'; | ||||
|  */ | ||||
| class DescriptionsButton extends TextTrackButton { | ||||
|  | ||||
|   constructor(player, options, ready){ | ||||
|   constructor(player, options, ready) { | ||||
|     super(player, options, ready); | ||||
|     this.el_.setAttribute('aria-label', 'Descriptions Menu'); | ||||
|  | ||||
|     let tracks = player.textTracks(); | ||||
|     const tracks = player.textTracks(); | ||||
|  | ||||
|     if (tracks) { | ||||
|       let changeHandler = Fn.bind(this, this.handleTracksChange); | ||||
|       const changeHandler = Fn.bind(this, this.handleTracksChange); | ||||
|  | ||||
|       tracks.addEventListener('change', changeHandler); | ||||
|       this.on('dispose', function() { | ||||
| @@ -37,14 +37,15 @@ class DescriptionsButton extends TextTrackButton { | ||||
|    * | ||||
|    * @method handleTracksChange | ||||
|    */ | ||||
|   handleTracksChange(event){ | ||||
|     let tracks = this.player().textTracks(); | ||||
|   handleTracksChange(event) { | ||||
|     const tracks = this.player().textTracks(); | ||||
|     let disabled = false; | ||||
|  | ||||
|     // Check whether a track of a different kind is showing | ||||
|     for (let i = 0, l = tracks.length; i < l; i++) { | ||||
|       let track = tracks[i]; | ||||
|       if (track['kind'] !== this.kind_ && track['mode'] === 'showing') { | ||||
|       const track = tracks[i]; | ||||
|  | ||||
|       if (track.kind !== this.kind_ && track.mode === 'showing') { | ||||
|         disabled = true; | ||||
|         break; | ||||
|       } | ||||
|   | ||||
| @@ -14,19 +14,19 @@ import Component from '../../component.js'; | ||||
|  */ | ||||
| class OffTextTrackMenuItem extends TextTrackMenuItem { | ||||
|  | ||||
|   constructor(player, options){ | ||||
|   constructor(player, options) { | ||||
|     // Create pseudo track info | ||||
|     // Requires options['kind'] | ||||
|     options['track'] = { | ||||
|       'kind': options['kind'], | ||||
|       'player': player, | ||||
|       'label': options['kind'] + ' off', | ||||
|       'default': false, | ||||
|       'mode': 'disabled' | ||||
|     options.track = { | ||||
|       player, | ||||
|       kind: options.kind, | ||||
|       label: options.kind + ' off', | ||||
|       default: false, | ||||
|       mode: 'disabled' | ||||
|     }; | ||||
|  | ||||
|     // MenuItem is selectable | ||||
|     options['selectable'] = true; | ||||
|     options.selectable = true; | ||||
|  | ||||
|     super(player, options); | ||||
|     this.selected(true); | ||||
| @@ -38,13 +38,14 @@ class OffTextTrackMenuItem extends TextTrackMenuItem { | ||||
|    * @param {Object} event Event object | ||||
|    * @method handleTracksChange | ||||
|    */ | ||||
|   handleTracksChange(event){ | ||||
|     let tracks = this.player().textTracks(); | ||||
|   handleTracksChange(event) { | ||||
|     const tracks = this.player().textTracks(); | ||||
|     let selected = true; | ||||
|  | ||||
|     for (let i = 0, l = tracks.length; i < l; i++) { | ||||
|       let track = tracks[i]; | ||||
|       if (track['kind'] === this.track['kind'] && track['mode'] === 'showing') { | ||||
|       const track = tracks[i]; | ||||
|  | ||||
|       if (track.kind === this.track.kind && track.mode === 'showing') { | ||||
|         selected = false; | ||||
|         break; | ||||
|       } | ||||
|   | ||||
| @@ -15,9 +15,9 @@ import Component from '../../component.js'; | ||||
|  */ | ||||
| class SubtitlesButton extends TextTrackButton { | ||||
|  | ||||
|   constructor(player, options, ready){ | ||||
|   constructor(player, options, ready) { | ||||
|     super(player, options, ready); | ||||
|     this.el_.setAttribute('aria-label','Subtitles Menu'); | ||||
|     this.el_.setAttribute('aria-label', 'Subtitles Menu'); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|   | ||||
| @@ -3,7 +3,6 @@ | ||||
|  */ | ||||
| import TrackButton from '../track-button.js'; | ||||
| import Component from '../../component.js'; | ||||
| import * as Fn from '../../utils/fn.js'; | ||||
| import TextTrackMenuItem from './text-track-menu-item.js'; | ||||
| import OffTextTrackMenuItem from './off-text-track-menu-item.js'; | ||||
|  | ||||
| @@ -17,7 +16,7 @@ import OffTextTrackMenuItem from './off-text-track-menu-item.js'; | ||||
|  */ | ||||
| class TextTrackButton extends TrackButton { | ||||
|  | ||||
|   constructor(player, options = {}){ | ||||
|   constructor(player, options = {}) { | ||||
|     options.tracks = player.textTracks(); | ||||
|  | ||||
|     super(player, options); | ||||
| @@ -29,25 +28,25 @@ class TextTrackButton extends TrackButton { | ||||
|    * @return {Array} Array of menu items | ||||
|    * @method createItems | ||||
|    */ | ||||
|   createItems(items=[]) { | ||||
|   createItems(items = []) { | ||||
|     // Add an OFF menu item to turn all tracks off | ||||
|     items.push(new OffTextTrackMenuItem(this.player_, { 'kind': this.kind_ })); | ||||
|     items.push(new OffTextTrackMenuItem(this.player_, {kind: this.kind_})); | ||||
|  | ||||
|     let tracks = this.player_.textTracks(); | ||||
|     const tracks = this.player_.textTracks(); | ||||
|  | ||||
|     if (!tracks) { | ||||
|       return items; | ||||
|     } | ||||
|  | ||||
|     for (let i = 0; i < tracks.length; i++) { | ||||
|       let track = tracks[i]; | ||||
|       const track = tracks[i]; | ||||
|  | ||||
|       // only add tracks that are of the appropriate kind and have a label | ||||
|       if (track['kind'] === this.kind_) { | ||||
|       if (track.kind === this.kind_) { | ||||
|         items.push(new TextTrackMenuItem(this.player_, { | ||||
|           track, | ||||
|           // MenuItem is selectable | ||||
|           'selectable': true, | ||||
|           'track': track | ||||
|           selectable: true | ||||
|         })); | ||||
|       } | ||||
|     } | ||||
|   | ||||
| @@ -17,20 +17,20 @@ import document from 'global/document'; | ||||
|  */ | ||||
| class TextTrackMenuItem extends MenuItem { | ||||
|  | ||||
|   constructor(player, options){ | ||||
|     let track = options['track']; | ||||
|     let tracks = player.textTracks(); | ||||
|   constructor(player, options) { | ||||
|     const track = options.track; | ||||
|     const tracks = player.textTracks(); | ||||
|  | ||||
|     // Modify options for parent MenuItem class's init. | ||||
|     options['label'] = track['label'] || track['language'] || 'Unknown'; | ||||
|     options['selected'] = track['default'] || track['mode'] === 'showing'; | ||||
|     options.label = track.label || track.language || 'Unknown'; | ||||
|     options.selected = track.default || track.mode === 'showing'; | ||||
|  | ||||
|     super(player, options); | ||||
|  | ||||
|     this.track = track; | ||||
|  | ||||
|     if (tracks) { | ||||
|       let changeHandler = Fn.bind(this, this.handleTracksChange); | ||||
|       const changeHandler = Fn.bind(this, this.handleTracksChange); | ||||
|  | ||||
|       tracks.addEventListener('change', changeHandler); | ||||
|       this.on('dispose', function() { | ||||
| @@ -52,7 +52,9 @@ class TextTrackMenuItem extends MenuItem { | ||||
|           // Android 2.3 throws an Illegal Constructor error for window.Event | ||||
|           try { | ||||
|             event = new window.Event('change'); | ||||
|           } catch(err){} | ||||
|           } catch (err) { | ||||
|             // continue regardless of error | ||||
|           } | ||||
|         } | ||||
|  | ||||
|         if (!event) { | ||||
| @@ -71,24 +73,26 @@ class TextTrackMenuItem extends MenuItem { | ||||
|    * @method handleClick | ||||
|    */ | ||||
|   handleClick(event) { | ||||
|     let kind = this.track['kind']; | ||||
|     let tracks = this.player_.textTracks(); | ||||
|     const kind = this.track.kind; | ||||
|     const tracks = this.player_.textTracks(); | ||||
|  | ||||
|     super.handleClick(event); | ||||
|  | ||||
|     if (!tracks) return; | ||||
|     if (!tracks) { | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     for (let i = 0; i < tracks.length; i++) { | ||||
|       let track = tracks[i]; | ||||
|       const track = tracks[i]; | ||||
|  | ||||
|       if (track['kind'] !== kind) { | ||||
|       if (track.kind !== kind) { | ||||
|         continue; | ||||
|       } | ||||
|  | ||||
|       if (track === this.track) { | ||||
|         track['mode'] = 'showing'; | ||||
|         track.mode = 'showing'; | ||||
|       } else { | ||||
|         track['mode'] = 'disabled'; | ||||
|         track.mode = 'disabled'; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| @@ -98,8 +102,8 @@ class TextTrackMenuItem extends MenuItem { | ||||
|    * | ||||
|    * @method handleTracksChange | ||||
|    */ | ||||
|   handleTracksChange(event){ | ||||
|     this.selected(this.track['mode'] === 'showing'); | ||||
|   handleTracksChange(event) { | ||||
|     this.selected(this.track.mode === 'showing'); | ||||
|   } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -15,7 +15,7 @@ import formatTime from '../../utils/format-time.js'; | ||||
|  */ | ||||
| class CurrentTimeDisplay extends Component { | ||||
|  | ||||
|   constructor(player, options){ | ||||
|   constructor(player, options) { | ||||
|     super(player, options); | ||||
|  | ||||
|     this.on(player, 'timeupdate', this.updateContent); | ||||
| @@ -28,14 +28,14 @@ class CurrentTimeDisplay extends Component { | ||||
|    * @method createEl | ||||
|    */ | ||||
|   createEl() { | ||||
|     let el = super.createEl('div', { | ||||
|     const el = super.createEl('div', { | ||||
|       className: 'vjs-current-time vjs-time-control vjs-control' | ||||
|     }); | ||||
|  | ||||
|     this.contentEl_ = Dom.createEl('div', { | ||||
|       className: 'vjs-current-time-display', | ||||
|       // label the current time for screen reader users | ||||
|       innerHTML: '<span class="vjs-control-text">Current Time </span>' + '0:00', | ||||
|       innerHTML: '<span class="vjs-control-text">Current Time </span>' + '0:00' | ||||
|     }, { | ||||
|       // tell screen readers not to automatically read the time as it changes | ||||
|       'aria-live': 'off' | ||||
| @@ -52,9 +52,10 @@ class CurrentTimeDisplay extends Component { | ||||
|    */ | ||||
|   updateContent() { | ||||
|     // Allows for smooth scrubbing, when player can't keep up. | ||||
|     let time = (this.player_.scrubbing()) ? this.player_.getCache().currentTime : this.player_.currentTime(); | ||||
|     let localizedText = this.localize('Current Time'); | ||||
|     let formattedTime = formatTime(time, this.player_.duration()); | ||||
|     const time = (this.player_.scrubbing()) ? this.player_.getCache().currentTime : this.player_.currentTime(); | ||||
|     const localizedText = this.localize('Current Time'); | ||||
|     const formattedTime = formatTime(time, this.player_.duration()); | ||||
|  | ||||
|     if (formattedTime !== this.formattedTime_) { | ||||
|       this.formattedTime_ = formattedTime; | ||||
|       this.contentEl_.innerHTML = `<span class="vjs-control-text">${localizedText}</span> ${formattedTime}`; | ||||
|   | ||||
| @@ -15,7 +15,7 @@ import formatTime from '../../utils/format-time.js'; | ||||
|  */ | ||||
| class DurationDisplay extends Component { | ||||
|  | ||||
|   constructor(player, options){ | ||||
|   constructor(player, options) { | ||||
|     super(player, options); | ||||
|  | ||||
|     this.on(player, 'durationchange', this.updateContent); | ||||
| @@ -28,7 +28,7 @@ class DurationDisplay extends Component { | ||||
|    * @method createEl | ||||
|    */ | ||||
|   createEl() { | ||||
|     let el = super.createEl('div', { | ||||
|     const el = super.createEl('div', { | ||||
|       className: 'vjs-duration vjs-time-control vjs-control' | ||||
|     }); | ||||
|  | ||||
| @@ -51,12 +51,15 @@ class DurationDisplay extends Component { | ||||
|    * @method updateContent | ||||
|    */ | ||||
|   updateContent() { | ||||
|     let duration = this.player_.duration(); | ||||
|     const duration = this.player_.duration(); | ||||
|  | ||||
|     if (duration && this.duration_ !== duration) { | ||||
|       this.duration_ = duration; | ||||
|       let localizedText = this.localize('Duration Time'); | ||||
|       let formattedTime = formatTime(duration); | ||||
|       this.contentEl_.innerHTML = `<span class="vjs-control-text">${localizedText}</span> ${formattedTime}`; // label the duration time for screen reader users | ||||
|       const localizedText = this.localize('Duration Time'); | ||||
|       const formattedTime = formatTime(duration); | ||||
|  | ||||
|       // label the duration time for screen reader users | ||||
|       this.contentEl_.innerHTML = `<span class="vjs-control-text">${localizedText}</span> ${formattedTime}`; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   | ||||
| @@ -15,7 +15,7 @@ import formatTime from '../../utils/format-time.js'; | ||||
|  */ | ||||
| class RemainingTimeDisplay extends Component { | ||||
|  | ||||
|   constructor(player, options){ | ||||
|   constructor(player, options) { | ||||
|     super(player, options); | ||||
|  | ||||
|     this.on(player, 'timeupdate', this.updateContent); | ||||
| @@ -29,14 +29,14 @@ class RemainingTimeDisplay extends Component { | ||||
|    * @method createEl | ||||
|    */ | ||||
|   createEl() { | ||||
|     let el = super.createEl('div', { | ||||
|     const el = super.createEl('div', { | ||||
|       className: 'vjs-remaining-time vjs-time-control vjs-control' | ||||
|     }); | ||||
|  | ||||
|     this.contentEl_ = Dom.createEl('div', { | ||||
|       className: 'vjs-remaining-time-display', | ||||
|       // label the remaining time for screen reader users | ||||
|       innerHTML: `<span class="vjs-control-text">${this.localize('Remaining Time')}</span> -0:00`, | ||||
|       innerHTML: `<span class="vjs-control-text">${this.localize('Remaining Time')}</span> -0:00` | ||||
|     }, { | ||||
|       // tell screen readers not to automatically read the time as it changes | ||||
|       'aria-live': 'off' | ||||
| @@ -55,6 +55,7 @@ class RemainingTimeDisplay extends Component { | ||||
|     if (this.player_.duration()) { | ||||
|       const localizedText = this.localize('Remaining Time'); | ||||
|       const formattedTime = formatTime(this.player_.remainingTime()); | ||||
|  | ||||
|       if (formattedTime !== this.formattedTime_) { | ||||
|         this.formattedTime_ = formattedTime; | ||||
|         this.contentEl_.innerHTML = `<span class="vjs-control-text">${localizedText}</span> -${formattedTime}`; | ||||
|   | ||||
| @@ -15,8 +15,8 @@ import * as Fn from '../utils/fn.js'; | ||||
|  */ | ||||
| class TrackButton extends MenuButton { | ||||
|  | ||||
|   constructor(player, options){ | ||||
|     let tracks = options.tracks; | ||||
|   constructor(player, options) { | ||||
|     const tracks = options.tracks; | ||||
|  | ||||
|     super(player, options); | ||||
|  | ||||
| @@ -28,7 +28,8 @@ class TrackButton extends MenuButton { | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     let updateHandler = Fn.bind(this, this.update); | ||||
|     const updateHandler = Fn.bind(this, this.update); | ||||
|  | ||||
|     tracks.addEventListener('removetrack', updateHandler); | ||||
|     tracks.addEventListener('addtrack', updateHandler); | ||||
|  | ||||
|   | ||||
| @@ -6,7 +6,7 @@ import Component from '../../component.js'; | ||||
| import * as Fn from '../../utils/fn.js'; | ||||
|  | ||||
| // Required children | ||||
| import VolumeLevel from './volume-level.js'; | ||||
| import './volume-level.js'; | ||||
|  | ||||
| /** | ||||
|  * The bar that contains the volume level and can be clicked on to adjust the level | ||||
| @@ -18,7 +18,7 @@ import VolumeLevel from './volume-level.js'; | ||||
|  */ | ||||
| class VolumeBar extends Slider { | ||||
|  | ||||
|   constructor(player, options){ | ||||
|   constructor(player, options) { | ||||
|     super(player, options); | ||||
|     this.on(player, 'volumechange', this.updateARIAAttributes); | ||||
|     player.ready(Fn.bind(this, this.updateARIAAttributes)); | ||||
| @@ -63,9 +63,8 @@ class VolumeBar extends Slider { | ||||
|   getPercent() { | ||||
|     if (this.player_.muted()) { | ||||
|       return 0; | ||||
|     } else { | ||||
|       return this.player_.volume(); | ||||
|     } | ||||
|     return this.player_.volume(); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
| @@ -95,7 +94,8 @@ class VolumeBar extends Slider { | ||||
|    */ | ||||
|   updateARIAAttributes() { | ||||
|     // Current value of volume bar as a percentage | ||||
|     let volume = (this.player_.volume() * 100).toFixed(2); | ||||
|     const volume = (this.player_.volume() * 100).toFixed(2); | ||||
|  | ||||
|     this.el_.setAttribute('aria-valuenow', volume); | ||||
|     this.el_.setAttribute('aria-valuetext', volume + '%'); | ||||
|   } | ||||
| @@ -106,7 +106,7 @@ VolumeBar.prototype.options_ = { | ||||
|   children: [ | ||||
|     'volumeLevel' | ||||
|   ], | ||||
|   'barName': 'volumeLevel' | ||||
|   barName: 'volumeLevel' | ||||
| }; | ||||
|  | ||||
| VolumeBar.prototype.playerEvent = 'volumechange'; | ||||
|   | ||||
| @@ -4,7 +4,7 @@ | ||||
| import Component from '../../component.js'; | ||||
|  | ||||
| // Required children | ||||
| import VolumeBar from './volume-bar.js'; | ||||
| import './volume-bar.js'; | ||||
|  | ||||
| /** | ||||
|  * The component for controlling the volume level | ||||
| @@ -16,15 +16,15 @@ import VolumeBar from './volume-bar.js'; | ||||
|  */ | ||||
| class VolumeControl extends Component { | ||||
|  | ||||
|   constructor(player, options){ | ||||
|   constructor(player, options) { | ||||
|     super(player, options); | ||||
|  | ||||
|     // hide volume controls when they're not supported by the current tech | ||||
|     if (player.tech_ && player.tech_['featuresVolumeControl'] === false) { | ||||
|     if (player.tech_ && player.tech_.featuresVolumeControl === false) { | ||||
|       this.addClass('vjs-hidden'); | ||||
|     } | ||||
|     this.on(player, 'loadstart', function(){ | ||||
|       if (player.tech_['featuresVolumeControl'] === false) { | ||||
|     this.on(player, 'loadstart', function() { | ||||
|       if (player.tech_.featuresVolumeControl === false) { | ||||
|         this.addClass('vjs-hidden'); | ||||
|       } else { | ||||
|         this.removeClass('vjs-hidden'); | ||||
|   | ||||
| @@ -18,7 +18,7 @@ import VolumeBar from './volume-control/volume-bar.js'; | ||||
|  */ | ||||
| class VolumeMenuButton extends PopupButton { | ||||
|  | ||||
|   constructor(player, options={}){ | ||||
|   constructor(player, options = {}) { | ||||
|     // Default to inline | ||||
|     if (options.inline === undefined) { | ||||
|       options.inline = true; | ||||
| @@ -48,7 +48,7 @@ class VolumeMenuButton extends PopupButton { | ||||
|  | ||||
|     // hide mute toggle if the current tech doesn't support volume control | ||||
|     function updateVisibility() { | ||||
|       if (player.tech_ && player.tech_['featuresVolumeControl'] === false) { | ||||
|       if (player.tech_ && player.tech_.featuresVolumeControl === false) { | ||||
|         this.addClass('vjs-hidden'); | ||||
|       } else { | ||||
|         this.removeClass('vjs-hidden'); | ||||
| @@ -58,19 +58,19 @@ class VolumeMenuButton extends PopupButton { | ||||
|     updateVisibility.call(this); | ||||
|     this.on(player, 'loadstart', updateVisibility); | ||||
|  | ||||
|     this.on(this.volumeBar, ['slideractive', 'focus'], function(){ | ||||
|     this.on(this.volumeBar, ['slideractive', 'focus'], function() { | ||||
|       this.addClass('vjs-slider-active'); | ||||
|     }); | ||||
|  | ||||
|     this.on(this.volumeBar, ['sliderinactive', 'blur'], function(){ | ||||
|     this.on(this.volumeBar, ['sliderinactive', 'blur'], function() { | ||||
|       this.removeClass('vjs-slider-active'); | ||||
|     }); | ||||
|  | ||||
|     this.on(this.volumeBar, ['focus'], function(){ | ||||
|     this.on(this.volumeBar, ['focus'], function() { | ||||
|       this.addClass('vjs-lock-showing'); | ||||
|     }); | ||||
|  | ||||
|     this.on(this.volumeBar, ['blur'], function(){ | ||||
|     this.on(this.volumeBar, ['blur'], function() { | ||||
|       this.removeClass('vjs-lock-showing'); | ||||
|     }); | ||||
|   } | ||||
| @@ -83,7 +83,8 @@ class VolumeMenuButton extends PopupButton { | ||||
|    */ | ||||
|   buildCSSClass() { | ||||
|     let orientationClass = ''; | ||||
|     if (!!this.options_.vertical) { | ||||
|  | ||||
|     if (this.options_.vertical) { | ||||
|       orientationClass = 'vjs-volume-menu-button-vertical'; | ||||
|     } else { | ||||
|       orientationClass = 'vjs-volume-menu-button-horizontal'; | ||||
| @@ -99,11 +100,11 @@ class VolumeMenuButton extends PopupButton { | ||||
|    * @method createPopup | ||||
|    */ | ||||
|   createPopup() { | ||||
|     let popup = new Popup(this.player_, { | ||||
|     const popup = new Popup(this.player_, { | ||||
|       contentElType: 'div' | ||||
|     }); | ||||
|  | ||||
|     let vb = new VolumeBar(this.player_, this.options_.volumeBar); | ||||
|     const vb = new VolumeBar(this.player_, this.options_.volumeBar); | ||||
|  | ||||
|     popup.addChild(vb); | ||||
|  | ||||
|   | ||||
| @@ -3,8 +3,6 @@ | ||||
|  */ | ||||
| import Component from './component'; | ||||
| import ModalDialog from './modal-dialog'; | ||||
|  | ||||
| import * as Dom from './utils/dom'; | ||||
| import mergeOptions from './utils/merge-options'; | ||||
|  | ||||
| /** | ||||
| @@ -46,6 +44,7 @@ class ErrorDisplay extends ModalDialog { | ||||
|    */ | ||||
|   content() { | ||||
|     let error = this.player().error(); | ||||
|  | ||||
|     return error ? this.localize(error.message) : ''; | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -3,7 +3,7 @@ | ||||
|  */ | ||||
| import * as Events from './utils/events.js'; | ||||
|  | ||||
| var EventTarget = function() {}; | ||||
| const EventTarget = function() {}; | ||||
|  | ||||
| EventTarget.prototype.allowedEvents_ = {}; | ||||
|  | ||||
| @@ -11,21 +11,25 @@ EventTarget.prototype.on = function(type, fn) { | ||||
|   // Remove the addEventListener alias before calling Events.on | ||||
|   // so we don't get into an infinite type loop | ||||
|   let ael = this.addEventListener; | ||||
|  | ||||
|   this.addEventListener = () => {}; | ||||
|   Events.on(this, type, fn); | ||||
|   this.addEventListener = ael; | ||||
| }; | ||||
|  | ||||
| EventTarget.prototype.addEventListener = EventTarget.prototype.on; | ||||
|  | ||||
| EventTarget.prototype.off = function(type, fn) { | ||||
|   Events.off(this, type, fn); | ||||
| }; | ||||
|  | ||||
| EventTarget.prototype.removeEventListener = EventTarget.prototype.off; | ||||
|  | ||||
| EventTarget.prototype.one = function(type, fn) { | ||||
|   // Remove the addEventListener alias before calling Events.on | ||||
|   // so we don't get into an infinite type loop | ||||
|   let ael = this.addEventListener; | ||||
|  | ||||
|   this.addEventListener = () => {}; | ||||
|   Events.one(this, type, fn); | ||||
|   this.addEventListener = ael; | ||||
| @@ -35,9 +39,7 @@ EventTarget.prototype.trigger = function(event) { | ||||
|   let type = event.type || event; | ||||
|  | ||||
|   if (typeof event === 'string') { | ||||
|     event = { | ||||
|       type: type | ||||
|     }; | ||||
|     event = {type}; | ||||
|   } | ||||
|   event = Events.fixEvent(event); | ||||
|  | ||||
| @@ -47,6 +49,7 @@ EventTarget.prototype.trigger = function(event) { | ||||
|  | ||||
|   Events.trigger(this, event); | ||||
| }; | ||||
|  | ||||
| // The standard DOM EventTarget.dispatchEvent() is aliased to trigger() | ||||
| EventTarget.prototype.dispatchEvent = EventTarget.prototype.trigger; | ||||
|  | ||||
|   | ||||
| @@ -7,7 +7,7 @@ import log from './utils/log'; | ||||
|  * Both work the same but node adds `super_` to the subClass | ||||
|  * and Bable adds the superClass as __proto__. Both seem useful. | ||||
|  */ | ||||
| const _inherits = function (subClass, superClass) { | ||||
| const _inherits = function(subClass, superClass) { | ||||
|   if (typeof superClass !== 'function' && superClass !== null) { | ||||
|     throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); | ||||
|   } | ||||
| @@ -44,10 +44,11 @@ const _inherits = function (subClass, superClass) { | ||||
|  * }); | ||||
|  * ``` | ||||
|  */ | ||||
| const extendFn = function(superClass, subClassMethods={}) { | ||||
| const extendFn = function(superClass, subClassMethods = {}) { | ||||
|   let subClass = function() { | ||||
|     superClass.apply(this, arguments); | ||||
|   }; | ||||
|  | ||||
|   let methods = {}; | ||||
|  | ||||
|   if (typeof subClassMethods === 'object') { | ||||
| @@ -66,7 +67,7 @@ const extendFn = function(superClass, subClassMethods={}) { | ||||
|   _inherits(subClass, superClass); | ||||
|  | ||||
|   // Extend subObj's prototype with functions and other properties from props | ||||
|   for (var name in methods) { | ||||
|   for (let name in methods) { | ||||
|     if (methods.hasOwnProperty(name)) { | ||||
|       subClass.prototype[name] = methods[name]; | ||||
|     } | ||||
|   | ||||
| @@ -74,7 +74,7 @@ for (let i = 0; i < apiMap.length; i++) { | ||||
|  | ||||
| // map the browser API names to the spec API names | ||||
| if (browserApi) { | ||||
|   for (let i=0; i<browserApi.length; i++) { | ||||
|   for (let i = 0; i < browserApi.length; i++) { | ||||
|     FullscreenApi[specApi[i]] = browserApi[i]; | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -8,13 +8,13 @@ import assign from 'object.assign'; | ||||
|  * | ||||
|  * @param {Number} code The media error code | ||||
|  */ | ||||
| let MediaError = function(code){ | ||||
| let MediaError = function(code) { | ||||
|   if (typeof code === 'number') { | ||||
|     this.code = code; | ||||
|   } else if (typeof code === 'string') { | ||||
|     // default code is zero, so this is a custom error | ||||
|     this.message = code; | ||||
|   } else if (typeof code === 'object') { // object | ||||
|   } else if (typeof code === 'object') { | ||||
|     assign(this, code); | ||||
|   } | ||||
|  | ||||
| @@ -52,13 +52,15 @@ MediaError.prototype.message = ''; | ||||
|  */ | ||||
| MediaError.prototype.status = null; | ||||
|  | ||||
| // These errors are indexed by the W3C standard numeric value. The order | ||||
| // should not be changed! | ||||
| MediaError.errorTypes = [ | ||||
|   'MEDIA_ERR_CUSTOM',            // = 0 | ||||
|   'MEDIA_ERR_ABORTED',           // = 1 | ||||
|   'MEDIA_ERR_NETWORK',           // = 2 | ||||
|   'MEDIA_ERR_DECODE',            // = 3 | ||||
|   'MEDIA_ERR_SRC_NOT_SUPPORTED', // = 4 | ||||
|   'MEDIA_ERR_ENCRYPTED'          // = 5 | ||||
|   'MEDIA_ERR_CUSTOM', | ||||
|   'MEDIA_ERR_ABORTED', | ||||
|   'MEDIA_ERR_NETWORK', | ||||
|   'MEDIA_ERR_DECODE', | ||||
|   'MEDIA_ERR_SRC_NOT_SUPPORTED', | ||||
|   'MEDIA_ERR_ENCRYPTED' | ||||
| ]; | ||||
|  | ||||
| MediaError.defaultMessages = { | ||||
| @@ -71,7 +73,7 @@ MediaError.defaultMessages = { | ||||
|  | ||||
| // Add types as properties on MediaError | ||||
| // e.g. MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED = 4; | ||||
| for (var errNum = 0; errNum < MediaError.errorTypes.length; errNum++) { | ||||
| for (let errNum = 0; errNum < MediaError.errorTypes.length; errNum++) { | ||||
|   MediaError[MediaError.errorTypes[errNum]] = errNum; | ||||
|   // values should be accessible on both the class and instance | ||||
|   MediaError.prototype[MediaError.errorTypes[errNum]] = errNum; | ||||
|   | ||||
| @@ -18,7 +18,7 @@ import toTitleCase from '../utils/to-title-case.js'; | ||||
|  */ | ||||
| class MenuButton extends ClickableComponent { | ||||
|  | ||||
|   constructor(player, options={}){ | ||||
|   constructor(player, options = {}) { | ||||
|     super(player, options); | ||||
|  | ||||
|     this.update(); | ||||
| @@ -36,7 +36,7 @@ class MenuButton extends ClickableComponent { | ||||
|    * @method update | ||||
|    */ | ||||
|   update() { | ||||
|     let menu = this.createMenu(); | ||||
|     const menu = this.createMenu(); | ||||
|  | ||||
|     if (this.menu) { | ||||
|       this.removeChild(this.menu); | ||||
| @@ -68,24 +68,25 @@ class MenuButton extends ClickableComponent { | ||||
|    * @method createMenu | ||||
|    */ | ||||
|   createMenu() { | ||||
|     var menu = new Menu(this.player_); | ||||
|     const menu = new Menu(this.player_); | ||||
|  | ||||
|     // Add a title list item to the top | ||||
|     if (this.options_.title) { | ||||
|       let title = Dom.createEl('li', { | ||||
|       const title = Dom.createEl('li', { | ||||
|         className: 'vjs-menu-title', | ||||
|         innerHTML: toTitleCase(this.options_.title), | ||||
|         tabIndex: -1 | ||||
|       }); | ||||
|  | ||||
|       menu.children_.unshift(title); | ||||
|       Dom.insertElFirst(title, menu.contentEl()); | ||||
|     } | ||||
|  | ||||
|     this.items = this['createItems'](); | ||||
|     this.items = this.createItems(); | ||||
|  | ||||
|     if (this.items) { | ||||
|       // Add menu items to the menu | ||||
|       for (var i = 0; i < this.items.length; i++) { | ||||
|       for (let i = 0; i < this.items.length; i++) { | ||||
|         menu.addItem(this.items[i]); | ||||
|       } | ||||
|     } | ||||
| @@ -98,7 +99,7 @@ class MenuButton extends ClickableComponent { | ||||
|    * | ||||
|    * @method createItems | ||||
|    */ | ||||
|   createItems(){} | ||||
|   createItems() {} | ||||
|  | ||||
|   /** | ||||
|    * Create the component's DOM element | ||||
| @@ -119,7 +120,7 @@ class MenuButton extends ClickableComponent { | ||||
|    * @method buildCSSClass | ||||
|    */ | ||||
|   buildCSSClass() { | ||||
|     var menuButtonClass = 'vjs-menu-button'; | ||||
|     let menuButtonClass = 'vjs-menu-button'; | ||||
|  | ||||
|     // If the inline option is passed, we want to use different styles altogether. | ||||
|     if (this.options_.inline === true) { | ||||
| @@ -141,11 +142,11 @@ class MenuButton extends ClickableComponent { | ||||
|    * @method handleClick | ||||
|    */ | ||||
|   handleClick() { | ||||
|     this.one(this.menu.contentEl(), 'mouseleave', Fn.bind(this, function(e){ | ||||
|     this.one(this.menu.contentEl(), 'mouseleave', Fn.bind(this, function(e) { | ||||
|       this.unpressButton(); | ||||
|       this.el_.blur(); | ||||
|     })); | ||||
|     if (this.buttonPressed_){ | ||||
|     if (this.buttonPressed_) { | ||||
|       this.unpressButton(); | ||||
|     } else { | ||||
|       this.pressButton(); | ||||
| @@ -189,8 +190,8 @@ class MenuButton extends ClickableComponent { | ||||
|   handleSubmenuKeyPress(event) { | ||||
|  | ||||
|     // Escape (27) key or Tab (9) key unpress the 'button' | ||||
|     if (event.which === 27 || event.which === 9){ | ||||
|       if (this.buttonPressed_){ | ||||
|     if (event.which === 27 || event.which === 9) { | ||||
|       if (this.buttonPressed_) { | ||||
|         this.unpressButton(); | ||||
|       } | ||||
|       // Don't preventDefault for Tab key - we still want to lose focus | ||||
| @@ -210,7 +211,8 @@ class MenuButton extends ClickableComponent { | ||||
|       this.buttonPressed_ = true; | ||||
|       this.menu.lockShowing(); | ||||
|       this.el_.setAttribute('aria-expanded', 'true'); | ||||
|       this.menu.focus(); // set the focus into the submenu | ||||
|       // set the focus into the submenu | ||||
|       this.menu.focus(); | ||||
|     } | ||||
|   } | ||||
|  | ||||
| @@ -224,7 +226,8 @@ class MenuButton extends ClickableComponent { | ||||
|       this.buttonPressed_ = false; | ||||
|       this.menu.unlockShowing(); | ||||
|       this.el_.setAttribute('aria-expanded', 'false'); | ||||
|       this.el_.focus(); // Set focus back to this menu button | ||||
|       // Set focus back to this menu button | ||||
|       this.el_.focus(); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   | ||||
| @@ -18,9 +18,9 @@ class MenuItem extends ClickableComponent { | ||||
|   constructor(player, options) { | ||||
|     super(player, options); | ||||
|  | ||||
|     this.selectable = options['selectable']; | ||||
|     this.selectable = options.selectable; | ||||
|  | ||||
|     this.selected(options['selected']); | ||||
|     this.selected(options.selected); | ||||
|  | ||||
|     if (this.selectable) { | ||||
|       // TODO: May need to be either menuitemcheckbox or menuitemradio, | ||||
| @@ -42,7 +42,7 @@ class MenuItem extends ClickableComponent { | ||||
|   createEl(type, props, attrs) { | ||||
|     return super.createEl('li', assign({ | ||||
|       className: 'vjs-menu-item', | ||||
|       innerHTML: this.localize(this.options_['label']), | ||||
|       innerHTML: this.localize(this.options_.label), | ||||
|       tabIndex: -1 | ||||
|     }, props), attrs); | ||||
|   } | ||||
| @@ -66,13 +66,13 @@ class MenuItem extends ClickableComponent { | ||||
|     if (this.selectable) { | ||||
|       if (selected) { | ||||
|         this.addClass('vjs-selected'); | ||||
|         this.el_.setAttribute('aria-checked','true'); | ||||
|         this.el_.setAttribute('aria-checked', 'true'); | ||||
|         // aria-checked isn't fully supported by browsers/screen readers, | ||||
|         // so indicate selected state to screen reader in the control text. | ||||
|         this.controlText(', selected'); | ||||
|       } else { | ||||
|         this.removeClass('vjs-selected'); | ||||
|         this.el_.setAttribute('aria-checked','false'); | ||||
|         this.el_.setAttribute('aria-checked', 'false'); | ||||
|         // Indicate un-selected state to screen reader | ||||
|         // Note that a space clears out the selected state text | ||||
|         this.controlText(' '); | ||||
|   | ||||
| @@ -15,7 +15,7 @@ import * as Events from '../utils/events.js'; | ||||
|  */ | ||||
| class Menu extends Component { | ||||
|  | ||||
|   constructor (player, options) { | ||||
|   constructor(player, options) { | ||||
|     super(player, options); | ||||
|  | ||||
|     this.focusedChild_ = -1; | ||||
| @@ -31,9 +31,9 @@ class Menu extends Component { | ||||
|    */ | ||||
|   addItem(component) { | ||||
|     this.addChild(component); | ||||
|     component.on('click', Fn.bind(this, function(){ | ||||
|     component.on('click', Fn.bind(this, function() { | ||||
|       this.unlockShowing(); | ||||
|       //TODO: Need to set keyboard focus back to the menuButton | ||||
|       // TODO: Need to set keyboard focus back to the menuButton | ||||
|     })); | ||||
|   } | ||||
|  | ||||
| @@ -44,21 +44,25 @@ class Menu extends Component { | ||||
|    * @method createEl | ||||
|    */ | ||||
|   createEl() { | ||||
|     let contentElType = this.options_.contentElType || 'ul'; | ||||
|     const contentElType = this.options_.contentElType || 'ul'; | ||||
|  | ||||
|     this.contentEl_ = Dom.createEl(contentElType, { | ||||
|       className: 'vjs-menu-content' | ||||
|     }); | ||||
|  | ||||
|     this.contentEl_.setAttribute('role', 'menu'); | ||||
|     var el = super.createEl('div', { | ||||
|  | ||||
|     const el = super.createEl('div', { | ||||
|       append: this.contentEl_, | ||||
|       className: 'vjs-menu' | ||||
|     }); | ||||
|  | ||||
|     el.setAttribute('role', 'presentation'); | ||||
|     el.appendChild(this.contentEl_); | ||||
|  | ||||
|     // Prevent clicks from bubbling up. Needed for Menu Buttons, | ||||
|     // where a click on the parent is significant | ||||
|     Events.on(el, 'click', function(event){ | ||||
|     Events.on(el, 'click', function(event) { | ||||
|       event.preventDefault(); | ||||
|       event.stopImmediatePropagation(); | ||||
|     }); | ||||
| @@ -72,11 +76,14 @@ class Menu extends Component { | ||||
|    * @param {Object} event Event object | ||||
|    * @method handleKeyPress | ||||
|    */ | ||||
|   handleKeyPress (event) { | ||||
|     if (event.which === 37 || event.which === 40) { // Left and Down Arrows | ||||
|   handleKeyPress(event) { | ||||
|     // Left and Down Arrows | ||||
|     if (event.which === 37 || event.which === 40) { | ||||
|       event.preventDefault(); | ||||
|       this.stepForward(); | ||||
|     } else if (event.which === 38 || event.which === 39) { // Up and Right Arrows | ||||
|  | ||||
|     // Up and Right Arrows | ||||
|     } else if (event.which === 38 || event.which === 39) { | ||||
|       event.preventDefault(); | ||||
|       this.stepBack(); | ||||
|     } | ||||
| @@ -87,21 +94,21 @@ class Menu extends Component { | ||||
|    * | ||||
|    * @method stepForward | ||||
|    */ | ||||
|    stepForward () { | ||||
|      let stepChild = 0; | ||||
|   stepForward() { | ||||
|     let stepChild = 0; | ||||
|  | ||||
|      if (this.focusedChild_ !== undefined) { | ||||
|        stepChild = this.focusedChild_ + 1; | ||||
|      } | ||||
|      this.focus(stepChild); | ||||
|    } | ||||
|     if (this.focusedChild_ !== undefined) { | ||||
|       stepChild = this.focusedChild_ + 1; | ||||
|     } | ||||
|     this.focus(stepChild); | ||||
|   } | ||||
|  | ||||
|    /** | ||||
|     * Move to previous (higher) menu item for keyboard users | ||||
|     * | ||||
|     * @method stepBack | ||||
|     */ | ||||
|   stepBack () { | ||||
|   /** | ||||
|    * Move to previous (higher) menu item for keyboard users | ||||
|    * | ||||
|    * @method stepBack | ||||
|    */ | ||||
|   stepBack() { | ||||
|     let stepChild = 0; | ||||
|  | ||||
|     if (this.focusedChild_ !== undefined) { | ||||
| @@ -116,10 +123,10 @@ class Menu extends Component { | ||||
|    * @param {Object|String} item Index of child item set focus on | ||||
|    * @method focus | ||||
|    */ | ||||
|   focus (item = 0) { | ||||
|     let children = this.children().slice(); | ||||
|     let haveTitle = children.length && children[0].className && | ||||
|       /vjs-menu-title/.test(children[0].className); | ||||
|   focus(item = 0) { | ||||
|     const children = this.children().slice(); | ||||
|     const haveTitle = children.length && children[0].className && | ||||
|       (/vjs-menu-title/).test(children[0].className); | ||||
|  | ||||
|     if (haveTitle) { | ||||
|       children.shift(); | ||||
|   | ||||
| @@ -3,10 +3,7 @@ | ||||
|  */ | ||||
| import * as Dom from './utils/dom'; | ||||
| import * as Fn from './utils/fn'; | ||||
| import log from './utils/log'; | ||||
|  | ||||
| import Component from './component'; | ||||
| import CloseButton from './close-button'; | ||||
|  | ||||
| const MODAL_CLASS_NAME = 'vjs-modal-dialog'; | ||||
| const ESC = 27; | ||||
| @@ -92,7 +89,7 @@ class ModalDialog extends Component { | ||||
|       'aria-describedby': `${this.id()}_description`, | ||||
|       'aria-hidden': 'true', | ||||
|       'aria-label': this.label(), | ||||
|       role: 'dialog' | ||||
|       'role': 'dialog' | ||||
|     }); | ||||
|   } | ||||
|  | ||||
| @@ -254,6 +251,7 @@ class ModalDialog extends Component { | ||||
|         // The close button should be a child of the modal - not its | ||||
|         // content element, so temporarily change the content element. | ||||
|         let temp = this.contentEl_; | ||||
|  | ||||
|         this.contentEl_ = this.el_; | ||||
|         close = this.addChild('closeButton', {controlText: 'Close Modal Dialog'}); | ||||
|         this.contentEl_ = temp; | ||||
|   | ||||
							
								
								
									
										312
									
								
								src/js/player.js
									
									
									
									
									
								
							
							
						
						
									
										312
									
								
								src/js/player.js
									
									
									
									
									
								
							| @@ -22,23 +22,25 @@ import safeParseTuple from 'safe-json-parse/tuple'; | ||||
| import assign from 'object.assign'; | ||||
| import mergeOptions from './utils/merge-options.js'; | ||||
| import textTrackConverter from './tracks/text-track-list-converter.js'; | ||||
| import ModalDialog from './modal-dialog'; | ||||
| import Tech from './tech/tech.js'; | ||||
| import AudioTrackList from './tracks/audio-track-list.js'; | ||||
| import VideoTrackList from './tracks/video-track-list.js'; | ||||
|  | ||||
| // Include required child components (importing also registers them) | ||||
| import MediaLoader from './tech/loader.js'; | ||||
| import PosterImage from './poster-image.js'; | ||||
| import TextTrackDisplay from './tracks/text-track-display.js'; | ||||
| import LoadingSpinner from './loading-spinner.js'; | ||||
| import BigPlayButton from './big-play-button.js'; | ||||
| import ControlBar from './control-bar/control-bar.js'; | ||||
| import ErrorDisplay from './error-display.js'; | ||||
| import TextTrackSettings from './tracks/text-track-settings.js'; | ||||
| import ModalDialog from './modal-dialog'; | ||||
| // The following imports are used only to ensure that the corresponding modules | ||||
| // are always included in the video.js package. Importing the modules will | ||||
| // execute them and they will register themselves with video.js. | ||||
| import './tech/loader.js'; | ||||
| import './poster-image.js'; | ||||
| import './tracks/text-track-display.js'; | ||||
| import './loading-spinner.js'; | ||||
| import './big-play-button.js'; | ||||
| import './control-bar/control-bar.js'; | ||||
| import './error-display.js'; | ||||
| import './tracks/text-track-settings.js'; | ||||
|  | ||||
| // Require html5 tech, at least for disposing the original video tag | ||||
| import Tech from './tech/tech.js'; | ||||
| import Html5 from './tech/html5.js'; | ||||
| // Import Html5 tech, at least for disposing the original video tag. | ||||
| import './tech/html5.js'; | ||||
|  | ||||
| /** | ||||
|  * An instance of the `Player` class is created when any of the Video.js setup methods are used to initialize a video. | ||||
| @@ -70,7 +72,7 @@ class Player extends Component { | ||||
|    * @param {Object=} options    Player options | ||||
|    * @param {Function=} ready    Ready callback function | ||||
|    */ | ||||
|   constructor(tag, options, ready){ | ||||
|   constructor(tag, options, ready) { | ||||
|     // Make sure tag ID exists | ||||
|     tag.id = tag.id || `vjs_video_${Guid.newGUID()}`; | ||||
|  | ||||
| @@ -96,11 +98,13 @@ class Player extends Component { | ||||
|     if (!options.language) { | ||||
|       if (typeof tag.closest === 'function') { | ||||
|         let closest = tag.closest('[lang]'); | ||||
|  | ||||
|         if (closest) { | ||||
|           options.language = closest.getAttribute('lang'); | ||||
|         } | ||||
|       } else { | ||||
|         let element = tag; | ||||
|  | ||||
|         while (element && element.nodeType === 1) { | ||||
|           if (Dom.getElAttributes(element).hasOwnProperty('lang')) { | ||||
|             options.language = element.getAttribute('lang'); | ||||
| @@ -124,7 +128,8 @@ class Player extends Component { | ||||
|                       'properties you want to override?'); | ||||
|     } | ||||
|  | ||||
|     this.tag = tag; // Store the original tag used to set options | ||||
|     // Store the original tag used to set options | ||||
|     this.tag = tag; | ||||
|  | ||||
|     // Store the tag attributes used to restore html5 element | ||||
|     this.tagAttributes = tag && Dom.getElAttributes(tag); | ||||
| @@ -179,7 +184,7 @@ class Player extends Component { | ||||
|     if (options.plugins) { | ||||
|       let plugins = options.plugins; | ||||
|  | ||||
|       Object.getOwnPropertyNames(plugins).forEach(function(name){ | ||||
|       Object.getOwnPropertyNames(plugins).forEach(function(name) { | ||||
|         if (typeof this[name] === 'function') { | ||||
|           this[name](plugins[name]); | ||||
|         } else { | ||||
| @@ -264,10 +269,18 @@ class Player extends Component { | ||||
|  | ||||
|     // Kill reference to this player | ||||
|     Player.players[this.id_] = null; | ||||
|     if (this.tag && this.tag.player) { this.tag.player = null; } | ||||
|     if (this.el_ && this.el_.player) { this.el_.player = null; } | ||||
|  | ||||
|     if (this.tech_) { this.tech_.dispose(); } | ||||
|     if (this.tag && this.tag.player) { | ||||
|       this.tag.player = null; | ||||
|     } | ||||
|  | ||||
|     if (this.el_ && this.el_.player) { | ||||
|       this.el_.player = null; | ||||
|     } | ||||
|  | ||||
|     if (this.tech_) { | ||||
|       this.tech_.dispose(); | ||||
|     } | ||||
|  | ||||
|     super.dispose(); | ||||
|   } | ||||
| @@ -290,7 +303,7 @@ class Player extends Component { | ||||
|     // ID will now reference player box, not the video tag | ||||
|     const attrs = Dom.getElAttributes(tag); | ||||
|  | ||||
|     Object.getOwnPropertyNames(attrs).forEach(function(attr){ | ||||
|     Object.getOwnPropertyNames(attrs).forEach(function(attr) { | ||||
|       // workaround so we don't totally break IE7 | ||||
|       // http://stackoverflow.com/questions/3653444/css-styles-not-applied-on-dynamic-elements-in-internet-explorer-7 | ||||
|       if (attr === 'class') { | ||||
| @@ -319,6 +332,7 @@ class Player extends Component { | ||||
|       this.styleEl_ = stylesheet.createStyleElement('vjs-styles-dimensions'); | ||||
|       let defaultsStyleEl = Dom.$('.vjs-styles-defaults'); | ||||
|       let head = Dom.$('head'); | ||||
|  | ||||
|       head.insertBefore(this.styleEl_, defaultsStyleEl ? defaultsStyleEl.nextSibling : head.firstChild); | ||||
|     } | ||||
|  | ||||
| @@ -330,8 +344,10 @@ class Player extends Component { | ||||
|  | ||||
|     // Hide any links within the video/audio tag, because IE doesn't hide them completely. | ||||
|     let links = tag.getElementsByTagName('a'); | ||||
|  | ||||
|     for (let i = 0; i < links.length; i++) { | ||||
|       let linkEl = links.item(i); | ||||
|  | ||||
|       Dom.addElClass(linkEl, 'vjs-hidden'); | ||||
|       linkEl.setAttribute('hidden', 'hidden'); | ||||
|     } | ||||
| @@ -348,7 +364,9 @@ class Player extends Component { | ||||
|     // insert the tag as the first child of the player element | ||||
|     // then manually add it to the children array so that this.addChild | ||||
|     // will work properly for other components | ||||
|     Dom.insertElFirst(tag, el); // Breaks iPhone, fixed in HTML5 setup. | ||||
|     // | ||||
|     // Breaks iPhone, fixed in HTML5 setup. | ||||
|     Dom.insertElFirst(tag, el); | ||||
|     this.children_.unshift(tag); | ||||
|  | ||||
|     this.el_ = el; | ||||
| @@ -444,7 +462,7 @@ class Player extends Component { | ||||
|     } | ||||
|  | ||||
|     // Check for width:height format | ||||
|     if (!/^\d+\:\d+$/.test(ratio)) { | ||||
|     if (!(/^\d+\:\d+$/).test(ratio)) { | ||||
|       throw new Error('Improper value supplied for aspect ratio. The format should be width:height, for example 16:9.'); | ||||
|     } | ||||
|     this.aspectRatio_ = ratio; | ||||
| @@ -516,14 +534,14 @@ class Player extends Component { | ||||
|       height = this.height_; | ||||
|     } else { | ||||
|       // Otherwise calculate the height from the ratio and the width | ||||
|       height = width  * ratioMultiplier; | ||||
|       height = width * ratioMultiplier; | ||||
|     } | ||||
|  | ||||
|     // Ensure the CSS class is valid by starting with an alpha character | ||||
|     if (/^[^a-zA-Z]/.test(this.id())) { | ||||
|       idClass = 'dimensions-'+this.id(); | ||||
|     if ((/^[^a-zA-Z]/).test(this.id())) { | ||||
|       idClass = 'dimensions-' + this.id(); | ||||
|     } else { | ||||
|       idClass = this.id()+'-dimensions'; | ||||
|       idClass = this.id() + '-dimensions'; | ||||
|     } | ||||
|  | ||||
|     // Ensure the right class is still on the player for the style element | ||||
| @@ -571,9 +589,9 @@ class Player extends Component { | ||||
|     this.isReady_ = false; | ||||
|  | ||||
|     // Grab tech-specific options from player options and add source and parent element to use. | ||||
|     var techOptions = assign({ | ||||
|     let techOptions = assign({ | ||||
|       source, | ||||
|       'nativeControlsForTouch': this.options_.nativeControlsForTouch, | ||||
|       'source': source, | ||||
|       'playerId': this.id(), | ||||
|       'techId': `${this.id()}_${techName}_api`, | ||||
|       'videoTracks': this.videoTracks_, | ||||
| @@ -602,13 +620,14 @@ class Player extends Component { | ||||
|     } | ||||
|  | ||||
|     // Initialize tech instance | ||||
|     let techComponent = Tech.getTech(techName); | ||||
|     let TechComponent = Tech.getTech(techName); | ||||
|  | ||||
|     // Support old behavior of techs being registered as components. | ||||
|     // Remove once that deprecated behavior is removed. | ||||
|     if (!techComponent) { | ||||
|       techComponent = Component.getComponent(techName); | ||||
|     if (!TechComponent) { | ||||
|       TechComponent = Component.getComponent(techName); | ||||
|     } | ||||
|     this.tech_ = new techComponent(techOptions); | ||||
|     this.tech_ = new TechComponent(techOptions); | ||||
|  | ||||
|     // player.triggerReady is always async, so don't need this to be async | ||||
|     this.tech_.ready(Fn.bind(this, this.handleTechReady_), true); | ||||
| @@ -704,6 +723,7 @@ class Player extends Component { | ||||
|       \`IWillNotUseThisInPlugins\` to the \`tech\` method. See | ||||
|       https://github.com/videojs/video.js/issues/2617 for more info. | ||||
|     `; | ||||
|  | ||||
|     window.alert(errorText); | ||||
|     throw new Error(errorText); | ||||
|   } | ||||
| @@ -794,9 +814,9 @@ class Player extends Component { | ||||
|     // This fixes both issues. Need to wait for API, so it updates displays correctly | ||||
|     if ((this.src() || this.currentSrc()) && this.tag && this.options_.autoplay && this.paused()) { | ||||
|       try { | ||||
|         delete this.tag.poster; // Chrome Fix. Fixed in Chrome v16. | ||||
|       } | ||||
|       catch (e) { | ||||
|         // Chrome Fix. Fixed in Chrome v16. | ||||
|         delete this.tag.poster; | ||||
|       } catch (e) { | ||||
|         log('deleting tag.poster throws in some browsers', e); | ||||
|       } | ||||
|       this.play(); | ||||
| @@ -954,9 +974,9 @@ class Player extends Component { | ||||
|    * @method handleTechFirstPlay_ | ||||
|    */ | ||||
|   handleTechFirstPlay_() { | ||||
|     //If the first starttime attribute is specified | ||||
|     //then we will start at the given offset in seconds | ||||
|     if(this.options_.starttime){ | ||||
|     // If the first starttime attribute is specified | ||||
|     // then we will start at the given offset in seconds | ||||
|     if (this.options_.starttime) { | ||||
|       this.currentTime(this.options_.starttime); | ||||
|     } | ||||
|  | ||||
| @@ -1024,7 +1044,9 @@ class Player extends Component { | ||||
|   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; | ||||
|     if (event.button !== 0) { | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     // When controls are disabled a click should not toggle playback because | ||||
|     // the click is considered a control | ||||
| @@ -1065,7 +1087,7 @@ class Player extends Component { | ||||
|    * @method handleTechTouchMove_ | ||||
|    */ | ||||
|   handleTechTouchMove_() { | ||||
|     if (this.userWasActive){ | ||||
|     if (this.userWasActive) { | ||||
|       this.reportUserActivity(); | ||||
|     } | ||||
|   } | ||||
| @@ -1127,6 +1149,7 @@ class Player extends Component { | ||||
|    */ | ||||
|   handleTechError_() { | ||||
|     let error = this.tech_.error(); | ||||
|  | ||||
|     this.error(error); | ||||
|   } | ||||
|  | ||||
| @@ -1181,7 +1204,8 @@ class Player extends Component { | ||||
|   } | ||||
|  | ||||
|   handleTechTextData_() { | ||||
|     var data = null; | ||||
|     let data = null; | ||||
|  | ||||
|     if (arguments.length > 1) { | ||||
|       data = arguments[1]; | ||||
|     } | ||||
| @@ -1259,15 +1283,17 @@ class Player extends Component { | ||||
|   techCall_(method, arg) { | ||||
|     // If it's not ready yet, call method when it is | ||||
|     if (this.tech_ && !this.tech_.isReady_) { | ||||
|       this.tech_.ready(function(){ | ||||
|       this.tech_.ready(function() { | ||||
|         this[method](arg); | ||||
|       }, true); | ||||
|  | ||||
|     // Otherwise call method now | ||||
|     } else { | ||||
|       try { | ||||
|         this.tech_ && this.tech_[method](arg); | ||||
|       } catch(e) { | ||||
|         if (this.tech_) { | ||||
|           this.tech_[method](arg); | ||||
|         } | ||||
|       } catch (e) { | ||||
|         log(e); | ||||
|         throw e; | ||||
|       } | ||||
| @@ -1290,18 +1316,17 @@ class Player extends Component { | ||||
|       // When that happens we'll catch the errors and inform tech that it's not ready any more. | ||||
|       try { | ||||
|         return this.tech_[method](); | ||||
|       } catch(e) { | ||||
|       } catch (e) { | ||||
|         // When building additional tech libs, an expected method may not be defined yet | ||||
|         if (this.tech_[method] === undefined) { | ||||
|           log(`Video.js: ${method} method not defined for ${this.techName_} playback technology.`, e); | ||||
|  | ||||
|         // When a method isn't available on the object it throws a TypeError | ||||
|         } else if (e.name === 'TypeError') { | ||||
|           log(`Video.js: ${method} unavailable on ${this.techName_} playback technology element.`, e); | ||||
|           this.tech_.isReady_ = false; | ||||
|         } else { | ||||
|           // When a method isn't available on the object it throws a TypeError | ||||
|           if (e.name === 'TypeError') { | ||||
|             log(`Video.js: ${method} unavailable on ${this.techName_} playback technology element.`, e); | ||||
|             this.tech_.isReady_ = false; | ||||
|           } else { | ||||
|             log(e); | ||||
|           } | ||||
|           log(e); | ||||
|         } | ||||
|         throw e; | ||||
|       } | ||||
| @@ -1414,7 +1439,8 @@ class Player extends Component { | ||||
|     // currentTime when scrubbing, but may not provide much performance benefit afterall. | ||||
|     // Should be tested. Also something has to read the actual current time or the cache will | ||||
|     // never get updated. | ||||
|     return this.cache_.currentTime = (this.techGet_('currentTime') || 0); | ||||
|     this.cache_.currentTime = (this.techGet_('currentTime') || 0); | ||||
|     return this.cache_.currentTime; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
| @@ -1495,10 +1521,10 @@ class Player extends Component { | ||||
|    * @method buffered | ||||
|    */ | ||||
|   buffered() { | ||||
|     var buffered = this.techGet_('buffered'); | ||||
|     let buffered = this.techGet_('buffered'); | ||||
|  | ||||
|     if (!buffered || !buffered.length) { | ||||
|       buffered = createTimeRange(0,0); | ||||
|       buffered = createTimeRange(0, 0); | ||||
|     } | ||||
|  | ||||
|     return buffered; | ||||
| @@ -1527,9 +1553,9 @@ class Player extends Component { | ||||
|    * @method bufferedEnd | ||||
|    */ | ||||
|   bufferedEnd() { | ||||
|     var buffered = this.buffered(), | ||||
|         duration = this.duration(), | ||||
|         end = buffered.end(buffered.length-1); | ||||
|     let buffered = this.buffered(); | ||||
|     let duration = this.duration(); | ||||
|     let end = buffered.end(buffered.length - 1); | ||||
|  | ||||
|     if (end > duration) { | ||||
|       end = duration; | ||||
| @@ -1557,7 +1583,8 @@ class Player extends Component { | ||||
|     let vol; | ||||
|  | ||||
|     if (percentAsDecimal !== undefined) { | ||||
|       vol = Math.max(0, Math.min(1, parseFloat(percentAsDecimal))); // Force value to between 0 and 1 | ||||
|       // Force value to between 0 and 1 | ||||
|       vol = Math.max(0, Math.min(1, parseFloat(percentAsDecimal))); | ||||
|       this.cache_.volume = vol; | ||||
|       this.techCall_('setVolume', vol); | ||||
|  | ||||
| @@ -1569,7 +1596,6 @@ class Player extends Component { | ||||
|     return (isNaN(vol)) ? 1 : vol; | ||||
|   } | ||||
|  | ||||
|  | ||||
|   /** | ||||
|    * Get the current muted state, or turn mute on or off | ||||
|    * ```js | ||||
| @@ -1589,7 +1615,7 @@ class Player extends Component { | ||||
|       this.techCall_('setMuted', muted); | ||||
|       return this; | ||||
|     } | ||||
|     return this.techGet_('muted') || false; // Default to false | ||||
|     return this.techGet_('muted') || false; | ||||
|   } | ||||
|  | ||||
|   // Check if current tech can support native fullscreen | ||||
| @@ -1645,7 +1671,7 @@ class Player extends Component { | ||||
|    * @method requestFullscreen | ||||
|    */ | ||||
|   requestFullscreen() { | ||||
|     var fsApi = FullscreenApi; | ||||
|     let fsApi = FullscreenApi; | ||||
|  | ||||
|     this.isFullscreen(true); | ||||
|  | ||||
| @@ -1658,7 +1684,7 @@ class Player extends Component { | ||||
|       // when canceling fullscreen. Otherwise if there's multiple | ||||
|       // players on a page, they would all be reacting to the same fullscreen | ||||
|       // events | ||||
|       Events.on(document, fsApi.fullscreenchange, Fn.bind(this, function documentFullscreenChange(e){ | ||||
|       Events.on(document, fsApi.fullscreenchange, Fn.bind(this, function documentFullscreenChange(e) { | ||||
|         this.isFullscreen(document[fsApi.fullscreenElement]); | ||||
|  | ||||
|         // If cancelling fullscreen, remove event listener. | ||||
| @@ -1695,17 +1721,18 @@ class Player extends Component { | ||||
|    * @method exitFullscreen | ||||
|    */ | ||||
|   exitFullscreen() { | ||||
|     var fsApi = FullscreenApi; | ||||
|     let fsApi = FullscreenApi; | ||||
|  | ||||
|     this.isFullscreen(false); | ||||
|  | ||||
|     // Check for browser element fullscreen support | ||||
|     if (fsApi.requestFullscreen) { | ||||
|       document[fsApi.exitFullscreen](); | ||||
|     } else if (this.tech_.supportsFullScreen()) { | ||||
|      this.techCall_('exitFullScreen'); | ||||
|       this.techCall_('exitFullScreen'); | ||||
|     } else { | ||||
|      this.exitFullWindow(); | ||||
|      this.trigger('fullscreenchange'); | ||||
|       this.exitFullWindow(); | ||||
|       this.trigger('fullscreenchange'); | ||||
|     } | ||||
|  | ||||
|     return this; | ||||
| @@ -1845,7 +1872,7 @@ class Player extends Component { | ||||
|     // Iterate over each `innerArray` element once per `outerArray` element and execute | ||||
|     // `tester` with both. If `tester` returns a non-falsy value, exit early and return | ||||
|     // that value. | ||||
|     let findFirstPassingTechSourcePair = function (outerArray, innerArray, tester) { | ||||
|     let findFirstPassingTechSourcePair = function(outerArray, innerArray, tester) { | ||||
|       let found; | ||||
|  | ||||
|       outerArray.some((outerChoice) => { | ||||
| @@ -1865,7 +1892,7 @@ class Player extends Component { | ||||
|     let flip = (fn) => (a, b) => fn(b, a); | ||||
|     let finder = ([techName, tech], source) => { | ||||
|       if (tech.canPlaySource(source, this.options_[techName.toLowerCase()])) { | ||||
|         return {source: source, tech: techName}; | ||||
|         return {source, tech: techName}; | ||||
|       } | ||||
|     }; | ||||
|  | ||||
| @@ -1920,6 +1947,7 @@ class Player extends Component { | ||||
|     } | ||||
|  | ||||
|     let currentTech = Tech.getTech(this.techName_); | ||||
|  | ||||
|     // Support old behavior of techs being registered as components. | ||||
|     // Remove once that deprecated behavior is removed. | ||||
|     if (!currentTech) { | ||||
| @@ -1948,7 +1976,7 @@ class Player extends Component { | ||||
|         this.currentType_ = source.type || ''; | ||||
|  | ||||
|         // wait until the tech is ready to set the source | ||||
|         this.ready(function(){ | ||||
|         this.ready(function() { | ||||
|  | ||||
|           // The setSource tech method was added with source handlers | ||||
|           // so older techs won't support it | ||||
| @@ -1984,7 +2012,7 @@ class Player extends Component { | ||||
|    * @method sourceList_ | ||||
|    */ | ||||
|   sourceList_(sources) { | ||||
|     var sourceTech = this.selectSource(sources); | ||||
|     let sourceTech = this.selectSource(sources); | ||||
|  | ||||
|     if (sourceTech) { | ||||
|       if (sourceTech.tech === this.techName_) { | ||||
| @@ -1996,7 +2024,7 @@ class Player extends Component { | ||||
|       } | ||||
|     } else { | ||||
|       // We need to wrap this in a timeout to give folks a chance to add error event handlers | ||||
|       this.setTimeout( function() { | ||||
|       this.setTimeout(function() { | ||||
|         this.error({ code: 4, message: this.localize(this.options_.notSupportedMessage) }); | ||||
|       }, 0); | ||||
|  | ||||
| @@ -2098,7 +2126,7 @@ class Player extends Component { | ||||
|   loop(value) { | ||||
|     if (value !== undefined) { | ||||
|       this.techCall_('setLoop', value); | ||||
|       this.options_['loop'] = value; | ||||
|       this.options_.loop = value; | ||||
|       return this; | ||||
|     } | ||||
|     return this.techGet_('loop'); | ||||
| @@ -2172,7 +2200,8 @@ class Player extends Component { | ||||
|    */ | ||||
|   controls(bool) { | ||||
|     if (bool !== undefined) { | ||||
|       bool = !!bool; // force boolean | ||||
|       bool = !!bool; | ||||
|  | ||||
|       // Don't trigger a change event unless it actually changed | ||||
|       if (this.controls_ !== bool) { | ||||
|         this.controls_ = bool; | ||||
| @@ -2218,7 +2247,8 @@ class Player extends Component { | ||||
|    */ | ||||
|   usingNativeControls(bool) { | ||||
|     if (bool !== undefined) { | ||||
|       bool = !!bool; // force boolean | ||||
|       bool = !!bool; | ||||
|  | ||||
|       // Don't trigger a change event unless it actually changed | ||||
|       if (this.usingNativeControls_ !== bool) { | ||||
|         this.usingNativeControls_ = bool; | ||||
| @@ -2302,7 +2332,9 @@ class Player extends Component { | ||||
|    * @return {Boolean} True if the player is in the ended state, false if not. | ||||
|    * @method ended | ||||
|    */ | ||||
|   ended() { return this.techGet_('ended'); } | ||||
|   ended() { | ||||
|     return this.techGet_('ended'); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Returns whether or not the player is in the "seeking" state. | ||||
| @@ -2310,7 +2342,9 @@ class Player extends Component { | ||||
|    * @return {Boolean} True if the player is in the seeking state, false if not. | ||||
|    * @method seeking | ||||
|    */ | ||||
|   seeking() { return this.techGet_('seeking'); } | ||||
|   seeking() { | ||||
|     return this.techGet_('seeking'); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Returns the TimeRanges of the media that are currently available | ||||
| @@ -2319,7 +2353,9 @@ class Player extends Component { | ||||
|    * @return {TimeRanges} the seekable intervals of the media timeline | ||||
|    * @method seekable | ||||
|    */ | ||||
|   seekable() { return this.techGet_('seekable'); } | ||||
|   seekable() { | ||||
|     return this.techGet_('seekable'); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Report user activity | ||||
| @@ -2363,8 +2399,8 @@ class Player extends Component { | ||||
|           // | ||||
|           // When this gets resolved in ALL browsers it can be removed | ||||
|           // https://code.google.com/p/chromium/issues/detail?id=103041 | ||||
|           if(this.tech_) { | ||||
|             this.tech_.one('mousemove', function(e){ | ||||
|           if (this.tech_) { | ||||
|             this.tech_.one('mousemove', function(e) { | ||||
|               e.stopPropagation(); | ||||
|               e.preventDefault(); | ||||
|             }); | ||||
| @@ -2387,14 +2423,15 @@ class Player extends Component { | ||||
|    * @method listenForUserActivity_ | ||||
|    */ | ||||
|   listenForUserActivity_() { | ||||
|     let mouseInProgress, lastMoveX, lastMoveY; | ||||
|  | ||||
|     let mouseInProgress; | ||||
|     let lastMoveX; | ||||
|     let lastMoveY; | ||||
|     let handleActivity = Fn.bind(this, this.reportUserActivity); | ||||
|  | ||||
|     let handleMouseMove = function(e) { | ||||
|       // #1068 - Prevent mousemove spamming | ||||
|       // 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; | ||||
|         lastMoveY = e.screenY; | ||||
|         handleActivity(); | ||||
| @@ -2435,7 +2472,8 @@ class Player extends Component { | ||||
|     // then gets picked up by this loop | ||||
|     // http://ejohn.org/blog/learning-from-twitter/ | ||||
|     let inactivityTimeout; | ||||
|     let activityCheck = this.setInterval(function() { | ||||
|  | ||||
|     this.setInterval(function() { | ||||
|       // Check to see if mouse/touch activity has happened | ||||
|       if (this.userActivity_) { | ||||
|         // Reset the activity tracker | ||||
| @@ -2447,16 +2485,17 @@ class Player extends Component { | ||||
|         // Clear any existing inactivity timeout to start the timer over | ||||
|         this.clearTimeout(inactivityTimeout); | ||||
|  | ||||
|         var timeout = this.options_['inactivityTimeout']; | ||||
|         let timeout = this.options_.inactivityTimeout; | ||||
|  | ||||
|         if (timeout > 0) { | ||||
|           // In <timeout> milliseconds, if no more activity has occurred the | ||||
|           // user will be considered inactive | ||||
|           inactivityTimeout = this.setTimeout(function () { | ||||
|           inactivityTimeout = this.setTimeout(function() { | ||||
|             // Protect against the case where the inactivityTimeout can trigger just | ||||
|             // before the next user activity is picked up by the activityCheck loop | ||||
|             // before the next user activity is picked up by the activity check loop | ||||
|             // causing a flicker | ||||
|             if (!this.userActivity_) { | ||||
|                 this.userActive(false); | ||||
|               this.userActive(false); | ||||
|             } | ||||
|           }, timeout); | ||||
|         } | ||||
| @@ -2481,11 +2520,10 @@ class Player extends Component { | ||||
|       return this; | ||||
|     } | ||||
|  | ||||
|     if (this.tech_ && this.tech_['featuresPlaybackRate']) { | ||||
|     if (this.tech_ && this.tech_.featuresPlaybackRate) { | ||||
|       return this.techGet_('playbackRate'); | ||||
|     } else { | ||||
|       return 1.0; | ||||
|     } | ||||
|     return 1.0; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
| @@ -2611,7 +2649,9 @@ class Player extends Component { | ||||
|   textTracks() { | ||||
|     // cannot use techGet_ directly because it checks to see whether the tech is ready. | ||||
|     // Flash is unlikely to be ready in time but textTracks should still work. | ||||
|     return this.tech_ && this.tech_['textTracks'](); | ||||
|     if (this.tech_) { | ||||
|       return this.tech_.textTracks(); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   /** | ||||
| @@ -2621,7 +2661,9 @@ class Player extends Component { | ||||
|    * @method remoteTextTracks | ||||
|    */ | ||||
|   remoteTextTracks() { | ||||
|     return this.tech_ && this.tech_['remoteTextTracks'](); | ||||
|     if (this.tech_) { | ||||
|       return this.tech_.remoteTextTracks(); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   /** | ||||
| @@ -2631,7 +2673,9 @@ class Player extends Component { | ||||
|    * @method remoteTextTrackEls | ||||
|    */ | ||||
|   remoteTextTrackEls() { | ||||
|     return this.tech_ && this.tech_['remoteTextTrackEls'](); | ||||
|     if (this.tech_) { | ||||
|       return this.tech_.remoteTextTrackEls(); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   /** | ||||
| @@ -2645,7 +2689,9 @@ class Player extends Component { | ||||
|    * @method addTextTrack | ||||
|    */ | ||||
|   addTextTrack(kind, label, language) { | ||||
|     return this.tech_ && this.tech_['addTextTrack'](kind, label, language); | ||||
|     if (this.tech_) { | ||||
|       return this.tech_.addTextTrack(kind, label, language); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   /** | ||||
| @@ -2655,7 +2701,9 @@ class Player extends Component { | ||||
|    * @method addRemoteTextTrack | ||||
|    */ | ||||
|   addRemoteTextTrack(options) { | ||||
|     return this.tech_ && this.tech_['addRemoteTextTrack'](options); | ||||
|     if (this.tech_) { | ||||
|       return this.tech_.addRemoteTextTrack(options); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   /** | ||||
| @@ -2666,8 +2714,10 @@ class Player extends Component { | ||||
|    */ | ||||
|   // destructure the input into an object with a track argument, defaulting to arguments[0] | ||||
|   // default the whole argument to an empty object if nothing was passed in | ||||
|   removeRemoteTextTrack({track = arguments[0]} = {}) { // jshint ignore:line | ||||
|     this.tech_ && this.tech_['removeRemoteTextTrack'](track); | ||||
|   removeRemoteTextTrack({track = arguments[0]} = {}) { | ||||
|     if (this.tech_) { | ||||
|       return this.tech_.removeRemoteTextTrack(track); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   /** | ||||
| @@ -2691,11 +2741,11 @@ class Player extends Component { | ||||
|   } | ||||
|  | ||||
|   // Methods to add support for | ||||
|   // initialTime: function(){ return this.techCall_('initialTime'); }, | ||||
|   // startOffsetTime: function(){ return this.techCall_('startOffsetTime'); }, | ||||
|   // played: function(){ return this.techCall_('played'); }, | ||||
|   // defaultPlaybackRate: function(){ return this.techCall_('defaultPlaybackRate'); }, | ||||
|   // defaultMuted: function(){ return this.techCall_('defaultMuted'); } | ||||
|   // initialTime: function() { return this.techCall_('initialTime'); }, | ||||
|   // startOffsetTime: function() { return this.techCall_('startOffsetTime'); }, | ||||
|   // played: function() { return this.techCall_('played'); }, | ||||
|   // defaultPlaybackRate: function() { return this.techCall_('defaultPlaybackRate'); }, | ||||
|   // defaultMuted: function() { return this.techCall_('defaultMuted'); } | ||||
|  | ||||
|   /** | ||||
|    * The player's language code | ||||
| @@ -2713,7 +2763,7 @@ class Player extends Component { | ||||
|       return this.language_; | ||||
|     } | ||||
|  | ||||
|     this.language_ = (''+code).toLowerCase(); | ||||
|     this.language_ = String(code).toLowerCase(); | ||||
|     return this; | ||||
|   } | ||||
|  | ||||
| @@ -2726,7 +2776,7 @@ class Player extends Component { | ||||
|    * @method languages | ||||
|    */ | ||||
|   languages() { | ||||
|     return  mergeOptions(Player.prototype.options_.languages, this.languages_); | ||||
|     return mergeOptions(Player.prototype.options_.languages, this.languages_); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
| @@ -2770,16 +2820,14 @@ class Player extends Component { | ||||
|    * @return {ModalDialog} | ||||
|    */ | ||||
|   createModal(content, options) { | ||||
|     let player = this; | ||||
|  | ||||
|     options = options || {}; | ||||
|     options.content = content || ''; | ||||
|  | ||||
|     let modal = new ModalDialog(player, options); | ||||
|     let modal = new ModalDialog(this, options); | ||||
|  | ||||
|     player.addChild(modal); | ||||
|     modal.on('dispose', function() { | ||||
|       player.removeChild(modal); | ||||
|     this.addChild(modal); | ||||
|     modal.on('dispose', () => { | ||||
|       this.removeChild(modal); | ||||
|     }); | ||||
|  | ||||
|     return modal.open(); | ||||
| @@ -2795,18 +2843,19 @@ class Player extends Component { | ||||
|    */ | ||||
|   static getTagSettings(tag) { | ||||
|     let baseOptions = { | ||||
|       'sources': [], | ||||
|       'tracks': [] | ||||
|       sources: [], | ||||
|       tracks: [] | ||||
|     }; | ||||
|  | ||||
|     const tagOptions = Dom.getElAttributes(tag); | ||||
|     const dataSetup = tagOptions['data-setup']; | ||||
|  | ||||
|     // Check if data-setup attr exists. | ||||
|     if (dataSetup !== null){ | ||||
|     if (dataSetup !== null) { | ||||
|       // Parse options JSON | ||||
|       // If empty string, make it a parsable json object. | ||||
|       const [err, data] = safeParseTuple(dataSetup || '{}'); | ||||
|  | ||||
|       if (err) { | ||||
|         log.error(err); | ||||
|       } | ||||
| @@ -2819,10 +2868,11 @@ class Player extends Component { | ||||
|     if (tag.hasChildNodes()) { | ||||
|       const children = tag.childNodes; | ||||
|  | ||||
|       for (let i=0, j=children.length; i<j; i++) { | ||||
|       for (let i = 0, j = children.length; i < j; i++) { | ||||
|         const child = children[i]; | ||||
|         // Change case needed: http://ejohn.org/blog/nodename-case-sensitivity/ | ||||
|         const childName = child.nodeName.toLowerCase(); | ||||
|  | ||||
|         if (childName === 'source') { | ||||
|           baseOptions.sources.push(Dom.getElAttributes(child)); | ||||
|         } else if (childName === 'track') { | ||||
| @@ -2844,6 +2894,7 @@ class Player extends Component { | ||||
| Player.players = {}; | ||||
|  | ||||
| let navigator = window.navigator; | ||||
|  | ||||
| /* | ||||
|  * Player instance options, surfaced using options | ||||
|  * options = Player.prototype.options_ | ||||
| @@ -2854,14 +2905,14 @@ let navigator = window.navigator; | ||||
|  */ | ||||
| Player.prototype.options_ = { | ||||
|   // Default order of fallback technology | ||||
|   techOrder: ['html5','flash'], | ||||
|   techOrder: ['html5', 'flash'], | ||||
|   // techOrder: ['flash','html5'], | ||||
|  | ||||
|   html5: {}, | ||||
|   flash: {}, | ||||
|  | ||||
|   // defaultVolume: 0.85, | ||||
|   defaultVolume: 0.00, // The freakin seaguls are driving me crazy! | ||||
|   defaultVolume: 0.00, | ||||
|  | ||||
|   // default inactivity timeout | ||||
|   inactivityTimeout: 2000, | ||||
| @@ -2892,47 +2943,49 @@ Player.prototype.options_ = { | ||||
|   notSupportedMessage: 'No compatible source was found for this media.' | ||||
| }; | ||||
|  | ||||
| // The following no-op expressions are here only for purposes of documentation. | ||||
|  | ||||
| /** | ||||
|  * Fired when the user agent begins looking for media data | ||||
|  * | ||||
|  * @event loadstart | ||||
|  */ | ||||
| Player.prototype.handleTechLoadStart_; | ||||
| Player.prototype.handleTechLoadStart_; // eslint-disable-line | ||||
|  | ||||
| /** | ||||
|  * Fired when the player has initial duration and dimension information | ||||
|  * | ||||
|  * @event loadedmetadata | ||||
|  */ | ||||
| Player.prototype.handleLoadedMetaData_; | ||||
| Player.prototype.handleLoadedMetaData_; // eslint-disable-line | ||||
|  | ||||
| /** | ||||
|  * Fired when the player receives text data | ||||
|  * | ||||
|  * @event textdata | ||||
|  */ | ||||
| Player.prototype.handleTextData_; | ||||
| Player.prototype.handleTextData_; // eslint-disable-line | ||||
|  | ||||
| /** | ||||
|  * Fired when the player has downloaded data at the current playback position | ||||
|  * | ||||
|  * @event loadeddata | ||||
|  */ | ||||
| Player.prototype.handleLoadedData_; | ||||
| Player.prototype.handleLoadedData_; // eslint-disable-line | ||||
|  | ||||
| /** | ||||
|  * Fired when the user is active, e.g. moves the mouse over the player | ||||
|  * | ||||
|  * @event useractive | ||||
|  */ | ||||
| Player.prototype.handleUserActive_; | ||||
| Player.prototype.handleUserActive_; // eslint-disable-line | ||||
|  | ||||
| /** | ||||
|  * Fired when the user is inactive, e.g. a short delay after the last mouse move or control interaction | ||||
|  * | ||||
|  * @event userinactive | ||||
|  */ | ||||
| Player.prototype.handleUserInactive_; | ||||
| Player.prototype.handleUserInactive_; // eslint-disable-line | ||||
|  | ||||
| /** | ||||
|  * Fired when the current playback position has changed * | ||||
| @@ -2941,31 +2994,31 @@ Player.prototype.handleUserInactive_; | ||||
|  * | ||||
|  * @event timeupdate | ||||
|  */ | ||||
| Player.prototype.handleTimeUpdate_; | ||||
| Player.prototype.handleTimeUpdate_; // eslint-disable-line | ||||
|  | ||||
| /** | ||||
|  * Fired when video playback ends | ||||
|  * | ||||
|  * @event ended | ||||
|  */ | ||||
| Player.prototype.handleTechEnded_; | ||||
| Player.prototype.handleTechEnded_; // eslint-disable-line | ||||
|  | ||||
| /** | ||||
|  * Fired when the volume changes | ||||
|  * | ||||
|  * @event volumechange | ||||
|  */ | ||||
| Player.prototype.handleVolumeChange_; | ||||
| Player.prototype.handleVolumeChange_; // eslint-disable-line | ||||
|  | ||||
| /** | ||||
|  * Fired when an error occurs | ||||
|  * | ||||
|  * @event error | ||||
|  */ | ||||
| Player.prototype.handleError_; | ||||
| Player.prototype.handleError_ = Player.prototype.handleError_; | ||||
|  | ||||
| Player.prototype.flexNotSupported_ = function() { | ||||
|   var elem = document.createElement('i'); | ||||
|   let elem = document.createElement('i'); | ||||
|  | ||||
|   // Note: We don't actually use flexBasis (or flexOrder), but it's one of the more | ||||
|   // common flex features that we can rely on when checking for flex support. | ||||
| @@ -2973,7 +3026,8 @@ Player.prototype.flexNotSupported_ = function() { | ||||
|           'webkitFlexBasis' in elem.style || | ||||
|           'mozFlexBasis' in elem.style || | ||||
|           'msFlexBasis' in elem.style || | ||||
|           'msFlexOrder' in elem.style /* IE10-specific (2012 flex spec)  */); | ||||
|           // IE10-specific (2012 flex spec) | ||||
|           'msFlexOrder' in elem.style); | ||||
| }; | ||||
|  | ||||
| Component.registerComponent('Player', Player); | ||||
|   | ||||
| @@ -10,7 +10,7 @@ import Player from './player.js'; | ||||
|  * @param  {Function} init The function that is run when the player inits | ||||
|  * @method plugin | ||||
|  */ | ||||
| var plugin = function(name, init){ | ||||
| const plugin = function(name, init) { | ||||
|   Player.prototype[name] = init; | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -3,10 +3,6 @@ | ||||
|  */ | ||||
| import ClickableComponent from '../clickable-component.js'; | ||||
| import Component from '../component.js'; | ||||
| import Popup from './popup.js'; | ||||
| import * as Dom from '../utils/dom.js'; | ||||
| import * as Fn from '../utils/fn.js'; | ||||
| import toTitleCase from '../utils/to-title-case.js'; | ||||
|  | ||||
| /** | ||||
|  * A button class with a popup control | ||||
| @@ -18,7 +14,7 @@ import toTitleCase from '../utils/to-title-case.js'; | ||||
|  */ | ||||
| class PopupButton extends ClickableComponent { | ||||
|  | ||||
|   constructor(player, options={}){ | ||||
|   constructor(player, options = {}) { | ||||
|     super(player, options); | ||||
|  | ||||
|     this.update(); | ||||
| @@ -30,7 +26,7 @@ class PopupButton extends ClickableComponent { | ||||
|    * @method update | ||||
|    */ | ||||
|   update() { | ||||
|     let popup = this.createPopup(); | ||||
|     const popup = this.createPopup(); | ||||
|  | ||||
|     if (this.popup) { | ||||
|       this.removeChild(this.popup); | ||||
| @@ -73,7 +69,7 @@ class PopupButton extends ClickableComponent { | ||||
|    * @method buildCSSClass | ||||
|    */ | ||||
|   buildCSSClass() { | ||||
|     var menuButtonClass = 'vjs-menu-button'; | ||||
|     let menuButtonClass = 'vjs-menu-button'; | ||||
|  | ||||
|     // If the inline option is passed, we want to use different styles altogether. | ||||
|     if (this.options_.inline === true) { | ||||
|   | ||||
| @@ -22,7 +22,7 @@ class Popup extends Component { | ||||
|    */ | ||||
|   addItem(component) { | ||||
|     this.addChild(component); | ||||
|     component.on('click', Fn.bind(this, function(){ | ||||
|     component.on('click', Fn.bind(this, function() { | ||||
|       this.unlockShowing(); | ||||
|     })); | ||||
|   } | ||||
| @@ -34,19 +34,22 @@ class Popup extends Component { | ||||
|    * @method createEl | ||||
|    */ | ||||
|   createEl() { | ||||
|     let contentElType = this.options_.contentElType || 'ul'; | ||||
|     const contentElType = this.options_.contentElType || 'ul'; | ||||
|  | ||||
|     this.contentEl_ = Dom.createEl(contentElType, { | ||||
|       className: 'vjs-menu-content' | ||||
|     }); | ||||
|     var el = super.createEl('div', { | ||||
|  | ||||
|     const el = super.createEl('div', { | ||||
|       append: this.contentEl_, | ||||
|       className: 'vjs-menu' | ||||
|     }); | ||||
|  | ||||
|     el.appendChild(this.contentEl_); | ||||
|  | ||||
|     // Prevent clicks from bubbling up. Needed for Popup Buttons, | ||||
|     // where a click on the parent is significant | ||||
|     Events.on(el, 'click', function(event){ | ||||
|     Events.on(el, 'click', function(event) { | ||||
|       event.preventDefault(); | ||||
|       event.stopImmediatePropagation(); | ||||
|     }); | ||||
|   | ||||
| @@ -17,7 +17,7 @@ import * as browser from './utils/browser.js'; | ||||
|  */ | ||||
| class PosterImage extends ClickableComponent { | ||||
|  | ||||
|   constructor(player, options){ | ||||
|   constructor(player, options) { | ||||
|     super(player, options); | ||||
|  | ||||
|     this.update(); | ||||
| @@ -90,6 +90,7 @@ class PosterImage extends ClickableComponent { | ||||
|       this.fallbackImg_.src = url; | ||||
|     } else { | ||||
|       let backgroundImage = ''; | ||||
|  | ||||
|       // Any falsey values should stay as an empty string, otherwise | ||||
|       // this will throw an extra error | ||||
|       if (url) { | ||||
|   | ||||
| @@ -10,27 +10,29 @@ import window from 'global/window'; | ||||
|  | ||||
| let _windowLoaded = false; | ||||
| let videojs; | ||||
|  | ||||
| let autoSetupTimeout; | ||||
|  | ||||
| // Automatically set up any tags that have a data-setup attribute | ||||
| var autoSetup = function(){ | ||||
| const autoSetup = function() { | ||||
|   // One day, when we stop supporting IE8, go back to this, but in the meantime...*hack hack hack* | ||||
|   // var vids = Array.prototype.slice.call(document.getElementsByTagName('video')); | ||||
|   // var audios = Array.prototype.slice.call(document.getElementsByTagName('audio')); | ||||
|   // var mediaEls = vids.concat(audios); | ||||
|  | ||||
|   // Because IE8 doesn't support calling slice on a node list, we need to loop through each list of elements | ||||
|   // to build up a new, combined list of elements. | ||||
|   var vids = document.getElementsByTagName('video'); | ||||
|   var audios = document.getElementsByTagName('audio'); | ||||
|   var mediaEls = []; | ||||
|   // Because IE8 doesn't support calling slice on a node list, we need to loop | ||||
|   // through each list of elements to build up a new, combined list of elements. | ||||
|   const vids = document.getElementsByTagName('video'); | ||||
|   const audios = document.getElementsByTagName('audio'); | ||||
|   const mediaEls = []; | ||||
|  | ||||
|   if (vids && vids.length > 0) { | ||||
|     for(let i=0, e=vids.length; i<e; i++) { | ||||
|     for (let i = 0, e = vids.length; i < e; i++) { | ||||
|       mediaEls.push(vids[i]); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   if (audios && audios.length > 0) { | ||||
|     for(let i=0, e=audios.length; i<e; i++) { | ||||
|     for (let i = 0, e = audios.length; i < e; i++) { | ||||
|       mediaEls.push(audios[i]); | ||||
|     } | ||||
|   } | ||||
| @@ -38,22 +40,23 @@ var autoSetup = function(){ | ||||
|   // Check if any media elements exist | ||||
|   if (mediaEls && mediaEls.length > 0) { | ||||
|  | ||||
|     for (let i=0, e=mediaEls.length; i<e; i++) { | ||||
|     for (let i = 0, e = mediaEls.length; i < e; i++) { | ||||
|       let mediaEl = mediaEls[i]; | ||||
|  | ||||
|       // Check if element exists, has getAttribute func. | ||||
|       // IE seems to consider typeof el.getAttribute == 'object' instead of 'function' like expected, at least when loading the player immediately. | ||||
|       // IE seems to consider typeof el.getAttribute == 'object' instead of | ||||
|       // 'function' like expected, at least when loading the player immediately. | ||||
|       if (mediaEl && mediaEl.getAttribute) { | ||||
|  | ||||
|         // Make sure this player hasn't already been set up. | ||||
|         if (mediaEl['player'] === undefined) { | ||||
|         if (mediaEl.player === undefined) { | ||||
|           let options = mediaEl.getAttribute('data-setup'); | ||||
|  | ||||
|           // Check if data-setup attr exists. | ||||
|           // We only auto-setup if they've added the data-setup attr. | ||||
|           if (options !== null) { | ||||
|             // Create new video.js instance. | ||||
|             let player = videojs(mediaEl); | ||||
|             videojs(mediaEl); | ||||
|           } | ||||
|         } | ||||
|  | ||||
| @@ -71,7 +74,7 @@ var autoSetup = function(){ | ||||
| }; | ||||
|  | ||||
| // Pause to let the DOM keep processing | ||||
| var autoSetupTimeout = function(wait, vjs){ | ||||
| autoSetupTimeout = function(wait, vjs) { | ||||
|   if (vjs) { | ||||
|     videojs = vjs; | ||||
|   } | ||||
| @@ -82,12 +85,12 @@ var autoSetupTimeout = function(wait, vjs){ | ||||
| if (document.readyState === 'complete') { | ||||
|   _windowLoaded = true; | ||||
| } else { | ||||
|   Events.one(window, 'load', function(){ | ||||
|   Events.one(window, 'load', function() { | ||||
|     _windowLoaded = true; | ||||
|   }); | ||||
| } | ||||
|  | ||||
| var hasLoaded = function() { | ||||
| const hasLoaded = function() { | ||||
|   return _windowLoaded; | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -42,7 +42,7 @@ class Slider extends Component { | ||||
|    * @return {Element} | ||||
|    * @method createEl | ||||
|    */ | ||||
|   createEl(type, props={}, attributes={}) { | ||||
|   createEl(type, props = {}, attributes = {}) { | ||||
|     // Add the slider element class to all sub classes | ||||
|     props.className = props.className + ' vjs-slider'; | ||||
|     props = assign({ | ||||
| @@ -54,7 +54,7 @@ class Slider extends Component { | ||||
|       'aria-valuenow': 0, | ||||
|       'aria-valuemin': 0, | ||||
|       'aria-valuemax': 100, | ||||
|       tabIndex: 0 | ||||
|       'tabIndex': 0 | ||||
|     }, attributes); | ||||
|  | ||||
|     return super.createEl(type, props, attributes); | ||||
| @@ -67,7 +67,7 @@ class Slider extends Component { | ||||
|    * @method handleMouseDown | ||||
|    */ | ||||
|   handleMouseDown(event) { | ||||
|     let doc = this.bar.el_.ownerDocument; | ||||
|     const doc = this.bar.el_.ownerDocument; | ||||
|  | ||||
|     event.preventDefault(); | ||||
|     Dom.blockTextSelection(); | ||||
| @@ -96,7 +96,7 @@ class Slider extends Component { | ||||
|    * @method handleMouseUp | ||||
|    */ | ||||
|   handleMouseUp() { | ||||
|     let doc = this.bar.el_.ownerDocument; | ||||
|     const doc = this.bar.el_.ownerDocument; | ||||
|  | ||||
|     Dom.unblockTextSelection(); | ||||
|  | ||||
| @@ -119,27 +119,31 @@ class Slider extends Component { | ||||
|   update() { | ||||
|     // In VolumeBar init we have a setTimeout for update that pops and update to the end of the | ||||
|     // execution stack. The player is destroyed before then update will cause an error | ||||
|     if (!this.el_) return; | ||||
|     if (!this.el_) { | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     // If scrubbing, we could use a cached value to make the handle keep up with the user's mouse. | ||||
|     // On HTML5 browsers scrubbing is really smooth, but some flash players are slow, so we might want to utilize this later. | ||||
|     // var progress =  (this.player_.scrubbing()) ? this.player_.getCache().currentTime / this.player_.duration() : this.player_.currentTime() / this.player_.duration(); | ||||
|     let progress = this.getPercent(); | ||||
|     let bar = this.bar; | ||||
|     const bar = this.bar; | ||||
|  | ||||
|     // If there's no bar... | ||||
|     if (!bar) return; | ||||
|     if (!bar) { | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     // Protect against no duration and other division issues | ||||
|     if (typeof progress !== 'number' || | ||||
|         progress !== progress || | ||||
|         progress < 0 || | ||||
|         progress === Infinity) { | ||||
|           progress = 0; | ||||
|       progress = 0; | ||||
|     } | ||||
|  | ||||
|     // Convert to a percentage for setting | ||||
|     let percentage = (progress * 100).toFixed(2) + '%'; | ||||
|     const percentage = (progress * 100).toFixed(2) + '%'; | ||||
|  | ||||
|     // Set the new bar width or height | ||||
|     if (this.vertical()) { | ||||
| @@ -155,8 +159,9 @@ class Slider extends Component { | ||||
|    * @param {Object} event Event object | ||||
|    * @method calculateDistance | ||||
|    */ | ||||
|   calculateDistance(event){ | ||||
|     let position = Dom.getPointerPosition(this.el_, event); | ||||
|   calculateDistance(event) { | ||||
|     const position = Dom.getPointerPosition(this.el_, event); | ||||
|  | ||||
|     if (this.vertical()) { | ||||
|       return position.y; | ||||
|     } | ||||
| @@ -179,10 +184,13 @@ class Slider extends Component { | ||||
|    * @method handleKeyPress | ||||
|    */ | ||||
|   handleKeyPress(event) { | ||||
|     if (event.which === 37 || event.which === 40) { // Left and Down Arrows | ||||
|     // Left and Down Arrows | ||||
|     if (event.which === 37 || event.which === 40) { | ||||
|       event.preventDefault(); | ||||
|       this.stepBack(); | ||||
|     } else if (event.which === 38 || event.which === 39) { // Up and Right Arrows | ||||
|  | ||||
|     // Up and Right Arrows | ||||
|     } else if (event.which === 38 || event.which === 39) { | ||||
|       event.preventDefault(); | ||||
|       this.stepForward(); | ||||
|     } | ||||
|   | ||||
| @@ -17,17 +17,19 @@ function FlashRtmpDecorator(Flash) { | ||||
|       stream: '' | ||||
|     }; | ||||
|  | ||||
|     if (!src) return parts; | ||||
|     if (!src) { | ||||
|       return parts; | ||||
|     } | ||||
|  | ||||
|     // Look for the normal URL separator we expect, '&'. | ||||
|     // If found, we split the URL into two pieces around the | ||||
|     // first '&'. | ||||
|     let connEnd = src.search(/&(?!\w+=)/); | ||||
|     let streamBegin; | ||||
|  | ||||
|     if (connEnd !== -1) { | ||||
|       streamBegin = connEnd + 1; | ||||
|     } | ||||
|     else { | ||||
|     } else { | ||||
|       // If there's not a '&', we use the last '/' as the delimiter. | ||||
|       connEnd = streamBegin = src.lastIndexOf('/') + 1; | ||||
|       if (connEnd === 0) { | ||||
| @@ -35,6 +37,7 @@ function FlashRtmpDecorator(Flash) { | ||||
|         connEnd = streamBegin = src.length; | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     parts.connection = src.substring(0, connEnd); | ||||
|     parts.stream = src.substring(streamBegin, src.length); | ||||
|  | ||||
| @@ -64,7 +67,7 @@ function FlashRtmpDecorator(Flash) { | ||||
|    * @param  {String} type    The mimetype to check | ||||
|    * @return {String}         'probably', 'maybe', or '' (empty string) | ||||
|    */ | ||||
|   Flash.rtmpSourceHandler.canPlayType = function(type){ | ||||
|   Flash.rtmpSourceHandler.canPlayType = function(type) { | ||||
|     if (Flash.isStreamingType(type)) { | ||||
|       return 'maybe'; | ||||
|     } | ||||
| @@ -78,7 +81,7 @@ function FlashRtmpDecorator(Flash) { | ||||
|    * @param  {Object} options The options passed to the tech | ||||
|    * @return {String}         'probably', 'maybe', or '' (empty string) | ||||
|    */ | ||||
|   Flash.rtmpSourceHandler.canHandleSource = function(source, options){ | ||||
|   Flash.rtmpSourceHandler.canHandleSource = function(source, options) { | ||||
|     let can = Flash.rtmpSourceHandler.canPlayType(source.type); | ||||
|  | ||||
|     if (can) { | ||||
| @@ -100,11 +103,11 @@ function FlashRtmpDecorator(Flash) { | ||||
|    * @param  {Flash}  tech     The instance of the Flash tech | ||||
|    * @param  {Object} options  The options to pass to the source | ||||
|    */ | ||||
|   Flash.rtmpSourceHandler.handleSource = function(source, tech, options){ | ||||
|   Flash.rtmpSourceHandler.handleSource = function(source, tech, options) { | ||||
|     let srcParts = Flash.streamToParts(source.src); | ||||
|  | ||||
|     tech['setRtmpConnection'](srcParts.connection); | ||||
|     tech['setRtmpStream'](srcParts.stream); | ||||
|     tech.setRtmpConnection(srcParts.connection); | ||||
|     tech.setRtmpStream(srcParts.stream); | ||||
|   }; | ||||
|  | ||||
|   // Register the native source handler | ||||
|   | ||||
| @@ -15,6 +15,7 @@ import window from 'global/window'; | ||||
| import assign from 'object.assign'; | ||||
|  | ||||
| let navigator = window.navigator; | ||||
|  | ||||
| /** | ||||
|  * Flash Media Controller - Wrapper for fallback SWF API | ||||
|  * | ||||
| @@ -25,12 +26,12 @@ let navigator = window.navigator; | ||||
|  */ | ||||
| class Flash extends Tech { | ||||
|  | ||||
|   constructor(options, ready){ | ||||
|   constructor(options, ready) { | ||||
|     super(options, ready); | ||||
|  | ||||
|     // Set the source when ready | ||||
|     if (options.source) { | ||||
|       this.ready(function(){ | ||||
|       this.ready(function() { | ||||
|         this.setSource(options.source); | ||||
|       }, true); | ||||
|     } | ||||
| @@ -38,7 +39,7 @@ class Flash extends Tech { | ||||
|     // 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 | ||||
|     if (options.startTime) { | ||||
|       this.ready(function(){ | ||||
|       this.ready(function() { | ||||
|         this.load(); | ||||
|         this.play(); | ||||
|         this.currentTime(options.startTime); | ||||
| @@ -84,29 +85,32 @@ class Flash extends Tech { | ||||
|     let flashVars = assign({ | ||||
|  | ||||
|       // SWF Callback Functions | ||||
|       'readyFunction': 'videojs.Flash.onReady', | ||||
|       'eventProxyFunction': 'videojs.Flash.onEvent', | ||||
|       'errorEventProxyFunction': 'videojs.Flash.onError', | ||||
|       readyFunction: 'videojs.Flash.onReady', | ||||
|       eventProxyFunction: 'videojs.Flash.onEvent', | ||||
|       errorEventProxyFunction: 'videojs.Flash.onError', | ||||
|  | ||||
|       // Player Settings | ||||
|       'autoplay': options.autoplay, | ||||
|       'preload': options.preload, | ||||
|       'loop': options.loop, | ||||
|       'muted': options.muted | ||||
|       autoplay: options.autoplay, | ||||
|       preload: options.preload, | ||||
|       loop: options.loop, | ||||
|       muted: options.muted | ||||
|  | ||||
|     }, options.flashVars); | ||||
|  | ||||
|     // Merge default parames with ones passed in | ||||
|     let params = assign({ | ||||
|       '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 | ||||
|       // Opaque is needed to overlay controls, but can affect playback performance | ||||
|       wmode: 'opaque', | ||||
|       // Using bgcolor prevents a white flash when the object is loading | ||||
|       bgcolor: '#000000' | ||||
|     }, options.params); | ||||
|  | ||||
|     // Merge default attributes with ones passed in | ||||
|     let attributes = assign({ | ||||
|       'id': objId, | ||||
|       'name': objId, // Both ID and Name needed or swf to identify itself | ||||
|       'class': 'vjs-tech' | ||||
|       // Both ID and Name needed or swf to identify itself | ||||
|       id: objId, | ||||
|       name: objId, | ||||
|       class: 'vjs-tech' | ||||
|     }, options.attributes); | ||||
|  | ||||
|     this.el_ = Flash.embed(options.swf, flashVars, params, attributes); | ||||
| @@ -167,8 +171,7 @@ class Flash extends Tech { | ||||
|     // Currently the SWF doesn't autoplay if you load a source later. | ||||
|     // e.g. Load player w/ no source, wait 2s, set src. | ||||
|     if (this.autoplay()) { | ||||
|       var tech = this; | ||||
|       this.setTimeout(function(){ tech.play(); }, 0); | ||||
|       this.setTimeout(() => this.play(), 0); | ||||
|     } | ||||
|   } | ||||
|  | ||||
| @@ -188,6 +191,7 @@ class Flash extends Tech { | ||||
|    */ | ||||
|   setCurrentTime(time) { | ||||
|     let seekable = this.seekable(); | ||||
|  | ||||
|     if (seekable.length) { | ||||
|       // clamp to the current seekable range | ||||
|       time = time > seekable.start(0) ? time : seekable.start(0); | ||||
| @@ -224,9 +228,8 @@ class Flash extends Tech { | ||||
|   currentSrc() { | ||||
|     if (this.currentSource_) { | ||||
|       return this.currentSource_.src; | ||||
|     } else { | ||||
|       return this.el_.vjs_getProperty('currentSrc'); | ||||
|     } | ||||
|     return this.el_.vjs_getProperty('currentSrc'); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
| @@ -237,10 +240,10 @@ class Flash extends Tech { | ||||
|   duration() { | ||||
|     if (this.readyState() === 0) { | ||||
|       return NaN; | ||||
|     } else { | ||||
|       let duration = this.el_.vjs_getProperty('duration'); | ||||
|       return duration >= 0 ? duration : Infinity; | ||||
|     } | ||||
|     let duration = this.el_.vjs_getProperty('duration'); | ||||
|  | ||||
|     return duration >= 0 ? duration : Infinity; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
| @@ -276,6 +279,7 @@ class Flash extends Tech { | ||||
|    */ | ||||
|   seekable() { | ||||
|     const duration = this.duration(); | ||||
|  | ||||
|     if (duration === 0) { | ||||
|       return createTimeRange(); | ||||
|     } | ||||
| @@ -290,6 +294,7 @@ class Flash extends Tech { | ||||
|    */ | ||||
|   buffered() { | ||||
|     let ranges = this.el_.vjs_getProperty('buffered'); | ||||
|  | ||||
|     if (ranges.length === 0) { | ||||
|       return createTimeRange(); | ||||
|     } | ||||
| @@ -305,7 +310,8 @@ class Flash extends Tech { | ||||
|    * @method supportsFullScreen | ||||
|    */ | ||||
|   supportsFullScreen() { | ||||
|     return false; // Flash does not allow fullscreen through javascript | ||||
|     // Flash does not allow fullscreen through javascript | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
| @@ -322,18 +328,23 @@ class Flash extends Tech { | ||||
|  | ||||
| } | ||||
|  | ||||
|  | ||||
| // Create setters and getters for attributes | ||||
| const _api = Flash.prototype; | ||||
| const _readWrite = 'rtmpConnection,rtmpStream,preload,defaultPlaybackRate,playbackRate,autoplay,loop,mediaGroup,controller,controls,volume,muted,defaultMuted'.split(','); | ||||
| const _readOnly = 'networkState,readyState,initialTime,startOffsetTime,paused,ended,videoWidth,videoHeight'.split(','); | ||||
|  | ||||
| function _createSetter(attr){ | ||||
|   var attrUpper = attr.charAt(0).toUpperCase() + attr.slice(1); | ||||
|   _api['set'+attrUpper] = function(val){ return this.el_.vjs_setProperty(attr, val); }; | ||||
| function _createSetter(attr) { | ||||
|   let attrUpper = attr.charAt(0).toUpperCase() + attr.slice(1); | ||||
|  | ||||
|   _api['set' + attrUpper] = function(val) { | ||||
|     return this.el_.vjs_setProperty(attr, val); | ||||
|   }; | ||||
| } | ||||
|  | ||||
| function _createGetter(attr) { | ||||
|   _api[attr] = function(){ return this.el_.vjs_getProperty(attr); }; | ||||
|   _api[attr] = function() { | ||||
|     return this.el_.vjs_getProperty(attr); | ||||
|   }; | ||||
| } | ||||
|  | ||||
| // Create getter and setters for all read/write attributes | ||||
| @@ -349,7 +360,7 @@ for (let i = 0; i < _readOnly.length; i++) { | ||||
|  | ||||
| /* Flash Support Testing -------------------------------------------------------- */ | ||||
|  | ||||
| Flash.isSupported = function(){ | ||||
| Flash.isSupported = function() { | ||||
|   return Flash.version()[0] >= 10; | ||||
|   // return swfobject.hasFlashPlayerVersion('10'); | ||||
| }; | ||||
| @@ -371,7 +382,7 @@ Flash.nativeSourceHandler = {}; | ||||
|  * @param  {String} type    The mimetype to check | ||||
|  * @return {String}         'probably', 'maybe', or '' (empty string) | ||||
|  */ | ||||
| Flash.nativeSourceHandler.canPlayType = function(type){ | ||||
| Flash.nativeSourceHandler.canPlayType = function(type) { | ||||
|   if (type in Flash.formats) { | ||||
|     return 'maybe'; | ||||
|   } | ||||
| @@ -386,11 +397,12 @@ Flash.nativeSourceHandler.canPlayType = function(type){ | ||||
|  * @param  {Object} options The options passed to the tech | ||||
|  * @return {String}         'probably', 'maybe', or '' (empty string) | ||||
|  */ | ||||
| Flash.nativeSourceHandler.canHandleSource = function(source, options){ | ||||
|   var type; | ||||
| Flash.nativeSourceHandler.canHandleSource = function(source, options) { | ||||
|   let type; | ||||
|  | ||||
|   function guessMimeType(src) { | ||||
|     var ext = Url.getFileExtension(src); | ||||
|     let ext = Url.getFileExtension(src); | ||||
|  | ||||
|     if (ext) { | ||||
|       return `video/${ext}`; | ||||
|     } | ||||
| @@ -416,7 +428,7 @@ Flash.nativeSourceHandler.canHandleSource = function(source, options){ | ||||
|  * @param  {Flash}  tech     The instance of the Flash tech | ||||
|  * @param  {Object} options  The options to pass to the source | ||||
|  */ | ||||
| Flash.nativeSourceHandler.handleSource = function(source, tech, options){ | ||||
| Flash.nativeSourceHandler.handleSource = function(source, tech, options) { | ||||
|   tech.setSrc(source.src); | ||||
| }; | ||||
|  | ||||
| @@ -424,7 +436,7 @@ Flash.nativeSourceHandler.handleSource = function(source, tech, options){ | ||||
|  * Clean up the source handler when disposing the player or switching sources.. | ||||
|  * (no cleanup is needed when supporting the format natively) | ||||
|  */ | ||||
| Flash.nativeSourceHandler.dispose = function(){}; | ||||
| Flash.nativeSourceHandler.dispose = function() {}; | ||||
|  | ||||
| // Register the native source handler | ||||
| Flash.registerSourceHandler(Flash.nativeSourceHandler); | ||||
| @@ -436,7 +448,7 @@ Flash.formats = { | ||||
|   'video/m4v': 'MP4' | ||||
| }; | ||||
|  | ||||
| Flash.onReady = function(currSwf){ | ||||
| Flash.onReady = function(currSwf) { | ||||
|   let el = Dom.getEl(currSwf); | ||||
|   let tech = el && el.tech; | ||||
|  | ||||
| @@ -450,7 +462,7 @@ Flash.onReady = function(currSwf){ | ||||
|  | ||||
| // The SWF isn't always ready when it says it is. Sometimes the API functions still need to be added to the object. | ||||
| // If it's not ready, we set a timeout to check again shortly. | ||||
| Flash.checkReady = function(tech){ | ||||
| Flash.checkReady = function(tech) { | ||||
|   // stop worrying if the tech has been disposed | ||||
|   if (!tech.el()) { | ||||
|     return; | ||||
| @@ -462,20 +474,21 @@ Flash.checkReady = function(tech){ | ||||
|     tech.triggerReady(); | ||||
|   } else { | ||||
|     // wait longer | ||||
|     this.setTimeout(function(){ | ||||
|       Flash['checkReady'](tech); | ||||
|     this.setTimeout(function() { | ||||
|       Flash.checkReady(tech); | ||||
|     }, 50); | ||||
|   } | ||||
| }; | ||||
|  | ||||
| // Trigger events from the swf on the player | ||||
| Flash.onEvent = function(swfID, eventName){ | ||||
| Flash.onEvent = function(swfID, eventName) { | ||||
|   let tech = Dom.getEl(swfID).tech; | ||||
|  | ||||
|   tech.trigger(eventName, Array.prototype.slice.call(arguments, 2)); | ||||
| }; | ||||
|  | ||||
| // Log errors from the swf | ||||
| Flash.onError = function(swfID, err){ | ||||
| Flash.onError = function(swfID, err) { | ||||
|   const tech = Dom.getEl(swfID).tech; | ||||
|  | ||||
|   // trigger MEDIA_ERR_SRC_NOT_SUPPORTED | ||||
| @@ -488,7 +501,7 @@ Flash.onError = function(swfID, err){ | ||||
| }; | ||||
|  | ||||
| // Flash Version Check | ||||
| Flash.version = function(){ | ||||
| Flash.version = function() { | ||||
|   let version = '0,0,0'; | ||||
|  | ||||
|   // IE | ||||
| @@ -496,18 +509,20 @@ Flash.version = function(){ | ||||
|     version = new window.ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version').replace(/\D+/g, ',').match(/^,?(.+),?$/)[1]; | ||||
|  | ||||
|   // other browsers | ||||
|   } catch(e) { | ||||
|   } catch (e) { | ||||
|     try { | ||||
|       if (navigator.mimeTypes['application/x-shockwave-flash'].enabledPlugin){ | ||||
|       if (navigator.mimeTypes['application/x-shockwave-flash'].enabledPlugin) { | ||||
|         version = (navigator.plugins['Shockwave Flash 2.0'] || navigator.plugins['Shockwave Flash']).description.replace(/\D+/g, ',').match(/^,?(.+),?$/)[1]; | ||||
|       } | ||||
|     } catch(err) {} | ||||
|     } catch (err) { | ||||
|       // satisfy linter | ||||
|     } | ||||
|   } | ||||
|   return version.split(','); | ||||
| }; | ||||
|  | ||||
| // Flash embedding method. Only used in non-iframe mode | ||||
| Flash.embed = function(swf, flashVars, params, attributes){ | ||||
| Flash.embed = function(swf, flashVars, params, attributes) { | ||||
|   const code = Flash.getEmbedCode(swf, flashVars, params, attributes); | ||||
|  | ||||
|   // Get element by embedding code and retrieving created element | ||||
| @@ -516,7 +531,7 @@ Flash.embed = function(swf, flashVars, params, attributes){ | ||||
|   return obj; | ||||
| }; | ||||
|  | ||||
| Flash.getEmbedCode = function(swf, flashVars, params, attributes){ | ||||
| Flash.getEmbedCode = function(swf, flashVars, params, attributes) { | ||||
|   const objTag = '<object type="application/x-shockwave-flash" '; | ||||
|   let flashVarsString = ''; | ||||
|   let paramsString = ''; | ||||
| @@ -524,36 +539,38 @@ Flash.getEmbedCode = function(swf, flashVars, params, attributes){ | ||||
|  | ||||
|   // Convert flash vars to string | ||||
|   if (flashVars) { | ||||
|     Object.getOwnPropertyNames(flashVars).forEach(function(key){ | ||||
|     Object.getOwnPropertyNames(flashVars).forEach(function(key) { | ||||
|       flashVarsString += `${key}=${flashVars[key]}&`; | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   // Add swf, flashVars, and other default params | ||||
|   params = assign({ | ||||
|     'movie': swf, | ||||
|     'flashvars': flashVarsString, | ||||
|     'allowScriptAccess': 'always', // Required to talk to swf | ||||
|     'allowNetworking': 'all' // All should be default, but having security issues. | ||||
|     movie: swf, | ||||
|     flashvars: flashVarsString, | ||||
|     // Required to talk to swf | ||||
|     allowScriptAccess: 'always', | ||||
|     // All should be default, but having security issues. | ||||
|     allowNetworking: 'all' | ||||
|   }, params); | ||||
|  | ||||
|   // Create param tags string | ||||
|   Object.getOwnPropertyNames(params).forEach(function(key){ | ||||
|   Object.getOwnPropertyNames(params).forEach(function(key) { | ||||
|     paramsString += `<param name="${key}" value="${params[key]}" />`; | ||||
|   }); | ||||
|  | ||||
|   attributes = assign({ | ||||
|     // Add swf to attributes (need both for IE and Others to work) | ||||
|     'data': swf, | ||||
|     data: swf, | ||||
|  | ||||
|     // Default to 100% width/height | ||||
|     'width': '100%', | ||||
|     'height': '100%' | ||||
|     width: '100%', | ||||
|     height: '100%' | ||||
|  | ||||
|   }, attributes); | ||||
|  | ||||
|   // Create Attributes string | ||||
|   Object.getOwnPropertyNames(attributes).forEach(function(key){ | ||||
|   Object.getOwnPropertyNames(attributes).forEach(function(key) { | ||||
|     attrsString += `${key}="${attributes[key]}" `; | ||||
|   }); | ||||
|  | ||||
|   | ||||
| @@ -10,7 +10,6 @@ import * as Url from '../utils/url.js'; | ||||
| import * as Fn from '../utils/fn.js'; | ||||
| import log from '../utils/log.js'; | ||||
| import tsml from 'tsml'; | ||||
| import TextTrack from '../../../src/js/tracks/text-track.js'; | ||||
| import * as browser from '../utils/browser.js'; | ||||
| import document from 'global/document'; | ||||
| import window from 'global/window'; | ||||
| @@ -28,7 +27,7 @@ import toTitleCase from '../utils/to-title-case.js'; | ||||
|  */ | ||||
| class Html5 extends Tech { | ||||
|  | ||||
|   constructor(options, ready){ | ||||
|   constructor(options, ready) { | ||||
|     super(options, ready); | ||||
|  | ||||
|     const source = options.source; | ||||
| @@ -74,7 +73,7 @@ class Html5 extends Tech { | ||||
|         } | ||||
|       } | ||||
|  | ||||
|       for (let i=0; i<removeNodes.length; i++) { | ||||
|       for (let i = 0; i < removeNodes.length; i++) { | ||||
|         this.el_.removeChild(removeNodes[i]); | ||||
|       } | ||||
|     } | ||||
| @@ -102,7 +101,7 @@ class Html5 extends Tech { | ||||
|  | ||||
|     if (this.featuresNativeTextTracks) { | ||||
|       if (crossoriginTracks) { | ||||
|         log.warn(tsml`Text Tracks are being loaded from another origin but the crossorigin attribute isn't used.  | ||||
|         log.warn(tsml`Text Tracks are being loaded from another origin but the crossorigin attribute isn't used. | ||||
|             This may prevent text tracks from loading.`); | ||||
|       } | ||||
|  | ||||
| @@ -140,7 +139,7 @@ class Html5 extends Tech { | ||||
|         tl.removeEventListener('addtrack', this[`handle${capitalType}TrackAdd_`]); | ||||
|         tl.removeEventListener('removetrack', this[`handle${capitalType}TrackRemove_`]); | ||||
|       } | ||||
|        | ||||
|  | ||||
|       // Stop removing old text tracks | ||||
|       if (tl) { | ||||
|         this.off('loadstart', this[`removeOld${capitalType}Tracks_`]); | ||||
| @@ -164,11 +163,12 @@ class Html5 extends Tech { | ||||
|     // Check if this browser supports moving the element into the box. | ||||
|     // On the iPhone video will break if you move the element, | ||||
|     // So we have to create a brand new element. | ||||
|     if (!el || this['movingMediaElementInDOM'] === false) { | ||||
|     if (!el || this.movingMediaElementInDOM === false) { | ||||
|  | ||||
|       // If the original tag is still there, clone and remove it. | ||||
|       if (el) { | ||||
|         const clone = el.cloneNode(true); | ||||
|  | ||||
|         el.parentNode.insertBefore(clone, el); | ||||
|         Html5.disposeMediaElement(el); | ||||
|         el = clone; | ||||
| @@ -178,6 +178,7 @@ class Html5 extends Tech { | ||||
|         // determine if native controls should be used | ||||
|         let tagAttributes = this.options_.tag && Dom.getElAttributes(this.options_.tag); | ||||
|         let attributes = mergeOptions({}, tagAttributes); | ||||
|  | ||||
|         if (!browser.TOUCH_ENABLED || this.options_.nativeControlsForTouch !== true) { | ||||
|           delete attributes.controls; | ||||
|         } | ||||
| @@ -192,10 +193,12 @@ class Html5 extends Tech { | ||||
|     } | ||||
|  | ||||
|     // Update specific tag settings, in case they were overridden | ||||
|     let settingsAttrs = ['autoplay','preload','loop','muted']; | ||||
|     let settingsAttrs = ['autoplay', 'preload', 'loop', 'muted']; | ||||
|  | ||||
|     for (let i = settingsAttrs.length - 1; i >= 0; i--) { | ||||
|       const attr = settingsAttrs[i]; | ||||
|       let overwriteAttrs = {}; | ||||
|  | ||||
|       if (typeof this.options_[attr] !== 'undefined') { | ||||
|         overwriteAttrs[attr] = this.options_[attr]; | ||||
|       } | ||||
| @@ -232,6 +235,7 @@ class Html5 extends Tech { | ||||
|       let setLoadstartFired = function() { | ||||
|         loadstartFired = true; | ||||
|       }; | ||||
|  | ||||
|       this.on('loadstart', setLoadstartFired); | ||||
|  | ||||
|       let triggerLoadstart = function() { | ||||
| @@ -241,9 +245,10 @@ class Html5 extends Tech { | ||||
|           this.trigger('loadstart'); | ||||
|         } | ||||
|       }; | ||||
|  | ||||
|       this.on('loadedmetadata', triggerLoadstart); | ||||
|  | ||||
|       this.ready(function(){ | ||||
|       this.ready(function() { | ||||
|         this.off('loadstart', setLoadstartFired); | ||||
|         this.off('loadedmetadata', triggerLoadstart); | ||||
|  | ||||
| @@ -281,8 +286,8 @@ class Html5 extends Tech { | ||||
|     } | ||||
|  | ||||
|     // We still need to give the player time to add event listeners | ||||
|     this.ready(function(){ | ||||
|       eventsToTrigger.forEach(function(type){ | ||||
|     this.ready(function() { | ||||
|       eventsToTrigger.forEach(function(type) { | ||||
|         this.trigger(type); | ||||
|       }, this); | ||||
|     }); | ||||
| @@ -303,7 +308,7 @@ class Html5 extends Tech { | ||||
|         tt.addEventListener('addtrack', this.handleTextTrackAdd_); | ||||
|         tt.addEventListener('removetrack', this.handleTextTrackRemove_); | ||||
|       } | ||||
|      | ||||
|  | ||||
|       // Remove (native) texttracks that are not used anymore | ||||
|       this.on('loadstart', this.removeOldTextTracks_); | ||||
|     } | ||||
| @@ -311,6 +316,7 @@ class Html5 extends Tech { | ||||
|  | ||||
|   handleTextTrackChange(e) { | ||||
|     let tt = this.textTracks(); | ||||
|  | ||||
|     this.textTracks().trigger({ | ||||
|       type: 'change', | ||||
|       target: tt, | ||||
| @@ -329,6 +335,7 @@ class Html5 extends Tech { | ||||
|  | ||||
|   handleVideoTrackChange_(e) { | ||||
|     let vt = this.videoTracks(); | ||||
|  | ||||
|     this.videoTracks().trigger({ | ||||
|       type: 'change', | ||||
|       target: vt, | ||||
| @@ -347,6 +354,7 @@ class Html5 extends Tech { | ||||
|  | ||||
|   handleAudioTrackChange_(e) { | ||||
|     let audioTrackList = this.audioTracks(); | ||||
|  | ||||
|     this.audioTracks().trigger({ | ||||
|       type: 'change', | ||||
|       target: audioTrackList, | ||||
| @@ -374,14 +382,15 @@ class Html5 extends Tech { | ||||
|     // This will loop over the techTracks and check if they are still used by the HTML5 video element | ||||
|     // If not, they will be removed from the emulated list | ||||
|     let removeTracks = []; | ||||
|  | ||||
|     if (!elTracks) { | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     for (let i = 0; i < techTracks.length; i++) { | ||||
|       let techTrack = techTracks[i]; | ||||
|  | ||||
|       let found = false; | ||||
|  | ||||
|       for (let j = 0; j < elTracks.length; j++) { | ||||
|         if (elTracks[j] === techTrack) { | ||||
|           found = true; | ||||
| @@ -396,6 +405,7 @@ class Html5 extends Tech { | ||||
|  | ||||
|     for (let i = 0; i < removeTracks.length; i++) { | ||||
|       const track = removeTracks[i]; | ||||
|  | ||||
|       techTracks.removeTrack_(track); | ||||
|     } | ||||
|   } | ||||
| @@ -403,18 +413,21 @@ class Html5 extends Tech { | ||||
|   removeOldTextTracks_() { | ||||
|     const techTracks = this.textTracks(); | ||||
|     const elTracks = this.el().textTracks; | ||||
|  | ||||
|     this.removeOldTracks_(techTracks, elTracks); | ||||
|   } | ||||
|  | ||||
|   removeOldAudioTracks_() { | ||||
|     const techTracks = this.audioTracks(); | ||||
|     const elTracks = this.el().audioTracks; | ||||
|  | ||||
|     this.removeOldTracks_(techTracks, elTracks); | ||||
|   } | ||||
|  | ||||
|   removeOldVideoTracks_() { | ||||
|     const techTracks = this.videoTracks(); | ||||
|     const elTracks = this.el().videoTracks; | ||||
|  | ||||
|     this.removeOldTracks_(techTracks, elTracks); | ||||
|   } | ||||
|  | ||||
| @@ -423,14 +436,18 @@ class Html5 extends Tech { | ||||
|    * | ||||
|    * @method play | ||||
|    */ | ||||
|   play() { this.el_.play(); } | ||||
|   play() { | ||||
|     this.el_.play(); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Pause for html5 tech | ||||
|    * | ||||
|    * @method pause | ||||
|    */ | ||||
|   pause() { this.el_.pause(); } | ||||
|   pause() { | ||||
|     this.el_.pause(); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Paused for html5 tech | ||||
| @@ -438,7 +455,9 @@ class Html5 extends Tech { | ||||
|    * @return {Boolean} | ||||
|    * @method paused | ||||
|    */ | ||||
|   paused() { return this.el_.paused; } | ||||
|   paused() { | ||||
|     return this.el_.paused; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Get current time | ||||
| @@ -446,7 +465,9 @@ class Html5 extends Tech { | ||||
|    * @return {Number} | ||||
|    * @method currentTime | ||||
|    */ | ||||
|   currentTime() { return this.el_.currentTime; } | ||||
|   currentTime() { | ||||
|     return this.el_.currentTime; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Set current time | ||||
| @@ -457,7 +478,7 @@ class Html5 extends Tech { | ||||
|   setCurrentTime(seconds) { | ||||
|     try { | ||||
|       this.el_.currentTime = seconds; | ||||
|     } catch(e) { | ||||
|     } catch (e) { | ||||
|       log(e, 'Video is not ready. (Video.js)'); | ||||
|       // this.warning(VideoJS.warnings.videoNotReady); | ||||
|     } | ||||
| @@ -469,7 +490,9 @@ class Html5 extends Tech { | ||||
|    * @return {Number} | ||||
|    * @method duration | ||||
|    */ | ||||
|   duration() { return this.el_.duration || 0; } | ||||
|   duration() { | ||||
|     return this.el_.duration || 0; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Get a TimeRange object that represents the intersection | ||||
| @@ -479,7 +502,9 @@ class Html5 extends Tech { | ||||
|    * @return {TimeRangeObject} | ||||
|    * @method buffered | ||||
|    */ | ||||
|   buffered() { return this.el_.buffered; } | ||||
|   buffered() { | ||||
|     return this.el_.buffered; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Get volume level | ||||
| @@ -487,7 +512,9 @@ class Html5 extends Tech { | ||||
|    * @return {Number} | ||||
|    * @method volume | ||||
|    */ | ||||
|   volume() { return this.el_.volume; } | ||||
|   volume() { | ||||
|     return this.el_.volume; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Set volume level | ||||
| @@ -495,7 +522,9 @@ class Html5 extends Tech { | ||||
|    * @param {Number} percentAsDecimal Volume percent as a decimal | ||||
|    * @method setVolume | ||||
|    */ | ||||
|   setVolume(percentAsDecimal) { this.el_.volume = percentAsDecimal; } | ||||
|   setVolume(percentAsDecimal) { | ||||
|     this.el_.volume = percentAsDecimal; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Get if muted | ||||
| @@ -503,7 +532,9 @@ class Html5 extends Tech { | ||||
|    * @return {Boolean} | ||||
|    * @method muted | ||||
|    */ | ||||
|   muted() { return this.el_.muted; } | ||||
|   muted() { | ||||
|     return this.el_.muted; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Set muted | ||||
| @@ -511,7 +542,9 @@ class Html5 extends Tech { | ||||
|    * @param {Boolean} If player is to be muted or note | ||||
|    * @method setMuted | ||||
|    */ | ||||
|   setMuted(muted) { this.el_.muted = muted; } | ||||
|   setMuted(muted) { | ||||
|     this.el_.muted = muted; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Get player width | ||||
| @@ -519,7 +552,9 @@ class Html5 extends Tech { | ||||
|    * @return {Number} | ||||
|    * @method width | ||||
|    */ | ||||
|   width() { return this.el_.offsetWidth; } | ||||
|   width() { | ||||
|     return this.el_.offsetWidth; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Get player height | ||||
| @@ -527,7 +562,9 @@ class Html5 extends Tech { | ||||
|    * @return {Number} | ||||
|    * @method height | ||||
|    */ | ||||
|   height() {  return this.el_.offsetHeight; } | ||||
|   height() { | ||||
|     return this.el_.offsetHeight; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Get if there is fullscreen support | ||||
| @@ -538,8 +575,9 @@ class Html5 extends Tech { | ||||
|   supportsFullScreen() { | ||||
|     if (typeof this.el_.webkitEnterFullScreen === 'function') { | ||||
|       let userAgent = window.navigator.userAgent; | ||||
|  | ||||
|       // Seems to be broken in Chromium/Chrome && Safari in Leopard | ||||
|       if (/Android/.test(userAgent) || !/Chrome|Mac OS X 10.5/.test(userAgent)) { | ||||
|       if ((/Android/).test(userAgent) || !(/Chrome|Mac OS X 10.5/).test(userAgent)) { | ||||
|         return true; | ||||
|       } | ||||
|     } | ||||
| @@ -552,7 +590,7 @@ class Html5 extends Tech { | ||||
|    * @method enterFullScreen | ||||
|    */ | ||||
|   enterFullScreen() { | ||||
|     var video = this.el_; | ||||
|     let video = this.el_; | ||||
|  | ||||
|     if ('webkitDisplayingFullscreen' in video) { | ||||
|       this.one('webkitbeginfullscreen', function() { | ||||
| @@ -571,7 +609,7 @@ class Html5 extends Tech { | ||||
|  | ||||
|       // playing and pausing synchronously during the transition to fullscreen | ||||
|       // can get iOS ~6.1 devices into a play/pause loop | ||||
|       this.setTimeout(function(){ | ||||
|       this.setTimeout(function() { | ||||
|         video.pause(); | ||||
|         video.webkitEnterFullScreen(); | ||||
|       }, 0); | ||||
| @@ -599,10 +637,10 @@ class Html5 extends Tech { | ||||
|   src(src) { | ||||
|     if (src === undefined) { | ||||
|       return this.el_.src; | ||||
|     } else { | ||||
|       // Setting src through `src` instead of `setSrc` will be deprecated | ||||
|       this.setSrc(src); | ||||
|     } | ||||
|  | ||||
|     // Setting src through `src` instead of `setSrc` will be deprecated | ||||
|     this.setSrc(src); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
| @@ -621,7 +659,7 @@ class Html5 extends Tech { | ||||
|    * | ||||
|    * @method load | ||||
|    */ | ||||
|   load(){ | ||||
|   load() { | ||||
|     this.el_.load(); | ||||
|   } | ||||
|  | ||||
| @@ -643,9 +681,8 @@ class Html5 extends Tech { | ||||
|   currentSrc() { | ||||
|     if (this.currentSource_) { | ||||
|       return this.currentSource_.src; | ||||
|     } else { | ||||
|       return this.el_.currentSrc; | ||||
|     } | ||||
|     return this.el_.currentSrc; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
| @@ -654,7 +691,9 @@ class Html5 extends Tech { | ||||
|    * @return {String} | ||||
|    * @method poster | ||||
|    */ | ||||
|   poster() { return this.el_.poster; } | ||||
|   poster() { | ||||
|     return this.el_.poster; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Set poster | ||||
| @@ -662,7 +701,9 @@ class Html5 extends Tech { | ||||
|    * @param {String} val URL to poster image | ||||
|    * @method | ||||
|    */ | ||||
|   setPoster(val) { this.el_.poster = val; } | ||||
|   setPoster(val) { | ||||
|     this.el_.poster = val; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Get preload attribute | ||||
| @@ -670,7 +711,9 @@ class Html5 extends Tech { | ||||
|    * @return {String} | ||||
|    * @method preload | ||||
|    */ | ||||
|   preload() { return this.el_.preload; } | ||||
|   preload() { | ||||
|     return this.el_.preload; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Set preload attribute | ||||
| @@ -678,7 +721,9 @@ class Html5 extends Tech { | ||||
|    * @param {String} val Value for preload attribute | ||||
|    * @method setPreload | ||||
|    */ | ||||
|   setPreload(val) { this.el_.preload = val; } | ||||
|   setPreload(val) { | ||||
|     this.el_.preload = val; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Get autoplay attribute | ||||
| @@ -686,7 +731,9 @@ class Html5 extends Tech { | ||||
|    * @return {String} | ||||
|    * @method autoplay | ||||
|    */ | ||||
|   autoplay() { return this.el_.autoplay; } | ||||
|   autoplay() { | ||||
|     return this.el_.autoplay; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Set autoplay attribute | ||||
| @@ -694,7 +741,9 @@ class Html5 extends Tech { | ||||
|    * @param {String} val Value for preload attribute | ||||
|    * @method setAutoplay | ||||
|    */ | ||||
|   setAutoplay(val) { this.el_.autoplay = val; } | ||||
|   setAutoplay(val) { | ||||
|     this.el_.autoplay = val; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Get controls attribute | ||||
| @@ -702,7 +751,9 @@ class Html5 extends Tech { | ||||
|    * @return {String} | ||||
|    * @method controls | ||||
|    */ | ||||
|   controls() { return this.el_.controls; } | ||||
|   controls() { | ||||
|     return this.el_.controls; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Set controls attribute | ||||
| @@ -710,7 +761,9 @@ class Html5 extends Tech { | ||||
|    * @param {String} val Value for controls attribute | ||||
|    * @method setControls | ||||
|    */ | ||||
|   setControls(val) { this.el_.controls = !!val; } | ||||
|   setControls(val) { | ||||
|     this.el_.controls = !!val; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Get loop attribute | ||||
| @@ -718,7 +771,9 @@ class Html5 extends Tech { | ||||
|    * @return {String} | ||||
|    * @method loop | ||||
|    */ | ||||
|   loop() { return this.el_.loop; } | ||||
|   loop() { | ||||
|     return this.el_.loop; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Set loop attribute | ||||
| @@ -726,7 +781,9 @@ class Html5 extends Tech { | ||||
|    * @param {String} val Value for loop attribute | ||||
|    * @method setLoop | ||||
|    */ | ||||
|   setLoop(val) { this.el_.loop = val; } | ||||
|   setLoop(val) { | ||||
|     this.el_.loop = val; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Get error value | ||||
| @@ -734,7 +791,9 @@ class Html5 extends Tech { | ||||
|    * @return {String} | ||||
|    * @method error | ||||
|    */ | ||||
|   error() { return this.el_.error; } | ||||
|   error() { | ||||
|     return this.el_.error; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Get whether or not the player is in the "seeking" state | ||||
| @@ -742,7 +801,9 @@ class Html5 extends Tech { | ||||
|    * @return {Boolean} | ||||
|    * @method seeking | ||||
|    */ | ||||
|   seeking() { return this.el_.seeking; } | ||||
|   seeking() { | ||||
|     return this.el_.seeking; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Get a TimeRanges object that represents the | ||||
| @@ -752,7 +813,9 @@ class Html5 extends Tech { | ||||
|    * @return {TimeRangeObject} | ||||
|    * @method seekable | ||||
|    */ | ||||
|   seekable() { return this.el_.seekable; } | ||||
|   seekable() { | ||||
|     return this.el_.seekable; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Get if video ended | ||||
| @@ -760,7 +823,9 @@ class Html5 extends Tech { | ||||
|    * @return {Boolean} | ||||
|    * @method ended | ||||
|    */ | ||||
|   ended() { return this.el_.ended; } | ||||
|   ended() { | ||||
|     return this.el_.ended; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Get the value of the muted content attribute | ||||
| @@ -770,7 +835,9 @@ class Html5 extends Tech { | ||||
|    * @return {Boolean} | ||||
|    * @method defaultMuted | ||||
|    */ | ||||
|   defaultMuted() { return this.el_.defaultMuted; } | ||||
|   defaultMuted() { | ||||
|     return this.el_.defaultMuted; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Get desired speed at which the media resource is to play | ||||
| @@ -778,7 +845,9 @@ class Html5 extends Tech { | ||||
|    * @return {Number} | ||||
|    * @method playbackRate | ||||
|    */ | ||||
|   playbackRate() { return this.el_.playbackRate; } | ||||
|   playbackRate() { | ||||
|     return this.el_.playbackRate; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Returns a TimeRanges object that represents the ranges of the | ||||
| @@ -787,7 +856,9 @@ class Html5 extends Tech { | ||||
|    * timeline that has been reached through normal playback | ||||
|    * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-played | ||||
|    */ | ||||
|   played() { return this.el_.played; } | ||||
|   played() { | ||||
|     return this.el_.played; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Set desired speed at which the media resource is to play | ||||
| @@ -795,7 +866,9 @@ class Html5 extends Tech { | ||||
|    * @param {Number} val Speed at which the media resource is to play | ||||
|    * @method setPlaybackRate | ||||
|    */ | ||||
|   setPlaybackRate(val) { this.el_.playbackRate = val; } | ||||
|   setPlaybackRate(val) { | ||||
|     this.el_.playbackRate = val; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Get the current state of network activity for the element, from | ||||
| @@ -808,7 +881,9 @@ class Html5 extends Tech { | ||||
|    * @return {Number} | ||||
|    * @method networkState | ||||
|    */ | ||||
|   networkState() { return this.el_.networkState; } | ||||
|   networkState() { | ||||
|     return this.el_.networkState; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Get a value that expresses the current state of the element | ||||
| @@ -823,7 +898,9 @@ class Html5 extends Tech { | ||||
|    * @return {Number} | ||||
|    * @method readyState | ||||
|    */ | ||||
|   readyState() { return this.el_.readyState; } | ||||
|   readyState() { | ||||
|     return this.el_.readyState; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Get width of video | ||||
| @@ -831,7 +908,9 @@ class Html5 extends Tech { | ||||
|    * @return {Number} | ||||
|    * @method videoWidth | ||||
|    */ | ||||
|   videoWidth() { return this.el_.videoWidth; } | ||||
|   videoWidth() { | ||||
|     return this.el_.videoWidth; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Get height of video | ||||
| @@ -839,7 +918,9 @@ class Html5 extends Tech { | ||||
|    * @return {Number} | ||||
|    * @method videoHeight | ||||
|    */ | ||||
|   videoHeight() { return this.el_.videoHeight; } | ||||
|   videoHeight() { | ||||
|     return this.el_.videoHeight; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Get text tracks | ||||
| @@ -862,7 +943,7 @@ class Html5 extends Tech { | ||||
|    * @method addTextTrack | ||||
|    */ | ||||
|   addTextTrack(kind, label, language) { | ||||
|     if (!this['featuresNativeTextTracks']) { | ||||
|     if (!this.featuresNativeTextTracks) { | ||||
|       return super.addTextTrack(kind, label, language); | ||||
|     } | ||||
|  | ||||
| @@ -877,8 +958,8 @@ class Html5 extends Tech { | ||||
|    * @return {HTMLTrackElement} | ||||
|    * @method addRemoteTextTrack | ||||
|    */ | ||||
|   addRemoteTextTrack(options={}) { | ||||
|     if (!this['featuresNativeTextTracks']) { | ||||
|   addRemoteTextTrack(options = {}) { | ||||
|     if (!this.featuresNativeTextTracks) { | ||||
|       return super.addRemoteTextTrack(options); | ||||
|     } | ||||
|  | ||||
| @@ -919,12 +1000,11 @@ class Html5 extends Tech { | ||||
|    * @method removeRemoteTextTrack | ||||
|    */ | ||||
|   removeRemoteTextTrack(track) { | ||||
|     if (!this['featuresNativeTextTracks']) { | ||||
|     if (!this.featuresNativeTextTracks) { | ||||
|       return super.removeRemoteTextTrack(track); | ||||
|     } | ||||
|  | ||||
|     let tracks, i; | ||||
|  | ||||
|     let tracks; | ||||
|     let trackElement = this.remoteTextTrackEls().getTrackElementByTrack_(track); | ||||
|  | ||||
|     // remove HTMLTrackElement and TextTrack from remote list | ||||
| @@ -933,7 +1013,8 @@ class Html5 extends Tech { | ||||
|  | ||||
|     tracks = this.$$('track'); | ||||
|  | ||||
|     i = tracks.length; | ||||
|     let i = tracks.length; | ||||
|  | ||||
|     while (i--) { | ||||
|       if (track === tracks[i] || track === tracks[i].track) { | ||||
|         this.el().removeChild(tracks[i]); | ||||
| @@ -943,7 +1024,6 @@ class Html5 extends Tech { | ||||
|  | ||||
| } | ||||
|  | ||||
|  | ||||
| /* HTML5 Support Testing ---------------------------------------------------- */ | ||||
|  | ||||
| /* | ||||
| @@ -955,6 +1035,7 @@ class Html5 extends Tech { | ||||
| */ | ||||
| Html5.TEST_VID = document.createElement('video'); | ||||
| let track = document.createElement('track'); | ||||
|  | ||||
| track.kind = 'captions'; | ||||
| track.srclang = 'en'; | ||||
| track.label = 'English'; | ||||
| @@ -965,10 +1046,10 @@ Html5.TEST_VID.appendChild(track); | ||||
|  * | ||||
|  * @return {Boolean} | ||||
|  */ | ||||
| Html5.isSupported = function(){ | ||||
| Html5.isSupported = function() { | ||||
|   // IE9 with no Media Player is a LIAR! (#984) | ||||
|   try { | ||||
|     Html5.TEST_VID['volume'] = 0.5; | ||||
|     Html5.TEST_VID.volume = 0.5; | ||||
|   } catch (e) { | ||||
|     return false; | ||||
|   } | ||||
| @@ -994,12 +1075,12 @@ Html5.nativeSourceHandler = {}; | ||||
|  * @param  {String} type    The mimetype to check | ||||
|  * @return {String}         'probably', 'maybe', or '' (empty string) | ||||
|  */ | ||||
| Html5.nativeSourceHandler.canPlayType = function(type){ | ||||
| Html5.nativeSourceHandler.canPlayType = function(type) { | ||||
|   // IE9 on Windows 7 without MediaPlayer throws an error here | ||||
|   // https://github.com/videojs/video.js/issues/519 | ||||
|   try { | ||||
|     return Html5.TEST_VID.canPlayType(type); | ||||
|   } catch(e) { | ||||
|   } catch (e) { | ||||
|     return ''; | ||||
|   } | ||||
| }; | ||||
| @@ -1011,15 +1092,15 @@ Html5.nativeSourceHandler.canPlayType = function(type){ | ||||
|  * @param  {Object} options The options passed to the tech | ||||
|  * @return {String}         'probably', 'maybe', or '' (empty string) | ||||
|  */ | ||||
| Html5.nativeSourceHandler.canHandleSource = function(source, options){ | ||||
|   var match, ext; | ||||
| Html5.nativeSourceHandler.canHandleSource = function(source, options) { | ||||
|  | ||||
|   // If a type was provided we should rely on that | ||||
|   if (source.type) { | ||||
|     return Html5.nativeSourceHandler.canPlayType(source.type); | ||||
|  | ||||
|   // If no type, fall back to checking 'video/[EXTENSION]' | ||||
|   } else if (source.src) { | ||||
|     // If no type, fall back to checking 'video/[EXTENSION]' | ||||
|     ext = Url.getFileExtension(source.src); | ||||
|     let ext = Url.getFileExtension(source.src); | ||||
|  | ||||
|     return Html5.nativeSourceHandler.canPlayType(`video/${ext}`); | ||||
|   } | ||||
| @@ -1036,7 +1117,7 @@ Html5.nativeSourceHandler.canHandleSource = function(source, options){ | ||||
|  * @param  {Html5}  tech     The instance of the Html5 tech | ||||
|  * @param  {Object} options  The options to pass to the source | ||||
|  */ | ||||
| Html5.nativeSourceHandler.handleSource = function(source, tech, options){ | ||||
| Html5.nativeSourceHandler.handleSource = function(source, tech, options) { | ||||
|   tech.setSrc(source.src); | ||||
| }; | ||||
|  | ||||
| @@ -1044,7 +1125,7 @@ Html5.nativeSourceHandler.handleSource = function(source, tech, options){ | ||||
| * Clean up the source handler when disposing the player or switching sources.. | ||||
| * (no cleanup is needed when supporting the format natively) | ||||
| */ | ||||
| Html5.nativeSourceHandler.dispose = function(){}; | ||||
| Html5.nativeSourceHandler.dispose = function() {}; | ||||
|  | ||||
| // Register the native source handler | ||||
| Html5.registerSourceHandler(Html5.nativeSourceHandler); | ||||
| @@ -1056,13 +1137,14 @@ Html5.registerSourceHandler(Html5.nativeSourceHandler); | ||||
|  * | ||||
|  * @return {Boolean} | ||||
|  */ | ||||
| Html5.canControlVolume = function(){ | ||||
| Html5.canControlVolume = function() { | ||||
|   // IE will error if Windows Media Player not installed #3315 | ||||
|   try { | ||||
|     var volume =  Html5.TEST_VID.volume; | ||||
|     let volume = Html5.TEST_VID.volume; | ||||
|  | ||||
|     Html5.TEST_VID.volume = (volume / 2) + 0.1; | ||||
|     return volume !== Html5.TEST_VID.volume; | ||||
|   } catch(e) { | ||||
|   } catch (e) { | ||||
|     return false; | ||||
|   } | ||||
| }; | ||||
| @@ -1072,7 +1154,7 @@ Html5.canControlVolume = function(){ | ||||
|  * | ||||
|  * @return {Boolean} | ||||
|  */ | ||||
| Html5.canControlPlaybackRate = function(){ | ||||
| Html5.canControlPlaybackRate = function() { | ||||
|   // Playback rate API is implemented in Android Chrome, but doesn't do anything | ||||
|   // https://github.com/videojs/video.js/issues/3180 | ||||
|   if (browser.IS_ANDROID && browser.IS_CHROME) { | ||||
| @@ -1080,10 +1162,11 @@ Html5.canControlPlaybackRate = function(){ | ||||
|   } | ||||
|   // IE will error if Windows Media Player not installed #3315 | ||||
|   try { | ||||
|     var playbackRate = Html5.TEST_VID.playbackRate; | ||||
|     let playbackRate = Html5.TEST_VID.playbackRate; | ||||
|  | ||||
|     Html5.TEST_VID.playbackRate = (playbackRate / 2) + 0.1; | ||||
|     return playbackRate !== Html5.TEST_VID.playbackRate; | ||||
|   } catch(e) { | ||||
|   } catch (e) { | ||||
|     return false; | ||||
|   } | ||||
| }; | ||||
| @@ -1094,7 +1177,7 @@ Html5.canControlPlaybackRate = function(){ | ||||
|  * @return {Boolean} | ||||
|  */ | ||||
| Html5.supportsNativeTextTracks = function() { | ||||
|   var supportsTextTracks; | ||||
|   let supportsTextTracks; | ||||
|  | ||||
|   // Figure out native text track support | ||||
|   // If mode is a number, we cannot change it because it'll disappear from view. | ||||
| @@ -1103,7 +1186,7 @@ Html5.supportsNativeTextTracks = function() { | ||||
|   // TODO: Investigate firefox: https://github.com/videojs/video.js/issues/1862 | ||||
|   supportsTextTracks = !!Html5.TEST_VID.textTracks; | ||||
|   if (supportsTextTracks && Html5.TEST_VID.textTracks.length > 0) { | ||||
|     supportsTextTracks = typeof Html5.TEST_VID.textTracks[0]['mode'] !== 'number'; | ||||
|     supportsTextTracks = typeof Html5.TEST_VID.textTracks[0].mode !== 'number'; | ||||
|   } | ||||
|   if (supportsTextTracks && browser.IS_FIREFOX) { | ||||
|     supportsTextTracks = false; | ||||
| @@ -1122,6 +1205,7 @@ Html5.supportsNativeTextTracks = function() { | ||||
|  */ | ||||
| Html5.supportsNativeVideoTracks = function() { | ||||
|   let supportsVideoTracks = !!Html5.TEST_VID.videoTracks; | ||||
|  | ||||
|   return supportsVideoTracks; | ||||
| }; | ||||
|  | ||||
| @@ -1132,10 +1216,10 @@ Html5.supportsNativeVideoTracks = function() { | ||||
|  */ | ||||
| Html5.supportsNativeAudioTracks = function() { | ||||
|   let supportsAudioTracks = !!Html5.TEST_VID.audioTracks; | ||||
|  | ||||
|   return supportsAudioTracks; | ||||
| }; | ||||
|  | ||||
|  | ||||
| /** | ||||
|  * An array of events available on the Html5 tech. | ||||
|  * | ||||
| @@ -1172,14 +1256,14 @@ Html5.Events = [ | ||||
|  * | ||||
|  * @type {Boolean} | ||||
|  */ | ||||
| Html5.prototype['featuresVolumeControl'] = Html5.canControlVolume(); | ||||
| Html5.prototype.featuresVolumeControl = Html5.canControlVolume(); | ||||
|  | ||||
| /* | ||||
|  * Set the tech's playbackRate support status | ||||
|  * | ||||
|  * @type {Boolean} | ||||
|  */ | ||||
| Html5.prototype['featuresPlaybackRate'] = Html5.canControlPlaybackRate(); | ||||
| Html5.prototype.featuresPlaybackRate = Html5.canControlPlaybackRate(); | ||||
|  | ||||
| /* | ||||
|  * Set the tech's status on moving the video element. | ||||
| @@ -1187,41 +1271,41 @@ Html5.prototype['featuresPlaybackRate'] = Html5.canControlPlaybackRate(); | ||||
|  * | ||||
|  * @type {Boolean} | ||||
|  */ | ||||
| Html5.prototype['movingMediaElementInDOM'] = !browser.IS_IOS; | ||||
| Html5.prototype.movingMediaElementInDOM = !browser.IS_IOS; | ||||
|  | ||||
| /* | ||||
|  * Set the the tech's fullscreen resize support status. | ||||
|  * HTML video is able to automatically resize when going to fullscreen. | ||||
|  * (No longer appears to be used. Can probably be removed.) | ||||
|  */ | ||||
| Html5.prototype['featuresFullscreenResize'] = true; | ||||
| Html5.prototype.featuresFullscreenResize = true; | ||||
|  | ||||
| /* | ||||
|  * Set the tech's progress event support status | ||||
|  * (this disables the manual progress events of the Tech) | ||||
|  */ | ||||
| Html5.prototype['featuresProgressEvents'] = true; | ||||
| Html5.prototype.featuresProgressEvents = true; | ||||
|  | ||||
| /* | ||||
|  * Sets the tech's status on native text track support | ||||
|  * | ||||
|  * @type {Boolean} | ||||
|  */ | ||||
| Html5.prototype['featuresNativeTextTracks'] = Html5.supportsNativeTextTracks(); | ||||
| Html5.prototype.featuresNativeTextTracks = Html5.supportsNativeTextTracks(); | ||||
|  | ||||
| /** | ||||
|  * Sets the tech's status on native text track support | ||||
|  * | ||||
|  * @type {Boolean} | ||||
|  */ | ||||
| Html5.prototype['featuresNativeVideoTracks'] = Html5.supportsNativeVideoTracks(); | ||||
| Html5.prototype.featuresNativeVideoTracks = Html5.supportsNativeVideoTracks(); | ||||
|  | ||||
| /** | ||||
|  * Sets the tech's status on native audio track support | ||||
|  * | ||||
|  * @type {Boolean} | ||||
|  */ | ||||
| Html5.prototype['featuresNativeAudioTracks'] = Html5.supportsNativeAudioTracks(); | ||||
| Html5.prototype.featuresNativeAudioTracks = Html5.supportsNativeAudioTracks(); | ||||
|  | ||||
| // HTML5 Feature detection and Device Fixes --------------------------------- // | ||||
| let canPlayType; | ||||
| @@ -1249,7 +1333,7 @@ Html5.patchCanPlayType = function() { | ||||
|       canPlayType = Html5.TEST_VID.constructor.prototype.canPlayType; | ||||
|     } | ||||
|  | ||||
|     Html5.TEST_VID.constructor.prototype.canPlayType = function(type){ | ||||
|     Html5.TEST_VID.constructor.prototype.canPlayType = function(type) { | ||||
|       if (type && mp4RE.test(type)) { | ||||
|         return 'maybe'; | ||||
|       } | ||||
| @@ -1259,7 +1343,8 @@ Html5.patchCanPlayType = function() { | ||||
| }; | ||||
|  | ||||
| Html5.unpatchCanPlayType = function() { | ||||
|   var r = Html5.TEST_VID.constructor.prototype.canPlayType; | ||||
|   let r = Html5.TEST_VID.constructor.prototype.canPlayType; | ||||
|  | ||||
|   Html5.TEST_VID.constructor.prototype.canPlayType = canPlayType; | ||||
|   canPlayType = null; | ||||
|   return r; | ||||
| @@ -1268,15 +1353,17 @@ Html5.unpatchCanPlayType = function() { | ||||
| // by default, patch the video element | ||||
| Html5.patchCanPlayType(); | ||||
|  | ||||
| Html5.disposeMediaElement = function(el){ | ||||
|   if (!el) { return; } | ||||
| Html5.disposeMediaElement = function(el) { | ||||
|   if (!el) { | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   if (el.parentNode) { | ||||
|     el.parentNode.removeChild(el); | ||||
|   } | ||||
|  | ||||
|   // remove any child track or source nodes to prevent their loading | ||||
|   while(el.hasChildNodes()) { | ||||
|   while (el.hasChildNodes()) { | ||||
|     el.removeChild(el.firstChild); | ||||
|   } | ||||
|  | ||||
| @@ -1294,15 +1381,18 @@ Html5.disposeMediaElement = function(el){ | ||||
|       } catch (e) { | ||||
|         // not supported | ||||
|       } | ||||
|     })(); | ||||
|     }()); | ||||
|   } | ||||
| }; | ||||
|  | ||||
| Html5.resetMediaElement = function(el){ | ||||
|   if (!el) { return; } | ||||
| Html5.resetMediaElement = function(el) { | ||||
|   if (!el) { | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   let sources = el.querySelectorAll('source'); | ||||
|   let i = sources.length; | ||||
|  | ||||
|   while (i--) { | ||||
|     el.removeChild(sources[i]); | ||||
|   } | ||||
| @@ -1316,8 +1406,10 @@ Html5.resetMediaElement = function(el){ | ||||
|     (function() { | ||||
|       try { | ||||
|         el.load(); | ||||
|       } catch (e) {} | ||||
|     })(); | ||||
|       } catch (e) { | ||||
|         // satisfy linter | ||||
|       } | ||||
|     }()); | ||||
|   } | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -3,7 +3,6 @@ | ||||
|  */ | ||||
| import Component from '../component.js'; | ||||
| import Tech from './tech.js'; | ||||
| import window from 'global/window'; | ||||
| import toTitleCase from '../utils/to-title-case.js'; | ||||
|  | ||||
| /** | ||||
| @@ -18,16 +17,17 @@ import toTitleCase from '../utils/to-title-case.js'; | ||||
|  */ | ||||
| class MediaLoader extends Component { | ||||
|  | ||||
|   constructor(player, options, ready){ | ||||
|   constructor(player, options, ready) { | ||||
|     super(player, options, ready); | ||||
|  | ||||
|     // If there are no sources when the player is initialized, | ||||
|     // load the first supported playback technology. | ||||
|  | ||||
|     if (!options.playerOptions['sources'] || options.playerOptions['sources'].length === 0) { | ||||
|       for (let i=0, j=options.playerOptions['techOrder']; i<j.length; i++) { | ||||
|     if (!options.playerOptions.sources || options.playerOptions.sources.length === 0) { | ||||
|       for (let i = 0, j = options.playerOptions.techOrder; i < j.length; i++) { | ||||
|         let techName = toTitleCase(j[i]); | ||||
|         let tech = Tech.getTech(techName); | ||||
|  | ||||
|         // Support old behavior of techs being registered as components. | ||||
|         // Remove once that deprecated behavior is removed. | ||||
|         if (!techName) { | ||||
| @@ -41,11 +41,11 @@ class MediaLoader extends Component { | ||||
|         } | ||||
|       } | ||||
|     } else { | ||||
|       // // Loop through playback technologies (HTML5, Flash) and check for support. | ||||
|       // // Then load the best source. | ||||
|       // // A few assumptions here: | ||||
|       // //   All playback technologies respect preload false. | ||||
|       player.src(options.playerOptions['sources']); | ||||
|       // Loop through playback technologies (HTML5, Flash) and check for support. | ||||
|       // Then load the best source. | ||||
|       // A few assumptions here: | ||||
|       //   All playback technologies respect preload false. | ||||
|       player.src(options.playerOptions.sources); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -10,10 +10,8 @@ import HTMLTrackElementList from '../tracks/html-track-element-list'; | ||||
| import mergeOptions from '../utils/merge-options.js'; | ||||
| import TextTrack from '../tracks/text-track'; | ||||
| import TextTrackList from '../tracks/text-track-list'; | ||||
| import VideoTrack from '../tracks/video-track'; | ||||
| import VideoTrackList from '../tracks/video-track-list'; | ||||
| import AudioTrackList from '../tracks/audio-track-list'; | ||||
| import AudioTrack from '../tracks/audio-track'; | ||||
| import * as Fn from '../utils/fn.js'; | ||||
| import log from '../utils/log.js'; | ||||
| import { createTimeRange } from '../utils/time-ranges.js'; | ||||
| @@ -22,6 +20,26 @@ import MediaError from '../media-error.js'; | ||||
| import window from 'global/window'; | ||||
| import document from 'global/document'; | ||||
|  | ||||
| function createTrackHelper(self, kind, label, language, options = {}) { | ||||
|   let tracks = self.textTracks(); | ||||
|  | ||||
|   options.kind = kind; | ||||
|  | ||||
|   if (label) { | ||||
|     options.label = label; | ||||
|   } | ||||
|   if (language) { | ||||
|     options.language = language; | ||||
|   } | ||||
|   options.tech = self; | ||||
|  | ||||
|   let track = new TextTrack(options); | ||||
|  | ||||
|   tracks.addTrack_(track); | ||||
|  | ||||
|   return track; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Base class for media (HTML5 Video, Flash) controllers | ||||
|  * | ||||
| @@ -32,7 +50,7 @@ import document from 'global/document'; | ||||
|  */ | ||||
| class Tech extends Component { | ||||
|  | ||||
|   constructor(options={}, ready=function(){}){ | ||||
|   constructor(options = {}, ready = function() {}) { | ||||
|     // we don't want the tech to report user activity automatically. | ||||
|     // This is done manually in addControlsListeners | ||||
|     options.reportTouchActivity = false; | ||||
| @@ -114,7 +132,7 @@ class Tech extends Component { | ||||
|    */ | ||||
|   trackProgress() { | ||||
|     this.stopTrackingProgress(); | ||||
|     this.progressInterval = this.setInterval(Fn.bind(this, function(){ | ||||
|     this.progressInterval = this.setInterval(Fn.bind(this, function() { | ||||
|       // Don't trigger unless buffered amount is greater than last time | ||||
|  | ||||
|       let numBufferedPercent = this.bufferedPercent(); | ||||
| @@ -169,7 +187,6 @@ class Tech extends Component { | ||||
|     this.clearInterval(this.progressInterval); | ||||
|   } | ||||
|  | ||||
|   /*! Time Tracking -------------------------------------------------------------- */ | ||||
|   /** | ||||
|    * Set event listeners for on play and pause and tracking current time | ||||
|    * | ||||
| @@ -200,10 +217,14 @@ class Tech extends Component { | ||||
|    * @method trackCurrentTime | ||||
|    */ | ||||
|   trackCurrentTime() { | ||||
|     if (this.currentTimeInterval) { this.stopTrackingCurrentTime(); } | ||||
|     this.currentTimeInterval = this.setInterval(function(){ | ||||
|     if (this.currentTimeInterval) { | ||||
|       this.stopTrackingCurrentTime(); | ||||
|     } | ||||
|     this.currentTimeInterval = this.setInterval(function() { | ||||
|       this.trigger({ type: 'timeupdate', target: this, manuallyTriggered: true }); | ||||
|     }, 250); // 42 = 24 fps // 250 is what Webkit uses // FF uses 15 | ||||
|  | ||||
|     // 42 = 24 fps // 250 is what Webkit uses // FF uses 15 | ||||
|     }, 250); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
| @@ -230,9 +251,13 @@ class Tech extends Component { | ||||
|     this.clearTracks(['audio', 'video', 'text']); | ||||
|  | ||||
|     // Turn off any manual progress or timeupdate tracking | ||||
|     if (this.manualProgress) { this.manualProgressOff(); } | ||||
|     if (this.manualProgress) { | ||||
|       this.manualProgressOff(); | ||||
|     } | ||||
|  | ||||
|     if (this.manualTimeUpdates) { this.manualTimeUpdatesOff(); } | ||||
|     if (this.manualTimeUpdates) { | ||||
|       this.manualTimeUpdatesOff(); | ||||
|     } | ||||
|  | ||||
|     super.dispose(); | ||||
|   } | ||||
| @@ -254,8 +279,10 @@ class Tech extends Component { | ||||
|     types.forEach((type) => { | ||||
|       let list = this[`${type}Tracks`]() || []; | ||||
|       let i = list.length; | ||||
|  | ||||
|       while (i--) { | ||||
|         let track = list[i]; | ||||
|  | ||||
|         if (type === 'text') { | ||||
|           this.removeRemoteTextTrack(track); | ||||
|         } | ||||
| @@ -315,7 +342,9 @@ class Tech extends Component { | ||||
|    */ | ||||
|   setCurrentTime() { | ||||
|     // improve the accuracy of manual timeupdates | ||||
|     if (this.manualTimeUpdates) { this.trigger({ type: 'timeupdate', target: this, manuallyTriggered: true }); } | ||||
|     if (this.manualTimeUpdates) { | ||||
|       this.trigger({ type: 'timeupdate', target: this, manuallyTriggered: true }); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   /** | ||||
| @@ -330,7 +359,9 @@ class Tech extends Component { | ||||
|  | ||||
|     let tracks = this.textTracks(); | ||||
|  | ||||
|     if (!tracks) return; | ||||
|     if (!tracks) { | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     tracks.addEventListener('removetrack', textTrackListChanges); | ||||
|     tracks.addEventListener('addtrack', textTrackListChanges); | ||||
| @@ -341,7 +372,6 @@ class Tech extends Component { | ||||
|     })); | ||||
|   } | ||||
|  | ||||
|  | ||||
|   /** | ||||
|    * Initialize audio and video track listeners | ||||
|    * | ||||
| @@ -374,12 +404,14 @@ class Tech extends Component { | ||||
|    */ | ||||
|   emulateTextTracks() { | ||||
|     let tracks = this.textTracks(); | ||||
|  | ||||
|     if (!tracks) { | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     if (!window['WebVTT'] && this.el().parentNode != null) { | ||||
|     if (!window.WebVTT && this.el().parentNode != null) { | ||||
|       let script = document.createElement('script'); | ||||
|  | ||||
|       script.src = this.options_['vtt.js'] || '../node_modules/videojs-vtt.js/dist/vtt.js'; | ||||
|       script.onload = () => { | ||||
|         this.trigger('vttjsloaded'); | ||||
| @@ -393,7 +425,7 @@ class Tech extends Component { | ||||
|       }); | ||||
|       // but have not loaded yet and we set it to true before the inject so that | ||||
|       // we don't overwrite the injected window.WebVTT if it loads right away | ||||
|       window['WebVTT'] = true; | ||||
|       window.WebVTT = true; | ||||
|       this.el().parentNode.appendChild(script); | ||||
|     } | ||||
|  | ||||
| @@ -403,6 +435,7 @@ class Tech extends Component { | ||||
|  | ||||
|       for (let i = 0; i < tracks.length; i++) { | ||||
|         let track = tracks[i]; | ||||
|  | ||||
|         track.removeEventListener('cuechange', updateDisplay); | ||||
|         if (track.mode === 'showing') { | ||||
|           track.addEventListener('cuechange', updateDisplay); | ||||
| @@ -620,7 +653,7 @@ class Tech extends Component { | ||||
|  * @type {TextTrackList} | ||||
|  * @private | ||||
|  */ | ||||
| Tech.prototype.textTracks_; | ||||
| Tech.prototype.textTracks_; // eslint-disable-line | ||||
|  | ||||
| /** | ||||
|  * List of associated audio tracks | ||||
| @@ -628,7 +661,7 @@ Tech.prototype.textTracks_; | ||||
|  * @type {AudioTrackList} | ||||
|  * @private | ||||
|  */ | ||||
| Tech.prototype.audioTracks_; | ||||
| Tech.prototype.audioTracks_; // eslint-disable-line | ||||
|  | ||||
| /** | ||||
|  * List of associated video tracks | ||||
| @@ -636,27 +669,7 @@ Tech.prototype.audioTracks_; | ||||
|  * @type {VideoTrackList} | ||||
|  * @private | ||||
|  */ | ||||
| Tech.prototype.videoTracks_; | ||||
|  | ||||
|  | ||||
| var createTrackHelper = function(self, kind, label, language, options={}) { | ||||
|   let tracks = self.textTracks(); | ||||
|  | ||||
|   options.kind = kind; | ||||
|  | ||||
|   if (label) { | ||||
|     options.label = label; | ||||
|   } | ||||
|   if (language) { | ||||
|     options.language = language; | ||||
|   } | ||||
|   options.tech = self; | ||||
|  | ||||
|   let track = new TextTrack(options); | ||||
|   tracks.addTrack_(track); | ||||
|  | ||||
|   return track; | ||||
| }; | ||||
| Tech.prototype.videoTracks_; // eslint-disable-line | ||||
|  | ||||
| Tech.prototype.featuresVolumeControl = true; | ||||
|  | ||||
| @@ -671,7 +684,7 @@ Tech.prototype.featuresTimeupdateEvents = false; | ||||
|  | ||||
| Tech.prototype.featuresNativeTextTracks = false; | ||||
|  | ||||
| /* | ||||
| /** | ||||
|  * A functional mixin for techs that want to use the Source Handler pattern. | ||||
|  * | ||||
|  * ##### EXAMPLE: | ||||
| @@ -679,16 +692,17 @@ Tech.prototype.featuresNativeTextTracks = false; | ||||
|  *   Tech.withSourceHandlers.call(MyTech); | ||||
|  * | ||||
|  */ | ||||
| Tech.withSourceHandlers = function(_Tech){ | ||||
|    /* | ||||
|     * Register a source handler | ||||
|     * Source handlers are scripts for handling specific formats. | ||||
|     * The source handler pattern is used for adaptive formats (HLS, DASH) that | ||||
|     * manually load video data and feed it into a Source Buffer (Media Source Extensions) | ||||
|     * @param  {Function} handler  The source handler | ||||
|     * @param  {Boolean}  first    Register it before any existing handlers | ||||
|     */ | ||||
|    _Tech.registerSourceHandler = function(handler, index){ | ||||
| Tech.withSourceHandlers = function(_Tech) { | ||||
|  | ||||
|   /** | ||||
|    * Register a source handler | ||||
|    * Source handlers are scripts for handling specific formats. | ||||
|    * The source handler pattern is used for adaptive formats (HLS, DASH) that | ||||
|    * manually load video data and feed it into a Source Buffer (Media Source Extensions) | ||||
|    * @param  {Function} handler  The source handler | ||||
|    * @param  {Boolean}  first    Register it before any existing handlers | ||||
|    */ | ||||
|   _Tech.registerSourceHandler = function(handler, index) { | ||||
|     let handlers = _Tech.sourceHandlers; | ||||
|  | ||||
|     if (!handlers) { | ||||
| @@ -703,12 +717,12 @@ Tech.withSourceHandlers = function(_Tech){ | ||||
|     handlers.splice(index, 0, handler); | ||||
|   }; | ||||
|  | ||||
|   /* | ||||
|   /** | ||||
|    * Check if the tech can support the given type | ||||
|    * @param  {String} type    The mimetype to check | ||||
|    * @return {String}         'probably', 'maybe', or '' (empty string) | ||||
|    */ | ||||
|   _Tech.canPlayType = function(type){ | ||||
|   _Tech.canPlayType = function(type) { | ||||
|     let handlers = _Tech.sourceHandlers || []; | ||||
|     let can; | ||||
|  | ||||
| @@ -723,15 +737,15 @@ Tech.withSourceHandlers = function(_Tech){ | ||||
|     return ''; | ||||
|   }; | ||||
|  | ||||
|    /* | ||||
|     * Return the first source handler that supports the source | ||||
|     * TODO: Answer question: should 'probably' be prioritized over 'maybe' | ||||
|     * @param  {Object} source  The source object | ||||
|     * @param  {Object} options The options passed to the tech | ||||
|     * @returns {Object}       The first source handler that supports the source | ||||
|     * @returns {null}         Null if no source handler is found | ||||
|     */ | ||||
|    _Tech.selectSourceHandler = function(source, options){ | ||||
|   /** | ||||
|    * Return the first source handler that supports the source | ||||
|    * TODO: Answer question: should 'probably' be prioritized over 'maybe' | ||||
|    * @param  {Object} source  The source object | ||||
|    * @param  {Object} options The options passed to the tech | ||||
|    * @returns {Object}       The first source handler that supports the source | ||||
|    * @returns {null}         Null if no source handler is found | ||||
|    */ | ||||
|   _Tech.selectSourceHandler = function(source, options) { | ||||
|     let handlers = _Tech.sourceHandlers || []; | ||||
|     let can; | ||||
|  | ||||
| @@ -746,13 +760,13 @@ Tech.withSourceHandlers = function(_Tech){ | ||||
|     return null; | ||||
|   }; | ||||
|  | ||||
|   /* | ||||
|   /** | ||||
|    * Check if the tech can support the given source | ||||
|    * @param  {Object} srcObj  The source object | ||||
|    * @param  {Object} options The options passed to the tech | ||||
|    * @return {String}         'probably', 'maybe', or '' (empty string) | ||||
|    */ | ||||
|   _Tech.canPlaySource = function(srcObj, options){ | ||||
|   _Tech.canPlaySource = function(srcObj, options) { | ||||
|     let sh = _Tech.selectSourceHandler(srcObj, options); | ||||
|  | ||||
|     if (sh) { | ||||
| @@ -762,16 +776,16 @@ Tech.withSourceHandlers = function(_Tech){ | ||||
|     return ''; | ||||
|   }; | ||||
|  | ||||
|   /* | ||||
|   /** | ||||
|    * When using a source handler, prefer its implementation of | ||||
|    * any function normally provided by the tech. | ||||
|    */ | ||||
|   let deferrable = [ | ||||
|       'seekable', | ||||
|       'duration' | ||||
|     ]; | ||||
|     'seekable', | ||||
|     'duration' | ||||
|   ]; | ||||
|  | ||||
|   deferrable.forEach(function (fnName) { | ||||
|   deferrable.forEach(function(fnName) { | ||||
|     let originalFn = this[fnName]; | ||||
|  | ||||
|     if (typeof originalFn !== 'function') { | ||||
| @@ -786,14 +800,14 @@ Tech.withSourceHandlers = function(_Tech){ | ||||
|     }; | ||||
|   }, _Tech.prototype); | ||||
|  | ||||
|    /* | ||||
|     * Create a function for setting the source using a source object | ||||
|     * and source handlers. | ||||
|     * Should never be called unless a source handler was found. | ||||
|     * @param {Object} source  A source object with src and type keys | ||||
|     * @return {Tech} self | ||||
|     */ | ||||
|    _Tech.prototype.setSource = function(source){ | ||||
|   /** | ||||
|    * Create a function for setting the source using a source object | ||||
|    * and source handlers. | ||||
|    * Should never be called unless a source handler was found. | ||||
|    * @param {Object} source  A source object with src and type keys | ||||
|    * @return {Tech} self | ||||
|    */ | ||||
|   _Tech.prototype.setSource = function(source) { | ||||
|     let sh = _Tech.selectSourceHandler(source, this.options_); | ||||
|  | ||||
|     if (!sh) { | ||||
|   | ||||
| @@ -21,6 +21,7 @@ const disableOthers = function(list, track) { | ||||
|     list[i].enabled = false; | ||||
|   } | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * A list of possible audio tracks. All functionality is in the | ||||
|  * base class Tracklist and the spec for AudioTrackList is located at: | ||||
|   | ||||
| @@ -37,7 +37,9 @@ class AudioTrack extends Track { | ||||
|     } | ||||
|  | ||||
|     Object.defineProperty(track, 'enabled', { | ||||
|       get() { return enabled; }, | ||||
|       get() { | ||||
|         return enabled; | ||||
|       }, | ||||
|       set(newEnabled) { | ||||
|         // an invalid or unchanged value | ||||
|         if (typeof newEnabled !== 'boolean' || newEnabled === enabled) { | ||||
|   | ||||
| @@ -7,7 +7,7 @@ import document from 'global/document'; | ||||
|  | ||||
| class HtmlTrackElementList { | ||||
|   constructor(trackElements = []) { | ||||
|     let list = this; | ||||
|     let list = this; // eslint-disable-line | ||||
|  | ||||
|     if (browser.IS_IE8) { | ||||
|       list = document.createElement('custom'); | ||||
|   | ||||
| @@ -39,8 +39,8 @@ class HTMLTrackElement extends EventTarget { | ||||
|   constructor(options = {}) { | ||||
|     super(); | ||||
|  | ||||
|     let readyState, | ||||
|         trackElement = this; | ||||
|     let readyState; | ||||
|     let trackElement = this; // eslint-disable-line | ||||
|  | ||||
|     if (browser.IS_IE8) { | ||||
|       trackElement = document.createElement('custom'); | ||||
|   | ||||
| @@ -20,7 +20,7 @@ import document from 'global/document'; | ||||
|  | ||||
| class TextTrackCueList { | ||||
|   constructor(cues) { | ||||
|     let list = this; | ||||
|     let list = this; // eslint-disable-line | ||||
|  | ||||
|     if (browser.IS_IE8) { | ||||
|       list = document.createElement('custom'); | ||||
|   | ||||
| @@ -2,28 +2,60 @@ | ||||
|  * @file text-track-display.js | ||||
|  */ | ||||
| import Component from '../component'; | ||||
| import Menu from '../menu/menu.js'; | ||||
| import MenuItem from '../menu/menu-item.js'; | ||||
| import MenuButton from '../menu/menu-button.js'; | ||||
| import * as Fn from '../utils/fn.js'; | ||||
| import document from 'global/document'; | ||||
| import window from 'global/window'; | ||||
|  | ||||
| const darkGray = '#222'; | ||||
| const lightGray = '#ccc'; | ||||
| const fontMap = { | ||||
|   monospace:             'monospace', | ||||
|   sansSerif:             'sans-serif', | ||||
|   serif:                 'serif', | ||||
|   monospaceSansSerif:    '"Andale Mono", "Lucida Console", monospace', | ||||
|   monospaceSerif:        '"Courier New", monospace', | ||||
|   monospace: 'monospace', | ||||
|   sansSerif: 'sans-serif', | ||||
|   serif: 'serif', | ||||
|   monospaceSansSerif: '"Andale Mono", "Lucida Console", monospace', | ||||
|   monospaceSerif: '"Courier New", monospace', | ||||
|   proportionalSansSerif: 'sans-serif', | ||||
|   proportionalSerif:     'serif', | ||||
|   casual:                '"Comic Sans MS", Impact, fantasy', | ||||
|   script:                '"Monotype Corsiva", cursive', | ||||
|   smallcaps:             '"Andale Mono", "Lucida Console", monospace, sans-serif' | ||||
|   proportionalSerif: 'serif', | ||||
|   casual: '"Comic Sans MS", Impact, fantasy', | ||||
|   script: '"Monotype Corsiva", cursive', | ||||
|   smallcaps: '"Andale Mono", "Lucida Console", monospace, sans-serif' | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * Add cue HTML to display | ||||
|  * | ||||
|  * @param {Number} color Hex number for color, like #f0e | ||||
|  * @param {Number} opacity Value for opacity,0.0 - 1.0 | ||||
|  * @return {RGBAColor} In the form 'rgba(255, 0, 0, 0.3)' | ||||
|  * @method constructColor | ||||
|  */ | ||||
| function constructColor(color, opacity) { | ||||
|   return 'rgba(' + | ||||
|     // color looks like "#f0e" | ||||
|     parseInt(color[1] + color[1], 16) + ',' + | ||||
|     parseInt(color[2] + color[2], 16) + ',' + | ||||
|     parseInt(color[3] + color[3], 16) + ',' + | ||||
|     opacity + ')'; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Try to update style | ||||
|  * Some style changes will throw an error, particularly in IE8. Those should be noops. | ||||
|  * | ||||
|  * @param {Element} el The element to be styles | ||||
|  * @param {CSSProperty} style The CSS property to be styled | ||||
|  * @param {CSSStyle} rule The actual style to be applied to the property | ||||
|  * @method tryUpdateStyle | ||||
|  */ | ||||
| function tryUpdateStyle(el, style, rule) { | ||||
|   try { | ||||
|     el.style[style] = rule; | ||||
|   } catch (e) { | ||||
|  | ||||
|     // Satisfies linter. | ||||
|     return; | ||||
|   } | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * The component for displaying text track cues | ||||
|  * | ||||
| @@ -35,7 +67,7 @@ const fontMap = { | ||||
|  */ | ||||
| class TextTrackDisplay extends Component { | ||||
|  | ||||
|   constructor(player, options, ready){ | ||||
|   constructor(player, options, ready) { | ||||
|     super(player, options, ready); | ||||
|  | ||||
|     player.on('loadstart', Fn.bind(this, this.toggleDisplay)); | ||||
| @@ -46,20 +78,20 @@ class TextTrackDisplay extends Component { | ||||
|     // Should probably be moved to an external track loader when we support | ||||
|     // tracks that don't need a display. | ||||
|     player.ready(Fn.bind(this, function() { | ||||
|       if (player.tech_ && player.tech_['featuresNativeTextTracks']) { | ||||
|       if (player.tech_ && player.tech_.featuresNativeTextTracks) { | ||||
|         this.hide(); | ||||
|         return; | ||||
|       } | ||||
|  | ||||
|       player.on('fullscreenchange', Fn.bind(this, this.updateDisplay)); | ||||
|  | ||||
|       let tracks = this.options_.playerOptions['tracks'] || []; | ||||
|       let tracks = this.options_.playerOptions.tracks || []; | ||||
|  | ||||
|       for (let i = 0; i < tracks.length; i++) { | ||||
|         let track = tracks[i]; | ||||
|         this.player_.addRemoteTextTrack(track); | ||||
|         this.player_.addRemoteTextTrack(tracks[i]); | ||||
|       } | ||||
|  | ||||
|       let modes = {'captions': 1, 'subtitles': 1}; | ||||
|       let modes = {captions: 1, subtitles: 1}; | ||||
|       let trackList = this.player_.textTracks(); | ||||
|       let firstDesc; | ||||
|       let firstCaptions; | ||||
| @@ -67,6 +99,7 @@ class TextTrackDisplay extends Component { | ||||
|       if (trackList) { | ||||
|         for (let i = 0; i < trackList.length; i++) { | ||||
|           let track = trackList[i]; | ||||
|  | ||||
|           if (track.default) { | ||||
|             if (track.kind === 'descriptions' && !firstDesc) { | ||||
|               firstDesc = track; | ||||
| @@ -95,7 +128,7 @@ class TextTrackDisplay extends Component { | ||||
|    * @method toggleDisplay | ||||
|    */ | ||||
|   toggleDisplay() { | ||||
|     if (this.player_.tech_ && this.player_.tech_['featuresNativeTextTracks']) { | ||||
|     if (this.player_.tech_ && this.player_.tech_.featuresNativeTextTracks) { | ||||
|       this.hide(); | ||||
|     } else { | ||||
|       this.show(); | ||||
| @@ -123,8 +156,8 @@ class TextTrackDisplay extends Component { | ||||
|    * @method clearDisplay | ||||
|    */ | ||||
|   clearDisplay() { | ||||
|     if (typeof window['WebVTT'] === 'function') { | ||||
|       window['WebVTT']['processCues'](window, [], this.el_); | ||||
|     if (typeof window.WebVTT === 'function') { | ||||
|       window.WebVTT.processCues(window, [], this.el_); | ||||
|     } | ||||
|   } | ||||
|  | ||||
| @@ -134,7 +167,7 @@ class TextTrackDisplay extends Component { | ||||
|    * @method updateDisplay | ||||
|    */ | ||||
|   updateDisplay() { | ||||
|     var tracks = this.player_.textTracks(); | ||||
|     let tracks = this.player_.textTracks(); | ||||
|  | ||||
|     this.clearDisplay(); | ||||
|  | ||||
| @@ -150,10 +183,12 @@ class TextTrackDisplay extends Component { | ||||
|     let captionsSubtitlesTrack = null; | ||||
|  | ||||
|     let i = tracks.length; | ||||
|  | ||||
|     while (i--) { | ||||
|       let track = tracks[i]; | ||||
|       if (track['mode'] === 'showing') { | ||||
|         if (track['kind'] === 'descriptions') { | ||||
|  | ||||
|       if (track.mode === 'showing') { | ||||
|         if (track.kind === 'descriptions') { | ||||
|           descriptionsTrack = track; | ||||
|         } else { | ||||
|           captionsSubtitlesTrack = track; | ||||
| @@ -175,27 +210,30 @@ class TextTrackDisplay extends Component { | ||||
|    * @method updateForTrack | ||||
|    */ | ||||
|   updateForTrack(track) { | ||||
|     if (typeof window['WebVTT'] !== 'function' || !track['activeCues']) { | ||||
|     if (typeof window.WebVTT !== 'function' || !track.activeCues) { | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     let overrides = this.player_['textTrackSettings'].getValues(); | ||||
|  | ||||
|     let overrides = this.player_.textTrackSettings.getValues(); | ||||
|     let cues = []; | ||||
|     for (let i = 0; i < track['activeCues'].length; i++) { | ||||
|       cues.push(track['activeCues'][i]); | ||||
|  | ||||
|     for (let i = 0; i < track.activeCues.length; i++) { | ||||
|       cues.push(track.activeCues[i]); | ||||
|     } | ||||
|  | ||||
|     window['WebVTT']['processCues'](window, cues, this.el_); | ||||
|     window.WebVTT.processCues(window, cues, this.el_); | ||||
|  | ||||
|     let i = cues.length; | ||||
|  | ||||
|     while (i--) { | ||||
|       let cue = cues[i]; | ||||
|  | ||||
|       if (!cue) { | ||||
|         continue; | ||||
|       } | ||||
|  | ||||
|       let cueDiv = cue.displayState; | ||||
|  | ||||
|       if (overrides.color) { | ||||
|         cueDiv.firstChild.style.color = overrides.color; | ||||
|       } | ||||
| @@ -236,6 +274,7 @@ class TextTrackDisplay extends Component { | ||||
|       } | ||||
|       if (overrides.fontPercent && overrides.fontPercent !== 1) { | ||||
|         const fontSize = window.parseFloat(cueDiv.style.fontSize); | ||||
|  | ||||
|         cueDiv.style.fontSize = (fontSize * overrides.fontPercent) + 'px'; | ||||
|         cueDiv.style.height = 'auto'; | ||||
|         cueDiv.style.top = 'auto'; | ||||
| @@ -253,38 +292,5 @@ class TextTrackDisplay extends Component { | ||||
|  | ||||
| } | ||||
|  | ||||
| /** | ||||
| * Add cue HTML to display | ||||
| * | ||||
| * @param {Number} color Hex number for color, like #f0e | ||||
| * @param {Number} opacity Value for opacity,0.0 - 1.0 | ||||
| * @return {RGBAColor} In the form 'rgba(255, 0, 0, 0.3)' | ||||
| * @method constructColor | ||||
| */ | ||||
| function constructColor(color, opacity) { | ||||
|   return 'rgba(' + | ||||
|     // color looks like "#f0e" | ||||
|     parseInt(color[1] + color[1], 16) + ',' + | ||||
|     parseInt(color[2] + color[2], 16) + ',' + | ||||
|     parseInt(color[3] + color[3], 16) + ',' + | ||||
|     opacity + ')'; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Try to update style | ||||
|  * Some style changes will throw an error, particularly in IE8. Those should be noops. | ||||
|  * | ||||
|  * @param {Element} el The element to be styles | ||||
|  * @param {CSSProperty} style The CSS property to be styled | ||||
|  * @param {CSSStyle} rule The actual style to be applied to the property | ||||
|  * @method tryUpdateStyle | ||||
|  */ | ||||
| function tryUpdateStyle(el, style, rule) { | ||||
|   // | ||||
|   try { | ||||
|     el.style[style] = rule; | ||||
|   } catch (e) {} | ||||
| } | ||||
|  | ||||
| Component.registerComponent('TextTrackDisplay', TextTrackDisplay); | ||||
| export default TextTrackDisplay; | ||||
|   | ||||
| @@ -13,13 +13,15 @@ | ||||
|  * @private | ||||
|  */ | ||||
| let trackToJson_ = function(track) { | ||||
|   let ret = ['kind', 'label', 'language', 'id', | ||||
|              'inBandMetadataTrackDispatchType', | ||||
|              'mode', 'src'].reduce((acc, prop, i) => { | ||||
|   let ret = [ | ||||
|     'kind', 'label', 'language', 'id', | ||||
|     'inBandMetadataTrackDispatchType', 'mode', 'src' | ||||
|   ].reduce((acc, prop, i) => { | ||||
|  | ||||
|     if (track[prop]) { | ||||
|       acc[prop] = track[prop]; | ||||
|     } | ||||
|      | ||||
|  | ||||
|     return acc; | ||||
|   }, { | ||||
|     cues: track.cues && Array.prototype.map.call(track.cues, function(cue) { | ||||
| @@ -50,6 +52,7 @@ let textTracksToJson = function(tech) { | ||||
|   let trackObjs = Array.prototype.map.call(trackEls, (t) => t.track); | ||||
|   let tracks = Array.prototype.map.call(trackEls, function(trackEl) { | ||||
|     let json = trackToJson_(trackEl.track); | ||||
|  | ||||
|     if (trackEl.src) { | ||||
|       json.src = trackEl.src; | ||||
|     } | ||||
| @@ -72,6 +75,7 @@ let textTracksToJson = function(tech) { | ||||
| let jsonToTextTracks = function(json, tech) { | ||||
|   json.forEach(function(track) { | ||||
|     let addedTrack = tech.addRemoteTextTrack(track).track; | ||||
|  | ||||
|     if (!track.src && track.cues) { | ||||
|       track.cues.forEach((cue) => addedTrack.addCue(cue)); | ||||
|     } | ||||
|   | ||||
| @@ -8,250 +8,7 @@ import log from '../utils/log.js'; | ||||
| import safeParseTuple from 'safe-json-parse/tuple'; | ||||
| import window from 'global/window'; | ||||
|  | ||||
| /** | ||||
|  * Manipulate settings of texttracks | ||||
|  * | ||||
|  * @param {Object} player  Main Player | ||||
|  * @param {Object=} options Object of option names and values | ||||
|  * @extends Component | ||||
|  * @class TextTrackSettings | ||||
|  */ | ||||
| class TextTrackSettings extends Component { | ||||
|  | ||||
|   constructor(player, options) { | ||||
|     super(player, options); | ||||
|     this.hide(); | ||||
|  | ||||
|     // Grab `persistTextTrackSettings` from the player options if not passed in child options | ||||
|     if (options.persistTextTrackSettings === undefined) { | ||||
|       this.options_.persistTextTrackSettings = this.options_.playerOptions.persistTextTrackSettings; | ||||
|     } | ||||
|  | ||||
|     Events.on(this.$('.vjs-done-button'), 'click', Fn.bind(this, function() { | ||||
|       this.saveSettings(); | ||||
|       this.hide(); | ||||
|     })); | ||||
|  | ||||
|     Events.on(this.$('.vjs-default-button'), 'click', Fn.bind(this, function() { | ||||
|       this.$('.vjs-fg-color > select').selectedIndex = 0; | ||||
|       this.$('.vjs-bg-color > select').selectedIndex = 0; | ||||
|       this.$('.window-color > select').selectedIndex = 0; | ||||
|       this.$('.vjs-text-opacity > select').selectedIndex = 0; | ||||
|       this.$('.vjs-bg-opacity > select').selectedIndex = 0; | ||||
|       this.$('.vjs-window-opacity > select').selectedIndex = 0; | ||||
|       this.$('.vjs-edge-style select').selectedIndex = 0; | ||||
|       this.$('.vjs-font-family select').selectedIndex = 0; | ||||
|       this.$('.vjs-font-percent select').selectedIndex = 2; | ||||
|       this.updateDisplay(); | ||||
|     })); | ||||
|  | ||||
|     Events.on(this.$('.vjs-fg-color > select'), 'change', Fn.bind(this, this.updateDisplay)); | ||||
|     Events.on(this.$('.vjs-bg-color > select'), 'change', Fn.bind(this, this.updateDisplay)); | ||||
|     Events.on(this.$('.window-color > select'), 'change', Fn.bind(this, this.updateDisplay)); | ||||
|     Events.on(this.$('.vjs-text-opacity > select'), 'change', Fn.bind(this, this.updateDisplay)); | ||||
|     Events.on(this.$('.vjs-bg-opacity > select'), 'change', Fn.bind(this, this.updateDisplay)); | ||||
|     Events.on(this.$('.vjs-window-opacity > select'), 'change', Fn.bind(this, this.updateDisplay)); | ||||
|     Events.on(this.$('.vjs-font-percent select'), 'change', Fn.bind(this, this.updateDisplay)); | ||||
|     Events.on(this.$('.vjs-edge-style select'), 'change', Fn.bind(this, this.updateDisplay)); | ||||
|     Events.on(this.$('.vjs-font-family select'), 'change', Fn.bind(this, this.updateDisplay)); | ||||
|  | ||||
|     if (this.options_.persistTextTrackSettings) { | ||||
|       this.restoreSettings(); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Create the component's DOM element | ||||
|    * | ||||
|    * @return {Element} | ||||
|    * @method createEl | ||||
|    */ | ||||
|   createEl() { | ||||
|     let uniqueId = this.id_; | ||||
|     let dialogLabelId = 'TTsettingsDialogLabel-' + uniqueId; | ||||
|     let dialogDescriptionId = 'TTsettingsDialogDescription-' + uniqueId; | ||||
|  | ||||
|     return super.createEl('div', { | ||||
|       className: 'vjs-caption-settings vjs-modal-overlay', | ||||
|       innerHTML: captionOptionsMenuTemplate(uniqueId, dialogLabelId, dialogDescriptionId), | ||||
|       tabIndex: -1 | ||||
|     }, { | ||||
|       role: 'dialog', | ||||
|       'aria-labelledby': dialogLabelId, | ||||
|       'aria-describedby': dialogDescriptionId | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Get texttrack settings | ||||
|    * Settings are | ||||
|    * .vjs-edge-style | ||||
|    * .vjs-font-family | ||||
|    * .vjs-fg-color | ||||
|    * .vjs-text-opacity | ||||
|    * .vjs-bg-color | ||||
|    * .vjs-bg-opacity | ||||
|    * .window-color | ||||
|    * .vjs-window-opacity | ||||
|    * | ||||
|    * @return {Object} | ||||
|    * @method getValues | ||||
|    */ | ||||
|   getValues() { | ||||
|     const textEdge = getSelectedOptionValue(this.$('.vjs-edge-style select')); | ||||
|     const fontFamily = getSelectedOptionValue(this.$('.vjs-font-family select')); | ||||
|     const fgColor = getSelectedOptionValue(this.$('.vjs-fg-color > select')); | ||||
|     const textOpacity = getSelectedOptionValue(this.$('.vjs-text-opacity > select')); | ||||
|     const bgColor = getSelectedOptionValue(this.$('.vjs-bg-color > select')); | ||||
|     const bgOpacity = getSelectedOptionValue(this.$('.vjs-bg-opacity > select')); | ||||
|     const windowColor = getSelectedOptionValue(this.$('.window-color > select')); | ||||
|     const windowOpacity = getSelectedOptionValue(this.$('.vjs-window-opacity > select')); | ||||
|     const fontPercent = window['parseFloat'](getSelectedOptionValue(this.$('.vjs-font-percent > select'))); | ||||
|  | ||||
|     let result = { | ||||
|       'backgroundOpacity': bgOpacity, | ||||
|       'textOpacity': textOpacity, | ||||
|       'windowOpacity': windowOpacity, | ||||
|       'edgeStyle': textEdge, | ||||
|       'fontFamily': fontFamily, | ||||
|       'color': fgColor, | ||||
|       'backgroundColor': bgColor, | ||||
|       'windowColor': windowColor, | ||||
|       'fontPercent': fontPercent | ||||
|     }; | ||||
|     for (let name in result) { | ||||
|       if (result[name] === '' || result[name] === 'none' || (name === 'fontPercent' && result[name] === 1.00)) { | ||||
|         delete result[name]; | ||||
|       } | ||||
|     } | ||||
|     return result; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Set texttrack settings | ||||
|    * Settings are | ||||
|    * .vjs-edge-style | ||||
|    * .vjs-font-family | ||||
|    * .vjs-fg-color | ||||
|    * .vjs-text-opacity | ||||
|    * .vjs-bg-color | ||||
|    * .vjs-bg-opacity | ||||
|    * .window-color | ||||
|    * .vjs-window-opacity | ||||
|    * | ||||
|    * @param {Object} values Object with texttrack setting values | ||||
|    * @method setValues | ||||
|    */ | ||||
|   setValues(values) { | ||||
|     setSelectedOption(this.$('.vjs-edge-style select'), values.edgeStyle); | ||||
|     setSelectedOption(this.$('.vjs-font-family select'), values.fontFamily); | ||||
|     setSelectedOption(this.$('.vjs-fg-color > select'), values.color); | ||||
|     setSelectedOption(this.$('.vjs-text-opacity > select'), values.textOpacity); | ||||
|     setSelectedOption(this.$('.vjs-bg-color > select'), values.backgroundColor); | ||||
|     setSelectedOption(this.$('.vjs-bg-opacity > select'), values.backgroundOpacity); | ||||
|     setSelectedOption(this.$('.window-color > select'), values.windowColor); | ||||
|     setSelectedOption(this.$('.vjs-window-opacity > select'), values.windowOpacity); | ||||
|  | ||||
|     let fontPercent = values.fontPercent; | ||||
|  | ||||
|     if (fontPercent) { | ||||
|       fontPercent = fontPercent.toFixed(2); | ||||
|     } | ||||
|  | ||||
|     setSelectedOption(this.$('.vjs-font-percent > select'), fontPercent); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Restore texttrack settings | ||||
|    * | ||||
|    * @method restoreSettings | ||||
|    */ | ||||
|   restoreSettings() { | ||||
|     let err, values; | ||||
|  | ||||
|     try { | ||||
|       [err, values] = safeParseTuple(window.localStorage.getItem('vjs-text-track-settings')); | ||||
|  | ||||
|       if (err) { | ||||
|         log.error(err); | ||||
|       } | ||||
|     } catch (e) { | ||||
|       log.warn(e); | ||||
|     } | ||||
|  | ||||
|     if (values) { | ||||
|       this.setValues(values); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Save texttrack settings to local storage | ||||
|    * | ||||
|    * @method saveSettings | ||||
|    */ | ||||
|   saveSettings() { | ||||
|     if (!this.options_.persistTextTrackSettings) { | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     let values = this.getValues(); | ||||
|     try { | ||||
|       if (Object.getOwnPropertyNames(values).length > 0) { | ||||
|         window.localStorage.setItem('vjs-text-track-settings', JSON.stringify(values)); | ||||
|       } else { | ||||
|         window.localStorage.removeItem('vjs-text-track-settings'); | ||||
|       } | ||||
|     } catch (e) { | ||||
|       log.warn(e); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Update display of texttrack settings | ||||
|    * | ||||
|    * @method updateDisplay | ||||
|    */ | ||||
|   updateDisplay() { | ||||
|     let ttDisplay = this.player_.getChild('textTrackDisplay'); | ||||
|     if (ttDisplay) { | ||||
|       ttDisplay.updateDisplay(); | ||||
|     } | ||||
|   } | ||||
|  | ||||
| } | ||||
|  | ||||
| Component.registerComponent('TextTrackSettings', TextTrackSettings); | ||||
|  | ||||
| function getSelectedOptionValue(target) { | ||||
|   let selectedOption; | ||||
|   // not all browsers support selectedOptions, so, fallback to options | ||||
|   if (target.selectedOptions) { | ||||
|     selectedOption = target.selectedOptions[0]; | ||||
|   } else if (target.options) { | ||||
|     selectedOption = target.options[target.options.selectedIndex]; | ||||
|   } | ||||
|  | ||||
|   return selectedOption.value; | ||||
| } | ||||
|  | ||||
| function setSelectedOption(target, value) { | ||||
|   if (!value) { | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   let i; | ||||
|   for (i = 0; i < target.options.length; i++) { | ||||
|     const option = target.options[i]; | ||||
|     if (option.value === value) { | ||||
|       break; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   target.selectedIndex = i; | ||||
| } | ||||
|  | ||||
| function captionOptionsMenuTemplate(uniqueId, dialogLabelId, dialogDescriptionId) { | ||||
|  | ||||
|   let template = ` | ||||
|     <div role="document"> | ||||
|       <div role="heading" aria-level="1" id="${dialogLabelId}" class="vjs-control-text">Captions Settings Dialog</div> | ||||
| @@ -367,9 +124,259 @@ function captionOptionsMenuTemplate(uniqueId, dialogLabelId, dialogDescriptionId | ||||
|           <button class="vjs-done-button">Done</button> | ||||
|         </div> | ||||
|       </div> <!-- vjs-tracksettings --> | ||||
|     </div> <!--  role="document" -->`; | ||||
|     </div> <!--  role="document" --> | ||||
|   `; | ||||
|  | ||||
|     return template; | ||||
|   return template; | ||||
| } | ||||
|  | ||||
| function getSelectedOptionValue(target) { | ||||
|   let selectedOption; | ||||
|  | ||||
|   // not all browsers support selectedOptions, so, fallback to options | ||||
|   if (target.selectedOptions) { | ||||
|     selectedOption = target.selectedOptions[0]; | ||||
|   } else if (target.options) { | ||||
|     selectedOption = target.options[target.options.selectedIndex]; | ||||
|   } | ||||
|  | ||||
|   return selectedOption.value; | ||||
| } | ||||
|  | ||||
| function setSelectedOption(target, value) { | ||||
|   if (!value) { | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   let i; | ||||
|  | ||||
|   for (i = 0; i < target.options.length; i++) { | ||||
|     const option = target.options[i]; | ||||
|  | ||||
|     if (option.value === value) { | ||||
|       break; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   target.selectedIndex = i; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Manipulate settings of texttracks | ||||
|  * | ||||
|  * @param {Object} player  Main Player | ||||
|  * @param {Object=} options Object of option names and values | ||||
|  * @extends Component | ||||
|  * @class TextTrackSettings | ||||
|  */ | ||||
| class TextTrackSettings extends Component { | ||||
|  | ||||
|   constructor(player, options) { | ||||
|     super(player, options); | ||||
|     this.hide(); | ||||
|  | ||||
|     // Grab `persistTextTrackSettings` from the player options if not passed in child options | ||||
|     if (options.persistTextTrackSettings === undefined) { | ||||
|       this.options_.persistTextTrackSettings = this.options_.playerOptions.persistTextTrackSettings; | ||||
|     } | ||||
|  | ||||
|     Events.on(this.$('.vjs-done-button'), 'click', Fn.bind(this, function() { | ||||
|       this.saveSettings(); | ||||
|       this.hide(); | ||||
|     })); | ||||
|  | ||||
|     Events.on(this.$('.vjs-default-button'), 'click', Fn.bind(this, function() { | ||||
|       this.$('.vjs-fg-color > select').selectedIndex = 0; | ||||
|       this.$('.vjs-bg-color > select').selectedIndex = 0; | ||||
|       this.$('.window-color > select').selectedIndex = 0; | ||||
|       this.$('.vjs-text-opacity > select').selectedIndex = 0; | ||||
|       this.$('.vjs-bg-opacity > select').selectedIndex = 0; | ||||
|       this.$('.vjs-window-opacity > select').selectedIndex = 0; | ||||
|       this.$('.vjs-edge-style select').selectedIndex = 0; | ||||
|       this.$('.vjs-font-family select').selectedIndex = 0; | ||||
|       this.$('.vjs-font-percent select').selectedIndex = 2; | ||||
|       this.updateDisplay(); | ||||
|     })); | ||||
|  | ||||
|     Events.on(this.$('.vjs-fg-color > select'), 'change', Fn.bind(this, this.updateDisplay)); | ||||
|     Events.on(this.$('.vjs-bg-color > select'), 'change', Fn.bind(this, this.updateDisplay)); | ||||
|     Events.on(this.$('.window-color > select'), 'change', Fn.bind(this, this.updateDisplay)); | ||||
|     Events.on(this.$('.vjs-text-opacity > select'), 'change', Fn.bind(this, this.updateDisplay)); | ||||
|     Events.on(this.$('.vjs-bg-opacity > select'), 'change', Fn.bind(this, this.updateDisplay)); | ||||
|     Events.on(this.$('.vjs-window-opacity > select'), 'change', Fn.bind(this, this.updateDisplay)); | ||||
|     Events.on(this.$('.vjs-font-percent select'), 'change', Fn.bind(this, this.updateDisplay)); | ||||
|     Events.on(this.$('.vjs-edge-style select'), 'change', Fn.bind(this, this.updateDisplay)); | ||||
|     Events.on(this.$('.vjs-font-family select'), 'change', Fn.bind(this, this.updateDisplay)); | ||||
|  | ||||
|     if (this.options_.persistTextTrackSettings) { | ||||
|       this.restoreSettings(); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Create the component's DOM element | ||||
|    * | ||||
|    * @return {Element} | ||||
|    * @method createEl | ||||
|    */ | ||||
|   createEl() { | ||||
|     let uniqueId = this.id_; | ||||
|     let dialogLabelId = 'TTsettingsDialogLabel-' + uniqueId; | ||||
|     let dialogDescriptionId = 'TTsettingsDialogDescription-' + uniqueId; | ||||
|  | ||||
|     return super.createEl('div', { | ||||
|       className: 'vjs-caption-settings vjs-modal-overlay', | ||||
|       innerHTML: captionOptionsMenuTemplate(uniqueId, dialogLabelId, dialogDescriptionId), | ||||
|       tabIndex: -1 | ||||
|     }, { | ||||
|       'role': 'dialog', | ||||
|       'aria-labelledby': dialogLabelId, | ||||
|       'aria-describedby': dialogDescriptionId | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Get texttrack settings | ||||
|    * Settings are | ||||
|    * .vjs-edge-style | ||||
|    * .vjs-font-family | ||||
|    * .vjs-fg-color | ||||
|    * .vjs-text-opacity | ||||
|    * .vjs-bg-color | ||||
|    * .vjs-bg-opacity | ||||
|    * .window-color | ||||
|    * .vjs-window-opacity | ||||
|    * | ||||
|    * @return {Object} | ||||
|    * @method getValues | ||||
|    */ | ||||
|   getValues() { | ||||
|     const textEdge = getSelectedOptionValue(this.$('.vjs-edge-style select')); | ||||
|     const fontFamily = getSelectedOptionValue(this.$('.vjs-font-family select')); | ||||
|     const fgColor = getSelectedOptionValue(this.$('.vjs-fg-color > select')); | ||||
|     const textOpacity = getSelectedOptionValue(this.$('.vjs-text-opacity > select')); | ||||
|     const bgColor = getSelectedOptionValue(this.$('.vjs-bg-color > select')); | ||||
|     const bgOpacity = getSelectedOptionValue(this.$('.vjs-bg-opacity > select')); | ||||
|     const windowColor = getSelectedOptionValue(this.$('.window-color > select')); | ||||
|     const windowOpacity = getSelectedOptionValue(this.$('.vjs-window-opacity > select')); | ||||
|     const fontPercent = window.parseFloat(getSelectedOptionValue(this.$('.vjs-font-percent > select'))); | ||||
|  | ||||
|     let result = { | ||||
|       fontPercent, | ||||
|       fontFamily, | ||||
|       textOpacity, | ||||
|       windowColor, | ||||
|       windowOpacity, | ||||
|       backgroundOpacity: bgOpacity, | ||||
|       edgeStyle: textEdge, | ||||
|       color: fgColor, | ||||
|       backgroundColor: bgColor | ||||
|     }; | ||||
|  | ||||
|     for (let name in result) { | ||||
|       if (result[name] === '' || result[name] === 'none' || (name === 'fontPercent' && result[name] === 1.00)) { | ||||
|         delete result[name]; | ||||
|       } | ||||
|     } | ||||
|     return result; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Set texttrack settings | ||||
|    * Settings are | ||||
|    * .vjs-edge-style | ||||
|    * .vjs-font-family | ||||
|    * .vjs-fg-color | ||||
|    * .vjs-text-opacity | ||||
|    * .vjs-bg-color | ||||
|    * .vjs-bg-opacity | ||||
|    * .window-color | ||||
|    * .vjs-window-opacity | ||||
|    * | ||||
|    * @param {Object} values Object with texttrack setting values | ||||
|    * @method setValues | ||||
|    */ | ||||
|   setValues(values) { | ||||
|     setSelectedOption(this.$('.vjs-edge-style select'), values.edgeStyle); | ||||
|     setSelectedOption(this.$('.vjs-font-family select'), values.fontFamily); | ||||
|     setSelectedOption(this.$('.vjs-fg-color > select'), values.color); | ||||
|     setSelectedOption(this.$('.vjs-text-opacity > select'), values.textOpacity); | ||||
|     setSelectedOption(this.$('.vjs-bg-color > select'), values.backgroundColor); | ||||
|     setSelectedOption(this.$('.vjs-bg-opacity > select'), values.backgroundOpacity); | ||||
|     setSelectedOption(this.$('.window-color > select'), values.windowColor); | ||||
|     setSelectedOption(this.$('.vjs-window-opacity > select'), values.windowOpacity); | ||||
|  | ||||
|     let fontPercent = values.fontPercent; | ||||
|  | ||||
|     if (fontPercent) { | ||||
|       fontPercent = fontPercent.toFixed(2); | ||||
|     } | ||||
|  | ||||
|     setSelectedOption(this.$('.vjs-font-percent > select'), fontPercent); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Restore texttrack settings | ||||
|    * | ||||
|    * @method restoreSettings | ||||
|    */ | ||||
|   restoreSettings() { | ||||
|     let err; | ||||
|     let values; | ||||
|  | ||||
|     try { | ||||
|       [err, values] = safeParseTuple(window.localStorage.getItem('vjs-text-track-settings')); | ||||
|  | ||||
|       if (err) { | ||||
|         log.error(err); | ||||
|       } | ||||
|     } catch (e) { | ||||
|       log.warn(e); | ||||
|     } | ||||
|  | ||||
|     if (values) { | ||||
|       this.setValues(values); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Save texttrack settings to local storage | ||||
|    * | ||||
|    * @method saveSettings | ||||
|    */ | ||||
|   saveSettings() { | ||||
|     if (!this.options_.persistTextTrackSettings) { | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     let values = this.getValues(); | ||||
|  | ||||
|     try { | ||||
|       if (Object.getOwnPropertyNames(values).length > 0) { | ||||
|         window.localStorage.setItem('vjs-text-track-settings', JSON.stringify(values)); | ||||
|       } else { | ||||
|         window.localStorage.removeItem('vjs-text-track-settings'); | ||||
|       } | ||||
|     } catch (e) { | ||||
|       log.warn(e); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Update display of texttrack settings | ||||
|    * | ||||
|    * @method updateDisplay | ||||
|    */ | ||||
|   updateDisplay() { | ||||
|     let ttDisplay = this.player_.getChild('textTrackDisplay'); | ||||
|  | ||||
|     if (ttDisplay) { | ||||
|       ttDisplay.updateDisplay(); | ||||
|     } | ||||
|   } | ||||
|  | ||||
| } | ||||
|  | ||||
| Component.registerComponent('TextTrackSettings', TextTrackSettings); | ||||
|  | ||||
| export default TextTrackSettings; | ||||
|   | ||||
| @@ -5,7 +5,6 @@ import TextTrackCueList from './text-track-cue-list'; | ||||
| import * as Fn from '../utils/fn.js'; | ||||
| import {TextTrackKind, TextTrackMode} from './track-enums'; | ||||
| import log from '../utils/log.js'; | ||||
| import document from 'global/document'; | ||||
| import window from 'global/window'; | ||||
| import Track from './track.js'; | ||||
| import { isCrossOrigin } from '../utils/url.js'; | ||||
| @@ -42,12 +41,12 @@ const parseCues = function(srcContent, track) { | ||||
|  | ||||
|   parser.parse(srcContent); | ||||
|   if (errors.length > 0) { | ||||
|     if (console.groupCollapsed) { | ||||
|       console.groupCollapsed(`Text Track parsing errors for ${track.src}`); | ||||
|     if (window.console && window.console.groupCollapsed) { | ||||
|       window.console.groupCollapsed(`Text Track parsing errors for ${track.src}`); | ||||
|     } | ||||
|     errors.forEach((error) => log.error(error)); | ||||
|     if (console.groupEnd) { | ||||
|       console.groupEnd(); | ||||
|     if (window.console && window.console.groupEnd) { | ||||
|       window.console.groupEnd(); | ||||
|     } | ||||
|   } | ||||
|  | ||||
| @@ -82,6 +81,7 @@ const loadTrack = function(src, track) { | ||||
|     if (typeof window.WebVTT !== 'function') { | ||||
|       if (track.tech_) { | ||||
|         let loadHandler = () => parseCues(responseBody, track); | ||||
|  | ||||
|         track.tech_.on('vttjsloaded', loadHandler); | ||||
|         track.tech_.on('vttjserror', () => { | ||||
|           log.error(`vttjs failed to load, stopping trying to process ${track.src}`); | ||||
| @@ -142,6 +142,7 @@ class TextTrack extends Track { | ||||
|     // on IE8 this will be a document element | ||||
|     // for every other browser this will be a normal object | ||||
|     let tt = super(settings); | ||||
|  | ||||
|     tt.tech_ = settings.tech; | ||||
|  | ||||
|     if (browser.IS_IE8) { | ||||
| @@ -159,6 +160,10 @@ class TextTrack extends Track { | ||||
|     let activeCues = new TextTrackCueList(tt.activeCues_); | ||||
|     let changed = false; | ||||
|     let timeupdateHandler = Fn.bind(tt, function() { | ||||
|  | ||||
|       // Accessing this.activeCues for the side-effects of updating itself | ||||
|       // due to it's nature as a getter function. Do not remove or cues will | ||||
|       // stop updating! | ||||
|       this.activeCues; | ||||
|       if (changed) { | ||||
|         this.trigger('cuechange'); | ||||
|   | ||||
| @@ -21,7 +21,7 @@ const VideoTrackKind = { | ||||
|   main: 'main', | ||||
|   sign: 'sign', | ||||
|   subtitles: 'subtitles', | ||||
|   commentary: 'commentary', | ||||
|   commentary: 'commentary' | ||||
| }; | ||||
|  | ||||
| /** | ||||
| @@ -38,12 +38,12 @@ const VideoTrackKind = { | ||||
|  * }; | ||||
|  */ | ||||
| const AudioTrackKind = { | ||||
|   alternative: 'alternative', | ||||
|   descriptions: 'descriptions', | ||||
|   main: 'main', | ||||
|   'alternative': 'alternative', | ||||
|   'descriptions': 'descriptions', | ||||
|   'main': 'main', | ||||
|   'main-desc': 'main-desc', | ||||
|   translation: 'translation', | ||||
|   commentary: 'commentary', | ||||
|   'translation': 'translation', | ||||
|   'commentary': 'commentary' | ||||
| }; | ||||
|  | ||||
| /** | ||||
| @@ -65,8 +65,6 @@ const TextTrackKind = { | ||||
|   metadata: 'metadata' | ||||
| }; | ||||
|  | ||||
|  | ||||
|  | ||||
| /** | ||||
|  * https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackmode | ||||
|  * | ||||
| @@ -78,9 +76,4 @@ const TextTrackMode = { | ||||
|   showing: 'showing' | ||||
| }; | ||||
|  | ||||
| /* jshint ignore:start */ | ||||
| // we ignore jshint here because it does not see | ||||
| // AudioTrackKind as defined here | ||||
| export default { VideoTrackKind, AudioTrackKind, TextTrackKind, TextTrackMode }; | ||||
| /* jshint ignore:end */ | ||||
|  | ||||
|   | ||||
| @@ -2,7 +2,6 @@ | ||||
|  * @file track-list.js | ||||
|  */ | ||||
| import EventTarget from '../event-target'; | ||||
| import * as Fn from '../utils/fn.js'; | ||||
| import * as browser from '../utils/browser.js'; | ||||
| import document from 'global/document'; | ||||
|  | ||||
| @@ -20,7 +19,7 @@ class TrackList extends EventTarget { | ||||
|   constructor(tracks = [], list = null) { | ||||
|     super(); | ||||
|     if (!list) { | ||||
|       list = this; | ||||
|       list = this; // eslint-disable-line | ||||
|       if (browser.IS_IE8) { | ||||
|         list = document.createElement('custom'); | ||||
|         for (let prop in TrackList.prototype) { | ||||
| @@ -119,6 +118,7 @@ class TrackList extends EventTarget { | ||||
|  | ||||
|     for (let i = 0, l = this.length; i < l; i++) { | ||||
|       let track = this[i]; | ||||
|  | ||||
|       if (track.id === id) { | ||||
|         result = track; | ||||
|         break; | ||||
|   | ||||
| @@ -19,7 +19,8 @@ class Track extends EventTarget { | ||||
|   constructor(options = {}) { | ||||
|     super(); | ||||
|  | ||||
|     let track = this; | ||||
|     let track = this; // eslint-disable-line | ||||
|  | ||||
|     if (browser.IS_IE8) { | ||||
|       track = document.createElement('custom'); | ||||
|       for (let prop in Track.prototype) { | ||||
| @@ -38,7 +39,9 @@ class Track extends EventTarget { | ||||
|  | ||||
|     for (let key in trackProps) { | ||||
|       Object.defineProperty(track, key, { | ||||
|         get() { return trackProps[key]; }, | ||||
|         get() { | ||||
|           return trackProps[key]; | ||||
|         }, | ||||
|         set() {} | ||||
|       }); | ||||
|     } | ||||
|   | ||||
| @@ -38,7 +38,9 @@ class VideoTrack extends Track { | ||||
|     } | ||||
|  | ||||
|     Object.defineProperty(track, 'selected', { | ||||
|       get() { return selected; }, | ||||
|       get() { | ||||
|         return selected; | ||||
|       }, | ||||
|       set(newSelected) { | ||||
|         // an invalid or unchanged value | ||||
|         if (typeof newSelected !== 'boolean' || newSelected === selected) { | ||||
|   | ||||
| @@ -24,18 +24,21 @@ export const IS_IPHONE = (/iPhone/i).test(USER_AGENT) && !IS_IPAD; | ||||
| export const IS_IPOD = (/iPod/i).test(USER_AGENT); | ||||
| export const IS_IOS = IS_IPHONE || IS_IPAD || IS_IPOD; | ||||
|  | ||||
| export const IOS_VERSION = (function(){ | ||||
|   var match = USER_AGENT.match(/OS (\d+)_/i); | ||||
|   if (match && match[1]) { return match[1]; } | ||||
| })(); | ||||
| export const IOS_VERSION = (function() { | ||||
|   let match = USER_AGENT.match(/OS (\d+)_/i); | ||||
|  | ||||
|   if (match && match[1]) { | ||||
|     return match[1]; | ||||
|   } | ||||
| }()); | ||||
|  | ||||
| export const IS_ANDROID = (/Android/i).test(USER_AGENT); | ||||
| export const ANDROID_VERSION = (function() { | ||||
|   // This matches Android Major.Minor.Patch versions | ||||
|   // ANDROID_VERSION is Major.Minor as a Number, if Minor isn't available, then only Major is returned | ||||
|   var match = USER_AGENT.match(/Android (\d+)(?:\.(\d+))?(?:\.(\d+))*/i), | ||||
|     major, | ||||
|     minor; | ||||
|   let match = USER_AGENT.match(/Android (\d+)(?:\.(\d+))?(?:\.(\d+))*/i); | ||||
|   let major; | ||||
|   let minor; | ||||
|  | ||||
|   if (!match) { | ||||
|     return null; | ||||
| @@ -48,10 +51,10 @@ export const ANDROID_VERSION = (function() { | ||||
|     return parseFloat(match[1] + '.' + match[2]); | ||||
|   } else if (major) { | ||||
|     return major; | ||||
|   } else { | ||||
|     return null; | ||||
|   } | ||||
| })(); | ||||
|   return null; | ||||
| }()); | ||||
|  | ||||
| // Old Android is defined as Version older than 2.3, and requiring a webkit version of the android browser | ||||
| export const IS_OLD_ANDROID = IS_ANDROID && (/webkit/i).test(USER_AGENT) && ANDROID_VERSION < 2.3; | ||||
| export const IS_NATIVE_ANDROID = IS_ANDROID && ANDROID_VERSION < 5 && appleWebkitVersion < 537; | ||||
| @@ -60,9 +63,9 @@ export const IS_FIREFOX = (/Firefox/i).test(USER_AGENT); | ||||
| export const IS_EDGE = (/Edge/i).test(USER_AGENT); | ||||
| export const IS_CHROME = !IS_EDGE && (/Chrome/i).test(USER_AGENT); | ||||
| export const IS_IE8 = (/MSIE\s8\.0/).test(USER_AGENT); | ||||
| export const IE_VERSION = (function(result){ | ||||
| export const IE_VERSION = (function(result) { | ||||
|   return result && parseFloat(result[1]); | ||||
| })((/MSIE\s(\d+)\.\d/).exec(USER_AGENT)); | ||||
| }((/MSIE\s(\d+)\.\d/).exec(USER_AGENT))); | ||||
|  | ||||
| export const TOUCH_ENABLED = !!(('ontouchstart' in window) || window.DocumentTouch && document instanceof window.DocumentTouch); | ||||
| export const BACKGROUND_SIZE_SUPPORTED = 'backgroundSize' in document.createElement('video').style; | ||||
|   | ||||
| @@ -13,8 +13,9 @@ import { createTimeRange } from './time-ranges.js'; | ||||
|  * @function bufferedPercent | ||||
|  */ | ||||
| export function bufferedPercent(buffered, duration) { | ||||
|   var bufferedDuration = 0, | ||||
|       start, end; | ||||
|   let bufferedDuration = 0; | ||||
|   let start; | ||||
|   let end; | ||||
|  | ||||
|   if (!duration) { | ||||
|     return 0; | ||||
| @@ -24,9 +25,9 @@ export function bufferedPercent(buffered, duration) { | ||||
|     buffered = createTimeRange(0, 0); | ||||
|   } | ||||
|  | ||||
|   for (let i = 0; i < buffered.length; i++){ | ||||
|   for (let i = 0; i < buffered.length; i++) { | ||||
|     start = buffered.start(i); | ||||
|     end   = buffered.end(i); | ||||
|     end = buffered.end(i); | ||||
|  | ||||
|     // buffered end can be bigger than duration by a very small fraction | ||||
|     if (end > duration) { | ||||
|   | ||||
| @@ -3,7 +3,7 @@ | ||||
|  */ | ||||
| import document from 'global/document'; | ||||
| import window from 'global/window'; | ||||
| import  * as Guid from './guid.js'; | ||||
| import * as Guid from './guid.js'; | ||||
| import log from './log.js'; | ||||
| import tsml from 'tsml'; | ||||
|  | ||||
| @@ -14,7 +14,7 @@ import tsml from 'tsml'; | ||||
|  * @return {Boolean} | ||||
|  */ | ||||
| function isNonBlankString(str) { | ||||
|   return typeof str === 'string' && /\S/.test(str); | ||||
|   return typeof str === 'string' && (/\S/).test(str); | ||||
| } | ||||
|  | ||||
| /** | ||||
| @@ -25,7 +25,7 @@ function isNonBlankString(str) { | ||||
|  * @return {Boolean} | ||||
|  */ | ||||
| function throwIfWhitespace(str) { | ||||
|   if (/\s/.test(str)) { | ||||
|   if ((/\s/).test(str)) { | ||||
|     throw new Error('class has illegal whitespace characters'); | ||||
|   } | ||||
| } | ||||
| @@ -40,6 +40,17 @@ function classRegExp(className) { | ||||
|   return new RegExp('(^|\\s)' + className + '($|\\s)'); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Determines, via duck typing, whether or not a value is a DOM element. | ||||
|  * | ||||
|  * @function isEl | ||||
|  * @param    {Mixed} value | ||||
|  * @return   {Boolean} | ||||
|  */ | ||||
| export function isEl(value) { | ||||
|   return !!value && typeof value === 'object' && value.nodeType === 1; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Creates functions to query the DOM using a given method. | ||||
|  * | ||||
| @@ -49,7 +60,7 @@ function classRegExp(className) { | ||||
|  * @return {Function} | ||||
|  */ | ||||
| function createQuerier(method) { | ||||
|   return function (selector, context) { | ||||
|   return function(selector, context) { | ||||
|     if (!isNonBlankString(selector)) { | ||||
|       return document[method](null); | ||||
|     } | ||||
| @@ -68,7 +79,7 @@ function createQuerier(method) { | ||||
|  * @return {Element}    Element with supplied ID | ||||
|  * @function getEl | ||||
|  */ | ||||
| export function getEl(id){ | ||||
| export function getEl(id) { | ||||
|   if (id.indexOf('#') === 0) { | ||||
|     id = id.slice(1); | ||||
|   } | ||||
| @@ -85,10 +96,10 @@ export function getEl(id){ | ||||
|  * @return {Element} | ||||
|  * @function createEl | ||||
|  */ | ||||
| export function createEl(tagName='div', properties={}, attributes={}){ | ||||
| export function createEl(tagName = 'div', properties = {}, attributes = {}) { | ||||
|   let el = document.createElement(tagName); | ||||
|  | ||||
|   Object.getOwnPropertyNames(properties).forEach(function(propName){ | ||||
|   Object.getOwnPropertyNames(properties).forEach(function(propName) { | ||||
|     let val = properties[propName]; | ||||
|  | ||||
|     // See #2176 | ||||
| @@ -104,8 +115,7 @@ export function createEl(tagName='div', properties={}, attributes={}){ | ||||
|     } | ||||
|   }); | ||||
|  | ||||
|   Object.getOwnPropertyNames(attributes).forEach(function(attrName){ | ||||
|     let val = attributes[attrName]; | ||||
|   Object.getOwnPropertyNames(attributes).forEach(function(attrName) { | ||||
|     el.setAttribute(attrName, attributes[attrName]); | ||||
|   }); | ||||
|  | ||||
| @@ -136,7 +146,7 @@ export function textContent(el, text) { | ||||
|  * @private | ||||
|  * @function insertElFirst | ||||
|  */ | ||||
| export function insertElFirst(child, parent){ | ||||
| export function insertElFirst(child, parent) { | ||||
|   if (parent.firstChild) { | ||||
|     parent.insertBefore(child, parent.firstChild); | ||||
|   } else { | ||||
| @@ -222,7 +232,7 @@ export function removeElData(el) { | ||||
|   // Remove the elIdAttr property from the DOM node | ||||
|   try { | ||||
|     delete el[elIdAttr]; | ||||
|   } catch(e) { | ||||
|   } catch (e) { | ||||
|     if (el.removeAttribute) { | ||||
|       el.removeAttribute(elIdAttr); | ||||
|     } else { | ||||
| @@ -242,10 +252,9 @@ export function removeElData(el) { | ||||
| export function hasElClass(element, classToCheck) { | ||||
|   if (element.classList) { | ||||
|     return element.classList.contains(classToCheck); | ||||
|   } else { | ||||
|     throwIfWhitespace(classToCheck); | ||||
|     return classRegExp(classToCheck).test(element.className); | ||||
|   } | ||||
|   throwIfWhitespace(classToCheck); | ||||
|   return classRegExp(classToCheck).test(element.className); | ||||
| } | ||||
|  | ||||
| /** | ||||
| @@ -339,7 +348,7 @@ export function toggleElClass(element, classToToggle, predicate) { | ||||
|  * @function setElAttributes | ||||
|  */ | ||||
| export function setElAttributes(el, attributes) { | ||||
|   Object.getOwnPropertyNames(attributes).forEach(function(attrName){ | ||||
|   Object.getOwnPropertyNames(attributes).forEach(function(attrName) { | ||||
|     let attrValue = attributes[attrName]; | ||||
|  | ||||
|     if (attrValue === null || typeof attrValue === 'undefined' || attrValue === false) { | ||||
| @@ -362,25 +371,29 @@ export function setElAttributes(el, attributes) { | ||||
|  * @function getElAttributes | ||||
|  */ | ||||
| export function getElAttributes(tag) { | ||||
|   var obj, knownBooleans, attrs, attrName, attrVal; | ||||
|   let obj; | ||||
|   let knownBooleans; | ||||
|   let attrs; | ||||
|   let attrName; | ||||
|   let attrVal; | ||||
|  | ||||
|   obj = {}; | ||||
|  | ||||
|   // known boolean attributes | ||||
|   // we can check for matching boolean properties, but older browsers | ||||
|   // won't know about HTML5 boolean attributes that we still read from | ||||
|   knownBooleans = ','+'autoplay,controls,loop,muted,default'+','; | ||||
|   knownBooleans = ',' + 'autoplay,controls,loop,muted,default' + ','; | ||||
|  | ||||
|   if (tag && tag.attributes && tag.attributes.length > 0) { | ||||
|     attrs = tag.attributes; | ||||
|  | ||||
|     for (var i = attrs.length - 1; i >= 0; i--) { | ||||
|     for (let i = attrs.length - 1; i >= 0; i--) { | ||||
|       attrName = attrs[i].name; | ||||
|       attrVal = attrs[i].value; | ||||
|  | ||||
|       // check for known booleans | ||||
|       // the matching element property will return a value for typeof | ||||
|       if (typeof tag[attrName] === 'boolean' || knownBooleans.indexOf(','+attrName+',') !== -1) { | ||||
|       if (typeof tag[attrName] === 'boolean' || knownBooleans.indexOf(',' + attrName + ',') !== -1) { | ||||
|         // the value of an included boolean attribute is typically an empty | ||||
|         // string ('') which would equal false if we just check for a false value. | ||||
|         // we also don't want support bad code like autoplay='false' | ||||
| @@ -492,17 +505,6 @@ export function getPointerPosition(el, event) { | ||||
|   return position; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Determines, via duck typing, whether or not a value is a DOM element. | ||||
|  * | ||||
|  * @function isEl | ||||
|  * @param    {Mixed} value | ||||
|  * @return   {Boolean} | ||||
|  */ | ||||
| export function isEl(value) { | ||||
|   return !!value && typeof value === 'object' && value.nodeType === 1; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Determines, via duck typing, whether or not a value is a text node. | ||||
|  * | ||||
| @@ -577,7 +579,7 @@ export function normalizeContent(content) { | ||||
|       return value; | ||||
|     } | ||||
|  | ||||
|     if (typeof value === 'string' && /\S/.test(value)) { | ||||
|     if (typeof value === 'string' && (/\S/).test(value)) { | ||||
|       return document.createTextNode(value); | ||||
|     } | ||||
|   }).filter(value => value); | ||||
|   | ||||
| @@ -7,321 +7,11 @@ | ||||
|  * robust as jquery's, so there's probably some differences. | ||||
|  */ | ||||
|  | ||||
| import  * as Dom from './dom.js'; | ||||
| import  * as Guid from './guid.js'; | ||||
| import * as Dom from './dom.js'; | ||||
| import * as Guid from './guid.js'; | ||||
| import window from 'global/window'; | ||||
| import document from 'global/document'; | ||||
|  | ||||
| /** | ||||
|  * Add an event listener to element | ||||
|  * It stores the handler function in a separate cache object | ||||
|  * and adds a generic handler to the element's event, | ||||
|  * along with a unique id (guid) to the element. | ||||
|  * | ||||
|  * @param  {Element|Object}   elem Element or object to bind listeners to | ||||
|  * @param  {String|Array}   type Type of event to bind to. | ||||
|  * @param  {Function} fn   Event listener. | ||||
|  * @method on | ||||
|  */ | ||||
| export function on(elem, type, fn){ | ||||
|   if (Array.isArray(type)) { | ||||
|     return _handleMultipleEvents(on, elem, type, fn); | ||||
|   } | ||||
|  | ||||
|   let data = Dom.getElData(elem); | ||||
|  | ||||
|   // We need a place to store all our handler data | ||||
|   if (!data.handlers) data.handlers = {}; | ||||
|  | ||||
|   if (!data.handlers[type]) data.handlers[type] = []; | ||||
|  | ||||
|   if (!fn.guid) fn.guid = Guid.newGUID(); | ||||
|  | ||||
|   data.handlers[type].push(fn); | ||||
|  | ||||
|   if (!data.dispatcher) { | ||||
|     data.disabled = false; | ||||
|  | ||||
|     data.dispatcher = function (event, hash){ | ||||
|  | ||||
|       if (data.disabled) return; | ||||
|       event = fixEvent(event); | ||||
|  | ||||
|       var handlers = data.handlers[event.type]; | ||||
|  | ||||
|       if (handlers) { | ||||
|         // Copy handlers so if handlers are added/removed during the process it doesn't throw everything off. | ||||
|         var handlersCopy = handlers.slice(0); | ||||
|  | ||||
|         for (var m = 0, n = handlersCopy.length; m < n; m++) { | ||||
|           if (event.isImmediatePropagationStopped()) { | ||||
|             break; | ||||
|           } else { | ||||
|             handlersCopy[m].call(elem, event, hash); | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     }; | ||||
|   } | ||||
|  | ||||
|   if (data.handlers[type].length === 1) { | ||||
|     if (elem.addEventListener) { | ||||
|       elem.addEventListener(type, data.dispatcher, false); | ||||
|     } else if (elem.attachEvent) { | ||||
|       elem.attachEvent('on' + type, data.dispatcher); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Removes event listeners from an element | ||||
|  * | ||||
|  * @param  {Element|Object}   elem Object to remove listeners from | ||||
|  * @param  {String|Array=}   type Type of listener to remove. Don't include to remove all events from element. | ||||
|  * @param  {Function} fn   Specific listener to remove. Don't include to remove listeners for an event type. | ||||
|  * @method off | ||||
|  */ | ||||
| export function off(elem, type, fn) { | ||||
|   // Don't want to add a cache object through getElData if not needed | ||||
|   if (!Dom.hasElData(elem)) return; | ||||
|  | ||||
|   let data = Dom.getElData(elem); | ||||
|  | ||||
|   // If no events exist, nothing to unbind | ||||
|   if (!data.handlers) { return; } | ||||
|  | ||||
|   if (Array.isArray(type)) { | ||||
|     return _handleMultipleEvents(off, elem, type, fn); | ||||
|   } | ||||
|  | ||||
|   // Utility function | ||||
|   var removeType = function(t){ | ||||
|      data.handlers[t] = []; | ||||
|      _cleanUpEvents(elem,t); | ||||
|   }; | ||||
|  | ||||
|   // Are we removing all bound events? | ||||
|   if (!type) { | ||||
|     for (let t in data.handlers) removeType(t); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   var handlers = data.handlers[type]; | ||||
|  | ||||
|   // If no handlers exist, nothing to unbind | ||||
|   if (!handlers) return; | ||||
|  | ||||
|   // If no listener was provided, remove all listeners for type | ||||
|   if (!fn) { | ||||
|     removeType(type); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   // We're only removing a single handler | ||||
|   if (fn.guid) { | ||||
|     for (let n = 0; n < handlers.length; n++) { | ||||
|       if (handlers[n].guid === fn.guid) { | ||||
|         handlers.splice(n--, 1); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   _cleanUpEvents(elem, type); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Trigger an event for an element | ||||
|  * | ||||
|  * @param  {Element|Object}      elem  Element to trigger an event on | ||||
|  * @param  {Event|Object|String} event A string (the type) or an event object with a type attribute | ||||
|  * @param  {Object} [hash] data hash to pass along with the event | ||||
|  * @return {Boolean=} Returned only if default was prevented | ||||
|  * @method trigger | ||||
|  */ | ||||
| export function trigger(elem, event, hash) { | ||||
|   // Fetches element data and a reference to the parent (for bubbling). | ||||
|   // Don't want to add a data object to cache for every parent, | ||||
|   // so checking hasElData first. | ||||
|   var elemData = (Dom.hasElData(elem)) ? Dom.getElData(elem) : {}; | ||||
|   var parent = elem.parentNode || elem.ownerDocument; | ||||
|       // type = event.type || event, | ||||
|       // handler; | ||||
|  | ||||
|   // If an event name was passed as a string, creates an event out of it | ||||
|   if (typeof event === 'string') { | ||||
|     event = { type:event, target:elem }; | ||||
|   } | ||||
|   // Normalizes the event properties. | ||||
|   event = fixEvent(event); | ||||
|  | ||||
|   // If the passed element has a dispatcher, executes the established handlers. | ||||
|   if (elemData.dispatcher) { | ||||
|     elemData.dispatcher.call(elem, event, hash); | ||||
|   } | ||||
|  | ||||
|   // Unless explicitly stopped or the event does not bubble (e.g. media events) | ||||
|     // recursively calls this function to bubble the event up the DOM. | ||||
|     if (parent && !event.isPropagationStopped() && event.bubbles === true) { | ||||
|       trigger.call(null, parent, event, hash); | ||||
|  | ||||
|   // If at the top of the DOM, triggers the default action unless disabled. | ||||
|   } else if (!parent && !event.defaultPrevented) { | ||||
|     var targetData = Dom.getElData(event.target); | ||||
|  | ||||
|     // Checks if the target has a default action for this event. | ||||
|     if (event.target[event.type]) { | ||||
|       // Temporarily disables event dispatching on the target as we have already executed the handler. | ||||
|       targetData.disabled = true; | ||||
|       // Executes the default action. | ||||
|       if (typeof event.target[event.type] === 'function') { | ||||
|         event.target[event.type](); | ||||
|       } | ||||
|       // Re-enables event dispatching. | ||||
|       targetData.disabled = false; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   // Inform the triggerer if the default was prevented by returning false | ||||
|   return !event.defaultPrevented; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Trigger a listener only once for an event | ||||
|  * | ||||
|  * @param  {Element|Object}   elem Element or object to | ||||
|  * @param  {String|Array}   type Name/type of event | ||||
|  * @param  {Function} fn Event handler function | ||||
|  * @method one | ||||
|  */ | ||||
| export function one(elem, type, fn) { | ||||
|   if (Array.isArray(type)) { | ||||
|     return _handleMultipleEvents(one, elem, type, fn); | ||||
|   } | ||||
|   var func = function(){ | ||||
|     off(elem, type, func); | ||||
|     fn.apply(this, arguments); | ||||
|   }; | ||||
|   // copy the guid to the new function so it can removed using the original function's ID | ||||
|   func.guid = fn.guid = fn.guid || Guid.newGUID(); | ||||
|   on(elem, type, func); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Fix a native event to have standard property values | ||||
|  * | ||||
|  * @param  {Object} event Event object to fix | ||||
|  * @return {Object} | ||||
|  * @private | ||||
|  * @method fixEvent | ||||
|  */ | ||||
| export function fixEvent(event) { | ||||
|  | ||||
|   function returnTrue() { return true; } | ||||
|   function returnFalse() { return false; } | ||||
|  | ||||
|   // Test if fixing up is needed | ||||
|   // Used to check if !event.stopPropagation instead of isPropagationStopped | ||||
|   // But native events return true for stopPropagation, but don't have | ||||
|   // other expected methods like isPropagationStopped. Seems to be a problem | ||||
|   // with the Javascript Ninja code. So we're just overriding all events now. | ||||
|   if (!event || !event.isPropagationStopped) { | ||||
|     var old = event || window.event; | ||||
|  | ||||
|     event = {}; | ||||
|     // Clone the old object so that we can modify the values event = {}; | ||||
|     // IE8 Doesn't like when you mess with native event properties | ||||
|     // Firefox returns false for event.hasOwnProperty('type') and other props | ||||
|     //  which makes copying more difficult. | ||||
|     // TODO: Probably best to create a whitelist of event props | ||||
|     for (var key in old) { | ||||
|       // Safari 6.0.3 warns you if you try to copy deprecated layerX/Y | ||||
|       // Chrome warns you if you try to copy deprecated keyboardEvent.keyLocation | ||||
|       // and webkitMovementX/Y | ||||
|       if (key !== 'layerX' && key !== 'layerY' && key !== 'keyLocation' && | ||||
|           key !== 'webkitMovementX' && key !== 'webkitMovementY') { | ||||
|         // Chrome 32+ warns if you try to copy deprecated returnValue, but | ||||
|         // we still want to if preventDefault isn't supported (IE8). | ||||
|         if (!(key === 'returnValue' && old.preventDefault)) { | ||||
|           event[key] = old[key]; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     // The event occurred on this element | ||||
|     if (!event.target) { | ||||
|       event.target = event.srcElement || document; | ||||
|     } | ||||
|  | ||||
|     // Handle which other element the event is related to | ||||
|     if (!event.relatedTarget) { | ||||
|       event.relatedTarget = event.fromElement === event.target ? | ||||
|         event.toElement : | ||||
|         event.fromElement; | ||||
|     } | ||||
|  | ||||
|     // Stop the default browser action | ||||
|     event.preventDefault = function () { | ||||
|       if (old.preventDefault) { | ||||
|         old.preventDefault(); | ||||
|       } | ||||
|       event.returnValue = false; | ||||
|       old.returnValue = false; | ||||
|       event.defaultPrevented = true; | ||||
|     }; | ||||
|  | ||||
|     event.defaultPrevented = false; | ||||
|  | ||||
|     // Stop the event from bubbling | ||||
|     event.stopPropagation = function () { | ||||
|       if (old.stopPropagation) { | ||||
|         old.stopPropagation(); | ||||
|       } | ||||
|       event.cancelBubble = true; | ||||
|       old.cancelBubble = true; | ||||
|       event.isPropagationStopped = returnTrue; | ||||
|     }; | ||||
|  | ||||
|     event.isPropagationStopped = returnFalse; | ||||
|  | ||||
|     // Stop the event from bubbling and executing other handlers | ||||
|     event.stopImmediatePropagation = function () { | ||||
|       if (old.stopImmediatePropagation) { | ||||
|         old.stopImmediatePropagation(); | ||||
|       } | ||||
|       event.isImmediatePropagationStopped = returnTrue; | ||||
|       event.stopPropagation(); | ||||
|     }; | ||||
|  | ||||
|     event.isImmediatePropagationStopped = returnFalse; | ||||
|  | ||||
|     // Handle mouse position | ||||
|     if (event.clientX != null) { | ||||
|       var doc = document.documentElement, body = document.body; | ||||
|  | ||||
|       event.pageX = event.clientX + | ||||
|         (doc && doc.scrollLeft || body && body.scrollLeft || 0) - | ||||
|         (doc && doc.clientLeft || body && body.clientLeft || 0); | ||||
|       event.pageY = event.clientY + | ||||
|         (doc && doc.scrollTop || body && body.scrollTop || 0) - | ||||
|         (doc && doc.clientTop || body && body.clientTop || 0); | ||||
|     } | ||||
|  | ||||
|     // Handle key presses | ||||
|     event.which = event.charCode || event.keyCode; | ||||
|  | ||||
|     // Fix button for mouse clicks: | ||||
|     // 0 == left; 1 == middle; 2 == right | ||||
|     if (event.button != null) { | ||||
|       event.button = (event.button & 1 ? 0 : | ||||
|         (event.button & 4 ? 1 : | ||||
|           (event.button & 2 ? 2 : 0))); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   // Returns fixed-up instance | ||||
|   return event; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Clean up the listener cache and dispatchers | ||||
| * | ||||
| @@ -331,7 +21,7 @@ export function fixEvent(event) { | ||||
|  * @method _cleanUpEvents | ||||
|  */ | ||||
| function _cleanUpEvents(elem, type) { | ||||
|   var data = Dom.getElData(elem); | ||||
|   let data = Dom.getElData(elem); | ||||
|  | ||||
|   // Remove the events of a particular type if there are none left | ||||
|   if (data.handlers[type].length === 0) { | ||||
| @@ -372,7 +62,346 @@ function _cleanUpEvents(elem, type) { | ||||
|  */ | ||||
| function _handleMultipleEvents(fn, elem, types, callback) { | ||||
|   types.forEach(function(type) { | ||||
|     //Call the event method for each one of the types | ||||
|     // Call the event method for each one of the types | ||||
|     fn(elem, type, callback); | ||||
|   }); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Fix a native event to have standard property values | ||||
|  * | ||||
|  * @param  {Object} event Event object to fix | ||||
|  * @return {Object} | ||||
|  * @private | ||||
|  * @method fixEvent | ||||
|  */ | ||||
| export function fixEvent(event) { | ||||
|  | ||||
|   function returnTrue() { | ||||
|     return true; | ||||
|   } | ||||
|  | ||||
|   function returnFalse() { | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   // Test if fixing up is needed | ||||
|   // Used to check if !event.stopPropagation instead of isPropagationStopped | ||||
|   // But native events return true for stopPropagation, but don't have | ||||
|   // other expected methods like isPropagationStopped. Seems to be a problem | ||||
|   // with the Javascript Ninja code. So we're just overriding all events now. | ||||
|   if (!event || !event.isPropagationStopped) { | ||||
|     let old = event || window.event; | ||||
|  | ||||
|     event = {}; | ||||
|     // Clone the old object so that we can modify the values event = {}; | ||||
|     // IE8 Doesn't like when you mess with native event properties | ||||
|     // Firefox returns false for event.hasOwnProperty('type') and other props | ||||
|     //  which makes copying more difficult. | ||||
|     // TODO: Probably best to create a whitelist of event props | ||||
|     for (let key in old) { | ||||
|       // Safari 6.0.3 warns you if you try to copy deprecated layerX/Y | ||||
|       // Chrome warns you if you try to copy deprecated keyboardEvent.keyLocation | ||||
|       // and webkitMovementX/Y | ||||
|       if (key !== 'layerX' && key !== 'layerY' && key !== 'keyLocation' && | ||||
|           key !== 'webkitMovementX' && key !== 'webkitMovementY') { | ||||
|         // Chrome 32+ warns if you try to copy deprecated returnValue, but | ||||
|         // we still want to if preventDefault isn't supported (IE8). | ||||
|         if (!(key === 'returnValue' && old.preventDefault)) { | ||||
|           event[key] = old[key]; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     // The event occurred on this element | ||||
|     if (!event.target) { | ||||
|       event.target = event.srcElement || document; | ||||
|     } | ||||
|  | ||||
|     // Handle which other element the event is related to | ||||
|     if (!event.relatedTarget) { | ||||
|       event.relatedTarget = event.fromElement === event.target ? | ||||
|         event.toElement : | ||||
|         event.fromElement; | ||||
|     } | ||||
|  | ||||
|     // Stop the default browser action | ||||
|     event.preventDefault = function() { | ||||
|       if (old.preventDefault) { | ||||
|         old.preventDefault(); | ||||
|       } | ||||
|       event.returnValue = false; | ||||
|       old.returnValue = false; | ||||
|       event.defaultPrevented = true; | ||||
|     }; | ||||
|  | ||||
|     event.defaultPrevented = false; | ||||
|  | ||||
|     // Stop the event from bubbling | ||||
|     event.stopPropagation = function() { | ||||
|       if (old.stopPropagation) { | ||||
|         old.stopPropagation(); | ||||
|       } | ||||
|       event.cancelBubble = true; | ||||
|       old.cancelBubble = true; | ||||
|       event.isPropagationStopped = returnTrue; | ||||
|     }; | ||||
|  | ||||
|     event.isPropagationStopped = returnFalse; | ||||
|  | ||||
|     // Stop the event from bubbling and executing other handlers | ||||
|     event.stopImmediatePropagation = function() { | ||||
|       if (old.stopImmediatePropagation) { | ||||
|         old.stopImmediatePropagation(); | ||||
|       } | ||||
|       event.isImmediatePropagationStopped = returnTrue; | ||||
|       event.stopPropagation(); | ||||
|     }; | ||||
|  | ||||
|     event.isImmediatePropagationStopped = returnFalse; | ||||
|  | ||||
|     // Handle mouse position | ||||
|     if (event.clientX != null) { | ||||
|       let doc = document.documentElement; | ||||
|       let body = document.body; | ||||
|  | ||||
|       event.pageX = event.clientX + | ||||
|         (doc && doc.scrollLeft || body && body.scrollLeft || 0) - | ||||
|         (doc && doc.clientLeft || body && body.clientLeft || 0); | ||||
|       event.pageY = event.clientY + | ||||
|         (doc && doc.scrollTop || body && body.scrollTop || 0) - | ||||
|         (doc && doc.clientTop || body && body.clientTop || 0); | ||||
|     } | ||||
|  | ||||
|     // Handle key presses | ||||
|     event.which = event.charCode || event.keyCode; | ||||
|  | ||||
|     // Fix button for mouse clicks: | ||||
|     // 0 == left; 1 == middle; 2 == right | ||||
|     if (event.button != null) { | ||||
|  | ||||
|       // The following is disabled because it does not pass videojs-standard | ||||
|       // and... yikes. | ||||
|       /* eslint-disable */ | ||||
|       event.button = (event.button & 1 ? 0 : | ||||
|         (event.button & 4 ? 1 : | ||||
|           (event.button & 2 ? 2 : 0))); | ||||
|       /* eslint-enable */ | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   // Returns fixed-up instance | ||||
|   return event; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Add an event listener to element | ||||
|  * It stores the handler function in a separate cache object | ||||
|  * and adds a generic handler to the element's event, | ||||
|  * along with a unique id (guid) to the element. | ||||
|  * | ||||
|  * @param  {Element|Object}   elem Element or object to bind listeners to | ||||
|  * @param  {String|Array}   type Type of event to bind to. | ||||
|  * @param  {Function} fn   Event listener. | ||||
|  * @method on | ||||
|  */ | ||||
| export function on(elem, type, fn) { | ||||
|   if (Array.isArray(type)) { | ||||
|     return _handleMultipleEvents(on, elem, type, fn); | ||||
|   } | ||||
|  | ||||
|   let data = Dom.getElData(elem); | ||||
|  | ||||
|   // We need a place to store all our handler data | ||||
|   if (!data.handlers) { | ||||
|     data.handlers = {}; | ||||
|   } | ||||
|  | ||||
|   if (!data.handlers[type]) { | ||||
|     data.handlers[type] = []; | ||||
|   } | ||||
|  | ||||
|   if (!fn.guid) { | ||||
|     fn.guid = Guid.newGUID(); | ||||
|   } | ||||
|  | ||||
|   data.handlers[type].push(fn); | ||||
|  | ||||
|   if (!data.dispatcher) { | ||||
|     data.disabled = false; | ||||
|  | ||||
|     data.dispatcher = function(event, hash) { | ||||
|  | ||||
|       if (data.disabled) { | ||||
|         return; | ||||
|       } | ||||
|  | ||||
|       event = fixEvent(event); | ||||
|  | ||||
|       let handlers = data.handlers[event.type]; | ||||
|  | ||||
|       if (handlers) { | ||||
|         // Copy handlers so if handlers are added/removed during the process it doesn't throw everything off. | ||||
|         let handlersCopy = handlers.slice(0); | ||||
|  | ||||
|         for (let m = 0, n = handlersCopy.length; m < n; m++) { | ||||
|           if (event.isImmediatePropagationStopped()) { | ||||
|             break; | ||||
|           } else { | ||||
|             handlersCopy[m].call(elem, event, hash); | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     }; | ||||
|   } | ||||
|  | ||||
|   if (data.handlers[type].length === 1) { | ||||
|     if (elem.addEventListener) { | ||||
|       elem.addEventListener(type, data.dispatcher, false); | ||||
|     } else if (elem.attachEvent) { | ||||
|       elem.attachEvent('on' + type, data.dispatcher); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Removes event listeners from an element | ||||
|  * | ||||
|  * @param  {Element|Object}   elem Object to remove listeners from | ||||
|  * @param  {String|Array=}   type Type of listener to remove. Don't include to remove all events from element. | ||||
|  * @param  {Function} fn   Specific listener to remove. Don't include to remove listeners for an event type. | ||||
|  * @method off | ||||
|  */ | ||||
| export function off(elem, type, fn) { | ||||
|   // Don't want to add a cache object through getElData if not needed | ||||
|   if (!Dom.hasElData(elem)) { | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   let data = Dom.getElData(elem); | ||||
|  | ||||
|   // If no events exist, nothing to unbind | ||||
|   if (!data.handlers) { | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   if (Array.isArray(type)) { | ||||
|     return _handleMultipleEvents(off, elem, type, fn); | ||||
|   } | ||||
|  | ||||
|   // Utility function | ||||
|   let removeType = function(t) { | ||||
|     data.handlers[t] = []; | ||||
|     _cleanUpEvents(elem, t); | ||||
|   }; | ||||
|  | ||||
|   // Are we removing all bound events? | ||||
|   if (!type) { | ||||
|     for (let t in data.handlers) { | ||||
|       removeType(t); | ||||
|     } | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   let handlers = data.handlers[type]; | ||||
|  | ||||
|   // If no handlers exist, nothing to unbind | ||||
|   if (!handlers) { | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   // If no listener was provided, remove all listeners for type | ||||
|   if (!fn) { | ||||
|     removeType(type); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   // We're only removing a single handler | ||||
|   if (fn.guid) { | ||||
|     for (let n = 0; n < handlers.length; n++) { | ||||
|       if (handlers[n].guid === fn.guid) { | ||||
|         handlers.splice(n--, 1); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   _cleanUpEvents(elem, type); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Trigger an event for an element | ||||
|  * | ||||
|  * @param  {Element|Object}      elem  Element to trigger an event on | ||||
|  * @param  {Event|Object|String} event A string (the type) or an event object with a type attribute | ||||
|  * @param  {Object} [hash] data hash to pass along with the event | ||||
|  * @return {Boolean=} Returned only if default was prevented | ||||
|  * @method trigger | ||||
|  */ | ||||
| export function trigger(elem, event, hash) { | ||||
|   // Fetches element data and a reference to the parent (for bubbling). | ||||
|   // Don't want to add a data object to cache for every parent, | ||||
|   // so checking hasElData first. | ||||
|   let elemData = (Dom.hasElData(elem)) ? Dom.getElData(elem) : {}; | ||||
|   let parent = elem.parentNode || elem.ownerDocument; | ||||
|       // type = event.type || event, | ||||
|       // handler; | ||||
|  | ||||
|   // If an event name was passed as a string, creates an event out of it | ||||
|   if (typeof event === 'string') { | ||||
|     event = {type: event, target: elem}; | ||||
|   } | ||||
|   // Normalizes the event properties. | ||||
|   event = fixEvent(event); | ||||
|  | ||||
|   // If the passed element has a dispatcher, executes the established handlers. | ||||
|   if (elemData.dispatcher) { | ||||
|     elemData.dispatcher.call(elem, event, hash); | ||||
|   } | ||||
|  | ||||
|   // Unless explicitly stopped or the event does not bubble (e.g. media events) | ||||
|   // recursively calls this function to bubble the event up the DOM. | ||||
|   if (parent && !event.isPropagationStopped() && event.bubbles === true) { | ||||
|     trigger.call(null, parent, event, hash); | ||||
|  | ||||
|   // If at the top of the DOM, triggers the default action unless disabled. | ||||
|   } else if (!parent && !event.defaultPrevented) { | ||||
|     let targetData = Dom.getElData(event.target); | ||||
|  | ||||
|     // Checks if the target has a default action for this event. | ||||
|     if (event.target[event.type]) { | ||||
|       // Temporarily disables event dispatching on the target as we have already executed the handler. | ||||
|       targetData.disabled = true; | ||||
|       // Executes the default action. | ||||
|       if (typeof event.target[event.type] === 'function') { | ||||
|         event.target[event.type](); | ||||
|       } | ||||
|       // Re-enables event dispatching. | ||||
|       targetData.disabled = false; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   // Inform the triggerer if the default was prevented by returning false | ||||
|   return !event.defaultPrevented; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Trigger a listener only once for an event | ||||
|  * | ||||
|  * @param  {Element|Object}   elem Element or object to | ||||
|  * @param  {String|Array}   type Name/type of event | ||||
|  * @param  {Function} fn Event handler function | ||||
|  * @method one | ||||
|  */ | ||||
| export function one(elem, type, fn) { | ||||
|   if (Array.isArray(type)) { | ||||
|     return _handleMultipleEvents(one, elem, type, fn); | ||||
|   } | ||||
|   let func = function() { | ||||
|     off(elem, type, func); | ||||
|     fn.apply(this, arguments); | ||||
|   }; | ||||
|  | ||||
|   // copy the guid to the new function so it can removed using the original function's ID | ||||
|   func.guid = fn.guid = fn.guid || Guid.newGUID(); | ||||
|   on(elem, type, func); | ||||
| } | ||||
|   | ||||
| @@ -16,7 +16,9 @@ import { newGUID } from './guid.js'; | ||||
|  */ | ||||
| export const bind = function(context, fn, uid) { | ||||
|   // Make sure the function has a unique ID | ||||
|   if (!fn.guid) { fn.guid = newGUID(); } | ||||
|   if (!fn.guid) { | ||||
|     fn.guid = newGUID(); | ||||
|   } | ||||
|  | ||||
|   // Create the new function that changes the context | ||||
|   let ret = function() { | ||||
|   | ||||
| @@ -11,7 +11,7 @@ | ||||
|  * @private | ||||
|  * @function formatTime | ||||
|  */ | ||||
| function formatTime(seconds, guide=seconds) { | ||||
| function formatTime(seconds, guide = seconds) { | ||||
|   seconds = seconds < 0 ? 0 : seconds; | ||||
|   let s = Math.floor(seconds % 60); | ||||
|   let m = Math.floor(seconds / 60 % 60); | ||||
|   | ||||
| @@ -10,7 +10,7 @@ let _guid = 1; | ||||
| /** | ||||
|  * Get the next unique ID | ||||
|  * | ||||
|  * @return {String}  | ||||
|  * @return {String} | ||||
|  * @function newGUID | ||||
|  */ | ||||
| export function newGUID() { | ||||
|   | ||||
| @@ -4,6 +4,8 @@ | ||||
| import window from 'global/window'; | ||||
| import {IE_VERSION} from './browser'; | ||||
|  | ||||
| let log; | ||||
|  | ||||
| /** | ||||
|  * Log messages to the console and history based on the type of message | ||||
|  * | ||||
| @@ -16,7 +18,6 @@ import {IE_VERSION} from './browser'; | ||||
|  *         but this is exposed as a parameter to facilitate testing. | ||||
|  */ | ||||
| export const logByType = (type, args, stringify = !!IE_VERSION && IE_VERSION < 11) => { | ||||
|   const console = window.console; | ||||
|  | ||||
|   // If there's no console then don't try to output messages, but they will | ||||
|   // still be stored in `log.history`. | ||||
| @@ -24,7 +25,7 @@ export const logByType = (type, args, stringify = !!IE_VERSION && IE_VERSION < 1 | ||||
|   // Was setting these once outside of this function, but containing them | ||||
|   // in the function makes it easier to test cases where console doesn't exist | ||||
|   // when the module is executed. | ||||
|   const fn = console && console[type] || function(){}; | ||||
|   const fn = window.console && window.console[type] || function() {}; | ||||
|  | ||||
|   if (type !== 'log') { | ||||
|  | ||||
| @@ -45,7 +46,9 @@ export const logByType = (type, args, stringify = !!IE_VERSION && IE_VERSION < 1 | ||||
|       if (a && typeof a === 'object' || Array.isArray(a)) { | ||||
|         try { | ||||
|           return JSON.stringify(a); | ||||
|         } catch (x) {} | ||||
|         } catch (x) { | ||||
|           return String(a); | ||||
|         } | ||||
|       } | ||||
|  | ||||
|       // Cast to string before joining, so we get null and undefined explicitly | ||||
| @@ -68,9 +71,9 @@ export const logByType = (type, args, stringify = !!IE_VERSION && IE_VERSION < 1 | ||||
|  * | ||||
|  * @function log | ||||
|  */ | ||||
| function log(...args) { | ||||
| log = function(...args) { | ||||
|   logByType('log', args); | ||||
| } | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * Keep a history of log messages | ||||
| @@ -93,5 +96,4 @@ log.error = (...args) => logByType('error', args); | ||||
|  */ | ||||
| log.warn = (...args) => logByType('warn', args); | ||||
|  | ||||
|  | ||||
| export default log; | ||||
|   | ||||
| @@ -4,12 +4,14 @@ | ||||
| import merge from 'lodash-compat/object/merge'; | ||||
|  | ||||
| function isPlain(obj) { | ||||
|   return !!obj | ||||
|     && typeof obj === 'object' | ||||
|     && obj.toString() === '[object Object]' | ||||
|     && obj.constructor === Object; | ||||
|   return !!obj && | ||||
|     typeof obj === 'object' && | ||||
|     obj.toString() === '[object Object]' && | ||||
|     obj.constructor === Object; | ||||
| } | ||||
|  | ||||
| let mergeOptions; | ||||
|  | ||||
| /** | ||||
|  * Merge customizer. video.js simply overwrites non-simple objects | ||||
|  * (like arrays) instead of attempting to overlay them. | ||||
| @@ -41,7 +43,7 @@ const customizer = function(destination, source) { | ||||
|  * provided objects | ||||
|  * @function mergeOptions | ||||
|  */ | ||||
| export default function mergeOptions() { | ||||
| mergeOptions = function() { | ||||
|   // contruct the call dynamically to handle the variable number of | ||||
|   // objects to merge | ||||
|   let args = Array.prototype.slice.call(arguments); | ||||
| @@ -57,4 +59,6 @@ export default function mergeOptions() { | ||||
|  | ||||
|   // return the mutated result object | ||||
|   return args[0]; | ||||
| } | ||||
| }; | ||||
|  | ||||
| export default mergeOptions; | ||||
|   | ||||
| @@ -2,6 +2,7 @@ import document from 'global/document'; | ||||
|  | ||||
| export let createStyleElement = function(className) { | ||||
|   let style = document.createElement('style'); | ||||
|  | ||||
|   style.className = className; | ||||
|  | ||||
|   return style; | ||||
|   | ||||
| @@ -1,5 +1,39 @@ | ||||
| import log from './log.js'; | ||||
|  | ||||
| function rangeCheck(fnName, index, maxIndex) { | ||||
|   if (index < 0 || index > maxIndex) { | ||||
|     throw new Error(`Failed to execute '${fnName}' on 'TimeRanges': The index provided (${index}) is greater than or equal to the maximum bound (${maxIndex}).`); | ||||
|   } | ||||
| } | ||||
|  | ||||
| function getRange(fnName, valueIndex, ranges, rangeIndex) { | ||||
|   if (rangeIndex === undefined) { | ||||
|     log.warn(`DEPRECATED: Function '${fnName}' on 'TimeRanges' called without an index argument.`); | ||||
|     rangeIndex = 0; | ||||
|   } | ||||
|   rangeCheck(fnName, rangeIndex, ranges.length - 1); | ||||
|   return ranges[rangeIndex][valueIndex]; | ||||
| } | ||||
|  | ||||
| function createTimeRangesObj(ranges) { | ||||
|   if (ranges === undefined || ranges.length === 0) { | ||||
|     return { | ||||
|       length: 0, | ||||
|       start() { | ||||
|         throw new Error('This TimeRanges object is empty'); | ||||
|       }, | ||||
|       end() { | ||||
|         throw new Error('This TimeRanges object is empty'); | ||||
|       } | ||||
|     }; | ||||
|   } | ||||
|   return { | ||||
|     length: ranges.length, | ||||
|     start: getRange.bind(null, 'start', 0, ranges), | ||||
|     end: getRange.bind(null, 'end', 1, ranges) | ||||
|   }; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * @file time-ranges.js | ||||
|  * | ||||
| @@ -13,7 +47,7 @@ import log from './log.js'; | ||||
|  * @private | ||||
|  * @method createTimeRanges | ||||
|  */ | ||||
| export function createTimeRanges(start, end){ | ||||
| export function createTimeRanges(start, end) { | ||||
|   if (Array.isArray(start)) { | ||||
|     return createTimeRangesObj(start); | ||||
|   } else if (start === undefined || end === undefined) { | ||||
| @@ -23,37 +57,3 @@ export function createTimeRanges(start, end){ | ||||
| } | ||||
|  | ||||
| export { createTimeRanges as createTimeRange }; | ||||
|  | ||||
| function createTimeRangesObj(ranges){ | ||||
|   if (ranges === undefined || ranges.length === 0) { | ||||
|     return { | ||||
|       length: 0, | ||||
|       start: function() { | ||||
|         throw new Error('This TimeRanges object is empty'); | ||||
|       }, | ||||
|       end: function() { | ||||
|         throw new Error('This TimeRanges object is empty'); | ||||
|       } | ||||
|     }; | ||||
|   } | ||||
|   return { | ||||
|     length: ranges.length, | ||||
|     start: getRange.bind(null, 'start', 0, ranges), | ||||
|     end: getRange.bind(null, 'end', 1, ranges) | ||||
|   }; | ||||
| } | ||||
|  | ||||
| function getRange(fnName, valueIndex, ranges, rangeIndex){ | ||||
|   if (rangeIndex === undefined) { | ||||
|     log.warn(`DEPRECATED: Function '${fnName}' on 'TimeRanges' called without an index argument.`); | ||||
|     rangeIndex = 0; | ||||
|   } | ||||
|   rangeCheck(fnName, rangeIndex, ranges.length - 1); | ||||
|   return ranges[rangeIndex][valueIndex]; | ||||
| } | ||||
|  | ||||
| function rangeCheck(fnName, index, maxIndex){ | ||||
|   if (index < 0 || index > maxIndex) { | ||||
|     throw new Error(`Failed to execute '${fnName}' on 'TimeRanges': The index provided (${index}) is greater than or equal to the maximum bound (${maxIndex}).`); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -8,7 +8,7 @@ | ||||
|  * @private | ||||
|  * @method toTitleCase | ||||
|  */ | ||||
| function toTitleCase(string){ | ||||
| function toTitleCase(string) { | ||||
|   return string.charAt(0).toUpperCase() + string.slice(1); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -16,6 +16,7 @@ export const parseUrl = function(url) { | ||||
|  | ||||
|   // add the url to an anchor and let the browser parse the URL | ||||
|   let a = document.createElement('a'); | ||||
|  | ||||
|   a.href = url; | ||||
|  | ||||
|   // IE8 (and 9?) Fix | ||||
| @@ -23,6 +24,7 @@ export const parseUrl = function(url) { | ||||
|   // added to the body, and an innerHTML is needed to trigger the parsing | ||||
|   let addToBody = (a.host === '' && a.protocol !== 'file:'); | ||||
|   let div; | ||||
|  | ||||
|   if (addToBody) { | ||||
|     div = document.createElement('div'); | ||||
|     div.innerHTML = `<a href="${url}"></a>`; | ||||
| @@ -36,7 +38,8 @@ export const parseUrl = function(url) { | ||||
|   // This is also needed for IE8 because the anchor loses its | ||||
|   // properties when it's removed from the dom | ||||
|   let details = {}; | ||||
|   for (var i = 0; i < props.length; i++) { | ||||
|  | ||||
|   for (let i = 0; i < props.length; i++) { | ||||
|     details[props[i]] = a[props[i]]; | ||||
|   } | ||||
|  | ||||
| @@ -45,6 +48,7 @@ export const parseUrl = function(url) { | ||||
|   if (details.protocol === 'http:') { | ||||
|     details.host = details.host.replace(/:80$/, ''); | ||||
|   } | ||||
|  | ||||
|   if (details.protocol === 'https:') { | ||||
|     details.host = details.host.replace(/:443$/, ''); | ||||
|   } | ||||
| @@ -65,11 +69,12 @@ export const parseUrl = function(url) { | ||||
|  * @private | ||||
|  * @method getAbsoluteURL | ||||
|  */ | ||||
| export const getAbsoluteURL = function(url){ | ||||
| export const getAbsoluteURL = function(url) { | ||||
|   // Check if absolute URL | ||||
|   if (!url.match(/^https?:\/\//)) { | ||||
|     // Convert to absolute URL. Flash hosted off-site needs an absolute URL. | ||||
|     let div = document.createElement('div'); | ||||
|  | ||||
|     div.innerHTML = `<a href="${url}">x</a>`; | ||||
|     url = div.firstChild.href; | ||||
|   } | ||||
| @@ -85,7 +90,7 @@ export const getAbsoluteURL = function(url){ | ||||
|  * @method getFileExtension | ||||
|  */ | ||||
| export const getFileExtension = function(path) { | ||||
|   if(typeof path === 'string'){ | ||||
|   if (typeof path === 'string') { | ||||
|     let splitPathRe = /^(\/?)([\s\S]*?)((?:\.{1,2}|[^\/]+?)(\.([^\.\/\?]+)))(?:[\/]*|[\?].*)$/i; | ||||
|     let pathParts = splitPathRe.exec(path); | ||||
|  | ||||
|   | ||||
| @@ -1,6 +1,9 @@ | ||||
| /** | ||||
|  * @file video.js | ||||
|  */ | ||||
|  | ||||
| /* global define */ | ||||
|  | ||||
| import window from 'global/window'; | ||||
| import document from 'global/document'; | ||||
| import * as setup from './setup'; | ||||
| @@ -28,8 +31,6 @@ import xhr from 'xhr'; | ||||
|  | ||||
| // Include the built-in techs | ||||
| import Tech from './tech/tech.js'; | ||||
| import Html5 from './tech/html5.js'; | ||||
| import Flash from './tech/flash.js'; | ||||
|  | ||||
| // HTML5 Element Shim for IE8 | ||||
| if (typeof HTMLVideoElement === 'undefined') { | ||||
| @@ -53,8 +54,8 @@ if (typeof HTMLVideoElement === 'undefined') { | ||||
|  * @mixes videojs | ||||
|  * @method videojs | ||||
|  */ | ||||
| function videojs(id, options, ready){ | ||||
|   let tag; // Element of ID | ||||
| function videojs(id, options, ready) { | ||||
|   let tag; | ||||
|  | ||||
|   // Allow for element or ID to be passed in | ||||
|   // String ID | ||||
| @@ -78,11 +79,10 @@ function videojs(id, options, ready){ | ||||
|       } | ||||
|  | ||||
|       return videojs.getPlayers()[id]; | ||||
|     } | ||||
|  | ||||
|     // Otherwise get element for ID | ||||
|     } else { | ||||
|       tag = Dom.getEl(id); | ||||
|     } | ||||
|     tag = Dom.getEl(id); | ||||
|  | ||||
|   // ID is a media element | ||||
|   } else { | ||||
| @@ -90,13 +90,14 @@ function videojs(id, options, ready){ | ||||
|   } | ||||
|  | ||||
|   // Check for a useable element | ||||
|   if (!tag || !tag.nodeName) { // re: nodeName, could be a box div also | ||||
|     throw new TypeError('The element or ID supplied is not valid. (videojs)'); // Returns | ||||
|   // re: nodeName, could be a box div also | ||||
|   if (!tag || !tag.nodeName) { | ||||
|     throw new TypeError('The element or ID supplied is not valid. (videojs)'); | ||||
|   } | ||||
|  | ||||
|   // Element may have a player attr referring to an already created player instance. | ||||
|   // If not, set up a new player and return the instance. | ||||
|   return tag['player'] || Player.players[tag.playerId] || new Player(tag, options, ready); | ||||
|   return tag.player || Player.players[tag.playerId] || new Player(tag, options, ready); | ||||
| } | ||||
|  | ||||
| // Add default styles | ||||
| @@ -106,6 +107,7 @@ if (window.VIDEOJS_NO_DYNAMIC_STYLE !== true) { | ||||
|   if (!style) { | ||||
|     style = stylesheet.createStyleElement('vjs-styles-defaults'); | ||||
|     let head = Dom.$('head'); | ||||
|  | ||||
|     head.insertBefore(style, head.firstChild); | ||||
|     stylesheet.setTextContent(style, ` | ||||
|       .video-js { | ||||
| @@ -121,7 +123,8 @@ if (window.VIDEOJS_NO_DYNAMIC_STYLE !== true) { | ||||
| } | ||||
|  | ||||
| // Run Auto-load players | ||||
| // You have to wait at least once in case this script is loaded after your video in the DOM (weird behavior only with minified version) | ||||
| // You have to wait at least once in case this script is loaded after your | ||||
| // video in the DOM (weird behavior only with minified version) | ||||
| setup.autoSetupTimeout(1, videojs); | ||||
|  | ||||
| /* | ||||
| @@ -269,12 +272,12 @@ videojs.TOUCH_ENABLED = browser.TOUCH_ENABLED; | ||||
|  * Mimics ES6 subclassing with the `extend` keyword | ||||
|  * ```js | ||||
|  *     // Create a basic javascript 'class' | ||||
|  *     function MyClass(name){ | ||||
|  *     function MyClass(name) { | ||||
|  *       // Set a property at initialization | ||||
|  *       this.myName = name; | ||||
|  *     } | ||||
|  *     // Create an instance method | ||||
|  *     MyClass.prototype.sayMyName = function(){ | ||||
|  *     MyClass.prototype.sayMyName = function() { | ||||
|  *       alert(this.myName); | ||||
|  *     }; | ||||
|  *     // Subclass the exisitng class and change the name | ||||
| @@ -337,12 +340,12 @@ videojs.mergeOptions = mergeOptions; | ||||
| /** | ||||
|  * Change the context (this) of a function | ||||
|  * | ||||
|  *     videojs.bind(newContext, function(){ | ||||
|  *     videojs.bind(newContext, function() { | ||||
|  *       this === newContext | ||||
|  *     }); | ||||
|  * | ||||
|  * NOTE: as of v5.0 we require an ES5 shim, so you should use the native | ||||
|  * `function(){}.bind(newContext);` instead of this. | ||||
|  * `function() {}.bind(newContext);` instead of this. | ||||
|  * | ||||
|  * @param  {*}        context The object to bind as scope | ||||
|  * @param  {Function} fn      The function to be bound to a scope | ||||
| @@ -365,7 +368,7 @@ videojs.bind = Fn.bind; | ||||
|  *       var player = this; | ||||
|  *       var alertText = myPluginOptions.text || 'Player is playing!' | ||||
|  * | ||||
|  *       player.on('play', function(){ | ||||
|  *       player.on('play', function() { | ||||
|  *         alert(alertText); | ||||
|  *       }); | ||||
|  *     }); | ||||
| @@ -410,7 +413,7 @@ videojs.plugin = plugin; | ||||
|  * @mixes videojs | ||||
|  * @method addLanguage | ||||
|  */ | ||||
| videojs.addLanguage = function(code, data){ | ||||
| videojs.addLanguage = function(code, data) { | ||||
|   code = ('' + code).toLowerCase(); | ||||
|   return merge(videojs.options.languages, { [code]: data })[code]; | ||||
| }; | ||||
| @@ -721,12 +724,12 @@ videojs.insertContent = Dom.insertContent; | ||||
|  * still support requirejs and browserify. This also needs to be closure | ||||
|  * compiler compatible, so string keys are used. | ||||
|  */ | ||||
| if (typeof define === 'function' && define['amd']) { | ||||
|   define('videojs', [], function(){ return videojs; }); | ||||
| if (typeof define === 'function' && define.amd) { | ||||
|   define('videojs', [], () => videojs); | ||||
|  | ||||
| // checking that module is an object too because of umdjs/umd#35 | ||||
| } else if (typeof exports === 'object' && typeof module === 'object') { | ||||
|   module['exports'] = videojs; | ||||
|   module.exports = videojs; | ||||
| } | ||||
|  | ||||
| export default videojs; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user