chromium/chromecast/browser/cast_media_blocker.cc

// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chromecast/browser/cast_media_blocker.h"

#include <utility>

#include "base/logging.h"
#include "chromecast/browser/cast_renderer_block_data.h"
#include "components/media_control/mojom/media_playback_options.mojom.h"
#include "content/public/browser/media_session.h"
#include "content/public/browser/web_contents.h"
#include "mojo/public/cpp/bindings/associated_remote.h"
#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"

namespace chromecast {

CastMediaBlocker::CastMediaBlocker(content::WebContents* web_contents)
    : media_control::MediaBlocker(web_contents),
      media_session_(content::MediaSession::Get(web_contents)) {
  media_session_->AddObserver(observer_receiver_.BindNewPipeAndPassRemote());
}

CastMediaBlocker::~CastMediaBlocker() = default;

void CastMediaBlocker::OnBlockMediaLoadingChanged() {
  UpdatePlayingState();
}

void CastMediaBlocker::BlockMediaStarting(bool blocked) {
  if (media_starting_blocked_ == blocked)
    return;

  media_starting_blocked_ = blocked;

  shell::CastRendererBlockData::SetRendererBlockForWebContents(
      web_contents(), media_starting_blocked_);

  UpdatePlayingState();
}

void CastMediaBlocker::UpdatePlayingState() {
  LOG(INFO) << __FUNCTION__
            << " media_loading_blocked=" << media_loading_blocked()
            << " media_starting_blocked=" << media_starting_blocked_
            << " suspended=" << suspended_ << " controllable=" << controllable_
            << " paused_by_user=" << paused_by_user_;

  // If blocking media, suspend if possible.
  if (PlayingBlocked()) {
    if (!suspended_ && controllable_) {
      Suspend();
    }
    return;
  }

  // If unblocking media, resume if media was not paused by user.
  if (!paused_by_user_ && suspended_ && controllable_) {
    paused_by_user_ = true;
    Resume();
  }
}

void CastMediaBlocker::EnableBackgroundVideoPlayback(bool enabled) {
  if (!web_contents())
    return;

  background_video_playback_enabled_ = enabled;
  UpdateBackgroundVideoPlaybackState();
}

bool CastMediaBlocker::PlayingBlocked() const {
  return (media_loading_blocked() || media_starting_blocked_);
}

void CastMediaBlocker::MediaSessionInfoChanged(
    media_session::mojom::MediaSessionInfoPtr session_info) {
  bool is_suspended = session_info->playback_state ==
                      media_session::mojom::MediaPlaybackState::kPaused;

  LOG(INFO) << __FUNCTION__
            << " media_loading_blocked=" << media_loading_blocked()
            << " media_starting_blocked=" << media_starting_blocked_
            << " is_suspended=" << is_suspended
            << " is_controllable=" << session_info->is_controllable
            << " paused_by_user=" << paused_by_user_;

  // Process controllability first.
  if (controllable_ != session_info->is_controllable) {
    controllable_ = session_info->is_controllable;

    // If not blocked, and we regain control and the media wasn't paused when
    // blocked, resume media if suspended.
    if (!PlayingBlocked() && !paused_by_user_ && is_suspended &&
        controllable_) {
      paused_by_user_ = true;
      Resume();
    }

    // Suspend if blocked and the session becomes controllable.
    if (PlayingBlocked() && !is_suspended && controllable_) {
      // Only suspend if suspended_ doesn't change. Otherwise, this will be
      // handled in the suspended changed block.
      if (suspended_ == is_suspended)
        Suspend();
    }
  }

  // TODO(crbug.com/40120884): Rename suspended to paused to be consistent with
  // MediaSession types.
  // Process suspended state next.
  if (suspended_ != is_suspended) {
    suspended_ = is_suspended;
    // If blocking, suspend media whenever possible.
    if (PlayingBlocked() && !suspended_) {
      // If media was resumed when blocked, the user tried to play music.
      paused_by_user_ = false;
      if (controllable_)
        Suspend();
    }

    // If not blocking, cache the user's play intent.
    if (!PlayingBlocked())
      paused_by_user_ = suspended_;
  }
}

void CastMediaBlocker::Suspend() {
  if (!media_session_)
    return;

  LOG(INFO) << "Suspending media session.";
  media_session_->Suspend(content::MediaSession::SuspendType::kSystem);
}

void CastMediaBlocker::Resume() {
  if (!media_session_)
    return;

  LOG(INFO) << "Resuming media session.";
  media_session_->Resume(content::MediaSession::SuspendType::kSystem);
}

void CastMediaBlocker::OnRenderFrameCreated(
    content::RenderFrameHost* render_frame_host) {
  UpdateRenderFrameBackgroundVideoPlaybackState(render_frame_host);
}

void CastMediaBlocker::UpdateBackgroundVideoPlaybackState() {
  if (!web_contents())
    return;
  web_contents()->ForEachRenderFrameHost(
      [this](content::RenderFrameHost* frame) {
        UpdateRenderFrameBackgroundVideoPlaybackState(frame);
      });
}

void CastMediaBlocker::UpdateRenderFrameBackgroundVideoPlaybackState(
    content::RenderFrameHost* frame) {
  mojo::AssociatedRemote<components::media_control::mojom::MediaPlaybackOptions>
      media_playback_options;
  frame->GetRemoteAssociatedInterfaces()->GetInterface(&media_playback_options);
  media_playback_options->SetBackgroundVideoPlaybackEnabled(
      background_video_playback_enabled_);
}

void CastMediaBlocker::SetMediaSessionForTesting(
    content::MediaSession* media_session) {
  media_session_ = media_session;
}

}  // namespace chromecast