chromium/chromecast/renderer/cast_content_renderer_client.cc

// Copyright 2014 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/renderer/cast_content_renderer_client.h"

#include <optional>
#include <utility>

#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/strings/string_number_conversions.h"
#include "base/task/sequenced_task_runner.h"
#include "build/build_config.h"
#include "chromecast/base/bitstream_audio_codecs.h"
#include "chromecast/base/cast_features.h"
#include "chromecast/base/chromecast_switches.h"
#include "chromecast/common/cors_exempt_headers.h"
#include "chromecast/crash/app_state_tracker.h"
#include "chromecast/media/base/media_codec_support.h"
#include "chromecast/media/base/supported_codec_profile_levels_memo.h"
#include "chromecast/public/media/media_capabilities_shlib.h"
#include "chromecast/renderer/cast_url_loader_throttle_provider.h"
#include "chromecast/renderer/cast_websocket_handshake_throttle_provider.h"
#include "chromecast/renderer/media/key_systems_cast.h"
#include "chromecast/renderer/media/media_caps_observer_impl.h"
#include "components/cast_receiver/renderer/public/content_renderer_client_mixins.h"
#include "components/media_control/renderer/media_playback_options.h"
#include "components/network_hints/renderer/web_prescient_networking_impl.h"
#include "components/on_load_script_injector/renderer/on_load_script_injector.h"
#include "components/url_rewrite/common/url_request_rewrite_rules.h"
#include "content/public/common/content_switches.h"
#include "content/public/renderer/render_frame.h"
#include "content/public/renderer/render_thread.h"
#include "media/base/audio_parameters.h"
#include "media/base/key_system_info.h"
#include "media/base/media.h"
#include "media/base/remoting_constants.h"
#include "media/remoting/receiver_controller.h"
#include "media/remoting/stream_provider.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "services/network/public/cpp/is_potentially_trustworthy.h"
#include "third_party/blink/public/platform/browser_interface_broker_proxy.h"
#include "third_party/blink/public/platform/web_runtime_features.h"
#include "third_party/blink/public/web/web_frame_widget.h"
#include "third_party/blink/public/web/web_security_policy.h"
#include "third_party/blink/public/web/web_settings.h"
#include "third_party/blink/public/web/web_view.h"

#if BUILDFLAG(IS_ANDROID)
#include "base/android/bundle_utils.h"
#include "chromecast/media/audio/cast_audio_device_factory.h"
#include "components/cdm/renderer/key_system_support_update.h"
#include "media/base/android/media_codec_util.h"
#else
#include "chromecast/renderer/memory_pressure_observer_impl.h"
#endif  // BUILDFLAG(IS_ANDROID)

