Consider user's preferred language when choosing text tracks
authoreric.carlson@apple.com <eric.carlson@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 2 Feb 2012 05:10:31 +0000 (05:10 +0000)
committereric.carlson@apple.com <eric.carlson@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 2 Feb 2012 05:10:31 +0000 (05:10 +0000)
https://bugs.webkit.org/show_bug.cgi?id=74121

Reviewed by Alexey Proskuryakov.

Source/WebCore:

Tests: media/track/track-language-preference.html
       media/track/track-prefer-captions.html

* html/HTMLMediaElement.cpp:
(WebCore::HTMLMediaElement::loadTimerFired): configureTextTracks -> configureNewTextTracks.
(WebCore::HTMLMediaElement::textTracksAreReady): Add more comments.
(WebCore::HTMLMediaElement::textTrackModeChanged): Ditto.
(WebCore::HTMLMediaElement::showingTrackWithSameKind): Minor restructuring.
(WebCore::HTMLMediaElement::userIsInterestedInThisTrackKind): Renamed from userIsInterestedInThisTrack,
    don't consider user's language preference.
(WebCore::HTMLMediaElement::configureTextTrackGroup): New, configure all tracks in a group,
    considering user's kind and language preferences.
(WebCore::HTMLMediaElement::configureNewTextTracks): New, configure all newly added tracks.
* html/HTMLMediaElement.h:
(WebCore::HTMLMediaElement::TrackGroup::TrackGroup):
(TrackGroup):

* platform/Language.cpp:
(WebCore::canonicalLanguageIdentifier): New, create a canonicalized version of a language string.
(WebCore::bestMatchingLanguage): New, return the language from the list that best matches the
    specified language.
(WebCore::preferredLanguageFromList): New, return the language in the specified list that best
    matches the user's language preference.
* platform/Language.h:

* testing/Internals.cpp:
(WebCore::Internals::setShouldDisplayTrackType): New, allow DRT to set the track type preference.
(WebCore::Internals::shouldDisplayTrackType): New, allow DRT to read the track type preference.
* testing/Internals.h:
* testing/Internals.idl:

LayoutTests:

* media/track/track-language-preference-expected.txt: Added.
* media/track/track-language-preference.html: Added.
* media/track/track-prefer-captions-expected.txt: Added.
* media/track/track-prefer-captions.html: Added.
* platform/mac/Skipped:

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

