AX: Media controls timeline should have percentage value description
authorn_wang@apple.com <n_wang@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 10 Aug 2016 23:30:24 +0000 (23:30 +0000)
committern_wang@apple.com <n_wang@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 10 Aug 2016 23:30:24 +0000 (23:30 +0000)
https://bugs.webkit.org/show_bug.cgi?id=160619

Reviewed by Eric Carlson.

Source/WebCore:

Added aria-valuetext attribute to the timeline slider and set its value to
the percentage format. Also formatted the elapsed/remaining timer's description
so that it's more human readable.

Test: http/tests/media/hls/video-duration-accessibility.html

* English.lproj/mediaControlsLocalizedStrings.js:
* Modules/mediacontrols/mediaControlsApple.js:
(Controller.prototype.drawVolumeBackground):
(Controller.prototype.formatTimeDescription):
(Controller.prototype.formatTime):
(Controller.prototype.updateTime):
(Controller.prototype.updateControlsWhileScrubbing):

LayoutTests:

* http/tests/media/hls/video-duration-accessibility-expected.txt: Added.
* http/tests/media/hls/video-duration-accessibility.html: Added.
* http/tests/media/resources/hls/generate-vod.php: Added.
* media/media-controls-accessibility-expected.txt:
* platform/efl/accessibility/media-element-expected.txt:
* platform/gtk/accessibility/media-element-expected.txt:
* platform/mac/accessibility/media-element-expected.txt:

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@204361 268f45cc-cd09-0410-ab3c-d52691b4dbfc

LayoutTests/ChangeLog
LayoutTests/http/tests/media/hls/video-duration-accessibility-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/media/hls/video-duration-accessibility.html [new file with mode: 0644]
LayoutTests/http/tests/media/resources/hls/generate-vod.php [new file with mode: 0644]
LayoutTests/media/media-controls-accessibility-expected.txt
LayoutTests/platform/efl/accessibility/media-element-expected.txt
LayoutTests/platform/gtk/accessibility/media-element-expected.txt
LayoutTests/platform/mac/accessibility/media-element-expected.txt
Source/WebCore/ChangeLog
Source/WebCore/English.lproj/mediaControlsLocalizedStrings.js
Source/WebCore/Modules/mediacontrols/mediaControlsApple.js

index 9fa12ce..d52561f 100644 (file)
@@ -1,3 +1,18 @@
+2016-08-10  Nan Wang  <n_wang@apple.com>
+
+        AX: Media controls timeline should have percentage value description
+        https://bugs.webkit.org/show_bug.cgi?id=160619
+
+        Reviewed by Eric Carlson.
+
+        * http/tests/media/hls/video-duration-accessibility-expected.txt: Added.
+        * http/tests/media/hls/video-duration-accessibility.html: Added.
+        * http/tests/media/resources/hls/generate-vod.php: Added.
+        * media/media-controls-accessibility-expected.txt:
+        * platform/efl/accessibility/media-element-expected.txt:
+        * platform/gtk/accessibility/media-element-expected.txt:
+        * platform/mac/accessibility/media-element-expected.txt:
+
 2016-08-09  Skachkov Oleksandr  <gskachkov@gmail.com>
 
         [ES2016] Implement Object.values
