0

Support text track selection in video controls

Enable the user to select between text tracks with a menu that is displayed when the CC button is clicked.

BUG=353105,495851

TEST=run-webkit-tests media*

Review URL: https://codereview.chromium.org/1079323002

Cr-Commit-Position: refs/heads/master@{#389201}
This commit is contained in:
srivats
2016-04-22 12:12:40 -07:00
committed by Commit bot
parent 556d013d1b
commit c060c876bf
43 changed files with 839 additions and 158 deletions

@ -856,6 +856,14 @@ below:
Please lengthen this text to <ph name="MIN_CHARACTERS">$2<ex>101</ex></ph> characters or more (you are currently using <ph name="CURRENT_LENGTH">$1<ex>100</ex></ph> characters).
</message>
<message name="IDS_MEDIA_TRACKS_NO_LABEL" desc="Menu item label for a text track that has no name specified">
Unknown
</message>
<message name="IDS_MEDIA_TRACKS_OFF" desc="Menu item label for a text track that represents disabling closed captions">
Off
</message>
<message name="IDS_PLUGIN_INITIALIZATION_ERROR" desc="A message displayed when a plugin failed to load">
Couldn't load plugin.
</message>

@ -337,6 +337,10 @@ static int ToMessageID(WebLocalizedString::Name name) {
return IDS_FORM_INPUT_WEEK_TEMPLATE;
case WebLocalizedString::WeekNumberLabel:
return IDS_FORM_WEEK_NUMBER_LABEL;
case WebLocalizedString::TextTracksNoLabel:
return IDS_MEDIA_TRACKS_NO_LABEL;
case WebLocalizedString::TextTracksOff:
return IDS_MEDIA_TRACKS_OFF;
// This "default:" line exists to avoid compile warnings about enum
// coverage when we add a new symbol to WebLocalizedString.h in WebKit.
// After a planned WebKit patch is landed, we need to add a case statement
@ -673,6 +677,24 @@ const DataResource kDataResources[] = {
{"mediaplayerOverlayPlayNew",
IDR_MEDIAPLAYER_OVERLAY_PLAY_BUTTON_NEW,
ui::SCALE_FACTOR_100P},
{"mediaplayerTrackSelectionCheckmark",
IDR_MEDIAPLAYER_TRACKSELECTION_CHECKMARK,
ui::SCALE_FACTOR_100P},
{"mediaplayerTrackSelectionCheckmarkNew",
IDR_MEDIAPLAYER_TRACKSELECTION_CHECKMARK_NEW,
ui::SCALE_FACTOR_100P},
{"mediaplayerClosedCaptionsIcon",
IDR_MEDIAPLAYER_CLOSEDCAPTIONS_ICON,
ui::SCALE_FACTOR_100P},
{"mediaplayerClosedCaptionsIconNew",
IDR_MEDIAPLAYER_CLOSEDCAPTIONS_ICON_NEW,
ui::SCALE_FACTOR_100P},
{"mediaplayerSubtitlesIcon",
IDR_MEDIAPLAYER_SUBTITLES_ICON,
ui::SCALE_FACTOR_100P},
{"mediaplayerSubtitlesIconNew",
IDR_MEDIAPLAYER_SUBTITLES_ICON_NEW,
ui::SCALE_FACTOR_100P},
{"searchCancel", IDR_SEARCH_CANCEL, ui::SCALE_FACTOR_100P},
{"searchCancelPressed", IDR_SEARCH_CANCEL_PRESSED, ui::SCALE_FACTOR_100P},
{"searchMagnifier", IDR_SEARCH_MAGNIFIER, ui::SCALE_FACTOR_100P},

@ -47,13 +47,18 @@ function mediaControlsButton(element, id)
return button;
}
function elementCoordinates(element)
{
var elementBoundingRect = element.getBoundingClientRect();
var x = elementBoundingRect.left + elementBoundingRect.width / 2;
var y = elementBoundingRect.top + elementBoundingRect.height / 2;
return new Array(x, y);
}
function mediaControlsButtonCoordinates(element, id)
{
var button = mediaControlsButton(element, id);
var buttonBoundingRect = button.getBoundingClientRect();
var x = buttonBoundingRect.left + buttonBoundingRect.width / 2;
var y = buttonBoundingRect.top + buttonBoundingRect.height / 2;
return new Array(x, y);
return elementCoordinates(button);
}
function mediaControlsButtonDimensions(element, id)
@ -119,12 +124,45 @@ function testClosedCaptionsButtonVisibility(expected)
}
}
function clickAtCoordinates(x, y)
{
eventSender.mouseMoveTo(x, y);
eventSender.mouseDown();
eventSender.mouseUp();
}
function clickCCButton()
{
consoleWrite("*** Click the CC button.");
eventSender.mouseMoveTo(captionsButtonCoordinates[0], captionsButtonCoordinates[1]);
eventSender.mouseDown();
eventSender.mouseUp();
clickAtCoordinates(captionsButtonCoordinates[0], captionsButtonCoordinates[1]);
}
function textTrackListItemAtIndex(video, index)
{
var textTrackListElementID = "-internal-media-controls-text-track-list";
var textTrackListElement = mediaControlsElement(internals.shadowRoot(video).firstChild, textTrackListElementID);
if (!textTrackListElement)
throw "Failed to find text track list element";
var trackListItems = textTrackListElement.childNodes;
for (var i = 0; i < trackListItems.length; i++) {
var trackListItem = trackListItems[i];
if (trackListItem.firstChild.getAttribute("data-track-index") == index)
return trackListItem;
}
}
function selectTextTrack(video, index)
{
clickCCButton();
var trackListItemElement = textTrackListItemAtIndex(video, index);
var trackListItemCoordinates = elementCoordinates(trackListItemElement);
clickAtCoordinates(trackListItemCoordinates[0], trackListItemCoordinates[1]);
}
function turnClosedCaptionsOff(video)
{
selectTextTrack(video, -1);
}
function runAfterHideMediaControlsTimerFired(func, mediaElement)

