blob: f4f894242353df980bbb48468bff483d7c23ca87 [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "core/html/media/MediaCustomControlsFullscreenDetector.h"
#include "core/dom/events/Event.h"
#include "core/fullscreen/Fullscreen.h"
#include "core/html/media/HTMLVideoElement.h"
#include "core/layout/IntersectionGeometry.h"
#include "platform/runtime_enabled_features.h"
#include "public/platform/TaskType.h"
#include "third_party/WebKit/public/platform/WebFullscreenVideoStatus.h"
namespace blink {
namespace {
constexpr double kCheckFullscreenIntervalSeconds = 1.0f;
constexpr float kMostlyFillViewportThresholdOfOccupationProportion = 0.85f;
constexpr float kMostlyFillViewportThresholdOfVisibleProportion = 0.75f;
} // anonymous namespace
MediaCustomControlsFullscreenDetector::MediaCustomControlsFullscreenDetector(
HTMLVideoElement& video)
: EventListener(kCPPEventListenerType),
video_element_(video),
check_viewport_intersection_timer_(
video.GetDocument().GetTaskRunner(TaskType::kUnthrottled),
this,
&MediaCustomControlsFullscreenDetector::
OnCheckViewportIntersectionTimerFired) {
if (VideoElement().isConnected())
Attach();
}
bool MediaCustomControlsFullscreenDetector::operator==(
const EventListener& other) const {
return this == &other;
}
void MediaCustomControlsFullscreenDetector::Attach() {
VideoElement().addEventListener(EventTypeNames::loadedmetadata, this, true);
VideoElement().GetDocument().addEventListener(
EventTypeNames::webkitfullscreenchange, this, true);
VideoElement().GetDocument().addEventListener(
EventTypeNames::fullscreenchange, this, true);
}
void MediaCustomControlsFullscreenDetector::Detach() {
VideoElement().removeEventListener(EventTypeNames::loadedmetadata, this,
true);
VideoElement().GetDocument().removeEventListener(
EventTypeNames::webkitfullscreenchange, this, true);
VideoElement().GetDocument().removeEventListener(
EventTypeNames::fullscreenchange, this, true);
check_viewport_intersection_timer_.Stop();
if (VideoElement().GetWebMediaPlayer()) {
VideoElement().GetWebMediaPlayer()->SetIsEffectivelyFullscreen(
blink::WebFullscreenVideoStatus::kNotEffectivelyFullscreen);
}
}
bool MediaCustomControlsFullscreenDetector::ComputeIsDominantVideoForTests(
const IntRect& target_rect,
const IntRect& root_rect,
const IntRect& intersection_rect) {
if (target_rect.IsEmpty() || root_rect.IsEmpty())
return false;
const float x_occupation_proportion =
1.0f * intersection_rect.Width() / root_rect.Width();
const float y_occupation_proportion =
1.0f * intersection_rect.Height() / root_rect.Height();
// If the viewport is mostly occupied by the video, return true.
if (std::min(x_occupation_proportion, y_occupation_proportion) >=
kMostlyFillViewportThresholdOfOccupationProportion) {
return true;
}
// If neither of the dimensions of the viewport is mostly occupied by the
// video, return false.
if (std::max(x_occupation_proportion, y_occupation_proportion) <
kMostlyFillViewportThresholdOfOccupationProportion) {
return false;
}
// If the video is mostly visible in the indominant dimension, return true.
// Otherwise return false.
if (x_occupation_proportion > y_occupation_proportion) {
return target_rect.Height() *
kMostlyFillViewportThresholdOfVisibleProportion <
intersection_rect.Height();
}
return target_rect.Width() * kMostlyFillViewportThresholdOfVisibleProportion <
intersection_rect.Width();
}
void MediaCustomControlsFullscreenDetector::handleEvent(
ExecutionContext* context,
Event* event) {
DCHECK(event->type() == EventTypeNames::loadedmetadata ||
event->type() == EventTypeNames::webkitfullscreenchange ||
event->type() == EventTypeNames::fullscreenchange);
// Video is not loaded yet.
if (VideoElement().getReadyState() < HTMLMediaElement::kHaveMetadata)
return;
if (!VideoElement().isConnected() || !IsVideoOrParentFullscreen()) {
check_viewport_intersection_timer_.Stop();
if (VideoElement().GetWebMediaPlayer()) {
VideoElement().GetWebMediaPlayer()->SetIsEffectivelyFullscreen(
blink::WebFullscreenVideoStatus::kNotEffectivelyFullscreen);
}
return;
}
check_viewport_intersection_timer_.StartOneShot(
kCheckFullscreenIntervalSeconds, FROM_HERE);
}
void MediaCustomControlsFullscreenDetector::ContextDestroyed() {
// This method is called by HTMLVideoElement when it observes context destroy.
// The reason is that when HTMLMediaElement observes context destroy, it will
// destroy webMediaPlayer() thus the final
// setIsEffectivelyFullscreen(kNotEffectivelyFullscreen) is not called.
Detach();
}
void MediaCustomControlsFullscreenDetector::
OnCheckViewportIntersectionTimerFired(TimerBase*) {
DCHECK(IsVideoOrParentFullscreen());
IntersectionGeometry geometry(nullptr, VideoElement(), Vector<Length>(),
true);
geometry.ComputeGeometry();
bool is_dominant = ComputeIsDominantVideoForTests(
geometry.TargetIntRect(), geometry.RootIntRect(),
geometry.IntersectionIntRect());
if (!VideoElement().GetWebMediaPlayer())
return;
if (!is_dominant) {
VideoElement().GetWebMediaPlayer()->SetIsEffectivelyFullscreen(
blink::WebFullscreenVideoStatus::kNotEffectivelyFullscreen);
return;
}
// Picture-in-Picture can be disabled by the website when the API is enabled.
bool picture_in_picture_allowed =
!RuntimeEnabledFeatures::PictureInPictureEnabled() &&
!VideoElement().FastHasAttribute(HTMLNames::disablepictureinpictureAttr);
if (picture_in_picture_allowed) {
VideoElement().GetWebMediaPlayer()->SetIsEffectivelyFullscreen(
blink::WebFullscreenVideoStatus::kFullscreenAndPictureInPictureEnabled);
} else {
VideoElement().GetWebMediaPlayer()->SetIsEffectivelyFullscreen(
blink::WebFullscreenVideoStatus::
kFullscreenAndPictureInPictureDisabled);
}
}
bool MediaCustomControlsFullscreenDetector::IsVideoOrParentFullscreen() {
Element* fullscreen_element =
Fullscreen::FullscreenElementFrom(VideoElement().GetDocument());
if (!fullscreen_element)
return false;
return fullscreen_element->contains(&VideoElement());
}
void MediaCustomControlsFullscreenDetector::Trace(blink::Visitor* visitor) {
EventListener::Trace(visitor);
visitor->Trace(video_element_);
}
} // namespace blink