From d19b8117f8dbcccf58c74eec88c6ea9b3f51ad32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Tam=C3=A1si?= Date: Wed, 27 Jul 2016 16:04:20 +0200 Subject: [PATCH 01/11] Chapters refactoring and fixes --- src/css/components/menu/_menu.scss | 4 +- .../text-track-controls/chapters-button.js | 138 ++++++++---------- .../chapters-track-menu-item.js | 1 + 3 files changed, 64 insertions(+), 79 deletions(-) diff --git a/src/css/components/menu/_menu.scss b/src/css/components/menu/_menu.scss index 543fa00de0..4d75864921 100644 --- a/src/css/components/menu/_menu.scss +++ b/src/css/components/menu/_menu.scss @@ -34,8 +34,8 @@ text-transform: lowercase; } -.vjs-menu li:focus, -.vjs-menu li:hover { +.vjs-menu li.vjs-menu-item:focus, +.vjs-menu li.vjs-menu-item:hover { outline: 0; @include background-color-with-alpha($secondary-background-color, $secondary-background-transparency); } diff --git a/src/js/control-bar/text-track-controls/chapters-button.js b/src/js/control-bar/text-track-controls/chapters-button.js index 0cc8606ea8..751bf80de8 100644 --- a/src/js/control-bar/text-track-controls/chapters-button.js +++ b/src/js/control-bar/text-track-controls/chapters-button.js @@ -24,8 +24,9 @@ import window from 'global/window'; */ class ChaptersButton extends TextTrackButton { - constructor(player, options, ready){ + constructor(player, options, ready) { super(player, options, ready); + this.updateHandler_ = Fn.bind(this, this.update); this.el_.setAttribute('aria-label','Chapters Menu'); } @@ -39,107 +40,90 @@ class ChaptersButton extends TextTrackButton { return `vjs-chapters-button ${super.buildCSSClass()}`; } - /** - * Create a menu item for each text track - * - * @return {Array} Array of menu items - * @method createItems - */ - createItems() { - let items = []; + update(event) { + if (this.track_ === undefined || (event && (event.type === 'addtrack' || event.type === 'removetrack'))) { + this.setTrack(this.findChaptersTrack()); + } + super.update(); + } + + setTrack(track) { + if (this.track_ === track) return; + + if (this.track_) { + let remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(this.track_); + if (remoteTextTrackEl) { + remoteTextTrackEl.removeEventListener('load', this.updateHandler_); + } + this.track_ = null; + } + + this.track_ = track; - let tracks = this.player_.textTracks(); + if (this.track_) { + this.track_['mode'] = 'hidden'; - if (!tracks) { - return items; + let remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(this.track_); + if (remoteTextTrackEl) { + remoteTextTrackEl.addEventListener('load', this.updateHandler_); + } } + } - for (let i = 0; i < tracks.length; i++) { + findChaptersTrack() { + let tracks = this.player_.textTracks() || []; + + for (let i = tracks.length - 1; i >= 0; i--) { + // We will always choose the last track as our chaptersTrack let track = tracks[i]; + if (track['kind'] === this.kind_) { - items.push(new TextTrackMenuItem(this.player_, { - 'track': track - })); + return track; } } + } - return items; + getMenuCaption() { + if (this.track_ && this.track_.label) + return this.track_.label; + return this.localize(toTitleCase(this.kind_)); } /** - * Create menu from chapter buttons + * Create menu from chapter track * * @return {Menu} Menu of chapter buttons * @method createMenu */ createMenu() { - let tracks = this.player_.textTracks() || []; - let chaptersTrack; - let items = this.items || []; - - for (let i = tracks.length - 1; i >= 0; i--) { + this.options_.title = this.getMenuCaption(); + return super.createMenu(); + } - // We will always choose the last track as our chaptersTrack - let track = tracks[i]; + /** + * Create a menu item for each chapter cue + * + * @return {Array} Array of menu items + * @method createItems + */ + createItems() { + let items = []; + if (!this.track_) return items; - if (track['kind'] === this.kind_) { - chaptersTrack = track; + let cues = this.track_['cues'], cue; - break; - } - } + for (let i = 0, l = cues.length; i < l; i++) { + cue = cues[i]; - let menu = this.menu; - if (menu === undefined) { - menu = new Menu(this.player_); - let title = Dom.createEl('li', { - className: 'vjs-menu-title', - innerHTML: toTitleCase(this.kind_), - tabIndex: -1 + let mi = new ChaptersTrackMenuItem(this.player_, { + 'track': this.track_, + 'cue': cue }); - 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 = []; - } - if (chaptersTrack && chaptersTrack.cues == null) { - chaptersTrack['mode'] = 'hidden'; - - let remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(chaptersTrack); - - if (remoteTextTrackEl) { - remoteTextTrackEl.addEventListener('load', (event) => this.update()); - } + items.push(mi); } - if (chaptersTrack && chaptersTrack.cues && chaptersTrack.cues.length > 0) { - let cues = chaptersTrack['cues'], cue; - - for (let i = 0, l = cues.length; i < l; i++) { - cue = cues[i]; - - let mi = new ChaptersTrackMenuItem(this.player_, { - 'track': chaptersTrack, - 'cue': cue - }); - - items.push(mi); - - menu.addChild(mi); - } - } - - if (items.length > 0) { - this.show(); - } - // Assigning the value of items back to this.items for next iteration - this.items = items; - return menu; + return items; } } diff --git a/src/js/control-bar/text-track-controls/chapters-track-menu-item.js b/src/js/control-bar/text-track-controls/chapters-track-menu-item.js index 40b717a0e4..718a81d509 100644 --- a/src/js/control-bar/text-track-controls/chapters-track-menu-item.js +++ b/src/js/control-bar/text-track-controls/chapters-track-menu-item.js @@ -21,6 +21,7 @@ class ChaptersTrackMenuItem extends MenuItem { let currentTime = player.currentTime(); // Modify options for parent MenuItem class's init. + options['selectable'] = true; options['label'] = cue.text; options['selected'] = (cue['startTime'] <= currentTime && currentTime < cue['endTime']); super(player, options); From bf016425a3e89101b2ca538320ee34a986663f4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Tam=C3=A1si?= Date: Fri, 29 Jul 2016 12:39:10 +0200 Subject: [PATCH 02/11] Added some unit tests for chapters functionality --- test/unit/test-helpers.js | 26 ++++ test/unit/tracks/text-track-controls.test.js | 149 +++++++++++++++++++ 2 files changed, 175 insertions(+) diff --git a/test/unit/test-helpers.js b/test/unit/test-helpers.js index 5d3c2d5696..0b13d0b4ad 100644 --- a/test/unit/test-helpers.js +++ b/test/unit/test-helpers.js @@ -125,6 +125,32 @@ var TestHelpers = { props.length; return run; + }, + + /** + * Triggers an event on a DOM node natively. + * + * @param {Element} element + * @param {string} eventType + */ + triggerDomEvent: function (element, eventType) { + var event; + + if (document.createEvent) { + event = document.createEvent('HTMLEvents'); + event.initEvent(eventType, true, true); + } else { + event = document.createEventObject(); + event.eventType = eventType; + } + + event.eventName = eventType; + + if (document.createEvent) { + element.dispatchEvent(event); + } else { + element.fireEvent('on' + event.eventType, event); + } } }; diff --git a/test/unit/tracks/text-track-controls.test.js b/test/unit/tracks/text-track-controls.test.js index 27aa442521..404a3237ed 100644 --- a/test/unit/tracks/text-track-controls.test.js +++ b/test/unit/tracks/text-track-controls.test.js @@ -262,3 +262,152 @@ if (!browser.IS_IE8) { player.dispose(); }); } + + +let chaptersTrack = { + kind: 'chapters', + label: 'Test Chapters' +}; + +test('chapters should not be displayed when text tracks list is empty', function() { + let player = TestHelpers.makePlayer(); + + ok(player.controlBar.chaptersButton.hasClass('vjs-hidden'), 'control is not displayed'); + equal(player.textTracks().length, 0, 'textTracks is empty'); + + player.dispose(); +}); + +test('chapters should not be displayed when there is chapters track but no cues', function() { + let player = TestHelpers.makePlayer({ + tracks: [chaptersTrack] + }); + + this.clock.tick(1000); + + ok(player.controlBar.chaptersButton.hasClass('vjs-hidden'), 'chapters menu is not displayed'); + equal(player.textTracks().length, 1, 'textTracks contains one item'); + + player.dispose(); +}); + +test('chapters should be displayed when cues added to initial track and button updated', function() { + let player = TestHelpers.makePlayer({ + tracks: [chaptersTrack] + }); + + this.clock.tick(1000); + + var chapters = player.textTracks()[0]; + chapters.addCue({ + startTime: 0, + endTime: 2, + text: 'Chapter 1' + }); + chapters.addCue({ + startTime: 2, + endTime: 4, + text: 'Chapter 2' + }); + equal(chapters.cues.length, 2); + + player.controlBar.chaptersButton.update(); + + ok(!player.controlBar.chaptersButton.hasClass('vjs-hidden'), 'chapters menu is displayed'); + + var menuItems = player.controlBar.chaptersButton.items; + + equal(menuItems.length, 2, 'menu contains two item'); + + player.dispose(); +}); + +test('chapters should be displayed when a track with added and button updated', function() { + let player = TestHelpers.makePlayer(); + + this.clock.tick(1000); + + var chapters = player.addTextTrack('chapters', 'Test Chapters', 'en'); + chapters.addCue({ + startTime: 0, + endTime: 2, + text: 'Chapter 1' + }); + chapters.addCue({ + startTime: 2, + endTime: 4, + text: 'Chapter 2' + }); + equal(chapters.cues.length, 2); + + player.controlBar.chaptersButton.update(); + + ok(!player.controlBar.chaptersButton.hasClass('vjs-hidden'), 'chapters menu is displayed'); + + var menuItems = player.controlBar.chaptersButton.items; + + equal(menuItems.length, 2, 'menu contains two item'); + + player.dispose(); +}); + +test('chapters menu should use track label as menu title', function() { + let player = TestHelpers.makePlayer({ + tracks: [chaptersTrack] + }); + + this.clock.tick(1000); + + var chapters = player.textTracks()[0]; + chapters.addCue({ + startTime: 0, + endTime: 2, + text: 'Chapter 1' + }); + chapters.addCue({ + startTime: 2, + endTime: 4, + text: 'Chapter 2' + }); + equal(chapters.cues.length, 2); + + player.controlBar.chaptersButton.update(); + + var menu = player.controlBar.chaptersButton.menu; + var menuTitle = menu.contentEl().firstChild.textContent; + + equal(menuTitle, 'Test Chapters', 'menu gets track label as title'); + + player.dispose(); +}); + +test('chapters should be displayed when remote track added and load event fired', function() { + let player = TestHelpers.makePlayer(); + + this.clock.tick(1000); + + var chaptersEl = player.addRemoteTextTrack(chaptersTrack); + + chaptersEl.track.addCue({ + startTime: 0, + endTime: 2, + text: 'Chapter 1' + }); + chaptersEl.track.addCue({ + startTime: 2, + endTime: 4, + text: 'Chapter 2' + }); + + equal(chaptersEl.track.cues.length, 2); + + TestHelpers.triggerDomEvent(chaptersEl, 'load'); + + ok(!player.controlBar.chaptersButton.hasClass('vjs-hidden'), 'chapters menu is displayed'); + + var menuItems = player.controlBar.chaptersButton.items; + + equal(menuItems.length, 2, 'menu contains two item'); + + player.dispose(); +}); \ No newline at end of file From cfa817f0bf5055b97488bb0e5121c23ea3f5d805 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Tam=C3=A1si?= Date: Fri, 29 Jul 2016 12:45:55 +0200 Subject: [PATCH 03/11] Fixing a typo --- test/unit/tracks/text-track-controls.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/tracks/text-track-controls.test.js b/test/unit/tracks/text-track-controls.test.js index 404a3237ed..3eac0145df 100644 --- a/test/unit/tracks/text-track-controls.test.js +++ b/test/unit/tracks/text-track-controls.test.js @@ -322,7 +322,7 @@ test('chapters should be displayed when cues added to initial track and button u player.dispose(); }); -test('chapters should be displayed when a track with added and button updated', function() { +test('chapters should be displayed when a track and its cures added and button updated', function() { let player = TestHelpers.makePlayer(); this.clock.tick(1000); From 1f8d3d85937870c0f187418c3d306ca26f30a6a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Tam=C3=A1si?= Date: Wed, 17 Aug 2016 09:13:48 +0200 Subject: [PATCH 04/11] Minor refactoring --- src/js/control-bar/text-track-controls/chapters-button.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/js/control-bar/text-track-controls/chapters-button.js b/src/js/control-bar/text-track-controls/chapters-button.js index 751bf80de8..bc42ab0537 100644 --- a/src/js/control-bar/text-track-controls/chapters-button.js +++ b/src/js/control-bar/text-track-controls/chapters-button.js @@ -110,10 +110,11 @@ class ChaptersButton extends TextTrackButton { let items = []; if (!this.track_) return items; - let cues = this.track_['cues'], cue; + let cues = this.track_['cues']; + if (!cues) return items; for (let i = 0, l = cues.length; i < l; i++) { - cue = cues[i]; + let cue = cues[i]; let mi = new ChaptersTrackMenuItem(this.player_, { 'track': this.track_, From d608354599e1bf63a312176ac669c9ea6b89bc45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Tam=C3=A1si?= Date: Wed, 17 Aug 2016 22:34:28 +0200 Subject: [PATCH 05/11] Fix issue with load eventhandler initialization --- src/js/control-bar/text-track-controls/chapters-button.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/js/control-bar/text-track-controls/chapters-button.js b/src/js/control-bar/text-track-controls/chapters-button.js index bc42ab0537..9f92ef3b0a 100644 --- a/src/js/control-bar/text-track-controls/chapters-button.js +++ b/src/js/control-bar/text-track-controls/chapters-button.js @@ -26,7 +26,6 @@ class ChaptersButton extends TextTrackButton { constructor(player, options, ready) { super(player, options, ready); - this.updateHandler_ = Fn.bind(this, this.update); this.el_.setAttribute('aria-label','Chapters Menu'); } @@ -50,6 +49,9 @@ class ChaptersButton extends TextTrackButton { setTrack(track) { if (this.track_ === track) return; + if (!this.updateHandler_) + this.updateHandler_ = Fn.bind(this, this.update); + if (this.track_) { let remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(this.track_); if (remoteTextTrackEl) { From 24db30726509465d161c920aaac793b378724188 Mon Sep 17 00:00:00 2001 From: cervengoc Date: Thu, 18 Aug 2016 00:00:02 +0200 Subject: [PATCH 06/11] Code style cleanup --- .../text-track-controls/chapters-button.js | 42 ++++++++++--------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/src/js/control-bar/text-track-controls/chapters-button.js b/src/js/control-bar/text-track-controls/chapters-button.js index bea49ed63c..d3c60eebb2 100644 --- a/src/js/control-bar/text-track-controls/chapters-button.js +++ b/src/js/control-bar/text-track-controls/chapters-button.js @@ -3,10 +3,7 @@ */ import TextTrackButton from './text-track-button.js'; import Component from '../../component.js'; -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 toTitleCase from '../../utils/to-title-case.js'; /** @@ -47,23 +44,27 @@ class ChaptersButton extends TextTrackButton { setTrack(track) { if (this.track_ === track) return; - if (!this.updateHandler_) + if (!this.updateHandler_) { this.updateHandler_ = Fn.bind(this, this.update); - + } + if (this.track_) { - let remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(this.track_); + const remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(this.track_); + if (remoteTextTrackEl) { remoteTextTrackEl.removeEventListener('load', this.updateHandler_); } + this.track_ = null; } this.track_ = track; if (this.track_) { - this.track_['mode'] = 'hidden'; + this.track_.mode = 'hidden'; + + const remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(this.track_); - let remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(this.track_); if (remoteTextTrackEl) { remoteTextTrackEl.addEventListener('load', this.updateHandler_); } @@ -71,21 +72,22 @@ class ChaptersButton extends TextTrackButton { } findChaptersTrack() { - let tracks = this.player_.textTracks() || []; - + const tracks = this.player_.textTracks() || []; + 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_) { return track; } } } getMenuCaption() { - if (this.track_ && this.track_.label) + if (this.track_ && this.track_.label) { return this.track_.label; + } return this.localize(toTitleCase(this.kind_)); } @@ -107,18 +109,18 @@ class ChaptersButton extends TextTrackButton { * @method createItems */ createItems() { - let items = []; + const items = []; if (!this.track_) return items; - let cues = this.track_['cues']; + const cues = this.track_.cues; if (!cues) return items; for (let i = 0, l = cues.length; i < l; i++) { - let cue = cues[i]; + const cue = cues[i]; - let mi = new ChaptersTrackMenuItem(this.player_, { - 'track': this.track_, - 'cue': cue + const mi = new ChaptersTrackMenuItem(this.player_, { + track: this.track_, + cue: cue }); items.push(mi); @@ -132,4 +134,4 @@ ChaptersButton.prototype.kind_ = 'chapters'; ChaptersButton.prototype.controlText_ = 'Chapters'; Component.registerComponent('ChaptersButton', ChaptersButton); -export default ChaptersButton; +export default ChaptersButton; \ No newline at end of file From 68741d220a03281729620458f410c68d90e59b7c Mon Sep 17 00:00:00 2001 From: cervengoc Date: Thu, 18 Aug 2016 00:08:02 +0200 Subject: [PATCH 07/11] Code style cleanup 2 --- .../text-track-controls/chapters-button.js | 26 ++++++---- test/unit/tracks/text-track-controls.test.js | 52 ++++++++++--------- 2 files changed, 42 insertions(+), 36 deletions(-) diff --git a/src/js/control-bar/text-track-controls/chapters-button.js b/src/js/control-bar/text-track-controls/chapters-button.js index d3c60eebb2..25d71e0ecf 100644 --- a/src/js/control-bar/text-track-controls/chapters-button.js +++ b/src/js/control-bar/text-track-controls/chapters-button.js @@ -40,12 +40,14 @@ class ChaptersButton extends TextTrackButton { } super.update(); } - + setTrack(track) { - if (this.track_ === track) return; + if (this.track_ === track) { + return; + } if (!this.updateHandler_) { - this.updateHandler_ = Fn.bind(this, this.update); + this.updateHandler_ = this.update.bind(this); } if (this.track_) { @@ -110,18 +112,20 @@ class ChaptersButton extends TextTrackButton { */ createItems() { const items = []; - if (!this.track_) return items; + + if (!this.track_) { + return items; + } const cues = this.track_.cues; - if (!cues) return items; + + if (!cues) { + return items; + } for (let i = 0, l = cues.length; i < l; i++) { const cue = cues[i]; - - const mi = new ChaptersTrackMenuItem(this.player_, { - track: this.track_, - cue: cue - }); + const mi = new ChaptersTrackMenuItem(this.player_, { track: this.track_, cue }); items.push(mi); } @@ -134,4 +138,4 @@ ChaptersButton.prototype.kind_ = 'chapters'; ChaptersButton.prototype.controlText_ = 'Chapters'; Component.registerComponent('ChaptersButton', ChaptersButton); -export default ChaptersButton; \ No newline at end of file +export default ChaptersButton; diff --git a/test/unit/tracks/text-track-controls.test.js b/test/unit/tracks/text-track-controls.test.js index 7147173a2d..67ec8859f2 100644 --- a/test/unit/tracks/text-track-controls.test.js +++ b/test/unit/tracks/text-track-controls.test.js @@ -295,7 +295,6 @@ if (!browser.IS_IE8) { }); } - let chaptersTrack = { kind: 'chapters', label: 'Test Chapters' @@ -329,8 +328,9 @@ test('chapters should be displayed when cues added to initial track and button u }); this.clock.tick(1000); - - var chapters = player.textTracks()[0]; + + const chapters = player.textTracks()[0]; + chapters.addCue({ startTime: 0, endTime: 2, @@ -344,13 +344,13 @@ test('chapters should be displayed when cues added to initial track and button u equal(chapters.cues.length, 2); player.controlBar.chaptersButton.update(); - + ok(!player.controlBar.chaptersButton.hasClass('vjs-hidden'), 'chapters menu is displayed'); - var menuItems = player.controlBar.chaptersButton.items; + const menuItems = player.controlBar.chaptersButton.items; equal(menuItems.length, 2, 'menu contains two item'); - + player.dispose(); }); @@ -358,8 +358,9 @@ test('chapters should be displayed when a track and its cures added and button u let player = TestHelpers.makePlayer(); this.clock.tick(1000); - - var chapters = player.addTextTrack('chapters', 'Test Chapters', 'en'); + + const chapters = player.addTextTrack('chapters', 'Test Chapters', 'en'); + chapters.addCue({ startTime: 0, endTime: 2, @@ -373,13 +374,13 @@ test('chapters should be displayed when a track and its cures added and button u equal(chapters.cues.length, 2); player.controlBar.chaptersButton.update(); - + ok(!player.controlBar.chaptersButton.hasClass('vjs-hidden'), 'chapters menu is displayed'); - var menuItems = player.controlBar.chaptersButton.items; + const menuItems = player.controlBar.chaptersButton.items; equal(menuItems.length, 2, 'menu contains two item'); - + player.dispose(); }); @@ -387,10 +388,11 @@ test('chapters menu should use track label as menu title', function() { let player = TestHelpers.makePlayer({ tracks: [chaptersTrack] }); - + this.clock.tick(1000); - - var chapters = player.textTracks()[0]; + + const chapters = player.textTracks()[0]; + chapters.addCue({ startTime: 0, endTime: 2, @@ -404,12 +406,12 @@ test('chapters menu should use track label as menu title', function() { equal(chapters.cues.length, 2); player.controlBar.chaptersButton.update(); - - var menu = player.controlBar.chaptersButton.menu; - var menuTitle = menu.contentEl().firstChild.textContent; + + const menu = player.controlBar.chaptersButton.menu; + const menuTitle = menu.contentEl().firstChild.textContent; equal(menuTitle, 'Test Chapters', 'menu gets track label as title'); - + player.dispose(); }); @@ -417,9 +419,9 @@ test('chapters should be displayed when remote track added and load event fired' let player = TestHelpers.makePlayer(); this.clock.tick(1000); - - var chaptersEl = player.addRemoteTextTrack(chaptersTrack); - + + const chaptersEl = player.addRemoteTextTrack(chaptersTrack); + chaptersEl.track.addCue({ startTime: 0, endTime: 2, @@ -434,12 +436,12 @@ test('chapters should be displayed when remote track added and load event fired' equal(chaptersEl.track.cues.length, 2); TestHelpers.triggerDomEvent(chaptersEl, 'load'); - + ok(!player.controlBar.chaptersButton.hasClass('vjs-hidden'), 'chapters menu is displayed'); - var menuItems = player.controlBar.chaptersButton.items; + const menuItems = player.controlBar.chaptersButton.items; equal(menuItems.length, 2, 'menu contains two item'); - + player.dispose(); -}); \ No newline at end of file +}); From 891eea0b2b43bf65bffd1839a9c862b41f49e19d Mon Sep 17 00:00:00 2001 From: cervengoc Date: Thu, 18 Aug 2016 00:09:19 +0200 Subject: [PATCH 08/11] Code style cleanup 3 --- test/unit/test-helpers.js | 4 ++-- test/unit/tracks/text-track-controls.test.js | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/test/unit/test-helpers.js b/test/unit/test-helpers.js index d4f2107ad7..65429c7e8e 100644 --- a/test/unit/test-helpers.js +++ b/test/unit/test-helpers.js @@ -138,8 +138,8 @@ const TestHelpers = { * @param {Element} element * @param {string} eventType */ - triggerDomEvent: function (element, eventType) { - var event; + triggerDomEvent(element, eventType) { + let event; if (document.createEvent) { event = document.createEvent('HTMLEvents'); diff --git a/test/unit/tracks/text-track-controls.test.js b/test/unit/tracks/text-track-controls.test.js index 67ec8859f2..526694ae9e 100644 --- a/test/unit/tracks/text-track-controls.test.js +++ b/test/unit/tracks/text-track-controls.test.js @@ -295,13 +295,13 @@ if (!browser.IS_IE8) { }); } -let chaptersTrack = { +const chaptersTrack = { kind: 'chapters', label: 'Test Chapters' }; test('chapters should not be displayed when text tracks list is empty', function() { - let player = TestHelpers.makePlayer(); + const player = TestHelpers.makePlayer(); ok(player.controlBar.chaptersButton.hasClass('vjs-hidden'), 'control is not displayed'); equal(player.textTracks().length, 0, 'textTracks is empty'); @@ -310,7 +310,7 @@ test('chapters should not be displayed when text tracks list is empty', function }); test('chapters should not be displayed when there is chapters track but no cues', function() { - let player = TestHelpers.makePlayer({ + const player = TestHelpers.makePlayer({ tracks: [chaptersTrack] }); @@ -323,7 +323,7 @@ test('chapters should not be displayed when there is chapters track but no cues' }); test('chapters should be displayed when cues added to initial track and button updated', function() { - let player = TestHelpers.makePlayer({ + const player = TestHelpers.makePlayer({ tracks: [chaptersTrack] }); @@ -355,7 +355,7 @@ test('chapters should be displayed when cues added to initial track and button u }); test('chapters should be displayed when a track and its cures added and button updated', function() { - let player = TestHelpers.makePlayer(); + const player = TestHelpers.makePlayer(); this.clock.tick(1000); @@ -385,7 +385,7 @@ test('chapters should be displayed when a track and its cures added and button u }); test('chapters menu should use track label as menu title', function() { - let player = TestHelpers.makePlayer({ + const player = TestHelpers.makePlayer({ tracks: [chaptersTrack] }); @@ -416,7 +416,7 @@ test('chapters menu should use track label as menu title', function() { }); test('chapters should be displayed when remote track added and load event fired', function() { - let player = TestHelpers.makePlayer(); + const player = TestHelpers.makePlayer(); this.clock.tick(1000); From cc08ff7cb30db44af9e05429fc17e36a2961c133 Mon Sep 17 00:00:00 2001 From: Zoltan Date: Tue, 4 Oct 2016 10:15:07 +0200 Subject: [PATCH 09/11] Merging videojs/master --- .babelrc | 2 +- .../ISSUE_TEMPLATE.md | 0 .../PULL_REQUEST_TEMPLATE.md | 0 CHANGELOG.md | 35 +- README.md | 4 +- build/grunt.js | 51 +- build/tasks/languages.js | 35 + component.json | 2 +- docs/guides/languages.md | 4 + docs/guides/setup.md | 2 +- docs/index.md | 4 +- docs/translations-needed.md | 357 ++++++++ lang/de.json | 8 +- lang/el.json | 8 +- lang/en.json | 2 + lang/fr.json | 20 +- package.json | 28 +- src/css/_utilities.scss | 2 +- src/css/components/_fullscreen.scss | 2 - src/css/components/_play-pause.scss | 2 - src/css/components/_progress.scss | 2 +- src/css/components/menu/_menu.scss | 3 +- src/css/ie8.css | 31 +- .../audio-track-button.js | 2 +- .../audio-track-menu-item.js | 4 +- .../progress-control/load-progress-bar.js | 5 +- src/js/media-error.js | 41 +- src/js/player.js | 569 +++++------- src/js/tech/html5.js | 812 ++++++++---------- src/js/tech/tech.js | 6 +- src/js/utils/dom.js | 7 +- src/js/utils/events.js | 7 +- src/js/video.js | 4 +- test/index.html | 2 + test/karma.conf.js | 10 +- test/require/browserify.js | 8 + test/require/node.js | 9 + test/require/webpack.js | 8 + test/unit/button.test.js | 2 + test/unit/controls.test.js | 2 + test/unit/events.test.js | 18 + test/unit/media-error.test.js | 69 ++ test/unit/player.test.js | 37 + test/unit/setup.test.js | 1 + test/unit/tech/html5.test.js | 28 + test/unit/tracks/audio-tracks.test.js | 1 + test/unit/tracks/text-tracks.test.js | 7 + test/unit/tracks/video-tracks.test.js | 2 + test/unit/video.test.js | 1 + 49 files changed, 1364 insertions(+), 902 deletions(-) rename ISSUE_TEMPLATE.md => .github/ISSUE_TEMPLATE.md (100%) rename PULL_REQUEST_TEMPLATE.md => .github/PULL_REQUEST_TEMPLATE.md (100%) create mode 100644 build/tasks/languages.js create mode 100644 docs/translations-needed.md create mode 100644 test/require/browserify.js create mode 100644 test/require/node.js create mode 100644 test/require/webpack.js create mode 100644 test/unit/media-error.test.js diff --git a/.babelrc b/.babelrc index 3bccd8fb2b..c6ef2e7bb3 100644 --- a/.babelrc +++ b/.babelrc @@ -1,4 +1,4 @@ { - "presets": ["es2015-loose"], + "presets": [ ["es2015", {"loose": true}] ], "plugins": ["transform-es3-property-literals", "transform-es3-member-expression-literals", "inline-json"] } diff --git a/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md similarity index 100% rename from ISSUE_TEMPLATE.md rename to .github/ISSUE_TEMPLATE.md diff --git a/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md similarity index 100% rename from PULL_REQUEST_TEMPLATE.md rename to .github/PULL_REQUEST_TEMPLATE.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 28e30dcbd1..1f7be8d359 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,20 @@ CHANGELOG ========= ## HEAD (Unreleased) +_(none)_ + +-------------------- + +## 5.12.2 (2016-09-28) +* Changes from 5.11.7 on the 5.12 branch + +## 5.12.1 (2016-08-25) +* Changes from 5.11.6 on the 5.12 branch + +## 5.13.0 (2016-08-25) +* Ignored release + +## 5.12.0 (2016-08-25) * @misteroneill, @BrandonOCasey, and @pagarwal123 updates all the code to pass the linter ([view](https://github.com/videojs/video.js/pull/3459)) * @misteroneill added ghooks to run linter on git push ([view](https://github.com/videojs/video.js/pull/3459)) * @BrandonOCasey removed unused base-styles.js file ([view](https://github.com/videojs/video.js/pull/3486)) @@ -15,8 +29,25 @@ CHANGELOG * @gkatsev added slack badge to README ([view](https://github.com/videojs/video.js/pull/3527)) * @gkatsev reverted back to qunitjs 1.x to unbreak IE8. Added es5-shim to tests ([view](https://github.com/videojs/video.js/pull/3533)) * @gkatsev updated build system to open es5 folder for bundles and dist folder other users ([view](https://github.com/videojs/video.js/pull/3445)) - --------------------- +* greenkeeper updated uglify ([view](https://github.com/videojs/video.js/pull/3547)) +* greenkeeper updated grunt-concurrent ([view](https://github.com/videojs/video.js/pull/3532)) +* greenkeeper updated karma-chrome-launcher ([view](https://github.com/videojs/video.js/pull/3553)) +* @gkatsev added tests for webpack and browserify bundling and node.js requiring ([view](https://github.com/videojs/video.js/pull/3558)) +* @rlchung fixed tests that weren't disposing players when they finished ([view](https://github.com/videojs/video.js/pull/3524)) + +## 5.11.7 (2016-09-28) +* @gkatsev checked throwIfWhitespace first in hasElClass ([view](https://github.com/videojs/video.js/pull/3640)) +* @misteroneill pinned grunt-contrib-uglify to ~0.11 to pin uglify to ~2.6 ([view](https://github.com/videojs/video.js/pull/3634)) +* @gkatsev set playerId on new el created for movingMediaElementInDOM. Fixes #3283 ([view](https://github.com/videojs/video.js/pull/3648)) + +## 5.11.6 (2016-08-25) +* @imbcmdth Added exception handling to event dispatcher ([view](https://github.com/videojs/video.js/pull/3580)) + +## 5.11.5 (2016-08-25) +* @misteroneill fixed wrapping native and emulated MediaErrors ([view](https://github.com/videojs/video.js/pull/3562)) +* @snyderizer fixed switching between audio tracks. Fixes #3510 ([view](https://github.com/videojs/video.js/pull/3538)) +* @jbarabander added title attribute to audio button. Fixes #3528 ([view](https://github.com/videojs/video.js/pull/3565)) +* @misteroneill fixed IE8 media error test failure ([view](https://github.com/videojs/video.js/pull/3568)) ## 5.11.4 (2016-08-16) _(none)_ diff --git a/README.md b/README.md index a7d152e7b3..8afda5bcf3 100644 --- a/README.md +++ b/README.md @@ -55,11 +55,11 @@ var player = videojs('really-cool-video', { /* Options */ }, function() { If you're ready to dive in, the [documentation](http://docs.videojs.com) is the first place to go for more information. ## Contributing -Video.js is a free and open source library, and we appreciate any help you're willing to give. Check out the [contributing guide](CONTRIBUTING.md). +Video.js is a free and open source library, and we appreciate any help you're willing to give. Check out the [contributing guide](/CONTRIBUTING.md). _Video.js uses [BrowserStack](https://browserstack.com) for compatibility testing_ ## Building your own Video.js from source -To build your own custom version read the section on [contributing code](CONTRIBUTING.md#contributing-code) and ["Building your own copy"](CONTRIBUTING.md#building-your-own-copy-of-videojs) in the contributing guide. +To build your own custom version read the section on [contributing code](/CONTRIBUTING.md#contributing-code) and ["Building your own copy"](/CONTRIBUTING.md#building-your-own-copy-of-videojs) in the contributing guide. ## License Video.js is licensed under the Apache License, Version 2.0. [View the license file](LICENSE) diff --git a/build/grunt.js b/build/grunt.js index 5b90947a67..25556b11f1 100644 --- a/build/grunt.js +++ b/build/grunt.js @@ -21,6 +21,7 @@ module.exports = function(grunt) { standalone: 'videojs' }, plugin: [ + ['bundle-collapser/plugin'], ['browserify-derequire'] ] }; @@ -166,6 +167,7 @@ module.exports = function(grunt) { swf: { cwd: 'node_modules/videojs-swf/dist/', src: 'video-js.swf', dest: 'build/temp/', expand: true, filter: 'isFile' }, ie8: { cwd: 'node_modules/videojs-ie8/dist/', src: ['**/**'], dest: 'build/temp/ie8/', expand: true, filter: 'isFile' }, dist: { cwd: 'build/temp/', src: ['**/**', '!test*'], dest: 'dist/', expand: true, filter: 'isFile' }, + a11y: { src: 'sandbox/descriptions.html.example', dest: 'sandbox/descriptions.test-a11y.html' }, // Can only test a file with a .html or .htm extension examples: { cwd: 'docs/examples/', src: ['**/**'], dest: 'dist/examples/', expand: true, filter: 'isFile' } }, cssmin: { @@ -348,7 +350,7 @@ module.exports = function(grunt) { transform: ['babelify'] }, plugin: [ - ['proxyquireify/plugin'] + ['proxyquireify/plugin', 'bundle-collapser/plugin'] ], banner: false, watch: true, @@ -434,6 +436,42 @@ module.exports = function(grunt) { options: { preferLocal: true } + }, + noderequire: { + command: 'node test/require/node.js', + options: { + failOnError: true + } + }, + browserify: { + command: 'browserify test/require/browserify.js -o build/temp/browserify.js', + options: { + preferLocal: true + } + }, + webpack: { + command: 'webpack test/require/webpack.js build/temp/webpack.js', + options: { + preferLocal: true + } + } + }, + accessibility: { + options: { + accessibilityLevel: 'WCAG2AA', + reportLevels: { + notice: false, + warning: true, + error: true + }, + ignore: [ + // Ignore the warning about needing elements + 'WCAG2AA.Principle1.Guideline1_3.1_3_1.H85.2' + ] + + }, + test: { + src: ['sandbox/descriptions.test-a11y.html'] } } }); @@ -443,6 +481,7 @@ module.exports = function(grunt) { grunt.loadNpmTasks('videojs-doc-generator'); grunt.loadNpmTasks('chg'); grunt.loadNpmTasks('gkatsev-grunt-sass'); + grunt.loadNpmTasks('grunt-accessibility'); const buildDependents = [ 'shell:lint', @@ -487,13 +526,21 @@ module.exports = function(grunt) { grunt.registerTask('default', ['test']); // The test script includes coveralls only when the TRAVIS env var is set. - grunt.registerTask('test', ['build', 'karma:defaults'].concat(process.env.TRAVIS && 'coveralls').filter(Boolean)); + grunt.registerTask('test', [ + 'build', + 'shell:noderequire', + 'shell:browserify', + 'shell:webpack', + 'karma:defaults', + 'test-a11y'].concat(process.env.TRAVIS && 'coveralls').filter(Boolean)); // Run while developing grunt.registerTask('dev', ['build', 'connect:dev', 'concurrent:watchSandbox']); grunt.registerTask('watchAll', ['build', 'connect:dev', 'concurrent:watchAll']); + grunt.registerTask('test-a11y', ['copy:a11y', 'accessibility']); + // Pick your testing, or run both in different terminals grunt.registerTask('test-ui', ['browserify:tests']); grunt.registerTask('test-cli', ['karma:watch']); diff --git a/build/tasks/languages.js b/build/tasks/languages.js new file mode 100644 index 0000000000..28990d9b92 --- /dev/null +++ b/build/tasks/languages.js @@ -0,0 +1,35 @@ +module.exports = function(grunt) { + grunt.registerTask('check-translations', 'Check that translations are up to date', function(){ + const source = require('../../lang/en.json'); + const table = require('markdown-table'); + let doc = grunt.file.read('docs/translations-needed.md'); + const tableRegex = /()(.|\n)*()/; + let tableData = [['Language file', 'Missing translations']]; + + grunt.file.recurse('lang', (abspath, rootdir, subdir, filename) => { + if (filename === 'en.json') { + return; + } + const target = require(`../../${abspath}`); + let missing = []; + for (const string in source) { + if (!target[string]) { + grunt.log.writeln(`${filename} missing "${string}"`); + missing.push(string); + } + } + if (missing.length > 0) { + grunt.log.error(`${filename} is missing ${missing.length} translations.`); + tableData.push([`${filename} (missing ${missing.length})`, missing[0]]); + for (var i = 1; i < missing.length; i++) { + tableData.push(['', missing[i]]); + } + } else { + grunt.log.ok(`${filename} is up to date.`); + tableData.push([`${filename} (Complete)`, '']); + } + }); + doc = doc.replace(tableRegex, `$1\n` + table(tableData) + `\n$3`); + grunt.file.write('docs/translations-needed.md', doc); + }); +}; diff --git a/component.json b/component.json index 4b894a9a1a..676402dc25 100644 --- a/component.json +++ b/component.json @@ -1,7 +1,7 @@ { "name": "video.js", "description": "An HTML5 and Flash video player with a common API and skin for both.", - "version": "5.11.4", + "version": "5.12.2", "keywords": [ "videojs", "html5", diff --git a/docs/guides/languages.md b/docs/guides/languages.md index 6b12b2e06e..d018cf96c0 100644 --- a/docs/guides/languages.md +++ b/docs/guides/languages.md @@ -110,6 +110,10 @@ NOTE: These need to be added after the core Video.js script. Notes: - This will add your language key/values to the Video.js player instances individually. If these values already exist in the global dictionary via the process above, those will be overridden for the player instance in question. +Updating default translations +----------------------------- + +A list of the current translations and any strings that need translation are at [docs/translations-needed.md](../translations-needed.md). After updating the language files in /lang/ running `grunt check-languages` will update that list. Setting Default Language in a Video.js Player --------------------------------------------- diff --git a/docs/guides/setup.md b/docs/guides/setup.md index 4fbdf71093..7e6a81f576 100644 --- a/docs/guides/setup.md +++ b/docs/guides/setup.md @@ -121,4 +121,4 @@ videojs(document.getElementsByClassName('awesome_video_class')[0], {}, function( }); ``` -\* If you have trouble playing back content you know is in the [correct format](http://blog.zencoder.com/2013/09/13/what-formats-do-i-need-for-html5-video/), your HTTP server might not be delivering the content with the correct [MIME type](http://en.wikipedia.org/wiki/Internet_media_type#Type_video). Please double check your content's headers before opening an [issue](https://github.com/videojs/video.js/blob/master/CONTRIBUTING.md). +\* If you have trouble playing back content you know is in the [correct format](http://blog.zencoder.com/2013/09/13/what-formats-do-i-need-for-html5-video/), your HTTP server might not be delivering the content with the correct [MIME type](http://en.wikipedia.org/wiki/Internet_media_type#Type_video). Please double check your content's headers before opening an [issue](/CONTRIBUTING.md). diff --git a/docs/index.md b/docs/index.md index 10b434fbd5..78df309bf1 100644 --- a/docs/index.md +++ b/docs/index.md @@ -33,5 +33,5 @@ There are two categories of docs: [Guides](./guides/) and [API docs](./api/). Gu * [Removing Players](./guides/removing-players.md) - Helpful for using VideoJS in single page apps. ## API Docs -- The most relevant API doc is the [player API doc](./api/vjs.Player.md). -- [Full list of API Docs](./api/) +- The most relevant API doc is the [player API doc](http://docs.videojs.com/docs/api/player.html) +- [Full list of API Docs](http://docs.videojs.com/docs/api/index.html) diff --git a/docs/translations-needed.md b/docs/translations-needed.md new file mode 100644 index 0000000000..f919cb5b45 --- /dev/null +++ b/docs/translations-needed.md @@ -0,0 +1,357 @@ +# Translations needed + +The currently available translations are in the lang dir. This table shows the completeness of those translations. Anything not listed does not exist yet, so go ahead and create it by copying `en.json`. + +If you add or update a translation run `grunt check-translations` to update the list and include this modified doc in the pull request. + +## Status of translations + + +| Language file | Missing translations | +| ----------------------- | ----------------------------------------------------------------------------------- | +| ar.json (missing 6) | Close Modal Dialog | +| | Descriptions | +| | descriptions off | +| | Audio Track | +| | The media is encrypted and we do not have the keys to decrypt it. | +| | , opens descriptions settings dialog | +| ba.json (missing 14) | Close Modal Dialog | +| | Descriptions | +| | descriptions off | +| | Audio Track | +| | The media is encrypted and we do not have the keys to decrypt it. | +| | Play Video | +| | Close | +| | Modal Window | +| | This is a modal window | +| | This modal can be closed by pressing the Escape key or activating the close button. | +| | , opens captions settings dialog | +| | , opens subtitles settings dialog | +| | , opens descriptions settings dialog | +| | , selected | +| bg.json (missing 14) | Close Modal Dialog | +| | Descriptions | +| | descriptions off | +| | Audio Track | +| | The media is encrypted and we do not have the keys to decrypt it. | +| | Play Video | +| | Close | +| | Modal Window | +| | This is a modal window | +| | This modal can be closed by pressing the Escape key or activating the close button. | +| | , opens captions settings dialog | +| | , opens subtitles settings dialog | +| | , opens descriptions settings dialog | +| | , selected | +| ca.json (missing 14) | Close Modal Dialog | +| | Descriptions | +| | descriptions off | +| | Audio Track | +| | The media is encrypted and we do not have the keys to decrypt it. | +| | Play Video | +| | Close | +| | Modal Window | +| | This is a modal window | +| | This modal can be closed by pressing the Escape key or activating the close button. | +| | , opens captions settings dialog | +| | , opens subtitles settings dialog | +| | , opens descriptions settings dialog | +| | , selected | +| cs.json (missing 14) | Close Modal Dialog | +| | Descriptions | +| | descriptions off | +| | Audio Track | +| | The media is encrypted and we do not have the keys to decrypt it. | +| | Play Video | +| | Close | +| | Modal Window | +| | This is a modal window | +| | This modal can be closed by pressing the Escape key or activating the close button. | +| | , opens captions settings dialog | +| | , opens subtitles settings dialog | +| | , opens descriptions settings dialog | +| | , selected | +| da.json (missing 14) | Close Modal Dialog | +| | Descriptions | +| | descriptions off | +| | Audio Track | +| | The media is encrypted and we do not have the keys to decrypt it. | +| | Play Video | +| | Close | +| | Modal Window | +| | This is a modal window | +| | This modal can be closed by pressing the Escape key or activating the close button. | +| | , opens captions settings dialog | +| | , opens subtitles settings dialog | +| | , opens descriptions settings dialog | +| | , selected | +| de.json (Complete) | | +| el.json (Complete) | | +| es.json (missing 14) | Close Modal Dialog | +| | Descriptions | +| | descriptions off | +| | Audio Track | +| | The media is encrypted and we do not have the keys to decrypt it. | +| | Play Video | +| | Close | +| | Modal Window | +| | This is a modal window | +| | This modal can be closed by pressing the Escape key or activating the close button. | +| | , opens captions settings dialog | +| | , opens subtitles settings dialog | +| | , opens descriptions settings dialog | +| | , selected | +| fa.json (missing 14) | Close Modal Dialog | +| | Descriptions | +| | descriptions off | +| | Audio Track | +| | The media is encrypted and we do not have the keys to decrypt it. | +| | Play Video | +| | Close | +| | Modal Window | +| | This is a modal window | +| | This modal can be closed by pressing the Escape key or activating the close button. | +| | , opens captions settings dialog | +| | , opens subtitles settings dialog | +| | , opens descriptions settings dialog | +| | , selected | +| fi.json (missing 14) | Close Modal Dialog | +| | Descriptions | +| | descriptions off | +| | Audio Track | +| | The media is encrypted and we do not have the keys to decrypt it. | +| | Play Video | +| | Close | +| | Modal Window | +| | This is a modal window | +| | This modal can be closed by pressing the Escape key or activating the close button. | +| | , opens captions settings dialog | +| | , opens subtitles settings dialog | +| | , opens descriptions settings dialog | +| | , selected | +| fr.json (Complete) | | +| hr.json (missing 14) | Close Modal Dialog | +| | Descriptions | +| | descriptions off | +| | Audio Track | +| | The media is encrypted and we do not have the keys to decrypt it. | +| | Play Video | +| | Close | +| | Modal Window | +| | This is a modal window | +| | This modal can be closed by pressing the Escape key or activating the close button. | +| | , opens captions settings dialog | +| | , opens subtitles settings dialog | +| | , opens descriptions settings dialog | +| | , selected | +| hu.json (missing 14) | Close Modal Dialog | +| | Descriptions | +| | descriptions off | +| | Audio Track | +| | The media is encrypted and we do not have the keys to decrypt it. | +| | Play Video | +| | Close | +| | Modal Window | +| | This is a modal window | +| | This modal can be closed by pressing the Escape key or activating the close button. | +| | , opens captions settings dialog | +| | , opens subtitles settings dialog | +| | , opens descriptions settings dialog | +| | , selected | +| it.json (missing 14) | Close Modal Dialog | +| | Descriptions | +| | descriptions off | +| | Audio Track | +| | The media is encrypted and we do not have the keys to decrypt it. | +| | Play Video | +| | Close | +| | Modal Window | +| | This is a modal window | +| | This modal can be closed by pressing the Escape key or activating the close button. | +| | , opens captions settings dialog | +| | , opens subtitles settings dialog | +| | , opens descriptions settings dialog | +| | , selected | +| ja.json (missing 14) | Close Modal Dialog | +| | Descriptions | +| | descriptions off | +| | Audio Track | +| | The media is encrypted and we do not have the keys to decrypt it. | +| | Play Video | +| | Close | +| | Modal Window | +| | This is a modal window | +| | This modal can be closed by pressing the Escape key or activating the close button. | +| | , opens captions settings dialog | +| | , opens subtitles settings dialog | +| | , opens descriptions settings dialog | +| | , selected | +| ko.json (missing 14) | Close Modal Dialog | +| | Descriptions | +| | descriptions off | +| | Audio Track | +| | The media is encrypted and we do not have the keys to decrypt it. | +| | Play Video | +| | Close | +| | Modal Window | +| | This is a modal window | +| | This modal can be closed by pressing the Escape key or activating the close button. | +| | , opens captions settings dialog | +| | , opens subtitles settings dialog | +| | , opens descriptions settings dialog | +| | , selected | +| nb.json (missing 14) | Close Modal Dialog | +| | Descriptions | +| | descriptions off | +| | Audio Track | +| | The media is encrypted and we do not have the keys to decrypt it. | +| | Play Video | +| | Close | +| | Modal Window | +| | This is a modal window | +| | This modal can be closed by pressing the Escape key or activating the close button. | +| | , opens captions settings dialog | +| | , opens subtitles settings dialog | +| | , opens descriptions settings dialog | +| | , selected | +| nl.json (missing 3) | Close Modal Dialog | +| | Audio Track | +| | The media is encrypted and we do not have the keys to decrypt it. | +| nn.json (missing 14) | Close Modal Dialog | +| | Descriptions | +| | descriptions off | +| | Audio Track | +| | The media is encrypted and we do not have the keys to decrypt it. | +| | Play Video | +| | Close | +| | Modal Window | +| | This is a modal window | +| | This modal can be closed by pressing the Escape key or activating the close button. | +| | , opens captions settings dialog | +| | , opens subtitles settings dialog | +| | , opens descriptions settings dialog | +| | , selected | +| pl.json (missing 7) | Close Modal Dialog | +| | Descriptions | +| | descriptions off | +| | Audio Track | +| | The media is encrypted and we do not have the keys to decrypt it. | +| | Play Video | +| | , opens descriptions settings dialog | +| pt-BR.json (missing 14) | Close Modal Dialog | +| | Descriptions | +| | descriptions off | +| | Audio Track | +| | The media is encrypted and we do not have the keys to decrypt it. | +| | Play Video | +| | Close | +| | Modal Window | +| | This is a modal window | +| | This modal can be closed by pressing the Escape key or activating the close button. | +| | , opens captions settings dialog | +| | , opens subtitles settings dialog | +| | , opens descriptions settings dialog | +| | , selected | +| ru.json (missing 14) | Close Modal Dialog | +| | Descriptions | +| | descriptions off | +| | Audio Track | +| | The media is encrypted and we do not have the keys to decrypt it. | +| | Play Video | +| | Close | +| | Modal Window | +| | This is a modal window | +| | This modal can be closed by pressing the Escape key or activating the close button. | +| | , opens captions settings dialog | +| | , opens subtitles settings dialog | +| | , opens descriptions settings dialog | +| | , selected | +| sr.json (missing 14) | Close Modal Dialog | +| | Descriptions | +| | descriptions off | +| | Audio Track | +| | The media is encrypted and we do not have the keys to decrypt it. | +| | Play Video | +| | Close | +| | Modal Window | +| | This is a modal window | +| | This modal can be closed by pressing the Escape key or activating the close button. | +| | , opens captions settings dialog | +| | , opens subtitles settings dialog | +| | , opens descriptions settings dialog | +| | , selected | +| sv.json (missing 14) | Close Modal Dialog | +| | Descriptions | +| | descriptions off | +| | Audio Track | +| | The media is encrypted and we do not have the keys to decrypt it. | +| | Play Video | +| | Close | +| | Modal Window | +| | This is a modal window | +| | This modal can be closed by pressing the Escape key or activating the close button. | +| | , opens captions settings dialog | +| | , opens subtitles settings dialog | +| | , opens descriptions settings dialog | +| | , selected | +| tr.json (missing 6) | Close Modal Dialog | +| | Descriptions | +| | descriptions off | +| | Audio Track | +| | The media is encrypted and we do not have the keys to decrypt it. | +| | , opens descriptions settings dialog | +| uk.json (missing 14) | Close Modal Dialog | +| | Descriptions | +| | descriptions off | +| | Audio Track | +| | The media is encrypted and we do not have the keys to decrypt it. | +| | Play Video | +| | Close | +| | Modal Window | +| | This is a modal window | +| | This modal can be closed by pressing the Escape key or activating the close button. | +| | , opens captions settings dialog | +| | , opens subtitles settings dialog | +| | , opens descriptions settings dialog | +| | , selected | +| vi.json (missing 14) | Close Modal Dialog | +| | Descriptions | +| | descriptions off | +| | Audio Track | +| | The media is encrypted and we do not have the keys to decrypt it. | +| | Play Video | +| | Close | +| | Modal Window | +| | This is a modal window | +| | This modal can be closed by pressing the Escape key or activating the close button. | +| | , opens captions settings dialog | +| | , opens subtitles settings dialog | +| | , opens descriptions settings dialog | +| | , selected | +| zh-CN.json (missing 13) | Close Modal Dialog | +| | Descriptions | +| | descriptions off | +| | Audio Track | +| | Play Video | +| | Close | +| | Modal Window | +| | This is a modal window | +| | This modal can be closed by pressing the Escape key or activating the close button. | +| | , opens captions settings dialog | +| | , opens subtitles settings dialog | +| | , opens descriptions settings dialog | +| | , selected | +| zh-TW.json (missing 13) | Close Modal Dialog | +| | Descriptions | +| | descriptions off | +| | Audio Track | +| | Play Video | +| | Close | +| | Modal Window | +| | This is a modal window | +| | This modal can be closed by pressing the Escape key or activating the close button. | +| | , opens captions settings dialog | +| | , opens subtitles settings dialog | +| | , opens descriptions settings dialog | +| | , selected | + \ No newline at end of file diff --git a/lang/de.json b/lang/de.json index 4bc84dcaac..57c72af76b 100644 --- a/lang/de.json +++ b/lang/de.json @@ -30,5 +30,11 @@ "This modal can be closed by pressing the Escape key or activating the close button.": "Durch Drücken der Esc-Taste bzw. Betätigung der Schaltfläche \"Schließen\" wird dieses modale Fenster geschlossen.", ", opens captions settings dialog": ", öffnet Einstellungen für Untertitel", ", opens subtitles settings dialog": ", öffnet Einstellungen für Untertitel", - ", selected": " (ausgewählt)" + ", selected": ", ausgewählt", + "Close Modal Dialog": "Modales Fenster schließen", + "Descriptions": "Beschreibungen", + "descriptions off": "Beschreibungen aus", + "The media is encrypted and we do not have the keys to decrypt it.": "Die Entschlüsselungsschlüssel für den verschlüsselten Medieninhalt sind nicht verfügbar.", + ", opens descriptions settings dialog": ", öffnet Einstellungen für Beschreibungen", + "Audio Track": "Tonspur" } diff --git a/lang/el.json b/lang/el.json index 3de96d03d9..8c55749bb6 100644 --- a/lang/el.json +++ b/lang/el.json @@ -18,17 +18,23 @@ "Captions": "Λεζάντες", "captions off": "απόκρυψη λεζάντων", "Chapters": "Κεφάλαια", + "Close Modal Dialog": "Κλείσιμο παραθύρου", + "Descriptions": "Περιγραφές", + "descriptions off": "απόκρυψη περιγραφών", + "Audio Track": "Ροή ήχου", "You aborted the media playback": "Ακυρώσατε την αναπαραγωγή", "A network error caused the media download to fail part-way.": "Ένα σφάλμα δικτύου προκάλεσε την αποτυχία μεταφόρτωσης του αρχείου προς αναπαραγωγή.", "The media could not be loaded, either because the server or network failed or because the format is not supported.": "Το αρχείο προς αναπαραγωγή δεν ήταν δυνατό να φορτωθεί είτε γιατί υπήρξε σφάλμα στον διακομιστή ή το δίκτυο, είτε γιατί ο τύπος του αρχείου δεν υποστηρίζεται.", "The media playback was aborted due to a corruption problem or because the media used features your browser did not support.": "Η αναπαραγωγή ακυρώθηκε είτε λόγω κατεστραμμένου αρχείου, είτε γιατί το αρχείο απαιτεί λειτουργίες που δεν υποστηρίζονται από το πρόγραμμα περιήγησης που χρησιμοποιείτε.", "No compatible source was found for this media.": "Δεν βρέθηκε συμβατή πηγή αναπαραγωγής για το συγκεκριμένο αρχείο.", - "Play video": "Αναπαραγωγή βίντεο", + "The media is encrypted and we do not have the keys to decrypt it.": "Το αρχείο προς αναπαραγωγή είναι κρυπτογραφημένo και δεν υπάρχουν τα απαραίτητα κλειδιά αποκρυπτογράφησης.", + "Play Video": "Αναπαραγωγή βίντεο", "Close": "Κλείσιμο", "Modal Window": "Aναδυόμενο παράθυρο", "This is a modal window": "Το παρών είναι ένα αναδυόμενο παράθυρο", "This modal can be closed by pressing the Escape key or activating the close button.": "Αυτό το παράθυρο μπορεί να εξαφανιστεί πατώντας το πλήκτρο Escape ή πατώντας το κουμπί κλεισίματος.", ", opens captions settings dialog": ", εμφανίζει τις ρυθμίσεις για τις λεζάντες", ", opens subtitles settings dialog": ", εμφανίζει τις ρυθμίσεις για τους υπότιτλους", + ", opens descriptions settings dialog": ", εμφανίζει τις ρυθμίσεις για τις περιγραφές", ", selected": ", επιλεγμένο" } diff --git a/lang/en.json b/lang/en.json index afc2f7f9d5..0ce9413798 100644 --- a/lang/en.json +++ b/lang/en.json @@ -21,11 +21,13 @@ "Close Modal Dialog": "Close Modal Dialog", "Descriptions": "Descriptions", "descriptions off": "descriptions off", + "Audio Track": "Audio Track", "You aborted the media playback": "You aborted the media playback", "A network error caused the media download to fail part-way.": "A network error caused the media download to fail part-way.", "The media could not be loaded, either because the server or network failed or because the format is not supported.": "The media could not be loaded, either because the server or network failed or because the format is not supported.", "The media playback was aborted due to a corruption problem or because the media used features your browser did not support.": "The media playback was aborted due to a corruption problem or because the media used features your browser did not support.", "No compatible source was found for this media.": "No compatible source was found for this media.", + "The media is encrypted and we do not have the keys to decrypt it.": "The media is encrypted and we do not have the keys to decrypt it.", "Play Video": "Play Video", "Close": "Close", "Modal Window": "Modal Window", diff --git a/lang/fr.json b/lang/fr.json index eb6cf98702..ab7c101849 100644 --- a/lang/fr.json +++ b/lang/fr.json @@ -15,12 +15,26 @@ "Playback Rate": "Vitesse de lecture", "Subtitles": "Sous-titres", "subtitles off": "Sous-titres désactivés", - "Captions": "Sous-titres", - "captions off": "Sous-titres désactivés", + "Captions": "Sous-titres transcrits", + "captions off": "Sous-titres transcrits désactivés", "Chapters": "Chapitres", + "Close Modal Dialog": "Fermer la boîte de dialogue modale", + "Descriptions": "Descriptions", + "descriptions off": "descriptions désactivées", + "Audio Track": "Piste audio", "You aborted the media playback": "Vous avez interrompu la lecture de la vidéo.", "A network error caused the media download to fail part-way.": "Une erreur de réseau a interrompu le téléchargement de la vidéo.", "The media could not be loaded, either because the server or network failed or because the format is not supported.": "Cette vidéo n'a pas pu être chargée, soit parce que le serveur ou le réseau a échoué ou parce que le format n'est pas reconnu.", "The media playback was aborted due to a corruption problem or because the media used features your browser did not support.": "La lecture de la vidéo a été interrompue à cause d'un problème de corruption ou parce que la vidéo utilise des fonctionnalités non prises en charge par votre navigateur.", - "No compatible source was found for this media.": "Aucune source compatible n'a été trouvée pour cette vidéo." + "No compatible source was found for this media.": "Aucune source compatible n'a été trouvée pour cette vidéo.", + "The media is encrypted and we do not have the keys to decrypt it.": "Le média est chiffré et nous n'avons pas les clés pour le déchiffrer.", + "Play Video": "Lire la vidéo", + "Close": "Fermer", + "Modal Window": "Fenêtre modale", + "This is a modal window": "Ceci est une fenêtre modale", + "This modal can be closed by pressing the Escape key or activating the close button.": "Ce modal peut être fermé en appuyant sur la touche Échap ou activer le bouton de fermeture.", + ", opens captions settings dialog": ", ouvrir les paramètres des sous-titres transcrits", + ", opens subtitles settings dialog": ", ouvrir les paramètres des sous-titres", + ", opens descriptions settings dialog": ", ouvrir les paramètres des descriptions", + ", selected": ", sélectionné" } diff --git a/package.json b/package.json index d3f08f871b..55d58f8e01 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "video.js", "description": "An HTML5 and Flash video player with a common API and skin for both.", - "version": "5.11.4", + "version": "5.12.2", "main": "./es5/video.js", "style": "./dist/video-js.css", "copyright": "Copyright Brightcove, Inc. ", @@ -16,7 +16,11 @@ "homepage": "http://videojs.com", "author": "Steve Heffernan", "scripts": { + "build": "grunt dist", + "change": "grunt chg-add", + "clean": "grunt clean", "lint": "vjsstandard", + "start": "grunt watchAll", "test": "grunt test" }, "repository": { @@ -27,7 +31,7 @@ "babel-runtime": "^6.9.2", "global": "4.3.0", "lodash-compat": "3.10.2", - "object.assign": "4.0.3", + "object.assign": "^4.0.4", "safe-json-parse": "4.0.0", "tsml": "1.0.1", "videojs-font": "2.0.0", @@ -42,13 +46,14 @@ "babel-plugin-transform-es3-member-expression-literals": "^6.8.0", "babel-plugin-transform-es3-property-literals": "^6.8.0", "babel-plugin-transform-runtime": "^6.9.0", - "babel-preset-es2015-loose": "^7.0.0", + "babel-preset-es2015": "^6.14.0", "babel-register": "^6.9.0", "babelify": "^7.3.0", "blanket": "^1.1.6", "browserify-derequire": "^0.9.4", "browserify-istanbul": "^0.2.1", "browserify-versionify": "^1.0.4", + "bundle-collapser": "^1.2.1", "chg": "^0.3.2", "es5-shim": "^4.1.3", "es6-shim": "^0.35.1", @@ -56,29 +61,30 @@ "gkatsev-grunt-sass": "^1.1.1", "grunt": "^0.4.4", "grunt-babel": "^6.0.0", + "grunt-accessibility": "^4.1.0", "grunt-banner": "^0.4.0", "grunt-browserify": "3.5.1", "grunt-cli": "~0.1.13", - "grunt-concurrent": "^1.0.0", + "grunt-concurrent": "^2.3.1", "grunt-contrib-clean": "~0.4.0a", "grunt-contrib-concat": "^0.5.1", "grunt-contrib-connect": "~0.7.1", "grunt-contrib-copy": "^0.8.0", - "grunt-contrib-cssmin": "~0.6.0", - "grunt-contrib-uglify": "^0.8.0", + "grunt-contrib-cssmin": "~1.0.2", + "grunt-contrib-uglify": "~0.11.0", "grunt-contrib-watch": "~0.1.4", "grunt-coveralls": "^1.0.0", "grunt-fastly": "^0.1.3", "grunt-github-releaser": "^0.1.17", "grunt-karma": "^2.0.0", - "grunt-shell": "^1.3.0", + "grunt-shell": "^2.0.0", "grunt-version": "~0.3.0", "grunt-videojs-languages": "0.0.4", "grunt-zip": "0.10.2", "karma": "^1.2.0", "karma-browserify": "^5.1.0", "karma-browserstack-launcher": "^1.0.1", - "karma-chrome-launcher": "^1.0.1", + "karma-chrome-launcher": "^2.0.0", "karma-coverage": "^1.1.1", "karma-detect-browsers": "^2.1.0", "karma-firefox-launcher": "^1.0.0", @@ -88,13 +94,15 @@ "karma-safari-launcher": "^1.0.0", "karma-sinon": "^1.0.5", "load-grunt-tasks": "^3.1.0", + "markdown-table": "^1.0.0", "proxyquireify": "^3.0.0", "qunitjs": "^1.23.1", "sinon": "^1.16.1", "time-grunt": "^1.1.1", - "uglify-js": "~2.3.6", + "uglify-js": "~2.7.3", "videojs-doc-generator": "0.0.1", - "videojs-standard": "^5.2.0" + "videojs-standard": "^5.2.0", + "webpack": "^1.13.2" }, "vjsstandard": { "ignore": [ diff --git a/src/css/_utilities.scss b/src/css/_utilities.scss index 6621c2be0e..994c1244dd 100644 --- a/src/css/_utilities.scss +++ b/src/css/_utilities.scss @@ -86,7 +86,7 @@ -webkit-user-select: $string; -moz-user-select: $string; -ms-user-select: $string; - user-select: $string + user-select: $string; } // https://developer.mozilla.org/en-US/docs/Web/CSS/box-shadow diff --git a/src/css/components/_fullscreen.scss b/src/css/components/_fullscreen.scss index b0c6b342e3..e415c3fa6c 100644 --- a/src/css/components/_fullscreen.scss +++ b/src/css/components/_fullscreen.scss @@ -1,8 +1,6 @@ .video-js .vjs-fullscreen-control { cursor: pointer; @include flex(none); -} -.video-js .vjs-fullscreen-control { @extend .vjs-icon-fullscreen-enter; } // Switch to the exit icon when the player is in fullscreen diff --git a/src/css/components/_play-pause.scss b/src/css/components/_play-pause.scss index 198350538e..abbe6e7b43 100644 --- a/src/css/components/_play-pause.scss +++ b/src/css/components/_play-pause.scss @@ -1,8 +1,6 @@ .video-js .vjs-play-control { cursor: pointer; @include flex(none); -} -.video-js .vjs-play-control { @extend .vjs-icon-play; } .video-js .vjs-play-control.vjs-playing { diff --git a/src/css/components/_progress.scss b/src/css/components/_progress.scss index ace8d26d77..f684b7cf57 100644 --- a/src/css/components/_progress.scss +++ b/src/css/components/_progress.scss @@ -31,7 +31,7 @@ // We need an increased hit area on hover .video-js .vjs-progress-control:hover .vjs-progress-holder { - font-size: 1.666666666666666666em + font-size: 1.666666666666666666em; } /* If we let the font size grow as much as everything else, the current time tooltip ends up diff --git a/src/css/components/menu/_menu.scss b/src/css/components/menu/_menu.scss index 4d75864921..bb6496f79b 100644 --- a/src/css/components/menu/_menu.scss +++ b/src/css/components/menu/_menu.scss @@ -14,7 +14,8 @@ .vjs-menu .vjs-menu-content { display: block; - padding: 0; margin: 0; + padding: 0; + margin: 0; overflow: auto; font-family: $text-font-family; } diff --git a/src/css/ie8.css b/src/css/ie8.css index 690f2d74e2..6ce8c23fdc 100644 --- a/src/css/ie8.css +++ b/src/css/ie8.css @@ -1,30 +1,11 @@ -/** -// IE 8 hack for media queries which the sass parser can encounter problems with -$ie8screen: "\\0screen"; - -// original home: css/components/_control-bar.scss -// IE8 is flakey with fonts, and you have to change the actual content to force -// fonts to show/hide properly. -// - "\9" IE8 hack didn't work for this -// Found in XP IE8 from http://modern.ie. Does not show up in "IE8 mode" in IE9 -.vjs-user-inactive.vjs-playing .vjs-control-bar :before { - @media #{$ie8screen} { content: ""; } -} - -// Video has started playing AND user is inactive -.vjs-has-started.vjs-user-inactive.vjs-playing .vjs-control-bar { - - // Make controls hidden in IE8 for now - @media #{$ie8screen} { - visibility: hidden; - } -} -*/ - @media \0screen { .vjs-user-inactive.vjs-playing .vjs-control-bar :before { - content: ""; } } + content: ""; + } +} @media \0screen { .vjs-has-started.vjs-user-inactive.vjs-playing .vjs-control-bar { - visibility: hidden; } } + visibility: hidden; + } +} diff --git a/src/js/control-bar/audio-track-controls/audio-track-button.js b/src/js/control-bar/audio-track-controls/audio-track-button.js index 3b84c3db6b..a49998b9ec 100644 --- a/src/js/control-bar/audio-track-controls/audio-track-button.js +++ b/src/js/control-bar/audio-track-controls/audio-track-button.js @@ -58,6 +58,6 @@ class AudioTrackButton extends TrackButton { return items; } } - +AudioTrackButton.prototype.controlText_ = 'Audio Track'; Component.registerComponent('AudioTrackButton', AudioTrackButton); export default AudioTrackButton; diff --git a/src/js/control-bar/audio-track-controls/audio-track-menu-item.js b/src/js/control-bar/audio-track-controls/audio-track-menu-item.js index b97f44a22f..97ba5e16eb 100644 --- a/src/js/control-bar/audio-track-controls/audio-track-menu-item.js +++ b/src/js/control-bar/audio-track-controls/audio-track-menu-item.js @@ -53,9 +53,7 @@ class AudioTrackMenuItem extends MenuItem { for (let i = 0; i < tracks.length; i++) { const track = tracks[i]; - if (track === this.track) { - track.enabled = true; - } + track.enabled = track === this.track; } } diff --git a/src/js/control-bar/progress-control/load-progress-bar.js b/src/js/control-bar/progress-control/load-progress-bar.js index 2e1a8aae1c..5ffa636b30 100644 --- a/src/js/control-bar/progress-control/load-progress-bar.js +++ b/src/js/control-bar/progress-control/load-progress-bar.js @@ -16,6 +16,7 @@ class LoadProgressBar extends Component { constructor(player, options) { super(player, options); + this.partEls_ = []; this.on(player, 'progress', this.update); } @@ -41,7 +42,7 @@ class LoadProgressBar extends Component { const buffered = this.player_.buffered(); const duration = this.player_.duration(); const bufferedEnd = this.player_.bufferedEnd(); - const children = this.el_.children; + const children = this.partEls_; // get the percent width of a time compared to the total end const percentify = function(time, end) { @@ -62,6 +63,7 @@ class LoadProgressBar extends Component { if (!part) { part = this.el_.appendChild(Dom.createEl()); + children[i] = part; } // set the percent based on the width of the progress bar (bufferedEnd) @@ -73,6 +75,7 @@ class LoadProgressBar extends Component { for (let i = children.length; i > buffered.length; i--) { this.el_.removeChild(children[i - 1]); } + children.length = buffered.length; } } diff --git a/src/js/media-error.js b/src/js/media-error.js index 831fe277a1..957871ec15 100644 --- a/src/js/media-error.js +++ b/src/js/media-error.js @@ -4,24 +4,45 @@ import assign from 'object.assign'; /* - * Custom MediaError to mimic the HTML5 MediaError + * Custom MediaError class which mimics the standard HTML5 MediaError class. * - * @param {Number} code The media error code + * @param {Number|String|Object|MediaError} value + * This can be of multiple types: + * - Number: should be a standard error code + * - String: an error message (the code will be 0) + * - Object: arbitrary properties + * - MediaError (native): used to populate a video.js MediaError object + * - MediaError (video.js): will return itself if it's already a + * video.js MediaError object. */ -const MediaError = function(code) { - if (typeof code === 'number') { - this.code = code; - } else if (typeof code === 'string') { +function MediaError(value) { + + // Allow redundant calls to this constructor to avoid having `instanceof` + // checks peppered around the code. + if (value instanceof MediaError) { + return value; + } + + if (typeof value === 'number') { + this.code = value; + } else if (typeof value === 'string') { // default code is zero, so this is a custom error - this.message = code; - } else if (typeof code === 'object') { - assign(this, code); + this.message = value; + } else if (typeof value === 'object') { + + // We assign the `code` property manually because native MediaError objects + // do not expose it as an own/enumerable property of the object. + if (typeof value.code === 'number') { + this.code = value.code; + } + + assign(this, value); } if (!this.message) { this.message = MediaError.defaultMessages[this.code] || ''; } -}; +} /* * The error code that refers two one of the defined diff --git a/src/js/player.js b/src/js/player.js index 0f58266d78..b95606bf09 100644 --- a/src/js/player.js +++ b/src/js/player.js @@ -44,6 +44,86 @@ import './tracks/text-track-settings.js'; // Import Html5 tech, at least for disposing the original video tag. import './tech/html5.js'; +const TECH_EVENTS_RETRIGGER = [ + /** + * Fired while the user agent is downloading media data + * + * @private + * @method Player.prototype.handleTechProgress_ + */ + 'progress', + /** + * Fires when the loading of an audio/video is aborted + * + * @private + * @method Player.prototype.handleTechAbort_ + */ + 'abort', + /** + * Fires when the browser is intentionally not getting media data + * + * @private + * @method Player.prototype.handleTechSuspend_ + */ + 'suspend', + /** + * Fires when the current playlist is empty + * + * @private + * @method Player.prototype.handleTechEmptied_ + */ + 'emptied', + /** + * Fires when the browser is trying to get media data, but data is not available + * + * @private + * @method Player.prototype.handleTechStalled_ + */ + 'stalled', + /** + * Fires when the browser has loaded meta data for the audio/video + * + * @private + * @method Player.prototype.handleTechLoadedmetadata_ + */ + 'loadedmetadata', + /** + * Fires when the browser has loaded the current frame of the audio/video + * + * @private + * @method Player.prototype.handleTechLoaddeddata_ + */ + 'loadeddata', + /** + * Fires when the current playback position has changed + * + * @private + * @method Player.prototype.handleTechTimeUpdate_ + */ + 'timeupdate', + /** + * Fires when the playing speed of the audio/video is changed + * + * @private + * @method Player.prototype.handleTechRatechange_ + */ + 'ratechange', + /** + * Fires when the volume has been changed + * + * @private + * @method Player.prototype.handleTechVolumechange_ + */ + 'volumechange', + /** + * Fires when the text track has been changed + * + * @private + * @method Player.prototype.handleTechTexttrackchange_ + */ + 'texttrackchange' +]; + /** * An instance of the `Player` class is created when any of the Video.js setup methods are used to initialize a video. * ```js @@ -60,20 +140,10 @@ import './tech/html5.js'; * @param {Element} tag The original video tag used for configuring options * @param {Object=} options Object of option names and values * @param {Function=} ready Ready callback function - * @extends Component * @class Player */ class Player extends Component { - /** - * player's constructor function - * - * @constructs - * @method init - * @param {Element} tag The original video tag used for configuring options - * @param {Object=} options Player options - * @param {Function=} ready Ready callback function - */ constructor(tag, options, ready) { // Make sure tag ID exists tag.id = tag.id || `vjs_video_${Guid.newGUID()}`; @@ -257,8 +327,6 @@ class Player extends Component { * ``` * This is especially helpful if you are dynamically adding and removing videos * to/from the DOM. - * - * @method dispose */ dispose() { this.trigger('dispose'); @@ -291,7 +359,6 @@ class Player extends Component { * Create the component's DOM element * * @return {Element} - * @method createEl */ createEl() { const el = this.el_ = super.createEl('div'); @@ -381,7 +448,6 @@ class Player extends Component { * * @param {Number=} value Value for width * @return {Number} Width when getting - * @method width */ width(value) { return this.dimension('width', value); @@ -392,7 +458,6 @@ class Player extends Component { * * @param {Number=} value Value for height * @return {Number} Height when getting - * @method height */ height(value) { return this.dimension('height', value); @@ -404,7 +469,6 @@ class Player extends Component { * @param {String} dimension Either width or height * @param {Number=} value Value for dimension * @return {Component} - * @method dimension */ dimension(dimension, value) { const privDimension = dimension + '_'; @@ -435,7 +499,6 @@ class Player extends Component { * Add/remove the vjs-fluid class * * @param {Boolean} bool Value of true adds the class, value of false removes the class - * @method fluid */ fluid(bool) { if (bool === undefined) { @@ -456,7 +519,6 @@ class Player extends Component { * * @param {String=} ratio Aspect ratio for player * @return aspectRatio - * @method aspectRatio */ aspectRatio(ratio) { if (ratio === undefined) { @@ -478,8 +540,6 @@ class Player extends Component { /** * Update styles of the player element (height, width and aspect ratio) - * - * @method updateStyleEl_ */ updateStyleEl_() { if (window.VIDEOJS_NO_DYNAMIC_STYLE === true) { @@ -568,7 +628,6 @@ class Player extends Component { * * @param {String} techName Name of the playback technology * @param {String} source Video source - * @method loadTech_ * @private */ loadTech_(techName, source) { @@ -637,6 +696,9 @@ class Player extends Component { textTrackConverter.jsonToTextTracks(this.textTracksJson_ || [], this.tech_); // Listen to all HTML5-defined events and trigger them on the player + TECH_EVENTS_RETRIGGER.forEach((event) => { + this.on(this.tech_, event, this[`handleTech${toTitleCase(event)}_`]); + }); this.on(this.tech_, 'loadstart', this.handleTechLoadStart_); this.on(this.tech_, 'waiting', this.handleTechWaiting_); this.on(this.tech_, 'canplay', this.handleTechCanPlay_); @@ -648,20 +710,9 @@ class Player extends Component { this.on(this.tech_, 'play', this.handleTechPlay_); this.on(this.tech_, 'firstplay', this.handleTechFirstPlay_); this.on(this.tech_, 'pause', this.handleTechPause_); - this.on(this.tech_, 'progress', this.handleTechProgress_); this.on(this.tech_, 'durationchange', this.handleTechDurationChange_); this.on(this.tech_, 'fullscreenchange', this.handleTechFullscreenChange_); this.on(this.tech_, 'error', this.handleTechError_); - this.on(this.tech_, 'suspend', this.handleTechSuspend_); - this.on(this.tech_, 'abort', this.handleTechAbort_); - this.on(this.tech_, 'emptied', this.handleTechEmptied_); - this.on(this.tech_, 'stalled', this.handleTechStalled_); - this.on(this.tech_, 'loadedmetadata', this.handleTechLoadedMetaData_); - this.on(this.tech_, 'loadeddata', this.handleTechLoadedData_); - this.on(this.tech_, 'timeupdate', this.handleTechTimeUpdate_); - this.on(this.tech_, 'ratechange', this.handleTechRateChange_); - this.on(this.tech_, 'volumechange', this.handleTechVolumeChange_); - this.on(this.tech_, 'texttrackchange', this.handleTechTextTrackChange_); this.on(this.tech_, 'loadedmetadata', this.updateStyleEl_); this.on(this.tech_, 'posterchange', this.handleTechPosterChange_); this.on(this.tech_, 'textdata', this.handleTechTextData_); @@ -688,7 +739,6 @@ class Player extends Component { /** * Unload playback technology * - * @method unloadTech_ * @private */ unloadTech_() { @@ -713,7 +763,6 @@ class Player extends Component { * * @param {Object} * @return {Object} The Tech - * @method tech */ tech(safety) { if (safety && safety.IWillNotUseThisInPlugins) { @@ -749,7 +798,6 @@ class Player extends Component { * on any controls will still keep the user active * * @private - * @method addTechControlsListeners_ */ addTechControlsListeners_() { // Make sure to remove all the previous listeners in case we are called multiple times. @@ -777,7 +825,6 @@ class Player extends Component { * Remove the listeners used for click and tap controls. This is needed for * toggling to controls disabled, where a tap/touch should do nothing. * - * @method removeTechControlsListeners_ * @private */ removeTechControlsListeners_() { @@ -793,7 +840,6 @@ class Player extends Component { /** * Player waits for the tech to be ready * - * @method handleTechReady_ * @private */ handleTechReady_() { @@ -828,8 +874,8 @@ class Player extends Component { /** * Fired when the user agent begins looking for media data * + * @event loadstart * @private - * @method handleTechLoadStart_ */ handleTechLoadStart_() { // TODO: Update to use `emptied` event instead. See #1277. @@ -858,7 +904,6 @@ class Player extends Component { * @param {Boolean} hasStarted The value of true adds the class the value of false remove the class * @return {Boolean} Boolean value if has started * @private - * @method hasStarted */ hasStarted(hasStarted) { if (hasStarted !== undefined) { @@ -882,7 +927,6 @@ class Player extends Component { * Fired whenever the media begins or resumes playback * * @private - * @method handleTechPlay_ */ handleTechPlay_() { this.removeClass('vjs-ended'); @@ -900,7 +944,6 @@ class Player extends Component { * Fired whenever the media begins waiting * * @private - * @method handleTechWaiting_ */ handleTechWaiting_() { this.addClass('vjs-waiting'); @@ -913,7 +956,6 @@ class Player extends Component { * which is not consistent between browsers. See #1351 * * @private - * @method handleTechCanPlay_ */ handleTechCanPlay_() { this.removeClass('vjs-waiting'); @@ -925,7 +967,6 @@ class Player extends Component { * which is not consistent between browsers. See #1351 * * @private - * @method handleTechCanPlayThrough_ */ handleTechCanPlayThrough_() { this.removeClass('vjs-waiting'); @@ -937,7 +978,6 @@ class Player extends Component { * which is not consistent between browsers. See #1351 * * @private - * @method handleTechPlaying_ */ handleTechPlaying_() { this.removeClass('vjs-waiting'); @@ -948,7 +988,6 @@ class Player extends Component { * Fired whenever the player is jumping to a new time * * @private - * @method handleTechSeeking_ */ handleTechSeeking_() { this.addClass('vjs-seeking'); @@ -959,7 +998,6 @@ class Player extends Component { * Fired when the player has finished jumping to a new time * * @private - * @method handleTechSeeked_ */ handleTechSeeked_() { this.removeClass('vjs-seeking'); @@ -973,7 +1011,6 @@ class Player extends Component { * prevent playback, use `myPlayer.one('play');` instead. * * @private - * @method handleTechFirstPlay_ */ handleTechFirstPlay_() { // If the first starttime attribute is specified @@ -990,7 +1027,6 @@ class Player extends Component { * Fired whenever the media has been paused * * @private - * @method handleTechPause_ */ handleTechPause_() { this.removeClass('vjs-playing'); @@ -998,21 +1034,11 @@ class Player extends Component { this.trigger('pause'); } - /** - * Fired while the user agent is downloading media data - * - * @private - * @method handleTechProgress_ - */ - handleTechProgress_() { - this.trigger('progress'); - } - /** * Fired when the end of the media resource is reached (currentTime == duration) * + * @event ended * @private - * @method handleTechEnded_ */ handleTechEnded_() { this.addClass('vjs-ended'); @@ -1030,7 +1056,6 @@ class Player extends Component { * Fired when the duration of the media resource is first known or changed * * @private - * @method handleTechDurationChange_ */ handleTechDurationChange_() { this.duration(this.techGet_('duration')); @@ -1041,7 +1066,6 @@ class Player extends Component { * * @param {Object=} event Event object * @private - * @method handleTechClick_ */ handleTechClick_(event) { // We're using mousedown to detect clicks thanks to Flash, but mousedown @@ -1066,7 +1090,6 @@ class Player extends Component { * activity state, which hides and shows the controls. * * @private - * @method handleTechTap_ */ handleTechTap_() { this.userActive(!this.userActive()); @@ -1076,7 +1099,6 @@ class Player extends Component { * Handle touch to start * * @private - * @method handleTechTouchStart_ */ handleTechTouchStart_() { this.userWasActive = this.userActive(); @@ -1086,7 +1108,6 @@ class Player extends Component { * Handle touch to move * * @private - * @method handleTechTouchMove_ */ handleTechTouchMove_() { if (this.userWasActive) { @@ -1098,7 +1119,6 @@ class Player extends Component { * Handle touch to end * * @private - * @method handleTechTouchEnd_ */ handleTechTouchEnd_(event) { // Stop the mouse events from also happening @@ -1109,7 +1129,6 @@ class Player extends Component { * Fired when the player switches in or out of fullscreen mode * * @private - * @method handleFullscreenChange_ */ handleFullscreenChange_() { if (this.isFullscreen()) { @@ -1124,7 +1143,6 @@ class Player extends Component { * use stageclick events triggered from inside the SWF instead * * @private - * @method handleStageClick_ */ handleStageClick_() { this.reportUserActivity(); @@ -1134,7 +1152,6 @@ class Player extends Component { * Handle Tech Fullscreen Change * * @private - * @method handleTechFullscreenChange_ */ handleTechFullscreenChange_(event, data) { if (data) { @@ -1147,7 +1164,6 @@ class Player extends Component { * Fires when an error occurred during the loading of an audio/video * * @private - * @method handleTechError_ */ handleTechError_() { const error = this.tech_.error(); @@ -1155,56 +1171,6 @@ class Player extends Component { this.error(error); } - /** - * Fires when the browser is intentionally not getting media data - * - * @private - * @method handleTechSuspend_ - */ - handleTechSuspend_() { - this.trigger('suspend'); - } - - /** - * Fires when the loading of an audio/video is aborted - * - * @private - * @method handleTechAbort_ - */ - handleTechAbort_() { - this.trigger('abort'); - } - - /** - * Fires when the current playlist is empty - * - * @private - * @method handleTechEmptied_ - */ - handleTechEmptied_() { - this.trigger('emptied'); - } - - /** - * Fires when the browser is trying to get media data, but data is not available - * - * @private - * @method handleTechStalled_ - */ - handleTechStalled_() { - this.trigger('stalled'); - } - - /** - * Fires when the browser has loaded meta data for the audio/video - * - * @private - * @method handleTechLoadedMetaData_ - */ - handleTechLoadedMetaData_() { - this.trigger('loadedmetadata'); - } - handleTechTextData_() { let data = null; @@ -1214,61 +1180,10 @@ class Player extends Component { this.trigger('textdata', data); } - /** - * Fires when the browser has loaded the current frame of the audio/video - * - * @private - * @method handleTechLoadedData_ - */ - handleTechLoadedData_() { - this.trigger('loadeddata'); - } - - /** - * Fires when the current playback position has changed - * - * @private - * @method handleTechTimeUpdate_ - */ - handleTechTimeUpdate_() { - this.trigger('timeupdate'); - } - - /** - * Fires when the playing speed of the audio/video is changed - * - * @private - * @method handleTechRateChange_ - */ - handleTechRateChange_() { - this.trigger('ratechange'); - } - - /** - * Fires when the volume has been changed - * - * @private - * @method handleTechVolumeChange_ - */ - handleTechVolumeChange_() { - this.trigger('volumechange'); - } - - /** - * Fires when the text track has been changed - * - * @private - * @method handleTechTextTrackChange_ - */ - handleTechTextTrackChange_() { - this.trigger('texttrackchange'); - } - /** * Get object for cached values. * * @return {Object} - * @method getCache */ getCache() { return this.cache_; @@ -1280,7 +1195,6 @@ class Player extends Component { * @param {String=} method Method * @param {Object=} arg Argument * @private - * @method techCall_ */ techCall_(method, arg) { // If it's not ready yet, call method when it is @@ -1308,7 +1222,6 @@ class Player extends Component { * @param {String} method Tech method * @return {Method} * @private - * @method techGet_ */ techGet_(method) { if (this.tech_ && this.tech_.isReady_) { @@ -1344,7 +1257,6 @@ class Player extends Component { * ``` * * @return {Player} self - * @method play */ play() { // Only calls the tech's play if we already have a src loaded @@ -1366,7 +1278,6 @@ class Player extends Component { * ``` * * @return {Player} self - * @method pause */ pause() { this.techCall_('pause'); @@ -1381,7 +1292,6 @@ class Player extends Component { * ``` * * @return {Boolean} false if the media is currently playing, or true otherwise - * @method paused */ paused() { // The initial state of paused should be true (in Safari it's actually false) @@ -1395,7 +1305,6 @@ class Player extends Component { * @param {Boolean} isScrubbing True/false the user is scrubbing * @return {Boolean} The scrubbing status when getting * @return {Object} The player when setting - * @method scrubbing */ scrubbing(isScrubbing) { if (isScrubbing !== undefined) { @@ -1425,7 +1334,6 @@ class Player extends Component { * @param {Number|String=} seconds The time to seek to * @return {Number} The time in seconds, when not setting * @return {Player} self, when the current time is set - * @method currentTime */ currentTime(seconds) { if (seconds !== undefined) { @@ -1457,7 +1365,6 @@ class Player extends Component { * * @param {Number} seconds Duration when setting * @return {Number} The duration of the video in seconds when getting - * @method duration */ duration(seconds) { if (seconds === undefined) { @@ -1495,7 +1402,6 @@ class Player extends Component { * Not a native video element function, but useful * * @return {Number} The time remaining in seconds - * @method remainingTime */ remainingTime() { return this.duration() - this.currentTime(); @@ -1521,7 +1427,6 @@ class Player extends Component { * ``` * * @return {Object} A mock TimeRange object (following HTML spec) - * @method buffered */ buffered() { let buffered = this.techGet_('buffered'); @@ -1542,7 +1447,6 @@ class Player extends Component { * (This method isn't in the HTML5 spec, but it's very convenient) * * @return {Number} A decimal between 0 and 1 representing the percent - * @method bufferedPercent */ bufferedPercent() { return bufferedPercent(this.buffered(), this.duration()); @@ -1553,7 +1457,6 @@ class Player extends Component { * This is used in the progress bar to encapsulate all time ranges. * * @return {Number} The end of the last buffered time range - * @method bufferedEnd */ bufferedEnd() { const buffered = this.buffered(); @@ -1580,7 +1483,6 @@ class Player extends Component { * @param {Number} percentAsDecimal The new volume as a decimal percent * @return {Number} The current volume when getting * @return {Player} self when setting - * @method volume */ volume(percentAsDecimal) { let vol; @@ -1611,7 +1513,6 @@ class Player extends Component { * @param {Boolean=} muted True to mute, false to unmute * @return {Boolean} True if mute is on, false if not when getting * @return {Player} self when setting mute - * @method muted */ muted(muted) { if (muted !== undefined) { @@ -1627,7 +1528,6 @@ class Player extends Component { * Check to see if fullscreen is supported * * @return {Boolean} - * @method supportsFullScreen */ supportsFullScreen() { return this.techGet_('supportsFullScreen') || false; @@ -1648,7 +1548,6 @@ class Player extends Component { * @param {Boolean=} isFS Update the player's fullscreen state * @return {Boolean} true if fullscreen false if not when getting * @return {Player} self when setting - * @method isFullscreen */ isFullscreen(isFS) { if (isFS !== undefined) { @@ -1671,7 +1570,6 @@ class Player extends Component { * Safari. * * @return {Player} self - * @method requestFullscreen */ requestFullscreen() { const fsApi = FullscreenApi; @@ -1721,7 +1619,6 @@ class Player extends Component { * ``` * * @return {Player} self - * @method exitFullscreen */ exitFullscreen() { const fsApi = FullscreenApi; @@ -1743,8 +1640,6 @@ class Player extends Component { /** * When fullscreen isn't supported we can stretch the video container to as wide as the browser will let us. - * - * @method enterFullWindow */ enterFullWindow() { this.isFullWindow = true; @@ -1768,7 +1663,6 @@ class Player extends Component { * Check for call to either exit full window or full screen on ESC key * * @param {String} event Event to check for key press - * @method fullWindowOnEscKey */ fullWindowOnEscKey(event) { if (event.keyCode === 27) { @@ -1782,8 +1676,6 @@ class Player extends Component { /** * Exit full window - * - * @method exitFullWindow */ exitFullWindow() { this.isFullWindow = false; @@ -1805,7 +1697,6 @@ class Player extends Component { * * @param {String} type The mimetype to check * @return {String} 'probably', 'maybe', or '' (empty string) - * @method canPlayType */ canPlayType(type) { let can; @@ -1847,7 +1738,6 @@ class Player extends Component { * * @param {Array} sources The sources for a media asset * @return {Object|Boolean} Object of source and tech order, otherwise false - * @method selectSource */ selectSource(sources) { // Get only the techs specified in `techOrder` that exist and are supported by the @@ -1942,7 +1832,6 @@ class Player extends Component { * @param {String|Object|Array=} source The source URL, object, or array of sources * @return {String} The current video source when getting * @return {String} The player when setting - * @method src */ src(source) { if (source === undefined) { @@ -2012,7 +1901,6 @@ class Player extends Component { * * @param {Array} sources Array of source objects * @private - * @method sourceList_ */ sourceList_(sources) { const sourceTech = this.selectSource(sources); @@ -2041,7 +1929,6 @@ class Player extends Component { * Begin loading the src data. * * @return {Player} Returns the player - * @method load */ load() { this.techCall_('load'); @@ -2053,7 +1940,6 @@ class Player extends Component { * and calls `reset` on the tech`. * * @return {Player} Returns the player - * @method reset */ reset() { this.loadTech_(toTitleCase(this.options_.techOrder[0]), null); @@ -2066,7 +1952,6 @@ class Player extends Component { * Can be used in conjuction with `currentType` to assist in rebuilding the current source object. * * @return {String} The current source - * @method currentSrc */ currentSrc() { return this.techGet_('currentSrc') || this.cache_.src || ''; @@ -2078,7 +1963,6 @@ class Player extends Component { * source and tech later * * @return {String} The source MIME type - * @method currentType */ currentType() { return this.currentType_ || ''; @@ -2090,7 +1974,6 @@ class Player extends Component { * @param {Boolean} value Boolean to determine if preload should be used * @return {String} The preload attribute value when getting * @return {Player} Returns the player when setting - * @method preload */ preload(value) { if (value !== undefined) { @@ -2107,7 +1990,6 @@ class Player extends Component { * @param {Boolean} value Boolean to determine if video should autoplay * @return {String} The autoplay attribute value when getting * @return {Player} Returns the player when setting - * @method autoplay */ autoplay(value) { if (value !== undefined) { @@ -2124,7 +2006,6 @@ class Player extends Component { * @param {Boolean} value Boolean to determine if video should loop * @return {String} The loop attribute value when getting * @return {Player} Returns the player when setting - * @method loop */ loop(value) { if (value !== undefined) { @@ -2149,7 +2030,6 @@ class Player extends Component { * @param {String=} src Poster image source URL * @return {String} poster URL when getting * @return {Player} self when setting - * @method poster */ poster(src) { if (src === undefined) { @@ -2183,7 +2063,6 @@ class Player extends Component { * the normal APIs. * * @private - * @method handleTechPosterChange_ */ handleTechPosterChange_() { if (!this.poster_ && this.tech_ && this.tech_.poster) { @@ -2199,7 +2078,6 @@ class Player extends Component { * * @param {Boolean} bool Set controls to showing or not * @return {Boolean} Controls are showing - * @method controls */ controls(bool) { if (bool !== undefined) { @@ -2246,7 +2124,6 @@ class Player extends Component { * @param {Boolean} bool True signals that native controls are on * @return {Player} Returns the player * @private - * @method usingNativeControls */ usingNativeControls(bool) { if (bool !== undefined) { @@ -2292,7 +2169,6 @@ class Player extends Component { * @param {*} err A MediaError or a String/Number to be turned into a MediaError * @return {MediaError|null} when getting * @return {Player} when setting - * @method error */ error(err) { if (err === undefined) { @@ -2309,12 +2185,7 @@ class Player extends Component { return this; } - // error instance - if (err instanceof MediaError) { - this.error_ = err; - } else { - this.error_ = new MediaError(err); - } + this.error_ = new MediaError(err); // add the vjs-error classname to the player this.addClass('vjs-error'); @@ -2329,42 +2200,10 @@ class Player extends Component { return this; } - /** - * Returns whether or not the player is in the "ended" state. - * - * @return {Boolean} True if the player is in the ended state, false if not. - * @method ended - */ - ended() { - return this.techGet_('ended'); - } - - /** - * Returns whether or not the player is in the "seeking" state. - * - * @return {Boolean} True if the player is in the seeking state, false if not. - * @method seeking - */ - seeking() { - return this.techGet_('seeking'); - } - - /** - * Returns the TimeRanges of the media that are currently available - * for seeking to. - * - * @return {TimeRanges} the seekable intervals of the media timeline - * @method seekable - */ - seekable() { - return this.techGet_('seekable'); - } - /** * Report user activity * * @param {Object} event Event object - * @method reportUserActivity */ reportUserActivity(event) { this.userActivity_ = true; @@ -2375,7 +2214,6 @@ class Player extends Component { * * @param {Boolean} bool Value when setting * @return {Boolean} Value if user is active user when getting - * @method userActive */ userActive(bool) { if (bool !== undefined) { @@ -2423,7 +2261,6 @@ class Player extends Component { * Listen for user activity based on timeout value * * @private - * @method listenForUserActivity_ */ listenForUserActivity_() { let mouseInProgress; @@ -2515,7 +2352,6 @@ class Player extends Component { * @param {Number} rate New playback rate to set. * @return {Number} Returns the new playback rate when setting * @return {Number} Returns the current playback rate when getting - * @method playbackRate */ playbackRate(rate) { if (rate !== undefined) { @@ -2536,7 +2372,6 @@ class Player extends Component { * @return {Boolean} Returns true if player is audio, false if not when getting * @return {Player} Returns the player if setting * @private - * @method isAudio */ isAudio(bool) { if (bool !== undefined) { @@ -2547,63 +2382,11 @@ class Player extends Component { return !!this.isAudio_; } - /** - * Returns the current state of network activity for the element, from - * the codes in the list below. - * - NETWORK_EMPTY (numeric value 0) - * The element has not yet been initialised. All attributes are in - * their initial states. - * - NETWORK_IDLE (numeric value 1) - * The element's resource selection algorithm is active and has - * selected a resource, but it is not actually using the network at - * this time. - * - NETWORK_LOADING (numeric value 2) - * The user agent is actively trying to download data. - * - NETWORK_NO_SOURCE (numeric value 3) - * The element's resource selection algorithm is active, but it has - * not yet found a resource to use. - * - * @see https://html.spec.whatwg.org/multipage/embedded-content.html#network-states - * @return {Number} the current network activity state - * @method networkState - */ - networkState() { - return this.techGet_('networkState'); - } - - /** - * Returns a value that expresses the current state of the element - * with respect to rendering the current playback position, from the - * codes in the list below. - * - HAVE_NOTHING (numeric value 0) - * No information regarding the media resource is available. - * - HAVE_METADATA (numeric value 1) - * Enough of the resource has been obtained that the duration of the - * resource is available. - * - HAVE_CURRENT_DATA (numeric value 2) - * Data for the immediate current playback position is available. - * - HAVE_FUTURE_DATA (numeric value 3) - * Data for the immediate current playback position is available, as - * well as enough data for the user agent to advance the current - * playback position in the direction of playback. - * - HAVE_ENOUGH_DATA (numeric value 4) - * The user agent estimates that enough data is available for - * playback to proceed uninterrupted. - * - * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-readystate - * @return {Number} the current playback rendering state - * @method readyState - */ - readyState() { - return this.techGet_('readyState'); - } - /** * Get a video track list * @link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist * * @return {VideoTrackList} thes current video track list - * @method videoTracks */ videoTracks() { // if we have not yet loadTech_, we create videoTracks_ @@ -2621,7 +2404,6 @@ class Player extends Component { * @link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist * * @return {AudioTrackList} thes current audio track list - * @method audioTracks */ audioTracks() { // if we have not yet loadTech_, we create videoTracks_ @@ -2634,20 +2416,19 @@ class Player extends Component { return this.tech_.audioTracks(); } - /* - * Text tracks are tracks of timed text events. - * Captions - text displayed over the video for the hearing impaired - * Subtitles - text displayed over the video for those who don't understand language in the video - * Chapters - text displayed in a menu allowing the user to jump to particular points (chapters) in the video - * Descriptions (not supported yet) - audio descriptions that are read back to the user by a screen reading device - */ + /** + * Text tracks are tracks of timed text events. + * Captions - text displayed over the video for the hearing impaired + * Subtitles - text displayed over the video for those who don't understand language in the video + * Chapters - text displayed in a menu allowing the user to jump to particular points (chapters) in the video + * Descriptions (not supported yet) - audio descriptions that are read back to the user by a screen reading device + */ /** * Get an array of associated text tracks. captions, subtitles, chapters, descriptions * http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-texttracks * * @return {Array} Array of track objects - * @method textTracks */ textTracks() { // cannot use techGet_ directly because it checks to see whether the tech is ready. @@ -2661,7 +2442,6 @@ class Player extends Component { * Get an array of remote text tracks * * @return {Array} - * @method remoteTextTracks */ remoteTextTracks() { if (this.tech_) { @@ -2673,7 +2453,6 @@ class Player extends Component { * Get an array of remote html track elements * * @return {HTMLTrackElement[]} - * @method remoteTextTrackEls */ remoteTextTrackEls() { if (this.tech_) { @@ -2689,7 +2468,6 @@ class Player extends Component { * @param {String} kind Captions, subtitles, chapters, descriptions, or metadata * @param {String=} label Optional label * @param {String=} language Optional language - * @method addTextTrack */ addTextTrack(kind, label, language) { if (this.tech_) { @@ -2701,7 +2479,6 @@ class Player extends Component { * Add a remote text track * * @param {Object} options Options for remote text track - * @method addRemoteTextTrack */ addRemoteTextTrack(options) { if (this.tech_) { @@ -2713,7 +2490,6 @@ class Player extends Component { * Remove a remote text track * * @param {Object} track Remote text track to remove - * @method removeRemoteTextTrack */ // 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 @@ -2727,7 +2503,6 @@ class Player extends Component { * Get video width * * @return {Number} Video width - * @method videoWidth */ videoWidth() { return this.tech_ && this.tech_.videoWidth && this.tech_.videoWidth() || 0; @@ -2737,7 +2512,6 @@ class Player extends Component { * Get video height * * @return {Number} Video height - * @method videoHeight */ videoHeight() { return this.tech_ && this.tech_.videoHeight && this.tech_.videoHeight() || 0; @@ -2759,7 +2533,6 @@ class Player extends Component { * @param {String} code The locale string * @return {String} The locale string when getting * @return {Player} self when setting - * @method language */ language(code) { if (code === undefined) { @@ -2776,7 +2549,6 @@ class Player extends Component { * Languages specified directly in the player options have precedence * * @return {Array} Array of languages - * @method languages */ languages() { return mergeOptions(Player.prototype.options_.languages, this.languages_); @@ -2786,7 +2558,6 @@ class Player extends Component { * Converts track info to JSON * * @return {Object} JSON object of options - * @method toJSON */ toJSON() { const options = mergeOptions(this.options_); @@ -2842,7 +2613,6 @@ class Player extends Component { * @param {Element} tag The player tag * @return {Array} An array of sources and track objects * @static - * @method getTagSettings */ static getTagSettings(tag) { const baseOptions = { @@ -2887,6 +2657,23 @@ class Player extends Component { return baseOptions; } + /** + * Determine wether or not flexbox is supported + * + * @return {Boolean} wether or not flexbox is supported + */ + flexNotSupported_() { + const 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. + return !('flexBasis' in elem.style || + 'webkitFlexBasis' in elem.style || + 'mozFlexBasis' in elem.style || + 'msFlexBasis' in elem.style || + // IE10-specific (2012 flex spec) + 'msFlexOrder' in elem.style); + } } /* @@ -2937,7 +2724,7 @@ Player.prototype.options_ = { 'textTrackSettings' ], - language: navigator.languages && navigator.languages[0] || navigator.userLanguage || navigator.language || 'en', + language: navigator && (navigator.languages && navigator.languages[0] || navigator.userLanguage || navigator.language) || 'en', // locales and their language translations languages: {}, @@ -2946,49 +2733,126 @@ 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_; // eslint-disable-line - +[ + /** + * Returns whether or not the player is in the "ended" state. + * + * @return {Boolean} True if the player is in the ended state, false if not. + * @method Player.prototype.ended + */ + 'ended', + /** + * Returns whether or not the player is in the "seeking" state. + * + * @return {Boolean} True if the player is in the seeking state, false if not. + * @method Player.prototype.seeking + */ + 'seeking', + /** + * Returns the TimeRanges of the media that are currently available + * for seeking to. + * + * @return {TimeRanges} the seekable intervals of the media timeline + * @method Player.prototype.seekable + */ + 'seekable', + /** + * Returns the current state of network activity for the element, from + * the codes in the list below. + * - NETWORK_EMPTY (numeric value 0) + * The element has not yet been initialised. All attributes are in + * their initial states. + * - NETWORK_IDLE (numeric value 1) + * The element's resource selection algorithm is active and has + * selected a resource, but it is not actually using the network at + * this time. + * - NETWORK_LOADING (numeric value 2) + * The user agent is actively trying to download data. + * - NETWORK_NO_SOURCE (numeric value 3) + * The element's resource selection algorithm is active, but it has + * not yet found a resource to use. + * + * @see https://html.spec.whatwg.org/multipage/embedded-content.html#network-states + * @return {Number} the current network activity state + * @method Player.prototype.networkState + */ + 'networkState', + /** + * Returns a value that expresses the current state of the element + * with respect to rendering the current playback position, from the + * codes in the list below. + * - HAVE_NOTHING (numeric value 0) + * No information regarding the media resource is available. + * - HAVE_METADATA (numeric value 1) + * Enough of the resource has been obtained that the duration of the + * resource is available. + * - HAVE_CURRENT_DATA (numeric value 2) + * Data for the immediate current playback position is available. + * - HAVE_FUTURE_DATA (numeric value 3) + * Data for the immediate current playback position is available, as + * well as enough data for the user agent to advance the current + * playback position in the direction of playback. + * - HAVE_ENOUGH_DATA (numeric value 4) + * The user agent estimates that enough data is available for + * playback to proceed uninterrupted. + * + * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-readystate + * @return {Number} the current playback rendering state + * @method Player.prototype.readyState + */ + 'readyState' +].forEach(function(fn) { + Player.prototype[fn] = function() { + return this.techGet_(fn); + }; +}); + +TECH_EVENTS_RETRIGGER.forEach(function(event) { + Player.prototype[`handleTech${toTitleCase(event)}_`] = function() { + return this.trigger(event); + }; +}); + +/* document methods */ /** * Fired when the player has initial duration and dimension information * * @event loadedmetadata + * @private + * @method Player.prototype.handleLoadedMetaData_ */ -Player.prototype.handleLoadedMetaData_; // eslint-disable-line /** * Fired when the player receives text data * * @event textdata + * @private + * @method Player.prototype.handleTextData_ */ -Player.prototype.handleTextData_; // eslint-disable-line /** * Fired when the player has downloaded data at the current playback position * * @event loadeddata + * @private + * @method 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 + * @private + * @method 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 + * @private + * @method Player.prototype.handleUserInactive_ */ -Player.prototype.handleUserInactive_; // eslint-disable-line /** * Fired when the current playback position has changed * @@ -2996,42 +2860,25 @@ Player.prototype.handleUserInactive_; // eslint-disable-line * playback technology in use. * * @event timeupdate + * @private + * @method Player.prototype.handleTimeUpdate_ */ -Player.prototype.handleTimeUpdate_; // eslint-disable-line - -/** - * Fired when video playback ends - * - * @event ended - */ -Player.prototype.handleTechEnded_; // eslint-disable-line /** * Fired when the volume changes * * @event volumechange + * @private + * @method Player.prototype.handleVolumeChange_ */ -Player.prototype.handleVolumeChange_; // eslint-disable-line /** * Fired when an error occurs * * @event error + * @private + * @method Player.prototype.handleError_ */ -Player.prototype.handleError_; // eslint-disable-line - -Player.prototype.flexNotSupported_ = function() { - const 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. - return !('flexBasis' in elem.style || - 'webkitFlexBasis' in elem.style || - 'mozFlexBasis' in elem.style || - 'msFlexBasis' in elem.style || - // IE10-specific (2012 flex spec) - 'msFlexOrder' in elem.style); -}; Component.registerComponent('Player', Player); export default Player; diff --git a/src/js/tech/html5.js b/src/js/tech/html5.js index afc6c53af2..ad41f3bc24 100644 --- a/src/js/tech/html5.js +++ b/src/js/tech/html5.js @@ -22,7 +22,6 @@ import toTitleCase from '../utils/to-title-case.js'; * * @param {Object=} options Object of option names and values * @param {Function=} ready Ready callback function - * @extends Tech * @class Html5 */ class Html5 extends Tech { @@ -78,25 +77,39 @@ class Html5 extends Tech { } } + // TODO: add text tracks into this list const trackTypes = ['audio', 'video']; - // ProxyNativeTextTracks + // ProxyNative Video/Audio Track trackTypes.forEach((type) => { + const elTracks = this.el()[`${type}Tracks`]; + const techTracks = this[`${type}Tracks`](); const capitalType = toTitleCase(type); - if (!this[`featuresNative${capitalType}Tracks`]) { + if (!this[`featuresNative${capitalType}Tracks`] || + !elTracks || + !elTracks.addEventListener) { return; } - const tl = this.el()[`${type}Tracks`]; - if (tl && tl.addEventListener) { - tl.addEventListener('change', Fn.bind(this, this[`handle${capitalType}TrackChange_`])); - tl.addEventListener('addtrack', Fn.bind(this, this[`handle${capitalType}TrackAdd_`])); - tl.addEventListener('removetrack', Fn.bind(this, this[`handle${capitalType}TrackRemove_`])); + this[`handle${capitalType}TrackChange_`] = (e) => { + techTracks.trigger({ + type: 'change', + target: techTracks, + currentTarget: techTracks, + srcElement: techTracks + }); + }; + this[`handle${capitalType}TrackAdd_`] = (e) => techTracks.addTrack(e.track); + this[`handle${capitalType}TrackRemove_`] = (e) => techTracks.removeTrack(e.track); - // Remove (native) trackts that are not used anymore - this.on('loadstart', this[`removeOld${capitalType}Tracks_`]); - } + elTracks.addEventListener('change', this[`handle${capitalType}TrackChange_`]); + elTracks.addEventListener('addtrack', this[`handle${capitalType}TrackAdd_`]); + elTracks.addEventListener('removetrack', this[`handle${capitalType}TrackRemove_`]); + this[`removeOld${capitalType}Tracks_`] = (e) => this.removeOldTracks_(techTracks, elTracks); + + // Remove (native) tracks that are not used anymore + this.on('loadstart', this[`removeOld${capitalType}Tracks_`]); }); if (this.featuresNativeTextTracks) { @@ -120,13 +133,15 @@ class Html5 extends Tech { this.setControls(true); } + // on iOS, we want to proxy `webkitbeginfullscreen` and `webkitendfullscreen` + // into a `fullscreenchange` event + this.proxyWebkitFullscreen_(); + this.triggerReady(); } /** * Dispose of html5 media element - * - * @method dispose */ dispose() { // Un-ProxyNativeTracks @@ -155,7 +170,6 @@ class Html5 extends Tech { * Create the component's DOM element * * @return {Element} - * @method createEl */ createEl() { let el = this.options_.tag; @@ -190,6 +204,8 @@ class Html5 extends Tech { }) ); } + + el.playerId = this.options_.playerId; } // Update specific tag settings, in case they were overridden @@ -333,44 +349,6 @@ class Html5 extends Tech { this.textTracks().removeTrack_(e.track); } - handleVideoTrackChange_(e) { - const vt = this.videoTracks(); - - this.videoTracks().trigger({ - type: 'change', - target: vt, - currentTarget: vt, - srcElement: vt - }); - } - - handleVideoTrackAdd_(e) { - this.videoTracks().addTrack_(e.track); - } - - handleVideoTrackRemove_(e) { - this.videoTracks().removeTrack_(e.track); - } - - handleAudioTrackChange_(e) { - const audioTrackList = this.audioTracks(); - - this.audioTracks().trigger({ - type: 'change', - target: audioTrackList, - currentTarget: audioTrackList, - srcElement: audioTrackList - }); - } - - handleAudioTrackAdd_(e) { - this.audioTracks().addTrack_(e.track); - } - - handleAudioTrackRemove_(e) { - this.audioTracks().removeTrack_(e.track); - } - /** * This is a helper function that is used in removeOldTextTracks_, removeOldAudioTracks_ and * removeOldVideoTracks_ @@ -417,24 +395,8 @@ class Html5 extends Tech { 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); - } - /** * Play for html5 tech - * - * @method play */ play() { const playPromise = this.el_.play(); @@ -446,40 +408,10 @@ class Html5 extends Tech { } } - /** - * Pause for html5 tech - * - * @method pause - */ - pause() { - this.el_.pause(); - } - - /** - * Paused for html5 tech - * - * @return {Boolean} - * @method paused - */ - paused() { - return this.el_.paused; - } - - /** - * Get current time - * - * @return {Number} - * @method currentTime - */ - currentTime() { - return this.el_.currentTime; - } - /** * Set current time * * @param {Number} seconds Current time of video - * @method setCurrentTime */ setCurrentTime(seconds) { try { @@ -494,89 +426,62 @@ class Html5 extends Tech { * Get duration * * @return {Number} - * @method duration */ duration() { return this.el_.duration || 0; } /** - * Get a TimeRange object that represents the intersection - * of the time ranges for which the user agent has all - * relevant media + * Get player width * - * @return {TimeRangeObject} - * @method buffered + * @return {Number} */ - buffered() { - return this.el_.buffered; + width() { + return this.el_.offsetWidth; } /** - * Get volume level + * Get player height * * @return {Number} - * @method volume */ - volume() { - return this.el_.volume; + height() { + return this.el_.offsetHeight; } /** - * Set volume level + * Proxy iOS `webkitbeginfullscreen` and `webkitendfullscreen` into + * `fullscreenchange` event * - * @param {Number} percentAsDecimal Volume percent as a decimal - * @method setVolume + * @private + * @method proxyWebkitFullscreen_ */ - setVolume(percentAsDecimal) { - this.el_.volume = percentAsDecimal; - } + proxyWebkitFullscreen_() { + if (!('webkitDisplayingFullscreen' in this.el_)) { + return; + } - /** - * Get if muted - * - * @return {Boolean} - * @method muted - */ - muted() { - return this.el_.muted; - } + const endFn = function() { + this.trigger('fullscreenchange', { isFullscreen: false }); + }; - /** - * Set muted - * - * @param {Boolean} If player is to be muted or note - * @method setMuted - */ - setMuted(muted) { - this.el_.muted = muted; - } + const beginFn = function() { + this.one('webkitendfullscreen', endFn); - /** - * Get player width - * - * @return {Number} - * @method width - */ - width() { - return this.el_.offsetWidth; - } + this.trigger('fullscreenchange', { isFullscreen: true }); + }; - /** - * Get player height - * - * @return {Number} - * @method height - */ - height() { - return this.el_.offsetHeight; + this.on('webkitbeginfullscreen', beginFn); + this.on('dispose', () => { + this.off('webkitbeginfullscreen', beginFn); + this.off('webkitendfullscreen', endFn); + }); } /** * Get if there is fullscreen support * * @return {Boolean} - * @method supportsFullScreen */ supportsFullScreen() { if (typeof this.el_.webkitEnterFullScreen === 'function') { @@ -592,22 +497,10 @@ class Html5 extends Tech { /** * Request to enter fullscreen - * - * @method enterFullScreen */ enterFullScreen() { const video = this.el_; - if ('webkitDisplayingFullscreen' in video) { - this.one('webkitbeginfullscreen', function() { - this.one('webkitendfullscreen', function() { - this.trigger('fullscreenchange', { isFullscreen: false }); - }); - - this.trigger('fullscreenchange', { isFullscreen: true }); - }); - } - if (video.paused && video.networkState <= video.HAVE_METADATA) { // attempt to prime the video element for programmatic access // this isn't necessary on the desktop but shouldn't hurt @@ -626,8 +519,6 @@ class Html5 extends Tech { /** * Request to exit fullscreen - * - * @method exitFullScreen */ exitFullScreen() { this.el_.webkitExitFullScreen(); @@ -638,7 +529,6 @@ class Html5 extends Tech { * * @param {Object=} src Source object * @return {Object} - * @method src */ src(src) { if (src === undefined) { @@ -649,30 +539,8 @@ class Html5 extends Tech { this.setSrc(src); } - /** - * Set video - * - * @param {Object} src Source object - * @deprecated - * @method setSrc - */ - setSrc(src) { - this.el_.src = src; - } - - /** - * Load media into player - * - * @method load - */ - load() { - this.el_.load(); - } - /** * Reset the tech. Removes all sources and calls `load`. - * - * @method reset */ reset() { Html5.resetMediaElement(this.el_); @@ -682,7 +550,6 @@ class Html5 extends Tech { * Get current source * * @return {Object} - * @method currentSrc */ currentSrc() { if (this.currentSource_) { @@ -691,253 +558,15 @@ class Html5 extends Tech { return this.el_.currentSrc; } - /** - * Get poster - * - * @return {String} - * @method poster - */ - poster() { - return this.el_.poster; - } - - /** - * Set poster - * - * @param {String} val URL to poster image - * @method - */ - setPoster(val) { - this.el_.poster = val; - } - - /** - * Get preload attribute - * - * @return {String} - * @method preload - */ - preload() { - return this.el_.preload; - } - - /** - * Set preload attribute - * - * @param {String} val Value for preload attribute - * @method setPreload - */ - setPreload(val) { - this.el_.preload = val; - } - - /** - * Get autoplay attribute - * - * @return {String} - * @method autoplay - */ - autoplay() { - return this.el_.autoplay; - } - - /** - * Set autoplay attribute - * - * @param {String} val Value for preload attribute - * @method setAutoplay - */ - setAutoplay(val) { - this.el_.autoplay = val; - } - - /** - * Get controls attribute - * - * @return {String} - * @method controls - */ - controls() { - return this.el_.controls; - } - /** * Set controls attribute * * @param {String} val Value for controls attribute - * @method setControls */ setControls(val) { this.el_.controls = !!val; } - /** - * Get loop attribute - * - * @return {String} - * @method loop - */ - loop() { - return this.el_.loop; - } - - /** - * Set loop attribute - * - * @param {String} val Value for loop attribute - * @method setLoop - */ - setLoop(val) { - this.el_.loop = val; - } - - /** - * Get error value - * - * @return {String} - * @method error - */ - error() { - return this.el_.error; - } - - /** - * Get whether or not the player is in the "seeking" state - * - * @return {Boolean} - * @method seeking - */ - seeking() { - return this.el_.seeking; - } - - /** - * Get a TimeRanges object that represents the - * ranges of the media resource to which it is possible - * for the user agent to seek. - * - * @return {TimeRangeObject} - * @method seekable - */ - seekable() { - return this.el_.seekable; - } - - /** - * Get if video ended - * - * @return {Boolean} - * @method ended - */ - ended() { - return this.el_.ended; - } - - /** - * Get the value of the muted content attribute - * This attribute has no dynamic effect, it only - * controls the default state of the element - * - * @return {Boolean} - * @method defaultMuted - */ - defaultMuted() { - return this.el_.defaultMuted; - } - - /** - * Get desired speed at which the media resource is to play - * - * @return {Number} - * @method playbackRate - */ - playbackRate() { - return this.el_.playbackRate; - } - - /** - * Returns a TimeRanges object that represents the ranges of the - * media resource that the user agent has played. - * @return {TimeRangeObject} the range of points on the media - * 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; - } - - /** - * Set desired speed at which the media resource is to play - * - * @param {Number} val Speed at which the media resource is to play - * @method setPlaybackRate - */ - setPlaybackRate(val) { - this.el_.playbackRate = val; - } - - /** - * Get the current state of network activity for the element, from - * the list below - * NETWORK_EMPTY (numeric value 0) - * NETWORK_IDLE (numeric value 1) - * NETWORK_LOADING (numeric value 2) - * NETWORK_NO_SOURCE (numeric value 3) - * - * @return {Number} - * @method networkState - */ - networkState() { - return this.el_.networkState; - } - - /** - * Get a value that expresses the current state of the element - * with respect to rendering the current playback position, from - * the codes in the list below - * HAVE_NOTHING (numeric value 0) - * HAVE_METADATA (numeric value 1) - * HAVE_CURRENT_DATA (numeric value 2) - * HAVE_FUTURE_DATA (numeric value 3) - * HAVE_ENOUGH_DATA (numeric value 4) - * - * @return {Number} - * @method readyState - */ - readyState() { - return this.el_.readyState; - } - - /** - * Get width of video - * - * @return {Number} - * @method videoWidth - */ - videoWidth() { - return this.el_.videoWidth; - } - - /** - * Get height of video - * - * @return {Number} - * @method videoHeight - */ - videoHeight() { - return this.el_.videoHeight; - } - - /** - * Get text tracks - * - * @return {TextTrackList} - * @method textTracks - */ - textTracks() { - return super.textTracks(); - } - /** * Creates and returns a text track object * @@ -946,7 +575,6 @@ class Html5 extends Tech { * @param {String=} label Label to identify the text track * @param {String=} language Two letter language abbreviation * @return {TextTrackObject} - * @method addTextTrack */ addTextTrack(kind, label, language) { if (!this.featuresNativeTextTracks) { @@ -962,7 +590,6 @@ class Html5 extends Tech { * @param {Object} options The object should contain values for * kind, language, label and src (location of the WebVTT file) * @return {HTMLTrackElement} - * @method addRemoteTextTrack */ addRemoteTextTrack(options = {}) { if (!this.featuresNativeTextTracks) { @@ -1003,7 +630,6 @@ class Html5 extends Tech { * Remove remote text track from TextTrackList object * * @param {TextTrackObject} track Texttrack object to remove - * @method removeRemoteTextTrack */ removeRemoteTextTrack(track) { if (!this.featuresNativeTextTracks) { @@ -1031,13 +657,13 @@ class Html5 extends Tech { /* HTML5 Support Testing ---------------------------------------------------- */ -/* -* Element for testing browser HTML5 video capabilities -* -* @type {Element} -* @constant -* @private -*/ +/** + * Element for testing browser HTML5 video capabilities + * + * @type {Element} + * @constant + * @private + */ Html5.TEST_VID = document.createElement('video'); const track = document.createElement('track'); @@ -1046,7 +672,7 @@ track.srclang = 'en'; track.label = 'English'; Html5.TEST_VID.appendChild(track); -/* +/** * Check if HTML5 video is supported by this browser/device * * @return {Boolean} @@ -1065,7 +691,7 @@ Html5.isSupported = function() { // Add Source Handler pattern functions to this tech Tech.withSourceHandlers(Html5); -/* +/** * The default native source handler. * This simply passes the source to the video element. Nothing fancy. * @@ -1074,7 +700,7 @@ Tech.withSourceHandlers(Html5); */ Html5.nativeSourceHandler = {}; -/* +/** * Check if the video element can play the given videotype * * @param {String} type The mimetype to check @@ -1090,7 +716,7 @@ Html5.nativeSourceHandler.canPlayType = function(type) { } }; -/* +/** * Check if the video element can handle the source natively * * @param {Object} source The source object @@ -1113,7 +739,7 @@ Html5.nativeSourceHandler.canHandleSource = function(source, options) { return ''; }; -/* +/** * Pass the source to the video element * Adaptive source handlers will have more complicated workflows before passing * video data to the video element @@ -1127,15 +753,15 @@ 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) -*/ + * 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() {}; // Register the native source handler Html5.registerSourceHandler(Html5.nativeSourceHandler); -/* +/** * Check if the volume can be changed in this browser/device. * Volume cannot be changed in a lot of mobile devices. * Specifically, it can't be changed from 1 on iOS. @@ -1154,7 +780,7 @@ Html5.canControlVolume = function() { } }; -/* +/** * Check if playbackRate is supported in this browser/device. * * @return {Boolean} @@ -1176,7 +802,7 @@ Html5.canControlPlaybackRate = function() { } }; -/* +/** * Check to see if native text tracks are supported by this browser/device * * @return {Boolean} @@ -1203,7 +829,7 @@ Html5.supportsNativeTextTracks = function() { return supportsTextTracks; }; -/* +/** * Check to see if native video tracks are supported by this browser/device * * @return {Boolean} @@ -1214,7 +840,7 @@ Html5.supportsNativeVideoTracks = function() { return supportsVideoTracks; }; -/* +/** * Check to see if native audio tracks are supported by this browser/device * * @return {Boolean} @@ -1256,21 +882,21 @@ Html5.Events = [ 'volumechange' ]; -/* +/** * Set the tech's volume control support status * * @type {Boolean} */ Html5.prototype.featuresVolumeControl = Html5.canControlVolume(); -/* +/** * Set the tech's playbackRate support status * * @type {Boolean} */ Html5.prototype.featuresPlaybackRate = Html5.canControlPlaybackRate(); -/* +/** * Set the tech's status on moving the video element. * In iOS, if you move a video element in the DOM, it breaks video playback. * @@ -1278,20 +904,26 @@ Html5.prototype.featuresPlaybackRate = Html5.canControlPlaybackRate(); */ 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; -/* +/** * Set the tech's progress event support status * (this disables the manual progress events of the Tech) */ Html5.prototype.featuresProgressEvents = true; -/* +/** + * Set the tech's timeupdate event support status + * (this disables the manual timeupdate events of the Tech) + */ +Html5.prototype.featuresTimeupdateEvents = true; + +/** * Sets the tech's status on native text track support * * @type {Boolean} @@ -1319,7 +951,7 @@ const mp4RE = /^video\/mp4/i; Html5.patchCanPlayType = function() { // Android 4.0 and above can play HLS to some extent but it reports being unable to do so - if (browser.ANDROID_VERSION >= 4.0) { + if (browser.ANDROID_VERSION >= 4.0 && !browser.IS_FIREFOX) { if (!canPlayType) { canPlayType = Html5.TEST_VID.constructor.prototype.canPlayType; } @@ -1418,6 +1050,270 @@ Html5.resetMediaElement = function(el) { } }; +/* Native HTML5 element property wrapping ----------------------------------- */ +// Wrap native properties with a getter +[ + /** + * Paused for html5 tech + * + * @method Html5.prototype.paused + * @return {Boolean} + */ + 'paused', + /** + * Get current time + * + * @method Html5.prototype.currentTime + * @return {Number} + */ + 'currentTime', + /** + * Get a TimeRange object that represents the intersection + * of the time ranges for which the user agent has all + * relevant media + * + * @return {TimeRangeObject} + * @method Html5.prototype.buffered + */ + 'buffered', + /** + * Get volume level + * + * @return {Number} + * @method Html5.prototype.volume + */ + 'volume', + /** + * Get if muted + * + * @return {Boolean} + * @method Html5.prototype.muted + */ + 'muted', + /** + * Get poster + * + * @return {String} + * @method Html5.prototype.poster + */ + 'poster', + /** + * Get preload attribute + * + * @return {String} + * @method Html5.prototype.preload + */ + 'preload', + /** + * Get autoplay attribute + * + * @return {String} + * @method Html5.prototype.autoplay + */ + 'autoplay', + /** + * Get controls attribute + * + * @return {String} + * @method Html5.prototype.controls + */ + 'controls', + /** + * Get loop attribute + * + * @return {String} + * @method Html5.prototype.loop + */ + 'loop', + /** + * Get error value + * + * @return {String} + * @method Html5.prototype.error + */ + 'error', + /** + * Get whether or not the player is in the "seeking" state + * + * @return {Boolean} + * @method Html5.prototype.seeking + */ + 'seeking', + /** + * Get a TimeRanges object that represents the + * ranges of the media resource to which it is possible + * for the user agent to seek. + * + * @return {TimeRangeObject} + * @method Html5.prototype.seekable + */ + 'seekable', + /** + * Get if video ended + * + * @return {Boolean} + * @method Html5.prototype.ended + */ + 'ended', + /** + * Get the value of the muted content attribute + * This attribute has no dynamic effect, it only + * controls the default state of the element + * + * @return {Boolean} + * @method Html5.prototype.defaultMuted + */ + 'defaultMuted', + /** + * Get desired speed at which the media resource is to play + * + * @return {Number} + * @method Html5.prototype.playbackRate + */ + 'playbackRate', + /** + * Returns a TimeRanges object that represents the ranges of the + * media resource that the user agent has played. + * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-played + * + * @return {TimeRangeObject} the range of points on the media + * timeline that has been reached through + * normal playback + * @method Html5.prototype.played + */ + 'played', + /** + * Get the current state of network activity for the element, from + * the list below + * - NETWORK_EMPTY (numeric value 0) + * - NETWORK_IDLE (numeric value 1) + * - NETWORK_LOADING (numeric value 2) + * - NETWORK_NO_SOURCE (numeric value 3) + * + * @return {Number} + * @method Html5.prototype.networkState + */ + 'networkState', + /** + * Get a value that expresses the current state of the element + * with respect to rendering the current playback position, from + * the codes in the list below + * - HAVE_NOTHING (numeric value 0) + * - HAVE_METADATA (numeric value 1) + * - HAVE_CURRENT_DATA (numeric value 2) + * - HAVE_FUTURE_DATA (numeric value 3) + * - HAVE_ENOUGH_DATA (numeric value 4) + * + * @return {Number} + * @method Html5.prototype.readyState + */ + 'readyState', + /** + * Get width of video + * + * @return {Number} + * @method Html5.prototype.videoWidth + */ + 'videoWidth', + /** + * Get height of video + * + * @return {Number} + * @method Html5.prototype.videoHeight + */ + 'videoHeight' +].forEach(function(prop) { + Html5.prototype[prop] = function() { + return this.el_[prop]; + }; +}); + +// Wrap native properties with a setter in this format: +// set + toTitleCase(name) +[ + /** + * Set volume level + * + * @param {Number} percentAsDecimal Volume percent as a decimal + * @method Html5.prototype.setVolume + */ + 'volume', + /** + * Set muted + * + * @param {Boolean} muted If player is to be muted or note + * @method Html5.prototype.setMuted + */ + 'muted', + /** + * Set video source + * + * @param {Object} src Source object + * @deprecated since version 5 + * @method Html5.prototype.setSrc + */ + 'src', + /** + * Set poster + * + * @param {String} val URL to poster image + * @method Html5.prototype.setPoster + */ + 'poster', + /** + * Set preload attribute + * + * @param {String} val Value for the preload attribute + * @method Htm5.prototype.setPreload + */ + 'preload', + /** + * Set autoplay attribute + * + * @param {Boolean} autoplay Value for the autoplay attribute + * @method setAutoplay + */ + 'autoplay', + /** + * Set loop attribute + * + * @param {Boolean} loop Value for the loop attribute + * @method Html5.prototype.setLoop + */ + 'loop', + /** + * Set desired speed at which the media resource is to play + * + * @param {Number} val Speed at which the media resource is to play + * @method Html5.prototype.setPlaybackRate + */ + 'playbackRate' +].forEach(function(prop) { + Html5.prototype['set' + toTitleCase(prop)] = function(v) { + this.el_[prop] = v; + }; +}); + +// wrap native functions with a function +[ + /** + * Pause for html5 tech + * + * @method Html5.prototype.pause + */ + 'pause', + /** + * Load media into player + * + * @method Html5.prototype.load + */ + 'load' +].forEach(function(prop) { + Html5.prototype[prop] = function() { + return this.el_[prop](); + }; +}); + Component.registerComponent('Html5', Html5); Tech.registerTech('Html5', Html5); export default Html5; diff --git a/src/js/tech/tech.js b/src/js/tech/tech.js index 890b4d4fc4..3e10ddfbab 100644 --- a/src/js/tech/tech.js +++ b/src/js/tech/tech.js @@ -309,11 +309,7 @@ class Tech extends Component { */ error(err) { if (err !== undefined) { - if (err instanceof MediaError) { - this.error_ = err; - } else { - this.error_ = new MediaError(err); - } + this.error_ = new MediaError(err); this.trigger('error'); } return this.error_; diff --git a/src/js/utils/dom.js b/src/js/utils/dom.js index 66fe9f597a..acb66680f7 100644 --- a/src/js/utils/dom.js +++ b/src/js/utils/dom.js @@ -67,7 +67,10 @@ function createQuerier(method) { if (isNonBlankString(context)) { context = document.querySelector(context); } - return (isEl(context) ? context : document)[method](selector); + + const ctx = isEl(context) ? context : document; + + return ctx[method] && ctx[method](selector); }; } @@ -250,10 +253,10 @@ export function removeElData(el) { * @param {String} classToCheck Classname to check */ export function hasElClass(element, classToCheck) { + throwIfWhitespace(classToCheck); if (element.classList) { return element.classList.contains(classToCheck); } - throwIfWhitespace(classToCheck); return classRegExp(classToCheck).test(element.className); } diff --git a/src/js/utils/events.js b/src/js/utils/events.js index febd27cc27..d75b96ba0a 100644 --- a/src/js/utils/events.js +++ b/src/js/utils/events.js @@ -9,6 +9,7 @@ import * as Dom from './dom.js'; import * as Guid from './guid.js'; +import log from './log.js'; import window from 'global/window'; import document from 'global/document'; @@ -248,7 +249,11 @@ export function on(elem, type, fn) { if (event.isImmediatePropagationStopped()) { break; } else { - handlersCopy[m].call(elem, event, hash); + try { + handlersCopy[m].call(elem, event, hash); + } catch (e) { + log.error(e); + } } } } diff --git a/src/js/video.js b/src/js/video.js index 92e358d16e..28638cd153 100644 --- a/src/js/video.js +++ b/src/js/video.js @@ -108,7 +108,9 @@ if (window.VIDEOJS_NO_DYNAMIC_STYLE !== true) { style = stylesheet.createStyleElement('vjs-styles-defaults'); const head = Dom.$('head'); - head.insertBefore(style, head.firstChild); + if (head) { + head.insertBefore(style, head.firstChild); + } stylesheet.setTextContent(style, ` .video-js { width: 300px; diff --git a/test/index.html b/test/index.html index 4da9b9675b..595b238bd9 100644 --- a/test/index.html +++ b/test/index.html @@ -14,6 +14,8 @@ + + diff --git a/test/karma.conf.js b/test/karma.conf.js index ba3bf6dce1..af00fe729d 100644 --- a/test/karma.conf.js +++ b/test/karma.conf.js @@ -13,6 +13,8 @@ module.exports = function(config) { '../build/temp/ie8/videojs-ie8.min.js', '../test/globals-shim.js', '../test/unit/**/*.js', + '../build/temp/browserify.js', + '../build/temp/webpack.js', { pattern: '../src/**/*.js', watched: true, included: false, served: false } ], @@ -87,10 +89,16 @@ module.exports = function(config) { } }; - if (process.env.TRAVIS) { + // Coverage reporting + // Coverage is enabled by passing the flag --coverage to npm test + var coverageFlag = process.env.npm_config_coverage; + var reportCoverage = process.env.TRAVIS || coverageFlag; + if (reportCoverage) { settings.browserify.transform.push('browserify-istanbul'); settings.reporters.push('coverage'); + } + if (process.env.TRAVIS) { if (process.env.BROWSER_STACK_USERNAME) { settings.browsers = [ 'chrome_bs', diff --git a/test/require/browserify.js b/test/require/browserify.js new file mode 100644 index 0000000000..dfcac20d6f --- /dev/null +++ b/test/require/browserify.js @@ -0,0 +1,8 @@ +/* eslint-disable no-var */ +/* eslint-env qunit */ +var videojs = require('../../'); + +QUnit.module('Browserify Require'); +QUnit.test('videojs should be requirable and bundled via browserify', function(assert) { + assert.ok(videojs, 'videojs is required properly'); +}); diff --git a/test/require/node.js b/test/require/node.js new file mode 100644 index 0000000000..01b1d481a0 --- /dev/null +++ b/test/require/node.js @@ -0,0 +1,9 @@ +/* eslint-disable no-console */ +try { + require('../../'); +} catch (e) { + console.error(e); + process.exit(1); +} + +process.exit(0); diff --git a/test/require/webpack.js b/test/require/webpack.js new file mode 100644 index 0000000000..8505d3fe52 --- /dev/null +++ b/test/require/webpack.js @@ -0,0 +1,8 @@ +/* eslint-disable no-var */ +/* eslint-env qunit */ +var videojs = require('../../'); + +QUnit.module('Webpack Require'); +QUnit.test('videojs should be requirable and bundled via webpack', function(assert) { + assert.ok(videojs, 'videojs is required properly'); +}); diff --git a/test/unit/button.test.js b/test/unit/button.test.js index 021bf6c427..9902cb4253 100644 --- a/test/unit/button.test.js +++ b/test/unit/button.test.js @@ -24,4 +24,6 @@ QUnit.test('should localize its text', function(assert) { assert.ok(el.nodeName.toLowerCase().match('button')); assert.ok(el.innerHTML.match(/vjs-control-text"?>Juego/)); assert.equal(el.getAttribute('title'), 'Juego'); + player.dispose(); + }); diff --git a/test/unit/controls.test.js b/test/unit/controls.test.js index ca4e12563f..079aa7f2fd 100644 --- a/test/unit/controls.test.js +++ b/test/unit/controls.test.js @@ -110,6 +110,7 @@ QUnit.test('should hide playback rate control if it\'s not supported', function( const playbackRate = new PlaybackRateMenuButton(player); assert.ok(playbackRate.el().className.indexOf('vjs-hidden') >= 0, 'playbackRate is not hidden'); + player.dispose(); }); QUnit.test('Fullscreen control text should be correct when fullscreenchange is triggered', function() { @@ -122,4 +123,5 @@ QUnit.test('Fullscreen control text should be correct when fullscreenchange is t player.isFullscreen(false); player.trigger('fullscreenchange'); QUnit.equal(fullscreentoggle.controlText(), 'Fullscreen', 'Control Text is correct while switching back to normal mode'); + player.dispose(); }); diff --git a/test/unit/events.test.js b/test/unit/events.test.js index f0d926cecd..cde5607643 100644 --- a/test/unit/events.test.js +++ b/test/unit/events.test.js @@ -259,3 +259,21 @@ QUnit.test('should have relatedTarget correctly set on the event', function(asse Events.trigger(el2, { type: 'click', relatedTarget: undefined }); }); + +QUnit.test('should execute remaining handlers after an exception in an event handler', function(assert) { + assert.expect(1); + + const el = document.createElement('div'); + const listener1 = function() { + throw new Error('GURU MEDITATION ERROR'); + }; + const listener2 = function() { + assert.ok(true, 'Click Triggered'); + }; + + Events.on(el, 'click', listener1); + Events.on(el, 'click', listener2); + + // 1 click + Events.trigger(el, 'click'); +}); diff --git a/test/unit/media-error.test.js b/test/unit/media-error.test.js new file mode 100644 index 0000000000..0e86973eb2 --- /dev/null +++ b/test/unit/media-error.test.js @@ -0,0 +1,69 @@ +/* eslint-env qunit */ +import window from 'global/window'; +import MediaError from '../../src/js/media-error'; + +const isModernBrowser = window.MediaError && Object.create && Object.defineProperty; + +/** + * Creates a real native MediaError object. + * + * @param {Number} code + * @param {String} [message] + * @return {MediaError} + */ +const createNativeMediaError = (code, message) => { + const err = Object.create(window.MediaError); + + Object.defineProperty(err, 'code', {value: code}); + + if (message) { + err.message = message; + } + + return err; +}; + +QUnit.module('MediaError'); + +QUnit.test('can be constructed from a number', function(assert) { + const mediaError = new MediaError(1); + + assert.strictEqual(mediaError.code, 1); + assert.strictEqual(mediaError.message, MediaError.defaultMessages['1']); +}); + +QUnit.test('can be constructed from a string', function(assert) { + const mediaError = new MediaError('hello, world'); + + assert.strictEqual(mediaError.code, 0); + assert.strictEqual(mediaError.message, 'hello, world'); +}); + +QUnit.test('can be constructed from an object', function(assert) { + const mediaError = new MediaError({code: 2}); + const mediaErrorMsg = new MediaError({code: 2, message: 'hello, world'}); + + assert.strictEqual(mediaError.code, 2); + assert.strictEqual(mediaError.message, MediaError.defaultMessages['2']); + assert.strictEqual(mediaErrorMsg.code, 2); + assert.strictEqual(mediaErrorMsg.message, 'hello, world'); +}); + +if (isModernBrowser) { + QUnit.test('can be constructed from a native MediaError object', function(assert) { + const mediaError = new MediaError(createNativeMediaError(3)); + const mediaErrorMsg = new MediaError(createNativeMediaError(4, 'hello, world')); + + assert.strictEqual(mediaError.code, 3); + assert.strictEqual(mediaError.message, MediaError.defaultMessages['3']); + assert.strictEqual(mediaErrorMsg.code, 4); + assert.strictEqual(mediaErrorMsg.message, 'hello, world'); + }); +} + +QUnit.test('can be constructed redundantly', function(assert) { + const mediaError = new MediaError(2); + const redundantMediaError = new MediaError(mediaError); + + assert.strictEqual(redundantMediaError, mediaError); +}); diff --git a/test/unit/player.test.js b/test/unit/player.test.js index 7e72387e52..efa724c3e2 100644 --- a/test/unit/player.test.js +++ b/test/unit/player.test.js @@ -188,6 +188,7 @@ QUnit.test('should set the width, height, and aspect ratio via a css class', fun // Change the aspect ratio player.aspectRatio('4:1'); assert.ok(confirmSetting('padding-top', '25%'), 'aspect ratio percent should match the newly set aspect ratio'); + player.dispose(); }); QUnit.test('should use an class name that begins with an alpha character', function(assert) { @@ -203,6 +204,8 @@ QUnit.test('should use an class name that begins with an alpha character', funct assert.ok(/\s*\.alpha1-dimensions\s*\{/.test(getStyleText(alphaPlayer.styleEl_)), 'appends -dimensions to an alpha player ID'); assert.ok(/\s*\.dimensions-1numeric\s*\{/.test(getStyleText(numericPlayer.styleEl_)), 'prepends dimensions- to a numeric player ID'); + alphaPlayer.dispose(); + numericPlayer.dispose(); }); QUnit.test('should wrap the original tag in the player div', function(assert) { @@ -265,6 +268,7 @@ QUnit.test('should hide the poster when play is called', function(assert) { player.tech_.trigger('play'); assert.equal(player.hasStarted(), true, 'the show poster flag is false after play'); + player.dispose(); }); QUnit.test('should load a media controller', function(assert) { @@ -482,6 +486,7 @@ QUnit.test('should register players with generated ids', function(assert) { assert.equal(player.el().id, player.id(), 'the player and element ids are equal'); assert.ok(Player.players[id], 'the generated id is registered'); + player.dispose(); }); QUnit.test('should not add multiple first play events despite subsequent loads', function(assert) { @@ -497,6 +502,7 @@ QUnit.test('should not add multiple first play events despite subsequent loads', player.tech_.trigger('loadstart'); player.tech_.trigger('loadstart'); player.tech_.trigger('play'); + player.dispose(); }); QUnit.test('should fire firstplay after resetting the player', function(assert) { @@ -529,6 +535,7 @@ QUnit.test('should fire firstplay after resetting the player', function(assert) player.tech_.trigger('loadstart'); // player.tech_.trigger('play'); assert.ok(fpFired, 'Third firstplay fired'); + player.dispose(); }); QUnit.test('should remove vjs-has-started class', function(assert) { @@ -544,7 +551,9 @@ QUnit.test('should remove vjs-has-started class', function(assert) { assert.ok(player.el().className.indexOf('vjs-has-started') === -1, 'vjs-has-started class removed'); player.tech_.trigger('play'); + assert.ok(player.el().className.indexOf('vjs-has-started') !== -1, 'vjs-has-started class added again'); + player.dispose(); }); QUnit.test('should add and remove vjs-ended class', function(assert) { @@ -565,6 +574,7 @@ QUnit.test('should add and remove vjs-ended class', function(assert) { player.tech_.trigger('loadstart'); assert.ok(player.el().className.indexOf('vjs-ended') === -1, 'vjs-ended class removed'); + player.dispose(); }); QUnit.test('player should handle different error types', function(assert) { @@ -615,6 +625,8 @@ QUnit.test('player should handle different error types', function(assert) { // restore error logging log.error.restore(); + + player.dispose(); }); QUnit.test('Data attributes on the video element should persist in the new wrapper element', function(assert) { @@ -627,6 +639,8 @@ QUnit.test('Data attributes on the video element should persist in the new wrapp const player = TestHelpers.makePlayer({}, tag); assert.equal(player.el().getAttribute('data-id'), dataId, 'data-id should be available on the new player element after creation'); + + player.dispose(); }); QUnit.test('should restore attributes from the original video tag when creating a new element', function(assert) { @@ -663,6 +677,7 @@ QUnit.test('should honor default inactivity timeout', function(assert) { assert.equal(player.userActive(), false, 'User is inactive after timeout expired'); clock.restore(); + player.dispose(); }); QUnit.test('should honor configured inactivity timeout', function(assert) { @@ -681,6 +696,7 @@ QUnit.test('should honor configured inactivity timeout', function(assert) { assert.equal(player.userActive(), false, 'User is inactive after timeout expired'); clock.restore(); + player.dispose(); }); QUnit.test('should honor disabled inactivity timeout', function(assert) { @@ -696,6 +712,7 @@ QUnit.test('should honor disabled inactivity timeout', function(assert) { assert.equal(player.userActive(), true, 'User is still active'); clock.restore(); + player.dispose(); }); QUnit.test('should clear pending errors on disposal', function(assert) { @@ -729,6 +746,7 @@ QUnit.test('pause is called when player ended event is fired and player is not p }; player.tech_.trigger('ended'); assert.equal(pauses, 1, 'pause was called'); + player.dispose(); }); QUnit.test('pause is not called if the player is paused and ended is fired', function(assert) { @@ -743,7 +761,9 @@ QUnit.test('pause is not called if the player is paused and ended is fired', fun pauses++; }; player.tech_.trigger('ended'); + assert.equal(pauses, 0, 'pause was not called when ended fired'); + player.dispose(); }); QUnit.test('should add an audio class if an audio el is used', function(assert) { @@ -752,6 +772,7 @@ QUnit.test('should add an audio class if an audio el is used', function(assert) const audioClass = 'vjs-audio'; assert.ok(player.el().className.indexOf(audioClass) !== -1, 'added ' + audioClass + ' css class'); + player.dispose(); }); QUnit.test('should add a video player region if a video el is used', function(assert) { @@ -760,6 +781,7 @@ QUnit.test('should add a video player region if a video el is used', function(as assert.ok(player.el().getAttribute('role') === 'region', 'region role is present'); assert.ok(player.el().getAttribute('aria-label') === 'video player', 'video player label present'); + player.dispose(); }); QUnit.test('should add an audio player region if an audio el is used', function(assert) { @@ -768,6 +790,7 @@ QUnit.test('should add an audio player region if an audio el is used', function( assert.ok(player.el().getAttribute('role') === 'region', 'region role is present'); assert.ok(player.el().getAttribute('aria-label') === 'audio player', 'audio player label present'); + player.dispose(); }); QUnit.test('should not be scrubbing while not seeking', function(assert) { @@ -776,7 +799,9 @@ QUnit.test('should not be scrubbing while not seeking', function(assert) { assert.equal(player.scrubbing(), false, 'player is not scrubbing'); assert.ok(player.el().className.indexOf('scrubbing') === -1, 'scrubbing class is not present'); player.scrubbing(false); + assert.equal(player.scrubbing(), false, 'player is not scrubbing'); + player.dispose(); }); QUnit.test('should be scrubbing while seeking', function(assert) { @@ -785,6 +810,7 @@ QUnit.test('should be scrubbing while seeking', function(assert) { player.scrubbing(true); assert.equal(player.scrubbing(), true, 'player is scrubbing'); assert.ok(player.el().className.indexOf('scrubbing') !== -1, 'scrubbing class is present'); + player.dispose(); }); QUnit.test('should throw on startup no techs are specified', function(assert) { @@ -827,6 +853,9 @@ QUnit.test('should have a sensible toJSON that is equivalent to player.options', popts.tracks[0].player = undefined; assert.deepEqual(player2.toJSON(), popts, 'no circular references'); + + player.dispose(); + player2.dispose(); }); QUnit.test('should ignore case in language codes and try primary code', function(assert) { @@ -849,6 +878,7 @@ QUnit.test('should ignore case in language codes and try primary code', function assert.strictEqual(player.localize('Error'), 'Problem', 'Used primary code localisation'); player.language('en-GB'); assert.strictEqual(player.localize('Good'), 'Brilliant', 'Ignored case'); + player.dispose(); }); QUnit.test('inherits language from parent element', function(assert) { @@ -889,8 +919,10 @@ QUnit.test('createModal()', function(assert) { assert.strictEqual(modal.content(), 'foo', 'content is set properly'); assert.ok(modal.opened(), 'modal is opened by default'); modal.close(); + assert.ok(spy.called, 'modal was disposed when closed'); assert.strictEqual(player.children().indexOf(modal), -1, 'modal was removed from player\'s children'); + player.dispose(); }); QUnit.test('createModal() options object', function(assert) { @@ -901,6 +933,7 @@ QUnit.test('createModal() options object', function(assert) { assert.strictEqual(modal.content(), 'foo', 'content argument takes precedence'); assert.strictEqual(modal.options_.label, 'boo', 'modal options are set properly'); modal.close(); + player.dispose(); }); QUnit.test('you can clear error in the error event', function(assert) { @@ -919,6 +952,7 @@ QUnit.test('you can clear error in the error event', function(assert) { assert.ok(!player.error(), 'we no longer have an error'); log.error.restore(); + player.dispose(); }); QUnit.test('Player#tech will return tech given the appropriate input', function(assert) { @@ -1004,6 +1038,7 @@ QUnit.test('Remove waiting class on timeupdate after tech waiting', function(ass assert.ok(/vjs-waiting/.test(player.el().className), 'vjs-waiting is added to the player el on tech waiting'); player.trigger('timeupdate'); assert.ok(!(/vjs-waiting/).test(player.el().className), 'vjs-waiting is removed from the player el on timeupdate'); + player.dispose(); }); QUnit.test('Make sure that player\'s style el respects VIDEOJS_NO_DYNAMIC_STYLE option', function(assert) { @@ -1064,6 +1099,8 @@ QUnit.test('When VIDEOJS_NO_DYNAMIC_STYLE is set, apply sizing directly to the t player.width(600); player.height(300); + assert.equal(player.tech_.el().width, 600, 'the width is equal to 600'); assert.equal(player.tech_.el().height, 300, 'the height is equal 300'); + player.dispose(); }); diff --git a/test/unit/setup.test.js b/test/unit/setup.test.js index 4da7ee3356..38efb033d0 100644 --- a/test/unit/setup.test.js +++ b/test/unit/setup.test.js @@ -14,4 +14,5 @@ QUnit.test('should set options from data-setup even if autoSetup is not called b assert.ok(player.options_.controls === true); assert.ok(player.options_.autoplay === false); assert.ok(player.options_.preload === 'auto'); + player.dispose(); }); diff --git a/test/unit/tech/html5.test.js b/test/unit/tech/html5.test.js index 63f058a2c4..270d879f7a 100644 --- a/test/unit/tech/html5.test.js +++ b/test/unit/tech/html5.test.js @@ -110,10 +110,12 @@ QUnit.test('patchCanPlayType patches canplaytype with our function, conditionall Html5.unpatchCanPlayType(); const oldAV = browser.ANDROID_VERSION; + const oldIsFirefox = browser.IS_FIREFOX; const video = document.createElement('video'); const canPlayType = Html5.TEST_VID.constructor.prototype.canPlayType; browser.ANDROID_VERSION = 4.0; + browser.IS_FIREFOX = false; Html5.patchCanPlayType(); assert.notStrictEqual(video.canPlayType, @@ -131,14 +133,39 @@ QUnit.test('patchCanPlayType patches canplaytype with our function, conditionall 'patched canPlayType and function returned from unpatch are equal'); browser.ANDROID_VERSION = oldAV; + browser.IS_FIREFOX = oldIsFirefox; + Html5.unpatchCanPlayType(); +}); + +QUnit.test('patchCanPlayType doesn\'t patch canplaytype with our function in Firefox for Android', function(assert) { + // the patch runs automatically so we need to first unpatch + Html5.unpatchCanPlayType(); + + const oldAV = browser.ANDROID_VERSION; + const oldIsFirefox = browser.IS_FIREFOX; + const video = document.createElement('video'); + const canPlayType = Html5.TEST_VID.constructor.prototype.canPlayType; + + browser.ANDROID_VERSION = 4.0; + browser.IS_FIREFOX = true; + Html5.patchCanPlayType(); + + assert.strictEqual(video.canPlayType, + canPlayType, + 'original canPlayType and patched canPlayType should be equal'); + + browser.ANDROID_VERSION = oldAV; + browser.IS_FIREFOX = oldIsFirefox; Html5.unpatchCanPlayType(); }); QUnit.test('should return maybe for HLS urls on Android 4.0 or above', function(assert) { const oldAV = browser.ANDROID_VERSION; + const oldIsFirefox = browser.IS_FIREFOX; const video = document.createElement('video'); browser.ANDROID_VERSION = 4.0; + browser.IS_FIREFOX = false; Html5.patchCanPlayType(); assert.strictEqual(video.canPlayType('application/x-mpegurl'), @@ -157,6 +184,7 @@ QUnit.test('should return maybe for HLS urls on Android 4.0 or above', function( 'maybe for vnd.apple.mpegurl'); browser.ANDROID_VERSION = oldAV; + browser.IS_FIREFOX = oldIsFirefox; Html5.unpatchCanPlayType(); }); diff --git a/test/unit/tracks/audio-tracks.test.js b/test/unit/tracks/audio-tracks.test.js index 8882997bc0..49b23c4ded 100644 --- a/test/unit/tracks/audio-tracks.test.js +++ b/test/unit/tracks/audio-tracks.test.js @@ -106,4 +106,5 @@ QUnit.test('when switching techs, we should not get a new audio track', function const secondTracks = player.audioTracks(); assert.ok(firstTracks === secondTracks, 'the tracks are equal'); + player.dispose(); }); diff --git a/test/unit/tracks/text-tracks.test.js b/test/unit/tracks/text-tracks.test.js index c575d09a85..fd7094c02c 100644 --- a/test/unit/tracks/text-tracks.test.js +++ b/test/unit/tracks/text-tracks.test.js @@ -293,6 +293,8 @@ QUnit.test('when switching techs, we should not get a new text track', function( const secondTracks = player.textTracks(); assert.ok(firstTracks === secondTracks, 'the tracks are equal'); + + player.dispose(); }); if (Html5.supportsNativeTextTracks()) { @@ -407,6 +409,7 @@ QUnit.test('removes cuechange event when text track is hidden for emulated track player.tech_.trigger('timeupdate'); assert.equal(numTextTrackChanges, 4, 'texttrackchange should be not be called since mode is hidden'); + player.dispose(); }); QUnit.test('should return correct remote text track values', function(assert) { @@ -506,6 +509,8 @@ QUnit.test('default text tracks should show by default', function(assert) { assert.equal(tracks[0].kind, 'captions', 'the captions track is present'); assert.equal(tracks[0].mode, 'showing', 'the captions track is showing'); + + player.dispose(); }); QUnit.test('default captions take precedence over default descriptions', function(assert) { @@ -536,6 +541,7 @@ QUnit.test('default captions take precedence over default descriptions', functio assert.equal(tracks[0].mode, 'disabled', 'the descriptions track is disabled'); assert.equal(tracks[1].kind, 'captions', 'the captions track is second'); assert.equal(tracks[1].mode, 'showing', 'the captions track is showing'); + player.dispose(); }); QUnit.test('removeRemoteTextTrack should be able to take both a track and the response from addRemoteTextTrack', function(assert) { @@ -564,4 +570,5 @@ QUnit.test('removeRemoteTextTrack should be able to take both a track and the re assert.equal(player.remoteTextTrackEls().length, 0, 'the track element was removed correctly'); + player.dispose(); }); diff --git a/test/unit/tracks/video-tracks.test.js b/test/unit/tracks/video-tracks.test.js index 503f9a8dfe..fc2c35b3ae 100644 --- a/test/unit/tracks/video-tracks.test.js +++ b/test/unit/tracks/video-tracks.test.js @@ -106,4 +106,6 @@ QUnit.test('when switching techs, we should not get a new video track', function const secondTracks = player.videoTracks(); assert.ok(firstTracks === secondTracks, 'the tracks are equal'); + + player.dispose(); }); diff --git a/test/unit/video.test.js b/test/unit/video.test.js index 96ab15fe7a..0ba89af735 100644 --- a/test/unit/video.test.js +++ b/test/unit/video.test.js @@ -129,6 +129,7 @@ QUnit.test('should expose plugin registry function', function(assert) { assert.ok(player.foo, 'should exist'); assert.equal(player.foo, pluginFunction, 'should be equal'); + player.dispose(); }); QUnit.test('should expose options and players properties for backward-compatibility', function(assert) { From 6c1a5d713735f180f380446f2f9d8d8ea913a152 Mon Sep 17 00:00:00 2001 From: Zoltan Date: Tue, 4 Oct 2016 10:36:05 +0200 Subject: [PATCH 10/11] Comments and refinements --- src/js/control-bar/text-track-controls/chapters-button.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/js/control-bar/text-track-controls/chapters-button.js b/src/js/control-bar/text-track-controls/chapters-button.js index 25d71e0ecf..a6e1b4da33 100644 --- a/src/js/control-bar/text-track-controls/chapters-button.js +++ b/src/js/control-bar/text-track-controls/chapters-button.js @@ -35,7 +35,7 @@ class ChaptersButton extends TextTrackButton { } update(event) { - if (this.track_ === undefined || (event && (event.type === 'addtrack' || event.type === 'removetrack'))) { + if (!this.track_ || (event && (event.type === 'addtrack' || event.type === 'removetrack'))) { this.setTrack(this.findChaptersTrack()); } super.update(); @@ -50,6 +50,7 @@ class ChaptersButton extends TextTrackButton { this.updateHandler_ = this.update.bind(this); } + // here this.track_ refers to the old track instance if (this.track_) { const remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(this.track_); @@ -62,6 +63,7 @@ class ChaptersButton extends TextTrackButton { this.track_ = track; + // here this.track_ refers to the new track instance if (this.track_) { this.track_.mode = 'hidden'; From 6e3a8af1684158633a6e4520cf1cac0065e44b79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Tam=C3=A1si?= Date: Tue, 15 Nov 2016 08:56:07 +0100 Subject: [PATCH 11/11] Fixed duplicate grunt-accessibility --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 0cd691ff60..a8d8db311d 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,6 @@ "grunt": "^0.4.4", "grunt-accessibility": "^5.0.0", "grunt-babel": "^6.0.0", - "grunt-accessibility": "^4.1.0", "grunt-banner": "^0.4.0", "grunt-browserify": "3.5.1", "grunt-cli": "~0.1.13",