| // 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 |