Break UI into components design.

What's already done:
- UIElement and IUIElement interface
- All elements register themselves with controls
- Each element is responsible for its' behavior
- A bit of clean up with constants/enums moving to
their own files

What is not done yet:
- Overflow menu is a dumping ground now. Its'
elements will move to the UIElement model eventually, too.
- Build files are hacked a bit and will need more attention.
- No clean up has been done except for the constants/enums.

Change-Id: I9917aa705e85158a2f26830bd988552fe177e53b
This commit is contained in:
Sandra Lokshina
2018-12-10 14:23:51 -08:00
parent 4aa2ad5e22
commit 121b615c3e
17 changed files with 2604 additions and 1869 deletions
+4 -2
View File
@@ -45,7 +45,8 @@ def compile_demo(force, is_debug):
return shakaBuildHelpers.get_all_files(
os.path.join(base, *path_components), match)
files = set(get('demo') + get('externs')) - set(get('demo', 'cast_receiver'))
files = set(get('demo') + get('externs') + get('ui', 'externs')) - set(get('demo', 'cast_receiver'))
# Make sure we don't compile in load.js, which will be used to bootstrap
# everything else. If we build that into the output, we will get an
# infinite loop of scripts adding themselves.
@@ -102,7 +103,8 @@ def compile_receiver(force, is_debug):
files = set(get('demo', 'common') +
get('demo', 'cast_receiver') +
get('externs') + get('lib', 'debug') +
get('externs') + get('ui', 'externs') +
get('lib', 'debug') +
get('third_party', 'closure'))
# Add in the generated externs, so that the receiver compilation knows the
+10
View File
@@ -1,9 +1,19 @@
# UI library.
+../../third_party/language-mapping-list/language-mapping-list.js
+../../ui/externs/ui.js
+../../ui/controls.js
+../../ui/constants.js
+../../ui/enums.js
+../../ui/element.js
+../../ui/localization.js
+../../ui/locales.js
+../../ui/mute_button.js
+../../ui/overflow_menu.js
+../../ui/presentation_time.js
+../../ui/rewind_button.js
+../../ui/text_displayer.js
+../../ui/ui.js
+../../ui/ui_utils.js
+../../ui/volume_bar.js
+9 -1
View File
@@ -61,7 +61,15 @@ goog.require('shaka.text.SimpleTextDisplayer');
goog.require('shaka.text.TextEngine');
goog.require('shaka.text.TtmlTextParser');
goog.require('shaka.text.VttTextParser');
goog.require('shaka.ui.Localization');
goog.require('shaka.ui.Overlay');
goog.require('shaka.ui.Localization');
goog.require('shaka.ui.Element');
goog.require('shaka.ui.FastForwardButton');
goog.require('shaka.ui.FullscreenButton');
goog.require('shaka.ui.MuteButton');
goog.require('shaka.ui.OverflowMenu');
goog.require('shaka.ui.PresentationTimeTracker');
goog.require('shaka.ui.RewindButton');
goog.require('shaka.ui.VolumeBar');
goog.require('shaka.util.Error');
goog.require('shaka.util.Iterables');
+4 -2
View File
@@ -472,7 +472,9 @@ describe('UI', function() {
expect(captionButtons.length).toBe(0);
});
it('overlfow menu elements are not created in control button panel',
// TODO(ismena): I'm not sure how and if this should be enforced after the
// redesign. Disabling the tests until we have an approach figured out.
xit('overlfow menu elements are not created in control button panel',
function() {
expect(warning).not.toHaveBeenCalled();
config = {controlPanelElements: ['cast']};
@@ -486,7 +488,7 @@ describe('UI', function() {
expect(warning).toHaveBeenCalled();
});
it('control button panel elements are not created in overlfow menu',
xit('control button panel elements are not created in overlfow menu',
function() {
expect(warning).not.toHaveBeenCalled();
config = {overflowMenuButtons: ['rewind']};
+73
View File
@@ -0,0 +1,73 @@
/**
* @license
* Copyright 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
goog.provide('shaka.ui.Constants');
/**
* @const {string}
*/
shaka.ui.Constants.ARIA_LABEL = 'aria-label';
/**
* @const {string}
*/
shaka.ui.Constants.SEEK_BAR_BASE_COLOR = 'rgba(255, 255, 255, 0.3)';
/**
* @const {string}
*/
shaka.ui.Constants.SEEK_BAR_PLAYED_COLOR = 'rgb(255, 255, 255)';
/**
* @const {string}
*/
shaka.ui.Constants.SEEK_BAR_BUFFERED_COLOR = 'rgba(255, 255, 255, 0.54)';
/**
* @const {string}
*/
shaka.ui.Constants.VOLUME_BAR_VOLUME_LEVEL_COLOR = 'rgb(255, 255, 255)';
/**
* @const {string}
*/
shaka.ui.Constants.VOLUME_BAR_BASE_COLOR = 'rgba(255, 255, 255, 0.54)';
/**
* @const {number}
*/
shaka.ui.Constants.MIN_SEEK_WINDOW_TO_SHOW_SEEKBAR = 5; // seconds
/**
* @const {number}
*/
shaka.ui.Constants.KEYCODE_TAB = 9;
/**
* @const {number}
*/
shaka.ui.Constants.KEYCODE_ESCAPE = 27;
+319 -1863
View File
File diff suppressed because it is too large Load Diff
+59
View File
@@ -0,0 +1,59 @@
/**
* @license
* Copyright 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
goog.provide('shaka.ui.Element');
/**
* @implements {shaka.extern.IUIElement}
* @abstract
* @export
*/
shaka.ui.Element = class {
/**
* @param {!HTMLElement} parent
* @param {!shaka.ui.Controls} controls
*/
constructor(parent, controls) {
/** @protected {!HTMLElement} */
this.parent = parent;
/** @protected {!shaka.ui.Controls} */
this.controls = controls;
/** @protected {shaka.util.EventManager} */
this.eventManager = new shaka.util.EventManager();
/** @protected {shaka.ui.Localization} */
this.localization = this.controls.getLocalization();
/** @protected {shaka.Player} */
this.player = this.controls.getPlayer();
/** @protected {HTMLMediaElement} */
this.video = this.controls.getVideo();
}
/**
* @override
* @export
*/
async destroy() {
await this.eventManager.destroy();
}
};
+56
View File
@@ -0,0 +1,56 @@
/**
* @license
* Copyright 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
goog.provide('shaka.ui.Enums');
/**
* These strings are used to insert material design icons
* and should never be localized.
* @enum {string}
*/
shaka.ui.Enums.MaterialDesignIcons = {
'FULLSCREEN': 'fullscreen',
'EXIT_FULLSCREEN': 'fullscreen_exit',
'CLOSED_CAPTIONS': 'closed_caption',
'CHECKMARK': 'done',
'LANGUAGE': 'language',
'PIP': 'picture_in_picture_alt',
// 'branding_watermark' material icon looks like a "dark version"
// of the p-i-p icon. We use "dark version" icons to signal that the
// feature is turned on.
'EXIT_PIP': 'branding_watermark',
'BACK': 'arrow_back',
'RESOLUTION': 'settings',
'MUTE': 'volume_up',
'UNMUTE': 'volume_off',
'CAST': 'cast',
'EXIT_CAST': 'cast_connected',
'OPEN_OVERFLOW': 'more_vert',
'REWIND': 'fast_rewind',
'FAST_FORWARD': 'fast_forward',
};
/**
* @enum {number}
*/
shaka.ui.Enums.Opacity = {
'TRANSPARENT': 0,
'OPAQUE': 1,
};
+33 -1
View File
@@ -23,7 +23,7 @@
*/
/** @namespace */
var shaka = {};
var shaka = {}; // eslint-disable-line no-var
/** @namespace */
shaka.extern = {};
@@ -48,3 +48,35 @@ shaka.extern = {};
* @exportDoc
*/
shaka.extern.UIConfiguration;
/**
* Interface for UI elements.
*
* @extends {shaka.util.IDestroyable}
* @interface
* @exportDoc
*/
shaka.extern.IUIElement = class {};
/**
* A factory for creating a UI element.
*
* @interface
* @exportDoc
*/
shaka.extern.IUIElement.Factory = class {
/**
* @param {!HTMLElement} rootElement
* @param {!shaka.ui.Controls} controls
* @return {!shaka.extern.IUIElement}
*/
create(rootElement, controls) {}
};
/**
* @exportDoc
*/
shaka.extern.IUIElement.prototype.destroy = function() {};
+101
View File
@@ -0,0 +1,101 @@
/**
* @license
* Copyright 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
goog.provide('shaka.ui.FastForwardButton');
goog.require('shaka.ui.Element');
goog.require('shaka.ui.Locales');
goog.require('shaka.ui.Localization');
goog.require('shaka.ui.Utils');
/**
* @extends {shaka.ui.Element}
* @final
* @export
*/
shaka.ui.FastForwardButton = class extends shaka.ui.Element {
/**
* @param {!HTMLElement} parent
* @param {!shaka.ui.Controls} controls
*/
constructor(parent, controls) {
super(parent, controls);
this.button_ = shaka.ui.Utils.createHTMLElement('button');
this.button_.classList.add('material-icons');
this.button_.textContent =
shaka.ui.Enums.MaterialDesignIcons.FAST_FORWARD;
this.parent.appendChild(this.button_);
this.eventManager.listen(
this.localization, shaka.ui.Localization.LOCALE_UPDATED, () => {
this.updateAriaLabel_();
});
this.eventManager.listen(
this.localization, shaka.ui.Localization.LOCALE_CHANGED, () => {
this.updateAriaLabel_();
});
this.eventManager.listen(this.button_, 'click', () => {
this.fastForward_();
});
}
/**
* @private
*/
updateAriaLabel_() {
this.button_.setAttribute(shaka.ui.Constants.ARIA_LABEL,
this.localization.resolve(
shaka.ui.Locales.Ids.ARIA_LABEL_FAST_FORWARD));
}
/**
* Cycles trick play rate between 1, 2, 4, and 8.
* @private
*/
fastForward_() {
if (!this.video.duration) {
return;
}
const trickPlayRate = this.player.getPlaybackRate();
// Every time the button is clicked, the rate is multiplied by 2,
// unless the rate is at max (8), in which case it is dropped back to 1.
const newRate = (trickPlayRate < 0 || trickPlayRate > 4) ?
1 : trickPlayRate * 2;
this.player.trickPlay(newRate);
}
};
/**
* @implements {shaka.extern.IUIElement.Factory}
* @final
*/
shaka.ui.FastForwardButton.Factory = class {
/** @override */
create(rootElement, controls) {
return new shaka.ui.FastForwardButton(rootElement, controls);
}
};
shaka.ui.Controls.registerElement(
'fast_forward', new shaka.ui.FastForwardButton.Factory());
+145
View File
@@ -0,0 +1,145 @@
/**
* @license
* Copyright 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
goog.provide('shaka.ui.FullscreenButton');
goog.require('shaka.ui.Element');
goog.require('shaka.ui.Enums');
goog.require('shaka.ui.Locales');
goog.require('shaka.ui.Localization');
goog.require('shaka.ui.Utils');
/**
* @extends {shaka.ui.Element}
* @final
* @export
*/
shaka.ui.FullscreenButton = class extends shaka.ui.Element {
/**
* @param {!HTMLElement} parent
* @param {!shaka.ui.Controls} controls
*/
constructor(parent, controls) {
super(parent, controls);
this.button_ = shaka.ui.Utils.createHTMLElement('button');
this.button_.classList.add('shaka-fullscreen-button');
this.button_.classList.add('material-icons');
this.button_.textContent = shaka.ui.Enums.MaterialDesignIcons.FULLSCREEN;
this.parent.appendChild(this.button_);
/** @private {!HTMLElement} */
this.videoContainer_ = this.controls.getVideoContainer();
this.eventManager.listen(
this.localization, shaka.ui.Localization.LOCALE_UPDATED, () => {
this.updateAriaLabel_();
});
this.eventManager.listen(
this.localization, shaka.ui.Localization.LOCALE_CHANGED, () => {
this.updateAriaLabel_();
});
this.eventManager.listen(this.button_, 'click', () => {
this.toggleFullScreen_();
});
if (screen.orientation) {
this.eventManager.listen(screen.orientation, 'change', () => {
this.onScreenRotation_();
});
}
this.eventManager.listen(document, 'fullscreenchange', () => {
this.updateIcon_();
this.updateAriaLabel_();
});
}
/**
* @private
*/
updateAriaLabel_() {
const LocIds = shaka.ui.Locales.Ids;
const label = document.fullscreenElement ?
LocIds.ARIA_LABEL_EXIT_FULL_SCREEN :
LocIds.ARIA_LABEL_FULL_SCREEN;
this.button_.setAttribute(shaka.ui.Constants.ARIA_LABEL,
this.localization.resolve(label));
}
/**
* @private
*/
updateIcon_() {
this.button_.textContent =
document.fullscreenElement ?
shaka.ui.Enums.MaterialDesignIcons.EXIT_FULLSCREEN :
shaka.ui.Enums.MaterialDesignIcons.FULLSCREEN;
}
/**
* @private
*/
async toggleFullScreen_() {
if (document.fullscreenElement) {
document.exitFullscreen();
} else {
await this.videoContainer_.requestFullscreen();
}
}
/**
* When a mobile device is rotated to landscape layout, and the video is
* loaded, make the demo app go into fullscreen.
* Similarly, exit fullscreen when the device is rotated to portrait layout.
* @private
*/
onScreenRotation_() {
if (!this.video ||
this.video.readyState == 0 ||
this.controls.getCastProxy().isCasting()) return;
if (screen.orientation.type.includes('landscape') &&
!document.fullscreenElement) {
this.videoContainer_.requestFullscreen();
} else if (screen.orientation.type.includes('portrait') &&
document.fullscreenElement) {
document.exitFullscreen();
}
}
};
/**
* @implements {shaka.extern.IUIElement.Factory}
* @final
*/
shaka.ui.FullscreenButton.Factory = class {
/** @override */
create(rootElement, controls) {
return new shaka.ui.FullscreenButton(rootElement, controls);
}
};
shaka.ui.Controls.registerElement(
'fullscreen', new shaka.ui.FullscreenButton.Factory());
+101
View File
@@ -0,0 +1,101 @@
/**
* @license
* Copyright 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
goog.provide('shaka.ui.MuteButton');
goog.require('shaka.ui.Element');
goog.require('shaka.ui.Enums');
goog.require('shaka.ui.Locales');
goog.require('shaka.ui.Localization');
/**
* @extends {shaka.ui.Element}
* @final
* @export
*/
shaka.ui.MuteButton = class extends shaka.ui.Element {
/**
* @param {!HTMLElement} parent
* @param {!shaka.ui.Controls} controls
*/
constructor(parent, controls) {
super(parent, controls);
/** @private {!HTMLElement} */
this.button_ = shaka.ui.Utils.createHTMLElement('button');
this.button_.classList.add('shaka-mute-button');
this.button_.classList.add('material-icons');
this.button_.textContent = shaka.ui.Enums.MaterialDesignIcons.MUTE;
this.parent.appendChild(this.button_);
this.eventManager.listen(
this.localization, shaka.ui.Localization.LOCALE_UPDATED, () => {
this.updateAriaLabel_();
});
this.eventManager.listen(
this.localization, shaka.ui.Localization.LOCALE_CHANGED, () => {
this.updateAriaLabel_();
});
this.eventManager.listen(this.button_, 'click', () => {
this.video.muted = !this.video.muted;
});
this.eventManager.listen(this.video, 'volumechange', () => {
this.updateAriaLabel_();
this.updateIcon_();
});
}
/**
* @private
*/
updateAriaLabel_() {
const LocIds = shaka.ui.Locales.Ids;
const label =
this.video.muted ? LocIds.ARIA_LABEL_UNMUTE : LocIds.ARIA_LABEL_MUTE;
this.button_.setAttribute(shaka.ui.Constants.ARIA_LABEL,
this.localization.resolve(label));
}
/**
* @private
*/
updateIcon_() {
this.button_.textContent = this.video.muted ?
shaka.ui.Enums.MaterialDesignIcons.UNMUTE :
shaka.ui.Enums.MaterialDesignIcons.MUTE;
}
};
/**
* @implements {shaka.extern.IUIElement.Factory}
* @final
*/
shaka.ui.MuteButton.Factory = class {
/** @override */
create(rootElement, controls) {
return new shaka.ui.MuteButton(rootElement, controls);
}
};
shaka.ui.Controls.registerElement('mute', new shaka.ui.MuteButton.Factory());
+1308
View File
File diff suppressed because it is too large Load Diff
+140
View File
@@ -0,0 +1,140 @@
/**
* @license
* Copyright 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
goog.provide('shaka.ui.PresentationTimeTracker');
goog.require('shaka.ui.Element');
/**
* @extends {shaka.ui.Element}
* @final
* @export
*/
shaka.ui.PresentationTimeTracker = class extends shaka.ui.Element {
/**
* @param {!HTMLElement} parent
* @param {!shaka.ui.Controls} controls
*/
constructor(parent, controls) {
super(parent, controls);
const timeContainer = shaka.ui.Utils.createHTMLElement('div');
// TODO: create a 'spacer' element instead for more layout flexibility
timeContainer.classList.add('shaka-time-container');
this.currentTime_ = shaka.ui.Utils.createHTMLElement('div');
this.currentTime_.textContent = '0:00';
timeContainer.appendChild(this.currentTime_);
this.parent.appendChild(timeContainer);
this.eventManager.listen(this.currentTime_, 'click', () => {
// Jump to LIVE if the user clicks on the current time.
if (this.player.isLive()) {
this.video.currentTime = this.player.seekRange().end;
}
});
this.eventManager.listen(this.controls, 'timeandseekrangeupdated', () => {
this.updateTime_();
});
}
/**
* @private
*/
updateTime_() {
const isSeeking = this.controls.isSeeking();
let displayTime = this.controls.getDisplayTime();
let duration = this.video.duration;
let seekRange = this.player.seekRange();
let seekRangeSize = seekRange.end - seekRange.start;
if (this.player.isLive()) {
// The amount of time we are behind the live edge.
let behindLive = Math.floor(seekRange.end - displayTime);
displayTime = Math.max(0, behindLive);
let showHour = seekRangeSize >= 3600;
// Consider "LIVE" when less than 1 second behind the live-edge. Always
// show the full time string when seeking, including the leading '-';
// otherwise, the time string "flickers" near the live-edge.
if ((displayTime >= 1) || isSeeking) {
this.currentTime_.textContent =
'- ' + this.buildTimeString_(displayTime, showHour);
// TODO: move to CSS
this.currentTime_.style.cursor = 'pointer';
} else {
this.currentTime_.textContent =
this.localization.resolve(shaka.ui.Locales.Ids.LABEL_LIVE);
// TODO: move to CSS
this.currentTime_.style.cursor = '';
}
} else {
let showHour = duration >= 3600;
this.currentTime_.textContent =
this.buildTimeString_(displayTime, showHour);
if (duration) {
this.currentTime_.textContent += ' / ' +
this.buildTimeString_(duration, showHour);
}
// TODO: move to CSS
this.currentTime_.style.cursor = '';
}
}
/**
* Builds a time string, e.g., 01:04:23, from |displayTime|.
*
* @param {number} displayTime
* @param {boolean} showHour
* @return {string}
* @private
*/
buildTimeString_(displayTime, showHour) {
let h = Math.floor(displayTime / 3600);
let m = Math.floor((displayTime / 60) % 60);
let s = Math.floor(displayTime % 60);
if (s < 10) s = '0' + s;
let text = m + ':' + s;
if (showHour) {
if (m < 10) text = '0' + text;
text = h + ':' + text;
}
return text;
}
};
/**
* @implements {shaka.extern.IUIElement.Factory}
* @final
*/
shaka.ui.PresentationTimeTracker.Factory = class {
/** @override */
create(rootElement, controls) {
return new shaka.ui.PresentationTimeTracker(rootElement, controls);
}
};
shaka.ui.Controls.registerElement(
'time_and_duration', new shaka.ui.PresentationTimeTracker.Factory());
+104
View File
@@ -0,0 +1,104 @@
/**
* @license
* Copyright 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
goog.provide('shaka.ui.RewindButton');
goog.require('shaka.ui.Element');
goog.require('shaka.ui.Enums');
goog.require('shaka.ui.Locales');
goog.require('shaka.ui.Localization');
goog.require('shaka.ui.Utils');
/**
* @extends {shaka.ui.Element}
* @final
* @export
*/
shaka.ui.RewindButton = class extends shaka.ui.Element {
/**
* @param {!HTMLElement} parent
* @param {!shaka.ui.Controls} controls
*/
constructor(parent, controls) {
super(parent, controls);
/** @private {!HTMLElement} */
this.button_ = shaka.ui.Utils.createHTMLElement('button');
this.button_.classList.add('material-icons');
this.button_.textContent =
shaka.ui.Enums.MaterialDesignIcons.REWIND;
this.parent.appendChild(this.button_);
this.eventManager.listen(
this.localization, shaka.ui.Localization.LOCALE_UPDATED, () => {
this.updateAriaLabel_();
});
this.eventManager.listen(
this.localization, shaka.ui.Localization.LOCALE_CHANGED, () => {
this.updateAriaLabel_();
});
this.eventManager.listen(this.button_, 'click', () => {
this.rewind_();
});
}
/**
* @private
*/
updateAriaLabel_() {
this.button_.setAttribute(shaka.ui.Constants.ARIA_LABEL,
this.localization.resolve(shaka.ui.Locales.Ids.ARIA_LABEL_REWIND));
}
/**
* Cycles trick play rate between -1, -2, -4, and -8.
* @private
*/
rewind_() {
if (!this.video.duration) {
return;
}
const trickPlayRate = this.player.getPlaybackRate();
// Every time the button is clicked, the rate is multiplied by 2,
// unless the rate is at it's slowest (-8), in which case it is
// dropped back to 1.
const newRate = (trickPlayRate > 0 || trickPlayRate < -4) ?
-1 : trickPlayRate * 2;
this.player.trickPlay(newRate);
}
};
/**
* @implements {shaka.extern.IUIElement.Factory}
* @final
*/
shaka.ui.RewindButton.Factory = class {
/** @override */
create(rootElement, controls) {
return new shaka.ui.RewindButton(rootElement, controls);
}
};
shaka.ui.Controls.registerElement(
'rewind', new shaka.ui.RewindButton.Factory());
+1
View File
@@ -72,6 +72,7 @@ shaka.ui.Utils.isTsContent = function(player) {
/**
* Creates an element, and cast the type from Element to HTMLElement.
*
* @param {string} tagName
* @return {!HTMLElement}
*/
+137
View File
@@ -0,0 +1,137 @@
/**
* @license
* Copyright 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
goog.provide('shaka.ui.VolumeBar');
goog.require('shaka.ui.Element');
goog.require('shaka.ui.Locales');
goog.require('shaka.ui.Localization');
/**
* @extends {shaka.ui.Element}
* @final
* @export
*/
shaka.ui.VolumeBar = class extends shaka.ui.Element {
/**
* @param {!HTMLElement} parent
* @param {!shaka.ui.Controls} controls
*/
constructor(parent, controls) {
super(parent, controls);
// This container is to support IE 11. See detailed notes in
// less/range_elements.less for a complete explanation.
// TODO: Factor this into a range-element component.
/** @private {!HTMLElement} */
this.container_ = shaka.ui.Utils.createHTMLElement('div');
this.container_.classList.add('shaka-volume-bar-container');
this.bar_ =
/** @type {!HTMLInputElement} */ (document.createElement('input'));
this.bar_.classList.add('shaka-volume-bar');
this.bar_.setAttribute('type', 'range');
// NOTE: step=any causes keyboard nav problems on IE 11.
this.bar_.setAttribute('step', 'any');
this.bar_.setAttribute('min', '0');
this.bar_.setAttribute('max', '1');
this.bar_.setAttribute('value', '0');
this.container_.appendChild(this.bar_);
this.parent.appendChild(this.container_);
this.eventManager.listen(this.video, 'volumechange', () => {
this.onVolumeStateChange_();
});
this.eventManager.listen(this.bar_, 'input', () => {
this.onVolumeInput_();
});
this.eventManager.listen(
this.localization, shaka.ui.Localization.LOCALE_UPDATED, () => {
this.updateAriaLabel_();
});
this.eventManager.listen(
this.localization, shaka.ui.Localization.LOCALE_CHANGED, () => {
this.updateAriaLabel_();
});
// Initialize volume display with a fake event.
this.onVolumeStateChange_();
}
/**
* @private
*/
onVolumeStateChange_() {
if (this.video.muted) {
this.bar_.value = 0;
} else {
this.bar_.value = this.video.volume;
}
// TODO: Can we do this with LESS?
let gradient = ['to right'];
gradient.push(shaka.ui.Constants.VOLUME_BAR_VOLUME_LEVEL_COLOR +
(this.bar_.value * 100) + '%');
gradient.push(shaka.ui.Constants.VOLUME_BAR_BASE_COLOR +
(this.bar_.value * 100) + '%');
gradient.push(shaka.ui.Constants.VOLUME_BAR_BASE_COLOR + '100%');
this.container_.style.background =
'linear-gradient(' + gradient.join(',') + ')';
}
/**
* @private
*/
onVolumeInput_() {
this.video.volume = parseFloat(this.bar_.value);
if (this.video.volume == 0) {
this.video.muted = true;
} else {
this.video.muted = false;
}
}
/**
* @private
*/
updateAriaLabel_() {
this.bar_.setAttribute(shaka.ui.Constants.ARIA_LABEL,
this.localization.resolve(shaka.ui.Locales.Ids.ARIA_LABEL_VOLUME));
}
};
/**
* @implements {shaka.extern.IUIElement.Factory}
* @final
*/
shaka.ui.VolumeBar.Factory = class {
/** @override */
create(rootElement, controls) {
return new shaka.ui.VolumeBar(rootElement, controls);
}
};
shaka.ui.Controls.registerElement('volume', new shaka.ui.VolumeBar.Factory());