namespace chromecast {
namespace shell {
namespace {
bool IsSupportedBitstreamAudioCodecHelper(::media::AudioCodec codec, int mask) {
  return (codec == ::media::AudioCodec::kAC3 &&
          (kBitstreamAudioCodecAc3 & mask)) ||
         (codec == ::media::AudioCodec::kEAC3 &&
          (kBitstreamAudioCodecEac3 & mask)) ||
         (codec == ::media::AudioCodec::kDTS &&
          (kBitstreamAudioCodecDts & mask)) ||
         (codec == ::media::AudioCodec::kDTSXP2 &&
          (kBitstreamAudioCodecDtsXP2 & mask)) ||
         (codec == ::media::AudioCodec::kMpegHAudio &&
          (kBitstreamAudioCodecMpegHAudio & mask));
}
}  // namespace

#if BUILDFLAG(IS_ANDROID)
// Audio renderer algorithm maximum capacity. 5s buffer is already large enough,
// we don't need a larger capacity. Otherwise audio renderer will double the
// buffer size when underrun happens, which will cause the playback paused to
// wait long time for enough buffers.
constexpr base::TimeDelta kAudioRendererMaxCapacity = base::Seconds(5);
// Audio renderer algorithm starting capacity.  Configure large enough to
// prevent underrun.
constexpr base::TimeDelta kAudioRendererStartingCapacity = base::Seconds(5);
constexpr base::TimeDelta kAudioRendererStartingCapacityEncrypted =
    base::Seconds(5);
#endif  // BUILDFLAG(IS_ANDROID)

CastContentRendererClient::CastContentRendererClient()
    : cast_receiver_mixins_(cast_receiver::ContentRendererClientMixins::Create(
          base::BindRepeating(&IsCorsExemptHeader))),
      supported_profiles_(
          std::make_unique<media::SupportedCodecProfileLevelsMemo>()),
      activity_url_filter_manager_(
          std::make_unique<CastActivityUrlFilterManager>()) {
#if BUILDFLAG(IS_ANDROID)
  // Registers a custom content::AudioDeviceFactory
  cast_audio_device_factory_ =
      std::make_unique<media::CastAudioDeviceFactory>();
#endif  // BUILDFLAG(IS_ANDROID)
}

CastContentRendererClient::~CastContentRendererClient() = default;

void CastContentRendererClient::RenderThreadStarted() {
  // Register as observer for media capabilities
  content::RenderThread* thread = content::RenderThread::Get();
  mojo::Remote<media::mojom::MediaCaps> media_caps;
  thread->BindHostReceiver(media_caps.BindNewPipeAndPassReceiver());
  mojo::PendingRemote<media::mojom::MediaCapsObserver> proxy;
  media_caps_observer_.reset(
      new media::MediaCapsObserverImpl(&proxy, supported_profiles_.get()));
  media_caps->AddObserver(std::move(proxy));

#if !BUILDFLAG(IS_ANDROID)
  // Register to observe memory pressure changes
  mojo::Remote<chromecast::mojom::MemoryPressureController>
      memory_pressure_controller;
  thread->BindHostReceiver(
      memory_pressure_controller.BindNewPipeAndPassReceiver());
  mojo::PendingRemote<chromecast::mojom::MemoryPressureObserver>
      memory_pressure_proxy;
  memory_pressure_observer_.reset(
      new MemoryPressureObserverImpl(&memory_pressure_proxy));
  memory_pressure_controller->AddObserver(std::move(memory_pressure_proxy));
#endif

  base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();

  std::string last_launched_app =
      command_line->GetSwitchValueNative(switches::kLastLaunchedApp);
  if (!last_launched_app.empty())
    AppStateTracker::SetLastLaunchedApp(last_launched_app);

  std::string previous_app =
      command_line->GetSwitchValueNative(switches::kPreviousApp);
  if (!previous_app.empty())
    AppStateTracker::SetPreviousApp(previous_app);
}

void CastContentRendererClient::RenderFrameCreated(
    content::RenderFrame* render_frame) {
  DCHECK(render_frame);

  cast_receiver_mixins_->RenderFrameCreated(*render_frame);

  // Lifetime is tied to |render_frame| via content::RenderFrameObserver.
  if (render_frame->IsMainFrame()) {
    main_frame_feature_manager_on_associated_interface_ =
        new FeatureManagerOnAssociatedInterface(render_frame);
  } else {
    new FeatureManagerOnAssociatedInterface(render_frame);
  }

  if (!app_media_capabilities_observer_receiver_.is_bound()) {
    mojo::Remote<mojom::ApplicationMediaCapabilities> app_media_capabilities;
    render_frame->GetBrowserInterfaceBroker().GetInterface(
        app_media_capabilities.BindNewPipeAndPassReceiver());
    app_media_capabilities->AddObserver(
        app_media_capabilities_observer_receiver_.BindNewPipeAndPassRemote());
  }

  activity_url_filter_manager_->OnRenderFrameCreated(render_frame);
}

void CastContentRendererClient::RunScriptsAtDocumentStart(
    content::RenderFrame* render_frame) {}

void CastContentRendererClient::RunScriptsAtDocumentEnd(
    content::RenderFrame* render_frame) {}

std::unique_ptr<::media::KeySystemSupportRegistration>
CastContentRendererClient::GetSupportedKeySystems(
    content::RenderFrame* render_frame,
    ::media::GetSupportedKeySystemsCB cb) {
#if BUILDFLAG(IS_ANDROID)
  return cdm::GetSupportedKeySystemsUpdates(render_frame,
                                            /*can_persist_data=*/true,
                                            std::move(cb));
#else
  ::media::KeySystemInfos key_systems;
  media::AddChromecastKeySystems(&key_systems,
                                 false /* enable_persistent_license_support */,
                                 false /* enable_playready */);
  std::move(cb).Run(std::move(key_systems));
  return nullptr;
#endif  // BUILDFLAG(IS_ANDROID)
}

bool CastContentRendererClient::IsSupportedAudioType(
    const ::media::AudioType& type) {
#if BUILDFLAG(IS_ANDROID)
  if (type.spatial_rendering)
    return false;

  // No ATV device we know of has (E)AC3 decoder, so it relies on the audio sink
  // device.
  if (type.codec == ::media::AudioCodec::kEAC3) {
    return kBitstreamAudioCodecEac3 &
           supported_bitstream_audio_codecs_info_.codecs;
  }
  if (type.codec == ::media::AudioCodec::kAC3) {
    return kBitstreamAudioCodecAc3 &
           supported_bitstream_audio_codecs_info_.codecs;
  }
  if (type.codec == ::media::AudioCodec::kDTS) {
    return kBitstreamAudioCodecDts &
           supported_bitstream_audio_codecs_info_.codecs;
  }
  if (type.codec == ::media::AudioCodec::kDTSXP2) {
    return kBitstreamAudioCodecDtsXP2 &
           supported_bitstream_audio_codecs_info_.codecs;
  }
  if (type.codec == ::media::AudioCodec::kMpegHAudio) {
    return kBitstreamAudioCodecMpegHAudio &
           supported_bitstream_audio_codecs_info_.codecs;
  }

  return ::media::IsDefaultSupportedAudioType(type);
#else
  if (type.profile == ::media::AudioCodecProfile::kXHE_AAC)
    return false;

  // If the HDMI sink supports bitstreaming the codec, then the vendor backend
  // does not need to support it.
  if (CheckSupportedBitstreamAudioCodec(type.codec, type.spatial_rendering))
    return true;

  media::AudioCodec codec = media::ToCastAudioCodec(type.codec);
  // Cast platform implements software decoding of Opus and FLAC, so only PCM
  // support is necessary in order to support Opus and FLAC.
  if (codec == media::kCodecOpus || codec == media::kCodecFLAC)
    codec = media::kCodecPCM;

  media::AudioConfig cast_audio_config;
  cast_audio_config.codec = codec;
  return media::MediaCapabilitiesShlib::IsSupportedAudioConfig(
      cast_audio_config);
#endif
}

bool CastContentRendererClient::IsSupportedVideoType(
    const ::media::VideoType& type) {
  // TODO(servolk): make use of eotf.

  // TODO(crbug.com/40124585): Check attached screen for support of
  // type.hdr_metadata_type.
  if (type.hdr_metadata_type != ::gfx::HdrMetadataType::kNone) {
    NOTIMPLEMENTED() << "HdrMetadataType support signaling not implemented.";
    return false;
  }

#if BUILDFLAG(IS_ANDROID)
  return supported_profiles_->IsSupportedVideoConfig(
      media::ToCastVideoCodec(type.codec, type.profile),
      media::ToCastVideoProfile(type.profile), type.level);
#else
  return media::MediaCapabilitiesShlib::IsSupportedVideoConfig(
      media::ToCastVideoCodec(type.codec, type.profile),
      media::ToCastVideoProfile(type.profile), type.level);
#endif
}

bool CastContentRendererClient::IsSupportedBitstreamAudioCodec(
    ::media::AudioCodec codec) {
  return IsSupportedBitstreamAudioCodecHelper(
      codec, supported_bitstream_audio_codecs_info_.codecs);
}

bool CastContentRendererClient::CheckSupportedBitstreamAudioCodec(
    ::media::AudioCodec codec,
    bool check_spatial_rendering) {
  if (!IsSupportedBitstreamAudioCodec(codec))
    return false;

  if (!check_spatial_rendering)
    return true;

  return IsSupportedBitstreamAudioCodecHelper(
      codec, supported_bitstream_audio_codecs_info_.spatial_rendering);
}

std::unique_ptr<blink::WebPrescientNetworking>
CastContentRendererClient::CreatePrescientNetworking(
    content::RenderFrame* render_frame) {
  return std::make_unique<network_hints::WebPrescientNetworkingImpl>(
      render_frame);
}

bool CastContentRendererClient::DeferMediaLoad(
    content::RenderFrame* render_frame,
    bool render_frame_has_played_media_before,
    base::OnceClosure closure) {
  return cast_receiver_mixins_->DeferMediaLoad(*render_frame,
                                               std::move(closure));
}

std::unique_ptr<::media::Demuxer>
CastContentRendererClient::OverrideDemuxerForUrl(
    content::RenderFrame* render_frame,
    const GURL& url,
    scoped_refptr<base::SequencedTaskRunner> task_runner) {
  if (render_frame->GetRenderFrameMediaPlaybackOptions()
          .is_remoting_renderer_enabled() &&
      url.SchemeIs(::media::remoting::kRemotingScheme)) {
    return std::make_unique<::media::remoting::StreamProvider>(
        ::media::remoting::ReceiverController::GetInstance(), task_runner);
  }
  return nullptr;
}

bool CastContentRendererClient::IsIdleMediaSuspendEnabled() {
  return false;
}

void CastContentRendererClient::
    SetRuntimeFeaturesDefaultsBeforeBlinkInitialization() {
  // Allow HtmlMediaElement.volume to be greater than 1, for normalization.
  blink::WebRuntimeFeatures::EnableFeatureFromString(
      "MediaElementVolumeGreaterThanOne", true);
  // Settings for ATV (Android defaults are not what we want).
  blink::WebRuntimeFeatures::EnableMediaControlsOverlayPlayButton(false);
}

void CastContentRendererClient::OnSupportedBitstreamAudioCodecsChanged(
    const BitstreamAudioCodecsInfo& info) {
  supported_bitstream_audio_codecs_info_ = info;
}

std::unique_ptr<blink::WebSocketHandshakeThrottleProvider>
CastContentRendererClient::CreateWebSocketHandshakeThrottleProvider() {
  return std::make_unique<CastWebSocketHandshakeThrottleProvider>(
      activity_url_filter_manager_.get());
}

std::unique_ptr<blink::URLLoaderThrottleProvider>
CastContentRendererClient::CreateURLLoaderThrottleProvider(
    blink::URLLoaderThrottleProviderType type) {
  auto throttle_provider = std::make_unique<CastURLLoaderThrottleProvider>(
      type, activity_url_filter_manager());
  return cast_receiver_mixins_->ExtendURLLoaderThrottleProvider(
      std::move(throttle_provider));
}

std::optional<::media::AudioRendererAlgorithmParameters>
CastContentRendererClient::GetAudioRendererAlgorithmParameters(
    ::media::AudioParameters audio_parameters) {
#if BUILDFLAG(IS_ANDROID)
  ::media::AudioRendererAlgorithmParameters parameters;
  parameters.max_capacity = kAudioRendererMaxCapacity;
  parameters.starting_capacity = kAudioRendererStartingCapacity;
  parameters.starting_capacity_for_encrypted =
      kAudioRendererStartingCapacityEncrypted;
  return std::optional<::media::AudioRendererAlgorithmParameters>(parameters);
#else
  return std::nullopt;
#endif
}

}  // namespace shell
}  // namespace chromecast