mirror of
https://github.com/videojs/video.js.git
synced 2025-07-15 01:34:23 +02:00
@misteroneill updated source code to pass linter
This commit is contained in:
committed by
Gary Katsevman
parent
c89b75699e
commit
e85c1c0391
47
.jshintrc
47
.jshintrc
@ -1,47 +0,0 @@
|
||||
{
|
||||
"evil" : true,
|
||||
"validthis": true,
|
||||
"node" : true,
|
||||
"debug" : true,
|
||||
"boss" : true,
|
||||
"expr" : true,
|
||||
"eqnull" : true,
|
||||
"quotmark" : "single",
|
||||
"sub" : true,
|
||||
"trailing" : true,
|
||||
"undef" : true,
|
||||
"laxbreak" : true,
|
||||
"esnext" : true,
|
||||
"eqeqeq" : true,
|
||||
"predef" : [
|
||||
"_V_",
|
||||
"goog",
|
||||
"console",
|
||||
|
||||
"require",
|
||||
"define",
|
||||
"module",
|
||||
"exports",
|
||||
"process",
|
||||
|
||||
"q",
|
||||
"asyncTest",
|
||||
"deepEqual",
|
||||
"equal",
|
||||
"expect",
|
||||
"module",
|
||||
"notDeepEqual",
|
||||
"notEqual",
|
||||
"notStrictEqual",
|
||||
"ok",
|
||||
"throws",
|
||||
"QUnit",
|
||||
"raises",
|
||||
"start",
|
||||
"stop",
|
||||
"strictEqual",
|
||||
"test",
|
||||
"throws",
|
||||
"sinon"
|
||||
]
|
||||
}
|
@ -114,14 +114,6 @@ module.exports = function(grunt) {
|
||||
build: ['build/temp/*'],
|
||||
dist: ['dist/*']
|
||||
},
|
||||
jshint: {
|
||||
src: {
|
||||
src: ['src/js/**/*.js', 'Gruntfile.js', 'test/unit/**/*.js'],
|
||||
options: {
|
||||
jshintrc: '.jshintrc'
|
||||
}
|
||||
}
|
||||
},
|
||||
uglify: {
|
||||
options: {
|
||||
sourceMap: true,
|
||||
@ -160,10 +152,6 @@ module.exports = function(grunt) {
|
||||
skin: {
|
||||
files: ['src/css/**/*'],
|
||||
tasks: ['sass']
|
||||
},
|
||||
jshint: {
|
||||
files: ['src/**/*', 'test/unit/**/*.js', 'Gruntfile.js'],
|
||||
tasks: 'jshint'
|
||||
}
|
||||
},
|
||||
connect: {
|
||||
@ -443,6 +431,14 @@ module.exports = function(grunt) {
|
||||
src: ['build/temp/video.js']
|
||||
}
|
||||
}
|
||||
},
|
||||
shell: {
|
||||
lint: {
|
||||
command: 'vjsstandard',
|
||||
options: {
|
||||
preferLocal: true
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -455,7 +451,6 @@ module.exports = function(grunt) {
|
||||
const buildDependents = [
|
||||
'clean:build',
|
||||
|
||||
'jshint',
|
||||
'browserify:build',
|
||||
'exorcise:build',
|
||||
'concat:novtt',
|
||||
|
12
package.json
12
package.json
@ -14,6 +14,7 @@
|
||||
"homepage": "http://videojs.com",
|
||||
"author": "Steve Heffernan",
|
||||
"scripts": {
|
||||
"lint": "vjsstandard",
|
||||
"test": "grunt test"
|
||||
},
|
||||
"repository": {
|
||||
@ -57,7 +58,6 @@
|
||||
"grunt-contrib-connect": "~0.7.1",
|
||||
"grunt-contrib-copy": "^0.8.0",
|
||||
"grunt-contrib-cssmin": "~0.6.0",
|
||||
"grunt-contrib-jshint": "~0.11.3",
|
||||
"grunt-contrib-less": "~0.6.4",
|
||||
"grunt-contrib-uglify": "^0.8.0",
|
||||
"grunt-contrib-watch": "~0.1.4",
|
||||
@ -66,6 +66,7 @@
|
||||
"grunt-fastly": "^0.1.3",
|
||||
"grunt-github-releaser": "^0.1.17",
|
||||
"grunt-karma": "^0.8.3",
|
||||
"grunt-shell": "^1.3.0",
|
||||
"grunt-version": "~0.3.0",
|
||||
"grunt-videojs-languages": "0.0.4",
|
||||
"grunt-zip": "0.10.2",
|
||||
@ -87,9 +88,10 @@
|
||||
"sinon": "^1.16.1",
|
||||
"time-grunt": "^1.1.1",
|
||||
"uglify-js": "~2.3.6",
|
||||
"videojs-doc-generator": "0.0.1"
|
||||
"videojs-doc-generator": "0.0.1",
|
||||
"videojs-standard": "^5.0.0"
|
||||
},
|
||||
"standard": {
|
||||
"vjsstandard": {
|
||||
"ignore": [
|
||||
"**/Gruntfile.js",
|
||||
"**/build/**",
|
||||
@ -97,7 +99,9 @@
|
||||
"**/docs/**",
|
||||
"**/lang/**",
|
||||
"**/sandbox/**",
|
||||
"**/test/**"
|
||||
"**/test/api/**",
|
||||
"**/test/coverage/**",
|
||||
"**/test/karma.conf.js"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -6,13 +6,17 @@
|
||||
import window from 'global/window';
|
||||
import document from 'global/document';
|
||||
|
||||
if (window.VIDEOJS_NO_BASE_THEME) return;
|
||||
if (window.VIDEOJS_NO_BASE_THEME) {
|
||||
return;
|
||||
}
|
||||
|
||||
const styles = '{{GENERATED_STYLES}}';
|
||||
|
||||
if (styles === '{{GENERATED'+'_STYLES}}');
|
||||
// Don't think we need this as it's a noop?
|
||||
// if (styles === '{{GENERATED'+'_STYLES}}');
|
||||
|
||||
const styleNode = document.createElement('style');
|
||||
|
||||
styleNode.innerHTML = styles;
|
||||
|
||||
document.head.insertBefore(styleNode, document.head.firstChild);
|
||||
|
@ -3,10 +3,7 @@
|
||||
*/
|
||||
import ClickableComponent from './clickable-component.js';
|
||||
import Component from './component';
|
||||
import * as Events from './utils/events.js';
|
||||
import * as Fn from './utils/fn.js';
|
||||
import log from './utils/log.js';
|
||||
import document from 'global/document';
|
||||
import assign from 'object.assign';
|
||||
|
||||
/**
|
||||
@ -32,7 +29,7 @@ class Button extends ClickableComponent {
|
||||
* @return {Element}
|
||||
* @method createEl
|
||||
*/
|
||||
createEl(tag='button', props={}, attributes={}) {
|
||||
createEl(tag = 'button', props = {}, attributes = {}) {
|
||||
props = assign({
|
||||
className: this.buildCSSClass()
|
||||
}, props);
|
||||
@ -53,8 +50,12 @@ class Button extends ClickableComponent {
|
||||
|
||||
// Add attributes for button element
|
||||
attributes = assign({
|
||||
type: 'button', // Necessary since the default button type is "submit"
|
||||
'aria-live': 'polite' // let the screen reader user know that the text of the button may change
|
||||
|
||||
// Necessary since the default button type is "submit"
|
||||
'type': 'button',
|
||||
|
||||
// let the screen reader user know that the text of the button may change
|
||||
'aria-live': 'polite'
|
||||
}, attributes);
|
||||
|
||||
let el = Component.prototype.createEl.call(this, tag, props, attributes);
|
||||
@ -73,8 +74,9 @@ class Button extends ClickableComponent {
|
||||
* @deprecated
|
||||
* @method addChild
|
||||
*/
|
||||
addChild(child, options={}) {
|
||||
addChild(child, options = {}) {
|
||||
let className = this.constructor.name;
|
||||
|
||||
log.warn(`Adding an actionable (user controllable) child to a Button (${className}) is not supported; use a ClickableComponent instead.`);
|
||||
|
||||
// Avoid the error message generated by ClickableComponent's addChild method
|
||||
@ -87,13 +89,15 @@ class Button extends ClickableComponent {
|
||||
* @method handleKeyPress
|
||||
*/
|
||||
handleKeyPress(event) {
|
||||
|
||||
// Ignore Space (32) or Enter (13) key operation, which is handled by the browser for a button.
|
||||
if (event.which === 32 || event.which === 13) {
|
||||
} else {
|
||||
super.handleKeyPress(event); // Pass keypress handling up for unsupported keys
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Pass keypress handling up for unsupported keys
|
||||
super.handleKeyPress(event);
|
||||
}
|
||||
}
|
||||
|
||||
Component.registerComponent('Button', Button);
|
||||
|
@ -39,7 +39,7 @@ class ClickableComponent extends Component {
|
||||
* @return {Element}
|
||||
* @method createEl
|
||||
*/
|
||||
createEl(tag='div', props={}, attributes={}) {
|
||||
createEl(tag = 'div', props = {}, attributes = {}) {
|
||||
props = assign({
|
||||
className: this.buildCSSClass(),
|
||||
tabIndex: 0
|
||||
@ -51,8 +51,10 @@ class ClickableComponent extends Component {
|
||||
|
||||
// Add ARIA attributes for clickable element which is not a native HTML button
|
||||
attributes = assign({
|
||||
role: 'button',
|
||||
'aria-live': 'polite' // let the screen reader user know that the text of the element may change
|
||||
'role': 'button',
|
||||
|
||||
// let the screen reader user know that the text of the element may change
|
||||
'aria-live': 'polite'
|
||||
}, attributes);
|
||||
|
||||
let el = super.createEl(tag, props, attributes);
|
||||
@ -91,9 +93,11 @@ class ClickableComponent extends Component {
|
||||
* @return {String}
|
||||
* @method controlText
|
||||
*/
|
||||
controlText(text, el=this.el()) {
|
||||
if (!text) return this.controlText_ || 'Need Text';
|
||||
|
||||
controlText(text, el = this.el()) {
|
||||
if (!text) {
|
||||
return this.controlText_ || 'Need Text';
|
||||
}
|
||||
|
||||
const localizedText = this.localize(text);
|
||||
|
||||
this.controlText_ = text;
|
||||
@ -121,14 +125,14 @@ class ClickableComponent extends Component {
|
||||
* @return {Component} The child component (created by this process if a string was used)
|
||||
* @method addChild
|
||||
*/
|
||||
addChild(child, options={}) {
|
||||
addChild(child, options = {}) {
|
||||
// TODO: Fix adding an actionable child to a ClickableComponent; currently
|
||||
// it will cause issues with assistive technology (e.g. screen readers)
|
||||
// which support ARIA, since an element with role="button" cannot have
|
||||
// actionable child elements.
|
||||
|
||||
//let className = this.constructor.name;
|
||||
//log.warn(`Adding a child to a ClickableComponent (${className}) can cause issues with assistive technology which supports ARIA, since an element with role="button" cannot have actionable child elements.`);
|
||||
// let className = this.constructor.name;
|
||||
// log.warn(`Adding a child to a ClickableComponent (${className}) can cause issues with assistive technology which supports ARIA, since an element with role="button" cannot have actionable child elements.`);
|
||||
|
||||
return super.addChild(child, options);
|
||||
}
|
||||
@ -179,12 +183,15 @@ class ClickableComponent extends Component {
|
||||
* @method handleKeyPress
|
||||
*/
|
||||
handleKeyPress(event) {
|
||||
|
||||
// Support Space (32) or Enter (13) key operation to fire a click event
|
||||
if (event.which === 32 || event.which === 13) {
|
||||
event.preventDefault();
|
||||
this.handleClick(event);
|
||||
} else if (super.handleKeyPress) {
|
||||
super.handleKeyPress(event); // Pass keypress handling up for unsupported keys
|
||||
|
||||
// Pass keypress handling up for unsupported keys
|
||||
super.handleKeyPress(event);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,6 @@
|
||||
*
|
||||
* Player Component - Base class for all UI objects
|
||||
*/
|
||||
|
||||
import window from 'global/window';
|
||||
import * as Dom from './utils/dom.js';
|
||||
import * as Fn from './utils/fn.js';
|
||||
@ -14,7 +13,6 @@ import toTitleCase from './utils/to-title-case.js';
|
||||
import assign from 'object.assign';
|
||||
import mergeOptions from './utils/merge-options.js';
|
||||
|
||||
|
||||
/**
|
||||
* Base UI Component class
|
||||
* Components are embeddable UI objects that are represented by both a
|
||||
@ -32,7 +30,7 @@ import mergeOptions from './utils/merge-options.js';
|
||||
* ```
|
||||
* Components are also event targets.
|
||||
* ```js
|
||||
* button.on('click', function(){
|
||||
* button.on('click', function() {
|
||||
* console.log('Button Clicked!');
|
||||
* });
|
||||
* button.trigger('customevent');
|
||||
@ -340,7 +338,7 @@ class Component {
|
||||
* @return {Component} The child component (created by this process if a string was used)
|
||||
* @method addChild
|
||||
*/
|
||||
addChild(child, options={}, index=this.children_.length) {
|
||||
addChild(child, options = {}, index = this.children_.length) {
|
||||
let component;
|
||||
let componentName;
|
||||
|
||||
@ -408,6 +406,7 @@ class Component {
|
||||
if (typeof component.el === 'function' && component.el()) {
|
||||
let childNodes = this.contentEl().children;
|
||||
let refNode = childNodes[index] || null;
|
||||
|
||||
this.contentEl().insertBefore(component.el(), refNode);
|
||||
}
|
||||
|
||||
@ -540,6 +539,7 @@ class Component {
|
||||
// If two of the same component are used, different names should be supplied
|
||||
// for each
|
||||
let newChild = this.addChild(name, opts);
|
||||
|
||||
if (newChild) {
|
||||
this[name] = newChild;
|
||||
}
|
||||
@ -563,13 +563,13 @@ class Component {
|
||||
return !workingChildren.some(function(wchild) {
|
||||
if (typeof wchild === 'string') {
|
||||
return child === wchild;
|
||||
} else {
|
||||
return child === wchild.name;
|
||||
}
|
||||
return child === wchild.name;
|
||||
});
|
||||
}))
|
||||
.map((child) => {
|
||||
let name, opts;
|
||||
let name;
|
||||
let opts;
|
||||
|
||||
if (typeof child === 'string') {
|
||||
name = child;
|
||||
@ -587,6 +587,7 @@ class Component {
|
||||
// See https://github.com/videojs/video.js/issues/2772
|
||||
let c = Component.getComponent(child.opts.componentClass ||
|
||||
toTitleCase(child.name));
|
||||
|
||||
return c && !Tech.isTech(c);
|
||||
})
|
||||
.forEach(handleAdd);
|
||||
@ -608,7 +609,7 @@ class Component {
|
||||
/**
|
||||
* Add an event listener to this component's element
|
||||
* ```js
|
||||
* var myFunc = function(){
|
||||
* var myFunc = function() {
|
||||
* var myComponent = this;
|
||||
* // Do something when the event is fired
|
||||
* };
|
||||
@ -797,7 +798,7 @@ class Component {
|
||||
* @return {Component}
|
||||
* @method ready
|
||||
*/
|
||||
ready(fn, sync=false) {
|
||||
ready(fn, sync = false) {
|
||||
if (fn) {
|
||||
if (this.isReady_) {
|
||||
if (sync) {
|
||||
@ -824,14 +825,14 @@ class Component {
|
||||
this.isReady_ = true;
|
||||
|
||||
// Ensure ready is triggerd asynchronously
|
||||
this.setTimeout(function(){
|
||||
this.setTimeout(function() {
|
||||
let readyQueue = this.readyQueue_;
|
||||
|
||||
// Reset Ready Queue
|
||||
this.readyQueue_ = [];
|
||||
|
||||
if (readyQueue && readyQueue.length > 0) {
|
||||
readyQueue.forEach(function(fn){
|
||||
readyQueue.forEach(function(fn) {
|
||||
fn.call(this);
|
||||
}, this);
|
||||
}
|
||||
@ -1106,11 +1107,13 @@ class Component {
|
||||
|
||||
if (typeof window.getComputedStyle === 'function') {
|
||||
const computedStyle = window.getComputedStyle(this.el_);
|
||||
|
||||
computedWidthOrHeight = computedStyle.getPropertyValue(widthOrHeight) || computedStyle[widthOrHeight];
|
||||
} else if (this.el_.currentStyle) {
|
||||
// ie 8 doesn't support computed style, shim it
|
||||
// return clientWidth or clientHeight instead for better accuracy
|
||||
const rule = `offset${toTitleCase(widthOrHeight)}`;
|
||||
|
||||
computedWidthOrHeight = this.el_[rule];
|
||||
}
|
||||
|
||||
@ -1194,7 +1197,7 @@ class Component {
|
||||
// So, if we moved only a small distance, this could still be a tap
|
||||
const xdiff = event.touches[0].pageX - firstTouch.pageX;
|
||||
const ydiff = event.touches[0].pageY - firstTouch.pageY;
|
||||
const touchDistance = Math.sqrt(xdiff * xdiff + ydiff * ydiff);
|
||||
const touchDistance = Math.sqrt(xdiff * xdiff + ydiff * ydiff);
|
||||
|
||||
if (touchDistance > tapMovementThreshold) {
|
||||
couldBeTap = false;
|
||||
|
@ -3,7 +3,6 @@
|
||||
*/
|
||||
import TrackButton from '../track-button.js';
|
||||
import Component from '../../component.js';
|
||||
import * as Fn from '../../utils/fn.js';
|
||||
import AudioTrackMenuItem from './audio-track-menu-item.js';
|
||||
|
||||
/**
|
||||
@ -40,19 +39,19 @@ class AudioTrackButton extends TrackButton {
|
||||
* @method createItems
|
||||
*/
|
||||
createItems(items = []) {
|
||||
let tracks = this.player_.audioTracks && this.player_.audioTracks();
|
||||
const tracks = this.player_.audioTracks && this.player_.audioTracks();
|
||||
|
||||
if (!tracks) {
|
||||
return items;
|
||||
}
|
||||
|
||||
for (let i = 0; i < tracks.length; i++) {
|
||||
let track = tracks[i];
|
||||
const track = tracks[i];
|
||||
|
||||
items.push(new AudioTrackMenuItem(this.player_, {
|
||||
track,
|
||||
// MenuItem is selectable
|
||||
'selectable': true,
|
||||
'track': track
|
||||
selectable: true
|
||||
}));
|
||||
}
|
||||
|
||||
|
@ -15,8 +15,8 @@ import * as Fn from '../../utils/fn.js';
|
||||
*/
|
||||
class AudioTrackMenuItem extends MenuItem {
|
||||
constructor(player, options) {
|
||||
let track = options.track;
|
||||
let tracks = player.audioTracks();
|
||||
const track = options.track;
|
||||
const tracks = player.audioTracks();
|
||||
|
||||
// Modify options for parent MenuItem class's init.
|
||||
options.label = track.label || track.language || 'Unknown';
|
||||
@ -27,7 +27,7 @@ class AudioTrackMenuItem extends MenuItem {
|
||||
this.track = track;
|
||||
|
||||
if (tracks) {
|
||||
let changeHandler = Fn.bind(this, this.handleTracksChange);
|
||||
const changeHandler = Fn.bind(this, this.handleTracksChange);
|
||||
|
||||
tracks.addEventListener('change', changeHandler);
|
||||
this.on('dispose', () => {
|
||||
@ -42,14 +42,16 @@ class AudioTrackMenuItem extends MenuItem {
|
||||
* @method handleClick
|
||||
*/
|
||||
handleClick(event) {
|
||||
let tracks = this.player_.audioTracks();
|
||||
const tracks = this.player_.audioTracks();
|
||||
|
||||
super.handleClick(event);
|
||||
|
||||
if (!tracks) return;
|
||||
if (!tracks) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = 0; i < tracks.length; i++) {
|
||||
let track = tracks[i];
|
||||
const track = tracks[i];
|
||||
|
||||
if (track === this.track) {
|
||||
track.enabled = true;
|
||||
|
@ -4,24 +4,24 @@
|
||||
import Component from '../component.js';
|
||||
|
||||
// Required children
|
||||
import PlayToggle from './play-toggle.js';
|
||||
import CurrentTimeDisplay from './time-controls/current-time-display.js';
|
||||
import DurationDisplay from './time-controls/duration-display.js';
|
||||
import TimeDivider from './time-controls/time-divider.js';
|
||||
import RemainingTimeDisplay from './time-controls/remaining-time-display.js';
|
||||
import LiveDisplay from './live-display.js';
|
||||
import ProgressControl from './progress-control/progress-control.js';
|
||||
import FullscreenToggle from './fullscreen-toggle.js';
|
||||
import VolumeControl from './volume-control/volume-control.js';
|
||||
import VolumeMenuButton from './volume-menu-button.js';
|
||||
import MuteToggle from './mute-toggle.js';
|
||||
import ChaptersButton from './text-track-controls/chapters-button.js';
|
||||
import DescriptionsButton from './text-track-controls/descriptions-button.js';
|
||||
import SubtitlesButton from './text-track-controls/subtitles-button.js';
|
||||
import CaptionsButton from './text-track-controls/captions-button.js';
|
||||
import AudioTrackButton from './audio-track-controls/audio-track-button.js';
|
||||
import PlaybackRateMenuButton from './playback-rate-menu/playback-rate-menu-button.js';
|
||||
import CustomControlSpacer from './spacer-controls/custom-control-spacer.js';
|
||||
import './play-toggle.js';
|
||||
import './time-controls/current-time-display.js';
|
||||
import './time-controls/duration-display.js';
|
||||
import './time-controls/time-divider.js';
|
||||
import './time-controls/remaining-time-display.js';
|
||||
import './live-display.js';
|
||||
import './progress-control/progress-control.js';
|
||||
import './fullscreen-toggle.js';
|
||||
import './volume-control/volume-control.js';
|
||||
import './volume-menu-button.js';
|
||||
import './mute-toggle.js';
|
||||
import './text-track-controls/chapters-button.js';
|
||||
import './text-track-controls/descriptions-button.js';
|
||||
import './text-track-controls/subtitles-button.js';
|
||||
import './text-track-controls/captions-button.js';
|
||||
import './audio-track-controls/audio-track-button.js';
|
||||
import './playback-rate-menu/playback-rate-menu-button.js';
|
||||
import './spacer-controls/custom-control-spacer.js';
|
||||
|
||||
/**
|
||||
* Container of main controls
|
||||
@ -42,7 +42,8 @@ class ControlBar extends Component {
|
||||
className: 'vjs-control-bar',
|
||||
dir: 'ltr'
|
||||
}, {
|
||||
'role': 'group' // The control bar is a group, so it can contain menuitems
|
||||
// The control bar is a group, so it can contain menuitems
|
||||
role: 'group'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ class LiveDisplay extends Component {
|
||||
* @method createEl
|
||||
*/
|
||||
createEl() {
|
||||
var el = super.createEl('div', {
|
||||
const el = super.createEl('div', {
|
||||
className: 'vjs-live-control vjs-control'
|
||||
});
|
||||
|
||||
|
@ -21,14 +21,15 @@ class MuteToggle extends Button {
|
||||
this.on(player, 'volumechange', this.update);
|
||||
|
||||
// hide mute toggle if the current tech doesn't support volume control
|
||||
if (player.tech_ && player.tech_['featuresVolumeControl'] === false) {
|
||||
if (player.tech_ && player.tech_.featuresVolumeControl === false) {
|
||||
this.addClass('vjs-hidden');
|
||||
}
|
||||
|
||||
this.on(player, 'loadstart', function() {
|
||||
this.update(); // We need to update the button to account for a default muted state.
|
||||
// We need to update the button to account for a default muted state.
|
||||
this.update();
|
||||
|
||||
if (player.tech_['featuresVolumeControl'] === false) {
|
||||
if (player.tech_.featuresVolumeControl === false) {
|
||||
this.addClass('vjs-hidden');
|
||||
} else {
|
||||
this.removeClass('vjs-hidden');
|
||||
@ -52,7 +53,7 @@ class MuteToggle extends Button {
|
||||
* @method handleClick
|
||||
*/
|
||||
handleClick() {
|
||||
this.player_.muted( this.player_.muted() ? false : true );
|
||||
this.player_.muted(this.player_.muted() ? false : true);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -61,8 +62,8 @@ class MuteToggle extends Button {
|
||||
* @method update
|
||||
*/
|
||||
update() {
|
||||
var vol = this.player_.volume(),
|
||||
level = 3;
|
||||
const vol = this.player_.volume();
|
||||
let level = 3;
|
||||
|
||||
if (vol === 0 || this.player_.muted()) {
|
||||
level = 0;
|
||||
@ -75,13 +76,14 @@ class MuteToggle extends Button {
|
||||
// Don't rewrite the button text if the actual text doesn't change.
|
||||
// This causes unnecessary and confusing information for screen reader users.
|
||||
// This check is needed because this function gets called every time the volume level is changed.
|
||||
let toMute = this.player_.muted() ? 'Unmute' : 'Mute';
|
||||
const toMute = this.player_.muted() ? 'Unmute' : 'Mute';
|
||||
|
||||
if (this.controlText() !== toMute) {
|
||||
this.controlText(toMute);
|
||||
}
|
||||
|
||||
/* TODO improve muted icon classes */
|
||||
for (var i = 0; i < 4; i++) {
|
||||
// TODO improve muted icon classes
|
||||
for (let i = 0; i < 4; i++) {
|
||||
Dom.removeElClass(this.el_, `vjs-vol-${i}`);
|
||||
}
|
||||
Dom.addElClass(this.el_, `vjs-vol-${level}`);
|
||||
|
@ -14,7 +14,7 @@ import Component from '../component.js';
|
||||
*/
|
||||
class PlayToggle extends Button {
|
||||
|
||||
constructor(player, options){
|
||||
constructor(player, options) {
|
||||
super(player, options);
|
||||
|
||||
this.on(player, 'play', this.handlePlay);
|
||||
@ -52,7 +52,8 @@ class PlayToggle extends Button {
|
||||
handlePlay() {
|
||||
this.removeClass('vjs-paused');
|
||||
this.addClass('vjs-playing');
|
||||
this.controlText('Pause'); // change the button text to "Pause"
|
||||
// change the button text to "Pause"
|
||||
this.controlText('Pause');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -63,7 +64,8 @@ class PlayToggle extends Button {
|
||||
handlePause() {
|
||||
this.removeClass('vjs-playing');
|
||||
this.addClass('vjs-paused');
|
||||
this.controlText('Play'); // change the button text to "Play"
|
||||
// change the button text to "Play"
|
||||
this.controlText('Play');
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ import * as Dom from '../../utils/dom.js';
|
||||
*/
|
||||
class PlaybackRateMenuButton extends MenuButton {
|
||||
|
||||
constructor(player, options){
|
||||
constructor(player, options) {
|
||||
super(player, options);
|
||||
|
||||
this.updateVisibility();
|
||||
@ -34,7 +34,7 @@ class PlaybackRateMenuButton extends MenuButton {
|
||||
* @method createEl
|
||||
*/
|
||||
createEl() {
|
||||
let el = super.createEl();
|
||||
const el = super.createEl();
|
||||
|
||||
this.labelEl_ = Dom.createEl('div', {
|
||||
className: 'vjs-playback-rate-value',
|
||||
@ -63,13 +63,13 @@ class PlaybackRateMenuButton extends MenuButton {
|
||||
* @method createMenu
|
||||
*/
|
||||
createMenu() {
|
||||
let menu = new Menu(this.player());
|
||||
let rates = this.playbackRates();
|
||||
const menu = new Menu(this.player());
|
||||
const rates = this.playbackRates();
|
||||
|
||||
if (rates) {
|
||||
for (let i = rates.length - 1; i >= 0; i--) {
|
||||
menu.addChild(
|
||||
new PlaybackRateMenuItem(this.player(), { 'rate': rates[i] + 'x'})
|
||||
new PlaybackRateMenuItem(this.player(), {rate: rates[i] + 'x'})
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -94,12 +94,13 @@ class PlaybackRateMenuButton extends MenuButton {
|
||||
*/
|
||||
handleClick() {
|
||||
// select next rate option
|
||||
let currentRate = this.player().playbackRate();
|
||||
let rates = this.playbackRates();
|
||||
const currentRate = this.player().playbackRate();
|
||||
const rates = this.playbackRates();
|
||||
|
||||
// this will select first one if the last one currently selected
|
||||
let newRate = rates[0];
|
||||
for (let i = 0; i < rates.length ; i++) {
|
||||
|
||||
for (let i = 0; i < rates.length; i++) {
|
||||
if (rates[i] > currentRate) {
|
||||
newRate = rates[i];
|
||||
break;
|
||||
@ -115,7 +116,7 @@ class PlaybackRateMenuButton extends MenuButton {
|
||||
* @method playbackRates
|
||||
*/
|
||||
playbackRates() {
|
||||
return this.options_['playbackRates'] || (this.options_.playerOptions && this.options_.playerOptions['playbackRates']);
|
||||
return this.options_.playbackRates || (this.options_.playerOptions && this.options_.playerOptions.playbackRates);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -126,10 +127,10 @@ class PlaybackRateMenuButton extends MenuButton {
|
||||
* @method playbackRateSupported
|
||||
*/
|
||||
playbackRateSupported() {
|
||||
return this.player().tech_
|
||||
&& this.player().tech_['featuresPlaybackRate']
|
||||
&& this.playbackRates()
|
||||
&& this.playbackRates().length > 0
|
||||
return this.player().tech_ &&
|
||||
this.player().tech_.featuresPlaybackRate &&
|
||||
this.playbackRates() &&
|
||||
this.playbackRates().length > 0
|
||||
;
|
||||
}
|
||||
|
||||
|
@ -14,13 +14,13 @@ import Component from '../../component.js';
|
||||
*/
|
||||
class PlaybackRateMenuItem extends MenuItem {
|
||||
|
||||
constructor(player, options){
|
||||
let label = options['rate'];
|
||||
let rate = parseFloat(label, 10);
|
||||
constructor(player, options) {
|
||||
const label = options.rate;
|
||||
const rate = parseFloat(label, 10);
|
||||
|
||||
// Modify options for parent MenuItem class's init.
|
||||
options['label'] = label;
|
||||
options['selected'] = rate === 1;
|
||||
options.label = label;
|
||||
options.selected = rate === 1;
|
||||
super(player, options);
|
||||
|
||||
this.label = label;
|
||||
|
@ -14,7 +14,7 @@ import * as Dom from '../../utils/dom.js';
|
||||
*/
|
||||
class LoadProgressBar extends Component {
|
||||
|
||||
constructor(player, options){
|
||||
constructor(player, options) {
|
||||
super(player, options);
|
||||
this.on(player, 'progress', this.update);
|
||||
}
|
||||
@ -38,14 +38,16 @@ class LoadProgressBar extends Component {
|
||||
* @method update
|
||||
*/
|
||||
update() {
|
||||
let buffered = this.player_.buffered();
|
||||
let duration = this.player_.duration();
|
||||
let bufferedEnd = this.player_.bufferedEnd();
|
||||
let children = this.el_.children;
|
||||
const buffered = this.player_.buffered();
|
||||
const duration = this.player_.duration();
|
||||
const bufferedEnd = this.player_.bufferedEnd();
|
||||
const children = this.el_.children;
|
||||
|
||||
// get the percent width of a time compared to the total end
|
||||
let percentify = function (time, end){
|
||||
let percent = (time / end) || 0; // no NaN
|
||||
const percentify = function(time, end) {
|
||||
// no NaN
|
||||
const percent = (time / end) || 0;
|
||||
|
||||
return ((percent >= 1 ? 1 : percent) * 100) + '%';
|
||||
};
|
||||
|
||||
@ -54,8 +56,8 @@ class LoadProgressBar extends Component {
|
||||
|
||||
// add child elements to represent the individual buffered time ranges
|
||||
for (let i = 0; i < buffered.length; i++) {
|
||||
let start = buffered.start(i);
|
||||
let end = buffered.end(i);
|
||||
const start = buffered.start(i);
|
||||
const end = buffered.end(i);
|
||||
let part = children[i];
|
||||
|
||||
if (!part) {
|
||||
@ -69,7 +71,7 @@ class LoadProgressBar extends Component {
|
||||
|
||||
// remove unused buffered range elements
|
||||
for (let i = children.length; i > buffered.length; i--) {
|
||||
this.el_.removeChild(children[i-1]);
|
||||
this.el_.removeChild(children[i - 1]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -55,24 +55,24 @@ class MouseTimeDisplay extends Component {
|
||||
}
|
||||
|
||||
handleMouseMove(event) {
|
||||
let duration = this.player_.duration();
|
||||
let newTime = this.calculateDistance(event) * duration;
|
||||
let position = event.pageX - Dom.findElPosition(this.el().parentNode).left;
|
||||
const duration = this.player_.duration();
|
||||
const newTime = this.calculateDistance(event) * duration;
|
||||
const position = event.pageX - Dom.findElPosition(this.el().parentNode).left;
|
||||
|
||||
this.update(newTime, position);
|
||||
}
|
||||
|
||||
update(newTime, position) {
|
||||
let time = formatTime(newTime, this.player_.duration());
|
||||
const time = formatTime(newTime, this.player_.duration());
|
||||
|
||||
this.el().style.left = position + 'px';
|
||||
this.el().setAttribute('data-current-time', time);
|
||||
|
||||
if (this.keepTooltipsInside) {
|
||||
let clampedPosition = this.clampPosition_(position);
|
||||
let difference = position - clampedPosition + 1;
|
||||
let tooltipWidth = parseFloat(window.getComputedStyle(this.tooltip).width);
|
||||
let tooltipWidthHalf = tooltipWidth / 2;
|
||||
const clampedPosition = this.clampPosition_(position);
|
||||
const difference = position - clampedPosition + 1;
|
||||
const tooltipWidth = parseFloat(window.getComputedStyle(this.tooltip).width);
|
||||
const tooltipWidthHalf = tooltipWidth / 2;
|
||||
|
||||
this.tooltip.innerHTML = time;
|
||||
this.tooltip.style.right = `-${tooltipWidthHalf - difference}px`;
|
||||
@ -98,9 +98,9 @@ class MouseTimeDisplay extends Component {
|
||||
return position;
|
||||
}
|
||||
|
||||
let playerWidth = parseFloat(window.getComputedStyle(this.player().el()).width);
|
||||
let tooltipWidth = parseFloat(window.getComputedStyle(this.tooltip).width);
|
||||
let tooltipWidthHalf = tooltipWidth / 2;
|
||||
const playerWidth = parseFloat(window.getComputedStyle(this.player().el()).width);
|
||||
const tooltipWidth = parseFloat(window.getComputedStyle(this.tooltip).width);
|
||||
const tooltipWidthHalf = tooltipWidth / 2;
|
||||
let actualPosition = position;
|
||||
|
||||
if (position < tooltipWidthHalf) {
|
||||
|
@ -3,7 +3,6 @@
|
||||
*/
|
||||
import Component from '../../component.js';
|
||||
import * as Fn from '../../utils/fn.js';
|
||||
import * as Dom from '../../utils/dom.js';
|
||||
import formatTime from '../../utils/format-time.js';
|
||||
|
||||
/**
|
||||
@ -16,7 +15,7 @@ import formatTime from '../../utils/format-time.js';
|
||||
*/
|
||||
class PlayProgressBar extends Component {
|
||||
|
||||
constructor(player, options){
|
||||
constructor(player, options) {
|
||||
super(player, options);
|
||||
this.updateDataAttr();
|
||||
this.on(player, 'timeupdate', this.updateDataAttr);
|
||||
@ -48,7 +47,8 @@ class PlayProgressBar extends Component {
|
||||
}
|
||||
|
||||
updateDataAttr() {
|
||||
let time = (this.player_.scrubbing()) ? this.player_.getCache().currentTime : this.player_.currentTime();
|
||||
const time = (this.player_.scrubbing()) ? this.player_.getCache().currentTime : this.player_.currentTime();
|
||||
|
||||
this.el_.setAttribute('data-current-time', formatTime(time, this.player_.duration()));
|
||||
}
|
||||
|
||||
|
@ -2,8 +2,9 @@
|
||||
* @file progress-control.js
|
||||
*/
|
||||
import Component from '../../component.js';
|
||||
import SeekBar from './seek-bar.js';
|
||||
import MouseTimeDisplay from './mouse-time-display.js';
|
||||
|
||||
import './seek-bar.js';
|
||||
import './mouse-time-display.js';
|
||||
|
||||
/**
|
||||
* The Progress Control component contains the seek bar, load progress,
|
||||
|
@ -4,12 +4,12 @@
|
||||
import window from 'global/window';
|
||||
import Slider from '../../slider/slider.js';
|
||||
import Component from '../../component.js';
|
||||
import LoadProgressBar from './load-progress-bar.js';
|
||||
import PlayProgressBar from './play-progress-bar.js';
|
||||
import TooltipProgressBar from './tooltip-progress-bar.js';
|
||||
import * as Fn from '../../utils/fn.js';
|
||||
import formatTime from '../../utils/format-time.js';
|
||||
import assign from 'object.assign';
|
||||
|
||||
import './load-progress-bar.js';
|
||||
import './play-progress-bar.js';
|
||||
import './tooltip-progress-bar.js';
|
||||
|
||||
/**
|
||||
* Seek Bar and holder for the progress bars
|
||||
@ -21,7 +21,7 @@ import assign from 'object.assign';
|
||||
*/
|
||||
class SeekBar extends Slider {
|
||||
|
||||
constructor(player, options){
|
||||
constructor(player, options) {
|
||||
super(player, options);
|
||||
this.on(player, 'timeupdate', this.updateProgress);
|
||||
this.on(player, 'ended', this.updateProgress);
|
||||
@ -65,9 +65,10 @@ class SeekBar extends Slider {
|
||||
this.updateAriaAttributes(this.tooltipProgressBar.el_);
|
||||
this.tooltipProgressBar.el_.style.width = this.bar.el_.style.width;
|
||||
|
||||
let playerWidth = parseFloat(window.getComputedStyle(this.player().el()).width);
|
||||
let tooltipWidth = parseFloat(window.getComputedStyle(this.tooltipProgressBar.tooltip).width);
|
||||
let tooltipStyle = this.tooltipProgressBar.el().style;
|
||||
const playerWidth = parseFloat(window.getComputedStyle(this.player().el()).width);
|
||||
const tooltipWidth = parseFloat(window.getComputedStyle(this.tooltipProgressBar.tooltip).width);
|
||||
const tooltipStyle = this.tooltipProgressBar.el().style;
|
||||
|
||||
tooltipStyle.maxWidth = Math.floor(playerWidth - (tooltipWidth / 2)) + 'px';
|
||||
tooltipStyle.minWidth = Math.ceil(tooltipWidth / 2) + 'px';
|
||||
tooltipStyle.right = `-${tooltipWidth / 2}px`;
|
||||
@ -76,9 +77,12 @@ class SeekBar extends Slider {
|
||||
|
||||
updateAriaAttributes(el) {
|
||||
// Allows for smooth scrubbing, when player can't keep up.
|
||||
let time = (this.player_.scrubbing()) ? this.player_.getCache().currentTime : this.player_.currentTime();
|
||||
el.setAttribute('aria-valuenow', (this.getPercent() * 100).toFixed(2)); // machine readable value of progress bar (percentage complete)
|
||||
el.setAttribute('aria-valuetext', formatTime(time, this.player_.duration())); // human readable value of progress bar (time complete)
|
||||
const time = (this.player_.scrubbing()) ? this.player_.getCache().currentTime : this.player_.currentTime();
|
||||
|
||||
// machine readable value of progress bar (percentage complete)
|
||||
el.setAttribute('aria-valuenow', (this.getPercent() * 100).toFixed(2));
|
||||
// human readable value of progress bar (time complete)
|
||||
el.setAttribute('aria-valuetext', formatTime(time, this.player_.duration()));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -88,7 +92,8 @@ class SeekBar extends Slider {
|
||||
* @method getPercent
|
||||
*/
|
||||
getPercent() {
|
||||
let percent = this.player_.currentTime() / this.player_.duration();
|
||||
const percent = this.player_.currentTime() / this.player_.duration();
|
||||
|
||||
return percent >= 1 ? 1 : percent;
|
||||
}
|
||||
|
||||
@ -115,7 +120,9 @@ class SeekBar extends Slider {
|
||||
let newTime = this.calculateDistance(event) * this.player_.duration();
|
||||
|
||||
// Don't let video end while scrubbing.
|
||||
if (newTime === this.player_.duration()) { newTime = newTime - 0.1; }
|
||||
if (newTime === this.player_.duration()) {
|
||||
newTime = newTime - 0.1;
|
||||
}
|
||||
|
||||
// Set new time (tell player to seek to new time)
|
||||
this.player_.currentTime(newTime);
|
||||
@ -141,7 +148,8 @@ class SeekBar extends Slider {
|
||||
* @method stepForward
|
||||
*/
|
||||
stepForward() {
|
||||
this.player_.currentTime(this.player_.currentTime() + 5); // more quickly fast forward for keyboard-only users
|
||||
// more quickly fast forward for keyboard-only users
|
||||
this.player_.currentTime(this.player_.currentTime() + 5);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -150,7 +158,8 @@ class SeekBar extends Slider {
|
||||
* @method stepBack
|
||||
*/
|
||||
stepBack() {
|
||||
this.player_.currentTime(this.player_.currentTime() - 5); // more quickly rewind for keyboard-only users
|
||||
// more quickly rewind for keyboard-only users
|
||||
this.player_.currentTime(this.player_.currentTime() - 5);
|
||||
}
|
||||
|
||||
}
|
||||
@ -161,7 +170,7 @@ SeekBar.prototype.options_ = {
|
||||
'mouseTimeDisplay',
|
||||
'playProgressBar'
|
||||
],
|
||||
'barName': 'playProgressBar'
|
||||
barName: 'playProgressBar'
|
||||
};
|
||||
|
||||
SeekBar.prototype.playerEvent = 'timeupdate';
|
||||
|
@ -3,7 +3,6 @@
|
||||
*/
|
||||
import Component from '../../component.js';
|
||||
import * as Fn from '../../utils/fn.js';
|
||||
import * as Dom from '../../utils/dom.js';
|
||||
import formatTime from '../../utils/format-time.js';
|
||||
|
||||
/**
|
||||
@ -16,7 +15,7 @@ import formatTime from '../../utils/format-time.js';
|
||||
*/
|
||||
class TooltipProgressBar extends Component {
|
||||
|
||||
constructor(player, options){
|
||||
constructor(player, options) {
|
||||
super(player, options);
|
||||
this.updateDataAttr();
|
||||
this.on(player, 'timeupdate', this.updateDataAttr);
|
||||
@ -30,7 +29,7 @@ class TooltipProgressBar extends Component {
|
||||
* @method createEl
|
||||
*/
|
||||
createEl() {
|
||||
let el = super.createEl('div', {
|
||||
const el = super.createEl('div', {
|
||||
className: 'vjs-tooltip-progress-bar vjs-slider-bar',
|
||||
innerHTML: `<div class="vjs-time-tooltip"></div>
|
||||
<span class="vjs-control-text"><span>${this.localize('Progress')}</span>: 0%</span>`
|
||||
@ -42,8 +41,9 @@ class TooltipProgressBar extends Component {
|
||||
}
|
||||
|
||||
updateDataAttr() {
|
||||
let time = (this.player_.scrubbing()) ? this.player_.getCache().currentTime : this.player_.currentTime();
|
||||
let formattedTime = formatTime(time, this.player_.duration());
|
||||
const time = (this.player_.scrubbing()) ? this.player_.getCache().currentTime : this.player_.currentTime();
|
||||
const formattedTime = formatTime(time, this.player_.duration());
|
||||
|
||||
this.el_.setAttribute('data-current-time', formattedTime);
|
||||
this.tooltip.innerHTML = formattedTime;
|
||||
}
|
||||
|
@ -29,8 +29,8 @@ class CustomControlSpacer extends Spacer {
|
||||
* @method createEl
|
||||
*/
|
||||
createEl() {
|
||||
let el = super.createEl({
|
||||
className: this.buildCSSClass(),
|
||||
const el = super.createEl({
|
||||
className: this.buildCSSClass()
|
||||
});
|
||||
|
||||
// No-flex/table-cell mode requires there be some content
|
||||
|
@ -12,24 +12,24 @@ import Component from '../../component.js';
|
||||
* @extends TextTrackMenuItem
|
||||
* @class CaptionSettingsMenuItem
|
||||
*/
|
||||
class CaptionSettingsMenuItem extends TextTrackMenuItem {
|
||||
class CaptionSettingsMenuItem extends TextTrackMenuItem {
|
||||
|
||||
constructor(player, options) {
|
||||
options['track'] = {
|
||||
'kind': options['kind'],
|
||||
'player': player,
|
||||
'label': options['kind'] + ' settings',
|
||||
'selectable': false,
|
||||
'default': false,
|
||||
options.track = {
|
||||
player,
|
||||
kind: options.kind,
|
||||
label: options.kind + ' settings',
|
||||
selectable: false,
|
||||
default: false,
|
||||
mode: 'disabled'
|
||||
};
|
||||
|
||||
// CaptionSettingsMenuItem has no concept of 'selected'
|
||||
options['selectable'] = false;
|
||||
options.selectable = false;
|
||||
|
||||
super(player, options);
|
||||
this.addClass('vjs-texttrack-settings');
|
||||
this.controlText(', opens ' + options['kind'] + ' settings dialog');
|
||||
this.controlText(', opens ' + options.kind + ' settings dialog');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -16,9 +16,9 @@ import CaptionSettingsMenuItem from './caption-settings-menu-item.js';
|
||||
*/
|
||||
class CaptionsButton extends TextTrackButton {
|
||||
|
||||
constructor(player, options, ready){
|
||||
constructor(player, options, ready) {
|
||||
super(player, options, ready);
|
||||
this.el_.setAttribute('aria-label','Captions Menu');
|
||||
this.el_.setAttribute('aria-label', 'Captions Menu');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -38,10 +38,11 @@ class CaptionsButton extends TextTrackButton {
|
||||
*/
|
||||
update() {
|
||||
let threshold = 2;
|
||||
|
||||
super.update();
|
||||
|
||||
// if native, then threshold is 1 because no settings button
|
||||
if (this.player().tech_ && this.player().tech_['featuresNativeTextTracks']) {
|
||||
if (this.player().tech_ && this.player().tech_.featuresNativeTextTracks) {
|
||||
threshold = 1;
|
||||
}
|
||||
|
||||
@ -59,10 +60,10 @@ class CaptionsButton extends TextTrackButton {
|
||||
* @method createItems
|
||||
*/
|
||||
createItems() {
|
||||
let items = [];
|
||||
const items = [];
|
||||
|
||||
if (!(this.player().tech_ && this.player().tech_['featuresNativeTextTracks'])) {
|
||||
items.push(new CaptionSettingsMenuItem(this.player_, { 'kind': this.kind_ }));
|
||||
if (!(this.player().tech_ && this.player().tech_.featuresNativeTextTracks)) {
|
||||
items.push(new CaptionSettingsMenuItem(this.player_, {kind: this.kind_}));
|
||||
}
|
||||
|
||||
return super.createItems(items);
|
||||
|
@ -7,9 +7,7 @@ import TextTrackMenuItem from './text-track-menu-item.js';
|
||||
import ChaptersTrackMenuItem from './chapters-track-menu-item.js';
|
||||
import Menu from '../../menu/menu.js';
|
||||
import * as Dom from '../../utils/dom.js';
|
||||
import * as Fn from '../../utils/fn.js';
|
||||
import toTitleCase from '../../utils/to-title-case.js';
|
||||
import window from 'global/window';
|
||||
|
||||
/**
|
||||
* The button component for toggling and selecting chapters
|
||||
@ -24,9 +22,9 @@ import window from 'global/window';
|
||||
*/
|
||||
class ChaptersButton extends TextTrackButton {
|
||||
|
||||
constructor(player, options, ready){
|
||||
constructor(player, options, ready) {
|
||||
super(player, options, ready);
|
||||
this.el_.setAttribute('aria-label','Chapters Menu');
|
||||
this.el_.setAttribute('aria-label', 'Chapters Menu');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -46,20 +44,18 @@ class ChaptersButton extends TextTrackButton {
|
||||
* @method createItems
|
||||
*/
|
||||
createItems() {
|
||||
let items = [];
|
||||
|
||||
let tracks = this.player_.textTracks();
|
||||
const items = [];
|
||||
const tracks = this.player_.textTracks();
|
||||
|
||||
if (!tracks) {
|
||||
return items;
|
||||
}
|
||||
|
||||
for (let i = 0; i < tracks.length; i++) {
|
||||
let track = tracks[i];
|
||||
if (track['kind'] === this.kind_) {
|
||||
items.push(new TextTrackMenuItem(this.player_, {
|
||||
'track': track
|
||||
}));
|
||||
const track = tracks[i];
|
||||
|
||||
if (track.kind === this.kind_) {
|
||||
items.push(new TextTrackMenuItem(this.player_, {track}));
|
||||
}
|
||||
}
|
||||
|
||||
@ -73,16 +69,16 @@ class ChaptersButton extends TextTrackButton {
|
||||
* @method createMenu
|
||||
*/
|
||||
createMenu() {
|
||||
let tracks = this.player_.textTracks() || [];
|
||||
const tracks = this.player_.textTracks() || [];
|
||||
let chaptersTrack;
|
||||
let items = this.items || [];
|
||||
|
||||
for (let i = tracks.length - 1; i >= 0; i--) {
|
||||
|
||||
// We will always choose the last track as our chaptersTrack
|
||||
let track = tracks[i];
|
||||
const track = tracks[i];
|
||||
|
||||
if (track['kind'] === this.kind_) {
|
||||
if (track.kind === this.kind_) {
|
||||
chaptersTrack = track;
|
||||
|
||||
break;
|
||||
@ -90,27 +86,30 @@ class ChaptersButton extends TextTrackButton {
|
||||
}
|
||||
|
||||
let menu = this.menu;
|
||||
|
||||
if (menu === undefined) {
|
||||
menu = new Menu(this.player_);
|
||||
let title = Dom.createEl('li', {
|
||||
|
||||
const title = Dom.createEl('li', {
|
||||
className: 'vjs-menu-title',
|
||||
innerHTML: toTitleCase(this.kind_),
|
||||
tabIndex: -1
|
||||
});
|
||||
|
||||
menu.children_.unshift(title);
|
||||
Dom.insertElFirst(title, menu.contentEl());
|
||||
} else {
|
||||
// We will empty out the menu children each time because we want a
|
||||
// fresh new menu child list each time
|
||||
items.forEach(item => menu.removeChild(item));
|
||||
// Empty out the ChaptersButton menu items because we no longer need them
|
||||
items = [];
|
||||
// We will empty out the menu children each time because we want a
|
||||
// fresh new menu child list each time
|
||||
items.forEach(item => menu.removeChild(item));
|
||||
// Empty out the ChaptersButton menu items because we no longer need them
|
||||
items = [];
|
||||
}
|
||||
|
||||
if (chaptersTrack && chaptersTrack.cues == null) {
|
||||
chaptersTrack['mode'] = 'hidden';
|
||||
if (chaptersTrack && (chaptersTrack.cues === null || chaptersTrack.cues === undefined)) {
|
||||
chaptersTrack.mode = 'hidden';
|
||||
|
||||
let remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(chaptersTrack);
|
||||
const remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(chaptersTrack);
|
||||
|
||||
if (remoteTextTrackEl) {
|
||||
remoteTextTrackEl.addEventListener('load', (event) => this.update());
|
||||
@ -118,14 +117,14 @@ class ChaptersButton extends TextTrackButton {
|
||||
}
|
||||
|
||||
if (chaptersTrack && chaptersTrack.cues && chaptersTrack.cues.length > 0) {
|
||||
let cues = chaptersTrack['cues'], cue;
|
||||
const cues = chaptersTrack.cues;
|
||||
|
||||
for (let i = 0, l = cues.length; i < l; i++) {
|
||||
cue = cues[i];
|
||||
const cue = cues[i];
|
||||
|
||||
let mi = new ChaptersTrackMenuItem(this.player_, {
|
||||
'track': chaptersTrack,
|
||||
'cue': cue
|
||||
const mi = new ChaptersTrackMenuItem(this.player_, {
|
||||
cue,
|
||||
track: chaptersTrack
|
||||
});
|
||||
|
||||
items.push(mi);
|
||||
|
@ -15,14 +15,14 @@ import * as Fn from '../../utils/fn.js';
|
||||
*/
|
||||
class ChaptersTrackMenuItem extends MenuItem {
|
||||
|
||||
constructor(player, options){
|
||||
let track = options['track'];
|
||||
let cue = options['cue'];
|
||||
let currentTime = player.currentTime();
|
||||
constructor(player, options) {
|
||||
const track = options.track;
|
||||
const cue = options.cue;
|
||||
const currentTime = player.currentTime();
|
||||
|
||||
// Modify options for parent MenuItem class's init.
|
||||
options['label'] = cue.text;
|
||||
options['selected'] = (cue['startTime'] <= currentTime && currentTime < cue['endTime']);
|
||||
options.label = cue.text;
|
||||
options.selected = (cue.startTime <= currentTime && currentTime < cue.endTime);
|
||||
super(player, options);
|
||||
|
||||
this.track = track;
|
||||
@ -47,11 +47,11 @@ class ChaptersTrackMenuItem extends MenuItem {
|
||||
* @method update
|
||||
*/
|
||||
update() {
|
||||
let cue = this.cue;
|
||||
let currentTime = this.player_.currentTime();
|
||||
const cue = this.cue;
|
||||
const currentTime = this.player_.currentTime();
|
||||
|
||||
// vjs.log(currentTime, cue.startTime);
|
||||
this.selected(cue['startTime'] <= currentTime && currentTime < cue['endTime']);
|
||||
this.selected(cue.startTime <= currentTime && currentTime < cue.endTime);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -16,14 +16,14 @@ import * as Fn from '../../utils/fn.js';
|
||||
*/
|
||||
class DescriptionsButton extends TextTrackButton {
|
||||
|
||||
constructor(player, options, ready){
|
||||
constructor(player, options, ready) {
|
||||
super(player, options, ready);
|
||||
this.el_.setAttribute('aria-label', 'Descriptions Menu');
|
||||
|
||||
let tracks = player.textTracks();
|
||||
const tracks = player.textTracks();
|
||||
|
||||
if (tracks) {
|
||||
let changeHandler = Fn.bind(this, this.handleTracksChange);
|
||||
const changeHandler = Fn.bind(this, this.handleTracksChange);
|
||||
|
||||
tracks.addEventListener('change', changeHandler);
|
||||
this.on('dispose', function() {
|
||||
@ -37,14 +37,15 @@ class DescriptionsButton extends TextTrackButton {
|
||||
*
|
||||
* @method handleTracksChange
|
||||
*/
|
||||
handleTracksChange(event){
|
||||
let tracks = this.player().textTracks();
|
||||
handleTracksChange(event) {
|
||||
const tracks = this.player().textTracks();
|
||||
let disabled = false;
|
||||
|
||||
// Check whether a track of a different kind is showing
|
||||
for (let i = 0, l = tracks.length; i < l; i++) {
|
||||
let track = tracks[i];
|
||||
if (track['kind'] !== this.kind_ && track['mode'] === 'showing') {
|
||||
const track = tracks[i];
|
||||
|
||||
if (track.kind !== this.kind_ && track.mode === 'showing') {
|
||||
disabled = true;
|
||||
break;
|
||||
}
|
||||
|
@ -14,19 +14,19 @@ import Component from '../../component.js';
|
||||
*/
|
||||
class OffTextTrackMenuItem extends TextTrackMenuItem {
|
||||
|
||||
constructor(player, options){
|
||||
constructor(player, options) {
|
||||
// Create pseudo track info
|
||||
// Requires options['kind']
|
||||
options['track'] = {
|
||||
'kind': options['kind'],
|
||||
'player': player,
|
||||
'label': options['kind'] + ' off',
|
||||
'default': false,
|
||||
'mode': 'disabled'
|
||||
options.track = {
|
||||
player,
|
||||
kind: options.kind,
|
||||
label: options.kind + ' off',
|
||||
default: false,
|
||||
mode: 'disabled'
|
||||
};
|
||||
|
||||
// MenuItem is selectable
|
||||
options['selectable'] = true;
|
||||
options.selectable = true;
|
||||
|
||||
super(player, options);
|
||||
this.selected(true);
|
||||
@ -38,13 +38,14 @@ class OffTextTrackMenuItem extends TextTrackMenuItem {
|
||||
* @param {Object} event Event object
|
||||
* @method handleTracksChange
|
||||
*/
|
||||
handleTracksChange(event){
|
||||
let tracks = this.player().textTracks();
|
||||
handleTracksChange(event) {
|
||||
const tracks = this.player().textTracks();
|
||||
let selected = true;
|
||||
|
||||
for (let i = 0, l = tracks.length; i < l; i++) {
|
||||
let track = tracks[i];
|
||||
if (track['kind'] === this.track['kind'] && track['mode'] === 'showing') {
|
||||
const track = tracks[i];
|
||||
|
||||
if (track.kind === this.track.kind && track.mode === 'showing') {
|
||||
selected = false;
|
||||
break;
|
||||
}
|
||||
|
@ -15,9 +15,9 @@ import Component from '../../component.js';
|
||||
*/
|
||||
class SubtitlesButton extends TextTrackButton {
|
||||
|
||||
constructor(player, options, ready){
|
||||
constructor(player, options, ready) {
|
||||
super(player, options, ready);
|
||||
this.el_.setAttribute('aria-label','Subtitles Menu');
|
||||
this.el_.setAttribute('aria-label', 'Subtitles Menu');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -3,7 +3,6 @@
|
||||
*/
|
||||
import TrackButton from '../track-button.js';
|
||||
import Component from '../../component.js';
|
||||
import * as Fn from '../../utils/fn.js';
|
||||
import TextTrackMenuItem from './text-track-menu-item.js';
|
||||
import OffTextTrackMenuItem from './off-text-track-menu-item.js';
|
||||
|
||||
@ -17,7 +16,7 @@ import OffTextTrackMenuItem from './off-text-track-menu-item.js';
|
||||
*/
|
||||
class TextTrackButton extends TrackButton {
|
||||
|
||||
constructor(player, options = {}){
|
||||
constructor(player, options = {}) {
|
||||
options.tracks = player.textTracks();
|
||||
|
||||
super(player, options);
|
||||
@ -29,25 +28,25 @@ class TextTrackButton extends TrackButton {
|
||||
* @return {Array} Array of menu items
|
||||
* @method createItems
|
||||
*/
|
||||
createItems(items=[]) {
|
||||
createItems(items = []) {
|
||||
// Add an OFF menu item to turn all tracks off
|
||||
items.push(new OffTextTrackMenuItem(this.player_, { 'kind': this.kind_ }));
|
||||
items.push(new OffTextTrackMenuItem(this.player_, {kind: this.kind_}));
|
||||
|
||||
let tracks = this.player_.textTracks();
|
||||
const tracks = this.player_.textTracks();
|
||||
|
||||
if (!tracks) {
|
||||
return items;
|
||||
}
|
||||
|
||||
for (let i = 0; i < tracks.length; i++) {
|
||||
let track = tracks[i];
|
||||
const track = tracks[i];
|
||||
|
||||
// only add tracks that are of the appropriate kind and have a label
|
||||
if (track['kind'] === this.kind_) {
|
||||
if (track.kind === this.kind_) {
|
||||
items.push(new TextTrackMenuItem(this.player_, {
|
||||
track,
|
||||
// MenuItem is selectable
|
||||
'selectable': true,
|
||||
'track': track
|
||||
selectable: true
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
@ -17,20 +17,20 @@ import document from 'global/document';
|
||||
*/
|
||||
class TextTrackMenuItem extends MenuItem {
|
||||
|
||||
constructor(player, options){
|
||||
let track = options['track'];
|
||||
let tracks = player.textTracks();
|
||||
constructor(player, options) {
|
||||
const track = options.track;
|
||||
const tracks = player.textTracks();
|
||||
|
||||
// Modify options for parent MenuItem class's init.
|
||||
options['label'] = track['label'] || track['language'] || 'Unknown';
|
||||
options['selected'] = track['default'] || track['mode'] === 'showing';
|
||||
options.label = track.label || track.language || 'Unknown';
|
||||
options.selected = track.default || track.mode === 'showing';
|
||||
|
||||
super(player, options);
|
||||
|
||||
this.track = track;
|
||||
|
||||
if (tracks) {
|
||||
let changeHandler = Fn.bind(this, this.handleTracksChange);
|
||||
const changeHandler = Fn.bind(this, this.handleTracksChange);
|
||||
|
||||
tracks.addEventListener('change', changeHandler);
|
||||
this.on('dispose', function() {
|
||||
@ -52,7 +52,9 @@ class TextTrackMenuItem extends MenuItem {
|
||||
// Android 2.3 throws an Illegal Constructor error for window.Event
|
||||
try {
|
||||
event = new window.Event('change');
|
||||
} catch(err){}
|
||||
} catch (err) {
|
||||
// continue regardless of error
|
||||
}
|
||||
}
|
||||
|
||||
if (!event) {
|
||||
@ -71,24 +73,26 @@ class TextTrackMenuItem extends MenuItem {
|
||||
* @method handleClick
|
||||
*/
|
||||
handleClick(event) {
|
||||
let kind = this.track['kind'];
|
||||
let tracks = this.player_.textTracks();
|
||||
const kind = this.track.kind;
|
||||
const tracks = this.player_.textTracks();
|
||||
|
||||
super.handleClick(event);
|
||||
|
||||
if (!tracks) return;
|
||||
if (!tracks) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = 0; i < tracks.length; i++) {
|
||||
let track = tracks[i];
|
||||
const track = tracks[i];
|
||||
|
||||
if (track['kind'] !== kind) {
|
||||
if (track.kind !== kind) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (track === this.track) {
|
||||
track['mode'] = 'showing';
|
||||
track.mode = 'showing';
|
||||
} else {
|
||||
track['mode'] = 'disabled';
|
||||
track.mode = 'disabled';
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -98,8 +102,8 @@ class TextTrackMenuItem extends MenuItem {
|
||||
*
|
||||
* @method handleTracksChange
|
||||
*/
|
||||
handleTracksChange(event){
|
||||
this.selected(this.track['mode'] === 'showing');
|
||||
handleTracksChange(event) {
|
||||
this.selected(this.track.mode === 'showing');
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ import formatTime from '../../utils/format-time.js';
|
||||
*/
|
||||
class CurrentTimeDisplay extends Component {
|
||||
|
||||
constructor(player, options){
|
||||
constructor(player, options) {
|
||||
super(player, options);
|
||||
|
||||
this.on(player, 'timeupdate', this.updateContent);
|
||||
@ -28,14 +28,14 @@ class CurrentTimeDisplay extends Component {
|
||||
* @method createEl
|
||||
*/
|
||||
createEl() {
|
||||
let el = super.createEl('div', {
|
||||
const el = super.createEl('div', {
|
||||
className: 'vjs-current-time vjs-time-control vjs-control'
|
||||
});
|
||||
|
||||
this.contentEl_ = Dom.createEl('div', {
|
||||
className: 'vjs-current-time-display',
|
||||
// label the current time for screen reader users
|
||||
innerHTML: '<span class="vjs-control-text">Current Time </span>' + '0:00',
|
||||
innerHTML: '<span class="vjs-control-text">Current Time </span>' + '0:00'
|
||||
}, {
|
||||
// tell screen readers not to automatically read the time as it changes
|
||||
'aria-live': 'off'
|
||||
@ -52,9 +52,10 @@ class CurrentTimeDisplay extends Component {
|
||||
*/
|
||||
updateContent() {
|
||||
// Allows for smooth scrubbing, when player can't keep up.
|
||||
let time = (this.player_.scrubbing()) ? this.player_.getCache().currentTime : this.player_.currentTime();
|
||||
let localizedText = this.localize('Current Time');
|
||||
let formattedTime = formatTime(time, this.player_.duration());
|
||||
const time = (this.player_.scrubbing()) ? this.player_.getCache().currentTime : this.player_.currentTime();
|
||||
const localizedText = this.localize('Current Time');
|
||||
const formattedTime = formatTime(time, this.player_.duration());
|
||||
|
||||
if (formattedTime !== this.formattedTime_) {
|
||||
this.formattedTime_ = formattedTime;
|
||||
this.contentEl_.innerHTML = `<span class="vjs-control-text">${localizedText}</span> ${formattedTime}`;
|
||||
|
@ -15,7 +15,7 @@ import formatTime from '../../utils/format-time.js';
|
||||
*/
|
||||
class DurationDisplay extends Component {
|
||||
|
||||
constructor(player, options){
|
||||
constructor(player, options) {
|
||||
super(player, options);
|
||||
|
||||
this.on(player, 'durationchange', this.updateContent);
|
||||
@ -28,7 +28,7 @@ class DurationDisplay extends Component {
|
||||
* @method createEl
|
||||
*/
|
||||
createEl() {
|
||||
let el = super.createEl('div', {
|
||||
const el = super.createEl('div', {
|
||||
className: 'vjs-duration vjs-time-control vjs-control'
|
||||
});
|
||||
|
||||
@ -51,12 +51,15 @@ class DurationDisplay extends Component {
|
||||
* @method updateContent
|
||||
*/
|
||||
updateContent() {
|
||||
let duration = this.player_.duration();
|
||||
const duration = this.player_.duration();
|
||||
|
||||
if (duration && this.duration_ !== duration) {
|
||||
this.duration_ = duration;
|
||||
let localizedText = this.localize('Duration Time');
|
||||
let formattedTime = formatTime(duration);
|
||||
this.contentEl_.innerHTML = `<span class="vjs-control-text">${localizedText}</span> ${formattedTime}`; // label the duration time for screen reader users
|
||||
const localizedText = this.localize('Duration Time');
|
||||
const formattedTime = formatTime(duration);
|
||||
|
||||
// label the duration time for screen reader users
|
||||
this.contentEl_.innerHTML = `<span class="vjs-control-text">${localizedText}</span> ${formattedTime}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,7 +15,7 @@ import formatTime from '../../utils/format-time.js';
|
||||
*/
|
||||
class RemainingTimeDisplay extends Component {
|
||||
|
||||
constructor(player, options){
|
||||
constructor(player, options) {
|
||||
super(player, options);
|
||||
|
||||
this.on(player, 'timeupdate', this.updateContent);
|
||||
@ -29,14 +29,14 @@ class RemainingTimeDisplay extends Component {
|
||||
* @method createEl
|
||||
*/
|
||||
createEl() {
|
||||
let el = super.createEl('div', {
|
||||
const el = super.createEl('div', {
|
||||
className: 'vjs-remaining-time vjs-time-control vjs-control'
|
||||
});
|
||||
|
||||
this.contentEl_ = Dom.createEl('div', {
|
||||
className: 'vjs-remaining-time-display',
|
||||
// label the remaining time for screen reader users
|
||||
innerHTML: `<span class="vjs-control-text">${this.localize('Remaining Time')}</span> -0:00`,
|
||||
innerHTML: `<span class="vjs-control-text">${this.localize('Remaining Time')}</span> -0:00`
|
||||
}, {
|
||||
// tell screen readers not to automatically read the time as it changes
|
||||
'aria-live': 'off'
|
||||
@ -55,6 +55,7 @@ class RemainingTimeDisplay extends Component {
|
||||
if (this.player_.duration()) {
|
||||
const localizedText = this.localize('Remaining Time');
|
||||
const formattedTime = formatTime(this.player_.remainingTime());
|
||||
|
||||
if (formattedTime !== this.formattedTime_) {
|
||||
this.formattedTime_ = formattedTime;
|
||||
this.contentEl_.innerHTML = `<span class="vjs-control-text">${localizedText}</span> -${formattedTime}`;
|
||||
|
@ -15,8 +15,8 @@ import * as Fn from '../utils/fn.js';
|
||||
*/
|
||||
class TrackButton extends MenuButton {
|
||||
|
||||
constructor(player, options){
|
||||
let tracks = options.tracks;
|
||||
constructor(player, options) {
|
||||
const tracks = options.tracks;
|
||||
|
||||
super(player, options);
|
||||
|
||||
@ -28,7 +28,8 @@ class TrackButton extends MenuButton {
|
||||
return;
|
||||
}
|
||||
|
||||
let updateHandler = Fn.bind(this, this.update);
|
||||
const updateHandler = Fn.bind(this, this.update);
|
||||
|
||||
tracks.addEventListener('removetrack', updateHandler);
|
||||
tracks.addEventListener('addtrack', updateHandler);
|
||||
|
||||
|
@ -6,7 +6,7 @@ import Component from '../../component.js';
|
||||
import * as Fn from '../../utils/fn.js';
|
||||
|
||||
// Required children
|
||||
import VolumeLevel from './volume-level.js';
|
||||
import './volume-level.js';
|
||||
|
||||
/**
|
||||
* The bar that contains the volume level and can be clicked on to adjust the level
|
||||
@ -18,7 +18,7 @@ import VolumeLevel from './volume-level.js';
|
||||
*/
|
||||
class VolumeBar extends Slider {
|
||||
|
||||
constructor(player, options){
|
||||
constructor(player, options) {
|
||||
super(player, options);
|
||||
this.on(player, 'volumechange', this.updateARIAAttributes);
|
||||
player.ready(Fn.bind(this, this.updateARIAAttributes));
|
||||
@ -63,9 +63,8 @@ class VolumeBar extends Slider {
|
||||
getPercent() {
|
||||
if (this.player_.muted()) {
|
||||
return 0;
|
||||
} else {
|
||||
return this.player_.volume();
|
||||
}
|
||||
return this.player_.volume();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -95,7 +94,8 @@ class VolumeBar extends Slider {
|
||||
*/
|
||||
updateARIAAttributes() {
|
||||
// Current value of volume bar as a percentage
|
||||
let volume = (this.player_.volume() * 100).toFixed(2);
|
||||
const volume = (this.player_.volume() * 100).toFixed(2);
|
||||
|
||||
this.el_.setAttribute('aria-valuenow', volume);
|
||||
this.el_.setAttribute('aria-valuetext', volume + '%');
|
||||
}
|
||||
@ -106,7 +106,7 @@ VolumeBar.prototype.options_ = {
|
||||
children: [
|
||||
'volumeLevel'
|
||||
],
|
||||
'barName': 'volumeLevel'
|
||||
barName: 'volumeLevel'
|
||||
};
|
||||
|
||||
VolumeBar.prototype.playerEvent = 'volumechange';
|
||||
|
@ -4,7 +4,7 @@
|
||||
import Component from '../../component.js';
|
||||
|
||||
// Required children
|
||||
import VolumeBar from './volume-bar.js';
|
||||
import './volume-bar.js';
|
||||
|
||||
/**
|
||||
* The component for controlling the volume level
|
||||
@ -16,15 +16,15 @@ import VolumeBar from './volume-bar.js';
|
||||
*/
|
||||
class VolumeControl extends Component {
|
||||
|
||||
constructor(player, options){
|
||||
constructor(player, options) {
|
||||
super(player, options);
|
||||
|
||||
// hide volume controls when they're not supported by the current tech
|
||||
if (player.tech_ && player.tech_['featuresVolumeControl'] === false) {
|
||||
if (player.tech_ && player.tech_.featuresVolumeControl === false) {
|
||||
this.addClass('vjs-hidden');
|
||||
}
|
||||
this.on(player, 'loadstart', function(){
|
||||
if (player.tech_['featuresVolumeControl'] === false) {
|
||||
this.on(player, 'loadstart', function() {
|
||||
if (player.tech_.featuresVolumeControl === false) {
|
||||
this.addClass('vjs-hidden');
|
||||
} else {
|
||||
this.removeClass('vjs-hidden');
|
||||
|
@ -18,7 +18,7 @@ import VolumeBar from './volume-control/volume-bar.js';
|
||||
*/
|
||||
class VolumeMenuButton extends PopupButton {
|
||||
|
||||
constructor(player, options={}){
|
||||
constructor(player, options = {}) {
|
||||
// Default to inline
|
||||
if (options.inline === undefined) {
|
||||
options.inline = true;
|
||||
@ -48,7 +48,7 @@ class VolumeMenuButton extends PopupButton {
|
||||
|
||||
// hide mute toggle if the current tech doesn't support volume control
|
||||
function updateVisibility() {
|
||||
if (player.tech_ && player.tech_['featuresVolumeControl'] === false) {
|
||||
if (player.tech_ && player.tech_.featuresVolumeControl === false) {
|
||||
this.addClass('vjs-hidden');
|
||||
} else {
|
||||
this.removeClass('vjs-hidden');
|
||||
@ -58,19 +58,19 @@ class VolumeMenuButton extends PopupButton {
|
||||
updateVisibility.call(this);
|
||||
this.on(player, 'loadstart', updateVisibility);
|
||||
|
||||
this.on(this.volumeBar, ['slideractive', 'focus'], function(){
|
||||
this.on(this.volumeBar, ['slideractive', 'focus'], function() {
|
||||
this.addClass('vjs-slider-active');
|
||||
});
|
||||
|
||||
this.on(this.volumeBar, ['sliderinactive', 'blur'], function(){
|
||||
this.on(this.volumeBar, ['sliderinactive', 'blur'], function() {
|
||||
this.removeClass('vjs-slider-active');
|
||||
});
|
||||
|
||||
this.on(this.volumeBar, ['focus'], function(){
|
||||
this.on(this.volumeBar, ['focus'], function() {
|
||||
this.addClass('vjs-lock-showing');
|
||||
});
|
||||
|
||||
this.on(this.volumeBar, ['blur'], function(){
|
||||
this.on(this.volumeBar, ['blur'], function() {
|
||||
this.removeClass('vjs-lock-showing');
|
||||
});
|
||||
}
|
||||
@ -83,7 +83,8 @@ class VolumeMenuButton extends PopupButton {
|
||||
*/
|
||||
buildCSSClass() {
|
||||
let orientationClass = '';
|
||||
if (!!this.options_.vertical) {
|
||||
|
||||
if (this.options_.vertical) {
|
||||
orientationClass = 'vjs-volume-menu-button-vertical';
|
||||
} else {
|
||||
orientationClass = 'vjs-volume-menu-button-horizontal';
|
||||
@ -99,11 +100,11 @@ class VolumeMenuButton extends PopupButton {
|
||||
* @method createPopup
|
||||
*/
|
||||
createPopup() {
|
||||
let popup = new Popup(this.player_, {
|
||||
const popup = new Popup(this.player_, {
|
||||
contentElType: 'div'
|
||||
});
|
||||
|
||||
let vb = new VolumeBar(this.player_, this.options_.volumeBar);
|
||||
const vb = new VolumeBar(this.player_, this.options_.volumeBar);
|
||||
|
||||
popup.addChild(vb);
|
||||
|
||||
|
@ -3,8 +3,6 @@
|
||||
*/
|
||||
import Component from './component';
|
||||
import ModalDialog from './modal-dialog';
|
||||
|
||||
import * as Dom from './utils/dom';
|
||||
import mergeOptions from './utils/merge-options';
|
||||
|
||||
/**
|
||||
@ -46,6 +44,7 @@ class ErrorDisplay extends ModalDialog {
|
||||
*/
|
||||
content() {
|
||||
let error = this.player().error();
|
||||
|
||||
return error ? this.localize(error.message) : '';
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
*/
|
||||
import * as Events from './utils/events.js';
|
||||
|
||||
var EventTarget = function() {};
|
||||
const EventTarget = function() {};
|
||||
|
||||
EventTarget.prototype.allowedEvents_ = {};
|
||||
|
||||
@ -11,21 +11,25 @@ EventTarget.prototype.on = function(type, fn) {
|
||||
// Remove the addEventListener alias before calling Events.on
|
||||
// so we don't get into an infinite type loop
|
||||
let ael = this.addEventListener;
|
||||
|
||||
this.addEventListener = () => {};
|
||||
Events.on(this, type, fn);
|
||||
this.addEventListener = ael;
|
||||
};
|
||||
|
||||
EventTarget.prototype.addEventListener = EventTarget.prototype.on;
|
||||
|
||||
EventTarget.prototype.off = function(type, fn) {
|
||||
Events.off(this, type, fn);
|
||||
};
|
||||
|
||||
EventTarget.prototype.removeEventListener = EventTarget.prototype.off;
|
||||
|
||||
EventTarget.prototype.one = function(type, fn) {
|
||||
// Remove the addEventListener alias before calling Events.on
|
||||
// so we don't get into an infinite type loop
|
||||
let ael = this.addEventListener;
|
||||
|
||||
this.addEventListener = () => {};
|
||||
Events.one(this, type, fn);
|
||||
this.addEventListener = ael;
|
||||
@ -35,9 +39,7 @@ EventTarget.prototype.trigger = function(event) {
|
||||
let type = event.type || event;
|
||||
|
||||
if (typeof event === 'string') {
|
||||
event = {
|
||||
type: type
|
||||
};
|
||||
event = {type};
|
||||
}
|
||||
event = Events.fixEvent(event);
|
||||
|
||||
@ -47,6 +49,7 @@ EventTarget.prototype.trigger = function(event) {
|
||||
|
||||
Events.trigger(this, event);
|
||||
};
|
||||
|
||||
// The standard DOM EventTarget.dispatchEvent() is aliased to trigger()
|
||||
EventTarget.prototype.dispatchEvent = EventTarget.prototype.trigger;
|
||||
|
||||
|
@ -7,7 +7,7 @@ import log from './utils/log';
|
||||
* Both work the same but node adds `super_` to the subClass
|
||||
* and Bable adds the superClass as __proto__. Both seem useful.
|
||||
*/
|
||||
const _inherits = function (subClass, superClass) {
|
||||
const _inherits = function(subClass, superClass) {
|
||||
if (typeof superClass !== 'function' && superClass !== null) {
|
||||
throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass);
|
||||
}
|
||||
@ -44,10 +44,11 @@ const _inherits = function (subClass, superClass) {
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
const extendFn = function(superClass, subClassMethods={}) {
|
||||
const extendFn = function(superClass, subClassMethods = {}) {
|
||||
let subClass = function() {
|
||||
superClass.apply(this, arguments);
|
||||
};
|
||||
|
||||
let methods = {};
|
||||
|
||||
if (typeof subClassMethods === 'object') {
|
||||
@ -66,7 +67,7 @@ const extendFn = function(superClass, subClassMethods={}) {
|
||||
_inherits(subClass, superClass);
|
||||
|
||||
// Extend subObj's prototype with functions and other properties from props
|
||||
for (var name in methods) {
|
||||
for (let name in methods) {
|
||||
if (methods.hasOwnProperty(name)) {
|
||||
subClass.prototype[name] = methods[name];
|
||||
}
|
||||
|
@ -74,7 +74,7 @@ for (let i = 0; i < apiMap.length; i++) {
|
||||
|
||||
// map the browser API names to the spec API names
|
||||
if (browserApi) {
|
||||
for (let i=0; i<browserApi.length; i++) {
|
||||
for (let i = 0; i < browserApi.length; i++) {
|
||||
FullscreenApi[specApi[i]] = browserApi[i];
|
||||
}
|
||||
}
|
||||
|
@ -8,13 +8,13 @@ import assign from 'object.assign';
|
||||
*
|
||||
* @param {Number} code The media error code
|
||||
*/
|
||||
let MediaError = function(code){
|
||||
let MediaError = function(code) {
|
||||
if (typeof code === 'number') {
|
||||
this.code = code;
|
||||
} else if (typeof code === 'string') {
|
||||
// default code is zero, so this is a custom error
|
||||
this.message = code;
|
||||
} else if (typeof code === 'object') { // object
|
||||
} else if (typeof code === 'object') {
|
||||
assign(this, code);
|
||||
}
|
||||
|
||||
@ -52,13 +52,15 @@ MediaError.prototype.message = '';
|
||||
*/
|
||||
MediaError.prototype.status = null;
|
||||
|
||||
// These errors are indexed by the W3C standard numeric value. The order
|
||||
// should not be changed!
|
||||
MediaError.errorTypes = [
|
||||
'MEDIA_ERR_CUSTOM', // = 0
|
||||
'MEDIA_ERR_ABORTED', // = 1
|
||||
'MEDIA_ERR_NETWORK', // = 2
|
||||
'MEDIA_ERR_DECODE', // = 3
|
||||
'MEDIA_ERR_SRC_NOT_SUPPORTED', // = 4
|
||||
'MEDIA_ERR_ENCRYPTED' // = 5
|
||||
'MEDIA_ERR_CUSTOM',
|
||||
'MEDIA_ERR_ABORTED',
|
||||
'MEDIA_ERR_NETWORK',
|
||||
'MEDIA_ERR_DECODE',
|
||||
'MEDIA_ERR_SRC_NOT_SUPPORTED',
|
||||
'MEDIA_ERR_ENCRYPTED'
|
||||
];
|
||||
|
||||
MediaError.defaultMessages = {
|
||||
@ -71,7 +73,7 @@ MediaError.defaultMessages = {
|
||||
|
||||
// Add types as properties on MediaError
|
||||
// e.g. MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED = 4;
|
||||
for (var errNum = 0; errNum < MediaError.errorTypes.length; errNum++) {
|
||||
for (let errNum = 0; errNum < MediaError.errorTypes.length; errNum++) {
|
||||
MediaError[MediaError.errorTypes[errNum]] = errNum;
|
||||
// values should be accessible on both the class and instance
|
||||
MediaError.prototype[MediaError.errorTypes[errNum]] = errNum;
|
||||
|
@ -18,7 +18,7 @@ import toTitleCase from '../utils/to-title-case.js';
|
||||
*/
|
||||
class MenuButton extends ClickableComponent {
|
||||
|
||||
constructor(player, options={}){
|
||||
constructor(player, options = {}) {
|
||||
super(player, options);
|
||||
|
||||
this.update();
|
||||
@ -36,7 +36,7 @@ class MenuButton extends ClickableComponent {
|
||||
* @method update
|
||||
*/
|
||||
update() {
|
||||
let menu = this.createMenu();
|
||||
const menu = this.createMenu();
|
||||
|
||||
if (this.menu) {
|
||||
this.removeChild(this.menu);
|
||||
@ -68,24 +68,25 @@ class MenuButton extends ClickableComponent {
|
||||
* @method createMenu
|
||||
*/
|
||||
createMenu() {
|
||||
var menu = new Menu(this.player_);
|
||||
const menu = new Menu(this.player_);
|
||||
|
||||
// Add a title list item to the top
|
||||
if (this.options_.title) {
|
||||
let title = Dom.createEl('li', {
|
||||
const title = Dom.createEl('li', {
|
||||
className: 'vjs-menu-title',
|
||||
innerHTML: toTitleCase(this.options_.title),
|
||||
tabIndex: -1
|
||||
});
|
||||
|
||||
menu.children_.unshift(title);
|
||||
Dom.insertElFirst(title, menu.contentEl());
|
||||
}
|
||||
|
||||
this.items = this['createItems']();
|
||||
this.items = this.createItems();
|
||||
|
||||
if (this.items) {
|
||||
// Add menu items to the menu
|
||||
for (var i = 0; i < this.items.length; i++) {
|
||||
for (let i = 0; i < this.items.length; i++) {
|
||||
menu.addItem(this.items[i]);
|
||||
}
|
||||
}
|
||||
@ -98,7 +99,7 @@ class MenuButton extends ClickableComponent {
|
||||
*
|
||||
* @method createItems
|
||||
*/
|
||||
createItems(){}
|
||||
createItems() {}
|
||||
|
||||
/**
|
||||
* Create the component's DOM element
|
||||
@ -119,7 +120,7 @@ class MenuButton extends ClickableComponent {
|
||||
* @method buildCSSClass
|
||||
*/
|
||||
buildCSSClass() {
|
||||
var menuButtonClass = 'vjs-menu-button';
|
||||
let menuButtonClass = 'vjs-menu-button';
|
||||
|
||||
// If the inline option is passed, we want to use different styles altogether.
|
||||
if (this.options_.inline === true) {
|
||||
@ -141,11 +142,11 @@ class MenuButton extends ClickableComponent {
|
||||
* @method handleClick
|
||||
*/
|
||||
handleClick() {
|
||||
this.one(this.menu.contentEl(), 'mouseleave', Fn.bind(this, function(e){
|
||||
this.one(this.menu.contentEl(), 'mouseleave', Fn.bind(this, function(e) {
|
||||
this.unpressButton();
|
||||
this.el_.blur();
|
||||
}));
|
||||
if (this.buttonPressed_){
|
||||
if (this.buttonPressed_) {
|
||||
this.unpressButton();
|
||||
} else {
|
||||
this.pressButton();
|
||||
@ -189,8 +190,8 @@ class MenuButton extends ClickableComponent {
|
||||
handleSubmenuKeyPress(event) {
|
||||
|
||||
// Escape (27) key or Tab (9) key unpress the 'button'
|
||||
if (event.which === 27 || event.which === 9){
|
||||
if (this.buttonPressed_){
|
||||
if (event.which === 27 || event.which === 9) {
|
||||
if (this.buttonPressed_) {
|
||||
this.unpressButton();
|
||||
}
|
||||
// Don't preventDefault for Tab key - we still want to lose focus
|
||||
@ -210,7 +211,8 @@ class MenuButton extends ClickableComponent {
|
||||
this.buttonPressed_ = true;
|
||||
this.menu.lockShowing();
|
||||
this.el_.setAttribute('aria-expanded', 'true');
|
||||
this.menu.focus(); // set the focus into the submenu
|
||||
// set the focus into the submenu
|
||||
this.menu.focus();
|
||||
}
|
||||
}
|
||||
|
||||
@ -224,7 +226,8 @@ class MenuButton extends ClickableComponent {
|
||||
this.buttonPressed_ = false;
|
||||
this.menu.unlockShowing();
|
||||
this.el_.setAttribute('aria-expanded', 'false');
|
||||
this.el_.focus(); // Set focus back to this menu button
|
||||
// Set focus back to this menu button
|
||||
this.el_.focus();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -18,9 +18,9 @@ class MenuItem extends ClickableComponent {
|
||||
constructor(player, options) {
|
||||
super(player, options);
|
||||
|
||||
this.selectable = options['selectable'];
|
||||
this.selectable = options.selectable;
|
||||
|
||||
this.selected(options['selected']);
|
||||
this.selected(options.selected);
|
||||
|
||||
if (this.selectable) {
|
||||
// TODO: May need to be either menuitemcheckbox or menuitemradio,
|
||||
@ -42,7 +42,7 @@ class MenuItem extends ClickableComponent {
|
||||
createEl(type, props, attrs) {
|
||||
return super.createEl('li', assign({
|
||||
className: 'vjs-menu-item',
|
||||
innerHTML: this.localize(this.options_['label']),
|
||||
innerHTML: this.localize(this.options_.label),
|
||||
tabIndex: -1
|
||||
}, props), attrs);
|
||||
}
|
||||
@ -66,13 +66,13 @@ class MenuItem extends ClickableComponent {
|
||||
if (this.selectable) {
|
||||
if (selected) {
|
||||
this.addClass('vjs-selected');
|
||||
this.el_.setAttribute('aria-checked','true');
|
||||
this.el_.setAttribute('aria-checked', 'true');
|
||||
// aria-checked isn't fully supported by browsers/screen readers,
|
||||
// so indicate selected state to screen reader in the control text.
|
||||
this.controlText(', selected');
|
||||
} else {
|
||||
this.removeClass('vjs-selected');
|
||||
this.el_.setAttribute('aria-checked','false');
|
||||
this.el_.setAttribute('aria-checked', 'false');
|
||||
// Indicate un-selected state to screen reader
|
||||
// Note that a space clears out the selected state text
|
||||
this.controlText(' ');
|
||||
|
@ -15,7 +15,7 @@ import * as Events from '../utils/events.js';
|
||||
*/
|
||||
class Menu extends Component {
|
||||
|
||||
constructor (player, options) {
|
||||
constructor(player, options) {
|
||||
super(player, options);
|
||||
|
||||
this.focusedChild_ = -1;
|
||||
@ -31,9 +31,9 @@ class Menu extends Component {
|
||||
*/
|
||||
addItem(component) {
|
||||
this.addChild(component);
|
||||
component.on('click', Fn.bind(this, function(){
|
||||
component.on('click', Fn.bind(this, function() {
|
||||
this.unlockShowing();
|
||||
//TODO: Need to set keyboard focus back to the menuButton
|
||||
// TODO: Need to set keyboard focus back to the menuButton
|
||||
}));
|
||||
}
|
||||
|
||||
@ -44,21 +44,25 @@ class Menu extends Component {
|
||||
* @method createEl
|
||||
*/
|
||||
createEl() {
|
||||
let contentElType = this.options_.contentElType || 'ul';
|
||||
const contentElType = this.options_.contentElType || 'ul';
|
||||
|
||||
this.contentEl_ = Dom.createEl(contentElType, {
|
||||
className: 'vjs-menu-content'
|
||||
});
|
||||
|
||||
this.contentEl_.setAttribute('role', 'menu');
|
||||
var el = super.createEl('div', {
|
||||
|
||||
const el = super.createEl('div', {
|
||||
append: this.contentEl_,
|
||||
className: 'vjs-menu'
|
||||
});
|
||||
|
||||
el.setAttribute('role', 'presentation');
|
||||
el.appendChild(this.contentEl_);
|
||||
|
||||
// Prevent clicks from bubbling up. Needed for Menu Buttons,
|
||||
// where a click on the parent is significant
|
||||
Events.on(el, 'click', function(event){
|
||||
Events.on(el, 'click', function(event) {
|
||||
event.preventDefault();
|
||||
event.stopImmediatePropagation();
|
||||
});
|
||||
@ -72,11 +76,14 @@ class Menu extends Component {
|
||||
* @param {Object} event Event object
|
||||
* @method handleKeyPress
|
||||
*/
|
||||
handleKeyPress (event) {
|
||||
if (event.which === 37 || event.which === 40) { // Left and Down Arrows
|
||||
handleKeyPress(event) {
|
||||
// Left and Down Arrows
|
||||
if (event.which === 37 || event.which === 40) {
|
||||
event.preventDefault();
|
||||
this.stepForward();
|
||||
} else if (event.which === 38 || event.which === 39) { // Up and Right Arrows
|
||||
|
||||
// Up and Right Arrows
|
||||
} else if (event.which === 38 || event.which === 39) {
|
||||
event.preventDefault();
|
||||
this.stepBack();
|
||||
}
|
||||
@ -87,21 +94,21 @@ class Menu extends Component {
|
||||
*
|
||||
* @method stepForward
|
||||
*/
|
||||
stepForward () {
|
||||
let stepChild = 0;
|
||||
stepForward() {
|
||||
let stepChild = 0;
|
||||
|
||||
if (this.focusedChild_ !== undefined) {
|
||||
stepChild = this.focusedChild_ + 1;
|
||||
}
|
||||
this.focus(stepChild);
|
||||
}
|
||||
if (this.focusedChild_ !== undefined) {
|
||||
stepChild = this.focusedChild_ + 1;
|
||||
}
|
||||
this.focus(stepChild);
|
||||
}
|
||||
|
||||
/**
|
||||
* Move to previous (higher) menu item for keyboard users
|
||||
*
|
||||
* @method stepBack
|
||||
*/
|
||||
stepBack () {
|
||||
/**
|
||||
* Move to previous (higher) menu item for keyboard users
|
||||
*
|
||||
* @method stepBack
|
||||
*/
|
||||
stepBack() {
|
||||
let stepChild = 0;
|
||||
|
||||
if (this.focusedChild_ !== undefined) {
|
||||
@ -116,10 +123,10 @@ class Menu extends Component {
|
||||
* @param {Object|String} item Index of child item set focus on
|
||||
* @method focus
|
||||
*/
|
||||
focus (item = 0) {
|
||||
let children = this.children().slice();
|
||||
let haveTitle = children.length && children[0].className &&
|
||||
/vjs-menu-title/.test(children[0].className);
|
||||
focus(item = 0) {
|
||||
const children = this.children().slice();
|
||||
const haveTitle = children.length && children[0].className &&
|
||||
(/vjs-menu-title/).test(children[0].className);
|
||||
|
||||
if (haveTitle) {
|
||||
children.shift();
|
||||
|
@ -3,10 +3,7 @@
|
||||
*/
|
||||
import * as Dom from './utils/dom';
|
||||
import * as Fn from './utils/fn';
|
||||
import log from './utils/log';
|
||||
|
||||
import Component from './component';
|
||||
import CloseButton from './close-button';
|
||||
|
||||
const MODAL_CLASS_NAME = 'vjs-modal-dialog';
|
||||
const ESC = 27;
|
||||
@ -92,7 +89,7 @@ class ModalDialog extends Component {
|
||||
'aria-describedby': `${this.id()}_description`,
|
||||
'aria-hidden': 'true',
|
||||
'aria-label': this.label(),
|
||||
role: 'dialog'
|
||||
'role': 'dialog'
|
||||
});
|
||||
}
|
||||
|
||||
@ -254,6 +251,7 @@ class ModalDialog extends Component {
|
||||
// The close button should be a child of the modal - not its
|
||||
// content element, so temporarily change the content element.
|
||||
let temp = this.contentEl_;
|
||||
|
||||
this.contentEl_ = this.el_;
|
||||
close = this.addChild('closeButton', {controlText: 'Close Modal Dialog'});
|
||||
this.contentEl_ = temp;
|
||||
|
312
src/js/player.js
312
src/js/player.js
@ -22,23 +22,25 @@ import safeParseTuple from 'safe-json-parse/tuple';
|
||||
import assign from 'object.assign';
|
||||
import mergeOptions from './utils/merge-options.js';
|
||||
import textTrackConverter from './tracks/text-track-list-converter.js';
|
||||
import ModalDialog from './modal-dialog';
|
||||
import Tech from './tech/tech.js';
|
||||
import AudioTrackList from './tracks/audio-track-list.js';
|
||||
import VideoTrackList from './tracks/video-track-list.js';
|
||||
|
||||
// Include required child components (importing also registers them)
|
||||
import MediaLoader from './tech/loader.js';
|
||||
import PosterImage from './poster-image.js';
|
||||
import TextTrackDisplay from './tracks/text-track-display.js';
|
||||
import LoadingSpinner from './loading-spinner.js';
|
||||
import BigPlayButton from './big-play-button.js';
|
||||
import ControlBar from './control-bar/control-bar.js';
|
||||
import ErrorDisplay from './error-display.js';
|
||||
import TextTrackSettings from './tracks/text-track-settings.js';
|
||||
import ModalDialog from './modal-dialog';
|
||||
// The following imports are used only to ensure that the corresponding modules
|
||||
// are always included in the video.js package. Importing the modules will
|
||||
// execute them and they will register themselves with video.js.
|
||||
import './tech/loader.js';
|
||||
import './poster-image.js';
|
||||
import './tracks/text-track-display.js';
|
||||
import './loading-spinner.js';
|
||||
import './big-play-button.js';
|
||||
import './control-bar/control-bar.js';
|
||||
import './error-display.js';
|
||||
import './tracks/text-track-settings.js';
|
||||
|
||||
// Require html5 tech, at least for disposing the original video tag
|
||||
import Tech from './tech/tech.js';
|
||||
import Html5 from './tech/html5.js';
|
||||
// Import Html5 tech, at least for disposing the original video tag.
|
||||
import './tech/html5.js';
|
||||
|
||||
/**
|
||||
* An instance of the `Player` class is created when any of the Video.js setup methods are used to initialize a video.
|
||||
@ -70,7 +72,7 @@ class Player extends Component {
|
||||
* @param {Object=} options Player options
|
||||
* @param {Function=} ready Ready callback function
|
||||
*/
|
||||
constructor(tag, options, ready){
|
||||
constructor(tag, options, ready) {
|
||||
// Make sure tag ID exists
|
||||
tag.id = tag.id || `vjs_video_${Guid.newGUID()}`;
|
||||
|
||||
@ -96,11 +98,13 @@ class Player extends Component {
|
||||
if (!options.language) {
|
||||
if (typeof tag.closest === 'function') {
|
||||
let closest = tag.closest('[lang]');
|
||||
|
||||
if (closest) {
|
||||
options.language = closest.getAttribute('lang');
|
||||
}
|
||||
} else {
|
||||
let element = tag;
|
||||
|
||||
while (element && element.nodeType === 1) {
|
||||
if (Dom.getElAttributes(element).hasOwnProperty('lang')) {
|
||||
options.language = element.getAttribute('lang');
|
||||
@ -124,7 +128,8 @@ class Player extends Component {
|
||||
'properties you want to override?');
|
||||
}
|
||||
|
||||
this.tag = tag; // Store the original tag used to set options
|
||||
// Store the original tag used to set options
|
||||
this.tag = tag;
|
||||
|
||||
// Store the tag attributes used to restore html5 element
|
||||
this.tagAttributes = tag && Dom.getElAttributes(tag);
|
||||
@ -179,7 +184,7 @@ class Player extends Component {
|
||||
if (options.plugins) {
|
||||
let plugins = options.plugins;
|
||||
|
||||
Object.getOwnPropertyNames(plugins).forEach(function(name){
|
||||
Object.getOwnPropertyNames(plugins).forEach(function(name) {
|
||||
if (typeof this[name] === 'function') {
|
||||
this[name](plugins[name]);
|
||||
} else {
|
||||
@ -264,10 +269,18 @@ class Player extends Component {
|
||||
|
||||
// Kill reference to this player
|
||||
Player.players[this.id_] = null;
|
||||
if (this.tag && this.tag.player) { this.tag.player = null; }
|
||||
if (this.el_ && this.el_.player) { this.el_.player = null; }
|
||||
|
||||
if (this.tech_) { this.tech_.dispose(); }
|
||||
if (this.tag && this.tag.player) {
|
||||
this.tag.player = null;
|
||||
}
|
||||
|
||||
if (this.el_ && this.el_.player) {
|
||||
this.el_.player = null;
|
||||
}
|
||||
|
||||
if (this.tech_) {
|
||||
this.tech_.dispose();
|
||||
}
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
@ -290,7 +303,7 @@ class Player extends Component {
|
||||
// ID will now reference player box, not the video tag
|
||||
const attrs = Dom.getElAttributes(tag);
|
||||
|
||||
Object.getOwnPropertyNames(attrs).forEach(function(attr){
|
||||
Object.getOwnPropertyNames(attrs).forEach(function(attr) {
|
||||
// workaround so we don't totally break IE7
|
||||
// http://stackoverflow.com/questions/3653444/css-styles-not-applied-on-dynamic-elements-in-internet-explorer-7
|
||||
if (attr === 'class') {
|
||||
@ -319,6 +332,7 @@ class Player extends Component {
|
||||
this.styleEl_ = stylesheet.createStyleElement('vjs-styles-dimensions');
|
||||
let defaultsStyleEl = Dom.$('.vjs-styles-defaults');
|
||||
let head = Dom.$('head');
|
||||
|
||||
head.insertBefore(this.styleEl_, defaultsStyleEl ? defaultsStyleEl.nextSibling : head.firstChild);
|
||||
}
|
||||
|
||||
@ -330,8 +344,10 @@ class Player extends Component {
|
||||
|
||||
// Hide any links within the video/audio tag, because IE doesn't hide them completely.
|
||||
let links = tag.getElementsByTagName('a');
|
||||
|
||||
for (let i = 0; i < links.length; i++) {
|
||||
let linkEl = links.item(i);
|
||||
|
||||
Dom.addElClass(linkEl, 'vjs-hidden');
|
||||
linkEl.setAttribute('hidden', 'hidden');
|
||||
}
|
||||
@ -348,7 +364,9 @@ class Player extends Component {
|
||||
// insert the tag as the first child of the player element
|
||||
// then manually add it to the children array so that this.addChild
|
||||
// will work properly for other components
|
||||
Dom.insertElFirst(tag, el); // Breaks iPhone, fixed in HTML5 setup.
|
||||
//
|
||||
// Breaks iPhone, fixed in HTML5 setup.
|
||||
Dom.insertElFirst(tag, el);
|
||||
this.children_.unshift(tag);
|
||||
|
||||
this.el_ = el;
|
||||
@ -444,7 +462,7 @@ class Player extends Component {
|
||||
}
|
||||
|
||||
// Check for width:height format
|
||||
if (!/^\d+\:\d+$/.test(ratio)) {
|
||||
if (!(/^\d+\:\d+$/).test(ratio)) {
|
||||
throw new Error('Improper value supplied for aspect ratio. The format should be width:height, for example 16:9.');
|
||||
}
|
||||
this.aspectRatio_ = ratio;
|
||||
@ -516,14 +534,14 @@ class Player extends Component {
|
||||
height = this.height_;
|
||||
} else {
|
||||
// Otherwise calculate the height from the ratio and the width
|
||||
height = width * ratioMultiplier;
|
||||
height = width * ratioMultiplier;
|
||||
}
|
||||
|
||||
// Ensure the CSS class is valid by starting with an alpha character
|
||||
if (/^[^a-zA-Z]/.test(this.id())) {
|
||||
idClass = 'dimensions-'+this.id();
|
||||
if ((/^[^a-zA-Z]/).test(this.id())) {
|
||||
idClass = 'dimensions-' + this.id();
|
||||
} else {
|
||||
idClass = this.id()+'-dimensions';
|
||||
idClass = this.id() + '-dimensions';
|
||||
}
|
||||
|
||||
// Ensure the right class is still on the player for the style element
|
||||
@ -571,9 +589,9 @@ class Player extends Component {
|
||||
this.isReady_ = false;
|
||||
|
||||
// Grab tech-specific options from player options and add source and parent element to use.
|
||||
var techOptions = assign({
|
||||
let techOptions = assign({
|
||||
source,
|
||||
'nativeControlsForTouch': this.options_.nativeControlsForTouch,
|
||||
'source': source,
|
||||
'playerId': this.id(),
|
||||
'techId': `${this.id()}_${techName}_api`,
|
||||
'videoTracks': this.videoTracks_,
|
||||
@ -602,13 +620,14 @@ class Player extends Component {
|
||||
}
|
||||
|
||||
// Initialize tech instance
|
||||
let techComponent = Tech.getTech(techName);
|
||||
let TechComponent = Tech.getTech(techName);
|
||||
|
||||
// Support old behavior of techs being registered as components.
|
||||
// Remove once that deprecated behavior is removed.
|
||||
if (!techComponent) {
|
||||
techComponent = Component.getComponent(techName);
|
||||
if (!TechComponent) {
|
||||
TechComponent = Component.getComponent(techName);
|
||||
}
|
||||
this.tech_ = new techComponent(techOptions);
|
||||
this.tech_ = new TechComponent(techOptions);
|
||||
|
||||
// player.triggerReady is always async, so don't need this to be async
|
||||
this.tech_.ready(Fn.bind(this, this.handleTechReady_), true);
|
||||
@ -704,6 +723,7 @@ class Player extends Component {
|
||||
\`IWillNotUseThisInPlugins\` to the \`tech\` method. See
|
||||
https://github.com/videojs/video.js/issues/2617 for more info.
|
||||
`;
|
||||
|
||||
window.alert(errorText);
|
||||
throw new Error(errorText);
|
||||
}
|
||||
@ -794,9 +814,9 @@ class Player extends Component {
|
||||
// This fixes both issues. Need to wait for API, so it updates displays correctly
|
||||
if ((this.src() || this.currentSrc()) && this.tag && this.options_.autoplay && this.paused()) {
|
||||
try {
|
||||
delete this.tag.poster; // Chrome Fix. Fixed in Chrome v16.
|
||||
}
|
||||
catch (e) {
|
||||
// Chrome Fix. Fixed in Chrome v16.
|
||||
delete this.tag.poster;
|
||||
} catch (e) {
|
||||
log('deleting tag.poster throws in some browsers', e);
|
||||
}
|
||||
this.play();
|
||||
@ -954,9 +974,9 @@ class Player extends Component {
|
||||
* @method handleTechFirstPlay_
|
||||
*/
|
||||
handleTechFirstPlay_() {
|
||||
//If the first starttime attribute is specified
|
||||
//then we will start at the given offset in seconds
|
||||
if(this.options_.starttime){
|
||||
// If the first starttime attribute is specified
|
||||
// then we will start at the given offset in seconds
|
||||
if (this.options_.starttime) {
|
||||
this.currentTime(this.options_.starttime);
|
||||
}
|
||||
|
||||
@ -1024,7 +1044,9 @@ class Player extends Component {
|
||||
handleTechClick_(event) {
|
||||
// We're using mousedown to detect clicks thanks to Flash, but mousedown
|
||||
// will also be triggered with right-clicks, so we need to prevent that
|
||||
if (event.button !== 0) return;
|
||||
if (event.button !== 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// When controls are disabled a click should not toggle playback because
|
||||
// the click is considered a control
|
||||
@ -1065,7 +1087,7 @@ class Player extends Component {
|
||||
* @method handleTechTouchMove_
|
||||
*/
|
||||
handleTechTouchMove_() {
|
||||
if (this.userWasActive){
|
||||
if (this.userWasActive) {
|
||||
this.reportUserActivity();
|
||||
}
|
||||
}
|
||||
@ -1127,6 +1149,7 @@ class Player extends Component {
|
||||
*/
|
||||
handleTechError_() {
|
||||
let error = this.tech_.error();
|
||||
|
||||
this.error(error);
|
||||
}
|
||||
|
||||
@ -1181,7 +1204,8 @@ class Player extends Component {
|
||||
}
|
||||
|
||||
handleTechTextData_() {
|
||||
var data = null;
|
||||
let data = null;
|
||||
|
||||
if (arguments.length > 1) {
|
||||
data = arguments[1];
|
||||
}
|
||||
@ -1259,15 +1283,17 @@ class Player extends Component {
|
||||
techCall_(method, arg) {
|
||||
// If it's not ready yet, call method when it is
|
||||
if (this.tech_ && !this.tech_.isReady_) {
|
||||
this.tech_.ready(function(){
|
||||
this.tech_.ready(function() {
|
||||
this[method](arg);
|
||||
}, true);
|
||||
|
||||
// Otherwise call method now
|
||||
} else {
|
||||
try {
|
||||
this.tech_ && this.tech_[method](arg);
|
||||
} catch(e) {
|
||||
if (this.tech_) {
|
||||
this.tech_[method](arg);
|
||||
}
|
||||
} catch (e) {
|
||||
log(e);
|
||||
throw e;
|
||||
}
|
||||
@ -1290,18 +1316,17 @@ class Player extends Component {
|
||||
// When that happens we'll catch the errors and inform tech that it's not ready any more.
|
||||
try {
|
||||
return this.tech_[method]();
|
||||
} catch(e) {
|
||||
} catch (e) {
|
||||
// When building additional tech libs, an expected method may not be defined yet
|
||||
if (this.tech_[method] === undefined) {
|
||||
log(`Video.js: ${method} method not defined for ${this.techName_} playback technology.`, e);
|
||||
|
||||
// When a method isn't available on the object it throws a TypeError
|
||||
} else if (e.name === 'TypeError') {
|
||||
log(`Video.js: ${method} unavailable on ${this.techName_} playback technology element.`, e);
|
||||
this.tech_.isReady_ = false;
|
||||
} else {
|
||||
// When a method isn't available on the object it throws a TypeError
|
||||
if (e.name === 'TypeError') {
|
||||
log(`Video.js: ${method} unavailable on ${this.techName_} playback technology element.`, e);
|
||||
this.tech_.isReady_ = false;
|
||||
} else {
|
||||
log(e);
|
||||
}
|
||||
log(e);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
@ -1414,7 +1439,8 @@ class Player extends Component {
|
||||
// currentTime when scrubbing, but may not provide much performance benefit afterall.
|
||||
// Should be tested. Also something has to read the actual current time or the cache will
|
||||
// never get updated.
|
||||
return this.cache_.currentTime = (this.techGet_('currentTime') || 0);
|
||||
this.cache_.currentTime = (this.techGet_('currentTime') || 0);
|
||||
return this.cache_.currentTime;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1495,10 +1521,10 @@ class Player extends Component {
|
||||
* @method buffered
|
||||
*/
|
||||
buffered() {
|
||||
var buffered = this.techGet_('buffered');
|
||||
let buffered = this.techGet_('buffered');
|
||||
|
||||
if (!buffered || !buffered.length) {
|
||||
buffered = createTimeRange(0,0);
|
||||
buffered = createTimeRange(0, 0);
|
||||
}
|
||||
|
||||
return buffered;
|
||||
@ -1527,9 +1553,9 @@ class Player extends Component {
|
||||
* @method bufferedEnd
|
||||
*/
|
||||
bufferedEnd() {
|
||||
var buffered = this.buffered(),
|
||||
duration = this.duration(),
|
||||
end = buffered.end(buffered.length-1);
|
||||
let buffered = this.buffered();
|
||||
let duration = this.duration();
|
||||
let end = buffered.end(buffered.length - 1);
|
||||
|
||||
if (end > duration) {
|
||||
end = duration;
|
||||
@ -1557,7 +1583,8 @@ class Player extends Component {
|
||||
let vol;
|
||||
|
||||
if (percentAsDecimal !== undefined) {
|
||||
vol = Math.max(0, Math.min(1, parseFloat(percentAsDecimal))); // Force value to between 0 and 1
|
||||
// Force value to between 0 and 1
|
||||
vol = Math.max(0, Math.min(1, parseFloat(percentAsDecimal)));
|
||||
this.cache_.volume = vol;
|
||||
this.techCall_('setVolume', vol);
|
||||
|
||||
@ -1569,7 +1596,6 @@ class Player extends Component {
|
||||
return (isNaN(vol)) ? 1 : vol;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the current muted state, or turn mute on or off
|
||||
* ```js
|
||||
@ -1589,7 +1615,7 @@ class Player extends Component {
|
||||
this.techCall_('setMuted', muted);
|
||||
return this;
|
||||
}
|
||||
return this.techGet_('muted') || false; // Default to false
|
||||
return this.techGet_('muted') || false;
|
||||
}
|
||||
|
||||
// Check if current tech can support native fullscreen
|
||||
@ -1645,7 +1671,7 @@ class Player extends Component {
|
||||
* @method requestFullscreen
|
||||
*/
|
||||
requestFullscreen() {
|
||||
var fsApi = FullscreenApi;
|
||||
let fsApi = FullscreenApi;
|
||||
|
||||
this.isFullscreen(true);
|
||||
|
||||
@ -1658,7 +1684,7 @@ class Player extends Component {
|
||||
// when canceling fullscreen. Otherwise if there's multiple
|
||||
// players on a page, they would all be reacting to the same fullscreen
|
||||
// events
|
||||
Events.on(document, fsApi.fullscreenchange, Fn.bind(this, function documentFullscreenChange(e){
|
||||
Events.on(document, fsApi.fullscreenchange, Fn.bind(this, function documentFullscreenChange(e) {
|
||||
this.isFullscreen(document[fsApi.fullscreenElement]);
|
||||
|
||||
// If cancelling fullscreen, remove event listener.
|
||||
@ -1695,17 +1721,18 @@ class Player extends Component {
|
||||
* @method exitFullscreen
|
||||
*/
|
||||
exitFullscreen() {
|
||||
var fsApi = FullscreenApi;
|
||||
let fsApi = FullscreenApi;
|
||||
|
||||
this.isFullscreen(false);
|
||||
|
||||
// Check for browser element fullscreen support
|
||||
if (fsApi.requestFullscreen) {
|
||||
document[fsApi.exitFullscreen]();
|
||||
} else if (this.tech_.supportsFullScreen()) {
|
||||
this.techCall_('exitFullScreen');
|
||||
this.techCall_('exitFullScreen');
|
||||
} else {
|
||||
this.exitFullWindow();
|
||||
this.trigger('fullscreenchange');
|
||||
this.exitFullWindow();
|
||||
this.trigger('fullscreenchange');
|
||||
}
|
||||
|
||||
return this;
|
||||
@ -1845,7 +1872,7 @@ class Player extends Component {
|
||||
// Iterate over each `innerArray` element once per `outerArray` element and execute
|
||||
// `tester` with both. If `tester` returns a non-falsy value, exit early and return
|
||||
// that value.
|
||||
let findFirstPassingTechSourcePair = function (outerArray, innerArray, tester) {
|
||||
let findFirstPassingTechSourcePair = function(outerArray, innerArray, tester) {
|
||||
let found;
|
||||
|
||||
outerArray.some((outerChoice) => {
|
||||
@ -1865,7 +1892,7 @@ class Player extends Component {
|
||||
let flip = (fn) => (a, b) => fn(b, a);
|
||||
let finder = ([techName, tech], source) => {
|
||||
if (tech.canPlaySource(source, this.options_[techName.toLowerCase()])) {
|
||||
return {source: source, tech: techName};
|
||||
return {source, tech: techName};
|
||||
}
|
||||
};
|
||||
|
||||
@ -1920,6 +1947,7 @@ class Player extends Component {
|
||||
}
|
||||
|
||||
let currentTech = Tech.getTech(this.techName_);
|
||||
|
||||
// Support old behavior of techs being registered as components.
|
||||
// Remove once that deprecated behavior is removed.
|
||||
if (!currentTech) {
|
||||
@ -1948,7 +1976,7 @@ class Player extends Component {
|
||||
this.currentType_ = source.type || '';
|
||||
|
||||
// wait until the tech is ready to set the source
|
||||
this.ready(function(){
|
||||
this.ready(function() {
|
||||
|
||||
// The setSource tech method was added with source handlers
|
||||
// so older techs won't support it
|
||||
@ -1984,7 +2012,7 @@ class Player extends Component {
|
||||
* @method sourceList_
|
||||
*/
|
||||
sourceList_(sources) {
|
||||
var sourceTech = this.selectSource(sources);
|
||||
let sourceTech = this.selectSource(sources);
|
||||
|
||||
if (sourceTech) {
|
||||
if (sourceTech.tech === this.techName_) {
|
||||
@ -1996,7 +2024,7 @@ class Player extends Component {
|
||||
}
|
||||
} else {
|
||||
// We need to wrap this in a timeout to give folks a chance to add error event handlers
|
||||
this.setTimeout( function() {
|
||||
this.setTimeout(function() {
|
||||
this.error({ code: 4, message: this.localize(this.options_.notSupportedMessage) });
|
||||
}, 0);
|
||||
|
||||
@ -2098,7 +2126,7 @@ class Player extends Component {
|
||||
loop(value) {
|
||||
if (value !== undefined) {
|
||||
this.techCall_('setLoop', value);
|
||||
this.options_['loop'] = value;
|
||||
this.options_.loop = value;
|
||||
return this;
|
||||
}
|
||||
return this.techGet_('loop');
|
||||
@ -2172,7 +2200,8 @@ class Player extends Component {
|
||||
*/
|
||||
controls(bool) {
|
||||
if (bool !== undefined) {
|
||||
bool = !!bool; // force boolean
|
||||
bool = !!bool;
|
||||
|
||||
// Don't trigger a change event unless it actually changed
|
||||
if (this.controls_ !== bool) {
|
||||
this.controls_ = bool;
|
||||
@ -2218,7 +2247,8 @@ class Player extends Component {
|
||||
*/
|
||||
usingNativeControls(bool) {
|
||||
if (bool !== undefined) {
|
||||
bool = !!bool; // force boolean
|
||||
bool = !!bool;
|
||||
|
||||
// Don't trigger a change event unless it actually changed
|
||||
if (this.usingNativeControls_ !== bool) {
|
||||
this.usingNativeControls_ = bool;
|
||||
@ -2302,7 +2332,9 @@ class Player extends Component {
|
||||
* @return {Boolean} True if the player is in the ended state, false if not.
|
||||
* @method ended
|
||||
*/
|
||||
ended() { return this.techGet_('ended'); }
|
||||
ended() {
|
||||
return this.techGet_('ended');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not the player is in the "seeking" state.
|
||||
@ -2310,7 +2342,9 @@ class Player extends Component {
|
||||
* @return {Boolean} True if the player is in the seeking state, false if not.
|
||||
* @method seeking
|
||||
*/
|
||||
seeking() { return this.techGet_('seeking'); }
|
||||
seeking() {
|
||||
return this.techGet_('seeking');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the TimeRanges of the media that are currently available
|
||||
@ -2319,7 +2353,9 @@ class Player extends Component {
|
||||
* @return {TimeRanges} the seekable intervals of the media timeline
|
||||
* @method seekable
|
||||
*/
|
||||
seekable() { return this.techGet_('seekable'); }
|
||||
seekable() {
|
||||
return this.techGet_('seekable');
|
||||
}
|
||||
|
||||
/**
|
||||
* Report user activity
|
||||
@ -2363,8 +2399,8 @@ class Player extends Component {
|
||||
//
|
||||
// When this gets resolved in ALL browsers it can be removed
|
||||
// https://code.google.com/p/chromium/issues/detail?id=103041
|
||||
if(this.tech_) {
|
||||
this.tech_.one('mousemove', function(e){
|
||||
if (this.tech_) {
|
||||
this.tech_.one('mousemove', function(e) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
});
|
||||
@ -2387,14 +2423,15 @@ class Player extends Component {
|
||||
* @method listenForUserActivity_
|
||||
*/
|
||||
listenForUserActivity_() {
|
||||
let mouseInProgress, lastMoveX, lastMoveY;
|
||||
|
||||
let mouseInProgress;
|
||||
let lastMoveX;
|
||||
let lastMoveY;
|
||||
let handleActivity = Fn.bind(this, this.reportUserActivity);
|
||||
|
||||
let handleMouseMove = function(e) {
|
||||
// #1068 - Prevent mousemove spamming
|
||||
// Chrome Bug: https://code.google.com/p/chromium/issues/detail?id=366970
|
||||
if(e.screenX !== lastMoveX || e.screenY !== lastMoveY) {
|
||||
if (e.screenX !== lastMoveX || e.screenY !== lastMoveY) {
|
||||
lastMoveX = e.screenX;
|
||||
lastMoveY = e.screenY;
|
||||
handleActivity();
|
||||
@ -2435,7 +2472,8 @@ class Player extends Component {
|
||||
// then gets picked up by this loop
|
||||
// http://ejohn.org/blog/learning-from-twitter/
|
||||
let inactivityTimeout;
|
||||
let activityCheck = this.setInterval(function() {
|
||||
|
||||
this.setInterval(function() {
|
||||
// Check to see if mouse/touch activity has happened
|
||||
if (this.userActivity_) {
|
||||
// Reset the activity tracker
|
||||
@ -2447,16 +2485,17 @@ class Player extends Component {
|
||||
// Clear any existing inactivity timeout to start the timer over
|
||||
this.clearTimeout(inactivityTimeout);
|
||||
|
||||
var timeout = this.options_['inactivityTimeout'];
|
||||
let timeout = this.options_.inactivityTimeout;
|
||||
|
||||
if (timeout > 0) {
|
||||
// In <timeout> milliseconds, if no more activity has occurred the
|
||||
// user will be considered inactive
|
||||
inactivityTimeout = this.setTimeout(function () {
|
||||
inactivityTimeout = this.setTimeout(function() {
|
||||
// Protect against the case where the inactivityTimeout can trigger just
|
||||
// before the next user activity is picked up by the activityCheck loop
|
||||
// before the next user activity is picked up by the activity check loop
|
||||
// causing a flicker
|
||||
if (!this.userActivity_) {
|
||||
this.userActive(false);
|
||||
this.userActive(false);
|
||||
}
|
||||
}, timeout);
|
||||
}
|
||||
@ -2481,11 +2520,10 @@ class Player extends Component {
|
||||
return this;
|
||||
}
|
||||
|
||||
if (this.tech_ && this.tech_['featuresPlaybackRate']) {
|
||||
if (this.tech_ && this.tech_.featuresPlaybackRate) {
|
||||
return this.techGet_('playbackRate');
|
||||
} else {
|
||||
return 1.0;
|
||||
}
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2611,7 +2649,9 @@ class Player extends Component {
|
||||
textTracks() {
|
||||
// cannot use techGet_ directly because it checks to see whether the tech is ready.
|
||||
// Flash is unlikely to be ready in time but textTracks should still work.
|
||||
return this.tech_ && this.tech_['textTracks']();
|
||||
if (this.tech_) {
|
||||
return this.tech_.textTracks();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2621,7 +2661,9 @@ class Player extends Component {
|
||||
* @method remoteTextTracks
|
||||
*/
|
||||
remoteTextTracks() {
|
||||
return this.tech_ && this.tech_['remoteTextTracks']();
|
||||
if (this.tech_) {
|
||||
return this.tech_.remoteTextTracks();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2631,7 +2673,9 @@ class Player extends Component {
|
||||
* @method remoteTextTrackEls
|
||||
*/
|
||||
remoteTextTrackEls() {
|
||||
return this.tech_ && this.tech_['remoteTextTrackEls']();
|
||||
if (this.tech_) {
|
||||
return this.tech_.remoteTextTrackEls();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2645,7 +2689,9 @@ class Player extends Component {
|
||||
* @method addTextTrack
|
||||
*/
|
||||
addTextTrack(kind, label, language) {
|
||||
return this.tech_ && this.tech_['addTextTrack'](kind, label, language);
|
||||
if (this.tech_) {
|
||||
return this.tech_.addTextTrack(kind, label, language);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2655,7 +2701,9 @@ class Player extends Component {
|
||||
* @method addRemoteTextTrack
|
||||
*/
|
||||
addRemoteTextTrack(options) {
|
||||
return this.tech_ && this.tech_['addRemoteTextTrack'](options);
|
||||
if (this.tech_) {
|
||||
return this.tech_.addRemoteTextTrack(options);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2666,8 +2714,10 @@ class Player extends Component {
|
||||
*/
|
||||
// destructure the input into an object with a track argument, defaulting to arguments[0]
|
||||
// default the whole argument to an empty object if nothing was passed in
|
||||
removeRemoteTextTrack({track = arguments[0]} = {}) { // jshint ignore:line
|
||||
this.tech_ && this.tech_['removeRemoteTextTrack'](track);
|
||||
removeRemoteTextTrack({track = arguments[0]} = {}) {
|
||||
if (this.tech_) {
|
||||
return this.tech_.removeRemoteTextTrack(track);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2691,11 +2741,11 @@ class Player extends Component {
|
||||
}
|
||||
|
||||
// Methods to add support for
|
||||
// initialTime: function(){ return this.techCall_('initialTime'); },
|
||||
// startOffsetTime: function(){ return this.techCall_('startOffsetTime'); },
|
||||
// played: function(){ return this.techCall_('played'); },
|
||||
// defaultPlaybackRate: function(){ return this.techCall_('defaultPlaybackRate'); },
|
||||
// defaultMuted: function(){ return this.techCall_('defaultMuted'); }
|
||||
// initialTime: function() { return this.techCall_('initialTime'); },
|
||||
// startOffsetTime: function() { return this.techCall_('startOffsetTime'); },
|
||||
// played: function() { return this.techCall_('played'); },
|
||||
// defaultPlaybackRate: function() { return this.techCall_('defaultPlaybackRate'); },
|
||||
// defaultMuted: function() { return this.techCall_('defaultMuted'); }
|
||||
|
||||
/**
|
||||
* The player's language code
|
||||
@ -2713,7 +2763,7 @@ class Player extends Component {
|
||||
return this.language_;
|
||||
}
|
||||
|
||||
this.language_ = (''+code).toLowerCase();
|
||||
this.language_ = String(code).toLowerCase();
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -2726,7 +2776,7 @@ class Player extends Component {
|
||||
* @method languages
|
||||
*/
|
||||
languages() {
|
||||
return mergeOptions(Player.prototype.options_.languages, this.languages_);
|
||||
return mergeOptions(Player.prototype.options_.languages, this.languages_);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2770,16 +2820,14 @@ class Player extends Component {
|
||||
* @return {ModalDialog}
|
||||
*/
|
||||
createModal(content, options) {
|
||||
let player = this;
|
||||
|
||||
options = options || {};
|
||||
options.content = content || '';
|
||||
|
||||
let modal = new ModalDialog(player, options);
|
||||
let modal = new ModalDialog(this, options);
|
||||
|
||||
player.addChild(modal);
|
||||
modal.on('dispose', function() {
|
||||
player.removeChild(modal);
|
||||
this.addChild(modal);
|
||||
modal.on('dispose', () => {
|
||||
this.removeChild(modal);
|
||||
});
|
||||
|
||||
return modal.open();
|
||||
@ -2795,18 +2843,19 @@ class Player extends Component {
|
||||
*/
|
||||
static getTagSettings(tag) {
|
||||
let baseOptions = {
|
||||
'sources': [],
|
||||
'tracks': []
|
||||
sources: [],
|
||||
tracks: []
|
||||
};
|
||||
|
||||
const tagOptions = Dom.getElAttributes(tag);
|
||||
const dataSetup = tagOptions['data-setup'];
|
||||
|
||||
// Check if data-setup attr exists.
|
||||
if (dataSetup !== null){
|
||||
if (dataSetup !== null) {
|
||||
// Parse options JSON
|
||||
// If empty string, make it a parsable json object.
|
||||
const [err, data] = safeParseTuple(dataSetup || '{}');
|
||||
|
||||
if (err) {
|
||||
log.error(err);
|
||||
}
|
||||
@ -2819,10 +2868,11 @@ class Player extends Component {
|
||||
if (tag.hasChildNodes()) {
|
||||
const children = tag.childNodes;
|
||||
|
||||
for (let i=0, j=children.length; i<j; i++) {
|
||||
for (let i = 0, j = children.length; i < j; i++) {
|
||||
const child = children[i];
|
||||
// Change case needed: http://ejohn.org/blog/nodename-case-sensitivity/
|
||||
const childName = child.nodeName.toLowerCase();
|
||||
|
||||
if (childName === 'source') {
|
||||
baseOptions.sources.push(Dom.getElAttributes(child));
|
||||
} else if (childName === 'track') {
|
||||
@ -2844,6 +2894,7 @@ class Player extends Component {
|
||||
Player.players = {};
|
||||
|
||||
let navigator = window.navigator;
|
||||
|
||||
/*
|
||||
* Player instance options, surfaced using options
|
||||
* options = Player.prototype.options_
|
||||
@ -2854,14 +2905,14 @@ let navigator = window.navigator;
|
||||
*/
|
||||
Player.prototype.options_ = {
|
||||
// Default order of fallback technology
|
||||
techOrder: ['html5','flash'],
|
||||
techOrder: ['html5', 'flash'],
|
||||
// techOrder: ['flash','html5'],
|
||||
|
||||
html5: {},
|
||||
flash: {},
|
||||
|
||||
// defaultVolume: 0.85,
|
||||
defaultVolume: 0.00, // The freakin seaguls are driving me crazy!
|
||||
defaultVolume: 0.00,
|
||||
|
||||
// default inactivity timeout
|
||||
inactivityTimeout: 2000,
|
||||
@ -2892,47 +2943,49 @@ Player.prototype.options_ = {
|
||||
notSupportedMessage: 'No compatible source was found for this media.'
|
||||
};
|
||||
|
||||
// The following no-op expressions are here only for purposes of documentation.
|
||||
|
||||
/**
|
||||
* Fired when the user agent begins looking for media data
|
||||
*
|
||||
* @event loadstart
|
||||
*/
|
||||
Player.prototype.handleTechLoadStart_;
|
||||
Player.prototype.handleTechLoadStart_; // eslint-disable-line
|
||||
|
||||
/**
|
||||
* Fired when the player has initial duration and dimension information
|
||||
*
|
||||
* @event loadedmetadata
|
||||
*/
|
||||
Player.prototype.handleLoadedMetaData_;
|
||||
Player.prototype.handleLoadedMetaData_; // eslint-disable-line
|
||||
|
||||
/**
|
||||
* Fired when the player receives text data
|
||||
*
|
||||
* @event textdata
|
||||
*/
|
||||
Player.prototype.handleTextData_;
|
||||
Player.prototype.handleTextData_; // eslint-disable-line
|
||||
|
||||
/**
|
||||
* Fired when the player has downloaded data at the current playback position
|
||||
*
|
||||
* @event loadeddata
|
||||
*/
|
||||
Player.prototype.handleLoadedData_;
|
||||
Player.prototype.handleLoadedData_; // eslint-disable-line
|
||||
|
||||
/**
|
||||
* Fired when the user is active, e.g. moves the mouse over the player
|
||||
*
|
||||
* @event useractive
|
||||
*/
|
||||
Player.prototype.handleUserActive_;
|
||||
Player.prototype.handleUserActive_; // eslint-disable-line
|
||||
|
||||
/**
|
||||
* Fired when the user is inactive, e.g. a short delay after the last mouse move or control interaction
|
||||
*
|
||||
* @event userinactive
|
||||
*/
|
||||
Player.prototype.handleUserInactive_;
|
||||
Player.prototype.handleUserInactive_; // eslint-disable-line
|
||||
|
||||
/**
|
||||
* Fired when the current playback position has changed *
|
||||
@ -2941,31 +2994,31 @@ Player.prototype.handleUserInactive_;
|
||||
*
|
||||
* @event timeupdate
|
||||
*/
|
||||
Player.prototype.handleTimeUpdate_;
|
||||
Player.prototype.handleTimeUpdate_; // eslint-disable-line
|
||||
|
||||
/**
|
||||
* Fired when video playback ends
|
||||
*
|
||||
* @event ended
|
||||
*/
|
||||
Player.prototype.handleTechEnded_;
|
||||
Player.prototype.handleTechEnded_; // eslint-disable-line
|
||||
|
||||
/**
|
||||
* Fired when the volume changes
|
||||
*
|
||||
* @event volumechange
|
||||
*/
|
||||
Player.prototype.handleVolumeChange_;
|
||||
Player.prototype.handleVolumeChange_; // eslint-disable-line
|
||||
|
||||
/**
|
||||
* Fired when an error occurs
|
||||
*
|
||||
* @event error
|
||||
*/
|
||||
Player.prototype.handleError_;
|
||||
Player.prototype.handleError_ = Player.prototype.handleError_;
|
||||
|
||||
Player.prototype.flexNotSupported_ = function() {
|
||||
var elem = document.createElement('i');
|
||||
let elem = document.createElement('i');
|
||||
|
||||
// Note: We don't actually use flexBasis (or flexOrder), but it's one of the more
|
||||
// common flex features that we can rely on when checking for flex support.
|
||||
@ -2973,7 +3026,8 @@ Player.prototype.flexNotSupported_ = function() {
|
||||
'webkitFlexBasis' in elem.style ||
|
||||
'mozFlexBasis' in elem.style ||
|
||||
'msFlexBasis' in elem.style ||
|
||||
'msFlexOrder' in elem.style /* IE10-specific (2012 flex spec) */);
|
||||
// IE10-specific (2012 flex spec)
|
||||
'msFlexOrder' in elem.style);
|
||||
};
|
||||
|
||||
Component.registerComponent('Player', Player);
|
||||
|
@ -10,7 +10,7 @@ import Player from './player.js';
|
||||
* @param {Function} init The function that is run when the player inits
|
||||
* @method plugin
|
||||
*/
|
||||
var plugin = function(name, init){
|
||||
const plugin = function(name, init) {
|
||||
Player.prototype[name] = init;
|
||||
};
|
||||
|
||||
|
@ -3,10 +3,6 @@
|
||||
*/
|
||||
import ClickableComponent from '../clickable-component.js';
|
||||
import Component from '../component.js';
|
||||
import Popup from './popup.js';
|
||||
import * as Dom from '../utils/dom.js';
|
||||
import * as Fn from '../utils/fn.js';
|
||||
import toTitleCase from '../utils/to-title-case.js';
|
||||
|
||||
/**
|
||||
* A button class with a popup control
|
||||
@ -18,7 +14,7 @@ import toTitleCase from '../utils/to-title-case.js';
|
||||
*/
|
||||
class PopupButton extends ClickableComponent {
|
||||
|
||||
constructor(player, options={}){
|
||||
constructor(player, options = {}) {
|
||||
super(player, options);
|
||||
|
||||
this.update();
|
||||
@ -30,7 +26,7 @@ class PopupButton extends ClickableComponent {
|
||||
* @method update
|
||||
*/
|
||||
update() {
|
||||
let popup = this.createPopup();
|
||||
const popup = this.createPopup();
|
||||
|
||||
if (this.popup) {
|
||||
this.removeChild(this.popup);
|
||||
@ -73,7 +69,7 @@ class PopupButton extends ClickableComponent {
|
||||
* @method buildCSSClass
|
||||
*/
|
||||
buildCSSClass() {
|
||||
var menuButtonClass = 'vjs-menu-button';
|
||||
let menuButtonClass = 'vjs-menu-button';
|
||||
|
||||
// If the inline option is passed, we want to use different styles altogether.
|
||||
if (this.options_.inline === true) {
|
||||
|
@ -22,7 +22,7 @@ class Popup extends Component {
|
||||
*/
|
||||
addItem(component) {
|
||||
this.addChild(component);
|
||||
component.on('click', Fn.bind(this, function(){
|
||||
component.on('click', Fn.bind(this, function() {
|
||||
this.unlockShowing();
|
||||
}));
|
||||
}
|
||||
@ -34,19 +34,22 @@ class Popup extends Component {
|
||||
* @method createEl
|
||||
*/
|
||||
createEl() {
|
||||
let contentElType = this.options_.contentElType || 'ul';
|
||||
const contentElType = this.options_.contentElType || 'ul';
|
||||
|
||||
this.contentEl_ = Dom.createEl(contentElType, {
|
||||
className: 'vjs-menu-content'
|
||||
});
|
||||
var el = super.createEl('div', {
|
||||
|
||||
const el = super.createEl('div', {
|
||||
append: this.contentEl_,
|
||||
className: 'vjs-menu'
|
||||
});
|
||||
|
||||
el.appendChild(this.contentEl_);
|
||||
|
||||
// Prevent clicks from bubbling up. Needed for Popup Buttons,
|
||||
// where a click on the parent is significant
|
||||
Events.on(el, 'click', function(event){
|
||||
Events.on(el, 'click', function(event) {
|
||||
event.preventDefault();
|
||||
event.stopImmediatePropagation();
|
||||
});
|
||||
|
@ -17,7 +17,7 @@ import * as browser from './utils/browser.js';
|
||||
*/
|
||||
class PosterImage extends ClickableComponent {
|
||||
|
||||
constructor(player, options){
|
||||
constructor(player, options) {
|
||||
super(player, options);
|
||||
|
||||
this.update();
|
||||
@ -90,6 +90,7 @@ class PosterImage extends ClickableComponent {
|
||||
this.fallbackImg_.src = url;
|
||||
} else {
|
||||
let backgroundImage = '';
|
||||
|
||||
// Any falsey values should stay as an empty string, otherwise
|
||||
// this will throw an extra error
|
||||
if (url) {
|
||||
|
@ -10,27 +10,29 @@ import window from 'global/window';
|
||||
|
||||
let _windowLoaded = false;
|
||||
let videojs;
|
||||
|
||||
let autoSetupTimeout;
|
||||
|
||||
// Automatically set up any tags that have a data-setup attribute
|
||||
var autoSetup = function(){
|
||||
const autoSetup = function() {
|
||||
// One day, when we stop supporting IE8, go back to this, but in the meantime...*hack hack hack*
|
||||
// var vids = Array.prototype.slice.call(document.getElementsByTagName('video'));
|
||||
// var audios = Array.prototype.slice.call(document.getElementsByTagName('audio'));
|
||||
// var mediaEls = vids.concat(audios);
|
||||
|
||||
// Because IE8 doesn't support calling slice on a node list, we need to loop through each list of elements
|
||||
// to build up a new, combined list of elements.
|
||||
var vids = document.getElementsByTagName('video');
|
||||
var audios = document.getElementsByTagName('audio');
|
||||
var mediaEls = [];
|
||||
// Because IE8 doesn't support calling slice on a node list, we need to loop
|
||||
// through each list of elements to build up a new, combined list of elements.
|
||||
const vids = document.getElementsByTagName('video');
|
||||
const audios = document.getElementsByTagName('audio');
|
||||
const mediaEls = [];
|
||||
|
||||
if (vids && vids.length > 0) {
|
||||
for(let i=0, e=vids.length; i<e; i++) {
|
||||
for (let i = 0, e = vids.length; i < e; i++) {
|
||||
mediaEls.push(vids[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (audios && audios.length > 0) {
|
||||
for(let i=0, e=audios.length; i<e; i++) {
|
||||
for (let i = 0, e = audios.length; i < e; i++) {
|
||||
mediaEls.push(audios[i]);
|
||||
}
|
||||
}
|
||||
@ -38,22 +40,23 @@ var autoSetup = function(){
|
||||
// Check if any media elements exist
|
||||
if (mediaEls && mediaEls.length > 0) {
|
||||
|
||||
for (let i=0, e=mediaEls.length; i<e; i++) {
|
||||
for (let i = 0, e = mediaEls.length; i < e; i++) {
|
||||
let mediaEl = mediaEls[i];
|
||||
|
||||
// Check if element exists, has getAttribute func.
|
||||
// IE seems to consider typeof el.getAttribute == 'object' instead of 'function' like expected, at least when loading the player immediately.
|
||||
// IE seems to consider typeof el.getAttribute == 'object' instead of
|
||||
// 'function' like expected, at least when loading the player immediately.
|
||||
if (mediaEl && mediaEl.getAttribute) {
|
||||
|
||||
// Make sure this player hasn't already been set up.
|
||||
if (mediaEl['player'] === undefined) {
|
||||
if (mediaEl.player === undefined) {
|
||||
let options = mediaEl.getAttribute('data-setup');
|
||||
|
||||
// Check if data-setup attr exists.
|
||||
// We only auto-setup if they've added the data-setup attr.
|
||||
if (options !== null) {
|
||||
// Create new video.js instance.
|
||||
let player = videojs(mediaEl);
|
||||
videojs(mediaEl);
|
||||
}
|
||||
}
|
||||
|
||||
@ -71,7 +74,7 @@ var autoSetup = function(){
|
||||
};
|
||||
|
||||
// Pause to let the DOM keep processing
|
||||
var autoSetupTimeout = function(wait, vjs){
|
||||
autoSetupTimeout = function(wait, vjs) {
|
||||
if (vjs) {
|
||||
videojs = vjs;
|
||||
}
|
||||
@ -82,12 +85,12 @@ var autoSetupTimeout = function(wait, vjs){
|
||||
if (document.readyState === 'complete') {
|
||||
_windowLoaded = true;
|
||||
} else {
|
||||
Events.one(window, 'load', function(){
|
||||
Events.one(window, 'load', function() {
|
||||
_windowLoaded = true;
|
||||
});
|
||||
}
|
||||
|
||||
var hasLoaded = function() {
|
||||
const hasLoaded = function() {
|
||||
return _windowLoaded;
|
||||
};
|
||||
|
||||
|
@ -42,7 +42,7 @@ class Slider extends Component {
|
||||
* @return {Element}
|
||||
* @method createEl
|
||||
*/
|
||||
createEl(type, props={}, attributes={}) {
|
||||
createEl(type, props = {}, attributes = {}) {
|
||||
// Add the slider element class to all sub classes
|
||||
props.className = props.className + ' vjs-slider';
|
||||
props = assign({
|
||||
@ -54,7 +54,7 @@ class Slider extends Component {
|
||||
'aria-valuenow': 0,
|
||||
'aria-valuemin': 0,
|
||||
'aria-valuemax': 100,
|
||||
tabIndex: 0
|
||||
'tabIndex': 0
|
||||
}, attributes);
|
||||
|
||||
return super.createEl(type, props, attributes);
|
||||
@ -67,7 +67,7 @@ class Slider extends Component {
|
||||
* @method handleMouseDown
|
||||
*/
|
||||
handleMouseDown(event) {
|
||||
let doc = this.bar.el_.ownerDocument;
|
||||
const doc = this.bar.el_.ownerDocument;
|
||||
|
||||
event.preventDefault();
|
||||
Dom.blockTextSelection();
|
||||
@ -96,7 +96,7 @@ class Slider extends Component {
|
||||
* @method handleMouseUp
|
||||
*/
|
||||
handleMouseUp() {
|
||||
let doc = this.bar.el_.ownerDocument;
|
||||
const doc = this.bar.el_.ownerDocument;
|
||||
|
||||
Dom.unblockTextSelection();
|
||||
|
||||
@ -119,27 +119,31 @@ class Slider extends Component {
|
||||
update() {
|
||||
// In VolumeBar init we have a setTimeout for update that pops and update to the end of the
|
||||
// execution stack. The player is destroyed before then update will cause an error
|
||||
if (!this.el_) return;
|
||||
if (!this.el_) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If scrubbing, we could use a cached value to make the handle keep up with the user's mouse.
|
||||
// On HTML5 browsers scrubbing is really smooth, but some flash players are slow, so we might want to utilize this later.
|
||||
// var progress = (this.player_.scrubbing()) ? this.player_.getCache().currentTime / this.player_.duration() : this.player_.currentTime() / this.player_.duration();
|
||||
let progress = this.getPercent();
|
||||
let bar = this.bar;
|
||||
const bar = this.bar;
|
||||
|
||||
// If there's no bar...
|
||||
if (!bar) return;
|
||||
if (!bar) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Protect against no duration and other division issues
|
||||
if (typeof progress !== 'number' ||
|
||||
progress !== progress ||
|
||||
progress < 0 ||
|
||||
progress === Infinity) {
|
||||
progress = 0;
|
||||
progress = 0;
|
||||
}
|
||||
|
||||
// Convert to a percentage for setting
|
||||
let percentage = (progress * 100).toFixed(2) + '%';
|
||||
const percentage = (progress * 100).toFixed(2) + '%';
|
||||
|
||||
// Set the new bar width or height
|
||||
if (this.vertical()) {
|
||||
@ -155,8 +159,9 @@ class Slider extends Component {
|
||||
* @param {Object} event Event object
|
||||
* @method calculateDistance
|
||||
*/
|
||||
calculateDistance(event){
|
||||
let position = Dom.getPointerPosition(this.el_, event);
|
||||
calculateDistance(event) {
|
||||
const position = Dom.getPointerPosition(this.el_, event);
|
||||
|
||||
if (this.vertical()) {
|
||||
return position.y;
|
||||
}
|
||||
@ -179,10 +184,13 @@ class Slider extends Component {
|
||||
* @method handleKeyPress
|
||||
*/
|
||||
handleKeyPress(event) {
|
||||
if (event.which === 37 || event.which === 40) { // Left and Down Arrows
|
||||
// Left and Down Arrows
|
||||
if (event.which === 37 || event.which === 40) {
|
||||
event.preventDefault();
|
||||
this.stepBack();
|
||||
} else if (event.which === 38 || event.which === 39) { // Up and Right Arrows
|
||||
|
||||
// Up and Right Arrows
|
||||
} else if (event.which === 38 || event.which === 39) {
|
||||
event.preventDefault();
|
||||
this.stepForward();
|
||||
}
|
||||
|
@ -17,17 +17,19 @@ function FlashRtmpDecorator(Flash) {
|
||||
stream: ''
|
||||
};
|
||||
|
||||
if (!src) return parts;
|
||||
if (!src) {
|
||||
return parts;
|
||||
}
|
||||
|
||||
// Look for the normal URL separator we expect, '&'.
|
||||
// If found, we split the URL into two pieces around the
|
||||
// first '&'.
|
||||
let connEnd = src.search(/&(?!\w+=)/);
|
||||
let streamBegin;
|
||||
|
||||
if (connEnd !== -1) {
|
||||
streamBegin = connEnd + 1;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// If there's not a '&', we use the last '/' as the delimiter.
|
||||
connEnd = streamBegin = src.lastIndexOf('/') + 1;
|
||||
if (connEnd === 0) {
|
||||
@ -35,6 +37,7 @@ function FlashRtmpDecorator(Flash) {
|
||||
connEnd = streamBegin = src.length;
|
||||
}
|
||||
}
|
||||
|
||||
parts.connection = src.substring(0, connEnd);
|
||||
parts.stream = src.substring(streamBegin, src.length);
|
||||
|
||||
@ -64,7 +67,7 @@ function FlashRtmpDecorator(Flash) {
|
||||
* @param {String} type The mimetype to check
|
||||
* @return {String} 'probably', 'maybe', or '' (empty string)
|
||||
*/
|
||||
Flash.rtmpSourceHandler.canPlayType = function(type){
|
||||
Flash.rtmpSourceHandler.canPlayType = function(type) {
|
||||
if (Flash.isStreamingType(type)) {
|
||||
return 'maybe';
|
||||
}
|
||||
@ -78,7 +81,7 @@ function FlashRtmpDecorator(Flash) {
|
||||
* @param {Object} options The options passed to the tech
|
||||
* @return {String} 'probably', 'maybe', or '' (empty string)
|
||||
*/
|
||||
Flash.rtmpSourceHandler.canHandleSource = function(source, options){
|
||||
Flash.rtmpSourceHandler.canHandleSource = function(source, options) {
|
||||
let can = Flash.rtmpSourceHandler.canPlayType(source.type);
|
||||
|
||||
if (can) {
|
||||
@ -100,11 +103,11 @@ function FlashRtmpDecorator(Flash) {
|
||||
* @param {Flash} tech The instance of the Flash tech
|
||||
* @param {Object} options The options to pass to the source
|
||||
*/
|
||||
Flash.rtmpSourceHandler.handleSource = function(source, tech, options){
|
||||
Flash.rtmpSourceHandler.handleSource = function(source, tech, options) {
|
||||
let srcParts = Flash.streamToParts(source.src);
|
||||
|
||||
tech['setRtmpConnection'](srcParts.connection);
|
||||
tech['setRtmpStream'](srcParts.stream);
|
||||
tech.setRtmpConnection(srcParts.connection);
|
||||
tech.setRtmpStream(srcParts.stream);
|
||||
};
|
||||
|
||||
// Register the native source handler
|
||||
|
@ -15,6 +15,7 @@ import window from 'global/window';
|
||||
import assign from 'object.assign';
|
||||
|
||||
let navigator = window.navigator;
|
||||
|
||||
/**
|
||||
* Flash Media Controller - Wrapper for fallback SWF API
|
||||
*
|
||||
@ -25,12 +26,12 @@ let navigator = window.navigator;
|
||||
*/
|
||||
class Flash extends Tech {
|
||||
|
||||
constructor(options, ready){
|
||||
constructor(options, ready) {
|
||||
super(options, ready);
|
||||
|
||||
// Set the source when ready
|
||||
if (options.source) {
|
||||
this.ready(function(){
|
||||
this.ready(function() {
|
||||
this.setSource(options.source);
|
||||
}, true);
|
||||
}
|
||||
@ -38,7 +39,7 @@ class Flash extends Tech {
|
||||
// Having issues with Flash reloading on certain page actions (hide/resize/fullscreen) in certain browsers
|
||||
// This allows resetting the playhead when we catch the reload
|
||||
if (options.startTime) {
|
||||
this.ready(function(){
|
||||
this.ready(function() {
|
||||
this.load();
|
||||
this.play();
|
||||
this.currentTime(options.startTime);
|
||||
@ -84,29 +85,32 @@ class Flash extends Tech {
|
||||
let flashVars = assign({
|
||||
|
||||
// SWF Callback Functions
|
||||
'readyFunction': 'videojs.Flash.onReady',
|
||||
'eventProxyFunction': 'videojs.Flash.onEvent',
|
||||
'errorEventProxyFunction': 'videojs.Flash.onError',
|
||||
readyFunction: 'videojs.Flash.onReady',
|
||||
eventProxyFunction: 'videojs.Flash.onEvent',
|
||||
errorEventProxyFunction: 'videojs.Flash.onError',
|
||||
|
||||
// Player Settings
|
||||
'autoplay': options.autoplay,
|
||||
'preload': options.preload,
|
||||
'loop': options.loop,
|
||||
'muted': options.muted
|
||||
autoplay: options.autoplay,
|
||||
preload: options.preload,
|
||||
loop: options.loop,
|
||||
muted: options.muted
|
||||
|
||||
}, options.flashVars);
|
||||
|
||||
// Merge default parames with ones passed in
|
||||
let params = assign({
|
||||
'wmode': 'opaque', // Opaque is needed to overlay controls, but can affect playback performance
|
||||
'bgcolor': '#000000' // Using bgcolor prevents a white flash when the object is loading
|
||||
// Opaque is needed to overlay controls, but can affect playback performance
|
||||
wmode: 'opaque',
|
||||
// Using bgcolor prevents a white flash when the object is loading
|
||||
bgcolor: '#000000'
|
||||
}, options.params);
|
||||
|
||||
// Merge default attributes with ones passed in
|
||||
let attributes = assign({
|
||||
'id': objId,
|
||||
'name': objId, // Both ID and Name needed or swf to identify itself
|
||||
'class': 'vjs-tech'
|
||||
// Both ID and Name needed or swf to identify itself
|
||||
id: objId,
|
||||
name: objId,
|
||||
class: 'vjs-tech'
|
||||
}, options.attributes);
|
||||
|
||||
this.el_ = Flash.embed(options.swf, flashVars, params, attributes);
|
||||
@ -167,8 +171,7 @@ class Flash extends Tech {
|
||||
// Currently the SWF doesn't autoplay if you load a source later.
|
||||
// e.g. Load player w/ no source, wait 2s, set src.
|
||||
if (this.autoplay()) {
|
||||
var tech = this;
|
||||
this.setTimeout(function(){ tech.play(); }, 0);
|
||||
this.setTimeout(() => this.play(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
@ -188,6 +191,7 @@ class Flash extends Tech {
|
||||
*/
|
||||
setCurrentTime(time) {
|
||||
let seekable = this.seekable();
|
||||
|
||||
if (seekable.length) {
|
||||
// clamp to the current seekable range
|
||||
time = time > seekable.start(0) ? time : seekable.start(0);
|
||||
@ -224,9 +228,8 @@ class Flash extends Tech {
|
||||
currentSrc() {
|
||||
if (this.currentSource_) {
|
||||
return this.currentSource_.src;
|
||||
} else {
|
||||
return this.el_.vjs_getProperty('currentSrc');
|
||||
}
|
||||
return this.el_.vjs_getProperty('currentSrc');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -237,10 +240,10 @@ class Flash extends Tech {
|
||||
duration() {
|
||||
if (this.readyState() === 0) {
|
||||
return NaN;
|
||||
} else {
|
||||
let duration = this.el_.vjs_getProperty('duration');
|
||||
return duration >= 0 ? duration : Infinity;
|
||||
}
|
||||
let duration = this.el_.vjs_getProperty('duration');
|
||||
|
||||
return duration >= 0 ? duration : Infinity;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -276,6 +279,7 @@ class Flash extends Tech {
|
||||
*/
|
||||
seekable() {
|
||||
const duration = this.duration();
|
||||
|
||||
if (duration === 0) {
|
||||
return createTimeRange();
|
||||
}
|
||||
@ -290,6 +294,7 @@ class Flash extends Tech {
|
||||
*/
|
||||
buffered() {
|
||||
let ranges = this.el_.vjs_getProperty('buffered');
|
||||
|
||||
if (ranges.length === 0) {
|
||||
return createTimeRange();
|
||||
}
|
||||
@ -305,7 +310,8 @@ class Flash extends Tech {
|
||||
* @method supportsFullScreen
|
||||
*/
|
||||
supportsFullScreen() {
|
||||
return false; // Flash does not allow fullscreen through javascript
|
||||
// Flash does not allow fullscreen through javascript
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -322,18 +328,23 @@ class Flash extends Tech {
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Create setters and getters for attributes
|
||||
const _api = Flash.prototype;
|
||||
const _readWrite = 'rtmpConnection,rtmpStream,preload,defaultPlaybackRate,playbackRate,autoplay,loop,mediaGroup,controller,controls,volume,muted,defaultMuted'.split(',');
|
||||
const _readOnly = 'networkState,readyState,initialTime,startOffsetTime,paused,ended,videoWidth,videoHeight'.split(',');
|
||||
|
||||
function _createSetter(attr){
|
||||
var attrUpper = attr.charAt(0).toUpperCase() + attr.slice(1);
|
||||
_api['set'+attrUpper] = function(val){ return this.el_.vjs_setProperty(attr, val); };
|
||||
function _createSetter(attr) {
|
||||
let attrUpper = attr.charAt(0).toUpperCase() + attr.slice(1);
|
||||
|
||||
_api['set' + attrUpper] = function(val) {
|
||||
return this.el_.vjs_setProperty(attr, val);
|
||||
};
|
||||
}
|
||||
|
||||
function _createGetter(attr) {
|
||||
_api[attr] = function(){ return this.el_.vjs_getProperty(attr); };
|
||||
_api[attr] = function() {
|
||||
return this.el_.vjs_getProperty(attr);
|
||||
};
|
||||
}
|
||||
|
||||
// Create getter and setters for all read/write attributes
|
||||
@ -349,7 +360,7 @@ for (let i = 0; i < _readOnly.length; i++) {
|
||||
|
||||
/* Flash Support Testing -------------------------------------------------------- */
|
||||
|
||||
Flash.isSupported = function(){
|
||||
Flash.isSupported = function() {
|
||||
return Flash.version()[0] >= 10;
|
||||
// return swfobject.hasFlashPlayerVersion('10');
|
||||
};
|
||||
@ -371,7 +382,7 @@ Flash.nativeSourceHandler = {};
|
||||
* @param {String} type The mimetype to check
|
||||
* @return {String} 'probably', 'maybe', or '' (empty string)
|
||||
*/
|
||||
Flash.nativeSourceHandler.canPlayType = function(type){
|
||||
Flash.nativeSourceHandler.canPlayType = function(type) {
|
||||
if (type in Flash.formats) {
|
||||
return 'maybe';
|
||||
}
|
||||
@ -386,11 +397,12 @@ Flash.nativeSourceHandler.canPlayType = function(type){
|
||||
* @param {Object} options The options passed to the tech
|
||||
* @return {String} 'probably', 'maybe', or '' (empty string)
|
||||
*/
|
||||
Flash.nativeSourceHandler.canHandleSource = function(source, options){
|
||||
var type;
|
||||
Flash.nativeSourceHandler.canHandleSource = function(source, options) {
|
||||
let type;
|
||||
|
||||
function guessMimeType(src) {
|
||||
var ext = Url.getFileExtension(src);
|
||||
let ext = Url.getFileExtension(src);
|
||||
|
||||
if (ext) {
|
||||
return `video/${ext}`;
|
||||
}
|
||||
@ -416,7 +428,7 @@ Flash.nativeSourceHandler.canHandleSource = function(source, options){
|
||||
* @param {Flash} tech The instance of the Flash tech
|
||||
* @param {Object} options The options to pass to the source
|
||||
*/
|
||||
Flash.nativeSourceHandler.handleSource = function(source, tech, options){
|
||||
Flash.nativeSourceHandler.handleSource = function(source, tech, options) {
|
||||
tech.setSrc(source.src);
|
||||
};
|
||||
|
||||
@ -424,7 +436,7 @@ Flash.nativeSourceHandler.handleSource = function(source, tech, options){
|
||||
* Clean up the source handler when disposing the player or switching sources..
|
||||
* (no cleanup is needed when supporting the format natively)
|
||||
*/
|
||||
Flash.nativeSourceHandler.dispose = function(){};
|
||||
Flash.nativeSourceHandler.dispose = function() {};
|
||||
|
||||
// Register the native source handler
|
||||
Flash.registerSourceHandler(Flash.nativeSourceHandler);
|
||||
@ -436,7 +448,7 @@ Flash.formats = {
|
||||
'video/m4v': 'MP4'
|
||||
};
|
||||
|
||||
Flash.onReady = function(currSwf){
|
||||
Flash.onReady = function(currSwf) {
|
||||
let el = Dom.getEl(currSwf);
|
||||
let tech = el && el.tech;
|
||||
|
||||
@ -450,7 +462,7 @@ Flash.onReady = function(currSwf){
|
||||
|
||||
// The SWF isn't always ready when it says it is. Sometimes the API functions still need to be added to the object.
|
||||
// If it's not ready, we set a timeout to check again shortly.
|
||||
Flash.checkReady = function(tech){
|
||||
Flash.checkReady = function(tech) {
|
||||
// stop worrying if the tech has been disposed
|
||||
if (!tech.el()) {
|
||||
return;
|
||||
@ -462,20 +474,21 @@ Flash.checkReady = function(tech){
|
||||
tech.triggerReady();
|
||||
} else {
|
||||
// wait longer
|
||||
this.setTimeout(function(){
|
||||
Flash['checkReady'](tech);
|
||||
this.setTimeout(function() {
|
||||
Flash.checkReady(tech);
|
||||
}, 50);
|
||||
}
|
||||
};
|
||||
|
||||
// Trigger events from the swf on the player
|
||||
Flash.onEvent = function(swfID, eventName){
|
||||
Flash.onEvent = function(swfID, eventName) {
|
||||
let tech = Dom.getEl(swfID).tech;
|
||||
|
||||
tech.trigger(eventName, Array.prototype.slice.call(arguments, 2));
|
||||
};
|
||||
|
||||
// Log errors from the swf
|
||||
Flash.onError = function(swfID, err){
|
||||
Flash.onError = function(swfID, err) {
|
||||
const tech = Dom.getEl(swfID).tech;
|
||||
|
||||
// trigger MEDIA_ERR_SRC_NOT_SUPPORTED
|
||||
@ -488,7 +501,7 @@ Flash.onError = function(swfID, err){
|
||||
};
|
||||
|
||||
// Flash Version Check
|
||||
Flash.version = function(){
|
||||
Flash.version = function() {
|
||||
let version = '0,0,0';
|
||||
|
||||
// IE
|
||||
@ -496,18 +509,20 @@ Flash.version = function(){
|
||||
version = new window.ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version').replace(/\D+/g, ',').match(/^,?(.+),?$/)[1];
|
||||
|
||||
// other browsers
|
||||
} catch(e) {
|
||||
} catch (e) {
|
||||
try {
|
||||
if (navigator.mimeTypes['application/x-shockwave-flash'].enabledPlugin){
|
||||
if (navigator.mimeTypes['application/x-shockwave-flash'].enabledPlugin) {
|
||||
version = (navigator.plugins['Shockwave Flash 2.0'] || navigator.plugins['Shockwave Flash']).description.replace(/\D+/g, ',').match(/^,?(.+),?$/)[1];
|
||||
}
|
||||
} catch(err) {}
|
||||
} catch (err) {
|
||||
// satisfy linter
|
||||
}
|
||||
}
|
||||
return version.split(',');
|
||||
};
|
||||
|
||||
// Flash embedding method. Only used in non-iframe mode
|
||||
Flash.embed = function(swf, flashVars, params, attributes){
|
||||
Flash.embed = function(swf, flashVars, params, attributes) {
|
||||
const code = Flash.getEmbedCode(swf, flashVars, params, attributes);
|
||||
|
||||
// Get element by embedding code and retrieving created element
|
||||
@ -516,7 +531,7 @@ Flash.embed = function(swf, flashVars, params, attributes){
|
||||
return obj;
|
||||
};
|
||||
|
||||
Flash.getEmbedCode = function(swf, flashVars, params, attributes){
|
||||
Flash.getEmbedCode = function(swf, flashVars, params, attributes) {
|
||||
const objTag = '<object type="application/x-shockwave-flash" ';
|
||||
let flashVarsString = '';
|
||||
let paramsString = '';
|
||||
@ -524,36 +539,38 @@ Flash.getEmbedCode = function(swf, flashVars, params, attributes){
|
||||
|
||||
// Convert flash vars to string
|
||||
if (flashVars) {
|
||||
Object.getOwnPropertyNames(flashVars).forEach(function(key){
|
||||
Object.getOwnPropertyNames(flashVars).forEach(function(key) {
|
||||
flashVarsString += `${key}=${flashVars[key]}&`;
|
||||
});
|
||||
}
|
||||
|
||||
// Add swf, flashVars, and other default params
|
||||
params = assign({
|
||||
'movie': swf,
|
||||
'flashvars': flashVarsString,
|
||||
'allowScriptAccess': 'always', // Required to talk to swf
|
||||
'allowNetworking': 'all' // All should be default, but having security issues.
|
||||
movie: swf,
|
||||
flashvars: flashVarsString,
|
||||
// Required to talk to swf
|
||||
allowScriptAccess: 'always',
|
||||
// All should be default, but having security issues.
|
||||
allowNetworking: 'all'
|
||||
}, params);
|
||||
|
||||
// Create param tags string
|
||||
Object.getOwnPropertyNames(params).forEach(function(key){
|
||||
Object.getOwnPropertyNames(params).forEach(function(key) {
|
||||
paramsString += `<param name="${key}" value="${params[key]}" />`;
|
||||
});
|
||||
|
||||
attributes = assign({
|
||||
// Add swf to attributes (need both for IE and Others to work)
|
||||
'data': swf,
|
||||
data: swf,
|
||||
|
||||
// Default to 100% width/height
|
||||
'width': '100%',
|
||||
'height': '100%'
|
||||
width: '100%',
|
||||
height: '100%'
|
||||
|
||||
}, attributes);
|
||||
|
||||
// Create Attributes string
|
||||
Object.getOwnPropertyNames(attributes).forEach(function(key){
|
||||
Object.getOwnPropertyNames(attributes).forEach(function(key) {
|
||||
attrsString += `${key}="${attributes[key]}" `;
|
||||
});
|
||||
|
||||
|
@ -10,7 +10,6 @@ import * as Url from '../utils/url.js';
|
||||
import * as Fn from '../utils/fn.js';
|
||||
import log from '../utils/log.js';
|
||||
import tsml from 'tsml';
|
||||
import TextTrack from '../../../src/js/tracks/text-track.js';
|
||||
import * as browser from '../utils/browser.js';
|
||||
import document from 'global/document';
|
||||
import window from 'global/window';
|
||||
@ -28,7 +27,7 @@ import toTitleCase from '../utils/to-title-case.js';
|
||||
*/
|
||||
class Html5 extends Tech {
|
||||
|
||||
constructor(options, ready){
|
||||
constructor(options, ready) {
|
||||
super(options, ready);
|
||||
|
||||
const source = options.source;
|
||||
@ -74,7 +73,7 @@ class Html5 extends Tech {
|
||||
}
|
||||
}
|
||||
|
||||
for (let i=0; i<removeNodes.length; i++) {
|
||||
for (let i = 0; i < removeNodes.length; i++) {
|
||||
this.el_.removeChild(removeNodes[i]);
|
||||
}
|
||||
}
|
||||
@ -102,7 +101,7 @@ class Html5 extends Tech {
|
||||
|
||||
if (this.featuresNativeTextTracks) {
|
||||
if (crossoriginTracks) {
|
||||
log.warn(tsml`Text Tracks are being loaded from another origin but the crossorigin attribute isn't used.
|
||||
log.warn(tsml`Text Tracks are being loaded from another origin but the crossorigin attribute isn't used.
|
||||
This may prevent text tracks from loading.`);
|
||||
}
|
||||
|
||||
@ -140,7 +139,7 @@ class Html5 extends Tech {
|
||||
tl.removeEventListener('addtrack', this[`handle${capitalType}TrackAdd_`]);
|
||||
tl.removeEventListener('removetrack', this[`handle${capitalType}TrackRemove_`]);
|
||||
}
|
||||
|
||||
|
||||
// Stop removing old text tracks
|
||||
if (tl) {
|
||||
this.off('loadstart', this[`removeOld${capitalType}Tracks_`]);
|
||||
@ -164,11 +163,12 @@ class Html5 extends Tech {
|
||||
// Check if this browser supports moving the element into the box.
|
||||
// On the iPhone video will break if you move the element,
|
||||
// So we have to create a brand new element.
|
||||
if (!el || this['movingMediaElementInDOM'] === false) {
|
||||
if (!el || this.movingMediaElementInDOM === false) {
|
||||
|
||||
// If the original tag is still there, clone and remove it.
|
||||
if (el) {
|
||||
const clone = el.cloneNode(true);
|
||||
|
||||
el.parentNode.insertBefore(clone, el);
|
||||
Html5.disposeMediaElement(el);
|
||||
el = clone;
|
||||
@ -178,6 +178,7 @@ class Html5 extends Tech {
|
||||
// determine if native controls should be used
|
||||
let tagAttributes = this.options_.tag && Dom.getElAttributes(this.options_.tag);
|
||||
let attributes = mergeOptions({}, tagAttributes);
|
||||
|
||||
if (!browser.TOUCH_ENABLED || this.options_.nativeControlsForTouch !== true) {
|
||||
delete attributes.controls;
|
||||
}
|
||||
@ -192,10 +193,12 @@ class Html5 extends Tech {
|
||||
}
|
||||
|
||||
// Update specific tag settings, in case they were overridden
|
||||
let settingsAttrs = ['autoplay','preload','loop','muted'];
|
||||
let settingsAttrs = ['autoplay', 'preload', 'loop', 'muted'];
|
||||
|
||||
for (let i = settingsAttrs.length - 1; i >= 0; i--) {
|
||||
const attr = settingsAttrs[i];
|
||||
let overwriteAttrs = {};
|
||||
|
||||
if (typeof this.options_[attr] !== 'undefined') {
|
||||
overwriteAttrs[attr] = this.options_[attr];
|
||||
}
|
||||
@ -232,6 +235,7 @@ class Html5 extends Tech {
|
||||
let setLoadstartFired = function() {
|
||||
loadstartFired = true;
|
||||
};
|
||||
|
||||
this.on('loadstart', setLoadstartFired);
|
||||
|
||||
let triggerLoadstart = function() {
|
||||
@ -241,9 +245,10 @@ class Html5 extends Tech {
|
||||
this.trigger('loadstart');
|
||||
}
|
||||
};
|
||||
|
||||
this.on('loadedmetadata', triggerLoadstart);
|
||||
|
||||
this.ready(function(){
|
||||
this.ready(function() {
|
||||
this.off('loadstart', setLoadstartFired);
|
||||
this.off('loadedmetadata', triggerLoadstart);
|
||||
|
||||
@ -281,8 +286,8 @@ class Html5 extends Tech {
|
||||
}
|
||||
|
||||
// We still need to give the player time to add event listeners
|
||||
this.ready(function(){
|
||||
eventsToTrigger.forEach(function(type){
|
||||
this.ready(function() {
|
||||
eventsToTrigger.forEach(function(type) {
|
||||
this.trigger(type);
|
||||
}, this);
|
||||
});
|
||||
@ -303,7 +308,7 @@ class Html5 extends Tech {
|
||||
tt.addEventListener('addtrack', this.handleTextTrackAdd_);
|
||||
tt.addEventListener('removetrack', this.handleTextTrackRemove_);
|
||||
}
|
||||
|
||||
|
||||
// Remove (native) texttracks that are not used anymore
|
||||
this.on('loadstart', this.removeOldTextTracks_);
|
||||
}
|
||||
@ -311,6 +316,7 @@ class Html5 extends Tech {
|
||||
|
||||
handleTextTrackChange(e) {
|
||||
let tt = this.textTracks();
|
||||
|
||||
this.textTracks().trigger({
|
||||
type: 'change',
|
||||
target: tt,
|
||||
@ -329,6 +335,7 @@ class Html5 extends Tech {
|
||||
|
||||
handleVideoTrackChange_(e) {
|
||||
let vt = this.videoTracks();
|
||||
|
||||
this.videoTracks().trigger({
|
||||
type: 'change',
|
||||
target: vt,
|
||||
@ -347,6 +354,7 @@ class Html5 extends Tech {
|
||||
|
||||
handleAudioTrackChange_(e) {
|
||||
let audioTrackList = this.audioTracks();
|
||||
|
||||
this.audioTracks().trigger({
|
||||
type: 'change',
|
||||
target: audioTrackList,
|
||||
@ -374,14 +382,15 @@ class Html5 extends Tech {
|
||||
// This will loop over the techTracks and check if they are still used by the HTML5 video element
|
||||
// If not, they will be removed from the emulated list
|
||||
let removeTracks = [];
|
||||
|
||||
if (!elTracks) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = 0; i < techTracks.length; i++) {
|
||||
let techTrack = techTracks[i];
|
||||
|
||||
let found = false;
|
||||
|
||||
for (let j = 0; j < elTracks.length; j++) {
|
||||
if (elTracks[j] === techTrack) {
|
||||
found = true;
|
||||
@ -396,6 +405,7 @@ class Html5 extends Tech {
|
||||
|
||||
for (let i = 0; i < removeTracks.length; i++) {
|
||||
const track = removeTracks[i];
|
||||
|
||||
techTracks.removeTrack_(track);
|
||||
}
|
||||
}
|
||||
@ -403,18 +413,21 @@ class Html5 extends Tech {
|
||||
removeOldTextTracks_() {
|
||||
const techTracks = this.textTracks();
|
||||
const elTracks = this.el().textTracks;
|
||||
|
||||
this.removeOldTracks_(techTracks, elTracks);
|
||||
}
|
||||
|
||||
removeOldAudioTracks_() {
|
||||
const techTracks = this.audioTracks();
|
||||
const elTracks = this.el().audioTracks;
|
||||
|
||||
this.removeOldTracks_(techTracks, elTracks);
|
||||
}
|
||||
|
||||
removeOldVideoTracks_() {
|
||||
const techTracks = this.videoTracks();
|
||||
const elTracks = this.el().videoTracks;
|
||||
|
||||
this.removeOldTracks_(techTracks, elTracks);
|
||||
}
|
||||
|
||||
@ -423,14 +436,18 @@ class Html5 extends Tech {
|
||||
*
|
||||
* @method play
|
||||
*/
|
||||
play() { this.el_.play(); }
|
||||
play() {
|
||||
this.el_.play();
|
||||
}
|
||||
|
||||
/**
|
||||
* Pause for html5 tech
|
||||
*
|
||||
* @method pause
|
||||
*/
|
||||
pause() { this.el_.pause(); }
|
||||
pause() {
|
||||
this.el_.pause();
|
||||
}
|
||||
|
||||
/**
|
||||
* Paused for html5 tech
|
||||
@ -438,7 +455,9 @@ class Html5 extends Tech {
|
||||
* @return {Boolean}
|
||||
* @method paused
|
||||
*/
|
||||
paused() { return this.el_.paused; }
|
||||
paused() {
|
||||
return this.el_.paused;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current time
|
||||
@ -446,7 +465,9 @@ class Html5 extends Tech {
|
||||
* @return {Number}
|
||||
* @method currentTime
|
||||
*/
|
||||
currentTime() { return this.el_.currentTime; }
|
||||
currentTime() {
|
||||
return this.el_.currentTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set current time
|
||||
@ -457,7 +478,7 @@ class Html5 extends Tech {
|
||||
setCurrentTime(seconds) {
|
||||
try {
|
||||
this.el_.currentTime = seconds;
|
||||
} catch(e) {
|
||||
} catch (e) {
|
||||
log(e, 'Video is not ready. (Video.js)');
|
||||
// this.warning(VideoJS.warnings.videoNotReady);
|
||||
}
|
||||
@ -469,7 +490,9 @@ class Html5 extends Tech {
|
||||
* @return {Number}
|
||||
* @method duration
|
||||
*/
|
||||
duration() { return this.el_.duration || 0; }
|
||||
duration() {
|
||||
return this.el_.duration || 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a TimeRange object that represents the intersection
|
||||
@ -479,7 +502,9 @@ class Html5 extends Tech {
|
||||
* @return {TimeRangeObject}
|
||||
* @method buffered
|
||||
*/
|
||||
buffered() { return this.el_.buffered; }
|
||||
buffered() {
|
||||
return this.el_.buffered;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get volume level
|
||||
@ -487,7 +512,9 @@ class Html5 extends Tech {
|
||||
* @return {Number}
|
||||
* @method volume
|
||||
*/
|
||||
volume() { return this.el_.volume; }
|
||||
volume() {
|
||||
return this.el_.volume;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set volume level
|
||||
@ -495,7 +522,9 @@ class Html5 extends Tech {
|
||||
* @param {Number} percentAsDecimal Volume percent as a decimal
|
||||
* @method setVolume
|
||||
*/
|
||||
setVolume(percentAsDecimal) { this.el_.volume = percentAsDecimal; }
|
||||
setVolume(percentAsDecimal) {
|
||||
this.el_.volume = percentAsDecimal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get if muted
|
||||
@ -503,7 +532,9 @@ class Html5 extends Tech {
|
||||
* @return {Boolean}
|
||||
* @method muted
|
||||
*/
|
||||
muted() { return this.el_.muted; }
|
||||
muted() {
|
||||
return this.el_.muted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set muted
|
||||
@ -511,7 +542,9 @@ class Html5 extends Tech {
|
||||
* @param {Boolean} If player is to be muted or note
|
||||
* @method setMuted
|
||||
*/
|
||||
setMuted(muted) { this.el_.muted = muted; }
|
||||
setMuted(muted) {
|
||||
this.el_.muted = muted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get player width
|
||||
@ -519,7 +552,9 @@ class Html5 extends Tech {
|
||||
* @return {Number}
|
||||
* @method width
|
||||
*/
|
||||
width() { return this.el_.offsetWidth; }
|
||||
width() {
|
||||
return this.el_.offsetWidth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get player height
|
||||
@ -527,7 +562,9 @@ class Html5 extends Tech {
|
||||
* @return {Number}
|
||||
* @method height
|
||||
*/
|
||||
height() { return this.el_.offsetHeight; }
|
||||
height() {
|
||||
return this.el_.offsetHeight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get if there is fullscreen support
|
||||
@ -538,8 +575,9 @@ class Html5 extends Tech {
|
||||
supportsFullScreen() {
|
||||
if (typeof this.el_.webkitEnterFullScreen === 'function') {
|
||||
let userAgent = window.navigator.userAgent;
|
||||
|
||||
// Seems to be broken in Chromium/Chrome && Safari in Leopard
|
||||
if (/Android/.test(userAgent) || !/Chrome|Mac OS X 10.5/.test(userAgent)) {
|
||||
if ((/Android/).test(userAgent) || !(/Chrome|Mac OS X 10.5/).test(userAgent)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -552,7 +590,7 @@ class Html5 extends Tech {
|
||||
* @method enterFullScreen
|
||||
*/
|
||||
enterFullScreen() {
|
||||
var video = this.el_;
|
||||
let video = this.el_;
|
||||
|
||||
if ('webkitDisplayingFullscreen' in video) {
|
||||
this.one('webkitbeginfullscreen', function() {
|
||||
@ -571,7 +609,7 @@ class Html5 extends Tech {
|
||||
|
||||
// playing and pausing synchronously during the transition to fullscreen
|
||||
// can get iOS ~6.1 devices into a play/pause loop
|
||||
this.setTimeout(function(){
|
||||
this.setTimeout(function() {
|
||||
video.pause();
|
||||
video.webkitEnterFullScreen();
|
||||
}, 0);
|
||||
@ -599,10 +637,10 @@ class Html5 extends Tech {
|
||||
src(src) {
|
||||
if (src === undefined) {
|
||||
return this.el_.src;
|
||||
} else {
|
||||
// Setting src through `src` instead of `setSrc` will be deprecated
|
||||
this.setSrc(src);
|
||||
}
|
||||
|
||||
// Setting src through `src` instead of `setSrc` will be deprecated
|
||||
this.setSrc(src);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -621,7 +659,7 @@ class Html5 extends Tech {
|
||||
*
|
||||
* @method load
|
||||
*/
|
||||
load(){
|
||||
load() {
|
||||
this.el_.load();
|
||||
}
|
||||
|
||||
@ -643,9 +681,8 @@ class Html5 extends Tech {
|
||||
currentSrc() {
|
||||
if (this.currentSource_) {
|
||||
return this.currentSource_.src;
|
||||
} else {
|
||||
return this.el_.currentSrc;
|
||||
}
|
||||
return this.el_.currentSrc;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -654,7 +691,9 @@ class Html5 extends Tech {
|
||||
* @return {String}
|
||||
* @method poster
|
||||
*/
|
||||
poster() { return this.el_.poster; }
|
||||
poster() {
|
||||
return this.el_.poster;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set poster
|
||||
@ -662,7 +701,9 @@ class Html5 extends Tech {
|
||||
* @param {String} val URL to poster image
|
||||
* @method
|
||||
*/
|
||||
setPoster(val) { this.el_.poster = val; }
|
||||
setPoster(val) {
|
||||
this.el_.poster = val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get preload attribute
|
||||
@ -670,7 +711,9 @@ class Html5 extends Tech {
|
||||
* @return {String}
|
||||
* @method preload
|
||||
*/
|
||||
preload() { return this.el_.preload; }
|
||||
preload() {
|
||||
return this.el_.preload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set preload attribute
|
||||
@ -678,7 +721,9 @@ class Html5 extends Tech {
|
||||
* @param {String} val Value for preload attribute
|
||||
* @method setPreload
|
||||
*/
|
||||
setPreload(val) { this.el_.preload = val; }
|
||||
setPreload(val) {
|
||||
this.el_.preload = val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get autoplay attribute
|
||||
@ -686,7 +731,9 @@ class Html5 extends Tech {
|
||||
* @return {String}
|
||||
* @method autoplay
|
||||
*/
|
||||
autoplay() { return this.el_.autoplay; }
|
||||
autoplay() {
|
||||
return this.el_.autoplay;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set autoplay attribute
|
||||
@ -694,7 +741,9 @@ class Html5 extends Tech {
|
||||
* @param {String} val Value for preload attribute
|
||||
* @method setAutoplay
|
||||
*/
|
||||
setAutoplay(val) { this.el_.autoplay = val; }
|
||||
setAutoplay(val) {
|
||||
this.el_.autoplay = val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get controls attribute
|
||||
@ -702,7 +751,9 @@ class Html5 extends Tech {
|
||||
* @return {String}
|
||||
* @method controls
|
||||
*/
|
||||
controls() { return this.el_.controls; }
|
||||
controls() {
|
||||
return this.el_.controls;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set controls attribute
|
||||
@ -710,7 +761,9 @@ class Html5 extends Tech {
|
||||
* @param {String} val Value for controls attribute
|
||||
* @method setControls
|
||||
*/
|
||||
setControls(val) { this.el_.controls = !!val; }
|
||||
setControls(val) {
|
||||
this.el_.controls = !!val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get loop attribute
|
||||
@ -718,7 +771,9 @@ class Html5 extends Tech {
|
||||
* @return {String}
|
||||
* @method loop
|
||||
*/
|
||||
loop() { return this.el_.loop; }
|
||||
loop() {
|
||||
return this.el_.loop;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set loop attribute
|
||||
@ -726,7 +781,9 @@ class Html5 extends Tech {
|
||||
* @param {String} val Value for loop attribute
|
||||
* @method setLoop
|
||||
*/
|
||||
setLoop(val) { this.el_.loop = val; }
|
||||
setLoop(val) {
|
||||
this.el_.loop = val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get error value
|
||||
@ -734,7 +791,9 @@ class Html5 extends Tech {
|
||||
* @return {String}
|
||||
* @method error
|
||||
*/
|
||||
error() { return this.el_.error; }
|
||||
error() {
|
||||
return this.el_.error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether or not the player is in the "seeking" state
|
||||
@ -742,7 +801,9 @@ class Html5 extends Tech {
|
||||
* @return {Boolean}
|
||||
* @method seeking
|
||||
*/
|
||||
seeking() { return this.el_.seeking; }
|
||||
seeking() {
|
||||
return this.el_.seeking;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a TimeRanges object that represents the
|
||||
@ -752,7 +813,9 @@ class Html5 extends Tech {
|
||||
* @return {TimeRangeObject}
|
||||
* @method seekable
|
||||
*/
|
||||
seekable() { return this.el_.seekable; }
|
||||
seekable() {
|
||||
return this.el_.seekable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get if video ended
|
||||
@ -760,7 +823,9 @@ class Html5 extends Tech {
|
||||
* @return {Boolean}
|
||||
* @method ended
|
||||
*/
|
||||
ended() { return this.el_.ended; }
|
||||
ended() {
|
||||
return this.el_.ended;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of the muted content attribute
|
||||
@ -770,7 +835,9 @@ class Html5 extends Tech {
|
||||
* @return {Boolean}
|
||||
* @method defaultMuted
|
||||
*/
|
||||
defaultMuted() { return this.el_.defaultMuted; }
|
||||
defaultMuted() {
|
||||
return this.el_.defaultMuted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get desired speed at which the media resource is to play
|
||||
@ -778,7 +845,9 @@ class Html5 extends Tech {
|
||||
* @return {Number}
|
||||
* @method playbackRate
|
||||
*/
|
||||
playbackRate() { return this.el_.playbackRate; }
|
||||
playbackRate() {
|
||||
return this.el_.playbackRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a TimeRanges object that represents the ranges of the
|
||||
@ -787,7 +856,9 @@ class Html5 extends Tech {
|
||||
* timeline that has been reached through normal playback
|
||||
* @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-played
|
||||
*/
|
||||
played() { return this.el_.played; }
|
||||
played() {
|
||||
return this.el_.played;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set desired speed at which the media resource is to play
|
||||
@ -795,7 +866,9 @@ class Html5 extends Tech {
|
||||
* @param {Number} val Speed at which the media resource is to play
|
||||
* @method setPlaybackRate
|
||||
*/
|
||||
setPlaybackRate(val) { this.el_.playbackRate = val; }
|
||||
setPlaybackRate(val) {
|
||||
this.el_.playbackRate = val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current state of network activity for the element, from
|
||||
@ -808,7 +881,9 @@ class Html5 extends Tech {
|
||||
* @return {Number}
|
||||
* @method networkState
|
||||
*/
|
||||
networkState() { return this.el_.networkState; }
|
||||
networkState() {
|
||||
return this.el_.networkState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a value that expresses the current state of the element
|
||||
@ -823,7 +898,9 @@ class Html5 extends Tech {
|
||||
* @return {Number}
|
||||
* @method readyState
|
||||
*/
|
||||
readyState() { return this.el_.readyState; }
|
||||
readyState() {
|
||||
return this.el_.readyState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get width of video
|
||||
@ -831,7 +908,9 @@ class Html5 extends Tech {
|
||||
* @return {Number}
|
||||
* @method videoWidth
|
||||
*/
|
||||
videoWidth() { return this.el_.videoWidth; }
|
||||
videoWidth() {
|
||||
return this.el_.videoWidth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get height of video
|
||||
@ -839,7 +918,9 @@ class Html5 extends Tech {
|
||||
* @return {Number}
|
||||
* @method videoHeight
|
||||
*/
|
||||
videoHeight() { return this.el_.videoHeight; }
|
||||
videoHeight() {
|
||||
return this.el_.videoHeight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get text tracks
|
||||
@ -862,7 +943,7 @@ class Html5 extends Tech {
|
||||
* @method addTextTrack
|
||||
*/
|
||||
addTextTrack(kind, label, language) {
|
||||
if (!this['featuresNativeTextTracks']) {
|
||||
if (!this.featuresNativeTextTracks) {
|
||||
return super.addTextTrack(kind, label, language);
|
||||
}
|
||||
|
||||
@ -877,8 +958,8 @@ class Html5 extends Tech {
|
||||
* @return {HTMLTrackElement}
|
||||
* @method addRemoteTextTrack
|
||||
*/
|
||||
addRemoteTextTrack(options={}) {
|
||||
if (!this['featuresNativeTextTracks']) {
|
||||
addRemoteTextTrack(options = {}) {
|
||||
if (!this.featuresNativeTextTracks) {
|
||||
return super.addRemoteTextTrack(options);
|
||||
}
|
||||
|
||||
@ -919,12 +1000,11 @@ class Html5 extends Tech {
|
||||
* @method removeRemoteTextTrack
|
||||
*/
|
||||
removeRemoteTextTrack(track) {
|
||||
if (!this['featuresNativeTextTracks']) {
|
||||
if (!this.featuresNativeTextTracks) {
|
||||
return super.removeRemoteTextTrack(track);
|
||||
}
|
||||
|
||||
let tracks, i;
|
||||
|
||||
let tracks;
|
||||
let trackElement = this.remoteTextTrackEls().getTrackElementByTrack_(track);
|
||||
|
||||
// remove HTMLTrackElement and TextTrack from remote list
|
||||
@ -933,7 +1013,8 @@ class Html5 extends Tech {
|
||||
|
||||
tracks = this.$$('track');
|
||||
|
||||
i = tracks.length;
|
||||
let i = tracks.length;
|
||||
|
||||
while (i--) {
|
||||
if (track === tracks[i] || track === tracks[i].track) {
|
||||
this.el().removeChild(tracks[i]);
|
||||
@ -943,7 +1024,6 @@ class Html5 extends Tech {
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* HTML5 Support Testing ---------------------------------------------------- */
|
||||
|
||||
/*
|
||||
@ -955,6 +1035,7 @@ class Html5 extends Tech {
|
||||
*/
|
||||
Html5.TEST_VID = document.createElement('video');
|
||||
let track = document.createElement('track');
|
||||
|
||||
track.kind = 'captions';
|
||||
track.srclang = 'en';
|
||||
track.label = 'English';
|
||||
@ -965,10 +1046,10 @@ Html5.TEST_VID.appendChild(track);
|
||||
*
|
||||
* @return {Boolean}
|
||||
*/
|
||||
Html5.isSupported = function(){
|
||||
Html5.isSupported = function() {
|
||||
// IE9 with no Media Player is a LIAR! (#984)
|
||||
try {
|
||||
Html5.TEST_VID['volume'] = 0.5;
|
||||
Html5.TEST_VID.volume = 0.5;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
@ -994,12 +1075,12 @@ Html5.nativeSourceHandler = {};
|
||||
* @param {String} type The mimetype to check
|
||||
* @return {String} 'probably', 'maybe', or '' (empty string)
|
||||
*/
|
||||
Html5.nativeSourceHandler.canPlayType = function(type){
|
||||
Html5.nativeSourceHandler.canPlayType = function(type) {
|
||||
// IE9 on Windows 7 without MediaPlayer throws an error here
|
||||
// https://github.com/videojs/video.js/issues/519
|
||||
try {
|
||||
return Html5.TEST_VID.canPlayType(type);
|
||||
} catch(e) {
|
||||
} catch (e) {
|
||||
return '';
|
||||
}
|
||||
};
|
||||
@ -1011,15 +1092,15 @@ Html5.nativeSourceHandler.canPlayType = function(type){
|
||||
* @param {Object} options The options passed to the tech
|
||||
* @return {String} 'probably', 'maybe', or '' (empty string)
|
||||
*/
|
||||
Html5.nativeSourceHandler.canHandleSource = function(source, options){
|
||||
var match, ext;
|
||||
Html5.nativeSourceHandler.canHandleSource = function(source, options) {
|
||||
|
||||
// If a type was provided we should rely on that
|
||||
if (source.type) {
|
||||
return Html5.nativeSourceHandler.canPlayType(source.type);
|
||||
|
||||
// If no type, fall back to checking 'video/[EXTENSION]'
|
||||
} else if (source.src) {
|
||||
// If no type, fall back to checking 'video/[EXTENSION]'
|
||||
ext = Url.getFileExtension(source.src);
|
||||
let ext = Url.getFileExtension(source.src);
|
||||
|
||||
return Html5.nativeSourceHandler.canPlayType(`video/${ext}`);
|
||||
}
|
||||
@ -1036,7 +1117,7 @@ Html5.nativeSourceHandler.canHandleSource = function(source, options){
|
||||
* @param {Html5} tech The instance of the Html5 tech
|
||||
* @param {Object} options The options to pass to the source
|
||||
*/
|
||||
Html5.nativeSourceHandler.handleSource = function(source, tech, options){
|
||||
Html5.nativeSourceHandler.handleSource = function(source, tech, options) {
|
||||
tech.setSrc(source.src);
|
||||
};
|
||||
|
||||
@ -1044,7 +1125,7 @@ Html5.nativeSourceHandler.handleSource = function(source, tech, options){
|
||||
* Clean up the source handler when disposing the player or switching sources..
|
||||
* (no cleanup is needed when supporting the format natively)
|
||||
*/
|
||||
Html5.nativeSourceHandler.dispose = function(){};
|
||||
Html5.nativeSourceHandler.dispose = function() {};
|
||||
|
||||
// Register the native source handler
|
||||
Html5.registerSourceHandler(Html5.nativeSourceHandler);
|
||||
@ -1056,13 +1137,14 @@ Html5.registerSourceHandler(Html5.nativeSourceHandler);
|
||||
*
|
||||
* @return {Boolean}
|
||||
*/
|
||||
Html5.canControlVolume = function(){
|
||||
Html5.canControlVolume = function() {
|
||||
// IE will error if Windows Media Player not installed #3315
|
||||
try {
|
||||
var volume = Html5.TEST_VID.volume;
|
||||
let volume = Html5.TEST_VID.volume;
|
||||
|
||||
Html5.TEST_VID.volume = (volume / 2) + 0.1;
|
||||
return volume !== Html5.TEST_VID.volume;
|
||||
} catch(e) {
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
@ -1072,7 +1154,7 @@ Html5.canControlVolume = function(){
|
||||
*
|
||||
* @return {Boolean}
|
||||
*/
|
||||
Html5.canControlPlaybackRate = function(){
|
||||
Html5.canControlPlaybackRate = function() {
|
||||
// Playback rate API is implemented in Android Chrome, but doesn't do anything
|
||||
// https://github.com/videojs/video.js/issues/3180
|
||||
if (browser.IS_ANDROID && browser.IS_CHROME) {
|
||||
@ -1080,10 +1162,11 @@ Html5.canControlPlaybackRate = function(){
|
||||
}
|
||||
// IE will error if Windows Media Player not installed #3315
|
||||
try {
|
||||
var playbackRate = Html5.TEST_VID.playbackRate;
|
||||
let playbackRate = Html5.TEST_VID.playbackRate;
|
||||
|
||||
Html5.TEST_VID.playbackRate = (playbackRate / 2) + 0.1;
|
||||
return playbackRate !== Html5.TEST_VID.playbackRate;
|
||||
} catch(e) {
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
@ -1094,7 +1177,7 @@ Html5.canControlPlaybackRate = function(){
|
||||
* @return {Boolean}
|
||||
*/
|
||||
Html5.supportsNativeTextTracks = function() {
|
||||
var supportsTextTracks;
|
||||
let supportsTextTracks;
|
||||
|
||||
// Figure out native text track support
|
||||
// If mode is a number, we cannot change it because it'll disappear from view.
|
||||
@ -1103,7 +1186,7 @@ Html5.supportsNativeTextTracks = function() {
|
||||
// TODO: Investigate firefox: https://github.com/videojs/video.js/issues/1862
|
||||
supportsTextTracks = !!Html5.TEST_VID.textTracks;
|
||||
if (supportsTextTracks && Html5.TEST_VID.textTracks.length > 0) {
|
||||
supportsTextTracks = typeof Html5.TEST_VID.textTracks[0]['mode'] !== 'number';
|
||||
supportsTextTracks = typeof Html5.TEST_VID.textTracks[0].mode !== 'number';
|
||||
}
|
||||
if (supportsTextTracks && browser.IS_FIREFOX) {
|
||||
supportsTextTracks = false;
|
||||
@ -1122,6 +1205,7 @@ Html5.supportsNativeTextTracks = function() {
|
||||
*/
|
||||
Html5.supportsNativeVideoTracks = function() {
|
||||
let supportsVideoTracks = !!Html5.TEST_VID.videoTracks;
|
||||
|
||||
return supportsVideoTracks;
|
||||
};
|
||||
|
||||
@ -1132,10 +1216,10 @@ Html5.supportsNativeVideoTracks = function() {
|
||||
*/
|
||||
Html5.supportsNativeAudioTracks = function() {
|
||||
let supportsAudioTracks = !!Html5.TEST_VID.audioTracks;
|
||||
|
||||
return supportsAudioTracks;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* An array of events available on the Html5 tech.
|
||||
*
|
||||
@ -1172,14 +1256,14 @@ Html5.Events = [
|
||||
*
|
||||
* @type {Boolean}
|
||||
*/
|
||||
Html5.prototype['featuresVolumeControl'] = Html5.canControlVolume();
|
||||
Html5.prototype.featuresVolumeControl = Html5.canControlVolume();
|
||||
|
||||
/*
|
||||
* Set the tech's playbackRate support status
|
||||
*
|
||||
* @type {Boolean}
|
||||
*/
|
||||
Html5.prototype['featuresPlaybackRate'] = Html5.canControlPlaybackRate();
|
||||
Html5.prototype.featuresPlaybackRate = Html5.canControlPlaybackRate();
|
||||
|
||||
/*
|
||||
* Set the tech's status on moving the video element.
|
||||
@ -1187,41 +1271,41 @@ Html5.prototype['featuresPlaybackRate'] = Html5.canControlPlaybackRate();
|
||||
*
|
||||
* @type {Boolean}
|
||||
*/
|
||||
Html5.prototype['movingMediaElementInDOM'] = !browser.IS_IOS;
|
||||
Html5.prototype.movingMediaElementInDOM = !browser.IS_IOS;
|
||||
|
||||
/*
|
||||
* Set the the tech's fullscreen resize support status.
|
||||
* HTML video is able to automatically resize when going to fullscreen.
|
||||
* (No longer appears to be used. Can probably be removed.)
|
||||
*/
|
||||
Html5.prototype['featuresFullscreenResize'] = true;
|
||||
Html5.prototype.featuresFullscreenResize = true;
|
||||
|
||||
/*
|
||||
* Set the tech's progress event support status
|
||||
* (this disables the manual progress events of the Tech)
|
||||
*/
|
||||
Html5.prototype['featuresProgressEvents'] = true;
|
||||
Html5.prototype.featuresProgressEvents = true;
|
||||
|
||||
/*
|
||||
* Sets the tech's status on native text track support
|
||||
*
|
||||
* @type {Boolean}
|
||||
*/
|
||||
Html5.prototype['featuresNativeTextTracks'] = Html5.supportsNativeTextTracks();
|
||||
Html5.prototype.featuresNativeTextTracks = Html5.supportsNativeTextTracks();
|
||||
|
||||
/**
|
||||
* Sets the tech's status on native text track support
|
||||
*
|
||||
* @type {Boolean}
|
||||
*/
|
||||
Html5.prototype['featuresNativeVideoTracks'] = Html5.supportsNativeVideoTracks();
|
||||
Html5.prototype.featuresNativeVideoTracks = Html5.supportsNativeVideoTracks();
|
||||
|
||||
/**
|
||||
* Sets the tech's status on native audio track support
|
||||
*
|
||||
* @type {Boolean}
|
||||
*/
|
||||
Html5.prototype['featuresNativeAudioTracks'] = Html5.supportsNativeAudioTracks();
|
||||
Html5.prototype.featuresNativeAudioTracks = Html5.supportsNativeAudioTracks();
|
||||
|
||||
// HTML5 Feature detection and Device Fixes --------------------------------- //
|
||||
let canPlayType;
|
||||
@ -1249,7 +1333,7 @@ Html5.patchCanPlayType = function() {
|
||||
canPlayType = Html5.TEST_VID.constructor.prototype.canPlayType;
|
||||
}
|
||||
|
||||
Html5.TEST_VID.constructor.prototype.canPlayType = function(type){
|
||||
Html5.TEST_VID.constructor.prototype.canPlayType = function(type) {
|
||||
if (type && mp4RE.test(type)) {
|
||||
return 'maybe';
|
||||
}
|
||||
@ -1259,7 +1343,8 @@ Html5.patchCanPlayType = function() {
|
||||
};
|
||||
|
||||
Html5.unpatchCanPlayType = function() {
|
||||
var r = Html5.TEST_VID.constructor.prototype.canPlayType;
|
||||
let r = Html5.TEST_VID.constructor.prototype.canPlayType;
|
||||
|
||||
Html5.TEST_VID.constructor.prototype.canPlayType = canPlayType;
|
||||
canPlayType = null;
|
||||
return r;
|
||||
@ -1268,15 +1353,17 @@ Html5.unpatchCanPlayType = function() {
|
||||
// by default, patch the video element
|
||||
Html5.patchCanPlayType();
|
||||
|
||||
Html5.disposeMediaElement = function(el){
|
||||
if (!el) { return; }
|
||||
Html5.disposeMediaElement = function(el) {
|
||||
if (!el) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (el.parentNode) {
|
||||
el.parentNode.removeChild(el);
|
||||
}
|
||||
|
||||
// remove any child track or source nodes to prevent their loading
|
||||
while(el.hasChildNodes()) {
|
||||
while (el.hasChildNodes()) {
|
||||
el.removeChild(el.firstChild);
|
||||
}
|
||||
|
||||
@ -1294,15 +1381,18 @@ Html5.disposeMediaElement = function(el){
|
||||
} catch (e) {
|
||||
// not supported
|
||||
}
|
||||
})();
|
||||
}());
|
||||
}
|
||||
};
|
||||
|
||||
Html5.resetMediaElement = function(el){
|
||||
if (!el) { return; }
|
||||
Html5.resetMediaElement = function(el) {
|
||||
if (!el) {
|
||||
return;
|
||||
}
|
||||
|
||||
let sources = el.querySelectorAll('source');
|
||||
let i = sources.length;
|
||||
|
||||
while (i--) {
|
||||
el.removeChild(sources[i]);
|
||||
}
|
||||
@ -1316,8 +1406,10 @@ Html5.resetMediaElement = function(el){
|
||||
(function() {
|
||||
try {
|
||||
el.load();
|
||||
} catch (e) {}
|
||||
})();
|
||||
} catch (e) {
|
||||
// satisfy linter
|
||||
}
|
||||
}());
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -3,7 +3,6 @@
|
||||
*/
|
||||
import Component from '../component.js';
|
||||
import Tech from './tech.js';
|
||||
import window from 'global/window';
|
||||
import toTitleCase from '../utils/to-title-case.js';
|
||||
|
||||
/**
|
||||
@ -18,16 +17,17 @@ import toTitleCase from '../utils/to-title-case.js';
|
||||
*/
|
||||
class MediaLoader extends Component {
|
||||
|
||||
constructor(player, options, ready){
|
||||
constructor(player, options, ready) {
|
||||
super(player, options, ready);
|
||||
|
||||
// If there are no sources when the player is initialized,
|
||||
// load the first supported playback technology.
|
||||
|
||||
if (!options.playerOptions['sources'] || options.playerOptions['sources'].length === 0) {
|
||||
for (let i=0, j=options.playerOptions['techOrder']; i<j.length; i++) {
|
||||
if (!options.playerOptions.sources || options.playerOptions.sources.length === 0) {
|
||||
for (let i = 0, j = options.playerOptions.techOrder; i < j.length; i++) {
|
||||
let techName = toTitleCase(j[i]);
|
||||
let tech = Tech.getTech(techName);
|
||||
|
||||
// Support old behavior of techs being registered as components.
|
||||
// Remove once that deprecated behavior is removed.
|
||||
if (!techName) {
|
||||
@ -41,11 +41,11 @@ class MediaLoader extends Component {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// // Loop through playback technologies (HTML5, Flash) and check for support.
|
||||
// // Then load the best source.
|
||||
// // A few assumptions here:
|
||||
// // All playback technologies respect preload false.
|
||||
player.src(options.playerOptions['sources']);
|
||||
// Loop through playback technologies (HTML5, Flash) and check for support.
|
||||
// Then load the best source.
|
||||
// A few assumptions here:
|
||||
// All playback technologies respect preload false.
|
||||
player.src(options.playerOptions.sources);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,10 +10,8 @@ import HTMLTrackElementList from '../tracks/html-track-element-list';
|
||||
import mergeOptions from '../utils/merge-options.js';
|
||||
import TextTrack from '../tracks/text-track';
|
||||
import TextTrackList from '../tracks/text-track-list';
|
||||
import VideoTrack from '../tracks/video-track';
|
||||
import VideoTrackList from '../tracks/video-track-list';
|
||||
import AudioTrackList from '../tracks/audio-track-list';
|
||||
import AudioTrack from '../tracks/audio-track';
|
||||
import * as Fn from '../utils/fn.js';
|
||||
import log from '../utils/log.js';
|
||||
import { createTimeRange } from '../utils/time-ranges.js';
|
||||
@ -22,6 +20,26 @@ import MediaError from '../media-error.js';
|
||||
import window from 'global/window';
|
||||
import document from 'global/document';
|
||||
|
||||
function createTrackHelper(self, kind, label, language, options = {}) {
|
||||
let tracks = self.textTracks();
|
||||
|
||||
options.kind = kind;
|
||||
|
||||
if (label) {
|
||||
options.label = label;
|
||||
}
|
||||
if (language) {
|
||||
options.language = language;
|
||||
}
|
||||
options.tech = self;
|
||||
|
||||
let track = new TextTrack(options);
|
||||
|
||||
tracks.addTrack_(track);
|
||||
|
||||
return track;
|
||||
}
|
||||
|
||||
/**
|
||||
* Base class for media (HTML5 Video, Flash) controllers
|
||||
*
|
||||
@ -32,7 +50,7 @@ import document from 'global/document';
|
||||
*/
|
||||
class Tech extends Component {
|
||||
|
||||
constructor(options={}, ready=function(){}){
|
||||
constructor(options = {}, ready = function() {}) {
|
||||
// we don't want the tech to report user activity automatically.
|
||||
// This is done manually in addControlsListeners
|
||||
options.reportTouchActivity = false;
|
||||
@ -114,7 +132,7 @@ class Tech extends Component {
|
||||
*/
|
||||
trackProgress() {
|
||||
this.stopTrackingProgress();
|
||||
this.progressInterval = this.setInterval(Fn.bind(this, function(){
|
||||
this.progressInterval = this.setInterval(Fn.bind(this, function() {
|
||||
// Don't trigger unless buffered amount is greater than last time
|
||||
|
||||
let numBufferedPercent = this.bufferedPercent();
|
||||
@ -169,7 +187,6 @@ class Tech extends Component {
|
||||
this.clearInterval(this.progressInterval);
|
||||
}
|
||||
|
||||
/*! Time Tracking -------------------------------------------------------------- */
|
||||
/**
|
||||
* Set event listeners for on play and pause and tracking current time
|
||||
*
|
||||
@ -200,10 +217,14 @@ class Tech extends Component {
|
||||
* @method trackCurrentTime
|
||||
*/
|
||||
trackCurrentTime() {
|
||||
if (this.currentTimeInterval) { this.stopTrackingCurrentTime(); }
|
||||
this.currentTimeInterval = this.setInterval(function(){
|
||||
if (this.currentTimeInterval) {
|
||||
this.stopTrackingCurrentTime();
|
||||
}
|
||||
this.currentTimeInterval = this.setInterval(function() {
|
||||
this.trigger({ type: 'timeupdate', target: this, manuallyTriggered: true });
|
||||
}, 250); // 42 = 24 fps // 250 is what Webkit uses // FF uses 15
|
||||
|
||||
// 42 = 24 fps // 250 is what Webkit uses // FF uses 15
|
||||
}, 250);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -230,9 +251,13 @@ class Tech extends Component {
|
||||
this.clearTracks(['audio', 'video', 'text']);
|
||||
|
||||
// Turn off any manual progress or timeupdate tracking
|
||||
if (this.manualProgress) { this.manualProgressOff(); }
|
||||
if (this.manualProgress) {
|
||||
this.manualProgressOff();
|
||||
}
|
||||
|
||||
if (this.manualTimeUpdates) { this.manualTimeUpdatesOff(); }
|
||||
if (this.manualTimeUpdates) {
|
||||
this.manualTimeUpdatesOff();
|
||||
}
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
@ -254,8 +279,10 @@ class Tech extends Component {
|
||||
types.forEach((type) => {
|
||||
let list = this[`${type}Tracks`]() || [];
|
||||
let i = list.length;
|
||||
|
||||
while (i--) {
|
||||
let track = list[i];
|
||||
|
||||
if (type === 'text') {
|
||||
this.removeRemoteTextTrack(track);
|
||||
}
|
||||
@ -315,7 +342,9 @@ class Tech extends Component {
|
||||
*/
|
||||
setCurrentTime() {
|
||||
// improve the accuracy of manual timeupdates
|
||||
if (this.manualTimeUpdates) { this.trigger({ type: 'timeupdate', target: this, manuallyTriggered: true }); }
|
||||
if (this.manualTimeUpdates) {
|
||||
this.trigger({ type: 'timeupdate', target: this, manuallyTriggered: true });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -330,7 +359,9 @@ class Tech extends Component {
|
||||
|
||||
let tracks = this.textTracks();
|
||||
|
||||
if (!tracks) return;
|
||||
if (!tracks) {
|
||||
return;
|
||||
}
|
||||
|
||||
tracks.addEventListener('removetrack', textTrackListChanges);
|
||||
tracks.addEventListener('addtrack', textTrackListChanges);
|
||||
@ -341,7 +372,6 @@ class Tech extends Component {
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Initialize audio and video track listeners
|
||||
*
|
||||
@ -374,12 +404,14 @@ class Tech extends Component {
|
||||
*/
|
||||
emulateTextTracks() {
|
||||
let tracks = this.textTracks();
|
||||
|
||||
if (!tracks) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!window['WebVTT'] && this.el().parentNode != null) {
|
||||
if (!window.WebVTT && this.el().parentNode != null) {
|
||||
let script = document.createElement('script');
|
||||
|
||||
script.src = this.options_['vtt.js'] || '../node_modules/videojs-vtt.js/dist/vtt.js';
|
||||
script.onload = () => {
|
||||
this.trigger('vttjsloaded');
|
||||
@ -393,7 +425,7 @@ class Tech extends Component {
|
||||
});
|
||||
// but have not loaded yet and we set it to true before the inject so that
|
||||
// we don't overwrite the injected window.WebVTT if it loads right away
|
||||
window['WebVTT'] = true;
|
||||
window.WebVTT = true;
|
||||
this.el().parentNode.appendChild(script);
|
||||
}
|
||||
|
||||
@ -403,6 +435,7 @@ class Tech extends Component {
|
||||
|
||||
for (let i = 0; i < tracks.length; i++) {
|
||||
let track = tracks[i];
|
||||
|
||||
track.removeEventListener('cuechange', updateDisplay);
|
||||
if (track.mode === 'showing') {
|
||||
track.addEventListener('cuechange', updateDisplay);
|
||||
@ -620,7 +653,7 @@ class Tech extends Component {
|
||||
* @type {TextTrackList}
|
||||
* @private
|
||||
*/
|
||||
Tech.prototype.textTracks_;
|
||||
Tech.prototype.textTracks_; // eslint-disable-line
|
||||
|
||||
/**
|
||||
* List of associated audio tracks
|
||||
@ -628,7 +661,7 @@ Tech.prototype.textTracks_;
|
||||
* @type {AudioTrackList}
|
||||
* @private
|
||||
*/
|
||||
Tech.prototype.audioTracks_;
|
||||
Tech.prototype.audioTracks_; // eslint-disable-line
|
||||
|
||||
/**
|
||||
* List of associated video tracks
|
||||
@ -636,27 +669,7 @@ Tech.prototype.audioTracks_;
|
||||
* @type {VideoTrackList}
|
||||
* @private
|
||||
*/
|
||||
Tech.prototype.videoTracks_;
|
||||
|
||||
|
||||
var createTrackHelper = function(self, kind, label, language, options={}) {
|
||||
let tracks = self.textTracks();
|
||||
|
||||
options.kind = kind;
|
||||
|
||||
if (label) {
|
||||
options.label = label;
|
||||
}
|
||||
if (language) {
|
||||
options.language = language;
|
||||
}
|
||||
options.tech = self;
|
||||
|
||||
let track = new TextTrack(options);
|
||||
tracks.addTrack_(track);
|
||||
|
||||
return track;
|
||||
};
|
||||
Tech.prototype.videoTracks_; // eslint-disable-line
|
||||
|
||||
Tech.prototype.featuresVolumeControl = true;
|
||||
|
||||
@ -671,7 +684,7 @@ Tech.prototype.featuresTimeupdateEvents = false;
|
||||
|
||||
Tech.prototype.featuresNativeTextTracks = false;
|
||||
|
||||
/*
|
||||
/**
|
||||
* A functional mixin for techs that want to use the Source Handler pattern.
|
||||
*
|
||||
* ##### EXAMPLE:
|
||||
@ -679,16 +692,17 @@ Tech.prototype.featuresNativeTextTracks = false;
|
||||
* Tech.withSourceHandlers.call(MyTech);
|
||||
*
|
||||
*/
|
||||
Tech.withSourceHandlers = function(_Tech){
|
||||
/*
|
||||
* Register a source handler
|
||||
* Source handlers are scripts for handling specific formats.
|
||||
* The source handler pattern is used for adaptive formats (HLS, DASH) that
|
||||
* manually load video data and feed it into a Source Buffer (Media Source Extensions)
|
||||
* @param {Function} handler The source handler
|
||||
* @param {Boolean} first Register it before any existing handlers
|
||||
*/
|
||||
_Tech.registerSourceHandler = function(handler, index){
|
||||
Tech.withSourceHandlers = function(_Tech) {
|
||||
|
||||
/**
|
||||
* Register a source handler
|
||||
* Source handlers are scripts for handling specific formats.
|
||||
* The source handler pattern is used for adaptive formats (HLS, DASH) that
|
||||
* manually load video data and feed it into a Source Buffer (Media Source Extensions)
|
||||
* @param {Function} handler The source handler
|
||||
* @param {Boolean} first Register it before any existing handlers
|
||||
*/
|
||||
_Tech.registerSourceHandler = function(handler, index) {
|
||||
let handlers = _Tech.sourceHandlers;
|
||||
|
||||
if (!handlers) {
|
||||
@ -703,12 +717,12 @@ Tech.withSourceHandlers = function(_Tech){
|
||||
handlers.splice(index, 0, handler);
|
||||
};
|
||||
|
||||
/*
|
||||
/**
|
||||
* Check if the tech can support the given type
|
||||
* @param {String} type The mimetype to check
|
||||
* @return {String} 'probably', 'maybe', or '' (empty string)
|
||||
*/
|
||||
_Tech.canPlayType = function(type){
|
||||
_Tech.canPlayType = function(type) {
|
||||
let handlers = _Tech.sourceHandlers || [];
|
||||
let can;
|
||||
|
||||
@ -723,15 +737,15 @@ Tech.withSourceHandlers = function(_Tech){
|
||||
return '';
|
||||
};
|
||||
|
||||
/*
|
||||
* Return the first source handler that supports the source
|
||||
* TODO: Answer question: should 'probably' be prioritized over 'maybe'
|
||||
* @param {Object} source The source object
|
||||
* @param {Object} options The options passed to the tech
|
||||
* @returns {Object} The first source handler that supports the source
|
||||
* @returns {null} Null if no source handler is found
|
||||
*/
|
||||
_Tech.selectSourceHandler = function(source, options){
|
||||
/**
|
||||
* Return the first source handler that supports the source
|
||||
* TODO: Answer question: should 'probably' be prioritized over 'maybe'
|
||||
* @param {Object} source The source object
|
||||
* @param {Object} options The options passed to the tech
|
||||
* @returns {Object} The first source handler that supports the source
|
||||
* @returns {null} Null if no source handler is found
|
||||
*/
|
||||
_Tech.selectSourceHandler = function(source, options) {
|
||||
let handlers = _Tech.sourceHandlers || [];
|
||||
let can;
|
||||
|
||||
@ -746,13 +760,13 @@ Tech.withSourceHandlers = function(_Tech){
|
||||
return null;
|
||||
};
|
||||
|
||||
/*
|
||||
/**
|
||||
* Check if the tech can support the given source
|
||||
* @param {Object} srcObj The source object
|
||||
* @param {Object} options The options passed to the tech
|
||||
* @return {String} 'probably', 'maybe', or '' (empty string)
|
||||
*/
|
||||
_Tech.canPlaySource = function(srcObj, options){
|
||||
_Tech.canPlaySource = function(srcObj, options) {
|
||||
let sh = _Tech.selectSourceHandler(srcObj, options);
|
||||
|
||||
if (sh) {
|
||||
@ -762,16 +776,16 @@ Tech.withSourceHandlers = function(_Tech){
|
||||
return '';
|
||||
};
|
||||
|
||||
/*
|
||||
/**
|
||||
* When using a source handler, prefer its implementation of
|
||||
* any function normally provided by the tech.
|
||||
*/
|
||||
let deferrable = [
|
||||
'seekable',
|
||||
'duration'
|
||||
];
|
||||
'seekable',
|
||||
'duration'
|
||||
];
|
||||
|
||||
deferrable.forEach(function (fnName) {
|
||||
deferrable.forEach(function(fnName) {
|
||||
let originalFn = this[fnName];
|
||||
|
||||
if (typeof originalFn !== 'function') {
|
||||
@ -786,14 +800,14 @@ Tech.withSourceHandlers = function(_Tech){
|
||||
};
|
||||
}, _Tech.prototype);
|
||||
|
||||
/*
|
||||
* Create a function for setting the source using a source object
|
||||
* and source handlers.
|
||||
* Should never be called unless a source handler was found.
|
||||
* @param {Object} source A source object with src and type keys
|
||||
* @return {Tech} self
|
||||
*/
|
||||
_Tech.prototype.setSource = function(source){
|
||||
/**
|
||||
* Create a function for setting the source using a source object
|
||||
* and source handlers.
|
||||
* Should never be called unless a source handler was found.
|
||||
* @param {Object} source A source object with src and type keys
|
||||
* @return {Tech} self
|
||||
*/
|
||||
_Tech.prototype.setSource = function(source) {
|
||||
let sh = _Tech.selectSourceHandler(source, this.options_);
|
||||
|
||||
if (!sh) {
|
||||
|
@ -21,6 +21,7 @@ const disableOthers = function(list, track) {
|
||||
list[i].enabled = false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A list of possible audio tracks. All functionality is in the
|
||||
* base class Tracklist and the spec for AudioTrackList is located at:
|
||||
|
@ -37,7 +37,9 @@ class AudioTrack extends Track {
|
||||
}
|
||||
|
||||
Object.defineProperty(track, 'enabled', {
|
||||
get() { return enabled; },
|
||||
get() {
|
||||
return enabled;
|
||||
},
|
||||
set(newEnabled) {
|
||||
// an invalid or unchanged value
|
||||
if (typeof newEnabled !== 'boolean' || newEnabled === enabled) {
|
||||
|
@ -7,7 +7,7 @@ import document from 'global/document';
|
||||
|
||||
class HtmlTrackElementList {
|
||||
constructor(trackElements = []) {
|
||||
let list = this;
|
||||
let list = this; // eslint-disable-line
|
||||
|
||||
if (browser.IS_IE8) {
|
||||
list = document.createElement('custom');
|
||||
|
@ -39,8 +39,8 @@ class HTMLTrackElement extends EventTarget {
|
||||
constructor(options = {}) {
|
||||
super();
|
||||
|
||||
let readyState,
|
||||
trackElement = this;
|
||||
let readyState;
|
||||
let trackElement = this; // eslint-disable-line
|
||||
|
||||
if (browser.IS_IE8) {
|
||||
trackElement = document.createElement('custom');
|
||||
|
@ -20,7 +20,7 @@ import document from 'global/document';
|
||||
|
||||
class TextTrackCueList {
|
||||
constructor(cues) {
|
||||
let list = this;
|
||||
let list = this; // eslint-disable-line
|
||||
|
||||
if (browser.IS_IE8) {
|
||||
list = document.createElement('custom');
|
||||
|
@ -2,28 +2,60 @@
|
||||
* @file text-track-display.js
|
||||
*/
|
||||
import Component from '../component';
|
||||
import Menu from '../menu/menu.js';
|
||||
import MenuItem from '../menu/menu-item.js';
|
||||
import MenuButton from '../menu/menu-button.js';
|
||||
import * as Fn from '../utils/fn.js';
|
||||
import document from 'global/document';
|
||||
import window from 'global/window';
|
||||
|
||||
const darkGray = '#222';
|
||||
const lightGray = '#ccc';
|
||||
const fontMap = {
|
||||
monospace: 'monospace',
|
||||
sansSerif: 'sans-serif',
|
||||
serif: 'serif',
|
||||
monospaceSansSerif: '"Andale Mono", "Lucida Console", monospace',
|
||||
monospaceSerif: '"Courier New", monospace',
|
||||
monospace: 'monospace',
|
||||
sansSerif: 'sans-serif',
|
||||
serif: 'serif',
|
||||
monospaceSansSerif: '"Andale Mono", "Lucida Console", monospace',
|
||||
monospaceSerif: '"Courier New", monospace',
|
||||
proportionalSansSerif: 'sans-serif',
|
||||
proportionalSerif: 'serif',
|
||||
casual: '"Comic Sans MS", Impact, fantasy',
|
||||
script: '"Monotype Corsiva", cursive',
|
||||
smallcaps: '"Andale Mono", "Lucida Console", monospace, sans-serif'
|
||||
proportionalSerif: 'serif',
|
||||
casual: '"Comic Sans MS", Impact, fantasy',
|
||||
script: '"Monotype Corsiva", cursive',
|
||||
smallcaps: '"Andale Mono", "Lucida Console", monospace, sans-serif'
|
||||
};
|
||||
|
||||
/**
|
||||
* Add cue HTML to display
|
||||
*
|
||||
* @param {Number} color Hex number for color, like #f0e
|
||||
* @param {Number} opacity Value for opacity,0.0 - 1.0
|
||||
* @return {RGBAColor} In the form 'rgba(255, 0, 0, 0.3)'
|
||||
* @method constructColor
|
||||
*/
|
||||
function constructColor(color, opacity) {
|
||||
return 'rgba(' +
|
||||
// color looks like "#f0e"
|
||||
parseInt(color[1] + color[1], 16) + ',' +
|
||||
parseInt(color[2] + color[2], 16) + ',' +
|
||||
parseInt(color[3] + color[3], 16) + ',' +
|
||||
opacity + ')';
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to update style
|
||||
* Some style changes will throw an error, particularly in IE8. Those should be noops.
|
||||
*
|
||||
* @param {Element} el The element to be styles
|
||||
* @param {CSSProperty} style The CSS property to be styled
|
||||
* @param {CSSStyle} rule The actual style to be applied to the property
|
||||
* @method tryUpdateStyle
|
||||
*/
|
||||
function tryUpdateStyle(el, style, rule) {
|
||||
try {
|
||||
el.style[style] = rule;
|
||||
} catch (e) {
|
||||
|
||||
// Satisfies linter.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The component for displaying text track cues
|
||||
*
|
||||
@ -35,7 +67,7 @@ const fontMap = {
|
||||
*/
|
||||
class TextTrackDisplay extends Component {
|
||||
|
||||
constructor(player, options, ready){
|
||||
constructor(player, options, ready) {
|
||||
super(player, options, ready);
|
||||
|
||||
player.on('loadstart', Fn.bind(this, this.toggleDisplay));
|
||||
@ -46,20 +78,20 @@ class TextTrackDisplay extends Component {
|
||||
// Should probably be moved to an external track loader when we support
|
||||
// tracks that don't need a display.
|
||||
player.ready(Fn.bind(this, function() {
|
||||
if (player.tech_ && player.tech_['featuresNativeTextTracks']) {
|
||||
if (player.tech_ && player.tech_.featuresNativeTextTracks) {
|
||||
this.hide();
|
||||
return;
|
||||
}
|
||||
|
||||
player.on('fullscreenchange', Fn.bind(this, this.updateDisplay));
|
||||
|
||||
let tracks = this.options_.playerOptions['tracks'] || [];
|
||||
let tracks = this.options_.playerOptions.tracks || [];
|
||||
|
||||
for (let i = 0; i < tracks.length; i++) {
|
||||
let track = tracks[i];
|
||||
this.player_.addRemoteTextTrack(track);
|
||||
this.player_.addRemoteTextTrack(tracks[i]);
|
||||
}
|
||||
|
||||
let modes = {'captions': 1, 'subtitles': 1};
|
||||
let modes = {captions: 1, subtitles: 1};
|
||||
let trackList = this.player_.textTracks();
|
||||
let firstDesc;
|
||||
let firstCaptions;
|
||||
@ -67,6 +99,7 @@ class TextTrackDisplay extends Component {
|
||||
if (trackList) {
|
||||
for (let i = 0; i < trackList.length; i++) {
|
||||
let track = trackList[i];
|
||||
|
||||
if (track.default) {
|
||||
if (track.kind === 'descriptions' && !firstDesc) {
|
||||
firstDesc = track;
|
||||
@ -95,7 +128,7 @@ class TextTrackDisplay extends Component {
|
||||
* @method toggleDisplay
|
||||
*/
|
||||
toggleDisplay() {
|
||||
if (this.player_.tech_ && this.player_.tech_['featuresNativeTextTracks']) {
|
||||
if (this.player_.tech_ && this.player_.tech_.featuresNativeTextTracks) {
|
||||
this.hide();
|
||||
} else {
|
||||
this.show();
|
||||
@ -123,8 +156,8 @@ class TextTrackDisplay extends Component {
|
||||
* @method clearDisplay
|
||||
*/
|
||||
clearDisplay() {
|
||||
if (typeof window['WebVTT'] === 'function') {
|
||||
window['WebVTT']['processCues'](window, [], this.el_);
|
||||
if (typeof window.WebVTT === 'function') {
|
||||
window.WebVTT.processCues(window, [], this.el_);
|
||||
}
|
||||
}
|
||||
|
||||
@ -134,7 +167,7 @@ class TextTrackDisplay extends Component {
|
||||
* @method updateDisplay
|
||||
*/
|
||||
updateDisplay() {
|
||||
var tracks = this.player_.textTracks();
|
||||
let tracks = this.player_.textTracks();
|
||||
|
||||
this.clearDisplay();
|
||||
|
||||
@ -150,10 +183,12 @@ class TextTrackDisplay extends Component {
|
||||
let captionsSubtitlesTrack = null;
|
||||
|
||||
let i = tracks.length;
|
||||
|
||||
while (i--) {
|
||||
let track = tracks[i];
|
||||
if (track['mode'] === 'showing') {
|
||||
if (track['kind'] === 'descriptions') {
|
||||
|
||||
if (track.mode === 'showing') {
|
||||
if (track.kind === 'descriptions') {
|
||||
descriptionsTrack = track;
|
||||
} else {
|
||||
captionsSubtitlesTrack = track;
|
||||
@ -175,27 +210,30 @@ class TextTrackDisplay extends Component {
|
||||
* @method updateForTrack
|
||||
*/
|
||||
updateForTrack(track) {
|
||||
if (typeof window['WebVTT'] !== 'function' || !track['activeCues']) {
|
||||
if (typeof window.WebVTT !== 'function' || !track.activeCues) {
|
||||
return;
|
||||
}
|
||||
|
||||
let overrides = this.player_['textTrackSettings'].getValues();
|
||||
|
||||
let overrides = this.player_.textTrackSettings.getValues();
|
||||
let cues = [];
|
||||
for (let i = 0; i < track['activeCues'].length; i++) {
|
||||
cues.push(track['activeCues'][i]);
|
||||
|
||||
for (let i = 0; i < track.activeCues.length; i++) {
|
||||
cues.push(track.activeCues[i]);
|
||||
}
|
||||
|
||||
window['WebVTT']['processCues'](window, cues, this.el_);
|
||||
window.WebVTT.processCues(window, cues, this.el_);
|
||||
|
||||
let i = cues.length;
|
||||
|
||||
while (i--) {
|
||||
let cue = cues[i];
|
||||
|
||||
if (!cue) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let cueDiv = cue.displayState;
|
||||
|
||||
if (overrides.color) {
|
||||
cueDiv.firstChild.style.color = overrides.color;
|
||||
}
|
||||
@ -236,6 +274,7 @@ class TextTrackDisplay extends Component {
|
||||
}
|
||||
if (overrides.fontPercent && overrides.fontPercent !== 1) {
|
||||
const fontSize = window.parseFloat(cueDiv.style.fontSize);
|
||||
|
||||
cueDiv.style.fontSize = (fontSize * overrides.fontPercent) + 'px';
|
||||
cueDiv.style.height = 'auto';
|
||||
cueDiv.style.top = 'auto';
|
||||
@ -253,38 +292,5 @@ class TextTrackDisplay extends Component {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Add cue HTML to display
|
||||
*
|
||||
* @param {Number} color Hex number for color, like #f0e
|
||||
* @param {Number} opacity Value for opacity,0.0 - 1.0
|
||||
* @return {RGBAColor} In the form 'rgba(255, 0, 0, 0.3)'
|
||||
* @method constructColor
|
||||
*/
|
||||
function constructColor(color, opacity) {
|
||||
return 'rgba(' +
|
||||
// color looks like "#f0e"
|
||||
parseInt(color[1] + color[1], 16) + ',' +
|
||||
parseInt(color[2] + color[2], 16) + ',' +
|
||||
parseInt(color[3] + color[3], 16) + ',' +
|
||||
opacity + ')';
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to update style
|
||||
* Some style changes will throw an error, particularly in IE8. Those should be noops.
|
||||
*
|
||||
* @param {Element} el The element to be styles
|
||||
* @param {CSSProperty} style The CSS property to be styled
|
||||
* @param {CSSStyle} rule The actual style to be applied to the property
|
||||
* @method tryUpdateStyle
|
||||
*/
|
||||
function tryUpdateStyle(el, style, rule) {
|
||||
//
|
||||
try {
|
||||
el.style[style] = rule;
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
Component.registerComponent('TextTrackDisplay', TextTrackDisplay);
|
||||
export default TextTrackDisplay;
|
||||
|
@ -13,13 +13,15 @@
|
||||
* @private
|
||||
*/
|
||||
let trackToJson_ = function(track) {
|
||||
let ret = ['kind', 'label', 'language', 'id',
|
||||
'inBandMetadataTrackDispatchType',
|
||||
'mode', 'src'].reduce((acc, prop, i) => {
|
||||
let ret = [
|
||||
'kind', 'label', 'language', 'id',
|
||||
'inBandMetadataTrackDispatchType', 'mode', 'src'
|
||||
].reduce((acc, prop, i) => {
|
||||
|
||||
if (track[prop]) {
|
||||
acc[prop] = track[prop];
|
||||
}
|
||||
|
||||
|
||||
return acc;
|
||||
}, {
|
||||
cues: track.cues && Array.prototype.map.call(track.cues, function(cue) {
|
||||
@ -50,6 +52,7 @@ let textTracksToJson = function(tech) {
|
||||
let trackObjs = Array.prototype.map.call(trackEls, (t) => t.track);
|
||||
let tracks = Array.prototype.map.call(trackEls, function(trackEl) {
|
||||
let json = trackToJson_(trackEl.track);
|
||||
|
||||
if (trackEl.src) {
|
||||
json.src = trackEl.src;
|
||||
}
|
||||
@ -72,6 +75,7 @@ let textTracksToJson = function(tech) {
|
||||
let jsonToTextTracks = function(json, tech) {
|
||||
json.forEach(function(track) {
|
||||
let addedTrack = tech.addRemoteTextTrack(track).track;
|
||||
|
||||
if (!track.src && track.cues) {
|
||||
track.cues.forEach((cue) => addedTrack.addCue(cue));
|
||||
}
|
||||
|
@ -8,250 +8,7 @@ import log from '../utils/log.js';
|
||||
import safeParseTuple from 'safe-json-parse/tuple';
|
||||
import window from 'global/window';
|
||||
|
||||
/**
|
||||
* Manipulate settings of texttracks
|
||||
*
|
||||
* @param {Object} player Main Player
|
||||
* @param {Object=} options Object of option names and values
|
||||
* @extends Component
|
||||
* @class TextTrackSettings
|
||||
*/
|
||||
class TextTrackSettings extends Component {
|
||||
|
||||
constructor(player, options) {
|
||||
super(player, options);
|
||||
this.hide();
|
||||
|
||||
// Grab `persistTextTrackSettings` from the player options if not passed in child options
|
||||
if (options.persistTextTrackSettings === undefined) {
|
||||
this.options_.persistTextTrackSettings = this.options_.playerOptions.persistTextTrackSettings;
|
||||
}
|
||||
|
||||
Events.on(this.$('.vjs-done-button'), 'click', Fn.bind(this, function() {
|
||||
this.saveSettings();
|
||||
this.hide();
|
||||
}));
|
||||
|
||||
Events.on(this.$('.vjs-default-button'), 'click', Fn.bind(this, function() {
|
||||
this.$('.vjs-fg-color > select').selectedIndex = 0;
|
||||
this.$('.vjs-bg-color > select').selectedIndex = 0;
|
||||
this.$('.window-color > select').selectedIndex = 0;
|
||||
this.$('.vjs-text-opacity > select').selectedIndex = 0;
|
||||
this.$('.vjs-bg-opacity > select').selectedIndex = 0;
|
||||
this.$('.vjs-window-opacity > select').selectedIndex = 0;
|
||||
this.$('.vjs-edge-style select').selectedIndex = 0;
|
||||
this.$('.vjs-font-family select').selectedIndex = 0;
|
||||
this.$('.vjs-font-percent select').selectedIndex = 2;
|
||||
this.updateDisplay();
|
||||
}));
|
||||
|
||||
Events.on(this.$('.vjs-fg-color > select'), 'change', Fn.bind(this, this.updateDisplay));
|
||||
Events.on(this.$('.vjs-bg-color > select'), 'change', Fn.bind(this, this.updateDisplay));
|
||||
Events.on(this.$('.window-color > select'), 'change', Fn.bind(this, this.updateDisplay));
|
||||
Events.on(this.$('.vjs-text-opacity > select'), 'change', Fn.bind(this, this.updateDisplay));
|
||||
Events.on(this.$('.vjs-bg-opacity > select'), 'change', Fn.bind(this, this.updateDisplay));
|
||||
Events.on(this.$('.vjs-window-opacity > select'), 'change', Fn.bind(this, this.updateDisplay));
|
||||
Events.on(this.$('.vjs-font-percent select'), 'change', Fn.bind(this, this.updateDisplay));
|
||||
Events.on(this.$('.vjs-edge-style select'), 'change', Fn.bind(this, this.updateDisplay));
|
||||
Events.on(this.$('.vjs-font-family select'), 'change', Fn.bind(this, this.updateDisplay));
|
||||
|
||||
if (this.options_.persistTextTrackSettings) {
|
||||
this.restoreSettings();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the component's DOM element
|
||||
*
|
||||
* @return {Element}
|
||||
* @method createEl
|
||||
*/
|
||||
createEl() {
|
||||
let uniqueId = this.id_;
|
||||
let dialogLabelId = 'TTsettingsDialogLabel-' + uniqueId;
|
||||
let dialogDescriptionId = 'TTsettingsDialogDescription-' + uniqueId;
|
||||
|
||||
return super.createEl('div', {
|
||||
className: 'vjs-caption-settings vjs-modal-overlay',
|
||||
innerHTML: captionOptionsMenuTemplate(uniqueId, dialogLabelId, dialogDescriptionId),
|
||||
tabIndex: -1
|
||||
}, {
|
||||
role: 'dialog',
|
||||
'aria-labelledby': dialogLabelId,
|
||||
'aria-describedby': dialogDescriptionId
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get texttrack settings
|
||||
* Settings are
|
||||
* .vjs-edge-style
|
||||
* .vjs-font-family
|
||||
* .vjs-fg-color
|
||||
* .vjs-text-opacity
|
||||
* .vjs-bg-color
|
||||
* .vjs-bg-opacity
|
||||
* .window-color
|
||||
* .vjs-window-opacity
|
||||
*
|
||||
* @return {Object}
|
||||
* @method getValues
|
||||
*/
|
||||
getValues() {
|
||||
const textEdge = getSelectedOptionValue(this.$('.vjs-edge-style select'));
|
||||
const fontFamily = getSelectedOptionValue(this.$('.vjs-font-family select'));
|
||||
const fgColor = getSelectedOptionValue(this.$('.vjs-fg-color > select'));
|
||||
const textOpacity = getSelectedOptionValue(this.$('.vjs-text-opacity > select'));
|
||||
const bgColor = getSelectedOptionValue(this.$('.vjs-bg-color > select'));
|
||||
const bgOpacity = getSelectedOptionValue(this.$('.vjs-bg-opacity > select'));
|
||||
const windowColor = getSelectedOptionValue(this.$('.window-color > select'));
|
||||
const windowOpacity = getSelectedOptionValue(this.$('.vjs-window-opacity > select'));
|
||||
const fontPercent = window['parseFloat'](getSelectedOptionValue(this.$('.vjs-font-percent > select')));
|
||||
|
||||
let result = {
|
||||
'backgroundOpacity': bgOpacity,
|
||||
'textOpacity': textOpacity,
|
||||
'windowOpacity': windowOpacity,
|
||||
'edgeStyle': textEdge,
|
||||
'fontFamily': fontFamily,
|
||||
'color': fgColor,
|
||||
'backgroundColor': bgColor,
|
||||
'windowColor': windowColor,
|
||||
'fontPercent': fontPercent
|
||||
};
|
||||
for (let name in result) {
|
||||
if (result[name] === '' || result[name] === 'none' || (name === 'fontPercent' && result[name] === 1.00)) {
|
||||
delete result[name];
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set texttrack settings
|
||||
* Settings are
|
||||
* .vjs-edge-style
|
||||
* .vjs-font-family
|
||||
* .vjs-fg-color
|
||||
* .vjs-text-opacity
|
||||
* .vjs-bg-color
|
||||
* .vjs-bg-opacity
|
||||
* .window-color
|
||||
* .vjs-window-opacity
|
||||
*
|
||||
* @param {Object} values Object with texttrack setting values
|
||||
* @method setValues
|
||||
*/
|
||||
setValues(values) {
|
||||
setSelectedOption(this.$('.vjs-edge-style select'), values.edgeStyle);
|
||||
setSelectedOption(this.$('.vjs-font-family select'), values.fontFamily);
|
||||
setSelectedOption(this.$('.vjs-fg-color > select'), values.color);
|
||||
setSelectedOption(this.$('.vjs-text-opacity > select'), values.textOpacity);
|
||||
setSelectedOption(this.$('.vjs-bg-color > select'), values.backgroundColor);
|
||||
setSelectedOption(this.$('.vjs-bg-opacity > select'), values.backgroundOpacity);
|
||||
setSelectedOption(this.$('.window-color > select'), values.windowColor);
|
||||
setSelectedOption(this.$('.vjs-window-opacity > select'), values.windowOpacity);
|
||||
|
||||
let fontPercent = values.fontPercent;
|
||||
|
||||
if (fontPercent) {
|
||||
fontPercent = fontPercent.toFixed(2);
|
||||
}
|
||||
|
||||
setSelectedOption(this.$('.vjs-font-percent > select'), fontPercent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore texttrack settings
|
||||
*
|
||||
* @method restoreSettings
|
||||
*/
|
||||
restoreSettings() {
|
||||
let err, values;
|
||||
|
||||
try {
|
||||
[err, values] = safeParseTuple(window.localStorage.getItem('vjs-text-track-settings'));
|
||||
|
||||
if (err) {
|
||||
log.error(err);
|
||||
}
|
||||
} catch (e) {
|
||||
log.warn(e);
|
||||
}
|
||||
|
||||
if (values) {
|
||||
this.setValues(values);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save texttrack settings to local storage
|
||||
*
|
||||
* @method saveSettings
|
||||
*/
|
||||
saveSettings() {
|
||||
if (!this.options_.persistTextTrackSettings) {
|
||||
return;
|
||||
}
|
||||
|
||||
let values = this.getValues();
|
||||
try {
|
||||
if (Object.getOwnPropertyNames(values).length > 0) {
|
||||
window.localStorage.setItem('vjs-text-track-settings', JSON.stringify(values));
|
||||
} else {
|
||||
window.localStorage.removeItem('vjs-text-track-settings');
|
||||
}
|
||||
} catch (e) {
|
||||
log.warn(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update display of texttrack settings
|
||||
*
|
||||
* @method updateDisplay
|
||||
*/
|
||||
updateDisplay() {
|
||||
let ttDisplay = this.player_.getChild('textTrackDisplay');
|
||||
if (ttDisplay) {
|
||||
ttDisplay.updateDisplay();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Component.registerComponent('TextTrackSettings', TextTrackSettings);
|
||||
|
||||
function getSelectedOptionValue(target) {
|
||||
let selectedOption;
|
||||
// not all browsers support selectedOptions, so, fallback to options
|
||||
if (target.selectedOptions) {
|
||||
selectedOption = target.selectedOptions[0];
|
||||
} else if (target.options) {
|
||||
selectedOption = target.options[target.options.selectedIndex];
|
||||
}
|
||||
|
||||
return selectedOption.value;
|
||||
}
|
||||
|
||||
function setSelectedOption(target, value) {
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
|
||||
let i;
|
||||
for (i = 0; i < target.options.length; i++) {
|
||||
const option = target.options[i];
|
||||
if (option.value === value) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
target.selectedIndex = i;
|
||||
}
|
||||
|
||||
function captionOptionsMenuTemplate(uniqueId, dialogLabelId, dialogDescriptionId) {
|
||||
|
||||
let template = `
|
||||
<div role="document">
|
||||
<div role="heading" aria-level="1" id="${dialogLabelId}" class="vjs-control-text">Captions Settings Dialog</div>
|
||||
@ -367,9 +124,259 @@ function captionOptionsMenuTemplate(uniqueId, dialogLabelId, dialogDescriptionId
|
||||
<button class="vjs-done-button">Done</button>
|
||||
</div>
|
||||
</div> <!-- vjs-tracksettings -->
|
||||
</div> <!-- role="document" -->`;
|
||||
</div> <!-- role="document" -->
|
||||
`;
|
||||
|
||||
return template;
|
||||
return template;
|
||||
}
|
||||
|
||||
function getSelectedOptionValue(target) {
|
||||
let selectedOption;
|
||||
|
||||
// not all browsers support selectedOptions, so, fallback to options
|
||||
if (target.selectedOptions) {
|
||||
selectedOption = target.selectedOptions[0];
|
||||
} else if (target.options) {
|
||||
selectedOption = target.options[target.options.selectedIndex];
|
||||
}
|
||||
|
||||
return selectedOption.value;
|
||||
}
|
||||
|
||||
function setSelectedOption(target, value) {
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
|
||||
let i;
|
||||
|
||||
for (i = 0; i < target.options.length; i++) {
|
||||
const option = target.options[i];
|
||||
|
||||
if (option.value === value) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
target.selectedIndex = i;
|
||||
}
|
||||
|
||||
/**
|
||||
* Manipulate settings of texttracks
|
||||
*
|
||||
* @param {Object} player Main Player
|
||||
* @param {Object=} options Object of option names and values
|
||||
* @extends Component
|
||||
* @class TextTrackSettings
|
||||
*/
|
||||
class TextTrackSettings extends Component {
|
||||
|
||||
constructor(player, options) {
|
||||
super(player, options);
|
||||
this.hide();
|
||||
|
||||
// Grab `persistTextTrackSettings` from the player options if not passed in child options
|
||||
if (options.persistTextTrackSettings === undefined) {
|
||||
this.options_.persistTextTrackSettings = this.options_.playerOptions.persistTextTrackSettings;
|
||||
}
|
||||
|
||||
Events.on(this.$('.vjs-done-button'), 'click', Fn.bind(this, function() {
|
||||
this.saveSettings();
|
||||
this.hide();
|
||||
}));
|
||||
|
||||
Events.on(this.$('.vjs-default-button'), 'click', Fn.bind(this, function() {
|
||||
this.$('.vjs-fg-color > select').selectedIndex = 0;
|
||||
this.$('.vjs-bg-color > select').selectedIndex = 0;
|
||||
this.$('.window-color > select').selectedIndex = 0;
|
||||
this.$('.vjs-text-opacity > select').selectedIndex = 0;
|
||||
this.$('.vjs-bg-opacity > select').selectedIndex = 0;
|
||||
this.$('.vjs-window-opacity > select').selectedIndex = 0;
|
||||
this.$('.vjs-edge-style select').selectedIndex = 0;
|
||||
this.$('.vjs-font-family select').selectedIndex = 0;
|
||||
this.$('.vjs-font-percent select').selectedIndex = 2;
|
||||
this.updateDisplay();
|
||||
}));
|
||||
|
||||
Events.on(this.$('.vjs-fg-color > select'), 'change', Fn.bind(this, this.updateDisplay));
|
||||
Events.on(this.$('.vjs-bg-color > select'), 'change', Fn.bind(this, this.updateDisplay));
|
||||
Events.on(this.$('.window-color > select'), 'change', Fn.bind(this, this.updateDisplay));
|
||||
Events.on(this.$('.vjs-text-opacity > select'), 'change', Fn.bind(this, this.updateDisplay));
|
||||
Events.on(this.$('.vjs-bg-opacity > select'), 'change', Fn.bind(this, this.updateDisplay));
|
||||
Events.on(this.$('.vjs-window-opacity > select'), 'change', Fn.bind(this, this.updateDisplay));
|
||||
Events.on(this.$('.vjs-font-percent select'), 'change', Fn.bind(this, this.updateDisplay));
|
||||
Events.on(this.$('.vjs-edge-style select'), 'change', Fn.bind(this, this.updateDisplay));
|
||||
Events.on(this.$('.vjs-font-family select'), 'change', Fn.bind(this, this.updateDisplay));
|
||||
|
||||
if (this.options_.persistTextTrackSettings) {
|
||||
this.restoreSettings();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the component's DOM element
|
||||
*
|
||||
* @return {Element}
|
||||
* @method createEl
|
||||
*/
|
||||
createEl() {
|
||||
let uniqueId = this.id_;
|
||||
let dialogLabelId = 'TTsettingsDialogLabel-' + uniqueId;
|
||||
let dialogDescriptionId = 'TTsettingsDialogDescription-' + uniqueId;
|
||||
|
||||
return super.createEl('div', {
|
||||
className: 'vjs-caption-settings vjs-modal-overlay',
|
||||
innerHTML: captionOptionsMenuTemplate(uniqueId, dialogLabelId, dialogDescriptionId),
|
||||
tabIndex: -1
|
||||
}, {
|
||||
'role': 'dialog',
|
||||
'aria-labelledby': dialogLabelId,
|
||||
'aria-describedby': dialogDescriptionId
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get texttrack settings
|
||||
* Settings are
|
||||
* .vjs-edge-style
|
||||
* .vjs-font-family
|
||||
* .vjs-fg-color
|
||||
* .vjs-text-opacity
|
||||
* .vjs-bg-color
|
||||
* .vjs-bg-opacity
|
||||
* .window-color
|
||||
* .vjs-window-opacity
|
||||
*
|
||||
* @return {Object}
|
||||
* @method getValues
|
||||
*/
|
||||
getValues() {
|
||||
const textEdge = getSelectedOptionValue(this.$('.vjs-edge-style select'));
|
||||
const fontFamily = getSelectedOptionValue(this.$('.vjs-font-family select'));
|
||||
const fgColor = getSelectedOptionValue(this.$('.vjs-fg-color > select'));
|
||||
const textOpacity = getSelectedOptionValue(this.$('.vjs-text-opacity > select'));
|
||||
const bgColor = getSelectedOptionValue(this.$('.vjs-bg-color > select'));
|
||||
const bgOpacity = getSelectedOptionValue(this.$('.vjs-bg-opacity > select'));
|
||||
const windowColor = getSelectedOptionValue(this.$('.window-color > select'));
|
||||
const windowOpacity = getSelectedOptionValue(this.$('.vjs-window-opacity > select'));
|
||||
const fontPercent = window.parseFloat(getSelectedOptionValue(this.$('.vjs-font-percent > select')));
|
||||
|
||||
let result = {
|
||||
fontPercent,
|
||||
fontFamily,
|
||||
textOpacity,
|
||||
windowColor,
|
||||
windowOpacity,
|
||||
backgroundOpacity: bgOpacity,
|
||||
edgeStyle: textEdge,
|
||||
color: fgColor,
|
||||
backgroundColor: bgColor
|
||||
};
|
||||
|
||||
for (let name in result) {
|
||||
if (result[name] === '' || result[name] === 'none' || (name === 'fontPercent' && result[name] === 1.00)) {
|
||||
delete result[name];
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set texttrack settings
|
||||
* Settings are
|
||||
* .vjs-edge-style
|
||||
* .vjs-font-family
|
||||
* .vjs-fg-color
|
||||
* .vjs-text-opacity
|
||||
* .vjs-bg-color
|
||||
* .vjs-bg-opacity
|
||||
* .window-color
|
||||
* .vjs-window-opacity
|
||||
*
|
||||
* @param {Object} values Object with texttrack setting values
|
||||
* @method setValues
|
||||
*/
|
||||
setValues(values) {
|
||||
setSelectedOption(this.$('.vjs-edge-style select'), values.edgeStyle);
|
||||
setSelectedOption(this.$('.vjs-font-family select'), values.fontFamily);
|
||||
setSelectedOption(this.$('.vjs-fg-color > select'), values.color);
|
||||
setSelectedOption(this.$('.vjs-text-opacity > select'), values.textOpacity);
|
||||
setSelectedOption(this.$('.vjs-bg-color > select'), values.backgroundColor);
|
||||
setSelectedOption(this.$('.vjs-bg-opacity > select'), values.backgroundOpacity);
|
||||
setSelectedOption(this.$('.window-color > select'), values.windowColor);
|
||||
setSelectedOption(this.$('.vjs-window-opacity > select'), values.windowOpacity);
|
||||
|
||||
let fontPercent = values.fontPercent;
|
||||
|
||||
if (fontPercent) {
|
||||
fontPercent = fontPercent.toFixed(2);
|
||||
}
|
||||
|
||||
setSelectedOption(this.$('.vjs-font-percent > select'), fontPercent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore texttrack settings
|
||||
*
|
||||
* @method restoreSettings
|
||||
*/
|
||||
restoreSettings() {
|
||||
let err;
|
||||
let values;
|
||||
|
||||
try {
|
||||
[err, values] = safeParseTuple(window.localStorage.getItem('vjs-text-track-settings'));
|
||||
|
||||
if (err) {
|
||||
log.error(err);
|
||||
}
|
||||
} catch (e) {
|
||||
log.warn(e);
|
||||
}
|
||||
|
||||
if (values) {
|
||||
this.setValues(values);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save texttrack settings to local storage
|
||||
*
|
||||
* @method saveSettings
|
||||
*/
|
||||
saveSettings() {
|
||||
if (!this.options_.persistTextTrackSettings) {
|
||||
return;
|
||||
}
|
||||
|
||||
let values = this.getValues();
|
||||
|
||||
try {
|
||||
if (Object.getOwnPropertyNames(values).length > 0) {
|
||||
window.localStorage.setItem('vjs-text-track-settings', JSON.stringify(values));
|
||||
} else {
|
||||
window.localStorage.removeItem('vjs-text-track-settings');
|
||||
}
|
||||
} catch (e) {
|
||||
log.warn(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update display of texttrack settings
|
||||
*
|
||||
* @method updateDisplay
|
||||
*/
|
||||
updateDisplay() {
|
||||
let ttDisplay = this.player_.getChild('textTrackDisplay');
|
||||
|
||||
if (ttDisplay) {
|
||||
ttDisplay.updateDisplay();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Component.registerComponent('TextTrackSettings', TextTrackSettings);
|
||||
|
||||
export default TextTrackSettings;
|
||||
|
@ -5,7 +5,6 @@ import TextTrackCueList from './text-track-cue-list';
|
||||
import * as Fn from '../utils/fn.js';
|
||||
import {TextTrackKind, TextTrackMode} from './track-enums';
|
||||
import log from '../utils/log.js';
|
||||
import document from 'global/document';
|
||||
import window from 'global/window';
|
||||
import Track from './track.js';
|
||||
import { isCrossOrigin } from '../utils/url.js';
|
||||
@ -42,12 +41,12 @@ const parseCues = function(srcContent, track) {
|
||||
|
||||
parser.parse(srcContent);
|
||||
if (errors.length > 0) {
|
||||
if (console.groupCollapsed) {
|
||||
console.groupCollapsed(`Text Track parsing errors for ${track.src}`);
|
||||
if (window.console && window.console.groupCollapsed) {
|
||||
window.console.groupCollapsed(`Text Track parsing errors for ${track.src}`);
|
||||
}
|
||||
errors.forEach((error) => log.error(error));
|
||||
if (console.groupEnd) {
|
||||
console.groupEnd();
|
||||
if (window.console && window.console.groupEnd) {
|
||||
window.console.groupEnd();
|
||||
}
|
||||
}
|
||||
|
||||
@ -82,6 +81,7 @@ const loadTrack = function(src, track) {
|
||||
if (typeof window.WebVTT !== 'function') {
|
||||
if (track.tech_) {
|
||||
let loadHandler = () => parseCues(responseBody, track);
|
||||
|
||||
track.tech_.on('vttjsloaded', loadHandler);
|
||||
track.tech_.on('vttjserror', () => {
|
||||
log.error(`vttjs failed to load, stopping trying to process ${track.src}`);
|
||||
@ -142,6 +142,7 @@ class TextTrack extends Track {
|
||||
// on IE8 this will be a document element
|
||||
// for every other browser this will be a normal object
|
||||
let tt = super(settings);
|
||||
|
||||
tt.tech_ = settings.tech;
|
||||
|
||||
if (browser.IS_IE8) {
|
||||
@ -159,6 +160,10 @@ class TextTrack extends Track {
|
||||
let activeCues = new TextTrackCueList(tt.activeCues_);
|
||||
let changed = false;
|
||||
let timeupdateHandler = Fn.bind(tt, function() {
|
||||
|
||||
// Accessing this.activeCues for the side-effects of updating itself
|
||||
// due to it's nature as a getter function. Do not remove or cues will
|
||||
// stop updating!
|
||||
this.activeCues;
|
||||
if (changed) {
|
||||
this.trigger('cuechange');
|
||||
|
@ -21,7 +21,7 @@ const VideoTrackKind = {
|
||||
main: 'main',
|
||||
sign: 'sign',
|
||||
subtitles: 'subtitles',
|
||||
commentary: 'commentary',
|
||||
commentary: 'commentary'
|
||||
};
|
||||
|
||||
/**
|
||||
@ -38,12 +38,12 @@ const VideoTrackKind = {
|
||||
* };
|
||||
*/
|
||||
const AudioTrackKind = {
|
||||
alternative: 'alternative',
|
||||
descriptions: 'descriptions',
|
||||
main: 'main',
|
||||
'alternative': 'alternative',
|
||||
'descriptions': 'descriptions',
|
||||
'main': 'main',
|
||||
'main-desc': 'main-desc',
|
||||
translation: 'translation',
|
||||
commentary: 'commentary',
|
||||
'translation': 'translation',
|
||||
'commentary': 'commentary'
|
||||
};
|
||||
|
||||
/**
|
||||
@ -65,8 +65,6 @@ const TextTrackKind = {
|
||||
metadata: 'metadata'
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackmode
|
||||
*
|
||||
@ -78,9 +76,4 @@ const TextTrackMode = {
|
||||
showing: 'showing'
|
||||
};
|
||||
|
||||
/* jshint ignore:start */
|
||||
// we ignore jshint here because it does not see
|
||||
// AudioTrackKind as defined here
|
||||
export default { VideoTrackKind, AudioTrackKind, TextTrackKind, TextTrackMode };
|
||||
/* jshint ignore:end */
|
||||
|
||||
|
@ -2,7 +2,6 @@
|
||||
* @file track-list.js
|
||||
*/
|
||||
import EventTarget from '../event-target';
|
||||
import * as Fn from '../utils/fn.js';
|
||||
import * as browser from '../utils/browser.js';
|
||||
import document from 'global/document';
|
||||
|
||||
@ -20,7 +19,7 @@ class TrackList extends EventTarget {
|
||||
constructor(tracks = [], list = null) {
|
||||
super();
|
||||
if (!list) {
|
||||
list = this;
|
||||
list = this; // eslint-disable-line
|
||||
if (browser.IS_IE8) {
|
||||
list = document.createElement('custom');
|
||||
for (let prop in TrackList.prototype) {
|
||||
@ -119,6 +118,7 @@ class TrackList extends EventTarget {
|
||||
|
||||
for (let i = 0, l = this.length; i < l; i++) {
|
||||
let track = this[i];
|
||||
|
||||
if (track.id === id) {
|
||||
result = track;
|
||||
break;
|
||||
|
@ -19,7 +19,8 @@ class Track extends EventTarget {
|
||||
constructor(options = {}) {
|
||||
super();
|
||||
|
||||
let track = this;
|
||||
let track = this; // eslint-disable-line
|
||||
|
||||
if (browser.IS_IE8) {
|
||||
track = document.createElement('custom');
|
||||
for (let prop in Track.prototype) {
|
||||
@ -38,7 +39,9 @@ class Track extends EventTarget {
|
||||
|
||||
for (let key in trackProps) {
|
||||
Object.defineProperty(track, key, {
|
||||
get() { return trackProps[key]; },
|
||||
get() {
|
||||
return trackProps[key];
|
||||
},
|
||||
set() {}
|
||||
});
|
||||
}
|
||||
|
@ -38,7 +38,9 @@ class VideoTrack extends Track {
|
||||
}
|
||||
|
||||
Object.defineProperty(track, 'selected', {
|
||||
get() { return selected; },
|
||||
get() {
|
||||
return selected;
|
||||
},
|
||||
set(newSelected) {
|
||||
// an invalid or unchanged value
|
||||
if (typeof newSelected !== 'boolean' || newSelected === selected) {
|
||||
|
@ -24,18 +24,21 @@ export const IS_IPHONE = (/iPhone/i).test(USER_AGENT) && !IS_IPAD;
|
||||
export const IS_IPOD = (/iPod/i).test(USER_AGENT);
|
||||
export const IS_IOS = IS_IPHONE || IS_IPAD || IS_IPOD;
|
||||
|
||||
export const IOS_VERSION = (function(){
|
||||
var match = USER_AGENT.match(/OS (\d+)_/i);
|
||||
if (match && match[1]) { return match[1]; }
|
||||
})();
|
||||
export const IOS_VERSION = (function() {
|
||||
let match = USER_AGENT.match(/OS (\d+)_/i);
|
||||
|
||||
if (match && match[1]) {
|
||||
return match[1];
|
||||
}
|
||||
}());
|
||||
|
||||
export const IS_ANDROID = (/Android/i).test(USER_AGENT);
|
||||
export const ANDROID_VERSION = (function() {
|
||||
// This matches Android Major.Minor.Patch versions
|
||||
// ANDROID_VERSION is Major.Minor as a Number, if Minor isn't available, then only Major is returned
|
||||
var match = USER_AGENT.match(/Android (\d+)(?:\.(\d+))?(?:\.(\d+))*/i),
|
||||
major,
|
||||
minor;
|
||||
let match = USER_AGENT.match(/Android (\d+)(?:\.(\d+))?(?:\.(\d+))*/i);
|
||||
let major;
|
||||
let minor;
|
||||
|
||||
if (!match) {
|
||||
return null;
|
||||
@ -48,10 +51,10 @@ export const ANDROID_VERSION = (function() {
|
||||
return parseFloat(match[1] + '.' + match[2]);
|
||||
} else if (major) {
|
||||
return major;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
})();
|
||||
return null;
|
||||
}());
|
||||
|
||||
// Old Android is defined as Version older than 2.3, and requiring a webkit version of the android browser
|
||||
export const IS_OLD_ANDROID = IS_ANDROID && (/webkit/i).test(USER_AGENT) && ANDROID_VERSION < 2.3;
|
||||
export const IS_NATIVE_ANDROID = IS_ANDROID && ANDROID_VERSION < 5 && appleWebkitVersion < 537;
|
||||
@ -60,9 +63,9 @@ export const IS_FIREFOX = (/Firefox/i).test(USER_AGENT);
|
||||
export const IS_EDGE = (/Edge/i).test(USER_AGENT);
|
||||
export const IS_CHROME = !IS_EDGE && (/Chrome/i).test(USER_AGENT);
|
||||
export const IS_IE8 = (/MSIE\s8\.0/).test(USER_AGENT);
|
||||
export const IE_VERSION = (function(result){
|
||||
export const IE_VERSION = (function(result) {
|
||||
return result && parseFloat(result[1]);
|
||||
})((/MSIE\s(\d+)\.\d/).exec(USER_AGENT));
|
||||
}((/MSIE\s(\d+)\.\d/).exec(USER_AGENT)));
|
||||
|
||||
export const TOUCH_ENABLED = !!(('ontouchstart' in window) || window.DocumentTouch && document instanceof window.DocumentTouch);
|
||||
export const BACKGROUND_SIZE_SUPPORTED = 'backgroundSize' in document.createElement('video').style;
|
||||
|
@ -13,8 +13,9 @@ import { createTimeRange } from './time-ranges.js';
|
||||
* @function bufferedPercent
|
||||
*/
|
||||
export function bufferedPercent(buffered, duration) {
|
||||
var bufferedDuration = 0,
|
||||
start, end;
|
||||
let bufferedDuration = 0;
|
||||
let start;
|
||||
let end;
|
||||
|
||||
if (!duration) {
|
||||
return 0;
|
||||
@ -24,9 +25,9 @@ export function bufferedPercent(buffered, duration) {
|
||||
buffered = createTimeRange(0, 0);
|
||||
}
|
||||
|
||||
for (let i = 0; i < buffered.length; i++){
|
||||
for (let i = 0; i < buffered.length; i++) {
|
||||
start = buffered.start(i);
|
||||
end = buffered.end(i);
|
||||
end = buffered.end(i);
|
||||
|
||||
// buffered end can be bigger than duration by a very small fraction
|
||||
if (end > duration) {
|
||||
|
@ -3,7 +3,7 @@
|
||||
*/
|
||||
import document from 'global/document';
|
||||
import window from 'global/window';
|
||||
import * as Guid from './guid.js';
|
||||
import * as Guid from './guid.js';
|
||||
import log from './log.js';
|
||||
import tsml from 'tsml';
|
||||
|
||||
@ -14,7 +14,7 @@ import tsml from 'tsml';
|
||||
* @return {Boolean}
|
||||
*/
|
||||
function isNonBlankString(str) {
|
||||
return typeof str === 'string' && /\S/.test(str);
|
||||
return typeof str === 'string' && (/\S/).test(str);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -25,7 +25,7 @@ function isNonBlankString(str) {
|
||||
* @return {Boolean}
|
||||
*/
|
||||
function throwIfWhitespace(str) {
|
||||
if (/\s/.test(str)) {
|
||||
if ((/\s/).test(str)) {
|
||||
throw new Error('class has illegal whitespace characters');
|
||||
}
|
||||
}
|
||||
@ -40,6 +40,17 @@ function classRegExp(className) {
|
||||
return new RegExp('(^|\\s)' + className + '($|\\s)');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines, via duck typing, whether or not a value is a DOM element.
|
||||
*
|
||||
* @function isEl
|
||||
* @param {Mixed} value
|
||||
* @return {Boolean}
|
||||
*/
|
||||
export function isEl(value) {
|
||||
return !!value && typeof value === 'object' && value.nodeType === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates functions to query the DOM using a given method.
|
||||
*
|
||||
@ -49,7 +60,7 @@ function classRegExp(className) {
|
||||
* @return {Function}
|
||||
*/
|
||||
function createQuerier(method) {
|
||||
return function (selector, context) {
|
||||
return function(selector, context) {
|
||||
if (!isNonBlankString(selector)) {
|
||||
return document[method](null);
|
||||
}
|
||||
@ -68,7 +79,7 @@ function createQuerier(method) {
|
||||
* @return {Element} Element with supplied ID
|
||||
* @function getEl
|
||||
*/
|
||||
export function getEl(id){
|
||||
export function getEl(id) {
|
||||
if (id.indexOf('#') === 0) {
|
||||
id = id.slice(1);
|
||||
}
|
||||
@ -85,10 +96,10 @@ export function getEl(id){
|
||||
* @return {Element}
|
||||
* @function createEl
|
||||
*/
|
||||
export function createEl(tagName='div', properties={}, attributes={}){
|
||||
export function createEl(tagName = 'div', properties = {}, attributes = {}) {
|
||||
let el = document.createElement(tagName);
|
||||
|
||||
Object.getOwnPropertyNames(properties).forEach(function(propName){
|
||||
Object.getOwnPropertyNames(properties).forEach(function(propName) {
|
||||
let val = properties[propName];
|
||||
|
||||
// See #2176
|
||||
@ -104,8 +115,7 @@ export function createEl(tagName='div', properties={}, attributes={}){
|
||||
}
|
||||
});
|
||||
|
||||
Object.getOwnPropertyNames(attributes).forEach(function(attrName){
|
||||
let val = attributes[attrName];
|
||||
Object.getOwnPropertyNames(attributes).forEach(function(attrName) {
|
||||
el.setAttribute(attrName, attributes[attrName]);
|
||||
});
|
||||
|
||||
@ -136,7 +146,7 @@ export function textContent(el, text) {
|
||||
* @private
|
||||
* @function insertElFirst
|
||||
*/
|
||||
export function insertElFirst(child, parent){
|
||||
export function insertElFirst(child, parent) {
|
||||
if (parent.firstChild) {
|
||||
parent.insertBefore(child, parent.firstChild);
|
||||
} else {
|
||||
@ -222,7 +232,7 @@ export function removeElData(el) {
|
||||
// Remove the elIdAttr property from the DOM node
|
||||
try {
|
||||
delete el[elIdAttr];
|
||||
} catch(e) {
|
||||
} catch (e) {
|
||||
if (el.removeAttribute) {
|
||||
el.removeAttribute(elIdAttr);
|
||||
} else {
|
||||
@ -242,10 +252,9 @@ export function removeElData(el) {
|
||||
export function hasElClass(element, classToCheck) {
|
||||
if (element.classList) {
|
||||
return element.classList.contains(classToCheck);
|
||||
} else {
|
||||
throwIfWhitespace(classToCheck);
|
||||
return classRegExp(classToCheck).test(element.className);
|
||||
}
|
||||
throwIfWhitespace(classToCheck);
|
||||
return classRegExp(classToCheck).test(element.className);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -339,7 +348,7 @@ export function toggleElClass(element, classToToggle, predicate) {
|
||||
* @function setElAttributes
|
||||
*/
|
||||
export function setElAttributes(el, attributes) {
|
||||
Object.getOwnPropertyNames(attributes).forEach(function(attrName){
|
||||
Object.getOwnPropertyNames(attributes).forEach(function(attrName) {
|
||||
let attrValue = attributes[attrName];
|
||||
|
||||
if (attrValue === null || typeof attrValue === 'undefined' || attrValue === false) {
|
||||
@ -362,25 +371,29 @@ export function setElAttributes(el, attributes) {
|
||||
* @function getElAttributes
|
||||
*/
|
||||
export function getElAttributes(tag) {
|
||||
var obj, knownBooleans, attrs, attrName, attrVal;
|
||||
let obj;
|
||||
let knownBooleans;
|
||||
let attrs;
|
||||
let attrName;
|
||||
let attrVal;
|
||||
|
||||
obj = {};
|
||||
|
||||
// known boolean attributes
|
||||
// we can check for matching boolean properties, but older browsers
|
||||
// won't know about HTML5 boolean attributes that we still read from
|
||||
knownBooleans = ','+'autoplay,controls,loop,muted,default'+',';
|
||||
knownBooleans = ',' + 'autoplay,controls,loop,muted,default' + ',';
|
||||
|
||||
if (tag && tag.attributes && tag.attributes.length > 0) {
|
||||
attrs = tag.attributes;
|
||||
|
||||
for (var i = attrs.length - 1; i >= 0; i--) {
|
||||
for (let i = attrs.length - 1; i >= 0; i--) {
|
||||
attrName = attrs[i].name;
|
||||
attrVal = attrs[i].value;
|
||||
|
||||
// check for known booleans
|
||||
// the matching element property will return a value for typeof
|
||||
if (typeof tag[attrName] === 'boolean' || knownBooleans.indexOf(','+attrName+',') !== -1) {
|
||||
if (typeof tag[attrName] === 'boolean' || knownBooleans.indexOf(',' + attrName + ',') !== -1) {
|
||||
// the value of an included boolean attribute is typically an empty
|
||||
// string ('') which would equal false if we just check for a false value.
|
||||
// we also don't want support bad code like autoplay='false'
|
||||
@ -492,17 +505,6 @@ export function getPointerPosition(el, event) {
|
||||
return position;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines, via duck typing, whether or not a value is a DOM element.
|
||||
*
|
||||
* @function isEl
|
||||
* @param {Mixed} value
|
||||
* @return {Boolean}
|
||||
*/
|
||||
export function isEl(value) {
|
||||
return !!value && typeof value === 'object' && value.nodeType === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines, via duck typing, whether or not a value is a text node.
|
||||
*
|
||||
@ -577,7 +579,7 @@ export function normalizeContent(content) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (typeof value === 'string' && /\S/.test(value)) {
|
||||
if (typeof value === 'string' && (/\S/).test(value)) {
|
||||
return document.createTextNode(value);
|
||||
}
|
||||
}).filter(value => value);
|
||||
|
@ -7,321 +7,11 @@
|
||||
* robust as jquery's, so there's probably some differences.
|
||||
*/
|
||||
|
||||
import * as Dom from './dom.js';
|
||||
import * as Guid from './guid.js';
|
||||
import * as Dom from './dom.js';
|
||||
import * as Guid from './guid.js';
|
||||
import window from 'global/window';
|
||||
import document from 'global/document';
|
||||
|
||||
/**
|
||||
* Add an event listener to element
|
||||
* It stores the handler function in a separate cache object
|
||||
* and adds a generic handler to the element's event,
|
||||
* along with a unique id (guid) to the element.
|
||||
*
|
||||
* @param {Element|Object} elem Element or object to bind listeners to
|
||||
* @param {String|Array} type Type of event to bind to.
|
||||
* @param {Function} fn Event listener.
|
||||
* @method on
|
||||
*/
|
||||
export function on(elem, type, fn){
|
||||
if (Array.isArray(type)) {
|
||||
return _handleMultipleEvents(on, elem, type, fn);
|
||||
}
|
||||
|
||||
let data = Dom.getElData(elem);
|
||||
|
||||
// We need a place to store all our handler data
|
||||
if (!data.handlers) data.handlers = {};
|
||||
|
||||
if (!data.handlers[type]) data.handlers[type] = [];
|
||||
|
||||
if (!fn.guid) fn.guid = Guid.newGUID();
|
||||
|
||||
data.handlers[type].push(fn);
|
||||
|
||||
if (!data.dispatcher) {
|
||||
data.disabled = false;
|
||||
|
||||
data.dispatcher = function (event, hash){
|
||||
|
||||
if (data.disabled) return;
|
||||
event = fixEvent(event);
|
||||
|
||||
var handlers = data.handlers[event.type];
|
||||
|
||||
if (handlers) {
|
||||
// Copy handlers so if handlers are added/removed during the process it doesn't throw everything off.
|
||||
var handlersCopy = handlers.slice(0);
|
||||
|
||||
for (var m = 0, n = handlersCopy.length; m < n; m++) {
|
||||
if (event.isImmediatePropagationStopped()) {
|
||||
break;
|
||||
} else {
|
||||
handlersCopy[m].call(elem, event, hash);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (data.handlers[type].length === 1) {
|
||||
if (elem.addEventListener) {
|
||||
elem.addEventListener(type, data.dispatcher, false);
|
||||
} else if (elem.attachEvent) {
|
||||
elem.attachEvent('on' + type, data.dispatcher);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes event listeners from an element
|
||||
*
|
||||
* @param {Element|Object} elem Object to remove listeners from
|
||||
* @param {String|Array=} type Type of listener to remove. Don't include to remove all events from element.
|
||||
* @param {Function} fn Specific listener to remove. Don't include to remove listeners for an event type.
|
||||
* @method off
|
||||
*/
|
||||
export function off(elem, type, fn) {
|
||||
// Don't want to add a cache object through getElData if not needed
|
||||
if (!Dom.hasElData(elem)) return;
|
||||
|
||||
let data = Dom.getElData(elem);
|
||||
|
||||
// If no events exist, nothing to unbind
|
||||
if (!data.handlers) { return; }
|
||||
|
||||
if (Array.isArray(type)) {
|
||||
return _handleMultipleEvents(off, elem, type, fn);
|
||||
}
|
||||
|
||||
// Utility function
|
||||
var removeType = function(t){
|
||||
data.handlers[t] = [];
|
||||
_cleanUpEvents(elem,t);
|
||||
};
|
||||
|
||||
// Are we removing all bound events?
|
||||
if (!type) {
|
||||
for (let t in data.handlers) removeType(t);
|
||||
return;
|
||||
}
|
||||
|
||||
var handlers = data.handlers[type];
|
||||
|
||||
// If no handlers exist, nothing to unbind
|
||||
if (!handlers) return;
|
||||
|
||||
// If no listener was provided, remove all listeners for type
|
||||
if (!fn) {
|
||||
removeType(type);
|
||||
return;
|
||||
}
|
||||
|
||||
// We're only removing a single handler
|
||||
if (fn.guid) {
|
||||
for (let n = 0; n < handlers.length; n++) {
|
||||
if (handlers[n].guid === fn.guid) {
|
||||
handlers.splice(n--, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_cleanUpEvents(elem, type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger an event for an element
|
||||
*
|
||||
* @param {Element|Object} elem Element to trigger an event on
|
||||
* @param {Event|Object|String} event A string (the type) or an event object with a type attribute
|
||||
* @param {Object} [hash] data hash to pass along with the event
|
||||
* @return {Boolean=} Returned only if default was prevented
|
||||
* @method trigger
|
||||
*/
|
||||
export function trigger(elem, event, hash) {
|
||||
// Fetches element data and a reference to the parent (for bubbling).
|
||||
// Don't want to add a data object to cache for every parent,
|
||||
// so checking hasElData first.
|
||||
var elemData = (Dom.hasElData(elem)) ? Dom.getElData(elem) : {};
|
||||
var parent = elem.parentNode || elem.ownerDocument;
|
||||
// type = event.type || event,
|
||||
// handler;
|
||||
|
||||
// If an event name was passed as a string, creates an event out of it
|
||||
if (typeof event === 'string') {
|
||||
event = { type:event, target:elem };
|
||||
}
|
||||
// Normalizes the event properties.
|
||||
event = fixEvent(event);
|
||||
|
||||
// If the passed element has a dispatcher, executes the established handlers.
|
||||
if (elemData.dispatcher) {
|
||||
elemData.dispatcher.call(elem, event, hash);
|
||||
}
|
||||
|
||||
// Unless explicitly stopped or the event does not bubble (e.g. media events)
|
||||
// recursively calls this function to bubble the event up the DOM.
|
||||
if (parent && !event.isPropagationStopped() && event.bubbles === true) {
|
||||
trigger.call(null, parent, event, hash);
|
||||
|
||||
// If at the top of the DOM, triggers the default action unless disabled.
|
||||
} else if (!parent && !event.defaultPrevented) {
|
||||
var targetData = Dom.getElData(event.target);
|
||||
|
||||
// Checks if the target has a default action for this event.
|
||||
if (event.target[event.type]) {
|
||||
// Temporarily disables event dispatching on the target as we have already executed the handler.
|
||||
targetData.disabled = true;
|
||||
// Executes the default action.
|
||||
if (typeof event.target[event.type] === 'function') {
|
||||
event.target[event.type]();
|
||||
}
|
||||
// Re-enables event dispatching.
|
||||
targetData.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Inform the triggerer if the default was prevented by returning false
|
||||
return !event.defaultPrevented;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger a listener only once for an event
|
||||
*
|
||||
* @param {Element|Object} elem Element or object to
|
||||
* @param {String|Array} type Name/type of event
|
||||
* @param {Function} fn Event handler function
|
||||
* @method one
|
||||
*/
|
||||
export function one(elem, type, fn) {
|
||||
if (Array.isArray(type)) {
|
||||
return _handleMultipleEvents(one, elem, type, fn);
|
||||
}
|
||||
var func = function(){
|
||||
off(elem, type, func);
|
||||
fn.apply(this, arguments);
|
||||
};
|
||||
// copy the guid to the new function so it can removed using the original function's ID
|
||||
func.guid = fn.guid = fn.guid || Guid.newGUID();
|
||||
on(elem, type, func);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix a native event to have standard property values
|
||||
*
|
||||
* @param {Object} event Event object to fix
|
||||
* @return {Object}
|
||||
* @private
|
||||
* @method fixEvent
|
||||
*/
|
||||
export function fixEvent(event) {
|
||||
|
||||
function returnTrue() { return true; }
|
||||
function returnFalse() { return false; }
|
||||
|
||||
// Test if fixing up is needed
|
||||
// Used to check if !event.stopPropagation instead of isPropagationStopped
|
||||
// But native events return true for stopPropagation, but don't have
|
||||
// other expected methods like isPropagationStopped. Seems to be a problem
|
||||
// with the Javascript Ninja code. So we're just overriding all events now.
|
||||
if (!event || !event.isPropagationStopped) {
|
||||
var old = event || window.event;
|
||||
|
||||
event = {};
|
||||
// Clone the old object so that we can modify the values event = {};
|
||||
// IE8 Doesn't like when you mess with native event properties
|
||||
// Firefox returns false for event.hasOwnProperty('type') and other props
|
||||
// which makes copying more difficult.
|
||||
// TODO: Probably best to create a whitelist of event props
|
||||
for (var key in old) {
|
||||
// Safari 6.0.3 warns you if you try to copy deprecated layerX/Y
|
||||
// Chrome warns you if you try to copy deprecated keyboardEvent.keyLocation
|
||||
// and webkitMovementX/Y
|
||||
if (key !== 'layerX' && key !== 'layerY' && key !== 'keyLocation' &&
|
||||
key !== 'webkitMovementX' && key !== 'webkitMovementY') {
|
||||
// Chrome 32+ warns if you try to copy deprecated returnValue, but
|
||||
// we still want to if preventDefault isn't supported (IE8).
|
||||
if (!(key === 'returnValue' && old.preventDefault)) {
|
||||
event[key] = old[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The event occurred on this element
|
||||
if (!event.target) {
|
||||
event.target = event.srcElement || document;
|
||||
}
|
||||
|
||||
// Handle which other element the event is related to
|
||||
if (!event.relatedTarget) {
|
||||
event.relatedTarget = event.fromElement === event.target ?
|
||||
event.toElement :
|
||||
event.fromElement;
|
||||
}
|
||||
|
||||
// Stop the default browser action
|
||||
event.preventDefault = function () {
|
||||
if (old.preventDefault) {
|
||||
old.preventDefault();
|
||||
}
|
||||
event.returnValue = false;
|
||||
old.returnValue = false;
|
||||
event.defaultPrevented = true;
|
||||
};
|
||||
|
||||
event.defaultPrevented = false;
|
||||
|
||||
// Stop the event from bubbling
|
||||
event.stopPropagation = function () {
|
||||
if (old.stopPropagation) {
|
||||
old.stopPropagation();
|
||||
}
|
||||
event.cancelBubble = true;
|
||||
old.cancelBubble = true;
|
||||
event.isPropagationStopped = returnTrue;
|
||||
};
|
||||
|
||||
event.isPropagationStopped = returnFalse;
|
||||
|
||||
// Stop the event from bubbling and executing other handlers
|
||||
event.stopImmediatePropagation = function () {
|
||||
if (old.stopImmediatePropagation) {
|
||||
old.stopImmediatePropagation();
|
||||
}
|
||||
event.isImmediatePropagationStopped = returnTrue;
|
||||
event.stopPropagation();
|
||||
};
|
||||
|
||||
event.isImmediatePropagationStopped = returnFalse;
|
||||
|
||||
// Handle mouse position
|
||||
if (event.clientX != null) {
|
||||
var doc = document.documentElement, body = document.body;
|
||||
|
||||
event.pageX = event.clientX +
|
||||
(doc && doc.scrollLeft || body && body.scrollLeft || 0) -
|
||||
(doc && doc.clientLeft || body && body.clientLeft || 0);
|
||||
event.pageY = event.clientY +
|
||||
(doc && doc.scrollTop || body && body.scrollTop || 0) -
|
||||
(doc && doc.clientTop || body && body.clientTop || 0);
|
||||
}
|
||||
|
||||
// Handle key presses
|
||||
event.which = event.charCode || event.keyCode;
|
||||
|
||||
// Fix button for mouse clicks:
|
||||
// 0 == left; 1 == middle; 2 == right
|
||||
if (event.button != null) {
|
||||
event.button = (event.button & 1 ? 0 :
|
||||
(event.button & 4 ? 1 :
|
||||
(event.button & 2 ? 2 : 0)));
|
||||
}
|
||||
}
|
||||
|
||||
// Returns fixed-up instance
|
||||
return event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up the listener cache and dispatchers
|
||||
*
|
||||
@ -331,7 +21,7 @@ export function fixEvent(event) {
|
||||
* @method _cleanUpEvents
|
||||
*/
|
||||
function _cleanUpEvents(elem, type) {
|
||||
var data = Dom.getElData(elem);
|
||||
let data = Dom.getElData(elem);
|
||||
|
||||
// Remove the events of a particular type if there are none left
|
||||
if (data.handlers[type].length === 0) {
|
||||
@ -372,7 +62,346 @@ function _cleanUpEvents(elem, type) {
|
||||
*/
|
||||
function _handleMultipleEvents(fn, elem, types, callback) {
|
||||
types.forEach(function(type) {
|
||||
//Call the event method for each one of the types
|
||||
// Call the event method for each one of the types
|
||||
fn(elem, type, callback);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix a native event to have standard property values
|
||||
*
|
||||
* @param {Object} event Event object to fix
|
||||
* @return {Object}
|
||||
* @private
|
||||
* @method fixEvent
|
||||
*/
|
||||
export function fixEvent(event) {
|
||||
|
||||
function returnTrue() {
|
||||
return true;
|
||||
}
|
||||
|
||||
function returnFalse() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Test if fixing up is needed
|
||||
// Used to check if !event.stopPropagation instead of isPropagationStopped
|
||||
// But native events return true for stopPropagation, but don't have
|
||||
// other expected methods like isPropagationStopped. Seems to be a problem
|
||||
// with the Javascript Ninja code. So we're just overriding all events now.
|
||||
if (!event || !event.isPropagationStopped) {
|
||||
let old = event || window.event;
|
||||
|
||||
event = {};
|
||||
// Clone the old object so that we can modify the values event = {};
|
||||
// IE8 Doesn't like when you mess with native event properties
|
||||
// Firefox returns false for event.hasOwnProperty('type') and other props
|
||||
// which makes copying more difficult.
|
||||
// TODO: Probably best to create a whitelist of event props
|
||||
for (let key in old) {
|
||||
// Safari 6.0.3 warns you if you try to copy deprecated layerX/Y
|
||||
// Chrome warns you if you try to copy deprecated keyboardEvent.keyLocation
|
||||
// and webkitMovementX/Y
|
||||
if (key !== 'layerX' && key !== 'layerY' && key !== 'keyLocation' &&
|
||||
key !== 'webkitMovementX' && key !== 'webkitMovementY') {
|
||||
// Chrome 32+ warns if you try to copy deprecated returnValue, but
|
||||
// we still want to if preventDefault isn't supported (IE8).
|
||||
if (!(key === 'returnValue' && old.preventDefault)) {
|
||||
event[key] = old[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The event occurred on this element
|
||||
if (!event.target) {
|
||||
event.target = event.srcElement || document;
|
||||
}
|
||||
|
||||
// Handle which other element the event is related to
|
||||
if (!event.relatedTarget) {
|
||||
event.relatedTarget = event.fromElement === event.target ?
|
||||
event.toElement :
|
||||
event.fromElement;
|
||||
}
|
||||
|
||||
// Stop the default browser action
|
||||
event.preventDefault = function() {
|
||||
if (old.preventDefault) {
|
||||
old.preventDefault();
|
||||
}
|
||||
event.returnValue = false;
|
||||
old.returnValue = false;
|
||||
event.defaultPrevented = true;
|
||||
};
|
||||
|
||||
event.defaultPrevented = false;
|
||||
|
||||
// Stop the event from bubbling
|
||||
event.stopPropagation = function() {
|
||||
if (old.stopPropagation) {
|
||||
old.stopPropagation();
|
||||
}
|
||||
event.cancelBubble = true;
|
||||
old.cancelBubble = true;
|
||||
event.isPropagationStopped = returnTrue;
|
||||
};
|
||||
|
||||
event.isPropagationStopped = returnFalse;
|
||||
|
||||
// Stop the event from bubbling and executing other handlers
|
||||
event.stopImmediatePropagation = function() {
|
||||
if (old.stopImmediatePropagation) {
|
||||
old.stopImmediatePropagation();
|
||||
}
|
||||
event.isImmediatePropagationStopped = returnTrue;
|
||||
event.stopPropagation();
|
||||
};
|
||||
|
||||
event.isImmediatePropagationStopped = returnFalse;
|
||||
|
||||
// Handle mouse position
|
||||
if (event.clientX != null) {
|
||||
let doc = document.documentElement;
|
||||
let body = document.body;
|
||||
|
||||
event.pageX = event.clientX +
|
||||
(doc && doc.scrollLeft || body && body.scrollLeft || 0) -
|
||||
(doc && doc.clientLeft || body && body.clientLeft || 0);
|
||||
event.pageY = event.clientY +
|
||||
(doc && doc.scrollTop || body && body.scrollTop || 0) -
|
||||
(doc && doc.clientTop || body && body.clientTop || 0);
|
||||
}
|
||||
|
||||
// Handle key presses
|
||||
event.which = event.charCode || event.keyCode;
|
||||
|
||||
// Fix button for mouse clicks:
|
||||
// 0 == left; 1 == middle; 2 == right
|
||||
if (event.button != null) {
|
||||
|
||||
// The following is disabled because it does not pass videojs-standard
|
||||
// and... yikes.
|
||||
/* eslint-disable */
|
||||
event.button = (event.button & 1 ? 0 :
|
||||
(event.button & 4 ? 1 :
|
||||
(event.button & 2 ? 2 : 0)));
|
||||
/* eslint-enable */
|
||||
}
|
||||
}
|
||||
|
||||
// Returns fixed-up instance
|
||||
return event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an event listener to element
|
||||
* It stores the handler function in a separate cache object
|
||||
* and adds a generic handler to the element's event,
|
||||
* along with a unique id (guid) to the element.
|
||||
*
|
||||
* @param {Element|Object} elem Element or object to bind listeners to
|
||||
* @param {String|Array} type Type of event to bind to.
|
||||
* @param {Function} fn Event listener.
|
||||
* @method on
|
||||
*/
|
||||
export function on(elem, type, fn) {
|
||||
if (Array.isArray(type)) {
|
||||
return _handleMultipleEvents(on, elem, type, fn);
|
||||
}
|
||||
|
||||
let data = Dom.getElData(elem);
|
||||
|
||||
// We need a place to store all our handler data
|
||||
if (!data.handlers) {
|
||||
data.handlers = {};
|
||||
}
|
||||
|
||||
if (!data.handlers[type]) {
|
||||
data.handlers[type] = [];
|
||||
}
|
||||
|
||||
if (!fn.guid) {
|
||||
fn.guid = Guid.newGUID();
|
||||
}
|
||||
|
||||
data.handlers[type].push(fn);
|
||||
|
||||
if (!data.dispatcher) {
|
||||
data.disabled = false;
|
||||
|
||||
data.dispatcher = function(event, hash) {
|
||||
|
||||
if (data.disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
event = fixEvent(event);
|
||||
|
||||
let handlers = data.handlers[event.type];
|
||||
|
||||
if (handlers) {
|
||||
// Copy handlers so if handlers are added/removed during the process it doesn't throw everything off.
|
||||
let handlersCopy = handlers.slice(0);
|
||||
|
||||
for (let m = 0, n = handlersCopy.length; m < n; m++) {
|
||||
if (event.isImmediatePropagationStopped()) {
|
||||
break;
|
||||
} else {
|
||||
handlersCopy[m].call(elem, event, hash);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (data.handlers[type].length === 1) {
|
||||
if (elem.addEventListener) {
|
||||
elem.addEventListener(type, data.dispatcher, false);
|
||||
} else if (elem.attachEvent) {
|
||||
elem.attachEvent('on' + type, data.dispatcher);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes event listeners from an element
|
||||
*
|
||||
* @param {Element|Object} elem Object to remove listeners from
|
||||
* @param {String|Array=} type Type of listener to remove. Don't include to remove all events from element.
|
||||
* @param {Function} fn Specific listener to remove. Don't include to remove listeners for an event type.
|
||||
* @method off
|
||||
*/
|
||||
export function off(elem, type, fn) {
|
||||
// Don't want to add a cache object through getElData if not needed
|
||||
if (!Dom.hasElData(elem)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let data = Dom.getElData(elem);
|
||||
|
||||
// If no events exist, nothing to unbind
|
||||
if (!data.handlers) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Array.isArray(type)) {
|
||||
return _handleMultipleEvents(off, elem, type, fn);
|
||||
}
|
||||
|
||||
// Utility function
|
||||
let removeType = function(t) {
|
||||
data.handlers[t] = [];
|
||||
_cleanUpEvents(elem, t);
|
||||
};
|
||||
|
||||
// Are we removing all bound events?
|
||||
if (!type) {
|
||||
for (let t in data.handlers) {
|
||||
removeType(t);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let handlers = data.handlers[type];
|
||||
|
||||
// If no handlers exist, nothing to unbind
|
||||
if (!handlers) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If no listener was provided, remove all listeners for type
|
||||
if (!fn) {
|
||||
removeType(type);
|
||||
return;
|
||||
}
|
||||
|
||||
// We're only removing a single handler
|
||||
if (fn.guid) {
|
||||
for (let n = 0; n < handlers.length; n++) {
|
||||
if (handlers[n].guid === fn.guid) {
|
||||
handlers.splice(n--, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_cleanUpEvents(elem, type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger an event for an element
|
||||
*
|
||||
* @param {Element|Object} elem Element to trigger an event on
|
||||
* @param {Event|Object|String} event A string (the type) or an event object with a type attribute
|
||||
* @param {Object} [hash] data hash to pass along with the event
|
||||
* @return {Boolean=} Returned only if default was prevented
|
||||
* @method trigger
|
||||
*/
|
||||
export function trigger(elem, event, hash) {
|
||||
// Fetches element data and a reference to the parent (for bubbling).
|
||||
// Don't want to add a data object to cache for every parent,
|
||||
// so checking hasElData first.
|
||||
let elemData = (Dom.hasElData(elem)) ? Dom.getElData(elem) : {};
|
||||
let parent = elem.parentNode || elem.ownerDocument;
|
||||
// type = event.type || event,
|
||||
// handler;
|
||||
|
||||
// If an event name was passed as a string, creates an event out of it
|
||||
if (typeof event === 'string') {
|
||||
event = {type: event, target: elem};
|
||||
}
|
||||
// Normalizes the event properties.
|
||||
event = fixEvent(event);
|
||||
|
||||
// If the passed element has a dispatcher, executes the established handlers.
|
||||
if (elemData.dispatcher) {
|
||||
elemData.dispatcher.call(elem, event, hash);
|
||||
}
|
||||
|
||||
// Unless explicitly stopped or the event does not bubble (e.g. media events)
|
||||
// recursively calls this function to bubble the event up the DOM.
|
||||
if (parent && !event.isPropagationStopped() && event.bubbles === true) {
|
||||
trigger.call(null, parent, event, hash);
|
||||
|
||||
// If at the top of the DOM, triggers the default action unless disabled.
|
||||
} else if (!parent && !event.defaultPrevented) {
|
||||
let targetData = Dom.getElData(event.target);
|
||||
|
||||
// Checks if the target has a default action for this event.
|
||||
if (event.target[event.type]) {
|
||||
// Temporarily disables event dispatching on the target as we have already executed the handler.
|
||||
targetData.disabled = true;
|
||||
// Executes the default action.
|
||||
if (typeof event.target[event.type] === 'function') {
|
||||
event.target[event.type]();
|
||||
}
|
||||
// Re-enables event dispatching.
|
||||
targetData.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Inform the triggerer if the default was prevented by returning false
|
||||
return !event.defaultPrevented;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger a listener only once for an event
|
||||
*
|
||||
* @param {Element|Object} elem Element or object to
|
||||
* @param {String|Array} type Name/type of event
|
||||
* @param {Function} fn Event handler function
|
||||
* @method one
|
||||
*/
|
||||
export function one(elem, type, fn) {
|
||||
if (Array.isArray(type)) {
|
||||
return _handleMultipleEvents(one, elem, type, fn);
|
||||
}
|
||||
let func = function() {
|
||||
off(elem, type, func);
|
||||
fn.apply(this, arguments);
|
||||
};
|
||||
|
||||
// copy the guid to the new function so it can removed using the original function's ID
|
||||
func.guid = fn.guid = fn.guid || Guid.newGUID();
|
||||
on(elem, type, func);
|
||||
}
|
||||
|
@ -16,7 +16,9 @@ import { newGUID } from './guid.js';
|
||||
*/
|
||||
export const bind = function(context, fn, uid) {
|
||||
// Make sure the function has a unique ID
|
||||
if (!fn.guid) { fn.guid = newGUID(); }
|
||||
if (!fn.guid) {
|
||||
fn.guid = newGUID();
|
||||
}
|
||||
|
||||
// Create the new function that changes the context
|
||||
let ret = function() {
|
||||
|
@ -11,7 +11,7 @@
|
||||
* @private
|
||||
* @function formatTime
|
||||
*/
|
||||
function formatTime(seconds, guide=seconds) {
|
||||
function formatTime(seconds, guide = seconds) {
|
||||
seconds = seconds < 0 ? 0 : seconds;
|
||||
let s = Math.floor(seconds % 60);
|
||||
let m = Math.floor(seconds / 60 % 60);
|
||||
|
@ -10,7 +10,7 @@ let _guid = 1;
|
||||
/**
|
||||
* Get the next unique ID
|
||||
*
|
||||
* @return {String}
|
||||
* @return {String}
|
||||
* @function newGUID
|
||||
*/
|
||||
export function newGUID() {
|
||||
|
@ -4,6 +4,8 @@
|
||||
import window from 'global/window';
|
||||
import {IE_VERSION} from './browser';
|
||||
|
||||
let log;
|
||||
|
||||
/**
|
||||
* Log messages to the console and history based on the type of message
|
||||
*
|
||||
@ -16,7 +18,6 @@ import {IE_VERSION} from './browser';
|
||||
* but this is exposed as a parameter to facilitate testing.
|
||||
*/
|
||||
export const logByType = (type, args, stringify = !!IE_VERSION && IE_VERSION < 11) => {
|
||||
const console = window.console;
|
||||
|
||||
// If there's no console then don't try to output messages, but they will
|
||||
// still be stored in `log.history`.
|
||||
@ -24,7 +25,7 @@ export const logByType = (type, args, stringify = !!IE_VERSION && IE_VERSION < 1
|
||||
// Was setting these once outside of this function, but containing them
|
||||
// in the function makes it easier to test cases where console doesn't exist
|
||||
// when the module is executed.
|
||||
const fn = console && console[type] || function(){};
|
||||
const fn = window.console && window.console[type] || function() {};
|
||||
|
||||
if (type !== 'log') {
|
||||
|
||||
@ -45,7 +46,9 @@ export const logByType = (type, args, stringify = !!IE_VERSION && IE_VERSION < 1
|
||||
if (a && typeof a === 'object' || Array.isArray(a)) {
|
||||
try {
|
||||
return JSON.stringify(a);
|
||||
} catch (x) {}
|
||||
} catch (x) {
|
||||
return String(a);
|
||||
}
|
||||
}
|
||||
|
||||
// Cast to string before joining, so we get null and undefined explicitly
|
||||
@ -68,9 +71,9 @@ export const logByType = (type, args, stringify = !!IE_VERSION && IE_VERSION < 1
|
||||
*
|
||||
* @function log
|
||||
*/
|
||||
function log(...args) {
|
||||
log = function(...args) {
|
||||
logByType('log', args);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Keep a history of log messages
|
||||
@ -93,5 +96,4 @@ log.error = (...args) => logByType('error', args);
|
||||
*/
|
||||
log.warn = (...args) => logByType('warn', args);
|
||||
|
||||
|
||||
export default log;
|
||||
|
@ -4,12 +4,14 @@
|
||||
import merge from 'lodash-compat/object/merge';
|
||||
|
||||
function isPlain(obj) {
|
||||
return !!obj
|
||||
&& typeof obj === 'object'
|
||||
&& obj.toString() === '[object Object]'
|
||||
&& obj.constructor === Object;
|
||||
return !!obj &&
|
||||
typeof obj === 'object' &&
|
||||
obj.toString() === '[object Object]' &&
|
||||
obj.constructor === Object;
|
||||
}
|
||||
|
||||
let mergeOptions;
|
||||
|
||||
/**
|
||||
* Merge customizer. video.js simply overwrites non-simple objects
|
||||
* (like arrays) instead of attempting to overlay them.
|
||||
@ -41,7 +43,7 @@ const customizer = function(destination, source) {
|
||||
* provided objects
|
||||
* @function mergeOptions
|
||||
*/
|
||||
export default function mergeOptions() {
|
||||
mergeOptions = function() {
|
||||
// contruct the call dynamically to handle the variable number of
|
||||
// objects to merge
|
||||
let args = Array.prototype.slice.call(arguments);
|
||||
@ -57,4 +59,6 @@ export default function mergeOptions() {
|
||||
|
||||
// return the mutated result object
|
||||
return args[0];
|
||||
}
|
||||
};
|
||||
|
||||
export default mergeOptions;
|
||||
|
@ -2,6 +2,7 @@ import document from 'global/document';
|
||||
|
||||
export let createStyleElement = function(className) {
|
||||
let style = document.createElement('style');
|
||||
|
||||
style.className = className;
|
||||
|
||||
return style;
|
||||
|
@ -1,5 +1,39 @@
|
||||
import log from './log.js';
|
||||
|
||||
function rangeCheck(fnName, index, maxIndex) {
|
||||
if (index < 0 || index > maxIndex) {
|
||||
throw new Error(`Failed to execute '${fnName}' on 'TimeRanges': The index provided (${index}) is greater than or equal to the maximum bound (${maxIndex}).`);
|
||||
}
|
||||
}
|
||||
|
||||
function getRange(fnName, valueIndex, ranges, rangeIndex) {
|
||||
if (rangeIndex === undefined) {
|
||||
log.warn(`DEPRECATED: Function '${fnName}' on 'TimeRanges' called without an index argument.`);
|
||||
rangeIndex = 0;
|
||||
}
|
||||
rangeCheck(fnName, rangeIndex, ranges.length - 1);
|
||||
return ranges[rangeIndex][valueIndex];
|
||||
}
|
||||
|
||||
function createTimeRangesObj(ranges) {
|
||||
if (ranges === undefined || ranges.length === 0) {
|
||||
return {
|
||||
length: 0,
|
||||
start() {
|
||||
throw new Error('This TimeRanges object is empty');
|
||||
},
|
||||
end() {
|
||||
throw new Error('This TimeRanges object is empty');
|
||||
}
|
||||
};
|
||||
}
|
||||
return {
|
||||
length: ranges.length,
|
||||
start: getRange.bind(null, 'start', 0, ranges),
|
||||
end: getRange.bind(null, 'end', 1, ranges)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @file time-ranges.js
|
||||
*
|
||||
@ -13,7 +47,7 @@ import log from './log.js';
|
||||
* @private
|
||||
* @method createTimeRanges
|
||||
*/
|
||||
export function createTimeRanges(start, end){
|
||||
export function createTimeRanges(start, end) {
|
||||
if (Array.isArray(start)) {
|
||||
return createTimeRangesObj(start);
|
||||
} else if (start === undefined || end === undefined) {
|
||||
@ -23,37 +57,3 @@ export function createTimeRanges(start, end){
|
||||
}
|
||||
|
||||
export { createTimeRanges as createTimeRange };
|
||||
|
||||
function createTimeRangesObj(ranges){
|
||||
if (ranges === undefined || ranges.length === 0) {
|
||||
return {
|
||||
length: 0,
|
||||
start: function() {
|
||||
throw new Error('This TimeRanges object is empty');
|
||||
},
|
||||
end: function() {
|
||||
throw new Error('This TimeRanges object is empty');
|
||||
}
|
||||
};
|
||||
}
|
||||
return {
|
||||
length: ranges.length,
|
||||
start: getRange.bind(null, 'start', 0, ranges),
|
||||
end: getRange.bind(null, 'end', 1, ranges)
|
||||
};
|
||||
}
|
||||
|
||||
function getRange(fnName, valueIndex, ranges, rangeIndex){
|
||||
if (rangeIndex === undefined) {
|
||||
log.warn(`DEPRECATED: Function '${fnName}' on 'TimeRanges' called without an index argument.`);
|
||||
rangeIndex = 0;
|
||||
}
|
||||
rangeCheck(fnName, rangeIndex, ranges.length - 1);
|
||||
return ranges[rangeIndex][valueIndex];
|
||||
}
|
||||
|
||||
function rangeCheck(fnName, index, maxIndex){
|
||||
if (index < 0 || index > maxIndex) {
|
||||
throw new Error(`Failed to execute '${fnName}' on 'TimeRanges': The index provided (${index}) is greater than or equal to the maximum bound (${maxIndex}).`);
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@
|
||||
* @private
|
||||
* @method toTitleCase
|
||||
*/
|
||||
function toTitleCase(string){
|
||||
function toTitleCase(string) {
|
||||
return string.charAt(0).toUpperCase() + string.slice(1);
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,7 @@ export const parseUrl = function(url) {
|
||||
|
||||
// add the url to an anchor and let the browser parse the URL
|
||||
let a = document.createElement('a');
|
||||
|
||||
a.href = url;
|
||||
|
||||
// IE8 (and 9?) Fix
|
||||
@ -23,6 +24,7 @@ export const parseUrl = function(url) {
|
||||
// added to the body, and an innerHTML is needed to trigger the parsing
|
||||
let addToBody = (a.host === '' && a.protocol !== 'file:');
|
||||
let div;
|
||||
|
||||
if (addToBody) {
|
||||
div = document.createElement('div');
|
||||
div.innerHTML = `<a href="${url}"></a>`;
|
||||
@ -36,7 +38,8 @@ export const parseUrl = function(url) {
|
||||
// This is also needed for IE8 because the anchor loses its
|
||||
// properties when it's removed from the dom
|
||||
let details = {};
|
||||
for (var i = 0; i < props.length; i++) {
|
||||
|
||||
for (let i = 0; i < props.length; i++) {
|
||||
details[props[i]] = a[props[i]];
|
||||
}
|
||||
|
||||
@ -45,6 +48,7 @@ export const parseUrl = function(url) {
|
||||
if (details.protocol === 'http:') {
|
||||
details.host = details.host.replace(/:80$/, '');
|
||||
}
|
||||
|
||||
if (details.protocol === 'https:') {
|
||||
details.host = details.host.replace(/:443$/, '');
|
||||
}
|
||||
@ -65,11 +69,12 @@ export const parseUrl = function(url) {
|
||||
* @private
|
||||
* @method getAbsoluteURL
|
||||
*/
|
||||
export const getAbsoluteURL = function(url){
|
||||
export const getAbsoluteURL = function(url) {
|
||||
// Check if absolute URL
|
||||
if (!url.match(/^https?:\/\//)) {
|
||||
// Convert to absolute URL. Flash hosted off-site needs an absolute URL.
|
||||
let div = document.createElement('div');
|
||||
|
||||
div.innerHTML = `<a href="${url}">x</a>`;
|
||||
url = div.firstChild.href;
|
||||
}
|
||||
@ -85,7 +90,7 @@ export const getAbsoluteURL = function(url){
|
||||
* @method getFileExtension
|
||||
*/
|
||||
export const getFileExtension = function(path) {
|
||||
if(typeof path === 'string'){
|
||||
if (typeof path === 'string') {
|
||||
let splitPathRe = /^(\/?)([\s\S]*?)((?:\.{1,2}|[^\/]+?)(\.([^\.\/\?]+)))(?:[\/]*|[\?].*)$/i;
|
||||
let pathParts = splitPathRe.exec(path);
|
||||
|
||||
|
@ -1,6 +1,9 @@
|
||||
/**
|
||||
* @file video.js
|
||||
*/
|
||||
|
||||
/* global define */
|
||||
|
||||
import window from 'global/window';
|
||||
import document from 'global/document';
|
||||
import * as setup from './setup';
|
||||
@ -28,8 +31,6 @@ import xhr from 'xhr';
|
||||
|
||||
// Include the built-in techs
|
||||
import Tech from './tech/tech.js';
|
||||
import Html5 from './tech/html5.js';
|
||||
import Flash from './tech/flash.js';
|
||||
|
||||
// HTML5 Element Shim for IE8
|
||||
if (typeof HTMLVideoElement === 'undefined') {
|
||||
@ -53,8 +54,8 @@ if (typeof HTMLVideoElement === 'undefined') {
|
||||
* @mixes videojs
|
||||
* @method videojs
|
||||
*/
|
||||
function videojs(id, options, ready){
|
||||
let tag; // Element of ID
|
||||
function videojs(id, options, ready) {
|
||||
let tag;
|
||||
|
||||
// Allow for element or ID to be passed in
|
||||
// String ID
|
||||
@ -78,11 +79,10 @@ function videojs(id, options, ready){
|
||||
}
|
||||
|
||||
return videojs.getPlayers()[id];
|
||||
}
|
||||
|
||||
// Otherwise get element for ID
|
||||
} else {
|
||||
tag = Dom.getEl(id);
|
||||
}
|
||||
tag = Dom.getEl(id);
|
||||
|
||||
// ID is a media element
|
||||
} else {
|
||||
@ -90,13 +90,14 @@ function videojs(id, options, ready){
|
||||
}
|
||||
|
||||
// Check for a useable element
|
||||
if (!tag || !tag.nodeName) { // re: nodeName, could be a box div also
|
||||
throw new TypeError('The element or ID supplied is not valid. (videojs)'); // Returns
|
||||
// re: nodeName, could be a box div also
|
||||
if (!tag || !tag.nodeName) {
|
||||
throw new TypeError('The element or ID supplied is not valid. (videojs)');
|
||||
}
|
||||
|
||||
// Element may have a player attr referring to an already created player instance.
|
||||
// If not, set up a new player and return the instance.
|
||||
return tag['player'] || Player.players[tag.playerId] || new Player(tag, options, ready);
|
||||
return tag.player || Player.players[tag.playerId] || new Player(tag, options, ready);
|
||||
}
|
||||
|
||||
// Add default styles
|
||||
@ -106,6 +107,7 @@ if (window.VIDEOJS_NO_DYNAMIC_STYLE !== true) {
|
||||
if (!style) {
|
||||
style = stylesheet.createStyleElement('vjs-styles-defaults');
|
||||
let head = Dom.$('head');
|
||||
|
||||
head.insertBefore(style, head.firstChild);
|
||||
stylesheet.setTextContent(style, `
|
||||
.video-js {
|
||||
@ -121,7 +123,8 @@ if (window.VIDEOJS_NO_DYNAMIC_STYLE !== true) {
|
||||
}
|
||||
|
||||
// Run Auto-load players
|
||||
// You have to wait at least once in case this script is loaded after your video in the DOM (weird behavior only with minified version)
|
||||
// You have to wait at least once in case this script is loaded after your
|
||||
// video in the DOM (weird behavior only with minified version)
|
||||
setup.autoSetupTimeout(1, videojs);
|
||||
|
||||
/*
|
||||
@ -269,12 +272,12 @@ videojs.TOUCH_ENABLED = browser.TOUCH_ENABLED;
|
||||
* Mimics ES6 subclassing with the `extend` keyword
|
||||
* ```js
|
||||
* // Create a basic javascript 'class'
|
||||
* function MyClass(name){
|
||||
* function MyClass(name) {
|
||||
* // Set a property at initialization
|
||||
* this.myName = name;
|
||||
* }
|
||||
* // Create an instance method
|
||||
* MyClass.prototype.sayMyName = function(){
|
||||
* MyClass.prototype.sayMyName = function() {
|
||||
* alert(this.myName);
|
||||
* };
|
||||
* // Subclass the exisitng class and change the name
|
||||
@ -337,12 +340,12 @@ videojs.mergeOptions = mergeOptions;
|
||||
/**
|
||||
* Change the context (this) of a function
|
||||
*
|
||||
* videojs.bind(newContext, function(){
|
||||
* videojs.bind(newContext, function() {
|
||||
* this === newContext
|
||||
* });
|
||||
*
|
||||
* NOTE: as of v5.0 we require an ES5 shim, so you should use the native
|
||||
* `function(){}.bind(newContext);` instead of this.
|
||||
* `function() {}.bind(newContext);` instead of this.
|
||||
*
|
||||
* @param {*} context The object to bind as scope
|
||||
* @param {Function} fn The function to be bound to a scope
|
||||
@ -365,7 +368,7 @@ videojs.bind = Fn.bind;
|
||||
* var player = this;
|
||||
* var alertText = myPluginOptions.text || 'Player is playing!'
|
||||
*
|
||||
* player.on('play', function(){
|
||||
* player.on('play', function() {
|
||||
* alert(alertText);
|
||||
* });
|
||||
* });
|
||||
@ -410,7 +413,7 @@ videojs.plugin = plugin;
|
||||
* @mixes videojs
|
||||
* @method addLanguage
|
||||
*/
|
||||
videojs.addLanguage = function(code, data){
|
||||
videojs.addLanguage = function(code, data) {
|
||||
code = ('' + code).toLowerCase();
|
||||
return merge(videojs.options.languages, { [code]: data })[code];
|
||||
};
|
||||
@ -721,12 +724,12 @@ videojs.insertContent = Dom.insertContent;
|
||||
* still support requirejs and browserify. This also needs to be closure
|
||||
* compiler compatible, so string keys are used.
|
||||
*/
|
||||
if (typeof define === 'function' && define['amd']) {
|
||||
define('videojs', [], function(){ return videojs; });
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
define('videojs', [], () => videojs);
|
||||
|
||||
// checking that module is an object too because of umdjs/umd#35
|
||||
} else if (typeof exports === 'object' && typeof module === 'object') {
|
||||
module['exports'] = videojs;
|
||||
module.exports = videojs;
|
||||
}
|
||||
|
||||
export default videojs;
|
||||
|
Reference in New Issue
Block a user