@ -37,7 +37,7 @@ async_test(function(t) {
ascendant.offsetTop;
ascendant.classList.add("cue");
if (window.internals)
assert_equals(internals.updateStyleAndReturnAffectedElementCount(), 8);
assert_equals(internals.updateStyleAndReturnAffectedElementCount(), 9);
assert_equals(getComputedStyle(cueNode).backgroundColor, green);
assert_equals(getComputedStyle(cNode).backgroundColor, red);

@ -0,0 +1,22 @@
Test that we can add a track dynamically and it is displayed on the track selection menu
EVENT(canplaythrough)
** Caption button should be visible and enabled.
EXPECTED (captionsButtonCoordinates[0] > '0') OK
EXPECTED (captionsButtonCoordinates[1] > '0') OK
EXPECTED (captionsButtonElement.disabled == 'false') OK
EXPECTED (video.textTracks.length == '2') OK
EXPECTED (video.textTracks[0].mode == 'showing') OK
EXPECTED (video.textTracks[1].mode == 'hidden') OK
Verify the default track is being displayed
EXPECTED (textTrackDisplayElement(video, 'display').innerText == 'Lorem') OK
Select the newly added track
*** Click the CC button.
EXPECTED (video.textTracks[1].mode == 'showing') OK
EXPECTED (video.textTracks[0].mode == 'disabled') OK
EXPECTED (textTrackDisplayElement(video, 'display').innerText == 'Bonjour') OK
END OF TEST

@ -0,0 +1,51 @@
<!DOCTYPE html>
<script src="../media-file.js"></script>
<!-- TODO(srivats): Convert test to testharness.js. crbug.com/588956
(Please avoid writing new tests using video-test.js) -->
<script src="../video-test.js"></script>
<script src="../media-controls.js"></script>
<script>
var trackCueText = "Bonjour";
function selectTrackAdded()
{
findMediaElement();
testClosedCaptionsButtonVisibility(true);
consoleWrite("");
testExpected("video.textTracks.length", 2);
testExpected("video.textTracks[0].mode", "showing");
testExpected("video.textTracks[1].mode", "hidden");
consoleWrite("");
consoleWrite("Verify the default track is being displayed");
testExpected("textTrackDisplayElement(video, 'display').innerText", "Lorem");
consoleWrite("");
consoleWrite("Select the newly added track");
selectTextTrack(video, 1);
testExpected("video.textTracks[1].mode", "showing");
testExpected("video.textTracks[0].mode", "disabled");
testExpected("textTrackDisplayElement(video, 'display').innerText", trackCueText);
endTest();
}
function addTextTrack()
{
track = video.addTextTrack("captions", "French", "fr");
track.addCue(new VTTCue(0.0, 1.0, trackCueText));
selectTrackAdded();
}
window.onload = function()
{
consoleWrite("Test that we can add a track dynamically and it is displayed on the track selection menu");
findMediaElement();
video.src = findMediaFile("video", "../content/test");
waitForEvent("canplaythrough", addTextTrack);
}
</script>
<video controls>
<track src="captions-webvtt/captions.vtt" kind="captions" label="English" srclang="en" default>
</video>

@ -0,0 +1,57 @@
Test that we can add multiple tracks and select between them from the track selection menu
EVENT(canplaythrough)
** Caption button should be visible and enabled.
EXPECTED (captionsButtonCoordinates[0] > '0') OK
EXPECTED (captionsButtonCoordinates[1] > '0') OK
EXPECTED (captionsButtonElement.disabled == 'false') OK
EXPECTED (video.textTracks.length == '5') OK
Select track at index 0
*** Click the CC button.
EXPECTED (video.textTracks[0].mode == 'showing') OK
EXPECTED (textTrackDisplayElement(video, 'display').innerText == 'English') OK
EXPECTED (video.textTracks[1].mode == 'disabled') OK
EXPECTED (video.textTracks[2].mode == 'disabled') OK
EXPECTED (video.textTracks[3].mode == 'disabled') OK
EXPECTED (video.textTracks[4].mode == 'disabled') OK
Select track at index 1
*** Click the CC button.
EXPECTED (video.textTracks[1].mode == 'showing') OK
EXPECTED (textTrackDisplayElement(video, 'display').innerText == 'Russian') OK
EXPECTED (video.textTracks[0].mode == 'disabled') OK
EXPECTED (video.textTracks[2].mode == 'disabled') OK
EXPECTED (video.textTracks[3].mode == 'disabled') OK
EXPECTED (video.textTracks[4].mode == 'disabled') OK
Select track at index 2
*** Click the CC button.
EXPECTED (video.textTracks[2].mode == 'showing') OK
EXPECTED (textTrackDisplayElement(video, 'display').innerText == 'French') OK
EXPECTED (video.textTracks[0].mode == 'disabled') OK
EXPECTED (video.textTracks[1].mode == 'disabled') OK
EXPECTED (video.textTracks[3].mode == 'disabled') OK
EXPECTED (video.textTracks[4].mode == 'disabled') OK
Select track at index 3
*** Click the CC button.
EXPECTED (video.textTracks[3].mode == 'showing') OK
EXPECTED (textTrackDisplayElement(video, 'display').innerText == 'Japanese') OK
EXPECTED (video.textTracks[0].mode == 'disabled') OK
EXPECTED (video.textTracks[1].mode == 'disabled') OK
EXPECTED (video.textTracks[2].mode == 'disabled') OK
EXPECTED (video.textTracks[4].mode == 'disabled') OK
Select track at index 4
*** Click the CC button.
EXPECTED (video.textTracks[4].mode == 'showing') OK
EXPECTED (textTrackDisplayElement(video, 'display').innerText == 'German') OK
EXPECTED (video.textTracks[0].mode == 'disabled') OK
EXPECTED (video.textTracks[1].mode == 'disabled') OK
EXPECTED (video.textTracks[2].mode == 'disabled') OK
EXPECTED (video.textTracks[3].mode == 'disabled') OK
END OF TEST

@ -0,0 +1,53 @@
<!DOCTYPE html>
<script src="../media-file.js"></script>
<!-- TODO(srivats): Convert test to testharness.js. crbug.com/588956
(Please avoid writing new tests using video-test.js) -->
<script src="../video-test.js"></script>
<script src="../media-controls.js"></script>
<script>
var trackLanguages = ["en", "ru", "fr", "jp", "de"];
var trackCueText = ["English", "Russian", "French", "Japanese", "German"];
function startTest()
{
findMediaElement();
testClosedCaptionsButtonVisibility(true);
consoleWrite("");
testExpected("video.textTracks.length", trackLanguages.length);
consoleWrite("");
for (var i = 0; i < trackLanguages.length; i++) {
consoleWrite("Select track at index " + i);
selectTextTrack(video, i);
testExpected("video.textTracks[" + i + "].mode", "showing");
testExpected("textTrackDisplayElement(video, 'display').innerText", trackCueText[i]);
for (var j = 0; j < trackLanguages.length; j++) {
if (j != i)
testExpected("video.textTracks[" + j + "].mode", "disabled");
}
consoleWrite("");
}
endTest();
}
function addTextTracks()
{
for (var i = 0; i < trackLanguages.length; i++) {
var track = video.addTextTrack("captions", trackCueText[i], trackLanguages[i]);
track.addCue(new VTTCue(0.0, 1.0, trackCueText[i]));
track.mode = "disabled";
}
startTest();
}
window.onload = function()
{
consoleWrite("Test that we can add multiple tracks and select between them from the track selection menu");
findMediaElement();
video.src = findMediaFile("video", "../content/test");
waitForEvent("canplaythrough", addTextTracks);
}
</script>
<video controls></video>

@ -1,4 +1,4 @@
Tests that the closed captions button, when toggled, updates the text track display area.
Tests that the closed captions button enables track switching
EVENT(canplaythrough)
@ -12,11 +12,11 @@ EXPECTED (video.textTracks.length == '1') OK
EXPECTED (video.textTracks[0].mode == 'disabled') OK
Failed to find text track container element
** Captions track should load and captions should become visible after button is clicked **
** Captions track should load and captions should become visible after a track is selected **
*** Click the CC button.
EXPECTED (textTrackDisplayElement(video, 'display').innerText == 'Lorem') OK
** Captions should not be visible after button is clicked again **
** Captions should not be visible after Off is clicked **
*** Click the CC button.
No text track cue with display id '-webkit-media-text-track-display' is currently visible

@ -1,19 +0,0 @@
Tests that appropriate language track is loaded, according to user preferences.
EVENT(canplaythrough)
** Caption button should be visible and enabled.
EXPECTED (captionsButtonCoordinates[0] > '0') OK
EXPECTED (captionsButtonCoordinates[1] > '0') OK
EXPECTED (captionsButtonElement.disabled == 'false') OK
** The captions track should be listed in textTracks, but disabled. **
EXPECTED (video.textTracks.length == '2') OK
EXPECTED (video.textTracks[0].mode == 'disabled') OK
EXPECTED (video.textTracks[1].mode == 'disabled') OK
** Set the user language preference so that the track will be chosen when the CC button is clicked. **
RUN(internals.setUserPreferredLanguages(['ar']))
*** Click the CC button.
END OF TEST

@ -1,52 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Test non-default track load according to user language preference.</title>
<script src=media-file.js></script>
<script src=media-controls.js></script>
<!-- TODO(philipj): Convert test to testharness.js. crbug.com/588956
(Please avoid writing new tests using video-test.js) -->
<script src=video-test.js></script>
<script>
var track;
function startTest()
{
if (!window.eventSender) {
consoleWrite("No eventSender found.");
failTest();
}
findMediaElement();
testClosedCaptionsButtonVisibility(true);
consoleWrite("");
consoleWrite("** The captions track should be listed in textTracks, but disabled. **");
testExpected("video.textTracks.length", 2);
testExpected("video.textTracks[0].mode", "disabled");
testExpected("video.textTracks[1].mode", "disabled");
consoleWrite("");
consoleWrite("** Set the user language preference so that the track will be chosen when the CC button is clicked. **");
run("internals.setUserPreferredLanguages(['ar'])");
clickCCButton();
}
function loaded()
{
findMediaElement();
waitForEvent('canplaythrough', startTest);
video.src = findMediaFile('video', 'content/counting');
}
</script>
</head>
<body onload="loaded()">
<p>Tests that appropriate language track is loaded, according to user preferences.</p>
<video controls>
<track src="track/captions-webvtt/captions-fast.vtt" kind="captions">
<track src="track/captions-webvtt/captions-fast.vtt" kind="captions" srclang="ar" onload="endTest()">
</video>
</body>
</html>

@ -1,4 +1,4 @@
Tests that multiple toggles of the closed captions button still display captions
Tests that tracks can be turned on and off through the track selection menu
EVENT(canplaythrough)
@ -14,19 +14,19 @@ No text track cue with display id '-webkit-media-text-track-display' is currentl
No text track cue with display id '-webkit-media-text-track-display' is currently visible
No text track cue with display id '-webkit-media-text-track-display' is currently visible
** Captions track should become visible after button is clicked **
** Captions track should become visible after the track is selected **
*** Click the CC button.
EXPECTED (displayElement.innerText == 'First') OK
EXPECTED (displayElement.innerText == 'Second') OK
EXPECTED (displayElement.innerText == 'Third') OK
** Captions should not be visible after button is clicked again **
** Captions should not be visible after they're turned off through the menu **
*** Click the CC button.
No text track cue with display id '-webkit-media-text-track-display' is currently visible
No text track cue with display id '-webkit-media-text-track-display' is currently visible
No text track cue with display id '-webkit-media-text-track-display' is currently visible
** Captions should become visible after button is clicked again **
** Captions track should become visible after the track is selected again **
*** Click the CC button.
EXPECTED (displayElement.innerText == 'First') OK
EXPECTED (displayElement.innerText == 'Second') OK

@ -2,22 +2,21 @@
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Test closed caption button toggling.</title>
<title>Test closed caption track selection on and off.</title>
<script src=media-file.js></script>
<script src=media-controls.js></script>
<!-- TODO(philipj): Convert test to testharness.js. crbug.com/588956
(Please avoid writing new tests using video-test.js) -->
<script src=video-test.js></script>
<script>
var displayElement;
var track;
var text = ["First", "Second", "Third"];
var displayElement;
function addTextTrack()
{
track = video.addTextTrack('captions');
var track = video.addTextTrack('captions');
for(var i = 0; i < 3; i++) {
for (var i = 0; i < 3; i++) {
var cue = new VTTCue(0, 120, text[i]);
track.addCue(cue);
}
@ -27,9 +26,9 @@
{
for (var i = 0; i < 3; i++) {
try {
displayElement = textTrackDisplayElement(video, 'display', i);
displayElement = textTrackDisplayElement(video, "display", i);
testExpected("displayElement.innerText", text[i]);
} catch(e) {
} catch (e) {
consoleWrite(e);
}
}
@ -54,18 +53,18 @@
checkCaptionsDisplay();
consoleWrite("");
consoleWrite("** Captions track should become visible after button is clicked **");
clickCCButton();
consoleWrite("** Captions track should become visible after the track is selected **");
selectTextTrack(video, 0);
checkCaptionsDisplay();
consoleWrite("");
consoleWrite("** Captions should not be visible after button is clicked again **");
clickCCButton();
consoleWrite("** Captions should not be visible after they're turned off through the menu **");
turnClosedCaptionsOff(video);
checkCaptionsDisplay();
consoleWrite("");
consoleWrite("** Captions should become visible after button is clicked again **");
clickCCButton();
consoleWrite("** Captions track should become visible after the track is selected again **");
selectTextTrack(video, 0);
checkCaptionsDisplay();
consoleWrite("");
@ -82,7 +81,7 @@
</script>
</head>
<body onload="loaded()">
<p>Tests that multiple toggles of the closed captions button still display captions</p>
<p>Tests that tracks can be turned on and off through the track selection menu</p>
<video controls></video>
</body>
</html>

@ -2,7 +2,7 @@
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Test closed caption button toggling.</title>
<title>Test closed caption track selection functionality.</title>
<script src=media-file.js></script>
<script src=media-controls.js></script>
<!-- TODO(philipj): Convert test to testharness.js. crbug.com/588956
@ -70,21 +70,21 @@
checkCaptionsDisplay();
consoleWrite("");
consoleWrite("** Captions track should load and captions should become visible after button is clicked **");
consoleWrite("** Captions track should load and captions should become visible after a track is selected **");
// Note: the test flow continues with "testCCButtonToggling" when the
// Note: the test flow continues with "testCCTrackSelectionFunctionality" when the
// "load" event of the single TextTrack fires up. While the test structure
// might seem weird, this avoids timeouts.
clickCCButton();
selectTextTrack(video, 0);
}
function testCCButtonToggling()
function testCCTrackSelectionFunctionality()
{
checkCaptionsDisplay();
consoleWrite("");
consoleWrite("** Captions should not be visible after button is clicked again **");
clickCCButton();
consoleWrite("** Captions should not be visible after Off is clicked **");
turnClosedCaptionsOff(video);
checkCaptionsDisplay();
removeHTMLTrackElement();
@ -94,7 +94,7 @@
testClosedCaptionsButtonVisibility(true);
consoleWrite("");
clickCCButton();
selectTextTrack(video, 0);
}
function trackError()
@ -118,9 +118,9 @@
</script>
</head>
<body onload="loaded()">
<p>Tests that the closed captions button, when toggled, updates the text track display area.</p>
<p>Tests that the closed captions button enables track switching</p>
<video controls>
<track src="track/captions-webvtt/captions-fast.vtt" kind="captions" onload="testCCButtonToggling()">
<track src="track/captions-webvtt/captions-fast.vtt" kind="captions" onload="testCCTrackSelectionFunctionality()">
</video>
</body>
</html>

@ -0,0 +1,26 @@
Test that we can display a track list menu and select tracks from the list
EVENT(canplaythrough)
** Caption button should be visible and enabled.
EXPECTED (captionsButtonCoordinates[0] > '0') OK
EXPECTED (captionsButtonCoordinates[1] > '0') OK
EXPECTED (captionsButtonElement.disabled == 'false') OK
EXPECTED (video.textTracks.length == '2') OK
EXPECTED (video.textTracks[0].mode == 'hidden') OK
EXPECTED (video.textTracks[1].mode == 'hidden') OK
Select track 0 and verify it is displayed
*** Click the CC button.
EXPECTED (video.textTracks[0].mode == 'showing') OK
EXPECTED (video.textTracks[1].mode == 'hidden') OK
EXPECTED (textTrackDisplayElement(video, 'display').innerText == 'Lorem') OK
Select track 1 and verify it is displayed
*** Click the CC button.
EXPECTED (video.textTracks[0].mode == 'disabled') OK
EXPECTED (video.textTracks[1].mode == 'showing') OK
EXPECTED (textTrackDisplayElement(video, 'display').innerText == 'first caption') OK
END OF TEST

@ -0,0 +1,49 @@
<!DOCTYPE html>
<script src="media-file.js"></script>
<!-- TODO(srivats): Convert test to testharness.js. crbug.com/588956
(Please avoid writing new tests using video-test.js) -->
<script src="video-test.js"></script>
<script src="media-controls.js"></script>
<script>
function startTest()
{
findMediaElement();
testClosedCaptionsButtonVisibility(true);
consoleWrite("");
testExpected("video.textTracks.length", 2);
testExpected("video.textTracks[0].mode", "hidden");
testExpected("video.textTracks[1].mode", "hidden");
consoleWrite("");
consoleWrite("Select track 0 and verify it is displayed");
selectTextTrack(video, 0);
testExpected("video.textTracks[0].mode", "showing");
testExpected("video.textTracks[1].mode", "hidden");
testExpected("textTrackDisplayElement(video, 'display').innerText", "Lorem");
consoleWrite("");
consoleWrite("Select track 1 and verify it is displayed");
selectTextTrack(video, 1);
testExpected("video.textTracks[0].mode", "disabled");
testExpected("video.textTracks[1].mode", "showing");
testExpected("textTrackDisplayElement(video, 'display').innerText", "first caption");
consoleWrite("");
endTest();
}
window.onload = function()
{
consoleWrite("Test that we can display a track list menu and select tracks from the list");
findMediaElement();
video.src = findMediaFile("video", "content/test");
enableAllTextTracks();
waitForEvent("canplaythrough", startTest);
}
</script>
<video controls>
<track src="track/captions-webvtt/captions.vtt" kind="captions" label="Track1">
<track src="track/captions-webvtt/long-word.vtt" kind="captions" label="Track2">
</video>

@ -456,6 +456,15 @@ template<> inline CSSPrimitiveValue::CSSPrimitiveValue(ControlPart e)
case MediaTimeRemainingPart:
m_value.valueID = CSSValueMediaTimeRemainingDisplay;
break;
case MediaTrackSelectionCheckmarkPart:
m_value.valueID = CSSValueInternalMediaTrackSelectionCheckmark;
break;
case MediaClosedCaptionsIconPart:
m_value.valueID = CSSValueInternalMediaClosedCaptionsIcon;
break;
case MediaSubtitlesIconPart:
m_value.valueID = CSSValueInternalMediaSubtitlesIcon;
break;
case MenulistPart:
m_value.valueID = CSSValueMenulist;
break;

@ -285,6 +285,11 @@ const static NameToPseudoStruct pseudoTypeWithoutArgumentsMap[] = {
{"-internal-list-box", CSSSelector::PseudoListBox},
{"-internal-media-controls-cast-button", CSSSelector::PseudoWebKitCustomElement},
{"-internal-media-controls-overlay-cast-button", CSSSelector::PseudoWebKitCustomElement},
{"-internal-media-controls-text-track-list", CSSSelector::PseudoWebKitCustomElement},
{"-internal-media-controls-text-track-list-item", CSSSelector::PseudoWebKitCustomElement},
{"-internal-media-controls-text-track-list-item-input", CSSSelector::PseudoWebKitCustomElement},
{"-internal-media-controls-text-track-list-kind-captions", CSSSelector::PseudoWebKitCustomElement},
{"-internal-media-controls-text-track-list-kind-subtitles", CSSSelector::PseudoWebKitCustomElement},
{"-internal-spatial-navigation-focus", CSSSelector::PseudoSpatialNavigationFocus},
{"-webkit-any-link", CSSSelector::PseudoAnyLink},
{"-webkit-autofill", CSSSelector::PseudoAutofill},

@ -636,6 +636,9 @@ media-current-time-display
media-time-remaining-display
-internal-media-cast-off-button
-internal-media-overlay-cast-off-button
-internal-media-track-selection-checkmark
-internal-media-closed-captions-icon
-internal-media-subtitles-icon
menulist
menulist-button
menulist-text

@ -44,6 +44,7 @@ video::-webkit-media-controls {
direction: ltr;
display: flex;
flex-direction: column;
font-family: Arial, Helvetica, sans-serif;
justify-content: flex-end;
align-items: center;
}
@ -204,7 +205,6 @@ audio::-webkit-media-controls-time-remaining-display, video::-webkit-media-contr
padding: 0;
line-height: 30px;
font-family: Arial, Helvetica, sans-serif;
font-size: 13px;
font-weight: bold;
font-style: normal;
@ -338,6 +338,82 @@ audio::-webkit-media-controls-fullscreen-volume-max-button, video::-webkit-media
display: none;
}
video::-webkit-scrollbar {
width: 12px;
}
video::-webkit-scrollbar-track {
box-shadow: inset 0 0 6px rgba(20, 20, 20, 0.3);
border-radius: 10px;
}
video::-webkit-scrollbar-thumb {
border-radius: 10px;
background-color: rgba(80, 80, 80, 0.8);
box-shadow: inset 0 0 6px rgba(20, 20, 20, 0.5);
}
video::-internal-media-controls-text-track-list {
position: absolute;
bottom: 35px;
right: 5px;
background: rgba(20, 20, 20, 0.8);
max-width: 50%;
max-height: 250px;
border-radius: 5px;
overflow-x: hidden;
overflow-y: auto;
text-overflow: ellipsis;
margin-bottom: 5px;
white-space: nowrap;
padding-top: 10px;
font-weight: bold;
font-size: 13px;
}
video::-internal-media-controls-text-track-list-item {
display: block;
color: white;
padding: 4px 30px 4px 10px;
border-bottom: 1px solid #555;
text-align: start;
line-height: 30px;
}
video::-internal-media-controls-text-track-list-item:hover {
background-color: rgba(105, 105, 105, 0.8);
}
video::-internal-media-controls-text-track-list-item-input {
-webkit-appearance: -internal-media-track-selection-checkmark;
visibility: hidden;
left: 0;
vertical-align: middle;
margin: 0 5px 0 0;
width: 25px;
height: 25px;
}
video::-internal-media-controls-text-track-list-item-input:checked {
visibility: visible;
}
video::-internal-media-controls-text-track-list-kind-captions {
-webkit-appearance: -internal-media-closed-captions-icon;
height: 24px;
width: 24px;
margin-left: 10px;
vertical-align: middle;
}
video::-internal-media-controls-text-track-list-kind-subtitles {
-webkit-appearance: -internal-media-subtitles-icon;
height: 24px;
width: 24px;
margin-left: 10px;
vertical-align: middle;
}
video::-webkit-media-text-track-container {
position: relative;
width: inherit;

@ -44,6 +44,7 @@ video::-webkit-media-controls {
direction: ltr;
display: flex;
flex-direction: column;
font-family: Segoe, "Helvetica Neue", Roboto, Arial, Helvetica, sans-serif;
justify-content: flex-end;
align-items: center;
}
@ -80,7 +81,6 @@ audio::-webkit-media-controls-panel, video::-webkit-media-controls-panel {
/* The duration is also specified in MediaControlElements.cpp and LayoutTests/media/media-controls.js */
transition: opacity 0.3s;
font-family: Segoe, "Helvetica Neue", Roboto, Arial, Helvetica, sans-serif ;
font-size: 14px;
font-weight: normal; /* Make sure that we don't inherit non-defaults. */
font-style: normal;
@ -359,6 +359,65 @@ audio::-webkit-media-controls-fullscreen-volume-max-button, video::-webkit-media
display: none;
}
video::-internal-media-controls-text-track-list {
position: absolute;
bottom: 48px;
right: 0px;
background-color: #fafafa;
max-width: 50%;
max-height: 250px;
min-width: 150px;
overflow-x: hidden;
overflow-y: auto;
white-space: nowrap;
font-size: 14px;
padding: 8px 0px;
}
video::-internal-media-controls-text-track-list-item {
display: block;
color: #424242;
text-align: start;
line-height: 40px;
padding-right: 16px;
text-overflow: ellipsis;
}
video::-internal-media-controls-text-track-list-item:hover {
background-color: #e0e0e0;
}
video::-internal-media-controls-text-track-list-item-input {
-webkit-appearance: -internal-media-track-selection-checkmark;
visibility: hidden;
left: 0;
vertical-align: middle;
margin: 0 5px 0 0;
width: 16px;
height: 16px;
margin-left: 12px;
}
video::-internal-media-controls-text-track-list-item-input:checked {
visibility: visible;
}
video::-internal-media-controls-text-track-list-kind-captions {
-webkit-appearance: -internal-media-closed-captions-icon;
height: 20px;
width: 20px;
margin-left: 10px;
vertical-align: middle;
}
video::-internal-media-controls-text-track-list-kind-subtitles {
-webkit-appearance: -internal-media-subtitles-icon;
height: 20px;
width: 20px;
margin-left: 10px;
vertical-align: middle;
}
video::-webkit-media-text-track-container {
position: relative;
width: inherit;

@ -413,8 +413,9 @@ HTMLMediaElement::HTMLMediaElement(const QualifiedName& tagName, Document& docum
, m_seeking(false)
, m_sentStalledEvent(false)
, m_sentEndEvent(false)
, m_closedCaptionsVisible(false)
, m_ignorePreloadNone(false)
, m_textTracksVisible(false)
, m_shouldPerformAutomaticTrackSelection(true)
, m_tracksAreReady(true)
, m_processingPreferenceChange(false)
, m_remoteRoutesAvailable(false)
@ -1231,6 +1232,11 @@ void HTMLMediaElement::textTrackModeChanged(TextTrack* track)
textTracks()->scheduleChangeEvent();
}
void HTMLMediaElement::disableAutomaticTextTrackSelection()
{
m_shouldPerformAutomaticTrackSelection = false;
}
bool HTMLMediaElement::isSafeToLoadURL(const KURL& url, InvalidURLAction actionIfInvalid)
{
if (!url.isValid()) {
@ -2592,10 +2598,13 @@ void HTMLMediaElement::honorUserPreferencesForAutomaticTextTrackSelection()
if (!m_textTracks || !m_textTracks->length())
return;
if (!m_shouldPerformAutomaticTrackSelection)
return;
AutomaticTrackSelection::Configuration configuration;
if (m_processingPreferenceChange)
configuration.disableCurrentlyEnabledTracks = true;
if (m_closedCaptionsVisible)
if (m_textTracksVisible)
configuration.forceEnableSubtitleOrCaptionTrack = true;
Settings* settings = document().settings();
@ -3265,9 +3274,9 @@ bool HTMLMediaElement::hasClosedCaptions() const
return false;
}
bool HTMLMediaElement::closedCaptionsVisible() const
bool HTMLMediaElement::textTracksVisible() const
{
return m_closedCaptionsVisible;
return m_textTracksVisible;
}
static void assertShadowRootChildren(ShadowRoot& shadowRoot)
@ -3323,30 +3332,10 @@ void HTMLMediaElement::mediaControlsDidBecomeVisible()
// When the user agent starts exposing a user interface for a video element,
// the user agent should run the rules for updating the text track rendering
// of each of the text tracks in the video element's list of text tracks ...
if (isHTMLVideoElement() && closedCaptionsVisible())
if (isHTMLVideoElement() && textTracksVisible())
ensureTextTrackContainer().updateDisplay(*this, TextTrackContainer::DidStartExposingControls);
}
void HTMLMediaElement::setClosedCaptionsVisible(bool closedCaptionVisible)
{
WTF_LOG(Media, "HTMLMediaElement::setClosedCaptionsVisible(%p, %s)", this, boolString(closedCaptionVisible));
if (!hasClosedCaptions())
return;
m_closedCaptionsVisible = closedCaptionVisible;
markCaptionAndSubtitleTracksAsUnconfigured();
m_processingPreferenceChange = true;
honorUserPreferencesForAutomaticTextTrackSelection();
m_processingPreferenceChange = false;
// As track visibility changed while m_processingPreferenceChange was set,
// there was no call to updateTextTrackDisplay(). This call is not in the
// spec, see the note in configureTextTrackDisplay().
updateTextTrackDisplay();
}
void HTMLMediaElement::setTextTrackKindUserPreferenceForAllMediaElements(Document* document)
{
auto it = documentToElementSetMap().find(document);
@ -3365,13 +3354,13 @@ void HTMLMediaElement::automaticTrackSelectionForUpdatedUserPreference()
markCaptionAndSubtitleTracksAsUnconfigured();
m_processingPreferenceChange = true;
m_closedCaptionsVisible = false;
m_textTracksVisible = false;
honorUserPreferencesForAutomaticTextTrackSelection();
m_processingPreferenceChange = false;
// If a track is set to 'showing' post performing automatic track selection,
// set closed captions state to visible to update the CC button and display the track.
m_closedCaptionsVisible = m_textTracks->hasShowingTracks();
// set text tracks state to visible to update the CC button and display the track.
m_textTracksVisible = m_textTracks->hasShowingTracks();
updateTextTrackDisplay();
}
@ -3496,7 +3485,7 @@ void HTMLMediaElement::configureTextTrackDisplay()
return;
bool haveVisibleTextTrack = m_textTracks->hasShowingTracks();
m_closedCaptionsVisible = haveVisibleTextTrack;
m_textTracksVisible = haveVisibleTextTrack;
if (!haveVisibleTextTrack && !mediaControls())
return;

@ -207,6 +207,7 @@ public:
void textTrackReadyStateChanged(TextTrack*);
void textTrackModeChanged(TextTrack*);
void disableAutomaticTextTrackSelection();
// EventTarget function.
// Both Node (via HTMLElement) and ActiveDOMObject define this method, which
@ -223,8 +224,7 @@ public:
virtual bool usesOverlayFullscreenVideo() const { return false; }
bool hasClosedCaptions() const;
bool closedCaptionsVisible() const;
void setClosedCaptionsVisible(bool);
bool textTracksVisible() const;
static void setTextTrackKindUserPreferenceForAllMediaElements(Document*);
void automaticTrackSelectionForUpdatedUserPreference();
@ -561,9 +561,11 @@ private:
// time has not changed since sending an "ended" event
bool m_sentEndEvent : 1;
bool m_closedCaptionsVisible : 1;
bool m_ignorePreloadNone : 1;
bool m_textTracksVisible : 1;
bool m_shouldPerformAutomaticTrackSelection : 1;
bool m_tracksAreReady : 1;
bool m_processingPreferenceChange : 1;
bool m_remoteRoutesAvailable : 1;

@ -48,11 +48,13 @@ enum MediaControlElementType {
MediaSliderThumb,
MediaShowClosedCaptionsButton,
MediaHideClosedCaptionsButton,
MediaTextTrackList,
MediaUnMuteButton,
MediaPauseButton,
MediaTimelineContainer,
MediaCurrentTimeDisplay,
MediaTimeRemainingDisplay,
MediaTrackSelectionCheckmark,
MediaControlsPanel,
MediaVolumeSliderContainer,
MediaVolumeSlider,

@ -32,19 +32,25 @@
#include "bindings/core/v8/ExceptionStatePlaceholder.h"
#include "core/InputTypeNames.h"
#include "core/dom/ClientRect.h"
#include "core/dom/Text.h"
#include "core/dom/shadow/ShadowRoot.h"
#include "core/events/MouseEvent.h"
#include "core/frame/LocalFrame.h"
#include "core/html/HTMLLabelElement.h"
#include "core/html/HTMLMediaSource.h"
#include "core/html/HTMLSpanElement.h"
#include "core/html/HTMLVideoElement.h"
#include "core/html/TimeRanges.h"
#include "core/html/shadow/MediaControls.h"
#include "core/html/track/TextTrackList.h"
#include "core/input/EventHandler.h"
#include "core/layout/LayoutTheme.h"
#include "core/layout/LayoutVideo.h"
#include "core/layout/api/LayoutSliderItem.h"
#include "platform/EventDispatchForbiddenScope.h"
#include "platform/Histogram.h"
#include "platform/RuntimeEnabledFeatures.h"
#include "platform/text/PlatformLocale.h"
#include "public/platform/Platform.h"
#include "public/platform/UserMetricsAction.h"
@ -57,6 +63,16 @@ namespace {
// This is the duration from mediaControls.css
const double fadeOutDuration = 0.3;
const QualifiedName& trackIndexAttrName()
{
// Save the track index in an attribute to avoid holding a pointer to the text track.
DEFINE_STATIC_LOCAL(QualifiedName, trackIndexAttr, (nullAtom, "data-track-index", nullAtom));
return trackIndexAttr;
}
// When specified as trackIndex, disable text tracks.
const int trackIndexOffValue = -1;
bool isUserInteractionEvent(Event* event)
{
const AtomicString& type = event->type();
@ -96,6 +112,21 @@ Element* elementFromCenter(Element& element)
return element.document().elementFromPoint(centerX , centerY);
}
bool hasDuplicateLabel(TextTrack* currentTrack)
{
DCHECK(currentTrack);
TextTrackList* trackList = currentTrack->trackList();
// The runtime of this method is quadratic but since there are usually very few text tracks it won't
// affect the performance much.
String currentTrackLabel = currentTrack->label();
for (unsigned i = 0; i < trackList->length(); i++) {
TextTrack* track = trackList->anonymousIndexedGetter(i);
if (currentTrack != track && currentTrackLabel == track->label())
return true;
}
return false;
}
} // anonymous namespace
MediaControlPanelElement::MediaControlPanelElement(MediaControls& mediaControls)
@ -371,20 +402,14 @@ MediaControlToggleClosedCaptionsButtonElement* MediaControlToggleClosedCaptionsB
void MediaControlToggleClosedCaptionsButtonElement::updateDisplayType()
{
bool captionsVisible = mediaElement().closedCaptionsVisible();
bool captionsVisible = mediaElement().textTracksVisible();
setDisplayType(captionsVisible ? MediaHideClosedCaptionsButton : MediaShowClosedCaptionsButton);
setChecked(captionsVisible);
}
void MediaControlToggleClosedCaptionsButtonElement::defaultEventHandler(Event* event)
{
if (event->type() == EventTypeNames::click) {
if (mediaElement().closedCaptionsVisible())
Platform::current()->recordAction(UserMetricsAction("Media.Controls.ClosedCaptionHide"));
else
Platform::current()->recordAction(UserMetricsAction("Media.Controls.ClosedCaptionShow"));
mediaElement().setClosedCaptionsVisible(!mediaElement().closedCaptionsVisible());
setChecked(mediaElement().closedCaptionsVisible());
mediaControls().toggleTextTrackList();
updateDisplayType();
event->setDefaultHandled();
}
@ -394,6 +419,144 @@ void MediaControlToggleClosedCaptionsButtonElement::defaultEventHandler(Event* e
// ----------------------------
MediaControlTextTrackListElement::MediaControlTextTrackListElement(MediaControls& mediaControls)
: MediaControlDivElement(mediaControls, MediaTextTrackList)
{
}
MediaControlTextTrackListElement* MediaControlTextTrackListElement::create(MediaControls& mediaControls)
{
MediaControlTextTrackListElement* element = new MediaControlTextTrackListElement(mediaControls);
element->setShadowPseudoId(AtomicString("-internal-media-controls-text-track-list"));
element->setIsWanted(false);
return element;
}
void MediaControlTextTrackListElement::defaultEventHandler(Event* event)
{
if (event->type() == EventTypeNames::change) {
// Identify which input element was selected and set track to showing
Node* target = event->target()->toNode();
if (!target || !target->isElementNode())
return;
disableShowingTextTracks();
int trackIndex = toElement(target)->getIntegralAttribute(trackIndexAttrName());
if (trackIndex != trackIndexOffValue) {
ASSERT(trackIndex >= 0);
showTextTrackAtIndex(trackIndex);
mediaElement().disableAutomaticTextTrackSelection();
}
mediaControls().toggleTextTrackList();
event->setDefaultHandled();
}
MediaControlDivElement::defaultEventHandler(event);
}
void MediaControlTextTrackListElement::setVisible(bool visible)
{
if (visible) {
setIsWanted(true);
refreshTextTrackListMenu();
} else {
setIsWanted(false);
}
}
void MediaControlTextTrackListElement::showTextTrackAtIndex(unsigned indexToEnable)
{
TextTrackList* trackList = mediaElement().textTracks();
if (indexToEnable >= trackList->length())
return;
TextTrack* track = trackList->anonymousIndexedGetter(indexToEnable);
if (track && track->canBeRendered())
track->setMode(TextTrack::showingKeyword());
}
void MediaControlTextTrackListElement::disableShowingTextTracks()
{
TextTrackList* trackList = mediaElement().textTracks();
for (unsigned i = 0; i < trackList->length(); ++i) {
TextTrack* track = trackList->anonymousIndexedGetter(i);
if (track->mode() == TextTrack::showingKeyword())
track->setMode(TextTrack::disabledKeyword());
}
}
String MediaControlTextTrackListElement::getTextTrackLabel(TextTrack* track)
{
if (!track)
return mediaElement().locale().queryString(WebLocalizedString::TextTracksOff);
String trackLabel = track->label();
if (trackLabel.isEmpty())
trackLabel = String(mediaElement().locale().queryString(WebLocalizedString::TextTracksNoLabel));
return trackLabel;
}
// TextTrack parameter when passed in as a nullptr, creates the "Off" list item in the track list.
Element* MediaControlTextTrackListElement::createTextTrackListItem(TextTrack* track)
{
int trackIndex = track ? track->trackIndex() : trackIndexOffValue;
HTMLLabelElement* trackItem = HTMLLabelElement::create(document(), nullptr);
trackItem->setShadowPseudoId(AtomicString("-internal-media-controls-text-track-list-item"));
HTMLInputElement* trackItemInput = HTMLInputElement::create(document(), nullptr, false);
trackItemInput->setShadowPseudoId(AtomicString("-internal-media-controls-text-track-list-item-input"));
trackItemInput->setType(InputTypeNames::checkbox);
trackItemInput->setIntegralAttribute(trackIndexAttrName(), trackIndex);
if (!mediaElement().textTracksVisible()) {
if (!track)
trackItemInput->setChecked(true);
} else {
// If there are multiple text tracks set to showing, they must all have
// checkmarks displayed.
if (track && track->mode() == TextTrack::showingKeyword())
trackItemInput->setChecked(true);
}
trackItem->appendChild(trackItemInput);
String trackLabel = getTextTrackLabel(track);
trackItem->appendChild(Text::create(document(), trackLabel));
// Add a track kind marker icon if there are multiple tracks with the same label or if the track has no label.
if (track && (track->label().isEmpty() || hasDuplicateLabel(track))) {
HTMLSpanElement* trackKindMarker = HTMLSpanElement::create(document());
if (track->kind() == track->captionsKeyword()) {
trackKindMarker->setShadowPseudoId(AtomicString("-internal-media-controls-text-track-list-kind-captions"));
} else {
ASSERT(track->kind() == track->subtitlesKeyword());
trackKindMarker->setShadowPseudoId(AtomicString("-internal-media-controls-text-track-list-kind-subtitles"));
}
trackItem->appendChild(trackKindMarker);
}
return trackItem;
}
void MediaControlTextTrackListElement::refreshTextTrackListMenu()
{
if (!mediaElement().hasClosedCaptions() || !mediaElement().textTracksAreReady())
return;
EventDispatchForbiddenScope::AllowUserAgentEvents allowEvents;
removeChildren(OmitSubtreeModifiedEvent);
// Construct a menu for subtitles and captions
// Pass in a nullptr to createTextTrackListItem to create the "Off" track item.
appendChild(createTextTrackListItem(nullptr));
TextTrackList* trackList = mediaElement().textTracks();
for (unsigned i = 0; i < trackList->length(); i++) {
TextTrack* track = trackList->anonymousIndexedGetter(i);
if (!track->canBeRendered())
continue;
appendChild(createTextTrackListItem(track));
}
}
// ----------------------------
MediaControlTimelineElement::MediaControlTimelineElement(MediaControls& mediaControls)
: MediaControlInputElement(mediaControls, MediaSlider)
{

@ -34,6 +34,8 @@
namespace blink {
class TextTrack;
// ----------------------------
class MediaControlPanelElement final : public MediaControlDivElement {
@ -146,6 +148,32 @@ private:
// ----------------------------
class MediaControlTextTrackListElement final : public MediaControlDivElement {
public:
static MediaControlTextTrackListElement* create(MediaControls&);
bool willRespondToMouseClickEvents() override { return true; }
void setVisible(bool);
private:
explicit MediaControlTextTrackListElement(MediaControls&);
void defaultEventHandler(Event*) override;
void refreshTextTrackListMenu();
// Returns the label for the track when a valid track is passed in and "Off" when the parameter is null.
String getTextTrackLabel(TextTrack*);
// Creates the track element in the list when a valid track is passed in and the "Off" item when the parameter is null.
Element* createTextTrackListItem(TextTrack*);
void showTextTrackAtIndex(unsigned);
void disableShowingTextTracks();
};
// ----------------------------
class MediaControlTimelineElement final : public MediaControlInputElement {
public:
static MediaControlTimelineElement* create(MediaControls&);

@ -116,6 +116,7 @@ MediaControls::MediaControls(HTMLMediaElement& mediaElement)
, m_muteButton(nullptr)
, m_volumeSlider(nullptr)
, m_toggleClosedCaptionsButton(nullptr)
, m_textTrackList(nullptr)
, m_castButton(nullptr)
, m_fullScreenButton(nullptr)
, m_hideMediaControlsTimer(this, &MediaControls::hideMediaControlsTimerFired)
@ -157,6 +158,12 @@ MediaControls* MediaControls::create(HTMLMediaElement& mediaElement)
// +-MediaControlToggleClosedCaptionsButtonElement (-webkit-media-controls-toggle-closed-captions-button)
// +-MediaControlCastButtonElement (-internal-media-controls-cast-button)
// \-MediaControlFullscreenButtonElement (-webkit-media-controls-fullscreen-button)
// +-MediaControlTextTrackListElement (-internal-media-controls-text-track-list)
// | {for each renderable text track}
// \-MediaControlTextTrackListItem (-internal-media-controls-text-track-list-item)
// +-MediaControlTextTrackListItemInput (-internal-media-controls-text-track-list-item-input)
// +-MediaControlTextTrackListItemCaptions (-internal-media-controls-text-track-list-kind-captions)
// +-MediaControlTextTrackListItemSubtitles (-internal-media-controls-text-track-list-kind-subtitles)
void MediaControls::initializeControls()
{
const bool useNewUi = RuntimeEnabledFeatures::newMediaPlaybackUiEnabled();
@ -231,6 +238,10 @@ void MediaControls::initializeControls()
m_enclosure = enclosure;
appendChild(enclosure);
MediaControlTextTrackListElement* textTrackList = MediaControlTextTrackListElement::create(*this);
m_textTrackList = textTrackList;
appendChild(textTrackList);
}
void MediaControls::reset()
@ -334,6 +345,9 @@ bool MediaControls::shouldHideMediaControls(unsigned behaviorFlags) const
const bool ignoreFocus = behaviorFlags & IgnoreFocus;
if (!ignoreFocus && (mediaElement().focused() || contains(document().focusedElement())))
return false;
// Don't hide the media controls when the text track list is showing.
if (m_textTrackList->isWanted())
return false;
return true;
}
@ -460,6 +474,16 @@ void MediaControls::refreshClosedCaptionsButtonVisibility()
BatchedControlUpdate batch(this);
}
void MediaControls::toggleTextTrackList()
{
if (!mediaElement().hasClosedCaptions()) {
m_textTrackList->setVisible(false);
return;
}
m_textTrackList->setVisible(!m_textTrackList->isWanted());
}
void MediaControls::refreshCastButtonVisibility()
{
refreshCastButtonVisibilityWithoutUpdate();
@ -759,6 +783,7 @@ DEFINE_TRACE(MediaControls)
visitor->trace(m_fullScreenButton);
visitor->trace(m_durationDisplay);
visitor->trace(m_enclosure);
visitor->trace(m_textTrackList);
visitor->trace(m_castButton);
visitor->trace(m_overlayCastButton);
HTMLDivElement::trace(visitor);

@ -59,6 +59,7 @@ public:
void changedClosedCaptionsVisibility();
void refreshClosedCaptionsButtonVisibility();
void toggleTextTrackList();
void enteredFullscreen();
void exitedFullscreen();
@ -141,6 +142,7 @@ private:
Member<MediaControlMuteButtonElement> m_muteButton;
Member<MediaControlVolumeSliderElement> m_volumeSlider;
Member<MediaControlToggleClosedCaptionsButtonElement> m_toggleClosedCaptionsButton;
Member<MediaControlTextTrackListElement> m_textTrackList;
Member<MediaControlCastButtonElement> m_castButton;
Member<MediaControlFullscreenButtonElement> m_fullScreenButton;

@ -414,7 +414,7 @@ void TextTrack::invalidateTrackIndex()
m_renderedTrackIndex = invalidTrackIndex;
}
bool TextTrack::isRendered()
bool TextTrack::isRendered() const
{
if (kind() != captionsKeyword() && kind() != subtitlesKeyword())
return false;
@ -425,6 +425,18 @@ bool TextTrack::isRendered()
return true;
}
bool TextTrack::canBeRendered() const
{
// A track can be displayed when it's of kind captions or subtitles and hasn't failed to load.
if (kind() != captionsKeyword() && kind() != subtitlesKeyword())
return false;
if (getReadinessState() == FailedToLoad)
return false;
return true;
}
TextTrackCueList* TextTrack::ensureTextTrackCueList()
{
if (!m_cues)

@ -102,7 +102,8 @@ public:
int trackIndex();
void invalidateTrackIndex();
bool isRendered();
bool isRendered() const;
bool canBeRendered() const;
int trackIndexRelativeToRenderedTracks();
bool hasBeenConfigured() const { return m_hasBeenConfigured; }

@ -54,7 +54,7 @@ LayoutObject* TextTrackContainer::createLayoutObject(const ComputedStyle&)
void TextTrackContainer::updateDisplay(HTMLMediaElement& mediaElement, ExposingControls exposingControls)
{
if (!mediaElement.closedCaptionsVisible()) {
if (!mediaElement.textTracksVisible()) {
removeChildren();
return;
}

@ -504,7 +504,7 @@ bool MediaControlsPainter::paintMediaToggleClosedCaptionsButton(const LayoutObje
bool isEnabled = mediaElement->hasClosedCaptions();
if (mediaElement->closedCaptionsVisible())
if (mediaElement->textTracksVisible())
return paintMediaButton(paintInfo.context, rect, mediaClosedCaptionButton, isEnabled);
return paintMediaButton(paintInfo.context, rect, mediaClosedCaptionButtonDisabled, isEnabled);
@ -541,6 +541,39 @@ bool MediaControlsPainter::paintMediaCastButton(const LayoutObject& object, cons
}
}
bool MediaControlsPainter::paintMediaTrackSelectionCheckmark(const LayoutObject& object, const PaintInfo& paintInfo, const IntRect& rect)
{
const HTMLMediaElement* mediaElement = toParentMediaElement(object);
if (!mediaElement)
return false;
static Image* mediaTrackSelectionCheckmark = platformResource("mediaplayerTrackSelectionCheckmark",
"mediaplayerTrackSelectionCheckmarkNew");
return paintMediaButton(paintInfo.context, rect, mediaTrackSelectionCheckmark);
}
bool MediaControlsPainter::paintMediaClosedCaptionsIcon(const LayoutObject& object, const PaintInfo& paintInfo, const IntRect& rect)
{
const HTMLMediaElement* mediaElement = toParentMediaElement(object);
if (!mediaElement)
return false;
static Image* mediaClosedCaptionsIcon = platformResource("mediaplayerClosedCaptionsIcon",
"mediaplayerClosedCaptionsIconNew");
return paintMediaButton(paintInfo.context, rect, mediaClosedCaptionsIcon);
}
bool MediaControlsPainter::paintMediaSubtitlesIcon(const LayoutObject& object, const PaintInfo& paintInfo, const IntRect& rect)
{
const HTMLMediaElement* mediaElement = toParentMediaElement(object);
if (!mediaElement)
return false;
static Image* mediaSubtitlesIcon = platformResource("mediaplayerSubtitlesIcon",
"mediaplayerSubtitlesIconNew");
return paintMediaButton(paintInfo.context, rect, mediaSubtitlesIcon);
}
void MediaControlsPainter::adjustMediaSliderThumbSize(ComputedStyle& style)
{
static Image* mediaSliderThumb = platformResource("mediaplayerSliderThumb",

@ -51,6 +51,9 @@ public:
static bool paintMediaFullscreenButton(const LayoutObject&, const PaintInfo&, const IntRect&);
static bool paintMediaOverlayPlayButton(const LayoutObject&, const PaintInfo&, const IntRect&);
static bool paintMediaCastButton(const LayoutObject&, const PaintInfo&, const IntRect&);
static bool paintMediaTrackSelectionCheckmark(const LayoutObject&, const PaintInfo&, const IntRect&);
static bool paintMediaClosedCaptionsIcon(const LayoutObject&, const PaintInfo&, const IntRect&);
static bool paintMediaSubtitlesIcon(const LayoutObject&, const PaintInfo&, const IntRect&);
static void adjustMediaSliderThumbSize(ComputedStyle&);
private:

@ -137,6 +137,12 @@ bool ThemePainter::paint(const LayoutObject& o, const PaintInfo& paintInfo, cons
case MediaCastOffButtonPart:
case MediaOverlayCastOffButtonPart:
return MediaControlsPainter::paintMediaCastButton(o, paintInfo, r);
case MediaTrackSelectionCheckmarkPart:
return MediaControlsPainter::paintMediaTrackSelectionCheckmark(o, paintInfo, r);
case MediaClosedCaptionsIconPart:
return MediaControlsPainter::paintMediaClosedCaptionsIcon(o, paintInfo, r);
case MediaSubtitlesIconPart:
return MediaControlsPainter::paintMediaSubtitlesIcon(o, paintInfo, r);
case MenulistButtonPart:
case TextFieldPart:
case TextAreaPart:

@ -50,7 +50,8 @@ enum ControlPart {
MediaEnterFullscreenButtonPart, MediaExitFullscreenButtonPart, MediaFullScreenVolumeSliderPart, MediaFullScreenVolumeSliderThumbPart, MediaMuteButtonPart, MediaPlayButtonPart,
MediaOverlayPlayButtonPart, MediaToggleClosedCaptionsButtonPart,
MediaSliderPart, MediaSliderThumbPart, MediaVolumeSliderContainerPart, MediaVolumeSliderPart, MediaVolumeSliderThumbPart,
MediaControlsBackgroundPart, MediaControlsFullscreenBackgroundPart, MediaCurrentTimePart, MediaTimeRemainingPart, MediaCastOffButtonPart, MediaOverlayCastOffButtonPart,
MediaControlsBackgroundPart, MediaControlsFullscreenBackgroundPart, MediaCurrentTimePart, MediaTimeRemainingPart, MediaCastOffButtonPart,
MediaOverlayCastOffButtonPart, MediaTrackSelectionCheckmarkPart, MediaClosedCaptionsIconPart, MediaSubtitlesIconPart,
MenulistPart, MenulistButtonPart, MenulistTextPart, MenulistTextFieldPart, MeterPart, ProgressBarPart, ProgressBarValuePart,
SliderHorizontalPart, SliderVerticalPart, SliderThumbHorizontalPart,
SliderThumbVerticalPart, CaretPart, SearchFieldPart, SearchFieldDecorationPart,

@ -44,6 +44,12 @@
<structure type="chrome_scaled_image" name="IDR_MEDIAPLAYER_OVERLAY_CAST_BUTTON_ON_NEW" file="blink/mediaplayer_overlay_cast_on_new.png" />
<structure type="chrome_scaled_image" name="IDR_MEDIAPLAYER_OVERLAY_PLAY_BUTTON" file="blink/mediaplayer_overlay_play.png" />
<structure type="chrome_scaled_image" name="IDR_MEDIAPLAYER_OVERLAY_PLAY_BUTTON_NEW" file="blink/mediaplayer_overlay_play_new.png" />
<structure type="chrome_scaled_image" name="IDR_MEDIAPLAYER_TRACKSELECTION_CHECKMARK" file="blink/mediaplayer_trackselection_checkmark.png" />
<structure type="chrome_scaled_image" name="IDR_MEDIAPLAYER_TRACKSELECTION_CHECKMARK_NEW" file="blink/mediaplayer_trackselection_checkmark_new.png" />
<structure type="chrome_scaled_image" name="IDR_MEDIAPLAYER_CLOSEDCAPTIONS_ICON" file="blink/mediaplayer_closedcaptions_icon.png" />
<structure type="chrome_scaled_image" name="IDR_MEDIAPLAYER_CLOSEDCAPTIONS_ICON_NEW" file="blink/mediaplayer_closedcaptions_icon_new.png" />
<structure type="chrome_scaled_image" name="IDR_MEDIAPLAYER_SUBTITLES_ICON" file="blink/mediaplayer_subtitles_icon.png" />
<structure type="chrome_scaled_image" name="IDR_MEDIAPLAYER_SUBTITLES_ICON_NEW" file="blink/mediaplayer_subtitles_icon_new.png" />
<structure type="chrome_scaled_image" name="IDR_SEARCH_CANCEL" file="blink/search_cancel.png" />
<structure type="chrome_scaled_image" name="IDR_SEARCH_CANCEL_PRESSED" file="blink/search_cancel_pressed.png" />
<structure type="chrome_scaled_image" name="IDR_SEARCH_MAGNIFIER" file="blink/search_magnifier.png" />

Binary file not shown.

After

(image error) Size: 1.9 KiB

Binary file not shown.

After

(image error) Size: 1.2 KiB

Binary file not shown.

After

(image error) Size: 1.5 KiB

Binary file not shown.

After

(image error) Size: 896 B

Binary file not shown.

After

(image error) Size: 1.6 KiB

Binary file not shown.

After

(image error) Size: 1.2 KiB

@ -138,6 +138,8 @@ struct WebLocalizedString {
SearchMenuRecentSearchesText, // Deprecated.
SelectMenuListText,
SubmitButtonDefaultLabel,
TextTracksNoLabel,
TextTracksOff,
ThisMonthButtonLabel,
ThisWeekButtonLabel,
ValidationBadInputForNumber,