diff --git a/LayoutTests/http/tests/media/hls/video-duration-accessibility-expected.txt b/LayoutTests/http/tests/media/hls/video-duration-accessibility-expected.txt
new file mode 100644 (file)
index 0000000..b7340b2
--- /dev/null
@@ -0,0 +1,18 @@
+
+EVENT(canplaythrough)
+EVENT(play)
+EXPECTED (video.duration != Infinity == 'true') OK
+EVENT(seeked)
+EXPECTED (elapsedTimer.description.indexOf('1 Second') !== -1 == 'true') OK
+EVENT(seeked)
+EXPECTED (elapsedTimer.description.indexOf('2 Seconds') !== -1 == 'true') OK
+EVENT(seeked)
+EXPECTED (elapsedTimer.description.indexOf('1 Minute') !== -1 == 'true') OK
+EVENT(seeked)
+EXPECTED (elapsedTimer.description.indexOf('2 Minutes') !== -1 == 'true') OK
+EVENT(seeked)
+EXPECTED (elapsedTimer.description.indexOf('1 Hour') !== -1 == 'true') OK
+EVENT(seeked)
+EXPECTED (elapsedTimer.description.indexOf('2 Hours') !== -1 == 'true') OK
+END OF TEST
+
diff --git a/LayoutTests/http/tests/media/hls/video-duration-accessibility.html b/LayoutTests/http/tests/media/hls/video-duration-accessibility.html
new file mode 100644 (file)
index 0000000..cdb5935
--- /dev/null
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <script src=../../media-resources/video-test.js></script>
+        <script src=../../media-resources/media-controls.js></script>
+        <script>
+        if (window.accessibilityController) {
+            var controls;
+            var elapsedTimer, videoElement;
+            var seekTimes = [1, 2, 70, 130, 3670, 7300];
+            var seekCount = 0;
+            
+            function start() {
+                video = document.getElementById('video');
+                waitForEventOnce('canplaythrough', function() { video.play(); });
+                waitForEventOnce('play', playing);
+                waitForEventAndFail('stalled');
+                waitForEvent('seeked', seek);
+                video.src = "../resources/hls/generate-vod.php?duration=8000";
+            }
+
+            function playing() {
+                video.pause();
+                setTimeout(function() {
+                    testExpected('video.duration != Infinity', true);
+                    video.fastSeek(seekTimes[0]);
+                }, 200);
+            }
+            
+            function seek() {
+                seekCount++;
+                videoElement = accessibilityController.accessibleElementById("video");
+                elapsedTimer = videoElement.childAtIndex(0).childAtIndex(3);
+                
+                if (elapsedTimer.description.indexOf("Hours") !== -1)
+                    testExpected("elapsedTimer.description.indexOf('2 Hours') !== -1", true);
+                else if (elapsedTimer.description.indexOf("Hour") !== -1)
+                    testExpected("elapsedTimer.description.indexOf('1 Hour') !== -1", true);
+                else if (elapsedTimer.description.indexOf("Minutes") !== -1)
+                    testExpected("elapsedTimer.description.indexOf('2 Minutes') !== -1", true);
+                else if (elapsedTimer.description.indexOf("Minute") !== -1)
+                    testExpected("elapsedTimer.description.indexOf('1 Minute') !== -1", true);
+                else if (elapsedTimer.description.indexOf("Seconds") !== -1)
+                    testExpected("elapsedTimer.description.indexOf('2 Seconds') !== -1", true);
+                else if (elapsedTimer.description.indexOf("Second") !== -1)
+                    testExpected("elapsedTimer.description.indexOf('1 Second') !== -1", true);
+                
+                if (seekCount == seekTimes.length)
+                    endTest();
+                else
+                    video.fastSeek(seekTimes[seekCount]);
+            }
+        }
+        </script>
+    </head>
+    <body onload="start()">
+        <video width="640" id="video" controls></video>
+    </body>
+</html>
diff --git a/LayoutTests/http/tests/media/resources/hls/generate-vod.php b/LayoutTests/http/tests/media/resources/hls/generate-vod.php
new file mode 100644 (file)
index 0000000..cc92652
--- /dev/null
@@ -0,0 +1,28 @@
+<?php
+# See the HLS ITEF spec: <http://tools.ietf.org/id/draft-pantos-http-live-streaming-12.txt>
+header("Status: 200 OK");
+header("HTTP/1.1 200 OK");
+header("Connection: close");
+header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
+header("Pragma: no-cache");
+header("Etag: " . '"' . filesize(__FILE__) . "-" . filemtime(__FILE__) . '"');
+header("Content-Type: application/x-mpegurl");
+
+$chunkDuration = 6.0272;
+$chunkCount = 5;
+if (array_key_exists("duration", $_GET)) 
+    $chunkCount = intval($_GET["duration"] / $chunkDuration);
+
+function println($string) { return print($string . PHP_EOL); }
+println("#EXTM3U");
+println("#EXT-X-TARGETDURATION:7");
+println("#EXT-X-VERSION:4");
+println("#EXT-X-MEDIA-SEQUENCE:0");
+println("#EXT-X-PLAYLIST-TYPE:VOD");
+
+for ($i = 0; $i < $chunkCount; ++$i) {
+    println("#EXTINF:" . $chunkDuration . ",");
+    println("test.ts");
+}
+
+println("#EXT-X-ENDLIST");
index b438304..66a1f16 100644 (file)
@@ -7,8 +7,8 @@ On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE
 PASS successfullyParsed is true
 
 TEST COMPLETE
