diff --git a/.jshintrc b/.jshintrc deleted file mode 100644 index 826ec9ffb2..0000000000 --- a/.jshintrc +++ /dev/null @@ -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" - ] -} diff --git a/build/grunt.js b/build/grunt.js index 379a815ac2..7372c7530b 100644 --- a/build/grunt.js +++ b/build/grunt.js @@ -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', diff --git a/package.json b/package.json index 892c4090cf..a44ed8387f 100644 --- a/package.json +++ b/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" ] } } diff --git a/src/js/base-styles.js b/src/js/base-styles.js index be6e302393..cd6ff7a128 100644 --- a/src/js/base-styles.js +++ b/src/js/base-styles.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); diff --git a/src/js/button.js b/src/js/button.js index 54067cc02c..c869de906f 100644 --- a/src/js/button.js +++ b/src/js/button.js @@ -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); diff --git a/src/js/clickable-component.js b/src/js/clickable-component.js index 2861b3a228..7d9774a792 100644 --- a/src/js/clickable-component.js +++ b/src/js/clickable-component.js @@ -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); } } diff --git a/src/js/component.js b/src/js/component.js index 9ef9d0caf3..18b1f2e95e 100644 --- a/src/js/component.js +++ b/src/js/component.js @@ -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; diff --git a/src/js/control-bar/audio-track-controls/audio-track-button.js b/src/js/control-bar/audio-track-controls/audio-track-button.js index b4f7d5575a..3b84c3db6b 100644 --- a/src/js/control-bar/audio-track-controls/audio-track-button.js +++ b/src/js/control-bar/audio-track-controls/audio-track-button.js @@ -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 })); } diff --git a/src/js/control-bar/audio-track-controls/audio-track-menu-item.js b/src/js/control-bar/audio-track-controls/audio-track-menu-item.js index fcaf428a43..b97f44a22f 100644 --- a/src/js/control-bar/audio-track-controls/audio-track-menu-item.js +++ b/src/js/control-bar/audio-track-controls/audio-track-menu-item.js @@ -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; diff --git a/src/js/control-bar/control-bar.js b/src/js/control-bar/control-bar.js index 1fbc82abd6..90d110f696 100644 --- a/src/js/control-bar/control-bar.js +++ b/src/js/control-bar/control-bar.js @@ -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' }); } } diff --git a/src/js/control-bar/live-display.js b/src/js/control-bar/live-display.js index 25fd01710b..017b5d0576 100644 --- a/src/js/control-bar/live-display.js +++ b/src/js/control-bar/live-display.js @@ -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' }); diff --git a/src/js/control-bar/mute-toggle.js b/src/js/control-bar/mute-toggle.js index aa063ecc23..16adb66b1b 100644 --- a/src/js/control-bar/mute-toggle.js +++ b/src/js/control-bar/mute-toggle.js @@ -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}`); diff --git a/src/js/control-bar/play-toggle.js b/src/js/control-bar/play-toggle.js index 5849d7808a..3781d135d4 100644 --- a/src/js/control-bar/play-toggle.js +++ b/src/js/control-bar/play-toggle.js @@ -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'); } } diff --git a/src/js/control-bar/playback-rate-menu/playback-rate-menu-button.js b/src/js/control-bar/playback-rate-menu/playback-rate-menu-button.js index 342c0aec16..993e94fdf2 100644 --- a/src/js/control-bar/playback-rate-menu/playback-rate-menu-button.js +++ b/src/js/control-bar/playback-rate-menu/playback-rate-menu-button.js @@ -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 ; } diff --git a/src/js/control-bar/playback-rate-menu/playback-rate-menu-item.js b/src/js/control-bar/playback-rate-menu/playback-rate-menu-item.js index 614d8c1cdb..6ec35dac4f 100644 --- a/src/js/control-bar/playback-rate-menu/playback-rate-menu-item.js +++ b/src/js/control-bar/playback-rate-menu/playback-rate-menu-item.js @@ -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; diff --git a/src/js/control-bar/progress-control/load-progress-bar.js b/src/js/control-bar/progress-control/load-progress-bar.js index ec20e8afff..2e1a8aae1c 100644 --- a/src/js/control-bar/progress-control/load-progress-bar.js +++ b/src/js/control-bar/progress-control/load-progress-bar.js @@ -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]); } } diff --git a/src/js/control-bar/progress-control/mouse-time-display.js b/src/js/control-bar/progress-control/mouse-time-display.js index e59be2caa4..c72d132adf 100644 --- a/src/js/control-bar/progress-control/mouse-time-display.js +++ b/src/js/control-bar/progress-control/mouse-time-display.js @@ -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) { diff --git a/src/js/control-bar/progress-control/play-progress-bar.js b/src/js/control-bar/progress-control/play-progress-bar.js index 7ddea6cf45..5b29ba862a 100644 --- a/src/js/control-bar/progress-control/play-progress-bar.js +++ b/src/js/control-bar/progress-control/play-progress-bar.js @@ -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())); } diff --git a/src/js/control-bar/progress-control/progress-control.js b/src/js/control-bar/progress-control/progress-control.js index 703989e4db..e142f42e3d 100644 --- a/src/js/control-bar/progress-control/progress-control.js +++ b/src/js/control-bar/progress-control/progress-control.js @@ -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, diff --git a/src/js/control-bar/progress-control/seek-bar.js b/src/js/control-bar/progress-control/seek-bar.js index 21fdc15adb..8fa34694a6 100644 --- a/src/js/control-bar/progress-control/seek-bar.js +++ b/src/js/control-bar/progress-control/seek-bar.js @@ -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'; diff --git a/src/js/control-bar/progress-control/tooltip-progress-bar.js b/src/js/control-bar/progress-control/tooltip-progress-bar.js index f1e092dad3..4a6db351ba 100644 --- a/src/js/control-bar/progress-control/tooltip-progress-bar.js +++ b/src/js/control-bar/progress-control/tooltip-progress-bar.js @@ -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: `
${this.localize('Progress')}: 0%` @@ -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; } diff --git a/src/js/control-bar/spacer-controls/custom-control-spacer.js b/src/js/control-bar/spacer-controls/custom-control-spacer.js index 7327442b4a..a5c5a21f34 100644 --- a/src/js/control-bar/spacer-controls/custom-control-spacer.js +++ b/src/js/control-bar/spacer-controls/custom-control-spacer.js @@ -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 diff --git a/src/js/control-bar/text-track-controls/caption-settings-menu-item.js b/src/js/control-bar/text-track-controls/caption-settings-menu-item.js index 712543f20b..7632802cb8 100644 --- a/src/js/control-bar/text-track-controls/caption-settings-menu-item.js +++ b/src/js/control-bar/text-track-controls/caption-settings-menu-item.js @@ -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'); } /** diff --git a/src/js/control-bar/text-track-controls/captions-button.js b/src/js/control-bar/text-track-controls/captions-button.js index b5ee37cfc9..929c841022 100644 --- a/src/js/control-bar/text-track-controls/captions-button.js +++ b/src/js/control-bar/text-track-controls/captions-button.js @@ -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); diff --git a/src/js/control-bar/text-track-controls/chapters-button.js b/src/js/control-bar/text-track-controls/chapters-button.js index 0cc8606ea8..004be740d2 100644 --- a/src/js/control-bar/text-track-controls/chapters-button.js +++ b/src/js/control-bar/text-track-controls/chapters-button.js @@ -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); diff --git a/src/js/control-bar/text-track-controls/chapters-track-menu-item.js b/src/js/control-bar/text-track-controls/chapters-track-menu-item.js index 40b717a0e4..2291da4c30 100644 --- a/src/js/control-bar/text-track-controls/chapters-track-menu-item.js +++ b/src/js/control-bar/text-track-controls/chapters-track-menu-item.js @@ -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); } } diff --git a/src/js/control-bar/text-track-controls/descriptions-button.js b/src/js/control-bar/text-track-controls/descriptions-button.js index 671c620c56..0b7bd02b45 100644 --- a/src/js/control-bar/text-track-controls/descriptions-button.js +++ b/src/js/control-bar/text-track-controls/descriptions-button.js @@ -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; } diff --git a/src/js/control-bar/text-track-controls/off-text-track-menu-item.js b/src/js/control-bar/text-track-controls/off-text-track-menu-item.js index 9cc124b9df..a23b82bd4d 100644 --- a/src/js/control-bar/text-track-controls/off-text-track-menu-item.js +++ b/src/js/control-bar/text-track-controls/off-text-track-menu-item.js @@ -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; } diff --git a/src/js/control-bar/text-track-controls/subtitles-button.js b/src/js/control-bar/text-track-controls/subtitles-button.js index c56731496e..207e8179d2 100644 --- a/src/js/control-bar/text-track-controls/subtitles-button.js +++ b/src/js/control-bar/text-track-controls/subtitles-button.js @@ -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'); } /** diff --git a/src/js/control-bar/text-track-controls/text-track-button.js b/src/js/control-bar/text-track-controls/text-track-button.js index 1decbeec24..fced8fb3ce 100644 --- a/src/js/control-bar/text-track-controls/text-track-button.js +++ b/src/js/control-bar/text-track-controls/text-track-button.js @@ -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 })); } } diff --git a/src/js/control-bar/text-track-controls/text-track-menu-item.js b/src/js/control-bar/text-track-controls/text-track-menu-item.js index 8b4c73bdad..ccb5b02010 100644 --- a/src/js/control-bar/text-track-controls/text-track-menu-item.js +++ b/src/js/control-bar/text-track-controls/text-track-menu-item.js @@ -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'); } } diff --git a/src/js/control-bar/time-controls/current-time-display.js b/src/js/control-bar/time-controls/current-time-display.js index ff99fc54e6..66a102aeea 100644 --- a/src/js/control-bar/time-controls/current-time-display.js +++ b/src/js/control-bar/time-controls/current-time-display.js @@ -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: 'Current Time ' + '0:00', + innerHTML: 'Current Time ' + '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 = `${localizedText} ${formattedTime}`; diff --git a/src/js/control-bar/time-controls/duration-display.js b/src/js/control-bar/time-controls/duration-display.js index 0496e3cd50..1b2cb27f50 100644 --- a/src/js/control-bar/time-controls/duration-display.js +++ b/src/js/control-bar/time-controls/duration-display.js @@ -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 = `${localizedText} ${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 = `${localizedText} ${formattedTime}`; } } diff --git a/src/js/control-bar/time-controls/remaining-time-display.js b/src/js/control-bar/time-controls/remaining-time-display.js index 8ac701f6b1..40914b7cd1 100644 --- a/src/js/control-bar/time-controls/remaining-time-display.js +++ b/src/js/control-bar/time-controls/remaining-time-display.js @@ -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: `${this.localize('Remaining Time')} -0:00`, + innerHTML: `${this.localize('Remaining Time')} -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 = `${localizedText} -${formattedTime}`; diff --git a/src/js/control-bar/track-button.js b/src/js/control-bar/track-button.js index f482465fa1..4b3056b1a8 100644 --- a/src/js/control-bar/track-button.js +++ b/src/js/control-bar/track-button.js @@ -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); diff --git a/src/js/control-bar/volume-control/volume-bar.js b/src/js/control-bar/volume-control/volume-bar.js index bf775cd6cc..db44eb9e59 100644 --- a/src/js/control-bar/volume-control/volume-bar.js +++ b/src/js/control-bar/volume-control/volume-bar.js @@ -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'; diff --git a/src/js/control-bar/volume-control/volume-control.js b/src/js/control-bar/volume-control/volume-control.js index 2095b3dfac..741b403c17 100644 --- a/src/js/control-bar/volume-control/volume-control.js +++ b/src/js/control-bar/volume-control/volume-control.js @@ -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'); diff --git a/src/js/control-bar/volume-menu-button.js b/src/js/control-bar/volume-menu-button.js index 83879ab5f9..d650a245e4 100644 --- a/src/js/control-bar/volume-menu-button.js +++ b/src/js/control-bar/volume-menu-button.js @@ -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); diff --git a/src/js/error-display.js b/src/js/error-display.js index 36cde55267..fcdd67c4ef 100644 --- a/src/js/error-display.js +++ b/src/js/error-display.js @@ -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) : ''; } } diff --git a/src/js/event-target.js b/src/js/event-target.js index 281d2b99c2..5fe9012583 100644 --- a/src/js/event-target.js +++ b/src/js/event-target.js @@ -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; diff --git a/src/js/extend.js b/src/js/extend.js index 963ca2eda2..1d1acfd8fe 100644 --- a/src/js/extend.js +++ b/src/js/extend.js @@ -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]; } diff --git a/src/js/fullscreen-api.js b/src/js/fullscreen-api.js index b594541ab1..72fa08a626 100644 --- a/src/js/fullscreen-api.js +++ b/src/js/fullscreen-api.js @@ -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 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 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 0) { - for(let i=0, e=vids.length; i 0) { - for(let i=0, e=audios.length; i 0) { - for (let i=0, e=mediaEls.length; i 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 = '`; }); 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]}" `; }); diff --git a/src/js/tech/html5.js b/src/js/tech/html5.js index 8d72e042c9..6b5f1e1420 100644 --- a/src/js/tech/html5.js +++ b/src/js/tech/html5.js @@ -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= 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 + } + }()); } }; diff --git a/src/js/tech/loader.js b/src/js/tech/loader.js index 14bc28c184..5d303e0eef 100644 --- a/src/js/tech/loader.js +++ b/src/js/tech/loader.js @@ -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 { 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) { diff --git a/src/js/tracks/audio-track-list.js b/src/js/tracks/audio-track-list.js index d1499bec29..216469ba39 100644 --- a/src/js/tracks/audio-track-list.js +++ b/src/js/tracks/audio-track-list.js @@ -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: diff --git a/src/js/tracks/audio-track.js b/src/js/tracks/audio-track.js index e5f0f7854a..c5f1afacb4 100644 --- a/src/js/tracks/audio-track.js +++ b/src/js/tracks/audio-track.js @@ -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) { diff --git a/src/js/tracks/html-track-element-list.js b/src/js/tracks/html-track-element-list.js index 9b38d05820..300b446a6a 100644 --- a/src/js/tracks/html-track-element-list.js +++ b/src/js/tracks/html-track-element-list.js @@ -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'); diff --git a/src/js/tracks/html-track-element.js b/src/js/tracks/html-track-element.js index d799375efb..182b56d76c 100644 --- a/src/js/tracks/html-track-element.js +++ b/src/js/tracks/html-track-element.js @@ -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'); diff --git a/src/js/tracks/text-track-cue-list.js b/src/js/tracks/text-track-cue-list.js index 3abd7ed069..dd457cad15 100644 --- a/src/js/tracks/text-track-cue-list.js +++ b/src/js/tracks/text-track-cue-list.js @@ -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'); diff --git a/src/js/tracks/text-track-display.js b/src/js/tracks/text-track-display.js index 40ca875b37..6cf9402d6d 100644 --- a/src/js/tracks/text-track-display.js +++ b/src/js/tracks/text-track-display.js @@ -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; diff --git a/src/js/tracks/text-track-list-converter.js b/src/js/tracks/text-track-list-converter.js index b7ab4dd1f5..8cc3d69874 100644 --- a/src/js/tracks/text-track-list-converter.js +++ b/src/js/tracks/text-track-list-converter.js @@ -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)); } diff --git a/src/js/tracks/text-track-settings.js b/src/js/tracks/text-track-settings.js index 50d845f04b..0baf8c7ff9 100644 --- a/src/js/tracks/text-track-settings.js +++ b/src/js/tracks/text-track-settings.js @@ -8,6 +8,159 @@ import log from '../utils/log.js'; import safeParseTuple from 'safe-json-parse/tuple'; import window from 'global/window'; +function captionOptionsMenuTemplate(uniqueId, dialogLabelId, dialogDescriptionId) { + let template = ` +
+
Captions Settings Dialog
+
Beginning of dialog window. Escape will cancel and close the window.
+
+
+
+ Text + + + + + + +
+
+ Background + + + + + + +
+
+ Window + + + + + + +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+
+
+ `; + + 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 * @@ -76,7 +229,7 @@ class TextTrackSettings extends Component { innerHTML: captionOptionsMenuTemplate(uniqueId, dialogLabelId, dialogDescriptionId), tabIndex: -1 }, { - role: 'dialog', + 'role': 'dialog', 'aria-labelledby': dialogLabelId, 'aria-describedby': dialogDescriptionId }); @@ -106,19 +259,20 @@ class TextTrackSettings extends Component { 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'))); + 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 + 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]; @@ -167,7 +321,8 @@ class TextTrackSettings extends Component { * @method restoreSettings */ restoreSettings() { - let err, values; + let err; + let values; try { [err, values] = safeParseTuple(window.localStorage.getItem('vjs-text-track-settings')); @@ -195,6 +350,7 @@ class TextTrackSettings extends Component { } let values = this.getValues(); + try { if (Object.getOwnPropertyNames(values).length > 0) { window.localStorage.setItem('vjs-text-track-settings', JSON.stringify(values)); @@ -213,6 +369,7 @@ class TextTrackSettings extends Component { */ updateDisplay() { let ttDisplay = this.player_.getChild('textTrackDisplay'); + if (ttDisplay) { ttDisplay.updateDisplay(); } @@ -222,154 +379,4 @@ class TextTrackSettings extends Component { 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 = ` -
-
Captions Settings Dialog
-
Beginning of dialog window. Escape will cancel and close the window.
-
-
-
- Text - - - - - - -
-
- Background - - - - - - -
-
- Window - - - - - - -
-
-
-
- - -
-
- - -
-
- - -
-
-
- - -
-
-
`; - - return template; -} - export default TextTrackSettings; diff --git a/src/js/tracks/text-track.js b/src/js/tracks/text-track.js index 582734f3d7..de58a75d45 100644 --- a/src/js/tracks/text-track.js +++ b/src/js/tracks/text-track.js @@ -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'); diff --git a/src/js/tracks/track-enums.js b/src/js/tracks/track-enums.js index ecab4dd4b3..e11d0d1c81 100644 --- a/src/js/tracks/track-enums.js +++ b/src/js/tracks/track-enums.js @@ -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 */ - diff --git a/src/js/tracks/track-list.js b/src/js/tracks/track-list.js index 3d15e164b3..b61b4c43c5 100644 --- a/src/js/tracks/track-list.js +++ b/src/js/tracks/track-list.js @@ -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; diff --git a/src/js/tracks/track.js b/src/js/tracks/track.js index 8c8a115350..a347fa60d8 100644 --- a/src/js/tracks/track.js +++ b/src/js/tracks/track.js @@ -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() {} }); } diff --git a/src/js/tracks/video-track.js b/src/js/tracks/video-track.js index fe8f172f53..deeb9c2794 100644 --- a/src/js/tracks/video-track.js +++ b/src/js/tracks/video-track.js @@ -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) { diff --git a/src/js/utils/browser.js b/src/js/utils/browser.js index 9bad972b40..70ce027c54 100644 --- a/src/js/utils/browser.js +++ b/src/js/utils/browser.js @@ -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; diff --git a/src/js/utils/buffer.js b/src/js/utils/buffer.js index 063a9ec95c..4a303aaa25 100644 --- a/src/js/utils/buffer.js +++ b/src/js/utils/buffer.js @@ -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) { diff --git a/src/js/utils/dom.js b/src/js/utils/dom.js index 3d8fb51679..4fe03202fe 100644 --- a/src/js/utils/dom.js +++ b/src/js/utils/dom.js @@ -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); diff --git a/src/js/utils/events.js b/src/js/utils/events.js index 4c9ca211d1..ab44f22f49 100644 --- a/src/js/utils/events.js +++ b/src/js/utils/events.js @@ -7,11 +7,193 @@ * 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'; +/** + * Clean up the listener cache and dispatchers +* + * @param {Element|Object} elem Element to clean up + * @param {String} type Type of event to clean up + * @private + * @method _cleanUpEvents + */ +function _cleanUpEvents(elem, type) { + let data = Dom.getElData(elem); + + // Remove the events of a particular type if there are none left + if (data.handlers[type].length === 0) { + delete data.handlers[type]; + // data.handlers[type] = null; + // Setting to null was causing an error with data.handlers + + // Remove the meta-handler from the element + if (elem.removeEventListener) { + elem.removeEventListener(type, data.dispatcher, false); + } else if (elem.detachEvent) { + elem.detachEvent('on' + type, data.dispatcher); + } + } + + // Remove the events object if there are no types left + if (Object.getOwnPropertyNames(data.handlers).length <= 0) { + delete data.handlers; + delete data.dispatcher; + delete data.disabled; + } + + // Finally remove the element data if there is no data left + if (Object.getOwnPropertyNames(data).length === 0) { + Dom.removeElData(elem); + } +} + +/** + * Loops through an array of event types and calls the requested method for each type. + * + * @param {Function} fn The event method we want to use. + * @param {Element|Object} elem Element or object to bind listeners to + * @param {String} type Type of event to bind to. + * @param {Function} callback Event listener. + * @private + * @function _handleMultipleEvents + */ +function _handleMultipleEvents(fn, elem, types, callback) { + types.forEach(function(type) { + // 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 @@ -23,7 +205,7 @@ import document from 'global/document'; * @param {Function} fn Event listener. * @method on */ -export function on(elem, type, fn){ +export function on(elem, type, fn) { if (Array.isArray(type)) { return _handleMultipleEvents(on, elem, type, fn); } @@ -31,29 +213,38 @@ export function 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) { + data.handlers = {}; + } - if (!data.handlers[type]) data.handlers[type] = []; + if (!data.handlers[type]) { + data.handlers[type] = []; + } - if (!fn.guid) fn.guid = Guid.newGUID(); + if (!fn.guid) { + fn.guid = Guid.newGUID(); + } data.handlers[type].push(fn); if (!data.dispatcher) { data.disabled = false; - data.dispatcher = function (event, hash){ + data.dispatcher = function(event, hash) { + + if (data.disabled) { + return; + } - if (data.disabled) return; event = fixEvent(event); - var handlers = data.handlers[event.type]; + 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. - var handlersCopy = handlers.slice(0); + let handlersCopy = handlers.slice(0); - for (var m = 0, n = handlersCopy.length; m < n; m++) { + for (let m = 0, n = handlersCopy.length; m < n; m++) { if (event.isImmediatePropagationStopped()) { break; } else { @@ -83,33 +274,41 @@ export function on(elem, type, fn){ */ export function off(elem, type, fn) { // Don't want to add a cache object through getElData if not needed - if (!Dom.hasElData(elem)) return; + if (!Dom.hasElData(elem)) { + return; + } let data = Dom.getElData(elem); // If no events exist, nothing to unbind - if (!data.handlers) { return; } + 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); + 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); + for (let t in data.handlers) { + removeType(t); + } return; } - var handlers = data.handlers[type]; + let handlers = data.handlers[type]; // If no handlers exist, nothing to unbind - if (!handlers) return; + if (!handlers) { + return; + } // If no listener was provided, remove all listeners for type if (!fn) { @@ -142,14 +341,14 @@ 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; + 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 }; + event = {type: event, target: elem}; } // Normalizes the event properties. event = fixEvent(event); @@ -160,13 +359,13 @@ export function trigger(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); + // 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); + let targetData = Dom.getElData(event.target); // Checks if the target has a default action for this event. if (event.target[event.type]) { @@ -197,182 +396,12 @@ export function one(elem, type, fn) { if (Array.isArray(type)) { return _handleMultipleEvents(one, elem, type, fn); } - var func = function(){ + 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); } - -/** - * 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 -* - * @param {Element|Object} elem Element to clean up - * @param {String} type Type of event to clean up - * @private - * @method _cleanUpEvents - */ -function _cleanUpEvents(elem, type) { - var data = Dom.getElData(elem); - - // Remove the events of a particular type if there are none left - if (data.handlers[type].length === 0) { - delete data.handlers[type]; - // data.handlers[type] = null; - // Setting to null was causing an error with data.handlers - - // Remove the meta-handler from the element - if (elem.removeEventListener) { - elem.removeEventListener(type, data.dispatcher, false); - } else if (elem.detachEvent) { - elem.detachEvent('on' + type, data.dispatcher); - } - } - - // Remove the events object if there are no types left - if (Object.getOwnPropertyNames(data.handlers).length <= 0) { - delete data.handlers; - delete data.dispatcher; - delete data.disabled; - } - - // Finally remove the element data if there is no data left - if (Object.getOwnPropertyNames(data).length === 0) { - Dom.removeElData(elem); - } -} - -/** - * Loops through an array of event types and calls the requested method for each type. - * - * @param {Function} fn The event method we want to use. - * @param {Element|Object} elem Element or object to bind listeners to - * @param {String} type Type of event to bind to. - * @param {Function} callback Event listener. - * @private - * @function _handleMultipleEvents - */ -function _handleMultipleEvents(fn, elem, types, callback) { - types.forEach(function(type) { - //Call the event method for each one of the types - fn(elem, type, callback); - }); -} diff --git a/src/js/utils/fn.js b/src/js/utils/fn.js index 69bbe83ad8..f2d4d411f4 100644 --- a/src/js/utils/fn.js +++ b/src/js/utils/fn.js @@ -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() { diff --git a/src/js/utils/format-time.js b/src/js/utils/format-time.js index dc6182c9c4..bd47c68079 100644 --- a/src/js/utils/format-time.js +++ b/src/js/utils/format-time.js @@ -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); diff --git a/src/js/utils/guid.js b/src/js/utils/guid.js index e832e7088b..3a124d8fc7 100644 --- a/src/js/utils/guid.js +++ b/src/js/utils/guid.js @@ -10,7 +10,7 @@ let _guid = 1; /** * Get the next unique ID * - * @return {String} + * @return {String} * @function newGUID */ export function newGUID() { diff --git a/src/js/utils/log.js b/src/js/utils/log.js index 332cda6e73..fca15985b2 100644 --- a/src/js/utils/log.js +++ b/src/js/utils/log.js @@ -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; diff --git a/src/js/utils/merge-options.js b/src/js/utils/merge-options.js index ac3558a9ed..c5493b2841 100644 --- a/src/js/utils/merge-options.js +++ b/src/js/utils/merge-options.js @@ -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; diff --git a/src/js/utils/stylesheet.js b/src/js/utils/stylesheet.js index b06f2bc3d8..d00214b599 100644 --- a/src/js/utils/stylesheet.js +++ b/src/js/utils/stylesheet.js @@ -2,6 +2,7 @@ import document from 'global/document'; export let createStyleElement = function(className) { let style = document.createElement('style'); + style.className = className; return style; diff --git a/src/js/utils/time-ranges.js b/src/js/utils/time-ranges.js index 1f17619665..f81c71db1c 100644 --- a/src/js/utils/time-ranges.js +++ b/src/js/utils/time-ranges.js @@ -1,37 +1,28 @@ import log from './log.js'; -/** - * @file time-ranges.js - * - * Should create a fake TimeRange object - * Mimics an HTML5 time range instance, which has functions that - * return the start and end times for a range - * TimeRanges are returned by the buffered() method - * - * @param {(Number|Array)} Start of a single range or an array of ranges - * @param {Number} End of a single range - * @private - * @method createTimeRanges - */ -export function createTimeRanges(start, end){ - if (Array.isArray(start)) { - return createTimeRangesObj(start); - } else if (start === undefined || end === undefined) { - return createTimeRangesObj(); +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}).`); } - return createTimeRangesObj([[start, end]]); } -export { createTimeRanges as createTimeRange }; +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){ +function createTimeRangesObj(ranges) { if (ranges === undefined || ranges.length === 0) { return { length: 0, - start: function() { + start() { throw new Error('This TimeRanges object is empty'); }, - end: function() { + end() { throw new Error('This TimeRanges object is empty'); } }; @@ -43,17 +34,26 @@ function createTimeRangesObj(ranges){ }; } -function getRange(fnName, valueIndex, ranges, rangeIndex){ - if (rangeIndex === undefined) { - log.warn(`DEPRECATED: Function '${fnName}' on 'TimeRanges' called without an index argument.`); - rangeIndex = 0; +/** + * @file time-ranges.js + * + * Should create a fake TimeRange object + * Mimics an HTML5 time range instance, which has functions that + * return the start and end times for a range + * TimeRanges are returned by the buffered() method + * + * @param {(Number|Array)} Start of a single range or an array of ranges + * @param {Number} End of a single range + * @private + * @method createTimeRanges + */ +export function createTimeRanges(start, end) { + if (Array.isArray(start)) { + return createTimeRangesObj(start); + } else if (start === undefined || end === undefined) { + return createTimeRangesObj(); } - rangeCheck(fnName, rangeIndex, ranges.length - 1); - return ranges[rangeIndex][valueIndex]; + return createTimeRangesObj([[start, end]]); } -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}).`); - } -} +export { createTimeRanges as createTimeRange }; diff --git a/src/js/utils/to-title-case.js b/src/js/utils/to-title-case.js index f3984f8190..c9f97b88e1 100644 --- a/src/js/utils/to-title-case.js +++ b/src/js/utils/to-title-case.js @@ -8,7 +8,7 @@ * @private * @method toTitleCase */ -function toTitleCase(string){ +function toTitleCase(string) { return string.charAt(0).toUpperCase() + string.slice(1); } diff --git a/src/js/utils/url.js b/src/js/utils/url.js index 6532c28fe4..e0e9b9f2cd 100644 --- a/src/js/utils/url.js +++ b/src/js/utils/url.js @@ -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 = ``; @@ -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 = `x`; 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); diff --git a/src/js/video.js b/src/js/video.js index e9ac832f47..f9fe04c8b6 100644 --- a/src/js/video.js +++ b/src/js/video.js @@ -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;