@@ -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