blob: 02e67c13881fffab0c02eae291bfec4183b120cc [file] [log] [blame]
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/gpu/android/android_video_surface_chooser_impl.h"
#include <memory>
#include "base/time/default_tick_clock.h"
namespace media {
// Minimum time that we require after a failed overlay attempt before we'll try
// again for an overlay.
constexpr base::TimeDelta MinimumDelayAfterFailedOverlay = base::Seconds(5);
AndroidVideoSurfaceChooserImpl::AndroidVideoSurfaceChooserImpl(
bool allow_dynamic,
const base::TickClock* tick_clock)
: allow_dynamic_(allow_dynamic), tick_clock_(tick_clock) {
// Use a DefaultTickClock if one wasn't provided.
if (!tick_clock_)
tick_clock_ = base::DefaultTickClock::GetInstance();
}
AndroidVideoSurfaceChooserImpl::~AndroidVideoSurfaceChooserImpl() {}
void AndroidVideoSurfaceChooserImpl::SetClientCallbacks(
UseOverlayCB use_overlay_cb,
UseTextureOwnerCB use_texture_owner_cb) {
DCHECK(use_overlay_cb && use_texture_owner_cb);
use_overlay_cb_ = std::move(use_overlay_cb);
use_texture_owner_cb_ = std::move(use_texture_owner_cb);
}
void AndroidVideoSurfaceChooserImpl::UpdateState(
absl::optional<AndroidOverlayFactoryCB> new_factory,
const State& new_state) {
DCHECK(use_overlay_cb_);
bool entered_fullscreen =
!current_state_.is_fullscreen && new_state.is_fullscreen;
current_state_ = new_state;
bool factory_changed = new_factory.has_value();
if (factory_changed)
overlay_factory_ = std::move(*new_factory);
if (!allow_dynamic_) {
if (!initial_state_received_) {
initial_state_received_ = true;
// Choose here so that Choose() doesn't have to handle non-dynamic.
// Note that we ignore |is_expecting_relayout| here, since it's transient.
// We don't want to pick TextureOwner permanently for that.
if (overlay_factory_ &&
((current_state_.is_fullscreen &&
!current_state_.promote_secure_only) ||
current_state_.is_secure || current_state_.is_required) &&
current_state_.video_rotation == VIDEO_ROTATION_0) {
SwitchToOverlay(false);
} else {
SwitchToTextureOwner();
}
}
return;
}
// If we're entering fullscreen, clear any previous failure attempt. It's
// likely that any previous failure was due to a lack of power efficiency,
// but entering fs likely changes that anyway.
if (entered_fullscreen)
most_recent_overlay_failure_ = base::TimeTicks();
// If the factory changed, we should cancel pending overlay requests and
// set the client state back to Unknown if they're using an old overlay.
if (factory_changed) {
overlay_ = nullptr;
if (client_overlay_state_ == kUsingOverlay)
client_overlay_state_ = kUnknown;
}
Choose();
}
void AndroidVideoSurfaceChooserImpl::Choose() {
// Pre-M we shouldn't be called.
DCHECK(allow_dynamic_);
// TODO(liberato): should this depend on resolution?
OverlayState new_overlay_state =
current_state_.promote_secure_only ? kUsingTextureOwner : kUsingOverlay;
// Do we require a power-efficient overlay?
bool needs_power_efficient = true;
// Try to use an overlay if possible for protected content. If the compositor
// won't promote, though, it's okay if we switch out. Set |is_required| in
// addition, if you don't want this behavior.
if (current_state_.is_secure) {
new_overlay_state = kUsingOverlay;
// Don't un-promote if not power efficient. If we did, then inline playback
// would likely not promote.
needs_power_efficient = false;
}
// If the compositor won't promote, then don't.
if (!current_state_.is_compositor_promotable)
new_overlay_state = kUsingTextureOwner;
// If we're PIP'd, then don't use an overlay unless it is required. It isn't
// positioned exactly right in some cases (crbug.com/917984).
if (current_state_.is_persistent_video)
new_overlay_state = kUsingTextureOwner;
// If we're expecting a relayout, then don't transition to overlay if we're
// not already in one. We don't want to transition out, though. This lets us
// delay entering on a fullscreen transition until blink relayout is complete.
// TODO(liberato): Detect this more directly.
if (current_state_.is_expecting_relayout &&
client_overlay_state_ != kUsingOverlay)
new_overlay_state = kUsingTextureOwner;
// If we're requesting an overlay, check that we haven't asked too recently
// since the last failure. This includes L1. We don't bother to check for
// our current state, since using an overlay would imply that our most recent
// failure was long ago enough to pass this check earlier.
if (new_overlay_state == kUsingOverlay) {
base::TimeDelta time_since_last_failure =
tick_clock_->NowTicks() - most_recent_overlay_failure_;
if (time_since_last_failure < MinimumDelayAfterFailedOverlay)
new_overlay_state = kUsingTextureOwner;
}
// If an overlay is required, then choose one. The only way we won't is if we
// don't have a factory or our request fails, or if it's rotated.
if (current_state_.is_required) {
new_overlay_state = kUsingOverlay;
// Required overlays don't need to be power efficient.
needs_power_efficient = false;
}
// Specifying a rotated overlay can NOTREACHED() in the compositor, so it's
// better to fail.
if (current_state_.video_rotation != VIDEO_ROTATION_0)
new_overlay_state = kUsingTextureOwner;
// If we have no factory, then we definitely don't want to use overlays.
if (!overlay_factory_)
new_overlay_state = kUsingTextureOwner;
if (current_state_.always_use_texture_owner)
new_overlay_state = kUsingTextureOwner;
// Make sure that we're in |new_overlay_state_|.
if (new_overlay_state == kUsingTextureOwner)
SwitchToTextureOwner();
else
SwitchToOverlay(needs_power_efficient);
}
void AndroidVideoSurfaceChooserImpl::SwitchToTextureOwner() {
// Invalidate any outstanding deletion callbacks for any overlays that we've
// provided to the client already. We assume that it will eventually drop
// them in response to the callback. Ready / failed callbacks aren't affected
// by this, since we own the overlay until those occur. We're about to
// drop |overlay_|, if we have one, which cancels them.
weak_factory_.InvalidateWeakPtrs();
// Cancel any outstanding overlay request, in case we're switching to overlay.
if (overlay_)
overlay_ = nullptr;
// Notify the client to switch if it's in the wrong state.
if (client_overlay_state_ != kUsingTextureOwner) {
DCHECK(use_texture_owner_cb_);
client_overlay_state_ = kUsingTextureOwner;
use_texture_owner_cb_.Run();
}
}
void AndroidVideoSurfaceChooserImpl::SwitchToOverlay(
bool needs_power_efficient) {
// If there's already an overlay request outstanding, then do nothing. We'll
// finish switching when it completes.
// TODO(liberato): If the power efficient flag for |overlay_| doesn't match
// |needs_power_efficient|, then we should cancel it anyway. In practice,
// this doesn't happen, so we ignore it.
if (overlay_)
return;
// Do nothing if the client is already using an overlay. Note that if one
// changes overlay factories, then this might not be true; an overlay from the
// old factory is not the same as an overlay from the new factory. However,
// we assume that ReplaceOverlayFactory handles that.
if (client_overlay_state_ == kUsingOverlay)
return;
// We don't modify |client_overlay_state_| yet, since we don't call the client
// back yet.
// Invalidate any outstanding callbacks. This is needed for the deletion
// callback, since for ready/failed callbacks, we still have ownership of the
// object. If we delete the object, then callbacks are cancelled anyway.
// We also don't want to receive the power efficient callback.
weak_factory_.InvalidateWeakPtrs();
AndroidOverlayConfig config;
// We bind all of our callbacks with weak ptrs, since we don't know how long
// the client will hold on to overlays. They could, in principle, show up
// long after the client is destroyed too, if codec destruction hangs.
config.ready_cb =
base::BindOnce(&AndroidVideoSurfaceChooserImpl::OnOverlayReady,
weak_factory_.GetWeakPtr());
config.failed_cb =
base::BindOnce(&AndroidVideoSurfaceChooserImpl::OnOverlayFailed,
weak_factory_.GetWeakPtr());
config.rect = current_state_.initial_position;
config.secure = current_state_.is_secure;
// Request power efficient overlays and callbacks if we're supposed to.
config.power_efficient = needs_power_efficient;
config.power_cb = base::BindRepeating(
&AndroidVideoSurfaceChooserImpl::OnPowerEfficientState,
weak_factory_.GetWeakPtr());
overlay_ = overlay_factory_.Run(std::move(config));
if (!overlay_)
SwitchToTextureOwner();
}
void AndroidVideoSurfaceChooserImpl::OnOverlayReady(AndroidOverlay* overlay) {
// |overlay_| is the only overlay for which we haven't gotten a ready callback
// back yet.
DCHECK_EQ(overlay, overlay_.get());
// Notify the overlay that we'd like to know if it's destroyed, so that we can
// update our internal state if the client drops it without being told.
overlay_->AddOverlayDeletedCallback(
base::BindOnce(&AndroidVideoSurfaceChooserImpl::OnOverlayDeleted,
weak_factory_.GetWeakPtr()));
client_overlay_state_ = kUsingOverlay;
use_overlay_cb_.Run(std::move(overlay_));
}
void AndroidVideoSurfaceChooserImpl::OnOverlayFailed(AndroidOverlay* overlay) {
// We shouldn't get a failure for any overlay except the incoming one.
DCHECK_EQ(overlay, overlay_.get());
overlay_ = nullptr;
most_recent_overlay_failure_ = tick_clock_->NowTicks();
// If the client isn't already using a TextureOwner, then switch to it.
// Note that this covers the case of kUnknown, when we might not have told the
// client anything yet. That's important for Initialize, so that a failed
// overlay request still results in some callback to the client to know what
// surface to start with.
SwitchToTextureOwner();
}
void AndroidVideoSurfaceChooserImpl::OnOverlayDeleted(AndroidOverlay* overlay) {
client_overlay_state_ = kUsingTextureOwner;
// We don't call SwitchToTextureOwner since the client dropped the overlay.
// It's already using TextureOwner.
}
void AndroidVideoSurfaceChooserImpl::OnPowerEfficientState(
AndroidOverlay* overlay,
bool is_power_efficient) {
// We cannot receive this before OnSurfaceReady, since that is the first
// callback if it arrives. Getting a new overlay clears any previous cbs.
DCHECK(!overlay_);
// We cannot receive it after switching to TextureOwner, since that also
// clears all callbacks.
DCHECK(client_overlay_state_ == kUsingOverlay);
// If the overlay has become power efficient, then take no action.
if (is_power_efficient)
return;
// If the overlay is now required, then keep it. It might have become
// required since we requested it.
if (current_state_.is_required)
return;
// If we're not able to switch dynamically, then keep the overlay.
if (!allow_dynamic_)
return;
// We could set the failure timer here, but we don't mostly for fullscreen.
// We don't want to delay transitioning to an overlay if the user re-enters
// fullscreen. TODO(liberato): Perhaps we should just clear the failure timer
// if we detect a transition into fs when we get new state from the client.
SwitchToTextureOwner();
}
} // namespace media