-elapsedTimer.description: AXDescription: Elapsed 00:00
-remainingTimer.description: AXDescription: Remaining -00:06
+elapsedTimer.description: AXDescription: Elapsed 0 Seconds
+remainingTimer.description: AXDescription: Remaining 6 Seconds
 
 muteButton.description: AXDescription: Mute
 muteButton.role: AXRole: AXCheckBox
index 4eb350c..803e0c6 100644 (file)
@@ -21,7 +21,7 @@ State at 'canplaythrough' event:
             role: AXRole: AXButton
 
 
-            title: AXTitle: Elapsed 00:00
+            title: AXTitle: Elapsed 0 Seconds
             role: AXRole: AXTimer
 
 
@@ -33,7 +33,7 @@ State at 'canplaythrough' event:
             role: AXRole: AXSlider
 
 
-            title: AXTitle: Remaining -00:06
+            title: AXTitle: Remaining 6 Seconds
             role: AXRole: AXTimer
 
 
index 4510503..d329737 100644 (file)
@@ -21,7 +21,7 @@ State at 'canplaythrough' event:
             role: AXRole: AXSlider
 
 
-            title: AXTitle: Remaining -00:06
+            title: AXTitle: Remaining 6 Seconds
             role: AXRole: AXTimer
 
 
index b307697..9cbd07b 100644 (file)
@@ -34,7 +34,7 @@ State at 'canplaythrough' event:
             role: AXRole: AXButton
 
 
-            description: AXDescription: Elapsed 00:00
+            description: AXDescription: Elapsed 0 Seconds
             role: AXRole: AXGroup
             subrole: AXSubrole: AXApplicationTimer
 
@@ -55,7 +55,7 @@ State at 'canplaythrough' event:
                 role: AXRole: AXValueIndicator
 
 
-            description: AXDescription: Remaining -00:06
+            description: AXDescription: Remaining 6 Seconds
             role: AXRole: AXGroup
             subrole: AXSubrole: AXApplicationTimer
 
index bd3e839..3c3b4d6 100644 (file)
@@ -1,3 +1,24 @@
+2016-08-10  Nan Wang  <n_wang@apple.com>
+
+        AX: Media controls timeline should have percentage value description
+        https://bugs.webkit.org/show_bug.cgi?id=160619
+
+        Reviewed by Eric Carlson.
+
+        Added aria-valuetext attribute to the timeline slider and set its value to
+        the percentage format. Also formatted the elapsed/remaining timer's description
+        so that it's more human readable.
+
+        Test: http/tests/media/hls/video-duration-accessibility.html
+
+        * English.lproj/mediaControlsLocalizedStrings.js:
+        * Modules/mediacontrols/mediaControlsApple.js:
+        (Controller.prototype.drawVolumeBackground):
+        (Controller.prototype.formatTimeDescription):
+        (Controller.prototype.formatTime):
+        (Controller.prototype.updateTime):
+        (Controller.prototype.updateControlsWhileScrubbing):
+
 2016-08-10  Anders Carlsson  <andersca@apple.com>
 
         Revert back to the old style member function state machine for callbacks
