Skip to content

Commit 681bb57

Browse files
committed
Add support for data-skin attribute; introduce "2020" skin
1 parent 0c22d8f commit 681bb57

11 files changed

Lines changed: 523 additions & 248 deletions

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ The following attributes are supported on both the `<audio>` and `<video>` eleme
211211
- **data-heading-level** - optional; Able Player injects an off-screen HTML heading "Media Player" (or localized equivalent) at the top of the player so screen reader users can easily find the player. It automatically assigns a heading level that is one level deeper than the closest parent heading. This attribute can be used to manually set the heading level, rather than relying on Able Player to assign it automatically. Valid values are 1 through 6. A value of 0 is also supported, and instructs Able Player to not inject a heading at all. The latter should be used only if the web page already includes a heading immediately prior to the media player.
212212
- **data-hide-controls** - optional; set to "true" to hide controls during playback. Controls are visibly hidden but still accessible to assistive technologies. Controls reappear if user presses any key or moves the mouse over the video player region.
213213
- **data-icon-type** - optional; "svg", "font" or "image"; "svg" is the default with automatic fallback to "font" unless either (a) the browser doesn't support icon fonts or (b) the user has a custom style sheet that may impact the display of icon fonts; in either case falls back to images. Should generally leave as is unless testing the fallback.
214+
- **data-skin** - optional; "legacy (default) or "2020". The default skin has two rows of controls, with the seekbar positioned in available space within the top row. The "2020" skin, introduced in version 4.2, has all buttons in one row beneath a full-width seekbar.
214215
- **data-speed-icons** - optional; "animals" (default) or "arrows". The default setting uses a turtle icon for *slower* and a rabbit icon for *faster*. Setting this to "arrows" uses the original Able Player icons (prior to version 3.5), arrows pointing up for *faster* and down for *slower*.
215216
- **data-start-time** - optional; time at which you want the audio to start playing (in seconds)
216217
- **data-volume** - optional; set the default volume (0 to 10; default is 7 to avoid overpowering screen reader audio)

build/ableplayer.dist.js

Lines changed: 171 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,17 @@ var AblePlayerInstances = [];
324324
this.vimeoDescId = $(media).data('vimeo-desc-id');
325325
}
326326

327+
// Skin
328+
// valid values of data-skin are:
329+
// 'legacy' (default), two rows of controls; seekbar positioned in available space within top row
330+
// '2020', all buttons in one row beneath a full-width seekbar
331+
if ($(media).data('skin') == '2020') {
332+
this.skin = '2020';
333+
}
334+
else {
335+
this.skin = 'legacy';
336+
}
337+
327338
// Icon type
328339
// By default, AblePlayer 3.0.33 and higher uses SVG icons for the player controls
329340
// Fallback for browsers that don't support SVG is scalable icomoon fonts
@@ -3012,6 +3023,7 @@ var AblePlayerInstances = [];
30123023
this.$mediaContainer = this.$media.wrap('<div class="able-media-container"></div>').parent();
30133024
this.$ableDiv = this.$mediaContainer.wrap('<div class="able"></div>').parent();
30143025
this.$ableWrapper = this.$ableDiv.wrap('<div class="able-wrapper"></div>').parent();
3026+
this.$ableWrapper.addClass('able-skin-' + this.skin);
30153027

