Search code examples
javascriptvue.jsaccessibilityuiaccessibility

HTML Audio accessibility with Input Range


Minimal problem example: https://codepen.io/sidouglas/pen/LYPZaOG

When input range is focused, with either Chrome Vox or Mac's Voice over — every value of valuetext is read out continuously.

<input
 ...
  v-bind:aria-valuemax="valueMax"
  v-bind:aria-valuenow="valueNow"
  v-bind:aria-valuetext="currentTime + ' of ' + totalDuration"
  ...
/>

Contrast this to the very accessible https://plyr.io/#audio component - When focused it continues to update its aria values, yet only announce to the screen reader twice.

Does anyone know how plyr does it?

new Vue({
  el: '#range',
  data: {
    valueMax:100,
    duration:100,
    current:0,
    totalDuration:'1:40'
  },
  computed:{
    currentTime: function() {
      return this.convertTime(this.current);
    },
     valueNow() {
      return Math.round(this.current);
    },
  },
  methods: {
    convertTime(time) {
      const minutes = Math.floor(time / 60);
      const seconds = time - (minutes * 60);
      return `${minutes}:${seconds.toFixed(0).toString().padStart(2, '0')}`;
    },
  },
  mounted: function(){
   var self = this;
   setInterval(function(){
     self.current = Number(self.current) + 1;
     if(self.current >= self.valueMax){
        self.current = 0;
     }
    },2000);
  },
});
.audio-file-player__slider-range{
  border:1px solid red;
  width:100%;
}

.audio-file-player__slider-range:focus {
  box-shadow: 0 0 10px blue;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="range">
  <input
         aria-label="Seek"
         aria-valuemin="0"
         autocomplete="off"
         class="audio-file-player__slider-range u-color-gray-111111 u-width-100"
         min="0"
         role="slider"
         step="1"
         type="range"
         v-bind:aria-valuemax="valueMax"
         v-bind:aria-valuenow="valueNow"
         v-bind:aria-valuetext="currentTime + ' of ' + totalDuration"
         v-bind:max="duration"
         v-model="current"
         />
  <p>current: {{ current }}</p>
  <p>aria-valuenow: {{ valueNow }}</p>
  <p>aria-valuetext: {{ currentTime + ' of ' + totalDuration }}</p>
</div>


Solution

  • For anyone who comes across this the easy solution is to override the value using aria-valuetext.

    When the item is playing I would set the aria-valuetext to 'current: currentTime, remaining: remainingTime' when it first receives focus.

    Then do not update the time while the item is playing (leave the aria-valuetext alone).

    Then when play is paused / stopped change the aria-valuetext again to reflect the current time.

    Finally I would update the current time on a throttle of say 200ms for when someone is seeking using the arrow keys.

    You can decide whether to do this while playing or only when not playing as I am not sure which is the best solution in terms of information vs interruption.

    I would lean towards not updating while playing as a user could always tab to the current time if you made that focusable.

    By doing this you can ensure that it does not interfere with playing music or announce 200 different times.

    plyr.io theory - it repeated for me too at first, then I changed my settings on announce speed. I believe the reason some people do and some people don't get announcements is the speed at which they update the current time (looks like 3-4 times a second) and I think that repeats so quickly that the Screen Reader decides not to confuse the user with it. Only a theory but my best guess as they aren't doing anything clever I can see.