index 2418113..1396898 100644 (file)
@@ -17,16 +17,22 @@ var UIStringTable = {
     'Error': 'Error',
     'Exit Full Screen': 'Exit Full Screen',
     'Fast Forward': 'Fast Forward',
+    'Hour' : 'Hour',
+    'Hours': 'Hours',
     'Live Broadcast': 'Live Broadcast',
     'Loading': 'Loading',
     'Maximum Volume': 'Maximum Volume',
     'Minimum Volume': 'Minimum Volume',
+    'Minute': 'Minute',
+    'Minutes': 'Minutes',
     'Mute': 'Mute',
     'Pause': 'Pause',
     'Play': 'Play',
     'Remaining': 'Remaining',
     'Rewind': 'Rewind',
     'Rewind ##sec## Seconds': 'Rewind ##sec## Seconds',
+    'Second': 'Second',
+    'Seconds': 'Seconds',
     'Show Controls': 'Show Controls',
     'Stalled': 'Stalled',
     'Start Playback': 'Start Playback',
@@ -38,4 +44,4 @@ var UIStringTable = {
     'Video Playback Placeholder': 'Video Playback Placeholder',
     'Volume': 'Volume',
     'Waiting': 'Waiting'
-};
\ No newline at end of file
+};
index 153fd83..5cc4187 100644 (file)
@@ -1568,6 +1568,25 @@ Controller.prototype = {
 
         ctx.restore();
     },
+    
+    formatTimeDescription: function(time)
+    {
+        if (isNaN(time))
+            time = 0;
+        var absTime = Math.abs(time);
+        var intSeconds = Math.floor(absTime % 60).toFixed(0);
+        var intMinutes = Math.floor((absTime / 60) % 60).toFixed(0);
+        var intHours = Math.floor(absTime / (60 * 60)).toFixed(0);
+        
+        var secondString = intSeconds == 1 ? 'Second' : 'Seconds';
+        var minuteString = intMinutes == 1 ? 'Minute' : 'Minutes';
+        var hourString = intHours == 1 ? 'Hour' : 'Hours';
+        if (intHours > 0)
+            return `${intHours} ${this.UIString(hourString)}, ${intMinutes} ${this.UIString(minuteString)}, ${intSeconds} ${this.UIString(secondString)}`;
+        if (intMinutes > 0)
+            return `${intMinutes} ${this.UIString(minuteString)}, ${intSeconds} ${this.UIString(secondString)}`;
+        return `${intSeconds} ${this.UIString(secondString)}`;
+    },
 
     formatTime: function(time)
     {
@@ -1769,10 +1788,15 @@ Controller.prototype = {
         var currentTime = this.video.currentTime;
         var timeRemaining = currentTime - this.video.duration;
         this.currentTimeClone.innerText = this.controls.currentTime.innerText = this.formatTime(currentTime);
-        this.controls.currentTime.setAttribute('aria-label', `${this.UIString('Elapsed')} ${this.formatTime(currentTime)}`);
+        this.controls.currentTime.setAttribute('aria-label', `${this.UIString('Elapsed')} ${this.formatTimeDescription(currentTime)}`);
         this.controls.timeline.value = this.video.currentTime;
         this.remainingTimeClone.innerText = this.controls.remainingTime.innerText = this.formatTime(timeRemaining);
-        this.controls.remainingTime.setAttribute('aria-label', `${this.UIString('Remaining')} ${this.formatTime(timeRemaining)}`);
+        this.controls.remainingTime.setAttribute('aria-label', `${this.UIString('Remaining')} ${this.formatTimeDescription(timeRemaining)}`);
+        
+        // Mark the timeline value in percentage format in accessibility.
+        var timeElapsedPercent = currentTime / this.video.duration;
+        timeElapsedPercent = Math.max(Math.min(1, timeElapsedPercent), 0);
+        this.controls.timeline.setAttribute('aria-valuetext', `${parseInt(timeElapsedPercent * 100)}%`);
     },
     
     updateControlsWhileScrubbing: function()