13 files changed:
LayoutTests/ChangeLog
LayoutTests/media/track/track-language-preference-expected.txt [new file with mode: 0644]
LayoutTests/media/track/track-language-preference.html [new file with mode: 0644]
LayoutTests/media/track/track-prefer-captions-expected.txt [new file with mode: 0644]
LayoutTests/media/track/track-prefer-captions.html [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/html/HTMLMediaElement.cpp
Source/WebCore/html/HTMLMediaElement.h
Source/WebCore/platform/Language.cpp
Source/WebCore/platform/Language.h
Source/WebCore/testing/Internals.cpp
Source/WebCore/testing/Internals.h
Source/WebCore/testing/Internals.idl

index caf31e3..cfc7adc 100644 (file)
@@ -1,3 +1,16 @@
+2012-02-01  Eric Carlson  <eric.carlson@apple.com>
+
+        Consider user's preferred language when choosing text tracks
+        https://bugs.webkit.org/show_bug.cgi?id=74121
+
+        Reviewed by Alexey Proskuryakov.
+
+        * media/track/track-language-preference-expected.txt: Added.
+        * media/track/track-language-preference.html: Added.
+        * media/track/track-prefer-captions-expected.txt: Added.
+        * media/track/track-prefer-captions.html: Added.
+        * platform/mac/Skipped:
+
 2012-02-01  Shinya Kawanaka  <shinyak@google.com>
 
         Select attribute of HTMLContentElement should be able be changed dynamically.
diff --git a/LayoutTests/media/track/track-language-preference-expected.txt b/LayoutTests/media/track/track-language-preference-expected.txt
new file mode 100644 (file)
index 0000000..a4eb724
--- /dev/null
@@ -0,0 +1,32 @@
+Tests that the user's preferred languages are honored.
+
+**Set track preferences and user preferred languages
+RUN(internals.setShouldDisplayTrackKind(document, 'Captions', true))
+RUN(internals.userPreferredLanguages = ['jp', 'es-ES', 'en', 'fr'])
+
+Test: a track language matches one of the user's preferred languages exactly.
+- creating tracks for: [fr,en,jp].
+EVENT(load)
+EXPECTED (track.readyState == '2') OK
+EXPECTED (track.srclang == 'jp') OK
+
+Test: a track language without locale exactly matches one of the user's preferred languages.
+- creating tracks for: [fr-CH,da].
+EVENT(load)
+EXPECTED (track.readyState == '2') OK
+EXPECTED (track.srclang == 'fr-CH') OK
+
+Test: a track language without locale matches one of the user's preferred languages without locale.
+- creating tracks for: [fr,es-MX].
+EVENT(load)
+EXPECTED (track.readyState == '2') OK
+EXPECTED (track.srclang == 'es-MX') OK
+
+Test: no track language matches any of the user's preferred languages.
+- creating tracks for: [fa,ru,no].
+EVENT(load)
+EXPECTED (track.readyState == '2') OK
+EXPECTED (track.srclang == 'fa') OK
+
+END OF TEST
+
diff --git a/LayoutTests/media/track/track-language-preference.html b/LayoutTests/media/track/track-language-preference.html
new file mode 100644 (file)
index 0000000..1777343
--- /dev/null
@@ -0,0 +1,117 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+
+        <style>
+            video { background-color: yellow; width: 320px; height: 240px;}
+        </style>
+        <script src=../media-file.js></script>
+        <script src=../video-test.js></script>
+        <script>
+
+            var timer = null;
+            var expectedLanguage;
+            var testList = 
+            {
+                current : -1,
+                tests : 
+                [ 
+                    {
+                        description: "a track language matches one of the user's preferred languages exactly", 
+                        languages : ['fr', 'en', 'jp'], 
+                        expectedLanguage : "jp", 
+                    },
+                    {
+                        description: "a track language without locale exactly matches one of the user's preferred languages", 
+                        languages : ['fr-CH', 'da'], 
+                        expectedLanguage : "fr-CH", 
+                    },
+                    {
+                        description: "a track language without locale matches one of the user's preferred languages without locale", 
+                        languages : ['fr', 'es-MX'], 
+                        expectedLanguage : "es-MX", 
+                    },
+                    {
+                        description: "no track language matches any of the user's preferred languages", 
+                        languages : ['fa', 'ru', 'no'], 
+                        expectedLanguage : "fa", 
+                    },
+                ]
+            };
+
+            function runNextTest()
+            {
+                consoleWrite("");
+                testList.current++;
+                if (testList.current >= testList.tests.length) {
+                    endTest();
+                    return;
+                }
+
+                consoleWrite("<b>Test: </b> <em>"+ testList.tests[testList.current].description + ".</em>");
+                createTrackElements(testList.tests[testList.current].languages);
+            }
+
+            function trackLoaded()
+            {
+                consoleWrite("EVENT(load)");
+                
+                // Don't log the event name because the order of the two events in not predictable.
+                track = event.target;
+                testExpected("track.readyState", HTMLTrackElement.LOADED);
+                testExpected("track.srclang", testList.tests[testList.current].expectedLanguage);
+
+                timer = setTimeout(runNextTest, 200);
+            }
+
+            function setPreferences()
+            {
+                if (!window.internals) {
+                    consoleWrite("<b>** This test only works in DRT! **<" + "/b>");
+                    return;
+                }
+
+                consoleWrite("<i>**Set track preferences and user preferred languages<" + "/i>");
+                run("internals.setShouldDisplayTrackKind(document, 'Captions', true)");
+                run("internals.userPreferredLanguages = ['jp', 'es-ES', 'en', 'fr']");
+            }
+            
+            function createTrackElement(language, src)
+            {
+                var track = document.createElement('track');
+                track.setAttribute('kind', "captions");
+                track.setAttribute('src', src);
+                track.setAttribute('srclang', language);
+                track.setAttribute('onload', 'trackLoaded()');
+                video.appendChild(track);
+            }
+
+            function createTrackElements(languages)
+            {
+                var tracks = document.querySelectorAll('track');
+                for (var ndx = 0; ndx < tracks.length; ++ndx)
+                    video.removeChild(tracks[ndx]);
+
+                consoleWrite("<i>- creating tracks for: [" + languages + "].<" + "/i>");
+                for (var ndx = 0; ndx < languages.length; ++ndx)
+                    createTrackElement(languages[ndx], "captions-webvtt/tc004-webvtt-file.vtt");
+            }
+
+            function setup()
+            {
+                findMediaElement();
+
+                setPreferences("Subtitles", true);
+
+                runNextTest();
+            }
+
+        </script>
+    </head>
+    <body onload="setup()">
+        <p>Tests that the user's preferred languages are honored.</p>
+        <video>
+        </video>
+    </body>
+</html>
diff --git a/LayoutTests/media/track/track-prefer-captions-expected.txt b/LayoutTests/media/track/track-prefer-captions-expected.txt
new file mode 100644 (file)
index 0000000..9a8a9e6
--- /dev/null
@@ -0,0 +1,25 @@
+Tests that the user preferences for track kind are honored.
+
+**Set preferences so subtitles and descriptions load, but captions do not
+RUN(internals.setShouldDisplayTrackKind(document, 'Subtitles', true))
+EXPECTED (internals.shouldDisplayTrackKind(document, 'Subtitles') == 'true') OK
+RUN(internals.setShouldDisplayTrackKind(document, 'Captions', false))
+EXPECTED (internals.shouldDisplayTrackKind(document, 'Captions') == 'false') OK
+RUN(internals.setShouldDisplayTrackKind(document, 'TextDescriptions', true))
+EXPECTED (internals.shouldDisplayTrackKind(document, 'TextDescriptions') == 'true') OK
+
+**Create track elements dynamically so they aren't processed by the media element until after preferences have been configured.
+- creating 'subtitles' track.
+- creating 'captions' track.
+- creating 'descriptions' track.
+
+EVENT(load)
+EXPECTED (track.readyState == '2') OK
+EXPECTED (track.kind != 'captions') OK
+
+EVENT(load)
+EXPECTED (track.readyState == '2') OK
+EXPECTED (track.kind != 'captions') OK
+
+END OF TEST
+
diff --git a/LayoutTests/media/track/track-prefer-captions.html b/LayoutTests/media/track/track-prefer-captions.html
new file mode 100644 (file)
index 0000000..ec012d4
--- /dev/null
@@ -0,0 +1,87 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+
+        <style>
+            video { background-color: yellow; width: 320px; height: 240px;}
+        </style>
+        <script src=../media-file.js></script>
+        <script src=../video-test.js></script>
+        <script>
+
+            var timer = null;
+            var counter = 0;
+
+            function trackLoaded()
+            {
+                consoleWrite("EVENT(load)");
+                
+                // Don't log the track type because the load order is not predictable.
+                track = event.target;
+                testExpected("track.readyState", HTMLTrackElement.LOADED);
+                testExpected("track.kind", "captions", "!=");
+
+                // End the test after a brief pause so we allow the third track to load if it will.
+                if (++counter == 2)
+                    timer = setTimeout(endTest, 200);
+
+                consoleWrite("");
+            }
+
+            function setTextTrackPreferences(type, flag)
+            {
+                if (!window.internals) {
+                    consoleWrite("<b>**This test only works in DRT<" + "/b>");
+                    return;
+                }
+
+                run("internals.setShouldDisplayTrackKind(document, '" + type + "', " + flag + ")");
+                testExpected("internals.shouldDisplayTrackKind(document, '" + type + "')", flag);
+            }
+            
+            function createTrackElement(kind, src)
+            {
+                consoleWrite("<i>- creating '" + kind + "' track.<" + "/i>");
+                var track = document.createElement('track');
+                track.setAttribute('kind', kind);
+                track.setAttribute('onload', 'trackLoaded()');
+                track.setAttribute('src', src);
+                video.appendChild(track);
+            }
+
+            function createTrackElements()
+            {
+                var tracks = document.querySelectorAll('track');
+                for (var ndx = 0; ndx < tracks.length; ++ndx)
+                    video.removeChild(tracks[ndx]);
+
+               createTrackElement("subtitles", "captions-webvtt/tc004-webvtt-file.vtt")
+               createTrackElement("captions", "captions-webvtt/tc004-webvtt-file.vtt")
+               createTrackElement("descriptions", "captions-webvtt/tc004-webvtt-file.vtt")
+            }
+
+            function setup()
+            {
+                findMediaElement();
+
+                consoleWrite("<i>**Set preferences so subtitles and descriptions load, but captions do not<" + "/i>");
+                setTextTrackPreferences("Subtitles", true);
+                setTextTrackPreferences("Captions", false);
+                setTextTrackPreferences("TextDescriptions", true);
+
+                // Create track elements dynamically so they aren't processed by the media element
+                // until after we have configured preferences.
+                consoleWrite("<br><i>**Create track elements dynamically so they aren't processed by the media element until after preferences have been configured.<" + "/i>");
+                createTrackElements();
+                consoleWrite("");
+            }
+
+        </script>
+    </head>
+    <body onload="setup()">
+        <p>Tests that the user preferences for track kind are honored.</p>
+        <video>
+        </video>
+    </body>
+</html>
index 1a4116c..2a4ca69 100644 (file)
@@ -1,3 +1,41 @@
+2012-02-01  Eric Carlson  <eric.carlson@apple.com>
+
+        Consider user's preferred language when choosing text tracks
+        https://bugs.webkit.org/show_bug.cgi?id=74121
+
+        Reviewed by Alexey Proskuryakov.
+
+        Tests: media/track/track-language-preference.html
+               media/track/track-prefer-captions.html
+
+        * html/HTMLMediaElement.cpp:
+        (WebCore::HTMLMediaElement::loadTimerFired): configureTextTracks -> configureNewTextTracks.
+        (WebCore::HTMLMediaElement::textTracksAreReady): Add more comments.
+        (WebCore::HTMLMediaElement::textTrackModeChanged): Ditto.
+        (WebCore::HTMLMediaElement::showingTrackWithSameKind): Minor restructuring.
+        (WebCore::HTMLMediaElement::userIsInterestedInThisTrackKind): Renamed from userIsInterestedInThisTrack,
+            don't consider user's language preference.
+        (WebCore::HTMLMediaElement::configureTextTrackGroup): New, configure all tracks in a group, 
+            considering user's kind and language preferences.
+        (WebCore::HTMLMediaElement::configureNewTextTracks): New, configure all newly added tracks.
+        * html/HTMLMediaElement.h:
+        (WebCore::HTMLMediaElement::TrackGroup::TrackGroup):
+        (TrackGroup):
+
+        * platform/Language.cpp:
+        (WebCore::canonicalLanguageIdentifier): New, create a canonicalized version of a language string.
+        (WebCore::bestMatchingLanguage): New, return the language from the list that best matches the 
+            specified language.
+        (WebCore::preferredLanguageFromList): New, return the language in the specified list that best
+            matches the user's language preference.
+        * platform/Language.h:
+
+        * testing/Internals.cpp:
+        (WebCore::Internals::setShouldDisplayTrackType): New, allow DRT to set the track type preference.
+        (WebCore::Internals::shouldDisplayTrackType): New, allow DRT to read the track type preference.
+        * testing/Internals.h:
+        * testing/Internals.idl:
+
 2012-02-01  Hayato Ito  <hayato@chromium.org>
 
         Change class hierarycy so that ShadowRoot can inherit DocumentFragment.
index 23ce7e4..e8bb788 100644 (file)
@@ -51,6 +51,7 @@
 #include "HTMLNames.h"
 #include "HTMLSourceElement.h"
 #include "HTMLVideoElement.h"
+#include "Language.h"
 #include "Logging.h"
 #include "MediaController.h"
 #include "MediaControls.h"
@@ -557,7 +558,7 @@ void HTMLMediaElement::loadTimerFired(Timer<HTMLMediaElement>*)
 
 #if ENABLE(VIDEO_TRACK)
     if (m_pendingLoadFlags & TextTrackResource)
-        configureTextTracks();
+        configureNewTextTracks();
 #endif
 
     m_pendingLoadFlags = 0;
@@ -965,6 +966,8 @@ void HTMLMediaElement::updateActiveTextTrackCues(float movieTime)
 
 bool HTMLMediaElement::textTracksAreReady() const
 {
+    // 4.8.10.12.1 Text track model
+    // ...
     // The text tracks of a media element are ready if all the text tracks whose mode was not 
     // in the disabled state when the element's resource selection algorithm last started now
     // have a text track readiness state of loaded or failed to load.
@@ -1000,7 +1003,7 @@ void HTMLMediaElement::textTrackModeChanged(TextTrack* track)
             if (trackElement->track() != track)
                 continue;
             
-            // Mark this track as "configured" so configureTextTrack won't change the mode again.
+            // Mark this track as "configured" so configureNewTextTracks won't change the mode again.
             trackElement->setHasBeenConfigured(true);
             if (track->mode() != TextTrack::DISABLED && trackElement->readyState() == HTMLTrackElement::NONE)
                 trackElement->scheduleLoad();
@@ -2240,15 +2243,13 @@ TextTrackList* HTMLMediaElement::textTracks()
 
 HTMLTrackElement* HTMLMediaElement::showingTrackWithSameKind(HTMLTrackElement* trackElement) const
 {
-    HTMLTrackElement* showingTrack = 0;
-    
     for (Node* node = firstChild(); node; node = node->nextSibling()) {
         if (trackElement == node)
             continue;
         if (!node->hasTagName(trackTag))
             continue;
 
-        showingTrack = static_cast<HTMLTrackElement*>(node);
+        HTMLTrackElement* showingTrack = static_cast<HTMLTrackElement*>(node);
         if (showingTrack->kind() == trackElement->kind() && showingTrack->track()->mode() == TextTrack::SHOWING)
             return showingTrack;
     }
@@ -2315,131 +2316,176 @@ bool HTMLMediaElement::userIsInterestedInThisLanguage(const String&) const
     return true;
 }
 
-bool HTMLMediaElement::userIsInterestedInThisTrack(HTMLTrackElement* trackElement) const
+bool HTMLMediaElement::userIsInterestedInThisTrackKind(String kind) const
 {
-    RefPtr<TextTrack> textTrack = trackElement->track();
-    if (!textTrack)
-        return false;
-
-    String kind = textTrack->kind();
-    if (!TextTrack::isValidKindKeyword(kind))
-        return false;
-
     // If ... the user has indicated an interest in having a track with this text track kind, text track language, ... 
     Settings* settings = document()->settings();
     if (!settings)
         return false;
 
-    if (kind == TextTrack::subtitlesKeyword() || kind == TextTrack::captionsKeyword()) {
-        if (kind == TextTrack::subtitlesKeyword() && !settings->shouldDisplaySubtitles())
-            return false;
-        if (kind == TextTrack::captionsKeyword() && !settings->shouldDisplayCaptions())
-            return false;
-        return userIsInterestedInThisLanguage(trackElement->srclang());
-    }
+    if (kind == TextTrack::subtitlesKeyword())
+        return settings->shouldDisplaySubtitles();
+    if (kind == TextTrack::captionsKeyword())
+        return settings->shouldDisplayCaptions();
+    if (kind == TextTrack::descriptionsKeyword())
+        return settings->shouldDisplayTextDescriptions();
 
-    if (kind == TextTrack::descriptionsKeyword()) {
-        if (!settings->shouldDisplayTextDescriptions())
-            return false;
-        return userIsInterestedInThisLanguage(trackElement->srclang());
-    }
-    
     return false;
 }
 
-void HTMLMediaElement::configureTextTrack(HTMLTrackElement* trackElement)
+void HTMLMediaElement::configureTextTrackGroup(const TrackGroup& group) const
 {
-#if !LOG_DISABLED
-    if (trackElement->hasTagName(trackTag)) {
-        KURL url = trackElement->getNonEmptyURLAttribute(srcAttr);
-        LOG(Media, "HTMLMediaElement::configureTextTrack - 'src' is %s", urlForLogging(url).utf8().data());
+    ASSERT(group.tracks.size());
+
+    String bestMatchingLanguage;
+    if (group.hasSrcLang) {
+        Vector<String> languages;
+        languages.reserveInitialCapacity(group.tracks.size());
+        for (size_t i = 0; i < group.tracks.size(); ++i) {
+            String srcLanguage = group.tracks[i]->track()->language();
+            if (srcLanguage.length())
+                languages.append(srcLanguage);
+        }
+        bestMatchingLanguage = preferredLanguageFromList(languages);
     }
-#endif
 
-    // 4.8.10.12.3 Sourcing out-of-band text tracks
-    
-    // When a text track corresponding to a track element is added to a media element's list of text tracks,
-    // the user agent must set the text track mode appropriately, as determined by the following conditions:
-    RefPtr<TextTrack> textTrack = trackElement->track();
-    if (!textTrack)
-        return;
-    
-    TextTrack::Mode mode = TextTrack::HIDDEN;
-    HTMLTrackElement* trackElementCurrentlyShowing = showingTrackWithSameKind(trackElement);
-    String kind = textTrack->kind();
-    bool hideDefaultTrack = false;
-
-    if (userIsInterestedInThisTrack(trackElement)) {
-        if (kind == TextTrack::subtitlesKeyword() || kind == TextTrack::captionsKeyword()) {
-            // * If the text track kind is subtitles or captions and the user has indicated an interest in having a
+    // First, find the track in the group that should be enabled (if any).
+    HTMLTrackElement* trackElementToEnable = 0;
+    HTMLTrackElement* defaultTrack = 0;
+    HTMLTrackElement* fallbackTrack = 0;
+    for (size_t i = 0; !trackElementToEnable && i < group.tracks.size(); ++i) {
+        HTMLTrackElement* trackElement = group.tracks[i];
+        RefPtr<TextTrack> textTrack = trackElement->track();
+
+        if (userIsInterestedInThisTrackKind(textTrack->kind())) {
+            // * If the text track kind is { [subtitles or captions] [descriptions] } and the user has indicated an interest in having a
             // track with this text track kind, text track language, and text track label enabled, and there is no
             // other text track in the media element's list of text tracks with a text track kind of either subtitles
             // or captions whose text track mode is showing
-            hideDefaultTrack = trackElementCurrentlyShowing && trackElementCurrentlyShowing->track()->showingByDefault();
-            if (!trackElementCurrentlyShowing || hideDefaultTrack) {
-                //    Let the text track mode be showing.
-                //    If there is a text track in the media element's list of text tracks whose text track mode is showing 
-                //    by default, the user agent must furthermore change that text track's text track mode to hidden.
-                mode = TextTrack::SHOWING;
-            }
-        } else if (kind == TextTrack::descriptionsKeyword()) {
-            // * If the text track kind is descriptions and the user has indicated an interest in having text 
-            // descriptions with this text track language and text track label enabled, and there is no other text 
-            // track in the media element's list of text tracks with a text track kind of descriptions whose text 
-            // track mode is showing
-            hideDefaultTrack = trackElementCurrentlyShowing && trackElementCurrentlyShowing->track()->showingByDefault();
-            if (!trackElementCurrentlyShowing || hideDefaultTrack) {
-                //    Let the text track mode be showing.
-                //    If there is a text track in the media element's list of text tracks whose text track mode is showing 
-                //    by default, the user agent must furthermore change that text track's text track mode to hidden.
-                mode = TextTrack::SHOWING;
-            }
-        } else if (kind == TextTrack::chaptersKeyword()) {
+            // ...
             // * If the text track kind is chapters and the text track language is one that the user agent has reason
             // to believe is appropriate for the user, and there is no other text track in the media element's list of
             // text tracks with a text track kind of chapters whose text track mode is showing
             //    Let the text track mode be showing.
-            if (!trackElementCurrentlyShowing)
-                mode = TextTrack::SHOWING;
+            if (bestMatchingLanguage.length()) {
+                if (textTrack->language() == bestMatchingLanguage)
+                    trackElementToEnable = trackElement;
+            } else if (trackElement->isDefault()) {
+                // The user is interested in this type of track, but their language preference doesn't match any track so we will
+                // enable the 'default' track.
+                defaultTrack = trackElement;
+            }
+
+            // Remember the first track that doesn't match language or have 'default' to potentially use as fallback.
+            if (!fallbackTrack)
+                fallbackTrack = trackElement;
+        } else if (!group.visibleTrack && !defaultTrack && trackElement->isDefault()) {
+            // * If the track element has a default attribute specified, and there is no other text track in the media
+            // element's list of text tracks whose text track mode is showing or showing by default
+            //    Let the text track mode be showing by default.
+            defaultTrack = trackElement;
         }
-    } else if (!trackElementCurrentlyShowing && trackElement->isDefault()) {
-        // * If the track element has a default attribute specified, and there is no other text track in the media
-        // element's list of text tracks whose text track mode is showing or showing by default
-        //    Let the text track mode be showing by default.
-        mode = TextTrack::SHOWING;
-        textTrack->setShowingByDefault(false);
-    } else {
-        // Otherwise
-        //    Let the text track mode be disabled.
-        mode = TextTrack::DISABLED;
     }
 
-    ExceptionCode unusedException;
-    if (hideDefaultTrack) {
-        trackElementCurrentlyShowing->track()->setMode(TextTrack::HIDDEN, unusedException);
-        trackElementCurrentlyShowing->track()->setShowingByDefault(false);
+    if (!trackElementToEnable && defaultTrack)
+        trackElementToEnable = defaultTrack;
+
+    // If no track matches the user's preferred language and non was marked 'default', enable the first track
+    // because the user has explicitly stated a preference for this kind of track.
+    if (!trackElementToEnable && fallbackTrack)
+        trackElementToEnable = fallbackTrack;
+
+    for (size_t i = 0; i < group.tracks.size(); ++i) {
+        HTMLTrackElement* trackElement = group.tracks[i];
+        RefPtr<TextTrack> textTrack = trackElement->track();
+        ExceptionCode unusedException;
+        
+        if (trackElementToEnable == trackElement) {
+            textTrack->setMode(TextTrack::SHOWING, unusedException);
+            if (defaultTrack == trackElement)
+                textTrack->setShowingByDefault(true);
+        } else {
+            if (textTrack->showingByDefault()) {
+                // If there is a text track in the media element's list of text tracks whose text track
+                // mode is showing by default, the user agent must furthermore change that text track's
+                // text track mode to hidden.
+                textTrack->setShowingByDefault(false);
+                textTrack->setMode(TextTrack::HIDDEN, unusedException);
+            } else
+                textTrack->setMode(TextTrack::DISABLED, unusedException);
+        }
     }
 
-    textTrack->setMode(mode, unusedException);
+    if (trackElementToEnable && group.defaultTrack && group.defaultTrack != trackElementToEnable) {
+        RefPtr<TextTrack> textTrack = group.defaultTrack->track();
+        if (textTrack && textTrack->showingByDefault()) {
+            ExceptionCode unusedException;
+            textTrack->setShowingByDefault(false);
+            textTrack->setMode(TextTrack::HIDDEN, unusedException);
+        }
+    }
 }
-void HTMLMediaElement::configureTextTracks()
+
+void HTMLMediaElement::configureNewTextTracks()
 {
+    TrackGroup captionAndSubtitleTracks(TrackGroup::CaptionsAndSubtitles);
+    TrackGroup descriptionTracks(TrackGroup::Description);
+    TrackGroup chapterTracks(TrackGroup::Chapter);
+    TrackGroup metadataTracks(TrackGroup::Metadata);
+    TrackGroup otherTracks(TrackGroup::Other);
+
     for (Node* node = firstChild(); node; node = node->nextSibling()) {
         if (!node->hasTagName(trackTag))
             continue;
+
         HTMLTrackElement* trackElement = static_cast<HTMLTrackElement*>(node);
+        RefPtr<TextTrack> textTrack = trackElement->track();
+        if (!textTrack)
+            continue;
+
+        String kind = textTrack->kind();
+        TrackGroup* currentGroup;
+        if (kind == TextTrack::subtitlesKeyword() || kind == TextTrack::captionsKeyword())
+            currentGroup = &captionAndSubtitleTracks;
+        else if (kind == TextTrack::descriptionsKeyword())
+            currentGroup = &descriptionTracks;
+        else if (kind == TextTrack::chaptersKeyword())
+            currentGroup = &chapterTracks;
+        else if (kind == TextTrack::metadataKeyword())
+            currentGroup = &metadataTracks;
+        else
+            currentGroup = &otherTracks;
+
+        if (!currentGroup->visibleTrack && textTrack->mode() == TextTrack::SHOWING)
+            currentGroup->visibleTrack = trackElement;
+        if (!currentGroup->defaultTrack && trackElement->isDefault())
+            currentGroup->defaultTrack = trackElement;
+
+        // Do not add this track to the group if it has already been automatically configured
+        // as we only want to call configureTextTrack once per track so that adding another 
+        // track after the initial configuration doesn't reconfigure every track - only those 
+        // that should be changed by the new addition. For example all metadata tracks are 
+        // disabled by default, and we don't want a track that has been enabled by script 
+        // to be disabled automatically when a new metadata track is added later.
+        if (trackElement->hasBeenConfigured())
+            continue;
         
-        // Only call configureTextTrack once per track so that adding another track after
-        // the initial configuration doesn't reconfigure every track, only those that should
-        // be changed by the new addition. For example all metadata tracks are disabled by 
-        // default, and we don't want a track that has been enabled by script to be disabled
-        // automatically when a new track element is added later.
-        if (!trackElement->hasBeenConfigured())
-            configureTextTrack(trackElement);
+        if (textTrack->language().length())
+            currentGroup->hasSrcLang = true;
+        currentGroup->tracks.append(trackElement);
     }
+    
+    if (captionAndSubtitleTracks.tracks.size())
+        configureTextTrackGroup(captionAndSubtitleTracks);
+    if (descriptionTracks.tracks.size())
+        configureTextTrackGroup(descriptionTracks);
+    if (chapterTracks.tracks.size())
+        configureTextTrackGroup(chapterTracks);
+    if (metadataTracks.tracks.size())
+        configureTextTrackGroup(metadataTracks);
+    if (otherTracks.tracks.size())
+        configureTextTrackGroup(otherTracks);
 }
-
 #endif
 
 bool HTMLMediaElement::havePotentialSourceChild()
index 59143a6..98fdf5a 100644 (file)
@@ -207,8 +207,29 @@ public:
     virtual void trackWasAdded(HTMLTrackElement*);
     virtual void trackWasRemoved(HTMLTrackElement*);
 
-    void configureTextTrack(HTMLTrackElement*);
-    void configureTextTracks();
+    struct TrackGroup {
+        enum GroupKind { CaptionsAndSubtitles, Description, Chapter, Metadata, Other };
+
+        TrackGroup(GroupKind kind)
+            : visibleTrack(0)
+            , defaultTrack(0)
+            , kind(kind)
+            , hasSrcLang(false)
+        {
+        }
+
+        Vector<HTMLTrackElement*> tracks;
+        HTMLTrackElement* visibleTrack;
+        HTMLTrackElement* defaultTrack;
+        GroupKind kind;
+        bool hasSrcLang;
+    };
+
+    void configureTextTrackGroupForLanguage(const TrackGroup&) const;
+    void configureNewTextTracks();
+    void configureTextTrackGroup(const TrackGroup&) const;
+
+    bool userIsInterestedInThisTrackKind(String) const;
     bool textTracksAreReady() const;
     void configureTextTrackDisplay();
 
@@ -401,7 +422,6 @@ private:
 #if ENABLE(VIDEO_TRACK)
     void updateActiveTextTrackCues(float);
     bool userIsInterestedInThisLanguage(const String&) const;
-    bool userIsInterestedInThisTrack(HTMLTrackElement*) const;
     HTMLTrackElement* showingTrackWithSameKind(HTMLTrackElement*) const;
 
     bool ignoreTrackDisplayUpdateRequests() const { return m_ignoreTrackDisplayUpdate > 0; }
index 03de916..47f09f6 100644 (file)
@@ -85,4 +85,62 @@ Vector<String> userPreferredLanguages()
     return platformUserPreferredLanguages();
 }
 
+static String canonicalLanguageIdentifier(const String& languageCode)
+{
+    String lowercaseLanguageCode = languageCode.lower();
+    
+    if (lowercaseLanguageCode.length() >= 3 && lowercaseLanguageCode[2] == '_')
+        lowercaseLanguageCode.replace(2, 1, "-");
+
+    return lowercaseLanguageCode;
+}
+
+static String bestMatchingLanguage(const String& language, const Vector<String>& languageList)
+{
+    bool canMatchLanguageOnly = (language.length() == 2 || (language.length() >= 3 && language[2] == '-'));
+    String languageWithoutLocaleMatch;
+    String languageMatchButNotLocale;
+
+    for (size_t i = 0; i < languageList.size(); ++i) {
+        String canonicalizedLanguageFromList = canonicalLanguageIdentifier(languageList[i]);
+
+        if (language == canonicalizedLanguageFromList)
+            return languageList[i];
+
+        if (canMatchLanguageOnly && canonicalizedLanguageFromList.length() >= 2) {
+            if (language[0] == canonicalizedLanguageFromList[0] && language[1] == canonicalizedLanguageFromList[1]) {
+                if (!languageWithoutLocaleMatch.length() && canonicalizedLanguageFromList.length() == 2)
+                    languageWithoutLocaleMatch = languageList[i];
+                if (!languageMatchButNotLocale.length() && canonicalizedLanguageFromList.length() >= 3)
+                    languageMatchButNotLocale = languageList[i];
+            }
+        }
+    }
+
+    // If we have both a language-only match and a languge-but-not-locale match, return the 
+    // languge-only match as is considered a "better" match. For example, if the list
+    // provided has both "en-GB" and "en" and the user prefers "en-US" we will return "en".
+    if (languageWithoutLocaleMatch.length())
+        return languageWithoutLocaleMatch;
+
+    if (languageMatchButNotLocale.length())
+        return languageMatchButNotLocale;
+    
+    return emptyString();
+}
+
+String preferredLanguageFromList(const Vector<String>& languageList)
+{
+    Vector<String> preferredLanguages = userPreferredLanguages();
+
+    for (size_t i = 0; i < preferredLanguages.size(); ++i) {
+        String bestMatch = bestMatchingLanguage(canonicalLanguageIdentifier(preferredLanguages[i]), languageList);
+
+        if (bestMatch.length())
+            return bestMatch;
+    }
+
+    return emptyString();
+}
+    
 }
index b148be0..411615c 100644 (file)
@@ -34,6 +34,7 @@ namespace WebCore {
 String defaultLanguage();
 Vector<String> userPreferredLanguages();
 void overrideUserPreferredLanguages(const Vector<String>&);
+String preferredLanguageFromList(const Vector<String>&);
 
 // The observer function will be called when system language changes.
 typedef void (*LanguageChangeObserverFunction)(void* context);
index 1709a9f..16447fc 100644 (file)
@@ -491,4 +491,53 @@ void Internals::setUserPreferredLanguages(const Vector<String>& languages)
     WebCore::overrideUserPreferredLanguages(languages);
 }
 
+void Internals::setShouldDisplayTrackKind(Document* document, const String& kind, bool enabled, ExceptionCode& ec)
+{
+    if (!document || !document->frame() || !document->frame()->settings()) {
+        ec = INVALID_ACCESS_ERR;
+        return;
+    }
+    
+#if ENABLE(VIDEO_TRACK)
+    Settings* settings = document->frame()->settings();
+    
+    if (equalIgnoringCase(kind, "Subtitles"))
+        settings->setShouldDisplaySubtitles(enabled);
+    else if (equalIgnoringCase(kind, "Captions"))
+        settings->setShouldDisplayCaptions(enabled);
+    else if (equalIgnoringCase(kind, "TextDescriptions"))
+        settings->setShouldDisplayTextDescriptions(enabled);
+    else
+        ec = SYNTAX_ERR;
+#else
+    UNUSED_PARAM(kind);
+    UNUSED_PARAM(enabled);
+#endif
+}
+
+bool Internals::shouldDisplayTrackKind(Document* document, const String& kind, ExceptionCode& ec)
+{
+    if (!document || !document->frame() || !document->frame()->settings()) {
+        ec = INVALID_ACCESS_ERR;
+        return false;
+    }
+    
+#if ENABLE(VIDEO_TRACK)
+    Settings* settings = document->frame()->settings();
+    
+    if (equalIgnoringCase(kind, "Subtitles"))
+        return settings->shouldDisplaySubtitles();
+    if (equalIgnoringCase(kind, "Captions"))
+        return settings->shouldDisplayCaptions();
+    if (equalIgnoringCase(kind, "TextDescriptions"))
+        return settings->shouldDisplayTextDescriptions();
+
+    ec = SYNTAX_ERR;
+    return false;
+#else
+    UNUSED_PARAM(kind);
+    return false;
+#endif
+}
+    
 }
index f1d2fb2..b872531 100644 (file)
@@ -102,6 +102,9 @@ public:
     Vector<String> userPreferredLanguages() const;
     void setUserPreferredLanguages(const Vector<String>&);
 
+    void setShouldDisplayTrackKind(Document*, const String& kind, bool, ExceptionCode&);
+    bool shouldDisplayTrackKind(Document*, const String& kind, ExceptionCode&);
+
     static const char* internalsId;
 
     InternalSettings* settings() const { return m_settings.get(); }
index 145aa6e..da081f5 100644 (file)
@@ -73,6 +73,10 @@ module window {
         long lastSpellCheckRequestSequence(in Document document) raises (DOMException);
         long lastSpellCheckProcessedSequence(in Document document) raises (DOMException);
 
+#if defined(ENABLE_VIDEO_TRACK) && ENABLE_VIDEO_TRACK
+        void setShouldDisplayTrackKind(in Document document, in DOMString kind, in boolean enabled) raises (DOMException);
+        boolean shouldDisplayTrackKind(in Document document, in DOMString trackKind) raises (DOMException);
+#endif
         attribute [Custom] Array userPreferredLanguages;
 
         readonly attribute InternalSettings settings;