| Index: media/remoting/remoting_renderer_controller.cc
|
| diff --git a/media/remoting/remoting_renderer_controller.cc b/media/remoting/remoting_renderer_controller.cc
|
| index ab938b98a7b9f08c3e7a9cdb6da2fa791824643e..515e08298ccd8e502f3be587800605d144722036 100644
|
| --- a/media/remoting/remoting_renderer_controller.cc
|
| +++ b/media/remoting/remoting_renderer_controller.cc
|
| @@ -7,6 +7,7 @@
|
| #include "base/bind.h"
|
| #include "base/logging.h"
|
| #include "base/threading/thread_checker.h"
|
| +#include "base/time/time.h"
|
| #include "media/remoting/remoting_cdm_context.h"
|
|
|
| namespace media {
|
| @@ -19,6 +20,7 @@ RemotingRendererController::RemotingRendererController(
|
|
|
| RemotingRendererController::~RemotingRendererController() {
|
| DCHECK(thread_checker_.CalledOnValidThread());
|
| + metrics_recorder_.WillStopSession(remoting::MEDIA_ELEMENT_DESTROYED);
|
| remoting_source_->RemoveClient(this);
|
| }
|
|
|
| @@ -28,6 +30,7 @@ void RemotingRendererController::OnStarted(bool success) {
|
| if (success) {
|
| VLOG(1) << "Remoting started successively.";
|
| if (remote_rendering_started_) {
|
| + metrics_recorder_.DidStartSession();
|
| DCHECK(!switch_renderer_cb_.is_null());
|
| switch_renderer_cb_.Run();
|
| } else {
|
| @@ -36,18 +39,24 @@ void RemotingRendererController::OnStarted(bool success) {
|
| } else {
|
| VLOG(1) << "Failed to start remoting.";
|
| remote_rendering_started_ = false;
|
| + metrics_recorder_.WillStopSession(remoting::START_RACE);
|
| }
|
| }
|
|
|
| void RemotingRendererController::OnSessionStateChanged() {
|
| DCHECK(thread_checker_.CalledOnValidThread());
|
| + UpdateFromSessionState(remoting::SINK_AVAILABLE, remoting::ROUTE_TERMINATED);
|
| +}
|
|
|
| - VLOG(1) << "OnSessionStateChanged: " << remoting_source_->state();
|
| +void RemotingRendererController::UpdateFromSessionState(
|
| + remoting::StartTrigger start_trigger,
|
| + remoting::StopTrigger stop_trigger) {
|
| + VLOG(1) << "UpdateFromSessionState: " << remoting_source_->state();
|
| if (!sink_available_changed_cb_.is_null())
|
| sink_available_changed_cb_.Run(IsRemoteSinkAvailable());
|
|
|
| UpdateInterstitial(base::nullopt);
|
| - UpdateAndMaybeSwitch();
|
| + UpdateAndMaybeSwitch(start_trigger, stop_trigger);
|
| }
|
|
|
| bool RemotingRendererController::IsRemoteSinkAvailable() {
|
| @@ -71,21 +80,45 @@ void RemotingRendererController::OnEnteredFullscreen() {
|
| DCHECK(thread_checker_.CalledOnValidThread());
|
|
|
| is_fullscreen_ = true;
|
| - UpdateAndMaybeSwitch();
|
| + // See notes in OnBecameDominantVisibleContent() for why this is forced:
|
| + is_dominant_content_ = true;
|
| + UpdateAndMaybeSwitch(remoting::ENTERED_FULLSCREEN,
|
| + remoting::UNKNOWN_STOP_TRIGGER);
|
| }
|
|
|
| void RemotingRendererController::OnExitedFullscreen() {
|
| DCHECK(thread_checker_.CalledOnValidThread());
|
|
|
| is_fullscreen_ = false;
|
| - UpdateAndMaybeSwitch();
|
| + // See notes in OnBecameDominantVisibleContent() for why this is forced:
|
| + is_dominant_content_ = false;
|
| + UpdateAndMaybeSwitch(remoting::UNKNOWN_START_TRIGGER,
|
| + remoting::EXITED_FULLSCREEN);
|
| }
|
|
|
| void RemotingRendererController::OnBecameDominantVisibleContent(
|
| bool is_dominant) {
|
| DCHECK(thread_checker_.CalledOnValidThread());
|
| +
|
| + // Two scenarios where "dominance" status mixes with fullscreen transitions:
|
| + //
|
| + // 1. Just before/after entering fullscreen, the element will, of course,
|
| + // become the dominant on-screen content via automatic page layout.
|
| + // 2. Just before/after exiting fullscreen, the element may or may not
|
| + // shrink in size enough to become non-dominant. However, exiting
|
| + // fullscreen was caused by a user action that explicitly indicates a
|
| + // desire to exit remoting, so even if the element is still dominant,
|
| + // remoting should be shut down.
|
| + //
|
| + // Thus, to achieve the desired behaviors, |is_dominant_content_| is force-set
|
| + // in OnEnteredFullscreen() and OnExitedFullscreen(), and changes to it here
|
| + // are ignored while in fullscreen.
|
| + if (is_fullscreen_)
|
| + return;
|
| +
|
| is_dominant_content_ = is_dominant;
|
| - UpdateAndMaybeSwitch();
|
| + UpdateAndMaybeSwitch(remoting::BECAME_DOMINANT_CONTENT,
|
| + remoting::BECAME_AUXILIARY_CONTENT);
|
| }
|
|
|
| void RemotingRendererController::OnSetCdm(CdmContext* cdm_context) {
|
| @@ -97,15 +130,16 @@ void RemotingRendererController::OnSetCdm(CdmContext* cdm_context) {
|
|
|
| remoting_source_->RemoveClient(this);
|
| remoting_source_ = remoting_cdm_context->GetRemotingSource();
|
| - remoting_source_->AddClient(this); // Calls OnSessionStateChanged().
|
| - UpdateAndMaybeSwitch();
|
| + remoting_source_->AddClient(this);
|
| + UpdateFromSessionState(remoting::CDM_READY, remoting::DECRYPTION_ERROR);
|
| }
|
|
|
| void RemotingRendererController::OnRemotePlaybackDisabled(bool disabled) {
|
| DCHECK(thread_checker_.CalledOnValidThread());
|
|
|
| is_remote_playback_disabled_ = disabled;
|
| - UpdateAndMaybeSwitch();
|
| + metrics_recorder_.OnRemotePlaybackDisabled(disabled);
|
| + UpdateAndMaybeSwitch(remoting::ENABLED_BY_PAGE, remoting::DISABLED_BY_PAGE);
|
| }
|
|
|
| void RemotingRendererController::OnSetPoster(const GURL& poster_url) {
|
| @@ -126,7 +160,10 @@ void RemotingRendererController::SetSwitchRendererCallback(
|
| DCHECK(!cb.is_null());
|
|
|
| switch_renderer_cb_ = cb;
|
| - UpdateAndMaybeSwitch();
|
| + // Note: Not calling UpdateAndMaybeSwitch() here since this method should be
|
| + // called during the initialization phase of this RemotingRendererController;
|
| + // and definitely before a whole lot of other things that are needed to
|
| + // trigger a switch.
|
| }
|
|
|
| void RemotingRendererController::SetRemoteSinkAvailableChangedCallback(
|
| @@ -160,7 +197,12 @@ void RemotingRendererController::OnMetadataChanged(
|
| DCHECK(thread_checker_.CalledOnValidThread());
|
|
|
| const gfx::Size old_size = pipeline_metadata_.natural_size;
|
| + const bool was_audio_codec_supported = has_audio() && IsAudioCodecSupported();
|
| + const bool was_video_codec_supported = has_video() && IsVideoCodecSupported();
|
| pipeline_metadata_ = metadata;
|
| + const bool is_audio_codec_supported = has_audio() && IsAudioCodecSupported();
|
| + const bool is_video_codec_supported = has_video() && IsVideoCodecSupported();
|
| + metrics_recorder_.OnPipelineMetadataChanged(metadata);
|
|
|
| is_encrypted_ = false;
|
| if (has_video())
|
| @@ -171,7 +213,23 @@ void RemotingRendererController::OnMetadataChanged(
|
| if (pipeline_metadata_.natural_size != old_size)
|
| UpdateInterstitial(base::nullopt);
|
|
|
| - UpdateAndMaybeSwitch();
|
| + remoting::StartTrigger start_trigger = remoting::UNKNOWN_START_TRIGGER;
|
| + if (!was_audio_codec_supported && is_audio_codec_supported)
|
| + start_trigger = remoting::SUPPORTED_AUDIO_CODEC;
|
| + if (!was_video_codec_supported && is_video_codec_supported) {
|
| + start_trigger = start_trigger == remoting::SUPPORTED_AUDIO_CODEC
|
| + ? remoting::SUPPORTED_AUDIO_AND_VIDEO_CODECS
|
| + : remoting::SUPPORTED_VIDEO_CODEC;
|
| + }
|
| + remoting::StopTrigger stop_trigger = remoting::UNKNOWN_STOP_TRIGGER;
|
| + if (was_audio_codec_supported && !is_audio_codec_supported)
|
| + stop_trigger = remoting::UNSUPPORTED_AUDIO_CODEC;
|
| + if (was_video_codec_supported && !is_video_codec_supported) {
|
| + stop_trigger = stop_trigger == remoting::UNSUPPORTED_AUDIO_CODEC
|
| + ? remoting::UNSUPPORTED_AUDIO_AND_VIDEO_CODECS
|
| + : remoting::UNSUPPORTED_VIDEO_CODEC;
|
| + }
|
| + UpdateAndMaybeSwitch(start_trigger, stop_trigger);
|
| }
|
|
|
| bool RemotingRendererController::IsVideoCodecSupported() {
|
| @@ -222,7 +280,7 @@ void RemotingRendererController::OnPlaying() {
|
| DCHECK(thread_checker_.CalledOnValidThread());
|
|
|
| is_paused_ = false;
|
| - UpdateAndMaybeSwitch();
|
| + UpdateAndMaybeSwitch(remoting::PLAY_COMMAND, remoting::UNKNOWN_STOP_TRIGGER);
|
| }
|
|
|
| void RemotingRendererController::OnPaused() {
|
| @@ -246,12 +304,16 @@ bool RemotingRendererController::ShouldBeRemoting() {
|
| // that the RemotingRendererImpl should be used. In the stopped states,
|
| // RemotingRendererImpl will display an interstitial to notify the user that
|
| // local rendering cannot be resumed.
|
| + //
|
| + // TODO(miu): Revisit this once more of the encrypted-remoting impl is
|
| + // in-place. For example, this will prevent metrics from recording session
|
| + // stop reasons.
|
| return state == RemotingSessionState::SESSION_STARTED ||
|
| state == RemotingSessionState::SESSION_STOPPING ||
|
| state == RemotingSessionState::SESSION_PERMANENTLY_STOPPED;
|
| }
|
|
|
| - if (irregular_playback_detected_)
|
| + if (encountered_renderer_fatal_error_)
|
| return false;
|
|
|
| switch (state) {
|
| @@ -289,7 +351,9 @@ bool RemotingRendererController::ShouldBeRemoting() {
|
| return is_fullscreen_ || is_dominant_content_;
|
| }
|
|
|
| -void RemotingRendererController::UpdateAndMaybeSwitch() {
|
| +void RemotingRendererController::UpdateAndMaybeSwitch(
|
| + remoting::StartTrigger start_trigger,
|
| + remoting::StopTrigger stop_trigger) {
|
| DCHECK(thread_checker_.CalledOnValidThread());
|
|
|
| bool should_be_remoting = ShouldBeRemoting();
|
| @@ -300,7 +364,8 @@ void RemotingRendererController::UpdateAndMaybeSwitch() {
|
| // Only switch to remoting when media is playing. Since the renderer is
|
| // created when video starts loading/playing, receiver will display a black
|
| // screen before video starts playing if switching to remoting when paused.
|
| - // Keep mirroring the video in this case is good for the user experience.
|
| + // Thus, the user experience is improved by not starting remoting until
|
| + // playback resumes.
|
| if (should_be_remoting && is_paused_)
|
| return;
|
|
|
| @@ -314,6 +379,8 @@ void RemotingRendererController::UpdateAndMaybeSwitch() {
|
| switch_renderer_cb_.Run();
|
| return;
|
| }
|
| + DCHECK_NE(start_trigger, remoting::UNKNOWN_START_TRIGGER);
|
| + metrics_recorder_.WillStartSession(start_trigger);
|
| // |switch_renderer_cb_.Run()| will be called after remoting is started
|
| // successfully.
|
| remoting_source_->StartRemoting(this);
|
| @@ -323,6 +390,8 @@ void RemotingRendererController::UpdateAndMaybeSwitch() {
|
| // force-stop the session when remoting has ended; so no need to call
|
| // StopRemoting() from here.
|
| DCHECK(!is_encrypted_);
|
| + DCHECK_NE(stop_trigger, remoting::UNKNOWN_STOP_TRIGGER);
|
| + metrics_recorder_.WillStopSession(stop_trigger);
|
| switch_renderer_cb_.Run();
|
| remoting_source_->StopRemoting(this);
|
| }
|
| @@ -376,29 +445,37 @@ void RemotingRendererController::DownloadPosterImage() {
|
| return;
|
| DCHECK(!poster_url_.is_empty());
|
|
|
| + const base::TimeTicks download_start_time = base::TimeTicks::Now();
|
| download_poster_cb_.Run(
|
| poster_url_,
|
| base::Bind(&RemotingRendererController::OnPosterImageDownloaded,
|
| - weak_factory_.GetWeakPtr(), poster_url_));
|
| + weak_factory_.GetWeakPtr(), poster_url_, download_start_time));
|
| }
|
|
|
| void RemotingRendererController::OnPosterImageDownloaded(
|
| const GURL& download_url,
|
| + base::TimeTicks download_start_time,
|
| const SkBitmap& image) {
|
| DCHECK(thread_checker_.CalledOnValidThread());
|
|
|
| + metrics_recorder_.OnPosterImageDownloaded(
|
| + base::TimeTicks::Now() - download_start_time, !image.drawsNothing());
|
| if (download_url != poster_url_)
|
| return; // The poster image URL has changed during the download.
|
| UpdateInterstitial(image);
|
| }
|
|
|
| -void RemotingRendererController::OnIrregularPlaybackDetected() {
|
| +void RemotingRendererController::OnRendererFatalError(
|
| + remoting::StopTrigger stop_trigger) {
|
| DCHECK(thread_checker_.CalledOnValidThread());
|
|
|
| - if (irregular_playback_detected_)
|
| + // Do not act on errors caused by things like Mojo pipes being closed during
|
| + // shutdown.
|
| + if (!remote_rendering_started_)
|
| return;
|
| - irregular_playback_detected_ = true;
|
| - UpdateAndMaybeSwitch();
|
| +
|
| + encountered_renderer_fatal_error_ = true;
|
| + UpdateAndMaybeSwitch(remoting::UNKNOWN_START_TRIGGER, stop_trigger);
|
| }
|
|
|
| } // namespace media
|
|
|