30163028
// NOTE: Excluding the following from youtube was resulting in a player
30173029
// that exceeds the width of the YouTube video
@@ -3797,76 +3809,134 @@ var AblePlayerInstances = [];
37973809
AblePlayer.prototype.calculateControlLayout = function () {
37983810

37993811
// Calculates the layout for controls based on media and options.
3800-
// Returns an object with keys 'ul', 'ur', 'bl', 'br' for upper-left, etc.
3801-
// Each associated value is array of control names to put at that location.
3802-
3803-
var controlLayout = {
3804-
'ul': ['play','restart','rewind','forward'],
3805-
'ur': ['seek'],
3806-
'bl': [],
3807-
'br': []
3808-
}
3812+
// Returns an array with 4 keys (for legacy skin) or 2 keys (for 2020 skin)
3813+
// Keys are the following order:
3814+
// 0 = Top left
3815+
// 1 = Top right
3816+
// 2 = Bottom left (legacy skin only)
3817+
// 3 = Bottom right (legacy skin only)
3818+
// Each key contains an array of control names to put in that section.
3819+
3820+
var controlLayout, volumeSupported, playbackSupported;
3821+
3822+
controlLayout = [];
3823+
controlLayout[0] = [];
3824+
controlLayout[1] = [];
3825+
if (this.skin === 'legacy') {
3826+
controlLayout[2] = [];
3827+
controlLayout[3] = [];
3828+
}
3829+
3830+
controlLayout[0].push('play');
3831+
controlLayout[0].push('restart');
3832+
controlLayout[0].push('rewind');
3833+
controlLayout[0].push('forward');
3834+
3835+
if (this.skin === 'legacy') {
3836+
controlLayout[1].push('seek');
3837+
}
38093838

38103839
if (this.hasPlaylist) {
3811-
controlLayout['ur'].push('previous');
3812-
controlLayout['ur'].push('next');
3840+
if (this.skin === 'legacy') {
3841+
controlLayout[0].push('previous');
3842+
controlLayout[0].push('next');
3843+
}
3844+
else if (this.skin === '2020') {
3845+
controlLayout[0].push('previous');
3846+
controlLayout[0].push('next');
3847+
}
38133848
}
38143849

3815-
// test for browser support for volume before displaying volume button
3816-
if (this.browserSupportsVolume()) {
3817-
// volume buttons are: 'mute','volume-soft','volume-medium','volume-loud'
3818-
// previously supported button were: 'volume-up','volume-down'
3819-
this.volumeButton = 'volume-' + this.getVolumeName(this.volume);
3820-
controlLayout['ur'].push('volume');
3850+
if (this.isPlaybackRateSupported()) {
3851+
playbackSupported = true;
3852+
if (this.skin === 'legacy') {
3853+
controlLayout[2].push('slower');
3854+
controlLayout[2].push('faster');
3855+
}
38213856
}
38223857
else {
3823-
this.volume = false;
3824-
}
3825-
3826-
// Calculate the two sides of the bottom-left grouping to see if we need separator pipe.
3827-
var bll = [];
3828-
var blr = [];
3829-
3830-
if (this.isPlaybackRateSupported()) {
3831-
bll.push('slower');
3832-
bll.push('faster');
3858+
playbackSupported = false;
38333859
}
38343860

38353861
if (this.mediaType === 'video') {
38363862
if (this.hasCaptions) {
3837-
bll.push('captions'); //closed captions
3863+
if (this.skin === 'legacy') {
3864+
controlLayout[2].push('captions');
3865+
}
3866+
else if (this.skin === '2020') {
3867+
controlLayout[1].push('captions');
3868+
}
38383869
}
38393870
if (this.hasSignLanguage) {
3840-
bll.push('sign'); // sign language
3871+
if (this.skin === 'legacy') {
3872+
controlLayout[2].push('sign');
3873+
}
3874+
else if (this.skin === '2020') {
3875+
controlLayout[1].push('sign');
3876+
}
38413877
}
38423878
if ((this.hasOpenDesc || this.hasClosedDesc) && (this.useDescriptionsButton)) {
3843-
bll.push('descriptions'); //audio description
3879+
if (this.skin === 'legacy') {
3880+
controlLayout[2].push('descriptions');
3881+
}
3882+
else if (this.skin === '2020') {
3883+
controlLayout[1].push('descriptions');
3884+
}
38443885
}
38453886
}
38463887
if (this.transcriptType === 'popup' && !(this.hideTranscriptButton)) {
3847-
bll.push('transcript');
3888+
if (this.skin === 'legacy') {
3889+
controlLayout[2].push('transcript');
3890+
}
3891+
else if (this.skin === '2020') {
3892+
controlLayout[1].push('transcript');
3893+
}
38483894
}
38493895

38503896
if (this.mediaType === 'video' && this.hasChapters && this.useChaptersButton) {
3851-
bll.push('chapters');
3897+
if (this.skin === 'legacy') {
3898+
controlLayout[2].push('chapters');
3899+
}
3900+
else if (this.skin === '2020') {
3901+
controlLayout[1].push('chapters');
3902+
}
3903+
}
3904+
3905+
if (playbackSupported && this.skin === '2020') {
3906+
controlLayout[1].push('faster');
3907+
controlLayout[1].push('slower');
38523908
}
38533909

3854-
controlLayout['br'].push('preferences');
3910+
if (this.skin === 'legacy') {
3911+
controlLayout[3].push('preferences');
3912+
}
3913+
else if (this.skin === '2020') {
3914+
controlLayout[1].push('preferences');
3915+
}
38553916

38563917
if (this.mediaType === 'video' && this.allowFullScreen) {
3857-
controlLayout['br'].push('fullscreen');
3918+
if (this.skin === 'legacy') {
3919+
controlLayout[3].push('fullscreen');
3920+
}
3921+
else {
3922+
controlLayout[1].push('fullscreen');
3923+
}
38583924
}
38593925

3860-
// Include the pipe only if we need to.
3861-
if (bll.length > 0 && blr.length > 0) {
3862-
controlLayout['bl'] = bll;
3863-
controlLayout['bl'].push('pipe');
3864-
controlLayout['bl'] = controlLayout['bl'].concat(blr);
3926+
if (this.browserSupportsVolume()) {
3927+
volumeSupported = true; // defined in case we decide to move volume button elsewhere
3928+
this.volumeButton = 'volume-' + this.getVolumeName(this.volume);
3929+
if (this.skin === 'legacy') {
3930+
controlLayout[1].push('volume');
3931+
}
3932+
else if (this.skin === '2020') {
3933+
controlLayout[1].push('volume');
3934+
}
38653935
}
38663936
else {
3867-
controlLayout['bl'] = bll.concat(blr);
3937+
volumeSupported = false;
3938+
this.volume = false;
38683939
}
3869-
38703940
return controlLayout;
38713941
};
38723942

@@ -3878,7 +3948,8 @@ var AblePlayerInstances = [];
38783948
// browser support (e.g., for sliders and speedButtons)
38793949
// user preferences (???)
38803950
// some controls are aligned on the left, and others on the right
3881-
var thisObj, baseSliderWidth, controlLayout, sectionByOrder, useSpeedButtons, useFullScreen,
3951+
var thisObj, baseSliderWidth, controlLayout, numSections,
3952+
sectionByOrder, useSpeedButtons, useFullScreen,
38823953
i, j, k, controls, $controllerSpan, $sliderDiv, sliderLabel, mediaTimes, duration, $pipe, $pipeImg,
38833954
tooltipId, tooltipX, tooltipY, control,
38843955
buttonImg, buttonImgSrc, buttonTitle, $newButton, iconClass, buttonIcon, buttonUse, svgPath,
@@ -3891,8 +3962,7 @@ var AblePlayerInstances = [];
38913962

38923963
// Initialize the layout into the this.controlLayout variable.
38933964
controlLayout = this.calculateControlLayout();
3894-
3895-
sectionByOrder = {0: 'ul', 1:'ur', 2:'bl', 3:'br'};
3965+
numSections = controlLayout.length;
38963966

38973967
// add an empty div to serve as a tooltip
38983968
tooltipId = this.mediaId + '-tooltip';
@@ -3902,15 +3972,29 @@ var AblePlayerInstances = [];
39023972
}).hide();
39033973
this.$controllerDiv.append(this.$tooltipDiv);
39043974

