Commit 054e29c3 authored by Becca Hughes's avatar Becca Hughes Committed by Commit Bot

Media Controls: Add "use-default-poster" state

Add a "use-default-poster" CSS class and add it if the video
element does not have a video frame or poster to display. As
per the spec then hide the controls apart from the play button
and display a gradient background.

BUG=761308

Change-Id: I579a1be27ecdb375e770b2fbc841593f7ce2abc3
Reviewed-on: https://chromium-review.googlesource.com/733092
Commit-Queue: Becca Hughes <beccahughes@chromium.org>
Reviewed-by: default avatarMounir Lamouri <mlamouri@chromium.org>
Cr-Commit-Position: refs/heads/master@{#512884}
parent fd502dfe
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
"backgroundColor": "#ADD8E6" "backgroundColor": "#ADD8E6"
}, },
{ {
"name": "LayoutFlexibleBox (relative positioned) DIV class='phase-pre-ready state-no-source'", "name": "LayoutFlexibleBox (relative positioned) DIV class='phase-pre-ready state-no-source use-default-poster'",
"bounds": [100, 100] "bounds": [100, 100]
}, },
{ {
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
<script> <script>
async_test(t => { async_test(t => {
const video = document.querySelector('video'); const video = document.querySelector('video');
checkControlsClassName(video, "phase-pre-ready state-no-source"); checkControlsClassName(video, "phase-pre-ready state-no-source use-default-poster");
video.onstalled = t.step_func_done(() => { video.onstalled = t.step_func_done(() => {
checkControlsClassName(video, "phase-ready state-buffering"); checkControlsClassName(video, "phase-ready state-buffering");
......
...@@ -11,7 +11,7 @@ async_test(t => { ...@@ -11,7 +11,7 @@ async_test(t => {
const video = document.querySelector('video'); const video = document.querySelector('video');
let was_paused = false; let was_paused = false;
checkControlsClassName(video, "phase-pre-ready state-no-source"); checkControlsClassName(video, "phase-pre-ready state-no-source use-default-poster");
video.onstalled = t.step_func(() => { video.onstalled = t.step_func(() => {
checkControlsClassName(video, "state-loading-metadata"); checkControlsClassName(video, "state-loading-metadata");
......
<!DOCTYPE html>
<html>
<title>Test that poster availability is reflected in CSS classes.</title>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="../media-controls.js"></script>
<script src="../media-file.js"></script>
<video controls width=400 preload=auto></video>
<script>
async_test(t => {
const video = document.querySelector('video');
checkControlsClassName(video, 'phase-pre-ready state-no-source use-default-poster');
video.oncanplay = t.step_func_done(() => {
checkControlsClassName(video, 'phase-ready state-stopped');
});
video.src = findMediaFile('video', '../content/counting');
});
</script>
</html>
<!DOCTYPE html>
<html>
<title>Test that poster availability is reflected in CSS classes.</title>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="../media-controls.js"></script>
<script src="../media-file.js"></script>
<video controls width=400 preload=auto poster="../content/greenbox.png"></video>
<script>
async_test(t => {
const video = document.querySelector('video');
checkControlsClassName(video, 'phase-pre-ready state-no-source');
video.oncanplay = t.step_func_done(() => {
checkControlsClassName(video, 'phase-ready state-stopped');
});
video.src = findMediaFile('video', '../content/counting');
});
</script>
</html>
...@@ -9,10 +9,10 @@ ...@@ -9,10 +9,10 @@
<script> <script>
async_test(t => { async_test(t => {
const video = document.querySelector('video'); const video = document.querySelector('video');
checkControlsClassName(video, "phase-pre-ready state-no-source"); checkControlsClassName(video, "phase-pre-ready state-no-source use-default-poster");
video.onerror = t.step_func_done(() => { video.onerror = t.step_func_done(() => {
checkControlsClassName(video, "phase-pre-ready state-no-source"); checkControlsClassName(video, "phase-pre-ready state-no-source use-default-poster");
}); });
video.src = findMediaFile("video", "../content/missing"); video.src = findMediaFile("video", "../content/missing");
......
...@@ -11,7 +11,7 @@ async_test(t => { ...@@ -11,7 +11,7 @@ async_test(t => {
const video = document.querySelector('video'); const video = document.querySelector('video');
let count = 0; let count = 0;
checkControlsClassName(video, "phase-pre-ready state-no-source"); checkControlsClassName(video, "phase-pre-ready state-no-source use-default-poster");
video.onplaying = t.step_func(() => { video.onplaying = t.step_func(() => {
checkControlsClassName(video, "phase-ready state-playing"); checkControlsClassName(video, "phase-ready state-playing");
......
<!DOCTYPE html>
<html>
<title>Test that poster availability is reflected in CSS classes.</title>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="../media-controls.js"></script>
<script src="../media-file.js"></script>
<video controls width=400 preload=metadata></video>
<script>
async_test(t => {
const video = document.querySelector('video');
let count = 0;
checkControlsClassName(video, 'phase-pre-ready state-no-source use-default-poster');
video.onloadedmetadata = t.step_func_done(() => {
checkControlsClassName(video, 'phase-ready state-stopped use-default-poster');
});
video.src = findMediaFile('video', '../content/counting');
});
</script>
</html>
...@@ -115,6 +115,8 @@ const char* kStateCSSClasses[6] = { ...@@ -115,6 +115,8 @@ const char* kStateCSSClasses[6] = {
constexpr int kModernControlsAudioButtonPadding = 20; constexpr int kModernControlsAudioButtonPadding = 20;
constexpr int kModernControlsVideoButtonPadding = 26; constexpr int kModernControlsVideoButtonPadding = 26;
const char kShowDefaultPosterCSSClass[] = "use-default-poster";
bool ShouldShowFullscreenButton(const HTMLMediaElement& media_element) { bool ShouldShowFullscreenButton(const HTMLMediaElement& media_element) {
// Unconditionally allow the user to exit fullscreen if we are in it // Unconditionally allow the user to exit fullscreen if we are in it
// now. Especially on android, when we might not yet know if // now. Especially on android, when we might not yet know if
...@@ -556,7 +558,17 @@ Node::InsertionNotificationRequest MediaControlsImpl::InsertedInto( ...@@ -556,7 +558,17 @@ Node::InsertionNotificationRequest MediaControlsImpl::InsertedInto(
} }
void MediaControlsImpl::UpdateCSSClassFromState() { void MediaControlsImpl::UpdateCSSClassFromState() {
const char* classes = kStateCSSClasses[State()]; StringBuilder builder;
builder.Append(kStateCSSClasses[State()]);
if (MediaElement().IsHTMLVideoElement() &&
!VideoElement().HasAvailableVideoFrame() &&
VideoElement().PosterImageURL().IsEmpty()) {
builder.Append(" ");
builder.Append(kShowDefaultPosterCSSClass);
}
const AtomicString& classes = builder.ToAtomicString();
if (getAttribute("class") != classes) if (getAttribute("class") != classes)
setAttribute("class", classes); setAttribute("class", classes);
} }
...@@ -1526,6 +1538,15 @@ void MediaControlsImpl::MaybeRecordOverflowTimeToAction() { ...@@ -1526,6 +1538,15 @@ void MediaControlsImpl::MaybeRecordOverflowTimeToAction() {
MediaControlOverflowMenuListElement::kTimeToAction); MediaControlOverflowMenuListElement::kTimeToAction);
} }
void MediaControlsImpl::OnLoadedData() {
UpdateCSSClassFromState();
}
HTMLVideoElement& MediaControlsImpl::VideoElement() {
DCHECK(MediaElement().IsHTMLVideoElement());
return *ToHTMLVideoElement(&MediaElement());
}
void MediaControlsImpl::Trace(blink::Visitor* visitor) { void MediaControlsImpl::Trace(blink::Visitor* visitor) {
visitor->Trace(element_mutation_callback_); visitor->Trace(element_mutation_callback_);
visitor->Trace(resize_observer_); visitor->Trace(resize_observer_);
......
...@@ -36,6 +36,7 @@ ...@@ -36,6 +36,7 @@
namespace blink { namespace blink {
class Event; class Event;
class HTMLVideoElement;
class MediaControlsMediaEventListener; class MediaControlsMediaEventListener;
class MediaControlsOrientationLockDelegate; class MediaControlsOrientationLockDelegate;
class MediaControlsRotateToFullscreenDelegate; class MediaControlsRotateToFullscreenDelegate;
...@@ -162,6 +163,10 @@ class MODULES_EXPORT MediaControlsImpl final : public HTMLDivElement, ...@@ -162,6 +163,10 @@ class MODULES_EXPORT MediaControlsImpl final : public HTMLDivElement,
// Update the CSS class when we think the state has updated. // Update the CSS class when we think the state has updated.
void UpdateCSSClassFromState(); void UpdateCSSClassFromState();
// Get the HTMLVideoElement that the controls are attached to. The caller must
// check that the element is a video element first.
HTMLVideoElement& VideoElement();
// Track the state of the controls. // Track the state of the controls.
enum ControlsState { enum ControlsState {
// There is no video source. // There is no video source.
...@@ -259,6 +264,7 @@ class MODULES_EXPORT MediaControlsImpl final : public HTMLDivElement, ...@@ -259,6 +264,7 @@ class MODULES_EXPORT MediaControlsImpl final : public HTMLDivElement,
void OnMediaKeyboardEvent(Event* event) { DefaultEventHandler(event); } void OnMediaKeyboardEvent(Event* event) { DefaultEventHandler(event); }
void OnWaiting(); void OnWaiting();
void OnLoadingProgress(); void OnLoadingProgress();
void OnLoadedData();
// Media control elements. // Media control elements.
Member<MediaControlOverlayEnclosureElement> overlay_enclosure_; Member<MediaControlOverlayEnclosureElement> overlay_enclosure_;
......
...@@ -40,6 +40,7 @@ void MediaControlsMediaEventListener::Attach() { ...@@ -40,6 +40,7 @@ void MediaControlsMediaEventListener::Attach() {
GetMediaElement().addEventListener(EventTypeNames::keyup, this, false); GetMediaElement().addEventListener(EventTypeNames::keyup, this, false);
GetMediaElement().addEventListener(EventTypeNames::waiting, this, false); GetMediaElement().addEventListener(EventTypeNames::waiting, this, false);
GetMediaElement().addEventListener(EventTypeNames::progress, this, false); GetMediaElement().addEventListener(EventTypeNames::progress, this, false);
GetMediaElement().addEventListener(EventTypeNames::loadeddata, this, false);
// Listen to two different fullscreen events in order to make sure the new and // Listen to two different fullscreen events in order to make sure the new and
// old APIs are handled. // old APIs are handled.
...@@ -172,6 +173,10 @@ void MediaControlsMediaEventListener::handleEvent( ...@@ -172,6 +173,10 @@ void MediaControlsMediaEventListener::handleEvent(
media_controls_->OnLoadingProgress(); media_controls_->OnLoadingProgress();
return; return;
} }
if (event->type() == EventTypeNames::loadeddata) {
media_controls_->OnLoadedData();
return;
}
// Fullscreen handling. // Fullscreen handling.
if (event->type() == EventTypeNames::fullscreenchange || if (event->type() == EventTypeNames::fullscreenchange ||
......
...@@ -490,3 +490,17 @@ audio::-webkit-media-controls-mute-button, ...@@ -490,3 +490,17 @@ audio::-webkit-media-controls-mute-button,
audio::-internal-media-controls-overflow-button { audio::-internal-media-controls-overflow-button {
flex: 0 0 32px; flex: 0 0 32px;
} }
/**
* Preload state
*/
.use-default-poster {
background: #F1F3F4;
}
.state-no-source input[pseudo="-webkit-media-controls-overlay-play-button" i],
.use-default-poster div[pseudo="-internal-media-controls-button-panel" i],
.use-default-poster input[pseudo="-webkit-media-controls-timeline" i] {
display: none;
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment