@@ -34,8 +34,8 @@ Object.assign(mejs.MepDefaults, {
3434 autoHLS : false ,
3535 /**
3636 * @type Function
37- */
38- qualityChangeCallback : null
37+ */
38+ qualityChangeCallback : null
3939} ) ;
4040
4141Object . assign ( MediaElementPlayer . prototype , {
@@ -148,16 +148,14 @@ Object.assign(MediaElementPlayer.prototype, {
148148 currentQuality = defaultValue ;
149149
150150 // Get initial quality
151-
152- player . qualitiesButton = document . createElement ( 'div' ) ;
153- player . qualitiesButton . className = `${ t . options . classPrefix } button ${ t . options . classPrefix } qualities-button` ;
154- player . qualitiesButton . innerHTML = `<button type="button" aria-controls="${ t . id } " title="${ qualityTitle } " ` +
155- `aria-label="${ qualityTitle } " tabindex="0">${ defaultValue } </button>` +
151+ const generateId = Math . floor ( Math . random ( ) * 100 ) ;
152+ player . qualitiesContainer = document . createElement ( 'div' ) ;
153+ player . qualitiesContainer . className = `${ t . options . classPrefix } button ${ t . options . classPrefix } qualities-button` ;
154+ player . qualitiesContainer . innerHTML = `<button type="button" title="${ qualityTitle } " aria-label="${ qualityTitle } " aria-controls="qualitieslist-${ generateId } ">${ defaultValue } </button>` +
156155 `<div class="${ t . options . classPrefix } qualities-selector ${ t . options . classPrefix } offscreen">` +
157- `<ul class="${ t . options . classPrefix } qualities-selector-list"></ul>` +
158- `</div>` ;
156+ `<ul class="${ t . options . classPrefix } qualities-selector-list" id="qualitieslist-${ generateId } " tabindex="-1"></ul></div>` ;
159157
160- t . addControlElement ( player . qualitiesButton , 'qualities' ) ;
158+ t . addControlElement ( player . qualitiesContainer , 'qualities' ) ;
161159
162160 qualityMap . forEach ( function ( value , key ) {
163161 if ( key !== 'map_keys_1' ) {
@@ -166,42 +164,77 @@ Object.assign(MediaElementPlayer.prototype, {
166164 quality = key ,
167165 inputId = `${ t . id } -qualities-${ quality } `
168166 ;
169- player . qualitiesButton . querySelector ( 'ul' ) . innerHTML += `<li class="${ t . options . classPrefix } qualities-selector-list-item">` +
170- `<input class="${ t . options . classPrefix } qualities-selector-input" type="radio" name="${ t . id } _qualities"` +
171- `disabled="disabled" value="${ quality } " id="${ inputId } " ` +
172- `${ ( quality === defaultValue ? ' checked="checked"' : '' ) } />` +
173- `<label for="${ inputId } " class="${ t . options . classPrefix } qualities-selector-label` +
174- `${ ( quality === defaultValue ? ` ${ t . options . classPrefix } qualities-selected` : '' ) } ">` +
175- `${ src . title || quality } </label>` +
176- `</li>` ;
167+ player . qualitiesContainer . querySelector ( 'ul' ) . innerHTML += `<li class="${ t . options . classPrefix } qualities-selector-list-item">` +
168+ `<input class="${ t . options . classPrefix } qualities-selector-input" type="radio" name="${ t . id } _qualities" disabled="disabled"` +
169+ `value="${ quality } " id="${ inputId } " ${ ( quality === defaultValue ? ' checked="checked"' : '' ) } />` +
170+ `<label for="${ inputId } " class="${ t . options . classPrefix } qualities-selector-label ${ ( quality === defaultValue ? ` ${ t . options . classPrefix } qualities-selected` : '' ) } ">` +
171+ `${ src . title || quality } </label></li>` ;
177172 }
178173 } ) ;
174+
175+ let isOffScreen = true ;
179176 const
180- inEvents = [ 'mouseenter' , 'focusin' ] ,
181- outEvents = [ 'mouseleave' , 'focusout' ] ,
177+ qualityContainer = player . qualitiesContainer ,
178+ qualityButton = player . qualitiesContainer . querySelector ( `button` ) ,
179+ qualitiesSelector = player . qualitiesContainer . querySelector ( `.${ t . options . classPrefix } qualities-selector` ) ,
180+ qualitiesList = player . qualitiesContainer . querySelector ( `.${ t . options . classPrefix } qualities-selector-list` ) ,
182181 // Enable inputs after they have been appended to controls to avoid tab and up/down arrow focus issues
183- radios = player . qualitiesButton . querySelectorAll ( 'input[type="radio"]' ) ,
184- labels = player . qualitiesButton . querySelectorAll ( `.${ t . options . classPrefix } qualities-selector-label` ) ,
185- selector = player . qualitiesButton . querySelector ( `.${ t . options . classPrefix } qualities-selector` )
182+ radios = player . qualitiesContainer . querySelectorAll ( 'input[type="radio"]' ) ,
183+ labels = player . qualitiesContainer . querySelectorAll ( `.${ t . options . classPrefix } qualities-selector-label` )
186184 ;
187185
188- // hover or keyboard focus
189- for ( let i = 0 , total = inEvents . length ; i < total ; i ++ ) {
190- player . qualitiesButton . addEventListener ( inEvents [ i ] , ( ) => {
191- mejs . Utils . removeClass ( selector , `${ t . options . classPrefix } offscreen` ) ;
192- selector . style . height = `${ selector . querySelector ( 'ul' ) . offsetHeight } px` ;
193- selector . style . top = `${ ( - 1 * parseFloat ( selector . offsetHeight ) ) } px` ;
194- } ) ;
186+ function hideSelector ( ) {
187+ setTimeout ( ( ) => {
188+ mejs . Utils . addClass ( qualitiesSelector , `${ t . options . classPrefix } offscreen` ) ;
189+ } , 50 ) ;
190+ qualityButton . removeAttribute ( 'aria-expanded' ) ;
191+ qualitiesList . style . display = `none` ;
192+ qualityButton . focus ( ) ;
193+ isOffScreen = true ;
195194 }
196195
197- for ( let i = 0 , total = outEvents . length ; i < total ; i ++ ) {
198- player . qualitiesButton . addEventListener ( outEvents [ i ] , ( ) => {
199- setTimeout ( ( ) => {
200- mejs . Utils . addClass ( selector , `${ t . options . classPrefix } offscreen` ) ;
201- } , 50 ) ;
202- } ) ;
196+ function showSelector ( ) {
197+ mejs . Utils . removeClass ( qualitiesSelector , `${ t . options . classPrefix } offscreen` ) ;
198+ qualitiesList . style . display = `block` ;
199+ qualitiesSelector . style . height = `${ qualitiesSelector . querySelector ( 'ul' ) . offsetHeight } px` ;
200+ qualitiesSelector . style . top = `${ ( - 1 * parseFloat ( qualitiesSelector . offsetHeight ) ) } px` ;
201+ qualityButton . setAttribute ( 'aria-expanded' , 'true' ) ;
202+ qualitiesList . focus ( ) ;
203+ isOffScreen = false ;
203204 }
204205
206+ qualityButton . addEventListener ( 'click' , ( ) => {
207+ if ( isOffScreen === true ) {
208+ showSelector ( ) ;
209+ } else {
210+ hideSelector ( ) ;
211+ }
212+ } ) ;
213+
214+ qualitiesList . addEventListener ( 'focusout' , ( event ) => {
215+ if ( ! qualityContainer . contains ( event . relatedTarget ) ) {
216+ hideSelector ( ) ;
217+ }
218+ } ) ;
219+
220+ qualityButton . addEventListener ( 'mouseenter' , ( ) => {
221+ showSelector ( ) ;
222+ } ) ;
223+
224+ qualityContainer . addEventListener ( 'mouseleave' , ( ) => {
225+ hideSelector ( ) ;
226+ } ) ;
227+
228+ // Close with Escape key.
229+ // Allow up/down arrow to change the selected radio without changing the volume.
230+ qualityContainer . addEventListener ( 'keydown' , ( event ) => {
231+ if ( event . key === "Escape" ) {
232+ hideSelector ( ) ;
233+ }
234+
235+ event . stopPropagation ( ) ;
236+ } ) ;
237+
205238 for ( let i = 0 , total = radios . length ; i < total ; i ++ ) {
206239 const radio = radios [ i ] ;
207240 radio . disabled = false ;
@@ -238,6 +271,7 @@ Object.assign(MediaElementPlayer.prototype, {
238271 }
239272 } ) ;
240273 }
274+
241275 for ( let i = 0 , total = labels . length ; i < total ; i ++ ) {
242276 labels [ i ] . addEventListener ( 'click' , function ( ) {
243277 const
@@ -248,10 +282,6 @@ Object.assign(MediaElementPlayer.prototype, {
248282 } ) ;
249283 }
250284
251- //Allow up/down arrow to change the selected radio without changing the volume.
252- selector . addEventListener ( 'keydown' , ( e ) => {
253- e . stopPropagation ( ) ;
254- } ) ;
255285 } ,
256286
257287 /**
@@ -262,8 +292,8 @@ Object.assign(MediaElementPlayer.prototype, {
262292 */
263293 cleanquality ( player ) {
264294 if ( player ) {
265- if ( player . qualitiesButton ) {
266- player . qualitiesButton . remove ( ) ;
295+ if ( player . qualitiesContainer ) {
296+ player . qualitiesContainer . remove ( ) ;
267297 }
268298 }
269299 } ,
@@ -358,7 +388,7 @@ Object.assign(MediaElementPlayer.prototype, {
358388 * @param {MediaElement } media
359389 */
360390 switchDashQuality ( player , media ) {
361- const radios = player . qualitiesButton . querySelectorAll ( 'input[type="radio"]' ) ;
391+ const radios = player . qualitiesContainer . querySelectorAll ( 'input[type="radio"]' ) ;
362392 for ( let index = 0 ; index < radios . length ; index ++ ) {
363393 if ( radios [ index ] . checked ) {
364394 if ( index === 0 ) {
@@ -377,7 +407,7 @@ Object.assign(MediaElementPlayer.prototype, {
377407 * @param {MediaElement } media
378408 */
379409 switchHLSQuality ( player , media ) {
380- const radios = player . qualitiesButton . querySelectorAll ( 'input[type="radio"]' ) ;
410+ const radios = player . qualitiesContainer . querySelectorAll ( 'input[type="radio"]' ) ;
381411 for ( let index = 0 ; index < radios . length ; index ++ ) {
382412 if ( radios [ index ] . checked ) {
383413 if ( index === 0 ) {
@@ -402,7 +432,7 @@ Object.assign(MediaElementPlayer.prototype, {
402432 ;
403433 currentQuality = newQuality ;
404434
405- const selected = player . qualitiesButton . querySelectorAll ( `.${ t . options . classPrefix } qualities-selected` ) ;
435+ const selected = player . qualitiesContainer . querySelectorAll ( `.${ t . options . classPrefix } qualities-selected` ) ;
406436 for ( let i = 0 , total = selected . length ; i < total ; i ++ ) {
407437 mejs . Utils . removeClass ( selected [ i ] , `${ t . options . classPrefix } qualities-selected` ) ;
408438 }
@@ -413,7 +443,7 @@ Object.assign(MediaElementPlayer.prototype, {
413443 mejs . Utils . addClass ( siblings [ j ] , `${ t . options . classPrefix } qualities-selected` ) ;
414444 }
415445
416- player . qualitiesButton . querySelector ( 'button' ) . innerHTML = newQuality ;
446+ player . qualitiesContainer . querySelector ( 'button' ) . innerHTML = newQuality ;
417447 } ,
418448
419449 /**
0 commit comments