+2013-03-20 Eric Carlson <eric.carlson@apple.com>
+
+ Allow ports specific text track menu
+ https://bugs.webkit.org/show_bug.cgi?id=112800
+
+ Reviewed by Dean Jackson.
+
+ * media/track/track-user-preferences-expected.txt:
+ * media/track/track-user-preferences.html:
+ * media/video-controls-captions-trackmenu-localized.html:
+ * media/video-controls-captions-trackmenu-sorted.html:
+ * media/video-controls-captions-trackmenu.html:
+ * platform/mac/media/video-controls-captions-trackmenu-expected.txt:
+ * platform/mac/media/video-controls-captions-trackmenu-localized-expected.txt:
+ * platform/mac/media/video-controls-captions-trackmenu-sorted-expected.txt:
+
2013-03-20 Julien Chaffraix <jchaffraix@webkit.org>
[CSS Grid Layout] Improper repainting when grid item change their position
Test 3: select 'fr' track from menu
- show captions menu.
-EXPECTED (trackMenuItems[1].className == '') OK
-EXPECTED (trackMenuItems[2].className == '') OK
-EXPECTED (trackMenuItems[3].className == 'selected') OK
-- click on menu item 2.
+EXPECTED (trackMenuItems[menuIndexForLanguage('French')].className == '') OK
+EXPECTED (trackMenuItems[menuIndexForLanguage('English')].className == '') OK
+EXPECTED (trackMenuItems[menuIndexForLanguage('Norwegian')].className == 'selected') OK
+- click on 'French' menu item.
EVENT(load)
EXPECTED (event.target.srclang == 'fr') OK
EXPECTED (event.target.readyState == '2') OK
Test 5: turning captions off from menu disables caption selection
- show captions menu.
-- click on menu item 0.
+- click on 'Off' menu item.
- creating tracks for: [ru,jp,en].
EXPECTED (video.textTracks[0].mode == 'disabled') OK
EXPECTED (video.textTracks[1].mode == 'disabled') OK
return trackListItems;
}
- function selectCaptionMenuItem(index)
+ function menuIndexForLanguage(language)
{
- consoleWrite("- click on menu item " + index + ".");
var trackMenuItems = trackMenuList();
+ for (i = 0; i < trackMenuItems.length; ++i) {
+ if (trackMenuItems[i].textContent.indexOf(language) >= 0)
+ break;
+ }
+ return (i < trackMenuItems.length) ? i : -1;
+ }
+
+ function selectCaptionMenuItem(language)
+ {
+ var trackMenuItems = trackMenuList();
+ var index = menuIndexForLanguage(language);
+ if (index < 0) {
+ failTest("Menu item " + language + " not found in track list menu.");
+ return;
+ }
+
+ consoleWrite("- click on '" + language + "' menu item.");
var selectedTrackItem = trackMenuItems[index];
var boundingRect = selectedTrackItem.getBoundingClientRect();
var x = boundingRect.left + boundingRect.width / 2;
{
var track = document.createElement('track');
track.setAttribute('kind', "captions");
- track.src = 'data:text/vtt,'+encodeURIComponent("WEBVTT\n\n00:00:00.000 --> 00:00:01.000\nCaption 1\n");
+ track.src = 'data:text/vtt,' + encodeURIComponent("WEBVTT\n\n00:00:00.000 --> 00:00:01.000\nCaption 1\n");
track.setAttribute('srclang', language);
track.setAttribute('onload', 'trackLoaded()');
if (isDefault) {
break;
case 3:
- // Clear the debug setting so it isn't considered in the track selection logic
- internals.settings.setShouldDisplayTrackKind('Captions', false);
-
consoleWrite("<br>Test 3: select 'fr' track from menu");
showCaptionMenu();
break;
case 4:
trackMenuItems = trackMenuList();
- testExpected("trackMenuItems[1].className", "");
- testExpected("trackMenuItems[2].className", "");
- testExpected("trackMenuItems[3].className", "selected");
+ testExpected("trackMenuItems[menuIndexForLanguage('French')].className", "");
+ testExpected("trackMenuItems[menuIndexForLanguage('English')].className", "");
+ testExpected("trackMenuItems[menuIndexForLanguage('Norwegian')].className", "selected");
- selectCaptionMenuItem(2);
+ selectCaptionMenuItem("French");
expectedLanguage = 'fr';
break;
break;
case 7:
- selectCaptionMenuItem(0);
+ selectCaptionMenuItem("Off");
createTrackElements([ 'ru', 'jp', 'en']);
timer = setTimeout(nextStep, 100);
break;
testExpected("item.textContent", "English CC");
item = captionsEntries[2];
- consoleWrite("<br>Third item in captions menu should be labelled 'Unknown'");
- testExpected("item.textContent", "Unknown");
+ consoleWrite("<br>Third item in captions menu should be labelled 'Unknown SDH'");
+ testExpected("item.textContent", "Unknown SDH");
item = captionsEntries[3];
consoleWrite("<br>Fourth item in captions menu should be labelled 'Unknown'");
<script src="video-test.js"></script>
<script src="trackmenu-test.js"></script>
<script>
+ var test = 0;
if (window.testRunner)
testRunner.dumpAsText();
- function testSortedMenu()
+ function textTrackMenuItems()
{
+ // Show and hide the menu to force a recalculation.
+ clickCCButton();
+ clickCCButton();
+
var trackListElement = getTrackListElement();
if (!trackListElement)
return;
failTest("There should be a ul element in track list menu");
return;
}
- var lastTrackLabel = null;
- var trackListItems = trackListSection.querySelectorAll("li");
- if (!trackListItems || trackListItems.length != 7) {
- failTest("There should be seven li elements in the menu");
- return;
- }
- for (var j = 0; j < trackListItems.length; j++) {
- var item = trackListItems[j];
- if (j == 0)
- logResult(item.textContent == "Off", "First item should be labelled off");
- else {
- if (lastTrackLabel)
- logResult(item.textContent > lastTrackLabel, lastTrackLabel + " comes before " + item.textContent);
- else
- logResult(true, item.textContent + " is the first real item in the list");
- lastTrackLabel = item.textContent;
- }
+ return trackListSection.querySelectorAll("li");
+ }
+
+ function testSortedMenu()
+ {
+ switch (test)
+ {
+ case 0:
+ consoleWrite("<br><i>** Test with preferred language set to English, track type set to captions<" + "/i>");
+ run("internals.settings.setShouldDisplayTrackKind('Captions', true)");
+ window.setTimeout(testSortedMenu, 0);
+ break;
+
+ case 1:
+ trackMenuItems = textTrackMenuItems();
+ testExpected("trackMenuItems[0].textContent", "Off");
+ testExpected("trackMenuItems[1].textContent", "u (English) SDH");
+ testExpected("trackMenuItems[2].textContent", "y (English-Australia) SDH");
+ testExpected("trackMenuItems[3].textContent", "v (English)");
+ testExpected("trackMenuItems[4].textContent", "x (English-Australia)");
+ testExpected("trackMenuItems[5].textContent", "w (Japanese) SDH");
+ testExpected("trackMenuItems[6].textContent", "z (Japanese)");
+ testExpected("video.textTracks.length", 6);
+ testExpected("trackMenuItems.length", 7);
+
+ consoleWrite("<br><i>** Change preferred language to Japanese, change preference to subtitles<" + "/i>");
+ run("internals.setUserPreferredLanguages(['ja'])");
+ run("internals.settings.setShouldDisplayTrackKind('Captions', false)");
+ run("internals.settings.setShouldDisplayTrackKind('Subtitles', true)");
+
+ window.setTimeout(testSortedMenu, 0);
+ break;
+
+ case 2:
+ trackMenuItems = textTrackMenuItems();
+ testExpected("trackMenuItems[0].textContent", "Off");
+ testExpected("trackMenuItems[1].textContent", "z (Japanese)");
+ testExpected("trackMenuItems[2].textContent", "v (English)");
+ testExpected("trackMenuItems[3].textContent", "x (English-Australia)");
+ testExpected("video.textTracks.length", 6);
+ testExpected("trackMenuItems.length", 4);
+
+ consoleWrite("");
+ endTest();
+ break;
}
- endTest();
+
+ ++test;
}
function start()
</head>
<body onload="start()">
- <p>Test that captions and subtitles are sorted in the menu.</p>
+ <p>Test that captions and subtitles are sorted in the menu according to user preferences.</p>
<video width="500" height="300" controls>
- <track label="c" kind="captions" src="track/captions-webvtt/captions-fast.vtt" srclang="ja">
- <track label="b" kind="captions" src="track/captions-webvtt/captions-fast.vtt" srclang="en-au">
- <track label="a" kind="captions" src="track/captions-webvtt/captions-fast.vtt" srclang="en">
- <track label="b" kind="subtitles" src="track/captions-webvtt/captions-fast.vtt" srclang="ja">
- <track label="a" kind="subtitles" src="track/captions-webvtt/captions-fast.vtt" srclang="en-au">
- <track label="c" kind="subtitles" src="track/captions-webvtt/captions-fast.vtt" srclang="en">
+ <track label="z" kind="subtitles" src="track/captions-webvtt/captions-fast.vtt" srclang="ja">
+ <track label="y" kind="captions" src="track/captions-webvtt/captions-fast.vtt" srclang="en-au">
+ <track label="x" kind="subtitles" src="track/captions-webvtt/captions-fast.vtt" srclang="en-au">
+ <track label="w" kind="captions" src="track/captions-webvtt/captions-fast.vtt" srclang="ja">
+ <track label="v" kind="subtitles" src="track/captions-webvtt/captions-fast.vtt" srclang="en">
+ <track label="u" kind="captions" src="track/captions-webvtt/captions-fast.vtt" srclang="en">
</video>
</body>
</html>
consoleWrite("<br>*** Add another text track.");
run('video.addTextTrack("captions", "Commentary", "ru")');
window.setTimeout(turnCaptionsOn, 100);
+ consoleWrite("");
}
function trackMenuList()
function testMenu()
{
- var trackListItems = trackMenuList();
+ trackListItems = trackMenuList();
var expectedItemCount = video.textTracks.length + 1;
consoleWrite("There should be " + expectedItemCount + " items in the menu.");
- testExpected(trackListItems.length, expectedItemCount);
+ testExpected("trackListItems.length", expectedItemCount);
}
function selectCaptionMenuItem(index, nextStep)
function turnCaptionsOn()
{
consoleWrite("*** Turning captions on");
- // Click on the third item, which is the second track (Off is the first item in the menu)
+ // Click on the second item, which is the second track (Off is the first item in the menu)
selectCaptionMenuItem(2, testCaptionsVisible);
}
<body onload="start()">
<p>Test that we are able to trigger the list of captions, and select from the list.</p>
<video width="500" height="300" controls>
+ <track kind="captions" src="track/captions-webvtt/captions-fast.vtt" srclang="fr">
<track kind="captions" src="track/captions-webvtt/captions-fast.vtt" srclang="en">
- <track kind="captions" src="track/captions-webvtt/captions-fast.vtt" srclang="en-au">
<track kind="captions" src="track/captions-webvtt/captions-fast.vtt" srclang="ja">
</video>
</body>
*** Add another text track.
RUN(video.addTextTrack("captions", "Commentary", "ru"))
+
*** Turning captions on
There should be 5 items in the menu.
-EXPECTED (5 == '5') OK
+EXPECTED (trackListItems.length == '5') OK
EXPECTED (video.textTracks.length == '4') OK
Track 0 should be showing
EXPECTED (video.textTracks[0].mode == 'showing') OK
RUN(video.removeChild(document.querySelectorAll("track")[0]))
*** Turning captions off
There should be 4 items in the menu.
-EXPECTED (4 == '4') OK
+EXPECTED (trackListItems.length == '4') OK
EXPECTED (video.textTracks.length == '3') OK
Track 0 should be disabled
EXPECTED (video.textTracks[0].mode == 'disabled') OK
EXPECTED (item.textContent == 'Off') OK
Second item in captions menu should be labelled 'English CC'
-EXPECTED (item.textContent == 'English CC'), OBSERVED 'Unknown' FAIL
+EXPECTED (item.textContent == 'English CC'), OBSERVED 'Unknown SDH' FAIL
-Third item in captions menu should be labelled 'Unknown'
-EXPECTED (item.textContent == 'Unknown') OK
+Third item in captions menu should be labelled 'Unknown SDH'
+EXPECTED (item.textContent == 'Unknown SDH'), OBSERVED 'Unknown' FAIL
Fourth item in captions menu should be labelled 'Unknown'
TypeError: 'undefined' is not an object (evaluating 'item.textContent')
-Test that captions and subtitles are sorted in the menu.
+Test that captions and subtitles are sorted in the menu according to user preferences.
EVENT(canplaythrough)
*** Set the user language preference.
RUN(internals.setUserPreferredLanguages(['en']))
-First item should be labelled off OK
-a English (Australia) is the first real item in the list OK
-a English (Australia) comes before a English SDH OK
-a English SDH comes before b English (Australia) SDH OK
-b English (Australia) SDH comes before b Japanese OK
-b Japanese comes before c English OK
-c English comes before c Japanese SDH OK
+
+** Test with preferred language set to English, track type set to captions
+RUN(internals.settings.setShouldDisplayTrackKind('Captions', true))
+EXPECTED (trackMenuItems[0].textContent == 'Off') OK
+EXPECTED (trackMenuItems[1].textContent == 'u (English) SDH') OK
+EXPECTED (trackMenuItems[2].textContent == 'y (English-Australia) SDH') OK
+EXPECTED (trackMenuItems[3].textContent == 'v (English)') OK
+EXPECTED (trackMenuItems[4].textContent == 'x (English-Australia)') OK
+EXPECTED (trackMenuItems[5].textContent == 'w (Japanese) SDH') OK
+EXPECTED (trackMenuItems[6].textContent == 'z (Japanese)') OK
+EXPECTED (video.textTracks.length == '6') OK
+EXPECTED (trackMenuItems.length == '7') OK
+
+** Change preferred language to Japanese, change preference to subtitles
+RUN(internals.setUserPreferredLanguages(['ja']))
+RUN(internals.settings.setShouldDisplayTrackKind('Captions', false))
+RUN(internals.settings.setShouldDisplayTrackKind('Subtitles', true))
+EXPECTED (trackMenuItems[0].textContent == 'Off') OK
+EXPECTED (trackMenuItems[1].textContent == 'z (Japanese)') OK
+EXPECTED (trackMenuItems[2].textContent == 'v (English)') OK
+EXPECTED (trackMenuItems[3].textContent == 'x (English-Australia)') OK
+EXPECTED (video.textTracks.length == '6') OK
+EXPECTED (trackMenuItems.length == '4') OK
+
END OF TEST
+2013-03-20 Eric Carlson <eric.carlson@apple.com>
+
+ Allow ports specific text track menu
+ https://bugs.webkit.org/show_bug.cgi?id=112800
+
+ Reviewed by Dean Jackson.
+
+ No new tests, existing tests updated for changes.
+
+ * English.lproj/Localizable.strings: Add localizable strings for text track names.
+
+ * html/HTMLMediaElement.cpp:
+ (WebCore::HTMLMediaElement::HTMLMediaElement): userPrefersCaptions -> shouldShowCaptions.
+ (WebCore::HTMLMediaElement::setSelectedTextTrack): toggleTrackAtIndex -> setSelectedTextTrack.
+ (WebCore::HTMLMediaElement::userPrefersCaptions): userPrefersCaptions -> shouldShowCaptions.
+ (WebCore::HTMLMediaElement::userIsInterestedInThisTrackKind): Get preferences from CaptionUserPreferences
+ instead of from Settings.
+ (WebCore::HTMLMediaElement::setSelectedTextTrack): Renamed from toggleTrackAtIndex. Now takes
+ a TextTrack* instead of an int.
+ (WebCore::HTMLMediaElement::captionPreferencesChanged): Don't force a recalculation unless
+ captions are enabled/disabled to avoid unnecessary thrash.
+ * html/HTMLMediaElement.h:
+
+ * html/shadow/MediaControlElements.cpp:
+ (WebCore::MediaControlClosedCaptionsTrackListElement::defaultEventHandler): Use the menu to track
+ map instead of just the track index.
+ (WebCore::MediaControlClosedCaptionsTrackListElement::updateDisplay): Build and configure the
+ menu from the menu to track map.
+ (WebCore::MediaControlClosedCaptionsTrackListElement::rebuildTrackListMenu): Start with a list
+ of tracks already sorted according to platform specific rules. Build a menu to track map
+ so tracks are retained while the menu is visible.
+ * html/shadow/MediaControlElements.h:
+
+ * html/shadow/MediaControls.cpp:
+ (WebCore::MediaControls::textTrackPreferencesChanged): Call closedCaptionTracksChanged so the
+ track menu will be rebuilt before it is shown again.
+
+ * html/track/InbandTextTrack.cpp:
+ (WebCore::InbandTextTrack::containsOnlyForcedSubtitles): New, passthrough to the private track.
+ (WebCore::InbandTextTrack::isMainProgramContent): Ditto.
+ (WebCore::InbandTextTrack::isEasyToRead): Ditto.
+ * html/track/InbandTextTrack.h:
+ * html/track/TextTrack.h:
+
+ * html/track/TextTrackList.cpp:
+ (WebCore::TextTrackList::contains): New.
+ * html/track/TextTrackList.h:
+
+ * page/CaptionUserPreferences.cpp:
+ (WebCore::CaptionUserPreferences::CaptionUserPreferences): Pull in from .h ditto.
+ (WebCore::CaptionUserPreferences::~CaptionUserPreferences): Ditto.
+ (WebCore::CaptionUserPreferences::shouldShowCaptions): Renamed from userPrefersCaptions.
+ (WebCore::CaptionUserPreferences::setShouldShowCaptions): New.
+ (WebCore::CaptionUserPreferences::userPrefersCaptions): Ditto.
+ (WebCore::CaptionUserPreferences::setUserPrefersCaptions): Ditto.
+ (WebCore::CaptionUserPreferences::userPrefersSubtitles): Ditto.
+ (WebCore::CaptionUserPreferences::setUserPrefersSubtitles): Ditto.
+ (WebCore::CaptionUserPreferences::userPrefersTextDescriptions): Ditto.
+ (WebCore::CaptionUserPreferences::setUserPrefersTextDescriptions): Ditto.
+ (WebCore::CaptionUserPreferences::displayNameForTrack): Ditto.
+ (WebCore::CaptionUserPreferences::sortedTrackListForMenu): Ditto.
+ * page/CaptionUserPreferences.h:
+
+ * page/CaptionUserPreferencesMac.h:
+ * page/CaptionUserPreferencesMac.mm:
+ (WebCore::CaptionUserPreferencesMac::shouldShowCaptions): Renamed from userPrefersCaptions.
+ (WebCore::CaptionUserPreferencesMac::userPrefersCaptions): New.
+ (WebCore::CaptionUserPreferences::userPrefersSubtitles): Ditto.
+ (WebCore::trackDisplayName): Update logic used to build track title.
+ (WebCore::CaptionUserPreferencesMac::displayNameForTrack): Call trackDisplayName.
+ (WebCore::textTrackCompare): Text track title sort comparison function.
+ (WebCore::CaptionUserPreferencesMac::sortedTrackListForMenu): New. Sort the list of tracks
+ according to title, language, and user preferences.
+
+ * platform/Language.cpp:
+ (WebCore::displayNameForLanguageLocale): Don't leak the CFLocale.
+
+ * platform/LocalizedStrings.cpp:
+ (WebCore::textTrackCountryAndLanguageMenuItemText): New.
+ (WebCore::textTrackLanguageMenuItemText): Ditto.
+ (WebCore::closedCaptionTrackMenuItemText): Ditto.
+ (WebCore::sdhTrackMenuItemText): Ditto.
+ (WebCore::easyReaderTrackMenuItemText): Ditto.
+ * platform/LocalizedStrings.h:
+
+ * platform/graphics/InbandTextTrackPrivate.h:
+ * platform/graphics/avfoundation/objc/InbandTextTrackPrivateAVFObjC.h:
+ * platform/graphics/avfoundation/objc/InbandTextTrackPrivateAVFObjC.mm:
+ (WebCore::InbandTextTrackPrivateAVFObjC::containsOnlyForcedSubtitles): New, return AVFoundation property.
+ (WebCore::InbandTextTrackPrivateAVFObjC::isMainProgramContent): Ditto.
+ (WebCore::InbandTextTrackPrivateAVFObjC::isEasyToRead): Ditto.
+
+ * testing/InternalSettings.cpp:
+ (WebCore::InternalSettings::setShouldDisplayTrackKind): Set setting via CaptionUserPreferences
+ instead of from Settings.
+ (WebCore::InternalSettings::shouldDisplayTrackKind): Get setting from CaptionUserPreferences
+ instead of from Settings.
+
2013-03-20 Zan Dobersek <zdobersek@igalia.com>
[GTK] Clean up TemporaryLinkStubs.cpp
if (document->page()) {
CaptionUserPreferences* captionPreferences = document->page()->group().captionPreferences();
if (captionPreferences->userHasCaptionPreferences())
- m_disableCaptions = !captionPreferences->userPrefersCaptions();
+ m_disableCaptions = !captionPreferences->shouldShowCaptions();
}
#endif
}
TrackDisplayUpdateScope scope(this);
if (!platformTrack) {
- toggleTrackAtIndex(textTracksOffIndex(), true);
+ setSelectedTextTrack(0);
return;
}
if (i == m_textTracks->length())
return;
- toggleTrackAtIndex(i, true);
+ setSelectedTextTrack(textTrack);
}
Vector<RefPtr<PlatformTextTrack> > HTMLMediaElement::platformTextTracks()
return false;
CaptionUserPreferences* captionPreferences = page->group().captionPreferences();
- return captionPreferences->userHasCaptionPreferences() && captionPreferences->userPrefersCaptions();
+ return captionPreferences->userHasCaptionPreferences() && captionPreferences->shouldShowCaptions();
}
bool HTMLMediaElement::userIsInterestedInThisTrackKind(String kind) const
if (m_disableCaptions)
return false;
- Settings* settings = document()->settings();
+ Page* page = document()->page();
+ if (!page)
+ return false;
+
+ CaptionUserPreferences* captionPreferences = page->group().captionPreferences();
bool userPrefersCaptionsOrSubtitles = m_closedCaptionsVisible || userPrefersCaptions();
if (kind == TextTrack::subtitlesKeyword())
- return (settings && settings->shouldDisplaySubtitles()) || userPrefersCaptionsOrSubtitles;
+ return captionPreferences->userPrefersSubtitles() || userPrefersCaptionsOrSubtitles;
if (kind == TextTrack::captionsKeyword())
- return (settings && settings->shouldDisplayCaptions()) || userPrefersCaptionsOrSubtitles;
+ return captionPreferences->userPrefersCaptions() || userPrefersCaptionsOrSubtitles;
if (kind == TextTrack::descriptionsKeyword())
- return settings && settings->shouldDisplayTextDescriptions();
+ return captionPreferences->userPrefersTextDescriptions() || userPrefersCaptionsOrSubtitles;
return false;
}
trackToEnable->setMode(TextTrack::showingKeyword());
}
-void HTMLMediaElement::toggleTrackAtIndex(int index, bool exclusive)
+void HTMLMediaElement::setSelectedTextTrack(TextTrack* trackToSelect)
{
TextTrackList* trackList = textTracks();
if (!trackList || !trackList->length())
return;
-
- CaptionUserPreferences* captionPreferences = document()->page() ? document()->page()->group().captionPreferences() : 0;
- if (captionPreferences)
- captionPreferences->setUserPrefersCaptions(index != textTracksOffIndex());
+ if (trackToSelect && !trackList->contains(trackToSelect))
+ return;
for (int i = 0, length = trackList->length(); i < length; ++i) {
TextTrack* track = trackList->item(i);
- if (i == index) {
- track->setMode(TextTrack::showingKeyword());
- if (captionPreferences && track->language().length())
- captionPreferences->setPreferredLanguage(track->language());
- }
- else if (exclusive || index == HTMLMediaElement::textTracksOffIndex())
+ if (!trackToSelect || track != trackToSelect)
track->setMode(TextTrack::disabledKeyword());
+ else
+ track->setMode(TextTrack::showingKeyword());
+ }
+
+ CaptionUserPreferences* captionPreferences = document()->page() ? document()->page()->group().captionPreferences() : 0;
+ if (captionPreferences) {
+ captionPreferences->setShouldShowCaptions(trackToSelect);
+ if (trackToSelect && trackToSelect->language().length())
+ captionPreferences->setPreferredLanguage(trackToSelect->language());
}
}
if (!isVideo())
return;
- m_processingPreferenceChange = true;
- setClosedCaptionsVisible(userPrefersCaptions());
-
if (hasMediaControls())
mediaControls()->textTrackPreferencesChanged();
+
+ bool prefersCaptions = userPrefersCaptions();
+ if (m_disableCaptions == !prefersCaptions)
+ return;
+
+ m_processingPreferenceChange = true;
+ setClosedCaptionsVisible(prefersCaptions);
}
void HTMLMediaElement::markCaptionAndSubtitleTracksAsUnconfigured()
void configureTextTracks();
void configureTextTrackGroup(const TrackGroup&);
- void toggleTrackAtIndex(int index, bool exclusive = true);
+ void setSelectedTextTrack(TextTrack*);
static int textTracksOffIndex() { return -1; }
static int textTracksIndexNotFound() { return -2; }
// Check if the event target has such a custom element and, if so,
// tell the HTMLMediaElement to enable that track.
+ RefPtr<TextTrack> textTrack;
+ MenuItemToTrackMap::iterator iter = m_menuToTrackMap.find(toElement(target));
+ if (iter != m_menuToTrackMap.end())
+ textTrack = iter->value;
+ m_menuToTrackMap.clear();
+ m_controls->toggleClosedCaptionTrackList();
+
int trackIndex = trackListIndexForElement(toElement(target));
if (trackIndex == HTMLMediaElement::textTracksIndexNotFound())
return;
if (!mediaElement)
return;
- mediaElement->toggleTrackAtIndex(trackIndex);
+ if (textTrack)
+ mediaElement->setSelectedTextTrack(textTrack.get());
+ else if (trackIndex == HTMLMediaElement::textTracksOffIndex())
+ mediaElement->setSelectedTextTrack(0);
- // We've selected a track to display, so we can now close the menu.
- m_controls->toggleClosedCaptionTrackList();
updateDisplay();
}
if (m_trackListHasChanged)
rebuildTrackListMenu();
-
+
bool captionsVisible = mediaElement->closedCaptionsVisible();
for (unsigned i = 0, length = m_menuItems.size(); i < length; ++i) {
RefPtr<Element> trackItem = m_menuItems[i];
+
int trackIndex = trackListIndexForElement(trackItem.get());
- if (trackIndex != HTMLMediaElement::textTracksIndexNotFound()) {
- if (trackIndex == HTMLMediaElement::textTracksOffIndex()) {
- if (captionsVisible)
- trackItem->classList()->remove(selectedClassValue, ASSERT_NO_EXCEPTION);
- else
- trackItem->classList()->add(selectedClassValue, ASSERT_NO_EXCEPTION);
- } else {
- TextTrack* track = trackList->item(trackIndex);
- if (!track)
- continue;
- if (track->mode() == TextTrack::showingKeyword())
- trackItem->classList()->add(selectedClassValue, ASSERT_NO_EXCEPTION);
- else
- trackItem->classList()->remove(selectedClassValue, ASSERT_NO_EXCEPTION);
- }
- }
- }
-#endif
-}
+ if (trackIndex == HTMLMediaElement::textTracksIndexNotFound())
+ continue;
-#if ENABLE(VIDEO_TRACK)
-static void insertTextTrackMenuItemIntoSortedContainer(RefPtr<Element>& item, RefPtr<Element>& container)
-{
- // The container will always have the "Off" entry already present and it
- // should remain at the start of the list.
- ASSERT(container->childNodeCount() > 0);
- ASSERT(item->childNodeCount() == 1); // Each item should have a single text node child for the label.
- String itemLabel = toText(item->firstChild())->wholeText();
-
- // This is an insertion sort :( However, there shouldn't be a horrible number of text track items.
- for (int i = 1, numChildNodes = container->childNodeCount(); i < numChildNodes; ++i) {
- Node* child = container->childNode(i);
- ASSERT(child->childNodeCount() == 1); // Each item should have a single text node child for the label.
- String childLabel = toText(child->firstChild())->wholeText();
- if (codePointCompareLessThan(itemLabel, childLabel)) {
- container->insertBefore(item, child);
- return;
+ if (trackIndex == HTMLMediaElement::textTracksOffIndex()) {
+ if (captionsVisible)
+ trackItem->classList()->remove(selectedClassValue, ASSERT_NO_EXCEPTION);
+ else
+ trackItem->classList()->add(selectedClassValue, ASSERT_NO_EXCEPTION);
+ continue;
}
+
+ RefPtr<TextTrack> textTrack;
+ MenuItemToTrackMap::iterator iter = m_menuToTrackMap.find(trackItem.get());
+ if (iter == m_menuToTrackMap.end())
+ continue;
+ textTrack = iter->value;
+ if (!textTrack)
+ continue;
+ if (textTrack->mode() == TextTrack::showingKeyword())
+ trackItem->classList()->add(selectedClassValue, ASSERT_NO_EXCEPTION);
+ else
+ trackItem->classList()->remove(selectedClassValue, ASSERT_NO_EXCEPTION);
}
- container->appendChild(item);
-}
#endif
+}
void MediaControlClosedCaptionsTrackListElement::rebuildTrackListMenu()
{
m_menuItems.clear();
m_trackListHasChanged = false;
+ m_menuToTrackMap.clear();
if (!mediaController()->hasClosedCaptions())
return;
return;
TextTrackList* trackList = mediaElement->textTracks();
-
if (!trackList || !trackList->length())
return;
Document* doc = document();
CaptionUserPreferences* captionsUserPreferences = doc->page()->group().captionPreferences();
+ Vector<RefPtr<TextTrack> > tracksForMenu = captionsUserPreferences->sortedTrackListForMenu(trackList);
RefPtr<Element> captionsHeader = doc->createElement(h3Tag, ASSERT_NO_EXCEPTION);
captionsHeader->appendChild(doc->createTextNode(textTrackSubtitlesText()));
appendChild(captionsHeader);
RefPtr<Element> captionsMenuList = doc->createElement(ulTag, ASSERT_NO_EXCEPTION);
- RefPtr<Element> trackItem;
-
- trackItem = doc->createElement(liTag, ASSERT_NO_EXCEPTION);
- trackItem->appendChild(doc->createTextNode(textTrackOffText()));
- trackItem->setAttribute(trackIndexAttributeName(), textTracksOffAttrValue, ASSERT_NO_EXCEPTION);
- captionsMenuList->appendChild(trackItem);
- m_menuItems.append(trackItem);
+ RefPtr<Element> menuItem;
+ menuItem = doc->createElement(liTag, ASSERT_NO_EXCEPTION);
+ menuItem->appendChild(doc->createTextNode(textTrackOffText()));
+ menuItem->setAttribute(trackIndexAttributeName(), textTracksOffAttrValue, ASSERT_NO_EXCEPTION);
+ captionsMenuList->appendChild(menuItem);
+ m_menuItems.append(menuItem);
- for (unsigned i = 0, length = trackList->length(); i < length; ++i) {
- TextTrack* track = trackList->item(i);
- trackItem = doc->createElement(liTag, ASSERT_NO_EXCEPTION);
+ for (unsigned i = 0, length = tracksForMenu.size(); i < length; ++i) {
+ RefPtr<TextTrack> textTrack = tracksForMenu[i];
+ menuItem = doc->createElement(liTag, ASSERT_NO_EXCEPTION);
// Add a custom attribute to the <li> element which will allow
// us to easily associate the user tapping here with the
// track. Since this list is rebuilt if the tracks change, we
// should always be in sync.
- trackItem->setAttribute(trackIndexAttributeName(), String::number(i), ASSERT_NO_EXCEPTION);
+ menuItem->setAttribute(trackIndexAttributeName(), String::number(i), ASSERT_NO_EXCEPTION);
- if (captionsUserPreferences)
- trackItem->appendChild(doc->createTextNode(captionsUserPreferences->displayNameForTrack(track)));
- else
- trackItem->appendChild(doc->createTextNode(track->label()));
+ menuItem->appendChild(doc->createTextNode(captionsUserPreferences->displayNameForTrack(textTrack.get())));
- insertTextTrackMenuItemIntoSortedContainer(trackItem, captionsMenuList);
- m_menuItems.append(trackItem);
+ captionsMenuList->appendChild(menuItem);
+ m_menuItems.append(menuItem);
+ m_menuToTrackMap.add(menuItem, textTrack);
}
appendChild(captionsMenuList);
typedef Vector<RefPtr<Element> > TrackMenuItems;
TrackMenuItems m_menuItems;
+#if ENABLE(VIDEO_TRACK)
+ typedef HashMap<RefPtr<Element>, RefPtr<TextTrack> > MenuItemToTrackMap;
+ MenuItemToTrackMap m_menuToTrackMap;
+#endif
MediaControls* m_controls;
bool m_trackListHasChanged;
};
{
if (m_textDisplayContainer)
m_textDisplayContainer->updateSizes(true);
+ closedCaptionTracksChanged();
}
#endif
return m_private->isClosedCaptions();
}
+bool InbandTextTrack::containsOnlyForcedSubtitles() const
+{
+ if (!m_private)
+ return false;
+
+ return m_private->containsOnlyForcedSubtitles();
+}
+
+bool InbandTextTrack::isMainProgramContent() const
+{
+ if (!m_private)
+ return false;
+
+ return m_private->isMainProgramContent();
+}
+
+bool InbandTextTrack::isEasyToRead() const
+{
+ if (!m_private)
+ return false;
+
+ return m_private->isEasyToRead();
+}
+
size_t InbandTextTrack::inbandTrackIndex()
{
ASSERT(m_private);
virtual ~InbandTextTrack();
virtual bool isClosedCaptions() const OVERRIDE;
+ virtual bool containsOnlyForcedSubtitles() const OVERRIDE;
+ virtual bool isMainProgramContent() const OVERRIDE;
+ virtual bool isEasyToRead() const OVERRIDE;
virtual void setMode(const AtomicString&) OVERRIDE;
size_t inbandTrackIndex();
}
#endif
+bool TextTrack::isMainProgramContent() const
+{
+ // "Main program" content is intrinsic to the presentation of the media file, regardless of locale. Content such as
+ // directors commentary is not "main program" because it is not essential for the presentation. HTML5 doesn't have
+ // a way to express this in a machine-reable form, it is typically done with the track label, so we assume that caption
+ // tracks are main content and all other track types are not.
+ return m_kind == captionsKeyword();
+}
+
} // namespace WebCore
#endif
virtual bool isClosedCaptions() const { return false; }
+ virtual bool containsOnlyForcedSubtitles() const { return false; }
+ virtual bool isMainProgramContent() const;
+ virtual bool isEasyToRead() const { return false; }
+
int trackIndex();
void invalidateTrackIndex();
tracks->remove(index);
}
+bool TextTrackList::contains(TextTrack* track) const
+{
+ const Vector<RefPtr<TextTrack> >* tracks = 0;
+
+ if (track->trackType() == TextTrack::TrackElement)
+ tracks = &m_elementTracks;
+ else if (track->trackType() == TextTrack::AddTrack)
+ tracks = &m_addTrackTracks;
+ else if (track->trackType() == TextTrack::InBand)
+ tracks = &m_inbandTracks;
+ else
+ ASSERT_NOT_REACHED();
+
+ return tracks->find(track) != notFound;
+}
+
const AtomicString& TextTrackList::interfaceName() const
{
return eventNames().interfaceForTextTrackList;
unsigned length() const;
int getTrackIndex(TextTrack*);
int getTrackIndexRelativeToRenderedTracks(TextTrack*);
+ bool contains(TextTrack*) const;
TextTrack* item(unsigned index);
void append(PassRefPtr<TextTrack>);
#if ENABLE(VIDEO_TRACK)
#include "CaptionUserPreferences.h"
+#include "Page.h"
#include "PageGroup.h"
+#include "Settings.h"
+#include "TextTrackList.h"
+#include <wtf/NonCopyingSort.h>
namespace WebCore {
+CaptionUserPreferences::CaptionUserPreferences(PageGroup* group)
+ : m_pageGroup(group)
+ , m_timer(this, &CaptionUserPreferences::timerFired)
+ , m_testingMode(false)
+ , m_havePreferences(false)
+ , m_shouldShowCaptions(false)
+{
+}
+
+CaptionUserPreferences::~CaptionUserPreferences()
+{
+}
+
+bool CaptionUserPreferences::shouldShowCaptions() const
+{
+ return m_testingMode ? m_shouldShowCaptions : false;
+}
+
+void CaptionUserPreferences::timerFired(Timer<CaptionUserPreferences>*)
+{
+ captionPreferencesChanged();
+}
+
+void CaptionUserPreferences::notify()
+{
+ if (!m_testingMode)
+ return;
+
+ m_havePreferences = true;
+ if (!m_timer.isActive())
+ m_timer.startOneShot(0);
+}
+
+void CaptionUserPreferences::setShouldShowCaptions(bool preference)
+{
+ m_shouldShowCaptions = preference;
+ notify();
+}
+
+bool CaptionUserPreferences::userPrefersCaptions() const
+{
+ Page* page = *(pageGroup()->pages().begin());
+ if (!page)
+ return false;
+
+ return page->settings()->shouldDisplayCaptions();
+}
+
void CaptionUserPreferences::setUserPrefersCaptions(bool preference)
{
- m_userPrefersCaptions = preference;
- if (m_testingMode) {
- m_havePreferences = true;
- captionPreferencesChanged();
- }
+ Page* page = *(pageGroup()->pages().begin());
+ if (!page)
+ return;
+
+ page->settings()->setShouldDisplayCaptions(preference);
+ notify();
+}
+
+bool CaptionUserPreferences::userPrefersSubtitles() const
+{
+ Page* page = *(pageGroup()->pages().begin());
+ if (!page)
+ return false;
+
+ return page->settings()->shouldDisplaySubtitles();
+}
+
+void CaptionUserPreferences::setUserPrefersSubtitles(bool preference)
+{
+ Page* page = *(pageGroup()->pages().begin());
+ if (!page)
+ return;
+
+ page->settings()->setShouldDisplaySubtitles(preference);
+ notify();
+}
+
+bool CaptionUserPreferences::userPrefersTextDescriptions() const
+{
+ Page* page = *(pageGroup()->pages().begin());
+ if (!page)
+ return false;
+
+ return page->settings()->shouldDisplayTextDescriptions();
+}
+
+void CaptionUserPreferences::setUserPrefersTextDescriptions(bool preference)
+{
+ Page* page = *(pageGroup()->pages().begin());
+ if (!page)
+ return;
+
+ page->settings()->setShouldDisplayTextDescriptions(preference);
+ notify();
}
void CaptionUserPreferences::captionPreferencesChanged()
void CaptionUserPreferences::setPreferredLanguage(String language)
{
m_userPreferredLanguage = language;
- if (m_testingMode) {
- m_havePreferences = true;
- captionPreferencesChanged();
- }
+ notify();
}
-String CaptionUserPreferences::displayNameForTrack(TextTrack* track) const
+static String trackDisplayName(TextTrack* track)
{
if (track->label().isEmpty() && track->language().isEmpty())
return textTrackNoLabelText();
return track->language();
}
+String CaptionUserPreferences::displayNameForTrack(TextTrack* track) const
+{
+ return trackDisplayName(track);
+}
+
+static bool textTrackCompare(const RefPtr<TextTrack>& a, const RefPtr<TextTrack>& b)
+{
+ return codePointCompare(trackDisplayName(a.get()), trackDisplayName(b.get())) < 0;
+}
+
+Vector<RefPtr<TextTrack> > CaptionUserPreferences::sortedTrackListForMenu(TextTrackList* trackList)
+{
+ ASSERT(trackList);
+
+ Vector<RefPtr<TextTrack> > tracksForMenu;
+
+ for (unsigned i = 0, length = trackList->length(); i < length; ++i)
+ tracksForMenu.append(trackList->item(i));
+
+ nonCopyingSort(tracksForMenu.begin(), tracksForMenu.end(), textTrackCompare);
+
+ return tracksForMenu;
+}
+
}
#endif // ENABLE(VIDEO_TRACK)
/*
- * Copyright (C) 2012 Apple Inc. All rights reserved.
+ * Copyright (C) 2012, 2013 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
#include "Language.h"
#include "LocalizedStrings.h"
#include "TextTrack.h"
+#include "Timer.h"
#include <wtf/PassOwnPtr.h>
#include <wtf/text/AtomicString.h>
namespace WebCore {
class PageGroup;
+class TextTrackList;
class CaptionUserPreferences {
public:
static PassOwnPtr<CaptionUserPreferences> create(PageGroup* group) { return adoptPtr(new CaptionUserPreferences(group)); }
- virtual ~CaptionUserPreferences() { }
+ virtual ~CaptionUserPreferences();
virtual bool userHasCaptionPreferences() const { return m_testingMode && m_havePreferences; }
- virtual bool userPrefersCaptions() const { return m_testingMode ? m_userPrefersCaptions : false; }
- virtual void setUserPrefersCaptions(bool preference);
+ virtual bool shouldShowCaptions() const;
+ virtual void setShouldShowCaptions(bool);
+
+ virtual bool userPrefersCaptions() const;
+ virtual void setUserPrefersCaptions(bool);
+
+ virtual bool userPrefersSubtitles() const;
+ virtual void setUserPrefersSubtitles(bool preference);
+
+ virtual bool userPrefersTextDescriptions() const;
+ virtual void setUserPrefersTextDescriptions(bool preference);
+
virtual float captionFontSizeScale(bool& important) const { important = false; return 0.05f; }
virtual String captionsStyleSheetOverride() const { return emptyString(); }
virtual Vector<String> preferredLanguages() const;
virtual String displayNameForTrack(TextTrack*) const;
+ virtual Vector<RefPtr<TextTrack> > sortedTrackListForMenu(TextTrackList*);
virtual bool testingMode() const { return m_testingMode; }
virtual void setTestingMode(bool override) { m_testingMode = override; }
- PageGroup* pageGroup() { return m_pageGroup; }
+ PageGroup* pageGroup() const { return m_pageGroup; }
protected:
- CaptionUserPreferences(PageGroup* group)
- : m_pageGroup(group)
- , m_testingMode(false)
- , m_havePreferences(false)
- , m_userPrefersCaptions(false)
- {
- }
+ CaptionUserPreferences(PageGroup*);
private:
+ void timerFired(Timer<CaptionUserPreferences>*);
+ void notify();
+
PageGroup* m_pageGroup;
+ Timer<CaptionUserPreferences> m_timer;
String m_userPreferredLanguage;
bool m_testingMode;
bool m_havePreferences;
- bool m_userPrefersCaptions;
+ bool m_shouldShowCaptions;
};
}
/*
- * Copyright (C) 2012 Apple Inc. All rights reserved.
+ * Copyright (C) 2012, 2013 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
#if HAVE(MEDIA_ACCESSIBILITY_FRAMEWORK)
virtual bool userHasCaptionPreferences() const OVERRIDE;
+ virtual bool shouldShowCaptions() const OVERRIDE;
+ virtual void setShouldShowCaptions(bool) OVERRIDE;
+
virtual bool userPrefersCaptions() const OVERRIDE;
- virtual void setUserPrefersCaptions(bool) OVERRIDE;
+ virtual bool userPrefersSubtitles() const OVERRIDE;
+
virtual float captionFontSizeScale(bool&) const OVERRIDE;
virtual String captionsStyleSheetOverride() const OVERRIDE;
virtual Vector<String> preferredLanguages() const OVERRIDE;
virtual void captionPreferencesChanged() OVERRIDE;
+
#endif
+ virtual Vector<RefPtr<TextTrack> > sortedTrackListForMenu(TextTrackList*) OVERRIDE;
virtual String displayNameForTrack(TextTrack*) const OVERRIDE;
private:
/*
- * Copyright (C) 2012 Apple Inc. All rights reserved.
+ * Copyright (C) 2012, 2013 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
#import "PageGroup.h"
#import "SoftLinking.h"
#import "TextTrackCue.h"
+#import "TextTrackList.h"
#import "UserStyleSheetTypes.h"
+#import <wtf/NonCopyingSort.h>
#import <wtf/RetainPtr.h>
#import <wtf/text/StringBuilder.h>
SOFT_LINK(MediaAccessibility, MACaptionAppearanceGetTextEdgeStyle, MACaptionAppearanceTextEdgeStyle, (MACaptionAppearanceDomain domain, MACaptionAppearanceBehavior *behavior), (domain, behavior))
SOFT_LINK(MediaAccessibility, MACaptionAppearanceAddSelectedLanguage, bool, (MACaptionAppearanceDomain domain, CFStringRef language), (domain, language));
SOFT_LINK(MediaAccessibility, MACaptionAppearanceCopySelectedLanguages, CFArrayRef, (MACaptionAppearanceDomain domain), (domain));
+SOFT_LINK(MediaAccessibility, MACaptionAppearanceCopyPreferredCaptioningMediaCharacteristics, CFArrayRef, (MACaptionAppearanceDomain domain), (domain));
SOFT_LINK_POINTER(MediaAccessibility, kMAXCaptionAppearanceSettingsChangedNotification, CFStringRef)
#define kMAXCaptionAppearanceSettingsChangedNotification getkMAXCaptionAppearanceSettingsChangedNotification()
}
#if HAVE(MEDIA_ACCESSIBILITY_FRAMEWORK)
-bool CaptionUserPreferencesMac::userPrefersCaptions() const
+
+bool CaptionUserPreferencesMac::shouldShowCaptions() const
{
if (testingMode() || !MediaAccessibilityLibrary())
- return CaptionUserPreferences::userPrefersCaptions();
+ return CaptionUserPreferences::shouldShowCaptions();
return MACaptionAppearanceGetShowCaptions(kMACaptionAppearanceDomainUser);
}
-void CaptionUserPreferencesMac::setUserPrefersCaptions(bool preference)
+void CaptionUserPreferencesMac::setShouldShowCaptions(bool preference)
{
if (testingMode() || !MediaAccessibilityLibrary()) {
- CaptionUserPreferences::setUserPrefersCaptions(preference);
+ CaptionUserPreferences::setShouldShowCaptions(preference);
return;
}
MACaptionAppearanceSetShowCaptions(kMACaptionAppearanceDomainUser, preference);
}
+bool CaptionUserPreferencesMac::userPrefersCaptions() const
+{
+ bool captionSetting = CaptionUserPreferences::userPrefersCaptions();
+ if (captionSetting || testingMode() || !MediaAccessibilityLibrary())
+ return captionSetting;
+
+ RetainPtr<CFArrayRef> captioningMediaCharacteristics(AdoptCF, MACaptionAppearanceCopyPreferredCaptioningMediaCharacteristics(kMACaptionAppearanceDomainUser));
+ return captioningMediaCharacteristics && CFArrayGetCount(captioningMediaCharacteristics.get());
+}
+
+bool CaptionUserPreferencesMac::userPrefersSubtitles() const
+{
+ bool subtitlesSetting = CaptionUserPreferences::userPrefersSubtitles();
+ if (subtitlesSetting || testingMode() || !MediaAccessibilityLibrary())
+ return subtitlesSetting;
+
+ RetainPtr<CFArrayRef> captioningMediaCharacteristics(AdoptCF, MACaptionAppearanceCopyPreferredCaptioningMediaCharacteristics(kMACaptionAppearanceDomainUser));
+ return !(captioningMediaCharacteristics && CFArrayGetCount(captioningMediaCharacteristics.get()));
+}
+
bool CaptionUserPreferencesMac::userHasCaptionPreferences() const
{
if (testingMode() || !MediaAccessibilityLibrary())
return userPreferredLanguages;
}
-#endif
-
-String CaptionUserPreferencesMac::displayNameForTrack(TextTrack* track) const
+#endif // HAVE(MEDIA_ACCESSIBILITY_FRAMEWORK)
+
+static String trackDisplayName(TextTrack* track)
{
- String label = track->label();
- String language = track->language();
- String preferredLanguage = defaultLanguage();
StringBuilder displayName;
-
- if (label.isEmpty() && language.isEmpty()) {
+ String label = track->label();
+ String trackLanguageIdentifier = track->language();
+
+ RetainPtr<CFLocaleRef> currentLocale(AdoptCF, CFLocaleCopyCurrent());
+ RetainPtr<CFStringRef> localeIdentifier(AdoptCF, CFLocaleCreateCanonicalLocaleIdentifierFromString(kCFAllocatorDefault, trackLanguageIdentifier.createCFString().get()));
+ RetainPtr<CFStringRef> languageCF(AdoptCF, CFLocaleCopyDisplayNameForPropertyValue(currentLocale.get(), kCFLocaleLanguageCode, localeIdentifier.get()));
+ String language = languageCF.get();
+ if (!label.isEmpty()) {
+ if (!language.isEmpty() && !label.contains(language)) {
+ RetainPtr<CFDictionaryRef> localeDict(AdoptCF, CFLocaleCreateComponentsFromLocaleIdentifier(kCFAllocatorDefault, localeIdentifier.get()));
+ if (localeDict) {
+ CFStringRef countryCode = 0;
+ String countryName;
+
+ CFDictionaryGetValueIfPresent(localeDict.get(), kCFLocaleCountryCode, (const void **)&countryCode);
+ if (countryCode) {
+ RetainPtr<CFStringRef> countryNameCF(AdoptCF, CFLocaleCopyDisplayNameForPropertyValue(currentLocale.get(), kCFLocaleCountryCode, countryCode));
+ countryName = countryNameCF.get();
+ }
+
+ if (!countryName.isEmpty())
+ displayName.append(textTrackCountryAndLanguageMenuItemText(label, countryName, language));
+ else
+ displayName.append(textTrackLanguageMenuItemText(label, language));
+ }
+ }
+ } else {
+ String languageAndLocale = CFLocaleCopyDisplayNameForPropertyValue(currentLocale.get(), kCFLocaleIdentifier, trackLanguageIdentifier.createCFString().get());
+ if (!languageAndLocale.isEmpty())
+ displayName.append(languageAndLocale);
+ else if (!language.isEmpty())
+ displayName.append(language);
+ else
+ displayName.append(localeIdentifier.get());
+ }
+
+ if (displayName.isEmpty())
displayName.append(textTrackNoLabelText());
+
+ if (track->isEasyToRead())
+ return easyReaderTrackMenuItemText(displayName.toString());
+
+ if (track->kind() != track->captionsKeyword())
return displayName.toString();
+
+ if (track->isClosedCaptions())
+ return closedCaptionTrackMenuItemText(displayName.toString());
+
+ return sdhTrackMenuItemText(displayName.toString());
+}
+
+String CaptionUserPreferencesMac::displayNameForTrack(TextTrack* track) const
+{
+ return trackDisplayName(track);
+}
+
+static String languageIdentifier(const String& languageCode)
+{
+ if (languageCode.isEmpty())
+ return languageCode;
+
+ String lowercaseLanguageCode = languageCode.lower();
+
+ if (lowercaseLanguageCode.length() >= 3 && (lowercaseLanguageCode[2] == '_' || lowercaseLanguageCode[2] == '-'))
+ lowercaseLanguageCode.truncate(2);
+
+ return lowercaseLanguageCode;
+}
+
+static bool textTrackCompare(const RefPtr<TextTrack>& a, const RefPtr<TextTrack>& b)
+{
+ String preferredLanguageDisplayName = displayNameForLanguageLocale(languageIdentifier(defaultLanguage()));
+ String aLanguageDisplayName = displayNameForLanguageLocale(languageIdentifier(a->language()));
+ String bLanguageDisplayName = displayNameForLanguageLocale(languageIdentifier(b->language()));
+
+ // Tracks in the user's preferred language are always at the top of the menu.
+ bool aIsPreferredLanguage = !codePointCompare(aLanguageDisplayName, preferredLanguageDisplayName);
+ bool bIsPreferredLanguage = !codePointCompare(bLanguageDisplayName, preferredLanguageDisplayName);
+ if ((aIsPreferredLanguage || bIsPreferredLanguage) && (aIsPreferredLanguage != bIsPreferredLanguage))
+ return aIsPreferredLanguage;
+
+ // Tracks not in the user's preferred language sort first by language ...
+ if (codePointCompare(aLanguageDisplayName, bLanguageDisplayName))
+ return codePointCompare(aLanguageDisplayName, bLanguageDisplayName) < 0;
+
+ // ... but when tracks have the same language, main program content sorts next highest ...
+ bool aIsMainContent = a->isMainProgramContent();
+ bool bIsMainContent = b->isMainProgramContent();
+ if ((aIsMainContent || bIsMainContent) && (aIsMainContent != bIsMainContent))
+ return aIsMainContent;
+
+ // ... and main program trakcs sort higher than CC tracks ...
+ bool aIsCC = a->isClosedCaptions();
+ bool bIsCC = b->isClosedCaptions();
+ if ((aIsCC || bIsCC) && (aIsCC != bIsCC)) {
+ if (aIsCC)
+ return aIsMainContent;
+ return bIsMainContent;
}
- if (!label.isEmpty())
- displayName.append(label);
+ // ... and tracks of the same type and language sort by the menu item text.
+ return codePointCompare(trackDisplayName(a.get()), trackDisplayName(b.get())) < 0;
+}
+
+Vector<RefPtr<TextTrack> > CaptionUserPreferencesMac::sortedTrackListForMenu(TextTrackList* trackList)
+{
+ ASSERT(trackList);
+
+ Vector<RefPtr<TextTrack> > tracksForMenu;
+ HashSet<String> languagesIncluded;
+ bool prefersAccessibilityTracks = userPrefersCaptions();
+
+ for (unsigned i = 0, length = trackList->length(); i < length; ++i) {
+ TextTrack* track = trackList->item(i);
+ String language = displayNameForLanguageLocale(track->language());
- AtomicString localeDisplayName = displayNameForLanguageLocale(language);
- if (!label.contains(localeDisplayName)) {
- if (displayName.length() > 0)
- displayName.append(" ");
- displayName.append(localeDisplayName);
+ if (track->isEasyToRead()) {
+ if (!language.isEmpty())
+ languagesIncluded.add(language);
+ tracksForMenu.append(track);
+ continue;
+ }
+
+ if (track->containsOnlyForcedSubtitles())
+ continue;
+
+ if (!language.isEmpty() && track->isMainProgramContent()) {
+ bool isAccessibilityTrack = track->kind() == track->captionsKeyword();
+ if (prefersAccessibilityTracks) {
+ // In the first pass, include only caption tracks if the user prefers accessibility tracks.
+ if (!isAccessibilityTrack) {
+ LOG(Media, "CaptionUserPreferencesMac::sortedTrackListForMenu - skipping %s because it is NOT an accessibility track", language.utf8().data());
+ continue;
+ }
+ } else {
+ // In the first pass, only include the first non-CC or SDH track with each language if the user prefers translation tracks.
+ if (isAccessibilityTrack) {
+ LOG(Media, "CaptionUserPreferencesMac::sortedTrackListForMenu - skipping %s because it is an accessibility track", language.utf8().data());
+ continue;
+ }
+ if (languagesIncluded.contains(language)) {
+ LOG(Media, "CaptionUserPreferencesMac::sortedTrackListForMenu - skipping %s because it is not the first with this language", language.utf8().data());
+ continue;
+ }
+ }
+ }
+
+ if (!language.isEmpty())
+ languagesIncluded.add(language);
+ tracksForMenu.append(track);
}
- if (track->kind() == track->captionsKeyword()) {
- if (track->isClosedCaptions())
- displayName.append(" CC");
- else
- displayName.append(" SDH");
+ // Now that we have filtered for the user's accessibility/translation preference, add all tracks with a unique language without regard to track type.
+ for (unsigned i = 0, length = trackList->length(); i < length; ++i) {
+ TextTrack* track = trackList->item(i);
+ String language = displayNameForLanguageLocale(track->language());
+
+ // All candidates with no languge were added the first time through.
+ if (language.isEmpty())
+ continue;
+
+ if (track->containsOnlyForcedSubtitles())
+ continue;
+
+ if (!languagesIncluded.contains(language) && track->isMainProgramContent()) {
+ languagesIncluded.add(language);
+ tracksForMenu.append(track);
+ }
}
- return displayName.toString();
-}
+ nonCopyingSort(tracksForMenu.begin(), tracksForMenu.end(), textTrackCompare);
+ return tracksForMenu;
+}
+
}
#endif // ENABLE(VIDEO_TRACK)
String displayNameForLanguageLocale(const String& localeName)
{
#if PLATFORM(MAC)
- if (!localeName.isNull() && !localeName.isEmpty())
- return CFLocaleCopyDisplayNameForPropertyValue(CFLocaleCopyCurrent(), kCFLocaleIdentifier, localeName.createCFString().get());
+ if (!localeName.isNull() && !localeName.isEmpty()) {
+ RetainPtr<CFLocaleRef> currentLocale(AdoptCF, CFLocaleCopyCurrent());
+ return CFLocaleCopyDisplayNameForPropertyValue(currentLocale.get(), kCFLocaleIdentifier, localeName.createCFString().get());
+ }
#endif
return localeName;
}
String textTrackNoLabelText()
{
- return WEB_UI_STRING_KEY("Unknown", "Unknown (closed captions track)", "Menu item label for a closed captions track that has no other name");
+ return WEB_UI_STRING_KEY("Unknown", "Unknown (text track)", "Menu item label for a text track that has no other name");
}
+
+#if PLATFORM(MAC)
+String textTrackCountryAndLanguageMenuItemText(const String& title, const String& country, const String& language)
+{
+ return formatLocalizedString(WEB_UI_STRING("%@ (%@-%@)", "Text track display name format that includes the country and language of the subtitle, in the form of 'Title (Language-Country)'"), title.createCFString().get(), language.createCFString().get(), country.createCFString().get());
+}
+
+String textTrackLanguageMenuItemText(const String& title, const String& language)
+{
+ return formatLocalizedString(WEB_UI_STRING("%@ (%@)", "Text track display name format that includes the language of the subtitle, in the form of 'Title (Language)'"), title.createCFString().get(), language.createCFString().get());
+}
+
+String closedCaptionTrackMenuItemText(const String& title)
+{
+ return formatLocalizedString(WEB_UI_STRING("%@ CC", "Text track contains closed captions"), title.createCFString().get());
+}
+
+String sdhTrackMenuItemText(const String& title)
+{
+ return formatLocalizedString(WEB_UI_STRING("%@ SDH", "Text track contains subtitles for the deaf and hard of hearing"), title.createCFString().get());
+}
+
+String easyReaderTrackMenuItemText(const String& title)
+{
+ return formatLocalizedString(WEB_UI_STRING("%@ Easy Reader", "Text track contains simplified (3rd grade level) subtitles"), title.createCFString().get());
+}
+#endif
+
#endif
String snapshottedPlugInLabelTitle()
String textTrackSubtitlesText();
String textTrackOffText();
String textTrackNoLabelText();
+#if PLATFORM(MAC)
+ String textTrackCountryAndLanguageMenuItemText(const String& title, const String& country, const String& language);
+ String textTrackLanguageMenuItemText(const String& title, const String& language);
+ String closedCaptionTrackMenuItemText(const String&);
+ String sdhTrackMenuItemText(const String&);
+ String easyReaderTrackMenuItemText(const String&);
+#endif
#endif
String snapshottedPlugInLabelTitle();
enum Kind { Subtitles, Captions, Descriptions, Chapters, Metadata, None };
virtual Kind kind() const { return Subtitles; }
virtual bool isClosedCaptions() const { return false; }
+ virtual bool containsOnlyForcedSubtitles() const { return false; }
+ virtual bool isMainProgramContent() const { return true; }
+ virtual bool isEasyToRead() const { return false; }
virtual AtomicString label() const { return emptyAtom; }
virtual AtomicString language() const { return emptyAtom; }
virtual InbandTextTrackPrivate::Kind kind() const OVERRIDE;
virtual bool isClosedCaptions() const OVERRIDE;
+ virtual bool containsOnlyForcedSubtitles() const OVERRIDE;
+ virtual bool isMainProgramContent() const OVERRIDE;
+ virtual bool isEasyToRead() const OVERRIDE;
virtual AtomicString label() const OVERRIDE;
virtual AtomicString language() const OVERRIDE;
virtual bool isDefault() const OVERRIDE;
#define AVMediaCharacteristicVisual getAVMediaCharacteristicVisual()
#define AVMediaCharacteristicAudible getAVMediaCharacteristicAudible()
#define AVMediaTypeClosedCaption getAVMediaTypeClosedCaption()
-#define AVMediaTypeVideo getAVMediaTypeVideo()
-#define AVMediaTypeAudio getAVMediaTypeAudio()
+#define AVMediaCharacteristicContainsOnlyForcedSubtitles getAVMediaCharacteristicContainsOnlyForcedSubtitles()
+#define AVMediaCharacteristicIsMainProgramContent getAVMediaCharacteristicIsMainProgramContent()
+#define AVMediaCharacteristicEasyToRead getAVMediaCharacteristicEasyToRead()
SOFT_LINK_POINTER(AVFoundation, AVMediaTypeClosedCaption, NSString *)
SOFT_LINK_POINTER(AVFoundation, AVMediaCharacteristicLegible, NSString *)
SOFT_LINK_POINTER(AVFoundation, AVMediaTypeSubtitle, NSString *)
SOFT_LINK_POINTER(AVFoundation, AVMediaCharacteristicTranscribesSpokenDialogForAccessibility, NSString *)
SOFT_LINK_POINTER(AVFoundation, AVMediaCharacteristicDescribesMusicAndSoundForAccessibility, NSString *)
+SOFT_LINK_POINTER(AVFoundation, AVMediaCharacteristicContainsOnlyForcedSubtitles, NSString *)
+SOFT_LINK_POINTER(AVFoundation, AVMediaCharacteristicIsMainProgramContent, NSString *)
+SOFT_LINK_POINTER(AVFoundation, AVMediaCharacteristicEasyToRead, NSString *)
+
#define AVMetadataItem getAVMetadataItemClass()
#define AVPlayerItemLegibleOutput getAVPlayerItemLegibleOutputClass()
#define AVMediaCharacteristicLegible getAVMediaCharacteristicLegible()
return [[m_mediaSelectionOption mediaType] isEqualToString:AVMediaTypeClosedCaption];
}
+bool InbandTextTrackPrivateAVFObjC::containsOnlyForcedSubtitles() const
+{
+ if (!m_mediaSelectionOption)
+ return false;
+
+ return [m_mediaSelectionOption hasMediaCharacteristic:AVMediaCharacteristicContainsOnlyForcedSubtitles];
+}
+
+bool InbandTextTrackPrivateAVFObjC::isMainProgramContent() const
+{
+ if (!m_mediaSelectionOption)
+ return false;
+
+ return [m_mediaSelectionOption hasMediaCharacteristic:AVMediaCharacteristicIsMainProgramContent];
+}
+
+bool InbandTextTrackPrivateAVFObjC::isEasyToRead() const
+{
+ if (!m_mediaSelectionOption)
+ return false;
+
+ return [m_mediaSelectionOption hasMediaCharacteristic:AVMediaCharacteristicEasyToRead];
+}
+
AtomicString InbandTextTrackPrivateAVFObjC::label() const
{
if (!m_mediaSelectionOption)
#include "config.h"
#include "InternalSettings.h"
+#include "CaptionUserPreferences.h"
#include "Document.h"
#include "ExceptionCode.h"
#include "Frame.h"
#include "Language.h"
#include "LocaleToScriptMapping.h"
#include "Page.h"
+#include "PageGroup.h"
#include "RuntimeEnabledFeatures.h"
#include "Settings.h"
#include "Supplementable.h"
InternalSettingsGuardForSettings();
#if ENABLE(VIDEO_TRACK)
+ if (!page())
+ return;
+ CaptionUserPreferences* captionPreferences = page()->group().captionPreferences();
+
if (equalIgnoringCase(kind, "Subtitles"))
- settings()->setShouldDisplaySubtitles(enabled);
+ captionPreferences->setUserPrefersSubtitles(enabled);
else if (equalIgnoringCase(kind, "Captions"))
- settings()->setShouldDisplayCaptions(enabled);
+ captionPreferences->setUserPrefersCaptions(enabled);
else if (equalIgnoringCase(kind, "TextDescriptions"))
- settings()->setShouldDisplayTextDescriptions(enabled);
+ captionPreferences->setUserPrefersTextDescriptions(enabled);
else
ec = SYNTAX_ERR;
#else
InternalSettingsGuardForSettingsReturn(false);
#if ENABLE(VIDEO_TRACK)
+ if (!page())
+ return false;
+ CaptionUserPreferences* captionPreferences = page()->group().captionPreferences();
+
if (equalIgnoringCase(kind, "Subtitles"))
- return settings()->shouldDisplaySubtitles();
+ return captionPreferences->userPrefersSubtitles();
if (equalIgnoringCase(kind, "Captions"))
- return settings()->shouldDisplayCaptions();
+ return captionPreferences->userPrefersCaptions();
if (equalIgnoringCase(kind, "TextDescriptions"))
- return settings()->shouldDisplayTextDescriptions();
+ return captionPreferences->userPrefersTextDescriptions();
ec = SYNTAX_ERR;
return false;
+2013-03-20 Eric Carlson <eric.carlson@apple.com>
+
+ Allow ports specific text track menu
+ https://bugs.webkit.org/show_bug.cgi?id=112800
+
+ Reviewed by Dean Jackson.
+
+ * WebKit.vcxproj/WebKitExportGenerator/WebKitExports.def.in: Add new exports.
+ * win/WebKit.vcproj/WebKitExports.def.in: Ditto.
+
2013-03-20 Roger Fong <roger_fong@apple.com>
AppleWin VS2010 build fix.
?setAuthorAndUserStylesEnabled@Settings@WebCore@@QAEX_N@Z
?setAcceleratedCompositingEnabled@Settings@WebCore@@QAEX_N@Z
?setAutofilled@HTMLInputElement@WebCore@@QAEX_N@Z
+ ?captionPreferences@PageGroup@WebCore@@QAEPAVCaptionUserPreferences@2@XZ
+ ?initGroup@Page@WebCore@@AAEXXZ
#if ENABLE(WORKERS)
?workerThreadCount@WorkerThread@WebCore@@SAIXZ
?setAuthorAndUserStylesEnabled@Settings@WebCore@@QAEX_N@Z
?setAcceleratedCompositingEnabled@Settings@WebCore@@QAEX_N@Z
?setAutofilled@HTMLInputElement@WebCore@@QAEX_N@Z
+ ?captionPreferences@PageGroup@WebCore@@QAEPAVCaptionUserPreferences@2@XZ
+ ?initGroup@Page@WebCore@@AAEXXZ
#if ENABLE(WORKERS)
?workerThreadCount@WorkerThread@WebCore@@SAIXZ