+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.
--- /dev/null
+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
+
--- /dev/null
+<!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>
--- /dev/null
+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
+
--- /dev/null
+<!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>
+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.
#include "HTMLNames.h"
#include "HTMLSourceElement.h"
#include "HTMLVideoElement.h"
+#include "Language.h"
#include "Logging.h"
#include "MediaController.h"
#include "MediaControls.h"
#if ENABLE(VIDEO_TRACK)
if (m_pendingLoadFlags & TextTrackResource)
- configureTextTracks();
+ configureNewTextTracks();
#endif
m_pendingLoadFlags = 0;
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.
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();
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;
}
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()
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();
#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; }
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();
+}
+
}
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);
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
+}
+
}
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(); }
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;