3975+
if (this.skin === '2020') {
3976+
// add a full-width seek bar
3977+
$sliderDiv = $('<div class="able-seekbar"></div>');
3978+
sliderLabel = this.mediaType + ' ' + this.tt.seekbarLabel;
3979+
this.$controllerDiv.append($sliderDiv);
3980+
if (typeof this.duration === 'undefined' || this.duration === 0) {
3981+
// set arbitrary starting duration, and change it when duration is known
3982+
this.duration = 60;
3983+
// also set elapsed to 0
3984+
this.elapsed = 0;
3985+
}
3986+
this.seekBar = new AccessibleSlider(this.mediaType, $sliderDiv, 'horizontal', baseSliderWidth, 0, this.duration, this.seekInterval, sliderLabel, 'seekbar', true, 'visible');
3987+
}
3988+
39053989
// step separately through left and right controls
3906-
for (i = 0; i <= 3; i++) {
3907-
controls = controlLayout[sectionByOrder[i]];
3908-
if ((i % 2) === 0) {
3990+
for (i = 0; i < numSections; i++) {
3991+
controls = controlLayout[i];
3992+
if ((i % 2) === 0) { // even keys on the left
39093993
$controllerSpan = $('<div>',{
39103994
'class': 'able-left-controls'
39113995
});
39123996
}
3913-
else {
3997+
else { // odd keys on the right
39143998
$controllerSpan = $('<div>',{
39153999
'class': 'able-right-controls'
39164000
});
@@ -5980,13 +6064,15 @@ if (thisObj.useTtml && (trackSrc.endsWith('.xml') || trackText.startsWith('<?xml
59806064
this.bodyDiv.wrap('<div></div>');
59816065
this.wrapperDiv = this.bodyDiv.parent();
59826066

5983-
if (orientation === 'horizontal') {
5984-
this.wrapperDiv.width(length);
5985-
this.loadedDiv.width(0);
5986-
}
5987-
else {
5988-
this.wrapperDiv.height(length);
5989-
this.loadedDiv.height(0);
6067+
if (this.skin === 'legacy') {
6068+
if (orientation === 'horizontal') {
6069+
this.wrapperDiv.width(length);
6070+
this.loadedDiv.width(0);
6071+
}
6072+
else {
6073+
this.wrapperDiv.height(length);
6074+
this.loadedDiv.height(0);
6075+
}
59906076
}
59916077
this.wrapperDiv.addClass('able-' + className + '-wrapper');
59926078

@@ -8285,33 +8371,36 @@ if (thisObj.useTtml && (trackSrc.endsWith('.xml') || trackText.startsWith('<?xml
82858371
if (typeof this.$elapsedTimeContainer !== 'undefined') {
82868372
this.$elapsedTimeContainer.text(this.formatSecondsAsColonTime(displayElapsed));
82878373
}
8288-
// Update seekbar width.
8289-
// To do this, we need to calculate the width of all buttons surrounding it.
8290-
if (this.seekBar) {
8291-
widthUsed = 0;
8292-
leftControls = this.seekBar.wrapperDiv.parent().prev('div.able-left-controls');
8293-
rightControls = leftControls.next('div.able-right-controls');
8294-
leftControls.children().each(function () {
8295-
if ($(this).prop('tagName')=='BUTTON') {
8296-
widthUsed += $(this).outerWidth(true); // true = include margin
8297-
}
8298-
});
8299-
rightControls.children().each(function () {
8300-
if ($(this).prop('tagName')=='BUTTON') {
8301-
widthUsed += $(this).outerWidth(true);
8302-
}
8303-
});
8304-
if (this.fullscreen) {
8305-
seekbarWidth = $(window).width() - widthUsed;
8306-
}
8307-
else {
8308-
seekbarWidth = this.$ableWrapper.width() - widthUsed;
8309-
}
8310-
// Sometimes some minor fluctuations based on browser weirdness, so set a threshold.
8311-
if (Math.abs(seekbarWidth - this.seekBar.getWidth()) > 5) {
8312-
this.seekBar.setWidth(seekbarWidth);
8313-
}
8314-
}
8374+
8375+
if (this.skin === 'legacy') {
8376+
// Update seekbar width.
8377+
// To do this, we need to calculate the width of all buttons surrounding it.
8378+
if (this.seekBar) {
8379+
widthUsed = 0;
8380+
leftControls = this.seekBar.wrapperDiv.parent().prev('div.able-left-controls');
8381+
rightControls = leftControls.next('div.able-right-controls');
8382+
leftControls.children().each(function () {
8383+
if ($(this).prop('tagName')=='BUTTON') {
8384+
widthUsed += $(this).outerWidth(true); // true = include margin
8385+
}
8386+
});
8387+
rightControls.children().each(function () {
8388+
if ($(this).prop('tagName')=='BUTTON') {
8389+
widthUsed += $(this).outerWidth(true);
8390+
}
8391+
});
8392+
if (this.fullscreen) {
8393+
seekbarWidth = $(window).width() - widthUsed;
8394+
}
8395+
else {
8396+
seekbarWidth = this.$ableWrapper.width() - widthUsed;
8397+
}
8398+
// Sometimes some minor fluctuations based on browser weirdness, so set a threshold.
8399+
if (Math.abs(seekbarWidth - this.seekBar.getWidth()) > 5) {
8400+
this.seekBar.setWidth(seekbarWidth);
8401+
}
8402+
}
8403+
}
83158404

83168405
// Update buffering progress.
83178406
// TODO: Currently only using the first HTML5 buffered interval,

0 commit comments

Comments
 (0)